Projects
home:Aloysius:branches:Essentials
gpac
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 2
View file
gpac.changes
Changed
@@ -1,4 +1,77 @@ ------------------------------------------------------------------- +Fri Feb 6 09:42:53 UTC 2026 - Luigi Baldoni <aloisio@gmx.com> + +- Update to version 26.02.0 + GPAC release naming is now changed to year.month scheme. ABI + version of this release is 16.5. + gpac: + * Added mode for testing defered graph linking apps + * `main()` can return filter session error code using -rv + MP4Box: + * Better support for HEIF/AVIF import + * new option 'times' to rewrite timestamps + Core tools: + * Network Capture and Replay using pcap or pcapng, including + looping, loss and reordering simulation + * HTTP/3 support for client and server + * Added libcurl as backend for GPAC downloader + * QuickJS 2025, FFMPEG 8 + * Improbed GFIO (including file deletion) + Media Formats: + * ISOBMFF external tracks support + * Event Message Tracks support + * Improved support SCTE-35, id3, CC, timecodes and other + markers + * Improved HDR signaling support + * IAMF support + * Motion JPEG2000 + * AC-4 support + * AVS3 support + MPEG-DASH & HLS: + * SSR support for L3D low latency, base64 encoding of init + segments + * HLS groups, IV injection + * DASH/HLS: new `segcts` option to derive startNumber from + first packet cts + * mux time prft injection + Remote monitoring: + * A new WebSocket based remote monitoring UI is available for + GPAC + * WebSocket server for JS scripts + Filters: + * DVB-I MABR FLUTE mux and demux + * MABR (ROUTE/FLUTE) HTTP repair support with full or partial + modes + * MABR (ROUTE/FLUTE) on unicast + * mediaserver.js HTTP gateway filter supporting MABR sources + * avmix playlists now accept ipid:// urls to locate input + pids, allowing to specify playlists independently from source + URLs + * ClosedCaptions encoder + * TTML merger + * flist: Playlist piping, DASH perdiod auto-switch signaling + * pin: flush signaling upon broken pipe + * M2TS: USAC support, non real-time NTP injection for TEMI, + real-time regulation option for tssplit and tsgendts + * dvb4linux is back (Terrestrial and Satelite including + dibseqc) + * reframer: time-aligned mode, time discontinuities handling + * nhml: ability to process fragmented streams + * bsrw: timecodes injection and rewriting + * seiloader filter for SEI and AV1 OBUs + * FFMPEG raw protocol support (use gpac for demux and mux) + Emscripten: + * Improved WASM support + * Fixes in UI + Other: + * Improved Wiki: glossary, developer section, ... + * Integration with GStreamer + (https://github.com/gpac/gst-gpac-plugin) + * Introduce unit tests in complement to the testsuite and + various buildbot continuous checks + * Many bug fixes and security patches + +------------------------------------------------------------------- Fri Sep 20 09:58:11 UTC 2024 - Manfred Hollstein <manfred.h@gmx.net> - Add gpac-missing-prototype.patch; uncovered by gcc14 on TW.
View file
gpac.spec
Changed
@@ -1,7 +1,7 @@ # # spec file for package gpac # -# Copyright (c) 2024 Packman Team <packman@links2linux.de> +# Copyright (c) 2026 Packman Team <packman@links2linux.de> # Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties @@ -19,7 +19,7 @@ %define sover 12 Name: gpac -Version: 2.4.0 +Version: 26.02.0 Release: 0 Summary: A multimedia framework covering MPEG-4, VRML/X3D and SVG License: LGPL-2.1-or-later @@ -27,18 +27,18 @@ Url: http://gpac.io Source: https://github.com/gpac/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz #PATCH-FIX-UPSTREAM i@marguerite.su - fix SVGGen ldflags -Patch2: gpac-0.7.1-SVGGen_abuild.patch +###Patch2: gpac-0.7.1-SVGGen_abuild.patch #PATCH-FIX-UPSTREAM wengxuetian@gmail.com - fix E: 64bit-portability-issue -Patch3: gpac-1.0.0-64bit-portability.patch -Patch12: gpac.ssl.patch +###Patch3: gpac-1.0.0-64bit-portability.patch +###Patch12: gpac.ssl.patch #PATCH-FIX-OPENSUSE gpac-rpath.patch aloisio@gmx.com - fix rpath error -Patch13: gpac-rpath.patch +####Patch13: gpac-rpath.patch %if 0%{?suse_version} > 1500 -#PATCH-FIX-OPENSUSE gpac.a52dec.patch manfred.h@gmx.net - add missing parameter to a52_init() -Patch15: gpac.a52dec.patch +###PATCH-FIX-OPENSUSE gpac.a52dec.patch manfred.h@gmx.net - add missing parameter to a52_init() +###Patch15: gpac.a52dec.patch %endif #PATCH-FIX-OPENSUSE gpac-missing-prototype.patch manfred.h@gmx.net - add missing prototype declarations -Patch16: gpac-missing-prototype.patch +###Patch16: gpac-missing-prototype.patch BuildRequires: Mesa-devel BuildRequires: dos2unix BuildRequires: doxygen
View file
gpac-rpath.patch
Changed
@@ -1,26 +1,26 @@ -Index: gpac-2.4.0/applications/gpac/Makefile +Index: gpac-26.02.0/applications/gpac/Makefile =================================================================== ---- gpac-2.4.0.orig/applications/gpac/Makefile -+++ gpac-2.4.0/applications/gpac/Makefile -@@ -58,7 +58,7 @@ ifeq ($(CONFIG_EMSCRIPTEN),yes) +--- gpac-26.02.0.orig/applications/gpac/Makefile ++++ gpac-26.02.0/applications/gpac/Makefile +@@ -59,7 +59,7 @@ ifeq ($(CONFIG_EMSCRIPTEN),yes) else ifeq ($(CONFIG_DARWIN),yes) #LINKFLAGS+= -Wl,-rpath,'@loader_path' else --LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc -+#LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +-LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc ++#LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc endif endif -Index: gpac-2.4.0/applications/mp4box/Makefile +Index: gpac-26.02.0/applications/mp4box/Makefile =================================================================== ---- gpac-2.4.0.orig/applications/mp4box/Makefile -+++ gpac-2.4.0/applications/mp4box/Makefile +--- gpac-26.02.0.orig/applications/mp4box/Makefile ++++ gpac-26.02.0/applications/mp4box/Makefile @@ -24,7 +24,7 @@ ifeq ($(CONFIG_EMSCRIPTEN),yes) else ifeq ($(CONFIG_DARWIN),yes) #LINKFLAGS+= -Wl,-rpath,'@loader_path' else --LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc -+#LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +-LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc ++#LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc endif ifeq ($(STATIC_BUILD),yes)
View file
gpac.ssl.patch
Changed
@@ -1,8 +1,8 @@ -Index: gpac-1.0.0/src/utils/downloader.c +Index: gpac-26.02.0/src/utils/downloader_ssl.c =================================================================== ---- gpac-1.0.0.orig/src/utils/downloader.c -+++ gpac-1.0.0/src/utils/downloader.c -@@ -406,8 +406,7 @@ Bool gf_ssl_init_lib() { +--- gpac-26.02.0.orig/src/utils/downloader_ssl.c ++++ gpac-26.02.0/src/utils/downloader_ssl.c +@@ -93,8 +93,7 @@ Bool gf_ssl_init_lib() #if OPENSSL_VERSION_NUMBER < 0x10100000L SSL_library_init(); SSL_load_error_strings(); @@ -12,18 +12,3 @@ #endif _ssl_is_initialized = GF_TRUE; -@@ -444,13 +443,13 @@ static int ssl_init(GF_DownloadManager * - case 1: - meth = SSLv2_client_method(); - break; --#endif - case 2: - meth = SSLv3_client_method(); - break; - case 3: - meth = TLSv1_client_method(); - break; -+#endif - #else /* for openssl 1.1+ this is the preferred method */ - case 0: - meth = TLS_client_method();
View file
gpac-2.4.0.tar.gz/.github/ISSUE_TEMPLATE.md
Deleted
@@ -1,7 +0,0 @@ -Thanks for reporting your issue. Please make sure these boxes are checked before submitting your issue - thank you! - -- I looked for a similar issue and couldn't find any. -- I tried with the latest version of GPAC. Installers available at https://gpac.io/downloads/gpac-nightly-builds/ -- I give enough information for contributors to reproduce my issue (meaningful title, github labels, platform and compiler, command-line ...). I can share files anonymously with this dropbox: https://www.mediafire.com/filedrop/filedrop_hosted.php?drop=eec9e058a9486fe4e99c33021481d9e1826ca9dbc242a6cfaab0fe95da5e5d95 - -Detailed guidelines: https://gpac.io/bug-reporting/
View file
gpac-2.4.0.tar.gz/.github/workflows/ga.yml
Deleted
@@ -1,35 +0,0 @@ -name: GitHub Actions -run-name: build with docker -on: push - -jobs: - master-build-and-deploy: - runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }} - steps: - - name: Check out code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: build image - run: docker build -t gpac-ubuntu -f build/docker/ubuntu.Dockerfile . - - name: check docker images - run: docker image list - - name: check docker run - run: docker run gpac-ubuntu || true - - name: login docker hub - run: docker login --username gpac --password ${{secrets.DOCKER_HUB_TOKEN}} - - name: tag docker image - run: docker tag gpac-ubuntu gpac/ubuntu:latest - - name: push docker image - run: docker push gpac/ubuntu:latest - - - other-builds: - runs-on: ubuntu-latest - if: ${{ github.ref != 'refs/heads/master' || github.event_name != 'push' }} - steps: - - name: Check out code - uses: actions/checkout@v3 - - name: build image - run: docker build -t gpac-ubuntu -f build/docker/ubuntu.Dockerfile .
View file
gpac-2.4.0.tar.gz/include/gpac/Remotery.h
Deleted
@@ -1,683 +0,0 @@ - - -/* -Copyright 2014-2018 Celtoys Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - - -/* - -Compiling ---------- - -* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include - directories to add Remotery/lib path. The required library ws2_32.lib should be picked - up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c. - -* Mac OS X (XCode) - simply add lib/Remotery.c and lib/Remotery.h to your program. - -* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for - library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c - -I lib -pthread -lm - -You can define some extra macros to modify what features are compiled into Remotery. These are -documented just below this comment. - -*/ - - -#ifndef RMT_INCLUDED_H -#define RMT_INCLUDED_H - -//! @cond Doxygen_Suppress - -// Set to 0 to not include any bits of Remotery in your build -#ifndef RMT_ENABLED -#define RMT_ENABLED 1 -#endif - -// Help performance of the server sending data to the client by marking this machine as little-endian -#ifndef RMT_ASSUME_LITTLE_ENDIAN -#define RMT_ASSUME_LITTLE_ENDIAN 0 -#endif - -// Used by the Celtoys TinyCRT library (not released yet) -#ifndef RMT_USE_TINYCRT -#define RMT_USE_TINYCRT 0 -#endif - -// Assuming CUDA headers/libs are setup, allow CUDA profiling -#ifndef RMT_USE_CUDA -#define RMT_USE_CUDA 0 -#endif - -// Assuming Direct3D 11 headers/libs are setup, allow D3D11 profiling -#ifndef RMT_USE_D3D11 -#define RMT_USE_D3D11 0 -#endif - -// Allow OpenGL profiling -#ifndef RMT_USE_OPENGL -#define RMT_USE_OPENGL 0 -#endif - -// Allow Metal profiling -#ifndef RMT_USE_METAL -#define RMT_USE_METAL 0 -#endif - -// Initially use POSIX thread names to name threads instead of Thread0, 1, ... -#ifndef RMT_USE_POSIX_THREADNAMES -#define RMT_USE_POSIX_THREADNAMES 0 -#endif - -// How many times we spin data back and forth between CPU & GPU -// to calculate average RTT (Roundtrip Time). Cannot be 0. -// Affects OpenGL & D3D11 -#ifndef RMT_GPU_CPU_SYNC_NUM_ITERATIONS -#define RMT_GPU_CPU_SYNC_NUM_ITERATIONS 16 -#endif - -// Time in seconds between each resync to compensate for drifting between GPU & CPU timers, -// effects of power saving, etc. Resyncs can cause stutter, lag spikes, stalls. -// Set to 0 for never. -// Affects OpenGL & D3D11 -#ifndef RMT_GPU_CPU_SYNC_SECONDS -#define RMT_GPU_CPU_SYNC_SECONDS 30 -#endif - -// Whether we should automatically resync if we detect a timer disjoint (e.g. -// changed from AC power to battery, GPU is overheating, or throttling up/down -// due to laptop savings events). Set it to 0 to avoid resync in such events. -// Useful if for some odd reason a driver reports a lot of disjoints. -// Affects D3D11 -#ifndef RMT_D3D11_RESYNC_ON_DISJOINT -#define RMT_D3D11_RESYNC_ON_DISJOINT 1 -#endif - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - Compiler/Platform Detection and Preprocessor Utilities ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -// Platform identification -#if defined(_WINDOWS) || defined(_WIN32) - #define RMT_PLATFORM_WINDOWS -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) - #define RMT_PLATFORM_LINUX - #define RMT_PLATFORM_POSIX -#elif defined(__APPLE__) - #define RMT_PLATFORM_MACOS - #define RMT_PLATFORM_POSIX -#endif - -#ifdef RMT_DLL - #if defined (RMT_PLATFORM_WINDOWS) - #if defined (RMT_IMPL) - #define RMT_API __declspec(dllexport) - #else - #define RMT_API __declspec(dllimport) - #endif - #elif defined (RMT_PLATFORM_POSIX) - #if defined (RMT_IMPL) - #define RMT_API __attribute__((visibility("default"))) - #else - #define RMT_API - #endif - #endif -#else - #define RMT_API -#endif - -// Allows macros to be written that can work around the inability to do: #define(x) #ifdef x -// with the C preprocessor. -#if RMT_ENABLED - #define IFDEF_RMT_ENABLED(t, f) t -#else - #define IFDEF_RMT_ENABLED(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_CUDA - #define IFDEF_RMT_USE_CUDA(t, f) t -#else - #define IFDEF_RMT_USE_CUDA(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_D3D11 - #define IFDEF_RMT_USE_D3D11(t, f) t -#else - #define IFDEF_RMT_USE_D3D11(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_OPENGL - #define IFDEF_RMT_USE_OPENGL(t, f) t -#else - #define IFDEF_RMT_USE_OPENGL(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_METAL - #define IFDEF_RMT_USE_METAL(t, f) t -#else - #define IFDEF_RMT_USE_METAL(t, f) f -#endif - - -// Public interface is written in terms of these macros to easily enable/disable itself -#define RMT_OPTIONAL(macro, x) IFDEF_ ## macro(x, ) -#define RMT_OPTIONAL_RET(macro, x, y) IFDEF_ ## macro(x, (y)) - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - Types ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -// Boolean -typedef unsigned int rmtBool; -#define RMT_TRUE ((rmtBool)1) -#define RMT_FALSE ((rmtBool)0) - - -// Unsigned integer types -typedef unsigned char rmtU8; -typedef unsigned short rmtU16; -typedef unsigned int rmtU32; -typedef unsigned long long rmtU64; - - -// Signed integer types -typedef char rmtS8; -typedef short rmtS16; -typedef int rmtS32; -typedef long long rmtS64; - - -// Const, null-terminated string pointer -typedef const char* rmtPStr; - - -// Handle to the main remotery instance -typedef struct Remotery Remotery; - - -// All possible error codes -typedef enum rmtError -{ - RMT_ERROR_NONE, - RMT_ERROR_RECURSIVE_SAMPLE, // Not an error but an internal message to calling code - - // System errors - RMT_ERROR_MALLOC_FAIL, // Malloc call within remotery failed - RMT_ERROR_TLS_ALLOC_FAIL, // Attempt to allocate thread local storage failed - RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL, // Failed to create a virtual memory mirror buffer - RMT_ERROR_CREATE_THREAD_FAIL, // Failed to create a thread for the server - - // Network TCP/IP socket errors - RMT_ERROR_SOCKET_INIT_NETWORK_FAIL, // Network initialisation failure (e.g. on Win32, WSAStartup fails) - RMT_ERROR_SOCKET_CREATE_FAIL, // Can't create a socket for connection to the remote viewer - RMT_ERROR_SOCKET_BIND_FAIL, // Can't bind a socket for the server - RMT_ERROR_SOCKET_LISTEN_FAIL, // Created server socket failed to enter a listen state - RMT_ERROR_SOCKET_SET_NON_BLOCKING_FAIL, // Created server socket failed to switch to a non-blocking state - RMT_ERROR_SOCKET_INVALID_POLL, // Poll attempt on an invalid socket - RMT_ERROR_SOCKET_SELECT_FAIL, // Server failed to call select on socket - RMT_ERROR_SOCKET_POLL_ERRORS, // Poll notified that the socket has errors - RMT_ERROR_SOCKET_ACCEPT_FAIL, // Server failed to accept connection from client - RMT_ERROR_SOCKET_SEND_TIMEOUT, // Timed out trying to send data - RMT_ERROR_SOCKET_SEND_FAIL, // Unrecoverable error occured while client/server tried to send data - RMT_ERROR_SOCKET_RECV_NO_DATA, // No data available when attempting a receive - RMT_ERROR_SOCKET_RECV_TIMEOUT, // Timed out trying to receive data - RMT_ERROR_SOCKET_RECV_FAILED, // Unrecoverable error occured while client/server tried to receive data - - // WebSocket errors - RMT_ERROR_WEBSOCKET_HANDSHAKE_NOT_GET, // WebSocket server handshake failed, not HTTP GET - RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_VERSION, // WebSocket server handshake failed, can't locate WebSocket version - RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_VERSION, // WebSocket server handshake failed, unsupported WebSocket version - RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_HOST, // WebSocket server handshake failed, can't locate host - RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_HOST, // WebSocket server handshake failed, host is not allowed to connect - RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_KEY, // WebSocket server handshake failed, can't locate WebSocket key - RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_KEY, // WebSocket server handshake failed, WebSocket key is ill-formed - RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL, // WebSocket server handshake failed, internal error, bad string code - RMT_ERROR_WEBSOCKET_DISCONNECTED, // WebSocket server received a disconnect request and closed the socket - RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER, // Couldn't parse WebSocket frame header - RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_SIZE, // Partially received wide frame header size - RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_MASK, // Partially received frame header data mask - RMT_ERROR_WEBSOCKET_RECEIVE_TIMEOUT, // Timeout receiving frame header - - RMT_ERROR_REMOTERY_NOT_CREATED, // Remotery object has not been created - RMT_ERROR_SEND_ON_INCOMPLETE_PROFILE, // An attempt was made to send an incomplete profile tree to the client - - // CUDA error messages - RMT_ERROR_CUDA_DEINITIALIZED, // This indicates that the CUDA driver is in the process of shutting down - RMT_ERROR_CUDA_NOT_INITIALIZED, // This indicates that the CUDA driver has not been initialized with cuInit() or that initialization has failed - RMT_ERROR_CUDA_INVALID_CONTEXT, // This most frequently indicates that there is no context bound to the current thread - RMT_ERROR_CUDA_INVALID_VALUE, // This indicates that one or more of the parameters passed to the API call is not within an acceptable range of values - RMT_ERROR_CUDA_INVALID_HANDLE, // This indicates that a resource handle passed to the API call was not valid - RMT_ERROR_CUDA_OUT_OF_MEMORY, // The API call failed because it was unable to allocate enough memory to perform the requested operation - RMT_ERROR_ERROR_NOT_READY, // This indicates that a resource handle passed to the API call was not valid - - // Direct3D 11 error messages - RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY, // Failed to create query for sample - - // OpenGL error messages - RMT_ERROR_OPENGL_ERROR, // Generic OpenGL error, no need to expose detail since app will need an OpenGL error callback registered - - RMT_ERROR_CUDA_UNKNOWN, -} rmtError; - - -typedef enum rmtSampleFlags -{ - // Default behavior - RMTSF_None = 0, - - // Search parent for same-named samples and merge timing instead of adding a new sample - RMTSF_Aggregate = 1, - - // Merge sample with parent if it's the same sample - RMTSF_Recursive = 2, -} rmtSampleFlags; - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - Public Interface ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -// Can call remotery functions on a null pointer -// TODO: Can embed extern "C" in these macros? - -#define rmt_Settings() \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_Settings(), NULL ) - -#define rmt_CreateGlobalInstance(rmt) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_CreateGlobalInstance(rmt), RMT_ERROR_NONE) - -#define rmt_DestroyGlobalInstance(rmt) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_DestroyGlobalInstance(rmt)) - -#define rmt_SetGlobalInstance(rmt) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SetGlobalInstance(rmt)) - -#define rmt_GetGlobalInstance() \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_GetGlobalInstance(), NULL) - -#define rmt_SetCurrentThreadName(rmt) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SetCurrentThreadName(rmt)) - -#define rmt_LogText(text) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_LogText(text)) - -#define rmt_SendText(text) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SendText(text)) - -#define rmt_EnableSampling(enable) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_EnableSampling(enable)) - -#define rmt_SamplingEnabled() \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SamplingEnabled()) - -#define rmt_BeginCPUSample(name, flags) \ - RMT_OPTIONAL(RMT_ENABLED, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginCPUSample(#name, flags, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginCPUSampleStore(name, flags, hashptr) \ - RMT_OPTIONAL(RMT_ENABLED, { \ - _rmt_BeginCPUSample(name, flags, hashptr); \ - }) - -#define rmt_BeginCPUSampleDynamic(namestr, flags) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_BeginCPUSample(namestr, flags, NULL)) - -#define rmt_EndCPUSample() \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_EndCPUSample()) - - -// Callback function pointer types -typedef void* (*rmtMallocPtr)(void* mm_context, rmtU32 size); -typedef void* (*rmtReallocPtr)(void* mm_context, void* ptr, rmtU32 size); -typedef void (*rmtFreePtr)(void* mm_context, void* ptr); -typedef void (*rmtInputHandlerPtr)(const char* text, void* context); - - -// Struture to fill in to modify Remotery default settings -typedef struct rmtSettings -{ - // Which port to listen for incoming connections on - rmtU16 port; - - // When this server exits it can leave the port open in TIME_WAIT state for - // a while. This forces subsequent server bind attempts to fail when - // restarting. If you find restarts fail repeatedly with bind attempts, set - // this to true to forcibly reuse the open port. - rmtBool reuse_open_port; - - // Only allow connections on localhost? - // For dev builds you may want to access your game from other devices but if - // you distribute a game to your players with Remotery active, probably best - // to limit connections to localhost. - rmtBool limit_connections_to_localhost; - - // How long to sleep between server updates, hopefully trying to give - // a little CPU back to other threads. - rmtU32 msSleepBetweenServerUpdates; - - // Size of the internal message queues Remotery uses - // Will be rounded to page granularity of 64k - rmtU32 messageQueueSizeInBytes; - - // If the user continuously pushes to the message queue, the server network - // code won't get a chance to update unless there's an upper-limit on how - // many messages can be consumed per loop. - rmtU32 maxNbMessagesPerUpdate; - - // Callback pointers for memory allocation - rmtMallocPtr malloc; - rmtReallocPtr realloc; - rmtFreePtr free; - void* mm_context; - - // Callback pointer for receiving input from the Remotery console - rmtInputHandlerPtr input_handler; - - // Context pointer that gets sent to Remotery console callback function - void* input_handler_context; - - rmtPStr logFilename; -} rmtSettings; - - -// Structure to fill in when binding CUDA to Remotery -typedef struct rmtCUDABind -{ - // The main context that all driver functions apply before each call - void* context; - - // Driver API function pointers that need to be pointed to - // Untyped so that the CUDA headers are not required in this file - // NOTE: These are named differently to the CUDA functions because the CUDA API has a habit of using - // macros to point function calls to different versions, e.g. cuEventDestroy is a macro for - // cuEventDestroy_v2. - void* CtxSetCurrent; - void* CtxGetCurrent; - void* EventCreate; - void* EventDestroy; - void* EventRecord; - void* EventQuery; - void* EventElapsedTime; - -} rmtCUDABind; - - -// Call once after you've initialised CUDA to bind it to Remotery -#define rmt_BindCUDA(bind) \ - RMT_OPTIONAL(RMT_USE_CUDA, _rmt_BindCUDA(bind)) - -// Mark the beginning of a CUDA sample on the specified asynchronous stream -#define rmt_BeginCUDASample(name, stream) \ - RMT_OPTIONAL(RMT_USE_CUDA, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginCUDASample(#name, &rmt_sample_hash_##name, stream); \ - }) - -// Mark the end of a CUDA sample on the specified asynchronous stream -#define rmt_EndCUDASample(stream) \ - RMT_OPTIONAL(RMT_USE_CUDA, _rmt_EndCUDASample(stream)) - - -#define rmt_BindD3D11(device, context) \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BindD3D11(device, context)) - -#define rmt_UnbindD3D11() \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_UnbindD3D11()) - -#define rmt_BeginD3D11Sample(name) \ - RMT_OPTIONAL(RMT_USE_D3D11, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginD3D11Sample(#name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginD3D11SampleDynamic(namestr) \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BeginD3D11Sample(namestr, NULL)) - -#define rmt_EndD3D11Sample() \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_EndD3D11Sample()) - - -#define rmt_BindOpenGL() \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BindOpenGL()) - -#define rmt_UnbindOpenGL() \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_UnbindOpenGL()) - -#define rmt_BeginOpenGLSample(name) \ - RMT_OPTIONAL(RMT_USE_OPENGL, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginOpenGLSample(#name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginOpenGLSampleStore(name, hashptr) \ - RMT_OPTIONAL(RMT_USE_OPENGL, { \ - _rmt_BeginOpenGLSample(name, hashptr); \ - }) - -#define rmt_BeginOpenGLSampleDynamic(namestr) \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BeginOpenGLSample(namestr, NULL)) - -#define rmt_EndOpenGLSample() \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_EndOpenGLSample()) - - -#define rmt_BindMetal(command_buffer) \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_BindMetal(command_buffer)); - -#define rmt_UnbindMetal() \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_UnbindMetal()); - -#define rmt_BeginMetalSample(name) \ - RMT_OPTIONAL(RMT_USE_METAL, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginMetalSample(#name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginMetalSampleDynamic(namestr) \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_BeginMetalSample(namestr, NULL)) - -#define rmt_EndMetalSample() \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_EndMetalSample()) - - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - C++ Public Interface Extensions ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#ifdef __cplusplus - - -#if RMT_ENABLED - -// Types that end samples in their destructors -extern "C" RMT_API void _rmt_EndCPUSample(void); -struct rmt_EndCPUSampleOnScopeExit -{ - ~rmt_EndCPUSampleOnScopeExit() - { - _rmt_EndCPUSample(); - } -}; -#if RMT_USE_CUDA -extern "C" RMT_API void _rmt_EndCUDASample(void* stream); -struct rmt_EndCUDASampleOnScopeExit -{ - rmt_EndCUDASampleOnScopeExit(void* stream) : stream(stream) - { - } - ~rmt_EndCUDASampleOnScopeExit() - { - _rmt_EndCUDASample(stream); - } - void* stream; -}; -#endif -#if RMT_USE_D3D11 -extern "C" RMT_API void _rmt_EndD3D11Sample(void); -struct rmt_EndD3D11SampleOnScopeExit -{ - ~rmt_EndD3D11SampleOnScopeExit() - { - _rmt_EndD3D11Sample(); - } -}; -#endif - -#if RMT_USE_OPENGL -extern "C" RMT_API void _rmt_EndOpenGLSample(void); -struct rmt_EndOpenGLSampleOnScopeExit -{ - ~rmt_EndOpenGLSampleOnScopeExit() - { - _rmt_EndOpenGLSample(); - } -}; -#endif - -#if RMT_USE_METAL -extern "C" RMT_API void _rmt_EndMetalSample(void); -struct rmt_EndMetalSampleOnScopeExit -{ - ~rmt_EndMetalSampleOnScopeExit() - { - _rmt_EndMetalSample(); - } -}; -#endif - -#endif - - - -// Pairs a call to rmt_Begin<TYPE>Sample with its call to rmt_End<TYPE>Sample when leaving scope -#define rmt_ScopedCPUSample(name, flags) \ - RMT_OPTIONAL(RMT_ENABLED, rmt_BeginCPUSample(name, flags)); \ - RMT_OPTIONAL(RMT_ENABLED, rmt_EndCPUSampleOnScopeExit rmt_ScopedCPUSample##name); -#define rmt_ScopedCUDASample(name, stream) \ - RMT_OPTIONAL(RMT_USE_CUDA, rmt_BeginCUDASample(name, stream)); \ - RMT_OPTIONAL(RMT_USE_CUDA, rmt_EndCUDASampleOnScopeExit rmt_ScopedCUDASample##name(stream)); -#define rmt_ScopedD3D11Sample(name) \ - RMT_OPTIONAL(RMT_USE_D3D11, rmt_BeginD3D11Sample(name)); \ - RMT_OPTIONAL(RMT_USE_D3D11, rmt_EndD3D11SampleOnScopeExit rmt_ScopedD3D11Sample##name); -#define rmt_ScopedOpenGLSample(name) \ - RMT_OPTIONAL(RMT_USE_OPENGL, rmt_BeginOpenGLSample(name)); \ - RMT_OPTIONAL(RMT_USE_OPENGL, rmt_EndOpenGLSampleOnScopeExit rmt_ScopedOpenGLSample##name); -#define rmt_ScopedMetalSample(name) \ - RMT_OPTIONAL(RMT_USE_METAL, rmt_BeginMetalSample(name)); \ - RMT_OPTIONAL(RMT_USE_METAL, rmt_EndMetalSampleOnScopeExit rmt_ScopedMetalSample##name); - -#endif - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - Private Interface - don't directly call these ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#if RMT_ENABLED - -#ifdef __cplusplus -extern "C" { -#endif - -RMT_API rmtSettings* _rmt_Settings( void ); -RMT_API enum rmtError _rmt_CreateGlobalInstance(Remotery** remotery); -RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery); -RMT_API void _rmt_SetGlobalInstance(Remotery* remotery); -RMT_API Remotery* _rmt_GetGlobalInstance(void); -RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name); -RMT_API void _rmt_LogText(rmtPStr text); -RMT_API void _rmt_SendText(rmtPStr text); -RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache); -RMT_API void _rmt_EndCPUSample(void); -RMT_API void _rmt_EnableSampling(rmtBool enable); -RMT_API rmtBool _rmt_SamplingEnabled(); - -#if RMT_USE_CUDA -RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind); -RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream); -RMT_API void _rmt_EndCUDASample(void* stream); -#endif - -#if RMT_USE_D3D11 -RMT_API void _rmt_BindD3D11(void* device, void* context); -RMT_API void _rmt_UnbindD3D11(void); -RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndD3D11Sample(void); -#endif - -#if RMT_USE_OPENGL -RMT_API void _rmt_BindOpenGL(); -RMT_API void _rmt_UnbindOpenGL(void); -RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndOpenGLSample(void); -#endif - -#if RMT_USE_METAL -RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndMetalSample(void); -#endif - -#ifdef __cplusplus - -} -#endif - -#if RMT_USE_METAL -#ifdef __OBJC__ -RMT_API void _rmt_BindMetal(id command_buffer); -RMT_API void _rmt_UnbindMetal(); -#endif -#endif - -#endif // RMT_ENABLED - -//! @endcond - -#endif
View file
gpac-2.4.0.tar.gz/include/gpac/cache.h
Deleted
@@ -1,283 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre, Pierre Souchay - * Copyright (c) Telecom ParisTech 2000-2019 - * All rights reserved - * - * This file is part of GPAC / common tools sub-project - * - * GPAC 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, or (at your option) - * any later version. - * - * GPAC 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 library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_CACHE_H_ -#define _GF_CACHE_H_ - -/*! -\file <gpac/cache.h> -\brief HTTP Cache management. - */ - -/*! -\addtogroup cache_grp -\brief HTTP Downloader Cache - -This section documents the file HTTP caching tools the GPAC framework. - -@{ - */ - - -#ifdef __cplusplus -extern "C" { -#endif - -#include <gpac/tools.h> - -/** - * Handle for Cache Entries. - * You can use the gf_cache_get_* functions to get the cache properties - */ -typedef struct __DownloadedCacheEntryStruct * DownloadedCacheEntry; - -/*! cache object*/ -typedef struct __CacheReaderStruct * GF_CacheReader; - -/** - -Free The DownloadedCacheEntry handle -\param entry The entry to delete -\return GF_OK - */ -GF_Err gf_cache_delete_entry( const DownloadedCacheEntry entry ); - -/** - * Get the ETag associated with this cache entry if any -\param entry The entry -\return The ETag if any was defined, NULL otherwise - */ -const char * gf_cache_get_etag_on_server( const DownloadedCacheEntry entry ); - -/** - -Set the eTag in the cache. Data is duplicated, so original string can be freed by caller. -\param entry The entry -\param eTag The eTag to set -\return GF_OK if entry and eTag are valid, GF_BAD_PARAM otherwise - */ -GF_Err gf_cache_set_etag_on_disk(const DownloadedCacheEntry entry, const char * eTag ); - - -/** - -Set the eTag in the cache. Data is duplicated, so original string can be freed by caller. -\param entry The entry -\param eTag The eTag to set -\return GF_OK if entry and eTag are valid, GF_BAD_PARAM otherwise - */ -GF_Err gf_cache_set_etag_on_server(const DownloadedCacheEntry entry, const char * eTag ); - - -/** - -Get the Mime-Type associated with this cache entry. -\param entry The entry -\return The Mime-Type (never NULL if entry is valid) - */ -const char * gf_cache_get_mime_type( const DownloadedCacheEntry entry ); - -/** - -Set the Mime-Type in the cache. Data is duplicated, so original string can be freed by caller. -\param entry The entry -\param mime_type The mime-type to set -\return GF_OK if entry and mime-type are valid, GF_BAD_PARAM otherwise - */ -GF_Err gf_cache_set_mime_type(const DownloadedCacheEntry entry, const char * mime_type ); - -/** - -Get the URL associated with this cache entry. -\param entry The entry -\return The Hash key (never NULL if entry is valid) - */ -const char * gf_cache_get_url( const DownloadedCacheEntry entry ); - -/** - -Tells whether a cache entry should be cached safely (no -\param entry The entry -\return 1 if entry should be cached - */ -Bool gf_cache_can_be_cached( const DownloadedCacheEntry entry ); - -/** - -Get the Last-Modified information associated with this cache entry. -\param entry The entry -\return The Last-Modified header (can be NULL) - */ -const char * gf_cache_get_last_modified_on_server ( const DownloadedCacheEntry entry ); - -/** - -Set the Last-Modified header for this cache entry -\param entry The entry -\param newLastModified The new value to set, will be duplicated -\return GF_OK if everything went alright, GF_BAD_PARAM if entry is NULL - */ -GF_Err gf_cache_set_last_modified_on_disk ( const DownloadedCacheEntry entry, const char * newLastModified ); - -/** - -Set the Last-Modified header for this cache entry -\param entry The entry -\param newLastModified The new value to set, will be duplicated -\return GF_OK if everything went alright, GF_BAD_PARAM if entry is NULL - */ -GF_Err gf_cache_set_last_modified_on_server ( const DownloadedCacheEntry entry, const char * newLastModified ); - -/** - -Get the file name of cache associated with this cache entry. -\param entry The entry -\return The Cache file (never NULL if entry is valid) - */ -const char * gf_cache_get_cache_filename( const DownloadedCacheEntry entry ); - -/** - -Get the real file size of the cache entry -\param entry The entry -\return the file size - */ -u32 gf_cache_get_cache_filesize( const DownloadedCacheEntry entry ); - -/*! - -Flushes The disk cache for this entry (by persisting the property file -\param entry The entry -\return error if any -*/ -GF_Err gf_cache_flush_disk_cache( const DownloadedCacheEntry entry ); - -/*! - -Set content length of resource -\param entry The entry -\param length size of the content in bytes -\return error if any - */ -GF_Err gf_cache_set_content_length( const DownloadedCacheEntry entry, u32 length ); - -/** - -Get content length of resource -\param entry The entry -\return size of the content in bytes - */ -u32 gf_cache_get_content_length( const DownloadedCacheEntry entry); - -/** -Get directives headers associated with the cache -\param entry The entry of cache to use -\param etag set to etag value or NULL if no cache -\param last_modif set to last modif value or NULL if no cache -\return GF_OK if everything went fine, GF_BAD_PARAM if parameters are wrong - */ -GF_Err gf_cache_get_http_headers(const DownloadedCacheEntry entry, const char **etag, const char **last_modif); - -/* - * Cache Management functions - */ - - /*! - -Computes the size of the cache elements in directory -\param directory containing cache files -\return size in bytes - */ -u64 gf_cache_get_size(const char * directory); - -/*! -Delete all cached files in given directory starting with startpattern -\param directory to clean up -\return GF_OK if everything went fine - */ -GF_Err gf_cache_delete_all_cached_files(const char * directory); - - -/*! - -Check if a given cache entry is corrupted (incomplete) -\param entry The entry -\return GF_TRUE if resource is corrupted - */ -Bool gf_cache_check_if_cache_file_is_corrupted(const DownloadedCacheEntry entry); - -/*! -Mark associated files as "to be deleted" when the cache entry is removed -\param entry The entry - */ -void gf_cache_entry_set_delete_files_when_deleted(const DownloadedCacheEntry entry); - -/*! -Check if associated files is marked as "to be deleted" when the cache entry is removed -\param entry The entry -\return GF_TRUE if cache entry is flaged as "to be deleted" - */ -Bool gf_cache_entry_is_delete_files_when_deleted(const DownloadedCacheEntry entry); - -/*! -Get the number of sessions for a cache entry -\param entry The entry -\return the number of sessions using this cache entry - */ -u32 gf_cache_get_sessions_count_for_cache_entry(const DownloadedCacheEntry entry); - -/*! -Get the start range of a cache entry -\param entry The entry -\return the start range in bytes - */ -u64 gf_cache_get_start_range( const DownloadedCacheEntry entry ); -/*! -Get the end range of a cache entry -\param entry The entry -\return the end range in bytes - */ -u64 gf_cache_get_end_range( const DownloadedCacheEntry entry ); - -/*! -Check if the entry is marked as "headers processed" (reply headers have been parsed) -\param entry The entry -\return GF_TRUE if the entry is marked - */ -Bool gf_cache_are_headers_processed(const DownloadedCacheEntry entry); -/*! -Mark the entry as "headers processed" -\param entry The entry -\return error if any - */ -GF_Err gf_cache_set_headers_processed(const DownloadedCacheEntry entry); - -/*! @} */ - -#ifdef __cplusplus -} -#endif - -#endif /* _GF_CACHE_H_ */
View file
gpac-2.4.0.tar.gz/share/vis
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/Code
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/Code/Console.js
Deleted
@@ -1,208 +0,0 @@ - -Console = (function() -{ - var BORDER = 10; - var HEIGHT = 200; - - - function Console(wm, server) - { - // Create the window and its controls - this.Window = wm.AddWindow("Console", 10, 10, 100, 100); - this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); - DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText"); - this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); - DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText"); - this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", "")); - this.UserInput.SetChangeHandler(Bind(ProcessInput, this)); - this.Window.ShowNoAnim(); - - // This accumulates log text as fast as is required - this.PageTextBuffer = ""; - this.PageTextUpdatePending = false; - this.AppTextBuffer = ""; - this.AppTextUpdatePending = false; - - // Setup command history control - this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", ); - this.CommandIndex = 0; - this.MaxNbCommands = 200; - DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this)); - DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this)); - - // At a much lower frequency this will update the console window - window.setInterval(Bind(UpdateHTML, this), 500); - - // Setup log requests from the server - this.Server = server; - server.SetConsole(this); - server.AddMessageHandler("LOGM", Bind(OnLog, this)); - - this.Window.SetOnResize(Bind(OnUserResize, this)); - } - - - Console.prototype.Log = function(text) - { - this.PageTextBuffer = LogText(this.PageTextBuffer, text); - this.PageTextUpdatePending = true; - } - - - Console.prototype.WindowResized = function(width, height) - { - // Place window - this.Window.SetPosition(BORDER, height - BORDER - 200); - this.Window.SetSize(width - 2 * BORDER, HEIGHT); - - ResizeInternals(this); - } - - - function OnLog(self, socket, data_view) - { - var data_view_reader = new DataViewReader(data_view, 4); - var text = data_view_reader.GetString(); - self.AppTextBuffer = LogText(self.AppTextBuffer, text); - self.AppTextUpdatePending = true; - } - - - function LogText(existing_text, new_text) - { - // Filter the text a little to make it safer - if (new_text == null) - new_text = "NULL"; - - // Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code - // This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators) - new_text = Convert.string_to_html_entities(new_text); - - // Prefix date and end with new line - var d = new Date(); - new_text = "" + d.toLocaleTimeString() + " " + new_text + "<br>"; - - // Append to local text buffer and ensure clip the oldest text to ensure a max size - existing_text = existing_text + new_text; - var max_len = 10 * 1024; - var len = existing_text.length; - if (len > max_len) - existing_text = existing_text.substr(len - max_len, max_len); - - return existing_text; - } - - function OnUserResize(self, evt) - { - ResizeInternals(self); - } - - function ResizeInternals(self) - { - // Place controls - var parent_size = self.Window.Size; - var mid_w = parent_size0 / 3; - self.UserInput.SetPosition(BORDER, parent_size1 - 2 * BORDER - 30); - self.UserInput.SetSize(parent_size0 - 100, 18); - var output_height = self.UserInput.Position1 - 2 * BORDER; - self.PageContainer.SetPosition(BORDER, BORDER); - self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height); - self.AppContainer.SetPosition(mid_w, BORDER); - self.AppContainer.SetSize(parent_size0 - mid_w - BORDER, output_height); - } - - - function UpdateHTML(self) - { - // Reset the current text buffer as html - - if (self.PageTextUpdatePending) - { - var page_node = self.PageContainer.Node; - page_node.innerHTML = self.PageTextBuffer; - page_node.scrollTop = page_node.scrollHeight; - self.PageTextUpdatePending = false; - } - - if (self.AppTextUpdatePending) - { - var app_node = self.AppContainer.Node; - app_node.innerHTML = self.AppTextBuffer; - app_node.scrollTop = app_node.scrollHeight; - self.AppTextUpdatePending = false; - } - } - - - function ProcessInput(self, node) - { - // Send the message exactly - var msg = node.value; - self.Server.Send("CONI" + msg); - - // Emit to console and clear - self.Log("> " + msg); - self.UserInput.SetValue(""); - - // Keep track of recently issued commands, with an upper bound - self.CommandHistory.push(msg); - var extra_commands = self.CommandHistory.length - self.MaxNbCommands; - if (extra_commands > 0) - self.CommandHistory.splice(0, extra_commands); - - // Set command history index to the most recent command - self.CommandIndex = self.CommandHistory.length; - - // Backup to local store - LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory); - - // Keep focus with the edit box - return true; - } - - - function OnKeyPress(self, evt) - { - evt = DOM.Event.Get(evt); - - if (evt.keyCode == Keyboard.Codes.UP) - { - if (self.CommandHistory.length > 0) - { - // Cycle backwards through the command history - self.CommandIndex--; - if (self.CommandIndex < 0) - self.CommandIndex = self.CommandHistory.length - 1; - var command = self.CommandHistoryself.CommandIndex; - self.UserInput.SetValue(command); - } - - // Stops default behavior of moving cursor to the beginning - DOM.Event.StopDefaultAction(evt); - } - - else if (evt.keyCode == Keyboard.Codes.DOWN) - { - if (self.CommandHistory.length > 0) - { - // Cycle fowards through the command history - self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length; - var command = self.CommandHistoryself.CommandIndex; - self.UserInput.SetValue(command); - } - - // Stops default behavior of moving cursor to the end - DOM.Event.StopDefaultAction(evt); - } - } - - - function OnFocus(self) - { - // Reset command index on focus - self.CommandIndex = self.CommandHistory.length; - } - - - return Console; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Code/DataViewReader.js
Deleted
@@ -1,47 +0,0 @@ - -// -// Simple wrapper around DataView that auto-advances the read offset and provides -// a few common data type conversions specific to this app -// -DataViewReader = (function () -{ - function DataViewReader(data_view, offset) - { - this.DataView = data_view; - this.Offset = offset; - } - - DataViewReader.prototype.GetUInt32 = function () - { - var v = this.DataView.getUint32(this.Offset, true); - this.Offset += 4; - return v; - } - - DataViewReader.prototype.GetUInt64 = function () - { - var v = this.DataView.getFloat64(this.Offset, true); - this.Offset += 8; - return v; - } - - DataViewReader.prototype.GetStringOfLength = function (string_length) - { - var string = ""; - for (var i = 0; i < string_length; i++) - { - string += String.fromCharCode(this.DataView.getInt8(this.Offset)); - this.Offset++; - } - - return string; - } - - DataViewReader.prototype.GetString = function () - { - var string_length = this.GetUInt32(); - return this.GetStringOfLength(string_length); - } - - return DataViewReader; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Code/PixelTimeRange.js
Deleted
@@ -1,61 +0,0 @@ - - -PixelTimeRange = (function() -{ - function PixelTimeRange(start_us, span_us, span_px) - { - this.Span_px = span_px; - this.Set(start_us, span_us); - } - - - PixelTimeRange.prototype.Set = function(start_us, span_us) - { - this.Start_us = start_us; - this.Span_us = span_us; - this.End_us = this.Start_us + span_us; - this.usPerPixel = this.Span_px / this.Span_us; - } - - - PixelTimeRange.prototype.SetStart = function(start_us) - { - this.Start_us = start_us; - this.End_us = start_us + this.Span_us; - } - - - PixelTimeRange.prototype.SetEnd = function(end_us) - { - this.End_us = end_us; - this.Start_us = end_us - this.Span_us; - } - - - PixelTimeRange.prototype.SetPixelSpan = function(span_px) - { - this.Span_px = span_px; - this.usPerPixel = this.Span_px / this.Span_us; - } - - - PixelTimeRange.prototype.PixelOffset = function(time_us) - { - return Math.floor((time_us - this.Start_us) * this.usPerPixel); - } - - - PixelTimeRange.prototype.PixelSize = function(time_us) - { - return Math.floor(time_us * this.usPerPixel); - } - - - PixelTimeRange.prototype.Clone = function() - { - return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px); - } - - - return PixelTimeRange; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Code/Remotery.js
Deleted
@@ -1,338 +0,0 @@ - -// -// TODO: Window resizing needs finer-grain control -// TODO: Take into account where user has moved the windows -// TODO: Controls need automatic resizing within their parent windows -// - - -Settings = (function() -{ - function Settings() - { - this.IsPaused = false; - } - - return Settings; - -})(); - - -Remotery = (function() -{ - // crack the url and get the parameter we want - var getUrlParameter = function getUrlParameter( search_param) - { - var page_url = decodeURIComponent( window.location.search.substring(1) ), - url_vars = page_url.split('&'), - param_name, - i; - - for (i = 0; i < url_vars.length; i++) - { - param_name = url_varsi.split('='); - - if (param_name0 === search_param) - { - return param_name1 === undefined ? true : param_name1; - } - } - }; - - function Remotery() - { - this.WindowManager = new WM.WindowManager(); - this.Settings = new Settings(); - - // "addr" param is ip:port and will override the local store version if passed in the URL - var addr = getUrlParameter( "addr" ); - if ( addr != null ) - this.ConnectionAddress = "ws://" + addr + "/rmt"; - else - this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt"); - - this.Server = new WebSocketConnection(); - this.Server.AddConnectHandler(Bind(OnConnect, this)); - - // Create the console up front as everything reports to it - this.Console = new Console(this.WindowManager, this.Server); - - // Create required windows - this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress); - this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this)); - this.TimelineWindow = new TimelineWindow(this.WindowManager, this.Settings, this.Server, Bind(OnTimelineCheck, this)); - this.TimelineWindow.SetOnHover(Bind(OnSampleHover, this)); - this.TimelineWindow.SetOnSelected(Bind(OnSampleSelected, this)); - - this.NbSampleWindows = 0; - this.SampleWindows = { }; - this.FrameHistory = { }; - this.SelectedFrames = { }; - this.NameMap = { }; - - this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this)); - this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this)); - - // Kick-off the auto-connect loop - AutoConnect(this); - - // Hook up resize event handler - DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this)); - OnResizeWindow(this); - - // Hook up browser-native canvas refresh - this.DisplayFrame = 0; - this.LastKnownPause = this.Settings.IsPaused; - var self = this; - (function display_loop() - { - window.requestAnimationFrame(display_loop); - DrawTimeline(self); - })(); - } - - - function AutoConnect(self) - { - // Only attempt to connect if there isn't already a connection or an attempt to connect - if (!self.Server.Connected()) - self.Server.Connect(self.ConnectionAddress); - - // Always schedule another check - window.setTimeout(Bind(AutoConnect, self), 2000); - } - - - function OnConnect(self) - { - // Connection address has been validated - LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress); - } - - - function OnAddressChanged(self, node) - { - // Update and disconnect, relying on auto-connect to reconnect - self.ConnectionAddress = node.value; - self.Server.Disconnect(); - - // Give input focus away - return false; - } - - - function DrawTimeline(self) - { - // Has pause state changed? - if (self.Settings.IsPaused != self.LastKnownPaused) - { - // When switching TO paused, draw one last frame to ensure the sample text gets drawn - self.LastKnownPaused = self.Settings.IsPaused; - self.TimelineWindow.DrawAllRows(); - return; - } - - // Don't waste time drawing the timeline when paused - if (self.Settings.IsPaused) - return; - - // requestAnimationFrame can run up to 60hz which is way too much for drawing the timeline - // Assume it's running at 60hz and skip frames to achieve 10hz instead - // Doing this instead of using setTimeout because it's better for browser rendering (or; will be once WebGL is in use) - // TODO: Expose as config variable because high refresh rate is great when using a separate viewiing machine - if ((self.DisplayFrame % 10) == 0) - self.TimelineWindow.DrawAllRows(); - - self.DisplayFrame++; - } - - - function DecodeSample(self, data_view_reader) - { - var sample = {}; - - // Get name hash and lookup name it map - sample.name_hash = data_view_reader.GetUInt32(); - sample.name = self.NameMapsample.name_hash; - - // If the name doesn't exist in the map yet, request it from the server - if (sample.name == undefined) - { - // Meanwhile, store the hash as the name - sample.name = { "string": sample.name_hash }; - self.NameMapsample.name_hash = sample.name; - self.Server.Send("GSMP" + sample.name_hash); - } - - // Get the rest of the sample data - sample.id = data_view_reader.GetUInt32(); - sample.colour = data_view_reader.GetStringOfLength(7); - sample.us_start = data_view_reader.GetUInt64(); - sample.us_length = data_view_reader.GetUInt64(); - sample.us_self = data_view_reader.GetUInt64(); - sample.call_count = data_view_reader.GetUInt32(); - sample.recurse_depth = data_view_reader.GetUInt32(); - - // Calculate dependent properties - sample.ms_length = (sample.us_length / 1000.0).toFixed(3); - sample.ms_self = (sample.us_self / 1000.0).toFixed(3); - - // Recurse into children - sample.children = ; - DecodeSampleArray(self, data_view_reader, sample.children); - - return sample; - } - - - function DecodeSampleArray(self, data_view_reader, samples) - { - var nb_samples = data_view_reader.GetUInt32(); - for (var i = 0; i < nb_samples; i++) - { - var sample = DecodeSample(self, data_view_reader); - samples.push(sample) - } - } - - - function DecodeSamples(self, data_view_reader) - { - // Message-specific header - var message = { }; - message.thread_name = data_view_reader.GetString(); - message.nb_samples = data_view_reader.GetUInt32(); - message.sample_digest = data_view_reader.GetUInt32(); - - // Read samples - message.samples = ; - message.samples.push(DecodeSample(self, data_view_reader)); - - return message; - } - - - function OnSamples(self, socket, data_view) - { - // Discard any new samples while paused - if (self.Settings.IsPaused) - return; - - // Binary decode incoming sample data - var message = DecodeSamples(self, new DataViewReader(data_view, 8)); - var name = message.thread_name; - - // Add to frame history for this thread - var thread_frame = new ThreadFrame(message); - if (!(name in self.FrameHistory)) - self.FrameHistoryname = ; - var frame_history = self.FrameHistoryname; - frame_history.push(thread_frame); - - // Discard old frames to keep memory-use constant - var max_nb_frames = 10000; - var extra_frames = frame_history.length - max_nb_frames; - if (extra_frames > 0) - frame_history.splice(0, extra_frames); - - // Create sample windows on-demand - if (!(name in self.SampleWindows)) - { - self.SampleWindowsname = new SampleWindow(self.WindowManager, name, self.NbSampleWindows); - self.SampleWindowsname.WindowResized(self.TimelineWindow.Window, self.Console.Window); - self.NbSampleWindows++; - MoveSampleWindows(this); - } - - // Set on the window and timeline - self.SampleWindowsname.OnSamples(message.nb_samples, message.sample_digest, message.samples); - self.TimelineWindow.OnSamples(name, frame_history); - } - - - function OnSampleName(self, socket, data_view) - { - // Add any names sent by the server to the local map - var data_view_reader = new DataViewReader(data_view, 4); - var name_hash = data_view_reader.GetUInt32(); - var name = data_view_reader.GetString(); - self.NameMapname_hash.string = name; - } - - - function OnTimelineCheck(self, name, evt) - { - // Show/hide the equivalent sample window and move all the others to occupy any left-over space - var target = DOM.Event.GetNode(evt); - self.SampleWindowsname.SetVisible(target.checked); - MoveSampleWindows(self); - } - - - function MoveSampleWindows(self) - { - // Stack all windows next to each other - var xpos = 0; - for (var i in self.SampleWindows) - { - var sample_window = self.SampleWindowsi; - if (sample_window.Visible) - sample_window.SetXPos(xpos++, self.TimelineWindow.Window, self.Console.Window); - } - } - - - function OnSampleHover(self, thread_name, hover) - { - // Hover only changes sample window contents when paused - var sample_window = self.SampleWindowsthread_name; - if (sample_window && self.Settings.IsPaused) - { - if (hover == null) - { - // When there's no hover, go back to the selected frame - if (self.SelectedFramesthread_name) - { - var frame = self.SelectedFramesthread_name; - sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples); - } - } - - else - { - // Populate with sample under hover - var frame = hover0; - sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples); - } - } - } - - - function OnSampleSelected(self, thread_name, select) - { - // Lookup sample window set the frame samples on it - if (select && thread_name in self.SampleWindows) - { - var sample_window = self.SampleWindowsthread_name; - var frame = select0; - self.SelectedFramesthread_name = frame; - sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples); - } - } - - - function OnResizeWindow(self) - { - // Resize windows - var w = window.innerWidth; - var h = window.innerHeight; - self.Console.WindowResized(w, h); - self.TitleWindow.WindowResized(w, h); - self.TimelineWindow.WindowResized(w, h, self.TitleWindow.Window); - for (var i in self.SampleWindows) - self.SampleWindowsi.WindowResized(self.TimelineWindow.Window, self.Console.Window); - } - - - return Remotery; -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/Code/SampleWindow.js
Deleted
@@ -1,215 +0,0 @@ - -SampleWindow = (function() -{ - function SampleWindow(wm, name, offset) - { - // Sample digest for checking if grid needs to be repopulated - this.NbSamples = 0; - this.SampleDigest = null; - - // Source sample reference to reduce repopulation - this.Samples = null; - - this.XPos = 10 + offset * 410; - this.Window = wm.AddWindow(name, 100, 100, 100, 100); - this.Window.ShowNoAnim(); - this.Visible = true; - - // Create a grid that's indexed by the unique sample ID - this.Grid = this.Window.AddControlNew(new WM.Grid()); - var cell_data = - { - Name: "Samples", - Length: "Time (ms)", - Self: "Self (ms)", - Calls: "Calls", - Recurse: "Recurse", - }; - var cell_classes = - { - Name: "SampleTitleNameCell", - Length: "SampleTitleTimeCell", - Self: "SampleTitleTimeCell", - Calls: "SampleTitleCountCell", - Recurse: "SampleTitleCountCell", - }; - this.RootRow = this.Grid.Rows.Add(cell_data, "GridGroup", cell_classes); - this.RootRow.Rows.AddIndex("_ID"); - } - - - SampleWindow.prototype.SetXPos = function(xpos, top_window, bottom_window) - { - Anim.Animate( - Bind(AnimatedMove, this, top_window, bottom_window), - this.XPos, 10 + xpos * 410, 0.25); - } - - - function AnimatedMove(self, top_window, bottom_window, val) - { - self.XPos = val; - self.WindowResized(top_window, bottom_window); - } - - - SampleWindow.prototype.SetVisible = function(visible) - { - if (visible != this.Visible) - { - if (visible == true) - this.Window.Show(); - else - this.Window.Hide(); - - this.Visible = visible; - } - } - - - SampleWindow.prototype.WindowResized = function(top_window, bottom_window) - { - var top = top_window.Position1 + top_window.Size1 + 10; - this.Window.SetPosition(this.XPos, top_window.Position1 + top_window.Size1 + 10); - this.Window.SetSize(400, bottom_window.Position1 - 10 - top); - } - - - SampleWindow.prototype.OnSamples = function(nb_samples, sample_digest, samples) - { - if (!this.Visible) - return; - - // If the source hasn't changed, don't repopulate - if (this.Samples == samples) - return; - this.Samples = samples; - - // Recreate all the HTML if the number of samples gets bigger - if (nb_samples > this.NbSamples) - { - GrowGrid(this.RootRow, nb_samples); - this.NbSamples = nb_samples; - } - - // If the content of the samples changes from previous update, update them all - if (this.SampleDigest != sample_digest) - { - this.RootRow.Rows.ClearIndex("_ID"); - var index = UpdateAllSampleFields(this.RootRow, samples, 0, ""); - this.SampleDigest = sample_digest; - - // Clear out any left-over rows - for (var i = index; i < this.RootRow.Rows.Rows.length; i++) - { - var row = this.RootRow.Rows.Rowsi; - DOM.Node.Hide(row.Node); - } - } - - else if (this.Visible) - { - // Otherwise just update the existing sample fields - UpdateChangedSampleFields(this.RootRow, samples, ""); - } - } - - - function GrowGrid(parent_row, nb_samples) - { - parent_row.Rows.Clear(); - - for (var i = 0; i < nb_samples; i++) - { - var cell_data = - { - _ID: i, - Name: "", - Length: "", - Self: "", - Calls: "", - Recurse: "", - }; - - var cell_classes = - { - Name: "SampleNameCell", - Length: "SampleTimeCell", - Self: "SampleTimeCell", - Calls: "SampleCountCell", - Recurse: "SampleCountCell", - }; - - parent_row.Rows.Add(cell_data, null, cell_classes); - } - } - - - function UpdateAllSampleFields(parent_row, samples, index, indent) - { - for (var i in samples) - { - var sample = samplesi; - - // Match row allocation in GrowGrid - var row = parent_row.Rows.Rowsindex++; - - // Sample row may have been hidden previously - DOM.Node.Show(row.Node); - - // Assign unique ID so that the common fast path of updating sample times only - // can lookup target samples in the grid - row.CellData._ID = sample.id; - parent_row.Rows.AddRowToIndex("_ID", sample.id, row); - - // Record sample name for later comparison - row.CellData.Name = sample.name.string; - - // Set sample name and colour - var name_node = row.CellNodes"Name"; - name_node.innerHTML = indent + sample.name.string; - DOM.Node.SetColour(name_node, sample.colour); - - row.CellNodes"Length".innerHTML = sample.ms_length; - row.CellNodes"Self".innerHTML = sample.ms_self; - row.CellNodes"Calls".innerHTML = sample.call_count; - row.CellNodes"Recurse".innerHTML = sample.recurse_depth; - - index = UpdateAllSampleFields(parent_row, sample.children, index, indent + " "); - } - - return index; - } - - - function UpdateChangedSampleFields(parent_row, samples, indent) - { - for (var i in samples) - { - var sample = samplesi; - - var row = parent_row.Rows.GetBy("_ID", sample.id); - if (row) - { - row.CellNodes"Length".innerHTML = sample.ms_length; - row.CellNodes"Self".innerHTML = sample.ms_self; - row.CellNodes"Calls".innerHTML = sample.call_count; - row.CellNodes"Recurse".innerHTML = sample.recurse_depth; - - // Sample name will change when it switches from hash ID to network-retrieved - // name. Quickly check that before re-applying the HTML for the name. - if (row.CellData.Name != sample.name.string) - { - var name_node = row.CellNodes"Name"; - row.CellData.Name = sample.name.string; - name_node.innerHTML = indent + sample.name.string; - } - } - - UpdateChangedSampleFields(parent_row, sample.children, indent + " "); - } - } - - - return SampleWindow; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Code/ThreadFrame.js
Deleted
@@ -1,28 +0,0 @@ - - -ThreadFrame = (function() -{ - function ThreadFrame(message) - { - // Persist the required message data - this.NbSamples = message.nb_samples; - this.SampleDigest = message.sample_digest; - this.Samples = message.samples; - - // Calculate the frame start/end times - this.StartTime_us = 0; - this.EndTime_us = 0; - var nb_root_samples = this.Samples.length; - if (nb_root_samples > 0) - { - var last_sample = this.Samplesnb_root_samples - 1; - this.StartTime_us = this.Samples0.us_start; - this.EndTime_us = last_sample.us_start + last_sample.us_length; - } - - this.Length_us = this.EndTime_us - this.StartTime_us; - } - - - return ThreadFrame; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Code/TimelineRow.js
Deleted
@@ -1,379 +0,0 @@ - - -TimelineRow = (function() -{ - var row_template = function(){/* - <div class='TimelineRow'> - <div class='TimelineRowCheck TimelineBox'> - <input class='TimelineRowCheckbox' type='checkbox' /> - </div> - <div class='TimelineRowExpand TimelineBox NoSelect'> - <div class='TimelineRowExpandButton'>+</div> - </div> - <div class='TimelineRowExpand TimelineBox NoSelect'> - <div class='TimelineRowExpandButton'>-</div> - </div> - <div class='TimelineRowLabel TimelineBox'></div> - <canvas class='TimelineRowCanvas'></canvas> - <div style="clear:left"></div> - </div> -*/}.toString().split(/\n/).slice(1, -1).join("\n"); - - - var CANVAS_Y_OFFSET = 0; - var CANVAS_BORDER = 1; - var SAMPLE_HEIGHT = 16; - var SAMPLE_BORDER = 1; - var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2; - var SAMPLE_Y_OFFSET = CANVAS_Y_OFFSET + CANVAS_BORDER + 1; - - - function TimelineRow(name, width, parent_node, frame_history, check_handler) - { - this.Name = name; - - // Create the row HTML and add to the parent - this.ContainerNode = DOM.Node.CreateHTML(row_template); - this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData"); - this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel"); - this.LabelNode.innerHTML = name; - this.CheckboxNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCheckbox"); - var expand_node_0 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 0); - var expand_node_1 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 1); - this.IncNode = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton"); - this.DecNode = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton"); - this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas"); - parent_node.appendChild(this.ContainerNode); - - // All sample view windows visible by default - this.CheckboxNode.checked = true; - DOM.Event.AddHandler(this.CheckboxNode, "change", function(evt) { check_handler(name, evt); }); - - // Manually hook-up events to simulate div:active - // I can't get the equivalent CSS to work in Firefox, so... - DOM.Event.AddHandler(this.IncNode, "mousedown", ExpandButtonDown); - DOM.Event.AddHandler(this.IncNode, "mouseup", ExpandButtonUp); - DOM.Event.AddHandler(this.IncNode, "mouseleave", ExpandButtonUp); - DOM.Event.AddHandler(this.DecNode, "mousedown", ExpandButtonDown); - DOM.Event.AddHandler(this.DecNode, "mouseup", ExpandButtonUp); - DOM.Event.AddHandler(this.DecNode, "mouseleave", ExpandButtonUp); - - // Pressing +/i increases/decreases depth - DOM.Event.AddHandler(this.IncNode, "click", Bind(IncDepth, this)); - DOM.Event.AddHandler(this.DecNode, "click", Bind(DecDepth, this)); - - // Setup the canvas - this.Depth = 1; - this.Ctx = this.CanvasNode.getContext("2d"); - this.SetSize(width); - this.Clear(); - - // Frame index to start at when looking for first visible sample - this.StartFrameIndex = 0; - - this.FrameHistory = frame_history; - this.VisibleFrames = ; - this.VisibleTimeRange = null; - - // Sample the mouse is currently hovering over - this.HoverSample = null; - this.HoverSampleDepth = 0; - - // Currently selected sample - this.SelectedSample = null; - this.SelectedSampleDepth = 0; - } - - - TimelineRow.prototype.SetSize = function(width) - { - // Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects. - this.CanvasNode.width = width; - this.CanvasNode.height = CANVAS_BORDER + SAMPLE_BORDER + SAMPLE_Y_SPACING * this.Depth; - this.Draw(true); - } - - - TimelineRow.prototype.Clear = function() - { - // Fill box that shows the boundary between thread rows - this.Ctx.fillStyle = "#666" - var b = CANVAS_BORDER; - this.Ctx.fillRect(b, b, this.CanvasNode.width - b * 2, this.CanvasNode.height - b * 2); - } - - - TimelineRow.prototype.SetVisibleFrames = function(time_range) - { - // Clear previous visible list - this.VisibleFrames = ; - if (this.FrameHistory.length == 0) - return; - - // Store a copy of the visible time range rather than referencing it - // This prevents external modifications to the time range from affecting rendering/selection - time_range = time_range.Clone(); - this.VisibleTimeRange = time_range; - - // The frame history can be reset outside this class - // This also catches the overflow to the end of the frame list below when a thread stops sending samples - var max_frame = Math.max(this.FrameHistory.length - 1, 0); - var start_frame_index = Math.min(this.StartFrameIndex, max_frame); - - // First do a back-track in case the time range moves negatively - while (start_frame_index > 0) - { - var frame = this.FrameHistorystart_frame_index; - if (time_range.Start_us > frame.StartTime_us) - break; - start_frame_index--; - } - - // Then search from this point for the first visible frame - while (start_frame_index < this.FrameHistory.length) - { - var frame = this.FrameHistorystart_frame_index; - if (frame.EndTime_us > time_range.Start_us) - break; - start_frame_index++; - } - - // Gather all frames up to the end point - this.StartFrameIndex = start_frame_index; - for (var i = start_frame_index; i < this.FrameHistory.length; i++) - { - var frame = this.FrameHistoryi; - if (frame.StartTime_us > time_range.End_us) - break; - this.VisibleFrames.push(frame); - } - } - - - TimelineRow.prototype.Draw = function(draw_text) - { - this.Clear(); - - // Draw all root samples in the visible frame set - for (var i in this.VisibleFrames) - { - var frame = this.VisibleFramesi; - DrawSamples(this, frame.Samples, 1, draw_text); - } - } - - - function DrawSamples(self, samples, depth, draw_text) - { - for (var i in samples) - { - var sample = samplesi; - DrawSample(self, sample, depth, draw_text); - - if (depth < self.Depth && sample.children != null) - DrawSamples(self, sample.children, depth + 1, draw_text); - } - } - - - TimelineRow.prototype.UpdateHoverSample = function(mouse_state, x_offset) - { - var hover = GetSampleAtPosition(this, mouse_state, x_offset); - if (hover) - this.SetHoverSample(hover1, hover2); - return hover; - } - - - TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, x_offset) - { - var select = GetSampleAtPosition(this, mouse_state, x_offset); - if (select) - this.SetSelectedSample(select1, select2); - return select; - } - - - TimelineRow.prototype.SetHoverSample = function(sample, sample_depth) - { - if (sample != this.HoverSample) - { - // Discard old highlight - // TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate - var old_sample = this.HoverSample; - var old_sample_depth = this.HoverSampleDepth; - this.HoverSample = null; - this.HoverSampleDepth = 0; - DrawSample(this, old_sample, old_sample_depth, true); - - // Add new highlight - this.HoverSample = sample; - this.HoverSampleDepth = sample_depth; - DrawSample(this, sample, sample_depth, true); - } - } - - - TimelineRow.prototype.SetSelectedSample = function(sample, sample_depth) - { - if (sample != this.SelectedSample) - { - // Discard old highlight - // TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate - var old_sample = this.SelectedSample; - var old_sample_depth = this.SelectedSampleDepth; - this.SelectedSample = null; - this.SelectedSampleDepth = 0; - DrawSample(this, old_sample, old_sample_depth, true); - - // Add new highlight - this.SelectedSample = sample; - this.SelectedSampleDepth = sample_depth; - DrawSample(this, sample, sample_depth, true); - } - } - - - function ExpandButtonDown(evt) - { - var node = DOM.Event.GetNode(evt); - DOM.Node.AddClass(node, "TimelineRowExpandButtonActive"); - } - - - function ExpandButtonUp(evt) - { - var node = DOM.Event.GetNode(evt); - DOM.Node.RemoveClass(node, "TimelineRowExpandButtonActive"); - } - - - function IncDepth(self) - { - self.Depth++; - self.SetSize(self.CanvasNode.width); - } - - - function DecDepth(self) - { - if (self.Depth > 1) - { - self.Depth--; - self.SetSize(self.CanvasNode.width); - } - } - - - function GetSampleAtPosition(self, mouse_state, x_offset) - { - // Mouse movement can occur before any data is sent to a timeline row - var time_range = self.VisibleTimeRange; - if (time_range == null) - return; - - // Get the time the mouse is over - var x = mouse_state.Position0 - x_offset; - var time_us = time_range.Start_us + x / time_range.usPerPixel; - - var canvas_y_offset = DOM.Node.GetPosition(self.CanvasNode)1; - var mouse_y_offset = mouse_state.Position1 - canvas_y_offset; - mouse_y_offset = Math.min(Math.max(mouse_y_offset, 0), self.CanvasNode.height); - var depth = Math.floor(mouse_y_offset / SAMPLE_Y_SPACING) + 1; - - // Search for the first frame to intersect this time - for (var i in self.VisibleFrames) - { - var frame = self.VisibleFramesi; - if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us) - { - var found_sample = FindSample(self, frame.Samples, time_us, depth, 1); - if (found_sample != null) - return frame, found_sample0, found_sample1 ; - } - } - - return null; - } - - - function FindSample(self, samples, time_us, target_depth, depth) - { - for (var i in samples) - { - var sample = samplesi; - if (depth == target_depth) - { - if (time_us >= sample.us_start && time_us < sample.us_start + sample.us_length) - return sample, depth ; - } - - else if (depth < target_depth && sample.children != null) - { - var found_sample = FindSample(self, sample.children, time_us, target_depth, depth + 1); - if (found_sample != null) - return found_sample; - } - } - - return null; - } - - - function DrawSample(self, sample, depth, draw_text) - { - if (sample == null) - return; - - // Determine pixel range of the sample - var time_range = self.VisibleTimeRange; - var x0 = time_range.PixelOffset(sample.us_start); - var x1 = x0 + time_range.PixelSize(sample.us_length); - - // Clip to padded timeline row - var min_x = 3; - var max_x = self.CanvasNode.width - 5; - x0 = Math.min(Math.max(x0, min_x), max_x); - x1 = Math.min(Math.max(x1, min_x), max_x); - - var offset_x = x0; - var offset_y = SAMPLE_Y_OFFSET + (depth - 1) * SAMPLE_Y_SPACING; - var size_x = x1 - x0; - var size_y = SAMPLE_HEIGHT; - - // Normal rendering - var ctx = self.Ctx; - ctx.fillStyle = sample.colour; - ctx.fillRect(offset_x, offset_y, size_x, size_y); - - // Highlight rendering - var b = (sample == self.HoverSample) ? 255 : 0; - var r = (sample == self.SelectedSample) ? 255 : 0; - if (b + r > 0) - { - ctx.lineWidth = 1; - ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")"; - ctx.strokeRect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1); - } - - // Draw sample names clipped to the bounds of the sample - // Also reject tiny samples with no space to render text - if (draw_text && size_x > 8) - { - ctx.save(); - ctx.beginPath(); - ctx.rect(offset_x + 2.5, offset_y + 1.5, size_x - 5, size_y - 3); - ctx.clip(); - ctx.font = "9px verdana"; - ctx.fillStyle = "black"; - var text = sample.name.string - text += " (" + sample.ms_length + "ms"; - text += ", " + sample.call_count + "c)"; - ctx.fillText(text, offset_x + 5.5, offset_y + 1.5 + 9); - ctx.restore(); - } - } - - - return TimelineRow; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Code/TimelineWindow.js
Deleted
@@ -1,284 +0,0 @@ - -// -// TODO: Use WebGL and instancing for quicker renders -// - - -TimelineWindow = (function() -{ - var BORDER = 10; - - var ROW_START_SIZE = 210; - - var ROW_END_SIZE = 20; // make room for scrollbar - - var box_template = "<div class='TimelineBox'></div>"; - - function TimelineWindow(wm, settings, server, check_handler) - { - this.Settings = settings; - - // Ordered list of thread rows on the timeline - this.ThreadRows = ; - - // Create window and containers - this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100); - this.Window.ShowNoAnim(); - this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160)); - DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer"); - - var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; - DOM.Event.AddHandler(this.TimelineContainer.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); - - // Setup timeline manipulation - this.MouseDown = false; - this.LastMouseState = null; - this.TimelineMoved = false; - this.OnHoverHandler = null; - this.OnSelectedHandler = null; - DOM.Event.AddHandler(this.TimelineContainer.Node, "mousedown", Bind(OnMouseDown, this)); - DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseup", Bind(OnMouseUp, this)); - DOM.Event.AddHandler(this.TimelineContainer.Node, "mousemove", Bind(OnMouseMove, this)); - - // Set time range AFTER the window has been created, as it uses the window to determine pixel coverage - this.TimeRange = new PixelTimeRange(0, 200 * 1000, RowWidth(this)); - - this.CheckHandler = check_handler; - - this.Window.SetOnResize(Bind(OnUserResize, this)); - } - - - TimelineWindow.prototype.SetOnHover = function(handler) - { - this.OnHoverHandler = handler; - } - - - TimelineWindow.prototype.SetOnSelected = function(handler) - { - this.OnSelectedHandler = handler; - } - - TimelineWindow.prototype.WindowResized = function(width, height, top_window) - { - // Resize window - var top = top_window.Position1 + top_window.Size1 + 10; - this.Window.SetPosition(10, top); - this.Window.SetSize(width - 2 * 10, 260); - - ResizeInternals(this); - } - - - TimelineWindow.prototype.ResetTimeRange = function() - { - this.TimeRange.SetStart(0); - } - - - TimelineWindow.prototype.OnSamples = function(thread_name, frame_history) - { - // Shift the timeline to the last entry on this thread - // As multiple threads come through here with different end frames, only do this for the latest - var last_frame = frame_historyframe_history.length - 1; - if (last_frame.EndTime_us > this.TimeRange.End_us) - this.TimeRange.SetEnd(last_frame.EndTime_us); - - // Search for the index of this thread - var thread_index = -1; - for (var i in this.ThreadRows) - { - if (this.ThreadRowsi.Name == thread_name) - { - thread_index = i; - break; - } - } - - // If this thread has not been seen before, add a new row to the list and re-sort - if (thread_index == -1) - { - var row = new TimelineRow(thread_name, RowWidth(this), this.TimelineContainer.Node, frame_history, this.CheckHandler); - this.ThreadRows.push(row); - this.ThreadRows.sort(function(a, b) { return b.Name.localeCompare(a.Name); }); - } - } - - - TimelineWindow.prototype.DrawAllRows = function() - { - var time_range = this.TimeRange; - var draw_text = this.Settings.IsPaused; - for (var i in this.ThreadRows) - { - var thread_row = this.ThreadRowsi; - thread_row.SetVisibleFrames(time_range); - thread_row.Draw(draw_text); - } - } - - - function RowXOffset(self) - { - // Add sizing of the label - // TODO: Use computed size - return DOM.Node.GetPosition(self.TimelineContainer.Node)0 + ROW_START_SIZE; - } - - - function RowWidth(self) - { - // Subtract sizing of the label - // TODO: Use computed size - return self.TimelineContainer.Size0 - (ROW_START_SIZE + ROW_END_SIZE); - } - - function OnUserResize(self, evt) - { - ResizeInternals(self); - } - - function ResizeInternals(self) - { - // Resize controls - var parent_size = self.Window.Size; - self.TimelineContainer.SetPosition(BORDER, 10); - self.TimelineContainer.SetSize(parent_size0 - 2 * BORDER, parent_size1 - 40); - - // Resize rows - var row_width = RowWidth(self); - for (var i in self.ThreadRows) - { - var row = self.ThreadRowsi; - row.SetSize(row_width); - } - - // Adjust time range to new width - self.TimeRange.SetPixelSpan(row_width); - self.DrawAllRows(); - } - - - function OnMouseScroll(self, evt) - { - var mouse_state = new Mouse.State(evt); - var scale = 1.11; - if (mouse_state.WheelDelta > 0) - scale = 1 / scale; - - // What time is the mouse hovering over? - var x = mouse_state.Position0 - RowXOffset(self); - var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel; - - // Calculate start time relative to the mouse hover position - var time_start_us = self.TimeRange.Start_us - time_us; - - // Scale and offset back to the hover time - self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale); - self.DrawAllRows(); - - // Prevent vertical scrolling on mouse-wheel - DOM.Event.StopDefaultAction(evt); - } - - - function OnMouseDown(self, evt) - { - // Only manipulate the timelime when paused - if (!self.Settings.IsPaused) - return; - - self.MouseDown = true; - self.LastMouseState = new Mouse.State(evt); - self.TimelineMoved = false; - DOM.Event.StopDefaultAction(evt); - } - - - function OnMouseUp(self, evt) - { - // Only manipulate the timelime when paused - if (!self.Settings.IsPaused) - return; - - var mouse_state = new Mouse.State(evt); - - self.MouseDown = false; - - if (!self.TimelineMoved) - { - // Search for the row being clicked and update its selection - var row_node = DOM.Event.GetNode(evt); - for (var i in self.ThreadRows) - { - var thread_row = self.ThreadRowsi; - if (thread_row.CanvasNode == row_node) - { - var select = thread_row.UpdateSelectedSample(mouse_state, RowXOffset(self)); - - // Call any selection handlers - if (self.OnSelectedHandler) - self.OnSelectedHandler(thread_row.Name, select); - - break; - } - } - } - } - - - function OnMouseMove(self, evt) - { - // Only manipulate the timelime when paused - if (!self.Settings.IsPaused) - return; - - var mouse_state = new Mouse.State(evt); - - if (self.MouseDown) - { - // Get the time the mouse is over - var x = mouse_state.Position0 - RowXOffset(self); - var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel; - - // Shift the visible time range with mouse movement - var time_offset_us = (mouse_state.Position0 - self.LastMouseState.Position0) / self.TimeRange.usPerPixel; - if (time_offset_us) - { - self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us); - self.DrawAllRows(); - self.TimelineMoved = true; - } - } - - else - { - // Highlight any samples the mouse moves over - var row_node = DOM.Event.GetNode(evt); - for (var i in self.ThreadRows) - { - var thread_row = self.ThreadRowsi; - if (thread_row.CanvasNode == row_node) - { - var hover = thread_row.UpdateHoverSample(mouse_state, RowXOffset(self)); - - if (self.OnHoverHandler) - self.OnHoverHandler(thread_row.Name, hover); - } - else - { - thread_row.SetHoverSample(null, 0); - if (self.OnHoverHandler) - self.OnHoverHandler(thread_row.Name, null); - } - } - } - - self.LastMouseState = mouse_state; - } - - - return TimelineWindow; -})(); -
View file
gpac-2.4.0.tar.gz/share/vis/Code/TitleWindow.js
Deleted
@@ -1,71 +0,0 @@ - -TitleWindow = (function() -{ - function TitleWindow(wm, settings, server, connection_address) - { - this.Settings = settings; - - this.Window = wm.AddWindow(" Remotery", 10, 10, 100, 100); - this.Window.ShowNoAnim(); - - this.PingContainer = this.Window.AddControlNew(new WM.Container(4, -13, 10, 10)); - DOM.Node.AddClass(this.PingContainer.Node, "PingContainer"); - - this.EditBox = this.Window.AddControlNew(new WM.EditBox(10, 5, 300, 18, "Connection Address", connection_address)); - - // Setup pause button - this.PauseButton = this.Window.AddControlNew(new WM.Button("Pause", 5, 5, { toggle: true })); - this.PauseButton.SetOnClick(Bind(OnPausePressed, this)); - - server.AddMessageHandler("PING", Bind(OnPing, this)); - - this.Window.SetOnResize(Bind(OnUserResize, this)); - } - - - TitleWindow.prototype.SetConnectionAddressChanged = function(handler) - { - this.EditBox.SetChangeHandler(handler); - } - - - TitleWindow.prototype.WindowResized = function(width, height) - { - this.Window.SetSize(width - 2 * 10, 50); - ResizeInternals(this); - } - - function OnUserResize(self, evt) - { - ResizeInternals(self); - } - - function ResizeInternals(self) - { - self.PauseButton.SetPosition(self.Window.Size0 - 60, 5); - } - - - function OnPausePressed(self) - { - self.Settings.IsPaused = self.PauseButton.IsPressed(); - if (self.Settings.IsPaused) - self.PauseButton.SetText("Paused"); - else - self.PauseButton.SetText("Pause"); - } - - - function OnPing(self, server) - { - // Set the ping container as active and take it off half a second later - DOM.Node.AddClass(self.PingContainer.Node, "PingContainerActive"); - window.setTimeout(Bind(function(self) - { - DOM.Node.RemoveClass(self.PingContainer.Node, "PingContainerActive"); - }, self), 500); - } - - - return TitleWindow; -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/Code/WebSocketConnection.js
Deleted
@@ -1,137 +0,0 @@ - -WebSocketConnection = (function() -{ - function WebSocketConnection() - { - this.MessageHandlers = { }; - this.Socket = null; - this.Console = null; - } - - - WebSocketConnection.prototype.SetConsole = function(console) - { - this.Console = console; - } - - - WebSocketConnection.prototype.Connected = function() - { - // Will return true if the socket is also in the process of connecting - return this.Socket != null; - } - - - WebSocketConnection.prototype.AddConnectHandler = function(handler) - { - this.AddMessageHandler("__OnConnect__", handler); - } - - - WebSocketConnection.prototype.AddDisconnectHandler = function(handler) - { - this.AddMessageHandler("__OnDisconnect__", handler); - } - - - WebSocketConnection.prototype.AddMessageHandler = function(message_name, handler) - { - // Create the message handler array on-demand - if (!(message_name in this.MessageHandlers)) - this.MessageHandlersmessage_name = ; - this.MessageHandlersmessage_name.push(handler); - } - - - WebSocketConnection.prototype.Connect = function(address) - { - // Disconnect if already connected - if (this.Connected()) - this.Disconnect(); - - Log(this, "Connecting to " + address); - - this.Socket = new WebSocket(address); - this.Socket.binaryType = "arraybuffer"; - this.Socket.onopen = Bind(OnOpen, this); - this.Socket.onmessage = Bind(OnMessage, this); - this.Socket.onclose = Bind(OnClose, this); - this.Socket.onerror = Bind(OnError, this); - } - - - WebSocketConnection.prototype.Disconnect = function() - { - Log(this, "Disconnecting"); - if (this.Connected()) - this.Socket.close(); - } - - - WebSocketConnection.prototype.Send = function(msg) - { - if (this.Connected()) - this.Socket.send(msg); - } - - - function Log(self, message) - { - self.Console.Log(message); - } - - - function CallMessageHandlers(self, message_name, data_view) - { - if (message_name in self.MessageHandlers) - { - var handlers = self.MessageHandlersmessage_name; - for (var i in handlers) - handlersi(self, data_view); - } - } - - - function OnOpen(self, event) - { - Log(self, "Connected"); - CallMessageHandlers(self, "__OnConnect__"); - } - - - function OnClose(self, event) - { - // Clear all references - self.Socket.onopen = null; - self.Socket.onmessage = null; - self.Socket.onclose = null; - self.Socket.onerror = null; - self.Socket = null; - - Log(self, "Disconnected"); - CallMessageHandlers(self, "__OnDisconnect__"); - } - - - function OnError(self, event) - { - Log(self, "Connection Error "); - } - - - function OnMessage(self, event) - { - var data_view = new DataView(event.data); - - var id = String.fromCharCode( - data_view.getInt8(0), - data_view.getInt8(1), - data_view.getInt8(2), - data_view.getInt8(3)); - - CallMessageHandlers(self, id, data_view); - } - - - return WebSocketConnection; -})();
View file
gpac-2.4.0.tar.gz/share/vis/Styles
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/Styles/Remotery.css
Deleted
@@ -1,234 +0,0 @@ - -body -{ - /* Take up the full page */ - width: 100%; - height: 100%; - margin: 0px; - - background-color: #999; - - touch-action: none; -} - - -/* Override default container style to remove 3D effect */ -.Container -{ - border: none; - box-shadow: none; -} - - -/* Override default edit box style to remove 3D effect */ -.EditBox -{ - border: none; - box-shadow: none; - width:200; -} - - - -.ConsoleText -{ - overflow:auto; - color: #BBB; - font: 9px Verdana; - margin: 2px; - white-space: pre; -} - - -.PingContainer -{ - background-color: #F55; - border-radius: 2px; - - /* Transition from green is gradual */ - transition: background-color 0.25s ease-in; -} - - -.PingContainerActive -{ - background-color: #5F5; - - /* Transition to green is instant */ - transition: none; -} - - -.SampleNameCell -{ - width:243px; -} -.SampleTimeCell -{ - width:52px; -} -.SampleCountCell -{ - width:43px; -} -.SampleTitleNameCell -{ - width:238px; - - padding: 1px 1px 1px 2px; - border: 1px solid; - border-radius: 2px; - - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; -} -.SampleTitleTimeCell -{ - width:47px; - - padding: 1px 1px 1px 2px; - border: 1px solid; - border-radius: 2px; - - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; -} -.SampleTitleCountCell -{ - width:38px; - - padding: 1px 1px 1px 2px; - border: 1px solid; - border-radius: 2px; - - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; -} - - -.TimelineBox -{ - /* Following style generally copies GridRowCell.GridGroup from BrowserLib */ - - padding: 1px 1px 1px 2px; - margin: 1px; - - border: 1px solid; - border-radius: 2px; - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; - - font: 9px Verdana; - color: #BBB; -} -.TimelineRow -{ - width: 100%; -} -.TimelineRowCheckbox -{ - width: 12px; - height: 12px; - margin: 0px; -} -.TimelineRowCheck -{ - /* Pull .TimelineRowExpand to the right of the checkbox */ - float:left; - - width: 14px; - height: 14px; -} -.TimelineRowExpand -{ - /* Pull .TimelineRowLabel to the right of +/- buttons */ - float:left; - - width: 14px; - height: 14px; -} -.TimelineRowExpandButton -{ - width: 11px; - height: 12px; - - color: #333; - - border: 1px solid; - - border-top-color:#F4F4F4; - border-left-color:#F4F4F4; - border-bottom-color:#8E8F8F; - border-right-color:#8E8F8F; - - /* Top-right to bottom-left grey background gradient */ - background: #f6f6f6; /* Old browsers */ - background: -moz-linear-gradient(-45deg, #f6f6f6 0%, #abaeb2 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f6f6f6), color-stop(100%,#abaeb2)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* IE10+ */ - background: linear-gradient(135deg, #f6f6f6 0%,#abaeb2 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f6f6', endColorstr='#abaeb2',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ - - text-align: center; - vertical-align: center; -} -.TimelineRowExpandButton:hover -{ - border-top-color:#79C6F9; - border-left-color:#79C6F9; - border-bottom-color:#385D72; - border-right-color:#385D72; - - /* Top-right to bottom-left blue background gradient, matching border */ - background: #f3f3f3; /* Old browsers */ - background: -moz-linear-gradient(-45deg, #f3f3f3 0%, #79c6f9 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f3f3f3), color-stop(100%,#79c6f9)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* IE10+ */ - background: linear-gradient(135deg, #f3f3f3 0%,#79c6f9 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3f3f3', endColorstr='#79c6f9',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ -} -.TimelineRowExpandButtonActive -{ - /* Simple means of shifting text within a div to the bottom-right */ - padding-left:1px; - padding-top:1px; - width:10px; - height:11px; -} -.TimelineRowLabel -{ - /* Pull .TimelineRowCanvas to the right of the label */ - float:left; - - width: 140px; - height: 14px; -} -.TimelineRowCanvas -{ -} - -/* enable vertical scrollbar in TimelineContainer (useful for many threads) */ -.TimelineContainer -{ - overflow-y: auto; -}
View file
gpac-2.4.0.tar.gz/share/vis/extern
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/Animation.js
Deleted
@@ -1,65 +0,0 @@ - -// -// Very basic linear value animation system, for now. -// - - -namespace("Anim"); - - -Anim.Animation = (function() -{ - var anim_hz = 60; - - - function Animation(anim_func, start_value, end_value, time, end_callback) - { - // Setup initial parameters - this.StartValue = start_value; - this.EndValue = end_value; - this.ValueInc = (end_value - start_value) / (time * anim_hz); - this.Value = start_value; - this.Complete = false; - this.EndCallback = end_callback; - - // Cache the update function to prevent recreating the closure - var self = this; - this.AnimFunc = anim_func; - this.AnimUpdate = function() { Update(self); } - - // Call for the start value - this.AnimUpdate(); - } - - - function Update(self) - { - // Queue up the next frame immediately - var id = window.setTimeout(self.AnimUpdate, 1000 / anim_hz); - - // Linear step the value and check for completion - self.Value += self.ValueInc; - if (Math.abs(self.Value - self.EndValue) < 0.01) - { - self.Value = self.EndValue; - self.Complete = true; - - if (self.EndCallback) - self.EndCallback(); - - window.clearTimeout(id); - } - - // Pass to the animation function - self.AnimFunc(self.Value); - } - - - return Animation; -})(); - - -Anim.Animate = function(anim_func, start_value, end_value, time, end_callback) -{ - return new Anim.Animation(anim_func, start_value, end_value, time, end_callback); -}
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/Bind.js
Deleted
@@ -1,92 +0,0 @@ -// -// This will generate a closure for the given function and optionally bind an arbitrary number of -// its initial arguments to specific values. -// -// Parameters: -// -// 0: Either the function scope or the function. -// 1: If 0 is the function scope, this is the function. -// Otherwise it's the start of the optional bound argument list. -// 2: Start of the optional bound argument list if 1 is the function. -// -// Examples: -// -// function GlobalFunction(p0, p1, p2) { } -// function ThisFunction(p0, p1, p2) { } -// -// var a = Bind("GlobalFunction"); -// var b = Bind(this, "ThisFunction"); -// var c = Bind("GlobalFunction", BoundParam0, BoundParam1); -// var d = Bind(this, "ThisFunction", BoundParam0, BoundParam1); -// var e = Bind(GlobalFunction); -// var f = Bind(this, ThisFunction); -// var g = Bind(GlobalFunction, BoundParam0, BoundParam1); -// var h = Bind(this, ThisFunction, BoundParam0, BoundParam1); -// -// a(0, 1, 2); -// b(0, 1, 2); -// c(2); -// d(2); -// e(0, 1, 2); -// f(0, 1, 2); -// g(2); -// h(2); -// -function Bind() -{ - // No closure to define? - if (arguments.length == 0) - return null; - - // Figure out which of the 4 call types is being used to bind - // Locate scope, function and bound parameter start index - - if (typeof(arguments0) == "string") - { - var scope = window; - var func = windowarguments0; - var start = 1; - } - - else if (typeof(arguments0) == "function") - { - var scope = window; - var func = arguments0; - var start = 1; - } - - else if (typeof(arguments1) == "string") - { - var scope = arguments0; - var func = scopearguments1; - var start = 2; - } - - else if (typeof(arguments1) == "function") - { - var scope = arguments0; - var func = arguments1; - var start = 2; - } - - else - { - // unknown - console.log("Bind() ERROR: Unknown bind parameter configuration"); - return; - } - - // Convert the arguments list to an array - var arg_array = Array.prototype.slice.call(arguments, start); - start = arg_array.length; - - return function() - { - // Concatenate incoming arguments - for (var i = 0; i < arguments.length; i++) - arg_arraystart + i = argumentsi; - - // Call the function in the given scope with the new arguments - return func.apply(scope, arg_array); - } -}
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/Convert.js
Deleted
@@ -1,218 +0,0 @@ - -namespace("Convert"); - - -// -// Convert between utf8 and b64 without raising character out of range exceptions with unicode strings -// Technique described here: http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html -// -Convert.utf8string_to_b64string = function(str) -{ - return btoa(unescape(encodeURIComponent(str))); -} -Convert.b64string_to_utf8string = function(str) -{ - return decodeURIComponent(escape(atob(str))); -} - - -// -// More general approach, converting between byte arrays and b64 -// Info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding -// -Convert.b64string_to_Uint8Array = function(sBase64, nBlocksSize) -{ - function b64ToUint6 (nChr) - { - return nChr > 64 && nChr < 91 ? - nChr - 65 - : nChr > 96 && nChr < 123 ? - nChr - 71 - : nChr > 47 && nChr < 58 ? - nChr + 4 - : nChr === 43 ? - 62 - : nChr === 47 ? - 63 - : - 0; - } - - var - sB64Enc = sBase64.replace(/^A-Za-z0-9\+\//g, ""), - nInLen = sB64Enc.length, - nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, - taBytes = new Uint8Array(nOutLen); - - for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) - { - nMod4 = nInIdx & 3; - nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; - if (nMod4 === 3 || nInLen - nInIdx === 1) - { - for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) - taBytesnOutIdx = nUint24 >>> (16 >>> nMod3 & 24) & 255; - nUint24 = 0; - } - } - - return taBytes; -} -Convert.Uint8Array_to_b64string = function(aBytes) -{ - function uint6ToB64 (nUint6) - { - return nUint6 < 26 ? - nUint6 + 65 - : nUint6 < 52 ? - nUint6 + 71 - : nUint6 < 62 ? - nUint6 - 4 - : nUint6 === 62 ? - 43 - : nUint6 === 63 ? - 47 - : - 65; - } - - var nMod3, sB64Enc = ""; - - for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) - { - nMod3 = nIdx % 3; - if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) - sB64Enc += "\r\n"; - nUint24 |= aBytesnIdx << (16 >>> nMod3 & 24); - if (nMod3 === 2 || aBytes.length - nIdx === 1) - { - sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); - nUint24 = 0; - } - } - - return sB64Enc.replace(/A(?=A$|$)/g, "="); -} - - -// -// Unicode and arbitrary value safe conversion between strings and Uint8Arrays -// -Convert.Uint8Array_to_string = function(aBytes) -{ - var sView = ""; - - for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) - { - nPart = aBytesnIdx; - sView += String.fromCharCode( - nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ - /* (nPart - 252 << 32) is not possible in ECMAScript! So...: */ - (nPart - 252) * 1073741824 + (aBytes++nIdx - 128 << 24) + (aBytes++nIdx - 128 << 18) + (aBytes++nIdx - 128 << 12) + (aBytes++nIdx - 128 << 6) + aBytes++nIdx - 128 - : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ - (nPart - 248 << 24) + (aBytes++nIdx - 128 << 18) + (aBytes++nIdx - 128 << 12) + (aBytes++nIdx - 128 << 6) + aBytes++nIdx - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ - (nPart - 240 << 18) + (aBytes++nIdx - 128 << 12) + (aBytes++nIdx - 128 << 6) + aBytes++nIdx - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ - (nPart - 224 << 12) + (aBytes++nIdx - 128 << 6) + aBytes++nIdx - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ - (nPart - 192 << 6) + aBytes++nIdx - 128 - : /* nPart < 127 ? */ /* one byte */ - nPart - ); - } - - return sView; -} -Convert.string_to_Uint8Array = function(sDOMStr) -{ - var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; - - /* mapping... */ - - for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) - { - nChr = sDOMStr.charCodeAt(nMapIdx); - nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; - } - - aBytes = new Uint8Array(nArrLen); - - /* transcription... */ - - for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) - { - nChr = sDOMStr.charCodeAt(nChrIdx); - if (nChr < 128) - { - /* one byte */ - aBytesnIdx++ = nChr; - } - else if (nChr < 0x800) - { - /* two bytes */ - aBytesnIdx++ = 192 + (nChr >>> 6); - aBytesnIdx++ = 128 + (nChr & 63); - } - else if (nChr < 0x10000) - { - /* three bytes */ - aBytesnIdx++ = 224 + (nChr >>> 12); - aBytesnIdx++ = 128 + (nChr >>> 6 & 63); - aBytesnIdx++ = 128 + (nChr & 63); - } - else if (nChr < 0x200000) - { - /* four bytes */ - aBytesnIdx++ = 240 + (nChr >>> 18); - aBytesnIdx++ = 128 + (nChr >>> 12 & 63); - aBytesnIdx++ = 128 + (nChr >>> 6 & 63); - aBytesnIdx++ = 128 + (nChr & 63); - } - else if (nChr < 0x4000000) - { - /* five bytes */ - aBytesnIdx++ = 248 + (nChr >>> 24); - aBytesnIdx++ = 128 + (nChr >>> 18 & 63); - aBytesnIdx++ = 128 + (nChr >>> 12 & 63); - aBytesnIdx++ = 128 + (nChr >>> 6 & 63); - aBytesnIdx++ = 128 + (nChr & 63); - } - else /* if (nChr <= 0x7fffffff) */ - { - /* six bytes */ - aBytesnIdx++ = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824); - aBytesnIdx++ = 128 + (nChr >>> 24 & 63); - aBytesnIdx++ = 128 + (nChr >>> 18 & 63); - aBytesnIdx++ = 128 + (nChr >>> 12 & 63); - aBytesnIdx++ = 128 + (nChr >>> 6 & 63); - aBytesnIdx++ = 128 + (nChr & 63); - } - } - - return aBytes; -} - - -// -// Converts all characters in a string that have equivalent entities to their ampersand/entity names. -// Based on https://gist.github.com/jonathantneal/6093551 -// -Convert.string_to_html_entities = (function() -{ - 'use strict'; - - var data = '34quot38amp39apos60lt62gt160nbsp161iexcl162cent163pound164curren165yen166brvbar167sect168uml169copy170ordf171laquo172not173shy174reg175macr176deg177plusmn178sup2179sup3180acute181micro182para183middot184cedil185sup1186ordm187raquo188frac14189frac12190frac34191iquest192Agrave193Aacute194Acirc195Atilde196Auml197Aring198AElig199Ccedil200Egrave201Eacute202Ecirc203Euml204Igrave205Iacute206Icirc207Iuml208ETH209Ntilde210Ograve211Oacute212Ocirc213Otilde214Ouml215times216Oslash217Ugrave218Uacute219Ucirc220Uuml221Yacute222THORN223szlig224agrave225aacute226acirc227atilde228auml229aring230aelig231ccedil232egrave233eacute234ecirc235euml236igrave237iacute238icirc239iuml240eth241ntilde242ograve243oacute244ocirc245otilde246ouml247divide248oslash249ugrave250uacute251ucirc252uuml253yacute254thorn255yuml402fnof913Alpha914Beta915Gamma916Delta917Epsilon918Zeta919Eta920Theta921Iota922Kappa923Lambda924Mu925Nu926Xi927Omicron928Pi929Rho931Sigma932Tau933Upsilon934Phi935Chi936Psi937Omega945alpha946beta947gamma948delta949epsilon950zeta951eta952theta953iota954kappa955lambda956mu957nu958xi959omicron960pi961rho962sigmaf963sigma964tau965upsilon966phi967chi968psi969omega977thetasym978upsih982piv8226bull8230hellip8242prime8243Prime8254oline8260frasl8472weierp8465image8476real8482trade8501alefsym8592larr8593uarr8594rarr8595darr8596harr8629crarr8656lArr8657uArr8658rArr8659dArr8660hArr8704forall8706part8707exist8709empty8711nabla8712isin8713notin8715ni8719prod8721sum8722minus8727lowast8730radic8733prop8734infin8736ang8743and8744or8745cap8746cup8747int8756there48764sim8773cong8776asymp8800ne8801equiv8804le8805ge8834sub8835sup8836nsub8838sube8839supe8853oplus8855otimes8869perp8901sdot8968lceil8969rceil8970lfloor8971rfloor9001lang9002rang9674loz9824spades9827clubs9829hearts9830diams338OElig339oelig352Scaron353scaron376Yuml710circ732tilde8194ensp8195emsp8201thinsp8204zwnj8205zwj8206lrm8207rlm8211ndash8212mdash8216lsquo8217rsquo8218sbquo8220ldquo8221rdquo8222bdquo8224dagger8225Dagger8240permil8249lsaquo8250rsaquo8364euro'; - var charCodes = data.split(/A-z+/); - var entities = data.split(/\d+/).slice(1); - - return function encodeHTMLEntities(text) - { - return text.replace(/\u00A0-\u2666<>"'&/g, function (match) - { - var charCode = String(match.charCodeAt(0)); - var index = charCodes.indexOf(charCode); - return '&' + (entitiesindex ? entitiesindex : '#' + charCode) + ';'; - }); - }; -})();
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/Core.js
Deleted
@@ -1,26 +0,0 @@ - -// TODO: requires function for checking existence of dependencies - - -function namespace(name) -{ - // Ensure all nested namespaces are created only once - - var ns_list = name.split("."); - var parent_ns = window; - - for (var i in ns_list) - { - var ns_name = ns_listi; - if (!(ns_name in parent_ns)) - parent_nsns_name = { }; - - parent_ns = parent_nsns_name; - } -} - - -function multiline(fn) -{ - return fn.toString().split(/\n/).slice(1, -1).join("\n"); -}
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/DOM.js
Deleted
@@ -1,499 +0,0 @@ - -namespace("DOM.Node"); -namespace("DOM.Event"); -namespace("DOM.Applet"); - - - -// -// ===================================================================================================================== -// ----- DOCUMENT NODE/ELEMENT EXTENSIONS ------------------------------------------------------------------------------ -// ===================================================================================================================== -// - - - -DOM.Node.Get = function(id) -{ - return document.getElementById(id); -} - - -// -// Set node position -// -DOM.Node.SetPosition = function(node, position) -{ - node.style.left = position0; - node.style.top = position1; -} -DOM.Node.SetX = function(node, x) -{ - node.style.left = x; -} -DOM.Node.SetY = function(node, y) -{ - node.style.top = y; -} - - -// -// Get the absolute position of a HTML element on the page -// -DOM.Node.GetPosition = function(element, account_for_scroll) -{ - // Recurse up through parents, summing offsets from their parent - var x = 0, y = 0; - for (var node = element; node != null; node = node.offsetParent) - { - x += node.offsetLeft; - y += node.offsetTop; - } - - if (account_for_scroll) - { - // Walk up the hierarchy subtracting away any scrolling - for (var node = element; node != document.body; node = node.parentNode) - { - x -= node.scrollLeft; - y -= node.scrollTop; - } - } - - return x, y; -} - - -// -// Set node size -// -DOM.Node.SetSize = function(node, size) -{ - node.style.width = size0; - node.style.height = size1; -} -DOM.Node.SetWidth = function(node, width) -{ - node.style.width = width; -} -DOM.Node.SetHeight = function(node, height) -{ - node.style.height = height; -} - - -// -// Get node OFFSET size: -// clientX includes padding -// offsetX includes padding and borders -// scrollX includes padding, borders and size of contained node -// -DOM.Node.GetSize = function(node) -{ - return node.offsetWidth, node.offsetHeight ; -} -DOM.Node.GetWidth = function(node) -{ - return node.offsetWidth; -} -DOM.Node.GetHeight = function(node) -{ - return node.offsetHeight; -} - - -// -// Set node opacity -// -DOM.Node.SetOpacity = function(node, value) -{ - node.style.opacity = value; -} - - -DOM.Node.SetColour = function(node, colour) -{ - node.style.color = colour; -} - - -// -// Hide a node by completely disabling its rendering (it no longer contributes to document layout) -// -DOM.Node.Hide = function(node) -{ - node.style.display = "none"; -} - - -// -// Show a node by restoring its influcen in document layout -// -DOM.Node.Show = function(node) -{ - node.style.display = "block"; -} - - -// -// Add a CSS class to a HTML element, specified last -// -DOM.Node.AddClass = function(node, class_name) -{ - // Ensure the class hasn't already been added - DOM.Node.RemoveClass(node, class_name); - node.className += " " + class_name; -} - - -// -// Remove a CSS class from a HTML element -// -DOM.Node.RemoveClass = function(node, class_name) -{ - // Remove all variations of where the class name can be in the string list - var regexp = new RegExp("\\b" + class_name + "\\b"); - node.className = node.className.replace(regexp, ""); -} - - - -// -// Check to see if a HTML element contains a class -// -DOM.Node.HasClass = function(node, class_name) -{ - var regexp = new RegExp("\\b" + class_name + "\\b"); - return regexp.test(node.className); -} - - -// -// Recursively search for a node with the given class name -// -DOM.Node.FindWithClass = function(parent_node, class_name, index) -{ - // Search the children looking for a node with the given class name - for (var i in parent_node.childNodes) - { - var node = parent_node.childNodesi; - if (DOM.Node.HasClass(node, class_name)) - { - if (index === undefined || index-- == 0) - return node; - } - - // Recurse into children - node = DOM.Node.FindWithClass(node, class_name); - if (node != null) - return node; - } - - return null; -} - - -// -// Check to see if one node logically contains another -// -DOM.Node.Contains = function(node, container_node) -{ - while (node != null && node != container_node) - node = node.parentNode; - return node != null; -} - - -// -// Create the HTML nodes specified in the text passed in -// Assumes there is only one root node in the text -// -DOM.Node.CreateHTML = function(html) -{ - var div = document.createElement("div"); - div.innerHTML = html; - - // First child may be a text node, followed by the created HTML - var child = div.firstChild; - if (child != null && child.nodeType == 3) - child = child.nextSibling; - return child; -} - - -// -// Make a copy of a HTML element, making it visible and clearing its ID to ensure it's not a duplicate -// -DOM.Node.Clone = function(name) -{ - // Get the template element and clone it, making sure it's renderable - var node = DOM.Node.Get(name); - node = node.cloneNode(true); - node.id = null; - node.style.display = "block"; - return node; -} - - -// -// Append an arbitrary block of HTML to an existing node -// -DOM.Node.AppendHTML = function(node, html) -{ - var child = DOM.Node.CreateHTML(html); - node.appendChild(child); - return child; -} - - -// -// Append a div that clears the float style -// -DOM.Node.AppendClearFloat = function(node) -{ - var div = document.createElement("div"); - div.style.clear = "both"; - node.appendChild(div); -} - - -// -// Check to see that the object passed in is an instance of a DOM node -// -DOM.Node.IsNode = function(object) -{ - return object instanceof Element; -} - - -// -// Create an "iframe shim" so that elements within it render over a Java Applet -// http://web.archive.org/web/20110707212850/http://www.oratransplant.nl/2007/10/26/using-iframe-shim-to-partly-cover-a-java-applet/ -// -DOM.Node.CreateShim = function(parent) -{ - var shimmer = document.createElement("iframe"); - - // Position the shimmer so that it's the same location/size as its parent - shimmer.style.position = "fixed"; - shimmer.style.left = parent.style.left; - shimmer.style.top = parent.style.top; - shimmer.style.width = parent.offsetWidth; - shimmer.style.height = parent.offsetHeight; - - // We want the shimmer to be one level below its contents - shimmer.style.zIndex = parent.style.zIndex - 1; - - // Ensure its empty - shimmer.setAttribute("frameborder", "0"); - shimmer.setAttribute("src", ""); - - // Add to the document and the parent - document.body.appendChild(shimmer); - parent.Shimmer = shimmer; - return shimmer; -} - - - -// -// ===================================================================================================================== -// ----- EVENT HANDLING EXTENSIONS ------------------------------------------------------------------------------------- -// ===================================================================================================================== -// - - - -// -// Retrieves the event from the first parameter passed into an HTML event -// -DOM.Event.Get = function(evt) -{ - // Internet explorer doesn't pass the event - return window.event || evt; -} - - -// -// Retrieves the element that triggered an event from the event object -// -DOM.Event.GetNode = function(evt) -{ - evt = DOM.Event.Get(evt); - - // Get the target element - var element; - if (evt.target) - element = evt.target; - else if (e.srcElement) - element = evt.srcElement; - - // Default Safari bug - if (element.nodeType == 3) - element = element.parentNode; - - return element; -} - - -// -// Stop default action for an event -// -DOM.Event.StopDefaultAction = function(evt) -{ - if (evt && evt.preventDefault) - evt.preventDefault(); - else if (window.event && window.event.returnValue) - window.event.returnValue = false; -} - - -// -// Stops events bubbling up to parent event handlers -// -DOM.Event.StopPropagation = function(evt) -{ - evt = DOM.Event.Get(evt); - if (evt) - { - evt.cancelBubble = true; - if (evt.stopPropagation) - evt.stopPropagation(); - } -} - - -// -// Stop both event default action and propagation -// -DOM.Event.StopAll = function(evt) -{ - DOM.Event.StopDefaultAction(evt); - DOM.Event.StopPropagation(evt); -} - - -// -// Adds an event handler to an event -// -DOM.Event.AddHandler = function(obj, evt, func) -{ - if (obj) - { - if (obj.addEventListener) - obj.addEventListener(evt, func, false); - else if (obj.attachEvent) - obj.attachEvent("on" + evt, func); - } -} - - -// -// Removes an event handler from an event -// -DOM.Event.RemoveHandler = function(obj, evt, func) -{ - if (obj) - { - if (obj.removeEventListener) - obj.removeEventListener(evt, func, false); - else if (obj.detachEvent) - obj.detachEvent("on" + evt, func); - } -} - - -// -// Get the position of the mouse cursor, page relative -// -DOM.Event.GetMousePosition = function(evt) -{ - evt = DOM.Event.Get(evt); - - var px = 0; - var py = 0; - if (evt.pageX || evt.pageY) - { - px = evt.pageX; - py = evt.pageY; - } - else if (evt.clientX || evt.clientY) - { - px = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - py = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return px, py; -} - - - -// -// ===================================================================================================================== -// ----- JAVA APPLET EXTENSIONS ---------------------------------------------------------------------------------------- -// ===================================================================================================================== -// - - - -// -// Create an applet element for loading a Java applet, attaching it to the specified node -// -DOM.Applet.Load = function(dest_id, id, code, archive) -{ - // Lookup the applet destination - var dest = DOM.Node.Get(dest_id); - if (!dest) - return; - - // Construct the applet element and add it to the destination - Debug.Log("Injecting applet DOM code"); - var applet = "<applet id='" + id + "' code='" + code + "' archive='" + archive + "'"; - applet += " width='" + dest.offsetWidth + "' height='" + dest.offsetHeight + "'>"; - applet += "</applet>"; - dest.innerHTML = applet; -} - - -// -// Moves and resizes a named applet so that it fits in the destination div element. -// The applet must be contained by a div element itself. This container div is moved along -// with the applet. -// -DOM.Applet.Move = function(dest_div, applet, z_index, hide) -{ - if (!applet || !dest_div) - return; - - // Before modifying any location information, hide the applet so that it doesn't render over - // any newly visible elements that appear while the location information is being modified. - if (hide) - applet.style.visibility = "hidden"; - - // Get its view rect - var pos = DOM.Node.GetPosition(dest_div); - var w = dest_div.offsetWidth; - var h = dest_div.offsetHeight; - - // It needs to be embedded in a <div> for correct scale/position adjustment - var container = applet.parentNode; - if (!container || container.localName != "div") - { - Debug.Log("ERROR: Couldn't find source applet's div container"); - return; - } - - // Reposition and resize the containing div element - container.style.left = pos0; - container.style.top = pos1; - container.style.width = w; - container.style.height = h; - container.style.zIndex = z_index; - - // Resize the applet itself - applet.style.width = w; - applet.style.height = h; - - // Everything modified, safe to show - applet.style.visibility = "visible"; -}
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/Keyboard.js
Deleted
@@ -1,149 +0,0 @@ - -namespace("Keyboard") - - -// ===================================================================================================================== -// Key codes copied from closure-library -// https://code.google.com/p/closure-library/source/browse/closure/goog/events/keycodes.js -// --------------------------------------------------------------------------------------------------------------------- -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -Keyboard.Codes = { - WIN_KEY_FF_LINUX : 0, - MAC_ENTER : 3, - BACKSPACE : 8, - TAB : 9, - NUM_CENTER : 12, // NUMLOCK on FF/Safari Mac - ENTER : 13, - SHIFT : 16, - CTRL : 17, - ALT : 18, - PAUSE : 19, - CAPS_LOCK : 20, - ESC : 27, - SPACE : 32, - PAGE_UP : 33, // also NUM_NORTH_EAST - PAGE_DOWN : 34, // also NUM_SOUTH_EAST - END : 35, // also NUM_SOUTH_WEST - HOME : 36, // also NUM_NORTH_WEST - LEFT : 37, // also NUM_WEST - UP : 38, // also NUM_NORTH - RIGHT : 39, // also NUM_EAST - DOWN : 40, // also NUM_SOUTH - PRINT_SCREEN : 44, - INSERT : 45, // also NUM_INSERT - DELETE : 46, // also NUM_DELETE - ZERO : 48, - ONE : 49, - TWO : 50, - THREE : 51, - FOUR : 52, - FIVE : 53, - SIX : 54, - SEVEN : 55, - EIGHT : 56, - NINE : 57, - FF_SEMICOLON : 59, // Firefox (Gecko) fires this for semicolon instead of 186 - FF_EQUALS : 61, // Firefox (Gecko) fires this for equals instead of 187 - FF_DASH : 173, // Firefox (Gecko) fires this for dash instead of 189 - QUESTION_MARK : 63, // needs localization - A : 65, - B : 66, - C : 67, - D : 68, - E : 69, - F : 70, - G : 71, - H : 72, - I : 73, - J : 74, - K : 75, - L : 76, - M : 77, - N : 78, - O : 79, - P : 80, - Q : 81, - R : 82, - S : 83, - T : 84, - U : 85, - V : 86, - W : 87, - X : 88, - Y : 89, - Z : 90, - META : 91, // WIN_KEY_LEFT - WIN_KEY_RIGHT : 92, - CONTEXT_MENU : 93, - NUM_ZERO : 96, - NUM_ONE : 97, - NUM_TWO : 98, - NUM_THREE : 99, - NUM_FOUR : 100, - NUM_FIVE : 101, - NUM_SIX : 102, - NUM_SEVEN : 103, - NUM_EIGHT : 104, - NUM_NINE : 105, - NUM_MULTIPLY : 106, - NUM_PLUS : 107, - NUM_MINUS : 109, - NUM_PERIOD : 110, - NUM_DIVISION : 111, - F1 : 112, - F2 : 113, - F3 : 114, - F4 : 115, - F5 : 116, - F6 : 117, - F7 : 118, - F8 : 119, - F9 : 120, - F10 : 121, - F11 : 122, - F12 : 123, - NUMLOCK : 144, - SCROLL_LOCK : 145, - - // OS-specific media keys like volume controls and browser controls. - FIRST_MEDIA_KEY : 166, - LAST_MEDIA_KEY : 183, - - SEMICOLON : 186, // needs localization - DASH : 189, // needs localization - EQUALS : 187, // needs localization - COMMA : 188, // needs localization - PERIOD : 190, // needs localization - SLASH : 191, // needs localization - APOSTROPHE : 192, // needs localization - TILDE : 192, // needs localization - SINGLE_QUOTE : 222, // needs localization - OPEN_SQUARE_BRACKET : 219, // needs localization - BACKSLASH : 220, // needs localization - CLOSE_SQUARE_BRACKET: 221, // needs localization - WIN_KEY : 224, - MAC_FF_META : 224, // Firefox (Gecko) fires this for the meta key instead of 91 - MAC_WK_CMD_LEFT : 91, // WebKit Left Command key fired, same as META - MAC_WK_CMD_RIGHT : 93, // WebKit Right Command key fired, different from META - WIN_IME : 229, - - // We've seen users whose machines fire this keycode at regular one - // second intervals. The common thread among these users is that - // they're all using Dell Inspiron laptops, so we suspect that this - // indicates a hardware/bios problem. - // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx - PHANTOM : 255 -}; -// =====================================================================================================================
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/LocalStore.js
Deleted
@@ -1,40 +0,0 @@ - -namespace("LocalStore"); - - -LocalStore.Set = function(class_name, class_id, variable_id, data) -{ - try - { - if (typeof(Storage) != "undefined") - { - var name = class_name + "_" + class_id + "_" + variable_id; - localStoragename = JSON.stringify(data); - } - } - catch (e) - { - console.log("Local Storage Set Error: " + e.message); - } -} - - -LocalStore.Get = function(class_name, class_id, variable_id, default_data) -{ - try - { - if (typeof(Storage) != "undefined") - { - var name = class_name + "_" + class_id + "_" + variable_id; - var data = localStoragename - if (data) - return JSON.parse(data); - } - } - catch (e) - { - console.log("Local Storage Get Error: " + e.message); - } - - return default_data; -} \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/Mouse.js
Deleted
@@ -1,83 +0,0 @@ - -namespace("Mouse"); - - -Mouse.State =(function() -{ - function State(event) - { - // Get button press states - if (typeof event.buttons != "undefined") - { - // Firefox - this.Left = (event.buttons & 1) != 0; - this.Right = (event.buttons & 2) != 0; - this.Middle = (event.buttons & 4) != 0; - } - else - { - // Chrome - this.Left = (event.button == 0); - this.Middle = (event.button == 1); - this.Right = (event.button == 2); - } - - // Get page-relative mouse position - this.Position = DOM.Event.GetMousePosition(event); - - // Get wheel delta - var delta = 0; - if (event.wheelDelta) - delta = event.wheelDelta / 120; // IE/Opera - else if (event.detail) - delta = -event.detail / 3; // Mozilla - this.WheelDelta = delta; - - // Get the mouse position delta - // Requires Pointer Lock API support - this.PositionDelta = - event.movementX || event.mozMovementX || event.webkitMovementX || 0, - event.movementY || event.mozMovementY || event.webkitMovementY || 0 - ; - } - - return State; -})(); - - -// -// Basic Pointer Lock API support -// https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock -// http://www.chromium.org/developers/design-documents/mouse-lock -// -// Note that API has not been standardised yet so browsers can implement functions with prefixes -// - - -Mouse.PointerLockSupported = function() -{ - return 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; -} - - -Mouse.RequestPointerLock = function(element) -{ - element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; - if (element.requestPointerLock) - element.requestPointerLock(); -} - - -Mouse.ExitPointerLock = function() -{ - document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; - if (document.exitPointerLock) - document.exitPointerLock(); -} - - -// Can use this element to detect whether pointer lock is enabled (returns non-null) -Mouse.PointerLockElement = function() -{ - return document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement; -}
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/Core/Code/MurmurHash3.js
Deleted
@@ -1,68 +0,0 @@ - -namespace("Hash"); - -/** - * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011) - * - * @author <a href="mailto:gary.court@gmail.com">Gary Court</a> - * @see http://github.com/garycourt/murmurhash-js - * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a> - * @see http://sites.google.com/site/murmurhash/ - * - * @param {string} key ASCII only - * @param {number} seed Positive integer only - * @return {number} 32-bit positive integer hash - */ - -Hash.Murmur3 = function(key, seed) -{ - var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i; - - remainder = key.length & 3; // key.length % 4 - bytes = key.length - remainder; - h1 = seed; - c1 = 0xcc9e2d51; - c2 = 0x1b873593; - i = 0; - - while (i < bytes) { - k1 = - ((key.charCodeAt(i) & 0xff)) | - ((key.charCodeAt(++i) & 0xff) << 8) | - ((key.charCodeAt(++i) & 0xff) << 16) | - ((key.charCodeAt(++i) & 0xff) << 24); - ++i; - - k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; - - h1 ^= k1; - h1 = (h1 << 13) | (h1 >>> 19); - h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; - h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); - } - - k1 = 0; - - switch (remainder) { - case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; - case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; - case 1: k1 ^= (key.charCodeAt(i) & 0xff); - - k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; - h1 ^= k1; - } - - h1 ^= key.length; - - h1 ^= h1 >>> 16; - h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; - h1 ^= h1 >>> 13; - h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; - h1 ^= h1 >>> 16; - - return h1 >>> 0; -} \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/Button.js
Deleted
@@ -1,131 +0,0 @@ - -namespace("WM"); - - -WM.Button = (function() -{ - var template_html = "<div class='Button notextsel'></div>"; - - - function Button(text, x, y, opts) - { - this.OnClick = null; - this.Toggle = opts && opts.toggle; - - this.Node = DOM.Node.CreateHTML(template_html); - - // Set node dimensions - this.SetPosition(x, y); - if (opts && opts.w && opts.h) - this.SetSize(opts.w, opts.h); - - // Override the default class name - if (opts && opts.class) - this.Node.className = opts.class; - - this.SetText(text); - - // Create the mouse press event handlers - DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); - this.OnMouseOutDelegate = Bind(OnMouseUp, this, false); - this.OnMouseUpDelegate = Bind(OnMouseUp, this, true); - } - - - Button.prototype.SetPosition = function(x, y) - { - this.Position = x, y ; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - Button.prototype.SetSize = function(w, h) - { - this.Size = w, h ; - DOM.Node.SetSize(this.Node, this.Size); - } - - - Button.prototype.SetText = function(text) - { - this.Node.innerHTML = text; - } - - - Button.prototype.SetOnClick = function(on_click) - { - this.OnClick = on_click; - } - - - Button.prototype.SetState = function(pressed) - { - if (pressed) - DOM.Node.AddClass(this.Node, "ButtonHeld"); - else - DOM.Node.RemoveClass(this.Node, "ButtonHeld"); - } - - - Button.prototype.ToggleState = function() - { - if (DOM.Node.HasClass(this.Node, "ButtonHeld")) - this.SetState(false); - else - this.SetState(true); - } - - - Button.prototype.IsPressed = function() - { - return DOM.Node.HasClass(this.Node, "ButtonHeld"); - } - - - function OnMouseDown(self, evt) - { - // Decide how to set the button state - if (self.Toggle) - self.ToggleState(); - else - self.SetState(true); - - // Activate release handlers - DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - DOM.Event.StopAll(evt); - } - - - function OnMouseUp(self, confirm, evt) - { - if (confirm) - { - // Only release for non-toggles - if (!self.Toggle) - self.SetState(false); - } - else - { - // Decide how to set the button state - if (self.Toggle) - self.ToggleState(); - else - self.SetState(false); - } - - // Remove release handlers - DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - // Call the click handler if this is a button press - if (confirm && self.OnClick) - self.OnClick(self); - - DOM.Event.StopAll(evt); - } - - - return Button; -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js
Deleted
@@ -1,237 +0,0 @@ - -namespace("WM"); - - -WM.ComboBoxPopup = (function() -{ - var body_template_html = "<div class='ComboBoxPopup'></div>"; - - var item_template_html = " \ - <div class='ComboBoxPopupItem notextsel'> \ - <div class='ComboBoxPopupItemText'></div> \ - <div class='ComboBoxPopupItemIcon'><img src='BrowserLibImages/tick.gif'></div> \ - <div style='clear:both'></div> \ - </div>"; - - - function ComboBoxPopup(combo_box) - { - this.ComboBox = combo_box; - this.ParentNode = combo_box.Node; - this.ValueNodes = ; - - // Create the template node - this.Node = DOM.Node.CreateHTML(body_template_html); - - DOM.Event.AddHandler(this.Node, "mousedown", Bind(SelectItem, this)); - this.CancelDelegate = Bind(this, "Cancel"); - } - - - ComboBoxPopup.prototype.SetValues = function(values) - { - // Clear existing values - this.Node.innerHTML = ""; - - // Generate HTML nodes for each value - this.ValueNodes = ; - for (var i in values) - { - var item_node = DOM.Node.CreateHTML(item_template_html); - var text_node = DOM.Node.FindWithClass(item_node, "ComboBoxPopupItemText"); - - item_node.Value = valuesi; - text_node.innerHTML = valuesi; - - this.Node.appendChild(item_node); - this.ValueNodes.push(item_node); - } - } - - - ComboBoxPopup.prototype.Show = function(selection_index) - { - // Initially match the position of the parent node - var pos = DOM.Node.GetPosition(this.ParentNode); - DOM.Node.SetPosition(this.Node, pos); - - // Take the width/z-index from the parent node - this.Node.style.width = this.ParentNode.offsetWidth; - this.Node.style.zIndex = this.ParentNode.style.zIndex + 1; - - // Setup event handlers - DOM.Event.AddHandler(document.body, "mousedown", this.CancelDelegate); - - // Show the popup so that the HTML layout engine kicks in before - // the layout info is used below - this.ParentNode.appendChild(this.Node); - - // Show/hide the tick image based on which node is selected - for (var i in this.ValueNodes) - { - var node = this.ValueNodesi; - var icon_node = DOM.Node.FindWithClass(node, "ComboBoxPopupItemIcon"); - - if (i == selection_index) - { - icon_node.style.display = "block"; - - // Also, shift the popup up so that the mouse is over the selected item and is highlighted - var item_pos = DOM.Node.GetPosition(this.ValueNodesselection_index); - var diff_pos = item_pos0 - pos0, item_pos1 - pos1 ; - pos = pos0 - diff_pos0, pos1 - diff_pos1 ; - } - else - { - icon_node.style.display = "none"; - } - } - - DOM.Node.SetPosition(this.Node, pos); - } - - - ComboBoxPopup.prototype.Hide = function() - { - DOM.Event.RemoveHandler(document.body, "mousedown", this.CancelDelegate); - this.ParentNode.removeChild(this.Node); - } - - - function SelectItem(self, evt) - { - // Search for which item node is being clicked on - var node = DOM.Event.GetNode(evt); - for (var i in self.ValueNodes) - { - var value_node = self.ValueNodesi; - if (DOM.Node.Contains(node, value_node)) - { - // Set the value on the combo box - self.ComboBox.SetValue(value_node.Value); - self.Hide(); - break; - } - } - } - - - function Cancel(self, evt) - { - // Don't cancel if the mouse up is anywhere on the popup or combo box - var node = DOM.Event.GetNode(evt); - if (!DOM.Node.Contains(node, self.Node) && - !DOM.Node.Contains(node, self.ParentNode)) - { - self.Hide(); - } - - - DOM.Event.StopAll(evt); - } - - - return ComboBoxPopup; -})(); - - -WM.ComboBox = (function() -{ - var template_html = " \ - <div class='ComboBox'> \ - <div class='ComboBoxText notextsel'></div> \ - <div class='ComboBoxIcon'><img src='BrowserLibImages/up_down.gif'></div> \ - <div style='clear:both'></div> \ - </div>"; - - - function ComboBox() - { - this.OnChange = null; - - // Create the template node and locate key nodes - this.Node = DOM.Node.CreateHTML(template_html); - this.TextNode = DOM.Node.FindWithClass(this.Node, "ComboBoxText"); - - // Create a reusable popup - this.Popup = new WM.ComboBoxPopup(this); - - // Set an empty set of values - this.SetValues(); - this.SetValue("<empty>"); - - // Create the mouse press event handlers - DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); - this.OnMouseOutDelegate = Bind(OnMouseUp, this, false); - this.OnMouseUpDelegate = Bind(OnMouseUp, this, true); - } - - - ComboBox.prototype.SetOnChange = function(on_change) - { - this.OnChange = on_change; - } - - - ComboBox.prototype.SetValues = function(values) - { - this.Values = values; - this.Popup.SetValues(values); - } - - - ComboBox.prototype.SetValue = function(value) - { - // Set the value and its HTML rep - var old_value = this.Value; - this.Value = value; - this.TextNode.innerHTML = value; - - // Call change handler - if (this.OnChange) - this.OnChange(value, old_value); - } - - - ComboBox.prototype.GetValue = function() - { - return this.Value; - } - - - function OnMouseDown(self, evt) - { - // If this check isn't made, the click will trigger from the popup, too - var node = DOM.Event.GetNode(evt); - if (DOM.Node.Contains(node, self.Node)) - { - // Add the depression class and activate release handlers - DOM.Node.AddClass(self.Node, "ComboBoxPressed"); - DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - DOM.Event.StopAll(evt); - } - } - - - function OnMouseUp(self, confirm, evt) - { - // Remove depression class and remove release handlers - DOM.Node.RemoveClass(self.Node, "ComboBoxPressed"); - DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - // If this is a confirmed press and there are some values in the list, show the popup - if (confirm && self.Values.length > 0) - { - var selection_index = self.Values.indexOf(self.Value); - self.Popup.Show(selection_index); - } - - DOM.Event.StopAll(evt); - } - - - return ComboBox; -})();
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/Container.js
Deleted
@@ -1,34 +0,0 @@ - -namespace("WM"); - - -WM.Container = (function() -{ - var template_html = "<div class='Container'></div>"; - - - function Container(x, y, w, h) - { - // Create a simple container node - this.Node = DOM.Node.CreateHTML(template_html); - this.SetPosition(x, y); - this.SetSize(w, h); - } - - - Container.prototype.SetPosition = function(x, y) - { - this.Position = x, y ; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - Container.prototype.SetSize = function(w, h) - { - this.Size = w, h ; - DOM.Node.SetSize(this.Node, this.Size); - } - - - return Container; -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/EditBox.js
Deleted
@@ -1,119 +0,0 @@ - -namespace("WM"); - - -WM.EditBox = (function() -{ - var template_html = " \ - <div class='EditBoxContainer'> \ - <div class='EditBoxLabel'>Label</div> \ - <input class='EditBox'> \ - </div>"; - - - function EditBox(x, y, w, h, label, text) - { - this.ChangeHandler = null; - - // Create node and locate its internal nodes - this.Node = DOM.Node.CreateHTML(template_html); - this.LabelNode = DOM.Node.FindWithClass(this.Node, "EditBoxLabel"); - this.EditNode = DOM.Node.FindWithClass(this.Node, "EditBox"); - - // Set label and value - this.LabelNode.innerHTML = label; - this.SetValue(text); - - this.SetPosition(x, y); - this.SetSize(w, h); - - this.PreviousValue = ""; - - // Hook up the event handlers - DOM.Event.AddHandler(this.EditNode, "focus", Bind(OnFocus, this)); - DOM.Event.AddHandler(this.EditNode, "keypress", Bind(OnKeyPress, this)); - DOM.Event.AddHandler(this.EditNode, "keydown", Bind(OnKeyDown, this)); - } - - - EditBox.prototype.SetPosition = function(x, y) - { - this.Position = x, y ; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - EditBox.prototype.SetSize = function(w, h) - { - this.Size = w, h ; - DOM.Node.SetSize(this.EditNode, this.Size); - } - - - EditBox.prototype.SetChangeHandler = function(handler) - { - this.ChangeHandler = handler; - } - - - EditBox.prototype.SetValue = function(value) - { - if (this.EditNode) - this.EditNode.value = value; - } - - - EditBox.prototype.GetValue = function() - { - if (this.EditNode) - return this.EditNode.value; - - return null; - } - - - EditBox.prototype.LoseFocus = function() - { - if (this.EditNode) - this.EditNode.blur(); - } - - - function OnFocus(self, evt) - { - // Backup on focus - self.PreviousValue = self.EditNode.value; - } - - - function OnKeyPress(self, evt) - { - // Allow enter to confirm the text only when there's data - if (evt.keyCode == 13 && self.EditNode.value != "" && self.ChangeHandler) - { - var focus = self.ChangeHandler(self.EditNode); - if (!focus) - self.EditNode.blur(); - self.PreviousValue = ""; - } - } - - - function OnKeyDown(self, evt) - { - // Allow escape to cancel any text changes - if (evt.keyCode == 27) - { - // On initial edit of the input, escape should NOT replace with the empty string - if (self.PreviousValue != "") - { - self.EditNode.value = self.PreviousValue; - } - - self.EditNode.blur(); - } - } - - - return EditBox; -})();
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/Grid.js
Deleted
@@ -1,248 +0,0 @@ - -namespace("WM"); - - -WM.GridRows = (function() -{ - function GridRows(parent_object) - { - this.ParentObject = parent_object; - - // Array of rows in the order they were added - this.Rows = ; - - // Collection of custom row indexes for fast lookup - this.Indexes = { }; - } - - - GridRows.prototype.AddIndex = function(cell_field_name) - { - var index = { }; - - // Go through existing rows and add to the index - for (var i in this.Rows) - { - var row = this.Rowsi; - if (cell_field_name in row.CellData) - { - var cell_field = row.CellDatacell_field_name; - indexcell_field = row; - } - } - - this.Indexescell_field_name = index; - } - - - GridRows.prototype.ClearIndex = function(index_name) - { - this.Indexesindex_name = { }; - } - - GridRows.prototype.AddRowToIndex = function(index_name, cell_data, row) - { - this.Indexesindex_namecell_data = row; - } - - - GridRows.prototype.Add = function(cell_data, row_classes, cell_classes) - { - var row = new WM.GridRow(this.ParentObject, cell_data, row_classes, cell_classes); - this.Rows.push(row); - return row; - } - - - GridRows.prototype.GetBy = function(cell_field_name, cell_data) - { - var index = this.Indexescell_field_name; - return indexcell_data; - } - - - GridRows.prototype.Clear = function() - { - // Remove all node references from the parent - for (var i in this.Rows) - { - var row = this.Rowsi; - row.Parent.BodyNode.removeChild(row.Node); - } - - // Clear all indexes - for (var i in this.Indexes) - this.Indexesi = { }; - - this.Rows = ; - } - - - return GridRows; -})(); - - -WM.GridRow = (function() -{ - var template_html = "<div class='GridRow'></div>"; - - - // - // 'cell_data' is an object with a variable number of fields. - // Any fields prefixed with an underscore are hidden. - // - function GridRow(parent, cell_data, row_classes, cell_classes) - { - // Setup data - this.Parent = parent; - this.IsOpen = true; - this.AnimHandle = null; - this.Rows = new WM.GridRows(this); - this.CellData = cell_data; - this.CellNodes = { } - - // Create the main row node - this.Node = DOM.Node.CreateHTML(template_html); - if (row_classes) - DOM.Node.AddClass(this.Node, row_classes); - - // Embed a pointer to the row in the root node so that it can be clicked - this.Node.GridRow = this; - - // Create nodes for each required cell - for (var attr in this.CellData) - { - if (this.CellData.hasOwnProperty(attr)) - { - var data = this.CellDataattr; - - // Update any grid row index references - if (attr in parent.Rows.Indexes) - parent.Rows.AddRowToIndex(attr, data, this); - - // Hide any cells with underscore prefixes - if (attr0 == "_") - continue; - - // Create a node for the cell and add any custom classes - var node = DOM.Node.AppendHTML(this.Node, "<div class='GridRowCell'></div>"); - if (cell_classes && attr in cell_classes) - DOM.Node.AddClass(node, cell_classesattr); - this.CellNodesattr = node; - - // If this is a Window Control, add its node to the cell - if (data instanceof Object && "Node" in data && DOM.Node.IsNode(data.Node)) - { - data.ParentNode = node; - node.appendChild(data.Node); - } - - else - { - // Otherwise just assign the data as text - node.innerHTML = data; - } - } - } - - // Add the body node for any children - if (!this.Parent.BodyNode) - this.Parent.BodyNode = DOM.Node.AppendHTML(this.Parent.Node, "<div class='GridRowBody'></div>"); - - // Add the row to the parent - this.Parent.BodyNode.appendChild(this.Node); - } - - - GridRow.prototype.Open = function() - { - // Don't allow open while animating - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - this.IsOpen = true; - - // Kick off open animation - var node = this.BodyNode; - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(node, val) }, - 0, this.Height, 0.2); - } - } - - - GridRow.prototype.Close = function() - { - // Don't allow close while animating - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - this.IsOpen = false; - - // Record height for the next open request - this.Height = this.BodyNode.offsetHeight; - - // Kick off close animation - var node = this.BodyNode; - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(node, val) }, - this.Height, 0, 0.2); - } - } - - - GridRow.prototype.Toggle = function() - { - if (this.IsOpen) - this.Close(); - else - this.Open(); - } - - - return GridRow; -})(); - - -WM.Grid = (function() -{ - var template_html = " \ - <div class='Grid'> \ - <div class='GridBody'></div> \ - </div>"; - - - function Grid() - { - this.Rows = new WM.GridRows(this); - - this.Node = DOM.Node.CreateHTML(template_html); - this.BodyNode = DOM.Node.FindWithClass(this.Node, "GridBody"); - - DOM.Event.AddHandler(this.Node, "dblclick", OnDblClick); - - var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; - DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); - } - - function OnDblClick(evt) - { - // Clicked on a header? - var node = DOM.Event.GetNode(evt); - if (DOM.Node.HasClass(node, "GridRowName")) - { - // Toggle rows open/close - var row = node.parentNode.GridRow; - if (row) - row.Toggle(); - } - } - - - function OnMouseScroll(self, evt) - { - var mouse_state = new Mouse.State(evt); - self.Node.scrollTop -= mouse_state.WheelDelta * 20; - } - - - return Grid; -})();
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/Label.js
Deleted
@@ -1,31 +0,0 @@ - -namespace("WM"); - - -WM.Label = (function() -{ - var template_html = "<div class='Label'></div>"; - - - function Label(x, y, text) - { - // Create the node - this.Node = DOM.Node.CreateHTML(template_html); - - // Allow position to be optional - if (x != null && y != null) - DOM.Node.SetPosition(this.Node, x, y); - - this.SetText(text); - } - - - Label.prototype.SetText = function(text) - { - if (text != null) - this.Node.innerHTML = text; - } - - - return Label; -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/Treeview.js
Deleted
@@ -1,352 +0,0 @@ - -namespace("WM"); - - -WM.Treeview = (function() -{ - var Margin = 10; - - - var tree_template_html = " \ - <div class='Treeview'> \ - <div class='TreeviewItemChildren' style='width:90%;float:left'></div> \ - <div class='TreeviewScrollbarInset'> \ - <div class='TreeviewScrollbar'></div> \ - </div> \ - <div style='clear:both'></div> \ - </div>"; - - - var item_template_html = " \ - <div class='TreeViewItem basicfont notextsel'> \ - <img src='' class='TreeviewItemImage'> \ - <div class='TreeviewItemText'></div> \ - <div style='clear:both'></div> \ - <div class='TreeviewItemChildren'></div> \ - <div style='clear:both'></div> \ - </div>"; - - - // TODO: Remove parent_node (required for stuff that doesn't use the WM yet) - function Treeview(x, y, width, height, parent_node) - { - // Cache initialisation options - this.ParentNode = parent_node; - this.Position = x, y ; - this.Size = width, height ; - - this.Node = null; - this.ScrollbarNode = null; - this.SelectedItem = null; - this.ContentsNode = null; - - // Setup options - this.HighlightOnHover = false; - this.EnableScrollbar = true; - this.HorizontalLayoutDepth = 1; - - // Generate an empty tree - this.Clear(); - } - - - Treeview.prototype.SetHighlightOnHover = function(highlight) - { - this.HighlightOnHover = highlight; - } - - - Treeview.prototype.SetEnableScrollbar = function(enable) - { - this.EnableScrollbar = enable; - } - - - Treeview.prototype.SetHorizontalLayoutDepth = function(depth) - { - this.HorizontalLayoutDepth = depth; - } - - - Treeview.prototype.SetNodeSelectedHandler = function(handler) - { - this.NodeSelectedHandler = handler; - } - - - Treeview.prototype.Clear = function() - { - this.RootItem = new WM.TreeviewItem(this, null, null, null, null); - this.GenerateHTML(); - } - - - Treeview.prototype.Root = function() - { - return this.RootItem; - } - - - Treeview.prototype.ClearSelection = function() - { - if (this.SelectedItem != null) - { - DOM.Node.RemoveClass(this.SelectedItem.Node, "TreeviewItemSelected"); - this.SelectedItem = null; - } - } - - - Treeview.prototype.SelectItem = function(item, mouse_pos) - { - // Notify the select handler - if (this.NodeSelectedHandler) - this.NodeSelectedHandler(item.Node, this.SelectedItem, item, mouse_pos); - - // Remove highlight from the old selection - this.ClearSelection(); - - // Swap in new selection and apply highlight - this.SelectedItem = item; - DOM.Node.AddClass(this.SelectedItem.Node, "TreeviewItemSelected"); - } - - - Treeview.prototype.GenerateHTML = function() - { - // Clone the template and locate important nodes - var old_node = this.Node; - this.Node = DOM.Node.CreateHTML(tree_template_html); - this.ChildrenNode = DOM.Node.FindWithClass(this.Node, "TreeviewItemChildren"); - this.ScrollbarNode = DOM.Node.FindWithClass(this.Node, "TreeviewScrollbar"); - - DOM.Node.SetPosition(this.Node, this.Position); - DOM.Node.SetSize(this.Node, this.Size); - - // Generate the contents of the treeview - GenerateTree(this, this.ChildrenNode, this.RootItem.Children, 0); - - // Cross-browser (?) means of adding a mouse wheel handler - var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; - DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); - - DOM.Event.AddHandler(this.Node, "dblclick", Bind(OnMouseDoubleClick, this)); - DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); - DOM.Event.AddHandler(this.Node, "mouseup", OnMouseUp); - - // Swap in the newly generated control node if it's already been attached to a parent - if (old_node && old_node.parentNode) - { - old_node.parentNode.removeChild(old_node); - this.ParentNode.appendChild(this.Node); - } - - if (this.EnableScrollbar) - { - this.UpdateScrollbar(); - DOM.Event.AddHandler(this.ScrollbarNode, "mousedown", Bind(OnMouseDown_Scrollbar, this)); - DOM.Event.AddHandler(this.ScrollbarNode, "mouseup", Bind(OnMouseUp_Scrollbar, this)); - DOM.Event.AddHandler(this.ScrollbarNode, "mouseout", Bind(OnMouseUp_Scrollbar, this)); - DOM.Event.AddHandler(this.ScrollbarNode, "mousemove", Bind(OnMouseMove_Scrollbar, this)); - } - - else - { - DOM.Node.Hide(DOM.Node.FindWithClass(this.Node, "TreeviewScrollbarInset")); - } - } - - - Treeview.prototype.UpdateScrollbar = function() - { - if (!this.EnableScrollbar) - return; - - var scrollbar_scale = Math.min((this.Node.offsetHeight - Margin * 2) / this.ChildrenNode.offsetHeight, 1); - this.ScrollbarNode.style.height = parseInt(scrollbar_scale * 100) + "%"; - - // Shift the scrollbar container along with the parent window - this.ScrollbarNode.parentNode.style.top = this.Node.scrollTop; - - var scroll_fraction = this.Node.scrollTop / (this.Node.scrollHeight - this.Node.offsetHeight); - var max_height = this.Node.offsetHeight - Margin; - var max_scrollbar_offset = max_height - this.ScrollbarNode.offsetHeight; - var scrollbar_offset = scroll_fraction * max_scrollbar_offset; - this.ScrollbarNode.style.top = scrollbar_offset; - } - - - function GenerateTree(self, parent_node, items, depth) - { - if (items.length == 0) - return null; - - for (var i in items) - { - var item = itemsi; - - // Create the node for this item and locate important nodes - var node = DOM.Node.CreateHTML(item_template_html); - var img = DOM.Node.FindWithClass(node, "TreeviewItemImage"); - var text = DOM.Node.FindWithClass(node, "TreeviewItemText"); - var children = DOM.Node.FindWithClass(node, "TreeviewItemChildren"); - - // Attach the item to the node - node.TreeviewItem = item; - item.Node = node; - - // Add the class which highlights selection on hover - if (self.HighlightOnHover) - DOM.Node.AddClass(node, "TreeviewItemHover"); - - // Instruct the children to wrap around - if (depth >= self.HorizontalLayoutDepth) - node.style.cssFloat = "left"; - - if (item.OpenImage == null || item.CloseImage == null) - { - // If there no images, remove the image node - node.removeChild(img); - } - else - { - // Set the image source to open - img.src = item.OpenImage.src; - img.style.width = item.OpenImage.width; - img.style.height = item.OpenImage.height; - item.ImageNode = img; - } - - // Setup the text to display - text.innerHTML = item.Label; - - // Add the div to the parent and recurse into children - parent_node.appendChild(node); - GenerateTree(self, children, item.Children, depth + 1); - item.ChildrenNode = children; - } - - // Clear the wrap-around - if (depth >= self.HorizontalLayoutDepth) - DOM.Node.AppendClearFloat(parent_node.parentNode); - } - - - function OnMouseScroll(self, evt) - { - // Get mouse wheel movement - var delta = evt.detail ? evt.detail * -1 : evt.wheelDelta; - delta *= 8; - - // Scroll the main window with wheel movement and clamp - self.Node.scrollTop -= delta; - self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2); - - self.UpdateScrollbar(); - } - - - function OnMouseDoubleClick(self, evt) - { - DOM.Event.StopDefaultAction(evt); - - // Get the tree view item being clicked, if any - var node = DOM.Event.GetNode(evt); - var tvitem = GetTreeviewItemFromNode(self, node); - if (tvitem == null) - return; - - if (tvitem.Children.length) - tvitem.Toggle(); - } - - - function OnMouseDown(self, evt) - { - DOM.Event.StopDefaultAction(evt); - - // Get the tree view item being clicked, if any - var node = DOM.Event.GetNode(evt); - var tvitem = GetTreeviewItemFromNode(self, node); - if (tvitem == null) - return; - - // If clicking on the image, expand any children - if (node.tagName == "IMG" && tvitem.Children.length) - { - tvitem.Toggle(); - } - - else - { - var mouse_pos = DOM.Event.GetMousePosition(evt); - self.SelectItem(tvitem, mouse_pos); - } - } - - - function OnMouseUp(evt) - { - // Event handler used merely to stop events bubbling up to containers - DOM.Event.StopPropagation(evt); - } - - - function OnMouseDown_Scrollbar(self, evt) - { - self.ScrollbarHeld = true; - - // Cache the mouse height relative to the scrollbar - self.LastY = evt.clientY; - self.ScrollY = self.Node.scrollTop; - - DOM.Node.AddClass(self.ScrollbarNode, "TreeviewScrollbarHeld"); - DOM.Event.StopDefaultAction(evt); - } - - - function OnMouseUp_Scrollbar(self, evt) - { - self.ScrollbarHeld = false; - DOM.Node.RemoveClass(self.ScrollbarNode, "TreeviewScrollbarHeld"); - } - - - function OnMouseMove_Scrollbar(self, evt) - { - if (self.ScrollbarHeld) - { - var delta_y = evt.clientY - self.LastY; - self.LastY = evt.clientY; - - var max_height = self.Node.offsetHeight - Margin; - var max_scrollbar_offset = max_height - self.ScrollbarNode.offsetHeight; - var max_contents_scroll = self.Node.scrollHeight - self.Node.offsetHeight; - var scale = max_contents_scroll / max_scrollbar_offset; - - // Increment the local float variable and assign, as scrollTop is of type int - self.ScrollY += delta_y * scale; - self.Node.scrollTop = self.ScrollY; - self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2); - - self.UpdateScrollbar(); - } - } - - - function GetTreeviewItemFromNode(self, node) - { - // Walk up toward the tree view node looking for this first item - while (node && node != self.Node) - { - if ("TreeviewItem" in node) - return node.TreeviewItem; - - node = node.parentNode; - } - - return null; - } - - return Treeview; -})();
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js
Deleted
@@ -1,109 +0,0 @@ - -namespace("WM"); - - -WM.TreeviewItem = (function() -{ - function TreeviewItem(treeview, name, data, open_image, close_image) - { - // Assign members - this.Treeview = treeview; - this.Label = name; - this.Data = data; - this.OpenImage = open_image; - this.CloseImage = close_image; - - this.Children = ; - - // The HTML node wrapping the item and its children - this.Node = null; - - // The HTML node storing the image for the open/close state feedback - this.ImageNode = null; - - // The HTML node storing just the children - this.ChildrenNode = null; - - // Animation handle for opening and closing the child nodes, only used - // if the tree view item as children - this.AnimHandle = null; - - // Open state of the item - this.IsOpen = true; - } - - - TreeviewItem.prototype.AddItem = function(name, data, open_image, close_image) - { - var item = new WM.TreeviewItem(this.Treeview, name, data, open_image, close_image); - this.Children.push(item); - return item; - } - - - TreeviewItem.prototype.Open = function() - { - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - // Swap to the open state - this.IsOpen = true; - if (this.ImageNode != null && this.OpenImage != null) - this.ImageNode.src = this.OpenImage.src; - - // Cache for closure binding - var child_node = this.ChildrenNode; - var end_height = this.StartHeight; - var treeview = this.Treeview; - - // Reveal the children and animate their height to max - this.ChildrenNode.style.display = "block"; - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(child_node, val) }, - 0, end_height, 0.2, - function() { treeview.UpdateScrollbar(); }); - - // Fade the children in - Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 0, 1, 0.2); - } - } - - - TreeviewItem.prototype.Close = function() - { - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - // Swap to the close state - this.IsOpen = false; - if (this.ImageNode != null && this.CloseImage != null) - this.ImageNode.src = this.CloseImage.src; - - // Cache for closure binding - var child_node = this.ChildrenNode; - var treeview = this.Treeview; - - // Mark the height of the item for reload later - this.StartHeight = child_node.offsetHeight; - - // Shrink the height of the children and hide them upon completion - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(child_node, val) }, - this.ChildrenNode.offsetHeight, 0, 0.2, - function() { child_node.style.display = "none"; treeview.UpdateScrollbar(); }); - - // Fade the children out - Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 1, 0, 0.2); - } - } - - - TreeviewItem.prototype.Toggle = function() - { - if (this.IsOpen) - this.Close(); - else - this.Open(); - } - - - return TreeviewItem; -})();
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/Window.js
Deleted
@@ -1,295 +0,0 @@ - -namespace("WM"); - - -WM.Window = (function() -{ - var template_html = multiline(function(){/* \ - <div class='Window'> - <div class='WindowTitleBar'> - <div class='WindowTitleBarText notextsel' style='float:left'>Window Title Bar</div> - <div class='WindowTitleBarClose notextsel' style='float:right'>✕</div> - </div> - <div class='WindowBody'> - </div> - <div class='WindowResizeHandle notextsel'>⋰</div> - </div> - */}); - - - function Window(manager, title, x, y, width, height, parent_node) - { - this.Manager = manager; - this.ParentNode = parent_node || document.body; - this.OnMove = null; - this.OnResize = null; - this.Visible = false; - this.AnimatedShow = false; - - // Clone the window template and locate key nodes within it - this.Node = DOM.Node.CreateHTML(template_html); - this.TitleBarNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBar"); - this.TitleBarTextNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarText"); - this.TitleBarCloseNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarClose"); - this.ResizeHandleNode = DOM.Node.FindWithClass(this.Node, "WindowResizeHandle"); - this.BodyNode = DOM.Node.FindWithClass(this.Node, "WindowBody"); - - // Setup the position and dimensions of the window - this.SetPosition(x, y); - this.SetSize(width, height); - - // Set the title text - this.TitleBarTextNode.innerHTML = title; - - // Hook up event handlers - DOM.Event.AddHandler(this.Node, "mousedown", Bind(this, "SetTop")); - DOM.Event.AddHandler(this.TitleBarNode, "mousedown", Bind(this, "BeginMove")); - DOM.Event.AddHandler(this.ResizeHandleNode, "mousedown", Bind(this, "BeginResize")); - DOM.Event.AddHandler(this.TitleBarCloseNode, "mouseup", Bind(this, "Hide")); - - // Create delegates for removable handlers - this.MoveDelegate = Bind(this, "Move"); - this.EndMoveDelegate = Bind(this, "EndMove") - this.ResizeDelegate = Bind(this, "Resize"); - this.EndResizeDelegate = Bind(this, "EndResize"); - } - - Window.prototype.SetOnMove = function(on_move) - { - this.OnMove = on_move; - } - - Window.prototype.SetOnResize = function(on_resize) - { - this.OnResize = on_resize; - } - - - Window.prototype.Show = function() - { - if (this.Node.parentNode != this.ParentNode) - { - this.ShowNoAnim(); - Anim.Animate(Bind(this, "OpenAnimation"), 0, 1, 1); - } - } - - - Window.prototype.ShowNoAnim = function() - { - // Add to the document - this.ParentNode.appendChild(this.Node); - this.AnimatedShow = false; - this.Visible = true; - } - - - Window.prototype.Hide = function(evt) - { - if (this.Node.parentNode == this.ParentNode && evt.button == 0) - { - if (this.AnimatedShow) - { - // Trigger animation that ends with removing the window from the document - Anim.Animate( - Bind(this, "CloseAnimation"), - 0, 1, 0.25, - Bind(this, "HideNoAnim")); - } - else - { - this.HideNoAnim(); - } - } - } - - - Window.prototype.HideNoAnim = function() - { - // Remove node - this.ParentNode.removeChild(this.Node); - this.Visible = false; - } - - - Window.prototype.SetTop = function() - { - this.Manager.SetTopWindow(this); - } - - - - Window.prototype.SetTitle = function(title) - { - this.TitleBarTextNode.innerHTML = title; - } - - - // TODO: Update this - Window.prototype.AddControl = function(control) - { - // Get all arguments to this function and replace the first with this window node - var args = .slice.call(arguments); - args0 = this.BodyNode; - - // Create the control and call its Init method with the modified arguments - var instance = new control(); - instance.Init.apply(instance, args); - - return instance; - } - - - Window.prototype.AddControlNew = function(control) - { - control.ParentNode = this.BodyNode; - this.BodyNode.appendChild(control.Node); - return control; - } - - - Window.prototype.Scale = function(t) - { - // Calculate window bounds centre/extents - var ext_x = this.Size0 / 2; - var ext_y = this.Size1 / 2; - var mid_x = this.Position0 + ext_x; - var mid_y = this.Position1 + ext_y; - - // Scale from the mid-point - DOM.Node.SetPosition(this.Node, mid_x - ext_x * t, mid_y - ext_y * t ); - DOM.Node.SetSize(this.Node, this.Size0 * t, this.Size1 * t ); - } - - - Window.prototype.OpenAnimation = function(val) - { - // Power ease in - var t = 1 - Math.pow(1 - val, 8); - this.Scale(t); - DOM.Node.SetOpacity(this.Node, 1 - Math.pow(1 - val, 8)); - this.AnimatedShow = true; - } - - - Window.prototype.CloseAnimation = function(val) - { - // Power ease out - var t = 1 - Math.pow(val, 4); - this.Scale(t); - DOM.Node.SetOpacity(this.Node, t); - } - - - Window.prototype.NotifyChange = function() - { - if (this.OnMove) - { - var pos = DOM.Node.GetPosition(this.Node); - this.OnMove(this, pos); - } - } - - - Window.prototype.BeginMove = function(evt) - { - // Calculate offset of the window from the mouse down position - var mouse_pos = DOM.Event.GetMousePosition(evt); - this.Offset = mouse_pos0 - this.Position0, mouse_pos1 - this.Position1 ; - - // Dynamically add handlers for movement and release - DOM.Event.AddHandler(document, "mousemove", this.MoveDelegate); - DOM.Event.AddHandler(document, "mouseup", this.EndMoveDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.Move = function(evt) - { - // Use the offset at the beginning of movement to drag the window around - var mouse_pos = DOM.Event.GetMousePosition(evt); - var offset = this.Offset; - var pos = mouse_pos0 - offset0, mouse_pos1 - offset1 ; - this.SetPosition(pos0, pos1); - - if (this.OnMove) - this.OnMove(this, pos); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.EndMove = function(evt) - { - // Remove handlers added during mouse down - DOM.Event.RemoveHandler(document, "mousemove", this.MoveDelegate); - DOM.Event.RemoveHandler(document, "mouseup", this.EndMoveDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.BeginResize = function(evt) - { - // Calculate offset of the window from the mouse down position - var mouse_pos = DOM.Event.GetMousePosition(evt); - this.MousePosBeforeResize = mouse_pos0, mouse_pos1 ; - this.SizeBeforeResize = this.Size; - - // Dynamically add handlers for movement and release - DOM.Event.AddHandler(document, "mousemove", this.ResizeDelegate); - DOM.Event.AddHandler(document, "mouseup", this.EndResizeDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.Resize = function(evt) - { - // Use the offset at the beginning of movement to drag the window around - var mouse_pos = DOM.Event.GetMousePosition(evt); - var offset = mouse_pos0 - this.MousePosBeforeResize0, mouse_pos1 - this.MousePosBeforeResize1 ; - this.SetSize(this.SizeBeforeResize0 + offset0, this.SizeBeforeResize1 + offset1); - - if (this.OnResize) - this.OnResize(this, this.Size); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.EndResize = function(evt) - { - // Remove handlers added during mouse down - DOM.Event.RemoveHandler(document, "mousemove", this.ResizeDelegate); - DOM.Event.RemoveHandler(document, "mouseup", this.EndResizeDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.SetPosition = function(x, y) - { - this.Position = x, y ; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - Window.prototype.SetSize = function(w, h) - { - w = Math.max(80, w); - h = Math.max(15, h); - this.Size = w, h ; - DOM.Node.SetSize(this.Node, this.Size); - } - - - Window.prototype.GetZIndex = function() - { - return parseInt(this.Node.style.zIndex); - } - - - return Window; -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js
Deleted
@@ -1,54 +0,0 @@ - -namespace("WM"); - - -WM.WindowManager = (function() -{ - function WindowManager() - { - // An empty list of windows under window manager control - this.Windows = ; - } - - - WindowManager.prototype.AddWindow = function(title, x, y, width, height, parent_node) - { - // Create the window and add it to the list of windows - var wnd = new WM.Window(this, title, x, y, width, height, parent_node); - this.Windows.push(wnd); - - // Always bring to the top on creation - wnd.SetTop(); - - return wnd; - } - - - WindowManager.prototype.SetTopWindow = function(top_wnd) - { - // Bring the window to the top of the window list - var top_wnd_index = this.Windows.indexOf(top_wnd); - if (top_wnd_index != -1) - this.Windows.splice(top_wnd_index, 1); - this.Windows.push(top_wnd); - - // Set a CSS z-index for each visible window from the bottom up - for (var i in this.Windows) - { - var wnd = this.Windowsi; - if (!wnd.Visible) - continue; - - // Ensure there's space between each window for the elements inside to be sorted - var z = (parseInt(i) + 1) * 10; - wnd.Node.style.zIndex = z; - - // Notify window that its z-order has changed - wnd.NotifyChange(); - } - } - - - return WindowManager; - -})(); \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Styles
Deleted
-(directory)
View file
gpac-2.4.0.tar.gz/share/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css
Deleted
@@ -1,652 +0,0 @@ - - -.notextsel -{ - /* Disable text selection so that it doesn't interfere with button-clicking */ - user-select: none; - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer */ - -khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */ - -webkit-user-select: none; /* Chrome, Safari, and Opera */ - -webkit-touch-callout: none; /* Disable Android and iOS callouts*/ - - /* Stops the text cursor over the label */ - cursor:default; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Window Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - -body -{ - /* Clip contents to browser window without adding scrollbars */ - overflow: hidden; -} - -.Window -{ - position:absolute; - - /* Clip all contents to the window border */ - overflow: hidden; - - background: #555; - - /*padding: 0px !important;*/ - - border-radius: 3px; - -moz-border-radius: 5px; - - -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; - box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; -} - -/*:root -{ - --SideBarSize: 5px; -} - -.WindowBodyDebug -{ - color: #BBB; - font: 9px Verdana; - white-space: nowrap; -} - -.WindowSizeLeft -{ - position: absolute; - left: 0px; - top: 0px; - width: var(--SideBarSize); - height: 100%; -} -.WindowSizeRight -{ - position: absolute; - left: calc(100% - var(--SideBarSize)); - top:0px; - width: var(--SideBarSize); - height:100%; -} -.WindowSizeTop -{ - position: absolute; - left: 0px; - top: 0px; - width: 100%; - height: var(--SideBarSize); -} -.WindowSizeBottom -{ - position: absolute; - left: 0px; - top: calc(100% - var(--SideBarSize)); - width: 100%; - height: var(--SideBarSize); -}*/ - - -.Window_Transparent -{ - /* Set transparency changes to fade in/out */ - opacity: 0.5; - transition: opacity 0.5s ease-out; - -moz-transition: opacity 0.5s ease-out; - -webkit-transition: opacity 0.5s ease-out; -} - -.Window_Transparent:hover -{ - opacity: 1; -} - -.WindowTitleBar -{ - height: 17px; - cursor: move; - /*overflow: hidden;*/ - - border-bottom: 1px solid #303030; - border-radius: 5px; -} - -.WindowTitleBarText -{ - color: #BBB; - font: 9px Verdana; - /*white-space: nowrap;*/ - - padding: 3px; - cursor: move; -} - -.WindowTitleBarClose -{ - color: #999999; - font: 9px Verdana; - - padding: 3px; - cursor: default; -} - -.WindowTitleBarClose:hover { - color: #bbb; -} - -.WindowResizeHandle -{ - color: #999999; - font: 17px Verdana; - padding: 3px; - cursor: se-resize; - position: absolute; - bottom: -7px; - right: -3px; -} - -.WindowBody { - position: absolute; - /* overflow: hidden; */ - display: block; - padding: 10px; - border-top: 1px solid #606060; - top: 18px; - left: 0; - right: 0; - bottom: 0; - height: auto; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Container Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Container -{ - /* Position relative to the parent window */ - position: absolute; - - /* Clip contents */ - /*overflow: hidden;*/ - - background:#2C2C2C; - - border: 1px black solid; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - -/*.Panel -{*/ - /* Position relative to the parent window */ - /*position: absolute;*/ - - /* Clip contents */ - /*overflow: hidden; - - background:#2C2C2C; - - border: 1px black solid;*/ - - /* Two inset box shadows to simulate depressing */ - /*-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;*/ -/*}*/ - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Ruler Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -/*.Ruler -{ - position: absolute; - - border: dashed 1px; - - opacity: 0.35; -}*/ - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Treeview Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Treeview -{ - position: absolute; - - background:#2C2C2C; - border: 1px solid black; - overflow:hidden; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - -.TreeviewItem -{ - margin:1px; - padding:2px; - border:solid 1px #2C2C2C; - background-color:#2C2C2C; -} - -.TreeviewItemImage -{ - float: left; -} - -.TreeviewItemText -{ - float: left; - margin-left:4px; -} - -.TreeviewItemChildren -{ - overflow: hidden; -} - -.TreeviewItemSelected -{ - background-color:#444; - border-color:#FFF; - - -webkit-transition: background-color 0.2s ease-in-out; - -moz-transition: background-color 0.2s ease-in-out; - -webkit-transition: border-color 0.2s ease-in-out; - -moz-transition: border-color 0.2s ease-in-out; -} - -/* Used to populate treeviews that want highlight on hover behavior */ -.TreeviewItemHover -{ -} - -.TreeviewItemHover:hover -{ - background-color:#111; - border-color:#444; - - -webkit-transition: background-color 0.2s ease-in-out; - -moz-transition: background-color 0.2s ease-in-out; - -webkit-transition: border-color 0.2s ease-in-out; - -moz-transition: border-color 0.2s ease-in-out; -} - -.TreeviewScrollbarInset -{ - float: right; - - position:relative; - - height: 100%; - - /* CRAZINESS PART A: Trying to get the inset and scrollbar to have 100% height match its container */ - margin: -8px -8px 0 0; - padding: 0 1px 14px 1px; - - width:20px; - background:#2C2C2C; - border: 1px solid black; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - -.TreeviewScrollbar -{ - position:relative; - - background:#2C2C2C; - border: 1px solid black; - - /* CRAZINESS PART B: Trying to get the inset and scrollbar to have 100% height match its container */ - padding: 0 0 10px 0; - margin: 1px 0 0 0; - - width: 18px; - height: 100%; - - border-radius:6px; - border-color:#000; - border-width:1px; - border-style:solid; - - /* The gradient for the button background */ - background-color:#666; - background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#383838)); - background: -moz-linear-gradient(top, #666, #383838); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666', endColorstr='#383838'); - - /* A box shadow and inset box highlight */ - -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; - box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; -} - -.TreeviewScrollbarHeld -{ - /* Reset the gradient to a full-colour background */ - background:#383838; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Edit Box Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.EditBoxContainer -{ - position: absolute; - padding:2px 10px 2px 10px; -} - -.EditBoxLabel -{ - float:left; - padding: 3px 4px 4px 4px; - font: 9px Verdana; -} - -.EditBox -{ - float:left; - - background:#666; - border: 1px solid; - border-radius: 6px; - padding: 3px 4px 3px 4px; - height: 20px; - - box-shadow: 1px 1px 1px #222 inset; - - transition: all 0.3s ease-in-out; -} - -.EditBox:focus -{ - background:#FFF; - outline:0; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Label Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Label -{ - /* Position relative to the parent window */ - position:absolute; - - color: #BBB; - font: 9px Verdana; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Combo Box Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.ComboBox -{ - position:absolute; - - /* TEMP! */ - width:90px; - - /* Height is fixed to match the font */ - height:14px; - - /* Align the text within the combo box */ - padding: 1px 0 0 5px; - - /* Solid, rounded border */ - border: 1px solid #111; - border-radius: 5px; - - /* http://www.colorzilla.com/gradient-editor/#e3e3e3+0,c6c6c6+22,b7b7b7+33,afafaf+50,a7a7a7+67,797979+82,414141+100;Custom */ - background: #e3e3e3; - background: -moz-linear-gradient(top, #e3e3e3 0%, #c6c6c6 22%, #b7b7b7 33%, #afafaf 50%, #a7a7a7 67%, #797979 82%, #414141 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e3e3e3), color-stop(22%,#c6c6c6), color-stop(33%,#b7b7b7), color-stop(50%,#afafaf), color-stop(67%,#a7a7a7), color-stop(82%,#797979), color-stop(100%,#414141)); - background: -webkit-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - background: -o-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - background: -ms-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - background: linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e3e3e3', endColorstr='#414141',GradientType=0 ); -} - -.ComboBoxPressed -{ - /* The reverse of the default background, simulating depression */ - background: #414141; - background: -moz-linear-gradient(top, #414141 0%, #797979 18%, #a7a7a7 33%, #afafaf 50%, #b7b7b7 67%, #c6c6c6 78%, #e3e3e3 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#414141), color-stop(18%,#797979), color-stop(33%,#a7a7a7), color-stop(50%,#afafaf), color-stop(67%,#b7b7b7), color-stop(78%,#c6c6c6), color-stop(100%,#e3e3e3)); - background: -webkit-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - background: -o-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - background: -ms-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - background: linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#414141', endColorstr='#e3e3e3',GradientType=0 ); -} - -.ComboBoxText -{ - /* Text info */ - color: #000; - font: 9px Verdana; - - float:left; -} - -.ComboBoxIcon -{ - /* Push the image to the far right */ - float:right; - - /* Align the image with the combo box */ - padding: 2px 5px 0 0; -} - -.ComboBoxPopup -{ - position: fixed; - - background: #CCC; - - border-radius: 5px; - - padding: 1px 0 1px 0; -} - -.ComboBoxPopupItem -{ - /* Text info */ - color: #000; - font: 9px Verdana; - - padding: 1px 1px 1px 5px; - - border-bottom: 1px solid #AAA; - border-top: 1px solid #FFF; -} - -.ComboBoxPopupItemText -{ - float:left; -} - -.ComboBoxPopupItemIcon -{ - /* Push the image to the far right */ - float:right; - - /* Align the image with the combo box */ - padding: 2px 5px 0 0; -} - -.ComboBoxPopupItem:first-child -{ - border-top: 0px; -} - -.ComboBoxPopupItem:last-child -{ - border-bottom: 0px; -} - -.ComboBoxPopupItem:hover -{ - color:#FFF; - background: #2036E1; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Grid Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - -.Grid { - overflow: auto; - background: #333; - height: 100%; - border-radius: 2px; -} - -.GridBody -{ - overflow-x: auto; - overflow-y: auto; - height: inherit; -} - -.GridRow -{ - display: inline-block; - white-space: nowrap; - - background:#303030; - - color: #BBB; - font: 9px Verdana; - - padding: 2px; -} - -.GridRow.GridGroup -{ - padding: 0px; -} - -.GridRow:nth-child(odd) -{ - background:#333; -} - -.GridRowCell -{ - display: inline-block; -} -.GridRowCell.GridGroup -{ - color: #BBB; - - /* Override default from name */ - width: 100%; - - padding: 1px 1px 1px 2px; - border: 1px solid; - border-radius: 2px; - - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; -} - -.GridRowBody -{ - /* Clip all contents for show/hide group*/ - overflow: hidden; - - /* Crazy CSS rules: controls for properties don't clip if this isn't set on this parent */ - position: relative; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Button Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Button -{ - /* Position relative to the parent window */ - position:absolute; - - border-radius:4px; - - /* Padding at the top includes 2px for the text drop-shadow */ - padding: 2px 5px 3px 5px; - - color: #BBB; - font: 9px Verdana; - text-shadow: 1px 1px 1px black; - text-align: center; - - background-color:#555; - - /* A box shadow and inset box highlight */ - -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; - box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; -} - -.Button:hover { - background-color: #616161; -} - -.Button.ButtonHeld -{ - /* Reset the gradient to a full-colour background */ - background:#383838; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -}
View file
gpac-2.4.0.tar.gz/share/vis/index.html
Deleted
@@ -1,55 +0,0 @@ - -<html xmlns="http://www.w3.org/1999/xhtml"> - - <head> - - <title>Remotery Viewer</title> - - <!-- Style Sheets --> - <link rel="stylesheet" type="text/css" href="extern/BrowserLib/WindowManager/Styles/WindowManager.css" /> - <link rel="stylesheet" type="text/css" href="Styles/Remotery.css" /> - - <!-- Utilities --> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/Core.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/DOM.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/Bind.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/Animation.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/Convert.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/LocalStore.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/Mouse.js"></script> - <script type="text/javascript" src="extern/BrowserLib/Core/Code/Keyboard.js"></script> - - <!-- User Interface Window Manager --> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/WindowManager.js"></script> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Window.js"></script> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Container.js"></script> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/EditBox.js"></script> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Grid.js"></script> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Label.js"></script> - <script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Button.js"></script> - - <!-- Main Application --> - <script type="text/javascript" src="Code/DataViewReader.js"></script> - <script type="text/javascript" src="Code/Console.js"></script> - <script type="text/javascript" src="Code/WebSocketConnection.js"></script> - <script type="text/javascript" src="Code/TitleWindow.js"></script> - <script type="text/javascript" src="Code/SampleWindow.js"></script> - <script type="text/javascript" src="Code/PixelTimeRange.js"></script> - <script type="text/javascript" src="Code/TimelineRow.js"></script> - <script type="text/javascript" src="Code/TimelineWindow.js"></script> - <script type="text/javascript" src="Code/ThreadFrame.js"></script> - <script type="text/javascript" src="Code/Remotery.js"></script> - - </head> - - <body> - - <script type="text/javascript"> - - var remotery = new Remotery(); - - </script> - - </body> - -</html> \ No newline at end of file
View file
gpac-2.4.0.tar.gz/src/quickjs/libbf.c
Deleted
@@ -1,8466 +0,0 @@ -/* - * Tiny arbitrary precision floating point library - * - * Copyright (c) 2017-2021 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include <stdlib.h> -#include <stdio.h> -#include <inttypes.h> -#include <math.h> -#include <string.h> -#include <assert.h> - -#ifdef __AVX2__ -#include <immintrin.h> -#endif - -#include "cutils.h" -#include "libbf.h" - -/* enable it to check the multiplication result */ -//#define USE_MUL_CHECK -/* enable it to use FFT/NTT multiplication */ -#define USE_FFT_MUL -/* enable decimal floating point support */ -#define USE_BF_DEC - -//#define inline __attribute__((always_inline)) - -#ifdef __AVX2__ -#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */ -#else -#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */ -#endif - -/* XXX: adjust */ -#define DIVNORM_LARGE_THRESHOLD 50 -#define UDIV1NORM_THRESHOLD 3 - -#if LIMB_BITS == 64 -#define FMT_LIMB1 "%" PRIx64 -#define FMT_LIMB "%016" PRIx64 -#define PRId_LIMB PRId64 -#define PRIu_LIMB PRIu64 - -#else - -#define FMT_LIMB1 "%x" -#define FMT_LIMB "%08x" -#define PRId_LIMB "d" -#define PRIu_LIMB "u" - -#endif - -typedef intptr_t mp_size_t; - -typedef int bf_op2_func_t(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags); - -#ifdef USE_FFT_MUL - -#define FFT_MUL_R_OVERLAP_A (1 << 0) -#define FFT_MUL_R_OVERLAP_B (1 << 1) -#define FFT_MUL_R_NORESIZE (1 << 2) - -static no_inline int fft_mul(bf_context_t *s, - bf_t *res, limb_t *a_tab, limb_t a_len, - limb_t *b_tab, limb_t b_len, int mul_flags); -static void fft_clear_cache(bf_context_t *s); -#endif -#ifdef USE_BF_DEC -static limb_t get_digit(const limb_t *tab, limb_t len, slimb_t pos); -#endif - - -/* could leading zeros */ -static inline int clz(limb_t a) -{ - if (a == 0) { - return LIMB_BITS; - } else { -#if LIMB_BITS == 64 - return clz64(a); -#else - return clz32(a); -#endif - } -} - -static inline int ctz(limb_t a) -{ - if (a == 0) { - return LIMB_BITS; - } else { -#if LIMB_BITS == 64 - return ctz64(a); -#else - return ctz32(a); -#endif - } -} - -static inline int ceil_log2(limb_t a) -{ - if (a <= 1) - return 0; - else - return LIMB_BITS - clz(a - 1); -} - -/* b must be >= 1 */ -static inline slimb_t ceil_div(slimb_t a, slimb_t b) -{ - if (a >= 0) - return (a + b - 1) / b; - else - return a / b; -} - -/* b must be >= 1 */ -static inline slimb_t floor_div(slimb_t a, slimb_t b) -{ - if (a >= 0) { - return a / b; - } else { - return (a - b + 1) / b; - } -} - -/* return r = a modulo b (0 <= r <= b - 1. b must be >= 1 */ -static inline limb_t smod(slimb_t a, slimb_t b) -{ - a = a % (slimb_t)b; - if (a < 0) - a += b; - return a; -} - -/* signed addition with saturation */ -static inline slimb_t sat_add(slimb_t a, slimb_t b) -{ - slimb_t r; - r = a + b; - /* overflow ? */ - if (((a ^ r) & (b ^ r)) < 0) - r = (a >> (LIMB_BITS - 1)) ^ (((limb_t)1 << (LIMB_BITS - 1)) - 1); - return r; -} - -#define malloc(s) malloc_is_forbidden(s) -#define free(p) free_is_forbidden(p) -#define realloc(p, s) realloc_is_forbidden(p, s) - -void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, - void *realloc_opaque) -{ - memset(s, 0, sizeof(*s)); - s->realloc_func = realloc_func; - s->realloc_opaque = realloc_opaque; -} - -void bf_context_end(bf_context_t *s) -{ - bf_clear_cache(s); -} - -void bf_init(bf_context_t *s, bf_t *r) -{ - r->ctx = s; - r->sign = 0; - r->expn = BF_EXP_ZERO; - r->len = 0; - r->tab = NULL; -} - -/* return 0 if OK, -1 if alloc error */ -int bf_resize(bf_t *r, limb_t len) -{ - limb_t *tab; - - if (len != r->len) { - tab = bf_realloc(r->ctx, r->tab, len * sizeof(limb_t)); - if (!tab && len != 0) - return -1; - r->tab = tab; - r->len = len; - } - return 0; -} - -/* return 0 or BF_ST_MEM_ERROR */ -int bf_set_ui(bf_t *r, uint64_t a) -{ - r->sign = 0; - if (a == 0) { - r->expn = BF_EXP_ZERO; - bf_resize(r, 0); /* cannot fail */ - } -#if LIMB_BITS == 32 - else if (a <= 0xffffffff) -#else - else -#endif - { - int shift; - if (bf_resize(r, 1)) - goto fail; - shift = clz(a); - r->tab0 = a << shift; - r->expn = LIMB_BITS - shift; - } -#if LIMB_BITS == 32 - else { - uint32_t a1, a0; - int shift; - if (bf_resize(r, 2)) - goto fail; - a0 = a; - a1 = a >> 32; - shift = clz(a1); - r->tab0 = a0 << shift; - r->tab1 = (a1 << shift) | (a0 >> (LIMB_BITS - shift)); - r->expn = 2 * LIMB_BITS - shift; - } -#endif - return 0; - fail: - bf_set_nan(r); - return BF_ST_MEM_ERROR; -} - -/* return 0 or BF_ST_MEM_ERROR */ -int bf_set_si(bf_t *r, int64_t a) -{ - int ret; - - if (a < 0) { - ret = bf_set_ui(r, -a); - r->sign = 1; - } else { - ret = bf_set_ui(r, a); - } - return ret; -} - -void bf_set_nan(bf_t *r) -{ - bf_resize(r, 0); /* cannot fail */ - r->expn = BF_EXP_NAN; - r->sign = 0; -} - -void bf_set_zero(bf_t *r, int is_neg) -{ - bf_resize(r, 0); /* cannot fail */ - r->expn = BF_EXP_ZERO; - r->sign = is_neg; -} - -void bf_set_inf(bf_t *r, int is_neg) -{ - bf_resize(r, 0); /* cannot fail */ - r->expn = BF_EXP_INF; - r->sign = is_neg; -} - -/* return 0 or BF_ST_MEM_ERROR */ -int bf_set(bf_t *r, const bf_t *a) -{ - if (r == a) - return 0; - if (bf_resize(r, a->len)) { - bf_set_nan(r); - return BF_ST_MEM_ERROR; - } - r->sign = a->sign; - r->expn = a->expn; - memcpy(r->tab, a->tab, a->len * sizeof(limb_t)); - return 0; -} - -/* equivalent to bf_set(r, a); bf_delete(a) */ -void bf_move(bf_t *r, bf_t *a) -{ - bf_context_t *s = r->ctx; - if (r == a) - return; - bf_free(s, r->tab); - *r = *a; -} - -static limb_t get_limbz(const bf_t *a, limb_t idx) -{ - if (idx >= a->len) - return 0; - else - return a->tabidx; -} - -/* get LIMB_BITS at bit position 'pos' in tab */ -static inline limb_t get_bits(const limb_t *tab, limb_t len, slimb_t pos) -{ - limb_t i, a0, a1; - int p; - - i = pos >> LIMB_LOG2_BITS; - p = pos & (LIMB_BITS - 1); - if (i < len) - a0 = tabi; - else - a0 = 0; - if (p == 0) { - return a0; - } else { - i++; - if (i < len) - a1 = tabi; - else - a1 = 0; - return (a0 >> p) | (a1 << (LIMB_BITS - p)); - } -} - -static inline limb_t get_bit(const limb_t *tab, limb_t len, slimb_t pos) -{ - slimb_t i; - i = pos >> LIMB_LOG2_BITS; - if (i < 0 || i >= len) - return 0; - return (tabi >> (pos & (LIMB_BITS - 1))) & 1; -} - -static inline limb_t limb_mask(int start, int last) -{ - limb_t v; - int n; - n = last - start + 1; - if (n == LIMB_BITS) - v = -1; - else - v = (((limb_t)1 << n) - 1) << start; - return v; -} - -static limb_t mp_scan_nz(const limb_t *tab, mp_size_t n) -{ - mp_size_t i; - for(i = 0; i < n; i++) { - if (tabi != 0) - return 1; - } - return 0; -} - -/* return != 0 if one bit between 0 and bit_pos inclusive is not zero. */ -static inline limb_t scan_bit_nz(const bf_t *r, slimb_t bit_pos) -{ - slimb_t pos; - limb_t v; - - pos = bit_pos >> LIMB_LOG2_BITS; - if (pos < 0) - return 0; - v = r->tabpos & limb_mask(0, bit_pos & (LIMB_BITS - 1)); - if (v != 0) - return 1; - pos--; - while (pos >= 0) { - if (r->tabpos != 0) - return 1; - pos--; - } - return 0; -} - -/* return the addend for rounding. Note that prec can be <= 0 (for - BF_FLAG_RADPNT_PREC) */ -static int bf_get_rnd_add(int *pret, const bf_t *r, limb_t l, - slimb_t prec, int rnd_mode) -{ - int add_one, inexact; - limb_t bit1, bit0; - - if (rnd_mode == BF_RNDF) { - bit0 = 1; /* faithful rounding does not honor the INEXACT flag */ - } else { - /* starting limb for bit 'prec + 1' */ - bit0 = scan_bit_nz(r, l * LIMB_BITS - 1 - bf_max(0, prec + 1)); - } - - /* get the bit at 'prec' */ - bit1 = get_bit(r->tab, l, l * LIMB_BITS - 1 - prec); - inexact = (bit1 | bit0) != 0; - - add_one = 0; - switch(rnd_mode) { - case BF_RNDZ: - break; - case BF_RNDN: - if (bit1) { - if (bit0) { - add_one = 1; - } else { - /* round to even */ - add_one = - get_bit(r->tab, l, l * LIMB_BITS - 1 - (prec - 1)); - } - } - break; - case BF_RNDD: - case BF_RNDU: - if (r->sign == (rnd_mode == BF_RNDD)) - add_one = inexact; - break; - case BF_RNDA: - add_one = inexact; - break; - case BF_RNDNA: - case BF_RNDF: - add_one = bit1; - break; - default: - abort(); - } - - if (inexact) - *pret |= BF_ST_INEXACT; - return add_one; -} - -static int bf_set_overflow(bf_t *r, int sign, limb_t prec, bf_flags_t flags) -{ - slimb_t i, l, e_max; - int rnd_mode; - - rnd_mode = flags & BF_RND_MASK; - if (prec == BF_PREC_INF || - rnd_mode == BF_RNDN || - rnd_mode == BF_RNDNA || - rnd_mode == BF_RNDA || - (rnd_mode == BF_RNDD && sign == 1) || - (rnd_mode == BF_RNDU && sign == 0)) { - bf_set_inf(r, sign); - } else { - /* set to maximum finite number */ - l = (prec + LIMB_BITS - 1) / LIMB_BITS; - if (bf_resize(r, l)) { - bf_set_nan(r); - return BF_ST_MEM_ERROR; - } - r->tab0 = limb_mask((-prec) & (LIMB_BITS - 1), - LIMB_BITS - 1); - for(i = 1; i < l; i++) - r->tabi = (limb_t)-1; - e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1); - r->expn = e_max; - r->sign = sign; - } - return BF_ST_OVERFLOW | BF_ST_INEXACT; -} - -/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is - assumed to have length 'l' (1 <= l <= r->len). Note: 'prec1' can be - infinite (BF_PREC_INF). 'ret' is 0 or BF_ST_INEXACT if the result - is known to be inexact. Can fail with BF_ST_MEM_ERROR in case of - overflow not returning infinity. */ -static int __bf_round(bf_t *r, limb_t prec1, bf_flags_t flags, limb_t l, - int ret) -{ - limb_t v, a; - int shift, add_one, rnd_mode; - slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec; - - /* e_min and e_max are computed to match the IEEE 754 conventions */ - e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1); - e_min = -e_range + 3; - e_max = e_range; - - if (flags & BF_FLAG_RADPNT_PREC) { - /* 'prec' is the precision after the radix point */ - if (prec1 != BF_PREC_INF) - prec = r->expn + prec1; - else - prec = prec1; - } else if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) { - /* restrict the precision in case of potentially subnormal - result */ - assert(prec1 != BF_PREC_INF); - prec = prec1 - (e_min - r->expn); - } else { - prec = prec1; - } - - /* round to prec bits */ - rnd_mode = flags & BF_RND_MASK; - add_one = bf_get_rnd_add(&ret, r, l, prec, rnd_mode); - - if (prec <= 0) { - if (add_one) { - bf_resize(r, 1); /* cannot fail */ - r->tab0 = (limb_t)1 << (LIMB_BITS - 1); - r->expn += 1 - prec; - ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; - return ret; - } else { - goto underflow; - } - } else if (add_one) { - limb_t carry; - - /* add one starting at digit 'prec - 1' */ - bit_pos = l * LIMB_BITS - 1 - (prec - 1); - pos = bit_pos >> LIMB_LOG2_BITS; - carry = (limb_t)1 << (bit_pos & (LIMB_BITS - 1)); - - for(i = pos; i < l; i++) { - v = r->tabi + carry; - carry = (v < carry); - r->tabi = v; - if (carry == 0) - break; - } - if (carry) { - /* shift right by one digit */ - v = 1; - for(i = l - 1; i >= pos; i--) { - a = r->tabi; - r->tabi = (a >> 1) | (v << (LIMB_BITS - 1)); - v = a; - } - r->expn++; - } - } - - /* check underflow */ - if (unlikely(r->expn < e_min)) { - if (flags & BF_FLAG_SUBNORMAL) { - /* if inexact, also set the underflow flag */ - if (ret & BF_ST_INEXACT) - ret |= BF_ST_UNDERFLOW; - } else { - underflow: - ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; - bf_set_zero(r, r->sign); - return ret; - } - } - - /* check overflow */ - if (unlikely(r->expn > e_max)) - return bf_set_overflow(r, r->sign, prec1, flags); - - /* keep the bits starting at 'prec - 1' */ - bit_pos = l * LIMB_BITS - 1 - (prec - 1); - i = bit_pos >> LIMB_LOG2_BITS; - if (i >= 0) { - shift = bit_pos & (LIMB_BITS - 1); - if (shift != 0) - r->tabi &= limb_mask(shift, LIMB_BITS - 1); - } else { - i = 0; - } - /* remove trailing zeros */ - while (r->tabi == 0) - i++; - if (i > 0) { - l -= i; - memmove(r->tab, r->tab + i, l * sizeof(limb_t)); - } - bf_resize(r, l); /* cannot fail */ - return ret; -} - -/* 'r' must be a finite number. */ -int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags) -{ - limb_t l, v, a; - int shift, ret; - slimb_t i; - - // bf_print_str("bf_renorm", r); - l = r->len; - while (l > 0 && r->tabl - 1 == 0) - l--; - if (l == 0) { - /* zero */ - r->expn = BF_EXP_ZERO; - bf_resize(r, 0); /* cannot fail */ - ret = 0; - } else { - r->expn -= (r->len - l) * LIMB_BITS; - /* shift to have the MSB set to '1' */ - v = r->tabl - 1; - shift = clz(v); - if (shift != 0) { - v = 0; - for(i = 0; i < l; i++) { - a = r->tabi; - r->tabi = (a << shift) | (v >> (LIMB_BITS - shift)); - v = a; - } - r->expn -= shift; - } - ret = __bf_round(r, prec1, flags, l, 0); - } - // bf_print_str("r_final", r); - return ret; -} - -/* return true if rounding can be done at precision 'prec' assuming - the exact result r is such that |r-a| <= 2^(EXP(a)-k). */ -/* XXX: check the case where the exponent would be incremented by the - rounding */ -int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k) -{ - BOOL is_rndn; - slimb_t bit_pos, n; - limb_t bit; - - if (a->expn == BF_EXP_INF || a->expn == BF_EXP_NAN) - return FALSE; - if (rnd_mode == BF_RNDF) { - return (k >= (prec + 1)); - } - if (a->expn == BF_EXP_ZERO) - return FALSE; - is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); - if (k < (prec + 2)) - return FALSE; - bit_pos = a->len * LIMB_BITS - 1 - prec; - n = k - prec; - /* bit pattern for RNDN or RNDNA: 0111.. or 1000... - for other rounding modes: 000... or 111... - */ - bit = get_bit(a->tab, a->len, bit_pos); - bit_pos--; - n--; - bit ^= is_rndn; - /* XXX: slow, but a few iterations on average */ - while (n != 0) { - if (get_bit(a->tab, a->len, bit_pos) != bit) - return TRUE; - bit_pos--; - n--; - } - return FALSE; -} - -/* Cannot fail with BF_ST_MEM_ERROR. */ -int bf_round(bf_t *r, limb_t prec, bf_flags_t flags) -{ - if (r->len == 0) - return 0; - return __bf_round(r, prec, flags, r->len, 0); -} - -/* for debugging */ -static __maybe_unused void dump_limbs(const char *str, const limb_t *tab, limb_t n) -{ - limb_t i; - printf("%s: len=%" PRId_LIMB "\n", str, n); - for(i = 0; i < n; i++) { - printf("%" PRId_LIMB ": " FMT_LIMB "\n", - i, tabi); - } -} - -void mp_print_str(const char *str, const limb_t *tab, limb_t n) -{ - slimb_t i; - printf("%s= 0x", str); - for(i = n - 1; i >= 0; i--) { - if (i != (n - 1)) - printf("_"); - printf(FMT_LIMB, tabi); - } - printf("\n"); -} - -static __maybe_unused void mp_print_str_h(const char *str, - const limb_t *tab, limb_t n, - limb_t high) -{ - slimb_t i; - printf("%s= 0x", str); - printf(FMT_LIMB, high); - for(i = n - 1; i >= 0; i--) { - printf("_"); - printf(FMT_LIMB, tabi); - } - printf("\n"); -} - -/* for debugging */ -void bf_print_str(const char *str, const bf_t *a) -{ - slimb_t i; - printf("%s=", str); - - if (a->expn == BF_EXP_NAN) { - printf("NaN"); - } else { - if (a->sign) - putchar('-'); - if (a->expn == BF_EXP_ZERO) { - putchar('0'); - } else if (a->expn == BF_EXP_INF) { - printf("Inf"); - } else { - printf("0x0."); - for(i = a->len - 1; i >= 0; i--) - printf(FMT_LIMB, a->tabi); - printf("p%" PRId_LIMB, a->expn); - } - } - printf("\n"); -} - -/* compare the absolute value of 'a' and 'b'. Return < 0 if a < b, 0 - if a = b and > 0 otherwise. */ -int bf_cmpu(const bf_t *a, const bf_t *b) -{ - slimb_t i; - limb_t len, v1, v2; - - if (a->expn != b->expn) { - if (a->expn < b->expn) - return -1; - else - return 1; - } - len = bf_max(a->len, b->len); - for(i = len - 1; i >= 0; i--) { - v1 = get_limbz(a, a->len - len + i); - v2 = get_limbz(b, b->len - len + i); - if (v1 != v2) { - if (v1 < v2) - return -1; - else - return 1; - } - } - return 0; -} - -/* Full order: -0 < 0, NaN == NaN and NaN is larger than all other numbers */ -int bf_cmp_full(const bf_t *a, const bf_t *b) -{ - int res; - - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - if (a->expn == b->expn) - res = 0; - else if (a->expn == BF_EXP_NAN) - res = 1; - else - res = -1; - } else if (a->sign != b->sign) { - res = 1 - 2 * a->sign; - } else { - res = bf_cmpu(a, b); - if (a->sign) - res = -res; - } - return res; -} - -/* Standard floating point comparison: return 2 if one of the operands - is NaN (unordered) or -1, 0, 1 depending on the ordering assuming - -0 == +0 */ -int bf_cmp(const bf_t *a, const bf_t *b) -{ - int res; - - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - res = 2; - } else if (a->sign != b->sign) { - if (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) - res = 0; - else - res = 1 - 2 * a->sign; - } else { - res = bf_cmpu(a, b); - if (a->sign) - res = -res; - } - return res; -} - -/* Compute the number of bits 'n' matching the pattern: - a= X1000..0 - b= X0111..1 - - When computing a-b, the result will have at least n leading zero - bits. - - Precondition: a > b and a.expn - b.expn = 0 or 1 -*/ -static limb_t count_cancelled_bits(const bf_t *a, const bf_t *b) -{ - slimb_t bit_offset, b_offset, n; - int p, p1; - limb_t v1, v2, mask; - - bit_offset = a->len * LIMB_BITS - 1; - b_offset = (b->len - a->len) * LIMB_BITS - (LIMB_BITS - 1) + - a->expn - b->expn; - n = 0; - - /* first search the equals bits */ - for(;;) { - v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS); - v2 = get_bits(b->tab, b->len, bit_offset + b_offset); - // printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2); - if (v1 != v2) - break; - n += LIMB_BITS; - bit_offset -= LIMB_BITS; - } - /* find the position of the first different bit */ - p = clz(v1 ^ v2) + 1; - n += p; - /* then search for '0' in a and '1' in b */ - p = LIMB_BITS - p; - if (p > 0) { - /* search in the trailing p bits of v1 and v2 */ - mask = limb_mask(0, p - 1); - p1 = bf_min(clz(v1 & mask), clz((~v2) & mask)) - (LIMB_BITS - p); - n += p1; - if (p1 != p) - goto done; - } - bit_offset -= LIMB_BITS; - for(;;) { - v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS); - v2 = get_bits(b->tab, b->len, bit_offset + b_offset); - // printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2); - if (v1 != 0 || v2 != -1) { - /* different: count the matching bits */ - p1 = bf_min(clz(v1), clz(~v2)); - n += p1; - break; - } - n += LIMB_BITS; - bit_offset -= LIMB_BITS; - } - done: - return n; -} - -static int bf_add_internal(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags, int b_neg) -{ - const bf_t *tmp; - int is_sub, ret, cmp_res, a_sign, b_sign; - - a_sign = a->sign; - b_sign = b->sign ^ b_neg; - is_sub = a_sign ^ b_sign; - cmp_res = bf_cmpu(a, b); - if (cmp_res < 0) { - tmp = a; - a = b; - b = tmp; - a_sign = b_sign; /* b_sign is never used later */ - } - /* abs(a) >= abs(b) */ - if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) { - /* zero result */ - bf_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD); - ret = 0; - } else if (a->len == 0 || b->len == 0) { - ret = 0; - if (a->expn >= BF_EXP_INF) { - if (a->expn == BF_EXP_NAN) { - /* at least one operand is NaN */ - bf_set_nan(r); - } else if (b->expn == BF_EXP_INF && is_sub) { - /* infinities with different signs */ - bf_set_nan(r); - ret = BF_ST_INVALID_OP; - } else { - bf_set_inf(r, a_sign); - } - } else { - /* at least one zero and not subtract */ - bf_set(r, a); - r->sign = a_sign; - goto renorm; - } - } else { - slimb_t d, a_offset, b_bit_offset, i, cancelled_bits; - limb_t carry, v1, v2, u, r_len, carry1, precl, tot_len, z, sub_mask; - - r->sign = a_sign; - r->expn = a->expn; - d = a->expn - b->expn; - /* must add more precision for the leading cancelled bits in - subtraction */ - if (is_sub) { - if (d <= 1) - cancelled_bits = count_cancelled_bits(a, b); - else - cancelled_bits = 1; - } else { - cancelled_bits = 0; - } - - /* add two extra bits for rounding */ - precl = (cancelled_bits + prec + 2 + LIMB_BITS - 1) / LIMB_BITS; - tot_len = bf_max(a->len, b->len + (d + LIMB_BITS - 1) / LIMB_BITS); - r_len = bf_min(precl, tot_len); - if (bf_resize(r, r_len)) - goto fail; - a_offset = a->len - r_len; - b_bit_offset = (b->len - r_len) * LIMB_BITS + d; - - /* compute the bits before for the rounding */ - carry = is_sub; - z = 0; - sub_mask = -is_sub; - i = r_len - tot_len; - while (i < 0) { - slimb_t ap, bp; - BOOL inflag; - - ap = a_offset + i; - bp = b_bit_offset + i * LIMB_BITS; - inflag = FALSE; - if (ap >= 0 && ap < a->len) { - v1 = a->tabap; - inflag = TRUE; - } else { - v1 = 0; - } - if (bp + LIMB_BITS > 0 && bp < (slimb_t)(b->len * LIMB_BITS)) { - v2 = get_bits(b->tab, b->len, bp); - inflag = TRUE; - } else { - v2 = 0; - } - if (!inflag) { - /* outside 'a' and 'b': go directly to the next value - inside a or b so that the running time does not - depend on the exponent difference */ - i = 0; - if (ap < 0) - i = bf_min(i, -a_offset); - /* b_bit_offset + i * LIMB_BITS + LIMB_BITS >= 1 - equivalent to - i >= ceil(-b_bit_offset + 1 - LIMB_BITS) / LIMB_BITS) - */ - if (bp + LIMB_BITS <= 0) - i = bf_min(i, (-b_bit_offset) >> LIMB_LOG2_BITS); - } else { - i++; - } - v2 ^= sub_mask; - u = v1 + v2; - carry1 = u < v1; - u += carry; - carry = (u < carry) | carry1; - z |= u; - } - /* and the result */ - for(i = 0; i < r_len; i++) { - v1 = get_limbz(a, a_offset + i); - v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS); - v2 ^= sub_mask; - u = v1 + v2; - carry1 = u < v1; - u += carry; - carry = (u < carry) | carry1; - r->tabi = u; - } - /* set the extra bits for the rounding */ - r->tab0 |= (z != 0); - - /* carry is only possible in add case */ - if (!is_sub && carry) { - if (bf_resize(r, r_len + 1)) - goto fail; - r->tabr_len = 1; - r->expn += LIMB_BITS; - } - renorm: - ret = bf_normalize_and_round(r, prec, flags); - } - return ret; - fail: - bf_set_nan(r); - return BF_ST_MEM_ERROR; -} - -static int __bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_add_internal(r, a, b, prec, flags, 0); -} - -static int __bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_add_internal(r, a, b, prec, flags, 1); -} - -limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, - limb_t n, limb_t carry) -{ - slimb_t i; - limb_t k, a, v, k1; - - k = carry; - for(i=0;i<n;i++) { - v = op1i; - a = v + op2i; - k1 = a < v; - a = a + k; - k = (a < k) | k1; - resi = a; - } - return k; -} - -limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) -{ - size_t i; - limb_t k, a; - - k=b; - for(i=0;i<n;i++) { - if (k == 0) - break; - a = tabi + k; - k = (a < k); - tabi = a; - } - return k; -} - -limb_t mp_sub(limb_t *res, const limb_t *op1, const limb_t *op2, - mp_size_t n, limb_t carry) -{ - int i; - limb_t k, a, v, k1; - - k = carry; - for(i=0;i<n;i++) { - v = op1i; - a = v - op2i; - k1 = a > v; - v = a - k; - k = (v > a) | k1; - resi = v; - } - return k; -} - -/* compute 0 - op2 */ -static limb_t mp_neg(limb_t *res, const limb_t *op2, mp_size_t n, limb_t carry) -{ - int i; - limb_t k, a, v, k1; - - k = carry; - for(i=0;i<n;i++) { - v = 0; - a = v - op2i; - k1 = a > v; - v = a - k; - k = (v > a) | k1; - resi = v; - } - return k; -} - -limb_t mp_sub_ui(limb_t *tab, limb_t b, mp_size_t n) -{ - mp_size_t i; - limb_t k, a, v; - - k=b; - for(i=0;i<n;i++) { - v = tabi; - a = v - k; - k = a > v; - tabi = a; - if (k == 0) - break; - } - return k; -} - -/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). - 1 <= shift <= LIMB_BITS - 1 */ -static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, - int shift, limb_t high) -{ - mp_size_t i; - limb_t l, a; - - assert(shift >= 1 && shift < LIMB_BITS); - l = high; - for(i = n - 1; i >= 0; i--) { - a = tabi; - tab_ri = (a >> shift) | (l << (LIMB_BITS - shift)); - l = a; - } - return l & (((limb_t)1 << shift) - 1); -} - -/* tabr = taba * b + l. Return the high carry */ -static limb_t mp_mul1(limb_t *tabr, const limb_t *taba, limb_t n, - limb_t b, limb_t l) -{ - limb_t i; - dlimb_t t; - - for(i = 0; i < n; i++) { - t = (dlimb_t)tabai * (dlimb_t)b + l; - tabri = t; - l = t >> LIMB_BITS; - } - return l; -} - -/* tabr += taba * b, return the high word. */ -static limb_t mp_add_mul1(limb_t *tabr, const limb_t *taba, limb_t n, - limb_t b) -{ - limb_t i, l; - dlimb_t t; - - l = 0; - for(i = 0; i < n; i++) { - t = (dlimb_t)tabai * (dlimb_t)b + l + tabri; - tabri = t; - l = t >> LIMB_BITS; - } - return l; -} - -/* size of the result : op1_size + op2_size. */ -static void mp_mul_basecase(limb_t *result, - const limb_t *op1, limb_t op1_size, - const limb_t *op2, limb_t op2_size) -{ - limb_t i, r; - - resultop1_size = mp_mul1(result, op1, op1_size, op20, 0); - for(i=1;i<op2_size;i++) { - r = mp_add_mul1(result + i, op1, op1_size, op2i); - resulti + op1_size = r; - } -} - -/* return 0 if OK, -1 if memory error */ -/* XXX: change API so that result can be allocated */ -int mp_mul(bf_context_t *s, limb_t *result, - const limb_t *op1, limb_t op1_size, - const limb_t *op2, limb_t op2_size) -{ -#ifdef USE_FFT_MUL - if (unlikely(bf_min(op1_size, op2_size) >= FFT_MUL_THRESHOLD)) { - bf_t r_s, *r = &r_s; - r->tab = result; - /* XXX: optimize memory usage in API */ - if (fft_mul(s, r, (limb_t *)op1, op1_size, - (limb_t *)op2, op2_size, FFT_MUL_R_NORESIZE)) - return -1; - } else -#endif - { - mp_mul_basecase(result, op1, op1_size, op2, op2_size); - } - return 0; -} - -/* tabr -= taba * b. Return the value to substract to the high - word. */ -static limb_t mp_sub_mul1(limb_t *tabr, const limb_t *taba, limb_t n, - limb_t b) -{ - limb_t i, l; - dlimb_t t; - - l = 0; - for(i = 0; i < n; i++) { - t = tabri - (dlimb_t)tabai * (dlimb_t)b - l; - tabri = t; - l = -(t >> LIMB_BITS); - } - return l; -} - -/* WARNING: d must be >= 2^(LIMB_BITS-1) */ -static inline limb_t udiv1norm_init(limb_t d) -{ - limb_t a0, a1; - a1 = -d - 1; - a0 = -1; - return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; -} - -/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 - / d' with 0 <= a1 < d. */ -static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, - limb_t d, limb_t d_inv) -{ - limb_t n1m, n_adj, q, r, ah; - dlimb_t a; - n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); - n_adj = a0 + (n1m & d); - a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; - q = (a >> LIMB_BITS) + a1; - /* compute a - q * r and update q so that the remainder is\ - between 0 and d - 1 */ - a = ((dlimb_t)a1 << LIMB_BITS) | a0; - a = a - (dlimb_t)q * d - d; - ah = a >> LIMB_BITS; - q += 1 + ah; - r = (limb_t)a + (ah & d); - *pr = r; - return q; -} - -/* b must be >= 1 << (LIMB_BITS - 1) */ -static limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, - limb_t b, limb_t r) -{ - slimb_t i; - - if (n >= UDIV1NORM_THRESHOLD) { - limb_t b_inv; - b_inv = udiv1norm_init(b); - for(i = n - 1; i >= 0; i--) { - tabri = udiv1norm(&r, r, tabai, b, b_inv); - } - } else { - dlimb_t a1; - for(i = n - 1; i >= 0; i--) { - a1 = ((dlimb_t)r << LIMB_BITS) | tabai; - tabri = a1 / b; - r = a1 % b; - } - } - return r; -} - -static int mp_divnorm_large(bf_context_t *s, - limb_t *tabq, limb_t *taba, limb_t na, - const limb_t *tabb, limb_t nb); - -/* base case division: divides taba0..na-1 by tabb0..nb-1. tabbnb - - 1 must be >= 1 << (LIMB_BITS - 1). na - nb must be >= 0. 'taba' - is modified and contains the remainder (nb limbs). tabq0..na-nb - contains the quotient with tabqna - nb <= 1. */ -static int mp_divnorm(bf_context_t *s, limb_t *tabq, limb_t *taba, limb_t na, - const limb_t *tabb, limb_t nb) -{ - limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; - slimb_t i, j; - - b1 = tabbnb - 1; - if (nb == 1) { - taba0 = mp_div1norm(tabq, taba, na, b1, 0); - return 0; - } - n = na - nb; - if (bf_min(n, nb) >= DIVNORM_LARGE_THRESHOLD) { - return mp_divnorm_large(s, tabq, taba, na, tabb, nb); - } - - if (n >= UDIV1NORM_THRESHOLD) - b1_inv = udiv1norm_init(b1); - else - b1_inv = 0; - - /* first iteration: the quotient is only 0 or 1 */ - q = 1; - for(j = nb - 1; j >= 0; j--) { - if (taban + j != tabbj) { - if (taban + j < tabbj) - q = 0; - break; - } - } - tabqn = q; - if (q) { - mp_sub(taba + n, taba + n, tabb, nb, 0); - } - - for(i = n - 1; i >= 0; i--) { - if (unlikely(tabai + nb >= b1)) { - q = -1; - } else if (b1_inv) { - q = udiv1norm(&dummy_r, tabai + nb, tabai + nb - 1, b1, b1_inv); - } else { - dlimb_t al; - al = ((dlimb_t)tabai + nb << LIMB_BITS) | tabai + nb - 1; - q = al / b1; - r = al % b1; - } - r = mp_sub_mul1(taba + i, tabb, nb, q); - - v = tabai + nb; - a = v - r; - c = (a > v); - tabai + nb = a; - - if (c != 0) { - /* negative result */ - for(;;) { - q--; - c = mp_add(taba + i, taba + i, tabb, nb, 0); - /* propagate carry and test if positive result */ - if (c != 0) { - if (++tabai + nb == 0) { - break; - } - } - } - } - tabqi = q; - } - return 0; -} - -/* compute r=B^(2*n)/a such as a*r < B^(2*n) < a*r + 2 with n >= 1. 'a' - has n limbs with an-1 >= B/2 and 'r' has n+1 limbs with rn = 1. - - See Modern Computer Arithmetic by Richard P. Brent and Paul - Zimmermann, algorithm 3.5 */ -int mp_recip(bf_context_t *s, limb_t *tabr, const limb_t *taba, limb_t n) -{ - mp_size_t l, h, k, i; - limb_t *tabxh, *tabt, c, *tabu; - - if (n <= 2) { - /* return ceil(B^(2*n)/a) - 1 */ - /* XXX: could avoid allocation */ - tabu = bf_malloc(s, sizeof(limb_t) * (2 * n + 1)); - tabt = bf_malloc(s, sizeof(limb_t) * (n + 2)); - if (!tabt || !tabu) - goto fail; - for(i = 0; i < 2 * n; i++) - tabui = 0; - tabu2 * n = 1; - if (mp_divnorm(s, tabt, tabu, 2 * n + 1, taba, n)) - goto fail; - for(i = 0; i < n + 1; i++) - tabri = tabti; - if (mp_scan_nz(tabu, n) == 0) { - /* only happens for a=B^n/2 */ - mp_sub_ui(tabr, 1, n + 1); - } - } else { - l = (n - 1) / 2; - h = n - l; - /* n=2p -> l=p-1, h = p + 1, k = p + 3 - n=2p+1-> l=p, h = p + 1; k = p + 2 - */ - tabt = bf_malloc(s, sizeof(limb_t) * (n + h + 1)); - tabu = bf_malloc(s, sizeof(limb_t) * (n + 2 * h - l + 2)); - if (!tabt || !tabu) - goto fail; - tabxh = tabr + l; - if (mp_recip(s, tabxh, taba + l, h)) - goto fail; - if (mp_mul(s, tabt, taba, n, tabxh, h + 1)) /* n + h + 1 limbs */ - goto fail; - while (tabtn + h != 0) { - mp_sub_ui(tabxh, 1, h + 1); - c = mp_sub(tabt, tabt, taba, n, 0); - mp_sub_ui(tabt + n, c, h + 1); - } - /* T = B^(n+h) - T */ - mp_neg(tabt, tabt, n + h + 1, 0); - tabtn + h++; - if (mp_mul(s, tabu, tabt + l, n + h + 1 - l, tabxh, h + 1)) - goto fail; - /* n + 2*h - l + 2 limbs */ - k = 2 * h - l; - for(i = 0; i < l; i++) - tabri = tabui + k; - mp_add(tabr + l, tabr + l, tabu + 2 * h, h, 0); - } - bf_free(s, tabt); - bf_free(s, tabu); - return 0; - fail: - bf_free(s, tabt); - bf_free(s, tabu); - return -1; -} - -/* return -1, 0 or 1 */ -static int mp_cmp(const limb_t *taba, const limb_t *tabb, mp_size_t n) -{ - mp_size_t i; - for(i = n - 1; i >= 0; i--) { - if (tabai != tabbi) { - if (tabai < tabbi) - return -1; - else - return 1; - } - } - return 0; -} - -//#define DEBUG_DIVNORM_LARGE -//#define DEBUG_DIVNORM_LARGE2 - -/* subquadratic divnorm */ -static int mp_divnorm_large(bf_context_t *s, - limb_t *tabq, limb_t *taba, limb_t na, - const limb_t *tabb, limb_t nb) -{ - limb_t *tabb_inv, nq, *tabt, i, n; - nq = na - nb; -#ifdef DEBUG_DIVNORM_LARGE - printf("na=%d nb=%d nq=%d\n", (int)na, (int)nb, (int)nq); - mp_print_str("a", taba, na); - mp_print_str("b", tabb, nb); -#endif - assert(nq >= 1); - n = nq; - if (nq < nb) - n++; - tabb_inv = bf_malloc(s, sizeof(limb_t) * (n + 1)); - tabt = bf_malloc(s, sizeof(limb_t) * 2 * (n + 1)); - if (!tabb_inv || !tabt) - goto fail; - - if (n >= nb) { - for(i = 0; i < n - nb; i++) - tabti = 0; - for(i = 0; i < nb; i++) - tabti + n - nb = tabbi; - } else { - /* truncate B: need to increment it so that the approximate - inverse is smaller that the exact inverse */ - for(i = 0; i < n; i++) - tabti = tabbi + nb - n; - if (mp_add_ui(tabt, 1, n)) { - /* tabt = B^n : tabb_inv = B^n */ - memset(tabb_inv, 0, n * sizeof(limb_t)); - tabb_invn = 1; - goto recip_done; - } - } - if (mp_recip(s, tabb_inv, tabt, n)) - goto fail; - recip_done: - /* Q=A*B^-1 */ - if (mp_mul(s, tabt, tabb_inv, n + 1, taba + na - (n + 1), n + 1)) - goto fail; - - for(i = 0; i < nq + 1; i++) - tabqi = tabti + 2 * (n + 1) - (nq + 1); -#ifdef DEBUG_DIVNORM_LARGE - mp_print_str("q", tabq, nq + 1); -#endif - - bf_free(s, tabt); - bf_free(s, tabb_inv); - tabb_inv = NULL; - - /* R=A-B*Q */ - tabt = bf_malloc(s, sizeof(limb_t) * (na + 1)); - if (!tabt) - goto fail; - if (mp_mul(s, tabt, tabq, nq + 1, tabb, nb)) - goto fail; - /* we add one more limb for the result */ - mp_sub(taba, taba, tabt, nb + 1, 0); - bf_free(s, tabt); - /* the approximated quotient is smaller than than the exact one, - hence we may have to increment it */ -#ifdef DEBUG_DIVNORM_LARGE2 - int cnt = 0; - static int cnt_max; -#endif - for(;;) { - if (tabanb == 0 && mp_cmp(taba, tabb, nb) < 0) - break; - tabanb -= mp_sub(taba, taba, tabb, nb, 0); - mp_add_ui(tabq, 1, nq + 1); -#ifdef DEBUG_DIVNORM_LARGE2 - cnt++; -#endif - } -#ifdef DEBUG_DIVNORM_LARGE2 - if (cnt > cnt_max) { - cnt_max = cnt; - printf("\ncnt=%d nq=%d nb=%d\n", cnt_max, (int)nq, (int)nb); - } -#endif - return 0; - fail: - bf_free(s, tabb_inv); - bf_free(s, tabt); - return -1; -} - -int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - int ret, r_sign; - - if (a->len < b->len) { - const bf_t *tmp = a; - a = b; - b = tmp; - } - r_sign = a->sign ^ b->sign; - /* here b->len <= a->len */ - if (b->len == 0) { - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - bf_set_nan(r); - ret = 0; - } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) { - if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) || - (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) { - bf_set_nan(r); - ret = BF_ST_INVALID_OP; - } else { - bf_set_inf(r, r_sign); - ret = 0; - } - } else { - bf_set_zero(r, r_sign); - ret = 0; - } - } else { - bf_t tmp, *r1 = NULL; - limb_t a_len, b_len, precl; - limb_t *a_tab, *b_tab; - - a_len = a->len; - b_len = b->len; - - if ((flags & BF_RND_MASK) == BF_RNDF) { - /* faithful rounding does not require using the full inputs */ - precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS; - a_len = bf_min(a_len, precl); - b_len = bf_min(b_len, precl); - } - a_tab = a->tab + a->len - a_len; - b_tab = b->tab + b->len - b_len; - -#ifdef USE_FFT_MUL - if (b_len >= FFT_MUL_THRESHOLD) { - int mul_flags = 0; - if (r == a) - mul_flags |= FFT_MUL_R_OVERLAP_A; - if (r == b) - mul_flags |= FFT_MUL_R_OVERLAP_B; - if (fft_mul(r->ctx, r, a_tab, a_len, b_tab, b_len, mul_flags)) - goto fail; - } else -#endif - { - if (r == a || r == b) { - bf_init(r->ctx, &tmp); - r1 = r; - r = &tmp; - } - if (bf_resize(r, a_len + b_len)) { - fail: - bf_set_nan(r); - ret = BF_ST_MEM_ERROR; - goto done; - } - mp_mul_basecase(r->tab, a_tab, a_len, b_tab, b_len); - } - r->sign = r_sign; - r->expn = a->expn + b->expn; - ret = bf_normalize_and_round(r, prec, flags); - done: - if (r == &tmp) - bf_move(r1, &tmp); - } - return ret; -} - -/* multiply 'r' by 2^e */ -int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags) -{ - slimb_t e_max; - if (r->len == 0) - return 0; - e_max = ((limb_t)1 << BF_EXT_EXP_BITS_MAX) - 1; - e = bf_max(e, -e_max); - e = bf_min(e, e_max); - r->expn += e; - return __bf_round(r, prec, flags, r->len, 0); -} - -/* Return e such as a=m*2^e with m odd integer. return 0 if a is zero, - Infinite or Nan. */ -slimb_t bf_get_exp_min(const bf_t *a) -{ - slimb_t i; - limb_t v; - int k; - - for(i = 0; i < a->len; i++) { - v = a->tabi; - if (v != 0) { - k = ctz(v); - return a->expn - (a->len - i) * LIMB_BITS + k; - } - } - return 0; -} - -/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the - integer defined as floor(a/b) and r = a - q * b. */ -static void bf_tdivremu(bf_t *q, bf_t *r, - const bf_t *a, const bf_t *b) -{ - if (bf_cmpu(a, b) < 0) { - bf_set_ui(q, 0); - bf_set(r, a); - } else { - bf_div(q, a, b, bf_max(a->expn - b->expn + 1, 2), BF_RNDZ); - bf_rint(q, BF_RNDZ); - bf_mul(r, q, b, BF_PREC_INF, BF_RNDZ); - bf_sub(r, a, r, BF_PREC_INF, BF_RNDZ); - } -} - -static int __bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - int ret, r_sign; - limb_t n, nb, precl; - - r_sign = a->sign ^ b->sign; - if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) { - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else if (a->expn == BF_EXP_INF) { - bf_set_inf(r, r_sign); - return 0; - } else { - bf_set_zero(r, r_sign); - return 0; - } - } else if (a->expn == BF_EXP_ZERO) { - if (b->expn == BF_EXP_ZERO) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set_zero(r, r_sign); - return 0; - } - } else if (b->expn == BF_EXP_ZERO) { - bf_set_inf(r, r_sign); - return BF_ST_DIVIDE_ZERO; - } - - /* number of limbs of the quotient (2 extra bits for rounding) */ - precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS; - nb = b->len; - n = bf_max(a->len, precl); - - { - limb_t *taba, na; - slimb_t d; - - na = n + nb; - taba = bf_malloc(s, (na + 1) * sizeof(limb_t)); - if (!taba) - goto fail; - d = na - a->len; - memset(taba, 0, d * sizeof(limb_t)); - memcpy(taba + d, a->tab, a->len * sizeof(limb_t)); - if (bf_resize(r, n + 1)) - goto fail1; - if (mp_divnorm(s, r->tab, taba, na, b->tab, nb)) { - fail1: - bf_free(s, taba); - goto fail; - } - /* see if non zero remainder */ - if (mp_scan_nz(taba, nb)) - r->tab0 |= 1; - bf_free(r->ctx, taba); - r->expn = a->expn - b->expn + LIMB_BITS; - r->sign = r_sign; - ret = bf_normalize_and_round(r, prec, flags); - } - return ret; - fail: - bf_set_nan(r); - return BF_ST_MEM_ERROR; -} - -/* division and remainder. - - rnd_mode is the rounding mode for the quotient. The additional - rounding mode BF_RND_EUCLIDIAN is supported. - - 'q' is an integer. 'r' is rounded with prec and flags (prec can be - BF_PREC_INF). -*/ -int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, - limb_t prec, bf_flags_t flags, int rnd_mode) -{ - bf_t a1_s, *a1 = &a1_s; - bf_t b1_s, *b1 = &b1_s; - int q_sign, ret; - BOOL is_ceil, is_rndn; - - assert(q != a && q != b); - assert(r != a && r != b); - assert(q != r); - - if (a->len == 0 || b->len == 0) { - bf_set_zero(q, 0); - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set(r, a); - return bf_round(r, prec, flags); - } - } - - q_sign = a->sign ^ b->sign; - is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); - switch(rnd_mode) { - default: - case BF_RNDZ: - case BF_RNDN: - case BF_RNDNA: - is_ceil = FALSE; - break; - case BF_RNDD: - is_ceil = q_sign; - break; - case BF_RNDU: - is_ceil = q_sign ^ 1; - break; - case BF_RNDA: - is_ceil = TRUE; - break; - case BF_DIVREM_EUCLIDIAN: - is_ceil = a->sign; - break; - } - - a1->expn = a->expn; - a1->tab = a->tab; - a1->len = a->len; - a1->sign = 0; - - b1->expn = b->expn; - b1->tab = b->tab; - b1->len = b->len; - b1->sign = 0; - - /* XXX: could improve to avoid having a large 'q' */ - bf_tdivremu(q, r, a1, b1); - if (bf_is_nan(q) || bf_is_nan(r)) - goto fail; - - if (r->len != 0) { - if (is_rndn) { - int res; - b1->expn--; - res = bf_cmpu(r, b1); - b1->expn++; - if (res > 0 || - (res == 0 && - (rnd_mode == BF_RNDNA || - get_bit(q->tab, q->len, q->len * LIMB_BITS - q->expn)))) { - goto do_sub_r; - } - } else if (is_ceil) { - do_sub_r: - ret = bf_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ); - ret |= bf_sub(r, r, b1, BF_PREC_INF, BF_RNDZ); - if (ret & BF_ST_MEM_ERROR) - goto fail; - } - } - - r->sign ^= a->sign; - q->sign = q_sign; - return bf_round(r, prec, flags); - fail: - bf_set_nan(q); - bf_set_nan(r); - return BF_ST_MEM_ERROR; -} - -int bf_rem(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags, int rnd_mode) -{ - bf_t q_s, *q = &q_s; - int ret; - - bf_init(r->ctx, q); - ret = bf_divrem(q, r, a, b, prec, flags, rnd_mode); - bf_delete(q); - return ret; -} - -static inline int bf_get_limb(slimb_t *pres, const bf_t *a, int flags) -{ -#if LIMB_BITS == 32 - return bf_get_int32(pres, a, flags); -#else - return bf_get_int64(pres, a, flags); -#endif -} - -int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags, int rnd_mode) -{ - bf_t q_s, *q = &q_s; - int ret; - - bf_init(r->ctx, q); - ret = bf_divrem(q, r, a, b, prec, flags, rnd_mode); - bf_get_limb(pq, q, BF_GET_INT_MOD); - bf_delete(q); - return ret; -} - -static __maybe_unused inline limb_t mul_mod(limb_t a, limb_t b, limb_t m) -{ - dlimb_t t; - t = (dlimb_t)a * (dlimb_t)b; - return t % m; -} - -#if defined(USE_MUL_CHECK) -static limb_t mp_mod1(const limb_t *tab, limb_t n, limb_t m, limb_t r) -{ - slimb_t i; - dlimb_t t; - - for(i = n - 1; i >= 0; i--) { - t = ((dlimb_t)r << LIMB_BITS) | tabi; - r = t % m; - } - return r; -} -#endif - -static const uint16_t sqrt_table192 = { -128,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,144,145,146,147,148,149,150,150,151,152,153,154,155,155,156,157,158,159,160,160,161,162,163,163,164,165,166,167,167,168,169,170,170,171,172,173,173,174,175,176,176,177,178,178,179,180,181,181,182,183,183,184,185,185,186,187,187,188,189,189,190,191,192,192,193,193,194,195,195,196,197,197,198,199,199,200,201,201,202,203,203,204,204,205,206,206,207,208,208,209,209,210,211,211,212,212,213,214,214,215,215,216,217,217,218,218,219,219,220,221,221,222,222,223,224,224,225,225,226,226,227,227,228,229,229,230,230,231,231,232,232,233,234,234,235,235,236,236,237,237,238,238,239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,251,252,252,253,253,254,254,255, -}; - -/* a >= 2^(LIMB_BITS - 2). Return (s, r) with s=floor(sqrt(a)) and - r=a-s^2. 0 <= r <= 2 * s */ -static limb_t mp_sqrtrem1(limb_t *pr, limb_t a) -{ - limb_t s1, r1, s, r, q, u, num; - - /* use a table for the 16 -> 8 bit sqrt */ - s1 = sqrt_table(a >> (LIMB_BITS - 8)) - 64; - r1 = (a >> (LIMB_BITS - 16)) - s1 * s1; - if (r1 > 2 * s1) { - r1 -= 2 * s1 + 1; - s1++; - } - - /* one iteration to get a 32 -> 16 bit sqrt */ - num = (r1 << 8) | ((a >> (LIMB_BITS - 32 + 8)) & 0xff); - q = num / (2 * s1); /* q <= 2^8 */ - u = num % (2 * s1); - s = (s1 << 8) + q; - r = (u << 8) | ((a >> (LIMB_BITS - 32)) & 0xff); - r -= q * q; - if ((slimb_t)r < 0) { - s--; - r += 2 * s + 1; - } - -#if LIMB_BITS == 64 - s1 = s; - r1 = r; - /* one more iteration for 64 -> 32 bit sqrt */ - num = (r1 << 16) | ((a >> (LIMB_BITS - 64 + 16)) & 0xffff); - q = num / (2 * s1); /* q <= 2^16 */ - u = num % (2 * s1); - s = (s1 << 16) + q; - r = (u << 16) | ((a >> (LIMB_BITS - 64)) & 0xffff); - r -= q * q; - if ((slimb_t)r < 0) { - s--; - r += 2 * s + 1; - } -#endif - *pr = r; - return s; -} - -/* return floor(sqrt(a)) */ -limb_t bf_isqrt(limb_t a) -{ - limb_t s, r; - int k; - - if (a == 0) - return 0; - k = clz(a) & ~1; - s = mp_sqrtrem1(&r, a << k); - s >>= (k >> 1); - return s; -} - -static limb_t mp_sqrtrem2(limb_t *tabs, limb_t *taba) -{ - limb_t s1, r1, s, q, u, a0, a1; - dlimb_t r, num; - int l; - - a0 = taba0; - a1 = taba1; - s1 = mp_sqrtrem1(&r1, a1); - l = LIMB_BITS / 2; - num = ((dlimb_t)r1 << l) | (a0 >> l); - q = num / (2 * s1); - u = num % (2 * s1); - s = (s1 << l) + q; - r = ((dlimb_t)u << l) | (a0 & (((limb_t)1 << l) - 1)); - if (unlikely((q >> l) != 0)) - r -= (dlimb_t)1 << LIMB_BITS; /* special case when q=2^l */ - else - r -= q * q; - if ((slimb_t)(r >> LIMB_BITS) < 0) { - s--; - r += 2 * (dlimb_t)s + 1; - } - tabs0 = s; - taba0 = r; - return r >> LIMB_BITS; -} - -//#define DEBUG_SQRTREM - -/* tmp_buf must contain (n / 2 + 1 limbs). *prh contains the highest - limb of the remainder. */ -static int mp_sqrtrem_rec(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n, - limb_t *tmp_buf, limb_t *prh) -{ - limb_t l, h, rh, ql, qh, c, i; - - if (n == 1) { - *prh = mp_sqrtrem2(tabs, taba); - return 0; - } -#ifdef DEBUG_SQRTREM - mp_print_str("a", taba, 2 * n); -#endif - l = n / 2; - h = n - l; - if (mp_sqrtrem_rec(s, tabs + l, taba + 2 * l, h, tmp_buf, &qh)) - return -1; -#ifdef DEBUG_SQRTREM - mp_print_str("s1", tabs + l, h); - mp_print_str_h("r1", taba + 2 * l, h, qh); - mp_print_str_h("r2", taba + l, n, qh); -#endif - - /* the remainder is in taba + 2 * l. Its high bit is in qh */ - if (qh) { - mp_sub(taba + 2 * l, taba + 2 * l, tabs + l, h, 0); - } - /* instead of dividing by 2*s, divide by s (which is normalized) - and update q and r */ - if (mp_divnorm(s, tmp_buf, taba + l, n, tabs + l, h)) - return -1; - qh += tmp_bufl; - for(i = 0; i < l; i++) - tabsi = tmp_bufi; - ql = mp_shr(tabs, tabs, l, 1, qh & 1); - qh = qh >> 1; /* 0 or 1 */ - if (ql) - rh = mp_add(taba + l, taba + l, tabs + l, h, 0); - else - rh = 0; -#ifdef DEBUG_SQRTREM - mp_print_str_h("q", tabs, l, qh); - mp_print_str_h("u", taba + l, h, rh); -#endif - - mp_add_ui(tabs + l, qh, h); -#ifdef DEBUG_SQRTREM - mp_print_str_h("s2", tabs, n, sh); -#endif - - /* q = qh, tabsl - 1 ... 0, r = taban - 1 ... l */ - /* subtract q^2. if qh = 1 then q = B^l, so we can take shortcuts */ - if (qh) { - c = qh; - } else { - if (mp_mul(s, taba + n, tabs, l, tabs, l)) - return -1; - c = mp_sub(taba, taba, taba + n, 2 * l, 0); - } - rh -= mp_sub_ui(taba + 2 * l, c, n - 2 * l); - if ((slimb_t)rh < 0) { - mp_sub_ui(tabs, 1, n); - rh += mp_add_mul1(taba, tabs, n, 2); - rh += mp_add_ui(taba, 1, n); - } - *prh = rh; - return 0; -} - -/* 'taba' has 2*n limbs with n >= 1 and taba2*n-1 >= 2 ^ (LIMB_BITS - - 2). Return (s, r) with s=floor(sqrt(a)) and r=a-s^2. 0 <= r <= 2 - * s. tabs has n limbs. r is returned in the lower n limbs of - taba. Its rn is the returned value of the function. */ -/* Algorithm from the article "Karatsuba Square Root" by Paul Zimmermann and - inspirated from its GMP implementation */ -int mp_sqrtrem(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n) -{ - limb_t tmp_buf18; - limb_t *tmp_buf; - mp_size_t n2; - int ret; - n2 = n / 2 + 1; - if (n2 <= countof(tmp_buf1)) { - tmp_buf = tmp_buf1; - } else { - tmp_buf = bf_malloc(s, sizeof(limb_t) * n2); - if (!tmp_buf) - return -1; - } - ret = mp_sqrtrem_rec(s, tabs, taba, n, tmp_buf, taba + n); - if (tmp_buf != tmp_buf1) - bf_free(s, tmp_buf); - return ret; -} - -/* Integer square root with remainder. 'a' must be an integer. r = - floor(sqrt(a)) and rem = a - r^2. BF_ST_INEXACT is set if the result - is inexact. 'rem' can be NULL if the remainder is not needed. */ -int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a) -{ - int ret; - - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - } else if (a->expn == BF_EXP_INF && a->sign) { - goto invalid_op; - } else { - bf_set(r, a); - } - if (rem1) - bf_set_ui(rem1, 0); - ret = 0; - } else if (a->sign) { - invalid_op: - bf_set_nan(r); - if (rem1) - bf_set_ui(rem1, 0); - ret = BF_ST_INVALID_OP; - } else { - bf_t rem_s, *rem; - - bf_sqrt(r, a, (a->expn + 1) / 2, BF_RNDZ); - bf_rint(r, BF_RNDZ); - /* see if the result is exact by computing the remainder */ - if (rem1) { - rem = rem1; - } else { - rem = &rem_s; - bf_init(r->ctx, rem); - } - /* XXX: could avoid recomputing the remainder */ - bf_mul(rem, r, r, BF_PREC_INF, BF_RNDZ); - bf_neg(rem); - bf_add(rem, rem, a, BF_PREC_INF, BF_RNDZ); - if (bf_is_nan(rem)) { - ret = BF_ST_MEM_ERROR; - goto done; - } - if (rem->len != 0) { - ret = BF_ST_INEXACT; - } else { - ret = 0; - } - done: - if (!rem1) - bf_delete(rem); - } - return ret; -} - -int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = a->ctx; - int ret; - - assert(r != a); - - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - } else if (a->expn == BF_EXP_INF && a->sign) { - goto invalid_op; - } else { - bf_set(r, a); - } - ret = 0; - } else if (a->sign) { - invalid_op: - bf_set_nan(r); - ret = BF_ST_INVALID_OP; - } else { - limb_t *a1; - slimb_t n, n1; - limb_t res; - - /* convert the mantissa to an integer with at least 2 * - prec + 4 bits */ - n = (2 * (prec + 2) + 2 * LIMB_BITS - 1) / (2 * LIMB_BITS); - if (bf_resize(r, n)) - goto fail; - a1 = bf_malloc(s, sizeof(limb_t) * 2 * n); - if (!a1) - goto fail; - n1 = bf_min(2 * n, a->len); - memset(a1, 0, (2 * n - n1) * sizeof(limb_t)); - memcpy(a1 + 2 * n - n1, a->tab + a->len - n1, n1 * sizeof(limb_t)); - if (a->expn & 1) { - res = mp_shr(a1, a1, 2 * n, 1, 0); - } else { - res = 0; - } - if (mp_sqrtrem(s, r->tab, a1, n)) { - bf_free(s, a1); - goto fail; - } - if (!res) { - res = mp_scan_nz(a1, n + 1); - } - bf_free(s, a1); - if (!res) { - res = mp_scan_nz(a->tab, a->len - n1); - } - if (res != 0) - r->tab0 |= 1; - r->sign = 0; - r->expn = (a->expn + 1) >> 1; - ret = bf_round(r, prec, flags); - } - return ret; - fail: - bf_set_nan(r); - return BF_ST_MEM_ERROR; -} - -static no_inline int bf_op2(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags, bf_op2_func_t *func) -{ - bf_t tmp; - int ret; - - if (r == a || r == b) { - bf_init(r->ctx, &tmp); - ret = func(&tmp, a, b, prec, flags); - bf_move(r, &tmp); - } else { - ret = func(r, a, b, prec, flags); - } - return ret; -} - -int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_op2(r, a, b, prec, flags, __bf_add); -} - -int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_op2(r, a, b, prec, flags, __bf_sub); -} - -int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_op2(r, a, b, prec, flags, __bf_div); -} - -int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, - bf_flags_t flags) -{ - bf_t b; - int ret; - bf_init(r->ctx, &b); - ret = bf_set_ui(&b, b1); - ret |= bf_mul(r, a, &b, prec, flags); - bf_delete(&b); - return ret; -} - -int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, - bf_flags_t flags) -{ - bf_t b; - int ret; - bf_init(r->ctx, &b); - ret = bf_set_si(&b, b1); - ret |= bf_mul(r, a, &b, prec, flags); - bf_delete(&b); - return ret; -} - -int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, - bf_flags_t flags) -{ - bf_t b; - int ret; - - bf_init(r->ctx, &b); - ret = bf_set_si(&b, b1); - ret |= bf_add(r, a, &b, prec, flags); - bf_delete(&b); - return ret; -} - -static int bf_pow_ui(bf_t *r, const bf_t *a, limb_t b, limb_t prec, - bf_flags_t flags) -{ - int ret, n_bits, i; - - assert(r != a); - if (b == 0) - return bf_set_ui(r, 1); - ret = bf_set(r, a); - n_bits = LIMB_BITS - clz(b); - for(i = n_bits - 2; i >= 0; i--) { - ret |= bf_mul(r, r, r, prec, flags); - if ((b >> i) & 1) - ret |= bf_mul(r, r, a, prec, flags); - } - return ret; -} - -static int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b, - limb_t prec, bf_flags_t flags) -{ - bf_t a; - int ret; - - if (a1 == 10 && b <= LIMB_DIGITS) { - /* use precomputed powers. We do not round at this point - because we expect the caller to do it */ - ret = bf_set_ui(r, mp_pow_decb); - } else { - bf_init(r->ctx, &a); - ret = bf_set_ui(&a, a1); - ret |= bf_pow_ui(r, &a, b, prec, flags); - bf_delete(&a); - } - return ret; -} - -/* convert to integer (infinite precision) */ -int bf_rint(bf_t *r, int rnd_mode) -{ - return bf_round(r, 0, rnd_mode | BF_FLAG_RADPNT_PREC); -} - -/* logical operations */ -#define BF_LOGIC_OR 0 -#define BF_LOGIC_XOR 1 -#define BF_LOGIC_AND 2 - -static inline limb_t bf_logic_op1(limb_t a, limb_t b, int op) -{ - switch(op) { - case BF_LOGIC_OR: - return a | b; - case BF_LOGIC_XOR: - return a ^ b; - default: - case BF_LOGIC_AND: - return a & b; - } -} - -static int bf_logic_op(bf_t *r, const bf_t *a1, const bf_t *b1, int op) -{ - bf_t b1_s, a1_s, *a, *b; - limb_t a_sign, b_sign, r_sign; - slimb_t l, i, a_bit_offset, b_bit_offset; - limb_t v1, v2, v1_mask, v2_mask, r_mask; - int ret; - - assert(r != a1 && r != b1); - - if (a1->expn <= 0) - a_sign = 0; /* minus zero is considered as positive */ - else - a_sign = a1->sign; - - if (b1->expn <= 0) - b_sign = 0; /* minus zero is considered as positive */ - else - b_sign = b1->sign; - - if (a_sign) { - a = &a1_s; - bf_init(r->ctx, a); - if (bf_add_si(a, a1, 1, BF_PREC_INF, BF_RNDZ)) { - b = NULL; - goto fail; - } - } else { - a = (bf_t *)a1; - } - - if (b_sign) { - b = &b1_s; - bf_init(r->ctx, b); - if (bf_add_si(b, b1, 1, BF_PREC_INF, BF_RNDZ)) - goto fail; - } else { - b = (bf_t *)b1; - } - - r_sign = bf_logic_op1(a_sign, b_sign, op); - if (op == BF_LOGIC_AND && r_sign == 0) { - /* no need to compute extra zeros for and */ - if (a_sign == 0 && b_sign == 0) - l = bf_min(a->expn, b->expn); - else if (a_sign == 0) - l = a->expn; - else - l = b->expn; - } else { - l = bf_max(a->expn, b->expn); - } - /* Note: a or b can be zero */ - l = (bf_max(l, 1) + LIMB_BITS - 1) / LIMB_BITS; - if (bf_resize(r, l)) - goto fail; - a_bit_offset = a->len * LIMB_BITS - a->expn; - b_bit_offset = b->len * LIMB_BITS - b->expn; - v1_mask = -a_sign; - v2_mask = -b_sign; - r_mask = -r_sign; - for(i = 0; i < l; i++) { - v1 = get_bits(a->tab, a->len, a_bit_offset + i * LIMB_BITS) ^ v1_mask; - v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS) ^ v2_mask; - r->tabi = bf_logic_op1(v1, v2, op) ^ r_mask; - } - r->expn = l * LIMB_BITS; - r->sign = r_sign; - bf_normalize_and_round(r, BF_PREC_INF, BF_RNDZ); /* cannot fail */ - if (r_sign) { - if (bf_add_si(r, r, -1, BF_PREC_INF, BF_RNDZ)) - goto fail; - } - ret = 0; - done: - if (a == &a1_s) - bf_delete(a); - if (b == &b1_s) - bf_delete(b); - return ret; - fail: - bf_set_nan(r); - ret = BF_ST_MEM_ERROR; - goto done; -} - -/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ -int bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b) -{ - return bf_logic_op(r, a, b, BF_LOGIC_OR); -} - -/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ -int bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b) -{ - return bf_logic_op(r, a, b, BF_LOGIC_XOR); -} - -/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ -int bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b) -{ - return bf_logic_op(r, a, b, BF_LOGIC_AND); -} - -/* conversion between fixed size types */ - -typedef union { - double d; - uint64_t u; -} Float64Union; - -int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode) -{ - Float64Union u; - int e, ret; - uint64_t m; - - ret = 0; - if (a->expn == BF_EXP_NAN) { - u.u = 0x7ff8000000000000; /* quiet nan */ - } else { - bf_t b_s, *b = &b_s; - - bf_init(a->ctx, b); - bf_set(b, a); - if (bf_is_finite(b)) { - ret = bf_round(b, 53, rnd_mode | BF_FLAG_SUBNORMAL | bf_set_exp_bits(11)); - } - if (b->expn == BF_EXP_INF) { - e = (1 << 11) - 1; - m = 0; - } else if (b->expn == BF_EXP_ZERO) { - e = 0; - m = 0; - } else { - e = b->expn + 1023 - 1; -#if LIMB_BITS == 32 - if (b->len == 2) { - m = ((uint64_t)b->tab1 << 32) | b->tab0; - } else { - m = ((uint64_t)b->tab0 << 32); - } -#else - m = b->tab0; -#endif - if (e <= 0) { - /* subnormal */ - m = m >> (12 - e); - e = 0; - } else { - m = (m << 1) >> 12; - } - } - u.u = m | ((uint64_t)e << 52) | ((uint64_t)b->sign << 63); - bf_delete(b); - } - *pres = u.d; - return ret; -} - -int bf_set_float64(bf_t *a, double d) -{ - Float64Union u; - uint64_t m; - int shift, e, sgn; - - u.d = d; - sgn = u.u >> 63; - e = (u.u >> 52) & ((1 << 11) - 1); - m = u.u & (((uint64_t)1 << 52) - 1); - if (e == ((1 << 11) - 1)) { - if (m != 0) { - bf_set_nan(a); - } else { - bf_set_inf(a, sgn); - } - } else if (e == 0) { - if (m == 0) { - bf_set_zero(a, sgn); - } else { - /* subnormal number */ - m <<= 12; - shift = clz64(m); - m <<= shift; - e = -shift; - goto norm; - } - } else { - m = (m << 11) | ((uint64_t)1 << 63); - norm: - a->expn = e - 1023 + 1; -#if LIMB_BITS == 32 - if (bf_resize(a, 2)) - goto fail; - a->tab0 = m; - a->tab1 = m >> 32; -#else - if (bf_resize(a, 1)) - goto fail; - a->tab0 = m; -#endif - a->sign = sgn; - } - return 0; -fail: - bf_set_nan(a); - return BF_ST_MEM_ERROR; -} - -/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there - is an overflow and 0 otherwise. */ -int bf_get_int32(int *pres, const bf_t *a, int flags) -{ - uint32_t v; - int ret; - if (a->expn >= BF_EXP_INF) { - ret = BF_ST_INVALID_OP; - if (flags & BF_GET_INT_MOD) { - v = 0; - } else if (a->expn == BF_EXP_INF) { - v = (uint32_t)INT32_MAX + a->sign; - } else { - v = INT32_MAX; - } - } else if (a->expn <= 0) { - v = 0; - ret = 0; - } else if (a->expn <= 31) { - v = a->taba->len - 1 >> (LIMB_BITS - a->expn); - if (a->sign) - v = -v; - ret = 0; - } else if (!(flags & BF_GET_INT_MOD)) { - ret = BF_ST_INVALID_OP; - if (a->sign) { - v = (uint32_t)INT32_MAX + 1; - if (a->expn == 32 && - (a->taba->len - 1 >> (LIMB_BITS - 32)) == v) { - ret = 0; - } - } else { - v = INT32_MAX; - } - } else { - v = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); - if (a->sign) - v = -v; - ret = 0; - } - *pres = v; - return ret; -} - -/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there - is an overflow and 0 otherwise. */ -int bf_get_int64(int64_t *pres, const bf_t *a, int flags) -{ - uint64_t v; - int ret; - if (a->expn >= BF_EXP_INF) { - ret = BF_ST_INVALID_OP; - if (flags & BF_GET_INT_MOD) { - v = 0; - } else if (a->expn == BF_EXP_INF) { - v = (uint64_t)INT64_MAX + a->sign; - } else { - v = INT64_MAX; - } - } else if (a->expn <= 0) { - v = 0; - ret = 0; - } else if (a->expn <= 63) { -#if LIMB_BITS == 32 - if (a->expn <= 32) - v = a->taba->len - 1 >> (LIMB_BITS - a->expn); - else - v = (((uint64_t)a->taba->len - 1 << 32) | - get_limbz(a, a->len - 2)) >> (64 - a->expn); -#else - v = a->taba->len - 1 >> (LIMB_BITS - a->expn); -#endif - if (a->sign) - v = -v; - ret = 0; - } else if (!(flags & BF_GET_INT_MOD)) { - ret = BF_ST_INVALID_OP; - if (a->sign) { - uint64_t v1; - v = (uint64_t)INT64_MAX + 1; - if (a->expn == 64) { - v1 = a->taba->len - 1; -#if LIMB_BITS == 32 - v1 = (v1 << 32) | get_limbz(a, a->len - 2); -#endif - if (v1 == v) - ret = 0; - } - } else { - v = INT64_MAX; - } - } else { - slimb_t bit_pos = a->len * LIMB_BITS - a->expn; - v = get_bits(a->tab, a->len, bit_pos); -#if LIMB_BITS == 32 - v |= (uint64_t)get_bits(a->tab, a->len, bit_pos + 32) << 32; -#endif - if (a->sign) - v = -v; - ret = 0; - } - *pres = v; - return ret; -} - -/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there - is an overflow and 0 otherwise. */ -int bf_get_uint64(uint64_t *pres, const bf_t *a) -{ - uint64_t v; - int ret; - if (a->expn == BF_EXP_NAN) { - goto overflow; - } else if (a->expn <= 0) { - v = 0; - ret = 0; - } else if (a->sign) { - v = 0; - ret = BF_ST_INVALID_OP; - } else if (a->expn <= 64) { -#if LIMB_BITS == 32 - if (a->expn <= 32) - v = a->taba->len - 1 >> (LIMB_BITS - a->expn); - else - v = (((uint64_t)a->taba->len - 1 << 32) | - get_limbz(a, a->len - 2)) >> (64 - a->expn); -#else - v = a->taba->len - 1 >> (LIMB_BITS - a->expn); -#endif - ret = 0; - } else { - overflow: - v = UINT64_MAX; - ret = BF_ST_INVALID_OP; - } - *pres = v; - return ret; -} - -/* base conversion from radix */ - -static const uint8_t digits_per_limb_tableBF_RADIX_MAX - 1 = { -#if LIMB_BITS == 32 -32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, -#else -64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, -#endif -}; - -static limb_t get_limb_radix(int radix) -{ - int i, k; - limb_t radixl; - - k = digits_per_limb_tableradix - 2; - radixl = radix; - for(i = 1; i < k; i++) - radixl *= radix; - return radixl; -} - -/* return != 0 if error */ -static int bf_integer_from_radix_rec(bf_t *r, const limb_t *tab, - limb_t n, int level, limb_t n0, - limb_t radix, bf_t *pow_tab) -{ - int ret; - if (n == 1) { - ret = bf_set_ui(r, tab0); - } else { - bf_t T_s, *T = &T_s, *B; - limb_t n1, n2; - - n2 = (((n0 * 2) >> (level + 1)) + 1) / 2; - n1 = n - n2; - // printf("level=%d n0=%ld n1=%ld n2=%ld\n", level, n0, n1, n2); - B = &pow_tablevel; - if (B->len == 0) { - ret = bf_pow_ui_ui(B, radix, n2, BF_PREC_INF, BF_RNDZ); - if (ret) - return ret; - } - ret = bf_integer_from_radix_rec(r, tab + n2, n1, level + 1, n0, - radix, pow_tab); - if (ret) - return ret; - ret = bf_mul(r, r, B, BF_PREC_INF, BF_RNDZ); - if (ret) - return ret; - bf_init(r->ctx, T); - ret = bf_integer_from_radix_rec(T, tab, n2, level + 1, n0, - radix, pow_tab); - if (!ret) - ret = bf_add(r, r, T, BF_PREC_INF, BF_RNDZ); - bf_delete(T); - } - return ret; - // bf_print_str(" r=", r); -} - -/* return 0 if OK != 0 if memory error */ -static int bf_integer_from_radix(bf_t *r, const limb_t *tab, - limb_t n, limb_t radix) -{ - bf_context_t *s = r->ctx; - int pow_tab_len, i, ret; - limb_t radixl; - bf_t *pow_tab; - - radixl = get_limb_radix(radix); - pow_tab_len = ceil_log2(n) + 2; /* XXX: check */ - pow_tab = bf_malloc(s, sizeof(pow_tab0) * pow_tab_len); - if (!pow_tab) - return -1; - for(i = 0; i < pow_tab_len; i++) - bf_init(r->ctx, &pow_tabi); - ret = bf_integer_from_radix_rec(r, tab, n, 0, n, radixl, pow_tab); - for(i = 0; i < pow_tab_len; i++) { - bf_delete(&pow_tabi); - } - bf_free(s, pow_tab); - return ret; -} - -/* compute and round T * radix^expn. */ -int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, - slimb_t expn, limb_t prec, bf_flags_t flags) -{ - int ret, expn_sign, overflow; - slimb_t e, extra_bits, prec1, ziv_extra_bits; - bf_t B_s, *B = &B_s; - - if (T->len == 0) { - return bf_set(r, T); - } else if (expn == 0) { - ret = bf_set(r, T); - ret |= bf_round(r, prec, flags); - return ret; - } - - e = expn; - expn_sign = 0; - if (e < 0) { - e = -e; - expn_sign = 1; - } - bf_init(r->ctx, B); - if (prec == BF_PREC_INF) { - /* infinite precision: only used if the result is known to be exact */ - ret = bf_pow_ui_ui(B, radix, e, BF_PREC_INF, BF_RNDN); - if (expn_sign) { - ret |= bf_div(r, T, B, T->len * LIMB_BITS, BF_RNDN); - } else { - ret |= bf_mul(r, T, B, BF_PREC_INF, BF_RNDN); - } - } else { - ziv_extra_bits = 16; - for(;;) { - prec1 = prec + ziv_extra_bits; - /* XXX: correct overflow/underflow handling */ - /* XXX: rigorous error analysis needed */ - extra_bits = ceil_log2(e) * 2 + 1; - ret = bf_pow_ui_ui(B, radix, e, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); - overflow = !bf_is_finite(B); - /* XXX: if bf_pow_ui_ui returns an exact result, can stop - after the next operation */ - if (expn_sign) - ret |= bf_div(r, T, B, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); - else - ret |= bf_mul(r, T, B, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); - if (ret & BF_ST_MEM_ERROR) - break; - if ((ret & BF_ST_INEXACT) && - !bf_can_round(r, prec, flags & BF_RND_MASK, prec1) && - !overflow) { - /* and more precision and retry */ - ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); - } else { - /* XXX: need to use __bf_round() to pass the inexact - flag for the subnormal case */ - ret = bf_round(r, prec, flags) | (ret & BF_ST_INEXACT); - break; - } - } - } - bf_delete(B); - return ret; -} - -static inline int to_digit(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - else if (c >= 'a' && c <= 'z') - return c - 'a' + 10; - else - return 36; -} - -/* add a limb at 'pos' and decrement pos. new space is created if - needed. Return 0 if OK, -1 if memory error */ -static int bf_add_limb(bf_t *a, slimb_t *ppos, limb_t v) -{ - slimb_t pos; - pos = *ppos; - if (unlikely(pos < 0)) { - limb_t new_size, d, *new_tab; - new_size = bf_max(a->len + 1, a->len * 3 / 2); - new_tab = bf_realloc(a->ctx, a->tab, sizeof(limb_t) * new_size); - if (!new_tab) - return -1; - a->tab = new_tab; - d = new_size - a->len; - memmove(a->tab + d, a->tab, a->len * sizeof(limb_t)); - a->len = new_size; - pos += d; - } - a->tabpos-- = v; - *ppos = pos; - return 0; -} - -static int bf_tolower(int c) -{ - if (c >= 'A' && c <= 'Z') - c = c - 'A' + 'a'; - return c; -} - -static int strcasestart(const char *str, const char *val, const char **ptr) -{ - const char *p, *q; - p = str; - q = val; - while (*q != '\0') { - if (bf_tolower(*p) != *q) - return 0; - p++; - q++; - } - if (ptr) - *ptr = p; - return 1; -} - -static int bf_atof_internal(bf_t *r, slimb_t *pexponent, - const char *str, const char **pnext, int radix, - limb_t prec, bf_flags_t flags, BOOL is_dec) -{ - const char *p, *p_start; - int is_neg, radix_bits, exp_is_neg, ret, digits_per_limb, shift; - limb_t cur_limb; - slimb_t pos, expn, int_len, digit_count; - BOOL has_decpt, is_bin_exp; - bf_t a_s, *a; - - *pexponent = 0; - p = str; - if (!(flags & BF_ATOF_NO_NAN_INF) && radix <= 16 && - strcasestart(p, "nan", &p)) { - bf_set_nan(r); - ret = 0; - goto done; - } - is_neg = 0; - - if (p0 == '+') { - p++; - p_start = p; - } else if (p0 == '-') { - is_neg = 1; - p++; - p_start = p; - } else { - p_start = p; - } - if (p0 == '0') { - if ((p1 == 'x' || p1 == 'X') && - (radix == 0 || radix == 16) && - !(flags & BF_ATOF_NO_HEX)) { - radix = 16; - p += 2; - } else if ((p1 == 'o' || p1 == 'O') && - radix == 0 && (flags & BF_ATOF_BIN_OCT)) { - p += 2; - radix = 8; - } else if ((p1 == 'b' || p1 == 'B') && - radix == 0 && (flags & BF_ATOF_BIN_OCT)) { - p += 2; - radix = 2; - } else { - goto no_prefix; - } - /* there must be a digit after the prefix */ - if (to_digit((uint8_t)*p) >= radix) { - bf_set_nan(r); - ret = 0; - goto done; - } - no_prefix: ; - } else { - if (!(flags & BF_ATOF_NO_NAN_INF) && radix <= 16 && - strcasestart(p, "inf", &p)) { - bf_set_inf(r, is_neg); - ret = 0; - goto done; - } - } - - if (radix == 0) - radix = 10; - if (is_dec) { - assert(radix == 10); - radix_bits = 0; - a = r; - } else if ((radix & (radix - 1)) != 0) { - radix_bits = 0; /* base is not a power of two */ - a = &a_s; - bf_init(r->ctx, a); - } else { - radix_bits = ceil_log2(radix); - a = r; - } - - /* skip leading zeros */ - /* XXX: could also skip zeros after the decimal point */ - while (*p == '0') - p++; - - if (radix_bits) { - shift = digits_per_limb = LIMB_BITS; - } else { - radix_bits = 0; - shift = digits_per_limb = digits_per_limb_tableradix - 2; - } - cur_limb = 0; - bf_resize(a, 1); - pos = 0; - has_decpt = FALSE; - int_len = digit_count = 0; - for(;;) { - limb_t c; - if (*p == '.' && (p > p_start || to_digit(p1) < radix)) { - if (has_decpt) - break; - has_decpt = TRUE; - int_len = digit_count; - p++; - } - c = to_digit(*p); - if (c >= radix) - break; - digit_count++; - p++; - if (radix_bits) { - shift -= radix_bits; - if (shift <= 0) { - cur_limb |= c >> (-shift); - if (bf_add_limb(a, &pos, cur_limb)) - goto mem_error; - if (shift < 0) - cur_limb = c << (LIMB_BITS + shift); - else - cur_limb = 0; - shift += LIMB_BITS; - } else { - cur_limb |= c << shift; - } - } else { - cur_limb = cur_limb * radix + c; - shift--; - if (shift == 0) { - if (bf_add_limb(a, &pos, cur_limb)) - goto mem_error; - shift = digits_per_limb; - cur_limb = 0; - } - } - } - if (!has_decpt) - int_len = digit_count; - - /* add the last limb and pad with zeros */ - if (shift != digits_per_limb) { - if (radix_bits == 0) { - while (shift != 0) { - cur_limb *= radix; - shift--; - } - } - if (bf_add_limb(a, &pos, cur_limb)) { - mem_error: - ret = BF_ST_MEM_ERROR; - if (!radix_bits) - bf_delete(a); - bf_set_nan(r); - goto done; - } - } - - /* reset the next limbs to zero (we prefer to reallocate in the - renormalization) */ - memset(a->tab, 0, (pos + 1) * sizeof(limb_t)); - - if (p == p_start) { - ret = 0; - if (!radix_bits) - bf_delete(a); - bf_set_nan(r); - goto done; - } - - /* parse the exponent, if any */ - expn = 0; - is_bin_exp = FALSE; - if (((radix == 10 && (*p == 'e' || *p == 'E')) || - (radix != 10 && (*p == '@' || - (radix_bits && (*p == 'p' || *p == 'P'))))) && - p > p_start) { - is_bin_exp = (*p == 'p' || *p == 'P'); - p++; - exp_is_neg = 0; - if (*p == '+') { - p++; - } else if (*p == '-') { - exp_is_neg = 1; - p++; - } - for(;;) { - int c; - c = to_digit(*p); - if (c >= 10) - break; - if (unlikely(expn > ((BF_RAW_EXP_MAX - 2 - 9) / 10))) { - /* exponent overflow */ - if (exp_is_neg) { - bf_set_zero(r, is_neg); - ret = BF_ST_UNDERFLOW | BF_ST_INEXACT; - } else { - bf_set_inf(r, is_neg); - ret = BF_ST_OVERFLOW | BF_ST_INEXACT; - } - goto done; - } - p++; - expn = expn * 10 + c; - } - if (exp_is_neg) - expn = -expn; - } - if (is_dec) { - a->expn = expn + int_len; - a->sign = is_neg; - ret = bfdec_normalize_and_round((bfdec_t *)a, prec, flags); - } else if (radix_bits) { - /* XXX: may overflow */ - if (!is_bin_exp) - expn *= radix_bits; - a->expn = expn + (int_len * radix_bits); - a->sign = is_neg; - ret = bf_normalize_and_round(a, prec, flags); - } else { - limb_t l; - pos++; - l = a->len - pos; /* number of limbs */ - if (l == 0) { - bf_set_zero(r, is_neg); - ret = 0; - } else { - bf_t T_s, *T = &T_s; - - expn -= l * digits_per_limb - int_len; - bf_init(r->ctx, T); - if (bf_integer_from_radix(T, a->tab + pos, l, radix)) { - bf_set_nan(r); - ret = BF_ST_MEM_ERROR; - } else { - T->sign = is_neg; - if (flags & BF_ATOF_EXPONENT) { - /* return the exponent */ - *pexponent = expn; - ret = bf_set(r, T); - } else { - ret = bf_mul_pow_radix(r, T, radix, expn, prec, flags); - } - } - bf_delete(T); - } - bf_delete(a); - } - done: - if (pnext) - *pnext = p; - return ret; -} - -/* - Return (status, n, exp). 'status' is the floating point status. 'n' - is the parsed number. - - If (flags & BF_ATOF_EXPONENT) and if the radix is not a power of - two, the parsed number is equal to r * - (*pexponent)^radix. Otherwise *pexponent = 0. -*/ -int bf_atof2(bf_t *r, slimb_t *pexponent, - const char *str, const char **pnext, int radix, - limb_t prec, bf_flags_t flags) -{ - return bf_atof_internal(r, pexponent, str, pnext, radix, prec, flags, - FALSE); -} - -int bf_atof(bf_t *r, const char *str, const char **pnext, int radix, - limb_t prec, bf_flags_t flags) -{ - slimb_t dummy_exp; - return bf_atof_internal(r, &dummy_exp, str, pnext, radix, prec, flags, FALSE); -} - -/* base conversion to radix */ - -#if LIMB_BITS == 64 -#define RADIXL_10 UINT64_C(10000000000000000000) -#else -#define RADIXL_10 UINT64_C(1000000000) -#endif - -static const uint32_t inv_log2_radixBF_RADIX_MAX - 1LIMB_BITS / 32 + 1 = { -#if LIMB_BITS == 32 -{ 0x80000000, 0x00000000,}, -{ 0x50c24e60, 0xd4d4f4a7,}, -{ 0x40000000, 0x00000000,}, -{ 0x372068d2, 0x0a1ee5ca,}, -{ 0x3184648d, 0xb8153e7a,}, -{ 0x2d983275, 0x9d5369c4,}, -{ 0x2aaaaaaa, 0xaaaaaaab,}, -{ 0x28612730, 0x6a6a7a54,}, -{ 0x268826a1, 0x3ef3fde6,}, -{ 0x25001383, 0xbac8a744,}, -{ 0x23b46706, 0x82c0c709,}, -{ 0x229729f1, 0xb2c83ded,}, -{ 0x219e7ffd, 0xa5ad572b,}, -{ 0x20c33b88, 0xda7c29ab,}, -{ 0x20000000, 0x00000000,}, -{ 0x1f50b57e, 0xac5884b3,}, -{ 0x1eb22cc6, 0x8aa6e26f,}, -{ 0x1e21e118, 0x0c5daab2,}, -{ 0x1d9dcd21, 0x439834e4,}, -{ 0x1d244c78, 0x367a0d65,}, -{ 0x1cb40589, 0xac173e0c,}, -{ 0x1c4bd95b, 0xa8d72b0d,}, -{ 0x1bead768, 0x98f8ce4c,}, -{ 0x1b903469, 0x050f72e5,}, -{ 0x1b3b433f, 0x2eb06f15,}, -{ 0x1aeb6f75, 0x9c46fc38,}, -{ 0x1aa038eb, 0x0e3bfd17,}, -{ 0x1a593062, 0xb38d8c56,}, -{ 0x1a15f4c3, 0x2b95a2e6,}, -{ 0x19d630dc, 0xcc7ddef9,}, -{ 0x19999999, 0x9999999a,}, -{ 0x195fec80, 0x8a609431,}, -{ 0x1928ee7b, 0x0b4f22f9,}, -{ 0x18f46acf, 0x8c06e318,}, -{ 0x18c23246, 0xdc0a9f3d,}, -#else -{ 0x80000000, 0x00000000, 0x00000000,}, -{ 0x50c24e60, 0xd4d4f4a7, 0x021f57bc,}, -{ 0x40000000, 0x00000000, 0x00000000,}, -{ 0x372068d2, 0x0a1ee5ca, 0x19ea911b,}, -{ 0x3184648d, 0xb8153e7a, 0x7fc2d2e1,}, -{ 0x2d983275, 0x9d5369c4, 0x4dec1661,}, -{ 0x2aaaaaaa, 0xaaaaaaaa, 0xaaaaaaab,}, -{ 0x28612730, 0x6a6a7a53, 0x810fabde,}, -{ 0x268826a1, 0x3ef3fde6, 0x23e2566b,}, -{ 0x25001383, 0xbac8a744, 0x385a3349,}, -{ 0x23b46706, 0x82c0c709, 0x3f891718,}, -{ 0x229729f1, 0xb2c83ded, 0x15fba800,}, -{ 0x219e7ffd, 0xa5ad572a, 0xe169744b,}, -{ 0x20c33b88, 0xda7c29aa, 0x9bddee52,}, -{ 0x20000000, 0x00000000, 0x00000000,}, -{ 0x1f50b57e, 0xac5884b3, 0x70e28eee,}, -{ 0x1eb22cc6, 0x8aa6e26f, 0x06d1a2a2,}, -{ 0x1e21e118, 0x0c5daab1, 0x81b4f4bf,}, -{ 0x1d9dcd21, 0x439834e3, 0x81667575,}, -{ 0x1d244c78, 0x367a0d64, 0xc8204d6d,}, -{ 0x1cb40589, 0xac173e0c, 0x3b7b16ba,}, -{ 0x1c4bd95b, 0xa8d72b0d, 0x5879f25a,}, -{ 0x1bead768, 0x98f8ce4c, 0x66cc2858,}, -{ 0x1b903469, 0x050f72e5, 0x0cf5488e,}, -{ 0x1b3b433f, 0x2eb06f14, 0x8c89719c,}, -{ 0x1aeb6f75, 0x9c46fc37, 0xab5fc7e9,}, -{ 0x1aa038eb, 0x0e3bfd17, 0x1bd62080,}, -{ 0x1a593062, 0xb38d8c56, 0x7998ab45,}, -{ 0x1a15f4c3, 0x2b95a2e6, 0x46aed6a0,}, -{ 0x19d630dc, 0xcc7ddef9, 0x5aadd61b,}, -{ 0x19999999, 0x99999999, 0x9999999a,}, -{ 0x195fec80, 0x8a609430, 0xe1106014,}, -{ 0x1928ee7b, 0x0b4f22f9, 0x5f69791d,}, -{ 0x18f46acf, 0x8c06e318, 0x4d2aeb2c,}, -{ 0x18c23246, 0xdc0a9f3d, 0x3fe16970,}, -#endif -}; - -static const limb_t log2_radixBF_RADIX_MAX - 1 = { -#if LIMB_BITS == 32 -0x20000000, -0x32b80347, -0x40000000, -0x4a4d3c26, -0x52b80347, -0x59d5d9fd, -0x60000000, -0x6570068e, -0x6a4d3c26, -0x6eb3a9f0, -0x72b80347, -0x766a008e, -0x79d5d9fd, -0x7d053f6d, -0x80000000, -0x82cc7edf, -0x8570068e, -0x87ef05ae, -0x8a4d3c26, -0x8c8ddd45, -0x8eb3a9f0, -0x90c10501, -0x92b80347, -0x949a784c, -0x966a008e, -0x982809d6, -0x99d5d9fd, -0x9b74948f, -0x9d053f6d, -0x9e88c6b3, -0xa0000000, -0xa16bad37, -0xa2cc7edf, -0xa4231623, -0xa570068e, -#else -0x2000000000000000, -0x32b803473f7ad0f4, -0x4000000000000000, -0x4a4d3c25e68dc57f, -0x52b803473f7ad0f4, -0x59d5d9fd5010b366, -0x6000000000000000, -0x6570068e7ef5a1e8, -0x6a4d3c25e68dc57f, -0x6eb3a9f01975077f, -0x72b803473f7ad0f4, -0x766a008e4788cbcd, -0x79d5d9fd5010b366, -0x7d053f6d26089673, -0x8000000000000000, -0x82cc7edf592262d0, -0x8570068e7ef5a1e8, -0x87ef05ae409a0289, -0x8a4d3c25e68dc57f, -0x8c8ddd448f8b845a, -0x8eb3a9f01975077f, -0x90c10500d63aa659, -0x92b803473f7ad0f4, -0x949a784bcd1b8afe, -0x966a008e4788cbcd, -0x982809d5be7072dc, -0x99d5d9fd5010b366, -0x9b74948f5532da4b, -0x9d053f6d26089673, -0x9e88c6b3626a72aa, -0xa000000000000000, -0xa16bad3758efd873, -0xa2cc7edf592262d0, -0xa4231623369e78e6, -0xa570068e7ef5a1e8, -#endif -}; - -/* compute floor(a*b) or ceil(a*b) with b = log2(radix) or - b=1/log2(radix). For is_inv = 0, strict accuracy is not guaranteed - when radix is not a power of two. */ -slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, - int is_ceil1) -{ - int is_neg; - limb_t a; - BOOL is_ceil; - - is_ceil = is_ceil1; - a = a1; - if (a1 < 0) { - a = -a; - is_neg = 1; - } else { - is_neg = 0; - } - is_ceil ^= is_neg; - if ((radix & (radix - 1)) == 0) { - int radix_bits; - /* radix is a power of two */ - radix_bits = ceil_log2(radix); - if (is_inv) { - if (is_ceil) - a += radix_bits - 1; - a = a / radix_bits; - } else { - a = a * radix_bits; - } - } else { - const uint32_t *tab; - limb_t b0, b1; - dlimb_t t; - - if (is_inv) { - tab = inv_log2_radixradix - 2; -#if LIMB_BITS == 32 - b1 = tab0; - b0 = tab1; -#else - b1 = ((limb_t)tab0 << 32) | tab1; - b0 = (limb_t)tab2 << 32; -#endif - t = (dlimb_t)b0 * (dlimb_t)a; - t = (dlimb_t)b1 * (dlimb_t)a + (t >> LIMB_BITS); - a = t >> (LIMB_BITS - 1); - } else { - b0 = log2_radixradix - 2; - t = (dlimb_t)b0 * (dlimb_t)a; - a = t >> (LIMB_BITS - 3); - } - /* a = floor(result) and 'result' cannot be an integer */ - a += is_ceil; - } - if (is_neg) - a = -a; - return a; -} - -/* 'n' is the number of output limbs */ -static int bf_integer_to_radix_rec(bf_t *pow_tab, - limb_t *out, const bf_t *a, limb_t n, - int level, limb_t n0, limb_t radixl, - unsigned int radixl_bits) -{ - limb_t n1, n2, q_prec; - int ret; - - assert(n >= 1); - if (n == 1) { - out0 = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); - } else if (n == 2) { - dlimb_t t; - slimb_t pos; - pos = a->len * LIMB_BITS - a->expn; - t = ((dlimb_t)get_bits(a->tab, a->len, pos + LIMB_BITS) << LIMB_BITS) | - get_bits(a->tab, a->len, pos); - if (likely(radixl == RADIXL_10)) { - /* use division by a constant when possible */ - out0 = t % RADIXL_10; - out1 = t / RADIXL_10; - } else { - out0 = t % radixl; - out1 = t / radixl; - } - } else { - bf_t Q, R, *B, *B_inv; - int q_add; - bf_init(a->ctx, &Q); - bf_init(a->ctx, &R); - n2 = (((n0 * 2) >> (level + 1)) + 1) / 2; - n1 = n - n2; - B = &pow_tab2 * level; - B_inv = &pow_tab2 * level + 1; - ret = 0; - if (B->len == 0) { - /* compute BASE^n2 */ - ret |= bf_pow_ui_ui(B, radixl, n2, BF_PREC_INF, BF_RNDZ); - /* we use enough bits for the maximum possible 'n1' value, - i.e. n2 + 1 */ - ret |= bf_set_ui(&R, 1); - ret |= bf_div(B_inv, &R, B, (n2 + 1) * radixl_bits + 2, BF_RNDN); - } - // printf("%d: n1=% " PRId64 " n2=%" PRId64 "\n", level, n1, n2); - q_prec = n1 * radixl_bits; - ret |= bf_mul(&Q, a, B_inv, q_prec, BF_RNDN); - ret |= bf_rint(&Q, BF_RNDZ); - - ret |= bf_mul(&R, &Q, B, BF_PREC_INF, BF_RNDZ); - ret |= bf_sub(&R, a, &R, BF_PREC_INF, BF_RNDZ); - - if (ret & BF_ST_MEM_ERROR) - goto fail; - /* adjust if necessary */ - q_add = 0; - while (R.sign && R.len != 0) { - if (bf_add(&R, &R, B, BF_PREC_INF, BF_RNDZ)) - goto fail; - q_add--; - } - while (bf_cmpu(&R, B) >= 0) { - if (bf_sub(&R, &R, B, BF_PREC_INF, BF_RNDZ)) - goto fail; - q_add++; - } - if (q_add != 0) { - if (bf_add_si(&Q, &Q, q_add, BF_PREC_INF, BF_RNDZ)) - goto fail; - } - if (bf_integer_to_radix_rec(pow_tab, out + n2, &Q, n1, level + 1, n0, - radixl, radixl_bits)) - goto fail; - if (bf_integer_to_radix_rec(pow_tab, out, &R, n2, level + 1, n0, - radixl, radixl_bits)) { - fail: - bf_delete(&Q); - bf_delete(&R); - return -1; - } - bf_delete(&Q); - bf_delete(&R); - } - return 0; -} - -/* return 0 if OK != 0 if memory error */ -static int bf_integer_to_radix(bf_t *r, const bf_t *a, limb_t radixl) -{ - bf_context_t *s = r->ctx; - limb_t r_len; - bf_t *pow_tab; - int i, pow_tab_len, ret; - - r_len = r->len; - pow_tab_len = (ceil_log2(r_len) + 2) * 2; /* XXX: check */ - pow_tab = bf_malloc(s, sizeof(pow_tab0) * pow_tab_len); - if (!pow_tab) - return -1; - for(i = 0; i < pow_tab_len; i++) - bf_init(r->ctx, &pow_tabi); - - ret = bf_integer_to_radix_rec(pow_tab, r->tab, a, r_len, 0, r_len, radixl, - ceil_log2(radixl)); - - for(i = 0; i < pow_tab_len; i++) { - bf_delete(&pow_tabi); - } - bf_free(s, pow_tab); - return ret; -} - -/* a must be >= 0. 'P' is the wanted number of digits in radix - 'radix'. 'r' is the mantissa represented as an integer. *pE - contains the exponent. Return != 0 if memory error. */ -static int bf_convert_to_radix(bf_t *r, slimb_t *pE, - const bf_t *a, int radix, - limb_t P, bf_rnd_t rnd_mode, - BOOL is_fixed_exponent) -{ - slimb_t E, e, prec, extra_bits, ziv_extra_bits, prec0; - bf_t B_s, *B = &B_s; - int e_sign, ret, res; - - if (a->len == 0) { - /* zero case */ - *pE = 0; - return bf_set(r, a); - } - - if (is_fixed_exponent) { - E = *pE; - } else { - /* compute the new exponent */ - E = 1 + bf_mul_log2_radix(a->expn - 1, radix, TRUE, FALSE); - } - // bf_print_str("a", a); - // printf("E=%ld P=%ld radix=%d\n", E, P, radix); - - for(;;) { - e = P - E; - e_sign = 0; - if (e < 0) { - e = -e; - e_sign = 1; - } - /* Note: precision for log2(radix) is not critical here */ - prec0 = bf_mul_log2_radix(P, radix, FALSE, TRUE); - ziv_extra_bits = 16; - for(;;) { - prec = prec0 + ziv_extra_bits; - /* XXX: rigorous error analysis needed */ - extra_bits = ceil_log2(e) * 2 + 1; - ret = bf_pow_ui_ui(r, radix, e, prec + extra_bits, - BF_RNDN | BF_FLAG_EXT_EXP); - if (!e_sign) - ret |= bf_mul(r, r, a, prec + extra_bits, - BF_RNDN | BF_FLAG_EXT_EXP); - else - ret |= bf_div(r, a, r, prec + extra_bits, - BF_RNDN | BF_FLAG_EXT_EXP); - if (ret & BF_ST_MEM_ERROR) - return BF_ST_MEM_ERROR; - /* if the result is not exact, check that it can be safely - rounded to an integer */ - if ((ret & BF_ST_INEXACT) && - !bf_can_round(r, r->expn, rnd_mode, prec)) { - /* and more precision and retry */ - ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); - continue; - } else { - ret = bf_rint(r, rnd_mode); - if (ret & BF_ST_MEM_ERROR) - return BF_ST_MEM_ERROR; - break; - } - } - if (is_fixed_exponent) - break; - /* check that the result is < B^P */ - /* XXX: do a fast approximate test first ? */ - bf_init(r->ctx, B); - ret = bf_pow_ui_ui(B, radix, P, BF_PREC_INF, BF_RNDZ); - if (ret) { - bf_delete(B); - return ret; - } - res = bf_cmpu(r, B); - bf_delete(B); - if (res < 0) - break; - /* try a larger exponent */ - E++; - } - *pE = E; - return 0; -} - -static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) -{ - int digit, i; - - if (radix == 10) { - /* specific case with constant divisor */ - for(i = len - 1; i >= 0; i--) { - digit = (limb_t)n % 10; - n = (limb_t)n / 10; - bufi = digit + '0'; - } - } else { - for(i = len - 1; i >= 0; i--) { - digit = (limb_t)n % radix; - n = (limb_t)n / radix; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - bufi = digit; - } - } -} - -/* for power of 2 radixes */ -static void limb_to_a2(char *buf, limb_t n, unsigned int radix_bits, int len) -{ - int digit, i; - unsigned int mask; - - mask = (1 << radix_bits) - 1; - for(i = len - 1; i >= 0; i--) { - digit = n & mask; - n >>= radix_bits; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - bufi = digit; - } -} - -/* 'a' must be an integer if the is_dec = FALSE or if the radix is not - a power of two. A dot is added before the 'dot_pos' digit. dot_pos - = n_digits does not display the dot. 0 <= dot_pos <= - n_digits. n_digits >= 1. */ -static void output_digits(DynBuf *s, const bf_t *a1, int radix, limb_t n_digits, - limb_t dot_pos, BOOL is_dec) -{ - limb_t i, v, l; - slimb_t pos, pos_incr; - int digits_per_limb, buf_pos, radix_bits, first_buf_pos; - char buf65; - bf_t a_s, *a; - - if (is_dec) { - digits_per_limb = LIMB_DIGITS; - a = (bf_t *)a1; - radix_bits = 0; - pos = a->len; - pos_incr = 1; - first_buf_pos = 0; - } else if ((radix & (radix - 1)) == 0) { - a = (bf_t *)a1; - radix_bits = ceil_log2(radix); - digits_per_limb = LIMB_BITS / radix_bits; - pos_incr = digits_per_limb * radix_bits; - /* digits are aligned relative to the radix point */ - pos = a->len * LIMB_BITS + smod(-a->expn, radix_bits); - first_buf_pos = 0; - } else { - limb_t n, radixl; - - digits_per_limb = digits_per_limb_tableradix - 2; - radixl = get_limb_radix(radix); - a = &a_s; - bf_init(a1->ctx, a); - n = (n_digits + digits_per_limb - 1) / digits_per_limb; - if (bf_resize(a, n)) { - dbuf_set_error(s); - goto done; - } - if (bf_integer_to_radix(a, a1, radixl)) { - dbuf_set_error(s); - goto done; - } - radix_bits = 0; - pos = n; - pos_incr = 1; - first_buf_pos = pos * digits_per_limb - n_digits; - } - buf_pos = digits_per_limb; - i = 0; - while (i < n_digits) { - if (buf_pos == digits_per_limb) { - pos -= pos_incr; - if (radix_bits == 0) { - v = get_limbz(a, pos); - limb_to_a(buf, v, radix, digits_per_limb); - } else { - v = get_bits(a->tab, a->len, pos); - limb_to_a2(buf, v, radix_bits, digits_per_limb); - } - buf_pos = first_buf_pos; - first_buf_pos = 0; - } - if (i < dot_pos) { - l = dot_pos; - } else { - if (i == dot_pos) - dbuf_putc(s, '.'); - l = n_digits; - } - l = bf_min(digits_per_limb - buf_pos, l - i); - dbuf_put(s, (uint8_t *)(buf + buf_pos), l); - buf_pos += l; - i += l; - } - done: - if (a != a1) - bf_delete(a); -} - -static void *bf_dbuf_realloc(void *opaque, void *ptr, size_t size) -{ - bf_context_t *s = opaque; - return bf_realloc(s, ptr, size); -} - -/* return the length in bytes. A trailing '\0' is added */ -static char *bf_ftoa_internal(size_t *plen, const bf_t *a2, int radix, - limb_t prec, bf_flags_t flags, BOOL is_dec) -{ - bf_context_t *ctx = a2->ctx; - DynBuf s_s, *s = &s_s; - int radix_bits; - - // bf_print_str("ftoa", a2); - // printf("radix=%d\n", radix); - dbuf_init2(s, ctx, bf_dbuf_realloc); - if (a2->expn == BF_EXP_NAN) { - dbuf_putstr(s, "NaN"); - } else { - if (a2->sign) - dbuf_putc(s, '-'); - if (a2->expn == BF_EXP_INF) { - if (flags & BF_FTOA_JS_QUIRKS) - dbuf_putstr(s, "Infinity"); - else - dbuf_putstr(s, "Inf"); - } else { - int fmt, ret; - slimb_t n_digits, n, i, n_max, n1; - bf_t a1_s, *a1 = &a1_s; - - if ((radix & (radix - 1)) != 0) - radix_bits = 0; - else - radix_bits = ceil_log2(radix); - - fmt = flags & BF_FTOA_FORMAT_MASK; - bf_init(ctx, a1); - if (fmt == BF_FTOA_FORMAT_FRAC) { - if (is_dec || radix_bits != 0) { - if (bf_set(a1, a2)) - goto fail1; -#ifdef USE_BF_DEC - if (is_dec) { - if (bfdec_round((bfdec_t *)a1, prec, (flags & BF_RND_MASK) | BF_FLAG_RADPNT_PREC) & BF_ST_MEM_ERROR) - goto fail1; - n = a1->expn; - } else -#endif - { - if (bf_round(a1, prec * radix_bits, (flags & BF_RND_MASK) | BF_FLAG_RADPNT_PREC) & BF_ST_MEM_ERROR) - goto fail1; - n = ceil_div(a1->expn, radix_bits); - } - if (flags & BF_FTOA_ADD_PREFIX) { - if (radix == 16) - dbuf_putstr(s, "0x"); - else if (radix == 8) - dbuf_putstr(s, "0o"); - else if (radix == 2) - dbuf_putstr(s, "0b"); - } - if (a1->expn == BF_EXP_ZERO) { - dbuf_putstr(s, "0"); - if (prec > 0) { - dbuf_putstr(s, "."); - for(i = 0; i < prec; i++) { - dbuf_putc(s, '0'); - } - } - } else { - n_digits = prec + n; - if (n <= 0) { - /* 0.x */ - dbuf_putstr(s, "0."); - for(i = 0; i < -n; i++) { - dbuf_putc(s, '0'); - } - if (n_digits > 0) { - output_digits(s, a1, radix, n_digits, n_digits, is_dec); - } - } else { - output_digits(s, a1, radix, n_digits, n, is_dec); - } - } - } else { - size_t pos, start; - bf_t a_s, *a = &a_s; - - /* make a positive number */ - a->tab = a2->tab; - a->len = a2->len; - a->expn = a2->expn; - a->sign = 0; - - /* one more digit for the rounding */ - n = 1 + bf_mul_log2_radix(bf_max(a->expn, 0), radix, TRUE, TRUE); - n_digits = n + prec; - n1 = n; - if (bf_convert_to_radix(a1, &n1, a, radix, n_digits, - flags & BF_RND_MASK, TRUE)) - goto fail1; - start = s->size; - output_digits(s, a1, radix, n_digits, n, is_dec); - /* remove leading zeros because we allocated one more digit */ - pos = start; - while ((pos + 1) < s->size && s->bufpos == '0' && - s->bufpos + 1 != '.') - pos++; - if (pos > start) { - memmove(s->buf + start, s->buf + pos, s->size - pos); - s->size -= (pos - start); - } - } - } else { -#ifdef USE_BF_DEC - if (is_dec) { - if (bf_set(a1, a2)) - goto fail1; - if (fmt == BF_FTOA_FORMAT_FIXED) { - n_digits = prec; - n_max = n_digits; - if (bfdec_round((bfdec_t *)a1, prec, (flags & BF_RND_MASK)) & BF_ST_MEM_ERROR) - goto fail1; - } else { - /* prec is ignored */ - prec = n_digits = a1->len * LIMB_DIGITS; - /* remove the trailing zero digits */ - while (n_digits > 1 && - get_digit(a1->tab, a1->len, prec - n_digits) == 0) { - n_digits--; - } - n_max = n_digits + 4; - } - n = a1->expn; - } else -#endif - if (radix_bits != 0) { - if (bf_set(a1, a2)) - goto fail1; - if (fmt == BF_FTOA_FORMAT_FIXED) { - slimb_t prec_bits; - n_digits = prec; - n_max = n_digits; - /* align to the radix point */ - prec_bits = prec * radix_bits - - smod(-a1->expn, radix_bits); - if (bf_round(a1, prec_bits, - (flags & BF_RND_MASK)) & BF_ST_MEM_ERROR) - goto fail1; - } else { - limb_t digit_mask; - slimb_t pos; - /* position of the digit before the most - significant digit in bits */ - pos = a1->len * LIMB_BITS + - smod(-a1->expn, radix_bits); - n_digits = ceil_div(pos, radix_bits); - /* remove the trailing zero digits */ - digit_mask = ((limb_t)1 << radix_bits) - 1; - while (n_digits > 1 && - (get_bits(a1->tab, a1->len, pos - n_digits * radix_bits) & digit_mask) == 0) { - n_digits--; - } - n_max = n_digits + 4; - } - n = ceil_div(a1->expn, radix_bits); - } else { - bf_t a_s, *a = &a_s; - - /* make a positive number */ - a->tab = a2->tab; - a->len = a2->len; - a->expn = a2->expn; - a->sign = 0; - - if (fmt == BF_FTOA_FORMAT_FIXED) { - n_digits = prec; - n_max = n_digits; - } else { - slimb_t n_digits_max, n_digits_min; - - assert(prec != BF_PREC_INF); - n_digits = 1 + bf_mul_log2_radix(prec, radix, TRUE, TRUE); - /* max number of digits for non exponential - notation. The rational is to have the same rule - as JS i.e. n_max = 21 for 64 bit float in base 10. */ - n_max = n_digits + 4; - if (fmt == BF_FTOA_FORMAT_FREE_MIN) { - bf_t b_s, *b = &b_s; - - /* find the minimum number of digits by - dichotomy. */ - /* XXX: inefficient */ - n_digits_max = n_digits; - n_digits_min = 1; - bf_init(ctx, b); - while (n_digits_min < n_digits_max) { - n_digits = (n_digits_min + n_digits_max) / 2; - if (bf_convert_to_radix(a1, &n, a, radix, n_digits, - flags & BF_RND_MASK, FALSE)) { - bf_delete(b); - goto fail1; - } - /* convert back to a number and compare */ - ret = bf_mul_pow_radix(b, a1, radix, n - n_digits, - prec, - (flags & ~BF_RND_MASK) | - BF_RNDN); - if (ret & BF_ST_MEM_ERROR) { - bf_delete(b); - goto fail1; - } - if (bf_cmpu(b, a) == 0) { - n_digits_max = n_digits; - } else { - n_digits_min = n_digits + 1; - } - } - bf_delete(b); - n_digits = n_digits_max; - } - } - if (bf_convert_to_radix(a1, &n, a, radix, n_digits, - flags & BF_RND_MASK, FALSE)) { - fail1: - bf_delete(a1); - goto fail; - } - } - if (a1->expn == BF_EXP_ZERO && - fmt != BF_FTOA_FORMAT_FIXED && - !(flags & BF_FTOA_FORCE_EXP)) { - /* just output zero */ - dbuf_putstr(s, "0"); - } else { - if (flags & BF_FTOA_ADD_PREFIX) { - if (radix == 16) - dbuf_putstr(s, "0x"); - else if (radix == 8) - dbuf_putstr(s, "0o"); - else if (radix == 2) - dbuf_putstr(s, "0b"); - } - if (a1->expn == BF_EXP_ZERO) - n = 1; - if ((flags & BF_FTOA_FORCE_EXP) || - n <= -6 || n > n_max) { - const char *fmt; - /* exponential notation */ - output_digits(s, a1, radix, n_digits, 1, is_dec); - if (radix_bits != 0 && radix <= 16) { - if (flags & BF_FTOA_JS_QUIRKS) - fmt = "p%+" PRId_LIMB; - else - fmt = "p%" PRId_LIMB; - dbuf_printf(s, fmt, (n - 1) * radix_bits); - } else { - if (flags & BF_FTOA_JS_QUIRKS) - fmt = "%c%+" PRId_LIMB; - else - fmt = "%c%" PRId_LIMB; - dbuf_printf(s, fmt, - radix <= 10 ? 'e' : '@', n - 1); - } - } else if (n <= 0) { - /* 0.x */ - dbuf_putstr(s, "0."); - for(i = 0; i < -n; i++) { - dbuf_putc(s, '0'); - } - output_digits(s, a1, radix, n_digits, n_digits, is_dec); - } else { - if (n_digits <= n) { - /* no dot */ - output_digits(s, a1, radix, n_digits, n_digits, is_dec); - for(i = 0; i < (n - n_digits); i++) - dbuf_putc(s, '0'); - } else { - output_digits(s, a1, radix, n_digits, n, is_dec); - } - } - } - } - bf_delete(a1); - } - } - dbuf_putc(s, '\0'); - if (dbuf_error(s)) - goto fail; - if (plen) - *plen = s->size - 1; - return (char *)s->buf; - fail: - bf_free(ctx, s->buf); - if (plen) - *plen = 0; - return NULL; -} - -char *bf_ftoa(size_t *plen, const bf_t *a, int radix, limb_t prec, - bf_flags_t flags) -{ - return bf_ftoa_internal(plen, a, radix, prec, flags, FALSE); -} - -/***************************************************************/ -/* transcendental functions */ - -/* Note: the algorithm is from MPFR */ -static void bf_const_log2_rec(bf_t *T, bf_t *P, bf_t *Q, limb_t n1, - limb_t n2, BOOL need_P) -{ - bf_context_t *s = T->ctx; - if ((n2 - n1) == 1) { - if (n1 == 0) { - bf_set_ui(P, 3); - } else { - bf_set_ui(P, n1); - P->sign = 1; - } - bf_set_ui(Q, 2 * n1 + 1); - Q->expn += 2; - bf_set(T, P); - } else { - limb_t m; - bf_t T1_s, *T1 = &T1_s; - bf_t P1_s, *P1 = &P1_s; - bf_t Q1_s, *Q1 = &Q1_s; - - m = n1 + ((n2 - n1) >> 1); - bf_const_log2_rec(T, P, Q, n1, m, TRUE); - bf_init(s, T1); - bf_init(s, P1); - bf_init(s, Q1); - bf_const_log2_rec(T1, P1, Q1, m, n2, need_P); - bf_mul(T, T, Q1, BF_PREC_INF, BF_RNDZ); - bf_mul(T1, T1, P, BF_PREC_INF, BF_RNDZ); - bf_add(T, T, T1, BF_PREC_INF, BF_RNDZ); - if (need_P) - bf_mul(P, P, P1, BF_PREC_INF, BF_RNDZ); - bf_mul(Q, Q, Q1, BF_PREC_INF, BF_RNDZ); - bf_delete(T1); - bf_delete(P1); - bf_delete(Q1); - } -} - -/* compute log(2) with faithful rounding at precision 'prec' */ -static void bf_const_log2_internal(bf_t *T, limb_t prec) -{ - limb_t w, N; - bf_t P_s, *P = &P_s; - bf_t Q_s, *Q = &Q_s; - - w = prec + 15; - N = w / 3 + 1; - bf_init(T->ctx, P); - bf_init(T->ctx, Q); - bf_const_log2_rec(T, P, Q, 0, N, FALSE); - bf_div(T, T, Q, prec, BF_RNDN); - bf_delete(P); - bf_delete(Q); -} - -/* PI constant */ - -#define CHUD_A 13591409 -#define CHUD_B 545140134 -#define CHUD_C 640320 -#define CHUD_BITS_PER_TERM 47 - -static void chud_bs(bf_t *P, bf_t *Q, bf_t *G, int64_t a, int64_t b, int need_g, - limb_t prec) -{ - bf_context_t *s = P->ctx; - int64_t c; - - if (a == (b - 1)) { - bf_t T0, T1; - - bf_init(s, &T0); - bf_init(s, &T1); - bf_set_ui(G, 2 * b - 1); - bf_mul_ui(G, G, 6 * b - 1, prec, BF_RNDN); - bf_mul_ui(G, G, 6 * b - 5, prec, BF_RNDN); - bf_set_ui(&T0, CHUD_B); - bf_mul_ui(&T0, &T0, b, prec, BF_RNDN); - bf_set_ui(&T1, CHUD_A); - bf_add(&T0, &T0, &T1, prec, BF_RNDN); - bf_mul(P, G, &T0, prec, BF_RNDN); - P->sign = b & 1; - - bf_set_ui(Q, b); - bf_mul_ui(Q, Q, b, prec, BF_RNDN); - bf_mul_ui(Q, Q, b, prec, BF_RNDN); - bf_mul_ui(Q, Q, (uint64_t)CHUD_C * CHUD_C * CHUD_C / 24, prec, BF_RNDN); - bf_delete(&T0); - bf_delete(&T1); - } else { - bf_t P2, Q2, G2; - - bf_init(s, &P2); - bf_init(s, &Q2); - bf_init(s, &G2); - - c = (a + b) / 2; - chud_bs(P, Q, G, a, c, 1, prec); - chud_bs(&P2, &Q2, &G2, c, b, need_g, prec); - - /* Q = Q1 * Q2 */ - /* G = G1 * G2 */ - /* P = P1 * Q2 + P2 * G1 */ - bf_mul(&P2, &P2, G, prec, BF_RNDN); - if (!need_g) - bf_set_ui(G, 0); - bf_mul(P, P, &Q2, prec, BF_RNDN); - bf_add(P, P, &P2, prec, BF_RNDN); - bf_delete(&P2); - - bf_mul(Q, Q, &Q2, prec, BF_RNDN); - bf_delete(&Q2); - if (need_g) - bf_mul(G, G, &G2, prec, BF_RNDN); - bf_delete(&G2); - } -} - -/* compute Pi with faithful rounding at precision 'prec' using the - Chudnovsky formula */ -static void bf_const_pi_internal(bf_t *Q, limb_t prec) -{ - bf_context_t *s = Q->ctx; - int64_t n, prec1; - bf_t P, G; - - /* number of serie terms */ - n = prec / CHUD_BITS_PER_TERM + 1; - /* XXX: precision analysis */ - prec1 = prec + 32; - - bf_init(s, &P); - bf_init(s, &G); - - chud_bs(&P, Q, &G, 0, n, 0, BF_PREC_INF); - - bf_mul_ui(&G, Q, CHUD_A, prec1, BF_RNDN); - bf_add(&P, &G, &P, prec1, BF_RNDN); - bf_div(Q, Q, &P, prec1, BF_RNDF); - - bf_set_ui(&P, CHUD_C); - bf_sqrt(&G, &P, prec1, BF_RNDF); - bf_mul_ui(&G, &G, (uint64_t)CHUD_C / 12, prec1, BF_RNDF); - bf_mul(Q, Q, &G, prec, BF_RNDN); - bf_delete(&P); - bf_delete(&G); -} - -static int bf_const_get(bf_t *T, limb_t prec, bf_flags_t flags, - BFConstCache *c, - void (*func)(bf_t *res, limb_t prec), int sign) -{ - limb_t ziv_extra_bits, prec1; - - ziv_extra_bits = 32; - for(;;) { - prec1 = prec + ziv_extra_bits; - if (c->prec < prec1) { - if (c->val.len == 0) - bf_init(T->ctx, &c->val); - func(&c->val, prec1); - c->prec = prec1; - } else { - prec1 = c->prec; - } - bf_set(T, &c->val); - T->sign = sign; - if (!bf_can_round(T, prec, flags & BF_RND_MASK, prec1)) { - /* and more precision and retry */ - ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); - } else { - break; - } - } - return bf_round(T, prec, flags); -} - -static void bf_const_free(BFConstCache *c) -{ - bf_delete(&c->val); - memset(c, 0, sizeof(*c)); -} - -int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = T->ctx; - return bf_const_get(T, prec, flags, &s->log2_cache, bf_const_log2_internal, 0); -} - -/* return rounded pi * (1 - 2 * sign) */ -static int bf_const_pi_signed(bf_t *T, int sign, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = T->ctx; - return bf_const_get(T, prec, flags, &s->pi_cache, bf_const_pi_internal, - sign); -} - -int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags) -{ - return bf_const_pi_signed(T, 0, prec, flags); -} - -void bf_clear_cache(bf_context_t *s) -{ -#ifdef USE_FFT_MUL - fft_clear_cache(s); -#endif - bf_const_free(&s->log2_cache); - bf_const_free(&s->pi_cache); -} - -/* ZivFunc should compute the result 'r' with faithful rounding at - precision 'prec'. For efficiency purposes, the final bf_round() - does not need to be done in the function. */ -typedef int ZivFunc(bf_t *r, const bf_t *a, limb_t prec, void *opaque); - -static int bf_ziv_rounding(bf_t *r, const bf_t *a, - limb_t prec, bf_flags_t flags, - ZivFunc *f, void *opaque) -{ - int rnd_mode, ret; - slimb_t prec1, ziv_extra_bits; - - rnd_mode = flags & BF_RND_MASK; - if (rnd_mode == BF_RNDF) { - /* no need to iterate */ - f(r, a, prec, opaque); - ret = 0; - } else { - ziv_extra_bits = 32; - for(;;) { - prec1 = prec + ziv_extra_bits; - ret = f(r, a, prec1, opaque); - if (ret & (BF_ST_OVERFLOW | BF_ST_UNDERFLOW | BF_ST_MEM_ERROR)) { - /* overflow or underflow should never happen because - it indicates the rounding cannot be done correctly, - but we do not catch all the cases */ - return ret; - } - /* if the result is exact, we can stop */ - if (!(ret & BF_ST_INEXACT)) { - ret = 0; - break; - } - if (bf_can_round(r, prec, rnd_mode, prec1)) { - ret = BF_ST_INEXACT; - break; - } - ziv_extra_bits = ziv_extra_bits * 2; - // printf("ziv_extra_bits=%" PRId64 "\n", (int64_t)ziv_extra_bits); - } - } - if (r->len == 0) - return ret; - else - return __bf_round(r, prec, flags, r->len, ret); -} - -/* add (1 - 2*e_sign) * 2^e */ -static int bf_add_epsilon(bf_t *r, const bf_t *a, slimb_t e, int e_sign, - limb_t prec, int flags) -{ - bf_t T_s, *T = &T_s; - int ret; - /* small argument case: result = 1 + epsilon * sign(x) */ - bf_init(a->ctx, T); - bf_set_ui(T, 1); - T->sign = e_sign; - T->expn += e; - ret = bf_add(r, r, T, prec, flags); - bf_delete(T); - return ret; -} - -/* Compute the exponential using faithful rounding at precision 'prec'. - Note: the algorithm is from MPFR */ -static int bf_exp_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - slimb_t n, K, l, i, prec1; - - assert(r != a); - - /* argument reduction: - T = a - n*log(2) with 0 <= T < log(2) and n integer. - */ - bf_init(s, T); - if (a->expn <= -1) { - /* 0 <= abs(a) <= 0.5 */ - if (a->sign) - n = -1; - else - n = 0; - } else { - bf_const_log2(T, LIMB_BITS, BF_RNDZ); - bf_div(T, a, T, LIMB_BITS, BF_RNDD); - bf_get_limb(&n, T, 0); - } - - K = bf_isqrt((prec + 1) / 2); - l = (prec - 1) / K + 1; - /* XXX: precision analysis ? */ - prec1 = prec + (K + 2 * l + 18) + K + 8; - if (a->expn > 0) - prec1 += a->expn; - // printf("n=%ld K=%ld prec1=%ld\n", n, K, prec1); - - bf_const_log2(T, prec1, BF_RNDF); - bf_mul_si(T, T, n, prec1, BF_RNDN); - bf_sub(T, a, T, prec1, BF_RNDN); - - /* reduce the range of T */ - bf_mul_2exp(T, -K, BF_PREC_INF, BF_RNDZ); - - /* Taylor expansion around zero : - 1 + x + x^2/2 + ... + x^n/n! - = (1 + x * (1 + x/2 * (1 + ... (x/n)))) - */ - { - bf_t U_s, *U = &U_s; - - bf_init(s, U); - bf_set_ui(r, 1); - for(i = l ; i >= 1; i--) { - bf_set_ui(U, i); - bf_div(U, T, U, prec1, BF_RNDN); - bf_mul(r, r, U, prec1, BF_RNDN); - bf_add_si(r, r, 1, prec1, BF_RNDN); - } - bf_delete(U); - } - bf_delete(T); - - /* undo the range reduction */ - for(i = 0; i < K; i++) { - bf_mul(r, r, r, prec1, BF_RNDN | BF_FLAG_EXT_EXP); - } - - /* undo the argument reduction */ - bf_mul_2exp(r, n, BF_PREC_INF, BF_RNDZ | BF_FLAG_EXT_EXP); - - return BF_ST_INEXACT; -} - -/* crude overflow and underflow tests for exp(a). a_low <= a <= a_high */ -static int check_exp_underflow_overflow(bf_context_t *s, bf_t *r, - const bf_t *a_low, const bf_t *a_high, - limb_t prec, bf_flags_t flags) -{ - bf_t T_s, *T = &T_s; - bf_t log2_s, *log2 = &log2_s; - slimb_t e_min, e_max; - - if (a_high->expn <= 0) - return 0; - - e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1); - e_min = -e_max + 3; - if (flags & BF_FLAG_SUBNORMAL) - e_min -= (prec - 1); - - bf_init(s, T); - bf_init(s, log2); - bf_const_log2(log2, LIMB_BITS, BF_RNDU); - bf_mul_ui(T, log2, e_max, LIMB_BITS, BF_RNDU); - /* a_low > e_max * log(2) implies exp(a) > e_max */ - if (bf_cmp_lt(T, a_low) > 0) { - /* overflow */ - bf_delete(T); - bf_delete(log2); - return bf_set_overflow(r, 0, prec, flags); - } - /* a_high < (e_min - 2) * log(2) implies exp(a) < (e_min - 2) */ - bf_const_log2(log2, LIMB_BITS, BF_RNDD); - bf_mul_si(T, log2, e_min - 2, LIMB_BITS, BF_RNDD); - if (bf_cmp_lt(a_high, T)) { - int rnd_mode = flags & BF_RND_MASK; - - /* underflow */ - bf_delete(T); - bf_delete(log2); - if (rnd_mode == BF_RNDU) { - /* set the smallest value */ - bf_set_ui(r, 1); - r->expn = e_min; - } else { - bf_set_zero(r, 0); - } - return BF_ST_UNDERFLOW | BF_ST_INEXACT; - } - bf_delete(log2); - bf_delete(T); - return 0; -} - -int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - int ret; - assert(r != a); - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - } else if (a->expn == BF_EXP_INF) { - if (a->sign) - bf_set_zero(r, 0); - else - bf_set_inf(r, 0); - } else { - bf_set_ui(r, 1); - } - return 0; - } - - ret = check_exp_underflow_overflow(s, r, a, a, prec, flags); - if (ret) - return ret; - if (a->expn < 0 && (-a->expn) >= (prec + 2)) { - /* small argument case: result = 1 + epsilon * sign(x) */ - bf_set_ui(r, 1); - return bf_add_epsilon(r, r, -(prec + 2), a->sign, prec, flags); - } - - return bf_ziv_rounding(r, a, prec, flags, bf_exp_internal, NULL); -} - -static int bf_log_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - bf_t U_s, *U = &U_s; - bf_t V_s, *V = &V_s; - slimb_t n, prec1, l, i, K; - - assert(r != a); - - bf_init(s, T); - /* argument reduction 1 */ - /* T=a*2^n with 2/3 <= T <= 4/3 */ - { - bf_t U_s, *U = &U_s; - bf_set(T, a); - n = T->expn; - T->expn = 0; - /* U= ~ 2/3 */ - bf_init(s, U); - bf_set_ui(U, 0xaaaaaaaa); - U->expn = 0; - if (bf_cmp_lt(T, U)) { - T->expn++; - n--; - } - bf_delete(U); - } - // printf("n=%ld\n", n); - // bf_print_str("T", T); - - /* XXX: precision analysis */ - /* number of iterations for argument reduction 2 */ - K = bf_isqrt((prec + 1) / 2); - /* order of Taylor expansion */ - l = prec / (2 * K) + 1; - /* precision of the intermediate computations */ - prec1 = prec + K + 2 * l + 32; - - bf_init(s, U); - bf_init(s, V); - - /* Note: cancellation occurs here, so we use more precision (XXX: - reduce the precision by computing the exact cancellation) */ - bf_add_si(T, T, -1, BF_PREC_INF, BF_RNDN); - - /* argument reduction 2 */ - for(i = 0; i < K; i++) { - /* T = T / (1 + sqrt(1 + T)) */ - bf_add_si(U, T, 1, prec1, BF_RNDN); - bf_sqrt(V, U, prec1, BF_RNDF); - bf_add_si(U, V, 1, prec1, BF_RNDN); - bf_div(T, T, U, prec1, BF_RNDN); - } - - { - bf_t Y_s, *Y = &Y_s; - bf_t Y2_s, *Y2 = &Y2_s; - bf_init(s, Y); - bf_init(s, Y2); - - /* compute ln(1+x) = ln((1+y)/(1-y)) with y=x/(2+x) - = y + y^3/3 + ... + y^(2*l + 1) / (2*l+1) - with Y=Y^2 - = y*(1+Y/3+Y^2/5+...) = y*(1+Y*(1/3+Y*(1/5 + ...))) - */ - bf_add_si(Y, T, 2, prec1, BF_RNDN); - bf_div(Y, T, Y, prec1, BF_RNDN); - - bf_mul(Y2, Y, Y, prec1, BF_RNDN); - bf_set_ui(r, 0); - for(i = l; i >= 1; i--) { - bf_set_ui(U, 1); - bf_set_ui(V, 2 * i + 1); - bf_div(U, U, V, prec1, BF_RNDN); - bf_add(r, r, U, prec1, BF_RNDN); - bf_mul(r, r, Y2, prec1, BF_RNDN); - } - bf_add_si(r, r, 1, prec1, BF_RNDN); - bf_mul(r, r, Y, prec1, BF_RNDN); - bf_delete(Y); - bf_delete(Y2); - } - bf_delete(V); - bf_delete(U); - - /* multiplication by 2 for the Taylor expansion and undo the - argument reduction 2*/ - bf_mul_2exp(r, K + 1, BF_PREC_INF, BF_RNDZ); - - /* undo the argument reduction 1 */ - bf_const_log2(T, prec1, BF_RNDF); - bf_mul_si(T, T, n, prec1, BF_RNDN); - bf_add(r, r, T, prec1, BF_RNDN); - - bf_delete(T); - return BF_ST_INEXACT; -} - -int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - - assert(r != a); - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - if (a->sign) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set_inf(r, 0); - return 0; - } - } else { - bf_set_inf(r, 1); - return 0; - } - } - if (a->sign) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } - bf_init(s, T); - bf_set_ui(T, 1); - if (bf_cmp_eq(a, T)) { - bf_set_zero(r, 0); - bf_delete(T); - return 0; - } - bf_delete(T); - - return bf_ziv_rounding(r, a, prec, flags, bf_log_internal, NULL); -} - -/* x and y finite and x > 0 */ -static int bf_pow_generic(bf_t *r, const bf_t *x, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - const bf_t *y = opaque; - bf_t T_s, *T = &T_s; - limb_t prec1; - - bf_init(s, T); - /* XXX: proof for the added precision */ - prec1 = prec + 32; - bf_log(T, x, prec1, BF_RNDF | BF_FLAG_EXT_EXP); - bf_mul(T, T, y, prec1, BF_RNDF | BF_FLAG_EXT_EXP); - if (bf_is_nan(T)) - bf_set_nan(r); - else - bf_exp_internal(r, T, prec1, NULL); /* no overflow/underlow test needed */ - bf_delete(T); - return BF_ST_INEXACT; -} - -/* x and y finite, x > 0, y integer and y fits on one limb */ -static int bf_pow_int(bf_t *r, const bf_t *x, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - const bf_t *y = opaque; - bf_t T_s, *T = &T_s; - limb_t prec1; - int ret; - slimb_t y1; - - bf_get_limb(&y1, y, 0); - if (y1 < 0) - y1 = -y1; - /* XXX: proof for the added precision */ - prec1 = prec + ceil_log2(y1) * 2 + 8; - ret = bf_pow_ui(r, x, y1 < 0 ? -y1 : y1, prec1, BF_RNDN | BF_FLAG_EXT_EXP); - if (y->sign) { - bf_init(s, T); - bf_set_ui(T, 1); - ret |= bf_div(r, T, r, prec1, BF_RNDN | BF_FLAG_EXT_EXP); - bf_delete(T); - } - return ret; -} - -/* x must be a finite non zero float. Return TRUE if there is a - floating point number r such as x=r^(2^n) and return this floating - point number 'r'. Otherwise return FALSE and r is undefined. */ -static BOOL check_exact_power2n(bf_t *r, const bf_t *x, slimb_t n) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - slimb_t e, i, er; - limb_t v; - - /* x = m*2^e with m odd integer */ - e = bf_get_exp_min(x); - /* fast check on the exponent */ - if (n > (LIMB_BITS - 1)) { - if (e != 0) - return FALSE; - er = 0; - } else { - if ((e & (((limb_t)1 << n) - 1)) != 0) - return FALSE; - er = e >> n; - } - /* every perfect odd square = 1 modulo 8 */ - v = get_bits(x->tab, x->len, x->len * LIMB_BITS - x->expn + e); - if ((v & 7) != 1) - return FALSE; - - bf_init(s, T); - bf_set(T, x); - T->expn -= e; - for(i = 0; i < n; i++) { - if (i != 0) - bf_set(T, r); - if (bf_sqrtrem(r, NULL, T) != 0) - return FALSE; - } - r->expn += er; - return TRUE; -} - -/* prec = BF_PREC_INF is accepted for x and y integers and y >= 0 */ -int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - bf_t ytmp_s; - BOOL y_is_int, y_is_odd; - int r_sign, ret, rnd_mode; - slimb_t y_emin; - - if (x->len == 0 || y->len == 0) { - if (y->expn == BF_EXP_ZERO) { - /* pow(x, 0) = 1 */ - bf_set_ui(r, 1); - } else if (x->expn == BF_EXP_NAN) { - bf_set_nan(r); - } else { - int cmp_x_abs_1; - bf_set_ui(r, 1); - cmp_x_abs_1 = bf_cmpu(x, r); - if (cmp_x_abs_1 == 0 && (flags & BF_POW_JS_QUIRKS) && - (y->expn >= BF_EXP_INF)) { - bf_set_nan(r); - } else if (cmp_x_abs_1 == 0 && - (!x->sign || y->expn != BF_EXP_NAN)) { - /* pow(1, y) = 1 even if y = NaN */ - /* pow(-1, +/-inf) = 1 */ - } else if (y->expn == BF_EXP_NAN) { - bf_set_nan(r); - } else if (y->expn == BF_EXP_INF) { - if (y->sign == (cmp_x_abs_1 > 0)) { - bf_set_zero(r, 0); - } else { - bf_set_inf(r, 0); - } - } else { - y_emin = bf_get_exp_min(y); - y_is_odd = (y_emin == 0); - if (y->sign == (x->expn == BF_EXP_ZERO)) { - bf_set_inf(r, y_is_odd & x->sign); - if (y->sign) { - /* pow(0, y) with y < 0 */ - return BF_ST_DIVIDE_ZERO; - } - } else { - bf_set_zero(r, y_is_odd & x->sign); - } - } - } - return 0; - } - bf_init(s, T); - bf_set(T, x); - y_emin = bf_get_exp_min(y); - y_is_int = (y_emin >= 0); - rnd_mode = flags & BF_RND_MASK; - if (x->sign) { - if (!y_is_int) { - bf_set_nan(r); - bf_delete(T); - return BF_ST_INVALID_OP; - } - y_is_odd = (y_emin == 0); - r_sign = y_is_odd; - /* change the directed rounding mode if the sign of the result - is changed */ - if (r_sign && (rnd_mode == BF_RNDD || rnd_mode == BF_RNDU)) - flags ^= 1; - bf_neg(T); - } else { - r_sign = 0; - } - - bf_set_ui(r, 1); - if (bf_cmp_eq(T, r)) { - /* abs(x) = 1: nothing more to do */ - ret = 0; - } else { - /* check the overflow/underflow cases */ - { - bf_t al_s, *al = &al_s; - bf_t ah_s, *ah = &ah_s; - limb_t precl = LIMB_BITS; - - bf_init(s, al); - bf_init(s, ah); - /* compute bounds of log(abs(x)) * y with a low precision */ - /* XXX: compute bf_log() once */ - /* XXX: add a fast test before this slow test */ - bf_log(al, T, precl, BF_RNDD); - bf_log(ah, T, precl, BF_RNDU); - bf_mul(al, al, y, precl, BF_RNDD ^ y->sign); - bf_mul(ah, ah, y, precl, BF_RNDU ^ y->sign); - ret = check_exp_underflow_overflow(s, r, al, ah, prec, flags); - bf_delete(al); - bf_delete(ah); - if (ret) - goto done; - } - - if (y_is_int) { - slimb_t T_bits, e; - int_pow: - T_bits = T->expn - bf_get_exp_min(T); - if (T_bits == 1) { - /* pow(2^b, y) = 2^(b*y) */ - bf_mul_si(T, y, T->expn - 1, LIMB_BITS, BF_RNDZ); - bf_get_limb(&e, T, 0); - bf_set_ui(r, 1); - ret = bf_mul_2exp(r, e, prec, flags); - } else if (prec == BF_PREC_INF) { - slimb_t y1; - /* specific case for infinite precision (integer case) */ - bf_get_limb(&y1, y, 0); - assert(!y->sign); - /* x must be an integer, so abs(x) >= 2 */ - if (y1 >= ((slimb_t)1 << BF_EXP_BITS_MAX)) { - bf_delete(T); - return bf_set_overflow(r, 0, BF_PREC_INF, flags); - } - ret = bf_pow_ui(r, T, y1, BF_PREC_INF, BF_RNDZ); - } else { - if (y->expn <= 31) { - /* small enough power: use exponentiation in all cases */ - } else if (y->sign) { - /* cannot be exact */ - goto general_case; - } else { - if (rnd_mode == BF_RNDF) - goto general_case; /* no need to track exact results */ - /* see if the result has a chance to be exact: - if x=a*2^b (a odd), x^y=a^y*2^(b*y) - x^y needs a precision of at least floor_log2(a)*y bits - */ - bf_mul_si(r, y, T_bits - 1, LIMB_BITS, BF_RNDZ); - bf_get_limb(&e, r, 0); - if (prec < e) - goto general_case; - } - ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_int, (void *)y); - } - } else { - if (rnd_mode != BF_RNDF) { - bf_t *y1; - if (y_emin < 0 && check_exact_power2n(r, T, -y_emin)) { - /* the problem is reduced to a power to an integer */ -#if 0 - printf("\nn=%" PRId64 "\n", -(int64_t)y_emin); - bf_print_str("T", T); - bf_print_str("r", r); -#endif - bf_set(T, r); - y1 = &ytmp_s; - y1->tab = y->tab; - y1->len = y->len; - y1->sign = y->sign; - y1->expn = y->expn - y_emin; - y = y1; - goto int_pow; - } - } - general_case: - ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_generic, (void *)y); - } - } - done: - bf_delete(T); - r->sign = r_sign; - return ret; -} - -/* compute sqrt(-2*x-x^2) to get |sin(x)| from cos(x) - 1. */ -static void bf_sqrt_sin(bf_t *r, const bf_t *x, limb_t prec1) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - bf_init(s, T); - bf_set(T, x); - bf_mul(r, T, T, prec1, BF_RNDN); - bf_mul_2exp(T, 1, BF_PREC_INF, BF_RNDZ); - bf_add(T, T, r, prec1, BF_RNDN); - bf_neg(T); - bf_sqrt(r, T, prec1, BF_RNDF); - bf_delete(T); -} - -static int bf_sincos(bf_t *s, bf_t *c, const bf_t *a, limb_t prec) -{ - bf_context_t *s1 = a->ctx; - bf_t T_s, *T = &T_s; - bf_t U_s, *U = &U_s; - bf_t r_s, *r = &r_s; - slimb_t K, prec1, i, l, mod, prec2; - int is_neg; - - assert(c != a && s != a); - - bf_init(s1, T); - bf_init(s1, U); - bf_init(s1, r); - - /* XXX: precision analysis */ - K = bf_isqrt(prec / 2); - l = prec / (2 * K) + 1; - prec1 = prec + 2 * K + l + 8; - - /* after the modulo reduction, -pi/4 <= T <= pi/4 */ - if (a->expn <= -1) { - /* abs(a) <= 0.25: no modulo reduction needed */ - bf_set(T, a); - mod = 0; - } else { - slimb_t cancel; - cancel = 0; - for(;;) { - prec2 = prec1 + a->expn + cancel; - bf_const_pi(U, prec2, BF_RNDF); - bf_mul_2exp(U, -1, BF_PREC_INF, BF_RNDZ); - bf_remquo(&mod, T, a, U, prec2, BF_RNDN, BF_RNDN); - // printf("T.expn=%ld prec2=%ld\n", T->expn, prec2); - if (mod == 0 || (T->expn != BF_EXP_ZERO && - (T->expn + prec2) >= (prec1 - 1))) - break; - /* increase the number of bits until the precision is good enough */ - cancel = bf_max(-T->expn, (cancel + 1) * 3 / 2); - } - mod &= 3; - } - - is_neg = T->sign; - - /* compute cosm1(x) = cos(x) - 1 */ - bf_mul(T, T, T, prec1, BF_RNDN); - bf_mul_2exp(T, -2 * K, BF_PREC_INF, BF_RNDZ); - - /* Taylor expansion: - -x^2/2 + x^4/4! - x^6/6! + ... - */ - bf_set_ui(r, 1); - for(i = l ; i >= 1; i--) { - bf_set_ui(U, 2 * i - 1); - bf_mul_ui(U, U, 2 * i, BF_PREC_INF, BF_RNDZ); - bf_div(U, T, U, prec1, BF_RNDN); - bf_mul(r, r, U, prec1, BF_RNDN); - bf_neg(r); - if (i != 1) - bf_add_si(r, r, 1, prec1, BF_RNDN); - } - bf_delete(U); - - /* undo argument reduction: - cosm1(2*x)= 2*(2*cosm1(x)+cosm1(x)^2) - */ - for(i = 0; i < K; i++) { - bf_mul(T, r, r, prec1, BF_RNDN); - bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ); - bf_add(r, r, T, prec1, BF_RNDN); - bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ); - } - bf_delete(T); - - if (c) { - if ((mod & 1) == 0) { - bf_add_si(c, r, 1, prec1, BF_RNDN); - } else { - bf_sqrt_sin(c, r, prec1); - c->sign = is_neg ^ 1; - } - c->sign ^= mod >> 1; - } - if (s) { - if ((mod & 1) == 0) { - bf_sqrt_sin(s, r, prec1); - s->sign = is_neg; - } else { - bf_add_si(s, r, 1, prec1, BF_RNDN); - } - s->sign ^= mod >> 1; - } - bf_delete(r); - return BF_ST_INEXACT; -} - -static int bf_cos_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) -{ - return bf_sincos(NULL, r, a, prec); -} - -int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set_ui(r, 1); - return 0; - } - } - - /* small argument case: result = 1+r(x) with r(x) = -x^2/2 + - O(X^4). We assume r(x) < 2^(2*EXP(x) - 1). */ - if (a->expn < 0) { - slimb_t e; - e = 2 * a->expn - 1; - if (e < -(prec + 2)) { - bf_set_ui(r, 1); - return bf_add_epsilon(r, r, e, 1, prec, flags); - } - } - - return bf_ziv_rounding(r, a, prec, flags, bf_cos_internal, NULL); -} - -static int bf_sin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) -{ - return bf_sincos(r, NULL, a, prec); -} - -int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set_zero(r, a->sign); - return 0; - } - } - - /* small argument case: result = x+r(x) with r(x) = -x^3/6 + - O(X^5). We assume r(x) < 2^(3*EXP(x) - 2). */ - if (a->expn < 0) { - slimb_t e; - e = sat_add(2 * a->expn, a->expn - 2); - if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { - bf_set(r, a); - return bf_add_epsilon(r, r, e, 1 - a->sign, prec, flags); - } - } - - return bf_ziv_rounding(r, a, prec, flags, bf_sin_internal, NULL); -} - -static int bf_tan_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - limb_t prec1; - - /* XXX: precision analysis */ - prec1 = prec + 8; - bf_init(s, T); - bf_sincos(r, T, a, prec1); - bf_div(r, r, T, prec1, BF_RNDF); - bf_delete(T); - return BF_ST_INEXACT; -} - -int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - assert(r != a); - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set_zero(r, a->sign); - return 0; - } - } - - /* small argument case: result = x+r(x) with r(x) = x^3/3 + - O(X^5). We assume r(x) < 2^(3*EXP(x) - 1). */ - if (a->expn < 0) { - slimb_t e; - e = sat_add(2 * a->expn, a->expn - 1); - if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { - bf_set(r, a); - return bf_add_epsilon(r, r, e, a->sign, prec, flags); - } - } - - return bf_ziv_rounding(r, a, prec, flags, bf_tan_internal, NULL); -} - -/* if add_pi2 is true, add pi/2 to the result (used for acos(x) to - avoid cancellation) */ -static int bf_atan_internal(bf_t *r, const bf_t *a, limb_t prec, - void *opaque) -{ - bf_context_t *s = r->ctx; - BOOL add_pi2 = (BOOL)(intptr_t)opaque; - bf_t T_s, *T = &T_s; - bf_t U_s, *U = &U_s; - bf_t V_s, *V = &V_s; - bf_t X2_s, *X2 = &X2_s; - int cmp_1; - slimb_t prec1, i, K, l; - - /* XXX: precision analysis */ - K = bf_isqrt((prec + 1) / 2); - l = prec / (2 * K) + 1; - prec1 = prec + K + 2 * l + 32; - // printf("prec=%d K=%d l=%d prec1=%d\n", (int)prec, (int)K, (int)l, (int)prec1); - - bf_init(s, T); - cmp_1 = (a->expn >= 1); /* a >= 1 */ - if (cmp_1) { - bf_set_ui(T, 1); - bf_div(T, T, a, prec1, BF_RNDN); - } else { - bf_set(T, a); - } - - /* abs(T) <= 1 */ - - /* argument reduction */ - - bf_init(s, U); - bf_init(s, V); - bf_init(s, X2); - for(i = 0; i < K; i++) { - /* T = T / (1 + sqrt(1 + T^2)) */ - bf_mul(U, T, T, prec1, BF_RNDN); - bf_add_si(U, U, 1, prec1, BF_RNDN); - bf_sqrt(V, U, prec1, BF_RNDN); - bf_add_si(V, V, 1, prec1, BF_RNDN); - bf_div(T, T, V, prec1, BF_RNDN); - } - - /* Taylor series: - x - x^3/3 + ... + (-1)^ l * y^(2*l + 1) / (2*l+1) - */ - bf_mul(X2, T, T, prec1, BF_RNDN); - bf_set_ui(r, 0); - for(i = l; i >= 1; i--) { - bf_set_si(U, 1); - bf_set_ui(V, 2 * i + 1); - bf_div(U, U, V, prec1, BF_RNDN); - bf_neg(r); - bf_add(r, r, U, prec1, BF_RNDN); - bf_mul(r, r, X2, prec1, BF_RNDN); - } - bf_neg(r); - bf_add_si(r, r, 1, prec1, BF_RNDN); - bf_mul(r, r, T, prec1, BF_RNDN); - - /* undo the argument reduction */ - bf_mul_2exp(r, K, BF_PREC_INF, BF_RNDZ); - - bf_delete(U); - bf_delete(V); - bf_delete(X2); - - i = add_pi2; - if (cmp_1 > 0) { - /* undo the inversion : r = sign(a)*PI/2 - r */ - bf_neg(r); - i += 1 - 2 * a->sign; - } - /* add i*(pi/2) with -1 <= i <= 2 */ - if (i != 0) { - bf_const_pi(T, prec1, BF_RNDF); - if (i != 2) - bf_mul_2exp(T, -1, BF_PREC_INF, BF_RNDZ); - T->sign = (i < 0); - bf_add(r, T, r, prec1, BF_RNDN); - } - - bf_delete(T); - return BF_ST_INEXACT; -} - -int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - int res; - - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - /* -PI/2 or PI/2 */ - bf_const_pi_signed(r, a->sign, prec, flags); - bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ); - return BF_ST_INEXACT; - } else { - bf_set_zero(r, a->sign); - return 0; - } - } - - bf_init(s, T); - bf_set_ui(T, 1); - res = bf_cmpu(a, T); - bf_delete(T); - if (res == 0) { - /* short cut: abs(a) == 1 -> +/-pi/4 */ - bf_const_pi_signed(r, a->sign, prec, flags); - bf_mul_2exp(r, -2, BF_PREC_INF, BF_RNDZ); - return BF_ST_INEXACT; - } - - /* small argument case: result = x+r(x) with r(x) = -x^3/3 + - O(X^5). We assume r(x) < 2^(3*EXP(x) - 1). */ - if (a->expn < 0) { - slimb_t e; - e = sat_add(2 * a->expn, a->expn - 1); - if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { - bf_set(r, a); - return bf_add_epsilon(r, r, e, 1 - a->sign, prec, flags); - } - } - - return bf_ziv_rounding(r, a, prec, flags, bf_atan_internal, (void *)FALSE); -} - -static int bf_atan2_internal(bf_t *r, const bf_t *y, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - const bf_t *x = opaque; - bf_t T_s, *T = &T_s; - limb_t prec1; - int ret; - - if (y->expn == BF_EXP_NAN || x->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } - - /* compute atan(y/x) assumming inf/inf = 1 and 0/0 = 0 */ - bf_init(s, T); - prec1 = prec + 32; - if (y->expn == BF_EXP_INF && x->expn == BF_EXP_INF) { - bf_set_ui(T, 1); - T->sign = y->sign ^ x->sign; - } else if (y->expn == BF_EXP_ZERO && x->expn == BF_EXP_ZERO) { - bf_set_zero(T, y->sign ^ x->sign); - } else { - bf_div(T, y, x, prec1, BF_RNDF); - } - ret = bf_atan(r, T, prec1, BF_RNDF); - - if (x->sign) { - /* if x < 0 (it includes -0), return sign(y)*pi + atan(y/x) */ - bf_const_pi(T, prec1, BF_RNDF); - T->sign = y->sign; - bf_add(r, r, T, prec1, BF_RNDN); - ret |= BF_ST_INEXACT; - } - - bf_delete(T); - return ret; -} - -int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, - limb_t prec, bf_flags_t flags) -{ - return bf_ziv_rounding(r, y, prec, flags, bf_atan2_internal, (void *)x); -} - -static int bf_asin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) -{ - bf_context_t *s = r->ctx; - BOOL is_acos = (BOOL)(intptr_t)opaque; - bf_t T_s, *T = &T_s; - limb_t prec1, prec2; - - /* asin(x) = atan(x/sqrt(1-x^2)) - acos(x) = pi/2 - asin(x) */ - prec1 = prec + 8; - /* increase the precision in x^2 to compensate the cancellation in - (1-x^2) if x is close to 1 */ - /* XXX: use less precision when possible */ - if (a->expn >= 0) - prec2 = BF_PREC_INF; - else - prec2 = prec1; - bf_init(s, T); - bf_mul(T, a, a, prec2, BF_RNDN); - bf_neg(T); - bf_add_si(T, T, 1, prec2, BF_RNDN); - - bf_sqrt(r, T, prec1, BF_RNDN); - bf_div(T, a, r, prec1, BF_RNDN); - if (is_acos) - bf_neg(T); - bf_atan_internal(r, T, prec1, (void *)(intptr_t)is_acos); - bf_delete(T); - return BF_ST_INEXACT; -} - -int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - int res; - - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_set_zero(r, a->sign); - return 0; - } - } - bf_init(s, T); - bf_set_ui(T, 1); - res = bf_cmpu(a, T); - bf_delete(T); - if (res > 0) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } - - /* small argument case: result = x+r(x) with r(x) = x^3/6 + - O(X^5). We assume r(x) < 2^(3*EXP(x) - 2). */ - if (a->expn < 0) { - slimb_t e; - e = sat_add(2 * a->expn, a->expn - 2); - if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { - bf_set(r, a); - return bf_add_epsilon(r, r, e, a->sign, prec, flags); - } - } - - return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)FALSE); -} - -int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = r->ctx; - bf_t T_s, *T = &T_s; - int res; - - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bf_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bf_const_pi(r, prec, flags); - bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ); - return BF_ST_INEXACT; - } - } - bf_init(s, T); - bf_set_ui(T, 1); - res = bf_cmpu(a, T); - bf_delete(T); - if (res > 0) { - bf_set_nan(r); - return BF_ST_INVALID_OP; - } else if (res == 0 && a->sign == 0) { - bf_set_zero(r, 0); - return 0; - } - - return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)TRUE); -} - -/***************************************************************/ -/* decimal floating point numbers */ - -#ifdef USE_BF_DEC - -#define adddq(r1, r0, a1, a0) \ - do { \ - limb_t __t = r0; \ - r0 += (a0); \ - r1 += (a1) + (r0 < __t); \ - } while (0) - -#define subdq(r1, r0, a1, a0) \ - do { \ - limb_t __t = r0; \ - r0 -= (a0); \ - r1 -= (a1) + (r0 > __t); \ - } while (0) - -#if LIMB_BITS == 64 - -/* Note: we assume __int128 is available */ -#define muldq(r1, r0, a, b) \ - do { \ - unsigned __int128 __t; \ - __t = (unsigned __int128)(a) * (unsigned __int128)(b); \ - r0 = __t; \ - r1 = __t >> 64; \ - } while (0) - -#define divdq(q, r, a1, a0, b) \ - do { \ - unsigned __int128 __t; \ - limb_t __b = (b); \ - __t = ((unsigned __int128)(a1) << 64) | (a0); \ - q = __t / __b; \ - r = __t % __b; \ - } while (0) - -#else - -#define muldq(r1, r0, a, b) \ - do { \ - uint64_t __t; \ - __t = (uint64_t)(a) * (uint64_t)(b); \ - r0 = __t; \ - r1 = __t >> 32; \ - } while (0) - -#define divdq(q, r, a1, a0, b) \ - do { \ - uint64_t __t; \ - limb_t __b = (b); \ - __t = ((uint64_t)(a1) << 32) | (a0); \ - q = __t / __b; \ - r = __t % __b; \ - } while (0) - -#endif /* LIMB_BITS != 64 */ - -static inline __maybe_unused limb_t shrd(limb_t low, limb_t high, long shift) -{ - if (shift != 0) - low = (low >> shift) | (high << (LIMB_BITS - shift)); - return low; -} - -static inline __maybe_unused limb_t shld(limb_t a1, limb_t a0, long shift) -{ - if (shift != 0) - return (a1 << shift) | (a0 >> (LIMB_BITS - shift)); - else - return a1; -} - -#if LIMB_DIGITS == 19 - -/* WARNING: hardcoded for b = 1e19. It is assumed that: - 0 <= a1 < 2^63 */ -#define divdq_base(q, r, a1, a0)\ -do {\ - uint64_t __a0, __a1, __t0, __t1, __b = BF_DEC_BASE; \ - __a0 = a0;\ - __a1 = a1;\ - __t0 = __a1;\ - __t0 = shld(__t0, __a0, 1);\ - muldq(q, __t1, __t0, UINT64_C(17014118346046923173)); \ - muldq(__t1, __t0, q, __b);\ - subdq(__a1, __a0, __t1, __t0);\ - subdq(__a1, __a0, 1, __b * 2); \ - __t0 = (slimb_t)__a1 >> 1; \ - q += 2 + __t0;\ - adddq(__a1, __a0, 0, __b & __t0);\ - q += __a1; \ - __a0 += __b & __a1; \ - r = __a0;\ -} while(0) - -#elif LIMB_DIGITS == 9 - -/* WARNING: hardcoded for b = 1e9. It is assumed that: - 0 <= a1 < 2^29 */ -#define divdq_base(q, r, a1, a0)\ -do {\ - uint32_t __t0, __t1, __b = BF_DEC_BASE; \ - __t0 = a1;\ - __t1 = a0;\ - __t0 = (__t0 << 3) | (__t1 >> (32 - 3)); \ - muldq(q, __t1, __t0, 2305843009U);\ - r = a0 - q * __b;\ - __t1 = (r >= __b);\ - q += __t1;\ - if (__t1)\ - r -= __b;\ -} while(0) - -#endif - -/* fast integer division by a fixed constant */ - -typedef struct FastDivData { - limb_t m1; /* multiplier */ - int8_t shift1; - int8_t shift2; -} FastDivData; - -/* From "Division by Invariant Integers using Multiplication" by - Torborn Granlund and Peter L. Montgomery */ -/* d must be != 0 */ -static inline __maybe_unused void fast_udiv_init(FastDivData *s, limb_t d) -{ - int l; - limb_t q, r, m1; - if (d == 1) - l = 0; - else - l = 64 - clz64(d - 1); - divdq(q, r, ((limb_t)1 << l) - d, 0, d); - (void)r; - m1 = q + 1; - // printf("d=%lu l=%d m1=0x%016lx\n", d, l, m1); - s->m1 = m1; - s->shift1 = l; - if (s->shift1 > 1) - s->shift1 = 1; - s->shift2 = l - 1; - if (s->shift2 < 0) - s->shift2 = 0; -} - -static inline limb_t fast_udiv(limb_t a, const FastDivData *s) -{ - limb_t t0, t1; - muldq(t1, t0, s->m1, a); - t0 = (a - t1) >> s->shift1; - return (t1 + t0) >> s->shift2; -} - -/* contains 10^i */ -const limb_t mp_pow_decLIMB_DIGITS + 1 = { - 1U, - 10U, - 100U, - 1000U, - 10000U, - 100000U, - 1000000U, - 10000000U, - 100000000U, - 1000000000U, -#if LIMB_BITS == 64 - 10000000000U, - 100000000000U, - 1000000000000U, - 10000000000000U, - 100000000000000U, - 1000000000000000U, - 10000000000000000U, - 100000000000000000U, - 1000000000000000000U, - 10000000000000000000U, -#endif -}; - -/* precomputed from fast_udiv_init(10^i) */ -static const FastDivData mp_pow_divLIMB_DIGITS + 1 = { -#if LIMB_BITS == 32 - { 0x00000001, 0, 0 }, - { 0x9999999a, 1, 3 }, - { 0x47ae147b, 1, 6 }, - { 0x0624dd30, 1, 9 }, - { 0xa36e2eb2, 1, 13 }, - { 0x4f8b588f, 1, 16 }, - { 0x0c6f7a0c, 1, 19 }, - { 0xad7f29ac, 1, 23 }, - { 0x5798ee24, 1, 26 }, - { 0x12e0be83, 1, 29 }, -#else - { 0x0000000000000001, 0, 0 }, - { 0x999999999999999a, 1, 3 }, - { 0x47ae147ae147ae15, 1, 6 }, - { 0x0624dd2f1a9fbe77, 1, 9 }, - { 0xa36e2eb1c432ca58, 1, 13 }, - { 0x4f8b588e368f0847, 1, 16 }, - { 0x0c6f7a0b5ed8d36c, 1, 19 }, - { 0xad7f29abcaf48579, 1, 23 }, - { 0x5798ee2308c39dfa, 1, 26 }, - { 0x12e0be826d694b2f, 1, 29 }, - { 0xb7cdfd9d7bdbab7e, 1, 33 }, - { 0x5fd7fe17964955fe, 1, 36 }, - { 0x19799812dea11198, 1, 39 }, - { 0xc25c268497681c27, 1, 43 }, - { 0x6849b86a12b9b01f, 1, 46 }, - { 0x203af9ee756159b3, 1, 49 }, - { 0xcd2b297d889bc2b7, 1, 53 }, - { 0x70ef54646d496893, 1, 56 }, - { 0x2725dd1d243aba0f, 1, 59 }, - { 0xd83c94fb6d2ac34d, 1, 63 }, -#endif -}; - -/* divide by 10^shift with 0 <= shift <= LIMB_DIGITS */ -static inline limb_t fast_shr_dec(limb_t a, int shift) -{ - return fast_udiv(a, &mp_pow_divshift); -} - -/* division and remainder by 10^shift */ -#define fast_shr_rem_dec(q, r, a, shift) q = fast_shr_dec(a, shift), r = a - q * mp_pow_decshift - -limb_t mp_add_dec(limb_t *res, const limb_t *op1, const limb_t *op2, - mp_size_t n, limb_t carry) -{ - limb_t base = BF_DEC_BASE; - mp_size_t i; - limb_t k, a, v; - - k=carry; - for(i=0;i<n;i++) { - /* XXX: reuse the trick in add_mod */ - v = op1i; - a = v + op2i + k - base; - k = a <= v; - if (!k) - a += base; - resi=a; - } - return k; -} - -limb_t mp_add_ui_dec(limb_t *tab, limb_t b, mp_size_t n) -{ - limb_t base = BF_DEC_BASE; - mp_size_t i; - limb_t k, a, v; - - k=b; - for(i=0;i<n;i++) { - v = tabi; - a = v + k - base; - k = a <= v; - if (!k) - a += base; - tabi = a; - if (k == 0) - break; - } - return k; -} - -limb_t mp_sub_dec(limb_t *res, const limb_t *op1, const limb_t *op2, - mp_size_t n, limb_t carry) -{ - limb_t base = BF_DEC_BASE; - mp_size_t i; - limb_t k, v, a; - - k=carry; - for(i=0;i<n;i++) { - v = op1i; - a = v - op2i - k; - k = a > v; - if (k) - a += base; - resi = a; - } - return k; -} - -limb_t mp_sub_ui_dec(limb_t *tab, limb_t b, mp_size_t n) -{ - limb_t base = BF_DEC_BASE; - mp_size_t i; - limb_t k, v, a; - - k=b; - for(i=0;i<n;i++) { - v = tabi; - a = v - k; - k = a > v; - if (k) - a += base; - tabi=a; - if (k == 0) - break; - } - return k; -} - -/* taba = taba * b + l. 0 <= b, l <= base - 1. Return the high carry */ -limb_t mp_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, - limb_t b, limb_t l) -{ - mp_size_t i; - limb_t t0, t1, r; - - for(i = 0; i < n; i++) { - muldq(t1, t0, tabai, b); - adddq(t1, t0, 0, l); - divdq_base(l, r, t1, t0); - tabri = r; - } - return l; -} - -/* tabr += taba * b. 0 <= b <= base - 1. Return the value to add - to the high word */ -limb_t mp_add_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, - limb_t b) -{ - mp_size_t i; - limb_t l, t0, t1, r; - - l = 0; - for(i = 0; i < n; i++) { - muldq(t1, t0, tabai, b); - adddq(t1, t0, 0, l); - adddq(t1, t0, 0, tabri); - divdq_base(l, r, t1, t0); - tabri = r; - } - return l; -} - -/* tabr -= taba * b. 0 <= b <= base - 1. Return the value to - substract to the high word. */ -limb_t mp_sub_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, - limb_t b) -{ - limb_t base = BF_DEC_BASE; - mp_size_t i; - limb_t l, t0, t1, r, a, v, c; - - /* XXX: optimize */ - l = 0; - for(i = 0; i < n; i++) { - muldq(t1, t0, tabai, b); - adddq(t1, t0, 0, l); - divdq_base(l, r, t1, t0); - v = tabri; - a = v - r; - c = a > v; - if (c) - a += base; - /* never bigger than base because r = 0 when l = base - 1 */ - l += c; - tabri = a; - } - return l; -} - -/* size of the result : op1_size + op2_size. */ -void mp_mul_basecase_dec(limb_t *result, - const limb_t *op1, mp_size_t op1_size, - const limb_t *op2, mp_size_t op2_size) -{ - mp_size_t i; - limb_t r; - - resultop1_size = mp_mul1_dec(result, op1, op1_size, op20, 0); - - for(i=1;i<op2_size;i++) { - r = mp_add_mul1_dec(result + i, op1, op1_size, op2i); - resulti + op1_size = r; - } -} - -/* taba = (taba + r*base^na) / b. 0 <= b < base. 0 <= r < - b. Return the remainder. */ -limb_t mp_div1_dec(limb_t *tabr, const limb_t *taba, mp_size_t na, - limb_t b, limb_t r) -{ - limb_t base = BF_DEC_BASE; - mp_size_t i; - limb_t t0, t1, q; - int shift; - -#if (BF_DEC_BASE % 2) == 0 - if (b == 2) { - limb_t base_div2; - /* Note: only works if base is even */ - base_div2 = base >> 1; - if (r) - r = base_div2; - for(i = na - 1; i >= 0; i--) { - t0 = tabai; - tabri = (t0 >> 1) + r; - r = 0; - if (t0 & 1) - r = base_div2; - } - if (r) - r = 1; - } else -#endif - if (na >= UDIV1NORM_THRESHOLD) { - shift = clz(b); - if (shift == 0) { - /* normalized case: b >= 2^(LIMB_BITS-1) */ - limb_t b_inv; - b_inv = udiv1norm_init(b); - for(i = na - 1; i >= 0; i--) { - muldq(t1, t0, r, base); - adddq(t1, t0, 0, tabai); - q = udiv1norm(&r, t1, t0, b, b_inv); - tabri = q; - } - } else { - limb_t b_inv; - b <<= shift; - b_inv = udiv1norm_init(b); - for(i = na - 1; i >= 0; i--) { - muldq(t1, t0, r, base); - adddq(t1, t0, 0, tabai); - t1 = (t1 << shift) | (t0 >> (LIMB_BITS - shift)); - t0 <<= shift; - q = udiv1norm(&r, t1, t0, b, b_inv); - r >>= shift; - tabri = q; - } - } - } else { - for(i = na - 1; i >= 0; i--) { - muldq(t1, t0, r, base); - adddq(t1, t0, 0, tabai); - divdq(q, r, t1, t0, b); - tabri = q; - } - } - return r; -} - -static __maybe_unused void mp_print_str_dec(const char *str, - const limb_t *tab, slimb_t n) -{ - slimb_t i; - printf("%s=", str); - for(i = n - 1; i >= 0; i--) { - if (i != n - 1) - printf("_"); - printf("%0*" PRIu_LIMB, LIMB_DIGITS, tabi); - } - printf("\n"); -} - -static __maybe_unused void mp_print_str_h_dec(const char *str, - const limb_t *tab, slimb_t n, - limb_t high) -{ - slimb_t i; - printf("%s=", str); - printf("%0*" PRIu_LIMB, LIMB_DIGITS, high); - for(i = n - 1; i >= 0; i--) { - printf("_"); - printf("%0*" PRIu_LIMB, LIMB_DIGITS, tabi); - } - printf("\n"); -} - -//#define DEBUG_DIV_SLOW - -#define DIV_STATIC_ALLOC_LEN 16 - -/* return q = a / b and r = a % b. - - tabana must be allocated if tabb1nb - 1 < B / 2. tabb1nb - 1 - must be != zero. na must be >= nb. 's' can be NULL if tabb1nb - 1 - >= B / 2. - - The remainder is returned in taba and contains nb libms. tabq - contains na - nb + 1 limbs. No overlap is permitted. - - Running time of the standard method: (na - nb + 1) * nb - Return 0 if OK, -1 if memory alloc error -*/ -/* XXX: optimize */ -static int mp_div_dec(bf_context_t *s, limb_t *tabq, - limb_t *taba, mp_size_t na, - const limb_t *tabb1, mp_size_t nb) -{ - limb_t base = BF_DEC_BASE; - limb_t r, mult, t0, t1, a, c, q, v, *tabb; - mp_size_t i, j; - limb_t static_tabbDIV_STATIC_ALLOC_LEN; - -#ifdef DEBUG_DIV_SLOW - mp_print_str_dec("a", taba, na); - mp_print_str_dec("b", tabb1, nb); -#endif - - /* normalize tabb */ - r = tabb1nb - 1; - assert(r != 0); - i = na - nb; - if (r >= BF_DEC_BASE / 2) { - mult = 1; - tabb = (limb_t *)tabb1; - q = 1; - for(j = nb - 1; j >= 0; j--) { - if (tabai + j != tabbj) { - if (tabai + j < tabbj) - q = 0; - break; - } - } - tabqi = q; - if (q) { - mp_sub_dec(taba + i, taba + i, tabb, nb, 0); - } - i--; - } else { - mult = base / (r + 1); - if (likely(nb <= DIV_STATIC_ALLOC_LEN)) { - tabb = static_tabb; - } else { - tabb = bf_malloc(s, sizeof(limb_t) * nb); - if (!tabb) - return -1; - } - mp_mul1_dec(tabb, tabb1, nb, mult, 0); - tabana = mp_mul1_dec(taba, taba, na, mult, 0); - } - -#ifdef DEBUG_DIV_SLOW - printf("mult=" FMT_LIMB "\n", mult); - mp_print_str_dec("a_norm", taba, na + 1); - mp_print_str_dec("b_norm", tabb, nb); -#endif - - for(; i >= 0; i--) { - if (unlikely(tabai + nb >= tabbnb - 1)) { - /* XXX: check if it is really possible */ - q = base - 1; - } else { - muldq(t1, t0, tabai + nb, base); - adddq(t1, t0, 0, tabai + nb - 1); - divdq(q, r, t1, t0, tabbnb - 1); - } - // printf("i=%d q1=%ld\n", i, q); - - r = mp_sub_mul1_dec(taba + i, tabb, nb, q); - // mp_dump("r1", taba + i, nb, bd); - // printf("r2=%ld\n", r); - - v = tabai + nb; - a = v - r; - c = a > v; - if (c) - a += base; - tabai + nb = a; - - if (c != 0) { - /* negative result */ - for(;;) { - q--; - c = mp_add_dec(taba + i, taba + i, tabb, nb, 0); - /* propagate carry and test if positive result */ - if (c != 0) { - if (++tabai + nb == base) { - break; - } - } - } - } - tabqi = q; - } - -#ifdef DEBUG_DIV_SLOW - mp_print_str_dec("q", tabq, na - nb + 1); - mp_print_str_dec("r", taba, nb); -#endif - - /* remove the normalization */ - if (mult != 1) { - mp_div1_dec(taba, taba, nb, mult, 0); - if (unlikely(tabb != static_tabb)) - bf_free(s, tabb); - } - return 0; -} - -/* divide by 10^shift */ -static limb_t mp_shr_dec(limb_t *tab_r, const limb_t *tab, mp_size_t n, - limb_t shift, limb_t high) -{ - mp_size_t i; - limb_t l, a, q, r; - - assert(shift >= 1 && shift < LIMB_DIGITS); - l = high; - for(i = n - 1; i >= 0; i--) { - a = tabi; - fast_shr_rem_dec(q, r, a, shift); - tab_ri = q + l * mp_pow_decLIMB_DIGITS - shift; - l = r; - } - return l; -} - -/* multiply by 10^shift */ -static limb_t mp_shl_dec(limb_t *tab_r, const limb_t *tab, mp_size_t n, - limb_t shift, limb_t low) -{ - mp_size_t i; - limb_t l, a, q, r; - - assert(shift >= 1 && shift < LIMB_DIGITS); - l = low; - for(i = 0; i < n; i++) { - a = tabi; - fast_shr_rem_dec(q, r, a, LIMB_DIGITS - shift); - tab_ri = r * mp_pow_decshift + l; - l = q; - } - return l; -} - -static limb_t mp_sqrtrem2_dec(limb_t *tabs, limb_t *taba) -{ - int k; - dlimb_t a, b, r; - limb_t taba12, s, r0, r1; - - /* convert to binary and normalize */ - a = (dlimb_t)taba1 * BF_DEC_BASE + taba0; - k = clz(a >> LIMB_BITS) & ~1; - b = a << k; - taba10 = b; - taba11 = b >> LIMB_BITS; - mp_sqrtrem2(&s, taba1); - s >>= (k >> 1); - /* convert the remainder back to decimal */ - r = a - (dlimb_t)s * (dlimb_t)s; - divdq_base(r1, r0, r >> LIMB_BITS, r); - taba0 = r0; - tabs0 = s; - return r1; -} - -//#define DEBUG_SQRTREM_DEC - -/* tmp_buf must contain (n / 2 + 1 limbs) */ -static limb_t mp_sqrtrem_rec_dec(limb_t *tabs, limb_t *taba, limb_t n, - limb_t *tmp_buf) -{ - limb_t l, h, rh, ql, qh, c, i; - - if (n == 1) - return mp_sqrtrem2_dec(tabs, taba); -#ifdef DEBUG_SQRTREM_DEC - mp_print_str_dec("a", taba, 2 * n); -#endif - l = n / 2; - h = n - l; - qh = mp_sqrtrem_rec_dec(tabs + l, taba + 2 * l, h, tmp_buf); -#ifdef DEBUG_SQRTREM_DEC - mp_print_str_dec("s1", tabs + l, h); - mp_print_str_h_dec("r1", taba + 2 * l, h, qh); - mp_print_str_h_dec("r2", taba + l, n, qh); -#endif - - /* the remainder is in taba + 2 * l. Its high bit is in qh */ - if (qh) { - mp_sub_dec(taba + 2 * l, taba + 2 * l, tabs + l, h, 0); - } - /* instead of dividing by 2*s, divide by s (which is normalized) - and update q and r */ - mp_div_dec(NULL, tmp_buf, taba + l, n, tabs + l, h); - qh += tmp_bufl; - for(i = 0; i < l; i++) - tabsi = tmp_bufi; - ql = mp_div1_dec(tabs, tabs, l, 2, qh & 1); - qh = qh >> 1; /* 0 or 1 */ - if (ql) - rh = mp_add_dec(taba + l, taba + l, tabs + l, h, 0); - else - rh = 0; -#ifdef DEBUG_SQRTREM_DEC - mp_print_str_h_dec("q", tabs, l, qh); - mp_print_str_h_dec("u", taba + l, h, rh); -#endif - - mp_add_ui_dec(tabs + l, qh, h); -#ifdef DEBUG_SQRTREM_DEC - mp_print_str_dec("s2", tabs, n); -#endif - - /* q = qh, tabsl - 1 ... 0, r = taban - 1 ... l */ - /* subtract q^2. if qh = 1 then q = B^l, so we can take shortcuts */ - if (qh) { - c = qh; - } else { - mp_mul_basecase_dec(taba + n, tabs, l, tabs, l); - c = mp_sub_dec(taba, taba, taba + n, 2 * l, 0); - } - rh -= mp_sub_ui_dec(taba + 2 * l, c, n - 2 * l); - if ((slimb_t)rh < 0) { - mp_sub_ui_dec(tabs, 1, n); - rh += mp_add_mul1_dec(taba, tabs, n, 2); - rh += mp_add_ui_dec(taba, 1, n); - } - return rh; -} - -/* 'taba' has 2*n limbs with n >= 1 and taba2*n-1 >= B/4. Return (s, - r) with s=floor(sqrt(a)) and r=a-s^2. 0 <= r <= 2 * s. tabs has n - limbs. r is returned in the lower n limbs of taba. Its rn is the - returned value of the function. */ -int mp_sqrtrem_dec(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n) -{ - limb_t tmp_buf18; - limb_t *tmp_buf; - mp_size_t n2; - n2 = n / 2 + 1; - if (n2 <= countof(tmp_buf1)) { - tmp_buf = tmp_buf1; - } else { - tmp_buf = bf_malloc(s, sizeof(limb_t) * n2); - if (!tmp_buf) - return -1; - } - taban = mp_sqrtrem_rec_dec(tabs, taba, n, tmp_buf); - if (tmp_buf != tmp_buf1) - bf_free(s, tmp_buf); - return 0; -} - -/* return the number of leading zero digits, from 0 to LIMB_DIGITS */ -static int clz_dec(limb_t a) -{ - if (a == 0) - return LIMB_DIGITS; - switch(LIMB_BITS - 1 - clz(a)) { - case 0: /* 1-1 */ - return LIMB_DIGITS - 1; - case 1: /* 2-3 */ - return LIMB_DIGITS - 1; - case 2: /* 4-7 */ - return LIMB_DIGITS - 1; - case 3: /* 8-15 */ - if (a < 10) - return LIMB_DIGITS - 1; - else - return LIMB_DIGITS - 2; - case 4: /* 16-31 */ - return LIMB_DIGITS - 2; - case 5: /* 32-63 */ - return LIMB_DIGITS - 2; - case 6: /* 64-127 */ - if (a < 100) - return LIMB_DIGITS - 2; - else - return LIMB_DIGITS - 3; - case 7: /* 128-255 */ - return LIMB_DIGITS - 3; - case 8: /* 256-511 */ - return LIMB_DIGITS - 3; - case 9: /* 512-1023 */ - if (a < 1000) - return LIMB_DIGITS - 3; - else - return LIMB_DIGITS - 4; - case 10: /* 1024-2047 */ - return LIMB_DIGITS - 4; - case 11: /* 2048-4095 */ - return LIMB_DIGITS - 4; - case 12: /* 4096-8191 */ - return LIMB_DIGITS - 4; - case 13: /* 8192-16383 */ - if (a < 10000) - return LIMB_DIGITS - 4; - else - return LIMB_DIGITS - 5; - case 14: /* 16384-32767 */ - return LIMB_DIGITS - 5; - case 15: /* 32768-65535 */ - return LIMB_DIGITS - 5; - case 16: /* 65536-131071 */ - if (a < 100000) - return LIMB_DIGITS - 5; - else - return LIMB_DIGITS - 6; - case 17: /* 131072-262143 */ - return LIMB_DIGITS - 6; - case 18: /* 262144-524287 */ - return LIMB_DIGITS - 6; - case 19: /* 524288-1048575 */ - if (a < 1000000) - return LIMB_DIGITS - 6; - else - return LIMB_DIGITS - 7; - case 20: /* 1048576-2097151 */ - return LIMB_DIGITS - 7; - case 21: /* 2097152-4194303 */ - return LIMB_DIGITS - 7; - case 22: /* 4194304-8388607 */ - return LIMB_DIGITS - 7; - case 23: /* 8388608-16777215 */ - if (a < 10000000) - return LIMB_DIGITS - 7; - else - return LIMB_DIGITS - 8; - case 24: /* 16777216-33554431 */ - return LIMB_DIGITS - 8; - case 25: /* 33554432-67108863 */ - return LIMB_DIGITS - 8; - case 26: /* 67108864-134217727 */ - if (a < 100000000) - return LIMB_DIGITS - 8; - else - return LIMB_DIGITS - 9; -#if LIMB_BITS == 64 - case 27: /* 134217728-268435455 */ - return LIMB_DIGITS - 9; - case 28: /* 268435456-536870911 */ - return LIMB_DIGITS - 9; - case 29: /* 536870912-1073741823 */ - if (a < 1000000000) - return LIMB_DIGITS - 9; - else - return LIMB_DIGITS - 10; - case 30: /* 1073741824-2147483647 */ - return LIMB_DIGITS - 10; - case 31: /* 2147483648-4294967295 */ - return LIMB_DIGITS - 10; - case 32: /* 4294967296-8589934591 */ - return LIMB_DIGITS - 10; - case 33: /* 8589934592-17179869183 */ - if (a < 10000000000) - return LIMB_DIGITS - 10; - else - return LIMB_DIGITS - 11; - case 34: /* 17179869184-34359738367 */ - return LIMB_DIGITS - 11; - case 35: /* 34359738368-68719476735 */ - return LIMB_DIGITS - 11; - case 36: /* 68719476736-137438953471 */ - if (a < 100000000000) - return LIMB_DIGITS - 11; - else - return LIMB_DIGITS - 12; - case 37: /* 137438953472-274877906943 */ - return LIMB_DIGITS - 12; - case 38: /* 274877906944-549755813887 */ - return LIMB_DIGITS - 12; - case 39: /* 549755813888-1099511627775 */ - if (a < 1000000000000) - return LIMB_DIGITS - 12; - else - return LIMB_DIGITS - 13; - case 40: /* 1099511627776-2199023255551 */ - return LIMB_DIGITS - 13; - case 41: /* 2199023255552-4398046511103 */ - return LIMB_DIGITS - 13; - case 42: /* 4398046511104-8796093022207 */ - return LIMB_DIGITS - 13; - case 43: /* 8796093022208-17592186044415 */ - if (a < 10000000000000) - return LIMB_DIGITS - 13; - else - return LIMB_DIGITS - 14; - case 44: /* 17592186044416-35184372088831 */ - return LIMB_DIGITS - 14; - case 45: /* 35184372088832-70368744177663 */ - return LIMB_DIGITS - 14; - case 46: /* 70368744177664-140737488355327 */ - if (a < 100000000000000) - return LIMB_DIGITS - 14; - else - return LIMB_DIGITS - 15; - case 47: /* 140737488355328-281474976710655 */ - return LIMB_DIGITS - 15; - case 48: /* 281474976710656-562949953421311 */ - return LIMB_DIGITS - 15; - case 49: /* 562949953421312-1125899906842623 */ - if (a < 1000000000000000) - return LIMB_DIGITS - 15; - else - return LIMB_DIGITS - 16; - case 50: /* 1125899906842624-2251799813685247 */ - return LIMB_DIGITS - 16; - case 51: /* 2251799813685248-4503599627370495 */ - return LIMB_DIGITS - 16; - case 52: /* 4503599627370496-9007199254740991 */ - return LIMB_DIGITS - 16; - case 53: /* 9007199254740992-18014398509481983 */ - if (a < 10000000000000000) - return LIMB_DIGITS - 16; - else - return LIMB_DIGITS - 17; - case 54: /* 18014398509481984-36028797018963967 */ - return LIMB_DIGITS - 17; - case 55: /* 36028797018963968-72057594037927935 */ - return LIMB_DIGITS - 17; - case 56: /* 72057594037927936-144115188075855871 */ - if (a < 100000000000000000) - return LIMB_DIGITS - 17; - else - return LIMB_DIGITS - 18; - case 57: /* 144115188075855872-288230376151711743 */ - return LIMB_DIGITS - 18; - case 58: /* 288230376151711744-576460752303423487 */ - return LIMB_DIGITS - 18; - case 59: /* 576460752303423488-1152921504606846975 */ - if (a < 1000000000000000000) - return LIMB_DIGITS - 18; - else - return LIMB_DIGITS - 19; -#endif - default: - return 0; - } -} - -/* for debugging */ -void bfdec_print_str(const char *str, const bfdec_t *a) -{ - slimb_t i; - printf("%s=", str); - - if (a->expn == BF_EXP_NAN) { - printf("NaN"); - } else { - if (a->sign) - putchar('-'); - if (a->expn == BF_EXP_ZERO) { - putchar('0'); - } else if (a->expn == BF_EXP_INF) { - printf("Inf"); - } else { - printf("0."); - for(i = a->len - 1; i >= 0; i--) - printf("%0*" PRIu_LIMB, LIMB_DIGITS, a->tabi); - printf("e%" PRId_LIMB, a->expn); - } - } - printf("\n"); -} - -/* return != 0 if one digit between 0 and bit_pos inclusive is not zero. */ -static inline limb_t scan_digit_nz(const bfdec_t *r, slimb_t bit_pos) -{ - slimb_t pos; - limb_t v, q; - int shift; - - if (bit_pos < 0) - return 0; - pos = (limb_t)bit_pos / LIMB_DIGITS; - shift = (limb_t)bit_pos % LIMB_DIGITS; - fast_shr_rem_dec(q, v, r->tabpos, shift + 1); - (void)q; - if (v != 0) - return 1; - pos--; - while (pos >= 0) { - if (r->tabpos != 0) - return 1; - pos--; - } - return 0; -} - -static limb_t get_digit(const limb_t *tab, limb_t len, slimb_t pos) -{ - slimb_t i; - int shift; - i = floor_div(pos, LIMB_DIGITS); - if (i < 0 || i >= len) - return 0; - shift = pos - i * LIMB_DIGITS; - return fast_shr_dec(tabi, shift) % 10; -} - -#if 0 -static limb_t get_digits(const limb_t *tab, limb_t len, slimb_t pos) -{ - limb_t a0, a1; - int shift; - slimb_t i; - - i = floor_div(pos, LIMB_DIGITS); - shift = pos - i * LIMB_DIGITS; - if (i >= 0 && i < len) - a0 = tabi; - else - a0 = 0; - if (shift == 0) { - return a0; - } else { - i++; - if (i >= 0 && i < len) - a1 = tabi; - else - a1 = 0; - return fast_shr_dec(a0, shift) + - fast_urem(a1, &mp_pow_divLIMB_DIGITS - shift) * - mp_pow_decshift; - } -} -#endif - -/* return the addend for rounding. Note that prec can be <= 0 for bf_rint() */ -static int bfdec_get_rnd_add(int *pret, const bfdec_t *r, limb_t l, - slimb_t prec, int rnd_mode) -{ - int add_one, inexact; - limb_t digit1, digit0; - - // bfdec_print_str("get_rnd_add", r); - if (rnd_mode == BF_RNDF) { - digit0 = 1; /* faithful rounding does not honor the INEXACT flag */ - } else { - /* starting limb for bit 'prec + 1' */ - digit0 = scan_digit_nz(r, l * LIMB_DIGITS - 1 - bf_max(0, prec + 1)); - } - - /* get the digit at 'prec' */ - digit1 = get_digit(r->tab, l, l * LIMB_DIGITS - 1 - prec); - inexact = (digit1 | digit0) != 0; - - add_one = 0; - switch(rnd_mode) { - case BF_RNDZ: - break; - case BF_RNDN: - if (digit1 == 5) { - if (digit0) { - add_one = 1; - } else { - /* round to even */ - add_one = - get_digit(r->tab, l, l * LIMB_DIGITS - 1 - (prec - 1)) & 1; - } - } else if (digit1 > 5) { - add_one = 1; - } - break; - case BF_RNDD: - case BF_RNDU: - if (r->sign == (rnd_mode == BF_RNDD)) - add_one = inexact; - break; - case BF_RNDNA: - case BF_RNDF: - add_one = (digit1 >= 5); - break; - case BF_RNDA: - add_one = inexact; - break; - default: - abort(); - } - - if (inexact) - *pret |= BF_ST_INEXACT; - return add_one; -} - -/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is - assumed to have length 'l' (1 <= l <= r->len). prec1 can be - BF_PREC_INF. BF_FLAG_SUBNORMAL is not supported. Cannot fail with - BF_ST_MEM_ERROR. - */ -static int __bfdec_round(bfdec_t *r, limb_t prec1, bf_flags_t flags, limb_t l) -{ - int shift, add_one, rnd_mode, ret; - slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec; - - /* XXX: align to IEEE 754 2008 for decimal numbers ? */ - e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1); - e_min = -e_range + 3; - e_max = e_range; - - if (flags & BF_FLAG_RADPNT_PREC) { - /* 'prec' is the precision after the decimal point */ - if (prec1 != BF_PREC_INF) - prec = r->expn + prec1; - else - prec = prec1; - } else if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) { - /* restrict the precision in case of potentially subnormal - result */ - assert(prec1 != BF_PREC_INF); - prec = prec1 - (e_min - r->expn); - } else { - prec = prec1; - } - - /* round to prec bits */ - rnd_mode = flags & BF_RND_MASK; - ret = 0; - add_one = bfdec_get_rnd_add(&ret, r, l, prec, rnd_mode); - - if (prec <= 0) { - if (add_one) { - bfdec_resize(r, 1); /* cannot fail because r is non zero */ - r->tab0 = BF_DEC_BASE / 10; - r->expn += 1 - prec; - ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; - return ret; - } else { - goto underflow; - } - } else if (add_one) { - limb_t carry; - - /* add one starting at digit 'prec - 1' */ - bit_pos = l * LIMB_DIGITS - 1 - (prec - 1); - pos = bit_pos / LIMB_DIGITS; - carry = mp_pow_decbit_pos % LIMB_DIGITS; - carry = mp_add_ui_dec(r->tab + pos, carry, l - pos); - if (carry) { - /* shift right by one digit */ - mp_shr_dec(r->tab + pos, r->tab + pos, l - pos, 1, 1); - r->expn++; - } - } - - /* check underflow */ - if (unlikely(r->expn < e_min)) { - if (flags & BF_FLAG_SUBNORMAL) { - /* if inexact, also set the underflow flag */ - if (ret & BF_ST_INEXACT) - ret |= BF_ST_UNDERFLOW; - } else { - underflow: - bfdec_set_zero(r, r->sign); - ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; - return ret; - } - } - - /* check overflow */ - if (unlikely(r->expn > e_max)) { - bfdec_set_inf(r, r->sign); - ret |= BF_ST_OVERFLOW | BF_ST_INEXACT; - return ret; - } - - /* keep the bits starting at 'prec - 1' */ - bit_pos = l * LIMB_DIGITS - 1 - (prec - 1); - i = floor_div(bit_pos, LIMB_DIGITS); - if (i >= 0) { - shift = smod(bit_pos, LIMB_DIGITS); - if (shift != 0) { - r->tabi = fast_shr_dec(r->tabi, shift) * - mp_pow_decshift; - } - } else { - i = 0; - } - /* remove trailing zeros */ - while (r->tabi == 0) - i++; - if (i > 0) { - l -= i; - memmove(r->tab, r->tab + i, l * sizeof(limb_t)); - } - bfdec_resize(r, l); /* cannot fail */ - return ret; -} - -/* Cannot fail with BF_ST_MEM_ERROR. */ -int bfdec_round(bfdec_t *r, limb_t prec, bf_flags_t flags) -{ - if (r->len == 0) - return 0; - return __bfdec_round(r, prec, flags, r->len); -} - -/* 'r' must be a finite number. Cannot fail with BF_ST_MEM_ERROR. */ -int bfdec_normalize_and_round(bfdec_t *r, limb_t prec1, bf_flags_t flags) -{ - limb_t l, v; - int shift, ret; - - // bfdec_print_str("bf_renorm", r); - l = r->len; - while (l > 0 && r->tabl - 1 == 0) - l--; - if (l == 0) { - /* zero */ - r->expn = BF_EXP_ZERO; - bfdec_resize(r, 0); /* cannot fail */ - ret = 0; - } else { - r->expn -= (r->len - l) * LIMB_DIGITS; - /* shift to have the MSB set to '1' */ - v = r->tabl - 1; - shift = clz_dec(v); - if (shift != 0) { - mp_shl_dec(r->tab, r->tab, l, shift, 0); - r->expn -= shift; - } - ret = __bfdec_round(r, prec1, flags, l); - } - // bf_print_str("r_final", r); - return ret; -} - -int bfdec_set_ui(bfdec_t *r, uint64_t v) -{ -#if LIMB_BITS == 32 - if (v >= BF_DEC_BASE * BF_DEC_BASE) { - if (bfdec_resize(r, 3)) - goto fail; - r->tab0 = v % BF_DEC_BASE; - v /= BF_DEC_BASE; - r->tab1 = v % BF_DEC_BASE; - r->tab2 = v / BF_DEC_BASE; - r->expn = 3 * LIMB_DIGITS; - } else -#endif - if (v >= BF_DEC_BASE) { - if (bfdec_resize(r, 2)) - goto fail; - r->tab0 = v % BF_DEC_BASE; - r->tab1 = v / BF_DEC_BASE; - r->expn = 2 * LIMB_DIGITS; - } else { - if (bfdec_resize(r, 1)) - goto fail; - r->tab0 = v; - r->expn = LIMB_DIGITS; - } - r->sign = 0; - return bfdec_normalize_and_round(r, BF_PREC_INF, 0); - fail: - bfdec_set_nan(r); - return BF_ST_MEM_ERROR; -} - -int bfdec_set_si(bfdec_t *r, int64_t v) -{ - int ret; - if (v < 0) { - ret = bfdec_set_ui(r, -v); - r->sign = 1; - } else { - ret = bfdec_set_ui(r, v); - } - return ret; -} - -static int bfdec_add_internal(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, bf_flags_t flags, int b_neg) -{ - bf_context_t *s = r->ctx; - int is_sub, cmp_res, a_sign, b_sign, ret; - - a_sign = a->sign; - b_sign = b->sign ^ b_neg; - is_sub = a_sign ^ b_sign; - cmp_res = bfdec_cmpu(a, b); - if (cmp_res < 0) { - const bfdec_t *tmp; - tmp = a; - a = b; - b = tmp; - a_sign = b_sign; /* b_sign is never used later */ - } - /* abs(a) >= abs(b) */ - if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) { - /* zero result */ - bfdec_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD); - ret = 0; - } else if (a->len == 0 || b->len == 0) { - ret = 0; - if (a->expn >= BF_EXP_INF) { - if (a->expn == BF_EXP_NAN) { - /* at least one operand is NaN */ - bfdec_set_nan(r); - ret = 0; - } else if (b->expn == BF_EXP_INF && is_sub) { - /* infinities with different signs */ - bfdec_set_nan(r); - ret = BF_ST_INVALID_OP; - } else { - bfdec_set_inf(r, a_sign); - } - } else { - /* at least one zero and not subtract */ - if (bfdec_set(r, a)) - return BF_ST_MEM_ERROR; - r->sign = a_sign; - goto renorm; - } - } else { - slimb_t d, a_offset, b_offset, i, r_len; - limb_t carry; - limb_t *b1_tab; - int b_shift; - mp_size_t b1_len; - - d = a->expn - b->expn; - - /* XXX: not efficient in time and memory if the precision is - not infinite */ - r_len = bf_max(a->len, b->len + (d + LIMB_DIGITS - 1) / LIMB_DIGITS); - if (bfdec_resize(r, r_len)) - goto fail; - r->sign = a_sign; - r->expn = a->expn; - - a_offset = r_len - a->len; - for(i = 0; i < a_offset; i++) - r->tabi = 0; - for(i = 0; i < a->len; i++) - r->taba_offset + i = a->tabi; - - b_shift = d % LIMB_DIGITS; - if (b_shift == 0) { - b1_len = b->len; - b1_tab = (limb_t *)b->tab; - } else { - b1_len = b->len + 1; - b1_tab = bf_malloc(s, sizeof(limb_t) * b1_len); - if (!b1_tab) - goto fail; - b1_tab0 = mp_shr_dec(b1_tab + 1, b->tab, b->len, b_shift, 0) * - mp_pow_decLIMB_DIGITS - b_shift; - } - b_offset = r_len - (b->len + (d + LIMB_DIGITS - 1) / LIMB_DIGITS); - - if (is_sub) { - carry = mp_sub_dec(r->tab + b_offset, r->tab + b_offset, - b1_tab, b1_len, 0); - if (carry != 0) { - carry = mp_sub_ui_dec(r->tab + b_offset + b1_len, carry, - r_len - (b_offset + b1_len)); - assert(carry == 0); - } - } else { - carry = mp_add_dec(r->tab + b_offset, r->tab + b_offset, - b1_tab, b1_len, 0); - if (carry != 0) { - carry = mp_add_ui_dec(r->tab + b_offset + b1_len, carry, - r_len - (b_offset + b1_len)); - } - if (carry != 0) { - if (bfdec_resize(r, r_len + 1)) { - if (b_shift != 0) - bf_free(s, b1_tab); - goto fail; - } - r->tabr_len = 1; - r->expn += LIMB_DIGITS; - } - } - if (b_shift != 0) - bf_free(s, b1_tab); - renorm: - ret = bfdec_normalize_and_round(r, prec, flags); - } - return ret; - fail: - bfdec_set_nan(r); - return BF_ST_MEM_ERROR; -} - -static int __bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags) -{ - return bfdec_add_internal(r, a, b, prec, flags, 0); -} - -static int __bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags) -{ - return bfdec_add_internal(r, a, b, prec, flags, 1); -} - -int bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, - (bf_op2_func_t *)__bfdec_add); -} - -int bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, - (bf_op2_func_t *)__bfdec_sub); -} - -int bfdec_mul(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags) -{ - int ret, r_sign; - - if (a->len < b->len) { - const bfdec_t *tmp = a; - a = b; - b = tmp; - } - r_sign = a->sign ^ b->sign; - /* here b->len <= a->len */ - if (b->len == 0) { - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - bfdec_set_nan(r); - ret = 0; - } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) { - if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) || - (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) { - bfdec_set_nan(r); - ret = BF_ST_INVALID_OP; - } else { - bfdec_set_inf(r, r_sign); - ret = 0; - } - } else { - bfdec_set_zero(r, r_sign); - ret = 0; - } - } else { - bfdec_t tmp, *r1 = NULL; - limb_t a_len, b_len; - limb_t *a_tab, *b_tab; - - a_len = a->len; - b_len = b->len; - a_tab = a->tab; - b_tab = b->tab; - - if (r == a || r == b) { - bfdec_init(r->ctx, &tmp); - r1 = r; - r = &tmp; - } - if (bfdec_resize(r, a_len + b_len)) { - bfdec_set_nan(r); - ret = BF_ST_MEM_ERROR; - goto done; - } - mp_mul_basecase_dec(r->tab, a_tab, a_len, b_tab, b_len); - r->sign = r_sign; - r->expn = a->expn + b->expn; - ret = bfdec_normalize_and_round(r, prec, flags); - done: - if (r == &tmp) - bfdec_move(r1, &tmp); - } - return ret; -} - -int bfdec_mul_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, - bf_flags_t flags) -{ - bfdec_t b; - int ret; - bfdec_init(r->ctx, &b); - ret = bfdec_set_si(&b, b1); - ret |= bfdec_mul(r, a, &b, prec, flags); - bfdec_delete(&b); - return ret; -} - -int bfdec_add_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, - bf_flags_t flags) -{ - bfdec_t b; - int ret; - - bfdec_init(r->ctx, &b); - ret = bfdec_set_si(&b, b1); - ret |= bfdec_add(r, a, &b, prec, flags); - bfdec_delete(&b); - return ret; -} - -static int __bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, - limb_t prec, bf_flags_t flags) -{ - int ret, r_sign; - limb_t n, nb, precl; - - r_sign = a->sign ^ b->sign; - if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) { - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - bfdec_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) { - bfdec_set_nan(r); - return BF_ST_INVALID_OP; - } else if (a->expn == BF_EXP_INF) { - bfdec_set_inf(r, r_sign); - return 0; - } else { - bfdec_set_zero(r, r_sign); - return 0; - } - } else if (a->expn == BF_EXP_ZERO) { - if (b->expn == BF_EXP_ZERO) { - bfdec_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bfdec_set_zero(r, r_sign); - return 0; - } - } else if (b->expn == BF_EXP_ZERO) { - bfdec_set_inf(r, r_sign); - return BF_ST_DIVIDE_ZERO; - } - - nb = b->len; - if (prec == BF_PREC_INF) { - /* infinite precision: return BF_ST_INVALID_OP if not an exact - result */ - /* XXX: check */ - precl = nb + 1; - } else if (flags & BF_FLAG_RADPNT_PREC) { - /* number of digits after the decimal point */ - /* XXX: check (2 extra digits for rounding + 2 digits) */ - precl = (bf_max(a->expn - b->expn, 0) + 2 + - prec + 2 + LIMB_DIGITS - 1) / LIMB_DIGITS; - } else { - /* number of limbs of the quotient (2 extra digits for rounding) */ - precl = (prec + 2 + LIMB_DIGITS - 1) / LIMB_DIGITS; - } - n = bf_max(a->len, precl); - - { - limb_t *taba, na, i; - slimb_t d; - - na = n + nb; - taba = bf_malloc(r->ctx, (na + 1) * sizeof(limb_t)); - if (!taba) - goto fail; - d = na - a->len; - memset(taba, 0, d * sizeof(limb_t)); - memcpy(taba + d, a->tab, a->len * sizeof(limb_t)); - if (bfdec_resize(r, n + 1)) - goto fail1; - if (mp_div_dec(r->ctx, r->tab, taba, na, b->tab, nb)) { - fail1: - bf_free(r->ctx, taba); - goto fail; - } - /* see if non zero remainder */ - for(i = 0; i < nb; i++) { - if (tabai != 0) - break; - } - bf_free(r->ctx, taba); - if (i != nb) { - if (prec == BF_PREC_INF) { - bfdec_set_nan(r); - return BF_ST_INVALID_OP; - } else { - r->tab0 |= 1; - } - } - r->expn = a->expn - b->expn + LIMB_DIGITS; - r->sign = r_sign; - ret = bfdec_normalize_and_round(r, prec, flags); - } - return ret; - fail: - bfdec_set_nan(r); - return BF_ST_MEM_ERROR; -} - -int bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags) -{ - return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, - (bf_op2_func_t *)__bfdec_div); -} - -/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the - integer defined as floor(a/b) and r = a - q * b. */ -static void bfdec_tdivremu(bf_context_t *s, bfdec_t *q, bfdec_t *r, - const bfdec_t *a, const bfdec_t *b) -{ - if (bfdec_cmpu(a, b) < 0) { - bfdec_set_ui(q, 0); - bfdec_set(r, a); - } else { - bfdec_div(q, a, b, 0, BF_RNDZ | BF_FLAG_RADPNT_PREC); - bfdec_mul(r, q, b, BF_PREC_INF, BF_RNDZ); - bfdec_sub(r, a, r, BF_PREC_INF, BF_RNDZ); - } -} - -/* division and remainder. - - rnd_mode is the rounding mode for the quotient. The additional - rounding mode BF_RND_EUCLIDIAN is supported. - - 'q' is an integer. 'r' is rounded with prec and flags (prec can be - BF_PREC_INF). -*/ -int bfdec_divrem(bfdec_t *q, bfdec_t *r, const bfdec_t *a, const bfdec_t *b, - limb_t prec, bf_flags_t flags, int rnd_mode) -{ - bf_context_t *s = q->ctx; - bfdec_t a1_s, *a1 = &a1_s; - bfdec_t b1_s, *b1 = &b1_s; - bfdec_t r1_s, *r1 = &r1_s; - int q_sign, res; - BOOL is_ceil, is_rndn; - - assert(q != a && q != b); - assert(r != a && r != b); - assert(q != r); - - if (a->len == 0 || b->len == 0) { - bfdec_set_zero(q, 0); - if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { - bfdec_set_nan(r); - return 0; - } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) { - bfdec_set_nan(r); - return BF_ST_INVALID_OP; - } else { - bfdec_set(r, a); - return bfdec_round(r, prec, flags); - } - } - - q_sign = a->sign ^ b->sign; - is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); - switch(rnd_mode) { - default: - case BF_RNDZ: - case BF_RNDN: - case BF_RNDNA: - is_ceil = FALSE; - break; - case BF_RNDD: - is_ceil = q_sign; - break; - case BF_RNDU: - is_ceil = q_sign ^ 1; - break; - case BF_RNDA: - is_ceil = TRUE; - break; - case BF_DIVREM_EUCLIDIAN: - is_ceil = a->sign; - break; - } - - a1->expn = a->expn; - a1->tab = a->tab; - a1->len = a->len; - a1->sign = 0; - - b1->expn = b->expn; - b1->tab = b->tab; - b1->len = b->len; - b1->sign = 0; - - // bfdec_print_str("a1", a1); - // bfdec_print_str("b1", b1); - /* XXX: could improve to avoid having a large 'q' */ - bfdec_tdivremu(s, q, r, a1, b1); - if (bfdec_is_nan(q) || bfdec_is_nan(r)) - goto fail; - // bfdec_print_str("q", q); - // bfdec_print_str("r", r); - - if (r->len != 0) { - if (is_rndn) { - bfdec_init(s, r1); - if (bfdec_set(r1, r)) - goto fail; - if (bfdec_mul_si(r1, r1, 2, BF_PREC_INF, BF_RNDZ)) { - bfdec_delete(r1); - goto fail; - } - res = bfdec_cmpu(r1, b); - bfdec_delete(r1); - if (res > 0 || - (res == 0 && - (rnd_mode == BF_RNDNA || - (get_digit(q->tab, q->len, q->len * LIMB_DIGITS - q->expn) & 1) != 0))) { - goto do_sub_r; - } - } else if (is_ceil) { - do_sub_r: - res = bfdec_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ); - res |= bfdec_sub(r, r, b1, BF_PREC_INF, BF_RNDZ); - if (res & BF_ST_MEM_ERROR) - goto fail; - } - } - - r->sign ^= a->sign; - q->sign = q_sign; - return bfdec_round(r, prec, flags); - fail: - bfdec_set_nan(q); - bfdec_set_nan(r); - return BF_ST_MEM_ERROR; -} - -int bfdec_rem(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags, int rnd_mode) -{ - bfdec_t q_s, *q = &q_s; - int ret; - - bfdec_init(r->ctx, q); - ret = bfdec_divrem(q, r, a, b, prec, flags, rnd_mode); - bfdec_delete(q); - return ret; -} - -/* convert to integer (infinite precision) */ -int bfdec_rint(bfdec_t *r, int rnd_mode) -{ - return bfdec_round(r, 0, rnd_mode | BF_FLAG_RADPNT_PREC); -} - -int bfdec_sqrt(bfdec_t *r, const bfdec_t *a, limb_t prec, bf_flags_t flags) -{ - bf_context_t *s = a->ctx; - int ret, k; - limb_t *a1, v; - slimb_t n, n1, prec1; - limb_t res; - - assert(r != a); - - if (a->len == 0) { - if (a->expn == BF_EXP_NAN) { - bfdec_set_nan(r); - } else if (a->expn == BF_EXP_INF && a->sign) { - goto invalid_op; - } else { - bfdec_set(r, a); - } - ret = 0; - } else if (a->sign || prec == BF_PREC_INF) { - invalid_op: - bfdec_set_nan(r); - ret = BF_ST_INVALID_OP; - } else { - if (flags & BF_FLAG_RADPNT_PREC) { - prec1 = bf_max(floor_div(a->expn + 1, 2) + prec, 1); - } else { - prec1 = prec; - } - /* convert the mantissa to an integer with at least 2 * - prec + 4 digits */ - n = (2 * (prec1 + 2) + 2 * LIMB_DIGITS - 1) / (2 * LIMB_DIGITS); - if (bfdec_resize(r, n)) - goto fail; - a1 = bf_malloc(s, sizeof(limb_t) * 2 * n); - if (!a1) - goto fail; - n1 = bf_min(2 * n, a->len); - memset(a1, 0, (2 * n - n1) * sizeof(limb_t)); - memcpy(a1 + 2 * n - n1, a->tab + a->len - n1, n1 * sizeof(limb_t)); - if (a->expn & 1) { - res = mp_shr_dec(a1, a1, 2 * n, 1, 0); - } else { - res = 0; - } - /* normalize so that a1 >= B^(2*n)/4. Not need for n = 1 - because mp_sqrtrem2_dec already does it */ - k = 0; - if (n > 1) { - v = a12 * n - 1; - while (v < BF_DEC_BASE / 4) { - k++; - v *= 4; - } - if (k != 0) - mp_mul1_dec(a1, a1, 2 * n, 1 << (2 * k), 0); - } - if (mp_sqrtrem_dec(s, r->tab, a1, n)) { - bf_free(s, a1); - goto fail; - } - if (k != 0) - mp_div1_dec(r->tab, r->tab, n, 1 << k, 0); - if (!res) { - res = mp_scan_nz(a1, n + 1); - } - bf_free(s, a1); - if (!res) { - res = mp_scan_nz(a->tab, a->len - n1); - } - if (res != 0) - r->tab0 |= 1; - r->sign = 0; - r->expn = (a->expn + 1) >> 1; - ret = bfdec_round(r, prec, flags); - } - return ret; - fail: - bfdec_set_nan(r); - return BF_ST_MEM_ERROR; -} - -/* The rounding mode is always BF_RNDZ. Return BF_ST_OVERFLOW if there - is an overflow and 0 otherwise. No memory error is possible. */ -int bfdec_get_int32(int *pres, const bfdec_t *a) -{ - uint32_t v; - int ret; - if (a->expn >= BF_EXP_INF) { - ret = 0; - if (a->expn == BF_EXP_INF) { - v = (uint32_t)INT32_MAX + a->sign; - /* XXX: return overflow ? */ - } else { - v = INT32_MAX; - } - } else if (a->expn <= 0) { - v = 0; - ret = 0; - } else if (a->expn <= 9) { - v = fast_shr_dec(a->taba->len - 1, LIMB_DIGITS - a->expn); - if (a->sign) - v = -v; - ret = 0; - } else if (a->expn == 10) { - uint64_t v1; - uint32_t v_max; -#if LIMB_BITS == 64 - v1 = fast_shr_dec(a->taba->len - 1, LIMB_DIGITS - a->expn); -#else - v1 = (uint64_t)a->taba->len - 1 * 10 + - get_digit(a->tab, a->len, (a->len - 1) * LIMB_DIGITS - 1); -#endif - v_max = (uint32_t)INT32_MAX + a->sign; - if (v1 > v_max) { - v = v_max; - ret = BF_ST_OVERFLOW; - } else { - v = v1; - if (a->sign) - v = -v; - ret = 0; - } - } else { - v = (uint32_t)INT32_MAX + a->sign; - ret = BF_ST_OVERFLOW; - } - *pres = v; - return ret; -} - -/* power to an integer with infinite precision */ -int bfdec_pow_ui(bfdec_t *r, const bfdec_t *a, limb_t b) -{ - int ret, n_bits, i; - - assert(r != a); - if (b == 0) - return bfdec_set_ui(r, 1); - ret = bfdec_set(r, a); - n_bits = LIMB_BITS - clz(b); - for(i = n_bits - 2; i >= 0; i--) { - ret |= bfdec_mul(r, r, r, BF_PREC_INF, BF_RNDZ); - if ((b >> i) & 1) - ret |= bfdec_mul(r, r, a, BF_PREC_INF, BF_RNDZ); - } - return ret; -} - -char *bfdec_ftoa(size_t *plen, const bfdec_t *a, limb_t prec, bf_flags_t flags) -{ - return bf_ftoa_internal(plen, (const bf_t *)a, 10, prec, flags, TRUE); -} - -int bfdec_atof(bfdec_t *r, const char *str, const char **pnext, - limb_t prec, bf_flags_t flags) -{ - slimb_t dummy_exp; - return bf_atof_internal((bf_t *)r, &dummy_exp, str, pnext, 10, prec, - flags, TRUE); -} - -#endif /* USE_BF_DEC */ - -#ifdef USE_FFT_MUL -/***************************************************************/ -/* Integer multiplication with FFT */ - -/* or LIMB_BITS at bit position 'pos' in tab */ -static inline void put_bits(limb_t *tab, limb_t len, slimb_t pos, limb_t val) -{ - limb_t i; - int p; - - i = pos >> LIMB_LOG2_BITS; - p = pos & (LIMB_BITS - 1); - if (i < len) - tabi |= val << p; - if (p != 0) { - i++; - if (i < len) { - tabi |= val >> (LIMB_BITS - p); - } - } -} - -#if defined(__AVX2__) - -typedef double NTTLimb; - -/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */ -#define NTT_MOD_LOG2_MIN 50 -#define NTT_MOD_LOG2_MAX 51 -#define NB_MODS 5 -#define NTT_PROOT_2EXP 39 -static const int ntt_int_bitsNB_MODS = { 254, 203, 152, 101, 50, }; - -static const limb_t ntt_modsNB_MODS = { 0x00073a8000000001, 0x0007858000000001, 0x0007a38000000001, 0x0007a68000000001, 0x0007fd8000000001, -}; - -static const limb_t ntt_proot2NB_MODS = { - { 0x00056198d44332c8, 0x0002eb5d640aad39, 0x00047e31eaa35fd0, 0x0005271ac118a150, 0x00075e0ce8442bd5, }, - { 0x000461169761bcc5, 0x0002dac3cb2da688, 0x0004abc97751e3bf, 0x000656778fc8c485, 0x0000dc6469c269fa, }, -}; - -static const limb_t ntt_mods_crNB_MODS * (NB_MODS - 1) / 2 = { - 0x00020e4da740da8e, 0x0004c3dc09c09c1d, 0x000063bd097b4271, 0x000799d8f18f18fd, - 0x0005384222222264, 0x000572b07c1f07fe, 0x00035cd08888889a, - 0x00066015555557e3, 0x000725960b60b623, - 0x0002fc1fa1d6ce12, -}; - -#else - -typedef limb_t NTTLimb; - -#if LIMB_BITS == 64 - -#define NTT_MOD_LOG2_MIN 61 -#define NTT_MOD_LOG2_MAX 62 -#define NB_MODS 5 -#define NTT_PROOT_2EXP 51 -static const int ntt_int_bitsNB_MODS = { 307, 246, 185, 123, 61, }; - -static const limb_t ntt_modsNB_MODS = { 0x28d8000000000001, 0x2a88000000000001, 0x2ed8000000000001, 0x3508000000000001, 0x3aa8000000000001, -}; - -static const limb_t ntt_proot2NB_MODS = { - { 0x1b8ea61034a2bea7, 0x21a9762de58206fb, 0x02ca782f0756a8ea, 0x278384537a3e50a1, 0x106e13fee74ce0ab, }, - { 0x233513af133e13b8, 0x1d13140d1c6f75f1, 0x12cde57f97e3eeda, 0x0d6149e23cbe654f, 0x36cd204f522a1379, }, -}; - -static const limb_t ntt_mods_crNB_MODS * (NB_MODS - 1) / 2 = { - 0x08a9ed097b425eea, 0x18a44aaaaaaaaab3, 0x2493f57f57f57f5d, 0x126b8d0649a7f8d4, - 0x09d80ed7303b5ccc, 0x25b8bcf3cf3cf3d5, 0x2ce6ce63398ce638, - 0x0e31fad40a57eb59, 0x02a3529fd4a7f52f, - 0x3a5493e93e93e94a, -}; - -#elif LIMB_BITS == 32 - -/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */ -#define NTT_MOD_LOG2_MIN 29 -#define NTT_MOD_LOG2_MAX 30 -#define NB_MODS 5 -#define NTT_PROOT_2EXP 20 -static const int ntt_int_bitsNB_MODS = { 148, 119, 89, 59, 29, }; - -static const limb_t ntt_modsNB_MODS = { 0x0000000032b00001, 0x0000000033700001, 0x0000000036d00001, 0x0000000037300001, 0x000000003e500001, -}; - -static const limb_t ntt_proot2NB_MODS = { - { 0x0000000032525f31, 0x0000000005eb3b37, 0x00000000246eda9f, 0x0000000035f25901, 0x00000000022f5768, }, - { 0x00000000051eba1a, 0x00000000107be10e, 0x000000001cd574e0, 0x00000000053806e6, 0x000000002cd6bf98, }, -}; - -static const limb_t ntt_mods_crNB_MODS * (NB_MODS - 1) / 2 = { - 0x000000000449559a, 0x000000001eba6ca9, 0x000000002ec18e46, 0x000000000860160b, - 0x000000000d321307, 0x000000000bf51120, 0x000000000f662938, - 0x000000000932ab3e, 0x000000002f40eef8, - 0x000000002e760905, -}; - -#endif /* LIMB_BITS */ - -#endif /* !AVX2 */ - -#if defined(__AVX2__) -#define NTT_TRIG_K_MAX 18 -#else -#define NTT_TRIG_K_MAX 19 -#endif - -typedef struct BFNTTState { - bf_context_t *ctx; - - /* used for mul_mod_fast() */ - limb_t ntt_mods_divNB_MODS; - - limb_t ntt_proot_powNB_MODS2NTT_PROOT_2EXP + 1; - limb_t ntt_proot_pow_invNB_MODS2NTT_PROOT_2EXP + 1; - NTTLimb *ntt_trigNB_MODS2NTT_TRIG_K_MAX + 1; - /* 1/2^n mod m */ - limb_t ntt_len_invNB_MODSNTT_PROOT_2EXP + 12; -#if defined(__AVX2__) - __m256d ntt_mods_cr_vecNB_MODS * (NB_MODS - 1) / 2; - __m256d ntt_mods_vecNB_MODS; - __m256d ntt_mods_inv_vecNB_MODS; -#else - limb_t ntt_mods_cr_invNB_MODS * (NB_MODS - 1) / 2; -#endif -} BFNTTState; - -static NTTLimb *get_trig(BFNTTState *s, int k, int inverse, int m_idx); - -/* add modulo with up to (LIMB_BITS-1) bit modulo */ -static inline limb_t add_mod(limb_t a, limb_t b, limb_t m) -{ - limb_t r; - r = a + b; - if (r >= m) - r -= m; - return r; -} - -/* sub modulo with up to LIMB_BITS bit modulo */ -static inline limb_t sub_mod(limb_t a, limb_t b, limb_t m) -{ - limb_t r; - r = a - b; - if (r > a) - r += m; - return r; -} - -/* return (r0+r1*B) mod m - precondition: 0 <= r0+r1*B < 2^(64+NTT_MOD_LOG2_MIN) -*/ -static inline limb_t mod_fast(dlimb_t r, - limb_t m, limb_t m_inv) -{ - limb_t a1, q, t0, r1, r0; - - a1 = r >> NTT_MOD_LOG2_MIN; - - q = ((dlimb_t)a1 * m_inv) >> LIMB_BITS; - r = r - (dlimb_t)q * m - m * 2; - r1 = r >> LIMB_BITS; - t0 = (slimb_t)r1 >> 1; - r += m & t0; - r0 = r; - r1 = r >> LIMB_BITS; - r0 += m & r1; - return r0; -} - -/* faster version using precomputed modulo inverse. - precondition: 0 <= a * b < 2^(64+NTT_MOD_LOG2_MIN) */ -static inline limb_t mul_mod_fast(limb_t a, limb_t b, - limb_t m, limb_t m_inv) -{ - dlimb_t r; - r = (dlimb_t)a * (dlimb_t)b; - return mod_fast(r, m, m_inv); -} - -static inline limb_t init_mul_mod_fast(limb_t m) -{ - dlimb_t t; - assert(m < (limb_t)1 << NTT_MOD_LOG2_MAX); - assert(m >= (limb_t)1 << NTT_MOD_LOG2_MIN); - t = (dlimb_t)1 << (LIMB_BITS + NTT_MOD_LOG2_MIN); - return t / m; -} - -/* Faster version used when the multiplier is constant. 0 <= a < 2^64, - 0 <= b < m. */ -static inline limb_t mul_mod_fast2(limb_t a, limb_t b, - limb_t m, limb_t b_inv) -{ - limb_t r, q; - - q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS; - r = a * b - q * m; - if (r >= m) - r -= m; - return r; -} - -/* Faster version used when the multiplier is constant. 0 <= a < 2^64, - 0 <= b < m. Let r = a * b mod m. The return value is 'r' or 'r + - m'. */ -static inline limb_t mul_mod_fast3(limb_t a, limb_t b, - limb_t m, limb_t b_inv) -{ - limb_t r, q; - - q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS; - r = a * b - q * m; - return r; -} - -static inline limb_t init_mul_mod_fast2(limb_t b, limb_t m) -{ - return ((dlimb_t)b << LIMB_BITS) / m; -} - -#ifdef __AVX2__ - -static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m) -{ - slimb_t v; - v = a; - if (v < 0) - v += m; - if (v >= m) - v -= m; - return v; -} - -static inline NTTLimb int_to_ntt_limb(limb_t a, limb_t m) -{ - return (slimb_t)a; -} - -static inline NTTLimb int_to_ntt_limb2(limb_t a, limb_t m) -{ - if (a >= (m / 2)) - a -= m; - return (slimb_t)a; -} - -/* return r + m if r < 0 otherwise r. */ -static inline __m256d ntt_mod1(__m256d r, __m256d m) -{ - return _mm256_blendv_pd(r, r + m, r); -} - -/* input: abs(r) < 2 * m. Output: abs(r) < m */ -static inline __m256d ntt_mod(__m256d r, __m256d mf, __m256d m2f) -{ - return _mm256_blendv_pd(r, r + m2f, r) - mf; -} - -/* input: abs(a*b) < 2 * m^2, output: abs(r) < m */ -static inline __m256d ntt_mul_mod(__m256d a, __m256d b, __m256d mf, - __m256d m_inv) -{ - __m256d r, q, ab1, ab0, qm0, qm1; - ab1 = a * b; - q = _mm256_round_pd(ab1 * m_inv, 0); /* round to nearest */ - qm1 = q * mf; - qm0 = _mm256_fmsub_pd(q, mf, qm1); /* low part */ - ab0 = _mm256_fmsub_pd(a, b, ab1); /* low part */ - r = (ab1 - qm1) + (ab0 - qm0); - return r; -} - -static void *bf_aligned_malloc(bf_context_t *s, size_t size, size_t align) -{ - void *ptr; - void **ptr1; - ptr = bf_malloc(s, size + sizeof(void *) + align - 1); - if (!ptr) - return NULL; - ptr1 = (void **)(((uintptr_t)ptr + sizeof(void *) + align - 1) & - ~(align - 1)); - ptr1-1 = ptr; - return ptr1; -} - -static void bf_aligned_free(bf_context_t *s, void *ptr) -{ - if (!ptr) - return; - bf_free(s, ((void **)ptr)-1); -} - -static void *ntt_malloc(BFNTTState *s, size_t size) -{ - return bf_aligned_malloc(s->ctx, size, 64); -} - -static void ntt_free(BFNTTState *s, void *ptr) -{ - bf_aligned_free(s->ctx, ptr); -} - -static no_inline int ntt_fft(BFNTTState *s, - NTTLimb *out_buf, NTTLimb *in_buf, - NTTLimb *tmp_buf, int fft_len_log2, - int inverse, int m_idx) -{ - limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j; - NTTLimb *tab_in, *tab_out, *tmp, *trig; - __m256d m_inv, mf, m2f, c, a0, a1, b0, b1; - limb_t m; - int l; - - m = ntt_modsm_idx; - - m_inv = _mm256_set1_pd(1.0 / (double)m); - mf = _mm256_set1_pd(m); - m2f = _mm256_set1_pd(m * 2); - - n = (limb_t)1 << fft_len_log2; - assert(n >= 8); - stride_in = n / 2; - - tab_in = in_buf; - tab_out = tmp_buf; - trig = get_trig(s, fft_len_log2, inverse, m_idx); - if (!trig) - return -1; - p = 0; - for(k = 0; k < stride_in; k += 4) { - a0 = _mm256_load_pd(&tab_ink); - a1 = _mm256_load_pd(&tab_ink + stride_in); - c = _mm256_load_pd(trig); - trig += 4; - b0 = ntt_mod(a0 + a1, mf, m2f); - b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); - a0 = _mm256_permute2f128_pd(b0, b1, 0x20); - a1 = _mm256_permute2f128_pd(b0, b1, 0x31); - a0 = _mm256_permute4x64_pd(a0, 0xd8); - a1 = _mm256_permute4x64_pd(a1, 0xd8); - _mm256_store_pd(&tab_outp, a0); - _mm256_store_pd(&tab_outp + 4, a1); - p += 2 * 4; - } - tmp = tab_in; - tab_in = tab_out; - tab_out = tmp; - - trig = get_trig(s, fft_len_log2 - 1, inverse, m_idx); - if (!trig) - return -1; - p = 0; - for(k = 0; k < stride_in; k += 4) { - a0 = _mm256_load_pd(&tab_ink); - a1 = _mm256_load_pd(&tab_ink + stride_in); - c = _mm256_setr_pd(trig0, trig0, trig1, trig1); - trig += 2; - b0 = ntt_mod(a0 + a1, mf, m2f); - b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); - a0 = _mm256_permute2f128_pd(b0, b1, 0x20); - a1 = _mm256_permute2f128_pd(b0, b1, 0x31); - _mm256_store_pd(&tab_outp, a0); - _mm256_store_pd(&tab_outp + 4, a1); - p += 2 * 4; - } - tmp = tab_in; - tab_in = tab_out; - tab_out = tmp; - - nb_blocks = n / 4; - fft_per_block = 4; - - l = fft_len_log2 - 2; - while (nb_blocks != 2) { - nb_blocks >>= 1; - p = 0; - k = 0; - trig = get_trig(s, l, inverse, m_idx); - if (!trig) - return -1; - for(i = 0; i < nb_blocks; i++) { - c = _mm256_set1_pd(trig0); - trig++; - for(j = 0; j < fft_per_block; j += 4) { - a0 = _mm256_load_pd(&tab_ink + j); - a1 = _mm256_load_pd(&tab_ink + j + stride_in); - b0 = ntt_mod(a0 + a1, mf, m2f); - b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); - _mm256_store_pd(&tab_outp + j, b0); - _mm256_store_pd(&tab_outp + j + fft_per_block, b1); - } - k += fft_per_block; - p += 2 * fft_per_block; - } - fft_per_block <<= 1; - l--; - tmp = tab_in; - tab_in = tab_out; - tab_out = tmp; - } - - tab_out = out_buf; - for(k = 0; k < stride_in; k += 4) { - a0 = _mm256_load_pd(&tab_ink); - a1 = _mm256_load_pd(&tab_ink + stride_in); - b0 = ntt_mod(a0 + a1, mf, m2f); - b1 = ntt_mod(a0 - a1, mf, m2f); - _mm256_store_pd(&tab_outk, b0); - _mm256_store_pd(&tab_outk + stride_in, b1); - } - return 0; -} - -static void ntt_vec_mul(BFNTTState *s, - NTTLimb *tab1, NTTLimb *tab2, limb_t fft_len_log2, - int k_tot, int m_idx) -{ - limb_t i, c_inv, n, m; - __m256d m_inv, mf, a, b, c; - - m = ntt_modsm_idx; - c_inv = s->ntt_len_invm_idxk_tot0; - m_inv = _mm256_set1_pd(1.0 / (double)m); - mf = _mm256_set1_pd(m); - c = _mm256_set1_pd(int_to_ntt_limb(c_inv, m)); - n = (limb_t)1 << fft_len_log2; - for(i = 0; i < n; i += 4) { - a = _mm256_load_pd(&tab1i); - b = _mm256_load_pd(&tab2i); - a = ntt_mul_mod(a, b, mf, m_inv); - a = ntt_mul_mod(a, c, mf, m_inv); - _mm256_store_pd(&tab1i, a); - } -} - -static no_inline void mul_trig(NTTLimb *buf, - limb_t n, limb_t c1, limb_t m, limb_t m_inv1) -{ - limb_t i, c2, c3, c4; - __m256d c, c_mul, a0, mf, m_inv; - assert(n >= 2); - - mf = _mm256_set1_pd(m); - m_inv = _mm256_set1_pd(1.0 / (double)m); - - c2 = mul_mod_fast(c1, c1, m, m_inv1); - c3 = mul_mod_fast(c2, c1, m, m_inv1); - c4 = mul_mod_fast(c2, c2, m, m_inv1); - c = _mm256_setr_pd(1, int_to_ntt_limb(c1, m), - int_to_ntt_limb(c2, m), int_to_ntt_limb(c3, m)); - c_mul = _mm256_set1_pd(int_to_ntt_limb(c4, m)); - for(i = 0; i < n; i += 4) { - a0 = _mm256_load_pd(&bufi); - a0 = ntt_mul_mod(a0, c, mf, m_inv); - _mm256_store_pd(&bufi, a0); - c = ntt_mul_mod(c, c_mul, mf, m_inv); - } -} - -#else - -static void *ntt_malloc(BFNTTState *s, size_t size) -{ - return bf_malloc(s->ctx, size); -} - -static void ntt_free(BFNTTState *s, void *ptr) -{ - bf_free(s->ctx, ptr); -} - -static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m) -{ - if (a >= m) - a -= m; - return a; -} - -static inline NTTLimb int_to_ntt_limb(slimb_t a, limb_t m) -{ - return a; -} - -static no_inline int ntt_fft(BFNTTState *s, NTTLimb *out_buf, NTTLimb *in_buf, - NTTLimb *tmp_buf, int fft_len_log2, - int inverse, int m_idx) -{ - limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j, m, m2; - NTTLimb *tab_in, *tab_out, *tmp, a0, a1, b0, b1, c, *trig, c_inv; - int l; - - m = ntt_modsm_idx; - m2 = 2 * m; - n = (limb_t)1 << fft_len_log2; - nb_blocks = n; - fft_per_block = 1; - stride_in = n / 2; - tab_in = in_buf; - tab_out = tmp_buf; - l = fft_len_log2; - while (nb_blocks != 2) { - nb_blocks >>= 1; - p = 0; - k = 0; - trig = get_trig(s, l, inverse, m_idx); - if (!trig) - return -1; - for(i = 0; i < nb_blocks; i++) { - c = trig0; - c_inv = trig1; - trig += 2; - for(j = 0; j < fft_per_block; j++) { - a0 = tab_ink + j; - a1 = tab_ink + j + stride_in; - b0 = add_mod(a0, a1, m2); - b1 = a0 - a1 + m2; - b1 = mul_mod_fast3(b1, c, m, c_inv); - tab_outp + j = b0; - tab_outp + j + fft_per_block = b1; - } - k += fft_per_block; - p += 2 * fft_per_block; - } - fft_per_block <<= 1; - l--; - tmp = tab_in; - tab_in = tab_out; - tab_out = tmp; - } - /* no twiddle in last step */ - tab_out = out_buf; - for(k = 0; k < stride_in; k++) { - a0 = tab_ink; - a1 = tab_ink + stride_in; - b0 = add_mod(a0, a1, m2); - b1 = sub_mod(a0, a1, m2); - tab_outk = b0; - tab_outk + stride_in = b1; - } - return 0; -} - -static void ntt_vec_mul(BFNTTState *s, - NTTLimb *tab1, NTTLimb *tab2, int fft_len_log2, - int k_tot, int m_idx) -{ - limb_t i, norm, norm_inv, a, n, m, m_inv; - - m = ntt_modsm_idx; - m_inv = s->ntt_mods_divm_idx; - norm = s->ntt_len_invm_idxk_tot0; - norm_inv = s->ntt_len_invm_idxk_tot1; - n = (limb_t)1 << fft_len_log2; - for(i = 0; i < n; i++) { - a = tab1i; - /* need to reduce the range so that the product is < - 2^(LIMB_BITS+NTT_MOD_LOG2_MIN) */ - if (a >= m) - a -= m; - a = mul_mod_fast(a, tab2i, m, m_inv); - a = mul_mod_fast3(a, norm, m, norm_inv); - tab1i = a; - } -} - -static no_inline void mul_trig(NTTLimb *buf, - limb_t n, limb_t c_mul, limb_t m, limb_t m_inv) -{ - limb_t i, c0, c_mul_inv; - - c0 = 1; - c_mul_inv = init_mul_mod_fast2(c_mul, m); - for(i = 0; i < n; i++) { - bufi = mul_mod_fast(bufi, c0, m, m_inv); - c0 = mul_mod_fast2(c0, c_mul, m, c_mul_inv); - } -} - -#endif /* !AVX2 */ - -static no_inline NTTLimb *get_trig(BFNTTState *s, - int k, int inverse, int m_idx) -{ - NTTLimb *tab; - limb_t i, n2, c, c_mul, m, c_mul_inv; - - if (k > NTT_TRIG_K_MAX) - return NULL; - - tab = s->ntt_trigm_idxinversek; - if (tab) - return tab; - n2 = (limb_t)1 << (k - 1); - m = ntt_modsm_idx; -#ifdef __AVX2__ - tab = ntt_malloc(s, sizeof(NTTLimb) * n2); -#else - tab = ntt_malloc(s, sizeof(NTTLimb) * n2 * 2); -#endif - if (!tab) - return NULL; - c = 1; - c_mul = s->ntt_proot_powm_idxinversek; - c_mul_inv = s->ntt_proot_pow_invm_idxinversek; - for(i = 0; i < n2; i++) { -#ifdef __AVX2__ - tabi = int_to_ntt_limb2(c, m); -#else - tab2 * i = int_to_ntt_limb(c, m); - tab2 * i + 1 = init_mul_mod_fast2(c, m); -#endif - c = mul_mod_fast2(c, c_mul, m, c_mul_inv); - } - s->ntt_trigm_idxinversek = tab; - return tab; -} - -void fft_clear_cache(bf_context_t *s1) -{ - int m_idx, inverse, k; - BFNTTState *s = s1->ntt_state; - if (s) { - for(m_idx = 0; m_idx < NB_MODS; m_idx++) { - for(inverse = 0; inverse < 2; inverse++) { - for(k = 0; k < NTT_TRIG_K_MAX + 1; k++) { - if (s->ntt_trigm_idxinversek) { - ntt_free(s, s->ntt_trigm_idxinversek); - s->ntt_trigm_idxinversek = NULL; - } - } - } - } -#if defined(__AVX2__) - bf_aligned_free(s1, s); -#else - bf_free(s1, s); -#endif - s1->ntt_state = NULL; - } -} - -#define STRIP_LEN 16 - -/* dst = buf1, src = buf2 */ -static int ntt_fft_partial(BFNTTState *s, NTTLimb *buf1, - int k1, int k2, limb_t n1, limb_t n2, int inverse, - limb_t m_idx) -{ - limb_t i, j, c_mul, c0, m, m_inv, strip_len, l; - NTTLimb *buf2, *buf3; - - buf2 = NULL; - buf3 = ntt_malloc(s, sizeof(NTTLimb) * n1); - if (!buf3) - goto fail; - if (k2 == 0) { - if (ntt_fft(s, buf1, buf1, buf3, k1, inverse, m_idx)) - goto fail; - } else { - strip_len = STRIP_LEN; - buf2 = ntt_malloc(s, sizeof(NTTLimb) * n1 * strip_len); - if (!buf2) - goto fail; - m = ntt_modsm_idx; - m_inv = s->ntt_mods_divm_idx; - c0 = s->ntt_proot_powm_idxinversek1 + k2; - c_mul = 1; - assert((n2 % strip_len) == 0); - for(j = 0; j < n2; j += strip_len) { - for(i = 0; i < n1; i++) { - for(l = 0; l < strip_len; l++) { - buf2i + l * n1 = buf1i * n2 + (j + l); - } - } - for(l = 0; l < strip_len; l++) { - if (inverse) - mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv); - if (ntt_fft(s, buf2 + l * n1, buf2 + l * n1, buf3, k1, inverse, m_idx)) - goto fail; - if (!inverse) - mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv); - c_mul = mul_mod_fast(c_mul, c0, m, m_inv); - } - - for(i = 0; i < n1; i++) { - for(l = 0; l < strip_len; l++) { - buf1i * n2 + (j + l) = buf2i + l *n1; - } - } - } - ntt_free(s, buf2); - } - ntt_free(s, buf3); - return 0; - fail: - ntt_free(s, buf2); - ntt_free(s, buf3); - return -1; -} - - -/* dst = buf1, src = buf2, tmp = buf3 */ -static int ntt_conv(BFNTTState *s, NTTLimb *buf1, NTTLimb *buf2, - int k, int k_tot, limb_t m_idx) -{ - limb_t n1, n2, i; - int k1, k2; - - if (k <= NTT_TRIG_K_MAX) { - k1 = k; - } else { - /* recursive split of the FFT */ - k1 = bf_min(k / 2, NTT_TRIG_K_MAX); - } - k2 = k - k1; - n1 = (limb_t)1 << k1; - n2 = (limb_t)1 << k2; - - if (ntt_fft_partial(s, buf1, k1, k2, n1, n2, 0, m_idx)) - return -1; - if (ntt_fft_partial(s, buf2, k1, k2, n1, n2, 0, m_idx)) - return -1; - if (k2 == 0) { - ntt_vec_mul(s, buf1, buf2, k, k_tot, m_idx); - } else { - for(i = 0; i < n1; i++) { - ntt_conv(s, buf1 + i * n2, buf2 + i * n2, k2, k_tot, m_idx); - } - } - if (ntt_fft_partial(s, buf1, k1, k2, n1, n2, 1, m_idx)) - return -1; - return 0; -} - - -static no_inline void limb_to_ntt(BFNTTState *s, - NTTLimb *tabr, limb_t fft_len, - const limb_t *taba, limb_t a_len, int dpl, - int first_m_idx, int nb_mods) -{ - slimb_t i, n; - dlimb_t a, b; - int j, shift; - limb_t base_mask1, a0, a1, a2, r, m, m_inv; - -#if 0 - for(i = 0; i < a_len; i++) { - printf("%" PRId64 ": " FMT_LIMB "\n", - (int64_t)i, tabai); - } -#endif - memset(tabr, 0, sizeof(NTTLimb) * fft_len * nb_mods); - shift = dpl & (LIMB_BITS - 1); - if (shift == 0) - base_mask1 = -1; - else - base_mask1 = ((limb_t)1 << shift) - 1; - n = bf_min(fft_len, (a_len * LIMB_BITS + dpl - 1) / dpl); - for(i = 0; i < n; i++) { - a0 = get_bits(taba, a_len, i * dpl); - if (dpl <= LIMB_BITS) { - a0 &= base_mask1; - a = a0; - } else { - a1 = get_bits(taba, a_len, i * dpl + LIMB_BITS); - if (dpl <= (LIMB_BITS + NTT_MOD_LOG2_MIN)) { - a = a0 | ((dlimb_t)(a1 & base_mask1) << LIMB_BITS); - } else { - if (dpl > 2 * LIMB_BITS) { - a2 = get_bits(taba, a_len, i * dpl + LIMB_BITS * 2) & - base_mask1; - } else { - a1 &= base_mask1; - a2 = 0; - } - // printf("a=0x%016lx%016lx%016lx\n", a2, a1, a0); - a = (a0 >> (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | - ((dlimb_t)a1 << (NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)) | - ((dlimb_t)a2 << (LIMB_BITS + NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)); - a0 &= ((limb_t)1 << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) - 1; - } - } - for(j = 0; j < nb_mods; j++) { - m = ntt_modsfirst_m_idx + j; - m_inv = s->ntt_mods_divfirst_m_idx + j; - r = mod_fast(a, m, m_inv); - if (dpl > (LIMB_BITS + NTT_MOD_LOG2_MIN)) { - b = ((dlimb_t)r << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | a0; - r = mod_fast(b, m, m_inv); - } - tabri + j * fft_len = int_to_ntt_limb(r, m); - } - } -} - -#if defined(__AVX2__) - -#define VEC_LEN 4 - -typedef union { - __m256d v; - double d4; -} VecUnion; - -static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len, - const NTTLimb *buf, int fft_len_log2, int dpl, - int nb_mods) -{ - const limb_t *mods = ntt_mods + NB_MODS - nb_mods; - const __m256d *mods_cr_vec, *mf, *m_inv; - VecUnion yNB_MODS; - limb_t uNB_MODS, carryNB_MODS, fft_len, base_mask1, r; - slimb_t i, len, pos; - int j, k, l, shift, n_limb1, p; - dlimb_t t; - - j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2; - mods_cr_vec = s->ntt_mods_cr_vec + j; - mf = s->ntt_mods_vec + NB_MODS - nb_mods; - m_inv = s->ntt_mods_inv_vec + NB_MODS - nb_mods; - - shift = dpl & (LIMB_BITS - 1); - if (shift == 0) - base_mask1 = -1; - else - base_mask1 = ((limb_t)1 << shift) - 1; - n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS; - for(j = 0; j < NB_MODS; j++) - carryj = 0; - for(j = 0; j < NB_MODS; j++) - uj = 0; /* avoid warnings */ - memset(tabr, 0, sizeof(limb_t) * r_len); - fft_len = (limb_t)1 << fft_len_log2; - len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl); - len = (len + VEC_LEN - 1) & ~(VEC_LEN - 1); - i = 0; - while (i < len) { - for(j = 0; j < nb_mods; j++) - yj.v = *(__m256d *)&bufi + fft_len * j; - - /* Chinese remainder to get mixed radix representation */ - l = 0; - for(j = 0; j < nb_mods - 1; j++) { - yj.v = ntt_mod1(yj.v, mfj); - for(k = j + 1; k < nb_mods; k++) { - yk.v = ntt_mul_mod(yk.v - yj.v, - mods_cr_vecl, mfk, m_invk); - l++; - } - } - yj.v = ntt_mod1(yj.v, mfj); - - for(p = 0; p < VEC_LEN; p++) { - /* back to normal representation */ - u0 = (int64_t)ynb_mods - 1.dp; - l = 1; - for(j = nb_mods - 2; j >= 1; j--) { - r = (int64_t)yj.dp; - for(k = 0; k < l; k++) { - t = (dlimb_t)uk * modsj + r; - r = t >> LIMB_BITS; - uk = t; - } - ul = r; - l++; - } - /* XXX: for nb_mods = 5, l should be 4 */ - - /* last step adds the carry */ - r = (int64_t)y0.dp; - for(k = 0; k < l; k++) { - t = (dlimb_t)uk * modsj + r + carryk; - r = t >> LIMB_BITS; - uk = t; - } - ul = r + carryl; - -#if 0 - printf("%" PRId64 ": ", i); - for(j = nb_mods - 1; j >= 0; j--) { - printf(" %019" PRIu64, uj); - } - printf("\n"); -#endif - - /* write the digits */ - pos = i * dpl; - for(j = 0; j < n_limb1; j++) { - put_bits(tabr, r_len, pos, uj); - pos += LIMB_BITS; - } - put_bits(tabr, r_len, pos, un_limb1 & base_mask1); - /* shift by dpl digits and set the carry */ - if (shift == 0) { - for(j = n_limb1 + 1; j < nb_mods; j++) - carryj - (n_limb1 + 1) = uj; - } else { - for(j = n_limb1; j < nb_mods - 1; j++) { - carryj - n_limb1 = (uj >> shift) | - (uj + 1 << (LIMB_BITS - shift)); - } - carrynb_mods - 1 - n_limb1 = unb_mods - 1 >> shift; - } - i++; - } - } -} -#else -static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len, - const NTTLimb *buf, int fft_len_log2, int dpl, - int nb_mods) -{ - const limb_t *mods = ntt_mods + NB_MODS - nb_mods; - const limb_t *mods_cr, *mods_cr_inv; - limb_t yNB_MODS, uNB_MODS, carryNB_MODS, fft_len, base_mask1, r; - slimb_t i, len, pos; - int j, k, l, shift, n_limb1; - dlimb_t t; - - j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2; - mods_cr = ntt_mods_cr + j; - mods_cr_inv = s->ntt_mods_cr_inv + j; - - shift = dpl & (LIMB_BITS - 1); - if (shift == 0) - base_mask1 = -1; - else - base_mask1 = ((limb_t)1 << shift) - 1; - n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS; - for(j = 0; j < NB_MODS; j++) - carryj = 0; - for(j = 0; j < NB_MODS; j++) - uj = 0; /* avoid warnings */ - memset(tabr, 0, sizeof(limb_t) * r_len); - fft_len = (limb_t)1 << fft_len_log2; - len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl); - for(i = 0; i < len; i++) { - for(j = 0; j < nb_mods; j++) { - yj = ntt_limb_to_int(bufi + fft_len * j, modsj); - } - - /* Chinese remainder to get mixed radix representation */ - l = 0; - for(j = 0; j < nb_mods - 1; j++) { - for(k = j + 1; k < nb_mods; k++) { - limb_t m; - m = modsk; - /* Note: there is no overflow in the sub_mod() because - the modulos are sorted by increasing order */ - yk = mul_mod_fast2(yk - yj + m, - mods_crl, m, mods_cr_invl); - l++; - } - } - - /* back to normal representation */ - u0 = ynb_mods - 1; - l = 1; - for(j = nb_mods - 2; j >= 1; j--) { - r = yj; - for(k = 0; k < l; k++) { - t = (dlimb_t)uk * modsj + r; - r = t >> LIMB_BITS; - uk = t; - } - ul = r; - l++; - } - - /* last step adds the carry */ - r = y0; - for(k = 0; k < l; k++) { - t = (dlimb_t)uk * modsj + r + carryk; - r = t >> LIMB_BITS; - uk = t; - } - ul = r + carryl; - -#if 0 - printf("%" PRId64 ": ", (int64_t)i); - for(j = nb_mods - 1; j >= 0; j--) { - printf(" " FMT_LIMB, uj); - } - printf("\n"); -#endif - - /* write the digits */ - pos = i * dpl; - for(j = 0; j < n_limb1; j++) { - put_bits(tabr, r_len, pos, uj); - pos += LIMB_BITS; - } - put_bits(tabr, r_len, pos, un_limb1 & base_mask1); - /* shift by dpl digits and set the carry */ - if (shift == 0) { - for(j = n_limb1 + 1; j < nb_mods; j++) - carryj - (n_limb1 + 1) = uj; - } else { - for(j = n_limb1; j < nb_mods - 1; j++) { - carryj - n_limb1 = (uj >> shift) | - (uj + 1 << (LIMB_BITS - shift)); - } - carrynb_mods - 1 - n_limb1 = unb_mods - 1 >> shift; - } - } -} -#endif - -static int ntt_static_init(bf_context_t *s1) -{ - BFNTTState *s; - int inverse, i, j, k, l; - limb_t c, c_inv, c_inv2, m, m_inv; - - if (s1->ntt_state) - return 0; -#if defined(__AVX2__) - s = bf_aligned_malloc(s1, sizeof(*s), 64); -#else - s = bf_malloc(s1, sizeof(*s)); -#endif - if (!s) - return -1; - memset(s, 0, sizeof(*s)); - s1->ntt_state = s; - s->ctx = s1; - - for(j = 0; j < NB_MODS; j++) { - m = ntt_modsj; - m_inv = init_mul_mod_fast(m); - s->ntt_mods_divj = m_inv; -#if defined(__AVX2__) - s->ntt_mods_vecj = _mm256_set1_pd(m); - s->ntt_mods_inv_vecj = _mm256_set1_pd(1.0 / (double)m); -#endif - c_inv2 = (m + 1) / 2; /* 1/2 */ - c_inv = 1; - for(i = 0; i <= NTT_PROOT_2EXP; i++) { - s->ntt_len_invji0 = c_inv; - s->ntt_len_invji1 = init_mul_mod_fast2(c_inv, m); - c_inv = mul_mod_fast(c_inv, c_inv2, m, m_inv); - } - - for(inverse = 0; inverse < 2; inverse++) { - c = ntt_prootinversej; - for(i = 0; i < NTT_PROOT_2EXP; i++) { - s->ntt_proot_powjinverseNTT_PROOT_2EXP - i = c; - s->ntt_proot_pow_invjinverseNTT_PROOT_2EXP - i = - init_mul_mod_fast2(c, m); - c = mul_mod_fast(c, c, m, m_inv); - } - } - } - - l = 0; - for(j = 0; j < NB_MODS - 1; j++) { - for(k = j + 1; k < NB_MODS; k++) { -#if defined(__AVX2__) - s->ntt_mods_cr_vecl = _mm256_set1_pd(int_to_ntt_limb2(ntt_mods_crl, - ntt_modsk)); -#else - s->ntt_mods_cr_invl = init_mul_mod_fast2(ntt_mods_crl, - ntt_modsk); -#endif - l++; - } - } - return 0; -} - -int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len) -{ - int dpl, fft_len_log2, n_bits, nb_mods, dpl_found, fft_len_log2_found; - int int_bits, nb_mods_found; - limb_t cost, min_cost; - - min_cost = -1; - dpl_found = 0; - nb_mods_found = 4; - fft_len_log2_found = 0; - for(nb_mods = 3; nb_mods <= NB_MODS; nb_mods++) { - int_bits = ntt_int_bitsNB_MODS - nb_mods; - dpl = bf_min((int_bits - 4) / 2, - 2 * LIMB_BITS + 2 * NTT_MOD_LOG2_MIN - NTT_MOD_LOG2_MAX); - for(;;) { - fft_len_log2 = ceil_log2((len * LIMB_BITS + dpl - 1) / dpl); - if (fft_len_log2 > NTT_PROOT_2EXP) - goto next; - n_bits = fft_len_log2 + 2 * dpl; - if (n_bits <= int_bits) { - cost = ((limb_t)(fft_len_log2 + 1) << fft_len_log2) * nb_mods; - // printf("n=%d dpl=%d: cost=%" PRId64 "\n", nb_mods, dpl, (int64_t)cost); - if (cost < min_cost) { - min_cost = cost; - dpl_found = dpl; - nb_mods_found = nb_mods; - fft_len_log2_found = fft_len_log2; - } - break; - } - dpl--; - if (dpl == 0) - break; - } - next: ; - } - if (!dpl_found) - abort(); - /* limit dpl if possible to reduce fixed cost of limb/NTT conversion */ - if (dpl_found > (LIMB_BITS + NTT_MOD_LOG2_MIN) && - ((limb_t)(LIMB_BITS + NTT_MOD_LOG2_MIN) << fft_len_log2_found) >= - len * LIMB_BITS) { - dpl_found = LIMB_BITS + NTT_MOD_LOG2_MIN; - } - *pnb_mods = nb_mods_found; - *pdpl = dpl_found; - return fft_len_log2_found; -} - -/* return 0 if OK, -1 if memory error */ -static no_inline int fft_mul(bf_context_t *s1, - bf_t *res, limb_t *a_tab, limb_t a_len, - limb_t *b_tab, limb_t b_len, int mul_flags) -{ - BFNTTState *s; - int dpl, fft_len_log2, j, nb_mods, reduced_mem; - slimb_t len, fft_len; - NTTLimb *buf1, *buf2, *ptr; -#if defined(USE_MUL_CHECK) - limb_t ha, hb, hr, h_ref; -#endif - - if (ntt_static_init(s1)) - return -1; - s = s1->ntt_state; - - /* find the optimal number of digits per limb (dpl) */ - len = a_len + b_len; - fft_len_log2 = bf_get_fft_size(&dpl, &nb_mods, len); - fft_len = (uint64_t)1 << fft_len_log2; - // printf("len=%" PRId64 " fft_len_log2=%d dpl=%d\n", len, fft_len_log2, dpl); -#if defined(USE_MUL_CHECK) - ha = mp_mod1(a_tab, a_len, BF_CHKSUM_MOD, 0); - hb = mp_mod1(b_tab, b_len, BF_CHKSUM_MOD, 0); -#endif - if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == 0) { - if (!(mul_flags & FFT_MUL_R_NORESIZE)) - bf_resize(res, 0); - } else if (mul_flags & FFT_MUL_R_OVERLAP_B) { - limb_t *tmp_tab, tmp_len; - /* it is better to free 'b' first */ - tmp_tab = a_tab; - a_tab = b_tab; - b_tab = tmp_tab; - tmp_len = a_len; - a_len = b_len; - b_len = tmp_len; - } - buf1 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods); - if (!buf1) - return -1; - limb_to_ntt(s, buf1, fft_len, a_tab, a_len, dpl, - NB_MODS - nb_mods, nb_mods); - if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == - FFT_MUL_R_OVERLAP_A) { - if (!(mul_flags & FFT_MUL_R_NORESIZE)) - bf_resize(res, 0); - } - reduced_mem = (fft_len_log2 >= 14); - if (!reduced_mem) { - buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods); - if (!buf2) - goto fail; - limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl, - NB_MODS - nb_mods, nb_mods); - if (!(mul_flags & FFT_MUL_R_NORESIZE)) - bf_resize(res, 0); /* in case res == b */ - } else { - buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len); - if (!buf2) - goto fail; - } - for(j = 0; j < nb_mods; j++) { - if (reduced_mem) { - limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl, - NB_MODS - nb_mods + j, 1); - ptr = buf2; - } else { - ptr = buf2 + fft_len * j; - } - if (ntt_conv(s, buf1 + fft_len * j, ptr, - fft_len_log2, fft_len_log2, j + NB_MODS - nb_mods)) - goto fail; - } - if (!(mul_flags & FFT_MUL_R_NORESIZE)) - bf_resize(res, 0); /* in case res == b and reduced mem */ - ntt_free(s, buf2); - buf2 = NULL; - if (!(mul_flags & FFT_MUL_R_NORESIZE)) { - if (bf_resize(res, len)) - goto fail; - } - ntt_to_limb(s, res->tab, len, buf1, fft_len_log2, dpl, nb_mods); - ntt_free(s, buf1); -#if defined(USE_MUL_CHECK) - hr = mp_mod1(res->tab, len, BF_CHKSUM_MOD, 0); - h_ref = mul_mod(ha, hb, BF_CHKSUM_MOD); - if (hr != h_ref) { - printf("ntt_mul_error: len=%" PRId_LIMB " fft_len_log2=%d dpl=%d nb_mods=%d\n", - len, fft_len_log2, dpl, nb_mods); - // printf("ha=0x" FMT_LIMB" hb=0x" FMT_LIMB " hr=0x" FMT_LIMB " expected=0x" FMT_LIMB "\n", ha, hb, hr, h_ref); - exit(1); - } -#endif - return 0; - fail: - ntt_free(s, buf1); - ntt_free(s, buf2); - return -1; -} - -#else /* USE_FFT_MUL */ - -int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len) -{ - return 0; -} - -#endif /* !USE_FFT_MUL */
View file
gpac-2.4.0.tar.gz/src/quickjs/libbf.h
Deleted
@@ -1,535 +0,0 @@ -/* - * Tiny arbitrary precision floating point library - * - * Copyright (c) 2017-2021 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIBBF_H -#define LIBBF_H - -#include <stddef.h> -#include <stdint.h> - -#if (INTPTR_MAX >= INT64_MAX) && !defined(_MSC_VER) -#define LIMB_LOG2_BITS 6 -#else -#define LIMB_LOG2_BITS 5 -#endif - -#define LIMB_BITS (1 << LIMB_LOG2_BITS) - -#if LIMB_BITS == 64 -typedef __int128 int128_t; -typedef unsigned __int128 uint128_t; -typedef int64_t slimb_t; -typedef uint64_t limb_t; -typedef uint128_t dlimb_t; -#define BF_RAW_EXP_MIN INT64_MIN -#define BF_RAW_EXP_MAX INT64_MAX - -#define LIMB_DIGITS 19 -#define BF_DEC_BASE UINT64_C(10000000000000000000) - -#else - -typedef int32_t slimb_t; -typedef uint32_t limb_t; -typedef uint64_t dlimb_t; -#define BF_RAW_EXP_MIN INT32_MIN -#define BF_RAW_EXP_MAX INT32_MAX - -#define LIMB_DIGITS 9 -#define BF_DEC_BASE 1000000000U - -#endif - -/* in bits */ -/* minimum number of bits for the exponent */ -#define BF_EXP_BITS_MIN 3 -/* maximum number of bits for the exponent */ -#define BF_EXP_BITS_MAX (LIMB_BITS - 3) -/* extended range for exponent, used internally */ -#define BF_EXT_EXP_BITS_MAX (BF_EXP_BITS_MAX + 1) -/* minimum possible precision */ -#define BF_PREC_MIN 2 -/* minimum possible precision */ -#define BF_PREC_MAX (((limb_t)1 << (LIMB_BITS - 2)) - 2) -/* some operations support infinite precision */ -#define BF_PREC_INF (BF_PREC_MAX + 1) /* infinite precision */ - -#if LIMB_BITS == 64 -#define BF_CHKSUM_MOD (UINT64_C(975620677) * UINT64_C(9795002197)) -#else -#define BF_CHKSUM_MOD 975620677U -#endif - -#define BF_EXP_ZERO BF_RAW_EXP_MIN -#define BF_EXP_INF (BF_RAW_EXP_MAX - 1) -#define BF_EXP_NAN BF_RAW_EXP_MAX - -/* +/-zero is represented with expn = BF_EXP_ZERO and len = 0, - +/-infinity is represented with expn = BF_EXP_INF and len = 0, - NaN is represented with expn = BF_EXP_NAN and len = 0 (sign is ignored) - */ -typedef struct { - struct bf_context_t *ctx; - int sign; - slimb_t expn; - limb_t len; - limb_t *tab; -} bf_t; - -typedef struct { - /* must be kept identical to bf_t */ - struct bf_context_t *ctx; - int sign; - slimb_t expn; - limb_t len; - limb_t *tab; -} bfdec_t; - -typedef enum { - BF_RNDN, /* round to nearest, ties to even */ - BF_RNDZ, /* round to zero */ - BF_RNDD, /* round to -inf (the code relies on (BF_RNDD xor BF_RNDU) = 1) */ - BF_RNDU, /* round to +inf */ - BF_RNDNA, /* round to nearest, ties away from zero */ - BF_RNDA, /* round away from zero */ - BF_RNDF, /* faithful rounding (nondeterministic, either RNDD or RNDU, - inexact flag is always set) */ -} bf_rnd_t; - -/* allow subnormal numbers. Only available if the number of exponent - bits is <= BF_EXP_BITS_USER_MAX and prec != BF_PREC_INF. */ -#define BF_FLAG_SUBNORMAL (1 << 3) -/* 'prec' is the precision after the radix point instead of the whole - mantissa. Can only be used with bf_round() and - bfdec_add|sub|mul|div|sqrt|round(). */ -#define BF_FLAG_RADPNT_PREC (1 << 4) - -#define BF_RND_MASK 0x7 -#define BF_EXP_BITS_SHIFT 5 -#define BF_EXP_BITS_MASK 0x3f - -/* shortcut for bf_set_exp_bits(BF_EXT_EXP_BITS_MAX) */ -#define BF_FLAG_EXT_EXP (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT) - -/* contains the rounding mode and number of exponents bits */ -typedef uint32_t bf_flags_t; - -typedef void *bf_realloc_func_t(void *opaque, void *ptr, size_t size); - -typedef struct { - bf_t val; - limb_t prec; -} BFConstCache; - -typedef struct bf_context_t { - void *realloc_opaque; - bf_realloc_func_t *realloc_func; - BFConstCache log2_cache; - BFConstCache pi_cache; - struct BFNTTState *ntt_state; -} bf_context_t; - -static inline int bf_get_exp_bits(bf_flags_t flags) -{ - int e; - e = (flags >> BF_EXP_BITS_SHIFT) & BF_EXP_BITS_MASK; - if (e == BF_EXP_BITS_MASK) - return BF_EXP_BITS_MAX + 1; - else - return BF_EXP_BITS_MAX - e; -} - -static inline bf_flags_t bf_set_exp_bits(int n) -{ - return ((BF_EXP_BITS_MAX - n) & BF_EXP_BITS_MASK) << BF_EXP_BITS_SHIFT; -} - -/* returned status */ -#define BF_ST_INVALID_OP (1 << 0) -#define BF_ST_DIVIDE_ZERO (1 << 1) -#define BF_ST_OVERFLOW (1 << 2) -#define BF_ST_UNDERFLOW (1 << 3) -#define BF_ST_INEXACT (1 << 4) -/* indicate that a memory allocation error occured. NaN is returned */ -#define BF_ST_MEM_ERROR (1 << 5) - -#define BF_RADIX_MAX 36 /* maximum radix for bf_atof() and bf_ftoa() */ - -static inline slimb_t bf_max(slimb_t a, slimb_t b) -{ - if (a > b) - return a; - else - return b; -} - -static inline slimb_t bf_min(slimb_t a, slimb_t b) -{ - if (a < b) - return a; - else - return b; -} - -void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, - void *realloc_opaque); -void bf_context_end(bf_context_t *s); -/* free memory allocated for the bf cache data */ -void bf_clear_cache(bf_context_t *s); - -static inline void *bf_realloc(bf_context_t *s, void *ptr, size_t size) -{ - return s->realloc_func(s->realloc_opaque, ptr, size); -} - -/* 'size' must be != 0 */ -static inline void *bf_malloc(bf_context_t *s, size_t size) -{ - return bf_realloc(s, NULL, size); -} - -static inline void bf_free(bf_context_t *s, void *ptr) -{ - /* must test ptr otherwise equivalent to malloc(0) */ - if (ptr) - bf_realloc(s, ptr, 0); -} - -void bf_init(bf_context_t *s, bf_t *r); - -static inline void bf_delete(bf_t *r) -{ - bf_context_t *s = r->ctx; - /* we accept to delete a zeroed bf_t structure */ - if (s && r->tab) { - bf_realloc(s, r->tab, 0); - } -} - -static inline void bf_neg(bf_t *r) -{ - r->sign ^= 1; -} - -static inline int bf_is_finite(const bf_t *a) -{ - return (a->expn < BF_EXP_INF); -} - -static inline int bf_is_nan(const bf_t *a) -{ - return (a->expn == BF_EXP_NAN); -} - -static inline int bf_is_zero(const bf_t *a) -{ - return (a->expn == BF_EXP_ZERO); -} - -static inline void bf_memcpy(bf_t *r, const bf_t *a) -{ - *r = *a; -} - -int bf_set_ui(bf_t *r, uint64_t a); -int bf_set_si(bf_t *r, int64_t a); -void bf_set_nan(bf_t *r); -void bf_set_zero(bf_t *r, int is_neg); -void bf_set_inf(bf_t *r, int is_neg); -int bf_set(bf_t *r, const bf_t *a); -void bf_move(bf_t *r, bf_t *a); -int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode); -int bf_set_float64(bf_t *a, double d); - -int bf_cmpu(const bf_t *a, const bf_t *b); -int bf_cmp_full(const bf_t *a, const bf_t *b); -int bf_cmp(const bf_t *a, const bf_t *b); -static inline int bf_cmp_eq(const bf_t *a, const bf_t *b) -{ - return bf_cmp(a, b) == 0; -} - -static inline int bf_cmp_le(const bf_t *a, const bf_t *b) -{ - return bf_cmp(a, b) <= 0; -} - -static inline int bf_cmp_lt(const bf_t *a, const bf_t *b) -{ - return bf_cmp(a, b) < 0; -} - -int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); -int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); -int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, bf_flags_t flags); -int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); -int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, bf_flags_t flags); -int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, - bf_flags_t flags); -int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags); -int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); -#define BF_DIVREM_EUCLIDIAN BF_RNDF -int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, - limb_t prec, bf_flags_t flags, int rnd_mode); -int bf_rem(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags, int rnd_mode); -int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, - bf_flags_t flags, int rnd_mode); -/* round to integer with infinite precision */ -int bf_rint(bf_t *r, int rnd_mode); -int bf_round(bf_t *r, limb_t prec, bf_flags_t flags); -int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a); -int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -slimb_t bf_get_exp_min(const bf_t *a); -int bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b); -int bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b); -int bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b); - -/* additional flags for bf_atof */ -/* do not accept hex radix prefix (0x or 0X) if radix = 0 or radix = 16 */ -#define BF_ATOF_NO_HEX (1 << 16) -/* accept binary (0b or 0B) or octal (0o or 0O) radix prefix if radix = 0 */ -#define BF_ATOF_BIN_OCT (1 << 17) -/* Do not parse NaN or Inf */ -#define BF_ATOF_NO_NAN_INF (1 << 18) -/* return the exponent separately */ -#define BF_ATOF_EXPONENT (1 << 19) - -int bf_atof(bf_t *a, const char *str, const char **pnext, int radix, - limb_t prec, bf_flags_t flags); -/* this version accepts prec = BF_PREC_INF and returns the radix - exponent */ -int bf_atof2(bf_t *r, slimb_t *pexponent, - const char *str, const char **pnext, int radix, - limb_t prec, bf_flags_t flags); -int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, - slimb_t expn, limb_t prec, bf_flags_t flags); - - -/* Conversion of floating point number to string. Return a null - terminated string or NULL if memory error. *plen contains its - length if plen != NULL. The exponent letter is "e" for base 10, - "p" for bases 2, 8, 16 with a binary exponent and "@" for the other - bases. */ - -#define BF_FTOA_FORMAT_MASK (3 << 16) - -/* fixed format: prec significant digits rounded with (flags & - BF_RND_MASK). Exponential notation is used if too many zeros are - needed.*/ -#define BF_FTOA_FORMAT_FIXED (0 << 16) -/* fractional format: prec digits after the decimal point rounded with - (flags & BF_RND_MASK) */ -#define BF_FTOA_FORMAT_FRAC (1 << 16) -/* free format: - - For binary radices with bf_ftoa() and for bfdec_ftoa(): use the minimum - number of digits to represent 'a'. The precision and the rounding - mode are ignored. - - For the non binary radices with bf_ftoa(): use as many digits as - necessary so that bf_atof() return the same number when using - precision 'prec', rounding to nearest and the subnormal - configuration of 'flags'. The result is meaningful only if 'a' is - already rounded to 'prec' bits. If the subnormal flag is set, the - exponent in 'flags' must also be set to the desired exponent range. -*/ -#define BF_FTOA_FORMAT_FREE (2 << 16) -/* same as BF_FTOA_FORMAT_FREE but uses the minimum number of digits - (takes more computation time). Identical to BF_FTOA_FORMAT_FREE for - binary radices with bf_ftoa() and for bfdec_ftoa(). */ -#define BF_FTOA_FORMAT_FREE_MIN (3 << 16) - -/* force exponential notation for fixed or free format */ -#define BF_FTOA_FORCE_EXP (1 << 20) -/* add 0x prefix for base 16, 0o prefix for base 8 or 0b prefix for - base 2 if non zero value */ -#define BF_FTOA_ADD_PREFIX (1 << 21) -/* return "Infinity" instead of "Inf" and add a "+" for positive - exponents */ -#define BF_FTOA_JS_QUIRKS (1 << 22) - -char *bf_ftoa(size_t *plen, const bf_t *a, int radix, limb_t prec, - bf_flags_t flags); - -/* modulo 2^n instead of saturation. NaN and infinity return 0 */ -#define BF_GET_INT_MOD (1 << 0) -int bf_get_int32(int *pres, const bf_t *a, int flags); -int bf_get_int64(int64_t *pres, const bf_t *a, int flags); -int bf_get_uint64(uint64_t *pres, const bf_t *a); - -/* the following functions are exported for testing only. */ -void mp_print_str(const char *str, const limb_t *tab, limb_t n); -void bf_print_str(const char *str, const bf_t *a); -int bf_resize(bf_t *r, limb_t len); -int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len); -int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags); -int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k); -slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, - int is_ceil1); -int mp_mul(bf_context_t *s, limb_t *result, - const limb_t *op1, limb_t op1_size, - const limb_t *op2, limb_t op2_size); -limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, - limb_t n, limb_t carry); -limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n); -int mp_sqrtrem(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n); -int mp_recip(bf_context_t *s, limb_t *tabr, const limb_t *taba, limb_t n); -limb_t bf_isqrt(limb_t a); - -/* transcendental functions */ -int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags); -int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags); -int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -#define BF_POW_JS_QUIRKS (1 << 16) /* (+/-1)^(+/-Inf) = NaN, 1^NaN = NaN */ -int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags); -int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, - limb_t prec, bf_flags_t flags); -int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); -int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); - -/* decimal floating point */ - -static inline void bfdec_init(bf_context_t *s, bfdec_t *r) -{ - bf_init(s, (bf_t *)r); -} -static inline void bfdec_delete(bfdec_t *r) -{ - bf_delete((bf_t *)r); -} - -static inline void bfdec_neg(bfdec_t *r) -{ - r->sign ^= 1; -} - -static inline int bfdec_is_finite(const bfdec_t *a) -{ - return (a->expn < BF_EXP_INF); -} - -static inline int bfdec_is_nan(const bfdec_t *a) -{ - return (a->expn == BF_EXP_NAN); -} - -static inline int bfdec_is_zero(const bfdec_t *a) -{ - return (a->expn == BF_EXP_ZERO); -} - -static inline void bfdec_memcpy(bfdec_t *r, const bfdec_t *a) -{ - bf_memcpy((bf_t *)r, (const bf_t *)a); -} - -int bfdec_set_ui(bfdec_t *r, uint64_t a); -int bfdec_set_si(bfdec_t *r, int64_t a); - -static inline void bfdec_set_nan(bfdec_t *r) -{ - bf_set_nan((bf_t *)r); -} -static inline void bfdec_set_zero(bfdec_t *r, int is_neg) -{ - bf_set_zero((bf_t *)r, is_neg); -} -static inline void bfdec_set_inf(bfdec_t *r, int is_neg) -{ - bf_set_inf((bf_t *)r, is_neg); -} -static inline int bfdec_set(bfdec_t *r, const bfdec_t *a) -{ - return bf_set((bf_t *)r, (bf_t *)a); -} -static inline void bfdec_move(bfdec_t *r, bfdec_t *a) -{ - bf_move((bf_t *)r, (bf_t *)a); -} -static inline int bfdec_cmpu(const bfdec_t *a, const bfdec_t *b) -{ - return bf_cmpu((const bf_t *)a, (const bf_t *)b); -} -static inline int bfdec_cmp_full(const bfdec_t *a, const bfdec_t *b) -{ - return bf_cmp_full((const bf_t *)a, (const bf_t *)b); -} -static inline int bfdec_cmp(const bfdec_t *a, const bfdec_t *b) -{ - return bf_cmp((const bf_t *)a, (const bf_t *)b); -} -static inline int bfdec_cmp_eq(const bfdec_t *a, const bfdec_t *b) -{ - return bfdec_cmp(a, b) == 0; -} -static inline int bfdec_cmp_le(const bfdec_t *a, const bfdec_t *b) -{ - return bfdec_cmp(a, b) <= 0; -} -static inline int bfdec_cmp_lt(const bfdec_t *a, const bfdec_t *b) -{ - return bfdec_cmp(a, b) < 0; -} - -int bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags); -int bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags); -int bfdec_add_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, - bf_flags_t flags); -int bfdec_mul(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags); -int bfdec_mul_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, - bf_flags_t flags); -int bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags); -int bfdec_divrem(bfdec_t *q, bfdec_t *r, const bfdec_t *a, const bfdec_t *b, - limb_t prec, bf_flags_t flags, int rnd_mode); -int bfdec_rem(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, - bf_flags_t flags, int rnd_mode); -int bfdec_rint(bfdec_t *r, int rnd_mode); -int bfdec_sqrt(bfdec_t *r, const bfdec_t *a, limb_t prec, bf_flags_t flags); -int bfdec_round(bfdec_t *r, limb_t prec, bf_flags_t flags); -int bfdec_get_int32(int *pres, const bfdec_t *a); -int bfdec_pow_ui(bfdec_t *r, const bfdec_t *a, limb_t b); - -char *bfdec_ftoa(size_t *plen, const bfdec_t *a, limb_t prec, bf_flags_t flags); -int bfdec_atof(bfdec_t *r, const char *str, const char **pnext, - limb_t prec, bf_flags_t flags); - -/* the following functions are exported for testing only. */ -extern const limb_t mp_pow_decLIMB_DIGITS + 1; -void bfdec_print_str(const char *str, const bfdec_t *a); -static inline int bfdec_resize(bfdec_t *r, limb_t len) -{ - return bf_resize((bf_t *)r, len); -} -int bfdec_normalize_and_round(bfdec_t *r, limb_t prec1, bf_flags_t flags); - -#endif /* LIBBF_H */
View file
gpac-2.4.0.tar.gz/src/utils/Remotery.c
Deleted
@@ -1,7449 +0,0 @@ -// -// Copyright 2014-2018 Celtoys Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/* -@Contents: - - @DEPS: External Dependencies - @TIMERS: Platform-specific timers - @TLS: Thread-Local Storage - @ATOMIC: Atomic Operations - @VMBUFFER: Mirror Buffer using Virtual Memory for auto-wrap - @NEW: New/Delete operators with error values for simplifying object create/destroy - @THREADS: Threads - @SAFEC: Safe C Library excerpts - @OBJALLOC: Reusable Object Allocator - @DYNBUF: Dynamic Buffer - @HASHTABLE: Integer pair hash map for inserts/finds. No removes for added simplicity. - @STRINGTABLE: Map from string hash to string offset in local buffer - @SOCKETS: Sockets TCP/IP Wrapper - @SHA1: SHA-1 Cryptographic Hash Function - @BASE64: Base-64 encoder - @MURMURHASH: Murmur-Hash 3 - @WEBSOCKETS: WebSockets - @MESSAGEQ: Multiple producer, single consumer message queue - @NETWORK: Network Server - @SAMPLE: Base Sample Description (CPU by default) - @SAMPLETREE: A tree of samples with their allocator - @TSAMPLER: Per-Thread Sampler - @REMOTERY: Remotery - @CUDA: CUDA event sampling - @D3D11: Direct3D 11 event sampling - @OPENGL: OpenGL event sampling - @METAL: Metal event sampling -*/ - -#define RMT_IMPL -#include <gpac/tools.h> - -#if defined(RMT_PLATFORM_WINDOWS) && !defined(__GNUC__) - #pragma comment(lib, "ws2_32.lib") -#endif - - -#if RMT_ENABLED - - -// Global settings -static rmtSettings g_Settings; -static rmtBool g_SettingsInitialized = RMT_FALSE; - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @DEPS: External Dependencies ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -// -// Required CRT dependencies -// -#if RMT_USE_TINYCRT - - #include <TinyCRT/TinyCRT.h> - #include <TinyCRT/TinyWinsock.h> - #include <Memory/Memory.h> - - #define CreateFileMapping CreateFileMappingA - -#else - - #ifdef RMT_PLATFORM_MACOS - #include <mach/mach_time.h> - #include <mach/vm_map.h> - #include <mach/mach.h> - #include <sys/time.h> - #else - #if !defined(__FreeBSD__) && !defined(__OpenBSD__) - #include <malloc.h> - #endif - #endif - - #include <assert.h> - - #ifdef RMT_PLATFORM_WINDOWS - #include <winsock2.h> - #ifndef __MINGW32__ - #include <intrin.h> - #endif - #undef min - #undef max - #ifdef _XBOX_ONE - #include "xmem.h" - #endif - #endif - - #ifdef RMT_PLATFORM_LINUX - #include <time.h> - #if defined(__FreeBSD__) || defined(__OpenBSD__) - #include <pthread_np.h> - #else - #include <sys/prctl.h> - #endif - #endif - - #if defined(RMT_PLATFORM_POSIX) - #include <stdlib.h> - #include <pthread.h> - #include <unistd.h> - #include <string.h> - #include <sys/socket.h> - #include <sys/mman.h> - #include <netinet/in.h> - #include <fcntl.h> - #include <errno.h> - #include <dlfcn.h> - #endif - - #ifdef __MINGW32__ - #include <pthread.h> - #endif - -#endif - -#if defined(_MSC_VER) && !defined(__clang__) - #define RMT_UNREFERENCED_PARAMETER(i) (i) -#else - #define RMT_UNREFERENCED_PARAMETER(i) (void)(1 ? (void)0 : ((void)i)) -#endif - - -#if RMT_USE_CUDA - #include <cuda.h> -#endif - - - -static rmtU8 minU8(rmtU8 a, rmtU8 b) -{ - return a < b ? a : b; -} -static rmtU16 maxU16(rmtU16 a, rmtU16 b) -{ - return a > b ? a : b; -} -static rmtS64 maxS64(rmtS64 a, rmtS64 b) -{ - return a > b ? a : b; -} - - -// Memory management functions -static void* rmtMalloc( rmtU32 size ) -{ - return g_Settings.malloc( g_Settings.mm_context, size ); -} - -static void* rmtRealloc( void* ptr, rmtU32 size) -{ - return g_Settings.realloc( g_Settings.mm_context, ptr, size ); -} - -static void rmtFree( void* ptr ) -{ - g_Settings.free( g_Settings.mm_context, ptr ); -} - -#if RMT_USE_OPENGL -// DLL/Shared Library functions - -static void* rmtLoadLibrary(const char* path) -{ - #if defined(RMT_PLATFORM_WINDOWS) - return (void*)LoadLibraryA(path); - #elif defined(RMT_PLATFORM_POSIX) - return dlopen(path, RTLD_LOCAL | RTLD_LAZY); - #else - return NULL; - #endif -} - -static void rmtFreeLibrary(void* handle) -{ - #if defined(RMT_PLATFORM_WINDOWS) - FreeLibrary((HMODULE)handle); - #elif defined(RMT_PLATFORM_POSIX) - dlclose(handle); - #endif -} - -#if defined(RMT_PLATFORM_WINDOWS) - typedef FARPROC ProcReturnType; -#else - typedef void* ProcReturnType; -#endif - -static ProcReturnType rmtGetProcAddress(void* handle, const char* symbol) -{ - #if defined(RMT_PLATFORM_WINDOWS) - return GetProcAddress((HMODULE)handle, (LPCSTR)symbol); - #elif defined(RMT_PLATFORM_POSIX) - return dlsym(handle, symbol); - #endif -} - - -#endif - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TIMERS: Platform-specific timers ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -// -// Get millisecond timer value that has only one guarantee: multiple calls are consistently comparable. -// On some platforms, even though this returns milliseconds, the timer may be far less accurate. -// -static rmtU32 msTimer_Get() -{ - #ifdef RMT_PLATFORM_WINDOWS - - return (rmtU32)GetTickCount(); - - #else - - clock_t time = clock(); - - // CLOCKS_PER_SEC is 128 on FreeBSD, causing div/0 - #if defined(__FreeBSD__) || defined(__OpenBSD__) - rmtU32 msTime = (rmtU32) (time * 1000 / CLOCKS_PER_SEC); - #else - rmtU32 msTime = (rmtU32) (time / (CLOCKS_PER_SEC / 1000)); - #endif - - return msTime; - - #endif -} - - -// -// Micro-second accuracy high performance counter -// -#ifndef RMT_PLATFORM_WINDOWS - typedef rmtU64 LARGE_INTEGER; -#endif -typedef struct -{ - LARGE_INTEGER counter_start; - double counter_scale; -} usTimer; - - -static void usTimer_Init(usTimer* timer) -{ - #if defined(RMT_PLATFORM_WINDOWS) - LARGE_INTEGER performance_frequency; - - assert(timer != NULL); - - // Calculate the scale from performance counter to microseconds - QueryPerformanceFrequency(&performance_frequency); - timer->counter_scale = 1000000.0 / performance_frequency.QuadPart; - - // Record the offset for each read of the counter - QueryPerformanceCounter(&timer->counter_start); - - #elif defined(RMT_PLATFORM_MACOS) - - mach_timebase_info_data_t nsScale; - mach_timebase_info( &nsScale ); - const double ns_per_us = 1.0e3; - timer->counter_scale = (double)(nsScale.numer) / ((double)nsScale.denom * ns_per_us); - - timer->counter_start = mach_absolute_time(); - - #elif defined(RMT_PLATFORM_LINUX) - - struct timespec tv; - clock_gettime(CLOCK_REALTIME, &tv); - timer->counter_start = (rmtU64)(tv.tv_sec * (rmtU64)1000000) + (rmtU64)(tv.tv_nsec * 0.001); - - #endif -} - - -static rmtU64 usTimer_Get(usTimer* timer) -{ - #if defined(RMT_PLATFORM_WINDOWS) - LARGE_INTEGER performance_count; - - assert(timer != NULL); - - // Read counter and convert to microseconds - QueryPerformanceCounter(&performance_count); - return (rmtU64)((performance_count.QuadPart - timer->counter_start.QuadPart) * timer->counter_scale); - - #elif defined(RMT_PLATFORM_MACOS) - - rmtU64 curr_time = mach_absolute_time(); - return (rmtU64)((curr_time - timer->counter_start) * timer->counter_scale); - - #elif defined(RMT_PLATFORM_LINUX) - - struct timespec tv; - clock_gettime(CLOCK_REALTIME, &tv); - return ((rmtU64)(tv.tv_sec * (rmtU64)1000000) + (rmtU64)(tv.tv_nsec * 0.001)) - timer->counter_start; - - #endif -} - - -static void msSleep(rmtU32 time_ms) -{ - #ifdef RMT_PLATFORM_WINDOWS - Sleep(time_ms); - #elif defined(RMT_PLATFORM_POSIX) - usleep(time_ms * 1000); - #endif -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TLS: Thread-Local Storage ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#define TLS_INVALID_HANDLE 0xFFFFFFFF - -#if defined(RMT_PLATFORM_WINDOWS) - typedef rmtU32 rmtTLS; -#else - typedef pthread_key_t rmtTLS; -#endif - -static rmtError tlsAlloc(rmtTLS* handle) -{ - assert(handle != NULL); - -#if defined(RMT_PLATFORM_WINDOWS) - - *handle = (rmtTLS)TlsAlloc(); - if (*handle == TLS_OUT_OF_INDEXES) - { - *handle = TLS_INVALID_HANDLE; - return RMT_ERROR_TLS_ALLOC_FAIL; - } - -#elif defined(RMT_PLATFORM_POSIX) - - if (pthread_key_create(handle, NULL) != 0) - { - *handle = TLS_INVALID_HANDLE; - return RMT_ERROR_TLS_ALLOC_FAIL; - } - -#endif - - return RMT_ERROR_NONE; -} - - -static void tlsFree(rmtTLS handle) -{ - assert(handle != TLS_INVALID_HANDLE); - -#if defined(RMT_PLATFORM_WINDOWS) - - TlsFree(handle); - -#elif defined(RMT_PLATFORM_POSIX) - - pthread_key_delete((pthread_key_t)handle); - -#endif -} - - -static void tlsSet(rmtTLS handle, void* value) -{ - assert(handle != TLS_INVALID_HANDLE); - -#if defined(RMT_PLATFORM_WINDOWS) - - TlsSetValue(handle, value); - -#elif defined(RMT_PLATFORM_POSIX) - - pthread_setspecific((pthread_key_t)handle, value); - -#endif -} - - -static void* tlsGet(rmtTLS handle) -{ - assert(handle != TLS_INVALID_HANDLE); - -#if defined(RMT_PLATFORM_WINDOWS) - - return TlsGetValue(handle); - -#elif defined(RMT_PLATFORM_POSIX) - - return pthread_getspecific((pthread_key_t)handle); - -#endif -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @ATOMIC: Atomic Operations ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -static rmtBool AtomicCompareAndSwap(rmtU32 volatile* val, long old_val, long new_val) -{ - #if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - return _InterlockedCompareExchange((long volatile*)val, new_val, old_val) == old_val ? RMT_TRUE : RMT_FALSE; - #elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_bool_compare_and_swap(val, (rmtU32) old_val, (rmtU32) new_val) ? RMT_TRUE : RMT_FALSE; - #endif -} - - -static rmtBool AtomicCompareAndSwapPointer(long* volatile* ptr, long* old_ptr, long* new_ptr) -{ - #if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - #ifdef _WIN64 - return _InterlockedCompareExchange64((__int64 volatile*)ptr, (__int64)new_ptr, (__int64)old_ptr) == (__int64)old_ptr ? RMT_TRUE : RMT_FALSE; - #else - return _InterlockedCompareExchange((long volatile*)ptr, (long)new_ptr, (long)old_ptr) == (long)old_ptr ? RMT_TRUE : RMT_FALSE; - #endif - #elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_bool_compare_and_swap(ptr, old_ptr, new_ptr) ? RMT_TRUE : RMT_FALSE; - #endif -} - - -// -// NOTE: Does not guarantee a memory barrier -// TODO: Make sure all platforms don't insert a memory barrier as this is only for stats -// Alternatively, add strong/weak memory order equivalents -// -static rmtS32 AtomicAdd(rmtS32 volatile* value, rmtS32 add) -{ - #if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - return _InterlockedExchangeAdd((long volatile*)value, (long)add); - #elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_fetch_and_add(value, add); - #endif -} - - -static void AtomicSub(rmtS32 volatile* value, rmtS32 sub) -{ - // Not all platforms have an implementation so just negate and add - AtomicAdd(value, -sub); -} - - -static void CompilerWriteFence() -{ -#if defined (__clang__) - __asm__ volatile("" : : : "memory"); -#elif defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - _WriteBarrier(); -#else - asm volatile ("" : : : "memory"); -#endif -} - - -static void CompilerReadFence() -{ -#if defined (__clang__) - __asm__ volatile("" : : : "memory"); -#elif defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - _ReadBarrier(); -#else - asm volatile ("" : : : "memory"); -#endif -} - - -static rmtU32 LoadAcquire(rmtU32* volatile address) -{ - rmtU32 value = *address; - CompilerReadFence(); - return value; -} - - -static long* LoadAcquirePointer(long* volatile* ptr) -{ - long* value = *ptr; - CompilerReadFence(); - return value; -} - - -static void StoreRelease(rmtU32* volatile address, rmtU32 value) -{ - CompilerWriteFence(); - *address = value; -} - - -static void StoreReleasePointer(long* volatile* ptr, long* value) -{ - CompilerWriteFence(); - *ptr = value; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @NEW: New/Delete operators with error values for simplifying object create/destroy ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -// Ensures the pointer is non-NULL, calls the destructor, frees memory and sets the pointer to NULL -#define Delete(type, obj) \ - if (obj != NULL) \ - { \ - type##_Destructor(obj); \ - rmtFree(obj); \ - obj = NULL; \ - } - - -// New is implemented in terms of two begin/end macros -// New will allocate enough space for the object and call the constructor -// If allocation fails the constructor won't be called -// If the constructor fails, the destructor is called and memory is released -// NOTE: Use of sizeof() requires that the type be defined at the point of call -// This is a disadvantage over requiring only a custom Create function -#define BeginNew(type, obj) \ - { \ - obj = (type*)rmtMalloc(sizeof(type)); \ - if (obj == NULL) \ - { \ - error = RMT_ERROR_MALLOC_FAIL; \ - } \ - else \ - { \ - - -#define EndNew(type, obj) \ - if (error != RMT_ERROR_NONE) \ - Delete(type, obj); \ - } \ - } - - -// Specialisations for New with varying constructor parameter counts -#define New_0(type, obj) \ - BeginNew(type, obj); error = type##_Constructor(obj); EndNew(type, obj) -#define New_1(type, obj, a0) \ - BeginNew(type, obj); error = type##_Constructor(obj, a0); EndNew(type, obj) -#define New_2(type, obj, a0, a1) \ - BeginNew(type, obj); error = type##_Constructor(obj, a0, a1); EndNew(type, obj) -#define New_3(type, obj, a0, a1, a2) \ - BeginNew(type, obj); error = type##_Constructor(obj, a0, a1, a2); EndNew(type, obj) - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @VMBUFFER: Mirror Buffer using Virtual Memory for auto-wrap ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -typedef struct VirtualMirrorBuffer -{ - // Page-rounded size of the buffer without mirroring - rmtU32 size; - - // Pointer to the first part of the mirror - // The second part comes directly after at ptr+size bytes - rmtU8* ptr; - -#ifdef RMT_PLATFORM_WINDOWS - #ifdef _XBOX_ONE - size_t page_count; - size_t* page_mapping; - #else - HANDLE file_map_handle; - #endif -#endif - -} VirtualMirrorBuffer; - - -#ifdef __ANDROID__ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <linux/ashmem.h> -#include <fcntl.h> -#include <string.h> -#define ASHMEM_DEVICE "/dev/ashmem" - -/* - * ashmem_create_region - creates a new ashmem region and returns the file - * descriptor, or <0 on error - * - * `name' is an optional label to give the region (visible in /proc/pid/maps) - * `size' is the size of the region, in page-aligned bytes - */ -int ashmem_create_region(const char *name, size_t size) -{ - int fd, ret; - - fd = open(ASHMEM_DEVICE, O_RDWR); - if (fd < 0) - return fd; - - if (name) { - char bufASHMEM_NAME_LEN = {0}; - - strncpy(buf, name, sizeof(buf)); - bufsizeof(buf)-1 = 0; - ret = ioctl(fd, ASHMEM_SET_NAME, buf); - if (ret < 0) - goto error; - } - - ret = ioctl(fd, ASHMEM_SET_SIZE, size); - if (ret < 0) - goto error; - - return fd; - -error: - close(fd); - return ret; -} -#endif // __ANDROID__ - -static rmtError VirtualMirrorBuffer_Constructor(VirtualMirrorBuffer* buffer, rmtU32 size, int nb_attempts) -{ - static const rmtU32 k_64 = 64 * 1024; - RMT_UNREFERENCED_PARAMETER(nb_attempts); - -#ifdef RMT_PLATFORM_LINUX - #if defined(__FreeBSD__) || defined(__OpenBSD__) - char path = "/tmp/ring-buffer-XXXXXX"; - #else - char path = "/dev/shm/ring-buffer-XXXXXX"; - #endif - int file_descriptor; -#endif - - // Round up to page-granulation; the nearest 64k boundary for now - size = (size + k_64 - 1) / k_64 * k_64; - - // Set defaults - buffer->size = size; - buffer->ptr = NULL; -#ifdef RMT_PLATFORM_WINDOWS - #ifdef _XBOX_ONE - buffer->page_count = 0; - buffer->page_mapping = NULL; - #else - buffer->file_map_handle = INVALID_HANDLE_VALUE; - #endif -#endif - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef _XBOX_ONE - - // Xbox version based on Windows version and XDK reference - - buffer->page_count = size / k_64; - if (buffer->page_mapping) - { - free( buffer->page_mapping ); - } - buffer->page_mapping = (size_t*)malloc( sizeof( ULONG )*buffer->page_count ); - - while(nb_attempts-- > 0) - { - rmtU8* desired_addr; - - // Create a page mapping for pointing to its physical address with multiple virtual pages - if (!AllocateTitlePhysicalPages( GetCurrentProcess(), MEM_LARGE_PAGES, &buffer->page_count, buffer->page_mapping )) - { - free( buffer->page_mapping ); - buffer->page_mapping = NULL; - break; - } - - // Reserve two contiguous pages of virtual memory - desired_addr = (rmtU8*)VirtualAlloc( 0, size * 2, MEM_RESERVE, PAGE_NOACCESS ); - if (desired_addr == NULL) - break; - - // Release the range immediately but retain the address for the next sequence of code to - // try and map to it. In the mean-time some other OS thread may come along and allocate this - // address range from underneath us so multiple attempts need to be made. - VirtualFree( desired_addr, 0, MEM_RELEASE ); - - // Immediately try to point both pages at the file mapping - if (MapTitlePhysicalPages( desired_addr, buffer->page_count, MEM_LARGE_PAGES, PAGE_READWRITE, buffer->page_mapping ) == desired_addr && - MapTitlePhysicalPages( desired_addr + size, buffer->page_count, MEM_LARGE_PAGES, PAGE_READWRITE, buffer->page_mapping ) == desired_addr + size) - { - buffer->ptr = desired_addr; - break; - } - - // Failed to map the virtual pages; cleanup and try again - FreeTitlePhysicalPages( GetCurrentProcess(), buffer->page_count, buffer->page_mapping ); - buffer->page_mapping = NULL; - } - -#else - - // Windows version based on https://gist.github.com/rygorous/3158316 - - while (nb_attempts-- > 0) - { - rmtU8* desired_addr; - - // Create a file mapping for pointing to its physical address with multiple virtual pages - buffer->file_map_handle = CreateFileMapping( - INVALID_HANDLE_VALUE, - 0, - PAGE_READWRITE, - 0, - size, - 0); - if (buffer->file_map_handle == NULL) - break; - - -#ifndef _UWP // NON-UWP Windows Desktop Version - - // Reserve two contiguous pages of virtual memory - desired_addr = (rmtU8*)VirtualAlloc(0, size * 2, MEM_RESERVE, PAGE_NOACCESS); - if (desired_addr == NULL) - break; - - // Release the range immediately but retain the address for the next sequence of code to - // try and map to it. In the mean-time some other OS thread may come along and allocate this - // address range from underneath us so multiple attempts need to be made. - VirtualFree(desired_addr, 0, MEM_RELEASE); - - // Immediately try to point both pages at the file mapping - if (MapViewOfFileEx(buffer->file_map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size, desired_addr) == desired_addr && - MapViewOfFileEx(buffer->file_map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size, desired_addr + size) == desired_addr + size) - { - buffer->ptr = desired_addr; - break; - } - -#else // UWP - - // Implementation based on example from: https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc2 - // - // Notes - // - just replaced the non-uwp functions by the uwp variants. - // - Both versions could be rewritten to not need the try-loop, see the example mentioned above. I just keep it as is for now. - // - Successfully tested on Hololens - desired_addr = (rmtU8*) VirtualAlloc2FromApp(NULL, NULL, 2 * size,MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,PAGE_NOACCESS,NULL, 0); - - // Split the placeholder region into two regions of equal size. - VirtualFree(desired_addr, size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); - - // Immediately try to point both pages at the file mapping. - if(MapViewOfFile3FromApp(buffer->file_map_handle,NULL, desired_addr, 0, size, MEM_REPLACE_PLACEHOLDER,PAGE_READWRITE,NULL,0)==desired_addr && - MapViewOfFile3FromApp(buffer->file_map_handle,NULL, desired_addr+size, 0, size, MEM_REPLACE_PLACEHOLDER,PAGE_READWRITE,NULL,0)== desired_addr + size) { - buffer->ptr = desired_addr; - break; - } -#endif - // Failed to map the virtual pages; cleanup and try again - CloseHandle(buffer->file_map_handle); - buffer->file_map_handle = NULL; - } - -#endif // _XBOX_ONE - -#endif - -#ifdef RMT_PLATFORM_MACOS - - // - // Mac version based on https://github.com/mikeash/MAMirroredQueue - // - // Copyright (c) 2010, Michael Ash - // All rights reserved. - // - // Redistribution and use in source and binary forms, with or without modification, are permitted provided that - // the following conditions are met: - // - // Redistributions of source code must retain the above copyright notice, this list of conditions and the following - // disclaimer. - // - // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - // following disclaimer in the documentation and/or other materials provided with the distribution. - // Neither the name of Michael Ash nor the names of its contributors may be used to endorse or promote products - // derived from this software without specific prior written permission. - // - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - // - - while (nb_attempts-- > 0) - { - vm_prot_t cur_prot, max_prot; - kern_return_t mach_error; - rmtU8* ptr = NULL; - rmtU8* target = NULL; - - // Allocate 2 contiguous pages of virtual memory - if (vm_allocate(mach_task_self(), (vm_address_t*)&ptr, size * 2, VM_FLAGS_ANYWHERE) != KERN_SUCCESS) - break; - - // Try to deallocate the last page, leaving its virtual memory address free - target = ptr + size; - if (vm_deallocate(mach_task_self(), (vm_address_t)target, size) != KERN_SUCCESS) - { - vm_deallocate(mach_task_self(), (vm_address_t)ptr, size * 2); - break; - } - - // Attempt to remap the page just deallocated to the buffer again - mach_error = vm_remap( - mach_task_self(), - (vm_address_t*)&target, - size, - 0, // mask - 0, // anywhere - mach_task_self(), - (vm_address_t)ptr, - 0, //copy - &cur_prot, - &max_prot, - VM_INHERIT_COPY); - - if (mach_error == KERN_NO_SPACE) - { - // Failed on this pass, cleanup and make another attempt - if (vm_deallocate(mach_task_self(), (vm_address_t)ptr, size) != KERN_SUCCESS) - break; - } - - else if (mach_error == KERN_SUCCESS) - { - // Leave the loop on success - buffer->ptr = ptr; - break; - } - - else - { - // Unknown error, can't recover - vm_deallocate(mach_task_self(), (vm_address_t)ptr, size); - break; - } - } - -#endif - -#ifdef RMT_PLATFORM_LINUX - - // Linux version based on now-defunct Wikipedia section http://en.wikipedia.org/w/index.php?title=Circular_buffer&oldid=600431497 - - -#ifdef __ANDROID__ - file_descriptor = ashmem_create_region("remotery_shm", size * 2); - if (file_descriptor < 0) { - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - } -#else - // Create a unique temporary filename in the shared memory folder - file_descriptor = mkstemp(path); - if (file_descriptor < 0) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - - // Delete the name - if (unlink(path)) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - - // Set the file size to twice the buffer size - // TODO: this 2x behavior can be avoided with similar solution to Win/Mac - if (ftruncate (file_descriptor, size * 2)) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - -#endif - // Map 2 contiguous pages - buffer->ptr = (rmtU8*)mmap(NULL, size * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (buffer->ptr == MAP_FAILED) - { - buffer->ptr = NULL; - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - } - - // Point both pages to the same memory file - if (mmap(buffer->ptr, size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, file_descriptor, 0) != buffer->ptr || - mmap(buffer->ptr + size, size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, file_descriptor, 0) != buffer->ptr + size) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - -#endif - - // Cleanup if exceeded number of attempts or failed - if (buffer->ptr == NULL) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - - return RMT_ERROR_NONE; -} - - -static void VirtualMirrorBuffer_Destructor(VirtualMirrorBuffer* buffer) -{ - assert(buffer != 0); - -#ifdef RMT_PLATFORM_WINDOWS - #ifdef _XBOX_ONE - if (buffer->page_mapping != NULL) - { - VirtualFree( buffer->ptr, 0, MEM_DECOMMIT ); //needed in conjunction with FreeTitlePhysicalPages - FreeTitlePhysicalPages( GetCurrentProcess(), buffer->page_count, buffer->page_mapping ); - free( buffer->page_mapping ); - buffer->page_mapping = NULL; - } - #else - if (buffer->file_map_handle != NULL) - { - // FIXME, don't we need to unmap the file views obtained in VirtualMirrorBuffer_Constructor, both for uwp/non-uwp - // See example https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc2 - - CloseHandle(buffer->file_map_handle); - buffer->file_map_handle = NULL; - } - #endif -#endif - -#ifdef RMT_PLATFORM_MACOS - if (buffer->ptr != NULL) - vm_deallocate(mach_task_self(), (vm_address_t)buffer->ptr, buffer->size * 2); -#endif - -#ifdef RMT_PLATFORM_LINUX - if (buffer->ptr != NULL) - munmap(buffer->ptr, buffer->size * 2); -#endif - - buffer->ptr = NULL; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @THREADS: Threads ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -typedef struct Thread_t rmtThread; -typedef rmtError(*ThreadProc)(rmtThread* thread); - - -struct Thread_t -{ - // OS-specific data - #if defined(RMT_PLATFORM_WINDOWS) - HANDLE handle; - #else - pthread_t handle; - #endif - - // Callback executed when the thread is created - ThreadProc callback; - - // Caller-specified parameter passed to Thread_Create - void* param; - - // Error state returned from callback - rmtError error; - - // External threads can set this to request an exit - volatile rmtBool request_exit; - -}; - - -#if defined(RMT_PLATFORM_WINDOWS) - - static DWORD WINAPI ThreadProcWindows(LPVOID lpParameter) - { - rmtThread* thread = (rmtThread*)lpParameter; - assert(thread != NULL); - thread->error = thread->callback(thread); - return thread->error == RMT_ERROR_NONE ? 0 : 1; - } - -#else - static void* StartFunc( void* pArgs ) - { - rmtThread* thread = (rmtThread*)pArgs; - assert(thread != NULL); - thread->error = thread->callback(thread); - return NULL; // returned error not use, check thread->error. - } -#endif - - -static int rmtThread_Valid(rmtThread* thread) -{ - assert(thread != NULL); - - #if defined(RMT_PLATFORM_WINDOWS) - return thread->handle != NULL; - #else - return !pthread_equal(thread->handle, pthread_self()); - #endif -} - - -static rmtError rmtThread_Constructor(rmtThread* thread, ThreadProc callback, void* param) -{ - assert(thread != NULL); - - thread->callback = callback; - thread->param = param; - thread->error = RMT_ERROR_NONE; - thread->request_exit = RMT_FALSE; - - // OS-specific thread creation - - #if defined (RMT_PLATFORM_WINDOWS) - - thread->handle = CreateThread( - NULL, // lpThreadAttributes - 0, // dwStackSize - ThreadProcWindows, // lpStartAddress - thread, // lpParameter - 0, // dwCreationFlags - NULL); // lpThreadId - - if (thread->handle == NULL) - return RMT_ERROR_CREATE_THREAD_FAIL; - - #else - - int32_t error = pthread_create( &thread->handle, NULL, StartFunc, thread ); - if (error) - { - // Contents of 'thread' parameter to pthread_create() are undefined after - // failure call so can't pre-set to invalid value before hand. - thread->handle = pthread_self(); - return RMT_ERROR_CREATE_THREAD_FAIL; - } - - #endif - - return RMT_ERROR_NONE; -} - - -static void rmtThread_RequestExit(rmtThread* thread) -{ - // Not really worried about memory barriers or delayed visibility to the target thread - assert(thread != NULL); - thread->request_exit = RMT_TRUE; -} - - -static void rmtThread_Join(rmtThread* thread) -{ - assert(rmtThread_Valid(thread)); - - #if defined(RMT_PLATFORM_WINDOWS) - WaitForSingleObject(thread->handle, INFINITE); - #else - pthread_join(thread->handle, NULL); - #endif -} - - -static void rmtThread_Destructor(rmtThread* thread) -{ - assert(thread != NULL); - - if (rmtThread_Valid(thread)) - { - // Shutdown the thread - rmtThread_RequestExit(thread); - rmtThread_Join(thread); - - // OS-specific release of thread resources - - #if defined(RMT_PLATFORM_WINDOWS) - CloseHandle(thread->handle); - thread->handle = NULL; - #endif - } -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SAFEC: Safe C Library excerpts - http://sourceforge.net/projects/safeclib/ ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -/*------------------------------------------------------------------ - * - * November 2008, Bo Berry - * - * Copyright (c) 2008-2011 by Cisco Systems, Inc - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - *------------------------------------------------------------------ - */ - - -// NOTE: Microsoft also has its own version of these functions so I'm do some hacky PP to remove them -#define strnlen_s strnlen_s_safe_c -#define strncat_s strncat_s_safe_c -#define strcpy_s strcpy_s_safe_c - - -#define RSIZE_MAX_STR (4UL << 10) /* 4KB */ -#define RCNEGATE(x) x - - -#define EOK ( 0 ) -#define ESNULLP ( 400 ) /* null ptr */ -#define ESZEROL ( 401 ) /* length is zero */ -#define ESLEMAX ( 403 ) /* length exceeds max */ -#define ESOVRLP ( 404 ) /* overlap undefined */ -#define ESNOSPC ( 406 ) /* not enough space for s2 */ -#define ESUNTERM ( 407 ) /* unterminated string */ -#define ESNOTFND ( 409 ) /* not found */ - -#ifndef _ERRNO_T_DEFINED -#define _ERRNO_T_DEFINED -typedef int errno_t; -#endif - -// rsize_t equivalent without going to the hassle of detecting if a platform has implemented C11/K3.2 -typedef unsigned int r_size_t; - -static r_size_t -strnlen_s (const char *dest, r_size_t dmax) -{ - r_size_t count; - - if (dest == NULL) { - return RCNEGATE(0); - } - - if (dmax == 0) { - return RCNEGATE(0); - } - - if (dmax > RSIZE_MAX_STR) { - return RCNEGATE(0); - } - - count = 0; - while (*dest && dmax) { - count++; - dmax--; - dest++; - } - - return RCNEGATE(count); -} - - -static errno_t -strstr_s (char *dest, r_size_t dmax, - const char *src, r_size_t slen, char **substring) -{ - - if (substring == NULL) { - return RCNEGATE(ESNULLP); - } - *substring = NULL; - - if (dest == NULL) { - return RCNEGATE(ESNULLP); - } - - if (dmax == 0) { - return RCNEGATE(ESZEROL); - } - - if (dmax > RSIZE_MAX_STR) { - return RCNEGATE(ESLEMAX); - } - - if (src == NULL) { - return RCNEGATE(ESNULLP); - } - - if (slen == 0) { - return RCNEGATE(ESZEROL); - } - - if (slen > RSIZE_MAX_STR) { - return RCNEGATE(ESLEMAX); - } - - /* - * src points to a string with zero length, or - * src equals dest, return dest - */ - if (*src == '\0' || dest == src) { - *substring = dest; - return RCNEGATE(EOK); - } - - while (*dest && dmax) { - r_size_t len; - r_size_t dlen; - int i; - i = 0; - len = slen; - dlen = dmax; - - while (srci && dlen) { - - /* not a match, not a substring */ - if (desti != srci) { - break; - } - - /* move to the next char */ - i++; - len--; - dlen--; - - if (srci == '\0' || !len) { - *substring = dest; - return RCNEGATE(EOK); - } - } - dest++; - dmax--; - } - - /* - * substring was not found, return NULL - */ - *substring = NULL; - return RCNEGATE(ESNOTFND); -} - - -static errno_t -strncat_s (char *dest, r_size_t dmax, const char *src, r_size_t slen) -{ - const char *overlap_bumper; - - if (dest == NULL) { - return RCNEGATE(ESNULLP); - } - - if (src == NULL) { - return RCNEGATE(ESNULLP); - } - - if (slen > RSIZE_MAX_STR) { - return RCNEGATE(ESLEMAX); - } - - if (dmax == 0) { - return RCNEGATE(ESZEROL); - } - - if (dmax > RSIZE_MAX_STR) { - return RCNEGATE(ESLEMAX); - } - - /* hold base of dest in case src was not copied */ - - if (dest < src) { - overlap_bumper = src; - - /* Find the end of dest */ - while (*dest != '\0') { - - if (dest == overlap_bumper) { - return RCNEGATE(ESOVRLP); - } - - dest++; - dmax--; - if (dmax == 0) { - return RCNEGATE(ESUNTERM); - } - } - - while (dmax > 0) { - if (dest == overlap_bumper) { - return RCNEGATE(ESOVRLP); - } - - /* - * Copying truncated before the source null is encountered - */ - if (slen == 0) { - *dest = '\0'; - return RCNEGATE(EOK); - } - - *dest = *src; - if (*dest == '\0') { - return RCNEGATE(EOK); - } - - dmax--; - slen--; - dest++; - src++; - } - - } else { - overlap_bumper = dest; - - /* Find the end of dest */ - while (*dest != '\0') { - - /* - * NOTE: no need to check for overlap here since src comes first - * in memory and we're not incrementing src here. - */ - dest++; - dmax--; - if (dmax == 0) { - return RCNEGATE(ESUNTERM); - } - } - - while (dmax > 0) { - if (src == overlap_bumper) { - return RCNEGATE(ESOVRLP); - } - - /* - * Copying truncated - */ - if (slen == 0) { - *dest = '\0'; - return RCNEGATE(EOK); - } - - *dest = *src; - if (*dest == '\0') { - return RCNEGATE(EOK); - } - - dmax--; - slen--; - dest++; - src++; - } - } - - /* - * the entire src was not copied, so the string will be nulled. - */ - return RCNEGATE(ESNOSPC); -} - - -errno_t -strcpy_s(char *dest, r_size_t dmax, const char *src) -{ - const char *overlap_bumper; - - if (dest == NULL) { - return RCNEGATE(ESNULLP); - } - - if (dmax == 0) { - return RCNEGATE(ESZEROL); - } - - if (dmax > RSIZE_MAX_STR) { - return RCNEGATE(ESLEMAX); - } - - if (src == NULL) { - *dest = '\0'; - return RCNEGATE(ESNULLP); - } - - if (dest == src) { - return RCNEGATE(EOK); - } - - if (dest < src) { - overlap_bumper = src; - - while (dmax > 0) { - if (dest == overlap_bumper) { - return RCNEGATE(ESOVRLP); - } - - *dest = *src; - if (*dest == '\0') { - return RCNEGATE(EOK); - } - - dmax--; - dest++; - src++; - } - - } - else { - overlap_bumper = dest; - - while (dmax > 0) { - if (src == overlap_bumper) { - return RCNEGATE(ESOVRLP); - } - - *dest = *src; - if (*dest == '\0') { - return RCNEGATE(EOK); - } - - dmax--; - dest++; - src++; - } - } - - /* - * the entire src must have been copied, if not reset dest - * to null the string. - */ - return RCNEGATE(ESNOSPC); -} - -#if !(defined(RMT_PLATFORM_LINUX) && RMT_USE_POSIX_THREADNAMES) - -/* very simple integer to hex */ -static const char* hex_encoding_table = "0123456789ABCDEF"; - -static void itoahex_s( char *dest, r_size_t dmax, rmtS32 value ) -{ - r_size_t len; - rmtS32 halfbytepos; - - halfbytepos = 8; - - /* strip leading 0's */ - while (halfbytepos > 1) - { - --halfbytepos; - if (value >> (4 * halfbytepos) & 0xF) - { - ++halfbytepos; - break; - } - } - - len = 0; - while(len + 1 < dmax && halfbytepos > 0) - { - --halfbytepos; - destlen = hex_encoding_tablevalue >> (4 * halfbytepos) & 0xF; - ++len; - } - - if (len < dmax) - { - destlen = 0; - } -} - -#endif - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @OBJALLOC: Reusable Object Allocator ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -// -// All objects that require free-list-backed allocation need to inherit from this type. -// -typedef struct ObjectLink_s -{ - struct ObjectLink_s* volatile next; -} ObjectLink; - - -static void ObjectLink_Constructor(ObjectLink* link) -{ - assert(link != NULL); - link->next = NULL; -} - - -typedef rmtError (*ObjConstructor)(void*); -typedef void (*ObjDestructor)(void*); - - -typedef struct -{ - // Object create/destroy parameters - rmtU32 object_size; - ObjConstructor constructor; - ObjDestructor destructor; - - // Number of objects in the free list - volatile rmtS32 nb_free; - - // Number of objects used by callers - volatile rmtS32 nb_inuse; - - // Total allocation count - volatile rmtS32 nb_allocated; - - ObjectLink* first_free; -} ObjectAllocator; - - -static rmtError ObjectAllocator_Constructor(ObjectAllocator* allocator, rmtU32 object_size, ObjConstructor constructor, ObjDestructor destructor) -{ - allocator->object_size = object_size; - allocator->constructor = constructor; - allocator->destructor = destructor; - allocator->nb_free = 0; - allocator->nb_inuse = 0; - allocator->nb_allocated = 0; - allocator->first_free = NULL; - return RMT_ERROR_NONE; -} - - -static void ObjectAllocator_Destructor(ObjectAllocator* allocator) -{ - // Ensure everything has been released to the allocator - assert(allocator != NULL); - //GPAC crude patch to avoid deadlock on opengl unbind upon exit -#if 0 - assert(allocator->nb_inuse == 0); -#endif - - // Destroy all objects released to the allocator - while (allocator->first_free != NULL) - { - ObjectLink* next = allocator->first_free->next; - assert(allocator->destructor != NULL); - allocator->destructor(allocator->first_free); - rmtFree(allocator->first_free); - allocator->first_free = next; - } -} - - -static void ObjectAllocator_Push(ObjectAllocator* allocator, ObjectLink* start, ObjectLink* end) -{ - assert(allocator != NULL); - assert(start != NULL); - assert(end != NULL); - - // CAS pop add range to the front of the list - for (;;) - { - ObjectLink* old_link = (ObjectLink*)allocator->first_free; - end->next = old_link; - if (AtomicCompareAndSwapPointer((long* volatile*)&allocator->first_free, (long*)old_link, (long*)start) == RMT_TRUE) - break; - } -} - - -static ObjectLink* ObjectAllocator_Pop(ObjectAllocator* allocator) -{ - ObjectLink* link; - - assert(allocator != NULL); - assert(allocator->first_free != NULL); - - // CAS pop from the front of the list - for (;;) - { - ObjectLink* old_link = (ObjectLink*)allocator->first_free; - ObjectLink* next_link = old_link->next; - if (AtomicCompareAndSwapPointer((long* volatile*)&allocator->first_free, (long*)old_link, (long*)next_link) == RMT_TRUE) - { - link = old_link; - break; - } - } - - link->next = NULL; - - return link; -} - - -static rmtError ObjectAllocator_Alloc(ObjectAllocator* allocator, void** object) -{ - // This function only calls the object constructor on initial malloc of an object - - assert(allocator != NULL); - assert(object != NULL); - - // Has the free list run out? - if (allocator->first_free == NULL) - { - rmtError error; - - // Allocate/construct a new object - void* free_object = rmtMalloc( allocator->object_size ); - if (free_object == NULL) - return RMT_ERROR_MALLOC_FAIL; - assert(allocator->constructor != NULL); - error = allocator->constructor(free_object); - if (error != RMT_ERROR_NONE) - { - // Auto-teardown on failure - assert(allocator->destructor != NULL); - allocator->destructor(free_object); - rmtFree(free_object); - return error; - } - - // Add to the free list - ObjectAllocator_Push(allocator, (ObjectLink*)free_object, (ObjectLink*)free_object); - AtomicAdd(&allocator->nb_allocated, 1); - AtomicAdd(&allocator->nb_free, 1); - } - - // Pull available objects from the free list - *object = ObjectAllocator_Pop(allocator); - AtomicSub(&allocator->nb_free, 1); - AtomicAdd(&allocator->nb_inuse, 1); - - return RMT_ERROR_NONE; -} - - -static void ObjectAllocator_Free(ObjectAllocator* allocator, void* object) -{ - // Add back to the free-list - assert(allocator != NULL); - ObjectAllocator_Push(allocator, (ObjectLink*)object, (ObjectLink*)object); - AtomicSub(&allocator->nb_inuse, 1); - AtomicAdd(&allocator->nb_free, 1); -} - - -static void ObjectAllocator_FreeRange(ObjectAllocator* allocator, void* start, void* end, rmtU32 count) -{ - assert(allocator != NULL); - ObjectAllocator_Push(allocator, (ObjectLink*)start, (ObjectLink*)end); - AtomicSub(&allocator->nb_inuse, count); - AtomicAdd(&allocator->nb_free, count); -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @DYNBUF: Dynamic Buffer ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -typedef struct -{ - rmtU32 alloc_granularity; - - rmtU32 bytes_allocated; - rmtU32 bytes_used; - - rmtU8* data; -} Buffer; - - -static rmtError Buffer_Constructor(Buffer* buffer, rmtU32 alloc_granularity) -{ - assert(buffer != NULL); - buffer->alloc_granularity = alloc_granularity; - buffer->bytes_allocated = 0; - buffer->bytes_used = 0; - buffer->data = NULL; - return RMT_ERROR_NONE; -} - - -static void Buffer_Destructor(Buffer* buffer) -{ - assert(buffer != NULL); - - if (buffer->data != NULL) - { - rmtFree(buffer->data); - buffer->data = NULL; - } -} - - -static rmtError Buffer_Grow(Buffer* buffer, rmtU32 length) -{ - // Calculate size increase rounded up to the requested allocation granularity - rmtU32 granularity = buffer->alloc_granularity; - rmtU32 allocate = buffer->bytes_allocated + length; - allocate = allocate + ((granularity - 1) - ((allocate - 1) % granularity)); - - buffer->bytes_allocated = allocate; - buffer->data = (rmtU8*)rmtRealloc(buffer->data, buffer->bytes_allocated); - if (buffer->data == NULL) - return RMT_ERROR_MALLOC_FAIL; - - return RMT_ERROR_NONE; -} - - -static rmtError Buffer_Write(Buffer* buffer, const void* data, rmtU32 length) -{ - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + length > buffer->bytes_allocated) - { - rmtError error = Buffer_Grow(buffer, length); - if (error != RMT_ERROR_NONE) - return error; - } - - // Copy all bytes - memcpy(buffer->data + buffer->bytes_used, data, length); - buffer->bytes_used += length; - - return RMT_ERROR_NONE; -} - -static rmtError Buffer_WriteStringZ(Buffer* buffer, rmtPStr string) -{ - assert(string != NULL); - return Buffer_Write(buffer, (void*)string, (rmtU32)strnlen_s(string, 2048) + 1); -} - - -static void U32ToByteArray(rmtU8* dest, rmtU32 value) -{ - // Commit as little-endian - dest0 = value & 255; - dest1 = (value >> 8) & 255; - dest2 = (value >> 16) & 255; - dest3 = value >> 24; -} - - -static rmtError Buffer_WriteU32(Buffer* buffer, rmtU32 value) -{ - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + sizeof(value) > buffer->bytes_allocated) - { - rmtError error = Buffer_Grow(buffer, sizeof(value)); - if (error != RMT_ERROR_NONE) - return error; - } - - // Copy all bytes - #if RMT_ASSUME_LITTLE_ENDIAN - *(rmtU32*)(buffer->data + buffer->bytes_used) = value; - #else - U32ToByteArray(buffer->data + buffer->bytes_used, value); - #endif - - buffer->bytes_used += sizeof(value); - - return RMT_ERROR_NONE; -} - - -static rmtBool IsLittleEndian() -{ - // Not storing this in a global variable allows the compiler to more easily optimise - // this away altogether. - union - { - unsigned int i; - unsigned char csizeof(unsigned int); - } u; - u.i = 1; - return u.c0 == 1 ? RMT_TRUE : RMT_FALSE; -} - - -static rmtError Buffer_WriteU64(Buffer* buffer, rmtU64 value) -{ - // Write as a double as Javascript DataView doesn't have a 64-bit integer read - - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + sizeof(value) > buffer->bytes_allocated) - { - rmtError error = Buffer_Grow(buffer, sizeof(value)); - if (error != RMT_ERROR_NONE) - return error; - } - - // Copy all bytes - #if RMT_ASSUME_LITTLE_ENDIAN - *(double*)(buffer->data + buffer->bytes_used) = (double)value; - #else - { - union - { - double d; - unsigned char csizeof(double); - } u; - rmtU8* dest = buffer->data + buffer->bytes_used; - u.d = (double)value; - if (IsLittleEndian()) - { - dest0 = u.c0; - dest1 = u.c1; - dest2 = u.c2; - dest3 = u.c3; - dest4 = u.c4; - dest5 = u.c5; - dest6 = u.c6; - dest7 = u.c7; - } - else - { - dest0 = u.c7; - dest1 = u.c6; - dest2 = u.c5; - dest3 = u.c4; - dest4 = u.c3; - dest5 = u.c2; - dest6 = u.c1; - dest7 = u.c0; - } - } - #endif - - buffer->bytes_used += sizeof(value); - - return RMT_ERROR_NONE; -} - - -static rmtError Buffer_WriteStringWithLength(Buffer* buffer, rmtPStr string) -{ - rmtU32 length = (rmtU32)strnlen_s(string, 2048); - rmtError error; - - error = Buffer_WriteU32(buffer, length); - if (error != RMT_ERROR_NONE) - return error; - - return Buffer_Write(buffer, (void*)string, length); -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @HASHTABLE: Integer pair hash map for inserts/finds. No removes for added simplicity. ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#define RMT_NOT_FOUND 0xFFFFFFFF - - -typedef struct -{ - // Non-zero, pre-hashed key - rmtU32 key; - - // Value that's not equal to RMT_NOT_FOUND - rmtU32 value; -} HashSlot; - - -typedef struct -{ - // Stats - rmtU32 max_nb_slots; - rmtU32 nb_slots; - - // Data - HashSlot* slots; -} rmtHashTable; - - -static rmtError rmtHashTable_Constructor(rmtHashTable* table, rmtU32 max_nb_slots) -{ - // Default initialise - assert(table != NULL); - table->max_nb_slots = max_nb_slots; - table->nb_slots = 0; - - // Allocate and clear the hash slots - table->slots = (HashSlot*)rmtMalloc(table->max_nb_slots * sizeof(HashSlot)); - if (table->slots == NULL) - return RMT_ERROR_MALLOC_FAIL; - memset(table->slots, 0, table->max_nb_slots * sizeof(HashSlot)); - - return RMT_ERROR_NONE; -} - - -static void rmtHashTable_Destructor(rmtHashTable* table) -{ - assert(table != NULL); - - if (table->slots != NULL) - { - rmtFree(table->slots); - table->slots = NULL; - } -} - - -static rmtError rmtHashTable_Resize(rmtHashTable* table); - - -static rmtError rmtHashTable_Insert(rmtHashTable* table, rmtU32 key, rmtU32 value) -{ - HashSlot* slot = NULL; - rmtError error = RMT_ERROR_NONE; - - // Calculate initial slot location for this key - rmtU32 index_mask = table->max_nb_slots - 1; - rmtU32 index = key & index_mask; - - assert(key != 0); - assert(value != RMT_NOT_FOUND); - - // Linear probe for free slot, reusing any existing key matches - // There will always be at least one free slot due to load factor management - while (table->slotsindex.key) - { - if (table->slotsindex.key == key) - { - // Counter occupied slot increments below - table->nb_slots--; - break; - } - - index = (index + 1) & index_mask; - } - - // Just verify that I've got no errors in the code above - assert(index < table->max_nb_slots); - - // Add to the table - slot = table->slots + index; - slot->key = key; - slot->value = value; - table->nb_slots++; - - // Resize when load factor is greater than 2/3 - if (table->nb_slots > (table->max_nb_slots * 2) / 3) - error = rmtHashTable_Resize(table); - - return error; -} - - -static rmtError rmtHashTable_Resize(rmtHashTable* table) -{ - rmtU32 old_max_nb_slots = table->max_nb_slots; - HashSlot* new_slots = NULL; - HashSlot* old_slots = table->slots; - rmtU32 i; - - // Increase the table size - rmtU32 new_max_nb_slots = table->max_nb_slots; - if (new_max_nb_slots < 8192 * 4) - new_max_nb_slots *= 4; - else - new_max_nb_slots *= 2; - - // Allocate and clear a new table - new_slots = (HashSlot*)rmtMalloc(new_max_nb_slots * sizeof(HashSlot)); - if (new_slots == NULL) - return RMT_ERROR_MALLOC_FAIL; - memset(new_slots, 0, new_max_nb_slots * sizeof(HashSlot)); - - // Update fields of the table after successful allocation only - table->slots = new_slots; - table->max_nb_slots = new_max_nb_slots; - table->nb_slots = 0; - - // Reinsert all objects into the new table - for (i = 0; i < old_max_nb_slots; i++) - { - HashSlot* slot = old_slots + i; - if (slot->key != 0) - rmtHashTable_Insert(table, slot->key, slot->value); - } - - rmtFree(old_slots); - - return RMT_ERROR_NONE; -} - - -static rmtU32 rmtHashTable_Find(rmtHashTable* table, rmtU32 key) -{ - // Calculate initial slot location for this key - rmtU32 index_mask = table->max_nb_slots - 1; - rmtU32 index = key & index_mask; - - // Linear probe for matching hash - while (table->slotsindex.key) - { - HashSlot* slot = table->slots + index; - - if (slot->key == key) - return slot->value; - - index = (index + 1) & index_mask; - } - - return RMT_NOT_FOUND; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @STRINGTABLE: Map from string hash to string offset in local buffer ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -typedef struct -{ - // Growable dynamic array of strings added so far - Buffer* text; - - // Map from text hash to text location in the buffer - rmtHashTable* text_map; -} StringTable; - - -static rmtError StringTable_Constructor(StringTable* table) -{ - rmtError error; - - // Default initialise - assert(table != NULL); - table->text = NULL; - table->text_map = NULL; - - // Allocate reasonably storage for initial sample names - - New_1(Buffer, table->text, 8 * 1024); - if (error != RMT_ERROR_NONE) - return error; - - New_1(rmtHashTable, table->text_map, 1 * 1024); - if (error != RMT_ERROR_NONE) - return error; - - return RMT_ERROR_NONE; -} - - -static void StringTable_Destructor(StringTable* table) -{ - assert(table != NULL); - - Delete(rmtHashTable, table->text_map); - Delete(Buffer, table->text); -} - - -static rmtPStr StringTable_Find(StringTable* table, rmtU32 name_hash) -{ - rmtU32 text_offset = rmtHashTable_Find(table->text_map, name_hash); - if (text_offset != RMT_NOT_FOUND) - return (rmtPStr)(table->text->data + text_offset); - return NULL; -} - - -static void StringTable_Insert(StringTable* table, rmtU32 name_hash, rmtPStr name) -{ - // Only add to the buffer if the string isn't already there - rmtU32 text_offset = rmtHashTable_Find(table->text_map, name_hash); - if (text_offset == RMT_NOT_FOUND) - { - // TODO: Allocation errors aren't being passed on to the caller - text_offset = table->text->bytes_used; - Buffer_WriteStringZ(table->text, name); - rmtHashTable_Insert(table->text_map, name_hash, text_offset); - } -} - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SOCKETS: Sockets TCP/IP Wrapper ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#ifndef RMT_PLATFORM_WINDOWS - typedef int SOCKET; - #define INVALID_SOCKET -1 - #define SOCKET_ERROR -1 - #define SD_SEND SHUT_WR - #define closesocket close -#endif - - -typedef struct -{ - SOCKET socket; -} TCPSocket; - - -typedef struct -{ - rmtBool can_read; - rmtBool can_write; - rmtError error_state; -} SocketStatus; - - -// -// Function prototypes -// -static void TCPSocket_Close(TCPSocket* tcp_socket); - - -static rmtError InitialiseNetwork() -{ - #ifdef RMT_PLATFORM_WINDOWS - - WSADATA wsa_data; - if (WSAStartup(MAKEWORD(2, 2), &wsa_data)) - return RMT_ERROR_SOCKET_INIT_NETWORK_FAIL; - if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2) - return RMT_ERROR_SOCKET_INIT_NETWORK_FAIL; - - return RMT_ERROR_NONE; - - #else - - return RMT_ERROR_NONE; - - #endif -} - - -static void ShutdownNetwork() -{ - #ifdef RMT_PLATFORM_WINDOWS - WSACleanup(); - #endif -} - - -static rmtError TCPSocket_Constructor(TCPSocket* tcp_socket) -{ - assert(tcp_socket != NULL); - tcp_socket->socket = INVALID_SOCKET; - return InitialiseNetwork(); -} - - -static void TCPSocket_Destructor(TCPSocket* tcp_socket) -{ - assert(tcp_socket != NULL); - TCPSocket_Close(tcp_socket); - ShutdownNetwork(); -} - - -static rmtError TCPSocket_RunServer(TCPSocket* tcp_socket, rmtU16 port, rmtBool reuse_open_port, rmtBool limit_connections_to_localhost) -{ - SOCKET s = INVALID_SOCKET; - struct sockaddr_in sin; - #ifdef RMT_PLATFORM_WINDOWS - u_long nonblock = 1; - #endif - - memset(&sin, 0, sizeof(sin) ); - assert(tcp_socket != NULL); - - // Try to create the socket - s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (s == SOCKET_ERROR) - return RMT_ERROR_SOCKET_CREATE_FAIL; - - if (reuse_open_port) - { - int enable = 1; - - // set SO_REUSEADDR so binding doesn't fail when restarting the application - // (otherwise the same port can't be reused within TIME_WAIT) - // I'm not checking for errors because if this fails (unlikely) we might still - // be able to bind to the socket anyway - #ifdef RMT_PLATFORM_POSIX - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); - #elif defined(RMT_PLATFORM_WINDOWS) - // windows also needs SO_EXCLUSEIVEADDRUSE, - // see http://www.andy-pearce.com/blog/posts/2013/Feb/so_reuseaddr-on-windows/ - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&enable, sizeof(enable)); - enable = 1; - setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&enable, sizeof(enable)); - #endif - } - - // Bind the socket to the incoming port - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(limit_connections_to_localhost ? INADDR_LOOPBACK : INADDR_ANY); - sin.sin_port = htons(port); - if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) - return RMT_ERROR_SOCKET_BIND_FAIL; - - // Connection is valid, remaining code is socket state modification - tcp_socket->socket = s; - - // Enter a listening state with a backlog of 1 connection - if (listen(s, 1) == SOCKET_ERROR) - return RMT_ERROR_SOCKET_LISTEN_FAIL; - - // Set as non-blocking - #ifdef RMT_PLATFORM_WINDOWS - if (ioctlsocket(tcp_socket->socket, FIONBIO, &nonblock) == SOCKET_ERROR) - return RMT_ERROR_SOCKET_SET_NON_BLOCKING_FAIL; - #else - if (fcntl(tcp_socket->socket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR) - return RMT_ERROR_SOCKET_SET_NON_BLOCKING_FAIL; - #endif - - return RMT_ERROR_NONE; -} - - -static void TCPSocket_Close(TCPSocket* tcp_socket) -{ - assert(tcp_socket != NULL); - - if (tcp_socket->socket != INVALID_SOCKET) - { - // Shutdown the connection, stopping all sends - int result = shutdown(tcp_socket->socket, SD_SEND); - if (result != SOCKET_ERROR) - { - // Keep receiving until the peer closes the connection - //int total = 0; - char temp_buf128; - while (result > 0) - { - result = (int)recv(tcp_socket->socket, temp_buf, sizeof(temp_buf), 0); - //total += result; - } - } - - // Close the socket and issue a network shutdown request - closesocket(tcp_socket->socket); - tcp_socket->socket = INVALID_SOCKET; - } -} - - -static SocketStatus TCPSocket_PollStatus(TCPSocket* tcp_socket) -{ - SocketStatus status; - fd_set fd_read, fd_write, fd_errors; - struct timeval tv; - - status.can_read = RMT_FALSE; - status.can_write = RMT_FALSE; - status.error_state = RMT_ERROR_NONE; - - assert(tcp_socket != NULL); - if (tcp_socket->socket == INVALID_SOCKET) - { - status.error_state = RMT_ERROR_SOCKET_INVALID_POLL; - return status; - } - - // Set read/write/error markers for the socket - FD_ZERO(&fd_read); - FD_ZERO(&fd_write); - FD_ZERO(&fd_errors); -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable:4127) // warning C4127: conditional expression is constant -#endif // _MSC_VER - FD_SET(tcp_socket->socket, &fd_read); - FD_SET(tcp_socket->socket, &fd_write); - FD_SET(tcp_socket->socket, &fd_errors); -#ifdef _MSC_VER -# pragma warning(pop) -#endif // _MSC_VER - - // Poll socket status without blocking - tv.tv_sec = 0; - tv.tv_usec = 0; - if (select(((int)tcp_socket->socket)+1, &fd_read, &fd_write, &fd_errors, &tv) == SOCKET_ERROR) - { - status.error_state = RMT_ERROR_SOCKET_SELECT_FAIL; - return status; - } - - status.can_read = FD_ISSET(tcp_socket->socket, &fd_read) != 0 ? RMT_TRUE : RMT_FALSE; - status.can_write = FD_ISSET(tcp_socket->socket, &fd_write) != 0 ? RMT_TRUE : RMT_FALSE; - status.error_state = FD_ISSET(tcp_socket->socket, &fd_errors) != 0 ? RMT_ERROR_SOCKET_POLL_ERRORS : RMT_ERROR_NONE; - return status; -} - - -static rmtError TCPSocket_AcceptConnection(TCPSocket* tcp_socket, TCPSocket** client_socket) -{ - SocketStatus status; - SOCKET s; - rmtError error; - - // Ensure there is an incoming connection - assert(tcp_socket != NULL); - status = TCPSocket_PollStatus(tcp_socket); - if (status.error_state != RMT_ERROR_NONE || !status.can_read) - return status.error_state; - - // Accept the connection - s = accept(tcp_socket->socket, 0, 0); - if (s == SOCKET_ERROR) - return RMT_ERROR_SOCKET_ACCEPT_FAIL; - -#ifdef SO_NOSIGPIPE - // On POSIX systems, send() may send a SIGPIPE signal when writing to an - // already closed connection. By setting this option, we prevent the - // signal from being emitted and send will instead return an error and set - // errno to EPIPE. - // - // This is supported on BSD platforms and not on Linux. - { - int flag = 1; - setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(flag)); - } -#endif - // Create a client socket for the new connection - assert(client_socket != NULL); - New_0(TCPSocket, *client_socket); - if (error != RMT_ERROR_NONE) - return error; - (*client_socket)->socket = s; - - return RMT_ERROR_NONE; -} - - -static int TCPSocketWouldBlock() -{ -#ifdef RMT_PLATFORM_WINDOWS - DWORD error = WSAGetLastError(); - return (error == WSAEWOULDBLOCK); - #else - int error = errno; - return (error == EAGAIN || error == EWOULDBLOCK); -#endif - -} - - -static rmtError TCPSocket_Send(TCPSocket* tcp_socket, const void* data, rmtU32 length, rmtU32 timeout_ms) -{ - SocketStatus status; - char* cur_data = NULL; - char* end_data = NULL; - rmtU32 start_ms = 0; - rmtU32 cur_ms = 0; - - assert(tcp_socket != NULL); - - start_ms = msTimer_Get(); - - // Loop until timeout checking whether data can be written - status.can_write = RMT_FALSE; - while (!status.can_write) - { - status = TCPSocket_PollStatus(tcp_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - - cur_ms = msTimer_Get(); - if (cur_ms - start_ms > timeout_ms) - return RMT_ERROR_SOCKET_SEND_TIMEOUT; - } - - cur_data = (char*)data; - end_data = cur_data + length; - - while (cur_data < end_data) - { - // Attempt to send the remaining chunk of data - int bytes_sent; - int send_flags = 0; -#ifdef MSG_NOSIGNAL - // On Linux this prevents send from emitting a SIGPIPE signal - // Equivalent on BSD to the SO_NOSIGPIPE option. - send_flags = MSG_NOSIGNAL; -#endif - bytes_sent = (int)send(tcp_socket->socket, cur_data, (int)(end_data - cur_data), send_flags); - - if (bytes_sent == SOCKET_ERROR || bytes_sent == 0) - { - // Close the connection if sending fails for any other reason other than blocking - if (bytes_sent != 0 && !TCPSocketWouldBlock()) - return RMT_ERROR_SOCKET_SEND_FAIL; - - // First check for tick-count overflow and reset, giving a slight hitch every 49.7 days - cur_ms = msTimer_Get(); - if (cur_ms < start_ms) - { - start_ms = cur_ms; - continue; - } - - // - // Timeout can happen when: - // - // 1) endpoint is no longer there - // 2) endpoint can't consume quick enough - // 3) local buffers overflow - // - // As none of these are actually errors, we have to pass this timeout back to the caller. - // - // TODO: This strategy breaks down if a send partially completes and then times out! - // - if (cur_ms - start_ms > timeout_ms) - { - return RMT_ERROR_SOCKET_SEND_TIMEOUT; - } - } - else - { - // Jump over the data sent - cur_data += bytes_sent; - } - } - - return RMT_ERROR_NONE; -} - - -static rmtError TCPSocket_Receive(TCPSocket* tcp_socket, void* data, rmtU32 length, rmtU32 timeout_ms) -{ - SocketStatus status; - char* cur_data = NULL; - char* end_data = NULL; - rmtU32 start_ms = 0; - rmtU32 cur_ms = 0; - - assert(tcp_socket != NULL); - - // Ensure there is data to receive - status = TCPSocket_PollStatus(tcp_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - if (!status.can_read) - return RMT_ERROR_SOCKET_RECV_NO_DATA; - - cur_data = (char*)data; - end_data = cur_data + length; - - // Loop until all data has been received - start_ms = msTimer_Get(); - while (cur_data < end_data) - { - int bytes_received = (int)recv(tcp_socket->socket, cur_data, (int)(end_data - cur_data), 0); - - if (bytes_received == SOCKET_ERROR || bytes_received == 0) - { - // Close the connection if receiving fails for any other reason other than blocking - if (bytes_received != 0 && !TCPSocketWouldBlock()) - return RMT_ERROR_SOCKET_RECV_FAILED; - - // First check for tick-count overflow and reset, giving a slight hitch every 49.7 days - cur_ms = msTimer_Get(); - if (cur_ms < start_ms) - { - start_ms = cur_ms; - continue; - } - - // - // Timeout can happen when: - // - // 1) data is delayed by sender - // 2) sender fails to send a complete set of packets - // - // As not all of these scenarios are errors, we need to pass this information back to the caller. - // - // TODO: This strategy breaks down if a receive partially completes and then times out! - // - if (cur_ms - start_ms > timeout_ms) - { - return RMT_ERROR_SOCKET_RECV_TIMEOUT; - } - } - else - { - // Jump over the data received - cur_data += bytes_received; - } - } - - return RMT_ERROR_NONE; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SHA1: SHA-1 Cryptographic Hash Function ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -// -// Typed to allow enforced data size specification -// -typedef struct -{ - rmtU8 data20; -} SHA1; - - -/* - Copyright (c) 2011, Micael Hildenborg - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Micael Hildenborg nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - Contributors: - Gustav - Several members in the gamedev.se forum. - Gregory Petrosyan - */ - - -// Rotate an integer value to left. -static unsigned int rol(const unsigned int value, const unsigned int steps) -{ - return ((value << steps) | (value >> (32 - steps))); -} - - -// Sets the first 16 integers in the buffert to zero. -// Used for clearing the W buffert. -static void clearWBuffert(unsigned int* buffert) -{ - int pos; - for (pos = 16; --pos >= 0;) - { - buffertpos = 0; - } -} - -static void innerHash(unsigned int* result, unsigned int* w) -{ - unsigned int a = result0; - unsigned int b = result1; - unsigned int c = result2; - unsigned int d = result3; - unsigned int e = result4; - - int round = 0; - - #define sha1macro(func,val) \ - { \ - const unsigned int t = rol(a, 5) + (func) + e + val + wround; \ - e = d; \ - d = c; \ - c = rol(b, 30); \ - b = a; \ - a = t; \ - } - - while (round < 16) - { - sha1macro((b & c) | (~b & d), 0x5a827999) - ++round; - } - while (round < 20) - { - wround = rol((wround - 3 ^ wround - 8 ^ wround - 14 ^ wround - 16), 1); - sha1macro((b & c) | (~b & d), 0x5a827999) - ++round; - } - while (round < 40) - { - wround = rol((wround - 3 ^ wround - 8 ^ wround - 14 ^ wround - 16), 1); - sha1macro(b ^ c ^ d, 0x6ed9eba1) - ++round; - } - while (round < 60) - { - wround = rol((wround - 3 ^ wround - 8 ^ wround - 14 ^ wround - 16), 1); - sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc) - ++round; - } - while (round < 80) - { - wround = rol((wround - 3 ^ wround - 8 ^ wround - 14 ^ wround - 16), 1); - sha1macro(b ^ c ^ d, 0xca62c1d6) - ++round; - } - - #undef sha1macro - - result0 += a; - result1 += b; - result2 += c; - result3 += d; - result4 += e; -} - - -static void calc(const void* src, const int bytelength, unsigned char* hash) -{ - int roundPos; - int lastBlockBytes; - int hashByte; - - // Init the result array. - unsigned int result5 = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; - - // Cast the void src pointer to be the byte array we can work with. - const unsigned char* sarray = (const unsigned char*) src; - - // The reusable round buffer - unsigned int w80; - - // Loop through all complete 64byte blocks. - const int endOfFullBlocks = bytelength - 64; - int endCurrentBlock; - int currentBlock = 0; - - while (currentBlock <= endOfFullBlocks) - { - endCurrentBlock = currentBlock + 64; - - // Init the round buffer with the 64 byte block data. - for (roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) - { - // This line will swap endian on big endian and keep endian on little endian. - wroundPos++ = (unsigned int) sarraycurrentBlock + 3 - | (((unsigned int) sarraycurrentBlock + 2) << 8) - | (((unsigned int) sarraycurrentBlock + 1) << 16) - | (((unsigned int) sarraycurrentBlock) << 24); - } - innerHash(result, w); - } - - // Handle the last and not full 64 byte block if existing. - endCurrentBlock = bytelength - currentBlock; - clearWBuffert(w); - lastBlockBytes = 0; - for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes) - { - wlastBlockBytes >> 2 |= (unsigned int) sarraylastBlockBytes + currentBlock << ((3 - (lastBlockBytes & 3)) << 3); - } - wlastBlockBytes >> 2 |= 0x80U << ((3 - (lastBlockBytes & 3)) << 3); - if (endCurrentBlock >= 56) - { - innerHash(result, w); - clearWBuffert(w); - } - w15 = bytelength << 3; - innerHash(result, w); - - // Store hash in result pointer, and make sure we get in in the correct order on both endian models. - for (hashByte = 20; --hashByte >= 0;) - { - hashhashByte = (resulthashByte >> 2 >> (((3 - hashByte) & 0x3) << 3)) & 0xff; - } -} - - -static SHA1 SHA1_Calculate(const void* src, unsigned int length) -{ - SHA1 hash; - assert((int)length >= 0); - calc(src, length, hash.data); - return hash; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @BASE64: Base-64 encoder ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -static const char* b64_encoding_table = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - - -static rmtU32 Base64_CalculateEncodedLength(rmtU32 length) -{ - // ceil(l * 4/3) - return 4 * ((length + 2) / 3); -} - - -static void Base64_Encode(const rmtU8* in_bytes, rmtU32 length, rmtU8* out_bytes) -{ - rmtU32 i; - rmtU32 encoded_length; - rmtU32 remaining_bytes; - - rmtU8* optr = out_bytes; - - for (i = 0; i < length; ) - { - // Read input 3 values at a time, null terminating - rmtU32 c0 = i < length ? in_bytesi++ : 0; - rmtU32 c1 = i < length ? in_bytesi++ : 0; - rmtU32 c2 = i < length ? in_bytesi++ : 0; - - // Encode 4 bytes for ever 3 input bytes - rmtU32 triple = (c0 << 0x10) + (c1 << 0x08) + c2; - *optr++ = b64_encoding_table(triple >> 3 * 6) & 0x3F; - *optr++ = b64_encoding_table(triple >> 2 * 6) & 0x3F; - *optr++ = b64_encoding_table(triple >> 1 * 6) & 0x3F; - *optr++ = b64_encoding_table(triple >> 0 * 6) & 0x3F; - } - - // Pad output to multiple of 3 bytes with terminating '=' - encoded_length = Base64_CalculateEncodedLength(length); - remaining_bytes = (3 - ((length + 2) % 3)) - 1; - for (i = 0; i < remaining_bytes; i++) - out_bytesencoded_length - 1 - i = '='; - - // Null terminate - out_bytesencoded_length = 0; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @MURMURHASH: MurmurHash3 - https://code.google.com/p/smhasher ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -//----------------------------------------------------------------------------- -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. -//----------------------------------------------------------------------------- - - -static rmtU32 rotl32(rmtU32 x, rmtS8 r) -{ - return (x << r) | (x >> (32 - r)); -} - - -// Block read - if your platform needs to do endian-swapping, do the conversion here -static rmtU32 getblock32(const rmtU32* p, int i) -{ - rmtU32 result; - const rmtU8 *src = ((const rmtU8 *)p) + i * sizeof(rmtU32); - memcpy(&result, src, sizeof(result)); - return result; -} - - -// Finalization mix - force all bits of a hash block to avalanche -static rmtU32 fmix32(rmtU32 h) -{ - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - return h; -} - - -static rmtU32 MurmurHash3_x86_32(const void* key, int len, rmtU32 seed) -{ - const rmtU8* data = (const rmtU8*)key; - const int nblocks = len / 4; - - rmtU32 h1 = seed; - - const rmtU32 c1 = 0xcc9e2d51; - const rmtU32 c2 = 0x1b873593; - - int i; - - const rmtU32 * blocks = (const rmtU32 *)(data + nblocks*4); - const rmtU8 * tail = (const rmtU8*)(data + nblocks*4); - - rmtU32 k1 = 0; - - //---------- - // body - - for (i = -nblocks; i; i++) - { - rmtU32 k2 = getblock32(blocks,i); - - k2 *= c1; - k2 = rotl32(k2,15); - k2 *= c2; - - h1 ^= k2; - h1 = rotl32(h1,13); - h1 = h1*5+0xe6546b64; - } - - //---------- - // tail - - switch(len & 3) - { - case 3: k1 ^= tail2 << 16; // fallthrough - case 2: k1 ^= tail1 << 8; // fallthrough - case 1: k1 ^= tail0; - k1 *= c1; - k1 = rotl32(k1,15); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - - h1 = fmix32(h1); - - return h1; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @WEBSOCKETS: WebSockets ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -enum WebSocketMode -{ - WEBSOCKET_NONE = 0, - WEBSOCKET_TEXT = 1, - WEBSOCKET_BINARY = 2, -}; - - -typedef struct -{ - TCPSocket* tcp_socket; - - enum WebSocketMode mode; - - rmtU32 frame_bytes_remaining; - rmtU32 mask_offset; - - union - { - rmtU8 mask4; - rmtU32 mask_u32; - } data; - -} WebSocket; - - -static void WebSocket_Close(WebSocket* web_socket); - - -static char* GetField(char* buffer, r_size_t buffer_length, rmtPStr field_name) -{ - char* field = NULL; - char* buffer_end = buffer + buffer_length - 1; - - r_size_t field_length = strnlen_s(field_name, buffer_length); - if (field_length == 0) - return NULL; - - // Search for the start of the field - if (strstr_s(buffer, buffer_length, field_name, field_length, &field) != EOK) - return NULL; - - // Field name is now guaranteed to be in the buffer so its safe to jump over it without hitting the bounds - field += strlen(field_name); - - // Skip any trailing whitespace - while (*field == ' ') - { - if (field >= buffer_end) - return NULL; - field++; - } - - return field; -} - - -static const char websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -static const char websocket_response = - "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Accept: "; - - -static rmtError WebSocketHandshake(TCPSocket* tcp_socket, rmtPStr limit_host) -{ - rmtU32 start_ms, now_ms; - - // Parsing scratchpad - char buffer1024; - char* buffer_ptr = buffer; - int buffer_len = sizeof(buffer) - 1; - char* buffer_end = buffer + buffer_len; - - char response_buffer256; - int response_buffer_len = sizeof(response_buffer) - 1; - - char* version; - char* host; - char* key; - char* key_end; - SHA1 hash; - - assert(tcp_socket != NULL); - - start_ms = msTimer_Get(); - - // Really inefficient way of receiving the handshake data from the browser - // Not really sure how to do this any better, as the termination requirement is \r\n\r\n - while (buffer_ptr - buffer < buffer_len) - { - rmtError error = TCPSocket_Receive(tcp_socket, buffer_ptr, 1, 20); - if (error == RMT_ERROR_SOCKET_RECV_FAILED) - return error; - - // If there's a stall receiving the data, check for a handshake timeout - if (error == RMT_ERROR_SOCKET_RECV_NO_DATA || error == RMT_ERROR_SOCKET_RECV_TIMEOUT) - { - now_ms = msTimer_Get(); - if (now_ms - start_ms > 1000) - return RMT_ERROR_SOCKET_RECV_TIMEOUT; - - continue; - } - - // Just in case new enums are added... - assert(error == RMT_ERROR_NONE); - - if (buffer_ptr - buffer >= 4) - { - if (*(buffer_ptr - 3) == '\r' && - *(buffer_ptr - 2) == '\n' && - *(buffer_ptr - 1) == '\r' && - *(buffer_ptr - 0) == '\n') - break; - } - - buffer_ptr++; - } - *buffer_ptr = 0; - - // HTTP GET instruction - if (memcmp(buffer, "GET", 3) != 0) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NOT_GET; - - // Look for the version number and verify that it's supported - version = GetField(buffer, buffer_len, "Sec-WebSocket-Version:"); - if (version == NULL) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_VERSION; - if (buffer_end - version < 2 || (version0 != '8' && (version0 != '1' || version1 != '3'))) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_VERSION; - - // Make sure this connection comes from a known host - host = GetField(buffer, buffer_len, "Host:"); - if (host == NULL) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_HOST; - if (limit_host != NULL) - { - r_size_t limit_host_len = strnlen_s(limit_host, 128); - char* found = NULL; - if (strstr_s(host, (r_size_t) (buffer_end - host), limit_host, limit_host_len, &found) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_HOST; - } - - // Look for the key start and null-terminate it within the receive buffer - key = GetField(buffer, buffer_len, "Sec-WebSocket-Key:"); - if (key == NULL) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_KEY; - if (strstr_s(key, (r_size_t) (buffer_end - key), "\r\n", 2, &key_end) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_KEY; - *key_end = 0; - - // Concatenate the browser's key with the WebSocket Protocol GUID and base64 encode - // the hash, to prove to the browser that this is a bonafide WebSocket server - buffer0 = 0; - if (strncat_s(buffer, buffer_len, key, (r_size_t) (key_end - key)) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - if (strncat_s(buffer, buffer_len, websocket_guid, sizeof(websocket_guid)) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - hash = SHA1_Calculate(buffer, (rmtU32)strnlen_s(buffer, buffer_len)); - Base64_Encode(hash.data, sizeof(hash.data), (rmtU8*)buffer); - - // Send the response back to the server with a longer timeout than usual - response_buffer0 = 0; - if (strncat_s(response_buffer, response_buffer_len, websocket_response, sizeof(websocket_response)) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - if (strncat_s(response_buffer, response_buffer_len, buffer, buffer_len) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - if (strncat_s(response_buffer, response_buffer_len, "\r\n\r\n", 4) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - - return TCPSocket_Send(tcp_socket, response_buffer, (rmtU32)strnlen_s(response_buffer, response_buffer_len), 1000); -} - - -static rmtError WebSocket_Constructor(WebSocket* web_socket, TCPSocket* tcp_socket) -{ - rmtError error = RMT_ERROR_NONE; - - assert(web_socket != NULL); - web_socket->tcp_socket = tcp_socket; - web_socket->mode = WEBSOCKET_NONE; - web_socket->frame_bytes_remaining = 0; - web_socket->mask_offset = 0; - web_socket->data.mask0 = 0; - web_socket->data.mask1 = 0; - web_socket->data.mask2 = 0; - web_socket->data.mask3 = 0; - - // Caller can optionally specify which TCP socket to use - if (web_socket->tcp_socket == NULL) - New_0(TCPSocket, web_socket->tcp_socket); - - return error; -} - - -static void WebSocket_Destructor(WebSocket* web_socket) -{ - WebSocket_Close(web_socket); -} - - -static rmtError WebSocket_RunServer(WebSocket* web_socket, rmtU16 port, rmtBool reuse_open_port, rmtBool limit_connections_to_localhost, enum WebSocketMode mode) -{ - // Create the server's listening socket - assert(web_socket != NULL); - web_socket->mode = mode; - return TCPSocket_RunServer(web_socket->tcp_socket, port, reuse_open_port, limit_connections_to_localhost); -} - - -static void WebSocket_Close(WebSocket* web_socket) -{ - assert(web_socket != NULL); - Delete(TCPSocket, web_socket->tcp_socket); -} - - -static SocketStatus WebSocket_PollStatus(WebSocket* web_socket) -{ - assert(web_socket != NULL); - return TCPSocket_PollStatus(web_socket->tcp_socket); -} - - -static rmtError WebSocket_AcceptConnection(WebSocket* web_socket, WebSocket** client_socket) -{ - TCPSocket* tcp_socket = NULL; - rmtError error; - - // Is there a waiting connection? - assert(web_socket != NULL); - error = TCPSocket_AcceptConnection(web_socket->tcp_socket, &tcp_socket); - if (error != RMT_ERROR_NONE || tcp_socket == NULL) - return error; - - // Need a successful handshake between client/server before allowing the connection - // TODO: Specify limit_host - error = WebSocketHandshake(tcp_socket, NULL); - if (error != RMT_ERROR_NONE) - return error; - - // Allocate and return a new client socket - assert(client_socket != NULL); - New_1(WebSocket, *client_socket, tcp_socket); - if (error != RMT_ERROR_NONE) - return error; - - (*client_socket)->mode = web_socket->mode; - - return RMT_ERROR_NONE; -} - - -static void WriteSize(rmtU32 size, rmtU8* dest, rmtU32 dest_size, rmtU32 dest_offset) -{ - int size_size = dest_size - dest_offset; - rmtU32 i; - for (i = 0; i < dest_size; i++) - { - int j = i - dest_offset; - desti = (j < 0) ? 0 : (size >> ((size_size - j - 1) * 8)) & 0xFF; - } -} - - -// For send buffers to preallocate -#define WEBSOCKET_MAX_FRAME_HEADER_SIZE 10 - - -static void WebSocket_PrepareBuffer(Buffer* buffer) -{ - char empty_frame_headerWEBSOCKET_MAX_FRAME_HEADER_SIZE; - - assert(buffer != NULL); - - // Reset to start - buffer->bytes_used = 0; - - // Allocate enough space for a maximum-sized frame header - Buffer_Write(buffer, empty_frame_header, sizeof(empty_frame_header)); -} - - -static rmtU32 WebSocket_FrameHeaderSize(rmtU32 length) -{ - if (length <= 125) - return 2; - if (length <= 65535) - return 4; - return 10; -} - - -static void WebSocket_WriteFrameHeader(WebSocket* web_socket, rmtU8* dest, rmtU32 length) -{ - rmtU8 final_fragment = 0x1 << 7; - rmtU8 frame_type = (rmtU8)web_socket->mode; - - dest0 = final_fragment | frame_type; - - // Construct the frame header, correctly applying the narrowest size - if (length <= 125) - { - dest1 = (rmtU8)length; - } - else if (length <= 65535) - { - dest1 = 126; - WriteSize(length, dest + 2, 2, 0); - } - else - { - dest1 = 127; - WriteSize(length, dest + 2, 8, 4); - } -} - - -static rmtError WebSocket_Send(WebSocket* web_socket, const void* data, rmtU32 length, rmtU32 timeout_ms) -{ - rmtError error; - SocketStatus status; - rmtU32 payload_length, frame_header_size, delta; - - assert(web_socket != NULL); - assert(data != NULL); - - // Can't send if there are socket errors - status = WebSocket_PollStatus(web_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - - // Assume space for max frame header has been allocated in the incoming data - payload_length = length - WEBSOCKET_MAX_FRAME_HEADER_SIZE; - frame_header_size = WebSocket_FrameHeaderSize(payload_length); - delta = WEBSOCKET_MAX_FRAME_HEADER_SIZE - frame_header_size; - data = (void*)((rmtU8*)data + delta); - length -= delta; - WebSocket_WriteFrameHeader(web_socket, (rmtU8*)data, payload_length); - - // Send frame header and data together - error = TCPSocket_Send(web_socket->tcp_socket, data, length, timeout_ms); - return error; -} - - -static rmtError ReceiveFrameHeader(WebSocket* web_socket) -{ - // TODO: Specify infinite timeout? - - rmtError error; - rmtU8 msg_header2 = { 0, 0 }; - int msg_length, size_bytes_remaining; - rmtBool mask_present; - - assert(web_socket != NULL); - - // Get message header - error = TCPSocket_Receive(web_socket->tcp_socket, msg_header, 2, 20); - if (error != RMT_ERROR_NONE) - return error; - - // Check for WebSocket Protocol disconnect - if (msg_header0 == 0x88) - return RMT_ERROR_WEBSOCKET_DISCONNECTED; - - // Check that the client isn't sending messages we don't understand - if (msg_header0 != 0x81 && msg_header0 != 0x82) - return RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER; - - // Get message length and check to see if it's a marker for a wider length - msg_length = msg_header1 & 0x7F; - size_bytes_remaining = 0; - switch (msg_length) - { - case 126: size_bytes_remaining = 2; break; - case 127: size_bytes_remaining = 8; break; - } - - if (size_bytes_remaining > 0) - { - int i; - // Receive the wider bytes of the length - rmtU8 size_bytes8; - error = TCPSocket_Receive(web_socket->tcp_socket, size_bytes, size_bytes_remaining, 20); - if (error != RMT_ERROR_NONE) - return RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_SIZE; - - // Calculate new length, MSB first - msg_length = 0; - for (i = 0; i < size_bytes_remaining; i++) - msg_length |= size_bytesi << ((size_bytes_remaining - 1 - i) * 8); - } - - // Receive any message data masks - mask_present = (msg_header1 & 0x80) != 0 ? RMT_TRUE : RMT_FALSE; - if (mask_present) - { - error = TCPSocket_Receive(web_socket->tcp_socket, web_socket->data.mask, 4, 20); - if (error != RMT_ERROR_NONE) - return error; - } - - web_socket->frame_bytes_remaining = msg_length; - web_socket->mask_offset = 0; - - return RMT_ERROR_NONE; -} - - -static rmtError WebSocket_Receive(WebSocket* web_socket, void* data, rmtU32* msg_len, rmtU32 length, rmtU32 timeout_ms) -{ - SocketStatus status; - char* cur_data; - char* end_data; - rmtU32 start_ms, now_ms; - rmtU32 bytes_to_read; - rmtError error; - - assert(web_socket != NULL); - - // Can't read with any socket errors - status = WebSocket_PollStatus(web_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - - cur_data = (char*)data; - end_data = cur_data + length; - - start_ms = msTimer_Get(); - while (cur_data < end_data) - { - // Get next WebSocket frame if we've run out of data to read from the socket - if (web_socket->frame_bytes_remaining == 0) - { - error = ReceiveFrameHeader(web_socket); - if (error != RMT_ERROR_NONE) - return error; - - // Set output message length only on initial receive - if (msg_len != NULL) - *msg_len = web_socket->frame_bytes_remaining; - } - - // Read as much required data as possible - bytes_to_read = web_socket->frame_bytes_remaining < length ? web_socket->frame_bytes_remaining : length; - error = TCPSocket_Receive(web_socket->tcp_socket, cur_data, bytes_to_read, 20); - if (error == RMT_ERROR_SOCKET_RECV_FAILED) - return error; - - // If there's a stall receiving the data, check for timeout - if (error == RMT_ERROR_SOCKET_RECV_NO_DATA || error == RMT_ERROR_SOCKET_RECV_TIMEOUT) - { - now_ms = msTimer_Get(); - if (now_ms - start_ms > timeout_ms) - return RMT_ERROR_SOCKET_RECV_TIMEOUT; - continue; - } - - // Apply data mask - if (web_socket->data.mask_u32 != 0) - { - rmtU32 i; - for (i = 0; i < bytes_to_read; i++) - { - *((rmtU8*)cur_data + i) ^= web_socket->data.maskweb_socket->mask_offset & 3; - web_socket->mask_offset++; - } - } - - cur_data += bytes_to_read; - web_socket->frame_bytes_remaining -= bytes_to_read; - } - - return RMT_ERROR_NONE; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @MESSAGEQ: Multiple producer, single consumer message queue ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -typedef enum MessageID -{ - MsgID_NotReady, - MsgID_LogText, - MsgID_SampleTree, - MsgID_None, - MsgID_SendText, - MsgID_Force32Bits = 0xFFFFFFFF, -} MessageID; - - -typedef struct Message -{ - MessageID id; - - rmtU32 payload_size; - - // For telling which thread the message came from in the debugger - struct ThreadSampler* thread_sampler; - - rmtU8 payload1; -} Message; - - -// Multiple producer, single consumer message queue that uses its own data buffer -// to store the message data. -typedef struct rmtMessageQueue -{ - rmtU32 size; - - // The physical address of this data buffer is pointed to by two sequential - // virtual memory pages, allowing automatic wrap-around of any reads or writes - // that exceed the limits of the buffer. - VirtualMirrorBuffer* data; - - // Read/write position never wrap allowing trivial overflow checks - // with easier debugging - rmtU32 read_pos; - rmtU32 write_pos; - -} rmtMessageQueue; - - -static rmtError rmtMessageQueue_Constructor(rmtMessageQueue* queue, rmtU32 size) -{ - rmtError error; - - assert(queue != NULL); - - // Set defaults - queue->size = 0; - queue->data = NULL; - queue->read_pos = 0; - queue->write_pos = 0; - - New_2(VirtualMirrorBuffer, queue->data, size, 10); - if (error != RMT_ERROR_NONE) - return error; - - // The mirror buffer needs to be page-aligned and will change the requested - // size to match that. - queue->size = queue->data->size; - - // Set the entire buffer to not ready message - memset(queue->data->ptr, MsgID_NotReady, queue->size); - - return RMT_ERROR_NONE; -} - - -static void rmtMessageQueue_Destructor(rmtMessageQueue* queue) -{ - assert(queue != NULL); - Delete(VirtualMirrorBuffer, queue->data); -} - - -static rmtU32 rmtMessageQueue_SizeForPayload(rmtU32 payload_size) -{ - // Add message header and align for ARM platforms - rmtU32 size = sizeof(Message) + payload_size; -#ifdef GPAC_64_BITS - size = (size + 7) & ~7U; -#else - size = (size + 3) & ~3U; -#endif - - return size; -} - - -static Message* rmtMessageQueue_AllocMessage(rmtMessageQueue* queue, rmtU32 payload_size, struct ThreadSampler* thread_sampler) -{ - Message* msg; - - rmtU32 write_size = rmtMessageQueue_SizeForPayload(payload_size); - - assert(queue != NULL); - - for (;;) - { - // Check for potential overflow - // Order of loads means allocation failure can happen when enough space has just been freed - // However, incorrect overflows are not possible - rmtU32 s = queue->size; - rmtU32 w = LoadAcquire(&queue->write_pos); - rmtU32 r = LoadAcquire(&queue->read_pos); - if ((int)(w - r) > ((int)(s - write_size))) - return NULL; - - // Point to the newly allocated space - msg = (Message*)(queue->data->ptr + (w & (s - 1))); - - // Increment the write position, leaving the loop if this is the thread that succeeded - if (AtomicCompareAndSwap(&queue->write_pos, w, w + write_size) == RMT_TRUE) - { - // Safe to set payload size after thread claims ownership of this allocated range - msg->payload_size = payload_size; - msg->thread_sampler = thread_sampler; - break; - } - } - - return msg; -} - - -static void rmtMessageQueue_CommitMessage(Message* message, MessageID id) -{ - assert(message != NULL); - - // Setting the message ID signals to the consumer that the message is ready - assert(LoadAcquire((rmtU32*)&message->id) == MsgID_NotReady); - StoreRelease((rmtU32*)&message->id, id); -} - - -Message* rmtMessageQueue_PeekNextMessage(rmtMessageQueue* queue) -{ - Message* ptr; - rmtU32 r, w; - MessageID id; - - assert(queue != NULL); - - // First check that there are bytes queued - w = LoadAcquire(&queue->write_pos); - r = queue->read_pos; - if (w - r == 0) - return NULL; - - // Messages are in the queue but may not have been commit yet - // Messages behind this one may have been commit but it's not reachable until - // the next one in the queue is ready. - r = r & (queue->size - 1); - ptr = (Message*)(queue->data->ptr + r); - id = (MessageID)LoadAcquire((rmtU32*)&ptr->id); - if (id != MsgID_NotReady) - return ptr; - - return NULL; -} - - -static void rmtMessageQueue_ConsumeNextMessage(rmtMessageQueue* queue, Message* message) -{ - rmtU32 message_size, read_pos; - - assert(queue != NULL); - assert(message != NULL); - - // Setting the message ID to "not ready" serves as a marker to the consumer that even though - // space has been allocated for a message, the message isn't ready to be consumed - // yet. - // - // We can't do that when allocating the message because multiple threads will be fighting for - // the same location. Instead, clear out any messages just read by the consumer before advancing - // the read position so that a winning thread's allocation will inherit the "not ready" state. - // - // This costs some write bandwidth and has the potential to flush cache to other cores. - message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - memset(message, MsgID_NotReady, message_size); - - // Advance read position - read_pos = queue->read_pos + message_size; - StoreRelease(&queue->read_pos, read_pos); -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @NETWORK: Network Server ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - -typedef rmtError (*Server_ReceiveHandler)(void*, char*, rmtU32); - - -typedef struct -{ - WebSocket* listen_socket; - - WebSocket* client_socket; - - rmtU32 last_ping_time; - - rmtU16 port; - - rmtBool reuse_open_port; - rmtBool limit_connections_to_localhost; - - // A dynamically-sized buffer used for binary-encoding messages and sending to the client - Buffer* bin_buf; - - // Handler for receiving messages from the client - Server_ReceiveHandler receive_handler; - void* receive_handler_context; -} Server; - - -static rmtError Server_CreateListenSocket(Server* server, rmtU16 port, rmtBool reuse_open_port, rmtBool limit_connections_to_localhost) -{ - rmtError error = RMT_ERROR_NONE; - - New_1(WebSocket, server->listen_socket, NULL); - if (error == RMT_ERROR_NONE) - error = WebSocket_RunServer(server->listen_socket, port, reuse_open_port, limit_connections_to_localhost, WEBSOCKET_BINARY); - - return error; -} - - -static rmtError Server_Constructor(Server* server, rmtU16 port, rmtBool reuse_open_port, rmtBool limit_connections_to_localhost) -{ - rmtError error; - - assert(server != NULL); - server->listen_socket = NULL; - server->client_socket = NULL; - server->last_ping_time = 0; - server->port = port; - server->reuse_open_port = reuse_open_port; - server->limit_connections_to_localhost = limit_connections_to_localhost; - server->bin_buf = NULL; - server->receive_handler = NULL; - server->receive_handler_context = NULL; - - // Create the binary serialisation buffer - New_1(Buffer, server->bin_buf, 4096); - if (error != RMT_ERROR_NONE) - return error; - - // Create the listening WebSocket - return Server_CreateListenSocket(server, port, reuse_open_port, limit_connections_to_localhost); -} - - -static void Server_Destructor(Server* server) -{ - assert(server != NULL); - Delete(WebSocket, server->client_socket); - Delete(WebSocket, server->listen_socket); - Delete(Buffer, server->bin_buf); -} - - -static rmtBool Server_IsClientConnected(Server* server) -{ - assert(server != NULL); - return server->client_socket != NULL ? RMT_TRUE : RMT_FALSE; -} - - -static void Server_DisconnectClient(Server* server) -{ - WebSocket* client_socket; - - assert(server != NULL); - - // NULL the variable before destroying the socket - client_socket = server->client_socket; - server->client_socket = NULL; - CompilerWriteFence(); - Delete(WebSocket, client_socket); -} - - -static rmtError Server_Send(Server* server, const void* data, rmtU32 length, rmtU32 timeout) -{ - assert(server != NULL); - if (Server_IsClientConnected(server)) - { - rmtError error = WebSocket_Send(server->client_socket, data, length, timeout); - if (error == RMT_ERROR_SOCKET_SEND_FAIL) - Server_DisconnectClient(server); - - return error; - } - - return RMT_ERROR_NONE; -} - - -static rmtError Server_ReceiveMessage(Server* server, char message_first_byte, rmtU32 message_length) -{ - char message_data1024; - rmtError error; - - // Check for potential message data overflow - if (message_length >= sizeof(message_data) - 1) - { - rmt_LogText("Ignoring console input bigger than internal receive buffer (1024 bytes)"); - return RMT_ERROR_NONE; - } - - // Receive the rest of the message - message_data0 = message_first_byte; - error = WebSocket_Receive(server->client_socket, message_data + 1, NULL, message_length - 1, 100); - if (error != RMT_ERROR_NONE) - return error; - message_datamessage_length = 0; - - // Each message must have a descriptive 4 byte header - if (message_length < 4) - return RMT_ERROR_NONE; - - // Dispatch to handler - if (server->receive_handler) - error = server->receive_handler(server->receive_handler_context, message_data, message_length); - - return error; -} - - -static void Server_Update(Server* server) -{ - rmtU32 cur_time; - - assert(server != NULL); - - // Recreate the listening socket if it's been destroyed earlier - if (server->listen_socket == NULL) - Server_CreateListenSocket(server, server->port, server->reuse_open_port, server->limit_connections_to_localhost); - - if (server->listen_socket != NULL && server->client_socket == NULL) - { - // Accept connections as long as there is no client connected - WebSocket* client_socket = NULL; - rmtError error = WebSocket_AcceptConnection(server->listen_socket, &client_socket); - if (error == RMT_ERROR_NONE) - { - server->client_socket = client_socket; - } - else - { - // Destroy the listen socket on failure to accept - // It will get recreated in another update - Delete(WebSocket, server->listen_socket); - } - } - - else - { - // Loop checking for incoming messages - for (;;) - { - // Inspect first byte to see if a message is there - char message_first_byte=0; - rmtU32 message_length=0; - rmtError error = WebSocket_Receive(server->client_socket, &message_first_byte, &message_length, 1, 0); - if (error == RMT_ERROR_NONE) - { - // Parse remaining message - error = Server_ReceiveMessage(server, message_first_byte, message_length); - if (error != RMT_ERROR_NONE) - { - Server_DisconnectClient(server); - break; - } - - // Check for more... - continue; - } - - // Passable errors... - if (error == RMT_ERROR_SOCKET_RECV_NO_DATA) - { - // No data available - break; - } - - if (error == RMT_ERROR_SOCKET_RECV_TIMEOUT) - { - // Data not available yet, can afford to ignore as we're only reading the first byte - break; - } - - // Anything else is an error that may have closed the connection - Server_DisconnectClient(server); - break; - } - } - - // Send pings to the client every second - cur_time = msTimer_Get(); - if (cur_time - server->last_ping_time > 1000) - { - Buffer* bin_buf = server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - Buffer_WriteStringZ(bin_buf, "PING"); - Server_Send(server, bin_buf->data, bin_buf->bytes_used, 10); - server->last_ping_time = cur_time; - } -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SAMPLE: Base Sample Description for CPU by default ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#define SAMPLE_NAME_LEN 128 - - -typedef enum SampleType -{ - SampleType_CPU, - SampleType_CUDA, - SampleType_D3D11, - SampleType_OpenGL, - SampleType_Metal, - SampleType_Count, -} SampleType; - - -typedef struct Sample -{ - // Inherit so that samples can be quickly allocated - ObjectLink Link; - - enum SampleType type; - - // Used to anonymously copy sample data without knowning its type - rmtU32 size_bytes; - - // Hash generated from sample name - rmtU32 name_hash; - - // Unique, persistent ID among all samples - rmtU32 unique_id; - - // Null-terminated string storing the hash-prefixed 6-digit colour - rmtU8 unique_id_html_colour8; - - // Links to related samples in the tree - struct Sample* parent; - struct Sample* first_child; - struct Sample* last_child; - struct Sample* next_sibling; - - // Keep track of child count to distinguish from repeated calls to the same function at the same stack level - // This is also mixed with the callstack hash to allow consistent addressing of any point in the tree - rmtU32 nb_children; - - // Sample end points and length in microseconds - rmtU64 us_start; - rmtU64 us_end; - rmtU64 us_length; - - // Total sampled length of all children - rmtU64 us_sampled_length; - - // Number of times this sample was used in a call in aggregate mode, 1 otherwise - rmtU32 call_count; - - // Current and maximum sample recursion depths - rmtU16 recurse_depth; - rmtU16 max_recurse_depth; - -} Sample; - - -static rmtError Sample_Constructor(Sample* sample) -{ - assert(sample != NULL); - - ObjectLink_Constructor((ObjectLink*)sample); - - sample->type = SampleType_CPU; - sample->size_bytes = sizeof(Sample); - sample->name_hash = 0; - sample->unique_id = 0; - sample->unique_id_html_colour0 = '#'; - sample->unique_id_html_colour1 = 0; - sample->unique_id_html_colour7 = 0; - sample->parent = NULL; - sample->first_child = NULL; - sample->last_child = NULL; - sample->next_sibling = NULL; - sample->nb_children = 0; - sample->us_start = 0; - sample->us_end = 0; - sample->us_length = 0; - sample->us_sampled_length =0; - sample->call_count = 0; - sample->recurse_depth = 0; - sample->max_recurse_depth = 0; - - return RMT_ERROR_NONE; -} - - -static void Sample_Destructor(Sample* sample) -{ - RMT_UNREFERENCED_PARAMETER(sample); -} - - -static void Sample_Prepare(Sample* sample, rmtU32 name_hash, Sample* parent) -{ - sample->name_hash = name_hash; - sample->unique_id = 0; - sample->parent = parent; - sample->first_child = NULL; - sample->last_child = NULL; - sample->next_sibling = NULL; - sample->nb_children = 0; - sample->us_start = 0; - sample->us_end = 0; - sample->us_length = 0; - sample->us_sampled_length = 0; - sample->call_count = 1; - sample->recurse_depth = 0; - sample->max_recurse_depth = 0; -} - - -#define BIN_ERROR_CHECK(stmt) { error = stmt; if (error != RMT_ERROR_NONE) return error; } - - -static rmtError bin_SampleArray(Buffer* buffer, Sample* first_sample); - - -static rmtError bin_Sample(Buffer* buffer, Sample* sample) -{ - rmtError error; - - assert(sample != NULL); - - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, sample->name_hash)); - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, sample->unique_id)); - BIN_ERROR_CHECK(Buffer_Write(buffer, sample->unique_id_html_colour, 7)); - BIN_ERROR_CHECK(Buffer_WriteU64(buffer, sample->us_start)); - BIN_ERROR_CHECK(Buffer_WriteU64(buffer, sample->us_length)); - BIN_ERROR_CHECK(Buffer_WriteU64(buffer, maxS64(sample->us_length - sample->us_sampled_length, 0))); - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, sample->call_count)); - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, sample->max_recurse_depth)); - BIN_ERROR_CHECK(bin_SampleArray(buffer, sample)); - - return RMT_ERROR_NONE; -} - - -static rmtError bin_SampleArray(Buffer* buffer, Sample* parent_sample) -{ - rmtError error; - Sample* sample; - - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, parent_sample->nb_children)); - for (sample = parent_sample->first_child; sample != NULL; sample = sample->next_sibling) - BIN_ERROR_CHECK(bin_Sample(buffer, sample)); - - return RMT_ERROR_NONE; -} - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SAMPLETREE: A tree of samples with their allocator ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -typedef struct SampleTree -{ - // Allocator for all samples - ObjectAllocator* allocator; - - // Root sample for all samples created by this thread - Sample* root; - - // Most recently pushed sample - Sample* current_parent; - -} SampleTree; - - -static rmtError SampleTree_Constructor(SampleTree* tree, rmtU32 sample_size, ObjConstructor constructor, ObjDestructor destructor) -{ - rmtError error; - - assert(tree != NULL); - - tree->allocator = NULL; - tree->root = NULL; - tree->current_parent = NULL; - - // Create the sample allocator - New_3(ObjectAllocator, tree->allocator, sample_size, constructor, destructor); - if (error != RMT_ERROR_NONE) - return error; - - // Create a root sample that's around for the lifetime of the thread - error = ObjectAllocator_Alloc(tree->allocator, (void**)&tree->root); - if (error != RMT_ERROR_NONE) - return error; - Sample_Prepare(tree->root, 0, NULL); - tree->current_parent = tree->root; - - return RMT_ERROR_NONE; -} - - -static void SampleTree_Destructor(SampleTree* tree) -{ - assert(tree != NULL); - - if (tree->root != NULL) - { - ObjectAllocator_Free(tree->allocator, tree->root); - tree->root = NULL; - } - - Delete(ObjectAllocator, tree->allocator); -} - - -static rmtU32 HashCombine(rmtU32 hash_a, rmtU32 hash_b) -{ - // A sequence of 32 uniformly random bits so that each bit of the combined hash is changed on application - // Derived from the golden ratio: UINT_MAX / ((1 + sqrt(5)) / 2) - // In reality it's just an arbitrary value which happens to work well, avoiding mapping all zeros to zeros. - // http://burtleburtle.net/bob/hash/doobs.html - static rmtU32 random_bits = 0x9E3779B9; - hash_a ^= hash_b + random_bits + (hash_a << 6) + (hash_a >> 2); - return hash_a; -} - - -static rmtError SampleTree_Push(SampleTree* tree, rmtU32 name_hash, rmtU32 flags, Sample** sample) -{ - Sample* parent; - rmtError error; - rmtU32 unique_id; - - // As each tree has a root sample node allocated, a parent must always be present - assert(tree != NULL); - assert(tree->current_parent != NULL); - parent = tree->current_parent; - - if ((flags & RMTSF_Aggregate) != 0) - { - // Linear search for previous instance of this sample name - Sample* sibling; - for (sibling = parent->first_child; sibling != NULL; sibling = sibling->next_sibling) - { - if (sibling->name_hash == name_hash) - { - tree->current_parent = sibling; - sibling->call_count++; - *sample = sibling; - return RMT_ERROR_NONE; - } - } - } - - // Collapse sample on recursion - if ((flags & RMTSF_Recursive) != 0 && parent->name_hash == name_hash) - { - parent->recurse_depth++; - parent->max_recurse_depth = maxU16(parent->max_recurse_depth, parent->recurse_depth); - parent->call_count++; - *sample = parent; - return RMT_ERROR_RECURSIVE_SAMPLE; - } - - // Allocate a new sample - error = ObjectAllocator_Alloc(tree->allocator, (void**)sample); - if (error != RMT_ERROR_NONE) - return error; - Sample_Prepare(*sample, name_hash, parent); - - // Generate a unique ID for this sample in the tree - unique_id = parent->unique_id; - unique_id = HashCombine(unique_id, (*sample)->name_hash); - unique_id = HashCombine(unique_id, parent->nb_children); - (*sample)->unique_id = unique_id; - - // Add sample to its parent - parent->nb_children++; - if (parent->first_child == NULL) - { - parent->first_child = *sample; - parent->last_child = *sample; - } - else - { - assert(parent->last_child != NULL); - parent->last_child->next_sibling = *sample; - parent->last_child = *sample; - } - - // Make this sample the new parent of any newly created samples - tree->current_parent = *sample; - - return RMT_ERROR_NONE; -} - - -static void SampleTree_Pop(SampleTree* tree, Sample* sample) -{ - assert(tree != NULL); - assert(sample != NULL); - assert(sample != tree->root); - tree->current_parent = sample->parent; -} - - -static ObjectLink* FlattenSampleTree(Sample* sample, rmtU32* nb_samples) -{ - Sample* child; - ObjectLink* cur_link = &sample->Link; - - assert(sample != NULL); - assert(nb_samples != NULL); - - *nb_samples += 1; - sample->Link.next = (ObjectLink*)sample->first_child; - - // Link all children together - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - ObjectLink* last_link = FlattenSampleTree(child, nb_samples); - last_link->next = (ObjectLink*)child->next_sibling; - cur_link = last_link; - } - - // Clear child info - sample->first_child = NULL; - sample->last_child = NULL; - sample->nb_children = 0; - - return cur_link; -} - - -static void FreeSampleTree(Sample* sample, ObjectAllocator* allocator) -{ - // Chain all samples together in a flat list - rmtU32 nb_cleared_samples = 0; - ObjectLink* last_link = FlattenSampleTree(sample, &nb_cleared_samples); - - // Release the complete sample memory range - if (sample->Link.next != NULL) - ObjectAllocator_FreeRange(allocator, sample, last_link, nb_cleared_samples); - else - ObjectAllocator_Free(allocator, sample); -} - - -typedef struct Msg_SampleTree -{ - Sample* root_sample; - - ObjectAllocator* allocator; - - rmtPStr thread_name; -} Msg_SampleTree; - - -static void AddSampleTreeMessage(rmtMessageQueue* queue, Sample* sample, ObjectAllocator* allocator, rmtPStr thread_name, struct ThreadSampler* thread_sampler) -{ - Msg_SampleTree* payload; - - // Attempt to allocate a message for sending the tree to the viewer - Message* message = rmtMessageQueue_AllocMessage(queue, sizeof(Msg_SampleTree), thread_sampler); - if (message == NULL) - { - // Discard the tree on failure - FreeSampleTree(sample, allocator); - return; - } - - // Populate and commit - payload = (Msg_SampleTree*)message->payload; - payload->root_sample = sample; - payload->allocator = allocator; - payload->thread_name = thread_name; - rmtMessageQueue_CommitMessage(message, MsgID_SampleTree); -} - - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TSAMPLER: Per-Thread Sampler ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#if RMT_USE_D3D11 -typedef struct D3D11 D3D11; -static rmtError D3D11_Create(D3D11** d3d11); -static void D3D11_Destructor(D3D11* d3d11); -#endif - - -typedef struct ThreadSampler -{ - // Name to assign to the thread in the viewer - rmtS8 name256; - - // Store a unique sample tree for each type - SampleTree* sample_treesSampleType_Count; - - // Table of all sample names encountered on this thread - StringTable* names; - -#if RMT_USE_D3D11 - D3D11* d3d11; -#endif - - // Next in the global list of active thread samplers - struct ThreadSampler* volatile next; - -} ThreadSampler; - - -static rmtError ThreadSampler_Constructor(ThreadSampler* thread_sampler) -{ - rmtError error; - int i; - - assert(thread_sampler != NULL); - - // Set defaults - for (i = 0; i < SampleType_Count; i++) - thread_sampler->sample_treesi = NULL; - thread_sampler->names = NULL; - thread_sampler->next = NULL; - #if RMT_USE_D3D11 - thread_sampler->d3d11 = NULL; - #endif - - // Set the initial name to Thread0 etc. or use the existing Linux name. - thread_sampler->name0 = 0; - #if defined(RMT_PLATFORM_LINUX) && RMT_USE_POSIX_THREADNAMES && !defined(__FreeBSD__) && !defined(__OpenBSD__) - prctl(PR_GET_NAME,thread_sampler->name,0,0,0); - #else - { - static rmtS32 countThreads = 0; - strncat_s(thread_sampler->name, sizeof(thread_sampler->name), "Thread", 6); - itoahex_s(thread_sampler->name + 6, sizeof(thread_sampler->name) - 6, AtomicAdd(&countThreads, 1)); - } - #endif - - // Create the CPU sample tree only - the rest are created on-demand as they need - // extra context information to function correctly. - New_3(SampleTree, thread_sampler->sample_treesSampleType_CPU, sizeof(Sample), (ObjConstructor)Sample_Constructor, (ObjDestructor)Sample_Destructor); - if (error != RMT_ERROR_NONE) - return error; - - // Create sample name string table - New_0(StringTable, thread_sampler->names); - if (error != RMT_ERROR_NONE) - return error; - - #if RMT_USE_D3D11 - error = D3D11_Create(&thread_sampler->d3d11); - if (error != RMT_ERROR_NONE) - return error; - #endif - - return RMT_ERROR_NONE; -} - - -static void ThreadSampler_Destructor(ThreadSampler* ts) -{ - int i; - - assert(ts != NULL); - - #if RMT_USE_D3D11 - Delete(D3D11, ts->d3d11); - #endif - - Delete(StringTable, ts->names); - - for (i = 0; i < SampleType_Count; i++) - Delete(SampleTree, ts->sample_treesi); -} - - -static rmtError ThreadSampler_Push(SampleTree* tree, rmtU32 name_hash, rmtU32 flags, Sample** sample) -{ - return SampleTree_Push(tree, name_hash, flags, sample); -} - - -static rmtBool ThreadSampler_Pop(ThreadSampler* ts, rmtMessageQueue* queue, Sample* sample) -{ - SampleTree* tree = ts->sample_treessample->type; - SampleTree_Pop(tree, sample); - - // Are we back at the root? - if (tree->current_parent == tree->root) - { - // Disconnect all samples from the root and pack in the chosen message queue - Sample* root = tree->root; - if (root) { - root->first_child = NULL; - root->last_child = NULL; - root->nb_children = 0; - } - AddSampleTreeMessage(queue, sample, tree->allocator, ts->name, ts); - - return RMT_TRUE; - } - - return RMT_FALSE; -} - - -static rmtU32 ThreadSampler_GetNameHash(ThreadSampler* ts, rmtPStr name, rmtU32* hash_cache) -{ - rmtU32 name_hash = 0; - - // Hash cache provided? - if (hash_cache != NULL) - { - // Calculate the hash first time round only - if (*hash_cache == 0) - { - assert(name != NULL); - *hash_cache = MurmurHash3_x86_32(name, (int)strnlen_s(name, 256), 0); - - // Also add to the string table on its first encounter - StringTable_Insert(ts->names, *hash_cache, name); - } - - return *hash_cache; - } - - // Have to recalculate and speculatively insert the name every time when no cache storage exists - name_hash = MurmurHash3_x86_32(name, (int)strnlen_s(name, 256), 0); - StringTable_Insert(ts->names, name_hash, name); - return name_hash; -} - - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @REMOTERY: Remotery ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#if RMT_USE_OPENGL -typedef struct OpenGL_t OpenGL; -static rmtError OpenGL_Create(OpenGL** opengl); -static void OpenGL_Destructor(OpenGL* opengl); -#endif - - -#if RMT_USE_METAL -typedef struct Metal_t Metal; -static rmtError Metal_Create(Metal** metal); -static void Metal_Destructor(Metal* metal); -#endif - - -struct Remotery -{ - Server* server; - - // Microsecond accuracy timer for CPU timestamps - usTimer timer; - - rmtTLS thread_sampler_tls_handle; - - // Linked list of all known threads being sampled - ThreadSampler* volatile first_thread_sampler; - - // Queue between clients and main remotery thread - rmtMessageQueue* mq_to_rmt_thread; - - // The main server thread - rmtThread* thread; - - // Set to trigger a map of each message on the remotery thread message queue - void (*map_message_queue_fn)(Remotery* rmt, Message*); - void* map_message_queue_data; - -#if RMT_USE_CUDA - rmtCUDABind cuda; -#endif - -#if RMT_USE_OPENGL - OpenGL* opengl; -#endif - -#if RMT_USE_METAL - Metal* metal; -#endif - - rmtBool sampling_disabled; -}; - - -// -// Global remotery context -// -static Remotery* g_Remotery = NULL; - - -// -// This flag marks the EXE/DLL that created the global remotery instance. We want to allow -// only the creating EXE/DLL to destroy the remotery instance. -// -static rmtBool g_RemoteryCreated = RMT_FALSE; - - -static void Remotery_DestroyThreadSamplers(Remotery* rmt); - - -static const rmtU8 g_DecimalToHex17 = "0123456789abcdef"; - - -static void GetSampleDigest(Sample* sample, rmtU32* digest_hash, rmtU32* nb_samples) -{ - Sample* child; - - assert(sample != NULL); - assert(digest_hash != NULL); - assert(nb_samples != NULL); - - // Concatenate this sample - (*nb_samples)++; - *digest_hash = HashCombine(*digest_hash, sample->unique_id); - - { - rmtU8 shift = 4; - - // Get 6 nibbles for lower 3 bytes of the name hash - rmtU8* sample_id = (rmtU8*)&sample->name_hash; - rmtU8 hex_sample_id6; - hex_sample_id0 = sample_id0 & 15; - hex_sample_id1 = sample_id0 >> 4; - hex_sample_id2 = sample_id1 & 15; - hex_sample_id3 = sample_id1 >> 4; - hex_sample_id4 = sample_id2 & 15; - hex_sample_id5 = sample_id2 >> 4; - - // As the nibbles will be used as hex colour digits, shift them up to make pastel colours - hex_sample_id0 = minU8(hex_sample_id0 + shift, 15); - hex_sample_id1 = minU8(hex_sample_id1 + shift, 15); - hex_sample_id2 = minU8(hex_sample_id2 + shift, 15); - hex_sample_id3 = minU8(hex_sample_id3 + shift, 15); - hex_sample_id4 = minU8(hex_sample_id4 + shift, 15); - hex_sample_id5 = minU8(hex_sample_id5 + shift, 15); - - // Convert the nibbles to hex for the final colour - sample->unique_id_html_colour1 = g_DecimalToHexhex_sample_id0; - sample->unique_id_html_colour2 = g_DecimalToHexhex_sample_id1; - sample->unique_id_html_colour3 = g_DecimalToHexhex_sample_id2; - sample->unique_id_html_colour4 = g_DecimalToHexhex_sample_id3; - sample->unique_id_html_colour5 = g_DecimalToHexhex_sample_id4; - sample->unique_id_html_colour6 = g_DecimalToHexhex_sample_id5; - } - - // Concatenate children - for (child = sample->first_child; child != NULL; child = child->next_sibling) - GetSampleDigest(child, digest_hash, nb_samples); -} - - -static rmtError Remotery_SendLogTextMessage(Remotery* rmt, Message* message) -{ - Buffer* bin_buf; - - assert(rmt != NULL); - assert(message != NULL); - - bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - Buffer_Write(bin_buf, message->payload, message->payload_size); - - return Server_Send(rmt->server, bin_buf->data, bin_buf->bytes_used, 20); -} - - -static rmtError bin_SampleTree(Buffer* buffer, Msg_SampleTree* msg) -{ - Sample* root_sample; - char thread_name256; - rmtU32 digest_hash = 0, nb_samples = 0; - rmtError error; - - assert(buffer != NULL); - assert(msg != NULL); - - // Get the message root sample - root_sample = msg->root_sample; - assert(root_sample != NULL); - - // Add any sample types as a thread name post-fix to ensure they get their own viewer - thread_name0 = 0; - strncat_s(thread_name, sizeof(thread_name), msg->thread_name, strnlen_s(msg->thread_name, 255)); - if (root_sample->type == SampleType_CUDA) - strncat_s(thread_name, sizeof(thread_name), " (CUDA)", 7); - if (root_sample->type == SampleType_D3D11) - strncat_s(thread_name, sizeof(thread_name), " (D3D11)", 8); - if (root_sample->type == SampleType_OpenGL) - strncat_s(thread_name, sizeof(thread_name), " (OpenGL)", 9); - if (root_sample->type == SampleType_Metal) - strncat_s(thread_name, sizeof(thread_name), " (Metal)", 8); - - // Get digest hash of samples so that viewer can efficiently rebuild its tables - GetSampleDigest(root_sample, &digest_hash, &nb_samples); - - // Write global message header - BIN_ERROR_CHECK(Buffer_Write(buffer, (void*)"SMPL ", 8)); - - // Write sample message header - BIN_ERROR_CHECK(Buffer_WriteStringWithLength(buffer, thread_name)); - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, nb_samples)); - BIN_ERROR_CHECK(Buffer_WriteU32(buffer, digest_hash)); - - // Write entire sample tree - BIN_ERROR_CHECK(bin_Sample(buffer, root_sample)); - - // Patch message size - U32ToByteArray(buffer->data + 4, buffer->bytes_used); - - return RMT_ERROR_NONE; -} - - - -#if RMT_USE_CUDA -static rmtBool AreCUDASamplesReady(Sample* sample); -static rmtBool GetCUDASampleTimes(Sample* root_sample, Sample* sample); -#endif - - -static rmtError Remotery_SendSampleTreeMessage(Remotery* rmt, Message* message) -{ - Msg_SampleTree* sample_tree; - rmtError error = RMT_ERROR_NONE; - Sample* sample; - Buffer* bin_buf; - - assert(rmt != NULL); - assert(message != NULL); - - // Get the message root sample - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->root_sample; - assert(sample != NULL); - - #if RMT_USE_CUDA - if (sample->type == SampleType_CUDA) - { - // If these CUDA samples aren't ready yet, stick them to the back of the queue and continue - rmtBool are_samples_ready; - rmt_BeginCPUSample(AreCUDASamplesReady, 0); - are_samples_ready = AreCUDASamplesReady(sample); - rmt_EndCPUSample(); - if (!are_samples_ready) - { - AddSampleTreeMessage(rmt->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->thread_name, message->thread_sampler); - return RMT_ERROR_NONE; - } - - // Retrieve timing of all CUDA samples - rmt_BeginCPUSample(GetCUDASampleTimes, 0); - GetCUDASampleTimes(sample->parent, sample); - rmt_EndCPUSample(); - } - #endif - - // Reset the buffer for sending a websocket message - bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - - // Serialise the sample tree and send to the viewer with a reasonably long timeout as the size - // of the sample data may be large - rmt_BeginCPUSample(bin_SampleTree, RMTSF_Aggregate); - error = bin_SampleTree(bin_buf, sample_tree); - rmt_EndCPUSample(); - if (error == RMT_ERROR_NONE) - { - rmt_BeginCPUSample(Server_Send, RMTSF_Aggregate); - error = Server_Send(rmt->server, bin_buf->data, bin_buf->bytes_used, 50000); - rmt_EndCPUSample(); - } - - // Release the sample tree back to its allocator - FreeSampleTree(sample, sample_tree->allocator); - - return error; -} - - -static rmtError Remotery_ConsumeMessageQueue(Remotery* rmt) -{ - rmtU32 nb_messages_sent = 0; - const rmtU32 maxNbMessagesPerUpdate = g_Settings.maxNbMessagesPerUpdate; - - assert(rmt != NULL); - - // Absorb as many messages in the queue while disconnected - if (Server_IsClientConnected(rmt->server) == RMT_FALSE) - return RMT_ERROR_NONE; - - // Loop reading the max number of messages for this update - while( nb_messages_sent++ < maxNbMessagesPerUpdate ) - { - rmtError error = RMT_ERROR_NONE; - Message* message = rmtMessageQueue_PeekNextMessage(rmt->mq_to_rmt_thread); - if (message == NULL) - break; - - switch (message->id) - { - // This shouldn't be possible - case MsgID_NotReady: - assert(RMT_FALSE); - break; - - // Dispatch to message handler - case MsgID_LogText: - case MsgID_SendText: - error = Remotery_SendLogTextMessage(rmt, message); - break; - case MsgID_SampleTree: - rmt_BeginCPUSample(SendSampleTreeMessage, RMTSF_Aggregate); - error = Remotery_SendSampleTreeMessage(rmt, message); - rmt_EndCPUSample(); - break; - - default: - break; - } - - // Consume the message before reacting to any errors - rmtMessageQueue_ConsumeNextMessage(rmt->mq_to_rmt_thread, message); - if (error != RMT_ERROR_NONE) - return error; - } - - return RMT_ERROR_NONE; -} - - -static void Remotery_FlushMessageQueue(Remotery* rmt) -{ - assert(rmt != NULL); - - // Loop reading all remaining messages - for (;;) - { - Message* message = rmtMessageQueue_PeekNextMessage(rmt->mq_to_rmt_thread); - if (message == NULL) - break; - - switch (message->id) - { - // These can be safely ignored - case MsgID_NotReady: - case MsgID_LogText: - break; - - // Release all samples back to their allocators - case MsgID_SampleTree: - { - Msg_SampleTree* sample_tree = (Msg_SampleTree*)message->payload; - FreeSampleTree(sample_tree->root_sample, sample_tree->allocator); - break; - } - - default: - break; - } - - rmtMessageQueue_ConsumeNextMessage(rmt->mq_to_rmt_thread, message); - } -} - - -static void Remotery_MapMessageQueue(Remotery* rmt) -{ - rmtU32 read_pos, write_pos; - rmtMessageQueue* queue; - - assert(rmt != NULL); - - // Wait until the caller sets the custom data - while (LoadAcquirePointer((long* volatile*)&rmt->map_message_queue_data) == NULL) - msSleep(1); - - // Snapshot the current write position so that we're not constantly chasing other threads - // that can have no effect on the thread requesting the map. - queue = rmt->mq_to_rmt_thread; - write_pos = LoadAcquire(&queue->write_pos); - - // Walk every message in the queue and call the map function - read_pos = queue->read_pos; - while (read_pos < write_pos) - { - rmtU32 r = read_pos & (queue->size - 1); - Message* message = (Message*)(queue->data->ptr + r); - rmtU32 message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - rmt->map_message_queue_fn(rmt, message); - read_pos += message_size; - } - - StoreReleasePointer((long* volatile*)&rmt->map_message_queue_data, NULL); -} - - -static rmtError Remotery_ThreadMain(rmtThread* thread) -{ - Remotery* rmt = (Remotery*)thread->param; - assert(rmt != NULL); - - rmt_SetCurrentThreadName("Remotery"); - - while (thread->request_exit == RMT_FALSE) - { - rmt_BeginCPUSample(Wakeup, 0); - - rmt_BeginCPUSample(ServerUpdate, 0); - Server_Update(rmt->server); - rmt_EndCPUSample(); - - rmt_BeginCPUSample(ConsumeMessageQueue, 0); - Remotery_ConsumeMessageQueue(rmt); - rmt_EndCPUSample(); - - rmt_EndCPUSample(); - - // Process any queue map requests - if (LoadAcquirePointer((long* volatile*)&rmt->map_message_queue_fn) != NULL) - { - Remotery_MapMessageQueue(rmt); - StoreReleasePointer((long* volatile*)&rmt->map_message_queue_fn, NULL); - } - - // - // NOTE-A - // - // Possible sequence of user events at this point: - // - // 1. Add samples to the queue. - // 2. Shutdown remotery. - // - // This loop will exit with unrelease samples. - // - - msSleep(g_Settings.msSleepBetweenServerUpdates); - } - - // Release all samples to their allocators as a consequence of NOTE-A - Remotery_FlushMessageQueue(rmt); - - return RMT_ERROR_NONE; -} - - -static rmtError Remotery_ReceiveMessage(void* context, char* message_data, rmtU32 message_length) -{ - Remotery* rmt = (Remotery*)context; - - // Manual dispatch on 4-byte message headers (message ID is little-endian encoded) - #define FOURCC(a, b, c, d) (rmtU32)( ((d) << 24) | ((c) << 16) | ((b) << 8) | (a) ) - rmtU32 message_id = *(rmtU32*)message_data; - - switch (message_id) - { - case FOURCC('C', 'O', 'N', 'I'): - { - rmt_LogText("Console message received..."); - rmt_LogText(message_data + 4); - - // Pass on to any registered handler - if (g_Settings.input_handler != NULL) - g_Settings.input_handler(message_data + 4, g_Settings.input_handler_context); - - break; - } - - case FOURCC('G', 'S', 'M', 'P'): - { - ThreadSampler* ts; - - // Convert name hash to integer - rmtU32 name_hash = 0; - const char* cur = message_data + 4; - const char* end = cur + message_length - 4; - while (cur < end) - name_hash = name_hash * 10 + *cur++ - '0'; - - // Search all threads for a matching string hash - for (ts = rmt->first_thread_sampler; ts != NULL; ts = ts->next) - { - rmtPStr name = StringTable_Find(ts->names, name_hash); - if (name != NULL) - { - rmtU32 name_length; - - // Construct a response message containing the matching name - Buffer* bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - Buffer_Write(bin_buf, "SSMP", 4); - Buffer_WriteU32(bin_buf, name_hash); - name_length = (rmtU32)strnlen_s(name, 256 - 12); - Buffer_WriteU32(bin_buf, name_length); - Buffer_Write(bin_buf, (void*)name, name_length); - - // Send back immediately as we're on the server thread - return Server_Send(rmt->server, bin_buf->data, bin_buf->bytes_used, 10); - } - } - - break; - } - } - - #undef FOURCC - - return RMT_ERROR_NONE; -} - - -static rmtError Remotery_Constructor(Remotery* rmt) -{ - rmtError error; - - assert(rmt != NULL); - - // Set default state - rmt->server = NULL; - rmt->thread_sampler_tls_handle = TLS_INVALID_HANDLE; - rmt->first_thread_sampler = NULL; - rmt->mq_to_rmt_thread = NULL; - rmt->thread = NULL; - rmt->map_message_queue_fn = NULL; - rmt->map_message_queue_data = NULL; - rmt->sampling_disabled = 0; - - #if RMT_USE_CUDA - rmt->cuda.CtxSetCurrent = NULL; - rmt->cuda.EventCreate = NULL; - rmt->cuda.EventDestroy = NULL; - rmt->cuda.EventElapsedTime = NULL; - rmt->cuda.EventQuery = NULL; - rmt->cuda.EventRecord = NULL; - #endif - - #if RMT_USE_OPENGL - rmt->opengl = NULL; - #endif - - #if RMT_USE_METAL - rmt->metal = NULL; - #endif - - // Kick-off the timer - usTimer_Init(&rmt->timer); - - // Allocate a TLS handle for the thread sampler - error = tlsAlloc(&rmt->thread_sampler_tls_handle); - if (error != RMT_ERROR_NONE) - return error; - - // Create the server - New_3(Server, rmt->server, g_Settings.port, g_Settings.reuse_open_port, g_Settings.limit_connections_to_localhost); - if (error != RMT_ERROR_NONE) - return error; - - // Setup incoming message handler - rmt->server->receive_handler = Remotery_ReceiveMessage; - rmt->server->receive_handler_context = rmt; - - // Create the main message thread with only one page - New_1(rmtMessageQueue, rmt->mq_to_rmt_thread, g_Settings.messageQueueSizeInBytes); - if (error != RMT_ERROR_NONE) - return error; - - #if RMT_USE_OPENGL - error = OpenGL_Create(&rmt->opengl); - if (error != RMT_ERROR_NONE) - return error; - #endif - - #if RMT_USE_METAL - error = Metal_Create(&rmt->metal); - if (error != RMT_ERROR_NONE) - return error; - #endif - - // Set as the global instance before creating any threads that uses it for sampling itself - assert(g_Remotery == NULL); - g_Remotery = rmt; - g_RemoteryCreated = RMT_TRUE; - - // Ensure global instance writes complete before other threads get a chance to use it - CompilerWriteFence(); - - // Create the main update thread once everything has been defined for the global remotery object - New_2(rmtThread, rmt->thread, Remotery_ThreadMain, rmt); - return error; -} - - -static void Remotery_Destructor(Remotery* rmt) -{ - assert(rmt != NULL); - - // Join the remotery thread before clearing the global object as the thread is profiling itself - Delete(rmtThread, rmt->thread); - - if (g_RemoteryCreated) - { - g_Remotery = NULL; - g_RemoteryCreated = RMT_FALSE; - } - - #if RMT_USE_OPENGL - Delete(OpenGL, rmt->opengl); - #endif - - #if RMT_USE_METAL - Delete(Metal, rmt->metal); - #endif - - Delete(rmtMessageQueue, rmt->mq_to_rmt_thread); - - Remotery_DestroyThreadSamplers(rmt); - - Delete(Server, rmt->server); - - if (rmt->thread_sampler_tls_handle != TLS_INVALID_HANDLE) - { - tlsFree(rmt->thread_sampler_tls_handle); - rmt->thread_sampler_tls_handle = 0; - } -} - - -static rmtError Remotery_GetThreadSampler(Remotery* rmt, ThreadSampler** thread_sampler) -{ - ThreadSampler* ts; - - // Is there a thread sampler associated with this thread yet? - assert(rmt != NULL); - ts = (ThreadSampler*)tlsGet(rmt->thread_sampler_tls_handle); - if (ts == NULL) - { - // Allocate on-demand - rmtError error; - New_0(ThreadSampler, *thread_sampler); - if (error != RMT_ERROR_NONE) - return error; - ts = *thread_sampler; - if (!ts) - return error; - - // Add to the beginning of the global linked list of thread samplers - for (;;) - { - ThreadSampler* old_ts = rmt->first_thread_sampler; - ts->next = old_ts; - - // If the old value is what we expect it to be then no other thread has - // changed it since this thread sampler was used as a candidate first list item - if (AtomicCompareAndSwapPointer((long* volatile*)&rmt->first_thread_sampler, (long*)old_ts, (long*)ts) == RMT_TRUE) - break; - } - - tlsSet(rmt->thread_sampler_tls_handle, ts); - } - - assert(thread_sampler != NULL); - *thread_sampler = ts; - return RMT_ERROR_NONE; -} - -static void Remotery_DestroyThreadSamplers(Remotery* rmt) -{ - // If the handle failed to create in the first place then it shouldn't be possible to create thread samplers - assert(rmt != NULL); - if (rmt->thread_sampler_tls_handle == TLS_INVALID_HANDLE) - { - assert(rmt->first_thread_sampler == NULL); - return; - } - - // Keep popping thread samplers off the linked list until they're all gone - // This does not make any assumptions, making it possible for thread samplers to be created while they're all - // deleted. While this is erroneous calling code, this will prevent a confusing crash. - while (rmt->first_thread_sampler != NULL) - { - ThreadSampler* ts; - - for (;;) - { - ThreadSampler* old_ts = rmt->first_thread_sampler; - ThreadSampler* next_ts = old_ts->next; - - if (AtomicCompareAndSwapPointer((long* volatile*)&rmt->first_thread_sampler, (long*)old_ts, (long*)next_ts) == RMT_TRUE) - { - ts = old_ts; - break; - } - } - - Delete(ThreadSampler, ts); - } -} - - -static void* CRTMalloc(void* mm_context, rmtU32 size) -{ - RMT_UNREFERENCED_PARAMETER(mm_context); - return malloc((size_t)size); -} - - -static void CRTFree(void* mm_context, void* ptr) -{ - RMT_UNREFERENCED_PARAMETER(mm_context); - free(ptr); -} - -static void* CRTRealloc(void* mm_context, void* ptr, rmtU32 size) -{ - RMT_UNREFERENCED_PARAMETER(mm_context); - return realloc(ptr, size); -} - - -RMT_API rmtSettings* _rmt_Settings(void) -{ - // Default-initialize on first call - if( g_SettingsInitialized == RMT_FALSE ) - { - g_Settings.port = 0x4597; - g_Settings.reuse_open_port = RMT_FALSE; - g_Settings.limit_connections_to_localhost = RMT_FALSE; - g_Settings.msSleepBetweenServerUpdates = 10; - g_Settings.messageQueueSizeInBytes = 128 * 1024; - g_Settings.maxNbMessagesPerUpdate = 10; - g_Settings.malloc = CRTMalloc; - g_Settings.free = CRTFree; - g_Settings.realloc = CRTRealloc; - g_Settings.input_handler = NULL; - g_Settings.input_handler_context = NULL; - g_Settings.logFilename = "rmtLog.txt"; - - g_SettingsInitialized = RMT_TRUE; - } - - return &g_Settings; -} - - -RMT_API rmtError _rmt_CreateGlobalInstance(Remotery** remotery) -{ - rmtError error; - - // Ensure load/acquire store/release operations match this enum size - assert(sizeof(MessageID) == sizeof(rmtU32)); - - // Default-initialise if user has not set values - rmt_Settings(); - - // Creating the Remotery instance also records it as the global instance - assert(remotery != NULL); - New_0(Remotery, *remotery); - return error; -} - - -RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery) -{ - // Ensure this is the module that created it - assert(g_RemoteryCreated == RMT_TRUE); - assert(g_Remotery == remotery); - Delete(Remotery, remotery); - assert(remotery==NULL); -} - - -RMT_API void _rmt_SetGlobalInstance(Remotery* remotery) -{ - // Default-initialise if user has not set values - rmt_Settings(); - - g_Remotery = remotery; -} - - -RMT_API Remotery* _rmt_GetGlobalInstance(void) -{ - return g_Remotery; -} - - -#ifdef RMT_PLATFORM_WINDOWS - #pragma pack(push,8) - typedef struct tagTHREADNAME_INFO - { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } THREADNAME_INFO; - #pragma pack(pop) -#endif - -static void SetDebuggerThreadName(const char* name) -{ - #if defined(RMT_PLATFORM_WINDOWS) && !defined(__GNUC__) - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = name; - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; - - #ifndef __MINGW32__ - __try - { - RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except(1 /* EXCEPTION_EXECUTE_HANDLER */) - { - } - #endif - #else - RMT_UNREFERENCED_PARAMETER(name); - #endif - - #ifdef RMT_PLATFORM_LINUX - // pthread_setname_np is a non-standard GNU extension. - char name_clamp16; - name_clamp0 = 0; - strncat_s(name_clamp, sizeof(name_clamp), name, 15); - #if defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), name_clamp); - #else - prctl(PR_SET_NAME,name_clamp,0,0,0); - #endif - #endif -} - -#define CHECK_REMOTERY() \ - if ((g_Remotery==NULL) || g_Remotery->sampling_disabled)\ - return; - - -RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - - // Get data for this thread - if (Remotery_GetThreadSampler(g_Remotery, &ts) != RMT_ERROR_NONE) - return; - - // Copy name and apply to the debugger - strcpy_s(ts->name, sizeof(ts->name), thread_name); - SetDebuggerThreadName(thread_name); -} - - -static rmtBool QueueLine(rmtMessageQueue* queue, unsigned char* text, rmtU32 size, struct ThreadSampler* thread_sampler) -{ - Message* message; - rmtU32 text_size; - - assert(queue != NULL); - - // Prefix with text size - text_size = size - 8; - U32ToByteArray(text + 4, text_size); - - // Allocate some space for the line - message = rmtMessageQueue_AllocMessage(queue, size, thread_sampler); - if (message == NULL) - return RMT_FALSE; - - // Copy the text and commit the message - memcpy(message->payload, text, size); - rmtMessageQueue_CommitMessage(message, MsgID_LogText); - - return RMT_TRUE; -} - -RMT_API void _rmt_SendText(rmtPStr text) -{ - //do not check for sampling enabled here - if (g_Remotery == NULL) - return; - - ThreadSampler* ts; - Message* message; - rmtU32 size = (rmtU32) strlen(text); - - Remotery_GetThreadSampler(g_Remotery, &ts); - - // Allocate some space for the line - message = rmtMessageQueue_AllocMessage(g_Remotery->mq_to_rmt_thread, size, ts); - if (message == NULL) - return; - - // Copy the text and commit the message - memcpy(message->payload, text, size); - rmtMessageQueue_CommitMessage(message, MsgID_SendText); -} - -RMT_API void _rmt_LogText(rmtPStr text) -{ - int start_offset, offset, i; - unsigned char line_buffer1024 = { 0 }; - ThreadSampler* ts; - - //do not check for sampling enabled here - if (g_Remotery==NULL) - return; - - Remotery_GetThreadSampler(g_Remotery, &ts); - - // Start the line with the message header - line_buffer0 = 'L'; - line_buffer1 = 'O'; - line_buffer2 = 'G'; - line_buffer3 = 'M'; - // Fill with spaces to enable viewing line_buffer without offset in a debugger - // (will be overwritten later by QueueLine/rmtMessageQueue_AllocMessage) - line_buffer4 = ' '; - line_buffer5 = ' '; - line_buffer6 = ' '; - line_buffer7 = ' '; - start_offset = 8; - - // There might be newlines in the buffer, so split them into multiple network calls - offset = start_offset; - for (i = 0; texti != 0; i++) - { - char c = texti; - - // Line wrap when too long or newline encountered - if (offset == sizeof(line_buffer) - 1 || c == '\n') - { - // Send the line up to now - if (QueueLine(g_Remotery->mq_to_rmt_thread, line_buffer, offset, ts) == RMT_FALSE) - return; - - // Restart line - offset = start_offset; - - // Don't add the newline character (if this was the reason for the flush) - // to the restarted line_buffer, let's skip it - if (c == '\n') - continue; - } - - line_bufferoffset++ = c; - } - - // Send the last line - if (offset > start_offset) - { - assert(offset < (int)sizeof(line_buffer)); - QueueLine(g_Remotery->mq_to_rmt_thread, line_buffer, offset, ts); - } -} - - -RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache) -{ - // 'hash_cache' stores a pointer to a sample name's hash value. Internally this is used to identify unique callstacks and it - // would be ideal that it's not recalculated each time the sample is used. This can be statically cached at the point - // of call or stored elsewhere when dynamic names are required. - // - // If 'hash_cache' is NULL then this call becomes more expensive, as it has to recalculate the hash of the name. - - ThreadSampler* ts; - - CHECK_REMOTERY() - - // TODO: Time how long the bits outside here cost and subtract them from the parent - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash = ThreadSampler_GetNameHash(ts, name, hash_cache); - if (ThreadSampler_Push(ts->sample_treesSampleType_CPU, name_hash, flags, &sample) == RMT_ERROR_NONE) - { - // If this is an aggregate sample, store the time in 'end' as we want to preserve 'start' - if (sample->call_count > 1) - sample->us_end = usTimer_Get(&g_Remotery->timer); - else - sample->us_start = usTimer_Get(&g_Remotery->timer); - } - } -} - - -RMT_API void _rmt_EndCPUSample(void) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - Sample* sample = ts->sample_treesSampleType_CPU->current_parent; - - if (sample->recurse_depth > 0) - { - sample->recurse_depth--; - } - else - { - rmtU64 us_end = usTimer_Get(&g_Remotery->timer); - - // Aggregate samples use us_end to store start so that us_start is preserved - rmtU64 us_length = 0; - if (sample->call_count > 1 && sample->max_recurse_depth == 0) - us_length = (us_end - sample->us_end); - else - us_length = (us_end - sample->us_start); - - sample->us_length += us_length; - - // Sum length on the parent to track un-sampled time in the parent - if (sample->parent != NULL) - sample->parent->us_sampled_length += us_length; - - ThreadSampler_Pop(ts, g_Remotery->mq_to_rmt_thread, sample); - } - } -} - -#if RMT_USE_OPENGL || RMT_USE_D3D11 -static void Remotery_DeleteSampleTree(Remotery* rmt, enum SampleType sample_type) -{ - ThreadSampler* ts; - - // Get the attached thread sampler and delete the sample tree - assert(rmt != NULL); - if (Remotery_GetThreadSampler(rmt, &ts) == RMT_ERROR_NONE) - { - SampleTree* sample_tree = ts->sample_treessample_type; - if (sample_tree != NULL) - { - Delete(SampleTree, sample_tree); - ts->sample_treessample_type = NULL; - } - } -} - - -static rmtBool rmtMessageQueue_IsEmpty(rmtMessageQueue* queue) -{ - assert(queue != NULL); - return queue->write_pos - queue->read_pos == 0; -} - -typedef struct GatherQueuedSampleData -{ - SampleType sample_type; - Buffer* flush_samples; -} GatherQueuedSampleData; - - -static void MapMessageQueueAndWait(Remotery* rmt, void (*map_message_queue_fn)(Remotery*rmt, Message*), void* data) -{ - // Basic spin lock on the map function itself - while (AtomicCompareAndSwapPointer((long* volatile*)&rmt->map_message_queue_fn, NULL, (long*)map_message_queue_fn) == RMT_FALSE) - msSleep(1); - - StoreReleasePointer((long* volatile*)&rmt->map_message_queue_data, (long*)data); - - // Wait until map completes - while (LoadAcquirePointer((long* volatile*)&rmt->map_message_queue_fn) != NULL) - msSleep(1); -} - - -static void GatherQueuedSamples(Remotery* rmt, Message* message) -{ - GatherQueuedSampleData* gather_data = (GatherQueuedSampleData*)rmt->map_message_queue_data; - - // Filter sample trees - if (message->id == MsgID_SampleTree) - { - Msg_SampleTree* sample_tree = (Msg_SampleTree*)message->payload; - Sample* sample = sample_tree->root_sample; - if (sample->type == gather_data->sample_type) - { - // Make a copy of the entire sample tree as the remotery thread may overwrite it while - // the calling thread tries to delete - rmtU32 message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - Buffer_Write(gather_data->flush_samples, message, message_size); - - // Mark the message empty - message->id = MsgID_None; - } - } -} - - -static void FreePendingSampleTrees(Remotery* rmt, SampleType sample_type, Buffer* flush_samples) -{ - rmtU8* data; - rmtU8* data_end; - - // Gather all sample trees currently queued for the Remotery thread - GatherQueuedSampleData gather_data; - gather_data.sample_type = sample_type; - gather_data.flush_samples = flush_samples; - MapMessageQueueAndWait(rmt, GatherQueuedSamples, &gather_data); - - // Release all sample trees to their allocators - data = flush_samples->data; - data_end = data + flush_samples->bytes_used; - while (data < data_end) - { - Message* message = (Message*)data; - rmtU32 message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - Msg_SampleTree* sample_tree = (Msg_SampleTree*)message->payload; - FreeSampleTree(sample_tree->root_sample, sample_tree->allocator); - data += message_size; - } -} - - -#endif - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @CUDA: CUDA event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#if RMT_USE_CUDA - - -typedef struct CUDASample -{ - // IS-A inheritance relationship - Sample base; - - // Pair of events that wrap the sample - CUevent event_start; - CUevent event_end; - -} CUDASample; - - -static rmtError MapCUDAResult(CUresult result) -{ - switch (result) - { - case CUDA_SUCCESS: return RMT_ERROR_NONE; - case CUDA_ERROR_DEINITIALIZED: return RMT_ERROR_CUDA_DEINITIALIZED; - case CUDA_ERROR_NOT_INITIALIZED: return RMT_ERROR_CUDA_NOT_INITIALIZED; - case CUDA_ERROR_INVALID_CONTEXT: return RMT_ERROR_CUDA_INVALID_CONTEXT; - case CUDA_ERROR_INVALID_VALUE: return RMT_ERROR_CUDA_INVALID_VALUE; - case CUDA_ERROR_INVALID_HANDLE: return RMT_ERROR_CUDA_INVALID_HANDLE; - case CUDA_ERROR_OUT_OF_MEMORY: return RMT_ERROR_CUDA_OUT_OF_MEMORY; - case CUDA_ERROR_NOT_READY: return RMT_ERROR_ERROR_NOT_READY; - default: return RMT_ERROR_CUDA_UNKNOWN; - } -} - - -#define CUDA_MAKE_FUNCTION(name, params) \ - typedef CUresult (CUDAAPI *name##Ptr) params; \ - name##Ptr name = (name##Ptr)g_Remotery->cuda.name; - - -#define CUDA_GUARD(call) \ - { \ - rmtError error = call; \ - if (error != RMT_ERROR_NONE) \ - return error; \ - } - - -// Wrappers around CUDA driver functions that manage the active context. -static rmtError CUDASetContext(void* context) -{ - CUDA_MAKE_FUNCTION(CtxSetCurrent, (CUcontext ctx)); - assert(CtxSetCurrent != NULL); - return MapCUDAResult(CtxSetCurrent((CUcontext)context)); -} -static rmtError CUDAGetContext(void** context) -{ - CUDA_MAKE_FUNCTION(CtxGetCurrent, (CUcontext* ctx)); - assert(CtxGetCurrent != NULL); - return MapCUDAResult(CtxGetCurrent((CUcontext*)context)); -} -static rmtError CUDAEnsureContext() -{ - void* current_context; - CUDA_GUARD(CUDAGetContext(¤t_context)); - - assert(g_Remotery != NULL); - if (current_context != g_Remotery->cuda.context) - CUDA_GUARD(CUDASetContext(g_Remotery->cuda.context)); - - return RMT_ERROR_NONE; -} - - -// Wrappers around CUDA driver functions that manage events -static rmtError CUDAEventCreate(CUevent* phEvent, unsigned int Flags) -{ - CUDA_MAKE_FUNCTION(EventCreate, (CUevent *phEvent, unsigned int Flags)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventCreate(phEvent, Flags)); -} -static rmtError CUDAEventDestroy(CUevent hEvent) -{ - CUDA_MAKE_FUNCTION(EventDestroy, (CUevent hEvent)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventDestroy(hEvent)); -} -static rmtError CUDAEventRecord(CUevent hEvent, void* hStream) -{ - CUDA_MAKE_FUNCTION(EventRecord, (CUevent hEvent, CUstream hStream)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventRecord(hEvent, (CUstream)hStream)); -} -static rmtError CUDAEventQuery(CUevent hEvent) -{ - CUDA_MAKE_FUNCTION(EventQuery, (CUevent hEvent)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventQuery(hEvent)); -} -static rmtError CUDAEventElapsedTime(float* pMilliseconds, CUevent hStart, CUevent hEnd) -{ - CUDA_MAKE_FUNCTION(EventElapsedTime, (float *pMilliseconds, CUevent hStart, CUevent hEnd)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventElapsedTime(pMilliseconds, hStart, hEnd)); -} - - -static rmtError CUDASample_Constructor(CUDASample* sample) -{ - rmtError error; - - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = SampleType_CUDA; - sample->base.size_bytes = sizeof(CUDASample); - sample->event_start = NULL; - sample->event_end = NULL; - - // Create non-blocking events with timing - assert(g_Remotery != NULL); - error = CUDAEventCreate(&sample->event_start, CU_EVENT_DEFAULT); - if (error == RMT_ERROR_NONE) - error = CUDAEventCreate(&sample->event_end, CU_EVENT_DEFAULT); - return error; -} - - -static void CUDASample_Destructor(CUDASample* sample) -{ - assert(sample != NULL); - - // Destroy events - if (sample->event_start != NULL) - CUDAEventDestroy(sample->event_start); - if (sample->event_end != NULL) - CUDAEventDestroy(sample->event_end); - - Sample_Destructor((Sample*)sample); -} - - -static rmtBool AreCUDASamplesReady(Sample* sample) -{ - rmtError error; - Sample* child; - - CUDASample* cuda_sample = (CUDASample*)sample; - assert(sample->type == SampleType_CUDA); - - // Check to see if both of the CUDA events have been processed - error = CUDAEventQuery(cuda_sample->event_start); - if (error != RMT_ERROR_NONE) - return RMT_FALSE; - error = CUDAEventQuery(cuda_sample->event_end); - if (error != RMT_ERROR_NONE) - return RMT_FALSE; - - // Check child sample events - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!AreCUDASamplesReady(child)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - - -static rmtBool GetCUDASampleTimes(Sample* root_sample, Sample* sample) -{ - Sample* child; - - CUDASample* cuda_root_sample = (CUDASample*)root_sample; - CUDASample* cuda_sample = (CUDASample*)sample; - - float ms_start, ms_end; - - assert(root_sample != NULL); - assert(sample != NULL); - - // Get millisecond timing of each sample event, relative to initial root sample - if (CUDAEventElapsedTime(&ms_start, cuda_root_sample->event_start, cuda_sample->event_start) != RMT_ERROR_NONE) - return RMT_FALSE; - if (CUDAEventElapsedTime(&ms_end, cuda_root_sample->event_start, cuda_sample->event_end) != RMT_ERROR_NONE) - return RMT_FALSE; - - // Convert to microseconds and add to the sample - sample->us_start = (rmtU64)(ms_start * 1000); - sample->us_end = (rmtU64)(ms_end * 1000); - sample->us_length = sample->us_end - sample->us_start; - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetCUDASampleTimes(root_sample, child)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - - -RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind) -{ - assert(bind != NULL); - if (g_Remotery != NULL) - g_Remotery->cuda = *bind; -} - - -RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - rmtError error; - Sample* sample; - rmtU32 name_hash = ThreadSampler_GetNameHash(ts, name, hash_cache); - - // Create the CUDA tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a CUDA binding is not yet available. - SampleTree** cuda_tree = &ts->sample_treesSampleType_CUDA; - if (*cuda_tree == NULL) - { - CUDASample* root_sample; - - New_3(SampleTree, *cuda_tree, sizeof(CUDASample), (ObjConstructor)CUDASample_Constructor, (ObjDestructor)CUDASample_Destructor); - if (error != RMT_ERROR_NONE) - return; - - // Record an event once on the root sample, used to measure absolute sample - // times since this point - root_sample = (CUDASample*)(*cuda_tree)->root; - error = CUDAEventRecord(root_sample->event_start, stream); - if (error != RMT_ERROR_NONE) - return; - } - - // Push the same and record its event - if (ThreadSampler_Push(*cuda_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - CUDASample* cuda_sample = (CUDASample*)sample; - CUDAEventRecord(cuda_sample->event_start, stream); - } - } -} - - -RMT_API void _rmt_EndCUDASample(void* stream) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - CUDASample* sample = (CUDASample*)ts->sample_treesSampleType_CUDA->current_parent; - if (sample->base.recurse_depth > 0) - { - sample->base.recurse_depth--; - } - else - { - CUDAEventRecord(sample->event_end, stream); - ThreadSampler_Pop(ts, g_Remotery->mq_to_rmt_thread, (Sample*)sample); - } - } -} - - -#endif // RMT_USE_CUDA - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @D3D11: Direct3D 11 event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#if RMT_USE_D3D11 - - -// As clReflect has no way of disabling C++ compile mode, this forces C interfaces everywhere... -#define CINTERFACE - -// ...unfortunately these C++ helpers aren't wrapped by the same macro but they can be disabled individually -#define D3D11_NO_HELPERS - -// Allow use of the D3D11 helper macros for accessing the C-style vtable -#define COBJMACROS - -#ifdef _MSC_VER - // Disable for d3d11.h - // warning C4201: nonstandard extension used : nameless struct/union - #pragma warning(push) - #pragma warning(disable: 4201) -#endif - -#include <d3d11.h> - -#ifdef _MSC_VER - #pragma warning(pop) -#endif - - -typedef struct D3D11 -{ - // Context set by user - ID3D11Device* device; - ID3D11DeviceContext* context; - - HRESULT last_error; - - // Queue to the D3D 11 main update thread - // Given that BeginSample/EndSample need to be called from the same thread that does the update, there - // is really no need for this to be a thread-safe queue. I'm using it for its convenience. - rmtMessageQueue* mq_to_d3d11_main; - - // Mark the first time so that remaining timestamps are offset from this - rmtU64 first_timestamp; - // Last time in us (CPU time, via usTimer_Get) since we last resync'ed CPU & GPU - rmtU64 last_resync; - - // Sample trees in transit in the message queue for release on shutdown - Buffer* flush_samples; -} D3D11; - - -static rmtError D3D11_Create(D3D11** d3d11) -{ - rmtError error; - - assert(d3d11 != NULL); - - // Allocate space for the D3D11 data - *d3d11 = (D3D11*)rmtMalloc(sizeof(D3D11)); - if (*d3d11 == NULL) - return RMT_ERROR_MALLOC_FAIL; - - // Set defaults - (*d3d11)->device = NULL; - (*d3d11)->context = NULL; - (*d3d11)->last_error = S_OK; - (*d3d11)->mq_to_d3d11_main = NULL; - (*d3d11)->first_timestamp = 0; - (*d3d11)->last_resync = 0; - (*d3d11)->flush_samples = NULL; - - New_1(rmtMessageQueue, (*d3d11)->mq_to_d3d11_main, g_Settings.messageQueueSizeInBytes); - if (error != RMT_ERROR_NONE) - { - Delete(D3D11, *d3d11); - return error; - } - - New_1(Buffer, (*d3d11)->flush_samples, 8 * 1024); - if (error != RMT_ERROR_NONE) - { - Delete(D3D11, *d3d11); - return error; - } - - return RMT_ERROR_NONE; -} - - -static void D3D11_Destructor(D3D11* d3d11) -{ - assert(d3d11 != NULL); - Delete(Buffer, d3d11->flush_samples); - Delete(rmtMessageQueue, d3d11->mq_to_d3d11_main); -} - -static HRESULT rmtD3D11Finish(ID3D11Device* device, ID3D11DeviceContext* context, - rmtU64 *out_timestamp, double *out_frequency) -{ - HRESULT result; - ID3D11Query* full_stall_fence; - ID3D11Query* query_disjoint; - D3D11_QUERY_DESC query_desc; - D3D11_QUERY_DESC disjoint_desc; - UINT64 timestamp; - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - - query_desc.Query = D3D11_QUERY_TIMESTAMP; - query_desc.MiscFlags = 0; - result = ID3D11Device_CreateQuery(device, &query_desc, &full_stall_fence); - if (result != S_OK) - return result; - - disjoint_desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - disjoint_desc.MiscFlags = 0; - result = ID3D11Device_CreateQuery(device, &disjoint_desc, &query_disjoint); - if (result != S_OK) - { - ID3D11Query_Release(full_stall_fence); - return result; - } - - ID3D11DeviceContext_Begin(context, (ID3D11Asynchronous*)query_disjoint); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)full_stall_fence); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)query_disjoint); - - result = S_FALSE; - - while( result == S_FALSE ) - { - result = ID3D11DeviceContext_GetData(context, (ID3D11Asynchronous*)query_disjoint, &disjoint, sizeof(disjoint), 0); - if (result != S_OK && result != S_FALSE) - { - ID3D11Query_Release(full_stall_fence); - ID3D11Query_Release(query_disjoint); - return result; - } - if( result == S_OK ) - { - result = ID3D11DeviceContext_GetData(context, (ID3D11Asynchronous*)full_stall_fence, ×tamp, sizeof(timestamp), 0); - if (result != S_OK && result != S_FALSE) - { - ID3D11Query_Release(full_stall_fence); - ID3D11Query_Release(query_disjoint); - return result; - } - } - //Give HyperThreading threads a breath on this spinlock. - YieldProcessor(); - } - - if (disjoint.Disjoint == FALSE) - { - double frequency = disjoint.Frequency / 1000000.0; - *out_timestamp = timestamp; - *out_frequency = frequency; - } - else - { - result = S_FALSE; - } - - ID3D11Query_Release(full_stall_fence); - ID3D11Query_Release(query_disjoint); - return result; -} - -static HRESULT SyncD3D11CpuGpuTimes(ID3D11Device* device, ID3D11DeviceContext* context, - rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - rmtU64 cpu_time_start = 0; - rmtU64 cpu_time_stop = 0; - rmtU64 average_half_RTT = 0; //RTT = Rountrip Time. - UINT64 gpu_base = 0; - double frequency = 1; - int i; - - HRESULT result; - result = rmtD3D11Finish(device, context, &gpu_base, &frequency); - if (result != S_OK && result != S_FALSE) - return result; - - for (i=0; i<RMT_GPU_CPU_SYNC_NUM_ITERATIONS; ++i) - { - rmtU64 half_RTT; - cpu_time_start = usTimer_Get(&g_Remotery->timer); - result = rmtD3D11Finish(device, context, &gpu_base, &frequency); - cpu_time_stop = usTimer_Get(&g_Remotery->timer); - - if (result != S_OK && result != S_FALSE) - return result; - - //Ignore attempts where there was a disjoint, since there would - //be a lot of noise in those readings for measuring the RTT - if (result == S_OK) - { - //Average the time it takes a roundtrip from CPU to GPU - //while doing nothing other than getting timestamps - half_RTT = (cpu_time_stop - cpu_time_start) >> 1ULL; - if( i == 0 ) - average_half_RTT = half_RTT; - else - average_half_RTT = (average_half_RTT + half_RTT) >> 1ULL; - } - } - - // All GPU times are offset from gpu_base, and then taken to - // the same relative origin CPU timestamps are based on. - // CPU is in us, we must translate it to ns. - *out_first_timestamp = gpu_base - (rmtU64)((cpu_time_start + average_half_RTT) * frequency); - *out_last_resync = cpu_time_stop; - - return result; -} - -typedef struct D3D11Timestamp -{ - // Inherit so that timestamps can be quickly allocated - ObjectLink Link; - - // Pair of timestamp queries that wrap the sample - ID3D11Query* query_start; - ID3D11Query* query_end; - - // A disjoint to measure frequency/stability - // TODO: Does *each* sample need one of these? - ID3D11Query* query_disjoint; - - rmtU64 cpu_timestamp; -} D3D11Timestamp; - - -static rmtError D3D11Timestamp_Constructor(D3D11Timestamp* stamp) -{ - ThreadSampler* ts; - D3D11_QUERY_DESC timestamp_desc; - D3D11_QUERY_DESC disjoint_desc; - ID3D11Device* device; - HRESULT* last_error; - - assert(stamp != NULL); - - ObjectLink_Constructor((ObjectLink*)stamp); - - // Set defaults - stamp->query_start = NULL; - stamp->query_end = NULL; - stamp->query_disjoint = NULL; - stamp->cpu_timestamp = 0; - - assert(g_Remotery != NULL); - assert(Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE); - assert(ts->d3d11 != NULL); - device = ts->d3d11->device; - last_error = &ts->d3d11->last_error; - - // Create start/end timestamp queries - timestamp_desc.Query = D3D11_QUERY_TIMESTAMP; - timestamp_desc.MiscFlags = 0; - *last_error = ID3D11Device_CreateQuery(device, ×tamp_desc, &stamp->query_start); - if (*last_error != S_OK) - return RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY; - *last_error = ID3D11Device_CreateQuery(device, ×tamp_desc, &stamp->query_end); - if (*last_error != S_OK) - return RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY; - - // Create disjoint query - disjoint_desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - disjoint_desc.MiscFlags = 0; - *last_error = ID3D11Device_CreateQuery(device, &disjoint_desc, &stamp->query_disjoint); - if (*last_error != S_OK) - return RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY; - - return RMT_ERROR_NONE; -} - - -static void D3D11Timestamp_Destructor(D3D11Timestamp* stamp) -{ - assert(stamp != NULL); - - // Destroy queries - if (stamp->query_disjoint != NULL) - ID3D11Query_Release(stamp->query_disjoint); - if (stamp->query_end != NULL) - ID3D11Query_Release(stamp->query_end); - if (stamp->query_start != NULL) - ID3D11Query_Release(stamp->query_start); -} - - -static void D3D11Timestamp_Begin(D3D11Timestamp* stamp, ID3D11DeviceContext* context) -{ - assert(stamp != NULL); - - // Start of disjoint and first query - stamp->cpu_timestamp = usTimer_Get(&g_Remotery->timer); - ID3D11DeviceContext_Begin(context, (ID3D11Asynchronous*)stamp->query_disjoint); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)stamp->query_start); -} - - -static void D3D11Timestamp_End(D3D11Timestamp* stamp, ID3D11DeviceContext* context) -{ - assert(stamp != NULL); - - // End of disjoint and second query - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)stamp->query_end); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)stamp->query_disjoint); -} - - -static HRESULT D3D11Timestamp_GetData(D3D11Timestamp* stamp, ID3D11Device* device, - ID3D11DeviceContext* context, rmtU64* out_start, rmtU64* out_end, rmtU64* out_first_timestamp, - rmtU64* out_last_resync) -{ - ID3D11Asynchronous* query_start; - ID3D11Asynchronous* query_end; - ID3D11Asynchronous* query_disjoint; - HRESULT result; - - UINT64 start; - UINT64 end; - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - - assert(stamp != NULL); - query_start = (ID3D11Asynchronous*)stamp->query_start; - query_end = (ID3D11Asynchronous*)stamp->query_end; - query_disjoint = (ID3D11Asynchronous*)stamp->query_disjoint; - - // Check to see if all queries are ready - // If any fail to arrive, wait until later - result = ID3D11DeviceContext_GetData(context, query_start, &start, sizeof(start), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (result != S_OK) - return result; - result = ID3D11DeviceContext_GetData(context, query_end, &end, sizeof(end), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (result != S_OK) - return result; - result = ID3D11DeviceContext_GetData(context, query_disjoint, &disjoint, sizeof(disjoint), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (result != S_OK) - return result; - - if (disjoint.Disjoint == FALSE) - { - double frequency = disjoint.Frequency / 1000000.0; - - // Mark the first timestamp. We may resync if we detect the GPU timestamp is in the - // past (i.e. happened before the CPU command) since it should be impossible. - assert(out_first_timestamp != NULL); - if (*out_first_timestamp == 0 || ((start - *out_first_timestamp) / frequency) < stamp->cpu_timestamp) - { - result = SyncD3D11CpuGpuTimes(device, context, out_first_timestamp, out_last_resync); - if (result != S_OK) - return result; - } - - // Calculate start and end timestamps from the disjoint info - *out_start = (rmtU64)((start - *out_first_timestamp) / frequency); - *out_end = (rmtU64)((end - *out_first_timestamp) / frequency); - } - else - { -#if RMT_D3D11_RESYNC_ON_DISJOINT - result = SyncD3D11CpuGpuTimes(device, context, out_first_timestamp, out_last_resync); - if (result != S_OK) - return result; -#endif - } - - return S_OK; -} - - -typedef struct D3D11Sample -{ - // IS-A inheritance relationship - Sample base; - - D3D11Timestamp* timestamp; - -} D3D11Sample; - - -static rmtError D3D11Sample_Constructor(D3D11Sample* sample) -{ - rmtError error; - - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = SampleType_D3D11; - sample->base.size_bytes = sizeof(D3D11Sample); - New_0(D3D11Timestamp, sample->timestamp); - - return RMT_ERROR_NONE; -} - - -static void D3D11Sample_Destructor(D3D11Sample* sample) -{ - Delete(D3D11Timestamp, sample->timestamp); - Sample_Destructor((Sample*)sample); -} - - -RMT_API void _rmt_BindD3D11(void* device, void* context) -{ - if (g_Remotery != NULL) - { - ThreadSampler* ts; - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - assert(ts->d3d11 != NULL); - - assert(device != NULL); - ts->d3d11->device = (ID3D11Device*)device; - assert(context != NULL); - ts->d3d11->context = (ID3D11DeviceContext*)context; - } - } -} - - -static void UpdateD3D11Frame(ThreadSampler* ts); - - -RMT_API void _rmt_UnbindD3D11(void) -{ - if (g_Remotery != NULL) - { - ThreadSampler* ts; - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - D3D11* d3d11 = ts->d3d11; - assert(d3d11 != NULL); - - // Stall waiting for the D3D queue to empty into the Remotery queue - while (!rmtMessageQueue_IsEmpty(d3d11->mq_to_d3d11_main)) - UpdateD3D11Frame(ts); - - // There will be a whole bunch of D3D11 sample trees queued up the remotery queue that need releasing - FreePendingSampleTrees(g_Remotery, SampleType_D3D11, d3d11->flush_samples); - - // Inform sampler to not add any more samples - d3d11->device = NULL; - d3d11->context = NULL; - - // Forcefully delete sample tree on this thread to release time stamps from - // the same thread that created them - Remotery_DeleteSampleTree(g_Remotery, SampleType_D3D11); - } - } -} - - -RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache) -{ - ThreadSampler* ts; - D3D11* d3d11; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash; - SampleTree** d3d_tree; - - // Has D3D11 been unbound? - d3d11 = ts->d3d11; - assert(d3d11 != NULL); - if (d3d11->device == NULL || d3d11->context == NULL) - return; - - name_hash = ThreadSampler_GetNameHash(ts, name, hash_cache); - - // Create the D3D11 tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a D3D11 binding is not yet available. - d3d_tree = &ts->sample_treesSampleType_D3D11; - if (*d3d_tree == NULL) - { - rmtError error; - New_3(SampleTree, *d3d_tree, sizeof(D3D11Sample), (ObjConstructor)D3D11Sample_Constructor, (ObjDestructor)D3D11Sample_Destructor); - if (error != RMT_ERROR_NONE) - return; - } - - // Push the sample and activate the timestamp - if (ThreadSampler_Push(*d3d_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - D3D11Sample* d3d_sample = (D3D11Sample*)sample; - D3D11Timestamp_Begin(d3d_sample->timestamp, d3d11->context); - } - } -} - - -static rmtBool GetD3D11SampleTimes(Sample* sample, ThreadSampler* ts, rmtU64* out_first_timestamp, - rmtU64* out_last_resync) -{ - Sample* child; - - D3D11Sample* d3d_sample = (D3D11Sample*)sample; - - assert(sample != NULL); - if (d3d_sample->timestamp != NULL) - { - HRESULT result; - - D3D11* d3d11 = ts->d3d11; - assert(d3d11 != NULL); - - assert(out_last_resync != NULL); - - #if (RMT_GPU_CPU_SYNC_SECONDS > 0) - if (*out_last_resync < d3d_sample->timestamp->cpu_timestamp) - { - //Convert from us to seconds. - rmtU64 time_diff = (d3d_sample->timestamp->cpu_timestamp - *out_last_resync) / 1000000ULL; - if (time_diff > RMT_GPU_CPU_SYNC_SECONDS) - { - result = SyncD3D11CpuGpuTimes(d3d11->device, d3d11->context, out_first_timestamp, out_last_resync); - if (result != S_OK) - { - d3d11->last_error = result; - return RMT_FALSE; - } - } - } - #endif - - result = D3D11Timestamp_GetData( - d3d_sample->timestamp, - d3d11->device, - d3d11->context, - &sample->us_start, - &sample->us_end, - out_first_timestamp, - out_last_resync); - - if (result != S_OK) - { - d3d11->last_error = result; - return RMT_FALSE; - } - - sample->us_length = sample->us_end - sample->us_start; - } - - // Sum length on the parent to track un-sampled time in the parent - if (sample->parent != NULL) - { - sample->parent->us_sampled_length += sample->us_length; - } - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetD3D11SampleTimes(child, ts, out_first_timestamp, out_last_resync)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - - -static void UpdateD3D11Frame(ThreadSampler* ts) -{ - D3D11* d3d11; - - CHECK_REMOTERY() - - d3d11 = ts->d3d11; - assert(d3d11 != NULL); - - rmt_BeginCPUSample(rmt_UpdateD3D11Frame, 0); - - // Process all messages in the D3D queue - for (;;) - { - Msg_SampleTree* sample_tree; - Sample* sample; - - Message* message = rmtMessageQueue_PeekNextMessage(d3d11->mq_to_d3d11_main); - if (message == NULL) - break; - - // There's only one valid message type in this queue - assert(message->id == MsgID_SampleTree); - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->root_sample; - assert(sample->type == SampleType_D3D11); - - // Retrieve timing of all D3D11 samples - // If they aren't ready leave the message unconsumed, holding up later frames and maintaining order - if (!GetD3D11SampleTimes(sample, ts, &d3d11->first_timestamp, &d3d11->last_resync)) - break; - - // Pass samples onto the remotery thread for sending to the viewer - AddSampleTreeMessage(g_Remotery->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->thread_name, message->thread_sampler); - rmtMessageQueue_ConsumeNextMessage(d3d11->mq_to_d3d11_main, message); - } - - rmt_EndCPUSample(); -} - - -RMT_API void _rmt_EndD3D11Sample(void) -{ - ThreadSampler* ts; - D3D11* d3d11; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - D3D11Sample* d3d_sample; - - // Has D3D11 been unbound? - d3d11 = ts->d3d11; - assert(d3d11 != NULL); - if (d3d11->device == NULL || d3d11->context == NULL) - return; - - // Close the timestamp - d3d_sample = (D3D11Sample*)ts->sample_treesSampleType_D3D11->current_parent; - if (d3d_sample->base.recurse_depth > 0) - { - d3d_sample->base.recurse_depth--; - } - else - { - if (d3d_sample->timestamp != NULL) - D3D11Timestamp_End(d3d_sample->timestamp, d3d11->context); - - // Send to the update loop for ready-polling - if (ThreadSampler_Pop(ts, d3d11->mq_to_d3d11_main, (Sample*)d3d_sample)) - // Perform ready-polling on popping of the root sample - UpdateD3D11Frame(ts); - } - } -} - - -#endif // RMT_USE_D3D11 - - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -@OpenGL: OpenGL event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - - - -#if RMT_USE_OPENGL - - -#ifndef APIENTRY -# if defined(__MINGW32__) || defined(__CYGWIN__) -# define APIENTRY __stdcall -# elif (defined(_MSC_VER) && (_MSC_VER >= 800)) || defined(_STDCALL_SUPPORTED) || defined(__BORLANDC__) -# define APIENTRY __stdcall -# else -# define APIENTRY -# endif -#endif - -#ifndef GLAPI -# if defined(__MINGW32__) || defined(__CYGWIN__) -# define GLAPI extern -# elif defined (_WIN32) -# define GLAPI WINGDIAPI -# else -# define GLAPI extern -# endif -#endif - -#ifndef GLAPIENTRY -#define GLAPIENTRY APIENTRY -#endif - -typedef rmtU32 GLenum; -typedef rmtU32 GLuint; -typedef rmtS32 GLint; -typedef rmtS32 GLsizei; -typedef rmtU64 GLuint64; -typedef rmtS64 GLint64; -typedef unsigned char GLubyte; - -typedef GLenum (GLAPIENTRY * PFNGLGETERRORPROC) (void); -typedef void (GLAPIENTRY * PFNGLGENQUERIESPROC) (GLsizei n, GLuint* ids); -typedef void (GLAPIENTRY * PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint* ids); -typedef void (GLAPIENTRY * PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); -typedef void (GLAPIENTRY * PFNGLENDQUERYPROC) (GLenum target); -typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint* params); -typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint* params); -typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64* params); -typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64* params); -typedef void (GLAPIENTRY * PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); -typedef void (GLAPIENTRY * PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *data); -typedef void (GLAPIENTRY * PFNGLFINISHPROC) (void); - -#define GL_NO_ERROR 0 -#define GL_QUERY_RESULT 0x8866 -#define GL_QUERY_RESULT_AVAILABLE 0x8867 -#define GL_TIME_ELAPSED 0x88BF -#define GL_TIMESTAMP 0x8E28 - -#define RMT_GL_GET_FUN(x) assert(g_Remotery->opengl->x != NULL); g_Remotery->opengl->x - -#define rmtglGenQueries RMT_GL_GET_FUN(__glGenQueries) -#define rmtglDeleteQueries RMT_GL_GET_FUN(__glDeleteQueries) -#define rmtglBeginQuery RMT_GL_GET_FUN(__glBeginQuery) -#define rmtglEndQuery RMT_GL_GET_FUN(__glEndQuery) -#define rmtglGetQueryObjectiv RMT_GL_GET_FUN(__glGetQueryObjectiv) -#define rmtglGetQueryObjectuiv RMT_GL_GET_FUN(__glGetQueryObjectuiv) -#define rmtglGetQueryObjecti64v RMT_GL_GET_FUN(__glGetQueryObjecti64v) -#define rmtglGetQueryObjectui64v RMT_GL_GET_FUN(__glGetQueryObjectui64v) -#define rmtglQueryCounter RMT_GL_GET_FUN(__glQueryCounter) -#define rmtglGetInteger64v RMT_GL_GET_FUN(__glGetInteger64v) -#define rmtglFinish RMT_GL_GET_FUN(__glFinish) - - -struct OpenGL_t -{ - // Handle to the OS OpenGL DLL - void* dll_handle; - - PFNGLGETERRORPROC __glGetError; - PFNGLGENQUERIESPROC __glGenQueries; - PFNGLDELETEQUERIESPROC __glDeleteQueries; - PFNGLBEGINQUERYPROC __glBeginQuery; - PFNGLENDQUERYPROC __glEndQuery; - PFNGLGETQUERYOBJECTIVPROC __glGetQueryObjectiv; - PFNGLGETQUERYOBJECTUIVPROC __glGetQueryObjectuiv; - PFNGLGETQUERYOBJECTI64VPROC __glGetQueryObjecti64v; - PFNGLGETQUERYOBJECTUI64VPROC __glGetQueryObjectui64v; - PFNGLQUERYCOUNTERPROC __glQueryCounter; - PFNGLGETINTEGER64VPROC __glGetInteger64v; - PFNGLFINISHPROC __glFinish; - - // Queue to the OpenGL main update thread - // Given that BeginSample/EndSample need to be called from the same thread that does the update, there - // is really no need for this to be a thread-safe queue. I'm using it for its convenience. - rmtMessageQueue* mq_to_opengl_main; - - // Mark the first time so that remaining timestamps are offset from this - rmtU64 first_timestamp; - // Last time in us (CPU time, via usTimer_Get) since we last resync'ed CPU & GPU - rmtU64 last_resync; - - // Sample trees in transit in the message queue for release on shutdown - Buffer* flush_samples; -}; - - -static GLenum rmtglGetError(void) -{ - if (g_Remotery != NULL) - { - assert(g_Remotery->opengl != NULL); - if (g_Remotery->opengl->__glGetError != NULL) - return g_Remotery->opengl->__glGetError(); - } - - return (GLenum)0; -} - - -#ifdef RMT_PLATFORM_LINUX - #ifdef __cplusplus - extern "C" void* glXGetProcAddressARB(const GLubyte*); - #else - extern void* glXGetProcAddressARB(const GLubyte*); - #endif -#endif - - -static ProcReturnType rmtglGetProcAddress(OpenGL* opengl, const char* symbol) -{ - #if defined(RMT_PLATFORM_WINDOWS) - { - // Get OpenGL extension-loading function for each call - typedef ProcReturnType (WINAPI * wglGetProcAddressFn)(LPCSTR); - assert(opengl != NULL); - { - wglGetProcAddressFn wglGetProcAddress = (wglGetProcAddressFn)rmtGetProcAddress(opengl->dll_handle, "wglGetProcAddress"); - if (wglGetProcAddress != NULL) - return wglGetProcAddress(symbol); - } - } - - #elif defined(RMT_PLATFORM_MACOS) && !defined(GLEW_APPLE_GLX) - - return rmtGetProcAddress(opengl->dll_handle, symbol); - - #elif defined(RMT_PLATFORM_LINUX) - - return glXGetProcAddressARB((const GLubyte*)symbol); - - #endif - - return NULL; -} - - -static rmtError OpenGL_Create(OpenGL** opengl) -{ - rmtError error; - - assert(opengl != NULL); - - *opengl = (OpenGL*)rmtMalloc(sizeof(OpenGL)); - if (*opengl == NULL) - return RMT_ERROR_MALLOC_FAIL; - - (*opengl)->dll_handle = NULL; - - (*opengl)->__glGetError = NULL; - (*opengl)->__glGenQueries = NULL; - (*opengl)->__glDeleteQueries = NULL; - (*opengl)->__glBeginQuery = NULL; - (*opengl)->__glEndQuery = NULL; - (*opengl)->__glGetQueryObjectiv = NULL; - (*opengl)->__glGetQueryObjectuiv = NULL; - (*opengl)->__glGetQueryObjecti64v = NULL; - (*opengl)->__glGetQueryObjectui64v = NULL; - (*opengl)->__glQueryCounter = NULL; - (*opengl)->__glGetInteger64v = NULL; - (*opengl)->__glFinish = NULL; - - (*opengl)->mq_to_opengl_main = NULL; - (*opengl)->first_timestamp = 0; - (*opengl)->last_resync = 0; - (*opengl)->flush_samples = NULL; - - New_1(Buffer, (*opengl)->flush_samples, 8 * 1024); - if (error != RMT_ERROR_NONE) - { - Delete(OpenGL, *opengl); - return error; - } - - New_1(rmtMessageQueue, (*opengl)->mq_to_opengl_main, g_Settings.messageQueueSizeInBytes); - - return error; -} - - -static void OpenGL_Destructor(OpenGL* opengl) -{ - assert(opengl != NULL); - Delete(rmtMessageQueue, opengl->mq_to_opengl_main); - Delete(Buffer, opengl->flush_samples); -} - -static void SyncOpenGLCpuGpuTimes(rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - rmtU64 cpu_time_start = 0; - rmtU64 cpu_time_stop = 0; - rmtU64 average_half_RTT = 0; //RTT = Rountrip Time. - GLint64 gpu_base = 0; - int i; - - rmtglFinish(); - - for (i=0; i<RMT_GPU_CPU_SYNC_NUM_ITERATIONS; ++i) - { - rmtU64 half_RTT; - - rmtglFinish(); - cpu_time_start = usTimer_Get(&g_Remotery->timer); - rmtglGetInteger64v(GL_TIMESTAMP, &gpu_base); - cpu_time_stop = usTimer_Get(&g_Remotery->timer); - //Average the time it takes a roundtrip from CPU to GPU - //while doing nothing other than getting timestamps - half_RTT = (cpu_time_stop - cpu_time_start) >> 1ULL; - if( i == 0 ) - average_half_RTT = half_RTT; - else - average_half_RTT = (average_half_RTT + half_RTT) >> 1ULL; - } - - // All GPU times are offset from gpu_base, and then taken to - // the same relative origin CPU timestamps are based on. - // CPU is in us, we must translate it to ns. - *out_first_timestamp = (rmtU64)(gpu_base) - (cpu_time_start + average_half_RTT) * 1000ULL; - *out_last_resync = cpu_time_stop; -} - - -typedef struct OpenGLTimestamp -{ - // Inherit so that timestamps can be quickly allocated - ObjectLink Link; - - // Pair of timestamp queries that wrap the sample - GLuint queries2; - rmtU64 cpu_timestamp; -} OpenGLTimestamp; - - -static rmtError OpenGLTimestamp_Constructor(OpenGLTimestamp* stamp) -{ - GLenum error; - - assert(stamp != NULL); - - ObjectLink_Constructor((ObjectLink*)stamp); - - // Set defaults - stamp->queries0 = stamp->queries1 = 0; - stamp->cpu_timestamp = 0; - - // Empty the error queue before using it for glGenQueries - while ((error = rmtglGetError()) != GL_NO_ERROR) - ; - - // Create start/end timestamp queries - assert(g_Remotery != NULL); - rmtglGenQueries(2, stamp->queries); - error = rmtglGetError(); - if (error != GL_NO_ERROR) - return RMT_ERROR_OPENGL_ERROR; - - return RMT_ERROR_NONE; -} - - -static void OpenGLTimestamp_Destructor(OpenGLTimestamp* stamp) -{ - assert(stamp != NULL); - - // Destroy queries - if (stamp->queries0 != 0) - rmtglDeleteQueries(2, stamp->queries); -} - - -static void OpenGLTimestamp_Begin(OpenGLTimestamp* stamp) -{ - assert(stamp != NULL); - - // First query - assert(g_Remotery != NULL); - stamp->cpu_timestamp = usTimer_Get(&g_Remotery->timer); - rmtglQueryCounter(stamp->queries0, GL_TIMESTAMP); -} - - -static void OpenGLTimestamp_End(OpenGLTimestamp* stamp) -{ - assert(stamp != NULL); - - // Second query - assert(g_Remotery != NULL); - rmtglQueryCounter(stamp->queries1, GL_TIMESTAMP); -} - -static rmtBool OpenGLTimestamp_GetData(OpenGLTimestamp* stamp, rmtU64* out_start, rmtU64* out_end, rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - GLuint64 start = 0, end = 0; - GLint startAvailable = 0, endAvailable = 0; - - assert(g_Remotery != NULL); - - assert(stamp != NULL); - assert(stamp->queries0 != 0 && stamp->queries1 != 0); - - // Check to see if all queries are ready - // If any fail to arrive, wait until later - rmtglGetQueryObjectiv(stamp->queries0, GL_QUERY_RESULT_AVAILABLE, &startAvailable); - if (!startAvailable) - return RMT_FALSE; - rmtglGetQueryObjectiv(stamp->queries1, GL_QUERY_RESULT_AVAILABLE, &endAvailable); - if (!endAvailable) - return RMT_FALSE; - - rmtglGetQueryObjectui64v(stamp->queries0, GL_QUERY_RESULT, &start); - rmtglGetQueryObjectui64v(stamp->queries1, GL_QUERY_RESULT, &end); - - // Mark the first timestamp. We may resync if we detect the GPU timestamp is in the - // past (i.e. happened before the CPU command) since it should be impossible. - assert(out_first_timestamp != NULL); - if (*out_first_timestamp == 0 || ((start - *out_first_timestamp) / 1000ULL) < stamp->cpu_timestamp) - SyncOpenGLCpuGpuTimes(out_first_timestamp, out_last_resync); - - // Calculate start and end timestamps (we want us, the queries give us ns) - *out_start = (rmtU64)(start - *out_first_timestamp) / 1000ULL; - *out_end = (rmtU64)(end - *out_first_timestamp) / 1000ULL; - - return RMT_TRUE; -} - - -typedef struct OpenGLSample -{ - // IS-A inheritance relationship - Sample base; - - OpenGLTimestamp* timestamp; - -} OpenGLSample; - - -static rmtError OpenGLSample_Constructor(OpenGLSample* sample) -{ - rmtError error; - - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = SampleType_OpenGL; - sample->base.size_bytes = sizeof(OpenGLSample); - New_0(OpenGLTimestamp, sample->timestamp); - - return RMT_ERROR_NONE; -} - - -static void OpenGLSample_Destructor(OpenGLSample* sample) -{ - Delete(OpenGLTimestamp, sample->timestamp); - Sample_Destructor((Sample*)sample); -} - - -RMT_API void _rmt_BindOpenGL() -{ - if (g_Remotery != NULL) - { - OpenGL* opengl = g_Remotery->opengl; - assert(opengl != NULL); - - #if defined (RMT_PLATFORM_WINDOWS) - opengl->dll_handle = rmtLoadLibrary("opengl32.dll"); - #elif defined (RMT_PLATFORM_MACOS) - opengl->dll_handle = rmtLoadLibrary("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"); - #elif defined (RMT_PLATFORM_LINUX) - opengl->dll_handle = rmtLoadLibrary("libGL.so"); - #endif - - opengl->__glGetError = (PFNGLGETERRORPROC)rmtGetProcAddress(opengl->dll_handle, "glGetError"); - opengl->__glGenQueries = (PFNGLGENQUERIESPROC)rmtglGetProcAddress(opengl, "glGenQueries"); - opengl->__glDeleteQueries = (PFNGLDELETEQUERIESPROC)rmtglGetProcAddress(opengl, "glDeleteQueries"); - opengl->__glBeginQuery = (PFNGLBEGINQUERYPROC)rmtglGetProcAddress(opengl, "glBeginQuery"); - opengl->__glEndQuery = (PFNGLENDQUERYPROC)rmtglGetProcAddress(opengl, "glEndQuery"); - opengl->__glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)rmtglGetProcAddress(opengl, "glGetQueryObjectiv"); - opengl->__glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)rmtglGetProcAddress(opengl, "glGetQueryObjectuiv"); - opengl->__glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VPROC)rmtglGetProcAddress(opengl, "glGetQueryObjecti64v"); - opengl->__glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VPROC)rmtglGetProcAddress(opengl, "glGetQueryObjectui64v"); - opengl->__glQueryCounter = (PFNGLQUERYCOUNTERPROC)rmtglGetProcAddress(opengl, "glQueryCounter"); - opengl->__glGetInteger64v = (PFNGLGETINTEGER64VPROC)rmtglGetProcAddress(opengl, "glGetInteger64v"); - opengl->__glFinish = (PFNGLFINISHPROC)rmtGetProcAddress(opengl->dll_handle, "glFinish"); - } -} - - -static void UpdateOpenGLFrame(void); - - -RMT_API void _rmt_UnbindOpenGL(void) -{ - if (g_Remotery != NULL) - { - OpenGL* opengl = g_Remotery->opengl; - assert(opengl != NULL); - - // Stall waiting for the OpenGL queue to empty into the Remotery queue - while (!rmtMessageQueue_IsEmpty(opengl->mq_to_opengl_main)) -#if 0 - UpdateOpenGLFrame(); -#else - //GPAC crude patch to avoid deadlock on opengl unbind upon exit - { - Message* message = rmtMessageQueue_PeekNextMessage(opengl->mq_to_opengl_main); - if (message == NULL) - break; - - rmtMessageQueue_ConsumeNextMessage(opengl->mq_to_opengl_main, message); - } -#endif - // There will be a whole bunch of OpenGL sample trees queued up the remotery queue that need releasing - FreePendingSampleTrees(g_Remotery, SampleType_OpenGL, opengl->flush_samples); - - // Forcefully delete sample tree on this thread to release time stamps from - // the same thread that created them - Remotery_DeleteSampleTree(g_Remotery, SampleType_OpenGL); - - // Release reference to the OpenGL DLL - if (opengl->dll_handle != NULL) - { - rmtFreeLibrary(opengl->dll_handle); - opengl->dll_handle = NULL; - } - } -} - - -RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - if (g_Remotery->opengl->dll_handle == NULL) return; - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash = ThreadSampler_GetNameHash(ts, name, hash_cache); - - // Create the OpenGL tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a OpenGL binding is not yet available. - SampleTree** ogl_tree = &ts->sample_treesSampleType_OpenGL; - if (*ogl_tree == NULL) - { - rmtError error; - New_3(SampleTree, *ogl_tree, sizeof(OpenGLSample), (ObjConstructor)OpenGLSample_Constructor, (ObjDestructor)OpenGLSample_Destructor); - if (error != RMT_ERROR_NONE) - return; - } - - // Push the sample and activate the timestamp - if (ThreadSampler_Push(*ogl_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - OpenGLSample* ogl_sample = (OpenGLSample*)sample; - OpenGLTimestamp_Begin(ogl_sample->timestamp); - } - // Empty the error queue before using it for glGenQueries - while ( rmtglGetError() != GL_NO_ERROR) - ; - } -} - - -static rmtBool GetOpenGLSampleTimes(Sample* sample, rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - Sample* child; - - OpenGLSample* ogl_sample = (OpenGLSample*)sample; - - assert(sample != NULL); - if (ogl_sample->timestamp != NULL) - { - assert(out_last_resync != NULL); - #if (RMT_GPU_CPU_SYNC_SECONDS > 0) - if (*out_last_resync < ogl_sample->timestamp->cpu_timestamp) - { - //Convert from us to seconds. - rmtU64 time_diff = (ogl_sample->timestamp->cpu_timestamp - *out_last_resync) / 1000000ULL; - if (time_diff > RMT_GPU_CPU_SYNC_SECONDS) - SyncOpenGLCpuGpuTimes(out_first_timestamp, out_last_resync); - } - #endif - - if (!OpenGLTimestamp_GetData(ogl_sample->timestamp, &sample->us_start, &sample->us_end, out_first_timestamp, out_last_resync)) - return RMT_FALSE; - - sample->us_length = sample->us_end - sample->us_start; - } - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetOpenGLSampleTimes(child, out_first_timestamp, out_last_resync)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - - -static void UpdateOpenGLFrame(void) -{ - OpenGL* opengl; - - CHECK_REMOTERY() - - opengl = g_Remotery->opengl; - assert(opengl != NULL); - - rmt_BeginCPUSample(rmt_UpdateOpenGLFrame, 0); - - // Process all messages in the OpenGL queue - while (1) - { - Msg_SampleTree* sample_tree; - Sample* sample; - - Message* message = rmtMessageQueue_PeekNextMessage(opengl->mq_to_opengl_main); - if (message == NULL) - break; - - // There's only one valid message type in this queue - assert(message->id == MsgID_SampleTree); - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->root_sample; - assert(sample->type == SampleType_OpenGL); - - // Retrieve timing of all OpenGL samples - // If they aren't ready leave the message unconsumed, holding up later frames and maintaining order - if (!GetOpenGLSampleTimes(sample, &opengl->first_timestamp,&opengl->last_resync)) - break; - - // Pass samples onto the remotery thread for sending to the viewer - AddSampleTreeMessage(g_Remotery->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->thread_name, message->thread_sampler); - rmtMessageQueue_ConsumeNextMessage(opengl->mq_to_opengl_main, message); - } - - rmt_EndCPUSample(); -} - - -RMT_API void _rmt_EndOpenGLSample(void) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - if (g_Remotery->opengl->dll_handle == NULL) return; - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - // Close the timestamp - OpenGLSample* ogl_sample = (OpenGLSample*)ts->sample_treesSampleType_OpenGL->current_parent; - if (ogl_sample->base.recurse_depth > 0) - { - ogl_sample->base.recurse_depth--; - } - else - { - if (ogl_sample->timestamp != NULL) - OpenGLTimestamp_End(ogl_sample->timestamp); - - // Send to the update loop for ready-polling - if (ThreadSampler_Pop(ts, g_Remotery->opengl->mq_to_opengl_main, (Sample*)ogl_sample)) - // Perform ready-polling on popping of the root sample - UpdateOpenGLFrame(); - } - } - // Empty the error queue before using it for glGenQueries - while ( rmtglGetError() != GL_NO_ERROR) - ; - -} - - - -#endif // RMT_USE_OPENGL - - - -/* - ------------------------------------------------------------------------------------------------------------------------ - ------------------------------------------------------------------------------------------------------------------------ - @Metal: Metal event sampling - ------------------------------------------------------------------------------------------------------------------------ - ------------------------------------------------------------------------------------------------------------------------ - */ - - - -#if RMT_USE_METAL - - - -struct Metal_t -{ - // Queue to the Metal main update thread - // Given that BeginSample/EndSample need to be called from the same thread that does the update, there - // is really no need for this to be a thread-safe queue. I'm using it for its convenience. - rmtMessageQueue* mq_to_metal_main; -}; - - -static rmtError Metal_Create(Metal** metal) -{ - rmtError error; - - assert(metal != NULL); - - *metal = (Metal*)rmtMalloc(sizeof(Metal)); - if (*metal == NULL) - return RMT_ERROR_MALLOC_FAIL; - - (*metal)->mq_to_metal_main = NULL; - - New_1(rmtMessageQueue, (*metal)->mq_to_metal_main, g_Settings.messageQueueSizeInBytes); - return error; -} - - -static void Metal_Destructor(Metal* metal) -{ - assert(metal != NULL); - Delete(rmtMessageQueue, metal->mq_to_metal_main); -} - - -typedef struct MetalTimestamp -{ - // Inherit so that timestamps can be quickly allocated - ObjectLink Link; - - // Output from GPU callbacks - rmtU64 start; - rmtU64 end; - rmtBool ready; -} MetalTimestamp; - - -static rmtError MetalTimestamp_Constructor(MetalTimestamp* stamp) -{ - assert(stamp != NULL); - - ObjectLink_Constructor((ObjectLink*)stamp); - - // Set defaults - stamp->start = 0; - stamp->end = 0; - stamp->ready = RMT_FALSE; - - return RMT_ERROR_NONE; -} - - -static void MetalTimestamp_Destructor(MetalTimestamp* stamp) -{ - assert(stamp != NULL); -} - - -rmtU64 rmtMetal_usGetTime() -{ - // Share the CPU timer for auto-sync - assert(g_Remotery != NULL); - return usTimer_Get(&g_Remotery->timer); -} - - -void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready); - - -static void MetalTimestamp_Begin(MetalTimestamp* stamp) -{ - assert(stamp != NULL); - stamp->ready = RMT_FALSE; - - // Metal can currently only issue callbacks at the command buffer level - // So for now measure execution of the entire command buffer - rmtMetal_MeasureCommandBuffer(&stamp->start, &stamp->end, &stamp->ready); -} - - -static void MetalTimestamp_End(MetalTimestamp* stamp) -{ - assert(stamp != NULL); - - // As Metal can currently only measure entire command buffers, this function is a no-op - // as the completed handler was already issued in Begin -} - - -static rmtBool MetalTimestamp_GetData(MetalTimestamp* stamp, rmtU64* out_start, rmtU64* out_end) -{ - assert(g_Remotery != NULL); - assert(stamp != NULL); - - // GPU writes ready flag when complete handler is called - if (stamp->ready == RMT_FALSE) - return RMT_FALSE; - - *out_start = stamp->start; - *out_end = stamp->end; - - return RMT_TRUE; -} - - -typedef struct MetalSample -{ - // IS-A inheritance relationship - Sample base; - - MetalTimestamp* timestamp; - -} MetalSample; - - -static rmtError MetalSample_Constructor(MetalSample* sample) -{ - rmtError error; - - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = SampleType_Metal; - sample->base.size_bytes = sizeof(MetalSample); - New_0(MetalTimestamp, sample->timestamp); - - return RMT_ERROR_NONE; -} - - -static void MetalSample_Destructor(MetalSample* sample) -{ - Delete(MetalTimestamp, sample->timestamp); - Sample_Destructor((Sample*)sample); -} - - -static void UpdateOpenGLFrame(void); - - -/*RMT_API void _rmt_UnbindMetal(void) -{ - if (g_Remotery != NULL) - { - Metal* metal = g_Remotery->metal; - assert(metal != NULL); - - // Stall waiting for the Metal queue to empty into the Remotery queue - while (!rmtMessageQueue_IsEmpty(metal->mq_to_metal_main)) - UpdateMetalFrame(); - - // Forcefully delete sample tree on this thread to release time stamps from - // the same thread that created them - Remotery_BlockingDeleteSampleTree(g_Remotery, SampleType_Metal); - } -}*/ - - -RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash = ThreadSampler_GetNameHash(ts, name, hash_cache); - - // Create the Metal tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a Metal binding is not yet available. - SampleTree** metal_tree = &ts->sample_treesSampleType_Metal; - if (*metal_tree == NULL) - { - rmtError error; - New_3(SampleTree, *metal_tree, sizeof(MetalSample), (ObjConstructor)MetalSample_Constructor, (ObjDestructor)MetalSample_Destructor); - if (error != RMT_ERROR_NONE) - return; - } - - // Push the sample and activate the timestamp - if (ThreadSampler_Push(*metal_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - MetalSample* metal_sample = (MetalSample*)sample; - MetalTimestamp_Begin(metal_sample->timestamp); - } - } -} - - -static rmtBool GetMetalSampleTimes(Sample* sample) -{ - Sample* child; - - MetalSample* metal_sample = (MetalSample*)sample; - - assert(sample != NULL); - if (metal_sample->timestamp != NULL) - { - if (!MetalTimestamp_GetData(metal_sample->timestamp, &sample->us_start, &sample->us_end)) - return RMT_FALSE; - - sample->us_length = sample->us_end - sample->us_start; - } - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetMetalSampleTimes(child)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - - -static void UpdateMetalFrame(void) -{ - Metal* metal; - - CHECK_REMOTERY() - - metal = g_Remotery->metal; - assert(metal != NULL); - - rmt_BeginCPUSample(rmt_UpdateMetalFrame, 0); - - // Process all messages in the Metal queue - while (1) - { - Msg_SampleTree* sample_tree; - Sample* sample; - - Message* message = rmtMessageQueue_PeekNextMessage(metal->mq_to_metal_main); - if (message == NULL) - break; - - // There's only one valid message type in this queue - assert(message->id == MsgID_SampleTree); - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->root_sample; - assert(sample->type == SampleType_Metal); - - // Retrieve timing of all Metal samples - // If they aren't ready leave the message unconsumed, holding up later frames and maintaining order - if (!GetMetalSampleTimes(sample)) - break; - - // Pass samples onto the remotery thread for sending to the viewer - AddSampleTreeMessage(g_Remotery->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->thread_name, message->thread_sampler); - rmtMessageQueue_ConsumeNextMessage(metal->mq_to_metal_main, message); - } - - rmt_EndCPUSample(); -} - - -RMT_API void _rmt_EndMetalSample(void) -{ - ThreadSampler* ts; - - CHECK_REMOTERY() - - if (Remotery_GetThreadSampler(g_Remotery, &ts) == RMT_ERROR_NONE) - { - // Close the timestamp - MetalSample* metal_sample = (MetalSample*)ts->sample_treesSampleType_Metal->current_parent; - if (metal_sample->base.recurse_depth > 0) - { - metal_sample->base.recurse_depth--; - } - else - { - if (metal_sample->timestamp != NULL) - MetalTimestamp_End(metal_sample->timestamp); - - // Send to the update loop for ready-polling - if (ThreadSampler_Pop(ts, g_Remotery->metal->mq_to_metal_main, (Sample*)metal_sample)) - // Perform ready-polling on popping of the root sample - UpdateMetalFrame(); - } - } -} - - - -#endif // RMT_USE_METAL - - -RMT_API void _rmt_EnableSampling(rmtBool enable) -{ - if (g_Remotery==NULL) return; - g_Remotery->sampling_disabled = !enable; -} -RMT_API rmtBool _rmt_SamplingEnabled() -{ - if (g_Remotery==NULL) return 0; - return !g_Remotery->sampling_disabled; -} - -#endif // RMT_ENABLED -
View file
gpac-2.4.0.tar.gz/src/utils/cache.c
Deleted
@@ -1,1108 +0,0 @@ -/* - * GPAC Multimedia Framework - * - * Authors: Jean Le Feuvre, Pierre Souchay - * Copyright (c) Telecom ParisTech 2010-2023 - * All rights reserved - * - * This file is part of GPAC / common tools sub-project - * - * GPAC 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, or (at your option) - * any later version. - * - * GPAC 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 library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/cache.h> - -#ifndef GPAC_DISABLE_NETWORK - -#include <gpac/network.h> -#include <gpac/download.h> -#include <gpac/token.h> -#include <gpac/thread.h> -#include <gpac/list.h> -#include <gpac/base_coding.h> -#include <gpac/tools.h> -#include <gpac/config_file.h> -#include <stdio.h> -#include <string.h> - -#if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500 -#include <unistd.h> -#endif - -static const char * CACHE_SECTION_NAME = "cache"; -static const char * CACHE_SECTION_NAME_URL = "url"; -static const char * CACHE_SECTION_NAME_RANGE = "range"; -static const char * CACHE_SECTION_NAME_ETAG = "ETag"; -static const char * CACHE_SECTION_NAME_MIME_TYPE = "Content-Type"; -static const char * CACHE_SECTION_NAME_CONTENT_SIZE = "Content-Length"; -static const char * CACHE_SECTION_NAME_LAST_MODIFIED = "Last-Modified"; - -enum CacheValid -{ - MUST_REVALIDATE = 1, - IS_HTTPS = 1<<1, - CORRUPTED = 1<<2, - NO_CACHE = 1<<3, - DELETED = 1<<4 -}; - -struct __CacheReaderStruct { - FILE * readPtr; - s64 readPosition; -}; - -typedef struct __DownloadedRangeStruc { - u32 start; - u32 end; - const char * filename; -} * DownloadedRange; - -//#define ENABLE_WRITE_MX - -/** -* This opaque structure handles the data from the cache -*/ -struct __DownloadedCacheEntryStruct -{ - /** - * URL of the cache (never NULL) - */ - char * url; - /** - * Hash of the cache (never NULL) - */ - char * hash; - /** - * Name of the cache filename, (can be NULL) - */ - char * cache_filename; - /** - * Name of the cached properties filename , (can be NULL) - */ - GF_Config * properties; - /** - * Theorical size of cache if any - */ - u32 contentLength; - /** - * Real size of cache - */ - u32 cacheSize; - /** - * GMT timestamp for revalidation - */ - u32 validity; - /** - * The last modification time on the server - */ - char * serverLastModified; - /** - * The last modification time of the cache if any - */ - char * diskLastModified; - /** - * ETag if any - */ - char * serverETag; - /** - * ETag if any - */ - char * diskETag; - /** - * Mime-type (never NULL) - */ - char * mimeType; - /** - * Write pointer for the cache - */ - FILE * writeFilePtr; - /** - * Bytes written during this cache session - */ - u32 written_in_cache; - /** - * Flag indicating whether we have to revalidate - */ - enum CacheValid flags; - - const GF_DownloadSession * write_session; - -#ifdef ENABLE_WRITE_MX - GF_Mutex * write_mutex; -#endif - - GF_List * sessions; - - Bool deletableFilesOnDelete; - - GF_DownloadManager * dm; - - /*start and end range of the cache*/ - u64 range_start, range_end; - - Bool continue_file; - Bool file_exists; - - u32 previousRangeContentLength; - /*set once headers have been processed*/ - Bool headers_done; - /** - * Set to 1 if file is not stored on disk - */ - Bool memory_stored; - u32 mem_allocated; - u8 *mem_storage; - char *forced_headers; - u32 downtime; - - GF_Blob cache_blob; - GF_Blob *external_blob; - Bool persistent; -}; - -Bool gf_cache_entry_persistent(const DownloadedCacheEntry entry) -{ - return entry ? entry->persistent : GF_FALSE; -} -void gf_cache_entry_set_persistent(const DownloadedCacheEntry entry) -{ - if (entry) entry->persistent = GF_TRUE; -} - -Bool delete_cache_files(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) { - const char * startPattern; - int sz; - gf_assert( cbck ); - gf_assert( item_name ); - gf_assert( item_path); - startPattern = (const char *) cbck; - sz = (u32) strlen( startPattern ); - if (!strncmp(startPattern, item_name, sz)) { - if (GF_OK != gf_file_delete(item_path)) - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE : failed to cleanup file %s\n", item_path)); - } - return GF_FALSE; -} - -static const char * cache_file_prefix = "gpac_cache_"; - -Bool gather_cache_size(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) -{ - u64 *out_size = (u64 *)cbck; - if (!strncmp(cache_file_prefix, item_name, strlen(cache_file_prefix))) { - *out_size += file_info->size; - } - return 0; -} - -u64 gf_cache_get_size(const char * directory) { - u64 size = 0; - gf_enum_directory(directory, GF_FALSE, gather_cache_size, (void*)&size, NULL); - return size; -} - -GF_Err gf_cache_delete_all_cached_files(const char * directory) { - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("Deleting cached files in %s...\n", directory)); - return gf_enum_directory( directory, GF_FALSE, delete_cache_files, (void*)cache_file_prefix, NULL); -} - -void gf_cache_entry_set_delete_files_when_deleted(const DownloadedCacheEntry entry) { - if (entry && !entry->persistent) - entry->deletableFilesOnDelete = GF_TRUE; -} - -Bool gf_cache_entry_is_delete_files_when_deleted(const DownloadedCacheEntry entry) -{ - if (!entry) - return GF_FALSE; - return entry->deletableFilesOnDelete; -} - -#define CHECK_ENTRY if (!entry) { GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE entry is null at " __FILE__ ":%d\n", __LINE__)); return GF_BAD_PARAM; } - -/* -* Getters functions -*/ - -const char * gf_cache_get_etag_on_server ( const DownloadedCacheEntry entry ) -{ - return entry ? entry->serverETag : NULL; -} - -const char * gf_cache_get_mime_type ( const DownloadedCacheEntry entry ) -{ - return entry ? entry->mimeType : NULL; -} - - -GF_Err gf_cache_set_headers_processed(const DownloadedCacheEntry entry) -{ - if (!entry) return GF_BAD_PARAM; - entry->headers_done = GF_TRUE; - return GF_OK; -} - -Bool gf_cache_are_headers_processed(const DownloadedCacheEntry entry) -{ - if (!entry) return GF_FALSE; - return entry->headers_done; -} - -GF_Err gf_cache_set_etag_on_server(const DownloadedCacheEntry entry, const char * eTag ) { - if (!entry) - return GF_BAD_PARAM; - if (entry->serverETag) - gf_free(entry->serverETag); - entry->serverETag = eTag ? gf_strdup(eTag) : NULL; - return GF_OK; -} - -GF_Err gf_cache_set_etag_on_disk(const DownloadedCacheEntry entry, const char * eTag ) { - if (!entry) - return GF_BAD_PARAM; - if (entry->diskETag) - gf_free(entry->diskETag); - entry->diskETag = eTag ? gf_strdup(eTag) : NULL; - return GF_OK; -} - -GF_Err gf_cache_set_mime_type(const DownloadedCacheEntry entry, const char * mime_type ) { - if (!entry) - return GF_BAD_PARAM; - if (entry->mimeType) - gf_free(entry->mimeType); - entry->mimeType = mime_type? gf_strdup( mime_type) : NULL; - return GF_OK; -} - -u64 gf_cache_get_start_range( const DownloadedCacheEntry entry ) -{ - return entry ? entry->range_start : 0; -} - -u64 gf_cache_get_end_range( const DownloadedCacheEntry entry ) -{ - return entry ? entry->range_end : 0; -} - -const char * gf_cache_get_url ( const DownloadedCacheEntry entry ) -{ - return entry ? entry->url : NULL; -} - -const char * gf_cache_get_last_modified_on_server ( const DownloadedCacheEntry entry ) -{ - return entry ? entry->serverLastModified : NULL; -} - -GF_Err gf_cache_set_last_modified_on_server ( const DownloadedCacheEntry entry, const char * newLastModified ) -{ - if (!entry) - return GF_BAD_PARAM; - if (entry->serverLastModified) - gf_free(entry->serverLastModified); - entry->serverLastModified = newLastModified ? gf_strdup(newLastModified) : NULL; - return GF_OK; -} - -GF_Err gf_cache_set_last_modified_on_disk ( const DownloadedCacheEntry entry, const char * newLastModified ) -{ - if (!entry) - return GF_BAD_PARAM; - if (entry->diskLastModified) - gf_free(entry->diskLastModified); - entry->diskLastModified = newLastModified ? gf_strdup(newLastModified) : NULL; - return GF_OK; -} - -#define _CACHE_TMP_SIZE 4096 - -GF_Err gf_cache_flush_disk_cache ( const DownloadedCacheEntry entry ) -{ - char buff100; - CHECK_ENTRY; - if ( !entry->properties) - return GF_OK; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE gf_cache_flush_disk_cache:%d for entry=%p\n", __LINE__, entry)); - gf_cfg_set_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_URL, entry->url); - - sprintf(buff, LLD"-"LLD, entry->range_start, entry->range_end); - gf_cfg_set_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_RANGE, buff); - - if (entry->mimeType) - gf_cfg_set_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_MIME_TYPE, entry->mimeType); - if (entry->diskETag) - gf_cfg_set_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_ETAG, entry->diskETag); - if (entry->diskLastModified) - gf_cfg_set_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_LAST_MODIFIED, entry->diskLastModified); - { - snprintf(buff, 16, "%d", entry->contentLength); - gf_cfg_set_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_CONTENT_SIZE, buff); - } - return gf_cfg_save ( entry->properties ); -} - -u32 gf_cache_get_cache_filesize ( const DownloadedCacheEntry entry ) -{ - return entry ? entry->cacheSize : -1; -} - -const char * gf_cache_get_cache_filename( const DownloadedCacheEntry entry ) -{ - return entry ? entry->cache_filename : NULL; -} - -GF_Err gf_cache_get_http_headers(const DownloadedCacheEntry entry, const char **etag, const char **last_modif) -{ - if (!entry || !etag || !last_modif) - return GF_BAD_PARAM; - - *etag = *last_modif = NULL; - if (entry->flags) - return GF_OK; - if (gf_cache_check_if_cache_file_is_corrupted(entry)) - return GF_OK; - - *etag = entry->diskETag; - *last_modif = entry->diskLastModified; - if (entry->persistent && entry->memory_stored) { - *etag = entry->serverETag; - *last_modif = entry->serverLastModified; - } - return GF_OK; -} - -#define _CACHE_HASH_SIZE 20 -#define _CACHE_MAX_EXTENSION_SIZE 6 -static const char * default_cache_file_suffix = ".dat"; -static const char * cache_file_info_suffix = ".txt"; - -DownloadedCacheEntry gf_cache_create_entry ( GF_DownloadManager * dm, const char * cache_directory, const char * url , u64 start_range, u64 end_range, Bool mem_storage, GF_Mutex *mx) -{ - char tmp_CACHE_TMP_SIZE; - u8 hash_CACHE_HASH_SIZE; - int sz; - char ext_CACHE_MAX_EXTENSION_SIZE; - DownloadedCacheEntry entry = NULL; - if ( !dm || !url || !cache_directory) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, - ("CACHE gf_cache_create_entry :%d, dm=%p, url=%s cache_directory=%s, aborting.\n", __LINE__, dm, url, cache_directory)); - return entry; - } - sz = (u32) strlen ( url ); - if ( sz > _CACHE_TMP_SIZE ) - { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, - ("CACHE gf_cache_create_entry:%d : ERROR, URL is too long (%d chars), more than %d chars.\n", __LINE__, sz, _CACHE_TMP_SIZE )); - return entry; - } - tmp0 = '\0'; - /*generate hash of the full url*/ - if (start_range && end_range) { - sprintf(tmp, "%s_"LLD"-"LLD, url, start_range, end_range ); - } else { - strcpy ( tmp, url ); - } - gf_sha1_csum ((u8*) tmp, (u32) strlen ( tmp ), hash ); - tmp0 = 0; - { - int i; - for ( i=0; i<20; i++ ) - { - char t3; - t2 = 0; - sprintf ( t, "%02X", hashi ); - strcat ( tmp, t ); - } - } - assert ( strlen ( tmp ) == (_CACHE_HASH_SIZE * 2) ); - - GF_SAFEALLOC(entry, struct __DownloadedCacheEntryStruct); - - if ( !entry ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("gf_cache_create_entry:%d : out of memory !\n", __LINE__)); - return NULL; - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, entry=%p\n", __LINE__, entry)); - - entry->url = gf_strdup ( url ); - entry->hash = gf_strdup ( tmp ); - - entry->memory_stored = mem_storage; - - entry->cacheSize = 0; - entry->contentLength = 0; - entry->serverETag = NULL; - entry->diskETag = NULL; - entry->flags = 0; - entry->validity = 0; - entry->diskLastModified = NULL; - entry->serverLastModified = NULL; - entry->dm = dm; - entry->range_start = start_range; - entry->range_end = end_range; - -#ifdef ENABLE_WRITE_MX - { - char name1024; - snprintf(name, sizeof(name), "CachedEntryWriteMx=%p, url=%s", (void*) entry, url); - entry->write_mutex = gf_mx_new(name); - gf_assert(entry->write_mutex); - } -#endif - - entry->deletableFilesOnDelete = GF_FALSE; - entry->write_session = NULL; - entry->sessions = gf_list_new(); - - if (entry->memory_stored) { - entry->cache_filename = (char*)gf_malloc ( strlen ("gmem://") + 8 + strlen("@") + 16 + 1); - } else { - /* Sizeof cache directory + hash + possible extension */ - entry->cache_filename = (char*)gf_malloc ( strlen ( cache_directory ) + strlen(cache_file_prefix) + strlen(tmp) + _CACHE_MAX_EXTENSION_SIZE + 1); - } - - if ( !entry->hash || !entry->url || !entry->cache_filename || !entry->sessions) - { - GF_Err err; - /* Probably out of memory */ - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, aborting due to OUT of MEMORY !\n", __LINE__)); - err = gf_cache_delete_entry ( entry ); - if ( err != GF_OK ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, failed to delete cache entry!\n", __LINE__)); - } - return NULL; - } - - if (entry->memory_stored) { - entry->cache_blob.mx = mx; - entry->cache_blob.data = entry->mem_storage; - entry->cache_blob.size = entry->contentLength; - char *burl = gf_blob_register(&entry->cache_blob); - if (burl) { - strcpy(entry->cache_filename, burl); - gf_free(burl); - } - return entry; - } - - - tmp0 = '\0'; - strcpy ( entry->cache_filename, cache_directory ); - strcat( entry->cache_filename, cache_file_prefix ); - strcat ( entry->cache_filename, entry->hash ); - strcpy ( tmp, url ); - - { - char * parser; - parser = strrchr ( tmp, '?' ); - if ( parser ) - parser0 = '\0'; - parser = strrchr ( tmp, '#' ); - if ( parser ) - parser0 = '\0'; - parser = strrchr ( tmp, '.' ); - if ( parser && ( strlen ( parser ) < _CACHE_MAX_EXTENSION_SIZE ) ) - strncpy(ext, parser, _CACHE_MAX_EXTENSION_SIZE); - else - strncpy(ext, default_cache_file_suffix, _CACHE_MAX_EXTENSION_SIZE); - assert (strlen(ext)); - strcat( entry->cache_filename, ext); - } - tmp0 = '\0'; - strcpy( tmp, cache_file_prefix); - strcat( tmp, entry->hash ); - strcat( tmp , ext); - strcat ( tmp, cache_file_info_suffix ); - entry->properties = gf_cfg_force_new ( cache_directory, tmp ); - if ( !entry->properties ) - { - GF_Err err; - /* out of memory ? */ - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, aborting due to OUT of MEMORY !\n", __LINE__)); - err = gf_cache_delete_entry ( entry ); - if ( err != GF_OK ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, failed to delete cache entry!\n", __LINE__)); - } - return NULL; - } - gf_cache_set_etag_on_disk(entry, gf_cfg_get_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_ETAG)); - gf_cache_set_etag_on_server(entry, gf_cfg_get_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_ETAG)); - gf_cache_set_mime_type(entry, gf_cfg_get_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_MIME_TYPE)); - gf_cache_set_last_modified_on_disk(entry, gf_cfg_get_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_LAST_MODIFIED)); - gf_cache_set_last_modified_on_server(entry, gf_cfg_get_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_LAST_MODIFIED)); - { - const char * keyValue = gf_cfg_get_key ( entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_URL ); - if ( keyValue == NULL || stricmp ( url, keyValue ) ) - entry->flags |= CORRUPTED; - - keyValue = gf_cfg_get_key(entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_RANGE); - if (keyValue) { - u64 s, e; - sscanf(keyValue, LLD"-"LLD, &s, &e); - /*mark as corrupted if not same range (we don't support this for the time being ...*/ - if ((s!=entry->range_start) || (e!=entry->range_end)) - entry->flags |= CORRUPTED; - } - } - gf_cache_check_if_cache_file_is_corrupted(entry); - - return entry; -} - -GF_Err gf_cache_set_content_length( const DownloadedCacheEntry entry, u32 length ) -{ - CHECK_ENTRY; - if (entry->continue_file) { - entry->contentLength = entry->previousRangeContentLength + length; - } else { - entry->contentLength = length; - } - return GF_OK; -} - -u32 gf_cache_get_content_length( const DownloadedCacheEntry entry) -{ - if (!entry) return 0; - if (entry->external_blob) { - return entry->external_blob->size; - } - return entry->contentLength; -} - -GF_Err gf_cache_close_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, Bool success ) -{ - GF_Err e = GF_OK; - CHECK_ENTRY; - if (!sess || !entry->write_session || entry->write_session != sess) - return GF_OK; - gf_assert( sess == entry->write_session ); - if (entry->writeFilePtr) { - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, - ("CACHE Closing file %s, %d bytes written.\n", entry->cache_filename, entry->written_in_cache)); - - if (gf_fflush( entry->writeFilePtr ) || gf_fclose( entry->writeFilePtr )) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to flush/close file on disk\n")); - e = GF_IO_ERR; - } - if (!e) { - e = gf_cache_flush_disk_cache(entry); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to flush cache entry on disk\n")); - } - } - if (!e && success) { - e = gf_cache_set_last_modified_on_disk( entry, gf_cache_get_last_modified_on_server(entry)); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to set last-modified\n")); - } else { - e = gf_cache_set_etag_on_disk( entry, gf_cache_get_etag_on_server(entry)); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to set etag\n")); - } - } - } - if (!e) { - e = gf_cache_flush_disk_cache(entry); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to flush cache entry on disk after etag/last-modified\n")); - } - } - -#if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500 - /* On UNIX, be sure to flush all the data */ - sync(); -#endif - entry->writeFilePtr = NULL; - if (GF_OK != e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to fully write file on cache, e=%d\n", e)); - } - } - entry->write_session = NULL; -#ifdef ENABLE_WRITE_MX - gf_mx_v(entry->write_mutex); -#endif - return e; -} - -GF_Err gf_cache_open_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess) -{ - CHECK_ENTRY; - if (!sess) - return GF_BAD_PARAM; -#ifdef ENABLE_WRITE_MX - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE,("CACHE Locking write mutex %p for entry=%s\n", (void*) (entry->write_mutex), entry->url) ); - gf_mx_p(entry->write_mutex); -#endif - entry->write_session = sess; - if (!entry->continue_file) { - gf_assert( ! entry->writeFilePtr); - - entry->written_in_cache = 0; - } - entry->flags &= ~CORRUPTED; - - if (entry->memory_stored) { - gf_mx_p(entry->cache_blob.mx); - - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Opening cache file %s for write (%s)...\n", entry->cache_filename, entry->url)); - if (!entry->mem_allocated || (entry->mem_allocated < entry->contentLength)) { - if (entry->contentLength) entry->mem_allocated = entry->contentLength; - else if (!entry->mem_allocated) entry->mem_allocated = 81920; - entry->mem_storage = (u8*)gf_realloc(entry->mem_storage, sizeof(char)* (entry->mem_allocated + 2) ); - } - entry->cache_blob.data = entry->mem_storage; - entry->cache_blob.size = entry->contentLength; - char *burl = gf_blob_register(&entry->cache_blob); - if (burl) { - strcpy(entry->cache_filename, burl); - gf_free(burl); - } - gf_mx_v(entry->cache_blob.mx); - - if (!entry->mem_allocated) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to create memory storage for file %s\n", entry->url)); - return GF_OUT_OF_MEM; - } - return GF_OK; - } - - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Opening cache file %s for write (%s)...\n", entry->cache_filename, entry->url)); - entry->writeFilePtr = gf_fopen(entry->cache_filename, entry->continue_file ? "a+b" : "wb"); - if (!entry->writeFilePtr) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, - ("CACHE Error while opening cache file %s for writting.\n", entry->cache_filename)); - entry->write_session = NULL; -#ifdef ENABLE_WRITE_MX - gf_mx_v(entry->write_mutex); -#endif - return GF_IO_ERR; - } - entry->file_exists = GF_TRUE; - if (entry->continue_file ) - gf_fseek(entry->writeFilePtr, 0, SEEK_END); - return GF_OK; -} - -GF_Err gf_cache_write_to_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, const char * data, const u32 size, GF_Mutex *mx) { - u32 read; - CHECK_ENTRY; - - if (!data || (!entry->writeFilePtr && !entry->mem_storage) || sess != entry->write_session) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("Incorrect parameter : data=%p, writeFilePtr=%p mem_storage=%p at "__FILE__"\n", data, entry->writeFilePtr, entry->mem_storage)); - return GF_BAD_PARAM; - } - - if (entry->memory_stored) { - if (!entry->cache_blob.mx) - entry->cache_blob.mx = mx; - gf_assert(mx); - gf_mx_p(entry->cache_blob.mx); - if (entry->written_in_cache + size > entry->mem_allocated) { - u32 new_size = MAX(entry->mem_allocated*2, entry->written_in_cache + size); - entry->mem_storage = (u8*)gf_realloc(entry->mem_storage, (new_size+2)); - entry->mem_allocated = new_size; - entry->cache_blob.data = entry->mem_storage; - entry->cache_blob.size = entry->contentLength; - char *burl = gf_blob_register(&entry->cache_blob); - if (burl) { - strcpy(entry->cache_filename, burl); - gf_free(burl); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Reallocating memory cache to %d bytes\n", new_size)); - } - memcpy(entry->mem_storage + entry->written_in_cache, data, size); - entry->written_in_cache += size; - memset(entry->mem_storage + entry->written_in_cache, 0, 2); - entry->cache_blob.size = entry->written_in_cache; - gf_mx_v(entry->cache_blob.mx); - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Storing %d bytes to memory\n", size)); - return GF_OK; - } - - read = (u32) gf_fwrite(data, size, entry->writeFilePtr); - if (read > 0) - entry->written_in_cache+= read; - if (read != size) { - /* Something bad happened */ - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, - ("CACHE Error while writting %d bytes of data to cache : has written only %d bytes.", size, read)); - gf_cache_close_write_cache(entry, sess, GF_FALSE); - gf_file_delete(entry->cache_filename); - return GF_IO_ERR; - } - if (gf_fflush(entry->writeFilePtr)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, - ("CACHE Error while flushing data bytes to cache file : %s.", entry->cache_filename)); - gf_cache_close_write_cache(entry, sess, GF_FALSE); - gf_file_delete(entry->cache_filename); - return GF_IO_ERR; - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Writing %d bytes to cache\n", size)); - return GF_OK; -} - -GF_Err gf_cache_delete_entry ( const DownloadedCacheEntry entry ) -{ - if ( !entry ) - return GF_OK; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE gf_cache_delete_entry:%d, entry=%p, url=%s\n", __LINE__, entry, entry->url)); - if (entry->writeFilePtr) { - /** Cache should have been close before, abornormal situation */ - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE gf_cache_delete_entry:%d, entry=%p, cache has not been closed properly\n", __LINE__, entry)); - gf_fclose(entry->writeFilePtr); - } -#ifdef ENABLE_WRITE_MX - if (entry->write_mutex) { - gf_mx_del(entry->write_mutex); - } -#endif - if (entry->file_exists && entry->deletableFilesOnDelete) { - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE url %s cleanup, deleting %s...\n", entry->url, entry->cache_filename)); - //if file exists, delete it (dash client may trash m3u8 files on the fly while converting them to MPD ...) - if (gf_file_exists(entry->cache_filename) && (gf_file_delete(entry->cache_filename)!=GF_OK) ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE gf_cache_delete_entry:%d, failed to delete file %s\n", __LINE__, entry->cache_filename)); - } - } -#ifdef ENABLE_WRITE_MX - entry->write_mutex = NULL; -#endif - entry->write_session = NULL; - entry->writeFilePtr = NULL; - if (entry->serverETag) - gf_free(entry->serverETag); - entry->serverETag = NULL; - - if (entry->diskETag) - gf_free(entry->diskETag); - entry->diskETag = NULL; - - if (entry->serverLastModified) - gf_free(entry->serverLastModified); - entry->serverLastModified = NULL; - - if (entry->diskLastModified) - gf_free(entry->diskLastModified); - entry->diskLastModified = NULL; - - if ( entry->hash ) { - gf_free ( entry->hash ); - entry->hash = NULL; - } - if ( entry->url ) { - gf_free ( entry->url ); - entry->url = NULL; - } - if ( entry->mimeType ) { - gf_free ( entry->mimeType ); - entry->mimeType = NULL; - } - if (entry->mem_storage && entry->mem_allocated) { - gf_free(entry->mem_storage); - } - if ( entry->forced_headers ) { - gf_free ( entry->forced_headers ); - } - - if ( entry->cache_filename ) { - gf_free ( entry->cache_filename ); - entry->cache_filename = NULL; - } - gf_blob_unregister(&entry->cache_blob); - - if (entry->external_blob) { - gf_blob_unregister(entry->external_blob); - entry->external_blob = NULL; - } - - if ( entry->properties ) { - const char * propfile = NULL; - if (entry->deletableFilesOnDelete) - propfile = gf_cfg_get_filename(entry->properties); - - if (propfile) { - //this may fail because the prop file is not yet flushed to disk - gf_file_delete( propfile ); - } - - gf_cfg_del ( entry->properties ); - entry->properties = NULL; - } - entry->dm = NULL; - if (entry->sessions) { - gf_assert( gf_list_count(entry->sessions) == 0); - gf_list_del(entry->sessions); - entry->sessions = NULL; - } - - gf_free (entry); - return GF_OK; -} - -Bool gf_cache_check_if_cache_file_is_corrupted(const DownloadedCacheEntry entry) -{ - FILE *the_cache = NULL; - if (entry->cache_filename && strncmp(entry->cache_filename, "gmem://", 7)) - the_cache = gf_fopen ( entry->cache_filename, "rb" ); - - if ( the_cache ) { - char * endPtr; - const char * keyValue = gf_cfg_get_key ( entry->properties, CACHE_SECTION_NAME, CACHE_SECTION_NAME_CONTENT_SIZE ); - - entry->cacheSize = ( u32 ) gf_fsize(the_cache); - gf_fclose ( the_cache ); - if (keyValue) { - entry->contentLength = (u32) strtoul( keyValue, &endPtr, 10); - if (*endPtr!='\0' || entry->contentLength != entry->cacheSize) { - entry->flags |= CORRUPTED; - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, Cache corrupted: file and cache info size mismatch.\n", __LINE__)); - } - } else { - entry->flags |= CORRUPTED; - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE gf_cache_create_entry:%d, CACHE is corrupted !\n", __LINE__)); - } - } else if (!entry->mem_storage) { - entry->flags |= CORRUPTED; - } - return entry->flags & CORRUPTED; -} - -s32 gf_cache_remove_session_from_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess) { - u32 i; - s32 count; - if (!entry || !sess || !entry->sessions) - return -1; - count = gf_list_count(entry->sessions); - for (i = 0 ; i < (u32)count; i++) { - GF_DownloadSession *s = (GF_DownloadSession*)gf_list_get(entry->sessions, i); - if (s == sess) { - gf_list_rem(entry->sessions, i); - count --; - break; - } - } - if (entry->write_session == sess) { - /* OK, this is not optimal to close it since we are in a mutex, - * but we don't want to risk to have another session opening - * a not fully closed cache entry */ - if (entry->writeFilePtr) { - if (gf_fclose(entry->writeFilePtr)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE gf_cache_remove_session_from_cache_entry:%d, Failed to properly close cache file '%s' of url '%s', cache may be corrupted !\n", __LINE__, entry->cache_filename, entry->url)); - } - } - entry->writeFilePtr = NULL; - entry->write_session = NULL; -#ifdef ENABLE_WRITE_MX - gf_mx_v(entry->write_mutex); -#endif - } - return count; -} - -u32 gf_cache_get_sessions_count_for_cache_entry(const DownloadedCacheEntry entry) -{ - if (!entry) - return 0; - return gf_list_count(entry->sessions); -} - - -s32 gf_cache_add_session_to_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess) { - u32 i; - s32 count; - if (!entry || !sess || !entry->sessions) - return -1; - count = gf_list_count(entry->sessions); - for (i = 0 ; i < (u32)count; i++) { - GF_DownloadSession *s = (GF_DownloadSession*)gf_list_get(entry->sessions, i); - if (s == sess) { - return count; - } - } - gf_list_add(entry->sessions, sess); - return count + 1; -} - -void gf_cache_set_end_range(DownloadedCacheEntry entry, u64 range_end) -{ - if (entry) { - entry->previousRangeContentLength = entry->contentLength; - entry->range_end = range_end; - entry->continue_file = GF_TRUE; - } -} - -Bool gf_cache_is_in_progress(const DownloadedCacheEntry entry) -{ - if (!entry) return GF_FALSE; - if (entry->writeFilePtr) return GF_TRUE; - if (entry->mem_storage && entry->written_in_cache && entry->contentLength && (entry->written_in_cache<entry->contentLength)) - return GF_TRUE; - return GF_FALSE; -} - -Bool gf_cache_set_mime(const DownloadedCacheEntry entry, const char *mime) -{ - if (!entry || !entry->memory_stored) return GF_FALSE; - if (entry->mimeType) gf_free(entry->mimeType); - entry->mimeType = gf_strdup(mime); - return GF_TRUE; -} - -Bool gf_cache_set_range(const DownloadedCacheEntry entry, u64 size, u64 start_range, u64 end_range) -{ - if (!entry || !entry->memory_stored) return GF_FALSE; - entry->range_start = start_range; - entry->range_end = end_range; - entry->contentLength = (u32) size; - entry->continue_file = GF_FALSE; - return GF_TRUE; -} - -Bool gf_cache_set_headers(const DownloadedCacheEntry entry, const char *headers) -{ - if (!entry || !entry->memory_stored) return GF_FALSE; - if (entry->forced_headers) gf_free(entry->forced_headers); - entry->forced_headers = headers ? gf_strdup(headers) : NULL; - return GF_TRUE; -} - -char *gf_cache_get_forced_headers(const DownloadedCacheEntry entry) -{ - if (!entry) return NULL; - return entry->forced_headers; -} -void gf_cache_set_downtime(const DownloadedCacheEntry entry, u32 download_time_ms) -{ - if (entry) entry->downtime = download_time_ms; -} -u32 gf_cache_get_downtime(const DownloadedCacheEntry entry) -{ - if (!entry) return 0; - return entry->downtime; -} -Bool gf_cache_is_done(const DownloadedCacheEntry entry) -{ - Bool res = GF_TRUE; - if (entry && entry->external_blob) { - gf_mx_p(entry->external_blob->mx); - res = (entry->external_blob->flags & GF_BLOB_IN_TRANSFER) ? GF_FALSE : GF_TRUE; - gf_mx_v(entry->external_blob->mx); - } else if (entry) { - res = (entry->cache_blob.flags & GF_BLOB_IN_TRANSFER) ? GF_FALSE : GF_TRUE; - } - return res; -} -const u8 *gf_cache_get_content(const DownloadedCacheEntry entry, u32 *size) -{ - if (!entry) return NULL; - if (entry->external_blob) { - u8 *data; - GF_Err e = gf_blob_get(entry->cache_filename, &data, size, NULL); - if (e) return NULL; - return data; - } - *size = entry->cache_blob.size; - return entry->cache_blob.data; -} -void gf_cache_release_content(const DownloadedCacheEntry entry) -{ - if (!entry) return; - if (!entry->external_blob) return; - gf_blob_release(entry->cache_filename); -} -Bool gf_cache_is_deleted(const DownloadedCacheEntry entry) -{ - if (!entry) return GF_TRUE; - if (entry->flags == DELETED) return GF_TRUE; - return GF_FALSE; -} - -Bool gf_cache_set_content(const DownloadedCacheEntry entry, GF_Blob *blob, Bool copy, GF_Mutex *mx) -{ - if (!entry || !entry->memory_stored) return GF_FALSE; - - if (!blob) { - entry->flags = DELETED; - if (entry->external_blob) { - gf_blob_unregister(entry->external_blob); - entry->external_blob = NULL; - } - return GF_TRUE; - } - if (blob->mx) - gf_mx_p(blob->mx); - - if (!copy) { - if (entry->mem_allocated) gf_free(entry->mem_storage); - entry->mem_storage = (u8 *) blob->data; - if (!entry->written_in_cache) { - char *burl = gf_blob_register(blob); - if (burl) { - strcpy(entry->cache_filename, burl); - gf_free(burl); - } - } - - entry->written_in_cache = blob->size; - entry->mem_allocated = 0; - entry->cache_blob.data = NULL; - entry->cache_blob.size = 0; - entry->external_blob = blob; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Storing %d bytes to memory from external module\n", blob->size)); - } else { - if (!entry->cache_blob.mx) - entry->cache_blob.mx = mx; - gf_mx_p(entry->cache_blob.mx); - - if (blob->size >= entry->mem_allocated) { - u32 new_size; - new_size = MAX(entry->mem_allocated*2, blob->size+1); - entry->mem_storage = (u8*)gf_realloc(entry->mem_allocated ? entry->mem_storage : NULL, (new_size+2)); - entry->mem_allocated = new_size; - entry->cache_blob.data = entry->mem_storage; - entry->cache_blob.size = entry->contentLength; - if (!entry->written_in_cache) { - char *burl = gf_blob_register(&entry->cache_blob); - if (burl) { - strcpy(entry->cache_filename, burl); - gf_free(burl); - } - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Reallocating memory cache to %d bytes\n", new_size)); - } - memcpy(entry->mem_storage, blob->data, blob->size); - entry->mem_storageblob->size = 0; - entry->cache_blob.size = entry->written_in_cache = blob->size; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Storing %d bytes to cache memory\n", blob->size)); - - gf_mx_v(entry->cache_blob.mx); - - entry->cache_blob.flags = blob->flags; - } - if (blob->flags & GF_BLOB_IN_TRANSFER) - entry->contentLength = 0; - else - entry->contentLength = blob->size; - - if (blob->mx) - gf_mx_v(blob->mx); - - return GF_TRUE; -} - -#endif //GPAC_DISABLE_NETWORK
View file
gpac-2.4.0.tar.gz/.gitattributes -> gpac-26.02.0.tar.gz/.gitattributes
Changed
@@ -18,8 +18,5 @@ *.dsp text eol=crlf *.dsw text eol=crlf -#tests -tests/media/xmlin4/input.txt text eol=crlf -tests/media/xmlin4/input.xml text eol=lf -tests/media/laser/*.xml text eol=lf -tests/media/ttml/ebu-ttd_sample.ttml text eol=lf +#gui +share/gui/gui.bt text eol=lf
View file
gpac-26.02.0.tar.gz/.github/ISSUE_TEMPLATE
Added
+(directory)
View file
gpac-26.02.0.tar.gz/.github/ISSUE_TEMPLATE/bug_report.md
Added
@@ -0,0 +1,11 @@ +--- +name: Bug +about: Report a bug +--- +Thanks for reporting your issue. Please make sure these boxes are checked before submitting your issue - thank you! + +- I looked for a similar issue and couldn't find any. +- I tried with the latest version of GPAC. Installers available at https://gpac.io/downloads/gpac-nightly-builds/ +- I give enough information for contributors to reproduce my issue (meaningful title, github labels, platform and compiler, command-line ...). I can share files anonymously with this dropbox: https://www.mediafire.com/filedrop/filedrop_hosted.php?drop=eec9e058a9486fe4e99c33021481d9e1826ca9dbc242a6cfaab0fe95da5e5d95 + +Detailed guidelines: https://gpac.io/bug-reporting/
View file
gpac-26.02.0.tar.gz/.github/ISSUE_TEMPLATE/config.yml
Added
@@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security + url: mailto:security@gpac.io + about: Report a security issue
View file
gpac-26.02.0.tar.gz/.github/scripts
Added
+(directory)
View file
gpac-26.02.0.tar.gz/.github/scripts/gitcopy.sh
Added
@@ -0,0 +1,64 @@ +#!/bin/bash + +# built from https://stackoverflow.com/a/76815733 + +if -z "$2" +then + echo "Usage: $0 <original_file> <duplicated_file>" + exit 0 +fi + +if ! -f "$1" +then + echo "$1 does not exist or is not a file" + exit 1 +fi + +if -n "$(git status --porcelain --untracked-files=no)" +then + echo "You have uncommitted changes. Pleae only run this script on a clean local repo." + exit 1 +fi + +originalFileLoc=$1 +newFileLoc=$2 + +branchName=chore/temp/duplicate-file-history-by-script +currentBranchName="$(git branch --show-current)" + +function copy_git_history() { + targetToCopy=$1 + newDestination=$2 + + echo + echo "-- copying $targetToCopy to $newDestination and keeping its history --" + echo + + git mv "$targetToCopy" "$newDestination" + git commit -m "duplicating $targetToCopy to $newDestination" + + git checkout HEAD~ "$targetToCopy" + git commit -m "restoring moved file $targetToCopy to its original location" +} + +echo +echo "---- git copying $originalFileLoc to $newFileLoc ----" +echo + +# create the new branch to store the changes +git checkout -b $branchName + +# create the duplicate file(s) +copy_git_history "$originalFileLoc" "$newFileLoc" + +# switch back to source branch +git checkout - +# merge the history back into the source branch to retain both copies +git merge --no-ff $branchName -m "Merging file history for copying $originalFileLoc to $newFileLoc" + +# delete the branch we created for history tracking purposes +git branch -D $branchName + +echo +echo "---- all done ----" +echo
View file
gpac-26.02.0.tar.gz/.github/workflows/abi-version.yml
Added
@@ -0,0 +1,255 @@ +name: abi version updater +run-name: abi version updater +on: + push: + branches: + - master + +concurrency: + group: update-abi + cancel-in-progress: false + +jobs: + prepare-matrix: + runs-on: ubuntu-latest + outputs: + latest_abi: ${{ steps.get-tags.outputs.latest_abi }} + skip_build: ${{ steps.check-abi-ahead.outputs.skip_build }} + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + sparse-checkout: "include/gpac/version.h" + + - name: Get latest `abi-*` tags + id: get-tags + run: | + git fetch --tags + latest_tag=$(git tag -l 'abi-*' --sort=-creatordate | head -n 1) + echo "latest_abi=$(git rev-parse $latest_tag)" >> $GITHUB_OUTPUT + + - name: Check if ABI on code is ahead of latest tag + id: check-abi-ahead + run: | + current_major=$(grep "define GPAC_VERSION_MAJOR" include/gpac/version.h | awk '{print $3}') + current_minor=$(grep "define GPAC_VERSION_MINOR" include/gpac/version.h | awk '{print $3}') + + latest_tag=$(git tag -l 'abi-*.*' --sort=-creatordate | head -n 1) + latest_tag=${latest_tag:4} + tag_major=$(echo $latest_tag | cut -d. -f1) + tag_minor=$(echo $latest_tag | cut -d. -f2) + + if "$current_major" -gt "$tag_major" || ( "$current_major" -eq "$tag_major" && "$current_minor" -gt "$tag_minor" ); then + echo "ABI version in code ($current_major.$current_minor) is ahead of latest tag ($tag_major.$tag_minor)" + + git config --global user.name "github-actionsbot" + git config --global user.email "41898282+github-actionsbot@users.noreply.github.com" + + new_minor=abi-$current_major.$current_minor + new_major=abi-$current_major + + echo "Tagging current commit with $new_minor and $new_major" + + if ! git rev-parse -q --verify "refs/tags/$new_minor" >/dev/null; then + git tag -a $new_minor -m "ABI version $current_major.$current_minor" ${{ github.sha }} + git push origin $new_minor + fi + + if ! git rev-parse -q --verify "refs/tags/$new_major" >/dev/null; then + git tag -a $new_major -m "ABI version $current_major" ${{ github.sha }} + git push origin $new_major + fi + + echo "skip_build=true" >> $GITHUB_OUTPUT + fi + + build-commits: + if: ${{ needs.prepare-matrix.outputs.latest_abi != github.sha && needs.prepare-matrix.outputs.skip_build != 'true' }} + runs-on: ubuntu-latest + container: gpac/ubuntu-deps:latest + needs: prepare-matrix + strategy: + matrix: + ref: + - ${{ needs.prepare-matrix.outputs.latest_abi }} + - ${{ github.sha }} + steps: + - name: Setup Node + shell: bash + run: | + apt-get update + apt-get install -y nodejs + + - name: Check out code + uses: actions/checkout@v4 + with: + ref: ${{ matrix.ref }} + path: gpac_public + + - name: Retrieve dependencies + shell: bash + run: cp -av /gpac-deps/gpac_public/extra_lib/* gpac_public/extra_lib/ + + - name: Build GPAC + working-directory: gpac_public + shell: bash + run: | + make distclean && ./configure --enable-debug && make -j$(nproc) + mkdir -p /binaries + cp -vf bin/gcc/* /binaries/ || true + + - name: Make debian package + working-directory: gpac_public + shell: bash + run: | + make distclean && ./configure --enable-debug + echo "debian/tmp/usr/include" >> debian/gpac.install + make deb + mv -vf gpac*.deb /binaries/ + + - name: Upload binaries + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.ref }}-binaries + path: /binaries/ + retention-days: 1 + + compare: + runs-on: ubuntu-latest + needs: prepare-matrix, build-commits + outputs: + changed: ${{ steps.compare-abi.outputs.changed }} + incompatible: ${{ steps.compare-abi.outputs.incompatible }} + steps: + - name: Download latest ABI binaries + uses: actions/download-artifact@v4 + with: + name: ${{ needs.prepare-matrix.outputs.latest_abi }}-binaries + path: ${{ runner.temp }}/latest-abi + + - name: Download current commit binaries + uses: actions/download-artifact@v4 + with: + name: ${{ github.sha }}-binaries + path: ${{ runner.temp }}/current-commit + + - name: Install abigail-tools + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y abigail-tools + + - name: Compare ABI + id: compare-abi + working-directory: ${{ runner.temp }} + shell: bash + run: | + set +e + abidiff --ignore-soname --no-unreferenced-symbols latest-abi/libgpac.so current-commit/libgpac.so > abi-diff.txt + ret=$? + set -e + + error=$(( (ret >> 0) & 1 )) + changed=$(( (ret >> 2) & 1 )) + incompatible=$(( (ret >> 3) & 1 )) + + if $error -ne 0 ; then + echo "Error running abipkgdiff" + exit 1 + fi + + echo "changed=$changed" >> $GITHUB_OUTPUT + echo "incompatible=$incompatible" >> $GITHUB_OUTPUT + + - name: Upload ABI diff + uses: actions/upload-artifact@v4 + with: + name: abi-diff + path: ${{ runner.temp }}/abi-diff.txt + retention-days: 1 + + update-repo: + runs-on: ubuntu-latest + needs: compare + if: ${{ needs.compare.outputs.changed == '1' || needs.compare.outputs.incompatible == '1' }} + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Retrieve the ABI diff + uses: actions/download-artifact@v4 + with: + name: abi-diff + path: ${{ runner.temp }} + + - name: Read the ABI diff + id: read-abi-diff + run: | + echo 'body<<EOF' >> $GITHUB_OUTPUT + cat ${{ runner.temp }}/abi-diff.txt >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + - name: Decide on new ABI version + id: decide-abi + shell: bash + run: | + latest_abi=$(git tag -l 'abi-*.*' --sort=-creatordate | head -n 1) + latest_abi=${latest_abi:4} + major=$(echo $latest_abi | cut -d. -f1) + minor=$(echo $latest_abi | cut -d. -f2) + + if ${{ needs.compare.outputs.incompatible }} -eq 1 ; then + major=$((major + 1)) + minor=0 + elif ${{ needs.compare.outputs.changed }} -eq 1 ; then + minor=$((minor + 1)) + fi + + echo "minor=$minor" >> $GITHUB_OUTPUT + echo "major=$major" >> $GITHUB_OUTPUT + + - name: Update the version + shell: bash + working-directory: include/gpac + run: | + sed -i "s/#define GPAC_VERSION_MAJOR 0-9*/#define GPAC_VERSION_MAJOR ${{ steps.decide-abi.outputs.major }}/g" version.h + sed -i "s/#define GPAC_VERSION_MINOR 0-9*/#define GPAC_VERSION_MINOR ${{ steps.decide-abi.outputs.minor }}/g" version.h + + - name: Set up Git for commit + run: | + git config --global user.name "github-actionsbot" + git config --global user.email "41898282+github-actionsbot@users.noreply.github.com" + + - name: Create a version change commit + id: commit-version + shell: bash + run: | + git commit -a \ + -m "Update ABI version to ${{ steps.decide-abi.outputs.major }}.${{ steps.decide-abi.outputs.minor }}" \ + -m "${{ steps.read-abi-diff.outputs.body }}" + git push origin master + echo "commit_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Update ABI tags + shell: bash + run: | + new_minor=abi-${{ steps.decide-abi.outputs.major }}.${{ steps.decide-abi.outputs.minor }} + new_major=abi-${{ steps.decide-abi.outputs.major }} + + if ! git rev-parse -q --verify "refs/tags/$new_minor" >/dev/null; then + git tag -a $new_minor \ + -m "ABI version ${{ steps.decide-abi.outputs.major }}.${{ steps.decide-abi.outputs.minor }}" \ + ${{ steps.commit-version.outputs.commit_sha }} + git push origin $new_minor + fi + + if ! git rev-parse -q --verify "refs/tags/$new_major" >/dev/null; then + git tag -a $new_major \ + -m "ABI version ${{ steps.decide-abi.outputs.major }}" \ + ${{ steps.commit-version.outputs.commit_sha }} + git push origin $new_major + fi
View file
gpac-26.02.0.tar.gz/.github/workflows/branches.yml
Added
@@ -0,0 +1,24 @@ +name: branches actions +run-name: build with docker +on: + push: + branches-ignore: + - master + +jobs: + + linux-image-build: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: build image + run: docker build -t gpac-ubuntu -f build/docker/ubuntu.Dockerfile . + + wasm-image-build: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: build image + run: docker build -t gpac-wasm -f build/docker/wasm.Dockerfile .
View file
gpac-26.02.0.tar.gz/.github/workflows/dist-linux.yml
Added
@@ -0,0 +1,197 @@ +name: Build & Distribute (Linux) + +on: + workflow_call: + inputs: + tag: + description: "Type a release tag or 'nightly' for a nightly build" + required: true + type: string + +jobs: + build: + strategy: + matrix: + architecture: amd64, arm64 + target: + - distro: ubuntu + codename: jammy # EOL: Apr 2027 + - distro: ubuntu + codename: noble # EOL: Apr 2029 + - distro: debian + codename: bullseye # EOL: Aug 2026 + - distro: debian + codename: bookworm # EOL: Jun 2028 + + name: Build for ${{ matrix.target.distro }} ${{ matrix.target.codename }} on ${{ matrix.architecture }} + + runs-on: ${{ matrix.architecture == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }} + container: + image: ${{ format('{0}:{1}', matrix.target.distro, matrix.target.codename) }} + options: --user root --workdir /workspace + + env: + DEBIAN_FRONTEND: noninteractive + CCACHE_DIR: ${{ github.workspace }}/ccache + + steps: + - name: Print environment + run: | + echo "Building ${{ inputs.tag }}" + echo "Running on ${{ matrix.target.distro }} ${{ matrix.target.codename }} for ${{ matrix.architecture }}" + echo "CCACHE_DIR: $CCACHE_DIR" + + - name: Update package list + run: apt update + + - name: Install build tools + run: | + apt install -y --no-install-recommends \ + build-essential pkg-config g++ git cmake yasm fakeroot dpkg-dev devscripts debhelper ccache + + - name: Install GPAC build dependencies + run: | + apt install -y --no-install-recommends \ + zlib1g-dev libfreetype6-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev \ + liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev \ + libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev \ + libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils \ + libcurl4-openssl-dev + + if "${{ matrix.target.distro }}" = "ubuntu" ; then + apt install -y --no-install-recommends libjpeg62-dev + else + apt install -y --no-install-recommends libjpeg62-turbo-dev + fi + + - name: Get GPAC source + uses: actions/checkout@v4 + with: + repository: gpac/gpac + fetch-depth: 0 + path: gpac_public + ref: ${{ inputs.tag == 'nightly' && 'master' || inputs.tag }} + + - name: Get manual dependencies + uses: actions/checkout@v4 + with: + repository: gpac/deps_unix + fetch-depth: 1 + path: deps_unix + submodules: true + + - name: Set up ccache + uses: actions/cache@v4 + with: + path: ${{ env.CCACHE_DIR }} + key: ccache-${{ matrix.target.distro }}-${{ matrix.target.codename }}-${{ matrix.architecture }}-${{ inputs.tag }} + + - name: Build manual dependencies + working-directory: deps_unix + run: ./build_all.sh linux + + - name: Build GPAC + working-directory: gpac_public + run: make deb -j$(nproc) + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + env: + DIST_TYPE: ${{ inputs.tag == 'nightly' && 'nightly-latest' || format('main-{0}', inputs.tag) }} + with: + name: linux-${{ env.DIST_TYPE }}-${{ matrix.target.distro }}-${{ matrix.target.codename }}@${{ matrix.architecture }} + path: gpac_public/*gpac*.deb + retention-days: 1 + + publish: + needs: build + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DIST_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DIST_SECRET_ACCESS_KEY }} + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: linux-${{ inputs.tag == 'nightly' && 'nightly-latest' || format('main-{0}', inputs.tag) }}-*-*@* + path: artifacts + + - name: Import GPG key + id: import-gpg + uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + trust_level: 5 # ultimate + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' + bundler-cache: true + + - name: Install deb-s3 + run: gem install deb-s3 + + - name: Publish the artifacts + shell: bash + run: | + set -euxo pipefail + shopt -s nullglob + + for folder in artifacts/*; do + echo "Processing folder $folder" + + for artifact in "$folder"/*; do + echo "Adding artifact $artifact to ${{ secrets.DIST_S3_BUCKET }} repository." + + # Rename the artifact so that there is no conflict + if $artifact == *.deb ; then + folder_name=$(basename "$(dirname "$artifact")") + + # Extract the required parts from the folder name + if $folder_name =~ linux-(^-+)-^-+-(^-+)-(^-+)@(^-+) ; then + component="${BASH_REMATCH1}" + distro="${BASH_REMATCH2}" + codename="${BASH_REMATCH3}" + arch="${BASH_REMATCH4}" + date=$(date +"%s%3N") + + # Get the version from the artifact name + file_name=$(basename "$artifact") + identifier=$(echo "$file_name" | cut -d'-' -f1) # gpac or libgpac + + # Create a new name for the artifact + new_name="${identifier}_${component}_${distro}_${codename}_${arch}_${date}.deb" + + # Copy the artifact to a new file with the new name + cp "$artifact" "${{ runner.temp }}/$new_name" + + # Upload the artifact with the new name + deb-s3 upload \ + ${{ runner.temp }}/$new_name \ + --bucket ${{ secrets.DIST_S3_BUCKET }} \ + --endpoint ${{ secrets.DIST_S3_ENDPOINT }} \ + --sign "${{ steps.import-gpg.outputs.keyid }}" \ + --prefix linux/$distro/ \ + --component $component \ + --codename $codename \ + --arch $arch \ + --origin https://dist.gpac.io \ + --lock \ + --preserve-versions + echo "Uploaded $new_name to ${{ secrets.DIST_S3_BUCKET }}/linux/$distro/" + else + echo "Invalid folder name format: $folder_name" + exit 1 + fi + else + echo "Artifact must be a .deb file: $artifact" + exit 1 + fi + done + done + + - name: Upload the public key + run: | + gpg --armor --export "${{ steps.import-gpg.outputs.fingerprint }}" > gpg.asc + aws s3 cp gpg.asc s3://${{ secrets.DIST_S3_BUCKET }}/linux/gpg.asc --endpoint-url ${{ secrets.DIST_S3_ENDPOINT }}
View file
gpac-26.02.0.tar.gz/.github/workflows/dist-wasm.yml
Added
@@ -0,0 +1,234 @@ +name: Build & Distribute (WebAssembly) + +on: + workflow_call: + inputs: + tag: + description: "Type a release tag or 'nightly' for a nightly build" + required: true + type: string + +jobs: + build: + strategy: + matrix: + variant: + - config: "full" + threaded: true + - config: "full" + threaded: false + - config: "lite" + threaded: false + + name: Build for WebAssembly (${{ matrix.variant.config }}, ${{ matrix.variant.threaded && 'threaded' || 'single-threaded' }}) + runs-on: "ubuntu-latest" + + env: + DEBIAN_FRONTEND: noninteractive + EMSDK_VERSION: 4.0.12 + + steps: + - name: Print environment + run: | + echo "Building ${{ inputs.tag }}" + echo "Running for WebAssembly with variant: ${{ matrix.variant.config }}, threaded: ${{ matrix.variant.threaded }}" + echo "CCACHE_DIR: $CCACHE_DIR" + + - name: Update package list + run: sudo apt update + + - name: Install build tools + run: | + sudo apt install -y --no-install-recommends \ + build-essential pkg-config g++ git cmake yasm python3 autotools-dev automake autoconf libtool + + - name: Install GPAC build dependencies + run: | + sudo apt install -y --no-install-recommends \ + zlib1g-dev libfreetype6-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev \ + liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev \ + libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev \ + libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils \ + libcurl4-openssl-dev libjpeg62-dev + + - name: Get Emscripten source + uses: actions/checkout@v4 + with: + repository: emscripten-core/emsdk + fetch-depth: 1 + path: emsdk + + - name: Set up Emscripten + working-directory: emsdk + run: | + ./emsdk install ${{ env.EMSDK_VERSION }} + ./emsdk activate ${{ env.EMSDK_VERSION }} + + # Update environment variables + echo "PATH=$PWD:$PWD/upstream/emscripten:$PATH" >> $GITHUB_ENV + echo "EMSDK=$PWD" >> $GITHUB_ENV + + - name: Get GPAC source + uses: actions/checkout@v4 + with: + repository: gpac/gpac + fetch-depth: 0 + path: gpac_public + ref: ${{ inputs.tag == 'nightly' && 'master' || inputs.tag }} + + - name: Get manual dependencies + uses: actions/checkout@v4 + if: ${{ matrix.variant.config != 'lite' }} + with: + repository: gpac/deps_wasm + fetch-depth: 1 + path: deps_wasm + submodules: true + + - name: Get latest commit hash for manual dependencies + id: get-commit + if: ${{ matrix.variant.config != 'lite' }} + working-directory: deps_wasm + run: echo "hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + id: cache-deps + if: ${{ matrix.variant.config != 'lite' }} + uses: actions/cache@v4 + with: + path: deps_wasm/wasm${{ matrix.variant.threaded && '_thread' || '' }} + key: wasm-deps-${{ matrix.variant.threaded && 'threaded' || 'single' }}-${{ steps.get-commit.outputs.hash }} + + - name: Cache Emscripten Build + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/emsdk/upstream/emscripten/cache + key: emscripten-${{ env.EMSDK_VERSION }}-${{ matrix.variant.config }}-${{ matrix.variant.threaded && 'threaded' || 'single' }}-${{ steps.get-commit.outputs.hash }} + + - name: Build manual dependencies + if: ${{ matrix.variant.config != 'lite' && !steps.cache-deps.outputs.cache-hit }} + working-directory: deps_wasm + run: ./wasm_extra_libs.sh ${{ matrix.variant.threaded && '--enable-threading' || '' }} + + - name: Set PKG_CONFIG_PATH + if: ${{ matrix.variant.config != 'lite' }} + working-directory: deps_wasm + run: | + # Update PKG_CONFIG_PATH + if "${{ matrix.variant.threaded }}" = "true" ; then + echo "PKG_CONFIG_PATH=$PWD/wasm_thread/lib/pkgconfig" >> $GITHUB_ENV + else + echo "PKG_CONFIG_PATH=$PWD/wasm/lib/pkgconfig" >> $GITHUB_ENV + fi + + - name: Build GPAC + working-directory: gpac_public + run: | + if "${{ matrix.variant.config }}" = "lite" ; then + ./configure --emscripten --extra-cflags="-Wno-pointer-sign -Wno-implicit-const-int-float-conversion" --em-opt=z \ + --isomedia-only --disable-threads --disable-isom-dump \ + --enable-reframer --enable-mp4mx --enable-mp4dmx --enable-fout --enable-webcodec + else + if "${{ matrix.variant.threaded }}" = "true" ; then + ./configure --emscripten --extra-cflags="-Wno-pointer-sign -Wno-implicit-const-int-float-conversion" + else + ./configure --emscripten --disable-threads --extra-cflags="-Wno-pointer-sign -Wno-implicit-const-int-float-conversion" + fi + fi + make -j$(nproc) + + - name: Prepare output directory + working-directory: gpac_public/bin/gcc + run: | + out="${{ github.workspace }}/output" + mkdir -p $out + if "${{ matrix.variant.config }}" = "full" ; then + if "${{ matrix.variant.threaded }}" = "true" ; then + cp gpac.js gpac.wasm $out + else + mv gpac.js gpac.st.js + mv gpac.wasm gpac.st.wasm + sed -i 's/gpac\.wasm/gpac.st.wasm/g' gpac.st.js + cp gpac.st.js gpac.st.wasm $out + fi + elif "${{ matrix.variant.config }}" = "lite" ; then + mv gpac.js gpac.lite.js + mv gpac.wasm gpac.lite.wasm + sed -i 's/gpac\.wasm/gpac.lite.wasm/g' gpac.lite.js + cp gpac.lite.js gpac.lite.wasm $out + fi + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + env: + DIST_TYPE: ${{ inputs.tag == 'nightly' && 'nightly-latest' || format('main-{0}', inputs.tag) }} + with: + name: wasm-${{ env.DIST_TYPE }}@${{ matrix.variant.config }}-${{ matrix.variant.threaded && 'threaded' || 'single' }} + path: output/* + retention-days: 1 + + publish: + needs: build + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DIST_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DIST_SECRET_ACCESS_KEY }} + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: wasm-${{ inputs.tag == 'nightly' && 'nightly-latest' || format('main-{0}', inputs.tag) }}@*-* + path: artifacts + + - name: Publish to S3 (bash) + shell: bash + run: | + set -euxo pipefail + shopt -s nullglob + + root="artifacts" + bucket="${{ secrets.DIST_S3_BUCKET }}" + extras="--endpoint-url ${{ secrets.DIST_S3_ENDPOINT }}" + + echo "Uploading artifacts from $root to bucket $bucket" + for dir in "$root"/wasm-*; do + -d "$dir" || continue + + slug="$(basename "$dir")" + component_part="${slug#wasm-}" + component_part="${component_part%%@*}" + + if "$component_part" = "nightly-latest" ; then + tag="$(date -u +%Y%m%d)" + prefix="wasm/nightly/$tag" + elif "$component_part" =~ ^main-(.+)$ ; then + tag="${BASH_REMATCH1}" + prefix="wasm/main/$tag" + else + echo "Skipping unrecognized component part: $component_part" >&2 + continue + fi + + echo "Processing $slug -> prefix $prefix" + for f in "$dir"/*; do + -f "$f" || continue + dest="s3://$bucket/$prefix/$(basename "$f")" + echo "Uploading $f -> $dest" + aws s3 cp "$f" "$dest" $extras + done + done + + if "${{ inputs.tag }}" = "nightly" ; then + echo "Updating 'latest' pointers" + for dir in "$root"/wasm-nightly-latest@*-*; do + -d "$dir" || continue + prefix="wasm" + slug="$(basename "$dir")" + for f in "$dir"/*; do + -f "$f" || continue + dest="s3://$bucket/$prefix/$(basename "$f")" + echo "Uploading $f -> $dest" + aws s3 cp "$f" "$dest" $extras + done + done + fi
View file
gpac-26.02.0.tar.gz/.github/workflows/distribution.yml
Added
@@ -0,0 +1,172 @@ +name: Distribution + +on: + # Used when nigthly builds needs to be refreshed or + # a tag should be added to main components + workflow_dispatch: + inputs: + refresh: + description: "Trigger a rebuild of today's nightly builds" + required: false + type: boolean + default: false + tag: + description: "Tag to build and add to the main components" + required: false + type: string + force: + description: "Build both main and nightly components, ignoring commit checks" + required: false + type: boolean + default: false + + # Used when publishing the release as main components + release: + types: published + + # Used to build nightly releases + schedule: + - cron: "0 0 * * *" # Every day at midnight + +# Only one distribution workflow can run at a time +concurrency: + group: "distribution" + cancel-in-progress: false + +env: + DEBIAN_FRONTEND: noninteractive + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + types: ${{ steps.set-types.outputs.types }} + main-tag: ${{ steps.decide-main-tag.outputs.tag }} + steps: + - name: Check workflow inputs + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + if "${{ github.event.inputs.force }}" = "false" && -z "${{ github.event.inputs.tag }}" && "${{ github.event.inputs.refresh }}" = "false" ; then + echo "Error: 'tag' input must be provided when 'force' and 'refresh' are both false." + exit 1 + fi + + - name: Get cached commit + id: get-latest-commit + uses: actions/cache@v4 + with: + path: ${{ runner.temp }}/gpac-latest-commit.txt + key: gpac-latest-commit-${{ github.sha }} + restore-keys: | + gpac-latest-commit- + + - name: Get latest gpac commit + id: git-status + run: | + LATEST_COMMIT=$(git ls-remote --refs https://github.com/gpac/gpac.git refs/heads/master | awk '{print $1}') + echo $LATEST_COMMIT > ${{ runner.temp }}/gpac-latest-commit.txt + echo "has_new_commits=$(if "${{ steps.get-latest-commit.outputs.cache-hit }}" = "false" ; then echo "true"; else echo "false"; fi)" >> $GITHUB_OUTPUT + + - name: Set types + id: set-types + run: | + types=() + # Add "main" type if: + # - release event + # - manual tag input + # - force input is true + if { + "${{ github.event_name }}" == "release" || + -n "${{ github.event.inputs.tag }}" || + "${{ github.event.inputs.force }}" == "true" ; + }; then + types+=("\"main\"") + fi + + # Add "nightly" type if: + # - scheduled run and had new commits since last run + # - manual refresh and had new commits since last run + # - force input is true + has_new_commits="${{ steps.git-status.outputs.has_new_commits }}" + if { + "${{ github.event_name }}" == "schedule" && "$has_new_commits" == "true" || + "${{ github.event.inputs.refresh }}" == "true" && "$has_new_commits" == "true" || + "${{ github.event.inputs.force }}" == "true" ; + }; then + types+=("\"nightly\"") + fi + echo "types=$(IFS=,; echo "${types*}")" >> $GITHUB_OUTPUT + + - name: Decide main tag + id: decide-main-tag + run: | + if "${{ github.event_name }}" == "release" ; then + echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + elif -n "${{ github.event.inputs.tag }}" ; then + echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT + else + # Get the latest v* tag from gpac/gpac repo + LATEST_TAG=$(git ls-remote --tags https://github.com/gpac/gpac.git | awk -F/ '/refs\/tags\/v0-9/{print $3}' | sort -V | tail -n1) + if -z "$LATEST_TAG" ; then + echo "Error: No v* tag found in gpac/gpac repository." + exit 1 + else + echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT + fi + fi + + - name: Workflow summary + run: | + echo "## Distribution Workflow Summary" >> $GITHUB_STEP_SUMMARY + echo "### Latest GPAC Commit" >> $GITHUB_STEP_SUMMARY + echo "$(cat ${{ runner.temp }}/gpac-latest-commit.txt)" >> $GITHUB_STEP_SUMMARY + echo "### New Commits Detected" >> $GITHUB_STEP_SUMMARY + if "${{ steps.git-status.outputs.has_new_commits }}" = "true" ; then + echo "Yes" >> $GITHUB_STEP_SUMMARY + else + echo "No" >> $GITHUB_STEP_SUMMARY + fi + echo "### Inputs" >> $GITHUB_STEP_SUMMARY + echo "- Tag: ${{ github.event.inputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "- Force: ${{ github.event.inputs.force }}" >> $GITHUB_STEP_SUMMARY + echo "- Refresh Nightly: ${{ github.event.inputs.refresh }}" >> $GITHUB_STEP_SUMMARY + echo "### Types to Build" >> $GITHUB_STEP_SUMMARY + if "${{ steps.set-types.outputs.types }}" = "" ; then + echo "No types to build." >> $GITHUB_STEP_SUMMARY + else + echo '${{ steps.set-types.outputs.types }}' | jq -r '.' | while read -r type; do + echo "- $type" >> $GITHUB_STEP_SUMMARY + done + fi + echo "### Main Tag" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.decide-main-tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + + dist-linux: + strategy: + matrix: + type: ${{ fromJson(needs.prepare.outputs.types) }} + + name: Build Linux (${{ matrix.type || 'unknown' }}) + needs: prepare + if: ${{ needs.prepare.outputs.types != '' }} + + uses: ./.github/workflows/dist-linux.yml + with: + tag: ${{ matrix.type == 'main' && needs.prepare.outputs.main-tag || 'nightly' }} + secrets: inherit + + dist-wasm: + strategy: + matrix: + type: ${{ fromJson(needs.prepare.outputs.types) }} + exclude: + - type: main # We'll build main on v2.5+ + + name: Build WASM (${{ matrix.type || 'unknown' }}) + needs: prepare + if: ${{ needs.prepare.outputs.types != '' }} + + uses: ./.github/workflows/dist-wasm.yml + with: + tag: ${{ matrix.type == 'main' && needs.prepare.outputs.main-tag || 'nightly' }} + secrets: inherit
View file
gpac-26.02.0.tar.gz/.github/workflows/master.yml
Added
@@ -0,0 +1,71 @@ +name: master actions +run-name: build with docker +on: + push: + branches: + - master + tags: + - v* + +jobs: + + gettag: + runs-on: ubuntu-latest + outputs: + version_tag: ${{ steps.set_version.outputs.VERSION }} + steps: + - name: 'Set Version Tag' + id: set_version + run: | + if "${{ github.ref_type }}" == "tag" ; then + VERSION=${{ github.ref_name }} + else + VERSION="latest" + fi + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + - name: 'Use Version Tag' + run: | + echo "The determined version tag is: ${{ steps.set_version.outputs.VERSION }}" + + linux-image-deploy: + runs-on: ubuntu-latest + needs: gettag + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: build image + run: docker build -t gpac-ubuntu -f build/docker/ubuntu.Dockerfile . + - name: check docker images + run: docker image list + - name: check docker run + run: docker run gpac-ubuntu || true + - name: login docker hub + run: docker login --username gpac --password ${{secrets.DOCKER_HUB_TOKEN}} + - name: tag docker image + run: docker tag gpac-ubuntu gpac/ubuntu:${{ needs.gettag.outputs.version_tag }} + - name: push docker image + run: docker push gpac/ubuntu:${{ needs.gettag.outputs.version_tag }} + + + wasm-image-deploy: + runs-on: ubuntu-latest + needs: gettag + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: build image + run: docker build -t gpac-wasm -f build/docker/wasm.Dockerfile . + - name: check docker images + run: docker image list + - name: check docker run + run: docker run gpac-wasm || true + - name: login docker hub + run: docker login --username gpac --password ${{secrets.DOCKER_HUB_TOKEN}} + - name: tag docker image + run: docker tag gpac-wasm gpac/wasm:${{ needs.gettag.outputs.version_tag }} + - name: push docker image + run: docker push gpac/wasm:${{ needs.gettag.outputs.version_tag }}
View file
gpac-26.02.0.tar.gz/.github/workflows/publish-release.yml
Added
@@ -0,0 +1,35 @@ +name: Create Github Release +run-name: Create Github Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + steps: + + - name: Generate Release Notes + run: | + echo "We are happy to announce the release of GPAC ${{ github.ref_name }}". >> release_body.md + echo "" >> release_body.md + echo "_Detailed info coming soon!_" >> release_body.md + echo "" >> release_body.md + echo "Installers are available at gpac.io(https://gpac.io/downloads/gpac-nightly-builds/)" >> release_body.md + echo "" >> release_body.md + echo "Enjoy, give us feedback and spread the news!" >> release_body.md + echo "" >> release_body.md + + - name: Create GitHub Release + uses: actions/create-release@v1 + with: + tag_name: ${{ github.ref }} + release_name: GPAC ${{ github.ref_name }} + body_path: release_body.md + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
View file
gpac-26.02.0.tar.gz/.github/workflows/testbuilds.yml
Added
@@ -0,0 +1,39 @@ +name: test builds +run-name: test builds + +on: + push: + pull_request: + # We include 'synchronize' so that if you push new code + # to a labeled PR, the tests run again automatically. + types: labeled, synchronize + +jobs: + freebsd-build: + if: | + github.event_name == 'push' || + contains(github.event.pull_request.labels.*.name, 'test with buildbot') + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build on FreeBSD + uses: vmactions/freebsd-vm@v1 + with: + copyback: false + prepare: | + pkg install -y \ + gcc gmake pkgconf git \ + freetype2 jpeg-turbo png libmad faad2 libogg libvorbis libtheora liba52 ffmpeg \ + libnghttp2 openjpeg xvid openssl sdl2 curl + run: | + set -e + gmake distclean + ./configure + gmake + gmake install + uname -a + which gpac + gpac -h + gpac -hx filters
View file
gpac-26.02.0.tar.gz/.github/workflows/ubuntu-deps-on-demand.yml
Added
@@ -0,0 +1,25 @@ +name: ubuntu-deps +run-name: build ubuntu-deps image +on: workflow_dispatch + +jobs: + linux-image-deploy: + runs-on: ubuntu-latest + #if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }} + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: build image + run: docker build -t gpac-ubuntu-deps -f build/docker/ubuntu-deps.Dockerfile . + - name: check docker images + run: docker image list + - name: check docker run + run: docker run gpac-ubuntu-deps || true + - name: login docker hub + run: docker login --username gpac --password ${{secrets.DOCKER_HUB_TOKEN}} + - name: tag docker image + run: docker tag gpac-ubuntu-deps gpac/ubuntu-deps:latest + - name: push docker image + run: docker push gpac/ubuntu-deps:latest
View file
gpac-26.02.0.tar.gz/.github/workflows/updatewiki.yml
Added
@@ -0,0 +1,32 @@ +name: update wiki +run-name: update wiki +on: + workflow_run: + workflows: master actions + types: + - completed + + +jobs: + update-wiki: + runs-on: ubuntu-latest + container: gpac/ubuntu:latest + steps: + - name: Check out wiki + run: + git clone https://ga:${{ secrets.GPAC_WIKI_TOKEN }}@github.com/$GITHUB_REPOSITORY_OWNER/wiki.git wiki + - name: Run genmd + working-directory: wiki/scripts + run: sh -x ./genmd.sh + - name: Commit if changes + working-directory: wiki + run: | + env + git config --unset-all http.https://github.com/.extraheader || true + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git status + git commit -m "update from $GITHUB_REPOSITORY@$GITHUB_SHA" || true + - name: Push wiki + working-directory: wiki + run: git push
View file
gpac-2.4.0.tar.gz/.gitignore -> gpac-26.02.0.tar.gz/.gitignore
Changed
@@ -4,6 +4,7 @@ *.user *.userosscache *.sln.docstates +.vscode *.userprefs *_i.c *_p.c @@ -48,11 +49,14 @@ Rreleases/ x64/ x86/ -build/ bld/ Bbin/ Oobj/ - +__pycache__/ +*.pycod +share/python/build/ +*egg-info + .DS_Store .AppleDouble .LSOverride @@ -89,5 +93,12 @@ tests/results/ tests/hash_refs/ +src/modules/**/*.dep +*doxygen_warnings.txt +*/nodejs/node_modules/ +*/nodejs/package-lock.json +applications/deprecated/ + + # nix output result
View file
gpac-2.4.0.tar.gz/Changelog -> gpac-26.02.0.tar.gz/Changelog
Changed
@@ -1,4 +1,72 @@ -#04/2024: GPAC 2.4 +# 05/02/2026 - GPAC 26.02 + +GPAC release naming is now changed to year.month scheme. ABI version of this release is 16.5. + +## gpac +- Added mode for testing defered graph linking apps +- `main()` can return filter session error code using -rv + +## MP4Box +- Better support for HEIF/AVIF import +- new option 'times' to rewrite timestamps + +## Core tools +- Network Capture and Replay using pcap or pcapng, including looping, loss and reordering simulation +- HTTP/3 support for client and server +- Added libcurl as backend for GPAC downloader +- QuickJS 2025, FFMPEG 8 +- Improbed GFIO (including file deletion) + +## Media Formats +- ISOBMFF external tracks support +- Event Message Tracks support +- Improved support SCTE-35, id3, CC, timecodes and other markers +- Improved HDR signaling support +- IAMF support +- Motion JPEG2000 +- AC-4 support +- AVS3 support + +## MPEG-DASH & HLS +- SSR support for L3D low latency, base64 encoding of init segments +- HLS groups, IV injection +- DASH/HLS: new `segcts` option to derive startNumber from first packet cts +- mux time prft injection + +## Remote monitoring +- A new WebSocket based remote monitoring UI is available for GPAC +- WebSocket server for JS scripts + +## Filters +- DVB-I MABR FLUTE mux and demux +- MABR (ROUTE/FLUTE) HTTP repair support with full or partial modes +- MABR (ROUTE/FLUTE) on unicast +- mediaserver.js HTTP gateway filter supporting MABR sources +- avmix playlists now accept ipid:// urls to locate input pids, allowing to specify playlists independently from source URLs +- ClosedCaptions encoder +- TTML merger +- flist: Playlist piping, DASH perdiod auto-switch signaling +- pin: flush signaling upon broken pipe +- M2TS: USAC support, non real-time NTP injection for TEMI, real-time regulation option for tssplit and tsgendts +- dvb4linux is back (Terrestrial and Satelite including dibseqc) +- reframer: time-aligned mode, time discontinuities handling +- nhml: ability to process fragmented streams +- bsrw: timecodes injection and rewriting +- seiloader filter for SEI and AV1 OBUs +- FFMPEG raw protocol support (use gpac for demux and mux) + +## Emscripten +- Improved WASM support +- Fixes in UI + +## Other +- Improved Wiki: glossary, developer section, ... +- Integration with GStreamer (gpac-gst-plugin(https://github.com/gpac/gst-gpac-plugin)) +- Introduce unit tests in complement to the testsuite and various buildbot continuous checks +- Many bug fixes and security patches + + +#17/04/2024: GPAC 2.4 ## Emscripten|WebAssembly(WASM) support - Session can run in worker or in main browser loop @@ -49,7 +117,7 @@ ## Misc - Migrated doc from github's wiki to wiki.gpac.io -- FFMPEG 7 support +- FFmpeg 7 support - Added features for configure (vout, aout, fonts, doc, evg) - Allow specifying network interface by name or IP (instead of IP only) - UDP/TCP filtering and recording to / playback from pcap, pcapng and GPAC gpc files @@ -64,7 +132,7 @@ - Conversion filters for VTT, TXG3 and TTML - SubstationAlpha subtitle import (basic) - DVB subpictures in M2TS mux and demux -- FFMPEG (mostly for Matroska) subtitle import and export (SRT/SSA to TX3G, TX3G to SRT, WebVTT and DVB subpictures) +- FFmpeg (mostly for Matroska) subtitle import and export (SRT/SSA to TX3G, TX3G to SRT, WebVTT and DVB subpictures) - EC3+Atmos signaling support - ALAC support - Improved DolbyVision muxing @@ -81,7 +149,7 @@ - UTC-based range extraction in reframer - Thumbnail generator filter - Added unframer filter -- FFMPEG bitstream filters support +- FFmpeg bitstream filters support - Initseg support in mp4dmx filter - Access to GPU textures of decoders in Python and JSF bindings (glpush filter for tests) - Chapters editing and original timestamp dispatch in reformer range extraction @@ -241,11 +309,11 @@ - HEVC tile splitting and merging filters - Compositor is a standalone filter (SVG/BIFS/VRML graphics in a filter chain) - Image encoding support through libjpg and libpng -- Full FFMPEG support: - * Encoding/decoding through FFMPEG libavcodec - * Multiplexing/demultiplexing through FFMPEG libavformat - * Device grabbers through FFMPEG libavdevice - * Raw audio and video filters through FFMPEG libavfilter +- Full FFmpeg support: + * Encoding/decoding through FFmpeg libavcodec + * Multiplexing/demultiplexing through FFmpeg libavformat + * Device grabbers through FFmpeg libavdevice + * Raw audio and video filters through FFmpeg libavfilter - Support for QuickJS (ES2002) and bindings for: * Complete filter API * GPAC software rasterizer (EVG) @@ -724,7 +792,7 @@ - fixed 3GPP text display bug when stopping and changing an animationStream using a 3GPP text object. - fixed AVC/H264 HP parsing - added BIFS track visual size info at BT/XMT encoding stage - - MOVED FFMPEG INCLUDE FILES TO LATEST CVS VERSION to support AVC/H264 HP decoding + - MOVED FFmpeg INCLUDE FILES TO LATEST CVS VERSION to support AVC/H264 HP decoding - added meta self reference item for dual-headed files - commit MPEG-4 LATM Audio hinting patch and added rtp aggregation. RTP reassembler NOT UPDATED YET - fixed bugs in TeXML parser @@ -870,7 +938,7 @@ - Xiph OGG demuxer: supports file, http download (not tested) and icecast servers. - Xiph Vorbis decoder - Xiph Theora support (should work with fluendo but I can't get any data from the server...) - - Better FFMPEG support (moved to latest ffmpeg cvs tarball). + - Better FFmpeg support (moved to latest ffmpeg cvs tarball). - all file associations in client are made through mime types, and changed handling of service (mime type query if possible before loading plugin) - network stats & changed all UIs for that. - decoder stats & changed all UIs for that. @@ -1074,16 +1142,16 @@ - PocketPC installer - OpenDivx plugin - clean-up extra lib package, install notes and added missing libs - - FFMPEG demuxer done (synchro not good and seeking pbs). GPAC now supports all FFMPEG-supported formats. + - FFmpeg demuxer done (synchro not good and seeking pbs). GPAC now supports all FFmpeg-supported formats. - new NSIS script for complete install - fixed hang on PocketPC WavOut and misc cleanups for PocketPC compilation - - H263 in 3GP files (use FFMPEG dec) + - H263 in 3GP files (use FFmpeg dec) - H263 streaming (RFC 2429) - B-frame parsing and CTS reconstruction in AVI importer (needs debug). - muxInfo.duration parameter is now in milliseconds (was in seconds) - '-node' options to MP4Box to get a given node syntax (fields default value and QP info) - relative time stamps in BT ('AT D1000 ' means 'at last AU TS + 1000') - - FFMPEG decoder plugin now working with latest CVS snapshot + - FFmpeg decoder plugin now working with latest CVS snapshot - MediaControl switching (several MC on single object) - separate file downloader API and streaming client API / cleanup of plugins and ESM module - base support for non-MPEG4 URLs in the scene (eg, url "http://whatever/resource")
View file
gpac-2.4.0.tar.gz/Makefile -> gpac-26.02.0.tar.gz/Makefile
Changed
@@ -9,7 +9,7 @@ vpath %.c $(SRC_PATH) -all: version +all: version unit_tests $(MAKE) -C src all $(MAKE) -C applications all ifneq ($(STATIC_BINARY),yes) @@ -23,13 +23,16 @@ GITREV_PATH:=$(SRC_PATH)/include/gpac/revision.h -TAG:=$(shell git --git-dir=$(SRC_PATH)/.git describe --tags --abbrev=0 2> /dev/null) -VERSION:=$(shell echo `git --git-dir=$(SRC_PATH)/.git describe --tags --long || echo "UNKNOWN"` | sed "s/^$(TAG)-//") +TAG:=$(shell git --git-dir=$(SRC_PATH)/.git describe --tags --abbrev=0 --match "v*" 2> /dev/null) +VERSION:=$(shell echo `git --git-dir=$(SRC_PATH)/.git describe --tags --long --match "v*" || echo "UNKNOWN"` | sed "s/^$(TAG)-//") BRANCH:=$(shell git --git-dir=$(SRC_PATH)/.git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "UNKNOWN") +# strip illegal debian version string characters + illegal filename charachers +DHBRANCH:=$(shell echo "$(BRANCH)" | sed 's/^-+.0-9a-zA-Z~/-/g' ) + version: @if -d $(SRC_PATH)/".git" ; then \ - echo "#define GPAC_GIT_REVISION \"$(VERSION)-$(BRANCH)\"" > $(GITREV_PATH).new; \ + echo "#define GPAC_GIT_REVISION \"$(VERSION)-$(DHBRANCH)\"" > $(GITREV_PATH).new; \ if ! diff -q $(GITREV_PATH) $(GITREV_PATH).new >/dev/null ; then \ mv $(GITREV_PATH).new $(GITREV_PATH); \ fi; \ @@ -54,7 +57,7 @@ $(MAKE) -C applications dep $(MAKE) -C modules dep -clean: +clean: unit_tests_clean $(MAKE) -C src clean $(MAKE) -C applications clean $(MAKE) -C modules clean @@ -72,11 +75,62 @@ @rm -f bin/gcc/gf_*$(DYN_LIB_SUFFIX) 2> /dev/null doc: - @cd $(SRC_PATH)/share/doc && doxygen + @cd $(SRC_PATH)/share/doc && doxygen && cp versions.html html-libgpac/ man: @cd $(SRC_PATH)/share/doc/man && MP4Box -genman && gpac -genman + +UT_CFG_PATH:=unittests/build/config + +unit_tests: +ifeq ($(UNIT_TESTS),yes) + @echo "Unit Tests:" + @echo "- configuring" + @mkdir -p unittests/build/bin/gcc + + @cp config.mak unittests/build/ + @sed 's|BUILD_PATH=$(BUILD_PATH)|BUILD_PATH=$(BUILD_PATH)/unittests/build|g' config.mak | \ + sed 's|-I"$(BUILD_PATH)"|-I"$(BUILD_PATH)/unittests/build"|g' > $(UT_CFG_PATH).mak.new + @if -e $(UT_CFG_PATH).mak ; then \ + if ! diff -q $(UT_CFG_PATH).mak $(UT_CFG_PATH).mak.new >/dev/null ; then \ + mv $(UT_CFG_PATH).mak.new $(UT_CFG_PATH).mak; \ + fi; \ + else \ + mv $(UT_CFG_PATH).mak.new $(UT_CFG_PATH).mak; \ + fi + + @sed 's/GF_STATIC static/GF_STATIC GF_EXPORT/' config.h > $(UT_CFG_PATH).h.new.tmp + @sed 's/GF_NOT_EXPORTED/GF_NOT_EXPORTED GF_EXPORT/' $(UT_CFG_PATH).h.new.tmp > $(UT_CFG_PATH).h.new + @rm $(UT_CFG_PATH).h.new.tmp + @if -e $(UT_CFG_PATH).h ; then \ + if ! diff -q $(UT_CFG_PATH).h $(UT_CFG_PATH).h.new >/dev/null ; then \ + mv $(UT_CFG_PATH).h.new $(UT_CFG_PATH).h; \ + else \ + rm $(UT_CFG_PATH).h.new; \ + fi; \ + else \ + mv $(UT_CFG_PATH).h.new $(UT_CFG_PATH).h; \ + fi + + @$(SRC_PATH)/unittests/build.sh > unittests/build/bin/gcc/unittests.c + + @echo "- building" + @cd unittests/build && $(MAKE) -C src && $(MAKE) -C src unit_tests + + @echo "- executing" + $(SRC_PATH)/unittests/launch.sh + + @echo "- done" +endif + +unit_tests_clean: +ifeq ($(UNIT_TESTS),yes) + @echo "Cleaning unit tests artifacts" + @rm -rf unittests/build/bin + @cd unittests/build && $(MAKE) -C src clean +endif + test_suite: @cd $(SRC_PATH)/testsuite && ./make_tests.sh -precommit -p=0 @@ -87,7 +141,7 @@ @echo "Generating lcov info in coverage.info" @rm -f ./gpac-conf-* > /dev/null @lcov -q -capture --directory . --output-file all.info - @lcov --remove all.info '*/usr/*' '*/opt/*' '*/include/*' '*/validator/*' '*/quickjs/*' '*/jsmods/WebGLRenderingContextBase*' '*/utils/Remotery*' '*/utils/gzio*' --output coverage.info + @lcov --remove all.info '*/usr/*' '*/opt/*' '*/include/*' '*/validator/*' '*/quickjs/*' '*/jsmods/WebGLRenderingContextBase*' '*/utils/gzio*' --output coverage.info @rm all.info @echo "Purging lcov info" @cd src ; for dir in * ; do cd .. ; sed -i -- "s/$$dir\/$$dir\//$$dir\//g" coverage.info; cd src; done ; cd .. @@ -145,7 +199,7 @@ $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/shaders" $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/scripts" $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/python" - $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/vis" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/rmtws" $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/default.cfg $(DESTDIR)$(prefix)/share/gpac/ ifneq ($(CONFIG_DARWIN),yes) @@ -161,14 +215,14 @@ $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gwlib.js "$(DESTDIR)$(prefix)/share/gpac/gui/" -ifeq ($(CONFIG_DARWIN),yes) +ifneq (,$(filter yes,$(CONFIG_DARWIN) $(CONFIG_FREEBSD))) cp $(SRC_PATH)/share/gui/icons/* "$(DESTDIR)$(prefix)/share/gpac/gui/icons/" cp -R $(SRC_PATH)/share/gui/extensions/* "$(DESTDIR)$(prefix)/share/gpac/gui/extensions/" cp $(SRC_PATH)/share/shaders/* "$(DESTDIR)$(prefix)/share/gpac/shaders/" cp -R $(SRC_PATH)/share/scripts/* "$(DESTDIR)$(prefix)/share/gpac/scripts/" cp -R $(SRC_PATH)/share/python/* "$(DESTDIR)$(prefix)/share/gpac/python/" cp $(SRC_PATH)/share/res/* "$(DESTDIR)$(prefix)/share/gpac/res/" - cp -R $(SRC_PATH)/share/vis/* "$(DESTDIR)$(prefix)/share/gpac/vis/" + cp -R $(SRC_PATH)/share/rmtws/* "$(DESTDIR)$(prefix)/share/gpac/rmtws/" else cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/gui/icons/* $(DESTDIR)$(prefix)/share/gpac/gui/icons/ cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/gui/extensions/* $(DESTDIR)$(prefix)/share/gpac/gui/extensions/ @@ -176,7 +230,7 @@ cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/scripts/* $(DESTDIR)$(prefix)/share/gpac/scripts/ cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/python/* $(DESTDIR)$(prefix)/share/gpac/python/ cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/res/* $(DESTDIR)$(prefix)/share/gpac/res/ - cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/vis/* $(DESTDIR)$(prefix)/share/gpac/vis/ + cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/rmtws/* $(DESTDIR)$(prefix)/share/gpac/rmtws/ endif lninstall: @@ -225,7 +279,7 @@ installdylib: -ifneq ($(STATIC_BINARY),yes) +ifneq ($(STATIC_BUILD),yes) $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" @@ -299,11 +353,12 @@ endif ifeq ($(CONFIG_LINUX),yes) + deb: git checkout -- debian/changelog fakeroot debian/rules clean # add version to changelog for final filename - sed -i -r "s/^(\w+) \((0-9\.+)(-A-Z+)?\)/\1 (\2\3-rev$(VERSION)-$(BRANCH))/" debian/changelog + sed -i -r "s/^(\w+) \((0-9\.+)(-A-Z+)?\)/\1 (\2\3-rev$(VERSION)-$(DHBRANCH))/" debian/changelog fakeroot debian/rules configure fakeroot debian/rules binary rm -rf debian/
View file
gpac-2.4.0.tar.gz/README.md -> gpac-26.02.0.tar.gz/README.md
Changed
@@ -1,7 +1,7 @@ !Build Status(https://tests.gpac.io/testres/badge/build/ubuntu64)(https://buildbot.gpac.io/#/grid?branch=master) !Tests(https://tests.gpac.io/testres/badge/tests/linux64)(https://tests.gpac.io/) -!Build Status(https://tests.gpac.io/testres/badge/build/ubuntu32)(https://buildbot.gpac.io/#/grid?branch=master) +!Build Status(https://tests.gpac.io/testres/badge/build/debian32)(https://buildbot.gpac.io/#/grid?branch=master) !Tests(https://tests.gpac.io/testres/badge/tests/linux32)(https://tests.gpac.io/) !Build Status(https://tests.gpac.io/testres/badge/build/windows64)(https://buildbot.gpac.io/#/grid?branch=master) @@ -25,9 +25,9 @@ # GPAC Introduction -Current version: 2.4 +Current version: 26.02 -Latest Release: 2.4 +Latest Release: 26.02 GPAC is an open-source multimedia framework focused on modularity and standards compliance. GPAC provides tools to process, inspect, package, stream, playback and interact with media content. Such content can be any combination of audio, video, subtitles, metadata, scalable graphics, encrypted media, 2D/3D graphics and ECMAScript. @@ -60,25 +60,25 @@ - Python and NodeJS bindings Features are encapsulated in processing modules called filters: -- to get the full list of available features, you can run the command line `gpac -h filters` or check filters' wiki(https://wiki.gpac.io/Filters/Filters/). -- to get the full list of playback features, check the dedicated wiki page(https://wiki.gpac.io/Player/Player/). +- to get the full list of available features, you can run the command line `gpac -h filters` or check filters' wiki(https://wiki.gpac.io/Filters/Filters). +- to get the full list of playback features, check the dedicated wiki page(https://wiki.gpac.io/Player/Player). # Tools ## MP4Box -MP4Box is a multi-purpose MP4 file manipulation for the prompt, featuring media importing and extracting, file inspection, DASH segmentation, RTP hinting, ... See `MP4Box -h`, `man MP4Box` or our wiki(https://wiki.gpac.io/MP4Box). +MP4Box is a multi-purpose MP4 file manipulation for the prompt, featuring media importing and extracting, file inspection, DASH segmentation, RTP hinting, ... See `MP4Box -h`, `man MP4Box` or our wiki(https://wiki.gpac.io/MP4Box/MP4Box). ## gpac -GPAC includes a filter engine in charge of stream management and used by most applications in GPAC - read this post(https://wiki.gpac.io/Rearchitecture) for more discussion on how this impacts MP4Box. -The gpac application is a direct interface to the filter engine of GPAC, allowing any combination of filters not enabled by other applications. See `gpac -h`, `man gpac`, `man gpac-filters` or our wiki(https://wiki.gpac.io/Filters) for more details. +GPAC includes a filter engine in charge of stream management and used by most applications in GPAC - read this post(https://wiki.gpac.io/Filters/Rearchitecture) for more discussion on how this impacts MP4Box. +The gpac application is a direct interface to the filter engine of GPAC, allowing any combination of filters not enabled by other applications. See `gpac -h`, `man gpac`, `man gpac-filters` or our wiki(https://wiki.gpac.io/Filters/Filters) for more details. # Getting started ## Download Stable and nightly builds installers for Windows, Linux, OSX, Android, iOS are available on gpac.io(https://gpac.io/downloads/). -If you want to compile GPAC yourself, please follow the instructions in the build section(https://wiki.gpac.io/Build-Introduction) of our wiki. +If you want to compile GPAC yourself, please follow the instructions in the build section(https://wiki.gpac.io/Build/Build-Introduction) of our wiki. ## Documentation The general GPAC framework documentation is available on wiki.gpac.io(https://wiki.gpac.io), including HowTos(https://wiki.gpac.io/Howtos/howtos/). @@ -120,5 +120,5 @@ - Web GUI - QUIC support - ROUTE file repair support -- FLUTE file repair support - +- FLUTE support +- Rust Bindings
View file
gpac-2.4.0.tar.gz/applications/generators/SVG/main.c -> gpac-26.02.0.tar.gz/applications/generators/SVG/main.c
Changed
@@ -523,7 +523,7 @@ strcpy(att->impl_type, "SVG_String"); fprintf(stdout, "Warning: using type SVG_String for attribute %s.\n", att->svg_name); } - } else { /* for some attributes, the type given in the RNG needs to be overriden */ + } else { /* for some attributes, the type given in the RNG needs to be overridden */ if (!strcmp(att->svg_name, "color")) { strcpy(att->impl_type, "SVG_Paint"); } else if (!strcmp(att->svg_name, "viewport-fill")) {
View file
gpac-2.4.0.tar.gz/applications/generators/WebGLGen/main.c -> gpac-26.02.0.tar.gz/applications/generators/WebGLGen/main.c
Changed
@@ -458,7 +458,7 @@ Bool is_array; const char *nat_type = get_arg_type(arg_type0, &is_array); fprintf(ifce_c, "\t{\n\tGF_WebGLObject *glo = JS_GetOpaque(argv0, %s_class_id);\n\tif (glo) {\n\tglo->gl_id=0;\n", nat_type); - fprintf(ifce_c, "\tJS_FreeValue(ctx, glo->obj);\n\tglo->obj = JS_UNDEFINED;\n\tgf_list_del_item(glo->par_ctx->all_objects, glo);\n"); + fprintf(ifce_c, "\tJS_FreeValue(ctx, glo->obj);\n\tglo->obj = JS_UNDEFINED;\n\tgf_list_del_item(glo->par_ctx->all_objects, glo);\n\tgf_free(glo);\n\tJS_SetOpaque(argv0, NULL);\n"); fprintf(ifce_c, "\t}\n\t}\n"); }
View file
gpac-2.4.0.tar.gz/applications/gpac/Makefile -> gpac-26.02.0.tar.gz/applications/gpac/Makefile
Changed
@@ -29,7 +29,7 @@ PROG="gpac"$(EXT) #export default symbols and modularize as libgpac -exports=-sMODULARIZE=1 -sEXPORT_NAME=libgpac -sEXPORTED_RUNTIME_METHODS=cwrap,stackSave,stackRestore,stackAlloc,addFunction,FS,IDBFS,allocateUTF8OnStack,getValue,setValue,UTF8ToString -sEXPORTED_FUNCTIONS=_malloc,_free,_main +exports=-O$(EM_OPT) -sMODULARIZE=1 -sEXPORT_ES6=1 -sEXPORT_NAME=libgpac -sEXPORTED_RUNTIME_METHODS=FS,IDBFS,stackSave,stackRestore,getValue,setValue,UTF8ToString,lengthBytesUTF8,stringToUTF8,cwrap,addFunction -sEXPORTED_FUNCTIONS=_malloc,_free,_main ifeq ($(DISABLE_THREADS),no) LINKFLAGS+=$(exports),PThread -sPTHREAD_POOL_SIZE=1 -sPTHREAD_POOL_SIZE_STRICT=0 else @@ -37,6 +37,7 @@ endif #default demo uses IDBFS LINKFLAGS+=-lidbfs.js +LINKFLAGS+=--pre-js $(SRC_PATH)/share/emscripten/gpac_pre.js else EXT= PROG=gpac @@ -58,7 +59,7 @@ else ifeq ($(CONFIG_DARWIN),yes) #LINKFLAGS+= -Wl,-rpath,'@loader_path' else -LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc endif endif
View file
gpac-2.4.0.tar.gz/applications/gpac/compositor_tools.c -> gpac-26.02.0.tar.gz/applications/gpac/compositor_tools.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022-2023 + * Copyright (c) Telecom ParisTech 2022-2024 * All rights reserved * * This file is part of GPAC / gpac application @@ -1035,10 +1035,8 @@ } #endif - if (gf_opts_get_bool("core", "proxy-on")) { - str = gf_opts_get_key("core", "proxy-name"); - if (str) fprintf(stderr, "HTTP Proxy %s enabled\n", str); - } + str = gf_opts_get_key("core", "proxy"); + if (str) fprintf(stderr, "HTTP Proxy %s enabled\n", str); if (rti_file) { update_rti("At GPAC load time\n"); @@ -1227,21 +1225,22 @@ return GF_FALSE; } +static char szBuf8192; + Bool mp4c_handle_prompt(u8 char_val) { MP4C_Command cmdtype = get_gui_cmd(char_val); switch (cmdtype) { case MP4C_OPEN: { - char szURL2000; gf_sc_disconnect(compositor); fprintf(stderr, "Enter the absolute URL\n"); - if (1 > scanf("%1999s", szURL)) { + if (1 > scanf("%8191s", szBuf)) { fprintf(stderr, "Cannot read absolute URL, aborting\n"); break; } if (rti_file) init_rti_logs(rti_file, use_rtix); - gf_sc_connect_from_time(compositor, szURL, 0, 0, 0, NULL); + gf_sc_connect_from_time(compositor, szBuf, 0, 0, 0, NULL); } break; case MP4C_RELOAD: @@ -1335,21 +1334,21 @@ case MP4C_DUMPSCENE: if (is_connected) { - char radnameGF_MAX_PATH, *sExt; + char *sExt; Bool xml_dump, std_out; - radname0 = 0; + szBuf0 = 0; do { fprintf(stderr, "Enter file radical name (+\'.x\' for XML dumping) - \"std\" for stderr: "); fflush(stderr); - } while( 1 > scanf("%1023s", radname)); - sExt = strrchr(radname, '.'); + } while( 1 > scanf("%8191s", szBuf)); + sExt = strrchr(szBuf, '.'); xml_dump = 0; if (sExt) { if (!stricmp(sExt, ".x")) xml_dump = 1; sExt0 = 0; } - std_out = strnicmp(radname, "std", 3) ? 0 : 1; - GF_Err e = gf_sc_dump_scene(compositor, std_out ? NULL : radname, NULL, xml_dump, 0); + std_out = strnicmp(szBuf, "std", 3) ? 0 : 1; + GF_Err e = gf_sc_dump_scene(compositor, std_out ? NULL : szBuf, NULL, xml_dump, 0); if (e<0) fprintf(stderr, "Dump failed: %s\n", gf_error_to_string(e)); else @@ -1397,42 +1396,41 @@ break; case MP4C_UPDATE: { - char szCom8192; fprintf(stderr, "Enter command to send:\n"); - szCom0 = 0; - if (1 > scanf("%8191^\t\n", szCom)) { + szBuf0 = 0; + if (1 > scanf("%8191^\t\n", szBuf)) { fprintf(stderr, "Cannot read command to send, aborting.\n"); break; } - GF_Err e = gf_sc_scene_update(compositor, NULL, szCom); + GF_Err e = gf_sc_scene_update(compositor, NULL, szBuf); if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); } break; case MP4C_EVALJS: { - char jsCode8192; fprintf(stderr, "Enter JavaScript code to evaluate:\n"); - jsCode0 = 0; - if (1 > scanf("%8191^\t\n", jsCode)) { + szBuf0 = 0; + if (1 > scanf("%8191^\t\n", szBuf)) { fprintf(stderr, "Cannot read code to evaluate, aborting.\n"); break; } - GF_Err e = gf_sc_scene_update(compositor, "application/ecmascript", jsCode); + GF_Err e = gf_sc_scene_update(compositor, "application/ecmascript", szBuf); if (e) fprintf(stderr, "Processing JS code failed: %s\n", gf_error_to_string(e)); } break; case MP4C_LOGS: { - char szLog1024, *cur_logs; + char *cur_logs; cur_logs = gf_log_get_tools_levels(); fprintf(stderr, "Enter new log level (current tools %s):\n", cur_logs); gf_free(cur_logs); - if (scanf("%1023s", szLog) < 1) { + szBuf0 = 0; + if (scanf("%8191s", szBuf) < 1) { fprintf(stderr, "Cannot read new log level, aborting.\n"); break; } - gf_log_modify_tools_levels(szLog); + gf_log_modify_tools_levels(szBuf); } break;
View file
gpac-2.4.0.tar.gz/applications/gpac/gpac.c -> gpac-26.02.0.tar.gz/applications/gpac/gpac.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2026 * All rights reserved * * This file is part of GPAC / gpac application @@ -41,7 +41,6 @@ GF_List *args_alloc = NULL; u32 gen_doc = 0; u32 help_flags = 0; -FILE *sidebar_md=NULL; FILE *helpout = NULL; const char *auto_gen_md_warning = "<!-- automatically generated - do not edit, patch gpac/applications/gpac/gpac.c -->\n"; @@ -68,6 +67,7 @@ static Bool print_meta_filters = GF_FALSE; static Bool load_test_filters = GF_FALSE; static s32 nb_loops = 0; +static Bool loop_if_error = GF_FALSE; static s32 runfor = 0; static Bool runfor_exit = GF_FALSE; static Bool runfor_fast = GF_FALSE; @@ -83,6 +83,7 @@ static Bool in_sig_handler = GF_FALSE; static Bool custom_event_proc=GF_FALSE; static u64 run_start_time = 0; +static Bool return_gferr = GF_FALSE; #ifdef GPAC_CONFIG_EMSCRIPTEN static Bool has_console; @@ -118,7 +119,21 @@ static void cleanup_file_io(void); static GF_Filter *load_custom_filter(GF_FilterSession *sess, char *opts, GF_Err *e); static u32 gpac_unit_tests(GF_MemTrackerType mem_track); -static Bool revert_cache_file(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info); +enum { + CACHE_OP_DELETE, + CACHE_OP_SHOW, + CACHE_OP_INFO, + CACHE_OP_UNFLATTEN, +}; + +static void do_cache_check(u32 op_type, char *argval); + +#ifdef GPAC_DEFER_MODE +static GF_Err print_pid_props(char *arg); +static GF_Err probe_pid_link(char *arg); +static GF_Err print_pid_dests(char *arg); +#endif + #ifdef WIN32 #include <windows.h> static BOOL WINAPI gpac_sig_handler(DWORD sig); @@ -204,10 +219,10 @@ static void reset_em_thread(); #endif -static int gpac_exit_fun(int code) +static int gpac_exit_fun(GF_Err code) { u32 i; - if (code>=0) { + if (code!=GF_BAD_PARAM) { for (i=1; i<gf_sys_get_argc(); i++) { if (!gf_sys_is_arg_used(i)) { GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Warning: argument %s set but not used\n", gf_sys_get_arg(i) )); @@ -232,7 +247,7 @@ } if ((helpout != stdout) && (helpout != stderr)) { if (gen_doc==2) { - fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/Filters\n"); + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/Filters/Filters\n"); fprintf(helpout, ".SH MORE\n.LP\nAuthors: GPAC developers, see git repo history (-log)\n" ".br\nFor bug reports, feature requests, more information and source code, visit https://github.com/gpac/gpac\n" ".br\nbuild: %s\n" @@ -243,20 +258,19 @@ gf_fclose(helpout); } - if (sidebar_md) { - gf_fclose(sidebar_md); - sidebar_md = NULL; - } - cleanup_logs(); gf_sys_close(); + if (code<0) { + if (return_gferr) code = -code; + else code = 1; + } #ifdef GPAC_MEMORY_TRACKING - if (!code && (gf_memory_size() || gf_file_handles_count() )) { + if (gf_memory_size() || gf_file_handles_count() ) { gf_log_set_tool_level(GF_LOG_MEMORY, GF_LOG_INFO); gf_memory_print(); - code = 2; + if (!code) code = 2; } #endif @@ -397,7 +411,7 @@ gf_fs_abort(session, GF_FS_FLUSH_FAST); break; case 3: - fprintf(stderr, "Aborting without flush %s...\n", nb_loops ? "and stoping loops" : ""); + fprintf(stderr, "Aborting without flush %s...\n", nb_loops ? "and stopping loops" : ""); gf_fs_abort(session, GF_FS_FLUSH_NONE); nb_loops=0; break; @@ -438,10 +452,12 @@ static Bool write_profile=GF_FALSE; static Bool write_core_opts=GF_FALSE; static Bool write_extensions=GF_FALSE; -static const char *session_js=NULL; +static GF_List *session_js=NULL; static Bool has_xopt = GF_FALSE; static Bool nothing_to_do = GF_TRUE; - +#ifdef GPAC_DEFER_MODE +static Bool defer_mode = GF_FALSE; +#endif #ifndef GPAC_DISABLE_NETWORK static Bool enum_net_ifces(void *cbk, const char *name, const char *IP, u32 flags) @@ -462,6 +478,23 @@ } #endif +#ifdef GPAC_DEFER_MODE +static void run_sess(void) +{ + if (use_step_mode) { + //max run (in case some filters are asking for RT reschedule + //this is only used for defer mdoe + u32 retry=10; + while (retry && !gf_fs_is_last_task(session)) { + gf_fs_run(session); + retry--; + } + } else { + gf_fs_run(session); + } +} +#endif + #ifndef GPAC_CONFIG_ANDROID static #endif @@ -484,6 +517,7 @@ //bools dump_stats = dump_graph = print_meta_filters = load_test_filters = GF_FALSE; runfor_exit = runfor_fast = enable_prompt = use_step_mode = in_sig_handler = custom_event_proc = GF_FALSE; + loop_if_error = GF_FALSE; //s32 nb_loops = runfor = 0; //u32 @@ -507,8 +541,9 @@ override_seps = view_filter_conn = dump_codecs = dump_formats = dump_proto_schemes = GF_FALSE; write_profile = write_core_opts = write_extensions = has_xopt = GF_FALSE; nothing_to_do = GF_TRUE; - - +#ifdef GPAC_DEFER_MODE + defer_mode = GF_FALSE; +#endif #ifdef GPAC_CONFIG_EMSCRIPTEN window_swap = GF_FALSE; @@ -550,7 +585,6 @@ gf_sys_init(mem_track, profile); - #ifdef GPAC_CONFIG_ANDROID //prevent destruction of JSRT until we unload the JNI gpac wrapper (see applications/gpac_android/src/main/jni/gpac_jni.cpp) gf_opts_set_key("temp", "static-jsrt", "true"); @@ -624,6 +658,14 @@ use_step_mode = GF_TRUE; #endif + if (gf_opts_get_bool("core", "rmt")) { + if (!session_js) session_js = gf_list_new(); + const char *rmt_path = gf_opts_get_key("core", "rmt-path"); + if (!rmt_path) + rmt_path = "$GSHARE/scripts/rmt/server.js"; + gf_list_insert(session_js, (char *)rmt_path, 0); + } + for (i=1; i<argc; i++) { char szArgName1024; char *arg = argvi; @@ -679,6 +721,9 @@ dump_all_props(NULL); } gpac_exit(0); + } else if (!strncmp(argvi+1, "props.", 6)) { + check_prop_def(argvi+1 + 6); + gpac_exit(0); } else if (!strcmp(argvi+1, "colors")) { dump_all_colors(); gpac_exit(0); @@ -691,6 +736,11 @@ } else if (!strcmp(argvi+1, "prompt")) { gpac_fsess_task_help(); gpac_exit(0); +#ifdef GPAC_DEFER_MODE + } else if (!strcmp(argvi+1, "defer")) { + gpac_defer_help(); + gpac_exit(0); +#endif } else if (!strcmp(argvi+1, "mp4c")) { #if defined(GPAC_CONFIG_ANDROID) || defined(GPAC_DISABLE_COMPOSITOR) gf_sys_format_help(helpout, help_flags, "-mp4c unavailable for android\n"); @@ -795,9 +845,11 @@ } gpac_alias_help(GF_ARGMODE_EXPERT); - gpac_credentials_help(GF_ARGMODE_EXPERT); +#ifdef GPAC_DEFER_MODE + gpac_defer_help(); +#endif if (gen_doc==1) { gf_fclose(helpout); helpout = gf_fopen("core_config.md", "w"); @@ -840,7 +892,7 @@ // dump_codecs = GF_TRUE; if (gen_doc==2) { - fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/Filters\n"); + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/Filters/Filters\n"); fprintf(helpout, ".SH MORE\n.LP\nAuthors: GPAC developers, see git repo history (-log)\n" ".br\nFor bug reports, feature requests, more information and source code, visit https://github.com/gpac/gpac\n" ".br\nbuild: %s\n" @@ -884,8 +936,9 @@ } else if (!strcmp(arg, "-wfx")) { write_profile = GF_TRUE; sflags |= GF_FS_FLAG_LOAD_META; - } else if (!strcmp(arg, "-sloop")) { + } else if (!strcmp(arg, "-sloop") || !strcmp(arg, "-eloop")) { nb_loops = -1; + if (!strcmp(arg, "-eloop")) loop_if_error = GF_TRUE; if (arg_val) nb_loops = get_s32(arg_val, "sloop"); } else if (!strcmp(arg, "-runfor")) { if (arg_val) runfor = 1000*get_u32(arg_val, "runfor"); @@ -901,13 +954,22 @@ } else if (!strcmp(arg, "-runforl")) { if (arg_val) runfor = 1000*get_u32(arg_val, "runforl"); exit_mode = 2; - } else if (!strcmp(arg, "-uncache")) { - const char *cache_dir = gf_opts_get_key("core", "cache"); - gf_enum_directory(cache_dir, GF_FALSE, revert_cache_file, NULL, ".txt"); - fprintf(stderr, "GPAC Cache dir %s flattened\n", cache_dir); + } else if (!strcmp(arg, "-cache-unflat")) { + do_cache_check(CACHE_OP_UNFLATTEN, arg_val); + gpac_exit(0); + } else if (!strcmp(arg, "-cache-list")) { + do_cache_check(CACHE_OP_SHOW, arg_val); + gpac_exit(0); + } else if (!strcmp(arg, "-cache-info")) { + do_cache_check(CACHE_OP_INFO, arg_val); + gpac_exit(0); + } else if (!strcmp(arg, "-cache-clean")) { + do_cache_check(CACHE_OP_DELETE, arg_val); gpac_exit(0); } else if (!strcmp(arg, "-cfg")) { nothing_to_do = GF_FALSE; + } else if (!strcmp(arg, "-rv")) { + return_gferr = GF_TRUE; } else if (!strcmp(arg, "-alias") || !strcmp(arg, "-aliasdoc")) { @@ -934,7 +996,7 @@ if (alias_val) alias_val0 = ' '; alias_set = GF_TRUE; } - else if (!strncmp(arg, "-seps", 5)) { + else if (!strncmp(arg, "-seps", 6)) { parse_sep_set(arg_val, &override_seps); } else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack")) { @@ -946,7 +1008,8 @@ } else if (!strcmp(arg, "-qe")) { exit_nocleanup = GF_TRUE; } else if (!strcmp(arg, "-js")) { - session_js = arg_val; + if (!session_js) session_js = gf_list_new(); + gf_list_add(session_js, arg_val); } else if (!strcmp(arg, "-r")) { enable_reports = 2; if (arg_val && !strlen(arg_val)) { @@ -958,6 +1021,26 @@ do_unit_tests = GF_TRUE; } else if (!strcmp(arg, "-cl")) { sflags |= GF_FS_FLAG_NO_IMPLICIT; + } else if (!strcmp(arg, "-sid")) { + sflags |= GF_FS_FLAG_REQUIRE_SOURCE_ID; +#ifdef GPAC_DEFER_MODE + } else if (!strcmp(arg, "-dl")) { + sflags |= GF_FS_FLAG_FORCE_DEFER_LINK; + defer_mode=GF_TRUE; + } else if (!strcmp(arg, "-np")) { + sflags |= GF_FS_FLAG_PREVENT_PLAY; + } else if (!strncmp(arg, "-rl", 3) + || !strncmp(arg, "-wl", 3) + || !strcmp(arg, "-f") + || !strcmp(arg, "-s") + || !strcmp(arg, "-g") + || !strcmp(arg, "-pi") + || !strcmp(arg, "-pl") + || !strcmp(arg, "-pd") + || !strcmp(arg, "-se") + || !strcmp(arg, "-m") + ) { +#endif } else if (!strcmp(arg, "-step")) { use_step_mode = GF_TRUE; #ifdef GPAC_CONFIG_EMSCRIPTEN @@ -978,6 +1061,7 @@ } } #endif + } else if (!strcmp(arg, "-xopt")) { has_xopt = GF_TRUE; #ifdef GPAC_CONFIG_IOS @@ -1006,7 +1090,7 @@ else { if (!has_xopt) { gpac_suggest_arg(arg); - gpac_exit(-1); + gpac_exit(GF_BAD_PARAM); } else { gf_sys_mark_arg_used(i, GF_FALSE); } @@ -1030,8 +1114,11 @@ } if ((list_filters>=2) || print_meta_filters || dump_codecs || dump_formats || print_filter_info) sflags |= GF_FS_FLAG_LOAD_META; - if (list_filters || print_filter_info) + if (list_filters || print_filter_info) { gf_opts_set_key("temp", "helponly", "yes"); + if (print_filter_info && (argmode>=GF_ARGMODE_EXPERT)) + gf_opts_set_key("temp", "helpexpert", "yes"); + } if (dump_proto_schemes || (gen_doc==1)) gf_opts_set_key("temp", "get_proto_schemes", "yes"); @@ -1080,7 +1167,6 @@ if (view_conn_for_filter && (argmode>=GF_ARGMODE_EXPERT)) sflags |= GF_FS_FLAG_PRINT_CONNECTIONS; - session = gf_fs_new_defaults(sflags); if (!session) { @@ -1132,10 +1218,18 @@ } if (session_js) { - e = gf_fs_load_script(session, session_js); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to load JS for session: %s\n", gf_error_to_string(e) )); - ERR_EXIT + u32 ijs, nb_js=gf_list_count(session_js); + for (ijs=0; ijs<nb_js; ijs++) { + const char *js_src = gf_list_get(session_js, ijs); + e = gf_fs_load_script(session, js_src); + if (e) { + if ((e==GF_URL_ERROR) && strstr(js_src, "/rmt/server.js")) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Monitoring script %s not found, check your installation\n Disabling remote monitoring\n", js_src)); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to load JS for session: %s\n", gf_error_to_string(e) )); + ERR_EXIT + } + } } } @@ -1149,6 +1243,96 @@ Bool f_loaded = GF_FALSE; char *arg = argvi; +#ifdef GPAC_DEFER_MODE + if (defer_mode) { + if (!strncmp(arg, "-rl", 3) || !strncmp(arg, "-wl", 3)) { + Bool do_run = GF_TRUE; + Bool use_all_filters = (arg4=='x') ? GF_TRUE : GF_FALSE; + char *sep = strchr(arg,'='); + s32 relink = sep ? atoi(sep+1) : 1; + if (!strncmp(arg, "-wl", 3)) do_run=GF_FALSE; + if (relink>=0) { + u32 retry=100; + GF_Filter *f = NULL; + if (use_all_filters) { + u32 count = gf_fs_get_filters_count(session); + f = gf_fs_get_filter(session, count-1-relink); + } else { + f = gf_list_get(loaded_filters, gf_list_count(loaded_filters)-1-relink); + } + if (!f) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Invalid filter index in %s\n", arg)); + e=GF_BAD_PARAM; + ERR_EXIT + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Relinking filter %s\n", gf_filter_get_name(f))); + while (retry && gf_fs_check_filter(session, f)) { + retry--; + e = gf_filter_reconnect_output(f, NULL); + //no output pids and no input, consider this is a source and retry + if ((e==GF_EOS) && !gf_filter_get_ipid_count(f) && do_run) { + run_sess(); +#ifndef GPAC_CONFIG_EMSCRIPTEN + gf_sleep(10); +#endif + continue; + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error relinking filter %s\n", gf_filter_get_name(f))); + ERR_EXIT + } + if (do_run) + run_sess(); + break; + } + if ((e==GF_EOS) && !retry && do_run) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No output pid after running session for %s - cannot flush session\n", gf_filter_get_name(f))); +#ifdef GPAC_CONFIG_EMSCRIPTEN + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("\tThis may require unwinding to main JS loop, not supported in defer mode (always blocking)\n", gf_filter_get_name(f))); +#endif + e = GF_NOT_SUPPORTED; + ERR_EXIT + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + continue; + } else if (!strcmp(arg, "-f")) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Running session\n")); + run_sess(); + continue; + } else if (!strcmp(arg, "-g")) { + gf_fs_print_connections(session); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + continue; + } else if (!strcmp(arg, "-s")) { + gf_fs_print_stats(session); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + continue; + } else if (!strncmp(arg, "-pi", 3)) { + e = print_pid_props(arg); + if (e) break; + continue; + } else if (!strncmp(arg, "-pl", 3)) { + e = probe_pid_link(arg); + if (e) { + ERR_EXIT + } + continue; + } else if (!strncmp(arg, "-pd", 3)) { + e = print_pid_dests(arg); + if (e) { + ERR_EXIT + } + continue; + } else if (!strncmp(arg, "-se", 3)) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Sending PLAY event\n")); + gf_fs_send_deferred_play(session); + } else if (!strncmp(arg, "-m=", 3)) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ( "%s\n", arg+3)); + } + } +#endif + if (!strcmp(arg, "-src") || !strcmp(arg, "-i") || !strcmp(arg, "-ib") || !strcmp(arg, "-ibx") ) { if (!strcmp(arg, "-ib") || !strcmp(arg, "-ibx")) { const char *fargs=NULL; @@ -1185,7 +1369,20 @@ continue; } if (!f_loaded && !has_xopt) { - if (arg0== separator_setSEP_LINK ) { + if (arg0 == separator_setSEP_LINK ) { + char *next_sep = NULL; + if (arg1==separator_setSEP_LINK) { + next_sep = strchr(arg+2, separator_setSEP_LINK); + } else { + next_sep = strchr(arg+1, separator_setSEP_LINK); + } + if (next_sep) { + e = gf_fs_process_link_directive(arg, NULL, loaded_filters, next_sep); + if (e) { + ERR_EXIT + } + continue; + } gf_list_add(links_directive, arg); continue; } @@ -1251,48 +1448,17 @@ gf_filter_tag_subsession(filter, current_subsession_id, current_source_id); while (gf_list_count(links_directive)) { - char *link_prev_filter_ext = NULL; - GF_Filter *link_from; - Bool reverse_order = GF_FALSE; - s32 link_filter_idx = -1; char *link = gf_list_pop_front(links_directive); - char *ext = strchr(link, separator_setSEP_FRAG); - if (ext) { - ext0 = 0; - link_prev_filter_ext = ext+1; - } - if (strlen(link)>1) { - if (link1 == separator_setSEP_LINK ) { - reverse_order = GF_TRUE; - link++; - } - link_filter_idx = 0; - if (strlen(link)>1) { - link_filter_idx = get_u32(link+1, "Link filter index"); - if (link_filter_idx < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Wrong filter index %d, must be positive\n", link_filter_idx)); - e = GF_BAD_PARAM; - ERR_EXIT - } - } - } else { - link_filter_idx = 0; - } - if (ext) ext0 = separator_setSEP_FRAG; - - if (reverse_order) - link_from = gf_list_get(loaded_filters, link_filter_idx); - else - link_from = gf_list_get(loaded_filters, gf_list_count(loaded_filters)-1-link_filter_idx); - - if (!link_from) { - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Wrong filter index @%d\n", link_filter_idx)); - e = GF_BAD_PARAM; + e = gf_fs_process_link_directive(link, filter, loaded_filters, NULL); + if (e) { ERR_EXIT } - gf_filter_set_source(filter, link_from, link_prev_filter_ext); } +#ifdef GPAC_DEFER_MODE + if (defer_mode) + fprintf(stdout, "Added filter %s for %s\n\n", gf_filter_get_name(filter), arg); +#endif gf_list_add(loaded_filters, filter); //implicit mode, check changes of source and sinks @@ -1493,11 +1659,15 @@ loaded_filters=NULL; cleanup_file_io(); + if (loop_if_error && nb_loops && e) + e = GF_OK; if (!e && nb_loops) { if (nb_loops>0) nb_loops--; loops_done++; fprintf(stderr, "session done, restarting (loop %d)\n", loops_done); + gf_net_reload_netcap(); + #ifndef GPAC_CONFIG_ANDROID fflush(stderr); @@ -1512,7 +1682,9 @@ #endif } - gpac_exit(e<0 ? 1 : 0); + if (session_js) gf_list_del(session_js); + + gpac_exit(e); } #if defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_CONFIG_IOS) @@ -1737,6 +1909,173 @@ } } +#ifdef GPAC_DEFER_MODE +static GF_Err extract_filter_and_pid(char *arg, GF_Filter **o_f, s32 *opid_idx, u8 *prefix_c) +{ + Bool use_all_filters = (arg3=='x') ? GF_TRUE : GF_FALSE; + if (prefix_c) *prefix_c = 0; + char *sep = strchr(arg,'='); + if (sep) { + switch (sep1) { + case '-': + case '+': + if (prefix_c) *prefix_c = sep1; + sep++; + break; + } + } + char *sep_pid = sep ? strchr(sep+1,':') : NULL; + if (sep_pid) sep_pid0=0; + char *sep_f = strchr(arg,'@'); + if (sep_f) sep_f0=0; + + u32 f_idx = sep ? atoi(sep+1) : 0; + *opid_idx=-1; + if (sep_pid) { + sep_pid0=':'; + *opid_idx = atoi(sep_pid+1); + } + if (sep_f) sep_f0='@'; + u32 count; + *o_f = NULL; + if (use_all_filters) { + count = gf_fs_get_filters_count(session); + *o_f = gf_fs_get_filter(session, count-1-f_idx); + } else { + count = gf_list_count(loaded_filters); + *o_f = gf_list_get(loaded_filters, count-1-f_idx); + } + if (!*o_f || !gf_fs_check_filter(session, *o_f)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No filter at index %d in arg %s\n", f_idx, arg)); + return GF_BAD_PARAM; + } + + if ((*opid_idx>=0) && (*opid_idx>=(s32)count)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No filter pid at index %d in filter %s in arg %s\n", *opid_idx, gf_filter_get_name(*o_f), arg )); + return GF_BAD_PARAM; + } + return GF_OK; +} + +static GF_Err print_pid_props(char *arg) +{ + GF_Filter *f; + s32 p_idx; + u8 prefix; + GF_Err e = extract_filter_and_pid(arg, &f, &p_idx, &prefix); + if (e) return e; + u32 j, count = gf_filter_get_opid_count(f); + if (!count) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filter %s has no output\n", gf_filter_get_name(f))); + return GF_OK; + } + for (j=0; j<count;j++) { + char szDumpGF_PROP_DUMP_ARG_SIZE; + u32 prop_idx=0; + GF_FilterPid *pid = gf_filter_get_opid(f, j); + if ((p_idx>=0) && (p_idx != j)) continue; + if (prefix=='-') { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filter %s PID #%d name: %s\n", gf_filter_get_name(f), j, gf_filter_pid_get_name(pid) )); + continue; + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filter %s PID %s properties:\n", gf_filter_get_name(f), gf_filter_pid_get_name(pid) )); + while (1) { + u32 p4cc=0; + const char *pname=NULL; + const GF_PropertyValue *p = gf_filter_pid_enum_properties(pid, &prop_idx, &p4cc, &pname); + if (!p) break; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Prop %s: %s\n", p4cc ? gf_props_4cc_get_name(p4cc) : pname, gf_props_dump(p4cc, p, szDump, GF_PROP_DUMP_DATA_NONE))); + } + prop_idx=0; + while (prefix=='+') { + u32 p4cc=0; + const char *pname=NULL; + const GF_PropertyValue *p = gf_filter_pid_enum_info(pid, &prop_idx, &p4cc, &pname); + if (!p) break; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Info %s: %s\n", p4cc ? gf_props_4cc_get_name(p4cc) : pname, gf_props_dump(p4cc, p, szDump, GF_PROP_DUMP_DATA_NONE))); + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + if (prefix=='-') { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + return GF_OK; +} +static GF_Err probe_pid_link(char *arg) +{ + GF_Filter *f; + s32 opid_idx; + u8 prefix; + char *fname = strchr(arg, '@'); + if (!fname) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Missing `@` directive in probe link\n")); + return GF_BAD_PARAM; + } + + GF_Err e = extract_filter_and_pid(arg, &f, &opid_idx, &prefix); + if (e) return e; + if (opid_idx<0) opid_idx=0; + char *res = NULL; + if (prefix=='+') + e = gf_filter_probe_links(f, opid_idx, fname+1, &res); + else + e = gf_filter_probe_link(f, opid_idx, fname+1, &res); + + if (res) { + if (!res0) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Probed chain from %s to %s: direct connection\n\n", gf_filter_get_name(f), fname+1)); + } else if (strchr(res, '|')) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Probed chains from %s to %s:\n", gf_filter_get_name(f), fname+1)); + char *cur=res; + while (1) { + u32 distance=0; + u32 priority=0; + char *sep = strchr(cur, '|'); + if (sep) sep0 = 0; + char *w_sep = strchr(cur, ','); + if (w_sep) { + w_sep0 = 0; + sscanf(cur, "%u;%u", &distance, &priority); + w_sep0 = ','; + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t- %s (priority %u distance %u)\n", w_sep ? w_sep+1 : cur, priority, distance)); + if (!sep) break; + sep0 = '|'; + cur = sep+1; + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Probed chain from %s to %s: %s\n", gf_filter_get_name(f), fname+1, res)); + } + gf_free(res); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("No filter chain from %s to %s: %s\n\n", gf_filter_get_name(f), fname+1, gf_error_to_string(e))); + } + return GF_OK; +} + +static GF_Err print_pid_dests(char *arg) +{ + GF_Filter *f; + s32 p_idx; + u8 prefix; + char *res; + GF_Err e = extract_filter_and_pid(arg, &f, &p_idx, &prefix); + if (e) return e; + + e = gf_filter_get_possible_destinations(f, p_idx, &res); + if (res) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Possible destinations for %s: %s\n", gf_filter_get_name(f), res)); + gf_free(res); + } else if (e==GF_FILTER_NOT_FOUND){ + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("No destinations for %s\n", gf_filter_get_name(f))); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Failed to probe possible destinations for %s: %s\n\n", gf_filter_get_name(f), gf_error_to_string(e))); + } + return GF_OK; +} +#endif + static char szFilter100; static char szCom2048; @@ -2068,8 +2407,10 @@ fprintf(stderr, "Active filters: %d\n", nb_active); if (static_logs) { - if (is_final && (!log_write || !static_logslog_write-1.szMsg)) + if (is_final && (!log_write || !static_logslog_write-1.szMsg)) { + gf_fs_lock_filters(fsess, GF_FALSE); return; + } fprintf(stderr, "\nLogs:\n"); for (i=0; i<log_write; i++) { @@ -2092,14 +2433,192 @@ fflush(stderr); } -static Bool revert_cache_file(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) + +typedef struct +{ + u32 op_type, nb_entries; + u32 total_size, min_size, max_size; + u64 min_created, max_created; + u64 min_expire, max_expire; + u64 min_hit, max_hit; + u32 min_nb_hit, max_nb_hit; + u64 date_min, date_max; +} CacheInfo; + + +#define TIMEFMT "%Y/%m/%dT%H:%M:%SZ" +static GFINLINE const char *format_date(u64 time, char *szDate) +{ + time_t date = time; + strftime(szDate, 99, TIMEFMT, gmtime(&date) ); + return szDate; +} + +static Bool cache_file_op(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) { #ifndef GPAC_DISABLE_NETWORK - const char *url; - GF_Config *cached; + const char *url, *opt; + CacheInfo *ci = (CacheInfo *)cbck; if (strncmp(item_name, "gpac_cache_", 11)) return GF_FALSE; - cached = gf_cfg_new(NULL, item_path); + GF_Config *cached = gf_cfg_new(NULL, item_path); + if (!cached) return GF_FALSE; url = gf_cfg_get_key(cached, "cache", "url"); + if (!url) { + gf_cfg_del(cached); + gf_file_delete(item_path); + char *sep = strstr(item_path, ".txt"); + if (sep) { + sep0 = 0; + gf_file_delete(item_path); + sep0 = '.'; + } + return GF_FALSE; + } + Bool in_range = GF_TRUE; + + if (ci->date_min || ci->date_max) { + opt = gf_cfg_get_key(cached, "cache", "Created"); + if (opt) { + u64 created; + sscanf(opt, LLU, &created); + //range + if (ci->date_max) { + if ((created >= ci->date_max) || (created <= ci->date_min)) in_range = GF_FALSE; + } + //exclude everything sooner than min time + else if (created <= ci->date_min) in_range = GF_FALSE; + } + } + + if (ci->op_type==CACHE_OP_DELETE) { + u32 it_size=0; + opt = gf_cfg_get_key(cached, "cache", "Content-Length"); + if (opt) it_size = atoi(opt); + + if (!in_range) { + ci->max_size += it_size; + ci->total_size ++; + gf_cfg_del(cached); + return GF_FALSE; + } + ci->min_size += it_size; + ci->nb_entries++; + gf_file_delete(item_path); + char *sep = strstr(item_path, ".txt"); + if (sep) { + sep0 = 0; + gf_file_delete(item_path); + sep0 = '.'; + } + gf_cfg_del(cached); + return GF_FALSE; + } + + if (ci->op_type==CACHE_OP_INFO) { + if (!in_range) { + gf_cfg_del(cached); + return GF_FALSE; + } + ci->nb_entries++; + u64 created=0, age; + opt = gf_cfg_get_key(cached, "cache", "Content-Length"); + if (opt) { + u32 size = atoi(opt); + ci->total_size += size; + if (!ci->min_size) ci->min_size = ci->max_size = size; + if (ci->min_size>size) ci->min_size = size; + if (ci->max_size<size) ci->max_size = size; + } + opt = gf_cfg_get_key(cached, "cache", "Created"); + if (opt) { + sscanf(opt, LLU, &created); + if (!ci->min_created) ci->min_created = ci->max_created = created; + if (ci->min_created>created) ci->min_created = created; + if (ci->max_created<created) ci->max_created = created; + } + opt = gf_cfg_get_key(cached, "cache", "MaxAge"); + if (opt) { + sscanf(opt, LLU, &age); + if (!ci->min_expire) ci->min_expire = ci->max_expire = age; + if (ci->min_expire>age) ci->min_expire = age; + if (ci->max_expire<age) ci->max_expire = age; + } + opt = gf_cfg_get_key(cached, "cache", "NumHit"); + if (opt) { + u32 nb_hits; + sscanf(opt, "%u", &nb_hits); + nb_hits--; + if (nb_hits) { + if (!ci->min_nb_hit) ci->min_nb_hit = ci->max_nb_hit = nb_hits; + if (ci->min_nb_hit>nb_hits) ci->min_nb_hit = nb_hits; + if (ci->max_nb_hit<nb_hits) ci->max_nb_hit = nb_hits; + + //only get hit times if hit + opt = gf_cfg_get_key(cached, "cache", "LastHit"); + if (opt) { + sscanf(opt, LLU, &age); + if (!ci->min_hit) ci->min_hit = ci->max_hit = age; + if (ci->min_hit>age) ci->min_hit = age; + if (ci->max_hit<age) ci->max_hit = age; + } + } + } + gf_cfg_del(cached); + return GF_FALSE; + } + if (!in_range) { + gf_cfg_del(cached); + return GF_FALSE; + } + + //cache print + if (ci->op_type==CACHE_OP_SHOW) { + gf_fprintf(stdout, "URL %s:\n", url); + char *sep = strstr(item_path, ".txt"); + sep0 = 0; + gf_fprintf(stdout, "\tDisk path: %s\n", item_path); + sep0 = '.'; + + u32 i, count = gf_cfg_get_key_count(cached, "cache"); + for (i=0; i<count; i++) { + char szDate100; + const char *name = gf_cfg_get_key_name(cached, "cache", i); + if (!name || !strcmp(name, "url")) continue; + const char *opt = gf_cfg_get_key(cached, "cache", name); + if (!opt) continue; + if (!strcmp(name, "MaxAge")) { + u64 expires; + char szDur100; + sscanf(opt, LLU, &expires); + s64 now = expires; + now-=gf_net_get_utc()/1000; + if (now>0) + gf_fprintf(stdout, "\tExpires: %s (in %s)\n", format_date(expires, szDate), gf_format_duration(now, 1, szDur) ); + continue; + } + if (!strcmp(name, "Created")) { + u64 created; + sscanf(opt, LLU, &created); + gf_fprintf(stdout, "\tCreated: %s\n", format_date(created, szDate)); + continue; + } + if (!strcmp(name, "LastHit")) { + u64 hit; + sscanf(opt, LLU, &hit); + gf_fprintf(stdout, "\tLastHit: %s\n", format_date(hit, szDate) ); + continue; + } + + gf_fprintf(stdout, "\t%s: %s\n", name, opt); + } + gf_fprintf(stdout, "\n"); + gf_cfg_del(cached); + return GF_FALSE; + } + + if (ci->op_type!=CACHE_OP_UNFLATTEN) return GF_FALSE; + + //cache unflatten if (url) url = strstr(url, "://"); if (url) { u32 i, len, dir_len=0, k=0; @@ -2144,6 +2663,63 @@ return GF_FALSE; } +static void do_cache_check(u32 op_type, char *arg_val) +{ + const char *cache_dir = gf_opts_get_key("core", "cache"); + CacheInfo ci = {0}; + ci.op_type = op_type; + + if (arg_val) { + u32 d_idx=0; + u64 now = gf_net_get_utc()/1000; + while (1) { + u64 date=0; + char *asep = strchr(arg_val, ';'); + if (asep) asep0 = 0; + if (strstr(arg_val, ":")) + date = gf_net_parse_date(arg_val)/1000; + else if (strcmp(arg_val, "0")) + date = now - atoi(arg_val); + + if (!d_idx) ci.date_min = date ? date : 1; + else ci.date_max = date ? date : now; + d_idx++; + + if (!asep) break; + asep0 = ';'; + arg_val=asep+1; + } + } + + gf_enum_directory(cache_dir, GF_FALSE, cache_file_op, &ci, ".txt"); + + if (op_type==CACHE_OP_UNFLATTEN) { + fprintf(stderr, "GPAC Cache dir %s flattened\n", cache_dir); + } else if (op_type==CACHE_OP_INFO) { + char szDate100; + u32 csize = gf_opts_get_int("core", "cache-size"); + if (!csize) csize = 1; + + gf_fprintf(stdout, "Cache info:\n\tMax size: "LLU" bytes\n\tNumber of items: %u\n\tTotal Size: %u (used %u %%)\n\tMin Size: %u\n\tMax Size: %u\n", csize, ci.nb_entries, ci.total_size, (u32) (ci.total_size*100/csize), ci.min_size, ci.max_size); + if (!ci.nb_entries) return; + gf_fprintf(stdout, "\tOldest entry: %s\n", format_date(ci.min_created, szDate) ); + gf_fprintf(stdout, "\tMost recent entry: %s\n", format_date(ci.max_created, szDate) ); + if (ci.min_expire) + gf_fprintf(stdout, "\tShortest expiration time: %s\n", format_date(ci.min_expire, szDate) ); + if (ci.max_expire) + gf_fprintf(stdout, "\tLongest expiration time: %s\n", format_date(ci.max_expire, szDate) ); + gf_fprintf(stdout, "\tHits: min %u max %u\n", ci.min_nb_hit, ci.max_nb_hit); + if (ci.min_nb_hit) { + gf_fprintf(stdout, "\tOldest hit time: %s\n", format_date(ci.min_hit, szDate) ); + gf_fprintf(stdout, "\tMost recent hit time: %s\n", format_date(ci.max_hit, szDate) ); + } + } else if (op_type==CACHE_OP_DELETE) { + if (ci.date_min || ci.date_max) { + gf_fprintf(stdout, "Removed %u items freed %d bytes %u - items remaining %u bytes\n", ci.nb_entries, ci.min_size, ci.total_size, ci.max_size); + } + } +} + typedef struct { @@ -2216,6 +2792,8 @@ *out_err = GF_OK; if (!strcmp(mode, "ref")) { + if (!ioctx_ref->nb_refs && (gf_list_find(all_gfio_defined, fileio_ref)<0)) + gf_list_add(all_gfio_defined, fileio_ref); ioctx_ref->nb_refs++; return fileio_ref; } @@ -2225,6 +2803,7 @@ if (ioctx_ref->nb_refs) return fileio_ref; + //fallback to close url = NULL; } @@ -2337,6 +2916,37 @@ return gfio; } +static GF_Err gpac_gfio_del(const char *url, const char *parent_gfio) +{ + GF_FileIO *gfio; + FileIOCtx *ioctx; + //delete on a gfio object + if (!parent_gfio) { + gfio = gf_fileio_from_url(url); + if (!gfio || (gf_list_find(all_gfio_defined, gfio)<0)) + return GF_EOS; + ioctx = gf_fileio_get_udta(gfio); + if (ioctx->filep) return GF_BAD_PARAM; + if (ioctx->path) gf_file_delete(ioctx->path); + return GF_OK; + } + //delete by URL relative to a parent gfio + gfio = gf_fileio_from_url(parent_gfio); + if (!gfio || (gf_list_find(all_gfio_defined, gfio)<0)) + return GF_EOS; + ioctx = gf_fileio_get_udta(gfio); + if (!ioctx->path) return GF_EOS; + + char *path = gf_url_concatenate(ioctx->path, url); + if (path) { + gf_file_delete(path); + gf_free(path); + return GF_OK; + } + return GF_EOS; +} + +Bool gfiodel_registered=GF_FALSE; static const char *make_fileio(const char *inargs, const char **out_arg, u32 io_mode, GF_Err *e) { @@ -2346,6 +2956,11 @@ *out_arg = NULL; if (sep) sep0 = 0; + if (!gfiodel_registered) { + gfiodel_registered = GF_TRUE; + gf_fileio_register_delete_proc(gpac_gfio_del); + } + GF_SAFEALLOC(ioctx, FileIOCtx); if (!ioctx) return NULL; ioctx->path = gf_strdup(inargs); @@ -2377,6 +2992,11 @@ static void cleanup_file_io() { + if (gfiodel_registered) { + gfiodel_registered = GF_FALSE; + gf_fileio_unregister_delete_proc(gpac_gfio_del); + } + if (!all_gfio_defined) return; while (gf_list_count(all_gfio_defined)) { GF_FileIO *gfio = gf_list_pop_back(all_gfio_defined); @@ -2475,11 +3095,12 @@ char input; GF_SessionDebugFlag flags=0; in_sig_handler = GF_TRUE; - fprintf(stderr, "\nToggle reports (r), print state (s for short, e for extended + shift: sticky)\n" + fprintf(stderr, "\nToggle reports (r), change logs (l), print state (s for short, e for extended + shift: sticky)\n" "\tor exit with fast (Y), full (f) or no (n) session flush ? \n"); rescan: input = gf_getch(); - if (!input || input == 0x0A || input == 0x0D) input = 'Y'; // user pressed "return" + if (!prev_was_cmd) + if (!input || input == 0x0A || input == 0x0D) input = 'Y'; // user pressed "return" switch (input) { case 'Y': case 'y': @@ -2538,6 +3159,21 @@ signal_processed = GF_FALSE; gf_fs_print_debug_info(session, flags|GF_FS_DEBUG_ALL); break; + case 'L': + case 'l': + { + char szLogs100; + prev_was_cmd = GF_TRUE; + signal_catched = GF_FALSE; + signal_processed = GF_FALSE; + fprintf(stdout, "Enter new logs settings:\n"); + if (1 > scanf("%99s", szLogs)) { + fprintf(stderr, "Cannot read the logs !\n"); + break; + } + gf_log_set_tools_levels(szLogs, GF_TRUE); + } + break; default: signal_processed = GF_TRUE; gf_fs_abort(session, GF_FS_FLUSH_NONE); @@ -2750,7 +3386,6 @@ u32 size; burl = gf_blob_register(&b); - gf_sys_profiler_set_callback(NULL, NULL); gf_blob_get(burl, &data, &size, NULL); gf_blob_unregister(&b); @@ -3007,7 +3642,6 @@ gf_audio_fmt_to_isobmf(0); gf_pixel_fmt_probe(0, NULL); gf_net_ntp_to_utc(0); - gf_sys_profiler_sampling_enabled(); #endif @@ -3070,6 +3704,7 @@ u64 now = gf_sys_clock_high_res(); sprintf(szVAL, LLU, now); gf_cfg_set_key(creds, user, "pass_date", szVAL); + gf_free(pass); return now; }
View file
gpac-2.4.0.tar.gz/applications/gpac/gpac.h -> gpac-26.02.0.tar.gz/applications/gpac/gpac.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022 + * Copyright (c) Telecom ParisTech 2022-2024 * All rights reserved * * This file is part of GPAC / gpac application @@ -51,6 +51,8 @@ #endif +GF_Err gf_fs_process_link_directive(char *link, GF_Filter *filter, GF_List *loaded_filters, char *ext_link); + //if uncommented, check argument description matches our conventions - see filter.h #define CHECK_DOC @@ -80,7 +82,7 @@ void dump_all_codecs(GF_SysArgMode argmode); void dump_all_formats(GF_SysArgMode argmode); void dump_all_proto_schemes(GF_SysArgMode argmode); - +void check_prop_def(char *pname); void write_core_options(void ); void write_file_extensions(void ); void write_filters_options(void); @@ -94,3 +96,12 @@ #endif void gpac_open_urls(const char *urls); + +#if !defined(GPAC_CONFIG_ANDROID) && !defined(GPAC_CONFIG_IOD) +//support for step-by-stpe graph construction +#define GPAC_DEFER_MODE +#endif + +#ifdef GPAC_DEFER_MODE +void gpac_defer_help(void); +#endif // GPAC_DEFER_MODE
View file
gpac-2.4.0.tar.gz/applications/gpac/gpac_help.c -> gpac-26.02.0.tar.gz/applications/gpac/gpac_help.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / gpac application @@ -27,7 +27,6 @@ #include <gpac/filters.h> #include "gpac.h" -extern FILE *sidebar_md; extern FILE *helpout; extern u32 gen_doc; extern u32 help_flags; @@ -41,6 +40,16 @@ extern GF_List *args_used; extern GF_List *args_alloc; extern u32 compositor_mode; + +GF_List *filter_classes = NULL; +typedef struct +{ + GF_ClassTypeHint type; + const char *name; + GF_List *filter_names; + GF_List *filter_descs; +} FilterCategory; + #define SEP_LIST 3 @@ -71,13 +80,14 @@ "- string: formatted as:\n" " - `value`: copies value to string.\n" " - `file@FILE`: load string from local `FILE` (opened in binary mode).\n" -" - `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/NHML-Format.\n" +" - `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/xmlformats/NHML-Format.\n" "- data: formatted as:\n" " - `size@address`: constant data block, not internally copied; `size` gives the size of the block, `address` the data pointer.\n" " - `0xBYTESTRING`: data block specified in hexadecimal, internally copied.\n" " - `file@FILE`: load data from local `FILE` (opened in binary mode).\n" -" - `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/NHML-Format.\n" +" - `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/xmlformats/NHML-Format.\n" " - `b64@DATA`: load data from base-64 encoded `DATA`.\n" +" - `FMT@val`: load values from val (comma-separated list) with `FMT` being `u8`, `s8`, `u16`, `s16`, `u32`, `s32`, `u64`, `s64`, `flt`, `dbl`, `hex` or `str`.\n" "- pointer: pointer address as formatted by `%p` in C.\n" "- string lists: formatted as `val1,val2,...`. Each value can also use `file@FILE` syntax.\n" "- integer lists: formatted as `val1,val2,...`\n" @@ -98,14 +108,14 @@ "This will fail to extract it and keep `:opt=VAL` as part of the URL.\n" "The escape mechanism is not needed for local source, for which file existence is probed during argument parsing. " "It is also not needed for builtin protocol handlers (`avin://`, `video://`, `audio://`, `pipe://`)\n" -"For `tcp://` and `udp://` protocols, the escape is not needed if a trailing `/` is appended after the port number.\n" +"For schemes not using a server path, e.g. `tcp://` and `udp://`, the escape is not needed if a trailing `/` is appended after the port number.\n" "EX -i tcp://127.0.0.1:1234:OPT\n" "This will fail to extract the URL and options.\n" "EX -i tcp://127.0.0.1:1234/:OPT\n" "This will extract the URL and options.\n" "Note: one trick to avoid the escape sequence is to declare the URLs option at the end, e.g. `f1:opt1=foo:url=http://bar`, provided you have only one URL parameter to specify on the filter.\n" "\n" -"It is possible to disable option parsing (for string options) by duplicating the separator.\n" +"It is possible to locally disable option parsing (usefull for string options) by duplicating the separator.\n" "EX filter::opt1=UDP://IP:PORT/:someopt=VAL::opt2=VAL2\n" "This will pass `UDP://IP:PORT/:someopt=VAL` to `opt1` without inspecting it, and `VAL2` to `opt2`.\n" " \n" @@ -150,19 +160,22 @@ "When a filter uses an option defined as a string using the same separator character as gpac, you can either " "modify the set of separators, or escape the separator by duplicating it. The options enclosed by duplicated " "separator are not parsed. This is mostly used for meta filters, such as ffmpeg, to pass options to sub-filters " -"such as libx264 (cf `x264opts` parameter).\n" +"such as libx264 (cf `x264opts` parameter). This can also be used to escape a list value containing the comma " +"separator character.\n" "EX f:a=foo:b=bar\n" "This will set option `a` to `foo` and option `b` to `bar` on the filter.\n" "EX f::a=foo:b=bar\n" "This will set option `a` to `foo:b=bar` on the filter.\n" "EX f:a=foo::b=bar:c::d=fun\n" "This will set option `a` to `foo`, `b` to `bar:c` and the option `d` to `fun` on the filter.\n" +"EX f:list=a,b1,,b2,c\n" +"This will set list values to `a`, `b1,b2`, and `c`.\n" "\n" "# Filter linking __LINK__\n" "\n" "Each filter exposes one or more sets of capabilities, called __capability bundle__, which are property type and values " "that must be matched or excluded by connecting PIDs.\n" -"To check the possible sources and destination for a filter `FNAME`, use `gpac -h links FNAME`\n" +"To check the possible sources and destinations for a filter `FNAME`, use `gpac -h links FNAME`\n" "\n" "The filter graph resolver uses this information together with the PID properties to link the different filters.\n" "\n" @@ -174,7 +187,7 @@ "Each PID is checked for possible connection to all defined filters, in their declaration order.\n" "For each filter `DST` accepting a connection from the PID, directly or with intermediate filters:\n" "- if `DST` filter has link directives, use them to allow or reject PID connection.\n" -"- otherwise, if __complete mode__ is enabled, allow connection..\n" +"- otherwise, if __complete mode__ is enabled, allow connection.\n" "- otherwise (__implicit mode__):\n" " - if `DST` is not a sink and is the first matching filter with no link directive, allow connection.\n" " - otherwise, if `DST` is not a sink and is not the first matching filter with no link directive, reject connection.\n" @@ -182,6 +195,7 @@ "\n" "In all linking modes, a filter can prevent being linked to a filter with no link directives by setting `RSID` option on the filter.\n" "This is typically needed when dynamically inserting/removing filters in an existing session where some filters have no ID defined and are not desired for the inserted chain.\n" +"A filter with `RSID` set is not clonable.\n" "\n" "EX gpac -i file.mp4 c=avc -o output\n" "With this setup in __implicit mode__:\n" @@ -350,7 +364,7 @@ "- cts, dts, dur, sap: uses properties of first packet in PID at template resolution time\n" "- OTHER: locates property 4CC for the given name, or property name if no 4CC matches.\n" " \n" -"`$$` is an escape for $\n" +"`$$` is an escape for `$`. For shells automatically replacing `$$` with `$`, the syntax `$;$` can be used to express `$$`.\n" "\n" "Templating can be useful when encoding several qualities in one pass.\n" "EX gpac -i dump.yuv:size=640x360 vcrop:wnd=0x0x320x180 c=avc:b=1M @2 c=avc:b=750k -o dump_$CropOrigin$x$Width$x$Height$.264\n" @@ -432,6 +446,10 @@ "EX gpac -i source.ts:#RepresentationID=$ServiceID$\n" "This will assign DASH Representation ID to the PID ServiceID value.\n" "\n" +"A property can also be removed by not specifying any value. Conditional removal is possible using the above syntax.\n" +"EX gpac -i source.ts:#FOO=\n" +"This will remove the `FOO` property on the output PID.\n" +"\n" "# Using option files\n" "It is possible to use a file to define options of a filter, by specifying the target file name as an option without value, i.e. `:myopts.txt`.\n" "Warning: Only local files are allowed.\n" @@ -464,6 +482,7 @@ " - application document directory for iOS\n" " - `EXTERNAL_STORAGE` environment variable if present or `/sdcard` otherwise for Android\n" " - user home directory for other platforms\n" +"- $GCFG: replaced by system path to folder containing GPAC config\n" "- $GLANG: replaced by the global config language option -lang(CORE)\n" "- $GUA: replaced by the global config user agent option -user-agent(CORE)\n" "- $GINC(init_val,inc): replaced by `init_val` and increment `init_val` by `inc` (positive or negative number, 1 if not specified) each time a new filter using this string is created.\n" @@ -704,6 +723,12 @@ gf_sys_print_core_help(helpout, help_flags, mode, mask); } +#ifdef GPAC_DEFER_MODE +#define GPAC_DEFER_H "- defer: print defer mode help\n" +#else +#define GPAC_DEFER_H +#endif + static GF_GPACArg gpac_args = { #ifdef GPAC_MEMORY_TRACKING @@ -711,7 +736,8 @@ GF_DEF_ARG("mem-track-stack", NULL, "enable memory tracker with stack dumping", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), #endif GF_DEF_ARG("ltf", NULL, "load test-unit filters (used for for unit tests only)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), - GF_DEF_ARG("sloop", NULL, "loop execution of session, creating a session at each loop, mainly used for testing. If no value is given, loops forever", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("sloop", NULL, "loop execution of session, creating a session at each loop, mainly used for testing, breaking at first error. If no value is given, loops forever", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("eloop", NULL, "same as sloop but does not break if error", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), GF_DEF_ARG("runfor", NULL, "run for the given amount of milliseconds, exit with full session flush", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), GF_DEF_ARG("runforf", NULL, "run for the given amount of milliseconds, exit with fast session flush", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), GF_DEF_ARG("runforx", NULL, "run for the given amount of milliseconds and exit with no cleanup", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), @@ -741,6 +767,7 @@ GF_DEF_ARG("ibx", NULL, "specify an input file to wrap as GF_FileIO object without caching (testing of GF_FileIO)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), GF_DEF_ARG("ob", NULL, "specify an output file to wrap as GF_FileIO object (testing of GF_FileIO)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), GF_DEF_ARG("cl", NULL, "force complete mode when no link directive are set - see filters help (-h doc)(filters_general)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("sid", NULL, "force source IDs to be present when attempting to link - see filters help (-h doc)(filters_general)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), #ifdef GPAC_CONFIG_EMSCRIPTEN GF_DEF_ARG("step=FPS:STEPS", NULL, "configure step mode in non-blocking session (enabled by default if no worker). Step mode is driven by requestAnimationFrame\n" @@ -776,6 +803,7 @@ "- layouts: print the builtin CICP audio channel layout names and their values\n" "- links: print possible connections between each supported filters (use -hx to view src->dst cap bundle detail)\n" "- links FNAME: print sources and sinks for filter `FNAME` (either builtin or JS filter)\n" + GPAC_DEFER_H "- FNAME: print filter `FNAME` info (multiple FNAME can be given)\n" " - For meta-filters, use `FNAME:INST`, e.g. `ffavin:avfoundation`\n" " - Use `*` to print info on all filters (__big output!__), `*:*` to print info on all filters including meta filter instances (__really big output!__)\n" @@ -791,8 +819,18 @@ GF_DEF_ARG("alias", NULL, "assign a new alias or remove an alias. Can be specified several times. See alias usage (-h alias)(#using-aliases)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), GF_DEF_ARG("aliasdoc", NULL, "assign documentation for a given alias (optional). Can be specified several times", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), - GF_DEF_ARG("uncache", NULL, "revert all items in GPAC cache directory to their original name and server path", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), - GF_DEF_ARG("js", NULL, "specify javascript file to use as controller of filter session", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("cache-info", NULL, "show cache info. Argument can be:\n" + "- absent: the entire cache is inspected\n" + "- B: filter entries created after `B`, with `B` a number of seconds prior to now or a date (0 means now)\n" + "- B;C: filter entries created after `B` but before `C`, with `B` and `C` either a number of seconds prior to now or a date\n" + " - If `B` is 0, min time is UTC=0\n" + " - If `C` is 0, max time is now\n" + "The argument syntax is the same for all cache options" + , NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("cache-unflat", NULL, "revert all items in GPAC cache directory to their original name and server path", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("cache-list", NULL, "list entries in cache", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("cache-clean", NULL, "clean cache", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("js", NULL, "specify javascript file to use as controller of filter session (can be set multiple times for multiple scripts)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), GF_DEF_ARG("wc", NULL, "write all core options in the config file unless already set", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), GF_DEF_ARG("we", NULL, "write all file extensions in the config file unless already set (useful to change some default file extensions)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), @@ -802,6 +840,7 @@ GF_DEF_ARG("genmd", NULL, "generate markdown doc", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_HIDE), GF_DEF_ARG("xopt", NULL, "unrecognized options and filters declaration following this option are ignored - used to pass arguments to GUI", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), GF_DEF_ARG("creds", NULL, "setup credentials as used by servers", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("rv", NULL, "return absolute value of GPAC internal error instead of 1 when error", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), #ifdef GPAC_CONFIG_IOS GF_DEF_ARG("req-gl", NULL, "forces loading SDL - iOS only", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), @@ -822,7 +861,7 @@ "__LINK__: a link instruction (e.g., `@`, `@2`, `@2#StreamType=Visual`, ...), see %s.\n" "__options__: one or more option strings, each starting with a `-` character.\n" " - an option using a single `-` indicates an option of gpac (see %s) or of libgpac (see %s)\n" - " - an option using `--` indicates a global filter or meta-filter (e.g. FFMPEG) option, e.g. `--block_size=1000` or `--profile=Baseline` (see %s)\n" + " - an option using `--` indicates a global filter or meta-filter (e.g. FFmpeg) option, e.g. `--block_size=1000` or `--profile=Baseline` (see %s)\n" " \n" "Filter declaration order may impact the link resolver which will try linking in declaration order. Most of the time for simple graphs, this has no impact. However, for complex graphs with no link declarations, this can lead to different results. \n" "Options do not require any specific order, and may be present anywhere, including between link statements or filter declarations. \n" @@ -832,7 +871,7 @@ "\n" "The possible options for gpac are:\n\n", (gen_doc==1) ? "gpac -h doc(filters_general#filter-declaration-filter)" : "`gpac -h doc`", - (gen_doc==1) ? "gpac -h doc(filters_general#explicit-links-between-filters-link)" : "`gpac -h doc`", + (gen_doc==1) ? "gpac -h doc(filters_general#filter-linking-link)" : "`gpac -h doc`", (gen_doc==1) ? "gpac -hx(gpac_general#h)" : "`gpac -hx`", (gen_doc==1) ? "gpac -hx core(core_options)" : "`gpac -hx core`", (gen_doc==1) ? "gpac -h doc(core_config#global-filter-options)" : "`gpac -h doc`", @@ -873,6 +912,74 @@ } } +#ifdef GPAC_DEFER_MODE +#ifndef GPAC_DISABLE_DOC +static const char *gpac_defer = +{ +"# Defer test mode\n" +"This mode can be used to test loading filters one by one and asking for link resolution explicitly.\n" +"This is mostly used to reproduce how sessions are build in more complex applications.\n" +"\n" +"The options `rl`, `pi`, `pl` and `pd` allow addressing a filter by index `F` in a list.\n" +"- if the option is suffixed with an `x` (e.g. `rlx=`), `F=0` means the last filter in the list of filters in the session\n" +"- otherwise, `F=0` means the last filter declared before the option\n" +"\n" +"The relink options `-rl` and `-rlx` always flush the session (run until no more tasks are scheduled).\n" +"The last run can be omitted.\n" +"\n" +"EX gpac -dl -np -i SRC reframer -g -rl -g inspect -g -rl\n" +"This will load SRC and reframer, print the graph (no connection), relink SRC, print the graph (connection to reframer), insert inspect, print the graph (no connection), relink reframer and run. No play event is sent here.\n" +"EX gpac -dl -np -i SRC reframer inspect:deep -g -rl=2 -g -rl -se\n" +"This will load SRC, reframer and inspect, print the graph (no connection), relink SRC, print the graph (connection to reframer), print the graph (no connection), relink reframer, send play and run.\n" +"\n" +"Linking can be done once filters are loaded, using the syntax `@F@SRC` or `@@F@SRC`:\n" +"- `@F` indicates the destination filter using a 0-based index `F` starting from the last laoded filter, e.g. `@0` indicates the last loaded filter.\n" +"- `@@F` indicates the target filter using a 0-based index `F` starting from the first laoded filter, e.g. `@@1` indicates the second loaded filter.\n" +"- `@SRC`or `@@SRC`: same syntax as link directives\n" +"Sources MUST be set before relinking outputs using -rl().\n" +"EX gpac -dl -i SRC F1 F2 ... @1@2 @0@2\n" +"This will set SRC as source to F1 and SRC as source to F2 after loading all filters.\n" +"\n" +"The following options are used in defer mode:\n\n", +}; +#endif +static GF_GPACArg gpac_defer_args = +{ + GF_DEF_ARG("dl", NULL, "enable defer linking mode for step-by-step graph building tests", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("np", NULL, "prevent play event from sinks", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("rl=F", NULL, "relink outputs of filter `F` (default 1)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("wl=F", NULL, "same as `-rl` but does not flush session)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("pi=+|-F:i", NULL, "print PID properties (all or of index `i`) of filter `F` (default 0)\n" + "- if prefixed with `-`: only list PIDs\n" + "- if prefixed with `+`: also print PID info", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("pl=+F:i@NAME", NULL, "probe filter chain from filter `F` (default 0) to the given filter `NAME`:\n" + "- if prefixed with `+`: print all known chains and their priorities", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("pd=F:i", NULL, "print possible PID destinations (all or of index `i`) of filter `F` (default 0)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("f", NULL, "flush session until no more tasks", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("g", NULL, "print graph", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("s", NULL, "print stats", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("se", NULL, "send PLAY event from sinks (only done once)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("m", NULL, "print message", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + {0} +}; + +void gpac_defer_help() +{ + u32 i=0; +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "%s", gf_sys_localized("gpac", "config", gpac_defer) ); +#else + gf_sys_format_help(helpout, help_flags, "%s", "GPAC compiled without built-in doc.\n"); +#endif + + while (gpac_defer_argsi.name) { + GF_GPACArg *arg = &gpac_defer_argsi; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "gpac"); + } +} +#endif + #ifndef GPAC_DISABLE_DOC static const char *gpac_config = { @@ -1164,7 +1271,11 @@ if (is_help) { if (!pass_exact) { const char *doc_helps = { - "log", "core", "modules", "doc", "alias", "props", "colors", "layouts", "cfg", "prompt", "codecs", "formats", "exts", "protocols", "links", "bin", "filters", "filters:*", "filters:@", "mp4c", "net", NULL + "log", "core", "modules", "doc", "alias", "props", "colors", "layouts", "cfg", "prompt", "codecs", "formats", "exts", "protocols", "links", "bin", "filters", "filters:*", "filters:@", "mp4c", "net", +#ifdef GPAC_DEFER_MODE + "defer", +#endif + NULL }; first = GF_FALSE; i=0; @@ -1279,7 +1390,7 @@ while ((prop_info = gf_props_get_description(i))) { i++; if (!prop_info->name) continue; - + if (gf_sys_word_match(fname, prop_info->name)) { if (!first) { first = GF_TRUE; @@ -1458,6 +1569,8 @@ char szDumpGF_PROP_DUMP_ARG_SIZE; const GF_FilterCapability *cap = &capsi; if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE) && i+1==nb_caps) break; + if (cap->flags & GF_CAPFLAG_RECONFIG) break; + if (!i) gf_sys_format_help(helpout, help_flags, "Capabilities Bundle:\n"); else if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { gf_sys_format_help(helpout, help_flags, "Capabilities Bundle:\n"); @@ -1492,7 +1605,12 @@ } if (gen_doc==1) { - gf_sys_format_help(helpout, help_flags, "<a id=\"%s\">", a->arg_name); + gf_sys_format_help(helpout, help_flags, "<div markdown class=\"option\">\n"); + if (a->flags & (GF_ARG_HINT_ADVANCED|GF_ARG_HINT_EXPERT)) { + gf_sys_format_help(helpout, help_flags, "<a id=\"%s\">", a->arg_name); + } else { + gf_sys_format_help(helpout, help_flags, "<a id=\"%s\" data-level=\"basic\">", a->arg_name); + } gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s", a->arg_name); gf_sys_format_help(helpout, help_flags, "</a> (%s", is_enum ? "enum" : gf_props_get_type_name(a->arg_type)); } else { @@ -1526,6 +1644,9 @@ } else { gf_sys_format_help(helpout, help_flags | GF_PRINTARG_OPT_DESC, "): %s\n", a->arg_desc); } + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "</div>\n"); + } //check syntax if (gen_doc) { @@ -1548,7 +1669,7 @@ static void print_filter_single_opt(const GF_FilterRegister *reg, char *optname, GF_Filter *filter_inst) { u32 idx=0; - Bool found = GF_FALSE; + Bool found = GF_FALSE, all_opt = !strcmp(optname, "*") || !strcmp(optname, "@"); const GF_FilterArgs *args = NULL; if (filter_inst) args = gf_filter_get_args(filter_inst); @@ -1557,15 +1678,14 @@ if (!args) return; - while (1) { + while (!found || all_opt) { const GF_FilterArgs *a = & argsidx; if (!a || !a->arg_name) break; idx++; - if (strcmp(a->arg_name, optname)) continue; + if (!all_opt && strcmp(a->arg_name, optname)) continue; print_filter_arg(a, 0); found = GF_TRUE; - break; } if (found) return; @@ -1589,9 +1709,51 @@ fprintf(stderr, "No such option %s for filter %s\n", optname, filter_inst ? gf_filter_get_name(filter_inst) : reg->name); } +static FilterCategory *get_filter_class(GF_ClassTypeHint hint) +{ + FilterCategory *c; + if (!filter_classes) filter_classes = gf_list_new(); + if (hint>=GF_FS_CLASS_LAST_DEFINED) hint = GF_FS_CLASS_UNSPECIFIED; + u32 i, count = gf_list_count(filter_classes); + for (i=0; i<count; i++) { + c = gf_list_get(filter_classes, i); + if (c->type == hint) return c; + } + GF_SAFEALLOC(c, FilterCategory); + c->type = hint; + c->filter_names = gf_list_new(); + c->filter_descs = gf_list_new(); + switch (hint) { + case GF_FS_CLASS_DEMULTIPLEXER: c->name = "Demultiplexers"; break; + case GF_FS_CLASS_MULTIPLEXER: c->name = "Mulitplexers"; break; + case GF_FS_CLASS_DECODER: c->name = "Decoders"; break; + case GF_FS_CLASS_ENCODER: c->name = "Encoders"; break; + case GF_FS_CLASS_CRYPTO: c->name = "Cryptography"; break; + case GF_FS_CLASS_MM_IO: c->name = "Multimedia I/O"; break; + case GF_FS_CLASS_NETWORK_IO: c->name = "Protocols I/O"; break; + case GF_FS_CLASS_SUBTITLE: c->name = "Text & Subtitles"; break; + case GF_FS_CLASS_AV: c->name = "Audio & Video Processing"; break; + case GF_FS_CLASS_STREAM: c->name = "Stream Manipulation"; break; + case GF_FS_CLASS_FRAMING: c->name = "Bitstream Framing"; break; + case GF_FS_CLASS_TOOL: c->name = "Utilities & Tools"; break; + default: c->name = "Unclassified"; break; + } + + for (i=0; i<count; i++) { + FilterCategory *ac = gf_list_get(filter_classes, i); + if (c->type < ac->type) { + gf_list_insert(filter_classes, c, i); + return c; + } + } + gf_list_add(filter_classes, c); + return c; +} + static void print_filter(const GF_FilterRegister *reg, GF_SysArgMode argmode, GF_Filter *filter_inst, char *inst_name) { u32 idx=0; + GF_ClassTypeHint hint_type = GF_FS_CLASS_UNSPECIFIED; const GF_FilterArgs *args = NULL; const char *reg_name, *reg_desc=NULL; #ifndef GPAC_DISABLE_DOC @@ -1602,11 +1764,14 @@ if (filter_inst) { reg_name = inst_name; reg_desc = gf_filter_get_description(filter_inst); + hint_type = gf_filter_get_class_hint(filter_inst); #ifndef GPAC_DISABLE_DOC reg_help = gf_filter_get_help(filter_inst); #endif } else if (reg) { reg_name = reg->name; + hint_type = reg->hint_class_type; + reg_desc = reg->name; #ifndef GPAC_DISABLE_DOC reg_desc = reg->description; reg_help = reg->help; @@ -1628,37 +1793,26 @@ helpout = gf_fopen(szName, "w"); fprintf(helpout, "%s", auto_gen_md_warning); - if (!sidebar_md) { - char *sbbuf = NULL; - if (gf_file_exists("_Sidebar.md")) { - char szLine1024; - u32 end_pos=0; - sidebar_md = gf_fopen("_Sidebar.md", "r"); - gf_fseek(sidebar_md, 0, SEEK_SET); - while (!feof(sidebar_md)) { - char *read = gf_fgets(szLine, 1024, sidebar_md); - if (!read) break; - if (!strncmp(szLine, "**Filters Help**", 16)) { - end_pos = (u32) gf_ftell(sidebar_md); - break; - } - } - if (!end_pos) end_pos = (u32) gf_ftell(sidebar_md); - if (end_pos) { - sbbuf = gf_malloc(end_pos+1); - gf_fseek(sidebar_md, 0, SEEK_SET); - end_pos = (u32) gf_fread(sbbuf, end_pos, sidebar_md); - sbbufend_pos=0; - gf_fclose(sidebar_md); - } - } - sidebar_md = gf_fopen("_Sidebar.md", "w"); - if (sbbuf) { - fprintf(sidebar_md, "%s\n \n", sbbuf); - gf_free(sbbuf); + if (hint_type==GF_FS_CLASS_UNSPECIFIED) { + fprintf(stderr, "filter %s without class, forbidden\n", reg_name); + exit(1); + } + FilterCategory *help_class = get_filter_class(hint_type); + + u32 idx, count = gf_list_count(help_class->filter_descs); + for (idx=0; idx<count; idx++) { + const char *areg_desc = gf_list_get(help_class->filter_descs, idx); + if (strcmp(reg_desc, areg_desc)<0) { + gf_list_insert(help_class->filter_names, gf_strdup(reg_name), idx); + gf_list_insert(help_class->filter_descs, gf_strdup(reg_desc), idx); + break; } } - fprintf(sidebar_md, "%s (%s)|%s \n", reg_desc, reg_name, reg_name); + if (idx==count) { + gf_list_add(help_class->filter_names, gf_strdup(reg_name)); + gf_list_add(help_class->filter_descs, gf_strdup(reg_desc)); + } + #ifndef GPAC_DISABLE_DOC if (!reg_help) { @@ -1672,7 +1826,7 @@ #endif gf_sys_format_help(helpout, help_flags, "Register name used to load filter: **%s**\n", reg_name); if (filter_inst) { - gf_sys_format_help(helpout, help_flags, "This is a JavaScript filter, not checked during graph resolution and needs explicit loading.\n"); + gf_sys_format_help(helpout, help_flags, "This is a JavaScript filter. It is not checked during graph resolution and needs explicit loading.\n"); } else { if (reg->flags & GF_FS_REG_EXPLICIT_ONLY) { gf_sys_format_help(helpout, help_flags, "This filter is not checked during graph resolution and needs explicit loading.\n"); @@ -1775,7 +1929,7 @@ if (args && !jsmod_help) { idx=0; if (gen_doc==1) { - gf_sys_format_help(helpout, help_flags, "# Options \n"); + gf_sys_format_help(helpout, help_flags, "# Options {.no-collapse}\n"); } else { switch (argmode) { case GF_ARGMODE_ALL: @@ -1993,6 +2147,84 @@ return GF_FALSE; } +static void patch_mkdocs_yml() +{ + const char *yml_src = "../../mkdocs.yml"; + if (!gf_file_exists("../../mkdocs.yml")) { + yml_src = "mkdocs.yml"; + FILE *t = gf_fopen(yml_src, "w"); + gf_fprintf(t, " - Filters:\n"); + gf_fclose(t); + } + + char szLine1024; + Bool in_filter_list=GF_FALSE; + char *oyml = NULL; + FILE *yml = gf_fopen(yml_src, "r"); + while (!feof(yml)) { + char *read = gf_fgets(szLine, 1024, yml); + if (!read) break; + if (in_filter_list && !strncmp(szLine, " - ", 4)) + in_filter_list = GF_FALSE; + + if (!strncmp(szLine, " - Filters:", 14)) { + gf_dynstrcat(&oyml, szLine, NULL); + in_filter_list = GF_TRUE; + + while (gf_list_count(filter_classes)) { + FilterCategory *fclass = gf_list_pop_front(filter_classes); + sprintf(szLine, " - %s:\r\n", fclass->name); + gf_dynstrcat(&oyml, szLine, NULL); + while (gf_list_count(fclass->filter_names)) { + char *fname = gf_list_pop_front(fclass->filter_names); + char *fdesc = gf_list_pop_front(fclass->filter_descs); + char *sep = NULL; + if (fclass->type == GF_FS_CLASS_DEMULTIPLEXER) sep = strstr(fdesc, " demultiplexer"); + else if (fclass->type == GF_FS_CLASS_MULTIPLEXER) sep = strstr(fdesc, " multiplexer"); + else if (fclass->type == GF_FS_CLASS_DECODER) sep = strstr(fdesc, " decoder"); + else if (fclass->type == GF_FS_CLASS_ENCODER) sep = strstr(fdesc, " encoder"); + + if (sep) sep0 = 0; + sprintf(szLine, " - %s: Filters/%s.md\r\n", fdesc, fname); + gf_dynstrcat(&oyml, szLine, NULL); + if (sep) sep0 = ' '; + gf_free(fname); + gf_free(fdesc); + } + gf_list_del(fclass->filter_names); + gf_list_del(fclass->filter_descs); + gf_free(fclass); + } + continue; + } + if (!in_filter_list) { + gf_dynstrcat(&oyml, szLine, NULL); + continue; + } + } + gf_fclose(yml); + if (oyml) { + FILE *yml = gf_fopen(yml_src, "w"); + gf_fwrite(oyml, (u32) strlen(oyml), yml); + gf_fclose(yml); + gf_free(oyml); + } + + while (gf_list_count(filter_classes)) { + FilterCategory *fclass = gf_list_pop_back(filter_classes); + while (gf_list_count(fclass->filter_names)) { + gf_free(gf_list_pop_back(fclass->filter_names)); + } + gf_list_del(fclass->filter_names); + + while (gf_list_count(fclass->filter_descs)) { + gf_free(gf_list_pop_back(fclass->filter_descs)); + } + gf_list_del(fclass->filter_descs); + gf_free(fclass); + } + gf_list_del(filter_classes); +} Bool print_filters(int argc, char **argv, GF_SysArgMode argmode) { @@ -2027,24 +2259,27 @@ u32 k; //all good to go, load filters for (k=1; k<(u32) argc; k++) { - char *arg = argvk; - char *sepe; - Bool found_freg = GF_FALSE; - char *optname = NULL; - if (arg0=='-') continue; + char *arg = argvk, *sepe=NULL, *sepo, *optname=NULL; + Bool found_freg = GF_FALSE, found_filter = GF_FALSE; - sepe = gf_file_basename(arg); - if (sepe) sepe = strchr(sepe, '.'); + if (arg0=='-') continue; + optname = gf_file_basename(arg); + sepe = optname ? strchr(optname, '.') : optname; + if (sepe && !strncmp(sepe, ".js.", 4)) sepe = strchr(sepe+1, '.'); + //special case to allow -h jsf:js=FILE.js.arg + else if (sepe && !strcmp(sepe, ".js")) { + char *jsopt = strstr(optname, "js="); + if (jsopt && (jsopt<sepe)) sepe = NULL; + } + optname = NULL; if (sepe) { - if (!strncmp(sepe, ".js.", 4)) sepe = strchr(sepe+1, '.'); - else if (!strcmp(sepe, ".js")) sepe = NULL; - if (sepe) { - sepe0 = 0; - optname = sepe+1; - } + sepe0 = 0; + optname = sepe+1; } + fname = arg; - for (i=0; i<count; i++) { + sepo = strchr(arg, ':'); + for (i=0; i<count && !found_filter; i++) { const GF_FilterRegister *reg = gf_fs_get_filter_register(session, i); //exact match if (!strcmp(arg, reg->name) ) { @@ -2052,14 +2287,12 @@ print_filter_single_opt(reg, optname, NULL); else print_filter(reg, argmode, NULL, NULL); - found_freg = GF_TRUE; + found_filter = GF_TRUE; } //search for name:*, also accept *:* else { - char *sepo = strchr(arg, ':'); - if (!strcmp(arg, "*:*") || !strcmp(arg, "@:@") - || (!sepo && (!strcmp(arg, "*") || !strcmp(arg, "@")) ) + || ((!strcmp(arg, "*") || !strcmp(arg, "@"))) || (sepo && (!strcmp(sepo, ":*") || !strcmp(sepo, ":@")) && !strncmp(reg->name, arg, 1+sepo - arg) ) ) { if (optname) @@ -2071,11 +2304,12 @@ } } } - if (found_freg) { + if (found_freg || found_filter) { found = GF_TRUE; } else /*if (!strchr(arg, ':')) */ { GF_SysArgMode _argmode = argmode; - char *js_opt = strchr(arg, ':'); + char *js_opt = sepo; + if (js_opt && !strncmp(js_opt+1, "js", 2)) js_opt = strrchr(js_opt+1, ':'); if (js_opt) { js_opt0 = 0; gf_opts_set_key("temp", "gpac-js-help", js_opt+1); @@ -2146,6 +2380,8 @@ } if (!sep) break; } + if (gen_doc==1) patch_mkdocs_yml(); + return GF_TRUE; } @@ -2199,6 +2435,46 @@ return GF_FALSE; } +void check_prop_def(char *pname) +{ + while (pname && pname0) { + if (pname0=='\n') pname++; + else if (pname0=='\r') pname++; + else break; + } + u32 pname_len = pname ? (u32) strlen(pname) : 0; + while (pname_len) { + if (pnamepname_len-1=='\n') pname_len--; + else if (pnamepname_len-1=='\r') pname_len--; + else break; + } + if (pname_len==4) { + u32 i; + Bool is_p4cc = GF_TRUE; + for (i=0; i<4;i++) { + if ((pnamei>='0') && (pnamei<'9')) {} + else if ((pnamei>='A') && (pnamei<='Z')) {} + else if ((pnamei>='a') && (pnamei<='z')) {} + else { + is_p4cc = GF_FALSE; + } + } + if (is_p4cc) { + u32 p4cc = GF_4CC(pname0,pname1,pname2,pname3); + const char *name = gf_props_4cc_get_name(p4cc); + if (name) return; + fprintf(stdout, "UNKNOWN '%c','%c','%c','%c'\n", pname0, pname1, pname2, pname3); + return; + } + } + if (!strcmp(pname, "check")) { + gf_props_sanity_check(); + return; + } + + fprintf(stdout, "INVALID %s\n", pname); +} + void dump_all_props(char *pname) { u32 i=0; @@ -2499,6 +2775,7 @@ if (! (reg->flags & GF_FS_REG_META)) continue; for (j=0; j<reg->nb_caps; j++) { if (!(reg->capsj.flags & GF_CAPFLAG_IN_BUNDLE)) continue; + if (reg->capsj.flags & GF_CAPFLAG_RECONFIG) break; if (reg->capsj.code != GF_PROP_PID_CODECID) continue; if (reg->capsj.val.type != GF_PROP_NAME) continue; if (gf_list_find(meta_codecs, (void *)reg)<0) @@ -2797,6 +3074,8 @@ if ((argmode<GF_ARGMODE_EXPERT) && (reg->flags&GF_FS_REG_META)) continue; for (j=0; j<reg->nb_caps; j++) { + if (reg->capsj.flags & GF_CAPFLAG_RECONFIG) break; + if (!(reg->capsj.flags & GF_CAPFLAG_IN_BUNDLE)) { //special case for meta filters, declaring only input ext (demux) or output ext (mux) if (reg->flags & GF_FS_REG_META) { @@ -2990,7 +3269,6 @@ for (i=0; i<count; i++) { const char *f = gf_opts_get_key_name(sname, i); if (!f) continue; - if ((argmode<GF_ARGMODE_EXPERT) && strchr(f, ':')) continue; char *proto = (char*)gf_opts_get_key(sname, f); if (!proto) continue; @@ -3043,19 +3321,25 @@ } for (k=0; k<2; k++) { GF_List *list = k ? pe->out : pe->in; - const char *lab = k ? "in" : "out"; + const char *lab = k ? "out" : "in"; c2 = gf_list_count(list); if (c2) { if (gen_doc!=1) gf_sys_format_help(helpout, help_flags, " %s", lab); - if ((gen_doc==1) || (argmode==GF_ARGMODE_ADVANCED) || (argmode==GF_ARGMODE_ALL)) { + if ((gen_doc==1) || (argmode>=GF_ARGMODE_ADVANCED) || (argmode==GF_ARGMODE_ALL)) { if (gen_doc!=1) gf_sys_format_help(helpout, help_flags, " ("); for (j=0;j<c2; j++) { char *f = gf_list_get(list, j); if (j) gf_sys_format_help(helpout, help_flags, " "); + char *sep = NULL; + if (!gen_doc && (argmode==GF_ARGMODE_ADVANCED)) { + sep = strchr(f, ':'); + if (sep) sep0=0; + } gf_sys_format_help(helpout, help_flags, "%s", f); + if (sep) sep0=':'; } if (gen_doc!=1) gf_sys_format_help(helpout, help_flags, ")"); @@ -3512,13 +3796,29 @@ void parse_sep_set(const char *arg, Bool *override_seps) { if (!arg) return; - u32 len = (u32) strlen(arg); + u32 len = (u32) strlen(arg), i; if (!len) return; + char save_sepssizeof(separator_set); + strcpy(save_seps, separator_set); + Bool save_override_seps=*override_seps; *override_seps = GF_TRUE; - if (len>=1) separator_set0 = arg0; - if (len>=2) separator_set1 = arg1; - if (len>=3) separator_set2 = arg2; - if (len>=4) separator_set3 = arg3; - if (len>=5) separator_set4 = arg4; - if (len>=6) separator_set5 = arg5; + + if (len+1>sizeof(separator_set)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Separator set too long (%s): ", arg)); + } + u32 copy_len = MIN(len, sizeof(separator_set)-1); + memcpy(separator_set, arg, copy_len); + separator_setcopy_len = 0; + if (len+1>sizeof(separator_set)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("truncating to (%s)\n", separator_set)); + } + + for(i=0; i+1 < sizeof(separator_set); i++) { + if(strchr(separator_set+i+1, separator_seti)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Invalid separator set (%s): duplicate characters found. Reverting to previous set (%s)\n", separator_set, save_seps)); + *override_seps = save_override_seps; + strcpy(separator_set, save_seps); + return; + } + } }
View file
gpac-2.4.0.tar.gz/applications/gpac/ios-Info.plist -> gpac-26.02.0.tar.gz/applications/gpac/ios-Info.plist
Changed
@@ -37,11 +37,11 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>2.4</string> + <string>26.02</string> <key>CFBundleSignature</key> <string>gpac</string> <key>CFBundleVersion</key> - <string>12.14.0</string> + <string>16.5.0</string> <key>NSCameraUsageDescription</key> <string>GPAC needs camera access to handle this URL</string> <key>NSMicrophoneUsageDescription</key>
View file
gpac-2.4.0.tar.gz/applications/mp4box/Makefile -> gpac-26.02.0.tar.gz/applications/mp4box/Makefile
Changed
@@ -24,7 +24,7 @@ else ifeq ($(CONFIG_DARWIN),yes) #LINKFLAGS+= -Wl,-rpath,'@loader_path' else -LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc endif ifeq ($(STATIC_BUILD),yes)
View file
gpac-2.4.0.tar.gz/applications/mp4box/filedump.c -> gpac-26.02.0.tar.gz/applications/mp4box/filedump.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / mp4box application @@ -61,7 +61,7 @@ #ifndef GPAC_DISABLE_SWF_IMPORT extern u32 swf_flags; #endif -extern Float swf_flatten_angle; +extern Double swf_flatten_angle; extern GF_FileType get_file_type_by_ext(char *inName); extern u32 fs_dump_flags; extern Bool dump_check_xml; @@ -115,17 +115,20 @@ } if (inName) { - char szName1024; - if (is_final_name) { - strcpy(szName, inName); - } else { - sprintf(szName, "%s.%s", inName, (tag_len>>31) ? "png" : "jpg"); + char *szName=NULL; + gf_dynstrcat(&szName, inName, NULL); + if (!is_final_name) { + gf_dynstrcat(&szName, (tag_len>>31) ? ".png" : ".jpg", NULL); } + if (!szName) return GF_OUT_OF_MEM; + t = gf_fopen(szName, "wb"); if (!t) { M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szName)); + gf_free(szName); return GF_IO_ERR; } + gf_free(szName); } else { t = stdout; } @@ -161,7 +164,7 @@ load.swf_import_flags |= GF_SM_SWF_USE_SVG; load.svgOutFile = inName; } - load.swf_flatten_limit = swf_flatten_angle; + load.swf_flatten_limit = (Float) swf_flatten_angle; ftype = get_file_type_by_ext(file); if (ftype == GF_FILE_TYPE_ISO_MEDIA) { @@ -199,11 +202,12 @@ if (do_log) { char szLogGF_MAX_PATH; - sprintf(szLog, "%s_dec.logs", inName); + snprintf(szLog, GF_ARRAY_LENGTH(szLog), "%s_dec.logs", inName); logs = gf_fopen(szLog, "wt"); - - gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_DEBUG); - prev_logs = gf_log_set_callback(logs, scene_coding_log); + if (logs) { + gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_DEBUG); + prev_logs = gf_log_set_callback(logs, scene_coding_log); + } } e = gf_sm_load_init(&load); if (!e) e = gf_sm_load_run(&load); @@ -380,7 +384,7 @@ load.fileName = file; load.ctx = ctx; - if (get_file_type_by_ext(file) == 1) { + if (get_file_type_by_ext(file) == GF_FILE_TYPE_ISO_MEDIA) { load.isom = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL); if (!load.isom) { M4_LOG(GF_LOG_ERROR, ("Cannot open file: %s\n", gf_error_to_string(gf_isom_last_error(NULL)))); @@ -816,7 +820,7 @@ u32 PrintBuiltInBoxes(char *argval, u32 do_cov) { u32 i, count=gf_isom_get_num_supported_boxes(); - + fprintf(stdout, "<Boxes>\n"); //index 0 is our internal unknown box handler for (i=1; i<count; i++) { @@ -891,7 +895,8 @@ fprintf(dump, "<RTPHintTrack trackID=\"%d\">\n", gf_isom_get_track_id(file, i+1)); gf_isom_sdp_track_get(file, i+1, &sdp, &size); - fprintf(dump, "<SDPInfo>%s</SDPInfo>", sdp); + if (sdp && size) + fprintf(dump, "<SDPInfo>%s</SDPInfo>", sdp); #ifndef GPAC_DISABLE_ISOM_HINTING for (j=0; j<gf_isom_get_sample_count(file, i+1); j++) { @@ -1049,7 +1054,7 @@ } #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_INSPECT) -void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted); +void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, u8 *sai_buffer, u32 sai_buffer_size, u32 offset_in_sample); #endif @@ -1097,7 +1102,7 @@ for (i=0; i<gf_list_count(arr); i++) {\ slc = gf_list_get(arr, i);\ fprintf(dump, " <NALU size=\"%d\" ", slc->size);\ - gf_inspect_dump_nalu(dump, (u8 *) slc->data, slc->size, _is_svc, is_hevc ? hevc_state : NULL, avc_state, is_vvc ? vvc_state : NULL, nalh_size, (dump_flags&1) ? GF_TRUE : GF_FALSE, GF_FALSE);\ + gf_inspect_dump_nalu(dump, (u8 *) slc->data, slc->size, _is_svc, is_hevc ? hevc_state : NULL, avc_state, is_vvc ? vvc_state : NULL, nalh_size, (dump_flags&1) ? GF_TRUE : GF_FALSE, GF_FALSE, NULL, 0, 0);\ }\ fprintf(dump, " </%sArray>\n", name);\ }\ @@ -1266,6 +1271,9 @@ fprintf(dump, " </SCALReferences>\n"); } + u32 sai_buffer_size=0, sai_buffer_alloc=0; + u8 *sai_buffer = NULL; + fprintf(dump, " <NALUSamples>\n"); gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT); for (i=0; i<count; i++) { @@ -1313,6 +1321,22 @@ size--; } } +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_INSPECT) + u32 sample_offset=0; + Bool is_encrypted = 0; + if (is_cenc_protected) { + e = gf_isom_get_sample_cenc_info(file, track, i + 1, &is_encrypted, NULL, NULL, NULL, NULL); + if (e != GF_OK) { + fprintf(dump, "dump_msg=\"Error %s while fetching encryption info for sample, assuming sample is encrypted\" ", gf_error_to_string(e) ); + is_encrypted = GF_TRUE; + } + sai_buffer_size = sai_buffer_alloc; + e = gf_isom_cenc_get_sample_aux_info(file, track, i+1, di, NULL, &sai_buffer, &sai_buffer_size); + if (sai_buffer_size > sai_buffer_alloc) + sai_buffer_alloc = sai_buffer_size; + } +#endif + while (size) { if (size<nalh_size) { fprintf(dump, " <!-- NALU number %d is corrupted: length field is %d but only %d remains -->\n", idx, nalh_size, size); @@ -1327,15 +1351,9 @@ } else { fprintf(dump, " <NALU size=\"%d\" ", nal_size); #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_INSPECT) - Bool is_encrypted = 0; - if (is_cenc_protected) { - e = gf_isom_get_sample_cenc_info(file, track, i + 1, &is_encrypted, NULL, NULL, NULL, NULL); - if (e != GF_OK) { - fprintf(dump, "dump_msg=\"Error %s while fetching encryption info for sample, assuming sample is encrypted\" ", gf_error_to_string(e) ); - is_encrypted = GF_TRUE; - } - } - gf_inspect_dump_nalu(dump, ptr, nal_size, has_svcc ? 1 : 0, hevc_state, avc_state, vvc_state, nalh_size, dump_flags, is_encrypted); + gf_inspect_dump_nalu(dump, ptr, nal_size, has_svcc ? 1 : 0, hevc_state, avc_state, vvc_state, nalh_size, dump_flags, is_encrypted, sai_buffer, sai_buffer_size, sample_offset); + + sample_offset += nal_size + nalh_size; #else fprintf(dump, "/>\n"); #endif @@ -1352,6 +1370,7 @@ } fprintf(dump, " </NALUSamples>\n"); fprintf(dump, "</NALUTrack>\n"); + if (sai_buffer) gf_free(sai_buffer); gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode); @@ -1515,7 +1534,7 @@ } #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_INSPECT) -void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu, u64 obu_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc); +void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu, u64 obu_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc, u8 *sai_buffer, u32 sai_buffer_size, u32 offset_in_sample); #endif static GF_Err dump_isom_obu(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc) @@ -1523,7 +1542,7 @@ GF_Err e = GF_OK; #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_INSPECT) u32 i, count, track, timescale; - AV1State av1; + AV1State *av1_state; ObuType obu_type = 0; u64 obu_size = 0; u32 hdr_size = 0; @@ -1532,10 +1551,13 @@ track = gf_isom_get_track_by_id(file, trackID); - gf_av1_init_state(&av1); - av1.config = gf_isom_av1_config_get(file, track, 1); - if (!av1.config) { + GF_SAFEALLOC(av1_state, AV1State); + if (!av1_state) return GF_OUT_OF_MEM; + gf_av1_init_state(av1_state); + av1_state->config = gf_isom_av1_config_get(file, track, 1); + if (!av1_state->config) { M4_LOG(GF_LOG_ERROR, ("Error: Track #%d is not AV1!\n", trackID)); + gf_free(av1_state); return GF_ISOM_INVALID_FILE; } @@ -1546,28 +1568,31 @@ fprintf(dump, " <OBUConfig>\n"); - for (i=0; i<gf_list_count(av1.config->obu_array); i++) { - GF_AV1_OBUArrayEntry *obu = gf_list_get(av1.config->obu_array, i); + for (i=0; i<gf_list_count(av1_state->config->obu_array); i++) { + GF_AV1_OBUArrayEntry *obu = gf_list_get(av1_state->config->obu_array, i); bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); - e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state); if (e<GF_OK) { - if (av1.config) gf_odf_av1_cfg_del(av1.config); - gf_av1_reset_state(&av1, GF_TRUE); + if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); return e; } - gf_inspect_dump_obu(dump, &av1, obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, dump_crc); + gf_inspect_dump_obu(dump, av1_state, obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, dump_crc, NULL, 0, 0); gf_bs_del(bs); } fprintf(dump, " </OBUConfig>\n"); fprintf(dump, " <OBUSamples>\n"); + u8 *sai_buffer = NULL; + u32 sai_buffer_size=0, sai_buffer_alloc=0; e = GF_OK; for (i=0; i<count; i++) { u64 dts, cts; - u32 size; + u32 size, di; u8 *ptr; - GF_ISOSample *samp = gf_isom_get_sample(file, track, i+1, NULL); + GF_ISOSample *samp = gf_isom_get_sample(file, track, i+1, &di); if (!samp) { e = gf_isom_last_error(file); break; @@ -1578,23 +1603,39 @@ fprintf(dump, " <Sample number=\"%d\" DTS=\""LLD"\" CTS=\""LLD"\" size=\"%d\" RAP=\"%d\" >\n", i+1, dts, cts, samp->dataLength, samp->IsRAP); if (cts<dts) fprintf(dump, "<!-- NEGATIVE CTS OFFSET! -->\n"); + + u32 sample_offset=0; + Bool is_encrypted = 0; + if (gf_isom_is_cenc_media(file, track, di)) { + e = gf_isom_get_sample_cenc_info(file, track, i + 1, &is_encrypted, NULL, NULL, NULL, NULL); + if (e != GF_OK) { + fprintf(dump, "dump_msg=\"Error %s while fetching encryption info for sample, assuming sample is encrypted\" ", gf_error_to_string(e) ); + is_encrypted = GF_TRUE; + } + sai_buffer_size = sai_buffer_alloc; + e = gf_isom_cenc_get_sample_aux_info(file, track, i+1, di, NULL, &sai_buffer, &sai_buffer_size); + if (sai_buffer_size > sai_buffer_alloc) + sai_buffer_alloc = sai_buffer_size; + } + idx = 1; ptr = samp->data; size = samp->dataLength; bs = gf_bs_new(ptr, size, GF_BITSTREAM_READ); while (size) { - e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state); if (e<GF_OK) break; if (obu_size > size) { fprintf(dump, " <!-- OBU number %d is corrupted: size is %d but only %d remains -->\n", idx, (u32) obu_size, size); break; } - gf_inspect_dump_obu(dump, &av1, ptr, obu_size, obu_type, obu_size, hdr_size, dump_crc); + gf_inspect_dump_obu(dump, av1_state, ptr, obu_size, obu_type, obu_size, hdr_size, dump_crc, sai_buffer, sai_buffer_size, sample_offset); ptr += obu_size; size -= (u32)obu_size; idx++; + sample_offset+=obu_size; } gf_bs_del(bs); fprintf(dump, " </Sample>\n"); @@ -1607,9 +1648,11 @@ } fprintf(dump, " </OBUSamples>\n"); fprintf(dump, "</OBUTrack>\n"); + if (sai_buffer) gf_free(sai_buffer); - if (av1.config) gf_odf_av1_cfg_del(av1.config); - gf_av1_reset_state(&av1, GF_TRUE); + if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); #endif return e; } @@ -1692,8 +1735,10 @@ Bool traf_start = 0; u32 sap_type = 0; u64 doffset; - + GF_ISOSample *samp = gf_isom_get_sample_info(file, track, i+1, &di, &doffset); + if (!samp) + continue; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS traf_start = gf_isom_sample_is_fragment_start(file, track, i+1, NULL); @@ -2050,12 +2095,13 @@ #ifndef GPAC_DISABLE_ISOM_DUMP -GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump, Bool merge_vtt_cues, Bool skip_init, Bool skip_samples) +GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump, Bool merge_vtt_cues, const char *init_seg, Bool skip_samples) { char szFileName1024; GF_Err e; FILE *dump = stdout; - Bool do_close=GF_FALSE; + Bool do_close = GF_FALSE; + Bool skip_init = init_seg ? GF_TRUE : GF_FALSE; if (!file) return GF_ISOM_INVALID_FILE; if (inName) { @@ -2085,6 +2131,11 @@ u32 i; //because of dump mode we need to reopen in regular read mode to avoid mem leaks GF_ISOFile *the_file = gf_isom_open(gf_isom_get_filename(file), GF_ISOM_OPEN_READ, NULL); + if (skip_init) { +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + gf_isom_open_segment(the_file, init_seg, 0, 0, 0); +#endif + } u32 tcount = gf_isom_get_track_count(the_file); fprintf(dump, "<Tracks>\n"); @@ -2099,6 +2150,9 @@ dumper.trackID = trackID; dumper.dump_file = dump; + if (msubtype == GF_ISOM_SUBTYPE_MPEG4_CRYP) + gf_isom_get_original_format_type(the_file, i+1, 1, &msubtype); + e = GF_OK; if (mtype == GF_ISOM_MEDIA_HINT) { #ifndef GPAC_DISABLE_ISOM_HINTING @@ -2188,37 +2242,13 @@ #endif -static char *format_duration(u64 dur, u32 timescale, char *szDur) +static const char *format_duration(u64 dur, u32 timescale, char *szDur) { - u32 h, m, s, ms; if ((dur==(u64) -1) || (dur==(u32) -1)) { strcpy(szDur, "Unknown"); return szDur; } - dur = (u64) (( ((Double) (s64) dur)/timescale)*1000); - h = (u32) (dur / 3600000); - m = (u32) (dur/ 60000) - h*60; - s = (u32) (dur/1000) - h*3600 - m*60; - ms = (u32) (dur) - h*3600000 - m*60000 - s*1000; - if (h<=24) { - sprintf(szDur, "%02d:%02d:%02d.%03d", h, m, s, ms); - } else { - u32 d = (u32) (dur / 3600000 / 24); - h = (u32) (dur/3600000)-24*d; - if (d<=365) { - sprintf(szDur, "%d Days, %02d:%02d:%02d.%03d", d, h, m, s, ms); - } else { - u32 y=0; - while (d>365) { - y++; - d-=365; - if (y%4) d--; - } - sprintf(szDur, "%d Years %d Days, %02d:%02d:%02d.%03d", y, d, h, m, s, ms); - } - - } - return szDur; + return gf_format_duration(dur, timescale, szDur); } static char *format_date(u64 time, char *szTime) @@ -2246,7 +2276,10 @@ u32 type; bin128 uuid; gf_isom_get_udta_type(file, track_number, i+1, &type, &uuid); - if ((type == GF_ISOM_BOX_TYPE_META) || (type==GF_ISOM_UDTA_GPAC_SRD)) { + if ((type == GF_ISOM_BOX_TYPE_META) + || (type==GF_ISOM_UDTA_GPAC_SRD) + || (type==GF_ISOM_BOX_TYPE_KIND) + ) { count--; } } @@ -2405,7 +2438,7 @@ } for (i=0; i<count; i++) { - char szDur20; + char szDur100; u64 chapter_time; const char *name; gf_isom_get_chapter(file, 0, i+1, &chapter_time, &name); @@ -2985,9 +3018,10 @@ GF_HEVCConfig *hevccfg, *lhvccfg; GF_OperatingPointsInformation *oinf; #if !defined(GPAC_DISABLE_AV_PARSERS) - HEVCState hevc_state; - memset(&hevc_state, 0, sizeof(HEVCState)); - hevc_state.sps_active_idx = -1; + HEVCState *hvc_state; + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return; + hvc_state->sps_active_idx = -1; #endif gf_isom_get_visual_info(file, trackNum, stsd_idx, &w, &h); @@ -3018,7 +3052,7 @@ if (hevccfg) { dump_hevc_track_info(file, trackNum, hevccfg #if !defined(GPAC_DISABLE_AV_PARSERS) - , &hevc_state + , hvc_state #endif ); gf_odf_hevc_cfg_del(hevccfg); @@ -3027,12 +3061,14 @@ if (lhvccfg) { dump_hevc_track_info(file, trackNum, lhvccfg #if !defined(GPAC_DISABLE_AV_PARSERS) - , &hevc_state + , hvc_state #endif ); gf_odf_hevc_cfg_del(lhvccfg); } - +#if !defined(GPAC_DISABLE_AV_PARSERS) + gf_free(hvc_state); +#endif if (gf_isom_get_oinf_info(file, trackNum, &oinf)) { fprintf(stderr, "\n\tOperating Points Information -"); fprintf(stderr, " scalability_mask %d (", oinf->scalability_mask); @@ -3307,6 +3343,17 @@ fprintf(stderr, " - ATMOS complexity index type %d", complexity_index_type); } fprintf(stderr, "\n"); + } else if (msub_type == GF_ISOM_SUBTYPE_AC4) { + u32 sr = 0; +#ifndef GPAC_DISABLE_AV_PARSERS + GF_AC4Config *ac4 = gf_isom_ac4_config_get(file, trackNum, stsd_idx); + if (ac4) { + nb_ch = ac4->channel_count; + sr = ac4->sample_rate; + gf_odf_ac4_cfg_del(ac4); + } +#endif + fprintf(stderr, "\tAC-4 stream - Sample Rate %d - %d channel(s)\n", sr, nb_ch); } else if (msub_type == GF_ISOM_SUBTYPE_3GP_SMV) { fprintf(stderr, "\t3GPP SMV stream - Sample Rate %d - %d channel(s) %d bits per samples\n", sr, nb_ch, (u32) bps); } else if (msub_type == GF_ISOM_SUBTYPE_3GP_DIMS) { @@ -3461,7 +3508,6 @@ #endif ); gf_odf_vvc_cfg_del(vvccfg); - fprintf(stderr, "\n"); } #if !defined(GPAC_DISABLE_AV_PARSERS) if (vvc_state) gf_free(vvc_state); @@ -3671,7 +3717,7 @@ fprintf(stderr, "\n"); } - if ( gf_media_get_rfc_6381_codec_name(file, trackNum, stsd_idx, szCodec, GF_FALSE, GF_FALSE) == GF_OK) { + if (gf_media_get_rfc_6381_codec_name(file, trackNum, stsd_idx, szCodec, GF_FALSE, GF_FALSE) == GF_OK) { fprintf(stderr, "\tRFC6381 Codec Parameters: %s\n", szCodec); } } @@ -3683,8 +3729,12 @@ u32 trackNum, i, j, ts, mtype, msub_type, timescale, count, alt_group, nb_groups, nb_edits, cdur, csize; u64 time_slice, dur, size; s32 cts_shift; - char szDur50; + Bool is_extk; + char szDur100; char *lang; + GF_ISOTrackID extk_id; + u32 extk_type, extk_flags; + const char *extk_loc = NULL; if (!is_track_num) { trackNum = gf_isom_get_track_by_id(file, trackID); @@ -3697,18 +3747,31 @@ return; } - timescale = gf_isom_get_media_timescale(file, trackNum); - fprintf(stderr, "# Track %d Info - ID %d - TimeScale %d\n", trackNum, trackID, timescale); + is_extk = gf_isom_is_external_track(file, trackNum, &extk_id, &extk_type, &extk_flags, &extk_loc); + if (is_extk) { + fprintf(stderr, "# Track %d Info - ID %d\n", trackNum, trackID); + fprintf(stderr, "External Track from %s trackID %d - type %s\n", extk_loc, extk_id, gf_4cc_to_str(extk_type)); + + dur = gf_isom_get_track_duration(file, trackNum); + timescale = gf_isom_get_timescale(file); + if (((s32)dur!=-1) || (extk_flags & 1)) + fprintf(stderr, "Track Duration %s\n", format_duration(dur, timescale, szDur)); + else + fprintf(stderr, "Track Duration same as source track\n"); + } else { + timescale = gf_isom_get_media_timescale(file, trackNum); + fprintf(stderr, "# Track %d Info - ID %d - TimeScale %d\n", trackNum, trackID, timescale); - dur = gf_isom_get_media_original_duration(file, trackNum); - size = gf_isom_get_media_duration(file, trackNum); - fprintf(stderr, "Media Duration %s ", format_duration(dur, timescale, szDur)); - if (dur != size) - fprintf(stderr, " (recomputed %s)", format_duration(size, timescale, szDur)); - fprintf(stderr, "\n"); + dur = gf_isom_get_media_original_duration(file, trackNum); + size = gf_isom_get_media_duration(file, trackNum); + fprintf(stderr, "Media Duration %s ", format_duration(dur, timescale, szDur)); + if (dur != size) + fprintf(stderr, " (recomputed %s)", format_duration(size, timescale, szDur)); + fprintf(stderr, "\n"); - if (gf_isom_check_data_reference(file, trackNum, 1) != GF_OK) { - M4_LOG(GF_LOG_WARNING, ("Track uses external data reference not supported by GPAC!\n")); + if (gf_isom_check_data_reference(file, trackNum, 1) != GF_OK) { + M4_LOG(GF_LOG_WARNING, ("Track uses external data reference not supported by GPAC!\n")); + } } nb_edits = gf_isom_get_edits_count(file, trackNum); @@ -3735,26 +3798,34 @@ fprintf(stderr, "Media Language: %s (%s)\n", GetLanguage(lang), lang ); if (lang) gf_free(lang); - mtype = gf_isom_get_media_type(file, trackNum); - msub_type = gf_isom_get_mpeg4_subtype(file, trackNum, 1); - if (!msub_type) msub_type = gf_isom_get_media_subtype(file, trackNum, 1); + if (is_extk) { + mtype = extk_type; + msub_type = 0; + } else { + mtype = gf_isom_get_media_type(file, trackNum); + msub_type = gf_isom_get_mpeg4_subtype(file, trackNum, 1); + if (!msub_type) msub_type = gf_isom_get_media_subtype(file, trackNum, 1); + } + if (gf_isom_is_track_referenced(file, trackNum, GF_ISOM_REF_CHAP)) { if (gf_isom_is_video_handler_type(mtype)) fprintf(stderr, "Chapter Thumbnails\n"); else if ((mtype==GF_ISOM_MEDIA_TEXT) || (mtype==GF_ISOM_MEDIA_SUBT)) fprintf(stderr, "Chapter Labels\n"); } - fprintf(stderr, "Media Samples: %d", gf_isom_get_sample_count(file, trackNum)); - cdur = gf_isom_get_constant_sample_duration(file, trackNum); - if (cdur) { - u32 ts = timescale; - gf_media_get_reduced_frame_rate(&ts, &cdur); - if (cdur>1) - fprintf(stderr, " - CFR %f/sec", ((Float)ts)/cdur); - else - fprintf(stderr, " - CFR %u/sec", ts); + if (!is_extk) { + fprintf(stderr, "Media Samples: %d", gf_isom_get_sample_count(file, trackNum)); + cdur = gf_isom_get_constant_sample_duration(file, trackNum); + if (cdur) { + u32 ts = timescale; + gf_media_get_reduced_frame_rate(&ts, &cdur); + if (cdur>1) + fprintf(stderr, " - CFR %f/sec", ((Float)ts)/cdur); + else + fprintf(stderr, " - CFR %u/sec", ts); + } + fprintf(stderr, "\n"); } - fprintf(stderr, "\n"); u32 idx=0; while (1) { @@ -3782,8 +3853,15 @@ u16 defaultDegradationPriority; u32 frag_samples; u64 frag_duration; + u64 tfdt = 0; + u32 j, traf_count = gf_isom_segment_get_track_fragment_count(file, 1); + for (j=0; j<traf_count; j++) { + u32 ID = gf_isom_segment_get_track_fragment_decode_time(file, 1, j+1, &tfdt); + if (ID == trackID) break; + } + gf_isom_get_fragmented_samples_info(file, trackID, &frag_samples, &frag_duration); - fprintf(stderr, "Fragmented track: %d samples - Media Duration %s\n", frag_samples, format_duration(frag_duration, timescale, szDur)); + fprintf(stderr, "Fragmented track: %d samples - Media Duration %s - First TFDT "LLU"\n", frag_samples, format_duration(frag_duration, timescale, szDur), tfdt); gf_isom_get_fragment_defaults(file, trackNum, &defaultDuration, &defaultSize, &defaultDescriptionIndex, &defaultRandomAccess, &defaultPadding, &defaultDegradationPriority); @@ -3794,9 +3872,11 @@ } #endif if (full_dump) { - const char *handler_name; - gf_isom_get_handler_name(file, trackNum, &handler_name); - fprintf(stderr, "Handler name: %s\n", handler_name); + if (!is_extk) { + const char *handler_name; + gf_isom_get_handler_name(file, trackNum, &handler_name); + fprintf(stderr, "Handler name: %s\n", handler_name); + } u32 ridx=0; while (1) { @@ -3817,7 +3897,7 @@ fprintf(stderr, "\n"); } - print_udta(file, trackNum, GF_FALSE); + print_udta(file, trackNum, GF_TRUE); DumpMetaItem(file, 0, trackNum, "\tTrack Meta"); @@ -3852,6 +3932,9 @@ } } + if (is_extk) + return; + count = gf_isom_get_sample_description_count(file, trackNum); if (!full_dump && (count>1)) { fprintf(stderr, "Sample Descriptions: %d\n\tuse MP4Box -info %d %s to list all sample descriptions\n", count, trackID, gf_isom_get_filename(file)); @@ -3860,6 +3943,7 @@ for (i=0; i<count; i++) { DumpStsdInfo(file, trackNum, full_dump, dump_m4sys, mtype, i+1, &is_od_track); } + fprintf(stderr, "\n"); switch (gf_isom_has_sync_points(file, trackNum)) { case 0: @@ -3976,7 +4060,7 @@ const u8 *data; u64 create, modif; Bool has_meta_tags = GF_FALSE; - char szDur50; + char szDur100; DumpMetaItem(file, 1, 0, "# File Meta"); if (!gf_isom_has_movie(file)) { @@ -4106,7 +4190,7 @@ GF_BitStream *bs = gf_bs_new(s->data, s->dataLength, GF_BITSTREAM_READ); GF_TextSample *txt = gf_isom_parse_text_sample(bs); if (txt) { - fprintf(stderr, "\t#%d - %s - \"%s\"\n", i+1, format_duration(s->DTS, 1000, szDur), txt->text); + fprintf(stderr, "\t#%d - %s - \"%s\"\n", i+1, format_duration(s->DTS, 1000, szDur), txt->text ? txt->text : ""); gf_isom_delete_text_sample(txt); } gf_bs_del(bs); @@ -4122,16 +4206,23 @@ i=0; while (1) { u32 int_val2, flags, itype; + const char *mean=NULL, *name=NULL; GF_ISOiTunesTag tag; u64 int_val; + u32 locale=0; s32 tag_idx; - GF_Err e = gf_isom_apple_enum_tag(file, i, &tag, &data, &data_len, &int_val, &int_val2, &flags); + GF_Err e = gf_isom_apple_enum_tag_ex(file, i, &tag, &data, &data_len, &int_val, &int_val2, &flags, &mean, &name, &locale); if (e) break; i++; tag_idx = gf_itags_find_by_itag(tag); if (tag_idx<0) { - fprintf(stderr, "\t%s: %s\n", gf_4cc_to_str(tag), data); + if (mean && name) + fprintf(stderr, "\t%s (%s): %s\n", name, mean, data); + else if (mean || name) + fprintf(stderr, "\t%s: %s\n", name ? name : mean, data); + else + fprintf(stderr, "\t%s: %s\n", gf_4cc_to_str(tag), data); continue; } fprintf(stderr, "\t%s: ", gf_itags_get_name(tag_idx) ); @@ -4190,7 +4281,9 @@ case GF_QT_KEY_JPEG: fprintf(stderr, "JPG Image (%d bytes)", key.value.data.data_len); break; case GF_QT_KEY_BMP: fprintf(stderr, "BMP Image (%d bytes)", key.value.data.data_len); break; case GF_QT_KEY_UTF8: - case GF_QT_KEY_UTF8_SORT: fprintf(stderr, "%s", key.value.string); break; + case GF_QT_KEY_UTF8_SORT: + fprintf(stderr, "%s", key.value.string ? key.value.string : ""); + break; case GF_QT_KEY_FLOAT: case GF_QT_KEY_DOUBLE: @@ -4346,6 +4439,7 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Program number %d found - %d streams:\n", prog->number, count)); for (i=0; i<count; i++) { GF_M2TS_ES *es = gf_list_get(prog->streams, i); + if (!es) continue; if (es->pid == prog->pmt_pid) { GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tPID %d: Program Map Table\n", es->pid)); } else { @@ -4480,7 +4574,7 @@ fprintf(dumper->pes_out_nhml, "baseMediaFile=\"%s\" ", dumper->dump); fprintf(dumper->pes_out_nhml, "inRootOD=\"yes\">\n"); } - gf_sl_depacketize(esd->slConfig, &header, sl_pck->data, sl_pck->data_len, &header_len); + gf_odf_sl_depacketize(esd->slConfig, &header, sl_pck->data, sl_pck->data_len, &header_len); gf_fwrite(sl_pck->data+header_len, sl_pck->data_len-header_len, dumper->pes_out); fprintf(dumper->pes_out_nhml, "<NHNTSample DTS=\""LLD"\" dataLength=\"%d\" isRAP=\"%s\"/>\n", header.decodingTimeStamp, sl_pck->data_len-header_len, (header.randomAccessPointFlag?"yes":"no")); } @@ -4502,7 +4596,7 @@ FILE *src; if (!prog_num && !out_name) { - fprintf(stderr, "No program number nor output filename specified. No timestamp file will be generated."); + fprintf(stderr, "No program number nor output filename specified. No timestamp file will be generated\n."); } src = gf_fopen(mpeg2ts_file, "rb"); @@ -4799,7 +4893,7 @@ } if (rep->segment_base) segment_base=GF_TRUE; - e = gf_mpd_resolve_url(mpd, rep, as, period, mpd_src, 0, GF_MPD_RESOLVE_URL_INIT, 0, 0, &seg_url, &out_range_start, &out_range_end, &segment_duration, &is_in_base_url, NULL, NULL, NULL); + e = gf_mpd_resolve_url(mpd, rep, as, period, mpd_src, 0, GF_MPD_RESOLVE_URL_INIT, 0, 0, &seg_url, &out_range_start, &out_range_end, &segment_duration, &is_in_base_url, NULL, NULL, NULL, -1); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error resolving init segment name : %s\n", gf_error_to_string(e))); continue; @@ -4827,7 +4921,7 @@ if (segment_base) continue; while (1) { - e = gf_mpd_resolve_url(mpd, rep, as, period, mpd_src, 0, GF_MPD_RESOLVE_URL_MEDIA, seg_idx, 0, &seg_url, &out_range_start, &out_range_end, &segment_duration, NULL, NULL, NULL, NULL); + e = gf_mpd_resolve_url(mpd, rep, as, period, mpd_src, 0, GF_MPD_RESOLVE_URL_MEDIA, seg_idx, 0, &seg_url, &out_range_start, &out_range_end, &segment_duration, NULL, NULL, NULL, NULL, -1); if (e) { if (e<0) { GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error resolving segment name : %s\n", gf_error_to_string(e)));
View file
gpac-2.4.0.tar.gz/applications/mp4box/fileimport.c -> gpac-26.02.0.tar.gz/applications/mp4box/fileimport.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / mp4box application @@ -82,7 +82,7 @@ size = gf_base64_decode((u8 *)src, size, data, size); } else if (is_string) { data = (u8 *) src; - size = (u32) strlen(src)+1; + size = (u32) strlen(src); is_box_array = 0; } else { GF_Err e = gf_file_load_data(src, (u8 **) &data, &size); @@ -107,7 +107,7 @@ extern u32 swf_flags; #endif -extern Float swf_flatten_angle; +extern Double swf_flatten_angle; extern Bool keep_sys_tracks; extern u32 fs_dump_flags; @@ -118,39 +118,42 @@ GF_Err e; u32 i, cur=1; Bool found; - GF_MediaImporter import; - memset(&import, 0, sizeof(GF_MediaImporter)); + GF_MediaImporter *import = gf_malloc(sizeof(GF_MediaImporter)); + if (!import) return GF_OUT_OF_MEM; + memset(import, 0, sizeof(GF_MediaImporter)); if (!track_id->type) - import.trackID = track_id->ID_or_num; + import->trackID = track_id->ID_or_num; - import.in_name = inName; - import.flags = GF_IMPORT_PROBE_ONLY; - import.print_stats_graph = fs_dump_flags; + import->in_name = inName; + import->flags = GF_IMPORT_PROBE_ONLY; + import->print_stats_graph = fs_dump_flags; - e = gf_media_import(&import); + e = gf_media_import(import); if (e) { M4_LOG(GF_LOG_ERROR, ("Error probing file %s: %s\n", inName, gf_error_to_string(e))); + gf_free(import); return e; } if (track_id->ID_or_num) { fprintf(stderr, "Import probing results for %s track %s%d:\n", inName, track_id->type ? "#" : "ID ", track_id->ID_or_num); } else { fprintf(stderr, "Import probing results for %s:\n", inName); - if (!import.nb_tracks) { + if (!import->nb_tracks) { M4_LOG(GF_LOG_WARNING, ("File has no selectable tracks\n")); + gf_free(import); return GF_OK; } - fprintf(stderr, "File has %d tracks\n", import.nb_tracks); + fprintf(stderr, "File has %d tracks\n", import->nb_tracks); } - if (import.probe_duration) { - fprintf(stderr, "Duration: %g s\n", (Double) (import.probe_duration/1000.0)); + if (import->probe_duration) { + fprintf(stderr, "Duration: %g s\n", (Double) (import->probe_duration/1000.0)); } found = 0; - for (i=0; i<import.nb_tracks; i++) { - u32 stype = import.tk_infoi.stream_type; + for (i=0; i<import->nb_tracks; i++) { + u32 stype = import->tk_infoi.stream_type; switch (track_id->type) { case 0: //by trackID - if (track_id->ID_or_num && (track_id->ID_or_num != import.tk_infoi.track_num) ) continue; + if (track_id->ID_or_num && (track_id->ID_or_num != import->tk_infoi.track_num) ) continue; break; case 1: //by track nul if (track_id->ID_or_num && (track_id->ID_or_num != i+1) ) continue; @@ -168,15 +171,15 @@ if ((track_id->type>1) && track_id->ID_or_num && (track_id->ID_or_num<cur)) continue; cur++; - if (!track_id->ID_or_num) fprintf(stderr, "- Track %d type: ", import.tk_infoi.track_num); + if (!track_id->ID_or_num) fprintf(stderr, "- Track %d type: ", import->tk_infoi.track_num); else fprintf(stderr, "Track type: "); switch (stype) { case GF_STREAM_VISUAL: - if (import.tk_infoi.media_subtype == GF_ISOM_MEDIA_AUXV) fprintf(stderr, "Auxiliary Video"); - else if (import.tk_infoi.media_subtype == GF_ISOM_MEDIA_PICT) fprintf(stderr, "Picture Sequence"); + if (import->tk_infoi.media_subtype == GF_ISOM_MEDIA_AUXV) fprintf(stderr, "Auxiliary Video"); + else if (import->tk_infoi.media_subtype == GF_ISOM_MEDIA_PICT) fprintf(stderr, "Picture Sequence"); else fprintf(stderr, "Video"); - if (import.tk_infoi.video_info.temporal_enhancement) fprintf(stderr, " Temporal Enhancement"); + if (import->tk_infoi.video_info.temporal_enhancement) fprintf(stderr, " Temporal Enhancement"); break; case GF_STREAM_AUDIO: fprintf(stderr, "Audio"); @@ -194,41 +197,41 @@ fprintf(stderr, "Metadata"); break; default: - fprintf(stderr, "Other (%s)", gf_4cc_to_str(import.tk_infoi.stream_type)); + fprintf(stderr, "Other (%s)", gf_4cc_to_str(import->tk_infoi.stream_type)); break; } - if (import.tk_infoi.codecid) fprintf(stderr, " - Codec %s", gf_codecid_name(import.tk_infoi.codecid)); + if (import->tk_infoi.codecid) fprintf(stderr, " - Codec %s", gf_codecid_name(import->tk_infoi.codecid)); - if (import.tk_infoi.lang) fprintf(stderr, " lang %s", gf_4cc_to_str(import.tk_infoi.lang)); + if (import->tk_infoi.lang) fprintf(stderr, " lang %s", gf_4cc_to_str(import->tk_infoi.lang)); - if (import.tk_infoi.mpeg4_es_id) fprintf(stderr, " MPEG-4 ESID %d", import.tk_infoi.mpeg4_es_id); + if (import->tk_infoi.mpeg4_es_id) fprintf(stderr, " MPEG-4 ESID %d", import->tk_infoi.mpeg4_es_id); - if (import.tk_infoi.prog_num) { - if (!import.nb_progs) { - fprintf(stderr, " Program %d", import.tk_infoi.prog_num); + if (import->tk_infoi.prog_num) { + if (!import->nb_progs) { + fprintf(stderr, " Program %d", import->tk_infoi.prog_num); } else { u32 j; - for (j=0; j<import.nb_progs; j++) { - if (import.tk_infoi.prog_num != import.pg_infoj.number) continue; - fprintf(stderr, " Program %s", import.pg_infoj.name); + for (j=0; j<import->nb_progs; j++) { + if (import->tk_infoi.prog_num != import->pg_infoj.number) continue; + fprintf(stderr, " Program %s", import->pg_infoj.name); break; } } } fprintf(stderr, "\n"); - if (import.tk_infoi.video_info.width && import.tk_infoi.video_info.height + if (import->tk_infoi.video_info.width && import->tk_infoi.video_info.height ) { - fprintf(stderr, "\tSize %dx%d", import.tk_infoi.video_info.width, import.tk_infoi.video_info.height); - if (import.tk_infoi.video_info.FPS) fprintf(stderr, " @ %g FPS", import.tk_infoi.video_info.FPS); - if (import.tk_infoi.stream_type==GF_STREAM_VISUAL) { - if (import.tk_infoi.video_info.par) fprintf(stderr, " PAR: %d:%d", import.tk_infoi.video_info.par >> 16, import.tk_infoi.video_info.par & 0xFFFF); + fprintf(stderr, "\tSize %dx%d", import->tk_infoi.video_info.width, import->tk_infoi.video_info.height); + if (import->tk_infoi.video_info.FPS) fprintf(stderr, " @ %g FPS", import->tk_infoi.video_info.FPS); + if (import->tk_infoi.stream_type==GF_STREAM_VISUAL) { + if (import->tk_infoi.video_info.par) fprintf(stderr, " PAR: %d:%d", import->tk_infoi.video_info.par >> 16, import->tk_infoi.video_info.par & 0xFFFF); } fprintf(stderr, "\n"); } - else if ((import.tk_infoi.stream_type==GF_STREAM_AUDIO) && import.tk_infoi.audio_info.sample_rate) { - fprintf(stderr, "\tSampleRate %d - %d channels\n", import.tk_infoi.audio_info.sample_rate, import.tk_infoi.audio_info.nb_channels); + else if ((import->tk_infoi.stream_type==GF_STREAM_AUDIO) && import->tk_infoi.audio_info.sample_rate) { + fprintf(stderr, "\tSampleRate %d - %d channels\n", import->tk_infoi.audio_info.sample_rate, import->tk_infoi.audio_info.nb_channels); } if (track_id->ID_or_num || (track_id->type>1)) { @@ -239,10 +242,11 @@ fprintf(stderr, "\n"); if (!found && track_id->ID_or_num) { - M4_LOG(GF_LOG_ERROR, ("Cannot find track %d in file\n", track_id->ID_or_num)); + M4_LOG(GF_LOG_ERROR, ("Cannot find track %u in file\n", track_id->ID_or_num)); return GF_BAD_PARAM; } M4_LOG(GF_LOG_INFO, ("For more details, use `gpac -i %s inspect:deep:analyze=on|bs`\n", gf_file_basename(inName))); + gf_free(import); return GF_OK; } @@ -288,7 +292,7 @@ gf_isom_reset_sample_count(NULL); gf_isom_set_traf_mss_timeext(NULL, 0, 0, 0); gf_isom_get_next_moof_number(NULL); - gf_isom_set_fragment_reference_time(NULL, 0, 0, 0); + gf_isom_set_fragment_reference_time(NULL, 0, 0, 0, 0); #endif //this one is not tested in master due to old-arch compat, to remove when we enable tests without old-arch gf_isom_get_audio_layout(file, track, 1, &layout); @@ -504,41 +508,45 @@ gf_bs_skip_bytes(bs, nal_size-1); } else if (codec_id==GF_CODECID_AV1) { - u64 obu_size, obu_start = (u32) gf_bs_get_position(bs); - u32 obu_hdr_size; - ObuType obu_type; - Bool obu_extension_flag, obu_has_size_field; - u8 temporal_id, spatial_id; - - gf_av1_parse_obu_header(bs, &obu_type, &obu_extension_flag, &obu_has_size_field, &temporal_id, &spatial_id); - obu_hdr_size = (u32) (gf_bs_get_position(bs) - obu_start); + #ifndef GPAC_DISABLE_AV_PARSERS + u64 obu_size, obu_start = (u32) gf_bs_get_position(bs); + u32 obu_hdr_size; + ObuType obu_type; + Bool obu_extension_flag, obu_has_size_field; + u8 temporal_id, spatial_id; - if (obu_has_size_field) { - obu_size = (u32)gf_av1_leb128_read(bs, NULL); + gf_av1_parse_obu_header(bs, &obu_type, &obu_extension_flag, &obu_has_size_field, &temporal_id, &spatial_id); obu_hdr_size = (u32) (gf_bs_get_position(bs) - obu_start); - } else { - obu_size = samp->dataLength - (u32) gf_bs_get_position(bs); - } - obu_size += obu_hdr_size; - if (obu_type==OBU_METADATA) { - gf_bs_seek(bs, obu_start+obu_hdr_size); - u32 metadata_type = (u32)gf_av1_leb128_read(bs, NULL); - if (metadata_type==OBU_METADATA_TYPE_ITUT_T35) { - //cf issue #2549 - if (gf_bs_read_u8(bs)==0xB5) { - const u8 rpu_hdr = {0x00, 0x3B, 0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08}; - const u32 rpu_hdr_len = sizeof (rpu_hdr); - u32 pos = (u32) gf_bs_get_position(bs); - u8 *t35_start = samp->data + pos; - if (!memcmp(t35_start, rpu_hdr, rpu_hdr_len)) { - _dovi.rpu_present_flag = 1; + + if (obu_has_size_field) { + obu_size = (u32)gf_av1_leb128_read(bs, NULL); + obu_hdr_size = (u32) (gf_bs_get_position(bs) - obu_start); + } else { + obu_size = samp->dataLength - (u32) gf_bs_get_position(bs); + } + obu_size += obu_hdr_size; + if (obu_type==OBU_METADATA) { + gf_bs_seek(bs, obu_start+obu_hdr_size); + ObuMetadataType metadata_type = (ObuMetadataType)gf_av1_leb128_read(bs, NULL); + if (metadata_type == OBU_METADATA_TYPE_ITUT_T35) { + //cf issue #2549 + if (gf_bs_read_u8(bs)==0xB5) { + const u8 rpu_hdr = {0x00, 0x3B, 0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08}; + const u32 rpu_hdr_len = sizeof (rpu_hdr); + u32 pos = (u32) gf_bs_get_position(bs); + u8 *t35_start = samp->data + pos; + if (!memcmp(t35_start, rpu_hdr, rpu_hdr_len)) { + _dovi.rpu_present_flag = 1; + } } } + } else if (obu_type<=OBU_TILE_LIST) { + _dovi.bl_present_flag = 1; } - } else if (obu_type<=OBU_TILE_LIST) { - _dovi.bl_present_flag = 1; - } - gf_bs_seek(bs, obu_start+obu_size); + gf_bs_seek(bs, obu_start+obu_size); + #else + return GF_NOT_SUPPORTED; + #endif } } gf_bs_del(bs); @@ -572,6 +580,8 @@ u32 movie_ts = gf_isom_get_timescale(dest); u32 media_ts = gf_isom_get_media_timescale(dest, track); u64 media_dur = gf_isom_get_media_duration(dest, track); + if (!movie_ts) movie_ts = 600; + if (!media_ts) media_ts = movie_ts; while (edits) { GF_Err e; @@ -687,6 +697,7 @@ } static const char *videofmt_names = { "component", "pal", "ntsc", "secam", "mac", "undef"}; +static GF_Err apply_timestamps(GF_ISOFile *file, GF_ISOTrackID trackID, const char *timestamp_source); GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, GF_FilterSession *fsess, char **mux_args_if_first_pass, char **mux_sid_if_first_pass, u32 tk_idx) @@ -702,9 +713,8 @@ u32 tmcd_track = 0, neg_ctts_mode=0; Bool keep_audelim = GF_FALSE; u32 print_stats_graph=fs_dump_flags; - GF_MediaImporter import; char *ext, *final_name=NULL, *handler_name, *rvc_config, *chapter_name; - GF_List *kinds; + GF_List *kinds = NULL; GF_TextFlagsMode txt_mode = GF_ISOM_TEXT_FLAGS_OVERWRITE; u8 max_layer_id_plus_one, max_temporal_id_plus_one; u32 clap_wn, clap_wd, clap_hn, clap_hd, clap_hon, clap_hod, clap_von, clap_vod; @@ -735,6 +745,8 @@ char *edits = NULL; const char *fail_msg = NULL; char *hdr_file=NULL; + char *first_ext=NULL; + char *timestamp_source=NULL; Bool set_ccst=GF_FALSE; Bool has_last_sample_dur=GF_FALSE; u32 fake_import = 0; @@ -749,9 +761,12 @@ u32 tkgp_type=0; s32 tkgp_id=0; Bool tkgp_add = GF_TRUE; + u32 is_extk = 0; u32 set_tk_idx=0; u32 *reorder_tk_ids = NULL; u32 reorder_tk_ids_count = 0; + Bool has_non_extk_opt=GF_FALSE; + GF_MediaImporter *import = NULL; dv_profile0 = 0; rvc_predefined = 0; @@ -770,8 +785,12 @@ import_flags = 0; fake_import = 1; } - - memset(&import, 0, sizeof(GF_MediaImporter)); + import = gf_malloc(sizeof(GF_MediaImporter)); + if (!import) { + e = GF_OUT_OF_MEM; + goto exit; + } + memset(import, 0, sizeof(GF_MediaImporter)); final_name = gf_strdup(inName); #ifdef WIN32 @@ -813,24 +832,28 @@ if (!ext) ext = gf_url_colon_suffix(final_name, '='); char c_sep = ext ? ext0 : 0; if (ext) ext0 = 0; + + char *frag = strrchr(final_name, '#'); + if (frag) frag0 = 0; + if (!strlen(final_name) || !strcmp(final_name, "self")) { fake_import = 2; src_is_isom = GF_TRUE; } - char *frag = strrchr(final_name, '#'); - if (frag) frag0 = 0; - if (gf_isom_probe_file(final_name)) + else if (gf_isom_probe_file(final_name)) src_is_isom = GF_TRUE; + if (frag) frag0 = '#'; if (ext) ext0 = c_sep; +reparse_opts: ext = gf_url_colon_suffix(final_name, '='); #define GOTO_EXIT(_msg) if (e) { fail_msg = _msg; goto exit; } -#define CHECK_FAKEIMPORT(_opt) if (fake_import) { M4_LOG(GF_LOG_ERROR, ("Option %s not available for self-reference import\n", _opt)); e = GF_BAD_PARAM; goto exit; } -#define CHECK_FAKEIMPORT_2(_opt) if (fake_import==1) { M4_LOG(GF_LOG_ERROR, ("Option %s not available for self-reference import\n", _opt)); e = GF_BAD_PARAM; goto exit; } +#define CHECK_FAKEIMPORT(_opt) if (fake_import) { M4_LOG(GF_LOG_ERROR, ("Option %s not available for cat or self-reference import\n", _opt)); e = GF_BAD_PARAM; goto exit; } +#define CHECK_FAKEIMPORT_2(_opt) if (fake_import==1) { M4_LOG(GF_LOG_ERROR, ("Option %s not available for cat or self-reference import\n", _opt)); e = GF_BAD_PARAM; goto exit; } handler_name = NULL; @@ -845,15 +868,15 @@ CHECK_FAKEIMPORT("dur") if (strchr(ext, '-')) { - import.duration.num = parse_s32(ext+5, "dur"); - import.duration.den = 1; + import->duration.num = parse_s32(ext+5, "dur"); + import->duration.den = 1; } else { - gf_parse_frac(ext+5, &import.duration); + gf_parse_frac(ext+5, &import->duration); } } else if (!strnicmp(ext+1, "start=", 6)) { CHECK_FAKEIMPORT("start") - import.start_time = atof(ext+7); + import->start_time = atof(ext+7); } else if (!strnicmp(ext+1, "lang=", 5)) { /* prevent leak if param is set twice */ @@ -923,11 +946,11 @@ CHECK_FAKEIMPORT("ext") /*extensions begin with '.'*/ if (*(ext+5) == '.') - import.force_ext = gf_strdup(ext+5); + import->force_ext = gf_strdup(ext+5); else { - import.force_ext = gf_calloc(1+strlen(ext+5)+1, 1); - import.force_ext0 = '.'; - strcat(import.force_ext+1, ext+5); + import->force_ext = gf_calloc(1+strlen(ext+5)+1, 1); + import->force_ext0 = '.'; + strcat(import->force_ext+1, ext+5); } } else if (!strnicmp(ext+1, "hdlr=", 5)) handler = GF_4CC(ext6, ext7, ext8, ext9); @@ -987,7 +1010,14 @@ else if (!stricmp(ext+1, "nosei")) { CHECK_FAKEIMPORT("nosei") import_flags |= GF_IMPORT_NO_SEI; } else if (!stricmp(ext+1, "svc") || !stricmp(ext+1, "lhvc") ) { CHECK_FAKEIMPORT("svc/lhvc") import_flags |= GF_IMPORT_SVC_EXPLICIT; } else if (!stricmp(ext+1, "nosvc") || !stricmp(ext+1, "nolhvc")) { CHECK_FAKEIMPORT("nosvc/nolhvc") import_flags |= GF_IMPORT_SVC_NONE; } - + else if (!stricmp(ext+1, "extk")) { + if (!src_is_isom) { + M4_LOG(GF_LOG_ERROR, ("extk option can only be used with ISOBMF/QT files\n")); + e = GF_BAD_PARAM; + goto exit; + } + if (!is_extk) is_extk = 1; + } /*split SVC layers*/ else if (!strnicmp(ext+1, "svcmode=", 8) || !strnicmp(ext+1, "lhvcmode=", 9)) { char *mode = ext+9; @@ -1023,7 +1053,7 @@ else if (!stricmp(ext+1, "subsamples")) { CHECK_FAKEIMPORT("subsamples") import_flags |= GF_IMPORT_SET_SUBSAMPLES; } else if (!stricmp(ext+1, "deps")) { CHECK_FAKEIMPORT("deps") import_flags |= GF_IMPORT_SAMPLE_DEPS; } else if (!stricmp(ext+1, "ccst")) { CHECK_FAKEIMPORT("ccst") set_ccst = GF_TRUE; } - else if (!stricmp(ext+1, "alpha")) { CHECK_FAKEIMPORT("alpha") import.is_alpha = GF_TRUE; } + else if (!stricmp(ext+1, "alpha")) { CHECK_FAKEIMPORT("alpha") import->is_alpha = GF_TRUE; } else if (!stricmp(ext+1, "forcesync")) { CHECK_FAKEIMPORT("forcesync") import_flags |= GF_IMPORT_FORCE_SYNC; } else if (!stricmp(ext+1, "xps_inband")) { CHECK_FAKEIMPORT("xps_inband") xps_inband = 1; } else if (!stricmp(ext+1, "xps_inbandx")) { CHECK_FAKEIMPORT("xps_inbandx") xps_inband = 2; } @@ -1096,7 +1126,7 @@ rvc_config = gf_strdup(ext+5); } } - else if (!strnicmp(ext+1, "fmt=", 4)) import.streamFormat = gf_strdup(ext+5); + else if (!strnicmp(ext+1, "fmt=", 4)) import->streamFormat = gf_strdup(ext+5); else if (!strnicmp(ext+1, "profile=", 8)) { if (!stricmp(ext+9, "high444")) profile = 244; @@ -1126,8 +1156,8 @@ else if (!strnicmp(ext+1, "novpsext", 8)) { CHECK_FAKEIMPORT("novpsext") import_flags |= GF_IMPORT_NO_VPS_EXTENSIONS; } else if (!strnicmp(ext+1, "keepav1t", 8)) { CHECK_FAKEIMPORT("keepav1t") import_flags |= GF_IMPORT_KEEP_AV1_TEMPORAL_OBU; } - else if (!strnicmp(ext+1, "font=", 5)) { CHECK_FAKEIMPORT("font") import.fontName = gf_strdup(ext+6); } - else if (!strnicmp(ext+1, "size=", 5)) { CHECK_FAKEIMPORT("size") import.fontSize = parse_u32(ext+6, "size"); } + else if (!strnicmp(ext+1, "font=", 5)) { CHECK_FAKEIMPORT("font") import->fontName = gf_strdup(ext+6); } + else if (!strnicmp(ext+1, "size=", 5)) { CHECK_FAKEIMPORT("size") import->fontSize = parse_u32(ext+6, "size"); } else if (!strnicmp(ext+1, "text_layout=", 12)) { if ( sscanf(ext+13, "%dx%dx%dx%d", &txtw, &txth, &txtx, &txty)==4) { text_layout = 1; @@ -1138,17 +1168,17 @@ } #ifndef GPAC_DISABLE_SWF_IMPORT - else if (!stricmp(ext+1, "swf-global")) { CHECK_FAKEIMPORT("swf-global") import.swf_flags |= GF_SM_SWF_STATIC_DICT; } - else if (!stricmp(ext+1, "swf-no-ctrl")) { CHECK_FAKEIMPORT("swf-no-ctrl") import.swf_flags &= ~GF_SM_SWF_SPLIT_TIMELINE; } - else if (!stricmp(ext+1, "swf-no-text")) { CHECK_FAKEIMPORT("swf-no-text") import.swf_flags |= GF_SM_SWF_NO_TEXT; } - else if (!stricmp(ext+1, "swf-no-font")) { CHECK_FAKEIMPORT("swf-no-font") import.swf_flags |= GF_SM_SWF_NO_FONT; } - else if (!stricmp(ext+1, "swf-no-line")) { CHECK_FAKEIMPORT("swf-no-line") import.swf_flags |= GF_SM_SWF_NO_LINE; } - else if (!stricmp(ext+1, "swf-no-grad")) { CHECK_FAKEIMPORT("swf-no-grad") import.swf_flags |= GF_SM_SWF_NO_GRADIENT; } - else if (!stricmp(ext+1, "swf-quad")) { CHECK_FAKEIMPORT("swf-quad") import.swf_flags |= GF_SM_SWF_QUAD_CURVE; } - else if (!stricmp(ext+1, "swf-xlp")) { CHECK_FAKEIMPORT("swf-xlp") import.swf_flags |= GF_SM_SWF_SCALABLE_LINE; } - else if (!stricmp(ext+1, "swf-ic2d")) { CHECK_FAKEIMPORT("swf-ic2d") import.swf_flags |= GF_SM_SWF_USE_IC2D; } - else if (!stricmp(ext+1, "swf-same-app")) { CHECK_FAKEIMPORT("swf-same-app") import.swf_flags |= GF_SM_SWF_REUSE_APPEARANCE; } - else if (!strnicmp(ext+1, "swf-flatten=", 12)) { CHECK_FAKEIMPORT("swf-flatten") import.swf_flatten_angle = (Float) atof(ext+13); } + else if (!stricmp(ext+1, "swf-global")) { CHECK_FAKEIMPORT("swf-global") import->swf_flags |= GF_SM_SWF_STATIC_DICT; } + else if (!stricmp(ext+1, "swf-no-ctrl")) { CHECK_FAKEIMPORT("swf-no-ctrl") import->swf_flags &= ~GF_SM_SWF_SPLIT_TIMELINE; } + else if (!stricmp(ext+1, "swf-no-text")) { CHECK_FAKEIMPORT("swf-no-text") import->swf_flags |= GF_SM_SWF_NO_TEXT; } + else if (!stricmp(ext+1, "swf-no-font")) { CHECK_FAKEIMPORT("swf-no-font") import->swf_flags |= GF_SM_SWF_NO_FONT; } + else if (!stricmp(ext+1, "swf-no-line")) { CHECK_FAKEIMPORT("swf-no-line") import->swf_flags |= GF_SM_SWF_NO_LINE; } + else if (!stricmp(ext+1, "swf-no-grad")) { CHECK_FAKEIMPORT("swf-no-grad") import->swf_flags |= GF_SM_SWF_NO_GRADIENT; } + else if (!stricmp(ext+1, "swf-quad")) { CHECK_FAKEIMPORT("swf-quad") import->swf_flags |= GF_SM_SWF_QUAD_CURVE; } + else if (!stricmp(ext+1, "swf-xlp")) { CHECK_FAKEIMPORT("swf-xlp") import->swf_flags |= GF_SM_SWF_SCALABLE_LINE; } + else if (!stricmp(ext+1, "swf-ic2d")) { CHECK_FAKEIMPORT("swf-ic2d") import->swf_flags |= GF_SM_SWF_USE_IC2D; } + else if (!stricmp(ext+1, "swf-same-app")) { CHECK_FAKEIMPORT("swf-same-app") import->swf_flags |= GF_SM_SWF_REUSE_APPEARANCE; } + else if (!strnicmp(ext+1, "swf-flatten=", 12)) { CHECK_FAKEIMPORT("swf-flatten") import->swf_flatten_angle = (Float) atof(ext+13); } #endif else if (!strnicmp(ext+1, "kind=", 5)) { @@ -1184,6 +1214,9 @@ else if (!strnicmp(ext+1, "rate=", 5)) { force_rate = parse_s32(ext+6, "rate"); } + else if (!strnicmp(ext+1, "times=", 6)) { + timestamp_source = gf_strdup(ext+7); + } else if (!stricmp(ext+1, "stats") || !stricmp(ext+1, "fstat")) print_stats_graph |= 1; else if (!stricmp(ext+1, "graph") || !stricmp(ext+1, "graph")) @@ -1197,16 +1230,16 @@ if (opt_dst) opt_dst0 = 0; if (fchain) fchain0 = 0; - if (opt_src) import.filter_src_opts = opt_src+6; - if (opt_dst) import.filter_dst_opts = opt_dst+6; + if (opt_src) import->filter_src_opts = opt_src+6; + if (opt_dst) import->filter_dst_opts = opt_dst+6; if (fchain) { //check for old syntax (0.9->1.0) :@@ if (fchain2=='@') { - import.filter_chain = fchain + 3; - import.is_chain_old_syntax = GF_TRUE; + import->filter_chain = fchain + 3; + import->is_chain_old_syntax = GF_TRUE; } else { - import.filter_chain = fchain + 2; - import.is_chain_old_syntax = GF_FALSE; + import->filter_chain = fchain + 2; + import->is_chain_old_syntax = GF_FALSE; } } @@ -1217,13 +1250,15 @@ else if (!strnicmp(ext+1, "asemode=", 8)){ char *mode = ext+9; if (!stricmp(mode, "v0-bs")) - import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS; + import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS; + else if (!stricmp(mode, "v0-s")) + import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_DEFAULT; else if (!stricmp(mode, "v0-2")) - import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2; + import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2; else if (!stricmp(mode, "v1")) - import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG; + import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG; else if (!stricmp(mode, "v1-qt")) - import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF; + import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF; else M4_LOG(GF_LOG_ERROR, ("Unrecognized audio sample entry mode %s, ignoring\n", mode)); } @@ -1332,7 +1367,7 @@ } else if (!strnicmp(ext+1, "tc=", 3)) { char *tc_str = ext+4; - + if (tc_str0 == 'd') { tc_drop_frame=GF_TRUE; tc_str+=1; @@ -1375,7 +1410,7 @@ } } else if (!strnicmp(ext+1, "ID=", 3)) { - import.target_trackID = (u32) parse_u32(ext+4, "ID"); + import->target_trackID = (u32) parse_u32(ext+4, "ID"); } else if (!strnicmp(ext+1, "tkgp=", 5)) { e = GF_BAD_PARAM; @@ -1413,12 +1448,23 @@ if (!mp4box_check_isom_fileopt(opt)) { M4_LOG(GF_LOG_ERROR, ("\t! Import option `%s` not available for ISOBMFF/QT sources, ignoring !\n", ext+1)); } + if ((!has_non_extk_opt || (is_extk==2)) && mp4box_check_non_extk_fileopt(opt)) { + has_non_extk_opt = GF_TRUE; + if (is_extk==2) { + M4_LOG(GF_LOG_ERROR, ("\t! Import option `%s` not allowed for external ISOBMFF tracks !\n", ext+1)); + e = GF_BAD_PARAM; + GOTO_EXIT(NULL) + } + } if (sep_eq) sep_eq0 = '='; } if (ext2) ext20 = ':'; - ext0 = 0; + if (!first_ext) { + first_ext = ext; + ext0 = 0; + } /* restart from where we stopped * if we didn't stop (ext2 null) then the end has been reached @@ -1429,7 +1475,7 @@ /*check duration import (old syntax)*/ ext = strrchr(final_name, '%'); if (ext && gf_sys_old_arch_compat()) { - if (gf_parse_frac(ext+1, &import.duration)) + if (gf_parse_frac(ext+1, &import->duration)) ext0 = 0; else ext = NULL; @@ -1459,9 +1505,9 @@ if (sep) sep0 = 0; //we have a fragment, we need to check if the track or the program is present in source - import.in_name = final_name; - import.flags = GF_IMPORT_PROBE_ONLY; - e = gf_media_import(&import); + import->in_name = final_name; + import->flags = GF_IMPORT_PROBE_ONLY; + e = gf_media_import(import); GOTO_EXIT("importing import"); if (!strnicmp(ext, "audio", 5)) do_audio = 1; @@ -1471,9 +1517,9 @@ else if (!strnicmp(ext, "trackID=", 8)) track_id = parse_u32(&ext8, "trackID"); else if (!strnicmp(ext, "PID=", 4)) track_id = parse_u32(&ext4, "ID"); else if (!strnicmp(ext, "program=", 8)) { - for (i=0; i<import.nb_progs; i++) { - if (!stricmp(import.pg_infoi.name, ext+8)) { - prog_id = import.pg_infoi.number; + for (i=0; i<import->nb_progs; i++) { + if (!stricmp(import->pg_infoi.name, ext+8)) { + prog_id = import->pg_infoi.number; do_all = 0; break; } @@ -1488,25 +1534,25 @@ //figure out trackID if (do_audio || do_video || do_auxv || do_pict || track_id) { Bool found = track_id ? GF_FALSE : GF_TRUE; - for (i=0; i<import.nb_tracks; i++) { - if (track_id && (import.tk_infoi.track_num==track_id)) { + for (i=0; i<import->nb_tracks; i++) { + if (track_id && (import->tk_infoi.track_num==track_id)) { found=GF_TRUE; break; } - if (do_audio && (import.tk_infoi.stream_type==GF_STREAM_AUDIO)) { - track_id = import.tk_infoi.track_num; + if (do_audio && (import->tk_infoi.stream_type==GF_STREAM_AUDIO)) { + track_id = import->tk_infoi.track_num; break; } - else if (do_video && (import.tk_infoi.stream_type==GF_STREAM_VISUAL)) { - track_id = import.tk_infoi.track_num; + else if (do_video && (import->tk_infoi.stream_type==GF_STREAM_VISUAL)) { + track_id = import->tk_infoi.track_num; break; } - else if (do_auxv && (import.tk_infoi.media_subtype==GF_ISOM_MEDIA_AUXV)) { - track_id = import.tk_infoi.track_num; + else if (do_auxv && (import->tk_infoi.media_subtype==GF_ISOM_MEDIA_AUXV)) { + track_id = import->tk_infoi.track_num; break; } - else if (do_pict && (import.tk_infoi.media_subtype==GF_ISOM_MEDIA_PICT)) { - track_id = import.tk_infoi.track_num; + else if (do_pict && (import->tk_infoi.media_subtype==GF_ISOM_MEDIA_PICT)) { + track_id = import->tk_infoi.track_num; break; } } @@ -1549,40 +1595,103 @@ if (!tw) tw = w; if (!th) th = h; if (ty==-1) ty = (h>(u32)th) ? h-th : 0; - import.text_width = tw; - import.text_height = th; + import->text_width = tw; + import->text_height = th; } if (is_chap && chap_ref) import_flags |= GF_IMPORT_NO_TEXT_FLUSH; } if (text_layout && txtw && txth) { - import.text_track_width = import.text_width ? import.text_width : txtw; - import.text_track_height = import.text_height ? import.text_height : txth; - import.text_width = txtw; - import.text_height = txth; - import.text_x = txtx; - import.text_y = txty; + import->text_track_width = import->text_width ? import->text_width : txtw; + import->text_track_height = import->text_height ? import->text_height : txth; + import->text_width = txtw; + import->text_height = txth; + import->text_x = txtx; + import->text_y = txty; } check_track_for_svc = check_track_for_lhvc = check_track_for_hevc = 0; source_magic = (u64) gf_crc_32((u8 *)inName, (u32) strlen(inName)); - if (!fake_import && (!fsess || mux_args_if_first_pass)) { - import.in_name = final_name; - import.dest = dest; - import.video_fps = force_fps; - import.frames_per_sample = frames_per_sample; - import.flags = import_flags; - import.keep_audelim = keep_audelim; - import.print_stats_graph = print_stats_graph; - import.xps_inband = xps_inband; - import.prog_id = prog_id; - import.trackID = track_id; - import.source_magic = source_magic; - import.track_index = tk_idx; + + if (is_extk) { + u32 ext_tk=0; + GF_ISOFile *isom_in = gf_isom_open(final_name, GF_ISOM_OPEN_READ, NULL); + if (!isom_in) { + e = gf_isom_last_error(NULL); + GOTO_EXIT("opening source") + } + if (!track_id) { + if (gf_isom_get_track_count(isom_in)>1) { + gf_isom_delete(isom_in); + e = GF_BAD_PARAM; + M4_LOG(GF_LOG_ERROR, ("More than one track in source and no trackID specified\n")); + GOTO_EXIT("adding track"); + } + ext_tk = 1; + } else { + ext_tk = gf_isom_get_track_by_id(isom_in, track_id); + if (!ext_tk) { + gf_isom_delete(isom_in); + e = GF_BAD_PARAM; + M4_LOG(GF_LOG_ERROR, ("No track with ID %u in source %s\n", track_id, final_name)); + GOTO_EXIT("adding track"); + } + } + if (has_non_extk_opt) { + is_extk=2; + if (first_ext) first_ext0 = ':'; + + goto reparse_opts; + } + u32 new_tk = 0; + Bool src_is_extk = gf_isom_is_external_track(isom_in, ext_tk, NULL, NULL, NULL, NULL); + if (src_is_extk) { + e = gf_isom_clone_track(isom_in, ext_tk, dest, 0, &new_tk); + GOTO_EXIT("cloning external track"); + } else { + u32 type = gf_isom_get_media_type(isom_in, ext_tk); + u32 timescale = gf_isom_get_media_timescale(isom_in, ext_tk); + new_tk = gf_isom_new_external_track(dest, 0, track_id, type, timescale, final_name); + + u32 mvscale = gf_isom_get_timescale(dest); + + if (new_tk) { + u32 w, h; + s32 tx, ty; + s16 layer; + if (gf_isom_get_track_layout_info(isom_in, ext_tk, &w, &h, &tx, &ty, &layer)==GF_OK) { + gf_isom_set_track_layout_info(dest, new_tk, w<<16, h<<16, tx<<16, ty<<16, layer); + } + if (import->duration.num>=0) { + u64 dur; + if (import->duration.num) + dur = gf_timestamp_rescale(import->duration.num, import->duration.den, mvscale); + else + dur = gf_timestamp_rescale(gf_isom_get_track_duration(isom_in, ext_tk), gf_isom_get_timescale(isom_in), mvscale); + gf_isom_force_track_duration(dest, new_tk, dur); + } + } + } + track_id = gf_isom_get_track_id(dest, new_tk); + fake_import = 1; + gf_isom_delete(isom_in); + } else if (!fake_import && (!fsess || mux_args_if_first_pass)) { + import->in_name = final_name; + import->dest = dest; + import->video_fps = force_fps; + import->frames_per_sample = frames_per_sample; + import->flags = import_flags; + import->keep_audelim = keep_audelim; + import->print_stats_graph = print_stats_graph; + import->xps_inband = xps_inband; + import->prog_id = prog_id; + import->trackID = track_id; + import->source_magic = source_magic; + import->track_index = tk_idx; //if moov timescale is <0 (auto mode) set it at import time if (moov_timescale<0) { - import.moov_timescale = moov_timescale; + import->moov_timescale = moov_timescale; } //otherwise force it now else if (moov_timescale>0) { @@ -1590,22 +1699,22 @@ GOTO_EXIT("changing timescale") } - import.run_in_session = fsess; - import.update_mux_args = NULL; + import->run_in_session = fsess; + import->update_mux_args = NULL; if (do_all) - import.flags |= GF_IMPORT_KEEP_REFS; + import->flags |= GF_IMPORT_KEEP_REFS; - e = gf_media_import(&import); + e = gf_media_import(import); if (e) { - if (import.update_mux_args) gf_free(import.update_mux_args); + if (import->update_mux_args) gf_free(import->update_mux_args); GOTO_EXIT("importing media"); } if (fsess) { - *mux_args_if_first_pass = import.update_mux_args; - import.update_mux_args = NULL; - *mux_sid_if_first_pass = import.update_mux_sid; - import.update_mux_sid = NULL; + *mux_args_if_first_pass = import->update_mux_args; + import->update_mux_args = NULL; + *mux_sid_if_first_pass = import->update_mux_sid; + import->update_mux_sid = NULL; goto exit; } } @@ -1622,7 +1731,7 @@ if ((tk_source_magic & 0xFFFFFFFFUL) != source_magic) continue; - tk_source_magic>>=32; + tk_source_magic>>=32; keep_handler = (tk_source_magic & 1) ? GF_TRUE : GF_FALSE; } else { keep_handler = GF_TRUE; @@ -1796,10 +1905,10 @@ GOTO_EXIT("changing timescale") } - if (import.asemode && (media_type==GF_ISOM_MEDIA_AUDIO)) { + if (import->asemode && (media_type==GF_ISOM_MEDIA_AUDIO)) { u32 sr, ch, bps; gf_isom_get_audio_info(dest, track, 1, &sr, &ch, &bps); - gf_isom_set_audio_info(dest, track, 1, sr, ch, bps, import.asemode); + gf_isom_set_audio_info(dest, track, 1, sr, ch, bps, import->asemode); } } @@ -1948,6 +2057,17 @@ reorder_tk_idsreorder_tk_ids_count = gf_isom_get_track_id(dest, track); reorder_tk_ids_count++; } + + if (timestamp_source) { + if (!track_id && gf_isom_get_track_count(dest)==1) + track_id = gf_isom_get_track_id(dest, 1); + + if (!track_id) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Warning: `times=`cannot be used with multi-track import, ignoring\n")); + } else { + apply_timestamps(dest, track_id, timestamp_source); + } + } } if (reorder_tk_ids_count) { @@ -2116,15 +2236,17 @@ gf_list_del(kinds); if (handler_name) gf_free(handler_name); if (chapter_name ) gf_free(chapter_name); - if (import.fontName) gf_free(import.fontName); - if (import.streamFormat) gf_free(import.streamFormat); - if (import.force_ext) gf_free(import.force_ext); + if (import->fontName) gf_free(import->fontName); + if (import->streamFormat) gf_free(import->streamFormat); + if (import->force_ext) gf_free(import->force_ext); if (rvc_config) gf_free(rvc_config); if (edits) gf_free(edits); if (szLan) gf_free((char *)szLan); if (icc_data) gf_free(icc_data); if (final_name) gf_free(final_name); if (reorder_tk_ids) gf_free(reorder_tk_ids); + if (import) gf_free(import); + if (timestamp_source) gf_free(timestamp_source); if (!e) return GF_OK; if (fail_msg) { @@ -2442,7 +2564,7 @@ if (fs_dump_flags & 2) gf_fs_print_connections(fs); gf_fs_del(fs); - + if (e<GF_OK) M4_LOG(GF_LOG_ERROR, ("Split failed: %s\n", gf_error_to_string(e) )); return e; @@ -2479,33 +2601,37 @@ avc_src = gf_isom_avc_config_get(orig, src_track, 1); avc_dst = gf_isom_avc_config_get(dest, dst_tk, 1); - if (!force_cat && (avc_src->AVCLevelIndication!=avc_dst->AVCLevelIndication)) { - dst_tk = 0; - } else if (!force_cat && (avc_src->AVCProfileIndication!=avc_dst->AVCProfileIndication)) { - dst_tk = 0; - } - else { - /*rewrite all samples if using different NALU size*/ - if (avc_src->nal_unit_size > avc_dst->nal_unit_size) { - gf_media_nal_rewrite_samples(dest, dst_tk, 8*avc_src->nal_unit_size); - avc_dst->nal_unit_size = avc_src->nal_unit_size; - } else if (avc_src->nal_unit_size < avc_dst->nal_unit_size) { - *orig_nal_len = avc_src->nal_unit_size; - *dst_nal_len = avc_dst->nal_unit_size; - } + if (avc_src && avc_dst) { - /*merge PS*/ - if (!merge_parameter_set(avc_src->sequenceParameterSets, avc_dst->sequenceParameterSets, "SPS")) + if (!force_cat && (avc_src->AVCLevelIndication!=avc_dst->AVCLevelIndication)) { dst_tk = 0; - if (!merge_parameter_set(avc_src->pictureParameterSets, avc_dst->pictureParameterSets, "PPS")) + } else if (!force_cat && (avc_src->AVCProfileIndication!=avc_dst->AVCProfileIndication)) { dst_tk = 0; + } + else { + /*rewrite all samples if using different NALU size*/ + if (avc_src->nal_unit_size > avc_dst->nal_unit_size) { + gf_media_nal_rewrite_samples(dest, dst_tk, 8*avc_src->nal_unit_size); + avc_dst->nal_unit_size = avc_src->nal_unit_size; + } else if (avc_src->nal_unit_size < avc_dst->nal_unit_size) { + *orig_nal_len = avc_src->nal_unit_size; + *dst_nal_len = avc_dst->nal_unit_size; + } - gf_isom_avc_config_update(dest, dst_tk, 1, avc_dst); - } + /*merge PS*/ + if (!merge_parameter_set(avc_src->sequenceParameterSets, avc_dst->sequenceParameterSets, "SPS")) + dst_tk = 0; + if (!merge_parameter_set(avc_src->pictureParameterSets, avc_dst->pictureParameterSets, "PPS")) + dst_tk = 0; + + gf_isom_avc_config_update(dest, dst_tk, 1, avc_dst); + } + } gf_odf_avc_cfg_del(avc_src); gf_odf_avc_cfg_del(avc_dst); + if (!dst_tk) { dst_tk = gf_isom_get_track_by_id(dest, tk_id); gf_isom_set_nalu_extract_mode(orig, src_track, GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG); @@ -3043,6 +3169,10 @@ if (samp->nb_pack) j+= samp->nb_pack-1; + if (samp->data && !samp->dataLength) { + // force deletetion, see gf_isom_sample_del() + samp->dataLength = samp->alloc_size; + } gf_isom_sample_del(&samp); if (e) goto err_exit; @@ -3314,11 +3444,11 @@ e = GF_OK; while (!feof(pl)) { - char szLine10000; + char szLine1000; char *url; u32 len; szLine0 = 0; - if (gf_fgets(szLine, 10000, pl) == NULL) break; + if (gf_fgets(szLine, 1000, pl) == NULL) break; if (szLine0=='#') continue; len = (u32) strlen(szLine); while (len && strchr("\r\n \t", szLinelen-1)) { @@ -3370,10 +3500,10 @@ #ifndef GPAC_DISABLE_SWF_IMPORT load.swf_import_flags = swf_flags; #endif - load.swf_flatten_limit = swf_flatten_angle; + load.swf_flatten_limit = (Float) swf_flatten_angle; /*since we're encoding we must get MPEG4 nodes only*/ load.flags = GF_SM_LOAD_MPEG4_STRICT; - + e = gf_sm_load_init(&load); if (e<0) { gf_sm_load_done(&load); @@ -3653,7 +3783,6 @@ \param inputContext initial BT upon which the chunk is based (shall not be NULL) \param outputContext: file name to dump the context after applying the new chunk to the input context can be NULL, without .bt -\param tmpdir can be NULL */ GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *outputContext) { @@ -4103,6 +4232,125 @@ return e; } +static GF_Err apply_timestamps(GF_ISOFile *file, GF_ISOTrackID trackID, const char *timestamp_source) +{ + Bool use_dts = GF_FALSE; + GF_Err e = GF_OK; + GF_ISOSample samp; + u32 di; + u64 data_offset; + u32 timescale=1000; + FILE *src = gf_fopen(timestamp_source, "rb"); + if (!src) return GF_URL_ERROR; + u32 track = gf_isom_get_track_by_id(file, trackID); + u32 sample_count = gf_isom_get_sample_count(file, track); + u32 cur_sample = 0; + u32 min_delta = 0; + u64 prev_cts=0; + u64 prev_dts=0; + u64 last_dts_written=0; + u32 nb_sample_in=0; + gf_isom_set_media_timescale(file, track, timescale, 0, 1); + memset(&samp, 0, sizeof(GF_ISOSample)); + while (!gf_feof(src)) { + char szLine100; + szLine0 = 0; + gf_fgets(szLine, 99, src); + szLine99=0; + u32 len = (u32) strlen(szLine); + while (len && (strchr(" \n\r", szLinelen-1) != NULL)) { + szLinelen-1 = 0; + len--; + } + if (!szLine0) continue; -#endif /*GPAC_DISABLE_ISOM_WRITE*/ + if (szLine0 == '#') { + char *ts_sep=strstr(szLine, "timescale="); + if (ts_sep) { + char *sep = strchr(ts_sep+10, ' '); + if (sep) sep0=0; + timescale = atoi(ts_sep+10); + gf_isom_set_media_timescale(file, track, timescale, 0, 1); + } + continue; + } + nb_sample_in++; + if (cur_sample>=sample_count) + continue; + + u64 dts, cts; + char *sep = strchr(szLine, ' '); + if (sep) { + use_dts = GF_TRUE; + sscanf(szLine, LLU" "LLU, &dts, &cts); + } else { + sscanf(szLine, LLU, &cts); + dts = cts; + } + + if (!cur_sample) { + prev_dts = dts; + prev_cts = cts; + cur_sample++; + continue; + } + if (!use_dts) { + if (dts>=prev_dts) { + if (dts==prev_dts) dts++; + s32 delta = (dts - prev_dts); + if (!min_delta || (min_delta>delta)) min_delta = delta; + } else { + //B-frame after ref + prev_dts = last_dts_written + 1; + last_dts_written += 1; + } + if (prev_cts<prev_dts) { + prev_dts = last_dts_written + 1; + last_dts_written += 1; + } + } + gf_isom_get_sample_info_ex(file, track, cur_sample, &di, &data_offset, &samp); + samp.DTS = prev_dts; + if (cur_sample==1) samp.DTS = 0; + samp.CTS_Offset = (s32)prev_cts - (s32) samp.DTS; + e = gf_isom_update_sample(file, track, cur_sample, &samp, GF_FALSE); + if (e) { + gf_fclose(src); + return e; + } + + last_dts_written = prev_dts; + cur_sample++; + prev_cts = cts; + prev_dts = dts; + } + //flush last + gf_isom_get_sample_info_ex(file, track, cur_sample, &di, &data_offset, &samp); + samp.DTS = prev_dts; + samp.CTS_Offset = prev_cts; + samp.CTS_Offset -= prev_dts; + e = gf_isom_update_sample(file, track, cur_sample, &samp, GF_FALSE); + if (e) { + gf_fclose(src); + return e; + } + + if (nb_sample_in>sample_count) { + M4_LOG(GF_LOG_WARNING, ("Too many inputs in timestamp file, ignoring last entries\n")); + } else if (cur_sample < sample_count) { + M4_LOG(GF_LOG_WARNING, ("Not enough samples in timestamp file, removing last %u samples\n", sample_count-cur_sample)); + + cur_sample++; + for (di=cur_sample; di<=sample_count; di++) { + gf_isom_remove_sample(file, track, cur_sample); + } + } + + gf_fclose(src); + return GF_OK; +} + + + +#endif /*GPAC_DISABLE_ISOM_WRITE*/
View file
gpac-2.4.0.tar.gz/applications/mp4box/live.c -> gpac-26.02.0.tar.gz/applications/mp4box/live.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2019 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * Authors: Jean Le Feuvre @@ -324,6 +324,7 @@ return rtpch; } +char szBuf8192; int live_session(int argc, char **argv) { GF_Err e; @@ -504,16 +505,15 @@ case 'U': case 'u': { - char szCom8192; fprintf(stderr, "Enter command to send:\n"); - szCom0 = 0; - if (1 > scanf("%8191^\t\n", szCom)) { + szBuf0 = 0; + if (1 > scanf("%8191^\t\n", szBuf)) { fprintf(stderr, "No command entered properly, aborting.\n"); break; } /*stdin flush bug*/ while (getchar()!='\n') {} - e = gf_seng_encode_from_string(livesess.seng, 0, 0, szCom, live_session_callback); + e = gf_seng_encode_from_string(livesess.seng, 0, 0, szBuf, live_session_callback); if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); e = gf_seng_aggregate_context(livesess.seng, 0); if (e) fprintf(stderr, "Aggregating context failed: %s\n", gf_error_to_string(e)); @@ -524,16 +524,15 @@ case 'E': case 'e': { - char szCom8192; fprintf(stderr, "Enter command to send:\n"); - szCom0 = 0; - if (1 > scanf("%8191^\t\n", szCom)) { + szBuf0 = 0; + if (1 > scanf("%8191^\t\n", szBuf)) { printf("No command entered properly, aborting.\n"); break; } /*stdin flush bug*/ while (getchar()!='\n') {} - e = gf_seng_encode_from_string(livesess.seng, 0, 1, szCom, live_session_callback); + e = gf_seng_encode_from_string(livesess.seng, 0, 1, szBuf, live_session_callback); if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); livesess.critical = (c=='E') ? 1 : 0; e = gf_seng_aggregate_context(livesess.seng, 0); @@ -544,13 +543,13 @@ case 'p': { - char radGF_MAX_PATH; + szBuf0 = 0; fprintf(stderr, "Enter output file name - \"std\" for stderr: "); - if (1 > scanf("%1023s", rad)) { + if (1 > scanf("%8191s", szBuf)) { fprintf(stderr, "No output file name entered, aborting.\n"); break; } - e = gf_seng_save_context(livesess.seng, !strcmp(rad, "std") ? NULL : rad); + e = gf_seng_save_context(livesess.seng, !strcmp(szBuf, "std") ? NULL : szBuf); fprintf(stderr, "Dump done (%s)\n", gf_error_to_string(e)); } break; @@ -664,6 +663,10 @@ update_length = gf_bs_read_u32(bs); hdr_length = 12; gf_bs_del(bs); + if ((update_length >= (u32)(SIZE_MAX-1)) || (update_length >= (u32)(GF_UINT_MAX-1))) { + M4_LOG(GF_LOG_ERROR, ("Processing command failed: update_length too long.\n")); + update_length = 0; + } } set_broadcast_params(&livesess, es_id, period, ts_delta, aggregate_on_stream, adjust_carousel_time, force_rap, aggregate_au, discard_pending, signal_rap, signal_critical, version_inc); @@ -673,29 +676,33 @@ break; } - if (update_buffer_size <= update_length) { + if (update_length && update_buffer_size <= update_length) { update_buffer = gf_realloc(update_buffer, update_length+1); update_buffer_size = update_length+1; } if (update_length && (bytes_read>hdr_length) ) { - memcpy(update_buffer, buffer+hdr_length, bytes_read-hdr_length); - bytes_received = bytes_read-hdr_length; + u32 to_copy = MIN(bytes_read-hdr_length, update_buffer_size); + memcpy(update_buffer, buffer+hdr_length, to_copy); + bytes_received = to_copy; } while (bytes_received<update_length) { e = gf_sk_receive(sk, buffer, 2048, &bytes_read); switch (e) { case GF_IP_NETWORK_EMPTY: + gf_sleep(10); break; - case GF_OK: - memcpy(update_buffer+bytes_received, buffer, bytes_read); - bytes_received += bytes_read; + case GF_OK:; + u32 to_copy = MIN(bytes_read, update_buffer_size-bytes_received); + memcpy(update_buffer+bytes_received, buffer, to_copy); + bytes_received += to_copy; break; default: fprintf(stderr, "Error with UDP socket : %s\n", gf_error_to_string(e)); break; } } - update_bufferupdate_length = 0; + if (update_buffer) + update_bufferupdate_length = 0; if (update_length) { e = gf_seng_encode_from_string(livesess.seng, es_id, aggregate_au ? 0 : 1, update_buffer, live_session_callback);
View file
gpac-2.4.0.tar.gz/applications/mp4box/mp4box.c -> gpac-26.02.0.tar.gz/applications/mp4box/mp4box.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2026 * All rights reserved * * This file is part of GPAC / mp4box application @@ -117,24 +117,25 @@ TRACK_ACTION_REM_TRACK= 0, TRACK_ACTION_SET_LANGUAGE, TRACK_ACTION_SET_DELAY, - TRACK_ACTION_SET_KMS_URI, - TRACK_ACTION_SET_PAR, - TRACK_ACTION_SET_HANDLER_NAME, TRACK_ACTION_ENABLE, TRACK_ACTION_DISABLE, TRACK_ACTION_REFERENCE, - TRACK_ACTION_RAW_EXTRACT, - TRACK_ACTION_REM_NON_RAP, TRACK_ACTION_SET_KIND, TRACK_ACTION_REM_KIND, TRACK_ACTION_SET_ID, TRACK_ACTION_SET_UDTA, TRACK_ACTION_SWAP_ID, - TRACK_ACTION_REM_NON_REFS, - TRACK_ACTION_SET_CLAP, TRACK_ACTION_SET_MX, TRACK_ACTION_SET_EDITS, TRACK_ACTION_SET_TIME, + + TRACK_ACTION_SET_KMS_URI, + TRACK_ACTION_SET_PAR, + TRACK_ACTION_SET_HANDLER_NAME, + TRACK_ACTION_RAW_EXTRACT, + TRACK_ACTION_REM_NON_RAP, + TRACK_ACTION_REM_NON_REFS, + TRACK_ACTION_SET_CLAP, TRACK_ACTION_SET_MEDIA_TIME, } TrackActionType; @@ -224,9 +225,9 @@ Bool insert_utc, chunk_mode, HintCopy, hint_no_offset, do_bin_xml, frag_real_time, force_co64, live_scene, use_mfra, dump_iod, samplegroups_in_traf; Bool mvex_after_traks, daisy_chain_sidx, use_ssix, single_segment, single_file, segment_timeline, has_add_image; Bool strict_cues, use_url_template, seg_at_rap, frag_at_rap, memory_frags, keep_utc, has_next_arg, no_cache, no_loop; -Bool conv_type_from_ext; +Bool conv_type_from_ext, dump_keep_comp; -u32 stat_level, hint_flags, import_flags, nb_add, nb_cat, crypt, agg_samples, nb_sdp_ex, max_ptime, split_size, nb_meta_act; +u32 stat_level, hint_flags, import_flags, nb_add, nb_cat, crypt_type, agg_samples, nb_sdp_ex, max_ptime, split_size, nb_meta_act; u32 nb_track_act, rtp_rate, major_brand, nb_alt_brand_add, nb_alt_brand_rem, old_interleave, minor_version, conv_type, nb_tsel_acts; u32 program_number, time_shift_depth, initial_moof_sn, dump_std, import_subtitle, dump_saps_mode, force_new, compress_moov; u32 track_dump_type, dump_isom, dump_timestamps, dump_nal_type, do_flat, print_info; @@ -286,14 +287,14 @@ use_mfra = dump_iod = samplegroups_in_traf = mvex_after_traks = daisy_chain_sidx = use_ssix = single_segment = single_file = GF_FALSE; segment_timeline = has_add_image = strict_cues = use_url_template = seg_at_rap = frag_at_rap = memory_frags = keep_utc = GF_FALSE; has_next_arg = no_cache = no_loop = GF_FALSE; - conv_type_from_ext = GF_FALSE; + conv_type_from_ext = dump_keep_comp = GF_FALSE; /*align cat is the new default behavior for -cat*/ align_cat=GF_TRUE; //u32 arg_parse_res = nb_mpd_base_urls = nb_dash_inputs = help_flags = 0; - stat_level = hint_flags = import_flags = nb_add = nb_cat = crypt = 0; + stat_level = hint_flags = import_flags = nb_add = nb_cat = crypt_type = 0; agg_samples = nb_sdp_ex = max_ptime = split_size = nb_meta_act = nb_track_act = 0; rtp_rate = major_brand = nb_alt_brand_add = nb_alt_brand_rem = old_interleave = minor_version = 0; conv_type = nb_tsel_acts = program_number = time_shift_depth = initial_moof_sn = dump_std = 0; @@ -607,7 +608,7 @@ "By default, MP4Box rewrites the input file. You can change this behavior by using the -out() option.\n" "MP4Box stores by default the file with 0.5 second interleaving and meta-data (`moov` ...) at the beginning, making it suitable for HTTP download-and-play. This may however takes longer to store the file, use -flat() to change this behavior.\n" " \n" - "MP4Box usually generates a temporary file when creating a new IsoMedia file. The location of this temporary file is OS-dependent, and it may happen that the drive/partition the temporary file is created on has not enough space or no write access. In such a case, you can specify a temporary file location with -tmp().\n" + "MP4Box usually generates a temporary file when creating a new IsoMedia file. The location of this temporary file is OS-dependent, and it may happen that the drive/partition the temporary file is created on has not enough space or no write access. In such a case, you can specify a temporary file location with -tmp(CORE).\n" " \n" "Track identifier for track-based operations (usually referred to as `tkID` in the help) use the following syntax:\n" "- INT: target is track with ID `INT`\n" @@ -635,7 +636,7 @@ MP4BOX_ARG("split", "split in files of given max duration (float number) in seconds. A trailing unit can be specified:\n" "- `M`, `m`: duration is in minutes\n" "- `H`, `h`: size is in hours", GF_ARG_STRING, 0, parse_split, 0, ARG_IS_FUN), - MP4BOX_ARG_ALT("split-rap", "splitr", "split in files at each new RAP", GF_ARG_STRING, 0, parse_split, 1, ARG_IS_FUN), + MP4BOX_ARG_ALT("split-rap", "splitr", "split in files at each new RAP", GF_ARG_BOOL, 0, parse_split, 1, ARG_IS_FUN), MP4BOX_ARG_ALT("split-size", "splits", "split in files of given max size (integer number) in kilobytes. A trailing unit can be specified:\n" "- `M`, `m`: size is in megabytes\n" "- `G`, `g`: size is in gigabytes", GF_ARG_STRING, 0, parse_split, 2, ARG_IS_FUN), @@ -716,6 +717,7 @@ "- $Init=NAME$ is replaced by NAME for init segment, ignored otherwise\n" "- $Index=NAME$ is replaced by NAME for index segments, ignored otherwise\n" "- $Path=PATH$ is replaced by PATH when creating segments, ignored otherwise\n" + "- $SubNumber%%0Nd$ is replaced by the segment subnumber in segment sequences, possibly prefixed with 0\n" "- $Segment=NAME$ is replaced by NAME for media segments, ignored for init segments", GF_ARG_STRING, 0, &seg_name, 0, 0), {"segment-ext", NULL, "set the segment extension, `null` means no extension", "m4s", NULL, GF_ARG_STRING, 0, &seg_ext, 0, 0}, {"init-segment-ext", NULL, "set the segment extension for init, index and bitstream switching segments, `null` means no extension\n", "mp4", NULL, GF_ARG_STRING, 0, &init_seg_ext, 0, 0}, @@ -732,12 +734,12 @@ MP4BOX_ARG("last-dynamic", "same as -dynamic() but close the period (insert lmsg brand if needed and update duration)", GF_ARG_BOOL, 0, &dash_mode, GF_DASH_DYNAMIC_LAST, 0), MP4BOX_ARG("mpd-duration", "set the duration in second of a live session (if `0`, you must use -mpd-refresh())", GF_ARG_DOUBLE, 0, &mpd_live_duration, 0, 0), MP4BOX_ARG("mpd-refresh", "specify MPD update time in seconds", GF_ARG_DOUBLE, 0, &mpd_update_time, 0, 0), - MP4BOX_ARG("time-shift", "specify MPD time shift buffer depth in seconds, `-1` to keep all files)", GF_ARG_INT, 0, &time_shift_depth, 0, 0), + MP4BOX_ARG("time-shift", "specify MPD time shift buffer depth in seconds, `-1` to keep all files. Default is 0", GF_ARG_INT, 0, &time_shift_depth, 0, 0), MP4BOX_ARG("subdur", "specify maximum duration in ms of the input file to be dashed in LIVE or context mode. This does not change the segment duration, but stops dashing once segments produced exceeded the duration. If there is not enough samples to finish a segment, data is looped unless -no-loop() is used which triggers a period end", GF_ARG_DOUBLE, 0, &dash_subduration, 0, 0), MP4BOX_ARG("run-for", "run for given ms the dash-live session then exits", GF_ARG_INT, 0, &run_for, 0, 0), MP4BOX_ARG("min-buffer", "specify MPD min buffer time in ms", GF_ARG_INT, 0, &min_buffer, 0, ARG_DIV_1000), MP4BOX_ARG("ast-offset", "specify MPD AvailabilityStartTime offset in ms if positive, or availabilityTimeOffset of each representation if negative", GF_ARG_INT, 0, &ast_offset_ms, 0, 0), - MP4BOX_ARG("dash-scale", "specify that timing for -dash(), -dash-live(), -subdur() and -do_frag() are expressed in given timescale (units per seconds) rather than ms", GF_ARG_INT, 0, &dash_scale, 0, ARG_NON_ZERO), + MP4BOX_ARG("dash-scale", "specify that timing for -dash(), -dash-live(), -subdur() and -frag() are expressed in given timescale (units per seconds) rather than ms", GF_ARG_INT, 0, &dash_scale, 0, ARG_NON_ZERO), MP4BOX_ARG("mem-frags", "fragmentation happens in memory rather than on disk before flushing to disk", GF_ARG_BOOL, 0, &memory_frags, 0, 0), MP4BOX_ARG("pssh", "set pssh store mode\n" "- v: initial movie\n" @@ -797,7 +799,7 @@ u32 i=0; gf_sys_format_help(helpout, help_flags, "# DASH Options\n" "Also see:\n" - "- the dasher `gpac -h dash`(dasher) filter documentation\n" + "- the dasher `gpac -h dasher`(dasher) filter documentation\n" "- DASH wiki|DASH-intro.\n" "\n" "# Specifying input files\n" @@ -806,6 +808,7 @@ "- #N: only use the track ID N from the source file (mapped to -tkid(mp4dmx))\n" "- #video: only use the first video track from the source file\n" "- #audio: only use the first audio track from the source file\n" + "- #Prop=Value: add PID filtering using the same syntax as SID fragments (cf `gpac -h doc`)\n" "- :id=NAME: set the representation ID to NAME. Reserved value `NULL` disables representation ID for multiplexed inputs. If not set, a default value is computed and all selected tracks from the source will be in the same output multiplex.\n" "- :dur=VALUE: process VALUE seconds (fraction) from the media. If VALUE is longer than media duration, last sample duration is extended.\n" "- :period=NAME: set the representation's period to NAME. Multiple periods may be used. Periods appear in the MPD in the same order as specified with this option\n" @@ -821,7 +824,7 @@ "- :desc_as_c=VALUE: add a descriptor at the AdaptationSet level. Value is ignored while creating AdaptationSet elements.\n" "- :desc_rep=VALUE: add a descriptor at the Representation level. Value is ignored while creating AdaptationSet elements.\n" "- :sscale: force movie timescale to match media timescale of the first track in the segment.\n" - "- :trackID=N: only use the track ID N from the source file\n" + "- :trackID=N: same as setting fragment `#trackID=`\n" "- @f1:args@fN:args@@fK:args: set a filter chain to insert between the source and the dasher. Each filter in the chain is formatted as a regular filter, see filter doc `gpac -h doc`(filters_general). If several filters are set:\n" " - they will be chained in the given order if separated by a single `@`\n" " - a new filter chain will be created if separated by a double `@@`. In this case, no representation ID is assigned to the source.\n" @@ -870,23 +873,23 @@ static MP4BoxArg m4b_imp_fileopt_args = { - GF_DEF_ARG("dur", NULL, "`XC` import only the specified duration from the media. Value can be:\n" + GF_DEF_ARG("dur", NULL, "`XCE` import only the specified duration from the media. Value can be:\n" " - positive float: specifies duration in seconds\n" " - fraction: specifies duration as NUM/DEN fraction\n" " - negative integer: specifies duration in number of coded frames", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("start", NULL, "`C` target start time in source media, may not be supported depending on the source", NULL, NULL, GF_ARG_DOUBLE, 0), - GF_DEF_ARG("lang", NULL, "`S` set imported media language code", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("delay", NULL, "`S` set imported media initial delay (>0) or initial skip (<0) in ms or as fractional seconds (`N/D`)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("lang", NULL, "`SE` set imported media language code", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("delay", NULL, "`SE` set imported media initial delay (>0) or initial skip (<0) in ms or as fractional seconds (`N/D`)", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("par", NULL, "`S` set visual pixel aspect ratio (see -par(MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("clap", NULL, "`S` set visual clean aperture (see -clap(MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("mx", NULL, "`S` set track matrix (see -mx(MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mx", NULL, "`SE` set track matrix (see -mx(MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("name", NULL, "`S` set track handler name", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("ext", NULL, "override file extension when importing", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("hdlr", NULL, "`S` set track handler type to the given code point (4CC)", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("stype", NULL, "`S` force sample description type to given code point (4CC), may likely break the file", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("tkhd", NULL, "`S` set track header flags has hex integer or as comma-separated list of `enable`, `movie`, `preview`, `size_ar` keywords (use `tkhd+=FLAGS` to add and `tkhd-=FLAGS` to remove)", NULL, NULL, GF_ARG_INT, 0), - GF_DEF_ARG("disable", NULL, "`S` disable imported track(s), use `disable=no` to force enabling a disabled track", NULL, NULL, GF_ARG_BOOL, 0), - GF_DEF_ARG("group", NULL, "`S` add the track as part of the G alternate group. If G is 0, the first available GroupID will be picked", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("tkhd", NULL, "`SE` set track header flags has hex integer or as comma-separated list of `enable`, `movie`, `preview`, `size_ar` keywords (use `tkhd+=FLAGS` to add and `tkhd-=FLAGS` to remove)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("disable", NULL, "`SE` disable imported track(s), use `disable=no` to force enabling a disabled track", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("group", NULL, "`SE` add the track as part of the G alternate group. If G is 0, the first available GroupID will be picked", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("fps", NULL, "`S` same as -fps()", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("rap", NULL, "`DS` import only RAP samples", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("refs", NULL, "`DS` import only reference pictures", NULL, NULL, GF_ARG_BOOL, 0), @@ -894,7 +897,7 @@ GF_DEF_ARG("agg", NULL, "`X` same as -agg()", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("dref", NULL, "`XC` same as -dref()", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("keep_refs", NULL, "`C` keep track reference when importing a single track", NULL, NULL, GF_ARG_BOOL, 0), - GF_DEF_ARG("nodrop", NULL, "same as -nodrop()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("nodrop", NULL, "same as -no-drop()", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("packed", NULL, "`X` same as -packed()", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("sbr", NULL, "same as -sbr()", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("sbrx", NULL, "same as -sbrx()", NULL, NULL, GF_ARG_BOOL, 0), @@ -902,8 +905,9 @@ GF_DEF_ARG("ps", NULL, "same as -ps()", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("psx", NULL, "same as -psx()", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("asemode", NULL, "`XS` set the mode to create the AudioSampleEntry. Value can be:\n" - " - v0-bs: use MPEG AudioSampleEntry v0 and the channel count from the bitstream (even if greater than 2) - default\n" + " - v0-s: use MPEG AudioSampleEntry v0 and the channel count from the bitstream (even if greater than 2, except for Dolby (e)AC3) - default\n" " - v0-2: use MPEG AudioSampleEntry v0 and the channel count is forced to 2\n" + " - v0-bs: use MPEG AudioSampleEntry v0 and the channel count from the bitstream\n" " - v1: use MPEG AudioSampleEntry v1 and the channel count from the bitstream\n" " - v1-qt: use QuickTime Sound Sample Description Version 1 and the channel count from the bitstream (even if greater than 2). This will also trigger using alis data references instead of url, even for non-audio tracks", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("audio_roll", NULL, "`S` add a roll sample group with roll_distance `N` for audio tracks", NULL, NULL, GF_ARG_INT, 0), @@ -916,7 +920,7 @@ GF_DEF_ARG("svcmode", NULL, "`DS` set SVC/LHVC import mode. Value can be:\n" " - split: each layer is in its own track\n" " - merge: all layers are merged in a single track\n" - " - splitbase: all layers are merged in a track, and the AVC base in another\n" + " - splitbase: all layers are merged in a track, and the base in another\n" " - splitnox: each layer is in its own track, and no extractors are written\n" " - splitnoxib: each layer is in its own track, no extractors are written, using inband param set signaling", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("temporal", NULL, "`DS` set HEVC/LHVC temporal sublayer import mode. Value can be:\n" @@ -926,7 +930,7 @@ GF_DEF_ARG("subsamples", NULL, "add SubSample information for AVC+SVC", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("deps", NULL, "import sample dependency information for AVC and HEVC", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("ccst", NULL, "`S` add default HEIF ccst box to visual sample entry", NULL, NULL, GF_ARG_BOOL, 0), - GF_DEF_ARG("forcesync", NULL, "force non IDR samples with I slices (OpenGOP or GDR) to be marked as sync points\n" + GF_DEF_ARG("forcesync", NULL, "`SE` force non IDR samples with I slices (OpenGOP or GDR) to be marked as sync points\n" "Warning: RESULTING FILE IS NOT COMPLIANT WITH THE SPEC but will fix seeking in most players", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("xps_inband", NULL, "`XC` set xPS inband for AVC/H264 and HEVC (for reverse operation, re-import from raw media)", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("xps_inbandx", NULL, "`XC` same as xps_inband and also keep first xPS in sample description", NULL, NULL, GF_ARG_BOOL, 0), @@ -936,14 +940,14 @@ GF_DEF_ARG("tiles", NULL, "`S` add HEVC tiles signaling and NALU maps without splitting the tiles into different tile tracks", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("split_tiles", NULL, "`DS` split HEVC tiles into different tile tracks, one tile (or all tiles of one slice) per track", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("negctts", NULL, "`S` use negative CTS-DTS offsets (ISO4 brand). Use `negctts=no` to force using positive offset on existing track", NULL, NULL, GF_ARG_BOOL, 0), - GF_DEF_ARG("chap", NULL, "`S` specify the track is a chapter track", NULL, NULL, GF_ARG_BOOL, 0), - GF_DEF_ARG("chapter", NULL, "`S` add a single chapter (old nero format) with given name lasting the entire file", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("chapfile", NULL, "`S` add a chapter file (old nero format)", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("layout", NULL, "`S` specify the track layout as `WxHxXxYxLAYER`. If `W` (resp `H`) is 0, the max width (resp height) of the tracks in the file are used", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("chap", NULL, "`SE` specify the track is a chapter track", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("chapter", NULL, "`SE` add a single chapter (old nero format) with given name lasting the entire file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("chapfile", NULL, "`SE` add a chapter file (old nero format)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("layout", NULL, "`SE` specify the track layout as `WxHxXxYxLAYER`. If `W` (resp `H`) is 0, the max width (resp height) of the tracks in the file are used", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("rescale", NULL, "`S` force media timescale to TS (int or fraction) and change the media duration", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("sampdur", NULL, "`S` force all samples duration (`D`) or sample durations and media timescale (`D/TS`), used to patch CFR files with broken timings", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("timescale", NULL, "`S` set imported media timescale to TS", NULL, NULL, GF_ARG_INT, 0), - GF_DEF_ARG("moovts", NULL, "`S` set movie timescale to TS. A negative value picks the media timescale of the first track imported", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("moovts", NULL, "`SE` set movie timescale to TS. A negative value picks the media timescale of the first track imported", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("noedit", NULL, "`XS` do not set edit list when importing B-frames video tracks", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("rvc", NULL, "`S` set RVC configuration for the media", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("fmt", NULL, "override format detection with given format - disable data probing and force `ext` option on source", NULL, NULL, GF_ARG_STRING, 0), @@ -973,7 +977,7 @@ GF_DEF_ARG("swf-ic2d", NULL, "use indexed curve 2D hardcoded proto", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("swf-same-app", NULL, "appearance nodes are reused", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("swf-flatten", NULL, "complementary angle below which 2 lines are merged, `0` means no flattening", NULL, NULL, GF_ARG_DOUBLE, 0), - GF_DEF_ARG("kind", NULL, "`S` set kind for the track as `schemeURI=value`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("kind", NULL, "`SE` set kind for the track as `schemeURI=value`", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("txtflags", NULL, "set display flags (hexa number) of text track. Use `txtflags+=FLAGS` to add flags and `txtflags-=FLAGS` to remove flags", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("rate", NULL, "force average rate and max rate to VAL (in bps) in btrt box. If 0, removes btrt box", NULL, NULL, GF_ARG_INT, 0), GF_DEF_ARG("stz2", NULL, "`S` use compact size table (for low-bitrates)", NULL, NULL, GF_ARG_BOOL, 0), @@ -995,22 +999,27 @@ GF_DEF_ARG("colorprim", NULL, "`S` force the colour primaries in VUI for AVC|H264 and HEVC (int or string, cf `-h cicp`)", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("colortfc", NULL, "`S` force transfer characteristics in VUI for AVC|H264 and HEVC (int or string, cf `-h cicp`)", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("colormx", NULL, "`S` force the matrix coefficients in VUI for the AVC|H264 and HEVC content (int or string, cf `-h cicp`)", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("tc", NULL, "`S` inject a single QT timecode. Value is formatted as:\n" + GF_DEF_ARG("tc", NULL, "`SE` inject a single QT timecode. Value is formatted as:\n" " - dFPS/FPS_den,h,m,s,f,framespertick: optional drop flag, framerate (integer or fractional), hours, minutes, seconds and frame number\n" " - : `d` is an optional flag used to indicate that the counter is in drop-frame format\n" " - : the `framespertick` is optional and defaults to round(framerate); it indicates the number of frames per counter tick", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("edits", NULL, "`S` override edit list, same syntax as -edits()", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("edits", NULL, "`SE` override edit list, same syntax as -edits(MP4B_GEN)", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("lastsampdur", NULL, "`S` set duration of the last sample. Value is formatted as:\n" " - no value: use the previous sample duration\n" " - integer: indicate the duration in milliseconds\n" " - N/D: indicate the duration as fractional second", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("ID", NULL, "`S` set target ID\n" + GF_DEF_ARG("ID", NULL, "`SE` set target ID\n" " - a value of 0 (default) will try to keep source track ID\n" " - a value of -1 will ignore source track ID\n" " - other value will try to set track ID to this value if no other track with same ID is present" "", NULL, NULL, GF_ARG_INT, 0), - GF_DEF_ARG("tkgp", NULL, "`S` assign track group to track. Value is formatted as `TYPE,N` with TYPE the track group type (4CC) and N the track group ID. A negative ID removes from track group ID -N", NULL, NULL, GF_ARG_STRING, 0), - GF_DEF_ARG("tkidx", NULL, "`S` set track position in track list, 1 being first track in file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("tkgp", NULL, "`SE` assign track group to track. Value is formatted as `TYPE,N` with TYPE the track group type (4CC) and N the track group ID. A negative ID removes from track group ID -N", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("tkidx", NULL, "`SE` set track position in track list, 1 being first track in file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("extk", NULL, "`CE` add track as external track", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("times", NULL, "`SE` modify timestamps using timestamp file specified in value. Timestamp file is formatted as:\n" + " - a line starting with `#` is a comment, in which `timescale=V` can be used to set timescale (1000 by default)\n" + " - empty lines are ignored\n" + " - one line per sample in decode order, formated as `cts` or `dts cts`", NULL, NULL, GF_ARG_STRING, 0), GF_DEF_ARG("stats", "fstat", "`C` print filter session stats after import", NULL, NULL, GF_ARG_BOOL, 0), GF_DEF_ARG("graph", "fgraph", "`C` print filter session graph after import", NULL, NULL, GF_ARG_BOOL, 0), {"sopt:OPTS", NULL, "set `OPTS` as additional arguments to source filter. `OPTS` can be any usual filter argument, see filter doc `gpac -h doc`(Filters)"}, @@ -1041,7 +1050,7 @@ " \n" "By default all imports are performed sequentially, and final interleaving is done at the end; this however requires a temporary file holding original ISOBMF file (if any) and added files before creating the final output. Since this can become quite large, it is possible to add media to a new file without temporary storage, using -flat(MP4B_GEN) option, but this disables media interleaving.\n" " \n" - "If you wish to create an interleaved new file with no temporary storage, use the -newfs(MP4B_GEN) option. The interleaving might not be as precise as when using -new() since it is dependent on multiplexer input scheduling (each execution might lead to a slightly different result). Additionally in this mode: \n" + "If you wish to create an interleaved new file with no temporary storage, use the -newfs(MP4B_GEN) option. The interleaving might not be as precise as when using -new(MP4B_GEN) since it is dependent on multiplexer input scheduling (each execution might lead to a slightly different result). Additionally in this mode: \n" " - Some multiplexing options (marked with `X` below) will be activated for all inputs (e.g. it is not possible to import one AVC track with `xps_inband` and another without).\n" " - Some multiplexing options (marked as `D` below) cannot be used as they require temporary storage for file edition.\n" " - Usage of -cat() is possible, but concatenated sources will not be interleaved in the output. If you wish to perform more complex cat/add operations without temp file, use a playlist(flist).\n" @@ -1061,6 +1070,8 @@ " \n" "When importing an ISOBMFF/QT file, only options marked as `C` or `S` can be used.\n" " \n" + "When importing as an external track, only options marked as `E` can be used.\n" + " \n" "Allowed per-file options:\n\n" ); @@ -1110,6 +1121,27 @@ return GF_FALSE; } +Bool mp4box_check_non_extk_fileopt(char *opt) +{ + GF_GPACArg *arg = NULL; + u32 i=0; + + while (m4b_imp_fileopt_argsi.name) { + arg = (GF_GPACArg *) &m4b_imp_fileopt_argsi; + i++; + if (!stricmp(arg->name, opt)) break; + arg = NULL; + } + if (!arg) return GF_FALSE; + if (arg->description0 != '`') return GF_TRUE; + const char *d = arg->description+1; + while (d0 != '`') { + if (d0=='E') return GF_FALSE; + d++; + } + return GF_TRUE; +} + MP4BoxArg m4b_senc_args = { @@ -1141,7 +1173,7 @@ "## General considerations\n" "MP4Box supports encoding and decoding of of BT, XMT, VRML and (partially) X3D formats int MPEG-4 BIFS, and encoding and decoding of XSR and SVG into MPEG-4 LASeR\n" "Any media track specified through a `MuxInfo` element will be imported in the resulting MP4 file.\n" - "See https://wiki.gpac.io/MPEG-4-BIFS-Textual-Format and related pages.\n" + "See https://wiki.gpac.io/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format and related pages.\n" "## Scene Random Access\n" "MP4Box can encode BIFS or LASeR streams and insert random access points at a given frequency. This is useful when packaging content for broadcast, where users will not turn in the scene at the same time. In MPEG-4 terminology, this is called the __scene carousel__." "## BIFS Chunk Processing\n" @@ -1173,7 +1205,7 @@ u32 i=0; gf_sys_format_help(helpout, help_flags, "# Encryption/Decryption Options\n" "MP4Box supports encryption and decryption of ISMA, OMA and CENC content, see encryption filter `gpac -h cecrypt`(cecrypt).\n" - "It requires a specific XML file called `CryptFile`, whose syntax is available at https://wiki.gpac.io/Common-Encryption\n" + "It requires a specific XML file called `CryptFile`, whose syntax is available at https://wiki.gpac.io/xmlformats/Common-Encryption\n" "Image files (HEIF) can also be crypted / decrypted, using CENC only.\n" " \n" "Options:\n" @@ -1205,7 +1237,7 @@ MP4BOX_ARG("ts", "signal AU Time Stamps in RTP packets (MPEG-4 Systems)", GF_ARG_BOOL, 0, &hint_flags, GP_RTP_PCK_SIGNAL_TS, ARG_BIT_MASK), MP4BOX_ARG("size", "signal AU size in RTP packets (MPEG-4 Systems)", GF_ARG_BOOL, 0, &hint_flags, GP_RTP_PCK_SIGNAL_SIZE, ARG_BIT_MASK), MP4BOX_ARG("idx", "signal AU sequence numbers in RTP packets (MPEG-4 Systems)", GF_ARG_BOOL, 0, &hint_flags, GP_RTP_PCK_SIGNAL_AU_IDX, ARG_BIT_MASK), - MP4BOX_ARG("iod", "prevent systems tracks embedding in IOD (MPEG-4 Systems), not compatible with -isma()", GF_ARG_BOOL, 0, ®ular_iod, 0, 0), + MP4BOX_ARG("iod", "prevent systems tracks embedding in IOD (MPEG-4 Systems), not compatible with -isma(MP4B_GEN)", GF_ARG_BOOL, 0, ®ular_iod, 0, 0), #endif {0} }; @@ -1274,6 +1306,7 @@ MP4BOX_ARG_ALT("diso", "dmp4", "dump IsoMedia file boxes in XML output", GF_ARG_BOOL, 0, &dump_isom, 1, 0), MP4BOX_ARG("dxml", "dump IsoMedia file boxes and known track samples in XML output", GF_ARG_BOOL, 0, &dump_isom, 2, 0), MP4BOX_ARG("disox", "dump IsoMedia file boxes except sample tables in XML output", GF_ARG_BOOL, 0, &dump_isom, 3, 0), + MP4BOX_ARG("keep-comp", "do not decompress boxes when dumping", GF_ARG_BOOL, 0, &dump_keep_comp, 3, 0), MP4BOX_ARG("keep-ods", "do not translate ISOM ODs and ESDs tags (debug purpose only)", GF_ARG_BOOL, 0, &no_odf_conf, 0, 0), #ifndef GPAC_DISABLE_SCENE_DUMP MP4BOX_ARG("bt", "dump scene to BT format", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_BT, ARG_HAS_VALUE), @@ -1357,13 +1390,13 @@ "- type=itype: item 4cc type (not needed if mime is provided)\n" "- mime=mtype: item mime type, none if not set\n" "- encoding=enctype: item content-encoding type, none if not set\n" - "- id=ID: item ID\n" - "- ref=4cc,id: reference of type 4cc to an other item (can be set multiple times)\n" - "- group=id,type: indicate the id and type of an alternate group for this item\n" + "- id=ID: item ID (strictly positive integer)\n" + "- ref=4cc,id: reference of type 4cc (such as 'dimg', 'auxl', 'cdsc') to an other item (can be set multiple times)\n" + "- group=4cc,id: indicate the type 4cc (such as 'altr') and id of an entity group this item belongs to\n" "- replace: replace existing item by new item" , GF_ARG_STRING, 0, parse_meta_args, META_ACTION_ADD_ITEM, ARG_IS_FUN), MP4BOX_ARG("add-image", "add the given file as HEIF image item, with parameter syntax `file_path:opt1:optN`. If `filepath` is omitted, source is the input MP4 file\n" - "- name, id, ref: see -add-item()\n" + "- name, id, ref, group: see -add-item()\n" "- primary: indicate that this item should be the primary item\n" "- time=t-e/i: use the next sync sample after time t (float, in sec, default 0). A negative time imports ALL intra frames as items\n" " - If `e` is set (float, in sec), import all sync samples between `t` and `e`\n" @@ -1379,7 +1412,7 @@ "- icc_path: path to icc data to add as color info\n" "- alpha: indicate that the image is an alpha image (should use ref=auxl also)\n" "- depth: indicate that the image is a depth image (should use ref=auxl also)\n" - "- it=ID: indicate the item ID of the source item to import\n" + "- it=ID: indicate the item ID of the source item to import. If unspecified and source has no tracks, all items are imported\n" "- itp=ID: same as `it=` but copy over all properties of the source item\n" "- tk=tkID: indicate the track ID of the source sample. If 0, uses the first video track in the file\n" "- samp=N: indicate the sample number of the source sample\n" @@ -1390,7 +1423,7 @@ "- keep_props=4CCs: coma-separated list of properties types to keep when replacing the image, e.g. `keep_props=auxC`\n" "- auxt=URN: mark image as auxiliary using given `URN`\n" "- auxd=FILE: use data from `FILE` as auxiliary extensions (cf `auxC` box)\n" - "- any other options will be passed as options to the media importer, see -add()" + "- any other options will be passed as options to the media importer, see -add(MP4B_IMP)" , GF_ARG_STRING, 0, parse_meta_args, META_ACTION_ADD_IMAGE_ITEM, ARG_IS_FUN), MP4BOX_ARG("add-derived-image", "create an image grid, overlay or identity item, with parameter syntax `:type=(grid|iovl|iden):opt1:optN`\n" "- image-grid-size=rxc: set the number of rows and columns of the grid\n" @@ -1515,6 +1548,9 @@ "Tags are specified as a colon-separated list `tag_name=tag_value:tag2=val2`\n" "Setting a tag with no value or value `NULL` removes the tag.\n" "Special tag value `clear` (or `reset`) removes all tags.\n" + "Special tag value `cust` indicates a custom domain tag, in which case the tag value must start with `DOMAIN,MEAN,`.\n" + "EX -itags cust='com.apple.iTunes,iTunEXTC,My Tag Value'\n" + "The `DOMAIN` and/or `NAME` strings can be empty.\n" "Unsupported tags can be added using their four character code as a tag name, and string value will be assumed.\n" "If the tag name length is 3, the prefix 0xA9 is used to create the four character code.\n" " \n" @@ -1667,10 +1703,11 @@ i++; gf_sys_print_arg(helpout, help_flags, arg, "mp4box-general"); } - gf_sys_format_help(helpout, help_flags, "\nReturn codes are 0 for no error, 1 for error" + gf_sys_format_help(helpout, help_flags, "\nReturn codes are:\n- 0: no error\n- 1: error\n" #ifdef GPAC_MEMORY_TRACKING - " and 2 for memory leak detection when -mem-track is used" + "- 2: memory leak detection when -mem-track is used\n" #endif + "- 3: call is too early when resuming dashing from an existing context\n" "\n"); } @@ -1960,8 +1997,8 @@ if (!strnicmp(szSlot, "tk=", 3)) { parse_track_id(&meta->track_id, szSlot+3, GF_FALSE); - if (act_type == META_ACTION_ADD_ITEM) - meta->root_meta = 0; + //allowed for all action types + meta->root_meta = 0; } else if (!strnicmp(szSlot, "id=", 3)) { meta->item_id = parse_u32(szSlot+3, "id"); @@ -2149,7 +2186,8 @@ } else if (!strnicmp(szSlot, "icc_path=", 9)) { CHECK_IMGPROP - strcpy(meta->image_props->iccPath, szSlot+9); + strncpy(meta->image_props->iccPath, szSlot+9, GF_ARRAY_LENGTH(meta->image_props->iccPath)-1); + meta->image_props->iccPathGF_ARRAY_LENGTH(meta->image_props->iccPath)-1 = 0; } else if (!stricmp(szSlot, "agrid") || !strnicmp(szSlot, "agrid=", 6)) { CHECK_IMGPROP @@ -2884,13 +2922,13 @@ { open_edit = GF_TRUE; if (!opt) { - crypt = 1; + crypt_type = 1; drm_file = arg_val; open_edit = GF_TRUE; return 0; } - crypt = 2; - if (arg_val && get_file_type_by_ext(arg_val) != 1) { + crypt_type = 2; + if (arg_val && get_file_type_by_ext(arg_val) != GF_FILE_TYPE_ISO_MEDIA) { drm_file = arg_val; return 0; } @@ -3153,7 +3191,7 @@ else if (!strcmp(arg_val, "dump")) PrintDumpUsage(); else if (!strcmp(arg_val, "import")) PrintImportUsage(); else if (!strcmp(arg_val, "format")) { - M4_LOG(GF_LOG_WARNING, ("see filters documentation(Filters), `gpac -h codecs`, `gpac -h formats` and `gpac -h protocols` \n")); + M4_LOG(GF_LOG_WARNING, ("see https://wiki.gpac.io/Filters/Filters/, `gpac -h codecs`, `gpac -h formats` and `gpac -h protocols`\n")); } else if (!strcmp(arg_val, "hint")) PrintHintUsage(); else if (!strcmp(arg_val, "encode")) PrintEncodeUsage(); @@ -3285,7 +3323,7 @@ PrintLiveUsage(); #endif - fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/MP4Box\n"); + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/MP4Box/MP4Box\n"); fprintf(helpout, ".SH MORE\n.LP\nAuthors: GPAC developers, see git repo history (-log)\n" ".br\nFor bug reports, feature requests, more information and source code, visit https://github.com/gpac/gpac\n" ".br\nbuild: %s\n" @@ -3508,10 +3546,12 @@ u32 i; /*parse our args*/ for (i = 1; i < (u32)argc; i++) { + u32 arg_idx = i; char *arg = argvi; /*input file(s)*/ if ((arg0 != '-') || !stricmp(arg, "--")) { char *arg_val = arg; + gf_sys_mark_arg_used(i, GF_TRUE); if (!stricmp(arg, "--")) { if (i+1==(u32)argc) { M4_LOG(GF_LOG_ERROR, ("Missing arg for `--` - please check usage\n")); @@ -3520,6 +3560,7 @@ has_next_arg = GF_TRUE; arg_val = argvi + 1; i++; + arg_idx=i; } if (argc < 3) { M4_LOG(GF_LOG_ERROR, ("Error - only one input file found as argument, please check usage\n")); @@ -3543,7 +3584,7 @@ } //all deprecated options else if (!stricmp(arg, "-grab-ts") || !stricmp(arg, "-atsc") || !stricmp(arg, "-rtp")) { - M4_LOG(GF_LOG_ERROR, ("Deprecated fuctionnality `%s` - use gpac application\n", arg)); + M4_LOG(GF_LOG_ERROR, ("Deprecated functionality `%s` - use gpac application\n", arg)); return 2; } else if (!stricmp(arg, "-write-buffer")) { @@ -3568,10 +3609,12 @@ return 2; } else if (!strncmp(arg, "-p=", 3)) { + gf_sys_mark_arg_used(i, GF_TRUE); continue; } #ifndef GPAC_MEMORY_TRACKING else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack")) { + gf_sys_mark_arg_used(i, GF_TRUE); continue; } #endif @@ -3580,6 +3623,10 @@ else if (mp4box_parse_single_arg(argc, argv, arg, &i)) { if (arg_parse_res) return mp4box_cleanup(arg_parse_res); + + gf_sys_mark_arg_used(arg_idx, GF_TRUE); + if (i>arg_idx) + gf_sys_mark_arg_used(i, GF_TRUE); } //not a MP4Box arg else { @@ -3587,8 +3634,14 @@ if (res==0) { PrintHelp(arg, GF_FALSE, GF_TRUE); return 2; - } else if (res==2) { - i++; + } else { + if (strncmp(arg, "--", 2)) { + gf_sys_mark_arg_used(arg_idx, GF_TRUE); + } + if (res==2) { + gf_sys_mark_arg_used(arg_idx+1, GF_TRUE); + i++; + } } } //live scene encoder does not use the unified parsing and should be moved as a scene encoder filter @@ -3828,6 +3881,10 @@ u8 PL; GF_ESD *esd = gf_isom_get_esd(file, track, 1); if (!esd) return; + if (!esd->decoderConfig) { + gf_odf_desc_del((GF_Descriptor *) esd); + return; + } switch (esd->decoderConfig->streamType) { case 0x04: @@ -3849,11 +3906,11 @@ case GF_CODECID_AAC_MPEG2_LCP: case GF_CODECID_AAC_MPEG2_SSRP: case GF_CODECID_AAC_MPEG4: - { - GF_M4ADecSpecInfo adsi; - gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &adsi); - if (adsi.audioPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, adsi.audioPL); - } + if (esd && esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo) { + GF_M4ADecSpecInfo adsi; + gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &adsi); + if (adsi.audioPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, adsi.audioPL); + } break; default: if (!PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, 0xFE); @@ -3920,11 +3977,14 @@ GF_FileType get_file_type_by_ext(char *inName) { GF_FileType type = GF_FILE_TYPE_NOT_SUPPORTED; - char *ext = strrchr(inName, '.'); + char *ext = gf_file_ext_start(inName); + char *sep_opt = ext ? strchr(ext, ':') : NULL; + if (sep_opt) sep_opt0 = 0; + if (ext) { char *sep; - if (!strcmp(ext, ".gz")) ext = strrchr(ext-1, '.'); ext+=1; + //remove .gz if any sep = strchr(ext, '.'); if (sep) sep0 = 0; @@ -3942,6 +4002,7 @@ type = GF_FILE_TYPE_SWF; } else if (!stricmp(ext, "jp2")) { if (sep) sep0 = '.'; + if (sep_opt) sep_opt0 = ':'; return GF_FILE_TYPE_NOT_SUPPORTED; } else type = GF_FILE_TYPE_NOT_SUPPORTED; @@ -3952,6 +4013,7 @@ /*try open file in read mode*/ if (!type && gf_isom_probe_file(inName)) type = GF_FILE_TYPE_ISO_MEDIA; + if (sep_opt) sep_opt0 = ':'; return type; } @@ -4090,13 +4152,21 @@ orig_box_overhead = 0; final_box_overhead = 0; while (gf_bs_available(bs_in)) { + u32 size = gf_bs_read_u32(bs_in); + + if (size < 8) { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + u32 type = gf_bs_read_u32(bs_in); const char *b4cc = gf_4cc_to_str(type); const u8 *replace = (const u8 *) strstr(compress_top_boxes, b4cc); if (!strcmp(b4cc, "moov")) has_mov = GF_TRUE; if (!replace && !replace_all) { + gf_bs_write_u32(bs_out, size); gf_bs_write_u32(bs_out, type); @@ -4223,6 +4293,7 @@ fout = gf_fopen(inName, "a+b"); if (!fout) { + fprintf(stderr, "Error opening file %s\n", inName); gf_fclose(fin); return mp4box_cleanup(1); } @@ -4234,7 +4305,7 @@ u32 nb_bytes = (u32) gf_fread(chunk, 4096, fin); if (gf_fwrite(chunk, nb_bytes, fout) != nb_bytes) { ret = 1; - fprintf(stderr, "Error appengin file\n"); + fprintf(stderr, "Error appending file\n"); break; } done += nb_bytes; @@ -4375,20 +4446,27 @@ possibly for later export (e.g. when converting SRT to TTXT, ...) */ #ifndef GPAC_DISABLE_MEDIA_IMPORT GF_Err e; - GF_MediaImporter import; + GF_MediaImporter *import; + /* Prepare the importer */ + GF_SAFEALLOC(import, GF_MediaImporter); + if (!import) { + M4_LOG(GF_LOG_ERROR, ("Allocation failed for importer\n")); + return mp4box_cleanup(1); + } + file = gf_isom_open("ttxt_convert", GF_ISOM_OPEN_WRITE, NULL); if (timescale && file) gf_isom_set_timescale(file, timescale); - memset(&import, 0, sizeof(GF_MediaImporter)); - import.dest = file; - import.in_name = inName; + import->dest = file; + import->in_name = inName; /* Start the import */ - e = gf_media_import(&import); + e = gf_media_import(import); if (e) { M4_LOG(GF_LOG_ERROR, ("Error importing %s: %s\n", inName, gf_error_to_string(e))); gf_isom_delete(file); gf_file_delete("ttxt_convert"); + gf_free(import); return mp4box_cleanup(1); } /* Prepare the export */ @@ -4407,6 +4485,7 @@ /* Clean the importer */ gf_isom_delete(file); gf_file_delete("ttxt_convert"); + gf_free(import); if (e) { M4_LOG(GF_LOG_ERROR, ("Error converting %s: %s\n", inName, gf_error_to_string(e))); return mp4box_cleanup(1); @@ -4687,6 +4766,7 @@ { GF_Err e; u32 i; + Bool call_too_early = GF_FALSE; Bool del_file = GF_FALSE; char szMPDGF_MAX_PATH, *sep; char szStateFileGF_MAX_PATH; @@ -4694,7 +4774,7 @@ u32 do_abort = 0; GF_DASHSegmenter *dasher=NULL; - if (crypt) { + if (crypt_type) { M4_LOG(GF_LOG_ERROR, ("MP4Box cannot use -crypt and -dash in the same pass. Please encrypt your content first, or specify encryption filters on dash sources.\n")); return GF_BAD_PARAM; } @@ -4714,6 +4794,9 @@ if ((dash_subduration>0) && (dash_duration > dash_subduration)) { M4_LOG(GF_LOG_WARNING, ("Warning: -subdur parameter (%g s) should be greater than segment duration (%g s), using segment duration instead\n", dash_subduration, dash_duration)); dash_subduration = dash_duration; + } else if (dash_live && !dash_subduration) { + M4_LOG(GF_LOG_WARNING, ("Warning: dash-live with no -subdur parameter no longer supported, using segment duration as subdur %g s\n", dash_duration)); + dash_subduration = dash_duration; } if (dash_mode && dash_live) @@ -4833,6 +4916,7 @@ if (!dash_live && (e==GF_EOS) ) { M4_LOG(GF_LOG_INFO, ("Nothing to dash, too early ...\n")); e = GF_OK; + if (!dash_live) call_too_early = GF_TRUE; } if (do_abort) @@ -4906,6 +4990,7 @@ if (del_file) gf_file_delete(inName); + if (call_too_early) return GF_NOT_READY; return e; } @@ -4928,6 +5013,11 @@ mdump.track_type = tka->target_track.type-1; mdump.sample_num = tka->sample_num; + if (gf_isom_is_external_track(file, gf_isom_get_track_by_id(file, mdump.trackID), NULL, NULL, NULL, NULL) && (tka->act_type>TRACK_ACTION_SET_TIME)) { + M4_LOG(GF_LOG_ERROR, ("Track extraction not allowed on external tracks\n")); + return GF_BAD_PARAM; + } + if (dump_std) { mdump.out_name = "std"; } @@ -5034,6 +5124,12 @@ //this can be 0 mdump.trackID = get_track_id(file, &tka->target_track); + if (gf_isom_is_external_track(file, gf_isom_get_track_by_id(file, mdump.trackID), NULL, NULL, NULL, NULL) && (tka->act_type>TRACK_ACTION_SET_TIME)) { + M4_LOG(GF_LOG_ERROR, ("Track extraction not allowed on external tracks\n")); + return GF_BAD_PARAM; + } + + if (tka->out_name) { mdump.out_name = tka->out_name; } else if (outName) { @@ -5081,7 +5177,13 @@ MetaAction *meta = &metasi; u32 tk_id = get_track_id(file, &meta->track_id); - if (tk_id) tk = gf_isom_get_track_by_id(file, tk_id); + if (tk_id) + tk = gf_isom_get_track_by_id(file, tk_id); + + if (!tk && meta->track_id.ID_or_num) { + M4_LOG(GF_LOG_ERROR, ("No such track %s %d in destination file\n", (meta->track_id.type==1) ? "number" : "ID", meta->track_id.ID_or_num)); + return GF_BAD_PARAM; + } switch (meta->act_type) { #ifndef GPAC_DISABLE_ISOM_WRITE @@ -5102,16 +5204,19 @@ if (e) break; } } - + if (!meta->szPath) { + M4_LOG(GF_LOG_ERROR, ("No item path specified\n", meta->item_id)); + return GF_BAD_PARAM; + } self_ref = !stricmp(meta->szPath, "NULL") || !stricmp(meta->szPath, "this") || !stricmp(meta->szPath, "self"); - e = gf_isom_add_meta_item(file, meta->root_meta, tk, self_ref, self_ref ? NULL : meta->szPath, - meta->szName, - meta->item_id, - meta->item_type, - meta->mime_type, - meta->enc_type, - meta->use_dref ? meta->szPath : NULL, NULL, - meta->image_props); + e = gf_isom_add_meta_item2(file, meta->root_meta, tk, self_ref, self_ref ? NULL : meta->szPath, + meta->szName, + &meta->item_id, + meta->item_type, + meta->mime_type, + meta->enc_type, + meta->use_dref ? meta->szPath : NULL, NULL, + meta->image_props); if (meta->item_refs && gf_list_count(meta->item_refs)) { u32 ref_i; for (ref_i = 0; ref_i < gf_list_count(meta->item_refs); ref_i++) { @@ -5119,6 +5224,9 @@ e = gf_isom_meta_add_item_ref(file, meta->root_meta, tk, meta->item_id, ref_entry->ref_item_id, ref_entry->ref_type, NULL); } } + if (e == GF_OK && meta->group_type) { + e = gf_isom_meta_add_item_group(file, meta->root_meta, tk, meta->item_id, meta->group_id, meta->group_type); + } do_save = GF_TRUE; break; case META_ACTION_ADD_IMAGE_ITEM: @@ -5127,6 +5235,7 @@ u32 src_tk_id = 1; GF_Fraction _frac = {0,0}; GF_ISOFile *fsrc = file; + Bool add_all_src_items = GF_FALSE; self_ref = GF_FALSE; tk = 0; @@ -5138,9 +5247,28 @@ self_ref = GF_TRUE; src_tk_id = tk_id; } else if (meta->szPath) { - if (meta->image_props && gf_isom_probe_file(meta->szPath) && !meta->image_props->tile_mode) { - meta->image_props->src_file = gf_isom_open(meta->szPath, GF_ISOM_OPEN_READ, NULL); - e = gf_isom_last_error(meta->image_props->src_file); + e = GF_OK; + if (src_tk_id && !meta->image_props && gf_isom_probe_file(meta->szPath)) { + GF_ISOFile *src_file = gf_isom_open(meta->szPath, GF_ISOM_OPEN_READ, NULL); + if (src_file && !gf_isom_get_track_count(src_file)) { + GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) { + e = GF_OUT_OF_MEM; + gf_isom_delete(src_file); + } else { + meta->image_props->src_file = src_file; + src_tk_id = 0; + add_all_src_items = GF_TRUE; + } + } else if (src_file) { + gf_isom_delete(src_file); + } + } + if ((e==GF_OK) && meta->image_props && gf_isom_probe_file(meta->szPath) && !meta->image_props->tile_mode) { + if (!meta->image_props->src_file) { + meta->image_props->src_file = gf_isom_open(meta->szPath, GF_ISOM_OPEN_READ, NULL); + e = gf_isom_last_error(meta->image_props->src_file); + } fsrc = meta->image_props->src_file; if (meta->image_props->item_ref_id) src_tk_id = 0; @@ -5168,7 +5296,7 @@ e = gf_isom_meta_get_next_item_id(file, meta->root_meta, tk, &meta->item_id); } if (e == GF_OK) { - if (!src_tk_id && (!meta->image_props || !meta->image_props->item_ref_id) ) { + if (!src_tk_id && (!meta->image_props || (!add_all_src_items && !meta->image_props->item_ref_id)) ) { u32 j; for (j=0; j<gf_isom_get_track_count(fsrc); j++) { if (gf_isom_is_video_handler_type (gf_isom_get_media_type(fsrc, j+1))) { @@ -5271,6 +5399,10 @@ if (e) break; } } + if (e == GF_OK && meta->group_type) { + e = gf_isom_meta_add_item_group(file, meta->root_meta, tk, meta->item_id, meta->group_id, meta->group_type); + if (e) break; + } } do_save = GF_TRUE; } @@ -5448,6 +5580,11 @@ u32 trackID = get_track_id(file, &tka->target_track); u32 track = trackID ? gf_isom_get_track_by_id(file, trackID) : 0; + if (gf_isom_is_external_track(file, track, NULL, NULL, NULL, NULL) && (tka->act_type>TRACK_ACTION_SET_TIME)) { + M4_LOG(GF_LOG_ERROR, ("Track action not allowed on external tracks\n")); + return GF_BAD_PARAM; + } + u32 newTrackID = get_track_id(file, &tka->newTrackID); timescale = gf_isom_get_timescale(file); @@ -5704,7 +5841,13 @@ next_tag_idx = 0; } //3CC tag, changed to @tag - if ( strlen(sep+1)==3) { + else if ( strlen(sep+1)==3) { + next_tag_idx = 0; + } + else if (!strncmp(sep+1, "WM/", 3)) { + next_tag_idx = 0; + } + else if (!strncmp(sep+1, "QT/", 3)) { next_tag_idx = 0; } //unrecognized tag tag @@ -5971,6 +6114,18 @@ gf_free(dash_inputs); dash_inputs = NULL; } + if (!ret_code) { + u32 i, found=0, count = gf_sys_get_argc(); + for (i=1; i<count;i++) { + if (gf_sys_is_arg_used(i)) continue; + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("\nWarning: the following arguments have been set but not used:\n")); + found = 1; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("%s\n", gf_sys_get_arg(i))); + } + } + if (logfile) gf_fclose(logfile); gf_sys_close(); @@ -5978,6 +6133,7 @@ if (mem_track && (gf_memory_size() || gf_file_handles_count() )) { gf_log_set_tool_level(GF_LOG_MEMORY, GF_LOG_INFO); gf_memory_print(); + if (!ret_code) ret_code = 2; } #endif return ret_code; @@ -6040,6 +6196,24 @@ helpout = stdout; + GF_LOG_Level level = verbose ? GF_LOG_DEBUG : GF_LOG_INFO; + gf_log_set_tool_level(GF_LOG_CONTAINER, level); + gf_log_set_tool_level(GF_LOG_SCENE, level); + gf_log_set_tool_level(GF_LOG_PARSER, level); + gf_log_set_tool_level(GF_LOG_MEDIA, level); + gf_log_set_tool_level(GF_LOG_CODING, level); + gf_log_set_tool_level(GF_LOG_DASH, level); +#ifdef GPAC_MEMORY_TRACKING + if (mem_track) + gf_log_set_tool_level(GF_LOG_MEMORY, level); +#endif + + e = gf_sys_set_args(argc, (const char **) argv); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error assigning libgpac arguments: %s\n", gf_error_to_string(e) )); + return mp4box_cleanup(1); + } + i = mp4box_parse_args(argc, argv); if (i) { return mp4box_cleanup(i - 1); @@ -6105,18 +6279,6 @@ } } - GF_LOG_Level level = verbose ? GF_LOG_DEBUG : GF_LOG_INFO; - gf_log_set_tool_level(GF_LOG_CONTAINER, level); - gf_log_set_tool_level(GF_LOG_SCENE, level); - gf_log_set_tool_level(GF_LOG_PARSER, level); - gf_log_set_tool_level(GF_LOG_MEDIA, level); - gf_log_set_tool_level(GF_LOG_CODING, level); - gf_log_set_tool_level(GF_LOG_DASH, level); -#ifdef GPAC_MEMORY_TRACKING - if (mem_track) - gf_log_set_tool_level(GF_LOG_MEMORY, level); -#endif - if (fuzz_chk) { file = gf_isom_open(inName, GF_ISOM_OPEN_READ_DUMP, NULL); if (file) gf_isom_close(file); @@ -6127,12 +6289,6 @@ return mp4box_cleanup(0); } - e = gf_sys_set_args(argc, (const char **) argv); - if (e) { - M4_LOG(GF_LOG_ERROR, ("Error assigning libgpac arguments: %s\n", gf_error_to_string(e) )); - return mp4box_cleanup(1); - } - if (raw_cat) return do_raw_cat(); @@ -6254,7 +6410,8 @@ if (dash_duration) { e = do_dash(); - if (e) return mp4box_cleanup(1); + if (e==GF_NOT_READY) return mp4box_cleanup(3); + else if (e) return mp4box_cleanup(1); goto exit; } @@ -6270,6 +6427,13 @@ //need to open input if (!file && !do_hash) { + char *ext = gf_file_ext_start(inName); + char *sep_opt = ext ? strchr(ext, ':') : NULL; + if (sep_opt) { + M4_LOG(GF_LOG_WARNING, ("Input name should not use options, ignoring %s\n", sep_opt)); + sep_opt0 = 0; + } + FILE *st = gf_fopen(inName, "rb"); Bool file_exists = 0; GF_ISOOpenMode omode; @@ -6278,17 +6442,19 @@ gf_fclose(st); } switch (get_file_type_by_ext(inName)) { - case 1: - omode = (u8) (force_new ? GF_ISOM_WRITE_EDIT : (open_edit ? GF_ISOM_OPEN_EDIT : ( ((dump_isom>0) || print_info) ? GF_ISOM_OPEN_READ_DUMP : GF_ISOM_OPEN_READ) ) ); + case GF_FILE_TYPE_ISO_MEDIA: + omode = (u8) (force_new ? GF_ISOM_WRITE_EDIT : (open_edit ? GF_ISOM_OPEN_EDIT : ( ((dump_isom>0) || print_info) ? GF_ISOM_OPEN_READ_DUMP : GF_ISOM_OPEN_READ) ) ); + if ((dump_isom>0) && dump_keep_comp) + omode = GF_ISOM_OPEN_READ_DUMP_NO_COMP; - if (crypt) { + if (crypt_type) { //keep fragment signaling in moov omode = GF_ISOM_OPEN_READ; if (use_init_seg) file = gf_isom_open(use_init_seg, GF_ISOM_OPEN_READ, NULL); } - if (!crypt && use_init_seg) { - file = gf_isom_open(use_init_seg, GF_ISOM_OPEN_READ_DUMP, NULL); + if (!crypt_type && use_init_seg) { + file = gf_isom_open(use_init_seg, omode, NULL); if (file) { #ifndef GPAC_DISABLE_ISOM_FRAGMENTS e = gf_isom_open_segment(file, inName, 0, 0, 0); @@ -6330,15 +6496,15 @@ #endif break; /*allowed for bt<->xmt*/ - case 2: - case 3: + case GF_FILE_TYPE_BT_WRL_X3DV: + case GF_FILE_TYPE_XMT_X3D: /*allowed for svg->lsr**/ - case 4: + case GF_FILE_TYPE_SVG: /*allowed for swf->bt, swf->xmt, swf->svg*/ - case 5: + case GF_FILE_TYPE_SWF: break; /*used for .saf / .lsr dump*/ - case 6: + case GF_FILE_TYPE_LSR_SAF: #ifndef GPAC_DISABLE_SCENE_DUMP if ((dump_mode==GF_SM_DUMP_LASER) || (dump_mode==GF_SM_DUMP_SVG)) { break; @@ -6351,24 +6517,28 @@ #ifndef GPAC_DISABLE_ISOM_WRITE else if (!open_edit && file_exists /* && !gf_isom_probe_file(inName) */ #ifndef GPAC_DISABLE_SCENE_DUMP - && dump_mode == GF_SM_DUMP_NONE + && dump_mode == GF_SM_DUMP_NONE #endif //GPAC_DISABLE_SCENE_DUMP - ) { + ) { /*************************************************************************************************/ #ifndef GPAC_DISABLE_MEDIA_IMPORT - if(dvbhdemux) - { - GF_MediaImporter import; + if(dvbhdemux) { + GF_MediaImporter *import; file = gf_isom_open("ttxt_convert", GF_ISOM_OPEN_WRITE, NULL); - memset(&import, 0, sizeof(GF_MediaImporter)); - import.dest = file; - import.in_name = inName; - import.flags = GF_IMPORT_MPE_DEMUX; - e = gf_media_import(&import); + GF_SAFEALLOC(import, GF_MediaImporter); + if (import) { + import->dest = file; + import->in_name = inName; + import->flags = GF_IMPORT_MPE_DEMUX; + e = gf_media_import(import); + } else { + e = GF_OUT_OF_MEM; + } if (e) { M4_LOG(GF_LOG_ERROR, ("Error importing %s: %s\n", inName, gf_error_to_string(e))); gf_isom_delete(file); gf_file_delete("ttxt_convert"); + if (import) gf_free(import); return mp4box_cleanup(1); } } @@ -6503,7 +6673,7 @@ } #ifndef GPAC_DISABLE_ISOM_DUMP if (dump_isom) { - e = dump_isom_xml(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, (dump_isom==2) ? GF_TRUE : GF_FALSE, merge_vtt_cues, use_init_seg ? GF_TRUE : GF_FALSE, (dump_isom==3) ? GF_TRUE : GF_FALSE); + e = dump_isom_xml(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, (dump_isom==2) ? GF_TRUE : GF_FALSE, merge_vtt_cues, use_init_seg ? inName : NULL, (dump_isom==3) ? GF_TRUE : GF_FALSE); if (e) goto err_exit; } if (dump_cr) dump_isom_ismacryp(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE); @@ -6677,7 +6847,7 @@ if ((conv_type == GF_ISOM_CONV_TYPE_ISMA) || (conv_type == GF_ISOM_CONV_TYPE_ISMA_EX)) { M4_LOG(GF_LOG_INFO, ("Converting to ISMA Audio-Video MP4 file\n")); /*keep ESIDs when doing ISMACryp*/ - e = gf_media_make_isma(file, crypt ? 1 : 0, GF_FALSE, (conv_type==GF_ISOM_CONV_TYPE_ISMA_EX) ? 1 : 0); + e = gf_media_make_isma(file, crypt_type ? 1 : 0, GF_FALSE, (conv_type==GF_ISOM_CONV_TYPE_ISMA_EX) ? 1 : 0); if (e) goto err_exit; do_save = GF_TRUE; } @@ -6759,19 +6929,19 @@ } #ifndef GPAC_DISABLE_CRYPTO - if (crypt) { - if (!drm_file && (crypt==1) ) { - M4_LOG(GF_LOG_ERROR, ("Missing DRM file location - usage '-%s drm_file input_file\n", (crypt==1) ? "crypt" : "decrypt")); + if (crypt_type) { + if (!drm_file && (crypt_type==1) ) { + M4_LOG(GF_LOG_ERROR, ("Missing DRM file location - usage '-%s drm_file input_file\n", (crypt_type==1) ? "crypt" : "decrypt")); e = GF_BAD_PARAM; goto err_exit; } - if (crypt == 1) { + if (crypt_type == 1) { if (use_init_seg) { e = gf_crypt_fragment(file, drm_file, outfile, inName, fs_dump_flags); } else { e = gf_crypt_file(file, drm_file, outfile, interleaving_time, fs_dump_flags); } - } else if (crypt ==2) { + } else if (crypt_type ==2) { if (use_init_seg) { e = gf_decrypt_fragment(file, drm_file, outfile, inName, fs_dump_flags); } else { @@ -6933,16 +7103,7 @@ return mp4box_cleanup(1); exit: - mp4box_cleanup(0); - -#ifdef GPAC_MEMORY_TRACKING - if (mem_track && (gf_memory_size() || gf_file_handles_count() )) { - gf_log_set_tool_level(GF_LOG_MEMORY, GF_LOG_INFO); - gf_memory_print(); - return 2; - } -#endif - return 0; + return mp4box_cleanup(0); } #if !defined(GPAC_CONFIG_ANDROID) && !defined(GPAC_CONFIG_EMSCRIPTEN)
View file
gpac-2.4.0.tar.gz/applications/mp4box/mp4box.h -> gpac-26.02.0.tar.gz/applications/mp4box/mp4box.h
Changed
@@ -72,6 +72,7 @@ #endif Bool mp4box_check_isom_fileopt(char *opt); +Bool mp4box_check_non_extk_fileopt(char *opt); #ifndef GPAC_DISABLE_ISOM_WRITE @@ -118,7 +119,7 @@ u32 PrintBuiltInBoxes(char *arg_val, u32 do_cov); #ifndef GPAC_DISABLE_ISOM_DUMP -GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump, Bool merge_vtt_cues, Bool skip_init, Bool skip_samples); +GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump, Bool merge_vtt_cues, const char *init_seg, Bool skip_samples); #endif
View file
gpac-2.4.0.tar.gz/build/android/README -> gpac-26.02.0.tar.gz/build/android/README
Changed
@@ -3,4 +3,4 @@ ********************************* -See https://wiki.gpac.io/GPAC-Build-Guide-for-Android +See https://wiki.gpac.io/Build/build/GPAC-Build-Guide-for-Android
View file
gpac-2.4.0.tar.gz/build/android/jni/common.mk -> gpac-26.02.0.tar.gz/build/android/jni/common.mk
Changed
@@ -1,6 +1,6 @@ COMMON_PATH := $(call my-dir) -# Common Flags for ligpac and modules +# Common Flags for libgpac and modules LOCAL_CFLAGS += -DGPAC_CONFIG_ANDROID LOCAL_CFLAGS += -DGPAC_HAVE_CONFIG_H LOCAL_CFLAGS += -DXP_UNIX
View file
gpac-2.4.0.tar.gz/build/android/jni/gpac_build_android -> gpac-26.02.0.tar.gz/build/android/jni/gpac_build_android
Changed
@@ -7,7 +7,7 @@ CLEAN=0 #DEBUG="NDK_DEBUG=0" DEBUG="NDK_DEBUG=0 APP_OPTIM=release" -#default should be assembleRelease but not working with gradle 2, to fix +#default should be assembleRelease but not working with gradle 2, to fix GRADLE_FLAG="assembleDebug" VERB="" @@ -66,7 +66,7 @@ echo "-h: print this screen" echo "-v: verbose build" echo "" -echo -e "\n\03332m If you have problem using this script, check wiki.gpac.io/GPAC-Build-Guide-for-Android or gpac/build/android/README \0330m\n" +echo -e "\n\03332m If you have problem using this script, check https://wiki.gpac.io/Build/build/GPAC-Build-Guide-for-Android or gpac/build/android/README \0330m\n" exit 0 fi
View file
gpac-2.4.0.tar.gz/build/android/jni/libgpac/Android.mk -> gpac-26.02.0.tar.gz/build/android/jni/libgpac/Android.mk
Changed
@@ -34,7 +34,7 @@ LOCAL_CFLAGS += -DGPAC_HAVE_CONFIG_H LOCAL_CFLAGS += -DNO_MALLINFO LOCAL_CFLAGS += -DGPAC_CONFIG_ANDROID -LOCAL_CFLAGS += -DGPAC_DISABLE_REMOTERY +LOCAL_CFLAGS += -DGPAC_DISABLE_RMTWS #for now QJS libc is disabled on android LOCAL_CFLAGS += -DGPAC_DISABLE_QJS_LIBC @@ -163,6 +163,7 @@ ../../../../src/filters/dec_odf.c \ ../../../../src/filters/dec_openhevc.c \ ../../../../src/filters/dec_opensvc.c \ + ../../../../src/filters/dec_scte35.c \ ../../../../src/filters/decrypt_cenc_isma.c \ ../../../../src/filters/dec_theora.c \ ../../../../src/filters/dec_ttml.c \ @@ -186,6 +187,7 @@ ../../../../src/filters/dmx_ogg.c \ ../../../../src/filters/dmx_saf.c \ ../../../../src/filters/dmx_vobsub.c \ + ../../../../src/filters/enc_cc.c \ ../../../../src/filters/enc_jpg.c \ ../../../../src/filters/enc_png.c \ ../../../../src/filters/encrypt_cenc_isma.c \ @@ -202,6 +204,7 @@ ../../../../src/filters/hevcmerge.c \ ../../../../src/filters/hevcsplit.c \ ../../../../src/filters/in_route.c \ + ../../../../src/filters/in_route_repair.c \ ../../../../src/filters/in_dvb4linux.c \ ../../../../src/filters/in_file.c \ ../../../../src/filters/in_http.c \ @@ -233,6 +236,7 @@ ../../../../src/filters/out_rtsp.c \ ../../../../src/filters/out_sock.c \ ../../../../src/filters/reframe_ac3.c \ + ../../../../src/filters/reframe_ac4.c \ ../../../../src/filters/reframe_adts.c \ ../../../../src/filters/reframe_amr.c \ ../../../../src/filters/reframe_av1.c \ @@ -253,11 +257,13 @@ ../../../../src/filters/resample_audio.c \ ../../../../src/filters/restamp.c \ ../../../../src/filters/rewind.c \ + ../../../../src/filters/rewrite_ac4.c \ ../../../../src/filters/rewrite_adts.c \ ../../../../src/filters/rewrite_mhas.c \ ../../../../src/filters/rewrite_mp4v.c \ ../../../../src/filters/rewrite_nalu.c \ ../../../../src/filters/rewrite_obu.c \ + ../../../../src/filters/sei_load.c \ ../../../../src/filters/tileagg.c \ ../../../../src/filters/tilesplit.c \ ../../../../src/filters/tssplit.c \ @@ -331,6 +337,7 @@ ../../../../src/media_tools/dsmcc.c \ ../../../../src/media_tools/dvb_mpe.c \ ../../../../src/media_tools/gpac_ogg.c \ + ../../../../src/media_tools/id3.c \ ../../../../src/media_tools/img.c \ ../../../../src/media_tools/isom_hinter.c \ ../../../../src/media_tools/isom_tools.c \ @@ -359,7 +366,7 @@ ../../../../src/odf/qos.c \ ../../../../src/odf/slc.c \ ../../../../src/quickjs/cutils.c \ - ../../../../src/quickjs/libbf.c \ + ../../../../src/quickjs/dtoa.c \ ../../../../src/quickjs/libregexp.c \ ../../../../src/quickjs/libunicode.c \ ../../../../src/quickjs/quickjs.c \ @@ -402,11 +409,16 @@ ../../../../src/utils/alloc.c \ ../../../../src/utils/base_encoding.c \ ../../../../src/utils/bitstream.c \ - ../../../../src/utils/cache.c \ ../../../../src/utils/color.c \ ../../../../src/utils/configfile.c \ ../../../../src/utils/constants.c \ ../../../../src/utils/downloader.c \ + ../../../../src/utils/downloader_cache.c \ + ../../../../src/utils/downloader_curl.c \ + ../../../../src/utils/downloader_hmux.c \ + ../../../../src/utils/downloader_nghttp2.c \ + ../../../../src/utils/downloader_ngtcp2.c \ + ../../../../src/utils/downloader_ssl.c \ ../../../../src/utils/error.c \ ../../../../src/utils/gltools.c \ ../../../../src/utils/gzio.c \ @@ -421,13 +433,16 @@ ../../../../src/utils/os_thread.c \ ../../../../src/utils/path2d.c \ ../../../../src/utils/path2d_stroker.c \ + ../../../../src/utils/rmt_ws.c \ ../../../../src/utils/sha1.c \ ../../../../src/utils/sha256.c \ + ../../../../src/utils/md5.c \ ../../../../src/utils/token.c \ ../../../../src/utils/uni_bidi.c \ ../../../../src/utils/unicode.c \ ../../../../src/utils/url.c \ ../../../../src/utils/utf.c \ + ../../../../src/utils/xml_bin_custom.c \ ../../../../src/utils/xml_parser.c \ ../../../../src/utils/zutil.c
View file
gpac-2.4.0.tar.gz/build/android/jni/libgpac/config.h -> gpac-26.02.0.tar.gz/build/android/jni/libgpac/config.h
Changed
@@ -3,6 +3,9 @@ #define GF_CONFIG_H #define GPAC_CONFIG_LINUX 1 +#define GPAC_SCHED_DEFAULT "lock" +#define GF_STATIC static +#define GF_NOT_EXPORTED #define GPAC_HAS_QJS 1 #define GPAC_HAS_MEDIACODEC #define GPAC_HAS_JPEG 1
View file
gpac-2.4.0.tar.gz/build/deprecated/symbian/libgpac.mmp -> gpac-26.02.0.tar.gz/build/deprecated/symbian/libgpac.mmp
Changed
@@ -56,6 +56,7 @@ SOURCE uni_bidi.c SOURCE url.c SOURCE utf.c +SOURCE xml_bin_custom.c SOURCE xml_parser.c SOURCE sha1.c // zlib symbian on sybian SDKs doesn't come with gzio
View file
gpac-2.4.0.tar.gz/build/docker/ubuntu-deps.Dockerfile -> gpac-26.02.0.tar.gz/build/docker/ubuntu-deps.Dockerfile
Changed
@@ -3,7 +3,7 @@ # set up the machine RUN apt-get -yqq update && apt-get install -y --no-install-recommends build-essential pkg-config g++ git cmake yasm fakeroot dpkg-dev devscripts debhelper ccache -RUN apt-get install -y --no-install-recommends zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils +RUN apt-get install -y --no-install-recommends zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libnghttp2-dev libopenjp2-7-dev libcaca-dev libxv-dev x11proto-video-dev libgl1-mesa-dev libglu1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-jackd2-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils libcurl4-openssl-dev RUN apt-get autoremove -y && apt-get clean -y
View file
gpac-26.02.0.tar.gz/build/docker/wasm.Dockerfile
Added
@@ -0,0 +1,56 @@ +# Common Dockerfile for building GPAC WASM +FROM ubuntu:latest AS base + +ARG EMSDK_VERSION=4.0.12 +ENV DEBIAN_FRONTEND=noninteractive + +# Install system dependencies +RUN apt-get -yqq update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + pkg-config \ + python3 \ + g++ \ + git \ + cmake \ + wget autotools-dev automake autoconf libtool && \ + apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# Get the emsdk repo +RUN git clone --depth 1 https://github.com/emscripten-core/emsdk.git + +RUN cd emsdk && ./emsdk install $EMSDK_VERSION && ./emsdk activate $EMSDK_VERSION + +# Update environment variables +ENV PATH="/emsdk:/emsdk/upstream/emscripten:${PATH}" +ENV EMSDK="/emsdk" +ENV EMSDK_NODE="/emsdk/node/22.16.0_64bit/bin/node" + +# Build GPAC WASM dependencies +FROM base AS deps + +# Install GPAC WASM dependencies +RUN git clone --recurse-submodules --depth 1 https://github.com/gpac/deps_wasm + +# Build GPAC WASM dependencies +WORKDIR /deps_wasm +RUN ./wasm_extra_libs.sh --enable-threading + +# Build GPAC +FROM base AS gpac + +# Copy GPAC source code +COPY . /gpac_public +WORKDIR /gpac_public + +# Copy GPAC WASM dependencies +COPY --from=deps /deps_wasm/wasm_thread /deps_wasm/wasm_thread +ENV PKG_CONFIG_PATH=/deps_wasm/wasm_thread/lib/pkgconfig + + +# Configure GPAC +RUN make distclean; ./configure --emscripten --extra-cflags="-Wno-pointer-sign -Wno-implicit-const-int-float-conversion" + +# Build GPAC +RUN make -j$(nproc) +RUN cp -a --remove-destination /gpac_public/share/emscripten/gpac.html /gpac_public/bin/gcc/
View file
gpac-2.4.0.tar.gz/build/msvc14/gpac.vcxproj -> gpac-26.02.0.tar.gz/build/msvc14/gpac.vcxproj
Changed
@@ -138,6 +138,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -183,6 +184,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -225,6 +227,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -270,6 +273,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -312,6 +316,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -353,6 +358,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
View file
gpac-2.4.0.tar.gz/build/msvc14/libgpac.vcxproj -> gpac-26.02.0.tar.gz/build/msvc14/libgpac.vcxproj
Changed
@@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug - MP4Box_only|Win32"> @@ -41,7 +41,6 @@ <ClInclude Include="..\..\include\gpac\base_coding.h" /> <ClInclude Include="..\..\include\gpac\bifs.h" /> <ClInclude Include="..\..\include\gpac\bitstream.h" /> - <ClInclude Include="..\..\include\gpac\cache.h" /> <ClInclude Include="..\..\include\gpac\color.h" /> <ClInclude Include="..\..\include\gpac\compositor.h" /> <ClInclude Include="..\..\include\gpac\configuration.h" /> @@ -105,7 +104,7 @@ <ClInclude Include="..\..\include\gpac\nodes_svg.h" /> <ClInclude Include="..\..\include\gpac\nodes_x3d.h" /> <ClInclude Include="..\..\include\gpac\path2d.h" /> - <ClInclude Include="..\..\include\gpac\Remotery.h" /> + <ClInclude Include="..\..\include\gpac\rmt_ws.h" /> <ClInclude Include="..\..\include\gpac\revision.h" /> <ClInclude Include="..\..\include\gpac\rtp_streamer.h" /> <ClInclude Include="..\..\include\gpac\scenegraph.h" /> @@ -264,6 +263,7 @@ <ClCompile Include="..\..\src\filters\dec_odf.c" /> <ClCompile Include="..\..\src\filters\dec_openhevc.c" /> <ClCompile Include="..\..\src\filters\dec_opensvc.c" /> + <ClCompile Include="..\..\src\filters\dec_scte35.c" /> <ClCompile Include="..\..\src\filters\dec_theora.c" /> <ClCompile Include="..\..\src\filters\dec_ttml.c" /> <ClCompile Include="..\..\src\filters\dec_ttxt.c" /> @@ -283,6 +283,7 @@ <ClCompile Include="..\..\src\filters\dmx_ogg.c" /> <ClCompile Include="..\..\src\filters\dmx_saf.c" /> <ClCompile Include="..\..\src\filters\dmx_vobsub.c" /> + <ClCompile Include="..\..\src\filters\enc_cc.c" /> <ClCompile Include="..\..\src\filters\enc_jpg.c" /> <ClCompile Include="..\..\src\filters\enc_png.c" /> <ClCompile Include="..\..\src\filters\encrypt_cenc_isma.c" /> @@ -300,6 +301,7 @@ <ClCompile Include="..\..\src\filters\hevcsplit.c" /> <ClCompile Include="..\..\src\filters\inspect.c" /> <ClCompile Include="..\..\src\filters\in_route.c" /> + <ClCompile Include="..\..\src\filters\in_route_repair.c" /> <ClCompile Include="..\..\src\filters\in_dvb4linux.c" /> <ClCompile Include="..\..\src\filters\in_file.c" /> <ClCompile Include="..\..\src\filters\in_http.c" /> @@ -334,6 +336,7 @@ <ClCompile Include="..\..\src\filters\out_video.c" /> <ClCompile Include="..\..\src\filters\reframer.c" /> <ClCompile Include="..\..\src\filters\reframe_ac3.c" /> + <ClCompile Include="..\..\src\filters\reframe_ac4.c" /> <ClCompile Include="..\..\src\filters\reframe_adts.c" /> <ClCompile Include="..\..\src\filters\reframe_amr.c" /> <ClCompile Include="..\..\src\filters\reframe_av1.c" /> @@ -351,8 +354,10 @@ <ClCompile Include="..\..\src\filters\reframe_rawvid.c" /> <ClCompile Include="..\..\src\filters\reframe_truehd.c" /> <ClCompile Include="..\..\src\filters\resample_audio.c" /> + <ClCompile Include="..\..\src\filters\sei_load.c" /> <ClCompile Include="..\..\src\filters\restamp.c" /> <ClCompile Include="..\..\src\filters\rewind.c" /> + <ClCompile Include="..\..\src\filters\rewrite_ac4.c" /> <ClCompile Include="..\..\src\filters\rewrite_adts.c" /> <ClCompile Include="..\..\src\filters\rewrite_mhas.c" /> <ClCompile Include="..\..\src\filters\rewrite_mp4v.c" /> @@ -372,6 +377,9 @@ <ClCompile Include="..\..\src\filters\write_qcp.c" /> <ClCompile Include="..\..\src\filters\write_tx3g.c" /> <ClCompile Include="..\..\src\filters\write_vtt.c" /> + <ClCompile Include="..\..\src\filters\dec_webcodec.c" /> + <ClCompile Include="..\..\src\filters\enc_webcodec.c" /> + <ClCompile Include="..\..\src\filters\avin_web.c" /> <ClCompile Include="..\..\src\filter_core\filter.c" /> <ClCompile Include="..\..\src\filter_core\filter_pck.c" /> <ClCompile Include="..\..\src\filter_core\filter_pid.c" /> @@ -452,6 +460,7 @@ <ClCompile Include="..\..\src\media_tools\dsmcc.c" /> <ClCompile Include="..\..\src\media_tools\dvb_mpe.c" /> <ClCompile Include="..\..\src\media_tools\gpac_ogg.c" /> + <ClCompile Include="..\..\src\media_tools\id3.c" /> <ClCompile Include="..\..\src\media_tools\img.c" /> <ClCompile Include="..\..\src\media_tools\crypt_tools.c" /> <ClCompile Include="..\..\src\media_tools\isom_hinter.c" /> @@ -481,7 +490,7 @@ <ClCompile Include="..\..\src\odf\qos.c" /> <ClCompile Include="..\..\src\odf\slc.c" /> <ClCompile Include="..\..\src\quickjs\cutils.c" /> - <ClCompile Include="..\..\src\quickjs\libbf.c"> + <ClCompile Include="..\..\src\quickjs\dtoa.c"> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Level1</WarningLevel> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug - MP4Box_only|x64'">Level1</WarningLevel> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release - MP4Box_only|x64'">Level1</WarningLevel> @@ -562,12 +571,17 @@ <ClCompile Include="..\..\src\utils\alloc.c" /> <ClCompile Include="..\..\src\utils\base_encoding.c" /> <ClCompile Include="..\..\src\utils\bitstream.c" /> - <ClCompile Include="..\..\src\utils\cache.c" /> <ClCompile Include="..\..\src\utils\color.c" /> <ClCompile Include="..\..\src\utils\configfile.c" /> <ClCompile Include="..\..\src\utils\constants.c" /> <ClCompile Include="..\..\src\utils\dlmalloc.c" /> <ClCompile Include="..\..\src\utils\downloader.c" /> + <ClCompile Include="..\..\src\utils\downloader_cache.c" /> + <ClCompile Include="..\..\src\utils\downloader_curl.c" /> + <ClCompile Include="..\..\src\utils\downloader_hmux.c" /> + <ClCompile Include="..\..\src\utils\downloader_nghttp2.c" /> + <ClCompile Include="..\..\src\utils\downloader_ngtcp2.c" /> + <ClCompile Include="..\..\src\utils\downloader_ssl.c" /> <ClCompile Include="..\..\src\utils\error.c" /> <ClCompile Include="..\..\src\utils\gltools.c" /> <ClCompile Include="..\..\src\utils\gzio.c" /> @@ -582,14 +596,16 @@ <ClCompile Include="..\..\src\utils\os_thread.c" /> <ClCompile Include="..\..\src\utils\path2d.c" /> <ClCompile Include="..\..\src\utils\path2d_stroker.c" /> - <ClCompile Include="..\..\src\utils\Remotery.c" /> + <ClCompile Include="..\..\src\utils\rmt_ws.c" /> <ClCompile Include="..\..\src\utils\sha1.c" /> <ClCompile Include="..\..\src\utils\sha256.c" /> + <ClCompile Include="..\..\src\utils\md5.c" /> <ClCompile Include="..\..\src\utils\token.c" /> <ClCompile Include="..\..\src\utils\unicode.c" /> <ClCompile Include="..\..\src\utils\uni_bidi.c" /> <ClCompile Include="..\..\src\utils\url.c" /> <ClCompile Include="..\..\src\utils\utf.c" /> + <ClCompile Include="..\..\src\utils\xml_bin_custom.c" /> <ClCompile Include="..\..\src\utils\xml_parser.c" /> </ItemGroup> <ItemGroup> @@ -730,7 +746,7 @@ <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -769,7 +785,7 @@ <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -808,7 +824,7 @@ <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -848,7 +864,7 @@ <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -885,7 +901,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -920,7 +936,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -956,7 +972,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -992,7 +1008,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <CompileAs>CompileAsC</CompileAs> - <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -1012,4 +1028,4 @@ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> -</Project> +</Project> \ No newline at end of file
View file
gpac-2.4.0.tar.gz/build/msvc14/libgpac.vcxproj.filters -> gpac-26.02.0.tar.gz/build/msvc14/libgpac.vcxproj.filters
Changed
@@ -201,6 +201,9 @@ <ClInclude Include="..\..\src\compositor\visual_manager_3d.h"> <Filter>compositor</Filter> </ClInclude> + <ClInclude Include="..\..\src\media_tools\id3.h"> + <Filter>media_tools</Filter> + </ClInclude> <ClInclude Include="..\..\src\media_tools\mpeg2_ps.h"> <Filter>media_tools</Filter> </ClInclude> @@ -225,9 +228,6 @@ <ClInclude Include="..\..\include\gpac\bitstream.h"> <Filter>include</Filter> </ClInclude> - <ClInclude Include="..\..\include\gpac\cache.h"> - <Filter>include</Filter> - </ClInclude> <ClInclude Include="..\..\include\gpac\color.h"> <Filter>include</Filter> </ClInclude> @@ -390,7 +390,7 @@ <ClInclude Include="..\..\src\filters\dec_nvdec_sdk.h"> <Filter>filters\nvdec</Filter> </ClInclude> - <ClInclude Include="..\..\include\gpac\Remotery.h"> + <ClInclude Include="..\..\include\gpac\rmt_ws.h"> <Filter>include</Filter> </ClInclude> <ClInclude Include="..\..\src\quickjs\cutils.h"> @@ -815,6 +815,9 @@ <ClCompile Include="..\..\src\media_tools\gpac_ogg.c"> <Filter>media_tools</Filter> </ClCompile> + <ClCompile Include="..\..\src\media_tools\id3.c"> + <Filter>media_tools</Filter> + </ClCompile> <ClCompile Include="..\..\src\media_tools\img.c"> <Filter>media_tools</Filter> </ClCompile> @@ -1001,9 +1004,6 @@ <ClCompile Include="..\..\src\utils\bitstream.c"> <Filter>utils</Filter> </ClCompile> - <ClCompile Include="..\..\src\utils\cache.c"> - <Filter>utils</Filter> - </ClCompile> <ClCompile Include="..\..\src\utils\color.c"> <Filter>utils</Filter> </ClCompile> @@ -1016,6 +1016,24 @@ <ClCompile Include="..\..\src\utils\downloader.c"> <Filter>utils</Filter> </ClCompile> + <ClCompile Include="..\..\src\utils\downloader_cache.c"> + <Filter>utils</Filter> + </ClCompile> + <ClCompile Include="..\..\src\utils\downloader_curl.c"> + <Filter>utils</Filter> + </ClCompile> + <ClCompile Include="..\..\src\utils\downloader_hmux.c"> + <Filter>utils</Filter> + </ClCompile> + <ClCompile Include="..\..\src\utils\downloader_nghttp2.c"> + <Filter>utils</Filter> + </ClCompile> + <ClCompile Include="..\..\src\utils\downloader_ngtcp2.c"> + <Filter>utils</Filter> + </ClCompile> + <ClCompile Include="..\..\src\utils\downloader_ssl.c"> + <Filter>utils</Filter> + </ClCompile> <ClCompile Include="..\..\src\utils\error.c"> <Filter>utils</Filter> </ClCompile> @@ -1055,6 +1073,9 @@ <ClCompile Include="..\..\src\utils\sha256.c"> <Filter>utils</Filter> </ClCompile> + <ClCompile Include="..\..\src\utils\md5.c"> + <Filter>utils</Filter> + </ClCompile> <ClCompile Include="..\..\src\utils\token.c"> <Filter>utils</Filter> </ClCompile> @@ -1070,6 +1091,9 @@ <ClCompile Include="..\..\src\utils\utf.c"> <Filter>utils</Filter> </ClCompile> + <ClCompile Include="..\..\src\utils\xml_bin_custom.c"> + <Filter>utils</Filter> + </ClCompile> <ClCompile Include="..\..\src\utils\xml_parser.c"> <Filter>utils</Filter> </ClCompile> @@ -1184,6 +1208,9 @@ <ClCompile Include="..\..\src\filters\dec_opensvc.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\dec_scte35.c"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\filters\dec_theora.c"> <Filter>filters</Filter> </ClCompile> @@ -1244,6 +1271,9 @@ <ClCompile Include="..\..\src\filters\dmx_vobsub.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\enc_cc.c"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\filters\enc_jpg.c"> <Filter>filters</Filter> </ClCompile> @@ -1343,6 +1373,9 @@ <ClCompile Include="..\..\src\filters\reframe_ac3.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\reframe_ac4.c"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\filters\reframe_adts.c"> <Filter>filters</Filter> </ClCompile> @@ -1385,12 +1418,18 @@ <ClCompile Include="..\..\src\filters\resample_audio.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\sei_load.c"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\filters\restamp.c"> <Filter>filters</Filter> </ClCompile> <ClCompile Include="..\..\src\filters\rewind.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\rewrite_ac4.c"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\filters\rewrite_adts.c"> <Filter>filters</Filter> </ClCompile> @@ -1481,12 +1520,18 @@ <ClCompile Include="..\..\src\filters\dec_nvdec_sdk.c"> <Filter>filters\nvdec</Filter> </ClCompile> - <ClCompile Include="..\..\src\utils\Remotery.c"> + <ClCompile Include="..\..\src\utils\rmt_ws.c"> <Filter>utils</Filter> </ClCompile> <ClCompile Include="..\..\src\filters\in_route.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\in_route_repair.c"> + <Filter>filters</Filter> + </ClCompile> + <ClCompile Include="..\..\src\filters\in_route.h"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\evg\ftgrays.c"> <Filter>evg</Filter> </ClCompile> @@ -1496,6 +1541,15 @@ <ClCompile Include="..\..\src\filters\out_rtp.c"> <Filter>filters</Filter> </ClCompile> + <ClCompile Include="..\..\src\filters\dec_webcodec.c"> + <Filter>filters</Filter> + </ClCompile> + <ClCompile Include="..\..\src\filters\enc_webcodec.c"> + <Filter>filters</Filter> + </ClCompile> + <ClCompile Include="..\..\src\filters\avin_web.c"> + <Filter>filters</Filter> + </ClCompile> <ClCompile Include="..\..\src\evg\raster_565.c"> <Filter>evg</Filter> </ClCompile> @@ -1523,7 +1577,7 @@ <ClCompile Include="..\..\src\quickjs\cutils.c"> <Filter>quickjs</Filter> </ClCompile> - <ClCompile Include="..\..\src\quickjs\libbf.c"> + <ClCompile Include="..\..\src\quickjs\dtoa.c"> <Filter>quickjs</Filter> </ClCompile> <ClCompile Include="..\..\src\quickjs\libregexp.c">
View file
gpac-2.4.0.tar.gz/build/msvc14/mp4box.vcxproj -> gpac-26.02.0.tar.gz/build/msvc14/mp4box.vcxproj
Changed
@@ -175,6 +175,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -224,6 +225,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -271,6 +273,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -317,6 +320,7 @@ <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -363,6 +367,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -410,6 +415,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -456,6 +462,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -501,6 +508,7 @@ <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> <WarningLevel>Level3</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> + <DisableSpecificWarnings>4996;4018;4244</DisableSpecificWarnings> </ClCompile> <ResourceCompile> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
View file
gpac-2.4.0.tar.gz/build/xcode/generate_ios.sh -> gpac-26.02.0.tar.gz/build/xcode/generate_ios.sh
Changed
@@ -4,10 +4,14 @@ echo "*** Set version within Info.plist application file ***" version=`grep '#define GPAC_VERSION ' ../../include/gpac/version.h | cut -d '"' -f 2` -TAG=$(git describe --tags --abbrev=0 2> /dev/null) -REVISION=$(echo `git describe --tags --long 2> /dev/null || echo "UNKNOWN"` | sed "s/^$TAG-//") +TAG=$(git describe --tags --abbrev=0 --match "v*" 2> /dev/null) +REVISION=$(echo `git describe --tags --long --match "v*" 2> /dev/null || echo "UNKNOWN"` | sed "s/^$TAG-//") BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "UNKNOWN") -rev="$REVISION-$BRANCH" + +#sanitize branch name for filenames +DHBRANCH=$(echo "$BRANCH" | sed 's/^-+.0-9a-zA-Z~/-/g' ) + +rev="$REVISION-$DHBRANCH" if "$rev" != "" then sed 's/<string>.*<\/string><!-- VERSION_REV_REPLACE -->/<string>'"$version"'<\/string>/' ../../applications/gpac/ios-Info.plist > ../../applications/gpac/ios-Info.plist.new @@ -24,7 +28,7 @@ fi echo "*** Compile and archive gpac4ios ***" -xcodebuild archive -project gpac4ios.xcodeproj -scheme gpac4ios -archivePath gpac4ios.xcarchive +xcodebuild archive -project gpac4ios.xcodeproj -scheme gpac4ios -archivePath gpac4ios.xcarchive -allowProvisioningUpdates if $? != 0 ; then exit 1 fi
View file
gpac-2.4.0.tar.gz/build/xcode/gpac.xcodeproj/project.pbxproj -> gpac-26.02.0.tar.gz/build/xcode/gpac.xcodeproj/project.pbxproj
Changed
@@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 3F04FCBC2C9CB179002BC889 /* xml_bin_custom.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F04FCBB2C9CB179002BC889 /* xml_bin_custom.c */; }; + 3F5F3F1F2C7EAC5D00C58257 /* id3.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F5F3F1E2C7EAC5D00C58257 /* id3.c */; }; + 3FDEA5B92D402C0B006F0673 /* enc_cc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FDEA5B82D402C0B006F0673 /* enc_cc.c */; }; 920100B018D5A444003D1ACA /* arith_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201FF8A18D5A444003D1ACA /* arith_decoder.c */; }; 920100B118D5A444003D1ACA /* bifs_codec.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201FF8B18D5A444003D1ACA /* bifs_codec.c */; }; 920100B218D5A444003D1ACA /* bifs_node_tables.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201FF8C18D5A444003D1ACA /* bifs_node_tables.c */; }; @@ -136,7 +139,6 @@ 920101A618D5A445003D1ACA /* alloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201008D18D5A444003D1ACA /* alloc.c */; }; 920101A718D5A445003D1ACA /* base_encoding.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201008E18D5A444003D1ACA /* base_encoding.c */; }; 920101A818D5A445003D1ACA /* bitstream.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201008F18D5A444003D1ACA /* bitstream.c */; }; - 920101A918D5A445003D1ACA /* cache.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201009018D5A444003D1ACA /* cache.c */; }; 920101AA18D5A445003D1ACA /* color.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201009118D5A444003D1ACA /* color.c */; }; 920101AB18D5A445003D1ACA /* configfile.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201009218D5A444003D1ACA /* configfile.c */; }; 920101AD18D5A445003D1ACA /* downloader.c in Sources */ = {isa = PBXBuildFile; fileRef = 9201009418D5A444003D1ACA /* downloader.c */; }; @@ -178,6 +180,7 @@ 92256CCD20E0DA26004EB243 /* encrypt_cenc_isma.c in Sources */ = {isa = PBXBuildFile; fileRef = 92256CCC20E0DA26004EB243 /* encrypt_cenc_isma.c */; }; 9225DD752326790C00F94C42 /* mpd.h in Headers */ = {isa = PBXBuildFile; fileRef = 9225DD742326790C00F94C42 /* mpd.h */; }; 92261A87239A6E4600C14199 /* out_http.c in Sources */ = {isa = PBXBuildFile; fileRef = 92261A86239A6E4600C14199 /* out_http.c */; }; + 92277E1B2BE8F32B005E0955 /* md5.c in Sources */ = {isa = PBXBuildFile; fileRef = 92277E1A2BE8F32B005E0955 /* md5.c */; }; 922B0FAA2A5E81EB003690BB /* dec_mpeghdec.c in Sources */ = {isa = PBXBuildFile; fileRef = 922B0FA92A5E81EB003690BB /* dec_mpeghdec.c */; }; 922B0FC72A602176003690BB /* caca_out.c in Sources */ = {isa = PBXBuildFile; fileRef = 922B0FC62A602176003690BB /* caca_out.c */; }; 922B0FC82A6028C5003690BB /* libgpac.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9201F93718D5A38A003D1ACA /* libgpac.dylib */; }; @@ -186,7 +189,7 @@ 922CF1271FCF169900F2F6A4 /* dmx_avi.c in Sources */ = {isa = PBXBuildFile; fileRef = 922CF1261FCF169900F2F6A4 /* dmx_avi.c */; }; 922E29832051940C00C8DCBB /* mux_avi.c in Sources */ = {isa = PBXBuildFile; fileRef = 922E29812051940C00C8DCBB /* mux_avi.c */; }; 922E29842051940C00C8DCBB /* out_audio.c in Sources */ = {isa = PBXBuildFile; fileRef = 922E29822051940C00C8DCBB /* out_audio.c */; }; - 9231B3AF2321533F00ADB4A9 /* Remotery.h in Headers */ = {isa = PBXBuildFile; fileRef = 9231B3AE2321533F00ADB4A9 /* Remotery.h */; }; + 9231B3AF2321533F00ADB4A9 /* rmt_ws.h in Headers */ = {isa = PBXBuildFile; fileRef = 9231B3AE2321533F00ADB4A9 /* rmt_ws.h */; }; 92334CA92333A8C900DA6051 /* libunicode.c in Sources */ = {isa = PBXBuildFile; fileRef = 92334C932333A8C800DA6051 /* libunicode.c */; }; 92334CB82333A8C900DA6051 /* quickjs.h in Headers */ = {isa = PBXBuildFile; fileRef = 92334CA22333A8C900DA6051 /* quickjs.h */; }; 92334CBA2333A8C900DA6051 /* quickjs.c in Sources */ = {isa = PBXBuildFile; fileRef = 92334CA42333A8C900DA6051 /* quickjs.c */; }; @@ -214,7 +217,6 @@ 923E89CE20B6F04600F299B2 /* tiny_aes.c in Sources */ = {isa = PBXBuildFile; fileRef = 923E89C920B6F04600F299B2 /* tiny_aes.c */; }; 923E89CF20B6F04600F299B2 /* g_crypt_tinyaes.c in Sources */ = {isa = PBXBuildFile; fileRef = 923E89CA20B6F04600F299B2 /* g_crypt_tinyaes.c */; }; 923E89D020B6F2D300F299B2 /* filter_props.c in Sources */ = {isa = PBXBuildFile; fileRef = 92F8D4721F71642E00616F7C /* filter_props.c */; }; - 923EB53223D1B78D00E1FFA1 /* libbf.c in Sources */ = {isa = PBXBuildFile; fileRef = 923EB53123D1B78D00E1FFA1 /* libbf.c */; }; 92455F8C2A52B4B90080321C /* dec_cc.c in Sources */ = {isa = PBXBuildFile; fileRef = 92455F8B2A52B4B90080321C /* dec_cc.c */; }; 9246EE7524A0DA2700F72EAD /* filter_session_js.c in Sources */ = {isa = PBXBuildFile; fileRef = 9246EE7424A0DA2700F72EAD /* filter_session_js.c */; }; 92493D8020E2A64000203C2C /* crypt_tools.c in Sources */ = {isa = PBXBuildFile; fileRef = 92493D7F20E2A64000203C2C /* crypt_tools.c */; }; @@ -223,6 +225,16 @@ 924E805F206D427A00AB580F /* reframe_rawpcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 924E805E206D427A00AB580F /* reframe_rawpcm.c */; }; 924FEE821BA6C63A003F77A6 /* iff.c in Sources */ = {isa = PBXBuildFile; fileRef = 924FEE811BA6C63A003F77A6 /* iff.c */; }; 9251E5442524EB9800CEB84D /* reframe_mhas.c in Sources */ = {isa = PBXBuildFile; fileRef = 9251E5432524EB9700CEB84D /* reframe_mhas.c */; }; + 925546952D3011C400E0AAF9 /* downloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 925546902D3011C400E0AAF9 /* downloader.h */; }; + 925546982D3011C400E0AAF9 /* downloader_cache.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546932D3011C400E0AAF9 /* downloader_cache.c */; }; + 9255469E2D301D4700E0AAF9 /* downloader_ssl.c in Sources */ = {isa = PBXBuildFile; fileRef = 9255469A2D301D4700E0AAF9 /* downloader_ssl.c */; }; + 9255469F2D301D4700E0AAF9 /* downloader_curl.c in Sources */ = {isa = PBXBuildFile; fileRef = 9255469B2D301D4700E0AAF9 /* downloader_curl.c */; }; + 925546A02D301D4700E0AAF9 /* downloader_emscripten.c in Sources */ = {isa = PBXBuildFile; fileRef = 9255469C2D301D4700E0AAF9 /* downloader_emscripten.c */; }; + 925546A32D314A9600E0AAF9 /* downloader_hmux.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546A22D314A9600E0AAF9 /* downloader_hmux.c */; }; + 925546A52D314BCA00E0AAF9 /* downloader_nghttp2.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546A42D314BCA00E0AAF9 /* downloader_nghttp2.c */; }; + 925546A72D3174DE00E0AAF9 /* downloader_ngtcp2.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546A62D3174DE00E0AAF9 /* downloader_ngtcp2.c */; }; + 925607742E1EC6880022BA42 /* reframe_ac4.c in Sources */ = {isa = PBXBuildFile; fileRef = 925607722E1EC6880022BA42 /* reframe_ac4.c */; }; + 925607752E1EC6880022BA42 /* rewrite_ac4.c in Sources */ = {isa = PBXBuildFile; fileRef = 925607732E1EC6880022BA42 /* rewrite_ac4.c */; }; 9256B38B208662FC00088537 /* mux_ts.c in Sources */ = {isa = PBXBuildFile; fileRef = 9256B38A208662FB00088537 /* mux_ts.c */; }; 9257307F2056948D009DB328 /* rewrite_adts.c in Sources */ = {isa = PBXBuildFile; fileRef = 9257307D2056948D009DB328 /* rewrite_adts.c */; }; 925730802056948D009DB328 /* rewrite_nalu.c in Sources */ = {isa = PBXBuildFile; fileRef = 9257307E2056948D009DB328 /* rewrite_nalu.c */; }; @@ -263,6 +275,7 @@ 9273463323506D370081AF74 /* storage.c in Sources */ = {isa = PBXBuildFile; fileRef = 9273463223506D370081AF74 /* storage.c */; }; 9274295824647AB8009ADD8D /* bsrw.c in Sources */ = {isa = PBXBuildFile; fileRef = 9274295724647AB8009ADD8D /* bsrw.c */; }; 92754D60207D24F600170383 /* filelist.c in Sources */ = {isa = PBXBuildFile; fileRef = 92754D5F207D24F600170383 /* filelist.c */; }; + 9279EA392DA6C78600E13C5E /* sei_load.c in Sources */ = {isa = PBXBuildFile; fileRef = 9279EA382DA6C78600E13C5E /* sei_load.c */; }; 927AAD0E1E44F492008C8E69 /* libgpac.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9201F93718D5A38A003D1ACA /* libgpac.dylib */; }; 927DB14C21A3C7100032D205 /* reframe_latm.c in Sources */ = {isa = PBXBuildFile; fileRef = 927DB14B21A3C7100032D205 /* reframe_latm.c */; }; 9285D1A7266F671F008FCD14 /* quickjs-libc.c in Sources */ = {isa = PBXBuildFile; fileRef = 9285D1A6266F671F008FCD14 /* quickjs-libc.c */; }; @@ -390,6 +403,7 @@ 92B9A5BD1F868D0000A24FE4 /* dec_laser.c in Sources */ = {isa = PBXBuildFile; fileRef = 92B9A5BC1F868CFF00A24FE4 /* dec_laser.c */; }; 92B9A5BF1F8695FA00A24FE4 /* dmx_saf.c in Sources */ = {isa = PBXBuildFile; fileRef = 92B9A5BE1F8695FA00A24FE4 /* dmx_saf.c */; }; 92B9A5C11F86ACDF00A24FE4 /* dec_opensvc.c in Sources */ = {isa = PBXBuildFile; fileRef = 92B9A5C01F86ACDF00A24FE4 /* dec_opensvc.c */; }; + 92B9A5C11F86CCDF00A24FE4 /* dec_scte35.c in Sources */ = {isa = PBXBuildFile; fileRef = 92B9A5C21F86ACDF00A24FE4 /* dec_scte35.c */; }; 92BA0AD5243B68AF006FC74E /* dec_mediacodec.c in Sources */ = {isa = PBXBuildFile; fileRef = 92BA0AD3243B68AF006FC74E /* dec_mediacodec.c */; }; 92BA0ADA243B6E2B006FC74E /* dektec_video_decl.c in Sources */ = {isa = PBXBuildFile; fileRef = 92BA0AD9243B6E2B006FC74E /* dektec_video_decl.c */; }; 92BB85661F7BFB63009BC9C8 /* ff_dec.c in Sources */ = {isa = PBXBuildFile; fileRef = 92BB85501F7BFB60009BC9C8 /* ff_dec.c */; }; @@ -424,7 +438,6 @@ 92BBFCAD1A7122AD006A7735 /* base_coding.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC4A1A7122AD006A7735 /* base_coding.h */; }; 92BBFCAE1A7122AD006A7735 /* bifs.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC4B1A7122AD006A7735 /* bifs.h */; }; 92BBFCAF1A7122AD006A7735 /* bitstream.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC4C1A7122AD006A7735 /* bitstream.h */; }; - 92BBFCB01A7122AD006A7735 /* cache.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC4D1A7122AD006A7735 /* cache.h */; }; 92BBFCB11A7122AD006A7735 /* color.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC4E1A7122AD006A7735 /* color.h */; }; 92BBFCB21A7122AD006A7735 /* compositor.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC4F1A7122AD006A7735 /* compositor.h */; }; 92BBFCB31A7122AD006A7735 /* config_file.h in Headers */ = {isa = PBXBuildFile; fileRef = 92BBFC501A7122AD006A7735 /* config_file.h */; }; @@ -526,10 +539,13 @@ 92E164AB20F8EBAE0024A9CA /* dec_nvdec_sdk.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E164A820F8EBAE0024A9CA /* dec_nvdec_sdk.c */; }; 92E164AC20F8EBAE0024A9CA /* dec_nvdec_sdk.h in Headers */ = {isa = PBXBuildFile; fileRef = 92E164A920F8EBAE0024A9CA /* dec_nvdec_sdk.h */; }; 92E164AD20F8EBAE0024A9CA /* dec_nvdec.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E164AA20F8EBAE0024A9CA /* dec_nvdec.c */; }; - 92E164BC20F8F2860024A9CA /* Remotery.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E164BA20F8F2860024A9CA /* Remotery.c */; }; + 92E164BC20F8F2860024A9CA /* rmt_ws.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E164BA20F8F2860024A9CA /* rmt_ws.c */; }; 92E249902057B973002CC029 /* resample_audio.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E2498F2057B973002CC029 /* resample_audio.c */; }; 92E318CF20645036007BE021 /* ff_rescale.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E318CE20645036007BE021 /* ff_rescale.c */; }; 92E318D120651834007BE021 /* vcrop.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E318D020651834007BE021 /* vcrop.c */; }; + 92E5E2BA2EB511D100FD8642 /* dtoa.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E5E2B92EB511D100FD8642 /* dtoa.c */; }; + 92E8E7EB2BF4B482003D785B /* in_route.h in Headers */ = {isa = PBXBuildFile; fileRef = 92E8E7E92BF4B481003D785B /* in_route.h */; }; + 92E8E7EC2BF4B482003D785B /* in_route_repair.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E8E7EA2BF4B481003D785B /* in_route_repair.c */; }; 92E9167C2073F1DB00EE92FB /* ff_enc.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E9167B2073F1DB00EE92FB /* ff_enc.c */; }; 92EB479120F50C4200E1F2DD /* reframe_av1.c in Sources */ = {isa = PBXBuildFile; fileRef = 92EB479020F50C4200E1F2DD /* reframe_av1.c */; }; 92EB479320F6333900E1F2DD /* rewrite_obu.c in Sources */ = {isa = PBXBuildFile; fileRef = 92EB479220F6333800E1F2DD /* rewrite_obu.c */; }; @@ -641,6 +657,9 @@ 28C5AA781A15FF3600372C59 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; 28C5AA7A1A15FF4700372C59 /* libiconv.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.2.dylib; path = usr/lib/libiconv.2.dylib; sourceTree = SDKROOT; }; 28C5AA7C1A15FF5700372C59 /* libiconv.2.4.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.2.4.0.dylib; path = usr/lib/libiconv.2.4.0.dylib; sourceTree = SDKROOT; }; + 3F04FCBB2C9CB179002BC889 /* xml_bin_custom.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = xml_bin_custom.c; sourceTree = "<group>"; }; + 3F5F3F1E2C7EAC5D00C58257 /* id3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = id3.c; sourceTree = "<group>"; }; + 3FDEA5B82D402C0B006F0673 /* enc_cc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = enc_cc.c; path = filters/enc_cc.c; sourceTree = "<group>"; }; 9201000018D5A444003D1ACA /* isom_intern.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = isom_intern.c; sourceTree = "<group>"; }; 9201000118D5A444003D1ACA /* isom_read.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = isom_read.c; sourceTree = "<group>"; }; 9201000218D5A444003D1ACA /* isom_store.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = isom_store.c; sourceTree = "<group>"; }; @@ -728,7 +747,6 @@ 9201008D18D5A444003D1ACA /* alloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = alloc.c; sourceTree = "<group>"; }; 9201008E18D5A444003D1ACA /* base_encoding.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base_encoding.c; sourceTree = "<group>"; }; 9201008F18D5A444003D1ACA /* bitstream.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bitstream.c; sourceTree = "<group>"; }; - 9201009018D5A444003D1ACA /* cache.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cache.c; sourceTree = "<group>"; }; 9201009118D5A444003D1ACA /* color.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = color.c; sourceTree = "<group>"; }; 9201009218D5A444003D1ACA /* configfile.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = configfile.c; sourceTree = "<group>"; }; 9201009318D5A444003D1ACA /* dlmalloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dlmalloc.c; sourceTree = "<group>"; }; @@ -813,6 +831,7 @@ 92256CCC20E0DA26004EB243 /* encrypt_cenc_isma.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = encrypt_cenc_isma.c; path = filters/encrypt_cenc_isma.c; sourceTree = "<group>"; }; 9225DD742326790C00F94C42 /* mpd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mpd.h; path = ../include/gpac/mpd.h; sourceTree = "<group>"; }; 92261A86239A6E4600C14199 /* out_http.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = out_http.c; path = filters/out_http.c; sourceTree = "<group>"; }; + 92277E1A2BE8F32B005E0955 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md5.c; sourceTree = "<group>"; }; 922B0FA92A5E81EB003690BB /* dec_mpeghdec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_mpeghdec.c; path = filters/dec_mpeghdec.c; sourceTree = "<group>"; }; 922B0FC12A602153003690BB /* gm_caca_out.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = gm_caca_out.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 922B0FC62A602176003690BB /* caca_out.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = caca_out.c; path = ../../modules/caca_out/caca_out.c; sourceTree = "<group>"; }; @@ -822,7 +841,7 @@ 922CF1261FCF169900F2F6A4 /* dmx_avi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dmx_avi.c; path = filters/dmx_avi.c; sourceTree = "<group>"; }; 922E29812051940C00C8DCBB /* mux_avi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mux_avi.c; path = filters/mux_avi.c; sourceTree = "<group>"; }; 922E29822051940C00C8DCBB /* out_audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = out_audio.c; path = filters/out_audio.c; sourceTree = "<group>"; }; - 9231B3AE2321533F00ADB4A9 /* Remotery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Remotery.h; path = ../include/gpac/Remotery.h; sourceTree = "<group>"; }; + 9231B3AE2321533F00ADB4A9 /* rmt_ws.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rmt_ws.h; path = ../include/gpac/rmt_ws.h; sourceTree = "<group>"; }; 92334C932333A8C800DA6051 /* libunicode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libunicode.c; path = quickjs/libunicode.c; sourceTree = "<group>"; }; 92334CA22333A8C900DA6051 /* quickjs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = quickjs.h; path = quickjs/quickjs.h; sourceTree = "<group>"; }; 92334CA42333A8C900DA6051 /* quickjs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = quickjs.c; path = quickjs/quickjs.c; sourceTree = "<group>"; }; @@ -847,7 +866,6 @@ 923E89C820B6F04600F299B2 /* g_crypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = g_crypt.c; path = crypto/g_crypt.c; sourceTree = "<group>"; }; 923E89C920B6F04600F299B2 /* tiny_aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = tiny_aes.c; path = crypto/tiny_aes.c; sourceTree = "<group>"; }; 923E89CA20B6F04600F299B2 /* g_crypt_tinyaes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = g_crypt_tinyaes.c; path = crypto/g_crypt_tinyaes.c; sourceTree = "<group>"; }; - 923EB53123D1B78D00E1FFA1 /* libbf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libbf.c; path = quickjs/libbf.c; sourceTree = "<group>"; }; 92455F8B2A52B4B90080321C /* dec_cc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_cc.c; path = filters/dec_cc.c; sourceTree = "<group>"; }; 9246EE7424A0DA2700F72EAD /* filter_session_js.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = filter_session_js.c; path = filter_core/filter_session_js.c; sourceTree = "<group>"; }; 92493D7F20E2A64000203C2C /* crypt_tools.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crypt_tools.c; sourceTree = "<group>"; }; @@ -856,6 +874,16 @@ 924E805E206D427A00AB580F /* reframe_rawpcm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reframe_rawpcm.c; path = filters/reframe_rawpcm.c; sourceTree = "<group>"; }; 924FEE811BA6C63A003F77A6 /* iff.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = iff.c; sourceTree = "<group>"; }; 9251E5432524EB9700CEB84D /* reframe_mhas.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reframe_mhas.c; path = filters/reframe_mhas.c; sourceTree = "<group>"; }; + 925546902D3011C400E0AAF9 /* downloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = downloader.h; sourceTree = "<group>"; }; + 925546932D3011C400E0AAF9 /* downloader_cache.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_cache.c; sourceTree = "<group>"; }; + 9255469A2D301D4700E0AAF9 /* downloader_ssl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_ssl.c; sourceTree = "<group>"; }; + 9255469B2D301D4700E0AAF9 /* downloader_curl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_curl.c; sourceTree = "<group>"; }; + 9255469C2D301D4700E0AAF9 /* downloader_emscripten.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_emscripten.c; sourceTree = "<group>"; }; + 925546A22D314A9600E0AAF9 /* downloader_hmux.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_hmux.c; sourceTree = "<group>"; }; + 925546A42D314BCA00E0AAF9 /* downloader_nghttp2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_nghttp2.c; sourceTree = "<group>"; }; + 925546A62D3174DE00E0AAF9 /* downloader_ngtcp2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_ngtcp2.c; sourceTree = "<group>"; }; + 925607722E1EC6880022BA42 /* reframe_ac4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reframe_ac4.c; path = filters/reframe_ac4.c; sourceTree = "<group>"; }; + 925607732E1EC6880022BA42 /* rewrite_ac4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rewrite_ac4.c; path = filters/rewrite_ac4.c; sourceTree = "<group>"; }; 9256B38A208662FB00088537 /* mux_ts.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mux_ts.c; path = filters/mux_ts.c; sourceTree = "<group>"; }; 9257307D2056948D009DB328 /* rewrite_adts.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rewrite_adts.c; path = filters/rewrite_adts.c; sourceTree = "<group>"; }; 9257307E2056948D009DB328 /* rewrite_nalu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rewrite_nalu.c; path = filters/rewrite_nalu.c; sourceTree = "<group>"; }; @@ -898,6 +926,7 @@ 9273463223506D370081AF74 /* storage.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = storage.c; path = jsmods/storage.c; sourceTree = "<group>"; }; 9274295724647AB8009ADD8D /* bsrw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bsrw.c; path = filters/bsrw.c; sourceTree = "<group>"; }; 92754D5F207D24F600170383 /* filelist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = filelist.c; path = filters/filelist.c; sourceTree = "<group>"; }; + 9279EA382DA6C78600E13C5E /* sei_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sei_load.c; path = filters/sei_load.c; sourceTree = "<group>"; }; 927AACFD1E44EEDC008C8E69 /* gpac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = gpac; sourceTree = BUILT_PRODUCTS_DIR; }; 927DB14B21A3C7100032D205 /* reframe_latm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reframe_latm.c; path = filters/reframe_latm.c; sourceTree = "<group>"; }; 9285D1A6266F671F008FCD14 /* quickjs-libc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "quickjs-libc.c"; path = "quickjs/quickjs-libc.c"; sourceTree = "<group>"; }; @@ -1030,6 +1059,7 @@ 92B9A5BC1F868CFF00A24FE4 /* dec_laser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_laser.c; path = filters/dec_laser.c; sourceTree = "<group>"; }; 92B9A5BE1F8695FA00A24FE4 /* dmx_saf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dmx_saf.c; path = filters/dmx_saf.c; sourceTree = "<group>"; }; 92B9A5C01F86ACDF00A24FE4 /* dec_opensvc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_opensvc.c; path = filters/dec_opensvc.c; sourceTree = "<group>"; }; + 92B9A5C21F86ACDF00A24FE4 /* dec_scte35.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_scte35.c; path = filters/dec_scte35.c; sourceTree = "<group>"; }; 92BA0AD3243B68AF006FC74E /* dec_mediacodec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_mediacodec.c; path = filters/dec_mediacodec.c; sourceTree = "<group>"; }; 92BA0AD9243B6E2B006FC74E /* dektec_video_decl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dektec_video_decl.c; path = ../modules/dektec_out/dektec_video_decl.c; sourceTree = "<group>"; }; 92BB854C1F7BE2BA009BC9C8 /*  */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = "\U0001"; path = "../../../../../../../\U0001"; sourceTree = "<group>"; }; @@ -1066,7 +1096,6 @@ 92BBFC4A1A7122AD006A7735 /* base_coding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = base_coding.h; path = ../include/gpac/base_coding.h; sourceTree = "<group>"; }; 92BBFC4B1A7122AD006A7735 /* bifs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bifs.h; path = ../include/gpac/bifs.h; sourceTree = "<group>"; }; 92BBFC4C1A7122AD006A7735 /* bitstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bitstream.h; path = ../include/gpac/bitstream.h; sourceTree = "<group>"; }; - 92BBFC4D1A7122AD006A7735 /* cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cache.h; path = ../include/gpac/cache.h; sourceTree = "<group>"; }; 92BBFC4E1A7122AD006A7735 /* color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = color.h; path = ../include/gpac/color.h; sourceTree = "<group>"; }; 92BBFC4F1A7122AD006A7735 /* compositor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = compositor.h; path = ../include/gpac/compositor.h; sourceTree = "<group>"; }; 92BBFC501A7122AD006A7735 /* config_file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config_file.h; path = ../include/gpac/config_file.h; sourceTree = "<group>"; }; @@ -1166,10 +1195,13 @@ 92E164A820F8EBAE0024A9CA /* dec_nvdec_sdk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_nvdec_sdk.c; path = filters/dec_nvdec_sdk.c; sourceTree = "<group>"; }; 92E164A920F8EBAE0024A9CA /* dec_nvdec_sdk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dec_nvdec_sdk.h; path = filters/dec_nvdec_sdk.h; sourceTree = "<group>"; }; 92E164AA20F8EBAE0024A9CA /* dec_nvdec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_nvdec.c; path = filters/dec_nvdec.c; sourceTree = "<group>"; }; - 92E164BA20F8F2860024A9CA /* Remotery.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Remotery.c; sourceTree = "<group>"; }; + 92E164BA20F8F2860024A9CA /* rmt_ws.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rmt_ws.c; sourceTree = "<group>"; }; 92E2498F2057B973002CC029 /* resample_audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = resample_audio.c; path = filters/resample_audio.c; sourceTree = "<group>"; }; 92E318CE20645036007BE021 /* ff_rescale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ff_rescale.c; path = filters/ff_rescale.c; sourceTree = "<group>"; }; 92E318D020651834007BE021 /* vcrop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vcrop.c; path = filters/vcrop.c; sourceTree = "<group>"; }; + 92E5E2B92EB511D100FD8642 /* dtoa.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = dtoa.c; path = quickjs/dtoa.c; sourceTree = "<group>"; }; + 92E8E7E92BF4B481003D785B /* in_route.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = in_route.h; path = filters/in_route.h; sourceTree = "<group>"; }; + 92E8E7EA2BF4B481003D785B /* in_route_repair.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = in_route_repair.c; path = filters/in_route_repair.c; sourceTree = "<group>"; }; 92E9167B2073F1DB00EE92FB /* ff_enc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ff_enc.c; path = filters/ff_enc.c; sourceTree = "<group>"; }; 92EB479020F50C4200E1F2DD /* reframe_av1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reframe_av1.c; path = filters/reframe_av1.c; sourceTree = "<group>"; }; 92EB479220F6333800E1F2DD /* rewrite_obu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rewrite_obu.c; path = filters/rewrite_obu.c; sourceTree = "<group>"; }; @@ -1284,6 +1316,7 @@ 9201002918D5A444003D1ACA /* dsmcc.c */, 9201002A18D5A444003D1ACA /* dvb_mpe.c */, 9201002C18D5A444003D1ACA /* gpac_ogg.c */, + 3F5F3F1E2C7EAC5D00C58257 /* id3.c */, 9201002F18D5A444003D1ACA /* img.c */, 9201003118D5A444003D1ACA /* isom_hinter.c */, 9201003218D5A444003D1ACA /* isom_tools.c */, @@ -1376,20 +1409,20 @@ 9201008C18D5A444003D1ACA /* utils */ = { isa = PBXGroup; children = ( - 929DE7462417DC020056AFA7 /* gzio.c */, - 9298677C235A146D008A2CC6 /* gltools.c */, 9201008D18D5A444003D1ACA /* alloc.c */, 9201008E18D5A444003D1ACA /* base_encoding.c */, 9201008F18D5A444003D1ACA /* bitstream.c */, - 9201009018D5A444003D1ACA /* cache.c */, 9201009118D5A444003D1ACA /* color.c */, 9201009218D5A444003D1ACA /* configfile.c */, 926CE14E1FE81865002B3CF6 /* constants.c */, + 9255468F2D3011AF00E0AAF9 /* downloader */, 9201009318D5A444003D1ACA /* dlmalloc.c */, - 9201009418D5A444003D1ACA /* downloader.c */, 9201009518D5A444003D1ACA /* error.c */, + 9298677C235A146D008A2CC6 /* gltools.c */, + 929DE7462417DC020056AFA7 /* gzio.c */, 9201009718D5A444003D1ACA /* list.c */, 9201009918D5A444003D1ACA /* math.c */, + 92277E1A2BE8F32B005E0955 /* md5.c */, 9201009B18D5A444003D1ACA /* module_wrap.h */, 9201009A18D5A444003D1ACA /* module.c */, 9201009C18D5A444003D1ACA /* os_config_init.c */, @@ -1401,7 +1434,7 @@ 920100A018D5A444003D1ACA /* os_thread.c */, 920100A118D5A444003D1ACA /* path2d.c */, 920100A218D5A444003D1ACA /* path2d_stroker.c */, - 92E164BA20F8F2860024A9CA /* Remotery.c */, + 92E164BA20F8F2860024A9CA /* rmt_ws.c */, 920100A418D5A444003D1ACA /* sha1.c */, 925A760E292F965D00664F38 /* sha256.c */, 920100A718D5A444003D1ACA /* token.c */, @@ -1409,6 +1442,7 @@ 920100A918D5A444003D1ACA /* unicode.c */, 920100AA18D5A444003D1ACA /* url.c */, 920100AB18D5A444003D1ACA /* utf.c */, + 3F04FCBB2C9CB179002BC889 /* xml_bin_custom.c */, 920100AC18D5A444003D1ACA /* xml_parser.c */, ); path = utils; @@ -1646,8 +1680,8 @@ 92334C8E2333A8B800DA6051 /* quickjs */ = { isa = PBXGroup; children = ( + 92E5E2B92EB511D100FD8642 /* dtoa.c */, 9285D1A6266F671F008FCD14 /* quickjs-libc.c */, - 923EB53123D1B78D00E1FFA1 /* libbf.c */, 92910FA7233536620052365D /* cutils.c */, 92910FA52335363F0052365D /* libregexp.c */, 92334C932333A8C800DA6051 /* libunicode.c */, @@ -1670,6 +1704,22 @@ name = in_rtp; sourceTree = "<group>"; }; + 9255468F2D3011AF00E0AAF9 /* downloader */ = { + isa = PBXGroup; + children = ( + 925546902D3011C400E0AAF9 /* downloader.h */, + 9201009418D5A444003D1ACA /* downloader.c */, + 925546932D3011C400E0AAF9 /* downloader_cache.c */, + 9255469B2D301D4700E0AAF9 /* downloader_curl.c */, + 9255469C2D301D4700E0AAF9 /* downloader_emscripten.c */, + 925546A22D314A9600E0AAF9 /* downloader_hmux.c */, + 925546A42D314BCA00E0AAF9 /* downloader_nghttp2.c */, + 925546A62D3174DE00E0AAF9 /* downloader_ngtcp2.c */, + 9255469A2D301D4700E0AAF9 /* downloader_ssl.c */, + ); + name = downloader; + sourceTree = "<group>"; + }; 92586A6E1EA7663A00A0838A /* ft_font */ = { isa = PBXGroup; children = ( @@ -1797,7 +1847,6 @@ 92BBFC4A1A7122AD006A7735 /* base_coding.h */, 92BBFC4B1A7122AD006A7735 /* bifs.h */, 92BBFC4C1A7122AD006A7735 /* bitstream.h */, - 92BBFC4D1A7122AD006A7735 /* cache.h */, 92BBFC4E1A7122AD006A7735 /* color.h */, 92BBFC4F1A7122AD006A7735 /* compositor.h */, 92BBFC501A7122AD006A7735 /* config_file.h */, @@ -1836,7 +1885,7 @@ 92BBFC911A7122AD006A7735 /* nodes_x3d.h */, 92BBFC941A7122AD006A7735 /* path2d.h */, 92BBFC951A7122AD006A7735 /* revision.h */, - 9231B3AE2321533F00ADB4A9 /* Remotery.h */, + 9231B3AE2321533F00ADB4A9 /* rmt_ws.h */, 928E9F9E250F5BA6000C0631 /* route.h */, 92BBFC971A7122AD006A7735 /* rtp_streamer.h */, 92BBFC981A7122AD006A7735 /* scene_engine.h */, @@ -2014,6 +2063,7 @@ 922B0FA92A5E81EB003690BB /* dec_mpeghdec.c */, 92BB85511F7BFB60009BC9C8 /* dec_odf.c */, 92B9A5C01F86ACDF00A24FE4 /* dec_opensvc.c */, + 92B9A5C21F86ACDF00A24FE4 /* dec_scte35.c */, 926656C31F7D3DF700299CC6 /* dec_theora.c */, 922B27712445D5CE001D4F72 /* dec_ttml.c */, 9234CF381FD6A6E400CCDFA1 /* dec_ttxt.c */, @@ -2036,6 +2086,7 @@ 928E7AC41FB9F2B800D2470E /* dmx_nhnt.c */, 92B9A5BE1F8695FA00A24FE4 /* dmx_saf.c */, 926998B31FEACE8F00AA759D /* dmx_vobsub.c */, + 3FDEA5B82D402C0B006F0673 /* enc_cc.c */, 9239B8D5207369E5005F7264 /* enc_jpg.c */, 9239B8D72073AFEF005F7264 /* enc_png.c */, 920F4C9C299D20CE00EC9D21 /* enc_webcodec.c */, @@ -2050,6 +2101,8 @@ 92BB855E1F7BFB61009BC9C8 /* in_http.c */, 9206822B20EA41530041169F /* in_pipe.c */, 928E9FA2250F5BC9000C0631 /* in_route.c */, + 92E8E7EA2BF4B481003D785B /* in_route_repair.c */, + 92E8E7E92BF4B481003D785B /* in_route.h */, 9234CF4C1FD8344500CCDFA1 /* in_rtp */, 9203644C20EE0F190087883E /* in_sock.c */, 92BB855F1F7BFB62009BC9C8 /* inspect.c */, @@ -2076,6 +2129,7 @@ 928A7F802059372C00E02E89 /* out_video.c */, 92061A841FE0484500B5C464 /* reframer.c */, 92BB85801F7C3C4A009BC9C8 /* reframe_ac3.c */, + 925607722E1EC6880022BA42 /* reframe_ac4.c */, 92BB85561F7BFB60009BC9C8 /* reframe_adts.c */, 92BB85841F7C606A009BC9C8 /* reframe_amr.c */, 92EB479020F50C4200E1F2DD /* reframe_av1.c */, @@ -2095,11 +2149,13 @@ 92E2498F2057B973002CC029 /* resample_audio.c */, 9265307927AD295200DEBE4B /* restamp.c */, 92FA79422076687100AE005F /* rewind.c */, + 925607732E1EC6880022BA42 /* rewrite_ac4.c */, 9257307D2056948D009DB328 /* rewrite_adts.c */, 9290E74F25263FC300BC0EE3 /* rewrite_mhas.c */, 925730812056A610009DB328 /* rewrite_mp4v.c */, 9257307E2056948D009DB328 /* rewrite_nalu.c */, 92EB479220F6333800E1F2DD /* rewrite_obu.c */, + 9279EA382DA6C78600E13C5E /* sei_load.c */, 92A08DBC20D946B6003589FF /* tileagg.c */, 92C236DC25769BA500CD59A9 /* tilesplit.c */, 9286070E239576D900FDA1ED /* tssplit.c */, @@ -2159,7 +2215,8 @@ 92BBFCAE1A7122AD006A7735 /* bifs.h in Headers */, 92BBFCED1A7122AD006A7735 /* mpeg4_odf.h in Headers */, 92BBFCBC1A7122AD006A7735 /* events_constants.h in Headers */, - 9231B3AF2321533F00ADB4A9 /* Remotery.h in Headers */, + 925546952D3011C400E0AAF9 /* downloader.h in Headers */, + 9231B3AF2321533F00ADB4A9 /* rmt_ws.h in Headers */, 92BBFCF61A7122AD006A7735 /* revision.h in Headers */, 92BBFCE31A7122AD006A7735 /* audio_out.h in Headers */, 92BBFCF51A7122AD006A7735 /* path2d.h in Headers */, @@ -2185,7 +2242,6 @@ 92BBFCCB1A7122AD006A7735 /* laser_dev.h in Headers */, 92BBFCCA1A7122AD006A7735 /* isomedia_dev.h in Headers */, 920100BC18D5A444003D1ACA /* script.h in Headers */, - 92BBFCB01A7122AD006A7735 /* cache.h in Headers */, 92BBFCFE1A7122AD006A7735 /* setup.h in Headers */, 92BBFCFB1A7122AD006A7735 /* scenegraph_svg.h in Headers */, 92BBFCC01A7122AD006A7735 /* html5_mse.h in Headers */, @@ -2220,6 +2276,7 @@ 92B9A5AA1F8660D700A24FE4 /* texturing.h in Headers */, 92BBFCFC1A7122AD006A7735 /* scenegraph_vrml.h in Headers */, 92BB85771F7BFB63009BC9C8 /* isoffin.h in Headers */, + 92E8E7EB2BF4B482003D785B /* in_route.h in Headers */, 92BBFD031A7122AD006A7735 /* thread.h in Headers */, 92BBFD051A7122AD006A7735 /* tools.h in Headers */, 92BBFCB91A7122AD006A7735 /* dsmcc.h in Headers */, @@ -2492,6 +2549,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 925546A72D3174DE00E0AAF9 /* downloader_ngtcp2.c in Sources */, 923E89D020B6F2D300F299B2 /* filter_props.c in Sources */, 9202FEB32048411D000EE9B1 /* dec_vorbis.c in Sources */, 9202FEB2204840FB000EE9B1 /* load_svg.c in Sources */, @@ -2522,14 +2580,17 @@ 920100B318D5A444003D1ACA /* com_dec.c in Sources */, 9234CF4A1FD8344000CCDFA1 /* in_rtp_signaling.c in Sources */, 920100B418D5A444003D1ACA /* com_enc.c in Sources */, + 925546A52D314BCA00E0AAF9 /* downloader_nghttp2.c in Sources */, 922B27722445D5CE001D4F72 /* dec_ttml.c in Sources */, 920100B518D5A444003D1ACA /* conditional.c in Sources */, 92EC32CD1FAC81D6003FB9C3 /* reframe_h263.c in Sources */, - 92E164BC20F8F2860024A9CA /* Remotery.c in Sources */, + 92E164BC20F8F2860024A9CA /* rmt_ws.c in Sources */, 92BB85781F7BFB63009BC9C8 /* load_bt_xmt.c in Sources */, 920100B618D5A444003D1ACA /* field_decode.c in Sources */, 92BB85721F7BFB63009BC9C8 /* ff_common.c in Sources */, + 92E5E2BA2EB511D100FD8642 /* dtoa.c in Sources */, 920100B718D5A444003D1ACA /* field_encode.c in Sources */, + 92277E1B2BE8F32B005E0955 /* md5.c in Sources */, 92455F8C2A52B4B90080321C /* dec_cc.c in Sources */, 9234CF471FD8344000CCDFA1 /* in_rtp.c in Sources */, 92B9A5911F8660D700A24FE4 /* mpeg4_geometry_2d.c in Sources */, @@ -2544,6 +2605,7 @@ 922E29832051940C00C8DCBB /* mux_avi.c in Sources */, 92BB85761F7BFB63009BC9C8 /* ff_dmx.c in Sources */, 92B9A5B91F866E0500A24FE4 /* mpeg4_inputsensor.c in Sources */, + 925607752E1EC6880022BA42 /* rewrite_ac4.c in Sources */, 92B9A5781F8660D700A24FE4 /* audio_mixer.c in Sources */, 920100BE18D5A444003D1ACA /* script_enc.c in Sources */, 920100BF18D5A444003D1ACA /* unquantize.c in Sources */, @@ -2579,6 +2641,9 @@ 9201010D18D5A444003D1ACA /* rtp_pck_mpeg4.c in Sources */, 92D6C59C2343BED700262217 /* xhr.c in Sources */, 92B9A5C11F86ACDF00A24FE4 /* dec_opensvc.c in Sources */, + 92B9A5C11F86CCDF00A24FE4 /* dec_scte35.c in Sources */, + 925546982D3011C400E0AAF9 /* downloader_cache.c in Sources */, + 9255469E2D301D4700E0AAF9 /* downloader_ssl.c in Sources */, 925CE3451FA2E642004EC6F5 /* decrypt_cenc_isma.c in Sources */, 9201010E18D5A444003D1ACA /* rtp_streamer.c in Sources */, 92B9A58D1F8660D700A24FE4 /* audio_input.c in Sources */, @@ -2614,6 +2679,7 @@ 9201011D18D5A444003D1ACA /* data_map.c in Sources */, 9201011E18D5A444003D1ACA /* drm_sample.c in Sources */, 9201012018D5A444003D1ACA /* hint_track.c in Sources */, + 9255469F2D301D4700E0AAF9 /* downloader_curl.c in Sources */, 92BB857D1F7BFD80009BC9C8 /* dec_xvid.c in Sources */, 92334CE12333AF0400DA6051 /* jsfilter.c in Sources */, 9201012118D5A444003D1ACA /* hinting.c in Sources */, @@ -2673,6 +2739,7 @@ 925975052215BA3A0007E02B /* raster_yuv.c in Sources */, 92E318D120651834007BE021 /* vcrop.c in Sources */, 92B9A5971F8660D700A24FE4 /* audio_render.c in Sources */, + 9279EA392DA6C78600E13C5E /* sei_load.c in Sources */, 9201014618D5A445003D1ACA /* dash_segmenter.c in Sources */, 9201014718D5A445003D1ACA /* dsmcc.c in Sources */, 92B9A5BF1F8695FA00A24FE4 /* dmx_saf.c in Sources */, @@ -2689,6 +2756,7 @@ 9201015118D5A445003D1ACA /* m2ts_mux.c in Sources */, 9201015218D5A445003D1ACA /* m3u8.c in Sources */, 92256CCD20E0DA26004EB243 /* encrypt_cenc_isma.c in Sources */, + 3F5F3F1F2C7EAC5D00C58257 /* id3.c in Sources */, 926998B41FEACE8F00AA759D /* dmx_vobsub.c in Sources */, 9201015318D5A445003D1ACA /* media_export.c in Sources */, 9201015418D5A445003D1ACA /* media_import.c in Sources */, @@ -2706,6 +2774,7 @@ 92E164AD20F8EBAE0024A9CA /* dec_nvdec.c in Sources */, 9201015918D5A445003D1ACA /* reedsolomon.c in Sources */, 92261A87239A6E4600C14199 /* out_http.c in Sources */, + 925546A02D301D4700E0AAF9 /* downloader_emscripten.c in Sources */, 9201015A18D5A445003D1ACA /* saf.c in Sources */, 92B9A5B71F866D6500A24FE4 /* scene_node_init.c in Sources */, 9201015C18D5A445003D1ACA /* vobsub.c in Sources */, @@ -2735,6 +2804,7 @@ 9201016618D5A445003D1ACA /* odf_command.c in Sources */, 9201016718D5A445003D1ACA /* odf_dump.c in Sources */, 92C0F37E21EC7BB0002961C8 /* out_rtp.c in Sources */, + 925607742E1EC6880022BA42 /* reframe_ac4.c in Sources */, 9201016818D5A445003D1ACA /* odf_parse.c in Sources */, 9201016918D5A445003D1ACA /* qos.c in Sources */, 9206822E20EA69680041169F /* out_pipe.c in Sources */, @@ -2782,6 +2852,7 @@ 9296BF2222CF7F5300DC9742 /* reframe_flac.c in Sources */, 9201017718D5A445003D1ACA /* swf_svg.c in Sources */, 92BB857F1F7C30AF009BC9C8 /* dec_j2k.c in Sources */, + 3F04FCBC2C9CB179002BC889 /* xml_bin_custom.c in Sources */, 923E89CE20B6F04600F299B2 /* tiny_aes.c in Sources */, 92B9A5071F83FD7800A24FE4 /* in_dvb4linux.c in Sources */, 92B9A5761F8660D700A24FE4 /* mpeg4_background2d.c in Sources */, @@ -2828,8 +2899,8 @@ 922CF1271FCF169900F2F6A4 /* dmx_avi.c in Sources */, 9201018F18D5A445003D1ACA /* x3d_nodes.c in Sources */, 92B9A5681F8660D700A24FE4 /* mpeg4_layer_3d.c in Sources */, + 92E8E7EC2BF4B482003D785B /* in_route_repair.c in Sources */, 9201019118D5A445003D1ACA /* xml_ns.c in Sources */, - 923EB53223D1B78D00E1FFA1 /* libbf.c in Sources */, 92B9A59F1F8660D700A24FE4 /* mpeg4_inline.c in Sources */, 92F8D4781F71642E00616F7C /* filter_pid.c in Sources */, 92BB85701F7BFB63009BC9C8 /* in_file.c in Sources */, @@ -2839,12 +2910,12 @@ 92B9A56D1F8660D700A24FE4 /* mpeg4_geometry_3d.c in Sources */, 920101A818D5A445003D1ACA /* bitstream.c in Sources */, 92B9A59C1F8660D700A24FE4 /* camera.c in Sources */, - 920101A918D5A445003D1ACA /* cache.c in Sources */, 926F15531FE867B000C40779 /* write_vtt.c in Sources */, 920101AA18D5A445003D1ACA /* color.c in Sources */, 92B9A5841F8660D700A24FE4 /* mpeg4_form.c in Sources */, 928E0BD628E2FC1900F2BA27 /* unframer.c in Sources */, 92BB9A671F9C827600524E5B /* dmx_dash.c in Sources */, + 3FDEA5B92D402C0B006F0673 /* enc_cc.c in Sources */, 92B9A5A71F8660D700A24FE4 /* texturing_gl.c in Sources */, 92B9A5A51F8660D700A24FE4 /* mpeg4_gradients.c in Sources */, 9286070B23884A6B00FDA1ED /* ff_mx.c in Sources */, @@ -2868,6 +2939,7 @@ 920101B518D5A445003D1ACA /* os_config_init.c in Sources */, 920101B818D5A445003D1ACA /* os_net.c in Sources */, 920101B918D5A445003D1ACA /* os_thread.c in Sources */, + 925546A32D314A9600E0AAF9 /* downloader_hmux.c in Sources */, 925E564C2798B87300BBD530 /* bs_agg.c in Sources */, 927DB14C21A3C7100032D205 /* reframe_latm.c in Sources */, 920101BA18D5A445003D1ACA /* path2d.c in Sources */, @@ -3099,6 +3171,7 @@ GPAC_HAS_VORBIS, GPAC_HAS_THEORA, GPAC_HAS_FFMPEG, + GPAC_HAS_CURL, FFMPEG_ENABLE_VVC, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; @@ -3114,17 +3187,20 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../include, - /opt/local/include, /usr/local/include, + /opt/local/include, "/opt/local/include/openjpeg-2.5", "/usr/local/include/openjpeg-2.5", /opt/homebrew/include, "/opt/homebrew/include/openjpeg-2.5", + ../../extra_lib/include/, + ../../src/quickjs/, ); LIBRARY_SEARCH_PATHS = ( /usr/local/lib, /opt/local/lib, /opt/homebrew/lib, + ../../extra_lib/lib/gcc, ); MACOSX_DEPLOYMENT_TARGET = 10.6; OBJROOT = build; @@ -3217,6 +3293,10 @@ "-lX11", "-lXext", "-lvvenc", + "-lcurl", + "-lngtcp2_crypto_quictls", + "-lnghttp3", + "-lngtcp2", "-lcaca", ); PRODUCT_NAME = gpac; @@ -3252,6 +3332,8 @@ GPAC_HAS_VORBIS, GPAC_HAS_THEORA, GPAC_HAS_FFMPEG, + GPAC_HAS_CURL, + GPAC_HAS_CURL, FFMPEG_ENABLE_VVC, ); GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; @@ -3266,17 +3348,20 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../include, - /opt/local/include, /usr/local/include, + /opt/local/include, "/opt/local/include/openjpeg-2.5", "/usr/local/include/openjpeg-2.5", /opt/homebrew/include, "/opt/homebrew/include/openjpeg-2.5", + ../../extra_lib/include/, + ../../src/quickjs/, ); LIBRARY_SEARCH_PATHS = ( /usr/local/lib, /opt/local/lib, /opt/homebrew/lib, + ../../extra_lib/lib/gcc, ); MACOSX_DEPLOYMENT_TARGET = 10.6; OBJROOT = build; @@ -3369,6 +3454,10 @@ "-lX11", "-lXext", "-lvvenc", + "-lcurl", + "-lngtcp2_crypto_quictls", + "-lnghttp3", + "-lngtcp2", "-lcaca", ); PRODUCT_NAME = gpac; @@ -3538,6 +3627,7 @@ ENABLE_NS_ASSERTIONS = NO; EXCLUDED_ARCHS = ""; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = GPAC_CONFIG_DARWIN; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; LIBRARY_SEARCH_PATHS = "";
View file
gpac-2.4.0.tar.gz/build/xcode/gpac4ios.xcodeproj/project.pbxproj -> gpac-26.02.0.tar.gz/build/xcode/gpac4ios.xcodeproj/project.pbxproj
Changed
@@ -20,8 +20,9 @@ 285DBBA21712C80E00AF2B9B /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 285DBB9D1712C80E00AF2B9B /* libavutil.a */; }; 285DBBA41712C80E00AF2B9B /* libswscale.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 285DBB9F1712C80E00AF2B9B /* libswscale.a */; }; 2897039C1A13C4050063E9C0 /* libswresample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2897039B1A13C4050063E9C0 /* libswresample.a */; }; + 3F04FCBA2C9CB139002BC889 /* xml_bin_custom.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F04FCB92C9CB139002BC889 /* xml_bin_custom.c */; }; + 3F5F3F212C7EAD0C00C58257 /* id3.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F5F3F202C7EAD0C00C58257 /* id3.c */; }; 7141FF301292CB9200FCB27D /* alloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 7141FF2F1292CB9200FCB27D /* alloc.c */; }; - 7147426012B918C900DB5A59 /* cache.c in Sources */ = {isa = PBXBuildFile; fileRef = 7147425F12B918C900DB5A59 /* cache.c */; }; 71A19BB212797D1B00ACFF58 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71A19BB112797D1B00ACFF58 /* OpenGLES.framework */; }; 71A19BC412797D9200ACFF58 /* libobjc.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 71A19BC312797D9200ACFF58 /* libobjc.dylib */; }; 71A19BCD12797DBA00ACFF58 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71A19BCC12797DBA00ACFF58 /* UIKit.framework */; settings = {ATTRIBUTES = (Required, ); }; }; @@ -307,6 +308,7 @@ 71CCF3FC12770A8C00339E12 /* tools.h in Headers */ = {isa = PBXBuildFile; fileRef = 71CCF3AD12770A8C00339E12 /* tools.h */; }; 71CCF3FE12770A8C00339E12 /* utf.h in Headers */ = {isa = PBXBuildFile; fileRef = 71CCF3AF12770A8C00339E12 /* utf.h */; }; 71CCF3FF12770A8C00339E12 /* xml.h in Headers */ = {isa = PBXBuildFile; fileRef = 71CCF3B012770A8C00339E12 /* xml.h */; }; + 89162EF52C00DA10004EA21C /* dec_scte35.c in Sources */ = {isa = PBXBuildFile; fileRef = 92EB6DC020E6E19700A97A49 /* dec_scte35.c */; }; 8925C0EE25DD6F8900AFB064 /* libnghttp2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8925C0ED25DD6F8900AFB064 /* libnghttp2.a */; }; 8959AC77211C9A26004D2C87 /* dmx_gsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC76211C9A26004D2C87 /* dmx_gsf.c */; }; 8959AC79211C9B29004D2C87 /* reframe_av1.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC78211C9B29004D2C87 /* reframe_av1.c */; }; @@ -316,7 +318,7 @@ 8959AC8B211C9C3C004D2C87 /* out_pipe.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC81211C9C3C004D2C87 /* out_pipe.c */; }; 8959AC8C211C9C3C004D2C87 /* out_sock.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC82211C9C3C004D2C87 /* out_sock.c */; }; 8959AC8D211C9C3C004D2C87 /* rewrite_obu.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC83211C9C3C004D2C87 /* rewrite_obu.c */; }; - 8959AC8F211C9C77004D2C87 /* Remotery.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC8E211C9C77004D2C87 /* Remotery.c */; }; + 8959AC8F211C9C77004D2C87 /* rmt_ws.c in Sources */ = {isa = PBXBuildFile; fileRef = 8959AC8E211C9C77004D2C87 /* rmt_ws.c */; }; 896CBC872110598E00220377 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 896CBC862110598E00220377 /* Security.framework */; }; 896CBC8921105A6900220377 /* libaom.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 896CBC8821105A6900220377 /* libaom.a */; }; 89F956CB2A6595470049D6D3 /* libmpegh.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F956CA2A6595470049D6D3 /* libmpegh.a */; }; @@ -328,6 +330,7 @@ 921FEBA01CF8670A00B437C2 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 921FEB9E1CF8665900B437C2 /* CoreLocation.framework */; }; 921FEBA11CF8671300B437C2 /* CoreMotion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 921FEB9C1CF8664800B437C2 /* CoreMotion.framework */; }; 92261A89239E326900C14199 /* out_http.c in Sources */ = {isa = PBXBuildFile; fileRef = 92261A88239E326900C14199 /* out_http.c */; }; + 92277E1D2BE910E1005E0955 /* md5.c in Sources */ = {isa = PBXBuildFile; fileRef = 92277E1C2BE910E1005E0955 /* md5.c */; }; 9227B8582799F8E700BF3D1B /* bs_agg.c in Sources */ = {isa = PBXBuildFile; fileRef = 9227B8562799F8E600BF3D1B /* bs_agg.c */; }; 9227B8592799F8E700BF3D1B /* bs_split.c in Sources */ = {isa = PBXBuildFile; fileRef = 9227B8572799F8E600BF3D1B /* bs_split.c */; }; 922B0FAC2A5EBCF2003690BB /* dec_mpeghdec.c in Sources */ = {isa = PBXBuildFile; fileRef = 922B0FAB2A5EBCF2003690BB /* dec_mpeghdec.c */; }; @@ -349,11 +352,12 @@ 92359C83250F651900525A42 /* mpd.h in Headers */ = {isa = PBXBuildFile; fileRef = 92359C7D250F651900525A42 /* mpd.h */; }; 92359C84250F651900525A42 /* route.h in Headers */ = {isa = PBXBuildFile; fileRef = 92359C7E250F651900525A42 /* route.h */; }; 92359C85250F651900525A42 /* evg.h in Headers */ = {isa = PBXBuildFile; fileRef = 92359C7F250F651900525A42 /* evg.h */; }; - 92359C86250F651900525A42 /* Remotery.h in Headers */ = {isa = PBXBuildFile; fileRef = 92359C80250F651900525A42 /* Remotery.h */; }; + 92359C86250F651900525A42 /* rmt_ws.h in Headers */ = {isa = PBXBuildFile; fileRef = 92359C80250F651900525A42 /* rmt_ws.h */; }; 92359C88250F652700525A42 /* route_dmx.c in Sources */ = {isa = PBXBuildFile; fileRef = 92359C87250F652700525A42 /* route_dmx.c */; }; 92359C8A250F657B00525A42 /* in_route.c in Sources */ = {isa = PBXBuildFile; fileRef = 92359C89250F657A00525A42 /* in_route.c */; }; - 923EB53423D1B87E00E1FFA1 /* libbf.c in Sources */ = {isa = PBXBuildFile; fileRef = 923EB53323D1B87E00E1FFA1 /* libbf.c */; }; + 923EB53423D1B87E00E1FFA1 /* dtoa.c in Sources */ = {isa = PBXBuildFile; fileRef = 923EB53323D1B87E00E1FFA1 /* dtoa.c */; }; 92455F8E2A530F560080321C /* dec_cc.c in Sources */ = {isa = PBXBuildFile; fileRef = 92455F8D2A530F560080321C /* dec_cc.c */; }; + 3FDEA5BB2D402C3D006F0673 /* enc_cc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FDEA5BA2D402C3D006F0673 /* enc_cc.c */; }; 92455FDB2A540A640080321C /* libcaption.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 92455FDA2A540A640080321C /* libcaption.a */; }; 9246EE7724A1240600F72EAD /* filter_session_js.c in Sources */ = {isa = PBXBuildFile; fileRef = 9246EE7624A1240600F72EAD /* filter_session_js.c */; }; 924AB67F281438E100EC006F /* default.cfg in Resources */ = {isa = PBXBuildFile; fileRef = 924AB67E281438E000EC006F /* default.cfg */; }; @@ -362,6 +366,16 @@ 924AB6852816F1E200EC006F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 924AB6842816F1E200EC006F /* GameController.framework */; }; 9251E540251E398700CEB84D /* res in Resources */ = {isa = PBXBuildFile; fileRef = 9251E53F251E398600CEB84D /* res */; }; 9251E542251E39A300CEB84D /* scripts in Resources */ = {isa = PBXBuildFile; fileRef = 9251E541251E39A300CEB84D /* scripts */; }; + 925546B12D3175DC00E0AAF9 /* downloader_hmux.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546A92D3175DB00E0AAF9 /* downloader_hmux.c */; }; + 925546B22D3175DC00E0AAF9 /* downloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 925546AA2D3175DB00E0AAF9 /* downloader.h */; }; + 925546B32D3175DC00E0AAF9 /* downloader_nghttp2.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546AB2D3175DC00E0AAF9 /* downloader_nghttp2.c */; }; + 925546B42D3175DC00E0AAF9 /* downloader_curl.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546AC2D3175DC00E0AAF9 /* downloader_curl.c */; }; + 925546B52D3175DC00E0AAF9 /* downloader_ngtcp2.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546AD2D3175DC00E0AAF9 /* downloader_ngtcp2.c */; }; + 925546B62D3175DC00E0AAF9 /* downloader_cache.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546AE2D3175DC00E0AAF9 /* downloader_cache.c */; }; + 925546B72D3175DC00E0AAF9 /* downloader_ssl.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546AF2D3175DC00E0AAF9 /* downloader_ssl.c */; }; + 925546B82D3175DC00E0AAF9 /* downloader_emscripten.c in Sources */ = {isa = PBXBuildFile; fileRef = 925546B02D3175DC00E0AAF9 /* downloader_emscripten.c */; }; + 925607782E1EC7030022BA42 /* rewrite_ac4.c in Sources */ = {isa = PBXBuildFile; fileRef = 925607762E1EC7030022BA42 /* rewrite_ac4.c */; }; + 925607792E1EC7030022BA42 /* reframe_ac4.c in Sources */ = {isa = PBXBuildFile; fileRef = 925607772E1EC7030022BA42 /* reframe_ac4.c */; }; 925730A21A0916D20057FF04 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 925730A11A0916D20057FF04 /* libiconv.dylib */; }; 925730A41A0916DB0057FF04 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 925730A31A0916DB0057FF04 /* libbz2.dylib */; }; 925A7611292F998500664F38 /* sha256.c in Sources */ = {isa = PBXBuildFile; fileRef = 925A7610292F998500664F38 /* sha256.c */; }; @@ -372,6 +386,7 @@ 9272B50627E4EFAD00D9D304 /* mux_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 9272B50527E4EFAD00D9D304 /* mux_ogg.c */; }; 92734635235084A50081AF74 /* storage.c in Sources */ = {isa = PBXBuildFile; fileRef = 92734634235084A50081AF74 /* storage.c */; }; 9274295A24647B98009ADD8D /* bsrw.c in Sources */ = {isa = PBXBuildFile; fileRef = 9274295924647B98009ADD8D /* bsrw.c */; }; + 9279EA3B2DA6F06D00E13C5E /* sei_load.c in Sources */ = {isa = PBXBuildFile; fileRef = 9279EA3A2DA6F06D00E13C5E /* sei_load.c */; }; 9281B63021C92D00006B0FD5 /* gui in Resources */ = {isa = PBXBuildFile; fileRef = 9281B62E21C92D00006B0FD5 /* gui */; }; 9281B63121C92D00006B0FD5 /* shaders in Resources */ = {isa = PBXBuildFile; fileRef = 9281B62F21C92D00006B0FD5 /* shaders */; }; 92852443237ACC2700165130 /* raster3d.c in Sources */ = {isa = PBXBuildFile; fileRef = 92852442237ACC2700165130 /* raster3d.c */; }; @@ -434,6 +449,8 @@ 92D6C5AD2343BF9400262217 /* dom_js.c in Sources */ = {isa = PBXBuildFile; fileRef = 92D6C5AA2343BF9400262217 /* dom_js.c */; }; 92DF5CA61BA6C6F80058A7BA /* iff.c in Sources */ = {isa = PBXBuildFile; fileRef = 92DF5CA51BA6C6F80058A7BA /* iff.c */; }; 92E8C7901A0CD58300E0436D /* validator.c in Sources */ = {isa = PBXBuildFile; fileRef = 92FA825916F2549D0002629E /* validator.c */; }; + 92E8E7EF2BF4F93E003D785B /* in_route_repair.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E8E7ED2BF4F93E003D785B /* in_route_repair.c */; }; + 92E8E7F02BF4F93E003D785B /* in_route.h in Headers */ = {isa = PBXBuildFile; fileRef = 92E8E7EE2BF4F93E003D785B /* in_route.h */; }; 92E9DFA22215CE9A00628420 /* g_crypt_tinyaes.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E9DF9D2215CE9900628420 /* g_crypt_tinyaes.c */; }; 92E9DFA32215CE9A00628420 /* tiny_aes.h in Headers */ = {isa = PBXBuildFile; fileRef = 92E9DF9E2215CE9900628420 /* tiny_aes.h */; }; 92E9DFA42215CE9A00628420 /* tiny_aes.c in Sources */ = {isa = PBXBuildFile; fileRef = 92E9DF9F2215CE9A00628420 /* tiny_aes.c */; }; @@ -564,7 +581,6 @@ 970A26CA13434D2C0007362C /* mpd.c in Sources */ = {isa = PBXBuildFile; fileRef = 970A26C813434D2C0007362C /* mpd.c */; }; 970A26CD13434D780007362C /* m3u8.h in Headers */ = {isa = PBXBuildFile; fileRef = 970A26CB13434D780007362C /* m3u8.h */; }; 970A26CE13434D780007362C /* mpd.h in Headers */ = {isa = PBXBuildFile; fileRef = 970A26CC13434D780007362C /* mpd.h */; }; - 970A26D213434D960007362C /* cache.h in Headers */ = {isa = PBXBuildFile; fileRef = 970A26CF13434D960007362C /* cache.h */; }; 970A26D813434DAC0007362C /* unicode.c in Sources */ = {isa = PBXBuildFile; fileRef = 970A26D613434DAC0007362C /* unicode.c */; }; 97116F211646E38D00BD1AD0 /* box_code_adobe.c in Sources */ = {isa = PBXBuildFile; fileRef = 97116F201646E38D00BD1AD0 /* box_code_adobe.c */; }; 9719CE751704C35000381CE6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9793AE341704C0BD00E0C938 /* CoreGraphics.framework */; }; @@ -612,8 +628,9 @@ 285DBB9D1712C80E00AF2B9B /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavutil.a; path = ../../extra_lib/lib/iOS/libavutil.a; sourceTree = "<group>"; }; 285DBB9F1712C80E00AF2B9B /* libswscale.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libswscale.a; path = ../../extra_lib/lib/iOS/libswscale.a; sourceTree = "<group>"; }; 2897039B1A13C4050063E9C0 /* libswresample.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libswresample.a; path = ../../extra_lib/lib/iOS/libswresample.a; sourceTree = "<group>"; }; + 3F04FCB92C9CB139002BC889 /* xml_bin_custom.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = xml_bin_custom.c; sourceTree = "<group>"; }; + 3F5F3F202C7EAD0C00C58257 /* id3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = id3.c; sourceTree = "<group>"; }; 7141FF2F1292CB9200FCB27D /* alloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = alloc.c; sourceTree = "<group>"; }; - 7147425F12B918C900DB5A59 /* cache.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cache.c; sourceTree = "<group>"; }; 71847811127860F000917298 /* libc.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libc.dylib; path = usr/lib/libc.dylib; sourceTree = SDKROOT; }; 71847813127860F000917298 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 71A199811278776700ACFF58 /* ft_font.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ft_font.c; path = ../../modules/ft_font/ft_font.c; sourceTree = "<group>"; }; @@ -926,7 +943,7 @@ 8959AC81211C9C3C004D2C87 /* out_pipe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = out_pipe.c; path = ../../src/filters/out_pipe.c; sourceTree = "<group>"; }; 8959AC82211C9C3C004D2C87 /* out_sock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = out_sock.c; path = ../../src/filters/out_sock.c; sourceTree = "<group>"; }; 8959AC83211C9C3C004D2C87 /* rewrite_obu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rewrite_obu.c; path = ../../src/filters/rewrite_obu.c; sourceTree = "<group>"; }; - 8959AC8E211C9C77004D2C87 /* Remotery.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Remotery.c; sourceTree = "<group>"; }; + 8959AC8E211C9C77004D2C87 /* rmt_ws.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rmt_ws.c; sourceTree = "<group>"; }; 896CBC862110598E00220377 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 896CBC8821105A6900220377 /* libaom.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaom.a; path = ../../extra_lib/lib/iOS/libaom.a; sourceTree = "<group>"; }; 89F956CA2A6595470049D6D3 /* libmpegh.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmpegh.a; path = ../../extra_lib/lib/iOS/libmpegh.a; sourceTree = "<group>"; }; @@ -938,6 +955,7 @@ 921FEB9C1CF8664800B437C2 /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = System/Library/Frameworks/CoreMotion.framework; sourceTree = SDKROOT; }; 921FEB9E1CF8665900B437C2 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 92261A88239E326900C14199 /* out_http.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = out_http.c; path = ../../src/filters/out_http.c; sourceTree = "<group>"; }; + 92277E1C2BE910E1005E0955 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md5.c; sourceTree = "<group>"; }; 9227B8562799F8E600BF3D1B /* bs_agg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bs_agg.c; path = ../../src/filters/bs_agg.c; sourceTree = "<group>"; }; 9227B8572799F8E600BF3D1B /* bs_split.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bs_split.c; path = ../../src/filters/bs_split.c; sourceTree = "<group>"; }; 922B0FAB2A5EBCF2003690BB /* dec_mpeghdec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_mpeghdec.c; path = ../../src/filters/dec_mpeghdec.c; sourceTree = "<group>"; }; @@ -959,11 +977,12 @@ 92359C7D250F651900525A42 /* mpd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpd.h; sourceTree = "<group>"; }; 92359C7E250F651900525A42 /* route.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = route.h; sourceTree = "<group>"; }; 92359C7F250F651900525A42 /* evg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evg.h; sourceTree = "<group>"; }; - 92359C80250F651900525A42 /* Remotery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Remotery.h; sourceTree = "<group>"; }; + 92359C80250F651900525A42 /* rmt_ws.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rmt_ws.h; sourceTree = "<group>"; }; 92359C87250F652700525A42 /* route_dmx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = route_dmx.c; sourceTree = "<group>"; }; 92359C89250F657A00525A42 /* in_route.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = in_route.c; path = ../../src/filters/in_route.c; sourceTree = "<group>"; }; - 923EB53323D1B87E00E1FFA1 /* libbf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libbf.c; path = ../../src/quickjs/libbf.c; sourceTree = "<group>"; }; + 923EB53323D1B87E00E1FFA1 /* dtoa.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dtoa.c; path = ../../src/quickjs/dtoa.c; sourceTree = "<group>"; }; 92455F8D2A530F560080321C /* dec_cc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_cc.c; path = ../../src/filters/dec_cc.c; sourceTree = "<group>"; }; + 3FDEA5BA2D402C3D006F0673 /* enc_cc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = enc_cc.c; path = ../../src/filters/enc_cc.c; sourceTree = "<group>"; }; 92455FDA2A540A640080321C /* libcaption.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcaption.a; path = ../../extra_lib/lib/iOS/libcaption.a; sourceTree = "<group>"; }; 9246EE7624A1240600F72EAD /* filter_session_js.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = filter_session_js.c; path = ../../src/filter_core/filter_session_js.c; sourceTree = "<group>"; }; 924AB67E281438E000EC006F /* default.cfg */ = {isa = PBXFileReference; lastKnownFileType = text; name = default.cfg; path = ../../share/default.cfg; sourceTree = "<group>"; }; @@ -973,6 +992,16 @@ 924AB6862816F20300EC006F /* CoreHaptics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreHaptics.framework; path = System/Library/Frameworks/CoreHaptics.framework; sourceTree = SDKROOT; }; 9251E53F251E398600CEB84D /* res */ = {isa = PBXFileReference; lastKnownFileType = folder; name = res; path = ../../share/res; sourceTree = "<group>"; }; 9251E541251E39A300CEB84D /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; name = scripts; path = ../../share/scripts; sourceTree = "<group>"; }; + 925546A92D3175DB00E0AAF9 /* downloader_hmux.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_hmux.c; sourceTree = "<group>"; }; + 925546AA2D3175DB00E0AAF9 /* downloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = downloader.h; sourceTree = "<group>"; }; + 925546AB2D3175DC00E0AAF9 /* downloader_nghttp2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_nghttp2.c; sourceTree = "<group>"; }; + 925546AC2D3175DC00E0AAF9 /* downloader_curl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_curl.c; sourceTree = "<group>"; }; + 925546AD2D3175DC00E0AAF9 /* downloader_ngtcp2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_ngtcp2.c; sourceTree = "<group>"; }; + 925546AE2D3175DC00E0AAF9 /* downloader_cache.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_cache.c; sourceTree = "<group>"; }; + 925546AF2D3175DC00E0AAF9 /* downloader_ssl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_ssl.c; sourceTree = "<group>"; }; + 925546B02D3175DC00E0AAF9 /* downloader_emscripten.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = downloader_emscripten.c; sourceTree = "<group>"; }; + 925607762E1EC7030022BA42 /* rewrite_ac4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rewrite_ac4.c; path = ../../src/filters/rewrite_ac4.c; sourceTree = "<group>"; }; + 925607772E1EC7030022BA42 /* reframe_ac4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reframe_ac4.c; path = ../../src/filters/reframe_ac4.c; sourceTree = "<group>"; }; 925730A11A0916D20057FF04 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; 925730A31A0916DB0057FF04 /* libbz2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbz2.dylib; path = usr/lib/libbz2.dylib; sourceTree = SDKROOT; }; 925A7610292F998500664F38 /* sha256.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sha256.c; sourceTree = "<group>"; }; @@ -982,6 +1011,7 @@ 9272B50527E4EFAD00D9D304 /* mux_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mux_ogg.c; path = ../../src/filters/mux_ogg.c; sourceTree = "<group>"; }; 92734634235084A50081AF74 /* storage.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = storage.c; path = ../../src/jsmods/storage.c; sourceTree = "<group>"; }; 9274295924647B98009ADD8D /* bsrw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bsrw.c; path = ../../src/filters/bsrw.c; sourceTree = "<group>"; }; + 9279EA3A2DA6F06D00E13C5E /* sei_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sei_load.c; path = ../../src/filters/sei_load.c; sourceTree = "<group>"; }; 9281B62E21C92D00006B0FD5 /* gui */ = {isa = PBXFileReference; lastKnownFileType = folder; name = gui; path = ../../share/gui; sourceTree = "<group>"; }; 9281B62F21C92D00006B0FD5 /* shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; name = shaders; path = ../../share/shaders; sourceTree = "<group>"; }; 92852442237ACC2700165130 /* raster3d.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = raster3d.c; path = ../../src/evg/raster3d.c; sourceTree = "<group>"; }; @@ -1039,6 +1069,8 @@ 92D6C5A92343BF9400262217 /* vrml_js.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vrml_js.c; sourceTree = "<group>"; }; 92D6C5AA2343BF9400262217 /* dom_js.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dom_js.c; sourceTree = "<group>"; }; 92DF5CA51BA6C6F80058A7BA /* iff.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = iff.c; sourceTree = "<group>"; }; + 92E8E7ED2BF4F93E003D785B /* in_route_repair.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = in_route_repair.c; path = ../../src/filters/in_route_repair.c; sourceTree = "<group>"; }; + 92E8E7EE2BF4F93E003D785B /* in_route.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = in_route.h; path = ../../src/filters/in_route.h; sourceTree = "<group>"; }; 92E9DF9D2215CE9900628420 /* g_crypt_tinyaes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = g_crypt_tinyaes.c; path = ../../src/crypto/g_crypt_tinyaes.c; sourceTree = "<group>"; }; 92E9DF9E2215CE9900628420 /* tiny_aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tiny_aes.h; path = ../../src/crypto/tiny_aes.h; sourceTree = "<group>"; }; 92E9DF9F2215CE9A00628420 /* tiny_aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = tiny_aes.c; path = ../../src/crypto/tiny_aes.c; sourceTree = "<group>"; }; @@ -1141,6 +1173,7 @@ 92EB6DBE20E4E19700A97A49 /* decrypt_cenc_isma.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = decrypt_cenc_isma.c; path = ../../src/filters/decrypt_cenc_isma.c; sourceTree = "<group>"; }; 92EB6DBF20E4E19700A97A49 /* enc_jpg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = enc_jpg.c; path = ../../src/filters/enc_jpg.c; sourceTree = "<group>"; }; 92EB6DC020E4E19700A97A49 /* dec_opensvc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_opensvc.c; path = ../../src/filters/dec_opensvc.c; sourceTree = "<group>"; }; + 92EB6DC020E6E19700A97A49 /* dec_scte35.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_scte35.c; path = ../../src/filters/dec_scte35.c; sourceTree = "<group>"; }; 92EB6DC120E4E19700A97A49 /* ff_enc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ff_enc.c; path = ../../src/filters/ff_enc.c; sourceTree = "<group>"; }; 92EB6DC220E4E19700A97A49 /* in_http.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = in_http.c; path = ../../src/filters/in_http.c; sourceTree = "<group>"; }; 92EB6DC320E4E19700A97A49 /* dec_vtb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dec_vtb.c; path = ../../src/filters/dec_vtb.c; sourceTree = "<group>"; }; @@ -1171,7 +1204,6 @@ 970A26C813434D2C0007362C /* mpd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mpd.c; sourceTree = "<group>"; }; 970A26CB13434D780007362C /* m3u8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = m3u8.h; sourceTree = "<group>"; }; 970A26CC13434D780007362C /* mpd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpd.h; sourceTree = "<group>"; }; - 970A26CF13434D960007362C /* cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cache.h; sourceTree = "<group>"; }; 970A26D613434DAC0007362C /* unicode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unicode.c; sourceTree = "<group>"; }; 970A270D1343540C0007362C /* libgcc_s.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libgcc_s.1.dylib; path = usr/lib/libgcc_s.1.dylib; sourceTree = SDKROOT; }; 97116F201646E38D00BD1AD0 /* box_code_adobe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = box_code_adobe.c; sourceTree = "<group>"; }; @@ -1572,6 +1604,7 @@ 9747B6F314864C7800280D95 /* dsmcc.c */, 71CCF1D01277045100339E12 /* dvb_mpe.c */, 71CCF1D21277045100339E12 /* gpac_ogg.c */, + 3F5F3F202C7EAD0C00C58257 /* id3.c */, 71CCF1D31277045100339E12 /* img.c */, 71CCF1D51277045100339E12 /* isom_hinter.c */, 71CCF1D61277045100339E12 /* isom_tools.c */, @@ -1672,15 +1705,15 @@ 71CCF22A1277045100339E12 /* base_encoding.c */, 71CCF22B1277045100339E12 /* bitstream.c */, 92EB6E3A20E4EBDB00A97A49 /* constants.c */, - 7147425F12B918C900DB5A59 /* cache.c */, 71CCF22C1277045100339E12 /* color.c */, 71CCF22D1277045100339E12 /* configfile.c */, - 71CCF22F1277045100339E12 /* downloader.c */, + 925546A82D3175C500E0AAF9 /* downloader */, 71CCF2301277045100339E12 /* error.c */, 929DE74824183FB40056AFA7 /* gzio.c */, 9298677E235A1587008A2CC6 /* gltools.c */, 71CCF2321277045100339E12 /* list.c */, 71CCF2331277045100339E12 /* math.c */, + 92277E1C2BE910E1005E0955 /* md5.c */, 71CCF2351277045100339E12 /* module_wrap.h */, 71CCF2341277045100339E12 /* module.c */, 97FF065F138CE4C400D6C707 /* os_config_init.c */, @@ -1695,11 +1728,12 @@ 97DDAFEE1569631900528219 /* sha1.c */, 925A7610292F998500664F38 /* sha256.c */, 71CCF23E1277045100339E12 /* token.c */, - 8959AC8E211C9C77004D2C87 /* Remotery.c */, + 8959AC8E211C9C77004D2C87 /* rmt_ws.c */, 71CCF23F1277045100339E12 /* uni_bidi.c */, 970A26D613434DAC0007362C /* unicode.c */, 71CCF2401277045100339E12 /* url.c */, 71CCF2411277045100339E12 /* utf.c */, + 3F04FCB92C9CB139002BC889 /* xml_bin_custom.c */, 71CCF2421277045100339E12 /* xml_parser.c */, 71CCF2441277045100339E12 /* zutil.c */, 71CCF2451277045100339E12 /* zutil.h */, @@ -1724,7 +1758,7 @@ 92359C7C250F651900525A42 /* filters.h */, 92359C7B250F651900525A42 /* main.h */, 92359C7D250F651900525A42 /* mpd.h */, - 92359C80250F651900525A42 /* Remotery.h */, + 92359C80250F651900525A42 /* rmt_ws.h */, 92359C7E250F651900525A42 /* route.h */, 92EB6E2120E4E1B500A97A49 /* crypt_tools.h */, 922EB5781AAE13C500DF46D0 /* dash.h */, @@ -1737,7 +1771,6 @@ 9747B6EC14864C6400280D95 /* ait.h */, 9747B6ED14864C6400280D95 /* dsmcc.h */, 9747B6EE14864C6400280D95 /* events_constants.h */, - 970A26CF13434D960007362C /* cache.h */, 71CCF36012770A8C00339E12 /* avparse.h */, 71CCF36112770A8C00339E12 /* base_coding.h */, 71CCF36212770A8C00339E12 /* bifs.h */, @@ -1836,7 +1869,7 @@ isa = PBXGroup; children = ( 9285D1BF266FB8D3008FCD14 /* quickjs-libc.c */, - 923EB53323D1B87E00E1FFA1 /* libbf.c */, + 923EB53323D1B87E00E1FFA1 /* dtoa.c */, 92334CC92333AB9900DA6051 /* cutils.c */, 92334CCA2333AB9A00DA6051 /* libregexp.c */, 92334CBE2333AB9900DA6051 /* libunicode.c */, @@ -1846,6 +1879,22 @@ name = quickjs; sourceTree = "<group>"; }; + 925546A82D3175C500E0AAF9 /* downloader */ = { + isa = PBXGroup; + children = ( + 925546AA2D3175DB00E0AAF9 /* downloader.h */, + 71CCF22F1277045100339E12 /* downloader.c */, + 925546AE2D3175DC00E0AAF9 /* downloader_cache.c */, + 925546AC2D3175DC00E0AAF9 /* downloader_curl.c */, + 925546B02D3175DC00E0AAF9 /* downloader_emscripten.c */, + 925546A92D3175DB00E0AAF9 /* downloader_hmux.c */, + 925546AB2D3175DC00E0AAF9 /* downloader_nghttp2.c */, + 925546AD2D3175DC00E0AAF9 /* downloader_ngtcp2.c */, + 925546AF2D3175DC00E0AAF9 /* downloader_ssl.c */, + ); + name = downloader; + sourceTree = "<group>"; + }; 929A929516EF8A7500B7C64F /* modules */ = { isa = PBXGroup; children = ( @@ -1951,6 +2000,7 @@ 92EB6DB220E4E19600A97A49 /* dec_odf.c */, 92EB6D7420E4E19200A97A49 /* dec_openhevc.c */, 92EB6DC020E4E19700A97A49 /* dec_opensvc.c */, + 92EB6DC020E6E19700A97A49 /* dec_scte35.c */, 92EB6DB920E4E19600A97A49 /* dec_theora.c */, 922B2773244625B0001D4F72 /* dec_ttml.c */, 92EB6D7E20E4E19200A97A49 /* dec_ttxt.c */, @@ -1971,6 +2021,7 @@ 92EB6D9F20E4E19500A97A49 /* dmx_ogg.c */, 92EB6D8C20E4E19400A97A49 /* dmx_saf.c */, 92EB6DA720E4E19600A97A49 /* dmx_vobsub.c */, + 3FDEA5BA2D402C3D006F0673 /* enc_cc.c */, 92EB6DBF20E4E19700A97A49 /* enc_jpg.c */, 92EB6D8F20E4E19400A97A49 /* enc_png.c */, 92EB6D8D20E4E19400A97A49 /* encrypt_cenc_isma.c */, @@ -1992,6 +2043,8 @@ 92EB6DC220E4E19700A97A49 /* in_http.c */, 8959AC7E211C9C3C004D2C87 /* in_pipe.c */, 92359C89250F657A00525A42 /* in_route.c */, + 92E8E7ED2BF4F93E003D785B /* in_route_repair.c */, + 92E8E7EE2BF4F93E003D785B /* in_route.h */, 92EB6D8620E4E19300A97A49 /* in_rtp_rtsp.c */, 92EB6DAE20E4E19600A97A49 /* in_rtp_sdp.c */, 92EB6DC520E4E19700A97A49 /* in_rtp_signaling.c */, @@ -2024,6 +2077,7 @@ 8959AC82211C9C3C004D2C87 /* out_sock.c */, 92EB6D7320E4E19200A97A49 /* out_video.c */, 92EB6DA920E4E19600A97A49 /* reframe_ac3.c */, + 925607772E1EC7030022BA42 /* reframe_ac4.c */, 92EB6D8920E4E19300A97A49 /* reframe_adts.c */, 92EB6D8420E4E19300A97A49 /* reframe_amr.c */, 8959AC78211C9B29004D2C87 /* reframe_av1.c */, @@ -2044,11 +2098,13 @@ 92EB6DB620E4E19600A97A49 /* resample_audio.c */, 92AB30C027ADA66F00A5CCCB /* restamp.c */, 92EB6D9320E4E19400A97A49 /* rewind.c */, + 925607762E1EC7030022BA42 /* rewrite_ac4.c */, 92EB6D8120E4E19300A97A49 /* rewrite_adts.c */, 928EB67425271B5800E47747 /* rewrite_mhas.c */, 92EB6D9E20E4E19500A97A49 /* rewrite_mp4v.c */, 92EB6DB420E4E19600A97A49 /* rewrite_nalu.c */, 8959AC83211C9C3C004D2C87 /* rewrite_obu.c */, + 9279EA3A2DA6F06D00E13C5E /* sei_load.c */, 92EB6DB520E4E19600A97A49 /* tileagg.c */, 92C236E72576DC4E00CD59A9 /* tilesplit.c */, 928607102395C0DE00FDA1ED /* tssplit.c */, @@ -2178,18 +2234,19 @@ 92359C85250F651900525A42 /* evg.h in Headers */, 71CCF3F512770A8C00339E12 /* setup.h in Headers */, 71CCF3F612770A8C00339E12 /* svg_types.h in Headers */, - 92359C86250F651900525A42 /* Remotery.h in Headers */, + 92359C86250F651900525A42 /* rmt_ws.h in Headers */, 92EB6D6D20E4E17F00A97A49 /* filter_session.h in Headers */, 71CCF3F712770A8C00339E12 /* sync_layer.h in Headers */, 71CCF3FA12770A8C00339E12 /* thread.h in Headers */, + 92E8E7F02BF4F93E003D785B /* in_route.h in Headers */, 71CCF3FB12770A8C00339E12 /* token.h in Headers */, + 925546B22D3175DC00E0AAF9 /* downloader.h in Headers */, 71CCF3FC12770A8C00339E12 /* tools.h in Headers */, 71CCF3FE12770A8C00339E12 /* utf.h in Headers */, 71CCF3FF12770A8C00339E12 /* xml.h in Headers */, 970A26CD13434D780007362C /* m3u8.h in Headers */, 970A26CE13434D780007362C /* mpd.h in Headers */, 922EB5821AAE13C500DF46D0 /* html5_mse.h in Headers */, - 970A26D213434D960007362C /* cache.h in Headers */, 92359C84250F651900525A42 /* route.h in Headers */, 922EB5871AAE13C500DF46D0 /* webvtt.h in Headers */, 9747B6EF14864C6400280D95 /* ait.h in Headers */, @@ -2249,7 +2306,7 @@ LastUpgradeCheck = 1020; TargetAttributes = { 71A19B1912796B5600ACFF58 = { - DevelopmentTeam = Q46G522WGV; + DevelopmentTeam = Y3MQ824CRS; }; }; }; @@ -2331,15 +2388,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 925546B52D3175DC00E0AAF9 /* downloader_ngtcp2.c in Sources */, + 89162EF52C00DA10004EA21C /* dec_scte35.c in Sources */, 92F930861A5ADC1A0072A85C /* os_divers.c in Sources */, 92EB6DCB20E4E19700A97A49 /* out_video.c in Sources */, 92E9DFA52215CE9A00628420 /* g_crypt.c in Sources */, 925FF2A71ABC55030085B04C /* os_file.c in Sources */, 92E8C7901A0CD58300E0436D /* validator.c in Sources */, 92EB6E2020E4E1A300A97A49 /* crypt_tools.c in Sources */, + 925546B62D3175DC00E0AAF9 /* downloader_cache.c in Sources */, 92EB6E0320E4E19700A97A49 /* dec_xvid.c in Sources */, + 925546B82D3175DC00E0AAF9 /* downloader_emscripten.c in Sources */, 92EB6DFB20E4E19700A97A49 /* write_nhnt.c in Sources */, - 8959AC8F211C9C77004D2C87 /* Remotery.c in Sources */, + 8959AC8F211C9C77004D2C87 /* rmt_ws.c in Sources */, 92EB6E0D20E4E19700A97A49 /* tileagg.c in Sources */, 92261A89239E326900C14199 /* out_http.c in Sources */, 92EB6E1D20E4E19700A97A49 /* in_rtp_signaling.c in Sources */, @@ -2388,6 +2449,7 @@ 71CCF24F1277045100339E12 /* predictive_mffield.c in Sources */, 92EB6DEB20E4E19700A97A49 /* rewind.c in Sources */, 71CCF2511277045100339E12 /* quantize.c in Sources */, + 3F5F3F212C7EAD0C00C58257 /* id3.c in Sources */, 71CCF2531277045100339E12 /* script_dec.c in Sources */, 71CCF2541277045100339E12 /* script_enc.c in Sources */, 928E0BD828E2FC5F00F2BA27 /* unframer.c in Sources */, @@ -2469,9 +2531,11 @@ 92EB6DEA20E4E19700A97A49 /* reframe_rawpcm.c in Sources */, 71CCF2901277045100339E12 /* texturing.c in Sources */, 71CCF2921277045100339E12 /* texturing_gl.c in Sources */, + 925546B32D3175DC00E0AAF9 /* downloader_nghttp2.c in Sources */, 71CCF2931277045100339E12 /* visual_manager.c in Sources */, 92EB6E0720E4E19700A97A49 /* isoffin_read.c in Sources */, 71CCF2951277045100339E12 /* visual_manager_2d.c in Sources */, + 925607792E1EC7030022BA42 /* reframe_ac4.c in Sources */, 92EB6DD120E4E19700A97A49 /* write_qcp.c in Sources */, 71CCF2971277045100339E12 /* visual_manager_2d_draw.c in Sources */, 71CCF2981277045100339E12 /* visual_manager_3d.c in Sources */, @@ -2518,7 +2582,7 @@ 71CCF2B41277045100339E12 /* drm_sample.c in Sources */, 92EB6DCD20E4E19700A97A49 /* reframe_nalu.c in Sources */, 9210BA1D234A909400ED2DFF /* evg.c in Sources */, - 923EB53423D1B87E00E1FFA1 /* libbf.c in Sources */, + 923EB53423D1B87E00E1FFA1 /* dtoa.c in Sources */, 71CCF2B51277045100339E12 /* isom_intern.c in Sources */, 71CCF2B61277045100339E12 /* isom_read.c in Sources */, 92EB6DCF20E4E19700A97A49 /* dmx_mpegps.c in Sources */, @@ -2593,6 +2657,7 @@ 92EB6DD620E4E19700A97A49 /* dec_ttxt.c in Sources */, 92B2A4A7229C0F47002A65A7 /* out_rtsp.c in Sources */, 71CCF2F11277045100339E12 /* odf_parse.c in Sources */, + 925546B72D3175DC00E0AAF9 /* downloader_ssl.c in Sources */, 92EB6DCA20E4E19700A97A49 /* dec_bifs.c in Sources */, 71CCF2F21277045100339E12 /* qos.c in Sources */, 71CCF2F31277045100339E12 /* slc.c in Sources */, @@ -2640,6 +2705,7 @@ 92EB6E3820E4E58E00A97A49 /* object_manager.c in Sources */, 71CCF3111277045100339E12 /* vrml_script.c in Sources */, 92EB6E3320E4E58E00A97A49 /* scene_ns.c in Sources */, + 925546B12D3175DC00E0AAF9 /* downloader_hmux.c in Sources */, 92734635235084A50081AF74 /* storage.c in Sources */, 92EB6E3420E4E58E00A97A49 /* mpeg4_mediasensor.c in Sources */, 71CCF3131277045100339E12 /* vrml_tools.c in Sources */, @@ -2647,6 +2713,8 @@ 9296BF2422CFBB3900DC9742 /* reframe_flac.c in Sources */, 71CCF3141277045100339E12 /* x3d_nodes.c in Sources */, 71CCF3161277045100339E12 /* xml_ns.c in Sources */, + 925607782E1EC7030022BA42 /* rewrite_ac4.c in Sources */, + 92277E1D2BE910E1005E0955 /* md5.c in Sources */, 92EB6E1A20E4E19700A97A49 /* in_http.c in Sources */, 92EB6E1720E4E19700A97A49 /* enc_jpg.c in Sources */, 92B2A4AF229D6646002A65A7 /* hevcsplit.c in Sources */, @@ -2661,10 +2729,12 @@ 92EB6DC720E4E19700A97A49 /* mux_ts.c in Sources */, 92EB6DD220E4E19700A97A49 /* ff_common.c in Sources */, 92EB6E1820E4E19700A97A49 /* dec_opensvc.c in Sources */, + 92EB6E1A20E4E19700A97A49 /* in_http.c in Sources */, 92EB6E3620E4E58E00A97A49 /* media_object.c in Sources */, 92EB6E0420E4E19700A97A49 /* ff_dmx.c in Sources */, 92EB6DCE20E4E19700A97A49 /* filelist.c in Sources */, 92EB6DE320E4E19700A97A49 /* compose.c in Sources */, + 3FDEA5BB2D402C3D006F0673 /* enc_cc.c in Sources */, 9290E74E25263F4F00BC0EE3 /* reframe_mhas.c in Sources */, 92E9DFAF2215CEA700628420 /* raster_rgb.c in Sources */, 929DE74924183FB40056AFA7 /* gzio.c in Sources */, @@ -2675,6 +2745,7 @@ 71CCF32C1277045100339E12 /* bitstream.c in Sources */, 921E055D29CDDF1800ED745E /* dec_uncv.c in Sources */, 928607152398F4C800FDA1ED /* ff_avf.c in Sources */, + 92E8E7EF2BF4F93E003D785B /* in_route_repair.c in Sources */, 71CCF32D1277045100339E12 /* color.c in Sources */, 92EB6E0120E4E19700A97A49 /* reframe_ac3.c in Sources */, 92EB6D6C20E4E17F00A97A49 /* filter_register.c in Sources */, @@ -2695,9 +2766,11 @@ 9227B8592799F8E700BF3D1B /* bs_split.c in Sources */, 71CCF33A1277045100339E12 /* os_thread.c in Sources */, 71CCF33B1277045100339E12 /* path2d.c in Sources */, + 925546B42D3175DC00E0AAF9 /* downloader_curl.c in Sources */, 71CCF33C1277045100339E12 /* path2d_stroker.c in Sources */, 92D6C5A72343BF6A00262217 /* scene_js.c in Sources */, 92AB30C127ADA66F00A5CCCB /* restamp.c in Sources */, + 9279EA3B2DA6F06D00E13C5E /* sei_load.c in Sources */, 71CCF33F1277045100339E12 /* token.c in Sources */, 71CCF3401277045100339E12 /* uni_bidi.c in Sources */, 71CCF3411277045100339E12 /* url.c in Sources */, @@ -2707,7 +2780,6 @@ 92E9DFB12215CEA700628420 /* ftgrays.c in Sources */, 71CCF3451277045100339E12 /* zutil.c in Sources */, 7141FF301292CB9200FCB27D /* alloc.c in Sources */, - 7147426012B918C900DB5A59 /* cache.c in Sources */, 92359C8A250F657B00525A42 /* in_route.c in Sources */, 970A26C913434D2C0007362C /* m3u8.c in Sources */, 92EB6E1620E4E19700A97A49 /* decrypt_cenc_isma.c in Sources */, @@ -2715,6 +2787,7 @@ 9288A4B425ADDCED006F7355 /* reframe_truehd.c in Sources */, 92EB6DDE20E4E19700A97A49 /* in_rtp_rtsp.c in Sources */, 92EB6DFF20E4E19700A97A49 /* dmx_vobsub.c in Sources */, + 3F04FCBA2C9CB139002BC889 /* xml_bin_custom.c in Sources */, 970A26D813434DAC0007362C /* unicode.c in Sources */, 97FF0660138CE4C400D6C707 /* os_config_init.c in Sources */, 9747B6F414864C7800280D95 /* ait.c in Sources */, @@ -2754,7 +2827,7 @@ COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = NO; DEPLOYMENT_LOCATION = YES; - DEVELOPMENT_TEAM = Q46G522WGV; + DEVELOPMENT_TEAM = Y3MQ824CRS; ENABLE_BITCODE = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_OBJC_CALL_CXX_CDTORS = YES; @@ -2785,7 +2858,7 @@ OTHER_CFLAGS = ""; OTHER_CPLUSPLUSFLAGS = ""; OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "com.telecom-paristech.gpac"; + PRODUCT_BUNDLE_IDENTIFIER = "com.telecom-paris.gpac"; PRODUCT_NAME = gpac4ios; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILEsdk=iphoneos*" = ""; @@ -2803,7 +2876,7 @@ "CODE_SIGN_IDENTITYsdk=iphoneos*" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEPLOYMENT_LOCATION = YES; - DEVELOPMENT_TEAM = Q46G522WGV; + DEVELOPMENT_TEAM = Y3MQ824CRS; ENABLE_BITCODE = NO; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; @@ -2832,7 +2905,7 @@ OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "com.telecom-paristech.gpac"; + PRODUCT_BUNDLE_IDENTIFIER = "com.telecom-paris.gpac"; PRODUCT_NAME = gpac4ios; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILEsdk=iphoneos*" = "";
View file
gpac-2.4.0.tar.gz/change_version.sh -> gpac-26.02.0.tar.gz/change_version.sh
Changed
@@ -1,54 +1,63 @@ #!/bin/sh +SED=sed +platform=`uname -s` +if $platform = "Darwin" ; then +SED=gsed +fi version="`grep '#define GPAC_VERSION ' \"./include/gpac/version.h\" | cut -d '"' -f 2`" -version_major=`grep '#define GPAC_VERSION_MAJOR ' ./include/gpac/version.h | sed 's/^0-9*//g'` -version_minor=`grep '#define GPAC_VERSION_MINOR ' ./include/gpac/version.h | sed 's/^0-9*//g'` -version_micro=`grep '#define GPAC_VERSION_MICRO ' ./include/gpac/version.h | sed 's/^0-9*//g'` +version_major=`grep '#define GPAC_VERSION_MAJOR ' ./include/gpac/version.h | $SED 's/^0-9*//g'` +version_minor=`grep '#define GPAC_VERSION_MINOR ' ./include/gpac/version.h | $SED 's/^0-9*//g'` +version_micro=`grep '#define GPAC_VERSION_MICRO ' ./include/gpac/version.h | $SED 's/^0-9*//g'` libgac_version="$version_major.$version_minor.$version_micro" echo "Version is $version - libgpac $libgac_version" #patch README.md source="README.md" -sed -e "s/README for GPAC version.*/README for GPAC version $version/;" $source > ftmp +$SED -e "s/Current version: .*/Current version: $version/;" $source | $SED -e "s/Latest Release: .*/Latest Release: $version/;" > ftmp rm $source mv ftmp $source #patch applications/gpac/ios-Info.plist source="applications/gpac/ios-Info.plist" -sed -e "/CFBundleShortVersionString/{n;s/.*/ <string>$version<\/string>/;}" $source | sed -e "/CFBundleVersion/{n;s/.*/ <string>$libgac_version<\/string>/;}" > ftmp +$SED -e "/CFBundleShortVersionString/{n;s/.*/ <string>$version<\/string>/;}" $source | $SED -e "/CFBundleVersion/{n;s/.*/ <string>$libgac_version<\/string>/;}" > ftmp rm $source mv ftmp $source # patch file gpac.pc source="gpac.pc" -sed -e "s/Version:.*/Version:$version/;" $source > ftmp +$SED -e "s/Version:.*/Version:$version/;" $source > ftmp rm $source mv ftmp $source # patch file gpac.spec source="gpac.spec" -sed -e "s/Version:.*/Version: $version/;" $source | sed -e "s/Release:.*/Release: $version/;" | sed -e "s/Source0:.*/Source0: gpac-$version.tar.gz/;" > ftmp +$SED -e "s/Version:.*/Version: $version/;" $source | $SED -e "s/Release:.*/Release: $version/;" | $SED -e "s/Source0:.*/Source0: gpac-$version.tar.gz/;" > ftmp rm $source mv ftmp $source # patch file debian/changelog source="debian/changelog" -sed -e "s/gpac (.*/gpac ($version) stable; urgency=low/;" $source | sed -e "s/Check https.*/Check https:\/\/github.com\/gpac\/gpac\/releases\/tag\/v$version/;" > ftmp +$SED -e "s/gpac (.*/gpac ($version) stable; urgency=low/;" $source | $SED -e "s/Check https.*/Check https:\/\/github.com\/gpac\/gpac\/releases\/tag\/v$version/;" > ftmp rm $source mv ftmp $source # patch packagers/win32_64/nsis/gpac_installer.nsi source="packagers/win32_64/nsis/gpac_installer.nsi" -sed -e "s/\!define GPAC_VERSION.*/\!define GPAC_VERSION $version/;" $source > ftmp +$SED -e "s/\!define GPAC_VERSION.*/\!define GPAC_VERSION $version/;" $source > ftmp rm $source mv ftmp $source #patch packagers/osx/GPAC.app/Contents/Info.plist source="packagers/osx/GPAC.app/Contents/Info.plist" -sed -e "/CFBundleShortVersionString/{n;s/.*/ <string>$version<\/string>/;}" $source | sed -e "/CFBundleVersion/{n;s/.*/ <string>$libgac_version<\/string>/;}" > ftmp +$SED -e "/CFBundleShortVersionString/{n;s/.*/ <string>$version<\/string>/;}" $source | $SED -e "/CFBundleVersion/{n;s/.*/ <string>$libgac_version<\/string>/;}" > ftmp rm $source mv ftmp $source +#patch mangpages +for source in share/doc/man/*.1 ; do + $SED -i "s/^build: .*$/build: $version/" $source +done
View file
gpac-2.4.0.tar.gz/check_revision.sh -> gpac-26.02.0.tar.gz/check_revision.sh
Changed
@@ -4,34 +4,35 @@ #check for .git - if so use nb commits till last tag for rev + commit id if -d ".git" ; then -TAG=$(git describe --tags --abbrev=0 2>>gtmp) -VERSION=$(echo `git describe --tags --long 2>>gtmp || echo "UNKNOWN"` | sed "s/^$TAG-//") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>>gtmp || echo "UNKNOWN") -revision="$VERSION-$BRANCH" + TAG=$(git describe --tags --abbrev=0 --match "v*" 2>>gtmp) + VERSION=$(echo `git describe --tags --long --match "v*" 2>>gtmp || echo "UNKNOWN"` | sed "s/^$TAG-//") + BRANCH=$(git rev-parse --abbrev-ref HEAD 2>>gtmp || echo "UNKNOWN") -rm gtmp + #sanitize branch name for filenames + DHBRANCH=$(echo "$BRANCH" | sed 's/^-+.0-9a-zA-Z~/-/g' ) -echo "#define GPAC_GIT_REVISION \"$revision\"" > htmp + revision="$VERSION-$DHBRANCH" -if ! diff htmp ./include/gpac/revision.h > /dev/null ; then -echo "revision has changed" -rm ./include/gpac/revision.h -mv htmp ./include/gpac/revision.h -else -rm htmp -fi - -else -#otherwise, check id -DEV is in the version name. If not consider this a release + rm gtmp -if ! -e ".include/gpac/revision.h" ; then + echo "#define GPAC_GIT_REVISION \"$revision\"" > htmp -if test "$version" != *"-DEV"* ; then -echo "#define GPAC_GIT_REVISION \"release\"" > ./include/gpac/revision.h + if ! diff htmp ./include/gpac/revision.h > /dev/null ; then + if ! -f ./include/gpac/revision.h ; then + echo "Revision has changed" + fi + rm ./include/gpac/revision.h + mv htmp ./include/gpac/revision.h + else + rm htmp + fi else -echo "#define GPAC_GIT_REVISION \"UNKNOWN_REV\"" > ./include/gpac/revision.h -fi - -fi - + #otherwise, check id -DEV is in the version name. If not consider this a release + if ! -e ".include/gpac/revision.h" ; then + if test "$version" != *"-DEV"* ; then + echo "#define GPAC_GIT_REVISION \"release\"" > ./include/gpac/revision.h + else + echo "#define GPAC_GIT_REVISION \"UNKNOWN_REV\"" > ./include/gpac/revision.h + fi + fi fi
View file
gpac-2.4.0.tar.gz/configure -> gpac-26.02.0.tar.gz/configure
Changed
@@ -1,7 +1,7 @@ #!/bin/sh # # GPAC configure script -# (c) 2003-2024 Telecom ParisTech +# (c) 2003-2026 Telecom Paris # Authors: Jean Le Feuvre, Romain Bouqueau, Aurelien David # #set -v @@ -74,6 +74,8 @@ install="${INSTALL:=install}" instflags="${INSTFLAGS:=-p}" cpu=`uname -m` +sched="free" +check_stack_size="32768" debuginfo="no" sdl_path="" sdl_local="no" @@ -91,6 +93,7 @@ libgpac_extralibs="" static_build="no" static_bin="no" +unit_tests="no" static_modules="no" lm_lib="" has_directx="no" @@ -118,10 +121,12 @@ is_64="no" is_em="no" is_emcfg="no" -has_remotery="yes" +has_rmtws="yes" em_type="js" em_size="0" +em_opt="2" fatal_assert="no" +no_stack_prot="no" #flags not set by generic lib config has_x11="no" @@ -238,12 +243,11 @@ push_feature "route" "ROUTE (ATSC3) support" push_feature "qjs" "QuickJS support" push_feature "qjs-libc" "QuickJS libc module support" -push_feature "qjs-stack" "!stack depth checking in QuickJS (WILL CRASH in multithreaded modes)" push_feature "tinygl" "!TinyGL support" push_feature "doc" "Embedded documentation" push_feature "evg" "EVG (GPAC vector graphics tools)" -push_feature "avdevice" "avdevice support in FFMPEG" -push_feature "avfilter" "avfilter support in FFMPEG" +push_feature "avdevice" "avdevice support in FFmpeg" +push_feature "avfilter" "avfilter support in FFmpeg" push_feature "net-cap" "network grab and replay support" push_feature "compositor" "*Compositor" @@ -262,7 +266,7 @@ push_feature "nhmlr" "*NHML demuxer" push_feature "nhntr" "*NHNT demuxer" push_feature "cecrypt" "*CENC Encryptor" -push_feature "flist" "*Playlist concetenator" +push_feature "flist" "*Playlist concatenator" push_feature "hevcmerge" "*HEVC tile merger" push_feature "hevcsplit" "*HEVC tile spliter" push_feature "fin" "*File input" @@ -276,6 +280,7 @@ push_feature "fout" "*File output" push_feature "pout" "*Pipe output" push_feature "rfac3" "*AC3 parser" +push_feature "rfac4" "*AC4 parser" push_feature "rfadts" "*ADTS parser" push_feature "rfamr" "*AMR parser" push_feature "rfav1" "*AV1/VP9 parser" @@ -298,6 +303,7 @@ push_feature "rewind" "*Reverse playback" push_feature "ufaac" "*ADTS and LATM rewriter" push_feature "ufmhas" "*MPEG-H Audio rewriter" +push_feature "ufac4" "*AC4 Audio rewriter" push_feature "ufm4v" "*MPEG 1/2/4 and VC1 video rewriter" push_feature "ufnalu" "*NALU-based video rewriter" push_feature "ufobu" "*AV1/VP9 video rewriter" @@ -308,6 +314,7 @@ push_feature "vcrop" "*Video croping" push_feature "vflip" "*Video flip" push_feature "writegen" "*Generic rewriter" +push_feature "webcodec" "*WebCodecs support" push_feature "nhmlw" "*NHML writer" push_feature "nhntw" "*NHNT writer" push_feature "writeqcp" "*QCP writer" @@ -339,8 +346,8 @@ } #zlib MUST be first -all_packages="zlib opensvc openhevc platinum freetype ssl jpeg openjpeg png mad a52 xvid faad ffmpeg freenect vorbis theora nghttp2" -all_packages="$all_packages oss dvb4linux alsa pulseaudio jack directfb hid lzma tinygl vtb ogg sdl caption mpeghdec libcaca" +all_packages="zlib opensvc openhevc platinum freetype ssl jpeg openjpeg png mad a52 xvid faad ffmpeg freenect vorbis theora nghttp2 ngtcp2 nghttp3" +all_packages="$all_packages oss dvb4linux alsa pulseaudio jack directfb hid lzma tinygl vtb ogg sdl caption mpeghdec libcaca curl" disabled_packages="" #init all has_FOO=no and set FOO_cflags= @@ -377,6 +384,8 @@ --extra-ldflags=ELDFLAGS add ELDFLAGS to LDFLAGS $LDFLAGS --extra-libs=ELIBS add ELIBS $ELIBS --cpu=CPU force cpu to CPU $cpu + --check-stack-size=SIZE maximum stack size $check_stack_size bytes + --no-stack-protector disables the default stack canaries --enable-debug produce debug version --enable-gprof enable profiling --enable-gcov enable coverage @@ -390,27 +399,29 @@ --disable-opt disable GCC optimizations --static-build link statically against libgpac but still allow dependencies to shared libraries (enable --static-modules) --static-bin enable static linking of MP4Box and gpac only (will enable --static-build), disable all libraries not linkable statically. + --unittests enable unit tests default=no --sdl-cfg=SDL_PATH specify path to sdl-config for local install $sdl_path --enable-sdl-static use static SDL linking default=no --X11-path=X11_PATH specify path for X11 includes and libraries $X11_PATH --dxsdk-path=DX_PATH specify DirectX SDK for MinGW $dxsdk_path - --extra-ff-ldflags=EF add EF flags to FFMPEG LDFLAGS $ffmpeg_extra_ldflags + --extra-ff-ldflags=EF add EF flags to FFmpeg LDFLAGS $ffmpeg_extra_ldflags --emscripten configure for emscripten build (same as running emconfigure configure) - this will disable some features by default (network, avdevice), use --enable-FOO to re-enable --em-type=EXT output type, can be js, html or wasm $em_type --em-size=V total memory in bytes to use for emscripten - if 0, dynamic memory growth will be used $em_size + --em-opt=OPT emscripten optimization level, can be 0, 1, 2, 3, s, or z (default: 2) $em_opt GPAC core configuration: --static-modules embed modules in libgpac rather than using dynamic library modules --enable-fixed-point enable fixed-point math --disable-ipv6 disable IPV6 support --enable-mem-track enable tracking of all memory allocated by gpac - --disable-remotery disable Remotery support + --disable-rmtws disable the websocket monitoring server --disable-x11 disable X11 --disable-x11-shm disable X11 shared memory support --disable-x11-xv disable X11 Xvideo support --enable-depth enables depth handling in the compositor (experimental for stereoscopic rendering) --disable-all disables all features in libgpac (see below) - --isomedia-only disables all features except "parsers import export isom-dump isoff isoff-write isoff-hint isoff-frag isoff-hds ttxt ttml vtt mpd dash hevc" + --isomedia-only disables all features except "parsers import export isom-dump isoff isoff-write isoff-hint isoff-frag isoff-hds ttxt ttml vtt txtin" Extra libraries configuration: @@ -625,6 +636,10 @@ ;; --cpu=*) cpu=`echo $opt | cut -d '=' -f 2` ;; + --check-stack-size=*) check_stack_size=`echo $opt | cut -d '=' -f 2` + ;; + --no-stack-protector) no_stack_prot="yes" + ;; --enable-debug) debuginfo="yes"; no_gcc_opt="yes" ;; --disable-opt) no_gcc_opt="yes" @@ -648,6 +663,8 @@ ;; --enable-sanitizer) enable_sanitizer="yes" ;; + --unittests) unit_tests="yes" + ;; --static-modules) static_modules="yes" ;; --sdl-cfg=*) sdl_path=`echo $opt | cut -d '=' -f 2`; sdl_local="yes" @@ -660,11 +677,15 @@ ;; --dxsdk-path=*) dxsdk_path=`echo $opt | cut -d '=' -f 2` ;; - --disable-pulseaudio) has_pulseaudio="no" + --disable-pulseaudio) append disabled_packages "pulseaudio" + ;; + --disable-jack) append disabled_packages "jack" ;; - --disable-jack) has_jack="no" + --disable-alsa) append disabled_packages "alsa" ;; - --disable-alsa) has_alsa="no" + --disable-oss) append disabled_packages "oss" + ;; + --disable-dvb4linux) append disabled_packages "dvb4linux" ;; --enable-gprof) gprof_build="yes"; ;; @@ -680,7 +701,7 @@ ;; --enable-mem-track) use_memory_tracking="yes" ;; - --disable-remotery) has_remotery="no" + --disable-rmtws) has_rmtws="no" ;; --disable-x11) append disabled_packages "x11" ;; @@ -694,8 +715,8 @@ #disable all packages except first one "zlib" disabled_packages=`echo $opt | cut -c6-` append disabled_packages "x11 sdl directx " - #enable all parsers, dash and isobmff related - enable_features "parsers import export isom-dump isoff isoff-write isoff-hint isoff-frag isoff-hds ttxt ttml vtt mpd dash hevc" + #enable all parsers and isobmff related + enable_features "parsers import export isom-dump isoff isoff-write isoff-hint isoff-frag isoff-hds ttxt ttml vtt txtin" ;; --use-*) v=`echo $opt | cut -c7-` set_package "$v" @@ -715,6 +736,12 @@ ;; --em-size=*) em_size=`echo $opt | cut -d '=' -f 2` ;; + --em-opt=*) em_opt=`echo $opt | cut -d '=' -f 2` + if test "$em_opt" != "0" -a "$em_opt" != "1" -a "$em_opt" != "2" -a "$em_opt" != "3" -a "$em_opt" != "s" -a "$em_opt" != "z"; then + echo "! Unrecognized emscripten optimization level $em_opt, using 2 !" + em_opt="2" + fi + ;; *) echo "! Unrecognized option $opt, ignoring !" ;; esac @@ -746,7 +773,7 @@ echo "Emscripten missing PKG_CONFIG_PATH, disabling pkg-config" pkg_config="no" fi - has_remotery="no" + has_rmtws="no" #to refine append disabled_packages "freenect platinum vtb oss alsa jack pulseaudio directfb x11 dvb4linux directx" @@ -790,11 +817,16 @@ fi fi ;; + arm64) + cpu="arm64" + sched=lock + ;; armv4l|arm) cpu="armv4l" ;; aarch64) cpu="aarch64" + sched=lock if -z "`echo $CFLAGS | grep -- -m32`" ; then is_64="yes" PIC_CFLAGS="-fPIC -DPIC" @@ -942,7 +974,7 @@ FreeBSD) make="gmake" - LDFLAGS="$LDFLAGS -export-dynamic" + LDFLAGS="$LDFLAGS -rdynamic" PTHREAD_CFLAGS=-pthread PTHREAD_LDFLAGS=-pthread freebsd="yes" @@ -968,8 +1000,8 @@ alt_macosx_dir="/opt/local" fi if test "$alt_macosx_dir" != "" ; then - CFLAGS_DIR="-I$alt_macosx_dir $CFLAGS_DIR" - LDFLAGS="-L$alt_macosx_dir $LDFLAGS" + CFLAGS_DIR="$CFLAGS_DIR -I$alt_macosx_dir -I$alt_macosx_dir/include" + LDFLAGS="$LDFLAGS -L$alt_macosx_dir -L$alt_macosx_dir/lib" fi fi @@ -1127,12 +1159,23 @@ if docc -fno-strict-aliasing ; then CFLAGS="$CFLAGS -fno-strict-aliasing" fi + +if test "$no_stack_prot" = "yes"; then + if docc -fno-stack-protector ; then + CFLAGS="$CFLAGS -fno-stack-protector" + fi +else + if docc -fstack-protector-strong ; then + CFLAGS="$CFLAGS -fstack-protector-strong" + fi +fi CXXFLAGS="$CFLAGS" if docc -lz -Wno-pointer-sign ; then CFLAGS="$CFLAGS -Wno-pointer-sign" fi + #GCC opt if test "$no_gcc_opt" = "no"; then CFLAGS="-O3 $CFLAGS" @@ -1359,20 +1402,20 @@ #test pkgconfig if test "$forced" != "local" -a "$custom" != "yes" ; then - if test "$cross_prefix" = "" -a "$is_port" != "yes" -a "$pkg_config" != "no"; then + if test "$cross_prefix" = "" -a "$is_port" != "yes" -a "$pkg_config" != "no" -a "$pkg_cfg_name" != ""; then if $pkg_config --exists $pkg_cfg_name ; then pck_cflags=`$pkg_config --cflags $pkg_cfg_name` pck_ldflags=`$pkg_config --libs $pkg_cfg_name` if test "$pck_cxx" = "yes" ; then echo "$package_src" > $TMPCXX - if docxx $pck_cflags $pck_ldflags ; then + if docxx $pck_cflags $pck_ldflags $LDFLAGS ; then has_pck="$sysname (pkgconfig)" return fi else echo "$package_src" > $TMPC - if docc $pck_cflags $pck_ldflags ; then + if docc $pck_cflags $pck_ldflags $LDFLAGS ; then has_pck="$sysname (pkgconfig)" return fi @@ -1474,7 +1517,7 @@ pck_ldflags="" pname="$1" - case "$disabled_packages" in + case "$disabled_packages " in *"$pname "*) shift eval "has_$pname=\"no\"" return @@ -1484,7 +1527,11 @@ config_package_test "$1" "$2" "$3" "$4" "$5" "$6" if test "$has_pck" != "no" ; then eval "cflags_$pname=\"$pck_cflags\"" - eval "ldflags_$pname=\"$pck_ldflags\"" + if test "$pname" = "ngtcp2" ; then + eval "ldflags_$pname=\"$pck_ldflags -lngtcp2_crypto_quictls\"" + else + eval "ldflags_$pname=\"$pck_ldflags\"" + fi fi eval "has_$pname=\"$has_pck\"" @@ -1532,6 +1579,8 @@ #we cannot use openssl vanilla since it is usually built with DSO and requires dlopen append disabled_packages "ssl" + # these need ssl + append disabled_packages "nghttp3 ngtcp2" #we cannot use ipv6 due to getaddrinfo not being present append disabled_packages "ipv6" @@ -1591,6 +1640,7 @@ #include <openssl/x509.h> #include <openssl/err.h> #include <openssl/rand.h> +#include <stddef.h> int main( void ) { SSL_CTX_set_options(NULL, SSL_OP_ALL); return 0; }' @@ -1617,6 +1667,7 @@ fi config_package png "libpng" "" "-lpng -lz" "png" '#include <png.h> +#include <stddef.h> int main( void ) { png_struct *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); return 0; }' @@ -1633,6 +1684,7 @@ config_package xvid "xvid" "" "-lxvidcore $PTHREAD_LDFLAGS" "" '#include <xvid.h> +#include <stddef.h> int main( void ) { void *codec; xvid_decore(codec, XVID_DEC_DESTROY, NULL, NULL); return 0; }' config_package faad "faad2" "" "-lfaad -lm" "" '#include <faad.h> @@ -1686,6 +1738,7 @@ config_package freenect "libfreenect" "" "-lfreenect" "freenect" '#include <libfreenect/libfreenect.h> +#include <stddef.h> int main( void ) { freenect_context *f_ctx; freenect_init(&f_ctx, NULL); return 0; }' @@ -1700,6 +1753,16 @@ #include <nghttp2/nghttp2.h> int main( void ) { nghttp2_session *ng_sess; nghttp2_session_del(ng_sess); return 0; }' +config_package ngtcp2 "libngtcp2" "" "-lngtcp2 -lngtcp2_crypto_quictls" "" '#include <stdio.h> +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto_quictls.h> +int main( void ) { ngtcp2_conn *ng_sess=NULL; ngtcp2_conn_del(ng_sess); return 0; }' + +config_package nghttp3 "libnghttp3" "" "-lnghttp3" "" '#include <stdio.h> +#include <nghttp3/nghttp3.h> +int main( void ) { nghttp3_conn *ng_sess=NULL; nghttp3_conn_del(ng_sess); return 0; }' + + config_package caption "libcaption" "" "-lcaption" "" '#include <caption/cea708.h> int main( void ) { caption_frame_t ccframe; caption_frame_init(&ccframe); return 0; }' @@ -1715,6 +1778,11 @@ int main( void ) { HANDLE_MPEGH_DECODER_CONTEXT codec = mpeghdecoder_init(2); return 0; }' +config_package curl "curl" "" "-lcurl" "" '#include <curl/curl.h> +int main( void ) { CURLM *curl_multi = curl_multi_init(); int fn_opt = CURLOPT_PREREQFUNCTION; const struct curl_easyoption *opt = curl_easy_option_next(NULL); return 0; }' + + + #look for VideoToolBox support if test "$darwin" = "yes" ; then ldflags_vtb="-framework CoreFoundation -framework CoreVideo -framework CoreMedia -framework VideoToolbox" @@ -1738,14 +1806,14 @@ else -check_has_lib oss "" '#include <sys/ioctl.h> +config_package oss "" "" "" "" '#include <sys/ioctl.h> #include <unistd.h> #include <fcntl.h> #include <sys/soundcard.h> int main( void ) { return 0; }' if test "$has_oss" = "no" ; then -check_has_lib oss "" '#include <sys/ioctl.h> +config_package oss "" "" "" "" '#include <sys/ioctl.h> #include <unistd.h> #include <fcntl.h> #include <soundcard.h> @@ -1780,19 +1848,17 @@ }' - -check_has_lib dvb4linux "" '#include <linux/dvb/dmx.h> +config_package dvb4linux "" "" "" "" '#include <linux/dvb/dmx.h> #include <linux/dvb/frontend.h> int main( void ) {return 0;}' -check_has_lib alsa "" '#include <alsa/asoundlib.h> +config_package alsa "libasound" "" "" "" '#include <alsa/asoundlib.h> int main( void ) {return 0;}' - -check_has_lib pulseaudio "" '#include <pulse/pulseaudio.h> +config_package pulseaudio "libpulse" "" "" "" '#include <pulse/pulseaudio.h> int main( void ) {return 0;}' -check_has_lib jack "" '#include <jack/jack.h> +config_package jack "libjack" "" "" "" '#include <jack/jack.h> int main( void ) {return 0;}' @@ -1823,7 +1889,7 @@ check_has_lib x11_glx "-I$X11_PATH/include -L$X11_PATH/lib" '#include <X11/Xlib.h> #include <GL/gl.h> #include <GL/glx.h> -int main( void ) { return 0; }' +int main( void ) { glXSwapBuffers(NULL, 0); return 0; }' fi #end x11 test @@ -1951,7 +2017,7 @@ #look for SDL support - we don't use check_has_lib for the time being -case "$disabled_packages" in +case "$disabled_packages " in *"sdl "*) has_sdl=no ;; *) sdl_too_old=no @@ -2064,6 +2130,10 @@ fi +if test "$check_stack_size" != "no" ; then + CFLAGS="$CFLAGS -Wframe-larger-than=$check_stack_size" +fi + if test "$debuginfo" = "no"; then CFLAGS="$CFLAGS -DNDEBUG" fi @@ -2167,6 +2237,9 @@ fi echo "" echo "** GPAC $version rev$revision Core Configuration **" +echo "Unit Tests: $unit_tests ('make unit_tests')" +echo "#define GF_STATIC static" >> $TMPH +echo "#define GF_NOT_EXPORTED" >> $TMPH if test "$static_bin" = "yes" ; then echo "Static binaries enabled" echo "#define GPAC_STATIC_BIN" >> $TMPH @@ -2177,6 +2250,7 @@ else echo "Static Modules: $static_modules" fi +echo "max stack size: $check_stack_size" echo "debug version: $debuginfo" echo "GProf enabled: $gprof_build" echo "Memory tracking enabled: $use_memory_tracking" @@ -2407,10 +2481,6 @@ echo "#define GF_SR_USE_DEPTH" >> $TMPH fi -if test "$disable_qjs_stack" = "no" ; then - echo "#define GPAC_QJS_STACK_CHECK" >> $TMPH -fi - if test "$disable_doc" = "yes" ; then echo "Doc disabled" echo "#define GPAC_DISABLE_DOC" >> $TMPH @@ -2467,6 +2537,8 @@ fi echo "OpenSSL support: $has_ssl" echo "nghttp2: $has_nghttp2" +echo "ngtcp2: $has_ngtcp2" +echo "nghttp3: $has_nghttp3" if test "$win32" != "yes" ; then echo "OSS Audio: $has_oss" @@ -2508,6 +2580,7 @@ echo "libcaption: $has_caption" echo "MpeghDecoder: $has_mpeghdec" echo "libcaca: $has_libcaca" +echo "libcurl: $has_curl" ffopts="" if test "$has_ffmpeg" != "no" ; then @@ -2517,9 +2590,9 @@ fi if -z $ffopts ; then -echo "FFMPEG: $has_ffmpeg" +echo "FFmpeg: $has_ffmpeg" else -echo "FFMPEG: $has_ffmpeg - \"$ffopts\" supported" +echo "FFmpeg: $has_ffmpeg - \"$ffopts\" supported" fi @@ -2544,7 +2617,7 @@ #we add no deprecate by default on osx (due to opengl ...) if test "$darwin" = "yes" ; then - CFLAGS="$CFLAGS -Wno-deprecated -Wno-deprecated-declarations" + CFLAGS="$CFLAGS -Wno-deprecated -Wno-deprecated-declarations -Wno-missing-braces" else CFLAGS="$CFLAGS -Wno-deprecated -Wno-deprecated-declarations -Wno-int-in-bool-context" fi @@ -2576,7 +2649,7 @@ else if test "$enable_sanitizer" = "yes" ; then - CFLAGS="$CFLAGS -fsanitize=address,undefined -fno-sanitize-recover -g -fno-omit-frame-pointer -DASAN_ENABLED" + CFLAGS="$CFLAGS -fsanitize=address,undefined -fno-sanitize-recover -g -fno-omit-frame-pointer -fno-stack-protector -U_FORTIFY_SOURCE -DASAN_ENABLED" LDFLAGS="$LDFLAGS -fsanitize=address,undefined -ldl" fi @@ -2631,6 +2704,9 @@ echo "man_dir=$man_dir" >> config.mak +echo "UNIT_TESTS=$unit_tests" >> config.mak + + echo "STATIC_MODULES=$static_modules" >> config.mak #for cross-compilation @@ -2724,6 +2800,7 @@ elif test "$is_em" = "yes" ; then echo "CONFIG_EMSCRIPTEN=yes" >> config.mak echo "EM_TYPE=$em_type" >> config.mak + echo "EM_OPT=$em_opt" >> config.mak echo "#define GPAC_CONFIG_EMSCRIPTEN" >> $TMPH else echo "#define GPAC_CONFIG_GENERIC" >> $TMPH @@ -2739,6 +2816,9 @@ fi +echo "#define GPAC_SCHED_DEFAULT \"$sched\"" >> $TMPH + + echo "INSTFLAGS=$INSTFLAGS" >> config.mak echo "CONFIG_JS=$enable_qjs" >> config.mak @@ -2787,8 +2867,8 @@ fi echo "CONFIG_FT=$has_freetype" >> config.mak -if test "$has_remotery" = "no" ; then - echo "#define GPAC_DISABLE_REMOTERY" >> $TMPH +if test "$has_rmtws" = "no" ; then + echo "#define GPAC_DISABLE_RMTWS" >> $TMPH fi @@ -2883,11 +2963,14 @@ echo "#define GPAC_HAS_HTTP2" >> $TMPH fi +if test "$has_ngtcp2" != "no" ; then + echo "#define GPAC_HAS_NGTCP2" >> $TMPH +fi + if test "$has_caption" != "no" ; then echo "#define GPAC_HAS_LIBCAPTION" >> $TMPH fi - if test "$has_libcaca" != "no" ; then echo "#define GPAC_HAS_LIBCACA" >> $TMPH echo "CONFIG_CACA=yes" >> config.mak @@ -2899,6 +2982,10 @@ echo "#define GPAC_HAS_MPEGHDECODER" >> $TMPH fi +if test "$has_curl" != "no" ; then + echo "#define GPAC_HAS_CURL" >> $TMPH +fi + #dump all cflags for pcklib in $all_packages ; do eval "has_pck=\$has_$pcklib" @@ -3078,10 +3165,9 @@ echo "PKG_CONFIG=$pkg_config" >> config.mak -#build tree in object directory if source path is different from current one -if test "$source_path_used" = "yes" ; then - echo "Creating compilation tree image" +build_object_files_directory_tree() +{ SRC_DIRS="src src/utils src/isomedia src/ietf src/odf src/bifs src/scenegraph src/filter_core src/filters src/crypto src/media_tools src/scene_manager src/compositor src/laser src/evg src/quickjs src/jsmods" APP_DIRS="applications/gpac applications/mp4box" @@ -3089,39 +3175,40 @@ for dir in $SRC_DIRS ; do mkdir -p "$dir" done - ln -sf "$source_path/Makefile" Makefile - ln -sf "$source_path/static.mak" static.mak - ln -sf "$source_path/src/Makefile" src/Makefile + ln -sf "$1/Makefile" Makefile + ln -sf "$1/static.mak" static.mak + ln -sf "$1/src/Makefile" src/Makefile mkdir -p applications - ln -sf "$source_path/applications/Makefile" applications/Makefile + ln -sf "$1/applications/Makefile" applications/Makefile mkdir -p applications/testapps for dir in $APP_DIRS ; do mkdir -p "$dir" - ln -sf "$source_path/$dir/Makefile" "$dir/Makefile" + ln -sf "$1/$dir/Makefile" "$dir/Makefile" done - - cur_dir="`pwd`" - cd "$cur_dir" - mkdir -p modules - ln -sf "$source_path/modules/Makefile" modules/Makefile + ln -sf "$1/modules/Makefile" modules/Makefile + ln -sf "$1/modules/common.mak" modules/common.mak - for moddir in "$source_path/modules/"* ; do + for moddir in "$1/modules/"* ; do if -d "$moddir" -a -f "$moddir/Makefile" ; then - dir="${moddir#"$source_path/"}" + dir="${moddir#"$1/"}" mkdir -p "$dir" ln -sf "$moddir/Makefile" "$dir/Makefile" fi done if test "$has_directx" = "yes"; then - ln -sf "$source_path/modules/dx_hw/hand.cur" modules/dx_hw/hand.cur - ln -sf "$source_path/modules/dx_hw/collide.cur" modules/dx_hw/collide.cur + ln -sf "$1/modules/dx_hw/hand.cur" modules/dx_hw/hand.cur + ln -sf "$1/modules/dx_hw/collide.cur" modules/dx_hw/collide.cur fi +} - cd "$cur_dir" +#build tree in object directory if source path is different from current one +if test "$source_path_used" = "yes" ; then + echo "Creating compilation tree image" + build_object_files_directory_tree "$source_path" fi echo "SRC_PATH=$source_path" >> config.mak @@ -3129,6 +3216,13 @@ echo "LOCAL_INC_PATH=$local_inc" >> config.mak +if test "$unit_tests" = "yes" ; then + mkdir -p unittests/build && cd unittests/build + build_object_files_directory_tree "$source_path" + cd - +fi + + echo "#endif" >> $TMPH @@ -3195,7 +3289,7 @@ echo "Name: gpac" echo "Description: GPAC Multimedia Framework" echo "URL: https://gpac.io" - echo "Version:$version" + echo "Version: $version" echo "Cflags: -I\${prefix}/include" echo "Libs: -L\${libdir} -lgpac" }
View file
gpac-2.4.0.tar.gz/debian/changelog -> gpac-26.02.0.tar.gz/debian/changelog
Changed
@@ -1,6 +1,5 @@ -gpac (2.4) stable; urgency=low +gpac (26.02) stable; urgency=low * Initial release - see https://github.com/gpac/gpac/releases for more details -- Jean Le Feuvre <jeanlf@gpac.io> Tue, 22 Feb 2022 15:00:00 +0200 -
View file
gpac-2.4.0.tar.gz/debian/gpac.install -> gpac-26.02.0.tar.gz/debian/gpac.install
Changed
@@ -1,12 +1,4 @@ debian/tmp/usr/bin -debian/tmp/usr/share/man/man1 +debian/tmp/usr/share debian/tmp/usr/lib/*.so* debian/tmp/usr/lib/gpac -debian/tmp/usr/share/gpac/res -debian/tmp/usr/share/gpac/gui -debian/tmp/usr/share/gpac/shaders -debian/tmp/usr/share/gpac/scripts -debian/tmp/usr/share/icons -debian/tmp/usr/share/applications - -
View file
gpac-2.4.0.tar.gz/debian/libgpac-dev.install -> gpac-26.02.0.tar.gz/debian/libgpac-dev.install
Changed
@@ -1,2 +1,3 @@ debian/tmp/usr/include debian/tmp/usr/lib/libgpac_static.a +debian/tmp/usr/lib/pkgconfig
View file
gpac-26.02.0.tar.gz/do_release.py
Added
@@ -0,0 +1,216 @@ +#!/usr/bin/env python3 + +import argparse +import re +import subprocess +import sys + +SED="sed" + +if sys.platform == "darwin": + SED="gsed" + + +def cmd(command, log=False, check=True, printcmd=False): + + stdout = subprocess.PIPE if log else None + + if printcmd: + print("Running: ", command) + + res = subprocess.run(command, text=True, shell=True, check=check, stdout=stdout, stderr=subprocess.STDOUT) + + return res.returncode, res.stdout + + +VER_PATTERN = re.compile(r"^(\d+)\.(\d+)(\.(\d+))?$") +ABI_PATTERN = re.compile(r"define GPAC_VERSION_MAJOR\s+(\d+).*?define GPAC_VERSION_MINOR\s+(\d+).*?define GPAC_VERSION_MICRO\s+(\d+)", re.M | re.S) + +GITLOG_PATTERN = re.compile(r"(fix|fixes|fixed|close|closes) #\d+", re.IGNORECASE) + + +VERSIONH_PATH = "include/gpac/version.h" +LIBGPACPY_PATH = "share/python/libgpac/libgpac.py" + +def parse_version(ver): + + m = VER_PATTERN.match(ver) + + if not m: return False, 0, 0, 0 + + return True, m.group(1), m.group(2), 0 if m.group(4) is None else m.group(4) + + +def get_current_abi(versionh): + + m = ABI_PATTERN.search(versionh) + + if not m: return False, 0, 0, 0 + + return True, m.group(1), m.group(2), m.group(3) + + +def filter_gitlog(gitlog): + + lines = + for line in gitlog.split("\n"): + parts = line.split(" ") + #commit = parts0 + msg = " ".join(parts1:) + + if not GITLOG_PATTERN.search(msg): + lines.append(line) + + return "\n".join(lines) + + + +def main(args): + + step = 0 + + rc, log = cmd("git status --porcelain --untracked-files=no", log=True) + if rc or log!="": + print(f"You have uncommited changes:\n{log}\nStash or commit before running the script.") + exit(1) + + rc, oldtag = cmd("git describe --tags --abbrev=0 --match 'v*'", log=True) + if rc or oldtag=="": + print(f"Couldn't get current tag.") + exit(1) + + args.oldtag = oldtag.strip() + args.nextver = f"{args.v1}.{int(args.v2)+1:0{len(args.v2)}}-DEV" + + print(f"Current tag: {args.oldtag}") + print(f"Next tag: {args.tag}") + print(f"Next DEV version: {args.nextver}") + + + with open(VERSIONH_PATH, "r") as f: + versionh = f.read() + + valid, args.abi1, args.abi2, args.abi3 = parse_version(args.abi) if args.abi else get_current_abi(versionh) + if not valid: + parser.print_usage() + exit(1) + + + ################################################# + step+=1; print(f"\n{step}. Patching {VERSIONH_PATH}...") + + versionh = re.sub(r'(#define GPAC_VERSION)(\s+)".*?"', rf'\1\2"{args.version}"', versionh) + + versionh = re.sub(r'(#define GPAC_VERSION_MAJOR) (\s*)\d+', rf'\1\2 {args.abi1}' , versionh) + versionh = re.sub(r'(#define GPAC_VERSION_MINOR) (\s*)\d+', rf'\1\2 {args.abi2}' , versionh) + versionh = re.sub(r'(#define GPAC_VERSION_MICRO) (\s*)\d+', rf'\1\2 {args.abi3}' , versionh) + + with open(VERSIONH_PATH, "w") as f: + f.write(versionh) + + + ################################################# + step+=1; print(f"\n{step}. Patching {LIBGPACPY_PATH}...") + + cmd(f"{SED} -Ei 's/GF_ABI_MAJOR:space:*=:space:*:digit:+/GF_ABI_MAJOR={args.abi1}/' {LIBGPACPY_PATH}") + cmd(f"{SED} -Ei 's/GF_ABI_MINOR:space:*=:space:*:digit:+/GF_ABI_MINOR={args.abi2}/' {LIBGPACPY_PATH}") + + + ################################################# + step+=1; print(f"\n{step}. Running change_version.sh...") + + rc, log = cmd("bash change_version.sh", log=True) + if rc: + print("Error running change_version.sh: \n", log) + exit(rc) + + + print("Final diff:\n") + cmd(f"git --no-pager diff --color") + + + ################################################# + if not args.no_make: + step+=1; print(f"\n{step}. Building gpac...") + + cmd("make distclean") + cmd("./configure") + cmd("make -j4") + + + ################################################# + if not args.no_commit: + step+=1; print(f"\n{step}. Commiting and tagging...") + + cmd(f"git commit -am 'GPAC Release {args.version}'", printcmd=True) + cmd(f"git tag -a {args.tag} -m 'GPAC Release {args.version}'", printcmd=True) + + + ################################################# + step+=1; print(f"\n{step}. Generating git log...") + rc, gitlog = cmd(f"git log --no-merges --oneline {args.oldtag}..{args.tag}", log=True, printcmd=True) + if not rc and gitlog!="": + # filter gitlog lines + gitlog = filter_gitlog(gitlog) + with open(f"gitlog_{args.oldtag}..{args.tag}.txt", "w") as f: + f.write(gitlog) + + ################################################# + if not args.no_nextver: + step+=1; print(f"\n{step}. Switching HEAD to new DEV version...") + + with open(VERSIONH_PATH, "r") as f: + versionh = f.read() + + versionh = re.sub(r'(#define GPAC_VERSION)(\s+)".*?"', rf'\1\2"{args.nextver}"', versionh) + + with open(VERSIONH_PATH, "w") as f: + f.write(versionh) + + rc, log = cmd("bash change_version.sh", log=True) + if rc: + print("Error running change_version.sh: \n", log) + exit(rc) + + #cmd(f"git commit -am 'moving to next dev version noCI'") + + + print(f"\nYou can now push with") + print(f"\tgit push --atomic origin master {args.tag}") + #print(f"\tOR git push origin master && git push --tags") + + + print(f"\nRelease {args.version} ready to push and publish.") + + if not args.no_nextver: + print(f"\nAfter the release is pushed you can commit and push the next DEV version:") + print(f"\tgit commit -am 'moving to next dev version noCI' && git push") + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + + parser.add_argument('version', help="main version number in the form M.m.u") + parser.add_argument('--abi', help="ABI version in the form M.m.u. If absent, will be read from version.h") + + parser.add_argument('--no-make', action="store_true", help="skips build after changes") + parser.add_argument('--no-commit', action="store_true", help="skips commit and tag") + parser.add_argument('--no-nextver', action="store_true", help="skips switching to next DEV version") + + args = parser.parse_args() + + + valid, args.v1, args.v2, args.v3 = parse_version(args.version) + if not valid: + parser.print_usage() + exit(1) + + + args.tag = f"v{args.v1}.{args.v2}.{args.v3}" + + main(args) + + exit(0)
View file
gpac-2.4.0.tar.gz/generate_installer.bat -> gpac-26.02.0.tar.gz/generate_installer.bat
Changed
@@ -49,9 +49,22 @@ if not "%diff%"=="" echo Local and remote revisions not in sync, consider pushing or pulling changes REM execute git and check if the result if found within revision.h -for /f "delims=" %%a in ('git describe --tags --long') do @set VERSION=%%a -for /f "delims=" %%a in ('git describe --tags --abbrev^=0') do @set TAG=%%a- +for /f "delims=" %%a in ('git describe --tags --long --match "v*" ') do @set VERSION=%%a +for /f "delims=" %%a in ('git describe --tags --abbrev^=0 --match "v*" ') do @set TAG=%%a- for /f "delims=" %%a in ('git rev-parse --abbrev-ref HEAD') do @set BRANCH=%%a + +REM sanitize BRANCH for filename +set "BRANCH=%BRANCH:/=-%" +set "BRANCH=%BRANCH:"=-%" +set "BRANCH=%BRANCH:<=-%" +set "BRANCH=%BRANCH:>=-%" +set "BRANCH=%BRANCH:|=-%" +set "BRANCH=%BRANCH:@=-%" +set "BRANCH=%BRANCH:_=-%" +set "BRANCH=%BRANCH:(=-%" +set "BRANCH=%BRANCH:)=-%" + + REM remove anotated tag from VERSION setlocal enabledelayedexpansion call set VERSION=%%VERSION:!TAG!=%%
View file
gpac-2.4.0.tar.gz/gpac.spec -> gpac-26.02.0.tar.gz/gpac.spec
Changed
@@ -1,11 +1,11 @@ # $Id: gpac.spec,v 1.5 2008-12-02 18:04:42 jeanlf Exp $ Summary: Framework for production, encoding, delivery and interactive playback of multimedia content Name: gpac -Version: 2.4 -Release: 2.4 +Version: 26.02 +Release: 26.02 License: LGPL Group: Applications/Multimedia -Source0: gpac-2.4.tar.gz +Source0: gpac-26.02.tar.gz URL: https://gpac.io/ BuildRoot: %{_tmppath}/%{name}-root Requires: SDL
View file
gpac-2.4.0.tar.gz/include/gpac/avparse.h -> gpac-26.02.0.tar.gz/include/gpac/avparse.h
Changed
@@ -154,7 +154,7 @@ void gf_m4v_parser_del_no_bs(GF_M4VParser *m4v); /*! parses the decoder specific info (if found) \param m4v the mpeg video parser -\param dsi the decoder spcific info structure to fill +\param dsi the decoder specific info structure to fill \return GF_OK if found, GF_EOS if not enough data, error otherwise */ GF_Err gf_m4v_parse_config(GF_M4VParser *m4v, GF_M4VDecSpecInfo *dsi); @@ -184,14 +184,14 @@ /*! decodes DSI/VOSHeader for MPEG4 \param rawdsi encoded MPEG-4 decoder config \param rawdsi_size size of encoded MPEG-4 decoder config -\param dsi the decoder spcific info structure to fill +\param dsi the decoder specific info structure to fill \return error if any */ GF_Err gf_m4v_get_config(u8 *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); /*! decodes DSI/VOSHeader for MPEG12 \param rawdsi encoded MPEG-1/2 decoder config \param rawdsi_size size of encoded MPEG-1/2 decoder config -\param dsi the decoder spcific info structure to fill +\param dsi the decoder specific info structure to fill \return error if any */ GF_Err gf_mpegv12_get_config(u8 *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); @@ -358,12 +358,12 @@ u32 gf_vorbis_check_frame(GF_VorbisParser *vp, u8 *data, u32 data_len); /*! parses opus header packets - initializes the config on success, leave it to NULL otherwise -\param cfg pointer to a opus config to fill +\param ocfg pointer to a opus config to fill \param data opus header buffer to parse \param data_len size of opus header buffer \return 1 if success, 0 if error */ -Bool gf_opus_parse_header(GF_OpusConfig *cfg, u8 *data, u32 data_len); +Bool gf_opus_parse_header(GF_OpusConfig *ocfg, u8 *data, u32 data_len); /*! checks if an opus frame is valid \param cfg pointer to a opus config to use @@ -384,8 +384,8 @@ u64 gf_mpegh_escaped_value(GF_BitStream *bs, u32 nBits1, u32 nBits2, u32 nBits3); /*! parse profile and level from a MHAS payload -\param ptr the MHAS payhload -\param size size of the MHAS payhload +\param ptr the MHAS payload +\param size size of the MHAS payload \param chan_layout set to the channel layout if found, 0 otherwise - optional, may be NULL \return the MHAS profile found, or -1 of not found */ @@ -641,7 +641,7 @@ //! \cond old name typedef struct __ac3_config GF_AC3Header; -//! \endcond +//! \endcond /*! parses an AC-3 header from a buffer \param buffer buffer to parse @@ -684,6 +684,12 @@ */ u32 gf_eac3_get_chan_loc_count(u32 chan_loc); +/*! gets the channel layout mask from EAC3 config +\param ac3 the decoded AC3 configuration +\return channel mask +*/ +u64 gf_ac3_get_channel_layout(GF_AC3Config *ac3); + /*! gets the total number of channels in an AC3 frame, including surround but not lfe \param acmod acmod of the associated frame header \return number of channels @@ -700,6 +706,26 @@ */ u32 gf_ac3_get_bitrate(u32 brcode); +/*! parses an AC-4 header from a buffer +\param buffer buffer to parse +\param buffer_size size of buffer to parse +\param pos set to start offset (in bytes) of the AC4 header parsed +\param out_hdr will be filled by parser +\param full_parse if GF_TRUE, complete parsing of the header will be done +\param start_from_toc if GF_TRUE, parsing starts from the toc +\return GF_TRUE if success +*/ +Bool gf_ac4_parser(u8 *buffer, u32 buffer_size, u32 *pos, GF_AC4Config *out_hdr, Bool full_parse, Bool start_from_toc); + +/*! parses an AC-4 header from a bitstream +\param bs bitstream to parse +\param hdr will be filled by parser +\param full_parse if GF_TRUE, complete parsing of the header and check for next frame/blocks presence will be done +\param start_from_toc if GF_TRUE, parsing starts from the toc +\return GF_TRUE if success +*/ +Bool gf_ac4_parser_bs(GF_BitStream *bs, GF_AC4Config *hdr, Bool full_parse, Bool start_from_toc); + /*! gets basic information from an AVC Sequence Parameter Set \param sps SPS NAL buffer \param sps_size size of buffer @@ -849,7 +875,9 @@ OBU_METADATA_TYPE_HDR_MDCV = 2, OBU_METADATA_TYPE_SCALABILITY = 3, OBU_METADATA_TYPE_ITUT_T35 = 4, - OBU_METADATA_TYPE_TIMECODE = 5 + OBU_METADATA_TYPE_TIMECODE = 5, + OBU_METADATA_TYPE_PRIVATE_TIMECODE_SIMPLE = 7, + OBU_METADATA_TYPE_PRIVATE_TIMECODE_SIMPLE_BIS = 32, //same as 7 } ObuMetadataType; /*! gets the name of a given OBU type @@ -858,6 +886,48 @@ */ const char *gf_av1_get_obu_name(ObuType obu_type); +/*!\brief IAMF OBU types */ +typedef enum { + OBU_IAMF_CODEC_CONFIG = 0, + OBU_IAMF_AUDIO_ELEMENT = 1, + OBU_IAMF_MIX_PRESENTATION = 2, + OBU_IAMF_PARAMETER_BLOCK = 3, + OBU_IAMF_TEMPORAL_DELIMITER = 4, + OBU_IAMF_AUDIO_FRAME = 5, + OBU_IAMF_AUDIO_FRAME_ID0 = 6, + OBU_IAMF_AUDIO_FRAME_ID1 = 7, + OBU_IAMF_AUDIO_FRAME_ID2 = 8, + OBU_IAMF_AUDIO_FRAME_ID3 = 9, + OBU_IAMF_AUDIO_FRAME_ID4 = 10, + OBU_IAMF_AUDIO_FRAME_ID5 = 11, + OBU_IAMF_AUDIO_FRAME_ID6 = 12, + OBU_IAMF_AUDIO_FRAME_ID7 = 13, + OBU_IAMF_AUDIO_FRAME_ID8 = 14, + OBU_IAMF_AUDIO_FRAME_ID9 = 15, + OBU_IAMF_AUDIO_FRAME_ID10 = 16, + OBU_IAMF_AUDIO_FRAME_ID11 = 17, + OBU_IAMF_AUDIO_FRAME_ID12 = 18, + OBU_IAMF_AUDIO_FRAME_ID13 = 19, + OBU_IAMF_AUDIO_FRAME_ID14 = 20, + OBU_IAMF_AUDIO_FRAME_ID15 = 21, + OBU_IAMF_AUDIO_FRAME_ID16 = 22, + OBU_IAMF_AUDIO_FRAME_ID17 = 23, + OBU_IAMF_RESERVED_24 = 24, + OBU_IAMF_RESERVED_25 = 25, + OBU_IAMF_RESERVED_26 = 26, + OBU_IAMF_RESERVED_27 = 27, + OBU_IAMF_RESERVED_28 = 28, + OBU_IAMF_RESERVED_29 = 29, + OBU_IAMF_RESERVED_30 = 30, + OBU_IAMF_SEQUENCE_HEADER = 31 +} IamfObuType; + +/*! gets the name of a given IAMF OBU type +\param obu_type the OBU type +\return the OBU name +*/ +const char *gf_iamf_get_obu_name(IamfObuType obu_type); + /*! @} */ #ifdef __cplusplus @@ -866,4 +936,3 @@ #endif /*_GF_PARSERS_AV_H_*/ -
View file
gpac-2.4.0.tar.gz/include/gpac/configuration.h -> gpac-26.02.0.tar.gz/include/gpac/configuration.h
Changed
@@ -46,6 +46,8 @@ /*Configuration for visual studio, 32/64 bits */ #if defined(_WIN32) && !defined(_WIN32_WCE) +#define WIN32_LEAN_AND_MEAN + #ifndef GPAC_MP4BOX_MINI #define GPAC_HAS_SSL @@ -69,8 +71,12 @@ #define GPAC_HAS_LIBCAPTION #define GPAC_HAS_MPEGHDECODER #define GPAC_HAS_LIBCACA +#define GPAC_HAS_CURL +#define GPAC_HAS_FD -/*IPv6 enabled - for win32, this is evaluated at compile time, !! do not uncomment !!*/ +#ifdef _WIN64 +//#define GPAC_HAS_NGTCP2 +#endif #define GPAC_MEMORY_TRACKING @@ -162,6 +168,8 @@ #define GPAC_HAS_POLL #define GPAC_HAS_LIBCACA +#define GPAC_HAS_NGTCP2 + #define GPAC_MEMORY_TRACKING #define GPAC_HAS_LIBCAPTION @@ -224,6 +232,9 @@ #endif +#define GPAC_SCHED_DEFAULT "free" + + /*disables player */ //#define GPAC_DISABLE_COMPOSITOR
View file
gpac-2.4.0.tar.gz/include/gpac/constants.h -> gpac-26.02.0.tar.gz/include/gpac/constants.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / exported constants @@ -50,7 +50,7 @@ Supported media stream types for media objects. */ -enum +typedef enum { /*!Unknown stream type*/ GF_STREAM_UNKNOWN = 0, @@ -104,7 +104,7 @@ GF_STREAM_FILE = 0xE1, //other stream types may be declared using their handler 4CC as defined in ISOBMFF -}; +} GF_StreamType; /*! Gets the stream type name based on stream type \param streamType stream type GF_STREAM_XXX as defined in constants.h @@ -150,6 +150,8 @@ GF_PIXEL_ALPHAGREY = GF_4CC('G','R','A','L'), /*!16 bit greyscale, first grey, then alpha*/ GF_PIXEL_GREYALPHA = GF_4CC('A','L','G','R'), + /*!8 bit RGB */ + GF_PIXEL_RGB_332 = GF_4CC('R','3','3','2'), /*!12 bit RGB on 16 bits (4096 colors)*/ GF_PIXEL_RGB_444 = GF_4CC('R','4','4','4'), /*!15 bit RGB*/ @@ -183,7 +185,7 @@ /*!RGB24 + depth plane (7 lower bits) + shape mask. Component ordering in bytes is R-G-B-(S+D).*/ GF_PIXEL_RGBDS = GF_4CC('3', 'C', 'D', 'S'), - /*internal format for OpenGL using pachek RGB 24 bit plus planar depth plane at the end of the image*/ + /*internal format for OpenGL using packed RGB 24 bit plus planar depth plane at the end of the image*/ GF_PIXEL_RGB_DEPTH = GF_4CC('R', 'G', 'B', 'd'), /*generic pixel format uncv from ISO/IEC 23001-17*/ @@ -328,6 +330,13 @@ */ u32 gf_pixel_get_nb_comp(GF_PixelFormat pixfmt); +/*! Gets the downsampling factor for this format +\param pixfmt pixel format code +\param downsample_w set to horizontal downsampling, 1 if none +\param downsample_h set to vertical downsampling, 1 if none +*/ +void gf_pixel_get_downsampling(GF_PixelFormat pixfmt, u32 *downsample_w, u32 *downsample_h); + /*! Checks if pixel format is transparent \param pixfmt pixel format code \return GF_TRUE if alpha channel is present, GF_FALSE otherwise @@ -479,6 +488,8 @@ GF_CODECID_AC3 = GF_4CC('a','c','-','3'), /*! codecid for enhanced AC-3 audio streams*/ GF_CODECID_EAC3 = GF_4CC('e','c','-','3'), + /*! codecid for AC-4 audio streams*/ + GF_CODECID_AC4 = GF_4CC('a','c','-','4'), /*! codecid for Dolby TrueHS audio streams*/ GF_CODECID_TRUEHD = GF_4CC('m','l','p','a'), /*! codecid for DRA audio streams*/ @@ -528,10 +539,14 @@ GF_CODECID_DVB_SUBS = GF_4CC( 'd', 'v', 'b', 's' ), GF_CODECID_DVB_TELETEXT = GF_4CC( 'd', 'v', 'b', 't' ), + + /*! codecid for SCTE35 streams (MPEG2-TS Sections payloads as per ANSI/SCTE 67 2017 (13.1.1.3)*/ + GF_CODECID_SCTE35 = GF_4CC( 's', 'c', '3', '5' ), + /*! \brief OGG DecoderConfig - The DecoderConfig for theora, vorbis and speek contains all intitialization ogg packets for the codec + The DecoderConfig for theora, vorbis and speek contains all initialization ogg packets for the codec and is formatted as follows:\n \code while (dsi_size) { @@ -597,6 +612,12 @@ GF_CODECID_VP9 = GF_4CC('V','P','0','9'), GF_CODECID_VP10 = GF_4CC('V','P','1','0'), + /*AVS2/3*/ + GF_CODECID_AVS2_VIDEO = GF_4CC('A','V','V','2'), + GF_CODECID_AVS2_AUDIO = GF_4CC('A','V','A','2'), + GF_CODECID_AVS3_VIDEO = GF_4CC('A','V','V','3'), + GF_CODECID_AVS3_AUDIO = GF_4CC('A','V','A','3'), + /*MPEG-H audio*/ GF_CODECID_MPHA = GF_4CC('m','p','h','a'), /*MPEG-H mux audio*/ @@ -612,6 +633,9 @@ GF_CODECID_TMCD = GF_4CC('t','m','c','d'), + /*Event Message Track (contains boxes)*/ + GF_CODECID_EVTE = GF_4CC('e','v','t','e'), + /*! codecid for FFV1*/ GF_CODECID_FFV1 = GF_4CC('f','f','v','1'), @@ -630,9 +654,13 @@ GF_CODECID_MSPEG4_V3 = GF_4CC('D','I','V','3'), GF_CODECID_ALAC = GF_4CC('A','L','A','C'), + GF_CODECID_DNXHD = GF_4CC('D','N','x','H'), //fake codec IDs for RTP - GF_CODECID_FAKE_MP2T = GF_4CC('M','P','2','T') + GF_CODECID_FAKE_MP2T = GF_4CC('M','P','2','T'), + + /*! codecid for IAMF*/ + GF_CODECID_IAMF = GF_4CC('i','a','m','f') } GF_CodecID; /*! Gets a textual description for the given codecID @@ -988,12 +1016,30 @@ */ u32 gf_audio_fmt_get_num_channels_from_layout(u64 chan_layout); -/*! get dloby chanmap value from cicp layout +/*! get dolby chanmap value from cicp layout \param cicp_layout channel CICP layout \return dolby chanmap */ u16 gf_audio_fmt_get_dolby_chanmap(u32 cicp_layout); +/*! get dolby chanmap value from channel layout +\param channel_layout channel layout mask +\return dolby chanmap +*/ +u16 gf_audio_fmt_get_dolby_chanmap_from_layout(u64 channel_layout); + +/*! get dolby AudioChannelConfiguration value from ac4 presentation_channel_mask_v1 +\param mask presentation channel mask v1 +\return dolby AudioChannelConfiguration value +*/ +u32 gf_audio_get_dolby_channel_config_value_from_mask(u32 mask); + +/*! get dolby channel count for HLS from ac4 presentation_channel_mask_v1 +\param mask presentation channel mask v1 +\return dolby channel count +*/ +u32 gf_ac4_dolby_channel_count_from_channel_mask_v1(u32 mask); + /*! enumerates CICP channel layout \param idx index of cicp layout value to query \param short_name set t o CICP name as used in GPAC - may be NULL @@ -1714,6 +1760,20 @@ GF_PROJ360_MESH }; +/*! Low latency HTTP adaptive streaming mode, set by dasher filter and used by other filter */ +enum +{ + /*! no low-latency profile*/ + GF_LLHAS_NONE = 0, + /*! LL-HLS using byte ranges */ + GF_LLHAS_BYTERANGES = 1, + /*! LL-HLS using separate parts */ + GF_LLHAS_PARTS = 2, + /*! DASH SSR mode (only sub-parts are generated */ + GF_LLHAS_SUBSEG = 3 +}; + + /*! user data used by GPAC to store SRD info*/ #define GF_ISOM_UDTA_GPAC_SRD GF_4CC('G','S','R','D')
View file
gpac-2.4.0.tar.gz/include/gpac/crypt_tools.h -> gpac-26.02.0.tar.gz/include/gpac/crypt_tools.h
Changed
@@ -138,6 +138,17 @@ GF_KEYROLL_PERIODS, } GF_KeyRollType; +/*! non-VCL crypt modes (for avc1 CTR CENC edition 1) */ +typedef enum +{ + /*! all encrypted (except nal header) */ + GF_CRYPT_NONVCL_CLEAR_NONE = 0, + /*! keep SEI and AUD in clear */ + GF_CRYPT_NONVCL_CLEAR_SEI_AUD, + /*! all clear */ + GF_CRYPT_NONVCL_CLEAR_ALL, +} GF_CryptNonVCL; + /*! Crypto information for one media stream*/ typedef struct { @@ -195,8 +206,10 @@ /*! forces a minimum clear range for subsamples (ignored otherwise) - the final offset is at least the slice header size*/ u32 crypt_byte_offset; - /* ! for avc1 ctr CENC edition 1 */ + /*! for avc1 CTR CENC edition 1 */ Bool allow_encrypted_slice_header; + /*! encrypt non-VCL NALUs - made to refine avc1 CTR CENC edition 1 support */ + GF_CryptNonVCL allow_encrypted_nonVCLs; /*! force cenc and cbc1: 0: default, 1: no block alignment of encrypted data, 2: always block align even if producing non encrypted samples*/ u32 block_align;
View file
gpac-2.4.0.tar.gz/include/gpac/dash.h -> gpac-26.02.0.tar.gz/include/gpac/dash.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2012-2023 + * Copyright (c) Telecom ParisTech 2012-2024 * All rights reserved * * This file is part of GPAC / Adaptive HTTP Streaming sub-project @@ -161,8 +161,7 @@ typedef struct __dash_client GF_DashClient; /*! Quality selection mode of initial segments*/ -typedef enum -{ +GF_OPT_ENUM (GF_DASHInitialSelectionMode, /*! selects the lowest quality when starting - if one of the representation does not have video (HLS), it may be selected*/ GF_DASH_SELECT_QUALITY_LOWEST=0, /*! selects the highest quality when starting*/ @@ -172,8 +171,8 @@ /*! selects the highest bandwidth when starting - for tiles all low priority tiles will have the lower (below max) bandwidth selected*/ GF_DASH_SELECT_BANDWIDTH_HIGHEST, /*! selects the highest bandwidth when starting - for tiles all low priority tiles will have their lowest bandwidth selected*/ - GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES -} GF_DASHInitialSelectionMode; + GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES, +); /*! create a new DASH client @@ -330,7 +329,10 @@ */ Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 group_idx); -/*! selects a group for playback. If group selection is enabled, other groups are alternate to this group (through the group attribute), they are automatically deselected +/*! selects a group for playback. If group selection is enabled, other groups are alternate to this group (through the group attribute), they are automatically deselected + + Seeking is NOT performed, it is the responsability to call \ref gf_dash_group_seek - this can be called before or after selecting + \param dash the target dash client \param group_idx the 0-based index of the target group \param select if GF_TRUE, will select this group and disable any alternate group. If GF_FALSE, only deselects the group @@ -344,13 +346,13 @@ */ s32 gf_dash_group_get_id(GF_DashClient *dash, u32 group_idx); -/*! gets cuirrent period ID +/*! gets current period ID \param dash the target dash client \return ID of the current period or NULL */ const char*gf_dash_get_period_id(GF_DashClient *dash); -/*! enables group selection through the group attribute +/*! enables group selection through the group attribute \param dash the target dash client \param enable if GF_TRUE, group selection will be done whenever selecting a new group */ @@ -369,21 +371,15 @@ */ void gf_dash_groups_set_language(GF_DashClient *dash, const char *lang_code_rfc_5646); -/*! returns the mime type of the media resources in this group -\param dash the target dash client -\param group_idx the 0-based index of the target group -\return the mime type of the segments in this group -*/ -const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 group_idx); - /*! returns the URL of the first media resource to play (init segment or first media segment depending on format). start_range and end_range are optional \param dash the target dash client \param group_idx the 0-based index of the target group \param start_range set to the byte start offset in the init segment \param end_range set to the byte end offset in the init segment +\param mime the mime type of the init segment \return URL of the init segment (can be a relative path if manifest is a local file) */ -const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 group_idx, u64 *start_range, u64 *end_range); +const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 group_idx, u64 *start_range, u64 *end_range, const char **mime); /*! returns the URL and IV associated with the first media segment if any (init segment or first media segment depending on format). This is used for full segment encryption modes of MPEG-2 TS segments. key_IV is optional @@ -402,7 +398,7 @@ */ const char *gf_dash_group_get_language(GF_DashClient *dash, u32 group_idx); -/*! returns the number of audio channelsof the group +/*! returns the number of audio channels of the group \param dash the target dash client \param group_idx the 0-based index of the target group \return the number of audio channels, or 0 if not audio or unspecified @@ -735,15 +731,14 @@ void gf_dash_split_adaptation_sets(GF_DashClient *dash); /*! low latency mode of dash client*/ -typedef enum -{ +GF_OPT_ENUM (GF_DASHLowLatencyMode, /*! disable low latency*/ GF_DASH_LL_DISABLE = 0, /*! strict respect of segment availability start time*/ GF_DASH_LL_STRICT, /*! allow fetching segments earlier than their availability start time in case of empty demux*/ GF_DASH_LL_EARLY_FETCH, -} GF_DASHLowLatencyMode; +); /*! allow early segment fetch in low latency mode \param dash the target dash client @@ -782,13 +777,33 @@ */ void gf_dash_enable_single_range_llhls(GF_DashClient *dash, Bool enable_single_range); -/*! create a new DASH client +/*! enable auto-switch mode \param dash the target dash cleint \param auto_switch_count forces representation switching (quality up if positive, down if negative) every auto_switch_count segments, set to 0 to disable \param auto_switch_loop if false (default when creating dasher), restart at lowest quality when higher quality is reached and vice-versa. If true, quality switches decreases then increase in loop */ void gf_dash_set_auto_switch(GF_DashClient *dash, s32 auto_switch_count, Bool auto_switch_loop); +/*! Cross Adaptation-set switching mdoe */ +GF_OPT_ENUM (GF_DASHCrossASMode, + /*! cross adaptation set is disabled*/ + GF_DASH_XAS_NONE = 0, + /*! cross adaptation set is enabled and only switches on the same codec*/ + GF_DASH_XAS_CODEC, + /*! cross adaptation set is enabled and can switch to any codec*/ + GF_DASH_XAS_ALL, +); + +/*! enable switching across adaptation sets + +When switching across adaptation sets is enabled and such sets are declared in the manifest, a single group will be declared for all sets in +the switching set, and switching will be handled by the client. + +\param dash the target dash cleint +\param cross_as_mode enable or disable +*/ +void gf_dash_enable_cross_as_switch(GF_DashClient *dash, GF_DASHCrossASMode cross_as_mode); + /*! returns active period start \param dash the target dash client \return period start in milliseconds @@ -861,6 +876,10 @@ Double average_duration; /*! list of segmentURLs if known, NULL otherwise. Used for onDemand profile to get segment sizes*/ const GF_List *seg_urls; + /*! URL (relative) of variant playlist*/ + const char *hls_variant_url; + /*! SSR flag, set to estimated num parts in SSR */ + u32 ssr; } GF_DASHQualityInfo; /*! gets information on a given quality @@ -919,8 +938,7 @@ /*! Tile adaptation mode This mode specifies how bitrate is allocated across tiles of the same video */ -typedef enum -{ +GF_OPT_ENUM(GF_DASHTileAdaptationMode, /*! each tile receives the same amount of bitrate (default strategy)*/ GF_DASH_ADAPT_TILE_NONE=0, /*! bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row*/ @@ -939,7 +957,7 @@ GF_DASH_ADAPT_TILE_CENTER, /*! bitrate decreased for all tiles on the center of the picture*/ GF_DASH_ADAPT_TILE_EDGES, -} GF_DASHTileAdaptationMode; +); /*! sets tile adaptation mode \param dash the target dash client @@ -1081,12 +1099,12 @@ */ void gf_dash_set_suggested_presentation_delay(GF_DashClient *dash, s32 spd); -/*! sets availabilityStartTime shift for ROUTE. By default the ROUTE tune-in is done by matching the last received segment name -to the segment template and deriving the ROUTE UTC reference from that. The function allows shifting the computed value by a given amount. +/*! sets availabilityStartTime shift for multicast (ROUTE, FLUTE). By default the multicast tune-in is done by matching the last received segment name +to the segment template and deriving the UTC reference from that. The function allows shifting the computed value by a given amount. \param dash the target dash client -\param ast_shift clock shift in milliseconds of the ROUTE receiver tune-in. Positive values shift the clock in the future, negative ones in the past +\param ast_shift clock shift in milliseconds of the multicast receiver tune-in. Positive values shift the clock in the future, negative ones in the past */ -void gf_dash_set_route_ast_shift(GF_DashClient *dash, s32 ast_shift); +void gf_dash_set_mcast_ast_shift(GF_DashClient *dash, s32 ast_shift); /*! gets the minimum wait time before calling \ref gf_dash_process again for unthreaded mode \param dash the target dash client
View file
gpac-2.4.0.tar.gz/include/gpac/download.h -> gpac-26.02.0.tar.gz/include/gpac/download.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -69,8 +69,6 @@ GF_NETIO_GET_CONTENT, /*!signal that request is sent and waiting for server reply*/ GF_NETIO_WAIT_FOR_REPLY, - /*!signal a header to user. */ - GF_NETIO_PARSE_HEADER, /*!signal request reply to user. The reply is always sent after the headers*/ GF_NETIO_PARSE_REPLY, /*!send data to the user*/ @@ -86,6 +84,8 @@ GF_NETIO_REQUEST_SESSION, /*! stream has been canceled by remote peer*/ GF_NETIO_CANCEL_STREAM, + /* only for icy*/ + GF_NETIO_ICY_META, } GF_NetIOStatus; /*!session download flags*/ @@ -111,32 +111,54 @@ GF_NETIO_SESSION_AUTO_CACHE = 1<<7, /*! use non-blocking IOs*/ GF_NETIO_SESSION_NO_BLOCK = 1<<8, + /*! session must be able to share underlying GF_Socket */ + GF_NETIO_SESSION_SHARE_SOCKET = 1<<9, + /*! disable proxy for this session */ + GF_NETIO_SESSION_NO_PROXY = 1<<10, } GF_NetIOFlags; /*!protocol I/O parameter*/ typedef struct { - /*!parameter message type*/ + /*!parameter message type + If value is GF_NETIO_GET_HEADER and callback resets the value to 0, aborts headers query + */ GF_NetIOStatus msg_type; /*error code if any. Valid for all message types.*/ GF_Err error; - /*!data received or data to send. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE (when no cache is setup) messages*/ + /*!data received or data to send. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE (when no cache is setup) messages + if error is set, payload is the server response body + */ const u8 *data; - /*!size of associated data. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE messages*/ + /*!size of associated data. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE messages + if error is set, payload is the server response body size + */ u32 size; - /*protocol header. Only valid for GF_NETIO_GET_HEADER, GF_NETIO_PARSE_HEADER and GF_NETIO_GET_METHOD*/ + /*protocol header. Only valid for GF_NETIO_GET_HEADER and GF_NETIO_GET_METHOD + if NULL for GF_NETIO_GET_HEADER, ignored + for GF_NETIO_ICY_META, set to "icy-meta" + */ const char *name; - /*protocol header value or server response. Only alid for GF_NETIO_GET_HEADER, GF_NETIO_PARSE_HEADER and GF_NETIO_PARSE_REPLY*/ - char *value; - /*message-dependend - for GF_NETIO_PARSE_REPLY, response code - for GF_NETIO_DATA_EXCHANGE - Set to 1 in to indicate end of chunk transfer - Set to 2 in GF_NETIO_DATA_EXCHANGE to indicate complete file is already received (replay of events from cache) - for all other, usage is reserved + /*protocol header value or server response. Only alid for GF_NETIO_GET_HEADER and GF_NETIO_PARSE_REPLY + if NULL for GF_NETIO_GET_HEADER, aborts headers query + for GF_NETIO_ICY_META, set to inband ICY metadata found */ - u32 reply; + char *value; + union { + /*message-dependend + for GF_NETIO_PARSE_REPLY, response code + for GF_NETIO_DATA_EXCHANGE + Set to 1 in to indicate end of chunk transfer + Set to 2 in GF_NETIO_DATA_EXCHANGE to indicate complete file is already received (replay of events from cache) + if error is set, reply is set to HTTP code + for all other, usage is reserved + */ + u32 reply; + /* for GF_NETIO_REQUEST_SESSION*/ + s64 stream_id; + }; + /*download session for which the message is being sent*/ GF_DownloadSession *sess; } GF_NETIO_Parameter; @@ -159,11 +181,10 @@ #ifndef GPAC_DISABLE_NETWORK #include <gpac/config_file.h> -#include <gpac/cache.h> /*! URL information object*/ typedef struct GF_URL_Info_Struct { - const char * protocol; + char * protocol; char * server_name; char * remotePath; char * canonicalRepresentation; @@ -477,6 +498,43 @@ \param netcap_id ID of netcap configuration to use, may be null (see gpac -h netcap) */ void gf_dm_sess_set_netcap_id(GF_DownloadSession *sess, const char *netcap_id); + +/*! +\brief sets max rate for a session + +Sets the maximum rate for a session without throtling other sessions. +\param sess the download session object +\param rate_in_bits_per_sec the new rate in bits per sec. If 0, HTTP rate will not be limited + */ +void gf_dm_sess_set_max_rate(GF_DownloadSession *sess, u32 rate_in_bits_per_sec); + +/*! +\brief sets max rate for a session + +Gets the maximum rate for a session +\param sess the download session object +\return the current rate in bits per sec, 0 if no limitation + */ +u32 gf_dm_sess_get_max_rate(GF_DownloadSession *sess); + +/*! +\brief Checks session regulation state + +Checks if last session fetch has been skipped due to rate limitation +\param sess the download session object +\return GF_TRUE if last call to \ref gf_dm_sess_fetch_data was skipped because of rate regulation + */ +Bool gf_dm_sess_is_regulated(GF_DownloadSession *sess); + +/*! +\brief Gets associated ressource size + +Gets the resource size as announced by the server. If byte-range request was , this size will be different from the total bytes expected in the session +\param sess the download session object +\return the resource size, 0 if unknown +*/ +u32 gf_dm_sess_get_resource_size(GF_DownloadSession * sess); + /*! \brief sets download manager max rate per session @@ -539,6 +597,11 @@ */ GF_Err gf_dm_set_localcache_provider(GF_DownloadManager *dm, Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool is_cache_destroy), void *lc_udta); +/** + * Handle for Cache Entries. + */ +typedef struct __DownloadedCacheEntryStruct * DownloadedCacheEntry; + /*! Adds a local entry in the cache @@ -552,7 +615,7 @@ \param download_time_ms indicates the download time of the associated resource, if known, 0 otherwise. \return a cache entry structure */ -DownloadedCacheEntry gf_dm_add_cache_entry(GF_DownloadManager *dm, const char *szURL, GF_Blob *blob, u64 start_range, u64 end_range, const char *mime, Bool clone_memory, u32 download_time_ms); +DownloadedCacheEntry gf_dm_add_cache_entry(GF_DownloadManager *dm, const char *szURL, GF_Blob *blob, u64 start_range, u64 end_range, const char *mime, Bool clone_memory, u32 download_time_ms); /*! Forces HTTP headers for a given cache entry @@ -667,6 +730,8 @@ GF_DownloadManager *gf_dm_new(GF_DownloadFilterSession *fsess); void gf_dm_del(GF_DownloadManager *dm); void gf_dm_sess_set_netcap_id(GF_DownloadSession *sess, const char *netcap_id); +void gf_dm_sess_set_max_rate(GF_DownloadSession *sess, u32 rate_in_bits_per_sec); +Bool gf_dm_sess_is_regulated(GF_DownloadSession *sess); #endif //GPAC_CONFIG_EMSCRIPTEN @@ -687,4 +752,3 @@ #endif /*_GF_DOWNLOAD_H_*/ -
View file
gpac-2.4.0.tar.gz/include/gpac/events_constants.h -> gpac-26.02.0.tar.gz/include/gpac/events_constants.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2022 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / Events management @@ -706,9 +706,6 @@ /*non-dom keys, used in LASeR*/ GF_KEY_CELL_SOFT1, /*soft1 key of cell phones*/ GF_KEY_CELL_SOFT2, /*soft2 key of cell phones*/ - - /*for joystick handling*/ - GF_KEY_JOYSTICK } GF_KeyCode;
View file
gpac-2.4.0.tar.gz/include/gpac/evg.h -> gpac-26.02.0.tar.gz/include/gpac/evg.h
Changed
@@ -173,7 +173,7 @@ Stencils are by default created in auto matrix mode. \param stencil the target stencil -\param auto_on If true, the surface current matrix will be added to the stencil matrix when drawing, otherwise the stencil matrix is in final surface coordinates +\param auto_on If true, the surface current matrix will be added to the stencil matrix when drawing, otherwise the stencil matrix is in final surface coordinates \return error if any */ GF_Err gf_evg_stencil_set_auto_matrix(GF_EVGStencil * stencil, Bool auto_on); @@ -999,7 +999,7 @@ /*! disables depth buffer write \note this is only used for 3D rasterizer, and fails 2D mode \param surf the target 3D surface -\param do_write if GF_TRUE, depth values are written to the depth buffer (default when creating the rasterizer) +\param do_write if GF_TRUE, depth values are written to the depth buffer (default when creating the rasterizer) \return error if any */ GF_Err gf_evg_surface_write_depth(GF_EVGSurface *surf, Bool do_write);
View file
gpac-2.4.0.tar.gz/include/gpac/filters.h -> gpac-26.02.0.tar.gz/include/gpac/filters.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2026 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -54,7 +54,7 @@ API Documentation of the filter managment system of GPAC. The filter management in GPAC is built using the following core objects: -- \ref GF_FilterSession in charge of: +- \ref fs_grp "GF_FilterSession" in charge of: - loading filters from register, managing argument parsing and co - resolving filter graphs to handle PID connection(s) - tracking data packets and properties exchanged on PIDs @@ -62,12 +62,12 @@ - ensuring thread-safe filter state: a filter may be called from any thread in the session (unless explicitly asked not to), but only by a single thread at any time. - \ref __gf_filter_register static structure describing possible entry points of the filter, possible arguments and input output PID capabilities. Each filter share the same API (register definition) regardless of its type: source/sink, mux/demux, encode/decode, raw media processing, encoded media processing, ... -- \ref GF_Filter is an instance of the filter register. A filter implementation typical tasks are: +- \ref fs_filter "GF_Filter" is an instance of the filter register. A filter implementation typical tasks are: - accepting new input PIDs (for non source filters) - defining new output PIDs (for non sink filters), applying any property change due to filter processing - consuming packets on the input PIDs - dispatching packets on the output PIDs -- \ref GF_FilterPid handling the connections between two filters. +- \ref fs_pid "GF_FilterPid" handling the connections between two filters. - PID natively supports fan-out (one filter PID connecting to multiple destinations). - A PID is in charge of dispatching packets to possible destinations and storing PID properties in sync with dispatched packets. - Whenever PID properties change, the next packet sent on that PID is associated with the new state, and the destination filter(s) will be called @@ -78,7 +78,7 @@ for processing. This is a semi-blocking design, which imply that if a filter has one of its PIDs in a non blocking state, it will be scheduled for processing. If a PID has multiple destinations and one of the destination consumes faster than the other one, the filter is currently not blocking (this might change in the near future). - A PID is in charge of managing the packet references across filters, by performing memory management of allocated data packets (avoid alloc/free at each packet but rather recycle the memory) and tracking shared packets references. -- \ref GF_FilterPacket holding data to dispatch from a filter on a given PID. +- \ref fs_pck "GF_FilterPacket" holding data to dispatch from a filter on a given PID. - Packets are always associated to a single output PID, ie it is not possible for a filter to send one packet to multiple PIDs, the data has to be cloned. - Packets have default attributes such as timestamps, size, random access status, start/end frame, etc, as well as optional properties. - All packets are reference counted. @@ -94,7 +94,7 @@ GPAC comes with a set of built-in filters in libgpac. It is also possible to define external filters in dynamic libraries. GPAC will look for such libraries - in default module folder and folders listed in GPAC config file section core, key mod-dirs. The files SHALL be named gf_* and export a function called RegisterFilter + in default module folder and folders listed in GPAC config file section core, key mod-dirs. The files SHALL be named gf_* and export a function called RegisterFilter with the following prototype: @@ -254,7 +254,7 @@ GF_FilterSession *gf_fs_new(s32 nb_threads, GF_FilterSchedulerType type, GF_FilterSessionFlags flags, const char *blacklist); /*! Creates a new filter session, loading parameters from gpac config. This will also load all available filter registers not blacklisted. -\param flags set of flags for the session. Only \ref GF_FS_FLAG_LOAD_META, \ref GF_FS_FLAG_NON_BLOCKING , \ref GF_FS_FLAG_NO_GRAPH_CACHE and \ref GF_FS_FLAG_PRINT_CONNECTIONS are used, other flags are set from config file or command line +\param flags set of flags for the session. Only \ref GF_FS_FLAG_LOAD_META, \ref GF_FS_FLAG_NON_BLOCKING , \ref GF_FS_FLAG_NO_GRAPH_CACHE and \ref GF_FS_FLAG_PRINT_CONNECTIONS are used, other flags are set from config file or command line \return the created filter session */ GF_FilterSession *gf_fs_new_defaults(GF_FilterSessionFlags flags); @@ -299,6 +299,34 @@ */ GF_Filter *gf_fs_load_filter(GF_FilterSession *session, const char *name, GF_Err *err_code); +/*! Parses a filter graph and loads the filters in the session. +The format of the filter graph is the same as the one used in the gpac command line. +\param fsess filter session +\param argc number of arguments +\param argv list of arguments +\param out_loaded_filters list of loaded filters, must be freed by the caller +\param out_links_directive list of link directives, must be freed by the caller +\return error if any +*/ +GF_Err gf_fs_parse_filter_graph(GF_FilterSession *fsess, int argc, char *argv, GF_List **out_loaded_filters, GF_List **out_links_directive); + + +/*! Parses a filter graph and loads the filters in the session. + +The format of the filter graph is the same as the one used in the gpac command line. +The input string will be split by spaces and supplied to \ref gf_fs_parse_filter_graph. + +This is a convenience function for command line parsing, and does not support all the features of the command line parser. If arguments are already parsed, use \ref gf_fs_parse_filter_graph instead. This method only handles quotes and spaces, and does not handle any other special characters. + +\param fsess filter session +\param graph_str filter graph string +\param out_loaded_filters list of loaded filters, must be freed by the caller +\param out_links_directive list of link directives, must be freed by the caller +\return error if any +*/ +GF_Err gf_fs_parse_filter_graph_str(GF_FilterSession *fsess, char *graph_str, GF_List **out_loaded_filters, GF_List **out_links_directive); + + /*! Checks if a filter register exists by name. \param session filter session \param name name of the filter register to check. @@ -382,7 +410,7 @@ /*! Loads a source filter from a URL and arguments \param session filter session \param url URL of the source to load. Can be a local file name, a full path (/.., \\...) or a full URL with scheme (eg http://, tcp://) -\param args arguments for the filter, see \ref gf_fs_load_filter - the arguments can also be set in the url, typycally using `:gpac:` option delimiter +\param args arguments for the filter, see \ref gf_fs_load_filter - the arguments can also be set in the url, typically using `:gpac:` option delimiter \param parent_url parent URL of the source, or NULL if none \param err if not NULL, is set to error code if any \return the filter loaded or NULL if error @@ -392,7 +420,7 @@ /*! Loads a destination filter from a URL and arguments \param session filter session \param url URL of the source to load. Can be a local file name, a full path (/.., \\...) or a full URL with scheme (eg http://, tcp://) -\param args arguments for the filter, see \ref gf_fs_load_filter - the arguments can also be set in the url, typycally using `:gpac:` option delimiter +\param args arguments for the filter, see \ref gf_fs_load_filter - the arguments can also be set in the url, typically using `:gpac:` option delimiter \param parent_url parent URL of the source, or NULL if none \param err if not NULL, is set to error code if any \return the filter loaded or NULL if error @@ -439,6 +467,20 @@ */ GF_Err gf_fs_post_user_task(GF_FilterSession *session, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name); + +/*! Posts a user task to the session +\param session filter session +\param task_execute the callback function for the task. The callback can return: + - GF_FALSE to cancel the task + - GF_TRUE to reschedule the task, in which case the task will be rescheduled immediately or after reschedule_ms. +\param udta_callback callback user data passed back to the task_execute function +\param log_name log name of the task. If NULL, default is "user_task" +\param delay delay in milliseconds before calling the task +\return the error code if any +*/ +GF_Err gf_fs_post_user_task_delay(GF_FilterSession *session, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name, u32 delay); + + /*! Posts a user task to the session main thread only \param session filter session \param task_execute the callback function for the task. The callback can return: @@ -610,6 +652,8 @@ u64 nb_hw_pck_sent; /*!number of processing errors in the lifetime of the filter*/ u32 nb_errors; + /*!number of errors since last process without errors*/ + u32 nb_current_errors; /*!number of bytes sent by this filter*/ u64 nb_bytes_sent; @@ -750,6 +794,13 @@ */ void *gf_fs_get_rt_udta(GF_FilterSession *session); +/*! Checks if a filter is still valid - typically used when not monitoring filter destruction at session level using \ref gf_fs_set_filter_creation_callback +\param session filter session +\param filter filter to check +\return GF_TRUE if filter is still valid until the next call to \ref gf_fs_run, GF_FALSE otherwise +*/ +Bool gf_fs_check_filter(GF_FilterSession *session, GF_Filter *filter); + /*! Fires an event on filter \param session filter session \param filter target filter - if NULL, event will be executed on all filters. Otherwise, the event will be executed directly if its type is \ref GF_FEVT_USER, and fired otherwise @@ -759,7 +810,6 @@ */ Bool gf_fs_fire_event(GF_FilterSession *session, GF_Filter *filter, GF_FilterEvent *evt, Bool upstream); - /*! callback functions for external monitoring of filter creation or destruction \param udta user data passed back to callback \param do_activate if true context must be activated for calling thread, otherwise context is no longer used @@ -775,6 +825,7 @@ */ GF_Err gf_fs_set_external_gl_provider(GF_FilterSession *session, gf_fs_gl_activate on_gl_activate, void *udta); + /*! Flags for debug info*/ typedef enum { @@ -853,7 +904,7 @@ GF_PROP_VEC4I = 13, /*! string property, memory is duplicated when setting the property and managed internally*/ GF_PROP_STRING = 14, - /*! string property, memory is NOT duplicated when setting the property but is then managed (and free) internally. + /*! string property, memory is NOT duplicated when setting the property but is then managed (and freed) internally. Only used when setting a property, the type then defaults to GF_PROP_STRING DO NOT USE the associate string field upon return from setting the property, it might have been destroyed*/ GF_PROP_STRING_NO_COPY= 15, @@ -884,7 +935,7 @@ GF_PROP_4CC_LIST = 26, /*! string list, memory is duplicated when setting the property - to use only with property assignment functions*/ GF_PROP_STRING_LIST_COPY = 27, - + /*! last non-enum property*/ GF_PROP_LAST_NON_ENUM, @@ -1065,6 +1116,9 @@ /*! Built-in property types See gpac help (gpac -h props) for codes, types, formats and and meaning + +All these property must use capital alpha-numerical code, no space or special charcters allowed. + \hideinitializer */ enum @@ -1094,6 +1148,7 @@ GF_PROP_PID_UNFRAMED = GF_4CC('P','F','R','M'), GF_PROP_PID_UNFRAMED_FULL_AU = GF_4CC('P','F','R','F'), GF_PROP_PID_DURATION = GF_4CC('P','D','U','R'), + GF_PROP_PID_DURATION_AVG = GF_4CC('E','D','U','R'), GF_PROP_PID_NB_FRAMES = GF_4CC('N','F','R','M'), GF_PROP_PID_FRAME_OFFSET = GF_4CC('F','R','M','O'), GF_PROP_PID_FRAME_SIZE = GF_4CC('C','F','R','S'), @@ -1114,6 +1169,7 @@ GF_PROP_PID_AUDIO_FORMAT = GF_4CC('A','F','M','T'), GF_PROP_PID_AUDIO_SPEED = GF_4CC('A','S','P','D'), GF_PROP_PID_UNFRAMED_LATM = GF_4CC('L','A','T','M'), + GF_PROP_PID_UNFRAMED_SRT = GF_4CC('U','S','R','T'), GF_PROP_PID_DELAY = GF_4CC('M','D','L','Y'), GF_PROP_PID_CTS_SHIFT = GF_4CC('M','D','T','S'), GF_PROP_PID_NO_PRIMING = GF_4CC('A','S','K','P'), @@ -1138,7 +1194,7 @@ GF_PROP_PID_HIDDEN = GF_4CC('H','I','D','E'), GF_PROP_PID_CROP_POS = GF_4CC('V','C','X','Y'), GF_PROP_PID_ORIG_SIZE = GF_4CC('V','O','W','H'), - GF_PROP_PID_SRD = GF_4CC('S','R','D',' '), + GF_PROP_PID_SRD = GF_4CC('S','R','D','I'), GF_PROP_PID_SRD_REF = GF_4CC('S','R','D','R'), GF_PROP_PID_SRD_MAP = GF_4CC('S','R','D','M'), GF_PROP_PID_ALPHA = GF_4CC('V','A','L','P'), @@ -1160,6 +1216,7 @@ GF_PROP_PID_REMOTE_URL = GF_4CC('R','U','R','L'), GF_PROP_PID_REDIRECT_URL = GF_4CC('R','E','L','O'), GF_PROP_PID_FILEPATH = GF_4CC('F','S','R','C'), + GF_PROP_PID_FILEALIAS = GF_4CC('F','A','L','I'), GF_PROP_PID_MIME = GF_4CC('M','I','M','E'), GF_PROP_PID_FILE_EXT = GF_4CC('F','E','X','T'), GF_PROP_PID_OUTPATH = GF_4CC('F','D','S','T'), @@ -1187,7 +1244,6 @@ GF_PROP_PID_ISMA_SELECTIVE_ENC = GF_4CC('I','S','S','E'), GF_PROP_PID_ISMA_IV_LENGTH = GF_4CC('I','S','I','V'), GF_PROP_PID_ISMA_KI_LENGTH = GF_4CC('I','S','K','I'), - GF_PROP_PID_ISMA_KI = GF_4CC('I','K','E','Y'), GF_PROP_PID_OMA_CRYPT_TYPE = GF_4CC('O','M','C','T'), GF_PROP_PID_OMA_CID = GF_4CC('O','M','I','D'), GF_PROP_PID_OMA_TXT_HDR = GF_4CC('O','M','T','H'), @@ -1218,6 +1274,11 @@ GF_PROP_PCK_UTC_TIME = GF_4CC('U','T','C','D'), GF_PROP_PCK_MEDIA_TIME = GF_4CC('M','T','I','M'), GF_PROP_PCK_MPD_SEGSTART = GF_4CC('F','M','S','S'), + GF_PROP_PCK_ID = GF_4CC('P','K','I','D'), + GF_PROP_PCK_REFS = GF_4CC('P','R','F','S'), + GF_PROP_PCK_UDTA = GF_4CC('P','U','D','T'), + GF_PROP_PCK_LLHAS_TEMPLATE = GF_4CC('P','S','R','T'), + GF_PROP_PCK_TIMECODE = GF_4CC('T','C','O','D'), GF_PROP_PID_MAX_FRAME_SIZE = GF_4CC('M','F','R','S'), GF_PROP_PID_AVG_FRAME_SIZE = GF_4CC('A','F','R','S'), @@ -1240,14 +1301,18 @@ GF_PROP_PID_PERIOD_START = GF_4CC('P','E','S','T'), GF_PROP_PID_PERIOD_DUR = GF_4CC('P','E','D','U'), GF_PROP_PID_REP_ID = GF_4CC('D','R','I','D'), + GF_PROP_PID_SSR = GF_4CC('S','S','R','R'), + GF_PROP_PID_AS_QUERY = GF_4CC('A','S','Q','R'), GF_PROP_PID_AS_ID = GF_4CC('D','A','I','D'), GF_PROP_PID_MUX_SRC = GF_4CC('M','S','R','C'), GF_PROP_PID_DASH_MODE = GF_4CC('D','M','O','D'), GF_PROP_PID_FORCE_SEG_SYNC = GF_4CC('D','F','S','S'), GF_PROP_PID_DASH_DUR = GF_4CC('D','D','U','R'), + GF_PROP_PID_DASH_FDUR = GF_4CC('F','D','U','R'), GF_PROP_PID_DASH_MULTI_PID = GF_4CC('D','M','S','D'), GF_PROP_PID_DASH_MULTI_PID_IDX = GF_4CC('D','M','S','I'), GF_PROP_PID_DASH_MULTI_TRACK = GF_4CC('D','M','T','K'), + GF_PROP_PID_DASH_INIT_BASE64 = GF_4CC('I','B','6','4'), GF_PROP_PID_ROLE = GF_4CC('R','O','L','E'), GF_PROP_PID_PERIOD_DESC = GF_4CC('P','D','E','S'), GF_PROP_PID_AS_COND_DESC = GF_4CC('A','C','D','S'), @@ -1260,6 +1325,7 @@ GF_PROP_PID_CLAMP_DUR = GF_4CC('D','C','M','D'), GF_PROP_PID_HLS_PLAYLIST = GF_4CC('H','L','V','P'), GF_PROP_PID_HLS_GROUPID = GF_4CC('H','L','G','I'), + GF_PROP_PID_HLS_GROUP_REND = GF_4CC('H','L','G','R'), GF_PROP_PID_HLS_FORCE_INF = GF_4CC('H','L','F','I'), GF_PROP_PID_HLS_EXT_MASTER = GF_4CC('H','L','M','X'), GF_PROP_PID_HLS_EXT_VARIANT = GF_4CC('H','L','V','X'), @@ -1278,6 +1344,7 @@ GF_PROP_PID_COLR_PRIMARIES = GF_4CC('C','P','R','M'), GF_PROP_PID_COLR_TRANSFER = GF_4CC('C','T','R','C'), + GF_PROP_PID_COLR_TRANSFER_ALT = GF_4CC('C','A','T','C'), GF_PROP_PID_COLR_MX = GF_4CC('C','M','X','C'), GF_PROP_PID_COLR_RANGE = GF_4CC('C','F','R','A'), GF_PROP_PID_COLR_CHROMAFMT = GF_4CC('C','F','M','T'), @@ -1299,12 +1366,13 @@ GF_PROP_PID_KEEP_AFTER_EOS = GF_4CC('P','K','A','E'), GF_PROP_PID_COVER_ART = GF_4CC('P','C','O','V'), GF_PROP_PID_ORIG_FRAG_URL = GF_4CC('O','F','R','A'), + GF_PROP_PID_VOD_SIDX_RANGE = GF_4CC('P','R','S','R'), - GF_PROP_PID_ROUTE_IP = GF_4CC('R','S','I','P'), - GF_PROP_PID_ROUTE_PORT = GF_4CC('R','S','P','N'), - GF_PROP_PID_ROUTE_NAME = GF_4CC('R','S','F','N'), - GF_PROP_PID_ROUTE_CAROUSEL = GF_4CC('R','S','C','R'), - GF_PROP_PID_ROUTE_SENDTIME = GF_4CC('R','S','S','T'), + GF_PROP_PID_MCAST_IP = GF_4CC('M','S','I','P'), + GF_PROP_PID_MCAST_PORT = GF_4CC('M','S','P','N'), + GF_PROP_PID_MCAST_NAME = GF_4CC('M','S','F','N'), + GF_PROP_PID_MCAST_CAROUSEL = GF_4CC('M','S','C','R'), + GF_PROP_PID_MCAST_SENDTIME = GF_4CC('M','S','S','T'), GF_PROP_PID_STEREO_TYPE = GF_4CC('P','S','T','T'), GF_PROP_PID_PROJECTION_TYPE = GF_4CC('P','P','J','T'), @@ -1322,21 +1390,26 @@ GF_PROP_PID_CHAP_TIMES = GF_4CC('C','H','P','T'), GF_PROP_PID_CHAP_NAMES = GF_4CC('C','H','P','N'), GF_PROP_PID_IS_CHAP = GF_4CC('P','C','H','P'), - //internal prop indicating the (main) streamtype of a PID before mux, only used for route setup GF_PROP_PID_PREMUX_STREAM_TYPE = GF_4CC('P','P','S','T'), GF_PROP_PID_CODEC_MERGEABLE = GF_4CC('P','C','M','B'), - GF_PROP_PID_FILE_REL = GF_4CC('F','N','R','L'), + GF_PROP_PCK_FILE_REL = GF_4CC('F','N','R','L'), //internal for HLS playlist reference, gives a unique ID identifying media mux, and indicated in packets carrying child playlists GF_PROP_PCK_HLS_REF = GF_4CC('H','P','L','R'), - //internal for HLS low latency - GF_PROP_PID_LLHLS = GF_4CC('H','L','S','L'), - GF_PROP_PCK_HLS_FRAG_NUM = GF_4CC('H','L','S','N'), + GF_PROP_PID_HLS_REF = GF_4CC('P','H','L','R'), + //internal for low latency HLS abd DASH: + //0 or not present: no low latency + //1: LL-HLS byte-range mode + //2: LL-HLS or DASH SSR separate parts mode + GF_PROP_PID_LLHAS_MODE = GF_4CC('H','L','H','S'), + // part number for LLHLS or DSH-SSR + GF_PROP_PCK_LLHAS_FRAG_NUM = GF_4CC('H','L','S','N'), //we also use this property on PID to signal sample-accurate seek info is present GF_PROP_PCK_SKIP_BEGIN = GF_4CC('P','C','K','S'), GF_PROP_PCK_SKIP_PRES = GF_4CC('P','C','K','D'), + GF_PROP_PCK_ORIG_DUR = GF_4CC('P','C','O','D'), //internal for DASH forward mode GF_PROP_PID_DASH_FWD = GF_4CC('D','F','W','D'), GF_PROP_PCK_DASH_MANIFEST = GF_4CC('D','M','P','D'), @@ -1346,19 +1419,19 @@ GF_PROP_PID_HLS_KMS = GF_4CC('H','L','S','K'), GF_PROP_PID_HLS_IV = GF_4CC('H','L','S','I'), GF_PROP_PID_CLEARKEY_URI = GF_4CC('C','C','K','U'), - //internal GF_PROP_PID_CLEARKEY_KID = GF_4CC('C','C','K','I'), - //internal, indicate DASH segments are generated in sparse mode (from context) GF_PROP_PID_DASH_SPARSE = GF_4CC('D','S','S','G'), - //internal, indicate DASH dependency group GF_PROP_PID_DASH_DEP_GROUP = GF_4CC('D','G','D','I'), + GF_PROP_PCK_DASH_PERIOD_START = GF_4CC('P','D','P','S'), + + GF_PROP_PID_HAS_SKIP_BEGIN = GF_4CC('P','S','B','P'), //internal property indicating pointer to associated GF_DownloadSession GF_PROP_PID_DOWNLOAD_SESSION = GF_4CC('G','H','T','T'), //PID has temi information GF_PROP_PID_HAS_TEMI = GF_4CC('P','T','E','M'), - //PID has no init segment associated (file forward mode of dasher) + GF_PROP_PID_SCTE35_PID = GF_4CC('S','C','3','5'), GF_PROP_PID_NO_INIT = GF_4CC('P','N','I','N'), //PID carries a manifest @@ -1367,26 +1440,40 @@ GF_PROP_PCK_XPS_MASK = GF_4CC('P','X','P','M'), GF_PROP_PCK_END_RANGE = GF_4CC('P','C','E','R'), - //internal, force creation of rewriter filter (only used for forcing reparse of NALU-based codecs) GF_PROP_PID_FORCE_UNFRAME = GF_4CC('P','F','U','F'), GF_PROP_PCK_SPLIT_START = GF_4CC('P','S','P','S'), GF_PROP_PCK_SPLIT_END = GF_4CC('P','S','P','E'), + GF_PROP_PID_INIT_NAME = GF_4CC('P','I','N','M'), + GF_PROP_PCK_SEG_URL = GF_4CC('S','U','R','L'), + GF_PROP_PCK_CENC_PSSH = GF_4CC('P','S','H','P'), - /*! Internal property used for meta demuxers ( FFMPEG, ...) codec ID + /*! Internal property used for meta demuxers ( FFmpeg, ...) codec ID Property can be: - pointer to codec context: only for ffdmx with old ffmpeg versions) - uint: AVCODEC_ID_* ffdmx with newer versions or ffenc output */ GF_PROP_PID_META_DEMUX_CODEC_ID = GF_4CC('M','D','C','I'), - - /*! Internal property used for meta demuxers ( FFMPEG, ...) codec name*/ GF_PROP_PID_META_DEMUX_CODEC_NAME = GF_4CC('M','D','C','N'), - /*! Internal property used for meta demuxers ( FFMPEG, ...) codec opaque data, u32*/ + /*! Internal property used for meta demuxers ( FFmpeg, ...) codec opaque data, u32*/ GF_PROP_PID_META_DEMUX_OPAQUE = GF_4CC('M','D','O','P'), + + GF_PROP_PCK_PARTIAL_REPAIR = GF_4CC('P','C','P','R'), + GF_PROP_PID_FAKE = GF_4CC('P','F','A','K'), + + GF_PROP_PID_SEI_LOADED = GF_4CC('P','S','E','I'), + GF_PROP_PCK_SEI_LOADED = GF_4CC('S','E','I','P'), + GF_PROP_PCK_CONTENT_LIGHT_LEVEL = GF_4CC('C','L','L','P'), + GF_PROP_PCK_MASTER_DISPLAY_COLOUR = GF_4CC('M','D','C','P'), + + GF_PROP_PCK_ORIGINAL_PTS = GF_4CC('O','P','T','S'), + GF_PROP_PCK_ORIGINAL_DTS = GF_4CC('O','D','T','S'), + GF_PROP_PID_MABR_URLS = GF_4CC('M','A','B','U'), + GF_PROP_PCK_FORCED_SUB = GF_4CC('P','C','F','S'), + }; /*! Block patching requirements for FILE pids, as signaled by GF_PROP_PID_DISABLE_PROGRESSIVE @@ -1456,6 +1543,16 @@ */ u32 gf_props_parse_enum(u32 type, const char *value); +/*! Parses a property value from string +\param type property type to parse +\param name property name to parse (for logs) +\param value string containing the value to parse +\param enum_values string containig enum_values, or NULL. enum_values are used for unsigned int properties, take the form "a|b|c" and resolve to 0|1|2. +\param list_sep_char value of the list separator character to use +\return the parsed property value +*/ +GF_PropertyValue gf_props_parse_value(u32 type, const char *name, const char *value, const char *enum_values, char list_sep_char); + /*! Get the name of a constant type property value \param type property type \param value value of constant @@ -1475,16 +1572,10 @@ */ u32 gf_props_get_base_type(u32 type); - -/*! Parses a property value from string -\param type property type to parse -\param name property name to parse (for logs) -\param value string containing the value to parse -\param enum_values string containig enum_values, or NULL. enum_values are used for unsigned int properties, take the form "a|b|c" and resolve to 0|1|2. -\param list_sep_char value of the list separator character to use -\return the parsed property value +/*! Checks consistency of defined properties +\return GF_TRUE if OK, GF_FALSE otherwise */ -GF_PropertyValue gf_props_parse_value(u32 type, const char *name, const char *value, const char *enum_values, char list_sep_char); +Bool gf_props_sanity_check(); /*! Maximum string size to use when dumping a property*/ #define GF_PROP_DUMP_ARG_SIZE 100 @@ -1525,7 +1616,7 @@ */ void gf_props_reset_single(GF_PropertyValue *prop); -/*! Property aplies only to packets */ +/*! Property applies only to packets - if not set, property only applies to PID*/ #define GF_PROP_FLAG_PCK 1 /*! Property is optional for GPAC GSF serialization (not transmitted over network when property removal is enabled) */ #define GF_PROP_FLAG_GSF_REM 1<<1 @@ -1566,61 +1657,61 @@ /*! Helper macro to set signed int property */ -#define PROP_SINT(_val) (GF_PropertyValue){.type=GF_PROP_SINT, .value.sint = _val} +#define PROP_SINT(_val) (GF_PropertyValue){GF_PROP_SINT, { .sint = (_val) }} /*! Helper macro to set unsigned int property */ -#define PROP_UINT(_val) (GF_PropertyValue){.type=GF_PROP_UINT, .value.uint = _val} +#define PROP_UINT(_val) (GF_PropertyValue){GF_PROP_UINT, { .uint = (_val) }} /*! Helper macro to set an enum property */ -#define PROP_ENUM(_val, _type) (GF_PropertyValue){.type=_type, .value.uint = _val} +#define PROP_ENUM(_val, _type) (GF_PropertyValue){_type, { .uint = (_val) }} /*! Helper macro to set 4CC unsigned int property */ -#define PROP_4CC(_val) (GF_PropertyValue){.type=GF_PROP_4CC, .value.uint = _val} +#define PROP_4CC(_val) (GF_PropertyValue){GF_PROP_4CC, { .uint = (_val) }} /*! Helper macro to set long signed int property */ -#define PROP_LONGSINT(_val) (GF_PropertyValue){.type=GF_PROP_LSINT, .value.longsint = _val} +#define PROP_LONGSINT(_val) (GF_PropertyValue){GF_PROP_LSINT, { .longsint = (_val) }} /*! Helper macro to set long unsigned int property */ -#define PROP_LONGUINT(_val) (GF_PropertyValue){.type=GF_PROP_LUINT, .value.longuint = _val} +#define PROP_LONGUINT(_val) (GF_PropertyValue){GF_PROP_LUINT, { .longuint = (_val) }} /*! Helper macro to set boolean property */ -#define PROP_BOOL(_val) (GF_PropertyValue){.type=GF_PROP_BOOL, .value.boolean = _val} +#define PROP_BOOL(_val) (GF_PropertyValue){GF_PROP_BOOL, { .boolean = (_val) }} /*! Helper macro to set fixed-point number property */ -#define PROP_FIXED(_val) (GF_PropertyValue){.type=GF_PROP_FLOAT, .value.fnumber = _val} +#define PROP_FIXED(_val) (GF_PropertyValue){GF_PROP_FLOAT, { .fnumber = (_val) }} /*! Helper macro to set float property */ -#define PROP_FLOAT(_val) (GF_PropertyValue){.type=GF_PROP_FLOAT, .value.fnumber = FLT2FIX(_val)} +#define PROP_FLOAT(_val) (GF_PropertyValue){GF_PROP_FLOAT, { .fnumber = FLT2FIX(_val) }} /*! Helper macro to set 32-bit fraction property from integers*/ -#define PROP_FRAC_INT(_num, _den) (GF_PropertyValue){.type=GF_PROP_FRACTION, .value.frac.num = _num, .value.frac.den = _den} +#define PROP_FRAC_INT(_num, _den) (GF_PropertyValue){GF_PROP_FRACTION, { .frac.num = (_num), .frac.den = (_den) }} /*! Helper macro to set 32-bit fraction property*/ -#define PROP_FRAC(_val) (GF_PropertyValue){.type=GF_PROP_FRACTION, .value.frac = _val } +#define PROP_FRAC(_val) (GF_PropertyValue){GF_PROP_FRACTION, { .frac = (_val) }} /*! Helper macro to set 64-bit fraction property from integers*/ -#define PROP_FRAC64(_val) (GF_PropertyValue){.type=GF_PROP_FRACTION64, .value.lfrac = _val} +#define PROP_FRAC64(_val) (GF_PropertyValue){GF_PROP_FRACTION64, { .lfrac = (_val) }} /*! Helper macro to set 64-bit fraction property*/ -#define PROP_FRAC64_INT(_num, _den) (GF_PropertyValue){.type=GF_PROP_FRACTION64, .value.lfrac.num = _num, .value.lfrac.den = _den} +#define PROP_FRAC64_INT(_num, _den) (GF_PropertyValue){GF_PROP_FRACTION64, { .lfrac.num = (_num), .lfrac.den = (_den) }} /*! Helper macro to set double property */ -#define PROP_DOUBLE(_val) (GF_PropertyValue){.type=GF_PROP_DOUBLE, .value.number = _val} +#define PROP_DOUBLE(_val) (GF_PropertyValue){GF_PROP_DOUBLE, { .number = (_val) }} /*! Helper macro to set string property */ -#define PROP_STRING(_val) (GF_PropertyValue){.type=GF_PROP_STRING, .value.string = (char *) _val} +#define PROP_STRING(_val) (GF_PropertyValue){GF_PROP_STRING, { .string = (char *)(_val) }} /*! Helper macro to set string property without string copy (string memory is owned by filter) */ -#define PROP_STRING_NO_COPY(_val) (GF_PropertyValue){.type=GF_PROP_STRING_NO_COPY, .value.string = _val} +#define PROP_STRING_NO_COPY(_val) (GF_PropertyValue){GF_PROP_STRING_NO_COPY, { .string = (char *)(_val) }} /*! Helper macro to set name property */ -#define PROP_NAME(_val) (GF_PropertyValue){.type=GF_PROP_NAME, .value.string = _val} +#define PROP_NAME(_val) (GF_PropertyValue){GF_PROP_NAME, { .string = (char *)(_val) }} /*! Helper macro to set data property */ -#define PROP_DATA(_val, _len) (GF_PropertyValue){.type=GF_PROP_DATA, .value.data.ptr = _val, .value.data.size=_len} +#define PROP_DATA(_val, _len) (GF_PropertyValue){GF_PROP_DATA, { .data.ptr = (_val), .data.size = (_len) }} /*! Helper macro to set data property without data copy ( memory is owned by filter) */ -#define PROP_DATA_NO_COPY(_val, _len) (GF_PropertyValue){.type=GF_PROP_DATA_NO_COPY, .value.data.ptr = _val, .value.data.size =_len} +#define PROP_DATA_NO_COPY(_val, _len) (GF_PropertyValue){GF_PROP_DATA_NO_COPY, { .data.ptr = (_val), .data.size = (_len) }} /*! Helper macro to set const data property */ -#define PROP_CONST_DATA(_val, _len) (GF_PropertyValue){.type=GF_PROP_CONST_DATA, .value.data.ptr = _val, .value.data.size = _len} +#define PROP_CONST_DATA(_val, _len) (GF_PropertyValue){GF_PROP_CONST_DATA, { .data.ptr = (_val), .data.size = (_len) }} /*! Helper macro to set 2D float vector property */ -#define PROP_VEC2(_val) (GF_PropertyValue){.type=GF_PROP_VEC2, .value.vec2 = _val} +#define PROP_VEC2(_val) (GF_PropertyValue){GF_PROP_VEC2, { .vec2 = (_val) }} /*! Helper macro to set 2D integer vector property */ -#define PROP_VEC2I(_val) (GF_PropertyValue){.type=GF_PROP_VEC2I, .value.vec2i = _val} +#define PROP_VEC2I(_val) (GF_PropertyValue){GF_PROP_VEC2I, { .vec2i = (_val) }} /*! Helper macro to set 2D integer vector property from integers*/ -#define PROP_VEC2I_INT(_x, _y) (GF_PropertyValue){.type=GF_PROP_VEC2I, .value.vec2i.x = _x, .value.vec2i.y = _y} +#define PROP_VEC2I_INT(_x, _y) (GF_PropertyValue){GF_PROP_VEC2I, { .vec2i.x = (_x), .vec2i.y = (_y) }} /*! Helper macro to set 3D integer vector property */ -#define PROP_VEC3I(_val) (GF_PropertyValue){.type=GF_PROP_VEC3I, .value.vec3i = _val} +#define PROP_VEC3I(_val) (GF_PropertyValue){GF_PROP_VEC3I, { .vec3i = (_val) }} /*! Helper macro to set 3D integer vector property from integers*/ -#define PROP_VEC3I_INT(_x, _y, _z) (GF_PropertyValue){.type=GF_PROP_VEC3I, .value.vec3i.x = _x, .value.vec3i.y = _y, .value.vec3i.z = _z} +#define PROP_VEC3I_INT(_x, _y, _z) (GF_PropertyValue){GF_PROP_VEC3I, { .vec3i.x = (_x), .vec3i.y = (_y), .vec3i.z = (_z) }} /*! Helper macro to set 4D integer vector property */ -#define PROP_VEC4I(_val) (GF_PropertyValue){.type=GF_PROP_VEC4I, .value.vec4i = _val} +#define PROP_VEC4I(_val) (GF_PropertyValue){GF_PROP_VEC4I, { .vec4i = (_val) }} /*! Helper macro to set 4D integer vector property from integers */ -#define PROP_VEC4I_INT(_x, _y, _z, _w) (GF_PropertyValue){.type=GF_PROP_VEC4I, .value.vec4i.x = _x, .value.vec4i.y = _y, .value.vec4i.z = _z, .value.vec4i.w = _w} +#define PROP_VEC4I_INT(_x, _y, _z, _w) (GF_PropertyValue){GF_PROP_VEC4I, { .vec4i.x = (_x), .vec4i.y = (_y), .vec4i.z = (_z), .vec4i.w = (_w) }} /*! Helper macro to set pointer property */ -#define PROP_POINTER(_val) (GF_PropertyValue){.type=GF_PROP_POINTER, .value.ptr = (void*)_val} +#define PROP_POINTER(_val) (GF_PropertyValue){GF_PROP_POINTER, { .ptr = (void *)(_val) }} /*! @} */ @@ -1651,7 +1742,7 @@ GF_FEVT_STOP and GF_FEVT_SOURCE_SEEK events are filtered to reset the PID buffers. -The following events may be used globally on a filter, e.g. without a PID associated to the event: +The following events may be used globally on a filter, e.g. without a PID associated to the event: GF_FEVT_FILE_DELETE: used for source and sinks, indicata a file deletion @@ -1661,7 +1752,7 @@ GF_FEVT_USER: -The filter session does not maintain a notion of paused or resume streams, it is up to the consummer to stop processing the data wgile paused. +The filter session does not maintain a notion of paused or resume streams, it is up to the consummer to stop processing the data while paused. The GF_FEVT_PAUSE and GF_FEVT_RESUME events are only used to trigger pause and resume on interactive channels such as an RTSP session, i.e. to tell the remote peer to stop and resume. @{ @@ -1701,7 +1792,7 @@ GF_FEVT_BUFFER_REQ, /*! filter session capability change, sent whenever global capabilities (max width, max height, ... ) are changed*/ GF_FEVT_CAPS_CHANGE, - /*! inidicates the PID could not be connected - the PID passed is an output PID of the filter, no specific event structure is associated*/ + /*! indicates the PID could not be connected - the PID passed is an output PID of the filter, no specific event structure is associated*/ GF_FEVT_CONNECT_FAIL, /*! user event, sent from compositor/vout down to filters*/ GF_FEVT_USER, @@ -1713,10 +1804,14 @@ /*! DASH fragment (cmaf chunk) size info, sent down from muxers to manifest generators*/ GF_FEVT_FRAGMENT_SIZE, - /*! Encoder hints*/ - GF_FEVT_ENCODE_HINTS, + /*! Transport hints*/ + GF_FEVT_TRANSPORT_HINTS, /*! NTP source clock send by other services (eg from TS to dash using TEMI) */ GF_FEVT_NTP_REF, + /*! Event sent by DASH/HLS demux to source to notify a quality change - used for ROUTE/MABR only */ + GF_FEVT_DASH_QUALITY_SELECT, + /*! Hint for network transmission event */ + GF_FEVT_NETWORK_HINT } GF_FEventType; /*! type: the type of the event*/ @@ -1762,9 +1857,10 @@ 0: range is in media time 1: range is in timesatmps 2: range is in media time but timestamps should not be shifted (hybrid dash only for now) + 3: range is in media time and seeking is disabled (closest RAP is used and no seek flags on packets) */ u8 timestamp_based; - /*! GF_FEVT_PLAY only, indicates the consumer only cares for the full file, not packets*/ + /*! GF_FEVT_PLAY / GF_FEVT_PLAY_HINT, indicates the consumer only cares for the full file, not packets*/ u8 full_file_only; /*! for GF_FEVT_PLAY: indicates any current download should be aborted @@ -1806,7 +1902,7 @@ u8 is_init_segment; /*!GF_FEVT_SOURCE_SWITCH only, ignore cache expiration directive for HTTP*/ u8 skip_cache_expiration; - /*! GF_FEVT_SOURCE_SEEK only, hint block size for source, might not be respected*/ + /*! GF_FEVT_SOURCE_SEEK only, hint block size for source, might not be respected*/ u32 hint_block_size; } GF_FEVT_SourceSeek; @@ -1816,6 +1912,8 @@ FILTER_EVENT_BASE /*! URL of segment this info is for, or NULL if single file*/ const char *seg_url; + /*! base64 of segment payload (for init) or NULL*/ + const char *base64_version; /*! media start range in segment file*/ u64 media_range_start; /*! media end range in segment file*/ @@ -1886,7 +1984,12 @@ typedef struct { FILTER_EVENT_BASE - /*! URL to delete, or "__gpac_self__" when asking source filter to delete file */ + /*! URL to delete, or "__gpac_self__" when asking source filter to delete file + + For gfio files, the syntax gfio://PTR@URL is allowed, with: + - PTR: the parent gfio pointer + - URL: the url of the file to delete, relative to the parent gfio + */ const char *url; } GF_FEVT_FileDelete; @@ -1919,17 +2022,30 @@ Bool pid_only; } GF_FEVT_BufferRequirement; +typedef enum +{ + /*! no hints */ + GF_TRANSPORT_HINTS_NONE = 0, + /*! event seen by an encoder */ + GF_TRANSPORT_HINTS_SAW_ENCODER = 1<<0, +} GF_TransportHintsFlags; -/*! Event structure for GF_FEVT_ENCODE_HINT*/ +/*! Event structure for GF_FEVT_TRANSPORT_HINT*/ typedef struct { FILTER_EVENT_BASE - /*! duration of intra (IDR, closed GOP) as expected by the dasher */ - GF_Fraction intra_period; + /*! flags for the hints */ + GF_TransportHintsFlags flags; + + /*! segment duration */ + GF_Fraction seg_duration; /*! if TRUE codec should only generate DSI (possibly no input frame, and all output packets will be discarded) */ Bool gen_dsi_only; -} GF_FEVT_EncodeHints; + + /* if TRUE reframer should hold packets until theoretical segment boundary */ + Bool wait_seg_boundary; +} GF_FEVT_TransportHints; /*! Event structure for GF_FEVT_NTP_REF*/ @@ -1942,6 +2058,46 @@ } GF_FEVT_NTPRef; +/*! Quality selection state*/ +typedef enum +{ + /*! Quality is selected*/ + GF_QUALITY_SELECTED = 0, + /*! Quality is not selected*/ + GF_QUALITY_UNSELECTED, + /*! Quality is disabled and will never be selected*/ + GF_QUALITY_DISABLED +} GF_QualtitySelectionState; + +/*! Event structure for GF_FEVT_DASH_QUALITY_SELECT*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! service ID as advertised by the source PID carrying the manifest*/ + u32 service_id; + /*! ID of period */ + const char *period_id; + /*! ID of adaptation set */ + s32 as_id; + /*! ID of representation for DASH, URL of variant playlist for HLS */ + const char *rep_id; + /*! selection state */ + GF_QualtitySelectionState select_type; +} GF_FEVT_DASHQualitySelection; + +/*! Event structure for GF_FEVT_NETWORK_HINT*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! MTU size */ + u32 mtu_size; + + /*! output type , 4CC (currently only MABR defined */ + u32 sink_type; + +} GF_FEVT_NetworkHint; + /*! Filter Event object */ @@ -1958,8 +2114,10 @@ GF_FEVT_SegmentSize seg_size; GF_FEVT_FragmentSize frag_size; GF_FEVT_FileDelete file_del; - GF_FEVT_EncodeHints encode_hints; + GF_FEVT_TransportHints transport_hints; GF_FEVT_NTPRef ntp; + GF_FEVT_DASHQualitySelection dash_select; + GF_FEVT_NetworkHint net_hint; }; /*! Gets readable name for event type @@ -2076,33 +2234,33 @@ } GF_FilterArgs; /*! Shortcut macro to assign singed integer capability type*/ -#define CAP_SINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_SINT, .value.sint = _b}, .flags=(_f) } +#define CAP_SINT(_f, _a, _b) { _a, { GF_PROP_SINT, { .sint = (_b) }}, NULL, _f } /*! Shortcut macro to assign unsigned integer capability type*/ -#define CAP_UINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_UINT, .value.uint = _b}, .flags=(_f) } +#define CAP_UINT(_f, _a, _b) { _a, { GF_PROP_UINT, { .uint = (_b) }}, NULL, _f } /*! Shortcut macro to assign unsigned integer capability type*/ -#define CAP_4CC(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_4CC, .value.uint = _b}, .flags=(_f) } +#define CAP_4CC(_f, _a, _b) { _a, { GF_PROP_4CC, { .uint = (_b) }}, NULL, _f } /*! Shortcut macro to assign signed long integer capability type*/ -#define CAP_LSINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_LSINT, .value.longsint = _b}, .flags=(_f) } +#define CAP_LSINT(_f, _a, _b) { _a, { GF_PROP_LSINT, { .longsint = (_b) }}, NULL, _f } /*! Shortcut macro to assign unsigned long integer capability type*/ -#define CAP_LUINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_LUINT, .value.longuint = _b}, .flags=(_f) } +#define CAP_LUINT(_f, _a, _b) { _a, { GF_PROP_LUINT, { .longuint = (_b) }}, NULL, _f } /*! Shortcut macro to assign boolean capability type*/ -#define CAP_BOOL(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_BOOL, .value.boolean = _b}, .flags=(_f) } +#define CAP_BOOL(_f, _a, _b) { _a, { GF_PROP_BOOL, { .boolean = (_b) }}, NULL, _f } /*! Shortcut macro to assign fixed-point number capability type*/ -#define CAP_FIXED(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_FLOAT, .value.fnumber = _b}, .flags=(_f) } +#define CAP_FIXED(_f, _a, _b) { _a, { GF_PROP_FLOAT, { .fnumber = (_b) }}, NULL, _f } /*! Shortcut macro to assign float capability type*/ -#define CAP_FLOAT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_FLOAT, .value.fnumber = FLT2FIX(_b)}, .flags=(_f) } +#define CAP_FLOAT(_f, _a, _b) { _a, { GF_PROP_FLOAT, { .fnumber = FLT2FIX(_b) }}, NULL, _f } /*! Shortcut macro to assign 32-bit fraction capability type*/ -#define CAP_FRAC_INT(_f, _a, _b, _c) { .code=_a, .val={.type=GF_PROP_FRACTION, .value.frac.num = _b, .value.frac.den = _c}, .flags=(_f) } +#define CAP_FRAC_INT(_f, _a, _b, _c) { _a, { GF_PROP_FRACTION, { .frac = { .num = (_b), .den = (_c) }}}, NULL, _f } /*! Shortcut macro to assign 32-bit fraction capability type from integers*/ -#define CAP_FRAC(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_FRACTION, .value.frac = _b}, .flags=(_f) } +#define CAP_FRAC(_f, _a, _b) { _a, { GF_PROP_FRACTION, { .frac = (_b) }}, NULL, _f } /*! Shortcut macro to assign double capability type*/ -#define CAP_DOUBLE(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_DOUBLE, .value.number = _b}, .flags=(_f) } +#define CAP_DOUBLE(_f, _a, _b) { _a, { GF_PROP_DOUBLE, { .number = (_b) }}, NULL, _f } /*! Shortcut macro to assign name (const string) capability type*/ -#define CAP_NAME(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_NAME, .value.string = _b}, .flags=(_f) } +#define CAP_NAME(_f, _a, _b) { _a, { GF_PROP_NAME, { .string = (char *)(_b) }}, NULL, _f } /*! Shortcut macro to assign string capability type*/ -#define CAP_STRING(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_STRING, .value.string = _b}, .flags=(_f) } +#define CAP_STRING(_f, _a, _b) { _a, { GF_PROP_STRING, { .string = (char *)(_b) }}, NULL, _f } /*! Shortcut macro to assign unsigned integer capability type with capability priority*/ -#define CAP_UINT_PRIORITY(_f, _a, _b, _p) { .code=_a, .val={.type=GF_PROP_UINT, .value.uint = _b}, .flags=(_f), .priority=_p} +#define CAP_UINT_PRIORITY(_f, _a, _b, _p) { _a, { GF_PROP_UINT, { .uint = (_b) }}, NULL, _f, _p } /*! Flags for filter capabilities*/ enum @@ -2119,8 +2277,16 @@ GF_CAPFLAG_LOADED_FILTER = 1<<4, /*! indicates that this capability (input or output) applies to all following bundles. This avoids repeating capabilities common to all bundles by setting them only in the first*/ GF_CAPFLAG_STATIC = 1<<5, - /*! Only used for input capabilities, indicates that this capability is optional in the input PID */ + /*! Currently only used for output capabilities, indicates that this capability is optional in the PID */ GF_CAPFLAG_OPTIONAL = 1<<6, + /*! Only checks presence of capability */ + GF_CAPFLAG_PRESENT = 1<<7, + /*! Indicates this capability is only used on reconfigure - ignored for graph resolution - reconfig caps shall be placed last + The value of such caps is ignored, only the type/name is used + + This cap should only be set for reconfigurable filters intended to be dynamically loaded for adaptation (eg resamplers, color-space conversion, rescalers) + */ + GF_CAPFLAG_RECONFIG = 1<<8, }; /*! Shortcut macro to set for input capability flags*/ @@ -2296,7 +2462,8 @@ a GL context (currently only in main thread) upon init, but not requiring it for the decode. Such decoders get their GL frames mapped (through get_gl_texture callback) in the main GL thread*/ GF_FS_REG_CONFIGURE_MAIN_THREAD = 1<<2, - /*! when set indicates the filter does not take part of dynamic filter chain resolution and can only be used by explicitly loading the filter*/ + /*! when set indicates the filter does not take part of dynamic filter chain resolution and can only be used by explicitly loading the filter + A filter with this flag and a \ref __gf_filter_register.reconfigure_output callback set will be checked when loading a chain for PID property adaptation*/ GF_FS_REG_EXPLICIT_ONLY = 1<<3, /*! when set ignores the filter weight during link resolution - this is typically needed by decoders requiring a specific reframing so that the weight of the reframer+decoder is the same as the weight of other decoders*/ GF_FS_REG_HIDE_WEIGHT = 1<<4, @@ -2343,6 +2510,40 @@ GF_FS_REG_CUSTOM = 0x40000000, } GF_FSRegisterFlags; +/*! Filter class type hint - these are only informative, used to generate documentation - the order of the enum corresponds to order of section in wki*/ +typedef enum +{ + /*! unspecified class type */ + GF_FS_CLASS_UNSPECIFIED = 0, + /*! filter is a stream manipulation tool */ + GF_FS_CLASS_STREAM, + /*! filter is a multimedia input/input tool */ + GF_FS_CLASS_MM_IO, + /*! filter is a file/network/protocols input/output tool */ + GF_FS_CLASS_NETWORK_IO, + /*! filter is a text and subtitle tool */ + GF_FS_CLASS_SUBTITLE, + /*! filter is a raw audio/video tool */ + GF_FS_CLASS_AV, + /*! filter is a generic tool */ + GF_FS_CLASS_TOOL, + /*! filter is a cryptographic tool */ + GF_FS_CLASS_CRYPTO, + /*! filter is a bitstream framing/unframing tool */ + GF_FS_CLASS_FRAMING, + /*! filter is a demultiplexer */ + GF_FS_CLASS_DEMULTIPLEXER, + /*! filter is a multiplexer */ + GF_FS_CLASS_MULTIPLEXER, + /*! filter is a decoder */ + GF_FS_CLASS_DECODER, + /*! filter is an encoder */ + GF_FS_CLASS_ENCODER, + /*! any value above this is considered as unknown */ + GF_FS_CLASS_LAST_DEFINED, + +} GF_ClassTypeHint; + /*! The filter register. Registries are loaded once at the start of the session and shall never be modified after that. If capabilities need to be changed for a specific filter, use \ref gf_filter_override_caps*/ struct __gf_filter_register @@ -2433,7 +2634,8 @@ */ Bool (*process_event)(GF_Filter *filter, const GF_FilterEvent *evt); - /*! optional - Called whenever an output PID needs format renegotiation. If not set, a filter chain will be loaded to solve the negotiation + /*! optional - Called whenever an output PID needs format renegotiation. If not set, a filter chain will be loaded to solve the negotiation, checking for filters with + this function set and with reconfigurable caps matching the negotiated set of properties. \param filter the target filter \param PID the filter output PID being reconfigured @@ -2463,7 +2665,10 @@ /*! for filters having the same match of input capabilities for a PID, the filter with priority at the lowest value will be used \note Scalable decoders should use high values, so that they are only selected when enhancement layers are present*/ - u8 priority; + s16 priority; + + /*! hint class type for doc generation, one of GF_ClassTypeHint */ + u8 hint_class_type; /*! optional for dynamic filter registries. Dynamic registries may declare any number of registries. The register_free function will be called to cleanup any allocated memory @@ -2573,6 +2778,18 @@ */ const char *gf_filter_get_name(GF_Filter *filter); +/*! Gets filter status +\param filter target filter +\return status string of the filter if it exists, else empty string +*/ +const char *gf_filter_get_status(GF_Filter *filter); + +/*! Gets bytes processed by filter +\param filter target filter +\return the nb_bytes_processed field of the filter +*/ +u64 gf_filter_get_bytes_done(GF_Filter *filter); + /*! Makes the filter sticky. A sticky filter is not removed when all its input PIDs are disconnected. Typically used by the player \param filter target filter */ @@ -2599,7 +2816,7 @@ /*! Asks task reschedule for a given delay. There is no guarantee that the task will be recalled at exactly the desired delay The function can be called several times while in process, the smallest reschedule time will be kept. - + \param filter target filter \param us_until_next number of microseconds to wait before recalling this task */ @@ -2623,6 +2840,9 @@ /*! Sets callback function on source filter setup failure + + A filter with a non-NULL callback will never get destroyed by internal filter session logic, even if it no longer has valid connections. This ensures that a filter loading another filter can be sure this filter is a valid object or has failed to setup. Setting the callback to NULL may trigger the filter removal if needed, hence access to the filter should not happen after reseting the callback. + \param filter target filter \param source_filter the source filter to monitor \param on_setup_error callback function to call upon source setup error - the callback can return GF_TRUE to cancel error reporting @@ -2694,6 +2914,18 @@ */ GF_Filter *gf_filter_connect_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err); +/*! Connects a source to the session, copying FilterID and sourceID of this filter source.. +\note The loaded filter will not connect to the calling filter, and will have the same ID, subsession_id and source_id as the first source of the filter + +\param filter the target filter +\param url url of source to connect to, with optional arguments. +\param parent_url url of parent if any +\param inherit_args if GF_TRUE, the source to connect will inherit arguments of the first source of the filter +\param err return code - can be NULL +\return the new source filter instance or NULL if error +*/ +GF_Filter *gf_filter_add_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err); + /*! Connects a destination to this filter \param filter the target filter \param url url of destination to connect to, with optional arguments. @@ -2713,6 +2945,8 @@ /*! Checks if a source filter can handle the given URL. The source filter is not loaded. +The resulting filter will not be clonable unless the `:clone` argument is passed. + \param filter the target filter \param url url of source to connect to, with optional arguments. \param parent_url url of parent if any @@ -2902,7 +3136,7 @@ /*! Trigger reconnection of output PIDs of a filter. This is needed when inserting a filter in the chain while the session is running \param filter the target filter \param for_pid reconnects only the given output PID - if NULL, reconnect all output PIDs -\return error if any +\return error if any, GF_EOS if no output pids available */ GF_Err gf_filter_reconnect_output(GF_Filter *filter, GF_FilterPid *for_pid); @@ -3188,6 +3422,19 @@ */ const char *gf_filter_get_description(GF_Filter *filter); +/*! used by script to set a per-instance class hint +\param filter target filter +\param class_hint theclass hint to set +\return error if any +*/ +GF_Err gf_filter_set_class_hint(GF_Filter *filter, GF_ClassTypeHint class_hint); + +/*! get a per-instance class hint +\param filter target filter +\return the filter instance description, NULL otherwise +*/ +GF_ClassTypeHint gf_filter_get_class_hint(GF_Filter *filter); + /*! used by script to set a per-instance version \param filter target filter \param new_version the new version to set @@ -3308,11 +3555,26 @@ */ GF_Err gf_filter_probe_link(GF_Filter *filter, u32 opid_idx, const char *fname, char **result_chain); +/*! Probes for possible link resolution towards a given filter description. Same as \ref gf_filter_probe_link but tests multiple links + +The syntax for each chain is `D;P,filters` with: +- `D`: distance between the source and target, as seen by the graph resolver (some filters may hide their distance) +- `P`: priority of the chain +- `filters`: comma-separated list of filters + +\param filter target filter +\param opid_idx output pid index of target filter +\param fname textual description of filter - If a source is used, returns an error. Destination can be identified using dst=URL pattern +\param result_chain resulting chains separated by a pipe character ('|') or NULL if error. MUST be freed by caller +\return error if any +*/ +GF_Err gf_filter_probe_links(GF_Filter *filter, u32 opid_idx, const char *fname, char **result_chain); + /*! Gets list of possible destinations for this filter \param filter target filter \param opid_idx output pid index of target filter. If negative, will check destinations for any of the output pids -\param result_list resulting list as comma-separated list, or NULL if error. MUST be freed by caller -\return error if any +\param result_list resulting list as comma-separated list, or NULL if error. MUST be freed by caller. An empty chain means direct connection is possible +\return error if any, GF_FILTER_NOT_FOUND if no available destinations */ GF_Err gf_filter_get_possible_destinations(GF_Filter *filter, s32 opid_idx, char **result_list); @@ -3401,6 +3663,19 @@ */ const char *gf_filter_path_escape_colon(GF_Filter *filter, const char *path); + +/*! Tags a filter for logging + + All logs generated on a thread with a tagged filter will be marked as issued by the associated filter. + + Tagging is handled internally for most filters. The function should only be used for filters using external threads calling back into libgpac (e.g. audio thread). + Tagging (resp. untagging) should be done before (resp. after) calling libgpac + +\param filter target filter +\param is_untag if true, untags the filter otherwise tags it +*/ +void gf_filter_log_tag(GF_Filter *filter, Bool is_untag); + /*! @} */ @@ -3458,6 +3733,14 @@ */ GF_Err gf_filter_pid_raw_new(GF_Filter *filter, const char *url, const char *local_file, const char *mime_type, const char *fext, const u8 *probe_data, u32 probe_size, Bool trust_mime, GF_FilterPid **out_pid); +/*! Creates an output PID for a gmem block, send packet as FILE and set created pid to EOS +\param filter the target filter +\param url gmem URL of the data block +\param out_pid the output PID to create or update. If no referer PID, a new PID will be created otherwise the PID will be updated +\return error code if any +*/ +GF_Err gf_filter_pid_raw_gmem(GF_Filter *filter, const char *url, GF_FilterPid **out_pid); + /*! Sets a new property on an output PID for built-in property names. Setting a new property will trigger a PID reconfigure at the consumption point of the next dispatched packet. Previous properties (ones set before last packet dispatch) will still be valid. You can remove any of them using \ref gf_filter_pid_set_property with NULL property, or reset the properties with \ref gf_filter_pid_reset_properties. @@ -3491,7 +3774,7 @@ GF_Err gf_filter_pid_set_property_dyn(GF_FilterPid *PID, char *name, const GF_PropertyValue *value); /*! Sets a new info property on an output PID for built-in property names. -Similar to \ref gf_filter_pid_set_property, but infos are not copied up the chain and to not trigger PID reconfiguration. +Similar to \ref gf_filter_pid_set_property, but infos are not copied up the chain and do not trigger PID reconfiguration. First packet dispatched after calling this function will be marked, and its fetching by the consuming filter will trigger a process_event notification. If the consuming filter copies properties from source packet to output packet, the flag will be passed to such new output packet. @@ -3736,7 +4019,7 @@ /*! loss rate in per-thousand - input pid only */ u32 loss_rate; - /*! timestamp and timescale of last packet droped + /*! timestamp and timescale of last packet dropped - For input PID, set to last packet dropped - For output PID, set to maximum TS value of last packet dropped on all PID destinations */ @@ -4145,6 +4428,12 @@ */ void *gf_filter_pid_get_alias_udta(GF_FilterPid *PID); +/*! Gets the filter owning the PID +\param PID the target filter PID +\return the filter owning the PID or NULL if error +*/ +GF_Filter *gf_filter_pid_get_owner(GF_FilterPid *PID); + /*! Gets the filter owning the input PID \param PID the target filter PID \return the filter owning the PID or NULL if error @@ -4228,7 +4517,7 @@ In order to handle reordering of packets, it is possible to keep references to either packets (may block the filter chain), or packet properties. Packets shall always be dispatched in their processing order (decode order). If reordering upon reception is needed, or AU interleaving is used, a filter SHALL do the reordering. -However, packets do not have to be send in their creation order: a created packet is not assigned to PID buffers until it is sent. +However, packets do not have to be sent in their creation order: a created packet is not assigned to PID buffers until it is sent. @{ */ @@ -4339,8 +4628,7 @@ If the source packet is referenced more than once (ie more than just the caller), a new packet on the output PID is allocated with source data copied. Otherwise, the source data is assigned to the output packet. -This is typically called by filters requiring read access to data for packets using frame interfaces -\warning The cloned packet will not have any dynamic properties set. +This is typically called by filters requiring read access to data for packets using frame interfaces. \param pck_source the target source packet \param cached_pck if not NULL, will try to reuse this packet if possible (if not possible, this packet will be destroyed) @@ -4348,6 +4636,19 @@ */ GF_FilterPacket *gf_filter_pck_dangling_copy(GF_FilterPacket *pck_source, GF_FilterPacket *cached_pck); +/*! Creates a detached clone of a packet from a source packet and copy all source properties to output. + +If the source packet uses a frame interface object or has no associated data, returns a copy of the packet. +Otherwise, the source data is copied in the output packet. + +This is typically called by filters reaggregating packets on their own. + +\param pck_source the target source packet +\param cached_pck if not NULL, will try to reuse this packet if possible (if not possible, this packet will be destroyed) +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_dangling_clone(GF_FilterPacket *pck_source, GF_FilterPacket *cached_pck); + /*! Marks memory of a shared packet as non-writable. By default \ref gf_filter_pck_new_shared and \ref gf_filter_pck_new_ref allow write access to internal memory in case the packet can be cloned (single reference used). If your filter relies on the content of the shared memory for its internal state, packet must be marked as read-only to avoid later state corruption. @@ -4373,6 +4674,8 @@ /*! Sends the packet on its output PID. Packets SHALL be sent in processing order (eg, decoding order for video). However, packets don't have to be sent in their allocation order. +Packet shall not be modified after this call, as it may be discarded during the call. + \param pck the target output packet to send \return error if any */ @@ -4579,6 +4882,19 @@ */ GF_FilterSAPType gf_filter_pck_get_sap(GF_FilterPacket *pck); +/*! Sets packet switch frame flag +\param pck target packet +\param is_switch_frame switch frame flag of the packet +\return error code if any +*/ +GF_Err gf_filter_pck_set_switch_frame(GF_FilterPacket *pck, Bool is_switch_frame); + +/*! Sets packet switch frame flag +\param pck target packet +\return switch frame flag of the packet +*/ +Bool gf_filter_pck_get_switch_frame(GF_FilterPacket *pck); + /*! Sets packet video interlacing flag \param pck target packet @@ -4857,7 +5173,7 @@ Each callback is optional, but a custom filter should at least have a process callback, and a configure_pid callback if not a source filter. Custom filters do not have any arguments exposed, and cannot be selected for sink or source filters. -If your app requires custom I/Os for source or sinks, use \ref GF_FileIO. +If your app requires custom I/Os for source or sinks, use \ref osfile_grp "GF_FileIO". @{ */ @@ -4873,7 +5189,7 @@ /*! Push a new capability for a custom filter \param filter the target filter \param code the capability code - cf \ref GF_FilterCapability -\param value the capability value - cf \ref GF_FilterCapability +\param value the capability value - cf \ref GF_FilterCapability - must not be NULL unless cap is a bundle start (i.e. flags is 0) \param name the capability name - cf \ref GF_FilterCapability \param flags the capability flags - cf \ref GF_FilterCapability \param priority the capability priority - cf \ref GF_FilterCapability @@ -4924,4 +5240,3 @@ #endif #endif //_GF_FILTERS_H_ -
View file
gpac-26.02.0.tar.gz/include/gpac/id3.h
Added
@@ -0,0 +1,33 @@ + +#ifndef _GF_ID3_H_ +#define _GF_ID3_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/bitstream.h> +#include <gpac/list.h> + +typedef struct { + u32 timescale; + u64 pts; + u32 scheme_uri_length; + char* scheme_uri; + u32 value_uri_length; + char* value_uri; + u32 data_length; + u8* data; +} GF_ID3_TAG; + +GF_Err gf_id3_tag_new(GF_ID3_TAG *tag, u32 timescale, u64 pts, u8 *data, u32 data_length); +void gf_id3_tag_free(GF_ID3_TAG *tag); +GF_Err gf_id3_to_bitstream(GF_ID3_TAG *tag, GF_BitStream *bs); +GF_Err gf_id3_list_to_bitstream(GF_List *tag_list, GF_BitStream *bs); +GF_Err gf_id3_from_bitstream(GF_ID3_TAG *tag, GF_BitStream *bs); + +#ifdef __cplusplus +} +#endif + +#endif // _GF_ID3_H_ +
View file
gpac-2.4.0.tar.gz/include/gpac/ietf.h -> gpac-26.02.0.tar.gz/include/gpac/ietf.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -1392,6 +1392,8 @@ /*is zip compression used in DIMS unit ?*/ GP_RTP_DIMS_COMPRESSED = (1<<12), + /* static RTP payloadID must be used, throw error if not defined*/ + GP_RTP_PCK_FORCE_STATIC_ID = (1<<13), }; @@ -1595,12 +1597,12 @@ /*! formats the "fmtp: " attribute for the MPEG-4 generic packetizer. sdpline shall be at least 2000 char \param builder the target RTP packetizer \param payload_name name of the payload to use (profile of RFC 3640) -\param sdp_line SDP line buffer to fill +\param out_sdp_line SDP line buffer produced - must be freed by caller \param dsi decoder config of stream if any, or NULL \param dsi_size size of the decoder config \return error if any */ -GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdp_line, char *dsi, u32 dsi_size); +GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char **out_sdp_line, char *dsi, u32 dsi_size); /*! formats SDP payload name and media name \param builder the target RTP packetizer \param payload_name the buffer to fill with the payload name
View file
gpac-2.4.0.tar.gz/include/gpac/internal/avilib.h -> gpac-26.02.0.tar.gz/include/gpac/internal/avilib.h
Changed
@@ -1,8 +1,8 @@ /* * avilib.h * - * Copyright (C) Thomas Östreich - June 2001 - * multiple audio track support Copyright (C) 2002 Thomas Östreich + * Copyright (C) Thomas �streich - June 2001 + * multiple audio track support Copyright (C) 2002 Thomas �streich * * Original code: * Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> @@ -95,7 +95,7 @@ typedef struct _avisuperindex_chunk { char fcc4; u32 dwSize; // size of this chunk - u16 wLongsPerEntry; // size of each entry in aIndex array (must be 8 for us) + u16 wLongsPerEntry; // size of each entry in aIndex array (must be 4 for us) u8 bIndexSubType; // future use. must be 0 u8 bIndexType; // one of AVI_INDEX_* codes u32 nEntriesInUse; // index of first unused member in aIndex array @@ -424,4 +424,3 @@ #endif /*GPAC_DISABLE_AVILIB*/ #endif /*_GF_AVILIB_H_*/ -
View file
gpac-2.4.0.tar.gz/include/gpac/internal/bifs_dev.h -> gpac-26.02.0.tar.gz/include/gpac/internal/bifs_dev.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / BIFS codec sub-project @@ -130,6 +130,7 @@ Bool is_com_dec; Double cts_offset; + u32 tree_depth; };
View file
gpac-26.02.0.tar.gz/include/gpac/internal/ff_dmx.h
Added
@@ -0,0 +1,52 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Deniz Ugur + * Copyright (c) Motion Spell 2025 + * All rights reserved + * + * This file is part of GPAC / Media Tools sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#ifndef _GF_FF_DMX_H_ +#define _GF_FF_DMX_H_ + +#include <gpac/setup.h> + +#ifdef GPAC_HAS_FFMPEG + +#include <gpac/tools.h> +#include <gpac/filters.h> + +#include <libavformat/avformat.h> + +typedef enum +{ + GF_FFDMX_EOS = -2, + GF_FFDMX_ERR = -1, + GF_FFDMX_OK = 0, + GF_FFDMX_HAS_MORE = 1 +} GF_FFDemuxCallbackRet; + +typedef GF_FFDemuxCallbackRet (*GF_FFDemuxCallbackFn)(void *udta, AVPacket **pkt); + +GF_Err gf_filter_bind_ffdmx_callbacks(GF_Filter *filter, void *udta, GF_FFDemuxCallbackFn on_pkt); + +#endif // GPAC_HAS_FFMPEG + +#endif // _GF_FF_DMX_H_ +
View file
gpac-2.4.0.tar.gz/include/gpac/internal/ietf_dev.h -> gpac-26.02.0.tar.gz/include/gpac/internal/ietf_dev.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -494,6 +494,9 @@ GF_Err gp_rtp_builder_do_mp2t(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); GF_Err gp_rtp_builder_do_vvc(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); GF_Err gp_rtp_builder_do_opus(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +#if GPAC_ENABLE_3GPP_DIMS_RTP +GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration); +#endif #define RTP_VVC_AGG_NAL 0x1C //28 #define RTP_VVC_FRAG_NAL 0x1D //29
View file
gpac-2.4.0.tar.gz/include/gpac/internal/isomedia_dev.h -> gpac-26.02.0.tar.gz/include/gpac/internal/isomedia_dev.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -84,6 +84,8 @@ GF_ISOM_BOX_TYPE_STRI = GF_4CC( 's', 't', 'r', 'i' ), GF_ISOM_BOX_TYPE_STRD = GF_4CC( 's', 't', 'r', 'd' ), GF_ISOM_BOX_TYPE_STSG = GF_4CC( 's', 't', 's', 'g' ), + GF_ISOM_BOX_TYPE_EXTK = GF_4CC( 'e', 'x', 't', 'k' ), + GF_ISOM_BOX_TYPE_EXTL = GF_4CC( 'e', 'x', 't', 'l' ), GF_ISOM_BOX_TYPE_UDTA = GF_4CC( 'u', 'd', 't', 'a' ), GF_ISOM_BOX_TYPE_VMHD = GF_4CC( 'v', 'm', 'h', 'd' ), @@ -122,6 +124,8 @@ GF_ISOM_BOX_TYPE_AKEY = GF_4CC( 'a', 'k', 'e', 'y' ), GF_ISOM_BOX_TYPE_FLXS = GF_4CC( 'f', 'l', 'x', 's' ), + GF_ISOM_BOX_TYPE_SRAT = GF_4CC( 's', 'r', 'a', 't' ), + #ifndef GPAC_DISABLE_ISOM_FRAGMENTS /*Movie Fragments*/ GF_ISOM_BOX_TYPE_MVEX = GF_4CC( 'm', 'v', 'e', 'x' ), @@ -204,10 +208,15 @@ GF_ISOM_BOX_TYPE_AV1C = GF_4CC('a', 'v', '1', 'C'), GF_ISOM_BOX_TYPE_AV01 = GF_4CC('a', 'v', '0', '1'), + /*AVS3 Video*/ + GF_ISOM_BOX_TYPE_AV3C = GF_4CC('a', 'v', '3', 'c'), + GF_ISOM_BOX_TYPE_AVS3 = GF_4CC('a', 'v', 's', '3'), + /*WebM*/ GF_ISOM_BOX_TYPE_VPCC = GF_4CC('v', 'p', 'c', 'C'), GF_ISOM_BOX_TYPE_VP08 = GF_4CC('v', 'p', '0', '8'), GF_ISOM_BOX_TYPE_VP09 = GF_4CC('v', 'p', '0', '9'), + GF_ISOM_BOX_TYPE_VP10 = GF_4CC('v', 'p', '1', '0'), GF_ISOM_BOX_TYPE_SMDM = GF_4CC('S', 'm', 'D', 'm'), GF_ISOM_BOX_TYPE_COLL = GF_4CC('C', 'o', 'L', 'L'), @@ -215,6 +224,10 @@ GF_ISOM_BOX_TYPE_OPUS = GF_4CC('O', 'p', 'u', 's'), GF_ISOM_BOX_TYPE_DOPS = GF_4CC('d', 'O', 'p', 's'), + /*IAMF*/ + GF_ISOM_BOX_TYPE_IAMF = GF_4CC('i', 'a', 'm', 'f'), + GF_ISOM_BOX_TYPE_IACB = GF_4CC('i', 'a', 'c', 'b'), + /*LASeR extension*/ GF_ISOM_BOX_TYPE_LSRC = GF_4CC( 'l', 's', 'r', 'C' ), GF_ISOM_BOX_TYPE_LSR1 = GF_4CC( 'l', 's', 'r', '1' ), @@ -235,6 +248,13 @@ GF_ISOM_BOX_TYPE_LEVA = GF_4CC( 'l', 'e', 'v', 'a' ), GF_ISOM_BOX_TYPE_PCRB = GF_4CC( 'p', 'c', 'r', 'b' ), GF_ISOM_BOX_TYPE_EMSG = GF_4CC( 'e', 'm', 's', 'g' ), + GF_ISOM_BOX_TYPE_RSOT = GF_4CC( 'r', 's', 'o', 't' ), + + /* 23001-18 EventMessage Track */ + GF_ISOM_BOX_TYPE_EMIB = GF_4CC( 'e', 'm', 'i', 'b' ), + GF_ISOM_BOX_TYPE_EMEB = GF_4CC( 'e', 'm', 'e', 'b' ), + GF_ISOM_BOX_TYPE_EVTE = GF_4CC( 'e', 'v', 't', 'e' ), + GF_ISOM_BOX_TYPE_SILB = GF_4CC( 's', 'i', 'l', 'b' ), /*3GPP text / MPEG-4 StreamingText*/ GF_ISOM_BOX_TYPE_FTAB = GF_4CC( 'f', 't', 'a', 'b' ), @@ -304,7 +324,6 @@ GF_ISOM_BOX_TYPE_RTP = GF_4CC( 'r', 't', 'p', ' ' ), GF_ISOM_BOX_TYPE_SDP = GF_4CC( 's', 'd', 'p', ' ' ), GF_ISOM_BOX_TYPE_HINF = GF_4CC( 'h', 'i', 'n', 'f' ), - GF_ISOM_BOX_TYPE_NAME = GF_4CC( 'n', 'a', 'm', 'e' ), GF_ISOM_BOX_TYPE_TRPY = GF_4CC( 't', 'r', 'p', 'y' ), GF_ISOM_BOX_TYPE_NUMP = GF_4CC( 'n', 'u', 'm', 'p' ), GF_ISOM_BOX_TYPE_TOTL = GF_4CC( 't', 'o', 't', 'l' ), @@ -383,6 +402,8 @@ GF_ISOM_BOX_TYPE_DAC3 = GF_4CC( 'd', 'a', 'c', '3' ), GF_ISOM_BOX_TYPE_EC3 = GF_4CC( 'e', 'c', '-', '3' ), GF_ISOM_BOX_TYPE_DEC3 = GF_4CC( 'd', 'e', 'c', '3' ), + GF_ISOM_BOX_TYPE_AC4 = GF_4CC( 'a', 'c', '-', '4' ), + GF_ISOM_BOX_TYPE_DAC4 = GF_4CC( 'd', 'a', 'c', '4' ), GF_ISOM_BOX_TYPE_DVCC = GF_4CC( 'd', 'v', 'c', 'C' ), GF_ISOM_BOX_TYPE_DVVC = GF_4CC( 'd', 'v', 'v', 'C' ), GF_ISOM_BOX_TYPE_DVH1 = GF_4CC( 'd', 'v', 'h', '1' ), @@ -435,6 +456,8 @@ GF_ISOM_BOX_TYPE_IENC = GF_4CC( 'i', 'e', 'n', 'c' ), GF_ISOM_BOX_TYPE_IAUX = GF_4CC('i', 'a', 'u', 'x'), GF_ISOM_BOX_TYPE_ILCE = GF_4CC( 'i', 'l', 'c', 'e' ), + GF_ISOM_BOX_TYPE_TXLO = GF_4CC( 't', 'x', 'l', 'o' ), + GF_ISOM_BOX_TYPE_FNCH = GF_4CC( 'f', 'n', 'c', 'h' ), /* MIAF Boxes */ GF_ISOM_BOX_TYPE_CLLI = GF_4CC('c', 'l', 'l', 'i'), @@ -475,7 +498,12 @@ GF_ISOM_BOX_TYPE_IHDR = GF_4CC('i','h','d','r'), GF_ISOM_BOX_TYPE_JP = GF_4CC('j','P',' ',' '), GF_ISOM_BOX_TYPE_JP2H = GF_4CC('j','p','2','h'), + GF_ISOM_BOX_TYPE_JP2P = GF_4CC('j','p','2','p'), + GF_ISOM_BOX_TYPE_JSUB = GF_4CC('j','s','u','b'), + GF_ISOM_BOX_TYPE_ORFO = GF_4CC('o','r','f','o'), GF_ISOM_BOX_TYPE_JP2K = GF_4CC('j','p','2','k'), + GF_ISOM_BOX_TYPE_J2KH = GF_4CC('j','2','k','H'), + GF_ISOM_BOX_TYPE_CDEF = GF_4CC('c','d','e','f'), GF_ISOM_BOX_TYPE_JPEG = GF_4CC('j','p','e','g'), GF_ISOM_BOX_TYPE_PNG = GF_4CC('p','n','g',' '), @@ -496,6 +524,7 @@ GF_QT_BOX_TYPE_FRMA = GF_4CC('f','r','m','a'), GF_QT_BOX_TYPE_TMCD = GF_4CC('t','m','c','d'), GF_QT_BOX_TYPE_NAME = GF_4CC('n','a','m','e'), + GF_QT_BOX_TYPE_MEAN = GF_4CC('m','e','a','n'), GF_QT_BOX_TYPE_TCMI = GF_4CC('t','c','m','i'), GF_QT_BOX_TYPE_FIEL = GF_4CC('f','i','e','l'), GF_QT_BOX_TYPE_GAMA = GF_4CC('g','a','m','a'), @@ -565,6 +594,16 @@ GF_ISOM_BOX_TYPE_GDAT = GF_4CC( 'g', 'd', 'a', 't' ), GF_ISOM_BOX_TYPE_KEYS = GF_4CC( 'k', 'e', 'y', 's' ), + + + GF_GPAC_BOX_TYPE_SREF = GF_4CC( 'G', 'P', 'S', 'R' ), + GF_ISOM_BOX_TYPE_CDRF = GF_4CC( 'c', 'd', 'r', 'f' ), + + GF_ISOM_BOX_TYPE_CMOV = GF_4CC( '!', 'm', 'o', 'v' ), + GF_ISOM_BOX_TYPE_CMOF = GF_4CC( '!', 'm', 'o', 'f' ), + GF_ISOM_BOX_TYPE_CSIX = GF_4CC( '!', 's', 'i', 'x' ), + GF_ISOM_BOX_TYPE_CSSX = GF_4CC( '!', 's', 's', 'x' ), + }; enum @@ -586,6 +625,8 @@ #define GF_ISOM_BS_COOKIE_QT_CONV (1<<2) #define GF_ISOM_BS_COOKIE_CLONE_TRACK (1<<3) #define GF_ISOM_BS_COOKIE_IN_UDTA (1<<4) +#define GF_ISOM_BS_COOKIE_NO_DECOMP (1<<5) +#define GF_ISOM_BS_COOKIE_NO_MABR_PATCH (1<<6) #ifndef GPAC_DISABLE_ISOM @@ -598,7 +639,10 @@ //internal flags (up to 16) //if flag is set, position checking of child boxes is ignored #define GF_ISOM_ORDER_FREEZE 1 +//if flag is set, box uses deflate compression #define GF_ISOM_BOX_COMPRESSED 2 +//if flag is set, box dump will skip size info +#define GF_ISOM_DUMP_SKIP_SIZE 4 /*the default size is 64, cause we need to handle large boxes... @@ -685,6 +729,7 @@ GF_Err gf_isom_box_array_read(GF_Box *s, GF_BitStream *bs); GF_Err gf_isom_box_parse_ex(GF_Box **outBox, GF_BitStream *bs, u32 parent_type, Bool is_root_box, u64 parent_size); +GF_Err gf_isom_parse_root_box(GF_Box **outBox, GF_BitStream *bs, u32 *boxType, u64 *bytesExpected, Bool progressive_mode); //writes box header - shall be called at the beginning of each xxxx_Write function //this function is not factorized in order to let box serializer modify box type before writing @@ -884,7 +929,7 @@ GF_ISOFile *mov; Bool mvex_after_traks; - Bool has_cmvd; + u32 has_cmvd; //for compressed mov, stores the difference between compressed and uncompressed payload s32 compressed_diff; //for compressed mov, indicates the file offset of the moov box start @@ -927,6 +972,16 @@ u32 track_group_id; } GF_TrackGroupTypeBox; +typedef struct { + GF_ISOM_FULL_BOX + u32 referenced_track_ID; + u32 referenced_handler_type; + u32 media_timescale; + char *location; +} GF_ExternalTrackLocationBox; + + + typedef struct { GF_ISOM_BOX @@ -938,9 +993,11 @@ /*meta box if any*/ struct __tag_meta_box *meta; GF_TrackGroupBox *groups; + /*external track location*/ + GF_ExternalTrackLocationBox *extl; GF_Box *Aperture; - + GF_MovieBox *moov; /*private for media padding*/ u32 padding_bytes; @@ -1181,7 +1238,7 @@ } GF_TimeToSampleBox; -/*TO CHECK - it could be reasonnable to only use 16bits for both count and offset*/ +/*TO CHECK - it could be reasonable to only use 16bits for both count and offset*/ typedef struct { u32 sampleCount; @@ -1204,6 +1261,7 @@ u32 r_FirstSampleInEntry; s32 max_cts_delta; + s32 min_neg_cts_offset; //u32 sample_num_max_cts_delta; } GF_CompositionOffsetBox; @@ -1328,7 +1386,7 @@ u32 avgBitrate; } GF_BitRateBox; -GF_BitRateBox *gf_isom_sample_entry_get_bitrate(GF_SampleEntryBox *ent, Bool create); +GF_BitRateBox *gf_isom_sample_entry_get_bitrate_box(GF_SampleEntryBox *ent, Bool create); typedef struct { @@ -1507,6 +1565,14 @@ GF_VPConfig *config; } GF_VPConfigurationBox; + +typedef struct +{ + GF_ISOM_BOX + GF_AVS3VConfig *config; +} GF_AVS3VConfigurationBox; + + typedef struct { GF_ISOM_FULL_BOX @@ -1561,10 +1627,38 @@ typedef struct { GF_ISOM_BOX + u32 signature; +} GF_JP2SignatureBox; + +typedef struct +{ + GF_ISOM_BOX GF_J2KImageHeaderBox *ihdr; GF_ColourInformationBox *colr; } GF_J2KHeaderBox; +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *compatible_brands; +} GF_JP2ProfileBox; + +typedef struct +{ + GF_ISOM_BOX + u8 horizontal_sub; + u8 vertical_sub; + u8 horizontal_offset; + u8 vertical_offset; +} GF_JP2SubSamplingBox; + +typedef struct +{ + GF_ISOM_BOX + u8 original_fieldcount; + u8 original_fieldorder; +} GF_JP2OriginalFormatBox; + typedef struct __full_video_sample_entry { GF_ISOM_VISUAL_SAMPLE_ENTRY @@ -1585,6 +1679,8 @@ GF_AV1ConfigurationBox *av1_config; /*vp8-9 extension*/ GF_VPConfigurationBox *vp_config; + /*avs3 video extension*/ + GF_AVS3VConfigurationBox *avs3v_config; /*jp2k extension*/ GF_J2KHeaderBox *jp2h; /*dolbyvision extension*/ @@ -1658,6 +1754,17 @@ GF_AC3Config cfg; } GF_AC3ConfigBox; +typedef struct +{ + GF_ISOM_BOX + GF_AC4Config cfg; +} GF_AC4ConfigBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 sampling_rate; +} GF_SamplingRateBox; typedef struct @@ -1721,6 +1828,13 @@ typedef struct { GF_ISOM_BOX + GF_IAConfig *cfg; +} GF_IAConfigurationBox; + + +typedef struct +{ + GF_ISOM_BOX GF_DTSConfig cfg; } GF_DTSSpecificBox; @@ -1771,6 +1885,9 @@ //for AC3/EC3 audio GF_AC3ConfigBox *cfg_ac3; + //for AC4 audio + GF_AC4ConfigBox *cfg_ac4; + //for AC3/EC3 audio GF_TrueHDConfigBox *cfg_mlp; @@ -1783,6 +1900,9 @@ //for FLAC GF_FLACConfigBox *cfg_flac; + //for IAMF + GF_IAConfigurationBox *cfg_iamf; + //for generic audio sample entry //box type as specified in the file (not this box's type!!) u32 EntryType; @@ -2028,16 +2148,16 @@ GF_Err gf_isom_add_subsample_info(GF_SubSampleInformationBox *sub_samples, u32 sampleNumber, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); #endif -/* Use to relate the composition and decoding timeline when signed composition is used*/ +/* Use to relate the composition and decoding timeline when signed composition is used */ typedef struct { GF_ISOM_FULL_BOX - s32 compositionToDTSShift; - s32 leastDecodeToDisplayDelta; - s32 greatestDecodeToDisplayDelta; - s32 compositionStartTime; - s32 compositionEndTime; + s64 compositionToDTSShift; + s64 leastDecodeToDisplayDelta; + s64 greatestDecodeToDisplayDelta; + s64 compositionStartTime; + s64 compositionEndTime; } GF_CompositionToDecodeBox; typedef struct @@ -2086,6 +2206,7 @@ u64 moof_start; u64 mdat_end; u64 first_dts; + u8 is_predicted_offset; } GF_TrafMapEntry; typedef struct @@ -2096,6 +2217,25 @@ u32 r_cur_sample, r_cur_idx; } GF_TrafToSampleMap; +void gf_isom_push_mdat_end(GF_ISOFile *mov, u64 mdat_end, Bool is_pred); + +typedef struct +{ + u32 sampleID; + u32 nb_refs; + u32 *sample_refs; +} GF_SampleRefEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *entries; + u32 id_shift; + + //for cdrf + u32 cdrf_entries, cdrf_cache_size; +} GF_SampleReferences; + typedef struct { GF_ISOM_BOX @@ -2112,6 +2252,7 @@ GF_DegradationPriorityBox *DegradationPriority; GF_PaddingBitsBox *PaddingBits; GF_SampleDependencyTypeBox *SampleDep; + GF_SampleReferences *SampleRefs; GF_TrafToSampleMap *traf_map; @@ -2208,6 +2349,9 @@ u32 group_id; u32 entity_id_count; u32 *entity_ids; + u8 *data; + u32 data_len; + } GF_EntityToGroupTypeBox; typedef struct @@ -2627,7 +2771,7 @@ Bool cannot_use_default; GF_ISOTrackID inherit_from_traf_id; - + GF_TrackFragmentRandomAccessBox *tfra; } GF_TrackExtendsBox; @@ -2663,6 +2807,7 @@ //temp storage of prft box GF_ISOTrackID reference_track_ID; u64 ntp, timestamp; + u32 prft_at_mux; //emsg to inject before moof, not part of the moof hierarchy ! GF_List *emsgs; @@ -2707,6 +2852,14 @@ u64 baseMediaDecodeTime; } GF_TFBaseMediaDecodeTimeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 original_duration; + u32 elapsed_duration; +} GF_TFOriginalDurationBox; + typedef struct { GF_ISOM_BOX @@ -2715,6 +2868,7 @@ /*keep a pointer to default flags*/ GF_TrackExtendsBox *trex; GF_SampleDependencyTypeBox *sdtp; + GF_SampleReferences *SampleRefs; // GF_SubSampleInformationBox *subs; GF_List *sub_samples; @@ -2734,6 +2888,7 @@ /*when data caching is on*/ u32 DataCache; GF_TFBaseMediaDecodeTimeBox *tfdt; + GF_TFOriginalDurationBox *rsot; u64 moof_start_in_bs; #ifdef GF_ENABLE_CTRN @@ -3088,7 +3243,7 @@ typedef struct { GF_ISOM_FULL_BOX - u32 reserved; + u32 locale; u8 *data; u32 dataSize; Bool qt_style; @@ -3098,6 +3253,9 @@ { GF_ISOM_BOX GF_DataBox *data; + + GF_NameBox *mean; + GF_NameBox *name; } GF_ListItemBox; typedef struct @@ -3257,19 +3415,44 @@ } GF_PcrInfoBox; +/*used for both 'emsg' and 'emib' boxes*/ typedef struct { GF_ISOM_FULL_BOX char *scheme_id_uri; char *value; u32 timescale; - u64 presentation_time_delta; + s64 presentation_time_delta; u32 event_duration; u32 event_id; u8 *message_data; u32 message_data_size; } GF_EventMessageBox; +typedef struct +{ + char *scheme_id_uri; + char *value; + u8 reserved : 7; + u8 atleast_once_flag : 1; +} GF_SchemeIdListBoxEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 number_of_schemes; + GF_List *schemes; /*list of GF_SchemeIdListBoxEntry*/ + u8 reserved : 7; + u8 other_schemes_flag : 1; +} GF_SchemeIdListBox; + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + GF_BitRateBox *btrt; + GF_SchemeIdListBox *silb; +} GF_EventMessageSampleEntryBox; + #ifndef GPAC_DISABLE_ISOM_ADOBE @@ -3419,6 +3602,12 @@ u8 *data; } GF_DefaultSampleGroupDescriptionEntry; +/*AV1 Switching Entry - Switching Frames*/ +typedef struct +{ + int unused; // C requires that a struct or union has at least one member +} GF_AV1SwitchingEntry; + /*VisualRandomAccessEntry - 'rap ' type*/ typedef struct { @@ -3850,6 +4039,29 @@ u32 aux_info_parameter; } GF_AuxiliaryInfoPropertyBox; + +typedef struct { + GF_ISOM_FULL_BOX + char* font_family; + char* font_style; + char* font_weight; +} GF_FontCharacteristicsPropertyBox; + + +typedef struct { + GF_ISOM_FULL_BOX + u32 reference_width; + u32 reference_height; + s32 x; + s32 y; + u32 width; + u32 height; + s16 font_size; + char* direction; + char* writing_mode; +} GF_TextLayoutPropertyBox; + + typedef struct { GF_ISOM_FULL_BOX @@ -4007,9 +4219,10 @@ /*this is the DataHandler structure each data handler has its own bitstream*/ #define GF_ISOM_BASE_DATA_HANDLER \ - u8 type; \ u64 curPos; \ - u8 mode; \ + u8 type; \ + u8 mode; \ + u8 use_blob; \ GF_BitStream *bs;\ u64 last_read_offset;\ char *szName; @@ -4048,12 +4261,12 @@ void gf_isom_datamap_del(GF_DataMap *ptr); GF_Err gf_isom_datamap_open(GF_MediaBox *minf, u32 dataRefIndex, u8 Edit); void gf_isom_datamap_close(GF_MediaInformationBox *minf); -u32 gf_isom_datamap_get_data(GF_DataMap *map, u8 *buffer, u32 bufferLength, u64 Offset); +u32 gf_isom_datamap_get_data(GF_DataMap *map, u8 *buffer, u32 bufferLength, u64 Offset, GF_BlobRangeStatus *range_status); /*File-based data map*/ GF_DataMap *gf_isom_fdm_new(const char *sPath, u8 mode); void gf_isom_fdm_del(GF_FileDataMap *ptr); -u32 gf_isom_fdm_get_data(GF_FileDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset); +u32 gf_isom_fdm_get_data(GF_FileDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset, GF_BlobRangeStatus *range_status); #ifndef GPAC_DISABLE_ISOM_WRITE GF_DataMap *gf_isom_fdm_new_temp(const char *sTempPath); @@ -4247,7 +4460,7 @@ GF_Err gf_isom_parse_movie_boxes(GF_ISOFile *mov, u32 *boxType, u64 *bytesMissing, Bool progressive_mode); GF_ISOFile *gf_isom_new_movie(); /*Movie and Track access functions*/ -GF_TrackBox *gf_isom_get_track_from_file(GF_ISOFile *the_file, u32 trackNumber); +GF_TrackBox *gf_isom_get_track_box(GF_ISOFile *the_file, u32 trackNumber); GF_TrackBox *gf_isom_get_track(GF_MovieBox *moov, u32 trackNumber); GF_TrackBox *gf_isom_get_track_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID); GF_TrackBox *gf_isom_get_track_from_original_id(GF_MovieBox *moov, u32 originalID, u32 originalFile); @@ -4283,6 +4496,8 @@ GF_Err Media_FindDataRef(GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex); Bool Media_IsSelfContained(GF_MediaBox *mdia, u32 StreamDescIndex); +Bool gf_isom_datamap_top_level_box_avail(GF_DataMap *map); + typedef enum { ISOM_DREF_MIXED = 0, @@ -4337,7 +4552,6 @@ GF_Err isom_on_block_out(void *cbk, u8 *data, u32 block_size); GF_Err FlushCaptureMode(GF_ISOFile *movie); -GF_Err CanAccessMovie(GF_ISOFile *movie, GF_ISOOpenMode Mode); GF_ISOFile *gf_isom_create_movie(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir); GF_Err gf_isom_insert_moov(GF_ISOFile *file); @@ -4411,7 +4625,7 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ Bool gf_isom_is_identical_sgpd(void *ptr1, void *ptr2, u32 grouping_type); -void sgpd_del_entry(u32 grouping_type, void *entry); +void sgpd_del_entry(u32 grouping_type, void *entry, Bool is_opaque); /*return type is either GF_DefaultSampleGroupDescriptionEntry if opaque sample group, or the structure associated with the grouping type*/ void *gf_isom_get_sample_group_info_entry(GF_ISOFile *the_file, GF_TrackBox *trak, u32 grouping_type, u32 sample_description_index, u32 *default_index, GF_SampleGroupDescriptionBox **out_sgdp); @@ -4432,6 +4646,7 @@ GF_Err stsd_on_child_box(GF_Box *ptr, GF_Box *a, Bool is_rem); GF_Err hnti_on_child_box(GF_Box *hnti, GF_Box *a, Bool is_rem); GF_Err udta_on_child_box(GF_Box *ptr, GF_Box *a, Bool is_rem); +GF_Err udta_on_child_box_ex(GF_Box *s, GF_Box *a, Bool is_rem, Bool rem_same_type); GF_Err edts_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem); GF_Err stdp_box_read(GF_Box *s, GF_BitStream *bs); GF_Err stbl_on_child_box(GF_Box *ptr, GF_Box *a, Bool is_rem); @@ -4440,6 +4655,7 @@ GF_Err minf_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem); GF_Err mdia_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem); GF_Err traf_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem); +GF_Err ilst_item_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem); /*rewrites avcC based on the given esd - this destroys the esd*/ GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd); @@ -4464,9 +4680,6 @@ Bool gf_isom_is_encrypted_entry(u32 entryType); -//too export in constants -Bool gf_cenc_validate_key_info(const u8 *key_info, u32 key_info_size); - GF_Err gf_isom_add_sample_aux_info_internal(GF_TrackBox *trak, void *_traf, u32 sampleNumber, u32 aux_type, u32 aux_info, u8 *data, u32 size); @@ -4781,7 +4994,8 @@ GF_Box *gf_isom_clone_config_box(GF_Box *box); GF_Err gf_isom_box_dump(void *ptr, FILE * trace); -GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace); +GF_Err gf_isom_box_dump_ex(void *ptr, FILE * trace, Bool subtree_root); +GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace, u16 parent_internal_flags); void gf_isom_registry_disable(u32 boxCode, Bool disable); @@ -4798,9 +5012,8 @@ #ifndef GPAC_DISABLE_ISOM_DUMP -GF_Err gf_isom_box_dump_ex(void *ptr, FILE * trace, u32 box_4cc); GF_Err gf_isom_box_dump_start(GF_Box *a, const char *name, FILE * trace); -GF_Err gf_isom_box_dump_start_ex(GF_Box *a, const char *name, FILE * trace, Bool force_version); +GF_Err gf_isom_box_dump_start_ex(GF_Box *a, const char *name, FILE * trace, Bool force_version, const char *spec, const char *container); void gf_isom_box_dump_done(const char *name, GF_Box *ptr, FILE *trace); Bool gf_isom_box_is_file_level(GF_Box *s); #endif @@ -4820,4 +5033,3 @@ #endif #endif //_GF_ISOMEDIA_DEV_H_ -
View file
gpac-2.4.0.tar.gz/include/gpac/internal/m3u8.h -> gpac-26.02.0.tar.gz/include/gpac/internal/m3u8.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Pierre Souchay - Jean Le Feuvre - Romain Bouqueau - * Copyright (c) Telecom ParisTech 2010-2021 + * Copyright (c) Telecom ParisTech 2010-2025 * All rights reserved * * This file is part of GPAC @@ -55,7 +55,7 @@ typedef enum e_playlistElementType { TYPE_PLAYLIST, TYPE_MEDIA, TYPE_UNKNOWN } PlaylistElementType; -typedef enum e_playlistElementDRMMethod { DRM_NONE, DRM_AES_128, DRM_CENC } PlaylistElementDRMMethod; +typedef enum e_playlistElementDRMMethod { DRM_NONE, DRM_AES_128, DRM_CENC_CBCS, DRM_CENC_CTR } PlaylistElementDRMMethod; typedef enum _e_MediaType { MEDIA_TYPE_UNKNOWN = 0, @@ -81,11 +81,14 @@ char *video_group; char *url; char *init_segment_url; + char *main_codecs; u64 init_byte_range_start, init_byte_range_end; //informative UTC start time u64 utc_start_time; u32 discontinuity; u32 channels; + u32 *alt_bandwidths; + u32 nb_alt_bandwidths; PlaylistElementDRMMethod drm_method; char *key_uri; bin128 key_iv; @@ -110,6 +113,7 @@ */ struct s_masterPlaylist { GF_List *streams; /*Stream*/ + u32 version; int current_stream; Bool playlist_needs_refresh; Bool independent_segments, low_latency;
View file
gpac-2.4.0.tar.gz/include/gpac/internal/media_dev.h -> gpac-26.02.0.tar.gz/include/gpac/internal/media_dev.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / Media Tools sub-project @@ -230,7 +230,18 @@ typedef struct { + u8 hours, minutes, seconds; + u16 n_frames; + Float max_fps; + u8 counting_type; + Bool cnt_dropped_flag, clock_timestamp_flag; +} AVCSeiPicTimingTimecode; + +typedef struct +{ u8 pic_struct; + u8 num_clock_ts; + AVCSeiPicTimingTimecode timecodes3; /*to be eventually completed by other pic_timing members*/ } AVCSeiPicTiming; @@ -242,20 +253,26 @@ typedef struct { AVCSeiRecoveryPoint recovery_point; + //valid if num_clock_ts is set AVCSeiPicTiming pic_timing; AVCSeiItuTT35DolbyVision dovi; - /*to be eventually completed by other sei*/ -} AVCSei; + + u8 clli_data4; + u8 mdcv_data24; + u8 clli_valid, mdcv_valid; + u8 has_3d_ref_disp_info; + u8 alternative_transfer_characteristics; +} GF_SEIInfo; typedef struct { AVC_SPS sps32; /* range allowed in the spec is 0..31 */ - s8 sps_active_idx, pps_active_idx; /*currently active sps; must be initalized to -1 in order to discard not yet decodable SEIs*/ + s8 sps_active_idx, pps_active_idx; /*currently active sps; must be initialized to -1 in order to discard not yet decodable SEIs*/ AVC_PPS pps255; AVCSliceInfo s_info; - AVCSei sei; + GF_SEIInfo sei; Bool is_svc; u8 last_nal_type_parsed; @@ -271,7 +288,16 @@ u32 data_length; } SVC_Extractor; +typedef struct +{ + Bool is_whitelist; + GF_PropUIntList seis; + s32 extra_filter; //- removes the sei, + keeps the sei +} SEI_Filter; +#ifdef __cplusplus +extern "C" { +#endif /*return sps ID or -1 if error*/ s32 gf_avc_read_sps(const u8 *sps_data, u32 sps_size, AVCState *avc, u32 subseq_sps, u32 *vui_flag_pos); s32 gf_avc_read_sps_bs(GF_BitStream *bs, AVCState *avc, u32 subseq_sps, u32 *vui_flag_pos); @@ -289,7 +315,11 @@ s32 gf_avc_parse_nalu(GF_BitStream *bs, AVCState *avc); /*remove SEI messages not allowed in MP4*/ /*nota: 'buffer' remains unmodified but cannot be set const*/ -u32 gf_avc_reformat_sei(u8 *buffer, u32 nal_size, Bool isobmf_rewrite, AVCState *avc); +u32 gf_avc_reformat_sei(u8 *buffer, u32 nal_size, Bool isobmf_rewrite, AVCState *avc, SEI_Filter *sei_filter); +#ifdef __cplusplus +} +#endif + #ifndef GPAC_DISABLE_AV_PARSERS @@ -311,6 +341,8 @@ Bool remove_video_info; //if set timing info is removed Bool remove_vui_timing_info; + //if set pic_struct is enabled + Bool enable_pic_struct; //new fullrange, -1 to use info from bitstream s32 fullrange; //new vidformat flag, -1 to use info from bitstream @@ -367,7 +399,11 @@ { u32 num_negative_pics; u32 num_positive_pics; - s32 delta_poc16; + s32 delta_poc32; + u8 used_by_curr_pic32; + u32 modif_idx_l032; + u32 modif_idx_l132; + u8 modif_flag_l0, modif_flag_l1; } HEVC_ReferencePictureSets; typedef struct @@ -392,7 +428,7 @@ u32 bitsSliceSegmentAddress; u32 num_short_term_ref_pic_sets, num_long_term_ref_pic_sps; - HEVC_ReferencePictureSets rps64; + HEVC_ReferencePictureSets rps65; Bool aspect_ratio_info_present_flag, long_term_ref_pics_present_flag, temporal_mvp_enable_flag, sample_adaptive_offset_enabled_flag; @@ -448,7 +484,7 @@ Bool deblocking_filter_control_present_flag, pic_disable_deblocking_filter_flag, pic_scaling_list_data_present_flag; u32 beta_offset_div2, tc_offset_div2, log2_parallel_merge_level_minus2; - + Bool curr_pic_ref_enabled_flag; } HEVC_PPS; typedef struct RepFormat @@ -491,8 +527,13 @@ u32 dimension_idMAX_LHVC_LAYERS16; u32 layer_id_in_nuhMAX_LHVC_LAYERS; u32 layer_id_in_vpsMAX_LHVC_LAYERS; - - u8 num_profile_tier_level, num_output_layer_sets; + u8 num_direct_ref_layers64; + u8 num_ref_list_layers64; + u8 num_profile_tier_level, num_output_layer_sets, default_ref_layers_active_flag; + u8 id_direct_ref_layers64MAX_LHVC_LAYERS; + u8 layer_idx_in_vpsMAX_LHVC_LAYERS; + u8 sub_layers_vps_max_minus1MAX_LHVC_LAYERS; + u8 max_tid_il_ref_pics_plus1MAX_LHVC_LAYERSMAX_LHVC_LAYERS; u32 profile_level_tier_idxMAX_LHVC_LAYERS; HEVC_ProfileTierLevel ext_ptlMAX_LHVC_LAYERS; @@ -509,18 +550,12 @@ Bool necessary_layers_flagMAX_LHVC_LAYERSMAX_LHVC_LAYERS; u8 LayerSetLayerIdListMAX_LHVC_LAYERSMAX_LHVC_LAYERS; u8 LayerSetLayerIdListMaxMAX_LHVC_LAYERS; //the highest value in LayerSetLayerIdListi + u8 max_one_active_ref_layer_flag; } HEVC_VPS; typedef struct { - AVCSeiRecoveryPoint recovery_point; - AVCSeiPicTiming pic_timing; - AVCSeiItuTT35DolbyVision dovi; -} HEVC_SEI; - -typedef struct -{ - u8 nal_unit_type; + u8 nal_unit_type, layer_id, temporal_id; u32 frame_num, poc_lsb, slice_type, header_size_with_emulation; s32 redundant_pic_cnt; @@ -545,6 +580,13 @@ HEVC_SPS *sps; HEVC_PPS *pps; + + HEVC_ReferencePictureSets *st_rps; + u32 num_ref_idx_l0_active, num_ref_idx_l1_active; + u32 nb_lt_ref_pics; + + u32 nb_reference_pocs; + s32 reference_pocs30; } HEVCSliceInfo; typedef struct _hevc_state @@ -555,23 +597,19 @@ //all other vars set by parser HEVC_SPS sps16; /* range allowed in the spec is 0..15 */ - s8 sps_active_idx; /*currently active sps; must be initalized to -1 in order to discard not yet decodable SEIs*/ + s8 sps_active_idx; /*currently active sps; must be initialized to -1 in order to discard not yet decodable SEIs*/ HEVC_PPS pps64; HEVC_VPS vps16; HEVCSliceInfo s_info; - HEVC_SEI sei; + GF_SEIInfo sei; //-1 or the value of the vps/sps/pps ID of the nal just parsed s32 last_parsed_vps_id; s32 last_parsed_sps_id; s32 last_parsed_pps_id; - - u8 clli_data4; - u8 mdcv_data24; - u8 clli_valid, mdcv_valid; } HEVCState; typedef struct hevc_combine{ @@ -605,6 +643,7 @@ /*parses HEVC SEI and fill state accordingly*/ void gf_hevc_parse_sei(char* buffer, u32 nal_size, HEVCState *hevc); +u32 gf_hevc_reformat_sei(char* buffer, u32 nal_size, Bool isobmf_rewrite, SEI_Filter *sei_filter); @@ -629,7 +668,7 @@ u8 ref_pic_typeVVC_MAX_REF_PICS; // u32 ref_pic_idVVC_MAX_REF_PICS; -// s32 pocVVC_MAX_REF_PICS; + s32 poc_deltaVVC_MAX_REF_PICS; // u32 nb_active_pics; // u8 delta_poc_msb_presentVVC_MAX_REF_PICS; // s32 delta_poc_msb_cycle_ltVVC_MAX_REF_PICS; @@ -804,18 +843,23 @@ VVC_RefPicList ph_rpl2; s32 ph_rpl_idx2; - //slive RPL state + //slice RPL state VVC_RefPicList rpl2; s32 rpl_idx2; + u32 num_ref_idx_active2; //slice header size in bytes u32 payload_start_offset; + + u32 nb_lt_or_il_pics; + u32 nb_reference_pocs; + u32 reference_pocs17; } VVCSliceInfo; /*TODO once we add HLS parsing (FDIS) */ typedef struct _vvc_state { - s8 sps_active_idx; /*currently active sps; must be initalized to -1 in order to discard not yet decodable SEIs*/ + s8 sps_active_idx; /*currently active sps; must be initialized to -1 in order to discard not yet decodable SEIs*/ //-1 or the value of the vps/sps/pps ID of the nal just parsed s32 last_parsed_vps_id; @@ -829,21 +873,26 @@ VVCSliceInfo s_info; + GF_SEIInfo sei; + //0: minimal parsing, used by most tools. Slice header and picture header are skipped //1: full parsing, error check: used to retrieve end of slice header //2: full parsing, no error check (used by dumpers) u32 parse_mode; - - - u8 clli_data4; - u8 mdcv_data24; - u8 clli_valid, mdcv_valid; } VVCState; +#ifdef __cplusplus +extern "C" { +#endif s32 gf_vvc_parse_nalu_bs(GF_BitStream *bs, VVCState *vvc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); void gf_vvc_parse_sei(char* buffer, u32 nal_size, VVCState *vvc); +u32 gf_vvc_reformat_sei(char *buffer, u32 nal_size, Bool isobmf_rewrite, SEI_Filter *sei_filter); Bool gf_vvc_slice_is_ref(VVCState *vvc); s32 gf_vvc_parse_nalu(u8 *data, u32 size, VVCState *vvc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); +#ifdef __cplusplus +} +#endif + void gf_vvc_parse_ps(GF_VVCConfig* hevccfg, VVCState* vvc, u32 nal_type); @@ -857,7 +906,10 @@ GF_Err gf_vp9_parse_sample(GF_BitStream *bs, GF_VPConfig *vp9_cfg, Bool *key_frame, u32 *FrameWidth, u32 *FrameHeight, u32 *renderWidth, u32 *renderHeight); GF_Err gf_vp9_parse_superframe(GF_BitStream *bs, u64 ivf_frame_size, u32 *num_frames_in_superframe, u32 frame_sizesVP9_MAX_FRAMES_IN_SUPERFRAME, u32 *superframe_index_size); - +typedef struct +{ + GF_AC4Config *config; +} AC4State; #define AV1_MAX_TILE_ROWS 64 #define AV1_MAX_TILE_COLS 64 @@ -880,7 +932,7 @@ { Bool is_first_frame; Bool seen_frame_header, seen_seq_header; - Bool key_frame, show_frame; + Bool key_frame, switch_frame, show_frame; AV1FrameType frame_type; GF_List *header_obus, *frame_obus; /*GF_AV1_OBUArrayEntry*/ AV1Tile tilesAV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS; @@ -986,10 +1038,9 @@ /*layer sizes for AVIF a1lx*/ u32 layer_size4; - - u8 clli_data4; - u8 mdcv_data24; - u8 clli_valid, mdcv_valid; + //if set to ono, only parse OBU_METADATA and skip the rest. If OBU_METADAT is present, the value is set to 2 + u32 parse_metadata_filter; + GF_SEIInfo sei; //set to one if a temporal delim is found when calling aom_av1_parse_temporal_unit_from_section5 u8 has_temporal_delim; @@ -1033,6 +1084,83 @@ u64 gf_av1_leb128_write(GF_BitStream *bs, u64 value); GF_Err gf_av1_parse_obu_header(GF_BitStream *bs, ObuType *obu_type, Bool *obu_extension_flag, Bool *obu_has_size_field, u8 *temporal_id, u8 *spatial_id); +typedef struct +{ + Bool seen_valid_iamf_seq_header; + Bool seen_first_frame; + Bool previous_obu_is_descriptor; + + // Track state while parsing temporal units. + Bool found_full_temporal_unit; + Bool seen_first_obu_in_temporal_unit; + int num_audio_frames_in_temporal_unit; + // True when enough samples have been seen to determine the pre-skip. + Bool pre_skip_is_finalized; + u64 previous_num_samples_to_trim_at_start; + u64 num_samples_to_trim_at_end; + + Bool cache_descriptor_obus; + GF_List *descriptor_obus, *temporal_unit_obus; /*GF_IamfObu*/ +} IamfStateFrame; + +typedef struct +{ + // Determined based on Sequence Header OBU. + u8 primary_profile; + u8 additional_profile; + // Determined based on Codec Config OBU. + u32 codec_id; + int num_samples_per_frame; + int sample_size; + int sample_rate; + s16 audio_roll_distance; + // Determined based on Audio Element OBUs. + int total_substreams; + // Determined based on the first Temporal Unit. + Bool bitstream_has_temporal_delimiters; + // Determined based on the initial Temporal Units. Only valid when `pre_skip_is_finalized`. + u64 pre_skip; + + /*frame parsing state*/ + IamfStateFrame frame_state; + + /* The temporal units (audio frame + parameter block OBUs) are written to this bitstream*/ + GF_BitStream *bs; + + u8 *temporal_unit_obus; + u32 temporal_unit_obus_alloc; + + /*IAMF config record - shall not be null when parsing - this is NOT destroyed by gf_iamf_reset_state(state, GF_TRUE) */ + GF_IAConfig *config; +} IAMFState; + +/*parses one IAMF OBU +\param bs bitstream object +\param obu_type OBU type +\param obu_size As an input the size of the input OBU (needed when obu_size is not coded). As an output the coded obu_size value. +\param state State of the frame parser +*/ +GF_Err gf_iamf_parse_obu(GF_BitStream *bs, IamfObuType *obu_type, u64 *obu_size, IAMFState *state); + +GF_Err aom_iamf_parse_temporal_unit(GF_BitStream *bs, IAMFState *state); + +/*checks if the input is an IAMF bitstream +\param bs bitstream object +*/ +Bool gf_media_probe_iamf(GF_BitStream *bs); + +/*! init IAMF frame parsing state +\param state the frame parser +*/ +void gf_iamf_init_state(IAMFState *state); + +/*! reset IAMF frame parsing state - this does not destroy the structure. +\param state the frame parser +\param is_destroy if TRUE, destroy the cached descriptor OBUs +*/ +void gf_iamf_reset_state(IAMFState *state, Bool is_destroy); + + /*! OPUS packet header*/ typedef struct { @@ -1122,7 +1250,7 @@ u32 InterleaveGroupID, u8 InterleaveGroupPriority); -void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, u32 w, u32 h, s32 tx, s32 ty, s16 l, u32 max_w, u32 max_h, char *tx3g_base64); +void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char **out_sdp_line, u32 w, u32 h, s32 tx, s32 ty, s16 l, u32 max_w, u32 max_h, char *tx3g_base64); #endif @@ -1133,7 +1261,7 @@ typedef struct _webvtt_sample GF_WebVTTSample; GF_WebVTTParser *gf_webvtt_parser_new(); -GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE *vtt_file, s32 unicode_type, Bool is_srt, +GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE **vtt_file, s32 unicode_type, Bool is_srt, void *user, GF_Err (*report_message)(void *, GF_Err, char *, const char *), void (*on_sample_parsed)(void *, GF_WebVTTSample *), void (*on_header_parsed)(void *, const char *)); @@ -1143,6 +1271,7 @@ void gf_webvtt_parser_restart(GF_WebVTTParser *parser); GF_Err gf_webvtt_parser_parse_payload(GF_WebVTTParser *parser, u64 start, u64 end, const char *vtt_pre, const char *vtt_cueid, const char *vtt_settings); GF_Err gf_webvtt_parser_flush(GF_WebVTTParser *parser); +GF_Err gf_webvtt_parser_parse_ext(GF_WebVTTParser *parser, FILE *ext_file, Bool in_eos); #include <gpac/webvtt.h> void gf_webvtt_parser_cue_callback(GF_WebVTTParser *parser, void (*on_cue_read)(void *, GF_WebVTTCue *), void *udta); @@ -1182,6 +1311,22 @@ /*build isobmf dec info from sequence header+ephdr (only seq hdr is parsed, only advanced profile is supprted) */ GF_Err gf_media_vc1_seq_header_to_dsi(const u8 *seq_hdr, u32 seq_hdr_len, u8 **dsi, u32 *dsi_size); +#ifndef GPAC_DISABLE_AV_PARSERS +typedef struct _gf_sei_loader GF_SEILoader; +GF_SEILoader *gf_sei_loader_new(); +void gf_sei_loader_del(GF_SEILoader *sei); +GF_Err gf_sei_init_from_pid(GF_SEILoader *sei, GF_FilterPid *pid); +GF_Err gf_sei_init_from_avc(GF_SEILoader *sei, AVCState *avc); +GF_Err gf_sei_init_from_hevc(GF_SEILoader *sei, HEVCState *hevc); +GF_Err gf_sei_init_from_vvc(GF_SEILoader *sei, VVCState *vvc); +GF_Err gf_sei_init_from_av1(GF_SEILoader *sei, AV1State *av1); + +GF_Err gf_sei_load_from_state(GF_SEILoader *sei, GF_FilterPacket *pck); +GF_Err gf_sei_load_from_packet(GF_SEILoader *sei, GF_FilterPacket *pck); + +void gf_av1_format_mdcv_to_mpeg(u8 mdcv_in24, u8 mdcv_out24); +#endif + #endif /*_GF_MEDIA_DEV_H_*/
View file
gpac-2.4.0.tar.gz/include/gpac/internal/scenegraph_dev.h -> gpac-26.02.0.tar.gz/include/gpac/internal/scenegraph_dev.h
Changed
@@ -938,7 +938,7 @@ void gf_node_delete_attributes(GF_Node *node); -GF_Node *gf_xml_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *inst_id, Bool deep); +GF_Node *gf_sg_xml_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *inst_id, Bool deep); GF_Err gf_dom_listener_del(GF_Node *listener, GF_DOMEventTarget *target);
View file
gpac-2.4.0.tar.gz/include/gpac/isomedia.h -> gpac-26.02.0.tar.gz/include/gpac/isomedia.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -169,8 +169,8 @@ { /*base media types*/ GF_ISOM_MEDIA_VISUAL = GF_4CC( 'v', 'i', 'd', 'e' ), - GF_ISOM_MEDIA_AUXV = GF_4CC( 'a', 'u', 'x', 'v' ), - GF_ISOM_MEDIA_PICT = GF_4CC( 'p', 'i', 'c', 't' ), + GF_ISOM_MEDIA_AUXV = GF_4CC( 'a', 'u', 'x', 'v' ), + GF_ISOM_MEDIA_PICT = GF_4CC( 'p', 'i', 'c', 't' ), GF_ISOM_MEDIA_AUDIO = GF_4CC( 's', 'o', 'u', 'n' ), GF_ISOM_MEDIA_HINT = GF_4CC( 'h', 'i', 'n', 't' ), GF_ISOM_MEDIA_META = GF_4CC( 'm', 'e', 't', 'a' ), @@ -247,10 +247,16 @@ /*AV1 media type*/ GF_ISOM_SUBTYPE_AV01 = GF_4CC('a', 'v', '0', '1'), + /*AVS3 Video media type*/ + GF_ISOM_SUBTYPE_AVS3 = GF_4CC('a', 'v', 's', '3'), + /*Opus media type*/ GF_ISOM_SUBTYPE_OPUS = GF_4CC('O', 'p', 'u', 's'), GF_ISOM_SUBTYPE_FLAC = GF_4CC( 'f', 'L', 'a', 'C' ), + /*IAMF media type*/ + GF_ISOM_SUBTYPE_IAMF = GF_4CC('i', 'a', 'm', 'f'), + /* VP */ GF_ISOM_SUBTYPE_VP08 = GF_4CC('v', 'p', '0', '8'), GF_ISOM_SUBTYPE_VP09 = GF_4CC('v', 'p', '0', '9'), @@ -276,6 +282,7 @@ GF_ISOM_SUBTYPE_AC3 = GF_4CC( 'a', 'c', '-', '3' ), GF_ISOM_SUBTYPE_EC3 = GF_4CC( 'e', 'c', '-', '3' ), + GF_ISOM_SUBTYPE_AC4 = GF_4CC( 'a', 'c', '-', '4' ), GF_ISOM_SUBTYPE_MP3 = GF_4CC( '.', 'm', 'p', '3' ), GF_ISOM_SUBTYPE_MLPA = GF_4CC( 'm', 'l', 'p', 'a' ), @@ -485,8 +492,12 @@ GF_ISOM_BRAND_AV01 = GF_4CC( 'a', 'v', '0', '1'), + GF_ISOM_BRAND_CAV3 = GF_4CC( 'c', 'a', 'v', '3'), // AVS 3 Video + GF_ISOM_BRAND_OPUS = GF_4CC( 'O', 'p', 'u', 's'), + GF_ISOM_BRAND_IAMF = GF_4CC( 'i', 'a', 'm', 'f'), + GF_ISOM_BRAND_ISMA = GF_4CC( 'I', 'S', 'M', 'A' ), /* dash related brands (ISO/IEC 23009-1) */ @@ -589,13 +600,15 @@ GF_ISOSAPType IsRAP; /*! allocated data size - used only when using static sample in \ref gf_isom_get_sample_ex*/ u32 alloc_size; - + /*! number of packed samples in this sample. If 0 or 1, only 1 sample is present only used for constant size and constant duration samples*/ u32 nb_pack; /*! read API only - sample duration (multiply by nb_pack to get full duration)*/ u32 duration; + /*! read API only - set to GF_TRUE if sample data is corrupted*/ + u32 corrupted; } GF_ISOSample; @@ -641,6 +654,8 @@ Samples may be added to the file in this mode, they will be stored in memory */ GF_ISOM_OPEN_READ_EDIT, + /*! same as GF_ISOM_OPEN_READ_DUMP but does not decompress boxes*/ + GF_ISOM_OPEN_READ_DUMP_NO_COMP } GF_ISOOpenMode; /*! indicates if target file is an IsoMedia file @@ -683,6 +698,13 @@ */ void gf_isom_delete(GF_ISOFile *isom_file); +/*! Checks if an open movie can be accessed in the given mode +\param isom_file the target ISO file +\param mode the desried open mode +\return GF_OK if access is possible, GF_ISOM_INVALID_MODE if access is not possible, GF_BAD_PARAM otherwise +*/ +GF_Err gf_isom_can_access_movie(GF_ISOFile *isom_file, GF_ISOOpenMode Mode); + /*! gets the last fatal error that occured in the file ANY FUNCTION OF THIS API WON'T BE PROCESSED IF THE FILE HAS AN ERROR \note Some function may return an error while the movie has no error @@ -1331,7 +1353,7 @@ /*! checks if a media has sync points \param isom_file the target ISO file \param trackNumber the target track -\return 0 if the media has no sync point info (eg, all samples are RAPs), 1 if the media has sync points (eg some samples are RAPs), 2 if the media has empty sync point info (no samples are RAPs - this will likely only happen +\return 0 if the media has no sync point info (eg, all samples are RAPs), 1 if the media has sync points (eg some samples are RAPs), 2 if the media has empty sync point info (no samples are RAPs - this will likely only happen in scalable context) */ u8 gf_isom_has_sync_points(GF_ISOFile *isom_file, u32 trackNumber); @@ -1724,6 +1746,24 @@ */ GF_Err gf_isom_get_trex_template(GF_ISOFile *isom_file, u32 trackNumber, u8 **output, u32 *output_size); +/*! Query mode for min negative ctts query*/ +typedef enum +{ + /*! Use CLSG if found, otherwise min value in samples*/ + GF_ISOM_MIN_NEGCTTS_ANY = 0, + /*! Use CLSG only*/ + GF_ISOM_MIN_NEGCTTS_CLSG, + /*! Use min value in samples only*/ + GF_ISOM_MIN_NEGCTTS_SAMPLES, +} GF_ISOMMinNegCtsQuery; +/*! gets the minimum CTS offset for tracks using negative cts +\param isom_file the destination ISO file +\param trackNumber the destination track +\param query_mode if set, ignore any CompositionToDecode box present +\return minimum negative CTS offset +*/ +s32 gf_isom_get_min_negative_cts_offset(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOMMinNegCtsQuery query_mode); + /*! sets the number of removed bytes form the input bitstream when using gmem:// url The number of bytes shall be the total number since the opening of the movie \param isom_file the target ISO file @@ -1750,6 +1790,37 @@ */ GF_Err gf_isom_purge_samples(GF_ISOFile *isom_file, u32 trackNumber, u32 nb_samples); +/*! External track flags*/ +enum +{ + /*! edit list override*/ + GF_ISOM_EXTK_EDTS_SKIP=1, + /*! location is a URN */ + GF_ISOM_EXTK_URN=1<<1, + /*! ignore source user data */ + GF_ISOM_EXTK_NO_UDTA=1<<2, + /*! ignore source meta */ + GF_ISOM_EXTK_NO_META=1<<3 +}; + +/*! checks if a track is an external track +\param isom_file the target ISO file +\param trackNumber the desired track to purge +\param tkid set to external track ID, may be NULL +\param type set to external track handler type, may be NULL +\param flags set to ExternalTrackLocation box flags, may be NULL +\param location set to external file location, may be NULL +\return GF_TRUE if track is an external track, GF_FALSE otherwise +*/ +Bool gf_isom_is_external_track(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID *tkid, u32 *type, u32 *flags, const char **location); + +/*! changes source URL, typically used when seeking operation change cache destination +\param isom_file the target ISO file +\param url the new url (local file path or gmem:// blob) +\return error if any +*/ +GF_Err gf_isom_switch_source(GF_ISOFile *isom_file, const char *url); + #ifndef GPAC_DISABLE_ISOM_DUMP /*! dumps file structures into XML trace file @@ -1879,15 +1950,26 @@ u32 gf_isom_new_track(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 MediaType, u32 TimeScale); /*! creates a new track from an encoded trak box. -\param isom_file the target ISO file -\param trackID the ID of the track - if 0, the track ID is chosen by the API +\param movie the target ISO file +\param trakID the ID of the track - if 0, the track ID is chosen by the API \param MediaType the handler type (four character code) of the media \param TimeScale the time scale of the media \param tk_box a serialized trak box to use as template \param tk_box_size the size of the serialized trak box \param udta_only only replace/inject udta box and entries \return the track number or 0 if error*/ -u32 gf_isom_new_track_from_template(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 MediaType, u32 TimeScale, u8 *tk_box, u32 tk_box_size, Bool udta_only); +u32 gf_isom_new_track_from_template(GF_ISOFile *movie, GF_ISOTrackID trakID, u32 MediaType, u32 TimeScale, u8 *tk_box, u32 tk_box_size, Bool udta_only); + +/*! creates a new external track +\param movie the target ISO file +\param trakID the ID of the track- if 0, the track ID is chosen by the API +\param refTrakID the ID of the referenced track (not checked by API) +\param MediaType the handler type (four character code) of the media +\param TimeScale the time scale of the media +\param uri location of external file +\return the track number or 0 if error +*/ +u32 gf_isom_new_external_track(GF_ISOFile *movie, GF_ISOTrackID trakID, GF_ISOTrackID refTrakID, u32 MediaType, u32 TimeScale, const char *uri); /*! removes a track - internal cross dependencies will be updated. \warning Any OD streams with references to this track through ODUpdate, ESDUpdate, ESDRemove commands @@ -1906,6 +1988,14 @@ */ GF_Err gf_isom_set_track_enabled(GF_ISOFile *isom_file, u32 trackNumber, Bool enableTrack); +/*! forces the track duration - should only be used for external tracks +\param isom_file the target ISO file +\param trackNumber the target track +\param duration track duration in movie timescale +\return error if any +*/ +GF_Err gf_isom_force_track_duration(GF_ISOFile *isom_file, u32 trackNumber, u64 duration); + /*! Track header flags operation type*/ typedef enum { @@ -2057,7 +2147,7 @@ */ GF_Err gf_isom_set_last_sample_duration_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 dur_num, u32 dur_den); -/*! patches last stts entry to make sure the cumulated duration equals the given next_dts value - this will overrite timing of all previous samples using an average dur +/*! patches last stts entry to make sure the cumulated duration equals the given next_dts value - this will overwrite timing of all previous samples using an average dur \param isom_file the target ISO file \param trackNumber the target track \param next_dts target decode time of next sample @@ -2157,7 +2247,7 @@ \param force_rescale_type type fo rescaling, Ignored if new_tsinc is not 0: - if set to 0, rescale timings. - if set to 1, only the media timescale is changed but media times are not updated. - - if set to 2, media timescale is updated if new_timescale is set, and all sample durations are set to new_tsinc + - if set to 2, media timescale is updated if new_timescale is set, and all sample durations are set to new_tsinc \return GF_EOS if no action taken (same config), or error if any */ GF_Err gf_isom_set_media_timescale(GF_ISOFile *isom_file, u32 trackNumber, u32 new_timescale, u32 new_tsinc, u32 force_rescale_type); @@ -2210,8 +2300,7 @@ GF_Err gf_isom_force_64bit_chunk_offset(GF_ISOFile *isom_file, Bool set_on); /*! compression mode of top-level boxes*/ -typedef enum -{ +GF_OPT_ENUM (GF_ISOCompressMode, /*! no compression is used*/ GF_ISOM_COMP_NONE=0, /*! only moov box is compressed*/ @@ -2220,11 +2309,11 @@ GF_ISOM_COMP_MOOF, /*! only moof and sidx boxes are compressed*/ GF_ISOM_COMP_MOOF_SIDX, - /*! only moof, sidx and ssix boxes are compressed*/ + /*! only moof, sidx and ssix boxes are compressed*/ GF_ISOM_COMP_MOOF_SSIX, - /*! all (moov, moof, sidx and ssix) boxes are compressed*/ + /*! all (moov, moof, sidx and ssix) boxes are compressed*/ GF_ISOM_COMP_ALL, -} GF_ISOCompressMode; +); enum { @@ -2307,10 +2396,10 @@ */ GF_Err gf_isom_add_chapter(GF_ISOFile *isom_file, u32 trackNumber, u64 timestamp, char *name); -/*! deletes copyright +/*! deletes chapter \param isom_file the target ISO file \param trackNumber the target track -\param index the 1-based index of the copyright notice to remove, or 0 to remove all chapters +\param index the 1-based index of the chapter notice to remove, or 0 to remove all chapters \return error if any */ GF_Err gf_isom_remove_chapter(GF_ISOFile *isom_file, u32 trackNumber, u32 index); @@ -2327,7 +2416,7 @@ \param EditDuration the duration of the edit in movie timecale \param MediaTime the corresponding media time of the start of the edit, in media timescale. -1 for empty edits \param EditMode the edit mode -\return error if any, GF_EOS if empty edit was inserted +\return error if any, GF_EOS if empty edit was inserted */ GF_Err gf_isom_set_edit(GF_ISOFile *isom_file, u32 trackNumber, u64 EditTime, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); @@ -2506,7 +2595,7 @@ /*! sets track matrix \param isom_file the target ISO file \param trackNumber the target track number -\param matrix the transformation matrix of the track on the movie canvas; all coeficients are expressed as 16.16 floating points +\param matrix the transformation matrix of the track on the movie canvas; all coefficients are expressed as 16.16 floating points \return error if any */ GF_Err gf_isom_set_track_matrix(GF_ISOFile *isom_file, u32 trackNumber, s32 matrix9); @@ -2607,18 +2696,20 @@ /*! Audio Sample Description signaling mode*/ -typedef enum { +GF_OPT_ENUM (GF_AudioSampleEntryImportMode, /*! use ISOBMF sample entry v0*/ GF_IMPORT_AUDIO_SAMPLE_ENTRY_NOT_SET = 0, - /*! use ISOBMF sample entry v0*/ - GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS, + /*! use ISOBMF sample entry v0 for best backward-compatibility*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_DEFAULT, /*! use ISOBMF sample entry v0 and forces channel count to 2*/ GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2, + /*! use ISOBMF sample entry v0 and forces values from bitstream*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS, /*! use ISOBMF sample entry v1*/ GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG, /*! use QTFF sample entry v1*/ GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF -} GF_AudioSampleEntryImportMode; +); /*! sets audio format information for a sample description @@ -2742,6 +2833,20 @@ GF_Err gf_isom_update_bitrate(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size); +/*! updates average and max bitrate of a sample description +if both average_bitrate and max_bitrate are 0, this removes any bitrate information +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param average_bitrate the average bitrate of the media for that sample description +\param max_bitrate the maximum bitrate of the media for that sample description +\param decode_buffer_size the decoder buffer size in bytes for that sample description +\param forced_for_mpeg4 if set to TRUE, the bitrate box will be added/removed even for MPEG-4 systems entries (mp4a, mp4v, mp4s) where the info is usually in the esds +\return error if any +*/ +GF_Err gf_isom_update_bitrate_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size, Bool forced_for_mpeg4); + + /*! track clone flags*/ typedef enum { @@ -2883,7 +2988,7 @@ - specifying a storage mode using \ref gf_isom_set_storage_mode - removing or adding tracks or items - removing, adding or updating samples - - using stdout, redirect file "_gpac_isobmff_redirect", memory file " gmem://" + - using stdout, redirect file "_gpac_isobmff_redirect", memory file " gmem://" In-place rewriting is enabled by default on files open in edit mode. @@ -3093,8 +3198,6 @@ */ GF_Err gf_isom_clone_pl_indications(GF_ISOFile *orig_file, GF_ISOFile *dest_file); -/*deletes chapter (1-based index, index 0 for all)*/ -GF_Err gf_isom_remove_chapter(GF_ISOFile *the_file, u32 trackNumber, u32 index); /*! associates a given SL config with a given ESD while extracting the OD information This is useful while reading the IOD / OD stream of an MP4 file. Note however that @@ -3237,6 +3340,15 @@ */ GF_Err gf_isom_update_video_sample_entry_fields(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 revision, u32 vendor, u32 temporalQ, u32 spatialQ, u32 horiz_res, u32 vert_res, u16 frames_per_sample, const char *compressor_name, s16 color_table_index); +/*! Sets all sample descriptions from a serialized sample description box, removing all child boxes of stsd +\param isom_file the target ISO file +\param trackNumber the target track number +\param stsd_data a serialized sample description box +\param stsd_data_size size of the serialized sample description +\return error if any +*/ +GF_Err gf_isom_set_track_stsd_templates(GF_ISOFile *isom_file, u32 trackNumber, u8 *stsd_data, u32 stsd_data_size); + /*! updates a sample description from a serialized sample description box. Only child boxes are removed in the process \param isom_file the target ISO file \param trackNumber the target track number @@ -3247,7 +3359,6 @@ */ GF_Err gf_isom_update_sample_description_from_template(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 *data, u32 size); - /*! creates a new unknown StreamDescription in the file. \note use this to store media not currently supported by the ISO media format or media types not implemented in this library \param isom_file the target ISO file @@ -3476,6 +3587,14 @@ */ GF_VPConfig *gf_isom_vp_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets AVS3 Video config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the AVS3 Video config - user is responsible for deleting it +*/ +GF_AVS3VConfig *gf_isom_avs3v_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + /*! gets DOVI config for a sample description \param isom_file the target ISO file \param trackNumber the target track @@ -3484,6 +3603,14 @@ */ GF_DOVIDecoderConfigurationRecord* gf_isom_dovi_config_get(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets IAMF config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the IAMF config - user is responsible for deleting it +*/ +GF_IAConfig* gf_isom_iamf_config_get(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + /*! checks if some tracks in file needs layer reconstruction \param isom_file the target ISO file \return GF_TRUE if track dependencies implying extractors or implicit reconstruction are found, GF_FALSE otherwise @@ -3738,7 +3865,6 @@ */ GF_Err gf_isom_vp_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_VPConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u32 vpx_type); - /*! creates new AV1 config \param isom_file the target ISO file \param trackNumber the target track @@ -3750,6 +3876,29 @@ */ GF_Err gf_isom_av1_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AV1Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! creates new AVS 3 Video config +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AVS3 Video config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_avs3v_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVS3VConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + + +/*! creates new IAMF config +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the IAMF config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_iamf_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_IAConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -3845,6 +3994,68 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +/*! gets an AC4 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return AC-4 config +*/ +GF_AC4Config *gf_isom_ac4_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates an AC4 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AC4 config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_ac4_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AC4Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! updates an AC4 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param cfg the AC4 config for this sample description +\return error if any +*/ +GF_Err gf_isom_ac4_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AC4Config *cfg); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets an AC4 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return AC-4 config +*/ +GF_AC4Config *gf_isom_ac4_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates an AC4 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AC4 config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_ac4_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AC4Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! updates an AC4 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param cfg the AC4 config for this sample description +\return error if any +*/ +GF_Err gf_isom_ac4_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AC4Config *cfg); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + /*! gets TrueHD sample description info \param isom_file the target ISO file \param trackNumber the target track @@ -3856,7 +4067,7 @@ GF_Err gf_isom_truehd_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *format_info, u32 *peak_data_rate); #ifndef GPAC_DISABLE_ISOM_WRITE -/*! creates a FLAC sample description +/*! creates a TrueHD sample description \param isom_file the target ISO file \param trackNumber the target track \param URLname URL value of the data reference, NULL if no data reference (media in the file) @@ -3970,6 +4181,19 @@ */ GF_Err gf_isom_get_tmcd_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *tmcd_flags, u32 *tmcd_fps_num, u32 *tmcd_fps_den, u32 *tmcd_fpt); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! creates an event message track metadata sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_evte_config_new(GF_ISOFile *isom_file, u32 trackNumber, u32 *outDescriptionIndex); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + /*! gets information of a raw PCM sample description, ISOBMFF style \param isom_file the target ISO file \param trackNumber the target track @@ -4001,7 +4225,7 @@ \param URLname URL value of the data reference, NULL if no data reference (media in the file) \param URNname URN value of the data reference, NULL if no data reference (media in the file) \param outDescriptionIndex set to the index of the created sample description -\param dsi the MPEGH audio config (payload of mhaC box): byte0=1 (config version) ,byte1=ProfileLevel, byte2=channel layout, byte3,byte4: the size of what follows the rest being a mpegh3daConfig +\param dsi the MPEGH audio config (payload of mhaC box): byte0=1 (config version) ,byte1=ProfileLevel, byte2=channel layout, byte3,byte4: the size of what follows the rest being a mpegh3daConfig \param dsi_size the size of the MPEGH audio config \param mha_subtype mha1/mha2:/mhm1/mhm2 subtype to use \return error if any @@ -4685,9 +4909,10 @@ \param reference_track_ID the ID of the track used as a reference for media timestamps \param ntp absolute NTP time \param timestamp media time corresponding to the NTP time, in reference track media timescale +\param at_mux whether the box should also contain the ntp time of when the movie fragment is written \return error if any */ -GF_Err gf_isom_set_fragment_reference_time(GF_ISOFile *isom_file, GF_ISOTrackID reference_track_ID, u64 ntp, u64 timestamp); +GF_Err gf_isom_set_fragment_reference_time(GF_ISOFile *isom_file, GF_ISOTrackID reference_track_ID, u64 ntp, u64 timestamp, Bool at_mux); /*! writes an empty sidx in the current movie. @@ -4719,6 +4944,16 @@ */ GF_Err gf_isom_setup_track_fragment_template(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u8 *boxes, u32 boxes_size, u8 force_traf_flags); +/*! sets up track fragment defaults using the given template. The template shall be a serialized array of one or more trex boxes + +\param isom_file the target ISO file +\param TrackID ID of the target track +\param orig_dur last sample original duration +\param elapsed_dur first sample elapsed duration +\return error if any +*/ +GF_Err gf_isom_set_fragment_original_duration(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u32 orig_dur, u32 elapsed_dur); + #ifdef GF_ENABLE_CTRN /*! enables track fragment inheriting from a given traf. This shall only be set when the inherited traf shares exactly the same syntax except the sample sizes, this library does not compute which @@ -4862,6 +5097,16 @@ */ GF_Err gf_isom_fragment_set_sample_rap_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_rap, u32 num_leading_samples); +/*! sets AV1 Switching Frame information for a sample in a track fragment +\param isom_file the target ISO file +\param trackID the ID of the target track +\param sample_number_in_frag the sample number of the sample in the traf +\param is_rap set to GF_TRUE to indicate the sample is a RAP sample (open-GOP), GF_FALSE otherwise +\param num_leading_samples set to the number of leading pictures for a RAP sample +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_av1_switch_frame_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_switch_Frame); + /*! sets sample dependency flags in a track fragment - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags \param isom_file the target ISO file \param trackID the ID of the target track @@ -5887,20 +6132,21 @@ \param pssh_mode 0: regular PSSH in moov, 1: PIFF PSSH in moov, 2: regular PSSH in meta \return error if any */ -GF_Err gf_cenc_set_pssh(GF_ISOFile *isom_file, bin128 systemID, u32 version, u32 KID_count, bin128 *KID, u8 *data, u32 len, u32 pssh_mode); +GF_Err gf_isom_cenc_set_pssh(GF_ISOFile *isom_file, bin128 systemID, u32 version, u32 KID_count, bin128 *KID, u8 *data, u32 len, u32 pssh_mode); /*! removes CENC senc box info \param isom_file the target ISO file \param trackNumber the target track \return error if any */ -GF_Err gf_isom_remove_samp_enc_box(GF_ISOFile *isom_file, u32 trackNumber); +GF_Err gf_isom_remove_cenc_senc_box(GF_ISOFile *isom_file, u32 trackNumber); + /*! removes all CENC sample groups \param isom_file the target ISO file \param trackNumber the target track \return error if any */ -GF_Err gf_isom_remove_samp_group_box(GF_ISOFile *isom_file, u32 trackNumber); +GF_Err gf_isom_remove_cenc_seig_sample_group(GF_ISOFile *isom_file, u32 trackNumber); #endif //GPAC_DISABLE_ISOM_WRITE @@ -6385,6 +6631,24 @@ */ GF_Err gf_isom_add_meta_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, Bool self_reference, char *resource_path, const char *item_name, u32 item_id, u32 item_type, const char *mime_type, const char *content_encoding, const char *URL, const char *URN, GF_ImageItemProperties *image_props); +/*! adds an item to a meta box from file (same as \ref gf_isom_add_meta_item but outputs the item's id in io_item_id) +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param self_reference if GF_TRUE, indicates that the item is in fact the entire container file +\param resource_path path to the file to add +\param item_name name of the item +\param io_item_id pointer to the item's id. If the pointed value is 0 or the same as an existing item, it is set to the item's newly attributed id +\param item_type four character code of item type +\param mime_type mime type of the item, can be NULL +\param content_encoding content encoding of the item, can be NULL +\param URL URL of the item for external data reference (data is not contained in meta parent file) +\param URN URN of the item for external data reference (data is not contained in meta parent file) +\param image_props image properties information for image items +\return error if any +*/ +GF_Err gf_isom_add_meta_item2(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, Bool self_reference, char *resource_path, const char *item_name, u32 *io_item_id, u32 item_type, const char *mime_type, const char *content_encoding, const char *URL, const char *URN, GF_ImageItemProperties *image_props); + #endif //GPAC_DISABLE_ISOM /*! item extend description*/ @@ -6671,6 +6935,24 @@ GF_Err gf_isom_apple_enum_tag(GF_ISOFile *isom_file, u32 idx, GF_ISOiTunesTag *out_tag, const u8 **data, u32 *data_len, u64 *out_int_val, u32 *out_int_val2, u32 *out_flags); +/*! enumerate itunes tags. + +\param isom_file the target ISO file +\param idx 0-based index of the tag to get +\param out_tag set to the tag code +\param data set to the tag data pointer - do not modify +\param data_len set to the size of the tag data. Data is set to NULL and data_size to 1 if the associated tag has no data +\param out_int_val set to the int/bool/frac numerator type for known tags, in which case data is set to NULL +\param out_int_val2 set to the frac denominator for known tags, in which case data is set to NULL +\param out_flags set to the flags value of the data container box +\param out_mean set to the mean string identifier, if any +\param out_name set to the name string identifier, if any +\param out_locale set to the locale data identifier, if any +\return error if any (GF_URL_ERROR if no more tags) +*/ +GF_Err gf_isom_apple_enum_tag_ex(GF_ISOFile *isom_file, u32 idx, GF_ISOiTunesTag *out_tag, const u8 **data, u32 *data_len, u64 *out_int_val, u32 *out_int_val2, u32 *out_flags, const char **out_mean, const char **out_name, u32 *out_locale); + + /*! enumerate WMA tags. \param isom_file the target ISO file @@ -6790,6 +7072,21 @@ */ GF_Err gf_isom_apple_set_tag(GF_ISOFile *isom_file, GF_ISOiTunesTag tag, const u8 *data, u32 data_len, u64 int_val, u32 int_val2); +/*! sets the given tag info. + +\param isom_file the target ISO file +\param for_tag the tag to set +\param data tag data buffer or string to parse +\param data_len size of the tag data buffer. If data is NULL and and data_len not 0, removes the given tag +\param int_val value for integer/boolean tags. If data and data_len are set, parse data as string to get the value +\param int_val2 value for fractional tags. If data and data_len are set, parse data as string to get the value +\param name domain name of tag, ignores for_tag if not null +\param mean mean of tag, ignores for_tag if not null +\param locale locale of tag, ignored if name and mean are null +\return error if any +*/ +GF_Err gf_isom_apple_set_tag_ex(GF_ISOFile *isom_file, GF_ISOiTunesTag for_tag, const u8 *data, u32 data_len, u64 int_val, u32 int_val2, const char *name, const char *mean, u32 locale); + /*! sets the given WMA tag info (only string tags are supported). @@ -6992,6 +7289,7 @@ GF_ISOM_SAMPLE_GROUP_VIPR = GF_4CC( 'v', 'i', 'p', 'r'), //p15 GF_ISOM_SAMPLE_GROUP_LBLI = GF_4CC( 'l', 'b', 'l', 'i'), //p15 GF_ISOM_SAMPLE_GROUP_3GAG = GF_4CC( '3', 'g', 'a', 'g'), //3gpp + GF_ISOM_SAMPLE_GROUP_AV1S = GF_4CC( 'a', 'v', '1', 's'), //av1-isobmff GF_ISOM_SAMPLE_GROUP_AVCB = GF_4CC( 'a', 'v', 'c', 'b'), //avif GF_ISOM_SAMPLE_GROUP_SPOR = GF_4CC( 's', 'p', 'o', 'r'), //p15 GF_ISOM_SAMPLE_GROUP_SULM = GF_4CC( 's', 'u', 'l', 'm'), //p15 @@ -7025,7 +7323,7 @@ \param trackNumber the target track \param sample_number sample number to query \param grouping_type four character code of grouping type of sample group description to query -\param grouping_type_parameter grouping type parameter of sample group description to query +\param grouping_type_parameter grouping type parameter of sample group description to query \param sampleGroupDescIndex set to the 1-based sample group description index, or 0 if no sample group of this type is associated \return error if any */ @@ -7096,6 +7394,18 @@ */ GF_Err gf_isom_set_sample_rap_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool is_rap, u32 num_leading_samples); +/*! sets AV1 Switching Frame info for sample_number +\warning Sample group info MUST be added in order (no insertion in the tables) + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param is_rap indicates if the sample is a RAP (open gop) sample +\param num_leading_samples indicates the number of leading samples (samples after this RAP that have dependences on samples before this RAP and hence should be discarded when tuning in) +\return error if any +*/ +GF_Err gf_isom_set_sample_av1_switch_frame_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool is_switch_Frame); + /*! sets roll_distance info for sample_number (number of frames before (<0) or after (>0) this sample to have a complete refresh of the decoded data (used by GDR in AVC) \warning Sample group info MUST be added in order (no insertion in the tables) @@ -7182,8 +7492,38 @@ GF_Err gf_isom_set_sample_group_in_traf(GF_ISOFile *isom_file); #endif +/*! sets sample references +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number - currently restricted to be the last sample +\param ID ID for the sample +\param nb_refs number of references for this sample, may be 0 if none (IDR) +\param refs IDs of samples this sample depends on +\return error if any*/ +GF_Err gf_isom_set_sample_references(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, s32 ID, u32 nb_refs, s32 *refs); + #endif // GPAC_DISABLE_ISOM_WRITE +/*! gets sample references +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param refID ID for the sample +\param nb_refs number of references for this sample, may be 0 if none (IDR) +\param refs IDs of samples this sample depends on. Do NOT modify +\return error if any, GF_NOT_FOUND if no such info*/ +GF_Err gf_isom_get_sample_references(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *refID, u32 *nb_refs, const u32 **refs); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! sets sample references for fragmented mode +\param isom_file the target ISO file +\param TrackID the target track ID +\param refID ID for the sample +\param nb_refs number of references for this sample, may be 0 if none (IDR) +\param refs IDs of samples this sample depends on +\return error if any*/ +GF_Err gf_isom_fragment_add_sample_references(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, s32 refID, u32 nb_refs, s32 *refs); +#endif /*! @} */ @@ -7195,5 +7535,3 @@ #endif /*_GF_ISOMEDIA_H_*/ - -
View file
gpac-2.4.0.tar.gz/include/gpac/list.h -> gpac-26.02.0.tar.gz/include/gpac/list.h
Changed
@@ -149,7 +149,7 @@ /*! \brief gets last item -Gets last item o fthe list +Gets last item of the list \param ptr target list object \return the last item */
View file
gpac-2.4.0.tar.gz/include/gpac/main.h -> gpac-26.02.0.tar.gz/include/gpac/main.h
Changed
@@ -140,7 +140,7 @@ #define GF_ARG_SUBSYS_AUDIO (1<<10) /*! argument applies to the font and text subsystem*/ #define GF_ARG_SUBSYS_TEXT (1<<11) -/*! argument applies to the remotery subsystem*/ +/*! argument applies to the rmtws subsystem*/ #define GF_ARG_SUBSYS_RMT (1<<12) /*! argument belongs to hack tools, usually never used*/ #define GF_ARG_SUBSYS_HACKS (1<<13) @@ -265,4 +265,3 @@ #endif #endif //_GF_MAIN_H_ -
View file
gpac-2.4.0.tar.gz/include/gpac/maths.h -> gpac-26.02.0.tar.gz/include/gpac/maths.h
Changed
@@ -47,7 +47,7 @@ This section documents the math and trigo functions used in the GPAC framework. GPAC can be compiled with fixed-point support, representing float values on a 16.16 signed integer, which implies a developer must take care of float computations when using GPAC.\n - A developer should not need to know in which mode the framework has been compiled as long as he uses + A developer should not need to know in which mode the framework has been compiled as long as (s)he uses the math functions of GPAC which work in both float and fixed-point mode.\n Using fixed-point version is decided at compilation time and cannot be changed. The feature is signaled through the GPAC_FIXED_POINT macro: when defined, GPAC has been compiled in fixed-point mode
View file
gpac-2.4.0.tar.gz/include/gpac/media_tools.h -> gpac-26.02.0.tar.gz/include/gpac/media_tools.h
Changed
@@ -699,8 +699,7 @@ Matches profile enum of dasher module: auto|live|onDemand|main|full|hbbtv1.5.live|dashavc264.live|dashavc264.onDemand */ -typedef enum -{ +GF_OPT_ENUM (GF_DashProfile, /*! auto profile, internal use only*/ GF_DASH_PROFILE_AUTO = 0, /*! Live dash profile for: live for ISOFF, SIMPLE for M2TS */ @@ -720,7 +719,7 @@ GF_DASH_PROFILE_AVC264_ONDEMAND, /*! industry profile DASH-IF ISOBMFF low latency */ GF_DASH_PROFILE_DASHIF_LL, -} GF_DashProfile; + ); /*! @@ -738,7 +737,7 @@ /*! out of band parameter sets except PPS and APS, used for VVC */ GF_DASH_BSMODE_INBAND_PPS, /*! both inband and out of band parameter sets */ - GF_DASH_BSMODE_BOTH, //Romain + GF_DASH_BSMODE_BOTH, /*! attempts to merge parameter sets in a single sample entry */ GF_DASH_BSMODE_MERGED, /*! parameter sets are in different sample entries */ @@ -752,8 +751,7 @@ DASH media presentation type \hideinitializer */ -typedef enum -{ +GF_OPT_ENUM (GF_DashDynamicMode, /*! DASH Presentation is static*/ GF_DASH_STATIC = 0, /*! DASH Presentation is dynamic*/ @@ -762,21 +760,20 @@ GF_DASH_DYNAMIC_LAST, /*! same as GF_DASH_DYNAMIC but prevents all segment cleanup */ GF_DASH_DYNAMIC_DEBUG, -} GF_DashDynamicMode; +); /*! DASH selector for content protection descriptor location \hideinitializer */ -typedef enum -{ +GF_OPT_ENUM (GF_DASH_ContentLocationMode, /*! content protection descriptor is at the adaptation set level*/ GF_DASH_CPMODE_ADAPTATION_SET=0, /*! content protection descriptor is at the representation level*/ GF_DASH_CPMODE_REPRESENTATION, /*! content protection descriptor is at the adaptation set and representation level*/ GF_DASH_CPMODE_BOTH, -} GF_DASH_ContentLocationMode; +); /*! DASH segmenter*/ typedef struct __gf_dash_segmenter GF_DASHSegmenter; @@ -960,8 +957,7 @@ /*! DASH PSSH storage mode*/ -typedef enum -{ +GF_OPT_ENUM (GF_DASHPSSHMode, //! PSSH box in moov only GF_DASH_PSSH_MOOV = 0, //! PSSH box in moof only @@ -974,7 +970,7 @@ GF_DASH_PSSH_MPD, //! Drop PSSH info from mpd and init seg GF_DASH_PSSH_NONE, -} GF_DASHPSSHMode; +); /*! Configure how default values for ISOBMFF are stored @@ -1137,6 +1133,14 @@ */ GF_Err gf_dasher_keep_source_utc(GF_DASHSegmenter *dasher, Bool keep_utc); +/*! + Keeps hls info used in -crypt and pass to dasher +\param dasher the DASH segmenter object +\param hls_info use for HLS playlist in -crypt, if NULL no HLS info is set +\return error if any +*/ +GF_Err gf_dasher_set_hls_info(GF_DASHSegmenter *dasher, char *hls_info); + #ifndef GPAC_DISABLE_ISOM_FRAGMENTS /*! save file as fragmented movie
View file
gpac-2.4.0.tar.gz/include/gpac/mediaobject.h -> gpac-26.02.0.tar.gz/include/gpac/mediaobject.h
Changed
@@ -168,7 +168,7 @@ //always resync the content of the decoded media buffer to the current time (used for video) GF_MO_FETCH_RESYNC, //never resync the content of the decoded media buffer (used fo audio) - //if clock is paused, do fetch (used for audio extraction) + //if clock is paused, do fetch (used for audio extraction) GF_MO_FETCH_PAUSED } GF_MOFetchMode;
View file
gpac-2.4.0.tar.gz/include/gpac/modules/video_out.h -> gpac-26.02.0.tar.gz/include/gpac/modules/video_out.h
Changed
@@ -157,7 +157,7 @@ GF_EVENT_SET_CURSOR: sets cursor GF_EVENT_SET_CAPTION: sets caption GF_EVENT_SHOWHIDE: show/hide output window for self-managed output - GF_EVENT_SIZE: inital window resize upon scene load + GF_EVENT_SIZE: initial window resize upon scene load GF_EVENT_VIDEO_SETUP: all HW related setup: * for 2D output, this means resizing the backbuffer if needed (depending on HW constraints) * for 3D output, this means re-setup of OpenGL context (depending on HW constraints).
View file
gpac-2.4.0.tar.gz/include/gpac/mpd.h -> gpac-26.02.0.tar.gz/include/gpac/mpd.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - Cyril Concolato - * Copyright (c) Telecom ParisTech 2010-2024 + * Copyright (c) Telecom ParisTech 2010-2025 * All rights reserved * * This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module @@ -68,8 +68,28 @@ GF_DASH_TEMPLATE_INITIALIZATION_SKIPINIT, /*! same as GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE but skip default "init" concatenation*/ GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE_SKIPINIT, + /*! resolve template for segment but keep $SubNumber$*/ + GF_DASH_TEMPLATE_SEGMENT_SUBNUMBER, } GF_DashTemplateSegmentType; +GF_OPT_ENUM (GF_DashAbsoluteURLMode, + /*! do not use absolute URL*/ + GF_DASH_ABS_URL_NO, + /*! use absolute URL only in variant playlists*/ + GF_DASH_ABS_URL_VARIANT, + /*! use absolute URL only in master playlist*/ + GF_DASH_ABS_URL_MASTER, + /*! use absolute URL everywhere*/ + GF_DASH_ABS_URL_BOTH, +); + +GF_OPT_ENUM (GF_DashHLSLowLatencyType, + GF_DASH_LL_HLS_OFF, + GF_DASH_LL_HLS_BR, + GF_DASH_LL_HLS_SF, + GF_DASH_LL_HLS_BRSF, +); + /*! formats the segment name according to its template \param seg_type the desired format mode \param is_bs_switching set to GF_TRUE to indicate the target segment is a bitstream switching segment @@ -140,6 +160,8 @@ u32 duration; /*MANDATORY*/ /*! may be 0xFFFFFFFF (-1) (\warning this needs further testing)*/ u32 repeat_count; + /*! for DASH SSR*/ + u32 nb_parts; } GF_MPD_SegmentTimelineEntry; /*! Segment Timeline*/ @@ -317,6 +339,8 @@ char *initialization; /*! bitstream switching segment template*/ char *bitstream_switching; + /*! part count for sub-segment representations*/ + u32 nb_parts; /*! internal, for HLS generation*/ const char *hls_init_name; @@ -465,6 +489,8 @@ u8 xlink_digestGF_SHA1_DIGEST_SIZE; /*! set to TRUE if not modified in the update of an xlink*/ Bool not_modified; + /*! representation uses SSR, value is estimated nb parts*/ + u32 use_ssr; } GF_DASH_RepresentationPlayback; /*! segment context used by the dasher, GPAC internal*/ @@ -575,7 +601,7 @@ /*! number of fragment infos */ GF_DASH_FragmentContext *frags; /*! HLS LL signaling - 0: disabled, 1: byte range, 2: files */ - u32 llhls_mode; + GF_DashHLSLowLatencyType llhls_mode; /*! HLS LL segment done */ Bool llhls_done; /*! HLS set to TRUE if encrypted */ @@ -584,6 +610,13 @@ char *hls_key_uri; /*! HLS IV*/ bin128 hls_iv; + + /*! start time of segment timeline entry */ + u64 stl_start; + /*! repeat count of segment timeline */ + u32 stl_rcount; + /*! LLHAS template*/ + char *llhas_template; } GF_DASH_SegmentContext; /*! Representation*/ @@ -643,19 +676,24 @@ const char *hls_single_file_name; /*! number of audio channels - HLS only*/ u32 nb_chan; + /*! CHANNELS attribute in string for special content - HLS only*/ + char str_chan20; /*! video FPS - HLS only*/ Double fps; /*! groupID (for HLS)*/ const char *groupID; + /*! groupIDs allowed in rendition (for HLS)*/ + const char **group_ids_rend; + u32 nb_group_ids_rend; /*! user assigned m3u8 name for this representation*/ - const char *m3u8_name; + char *m3u8_name; /*! generated m3u8 name if no user-assigned one*/ char *m3u8_var_name; /*! temp file for m3u8 generation*/ FILE *m3u8_var_file; - /*! for m3u8: 0: not encrypted, 1: full segment, 2: CENC*/ + /*! for m3u8: 0: not encrypted, 1: full segment, 2: CENC CBC, 2: CENC CTR*/ u8 crypto_type; u8 def_kms_used; @@ -681,6 +719,8 @@ Bool sub_forced; const char *hls_forced; + + const char *init_base64; } GF_MPD_Representation; /*! AdaptationSet*/ @@ -714,6 +754,12 @@ GF_MPD_Fractional min_framerate; /*! max framerate*/ GF_MPD_Fractional max_framerate; + /*! set if sub-segment representation is used + 0: not used + 1: LL-HLS compatibiliity + 2: regular SSR + */ + u32 ssr_mode; /*! set if segment boundaries are time-aligned across qualities*/ Bool segment_alignment; /*! set if a single init segment is needed (no reinit at quality switch)*/ @@ -732,6 +778,8 @@ GF_List *viewpoint; /*! content component descriptor list if any*/ GF_List *content_component; + /*! inband streams events */ + GF_List *inband_event; /*! base URL (alternate location) list if any*/ GF_List *base_URLs; @@ -766,6 +814,14 @@ Double hls_ll_target_frag_dur; } GF_MPD_AdaptationSet; +/*! structure used to signal inband events*/ +typedef struct { + /*! Scheme ID Uri of the inband event */ + char *scheme_id_uri; + /*! Value of the inband event */ + char *value; +} GF_MPD_Inband_Event; + /*! MPD offering type*/ typedef enum { /*! content is statically available*/ @@ -850,9 +906,9 @@ char *profiles; /*! offering type*/ GF_MPD_Type type; - /*! UTC of availability start anchor, expressed in milliseconds, MANDATORY if type=dynamic*/ + /*! UTC of availability start anchor, expressed in milliseconds, MANDATORY if type=dynamic*/ u64 availabilityStartTime; - /*! UTC of availability end anchor, expressed in milliseconds*/ + /*! UTC of availability end anchor, expressed in milliseconds*/ u64 availabilityEndTime; /*! UTC of last publishing of the manifest*/ u64 publishTime; @@ -929,7 +985,7 @@ /*! user-defined PART-HOLD-BACK, auto computed if <=0*/ Double llhls_part_holdback; //als absolute url flag - u32 hls_abs_url; + GF_DashAbsoluteURLMode hls_abs_url; Bool m3u8_use_repid; Bool hls_audio_primary; @@ -1184,11 +1240,12 @@ \param out_key_url set to the key URL for the segment for HLS (optional, may be NULL) \param key_iv set to the key IV for the segment for HLS (optional, may be NULL) \param out_start_number set to the start_number used (optional, may be NULL) +\param subseg_index index of subseg, -1 means no SSR is used \return error if any */ GF_Err gf_mpd_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, const char *mpd_url, u32 base_url_index, GF_MPD_URLResolveType resolve_type, u32 item_index, u32 nb_segments_removed, - char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration, Bool *is_in_base_url, char **out_key_url, bin128 *key_iv, u32 *out_start_number); + char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration, Bool *is_in_base_url, char **out_key_url, bin128 *key_iv, u32 *out_start_number, s32 subseg_index); /*! get duration of the presentation \param mpd the target MPD @@ -1237,11 +1294,12 @@ \param in_rep the target Representation \param out_segment_index the corresponding segment index \param out_opt_seek_time the corresponding seek time (start time of segment in seconds) (optional, may be NULL) +\param out_seg_dur the corresponding segment duration in seconds, may be null \return error if any */ GF_Err gf_mpd_seek_in_period(Double seek_time, MPDSeekMode seek_mode, GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, - u32 *out_segment_index, Double *out_opt_seek_time); + u32 *out_segment_index, Double *out_opt_seek_time, Double *out_seg_dur); /*! deletes a GF_MPD_BaseURL structure (type-casted to void *) \param _item the GF_MPD_BaseURL to free @@ -1291,7 +1349,16 @@ */ GF_MPD_Descriptor *gf_mpd_get_descriptor(GF_List *desclist, char *scheme_id); -/*! @} */ #endif /*GPAC_DISABLE_MPD*/ +/*! resolve the SubNumber template, utility function used by some output filters +\param llhas_template template for the segment, or NULL if none +\param segment_filename segment filename +\param part_idx index of part to use +\return resolved file name for the given part of the segment +*/ +char *gf_mpd_resolve_subnumber(char *llhas_template, char *segment_filename, u32 part_idx); + +/*! @} */ + #endif // _MPD_H_
View file
gpac-2.4.0.tar.gz/include/gpac/mpeg4_odf.h -> gpac-26.02.0.tar.gz/include/gpac/mpeg4_odf.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / MPEG-4 Object Descriptor sub-project @@ -347,7 +347,7 @@ IPMP_CP_CM = 3, /*control point in BIFS tree (???)*/ IPMP_CP_BIFS = 4 - /*the rest is reserved or forbidden(0xFF)*/ + /*the rest is reserved or forbidden(0xFF)*/ }; /*! IPMPX base classe*/ @@ -1052,7 +1052,7 @@ } GF_VVCConfig; -/*! used for storing AV1 OBUs*/ +/*! used for storing AV1 or IAMF OBUs*/ typedef struct { u64 obu_length; @@ -1123,6 +1123,14 @@ u8 force_dv; } GF_DOVIDecoderConfigurationRecord; +/*! AVS3 Video av3C */ +typedef struct { + u8 configurationVersion; + u16 sequence_header_length; + u8* sequence_header; // 8*sequence_header_length bits + u8 library_dependency_idc; // 6 bits reserved at '1' + 2 bits +} GF_AVS3VConfig; + /*! Media Segment Descriptor used for Media Control Extensions*/ typedef struct { @@ -1565,6 +1573,38 @@ \param cfg the DolbyVision config to destroy*/ void gf_odf_dovi_cfg_del(GF_DOVIDecoderConfigurationRecord *cfg); +/*! creates a AVS3 Video descriptor +\return a newly allocated descriptor +*/ +GF_AVS3VConfig *gf_odf_avs3v_cfg_new(); +/*! AVS3 Video config constructor +\param cfg the AVS3 Video config to destroy*/ +void gf_odf_avs3v_cfg_del(GF_AVS3VConfig *cfg); +/*! writes AVS3 Video config to bitstream +\param cfg the AVS3 Video config to write +\param bs bitstream containing the encoded AVS3 Video decoder specific info +\return error code if any +*/ +GF_Err gf_odf_avs3v_cfg_write_bs(GF_AVS3VConfig *cfg, GF_BitStream *bs); +/*! writes AVS3 Video config to buffer +\param cfg the AVS3 Video config to write +\param outData set to an allocated encoded buffer - it is the caller responsibility to free this +\param outSize set to the encoded buffer size +\return error if any +*/ +GF_Err gf_odf_avs3v_cfg_write(GF_AVS3VConfig *cfg, u8 **outData, u32 *outSize); +/*! gets AVS3 Video config from bitstream +\param bs bitstream containing the encoded AV1 decoder specific info +\return the decoded AVS3 Video config +*/ +GF_AVS3VConfig *gf_odf_avs3v_cfg_read_bs(GF_BitStream *bs); +/*! gets AVS3 Video config from buffer +\param dsi encoded AVS3 Video config +\param dsi_size size of encoded AVS3 Video config +\return the decoded AVS3 Video config +*/ +GF_AVS3VConfig *gf_odf_avs3v_cfg_read(u8 *dsi, u32 dsi_size); + /*! AC-3 and E-AC3 stream info */ typedef struct __ec3_stream @@ -1640,7 +1680,7 @@ \param cfg the AC3/EC3 config to fill \return Error if any */ -GF_Err gf_odf_ac3_config_parse(u8 *dsi, u32 dsi_len, Bool is_ec3, GF_AC3Config *cfg); +GF_Err gf_odf_ac3_cfg_parse(u8 *dsi, u32 dsi_len, Bool is_ec3, GF_AC3Config *cfg); /*! parses an AC3/EC3 sample description from bitstream \param bs the bitstream object @@ -1648,8 +1688,205 @@ \param cfg the AC3/EC3 config to fill \return Error if any */ -GF_Err gf_odf_ac3_config_parse_bs(GF_BitStream *bs, Bool is_ec3, GF_AC3Config *cfg); +GF_Err gf_odf_ac3_cfg_parse_bs(GF_BitStream *bs, Bool is_ec3, GF_AC3Config *cfg); + +typedef struct { + u8 b_4_back_channels_present; + u8 b_centre_present; + u8 top_channels_present; + u8 dsi_sf_multiplier; + u8 b_substream_bitrate_indicator; + u8 substream_bitrate_indicator; + u32 dsi_substream_channel_mask; + u8 b_ajoc; + u8 b_static_dmx; + u8 n_dmx_objects_minus1; + u8 n_umx_objects_minus1; + u8 b_substream_contains_bed_objects; + u8 b_substream_contains_dynamic_objects; + u8 b_substream_contains_ISF_objects; + + // auxiliary information, used to parse the frame + u8 b_lfe; + u32 ch_mode; +} GF_AC4SubStream; + +typedef struct { + u8 b_substreams_present; + u8 b_hsf_ext; + u8 b_channel_coded; + u8 b_content_type; + u8 content_classifier; + u8 b_language_indicator; + u8 n_language_tag_bytes; + u8 language_tag_bytes64; // n_language_tag_bytes is 6 bits + u8 dolby_atmos_indicator; + u8 n_lf_substreams; + GF_List *substreams; // GF_AC4SubStream +} GF_AC4SubStreamGroupV1; + +typedef struct { + u8 bit_rate_mode; + u32 bit_rate; + u32 bit_rate_precision; +} GF_AC4BitrateDsi; + +typedef struct { + u16 name_len; + u8 presentation_name256; // restrict to 256 char + u8 n_targets; + u8 target_md_compat32; + u8 target_device_category32; +} GF_AC4AlternativeInfo; + +typedef struct { + u8 presentation_version; + u8 presentation_config; + u8 mdcompat; + u8 b_presentation_id; + u8 presentation_id; + u8 dsi_frame_rate_multiply_info; + u8 dsi_frame_rate_fraction_info; + u8 presentation_emdf_version; + u16 presentation_key_id; + u8 b_presentation_channel_coded; + u8 dsi_presentation_ch_mode; + u8 pres_b_4_back_channels_present; + u8 pres_top_channel_pairs; + u32 presentation_channel_mask_v1; + u8 b_presentation_core_differs; + u8 b_presentation_core_channel_coded; + u8 dsi_presentation_channel_mode_core; + u8 b_presentation_filter; + u8 b_enable_presentation; + u8 n_filter_bytes; + u8 b_multi_pid; + u8 n_skip_bytes; + u8 b_pre_virtualized; + u8 b_add_emdf_substreams; + u8 n_add_emdf_substreams; + u8 substream_emdf_version128; + u16 substream_key_id128; + u8 b_presentation_bitrate_info; + GF_AC4BitrateDsi ac4_bitrate_dsi; + u8 b_alternative; + GF_AC4AlternativeInfo alternative_info; + u8 de_indicator; + u8 dolby_atmos_indicator; + u8 b_extended_presentation_id; + u16 extended_presentation_id; + u8 n_substream_groups; + GF_List *substream_groups; // GF_AC4SubStreamGroupV1 + + // auxiliary information, not exist in DSI + GF_List *substream_group_indexs; +} GF_AC4PresentationV1; + +/*! AC-4 stream info */ +typedef struct __ac4_stream +{ + u8 ac4_dsi_version; + u8 bitstream_version; + u8 fs_index; + u8 frame_rate_index; + u8 b_iframe_global; + u8 b_program_id; + u16 short_program_id; + u8 b_uuid; + u8 program_uuid16; + GF_AC4BitrateDsi ac4_bitrate_dsi; + u16 n_presentations; + GF_List *presentations; // GF_AC4PresentationV1 +} GF_AC4StreamInfo; + +/*! AC4 config record - see dolby specs ETSI TS 103 190 */ +/* please use gf_odf_ac4_cfg_deep_copy(), gf_odf_ac4_cfg_clean_list() to copy and destroy GF_AC4Config*/ +typedef struct __ac4_config +{ + /*! streams info */ + GF_AC4StreamInfo stream; + /*! sample rate */ + u32 sample_rate; + /*! size of the complete frame*/ + u32 frame_size; + /* channel count */ + u32 channel_count; + /* sample_delta units of media time scale */ + u32 sample_duration; + /* media time scale 1/sec*/ + u32 media_time_scale; + /* sync frame header size */ + u32 header_size; + /* sync frame CRC size */ + u32 crc_size; + /* frame toc size */ + u32 toc_size; +} GF_AC4Config; + +#define GF_AC4_DESCMODE_PARSE 0 +#define GF_AC4_DESCMODE_WRITE 1 +#define GF_AC4_DESCMODE_GETSIZE 2 + +/*! parse/write/get the size of Dolby AC4 DSI V1 +\param dsi the GF_AC4StreamInfo to parse/write/get the size +\param bs the bitstream object in which to write the config +\param size the address of the size +\param desc_mode the mode should be GF_AC4_DESCMODE_PARSE/GF_AC4_DESCMODE_WRITE/GF_AC4_DESCMODE_GETSIZE +\return error if any +*/ +GF_Err gf_odf_ac4_cfg_dsi_v1(GF_AC4StreamInfo *dsi, GF_BitStream *bs, u64 *size, u8 desc_mode); + +/*! writes Dolby AC4 config to buffer +\param cfg the Dolby AC4 config to write +\param bs the bitstream object in which to write the config +\return error if any +*/ +GF_Err gf_odf_ac4_cfg_write_bs(GF_AC4Config *cfg, GF_BitStream *bs); + +/*! writes Dolby AC4 config to buffer +\param cfg the Dolby AC4 config to write +\param data set to created output buffer, must be freed by caller +\param size set to created output buffer size +\return error if any +*/ +GF_Err gf_odf_ac4_cfg_write(GF_AC4Config *cfg, u8 **data, u32 *size); + +/*! parses an AC4 sample description +\param dsi the encoded config +\param dsi_len the encoded config size +\param cfg the AC4 config to fill +\return Error if any +*/ +GF_Err gf_odf_ac4_cfg_parse(u8 *dsi, u32 dsi_len, GF_AC4Config *cfg); +/*! parses an AC4 sample description from bitstream +\param bs the bitstream object +\param cfg the AC4 config to fill +\return Error if any +*/ +GF_Err gf_odf_ac4_cfg_parse_bs(GF_BitStream *bs, GF_AC4Config *cfg); + +/*! get the size of an AC4 sample description from bitstream +\param cfg the AC4 config to fill +\return 0 if any +*/ +u64 gf_odf_ac4_cfg_size(GF_AC4Config *cfg); + +/*! copy the GF_AC4Config +\param dst the address of AC4 config to fill +\param src the address of source +*/ +void gf_odf_ac4_cfg_deep_copy(GF_AC4Config *dst, GF_AC4Config *src); + +/*! clean the GF_List data in GF_AC4Config +\param hdr the address of AC4 config to clean +*/ +void gf_odf_ac4_cfg_clean_list(GF_AC4Config *hdr); + +/*! destroy the GF_AC4Config +\param cfg the address of AC4 config to destroy +*/ +void gf_odf_ac4_cfg_del(GF_AC4Config *cfg); /*! Opus decoder config*/ @@ -1707,6 +1944,77 @@ */ GF_Err gf_odf_opus_cfg_parse_bs(GF_BitStream *bs, GF_OpusConfig *cfg); +/*! Used for storing IAMF OBUs */ +typedef struct +{ + /* Size of raw_obu_bytes, including the header and payload. + * This is different from `obu_size` in the IAMF spec Section 3.2, + * which includes only the partial header size and the payload. + */ + u64 obu_length; + int obu_type; /* IamfObuType */ + u8* raw_obu_bytes; +} GF_IamfObu; + +/*! Used for storing the IAMF configuration from the `iacb` box */ +typedef struct +{ + u8 configurationVersion; + u32 configOBUs_size; + GF_List *configOBUs; /* GF_IamfObu */ +} GF_IAConfig; + +/*! IAMF config constructor +\return the created config +*/ +GF_IAConfig *gf_odf_iamf_cfg_new(); + +/*! writes IAMF config to buffer +\param cfg the IAMF config to write +\param outData set to an allocated encoded buffer - it is the caller responsibility to free this +\param outSize set to the encoded dsi buffer size +\return error if any +*/ +GF_Err gf_odf_iamf_cfg_write(GF_IAConfig *cfg, u8 **outData, u32 *outSize); + +/*! Writes the IAMF config to bitstream +\param cfg the IAMF config to write +\param bs the bitstream object +\return error code if any +*/ +GF_Err gf_odf_iamf_cfg_write_bs(GF_IAConfig *cfg, GF_BitStream *bs); + +/*! IAMF config destructor +\param cfg the IAMF config to destroy +*/ +void gf_odf_iamf_cfg_del(GF_IAConfig *cfg); + +/*! gets GF_IAConfig from MPEG-4 DSI +\param dsi encoded IAMF decoder specific info +\param dsi_size encoded IAMF decoder specific info size +\return the decoded IAMF config + */ +GF_IAConfig *gf_odf_iamf_cfg_read(u8 *dsi, u32 dsi_size); + +/*! Reads the IAMF config from the bitstream + \param bs bitstream containing the encoded IAMF descriptors + \return the IAMF config + */ +GF_IAConfig *gf_odf_iamf_cfg_read_bs(GF_BitStream *bs); + +/*! Reads the IAMF config from the bitstream + \param bs bitstream containing the encoded IAMF descriptors + \param size size of the encoded structure in the bitstream. A value of 0 means "until the end", equivalent to gf_odf_iamf_cfg_read_bs + \return the IAMF config + */ +GF_IAConfig *gf_odf_iamf_cfg_read_bs_size(GF_BitStream *bs, u32 size); + +/*! Returns the size of the IAMF config + \param cfg the IAMF config + \return 0 if error, otherwise the IAMF config size + */ +u32 gf_odf_iamf_cfg_size(GF_IAConfig *cfg); + /*! destroy the descriptors in a list but not the list \param descList descriptor list to destroy \return error if any
View file
gpac-2.4.0.tar.gz/include/gpac/mpegts.h -> gpac-26.02.0.tar.gz/include/gpac/mpegts.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Cyril Concolato, Romain Bouqueau - * Copyright (c) Telecom ParisTech 2006-2023 + * Copyright (c) Telecom ParisTech 2006-2025 * * This file is part of GPAC / MPEG2-TS sub-project * @@ -55,6 +55,7 @@ /*! metadata types for GF_M2TS_METADATA_POINTER_DESCRIPTOR*/ enum { GF_M2TS_META_ID3 = GF_4CC('I','D','3',' '), + GF_M2TS_META_KLVA = GF_4CC('K','L','V','A'), }; @@ -74,7 +75,8 @@ GF_M2TS_ISO_639_LANGUAGE_DESCRIPTOR = 0x0A, GF_M2TS_DVB_IP_MAC_PLATFORM_NAME_DESCRIPTOR = 0x0C, GF_M2TS_DVB_IP_MAC_PLATFORM_PROVIDER_NAME_DESCRIPTOR = 0x0D, - GF_M2TS_DVB_TARGET_IP_SLASH_DESCRIPTOR = 0x0F, + GF_M2TS_MAX_BITRATE_DESCRIPTOR = 0x0E, + GF_M2TS_PRIVATE_DATA_INDICATOR_DESCRIPTOR = 0x0F, /* ... */ GF_M2TS_DVB_STREAM_LOCATION_DESCRIPTOR =0x13, /* ... */ @@ -140,7 +142,9 @@ GF_M2TS_DVB_EAC3_DESCRIPTOR = 0x7A, GF_M2TS_DVB_LOGICAL_CHANNEL_DESCRIPTOR = 0x83, - GF_M2TS_DOLBY_VISION_DESCRIPTOR = 0xB0 + GF_M2TS_DOLBY_VISION_DESCRIPTOR = 0xB0, + + GF_M2TS_DVB_EXT_DESCRIPTOR = 0x7f }; /*! Reserved PID values */ @@ -213,6 +217,7 @@ GF_M2TS_TABLE_ID_DIT = 0x7E, GF_M2TS_TABLE_ID_SIT = 0x7F, /* max size for section 4096 */ /* 0x80 - 0xfe reserved */ + GF_M2TS_TABLE_ID_SCTE35_SPLICE_INFO = 0xFC, /* 0xff reserved */ }; @@ -277,23 +282,33 @@ GF_M2TS_HLS_AAC_CRYPT = 0xcf, GF_M2TS_HLS_AVC_CRYPT = 0xdb, + GF_M2TS_VIDEO_AVS2 = 0xD2, + GF_M2TS_AUDIO_AVS2 = 0xD3, + GF_M2TS_VIDEO_AVS3 = 0xD4, + GF_M2TS_AUDIO_AVS3 = 0xD5, + /*the rest is internal use*/ - GF_M2TS_VIDEO_VC1 = 0xEA, - GF_M2TS_VIDEO_DCII = 0x80, - GF_M2TS_AUDIO_AC3 = 0x81, - GF_M2TS_AUDIO_DTS = 0x82, - GF_M2TS_AUDIO_TRUEHD = 0x83, - GF_M2TS_AUDIO_EC3 = 0x84, - GF_M2TS_MPE_SECTIONS = 0x90, - GF_M2TS_SUBTITLE_DVB = 0x100, - GF_M2TS_AUDIO_OPUS = 0x101, - GF_M2TS_VIDEO_AV1 = 0x102, - - GF_M2TS_DVB_TELETEXT = 0x152, - GF_M2TS_DVB_VBI = 0x153, - GF_M2TS_DVB_SUBTITLE = 0x154, - GF_M2TS_METADATA_ID3_HLS = 0x155, + GF_M2TS_VIDEO_VC1 = 0xEA, + GF_M2TS_VIDEO_DCII = 0x80, + GF_M2TS_AUDIO_AC3 = 0x81, + GF_M2TS_AUDIO_DTS = 0x82, + GF_M2TS_AUDIO_TRUEHD = 0x83, + GF_M2TS_AUDIO_EC3 = 0x84, + GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS = 0x86, + GF_M2TS_MPE_SECTIONS = 0x90, + GF_M2TS_SUBTITLE_DVB = 0x100, + GF_M2TS_AUDIO_OPUS = 0x101, + GF_M2TS_VIDEO_AV1 = 0x102, + GF_M2TS_AUDIO_AC4 = 0x103, + + GF_M2TS_DVB_TELETEXT = 0x152, + GF_M2TS_DVB_VBI = 0x153, + GF_M2TS_DVB_SUBTITLE = 0x154, + GF_M2TS_METADATA_ID3_HLS = 0x155, + GF_M2TS_METADATA_ID3_KLVA = 0x156, + GF_M2TS_METADATA_SRT = 0x157, + GF_M2TS_METADATA_TEXT = 0x158, } GF_M2TSStreamType; @@ -301,18 +316,24 @@ /*! MPEG-2 TS Registration codes types*/ enum { - GF_M2TS_RA_STREAM_AC3 = GF_4CC('A','C','-','3'), - GF_M2TS_RA_STREAM_EAC3 = GF_4CC('E','A','C','3'), - GF_M2TS_RA_STREAM_VC1 = GF_4CC('V','C','-','1'), - GF_M2TS_RA_STREAM_HEVC = GF_4CC('H','E','V','C'), - GF_M2TS_RA_STREAM_DTS1 = GF_4CC('D','T','S','1'), - GF_M2TS_RA_STREAM_DTS2 = GF_4CC('D','T','S','2'), - GF_M2TS_RA_STREAM_DTS3 = GF_4CC('D','T','S','3'), - GF_M2TS_RA_STREAM_OPUS = GF_4CC('O','p','u','s'), - GF_M2TS_RA_STREAM_DOVI = GF_4CC('D','O','V','I'), - GF_M2TS_RA_STREAM_AV1 = GF_4CC('A','V','0','1'), - - GF_M2TS_RA_STREAM_GPAC = GF_4CC('G','P','A','C') + GF_M2TS_RA_STREAM_AC3 = GF_4CC('A','C','-','3'), + GF_M2TS_RA_STREAM_EAC3 = GF_4CC('E','A','C','3'), + GF_M2TS_RA_STREAM_VC1 = GF_4CC('V','C','-','1'), + GF_M2TS_RA_STREAM_HEVC = GF_4CC('H','E','V','C'), + GF_M2TS_RA_STREAM_DTS1 = GF_4CC('D','T','S','1'), + GF_M2TS_RA_STREAM_DTS2 = GF_4CC('D','T','S','2'), + GF_M2TS_RA_STREAM_DTS3 = GF_4CC('D','T','S','3'), + GF_M2TS_RA_STREAM_OPUS = GF_4CC('O','p','u','s'), + GF_M2TS_RA_STREAM_DOVI = GF_4CC('D','O','V','I'), + GF_M2TS_RA_STREAM_AVSA = GF_4CC('A','V','S','A'), // AVS2-3 Audio + GF_M2TS_RA_STREAM_AVSV = GF_4CC('A','V','S','V'), // AVS2-3 Video + GF_M2TS_RA_STREAM_AV1 = GF_4CC('A','V','0','1'), + GF_M2TS_RA_STREAM_SCTE35 = GF_4CC('C','U','E','I'), + + GF_M2TS_RA_STREAM_GPAC = GF_4CC('G','P','A','C'), + GF_M2TS_RA_STREAM_SRT = GF_4CC('S','R','T',' '), + GF_M2TS_RA_STREAM_TXT = GF_4CC('T','E','X','T'), + GF_M2TS_RA_STREAM_AC4 = GF_4CC('A','C','-','4'), }; @@ -347,18 +368,12 @@ /*! Maximum number of service in a TS*/ #define GF_M2TS_MAX_SERVICES 65535 -/*! Maximum size of the buffer in UDP */ -#ifdef WIN32 -#define GF_M2TS_UDP_BUFFER_SIZE 0x80000 -#else -//fixme - issues on linux and OSX with large stack size -//we need to change default stack size for TS thread -#define GF_M2TS_UDP_BUFFER_SIZE 0x40000 -#endif - /*! Maximum PCR value */ #define GF_M2TS_MAX_PCR 2576980377811ULL +/*! Maximum PCR value in 90Khz scale */ +#define GF_M2TS_MAX_PCR_90K 8589934592 + /*! gets the stream name for an MPEG-2 stream type \param streamType the target stream type \return name of the stream type*/ @@ -495,6 +510,18 @@ GF_M2TS_EVT_TEMI_LOCATION, /*! a TEMI timecode has been found*/ GF_M2TS_EVT_TEMI_TIMECODE, + + /*! a SCTE35 splice info has been found*/ + GF_M2TS_EVT_SCTE35_SPLICE_INFO, + + /*! a generic ID3 tag has been found*/ + GF_M2TS_EVT_ID3, + + /*! a generic section has been found*/ + GF_M2TS_EVT_SECTION, + /*! a generic section has been updated*/ + GF_M2TS_EVT_SECTION_UPDATE, + /*! a stream is about to be removed - - associated parameter: pointer to GF_M2TS_ES being removed*/ GF_M2TS_EVT_STREAM_REMOVED }; @@ -599,7 +626,7 @@ METADATA_CARRIAGE_OTHER = 3 }; -/*! MPEG-2 TS demuxer metadat pointer*/ +/*! MPEG-2 TS demuxer metadata pointer*/ typedef struct tag_m2ts_metadata_pointer_descriptor { u16 application_format; u32 application_format_identifier; @@ -686,6 +713,8 @@ /*! continuity counter check for pure PCR PIDs*/ s16 pcr_cc; + s64 pcr_base_offset; + void *user; } GF_M2TS_Program; @@ -771,6 +800,7 @@ u64 DTS; /*! size of PES header*/ u8 hdr_data_len; + u8 has_pts; } GF_M2TS_PESHeader; /*! Section elementary stream*/ @@ -818,6 +848,13 @@ u8 decoder_config_service_id; } GF_M2TS_MetadataDescriptor; +enum { + GF_M2TS_AUDIO_SUBSTREAM_COMP = 1, + GF_M2TS_AUDIO_DESCRIPTION = 1<<1, + GF_M2TS_AUDIO_SUB_DESCRIPTION = 1<<2, + GF_M2TS_AUDIO_HEARING_IMPAIRED = 1<<3, +}; + //! @cond Doxygen_Suppress /*! MPEG-2 TS ES object*/ @@ -867,14 +904,15 @@ u32 last_pat_packet_number, before_last_pat_pn, before_last_pes_start_pn; /*! PES reframer callback. If NULL, pes processing is skipped - - returns the number of bytes NOT consumed from the input data buffer - these bytes are kept when reassembling the next PES packet*/ + returns the number of bytes NOT consumed from the input data buffer - these bytes are kept when reassembling the next PES packet*/ u32 (*reframe)(struct tag_m2ts_demux *ts, struct tag_m2ts_pes *pes, Bool same_pts, u8 *data, u32 data_len, GF_M2TS_PESHeader *hdr); /*! DVB subtitling info*/ GF_M2TS_DVB_Subtitling_Descriptor sub; /*! Metadata descriptor (for ID3)*/ GF_M2TS_MetadataDescriptor *metadata_descriptor; + /*! AVS3 Video descriptors*/ + u8 avs3_video_descriptor10; /*! last received TEMI payload*/ u8 *temi_tc_desc; @@ -882,14 +920,18 @@ u32 temi_tc_desc_len; /*! allocated size of TEMI reception buffer*/ u32 temi_tc_desc_alloc_size; - /*! last decoded temi (may be one ahead of time as the last received TEMI)*/ GF_M2TS_TemiTimecodeDescriptor temi_tc; /*! flag set to indicate a TEMI descriptor should be flushed with next packet*/ Bool temi_pending; + /*! flag set to indicate the last PES packet was not flushed (HLS) to avoid warning on same PTS/DTS used*/ Bool is_resume; - /*! DolbiVison info, last byte set to 1 if non-compatible signaling*/ + + Bool is_protected; + u32 audio_flags; + + /*! DolbyVison info, last byte set to 1 if non-compatible signaling*/ u8 dv_info25; u64 map_utc, map_utc_pcr, map_pcr; @@ -1064,6 +1106,8 @@ u32 pid; /*parent stream if any/already declared*/ GF_M2TS_ES *stream; + //PCR plus one, 0 if no pcr + u64 pcr_plus_one; } GF_M2TS_TSPCK; typedef struct @@ -1073,6 +1117,37 @@ u16 ex_table_id; } GF_M2TS_SectionInfo; +typedef struct +{ + u8 version_number; + u8 table_id; + u16 ex_table_id; + u32 num_sections; + /*parent stream*/ + GF_M2TS_ES *stream; + //section index from 0 to num_sections + u32 section_idx; + //section data + u8 *section_data; + u32 section_data_len; + //pts estimated at table completion + u64 pts; +} GF_M2TS_GenericSectionInfo; + + +/*! raw TS demux options*/ +typedef enum +{ + /*! regular demux mode */ + GF_M2TS_RAW_NONE = 0, + /*! split mode: only demux PAT and create streams, forward all other packets */ + GF_M2TS_RAW_SPLIT, + /*! forward mode: forward all TS packets after PCR extraction */ + GF_M2TS_RAW_FORWARD, + /*! probe mode: only parse header */ + GF_M2TS_RAW_PROBE, +} GF_M2TSRawMode; + /*! MPEG-2 TS demuxer*/ struct tag_m2ts_demux { @@ -1136,6 +1211,7 @@ struct __gf_dvb_mpe_ip_platform *ip_platform; /*! current TS packet number*/ u32 pck_number; + u32 pck_errors; /*! TS packet number of last seen packet containing PAT start */ u32 last_pat_start_num; @@ -1156,10 +1232,8 @@ /*! triggers all table reset*/ Bool table_reset; - /*! raw mode only parses PAT and PMT, and forward each packet as a GF_M2TS_EVT_PCK event, except PAT packets which must be recreated - if set, on_event shall be non-null - */ - Bool split_mode; + /*! raw demux mode */ + GF_M2TSRawMode raw_mode; }; //! @endcond @@ -1473,7 +1547,7 @@ u32 gpac_meta_dsi_size; /*! GPAC unmapped meta codec decoder config*/ u8 *gpac_meta_dsi; - /*! GPAC unmapped meta codec name if knwon*/ + /*! GPAC unmapped meta codec name if known*/ const char *gpac_meta_name; } GF_ESInterface; @@ -1606,7 +1680,7 @@ is available in PES, don't copy from next*/ u32 min_bytes_copy_from_next; /*! process PES or table update/framing - returns the priority of the stream, 0 meaning not scheduled, 1->N highest priority sent first*/ + returns the priority of the stream, 0 meaning not scheduled, 1->N highest priority sent first*/ u32 (*process)(struct __m2ts_mux *muxer, struct __m2ts_mux_stream *stream); /*! stream type*/ @@ -1680,6 +1754,9 @@ /*! list of GF_M2TSDescriptor to add to the MPEG-2 stream. By default set to NULL*/ GF_List *loop_descriptors; + /*! frame number for SRT encapsulation*/ + u32 num_frame; + /*! packet SAP type when segmenting the TS*/ u32 pck_sap_type; /*! packet SAP time (=PTS) when segmenting the TS*/ @@ -1767,15 +1844,14 @@ }; /*! AU packing per pes configuration*/ -typedef enum -{ +GF_OPT_ENUM (GF_M2TS_PackMode, /*! only audio AUs are packed in a single PES, video and systems are not (recommended default)*/ GF_M2TS_PACK_AUDIO_ONLY=0, /*! never pack AUs in a single PES*/ GF_M2TS_PACK_NONE, /*! always try to pack AUs in a single PES*/ - GF_M2TS_PACK_ALL -} GF_M2TS_PackMode; + GF_M2TS_PACK_ALL, +); /*! MPEG-2 TS muxer*/ struct __m2ts_mux { @@ -1985,7 +2061,7 @@ /*! sets initial PCR value for all programs in multiplex. If this is not called, each program will use a random initial PCR \param muxer the target MPEG-2 TS multiplexer -\param init_pcr_value initial PCR value in 90kHz +\param init_pcr_value initial PCR value in 27 MHz \return error if any */ GF_Err gf_m2ts_mux_set_initial_pcr(GF_M2TS_Mux *muxer, u64 init_pcr_value); @@ -2015,6 +2091,19 @@ */ void gf_m2ts_mux_enable_sdt(GF_M2TS_Mux *muxer, u32 refresh_rate_ms); +/*! update a given table +\param stream target stream carrying the section +\param table_id ID of the table +\param table_id_extension extended ID of the table +\param table_payload payload to send +\param table_payload_length payload length to send +\param use_syntax_indicator inject section syntax extension (extended ID and fragmentation info of the table) +\param private_indicator private indicator flag of section header +*/ +void gf_m2ts_mux_table_update(GF_M2TS_Mux_Stream *stream, u8 table_id, u16 table_id_extension, + u8 *table_payload, u32 table_payload_length, + Bool use_syntax_indicator, Bool private_indicator); + #endif /*GPAC_DISABLE_MPEG2TS_MUX*/
View file
gpac-2.4.0.tar.gz/include/gpac/network.h -> gpac-26.02.0.tar.gz/include/gpac/network.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -146,7 +146,7 @@ Returns a pointer to the first colon at the end of a filename or URL, if any. If assign_sep is specified, for example '=', the function will make sure that the colon is after the file extension if found and that '=' is not present between colon and file ext. -This is used to parse 'a:b.mp4:c' (expected result ':c...' and not ':b...') vs 'a:b=c.mp4' ' (expected result ':b') +This is used to parse 'a:b.mp4:c' (expected result ':c...' and not ':b...') vs 'a:b=c.mp4' ' (expected result ':b') \param URL path or URL to inspect \param assign_sep value of assignment operand character. If 0, only checks for colon, otherwise chec that no assign sep or colon is present before file extension, if present @@ -327,6 +327,16 @@ */ s32 gf_net_get_ntp_diff_ms(u64 ntp); +/*! + +Adds or remove a given amount of microseconds to an NTP timestamp +\param ntp NTP timestamp +\param usec microseconds to add/remove +\return adjusted NTP timestamp + */ +GF_EXPORT +u64 gf_net_ntp_add_usec(u64 ntp, s32 usec); + /*! @@ -346,6 +356,15 @@ */ const char *gf_errno_str(int errnoval); + +/*! +\brief reloads netcap filters + +Reloads netcap filters, closing all attached files and deassociating sockets - this should only be done called when reloading a session +\return error if any + */ +GF_Err gf_net_reload_netcap(); + /*! @} */ #ifndef GPAC_DISABLE_NETWORK @@ -366,8 +385,8 @@ GF_SOCK_REUSE_PORT = 1, /*!Forces IPV6 if available.*/ GF_SOCK_FORCE_IPV6 = 1<<1, - /*!Does not perfom the actual bind, only keeps address and port.*/ - GF_SOCK_FAKE_BIND = 1<<2 + /*! Indicates the socket will be used to send , only used in test modes*/ + GF_SOCK_IS_SENDER = 1<<2 }; /*! @@ -585,6 +604,17 @@ GF_Err gf_sk_get_remote_address(GF_Socket *sock, char *buffer); /*! +\brief get remote address + +Gets the remote address and port of a peer. The socket MUST be connected. +\param sock the socket object +\param buffer destination buffer for IP address. Buffer must be GF_MAX_IP_NAME_LEN long +\param port set to the remote port, may be NULL +\return error if any + */ +GF_Err gf_sk_get_remote_address_port(GF_Socket *sock, char *buffer, u32 *port); + +/*! \brief set remote address Sets the remote address of a socket. This is used by connectionless sockets using SendTo and ReceiveFrom @@ -670,6 +700,22 @@ */ GF_Err gf_sk_probe(GF_Socket *sock); +/*! +Bumps lower part of IP address by the given increment eg X.X.X.Y -> X.X.X.Z with Z=Y+increment +\param in_ip the input IP v4 or v6 address +\param increment the increment to apply +\return the newly computed address or NULL if error - must be freed bu user + */ +char *gf_net_bump_ip_address(const char *in_ip, u32 increment); + +/*! +Gets IP associated with an interface +\param ip_or_name the input interface name or IP v4 or v6 address +\param ipv4 set t o v4 address - can be NULL but shall be freed by user +\param ipv6 set t o v6 address - can be NULL but shall be freed by user +\return GF_TRUE if success + */ +Bool gf_net_get_adapter_ip(const char *ip_or_name, char **ipv4, char **ipv6); /*! socket selection mode*/ typedef enum @@ -749,4 +795,3 @@ #endif /*_GF_NET_H_*/ -
View file
gpac-26.02.0.tar.gz/include/gpac/rmt_ws.h
Added
@@ -0,0 +1,121 @@ +#ifndef RMT_WS_INCLUDED_H +#define RMT_WS_INCLUDED_H + +//! type for callbacks called when a new client connects +//! \param task user data sent back to the callback +//! \param new_client a structure representing the client (of type RMT_ClientCtx) +typedef void (*rmt_on_new_client_cbk)(void *task, void* new_client); + + +//! Handle to the main instance +typedef struct RMT_WS RMT_WS; + +//! creates the main instance +RMT_WS* rmt_ws_new(); + +//! starts the server thread +void rmt_ws_run(RMT_WS*); + +//! deletes the main instance +void rmt_ws_del(RMT_WS* rmt_ws); + +//! Struture to fill in to modify default settings +typedef struct RMT_Settings { + + //! Which port to listen for incoming connections on + u16 port; + + //! inactivity timeout before closing connections + u32 timeout_secs; + + //! time between websocket ping requests (0 to disable) + u32 ping_secs; + + + //! Only allow connections on localhost? + Bool limit_connections_to_localhost; + + //! How long to sleep between server updates + u32 msSleepBetweenServerUpdates; + + //! function to call when a new websocket connection is accepted + rmt_on_new_client_cbk on_new_client_cbk; + //! context for on_new_client_cbk + void* on_new_client_cbk_task; + + //! server certificate and private key to use for ssl websocket (null to disable wss) + const char* cert; + const char* pkey; + +} RMT_Settings; + +//! gets the current rmtws settings (creates the structure if necessary) +RMT_Settings* gf_rmt_get_settings(RMT_WS*); + +//! structure representing the http server +typedef struct __rmt_serverctx RMT_ServerCtx; + +//! structure representing a websocket client +typedef struct __rmt_clientctx RMT_ClientCtx; + +typedef enum { + RMT_CALLBACK_NONE=0x12, + RMT_CALLBACK_JS, + RMT_CALLBACK_NODE, +} RMT_Callback_type; + +//! sets the callback called when new clients connect to the sever +//! \param rmt the structure representing the server handler +//! \param task user data stored and passed back to the callback +//! \param cbk the callback of type \ref rmt_on_new_client_cbk +void gf_rmt_set_on_new_client_cbk(RMT_WS* rmt, void *task, rmt_on_new_client_cbk cbk); + +//! gets the userdata associated with the new client callback if defined +void* gf_rmt_get_on_new_client_task(RMT_WS*); + +//! gets a string representing the client in the format ip:port +//! \param client the client object +//! \return a "ip:port" string of the given client +const char* gf_rmt_get_peer_address(RMT_ClientCtx* client); + +//! sends data to a client on the websocket +//! \param client the client object +//! \param msg a buffer containing the data to send +//! \param size the size of the data to send +//! \param is_binary false if we're sending a utf8 string, true otherwise +GF_Err gf_rmt_client_send_to_ws(RMT_ClientCtx* client, const char* msg, u64 size, Bool is_binary); + +//! type for callbacks called when a client receives data on the websocket +//! \param task user data passed back to the callback +//! \param payload a buffer containing the data received +//! \param size the size of the data received +//! \param is_binary false the data is a utf8 string, true otherwise +typedef void (*rmt_client_on_data_cbk)(void* task, const u8* payload, u64 size, Bool is_binary); + +//! sets the callback called when a client receives data +//! \param client the client for which we are setting the callback +//! \param task user data stored and passed back to the callback +//! \param cbk the callback of type \ref rmt_client_on_data_cbk +void gf_rmt_client_set_on_data_cbk(RMT_ClientCtx* client, void* task, rmt_client_on_data_cbk cbk); + +//! gets the userdata associated with the client on data callback if defined +void* gf_rmt_client_get_on_data_task(RMT_ClientCtx* client); + +//! type for callbacks called when a client is deleted (e.g. on disconnects) +//! \param task user data passed back to the callback +typedef void (*rmt_client_on_del_cbk)(void* task); + +//! sets the callback called when a client is deleted +//! \param client the client for which we are setting the callback +//! \param task user data stored and passed back to the callback +//! \param cbk the callback of type \ref rmt_client_on_del_cbk +void gf_rmt_client_set_on_del_cbk(RMT_ClientCtx* client, void* task, rmt_client_on_del_cbk cbk); + +//! gets the userdata associated with the client on deleted callback if defined +void* gf_rmt_client_get_on_del_task(RMT_ClientCtx* client); + +//! gets the ws server handler associated with a client +RMT_WS* gf_rmt_client_get_rmt(RMT_ClientCtx* client); + + +#endif
View file
gpac-2.4.0.tar.gz/include/gpac/route.h -> gpac-26.02.0.tar.gz/include/gpac/route.h
Changed
@@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * - * This file is part of GPAC / ROUTE (ATSC3, DVB-I) demuxer + * This file is part of GPAC / ROUTE (ATSC3, DVB-MABR) and DVB-MABR demuxer * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -63,22 +63,26 @@ /*!The types of events used to communicate withe the demuxer user.*/ typedef enum { - /*! A new service detected, service ID is in evt_param, no file info*/ + /*! A new service is detected, service ID is in evt_param, no file info*/ GF_ROUTE_EVT_SERVICE_FOUND = 0, /*! Service scan completed, no evt_param, no file info*/ GF_ROUTE_EVT_SERVICE_SCAN, - /*! New MPD available for service, service ID is in evt_param, no file info*/ + /*! New MPD or HLS master playlist available for service, service ID is in evt_param, file info carries manifest info*/ GF_ROUTE_EVT_MPD, + /*! HLS variant update for service, service ID is in evt_param, file info carries variant info*/ + GF_ROUTE_EVT_HLS_VARIANT, /*! static file update (with predefined TOI), service ID is in evt_param*/ GF_ROUTE_EVT_FILE, /*! Segment reception, identified through a file template, service ID is in evt_param*/ GF_ROUTE_EVT_DYN_SEG, /*! fragment reception (part of a segment), identified through a file template, service ID is in evt_param - \note The data is always beginning at the start of the object + \note The data always begins at the start of the object */ GF_ROUTE_EVT_DYN_SEG_FRAG, /*! Object deletion (only for dynamic TOIs), used to notify the cache that an object is no longer available. File info only contains the filename being removed*/ GF_ROUTE_EVT_FILE_DELETE, + /*! Delayed data reception */ + GF_ROUTE_EVT_LATE_DATA, } GF_ROUTEEventType; enum @@ -111,33 +115,71 @@ } GF_LCTFragInfo; +/*! Type of partial event*/ +typedef enum +{ + /* object is done receiving*/ + GF_LCTO_PARTIAL_NONE=0, + /* object data being notified is the beginning of the payload*/ + GF_LCTO_PARTIAL_BEGIN, + /* object data being notified is the complete reception buffer (for low latency mode), POTENTIALLY with holes in it*/ + GF_LCTO_PARTIAL_ANY, +} GF_LCTObjectPartial; + /*! Structure used to communicate file objects properties to the user*/ typedef struct { /*! original file name*/ const char *filename; - /*! blob data pointer*/ + /*! mime type if known, NULL otherwise*/ + const char *mime; + /*! blob data pointer - the route user is responsible for setting the blob flags if desired*/ GF_Blob *blob; - /*! total size of object if known, 0 otherwise*/ + /*! total size of object if known, 0 otherwise (TOL not received for route, last fragment not received for mabr+flute)*/ u32 total_size; /*! object TSI*/ u32 tsi; /*! object TOI*/ u32 toi; + /*! start time in ms*/ + u32 start_time; /*! download time in ms*/ u32 download_ms; /*! flag set if file content has been modified - not set for GF_ROUTE_EVT_DYN_SEG (always true)*/ Bool updated; - - /*! number of fragments, only set for GF_ROUTE_EVT_DYN_SEG*/ - u32 nb_frags; - /*! fragment info, only set for GF_ROUTE_EVT_DYN_SEG*/ - GF_LCTFragInfo *frags; - + /*! flag set if first segment has been received for the given TSI - not set for init segments*/ + Bool first_toi_received; + + /*! number of fragments, only set for GF_ROUTE_EVT_DYN_SEG*/ + u32 nb_frags; + /*! fragment info, set for all file events - this info is shared with the LCT object being reassembled and should not be modified concurrently from route demux + Any reallocation of the fragment info SHALL be done using \ref gf_route_dmx_patch_frag_info + */ + GF_LCTFragInfo *frags; + + /*! offset of late received data, only for GF_ROUTE_EVT_LATE_DATA*/ + u32 late_fragment_offset; + + /*! for DASH,period ID, NULL otherwise*/ + char *dash_period_id; + /*! for DASH, AS ID, -1 otherwise*/ + s32 dash_as_id; + /*! for DASH, Representation ID, for HLS variant name, NULL otherwise*/ + char *dash_rep_id; + + /*partial state used for all calls + if event indicates a file transfer completion (GF_ROUTE_EVT_FILE, GF_ROUTE_EVT_DYN_SEG), this relects the corrupted state + of the reception + */ + GF_LCTObjectPartial partial; + /*! user data set to current object after callback, and passed back on next callbacks on same object Only used for GF_ROUTE_EVT_FILE, GF_ROUTE_EVT_DYN_SEG, GF_ROUTE_EVT_DYN_SEG_FRAG and GF_ROUTE_EVT_FILE_DELETE */ void *udta; + + /*! channel hint set by application; 0 if unknown*/ + u32 channel_hint; } GF_ROUTEEventFileInfo; /*! Creates a new ROUTE ATSC3.0 demultiplexer @@ -185,6 +227,18 @@ */ GF_ROUTEDmx *gf_route_dmx_new_ex(const char *ip, u32 port, const char *ifce, u32 sock_buffer_size, const char *netcap_id, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo), void *udta); +/*! Creates a new DVB MABR Flute demultiplexer +\param ip IP address of LCT session carrying the initial FDT +\param port port of LCT session carrying the initial FDT +\param ifce network interface to monitor, NULL for INADDR_ANY +\param sock_buffer_size default buffer size for the udp sockets. If 0, uses 0x2000 +\param netcap_id ID of netcap configuration to use, may be null (see gpac -h netcap) +\param on_event the user callback function +\param udta the user data passed back by the callback +\return the demultiplexer created +*/ +GF_ROUTEDmx *gf_dvb_mabr_dmx_new(const char *ip, u32 port, const char *ifce, u32 sock_buffer_size, const char *netcap_id, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo), void *udta); + /*! Deletes an ROUTE demultiplexer \param routedmx the ROUTE demultiplexer to delete */ @@ -197,23 +251,45 @@ GF_Err gf_route_dmx_process(GF_ROUTEDmx *routedmx); +/*! Checks if there are some active multicast sockets +\param routedmx the ROUTE demultiplexer +\return GF_TRUE if some multicast sockets are active, GF_FALSE otherwise + */ +Bool gf_route_dmx_has_active_multicast(GF_ROUTEDmx *routedmx); + +/*! Checks for object being timeouts - this should only be called when \ref gf_route_dmx_process returns GF_IP_NETWORK_EMPTY for the first time in a batch +\param routedmx the ROUTE demultiplexer + */ +void gf_route_dmx_check_timeouts(GF_ROUTEDmx *routedmx); + /*! Sets reordering on. \param routedmx the ROUTE demultiplexer -\param force_reorder if TRUE, the order flag in ROUTE/LCT is ignored and objects are gathered for the given time. Otherwise, if order flag is set in ROUTE/LCT, an object is considered done as soon as a new object starts -\param timeout_ms maximum delay to wait before considering the object is done when ROUTE/LCT order is not used. A value of 0 implies waiting forever (default value is 5s). +\param reorder_needed if TRUE, the order flag in ROUTE/LCT is ignored and objects are gathered for the given time. Otherwise, if order flag is set in ROUTE/LCT, an object is considered done as soon as a new object starts +\param timeout_us maximum delay in microseconds to wait before considering the object is done when ROUTE/LCT order is not used. A value of 0 implies any out-of-order packet triggers a download completion (default value is 1 ms). \return error code if any */ -GF_Err gf_route_set_reorder(GF_ROUTEDmx *routedmx, Bool force_reorder, u32 timeout_ms); +GF_Err gf_route_dmx_set_reorder(GF_ROUTEDmx *routedmx, Bool reorder_needed, u32 timeout_us); + +/*! Progressive dispatch mode for LCT objects*/ +typedef enum +{ + /*! notification is only sent once the entire object is received*/ + GF_ROUTE_DISPATCH_FULL = 0, + /*! notifications are sent whenever the first byte-range starting at 0 changes, in which case the partial field is set to GF_LCTO_PARTIAL_BEGIN*/ + GF_ROUTE_DISPATCH_PROGRESSIVE, + /*! notifications are sent whenever a new packet is received, in which case the partial field is set to GF_LCTO_PARTIAL_ANY*/ + GF_ROUTE_DISPATCH_OUT_OF_ORDER, +} GF_RouteProgressiveDispatch; /*! Allow segments to be sent while being downloaded. - + \note Files with a static TOI association are always sent once completely received, other files using TOI templating may be sent while being received if enabled. The data sent is always contiguous data since the beginning of the file in that case. - + \param routedmx the ROUTE demultiplexer -\param allow_progressive if TRUE, fragments of segments will be sent during download +\param dispatch_mode set dispatch mode \return error code if any */ -GF_Err gf_route_set_allow_progressive_dispatch(GF_ROUTEDmx *routedmx, Bool allow_progressive); +GF_Err gf_route_set_dispatch_mode(GF_ROUTEDmx *routedmx, GF_RouteProgressiveDispatch dispatch_mode); /*! Sets the service ID to tune into for ATSC 3.0 \param routedmx the ROUTE demultiplexer @@ -248,6 +324,16 @@ */ GF_Err gf_route_dmx_force_keep_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName); +/*! Set force-keep flag on object by TSI and TOI - typically used for repair +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to query +\param tsi transport service identifier +\param toi transport object identifier +\param force_keep force_keep flag. When set back to false, this does not trigger a cleanup, it is up to the application to do so +\return error if any, GF_NOT_FOUND if no such object + */ +GF_Err gf_route_dmx_force_keep_object(GF_ROUTEDmx *routedmx, u32 service_id, u32 tsi, u32 toi, Bool force_keep); + /*! Removes the first object loaded in the service \param routedmx the ROUTE demultiplexer \param service_id ID of the service to query @@ -294,7 +380,7 @@ */ u64 gf_route_dmx_get_recv_bytes(GF_ROUTEDmx *routedmx); -/*! Gather only objects with given TSI (for debug purposes) +/*! Gather only objects with given TSI (for debug purposes) \param routedmx the ROUTE demultiplexer \param tsi the target TSI, 0 for no filtering */ @@ -314,6 +400,61 @@ */ void *gf_route_dmx_get_service_udta(GF_ROUTEDmx *routedmx, u32 service_id); + +/*! Patch fragment info of object after a repair +\param routedmx the ROUTE demultiplexer +\param service_id the target service +\param finfo file info event as passed to the caller. Only tsi and toi info are used to loacate the object. The frags and nb_frags fileds are updated by this function +\param br_start start offset of byte range being patched +\param br_end end offset of byte range being patched +\return error if any + */ +GF_Err gf_route_dmx_patch_frag_info(GF_ROUTEDmx *routedmx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 br_start, u32 br_end); + +/*! Patch object size after a repair - this might be needed by repair when the file size was not known +\param routedmx the ROUTE demultiplexer +\param service_id the target service +\param finfo file info event as passed to the caller. Only tsi and toi info are used to loacate the object. The frags and nb_frags fileds are updated by this function +\param new_size the new size to set +\return error if any + */ +GF_Err gf_route_dmx_patch_blob_size(GF_ROUTEDmx *routedmx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 new_size); + +/*! Set hint to the identified object - if object has an associated channel (eg HAS representation), set the hint on this channel. The hint is passed back in the \ref GF_ROUTEEventFileInfo and is typically used to store mime type of the object + +\param routedmx the ROUTE demultiplexer +\param service_id the target service +\param tsi tsi of channel +\param toi toi of object +\param hint the new size to set +\return error if any + */ +GF_Err gf_route_dmx_set_object_hint(GF_ROUTEDmx *routedmx, u32 service_id, u32 tsi, u32 toi, u32 hint); + +/*! Set active status of a representation +\param routedmx the ROUTE demultiplexer +\param service_id the target service +\param period_id ID of the DASH period containing the representation, may be NULL +\param as_id ID of the DASH adaptation set containing the representation, may be 0 +\param rep_id ID of the period containing the representation or HLS variant playlist URL, shall not be NULL +\param is_selected representation status +\return error if any + */ +GF_Err gf_route_dmx_mark_active_quality(GF_ROUTEDmx *routedmx, u32 service_id, const char *period_id, s32 as_id, const char *rep_id, Bool is_selected); + +/*! Cancel all current transfer on all services +\param routedmx the ROUTE demultiplexer + */ +void gf_route_dmx_reset_all(GF_ROUTEDmx *routedmx); + +/*! Gets repair info for MABR +\param routedmx the ROUTE demultiplexer +\param service_id the service identifier +\param base_uri set to base URI used in MABR filenames if present or to NULL otherwise - may be NULL +\param repair_server set to repair server address if present or to NULL otherwise - may be NULL + */ +void gf_route_dmx_get_repair_info(GF_ROUTEDmx *routedmx, u32 service_id, const char **base_uri, const char **repair_server);; + /*! @} */ #ifdef __cplusplus } @@ -322,4 +463,3 @@ #endif /* GPAC_DISABLE_ROUTE */ #endif //_GF_ROUTE_H_ -
View file
gpac-2.4.0.tar.gz/include/gpac/rtp_streamer.h -> gpac-26.02.0.tar.gz/include/gpac/rtp_streamer.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / media tools sub-project @@ -74,7 +74,7 @@ \param sample_rate audio sample rate \param nb_ch number of channels in audio streams \param is_crypted Boolean indicating if the stream is crypted -\param IV_length lenght of the Initialisation Vector used for encryption +\param IV_length length of the Initialisation Vector used for encryption \param KI_length length of the key index \param MinSize minimum AU size, 0 if unknown \param MaxSize maximum AU size, 0 if unknown @@ -200,12 +200,12 @@ \param tx text window horizontal offset \param ty text window vertical offset \param tl text window z-index -\param nb_chan number of audio channels, 0 if unknown +\param nb_channels number of audio channels, 0 if unknown \param for_rtsp if GF_TRUE, produces the SDP for an RTSP describe (no port info) \param out_sdp_buffer location to the SDP buffer to allocate and fill \return error if any */ -GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, const u8 *dsi_enh, u32 dsi_enh_len, char *KMS_URI, u32 width, u32 height, u32 tw, u32 th, s32 tx, s32 ty, s16 tl, u32 nb_chan, Bool for_rtsp, char **out_sdp_buffer); +GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, const u8 *dsi_enh, u32 dsi_enh_len, char *KMS_URI, u32 width, u32 height, u32 tw, u32 th, s32 tx, s32 ty, s16 tl, u32 nb_channels, Bool for_rtsp, char **out_sdp_buffer); /*! sends a full Access Unit over RTP \param rtp the target RTP streamer @@ -309,7 +309,7 @@ GF_Err gf_rtp_streamer_set_interleave_callbacks(GF_RTPStreamer *streamer, GF_Err (*RTP_TCPCallback)(void *cbk1, void *cbk2, Bool is_rtcp, u8 *pck, u32 pck_size), void *cbk1, void *cbk2); -/*! callback function for procesing RTCP receiver reports +/*! callback function for processing RTCP receiver reports \param cbk user data passed to \ref gf_rtp_streamer_read_rtcp \param ssrc ssrc for this report, 0 if same as ssrc of channel \param rtt_ms round-trip time estimate in ms @@ -338,6 +338,12 @@ */ u32 gf_rtp_streamer_get_timescale(GF_RTPStreamer *streamer); +/*! gets codecid of this streamer +\param streamer the target RTP streamer +\return CodecID +*/ +u32 gf_rtp_streamer_get_codecid(GF_RTPStreamer *streamer); + /*! @} */ #ifdef __cplusplus @@ -347,4 +353,3 @@ #endif //GPAC_DISABLE_ISOM && GPAC_DISABLE_STREAMING #endif /*_GF_RTPSTREAMER_H_*/ -
View file
gpac-2.4.0.tar.gz/include/gpac/setup.h -> gpac-26.02.0.tar.gz/include/gpac/setup.h
Changed
@@ -384,6 +384,36 @@ #define NULL 0 #endif + +#ifdef GPAC_HAS_FD +#ifndef WIN32 +#include <unistd.h> +#define lseek_64 lseek +#define O_BINARY 0 +#else +#include <io.h> +#define lseek_64 _lseeki64 +#endif +#include <fcntl.h> +#include <sys/stat.h> + +#if defined(WIN32) && !defined(open) +# define open _open +# define close _close +# define read _read +# define write _write +#endif + +#if defined(WIN32) && !defined(S_IWUSR) +# define S_IWUSR _S_IWRITE +# define S_IRUSR _S_IREAD +# define S_IRGRP 0 +# define S_IWGRP 0 +# define S_IROTH 0 +# endif + +#endif + //! @endcond @@ -413,6 +443,12 @@ #define GF_INT_MAX INT_MAX /*! min possible value for s32*/ #define GF_INT_MIN INT_MIN +/*! max possible value for u64*/ +#define GF_UINT64_MAX ULLONG_MAX +/*! max possible value for s64*/ +#define GF_INT64_MAX LLONG_MAX +/*! min possible value for s64*/ +#define GF_INT64_MIN LLONG_MIN #ifndef MIN /*! get the smallest of two numbers*/ @@ -423,7 +459,7 @@ #define MAX(X, Y) ((X)>(Y)?(X):(Y)) #endif -/*! get the absolute difference betwee two numbers*/ +/*! get the absolute difference between two numbers*/ #define ABSDIFF(a, b) ( ( (a) > (b) ) ? ((a) - (b)) : ((b) - (a)) ) #ifndef ABS @@ -439,6 +475,10 @@ } Bool; #endif +#define GF_OPT_ENUM(name, ...) \ + typedef enum { __VA_ARGS__ } name##_t; \ + typedef u32 name + /*! 32 bit fraction*/ typedef struct { s32 num; @@ -857,6 +897,17 @@ #endif #endif + +//! @cond Doxygen_Suppress +/*needed for unittests (disabled)*/ +#ifndef GF_STATIC +#define GF_STATIC static +#endif +#ifndef GF_NOT_EXPORTED +#define GF_NOT_EXPORTED +#endif +//! @endcond + #ifdef __cplusplus } #endif
View file
gpac-2.4.0.tar.gz/include/gpac/svg_types.h -> gpac-26.02.0.tar.gz/include/gpac/svg_types.h
Changed
@@ -532,7 +532,7 @@ SVG_COLOR_INACTIVE_CAPTION, /* Inactive window caption. */ SVG_COLOR_INACTIVE_CAPTION_TEXT, /*Color of text in an inactive caption. */ SVG_COLOR_INFO_BACKGROUND, /* Background color for tooltip controls. */ - SVG_COLOR_INFO_TEXT, /*Text color for tooltip controls. */ + SVG_COLOR_INFO_TEXT, /*Text color for tooltip controls. */ SVG_COLOR_MENU, /*Menu background. */ SVG_COLOR_MENU_TEXT, /* Text in menus. */ SVG_COLOR_SCROLLBAR, /* Scroll bar gray area. */ @@ -790,7 +790,7 @@ /*! SVG zoom and pan*/ typedef u8 SVG_ZoomAndPan; -/*! SVG lenght adjust types */ +/*! SVG length adjust types */ enum { LENGTHADJUST_UNKNOWN = 0, LENGTHADJUST_SPACING = 1,
View file
gpac-2.4.0.tar.gz/include/gpac/sync_layer.h -> gpac-26.02.0.tar.gz/include/gpac/sync_layer.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / SL header file @@ -133,7 +133,7 @@ bin128 constant_IV; /*version_number are pushed from m2ts sections to the mpeg4sl layer so as to handle mpeg4 stream dependencies*/ u8 m2ts_version_number_plus_one; - //0: not mpeg-2 TS PCR, 1: MEPG-2 TS PCR, 2: MPEG-2 TS PCR with discontinuity + //0: not mpeg-2 TS PCR, 1: MPEG-2 TS PCR, 2: MPEG-2 TS PCR with discontinuity u8 m2ts_pcr; /* HTML5 MSE Packet info */ s64 timeStampOffset; @@ -169,7 +169,7 @@ \param PDULength the size of the SL packet \param HeaderLen set to size of the SL header - payload will start at PDU + *HeaderLen */ -void gf_sl_depacketize(GF_SLConfig *slConfig, GF_SLHeader *Header, const u8 *PDU, u32 PDULength, u32 *HeaderLen); +void gf_odf_sl_depacketize(GF_SLConfig *slConfig, GF_SLHeader *Header, const u8 *PDU, u32 PDULength, u32 *HeaderLen); /*! @} */
View file
gpac-2.4.0.tar.gz/include/gpac/thread.h -> gpac-26.02.0.tar.gz/include/gpac/thread.h
Changed
@@ -106,6 +106,8 @@ #define safe_int_add(__v, inc_val) InterlockedExchangeAdd((int *) (__v), inc_val) #define safe_int_sub(__v, dec_val) InterlockedExchangeAdd((int *) (__v), -dec_val) +/*! atomic add and gets the value *before* the add */ +#define safe_int_fetch_add(__v, inc_val) InterlockedExchangeAdd((int *) (__v), inc_val) #ifdef GPAC_64_BITS #define safe_int64_add(__v, inc_val) InterlockedExchangeAdd64((LONGLONG *) (__v), inc_val) #define safe_int64_sub(__v, dec_val) InterlockedExchangeAdd64((LONGLONG *) (__v), -dec_val) @@ -129,7 +131,8 @@ #define safe_int64_add(__v, inc_val) InterlockedAdd64((LONG64 *) (__v), inc_val) /*! atomic large integer subtraction */ #define safe_int64_sub(__v, dec_val) InterlockedAdd64((LONG64 *) (__v), -dec_val) - +/*! atomic add and gets the value *before* the add */ +#define safe_int_fetch_add(__v, inc_val) InterlockedExchangeAdd((int *) (__v), inc_val) #endif //winxp #else //not windows @@ -148,6 +151,8 @@ #define safe_int64_add(__v, inc_val) __atomic_add_fetch((int64_t *) (__v), inc_val, __ATOMIC_SEQ_CST) /*! atomic large integer subtraction */ #define safe_int64_sub(__v, dec_val) __atomic_sub_fetch((int64_t *) (__v), dec_val, __ATOMIC_SEQ_CST) +/*! atomic add and gets the value *before* the add */ +#define safe_int_fetch_add(__v, inc_val) __atomic_fetch_add((int *) (__v), inc_val, __ATOMIC_SEQ_CST) #else @@ -163,6 +168,8 @@ #define safe_int64_add(__v, inc_val) __sync_add_and_fetch((int64_t *) (__v), inc_val) /*! atomic large integer subtraction */ #define safe_int64_sub(__v, dec_val) __sync_sub_and_fetch((int64_t *) (__v), dec_val) +/*! atomic add and gets the value *before* the add */ +#define safe_int_fetch_add(__v, inc_val) __sync_fetch_and_add((int *) (__v), inc_val) #endif //GPAC_NEED_LIBATOMIC
View file
gpac-2.4.0.tar.gz/include/gpac/tools.h -> gpac-26.02.0.tar.gz/include/gpac/tools.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -73,6 +73,11 @@ #define GF_4CC(a,b,c,d) ((((u32)a)<<24)|(((u32)b)<<16)|(((u32)c)<<8)|((u32)d)) #endif +/*! Macro formatting 4CC from compiler-constant string of 4 characters +\hideinitializer + */ +#define GF_4CC_CSTR(s) GF_4CC(s0,s1,s2,s3) + /*! minimum buffer size to hold any 4CC in string format*/ #define GF_4CC_MSIZE 10 @@ -144,7 +149,7 @@ GF_SCRIPT_ERROR = -8, /*! Buffer is too small to contain decoded data. Decoders shall use this error whenever they need to resize their output memory buffers*/ GF_BUFFER_TOO_SMALL = -9, - /*! The bitstream is not compliant to the specfication it refers to*/ + /*! The bitstream is not compliant to the specification it refers to*/ GF_NON_COMPLIANT_BITSTREAM = -10, /*! No filter could be found to handle the desired media type*/ GF_FILTER_NOT_FOUND = -11, @@ -206,7 +211,9 @@ /*! filter PID config requires new instance of filter */ GF_REQUIRES_NEW_INSTANCE = -56, /*! filter PID config cannot be supported by this filter, no use trying to find an alternate input filter chain*/ - GF_FILTER_NOT_SUPPORTED = -57 + GF_FILTER_NOT_SUPPORTED = -57, + /*! server does not support range requests: response with status=200 to a request with byte range*/ + GF_IO_BYTE_RANGE_NOT_SUPPORTED = -58, } GF_Err; /*! @@ -409,6 +416,122 @@ */ Bool gf_strict_atoui(const char* str, u32* ans); +/*! +\brief formats a duration + +Formats a duration into a string +\param dur duration expressed in timescale +\param timescale number of ticks per second in duration +\param szDur the buffer to format +\return the formated input buffer +*/ +const char *gf_format_duration(u64 dur, u32 timescale, char szDur100); + +/*! +\brief timecode type + */ +typedef struct +{ + Float max_fps; + u16 n_frames; + u8 hours, minutes, seconds; + u8 drop_frame, negative; + u8 counting_type; +} GF_TimeCode; + +/*! +\brief formats a timecode + +Formats a timecode into a string +\param tc timecode to format +\param szTimecode the buffer to format +\return the formated input buffer +*/ +const char* gf_format_timecode(GF_TimeCode *tc, char szTimecode100); + +/*! +\brief converts a timecode to timestamp + +Converts a timecode to a timestamp in the given timescale +\param tc timecode to convert +\param timescale timescale to convert to +\return the timestamp in the given timescale +*/ +u64 gf_timecode_to_timestamp(GF_TimeCode *tc, u32 timescale); + +/*! +\brief compare timecodes + +Compares two timecodes +\param value1 value to compare +\param value2 value to compare +\return GF_TRUE if value1 is stricly less than value2 + */ +Bool gf_timecode_less(GF_TimeCode *value1, GF_TimeCode *value2); + +/*! +\brief compare timecodes + +Compares two timecodes +\param value1 value to compare +\param value2 value to compare +\return GF_TRUE if value1 is stricly less than or equal to value2 + */ +Bool gf_timecode_less_or_equal(GF_TimeCode *value1, GF_TimeCode *value2); + +/*! +\brief compare timecodes + +Compares two timecodes +\param value1 value to compare +\param value2 value to compare +\return GF_TRUE if value1 is stricly greater than value2 + */ +Bool gf_timecode_greater(GF_TimeCode *value1, GF_TimeCode *value2); + +/*! +\brief compare timecodes + +Compares two timecodes +\param value1 value to compare +\param value2 value to compare +\return GF_TRUE if value1 is stricly greater than or equal to value2 + */ +Bool gf_timecode_greater_or_equal(GF_TimeCode *value1, GF_TimeCode *value2); + +/*! +\brief compare timecodes + +Compares two timecodes +\param value1 value to compare +\param value2 value to compare +\return GF_TRUE if value1 is equal to value2 + */ +Bool gf_timecode_equal(GF_TimeCode *value1, GF_TimeCode *value2); + + +/*! +\brief Get CENC IV size + +Get CENC IV size from a key info chunk +\param key_info CENC key info buffer +\param key_info_size CENC key info buffer size +\param key_idx index of key for multi-key cases, 0 otherwise +\param const_iv_size set to const IV size if const IV is used, otherwise set to 0 - can be NULL +\param const_iv set to const IV start in key_info buffer when constant IV is used, otherwise set to NULL - can be NULL +\return IV size in bytes if constant IV is not used, otherwise 0 + */ +u8 gf_cenc_key_info_get_iv_size(const u8 *key_info, u32 key_info_size, u32 key_idx, u8 *const_iv_size, const u8 **const_iv); + +/*! +\brief validate a CENC key info chunk + +Checks whether a CENC key info chunk is valid or not +\param key_info CENC key info buffer +\param key_info_size CENC key info buffer size +\return GF_TRUE if this chunk looks like a CENC key info buffer, GF_FALSE otherwise +*/ +Bool gf_cenc_validate_key_info(const u8 *key_info, u32 key_info_size); /*! @} */ @@ -424,9 +547,9 @@ The library can also be configured from your program using \ref gf_opts_set_key and related functions right after initializing the library. -For more information on configuration options, see \code gpac -hx core \endcode and https://wiki.gpac.io/core_options +For more information on configuration options, see \code gpac -hx core \endcode and https://wiki.gpac.io/Filters/core_options -For more information on filters configuration options, see https://wiki.gpac.io/Filters +For more information on filters configuration options, see https://wiki.gpac.io/Filters/Filters @{ */ @@ -618,39 +741,40 @@ */ const char *gf_sys_features(Bool disabled); -/*! callback function for remotery profiler - \param udta user data passed by \ref gf_sys_profiler_set_callback - \param text string sent by webbrowser client -*/ -typedef void (*gf_rmt_user_callback)(void *udta, const char* text); +/*! solves path starting with replacement keywords: + - $GDOCS: replaced by path to user document , OS-specific + - application document directory for iOS + - EXTERNAL_STORAGE environment variable if present or '/sdcard' otherwise for Android + - user home directory for other platforms + - $GCFG: replaced by path to GPAC config directory for the current profile -/*! Enables remotery profiler callback. If remotery is enabled, commands sent via webbrowser client will be forwarded to the callback function specified -\param udta user data -\param rmt_usr_cbk callback function -\return GF_OK if success, GF_BAD_PARAM if profiler is not running, GF_NOT_SUPPORTED if profiler not supported +\param tpl_path url to translate, must start with $GDOCS or $GCFG +\param szPath path to store the result +\return GF_TRUE if success, GF_FALSE otherwise. */ -GF_Err gf_sys_profiler_set_callback(void *udta, gf_rmt_user_callback rmt_usr_cbk); - +Bool gf_sys_solve_path(const char *tpl_path, char szPathGF_MAX_PATH); -/*! Sends a log message to remotery web client -\param msg text message to send. The message format should be json -\return GF_OK if success, GF_BAD_PARAM if profiler is not running, GF_NOT_SUPPORTED if profiler not supported +/*! Enables or disables the rmt websocket monitoring server +\param start If true starts the webserver, if false stops it +\return GF_OK if success, GF_BAD_PARAM if error, GF_NOT_SUPPORTED if ws server not supported */ -GF_Err gf_sys_profiler_log(const char *msg); +GF_Err gf_sys_enable_rmtws(Bool start); -/*! Sends a message to remotery web client -\param msg text message to send. The message format should be json -\return GF_OK if success, GF_BAD_PARAM if profiler is not running, GF_NOT_SUPPORTED if profiler not supported +/*! Enables or disables the rmt websocket user server +\param start If true starts the webserver, if false stops it +\return GF_OK if success, GF_BAD_PARAM if error, GF_NOT_SUPPORTED if ws server not supported */ -GF_Err gf_sys_profiler_send(const char *msg); +GF_Err gf_sys_enable_userws(Bool start); -/*! Enables sampling times in RMT - \param enable if GF_TRUE, sampling will be enabled, otherwise disabled*/ -void gf_sys_profiler_enable_sampling(Bool enable); +/*! Returns the monitoring websocket server handler +\return the object to cast to RMT_WS* +*/ +void* gf_sys_get_rmtws(); -/*! Checks if sampling is enabled in RMT. Sampling is by default enabled when enabling remotery - \return GF_TRUE if sampling is enabled, GF_FALSE otherwise*/ -Bool gf_sys_profiler_sampling_enabled(); +/*! Returns the user websocket server handler +\return the object to cast to RMT_WS* +*/ +void* gf_sys_get_userws(); /*! GPAC Log tools @@ -811,6 +935,8 @@ GF_LOG_CONSOLE, /*! Log for all messages coming the application, not used by libgpac or the modules*/ GF_LOG_APP, + /*! Log for all info regarding the rmt_ws server and bindings*/ + GF_LOG_RMTWS, /*! special value used to set a level for all tools*/ GF_LOG_ALL, @@ -890,7 +1016,7 @@ Gets log tool name \param log_tool tool to check -\return name, or "unknwon" if not known +\return name, or "unknown" if not known */ const char *gf_log_tool_name(GF_LOG_Tool log_tool); @@ -938,6 +1064,15 @@ */ Bool gf_log_use_file(); +/*! +\brief Parses a log tool + +Parses a log tool by name +\param logs the name to parse +\return log tool value +*/ +u32 gf_log_parse_tool(const char *logs); + #ifdef GPAC_DISABLE_LOG void gf_log_check_error(u32 ll, u32 lt); #define GF_LOG(_ll, _lm, __args) gf_log_check_error(_ll, _lm); @@ -1104,16 +1239,32 @@ GF_Err gf_bin128_parse(const char *string, bin128 value); +/*! blob range status */ +typedef enum +{ + /*! blob range is valid */ + GF_BLOB_RANGE_VALID=0, + /*! blob range is not valid, still in transfer */ + GF_BLOB_RANGE_IN_TRANSFER, + /*! blob range is not in transfer and is (partially or completely) lost */ + GF_BLOB_RANGE_CORRUPTED, +} GF_BlobRangeStatus; + +/*! blob flags*/ enum { + /*! blob is in transfer */ GF_BLOB_IN_TRANSFER = 1, + /*! blob is corrupted */ GF_BLOB_CORRUPTED = 1<<1, + /*! blob is parsable (valid mux format) but had partial repair only (media holes) */ + GF_BLOB_PARTIAL_REPAIR = 1<<2 }; /*! * Blob structure used to pass data pointer around */ -typedef struct +typedef struct __gf_blob { /*! data block of blob */ u8 *data; @@ -1127,6 +1278,15 @@ /*! blob mutex for multi-thread access */ struct __tag_mutex *mx; #endif + /*! last blob modification time (write access) in microsec , 0 if unknown*/ + u64 last_modification_time; + /*! function used to query if a range of a blob in transfer is valid. If NULL, any range is invalid until transfer is done + when set this function overrides the blob flags for gf_blob_query_range + If check_when_complete is true, range will be checked when blob transfer is over; if false, range will either be valid or corrupted for a transferred blob + size is updated to the maximum number of consecutive bytes starting from the goven offset */ + GF_BlobRangeStatus (*range_valid)(struct __gf_blob *blob, Bool check_when_complete, u64 start, u32 *size); + /*! private data for range_valid function*/ + void *range_udta; } GF_Blob; /*! @@ -1140,12 +1300,39 @@ GF_Err gf_blob_get(const char *blob_url, u8 **out_data, u32 *out_size, u32 *blob_flags); /*! + * Checks if a given byte range is valid in blob +\param blob blob object +\param check_when_complete when true, range will be checked when blob transfer is over; when false, range will either be valid or corrupted for a transferred blob) +\param start_offset start offset of data to check in blob +\param size size of data to check in blob +\return blob range status + */ +GF_BlobRangeStatus gf_blob_query_range(GF_Blob *blob, Bool check_when_complete, u64 start_offset, u32 size); + +/*! * Releases blob data \param blob_url URL of blob object (ie gmem://%p) \return error code */ GF_Err gf_blob_release(const char *blob_url); + +/*! + * Retrieves data associated with a blob. If success, \ref gf_blob_release_ex must be called after this +\param blob the blob object +\param out_data if success, set to blob data pointer +\param out_size if success, set to blob data size +\param blob_flags if success, set to blob flags - may be NULL +\return error code + */ +GF_Err gf_blob_get_ex(GF_Blob *blob, u8 **out_data, u32 *out_size, u32 *blob_flags); +/*! + * Releases blob data +\param blob the blob object +\return error code + */ +GF_Err gf_blob_release_ex(GF_Blob *blob); + /*! * Registers a new blob \param blob blob object @@ -1301,6 +1488,38 @@ */ u32 gf_sys_get_process_id(); + +/*! lockfile status*/ +typedef enum { + /*! lockfile creation failed*/ + GF_LOCKFILE_FAILED=0, + /*! lockfile creation succeeded, creating a new lock file*/ + GF_LOCKFILE_NEW, + /*! lockfile creation succeeded, lock file was already present and created by this process*/ + GF_LOCKFILE_REUSE +} GF_LockStatus; + +/*! +\brief Creates a lock file + +Creates a lock file for the current process. A lockfile contains a single string giving the creator process ID +If a lock file exists with a process ID no longer running, the lock file will be granted to the caller. +Lock files are removed using \ref gf_file_delete +\param lockfile name of the lockfile +\return return status +*/ +GF_LockStatus gf_sys_create_lockfile(const char *lockfile); + +/*! +\brief Checks a process is valid + +Checks if a process is running by its ID +\param process_id process ID +\return GF_TRUE if process is running, GF_FALSE otherwise +*/ +Bool gf_sys_check_process_id(u32 process_id); + + /*!\brief run-time system info object The Run-Time Info object is used to get CPU and memory occupation of the calling process. @@ -1454,8 +1673,8 @@ /** -Gets a newly allocated string containing the default cache directory. -It is the responsibility of the caller to free the string. +Gets a globally allocated string containing the default cache directory. +The caller shall not free the string. \return a fully qualified path to the default cache directory */ const char * gf_get_default_cache_directory(); @@ -1580,6 +1799,15 @@ u64 gf_fsize(FILE *fp); /*! +\brief file size helper for a file descriptor + +Gets the file size given a file descriptor. +\param fd file descriptor to check +\return file size in bytes +*/ +u64 gf_fd_fsize(int fd); + +/*! \brief file IO checker Checks if the given FILE object is a native FILE or a GF_FileIO wrapper. @@ -1621,12 +1849,13 @@ \brief file opening Opens a file, potentially using file IO if the parent URL is a File IO wrapper + +\note If a parent GFIO file is used and file_name was obtained without using \ref gf_url_concatenate on the parent GFIO, make sur to call \ref gf_fileio_open_url in "url" mode to prevent further concatenations by the GFIO handler. + \param file_name same as fopen \param parent_url URL of parent file. If not a file io wrapper (gfio://), the function is equivalent to gf_fopen \param mode same as fopen - value "mkdir" checks if parent dir(s) need to be created, create them if needed and returns NULL (no file open) \param no_warn if GF_TRUE, do not throw log message if failure -\return stream handle of the file object -\note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. \return stream habdle of the file or file IO object*/ FILE *gf_fopen_ex(const char *file_name, const char *parent_url, const char *mode, Bool no_warn); @@ -1776,6 +2005,16 @@ \return GF_TRUE if file exists */ Bool gf_file_exists_ex(const char *file_name, const char *par_name); +/*! +\brief Open file descriptor + +Opens a file descriptor - this is simply a wrapper aroun open taking care of UTF8 for windows +\param file_name path of the file to check +\param oflags same parameters as open flags for open +\param pflags same parameters as permission flags for open +\return file descriptor, -1 if error*/ +s32 gf_fd_open(const char *file_name, u32 oflags, u32 pflags); + /*! File IO wrapper object*/ typedef struct __gf_file_io GF_FileIO; @@ -1786,7 +2025,7 @@ \param mode opening mode of file, same as fopen mode. The following additional modes are defined: - "ref": indicates this FileIO object is used by some part of the code and must not be destroyed upon closing of the file. Associated URL is null - "unref": indicates this FileIO object is not used by some part of the code and may be destroyed if no more references to this object are set. Associated URL is null - - "url": indicates to create a new FileIO object for the given URL without opening the output file. The resulting FileIO object must be garbage collected by the app in case its is never used by the callers + - "url": indicates to create a new FileIO object for the given URL without opening the output file. The resulting FileIO object must be garbage collected by the app in case it is never used by the callers - "probe": checks if the file exists, but no need to open the file. The function should return NULL in this case. If file does not exist, set out_error to GF_URL_ERROR - "close": indicates the fileIO object is being closed (fclose) \param out_error must be set to error code if any (never NULL) @@ -1989,6 +2228,27 @@ */ Bool gf_fileio_write_mode(GF_FileIO *fileio); +/*! Function callback for GF_FileIO delete events. The callback is NOT thread-safe in GPAC, applications should take care of ensuring safety + +\note Applications must make sure that the underlying gfio objects are defined as GPAC does not track allocated gfio objects. + + \param url a gfio:// url to be deleted or a regular name if parent_gfio_url is NULL. + \param parent_gfio_url the parent GFIO associated with the URL string, or NULL if url is a gfio url + \return error code if any, GF_EOS if this callback is not handling this specific gfio +*/ +typedef GF_Err (*gfio_delete_proc)(const char *url, const char *parent_gfio_url); + +/*! Register a GF_FileIO delete callback. This function is NOT threadsafe, applications should take care of ensuring safety +\param del_proc the calback to register. It MUST be valid until unregistered +\return error if any +*/ +GF_Err gf_fileio_register_delete_proc(gfio_delete_proc del_proc); + +/*! Unregister a GF_FileIO delete callback. This function is NOT threadsafe, applications should take care of ensuring safety +\param del_proc the calback to unregister. +*/ +void gf_fileio_unregister_delete_proc(gfio_delete_proc del_proc); + /*! @} */ /*! @@ -2184,7 +2444,7 @@ /*! gets SHA-1 of input buffer \param buf input buffer to hash -\param buflen sizeo of input buffer in bytes +\param buflen size of input buffer in bytes \param digest buffer to store message digest */ void gf_sha1_csum(u8 *buf, u32 buflen, u8 digestGF_SHA1_DIGEST_SIZE); @@ -2194,12 +2454,22 @@ #define GF_SHA256_DIGEST_SIZE 32 /*! gets SHA-256 of input buffer \param buf input buffer to hash -\param buflen sizeo of input buffer in bytes +\param buflen size of input buffer in bytes \param digest buffer to store message digest */ void gf_sha256_csum(const void *buf, u64 buflen, u8 digestGF_SHA256_DIGEST_SIZE); +/*! checksum size for MD5*/ +#define GF_MD5_DIGEST_SIZE 16 + +/*! gets MD5 of input buffer +\param buf input buffer to hash +\param buflen size of input buffer in bytes +\param digest buffer to store message digest + */ +void gf_md5_csum(const void *buf, u32 buflen, u8 digestGF_MD5_DIGEST_SIZE); + /*! \addtogroup libsys_grp @{ @@ -2316,43 +2586,18 @@ //! @cond Doxygen_Suppress -#if defined(GPAC_DISABLE_3D) && !defined(GPAC_DISABLE_REMOTERY) -#define GPAC_DISABLE_REMOTERY 1 -#endif - -#ifdef GPAC_DISABLE_REMOTERY +#ifdef GPAC_DISABLE_RMTWS #define RMT_ENABLED 0 -#else -#define RMT_USE_OPENGL 1 #endif -#include <gpac/Remotery.h> - -#define GF_RMT_AGGREGATE RMTSF_Aggregate -/*! begins remotery CPU sample*/ -#define gf_rmt_begin rmt_BeginCPUSample -/*! begins remotery CPU sample with hash*/ -#define gf_rmt_begin_hash rmt_BeginCPUSampleStore -/*! ends remotery CPU sample*/ -#define gf_rmt_end rmt_EndCPUSample -/*! sets remotery thread name*/ -#define gf_rmt_set_thread_name rmt_SetCurrentThreadName -/*! logs remotery text*/ -#define gf_rmt_log_text rmt_LogText -/*! begins remotery OpenGL sample*/ -#define gf_rmt_begin_gl rmt_BeginOpenGLSample -/*! begins remotery OpenGL sample with hash*/ -#define gf_rmt_begin_gl_hash rmt_BeginOpenGLSampleStore -/*!ends remotery OpenGL sample*/ -#define gf_rmt_end_gl rmt_EndOpenGLSample +#include <gpac/rmt_ws.h> //! @endcond /* \cond dummy */ -/*to call whenever the OpenGL library is opened - this function is needed to bind OpenGL and remotery, and to load -OpenGL extensions on windows +/*to call whenever the OpenGL library is opened - this function is needed to load OpenGL extensions on windows not exported, and not included in src/compositor/gl_inc.h since it may be needed even when no OpenGL calls are made by the caller*/ void gf_opengl_init();
View file
gpac-2.4.0.tar.gz/include/gpac/version.h -> gpac-26.02.0.tar.gz/include/gpac/version.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2012-2023 + * Copyright (c) Telecom ParisTech 2012-2026 * All rights reserved * * This file is part of GPAC @@ -42,13 +42,13 @@ * SONAME versions must be digits (not strings) */ /*! Macro giving GPAC version name expressed as a printable string*/ -#define GPAC_VERSION "2.4" +#define GPAC_VERSION "26.02" // WARNING: when bumping, reflect the changes in share/python/libgpac.py !! /*! ABI Major number of libgpac */ -#define GPAC_VERSION_MAJOR 12 +#define GPAC_VERSION_MAJOR 16 /*! ABI Minor number of libgpac */ -#define GPAC_VERSION_MINOR 14 +#define GPAC_VERSION_MINOR 5 /*! ABI Micro number of libgpac */ #define GPAC_VERSION_MICRO 0
View file
gpac-2.4.0.tar.gz/include/gpac/xml.h -> gpac-26.02.0.tar.gz/include/gpac/xml.h
Changed
@@ -98,7 +98,9 @@ /*! list of children nodes of the node, for XML node type only - can be NULL if no content*/ GF_List *content; /*! original pos in parent (used for DASH MPD)*/ - u32 orig_pos; + u16 orig_pos; + /*! set to 1 if content comes from an existing XML and character checking should be skipped*/ + u16 valid_content; } GF_XMLNode; /*! @} */ @@ -283,6 +285,12 @@ */ u32 gf_xml_dom_get_line(GF_DOMParser *parser); +/*! Enable marking all text nodes as already validated , so that re-serializing will skip text string checking +\param dom the dom parser +\return error if any + */ +GF_Err gf_xml_dom_enable_passthrough(GF_DOMParser *dom); + /*! Gets the number of root nodes in the document (not XML compliant, but used in DASH for remote periods) \param parser the DOM parser to use \return the number of root elements in the document @@ -295,7 +303,7 @@ */ GF_XMLNode *gf_xml_dom_get_root_idx(GF_DOMParser *parser, u32 idx); -/*! Gets the root node of the document and assign the parser root to NULL. +/*! Gets the root node of the document and assigns the parser root to NULL. \param parser the DOM parser to use \return the root element at the given index, or NULL if error. The element must be freed by the caller */ @@ -309,7 +317,6 @@ */ char *gf_xml_dom_serialize(GF_XMLNode *node, Bool content_only, Bool no_escape); - /*! Serialize a root document node - same as \ref gf_xml_dom_serialize but insert \code <?xml version="1.0" encoding="UTF-8"?> \endcode at beginning \param node the node to flush \param content_only Whether to include or not the parent node @@ -367,7 +374,7 @@ \param expected_ns_prefix optional expected namespace prefix for node n \return error code or GF_OK */ -GF_Err gf_xml_get_element_check_namespace(const GF_XMLNode *n, const char *expected_node_name, const char *expected_ns_prefix); +GF_Err gf_xml_dom_node_check_namespace(const GF_XMLNode *n, const char *expected_node_name, const char *expected_ns_prefix); /*! Writes a string to an xml file and replaces forbidden chars with xml entities *\param file the xml output file @@ -377,6 +384,13 @@ */ void gf_xml_dump_string(FILE* file, const char* before, const char* str, const char* after); +/*! Deep cloning of a node + *\param node the node to clone + *\return the cloned node + */ +GF_XMLNode *gf_xml_dom_node_clone(GF_XMLNode *node); + + /*! @} */
View file
gpac-26.02.0.tar.gz/include/tests.h
Added
@@ -0,0 +1,41 @@ +#pragma once + +#include <gpac/tools.h> // Bool + +#define unittest(suffix) void test_##suffix(void) + +extern int checks_passed; +extern int checks_failed; + +static Bool verbose_ut = GF_FALSE; +static Bool fatal_ut = GF_TRUE; + +#define assert_generic(expr, trace) \ + do { \ + if (expr) { \ + if (verbose_ut) \ + printf("Assertion passed: \"%s\", File: \"%s\", Line: %d, Function: \"%s\"\n", #expr, __FILE__, __LINE__, __func__); \ + checks_passed++; \ + } else { \ + printf("Assertion failed: \"%s\", File: \"%s\", Line: %d, Function: \"%s\"\n", #expr, __FILE__, __LINE__, __func__); \ + if (verbose_ut) trace; \ + checks_failed++; \ + if (fatal_ut) checks_failed|=0x8000000; \ + } \ + } while (0) + +#define assert_true(expr) assert_generic(expr, {}) +#define assert_false(expr) assert_true(!(expr)) + +#define SEP " " +#define assert_equal_str(str1, str2) assert_generic(!strcmp((str1), (str2)), printf(SEP #str1"(=\"%s\")\n" SEP #str2 "(=\"%s\")\n", str1, str2)) +#define assert_not_equal_str(str1, str2) assert_generic( strcmp((str1), (str2)), printf(SEP #str1"(=\"%s\")\n" SEP #str2 "(=\"%s\")\n", str1, str2)) +#define assert_equal_mem(m1, m2, sz) assert_true(memcmp(m1, m2, sz) == 0) +#define assert_not_null(ptr) assert_true((ptr) != NULL) + +#define assert_equal_op(a, b, op, t) assert_generic(a op b, { printf(SEP "\"" #a "\"(=" t ") " #op " \"" #b "\"(=" t ")\n", a, b); }) +#define assert_equal(a, b, t) assert_equal_op(a, b, ==, t) +#define assert_greater(a, b, t) assert_equal_op(a, b, > , t) +#define assert_greater_equal(a, b, t) assert_equal_op(a, b, >=, t) +#define assert_less(a, b, t) assert_equal_op(a, b, < , t) +#define assert_less_equal(a, b, t) assert_equal_op(a, b, <=, t)
View file
gpac-2.4.0.tar.gz/mkdmg.sh -> gpac-26.02.0.tar.gz/mkdmg.sh
Changed
@@ -40,8 +40,8 @@ mkdir -p tmpdmg/GPAC.app/Contents/MacOS/modules mkdir -p tmpdmg/GPAC.app/Contents/MacOS/lib -cp bin/gcc/gm* tmpdmg/GPAC.app/Contents/MacOS/modules -cp bin/gcc/gf_* tmpdmg/GPAC.app/Contents/MacOS/modules +cp bin/gcc/gm* tmpdmg/GPAC.app/Contents/MacOS/modules || true +cp bin/gcc/gf_* tmpdmg/GPAC.app/Contents/MacOS/modules || true cp bin/gcc/libgpac.dylib tmpdmg/GPAC.app/Contents/MacOS/lib if -f bin/gcc/libopenhevc.1.dylib ; then cp bin/gcc/libopenhevc.1.dylib tmpdmg/GPAC.app/Contents/MacOS/lib @@ -69,7 +69,7 @@ echo Copying shared resources rsync -r --exclude=.git $source_path/share/res ./tmpdmg/GPAC.app/Contents/MacOS/share/ rsync -r --exclude=.git $source_path/share/gui ./tmpdmg/GPAC.app/Contents/MacOS/share/ -rsync -r --exclude=.git $source_path/share/vis ./tmpdmg/GPAC.app/Contents/MacOS/share/ +rsync -r --exclude=.git $source_path/share/rmtws ./tmpdmg/GPAC.app/Contents/MacOS/share/ rsync -r --exclude=.git $source_path/share/shaders ./tmpdmg/GPAC.app/Contents/MacOS/share/ rsync -r --exclude=.git $source_path/share/scripts ./tmpdmg/GPAC.app/Contents/MacOS/share/ rsync -r --exclude=.git $source_path/share/python ./tmpdmg/GPAC.app/Contents/MacOS/share/ @@ -80,10 +80,14 @@ cur_dir=`pwd` cd $source_path -TAG=$(git describe --tags --abbrev=0 2> /dev/null) -REVISION=$(echo `git describe --tags --long 2> /dev/null || echo "UNKNOWN"` | sed "s/^$TAG-//") +TAG=$(git describe --tags --abbrev=0 --match "v*" 2> /dev/null) +REVISION=$(echo `git describe --tags --long --match "v*" 2> /dev/null || echo "UNKNOWN"` | sed "s/^$TAG-//") BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "UNKNOWN") -rev="$REVISION-$BRANCH" + +#sanitize branch name for filenames +DHBRANCH=$(echo "$BRANCH" | sed 's/^-+.0-9a-zA-Z~/-/g' ) + +rev="$REVISION-$DHBRANCH" cd $cur_dir full_version=$version @@ -121,5 +125,3 @@ echo "$pck_name ready" rm -rf tmpdmg rm -rf tmppkg.pkg - -
View file
gpac-2.4.0.tar.gz/modules/Makefile -> gpac-26.02.0.tar.gz/modules/Makefile
Changed
@@ -14,11 +14,13 @@ #PLUGDIRS+=freenect endif -ifeq ($(CONFIG_ALSA),yes) +ifeq ($(CONFIG_ALSA),no) +else PLUGDIRS+=alsa endif -ifeq ($(CONFIG_JACK),yes) +ifeq ($(CONFIG_JACK),no) +else PLUGDIRS+=jack endif @@ -30,7 +32,8 @@ PLUGDIRS+=caca_out endif -ifeq ($(CONFIG_PULSEAUDIO),yes) +ifeq ($(CONFIG_PULSEAUDIO),no) +else PLUGDIRS+=pulseaudio endif
View file
gpac-2.4.0.tar.gz/modules/alsa/Makefile -> gpac-26.02.0.tar.gz/modules/alsa/Makefile
Changed
@@ -5,20 +5,12 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(oss_cflags) LDFLAGS+=$(oss_ldflags) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak #common obj OBJS= alsa.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_alsa$(DYN_LIB_SUFFIX) @@ -29,13 +21,13 @@ $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) $(LDFLAGS) -lasound -L../../bin/gcc -lgpac -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/caca_out/Makefile -> gpac-26.02.0.tar.gz/modules/caca_out/Makefile
Changed
@@ -4,21 +4,13 @@ CFLAGS=-I"$(SRC_PATH)/include" -I/opt/local/include $(OPTFLAGS) $(libcaca_cflags) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - LINKFLAGS=-L../../bin/gcc -lgpac -L/opt/local/lib $(libcaca_ldflags) +include ../common.mak + #common obj OBJS=caca_out.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_caca_out$(DYN_LIB_SUFFIX) @@ -29,13 +21,13 @@ $(LIB): $(OBJS) $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-26.02.0.tar.gz/modules/common.mak
Added
@@ -0,0 +1,16 @@ + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(CONFIG_EMSCRIPTEN),yes) +else ifeq ($(CONFIG_DARWIN),yes) +else +LDFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,$(prefix)/lib -Wl,-rpath-link,../../bin/gcc +endif
View file
gpac-2.4.0.tar.gz/modules/dec_openhevc/Makefile -> gpac-26.02.0.tar.gz/modules/dec_openhevc/Makefile
Changed
@@ -4,22 +4,14 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(openhevc_cflags) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - ifeq ($(CONFIG_DARWIN),yes) LINKFLAGS+=-L../../bin/gcc $(openhevc_ldflags) else LINKFLAGS+=-L../../bin/gcc $(openhevc_ldflags) -Wl,-Bsymbolic endif +include ../common.mak + #common obj OBJS=dec_openhevc.o SRCS=$(SRC_PATH)/src/filters/dec_openhevc.c @@ -44,13 +36,13 @@ $(LIB): $(OBJS) $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/dektec_out/dektec_video_decl.c -> gpac-26.02.0.tar.gz/modules/dektec_out/dektec_video_decl.c
Changed
@@ -3,7 +3,7 @@ * * Authors: Romain Bouqueau, Jean Le Feuvre * Copyright (c) 2014-2022 GPAC Licensing -* Copyright (c) 2016-2020 Telecom Paris +* Copyright (c) 2016-2024 Telecom Paris * All rights reserved * * This file is part of GPAC / Dektec SDI video output filter @@ -62,7 +62,7 @@ GPAC_MODULE_EXPORT GF_FilterRegister *RegisterFilter(GF_FilterSession *session) #else -GF_FilterRegister *dtout_register(GF_FilterSession *session) +const GF_FilterRegister *dtout_register(GF_FilterSession *session) #endif { memset(DTOutCaps, 0, sizeof(DTOutCaps)); @@ -95,13 +95,14 @@ DTOutRegister.name = "dtout"; #ifndef GPAC_DISABLE_DOC - DTOutRegister.description = "DekTec SDIOut"; + DTOutRegister.description = "DekTec SDI output"; DTOutRegister.help = "This filter provides SDI output to be used with __DTA 2174__ or __DTA 2154__ cards."; #endif DTOutRegister.private_size = sizeof(GF_DTOutCtx); DTOutRegister.args = DTOutArgs; DTOutRegister.caps = DTOutCaps; DTOutRegister.nb_caps = 3; + DTOutRegister.hint_class_type = GF_FS_CLASS_MM_IO; #if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) DTOutRegister.initialize = dtout_initialize;
View file
gpac-2.4.0.tar.gz/modules/deprecated/old_arch/platinum/GenericDevice.h -> gpac-26.02.0.tar.gz/modules/deprecated/old_arch/platinum/GenericDevice.h
Changed
@@ -190,7 +190,7 @@ class GPAC_Service : public PLT_Service { public: - GPAC_Service(PLT_DeviceData* device, const char* type = NULL, const char* id = NULL, const char* name = NULL, const char* last_change_namespace = NULL); + GPAC_Service(PLT_DeviceData* device, const char* type = NULL, const char* id = NULL, const char* name = NULL, const char* last_change_namespace = NULL); ~GPAC_Service(); #ifdef GPAC_HAS_SPIDERMONKEY
View file
gpac-2.4.0.tar.gz/modules/dx_hw/dx_window.c -> gpac-26.02.0.tar.gz/modules/dx_hw/dx_window.c
Changed
@@ -27,6 +27,7 @@ #include "dx_hw.h" #include <gpac/utf.h> #include "resource.h" +#include <shellapi.h> /*crude redef of winuser.h due to windows/winsock2 conflicts*/ #ifndef WM_MOUSEWHEEL @@ -815,9 +816,9 @@ wchar_t *wcaption; size_t len; size_t len_res; - len = (strlen(str_src) + 1)*sizeof(wchar_t); + len = ((strlen(str_src)/2)*2 + 2) * sizeof(wchar_t); wcaption = (wchar_t *)gf_malloc(len); - len_res = gf_utf8_mbstowcs(wcaption, len, &str_src); + len_res = gf_utf8_mbstowcs(wcaption, len+1, &str_src); if (len_res != -1) { SetWindowTextW(ctx->os_hwnd, wcaption); }
View file
gpac-2.4.0.tar.gz/modules/ft_font/Makefile -> gpac-26.02.0.tar.gz/modules/ft_font/Makefile
Changed
@@ -4,24 +4,16 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(freetype_cflags) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak #common obj -OBJS= ft_font.o +OBJS= ft_font.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_ft_font$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols ft_font.def +#LDFLAGS+=-export-symbols ft_font.def endif @@ -42,13 +34,13 @@ endif -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/ft_font/ft_font.c -> gpac-26.02.0.tar.gz/modules/ft_font/ft_font.c
Changed
@@ -176,6 +176,21 @@ /*try to assign default fixed fonts*/ if (!bold && !italic) { + + /* if a bold or italic default has been set, prefer a regular one */ + if (ftpriv->font_default && (strstr(ftpriv->font_default, " Bold") || strstr(ftpriv->font_default, " Italic"))) { + u32 gidx; + FT_Select_Charmap(face, FT_ENCODING_UNICODE); + gidx = FT_Get_Char_Index(face, (u32) 'a'); + if (gidx) gidx = FT_Get_Char_Index(face, (u32) 'z'); + if (gidx) gidx = FT_Get_Char_Index(face, (u32) '1'); + if (gidx) gidx = FT_Get_Char_Index(face, (u32) '@'); + if (gidx) { + gf_free(ftpriv->font_default); + ftpriv->font_default = gf_strdup(szfont); + } + } + strcpy(szfont, face->family_name); strlwr(szfont); @@ -501,6 +516,7 @@ char *fontName; const char *opt; Bool is_def_font = GF_FALSE; + Bool no_style_check = GF_FALSE; FTBuilder *ftpriv = (FTBuilder *)dr->udta; fontName = (char *) OrigFontName; @@ -524,6 +540,11 @@ is_def_font = GF_TRUE; OrigFontName = "TYPEWRITER"; } + if (!styles) { + if (ftpriv->font_fixed && !strcmp(fontName, ftpriv->font_fixed)) no_style_check = GF_TRUE; + else if (ftpriv->font_sans && !strcmp(fontName, ftpriv->font_sans)) no_style_check = GF_TRUE; + else if (ftpriv->font_serif && !strcmp(fontName, ftpriv->font_serif)) no_style_check = GF_TRUE; + } /*first look in loaded fonts*/ ftpriv->active_face = ft_font_in_cache(ftpriv, fontName, styles); @@ -569,7 +590,7 @@ //handle collections, figure out which face matches our styles num_faces = face->num_faces; for (i=0; i<num_faces; i++) { - if ( ft_check_face(face, NULL, checkStyles)) { + if (no_style_check || ft_check_face(face, NULL, checkStyles)) { gf_free(fname); gf_list_add(ftpriv->loaded_fonts, face); ftpriv->active_face = face;
View file
gpac-2.4.0.tar.gz/modules/jack/Makefile -> gpac-26.02.0.tar.gz/modules/jack/Makefile
Changed
@@ -4,20 +4,12 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(oss_cflags) -Wno-deprecated-declarations -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak #common obj OBJS= jack.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_jack$(DYN_LIB_SUFFIX) @@ -27,13 +19,13 @@ $(LIB): $(OBJS) $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -L$(prefix)/$(libdir) -ljack -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/jack/jack.c -> gpac-26.02.0.tar.gz/modules/jack/jack.c
Changed
@@ -213,7 +213,7 @@ jack_options_t options = JackNullOption; memset (ctx->jackClientName, 0, MAX_JACK_CLIENT_NAME_SZ); - snprintf (ctx->jackClientName, MAX_JACK_CLIENT_NAME_SZ, "gpac-%d", gf_sys_get_process_id() ); + snprintf (ctx->jackClientName, MAX_JACK_CLIENT_NAME_SZ, "gpac-%u", gf_sys_get_process_id() ); ctx->autoConnect = gf_module_get_bool((GF_BaseInterface *)dr, "auto-connect");
View file
gpac-2.4.0.tar.gz/modules/pulseaudio/Makefile -> gpac-26.02.0.tar.gz/modules/pulseaudio/Makefile
Changed
@@ -4,20 +4,12 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak #common obj OBJS= pulseaudio.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_pulseaudio$(DYN_LIB_SUFFIX) @@ -25,15 +17,15 @@ $(LIB): $(OBJS) echo $(LDFLAGS) - $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -lpulse -lpulse-simple + $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -lpulse -lpulse-simple -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/pulseaudio/pulseaudio.c -> gpac-26.02.0.tar.gz/modules/pulseaudio/pulseaudio.c
Changed
@@ -33,6 +33,8 @@ #include <pulse/simple.h> #include <gpac/modules/audio_out.h> +#define BUFF_SIZE 8192 + typedef struct { pa_simple *playback_handle; @@ -41,6 +43,7 @@ const char *output_description; u32 errors; u32 consecutive_zero_reads; + char dataBUFF_SIZE; } PulseAudioContext; static void @@ -137,12 +140,9 @@ return GF_OK; } -#define BUFF_SIZE 8192 - static void PulseAudio_WriteAudio (GF_AudioOutput * dr) { - char dataBUFF_SIZE; int written = 0; int pa_error = 0; PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; @@ -157,7 +157,7 @@ } return; } - written = dr->FillBuffer (dr->audio_renderer, data, BUFF_SIZE / 4); + written = dr->FillBuffer (dr->audio_renderer, ctx->data, BUFF_SIZE / 4); if (written <= 0) { ctx->consecutive_zero_reads++; @@ -171,7 +171,7 @@ return; } ctx->consecutive_zero_reads = 0; - /*written = */pa_simple_write (ctx->playback_handle, data, written, &pa_error); + /*written = */pa_simple_write (ctx->playback_handle, ctx->data, written, &pa_error); if (pa_error != 0) { if (ctx->errors < 1)
View file
gpac-2.4.0.tar.gz/modules/sdl_out/Makefile -> gpac-26.02.0.tar.gz/modules/sdl_out/Makefile
Changed
@@ -4,15 +4,7 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(sdl_cflags) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak LINKFLAGS=-L../../bin/gcc -lgpac $(sdl_ldflags) @@ -23,11 +15,11 @@ #common obj OBJS=sdl_out.o audio.o video.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_sdl_out$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols sdl_out.def +#LDFLAGS+=-export-symbols sdl_out.def endif @@ -37,13 +29,13 @@ $(LIB): $(OBJS) $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/test_filter/test_filter.c -> gpac-26.02.0.tar.gz/modules/test_filter/test_filter.c
Changed
@@ -27,6 +27,7 @@ #include <gpac/filters.h> #include <gpac/avparse.h> #include <gpac/constants.h> +#include <gpac/module.h> // GPAC_MODULE_EXPORT typedef struct
View file
gpac-2.4.0.tar.gz/modules/validator/Makefile -> gpac-26.02.0.tar.gz/modules/validator/Makefile
Changed
@@ -4,20 +4,12 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak #common obj OBJS=validator.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_validator$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) @@ -34,13 +26,13 @@ endif -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/modules/wav_out/wav_out.c -> gpac-26.02.0.tar.gz/modules/wav_out/wav_out.c
Changed
@@ -26,6 +26,7 @@ #include <gpac/modules/audio_out.h> #include <windows.h> +#include <mmeapi.h> #define MAX_AUDIO_BUFFER 30
View file
gpac-2.4.0.tar.gz/modules/x11_out/Makefile -> gpac-26.02.0.tar.gz/modules/x11_out/Makefile
Changed
@@ -4,15 +4,7 @@ CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -Wno-deprecated-declarations -ifeq ($(DEBUGBUILD),yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD),yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif +include ../common.mak CFLAGS+=-I"$(SRC_PATH)/include" @@ -56,11 +48,11 @@ #common obj OBJS=x11_out.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_x11_out$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols +#LDFLAGS+=-export-symbols endif @@ -74,13 +66,13 @@ endif -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean
View file
gpac-2.4.0.tar.gz/packagers/osx/GPAC.app/Contents/Info.plist -> gpac-26.02.0.tar.gz/packagers/osx/GPAC.app/Contents/Info.plist
Changed
@@ -516,7 +516,7 @@ <string>yes</string> </dict> <key>CFBundleShortVersionString</key> - <string>2.4</string> + <string>26.02</string> <key>CFBundleSignature</key> <string>gpac</string> <key>CFBundleURLTypes</key> @@ -567,7 +567,7 @@ <key>NSMicrophoneUsageDescription</key> <string>GPAC needs microphone access to handle this URL</string> <key>CFBundleVersion</key> - <string>12.14.0</string> + <string>16.5.0</string> <key>LSMinimumSystemVersion</key> <string>10.5.0</string> <key>NSMainNibFile</key>
View file
gpac-2.4.0.tar.gz/packagers/win32_64/nsis/gpac_installer.nsi -> gpac-26.02.0.tar.gz/packagers/win32_64/nsis/gpac_installer.nsi
Changed
@@ -1,6 +1,6 @@ ;-------------------------------- ;General -!define GPAC_VERSION 2.4 +!define GPAC_VERSION 26.02 !include default.out !define GPAC_ROOT ..\..\.. @@ -218,8 +218,10 @@ File "${GPAC_BIN}\gm_dx_hw.dll" ;all our deps - File "${GPAC_BIN}\libcryptoMD.dll" - File "${GPAC_BIN}\libsslMD.dll" + File "${GPAC_BIN}\libcrypto*.dll" + File "${GPAC_BIN}\libssl*.dll" + File /nonfatal "${GPAC_BIN}\libnghttp3*.dll" + File /nonfatal "${GPAC_BIN}\libngtcp2*.dll" File "${GPAC_BIN}\avcodec-*.dll" File "${GPAC_BIN}\avdevice-*.dll" File "${GPAC_BIN}\avfilter-*.dll" @@ -230,6 +232,7 @@ File "${GPAC_BIN}\postproc-*.dll" File "${GPAC_BIN}\libx264-*.dll" File "${GPAC_BIN}\OpenSVCDecoder.dll" + File "${GPAC_BIN}\libcurl*.dll" ;copy shared resources SetOutPath $INSTDIR\share @@ -259,6 +262,10 @@ SetOutPath $INSTDIR\share\lang File /r /x .git ${GPAC_ROOT}\share\lang\* + ;copy rmtws + SetOutPath $INSTDIR\share\rmtws + File /r /x .git ${GPAC_ROOT}\share\rmtws\* + ;create default cache SetOutPath $INSTDIR\cache SetOutPath $INSTDIR @@ -324,13 +331,6 @@ Call AddToPath SectionEnd -Section "Remotery Visualizer" SecRMT - SectionIn 1 - SetOutPath $INSTDIR\share\vis - File /r /x .git ${GPAC_ROOT}\share\vis\* -SectionEnd - - Section "Python Bindings" SecPython SectionIn 1 SetOutPath $INSTDIR\share\python @@ -392,7 +392,6 @@ CreateDirectory "$SMPROGRAMS\GPAC" CreateShortCut "$SMPROGRAMS\GPAC\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 CreateShortCut "$SMPROGRAMS\GPAC\GPAC.lnk" "$INSTDIR\gpac.exe" "" - CreateShortCut "$SMPROGRAMS\GPAC\Remotery GPAC.lnk" "$INSTDIR\share\vis\index.html" "" CreateShortCut "$SMPROGRAMS\GPAC\Readme.lnk" "$INSTDIR\ReadMe.txt" CreateShortCut "$SMPROGRAMS\GPAC\License.lnk" "$INSTDIR\License.txt" CreateShortCut "$SMPROGRAMS\GPAC\History.lnk" "$INSTDIR\changelog.txt" @@ -413,7 +412,6 @@ ; !insertmacro MUI_DESCRIPTION_TEXT ${SecMPEGU} "Support for W3C and MPEG-U Widgets" !insertmacro MUI_DESCRIPTION_TEXT ${SecSDK} "Headers and library files needed to develop modules for GPAC or applications based on GPAC" !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} "Python bindings for libgpac" - !insertmacro MUI_DESCRIPTION_TEXT ${SecRMT} "Remotery HTML visualizer" !insertmacro MUI_DESCRIPTION_TEXT ${SecSDL} "SDL Audio and Video output" !insertmacro MUI_DESCRIPTION_TEXT ${SecCACA} "Support for ASCII video output" !insertmacro MUI_DESCRIPTION_TEXT ${SecValidator} "GPAC Test Validator"
View file
gpac-2.4.0.tar.gz/share/doc/CODING_STYLE -> gpac-26.02.0.tar.gz/share/doc/CODING_STYLE
Changed
@@ -95,8 +95,6 @@ various JS scripts used by GPAC. - *gpac/share/shaders/* GLSL shaders used by the compositor. -- *gpac/share/vis/* - Remotery web client. - *gpac/src/bifs/* BInary Format for Scene coding (decoder and encoder) (BIFS tables are with MPEG4Gen application in gpac/applications/generators/MPEG4) - *gpac/src/compositor/*
View file
gpac-2.4.0.tar.gz/share/doc/doxyfile -> gpac-26.02.0.tar.gz/share/doc/doxyfile
Changed
@@ -1,4 +1,4 @@ -# Doxyfile 1.9.1 +# Doxyfile 1.9.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value value, ... # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x configFile +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv configFile #--------------------------------------------------------------------------- # Project related configuration options @@ -60,16 +70,28 @@ OUTPUT_DIRECTORY = . -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,26 +103,18 @@ # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -115,7 +129,7 @@ # brief descriptions will be completely suppressed. # The default value is: YES. -REPEAT_BRIEF = NO +REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found @@ -258,18 +272,18 @@ # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) -ALIASES = +ALIASES = URL= # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -312,8 +326,8 @@ # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files @@ -349,6 +363,17 @@ TOC_INCLUDE_HEADINGS = 5 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -460,19 +485,27 @@ LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you +# which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = YES + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -485,7 +518,7 @@ # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. @@ -554,7 +587,8 @@ # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -585,14 +619,15 @@ # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO @@ -610,6 +645,12 @@ HIDE_COMPOUND_REFERENCE= NO +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -767,7 +808,8 @@ # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE @@ -813,30 +855,52 @@ WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = YES +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. -#WARN_AS_ERROR = FAIL_ON_WARNINGS WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen @@ -845,13 +909,27 @@ # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = doxygen_warnings.txt @@ -878,17 +956,33 @@ ../../share/doc/idl/dash_algo.idl \ ../../share/doc/idl/httpout.idl \ ../../share/doc/idl/nodejs.idl \ - ../../share/python/libgpac/libgpac.py + ../../share/python/libgpac/libgpac.py \ + ../../applications/mp4box \ + ../../applications/gpac \ + ../../include \ + ../../src \ + ../../modules # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. @@ -900,12 +994,12 @@ # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -938,7 +1032,7 @@ # be searched for input files as well. # The default value is: NO. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a @@ -974,18 +1068,15 @@ EXCLUDE_PATTERNS = */doc/* \ */tests/* \ */build/* \ - */bin/* \ - */applications/* \ - */modules/* + */deprecated/* \ + */unittests/* \ + */bin/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -1030,6 +1121,11 @@ # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -1071,6 +1167,15 @@ USE_MDFILE_AS_MAINPAGE = ../../README.md +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1168,9 +1273,11 @@ CLANG_ASSISTED_PARSING = NO -# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to -# YES then doxygen will add the directory of each input to the include path. +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. # The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_ADD_INC_PATHS = YES @@ -1206,10 +1313,11 @@ ALPHABETICAL_INDEX = NO -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1256,7 +1364,7 @@ # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = gpacheader.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1266,7 +1374,7 @@ # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = gpacfooter.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1288,10 +1396,15 @@ # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = gpacstylesheet.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1303,9 +1416,22 @@ HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. @@ -1315,7 +1441,7 @@ HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1333,15 +1459,6 @@ HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1361,6 +1478,13 @@ HTML_DYNAMIC_SECTIONS = YES +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1397,6 +1521,13 @@ DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1422,8 +1553,12 @@ # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1480,6 +1615,16 @@ TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1582,16 +1727,28 @@ # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # @@ -1616,6 +1773,13 @@ EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for @@ -1636,17 +1800,6 @@ FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1664,11 +1817,29 @@ USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1681,15 +1852,21 @@ # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = @@ -1869,29 +2046,31 @@ EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. -# -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = @@ -1934,10 +2113,16 @@ USE_PDFLATEX = NO -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if <return> is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1950,16 +2135,6 @@ LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1968,14 +2143,6 @@ LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2040,16 +2207,6 @@ RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -2146,21 +2303,12 @@ DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. @@ -2168,6 +2316,32 @@ GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if an a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + +#--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2222,7 +2396,7 @@ # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2230,7 +2404,7 @@ # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. @@ -2241,7 +2415,8 @@ # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = ../../include @@ -2271,7 +2446,7 @@ # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = GF_OPT_ENUM # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2308,15 +2483,15 @@ GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2330,25 +2505,9 @@ EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2357,7 +2516,7 @@ # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: YES. @@ -2374,49 +2533,73 @@ DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, +# Edge and Graph Attributes specification</a> You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a +# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about +# arrows shapes.</a> +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' <a +# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2476,7 +2659,9 @@ # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2485,7 +2670,10 @@ # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2525,23 +2713,32 @@ # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, -# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, -# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, +# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, +# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2573,11 +2770,12 @@ DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2586,10 +2784,10 @@ DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = @@ -2613,7 +2811,7 @@ # Minimum value: 0, maximum value: 10000, default value: 50. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_GRAPH_MAX_NODES = 100 +DOT_GRAPH_MAX_NODES = 200 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # generated by dot. A depth value of 3 means that only nodes reachable from the @@ -2625,19 +2823,7 @@ # Minimum value: 0, maximum value: 1000, default value: 0. # This tag requires that the tag HAVE_DOT is set to YES. -MAX_DOT_GRAPH_DEPTH = 1000 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO +MAX_DOT_GRAPH_DEPTH = 1 # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This @@ -2651,6 +2837,8 @@ # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2659,8 +2847,24 @@ # If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. # -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# <outfile_format> -o <outputfile> <inputfile>. The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS =
View file
gpac-2.4.0.tar.gz/share/doc/doxyfile-full -> gpac-26.02.0.tar.gz/share/doc/doxyfile-full
Changed
@@ -45,7 +45,7 @@ # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "Open Source Multimedia Framework. For more information, check out http://gpac.wp.mines-telecom.fr" +PROJECT_BRIEF = "Open Source Multimedia Framework. For more information, check out https://gpac.io" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels
View file
gpac-26.02.0.tar.gz/share/doc/gpacfooter.html
Added
@@ -0,0 +1,17 @@ +<!-- HTML footer for doxygen 1.9.8--> +<!-- start footer part --> +<!--BEGIN GENERATE_TREEVIEW--> +<div id="nav-path" class="navpath"><!-- id is needed for treeview function! --> + <ul> + $navpath + <li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li> + </ul> +</div> +<!--END GENERATE_TREEVIEW--> +<!--BEGIN !GENERATE_TREEVIEW--> +<hr class="footer"/><address class="footer"><small> +$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion +</small></address> +<!--END !GENERATE_TREEVIEW--> +</body> +</html>
View file
gpac-26.02.0.tar.gz/share/doc/gpacheader.html
Added
@@ -0,0 +1,77 @@ +<!-- HTML header for doxygen 1.9.8--> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen $doxygenversion"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME--> +<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME--> +<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/> +<!--BEGIN DISABLE_INDEX--> + <!--BEGIN FULL_SIDEBAR--> +<script type="text/javascript">var page_layout=1;</script> + <!--END FULL_SIDEBAR--> +<!--END DISABLE_INDEX--> +<script type="text/javascript" src="$relpath^jquery.js"></script> +<script type="text/javascript" src="$relpath^dynsections.js"></script> +$treeview +$search +$mathjax +$darkmode +<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> +$extrastylesheet +</head> +<body> +<!--BEGIN DISABLE_INDEX--> + <!--BEGIN FULL_SIDEBAR--> +<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! --> + <!--END FULL_SIDEBAR--> +<!--END DISABLE_INDEX--> + +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> + +<!--BEGIN TITLEAREA--> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0" id="titleareatable"> + <tbody> + <tr id="projectrow"> + <!--BEGIN PROJECT_LOGO--> + <td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td> + <!--END PROJECT_LOGO--> + <!--BEGIN PROJECT_NAME--> + <td id="projectalign"> + <div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber"> $projectnumber</span><!--END PROJECT_NUMBER--> + </div> + <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF--> + </td> + <td id="versionnav"> + <iframe src="versions.html" frameBorder="0" width="100%" ></iframe> + </td> + <!--END PROJECT_NAME--> + <!--BEGIN !PROJECT_NAME--> + <!--BEGIN PROJECT_BRIEF--> + <td> + <div id="projectbrief">$projectbrief</div> + </td> + <!--END PROJECT_BRIEF--> + <!--END !PROJECT_NAME--> + <!--BEGIN DISABLE_INDEX--> + <!--BEGIN SEARCHENGINE--> + <!--BEGIN !FULL_SIDEBAR--> + <td>$searchbox</td> + <!--END !FULL_SIDEBAR--> + <!--END SEARCHENGINE--> + <!--END DISABLE_INDEX--> + </tr> + <!--BEGIN SEARCHENGINE--> + <!--BEGIN FULL_SIDEBAR--> + <tr><td colspan="2">$searchbox</td></tr> + <!--END FULL_SIDEBAR--> + <!--END SEARCHENGINE--> + </tbody> +</table> +</div> +<!--END TITLEAREA--> +<!-- end header part -->
View file
gpac-26.02.0.tar.gz/share/doc/gpacstylesheet.css
Added
@@ -0,0 +1,2063 @@ +/* The standard CSS for doxygen 1.9.8*/ + +html { +/* page base colors */ +--page-background-color: white; +--page-foreground-color: black; +--page-link-color: #3D578C; +--page-visited-link-color: #4665A2; + +/* index */ +--index-odd-item-bg-color: #F8F9FC; +--index-even-item-bg-color: white; +--index-header-color: black; +--index-separator-color: #A0A0A0; + +/* header */ +--header-background-color: #F9FAFC; +--header-separator-color: #C4CFE5; +--header-gradient-image: url('nav_h.png'); +--group-header-separator-color: #879ECB; +--group-header-color: #354C7B; +--inherit-header-color: gray; + +--footer-foreground-color: #2A3D61; +--footer-logo-width: 104px; +--citation-label-color: #334975; +--glow-color: cyan; + +--title-background-color: white; +--title-separator-color: #5373B4; +--directory-separator-color: #9CAFD4; +--separator-color: #4A6AAA; + +--blockquote-background-color: #F7F8FB; +--blockquote-border-color: #9CAFD4; + +--scrollbar-thumb-color: #9CAFD4; +--scrollbar-background-color: #F9FAFC; + +--icon-background-color: #728DC1; +--icon-foreground-color: white; +--icon-doc-image: url('doc.svg'); +--icon-folder-open-image: url('folderopen.svg'); +--icon-folder-closed-image: url('folderclosed.svg'); + +/* brief member declaration list */ +--memdecl-background-color: #F9FAFC; +--memdecl-separator-color: #DEE4F0; +--memdecl-foreground-color: #555; +--memdecl-template-color: #4665A2; + +/* detailed member list */ +--memdef-border-color: #A8B8D9; +--memdef-title-background-color: #E2E8F2; +--memdef-title-gradient-image: url('nav_f.png'); +--memdef-proto-background-color: #DFE5F1; +--memdef-proto-text-color: #253555; +--memdef-proto-text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--memdef-doc-background-color: white; +--memdef-param-name-color: #602020; +--memdef-template-color: #4665A2; + +/* tables */ +--table-cell-border-color: #2D4068; +--table-header-background-color: #374F7F; +--table-header-foreground-color: #FFFFFF; + +/* labels */ +--label-background-color: #728DC1; +--label-left-top-border-color: #5373B4; +--label-right-bottom-border-color: #C4CFE5; +--label-foreground-color: white; + +/** navigation bar/tree/menu */ +--nav-background-color: #F9FAFC; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_b.png'); +--nav-gradient-hover-image: url('tab_h.png'); +--nav-gradient-active-image: url('tab_a.png'); +--nav-gradient-active-image-parent: url("../tab_a.png"); +--nav-separator-image: url('tab_s.png'); +--nav-breadcrumb-image: url('bc_s.png'); +--nav-breadcrumb-border-color: #C2CDE4; +--nav-splitbar-image: url('splitbar.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #283A5D; +--nav-text-hover-color: white; +--nav-text-active-color: white; +--nav-text-normal-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #364D7C; +--nav-menu-background-color: white; +--nav-menu-foreground-color: #555555; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.5); +--nav-arrow-color: #9CAFD4; +--nav-arrow-selected-color: #9CAFD4; + +/* table of contents */ +--toc-background-color: #F4F6FA; +--toc-border-color: #D8DFEE; +--toc-header-color: #4665A2; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='5px' fill='grey'><text x='0' y='5' font-size='10'>&%238595;</text></svg>"); + +/** search field */ +--search-background-color: white; +--search-foreground-color: #909090; +--search-magnification-image: url('mag.svg'); +--search-magnification-select-image: url('mag_sel.svg'); +--search-active-color: black; +--search-filter-background-color: #F9FAFC; +--search-filter-foreground-color: black; +--search-filter-border-color: #90A5CE; +--search-filter-highlight-text-color: white; +--search-filter-highlight-bg-color: #3D578C; +--search-results-foreground-color: #425E97; +--search-results-background-color: #EEF1F7; +--search-results-border-color: black; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #555; + +/** code fragments */ +--code-keyword-color: #008000; +--code-type-keyword-color: #604020; +--code-flow-keyword-color: #E08000; +--code-comment-color: #800000; +--code-preprocessor-color: #806020; +--code-string-literal-color: #002080; +--code-char-literal-color: #008080; +--code-xml-cdata-color: black; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #000000; +--code-vhdl-keyword-color: #700070; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #4665A2; +--code-external-link-color: #4665A2; +--fragment-foreground-color: black; +--fragment-background-color: #FBFCFD; +--fragment-border-color: #C4CFE5; +--fragment-lineno-border-color: #00FF00; +--fragment-lineno-background-color: #E8E8E8; +--fragment-lineno-foreground-color: black; +--fragment-lineno-link-fg-color: #4665A2; +--fragment-lineno-link-bg-color: #D8D8D8; +--fragment-lineno-link-hover-fg-color: #4665A2; +--fragment-lineno-link-hover-bg-color: #C8C8C8; +--tooltip-foreground-color: black; +--tooltip-background-color: white; +--tooltip-border-color: gray; +--tooltip-doc-color: grey; +--tooltip-declaration-color: #006318; +--tooltip-link-color: #4665A2; +--tooltip-shadow: 1px 1px 7px gray; +--fold-line-color: #808080; +--fold-minus-image: url('minus.svg'); +--fold-plus-image: url('plus.svg'); +--fold-minus-image-relpath: url('../../minus.svg'); +--fold-plus-image-relpath: url('../../plus.svg'); + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + color-scheme: dark; + +/* page base colors */ +--page-background-color: black; +--page-foreground-color: #C9D1D9; +--page-link-color: #90A5CE; +--page-visited-link-color: #A3B4D7; + +/* index */ +--index-odd-item-bg-color: #0B101A; +--index-even-item-bg-color: black; +--index-header-color: #C4CFE5; +--index-separator-color: #334975; + +/* header */ +--header-background-color: #070B11; +--header-separator-color: #141C2E; +--header-gradient-image: url('nav_hd.png'); +--group-header-separator-color: #283A5D; +--group-header-color: #90A5CE; +--inherit-header-color: #A0A0A0; + +--footer-foreground-color: #5B7AB7; +--footer-logo-width: 60px; +--citation-label-color: #90A5CE; +--glow-color: cyan; + +--title-background-color: #090D16; +--title-separator-color: #354C79; +--directory-separator-color: #283A5D; +--separator-color: #283A5D; + +--blockquote-background-color: #101826; +--blockquote-border-color: #283A5D; + +--scrollbar-thumb-color: #283A5D; +--scrollbar-background-color: #070B11; + +--icon-background-color: #334975; +--icon-foreground-color: #C4CFE5; +--icon-doc-image: url('docd.svg'); +--icon-folder-open-image: url('folderopend.svg'); +--icon-folder-closed-image: url('folderclosedd.svg'); + +/* brief member declaration list */ +--memdecl-background-color: #0B101A; +--memdecl-separator-color: #2C3F65; +--memdecl-foreground-color: #BBB; +--memdecl-template-color: #7C95C6; + +/* detailed member list */ +--memdef-border-color: #233250; +--memdef-title-background-color: #1B2840; +--memdef-title-gradient-image: url('nav_fd.png'); +--memdef-proto-background-color: #19243A; +--memdef-proto-text-color: #9DB0D4; +--memdef-proto-text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.9); +--memdef-doc-background-color: black; +--memdef-param-name-color: #D28757; +--memdef-template-color: #7C95C6; + +/* tables */ +--table-cell-border-color: #283A5D; +--table-header-background-color: #283A5D; +--table-header-foreground-color: #C4CFE5; + +/* labels */ +--label-background-color: #354C7B; +--label-left-top-border-color: #4665A2; +--label-right-bottom-border-color: #283A5D; +--label-foreground-color: #CCCCCC; + +/** navigation bar/tree/menu */ +--nav-background-color: #101826; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_bd.png'); +--nav-gradient-hover-image: url('tab_hd.png'); +--nav-gradient-active-image: url('tab_ad.png'); +--nav-gradient-active-image-parent: url("../tab_ad.png"); +--nav-separator-image: url('tab_sd.png'); +--nav-breadcrumb-image: url('bc_sd.png'); +--nav-breadcrumb-border-color: #2A3D61; +--nav-splitbar-image: url('splitbard.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #B6C4DF; +--nav-text-hover-color: #DCE2EF; +--nav-text-active-color: #DCE2EF; +--nav-text-normal-shadow: 0px 1px 1px black; +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #B6C4DF; +--nav-menu-background-color: #05070C; +--nav-menu-foreground-color: #BBBBBB; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.2); +--nav-arrow-color: #334975; +--nav-arrow-selected-color: #90A5CE; + +/* table of contents */ +--toc-background-color: #151E30; +--toc-border-color: #202E4A; +--toc-header-color: #A3B4D7; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='5px'><text x='0' y='5' font-size='10' fill='grey'>&%238595;</text></svg>"); + +/** search field */ +--search-background-color: black; +--search-foreground-color: #C5C5C5; +--search-magnification-image: url('mag_d.svg'); +--search-magnification-select-image: url('mag_seld.svg'); +--search-active-color: #C5C5C5; +--search-filter-background-color: #101826; +--search-filter-foreground-color: #90A5CE; +--search-filter-border-color: #7C95C6; +--search-filter-highlight-text-color: #BCC9E2; +--search-filter-highlight-bg-color: #283A5D; +--search-results-background-color: #101826; +--search-results-foreground-color: #90A5CE; +--search-results-border-color: #7C95C6; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #2F436C; + +/** code fragments */ +--code-keyword-color: #CC99CD; +--code-type-keyword-color: #AB99CD; +--code-flow-keyword-color: #E08000; +--code-comment-color: #717790; +--code-preprocessor-color: #65CABE; +--code-string-literal-color: #7EC699; +--code-char-literal-color: #00E0F0; +--code-xml-cdata-color: #C9D1D9; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #C0C0C0; +--code-vhdl-keyword-color: #CF53C9; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #79C0FF; +--code-external-link-color: #79C0FF; +--fragment-foreground-color: #C9D1D9; +--fragment-background-color: black; +--fragment-border-color: #30363D; +--fragment-lineno-border-color: #30363D; +--fragment-lineno-background-color: black; +--fragment-lineno-foreground-color: #6E7681; +--fragment-lineno-link-fg-color: #6E7681; +--fragment-lineno-link-bg-color: #303030; +--fragment-lineno-link-hover-fg-color: #8E96A1; +--fragment-lineno-link-hover-bg-color: #505050; +--tooltip-foreground-color: #C9D1D9; +--tooltip-background-color: #202020; +--tooltip-border-color: #C9D1D9; +--tooltip-doc-color: #D9E1E9; +--tooltip-declaration-color: #20C348; +--tooltip-link-color: #79C0FF; +--tooltip-shadow: none; +--fold-line-color: #808080; +--fold-minus-image: url('minusd.svg'); +--fold-plus-image: url('plusd.svg'); +--fold-minus-image-relpath: url('../../minusd.svg'); +--fold-plus-image-relpath: url('../../plusd.svg'); + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +}} +body { + background-color: var(--page-background-color); + color: var(--page-foreground-color); +} + +body, table, div, p, dl { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 22px; +} + +/* @group Heading Levels */ + +.title { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 28px; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h1.groupheader { + font-size: 150%; +} + +h2.groupheader { + border-bottom: 1px solid var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--glow-color); +} + +dt { + font-weight: bold; +} + +p.startli, p.startdd { + margin-top: 2px; +} + +th p.starttd, th p.intertd, th p.endtd { + font-size: 100%; + font-weight: 700; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +p.interli { +} + +p.interdd { +} + +p.intertd { +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.navtab { + padding-right: 15px; + text-align: right; + line-height: 110%; +} + +div.navtab table { + border-spacing: 0; +} + +td.navtab { + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL { + background-image: var(--nav-gradient-active-image); + background-repeat:repeat-x; + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL a, td.navtabHL a:visited { + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +a.navtab { + font-weight: bold; +} + +div.qindex{ + text-align: center; + width: 100%; + line-height: 140%; + font-size: 130%; + color: var(--index-separator-color); +} + +#main-menu a:focus { + outline: auto; + z-index: 10; + position: relative; +} + +dt.alphachar{ + font-size: 180%; + font-weight: bold; +} + +.alphachar a{ + color: var(--index-header-color); +} + +.alphachar a:hover, .alphachar a:visited{ + text-decoration: none; +} + +.classindex dl { + padding: 25px; + column-count:1 +} + +.classindex dd { + display:inline-block; + margin-left: 50px; + width: 90%; + line-height: 1.15em; +} + +.classindex dl.even { + background-color: var(--index-even-item-bg-color); +} + +.classindex dl.odd { + background-color: var(--index-odd-item-bg-color); +} + +@media(min-width: 1120px) { + .classindex dl { + column-count:2 + } +} + +@media(min-width: 1320px) { + .classindex dl { + column-count:3 + } +} + + +/* @group Link Styling */ + +a { + color: var(--page-link-color); + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: var(--page-visited-link-color); +} + +a:hover { + text-decoration: underline; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited, a.line, a.line:visited { + color: var(--code-link-color); +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: var(--code-external-link-color); +} + +a.code.hl_class { /* style for links to class names in code snippets */ } +a.code.hl_struct { /* style for links to struct names in code snippets */ } +a.code.hl_union { /* style for links to union names in code snippets */ } +a.code.hl_interface { /* style for links to interface names in code snippets */ } +a.code.hl_protocol { /* style for links to protocol names in code snippets */ } +a.code.hl_category { /* style for links to category names in code snippets */ } +a.code.hl_exception { /* style for links to exception names in code snippets */ } +a.code.hl_service { /* style for links to service names in code snippets */ } +a.code.hl_singleton { /* style for links to singleton names in code snippets */ } +a.code.hl_concept { /* style for links to concept names in code snippets */ } +a.code.hl_namespace { /* style for links to namespace names in code snippets */ } +a.code.hl_package { /* style for links to package names in code snippets */ } +a.code.hl_define { /* style for links to macro names in code snippets */ } +a.code.hl_function { /* style for links to function names in code snippets */ } +a.code.hl_variable { /* style for links to variable names in code snippets */ } +a.code.hl_typedef { /* style for links to typedef names in code snippets */ } +a.code.hl_enumvalue { /* style for links to enum value names in code snippets */ } +a.code.hl_enumeration { /* style for links to enumeration names in code snippets */ } +a.code.hl_signal { /* style for links to Qt signal names in code snippets */ } +a.code.hl_slot { /* style for links to Qt slot names in code snippets */ } +a.code.hl_friend { /* style for links to friend names in code snippets */ } +a.code.hl_dcop { /* style for links to KDE3 DCOP names in code snippets */ } +a.code.hl_property { /* style for links to property names in code snippets */ } +a.code.hl_event { /* style for links to event names in code snippets */ } +a.code.hl_sequence { /* style for links to sequence names in code snippets */ } +a.code.hl_dictionary { /* style for links to dictionary names in code snippets */ } + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul { + overflow: visible; +} + +ul.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + list-style-type: none; +} + +#side-nav ul { + overflow: visible; /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; /*Fixed: fragment lines overlap floating elements*/ + overflow-y: hidden; +} + +pre.fragment { + border: 1px solid var(--fragment-border-color); + background-color: var(--fragment-background-color); + color: var(--fragment-foreground-color); + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + font-family: var(--font-family-monospace); + font-size: 105%; +} + +div.fragment { + padding: 0 0 1px 0; /*Fixed: last line underline overlap border*/ + margin: 4px 8px 4px 2px; + color: var(--fragment-foreground-color); + background-color: var(--fragment-background-color); + border: 1px solid var(--fragment-border-color); +} + +div.line { + font-family: var(--font-family-monospace); + font-size: 13px; + min-height: 13px; + line-height: 1.2; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content:"\000A"; + white-space: pre; +} + +div.line.glow { + background-color: var(--glow-color); + box-shadow: 0 0 10px var(--glow-color); +} + +span.fold { + margin-left: 5px; + margin-right: 1px; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; + display: inline-block; + width: 12px; + height: 12px; + background-repeat:no-repeat; + background-position:center; +} + +span.lineno { + padding-right: 4px; + margin-right: 9px; + text-align: right; + border-right: 2px solid var(--fragment-lineno-border-color); + color: var(--fragment-lineno-foreground-color); + background-color: var(--fragment-lineno-background-color); + white-space: pre; +} +span.lineno a, span.lineno a:visited { + color: var(--fragment-lineno-link-fg-color); + background-color: var(--fragment-lineno-link-bg-color); +} + +span.lineno a:hover { + color: var(--fragment-lineno-link-hover-fg-color); + background-color: var(--fragment-lineno-link-hover-bg-color); +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + color: var(--page-foreground-color); + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 8px; +} + +p.formulaDsp { + text-align: center; +} + +img.dark-mode-visible { + display: none; +} +img.light-mode-visible { + display: none; +} + +img.formulaDsp { + +} + +img.formulaInl, img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; + width: var(--footer-logo-width); +} + +.compoundTemplParams { + color: var(--memdecl-template-color); + font-size: 80%; + line-height: 120%; +} + +/* @group Code Colorization */ + +span.keyword { + color: var(--code-keyword-color); +} + +span.keywordtype { + color: var(--code-type-keyword-color); +} + +span.keywordflow { + color: var(--code-flow-keyword-color); +} + +span.comment { + color: var(--code-comment-color); +} + +span.preprocessor { + color: var(--code-preprocessor-color); +} + +span.stringliteral { + color: var(--code-string-literal-color); +} + +span.charliteral { + color: var(--code-char-literal-color); +} + +span.xmlcdata { + color: var(--code-xml-cdata-color); +} + +span.vhdldigit { + color: var(--code-vhdl-digit-color); +} + +span.vhdlchar { + color: var(--code-vhdl-char-color); +} + +span.vhdlkeyword { + color: var(--code-vhdl-keyword-color); +} + +span.vhdllogic { + color: var(--code-vhdl-logic-color); +} + +blockquote { + background-color: var(--blockquote-background-color); + border-left: 2px solid var(--blockquote-border-color); + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid var(--table-cell-border-color); +} + +th.dirtab { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid var(--separator-color); +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--glow-color); + box-shadow: 0 0 15px var(--glow-color); +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: var(--memdecl-background-color); + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: var(--memdecl-foreground-color); +} + +.memSeparator { + border-bottom: 1px solid var(--memdecl-separator-color); + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight, .memTemplItemRight { + width: 100%; +} + +.memTemplParams { + color: var(--memdecl-template-color); + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-image: var(--memdef-title-gradient-image); + background-repeat: repeat-x; + background-color: var(--memdef-title-background-color); + line-height: 1.25; + font-weight: 300; + float:left; +} + +.permalink +{ + font-size: 65%; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: var(--memdef-template-color); + font-weight: normal; + margin-left: 9px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px var(--glow-color); +} + +.memname { + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 0px 6px 0px; + color: var(--memdef-proto-text-color); + font-weight: bold; + text-shadow: var(--memdef-proto-text-shadow); + background-color: var(--memdef-proto-background-color); + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; +} + +.overload { + font-family: var(--font-family-monospace); + font-size: 65%; +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 10px 2px 10px; + border-top-width: 0; + background-image:url('nav_g.png'); + background-repeat:repeat-x; + background-color: var(--memdef-doc-background-color); + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: var(--memdef-param-name-color); + white-space: nowrap; +} +.paramname em { + font-style: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname, .tparams .paramname, .exception .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, .tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, .tparams .paramdir { + font-family: var(--font-family-monospace); + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: var(--label-background-color); + border-top:1px solid var(--label-left-top-border-color); + border-left:1px solid var(--label-left-top-border-color); + border-right:1px solid var(--label-right-bottom-border-color); + border-bottom:1px solid var(--label-right-bottom-border-color); + text-shadow: none; + color: var(--label-foreground-color); + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid var(--directory-separator-color); + border-bottom: 1px solid var(--directory-separator-color); + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.odd { + padding-left: 6px; + background-color: var(--index-odd-item-bg-color); +} + +.directory tr.even { + padding-left: 6px; + background-color: var(--index-even-item-bg-color); +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: var(--page-link-color); +} + +.arrow { + color: var(--nav-arrow-color); + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; +} + +.icon { + font-family: var(--font-family-icon); + line-height: normal; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: var(--icon-background-color); + color: var(--icon-foreground-color); + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-folder-open-image); + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-folder-closed-image); + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-doc-image); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: var(--footer-foreground-color); +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid var(--memdef-border-color); + border-spacing: 0px; + border-radius: 4px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid var(--memdef-border-color); + border-bottom: 1px solid var(--memdef-border-color); + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--memdef-border-color); +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image: var(--memdef-title-gradient-image); + background-repeat:repeat-x; + background-color: var(--memdef-title-background-color); + font-size: 90%; + color: var(--memdef-proto-text-color); + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + font-weight: 400; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: var(--nav-gradient-image); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image: var(--nav-gradient-image); + background-repeat:repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color:var(--nav-text-normal-color); + border:solid 1px var(--nav-breadcrumb-border-color); + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:var(--nav-breadcrumb-image); + background-repeat:no-repeat; + background-position:right; + color: var(--nav-foreground-color); +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; + color: var(--nav-text-normal-color); + font-family: var(--font-family-nav); + text-shadow: var(--nav-text-normal-shadow); + text-decoration: none; +} + +.navpath li.navelem a:hover +{ + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color: var(--footer-foreground-color); + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +table.classindex +{ + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image: var(--header-gradient-image); + background-repeat:repeat-x; + background-color: var(--header-background-color); + margin: 0px; + border-bottom: 1px solid var(--header-separator-color); +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +.PageDocRTL-title div.headertitle { + text-align: right; + direction: rtl; +} + +dl { + padding: 0 0 0 0; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug, dl.examples */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.note { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #D0C000; +} + +dl.warning, dl.attention { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00D000; +} + +dl.deprecated { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #505050; +} + +dl.todo { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00C0E0; +} + +dl.test { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #3030E0; +} + +dl.bug { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #C08050; +} + +dl.section dd { + margin-bottom: 6px; +} + + +#projectrow +{ + height: 56px; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectalign +{ + vertical-align: middle; + padding-left: 0.5em; +} + +#projectname +{ + font-size: 200%; + font-family: var(--font-family-title); + margin: 0px; + padding: 2px 0px; +} + +#projectbrief +{ + font-size: 90%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font-size: 50%; + font-family: 50% var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid var(--title-separator-color); + background-color: var(--title-background-color); +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.plantumlgraph +{ + text-align: center; +} + +.diagraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:var(--citation-label-color); + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; + text-align:right; + width:52px; +} + +dl.citelist dd { + margin:2px 0 2px 72px; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: var(--toc-background-color); + border: 1px solid var(--toc-border-color); + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +div.toc li { + background: var(--toc-down-arrow-image) no-repeat scroll 0 5px transparent; + font: 10px/1.2 var(--font-family-toc); + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 var(--font-family-toc); + color: var(--toc-header-color); + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 15px; +} + +div.toc li.level4 { + margin-left: 15px; +} + +span.emoji { + /* font family used at the site: https://unicode.org/emoji/charts/full-emoji-list.html + * font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort; + */ +} + +span.obfuscator { + display: none; +} + +.inherit_header { + font-weight: bold; + color: var(--inherit-header-color); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + /*white-space: nowrap;*/ + color: var(--tooltip-foreground-color); + background-color: var(--tooltip-background-color); + border: 1px solid var(--tooltip-border-color); + border-radius: 4px 4px 4px 4px; + box-shadow: var(--tooltip-shadow); + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: var(--tooltip-doc-color); + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip a { + color: var(--tooltip-link-color); +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: var(--tooltip-declaration-color); +} + +#powerTip div { + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: var(--font-family-tooltip); + line-height: 16px; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before, #powerTip.ne:before, #powerTip.nw:before { + border-top-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + +/* @group Markdown */ + +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.markdownTable tr { +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, td.markdownTableBodyCenter { + text-align: center +} + +tt, code, kbd, samp +{ + display: inline-block; +} +/* @end */ + +u { + text-decoration: underline; +} + +details>summary { + list-style-type: none; +} + +details > summary::-webkit-details-marker { + display: none; +} + +details>summary::before { + content: "\25ba"; + padding-right:4px; + font-size: 80%; +} + +detailsopen>summary::before { + content: "\25bc"; + padding-right:4px; + font-size: 80%; +} + +body { + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); +} + +::-webkit-scrollbar { + background-color: var(--scrollbar-background-color); + height: 12px; + width: 12px; +} +::-webkit-scrollbar-thumb { + border-radius: 6px; + box-shadow: inset 0 0 12px 12px var(--scrollbar-thumb-color); + border: solid 2px transparent; +} +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-background-color); +} + +#titleareatable { + width: 100%; +} + +#projectlogo { + width: 1%; + white-space: nowrap; +} + +#projectalign { + width: 1%; + white-space: nowrap; +} + +#versionnav { + text-align: right; + padding-right: 30px; +}
View file
gpac-2.4.0.tar.gz/share/doc/idl/core.idl -> gpac-26.02.0.tar.gz/share/doc/idl/core.idl
Changed
@@ -159,8 +159,8 @@ Fraction get_ntp(); -/*! shifts an NTP 64-bit timestamp by the given number of microseconds -\param ntp source NTP +/*! shifts an NTP 64-bit timestamp by the given number of microseconds +\param ntp source NTP \param microseconds number of microseconds to add (positive) or remove (negative) \return adjusted NTP 64 bit timestamp as fraction, 'n' continaing seconds, 'd' containing fractional part */ @@ -400,6 +400,42 @@ Rect rect_intersect(Rect r1, Rect r2); +/*! enables websocket monitoring server - see \ref gf_sys_enable_rmtws +* +* define the \ref rmt_on_new_client attribute to set callbacks +* \param enable (default: true) will enable the server if true, stop it if false +*/ +void enable_rmtws(bool enable); + +/*! enables user websocket server - see \ref gf_sys_enable_userws +* +* define the \ref userws_on_new_client attribute to set callbacks +* \param enable (default: true) will enable the server if true, stop it if false +*/ +void enable_userws(bool enable); + +/*! get the log tools and levels - see \ref gf_log_get_tools_levels +\param original_logs if set to true, return the logs set by the application, otherwise current logs as possibly modified by JS +\return the log-level string +*/ +DOMString get_logs(bool original_logs=GF_FALSE); + +/*! set the log tools and levels - see \ref gf_log_set_tools_levels +\param logs string describing logs and levels. Using null will reset logs to original value before modified by JS +\param reset reset all tools if true, otherwise only modify specified tools +*/ +void set_logs(DOMString logs, bool reset=false); + +/*! set the callback function when new clients connect to the monitoring websocket server +* the function shall take one parameter of type \ref JS_RMTClient +*/ +optional attribute function rmt_on_new_client; + +/*! set the callback function when new clients connect to the user websocket server +* the function shall take one parameter of type \ref JS_RMTClient +*/ +optional attribute function userws_on_new_client; + /*! number of cores */ attribute readonly unsigned long nb_cores; @@ -511,6 +547,15 @@ readonly unsigned long version_micro; +/*! get/set extended log mode status*/ +boolean use_logx; + +/*! set the callback function for logs, or disable log callback and reset tools to original value if null. +* - For regular mode, the callback function is of form log(DOMString tool, unsigned int level, DOMString message). +* - For extended mode, the callback function is of form log(DOMString tool, unsigned int level, DOMString message, unsigned int thread_id, Object caller), with object either a FilterSession, a Filter or null. +*/ +optional attribute function on_log; + }; /*! object used for TTY size info - cannot be created through constructor*/ @@ -526,7 +571,7 @@ /*! object used for file enumeration - cannot be created through constructor*/ interface FileInformation { /*! file name*/ - DOMString name; + DOMString name; /*! file path*/ DOMString path; /*! true if directory*/ @@ -942,7 +987,38 @@ }; +/*! Object representing a monitoring websocket client +* +* will be passed to the session \ref rmt_on_new_client callback +* and to \ref userws_on_new_client if the user websocket server is enabled +*/ +interface JS_RMTClient { + + /*! a callback called when the client receives data + * + * the function shall be of type function(data) + * where data will be either of type string (for utf8 data) or arraybuffer (for binary data) + */ + optional attribute function on_data; + /*! a callback called when the client disconnects or is deleted */ + optional attribute function on_close; -/*! @} */ + /*! a string of format ip:port representing the client */ + readonly attribute string peer_address; + + /*! sends data to the client on the websocket + \param data the data to send as a utf8 string + */ + void send(string data); + /*! sends data to the client on the websocket + \param data the data to send as a binary arraybuffer + */ + void send(arraybuffer data); + + +} + + +/*! @} */
View file
gpac-2.4.0.tar.gz/share/doc/idl/evg.idl -> gpac-26.02.0.tar.gz/share/doc/idl/evg.idl
Changed
@@ -266,11 +266,11 @@ Float32Buffer depth_buffer; /*! sets current projection matrix to use -\param projection_matrix the 16 float coeficients of the matrix, column-first. When using Matrix object, you can pass Matrix.m +\param projection_matrix the 16 float coefficients of the matrix, column-first. When using Matrix object, you can pass Matrix.m */ void projection(Float32Buffer projection_matrix); /*! sets current modelview matrix to use -\param modelview_matrix the 16 float coeficients of the matrix, column-first. When using Matrix object, you can pass Matrix.m +\param modelview_matrix the 16 float coefficients of the matrix, column-first. When using Matrix object, you can pass Matrix.m */ void modelview(Float32Buffer modelview_matrix); @@ -600,7 +600,7 @@ \param from the color matrix to use*/ Matrix2D(Matrix2D from); /*! constructor - \param coefs the matrix coeficients + \param coefs the matrix coefficients */ Matrix2D(double coefs...); @@ -707,7 +707,7 @@ \param from the color matrix to use */ ColorMatrix(ColorMatrix from); /*! constructor - \param coefs the matrix coeficients + \param coefs the matrix coefficients */ ColorMatrix(double coefs...); @@ -1138,7 +1138,7 @@ attribute unsigned long h; /*! kernel normalization. If 0 or undefined, no normalization is applied*/ attribute unsigned long norm; - /*! kernel coeficients. The coeficients are unsigned integers, and there shall be at least w * h coefficients*/ + /*! kernel coefficients. The coefficients are unsigned integers, and there shall be at least w * h coefficients*/ attribute Array k; }; @@ -1264,7 +1264,7 @@ /*! indicates matrix is a 3D matrix*/ attribute const boolean is3D = true; -/*! float buffer of coeficients (typically use matrix.m to pass matrices uniforms)*/ +/*! float buffer of coefficients (typically use matrix.m to pass matrices uniforms)*/ attribute Array<float> m; /*! yaw value of matrix*/ attribute readonly float yaw; @@ -1283,7 +1283,7 @@ attribute readonly Vec3f dec_shear; /*! copies matrix -\param from matrix to copy coeficient from +\param from matrix to copy coefficient from \return the matrix object */ Matrix copy(Matrix from); @@ -1502,7 +1502,7 @@ /*! Shader object common to vertex and fragment shaders Although QuickJS is an amazing piece of software, calling JavaScript for each fragment is just too costly. The shader object provides a simple -set of tools to produce a vertex or fragment output at reasonnable speed, without interacting with JavaScript. +set of tools to produce a vertex or fragment output at reasonable speed, without interacting with JavaScript. # Generic principles
View file
gpac-2.4.0.tar.gz/share/doc/idl/filtersession.idl -> gpac-26.02.0.tar.gz/share/doc/idl/filtersession.idl
Changed
@@ -26,7 +26,7 @@ \param log the string to write */ void print(DOMString log); -/*! global context function for printing +/*! global context function for printing \param log_level the log level to use: GF_LOG_DEBUG, GF_LOG_INFO, GF_LOG_WARNING, GF_LOG_ERROR. The special value -2 means print as gf_sys_format_help without highlight, the special value -1 means print as gf_sys_format_help with hightlight of first) \param log_str the string to write */ @@ -36,7 +36,7 @@ /*!\brief JSFilterSession API The JSFilterSession interface has a single instance exposed to the script under the name "session". -It implements binding to the underlying filter session object, see \ref GF_FilterSession. +It implements binding to the underlying filter session object, see \ref fs_grp. */ interface JSFilterSession { @@ -57,8 +57,9 @@ /*! posts a task to the main scheduler - see \ref gf_fs_post_user_task \param task_callback the callback function to use. This callback functions has no parameters, and returns false or an exception to abort the task, true to reschedule the task immediately or a reschedule time in milliseconds \param task_name optional value giving a label for the task +\return identifier for callback */ -void post_task(function task_callback, optional DOMString task_name=null); +unsigned int post_task(function task_callback, optional DOMString task_name=null); /*! aborts the filter session - see \ref gf_fs_abort \param flush_type if true, wait for all packets currently pending to be processed before closing the session @@ -71,40 +72,23 @@ */ void lock_filters(boolean do_lock); -/*! returns the JSFSFilter object for the given index. -\param index index of filter to query. This index is only valid when session is locked +/*! returns the JSFSFilter object for the given index. +\param index index of filter to query. This index is only valid when session is locked \return the filter object, null if none found */ JSFSFilter get_filter(unsigned long index); -/*! returns the JSFSFilter object for a given filter iname. -\param iname the iname of filter to query +/*! returns the JSFSFilter object for a given filter iname. +\param iname the iname of filter to query \return the filter object, null if none found */ JSFSFilter get_filter(DOMString iname); - -/*! enable remotery - this should be called as early as possible in the session -*/ -void enable_rmt(); - -/*! sends the given string to the remotery client(s) -\param command the command to send -\return error code if any -*/ -void rmt_send(DOMString command); - -/*! sets callback function to use when processing remotery client request -\param callback the callback function to call. This function takes one parameter which is the text being received -\return error code if any -*/ -void set_rmt_fun(function callback); - /*! inserts a filter in graph \param filter_to_add string describin the filter to add, can be in the form "src=" for sources, "dst=" for sinks or regular string for filters. \param link_from filter used as source for the created filters - see \ref gf_filter_set_source -\param link_args arguments for the link (used to assign new filter SID - see \ref gf_filter_set_source +\param link_args arguments for the link (used to assign new filter SID - see \ref gf_filter_set_source). An empty string means no arguments \param relative_to_script if false, URLs for source and sinks filters are relative to the current working directory, if true they are relative to the script path \return new filter created */ @@ -112,23 +96,28 @@ /*! removes a filter in the media session \param filter filter object to remove, can be a JSFSFilter object (in this case, function is equivalent to JSFSFilter.remove) or a JSFilter object +\param src_filter source filter. If set, disconnect the chain between the source and the filter before removing the filter. The source must be a valid source (no input pids) - cf \ref gf_filter_remove_src */ -void remove_filter(Object filter); +void remove_filter(Object filter, Object src_filter=null); /*! sets callback function to get notifications upon each new filter creation. \param callback callback function - it is passed a single parameter, the filter object +\return identifier for callback */ -void set_new_filter_fun(function callback); +unsigned int set_new_filter_fun(function callback); /*! sets callback function to get notifications upon each filter destruction. \param callback the callback function - it is passed a single parameter, the filter object +\return identifier for callback */ -void set_del_filter_fun(function callback); +unsigned int set_del_filter_fun(function callback); -/*! sets callback function to get events being sent back to application (see \ref gf_fs_set_ui_callback). -\param callback callback function - it is passed a single parameter set, an event instance of FilterEvent interface. The return value of the function should be as indicated for each event type*/ -void set_event_fun(function callback); +/*! sets callback function to get events being sent back to application (see \ref gf_fs_set_ui_callback). +\param callback callback function - it is passed a single parameter set, an event instance of FilterEvent interface. The return value of the function should be as indicated for each event type +\return identifier for callback +*/ +unsigned int set_event_fun(function callback); /*! sets callback function to get authentication requests (see \ref gf_fs_set_ui_callback). \param callback callback function - the function takes 5 parameters and no return value type: @@ -137,9 +126,14 @@ - pass: password if known - secure: boolean indicating if connection is secured (TLS) - auth_obj: object used to acknowledge authentication, with a single function `done()` taking up to 3 parameters: user, passord and boolean indicating if credentials should be stored. If user or password are not set, authentication fails +\return identifier for callback */ -void set_auth_fun(function callback); +unsigned int set_auth_fun(function callback); +/*! removes callback function by identifier +\param callback_id the callback identifier +*/ +void remove_callback(callback_id); /*! fires a given event on all registered user event if no filter is specified, or on the filter \param evt the event to send @@ -148,7 +142,7 @@ \return true if event was fired, false if no event target was found or if the filter is not an event target*/ boolean fire_event(FilterEvent evt, optional JSFSFilter *filter=null, optional boolean upstream=false); -/*! enables session reporting - see \ref gf_fs_enable_reporting. +/*! enables session reporting - see \ref gf_fs_enable_reporting. \param enable enables reporting if true*/ void reporting(boolean enable); @@ -231,8 +225,8 @@ /*!\brief JSFSFilter API -The JSFSFilter interface provides tools to query and update filters in a session. -It implements binding to the underlying filter object, see \ref GF_Filter. +The JSFSFilter interface provides tools to query and update filters in a session. +It implements binding to the underlying filter object, see \ref fs_filter. */ interface JSFSFilter { /*! filter name - may be changed at run-time by the filter*/ @@ -273,8 +267,10 @@ attribute readonly unsigned long long bytes_sent; /*! number of tasks executed*/ attribute readonly unsigned long tasks; -/*! number of errors*/ +/*! number of errors on the filter, never reseted*/ attribute readonly unsigned long errors; +/*! number of consecutive errors, reseted at each successfull processing*/ +attribute readonly unsigned long current_errors; /*! set to true if report has been updated since last query*/ attribute readonly boolean report_updated; /*! class of filter, can be "rawin", "demuxer", "decoder", "encoder", "muxer", "rawout", "mediasink", "mediasource", "unknown"*/ @@ -313,7 +309,7 @@ /*! Enumerates properties on input pid \param idx the index of the input pid to query -\param fun_callback function called for each property in the input pid. The function has three parameters: name (DOMString), type (DOMString), value (FilterProperty) +\param fun_callback function called for each property in the input pid. The function has three parameters: name (DOMString), type (DOMString), value (FilterProperty) */ void ipid_props(unsigned long idx, function fun_callback); @@ -326,7 +322,7 @@ /*! Enumerates properties on output pid \param idx the index of the output pid to query -\param fun_callback function called for each property in the output pid. The function has three parameters: name (DOMString), type (DOMString), value (FilterProperty) +\param fun_callback function called for each property in the output pid. The function has three parameters: name (DOMString), type (DOMString), value (FilterProperty) */ void opid_props(unsigned long idx, function fun_callback); @@ -384,13 +380,13 @@ /*! Gets all arguments (options) of the filter \param value_only if true, only returns name (DOMString) and value (FilterProperty) of each argument. Otherwise returns the full argument (JSFSFilterArg) -\return array of JSFSFilterArg +\return array of JSFSFilterArg */ Array all_args(optional boolean value_only=true); /*! Gets value of a given argument of the filter \param arg_name argument name -\return property value, or null +\return property value, or null */ FilterProperty get_arg(DOMString arg_name); @@ -452,6 +448,4 @@ }; - /*! @} */ -
View file
gpac-2.4.0.tar.gz/share/doc/idl/httpout.idl -> gpac-26.02.0.tar.gz/share/doc/idl/httpout.idl
Changed
@@ -100,7 +100,7 @@ attribute void write(ArrayBuffer buffer); /*! callback function used to monitor end of session. It is set to null by default -\param error GPAC error code for the session +\param error GPAC error code for the session. If 1 (GF_EOS), the session is ended but underlying network is kept alive, otherwise session is destroyed */ attribute void close(unsigned long error);
View file
gpac-2.4.0.tar.gz/share/doc/idl/jsf.idl -> gpac-26.02.0.tar.gz/share/doc/idl/jsf.idl
Changed
@@ -30,7 +30,7 @@ \param log the string to write */ void print(DOMString log); -/*! global context function for printing +/*! global context function for printing \param log_level the log level to use: GF_LOG_DEBUG, GF_LOG_INFO, GF_LOG_WARNING, GF_LOG_ERROR. The special value -2 means print as gf_sys_format_help without highlight, the special value -1 means print as gf_sys_format_help with hightlight of first) \param log_str the string to write */ @@ -40,8 +40,8 @@ /*!\brief JSFilter API The JSFilter interface has a single instance exposed to the script under the name "filter". -It implements binding to the underlying filter object, see \ref GF_Filter. -The JSFilter provides callback functions mapping the functionnalities of \ref __gf_filter_register. +It implements binding to the underlying filter object, see \ref fs_filter "GF_Filter". +The JSFilter provides callback functions mapping the functionalities of \ref __gf_filter_register. */ interface JSFilter { /*! initialize the filter. This is a callback function assignable by the script. It is called once all filter arguments have been parsed. @@ -51,7 +51,6 @@ attribute GF_Err initialize(); /*! finalize the filter. This is a callback function assignable by the script. It is called immediately before destruction of the JavaScript context. See \ref __gf_filter_register.finalize. -\return void */ attribute void finalize(); /*! configures or reconfigures an input pid @@ -100,6 +99,8 @@ readonly attribute DOMString sep_name; /*! see \ref gf_filter_get_sep*/ readonly attribute DOMString sep_list; +/*! source args, only valid during initialize callback - see \ref gf_filter_get_src_args*/ +readonly attribute DOMString src_args; /*! see \ref gf_filter_get_dst_args*/ readonly attribute DOMString dst_args; /*! see \ref gf_filter_get_dst_name*/ @@ -182,7 +183,7 @@ void send_event(FilterEvent evt, optional boolean upstream=false); /*! queries info on a filter - an event on the filter - see \ref gf_filter_get_info and \ref gf_filter_get_info_str + an event on the filter - see \ref gf_filter_get_info and \ref gf_filter_get_info_str \param info_name the property name or ID of the info to query \param is_string indicate if the name describes a built-in property or a user-defined property \return the property if found, null otherwise @@ -291,13 +292,13 @@ /*! assigns source name to a filter - see \ref gf_filter_set_source \param from a Filter or a FilterInstance object to set a a source for this filter -\param source_id the source ID to assign +\param source_id the source ID to assign - empty string means no source_id */ void set_source(Filter from, optional DOMString source_id=null); /*! assigns restricted source name to a filter - see \ref gf_filter_set_source_restricted \param from a Filter or a FilterInstance object to set a a source for this filter -\param source_id the source ID to assign +\param source_id the source ID to assign - empty string means no source_id */ void set_source_restricted(Filter from, optional DOMString source_id=null); @@ -311,7 +312,6 @@ /*! setup failure notification. This is a callback function assignable by the script. It is when loading a source or destination filter fails asynchronously \param e error code if any -\return void */ attribute void on_setup_failure(GF_Err e); @@ -321,7 +321,7 @@ void send_event(FilterEvent evt); /*! queries info on a filter - an event on the filter - see \ref gf_filter_get_info and \ref gf_filter_get_info_str + an event on the filter - see \ref gf_filter_get_info and \ref gf_filter_get_info_str \param info_name the property name or ID of the info to query \param is_string indicate if the name describes a built-in property or a user-defined property \return the property if found, null otherwise @@ -351,7 +351,7 @@ /*! removes a source filter - see \ref gf_filter_remove_src*/ void remove(); -/*! checks if a filter and its connected output chain(s) have PID connection pending +/*! checks if a filter and its connected output chain(s) have PID connection pending \param stop_at a Filter or a FilterInstance object indicating where to stop the analysis. If not set and the filter is a source filter, automatically set to the Filter running the script \return true if pid connections are pending */ @@ -387,7 +387,7 @@ }; -/*! The FilterPid object is a binding for \ref GF_FilterPid*/ +/*! The FilterPid object is a binding for \ref fs_pid "GF_FilterPid" */ interface FilterPid { /*! name of the PID*/ @@ -398,6 +398,8 @@ readonly attribute boolean eos_seen; /*! end of stream pending state - see \ref gf_filter_pid_eos_received*/ readonly attribute boolean eos_received; +/*! true if end of stream is a flush event - see \ref gf_filter_pid_is_flush_eos*/ +readonly attribute boolean is_flush; /*! blocking state - see \ref gf_filter_pid_would_block*/ readonly attribute boolean would_block; /*! sparse status - see \ref gf_filter_pid_is_sparse*/ @@ -456,7 +458,7 @@ void send_event(FilterEvent evt); /*! enumerates properties or info on the pid - see \ref gf_filter_pid_enum_properties and \ref gf_filter_pid_enum_info -\param index the 0-based index of the property. +\param index the 0-based index of the property. \param enum_info if set, enumerates info instead of properties \return null if no more properties to enumerate; otherwise an object: \code @@ -497,7 +499,7 @@ boolean is_filter_in_parents(GF_Filter filter); /*! gets buffer occupancy - see \ref gf_filter_pid_get_buffer_occupancy -\param filter the Filter or FilterInstance object to check +\param filter the Filter or FilterInstance object to check \return a buffer occupancy object: \code { @@ -538,7 +540,7 @@ FilterProperty query_caps(DOMString name, optional boolean is_user); -/*! gets statistics for the pid +/*! gets statistics for the pid \return A statistics object \code boolean disconnected; @@ -564,7 +566,7 @@ */ Object get_stats(); -/*! gets clock info for the pid +/*! gets clock info for the pid \return A clock info object: \code unsigned long type; @@ -674,9 +676,9 @@ }; -/*! FilterPacket provides binding for \ref GF_FilterPacket +/*! FilterPacket provides binding for \ref fs_pck "GF_FilterPacket" -Packet data is made accessible through an ArrayBuffer object. This object is destroyed when truncating or expanding the data, you must get it again using pck.data. +Packet data is made accessible through an ArrayBuffer object. This object is destroyed when truncating or expanding the data, you must get it again using pck.data. */ interface FilterPacket { /*!start flag*/ @@ -743,7 +745,7 @@ /*! enumerates properties of the packet -\param index the 0-based index of the property. +\param index the 0-based index of the property. \return null if no more properties to enumerate; otherwise an object: \code { @@ -777,7 +779,7 @@ /*! discard packet - gf_filter_pck_discard*/ void discard(); -/*! sets a property on packet - see \ref gf_filter_pck_set_property +/*! sets a property on packet - see \ref gf_filter_pck_set_property \param name the ID or name of the builtin property \param prop the property to set. If null, removes the property. \param is_user if set, indicates the queried property is a user-defined property rather than a built-in property @@ -785,14 +787,14 @@ void set_prop(DOMString name, FilterProperty prop, optional boolean is_user=false); /*! appends data to packet - see \ref gf_filter_pck_expand. New data can be accessed by through the data field. -\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. +\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. \param str the string to append \return an arraybuffer containing the corresponding appended packet data. Full packet data is available through FilterPacket.data */ ArrayBuffer append(DOMString str); /*! appends data to packet - see \ref gf_filter_pck_expand. New data can be accessed by through the data field. -\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. +\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. \param size the number of bytes to append \return an arraybuffer containing the corresponding appended packet data. Full packet data is available through FilterPacket.data */ @@ -800,14 +802,14 @@ /*! appends data to packet - see \ref gf_filter_pck_expand. New data can be accessed by through the data field. -\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. +\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. \param ab the array buffer to append \return an arraybuffer containing the corresponding appended packet data. Full packet data is available through FilterPacket.data */ ArrayBuffer append(ArrayBuffer ab); /*! truncates the packet to the indicated size - \ref gf_filter_pck_truncate -\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. +\warning any previous access to the packet data will be in detached state (no more data in the array buffer) after the call. \param size new packet size*/ void truncate(unsigned long size); @@ -825,12 +827,12 @@ \param cached_pck if set, will be reuse for creation of new packet. This can greatly reduce memory allocations \return the new FilterPacket or None if failure or None if failure ( if grabbing the frame into a local copy failed) */ -void clone(optional FilterPacket cached_pck = null); +FilterPacket clone(optional FilterPacket cached_pck = null); }; /*! FilterEvent expose a filter event object, either for processing a received event or triggering a new event. -The read access to the members of the structure is cross-checked with the event type and throw an error when attempting to access the wrong field +The read access to the members of the structure is cross-checked with the event type and throw an error when attempting to access the wrong field When a field is marked as read-only, this means the event cannot be fired, it can only be received. */ @@ -839,7 +841,7 @@ /*! constructor \param type the type of event. This type cannot be changed -\code +\code let evt = new FilterEvent(GF_FEVT_PLAY); \endcode */ @@ -1042,4 +1044,3 @@ /*! @} */ -
View file
gpac-2.4.0.tar.gz/share/doc/idl/nodejs.idl -> gpac-26.02.0.tar.gz/share/doc/idl/nodejs.idl
Changed
@@ -15,7 +15,7 @@ Unless explictly stated, errors are handled through exceptions. -All constants from GPAC are exported in the module object (e.g. use gpac.GF_Err ...) +All constants from GPAC are exported in the module object (e.g. use gpac.GF_Err ...) The API is very close to the GPAC python bindings. @@ -32,7 +32,6 @@ \param mem_track mem tracker mode \param profile profile name, null for default -\return */ void init(unsigned long mem_track=0, DOMString profile=null); @@ -47,7 +46,6 @@ \param logs \param reset if true, resets all logs to default -\return */ void set_logs(DOMString logs, boolean reset=false); @@ -66,30 +64,29 @@ */ void set_args(Array args); -/*! set profiler (Remotery) callback - see \ref gf_sys_profiler_set_callback -\param callback function for Remotery message, takes a single string parameter and no return values +/*! enables websocket monitoring server - see \ref gf_sys_enable_rmtws +* +* define the \ref rmt_on_new_client attribute to set callbacks +\param enable enable or disable server */ -void set_rmt_fun(function callback); +void enable_rmtws(boolean enable); -/*! send log message to profiler (Remotery) - see \ref gf_sys_profiler_log -\param text text to send +/*! enables user websocket server - see \ref gf_sys_enable_userws +* +* define the \ref userws_on_new_client attribute to set callbacks +* \param enable (default: true) will enable the server if true, stop it if false */ -void rmt_log(DOMString text); +void enable_userws(bool enable); -/*! send message to profiler (Remotery) - see \ref gf_sys_profiler_send -\param text text to send +/*! set the callback function when new clients connect to the monitoring websocket server +* the function shall take one parameter of type \ref _RMTClient */ -void rmt_send(DOMString text); +optional attribute function rmt_on_new_client; -/*! check if profiler (Remotery) sampling is enabled - see \ref gf_sys_profiler_sampling_enabled -\return true if enabled, false otherwise +/*! set the callback function when new clients connect to the user websocket server +* the function shall take one parameter of type \ref _RMTClient */ -boolean rmt_on(); - -/*! enable or disable sampling in profiler (Remotery) - see \ref gf_sys_profiler_enable_sampling -\param do_enable enable or disable sampling -*/ -void rmt_enable(boolean do_enable); +optional attribute function userws_on_new_client; /*! GPAC event proc callback, initially set to null \param evt the user event being dispatched @@ -124,6 +121,41 @@ /*! FileIO constructor*/ attribute _FileIO FileIO; + + +/*! Object representing a monitoring websocket client +* +* will be passed to the session \ref rmt_on_new_client callback +* and to \ref userws_on_new_client if the user websocket server is enabled +*/ +interface _RMTClient { + + /*! a callback called when the client receives data + * + * the function shall be of type function(data) + * where data will be either of type string (for utf8 data) or arraybuffer (for binary data) + */ + optional attribute function on_data; + + /*! a callback called when the client disconnects or is deleted */ + optional attribute function on_close; + + /*! a string of format ip:port representing the client */ + readonly attribute string peer_address; + + /*! sends data to the client on the websocket + \param data the data to send as a utf8 string + */ + void send(string data); + + /*! sends data to the client on the websocket + \param data the data to send as a binary arraybuffer + */ + void send(arraybuffer data); + + +} + /*! @} */ @@ -134,7 +166,7 @@ \ingroup nodejs_grp \brief FilterSession management -See \ref GF_FilterSession +See \ref fs_grp The default behaviour is to run the session in blocking mode, which will prevent executing anything until the end of a session. While this may be OK for workers or simple applications, it is obviously problematic if you have async tasks to be performed by your node app. @@ -190,7 +222,7 @@ /*! FilterSession object*/ interface _FilterSession { -/*! constructor for filter session - see \ref gf_fs_new_defaults. Other options MUST be passed as libgpac options using \ref set_args +/*! constructor for filter session - see \ref gf_fs_new_defaults. Other options MUST be passed as libgpac options using \ref set_args \param flags session flags */ FilterSession(unsigned long flags=0); @@ -242,7 +274,7 @@ /*! post a user task to the filter sesison - see \ref gf_fs_post_user_task -The task object must have an execute callback with no parameter and returning false to cancel the task or the reschedule time in milliseconds +The task object must have an execute callback with no parameter and returning false to cancel the task or the reschedule time in milliseconds \param task task object to post */ @@ -318,7 +350,7 @@ \ingroup nodejs_grp \brief Filter management -See \ref GF_Filter +See \ref fs_filter @{ */ @@ -478,7 +510,7 @@ - filter_alias which is exposed as a boolean. */ interface FilterStatistics { - + }; /*! Statistics object for pid as defined in libgpac. @@ -490,7 +522,7 @@ }; -/*! PropertyValue for filter, PIDs and packets are passed as +/*! PropertyValue for filter, PIDs and packets are passed as : - native JS types for integers and strings - nFraction for 32 and 64 bit fractions @@ -505,7 +537,7 @@ Properties corresponding to constants are usually passed as strings, for example `StreamType`, `CodecID`, `PixelFormat`, `AudioFormat`. */ interface PropertyValue { - + }; /*! fraction object */ @@ -567,7 +599,7 @@ - process_event: callback for processing and event - reconfigure_output: callback for output reconfiguration (PID capability negotiation) -A custom filter must also declare its capabilities, input and output, using push_cap method +A custom filter must also declare its capabilities, input and output, using push_cap method \code let fs = new gpac.FilterSession(); @@ -623,7 +655,7 @@ \endcode -See \ref GF_Filter +See \ref fs_filter @{ */ @@ -889,7 +921,7 @@ void allow_direct_dispatch(); /*! get current clock type info - see \ref gf_filter_pid_get_clock_info -\return clock type +\return clock type */ unsigned long get_clock_type(); @@ -972,7 +1004,7 @@ _FilterPacket new_pck(unsigned long size=0); /*! creates a new packet sharing memory of the filter - see \ref gf_filter_pck_new_shared -The filter object must have a `packet_release` method with arguments FilterPid, FilterPacket +The filter object must have a `packet_release` method with arguments FilterPid, FilterPacket \param data the data to use \return the new FilterPacket or None if failure */ @@ -1009,7 +1041,7 @@ /*! if true, the session has been aborted and this is the final flush for this buffer*/ boolean is_final_flush; - + }; /*! filter packet for custom filters @@ -1155,7 +1187,7 @@ */ interface _FilterEvent { - + }; @@ -1181,7 +1213,7 @@ /*! interface for custom DASH algorithms Callbacks may be changed at runtime, however if the object passed to initialize the binding has no on_download_monitor function, rate monitoring will be disabled for the binding*/ -interface _DASHCustomAlgorithm +interface _DASHCustomAlgorithm { /*! Callback (optional) called upon a period reset. @@ -1226,7 +1258,7 @@ /*! interface for custom DASH groups*/ -interface _DASHGroup +interface _DASHGroup { /*! Index of group, as used in callbacks */ @@ -1243,7 +1275,7 @@ }; /*! interface for custom DASH groups*/ -interface _DASHQualityInfo +interface _DASHQualityInfo { /*! bandwidth in bits per second*/ @@ -1297,7 +1329,7 @@ }; /*! segemnt size info*/ -interface _DASHSegmentInfo +interface _DASHSegmentInfo { /*! segemnt size*/ attribute readonly unsigned long long size; @@ -1367,7 +1399,7 @@ /*! duration of segment being downloaded, in milliseconds - 0 if unknown*/ attribute readonly unsigned long current_seg_dur; - + }; /*! Spacial Relationship Descriptor for DASH group*/ @@ -1390,7 +1422,7 @@ /*! total height of SRD descriptor for this tile*/ attribute readonly unsigned long fh; - + }; @@ -1409,7 +1441,7 @@ This allows generating content in NodeJS without any disk IO, or passing NodeJS data as input to GPAC without intermediate file. -See \ref GF_FileIO for more details +See \ref osfile_grp for more details A FileIO object is constructed from the URL to wrap and a factory object with callbacks used to access the file. For example, to wrap a file for input: @@ -1628,7 +1660,7 @@ attribute unsigned long write(ArrayBuffer buffer); /*! callback function used to monitor end of session. It is undefined by default -\param error GPAC error code for the session +\param error GPAC error code for the session. If 1 (GF_EOS), the session is ended but underlying network is kept alive, otherwise session is destroyed */ attribute void close(unsigned long error); @@ -1646,5 +1678,3 @@ }; /*! @} */ - -
View file
gpac-2.4.0.tar.gz/share/doc/man/gpac-filters.1 -> gpac-26.02.0.tar.gz/share/doc/man/gpac-filters.1
Changed
@@ -18,7 +18,7 @@ .SH inspect .LP .br -Description: Inspect packets +Description: Packet inspector .br .br @@ -28,7 +28,7 @@ .br The default options inspect only PID changes. .br -If .I full is not set, .I mode=frame is forced and PID properties are formatted in human-readable form, one PID per line. +If .I full is not set, .I mode is forced to frame and PID properties are formatted in human-readable form, one PID per line. .br Otherwise, all properties are dumped. .br @@ -55,6 +55,8 @@ .br * ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available .br +* tmcd: timecode as provided in SEI, N/A if not available (requires reframer) +.br * dur: duration in stream timescale .br * frame: framing status @@ -175,7 +177,7 @@ .SH Options (expert): .LP .br -log (str, default: stdout, Enum: _any|stderr|stdout|GLOG|null): set probe log filename +log (str, default: stdout, Enum: _any|stderr|stdout|GLOG|TL|null): set probe log filename .br * _any: target file path and name .br @@ -185,6 +187,8 @@ .br * GLOG: use GPAC logs app@info .br +* TL: use GPAC log tool TL at level info +.br * null: silent mode .br @@ -253,6 +257,10 @@ .br rbuffer (uint, default: 0, updatable): rebuffer trigger in ms. If 0 or more than buffer, disable rebuffering .br +stats (bool, default: false): compute statistics for PIDs +.br +timeout (uint, default: 5000): timeout in ms when doing simple inspection in case no packets are received on some PIDs +.br test (enum, default: no, updatable): skip predefined set of properties, used for test mode .br * no: no properties skipped @@ -278,7 +286,7 @@ .SH probe .LP .br -Description: Probe source +Description: Source prober .br .br @@ -407,7 +415,6 @@ .br The compositor can act as a source filter when the .I src option is explicitly set, independently from the operating mode: .br -Example .br gpac compositor:src=source.mp4 vout .br @@ -417,7 +424,6 @@ .br The compositor can act as a source filter when the source url uses one of the compositor built-in protocol schemes: .br -Example .br gpac -i mosaic://URL1:URL2 vout .br @@ -592,9 +598,9 @@ .br epow2 (bool, default: true, updatable): emulate power-of-2 textures for OpenGL (old hardware). Ignored if OpenGL rectangular texture extension is enabled .br -* yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations +* true: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations .br -* no: video is resized to a power of 2 texture when mapping to a shape +* false: video is resized to a power of 2 texture when mapping to a shape .br .br @@ -750,7 +756,7 @@ .br noaudio (bool, default: false): disable audio output .br -opfmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format to use for output. Ignored in .I player mode +opfmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb8|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format to use for output. Ignored in .I player mode .br .br @@ -930,6 +936,20 @@ .br initseg (str): local init segment name when input is a single ISOBMFF segment .br +extk (bool, default: true): allow external track loading +.br +ctso (sint): value to add to CTS offset for tracks using negative ctts +.br +- set to -1 to use the cslg box info or the minimum cts offset present in the track +.br +- set to -2 to use the minimum cts offset present in the track (cslg ignored) +.br + +.br +norw (bool, default: false): skip reformatting of samples - should only be used when rewriting fragments +.br +keepc (bool, default: true): keep corrupted samples (for multicast sources only) +.br .br .SH bifsdec @@ -993,6 +1013,22 @@ .br .br +.SS Packet Injecting +.br +The filter can be used to inject a single packet instead of a file using .I pck option. +.br +No specific properties are attached, except a timescale if .I ptime is set. +.br +Example +.br +gpac fin:pck=str@"My Sample Text":ptime=2500/100:#CodecID=stxt:#StreamType=text +.br + +.br +This will declare the PID as WebVTT and send a single packet with payload My Sample Text and a timestamp value of 25 second. +.br + +.br .SH Options (expert): .LP .br @@ -1008,12 +1044,14 @@ .br pck (mem): data to use instead of file .br +ptime (frac, default: 0/0): timing for data packet, ignored if den is 0 +.br .br .SH btplay .LP .br -Description: BT/XMT/X3D loader +Description: BT/XMT/X3D decoder .br .br @@ -1021,7 +1059,7 @@ .br .br -When .I sax_dur=N is set, the filter will do a progressive load of the source and cancel current loading when processing time is higher than N. +When .I sax_dur is set to N, the filter will do a progressive load of the source and cancel current loading when processing time is higher than N. .br .br @@ -1066,7 +1104,7 @@ .br * auto: cache to disk if content length is known, no cache otherwise .br -* disk: cache to disk, discard once session is no longer used +* disk: cache to disk, discard once session is no longer used .br * keep: cache to disk and keep .br @@ -1088,12 +1126,14 @@ .br blockio (bool, default: false): use blocking IO .br +idelay (uint, default: 0): delay first request by the given number of ms +.br .br .SH svgplay .LP .br -Description: SVG loader +Description: SVG decoder .br .br @@ -1101,7 +1141,7 @@ .br .br -When .I sax_dur=N is set, the filter will do a progressive load of the source and cancel current loading when processing time is higher than N. +When .I sax_dur is set to N, the filter will do a progressive load of the source and cancel current loading when processing time is higher than N. .br .br @@ -1268,6 +1308,32 @@ .br .br +.SH xviddec +.LP +.br +Description: XVid decoder +.br + +.br +This filter decodes MPEG-4 part 2 (and DivX) through libxvidcore library. +.br + +.br +.SH Options (expert): +.LP +.br +deblock_y (bool, default: false): enable Y deblocking +.br +deblock_uv (bool, default: false): enable UV deblocking +.br +film_effect (bool, default: false): enable film effect +.br +dering_y (bool, default: false): enable Y deblocking +.br +dering_uv (bool, default: false): enable UV deblocking +.br + +.br .SH j2kdec .LP .br @@ -1303,6 +1369,39 @@ .br .br +.SH rfac4 +.LP +.br +Description: AC4 reframer +.br + +.br +This filter parses AC4 files/data and outputs corresponding audio PID and frames. +.br + +.br +No options +.br + +.br +.SH ufac4 +.LP +.br +Description: AC4 writer +.br + +.br +This filter converts MPEG-H Audio streams into AC4 encapsulated data. +.br + +.br +.SH Options (expert): +.LP +.br +rcfg (bool, default: true): force repeating decoder config at each I-frame +.br + +.br .SH a52dec .LP .br @@ -1410,6 +1509,18 @@ .br dvbtxt (bool, default: false): export DVB teletext streams .br +upes (enum, default: no): keep unknown PES streams +.br +* no: ignored the streams +.br +* info: declare the stream as fake (no data forward), turns on dvbtxt +.br +* full: declare the stream and sends data +.br + +.br +mappcr (bool, default: true): remap PCR and timestamps into continuous timeline +.br .br .SH sockin @@ -1455,6 +1566,11 @@ .br On OSX with VM packet replay you will need to force multicast routing, e.g. route add -net 239.255.1.4/32 -interface vboxnet0 .br +.SH Time Regulation +.LP +.br +The filter uses the time between the last two received packets to estimates how often it should check for inputs. The maximum and minimum times to wait between two calls is given by the .I mwait option. The maximum time may need to be reduced for very high bitrates sources. +.br .br .SH Options (expert): @@ -1484,6 +1600,8 @@ .br timeout (uint, default: 10000): set timeout in ms for UDP socket(s), 0 to disable timeout .br +mwait (v2di, default: 1x30): set min and max wait times in ms to avoid too frequent polling +.br reorder_pck (uint, default: 100): number of packets delay for RTP reordering (M2TS over RTP) .br reorder_delay (uint, default: 10): number of ms delay for RTP reordering (M2TS over RTP) @@ -1501,15 +1619,43 @@ .br .br -Experimental DVB support for linux, requires a channel config file through .I chcfg +This filter reads raw MPEG-2 TS from DVB-T/T2 and DVB-S/S2 cards on linux. .br - + +.br +The URL scheme used is dvb:// with the following syntaxes: +.br +* `dvb://CHAN`: tunes to channel CHAN in the channel configuration file. +.br +* `dvb://+CHAN`: tunes to multiplex contaning channel CHAN and expose all programs. +.br +* `dvb://=N`: tunes to the N-th channel in the channel configuration file. +.br +* `dvb://@FREQ`: tunes to frequency FREQ and exposes all channels in multiplex. +.br +* `dvb://@=N`: tunes to N-th frequency and exposes all channels in multiplex. +.br +* `dvb://@chlist`: populates the .I chans option with available channels in the configuration file and do nothing else. +.br + +.br +When tuning by channel name CHAN, the first entry in the channel configuration file starting with CHAN will be used. +.br + +.br +The channel configuration file is set through .I chcfg. The expected format is VDR as produced by w_scan, with a syntax extended for comment lines, starting with #. +.br +Within a comment line, the following keywords can be used to override defaults: +.br + * `dev=N`: set the adapter index (N integer) or full path (N string). +.br + * `idx=K`: set the frontend index K. .br -The URL syntax is dvb://CHANNAME@FRONTEND, with: + * `csidx=S`: set the committed switch index for DiSEqC. .br - * CHANNAME: the channel name as listed in the channel config file + .br - * frontend: the index of the DVB adapter to use (optional, default is 0) +To view the default channels, use gpac -hx dvbin. .br .br @@ -1518,9 +1664,21 @@ .br src (cstr): URL of source content .br -block_size (uint, default: 65536): block size used to read file +block_size (uint, default: 65536): block size used to read device +.br +chcfg (cstr, default: $GCFG/channels.conf): path to channels configuration file +.br +dev (str, default: 0): path to DVB adapter - if first character is a number, this is the device index +.br +idx (uint, default: 0): frontend index +.br +timeout (uint, default: 5000): timeout in ms before tune failure .br -chcfg (cstr): path to channels.conf file +csleep (uint, default: 15): config sleep in ms between DiSEqC commands +.br +csidx (uint, default: 0): committed switch index for DiSEqC +.br +chans (strl): list of all channels, only pupulated for dvb://@chlist URL .br .br @@ -1618,7 +1776,7 @@ .SH dashin .LP .br -Description: MPEG-DASH and HLS client +Description: DASH & HLS client .br .br @@ -1666,7 +1824,6 @@ .br To expose a live DASH session to route: .br -Example .br gpac -i MANIFEST_URL dashin:forward=file -o route://225.0.0.1:8000/ .br @@ -1842,7 +1999,7 @@ .br spd (sint, default: -I): suggested presentation delay in ms .br -route_shift (sint, default: 0): shift ROUTE requests time by given ms +mcast_shift (sint, default: 0): shift requests time by given ms for multicast sources .br server_utc (bool, default: yes): use ServerUTC or Date HTTP headers instead of local UTC .br @@ -1926,6 +2083,16 @@ .br groupsel (bool, default: no): select groups based on language (by default all playable groups are exposed) .br +xas (enum, default: codec): enable cross adaptation set switching (disabled if .I split_as is set) +.br +* no: disabled +.br +* codec: switching across sets only allowed for same codec +.br +* all: switching across sets allowed across any representation types +.br + +.br chain_mode (enum, default: on): MPD chaining mode .br * off: do not use MPD chaining @@ -1940,6 +2107,18 @@ .br bsmerge (bool, default: true): allow merging of video bitstreams (only HEVC for now) .br +keep_burl (enum, default: strip): control BaseURL in manifest +.br +* strip: strip BaseURL (default) +.br +* keep: keep BaseURL +.br +* inject: inject local relative URL before BaseURL value specified by relative_url option +.br + +.br +relative_url (str, default: ./): relative string to inject before BaseURL when keep_base_url is set to inject +.br .br .SH cdcrypt @@ -1957,7 +2136,7 @@ .br Otherwise, the filter uses a configuration file. .br -The syntax is available at https://wiki.gpac.io/Common-Encryption +The syntax is available at https://wiki.gpac.io/xmlformats/Common-Encryption .br The DRM config file can be set per PID using the property DecryptInfo (highest priority), CryptInfo (lower priority) or set at the filter level using .I cfile (lowest priority). .br @@ -2005,7 +2184,7 @@ .br The CENC encryptor supports CENC, ISMA and Adobe encryption. It uses a DRM config file for declaring keys. .br -The syntax is available at https://wiki.gpac.io/Common-Encryption +The syntax is available at https://wiki.gpac.io/xmlformats/Common-Encryption .br The DRM config file can be set per PID using the property CryptInfo, or set at the filter level using .I cfile. .br @@ -2026,6 +2205,8 @@ .br bk_stats (bool): print number of encrypted blocks to stdout upon exit .br +bk_skip (bool): skip encryption but performs all other tasks (test mode) +.br .br .SH mp4mx @@ -2056,7 +2237,6 @@ .br To force non-item streams to be multiplexed as items, use #ItemID option on that PID: .br -Example .br gpac -i source.jpg:#ItemID=1 -o file.mp4 .br @@ -2123,7 +2303,7 @@ .br QT tags can be specified using qtt_NAME property names, and will be added using formatting specified in MP4Box -h tags. .br -Other tag class may be specified using tag_NAME property names, and will be added if .I tags is set to all using: +Other tag class may be specified using tag_NAME property names, and will be added if .I itags is set to all using: .br - NAME as a box 4CC if NAME is four characters long .br @@ -2133,6 +2313,8 @@ .br .br +Property names formatted as cust_NAME@MEAN are added as a custom tag with name NAME and mean MEAN. Both NAME and MEAN can be empty. +.br .SH User data .LP .br @@ -2177,7 +2359,7 @@ .br The property grp_EMSG consists in one or more EventMessageBox as defined in MPEG-DASH. .br -- in fragmented mode, presence of these boxes in a packet will start a new fragment, with the boxes written before the moof +- in fragmented mode, presence of this property in a packet will start a new fragment, with the boxes written before the moof .br - in regular mode, an internal sample group of type EMSG is currently used for emsg box storage .br @@ -2214,7 +2396,7 @@ .br .br -The default media type used for a PID can be overriden using property StreamSubtype. +The default media type used for a PID can be overridden using property StreamSubtype. .br Example .br @@ -2243,7 +2425,9 @@ .br dref (bool, default: false): only reference data from source file - not compatible with all media sources .br -ctmode (enum, default: edit): set composition offset mode for video tracks +ctmode (enum, default: auto): set composition offset mode for video tracks +.br +* auto: if fragmenting an ISOBMFF source, use source settings otherwise resolve to edit .br * edit: uses edit lists to shift first frame to presentation time 0 .br @@ -2333,12 +2517,22 @@ .br tfdt_traf (bool, default: false): force tfdt box in each traf .br -nofragdef (bool, default: false): disable default flags in fragments +nofragdef (bool, default: false): disable default fragment flags in initial moov .br straf (bool, default: false): use a single traf per moof (smooth streaming and co) .br strun (bool, default: false): use a single trun per traf (smooth streaming and co) .br +prft (enum, default: sender): set prft box mode, disabled if not fragmented mode +.br +* off: disable prft box +.br +* sender: put ntp time before encoder +.br +* both: put sender time (if available) and ntp time when writing the moof +.br + +.br psshs (enum, default: moov): set pssh boxes store mode .br * moof: in first moof of each segments @@ -2405,6 +2599,8 @@ .br styp (str): set segment styp major brand (and optionally version) to the given 4CC.version .br +lmsg (bool, default: false): set lmsg brand for the last segment or fragment +.br mediats (sint, default: 0): set media timescale. A value of 0 means inherit from PID, a value of -1 means derive from samplerate or frame rate .br ase (enum, default: v0): set audio sample entry mode for more than stereo layouts @@ -2503,7 +2699,7 @@ .br dvsingle (bool, default: false): ignore DolbyVision profile 8 in xps inband mode if profile 5 is already set .br -tsalign (bool, default: true): enable timeline realignment to 0 for first sample - if false, this will keep original timing with empty edit (possibly long) at begin) +tsalign (bool, default: true): enable timeline realignment to 0 for first sample - if false, this will keep original timing with empty edit (possibly long) at begin .br chapm (enum, default: both): chapter storage mode .br @@ -2533,6 +2729,8 @@ .br trunv1 (bool, default: false): force using version 1 of trun regardless of media type or CMAF brand .br +rsot (bool, default: false): inject redundant sample timing information when present +.br .br .SH rfqcp @@ -2612,7 +2810,7 @@ .br This filter reads NHNT files/data to produce a media PID and frames. .br -NHNT documentation is available at https://wiki.gpac.io/NHNT-Format +NHNT documentation is available at https://wiki.gpac.io/xmlformats/NHNT-Format .br .br @@ -2634,7 +2832,7 @@ .br This filter reads NHML files/data to produce a media PID and frames. .br -NHML documentation is available at https://wiki.gpac.io/NHML-Format +NHML documentation is available at https://wiki.gpac.io/xmlformats/NHML-Format .br .br @@ -2671,6 +2869,8 @@ .br explicit (bool, default: false): use explicit layered (SVC/LHVC) import .br +force_sync (bool, default: false): force sync points on non-IDR samples with I slices (not compliant) +.br strict_poc (enum, default: off): delay frame output of an entire GOP to ensure CTS info is correct when POC suddenly changes .br * off: disable GOP buffering @@ -2695,10 +2895,14 @@ .br deps (bool, default: false): import sample dependency information .br +refs (bool, default: false): import sample reference picture list (currently only for HEVC and VVC) +.br seirw (bool, default: true): rewrite AVC sei messages for ISOBMFF constraints .br audelim (bool, default: false): keep Access Unit delimiter in payload .br +keepfiller (bool, default: false): keep filler NAL units in output +.br notime (bool, default: false): ignore input timestamps, rebuild from 0 .br dv_mode (enum, default: auto): signaling for DolbyVision @@ -2735,7 +2939,7 @@ .br .br -bsdbg (enum, default: off): debug NAL parsing in parser@debug logs +bsdbg (enum, default: off): debug NAL parsing in media@debug logs .br * off: not enabled .br @@ -2799,7 +3003,7 @@ .br * WebVTT: https://www.w3.org/TR/webvtt1/ .br -* TTXT: https://wiki.gpac.io/TTXT-Format-Documentation +* TTXT: https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation .br * QT 3GPP Text XML (TexML): Apple QT6, likely deprecated .br @@ -2817,6 +3021,8 @@ .br * TTML: ISO/IEC 14496-30 XML subtitles .br +* stxt and sbtt: ISO/IEC 14496-30 text stream and text subtitles +.br * Others: 3GPP/QT Timed Text .br @@ -2868,7 +3074,6 @@ .br The subtitle zero time must be prefixed with T when the option is not set as a global argument: .br -Example .br gpac -i test.ttml:ttml_zero=T10:00:00 ... .br @@ -2887,9 +3092,10 @@ .SH Simple Text Support .LP .br -The text loader can convert input files in simple text streams of a single packet, by forcing the codec type on the input:EX gpac -i test.txt:#CodecID=stxt ... +The text loader can convert input files in simple text streams of a single packet, by forcing the codec type on the input: .br -Example +.br +gpac -i test.txt:#CodecID=stxt ... .br gpac fin:pck="Text Data":#CodecID=stxt ... .br @@ -2907,10 +3113,19 @@ .br .br -.SH Options (expert): +.SH Notes .LP .br -webvtt (bool, default: false): force WebVTT import of SRT files +When reframing simple text streams from demuxers (e.g. subtitles from MKV), the output format of these streams can be selected using .I stxtmod. +.br + +.br +When importing SRT, SUB or SSA files, the output format of the PID can be selected using .I stxtmod. +.br + +.br +.SH Options (expert): +.LP .br nodefbox (bool, default: false): skip default text box .br @@ -2948,13 +3163,17 @@ .br stxtdur (frac, default: 1): duration for simple text .br -stxtmod (enum, default: none): simple text stream mode +stxtmod (enum, default: tx3g): text stream mode for simple text streams and SRT inputs +.br +* stxt: output PID formatted as simple text stream (remove markup in VTT/SRT payload) +.br +* sbtt: output PID formatted as subtitle text stream (keep markup in VTT/SRT payload) .br -* none: declares output PID as simple text stream +* tx3g: output PID formatted as TX3G/Apple stream .br -* tx3g: declares output PID as TX3G/Apple stream +* vtt: output PID formatted as WebVTT stream .br -* vtt: declares output PID as WebVTT stream +* webvtt: same as vtt (for backward compatiblity .br .br @@ -2969,7 +3188,7 @@ .br This filter decodes TTXT/TX3G streams into a BIFS scene graph of the compositor filter. .br -The TTXT documentation is available at https://wiki.gpac.io/TTXT-Format-Documentation +The TTXT documentation is available at https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation .br .br @@ -3217,6 +3436,12 @@ .br .br +By default output files are created directly, which may lead to issues if concurrent programs attempt to access them. +.br +By enabling .I atomic, files will be created in target destination folder with the .gftmp suffix and move to their final name upon close. +.br + +.br .SH Discard sink mode .LP .br @@ -3294,12 +3519,14 @@ .br force_null (bool, default: false): force no output regardless of file name .br +atomic (bool, default: false): use atomic file write for non append modes +.br .br .SH uflatm .LP .br -Description: Raw AAC to LATM writer +Description: LATM rewriter .br .br @@ -3317,7 +3544,7 @@ .SH ufadts .LP .br -Description: ADTS writer +Description: ADTS rewriter .br .br @@ -3343,7 +3570,7 @@ .SH ufmhas .LP .br -Description: MHAS writer +Description: MHAS rewriter .br .br @@ -3354,14 +3581,14 @@ .SH Options (expert): .LP .br -syncp (bool, default: yes): if set, insert sync packet at each frame, otherwise only at SAP +syncp (bool, default: false): if set, insert sync packet at each frame, otherwise only at SAP .br .br .SH reframer .LP .br -Description: Media Reframer +Description: Media reframer .br .br @@ -3421,7 +3648,6 @@ .br For example to simulate a live DASH: .br -Example .br gpac -i m.mp4 reframer:rt=on -o live.mpd:dynamic .br @@ -3436,6 +3662,8 @@ .br The formats allowed for times specifiers are: .br +* 'TC'HH:MM:SS:FF: specify time in timecode +.br * 'T'H:M:S, 'T'M:S: specify time in hours, minutes, seconds .br * 'T'H:M:S.MS, 'T'M:S.MS, 'T'S.MS: specify time in hours, minutes, seconds and milliseconds @@ -3600,6 +3828,27 @@ .br .br +.SH Absorbing stream discontinuities +.LP +.br +Discontinuities may happen quite often in streaming sessions due to resolution switching, codec change, etc ... +.br +While GPAC handles these discontinuities internally, it may be desired to ignore them, for example when a source is known to have no discontinuity but GPAC detects some due to network errors or other changing properties that should be ignored. +.br +The .I nodisc option allows removing all discontinuities once a stream is setup. +.br +Warning: Make sure you know what you are doing as using this option could make the stream not playable (ignoring a codec config change). +.br +Example +.br +gpac -i SOMEURL reframer:nodisc -o DASH_ORIGIN +.br + +.br +In this example, the dasher filter will never trigger a period switch due to input stream discontinuity. +.br + +.br .SH Options (expert): .LP .br @@ -3607,11 +3856,13 @@ .br rt (enum, default: off, updatable): real-time regulation mode of input .br -* off: disables real-time regulation +* off: disable real-time regulation +.br +* on: enable real-time regulation, one clock per PID .br -* on: enables real-time regulation, one clock per PID +* sync: enable real-time regulation, one clock for all PIDs .br -* sync: enables real-time regulation one clock for all PIDs +* align: send packets in DTS order following one clock for all PIDs (undo input packet bursts), no real-time regulation .br .br @@ -3637,7 +3888,7 @@ .br frames (sintl, updatable): drop all except listed frames (first being 1). A negative value -V keeps only first frame every V frames .br -xs (strl): extraction start time(s) +xs (strl): extraction start time(s). If not set and an extraction end time is set, 0 is used .br xe (strl): extraction end time(s). If less values than start times, the last time interval extracted is an open range .br @@ -3657,6 +3908,8 @@ .br xots (bool, default: false): keep original timestamps after extraction .br +xdts (bool, default: false): compute start times based on DTS and not CTS +.br nosap (bool, default: false): do not cut at SAP when extracting range (may result in broken streams) .br splitrange (bool, default: false): signal file boundary at each extraction first packet for template-base file generation @@ -3679,6 +3932,8 @@ .br * media: use UTC of media (abort if none found) .br +* tc: use timecode of media (be careful: considered day will be today) +.br .br utc_probe (uint, default: 5000): timeout in milliseconds to try to acquire UTC reference from media @@ -3695,14 +3950,18 @@ .br .br +sapcue (uint, default: 0): treat SAPs smaller than or equal to this value as cue points +.br rmseek (bool, default: false, updatable): remove seek flag of all sent packets .br +nodisc (bool, default: false, updatable): ignore all discontinuities from input - see filter help +.br .br .SH writegen .LP .br -Description: Stream to file +Description: Stream to File converter .br .br @@ -3717,7 +3976,7 @@ .br exporter (bool, default: false): compatibility with old exporter, displays export results .br -pfmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format for raw extract. If not set, derived from extension +pfmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb8|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format for raw extract. If not set, derived from extension .br .br @@ -3737,7 +3996,7 @@ .br .br -split (bool, default: false): force one file per decoded frame +split (bool, default: false): force one file per frame .br frame (bool, default: false): force single frame dump with no rewrite. In this mode, all codec types are supported .br @@ -3759,12 +4018,16 @@ .br .br +add_nl (bool, default: false): add new line after each packet when dumping text streams +.br +rawb (bool, default: false): force direct dump of input without framing rewrite. In this mode, all codec types are supported +.br .br .SH ufnalu .LP .br -Description: AVC/HEVC to AnnexB writer +Description: AVC/HEVC to AnnexB rewriter .br .br @@ -3814,7 +4077,7 @@ .SH ufvtt .LP .br -Description: WebVTT unframer +Description: WebVTT rewriter .br .br @@ -3829,6 +4092,8 @@ .br merge_cues (bool, default: true): merge VTT cues (undo ISOBMFF cue split) .br +noempty (bool, default: false): do not create an empty file if no VTT cues are present +.br .br .SH nhntw @@ -3840,7 +4105,7 @@ .br This filter converts a single stream to an NHNT output file. .br -NHNT documentation is available at https://wiki.gpac.io/NHNT-Format +NHNT documentation is available at https://wiki.gpac.io/xmlformats/NHNT-Format .br .br @@ -3862,7 +4127,7 @@ .br This filter converts a single stream to an NHML output file. .br -NHML documentation is available at https://wiki.gpac.io/NHML-Format +NHML documentation is available at https://wiki.gpac.io/xmlformats/NHML-Format .br .br @@ -3879,6 +4144,8 @@ .br pckp (bool, default: false): full NHML dump .br +payload (bool, default: false): dump payload (scte35 only at the moment), should be combined with ǹhmlonly +.br chksum (enum, default: none): insert frame checksum .br * none: no checksum @@ -3894,7 +4161,7 @@ .SH vobsubdmx .LP .br -Description: VobSub parser +Description: VobSub demultiplexer .br .br @@ -3907,6 +4174,8 @@ .br blankframe (bool, default: true): force inserting a blank frame if first subpic is not at 0 .br +keepempty (bool, default: false): declare VobSub tracks with no frames +.br .br .SH avimx @@ -3956,7 +4225,7 @@ .br .br -This filter writes a single uncompressed audio input PID to a sound card or other audio output device. +This filter writes a single PCM (uncompressed) audio input PID to a sound card or other audio output device. .br .br @@ -4010,7 +4279,7 @@ .SH ufm4v .LP .br -Description: M4V writer +Description: M4V rewriter .br .br @@ -4028,7 +4297,7 @@ .SH ufvc1 .LP .br -Description: VC1 writer +Description: VC1 rewriter .br .br @@ -4198,7 +4467,7 @@ .SH vcrop .LP .br -Description: Video crop +Description: Video cropper .br .br @@ -4230,7 +4499,7 @@ .SH vflip .LP .br -Description: Video flip +Description: Video flipper .br .br @@ -4275,7 +4544,7 @@ .br size (v2di, default: 0x0): source video resolution .br -spfmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): source pixel format. When not set, derived from file extension +spfmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb8|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): source pixel format. When not set, derived from file extension .br .br @@ -4449,7 +4718,7 @@ .br .br -When .I ka is used to keep refreshing the playlist on regular basis, the playlist must end with a new line. +When .I plka is used to keep refreshing the playlist on regular basis, the playlist must end with a new line. .br Playlist refreshing will abort: .br @@ -4461,7 +4730,7 @@ .br A playlist directive line can contain zero or more directives, separated with space. The following directives are supported: .br -* repeat=N: repeats N times the content (hence played N+1). +* repeat=N: repeats N times the content (hence played N+1), infinite loop if negative. .br * start=T: tries to play the file from start time T seconds (double format only). This may not work with some files/formats not supporting seeking. .br @@ -4493,11 +4762,21 @@ .br * chap=NAME: assigns chapter name at the start of next URL (filter always removes source chapter names). .br +* base_url=PATH: overrides base URL for all following entries in the playlist. To reset, use an empty string. +.br + +.br +When the playlist is transmitted as packets (pipes, sockets, ...), the following directives also apply: +.br +* replace: replaces the entire playlist content with new packet payload, otherwise concatenate (payload must start with #replace). +.br +* purge: avoids having the playlist continuously growing by removing all inactive content before previous #purge directive, evaluated at each new packet only (payload must start with #purge). +.br .br The following global options (applying to the filter, not the sources) may also be set in the playlist: .br -* ka=N: force .I ka option to N millisecond refresh. +* ka=N: force .I plka option to N millisecond refresh. .br * floop=N: set .I floop option from within playlist. .br @@ -4702,7 +4981,7 @@ .br timescale (uint, default: 0): force output timescale on all PIDs (0 uses the timescale of the first PID found) .br -ka (uint, default: 0): keep playlist alive (disable loop), waiting for a new input to be added or #end directive to end playlist. The value specifies the refresh rate in ms +plka (uint, default: 0): keep playlist alive (disable loop), waiting for a new input to be added or #end directive to end playlist. The value specifies the refresh rate in ms .br timeout (luint, default: -1): timeout in ms after which the playlist is considered dead (-1 means indefinitely) .br @@ -4722,6 +5001,8 @@ .br sigcues (bool, default: false): inject CueStart property at each source begin (new or repeated) for DASHing .br +sigperiods (bool, default: false): ask for a new DASH Period at each source begin ; useful when media timing needs to be reset at loops (TTML, ...) +.br fdel (bool, default: false): delete source files after processing in playlist mode (does not delete the playlist) .br keepts (bool, default: false): keep initial timestamps unmodified (no reset to 0) @@ -4815,6 +5096,8 @@ .br * N: insert NTP timestamp in TEMI timeline descriptor .br +* n: insert NTP timestamp using NTP for first packet than incrementing based on media timestamp (for non real-time) +.br * ID_OR_URL: If number, indicate the TEMI ID to use for external timeline. Otherwise, give the URL to insert .br @@ -4863,6 +5146,10 @@ .br Warning: multipliers (k,m,g) are not supported in TEMI options. .br + +.br +When input TEMI properties are found, they can be removed using .I temi_fwd. When rewritten, any NTP information present is rewritten to the current NTP. +.br .SH Adaptive Streaming .LP .br @@ -4878,10 +5165,31 @@ .br The filter watches the property FileNumber on incoming packets to create new files, or new segments in DASH mode. .br +.SH Custom streams +.LP +.br The filter will look for property M2TSRA set on the input stream. .br The value can either be a 4CC or a string, indicating the MP2G-2 TS Registration tag for unknown media types. .br +The value SRT (alias: srt, SRT) will inject an SRT header with frame number increasing at each packet and start time 0. +.br +Example +.br +gpac -i source.srt:#M2TSRA='SRT ' -o mux.ts +.br + +.br +This will inject the content of the source SRT as a PES data stream, removing any markup. +.br +Example +.br +gpac -i source.srt:stxtmod=sbtt:#M2TSRA='SRT ' -o mux.ts +.br + +.br +This will inject the content of the source SRT as a PES data stream, keeping the markup. +.br .br .SH Notes @@ -4891,6 +5199,12 @@ .br .br +By default text streams are embeded using HLS ID3 schemes, use M2TSRA property to use raw private PES. +.br +WebVTT header and TX3G formatting are removed, only the text data is injected. +.br + +.br .SH Options (expert): .LP .br @@ -4956,7 +5270,7 @@ .br pcr_only (bool, default: false): enable PCR-only TS packets .br -pcr_init (lsint, default: -1): set initial PCR value for the programs. A negative value means random value is picked +pcr_init (lsint, default: -1): set initial PCR value for the programs. -1 means random value is picked, other negative value means offset to maximum PCR .br sid (uint, default: 0): set service ID for the program .br @@ -4976,12 +5290,22 @@ .br keepts (bool, default: false): keep cts/dts untouched and adjust PCR accordingly, used to keep TS unmodified when dashing .br +temi_fwd (enum, default: fwd): input TEMI properties when remuwing +.br +* drop: remove input descriptors +.br +* fwd: forward input descriptors +.br +* ntp: forward input descriptors after NTP rewriting +.br + +.br .br .SH dasher .LP .br -Description: DASH and HLS segmenter +Description: DASH & HLS segmenter .br .br @@ -5015,7 +5339,7 @@ .br .SS Template strings .br -The segmenter uses templates to derive output file names, regardless of the DASH mode (even when templates are not used). The default one is $File$_dash for ondemand and single file modes, and $File$_$Number$ for separate segment files +The segmenter uses templates to derive output file names and folder, regardless of the DASH mode (even when templates are not used). The default one is $File$_dash for ondemand and single file modes, and $File$_$Number$ for separate segment files .br Example .br @@ -5045,6 +5369,8 @@ .br * $Bandwidth$: replaced by representation bandwidth. .br +* $SubNumber%%0Nd$: replaced by the segment number in the segment sequence, possibly prefixed with 0 +.br Note: these strings are not replaced in the manifest templates elements. .br @@ -5071,6 +5397,10 @@ .br .br +Other properties can also be set, see below. +.br + +.br .SS PID assignment and configuration .br To assign PIDs into periods and adaptation sets and configure the session, the segmenter looks for the following properties on each input PID: @@ -5169,7 +5499,6 @@ .br The segmenter handles the XML descriptor as a string and does not attempt to validate it. Descriptors, as well as some segmenter filter arguments, are string lists (comma-separated by default), so that multiple descriptors can be added: .br -Example .br gpac -i m1.mp4:#RDesc=<Elem attribute="1"/>,<Elem2>text</Elem2> -o test.mpd .br @@ -5261,6 +5590,10 @@ .br .br +When .I sflush is set to single, segmentation is skipped and a single segment is generated per input. +.br + +.br .SS Dynamic (real-time live) Mode .br The dasher does not perform real-time regulation by default. @@ -5319,7 +5652,6 @@ .br You can combine LL-HLS and DASH-LL generation: .br -Example .br gpac -i source.mp4 reframer:rt=on -o live.mpd:dual:segdur=2:cdur=0.2:asto=1.8:llhls=br:profile=live:dmode=dynamic .br @@ -5387,7 +5719,6 @@ .br This feature is typically combined with a list of files as input: .br -Example .br gpac -i list.m3u:sigcues -o res/live.mpd .br @@ -5501,7 +5832,7 @@ .br Example .br -gpac -i s1.mp4 -i s2.mp4:#CryptInfo=clear:#Period=3 -i s3.mp4:#Period=3 dasher:gencues cecrypt:cfile=roll_period.xml -o live.mpd +gpac -i s1.mp4 -i s2.mp4:#CryptInfo=clear:#Period=2 -i s3.mp4:#Period=3 dasher:gencues cecrypt:cfile=roll_period.xml -o live.mpd .br .br @@ -5533,6 +5864,74 @@ .br .br +.SS Batch Operations +.br +The segmentation can be performed in multiple calls using a DASH context set with .I state. +.br +Between calls, the PIDs are reassigned by checking that the PID ID match between the calls and: +.br +- the input file names match between the calls +.br +- or the representation ID (and period ID if specified) match between the calls +.br + +.br +If a PID is not matched, it will be assigned to a new period. +.br + +.br +The default behaviour assume that the same inputs are used for segmentation and rebuilds a contiguous timeline at each new file start. +.br +If the inputs change but form a continuous timeline, -keep_ts)() must be used to skip timeline reconstruction. +.br + +.br +The inputs will be segmented for a duration of .I subdur if set, otherwise the input media duration. +.br +When inputs are over, they are restarted if .I loop is set otherwise a new period is created. +.br +To avoid this behaviour, the .I sflush option should be set to end or single, indicating that further sources for the same representations will be added in subsequent calls. When .I sflush is not off, the .I loop option is ignored. +.br + +.br +Example +.br +gpac -i SRC -o dash.mpd:segdur=2:state=CTX && gpac -i SRC -o dash.mpd:segdur=2:state=CTX +.br + +.br +This will generate all dash segments for SRC (last one possibly shorter) and create a new period at end of input. +.br +Example +.br +gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop +.br + +.br +This will generate all dash segments for SRC and restart SRC to fill-up last segment. +.br +Example +.br +gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end +.br + +.br +This will generate all dash segments for SRC without looping/closing the period at end of input. Timestamps in the second call will be rewritten to be contiguous with timestamp at end of first call. +.br +Example +.br +gpac -i SRC1 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts && gpac -i SRC2 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts +.br + +.br +This will generate all dash segments for SRC1 without looping/closing the period at end of input, then for SRC2. Timestamps of the sources will not be rewritten. +.br + +.br +Note: The default behaviour of MP4Box -dash-ctx option is to set the .I loop to true. +.br + +.br .SS Output redirecting .br When loaded implicitly during link resolution, the dasher will only link its outputs to the target sink @@ -5607,6 +6006,8 @@ .br * SegSync: indicates that fragments/segments must be completely flushed before sending back size events .br +* InitBase64: indicates that the base64-encoded init segment must be set in the init segment size event +.br .br .SH Options (expert): @@ -5664,7 +6065,7 @@ .br * both: inband and outband parameter sets .br -* pps: moves PPS and APS inband, keep VPS,SPS and DCI out of band (used for VVC RPR) +* pps: moves PPS and APS inband, keep VPS, SPS and DCI out of band (used for VVC RPR) .br * force: enables it even if only one representation .br @@ -5692,13 +6093,13 @@ .br * raw: uses raw media format (disables multiplexed representations) .br -* auto: guess format based on extension, default to mp4 if no extension +* auto: guesses format based on extension, defaults to mp4 if no extension is provided .br .br rawsub (bool, default: no): use raw subtitle format instead of encapsulating in container .br -asto (dbl, default: 0): availabilityStartTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations +asto (dbl, default: 0): availabilityTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations .br profile (enum, default: auto): target DASH profile. This will set default option values to ensure conformance to the desired profile. For MPEG-2 TS, only main and live are used, others default to main .br @@ -5724,6 +6125,8 @@ .br profX (str): list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used. If starting with +, the profile string by default is erased and + is skipped .br +query (str): query parameters to append for segment requests (Annex I) +.br cp (enum, default: set): content protection element location .br * set: in adaptation set element @@ -5780,14 +6183,16 @@ .br keep_segs (bool, default: false): do not delete segments no longer in time-shift buffer .br -subdur (dbl, default: 0): maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration -.br ast (str): set start date (as xs:date, e.g. YYYY-MM-DDTHH:MM:SSZ) for live mode. Default is now. !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !! .br state (str): path to file used to store/reload state info when simulating live. This is stored as a valid MPD with GPAC XML extensions .br +keep_ts (bool, default: false): do not shift timestamp when reloading a context +.br loop (bool, default: false): loop sources when dashing with subdur and state. If not set, a new period is created once the sources are over .br +subdur (dbl, default: 0): maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration +.br split (bool, default: true): enable cloning samples for text/metadata/scene description streams, marking further clones as redundant .br hlsc (bool, default: false): insert clock reference in variant playlist in live HLS @@ -5798,9 +6203,9 @@ .br strict_sap (enum, default: off): strict mode for sap .br -* off: ignore SAP types for PID other than video, enforcing _startsWithSAP=1_ +* off: ignore SAP types for PID other than video, enforcing AdaptationSet@startsWithSAP=1 .br -* sig: same as .I off but keep _startsWithSAP_ to the true SAP value +* sig: same as -off but keep AdaptationSet@startsWithSAP to the true SAP value .br * on: warn if any PID uses SAP 3 or 4 and switch to FULL profile .br @@ -5808,7 +6213,7 @@ .br .br -subs_sidx (sint, default: -1): number of subsegments per sidx. negative value disables sidx. Only used to inherit sidx option of destination +subs_sidx (sint, default: -1): number of subsegments per sidx. Negative value disables sidx. Only used to inherit sidx option of destination .br cmpd (bool, default: false): skip line feed and spaces in MPD XML for compactness .br @@ -5816,6 +6221,8 @@ .br dual (bool): indicate to produce both MPD and M3U files .br +segcts (bool): compute the segment number by dividing the first CTS by .I segdur +.br sigfrag (bool): use manifest generation only mode .br sbound (enum, default: out): indicate how the theoretical segment start TSS (= segment_number * duration) should be handled @@ -5842,7 +6249,15 @@ .br utcs (str): URL to use as time server / UTCTiming source. Special value inband enables inband UTC (same as publishTime), special prefix xsd@ uses xsDateTime schemeURI rather than ISO .br -force_flush (bool, default: false): force generating a single segment for each input. This can be useful in batch mode when average source duration is known and used as segment duration but actual duration may sometimes be greater +sflush (enum, default: off): segment flush mode - see filter help: +.br +* off: no specific actions +.br +* single: force generating a single segment for each input +.br +* end: skip loop detection and clamp duration adjustment at end of input, used for state mode +.br + .br last_seg_merge (bool, default: false): force merging last segment if less than half the target duration .br @@ -5874,13 +6289,15 @@ .br hlsx (strl): list of string to append to master HLS header before variants with '#foo','#bar=val' added as #foo \n #bar=val .br +hlsiv (bool, default: true): inject IV in variant HLS playlist +.br ll_preload_hint (bool, default: true): inject preload hint for LL-HLS .br ll_rend_rep (bool, default: true): inject rendition reports for LL-HLS .br ll_part_hb (dbl, default: -1): user-defined part hold-back for LLHLS, negative value means 3 times max part duration in session .br -ckurl (str): set the ClearKey URL common to all encrypted streams (overriden by CKUrl pid property) +ckurl (str): set the ClearKey URL common to all encrypted streams (overridden by CKUrl pid property) .br hls_absu (enum, default: no): use absolute url in HLS generation using first URL in base .br @@ -5920,7 +6337,7 @@ .br * single: change period if PID configuration changes .br -* force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or add insertion not using periodID) +* force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or ad insertion not using periodID) .br * stsd: change period if PID configuration changes unless new configuration was advertised in initial config .br @@ -5950,12 +6367,20 @@ .br tpl_force (bool, default: false): use template string as is without trying to add extension or solve conflicts in names .br +inband_event (bool, default: false): insert a default inband event stream in the DASH manifest +.br +ttml_agg (bool, default: false): force aggregation of TTML samples of a DASH segment into a single sample +.br +evte_agg (bool, default: false): force aggregation of Event Track samples of a DASH segment into a single sample +.br +base64 (bool, default: false): embed init segments in manifests as base64 +.br .br .SH tileagg .LP .br -Description: HEVC tile aggregator +Description: HEVC Tile aggregator .br .br @@ -5975,7 +6400,7 @@ .SH tilesplit .LP .br -Description: HEVC tile bitstream splitter +Description: HEVC Tile splitter .br .br @@ -6013,7 +6438,7 @@ .SH pin .LP .br -Description: pipe input +Description: Pipe input .br .br @@ -6042,6 +6467,8 @@ .br .br +When reading from stdin, the default timeout is 10 seconds. +.br .SH Named pipes .LP .br @@ -6123,7 +6550,7 @@ .SH pout .LP .br -Description: pipe output +Description: Pipe output .br .br @@ -6193,7 +6620,7 @@ .SH gsfmx .LP .br -Description: GSF Multiplexer +Description: GSF multiplexer .br .br @@ -6231,7 +6658,6 @@ .br The .I skp option may also be used to specify which property to drop: .br -Example .br skp="4CC1,Name2 .br @@ -6256,7 +6682,6 @@ .br In order to demultiplex such a file, the gsfdmxfilter will likely need to be explicitly loaded: .br -Example .br gpac -i mux.gsf gsfdmx -o dump/$File$:dynext .br @@ -6270,7 +6695,6 @@ .br To allow a mix of files and streams, use .I mixed: .br -Example .br gpac -i source.mp4 gsfmx:dst=manifest.mpd:mixed -o dump.gsf .br @@ -6458,11 +6882,11 @@ .SH rfav1 .LP .br -Description: AV1/IVF/VP9 reframer +Description: AV1/IVF/VP9/IAMF reframer .br .br -This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and outputs corresponding visual PID and frames. +This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and outputs corresponding visual PID and frames. It also parses IAMF OBU and outputs corresponding temporal units containing audio frames and parameter blocks. .br .br @@ -6496,7 +6920,7 @@ .SH ufobu .LP .br -Description: IVF/OBU/annexB writer +Description: IVF/OBU/annexB rewriter .br .br @@ -6504,6 +6928,8 @@ .br The temporal delimiter OBU is re-inserted in annexB (.av1 and .av1bfiles, with obu_size set) and OBU sequences (.obufiles, without obu_size) .br +Timecode metadata optionally inserted +.br Note: VP8/9 codecs will only use IVF output (equivalent to file extension .ivf or :ext=ivf set on output). .br @@ -6574,16 +7000,18 @@ .SH routein .LP .br -Description: ROUTE input +Description: MABR & ROUTE input .br .br -This filter is a receiver for ROUTE sessions (ATSC 3.0 and generic ROUTE). +This filter is a receiver for file delivery over multicast. It currently supports ATSC 3.0, generic ROUTE and DVB-MABR flute. .br - ATSC 3.0 mode is identified by the URL atsc://. .br - Generic ROUTE mode is identified by the URL route://IP:PORT. .br +- DVB-MABR mode is identified by the URL mabr://IP:PORT pointing to the bootstrap FLUTE channel carrying the multicast gateway configuration. +.br .br The filter can work in cached mode, source mode or standalone mode. @@ -6591,7 +7019,7 @@ .SH Cached mode .LP .br -The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using http://groute/serviceN/ as service root, N being the ROUTE service ID. +The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using http://gmcast/serviceN/ as service root, N being the multicast service ID. .br In cached mode, repeated files are always pushed to cache. .br @@ -6601,19 +7029,19 @@ .br The cached MPD is assigned the following headers: .br -* `x-route`: integer value, indicates the ROUTE service ID. +* `x-mcast`: boolean value, if yes indicates the file comes from a multicast. .br -* `x-route-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast. +* `x-mcast-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast. .br -* `x-route-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling). +* `x-mcast-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling). .br -* `x-route-loop`: boolean value, if yes indicates a loop in the service has been detected (usually pcap replay loop). +* `x-mcast-loop`: boolean value, if yes indicates a loop (e.g. pcap replay) in the service has been detected - only checked if .I cloop is set. .br .br The cached files are assigned the following headers: .br -* `x-route`: boolean value, if yes indicates the file comes from an ROUTE session. +* `x-mcast`: boolean value, if yes indicates the file comes from a multicast. .br .br @@ -6626,8 +7054,6 @@ .br In source mode, the filter outputs files on a single output PID of type file. The files are dispatched once fully received, the output PID carries a sequence of complete files. Repeated files are not sent unless requested. .br -If needed, one PID per TSI can be used rather than a single PID. This avoids mixing files of different mime types on the same PID (e.g. HAS manifest and ISOBMFF). -.br Example .br gpac -i atsc://gcache=false -o $ServiceID$/$File$:dynext @@ -6638,8 +7064,16 @@ .br .br +If needed, one PID per TSI can be used rather than a single PID using .I stsi. This avoids mixing files of different mime types on the same PID (e.g. HAS manifest and ISOBMFF). +.br +In this mode, each packet starting a new file carries the file name as a property. If .I repair is enabled in this mode, progressive dispatch of files will be done. +.br + +.br If .I max_segs is set, file deletion event will be triggered in the filter chain. .br +Note: The .I nbcached option is ignored in this mode. +.br .br .SH Standalone mode @@ -6657,18 +7091,28 @@ .br .br +In this mode, files are always written once completely recieved, regardless of the .I repair option. +.br + +.br If .I max_segs is set, old files will be deleted. .br +Note: The .I nbcached option is ignored in this mode. +.br .br .SH File Repair .LP .br -In case of losses or incomplete segment reception (during tune-in), the files are patched as follows: +In case of losses or incomplete segment reception (during tune-in or HTTP partial repair), the files are patched as follows: .br * MPEG-2 TS: all lost ranges are adjusted to 188-bytes boundaries, and transformed into NULL TS packets. .br -* ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in free boxes, except mdat kept as is if .I repair is set to simple. +* ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in free boxes, except mdat: +.br + - if repair=simple, mdat is kept if incomplete (broken file), +.br + - if repair=strict, mdat is moved to free if incomplete and the preceeding moof is also moved to free. .br .br @@ -6676,6 +7120,10 @@ .br .br +Note: A partially patched segment is no longer considered corrupted and will be dispatched regardless of .I kc. +.br + +.br .SH Interface setup .LP .br @@ -6683,15 +7131,13 @@ .br For ATSC, you will have to do this for the base signaling multicast (224.0.23.60): .br -Example .br route add -net 224.0.23.60/32 -interface vboxnet0 .br .br -Then for each ROUTE service in the multicast: +Then for each multicast service in the multicast: .br -Example .br route add -net 239.255.1.4/32 -interface vboxnet0 .br @@ -6708,7 +7154,17 @@ .br gcache (bool, default: true): indicate the files should populate GPAC HTTP cache .br -tunein (sint, default: -2): service ID to bootstrap on for ATSC 3.0 mode (0 means tune to no service, -1 tune all services -2 means tune on first service found) +tunein (sint, default: -2): service ID to bootstrap on. Special values: +.br +* 0: tune to no service +.br +* -1: tune all services +.br +* -2: tune on first service found +.br +* -3: detect all services and do not join multicast +.br + .br buffer (uint, default: 0x80000): receive buffer size to use in bytes .br @@ -6730,13 +7186,15 @@ .br odir (str): output directory for standalone mode .br -reorder (bool, default: false): ignore order flag in ROUTE/LCT packets, avoiding considering object done when TOI changes +reorder (bool, default: true): consider packets are not always in order - if false, this will evaluate an LCT object as done when TOI changes +.br +cloop (bool, default: false): check for loops based on TOI (used for capture replay) .br -rtimeout (uint, default: 5000): default timeout in ms to wait when gathering out-of-order packets +rtimeout (uint, default: 500000): default timeout in µs to wait when gathering out-of-order packets .br fullseg (bool, default: false): only dispatch full segments in cache mode (always true for other modes) .br -repair (enum, default: simple): repair mode for corrupted files +repair (enum, default: strict): repair mode for corrupted files .br * no: no repair is performed .br @@ -6744,10 +7202,40 @@ .br * strict: incomplete mdat boxes will be lost as well as preceding moof boxes .br -* full: HTTP-based repair, not yet implemented +* full: HTTP-based repair of all lost packets .br .br +repair_urls (strl): repair servers urls - if set, repair is set to full +.br +max_sess (uint, default: 1): max number of concurrent HTTP repair sessions +.br +llmode (bool, default: true): enable low-latency access +.br +dynsel (bool, default: true): dynamically enable and disable multicast groups based on their selection state +.br +range_merge (uint, default: 10000): merge ranges in HTTP repair if distant from less than given amount of bytes +.br +minrecv (uint, default: 20): redownload full file in HTTP repair if received bytes is less than given percentage of file size, 0 means complete file redownload if any error +.br +riso (enum, default: none): advanced options for ISOBMFF HTTP repair +.br +* none: use regular http repair (sequential repair) +.br +* simple: first repair all non-mdat boxes then repair mdat in order +.br +* partial: only repair all non-mdat moxes, leaving holes in mdat +.br +* deps: same as simple and repair only samples depended upon by other samples +.br +* depx: same as deps but do not hide moof of incomplete mdat (tests only) +.br + +.br +ka (bool, default: false): keep service alive if multicast is down +.br +chkiso (bool, default: false): check isobmf structure after repair (debug) +.br .br .SH rtpout @@ -6809,7 +7297,7 @@ .br port (uint, default: 7000): port for first stream in session .br -loop (bool, default: true): loop all streams in session (not always possible depending on source type) +loop (bool, default: false): loop all streams in session (not always possible depending on source type) .br mpeg4 (bool, default: false): send all streams using MPEG-4 generic payload format if possible .br @@ -6839,12 +7327,16 @@ .br mime (cstr): set mime type for direct RTP mode .br +speed (dbl, default: 1.0): set streaming speed. If negative and start is 0, start is set to -1 +.br +start (dbl, default: 0.0): set streaming start offset. A negative value means percent of media duration with -1 equal to duration +.br .br .SH rtspout .LP .br -Description: RTSP Server +Description: RTSP server .br .br @@ -6875,7 +7367,6 @@ .br The filter can work as a simple output filter by specifying the .I dst option: .br -Example .br gpac -i source -o rtsp://myip/sessionname .br @@ -6892,7 +7383,6 @@ .br The filter can work as a regular RTSP server by specifying the .I mounts option to indicate paths of media file to be served: .br -Example .br gpac rtspout:mounts=mydir1,mydir2 .br @@ -7033,7 +7523,7 @@ .br close (bool, default: false): close RTSP connection after each request, except when RTP over RTSP is used .br -loop (bool, default: true): loop all streams in session (not always possible depending on source type) +loop (bool, default: false): loop all streams in session (not always possible depending on source type) .br dynurl (bool, default: false): allow dynamic service assembly .br @@ -7070,7 +7560,7 @@ .SH httpout .LP .br -Description: HTTP Server +Description: HTTP server .br .br @@ -7110,6 +7600,10 @@ .br .br +Warning: files uploaded / created in the write directory are always created in non-atomic modes. +.br + +.br A directory rule file (cf gpac -h creds) can be specified in .I rdirs but NOT in .I wdir. When rules are used: .br - if a directory has a name rule, it will be used in the URL @@ -7130,6 +7624,20 @@ .br .br +To authenticate services handled by bindings, use a non-existing directory and a name describing the authentication. +.br +Example +.br +NonExistingDir +.br +name=service_root +.br + +.br +Requests in the form http://SERVER/service_root/* will be authenticated by this rule. +.br + +.br Listing can be enabled on server using .I dlist. .br When disabled, a GET on a directory will fail. @@ -7142,7 +7650,7 @@ .br .br -Text files are compressed using gzip or deflate if the client accepts these encodings, unless .I no_z is set. +Text files are compressed using gzip or deflate if the client accepts these encodings, unless .I zmax is set to 0. .br .br @@ -7192,7 +7700,6 @@ .br This mode is mostly useful to setup live HTTP streaming of media sessions such as MP3, MPEG-2 TS or other multiplexed representations: .br -Example .br gpac -i MP3_SOURCE -o http://localhost/live.mp3 --hold .br @@ -7360,7 +7867,7 @@ .br dst (cstr): location of destination resource .br -port (uint, default: 0): server port +port (uintl, default: 0): server port .br ifce (str): default network interface to use .br @@ -7442,12 +7949,18 @@ .br zmax (uint, default: 50000): maximum uncompressed size allowed for gzip or deflate compression for text files (only enabled if client indicates it), 0 will disable compression .br +cte (bool, default: true): use chunked transfer-encoding mode when possible +.br +maxs (uint, default: 50M): maximum upload size allowed in bytes +.br +norange (bool, default: false): disable byte range support in GET (reply 200 on partial requests) +.br .br .SH hevcsplit .LP .br -Description: HEVC tile splitter +Description: HEVC Tile extractor .br .br @@ -7655,7 +8168,7 @@ .SH tssplit .LP .br -Description: MPEG Transport Stream splitter +Description: MPEG-2 TS splitter .br .br @@ -7678,12 +8191,40 @@ .br nb_pack (uint, default: 10): pack N packets before sending .br +gendts (bool, default: false): generate timestamps on output packets based on PCR +.br +kpad (bool, default: false): keep padding (null) TS packets +.br +rt (bool, default: false): enable real-time regulation +.br + +.br +.SH tsgendts +.LP +.br +Description: MPEG-2 TS timestamper +.br + +.br +This filter restamps input MPEG-2 transport stream based on PCR. +.br + +.br +.SH Options (expert): +.LP +.br +nb_pack (uint, default: 10): pack N packets before sending +.br +kpad (bool, default: false): keep padding (null) TS packets +.br +rt (bool, default: false): enable real-time regulation +.br .br .SH bsrw .LP .br -Description: Compressed bitstream rewriter +Description: Bitstream metadata rewriter .br .br @@ -7707,6 +8248,10 @@ .br - color primaries, transfer characteristics and matrix coefficients (or remove all info) .br + - (AVC|HEVC) timecode- AV1: +.br + - timecode +.br - ProRes: .br - sample aspect ratio @@ -7731,6 +8276,67 @@ .br The filter will work in passthrough mode for all other codecs and media types. .br +.SH Timecode Manipulation +.LP +.br +One can optionally set the .I tcxs and .I tcxe to define the start and end of timecode manipulation. By default, the filter will process all packets. +.br +Some modes require you to define .I tcsc. This follows the same format as the timecode itself (.I -. The use of negative values is only meaningful in the shift mode. It's also possible to set .I tcsc to first to infer the value from the first timecode when timecode manipulation starts. In this case, unless a timecode is found, the filter will not perform any operation. +.br +.SS Modes +.br +Timecode manipulation has four modes and they all have their own operating nuances. +.br +.P +.B +Remove +.br +Remove all timecodes from the bitstream. +.br +.P +.B +Insert +.br +Insert timecodes based on the CTS. If .I tcsc is set, it will be used as timecode offset. +.br +This mode will overwrite existing timecodes (if any). +.br +.P +.B +Shift +.br +Shift all timecodes by the value defined in .I tcsc. +.br +This mode will only modify timecodes if they exists, no new timecode will be inserted. +.br +.P +.B +Constant +.br +Set all timecodes to the value defined in .I tcsc. +.br +Again, this mode wouldn't insert new timecodes. +.br +.P +.B +UTC +.br +Uses the SenderNTP property, UTC property on the packet, or the current UTC time to set the timecode. +.br +This mode will overwrite existing timecodes (if any). +.br +.SS Examples +.br +Example +.br +gpac -i in.mp4 bsrw:tc=insert dst +.br +gpac -i in.mp4 bsrw:tc=insert:tcsc=TC00:00:10:00 dst +.br +gpac -i in.mp4 bsrw:tc=shift:tcsc=TC00:00:10:00:tcxs=TC00:01:00:00 dst +.br + +.br .br .SH Options (expert): @@ -7744,7 +8350,7 @@ .br .br -cmx (cmxc, default: -1, Enum: GBR|BT709|undef|FCC|BT601|SMPTE170|SMPTE240|YCgCo|BT2020|BT2020cl|YDzDx, updatable): color matrix coeficients according to ISO/IEC 23001-8 / 23091-2 +cmx (cmxc, default: -1, Enum: GBR|BT709|undef|FCC|BT601|SMPTE170|SMPTE240|YCgCo|BT2020|BT2020cl|YDzDx, updatable): color matrix coefficients according to ISO/IEC 23001-8 / 23091-2 .br .br @@ -7770,9 +8376,35 @@ .br gpcflags (sint, default: -1, updatable): general compatibility flags for HEVC .br +tcxs (str, updatable): timecode manipulation start +.br +tcxe (str, updatable): timecode manipulation end +.br +tcdf (bool, default: false, updatable): use NTSC drop-frame counting for timecodes +.br +tcsc (str, updatable): timecode constant for use with shift/constant modes +.br +tc (enum, default: none, updatable): timecode manipulation mode +.br +* none: do not change anything +.br +* remove: remove timecodes +.br +* insert: insert timecodes based on cts or tcsc (if provided) +.br +* shift: shift timecodes based by tcsc +.br +* constant: overwrite timecodes with tcsc +.br +* utc: insert timecodes based on the utc time on the packet or the current time +.br + +.br +seis (uintl, updatable): list of SEI message types (4,137,144,...). When used with rmsei, this serves as a blacklist. If left empty, all SEIs will be removed. Otherwise, it serves as a whitelist +.br rmsei (bool, default: false, updatable): remove SEI messages from bitstream for AVC|H264, HEVC and VVC .br -vidfmt (enum, default: -1, updatable): video format for AVC|H264, HEVC and VVC (component|pal|ntsc|secam|mac|undef) +vidfmt (sint, default: -1, Enum: component|pal|ntsc|secam|mac|undef, updatable): video format for AVC|H264, HEVC and VVC .br .br @@ -7781,7 +8413,7 @@ .SH bssplit .LP .br -Description: Compressed layered bitstream splitter +Description: Layered bitstream splitter .br .br @@ -7867,7 +8499,7 @@ .SH bsagg .LP .br -Description: Compressed layered bitstream aggregator +Description: Layered bitstream aggregator .br .br @@ -7903,7 +8535,7 @@ .SH ufttxt .LP .br -Description: TX3G unframer +Description: TX3G rewriter .br .br @@ -8090,15 +8722,15 @@ .SH ffdmx .LP .br -Description: FFMPEG demultiplexer +Description: FFmpeg demultiplexer .br -Version: Lavf61.3.100 +Version: Lavf62.4.102 .br .br -This filter demultiplexes an input file or open a source protocol using FFMPEG. +This filter demultiplexes an input file or open a source protocol using FFmpeg. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details. +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details. .br To list all supported demultiplexers for your GPAC build, use gpac -h ffdmx:*. .br @@ -8110,6 +8742,23 @@ .br .br +.SH Raw protocol mode +.LP +.br +The .I proto flag will disable FFmpeg demuxer and use GPAC instead. Default format is probed from initial data but can be set using .I ext or .I mime if probing is disabled. +.br +Example +.br +gpac -i srt://127.0.0.1:1234:gpac:proto inspectThis will use the SRT protocol handler but GPAC demultiplexer +.br + +.br + +.br +In this mode, the filter uses the time between the last two received packets to estimates how often it should check for inputs. The maximum and minimum times to wait between two calls is given by the .I mwait option. The maximum time may need to be reduced for very high bitrates sources. +.br + +.br .SH Options (expert): .LP .br @@ -8121,6 +8770,14 @@ .br strbuf_min (uint, default: 1MB): internal buffer size when demuxing from GPAC's input stream .br +proto (bool, default: false): use protocol handler only and bypass FFmpeg demuxer +.br +mwait (v2di, default: 1x30): set min and max wait times in ms to avoid too frequent polling in proto mode +.br +ext (str): indicate file extension of data in raw protocol mode +.br +mime (str): indicate mime type of data in raw protocol mode +.br * (str): any possible options defined for AVFormatContext and sub-classes. See gpac -hx ffdmx and gpac -hx ffdmx:* .br @@ -8128,15 +8785,15 @@ .SH ffdec .LP .br -Description: FFMPEG decoder +Description: FFmpeg decoder .br -Version: Lavc61.5.101 +Version: Lavc62.15.100 .br .br -This filter decodes audio and video streams using FFMPEG. +This filter decodes audio and video streams using FFmpeg. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details. +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details. .br To list all supported decoders for your GPAC build, use gpac -h ffdec:*. .br @@ -8144,6 +8801,8 @@ .br Options can be passed from prompt using --OPT=VAL .br +Decoder flags can be passed directly as :FLAGNAME. +.br The default threading mode is to let libavcodec decide how many threads to use. To enforce single thread, use --threads=1 .br @@ -8151,13 +8810,13 @@ .SH Codec Map .LP .br -The .I ffcmap option allows specifying FFMPEG codecs for codecs not supported by GPAC. +The .I ffcmap option allows specifying FFmpeg codecs for codecs not supported by GPAC. .br Each entry in the list is formatted as GID@name or GID@+name, with: .br * GID: 4CC or 32 bit identifier of codec ID, as indicated by gpac -i source inspect:full .br -* name: FFMPEG codec name +* name: FFmpeg codec name .br * `+': is set and extra data is set and formatted as an ISOBMFF box, removes box header .br @@ -8187,15 +8846,15 @@ .SH ffavin .LP .br -Description: FFMPEG AV Capture +Description: FFmpeg AV capture .br -Version: Lavd61.2.100 +Version: Lavd62.2.100 .br .br -Reads from audio/video capture devices using FFMPEG. +Reads from audio/video capture devices using FFmpeg. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details. +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details. .br To list all supported grabbers for your GPAC build, use gpac -h ffavin:*. .br @@ -8240,7 +8899,6 @@ .br You may need to escape the .I dev option if the format uses ':' as separator, as is the case for AVFoundation: .br -Example .br gpac -i av://::dev=0:1 ... .br @@ -8280,13 +8938,13 @@ .SH ffsws .LP .br -Description: FFMPEG video rescaler +Description: FFmpeg video rescaler .br -Version: SwS8.2.100 +Version: SwS9.3.100 .br .br -This filter rescales raw video data using FFMPEG to the specified size and pixel format. +This filter rescales raw video data using FFmpeg to the specified size and pixel format. .br .SS Output size assignment .br @@ -8294,7 +8952,7 @@ .br .br -If .I osize is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If .I keepar=nosrc, input sample aspect ratio is ignored. +If .I osize is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If .I keepar = nosrc, input sample aspect ratio is ignored. .br .SS Aspect Ratio and Sample Aspect Ratio .br @@ -8310,7 +8968,7 @@ .br .br -When aspect ratio is not kept (.I keepar=off): +When aspect ratio is not kept (.I keepar = off): .br - source is resampled to desired dimensions .br @@ -8318,15 +8976,15 @@ .br .br -When aspect ratio is partially kept (.I keepar=nosrc): +When aspect ratio is partially kept (.I keepar = nosrc): .br - resampling is done on the input data without taking input sample aspect ratio into account .br -- if output sample aspect ratio is not set (.I osar=0/N), source aspect ratio is forwarded to output. +- if output sample aspect ratio is not set (.I osar = 0/N), source aspect ratio is forwarded to output. .br .br -When aspect ratio is fully kept (.I keepar=full), output aspect ratio is force to 1/1 if not set. +When aspect ratio is fully kept (.I keepar = full), output aspect ratio is force to 1/1 if not set. .br .br @@ -8348,7 +9006,7 @@ .br .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details .br .br @@ -8357,7 +9015,7 @@ .br osize (v2di): osize of output video .br -ofmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format for output video. When not set, input format is used +ofmt (pfmt, default: none, Enum: none|yuv420|yvu420|yuv420_10|yuv422|yuv422_10|yuv444|yuv444_10|uyvy|vyuy|yuyv|yvyu|uyvl|vyul|yuyl|yvyl|nv12|nv21|nv1l|nv2l|yuva|yuvd|yuv444a|yuv444p|v308|yuv444ap|v408|v410|v210|grey|algr|gral|rgb8|rgb4|rgb5|rgb6|rgba|argb|bgra|abgr|rgb|bgr|xrgb|rgbx|xbgr|bgrx|rgbd|rgbds|uncv): pixel format for output video. When not set, input format is used .br .br @@ -8400,15 +9058,15 @@ .SH ffenc .LP .br -Description: FFMPEG encoder +Description: FFmpeg encoder .br -Version: Lavc61.5.101 +Version: Lavc62.15.100 .br .br -This filter encodes audio and video streams using FFMPEG. +This filter encodes audio and video streams using FFmpeg. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details. +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details. .br To list all supported encoders for your GPAC build, use gpac -h ffenc:*. .br @@ -8422,6 +9080,14 @@ .br Options can be passed from prompt using --OPT=VAL (global options) or appending ::OPT=VAL to the desired encoder filter. .br +Encoder flags can be passed directly as :FLAGNAME. +.br + +.br +Note +.br +Setting the :low_delay flag will set by default profile=baseline, preset=ultrafast and tune=zerolatency options as well as x264-params for AVC|H264. If one or more of these options are set as filter arguments, no defaulting is used for all these options. +.br .br The filter will look for property TargetRate on input PID to set the desired bitrate per PID. @@ -8471,6 +9137,18 @@ .br rld (bool, default: false, updatable): force reloading of encoder when arguments are updated .br +round (sint, default: 1, updatable): round video up or down +.br +* 0: no rounding +.br +* 1: round up to match codec YUF format requirements +.br +* -1: round down to match codec YUF format requirements +.br +* other: round to lower (negative value) or higher (positive value), for example CTU size +.br + +.br * (str): any possible options defined for AVCodecContext and sub-classes. see gpac -hx ffenc and gpac -hx ffenc:* .br @@ -8478,15 +9156,15 @@ .SH ffmx .LP .br -Description: FFMPEG multiplexer +Description: FFmpeg multiplexer .br -Version: Lavf61.3.100 +Version: Lavf62.4.102 .br .br -Multiplexes files and open output protocols using FFMPEG. +Multiplexes files and open output protocols using FFmpeg. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details. +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details. .br To list all supported multiplexers for your GPAC build, use gpac -h ffmx:*.This will list both supported output formats and protocols. .br @@ -8512,6 +9190,16 @@ .br .br +The .I proto flag will disable FFmpeg muxer and use GPAC instead. Default format is MPEG-2 TS and can be specified using .I ext or .I mime. +.br +Example +.br +gpac -i SRC -o srt://127.0.0.1:1234:gpac:proto:ext=mp4:fragThis will use the SRT protocol handler but GPAC m2ts multiplexer or mp4 muxer if ext=mp4:frag is set +.br + +.br + +.br .SH Options (expert): .LP .br @@ -8535,6 +9223,10 @@ .br keepts (bool, default: true): do not shift input timeline back to 0 .br +proto (bool, default: false): use protocol only: do not try to mux (useful when sending a SRT stream with remuxing) +.br +psleep (uint, default: 1000): in protocol only mode, sleep for given amount of ms before EOS (do not kill connection right away for some protocols) +.br * (str): any possible options defined for AVFormatContext and sub-classes (see gpac -hx ffmx and gpac -hx ffmx:*) .br @@ -8542,15 +9234,15 @@ .SH ffavf .LP .br -Description: FFMPEG AVFilter +Description: FFmpeg AV Filter .br -Version: Lavfi10.2.101 +Version: Lavfi11.8.100 .br .br This filter provides libavfilter raw audio and video tools. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details .br To list all supported avfilters for your GPAC build, use gpac -h ffavf:*. .br @@ -8569,7 +9261,7 @@ .br .br -Unlike other FFMPEG bindings in GPAC, this filter does not parse other libavfilter options, you must specify them directly in the filter chain, and the .I f option will have to be escaped. +Unlike other FFmpeg bindings in GPAC, this filter does not parse other libavfilter options, you must specify them directly in the filter chain, and the .I f option will have to be escaped. .br Example .br @@ -8583,7 +9275,6 @@ .br For complex filter graphs, it is possible to store options in a file (e.g. opts.txt): .br -Example .br :f=anullsrc=channel_layout=5.1:sample_rate=48000 .br @@ -8591,7 +9282,6 @@ .br And load arguments from file: .br -Example .br ffavf:opts.txt aout .br @@ -8685,15 +9375,15 @@ .SH ffbsf .LP .br -Description: FFMPEG BitStream filter +Description: FFmpeg bitstream filter .br -Version: Lavu59.13.100 +Version: Lavu60.12.100 .br .br This filter provides bitstream filters (BSF) for compressed audio and video formats. .br -See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details +See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details .br To list all supported bitstream filters for your GPAC build, use gpac -h ffbsf:*. .br @@ -8737,7 +9427,7 @@ .br .br -For more information on how to use JS filters, please check https://wiki.gpac.io/jsfilter +For more information on how to use JS filters, please check https://wiki.gpac.io/Howtos/jsf/jsfilter .br .br @@ -8753,13 +9443,13 @@ .SH routeout .LP .br -Description: ROUTE output +Description: MABR & ROUTE output .br .br -The ROUTE output filter is used to distribute a live file-based session using ROUTE. +The ROUTE output filter is used to distribute a live file-based session using ROUTE or DVB-MABR. .br -The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE signaling. +The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE or DVB-MABR signaling. .br .br @@ -8769,6 +9459,8 @@ .br * `route://IP:port`: session is a ROUTE session running on given multicast IP and port .br +* `mabr://IP:port`: session is a DVB-MABR session using FLUTE running on given multicast IP and port +.br .br The filter only accepts input PIDs of type FILE. @@ -8783,15 +9475,15 @@ .br For raw file PIDs, the filter will look for the following properties: .br -* `ROUTEName`: set resource name. If not found, uses basename of URL +* `MCASTName`: set resource name. If not found, uses basename of URL .br -* `ROUTECarousel`: set repeat period. If not found, uses .I carousel. If 0, the file is only sent once +* `MCASTCarousel`: set repeat period. If not found, uses .I carousel. If 0, the file is only sent once .br -* `ROUTEUpload`: set resource upload time. If not found, uses .I carousel. If 0, the file will be sent as fast as possible. +* `MCASTUpload`: set resource upload time. If not found, uses .I carousel. If 0, the file will be sent as fast as possible. .br .br -When DASHing for ROUTE or single service ATSC, a file extension, either in .I dst or in .I ext, may be used to identify the HAS session type (DASH or HLS). +When DASHing for ROUTE, DVB-MABR or single service ATSC, a file extension, either in .I dst or in .I ext, may be used to identify the HAS session type (DASH or HLS). .br Example .br @@ -8811,7 +9503,6 @@ .br If multiple services with different formats are needed, you will need to explicit your filters: .br -Example .br gpac -i DASH_URL:#ServiceID=1 dashin:forward=file:FID=1 -i HLS_URL:#ServiceID=2 dashin:forward=file:FID=2 -o atsc://:SID=1,2 .br @@ -8825,11 +9516,15 @@ .br .br -By default, all streams in a service are assigned to a single route session, and differentiated by ROUTE TSI (see .I splitlct). +The filter will look for MCASTIP and MCASTPort properties on the incoming PID to setup multicast of each service. If not found, the default .I ip and port will be used, with port incremented by one for each new multicast stream. +.br + +.br +By default, all streams in a service are assigned to a single multicast session, and differentiated by TSI (see .I splitlct). .br TSI are assigned as follows: .br -- signaling TSI is always 0 +- signaling TSI is always 0 for ROUTE, 1 for DVB+Flute .br - raw files are assigned TSI 1 and increasing number of TOI .br @@ -8837,6 +9532,18 @@ .br .br +When .I splitlct is set to mcast, the IP multicast address is computed as follows: +.br + - if MCASTIP is set on the PID and is different from the service multicast IP, it is used +.br + - otherwise the service multicast IP plus one is used +.br +The multicast port used is set as follows: +.br +- if MCASTPort is set on the PID, it is used +.br +- otherwise the same port as the service one is used. +.br Init segments and HLS child playlists are sent before each new segment, independently of .I carousel. .br .SH ATSC 3.0 mode @@ -8844,9 +9551,7 @@ .br In this mode, the filter allows multiple service multiplexing, identified through the ServiceID property. .br -By default, a single multicast IP is used for route sessions, each service will be assigned a different port. -.br -The filter will look for ROUTEIP and ROUTEPort properties on the incoming PID. If not found, the default .I ip and .I port will be used. +By default (see above), a single multicast IP is used for route sessions, each service will be assigned a different port. .br .br @@ -8879,10 +9584,47 @@ .br .br +.SH DVB-MABR mode +.LP +.br +In this mode, the filter allows multiple service multiplexing, identified through the ServiceID and ServiceName properties. +.br +Note: .I ip and .I first_port are used to send the multicast gateway configuration. .I first_port is used only if no port is specified in .I dst. +.br + +.br +The session will carry DVB-MABR gateway configuration, maifests and init segments on TSI=1. The .I use_inband option can be used to send manifests and init segments in media multicast sessions. +.br + +.br +The FLUTE session always uses a symbol length of .I mtu minus 44 bytes. +.br + +.br +The MABRBaseURLs property can be set on sources to declare a list of alternate repair servers to be injected. +.br +Each base URL can be prefixed with N;, where N gives the relative weight of the server, a negative value skipping the server. +.br +The special value src is used to indicate the source of the session. +.br +Example +.br +gpac -i HTTP_MPD_URL:gpac::#MABRBaseURLs=-1;src,SOME_ALT_URL dashin:forward=file -o mabr://225.0.0.1:1234/ +.br + +.br +This will forward the source DASH session to multicast and: +.br +- hide the source server as a repair URL +.br +- add SOME_ALT_URL as a repair URL +.br + +.br .SH Low latency mode .LP .br -When using low-latency mode, the input media segments are not re-assembled in a single packet but are instead sent as they are received. +When using low-latency mode .I llmode, the input media segments are not re-assembled in a single packet but are instead sent as they are received. .br In order for the real-time scheduling of data chunks to work, each fragment of the segment should have a CTS and timestamp describing its timing. .br @@ -8894,6 +9636,10 @@ .br .br +In this mode, init segments and manifests are sent at the frequency given by property MCASTCarousel of the source PID if set or by .I carousel option. +.br +Indicating MCASTCarousel=0 will disable mid-segment repeating of manifests and init segments. +.br .SH Examples .LP .br @@ -8907,7 +9653,6 @@ .br Multiplexing an existing DASH session in route: .br -Example .br gpac -i source.mpd dashin:forward=file -o route://225.1.1.0:6000/ .br @@ -8915,7 +9660,6 @@ .br Multiplexing an existing DASH session in atsc: .br -Example .br gpac -i source.mpd dashin:forward=file -o atsc:// .br @@ -8923,7 +9667,6 @@ .br Dashing and multiplexing in route: .br -Example .br gpac -i source.mp4 dasher:profile=live -o route://225.1.1.0:6000/manifest.mpd .br @@ -8931,7 +9674,6 @@ .br Dashing and multiplexing in route Low Latency: .br -Example .br gpac -i source.mp4 dasher -o route://225.1.1.0:6000/manifest.mpd:profile=live:cdur=0.2:llmode .br @@ -8941,9 +9683,8 @@ .br Sending a single file in ROUTE using half a second upload time, 2 seconds carousel: .br -Example .br -gpac -i URL:#ROUTEUpload=0.5:#ROUTECarousel=2 -o route://225.1.1.0:6000/ +gpac -i URL:#MCASTUpload=0.5:#MCASTCarousel=2 -o route://225.1.1.0:6000/ .br .br @@ -8951,7 +9692,6 @@ .br Common mistakes: .br -Example .br gpac -i source.mpd -o route://225.1.1.0:6000/ .br @@ -8979,6 +9719,20 @@ .br .br +.SH Error simulation +.LP +.br +It is possible to simulate errors with .I errsim. In this mode the LCT network sender implements a 2-state Markov chain: +.br +.br +gpac -i source.mpd dasher -o route://225.1.1.0:6000/:errsim=1.0x98.0 +.br + +.br +This will set a 1.0 percent chance to transition to error (not sending data over the network) and 98.0 percent chance to transition from error back to OK. +.br + +.br .SH Options (expert): .LP .br @@ -9010,6 +9764,8 @@ .br * all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling .br +* mcast: all streams are in dedicated multicast groups +.br .br korean (bool, default: false): use Korean version of ATSC 3.0 spec instead of US @@ -9024,6 +9780,26 @@ .br nozip (bool, default: false): do not zip signaling package (STSID+manifest) .br +flute (bool, default: true): use flute for DVB-MABR object delivery +.br +csum (enum, default: meta): send MD5 checksum for DVB flute +.br +* no: do not send checksum +.br +* meta: only send checksum for configuration files, manifests and init segments +.br +* all: send checksum for everything +.br + +.br +recv_obj_timeout (uint, default: 50): set timeout period in ms before client resorts to unicast repair +.br +errsim (v2d, default: 0.0x100.0): simulate errors using a 2-state Markov chain. Value are percentages +.br +use_inband (bool, default: false): send manifest and init segments in media transport sessions for MABR +.br +ssm (bool, default: false): indicate source-specific multicast for DVB-MABR, requires ifce to be set +.br .br .SH rftruehd @@ -9109,7 +9885,7 @@ .SH restamp .LP .br -Description: Packet timestamp rewriter +Description: Timestamp rewriter .br .br @@ -9131,11 +9907,11 @@ .br - otherwise (video PID), constant frame rate is assumed and: .br - - if .I rawv=no, video frame rate is changed to the specified rate (speed-up or slow-down). + - if .I rawv = no, video frame rate is changed to the specified rate (speed-up or slow-down). .br - - if .I rawv=force, input video stream is decoded and video frames are dropped/copied to match the new rate. + - if .I rawv = force, input video stream is decoded and video frames are dropped/copied to match the new rate. .br - - if .I rawv=dyn, input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate. + - if .I rawv = dyn, input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate. .br .br @@ -9209,7 +9985,7 @@ .SH unframer .LP .br -Description: Stream unframer +Description: Stream rewriter .br .br @@ -9238,7 +10014,7 @@ .SH writeuf .LP .br -Description: Stream to unframed format +Description: Framed to Unframed converter .br .br @@ -9250,6 +10026,21 @@ .br .br +.SH ttmlmerge +.LP +.br +Description: TTML sample merger +.br + +.br +Merge input samples into a single TTML sample. Merging restarts at the start of DASH segments. +.br + +.br +No options +.br + +.br .SH uncvdec .LP .br @@ -9381,7 +10172,10 @@ .br If representation IDs are not assigned during index creation, they default to the 1-based index of the source. You can check them using: .br -EX: gpac -i src.ghi inspect:full +.br +gpac -i src.ghi inspect:full +.br + .br .br @@ -9437,7 +10231,7 @@ .br - If non-fragmented MP4 are used, it is recommended to use single-track files to decrease the movie box size and speedup parsing. .br -- MPEG-2 TS sources will be slower since they require PES reframing and AU reformating, resulting in more IOs than with mp4. +- MPEG-2 TS sources will be slower since they require PES reframing and AU reformatting, resulting in more IOs than with mp4. .br - other seekable sources will likely be slower (seeking, reframing) and are not recommended. .br @@ -9487,7 +10281,7 @@ .br .br -If .I osize is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If .I keepar=nosrc, input sample aspect ratio is ignored. +If .I osize is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If .I keepar = nosrc, input sample aspect ratio is ignored. .br .SS Aspect Ratio and Sample Aspect Ratio .br @@ -9503,7 +10297,7 @@ .br .br -When aspect ratio is not kept (.I keepar=off): +When aspect ratio is not kept (.I keepar = off): .br - source is resampled to desired dimensions .br @@ -9511,15 +10305,15 @@ .br .br -When aspect ratio is partially kept (.I keepar=nosrc): +When aspect ratio is partially kept (.I keepar = nosrc): .br - resampling is done on the input data without taking input sample aspect ratio into account .br -- if output sample aspect ratio is not set (.I osar=0/N), source aspect ratio is forwarded to output. +- if output sample aspect ratio is not set (.I osar = 0/N), source aspect ratio is forwarded to output. .br .br -When aspect ratio is fully kept (.I keepar=full), output aspect ratio is force to 1/1 if not set. +When aspect ratio is fully kept (.I keepar = full), output aspect ratio is force to 1/1 if not set. .br .br @@ -9577,14 +10371,178 @@ .br .br +.SH Options (expert): +.LP +.br +field (uint, default: 1): field to decode +.br +agg (enum, default: none): output aggregation mode +.br +* none: forward data as decoded (default) +.br +* word: aggregate words (separated by a space) +.br + +.br + +.br +.SH ccenc +.LP +.br +Description: Closed-Caption encoder +.br + +.br +This filter encodes Closed Captions to the video stream. +.br +Supported video media types are MPEG2, AVC, HEVC, VVC and AV1 streams. +.br + +.br +Only a subset of CEA 608/708 is supported. +.br + +.br +.SH Options (expert): +.LP +.br +vb_time (frac, default: 1/1): time to hold video packets if no caption is available +.br + +.br +.SH scte35dec +.LP +.br +Description: SCTE35 decoder +.br + +.br +This filter writes the SCTE-35 markers attached as properties to audio and video +.br +packets or inside a dedicated stream, as 23001-18 'emib' boxes. It also creates +.br +empty 'emeb' box in between following segmentation as hinted by the graph. +.br + +.br +.SH Options (expert): +.LP +.br +mode (enum, default: 23001-18): mode to operate in +.br +* 23001-18: extract SCTE-35 markers as emib/emeb boxes for Event Tracks +.br +* passthrough: pass-through mode adding cue start property on splice points +.br + +.br +sampdur (frac, default: 0/1): segmentation duration in seconds. Default value 0 only flushes when content changes +.br + +.br +.SH seiload +.LP +.br +Description: SEI message loader +.br + +.br +This filter loads known inband metadata as packet properties +.br + +.br No options .br .br +.SH wcdec +.LP +.br +Description: WebCodec decoder +.br + +.br +This filter decodes video streams using WebCodec decoder of the browser +.br + +.br +.SH Options (expert): +.LP +.br +queued (uint, default: 10): maximum number of packets to queue in webcodec instance +.br + +.br +.SH wcenc +.LP +.br +Description: WebCodec encoder +.br + +.br +This filter encodes video streams using WebCodec encoder of the browser +.br + +.br +.SH Options (expert): +.LP +.br +c (str): codec identifier. Can be any supported GPAC codec name or ffmpeg codec name - updated to ffmpeg codec name after initialization +.br +fintra (frac, default: -1/1): force intra / IDR frames at the given period in sec, e.g. fintra=2 will force an intra every 2 seconds and fintra=1001/1000 will force an intra every 30 frames on 30000/1001=29.97 fps video; ignored for audio +.br +all_intra (bool, default: false, updatable): only produce intra frames +.br +b (uint, default: 0): bitrate in bits per seconds, defaults to 2M for video and 124K for audio +.br +queued (uint, default: 10): maximum number of packets to queue in webcodec instance +.br + +.br +.SH webgrab +.LP +.br +Description: Web-based AV capture +.br + +.br +This filter grabs audio and video streams MediaStreamTrackProcessor of the browser +.br + +.br +Supported URL schemes: +.br +- video:// grabs from camera +.br +- audio:// grabs from microphone +.br +- av:// grabs both audio from microphone and video from camera +.br +- video://ELTID grabs from DOM element with ID ELTID (the element must be a valid element accepted by VideoFrame constructor) +.br + +.br +.SH Options (expert): +.LP +.br +src (str): source url +.br +vsize (v2di, default: 0x0): desired webcam resolution +.br +back (bool, default: false): use back camera +.br +ntp (bool, default: false): mark packets with NTP +.br +alpha (bool, default: false): keep alpha when brabbing canvas +.br +fps (frac, default: 0/1): framerate to use when grabbing images - 0 FPS means single image +.br + +.br .SH dtout .LP .br -Description: DekTec SDIOut +Description: DekTec SDI output .br .br @@ -9650,7 +10608,7 @@ .SH uncvg .LP .br -Description: Uncompressed Video File Format Generator Utility +Description: Uncompressed Video Generator .br Version: 1.0 .br @@ -9803,7 +10761,7 @@ .SH thumbs .LP .br -Description: Thumbnail collection generator +Description: Thumbnail generator .br Version: 1.0 .br @@ -9995,10 +10953,622 @@ .br .br +.SH txtgen +.LP +.br +Description: Text Generator +.br +Version: 1.0 +.br +Author: GPAC Team +.br + +.br +This filter generates text streams based on the provided .I src file. By default, the filter uses a lorem ipsum text file +.br +The .I type parameter sets the text generation mode. If set to 'txt', the filter will generate text based on the source file +.br +If set to 'utc', the filter will generate text based on the current UTC time. If set to 'ntp', the filter will generate text based on the current NTP time +.br +When the .I unit is set to 'w', the filter will generate text based on words. When set to 'l', the filter will generate text based on lines +.br +The .I fdur parameter sets the frame duration of the text stream. Total duration of the text stream is set by the .I dur parameter. If set to 0/0, the text stream will be infinite +.br +The .I rollup parameter enables roll-up mode up to the specified number of lines. In roll-up mode, the filter will accumulate text until the specified number of lines is reached. +.br +When the number of lines is reached, the filter will remove the first line and continue accumulating text +.br +You would use .I rollup in combination with .I unit set to 'l' to create a roll-up subtitle effect. Or set .I unit to 'w' to create a roll-up text effect. +.br +The .I lmax parameter sets the maximum number of characters in a line. If the line in the source file is longer than this, the excess text will be wrapped. 0 means no limit +.br +When .I rt is set to true, the filter will generate text in real-time. If set to false, the filter will generate text as fast as possible +.br + +.br +.SH Options (expert): +.LP +.br +src (str, default: /usr/local/share/gpac/scripts/jsf/txtgen/lipsum.txt): source of text. If not set, the filter will use lorem ipsum text +.br +type (enum, default: txt): type of text to generate +.br +* txt: plain text (uses src option) +.br +* utc: UTC time +.br +* ntp: NTP time +.br + +.br +unit (enum, default: l): minimum unit of text from the source +.br +* w: word +.br +* l: line +.br + +.br +fdur (frac, default: 1/1): duration of each frame +.br +lmax (uint, default: 32): maximum number of characters in a line. If the line in the source file is longer than this, the excess text will be wrapped. 0 means no limit +.br +dur (frac, default: 0/0): duration of the text stream +.br +rollup (uint, default: 0): enable roll-up mode up to the specified number of lines +.br +lock (bool, default: false): lock timing to text generation +.br +rt (bool, default: true): real-time mode +.br + +.br +.SH mediaserver +.LP +.br +Description: Media Server +.br +Version: 1.0 +.br +Author: GPAC team - (c) Telecom Paris 2024 - license LGPL v2 +.br + +.br +This filter is an HTTP server and proxy for GET and HEAD requests supporting Multicast ABR sources. +.br + +.br +This filter does not produce PIDs, it attaches to HTTP output filter. +.br +If no HTTP output filter is specified in the session, the filter will create one. +.br +The file .gpac_auth, if present in current working directory, will be used for authentication unless --rdirs is set. +.br + +.br +If more options need to be specified for the HTTP output filter, they can be passed as local options or using global options: +.br +.br +gpac mediaserver:cors=on --user_agent=MyUser +.br + +.br + +.br +Although not recommended, a server may be specified explicitly: +.br +.br +gpac mediaserver httpout:OPTS +.br + +.br +In this case, the first httpout filter created will be used. +.br + +.br +Default request handling of httpout filter through read / write directories is disabled. +.br + +.br +.SH Services Configuration +.LP +.br +The service configuration is set using .I scfg. It shall be a JSON file containing a single array of services. +.br +Each service is a JSON object with one or more of the following properties: +.br +* id: (string, default null) Service identifier used for logs +.br +* active: (boolean, default true) service is ignored if false +.br +* http: (string, default null) URL of remote service to proxy (either resource name or server path) +.br +* gcache: (boolean, default false) use gpac local disk cache when fetching media from HTTP for this service +.br +* local: (string, default null) local mount point of this service +.br +* keepalive: (number, default 4) remove the service if no request received for the indicated delay in seconds (0 force service to stay in memory forever) +.br +* mabr: (string, default null) address of multicast ABR source for this service +.br +* timeshift: (number, default 10) time in seconds a cached file remains in memory +.br +* unload: (number, default 4) multicast unload policy +.br +* activate: (number, default 1) multicast activation policy +.br +* mcache: (boolean, default false) cache manifest files +.br +* repair: (string, default false) enable unicast repair in MABR stack +.br + * false: disable repair +.br + * true: enable repair using source URL or repair servers indicated in MABR +.br + * auto: enable repair only from repair servers indicated in MABR +.br +* corrupted: (boolean, default false) forward corrupted files if parsable (valid container syntax, broken media) +.br +* check_ip: (boolean, , default false) monitor IP address and port rather than connection when tracking active clients +.br +* noproxy: (boolean) disable proxy for service when local mount point is set. Default is true if both local and http are set, false otherwise +.br +* sources: (array, default null) list of sources objects for file-only services. Each source has the following property: +.br +* name: name as used in resource path, +.br +* url: local or remote URL to use for this resource. +.br +* js: (string, default null) built-in or custom request resolver +.br + +.br +Any JSON object with the property comment set will be ignored. +.br + +.br +Not all properties are used for each type of service and other properties can be defined by custom request resolvers. +.br + +.br +.SH Proxy versus Server +.LP +.br +All services using http option can be exposed by the server without exposing the origin URL, rather than being proxied. To enable this, the local service configuration option must be set to: +.br +- the exposed server path, in which case manifest names are not rewritten +.br +- or the exposed manifest path, in which case manifest names are rewritten, but only one manifest can be exposed (does not work with dual MPD and M3U8 services) +.br + +.br +Example +.br +{ "http": "https://test.com/live/dash/live.mpd", "local": "/service1/"} +.br + +.br +The server will translate any request /service1/foo/bar.ext into https://test.com/live/dash/foo/bar.ext. +.br + +.br +Example +.br +{ "http": "https://test.com/live/dash/live.mpd", "local": "/service1/manifest.mpd"} +.br + +.br +The server will translate: +.br +- request /service1/manifest.mpd into https://test.com/live/dash/live.mpd +.br +- any request /service1/foo/bar.ext into https://test.com/live/dash/foo/bar.ext +.br + +.br +Note: The URL must point to a self-contained subdirectory of the remote site. Any URLs outside this directory will either fail or be resolved as absolute path on the remote site. +.br + +.br +When local is not set, these services are always acting as proxies for the http URL. +.br + +.br +When noproxy is explicitly set to false for the services with both http and local, the remote URL will be available as a proxy service as well. +.br + +.br +.SH HTTP Proxy and Relay +.LP +.br +The server can act as a proxy for HTTP requests, either for any requests or by domain or resource name. +.br + +.br +Service configuration parameters used : http (mandatory), gcache, local. +.br + +.br +Configuration for activating proxy for a specific network path: +.br +.br +{ "http": "https://test.com/video/"} +.br + +.br + +.br +Configuration for activating proxy for any network path: +.br +.br +{ "http": "*"} +.br + +.br + +.br +Configuration for a relay on a given path: +.br +.br +{ "http": "https://test.com/some/path/to/video/", "local": "/myvids/"} +.br + +.br + +.br +This will resolve any request http://localhost/myvids/* to https://test.com/some/path/to/video/* +.br + +.br +Note: The requests are never cached in memory in this mode, but can be cached on disk if gcache is set. +.br + +.br +.SH HTTP Streaming Cache +.LP +.br +The server can act as a cache for live HTTP streaming sessions. The live edge can be cached in memory for a given duration. +.br + +.br +Service configuration parameters used : http ( mandatory), timeshift, mcache, gcache, keepalive and local. +.br + +.br +Configuration for proxying while caching a live HTTP streaming service: +.br +.br +{ "http": "https://test.com/dash/live.mpd", "timeshift": 30 } +.br + +.br + +.br +Configuration for relay caching a live HTTP streaming service: +.br +.br +{ "http": "https://test.com/dash/live.mpd", "timeshift": 30, "local": "/myservice/test.mpd"} +.br + +.br + +.br +The local service configuration option can be set to: +.br +- the exposed server path, in which case manifest names are not rewritten +.br +- or the exposed manifest path, in which case manifest names are rewritten, but only one manifest can be exposed (does not work with dual MPD and M3U8 services) +.br + +.br +.SH Multicast ABR Gateway +.LP +.br +The server can be configured to use a multicast ABR source for an HTTP streaming service, without any HTTP source. +.br + +.br +Service configuration parameters used : mabr (mandatory), local (mandatory), corrupted, timeshift and keepalive. +.br + +.br +The multicast source can be DVB-MABR (e.g. mabr://235.0.0.1:1234/), ATSC3.0 (e.g. atsc://) or ROUTE (e.g. route://235.0.0.1:1234/). +.br +- If the multicast is replayed from a file, netcap ID shall be set in this multicast URL (e.g. :NCID=N). +.br +- If a specific IP interface is used, it can also be set in multicast URL (e.g. :ifce=IP). +.br + +.br +For example, with local set to /service/live.mpd with mabr set, the server will expose the multicast service as http://localhost/service/live.mpd. +.br +The manifest name can be omitted, in which case the exact manifest name used in the broadcast shall be used (and known to the client). +.br + +.br +Configuration for exposing a MABR session: +.br +.br +{ "mabr": "mabr://234.0.0.1:1234", "local": "/service1", "timeshift": 30 } +.br + +.br + +.br +.SH Multicast ABR Gateway with HTTP cache +.LP +.br +The server can be configured to use a multicast source as an alternate data source of a given HTTP streaming service. +.br + +.br +Service configuration parameters used : http (mandatory), mabr (mandatory), local, corrupted, timeshift, repair, gcache, mcache, unload, activate, keepalive and js. +.br + +.br +The multicast service can be dynamically loaded at run-time using the unload service configuration option: +.br +- if 0, the multicast is started when loading the server and never ended, +.br +- otherwise, the multicast is started dynamically and ended unload seconds after last deactivation. +.br + +.br +The qualities in the multicast service can be dynamically activated or deactivated using the activate service configuration option: +.br +- if 0, multicast streams are never deactivated, +.br +- otherwise, a multicast representation is activated only if at least activate clients are consuming it, and deactivated otherwise. +.br + +.br +The multicast service can use repair options of the MABR stack using repair service configuration option: +.br +- if false, the file will not be sent until completely received (this increases latency), +.br +- otherwise, file data will be pushed as soon as available in order (after reception or repair). +.br + +.br +If the corrupted option is set together with repair, HTTP-based repair is disabled and corrupted files are patched using the repair=strict mode of the routein filter. +.br +If files are completely lost, they will be fetched from httpsource. +.br +Warning: This may likely result in decoding/buffering pipeline errors and could fail with some players expecting no timeline holes (such as browsers). GPAC supports this. +.br + +.br +The number of active clients on a given quality is computed using the client connection state: any disconnect/reconnect from a client for the same quality will trigger a deactivate+activate sequence. +.br +If check_ip is set to true, the remote IP address+port are used instead of the connection. This however assumes that each client has a unique IP/port which may not always be true (NATs). +.br + +.br +If timeshift is 0 for the service, multicast segments will be trashed as soon as not in use (potentially before the client request). +.br + +.br +Note: Manifest files coming from multicast are currently never cached. +.br + +.br +Configuration for caching a live HTTP streaming service with MABR backup: +.br +.br +{ "http": "https://test.com/dash/live.mpd", "mabr": "mabr://234.0.0.1:1234", "timeshift": 30} +.br + +.br + +.br +For such services, the custom HTTP header X-From-MABR is defined: +.br +- for client request, a value of no will disable MABR cache for this request; if absent or value is yes, MABR cache will be used if available +.br +- for client response, a value of yes indicates the content comes from the MABR cache; if absent or value is no or off-edge, the content comes from HTTP (off-edge indicates a request outside of the timeshift buffer) +.br + +.br +The dedicated root endpoint /stats returns, the response content type's count (yes, no, off-edge) served by the gateway. +.br + +.br +The js option can be set to a JS module exporting the following functions: +.br +* init : (mandatory) The function is called once at the start of the server. Parameters: +.br + * scfg: the service configuration object +.br + * return value: must be true if configuration and initialization are successful, false otherwise. +.br + +.br +* service_activation : (optional) The function is called when the service is activated or deactivated. Parameters: +.br + * do_activate (boolean): if true, service is being loaded otherwise it is being unloaded +.br + * return value: none +.br + +.br +* quality_activation : (optional) The function is called when the given quality is to be activated or deactivated. If not present, (de)activation always happens. Parameters (in order): +.br + * do_activate (boolean): if true, quality is being activated otherwise it is being deactivated +.br + * service_id (integer): ID of the service as announced in the multicast +.br + * period_id (string): ID of the DASH Period, ignored (empty) for HLS +.br + * adaptationSet_ID (integer): ID of the DASH AdaptationSet, ignored (-1) for HLS +.br + * representation_ID (string): ID of the DASH representation or name of the HLS variant playlist +.br + * return value: shall be true if activation/deactivation shall proceed and false if activation/deactivation shall be canceled. +.br + +.br +* get_mcast_address : (optional) The function is called when the service is activated. Parameters: +.br + * service_url (string): URL of service for which the multicast adress is queried +.br + * return value: shall be the multicast address to use for the service or null if no multicast is used (active multicast wil then be deactivated). +.br + +.br +.SH File Services +.LP +.br +A file system directory can be exposed as a service. +.br + +.br +Service configuration parameters used : local (mandatory), sources (mandatory), gcache and keepalive. +.br + +.br +The local service configuration option must be set to the desired service path, and the sources service configuration option must one or more valid sources. +.br +Each source is either a file, a directory or a remote URL. +.br + +.br +Configuration for exposing a directory: +.br +.br +{ "local": "/dserv/", "sources": { "name": "foo/", "url": "my_dir/" } } +.br + +.br +This service will expose the content of directory my_dir/* as http://localhost/dserv/foo/*. +.br + +.br +In this mode, file serving is handled directly by httpout filter and no memory caching is used. +.br +If the source is a remote HTTP one, the gcache option will indicate if GPAC local cache shall be used. +.br + +.br +.SH Module development +.LP +.br + +.br +A JS module can be specified using the js option in the service configuration. The module export functions are: +.br +.SS init (mandatory) +.br +The function is called once at the start of the server +.br +Parameter: the service configuration object +.br +return value must be true if configuration and initialization are successful, false otherwise +.br + +.br +.SS resolve (mandatory) +.br +Parameter: an HTTP request object from GPAC +.br + +.br +The function returns an array of two values result, delay: +.br +* result: null if error, a resolved string indicating either a local file or the reply body, or an object +.br +* delay: if true, the reply is being delayed by the module for later processing +.br + +.br +When an object is returned, the request is handled by the JS module in charge of sending the reply and reading the data. The object shall have the following properties: +.br +* read: same semantics as the request read method +.br +* on_close: optional function called when the request is closed +.br + +.br +.SH Built-in modules +.LP +.br + +.br +.SS Source Remultiplexer +.br +Module is loaded when using js=remux +.br + +.br +This module remultiplexes the source files in a desired format without transcoding. +.br + +.br +Service configuration parameters used : local (mandatory), sources (mandatory). +.br + +.br +Service configuration additional parameters +.br +* fmt: default format to use (default is mp4). Supported formats are: +.br + * `src`: no remultiplexing +.br + * `mp4`: fragmented MP4 +.br + * `ts`: MPEG-2 TS +.br + * `gsf`: GPAC streaming format +.br + * `dash`: MPEG-DASH format, single quality +.br + * `hls`: HLS format, single quality +.br + +.br +Sources are described using the sources array in the service configuration. +.br + +.br +CGI parameters for request are: +.br +* fmt: (string, same as service configuration fmt) multiplexing format. If set to src, all other CGI parameters are ignored. +.br +* start: (number, default 0) start time in second of re-multiplexed content. +.br +* speed: (number, default 1) speed (>=0), keep video stream only and remove non SAP frames. +.br +* media: (string, default av) media filtering type +.br + * 'av': keep both audio and video +.br + * 'a': keep only audio +.br + * 'v': keep only video +.br + +.br +Configuration for serving a directory with remultiplexing to mp4: +.br +.br +{"local": "/service1/", "js": "remux", "sources": {"name": "vids", "url": "/path/to/vids/"}, "fmt": "mp4"} +.br + +.br + +.br +.SH Options (expert): +.LP +.br +scfg (str): service configuration file +.br +quit (bool, default: false): exit server once last service has been deactivated +.br + +.br .SH avmix .LP .br -Description: Audio Video Mixer +Description: Audio Video mixer .br Author: GPAC team .br @@ -10376,6 +11946,34 @@ .B Notes .br +The special URL scheme ipid:// can be used to locate an input pid by link directives. +.br +Example +.br +in=ipid://#foo=bar +.br + +.br +This will use pids having property foo with value bar, regardless of source filter ID. +.br + +.br +Example +.br +in=ipid://TEST#foo=bar +.br + +.br +This will use pids having property foo with value bar coming from filter with ID TEST. +.br + +.br +When using the ipid:// scheme, filter chains cannot be specified (in accepts a single argument) and port is ignored. +.br +The syntax for link directive is the same as in gpac. However, if a listed property is not found on the input pid, the matching will fail. +.br + +.br When launching a child process, the input filter is created first and the child process launched afterwards. .br @@ -10578,7 +12176,11 @@ .br .br -EX: "mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" +Example +.br +"mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" +.br + .br .br @@ -10880,7 +12482,11 @@ .br .br -EX: { "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } +Example +.br +{ "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } +.br + .br This will change scene s1 rotation every 2 seconds .br @@ -10938,7 +12544,7 @@ .br Example .br -{'watch': 's1@rotation', 'target': 's2@rotation'} +{"watch": "s1@rotation", "target": "s2@rotation"} .br .br @@ -10948,7 +12554,7 @@ .br Example .br -{'watch': 's1@rotation', 'target': 'get_scene('s2').set('rotation', -value); } +{"watch": "s1@rotation", "target": "get_scene('s2').set('rotation', -value);" } .br .br @@ -10986,7 +12592,7 @@ .br Example .br -{'watch': 'mousemove', 'target': 'let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );'} +{"watch": "mousemove", "target": "let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );"} .br .br @@ -11232,77 +12838,77 @@ .SH Scene modules .LP .br -.SS Scene mask +.SS Scene clear .br -This scene sets the canvas alpha mask mode. +This scene clears the canvas area covered by the scene with a given color. .br .br -The canvas alpha mask is always full screen. +The default clear color of the mixer is black. .br .br -In software mode, combining mask effect in record mode and reverse group drawing allows drawing front to back while writing pixels only once. +The clear area is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed scene area will be cleared. .br .br Options: .br -* mode ('off'): if set, reset clipper otherwise set it to scene position and size +* color ('none'): clear color .br - * off: mask is disabled +.SS Scene clip .br - * on: mask is enabled and cleared, further draw operations will take place on mask +This scene resets the canvas clipper or sets the canvas clipper to the scene area. .br - * onkeep: mask is enabled but not cleared, further draw operations will take place on mask + .br - * use: mask is enabled, further draw operations will be filtered by mask +The clipper is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed clipper will be used. .br - * use_inv: mask is enabled, further draw operations will be filtered by 1-mask + .br - * rec: mask is in record mode, further draw operations will be drawn on output and will set mask value to 0 +Clippers are handled through a stack, resetting the clipper pops the stack and restores previous clipper. .br - +If a clipper is already defined when setting the clipper, the clipper set is the intersection of the two clippers. .br -.SS Scene clear + .br -This scene clears the canvas area covered by the scene with a given color. +Options: .br - +* reset (false): if set, reset clipper otherwise set it to scene position and size .br -The default clear color of the mixer is black. +* stack (true): if false, clipper is set/reset independently of the clipper stack (no intersection, no push/pop of the stack) .br - +.SS Scene mask .br -The clear area is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed scene area will be cleared. +This scene sets the canvas alpha mask mode. .br .br -Options: -.br -* color ('none'): clear color +The canvas alpha mask is always full screen. .br -.SS Scene clip + .br -This scene resets the canvas clipper or sets the canvas clipper to the scene area. +In software mode, combining mask effect in record mode and reverse group drawing allows drawing front to back while writing pixels only once. .br .br -The clipper is always axis-aligned in output frame, so when skew/rotation are present, the axis-aligned bounding box of the transformed clipper will be used. +Options: .br - +* mode ('off'): if set, reset clipper otherwise set it to scene position and size .br -Clippers are handled through a stack, resetting the clipper pops the stack and restores previous clipper. + * off: mask is disabled .br -If a clipper is already defined when setting the clipper, the clipper set is the intersection of the two clippers. + * on: mask is enabled and cleared, further draw operations will take place on mask .br - + * onkeep: mask is enabled but not cleared, further draw operations will take place on mask .br -Options: + * use: mask is enabled, further draw operations will be filtered by mask .br -* reset (false): if set, reset clipper otherwise set it to scene position and size + * use_inv: mask is enabled, further draw operations will be filtered by 1-mask .br -* stack (true): if false, clipper is set/reset independently of the clipper stack (no intersection, no push/pop of the stack) + * rec: mask is in record mode, further draw operations will be drawn on output and will set mask value to 0 +.br + .br .SS Scene shape .br @@ -11671,6 +13277,14 @@ .SH Transition modules .LP .br +.SS Transition fade - software/GPU +.br +This transition performs fade to/from color of source videos +.br +Options: +.br +* color ('black'): fade color +.br .SS Transition gltrans - GPU only .br This transition module wraps gl-transitions, see https://gl-transitions.com/ and gpac -h avmix:gltrans for builtin transitions @@ -11679,6 +13293,10 @@ .br * fx (''): effect name for built-in effects, or path to gl-transition GLSL file .br +.SS Transition mix - software/GPU +.br +This transition performs cross-fade of source videos +.br .SS Transition swipe - software/GPU .br This transition performs simple 2D affine transformations for source videos transitions, with configurable effect origin @@ -11719,20 +13337,6 @@ .br .br -.SS Transition mix - software/GPU -.br -This transition performs cross-fade of source videos -.br -.SS Transition fade - software/GPU -.br -This transition performs fade to/from color of source videos -.br -Options: -.br -* color ('black'): fade color -.br - -.br .SH Options (expert): .LP .br @@ -11742,11 +13346,11 @@ .br gpu (enum, default: off): enable GPU usage .br - * off: no GPU +* off: no GPU .br - * mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients +* mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients .br - * all: try to use GPU for everything +* all: try to use GPU for everything .br .br @@ -11770,11 +13374,11 @@ .br dynpfmt (enum, default: init): allow dynamic change of output pixel format in software mode .br - * off: pixel format is forced to desired value +* off: pixel format is forced to desired value .br - * init: pixel format is forced to format of fullscreen input in first generated frame +* init: pixel format is forced to format of fullscreen input in first generated frame .br - * all: pixel format changes each time a full-screen input PID at same resolution is used +* all: pixel format changes each time a full-screen input PID at same resolution is used .br .br @@ -11809,7 +13413,7 @@ .br By default, video UTC and date are computed at each frame generation from current clock and not from frame number. .br -This will result in broken timing when playing at speeds other than 1.0. +This will result in broken UTC timing text when playing at speeds other than 1.0. .br This can be changed using .I lock. .br @@ -11834,7 +13438,6 @@ .br Target encoding bitrates can be assigned to each output using .I rates. This can be useful when generating dash: .br -Example .br gpac avgen:sizes=1280x720,1920x1080:rates=2M,5M c=aac:FID=1 c=264:FID=2:clone -o live.mpd:SID=1,2 .br @@ -11881,6 +13484,14 @@ .br .br +evte (uint, default: 0): output event stream +.br +* 0: disable +.br +* 1+: period (sec) of dummy events +.br + +.br freq (uint, default: 440): frequency of beep .br freq2 (uint, default: 659): frequency of odd beep @@ -11915,11 +13526,11 @@ .br pack (enum, default: no): packing mode for stereo views .br - * no: no packing +* no: no packing .br - * ss: side by side packing, forces .I views to 2 +* ss: side by side packing, forces .I views to 2 .br - * tb: top-bottom packing, forces .I views to 2 +* tb: top-bottom packing, forces .I views to 2 .br .br @@ -11931,20 +13542,22 @@ .br logt (bool): log frame time to console .br +banner (str, default: many thanks to QuickJS, FreeType, OpenSSL, SDL, FFmpeg, OpenHEVC, libjpeg, libpng, faad2, libmad, a52dec, xvid, OGG ...): banner text to display +.br .br .SH EXAMPLES .TP -Basic and advanced examples are available at https://wiki.gpac.io/Filters +Basic and advanced examples are available at https://wiki.gpac.io/Filters/Filters .SH MORE .LP Authors: GPAC developers, see git repo history (-log) .br For bug reports, feature requests, more information and source code, visit https://github.com/gpac/gpac .br -build: 2.4 +build: 26.02 .br -Copyright: (c) 2000-2023 Telecom Paris distributed under LGPL v2.1+ - https://gpac.io +Copyright: (c) 2000-2024 Telecom Paris distributed under LGPL v2.1+ - https://gpac.io .br .SH SEE ALSO .LP
View file
gpac-2.4.0.tar.gz/share/doc/man/gpac.1 -> gpac-26.02.0.tar.gz/share/doc/man/gpac.1
Changed
@@ -21,7 +21,7 @@ .br - an option using a single - indicates an option of gpac (see gpac -hx) or of libgpac (see gpac -hx core) .br - - an option using -- indicates a global filter or meta-filter (e.g. FFMPEG) option, e.g. --block_size=1000 or --profile=Baseline (see gpac -h doc) + - an option using -- indicates a global filter or meta-filter (e.g. FFmpeg) option, e.g. --block_size=1000 or --profile=Baseline (see gpac -h doc) .br .br @@ -59,7 +59,12 @@ .TP .B \-sloop (int) .br -loop execution of session, creating a session at each loop, mainly used for testing. If no value is given, loops forever +loop execution of session, creating a session at each loop, mainly used for testing, breaking at first error. If no value is given, loops forever +.br +.TP +.B \-eloop (int) +.br +same as sloop but does not break if error .br .TP .B \-runfor (int) @@ -165,6 +170,11 @@ force complete mode when no link directive are set - see filters help (-h doc) .br .TP +.B \-sid +.br +force source IDs to be present when attempting to link - see filters help (-h doc) +.br +.TP .B \-step .br test step mode in non-blocking session @@ -220,6 +230,8 @@ .br * links FNAME: print sources and sinks for filter FNAME (either builtin or JS filter) .br +* defer: print defer mode help +.br * FNAME: print filter FNAME info (multiple FNAME can be given) .br - For meta-filters, use FNAME:INST, e.g. ffavin:avfoundation @@ -256,14 +268,41 @@ assign documentation for a given alias (optional). Can be specified several times .br .TP -.B \-uncache +.B \-cache-info +.br +show cache info. Argument can be: +.br +* absent: the entire cache is inspected +.br +* B: filter entries created after B, with B a number of seconds prior to now or a date (0 means now) +.br +* B;C: filter entries created after B but before C, with B and C either a number of seconds prior to now or a date +.br + - If B is 0, min time is UTC=0 +.br + - If C is 0, max time is now +.br +The argument syntax is the same for all cache options +.br +.TP +.B \-cache-unflat .br revert all items in GPAC cache directory to their original name and server path .br .TP +.B \-cache-list +.br +list entries in cache +.br +.TP +.B \-cache-clean (int) +.br +clean cache +.br +.TP .B \-js (string) .br -specify javascript file to use as controller of filter session +specify javascript file to use as controller of filter session (can be set multiple times for multiple scripts) .br .TP .B \-wc @@ -295,6 +334,11 @@ .br setup credentials as used by servers .br +.TP +.B \-rv +.br +return absolute value of GPAC internal error instead of 1 when error +.br .br @@ -344,9 +388,9 @@ .br set scheduler mode .br -* free: lock-free queues except for task list (default) +* free: lock-free queues except for task list (default on most platforms) .br -* lock: mutexes for queues when several threads +* lock: mutexes for queues when several threads (default on arm64/aarch64) .br * freex: lock-free queues including for task lists (experimental) .br @@ -365,6 +409,11 @@ set maximum sleep time slot in milliseconds when regulation is enabled .br .TP +.B \-step-link +.br +load filters one by one when solvink a link instead of loading all filters for the solved path +.br +.TP .B \-threads (int) .br set N extra thread for the session. -1 means use all available cores @@ -409,6 +458,11 @@ .br default buffer size in frames when timing is not available .br +.TP +.B \-check-props +.br +check known property types upon assignment and PID vs packet types upon fetch (in test mode, exit with error code 5 if mismatch) +.br .SH Using Aliases .PL The gpac command line can become quite complex when many sources or filters are used. In order to simplify this, an alias system is provided. @@ -610,6 +664,137 @@ .br Servers in GPAC currently only support the Basic HTTP authentication scheme, and should preferably be run over TLS. .br +.SH Defer test mode +.LP +.br +This mode can be used to test loading filters one by one and asking for link resolution explicitly. +.br +This is mostly used to reproduce how sessions are build in more complex applications. +.br + +.br +The options rl, pi, pl and pd allow addressing a filter by index F in a list. +.br +- if the option is suffixed with an x (e.g. rlx=), F=0 means the last filter in the list of filters in the session +.br +- otherwise, F=0 means the last filter declared before the option +.br + +.br +The relink options -rl and -rlx always flush the session (run until no more tasks are scheduled). +.br +The last run can be omitted. +.br + +.br +Example +.br +gpac -dl -np -i SRC reframer -g -rl -g inspect -g -rl +.br + +.br +This will load SRC and reframer, print the graph (no connection), relink SRC, print the graph (connection to reframer), insert inspect, print the graph (no connection), relink reframer and run. No play event is sent here. +.br +Example +.br +gpac -dl -np -i SRC reframer inspect:deep -g -rl=2 -g -rl -se +.br + +.br +This will load SRC, reframer and inspect, print the graph (no connection), relink SRC, print the graph (connection to reframer), print the graph (no connection), relink reframer, send play and run. +.br + +.br +Linking can be done once filters are loaded, using the syntax @F@SRC or @@F@SRC: +.br +- @F indicates the destination filter using a 0-based index F starting from the last laoded filter, e.g. @0 indicates the last loaded filter. +.br +- @@F indicates the target filter using a 0-based index F starting from the first laoded filter, e.g. @@1 indicates the second loaded filter. +.br +* `@SRC`or `@@SRC`: same syntax as link directives +.br +Sources MUST be set before relinking outputs using .I rl. +.br +Example +.br +gpac -dl -i SRC F1 F2 ... @1@2 @0@2 +.br + +.br +This will set SRC as source to F1 and SRC as source to F2 after loading all filters. +.br + +.br +The following options are used in defer mode: +.br + +.br +.TP +.B \-dl +.br +enable defer linking mode for step-by-step graph building tests +.br +.TP +.B \-np +.br +prevent play event from sinks +.br +.TP +.B \-rl=F (string) +.br +relink outputs of filter F (default 1) +.br +.TP +.B \-wl=F (string) +.br +same as -rl but does not flush session) +.br +.TP +.B \-pi=+|-F:i (string) +.br +print PID properties (all or of index i) of filter F (default 0) +.br +* if prefixed with `-`: only list PIDs +.br +* if prefixed with `+`: also print PID info +.br +.TP +.B \-pl=+F:i@NAME (string) +.br +probe filter chain from filter F (default 0) to the given filter NAME: +.br +* if prefixed with `+`: print all known chains and their priorities +.br +.TP +.B \-pd=F:i (string) +.br +print possible PID destinations (all or of index i) of filter F (default 0) +.br +.TP +.B \-f +.br +flush session until no more tasks +.br +.TP +.B \-g +.br +print graph +.br +.TP +.B \-s +.br +print stats +.br +.TP +.B \-se +.br +send PLAY event from sinks (only done once) +.br +.TP +.B \-m (string) +.br +print message +.br .SH Configuration file .LP .br @@ -738,6 +923,11 @@ .LP .br .TP +.B \-tmp (string) +.br +specify directory for temporary file creation instead of OS-default temporary file management +.br +.TP .B \-noprog .br disable progress messages @@ -880,6 +1070,16 @@ disable all mutexes, threads and semaphores (do not use if unsure about threading used) .br .TP +.B \-xml-max-csize (int, default: 100k) +.br +maximum XML content or attribute size +.br +.TP +.B \-users (string) +.br +authentication configuration file for users and groups +.br +.TP .B \-netcap (string) .br set packet capture and filtering rules formatted as CFGRULES. Each -netcap argument will define a configuration @@ -898,23 +1098,29 @@ .br RULES is an optional list of OPT,OPT2... with OPT in: .br -* m=N: set rule mode - N can be r for reception only (default), w for send only or rw for both +* m=K: set rule mode - K can be r for reception only (default), w for send only or rw for both +.br +* s=K: set packet start range to K .br -* s=N: set packet start range to N +* e=K: set packet end range to K - only used for r and f rules, 0 or not set means rule apply until end .br -* e=N: set packet end range to N (only used for r and f rules) +* n=K: set number of packets to drop to K - not set, 0 or 1 means single packet .br -* n=N: set number of packets to drop to N - not set, 0 or 1 means single packet +* r=K: random drop n packet every K .br -* r=N: random drop one packet every N +* f=K: drop first n packets every K .br -* f=N: drop first packet every N +* d=K: reorder n packets after the next K packets, can be used with f or r rules .br -* p=P: local port number to filter, if not set the rule applies to all packets +* p=K: filter packets on port K only, if not set the rule applies to all packets .br -* o=N: patch packet instead of droping (always true for TCP), replacing byte at offset N (0 is first byte, <0 for random) +* o=K: patch packet instead of dropping (always true for TCP), replacing byte at offset K (0 is first byte, <0 for random) .br -* v=N: set patch byte value to N (hexa) or negative value for random (default) +* v=K: set patch byte value to K (hexa) or negative value for random (default) +.br +* S=K: same as s but adds number of capture file reload/loop +.br +* E=K: same as e but adds number of capture file reload/loop .br .br @@ -952,19 +1158,9 @@ cache directory location .br .TP -.B \-proxy-on +.B \-proxy (string) .br -enable HTTP proxy -.br -.TP -.B \-proxy-name (string) -.br -set HTTP proxy address -.br -.TP -.B \-proxy-port (int, default: 80) -.br -set HTTP proxy port +set HTTP proxy server address and port (if no protocol scheme is set, use same as target) .br .TP .B \-maxrate (int) @@ -989,7 +1185,12 @@ .TP .B \-cache-size (int, default: 100M) .br -specify cache size in bytes +specify maximum cache size on disk in bytes +.br +.TP +.B \-cache-check (int, default: 60) +.br +cache clean interval in seconds, 0 only clean cache at startup .br .TP .B \-tcp-timeout (int, default: 5000) @@ -1012,6 +1213,11 @@ enable accepting broken SSL certificates .br .TP +.B \-ca-bundle (string) +.br +path to a custom CA certificates bundle file +.br +.TP .B \-user-agent,-ua (string) .br set user agent name for HTTP/RTSP @@ -1062,6 +1268,29 @@ enable intermediate copy of data in nghttp2 (default is disabled but may report as broken frames in wireshark) .br .TP +.B \-curl +.br +use CURL instead of GPAC HTTP stack +.br +.TP +.B \-h3 (Enum, default: auto) +.br +set HTTP/3 mode +.br +* no: disable HTTP/3 +.br +* first: force trying first with HTTP/3 +.br +* auto: connect using HTTP 1 or 2 and use HTTP/3 for next request(s) if announced +.br +* only: only use HTTP/3 +.br +.TP +.B \-h3-trace +.br +trace QUIC and HTTP3 +.br +.TP .B \-dbg-edges .br log edges status in filter graph before dijkstra resolution (for debug). Edges are logged as edge_source(status(disable_depth), weight, src_cap_idx -> dst_cap_idx) @@ -1102,9 +1331,9 @@ .br set scheduler mode .br -* free: lock-free queues except for task list (default) +* free: lock-free queues except for task list (default on most platforms) .br -* lock: mutexes for queues when several threads +* lock: mutexes for queues when several threads (default on arm64/aarch64) .br * freex: lock-free queues including for task lists (experimental) .br @@ -1123,6 +1352,11 @@ set maximum sleep time slot in milliseconds when regulation is enabled .br .TP +.B \-step-link +.br +load filters one by one when solvink a link instead of loading all filters for the solved path +.br +.TP .B \-threads (int) .br set N extra thread for the session. -1 means use all available cores @@ -1168,6 +1402,11 @@ default buffer size in frames when timing is not available .br .TP +.B \-check-props +.br +check known property types upon assignment and PID vs packet types upon fetch (in test mode, exit with error code 5 if mismatch) +.br +.TP .B \-gl-bits-comp (int, default: 8) .br number of bits per color component in OpenGL @@ -1236,49 +1475,69 @@ * other: attempt to parse anyway .br .TP -.B \-rmt +.B \-srt-forced .br -enable profiling through Remotery. A copy of Remotery visualizer is in gpac/share/vis, usually installed in /usr/share/gpac/vis or Program Files/GPAC/vis +enable SRT with forced subtitles extensions .br .TP -.B \-rmt-port (int, default: 17815) +.B \-rmt .br -set remotery port +enable remote monitoring webserver .br .TP -.B \-rmt-reuse +.B \-rmt-port (int, default: 6363) .br -allow remotery to reuse port +set rmt ws port .br .TP .B \-rmt-localhost .br -make remotery only accepts localhost connection +make rmt ws only accepts localhost connection .br .TP .B \-rmt-sleep (int, default: 10) .br -set remotery sleep (ms) between server updates +set rmt ws sleep (ms) between server updates +.br +.TP +.B \-rmt-cert (string) +.br +rmt ws: certificate file in PEM format to use for TLS mode .br .TP -.B \-rmt-nmsg (int, default: 10) +.B \-rmt-pkey (string) .br -set remotery number of messages per update +rmt ws: private key file in PEM format to use for TLS mode .br .TP -.B \-rmt-qsize (int, default: 131072) +.B \-userws-port (int, default: 6364) .br -set remotery message queue size in bytes +set user ws port .br .TP -.B \-rmt-log +.B \-userws-localhost .br -redirect logs to remotery (experimental, usually not well handled by browser) +make userws ws only accepts localhost connection .br .TP -.B \-rmt-ogl +.B \-userws-sleep (int, default: 10) .br -make remotery sample opengl calls +set userws sleep (ms) between server updates +.br +.TP +.B \-userws-cert (string) +.br +userws: certificate file in PEM format to use for TLS mode +.br +.TP +.B \-userws-pkey (string) +.br +userws: private key file in PEM format to use for TLS mode +.br +.TP +.B \-diso-nosize +.br +skip box size info when dumping ISOBMFF .br .TP .B \-m2ts-vvc-old @@ -1305,6 +1564,16 @@ .br use box definitions in the given directory for XML dump .br +.TP +.B \-no-mabr-patch +.br +disable GPAC parsing of patched isom boxes from mabr (will behave like most browsers/players) +.br +.TP +.B \-no-cdrf +.br +disable cdrf sample dep +.br .SH libgpac logs options: .LP .br @@ -1356,6 +1625,8 @@ .br * debug: logs all messages .br +* strict: exit if error for this log tool and use default log level if tool +.br .br toolX can be one of: @@ -1412,11 +1683,13 @@ .br * rti: run-time stats of compositor .br -* all: all tools logged - other tools can be specified afterwards. +* all: all tools logged - other tools can be specified afterwards .br The special keyword ncl can be set to disable color logs. .br -The special keyword strict can be set to exit at first error. +The special keyword strict can be set to exit at first error on any tool. +.br +levelX can accept the suffix +strict to force strict error only for the given log tool(s). .br .br @@ -1476,7 +1749,7 @@ .br * `file@FILE`: load string from local FILE (opened in binary mode). .br - * `bxml@FILE`: binarize XML from local FILE and set property type to data - see https://wiki.gpac.io/NHML-Format. + * `bxml@FILE`: binarize XML from local FILE and set property type to data - see https://wiki.gpac.io/xmlformats/NHML-Format. .br * data: formatted as: .br @@ -1486,10 +1759,12 @@ .br * `file@FILE`: load data from local FILE (opened in binary mode). .br - * `bxml@FILE`: binarize XML from local FILE - see https://wiki.gpac.io/NHML-Format. + * `bxml@FILE`: binarize XML from local FILE - see https://wiki.gpac.io/xmlformats/NHML-Format. .br * `b64@DATA`: load data from base-64 encoded DATA. .br + * `FMT@val`: load values from val (comma-separated list) with FMT being u8, s8, u16, s16, u32, s32, u64, s64, flt, dbl, hex or str. +.br * pointer: pointer address as formatted by %p in C. .br * string lists: formatted as val1,val2,.... Each value can also use file@FILE syntax. @@ -1539,7 +1814,7 @@ .br The escape mechanism is not needed for local source, for which file existence is probed during argument parsing. It is also not needed for builtin protocol handlers (avin://, video://, audio://, pipe://) .br -For tcp:// and udp:// protocols, the escape is not needed if a trailing / is appended after the port number. +For schemes not using a server path, e.g. tcp:// and udp://, the escape is not needed if a trailing / is appended after the port number. .br Example .br @@ -1561,7 +1836,7 @@ .br .br -It is possible to disable option parsing (for string options) by duplicating the separator. +It is possible to locally disable option parsing (usefull for string options) by duplicating the separator. .br Example .br @@ -1661,7 +1936,7 @@ .br .SS Escaping option separators .br -When a filter uses an option defined as a string using the same separator character as gpac, you can either modify the set of separators, or escape the separator by duplicating it. The options enclosed by duplicated separator are not parsed. This is mostly used for meta filters, such as ffmpeg, to pass options to sub-filters such as libx264 (cf x264opts parameter). +When a filter uses an option defined as a string using the same separator character as gpac, you can either modify the set of separators, or escape the separator by duplicating it. The options enclosed by duplicated separator are not parsed. This is mostly used for meta filters, such as ffmpeg, to pass options to sub-filters such as libx264 (cf x264opts parameter). This can also be used to escape a list value containing the comma separator character. .br Example .br @@ -1687,6 +1962,14 @@ .br This will set option a to foo, b to bar:c and the option d to fun on the filter. .br +Example +.br +f:list=a,b1,,b2,c +.br + +.br +This will set list values to a, b1,b2, and c. +.br .br .SH Filter linking LINK @@ -1696,7 +1979,7 @@ .br Each filter exposes one or more sets of capabilities, called capability bundle, which are property type and values that must be matched or excluded by connecting PIDs. .br -To check the possible sources and destination for a filter FNAME, use gpac -h links FNAME +To check the possible sources and destinations for a filter FNAME, use gpac -h links FNAME .br .br @@ -1720,7 +2003,7 @@ .br - if DST filter has link directives, use them to allow or reject PID connection. .br -- otherwise, if complete mode is enabled, allow connection.. +- otherwise, if complete mode is enabled, allow connection. .br - otherwise (implicit mode): .br @@ -1736,6 +2019,8 @@ .br This is typically needed when dynamically inserting/removing filters in an existing session where some filters have no ID defined and are not desired for the inserted chain. .br +A filter with RSID set is not clonable. +.br .br Example @@ -2186,7 +2471,7 @@ .br .br -$$ is an escape for $ +$$ is an escape for $. For shells automatically replacing $$ with $, the syntax $;$ can be used to express $$. .br .br @@ -2205,7 +2490,6 @@ .br When a filter accepts a single connection and has a connected input, it is no longer available for dynamic resolution. There may be cases where this behavior is undesired. Take a HEIF file with N items and do: .br -Example .br gpac -i img.heif -o dump_$ItemID$.jpg .br @@ -2245,7 +2529,6 @@ .br You can explicitly deactivate the cloning instructions: .br -Example .br gpac -i dual_audio resample:osr=48k:clone=0 c=aac -o dst .br @@ -2278,7 +2561,6 @@ .br Cloning in implicit linking mode applies to output as well: .br -Example .br gpac -i dual_audio -o dst_$PID$.aac .br @@ -2397,6 +2679,18 @@ .br .br +A property can also be removed by not specifying any value. Conditional removal is possible using the above syntax. +.br +Example +.br +gpac -i source.ts:#FOO= +.br + +.br +This will remove the FOO property on the output PID. +.br + +.br .SH Using option files .LP .br @@ -2480,6 +2774,8 @@ .br - user home directory for other platforms .br +* $GCFG: replaced by system path to folder containing GPAC config +.br * $GLANG: replaced by the global config language option .I -lang .br * $GUA: replaced by the global config user agent option .I -user-agent @@ -2490,7 +2786,6 @@ .br The $GINC construct can be used to dynamically assign numbers in filter chains: .br -Example .br gpac -i source.ts tssplit @#ServiceID= -o dump_$GINC(10,2).ts .br @@ -2821,12 +3116,12 @@ .TP .B ServiceName (SNAM,str,D ) .br -Name of parent service +Name of parent service, signled as PID info .br .TP .B ServiceProvider (SPRO,str,D ) .br -Provider of parent service +Provider of parent service, signled as PID info .br .TP .B StreamType (PMST,uint, ) @@ -2874,9 +3169,19 @@ Media is unframed AAC in LATM format .br .TP +.B USRT (USRT,bool, ) +.br +Media is unframed SRT, header is in payload with 0 start time +.br +.TP .B Duration (PDUR,lfrac, ) .br -Media duration (a negative value means an estimated duration based on rate) +Media duration +.br +.TP +.B EstimatedDuration (EDUR,bool, ) +.br +Media duration is an estimated duration based on rate .br .TP .B NumFrames (NFRM,uint,D ) @@ -2916,7 +3221,7 @@ .TP .B ProfileLevel (PRPL,uint,D ) .br -MPEG-4 profile and level +Profile and level indication .br .TP .B DecoderConfig (DCFG,mem, ) @@ -3041,7 +3346,7 @@ .TP .B SAR (PSAR,frac, ) .br -Sample (i.e. pixel) aspect ratio +Sample (i.e. pixel) aspect ratio (negative values mean no SAR and removal of info in containers) .br .TP .B MaxWidth (MWID,uint, ) @@ -3094,7 +3399,7 @@ Original resolution of video .br .TP -.B SRD (SRD ,v4di, ) +.B SRD (SRDI,v4di, ) .br Position and size of the video in the referential given by SRDRef .br @@ -3203,6 +3508,11 @@ Path of source file on file system .br .TP +.B FileAlias (FALI,str,D ) +.br +Alias name for source file, replace $URL$ and $File$ in templates +.br +.TP .B MIMEType (MIME,str,D ) .br MIME type of source @@ -3451,7 +3761,7 @@ ModeSet for AMR and AMR-WideBand .br .TP -.B SubSampleInfo (SUBS,mem, ) +.B SubSampleInfo (SUBS,mem, P) .br Binary blob describing N subsamples of the sample, formatted as N (u32)flags(u32)size(u32)codec_param(u8)priority(u8) discardable. Subsamples for a given flag MUST appear in order, however flags can be interleaved .br @@ -3491,7 +3801,7 @@ Set on packets marking the beginning of a DASH/HLS segment for cue-driven segmentation - see dasher help .br .TP -.B MediaTime (MTIM,dbl,D ) +.B MediaTime (MTIM,dbl,DP) .br Corresponding media time of the parent packet (0 being the origin) .br @@ -3533,7 +3843,7 @@ .TP .B STSDTemplate (ISTD,mem,D ) .br -ISOBMFF serialized sample description box (stsd entry) for this PID +ISOBMFF serialized sample description entry for this PID .br .TP .B MovieUserData (IMUD,mem,D ) @@ -3593,7 +3903,23 @@ .TP .B ASID (DAID,uint,D ) .br -ID of parent DASH AS +ID of parent DASH Adaptation Set +.br +.TP +.B SSR (SSRR,sint,D ) +.br +ID of Adaptation Set: +.br +* same value as ASID: regular SSR not used for cross-AS switching +.br +* ID of another AdaptationSet: enable cross-AS switching between this AS and the referenced one +.br +* negative value: LL-HLS compatability mode +.br +.TP +.B ASQuery (ASQR,str,D ) +.br +Query string to add to this Adaptation Set when requesting segments .br .TP .B MuxSrc (MSRC,str,D ) @@ -3606,6 +3932,11 @@ DASH mode to be used by multiplexer if any, set by dasher. 0 is no DASH, 1 is regular DASH, 2 is VoD .br .TP +.B InitBase64 (IB64,bool,D ) +.br +Indicate that multiplexer should send the base64 encoded version of the init segment +.br +.TP .B SegSync (DFSS,bool,D ) .br Indicate segment must be completely flushed before sending segment/fragment size events @@ -3616,6 +3947,26 @@ DASH target segment duration in seconds .br .TP +.B FragDur (FDUR,frac,D ) +.br +DASH target fragment duration in seconds +.br +.TP +.B DashMultiPid (DMSD,ptr,D ) +.br +Pointer to the GF_List of input PIDs for multi-stsd entries segments, set by dasher +.br +.TP +.B DashMultiPidIdx (DMSI,uint,D ) +.br +1-based index of PID in the multi PID list, set by dasher +.br +.TP +.B DashMultiTrack (DMTK,ptr,D ) +.br +Pointer to the GF_List of input PIDs for multi-tracks segments, set by dasher +.br +.TP .B Role (ROLE,strl,D ) .br List of roles for this PID, where each role string can be a DASH role, a URN:role-value or any other string (this will throw a warning and use a custom URI for the role) @@ -3676,6 +4027,11 @@ Name of HLS Group of a stream .br .TP +.B HLSRend (HLGR,strl,D ) +.br +List of HLS group allowed in group rendition - when not set, all groups are allowed +.br +.TP .B HLSForce (HLFI,str,D ) .br Force writing EXT-X-STREAM-INF if stream is in a rendition group, value is the name of associated groups (can be empty) @@ -3726,24 +4082,29 @@ DASH forward mode is used for this PID. If 2, the manifest is also carried in packet propery .br .TP -.B DFManifest (DMPD,str,D ) +.B DFManifest (DMPD,str,DP) .br Value of manifest in forward mode .br .TP -.B DFVariant (DHLV,strl,D ) +.B DFVariant (DHLV,strl,DP) .br Value of variant playlist in forward mode .br .TP -.B DFVariantName (DHLN,strl,D ) +.B DFVariantName (DHLN,strl,DP) .br Value of variant playlist name in forward mode .br .TP .B DFPStart (DPST,luint,D ) .br -Value of active period start time in forward mode +Value of active period start time in ms in forward mode +.br +.TP +.B DFPckPStart (PDPS,bool,DP) +.br +Indicate new period start (only set on first packets of non-first periods) .br .TP .B HLSKey (HLSK,str, ) @@ -3771,6 +4132,11 @@ Color transfer characteristics .br .TP +.B ColorTransferAlternative (CATC,ctfc,D ) +.br +Alternative Color transfer characteristics +.br +.TP .B ColorMatrix (CMXC,cmxc,D ) .br Color matrix coefficient @@ -3838,12 +4204,17 @@ .TP .B FragTFDT (PFRT,luint,DP) .br -Decode time of first packet in fragmentt +Decode time of first packet in fragment .br .TP .B SIDXRange (PFSR,lfrac,DP) .br -Start and end position in bytes of sidx if packet is a fragment or segment start +Start and end position in bytes of sidx in segment if any +.br +.TP +.B VODSIDXRange (PRSR,lfrac,D ) +.br +Start and end position in bytes of root sidx .br .TP .B MoofTemplate (MFTP,mem,DP) @@ -3896,29 +4267,29 @@ Fragment URL (without '#') of original URL (used by some filters to set the property on media PIDs) .br .TP -.B ROUTEIP (RSIP,str,D ) +.B MCASTIP (MSIP,str,D ) .br -ROUTE session IP address +session Multicast IP address for ROUTE/MABR .br .TP -.B ROUTEPort (RSPN,uint,D ) +.B MCASTPort (MSPN,uint,D ) .br -ROUTE session port number +session port number for ROUTE/MABR .br .TP -.B ROUTEName (RSFN,str,D ) +.B MCASTName (MSFN,str,D ) .br -Name (location) of raw file to advertise in ROUTE session +Name (location) of raw file to advertise in ROUTE/MABR session .br .TP -.B ROUTECarousel (RSCR,frac,D ) +.B MCASTCarousel (MSCR,frac,D ) .br -Carousel period in seconds of raw file in ROUTE session +Carousel period in seconds of raw file or low-latency manifest/init segments for ROUTE/MABR sessions .br .TP -.B ROUTEUpload (RSST,frac,D ) +.B MCASTUpload (MSST,frac,D ) .br -Upload time in seconds of raw file in ROUTE session +Upload time in seconds of raw files for ROUTE/MABR sessions .br .TP .B Stereo (PSTT,uint,D ) @@ -3931,7 +4302,7 @@ Projection type of video .br .TP -.B InitalPose (PPOS,v3di,D ) +.B InitialPose (PPOS,v3di,D ) .br Initial pose for 360 video, in degrees expressed as 16.16 bits (x is yaw, y is pitch, z is roll) .br @@ -3963,7 +4334,7 @@ .TP .B IsManifest (PHSM,uint,D ) .br -PID is a HAS manifest (MSB=1 if live) +PID is a HAS manifest (bit 9 set to 1 if live), lower 8 bits value can be .br * 0: not a manifest .br @@ -3986,13 +4357,13 @@ .TP .B ForcedSub (PFCS,uint,D ) .br -PID or Packet is forced sub +PID forced sub .br -0: not forced +* 0: not forced .br -1: forced frame +* 1: some frames are forced .br -2: all frames are forced (PID only) +* 2: all frames are forced .br .TP .B ChapTimes (CHPT,uintl,D ) @@ -4012,7 +4383,7 @@ .TP .B SkipBegin (PCKS,uint, P) .br -Amount of media to skip from beginning of packet in PID timescale +Amount of media to skip from beginning of packet in PID timescale (when set o PID, indicate packets with skip will be present) .br .TP .B SkipPres (PCKD,bool, P) @@ -4020,19 +4391,34 @@ Packet and any following with CTS greater than this packet shall not be presented (used by reframer to create edit lists) .br .TP -.B HLSRef (HPLR,luint,DP) +.B OriginalDuration (PCOD,frac, P) +.br +Elapsed time (.num) and original duration (.den, 0 if last copy of packet) for redundant packets +.br +.TP +.B HasSkipBegin (PSBP,bool, ) +.br +Indicate if PID will carry packets with SkipBegin properties +.br +.TP +.B HLSRef (PHLR,luint,D ) .br HLS playlist reference, gives a unique ID identifying media mux, and indicated in packets carrying child playlists .br .TP -.B LLHLS (HLSL,uint,D ) +.B PckHLSRef (HPLR,luint,DP) .br -HLS low latency mode +Same as HLSRef but carried on packets for ROUTE/MABR file transfer .br .TP -.B LLHLSFragNum (HLSN,uint, P) +.B LLHAS (HLHS,uint,D ) .br -LLHLS fragment number +DASH/HLS low latency mode +.br +.TP +.B LLHASFragNum (HLSN,uint,DP) +.br +DASH-SSR/LLHLS fragment number .br .TP .B DownloadSession (GHTT,ptr,D ) @@ -4054,6 +4440,201 @@ .br Signal packet is the last in the desired play range .br +.TP +.B RefID (PKID,sint, P) +.br +packet identifier for dependency (usually POC for video) +.br +.TP +.B Refs (PRFS,sintl, P) +.br +list of packet identifier this packet depends on +.br +.TP +.B UDTA (PUDT,ptr,DP) +.br +User data for the packet +.br +.TP +.B Timecode (TCOD,mem, P) +.br +First timecode extracted from SEI (if present) +.br +.TP +.B DOVI (DOVI,mem, ) +.br +DolbyVision configuration +.br +.TP +.B OutPath (FDST,str, ) +.br +Output file name of PID used by some filters creating additional raw PIDs +.br +.TP +.B ACrypMeta (AMET,mem, ) +.br +Meta-data for Adobe encryption +.br +.TP +.B HasCRoll (CROL,bool, ) +.br +Indicates if key roll is used in CENC +.br +.TP +.B STSDAllTemplates (ISTA,mem,D ) +.br +ISOBMFF serialized sample description box for this PID +.br +.TP +.B STSDTemplateIdx (ISTI,uint,D ) +.br +Index of corresponding STSDAllTemplates +.br +.TP +.B PremuxType (PPST,uint, ) +.br +Main streamtype of the PID before mux, only used for ROUTE/MABR setup +.br +.TP +.B CodecMerge (PCMB,uint,D ) +.br +Indicate the PID can be merged with other streams with same value for single decoding (HEVC only for now) +.br +.TP +.B RelativePath (FNRL,bool,DP) +.br +Indicate the packet file name uses relative path +.br +.TP +.B ClearKeyID (CCKI,mem,D ) +.br +Key ID for ClearKey scheme +.br +.TP +.B DashSparse (DSSG,bool,D ) +.br +indicate DASH segments are generated in sparse mode (from context) +.br +.TP +.B DashDepGroup (DGDI,uint,D ) +.br +indicate DASH dependency group ID +.br +.TP +.B SC35Ref (SC35,uint,D ) +.br +PID has SCTE35 information carried on indicated PID number +.br +.TP +.B NoInit (PNIN,bool,D ) +.br +PID does not use any init segment in DASH (file forward mode of dasher, only used for ROUTE/MABR) +.br +.TP +.B ForceUnframe (PFUF,bool,D ) +.br +force creation of rewriter filter (only used for forcing reparse of NALU-based codecs) +.br +.TP +.B MetaCodecID (MDCI,uint,D ) +.br +identifier for meta codecs (FFmpeg, ...) +.br +.TP +.B MetaCodecName (MDCN,str,D ) +.br +Name used by for meta codecs (FFmpeg, ...) +.br +.TP +.B MetaCodecOpaque (MDOP,uint,D ) +.br +Internal property used for meta demuxers ( FFmpeg, ...) codec opaque data +.br +.TP +.B HASSegStart (FMSS,lfrac,DP) +.br +Start time of segment for ROUTE/MABR scheduling +.br +.TP +.B SplitStart (PSPS,uint,DP) +.br +split start time of packet in PID timescale, for index-based dashing +.br +.TP +.B SplitEnd (PSPE,uint,DP) +.br +split end time of packet in PID timescale, for index-based dashing +.br +.TP +.B InitName (PINM,str,D ) +.br +Name of init segment when dashing, used for ROUTE/MABR +.br +.TP +.B SegURL (SURL,str,DP) +.br +URL of source segment (when forwarding fragment boundaries) +.br +.TP +.B DynPSSH (PSHP,mem, P) +.br +PSSH blob for CENC, same format as CENC_PSSH, used when using master key and roll keys, signaled on first packet of segment where the PSSH changes +.br +.TP +.B LLHASTemplate (PSRT,str, P) +.br +Template for DASH-SSR and LLHLS sub-segments +.br +.TP +.B PartialRepair (PCPR,bool, P) +.br +indicate the mux data in the associated data is parsable but contains errors (only set on corrupted packets) +.br +.TP +.B SEILoaded (PSEI,bool,D ) +.br +indicate that PID is extracting SEI/inband data from packets +.br +.TP +.B Fake (PFAK,bool,D ) +.br +Indicate a stream present in the source but not delivered as a PID +.br +.TP +.B ContentLightLevel (CLLP,mem,DP) +.br +Content light level, payload of clli box (see ISO/IEC 14496-12), can be set as a list of 2 integers in fragment declaration (e.g. "=max_cll,max_pic_avg_ll") +.br +.TP +.B MasterDisplayColour (MDCP,mem,DP) +.br +Master display colour info, payload of mdcv box (see ISO/IEC 14496-12), can be set as a list of 10 integers in fragment declaration (e.g. "=dpx0,dpy0,dpx1,dpy1,dpx2,dpy2,wpx,wpy,max,min") +.br +.TP +.B SEILoaded (SEIP,bool,DP) +.br +indicate that packet has SEI/inband data in its properties +.br +.TP +.B OriginalPTS (OPTS,luint,DP) +.br +indicate original PTS or PCR when remapping M2TS PCR +.br +.TP +.B OriginalDTS (ODTS,luint,DP) +.br +indicate original DTS when remapping M2TS PCR +.br +.TP +.B MABRBaseURLs (MABU,strl,D ) +.br +optionnal URLs for MABR - if first is nonesource server is not declared as repair server +.br +.TP +.B Forced (PCFS,bool,DP) +.br +indicate packet is a forced subtitle +.br .SH Pixel formats .LP .br @@ -4213,6 +4794,11 @@ Grey+Alpha 8 bit .br .TP +.B rgb8 (ext *.rgb8) +.br +RGB 332, 8 bits / pixel +.br +.TP .B rgb4 (ext *.rgb4) .br RGB 444, 12 bits (16 stored) / pixel @@ -4714,6 +5300,11 @@ Enhanced AC3 Audio .br .TP +.B ac4 +.br +AC4 Audio +.br +.TP .B mlp .br Dolby TrueHD @@ -4729,7 +5320,7 @@ G719 Audio .br .TP -.B dstc +.B dtsc .br DTS Coherent Acoustics and Digital Surround Audio .br @@ -4739,7 +5330,7 @@ DTS-HD High Resolution Audio and DTS-Master Audio .br .TP -.B dstl +.B dtsl .br DTS-HD Substream containing only XLLAudio .br @@ -4839,6 +5430,11 @@ Opus Audio .br .TP +.B iamf +.br +AOM IAMF (Immersive Audio Model and Formats) +.br +.TP .B flac .br Flac Audio @@ -4959,6 +5555,16 @@ VP10 Video .br .TP +.B avsv|avs3 +.br +AVS3 Video +.br +.TP +.B avsa|avs3 +.br +AVS3 Audio +.br +.TP .B mhas .br MPEG-H Audio @@ -5001,7 +5607,7 @@ .TP .B ffmpeg .br -FFMPEG unmapped codec +FFmpeg unmapped codec .br .TP .B tmcd @@ -5009,6 +5615,16 @@ QT TimeCode .br .TP +.B sc35 +.br +SCTE35 +.br +.TP +.B evte +.br +Event Messages +.br +.TP .B vvc|266|h266 .br VVC Video @@ -5026,7 +5642,7 @@ .TP .B ffv1 .br -FFMPEG Video Codec 1 +FFmpeg Video Codec 1 .br .TP .B dvbs @@ -5048,6 +5664,11 @@ .br Apple Lossless Audio .br +.TP +.B dnx +.br +AViD DNxHD +.br .SH Stream types .LP .br @@ -5074,17 +5695,17 @@ .TP .B 3/2.0 (int 5) .br -Layout 0x0000000000000307 +Layout 0x0000000000000037 .br .TP .B 3/2.1 (int 6) .br -Layout 0x000000000000030f +Layout 0x000000000000003f .br .TP .B 5/2.1 (int 7) .br -Layout 0x000000000000030f +Layout 0x000000000000003f .br .TP .B 1+1 (int 8) @@ -5119,12 +5740,12 @@ .TP .B 5/2.1 (int 14) .br -Layout 0x000000000006030f +Layout 0x000000000006003f .br .TP .B 5/5.2 (int 15) .br -Layout 0x000000000606630f +Layout 0x000000000606603f .br .TP .B 5/4.1 (int 16) @@ -5153,16 +5774,16 @@ .br .SH EXAMPLES .TP -Basic and advanced examples are available at https://wiki.gpac.io/Filters +Basic and advanced examples are available at https://wiki.gpac.io/Filters/Filters .SH MORE .LP Authors: GPAC developers, see git repo history (-log) .br For bug reports, feature requests, more information and source code, visit https://github.com/gpac/gpac .br -build: 2.4 +build: 26.02 .br -Copyright: (c) 2000-2023 Telecom Paris distributed under LGPL v2.1+ - https://gpac.io +Copyright: (c) 2000-2024 Telecom Paris distributed under LGPL v2.1+ - https://gpac.io .br .SH SEE ALSO .LP
View file
gpac-2.4.0.tar.gz/share/doc/man/mp4box.1 -> gpac-26.02.0.tar.gz/share/doc/man/mp4box.1
Changed
@@ -24,7 +24,7 @@ .br .br -MP4Box usually generates a temporary file when creating a new IsoMedia file. The location of this temporary file is OS-dependent, and it may happen that the drive/partition the temporary file is created on has not enough space or no write access. In such a case, you can specify a temporary file location with .I tmp. +MP4Box usually generates a temporary file when creating a new IsoMedia file. The location of this temporary file is OS-dependent, and it may happen that the drive/partition the temporary file is created on has not enough space or no write access. In such a case, you can specify a temporary file location with .I -tmp. .br .br @@ -515,7 +515,7 @@ .br Also see: .br -- the dasher `gpac -h dash` filter documentation +- the dasher `gpac -h dasher` filter documentation .br - DASH wiki|DASH-intro. .br @@ -534,6 +534,8 @@ .br * #audio: only use the first audio track from the source file .br +* #Prop=Value: add PID filtering using the same syntax as SID fragments (cf gpac -h doc) +.br * :id=NAME: set the representation ID to NAME. Reserved value NULL disables representation ID for multiplexed inputs. If not set, a default value is computed and all selected tracks from the source will be in the same output multiplex. .br * :dur=VALUE: process VALUE seconds (fraction) from the media. If VALUE is longer than media duration, last sample duration is extended. @@ -566,7 +568,7 @@ .br * :sscale: force movie timescale to match media timescale of the first track in the segment. .br -* :trackID=N: only use the track ID N from the source file +* :trackID=N: same as setting fragment #trackID= .br * @f1:args@fN:args@@fK:args: set a filter chain to insert between the source and the dasher. Each filter in the chain is formatted as a regular filter, see filter doc `gpac -h doc`. If several filters are set: .br @@ -665,6 +667,8 @@ .br - $Path=PATH$ is replaced by PATH when creating segments, ignored otherwise .br +- $SubNumber%%0Nd$ is replaced by the segment subnumber in segment sequences, possibly prefixed with 0 +.br - $Segment=NAME$ is replaced by NAME for media segments, ignored for init segments .br .TP @@ -749,7 +753,7 @@ .TP .B \-time-shift (int) .br -specify MPD time shift buffer depth in seconds, -1 to keep all files) +specify MPD time shift buffer depth in seconds, -1 to keep all files. Default is 0 .br .TP .B \-subdur (number) @@ -774,7 +778,7 @@ .TP .B \-dash-scale (int) .br -specify that timing for .I dash, .I dash-live, .I subdur and .I do_frag are expressed in given timescale (units per seconds) rather than ms +specify that timing for .I dash, .I dash-live, .I subdur and .I frag are expressed in given timescale (units per seconds) rather than ms .br .TP .B \-mem-frags @@ -1033,7 +1037,7 @@ * `H`, `h`: size is in hours .br .TP -.B \-split-rap,-splitr (string) +.B \-split-rap,-splitr .br split in files at each new RAP .br @@ -1137,6 +1141,11 @@ dump IsoMedia file boxes except sample tables in XML output .br .TP +.B \-keep-comp +.br +do not decompress boxes when dumping +.br +.TP .B \-keep-ods .br do not translate ISOM ODs and ESDs tags (debug purpose only) @@ -1414,7 +1423,7 @@ .br .br -If you wish to create an interleaved new file with no temporary storage, use the .I -newfs option. The interleaving might not be as precise as when using .I new since it is dependent on multiplexer input scheduling (each execution might lead to a slightly different result). Additionally in this mode: +If you wish to create an interleaved new file with no temporary storage, use the .I -newfs option. The interleaving might not be as precise as when using .I -new since it is dependent on multiplexer input scheduling (each execution might lead to a slightly different result). Additionally in this mode: .br - Some multiplexing options (marked with X below) will be activated for all inputs (e.g. it is not possible to import one AVC track with xps_inband and another without). .br @@ -1462,6 +1471,10 @@ .br .br +When importing as an external track, only options marked as E can be used. +.br + +.br Allowed per-file options: .br @@ -1469,7 +1482,7 @@ .TP .B dur (int) .br -XC import only the specified duration from the media. Value can be: +XCE import only the specified duration from the media. Value can be: .br * positive float: specifies duration in seconds .br @@ -1485,12 +1498,12 @@ .TP .B lang (string) .br -S set imported media language code +SE set imported media language code .br .TP .B delay (int) .br -S set imported media initial delay (>0) or initial skip (<0) in ms or as fractional seconds (N/D) +SE set imported media initial delay (>0) or initial skip (<0) in ms or as fractional seconds (N/D) .br .TP .B par (string) @@ -1505,7 +1518,7 @@ .TP .B mx (string) .br -S set track matrix (see .I -mx ) +SE set track matrix (see .I -mx ) .br .TP .B name (string) @@ -1530,17 +1543,17 @@ .TP .B tkhd (int) .br -S set track header flags has hex integer or as comma-separated list of enable, movie, preview, size_ar keywords (use tkhd+=FLAGS to add and tkhd-=FLAGS to remove) +SE set track header flags has hex integer or as comma-separated list of enable, movie, preview, size_ar keywords (use tkhd+=FLAGS to add and tkhd-=FLAGS to remove) .br .TP .B disable .br -S disable imported track(s), use disable=no to force enabling a disabled track +SE disable imported track(s), use disable=no to force enabling a disabled track .br .TP .B group (int) .br -S add the track as part of the G alternate group. If G is 0, the first available GroupID will be picked +SE add the track as part of the G alternate group. If G is 0, the first available GroupID will be picked .br .TP .B fps (string) @@ -1580,7 +1593,7 @@ .TP .B nodrop .br -same as .I nodrop +same as .I no-drop .br .TP .B packed @@ -1669,7 +1682,7 @@ .br * merge: all layers are merged in a single track .br - * splitbase: all layers are merged in a track, and the AVC base in another + * splitbase: all layers are merged in a track, and the base in another .br * splitnox: each layer is in its own track, and no extractors are written .br @@ -1704,7 +1717,7 @@ .TP .B forcesync .br -force non IDR samples with I slices (OpenGOP or GDR) to be marked as sync points +SE force non IDR samples with I slices (OpenGOP or GDR) to be marked as sync points .br Warning: RESULTING FILE IS NOT COMPLIANT WITH THE SPEC but will fix seeking in most players .br @@ -1751,22 +1764,22 @@ .TP .B chap .br -S specify the track is a chapter track +SE specify the track is a chapter track .br .TP .B chapter (string) .br -S add a single chapter (old nero format) with given name lasting the entire file +SE add a single chapter (old nero format) with given name lasting the entire file .br .TP .B chapfile (string) .br -S add a chapter file (old nero format) +SE add a chapter file (old nero format) .br .TP .B layout (string) .br -S specify the track layout as WxHxXxYxLAYER. If W (resp H) is 0, the max width (resp height) of the tracks in the file are used +SE specify the track layout as WxHxXxYxLAYER. If W (resp H) is 0, the max width (resp height) of the tracks in the file are used .br .TP .B rescale (int) @@ -1786,7 +1799,7 @@ .TP .B moovts (int) .br -S set movie timescale to TS. A negative value picks the media timescale of the first track imported +SE set movie timescale to TS. A negative value picks the media timescale of the first track imported .br .TP .B noedit @@ -1918,7 +1931,7 @@ .TP .B kind (string) .br -S set kind for the track as schemeURI=value +SE set kind for the track as schemeURI=value .br .TP .B txtflags (int) @@ -2001,7 +2014,7 @@ .TP .B tc (string) .br -S inject a single QT timecode. Value is formatted as: +SE inject a single QT timecode. Value is formatted as: .br * dFPS/FPS_den,h,m,s,f,framespertick: optional drop flag, framerate (integer or fractional), hours, minutes, seconds and frame number .br @@ -2012,7 +2025,7 @@ .TP .B edits (string) .br -S override edit list, same syntax as .I edits +SE override edit list, same syntax as .I -edits .br .TP .B lastsampdur (string) @@ -2028,7 +2041,7 @@ .TP .B ID (int) .br -S set target ID +SE set target ID .br - a value of 0 (default) will try to keep source track ID .br @@ -2039,12 +2052,28 @@ .TP .B tkgp (string) .br -S assign track group to track. Value is formatted as TYPE,N with TYPE the track group type (4CC) and N the track group ID. A negative ID removes from track group ID -N +SE assign track group to track. Value is formatted as TYPE,N with TYPE the track group type (4CC) and N the track group ID. A negative ID removes from track group ID -N .br .TP .B tkidx (string) .br -S set track position in track list, 1 being first track in file +SE set track position in track list, 1 being first track in file +.br +.TP +.B extk +.br +CE add track as external track +.br +.TP +.B times (string) +.br +SE modify timestamps using timestamp file specified in value. Timestamp file is formatted as: +.br + - a line starting with # is a comment, in which timescale=V can be used to set timescale (1000 by default) +.br + - empty lines are ignored +.br + - one line per sample in decode order, formated as cts or dts cts .br .TP .B stats,-fstat @@ -2279,7 +2308,7 @@ .TP .B \-iod .br -prevent systems tracks embedding in IOD (MPEG-4 Systems), not compatible with .I isma +prevent systems tracks embedding in IOD (MPEG-4 Systems), not compatible with .I -isma .br .SH MPEG-4 Scene Encoding Options .LP @@ -2290,7 +2319,7 @@ .br Any media track specified through a MuxInfo element will be imported in the resulting MP4 file. .br -See https://wiki.gpac.io/MPEG-4-BIFS-Textual-Format and related pages. +See https://wiki.gpac.io/Howtos/scenecoding/MPEG-4-BIFS-Textual-Format and related pages. .br .SS Scene Random Access .br @@ -2383,7 +2412,7 @@ .br MP4Box supports encryption and decryption of ISMA, OMA and CENC content, see encryption filter `gpac -h cecrypt`. .br -It requires a specific XML file called CryptFile, whose syntax is available at https://wiki.gpac.io/Common-Encryption +It requires a specific XML file called CryptFile, whose syntax is available at https://wiki.gpac.io/xmlformats/Common-Encryption .br Image files (HEIF) can also be crypted / decrypted, using CENC only. .br @@ -2445,11 +2474,11 @@ .br * encoding=enctype: item content-encoding type, none if not set .br -* id=ID: item ID +* id=ID: item ID (strictly positive integer) .br -* ref=4cc,id: reference of type 4cc to an other item (can be set multiple times) +* ref=4cc,id: reference of type 4cc (such as 'dimg', 'auxl', 'cdsc') to an other item (can be set multiple times) .br -* group=id,type: indicate the id and type of an alternate group for this item +* group=4cc,id: indicate the type 4cc (such as 'altr') and id of an entity group this item belongs to .br * replace: replace existing item by new item .br @@ -2458,7 +2487,7 @@ .br add the given file as HEIF image item, with parameter syntax file_path:opt1:optN. If filepath is omitted, source is the input MP4 file .br -* name, id, ref: see .I add-item +* name, id, ref, group: see .I add-item .br * primary: indicate that this item should be the primary item .br @@ -2512,7 +2541,7 @@ .br * auxd=FILE: use data from FILE as auxiliary extensions (cf auxC box) .br -- any other options will be passed as options to the media importer, see .I add +- any other options will be passed as options to the media importer, see .I -add .br .TP .B \-add-derived-image (string) @@ -2646,6 +2675,16 @@ .br Special tag value clear (or reset) removes all tags. .br +Special tag value cust indicates a custom domain tag, in which case the tag value must start with DOMAIN,MEAN,. +.br +Example +.br +-itags cust='com.apple.iTunes,iTunEXTC,My Tag Value' +.br + +.br +The DOMAIN and/or NAME strings can be empty. +.br Unsupported tags can be added using their four character code as a tag name, and string value will be assumed. .br If the tag name length is 3, the prefix 0xA9 is used to create the four character code. @@ -2718,29 +2757,29 @@ .br .SS Supported tag names (name, value, type, aliases) .br -title (A9nam) string (alias name) +title (©nam) string (alias name) .br -artist (A9ART) string +artist (©ART) string .br album_artist (aART) string (alias albumArtist) .br -album (A9alb) string +album (©alb) string .br -group (A9grp) string (alias grouping) +group (©grp) string (alias grouping) .br -composer (A9com) string +composer (©com) string .br -writer (A9wrt) string +writer (©wrt) string .br -conductor (A9con) string +conductor (©con) string .br -comment (A9cmt) string (alias comments) +comment (©cmt) string (alias comments) .br genre (gnre) string (ID3 genre tag) .br -created (A9day) string (alias releaseDate) +created (©day) string (alias releaseDate) .br -track (A9trk) string +track (©trk) string .br tracknum (trkn) fraction (syntax: A/B or A, B will be 0) .br @@ -2764,7 +2803,7 @@ .br ldesc (ldes) string (alias longDescription) .br -lyrics (A9lyr) string +lyrics (©lyr) string .br sort_name (sonm) string (alias sortName) .br @@ -2782,9 +2821,9 @@ .br copyright (cprt) string .br -tool (A9too) string (alias encodingTool) +tool (©too) string (alias encodingTool) .br -encoder (A9enc) string (alias encodedBy) +encoder (©enc) string (alias encodedBy) .br pdate (purd) string (alias purchaseDate) .br @@ -2804,49 +2843,49 @@ .br gapless (pgap) bool (yes or no) .br -art_director (A9ard) string +art_director (©ard) string .br -arranger (A9arg) string +arranger (©arg) string .br -lyricist (A9aut) string +lyricist (©aut) string .br -acknowledgement (A9cak) string +acknowledgement (©cak) string .br -song_description (A9des) string +song_description (©des) string .br -director (A9dir) string +director (©dir) string .br -equalizer (A9equ) string +equalizer (©equ) string .br -liner (A9lnt) string +liner (©lnt) string .br -record_company (A9mak) string +record_company (©mak) string .br -original_artist (A9ope) string +original_artist (©ope) string .br -phono_rights (A9phg) string +phono_rights (©phg) string .br -producer (A9prd) string +producer (©prd) string .br -performer (A9prf) string +performer (©prf) string .br -publisher (A9pub) string +publisher (©pub) string .br -sound_engineer (A9sne) string +sound_engineer (©sne) string .br -soloist (A9sol) string +soloist (©sol) string .br -credits (A9src) string +credits (©src) string .br -thanks (A9thx) string +thanks (©thx) string .br -online_info (A9url) string +online_info (©url) string .br -exec_producer (A9xpd) string +exec_producer (©xpd) string .br -genre (A9gen) string (ID3 genre tag) +genre (©gen) string (ID3 genre tag) .br -location (A9xyz) string +location (©xyz) string .br .SH Live Scene Encoder Options .LP @@ -2934,16 +2973,16 @@ .br .SH EXAMPLES .TP -Basic and advanced examples are available at https://wiki.gpac.io/MP4Box +Basic and advanced examples are available at https://wiki.gpac.io/MP4Box/MP4Box .SH MORE .LP Authors: GPAC developers, see git repo history (-log) .br For bug reports, feature requests, more information and source code, visit https://github.com/gpac/gpac .br -build: 2.4 +build: 26.02 .br -Copyright: (c) 2000-2023 Telecom Paris distributed under LGPL v2.1+ - https://gpac.io +Copyright: (c) 2000-2024 Telecom Paris distributed under LGPL v2.1+ - https://gpac.io .br .SH SEE ALSO .LP
View file
gpac-26.02.0.tar.gz/share/doc/versions.html
Added
@@ -0,0 +1,14 @@ +<html> +<head> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +<link href="gpacstylesheet.css" rel="stylesheet" type="text/css"/> +</head> +<body style="height: 100%;background-color: var(--title-background-color); overflow:hidden;"> +<base target="_parent" /> + +<p style="text-align: right;"> +Versions: <a href="/">latest</a> + +</p> +</body> +</html>
View file
gpac-2.4.0.tar.gz/share/emscripten/gpac.html -> gpac-26.02.0.tar.gz/share/emscripten/gpac.html
Changed
@@ -250,7 +250,6 @@ onkeypress=" if (event.keyCode == 13) { if (!is_custfs) { - var cwd = FS.cwd() == '/' ? '~' : FS.cwd(); document.getElementById('output').innerHTML += this.value + '<br>'; } process_command(); @@ -351,7 +350,7 @@ <h2>Console usage</h2> <p>This page allows running the <strong>MP4Box</strong> and <strong>GPAC</strong> applications in web browsers.</p> <p>The webassembly build is experimental. It tries to uses webcodecs whenever decoding or encoding is involved. If webcodecs can't be used, it falls back to software implementation running in wasm.</p> - <p>Select an exemple and make edits, or type in your own command. Finally press <code>Enter</code> key or click the <code>Run</code> button.</p> + <p>Select an example and make edits, or type in your own command. Finally press <code>Enter</code> key or click the <code>Run</code> button.</p> <p>Remote <strong>DASH</strong> and <strong>HLS</strong> playlists can be used as input, provided that they are hosted with proper <strong>CORS</strong> configurations.</p> <p>The console also provides basic shell file system, commands to import media export media files, and support advanced gpac configuration, as described below:</p> <h3> @@ -479,6 +478,7 @@ let fsess = null; let prev_filter = null; let defer_mode = false; + let logs_val = null; let suspend_sess = false; let fs_steps = 20; let FS = null; @@ -663,7 +663,7 @@ s = FS.analyzePath('/idbfs/.gpac/history.txt', true); if (s.error) return; let strs = FS.readFile('/idbfs/.gpac/history.txt', { encoding: "utf8"}).split('\n'); - strs.forEach(s => { if (s.length) cmd_history.push(s); }); + strs.forEach(s => { if (s.length && (cmd_history.indexOf(s)<0)) cmd_history.push(s); }); } //save command history to /idbfs/.gpac/history.txt function save_history() { @@ -719,7 +719,7 @@ setCommand(''); } - const onCommandRun = (cmd) => { + function onCommandRun(cmd) { // reset select example selCmdElement.value = -1; selCmdElement.hidden = true; @@ -728,6 +728,10 @@ set_status(''); }; + function expandFilePath(fp) { + return fp.startsWith('/') ? fp : `${FS.cwd()}/${fp}` + } + //our basic terminal emulation function process_command() { const cmd = cmdElement.value; @@ -753,6 +757,7 @@ if (is_custfs) { run_prompt(cmd); + setCommand(''); return; } @@ -789,33 +794,41 @@ } //save history + let cmd_idx = cmd_history.indexOf(cmd); + if (cmd_idx>=0) { + cmd_history.splice(cmd_idx, 1); + } cmd_history.push(cmd); save_history(); cmd_idx = -1; //file or directory import if (args0=='imp') { - let is_dir = false; - let is_mdir = false; + + let is_mdir = false; // import directory content + let file_name = null; + let is_dir = false; // import into directory ? + args.shift(); + if (args.length && (args0=='-d')) { is_mdir = true; args.shift(); } - let file_name = null; - if (args.length) file_name = args0; - - if (file_name && (file_name.charAt(file_name.length-1) == '/')) - is_dir = true; - - if (file_name && !is_dir && is_mdir) { - file_name += '/'; - } - if (is_mdir) is_dir=true; - - if (file_name) + + if (args.length){ + file_name = args0; + is_dir = (file_name.charAt(file_name.length-1) == '/'); + if (is_mdir && !is_dir) { + file_name += '/'; + } + file_name = expandFilePath(file_name); check_file_dir(file_name, is_dir); + } + if (is_mdir) + is_dir=true; + let input = document.createElement('input'); input'type' = 'file'; if (!is_mdir) { @@ -839,7 +852,7 @@ fname += name; } else { //copy as is - fname = file.webkitRelativePath; + fname = file.webkitRelativePath; } } else { fname += file.name; @@ -848,6 +861,7 @@ if (!fname) fname = file.name; read.addEventListener('loadend', (e) => { const uint8_view = new Uint8Array(read.result); + fname = expandFilePath(fname); if (is_mdir) check_file_dir(fname, false); FS.writeFile(fname, uint8_view); }); @@ -1030,6 +1044,7 @@ if (args0=='fs') { set_status('Starting interactive graph builder'); set_text(''); + setCommand(''); defer_mode=true; fs_steps=20; is_custfs = true; @@ -1126,9 +1141,11 @@ LIBGPAC.fs_del(fsess); fsess=null; LIBGPAC.uninit(); - set_status("leaving interactive mode"); } - return; + set_status("leaving interactive mode"); + set_text(''); + setCommand(''); + return com_exit(); } if (args0=='d') { if (!fsess) { @@ -1137,23 +1154,35 @@ } return; } + if (args0=='l') { + logs_val = args1; + return; + } + if (args0=='s') { if (args.length>1) { fs_steps = parseInt(args1); - if (fs_steps<=0) fs_steps=1; - set_status("will use " + fs_steps + " steps per callback"); + if (fs_steps==0) fs_steps=1; + if (fs_steps<0) + set_status("will use blocking mode"); + else + set_status("will use " + fs_steps + " steps per callback"); } return; } if (args0=='a') { if (args.length<2) return set_status('Invalid add command'); if (!fsess) { - let flag = 1<<2; //always run async + let flag = 0; + if (fs_steps>0) flag |= 1<<2; if (defer_mode) flag |= (1<<13); LIBGPAC.init(0, null); + if (logs_val) + LIBGPAC.log_set_tools_levels(logs_val, 1); fsess = LIBGPAC.fs_new_defaults(flag); - set_status("Creating filter session in non-blocking mode" + (defer_mode ? ' - deferred linking' : '') ); + set_status("Creating filter session in " + ((fs_steps<0) ? "" : "non-") + "blocking mode" + (defer_mode ? ' - deferred linking' : '') ); + if (fs_steps<0) fs_steps=1; //for run_session } if (prev_filter && defer_mode) { LIBGPAC.filter_reconnect_output(prev_filter, null); @@ -1185,7 +1214,7 @@ prev_filter = f; let res = LIBGPAC.getValue(e, 'i32'); LIBGPAC._free(e); - set_status("added filter "+args1+ " returned " + res); + set_status("added filter "+args1+ " returned: " + LIBGPAC.e2s(res)); return; } if (args0=='r') { @@ -1201,6 +1230,10 @@ } return; } + if (args0=='clear') { + if (outputElement) outputElement.innerText = ''; + return; + } if (args0=='p') { @@ -1221,10 +1254,13 @@ var c_str = LIBGPAC.getValue(p_str, "i32"); var chain = LIBGPAC.UTF8ToString(c_str); LIBGPAC.free(c_str); - set_text('resolved chain from ' + LIBGPAC.filter_get_name(prev_filter) + ' to ' + n + ' is: ' + chain); + if (chain.length) + set_text('resolved chain from ' + LIBGPAC.filter_get_name(prev_filter) + ' to ' + n + ' is: ' + chain); + else + set_text('Direct connection from from ' + LIBGPAC.filter_get_name(prev_filter) + ' to ' + n + ' is possible'); } LIBGPAC._free(p_str); - return set_status(res ? ('Failed'+res) : 'OK'); + return set_status(res ? ('Failed: '+ LIBGPAC.e2s(res) ) : 'OK'); } if (args0=='l') { let idx=-1; @@ -1284,11 +1320,13 @@ let help="Possible commands:\n"; help+= "- d: set defer linking mode (default: "+ defer_mode + ")\n"; - help+= "- s: set number of steps before returning control to JS (default: "+ fs_steps + ")\n"; + help+= "- s: set number of steps before returning control to JS, use -1 for blocking mode (default: "+ fs_steps + ")\n"; + help+= "- l LT: set log tools and levels, same syntax as -logs\n"; help+= "- a DESC: add filter, use src=URL for source, dst=URL for sinks. $(name) replacement rules are allowed\n"; help+= "- r: run or resume session\n"; help+= "- c: suspend session while running\n"; help+= "- v: view connections and stats\n"; + help+= "- q: exit custom mode\n"; help+= "\nThe following only apply to the last added filter:\n"; help+= "- i: print pid properties\n"; help+= "- p pid_idx=0 DESC: probe resolution from pid with index pid_idx of last added filter to filter description DESC\n"; @@ -1324,12 +1362,7 @@ GPAC.stack = LIBGPAC.stackSave(); args.unshift(is_mp4box ? "MP4Box" : "gpac"); var argc = args.length; - var argv = LIBGPAC.stackAlloc((argc + 1) * 4); - var argv_ptr = argv >> 2; - args.forEach(arg => { - LIBGPAC.HEAP32argv_ptr++ = LIBGPAC.allocateUTF8OnStack(arg); - }); - LIBGPAC.HEAP32argv_ptr = 0; + var argv = LIBGPAC.stringsToPtr(args); try { LIBGPAC"_main"(argc, argv); @@ -1549,7 +1582,7 @@ // It is not optimized but can help you design your own fileIOs for custom GPAC@WASM integration const consolePrefix = () => { - return "<span class=\"cwd\">~"+FS.cwd()+"</span>$ "; + return "<span class=\"cwd\">"+FS.cwd()+"</span>$ "; }; //check support for fetch upload using readable stream @@ -2255,6 +2288,7 @@ _lib.free = LIBGPAC.cwrap('gf_url_free', null, 'number'); _lib.log_set_tools_levels = LIBGPAC.cwrap('gf_log_set_tools_levels', 'number', 'string', 'number'); + _lib.e2s = LIBGPAC.cwrap('gf_error_to_string', 'string', 'number'); //load basic filter session api _lib.fs_new_defaults = LIBGPAC.cwrap('gf_fs_new_defaults', 'number', 'number'); @@ -2555,15 +2589,14 @@ }; </script> - <!-- load module - default emscripten compil is with MODULARIZE and export name "libgpac" --> - <script async type="text/javascript" src="gpac.js"></script> - <script> + <!-- load module - default emscripten compilation is with MODULARIZE, EXPORT_ES6 and export name "libgpac" --> + <script type="module" async> + import libgpac from './gpac.js'; + window.addEventListener('load', function() { libgpac(LIBGPAC); - }); </script> - </body> </html>
View file
gpac-26.02.0.tar.gz/share/emscripten/gpac_pre.js
Added
@@ -0,0 +1,76 @@ +/** + * Constants + */ + +const SIZE_I32 = Uint32Array.BYTES_PER_ELEMENT; +const DEFAULT_ARGS = ; + +Module"SIZE_I32" = SIZE_I32; +Module"DEFAULT_ARGS" = DEFAULT_ARGS; +Module"logger" = () => {}; +Module"noInitialRun" = true; +Module"noExitRuntime" = true; + +/** + * Functions + */ + +function stringToPtr(str) { + const len = Module"lengthBytesUTF8"(str) + 1; + const ptr = Module"_malloc"(len); + Module"stringToUTF8"(str, ptr, len); + + return ptr; +} + +function stringsToPtr(strs) { + const len = strs.length; + const ptr = Module"_malloc"(len * SIZE_I32); + for (let i = 0; i < len; i++) { + Module"setValue"(ptr + SIZE_I32 * i, stringToPtr(strsi), "i32"); + } + + return ptr; +} + +function print(message) { + Module"logger"({ type: "stdout", message }); +} + +function printErr(message) { + Module"logger"({ type: "stderr", message }); +} + +function exec(tool, ..._args) { + const args = tool, ...Module"DEFAULT_ARGS", ..._args; + try { + Module"_main"(args.length, stringsToPtr(args)); + } catch (e) { + if (e !== "unwind") { + throw e; + } + } +} + +function _locateFile(path, prefix) { + const hashedWasmBinaryFile = Module"hashedWasmBinaryFile"; + if (hashedWasmBinaryFile && path.endsWith(".wasm")) { + return hashedWasmBinaryFile; + } + return prefix + path; +} + +function setLogger(logger) { + Module"logger" = logger; +} + +Module"stringToPtr" = stringToPtr; +Module"stringsToPtr" = stringsToPtr; +Module"locateFile" = _locateFile; +Module"exec" = exec; + +if (libgpac.use_logger) { + Module"print" = print; + Module"printErr" = printErr; + Module"setLogger" = setLogger; +}
View file
gpac-2.4.0.tar.gz/share/gui/extensions/about/info.js -> gpac-26.02.0.tar.gz/share/gui/extensions/about/info.js
Changed
@@ -5,7 +5,7 @@ str += '\nMore info: https://gpac.io'; str += '\nDistributed under LGPL v2.1 or any later version'; str += '\n(c) 2002-2020 Telecom Paris'; - str += '\n\nThanks to all great OSS tools used in GPAC:\nQuickJS, FreeType, FFMPEG, OpenHEVC, libjpeg, libpng, faad2, libmad, SDL, ...\n\n'; + str += '\n\nThanks to all great OSS tools used in GPAC:\nQuickJS, FreeType, FFmpeg, OpenHEVC, libjpeg, libpng, faad2, libmad, SDL, ...\n\n'; notif = gw_new_message(null, 'About GPAC', str, 0); notif.set_size(gw_display_width, gw_display_height); notif.set_alpha(0.8);
View file
gpac-2.4.0.tar.gz/share/gui/extensions/player/stats.js -> gpac-26.02.0.tar.gz/share/gui/extensions/player/stats.js
Changed
@@ -103,6 +103,8 @@ label += 'Bandwidth: ' + q.bandwidth; label += '\n'; label += 'Enabled: ' + !q.disabled; + if (q.ssr) + label += ' - SSR Parts ' + q.ssr; label += '\n'; if (q.width) { label += 'Size ' + q.width + 'x' + q.height + (q.interlaced ? ' interlaced @' : ' progressive @') + q.fps + ' FPS - SAR ' + q.par_num + '/' + q.par_den;
View file
gpac-2.4.0.tar.gz/share/gui/gwlib.js -> gpac-26.02.0.tar.gz/share/gui/gwlib.js
Changed
@@ -605,7 +605,7 @@ s.font = gw_new_fontstyle(gwskin.default_label_font_size, 1); s.font.skin = true; -//overrite style for button to have its own text color +//overwrite style for button to have its own text color s = { name: 'button' }; gwskin.styles.push(s); s.text = gw_new_appearance(1, 1, 1);
View file
gpac-2.4.0.tar.gz/share/lang/fr.txt -> gpac-26.02.0.tar.gz/share/lang/fr.txt
Changed
@@ -83,31 +83,31 @@ Le nom peut être omis pour les énumérations (par exemple :disp=pbo est équivalent à :pbo), à condition que les développeurs de filtres fassent attention à ne pas réutiliser les noms d'énumérations dans un même filtre! Lorsque des paramètres textes sont utilisés (par exemple, des URL), il est recommandé d'utiliser le mot-clé "gpac" pour séparer les arguments. -EX: filtre: ARG=http://foo/bar?yes:gpac:opt=VAL +EX filtre: ARG=http://foo/bar?yes:gpac:opt=VAL Cela permettra d'extraire correctement l'URL -EX: filtre: ARG=http://foo/bar?yes:opt=VAL +EX filtre: ARG=http://foo/bar?yes:opt=VAL Cela ne parviendra pas à l'extraire l'option et conservera :opt=VAL dans l'URL Remarque: le mécanisme d'échappement n'est pas nécessaire pour les sources locales, pour lesquelles l'existence du fichier est recherchée lors de l'analyse des arguments Il n'est également pas nécessaire pour les procotoles intégrés (avin://, video://, audio://, pipe://) Pour les protocoles tcp:// et udp://, l'échappement n'est pas nécessaire si un / final est ajouté après le port -EX: -i tcp: //127.0.0.1: 1234:OPT +EX -i tcp: //127.0.0.1: 1234:OPT Cela n'arrivera pas à extraire l'URL et les options -EX: -i tcp: //127.0.0.1: 1234/:OPT +EX -i tcp: //127.0.0.1: 1234/:OPT Cela va extraire l'URL et les options Remarque: une astuce pour éviter la séquence d'échappement consiste à déclarer l'option urls à la fin, par exemple, f1:opt1=foo:url=http:// bar, à condition que vous n'ayez qu'une seule URL. Voir les arguments héritant ci-dessous. Les filtres source et destination n'ont pas besoin d'être adressés par le nom du filtre, il suffit de spécifier src= ou dst=. Vous pouvez également utiliser la syntaxe -src URL ou -i URL pour les sources et -dst URL ou -o URL pour les destinations. Cela permet d'utiliser la complétion automatique de votre invite de commande. -EX: src=fichier.mp4 EX: -src fichier.mp4 EX: -i fichier.mp4 +EX src=fichier.mp4 EX -src fichier.mp4 EX -i fichier.mp4 Ceci trouvera un filtre (par exemple "fin") capable de charger fichier.mp4 Le même résultat peut être obtenu en utilisant "fin:src=fichier.mp4" -EX: src=fichier.mp4 dst=dump.yuv +EX src=fichier.mp4 dst=dump.yuv Cela décodera le contenu vidéo de file.mp4 et l'écrira dans dump.yuv Des filtres source ou destination spécifiques peuvent également être spécifiés à l'aide de filterName:src=URL ou filterName:dst=URL. Il existe une option spéciale appelée "gfreg" qui permet de spécifier les filtres à utiliser lors de la gestion des URL. -EX: src=fichier.mp4:gfreg=ffdmx,ffdec +EX src=fichier.mp4:gfreg=ffdmx,ffdec Cela utilisera ffdmx pour gérer le fichier et ffdec pour décoder Cela peut être utilisé pour tester un filtre spécifique lorsque d'autres chaînes de filtres sont possibles. @@ -118,11 +118,11 @@ pfmt=4CC indique le format de pixel cible de la source, si supporté par le codec all_intra=BOOL indique que toutes les images doivent être intra-images, si supporté par le codec D'autres options peuvent être transmises aux filtres acceptant des arguments génériques (comme c'est le cas pour ffmpeg). -EX: src=dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true dst=raw.264 +EX src=dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true dst=raw.264 Cela encode un ficher YUV 25 fps en AVC à 175 kbps avec une durée de GOP de 2 secondes, en utilisant des paramètres de codage fast et GOP fermé de ffmpeg. Note: l'opération inverse (forcer un décodage) est possible en utilisant le filtre reframer: -EX: src=fichier.mp4 reframer:raw=av @ -o null +EX src=fichier.mp4 reframer:raw=av @ -o null Cela forcera le décodage du média à partir du fichier et jettera le résultat (par exemple, pour un benchmark du décodeur) Expliciter des liens entre les filtres LINK @@ -130,9 +130,9 @@ Le lien entre les filtres peut être spécifié manuellement. La syntaxe est un caractère '@' optionnellement suivi d'un entier (0 si omis). Cela indique quels filtres spécifiés antérieurement (le précédent étant associé à 0) doivent être liés au prochain filtre indiqué. Seule la dernière directive de liaison avant un filtre est utilisée pour configurer des liens pour ce filtre. -EX: fA fB @1 fC +EX fA fB @1 fC Ceci indique de diriger les sorties fA vers fC -EX: fA fB @1 @0 fC +EX fA fB @1 @0 fC Cela indique de diriger les sorties du fB vers le fC, @1 est ignoré Si aucune directive de lien n'est donnée, les liens seront résolus dynamiquement pour générer autant de connexions que possible. @@ -142,11 +142,11 @@ - FID=nom, qui définit l'ID de filtre - SID=nom1,nom2..., qui définit les ID sources restreignant la liste des entrées possibles pour un filtre. -EX: fA fB @1 fC +EX fA fB @1 fC Ceci est équivalent à "fA fB:FID=1 fC:SID=1" -EX: fA: FID = 1 fB fC: SID = 1 +EX fA: FID = 1 fB fC: SID = 1 Ceci indique de diriger les sorties fA vers fC -EX: fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2 +EX fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2 Cela indique de diriger les sorties fA vers fC et les sorties fA et fB vers fD REMARQUE: Un filtre avec ID source défini ne peut pas être connecté en entrée de filtres sans ID. @@ -166,42 +166,42 @@ nom#P4CC+VAL: accepte uniquement les pids avec une propriété strictement supérieure à VAL (uniquement pour les propriétés à 1 dimension). Un ID source peut aussi utiliser le charactère * pour faire correspondre une ou plusieurs propriétés, quel que soit le filtre source:\n" -EX: fA fB:SID=*#ServiceID=2 +EX fA fB:SID=*#ServiceID=2 Ceci indique fB n'accepte comme entrée que des pids avec une propriété ServiceID égale à 2, indépendemment des identifiant de filtres de ces pids. REMARQUE: Ces extensions fonctionnent également avec le raccourci LINK: -EX: fA fB @1#video fC +EX fA fB @1#video fC Ceci indique de diriger les sorties vidéo fA vers fC -EX: src=img.heif @#ItemID=200 vout +EX src=img.heif @#ItemID=200 vout Cela indique de connecter à vout uniquement les pids avec une propriété ItemID égale à 200. -EX: src=vid.mp4 @#PID=1 vout +EX src=vid.mp4 @#PID=1 vout Cela indique de connecter à vout uniquement les pids avec une propriété ID égale à 1. -EX: src=vid.mp4 @#Width=640 vout +EX src=vid.mp4 @#Width=640 vout Cela indique de connecter à vout uniquement les pids avec une propriété Width égale à 640. -EX: src=vid.mp4 @#Width-640 vout +EX src=vid.mp4 @#Width-640 vout Cela indique de connecter à vout uniquement les pids dont la propriété Width est inférieure à 640. Plusieurs fragments peuvent être spécifiés pour tester plusieurs propriétés de pid -EX: src=vid.mp4 @#Width=640#Height+380 vout +EX src=vid.mp4 @#Width=640#Height+380 vout Cela indique de connecter à vout uniquement les pids avec une largeur égale à 640 et une hauteur supérieure à 380. Notez que si un pid est connecté à un filtre chargé explicitement, aucune résolution de lien dynamique supplémentaire ne sera effectuée pour le connecter à d'autres filtres. Les directives de liaison doivent être soigneusement établies -EX: src=fichier.mp4 @ reframer dst=dump.mp4 +EX src=fichier.mp4 @ reframer dst=dump.mp4 Cela connectera le pid src (type fichier) à dst (type fichier) car dst n'a pas de sourceID et acceptera donc l'entrée de src. Comme le pid est connecté, le moteur de filtres n'essaiera pas de résoudre le lien entre src et reframer. Le résultat est une copie directe du fichier source, le reframer étant inutilisé -EX: src=fichier.mp4 reframer @ dst=dump.mp4 +EX src=fichier.mp4 reframer @ dst=dump.mp4 Cela forcera dst à n'accepter que les pids de reframer, un multiplexeur sera chargé pour résoudre ce lien et src pid sera connecté à reframer (aucun ID de source), chargeant un démultiplexeur pour résoudre le lien. Le résultat est un remux complet du fichier source Héritage d'arguments Sauf si explicitement désactivé (option -max-chain), le moteur de filtres résoudra les connexions implicites ou explicites (LINK) entre les filtres et allouera toute chaîne de filtres intermédiaires nécessaires à la connexion des filtres. Ce faisant, il charge de nouveaux filtres avec des arguments hérités à la fois de la source et de la destination. -EX: src=fichier.mp4:OPT dst=fichier.aac dst=fichier.264 +EX src=fichier.mp4:OPT dst=fichier.aac dst=fichier.264 Ceci passera les arguments ":OPT" à tous les filtres chargés entre la source et les deux destinations -EX: src=fichier.mp4 dst=fichier.aac:OPT dst=fichier.264 +EX src=fichier.mp4 dst=fichier.aac:OPT dst=fichier.264 Cela passera les arguments ":OPT" à tous les filtres chargés entre la source et la destination file.aac REMARQUE: les arguments de destination hérités sont les arguments placés APRÈS l'option dst= -EX: src=fichier.mp4 fout:OPTFOO:dst=fichier.aac:OPTBAR +EX src=fichier.mp4 fout:OPTFOO:dst=fichier.aac:OPTBAR Cela passera les arguments ":OPTBAR" à tous les filtres chargés entre la source et la destination file.aac, mais pas ":OPTFOO" Modèles d'URL de destination @@ -221,17 +221,17 @@ AUTRE: utilise la propriété du pid dont le nom correspond au nom donné (résolution de 4CC ou nom donné si aucune correspondance 4CC). L'utilisation de modèles d'URL peut être utile lors du codage de plusieurs qualités en une seule passe: -EX: src=dump.yuv:taille=640x360 vcrop:wnd=0x0x320x180 enc:c=avc:b=1M @2 enc:c=avc:b=750k dst=dump_$CropOrigin$x$Width$x$Height$.264 +EX src=dump.yuv:taille=640x360 vcrop:wnd=0x0x320x180 enc:c=avc:b=1M @2 enc:c=avc:b=750k dst=dump_$CropOrigin$x$Width$x$Height$.264 Cela créera une version recadrée de la source, codée en AVC à 1M et une version non recadrée en AVC à 750 Ko. Les sorties sont dump_0x0x320x180.264 pour la version recadrée et dump_0x0x640x360.264 pour la version non recadrée. Duplication de filtres Lorsqu'un filtre accepte une seule connexion et possède une entrée connectée, la résolution dynamique n'est plus possible pour cette destination. Il peut y avoir des cas où ce comportement est indésirable. Soit un fichier HEIF avec N items: -EX: src=img.heif dst=dump_$ItemID$.jpg +EX src=img.heif dst=dump_$ItemID$.jpg Dans ce cas, un seul item (probablement le premier déclaré dans le fichier) se connectera à la destination. Les autres items ne seront pas connectés car la destination n'accepte qu'un seul pid d'entrée. Il existe une option spéciale "clone" permettant de cloner les filtres de destination avec les mêmes arguments: -EX: src=img.heif dst=dump_$ItemID$.jpg:clone +EX src=img.heif dst=dump_$ItemID$.jpg:clone Dans ce cas, la destination sera clonée pour chaque item et tous seront exportés vers différents fichiers JPEG grâce au modèle d'URL. Modèles de chaînes de filtres @@ -239,7 +239,7 @@ Dans certains cas, le nombre de sorties désirées dépend de la source; par exemple, extraire N fichiers d'un multiplex de N services. Il est possible d'utiliser un nom de propriété de pid dans l'ID source d'un filtre avec la valeur '*'. Dans ce cas, chaque fois qu'un nouveau PID avec une nouvelle valeur pour la propriété est trouvé, le filtre avec un tel ID source sera automatiquement cloné. -EX: src=source.ts dst=fichier_$ServiceID$.mp4:SID=*#ServiceID=* +EX src=source.ts dst=fichier_$ServiceID$.mp4:SID=*#ServiceID=* Dans cet example, chaque nouvelle valeur ServiceID trouvée lors de la connexion des PID à la destination créera un nouveau fichier de destination. AVERTISSEMENT: cette fonctionnalité ne doit être utilisée qu'avec une seule propriétéà '*' par ID source, les résultats ne sont pas définis sinon. @@ -248,7 +248,7 @@ Il est possible de définir des propriétés sur des pids de sortie déclarés par un filtre. Cela permet de marquer des parties du graphe avec des propriétés différentes de celles des autres parties (par exemple, ServiceID). La syntaxe utilise le séparateur de fragments pour identifier les propriétés, par exemple #Nom=Valeur, afin de définir la propriété voulue (4cc, nom intégré ou tout nom) avec une valeur donnée. Si une propriété non prédéfinie est utilisée, la valeur sera définie sous forme de chaîne. AVERTISSEMENT: les propriétés ne sont pas filtrées et remplacent les propriétés source. Veillez à ne pas corrompre la session en substituant des propriétés essentielles telles que Width, Height, SampleRate ! -EX: -i v1.mp4:# ServiceID=4 -i v2.mp4:# ServiceID=2 -o dump.ts +EX -i v1.mp4:# ServiceID=4 -i v2.mp4:# ServiceID=2 -o dump.ts Cela multiplexera les flux dans dump.ts, en utilisant ServiceID 4 pour les pids de v1.mp4 et ServiceID 2 pour les pids de v2.mp4. Filtres externes @@ -282,7 +282,7 @@ author: outils de création (hint, import, export) sync: messages de synchro codec: messages des encodeurs/décodeurs - parser: scene parsers (svg, xmt, bt) + parser: scene parsers (svg, xmt, bt) media: gestion des objets media dans le compositor scene: arbre de scene et gestionnaire de scene script: scripts @@ -298,11 +298,11 @@ module: GPAC modules (av out, font engine, 2D rasterizer) filter: gestionnaire de filtres sched: ordonnanceur des filtres - mutex: mutex calls + mutEX mutex calls all: tous les outils enregistrés - d'autres outils peuvent être spécifiés ultérieurement. Le mot clé spécial "ncl" peut être défini pour désactiver les couleurs. Le mot clé spécial "strict" peut être défini pour quitter à la première erreur. - EX: -logs all@info:dash@debug:ncl + EX -logs all@info:dash@debug:ncl Cela passe tous les outils en niveau information, dash en niveau débogage et désactive les couleurs.@ logs_crc=3235072836 noprog=désactive les messages de progression @@ -373,7 +373,7 @@ sched=@définir le mode de l'ordonnanceur de filtres. Les modes possibles sont: free: utilise des files d'attente sans mutex, sauf pour la liste des tâches (par défaut) lock: utilise des mutex pour les files d'attente lorsque plusieurs threads - freex: utilise des files d'attente sans mutex, y compris pour les listes de tâches (expérimental) + freEX utilise des files d'attente sans mutex, y compris pour les listes de tâches (expérimental) flock: utilise des mutex pour les files d'attente même sans thread (mode débogage) direct: pas de threads et éxecution directe des tâches autant que possible (mode débogage)@ sched_crc=2961967838 @@ -439,24 +439,18 @@ font-dirs_crc=111907503 rescan-fonts=ndique que le(s) répertoire(s) des polices de charactères doit être réanalysé rescan-fonts_crc=3797274707 -rmt=active le profilage via Remotery (https://github.com/Celtoys/Remotery). Une copie du visualiseur Remotery se trouve dans gpac/share/vis, généralement installée dans /usr/share/gpac/vis ou Program Files/GPAC/vis -rmt_crc=2323200474 -rmt-port=définit le port de Remotery -rmt-port_crc=3708528921 -rmt-reuse=autorise la réutilisation de port ouvert par Remotery -rmt-reuse_crc=3607580606 -rmt-localhost=force Remotery à n'autoriser que les connections depuis localhost -rmt-localhost_crc=1239978781 -rmt-sleep=temps de veille de Remotery en ms entre deux mise à jours serveur -rmt-sleep_crc=3230696886 -rmt-nmsg=définit le nombre de messages par mise à jour pour Remotery -rmt-nmsg_crc=4237510619 -rmt-qsize=définit la taille de la queue de message pour Remotery -rmt-qsize_crc=1971622359 -rmt-log=redirige les journaux vers remotery (expérimental, généralement mal géré par le navigateur) -rmt-log_crc=3311201781 -rmt-ogl=analyse aussi les performances OpenGL pour Remotery -rmt-ogl_crc=978980191 +rmt=active le monitoring via websocket rmtws. Des exemples d'utilisation se trouvent dans gpac/share/rmtws, généralement installée dans /usr/share/gpac/rmtws ou Program Files/GPAC/rmtws +rmt_crc=1219471459 +rmt-port=définit le port de rmtws +rmt-port_crc=1480720728 +rmt-localhost=force rmtws à n'autoriser que les connections depuis localhost +rmt-localhost_crc=2774751684 +rmt-sleep=temps de veille de rmtws en ms entre deux mise à jours serveur +rmt-sleep_crc=2301762439 +rmt-cert=Fichier contenant le certificat au format PEM pour activer le mode TLS de rmtws +rmt-cert_crc=1601346270 +rmt-pkey=Fichier contenant la clé privée au format PEM pour activer le mode TLS de rmtws +rmt-pkey_crc=2436185140 properties ID=ID du flux ID_crc=131976233 @@ -542,7 +536,7 @@ PixelFormat_crc=1678811690 Stride=Largeur de ligne en octet d'un plan Y/alpha ou de l'image (formats non planaire) Stride_crc=76106405 -StrideUV=Largeur de ligne en octet d'un plan U/V +StrideUV=Largeur de ligne en octet d'un plan U/V StrideUV_crc=1964825709 BitDepthLuma=Nombre de bits pour les composants luma BitDepthLuma_crc=4028264643 @@ -654,7 +648,7 @@ SenderNTP_crc=618552461 Encrypted=Les paquets du flux sont cryptés par défaut (mais l'état de cryptage est indiqué par paquet) - les modifications sont signalées via pid_set_info (pas de reconfiguration) Encrypted_crc=18696922 -OMAPreview=Plage d'aperçu en clair pour OMA +OMAPreview=Plage d'aperçu en clair pour OMA OMAPreview_crc=112468299 CENC_PSSH=Blob binaire PSSH pour CENC, au format (u32)NbSystems (bin128)SystemID(u32)version(u32)KID_count(bin128)keyID(u32)priv_size(char*priv_size)priv_data CENC_PSSH_crc=297854741 @@ -765,7 +759,7 @@ pid.P4CC: PID property 4CC pid.PropName: PID property name -EX: fmt="PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$" dumps packet number, cts and dts, eg "PID 1 packet 10 DTS 100 CTS 108 \n" +EX fmt="PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$" dumps packet number, cts and dts, eg "PID 1 packet 10 DTS 100 CTS 108 \n" An unrecognized keywork or missing property will resolve to an empty string @@ -847,7 +841,7 @@ textxt_crc=621473674 out8b=converts 10-bit video to 8 bit texture before GPU upload. out8b_crc=2444281991 -drop=drops late frame when drawing. By default frames are not droped until a heavy desync of 1 sec is observed +drop=drops late frame when drawing. By default frames are not dropped until a heavy desync of 1 sec is observed drop_crc=909051951 sclock=forces synchronizing all streams on a single clock sclock_crc=423984299 @@ -883,11 +877,11 @@ apan_crc=485972319 async=audio resynchronization; if disabled, audio data is never dropped but may get out of sync async_crc=3135160244 -buf=playout buffer in ms. Overriden by BufferLenth property of input pid +buf=playout buffer in ms. Overridden by BufferLenth property of input pid buf_crc=2203983687 -rbuf=rebuffer trigger in ms. Overriden by RebufferLenth property of input pid +rbuf=rebuffer trigger in ms. Overridden by RebufferLenth property of input pid rbuf_crc=271064352 -mbuf=max buffer in ms (must be greater than playout buffer). Overriden by BufferMaxOccupancy property of input pid +mbuf=max buffer in ms (must be greater than playout buffer). Overridden by BufferMaxOccupancy property of input pid mbuf_crc=4276787499 ogl=specifies 2D rendering mode. When on, this will involve polygon tesselation which may not be supported on all platforms, and 2D graphics will not look as nice as 2D mode. In hybrid mode, the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all textures. The raster mode only uses OpenGL for pixel IO but does not perform polygin fill (no tesselation) (slow, mainly for test purposes). ogl_crc=3373719114 @@ -941,7 +935,7 @@ fpack_crc=2619447659 camlay=sets camera layout in multiview modes camlay_crc=4152486677 -iod=specifies the eye separation in cm (distance between the cameras). +iod=specifies the eye separation in cm (distance between the cameras). iod_crc=1720965022 rview=reverse view order rview_crc=591231777 @@ -1123,7 +1117,7 @@ oggdmx desc=OGG demuxer desc_crc=209586963 -index_dur=indexing window length (unimplemented, only 0 disables stream probing for duration), +index_dur=indexing window length (unimplemented, only 0 disables stream probing for duration), index_dur_crc=4269948406 vorbisdec desc=OGG/Vorbis decoder @@ -1171,7 +1165,7 @@ mime_crc=2546177900 block=set blocking mode for socket(s) block_crc=3267148328 -reorder_pck=number of packets delay for RTP reordering (M2TS over RTP) +reorder_pck=number of packets delay for RTP reordering (M2TS over RTP) reorder_pck_crc=2530740796 reorder_delay=number of ms delay for RTP reordering (M2TS over RTP) reorder_delay_crc=3603041699 @@ -1304,7 +1298,7 @@ pack_nal_crc=2589428755 xps_inband=Use inband param set for AVC/HEVC/.... If mix, creates non-standard files using single sample entry with first PSs found, and moves other PS inband xps_inband_crc=131735851 -store=Write mode. inter uses cdur to interleave the file. flat writes a flat file, file at end. cap flushes to disk as soon as samples are added. tight uses per-sample interleaving of all tracks. frag framents the file using cdur duration. sfrag framents the file using cdur duration but adjusting to start with SAP1/3. +store=Write mode. inter uses cdur to interleave the file. flat writes a flat file, file at end. cap flushes to disk as soon as samples are added. tight uses per-sample interleaving of all tracks. frag framents the file using cdur duration. sfrag framents the file using cdur duration but adjusting to start with SAP1/3. store_crc=809072118 cdur=chunk duration for interleaving and fragmentation modes cdur_crc=3889103669 @@ -1545,7 +1539,7 @@ In regular mode, the filter will dump to file incomming packets (stream type file), starting a new file for each packet having a start block set, unless operating in cat mode. The output file name can use gpac templating mechanism, see gpac help.@ help_crc=606079466 -dst=location of destination file - see filter help +dst=location of destination file - see filter help dst_crc=2944539646 append=open in append mode append_crc=3838208913 @@ -1802,9 +1796,9 @@ desc_crc=869983974 help=@GPAC TS multiplexer selects M2TS PID for media streams using the PID of the PMT plus the stream index. For example, default config creates the first program with a PMT PID 100, the first stream will have a PID of 101. -Streams are grouped in programs based on input PID property ServiceID if present. If absent, stream will go in program with service ID inidcated by sid option -name option is overriden by input PID property ServiceName -provider option is overriden by input PID property ServiceProvider +Streams are grouped in programs based on input PID property ServiceID if present. If absent, stream will go in program with service ID indicated by sid option +name option is overridden by input PID property ServiceName +provider option is overridden by input PID property ServiceProvider The temi option allows specifying a list of URLs or timeline IDs to insert in the program. Only a single TEMI timeline can be specified per PID. @@ -1837,7 +1831,7 @@ pmt_version_crc=4041161259 disc=sets the discontinuity marker for the first packet of each stream disc_crc=2802223434 -repeat_rate=interval in ms between two carousel send for MPEG-4 systems. Is overriden by carousel duration PID property if defined +repeat_rate=interval in ms between two carousel send for MPEG-4 systems. Is overridden by carousel duration PID property if defined repeat_rate_crc=468619753 repeat_img=interval in ms between resending (as PES) of single-image streams. If 0, image data is sent once only repeat_img_crc=2366709629 @@ -1884,10 +1878,10 @@ desc_crc=839712355 help=@GPAC DASH and HLS segmenter The segmenter uses template strings to derive output file names, regardless of the DASH mode (even when templates are not used) -The default template is $File$_dash for ondemand and single file modes, and $File$_$Number$ for seperate segment files - EX: template=Great_$File$_$Width$_$Number$ +The default template is $File$_dash for ondemand and single file modes, and $File$_$Number$ for separate segment files + EX template=Great_$File$_$Width$_$Number$ If source is foo.mp4 with 640x360 video, this will resolve in Great_foo_640_$Number$ for the DASH template - EX: template=Great_$File$_$Width$ + EX template=Great_$File$_$Width$ If source is foo.mp4 with 640x360 video, this will resolve in Great_foo_640.mp4 for onDemand case Standard DASH replacement strings @@ -1916,29 +1910,29 @@ Template: overrides dasher template for this PID DashDur: overrides dasher segment duration for this PID StartNumber: sets the start number for the first segment in the PID, default is 1 - Non-dash properties: Bitrate, SAR, Language, Width, Height, SampleRate, NumChannels, Language, ID, DependencyID, FPS, Interlaced. These properties are used to setup each representation and can be overriden on input PIDs using the general PID property settings (cf global help). - EX: src=test.mp4:#Bitrate=1M dst=test.mpd + Non-dash properties: Bitrate, SAR, Language, Width, Height, SampleRate, NumChannels, Language, ID, DependencyID, FPS, Interlaced. These properties are used to setup each representation and can be overridden on input PIDs using the general PID property settings (cf global help). + EX src=test.mp4:#Bitrate=1M dst=test.mpd This will force declaring a bitrate of 1M for the representation, regardless of actual source bitrate - EX: src=muxav.mp4 dst=test.mpd + EX src=muxav.mp4 dst=test.mpd This will create unmuxed DASH segments - EX: src=muxav.mp4:#Representation=1 dst=test.mpd + EX src=muxav.mp4:#Representation=1 dst=test.mpd This will create muxed DASH segments - EX: src=m1.mp4 src=m2.mp4:#Period=Yep dst=test.mpd + EX src=m1.mp4 src=m2.mp4:#Period=Yep dst=test.mpd This will put src m1.mp4 in first period, m2.mp4 in second period - EX: src=m1.mp4:#BUrl=http://foo/bar dst=test.mpd + EX src=m1.mp4:#BUrl=http://foo/bar dst=test.mpd This will assign a base URL to src m1.mp4 - EX: src=m1.mp4:#ASCDesc=<ElemName val="attval">text</ElemName> dst=test.mpd + EX src=m1.mp4:#ASCDesc=<ElemName val="attval">text</ElemName> dst=test.mpd This will assign the specified XML descriptor to the adaptation set. Note that this can be used to inject most DASH descriptors not natively handled by the dasher The dasher handles the XML descriptor as a string and does not attempt to validate it. Descriptors, as well as some dasher filter arguments, are string lists (comma-separated by default), so that multiple descriptors can be added: - EX: src=m1.mp4:#RDesc=<Elem attribute="1"/>,<Elem2>text</Elem2> dst=test.mpd + EX src=m1.mp4:#RDesc=<Elem attribute="1"/>,<Elem2>text</Elem2> dst=test.mpd This will insert two descriptors in the representation(s) of m1.mp4 - EX: src=video.mp4:#Template=foo$Number$ src=audio.mp4:#Template=bar$Number$ dst=test.mpd + EX src=video.mp4:#Template=foo$Number$ src=audio.mp4:#Template=bar$Number$ dst=test.mpd This will assign different templates to the audio and video sources. - EX: src=null:#xlink=http://foo/bar.xml:#PDur=4 src=m.mp4:#PStart=-1 + EX src=null:#xlink=http://foo/bar.xml:#PDur=4 src=m.mp4:#PStart=-1 This will insert an create an MPD with first a remote period then a regular one - EX: src=null:#xlink=http://foo/bar.xml:#PStart=6 src=m.mp4 + EX src=null:#xlink=http://foo/bar.xml:#PStart=6 src=m.mp4 This will insert an create an MPD with first a regular period, dashing ony 6s of content, then a remote one The dasher will request muxing filter chains for each representation and will reassign PID IDs @@ -2113,7 +2107,7 @@ Data format of the pipe should be specified using extension (either in file name or through ext option) or mime type. If neither is set, data format probing will be done -On Windows hosts, the default pipe prefix is "\\.\pipe\gpac\" if no prefix is set +On Windows hosts, the default pipe prefix is "\\.\pipe\gpac\" if no prefix is set EX dst=mypipe resolves in \\.\pipe\gpac\mypipe EX dst=\\.\pipe\myapp\mypipe resolves in \\.\pipe\myapp\mypipe Any destination name starting with \\ is used as is, with \ translated in / @@ -2151,7 +2145,7 @@ Data format of the pipe must currently be specified using extension (either in filename or through ext option) or mime type The pipe name indicated in dst can use template mechanisms from gpac, e.g. dst=pipe_$ServiceID$ -On Windows hosts, the default pipe prefix is "\\.\pipe\gpac\" if no prefix is set +On Windows hosts, the default pipe prefix is "\\.\pipe\gpac\" if no prefix is set EX dst=mypipe resolves in \\.\pipe\gpac\mypipe EX dst=\\.\pipe\myapp\mypipe resolves in \\.\pipe\myapp\mypipe Any destination name starting with \\ is used as is, with \ translated in / @@ -2191,7 +2185,7 @@ The header/tunein packet may get quite big when all pid properties are kept. In order to help reduce its size, the minp option can be used: this will remove all built-in properties marked as dropable (cf property help) as well as all non built-in properties. The skp option may also be used to specify which property to drop: - EX: skp="4CC1,Name2 + EX skp="4CC1,Name2 This will remove properties of type 4CC1 and properties (built-in or not) of name Name2 @ @@ -2245,16 +2239,16 @@ help=@This filter handles generic output sockets (mono-directionnal) in blocking mode only. The filter can work in server mode, waiting for source connections, or can directly connect In server mode, the filter can be instructed to keep running at the end of the stream -In server mode, the default behavior is to keep input packets when no more clients are connected; this can be adjusted though the kp option, however there is no realtime regulation of how fast packets are droped. +In server mode, the default behavior is to keep input packets when no more clients are connected; this can be adjusted though the kp option, however there is no realtime regulation of how fast packets are dropped. If your sources are not real time, consider adding a real-time scheduler in the chain (cf reframer filter), or set the send rate option Your platform supports unix domain sockets, use tcpu://NAME and udpu://NAME The socket output can be configured to drop or revert packet order for test purposes. In both cases, a window size in packets is specified as the drop/revert fraction denominator, and the index of the packet to drop/revert is given as the numerator If the numerator is 0, a packet is randomly chosen in that window - EX: :pckd=4/10 + EX :pckd=4/10 This drops every 4th packet of each 10 packet window - EX: :pckr=0/100 + EX :pckr=0/100 This reverts the send order of one random packet in each 100 packet window @
View file
gpac-2.4.0.tar.gz/share/nodejs/src/gpac_napi.c -> gpac-26.02.0.tar.gz/share/nodejs/src/gpac_napi.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2021-2024 + * Copyright (c) Telecom ParisTech 2021-2025 * All rights reserved * * This file is part of GPAC / NodeJS module @@ -44,13 +44,15 @@ u32 nb_args; char **argv; - napi_async_context rmt_ctx; - napi_ref rmt_ref; - u32 main_thid; - GF_List *rmt_messages; + + napi_async_context rmt_ctx; + GF_List *rmt_new_clients; + GF_List* rmt_task_calls; + GF_List* rmt_delete_calls; struct _napi_session *fs_rmt_handler; - Bool rmt_task_scheduled; + + } GPAC_NAPI; #define GF_SETUP_ERROR 0 @@ -138,6 +140,8 @@ napi_type_tag fileio_tag = {0x475041434e4f4445, 0x474646494c45494f}; napi_type_tag httpsess_tag = {0x475041434e4f4445, 0x474646494cf0df5c}; +napi_type_tag rmtclient_tag = {0x475041434e4f4445, 0x174646494cf0df5c}; + napi_value dashin_bind(napi_env env, napi_callback_info info); void dashin_detach_binding(NAPI_Filter *napi_f); @@ -145,8 +149,7 @@ void httpout_detach_binding(NAPI_Filter *napi_f); -void dummy_finalize(napi_env env, void* finalize_data, void* finalize_hint) -{ +void dummy_finalize(napi_env env, void* finalize_data, void* finalize_hint) { } napi_value gpac_init(napi_env env, napi_callback_info info) @@ -207,123 +210,472 @@ return val; } -void gpac_rmt_callback_exec(napi_env env, GPAC_NAPI *gpac) +napi_value gpac_sys_keyname(napi_env env, napi_callback_info info) { + napi_value val; +#ifndef GPAC_DISABLE_SVG + const char *gf_dom_get_friendly_name(u32 key_identifier); + + NARG_ARGS(1, 1) + NARG_S32(code, 0, 0); + NAPI_CALL( napi_create_string_utf8(env, gf_dom_get_friendly_name(code), NAPI_AUTO_LENGTH, &val) ); +#else + NAPI_CALL( napi_create_string_utf8(env, "unknown", NAPI_AUTO_LENGTH, &val) ); +#endif + return val; +} + + +static u32 RMT_CLIENT_PROP_PEER_ADDRESS = 0; +static u32 RMT_CLIENT_PROP_ON_DATA = 1; +static u32 RMT_CLIENT_PROP_ON_CLOSE = 2; + + +#define RMT_CLIENT\ + RMT_ClientCtx* client=NULL;\ + bool _tag;\ + NAPI_CALL( napi_check_object_type_tag(env, this_val, &rmtclient_tag, &_tag) );\ + if (!_tag) {\ + napi_throw_error(env, NULL, "Not a RMTClient object");\ + return NULL;\ + }\ + NAPI_CALL( napi_unwrap(env, this_val, (void**) &client) );\ + + +napi_value rmt_client_getter(napi_env env, napi_callback_info info) { + void *magic; napi_status status; - napi_value global, fun_val; + napi_value this_val, ret; + NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) ); + RMT_CLIENT - status = napi_get_global(env, &global); - if (status == napi_ok) - status = napi_get_reference_value(env, gpac->rmt_ref, &fun_val); + if (!client) + return NULL; - if (status == napi_ok) { - while (gf_list_count(gpac->rmt_messages)) { - napi_value res, arg; - char *msg = gf_list_pop_front(gpac->rmt_messages); - - status = napi_create_string_utf8(env, msg, NAPI_AUTO_LENGTH, &arg); - if (status == napi_ok) - status = napi_make_callback(env, gpac->rmt_ctx, global, fun_val, 1, &arg, &res); + if (magic == &RMT_CLIENT_PROP_PEER_ADDRESS) { + const char *peer = gf_rmt_get_peer_address(client); + if (!peer) return NULL; + NAPI_CALL( napi_create_string_utf8(env, peer, NAPI_AUTO_LENGTH, &ret) ); + return ret; + } + + return NULL; + +} + +typedef struct { + u32 type; + + napi_env env; + napi_ref _this; + napi_ref fun; + + void* client; + +} NAPI_RMT_Task ; - gf_free(msg); +typedef struct { + + NAPI_RMT_Task* task; + + napi_value arg; + +} NAPI_RMT_Task_Call ; + + +void napi_rmt_task_del(napi_env env, NAPI_RMT_Task* task) { + + if (task && task->type == RMT_CALLBACK_NODE) { + uint32_t res; + if (task->_this) { + napi_reference_unref(env, task->_this, &res); + napi_delete_reference(env, task->_this); + } + task->_this = NULL; + + if (task->fun) { + napi_reference_unref(env, task->fun, &res); + napi_delete_reference(env, task->fun); } + task->fun = NULL; + + gf_free(task); + } + } -Bool fs_flush_rmt(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms) -{ - NAPI_Session *napi_fs = callback; - GPAC_NAPI *gpac=NULL; - napi_get_instance_data(napi_fs->env, (void **) &gpac); - if (!gpac) return GF_FALSE; - gpac_rmt_callback_exec(napi_fs->env, gpac); - if (!gf_list_count(gpac->rmt_messages)) { - gpac->rmt_task_scheduled = GF_FALSE; - return GF_FALSE; + +Bool fs_flush_rmt(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms); + +void gpac_rmt_client_on_delete(void *udta) { + + NAPI_RMT_Task* task = udta; + + if (!task || task->type != RMT_CALLBACK_NODE ) + return; + + napi_env env = task->env; + + GPAC_NAPI *gpac; + if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) { + napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance"); + return; } - *reschedule_ms = 10; - return GF_TRUE; + + if (task->client) { + RMT_ClientCtx* client = task->client; + NAPI_RMT_Task* datatask = gf_rmt_client_get_on_data_task(client); + gf_rmt_client_set_on_data_cbk(client, NULL, NULL); + napi_rmt_task_del(env, datatask); + + gf_rmt_client_set_on_del_cbk(client, NULL, NULL); + } + + if (!gpac->rmt_delete_calls) gpac->rmt_delete_calls = gf_list_new(); + gf_list_add(gpac->rmt_delete_calls, task ); + + if (gpac->fs_rmt_handler) { + gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RMTClientOnData"); + } + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("gpac_rmt_client_on_delete with no fs_rmt_handler\n")); + } + + + } -void gpac_rmt_callback(void *udta, const char *message) -{ - GPAC_NAPI *gpac=NULL; - napi_get_instance_data((napi_env) udta, (void **) &gpac); - if (!gpac) return; +void gpac_rmt_client_on_data(void *udta, const u8* payload, u64 size, Bool is_binary) { + + NAPI_RMT_Task* task = udta; - if (!gpac->rmt_messages) gpac->rmt_messages = gf_list_new(); - gf_list_add(gpac->rmt_messages, gf_strdup(message) ); + if (!task || task->type != RMT_CALLBACK_NODE ) + return; - //session is running in blocking mode, schedule task to call NodeJS from main thread - if (gpac->fs_rmt_handler && !gpac->rmt_task_scheduled) { - gpac->rmt_task_scheduled = GF_TRUE; - gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RemoteryFlush"); + napi_env env = task->env; + + GPAC_NAPI *gpac; + if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) { + napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance"); + return; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_rmt_client_on_data binary %d size %llu: %.*s\n", is_binary, size, size, payload)); + + napi_value arg; + // napi_status status; //TODO: check status + if (is_binary) { + void* abdata; + /*status = */napi_create_arraybuffer(env, size, &abdata, &arg); + memcpy(abdata, payload, size); } + else { + /*status = */napi_create_string_utf8(env, (const char*)payload, size, &arg); + } + + NAPI_RMT_Task_Call* on_data_call; + GF_SAFEALLOC(on_data_call, NAPI_RMT_Task_Call); + on_data_call->task = task; + on_data_call->arg = arg; + + + if (!gpac->rmt_task_calls) gpac->rmt_task_calls = gf_list_new(); + gf_list_add(gpac->rmt_task_calls, on_data_call ); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_rmt_client_on_data added call %p\n", on_data_call)); + + if (gpac->fs_rmt_handler) { + + gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RMTClientOnData"); + + } + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("gpac_rmt_client_on_data with no fs_rmt_handler\n")); + } + + + } +napi_value rmt_client_setter(napi_env env, napi_callback_info info) { -napi_value gpac_set_rmt_fun(napi_env env, napi_callback_info info) -{ NAPI_GPAC - napi_value global, name; - NARG_ARGS(1, 0) - gpac->is_init = GF_TRUE; - if (!argc) { - if (gpac->rmt_ctx) { - uint32_t res; - napi_async_destroy(env, gpac->rmt_ctx); - napi_reference_unref(env, gpac->rmt_ref, &res); - napi_delete_reference(env, gpac->rmt_ref); - gpac->rmt_ctx = NULL; + NARG_ARGS_THIS_MAGIC(1, 1) + + RMT_CLIENT + + if (magic == &RMT_CLIENT_PROP_ON_DATA) { + + napi_valuetype rtype; + napi_typeof(env, argv0, &rtype); + + if (rtype == napi_null || rtype == napi_undefined ) { + + NAPI_RMT_Task* task = gf_rmt_client_get_on_data_task(client); + napi_rmt_task_del(env, task); + + gf_rmt_client_set_on_data_cbk(client, NULL, NULL); + + } else if (rtype == napi_function) { + + if (!gpac->rmt_ctx) { + napi_value global, name; + NAPI_CALL( napi_get_global(env, &global) ); + NAPI_CALL( napi_create_string_utf8(env, "GPAC_RMTCallback", NAPI_AUTO_LENGTH, &name) ); + NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) ) + } + + + NAPI_RMT_Task *task; + GF_SAFEALLOC(task, NAPI_RMT_Task); + + task->type = RMT_CALLBACK_NODE; + task->env = env; + task->client = client; + napi_create_reference(env, this_val, 1, &task->_this); + napi_create_reference(env, argv0, 1, &task->fun); + + gf_rmt_client_set_on_data_cbk(client, task, gpac_rmt_client_on_data); + } - gf_sys_profiler_set_callback(NULL, NULL); - return NULL; + } + else if (magic == &RMT_CLIENT_PROP_ON_CLOSE) { + + napi_valuetype rtype; + napi_typeof(env, argv0, &rtype); + + if (rtype == napi_null || rtype == napi_undefined ) { + + NAPI_RMT_Task* task = gf_rmt_client_get_on_del_task(client); + if (task && task->type == RMT_CALLBACK_NODE) { + // reset the js function but keep the ref to the client for on_delete + uint32_t res; + napi_reference_unref(env, task->fun, &res); + napi_delete_reference(env, task->fun); + task->fun = NULL; + } + + + + } else if (rtype == napi_function) { + + if (!gpac->rmt_ctx) { + napi_value global, name; + NAPI_CALL( napi_get_global(env, &global) ); + NAPI_CALL( napi_create_string_utf8(env, "GPAC_RMTCallback", NAPI_AUTO_LENGTH, &name) ); + NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) ) + } - NAPI_CALL( napi_get_global(env, &global) ); - NAPI_CALL( napi_create_string_utf8(env, "GPAC_RemoteryCallback", NAPI_AUTO_LENGTH, &name) ); - NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) ) - gf_sys_profiler_set_callback(env, gpac_rmt_callback); - napi_create_reference(env, argv0, 1, &gpac->rmt_ref); + NAPI_RMT_Task *task = gf_rmt_client_get_on_del_task(client); + if (!task) + GF_SAFEALLOC(task, NAPI_RMT_Task); + + task->type = RMT_CALLBACK_NODE; + task->env = env; + task->client = client; + napi_create_reference(env, this_val, 1, &task->_this); + napi_create_reference(env, argv0, 1, &task->fun); + + gf_rmt_client_set_on_del_cbk(client, task, gpac_rmt_client_on_delete); + + } + + } + return NULL; + } -napi_value gpac_rmt_log(napi_env env, napi_callback_info info) -{ - NARG_ARGS(1, 1) - NARG_STR(msg, 0, NULL); - if (msg) - gf_sys_profiler_log(msg); +napi_value rmt_client_send(napi_env env, napi_callback_info info) { + + + NARG_ARGS_THIS(1,1) + RMT_CLIENT + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_send got client %p\n", client)); + + bool is_arraybuffer = 0; + NAPI_CALL( napi_is_arraybuffer(env, argv0, &is_arraybuffer) ); + + if (is_arraybuffer) { + + u8* data=NULL; + size_t len=0; + NAPI_CALL(napi_get_arraybuffer_info(env, argv0, (void **)&data, &len) ); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_send got bin data of size %d\n", len)); + + gf_rmt_client_send_to_ws(client, (const char*)data, len, GF_TRUE); + + //free(data); //freed elsewhere??? + } + else { + NARG_STR_ALLOC(data, 0, NULL); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_send got string data %s\n", data)); + + gf_rmt_client_send_to_ws(client, data, strlen(data), GF_FALSE); + + gf_free(data); + } + + return NULL; + } -napi_value gpac_rmt_send(napi_env env, napi_callback_info info) -{ - NARG_ARGS(1, 1) - NARG_STR(msg, 0, NULL); - if (msg) - gf_sys_profiler_send(msg); - return NULL; +napi_value grap_wrap_rmt_new_client(napi_env env, RMT_ClientCtx* client) { + napi_status status; + napi_value obj; + + napi_property_descriptor rmt_client_properties = { + { "peer_address", NULL, NULL, rmt_client_getter, NULL, NULL, napi_enumerable, &RMT_CLIENT_PROP_PEER_ADDRESS}, + { "on_data", NULL, NULL, NULL, rmt_client_setter, NULL, napi_enumerable, &RMT_CLIENT_PROP_ON_DATA}, + { "on_close", NULL, NULL, NULL, rmt_client_setter, NULL, napi_enumerable, &RMT_CLIENT_PROP_ON_CLOSE}, + { "send", NULL, rmt_client_send, NULL, NULL, NULL, napi_enumerable, NULL}, + }; + + NAPI_CALL( napi_create_object(env, &obj) ); + + NAPI_CALL( napi_define_properties(env, obj, sizeof(rmt_client_properties)/sizeof(napi_property_descriptor), rmt_client_properties) ); + + NAPI_CALL( napi_type_tag_object(env, obj, &rmtclient_tag) ); + + NAPI_CALL( napi_wrap(env, obj, client, dummy_finalize, NULL, NULL) ); + + NAPI_RMT_Task *task; + GF_SAFEALLOC(task, NAPI_RMT_Task); + + task->type = RMT_CALLBACK_NODE; + task->env = env; + task->client = client; + napi_create_reference(env, obj, 1, &task->_this); + + gf_rmt_client_set_on_del_cbk(client, task, gpac_rmt_client_on_delete); + + return obj; } -napi_value gpac_rmt_on(napi_env env, napi_callback_info info) + +Bool fs_flush_rmt(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms) { - napi_status status; - napi_value val; - NAPI_CALL( napi_get_boolean(env, gf_sys_profiler_sampling_enabled(), &val) ); - return val; + NAPI_Session *napi_fs = callback; + napi_env env = napi_fs->env; + GPAC_NAPI *gpac=NULL; + + napi_get_instance_data(env, (void **) &gpac); + if (!gpac) return GF_FALSE; + + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d fs_flush_rmt clients %d tasks %d deletes %d gpac->rmt_ctx %p\n", __FILE__, __LINE__, gf_list_count(gpac->rmt_new_clients), gf_list_count(gpac->rmt_task_calls), gf_list_count(gpac->rmt_delete_calls), gpac->rmt_ctx)); + + while (gf_list_count(gpac->rmt_delete_calls)) { + + napi_value res; + + NAPI_RMT_Task* task = gf_list_pop_front(gpac->rmt_delete_calls); + + napi_value _this; + napi_get_reference_value(env, task->_this, &_this); + + RMT_ClientCtx* client; + napi_remove_wrap(env, _this, (void**) &client); + + napi_wrap(env, _this, NULL, NULL, NULL, NULL); + + if (task->fun) { + napi_value fun; + napi_get_reference_value(env, task->fun, &fun); + napi_make_callback(env, gpac->rmt_ctx, _this, fun, 0, NULL, &res); + } + + napi_rmt_task_del(env, task); + + + } + + while (gf_list_count(gpac->rmt_new_clients)) { + + void* client = gf_list_pop_front(gpac->rmt_new_clients); + + RMT_WS* rmt = gf_rmt_client_get_rmt((RMT_ClientCtx*) client); + NAPI_RMT_Task* task = gf_rmt_get_on_new_client_task(rmt); + if (task && gpac->rmt_ctx) { + + napi_value res, _this, fun; + // napi_status status; //TODO: check status + + napi_get_reference_value(env, task->_this, &_this); + napi_get_reference_value(env, task->fun, &fun); + + napi_value jsclient = grap_wrap_rmt_new_client(env, client); + napi_valuetype rtype; + napi_typeof(env, jsclient, &rtype); + + /*status = */napi_make_callback(env, gpac->rmt_ctx, _this, fun, 1, &jsclient, &res); + } + + + } + + + while (gf_list_count(gpac->rmt_task_calls)) { + + NAPI_RMT_Task_Call* call = gf_list_pop_front(gpac->rmt_task_calls); + + if (call && call->task) { + + if (gpac->rmt_ctx) { + + napi_value res, _this, fun; + // napi_status status; //TODO: check status + + napi_get_reference_value(env, call->task->_this, &_this); + napi_get_reference_value(env, call->task->fun, &fun); + + /*status = */napi_make_callback(env, gpac->rmt_ctx, _this, fun, 1, &call->arg, &res); + // else { + // napi_extended_error_info* error_info = NULL; + // napi_get_last_error_info(env, &error_info); + // const char* err_message = error_info->error_message; + // fprintf(stderr, "ERR cb status %d err: %s\n", status, err_message); + // } + } + + gf_free(call); + } + + + } + + if (!gf_list_count(gpac->rmt_task_calls) && !gf_list_count(gpac->rmt_new_clients)) { + return GF_FALSE; + } + *reschedule_ms = 10; + return GF_TRUE; } -napi_value gpac_rmt_enable(napi_env env, napi_callback_info info) +napi_value gpac_enable_rmtws(napi_env env, napi_callback_info info) +{ + NARG_ARGS(1, 1) + NARG_BOOL(enable, 0, GF_TRUE); + NAPI_GPAC + gpac->is_init = GF_TRUE; + gf_sys_enable_rmtws(enable); + return NULL; +} +napi_value gpac_enable_userws(napi_env env, napi_callback_info info) { NARG_ARGS(1, 1) - NARG_BOOL(val, 0, GF_FALSE); + NARG_BOOL(enable, 0, GF_TRUE); NAPI_GPAC gpac->is_init = GF_TRUE; - gf_sys_profiler_enable_sampling(val); + gf_sys_enable_userws(enable); return NULL; } @@ -369,6 +721,8 @@ static void gpac_napi_finalize(napi_env env, void* finalize_data, void* finalize_hint) { + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_napi_finalize() \n")); GPAC_NAPI *inst = finalize_data; if (inst->nb_args) { @@ -379,15 +733,38 @@ gf_free(inst->argv); } - napi_delete_reference(env, inst->rmt_ref); if (inst->str_buf) gf_free(inst->str_buf); - if (inst->rmt_messages) { - while (gf_list_count(inst->rmt_messages)) { - char *msg = gf_list_pop_back(inst->rmt_messages); - gf_free(msg); - } - gf_list_del(inst->rmt_messages); + + RMT_WS* rmt = (RMT_WS*) gf_sys_get_rmtws(); + NAPI_RMT_Task* task = gf_rmt_get_on_new_client_task(rmt); + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + napi_rmt_task_del(env, task); + + rmt = (RMT_WS*) gf_sys_get_userws(); + task = gf_rmt_get_on_new_client_task(rmt); + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + napi_rmt_task_del(env, task); + + if (inst->rmt_new_clients) { + gf_list_del(inst->rmt_new_clients); + inst->rmt_new_clients = NULL; + } + if (inst->rmt_delete_calls) { + gf_list_del(inst->rmt_delete_calls); + inst->rmt_delete_calls = NULL; + } + if (inst->rmt_task_calls) { + while (gf_list_count(inst->rmt_task_calls)) { + NAPI_RMT_Task_Call *call = gf_list_pop_back(inst->rmt_task_calls); + gf_free(call); + } + gf_list_del(inst->rmt_task_calls); + } + if (inst->rmt_ctx) { + napi_async_destroy(env, inst->rmt_ctx); + inst->rmt_ctx = NULL; } + //close gpac gf_sys_close(); @@ -1876,6 +2253,9 @@ static u32 FILTER_PROP_ID = 1; static u32 FILTER_PROP_IPIDS = 2; static u32 FILTER_PROP_OPIDS = 3; +static u32 FILTER_PROP_STATUS = 4; +static u32 FILTER_PROP_BYTES_DONE = 5; + #define FILTER\ GF_Filter *f=NULL;\ @@ -1934,6 +2314,18 @@ return ret; } + if (magic == &FILTER_PROP_STATUS) { + const char* statusstr = gf_filter_get_status(f); + NAPI_CALL( napi_create_string_utf8(env, statusstr, NAPI_AUTO_LENGTH, &ret) ); + return ret; + } + + if (magic == &FILTER_PROP_BYTES_DONE) { + u64 bytes_dones = gf_filter_get_bytes_done(f); + NAPI_CALL( napi_create_int64(env, bytes_dones, &ret) ); + return ret; + } + return NULL; } @@ -2238,6 +2630,7 @@ SET_U64(nb_pck_sent) SET_U64(nb_hw_pck_sent) SET_U32(nb_errors) + SET_U32(nb_current_errors) SET_U64(nb_bytes_sent) SET_U64(time_process) SET_S32(percent) @@ -2778,6 +3171,29 @@ prop = gf_filter_pid_get_property_str(pid, pname); } if (!prop) { + + if (!strcmp(pname, "buffer")) { + NAPI_CALL( napi_create_int64(env, (s64) gf_filter_pid_query_buffer_duration(pid, GF_FALSE), &res) ); + return res; + } + if (!strcmp(pname, "buffer_total")) { + NAPI_CALL( napi_create_int64(env, (s64) gf_filter_pid_query_buffer_duration(pid, GF_TRUE), &res) ); + return res; + } + if (!strcmp(pname, "name")) { + const char *fname = gf_filter_pid_get_name(pid); + if (fname) { + NAPI_CALL( napi_create_string_utf8(env, fname, NAPI_AUTO_LENGTH, &res) ); + } else { + NAPI_CALL( napi_get_null(env, &res) ); + } + return res; + } + if (!strcmp(pname, "eos")) { + NAPI_CALL( napi_get_boolean(env, gf_filter_pid_is_eos(pid), &res) ); + return res; + } + napi_value res; NAPI_CALL(napi_get_null(env, &res) ); return res; @@ -2989,6 +3405,13 @@ NAPI_CALL( napi_create_int32(env, arg->flags, &val) ); NAPI_CALL(napi_set_named_property(env, n_arg, "flags", val) ); + char argvalGF_PROP_DUMP_ARG_SIZE; + gf_filter_get_arg_str(f, arg->arg_name, argval); + NAPI_CALL( napi_create_string_utf8(env, argval, NAPI_AUTO_LENGTH, &val) ); + NAPI_CALL(napi_set_named_property(env, n_arg, "value", val) ); + + + NAPI_CALL(napi_set_element(env, res, a_idx, n_arg) ); a_idx++; } @@ -3198,7 +3621,7 @@ if (!has_fun) return GF_FALSE; status = napi_get_named_property(napi_f->env, obj, "process_event", &fun_val); - if (status != napi_ok) return GF_BAD_PARAM; + if (status != napi_ok) return GF_FALSE; arg = wrap_filterevent(napi_f->env, (GF_FilterEvent *)evt, NULL); status = napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun_val, 1, &arg, &res); @@ -3454,6 +3877,7 @@ static u32 FS_PROP_HTTP_RATE = 2; static u32 FS_PROP_MAX_HTTP_RATE = 3; + napi_value fs_getter(napi_env env, napi_callback_info info) { void *magic; @@ -3502,23 +3926,14 @@ NAPI_GPAC NAPI_Session *napi_fs = gf_fs_get_rt_udta(fs); - if (napi_fs->blocking) { - //session is running in blocking mode, schedule task to call NodeJS from main thread - if (gpac->rmt_ref) { - gpac->fs_rmt_handler = napi_fs; - gpac->rmt_task_scheduled = GF_TRUE; - gf_fs_post_user_task(fs, fs_flush_rmt, napi_fs, "RemoteryFlush"); - } - e = gf_fs_run(fs); + gpac->fs_rmt_handler = napi_fs; + if (gf_list_count(gpac->rmt_new_clients) || gf_list_count(gpac->rmt_task_calls) || gf_list_count(gpac->rmt_delete_calls)) { + gf_fs_post_user_task(fs, fs_flush_rmt, napi_fs, "RMTFlush"); + } - gpac->fs_rmt_handler = NULL; - } else { - e = gf_fs_run(fs); + e = gf_fs_run(fs); - //flush rmt events - if (gpac->rmt_ref) - gpac_rmt_callback_exec(env, gpac); - } + //TODO: run flush rmt again if not blocking? if (e>=GF_OK) { e = gf_fs_get_last_connect_error(fs); @@ -3562,6 +3977,8 @@ { "ID", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_ID}, { "nb_ipid", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_IPIDS}, { "nb_opid", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_OPIDS}, + { "status", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_STATUS}, + { "bytes_done", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_BYTES_DONE}, { "remove", 0, filter_remove, 0, 0, 0, napi_enumerable, 0 }, { "update", 0, filter_update, 0, 0, 0, napi_enumerable, 0 }, { "set_source", 0, filter_set_source, 0, 0, 0, napi_enumerable, 0 }, @@ -3965,7 +4382,7 @@ napi_env env = napi_fs->env; //set to NULL means final nodeJS addon shutdown, do not notify - + if (napi_fs->async_ctx != NULL) { GPAC_NAPI *gpac; if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) { @@ -4774,6 +5191,98 @@ static napi_status InitConstants(napi_env env, napi_value exports); napi_status init_fio_class(napi_env env, napi_value exports, GPAC_NAPI *inst); + +static u32 NODEGPAC_PROP_RMT_ON_NEW_CLIENT = 0; +static u32 NODEGPAC_PROP_USERWS_ON_NEW_CLIENT = 1; + + + +void gpac_rmt_on_new_client(void *udta, void* new_client) { + + NAPI_RMT_Task* task = udta; + + if (!task || task->type != RMT_CALLBACK_NODE ) + return; + + napi_env env = task->env; + + GPAC_NAPI *gpac; + if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) { + napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance"); + return; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_rmt_on_new_client %p\n", new_client)); + + + if (!gpac->rmt_new_clients) gpac->rmt_new_clients = gf_list_new(); + gf_list_add(gpac->rmt_new_clients, new_client ); + + if (gpac->fs_rmt_handler) { + + gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RMTOnNewClient"); + } + +} + + +napi_value nodegpac_setter(napi_env env, napi_callback_info info) +{ + NAPI_GPAC + NARG_ARGS_THIS_MAGIC(1, 1) + + gpac->is_init = GF_TRUE; + + if (magic == &NODEGPAC_PROP_RMT_ON_NEW_CLIENT || magic == &NODEGPAC_PROP_USERWS_ON_NEW_CLIENT) { + + napi_valuetype rtype; + napi_typeof(env, argv0, &rtype); + + RMT_WS* rmt = NULL; + if (magic == &NODEGPAC_PROP_RMT_ON_NEW_CLIENT) rmt = (RMT_WS*) gf_sys_get_rmtws(); + if (magic == &NODEGPAC_PROP_USERWS_ON_NEW_CLIENT) rmt = (RMT_WS*) gf_sys_get_userws(); + + if (!rmt) + return NULL; + + if (rtype == napi_null || rtype == napi_undefined ) { + + if (gpac->rmt_ctx) { + napi_async_destroy(env, gpac->rmt_ctx); + gpac->rmt_ctx = NULL; + } + NAPI_RMT_Task* task = gf_rmt_get_on_new_client_task(rmt); + napi_rmt_task_del(env, task); + + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + + + } else if (rtype == napi_function) { + + if (!gpac->rmt_ctx) { + napi_value global, name; + NAPI_CALL( napi_get_global(env, &global) ); + NAPI_CALL( napi_create_string_utf8(env, "GPAC_RMTCallback", NAPI_AUTO_LENGTH, &name) ); + NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) ) + } + + NAPI_RMT_Task *task; + GF_SAFEALLOC(task, NAPI_RMT_Task); + task->type = RMT_CALLBACK_NODE; + task->env = env; + napi_create_reference(env, this_val, 1, &task->_this); + napi_create_reference(env, argv0, 1, &task->fun); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("setting on new client cb %p\n", gpac_rmt_on_new_client)); + gf_rmt_set_on_new_client_cbk(rmt, task, gpac_rmt_on_new_client); + + } + + } + + return NULL; +} + napi_value Init(napi_env env, napi_value exports) { GPAC_NAPI *inst; @@ -4803,12 +5312,19 @@ DEF_FUN("set_args", gpac_set_args); DEF_FUN("sys_clock", gpac_sys_clock); DEF_FUN("sys_clock_high_res", gpac_sys_clock_high_res); + DEF_FUN("sys_keyname", gpac_sys_keyname); - DEF_FUN("set_rmt_fun", gpac_set_rmt_fun); - DEF_FUN("rmt_send", gpac_rmt_send); - DEF_FUN("rmt_on", gpac_rmt_on); - DEF_FUN("rmt_enable", gpac_rmt_enable); + DEF_FUN("enable_rmtws", gpac_enable_rmtws); + napi_property_descriptor rmtws_properties = { + { "rmt_on_new_client", NULL, NULL, NULL, nodegpac_setter, NULL, napi_enumerable, &NODEGPAC_PROP_RMT_ON_NEW_CLIENT}, + }; + NAPI_CALL( napi_define_properties(env, exports, sizeof(rmtws_properties)/sizeof(napi_property_descriptor), rmtws_properties) ); + DEF_FUN("enable_userws", gpac_enable_userws); + napi_property_descriptor userws_properties = { + { "userws_on_new_client", NULL, NULL, NULL, nodegpac_setter, NULL, napi_enumerable, &NODEGPAC_PROP_USERWS_ON_NEW_CLIENT}, + }; + NAPI_CALL( napi_define_properties(env, exports, sizeof(userws_properties)/sizeof(napi_property_descriptor), userws_properties) ); status = init_fs_class(env, exports, inst); if (status != napi_ok) return NULL; @@ -4819,6 +5335,7 @@ status = init_fio_class(env, exports, inst); if (status != napi_ok) return NULL; + inst->main_thid = gf_th_id(); //default init @@ -5014,214 +5531,6 @@ DEF_CONST(GF_EVENT_CODEC_SLOW) DEF_CONST(GF_EVENT_CODEC_OK) - DEF_CONST(GF_KEY_UNIDENTIFIED) - DEF_CONST(GF_KEY_ACCEPT) - DEF_CONST(GF_KEY_AGAIN) - DEF_CONST(GF_KEY_ALLCANDIDATES) - DEF_CONST(GF_KEY_ALPHANUM) - DEF_CONST(GF_KEY_ALT) - DEF_CONST(GF_KEY_ALTGRAPH) - DEF_CONST(GF_KEY_APPS) - DEF_CONST(GF_KEY_ATTN) - DEF_CONST(GF_KEY_BROWSERBACK) - DEF_CONST(GF_KEY_BROWSERFAVORITES) - DEF_CONST(GF_KEY_BROWSERFORWARD) - DEF_CONST(GF_KEY_BROWSERHOME) - DEF_CONST(GF_KEY_BROWSERREFRESH) - DEF_CONST(GF_KEY_BROWSERSEARCH) - DEF_CONST(GF_KEY_BROWSERSTOP) - DEF_CONST(GF_KEY_CAPSLOCK) - DEF_CONST(GF_KEY_CLEAR) - DEF_CONST(GF_KEY_CODEINPUT) - DEF_CONST(GF_KEY_COMPOSE) - DEF_CONST(GF_KEY_CONTROL) - DEF_CONST(GF_KEY_CRSEL) - DEF_CONST(GF_KEY_CONVERT) - DEF_CONST(GF_KEY_COPY) - DEF_CONST(GF_KEY_CUT) - DEF_CONST(GF_KEY_DOWN) - DEF_CONST(GF_KEY_END) - DEF_CONST(GF_KEY_ENTER) - DEF_CONST(GF_KEY_ERASEEOF) - DEF_CONST(GF_KEY_EXECUTE) - DEF_CONST(GF_KEY_EXSEL) - DEF_CONST(GF_KEY_F1) - DEF_CONST(GF_KEY_F2) - DEF_CONST(GF_KEY_F3) - DEF_CONST(GF_KEY_F4) - DEF_CONST(GF_KEY_F5) - DEF_CONST(GF_KEY_F6) - DEF_CONST(GF_KEY_F7) - DEF_CONST(GF_KEY_F8) - DEF_CONST(GF_KEY_F9) - DEF_CONST(GF_KEY_F10) - DEF_CONST(GF_KEY_F11) - DEF_CONST(GF_KEY_F12) - DEF_CONST(GF_KEY_F13) - DEF_CONST(GF_KEY_F14) - DEF_CONST(GF_KEY_F15) - DEF_CONST(GF_KEY_F16) - DEF_CONST(GF_KEY_F17) - DEF_CONST(GF_KEY_F18) - DEF_CONST(GF_KEY_F19) - DEF_CONST(GF_KEY_F20) - DEF_CONST(GF_KEY_F21) - DEF_CONST(GF_KEY_F22) - DEF_CONST(GF_KEY_F23) - DEF_CONST(GF_KEY_F24) - DEF_CONST(GF_KEY_FINALMODE) - DEF_CONST(GF_KEY_FIND) - DEF_CONST(GF_KEY_FULLWIDTH) - DEF_CONST(GF_KEY_HALFWIDTH) - DEF_CONST(GF_KEY_HANGULMODE) - DEF_CONST(GF_KEY_HANJAMODE) - DEF_CONST(GF_KEY_HELP) - DEF_CONST(GF_KEY_HIRAGANA) - DEF_CONST(GF_KEY_HOME) - DEF_CONST(GF_KEY_INSERT) - DEF_CONST(GF_KEY_JAPANESEHIRAGANA) - DEF_CONST(GF_KEY_JAPANESEKATAKANA) - DEF_CONST(GF_KEY_JAPANESEROMAJI) - DEF_CONST(GF_KEY_JUNJAMODE) - DEF_CONST(GF_KEY_KANAMODE) - DEF_CONST(GF_KEY_KANJIMODE) - DEF_CONST(GF_KEY_KATAKANA) - DEF_CONST(GF_KEY_LAUNCHAPPLICATION1) - DEF_CONST(GF_KEY_LAUNCHAPPLICATION2) - DEF_CONST(GF_KEY_LAUNCHMAIL) - DEF_CONST(GF_KEY_LEFT) - DEF_CONST(GF_KEY_META) - DEF_CONST(GF_KEY_MEDIANEXTTRACK) - DEF_CONST(GF_KEY_MEDIAPLAYPAUSE) - DEF_CONST(GF_KEY_MEDIAPREVIOUSTRACK) - DEF_CONST(GF_KEY_MEDIASTOP) - DEF_CONST(GF_KEY_MODECHANGE) - DEF_CONST(GF_KEY_NONCONVERT) - DEF_CONST(GF_KEY_NUMLOCK) - DEF_CONST(GF_KEY_PAGEDOWN) - DEF_CONST(GF_KEY_PAGEUP) - DEF_CONST(GF_KEY_PASTE) - DEF_CONST(GF_KEY_PAUSE) - DEF_CONST(GF_KEY_PLAY) - DEF_CONST(GF_KEY_PREVIOUSCANDIDATE) - DEF_CONST(GF_KEY_PRINTSCREEN) - DEF_CONST(GF_KEY_PROCESS) - DEF_CONST(GF_KEY_PROPS) - DEF_CONST(GF_KEY_RIGHT) - DEF_CONST(GF_KEY_ROMANCHARACTERS) - DEF_CONST(GF_KEY_SCROLL) - DEF_CONST(GF_KEY_SELECT) - DEF_CONST(GF_KEY_SELECTMEDIA) - DEF_CONST(GF_KEY_SHIFT) - DEF_CONST(GF_KEY_STOP) - DEF_CONST(GF_KEY_UP) - DEF_CONST(GF_KEY_UNDO) - DEF_CONST(GF_KEY_VOLUMEDOWN) - DEF_CONST(GF_KEY_VOLUMEMUTE) - DEF_CONST(GF_KEY_VOLUMEUP) - DEF_CONST(GF_KEY_WIN) - DEF_CONST(GF_KEY_ZOOM) - DEF_CONST(GF_KEY_BACKSPACE) - DEF_CONST(GF_KEY_TAB) - DEF_CONST(GF_KEY_CANCEL) - DEF_CONST(GF_KEY_ESCAPE) - DEF_CONST(GF_KEY_SPACE) - DEF_CONST(GF_KEY_EXCLAMATION) - DEF_CONST(GF_KEY_QUOTATION) - DEF_CONST(GF_KEY_NUMBER) - DEF_CONST(GF_KEY_DOLLAR) - DEF_CONST(GF_KEY_AMPERSAND) - DEF_CONST(GF_KEY_APOSTROPHE) - DEF_CONST(GF_KEY_LEFTPARENTHESIS) - DEF_CONST(GF_KEY_RIGHTPARENTHESIS) - DEF_CONST(GF_KEY_STAR) - DEF_CONST(GF_KEY_PLUS) - DEF_CONST(GF_KEY_COMMA) - DEF_CONST(GF_KEY_HYPHEN) - DEF_CONST(GF_KEY_FULLSTOP) - DEF_CONST(GF_KEY_SLASH) - DEF_CONST(GF_KEY_0) - DEF_CONST(GF_KEY_1) - DEF_CONST(GF_KEY_2) - DEF_CONST(GF_KEY_3) - DEF_CONST(GF_KEY_4) - DEF_CONST(GF_KEY_5) - DEF_CONST(GF_KEY_6) - DEF_CONST(GF_KEY_7) - DEF_CONST(GF_KEY_8) - DEF_CONST(GF_KEY_9) - DEF_CONST(GF_KEY_COLON) - DEF_CONST(GF_KEY_SEMICOLON) - DEF_CONST(GF_KEY_LESSTHAN) - DEF_CONST(GF_KEY_EQUALS) - DEF_CONST(GF_KEY_GREATERTHAN) - DEF_CONST(GF_KEY_QUESTION) - DEF_CONST(GF_KEY_AT) - DEF_CONST(GF_KEY_A) - DEF_CONST(GF_KEY_B) - DEF_CONST(GF_KEY_C) - DEF_CONST(GF_KEY_D) - DEF_CONST(GF_KEY_E) - DEF_CONST(GF_KEY_F) - DEF_CONST(GF_KEY_G) - DEF_CONST(GF_KEY_H) - DEF_CONST(GF_KEY_I) - DEF_CONST(GF_KEY_J) - DEF_CONST(GF_KEY_K) - DEF_CONST(GF_KEY_L) - DEF_CONST(GF_KEY_M) - DEF_CONST(GF_KEY_N) - DEF_CONST(GF_KEY_O) - DEF_CONST(GF_KEY_P) - DEF_CONST(GF_KEY_Q) - DEF_CONST(GF_KEY_R) - DEF_CONST(GF_KEY_S) - DEF_CONST(GF_KEY_T) - DEF_CONST(GF_KEY_U) - DEF_CONST(GF_KEY_V) - DEF_CONST(GF_KEY_W) - DEF_CONST(GF_KEY_X) - DEF_CONST(GF_KEY_Y) - DEF_CONST(GF_KEY_Z) - DEF_CONST(GF_KEY_LEFTSQUAREBRACKET) - DEF_CONST(GF_KEY_BACKSLASH) - DEF_CONST(GF_KEY_RIGHTSQUAREBRACKET) - DEF_CONST(GF_KEY_CIRCUM) - DEF_CONST(GF_KEY_UNDERSCORE) - DEF_CONST(GF_KEY_GRAVEACCENT) - DEF_CONST(GF_KEY_LEFTCURLYBRACKET) - DEF_CONST(GF_KEY_PIPE) - DEF_CONST(GF_KEY_RIGHTCURLYBRACKET) - DEF_CONST(GF_KEY_DEL) - DEF_CONST(GF_KEY_INVERTEXCLAMATION) - DEF_CONST(GF_KEY_DEADGRAVE) - DEF_CONST(GF_KEY_DEADEACUTE) - DEF_CONST(GF_KEY_DEADCIRCUM) - DEF_CONST(GF_KEY_DEADTILDE) - DEF_CONST(GF_KEY_DEADMACRON) - DEF_CONST(GF_KEY_DEADBREVE) - DEF_CONST(GF_KEY_DEADABOVEDOT) - DEF_CONST(GF_KEY_DEADDIARESIS) - DEF_CONST(GF_KEY_DEADRINGABOVE) - DEF_CONST(GF_KEY_DEADDOUBLEACUTE) - DEF_CONST(GF_KEY_DEADCARON) - DEF_CONST(GF_KEY_DEADCEDILLA) - DEF_CONST(GF_KEY_DEADOGONEK) - DEF_CONST(GF_KEY_DEADIOTA) - DEF_CONST(GF_KEY_EURO) - DEF_CONST(GF_KEY_DEADVOICESOUND) - DEF_CONST(GF_KEY_DEADSEMIVOICESOUND) - DEF_CONST(GF_KEY_CHANNELUP) - DEF_CONST(GF_KEY_CHANNELDOWN) - DEF_CONST(GF_KEY_TEXT) - DEF_CONST(GF_KEY_INFO) - DEF_CONST(GF_KEY_EPG) - DEF_CONST(GF_KEY_RECORD) - DEF_CONST(GF_KEY_BEGINPAGE) - DEF_CONST(GF_KEY_CELL_SOFT1) - DEF_CONST(GF_KEY_CELL_SOFT2) - DEF_CONST(GF_KEY_JOYSTICK) - DEF_CONST(GF_KEY_MOD_SHIFT) DEF_CONST(GF_KEY_MOD_CTRL) DEF_CONST(GF_KEY_MOD_ALT) @@ -5270,6 +5579,7 @@ DEF_CONST(GF_CAPFLAG_LOADED_FILTER) DEF_CONST(GF_CAPFLAG_STATIC) DEF_CONST(GF_CAPFLAG_OPTIONAL) + DEF_CONST(GF_CAPFLAG_RECONFIG) DEF_CONST(GF_CAPS_INPUT) DEF_CONST(GF_CAPS_INPUT_OPT) DEF_CONST(GF_CAPS_INPUT_STATIC) @@ -5419,6 +5729,8 @@ QSET_BOOL(is_selected) QSET_DOUBLE(ast_offset) QSET_DOUBLE(average_duration) + QSET_STR(hls_variant_url) + QSET_U32(ssr) p_desc.utf8name = "sizes"; napi_create_array(env, &p_desc.value);
View file
gpac-2.4.0.tar.gz/share/nodejs/test/gpac.js -> gpac-26.02.0.tar.gz/share/nodejs/test/gpac.js
Changed
@@ -89,6 +89,7 @@ console.log(); console.log('GPAC NodeJS version ' + gpac.version + ' libgpac ' + gpac.abi_major + ':' + gpac.abi_minor + '.' + gpac.abi_micro); console.log('' + gpac.copyright_cite); + process.exit(0); } bad_param = (v) => { @@ -315,11 +316,6 @@ } -gpac.set_rmt_fun( (msg) => { - console.log('RMT got message ' + msg); - gpac.rmt_send('ACK for ' + msg); -}); - let fs; if (run_mode==RUN_SYNC) { fs = new gpac.FilterSession(); @@ -329,7 +325,7 @@ fs.on_filter_new = function(f) { - if (is_verbose) + if (is_verbose) console.log('New filter created: ' + f.name); if (dash_algo && (f.name=="dashin")) { try { @@ -347,7 +343,7 @@ } } fs.on_filter_del = function(f) { - if (is_verbose) + if (is_verbose) console.log('Filter deleted: ' + f.name); }
View file
gpac-2.4.0.tar.gz/share/python/libgpac/libgpac.py -> gpac-26.02.0.tar.gz/share/python/libgpac/libgpac.py
Changed
@@ -2,7 +2,7 @@ # GPAC - Multimedia Framework C SDK # # Authors: Jean Le Feuvre -# Copyright (c) Telecom Paris 2020-2024 +# Copyright (c) Telecom Paris 2020-2026 # All rights reserved # # Python ctypes bindings for GPAC (core initialization and filters API only) @@ -23,7 +23,7 @@ # -## +## # \defgroup pyapi_grp Python APIs # \brief Python API for libgpac. # @@ -39,19 +39,19 @@ # # # Error handling # -# Errors are handled through raising exceptions, except callback methods wich must return a \ref GF_Err value. +# Errors are handled through raising exceptions, except callback methods wich must return a \ref GF_Err value. # # Properties handling # -# Properties types are automatically converted to and from string. If the property name is not a built-in property type, the property is assumed to be a user-defined property. +# Properties types are automatically converted to and from string. If the property name is not a built-in property type, the property is assumed to be a user-defined property. # For example, when querying or setting a stream type property, use the property name `StreamType`. # See `gpac -h props`(https://wiki.gpac.io/Filters/filters_properties/) for the complete list of built-in property names. # # Properties values are automatically converted to or from python types whenever possible. Types with no python equivalent (vectors, fractions) are defined as classes in python. # For example: -# - when setting a UIntList property, pass a python list of ints -# - when reading a UIntList property, a python list of ints will be returned -# - when setting a PropVec2i property, pass a PropVec2i object -# - when setting a PropVec2iList property, pass a python list of PropVec2i +# - when setting a UIntList property, pass a python list of ints +# - when reading a UIntList property, a python list of ints will be returned +# - when setting a PropVec2i property, pass a PropVec2i object +# - when setting a PropVec2iList property, pass a python list of PropVec2i # # 4CCs are handled as strings in python, and list of 4CCs are handled as list of strings # @@ -107,18 +107,18 @@ # # # # Posting user tasks -# +# # You can post tasks to the session scheduler to get called back (useful when running the session in blocking mode) # Tasks must derive FilterTask class and implement their own `execute` method # \code -# class MyTask(FilterTask): +# class MyTask(FilterTask): # def exectute(self): # do_something() # #last scheduled task (session is over), abort # if self.session.last_task: -# return -1 -# //keep task active, reschedule in 500 ms -# return 500 +# return -1 +# //keep task active, reschedule in 500 ms +# return 500 # # task = MyTask('CustomTask') # fs.post_task(task) @@ -136,16 +136,15 @@ # - configure_pid: callback for PID configuration, mandatory if your filter is not a source # - process: callback for processing # - process_event: callback for processing and event -# - probe_data: callback for probing a data format # - reconfigure_output: callback for output reconfiguration (PID capability negotiation) # -# A custom filter must also declare its capabilities, input and output, using push_cap method +# A custom filter must also declare its capabilities, input and output, using push_cap method # \code -# class MyFilter(FilterTask): +# class MyFilter(FilterTask): # def __init__(self, session): # # !! call constructor of parent class first !! # gpac.FilterCustom.__init__(self, session, "PYnspect") -# #indicate what we accept and produce - this can be done either in the constructor or after, but before calling \ref run or \ref run_step +# #indicate what we accept and produce - this can be done either in the constructor or after, but before calling \ref run or \ref run_step # #here we indicate we accept anything of type visual, and produce anything of type visual # self.push_cap("StreamType", "Visual", gpac.GF_CAPS_INPUT_OUTPUT) # @@ -167,18 +166,14 @@ # #def process_event(self, evt): # #do something, return value is True (cancelled) or False # -# #probe_data takes a NumPy array if numpy is available (and _size can be ignored) or a POINTER(c_ubyte) otherwise -# #def probe_data(self, data, _size): -# #do something, return value is mime type or None -# -# #probe_data takes the target output pid as parameter +# #reconfigure_output takes the target output pid as parameter # #def reconfigure_output(self, opid): # #do something, return value is a GF_Err value # # \endcode # # \note -# The \ref FilterCustom object has a list of input and output PIDs created. Before a callback to configure_pid, the input PID is not registered to the list +# The \ref FilterCustom object has a list of input and output PIDs created. Before a callback to configure_pid, the input PID is not registered to the list # of input PIDs if this is the first time the PID is configured. # # \code @@ -193,7 +188,7 @@ # -## +## # \defgroup pycore_grp libgpac core tools # \ingroup pyapi_grp Python APIs # \brief Core tools for libgpac. @@ -240,9 +235,9 @@ print('Failed to locate libgpac (.so/.dll/.dylib) - make sure it is in your system path') os._exit(1) -#change this to reflect API we encapsulate. An incomatibility in either of these will throw a warning -GF_ABI_MAJOR=12 -GF_ABI_MINOR=14 +#change this to reflect API we encapsulate. An incompatibility in either of these will throw a warning +GF_ABI_MAJOR=16 +GF_ABI_MINOR=5 gpac_abi_major=_libgpac.gf_gpac_abi_major() gpac_abi_minor=_libgpac.gf_gpac_abi_minor() @@ -282,7 +277,7 @@ #error to string helper _libgpac.gf_error_to_string.argtypes = c_int _libgpac.gf_error_to_string.restype = c_char_p - + _libgpac.gf_gpac_version.restype = c_char_p _libgpac.gf_gpac_copyright.restype = c_char_p _libgpac.gf_gpac_copyright_cite.restype = c_char_p @@ -297,10 +292,19 @@ _libgpac.gf_sys_clock.restype = c_uint _libgpac.gf_sys_clock_high_res.restype = c_ulonglong -_libgpac.gf_sys_profiler_log.argtypes = c_char_p -_libgpac.gf_sys_profiler_send.argtypes = c_char_p -_libgpac.gf_sys_profiler_sampling_enabled.restype = gf_bool -_libgpac.gf_sys_profiler_enable_sampling.argtypes = gf_bool +_libgpac.gf_sys_enable_rmtws.argtypes = gf_bool +_libgpac.gf_sys_get_rmtws.argtypes = +_libgpac.gf_sys_get_rmtws.restype = c_void_p + +_libgpac.gf_sys_enable_userws.argtypes = gf_bool +_libgpac.gf_sys_get_userws.argtypes = +_libgpac.gf_sys_get_userws.restype = c_void_p + + +_libgpac.gf_rmt_get_peer_address.argtypes = c_void_p +_libgpac.gf_rmt_get_peer_address.restype = c_char_p +_libgpac.gf_rmt_client_send_to_ws.argtypes = c_void_p, c_void_p, c_uint64, gf_bool + _libgpac.gf_4cc_to_str.argtypes = c_uint _libgpac.gf_4cc_to_str.restype = c_char_p @@ -314,12 +318,6 @@ _libgpac.gf_props_enum_name.argtypes = c_uint, c_uint _libgpac.gf_props_enum_name.restype = c_char_p -#default init of libgpac -_libgpac.user_init = False -err = _libgpac.gf_sys_init(0, None) -if err<0: - raise Exception('Failed to initialize libgpac: ' + e2s(err)) - #\endcond ##libgpac version (string) @@ -338,6 +336,15 @@ def e2s(err): return _libgpac.gf_error_to_string(err).decode('utf-8') +##\cond private +#default init of libgpac +_libgpac.user_init = False +err = _libgpac.gf_sys_init(0, None) +if err<0: + raise Exception('Failed to initialize libgpac: ' + e2s(err)) + +#\endcond + ## initialize libgpac - see \ref gf_sys_init # \param mem_track @@ -405,58 +412,150 @@ _libgpac.gf_sys_set_args(nb_args, cast(_libgpac._args, POINTER(POINTER(c_char))) ) + +## enables websocket monitoring server +# \param enable True/False enable or disable server +def enable_rmtws(enable=True): + _libgpac.user_init = True + _libgpac.gf_sys_enable_rmtws(enable) + +## enables the user websocket server +# \param enable True/False enable or disable server +def enable_userws(enable=True): + _libgpac.user_init = True + _libgpac.gf_sys_enable_userws(enable) + + ##\cond private -_libgpac.gf_sys_profiler_set_callback.argtypes = py_object, c_void_p -@CFUNCTYPE(c_int, c_void_p, c_char_p) -def rmt_fun_cbk(_udta, text): +_libgpac.gf_rmt_client_set_on_del_cbk.argtypes = c_void_p, py_object, c_void_p +@CFUNCTYPE(c_int, c_void_p) +def rmt_fun_on_client_close_cbk(_udta): + print("rmt_fun_on_client_close_cbk") obj = cast(_udta, py_object).value - obj.on_rmt_event(text.decode('utf-8')) + obj._on_delete() return 0 ##\endcond private +##\cond private +_libgpac.gf_rmt_client_set_on_data_cbk.argtypes = c_void_p, py_object, c_void_p +@CFUNCTYPE(c_int, c_void_p, c_void_p, c_uint64, gf_bool) +def rmt_fun_on_client_data_cbk(_udta, data, size, is_binary): + obj = cast(_udta, py_object).value + data = string_at(data, size) + print(f"raw data: {data}") + obj._on_data(data, size, is_binary) + return 0 +##\endcond private -## set profiler (Remotery) callback - see \ref gf_sys_profiler_set_callback -# \param callback_obj object to call back, must have a method `on_rmt_event` taking a single string parameter -# \return True if success, False if no Remotery support -def set_rmt_fun(callback_obj): - _libgpac.user_init = True - if hasattr(callback_obj, 'on_rmt_event')==False: - raise Exception('No on_rmt_event function on callback') - err = _libgpac.gf_sys_profiler_set_callback(py_object(callback_obj), rmt_fun_cbk) - if err<0: - return False - return True -## send message to profiler (Remotery) - see \ref gf_sys_profiler_log -# \param text text to send -# \return True if success, False if no Remotery support -def rmt_log(text): - err = _libgpac.gf_sys_profiler_log(text.encode('utf-8')) - if err<0: - return False - return True +## RMTClient object representing a websocket client +# will be passed as parameter on rmt_ws callbacks +class RMTClient(): + def __init__(self, handler, client): + self._handler = handler + self._client = client -## send message to profiler (Remotery) - see \ref gf_sys_profiler_send -# \param text text to send -# \return True if success, False if no Remotery support -def rmt_send(text): - err = _libgpac.gf_sys_profiler_send(text.encode('utf-8')) - if err<0: - return False - return True + if hasattr(self._handler, 'on_client_close'): + _libgpac.gf_rmt_client_set_on_del_cbk(self._client, py_object(self), rmt_fun_on_client_close_cbk) -## check if profiler (Remotery) sampling is enabled - see \ref gf_sys_profiler_sampling_enabled -# \return True if enabled, False otherwise -def rmt_on(): - return _libgpac.gf_sys_profiler_sampling_enabled() + if hasattr(self._handler, 'on_client_data'): + _libgpac.gf_rmt_client_set_on_data_cbk(self._client, py_object(self), rmt_fun_on_client_data_cbk) -## enable or disable sampling in profiler (Remotery) - see \ref gf_sys_profiler_enable_sampling -# \param value enable or disable sampling -# \return -def rmt_enable(value): + + def _on_data(self, data, size, is_binary): + if hasattr(self._handler, 'on_client_data'): + if not is_binary: + data = data.decode("utf-8") + self._handler.on_client_data(self, data) + + + def _on_delete(self): + if hasattr(self._handler, 'on_client_data'): + err = _libgpac.gf_rmt_client_set_on_data_cbk(self._client, py_object(), None) + + if hasattr(self._handler, 'on_client_close'): + err = _libgpac.gf_rmt_client_set_on_del_cbk(self._client, py_object(), None) + self._handler.on_client_close(self) + + self._client = None + + ## get the ip+port of the client (can be used as client id) + def peer_address(self): + if self._client: + return _libgpac.gf_rmt_get_peer_address(self._client).decode("utf-8") + pass + + ## send data to the client on the websocket + def send(self, data): + print(f"client {self._client} sending {data} type {type(data)}") + if self._client: + is_binary = True + if type(data) == str: + data = data.encode('utf-8') + is_binary = False + + return _libgpac.gf_rmt_client_send_to_ws(self._client, data, len(data), is_binary) + pass + +## RMTHandler object handling the callbacks for rmtws events +# +# to be passed to \ref python.libgpac.libgpac.set_rmt_handler "set_rmt_handler()" +class RMTHandler(): + + + ## called when a new client connects to the websocket + # \param client an object of type \ref python.libgpac.libgpac.RMTClient "RMTClient" representing the new client + def on_new_client(self, client: RMTClient): + pass + + ## called when a client disconnects from the websocket + # \param client an object of type \ref python.libgpac.libgpac.RMTClient "RMTClient" representing the client + def on_client_close(self, client: RMTClient): + pass + + ## called when a client receives data on its websocket + # \param client an object of type \ref python.libgpac.libgpac.RMTClient "RMTClient" representing the client + # \param data the received data, can be either str or bytes depending on the exchanged data + def on_client_data(self, client: RMTClient, data): + pass + + + +##\cond private +_libgpac.gf_rmt_set_on_new_client_cbk.argtypes = c_void_p, py_object, c_void_p + +@CFUNCTYPE(c_int, c_void_p, c_void_p) +def rmt_fun_on_new_client_cbk(_udta, client): + obj = cast(_udta, py_object).value + + rmt_client = RMTClient(obj, client) + obj.on_new_client(rmt_client) + return 0 +##\endcond private + +## set the handler for rmt_ws +# \param callback_obj an object of type \ref python.libgpac.libgpac.RMTHandler "RMTHandler" implementing the desired callbacks +def set_rmt_handler(callback_obj): + _libgpac.user_init = True + rmt = _libgpac.gf_sys_get_rmtws() + if hasattr(callback_obj, 'on_new_client'): + err = _libgpac.gf_rmt_set_on_new_client_cbk(rmt, py_object(callback_obj), rmt_fun_on_new_client_cbk) + if err<0: + return False + + return True + +## set the handler for the user websocket server +# \param callback_obj an object of type \ref python.libgpac.libgpac.RMTHandler "RMTHandler" implementing the desired callbacks +def set_userws_handler(callback_obj): _libgpac.user_init = True - _libgpac.gf_sys_profiler_enable_sampling(value) - + rmt = _libgpac.gf_sys_get_userws() + if hasattr(callback_obj, 'on_new_client'): + err = _libgpac.gf_rmt_set_on_new_client_cbk(rmt, py_object(callback_obj), rmt_fun_on_new_client_cbk) + if err<0: + return False + + return True ## sleep for given time in milliseconds # \param value time to sleep @@ -468,10 +567,10 @@ ## @} -## +## # \defgroup pystruct_grp Structure Wrappers # \ingroup pyapi_grp -# \brief Python Structures +# \brief Python Structures # # Python Wrappers for gpac C structures used in this API # @@ -511,6 +610,7 @@ ("nb_pck_sent", c_ulonglong), ("nb_hw_pck_sent", c_ulonglong), ("nb_errors", c_uint), + ("nb_current_errors", c_uint), ("nb_bytes_sent", c_ulonglong), ("time_process", c_ulonglong), ("percent", c_int), @@ -636,7 +736,7 @@ ## \endcond ## filter prop type, as defined in libgpac and usable as a Python object -#Fields have the same types, names and semantics as \ref GF_PropUIntList +#Fields have the same types, names and semantics as \ref GF_PropStringList class PropStringList(Structure): ## \cond private _fields_ = ("vals", POINTER(c_char_p)), ("nb_items", c_uint) @@ -833,7 +933,7 @@ ## \endcond -#TODO GF_FEVT_Event; +#TODO GF_FEVT_Event ## event value, as defined in libgpac and usable as a Python object #Fields have the same types, names and semantics as \ref GF_FEVT_FileDelete @@ -876,14 +976,16 @@ ## \endcond ## event value, as defined in libgpac and usable as a Python object -#Fields have the same types, names and semantics as \ref GF_FEVT_EncodeHints -class FEVT_EncodeHints(Structure): +#Fields have the same types, names and semantics as \ref GF_FEVT_TransportHints +class FEVT_TransportHints(Structure): ## \cond private _fields_ = ("type", c_uint), ("on_pid", _gf_filter_pid), - ("intra_period", Fraction), - ("gen_dsi_only", gf_bool) + ("flags", c_uint), + ("seg_duration", Fraction), + ("gen_dsi_only", gf_bool), + ("wait_seg_boundary", gf_bool) ## \endcond @@ -1022,20 +1124,20 @@ ("play", FEVT_Play), ("seek", FEVT_SourceSeek), ("attach_scene", FEVT_AttachScene), - ("user", FEVT_UserEvent), + ("user_event", FEVT_UserEvent), ("quality_switch", FEVT_QualitySwitch), ("visibility_hint", FEVT_VisibilityHint), ("buffer_req", FEVT_BufferRequirement), ("seg_size", FEVT_SegmentSize), ("frag_size", FEVT_FragmentSize), ("file_del", FEVT_FileDelete), - ("encode_hints", FEVT_EncodeHints), + ("transport_hints", FEVT_TransportHints), ("ntp", FEVT_NTPRef) ## \endcond -## Buffer occupancy object +## Buffer occupancy object class BufferOccupancy: ##\cond private def __init__(self, max_units, nb_pck, max_dur, dur, is_final_flush): @@ -1055,7 +1157,7 @@ ## @} -## +## # \defgroup pycst_grp Constants # \ingroup pyapi_grp # \brief Constants definitions @@ -1208,6 +1310,10 @@ #see \ref GF_PROP_4CC_LIST GF_PROP_4CC_LIST=26 +##\cond private +GF_PROP_STRING_LIST_COPY=27 +##\endcond + ##\hideinitializer #see \ref GF_PROP_FIRST_ENUM GF_PROP_FIRST_ENUM=40 @@ -1336,6 +1442,13 @@ ##\hideinitializer #see GF_CAPFLAG_OPTIONAL GF_CAPFLAG_OPTIONAL = 1<<6 +##\hideinitializer +#see GF_CAPFLAG_PRESENT +GF_CAPFLAG_PRESENT = 1<<7 +##\hideinitializer +#see GF_CAPFLAG_RECONFIG +GF_CAPFLAG_RECONFIG = 1<<8 + #helpers ##\hideinitializer @@ -1581,6 +1694,9 @@ _libgpac.gf_pixel_fmt_sname.argtypes = c_uint _libgpac.gf_pixel_fmt_sname.restype = c_char_p +_libgpac.gf_audio_fmt_sname.argtypes = c_uint +_libgpac.gf_audio_fmt_sname.restype = c_char_p + @CFUNCTYPE(c_int, _gf_filter_session, c_void_p, POINTER(c_uint)) def fs_task_fun(sess, cbk, resched): @@ -1620,7 +1736,7 @@ ##\endcond -## Task object for user callbacks from libgpac scheduler +## Task object for user callbacks from libgpac scheduler class FilterTask: ## constructor for tasks #\param name name of the task (used for logging) @@ -1637,7 +1753,7 @@ return -1 -## filter session object - see \ref GF_FilterSession +## filter session object - see \ref fs_grp class FilterSession: ## constructor for filter session - see \ref gf_fs_new #\param flags session flags (int) @@ -1673,7 +1789,7 @@ ## delete an existing filter session - see \ref gf_fs_del - #\warning The filter session must be explicitly destroyed if close (\ref gf_sys_close) is called after that + #\warning The filter session must be explicitly destroyed if close (\ref gf_sys_close) is called after that #\return def delete(self): ##\cond private @@ -1719,7 +1835,7 @@ #\return def run(self): err = _libgpac.gf_fs_run(self._sess) - if err<0: + if err<0: raise Exception('Failed to run session: ' + e2s(err)) ##load source filter - see \ref gf_fs_load_source @@ -1733,7 +1849,7 @@ else: _filter = _libgpac.gf_fs_load_source(self._sess, URL.encode('utf-8'), None, None, byref(errp)) err = errp.value - if err<0: + if err<0: raise Exception('Failed to load source filter ' + URL + ': ' + e2s(err)) return self._to_filter(_filter) @@ -1748,7 +1864,7 @@ else: _filter = _libgpac.gf_fs_load_destination(self._sess, URL.encode('utf-8'), None, None, byref(errp)) err = errp.value - if err<0: + if err<0: raise Exception('Failed to load destination filter ' + URL + ': ' + e2s(err)) return self._to_filter(_filter) @@ -1760,7 +1876,7 @@ errp = c_int(0) _filter = _libgpac.gf_fs_load_filter(self._sess, fname.encode('utf-8'), byref(errp)) err = errp.value - if err<0: + if err<0: raise Exception('Failed to load filter ' + fname + ': ' + e2s(err)) return self._to_filter(_filter) @@ -1770,7 +1886,7 @@ def post(self, task): task.session = self err = _libgpac.gf_fs_post_user_task(self._sess, fs_task_fun, py_object(task), task.name.encode('utf-8')) - if err<0: + if err<0: raise Exception('Failed to post task ' + task.name + ': ' + e2s(err)) self._tasks.append(task) @@ -1786,7 +1902,7 @@ #\return def abort(self, flush=0): _libgpac.gf_fs_abort(self._sess, flush) - + ##get a filter by index - see \ref gf_fs_get_filter #\param index index of filter #\return Filter object @@ -1796,7 +1912,7 @@ raise Exception('Failed to get filter at index ' + str(index) ) return self._to_filter(f) - + ##lock the session - see \ref gf_fs_lock_filters #\param lock if True, locks otherwise unlocks #\return @@ -1859,7 +1975,7 @@ @property def http_bitrate(self): return _libgpac.gf_fs_get_http_rate(self._sess) - + @property def http_max_bitrate(self): return _libgpac.gf_fs_get_http_max_rate(self._sess) @@ -1889,6 +2005,10 @@ _libgpac.gf_filter_get_id.restype = c_char_p _libgpac.gf_filter_get_ipid_count.argtypes = _gf_filter _libgpac.gf_filter_get_opid_count.argtypes = _gf_filter +_libgpac.gf_filter_get_status.argtypes = _gf_filter +_libgpac.gf_filter_get_status.restype = c_char_p +_libgpac.gf_filter_get_bytes_done.argtypes = _gf_filter + @@ -1941,6 +2061,9 @@ _libgpac.gf_codecid_file_ext.argtypes = c_uint _libgpac.gf_codecid_file_ext.restype = c_char_p +_libgpac.gf_filter_pid_get_owner.argtypes = _gf_filter_pid +_libgpac.gf_filter_pid_get_owner.restype = _gf_filter + _libgpac.gf_filter_pid_get_source_filter.argtypes = _gf_filter_pid _libgpac.gf_filter_pid_get_source_filter.restype = _gf_filter @@ -1955,6 +2078,9 @@ _libgpac.gf_filter_get_info_str.argtypes = _gf_filter, c_char_p, POINTER(POINTER(_gf_property_entry)) _libgpac.gf_filter_get_info_str.restype = POINTER(PropertyValue) +_libgpac.gf_filter_get_arg.argtypes = _gf_filter, c_char_p, POINTER(PropertyValue) +_libgpac.gf_filter_get_arg.restype = gf_bool + _libgpac.gf_filter_require_source_id.argtypes = _gf_filter _libgpac.gf_filter_probe_link.argtypes = _gf_filter, c_uint, c_char_p, POINTER(c_char_p) @@ -2033,34 +2159,34 @@ ## even values are header names, odd values are header values self.headers_out= - ## throttle the connection - if not overriden by subclass, not used + ## throttle the connection - if not overridden by subclass, not used #\param done amount of bytes of ressource sent #\param total total size of ressource #\return a timeout in microseconds, or 0 to process immediately def throttle(self, done, total): return 0 - ## read data for the request - if not overriden by subclass, not used + ## read data for the request - if not overridden by subclass, not used #\param buf NP array (or c_ubyte pointer if no numpy support) to write data to #\param size size of array to fill #\return amount of bytes read, negative value means no data available yet, 0 means end of file def read(self, buf, size): return 0 - ## write data for the request (PUT/POST) - if not overriden by subclass, not used + ## write data for the request (PUT/POST) - if not overridden by subclass, not used #\param buf NP array (or c_ubyte pointer if no numpy support) containing data from client #\param size number of valid bytes in the array #\return def write(self, buf, size): pass - ## close callback for the request - if not overriden by subclass, not used - #\param reason GPAC error code of the end of session + ## close callback for the request - if not overridden by subclass, not used + #\param reason GPAC error code of the end of session. If 1 (GF_EOS), the session is ended but underlying network is kept alive, otherwise session is destroyed #\return def close(self, reason): pass - ## callback for the request - this shoulld be overriden by subclass, default behaviour being to delegate to GPAC + ## callback for the request - this shoulld be overridden by subclass, default behaviour being to delegate to GPAC #\param method HTTP method used, as string #\param url URL of the HTTP request #\param auth_code Authentication reply code - requests are pre-identified using GPAC credentials: a value of 401 indicates no identification, 200 indicates identification OK, 403 indicates failure @@ -2069,7 +2195,7 @@ def on_request(self, method, url, auth_code, headers): self.send() - ## Send the reply to the client. This can be called aither upon \ref on_request or later (asynchronously) + ## Send the reply to the client. This can be called aither upon \ref python.libgpac.libgpac.HTTPOutRequest.on_request "on_request()" or later (asynchronously) #\return def send(self): hdrs = None @@ -2077,10 +2203,10 @@ if nb_hdrs: hdrs = (POINTER(c_char)*nb_hdrs)() i=0 - for str in self.headers_out: - hdrsi = create_string_buffer(str.encode('utf-8')) + for hdr in self.headers_out: + hdrsi = create_string_buffer(hdr.encode('utf-8')) i+=1 - body = None; + body = None if self.body: body = self.body.encode('utf-8') ret = _libgpac.gf_httpout_send_request(self._session, py_object(self), self.reply, body, nb_hdrs, hdrs, @@ -2090,7 +2216,7 @@ httpout_cbk_close ) ## \cond private - self._skip_close = True if type(self).close == HTTPOutRequest.close else False; + self._skip_close = True if type(self).close == HTTPOutRequest.close else False ## \endcond ## @} @@ -2109,7 +2235,7 @@ headers= for i in range(nb_hdrs): headers.append(hdrsi.decode('utf-8')) - req.on_request(method.decode('utf-8'), url.decode('utf-8'), auth_code, headers); + req.on_request(method.decode('utf-8'), url.decode('utf-8'), auth_code, headers) return 0 @@ -2143,6 +2269,8 @@ ("ast_offset", c_double), ("avg_duration", c_double), ("sizes", _gf_list), + ("hls_variant_url", c_char_p), + ("ssr", c_uint), class DASHByteRange(Structure): @@ -2174,7 +2302,7 @@ ## \endcond -## +## # \defgroup pydash_grp DASH custom algorithm # \ingroup pyapi_grp # \brief Python API for libgpac DASH client. @@ -2217,6 +2345,12 @@ self.ast_offset = qinfon.ast_offset ## Average segment duration in seconds, 0 if unknown self.avg_duration = qinfon.avg_duration + ## HLS variant name + self.hls_variant_url = None + if qinfon.hls_variant_url != None: + self.hls_variant_url = qinfon.hls_variant_url.decode('utf-8') + ## SSR representation, estimated number of parts (subsegments), 1 if unknown, 0 if not SSR + self.ssr = qinfon.ssr ## list of segment sizes for VoD cases, None otherwise or if unknown self.sizes = None ## \cond private @@ -2387,22 +2521,22 @@ # - 0: end of period (groups are no longer valid) # - 1: start of a static period # - 2: start of a dynamic (live) period - #\return + #\return def on_period_reset(self, reset_type): pass ##Callback (optional) called when a new group (adaptation set) is created - #\param group the newly created \ref DASHGroup - #\return + #\param group the newly created \ref python.libgpac.libgpac.DASHGroup + #\return def on_new_group(self, group): pass ##Callback (mandatory) called at the end of the segment download to perform rate adaptation - #\param group the \ref DASHGroup on which to perform adaptation - #\param base_group the associated base \ref DASHGroup (tiling only), or None if no base group + #\param group the \ref python.libgpac.libgpac.DASHGroup "DASHGroup" on which to perform adaptation + #\param base_group the associated base \ref python.libgpac.libgpac.DASHGroup "DASHGroup" (tiling only), or None if no base group #\param force_low_complexity indicates that the client would like a lower complexity (typically because it is dropping frames) - #\param stats the \ref DASHGroupStatistics for the downloaded segment + #\param stats the \ref python.libgpac.libgpac.DASHGroupStatistics for the downloaded segment #\return value can be: # - new quality index, # - -1 to take no decision @@ -2412,8 +2546,8 @@ pass ##Callback (optional) called on regular basis during a segment download - #\param group the \ref DASHGroup associated with the current download - #\param stats the \ref DASHGroupDownloadStatistics for the download + #\param group the \ref python.libgpac.libgpac.DASHGroup "DASHGroup" associated with the current download + #\param stats the \ref python.libgpac.libgpac.DASHGroupDownloadStatistics "DASHGroupDownloadStatistics" for the download #\return value can be: # - `-1` to continue download # - `-2` to abort download but without retrying to downloading the same segment at lower quality @@ -2475,6 +2609,8 @@ return _libgpac.gf_stream_type_name(prop.value.uint).decode('utf-8') if pname=="PixelFormat": return _libgpac.gf_pixel_fmt_sname(prop.value.uint).decode('utf-8') + if pname=="AudioFormat": + return _libgpac.gf_audio_fmt_sname(prop.value.uint).decode('utf-8') if pname=="CodecID": cid = _libgpac.gf_codecid_file_ext(prop.value.uint).decode('utf-8') names=cid.split('|') @@ -2510,7 +2646,7 @@ if ptype==GF_PROP_VEC4I: return prop.value.vec4i if ptype==GF_PROP_STRING or ptype==GF_PROP_STRING_NO_COPY or ptype==GF_PROP_NAME: - return prop.value.string.decode('utf-8') + return prop.value.string.decode('utf-8') if prop.value.string else "" if ptype==GF_PROP_DATA or ptype==GF_PROP_DATA_NO_COPY or ptype==GF_PROP_CONST_DATA: return prop.value.data if ptype==GF_PROP_POINTER: @@ -2530,7 +2666,7 @@ if ptype==GF_PROP_4CC_LIST: res = for i in range(prop.value.uint_list.nb_items): - val = _libgpac.gf_4cc_to_str(prop.value.uint).decode('utf-8') + val = _libgpac.gf_4cc_to_str(prop.value.uint_list.valsi).decode('utf-8') res.append(val) return res if ptype==GF_PROP_SINT_LIST: @@ -2572,6 +2708,12 @@ ##number of output pids for that filter, readonly - see \ref gf_filter_get_opid_count #\hideinitializer self.nb_opid=0 + ##status string for some filters, readonly - see \ref gf_filter_get_status + #\hideinitializer + self.status=0 + ##bytes processed, readonly - see \ref gf_filter_get_bytes_done + #\hideinitializer + self.bytes_done=0 ## \cond private @@ -2663,6 +2805,11 @@ prop = _libgpac.gf_filter_pid_get_property_str(pid, _name) if prop: return _prop_to_python(prop_name, prop.contents) + else: + pypid = FilterPid(self, pid, None) + if hasattr(pypid, prop_name): + return getattr(pypid, prop_name) + return None def _pid_prop(self, idx, prop_name, IsInput): @@ -2672,7 +2819,7 @@ pid = _libgpac.gf_filter_get_opid(self._filter, idx) if not pid: raise Exception('No PID with index ' + str(idx) + ' in filter ' + self.name ) - return self._pid_prop_ex(self, prop_name, pid, False) + return self._pid_prop_ex(prop_name, pid, False) def _pid_enum_props_ex(self, callback_obj, _pid): if hasattr(callback_obj, 'on_prop_enum')==False: @@ -2706,7 +2853,7 @@ self._pid_enum_props_ex(callback_obj, pid) - ## \endcond + ## \endcond ##get an input pid property by name #\param idx index of input pid @@ -2784,7 +2931,7 @@ f_idx=0 while True: f = _libgpac.gf_filter_pid_enum_destinations(pid, f_idx) - f_idx+=1 + f_idx+=1 if f==None: break _filter = self._session._to_filter(f) @@ -2806,6 +2953,40 @@ a_idx+=1 return res + ##gets the current value of an argument of the filter - see \ref gf_filter_get_arg + #\param arg_name name of argument (as python str) + #\return argument value or None if not found + def get_arg_value(self, arg_name): + prop = PropertyValue() + res = _libgpac.gf_filter_get_arg(self._filter, arg_name.encode('utf-8'), byref(prop)) + if res: + return _prop_to_python(arg_name, prop) + else: + return None + + + ##gets all arguments of filter with type, description, and value as a python dict + #\return array of dictionnary structure containing arguments details + def all_args_value(self): + res = + + for arg in self.all_args(): + arg_name = arg.name.decode('utf-8') + argval = self.get_arg_value(arg_name) + + res.append( { + 'name': arg_name, + 'type': _libgpac.gf_props_get_type_name(arg.type).decode('utf-8'), + 'value': str(argval), + 'description': arg.description.decode('utf-8'), + 'default': "" if not arg.default else arg.default.decode('utf-8'), + 'min_max_enum': "" if not arg.min_max_enum else arg.min_max_enum.decode('utf-8'), + }) + + return res + + + ##gets a property info on a filter - see \ref gf_filter_get_info and \ref gf_filter_get_info_str #\param prop_name property to query #\return property value or None if not found @@ -2828,7 +3009,7 @@ def get_statistics(self): stats = FilterStats() err = _libgpac.gf_filter_get_stats(self._filter, byref(stats)) - if err<0: + if err<0: raise Exception('Failed to fetch filter stats: ' + e2s(err)) return stats @@ -2840,7 +3021,7 @@ ##Resolves link from given output pid of filter to a filter description. The described filter is not loaded in the graph - see \ref gf_filter_probe_link #\param opid_idx 0-based index of the output pid - #\param name filter description to link to; this can be any filter description + #\param name filter description to link to - this can be any filter description #\return None if no possible link or a list containing the filters in the resolved chain from current filter to destination def probe_link(self, opid_idx, name): links = c_char_p(0) @@ -2876,7 +3057,7 @@ err = _libgpac.gf_filter_bind_dash_algo_callbacks(self._filter, py_object(object), dash_period_reset, dash_group_new, dash_rate_adaptation, dash_download_monitor) else: err = _libgpac.gf_filter_bind_dash_algo_callbacks(self._filter, py_object(object), dash_period_reset, dash_group_new, dash_rate_adaptation, None) - if err<0: + if err<0: raise Exception('Failed to bind dash algo: ' + e2s(err)) return 0 @@ -2896,7 +3077,7 @@ # #Binds the given object to the underlying filter for callbacks override - only supported by DASH demuxer for the current time # - #For DASH, the object must derive from or implement the methods of the \ref DASHCustomAlgorithm class: + #For DASH, the object must derive from or implement the methods of the \ref python.libgpac.libgpac.DASHCustomAlgorithm "DASHCustomAlgorithm" class: # #\param object object to bind #\return @@ -2915,7 +3096,10 @@ @property def ID(self): - return _libgpac.gf_filter_get_id(self._filter).decode('utf-8') + f_id = _libgpac.gf_filter_get_id(self._filter); + if not f_id: + return None + return f_id.decode('utf-8') @property def nb_ipid(self): @@ -2925,7 +3109,15 @@ def nb_opid(self): return _libgpac.gf_filter_get_opid_count(self._filter) - ##\endcond + @property + def status(self): + return _libgpac.gf_filter_get_status(self._filter).decode('utf-8') + + @property + def bytes_done(self): + return _libgpac.gf_filter_get_bytes_done(self._filter) + + ##\endcond @@ -2963,7 +3155,8 @@ _libgpac.gf_cicp_parse_color_matrix.argtypes = c_char_p _libgpac.gf_cicp_parse_color_matrix.restype = c_uint - +def check_integer(val): + return (isinstance(val,float) and val%1==val) or isinstance(val,int) def _make_prop(prop4cc, propname, prop, custom_type=0): prop_val = PropertyValue() @@ -2983,6 +3176,12 @@ elif propname=="CodecID": prop_val.value.uint = _libgpac.gf_codecid_parse(prop.encode('utf-8')) return prop_val + elif propname=="PixelFormat": + prop_val.value.uint = _libgpac.gf_pixel_fmt_parse(prop.encode('utf-8')) + return prop_val + elif propname=="AudioFormat": + prop_val.value.uint = _libgpac.gf_audio_fmt_parse(prop.encode('utf-8')) + return prop_val if ptype==GF_PROP_SINT: prop_val.value.sint = prop @@ -2998,23 +3197,23 @@ prop_val.value.boolean = prop elif ptype==GF_PROP_FRACTION: if hasattr(prop, 'den') and hasattr(prop, 'num'): - prop_val.value.frac.num = prop.num - prop_val.value.frac.den = prop.den - elif is_integer(prop): - prop_val.value.frac.num = prop + prop_val.value.frac.num = int(prop.num) + prop_val.value.frac.den = int(prop.den) + elif check_integer(prop): + prop_val.value.frac.num = int(prop) prop_val.value.frac.den = 1 else: - prop_val.value.frac.num = 1000*prop + prop_val.value.frac.num = int(1000*prop) prop_val.value.frac.den = 1000 elif ptype==GF_PROP_FRACTION64: if hasattr(prop, 'den') and hasattr(prop, 'num'): - prop_val.value.lfrac.num = prop.num - prop_val.value.lfrac.den = prop.den - elif is_integer(prop): - prop_val.value.lfrac.num = prop + prop_val.value.lfrac.num = int(prop.num) + prop_val.value.lfrac.den = int(prop.den) + elif check_integer(prop): + prop_val.value.lfrac.num = int(prop) prop_val.value.lfrac.den = 1 else: - prop_val.value.lfrac.num = 1000000*prop + prop_val.value.lfrac.num = int(1000000*prop) prop_val.value.lfrac.den = 1000000 elif ptype==GF_PROP_FLOAT: prop_val.value.fnumber = prop @@ -3056,39 +3255,43 @@ elif ptype==GF_PROP_STRING_LIST: if isinstance(prop, list)==False: raise Exception('Property is not a list') - prop_val.value.string_list.nb_items = len(prop) - prop_val.value.string_list.vals = (POINTER(c_char) * len(prop))() - i=0 - for str in list: - prop_val.value.string_list.valsi = create_string_buffer(str.encode('utf-8')) - i+=1 + #force copy of prop + prop_val.type = GF_PROP_STRING_LIST_COPY + nb_vals = len(prop) + prop_val.value.string_list.nb_items = nb_vals + prop_val.value.string_list.vals = cast((POINTER(c_char) * nb_vals)(), POINTER(c_char_p)) + for i, s_val in enumerate(prop): + prop_val.value.string_list.valsi = cast(create_string_buffer(s_val.encode('utf-8')), c_char_p) elif ptype==GF_PROP_UINT_LIST: if isinstance(prop, list)==False: raise Exception('Property is not a list') - prop_val.value.uint_list.nb_items = len(prop) - prop_val.value.uint_list.vals = (c_uint * len(prop))(*prop) + nb_vals = len(prop) + prop_val.value.uint_list.nb_items = nb_vals + prop_val.value.uint_list.vals = (c_uint * nb_vals)(*prop) elif ptype==GF_PROP_4CC_LIST: if isinstance(prop, list)==False: raise Exception('Property is not a list') - prop_val.value.uint_list.nb_items = len(prop) - prop_val.value.uint_list.vals = (c_uint * len(prop)) - i=0 - for str in list: - prop_val.value.uint_list.valsi = _libgpac.gf_4cc_parse( str.encode('utf-8') ) - i+=1 + nb_vals = len(prop) + prop_val.value.uint_list.nb_items = nb_vals + prop_val.value.uint_list.vals = (c_uint * nb_vals)() + for i, s_val in enumerate(prop): + prop_val.value.uint_list.valsi = _libgpac.gf_4cc_parse( s_val.encode('utf-8') ) elif ptype==GF_PROP_SINT_LIST: if isinstance(prop, list)==False: raise Exception('Property is not a list') - prop_val.value.sint_list.nb_items = len(prop) - prop_val.value.sint_list.vals = (c_int * len(prop))(*prop) + nb_vals = len(prop) + prop_val.value.sint_list.nb_items = nb_vals + prop_val.value.sint_list.vals = (c_int * nb_vals)(*prop) elif ptype==GF_PROP_VEC2I_LIST: if isinstance(prop, list)==False: raise Exception('Property is not a list') - prop_val.value.sint_list.nb_items = len(prop) - prop_val.value.v2i_list.vals = (GF_PropVec2i * len(prop)) - for i in range (len(prop)): - prop_val.value.v2i_list.valsi.x = propi.x - prop_val.value.v2i_list.valsi.y = propi.y + nb_vals = len(prop) + prop_val.value.sint_list.nb_items = nb_vals + prop_val.value.v2i_list.vals = (PropVec2i * nb_vals)() + for i, vec in enumerate (prop): + print('obj is ' + str(vec) ) + prop_val.value.v2i_list.valsi.x = vec.x + prop_val.value.v2i_list.valsi.y = vec.y elif _libgpac.gf_props_type_is_enum(ptype): prop_val.value.uint = _libgpac.gf_props_parse_enum(ptype, prop.encode('utf-8')) @@ -3158,23 +3361,6 @@ return 1 return 0 -_libgpac.gf_filter_set_probe_data_cbk.argtypes = _gf_filter, c_void_p -@CFUNCTYPE(c_int, POINTER(c_ubyte), c_uint, POINTER(c_uint) ) -def filter_cbk_probe_data(_data, _size, _probe): - obj = _libgpac.gf_filter_get_rt_udta(_f) - _filter = cast(obj, py_object).value - if numpy_support: - ar_data = np.ctypeslib.as_array(_data, (_size,)) - ar_data.flags.writeable=False - res = _filter.probe_data(ar_data, _size) - else: - res = _filter.probe_data(_data, _size) - if res==None: - _probe.contents=0 - return None - _probe.contents=2 #GF_FPROBE_MAYBE_SUPPORTED - return res.encode('utf-8') - _libgpac.gf_filter_set_reconfigure_output_ckb.argtypes = _gf_filter, c_void_p @CFUNCTYPE(c_int, _gf_filter, _gf_filter_pid ) @@ -3236,12 +3422,19 @@ errp = c_int(0) _filter = _libgpac.gf_fs_new_filter(session._sess, fname.encode('utf-8'), flags | GF_FS_REG_MAIN_THREAD, byref(errp)) err = errp.value - if err<0: - raise Exception('Failed to create filter ' + URL + ': ' + e2s(err)) + if err<0: + raise Exception('Failed to create filter ' + fname + ': ' + e2s(err)) #create base class Filter.__init__(self, session, _filter) - #register with our filter bank - session._filters.append(self) + #filter is registered with our filter bank (always done in callback from fs_new_filter) as a generic gpac.Filter object + #remove it and add this new custom filter + #we browse the list for safety, although with current code the new gpac.Filter object is always the last one, so pop() would be enough + for f in session._filters: + if self._filter != f._filter: + continue + session._filters.remove(f) + session._filters.append(self) + break #setup callback udta and callback functions _libgpac.gf_filter_set_rt_udta(_filter, py_object(self) ) @@ -3254,9 +3447,6 @@ if hasattr(self, 'process_event'): _libgpac.gf_filter_set_process_event_ckb(self._filter, filter_cbk_process_event) - if hasattr(self, 'probe_data'): - _libgpac.gf_filter_set_probe_data_ckb(self._filter, filter_cbk_probe_data) - if hasattr(self, 'reconfigure_output'): _libgpac.gf_filter_set_reconfigure_output_ckb(self._filter, filter_cbk_reconfigure_output) @@ -3312,7 +3502,7 @@ prop_val = _make_prop(prop_4cc, pcode, prop, custom_type) err = _libgpac.gf_filter_push_caps(self._filter, prop_4cc, byref(prop_val), prop_name, flag, priority) - if err<0: + if err<0: raise Exception('Failed to push cap: ' + e2s(err)) ##create a new output pid for this filter - see \ref gf_filter_pid_new @@ -3412,19 +3602,19 @@ def nb_evts_queued(self): return _libgpac.gf_filter_get_num_events_queued(self._filter) - @property + @property def clock_hint_time(self): val = c_ulonglong(0) _libgpac.gf_filter_get_clock_hint(self._filter, byref(val), None) return val.value - @property + @property def clock_hint_mediatime(self): - val = Fraction64 + val = Fraction64() _libgpac.gf_filter_get_clock_hint(self._filter, None, byref(val)) - return val.value + return val - @property + @property def connections_pending(self): return _libgpac.gf_filter_connections_pending(self._filter) @@ -3609,6 +3799,9 @@ ##True if buffer is full, readonly - see \ref gf_filter_pid_query_buffer_duration #\hideinitializer self.buffer_full=0 + ##total level of buffer, readonly - see \ref gf_filter_pid_query_buffer_duration + #\hideinitializer + self.buffer_total=0 ##True if no pending packet, readonly - see \ref gf_filter_pid_first_packet_is_empty #\hideinitializer self.first_empty=0 @@ -3698,7 +3891,7 @@ err = _libgpac.gf_filter_pid_copy_properties(self._pid, ipid._pid) if err<0: raise Exception('Cannot copy properties: ' + e2s(err) ) - + ##removes all properties of the current pid - see \ref gf_filter_pid_reset_properties #\return def reset_props(self): @@ -3830,7 +4023,7 @@ dur = c_uint(0) in_final_flush = _libgpac.gf_filter_pid_get_buffer_occupancy(self._pid, byref(max_units), byref(nb_pck), byref(max_dur), byref(dur) ) in_final_flush = not in_final_flush - return BufferOccupancy(max_units, nb_pck, max_dur, dur, not_in_final_flush) + return BufferOccupancy(max_units, nb_pck, max_dur, dur, in_final_flush) ##sets loose connect mode - see \ref gf_filter_pid_set_loose_connect #\return @@ -3890,7 +4083,7 @@ prop_val = _libgpac.gf_filter_pid_caps_query_str(self._pid, _pname) if prop_val: - return _prop_to_python(pname, prop.contents) + return _prop_to_python(prop_name, prop_val.contents) return None ##negotiates a capability property on input PID - see \ref gf_filter_pid_negotiate_property and \ref gf_filter_pid_negotiate_property_dyn @@ -3953,7 +4146,7 @@ return None ##creates a new packet sharing memory of the filter - see \ref gf_filter_pck_new_shared - #The filter object must have a `packet_release` method with arguments FilterPid, FilterPacket + #The filter object must have a `packet_release` method with arguments FilterPid, FilterPacket #\param data the data to use. If NumPy is detected, accept a NP Array. Otherwise data is type casted into POINTER(c_ubyte) #\return the new FilterPacket or None if failure def new_pck_shared(self, data): @@ -4091,6 +4284,12 @@ else: return False + ##total buffer level - see \ref gf_filter_pid_query_buffer_duration + #\return + @property + def buffer_total(self): + return _libgpac.gf_filter_pid_query_buffer_duration(self._pid, True) + ##True if no pending packet - see \ref gf_filter_pid_first_packet_is_empty #\return @property @@ -4264,7 +4463,7 @@ ##\endcond private -## OpenGL texture info +## OpenGL texture info class GLTextureInfo: ##\cond private def __init__(self, gl_id, gl_format): @@ -4422,7 +4621,7 @@ ##creates a new packet cloning a source packet - see \ref gf_filter_pck_dangling_copy. #The resulting packet is read/write mode and may have its own memory allocated. #This is typically used by sink filters wishing to access underling GPU data of a packet using frame interface. - #the resulting packet can be explicitly discarded using \ref discard, otherwise will be garbage collected. + #the resulting packet can be explicitly discarded using \ref python.libgpac.libgpac.FilterPacket.discard "discard()", otherwise will be garbage collected. #\param cached_pck if set, will be reuse for creation of new packet. This can greatly reduce memory allocations #\return the new FilterPacket or None if failure or None if failure ( if grabbing the frame into a local copy failed) def clone(self, cached_pck=None): @@ -4583,7 +4782,7 @@ data = np.ctypeslib.as_array(data, (size.value,)) if self._readonly: data.flags.writeable=False - return data + return data @property def start(self): @@ -4666,7 +4865,7 @@ return _libgpac.gf_filter_pck_get_roll_info(self._pck) @roll.setter - def roll(self): + def roll(self, value): if self._is_src: raise Exception('Cannot set roll on source packet') return _libgpac.gf_filter_pck_set_roll_info(self._pck, value) @@ -4706,7 +4905,7 @@ return _libgpac.gf_filter_pck_get_seq_num(self._pck) @seqnum.setter - def seqnum(self): + def seqnum(self, value): if self._is_src: raise Exception('Cannot set segnum on source packet') return _libgpac.gf_filter_pck_set_seq_num(self._pck, value) @@ -4798,7 +4997,7 @@ mode = _mode.decode('utf-8') error.contents.value=GF_OK if mode=="url": - path = url_concatenate(_libgpac.gf_fileio_resource_url(_fio_ref), _url); + path = url_concatenate(_libgpac.gf_fileio_resource_url(_fio_ref), _url) new_gfio = FileIO(path, fio_ref) fio_ref.factory.pending_urls.append(new_gfio) #prevent garbage collection @@ -4814,7 +5013,7 @@ if mode=="probe": if not hasattr(fio_ref.factory.root_obj, 'exists'): return None - if not fio_ref.factory.root_obj.exists(url): + if not fio_ref.factory.root_obj.exists(_url.decode('utf-8')): error.contents.value=GF_URL_ERROR return None @@ -4869,7 +5068,7 @@ fio_ref.factory.all_refs-=1 fio_ref.factory.pending_urls.remove(afio) fio = afio - break; + break #we need to create a new FileIO created=False @@ -4978,7 +5177,7 @@ #Writes the file #- buffer: numpy array to fill if numpy support, ctypes.c_ubyte otherwise #- size: number of bytes to write starting from first byte in buffer -#- return number of bytes writen, at most the size of the array +#- return number of bytes written, at most the size of the array # # \code int read(numy buffer, unsigned long size)\endcode #Reads the file
View file
gpac-26.02.0.tar.gz/share/res/ca-bundle.crt
Added
@@ -0,0 +1,3581 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Mon Mar 11 15:25:27 2024 GMT +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 4d96bd539f4719e9ace493757afbe4a23ee8579de1c97fbebc50bba3c12e8c1e +## + + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG +A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj +dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw +NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD +ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz +2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b +bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 +NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd +gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW +VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f +Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ +juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas +DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS +VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI +04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl +0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd +Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY +YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp +adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x +xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP +jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM +MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z +ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r +AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +Security Communication RootCA3 +============================== +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw +IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD +b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw +CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE +AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r +hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE +NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2 +/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm +npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY +XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK +p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC +3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf +GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw +CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS +YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu +Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O +H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx +YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ +XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml ++LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn +KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9 +dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm +6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg== +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- + +BJCA Global Root CA1 +==================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG +EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK +Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG +A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD +DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm +CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS +sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn +P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW +yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj +eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn +MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b +OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh +GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK +H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB +AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ +dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 +60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh +TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW +4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp +GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx +4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps +3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S +SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= +-----END CERTIFICATE----- + +BJCA Global Root CA2 +==================== +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD +TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg +R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE +BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC +SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl +SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK +/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI +1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 +W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g +UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx +eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot +6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2 +Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW +pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M +MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE +SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9 +Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7 +3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft +nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6 +uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq +ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs +vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c +Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif +BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9 +lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo +KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH ++VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4 +5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM +3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck +jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf +Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W +NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+ +o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/ +oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc +1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM +6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V +rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx +7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC +e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W +Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp +M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf +hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr +eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE +VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t +Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx +cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF +1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa +MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd +gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O +HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm +YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr +dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ +iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN +lM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- + +Telekom Security TLS ECC Root 2020 +================================== +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl +a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz +NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg +R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG +SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1 +2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC +MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ +Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU +ga/sf+Rn27iQ7t0l +-----END CERTIFICATE----- + +Telekom Security TLS RSA Root 2023 +================================== +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU +ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy +NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp +dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC +KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP +GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx +UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo +l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9 +FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v +zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg +rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML +KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S +WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+ +sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp +kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy +/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4 +mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz +aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa +oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8 +wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE +HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0 +o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE-----
View file
gpac-26.02.0.tar.gz/share/rmtws
Added
+(directory)
View file
gpac-26.02.0.tar.gz/share/rmtws/index.html
Added
@@ -0,0 +1,50 @@ +<html> + <head> + + </head> + <body> + + <pre id="precontent"> + + </pre> + <script> + +let Socket = new WebSocket("ws://localhost:6363"); +Socket.binaryType = "arraybuffer"; + +Socket.onopen = function (evt) { + console.log("ws connected", evt); + + Socket.send("json:" + JSON.stringify({'message': 'get_all_filters'})); +}; + +function handleWSmessage(text) { + jtext = JSON.parse(text); + + if ("message" in jtext) { + + if ( (jtext'message' == "filters" || jtext'message' == "update") && "filters" in jtext ) { + + ppfilters = JSON.stringify(jtext'filters', null, 2); + document.getElementById("precontent").innerText = ppfilters; + + } + } +} + +Socket.onmessage = function (msg) { + + if (typeof(msg.data)=="string") { + decoded = msg.data; + handleWSmessage(decoded); + } else { + decoded = new Uint8Array(msg.data); + // do something with binary messages? + } + +}; + + </script> + + </body> +</html> \ No newline at end of file
View file
gpac-26.02.0.tar.gz/share/rmtws/jsrmt.js
Added
@@ -0,0 +1,288 @@ +import { Sys as sys } from 'gpaccore' + + +let all_filters = ; + + + +session.reporting(true); + + + +function on_all_connected(cb) { + + session.post_task( ()=> { + + let local_connected = true; + let all_js_filters = ; + + session.lock_filters(true); + + for (let i=0; i<session.nb_filters; i++) { + let f = session.get_filter(i); + + if (f.is_destroyed()) continue; + + if (!f.nb_opid && !f.nb_ipid) { + local_connected = false; + print("Filter not connected: "); + print(JSON.stringify(gpac_filter_to_object(f))); + break; + } + + all_js_filters.push(gpac_filter_to_object(f)); + } + + session.lock_filters(false); + + if (local_connected) { + cb(all_js_filters); // should prop be inside the lock? + return false; + } + + return 2000; + }); + +} + + + + +let filter_props_lite = 'name', 'status', 'bytes_done', 'type', 'ID', 'nb_ipid', 'nb_opid', 'idx', 'itag' +let filter_args_lite = +let pid_props_lite = + +function gpac_filter_to_object(f, full=false) { + let jsf = {}; + + for (let prop in f) { + if (full || filter_props_lite.includes(prop)) + jsfprop = fprop; + } + + jsf'gpac_args' = ; // filtrer par type de filtre ? + + if (full) { //TODO: remove tmp hack to avoid pfmt error on ffenc + // let all_args = f.all_args(false); // full args + let all_args = f.all_args(true); // full args => error in js interface (param is reverse of value_only) + // console.log(JSON.stringify(all_args)); + for (let arg of all_args) { + if (arg && (full || filter_args_lite.includes(arg.name))) + jsf'gpac_args'.push(arg) + + } + } + + jsf'ipid' = {}; + jsf'opid' = {}; + + for (let d=0; d<f.nb_ipid; d++) { + let pidname = f.ipid_props(d, "name"); + let jspid = {}; + + f.ipid_props(d, (name, type, val) => { + if (full || pid_props_lite.includes(name)) + jspidname = {'type': type, 'val': val}; + + }); + jspid"buffer" = f.ipid_props(d, "buffer"); + // jspid"buffer_total" = f.ipid_props(d, "buffer_total"); + jspid'source_idx' = f.ipid_source(d).idx; + + jsf'ipid'pidname = jspid; + } + + for (let d=0; d<f.nb_opid; d++) { + let pidname = f.opid_props(d, "name"); + let jspid = {}; + + f.opid_props(d, (name, type, val) => { + if (full || pid_props_lite.includes(name)) + jspidname = {'type': type, 'val': val}; + + }); + jspid"buffer" = f.opid_props(d, "buffer"); + // jspid"buffer_total" = f.opid_props(d, "buffer_total"); + jsf'opid'pidname = jspid; + } + + return jsf; + +} + +let filter_uid = 0; + +session.set_new_filter_fun( (f) => { + print("new filter " + f.name); + f.idx = filter_uid++; + f.iname = ''+f.idx; + + all_filters.push(f); + + console.log("NEW FILTER ITAG " + f.itag); + if (f.itag == "NODISPLAY") + return + + +} ); + +session.set_del_filter_fun( (f) => { + print("delete filter " + f.iname + " " + f.name); + let idx = all_filters.indexOf(f); + if (idx>=0) + all_filters.splice(idx, 1); + + console.log("RM FILTER ITAG " + f.itag); + if (f.itag == "NODISPLAY") + return + + +}); + +session.set_event_fun( (evt) => { + +}); + + + + + + + + +let all_clients = ; +let cid = 0; + + +let remove_client = function(client_id) { + for (let i = 0; i<all_clients.length; i++) { + if (all_clientsi.id == client_id) { + all_clients.splice(i,1); + return + } + } +} + +function JSClient(id, client) { + this.id = id; + this.client = client; + + this.details_needed = {}; + + this.on_client_data = function(msg) { + + console.log("All clients:"); + for (let jc of all_clients) { + console.log("Client ", jc.id, jc.client.peer_address ); + } + + console.log("on_client_data on client id ", this.id, " len ", msg.length, msg); + console.log("this has peer:", this.client.peer_address); + + let text = msg; + if (text.startsWith("json:")) { + try { + let jtext = JSON.parse(text.substr(5)); + if (!('message' in jtext)) return; + + if ( jtext'message' == 'get_all_filters' ) { + print("Sending all filters when ready"); + this.send_all_filters(); + + } + + } catch(e) { + console.log(e); + } + } + + } + + + this.send_all_filters = function () { + + on_all_connected( (all_js_filters) => { + + print("----- all connected -----"); + print(JSON.stringify(all_js_filters, null, 1)); + print("-------------------------"); + + this.client.send(JSON.stringify({ 'message': 'filters', 'filters': all_js_filters })); + + session.post_task( ()=> { + + let js_filters = ; + + session.lock_filters(true); + + for (let i=0; i<session.nb_filters; i++) { + let f = session.get_filter(i); + js_filters.push(gpac_filter_to_object(f)); + } + + session.lock_filters(false); + + if (this.client) { + this.client.send(JSON.stringify({ 'message': 'update', 'filters': js_filters })); + } + + + + return 5000; + }); + + + }); + } + + + + +} + +sys.enable_rmtws(); + +sys.rmt_on_new_client = function(client) { + console.log("rmt on client"); + print(typeof(client)); + + let js_client = new JSClient(++cid, client); + + all_clients.push(js_client); + + console.log("New ws client ", js_client.id, " gpac peer ", js_client.client.peer_address); + + js_client.client.on_data = (msg) => { + if (typeof(msg) == "string") + js_client.on_client_data(msg); + else { + let buf = new Uint8Array(msg) + console.log("Got binary message of type", typeof(msg), "len ", buf.length, "with data:", buf); + + } + } + + js_client.client.on_close = function() { + console.log("ON_CLOSE on client ", js_client.id, " ", client.peer_address); + remove_client(js_client.id); + js_client.client = null; + } +} + + +sys.enable_userws(); + +sys.userws_on_new_client = function(client) { + console.log("userws new client", client.peer_address); + + client.on_data = (msg) => { + + console.log("Client ", client.peer_address, " got message: ", msg); + + client.send("ACK"); + } + + client.on_close = function() { + console.log("ON_CLOSE on client ", client.peer_address); + } +} \ No newline at end of file
View file
gpac-26.02.0.tar.gz/share/rmtws/nodermt.js
Added
@@ -0,0 +1,257 @@ +const gpac = require('../nodejs'); + +console.log('GPAC NodeJS version ' + gpac.version + ' libgpac ' + gpac.abi_major + ':' + gpac.abi_minor + '.' + gpac.abi_micro); + +var FILTERS = +var gpac_args = + +gpac.init(gpac.GF_MemTrackerSimple, "0"); + + +process.argv.forEach( val => { + if (val.startsWith('-f=')) { FILTERS.push( val.substring(3) ); } + else gpac_args.push(val); + +} ) + +gpac.set_args(gpac_args); + +let fs = new gpac.FilterSession(); + + +////////// RMT_WS example ///////// +gpac.enable_rmtws(true); + + + +let filter_props_lite = 'name', 'status', 'bytes_done', 'type', 'ID', 'nb_ipid', 'nb_opid', 'idx', 'itag' +let filter_args_lite = +let pid_props_lite = + +function gpac_filter_to_object(f, full=false) { + let jsf = {}; + + for (let prop in f) { + if (full || filter_props_lite.includes(prop)) + jsfprop = fprop; + } + + jsf'gpac_args' = ; + + if (full) { + // let all_args = f.all_args(false); // full args + let all_args = f.all_args(true); // full args => error in js interface (param is reverse of value_only) + // console.log(JSON.stringify(all_args)); + for (let arg of all_args) { + if (arg && (full || filter_args_lite.includes(arg.name))) + jsf'gpac_args'.push(arg) + + } + } + + jsf'ipid' = {}; + jsf'opid' = {}; + + for (let d=0; d<f.nb_ipid; d++) { + let pidname = f.ipid_prop(d, "name"); + let jspid = {}; + + f.ipid_enum_props(d, (name, val, type) => { + if (full || pid_props_lite.includes(name)) + jspidname = {'type': type, 'val': val}; + + }); + + jspid"buffer" = f.ipid_prop(d, "buffer"); + // jspid"buffer_total" = f.ipid_prop(d, "buffer_total"); + jspid'source_idx' = f.ipid_source(d).idx; + + jsf'ipid'pidname = jspid; + } + + for (let d=0; d<f.nb_opid; d++) { + let pidname = f.opid_prop(d, "name"); + let jspid = {}; + + f.opid_enum_props(d, (name, val, type) => { + if (full || pid_props_lite.includes(name)) + jspidname = {'type': type, 'val': val}; + + }); + + jspid"buffer" = f.opid_prop(d, "buffer"); + // jspid"buffer_total" = f.opid_prop(d, "buffer_total"); + jspid"dest_idx" = ; + + let sinks = f.opid_sinks(d); + for (fsink of sinks) { + jspid"dest_idx".push(fsink.idx); + } + jsf'opid'pidname = jspid; + } + return jsf; + +} + + +let all_clients = ; +let cid = 0; + +let get_all_filters = function() { + var all_filters = ; + + for (let i=0; i<fs.nb_filters; i++) { + let f = fs.get_filter(i); + let jf = gpac_filter_to_object(f); + + all_filters.push(jf) + } + + return all_filters; +} + +let on_client_data = function(js_client, msg) { + + console.log("All clients:"); + for (let jc of all_clients) { + console.log("Client ", jc.id, jc.gpac.peer_address ); + } + + console.log("on_client_data on client id ", js_client.id, " len ", msg.length, msg); + console.log("this has peer:", js_client.gpac.peer_address); + + //js_client.gpac.send("reply from this function on client" + js_client.id + " orig: " + msg); + + if (msg.startsWith("json:")) { + + console.log("Got json message"); + + let jtext = JSON.parse(msg.substr(5)); + console.log("parsed json object", jtext, ('message' in jtext)); + + if (!('message' in jtext)) return; + + if ( jtext'message' == 'get_all_filters' ) { + console.log("Sending all filters"); + + fs.print_graph() + fs.print_stats() + + if (js_client && js_client.gpac && js_client.gpac.peer_address) + js_client.gpac.send(JSON.stringify({ 'message': 'filters', 'filters': get_all_filters() })); + + + t = {}; + t.execute = function() { + + if (js_client && js_client.gpac && js_client.gpac.peer_address) { + js_client.gpac.send(JSON.stringify({ 'message': 'update', 'filters': get_all_filters() })); + + return 1000; + } + else { + return -1; + } + } + fs.post_task(t); + + } + } +} + +let remove_client = function(client_id) { + for (let i = 0; i<all_clients.length; i++) { + if (all_clientsi.id == client_id) { + all_clients.splice(i,1); + return + } + } +} + +gpac.rmt_on_new_client = function(client) { + console.log("rmt on client"); + console.log(typeof(client)); + + let js_client = { + id: ++cid, + gpac: client + + } + + all_clients.push(js_client); + + console.log("New ws client ", js_client.id, " gpac peer ", js_client.gpac.peer_address); + + js_client.gpac.on_data = (msg) => { + if (typeof(msg) == "string") + on_client_data(js_client, msg); + else { + let buf = new Uint8Array(msg) + console.log("Got binary message of type", typeof(msg), "len ", buf.length, "with data:", buf); + + } + } + + js_client.gpac.on_close = function() { + console.log("ON_CLOSE on client ", js_client.id, " ", client.peer_address); + remove_client(js_client.id); + } + + +} + + +gpac.enable_userws(true); + +gpac.userws_on_new_client = function(client) { + + console.log("USERWS new client ", client.peer_address); + + client.on_data = (msg) => { + console.log("USERWS client", client.peer_address, "received", msg); + + client.send("ACK"); + } + + client.on_close = () => { + console.log("USERWS client", client.peer_address, "disconnected"); + } +} + +/////////////////// +let filter_uid = 0; + +fs.on_filter_new = function(f) { + console.log('New filter created: ' + f.name); + f.idx = filter_uid++; + f.iname = ''+f.idx; + +} +fs.on_filter_del = function(f) { + console.log('Filter deleted: ' + f.name); +} + +fs.on_event = function(evt) { + if (evt.ui_type == gpac.GF_EVENT_QUIT) { + fs.abort(gpac.GF_FS_FLUSH_NONE); + return true; + } + return false; +} + +const session_done = () => { + console.log('Session done running, graph: '); + fs.print_graph(); +}; + + +//load filters +FILTERS.forEach( fname => { + console.log('Loading filter ' + fname); + let f = fs.load(fname); +}); + +console.log('Running in blocking mode'); +fs.run(); +session_done(); +return;
View file
gpac-26.02.0.tar.gz/share/rmtws/pyrmt.py
Added
@@ -0,0 +1,219 @@ +import libgpac as gpac +import sys + + +print("Welcome to GPAC Python !\nVersion: " + gpac.version + "\n" + gpac.copyright_cite) + + + +gpac.init(1, b"0") + +gpac_args = +FILTERS = + +for arg in sys.argv: + if arg.startswith("-f="): + FILTERS.append(arg3:) + else: + gpac_args.append(arg) + +gpac.set_args(gpac_args) + +fid = 0 + +#define a custom filter session +class MyFilterSession(gpac.FilterSession): + + def on_filter_new(self, f): + print("new filter " + f.name) + global fid + fid+=1 + f.idx = fid + + def on_filter_del(self, f): + print("del filter " + f.name) + +fs = MyFilterSession() + +for filter in FILTERS: + print("loading filter ", filter) + fs.load(filter) + +fs.reporting(True) + + + +############### + +gpac.enable_rmtws() + +import pprint +import json + +class MyRMTHandler(gpac.RMTHandler): + + def __init__(self): + self.clients = + + def on_new_client(self, client): + print(f"new client {client} {client.peer_address()}") + self.clients.append(client) + pprint.pprint(self.clients) + + def on_client_close(self, client): + print(f"del client {client} {client.peer_address()}") + self.clients.remove(client) + pprint.pprint(self.clients) + + def on_client_data(self, client, data): + print(f"client {client.peer_address()} got data: {data}") + pprint.pprint(self.clients) + + if (type(data) == str and data.startswith("json:")): + jtext = json.loads(data5:) + pprint.pprint(jtext) + + if 'message' in jtext and jtext'message' == "get_all_filters": + + all_filters = get_all_filters() + client.send(json.dumps({'message': 'filters', 'filters': all_filters})) + + fs.post(UpdateTask(client, full=True)) + + + +filter_props_lite = 'name', 'status', 'bytes_done', 'type', 'ID', 'nb_ipid', 'nb_opid', 'idx', 'itag' + +def filter_to_dict(f, full=False): + jsf = {} + + for prop in filter_props_lite: + if hasattr(f, prop): + jsfprop = getattr(f, prop) + + jsf"gpac_args" = + + if full: + jsf"gpac_args" = f.all_args_value() + + + jsf'ipid' = {} + jsf'opid' = {} + + for i in range(f.nb_ipid): + + pidname = f.ipid_prop(i, "name") + + pid_enum = PropsEnum(full) + f.ipid_enum_props(i, pid_enum) + pid_obj = pid_enum.pid_props + + pid_obj'buffer' = f.ipid_prop(i, "buffer") + pid_obj'eos' = f.ipid_prop(i, "eos") + + pid_obj'source_idx' = f.ipid_source(i).idx + + jsf'ipid'pidname = pid_obj + + + for i in range(f.nb_opid): + + pidname = f.opid_prop(i, "name") + + pid_enum = PropsEnum(full) + f.opid_enum_props(i, pid_enum) + pid_obj = pid_enum.pid_props + + pid_obj'buffer' = f.opid_prop(i, "buffer") + + pid_obj'dest_idx' = fsink.idx for fsink in f.opid_sinks(i) + + jsf'opid'pidname = pid_obj + + + return jsf + + +class PropsEnum: + def __init__(self, full=False, pid_props_lite=): + self.pid_props = {} + self.full = full + self.pid_props_lite = + + def on_prop_enum(self, pname, pval): + if self.full or pname in self.pid_props_lite: + self.pid_propspname = str(pval) + + +def get_all_filters(full=False): + all_filters = + for i in range(fs.nb_filters): + f = fs.get_filter(i) + all_filters.append(filter_to_dict(f, full)) + return all_filters + + +class UpdateTask(gpac.FilterTask): + + def __init__(self, client, full=False): + super().__init__("updatefilters") + self.client = client + self.full = full + + def execute(self): + + all_filters = get_all_filters(self.full) + if self.client: + self.client.send(json.dumps({'message': 'update', 'filters': all_filters})) + return 1000 + else: + return None + + + +rmt_handler = MyRMTHandler() +gpac.set_rmt_handler(rmt_handler) + +############## + +##### USER WS ##### + +class MyWSHandler(gpac.RMTHandler): + + def __init__(self): + self.clients = + + def on_new_client(self, client): + print(f"WS new client {client} {client.peer_address()}") + self.clients.append(client) + pprint.pprint(self.clients) + + def on_client_close(self, client): + print(f"WS del client {client} {client.peer_address()}") + self.clients.remove(client) + pprint.pprint(self.clients) + + def on_client_data(self, client, data): + print(f"WS client {client.peer_address()} got data: {data}") + pprint.pprint(self.clients) + + client.send("ACK") + + +gpac.enable_userws() +ws_handler = MyWSHandler() +gpac.set_userws_handler(ws_handler) + +############## + + + + +#run the session in blocking mode +fs.run() + +fs.print_stats() +fs.print_graph() + +fs.delete() +gpac.close()
View file
gpac-26.02.0.tar.gz/share/rmtws/userws.html
Added
@@ -0,0 +1,48 @@ +<html> + <head> + + </head> + <body> + + <pre id="precontent"> + + </pre> + <script> + +let Socket = new WebSocket("ws://localhost:6364"); +Socket.binaryType = "arraybuffer"; + +function greet() { + Socket.send("USERWS YO"); +} + +Socket.onopen = function (evt) { + console.log("ws connected", evt); + + setInterval(greet, 2000); +}; + +var msg_count = 0; + +function handleWSmessage(text) { + msg_count++; + document.getElementById("precontent").innerText = msg_count + ": " + text; + +} + +Socket.onmessage = function (msg) { + + if (typeof(msg.data)=="string") { + decoded = msg.data; + handleWSmessage(decoded); + } else { + decoded = new Uint8Array(msg.data); + // do something with binary messages? + } + +}; + + </script> + + </body> +</html> \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/scripts/jsf/avgen/init.js -> gpac-26.02.0.tar.gz/share/scripts/jsf/avgen/init.js
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020 + * Copyright (c) Telecom ParisTech 2020-2024 * All rights reserved * * This file is part of GPAC / AVGenerator filter @@ -24,11 +24,14 @@ */ import * as evg from 'evg' +import { Bitstream as BS } from 'gpaccore' import { Sys as sys } from 'gpaccore' +import { File as File } from 'gpaccore' filter.pids = ; filter.set_name("avgen"); +filter.set_class_hint(GF_FS_CLASS_MM_IO); filter.set_desc("AV Counter Generator"); filter.set_version("1.0"); filter.set_author("GPAC Team"); @@ -38,7 +41,7 @@ +"\n" +"When -adjust() is set, the first video frame is adjusted such that a full circle happens at each exact second according to the system UTC clock.\n" +"By default, video UTC and date are computed at each frame generation from current clock and not from frame number.\n" -+"This will result in broken timing when playing at speeds other than 1.0.\n" ++"This will result in broken UTC timing text when playing at speeds other than 1.0.\n" +"This can be changed using -lock().\n" +"\n" +"Audio beep is generated every second, with octave (2xfreq) of even beep used every 10 seconds.\n" @@ -66,6 +69,7 @@ ); filter.set_arg({ name: "type", desc: "output selection\n- a: audio only\n- v: video only\n- av: audio and video", type: GF_PROP_UINT, def: "av", minmax_enum: "a|v|av"} ); +filter.set_arg({ name: "evte", desc: "output event stream\n- 0: disable\n- 1+: period (sec) of dummy events", type: GF_PROP_UINT, def: "0"} ); filter.set_arg({ name: "freq", desc: "frequency of beep", type: GF_PROP_UINT, def: "440"} ); filter.set_arg({ name: "freq2", desc: "frequency of odd beep", type: GF_PROP_UINT, def: "659"} ); filter.set_arg({ name: "sr", desc: "output samplerate", type: GF_PROP_UINT, def: "44100"} ); @@ -82,11 +86,16 @@ filter.set_arg({ name: "copy", desc: "copy the framebuffer into each video packet instead of using packet references", type: GF_PROP_BOOL, def: "false"} ); filter.set_arg({ name: "dur", desc: "run for the given time in second", type: GF_PROP_FRACTION, def: "0/0"} ); filter.set_arg({ name: "adjust", desc: "adjust start time to synchronize counter and UTC", type: GF_PROP_BOOL, def: "true"} ); -filter.set_arg({ name: "pack", desc: "packing mode for stereo views\n - no: no packing\n - ss: side by side packing, forces -views() to 2\n - tb: top-bottom packing, forces -views() to 2", type: GF_PROP_UINT, def: "no", minmax_enum: "no|ss|tb"} ); +filter.set_arg({ name: "pack", desc: "packing mode for stereo views\n- no: no packing\n- ss: side by side packing, forces -views() to 2\n- tb: top-bottom packing, forces -views() to 2", type: GF_PROP_UINT, def: "no", minmax_enum: "no|ss|tb"} ); filter.set_arg({ name: "disparity", desc: "disparity in pixels between left-most and right-most views", type: GF_PROP_UINT, def: "20"} ); filter.set_arg({ name: "views", desc: "number of views", type: GF_PROP_UINT, def: "1"} ); filter.set_arg({ name: "rates", desc: "number of target bitrates to assign, one per size", type: GF_PROP_STRING_LIST} ); filter.set_arg({ name: "logt", desc: "log frame time to console", type: GF_PROP_BOOL} ); +filter.set_arg({ name: "banner", desc: "banner text to display", type: GF_PROP_STRING, def: "many thanks to QuickJS, FreeType, OpenSSL, SDL, FFmpeg, OpenHEVC, libjpeg, libpng, faad2, libmad, a52dec, xvid, OGG ..."} ); + +let evte_cts = 0; +let evte_pid = null; +let evte_playing = false; let audio_osize=0; let audio_cts=0; @@ -107,32 +116,60 @@ let brush = new evg.SolidBrush(); let video_playing=false; let start_date = 0; -let banner = 'many thanks to QuickJS, FreeType, OpenSSL, SDL, FFmpeg, OpenHEVC, libjpeg, libpng, faad2, libmad, a52dec, xvid, OGG ...'; let frame_offset = 0; let nb_frame_init = 0; let utc_init = 0; let ntp_init = 0; /*create a text*/ -let text = new evg.Text(); -text.font = 'SANS'; -text.fontsize = 20; -text.baseline = GF_TEXT_BASELINE_HANGING; -text.align=GF_TEXT_ALIGN_CENTER; -text.lineSpacing=0; +let text = null; filter.frame_pending = 0; filter.initialize = function() { if (filter.type != 1) { - this.set_cap({id: "StreamType", value: "Audio", output: true} ); + this.set_cap({id: "StreamType", value: "Audio", output: true} ); } if (filter.type != 0) { this.set_cap({id: "StreamType", value: "Video", output: true} ); } + if (filter.evte) { + this.set_cap({id: "StreamType", value: "Metadata", output: true} ); + } this.set_cap({id: "CodecID", value: "raw", output: true} ); + let gpac_help = sys.get_opt("temp", "gpac-help"); + let gpac_doc = (sys.get_opt("temp", "gendoc") == "yes") ? true : false; + if (gpac_help || gpac_doc) return; + + text = new evg.Text(); + text.font = 'SANS'; + text.fontsize = 20; + text.baseline = GF_TEXT_BASELINE_HANGING; + text.align=GF_TEXT_ALIGN_CENTER; + text.lineSpacing=0; + + let pid_id_offset = 1; + + //setup event + if (filter.evte) { + evte_pid = this.new_pid(); + evte_pid.set_prop('StreamType', 'Metadata'); + evte_pid.set_prop('CodecID', 'evte'); + evte_pid.set_prop('Cached', true); + evte_pid.set_prop('Timescale', filter.fps.n); + evte_pid.name = "event"; + evte_pid.set_prop('ID', pid_id_offset++); + + //we send 8 bytes empty events + let bps = Math.max(Math.floor(8*8 / filter.evte), 1); + evte_pid.set_prop('Bitrate', bps); + + //send first at cts=0 + evte_cts -= filter.evte * filter.fps.n; + } + //setup audio if (filter.type != 1) { audio_pid = this.new_pid(); @@ -144,7 +181,7 @@ audio_pid.set_prop('AudioFormat', 'flt'); audio_pid.set_prop('Cached', true); audio_pid.name = "audio"; - audio_pid.set_prop('ID', 1); + audio_pid.set_prop('ID', pid_id_offset); if (!filter.freq) filter.freq = 440; @@ -221,7 +258,7 @@ } else { vpid.name = name; } - vpid.set_prop('ID', 1 + (vid+1)*filter.views + view); + vpid.set_prop('ID', pid_id_offset + (vid+1)*filter.views + view); vsrc.video_pids.push(vpid); } @@ -280,7 +317,7 @@ let scale; if (is_testcard) { scale = disp_w/2 / tx.width; - } else { + } else { scale = disp_w/6 / tx.width; } let rw = scale * tx.width; @@ -355,7 +392,14 @@ else fps = Math.floor(100*fps) / 100; vprop += '' + fps + ' FPS'; - text.set_text('GPAC AV Generator', 'v'+sys.version_full, ' ', 'UTC Locked: ' + (filter.lock ? 'yes' : 'no'), ' ', vprop); + try { + text.set_text('GPAC AV Generator', 'v'+sys.version_full, ' ', 'UTC Locked: ' + (filter.lock ? 'yes' : 'no'), ' ', vprop); + } catch (e) { + print(GF_LOG_INFO, "----") + print(GF_LOG_INFO, e) + print(GF_LOG_INFO, "----") + print(GF_LOG_WARNING, "Fonts disabled"); + } mmx.identity = true; mmx.translate(t_x+10, oy-rh/5); @@ -375,21 +419,26 @@ { if (evt.type == GF_FEVT_STOP) { if (pid === audio_pid) audio_playing = false; + else if (pid === evte_pid) evte_playing = false; else video_playing = false; - } + } else if (evt.type == GF_FEVT_PLAY) { if (pid === audio_pid) audio_playing = true; + else if (pid === evte_pid) evte_playing = true; else video_playing = true; filter.reschedule(); - } + } } filter.process = function() { - if (!audio_playing && !video_playing) return GF_EOS; + if (!audio_playing && !video_playing && !evte_playing) return GF_EOS; + + //start by processing event, then video (adjusting start time) + if (evte_playing) + process_eventmsg(); - //start by processing video, adjusting start time - if (video_playing) + if (video_playing) process_video(); if (audio_playing) @@ -397,6 +446,59 @@ return GF_OK; } +function get_emeb_box() +{ + let pck = evte_pid.new_packet(8); + pck.cts = evte_cts; + pck.dur = filter.evte * filter.fps.n; + pck.sap = GF_FILTER_SAP_1; + + let bs = new BS(pck.data, true); + bs.put_u32(8); //size + bs.put_4cc("emeb"); //type + + return pck; +} + +function process_eventmsg() +{ + if (!evte_pid || evte_pid.would_block) + return; + //perform regulation iof audio or video are being generated + if (audio_playing || video_playing) { + let nb_sec; + if (filter.type == 0) { + nb_sec = audio_cts * filter.dur.d / filter.sr; + } else { + nb_sec = video_cts * filter.fps.d / filter.fps.n; + } + + //send event for the period + if (nb_sec * filter.fps.n < evte_cts + filter.evte * filter.fps.n) return; + } + evte_cts += filter.evte * filter.fps.n; + + let pck = get_emeb_box(); + pck.send(); + + let done = false; + //evte only, check duration + if (!audio_playing && !video_playing) { + if (filter.dur.d && (evte_cts * filter.dur.d >= filter.dur.n * filter.fps.n)) { + print("done playing, cts " + evte_cts); + done = true; + } + } else { + if ((!audio_playing || !video_playing) && evte_cts > 0) { + done=true; + } + } + if (done) { + evte_playing = false + evte_pid.eos = true; + } +} + function process_audio() { if (!audio_pid || audio_pid.would_block) @@ -408,7 +510,7 @@ for (let i=0; i<filter.flen; i++) { let idx; let samp = 0; - let cur_pos = audio_pos - nb_secs*filter.sr; + let cur_pos = audio_pos - nb_secs*filter.sr; if (cur_pos < 0) {} else if (cur_pos > audio_beep_len) { if (audio_in_beep) { @@ -452,8 +554,8 @@ pck.cts = audio_cts; pck.dur = filter.flen; pck.sap = GF_FILTER_SAP_1; - - //when prop value is set to true for 'SenderNTP', automatically set + + //when prop value is set to true for 'SenderNTP', automatically set if (!videos.length && filter.ntp) pck.set_prop('SenderNTP', true); pck.send(); @@ -480,14 +582,14 @@ let utc, ntp; //remember start date for lock, and compute initial offset in cycles so that we reach tull cycle at each second if (!start_date) { - start_date = date.getTime(); + start_date = date.getTime(); let ms_init = date.getMilliseconds(); if (filter.adjust) { frame_offset = filter.fps.n * ms_init / 1000; //in audio samples audio_pos = Math.floor(ms_init * filter.sr / 1000); - + //move to nb frames nb_frame_init = Math.floor(frame_offset / filter.fps.d); frame_offset = filter.fps.d * nb_frame_init; @@ -542,7 +644,7 @@ if (!vsrc.init_banner_done) { vsrc.init_banner_done = true; } - + let forward_idx = 0; let a_src = vsrc; while (a_src) { @@ -592,6 +694,18 @@ } } +function ntpFractionToInt(fraction) { + if (typeof fraction !== 'bigint') { + // Coerce to BigInt so that very large values are handled safely + fraction = BigInt(Math.floor(Number(fraction))); + } + + const DENUM = 2**32-1; + + // Perform the division in double precision. + return Number(fraction) / Number(DENUM); +} + function draw_view(vsrc, view_idx, col, col_idx, cycle_time, h, m, s, nbf, video_frame, date, utc, ntp) { let disp_w = vsrc.w; @@ -656,24 +770,24 @@ if (col_idx) { brush.set_color('white'); - } else { + } else { brush.set_color('grey'); } path.ellipse(0, 0, 2*r, 2*r); vsrc.canvas.path = path; vsrc.canvas.fill(brush); - + if (cycle_time) { path.reset(); let start = Math.PI/2 - cycle_time * 2 * Math.PI; path.arc(r/2, start, Math.PI/2, 2); } else { - col_idx = !col_idx; + col_idx = !col_idx; } if (col_idx) { brush.set_color('grey'); - } else { + } else { brush.set_color('white'); } vsrc.canvas.path = path; @@ -685,7 +799,7 @@ let t = 'Time: '; if (h<10) t = t+'0'+h; else t = t+''+h; - + if (m<10) t = t+':0'+m; else t = t + ':' + m; @@ -720,7 +834,7 @@ if (view_idx && !filter.pack) return; - text.set_text(' Date: ' + date.toUTCString(), ' Local: ' + date, ' UTC (ms): ' + utc, ' NTP (s.f): ' + ntp.n + '.' + ntp.d.toString(16) ); + text.set_text(' Date: ' + date.toUTCString(), ' Local: ' + date, ' UTC (ms): ' + utc, ' NTP (s.f): ' + ntp.n + '.' + ntpFractionToInt(ntp.d) ); mx.identity = true; if (filter.pack==1) @@ -733,7 +847,7 @@ brush.set_color('white'); vsrc.canvas.fill(brush); - if (!filter.dyn && vsrc.init_banner_done) + if (!filter.dyn && vsrc.init_banner_done) return; if (filter.pack==1) @@ -756,10 +870,10 @@ mx.scale(1, 0.5); if (!filter.dyn) { - text.set_text(sys.copyright, banner); - mx.translate(0, text.fontsize/2); + text.set_text(sys.copyright, filter.banner); + mx.translate(sys.copyright.length*text.fontsize/2.5, text.fontsize/2); } else { - text.set_text(sys.copyright + ' - ' + banner); + text.set_text(sys.copyright + ' - ' + filter.banner); } mx.translate(t_x + pos_x, t_y -disp_h/2 + text.fontsize); @@ -777,4 +891,3 @@ vsrc.canvas.clipper = null; } -
View file
gpac-2.4.0.tar.gz/share/scripts/jsf/avmix/help.js -> gpac-26.02.0.tar.gz/share/scripts/jsf/avmix/help.js
Changed
@@ -185,6 +185,16 @@ - raw (true): indicate if input port is decoded AV (true) or compressed AV (false) when using a dedicated gpac process, ignored otherwise ### Notes +The special URL scheme \`ipid://\` can be used to locate an input pid by link directives. +EX in=ipid://#foo=bar +This will use pids having property \`foo\` with value \`bar\`, regardless of source filter ID. + +EX in=ipid://TEST#foo=bar +This will use pids having property \`foo\` with value \`bar\` coming from filter with ID \`TEST\`. + +When using the \`ipid://\` scheme, filter chains cannot be specified (in accepts a single argument) and \`port\` is ignored. +The syntax for link directive is the same as in gpac. However, if a listed property is not found on the input pid, the matching will fail. + When launching a child process, the input filter is created first and the child process launched afterwards. Warning: When launching a child process directly (e.g. \`in="ffmpeg ..."\`), any relative URL used in \`in\` must be relative to the current working directory. @@ -284,7 +294,7 @@ The \`JSFun\` may return false to indicate that the scene should be considered as inactive. Any other return value (undefined or not false) will mark the scene as active. -EX: "mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" +EX "mxjs": "tr.rotation = (get_media_time() % 8) * 360 / 8; tr.update=true;" ## Grouping @@ -421,7 +431,7 @@ The \`JSFun\` function specified by \`fun\` has no input parameter. The return value (default 0) is the number of seconds (float) to wait until next evaluation of the script. -EX: { "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } +EX { "script": "let s=get_scene('s1'); let rot = s.get('rotation'); rot += 10; s.set('rotation', rot); return 2;" } This will change scene \`s1\` rotation every 2 seconds @@ -448,10 +458,10 @@ Only the \`active\` property can be animated or updated in a watcher. -EX {'watch': 's1@rotation', 'target': 's2@rotation'} +EX {"watch": "s1@rotation", "target": "s2@rotation"} This will copy s1.rotation to s2.rotation. -EX {'watch': 's1@rotation', 'target': 'get_scene('s2').set('rotation', -value); } +EX {"watch": "s1@rotation", "target": "get_scene('s2').set('rotation', -value);" } This will copy the -1*s1.rotation to s2.rotation. ### Watching UI events @@ -467,7 +477,7 @@ When event monitoring is used, the \`target\` must be a javascript callback (i.e. it cannot be \`ID@prop\`). The javascript function will be called with a single argument \`evt\` containing the GPAC event. -EX {'watch': 'mousemove', 'target': 'let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );'} +EX {"watch": "mousemove", "target": "let s = mouse_over(evt); get_scene('s2').set('fill', (s && (s.id=='s1') ? 'white' : 'black' );"} This will set s1 fill color to white of mouse is over s2 and to black otherwise. ## Styles
View file
gpac-2.4.0.tar.gz/share/scripts/jsf/avmix/init.js -> gpac-26.02.0.tar.gz/share/scripts/jsf/avmix/init.js
Changed
@@ -1,3 +1,28 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2020-2024 + * All rights reserved + * + * This file is part of GPAC / AVGenerator filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + import * as evg from 'evg' import { Sys as sys } from 'gpaccore' import { AudioMixer as amix } from 'gpaccore' @@ -95,7 +120,7 @@ - pck: current pck if any - frame_ts: current frame timestamp in output video timescale - rotate: video Rotation property -- Mirror: video Mirror property +- Mirror: video Mirror property Each scene must implement: - update(): check value of this.update_flag to see if scene has to be rebuild @@ -133,7 +158,7 @@ */ globalThis.canvas_blit = canvas_blit; /*texture path on canvas - function shall only be used inside scene.draw() -The input textures are fteched from the sequences associated with the scene +The input textures are fteched from the sequences associated with the scene \param path path (type Path) to draw \param op_type (=0) multitexture operand type \param op_param (=0) multitexture operand param @@ -262,16 +287,17 @@ //metadata filter.set_name("AVMix"); -filter.set_desc("Audio Video Mixer"); +filter.set_class_hint(GF_FS_CLASS_AV); +filter.set_desc("Audio Video mixer"); filter.set_author("GPAC team"); //global options filter.set_arg({ name: "pl", desc: "local playlist file to load", type: GF_PROP_STRING, def: "avmix.json" } ); filter.set_arg({ name: "live", desc: "live mode", type: GF_PROP_BOOL, def: "true"} ); filter.set_arg({ name: "gpu", desc: `enable GPU usage - - off: no GPU - - mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients - - all: try to use GPU for everything`, type: GF_PROP_UINT, def: "off", minmax_enum: 'off|mix|all', hint:"advanced"} ); +- off: no GPU +- mix: only render textured path to GPU, use software rasterizer for the outlines, solid fills and gradients +- all: try to use GPU for everything`, type: GF_PROP_UINT, def: "off", minmax_enum: 'off|mix|all', hint:"advanced"} ); filter.set_arg({ name: "thread", desc: "use threads for software rasterizer (-1 for all available cores)", type: GF_PROP_SINT, def: "-1", hint:"expert"} ); filter.set_arg({ name: "lwait", desc: "timeout in ms before considering no signal is present", type: GF_PROP_UINT, def: "1000", hint:"expert"} ); filter.set_arg({ name: "ltimeout", desc: "timeout in ms before restarting child processes", type: GF_PROP_UINT, def: "4000", hint:"expert"} ); @@ -284,9 +310,9 @@ filter.set_arg({ name: "fps", desc: "output video frame rate", type: GF_PROP_FRACTION, def: "25"} ); filter.set_arg({ name: "pfmt", desc: "output pixel format. Use \`rgba\` in GPU mode to force alpha channel", type: GF_PROP_PIXFMT, def: "yuv"} ); filter.set_arg({ name: "dynpfmt", desc: `allow dynamic change of output pixel format in software mode - - off: pixel format is forced to desired value - - init: pixel format is forced to format of fullscreen input in first generated frame - - all: pixel format changes each time a full-screen input PID at same resolution is used`, type: GF_PROP_UINT, def: "init", minmax_enum: 'off|init|all', hint:"expert"} ); +- off: pixel format is forced to desired value +- init: pixel format is forced to format of fullscreen input in first generated frame +- all: pixel format changes each time a full-screen input PID at same resolution is used`, type: GF_PROP_UINT, def: "init", minmax_enum: 'off|init|all', hint:"expert"} ); //audio output options filter.set_arg({ name: "sr", desc: "output audio sample rate, 0 disable audio output", type: GF_PROP_UINT, def: "44100"} ); @@ -370,7 +396,7 @@ filter._help += ' - ' + name + ': ' + obj.description + '\n'; filter.set_help(filter._help); return; - } + } filter._help += (single_mod_help ? '# ' : '## ') + rad + ' `' + name + '`'; if (mod_type==1) { @@ -379,10 +405,10 @@ if (typeof inst.setup != 'function') { filter._help += ' - GPU only'; } else { - filter._help += ' - software/GPU'; + filter._help += ' - software/GPU'; } } else { - filter._help += ' - software only'; + filter._help += ' - software only'; } } filter._help += '\n' + obj.help + '\n'; @@ -440,12 +466,12 @@ filter._help += obj.help_playlist + '\n'; } - let scripts = sys.enum_directory(path+'scenes/', "*.js"); + let scripts = sys.enum_directory(path+'scenes/', "*.js").sort((a, b) => a.name.localeCompare(b.name)); for (let i=0; i<scripts.length; i++) { let name = scriptsi.name; import('./scenes/' + name).then(obj => { build_help_mod(obj, name, 0, i); }).catch(err => {}); } - scripts = sys.enum_directory(path+'transitions/', "*.js"); + scripts = sys.enum_directory(path+'transitions/', "*.js").sort((a, b) => a.name.localeCompare(b.name)); for (let i=0; i<scripts.length; i++) { let name = scriptsi.name; import('./transitions/' + name).then(obj => { build_help_mod(obj, name, 1, i); }).catch(err => {}); @@ -453,7 +479,7 @@ filter.set_help(filter._help); } -filter.initialize = function() +filter.initialize = function() { let gpac_help = sys.get_opt("temp", "gpac-help"); let gpac_doc = (sys.get_opt("temp", "gendoc") == "yes") ? true : false; @@ -488,7 +514,7 @@ name = help_mod + '.js'; import('./scenes/' + name).then(obj => { build_help_mod(obj, name, 0, 1); }).catch(err => {}); return GF_OK; - } + } script = path+'transitions/'+help_mod+'.js'; if (sys.file_exists(script)) { name = help_mod + '.js'; @@ -561,7 +587,7 @@ if (!filter.ltimeout) filter.ltimeout = 1000; - + if (filter.lwait > filter.ltimeout) filter.lwait = filter.ltimeout; } @@ -614,7 +640,7 @@ { if (!filter.vsize.x || !filter.vsize.y) { if (vout) vout.remove(); - vout = null; + vout = null; return; } if (filter.gpu) { @@ -681,7 +707,7 @@ { if (!filter.sr || !filter.ch) { if (aout) aout.remove(); - aout = null; + aout = null; return; } print(GF_LOG_INFO, (filter.live ? 'Live ' : '' ) + 'Audio output ' + filter.sr + ' Hz ' + filter.ch + ' Channels'); @@ -726,7 +752,7 @@ audio_max_resched_dur_us = audio_frame_dur_us/3; } -filter.configure_pid = function(pid) +filter.configure_pid = function(pid) { if (pids.indexOf(pid)<0) { pids.push(pid); @@ -771,7 +797,7 @@ pid.mirror = pid.get_prop('Mirror'); pid.rotate = pid.get_prop('Rotate'); pid.pfmt_check = 0; - } + } else if (p == 'Audio') { //silently ignore if (!aout) return GF_EOS; @@ -828,12 +854,12 @@ seq.sources.forEach(src => { if (pid.source === src) scene.resetup_pids = true; - }); - }); + }); + }); }); } -filter.remove_pid = function(pid) +filter.remove_pid = function(pid) { let index = pids.indexOf(pid); if (pid.pck) { @@ -864,7 +890,7 @@ scene.resetup_pids = true; break; } - } + } }); } @@ -892,7 +918,7 @@ return; } - if (evt.type == GF_FEVT_STOP) { + if (evt.type == GF_FEVT_STOP) { if (pid==aout) { audio_playing = false; if (!wait_pid_play) aout.eos = true; @@ -909,8 +935,8 @@ if (!wait_pid_play) return false; } return true; - } - if (evt.type == GF_FEVT_PLAY) { + } + if (evt.type == GF_FEVT_PLAY) { if (pid==aout) { audio_playing = true; } else if (pid==vout) { @@ -921,7 +947,7 @@ wait_pid_play--; return true; } - //cancel all other events + //cancel all other events return true; } @@ -983,7 +1009,7 @@ pid.pck.unref(); pid.pck = null; } - pid.send_event( new FilterEvent(GF_FEVT_STOP) ); + pid.send_event( new FilterEvent(GF_FEVT_STOP) ); pid.discard = true; }); root_scene.scenes.length = 0; @@ -1022,9 +1048,9 @@ do_video = true; } } - //audio ahead of next video frame, don't do audio - if not generating video, do nothing + //audio ahead of next video frame, don't do audio - if not generating video, do nothing if (!do_video && (!audio_playing || (audio_time * video_timescale > (video_time+video_time_inc) * audio_timescale))) { - //notfiy we are still alive + //notify we are still alive filter.reschedule(0); if (filter.live) { let now = sys.clock_us(); @@ -1176,7 +1202,7 @@ seq.sources.forEach(src => { if (src.in_prefetch) return; - src.pids.forEach(pid => { + src.pids.forEach(pid => { //check audio pids if (pid.type == TYPE_AUDIO) { @@ -1198,7 +1224,7 @@ else scene.mod.pids.push(pid_link); }); - }); + }); }); prefetching.forEach(pid_link => { @@ -1229,7 +1255,7 @@ if (!pidlink.pid.pck) { if (pidlink.pid.source.in_prefetch) { - return; + return; } if (pidlink.pid.source.no_signal) { if (pidlink.texture) return; @@ -1242,7 +1268,7 @@ if (!pidlink.pid.texture) { if (!pidlink.pid.pck) { ready = false; - return; + return; } create_pid_texture(pidlink.pid); } @@ -1278,7 +1304,7 @@ let axis = scene.axis; let orientation = scene.orientation; - if (scene.untransform) + if (scene.untransform) scene.mx_untransform = true; else if (scene.mx_untransform) scene.mx_untransform = false; @@ -1775,7 +1801,7 @@ if (!scene_update_visual_pids(scene)) return; scene.no_signal_time = 0; - //sequences defined, check for no sequence active if autoshow / nosig are set + //sequences defined, check for no sequence active if autoshow / nosig are set if (scene.sequences.length && scene.check_active) { let disabled=true; let min_nosig = 0; @@ -1810,7 +1836,7 @@ if (scene.mod.update_flag) scene.gl_uniforms_reload = true; - else if (scene.gl_uniforms_reload) + else if (scene.gl_uniforms_reload) scene.gl_uniforms_reload = false; @@ -2100,10 +2126,21 @@ root_scene.scenes.forEach(elm => do_traverse_all(elm, update_scene) ); } +let nolive_timeout_start = 0; function process_video() { if (!video_inputs_ready) { if (!video_time && !live_forced_play) { + let waitfor = filter.lwait; + if (!filter.live) waitfor *= 10; + + if (!nolive_timeout_start) nolive_timeout_start = current_utc_clock; + else if (current_utc_clock - nolive_timeout_start > waitfor) { + print(GF_LOG_ERROR, 'No video input after ' + waitfor + ' ms in non-live mode, aborting'); + do_terminate(); + do_audio = false; + return; + } print(GF_LOG_DEBUG, 'video not init'); do_audio = false; return; @@ -2236,7 +2273,7 @@ vout.set_prop('StrideUV', null); last_forward_pixfmt = 0; canvas_reconfig = true; - } + } clip_stack.length = 0; global_branch_depth = 0; @@ -2329,7 +2366,7 @@ canvas.pix_fmt = pfmt; set_canvas_yuv(pfmt); } else if (canvas_reconfig) { - canvas_reconfig = false; + canvas_reconfig = false; try { canvas.reassign(video_width, video_height, pfmt, frame.data); } catch (e) { @@ -2340,7 +2377,7 @@ canvas.pix_fmt = pfmt; set_canvas_yuv(pfmt); - } else { + } else { canvas.reassign(frame.data); } @@ -2382,7 +2419,7 @@ } next_video_gen_time = vtime + video_frame_dur_us; } else { - print(GF_LOG_DEBUG, 'sent video frame TS ' + (video_time) + '/' + video_timescale); + print(GF_LOG_DEBUG, 'sent video frame TS ' + (video_time) + '/' + video_timescale); } video_time += video_time_inc; @@ -2404,8 +2441,8 @@ let mix_pids = ; //if video is playing and not blocking, process audio event if blocking - this avoids cases where output frames are consumed by burst - //and the video burst happens befor the audio burst: we need to send video to fill the burst then unblock audio, but video won't be procesed - //if audio is blocking... + //and the video burst happens befor the audio burst: we need to send video to fill the burst then unblock audio, but video won't be processed + //if audio is blocking... if (aout.would_block && (!video_playing || vout.would_block)) { return; } @@ -2715,7 +2752,7 @@ print(GF_LOG_DEBUG, 'source ' + next_src.logname + ' will end before sequence start offset ' + s.sequence.start_offset + ' - skipping'); s = next_src; force_load_source = true; - continue; + continue; } if (!is_same_source) { @@ -2795,7 +2832,7 @@ }); } else { inactive_sources++; - return; + return; } } @@ -2842,7 +2879,7 @@ nb_over++; return; } - + let tdiff = current_utc_clock - pid.last_pck_time; if (tdiff > filter.lwait) { force_wait_pid = false; @@ -2865,14 +2902,14 @@ } } return; - } + } if (force_wait_pid || !pid.pck) { if (pid.type==TYPE_VIDEO) wait_video = true; else wait_audio = true; - } + } return; } if (pid.type==TYPE_UNHANDLED) { @@ -3082,7 +3119,7 @@ relaunch = s.keep_alive ? 1 : 0; } } - //process is still alive but we are done + //process is still alive but we are done else if (!source_restart) { print(GF_LOG_INFO, 'Child process for src ' + s.logname + ' still alive but eos notified'); } @@ -3141,7 +3178,7 @@ } else { nb_over = 0; } - } else { + } else { nb_over = 0; } @@ -3192,7 +3229,7 @@ }); s.timeline_init = true; - if (s.seek && !s.video_time_at_init) + if (s.seek && !s.video_time_at_init) s.video_time_at_init = video_time+1; fetch_source(s); @@ -3206,7 +3243,7 @@ //active sources and waiting for inputs else if (s.pids.length) { if (wait_video) video_inputs_ready = false; - if (wait_audio) audio_inputs_ready = false; + if (wait_audio) audio_inputs_ready = false; } return; } @@ -3231,7 +3268,7 @@ if (par_seq && (elem.sequence != par_seq)) continue; //same id, this is our source if (id && (elem.id == id)) return elem; - + //if source ID is given and differs from source ID, not our source. This allows having 2 sources with the same URLs in the same sequence if (id && (elem.id != id)) continue; @@ -3253,14 +3290,21 @@ { let type = pid.get_prop("StreamType"); let res = null; - sources.forEach( elem => { + sources.forEach( elem => { elem.fsrc.forEach( (f, index) => { + if (typeof f.__dummy_tmp != 'undefined') { + if (pid.match_source(f._tmp_arg)) { + pid.skipped = false; + res = elem; + } + return; + } if (!pid.is_filter_in_parents(f)) return; - if ((f.media_type=="all") + if ((f.media_type=="all") || ((type=='Visual') && (f.media_type.indexOf('v')>=0)) || ((type=='Audio') && (f.media_type.indexOf('a')>=0)) - || ((type=='Text') && (f.media_type.indexOf('t')>=0)) + || ((type=='Text') && (f.media_type.indexOf('t')>=0)) ) { pid.skipped = false; res = elem; @@ -3279,7 +3323,7 @@ evt.start_range = source.media_start + source.sequence.start_offset; if (source.video_time_at_init) { evt.start_range += (video_time - (source.video_time_at_init-1)) / video_timescale; - } + } pid.done = false; print(GF_LOG_DEBUG, 'Playing PID ' + source.logname + '.' + pid.name + ' @start=' + evt.start_range); @@ -3335,7 +3379,7 @@ pid.pck.unref(); pid.pck = null; } - pid.send_event( new FilterEvent(GF_FEVT_STOP) ); + pid.send_event( new FilterEvent(GF_FEVT_STOP) ); }); } @@ -3384,7 +3428,7 @@ let sep = link.indexOf('#'); if (sep<0) { - if (link.length) + if (link.length) link_idx = parseInt(link); } else { let vals = arg.slice(sep); @@ -3397,15 +3441,14 @@ print(GF_LOG_ERROR, 'Wrong filter index ' + link_idx + ' in link directive ' + link); broken_links = true; return; - } + } let f_src = fchainlink_idx; - if (target) { target.set_source(f_src, link_arg); } else { filter.set_source_restricted(f_src, link_arg); } - + f_src.require_source_id = true; }); return broken_links; } @@ -3425,20 +3468,23 @@ let f, args, i; let port=""; - let opts=""; let append_filter = null; let local_filter = null; - if (typeof src.port == 'string') port = src.port; - if (typeof src.opts == 'string') opts = src.opts; - let use_raw = (typeof src.raw == 'boolean') ? src.raw : true; + + if (src.in.indexOf('ipid://')<0) { + if (typeof src.port == 'string') port = src.port; + } src.local_pipe = null; src.process_id = null; if (port.length) { + let use_raw = (typeof src.raw == 'boolean') ? src.raw : true; + let opts=""; + if (typeof src.opts == 'string') opts = src.opts; let do_cat_url = true; let src_url = ""; let rfopts = "reframer"; - if (use_raw) { + if (use_raw) { rfopts += ":raw=av"; } //use real-time regulation @@ -3458,7 +3504,7 @@ src_url += "gpac src="; } else if ((port==='tcp') || (port==='tcpu')) { - local_filter = port+"://127.0.0.1:" + current_port + "/:listen"; + local_filter = port+"://127.0.0.1:" + current_port + "/:listen"; if (s.keep_alive) { local_filter += ':ka'; s.has_ka_process = true; @@ -3485,13 +3531,13 @@ } let cat_url; if (idx>=0) { - cat_url = src.in.slice(0, idx); + cat_url = src.in.slice(0, idx); } else { - cat_url = src.in; + cat_url = src.in; } cat_url = sys.url_cat(filter.pl, cat_url); if (idx>=0) { - cat_url += src.in.slice(idx); + cat_url += src.in.slice(idx); } src_url += cat_url; } else { @@ -3512,13 +3558,13 @@ } filter.lock_all(true); - try { + try { f = filter.add_source(local_filter); } catch (e) { print(GF_LOG_ERROR, 'Add source ' + local_filter + ' failed: ' + e); disable_source(s); filter.lock_all(false); - return; + return; } filter.set_source(f); filter.lock_all(false); @@ -3536,13 +3582,13 @@ } print(GF_LOG_INFO, 'Launch process for ' + args + ' OK'); return; - } + } //local load filter.lock_all(true); //parse command line args = src.in.split(' '); - args = args.filter(function(item){return item;}); + args = args.filter(function(item){return item;}); let links = ; let fchain = ; let prev_f = null; @@ -3556,11 +3602,24 @@ continue; } - try { + try { if (prev_f) { f = filter.add_filter(arg); - } else { - //relative to playlist + } + //ipid load + else if (arg.startsWith('ipid://')) { + f = {}; + f.__dummy_tmp = true; + f._tmp_arg = arg; + f.remove = function(){}; + links.length = 0; + if (i+1<args.length) { + print(GF_LOG_WARNING, 'Cannot use filter chain with ipid:// source scheme, chain must be specified at prompt or when creating graph - ignoring'); + args.length = 0; + } + } + //relative to playlist + else { f = filter.add_source(arg, filter.pl); links.length = 0; } @@ -3568,14 +3627,14 @@ print(GF_LOG_ERROR, 'Add ' + (prev_f ? 'filter' : 'source') + ' ' + arg + ' failed: ' + e); disable_source(s); filter.lock_all(false); - return; + return; } //setup links if (apply_links(links, f, fchain)) { disable_source(s); filter.lock_all(false); - return; + return; } //add to chain @@ -3595,11 +3654,11 @@ if (apply_links(links, null, fchain)) { disable_source(s); filter.lock_all(false); - return; + return; } - prev_f = null; + prev_f = null; } - if (prev_f) { + if (prev_f && (typeof f.__dummy_tmp == 'undefined')) { filter.set_source_restricted(prev_f); } @@ -3685,9 +3744,9 @@ sources.push(s); if (s.id==="") { - s.logname = el.src0.in.split('\\').pop().split('/').pop(); + s.logname = el.src0.in.split('\\').pop().split('/').pop(); } else { - s.logname = s.id; + s.logname = s.id; } let parent_seq = get_sequence_by_json(seq); @@ -3711,7 +3770,7 @@ open_source(s); play_source(s); } else { - print(GF_LOG_DEBUG, 'queue source in seq'); + print(GF_LOG_DEBUG, 'queue source in seq'); } } @@ -3866,7 +3925,7 @@ } seq.stop_time = parse_date_time(pl.stop, true); - if ((seq.stop_time<0) || (seq.stop_time<=seq.start_time)) + if ((seq.stop_time<0) || (seq.stop_time<=seq.start_time)) seq.stop_time = -1; seq.transition_effect = pl.transition || null; @@ -3905,7 +3964,7 @@ } if (Array.isArray(pl.sources)) { for (let i=0; i<pl.sources.length; i++) { - let s_id = pl.sourcesi; + let s_id = pl.sourcesi; if (typeof s_id != 'string') { print(GF_LOG_WARNING, 'Invalid scene.sources element ' + s_id + ', expecting string - ignoring element ' + JSON.stringify(pl) ); return false; @@ -4044,7 +4103,7 @@ if (s.length==2) { filter.vsize.x = parseInt(s0); filter.vsize.y = parseInt(s1); - } + } else print(GF_LOG_WARNING, "Wrong syntax for option \`vsize\` in playlist config, ignoring"); } else if (propertyName == 'fps') { @@ -4055,7 +4114,7 @@ } else if (typeof pl.fps == 'number') { filter.fps.n = pl.fps; filter.fps.d = 1; - } + } else print(GF_LOG_WARNING, "Wrong syntax for option \`fps\` in playlist config, ignoring"); } else if (propertyName == 'dynpfmt') { @@ -4271,7 +4330,7 @@ } group.set = function(prop, val) { let update_type = group_get_update_type(prop, false); - //no modifications allowed or not defined + //no modifications allowed or not defined if (update_type<0) { return; } @@ -4355,7 +4414,7 @@ if (sys.file_exists(url)) { return new Function(args, sys.load_file(url, true) ); } else { - return new Function(args, jscode); + return new Function(args, jscode); } } @@ -4365,7 +4424,7 @@ print(GF_LOG_WARNING, 'Unrecognized script ' + JSON.stringify(pl) ); return; } - if (pl.skip || false) + if (pl.skip || false) return; let id = pl.id || null; @@ -4379,7 +4438,7 @@ print(GF_LOG_WARNING, "Multiple scripts with id `" + id + "` defined, ignoring subsequent declarations"); return null; } - + script_obj.pl_update = true; script_obj.next_time = 0; @@ -4423,7 +4482,7 @@ if (script.next_time) { if (video_playing && (script.next_time>video_time)) continue; if (audio_playing && (script.next_time>audio_time)) continue; - } + } try { let res = script.run_script.apply(script, ) || 0; @@ -4576,7 +4635,7 @@ if (evt != 'events') { watcher.evt = sys.get_event_type(evt0); if (!watcher.evt) { - print(GF_LOG_WARNING, 'Unkown event type ' + pl.watch + ' discarding watcher'); + print(GF_LOG_WARNING, 'Unknown event type ' + pl.watch + ' discarding watcher'); remove_watcher(watcher, false); return; } @@ -4804,7 +4863,7 @@ } else if (type==='config') { if (!playlist_loaded) { parse_config(pl); - } + } } else if (type==='style') { parse_style(pl); } else { @@ -4979,7 +5038,7 @@ return; } else if (!Array.isArray(paramsname) && (typeof paramsname == 'object')) { return; - } else { + } else { new_val = paramsname; } @@ -5180,7 +5239,7 @@ } if (s) scene.sequences.push(s); - }); + }); } scene.mod.update_flag = UPDATE_PID; @@ -5262,8 +5321,8 @@ } } } - - //no modifications allowed or not defined + + //no modifications allowed or not defined if (update_type<0) { return; } @@ -5356,7 +5415,7 @@ } let last_val = -1; - pl.keys.forEach (v => { + pl.keys.forEach (v => { if (typeof v != 'number') valid=false; if (last_val<0) { if (v) valid = false; @@ -5435,7 +5494,7 @@ }); return true; -} +} function parse_date_time(d, for_seq) { @@ -5480,7 +5539,7 @@ function parse_timer(pl) { - if (!validate_timer(pl)) + if (!validate_timer(pl)) return; let eval_start_time = false; @@ -5504,16 +5563,16 @@ let crc = sys.crc32(JSON.stringify(pl)); if (crc != timer.crc) { - //we don't track changes of the timer, we blindly replace it + //we don't track changes of the timer, we blindly replace it if (timer.crc) timer_restore(timer); - + timer.crc = crc; } else { return; } - timer.keys = pl.keys; + timer.keys = pl.keys; timer.anims = ; pl.anims.forEach(anim =>{ let a = {}; @@ -5528,8 +5587,8 @@ a.mode = 2; a.fun = fn_from_script('interp', anim.mode); } - } - + } + a.postfun = (typeof anim.postfun == 'string') ? fn_from_script('res', 'interp', anim.postfun) : null; let mod = anim.end || "freeze"; @@ -5635,7 +5694,7 @@ a.targets.push(tar); }); timer.anims.push(a); - }); + }); timer.loop = pl.loop || false; timer.pause = pl.pause || false; @@ -5722,7 +5781,7 @@ } let frac = (video_time - timer.activation_time) * video_time_inc / video_timescale; - + if (frac > timer.duration) { if (!timer.loop && (timer.stop_time<0) ) { timer.active_state = 2; @@ -6152,7 +6211,7 @@ } let s1 = null; if (active_scene && active_scene.mod.pids.length) { - if (sys.test_mode) + if (sys.test_mode) s1 = 'Signal lost'; else if (active_scene.mod.pids0.pid.source.no_signal_state) s1 = 'No signal (next scheduled in ' + Math.floor(active_scene.mod.pids0.pid.source.no_signal_state) + ' s)'; @@ -6257,7 +6316,7 @@ let zNear = (active_camera && active_camera.znear) ? active_camera.znear : 0.1; let zFar = (active_camera && active_camera.zfar) ? active_camera.zfar : (10 * (video_width>video_height ? video_width : video_height)); - //pick view distance so that with no transformation, a rectangular scene with size 100%x100% is exactly fullscreen + //pick view distance so that with no transformation, a rectangular scene with size 100%x100% is exactly fullscreen let final_z = video_height/2 / Math.tan(fieldOfView/2); let pos = {x:0, y:0, z: final_z}; let center = {x:0, y:0, z: 0}; @@ -6353,7 +6412,7 @@ } if (active_scene.mod.pids.length && active_scene.mod.pids0.pid && active_scene.mod.pids0.pid.source.no_signal) { draw_scene_no_signal(); - return; + return; } uniform_reload = active_scene.gl_uniforms_reload; } else { @@ -6731,7 +6790,7 @@ if (!tx) return; canvas_draw(path, tx); - return; + return; } if (op_type) { @@ -8205,4 +8264,3 @@ } return null; } -
View file
gpac-2.4.0.tar.gz/share/scripts/jsf/glpush.js -> gpac-26.02.0.tar.gz/share/scripts/jsf/glpush.js
Changed
@@ -1,3 +1,28 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2020-2024 + * All rights reserved + * + * This file is part of GPAC / AVGenerator filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + import {WebGLContext} from 'webgl' import {Texture, Matrix} from 'evg' import { Sys as sys } from 'gpaccore' @@ -5,6 +30,7 @@ //metadata filter.set_name("glpush"); +filter.set_class_hint(GF_FS_CLASS_AV); filter.set_desc("GPU texture uploader"); filter.set_version("1.0"); filter.set_author("GPAC team"); @@ -20,7 +46,6 @@ let gpac_doc = (sys.get_opt("temp", "gendoc") == "yes") ? true : false; //don't initialize gl if doc gen or help if (gpac_help || gpac_doc) return; - gl = new WebGLContext(16, 16); } let pids=; @@ -28,13 +53,19 @@ function cleanup_texture(pid) { pid.o_textures.forEach( t => { - gl.deleteTexture(t.id); + if (t.id) gl.deleteTexture(t.id); }); pid.o_textures = ; } filter.configure_pid = function(pid) { + //init gl only when configuring input - doing it at init may result in uninitialized GL + if (!gl) { + gl = new WebGLContext(16, 16); + if (!gl) return GF_SERVICE_ERROR; + } + if (typeof pid.o_pid == 'undefined') { pid.o_pid = this.new_pid(); pid.o_pid.i_pid = pid; @@ -64,7 +95,7 @@ pid.o_h = i_h; pid.o_pf = i_pf; cleanup_texture(pid); - print('Configure pid ' + pid.o_w + 'x' + pid.o_h + '@' + pid.o_pf); + print(GF_LOG_DEBUG, 'Configure pid ' + pid.o_w + 'x' + pid.o_h + '@' + pid.o_pf); if (!i_stride) i_stride = i_w;
View file
gpac-26.02.0.tar.gz/share/scripts/jsf/mediaserver
Added
+(directory)
View file
gpac-26.02.0.tar.gz/share/scripts/jsf/mediaserver/init.js
Added
@@ -0,0 +1,2279 @@ +import { Sys as sys } from 'gpaccore' +import {XMLHttpRequest} from 'xhr' + +globalThis.sys = sys; + +let server_ports=0; +let do_quit = false; +let force_quit = false; +let all_requests = ; + +const MANI_DASH = 1; +const MANI_HLS = 2; +const HLS_MASTER = 1; +const HLS_VARIANT = 2; +const LIVE_EDGE_MAX_DIST = 5; +const DEACTIVATE_TIMEOUT_MS = 5000; +//safety on how long we will make the client wait for the MABR file to be available +//the overall timeout depends on repair parameters +const MABR_TIMEOUT_SAFETY = 200; + +//timeout before MABR self-deactivates if no request on a PID +const MABR_PID_DEACTIVATION_TIMEOUT = 10000; + +const CACHE_TYPE_HTTP = 0; +const CACHE_TYPE_MOD = 1; +const CACHE_TYPE_MABR = 2; + +const DEFAULT_MABR_UNLOAD_SEC = 4; +const DEFAULT_KEEPALIVE_SEC = 4; +const DEFAULT_ACTIVATE_CLIENTS = 1; +const DEFAULT_MCACHE = false; +const DEFAULT_REPAIR = 'false'; // 'false', 'true' or 'auto' +const DEFAULT_CORRUPTED = false; +const DEFAULT_TIMESHIFT = 10; +const DEFAULT_GCACHE = false; +const DEFAULT_CHECKIP = false; + +//metadata +filter.set_name("mediaserver"); +filter.set_class_hint(GF_FS_CLASS_NETWORK_IO); +filter.set_desc("Media Server"); +filter.set_version("1.0"); +filter.set_author("GPAC team - (c) Telecom Paris 2024 - license LGPL v2"); + +let filter_help = `This filter is an HTTP server and proxy for GET and HEAD requests supporting Multicast ABR sources. + +This filter does not produce PIDs, it attaches to HTTP output filter. +If no HTTP output filter is specified in the session, the filter will create one. +The file \`.gpac_auth\`, if present in current working directory, will be used for authentication unless \`--rdirs\` is set. + +If more options need to be specified for the HTTP output filter, they can be passed as local options or using global options: +EX gpac mediaserver:cors=on --user_agent=MyUser + +Although not recommended, a server may be specified explicitly: +EX gpac mediaserver httpout:OPTS +In this case, the first \`httpout\` filter created will be used. + +Default request handling of \`httpout\` filter through read / write directories is disabled. + +# Services Configuration +The service configuration is set using -scfg(). It shall be a JSON file containing a single array of services. +Each service is a JSON object with one or more of the following properties: +- id: (string, default null) Service identifier used for logs +- active: (boolean, default true) service is ignored if false +- http: (string, default null) URL of remote service to proxy (either resource name or server path) +- gcache: (boolean, default ${DEFAULT_GCACHE}) use gpac local disk cache when fetching media from HTTP for this service +- local: (string, default null) local mount point of this service +- keepalive: (number, default ${DEFAULT_KEEPALIVE_SEC}) remove the service if no request received for the indicated delay in seconds (0 force service to stay in memory forever) +- mabr: (string, default null) address of multicast ABR source for this service +- timeshift: (number, default ${DEFAULT_TIMESHIFT}) time in seconds a cached file remains in memory +- unload: (number, default ${DEFAULT_MABR_UNLOAD_SEC}) multicast unload policy +- activate: (number, default ${DEFAULT_ACTIVATE_CLIENTS}) multicast activation policy +- mcache: (boolean, default ${DEFAULT_MCACHE}) cache manifest files +- repair: (string, default ${DEFAULT_REPAIR}) enable unicast repair in MABR stack + - false: disable repair + - true: enable repair using source URL or repair servers indicated in MABR + - auto: enable repair only from repair servers indicated in MABR +- corrupted: (boolean, default ${DEFAULT_CORRUPTED}) forward corrupted files if parsable (valid container syntax, broken media) +- check_ip: (boolean, , default ${DEFAULT_CHECKIP}) monitor IP address and port rather than connection when tracking active clients +- noproxy: (boolean) disable proxy for service when local mount point is set. Default is \`true\` if both \`local\` and \`http\` are set, \`false\` otherwise +- sources: (array, default null) list of sources objects for file-only services. Each source has the following property: +- name: name as used in resource path, +- url: local or remote URL to use for this resource. +- js: (string, default null) built-in or custom request resolver + +Any JSON object with the property \`comment\` set will be ignored. + +Not all properties are used for each type of service and other properties can be defined by custom request resolvers. + +# Proxy versus Server +All services using \`http\` option can be exposed by the server without exposing the origin URL, rather than being proxied. To enable this, the \`local\` service configuration option must be set to: +- the exposed server path, in which case manifest names are not rewritten +- or the exposed manifest path, in which case manifest names are rewritten, but only one manifest can be exposed (does not work with dual MPD and M3U8 services) + +EX { "http": "https://test.com/live/dash/live.mpd", "local": "/service1/"} +The server will translate any request \`/service1/foo/bar.ext\` into \`https://test.com/live/dash/foo/bar.ext\`. + +EX { "http": "https://test.com/live/dash/live.mpd", "local": "/service1/manifest.mpd"} +The server will translate: +- request \`/service1/manifest.mpd\` into \`https://test.com/live/dash/live.mpd\` +- any request \`/service1/foo/bar.ext\` into \`https://test.com/live/dash/foo/bar.ext\` + +Note: The URL must point to a self-contained subdirectory of the remote site. Any URLs outside this directory will either fail or be resolved as absolute path on the remote site. + +When \`local\` is not set, these services are always acting as proxies for the \`http\` URL. + +When \`noproxy\` is explicitly set to false for the services with both \`http\` and \`local\`, the remote URL will be available as a proxy service as well. + +# HTTP Proxy and Relay +The server can act as a proxy for HTTP requests, either for any requests or by domain or resource name. + +__Service configuration parameters used :__ \`http\` (mandatory), \`gcache\`, \`local\`. + +Configuration for activating proxy for a specific network path: +EX { "http": "https://test.com/video/"} + +Configuration for activating proxy for any network path: +EX { "http": "*"} + +Configuration for a relay on a given path: +EX { "http": "https://test.com/some/path/to/video/", "local": "/myvids/"} + +This will resolve any request \`http://localhost/myvids/*\` to \`https://test.com/some/path/to/video/*\` + +Note: The requests are never cached in memory in this mode, but can be cached on disk if \`gcache\` is set. + +# HTTP Streaming Cache +The server can act as a cache for live HTTP streaming sessions. The live edge can be cached in memory for a given duration. + +__Service configuration parameters used :__ \`http\` ( mandatory), \`timeshift\`, \`mcache\`, \`gcache\`, \`keepalive\` and \`local\`. + +Configuration for proxying while caching a live HTTP streaming service: +EX { "http": "https://test.com/dash/live.mpd", "timeshift": 30 } + +Configuration for relay caching a live HTTP streaming service: +EX { "http": "https://test.com/dash/live.mpd", "timeshift": 30, "local": "/myservice/test.mpd"} + +The \`local\` service configuration option can be set to: +- the exposed server path, in which case manifest names are not rewritten +- or the exposed manifest path, in which case manifest names are rewritten, but only one manifest can be exposed (does not work with dual MPD and M3U8 services) + +# Multicast ABR Gateway +The server can be configured to use a multicast ABR source for an HTTP streaming service, without any HTTP source. + +__Service configuration parameters used :__ \`mabr\` (mandatory), \`local\` (mandatory), \`corrupted\`, \`timeshift\` and \`keepalive\`. + +The multicast source can be DVB-MABR (e.g. \`mabr://235.0.0.1:1234/\`), ATSC3.0 (e.g. \`atsc://\`) or ROUTE (e.g. \`route://235.0.0.1:1234/\`). +- If the multicast is replayed from a file, netcap ID shall be set in this multicast URL (e.g. \`:NCID=N\`). +- If a specific IP interface is used, it can also be set in multicast URL (e.g. \`:ifce=IP\`). + +For example, with \`local\` set to \`/service/live.mpd\` with \`mabr\` set, the server will expose the multicast service as \`http://localhost/service/live.mpd\`. +The manifest name can be omitted, in which case the exact manifest name used in the broadcast shall be used (and known to the client). + +Configuration for exposing a MABR session: +EX { "mabr": "mabr://234.0.0.1:1234", "local": "/service1", "timeshift": 30 } + +# Multicast ABR Gateway with HTTP cache +The server can be configured to use a multicast source as an alternate data source of a given HTTP streaming service. + +__Service configuration parameters used :__ \`http\` (mandatory), \`mabr\` (mandatory), \`local\`, \`corrupted\`, \`timeshift\`, \`repair\`, \`gcache\`, \`mcache\`, \`unload\`, \`activate\`, \`keepalive\` and \`js\`. + +The multicast service can be dynamically loaded at run-time using the \`unload\` service configuration option: +- if 0, the multicast is started when loading the server and never ended, +- otherwise, the multicast is started dynamically and ended \`unload\` seconds after last deactivation. + +The qualities in the multicast service can be dynamically activated or deactivated using the \`activate\` service configuration option: +- if 0, multicast streams are never deactivated, +- otherwise, a multicast representation is activated only if at least \`activate\` clients are consuming it, and deactivated otherwise. + +The multicast service can use repair options of the MABR stack using \`repair\` service configuration option: +- if false, the file will not be sent until completely received (this increases latency), +- otherwise, file data will be pushed as soon as available in order (after reception or repair). + +If the \`corrupted\` option is set together with \`repair\`, HTTP-based repair is disabled and corrupted files are patched using the \`repair=strict\` mode of the \`routein\` filter. +If files are completely lost, they will be fetched from \`http\`source. +Warning: This may likely result in decoding/buffering pipeline errors and could fail with some players expecting no timeline holes (such as browsers). GPAC supports this. + +The number of active clients on a given quality is computed using the client connection state: any disconnect/reconnect from a client for the same quality will trigger a deactivate+activate sequence. +If \`check_ip\` is set to true, the remote IP address+port are used instead of the connection. This however assumes that each client has a unique IP/port which may not always be true (NATs). + +If \`timeshift\` is 0 for the service, multicast segments will be trashed as soon as not in use (potentially before the client request). + +Note: Manifest files coming from multicast are currently never cached. + +Configuration for caching a live HTTP streaming service with MABR backup: +EX { "http": "https://test.com/dash/live.mpd", "mabr": "mabr://234.0.0.1:1234", "timeshift": 30} + +For such services, the custom HTTP header \`X-From-MABR\` is defined: +- for client request, a value of \`no\` will disable MABR cache for this request; if absent or value is \`yes\`, MABR cache will be used if available +- for client response, a value of \`yes\` indicates the content comes from the MABR cache; if absent or value is \`no\` or \`off-edge\`, the content comes from HTTP (\`off-edge\` indicates a request outside of the timeshift buffer) + +The dedicated root endpoint \`/stats\` returns, the response content type's count (\`yes\`, \`no\`, \`off-edge\`) served by the gateway. + + +The \`js\` option can be set to a JS module exporting the following functions: +- init : (mandatory) The function is called once at the start of the server. Parameters: + - scfg: the service configuration object + - return value: must be true if configuration and initialization are successful, false otherwise. + +- service_activation : (optional) The function is called when the service is activated or deactivated. Parameters: + - do_activate (boolean): if true, service is being loaded otherwise it is being unloaded + - return value: none + +- quality_activation : (optional) The function is called when the given quality is to be activated or deactivated. If not present, (de)activation always happens. Parameters (in order): + - do_activate (boolean): if true, quality is being activated otherwise it is being deactivated + - service_id (integer): ID of the service as announced in the multicast + - period_id (string): ID of the DASH Period, ignored (empty) for HLS + - adaptationSet_ID (integer): ID of the DASH AdaptationSet, ignored (-1) for HLS + - representation_ID (string): ID of the DASH representation or name of the HLS variant playlist + - return value: shall be true if activation/deactivation shall proceed and false if activation/deactivation shall be canceled. + +- get_mcast_address : (optional) The function is called when the service is activated. Parameters: + - service_url (string): URL of service for which the multicast adress is queried + - return value: shall be the multicast address to use for the service or null if no multicast is used (active multicast wil then be deactivated). + +# File Services +A file system directory can be exposed as a service. + +__Service configuration parameters used :__ \`local\` (mandatory), \`sources\` (mandatory), \`gcache\` and \`keepalive\`. + +The \`local\` service configuration option must be set to the desired service path, and the \`sources\` service configuration option must one or more valid sources. +Each source is either a file, a directory or a remote URL. + +Configuration for exposing a directory: +EX { "local": "/dserv/", "sources": { "name": "foo/", "url": "my_dir/" } } +This service will expose the content of directory \`my_dir/*\` as \`http://localhost/dserv/foo/*\`. + +In this mode, file serving is handled directly by httpout filter and no memory caching is used. +If the source is a remote HTTP one, the \`gcache\` option will indicate if GPAC local cache shall be used. + +# Module development + +A JS module can be specified using the \`js\` option in the service configuration. The module export functions are: +## init (mandatory) +The function is called once at the start of the server +Parameter: the service configuration object +return value must be true if configuration and initialization are successful, false otherwise + +## resolve (mandatory) +Parameter: an HTTP request object from GPAC + +The function returns an array of two values \`result, delay\`: +- result: null if error, a resolved string indicating either a local file or the reply body, or an object +- delay: if true, the reply is being delayed by the module for later processing + +When an object is returned, the request is handled by the JS module in charge of sending the reply and reading the data. The object shall have the following properties: +- read: same semantics as the request read method +- on_close: optional function called when the request is closed + +# Built-in modules + + +`; + + +filter.set_arg({ name: "scfg", desc: "service configuration file", type: GF_PROP_STRING} ); +filter.set_arg({ name: "quit", desc: "exit server once last service has been deactivated", type: GF_PROP_BOOL, def: "false"} ); + +let services_defs = ; + +let all_services = ; + +function do_log(level, msg) +{ + if (arguments.length==3) print(level, `${arguments2}: ${msg}`); + else print(level, msg); +} + +function setup_representation(rep) +{ + rep.mabr_active = false; + rep.mabr_deactivate_timeout = 0; + rep.nb_active = 0; + rep.first_mabr_tune = 0; + rep.seg_id = null; + rep.seg_dur = 0; + rep.hls_seq_start = 0; + rep.live_edge = false; + rep.radical = null; + rep.suffix = null; + rep.last_edge_compute = 0; + if (rep.template) { + let idx = rep.template.indexOf('$'); + rep.radical = rep.template.substring(0, idx); + let remain = rep.template.substring(idx+1); + idx = remain.indexOf('$'); + rep.suffix = remain.substring(idx+1); + } +} + +function update_manifest(manifest_ab, target_url, mabr_service, cache_file) +{ + let mani = sys.mpd_parse(manifest_ab); + if (!mani) return; + + let hls_active_rep = null; + let service = mabr_service ? mabr_service : all_services.find(s => { + if (target_url.indexOf(s.url)>=0) return true; + if (!s.manifest || !s.manifest.m3u8) return false; + if (!s.manifest) return false; + let period = s.manifest.periods0; + //for HLS, locate rep for this variant URL + let rep = period.reps.find(e => { + if (!e.xlink) return false; + //absolute URLs + if (e.xlink == target_url) return true; + let idx = target_url.indexOf(e.xlink); + if (idx<0) return false; + if (target_url.startsWith(s.base_url)) return true; + return false; + }); + if (rep) { + hls_active_rep = rep; + return true; + } + return false; + }); + if (!service) { + if (mani.m3u8 == HLS_VARIANT) { + do_log(GF_LOG_ERROR, `Failed to locate service for HLS variant ${target_url}`); + return; + } + service = create_service(target_url, true, null); + } + + //HLS master playlist + if (mani.m3u8 == HLS_MASTER) { + //we assume master playlist is static + if (service.manifest) return; + let period = mani.periods0; + period.reps = period.reps.sort((a, b) => a.bandwidth - b.bandwidth); + for (let r=0; r<period.reps.length; r++) { + setup_representation( period.repsr ); + } + service.manifest = mani; + return; + } + //HLS variant playlist + if (mani.m3u8 == HLS_VARIANT) { + if (!service.manifest) return; + let period = service.manifest.periods0; + if (!hls_active_rep) { + do_log(GF_LOG_ERROR, `Service ${service.id} cannot find rep for target ${target_url}`); + return; + } + hls_active_rep.segments = mani.segments; + hls_active_rep.hls_seq_start = mani.seq_start; + hls_active_rep.live_seg_num = mani.live_seg_num; + service.manifest.live = mani.live; + if (cache_file) { + cache_file.manifest_min_update = mani.min_update; + } + + do_log(GF_LOG_DEBUG, `Service ${service.id} updated HLS manifest`); // + JSON.stringify(service.manifest)); + return; + } + + for (let p=0; p<mani.periods.length; p++) { + let period = mani.periodsp; + period.reps = period.reps.sort((a, b) => a.bandwidth - b.bandwidth); + for (let r=0; r<period.reps.length; r++) { + setup_representation( period.repsr ); + } + } + let updated = false; + if (!service.manifest) { + service.manifest = mani; + } else { + updated = true; + service.manifest.live_utc = mani.live_utc; + //update existing manifest + //remove old periods + for (let p=0; p<service.manifest.periods.length; p++) { + let period = service.manifest.periodsp; + let new_p = mani.periods.find(e => e.id == period.id); + if (new_p) continue; + service.manifest.periods.splice(p); + p--; + continue; + } + //push new periods + for (let p=0; p<mani.periods.length; p++) { + let rep_inserted = false; + let period = mani.periodsp; + let old_p = service.manifest.periods.find(e => e.id == period.id); + if (!old_p) { + service.manifest.periods.push(period); + continue; + } + //same period, update reps + for (let r=0; r<period.reps.length; r++) { + let rep = period.repsr; + let old_r = old_p.reps.find(e => (e.ID == rep.ID || e.bandwidth == rep.bandwidth)); + //rep injection is not prohibited, rep removal is + if (!old_r) { + setup_representation( rep); + old_p.reps.push(rep); + rep_inserted = true; + continue; + } + //same rep, update segments and live edge info + old_p.bandwidth = rep.bandwidth; + old_r.segments = rep.segments; + old_r.live_seg_num = rep.live_seg_num; + } + if (rep_inserted) { + old_p.reps = old_p.reps.sort((a, b) => a.bandwidth - b.bandwidth); + } + } + } + if (cache_file) { + cache_file.manifest_min_update = mani.min_update; + } + do_log(GF_LOG_DEBUG, `Service ${service.id} manifest ${ (updated ? 'updated' : 'received')} for ${target_url}`); // + ': ' + JSON.stringify(service.manifest)); +} + +function locate_service(target_url) +{ + let service = null; + for (let i=0; i<all_services.length; i++) { + service = all_servicesi; + if (service.url == target_url) break; + if (! service.manifest) { + service=null; + continue; + } + + //todo, deal with base urls ? + let base_url = service.base_url; + if (target_url.startsWith(base_url)) break; + service=null; + } + return service; +} + +function locate_service_quality(target_url, in_service) +{ + let service = in_service; + let with_base_url = null; + if (!service) { + for (let i=0; i<all_services.length; i++) { + service = all_servicesi; + if (service.url == target_url) break; + if (! service.manifest) { + service=null; + continue; + } + + //todo, deal with base urls ? + let base_url = service.base_url; + if (target_url.startsWith(base_url)) break; + service=null; + } + if (!service) return null,null,null; + } + if (!service.manifest) return service, null,null; + + if (!with_base_url) with_base_url = service.base_url; + + //extract seg name + let seg_name = target_url.substring(with_base_url.length); + + let period = null; + let rep = null; + let now = sys.get_utc(); + //find segment + for (let p=0; p<service.manifest.periods.length; p++) { + period = service.manifest.periodsp; + for (let r=0; r<period.reps.length; r++) { + rep = period.repsr; + rep.seg_id = null; + rep.seg_dur = 0; + rep.live_edge = false; + rep.seg_num = 0; + if (rep.xlink && (seg_name.indexOf(rep.xlink)>=0)) { + rep=null; + continue; + } + if (rep.radical && ! seg_name.startsWith(rep.radical)) { + rep=null; + continue; + } + + //m3u8 or SegmentTimeline + if (rep.segments.length) { + let found_rep = false; + for (let si=0; si<rep.segments.length; si++) { + let s_idx = rep.segments.length - 1 - si; + let seg = rep.segmentss_idx; + if (seg.url == seg_name) { + rep.seg_dur = seg.duration * 1000 / rep.timescale; + if (rep.radical) { + let num_time = seg_name.substring(rep.radical.length); + let idx = num_time.indexOf(rep.suffix); + if (idx>=0) { + rep.seg_id = num_time.substring(0, idx); + } + } else if (service.manifest.m3u8) { + rep.seg_id = '' + (rep.hls_seq_start + s_idx); + } + if (si<=LIVE_EDGE_MAX_DIST) { + rep.live_edge = true; + } + found_rep = true; + break; + } + } + if (found_rep) break; + } + + if (rep.template) { + let num_time = seg_name.substring(rep.radical.length); + let idx = num_time.indexOf(rep.suffix); + if (idx>=0) { + rep.seg_id = num_time.substring(0, idx); + } + if (!rep.seg_dur) + rep.seg_dur = rep.duration * 1000 / rep.timescale; + + //compute live edge time divide then multiply to avoid overflow + if (rep.last_edge_compute + rep.seg_dur < now) { + let live_edge_num = now; + live_edge_num -= service.manifest.ast; + live_edge_num -= period.start; + live_edge_num /= 1000; + live_edge_num /= rep.duration; + live_edge_num *= rep.timescale; + live_edge_num = Math.ceil(live_edge_num) + 1; + rep.live_seg_num = live_edge_num; + rep.last_edge_compute = now; + } + //todo in live, figure out if we are on the live edge + if (rep.live_seg_num && rep.seg_id) { + rep.seg_num = parseInt(rep.seg_id); + let seg_num = rep.seg_num - rep.live_seg_num; + if (Math.abs(seg_num) <= LIVE_EDGE_MAX_DIST) { + rep.live_edge = true; + } + } + break; + } + rep = null; + } + if (rep) { + rep.period_id = period.ID; + break; + } + period = null; + } + do_log(GF_LOG_DEBUG, `Located service ${service.id} for URL ${seg_name}${(rep ? (' rep: ' + rep.ID ) : '' )}`); + return service, rep, period; +} + + +function cat_buffer(dst_ab, src_ab) +{ + let tmp = new Uint8Array(dst_ab ? dst_ab.byteLength + src_ab.byteLength : src_ab.byteLength); + if (dst_ab) + tmp.set(new Uint8Array(dst_ab), 0); + tmp.set(new Uint8Array(src_ab), dst_ab ? dst_ab.byteLength : 0); + return tmp.buffer; +} +globalThis.cat_buffer = cat_buffer; + + +function get_mediaserver_stats(){ + return all_services.reduce((r,s) => { + rs.id = s.stats; + return r; + }, {}); +} +globalThis.get_mediaserver_stats = get_mediaserver_stats; + + +//custom HTTPout request handler +let httpout = {}; +httpout.on_request = (req) => +{ + //use pre-authentication done by server + if (req.auth_code!=200) { + req.reply = req.auth_code; + req.send(); + return; + } + + //not yet supported + if ((req.method !== 'GET') && (req.method !== 'HEAD')) { + req.reply = 405; + req.send('Method not allowed by server'); + return; + } + + do_log(GF_LOG_DEBUG, `Got request ${req.method} for ${req.url}`); + + if (req.url.startsWith('/stats')){ + req.reply = 200; + req.body = JSON.stringify(get_mediaserver_stats()); + req.send(); + return; + } + + //setup request + req.target_url = null; + req.is_head = (req.method === 'HEAD'); + req.start_time = sys.clock_ms(); + req.bytes_in_req = 0; + req.cache_file = null; + req.ab_offset = 0; + req.ab_queue = null; + req.ab_total_bytes = 0; + req.data = null; + req.waiting_mabr = 0; + req.xhr_done = false; + req.activate_rep_timeout = null; + req.xhr = null; + req.br_start=0; + req.br_end=0; + req.from_cache=false; + req.no_cache=false; + req.do_cache = false; + req.jsmod=null; + req.up_rate=0; + req.service = null; + + all_requests.push(req); + + req.get_down_rate = function() { + let bitrate = this.bytes_in_req * 8; // /1000 for kbits but we then need to divide by time/1000 for secs + let time = sys.clock_ms() - this.start_time; + if (!time) time=1; + return Math.floor(1000*bitrate/time), time; + } + req.get_up_rate = function() { + return this.up_rate; + } + req.on_done = function() { + let bitrate, time; + bitrate, time = this.get_down_rate(); + bitrate = Math.floor(bitrate/1000); + let orig = 'HTTP' + if (this.from_cache) { + orig = 'local cache'; + } else if (this.cache_file) { + orig = ((this.cache_file.cache_type==CACHE_TYPE_MABR) ? 'MABR' : 'HTTP') +' cache'; + } else if (this.jsmod) { + orig = 'JS module'; + } + do_log(GF_LOG_INFO, `Done sending ${this.url} (${this.reply}) from ${orig} in ${time} ms at ${bitrate} kbps (${this.bytes_in_req} bytes)`); + }; + + //used when reading from JS module + req._read_from_mod = function(ab) { + let res = this.jsmod.read(ab); + if (res==0) { + this.on_done(); + return 0; + } + if (res<0) return res; + this.bytes_in_req += res; + return res; + } + //used when reading from XHR without cache + req._read_from_queue = function(ab) { + if (!this.ab_queue.length) { + if (this.xhr_done) { + this.on_done(); + if (this.activate_rep_timeout) this.activate_rep_timeout.update(); + return 0; + } + return -1; + } + let q_ab=this.ab_queue0; + let to_read = q_ab.byteLength - this.ab_offset; + if (to_read > ab.byteLength) to_read = ab.byteLength; + new Uint8Array(ab, 0, to_read).set(new Uint8Array(q_ab, this.ab_offset, to_read) ); + this.ab_offset += to_read; + if (this.ab_offset == q_ab.byteLength) { + this.ab_offset = 0; + this.ab_queue.shift(); + } + this.bytes_in_req += to_read; + return to_read; + } + + //used when reading from MABR service or cached file + req._read_from_buffer = function(ab) { + if (this.is_head || !this.cache_file) return 0; + + let src_ab = this.cache_file.data; + if (src_ab.byteLength <= this.ab_offset) { + if (this.cache_file.done) { + this.on_done(); + this.cache_file.release(this); + this.cache_file = null; + if (this.activate_rep_timeout) this.activate_rep_timeout.update(); + return 0; + } + //waiting for data + return -1; + } + + let to_read = src_ab.byteLength - this.ab_offset; + if (to_read > ab.byteLength) to_read = ab.byteLength; + new Uint8Array(ab, 0, to_read).set(new Uint8Array(src_ab, this.ab_offset, to_read) ); + this.ab_offset += to_read; + this.bytes_in_req += to_read; + return to_read; + }; + + req._read_abort = function(ab) { + return 0; + }; + + //end of session + req.close = function(code) { + if (code<0) + do_log(GF_LOG_DEBUG, `Client session ${this.url} is closed: ${sys.error_string(code)}`); + + let ridx = all_requests.indexOf(this); + if (ridx>=0) all_requests.splice(ridx, 1); + if (do_quit && !all_requests.length && !all_services.length) force_quit = true; + if (this.jsmod && typeof this.jsmod.on_close == 'function') this.jsmod.on_close(); + this.jsmod = null; + + this.ab_queue = null; + if (this.cache_file) this.cache_file.release(this); + if (this.activate_rep_timeout) this.activate_rep_timeout.update(); + else if (req.service && req.service.keepalive && !req.service.mabr) { + let timeout = (code<GF_OK) ? 100 : 1000*req.service.keepalive; + req.service.unload_timeout = sys.clock_ms() + timeout; + } + //we have an XHR, abort it if needed and move to XHR cache for later reuse + if (this.xhr) { + if (this.xhr && (this.xhr.readyState < 4)) this.xhr.abort(); + if (this.service) { + this.xhr.last_used = sys.clock_ms(); + this.service.xhr_cache.push(this.xhr); + this.xhr = null; + } + } + }; + + req.set_cache_file = function(file) { + if (this.cache_file) return; + this.cache_file = file; + file.nb_users ++; + this.read = this._read_from_buffer; + if (file.xhr_status) { + this.flush_request(); + } else { + if (!file.pending_reqs) file.pending_reqs = ; + file.pending_reqs.push(this); + } + if (file.pid) file.pid.deactivate_timeout = 0; + }; + req.flush_request = function() { + this.reply = this.cache_file.xhr_status; + this.headers_out.push( { "name" : 'Content-Type', "value": this.cache_file.mime} ); + if (this.cache_file.headers) + this.cache_file.headers.forEach( hdr => this.headers_out.push(hdr) ); + if (this.cache_file.range) + this.headers_out.push( { "name" : 'Content-Range', "value": this.cache_file.range} ); + if (this.cache_file.aborted) { + } else if (this.cache_file.done) { + this.headers_out.push( { "name" : 'Content-Length', "value": this.cache_file.data.byteLength} ); + } else { + this.headers_out.push( { "name" : 'Transfer-Encoding', "value": 'chunked'} ); + } + if (this.cache_file.cache_type==CACHE_TYPE_MABR) { + this.service.update_mabr_stats('yes'); + this.headers_out.push( { "name" : 'X-From-MABR', "value": 'yes'} ); + } + else if (this.service && this.service.mabr) { + const val = this.live_edge ? 'no' : 'off-edge'; + this.service.update_mabr_stats(val); + this.headers_out.push( { "name" : 'X-From-MABR', "value": val} ); + } + this.send(); + }; + + + req.fetch_unicast = function() { + //already setup through set_cache_file - typically happens when N requests are pending on MABR file that is later cancelled + //if source can still be cached, a cache file will be created and assigned to all pending requests for the same url + if (this.cache_file) return; + + //MABR without origin server + if (this.service && !this.service.url && !this.service.mabr_repair_url) { + do_log(GF_LOG_DEBUG, `No URL for base repair, cannot fetch ${this.target_url}`); + this.reply = 404; + this.send(); + return; + } + + this.ab_queue = ; + this.manifest_ab = null; + this.read = this._read_from_queue; + + //DASH/HLS rep, create a cache file for media + if (active_rep && !this.no_cache) { + this.cache_file = this.service.create_cache_file(this.target_url, "video/mp4", this.br_start, this.br_end, CACHE_TYPE_HTTP); + this.cache_file.nb_users ++; + this.read = this._read_from_buffer; + } + //DASH/HLS manifest, create a cache file for media + else if (this.service && this.manifest_type && this.service.mani_cache) { + this.cache_file = this.service.create_cache_file(this.target_url,(this.manifest_type==MANI_DASH) ? "application/dash+xml" : "application/vnd.apple", this.br_start, this.br_end, CACHE_TYPE_HTTP); + this.cache_file.nb_users ++; + this.read = this._read_from_buffer; + } + //MABR without origin server but repair URL signaled from mabr, update target url + if (this.service && !this.service.url) { + this.target_url = sys.url_cat(this.service.mabr_repair_url, this.target_url); + } + + this.xhr = null; + if (this.service && this.service.xhr_cache.length) { + this.xhr = this.service.xhr_cache.shift(); + } + if (!this.xhr) + this.xhr = new XMLHttpRequest(); + + this.xhr.last_used = sys.clock_ms(); + let do_cache = false; + if (!this.cache_file) { + do_cache = req.do_cache; + } + this.xhr.cache = do_cache ? "normal" : "none"; + this.xhr.responseType = "push"; + this.xhr.onprogress = function (evt) { + if (!evt) return; + if (evt.buffer.byteLength) { + let new_bytes = evt.buffer.slice(0); + req.ab_queue.push(new_bytes); + req.ab_total_bytes += new_bytes.byteLength; + req.up_rate = evt.bps; + + //gather manifest + if (req.manifest_type) { + req.manifest_ab = cat_buffer(req.manifest_ab, new_bytes); + } + //gather cache (can be media or manifest) + if (req.cache_file) { + req.cache_file.data = cat_buffer(req.cache_file.data, new_bytes); + } + let client_rate, req_time; + client_rate, req_time = req.get_down_rate(); + let buf_size = req.ab_total_bytes - req.bytes_in_req; + //do_log(GF_LOG_DEBUG, `Up rate ${evt.bps} bps - down rate ${client_rate} bps ${buf_size} queued bytes`); + if (buf_size > 1000000) return 10000; + else if (buf_size > 200000) return client_rate; + return 0; + } + }; + + this.xhr.onerror = function() { + do_log(GF_LOG_ERROR, `Request failed for ${req.target_url}`); + if (req.reply) return; + req.reply = 502; + req.send(); + req.read = req._read_abort; + if (req.cache_file) { + req.cache_file.xhr_status = req.reply; + req.cache_file.aborted = true; + req.cache_file.done = true; + //flush all pending requests that were waiting for XHR status + if (req.cache_file.pending_reqs) { + req.cache_file.pending_reqs.forEach(r => r.flush_request() ); + req.cache_file.pending_reqs = null; + } + } + }; + + this.xhr.onreadystatechange = function() { + if (this.readyState == 4) { + do_log(GF_LOG_DEBUG, `${req.target_url} received`); + if (req.manifest_type) { + update_manifest(req.manifest_ab, req.target_url, req.service, req.cache_file); + req.manifest_ab = null; + } + //cache can be set for manifests or media + if (req.cache_file) { + do_log(GF_LOG_INFO, `${req.target_url} closing cache file`); + + req.cache_file.done = true; + //purge delay applies on received file + req.cache_file.received = sys.clock_ms(); + } + req.xhr_done = true; + //and move to XHR cache + if (req.service) { + req.xhr.last_used = sys.clock_ms(); + req.service.xhr_cache.push(req.xhr); + req.xhr = null; + } + return; + } + if (this.readyState != 2) return; + let content_range = null; + if (req.cache_file) req.cache_file.headers = ; + //we got our headers + let headers = this.getAllResponseHeaders().split('\r\n'); + headers.forEach(n => { + let h = n.split(': '); + let hdr = { "name" : h0, "value": h1}; + //filter headers + let h_name = h0.toLowerCase(); + if (h_name === 'upgrade') return; + if (h_name === ':status') return; + if (h_name === 'content-range') content_range = h1; + + req.headers_out.push(hdr); + if (req.cache_file) { + //cache some headers + if ((h_name !== 'content-length') + && (h_name !== 'transfer-encoding') + && (h_name !== 'server') + && (h_name !== 'date') + ) { + req.cache_file.headers.push(hdr); + } + } + + if (h_name == 'content-type') { + if ((h1.indexOf('application/vnd.apple')>=0) || (h1.indexOf('x-mpegurl')>=0)) { + req.manifest_type = MANI_HLS; + } + else if (h1.indexOf('dash+xml')>=0) { + req.manifest_type = MANI_DASH; + } + if (req.cache_file) req.cache_file.mime = h1; + } + }); + do_log(GF_LOG_DEBUG, `Sending reply to ${req.url} code ${req.xhr.status}`); + req.reply = req.xhr.status; + if (req.service && req.service.mabr) { + const val = req.live_edge ? 'no' : 'off-edge'; + req.service.update_mabr_stats(val); + req.headers_out.push( { "name" : 'X-From-MABR', "value": val} ); + } + req.send(); + + if (req.cache_file) { + req.cache_file.xhr_status = req.reply; + if (req.reply == 206) req.cache_file.range = content_range; + //prune if error + if (req.reply>=400) { + req.cache_file.headers = null; + req.cache_file.aborted = true; + req.xhr_done = true; + } + //flush all pending requests that were waiting for XHR status + if (req.cache_file.pending_reqs) { + req.cache_file.pending_reqs.forEach(r => r.flush_request() ); + req.cache_file.pending_reqs = null; + } + } + }; + this.xhr.open(this.method, this.target_url); + this.xhr.send(); + }; + + //preprocess headers to locate host and extract range + let disable_mabr_cache = false; + let host = null; + let referer = null; + req.headers_in.forEach(h => { + let hlwr = h.name.toLowerCase(); + if (hlwr == "host") host = h.value; + if (hlwr == "referer") referer = h.value; + else if (hlwr == ":authority") host = h.value; + else if ((hlwr == "x-from-mabr") && (h.value.toLowerCase() == 'no')) disable_mabr_cache = true; + else if (hlwr == "range") { + let hdr_range = h.value.split('='); + let range = hdr_range1; + if (range.indexOf(',')>=0) { + req.no_cache = true; + } else { + let idx = range.indexOf('-'); + req.br_start = parseInt(range.substring(0, idx)); + req.br_end = parseInt(range.substring(idx+1)); + //only cache closed ranges + if (!idx || !req.br_end) req.no_cache = true; + } + } + }); + + let check_any_allowed = true; + //support http://myhost/http://url - not really standard + if (!req.service && + (req.url.startsWith('/http://') + || req.url.startsWith('/http%3A%2F%2F') + || req.url.startsWith('/https://') + || req.url.startsWith('/https%3A%2F%2F') + )) { + req.target_url = req.url.substring(1); + if ((target_url.indexOf("127.0.0.1")>=0) || (target_url.indexOf("localhost")>=0)) { + req.reply = 405; + req.send('Must use Host for localhost proxying'); + return; + } + } + //look in mount points + if (!req.target_url) { + let host = null; + let full_name = true; + let service_def = services_defs.find( s => (s.local && (req.url.indexOf(s.local)==0) ) ); + if (!service_def) { + service_def = services_defs.find( s => (s.local_base && (req.url.indexOf(s.local_base)==0) ) ); + full_name = false; + } + //special case for MABR services, check we are loaded + if (service_def && service_def.mabr) { + req.service = locate_service(req.url); + //if http source, only start once we fetch the manifest, otherwise start now + if (!req.service && !service_def.http) + req.service = create_service(null, true, service_def); + req.target_url = req.url; + //fall through + } + //for other services, format target url + if (service_def && !req.service) { + check_any_allowed = false; + req.do_cache = service_def.gcache; + if (service_def.http) { + if (full_name) { + req.target_url = req.url.replace(service_def.local, service_def.http); + } else { + req.target_url = req.url.replace(service_def.local_base, service_def.http_proto + '://' + service_def.http_host + service_def.http_path); + } + } else { + req.target_url = req.url; + } + } + + if (!service_def && referer) { + let src = referer.split('://'); + src = src(src.length>1) ? 1 : 0; + src = src.split('/'); + src.shift(); + src = '/'+src.join('/'); + service_def = services_defs.find( s => (s.local_base && (src.indexOf(s.local_base)==0) ) ); + if (service_def) { + check_any_allowed = false; + req.do_cache = service_def.gcache; + req.target_url = service_def.http_proto + '://' + service_def.http_host + req.url; + } + } + + //local dir or custom JS + if (service_def && !service_def.http && !service_def.mabr) { + //custom JS handler + if (service_def.js_mod) { + let resolved, delayed; + resolved, delayed = service_def.js_mod.resolve(req); + + if (!resolved) { + if (!req.reply) { + do_log(GF_LOG_DEBUG, `Invalid JS handler for ${req.url}`); + req.reply = 404; + req.send(); + } + return; + } + if (delayed) return; + + if (typeof resolved == 'string') { + if (req.from_cache) { + req.read = req._read_from_buffer; + req.target_url = resolved; + } else if (resolved.indexOf('http')>=0) { + req.target_url = resolved; + } else if (typeof resolved == 'string') { + req.body = resolved; + req.send(); + return; + } + } else if (typeof resolved == 'object') { + req.jsmod = resolved; + if (typeof resolved.read == 'function') { + req.read = req._read_from_mod; + return; + } + //otherwise we read from cache and module will populate it + req.read = req._read_from_buffer; + req.from_cache = true; + } else { + req.reply = 501; + req.send('Invalid module resolution'); + return; + } + } + //custom mount point + else if (service_def.sources.length && service_def.local_base) { + let my_url = req.url.replace(service_def.local_base, ''); + let e = service_def.sources.find( a => (a.name == my_url) ); + if (!e) e = service_def.sources.find( a => my_url.startsWith(a.name) ); + if (!e) { + do_log(GF_LOG_DEBUG, `Invalid service definition for ${req.url}`); + req.reply = 404; + req.send(); + return; + } + if (e.url.startsWith('http')) { + req.target_url = e.url; + } else if (e.url.indexOf('://')>=0) { + req.reply = 501; + req.send('Protocol not supported'); + return; + } else { + my_url = my_url.replace(e.name, e.url); + if (sys.file_exists(my_url)) { + req.body = my_url; + req.send(); + return; + } else { + do_log(GF_LOG_DEBUG, `Invalid local path for ${req.url}`); + req.reply = 404; + req.send(); + return; + } + } + } + } + if (req.target_url && !req.from_cache && check_any_allowed) { + //rewrite request url + req.url = req.target_url.substring(req.target_url.indexOf('/', 8)); + } + } + //running as real proxy, check host + if (!req.target_url) { + if (!host) { + req.reply = 405; + req.send('Invalid host name'); + return; + } + let hport = req.tls ? 443 : 80; + let splith = host.split(':'); + if (splith.length>1) { + hport = parseInt(splithsplith.length-1); + } + if ((host.indexOf("127.0.0.1")>=0) || (host.indexOf("localhost")>=0)) { + let e = server_ports.find( e => e == hport); + if (e) { + req.reply = 405; + req.send('Invalid host pointing to this proxy'); + return; + } + } + let use_tls=false; + if (hport!==443) { + let url_no_proto = host + req.url; + let service = all_services.find( s => (url_no_proto.indexOf(s.base_url_no_proto)>=0) ); + if (service && service.force_tls) use_tls = true; + } else { + use_tls = true; + } + //remove default ports in host + if (use_tls && (hport == 443)) host = host.replace(":443", ""); + else if (!use_tls && (hport == 80)) host = host.replace(":80", ""); + + req.target_url = (use_tls ? 'https://' : 'http://') + host + req.url; + } + if (! req.target_url) { + do_log(GF_LOG_DEBUG, `No service found for ${req.url}`); + req.reply = 404; + req.send('No such service'); + return; + } + + do_log(GF_LOG_INFO, `Processing request ${req.method} for ${req.url}`); + do_log(GF_LOG_DEBUG, `Resolved request URL ${req.target_url}`); + let url_lwr = req.target_url.toLowerCase(); + if (url_lwr.indexOf('.m3u8')>=0) req.manifest_type = MANI_HLS; + else if (url_lwr.indexOf('.mpd')>=0) req.manifest_type = MANI_DASH; + else req.manifest_type = 0; + + //locate service + let period, active_rep; + req.service, active_rep, period = locate_service_quality(req.target_url, req.service); + + let purge_delay = req.service ? req.service.purge_delay : 0; + if (!purge_delay) req.no_cache = true; + + //example of how to force highest bandwidth - this assumes segment alignment across all reps and bitstream switching + //not working for HLS as we would need to extract the segment name from the target variant playlist which we may not have + if (0 && active_rep && !req.service.m3u8) { + let reps = period.reps.filter(r => (active_rep.as_idx== r.as_idx) && (r!==active_rep) ); + if (reps.length) { + let force_rep = reps reps.length-1; + let idx = req.target_url.indexOf(active_rep.radical); + let new_url = req.target_url.substring(0, idx) + force_rep.radical + active_rep.seg_id + active_rep.suffix; + req.target_url = new_url; + force_rep.seg_id = active_rep.seg_id; + force_rep.seg_dur = active_rep.seg_dur; + active_rep = force_rep; + } + } + + if (!req.service && check_any_allowed) { + //pure proxy, check allowed domains + let service_def = services_defs.find( s => (!s.noproxy && (host === s.http_host) && req.url.startsWith(s.http_path)) ); + if (!service_def) service_def = services_defs.find( s => (s.http === '*') ); + if (!service_def) { + do_log(GF_LOG_ERROR, `Proxying of ${req.url} not allowed`); + req.reply = 404; + req.send('Requested service is not exposed by server'); + return; + } + req.do_cache = service_def.gcache; + //little opt: create mabr service for dash before sending the request, so that we can signal X-From-MABR=no in the answer + if (req.manifest_type && service_def.mabr) { + req.service = create_service(req.target_url, true, null); + } + } + + if (req.service) { + req.do_cache = req.service.gcache; + //deactivate timeout, will be reactivated when removing request + req.service.unload_timeout = 0; + if (req.manifest_type && !req.service.mani_cache && !req.from_cache) req.no_cache = true; + + //check mcast + if (req.service.dyn_mabr) { + req.service.check_dyn_mabr(); + } + } + + //look in mem cache + if (req.service && !req.no_cache) { + let f = req.service.get_file(req.from_cache ? req.target_url : req.url, req.br_start, req.br_end); + //only cache manifest if not too old - we use more than the min update due to transfer times in mabr + if (f && !f.sticky && (f.cache_type == CACHE_TYPE_MABR) && req.manifest_type && (req.service.url || req.service.mabr_repair_url)) { + if (f.manifest_min_update && (f.received + 3*f.manifest_min_update/2 < sys.clock_ms())) { + //trash + req.service.mem_cache.splice(req.service.mem_cache.indexOf(f) , 1); + do_log(GF_LOG_WARNING, `Cached manifest ${req.url} too old by ${sys.clock_ms() - f.received - f.manifest_min_update} ms - removing and refreshing (${f.received} - ${f.url})`); + f = null; + } + } + + if (f) { + do_log(GF_LOG_INFO, `File ${req.url} present in cache ${ f.done ? '(completed)' : '(transfer in progress)'} (${f.received} - ${f.url})`); + req.service.mark_active_rep(active_rep, req); + return req.set_cache_file(f); + } + } + if (!req.service) { + if (req.target_url.startsWith('http')<0) { + req.reply = 404; + req.send('invalid path'); + return; + } + do_log(GF_LOG_INFO, `No associated service configured, going for HTTP`); + req.fetch_unicast(); + return; + } + + //load MABR service if present + if (req.service.mabr && !req.service.mabr_loaded && !disable_mabr_cache) { + req.service.load_mabr(); + } + //multicast service only + if (req.service.mabr && !req.service.url && !active_rep && !disable_mabr_cache) { + req.waiting_mabr_start = sys.clock_ms(); + req.waiting_mabr = req.waiting_mabr_start + MABR_TIMEOUT_SAFETY; + req.service.pending_reqs.push(req); + return; + } + if (req.from_cache) { + req.service.pending_reqs.push(req); + return; + } + + if (active_rep) { + //mark rep active - this will trigger mcast joining so do this before checking mcast + req.service.mark_active_rep(active_rep, req); + + if (disable_mabr_cache) { + do_log(GF_LOG_INFO, `Rep ${active_rep.ID} seg ${req.url} disbaled MABR for this request, going for HTTP`); + } + else if (req.service && req.service.mabr_failure) { + do_log(GF_LOG_INFO, `Rep ${active_rep.ID} seg ${req.url} MABR setup failed, going for HTTP`); + } + //check if we have mabr active + else if (active_rep.live_edge && active_rep.mabr_active) { + if (req.service && req.service.last_mabr_active && (req.service.last_mabr_active + 3*active_rep.seg_dur/2 < req.start_time)) { + do_log(GF_LOG_WARNING, `Rep ${active_rep.ID} MABR is down since ${req.start_time - req.service.last_mabr_active} ms, going for HTTP`); + } + else if (active_rep.first_mabr_tune) { + do_log(GF_LOG_INFO, `Rep ${active_rep.ID} seg ${req.url} first segment request and no low-latency, going for HTTP`); + active_rep.first_mabr_tune = 0; + } else { + //set a timeout for the request in case MABR fails + req.waiting_mabr_start = sys.clock_ms(); + //we use 3 x segment duration since we don't know if the source was low-latency or not so in worst case we have: + //- one seg_dur delay for multicaster to send the file + //- one seg_dur delay for the file to be received + //- repair delay + req.waiting_mabr = req.waiting_mabr_start + 3*active_rep.seg_dur + MABR_TIMEOUT_SAFETY; + do_log(GF_LOG_INFO, `Waiting for file ${req.url} from MABR (ID ${req.service.mabr_service_id}) - will wait max ${req.waiting_mabr-req.waiting_mabr_start} ms for seg dur ${active_rep.seg_dur}`); + + req.service.pending_reqs.push(req); + return; + } + } else if (active_rep.mabr_active) { + do_log(GF_LOG_INFO, `Rep ${active_rep.ID} seg ${req.url} num ${active_rep.seg_num} is not on live edge ${active_rep.live_seg_num}, going for HTTP`); + } else if (req.service.mabr_service_id) { + do_log(GF_LOG_INFO, `Rep ${active_rep.ID} seg ${req.url} has no active multicast, going for HTTP`); + } else if (req.service.mabr) { + do_log(GF_LOG_INFO, `Rep ${active_rep.ID} seg ${req.url} waiting for multicast bootstrap, going for HTTP`); + } + } + //fetch from unicast + req.fetch_unicast(); +} + +function create_service(http_url, force_mcast_activate, forced_sdesc) +{ + //mabr service is null by default + let s = {}; + s.url = http_url; + s.mabr = null; + s.local = null; + s.mabr_loaded = false; + s.mabr_failure = false; + s.mem_cache = ; + s.id = all_services.length+1; + s.mabr_service_id = 0; + s.source = null; + s.sink = null; + s.pending_reqs=; + s.active_reps_timeouts=; + s.nb_mabr_active = 0; + s.mabr_unload_timeout = 0; + s.dyn_mabr = false; + s.last_mabr_active = 0; + s.unload_timeout = 0; + s.purge_delay = 0; + s.mani_cache = false; + s.gcache = false; + s.check_ip = DEFAULT_CHECKIP; + s.keepalive = DEFAULT_KEEPALIVE_SEC; + s.xhr_cache = ; + s.pending_deactivate = ; + s.mabr_repair_url = null; + if (http_url && http_url.toLowerCase().startsWith('https://')) s.force_tls = true; + else s.force_tls = false; + s.hdlr = null; + s.manifest = null; + //do we have a mcast service for this ? + let serv_cfg = forced_sdesc; + if (!forced_sdesc) { + let url_noport = http_url.replace(/(^\/\:+):\/\/(^\/+):(0-9+)\//, "$1://$2/"); + serv_cfg = services_defs.find(e => e.http == url_noport); + if (!serv_cfg) serv_cfg = services_defs.find(e => e.http == http_url); + } + + if (serv_cfg) { + do_log(GF_LOG_DEBUG, `Service ${forced_sdesc ? forced_sdesc.local_base : http_url} has custom config${ (serv_cfg.mabr ? ' and MABR' : '')}`); + s.mabr = serv_cfg.mabr; + s.unload = serv_cfg.unload; + s.mabr_min_active = serv_cfg.activate; + s.purge_delay = 1000 * serv_cfg.timeshift; + s.mani_cache = serv_cfg.mcache; + s.repair = serv_cfg.repair; + s.corrupted = serv_cfg.corrupted; + s.local = serv_cfg.local; + s.keepalive = serv_cfg.keepalive; + s.gcache = serv_cfg.gcache; + s.check_ip = serv_cfg.check_ip; + if (serv_cfg.id) s.id = serv_cfg.id; + s.dyn_mabr = serv_cfg.dyn_mabr; + } + + if (!http_url) { + s.base_url = serv_cfg.local_base; + s.base_url_no_proto = serv_cfg.local_base; + } else { + //remove cgi and remove resource name + let str = http_url.split('?'); + str = str0.split('/') + str.pop(); + s.base_url = str.join('/') + '/'; + //remove protocol scheme + s.base_url_no_proto = s.base_url.split('://')1; + } + + s.get_file = function(url, br_start, br_end) + { + let file = null; + for (let i=0; i<this.mem_cache.length; i++) { + let f = this.mem_cachei; + if (f.aborted) continue; + if (f.url.indexOf(url)>=0) {} + //in case the whole server path was not sent in mabr + else if (f.cache_type && (url.indexOf(f.url)>=0)) {} + else continue; + //only deal with exact byte ranges - we could optimize to allow sub-ranges + if (f.br_start==br_start && f.br_end==br_end) return f; + } + return null; + }; + s.unqueue_pending_request = function(url, from_mabr) + { + let ext=null; + let pending = this.pending_reqs.find (r =>{ + if (url.indexOf(r.url)>=0) return true; + //in case the whole server path was not sent in mabr + if (from_mabr && (r.url.indexOf(url)>=0) ) return true; + if (r.from_cache && (r.url.indexOf(url)>=0) ) return true; + return false; + }); + //if no match and direct multicast, locate manifests by extension + if (!pending && from_mabr && !s.url) { + let ext = url.split('.').pop().toLowerCase(); + if ((ext==="mpd") || (ext==="m3u8")) { + pending = this.pending_reqs.find (r => { + if (r.url.indexOf(ext)>=0) return true; + return false; + }); + } + } + if (pending) + this.pending_reqs.splice(this.pending_reqs.indexOf(pending), 1); + return pending; + }; + + s.create_cache_file = function(url, mime, br_start, br_end, cache_type=CACHE_TYPE_MOD, pid=null) + { + let file = {}; + file.url = url; + file.mime = mime || 'video/mp4'; + file.received = sys.clock_ms(); + file.data = new ArrayBuffer(); + file.data_tmp = null; + file.data_tmp_done = false; + this.mem_cache.push(file); + file.done = false; + file.aborted = false; + file.xhr_status = 0; + file.pending_reqs = null; + file.headers = null; + file.nb_users = 0; + file.sticky = 0; + file.br_start = br_start; + file.br_end = br_end; + file.cache_type = cache_type; + file.range = null; + file.pid = pid; + file.manifest_min_update = 0; + if (cache_type) { + file.xhr_status = 200; + } + file.release = function(req=null) { + this.nb_users --; + if (!this.nb_users && this.data_tmp) { + this.data = this.data_tmp; + this.data_tmp = null; + this.done = this.data_tmp_done; + this.data_tmp_done = false; + } + //special case for files created for out of date cached manifests, revert cache type to MABR + if (req && !this.nb_users && req.service && req.manifest_type && req.service.mani_cache) { + this.cache_type = CACHE_TYPE_MABR; + this.xhr_status = 200; + } + }; + + //get any pending request(s) for this file + while (true) { + //we use req.url , eg server path, and not target_url which includes server name + let pending = this.unqueue_pending_request(url, (cache_type==CACHE_TYPE_MABR)); + if (!pending) break; + pending.waiting_mabr = 0; + pending.set_cache_file(file); + let ellapsed = pending.waiting_mabr_start ? (sys.clock_ms() - pending.waiting_mabr_start) : 0; + do_log(GF_LOG_DEBUG, `Found pending request (${url} ${pending.target_url}) for ${file.url}, canceling timeout (${ellapsed} ms since request)`); + if (pid) pid.deactivate_timeout=0; + } + do_log(GF_LOG_DEBUG, `Start ${(cache_type==CACHE_TYPE_MABR) ? 'MABR ' : ''}reception of ${file.url} mime ${file.mime} - ${this.mem_cache.length} files in service cache`); + return file; + }; + + s.mark_active_rep = function(rep, request) + { + if (!rep || !request) return; + let use_same_conn = false; + let connection_id = s.check_ip ? (request.ip + ':'+request.port) : request.netid; + + let idx = s.pending_deactivate.indexOf(rep); + if (idx>=0) s.pending_deactivate.splice(idx, 1); + + //mark number of active requests for this quality + if (!rep.nb_active) { + do_log(GF_LOG_INFO, `Service ${this.id} Rep ${rep.ID} is now active`); + if (!this.repair) + rep.first_mabr_tune = 1; + } else { + //check if we have active requests on the same connection to be deactivated. If so, extend timeout of last found + //and do not change nb_active + //this avoids activate/deactivate on same connection which would trigger spurious mcast joins/leaves + let same_conn = this.active_reps_timeouts.filter(e => e.rep.ID == rep.ID && e.cid == connection_id); + if (same_conn.length) { + let rep_to = same_connsame_conn.length-1; + rep_to.update(); + do_log(GF_LOG_DEBUG, `Service ${this.id} same connection used for same quality ${rep.ID}, extending inactive timeout`); + use_same_conn = true; + } + } + if (!use_same_conn) { + rep.nb_active++; + request.activate_rep_timeout = {"rep": rep, "timeout": 0, "cid": connection_id, "mabr_canceled": false}; + request.activate_rep_timeout.update = function() { + //timeout for active quality is segment duration + safety + this.timeout = sys.clock_ms() + this.rep.seg_dur + DEACTIVATE_TIMEOUT_MS; + }; + request.activate_rep_timeout.update(); + this.active_reps_timeouts.push( request.activate_rep_timeout ); + } + + //check if we need to activate multicast + if (this.mabr_loaded && !rep.mabr_active + && (!this.mabr_min_active || (rep.nb_active>=this.mabr_min_active)) + ) { + this.activate_mabr(rep, true); + } + }; + + s.activate_mabr = function(rep, do_activate) + { + if (!this.mabr_service_id || !this.source || !rep) return; + + if (serv_cfg && serv_cfg.js_mod && (serv_cfg.js_mod.quality_activation != null)) { + let req_ok = serv_cfg.js_mod.quality_activation(do_activate, this.mabr_service_id, rep.period_id, rep.AS_ID, rep.ID); + if (!req_ok) return; + } + + if (do_activate) { + this.nb_mabr_active++; + this.mabr_unload_timeout = 0; + do_log(GF_LOG_INFO, `Service ${this.id} MABR activated for Rep ${rep.ID} - active in service ${this.nb_mabr_active}`); + } else if (this.nb_mabr_active) { + this.nb_mabr_active--; + do_log(GF_LOG_INFO, `Service ${this.id} MABR deactivated for Rep ${rep.ID} - active in service ${this.nb_mabr_active}`); + if (!this.nb_mabr_active && this.unload) { + this.mabr_unload_timeout = sys.clock_ms() + 1000*this.unload; + } + //no deactivation of multicast channels + if (this.mabr_min_active==0) { + rep.mabr_active = true; + return; + } + } + + let evt = new FilterEvent(GF_FEVT_DASH_QUALITY_SELECT); + evt.service_id = this.mabr_service_id; + evt.period_id = rep.period_id; + evt.as_id = rep.AS_ID; + evt.rep_id = rep.ID; + evt.select = do_activate ? 0 : 1; + rep.mabr_active = do_activate; + session.fire_event(evt, this.source, false, true); + }; + + s.load_mabr = function() + { + if (!this.mabr || this.mabr_loaded) return; + //load routein + let args = 'src=' + this.mabr; + if (this.mabr.indexOf('gpac:')<0) args += ':gpac'; + //routein config: + // - no-cache mode to fetch files directly + // - single pid per TSI to have files for a given quality on a single PID + // - keep alive to disable timeout when multicast is down + // - require source ID in case we use a capture file, we could get the first PID before returning from the add_filter function + args += ':gcache=0:stsi:ka:RSID'; + // - multicast is dynamically enabled/disabled, start with all services tuned but disabled + // only do this if HTTP mirror - otherwise, activate everything to make sure we fetch the manifests and init segments + if (this.url && this.mabr_min_active>0) args += ':tunein=-3'; + //add repair option last + if (! this.url) { + if (!this.repair) { + args += ':repair=strict'; + this.repair = 1; + } else { + args += ':repair=full'; + } + } + else if (this.repair) { + //escape URL option + args += '::repair_urls='+this.url; + } else if (s.corrupted) { + args += ':repair=strict'; + } + + this.source = session.add_filter(args); + if (!this.source) { + do_log(GF_LOG_ERROR, `Service ${this.id} Failed to load MABR demux for ${this.mabr}`); + this.mabr_failure = true; + return; + } + this.source.require_source_id(); + this.mabr_loaded = true; + this.nb_mabr_active = 0; + this.mabr_unload_timeout = 0; + this.last_mabr_active = 0; + + //create custom sink accepting files + this.sink = session.new_filter("RouteSink"+this.id); + this.sink.set_cap({id: "StreamType", value: "File", in: true} ); + this.sink.max_pids=-1; + this.sink.pids=; + + this.sink.on_setup_error = function(srcf, err) { + do_log(GF_LOG_WARNING, `Service ${s.id} MABR setup error ${sys.error_string(err)}`); + s.mabr_failure = true; + if (s.sink) session.remove_filter(s.sink); + if (s.source) session.remove_filter(s.source); + s.sink = null; + s.source = null; + }; + this.sink.watch_setup_failure(this.source); + + this.sink.configure_pid = function(pid) + { + //reconfigure (new file), get previous file and mar as done + if (this.pids.indexOf(pid)>=0) { + //get previous file + if (pid.url) { + let file = s.mem_cache.find(f => f.url===pid.url); + if (file && !file.done) { + do_log(GF_LOG_INFO, `${pid.url} closing cache file as new file from MABR starts ${pid.get_prop('URL')}`); + file.done = true; + } + } + } else { + //initial declaration + this.pids.push(pid); + //send play + let evt = new FilterEvent(GF_FEVT_PLAY); + evt.start_range = 0.0; + pid.send_event(evt); + //no repair, we must dispatch full files + if (!s.repair) pid.framing = true; + //auto-deactivate unless unload is set + if (s.mabr_min_active) { + pid.deactivate_timeout = MABR_PID_DEACTIVATION_TIMEOUT + sys.clock_ms(); + } + else + pid.deactivate_timeout = 0; + } + + if (!s.mabr_service_id) { + s.mabr_service_id = pid.get_prop('ServiceID'); + do_log(GF_LOG_INFO, `MABR configured for service ${s.mabr_service_id}`); + } + s.last_mabr_active = sys.clock_ms(); + //get new URL for this pid - can be null when service is just being announced + pid.url = pid.get_prop('URL'); + if (pid.url && (pid.url.charAt(0) != '/')) pid.url = '/' + pid.url; + pid.mime = pid.get_prop('MIMEType'); + pid.corrupted = false; + let is_manifest=false; + if (pid.url) { + let purl = pid.url.toLowerCase(); + //do not cache HLS/DASH manifests + if ((purl.indexOf('.m3u8')>=0)|| (purl.indexOf('.mpd')>=0)) is_manifest = true; + else is_manifest = false; + } + pid.do_skip = false; + pid.is_manifest = false; + if (is_manifest) { + pid.is_manifest = true; + //we want full files for the manifest + pid.framing = true; + if ((s.url !== null) && !s.mani_cache) + pid.do_skip = true; + pid.deactivate_timeout = 0; + } + if (!s.url) { + let urls = pid.get_prop('MABRBaseURLs'); + s.mabr_repair_url = urls ? urls0 : null; + } + }; + + this.push_to_cache = function (pid, pck) { + let file = this.mem_cache.find(f => f.url===pid.url); + let corrupted = pck.corrupted ? 1 : 0; + if (corrupted && pck.get_prop('PartialRepair')) { + corrupted = s.corrupted ? 0 : 2; + } + //file corrupted and no repair, move to HTTP + if (!s.repair && corrupted) { + let log_done = false; + //cache file shall never be created at this point since we only aggregate full files when no repair + if (file) { + do_log(GF_LOG_ERROR, `Service ${this.id} receiving corrupted MABR packet and cache file was already setup, bug in code !`); + file.data.byteLength = 0; + } + + //get any pending request(s) for this file + while (true) { + let pending = this.unqueue_pending_request(pid.url, true); + if (!pending) break; + //signal mabr was canceled + pending.waiting_mabr = 0; + if (pending.activate_rep_timeout) { + pending.activate_rep_timeout.update(); + pending.activate_rep_timeout.mabr_canceled = true; + } + if (!log_done) { + do_log(GF_LOG_INFO, `Corrupted MABR packet for ${pid.url} (valid container ${corrupted==2}), switching to HTTP`); + log_done = true; + } + //the first one will create a cache file if possible, associated it to the other pending requests and potentially removing them + pending.fetch_unicast(); + } + if (!log_done) { + do_log(GF_LOG_INFO, `Corrupted MABR packet for ${pid.url} (valid container ${corrupted==2}), removing`); + } + return; + } else if (corrupted) { + do_log(GF_LOG_WARNING, `MABR Repair failed for ${pid.url} (valid container ${corrupted==2}), broken data sent to client`); + } + if (!file) { + file = this.create_cache_file(pid.url, pid.mime, 0, 0, CACHE_TYPE_MABR, pid); + } + //cache file exists, it was created from a pending request or an old request + else if (pck.start) { + //update to a file being sent, we need to create a temp buffer + //which will be reassigned once no more users are on the file + if (file.nb_users) { + file.data_tmp = new ArrayBuffer(); + file.data_tmp_done = false; + } else { + file.data = new ArrayBuffer(); + file.data_tmp = null; + file.data_tmp_done = false; + file.done = false; + } + } + + do_log(GF_LOG_DEBUG, `Service ${this.id} receiving MABR packet for ${pid.url} size ${pck.size} end ${pck.end}`); + + //reagregate packet + if (pck.size) { + if (file.data_tmp) + file.data_tmp = cat_buffer(file.data_tmp, pck.data); + else + file.data = cat_buffer(file.data, pck.data); + } + if (pck.start && pck.end) { + do_log(GF_LOG_INFO, `Service ${this.id} got MABR file ${pid.url} in one packet`); + } + else if (pck.start) { + do_log(GF_LOG_INFO, `Service ${this.id} start receiving MABR file ${pid.url}`); + } + else if (pck.end) { + do_log(GF_LOG_INFO, `Service ${this.id} received MABR file ${pid.url}`); + } + if (pck.end) { + if (file.data_tmp) { + if (!file.nb_users) { + file.data = file.data_tmp; + file.data_tmp = null; + file.data_tmp_done = false; + file.done = true; + } else { + file.data_tmp_done = true; + } + } else { + file.done = true; + } + file.received = sys.clock_ms(); + if (pid.is_manifest) { + update_manifest(file.data_tmp ? file.data_tmp : file.data, file.url, s, file); + } + } + }; + + this.sink.process = function(pid) + { + let has_pck=false; + this.pids.forEach(function(pid) { + while (1) { + let pck = pid.get_packet(); + if (!pck) break; + if (!pid.do_skip && !pid.corrupted) + s.push_to_cache(pid, pck); + pid.drop_packet(); + has_pck=true; + } + if (pid.deactivate_timeout && (pid.deactivate_timeout<sys.clock_ms())) { + print(GF_LOG_INFO, `PID ${pid.url} without active requests - deactivating multicast`); + let evt = new FilterEvent(GF_FEVT_DASH_QUALITY_SELECT); + evt.service_id = s.mabr_service_id; + evt.period_id = pid.get_prop('Period'); + evt.as_id = pid.get_prop('ASID'); + evt.rep_id = pid.get_prop('Representation'); + evt.select = 1; + session.fire_event(evt, s.source, false, true); + pid.deactivate_timeout = 0; + } + }); + if (has_pck) s.last_mabr_active = sys.clock_ms(); + }; + //don't use dash client, we just need to get the files + this.sink.set_source(this.source); + do_log(GF_LOG_INFO, `Service ${this.id} loaded MABR for ${this.mabr}${this.repair ? ' with repair' : ''}`); + + if (serv_cfg && serv_cfg.js_mod && typeof serv_cfg.js_mod.service_activation === 'function') { + serv_cfg.js_mod.service_activation(true); + } + }; + s.unload_mabr = function() { + if (this.source) { + do_log(GF_LOG_INFO, `Service ${this.id} stopping MABR`); + if (this.sink) session.remove_filter(this.sink); + if (this.source) session.remove_filter(this.source); + this.sink = null; + this.source = null; + this.mabr_loaded = false; + this.mabr_failure = false; + + if (serv_cfg && serv_cfg.js_mod && typeof serv_cfg.js_mod.service_activation === 'function') { + serv_cfg.js_mod.service_activation(false); + } + //cancel all pending requests on MABR files + this.check_pending_requests(sys.clock_ms(), true); + + //clear all cache files from the multicast + this.mem_cache.forEach(f => { + if (f.cache_type == CACHE_TYPE_MABR) { + f.done = true; + f.aborted = true; + } + }); + //abort all active reps + for (let i=0; i<this.active_reps_timeouts.length; i++) { + let o = this.active_reps_timeoutsi; + let active_rep = o.rep; + this.active_reps_timeouts.splice(i, 1); + i--; + active_rep.mabr_active=0; + } + this.mabr_service_id = 0; + } + }; + + s.check_dyn_mabr = function() { + let mabr_old = this.mabr; + this.mabr = serv_cfg.js_mod.get_mcast_address(serv_cfg.http); + if (this.mabr == mabr_old) return; + if (this.mabr_loaded) this.unload_mabr(); + if (!this.mabr) return; + this.load_mabr(); + }; + + s.check_pending_requests = function(now, force_deactivate) { + //check timeout on pending reqs waiting for MABR + for (let i=0; i<this.pending_reqs.length; i++) { + let req = this.pending_reqsi; + if (!force_deactivate) { + if (!req.waiting_mabr || (req.waiting_mabr>now)) continue; + } + if (! this.url && !this.mabr_repair_url) { + do_log(GF_LOG_DEBUG, `MABR timeout for ${req.url} after ${now - req.waiting_mabr_start} ms`); + this.pending_reqs.splice(i, 1); + i--; + req.reply = 404; + req.send(); + continue; + } + do_log(GF_LOG_DEBUG, `MABR timeout for ${req.url} after ${now - req.waiting_mabr_start} ms - fetching from HTTP at ` + req.target_url); + this.pending_reqs.splice(this.pending_reqs.indexOf(req), 1); + i--; + if (req.activate_rep_timeout) { + req.activate_rep_timeout.update(); + req.activate_rep_timeout.mabr_canceled = true; + if (req.cache_file) req.cache_file.cache_type = CACHE_TYPE_HTTP + } + req.waiting_mabr = 0; + //the first one will create a cache file if possible, associated it to the other pending requests and potentially removing them + let prev_len = this.pending_reqs.length; + req.fetch_unicast(); + //if some pending were removed, restart the loop + if (prev_len != this.pending_reqs.length) { + i = -1; + } + } + }; + + all_services.push(s); + do_log(GF_LOG_INFO, `Created Service ID ${s.id} for ${s.url ? s.url : serv_cfg.local_base}${(s.mabr || s.dyn_mabr) ? ' with MABR' : ''}`); + //and load if requested - we force mcast activation when an access to the mpd is first detected + if (force_mcast_activate && s.mabr) + s.load_mabr(); + + s.stats = { + "X-From-MABR": { + "yes": 0, + "no": 0, + "off-edge": 0 + } + } + s.update_mabr_stats = function(x_from_mabr){ + if(this.stats"X-From-MABR"x_from_mabr != undefined){ + this.stats"X-From-MABR"x_from_mabr++ + } else { + do_log(GF_LOG_INFO, `update_mabr_stats ${this.stats} `); + } + } + return s; +} +globalThis.all_services = all_services; +globalThis.create_service = create_service; + +let script_args=null; +function do_init() +{ + if (http_out_f == null) { + let rdir = (sys.file_exists('.gpac_auth')) ? '.gpac_auth' : "gmem"; + sys.args.forEach(a => { + if (a.indexOf('rdirs=')>=0) rdir = null; + }); + if (script_args && script_args.indexOf('rdirs=')>=0) rdir = null; + + let http_args = "httpout"; + if (rdir) http_args += ":rdirs="+rdir; + if (script_args) http_args += script_args; + + try { + let http = session.add_filter(http_args); + server_ports = http.get_arg("port"); + do_log(GF_LOG_INFO, `Starting HTTP server on port ${server_ports}`); + } catch (e) { + do_log(GF_LOG_ERROR, `Failed to start HTTP server`); + sys.exit(1); + } + } else { + server_ports = http_out_f.get_arg("port"); + do_log(GF_LOG_INFO, `Attached server running on port ${server_ports}`); + } + //preload services + services_defs.forEach(sd => { + if (sd.unload) return; + if (sd.dyn_mabr) return; + let s = create_service(sd.http, false, sd); + if (!s) { + do_log(GF_LOG_ERROR, `Failed to create service ${sd.http}`); + } else { + s.load_mabr(); + } + }); + + session.post_task( () => { + let do_gc = false; + let now = sys.clock_ms(); + for (let i=0; i<all_services.length; i++) { + let s = all_servicesi; + //cleanup mem cache + for (let i=0; i<s.mem_cache.length; i++) { + let f = s.mem_cachei; + if (f.nb_users || f.sticky) continue; + if (!f.aborted && (f.received + s.purge_delay >= now)) continue; + s.mem_cache.splice(i, 1); + i--; + do_log(GF_LOG_DEBUG, `Removing ${f.url} from cache - remain ${s.mem_cache.length}`); + } + + //check timeout on pending reqs waiting for MABR + s.check_pending_requests(now, false); + + //check reps that could be deactivated after a MABR timeout + for (let i=0; i<s.pending_deactivate.length; i++) { + let rep = s.pending_deactivatei; + if (rep.mabr_deactivate_timeout < now) continue; + s.pending_deactivate.splice(i, 1); + i--; + do_log(GF_LOG_INFO, `Service ${s.id} Rep ${rep.ID} no longer on multicast`); + s.activate_mabr(rep, false); + } + + //check timeout on active reps + for (let i=0; i<s.active_reps_timeouts.length; i++) { + let o = s.active_reps_timeoutsi; + if (o.timeout > now) break; + let active_rep = o.rep; + let mabr_canceled = o.mabr_canceled; + s.active_reps_timeouts.splice(i, 1); + i--; + active_rep.nb_active--; + if (!active_rep.nb_active) { + do_log(GF_LOG_INFO, `Service ${s.id} Rep ${active_rep.ID} is now inactive`); + if (s.keepalive) + s.unload_timeout = sys.clock_ms() + 1000*s.keepalive; + } else { + do_log(GF_LOG_DEBUG, `Service ${s.id} Rep ${active_rep.ID} is still active (active clients ${active_rep.nb_active})`); + } + if (active_rep.mabr_active && s.mabr_min_active && (active_rep.nb_active<s.mabr_min_active) ) { + if (!mabr_canceled) { + s.activate_mabr(active_rep, false); + } else { + active_rep.mabr_deactivate_timeout = sys.clock_ms() + 2000; + s.pending_deactivate.push(active_rep); + } + } + } + //cleanup XHRs + for (let i=0; i<s.xhr_cache.length; i++) { + let xhr = s.xhr_cachei; + if (xhr.last_used + 10000 >= now) continue; + s.xhr_cache.splice(i, 1); + i--; + do_gc = true; + } + //check timeout on MABR service deactivation + if (s.mabr_unload_timeout && (s.mabr_unload_timeout < now)) { + s.mabr_unload_timeout = 0; + if (!s.nb_mabr_active) { + s.unload_mabr(); + do_gc = true; + } + } + //check service unload + if (s.unload_timeout && (s.unload_timeout < now)) { + s.unload_mabr(); + do_log(GF_LOG_INFO, `Service ${s.id} unloading`); + all_services.splice(i, 1); + if (typeof s.on_close == 'function') s.on_close(); + if (!all_services.length && do_quit) { + print("No more services and quit requested - exiting"); + session.remove_filter(http_out_f); + return false; + } + i--; + do_gc = true; + } + } + if (force_quit) { + print("No more requests and quit requested - exiting"); + session.remove_filter(http_out_f); + return false; + } + + if (do_gc) sys.gc(); + if (session.last_task) return false; + //perform cleanup every 100ms + return 100; + + }, "mediaserver_cleanup"); +} + +let http_out_f = null; +//catch filter creation +session.set_new_filter_fun( (f) => { + //bind our custom http logic + if (!http_out_f && (f.name == "httpout")) { + http_out_f = f; + f.bind(httpout); + } +}); + +let mods_pending=0; + +function get_base_url(url) +{ + let str = url.split('/'); + str.pop(); + return str.join('/') + '/'; +} + +filter.initialize = function() { + + let gpac_help = sys.get_opt("temp", "gpac-help"); + let gpac_doc = (sys.get_opt("temp", "gendoc") == "yes") ? true : false; + if (gpac_help || gpac_doc) { + filter._help = filter_help; + filter.set_help(filter._help); + + let scripts = sys.enum_directory(filter.jspath, "*.js").sort((a, b) => a.name.localeCompare(b.name)); + scripts.forEach(s => { + if (s.name == "init.js") return; + import(s.name).then(obj => { + + filter._help += '## ' + obj.description + '\nModule is loaded when using `js='+s.name.replace('.js', '')+'`\n\n' + obj.help + '\n'; + filter.set_help(filter._help); + }).catch(err => {}); + }); + return GF_OK; + } + + do_quit = filter.quit; + script_args = filter.src_args; + if (script_args) { + let local_args = "quit", "services"; + script_args = script_args.split(':'); + local_args.forEach(a => { + let e = script_args.find(e => e.startsWith(a)); + if (e) script_args.splice(script_args.indexOf(e), 1); + }); + script_args = script_args.join(':'); + } else { + script_args=""; + } + if (!filter.scfg || !filter.scfg.length) { + //todo: we should allow to specify a default service description file in ~/.gpac + do_log(GF_LOG_WARNING, "No service description provided, configuring as proxy for any URL"); + services_defs = {'http': '*', 'unload': true} ; + } else { + try { + let ab = sys.load_file(filter.scfg, true); + services_defs = JSON.parse(ab); + if (!Array.isArray(services_defs)) throw "Invalid JSON, expecting array"; + + services_defs = services_defs.filter(e => ((typeof e.active === 'undefined') || e.active) && typeof e.comment === 'undefined' ); + services_defs.forEach(sd => { + //check options are valid + if (typeof sd.active == 'undefined') sd.active = true; + else if (typeof sd.active != 'boolean') sd.active = false; + if (typeof sd.http == 'undefined') sd.http = null; + else if (typeof sd.http != 'string') throw "Missing or invalid http property, expecting string got "+typeof sd.http; + if (typeof sd.mabr == 'undefined') sd.mabr = null; + else if (typeof sd.mabr != 'string') throw "Missing or invalid mabr property, expecting string got "+typeof sd.mabr; + if (typeof sd.local == 'undefined') sd.local = null; + else if (typeof sd.local != 'string') throw "Missing or invalid local property, expecting string got "+typeof sd.local; + if (typeof sd.unload != 'number') sd.unload = DEFAULT_MABR_UNLOAD_SEC; + else if (sd.unload < 0) throw "Invalid unload property, expecting positive number"; + if (typeof sd.activate != 'number') sd.activate = DEFAULT_ACTIVATE_CLIENTS; + else if (sd.activate < 0) throw "Invalid activate property, expecting positive number"; + if (typeof sd.timeshift != 'number') sd.timeshift = DEFAULT_TIMESHIFT; + else if (sd.timeshift < 0) throw "Invalid timeshift property, expecting positive number"; + if (typeof sd.mcache != 'boolean') sd.mcache = DEFAULT_MCACHE; + + if (typeof sd.repair != 'boolean') { + sd.repair = sd.repair ? 1 : 0; + } else { + if (typeof sd.repair != 'string') sd.repair = DEFAULT_REPAIR; + if (sd.repair === 'true') sd.repair = 1; + else if (sd.repair === 'auto') sd.repair = 2; + else sd.repair = 0; + } + + if (typeof sd.corrupted != 'boolean') sd.corrupted = DEFAULT_CORRUPTED; + if (typeof sd.gcache != 'boolean') sd.gcache = DEFAULT_GCACHE; + if (typeof sd.keepalive != 'number') sd.keepalive = DEFAULT_KEEPALIVE_SEC; + else if (sd.keepalive < 0) throw "Invalid keepalive property, expecting positive number"; + if (typeof sd.check_ip != 'boolean') sd.check_ip = DEFAULT_CHECKIP; + if (typeof sd.id == 'undefined') sd.id = null; + if (typeof sd.sources == 'undefined') sd.sources = ; + else if (!Array.isArray(sd.sources)) { + throw "Invalid sources property, expecting array"; + } else { + sd.sources.forEach(a => { + if (typeof a.name != 'string') throw "Invalid name in source element, expecting string"; + if (typeof a.url != 'string') throw "Invalid url in source element, expecting string"; + }); + } + if (typeof sd.js == 'undefined') sd.js = null; + else if (typeof sd.js != 'string') throw "Missing or invalid js property, expecting string got "+typeof sd.local; + if (typeof sd.noproxy == 'undefined') sd.noproxy = (sd.http && sd.local) ? true : false; + else if (typeof sd.noproxy != 'boolean') throw "Missing or invalid noproxy property, expecting boolean got "+typeof sd.noproxy; + + if (!sd.http && !sd.local) throw "At least one of `http` or `local` shall be specified"; + + //safety checks + if (!sd.unload) sd.keepalive = 0; + if (sd.unload>sd.keepalive) sd.keepalive = sd.unload; + + //extract proto, host and path + if (sd.http) { + //remove default ports + let url_lwr = sd.http.toLowerCase(); + if (url_lwr.startsWith('https://')) { + sd.http = sd.http.replace(":443/", "/"); + } + if (url_lwr.startsWith('http://')) { + sd.http = sd.http.replace(":80/", "/"); + } + let idx = sd.http.indexOf('://'); + sd.http_proto = sd.http.substring(0, idx); + let str = sd.http.substring(idx+3).split('/'); + sd.http_host = str0; + str.shift(); + str.pop(); + if(str.length) { + sd.http_path = '/'+str.join('/') + '/'; + } else { + sd.http_path = '/'; + } + } else { + sd.http_proto = null; + sd.http_host = null; + sd.http_path = null; + } + //if local is a file, get base path + sd.local_base = null; + if (sd.local) { + sd.local = sd.local.split('?')0; + if (!sd.local.length) sd.local = null; + else if (sd.local.charAt(sd.local.length-1) != '/') { + let str = sd.local.split('/') + str.pop(); + sd.local_base = str.join('/') + '/'; + } else { + sd.local_base = sd.local; + sd.local = null; + } + } + //load JS module if present + sd.dyn_mabr = false; + }); + //remove all inactive services + services_defs = services_defs.filter(e => e.active ); + //check for duplicates + services_defs.forEach(sd => { + if (!sd.active) return; + let same_orig = services_defs.filter( e => (e.active && e.http === sd.http)); + if (same_orig.length > 1) { + throw `Multiple active services with same origin ${sd.http} not allowed`; + } + }); + + //check for JS modules + services_defs.forEach(sd => { + if (sd.js) { + mods_pending++; + let script_src = sys.url_cat(filter.scfg, sd.js); + //local path + if (sys.file_exists(script_src)) {} + else if (sys.file_exists(sd.js+'.js')) { + script_src = sd.js+'.js'; + } + //path in our filter directory + else { + script_src = filter.jspath + sd.js + '.js'; + if (!sys.file_exists(script_src)) throw "File " + sd.js + " does not exist"; + } + import(script_src).then(obj_mod => { + if (typeof obj_mod.init !== 'function') throw "Invalid module, missing `init` function"; + if (typeof obj_mod.get_mcast_address === 'function') { + sd.dyn_mabr = true; + sd.mabr = null; + } + if (sd.mabr || sd.dyn_mabr) { + if (typeof obj_mod.quality_activation !== 'function') obj_mod.quality_activation = null; + } else { + if (typeof obj_mod.resolve !== 'function') throw "Invalid module, missing `resolve` function"; + } + mods_pending--; + sd.logname = sd.js; + sd.log = function(level, msg) { + do_log(level, msg, this.logname); + }; + obj_mod.init(sd); + sd.js_mod = obj_mod; + }).catch(e => { + do_log(GF_LOG_ERROR, `Failed to load service script ${sd.js}: ${e}`); + sys.exit(1); + }); + } else { + sd.js_mod = null; + } + }); + } catch (e) { + do_log(GF_LOG_ERROR, `Failed to load services configuration ${filter.scfg}: ${e}`); + sys.exit(1); + } + } + + //after this, filter object is no longer available in JS since we don't set caps or setup filter.process + session.post_task( () => { + if (mods_pending) return 100; + do_init(); + return false; + }, "init", 200); + +};
View file
gpac-26.02.0.tar.gz/share/scripts/jsf/mediaserver/remux.js
Added
@@ -0,0 +1,307 @@ +export const description = "Source Remultiplexer"; + +const DEF_FORMAT = 'mp4'; +const DEF_FILTERING = 'av'; + +export const help = `This module remultiplexes the source files in a desired format without transcoding. + +__Service configuration parameters used :__ \`local\` (mandatory), \`sources\` (mandatory). + +__Service configuration additional parameters__ +- fmt: default format to use (default is ${DEF_FORMAT}). Supported formats are: + - \`src\`: no remultiplexing + - \`mp4\`: fragmented MP4 + - \`ts\`: MPEG-2 TS + - \`gsf\`: GPAC streaming format + - \`dash\`: MPEG-DASH format, single quality + - \`hls\`: HLS format, single quality + +Sources are described using the \`sources\` array in the service configuration. + +CGI parameters for request are: +- fmt: (string, same as service configuration \`fmt\`) multiplexing format. If set to \`src\`, all other CGI parameters are ignored. +- start: (number, default 0) start time in second of re-multiplexed content. +- speed: (number, default 1) speed (>=0), keep video stream only and remove non SAP frames. +- media: (string, default \`${DEF_FILTERING}\`) media filtering type + - 'av': keep both audio and video + - 'a': keep only audio + - 'v': keep only video + + +Configuration for serving a directory with remultiplexing to mp4: +EX {"local": "/service1/", "js": "remux", "sources": {"name": "vids", "url": "/path/to/vids/"}, "fmt": "mp4"} +`; + +let config = null; +export function init(service_config) +{ + config = service_config; + //validate configuration + if (!config.local_base) { + config.log(GF_LOG_ERROR, 'Missing `local`mount point in service configuration'); + return false; + } + if (!config.sources.length) { + config.log(GF_LOG_ERROR, 'No sources defined Missing `local` mount point in service configuration'); + return false; + } +}; + + +export function resolve(req) { + try { + + let orig_url = req.url; + let cgi = req.url.split('?'); + let req_url = cgi0; + let multi_pid=false; + let start = 0; + let speed = 1; + let media = DEF_FILTERING; + let fmt=DEF_FORMAT; + if (typeof config.fmt == 'string') + fmt = config.fmt; + + if (cgi.length>1) { + cgi = cgi1.split('&'); + cgi.forEach(a => { + a = a.split('='); + if ((a0 == 'fmt') && (a.length>=1)) fmt = a1; + if ((a0 == 'start') && (a.length>=1)) start = parseFloat(a1); + if ((a0 == 'speed') && (a.length>=1)) speed = parseFloat(a1); + if ((a0 == 'media') && (a.length>=1)) media = a1; + }); + } + + let loc_url = null; + let loc_name = null; + //check if we have loaded a service for this URL, for DASH/HLS - todo we need to handle multi-user access and seek requests ! + req.service = all_services.find( e => e.local == config.local); + if (req.service) { + req.from_cache = true; + if (fmt =='dash') req_url += "_live.mpd"; + else if (fmt =='hls') req_url += "_live.m3u8"; + return req_url, false; + } + let my_url = req_url.replace(config.local_base, ''); + let e = config.sources.find( a => (a.name == my_url) ); + if (!e) e = config.sources.find( a => my_url.startsWith(a.name) ); + if (!e) { + config.log(GF_LOG_WARNING, 'Invalid source URL ' + req.url + ' for ' + config.sources.length); + return null, false; + } + loc_url = e.url; + loc_name = e.name; + if (loc_url.indexOf('://') < 0) { + loc_url = my_url.replace(e.name, e.url); + if (!sys.file_exists(loc_url)) { + config.log(GF_LOG_WARNING, 'URL not found ' + loc_url); + return null, false; + } + } + + if (fmt=='src') { + req.target_url = loc_url; + return loc_url, false; + } + + loc_name = sys.basename(loc_name); + + let link_args=''; + if (speed>2) link_args = 'video'; + else if (speed<=0) link_args = 'video'; + else if (media=='a') link_args = 'audio'; + else if (media=='v') link_args = 'video'; + let mux_args=null; + let src_opt=''; + let def_mime=null; + let segdur=4; + let cdur=1; + let asto=4; + let stl=''; + //stl=':stl'; + + fmt=fmt.toLowerCase(); + if ((fmt=='mp4')||(fmt=='fmp4')) { + mux_args = 'mp4mx:frag:cdur=1'; + def_mime = 'video/mp4'; + } + else if ((fmt=='m2ts')||(fmt=='ts')) { + mux_args = 'm2tsmx:nb_pack=20'; + def_mime = 'video/mp2t'; + } + else if (fmt=='gsf') { + mux_args = 'gsfmx'; + def_mime = 'application/x-gpac-sf'; + } + else if (fmt=='dash') { + //we use segment timeline, should always be supported and we have no clue about the SAP frequency in source + mux_args = `dasher:segdur=${segdur}:cdur=${cdur}:asto=${asto}:dynamic${stl}:sreg:tsalign=0:mname=${loc_name}_live.mpd`; + def_mime = 'application/dash+xml'; + multi_pid=true; + } + else if (fmt=='hls') { + mux_args = `dasher:segdur=${segdur}:cdur=${cdur}:asto=${asto}:dynamic:sreg:tsalign=0:mname=${loc_name}_live.m3u8`; + def_mime = 'application/dash+xml'; + multi_pid=true; + } + else { + req.reply = 401; + req.send('Invalid request format, please use one of mp4, ts or gsf'); + return null, false; + } + + //gather our state + let s_state = {}; + let args = 'src='+loc_url; + if (multi_pid) { + //override both URL and sourcepath properties + args += ':gpac:#FileAlias=' + loc_name; + } + s_state.src = session.add_filter(args); + s_state.src.require_source_id(); + let prev_f = s_state.src; + + if ((speed>2) || (speed<=0)) { + s_state.reframer = session.add_filter('reframer:saps=1,2,3', s_state.src, link_args); + s_state.reframer.require_source_id(); + prev_f = s_state.reframer; + } + //todo - add transcoding here + + s_state.mux = session.add_filter(mux_args, prev_f, link_args); + s_state.mux.require_source_id(); + + s_state.sink = session.new_filter("TransmuxSink"); + s_state.sink.set_cap({id: "StreamType", value: "File", in: true} ); + s_state.sink.set_source(s_state.mux, link_args); + if (multi_pid) s_state.sink.max_pids = -1; + + s_state.sink.pids = ; + s_state.sink.configure_pid = function(pid) { + if (this.pids.indexOf(pid)<0) { + this.pids.push(pid); + //send play + let evt = new FilterEvent(GF_FEVT_PLAY); + evt.start_range = start; + evt.timestamp_based = 3; + pid.send_event(evt); + + if (!multi_pid) { + let mime = pid.get_prop('MIMEType'); + if (!mime) mime = 'video/mp4'; + req.headers_out.push( { "name" : 'Content-Type', "value": mime} ); + req.headers_out.push( { "name" : 'Transfer-Encoding', "value": 'chunked'} ); + req.headers_out.push( { "name" : 'Cache-Control', "value": 'no-cache'} ); + req.reply = 200; + req.send(); + } + } + let init_name = pid.get_prop('InitName'); + if (init_name) { + pid.url = init_name; + } else { + pid.url = pid.get_prop('URL'); + } + pid.is_manifest = false; + if (pid.get_prop('IsManifest')) pid.is_manifest = true; + }; + + if (multi_pid) { + //create service for this session + req.service = create_service(null, false, config); + + s_state.sink.process = function() { + this.pids.forEach(function(pid) { + let s = req.service; + while (1) { + let pck = pid.get_packet(); + if (!pck) break; + + let file = s.mem_cache.find(f => f.url===pid.url); + if (pck.start) { + let url = pck.get_prop('FileName'); + if (url) pid.url = url; + if (file && !file.done) { + config.log(GF_LOG_DEBUG, `closing cache file ${file.url}`); + file.done = true; + } + file = s.mem_cache.find(f => f.url===pid.url); + } + + if (!file) { + file = s.create_cache_file(pid.url, pid.mime, 0, 0, 1); + if (pid.is_manifest) file.sticky = true; + } else if (pck.start) { + file.received = sys.clock_ms(); + file.data = new ArrayBuffer(); + file.done = false; + file.aborted = false; + } + if (pck.size) + file.data = cat_buffer(file.data, pck.data); + + if (pck.end) { + file.done = true; + } + pid.drop_packet(); + } + }); + }; + req.from_cache = true; + req.target_url = req_url+'_live.' + (fmt=='hls' ? 'm3u8' : 'mpd'); + req.url = req.target_url; + req.service.on_close = function() { + config.log(GF_LOG_DEBUG, `Removing transmux filter chain file for ${orig_url}`); + session.remove_filter(s_state.sink, s_state.src); + }; + if (req.service.keepalive) req.service.keepalive = 10; + return req.target_url, false; + } + + s_state.waiting = false; + s_state.pck_offset = 0; + s_state.sink.process = function() + { + //This is a sink so never blocking, we will get called as fast as possible (whenever input is here) + //we reschedule so that we always sleep unless a packet is needed + //we don't need to be called very often + if (!s_state.waiting) this.reschedule(20000); + }; + //custom read method, fetch directly from sink as we don't provide any caching for the result + s_state.read = function(ab) { + this.waiting = false; + let pid = this.sink.pids0; + let pck = pid.get_packet(); + if (!pck) { + if (pid.eos && !pid.is_flush) return 0; + this.waiting = true; + return -1; + } + let src_ab = pck.data; + let to_read = src_ab.byteLength - this.pck_offset; + if (to_read > ab.byteLength) to_read = ab.byteLength; + new Uint8Array(ab, 0, to_read).set(new Uint8Array(src_ab, this.pck_offset, to_read) ); + this.pck_offset += to_read; + if (this.pck_offset === src_ab.byteLength) { + pid.drop_packet(); + this.pck_offset = 0; + } + return to_read; + }; + s_state.on_close = function() { + config.log(GF_LOG_DEBUG, `Removing transmux filter chain for ${orig_url}`); + session.remove_filter(this.sink, this.src); + this.src = null; + this.sink = null; + this.mux = null; + }; + return s_state, false; + + } catch (e) { + config.log(GF_LOG_ERROR, `Failed to setup filter chain for ${orig_url}`); + req.reply = 404; + req.send('Failed to setup filter chain: ' + e); + return null, false; + } +}
View file
gpac-2.4.0.tar.gz/share/scripts/jsf/thumbs/init.js -> gpac-26.02.0.tar.gz/share/scripts/jsf/thumbs/init.js
Changed
@@ -1,10 +1,36 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2020-2024 + * All rights reserved + * + * This file is part of GPAC / AVGenerator filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + import * as evg from 'evg' import { Sys as sys } from 'gpaccore' import { File as File } from 'gpaccore' //metadata filter.set_name("thumbs"); -filter.set_desc("Thumbnail collection generator"); +filter.set_class_hint(GF_FS_CLASS_AV); +filter.set_desc("Thumbnail generator"); filter.set_version("1.0"); filter.set_author("GPAC team"); filter.set_help(`This filter generates screenshots from a video stream.
View file
gpac-26.02.0.tar.gz/share/scripts/jsf/txtgen
Added
+(directory)
View file
gpac-26.02.0.tar.gz/share/scripts/jsf/txtgen/init.js
Added
@@ -0,0 +1,389 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Deniz Ugur + * Copyright (c) Motion Spell 2025 + * All rights reserved + * + * This file is part of GPAC / Text Generator filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +import { Sys as sys } from "gpaccore"; +import { File as File } from "gpaccore"; + +filter.pids = ; + +filter.set_name("txtgen"); +filter.set_class_hint(GF_FS_CLASS_MM_IO); +filter.set_desc("Text Generator"); +filter.set_version("1.0"); +filter.set_author("GPAC Team"); +filter.set_help( + "This filter generates text streams based on the provided -src() file. By default, the filter uses a lorem ipsum text file\n" + + "The -type() parameter sets the text generation mode. If set to 'txt', the filter will generate text based on the source file\n" + + "If set to 'utc', the filter will generate text based on the current UTC time. If set to 'ntp', the filter will generate text based on the current NTP time\n" + + "When the -unit() is set to 'w', the filter will generate text based on words. When set to 'l', the filter will generate text based on lines\n" + + "The -fdur() parameter sets the frame duration of the text stream. " + + "Total duration of the text stream is set by the -dur() parameter. If set to 0/0, the text stream will be infinite\n" + + "The -rollup() parameter enables roll-up mode up to the specified number of lines. In roll-up mode, the filter will accumulate text until the specified number of lines is reached.\n" + + "When the number of lines is reached, the filter will remove the first line and continue accumulating text\n" + + "You would use -rollup() in combination with -unit() set to 'l' to create a roll-up subtitle effect. Or set -unit() to 'w' to create a roll-up text effect.\n" + + "The -lmax() parameter sets the maximum number of characters in a line. If the line in the source file is longer than this, the excess text will be wrapped. 0 means no limit\n" + + "When -rt() is set to true, the filter will generate text in real-time. If set to false, the filter will generate text as fast as possible" +); + +filter.set_arg({ + name: "src", + desc: "source of text. If not set, the filter will use lorem ipsum text", + type: GF_PROP_STRING, + def: filter.jspath + "lipsum.txt", +}); +filter.set_arg({ + name: "type", + desc: "type of text to generate\n- txt: plain text (uses src option)\n- utc: UTC time\n- ntp: NTP time", + type: GF_PROP_UINT, + def: "txt", + minmax_enum: "txt|utc|ntp", +}); +filter.set_arg({ + name: "unit", + desc: "minimum unit of text from the source\n- w: word\n- l: line", + type: GF_PROP_UINT, + def: "l", + minmax_enum: "w|l", +}); +filter.set_arg({ + name: "fdur", + desc: "duration of each frame", + type: GF_PROP_FRACTION, + def: "1/1", +}); +filter.set_arg({ + name: "lmax", + desc: "maximum number of characters in a line. If the line in the source file is longer than this, the excess text will be wrapped. 0 means no limit", + type: GF_PROP_UINT, + def: 32, +}); +filter.set_arg({ + name: "dur", + desc: "duration of the text stream", + type: GF_PROP_FRACTION, + def: "0/0", +}); +filter.set_arg({ + name: "rollup", + desc: "enable roll-up mode up to the specified number of lines", + type: GF_PROP_UINT, + def: 0, +}); +filter.set_arg({ + name: "lock", + desc: "lock timing to text generation", + type: GF_PROP_BOOL, + def: "false", +}); +filter.set_arg({ + name: "rt", + desc: "real-time mode", + type: GF_PROP_BOOL, + def: "true", +}); + +let text_cts = 0; +let text_pid = null; + +let text = ; +let text_idx = 0; +let text_playing = false; +let dyn_rp_text = ""; + +let start_date = 0; +let utc_init = 0; +let ntp_init = 0; +let last_utc = 0; + +filter.frame_pending = 0; + +filter.initialize = function () { + this.set_cap({ id: "StreamType", value: "Text", output: true }); + this.set_cap({ id: "CodecID", value: "txt", output: true }); + + let gpac_help = sys.get_opt("temp", "gpac-help"); + let gpac_doc = sys.get_opt("temp", "gendoc") == "yes" ? true : false; + if (gpac_help || gpac_doc) return; + + if (filter.fdur.n <= 0) { + print(GF_LOG_ERROR, "Unit duration cannot be 0 or negative"); + return GF_BAD_PARAM; + } else if (filter.dur.n < 0) { + print(GF_LOG_ERROR, "Stream duration cannot be negative"); + return GF_BAD_PARAM; + } + + //setup text + text_pid = this.new_pid(); + text_pid.set_prop("StreamType", "Text"); + text_pid.set_prop("CodecID", "txt"); + text_pid.set_prop("Unframed", true); + text_pid.set_prop("Cached", true); + text_pid.set_prop("Timescale", 1000); + text_pid.name = "text"; + text_pid.set_prop("ID", 1); + text_pid.set_prop("Bitrate", 1); + + //using UTC or NTP + if (filter.type != 0) { + if (filter.unit == 1) { + print( + GF_LOG_DEBUG, + "UTC/NTP mode does not support line unit, switching to word unit" + ); + filter.unit = 0; //since "line" is the default, don't warn + } + return; + } + + //load lipsum text + let file = new File(filter.src, "r"); + let size = file.size; + while (!file.eof) { + let line = file.gets(); + if (line) { + if (line.trim().length == 0) { + if (filter.unit == 1) text.push(null); + continue; + } + + if (filter.unit == 0) { + let words = line.trim().split(" "); + for (let i = 0; i < words.length; i++) { + text.push(wordsi); + } + } else if (filter.unit == 1) { + text.push(line.trim()); + } + } + } + text.push(null); + file.close(); + + if (filter.lmax > 0) { + let newText = ; + let prevWarped = false; + for (let i = 0; i < text.length; i++) { + let cur = texti; + if (cur) { + let words = cur.split(" "); + let line = ""; + if (prevWarped && newText.length) { + if (newTextnewText.length - 1 != null) { + line = newText.pop(); + } + } + for (let j = 0; j < words.length; j++) { + if (line.length + wordsj.length > filter.lmax) { + if (!line.length) { + print( + GF_LOG_WARNING, + "Encountered a word longer than lmax (" + + wordsj.length + + " > " + + filter.lmax + + "), adjusting lmax" + ); + filter.lmax = wordsj.length + 1; + } else { + newText.push(line); + line = ""; + prevWarped = true; + } + } + if (line.length) line += " "; + line += wordsj; + } + newText.push(line); + } else { + newText.push(null); + } + } + text = newText; + } + + //rollup algorithm + if (filter.rollup > 0) { + let newText = ; + let accumulatedText = ""; + let lineCount = 0; + for (let i = 0; i < text.length; i++) { + let cur = texti; + + if (cur) { + if (filter.unit == 0) { + if (accumulatedText.length > 0) accumulatedText += " "; + else lineCount++; + } + let lastLine = accumulatedText.lastIndexOf("\n") + 1; + let lastLineLength = accumulatedText.length - lastLine; + if (lastLineLength + cur.length > filter.lmax) { + lineCount++; + accumulatedText += "\n"; + } + accumulatedText += cur; + } + if (cur == null || filter.unit == 1) { + lineCount++; + accumulatedText += "\n"; + } + + while (lineCount > filter.rollup) { + //remove first line + let idx = accumulatedText.indexOf("\n"); + accumulatedText = accumulatedText.substring(idx + 1); + lineCount--; + } + + //clear leading newlines + while (accumulatedText.startsWith("\n")) { + accumulatedText = accumulatedText.substring(1); + lineCount--; + } + + //clear trailing newlines + while (accumulatedText.endsWith("\n\n")) { + accumulatedText = accumulatedText.substring( + 0, + accumulatedText.length - 1 + ); + lineCount--; + } + + if (text.length - 1 != i) { + newText.push(accumulatedText.trim()); + } + } + newText.push(null); + text = newText; + } + + //subtitle at every N ms + let bitrate = Math.floor( + ((size / text.length) * 8) / (filter.fdur.n / filter.fdur.d) + ); + text_pid.set_prop("Bitrate", bitrate); +}; + +filter.process_event = function (pid, evt) { + if (evt.type == GF_FEVT_STOP) { + if (pid === text_pid) text_playing = false; + } else if (evt.type == GF_FEVT_PLAY) { + if (pid === text_pid) text_playing = true; + filter.reschedule(); + } +}; + +function getTimes() { + let date = new Date(); + let utc, ntp; + if (filter.lock) { + let elapsed_us = 1000 * text_cts; + let elapsed_ms = Math.floor(elapsed_us / 1000); + date.setTime(start_date + elapsed_ms); + utc = utc_init + elapsed_ms; + ntp = sys.ntp_shift(ntp_init, elapsed_us); + } else { + utc = sys.get_utc(); + ntp = sys.get_ntp(); + } + return utc, ntp; +} + +filter.process = function () { + if (!text_playing) return GF_EOS; + if (!text_pid || text_pid.would_block) return GF_OK; + + //init times + if (!start_date) { + start_date = new Date().getTime(); + utc_init = sys.get_utc(); + ntp_init = sys.get_ntp(); + } + + //regulate text generation + let interval_ms = Math.floor((1000 * filter.fdur.n) / filter.fdur.d); + if (filter.rt) { + if (last_utc && sys.get_utc() - last_utc < interval_ms) return GF_OK; + last_utc = sys.get_utc(); + filter.reschedule(interval_ms); + } + + //decide on unit + let unit; + if (filter.type == 0) { + unit = texttext_idx++ % text.length; + } else { + let times = getTimes(); + if (filter.type == 1) { + unit = new Date(times0).toUTCString(); + } else { + unit = times1.n + "." + times1.d.toString(16); + } + } + + //handle gaps + if (filter.rollup && filter.type > 0) { + let lineCount = dyn_rp_text.split("\n").length; + if (lineCount >= filter.rollup) { + dyn_rp_text = dyn_rp_text.substring(dyn_rp_text.indexOf("\n") + 1); + } + if (dyn_rp_text.length) dyn_rp_text += "\n"; + dyn_rp_text += unit; + } else if (unit == null) { + text_cts += interval_ms; + return GF_OK; + } + + //prepare packet + let text_to_send = dyn_rp_text.length ? dyn_rp_text : unit; + let pck = text_pid.new_packet(text_to_send.length); + let farray = new Uint8Array(pck.data); + for (let i = 0; i < text_to_send.length; i++) { + farrayi = text_to_send.charCodeAt(i); + } + let dur = interval_ms; + pck.cts = text_cts; + pck.dur = dur; + pck.sap = GF_FILTER_SAP_1; + + //update text cts and reset acc + text_cts += dur; + + //send packet + pck.send(); + + //check if we should stop + if ( + filter.dur.d && + text_cts >= Math.floor((1000 * filter.dur.n) / filter.dur.d) + ) { + print("done playing, cts " + text_cts); + text_playing = false; + text_pid.eos = true; + } + + return GF_OK; +};
View file
gpac-26.02.0.tar.gz/share/scripts/jsf/txtgen/lipsum.txt
Added
@@ -0,0 +1,21 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Mauris nec nibh vehicula, suscipit nulla id, euismod eros. +Pellentesque sollicitudin arcu nec erat venenatis egestas. +Sed ullamcorper tortor et venenatis vestibulum. +Suspendisse ac metus egestas, consectetur mauris id, placerat velit. + +Nam in quam accumsan, elementum purus sit amet, sagittis diam. +Nunc aliquam quam at erat placerat, non dapibus nisi faucibus. +Aliquam eget orci eget purus ullamcorper dictum. + +Nunc pulvinar lacus elementum laoreet porta. +In sed mi ac est ultricies feugiat id nec nulla. + +Curabitur lobortis metus non nibh placerat pretium. +Cras iaculis nisl sit amet urna ullamcorper, id tempor diam fringilla. + +Donec bibendum est et iaculis porta. +Nulla egestas ex sit amet felis malesuada, aliquam rutrum magna dignissim. +Aliquam nec tellus volutpat, efficitur orci id, facilisis sem. +Donec at nisi ut velit semper ultrices. +Etiam a magna dignissim, commodo dolor ac, ullamcorper eros. \ No newline at end of file
View file
gpac-2.4.0.tar.gz/share/scripts/jsf/uncvg.js -> gpac-26.02.0.tar.gz/share/scripts/jsf/uncvg.js
Changed
@@ -1,3 +1,28 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2022-2024 + * All rights reserved + * + * This file is part of GPAC / AVGenerator filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + import { Bitstream as BS } from 'gpaccore' import { Sys as sys } from 'gpaccore' import * as evg from 'evg' @@ -18,7 +43,8 @@ //metadata filter.set_name("uncvg"); -filter.set_desc("Uncompressed Video File Format Generator Utility"); +filter.set_class_hint(GF_FS_CLASS_MM_IO); +filter.set_desc("Uncompressed Video Generator"); filter.set_version("1.0"); filter.set_author("GPAC team - (c) Telecom ParisTech 2023 - license LGPL v2"); filter.set_help("This filter provides generation of test images for ISO/IEC 23001-17\n"
View file
gpac-26.02.0.tar.gz/share/scripts/rmt
Added
+(directory)
View file
gpac-26.02.0.tar.gz/share/scripts/rmt/README.md
Added
@@ -0,0 +1,7 @@ +## GPAC realtime monitoring UI + +This folder contains the single-file versions of the UI (index.html) and JS controller (server.js) of the monitoring interface. + +You can find the development version at: https://github.com/gpac/gpac_monitor/ + +And details about the websocket monitoring server at: https://wiki.gpac.io/Developers/tutorials/rmtws \ No newline at end of file
View file
gpac-26.02.0.tar.gz/share/scripts/rmt/index.html
Added
@@ -0,0 +1,274 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>GPAC_Graph_Monitor</title> + + + <link rel="stylesheet" href="https://rsms.me/inter/inter.css"> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&display=swap" rel="stylesheet"> + <script type="module" crossorigin>var jre=Object.defineProperty;var vj=e=>{throw TypeError(e)};var Lre=(e,t,n)=>t in e?jre(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):et=n;var Le=(e,t,n)=>Lre(e,typeof t!="symbol"?t+"":t,n),Ore=(e,t,n)=>t.has(e)||vj("Cannot "+n);var zs=(e,t,n)=>(Ore(e,t,"read from private field"),n?n.call(e):t.get(e)),yj=(e,t,n)=>t.has(e)?vj("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n);function Dre(e,t){for(var n=0;n<t.length;n++){const r=tn;if(typeof r!="string"&&!Array.isArray(r)){for(const o in r)if(o!=="default"&&!(o in e)){const i=Object.getOwnPropertyDescriptor(r,o);i&&Object.defineProperty(e,o,i.get?i:{enumerable:!0,get:()=>ro})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('linkrel="modulepreload"'))r(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();var rs=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function qf(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function Fre(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var n=function r(){return this instanceof r?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};n.prototype=t.prototype}else n={};return Object.defineProperty(n,"__esModule",{value:!0}),Object.keys(e).forEach(function(r){var o=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(n,r,o.get?o:{enumerable:!0,get:function(){return er}})}),n}var e6={exports:{}},dx={},t6={exports:{}},Dt={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Bm=Symbol.for("react.element"),$re=Symbol.for("react.portal"),zre=Symbol.for("react.fragment"),Hre=Symbol.for("react.strict_mode"),Bre=Symbol.for("react.profiler"),Wre=Symbol.for("react.provider"),Ure=Symbol.for("react.context"),qre=Symbol.for("react.forward_ref"),Gre=Symbol.for("react.suspense"),Vre=Symbol.for("react.memo"),Yre=Symbol.for("react.lazy"),xj=Symbol.iterator;function Kre(e){return e===null||typeof e!="object"?null:(e=xj&&exj||e"@@iterator",typeof e=="function"?e:null)}var n6={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},r6=Object.assign,o6={};function Gf(e,t,n){this.props=e,this.context=t,this.refs=o6,this.updater=n||n6}Gf.prototype.isReactComponent={};Gf.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Gf.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function i6(){}i6.prototype=Gf.prototype;function kT(e,t,n){this.props=e,this.context=t,this.refs=o6,this.updater=n||n6}var PT=kT.prototype=new i6;PT.constructor=kT;r6(PT,Gf.prototype);PT.isPureReactComponent=!0;var bj=Array.isArray,s6=Object.prototype.hasOwnProperty,IT={current:null},a6={key:!0,ref:!0,__self:!0,__source:!0};function l6(e,t,n){var r,o={},i=null,s=null;if(t!=null)for(r in t.ref!==void 0&&(s=t.ref),t.key!==void 0&&(i=""+t.key),t)s6.call(t,r)&&!a6.hasOwnProperty(r)&&(or=tr);var a=arguments.length-2;if(a===1)o.children=n;else if(1<a){for(var l=Array(a),c=0;c<a;c++)lc=argumentsc+2;o.children=l}if(e&&e.defaultProps)for(r in a=e.defaultProps,a)or===void 0&&(or=ar);return{$$typeof:Bm,type:e,key:i,ref:s,props:o,_owner:IT.current}}function Xre(e,t){return{$$typeof:Bm,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function AT(e){return typeof e=="object"&&e!==null&&e.$$typeof===Bm}function Zre(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/=:/g,function(n){return tn})}var wj=/\/+/g;function v1(e,t){return typeof e=="object"&&e!==null&&e.key!=null?Zre(""+e.key):t.toString(36)}function m0(e,t,n,r,o){var i=typeof e;(i==="undefined"||i==="boolean")&&(e=null);var s=!1;if(e===null)s=!0;else switch(i){case"string":case"number":s=!0;break;case"object":switch(e.$$typeof){case Bm:case $re:s=!0}}if(s)return s=e,o=o(s),e=r===""?"."+v1(s,0):r,bj(o)?(n="",e!=null&&(n=e.replace(wj,"$&/")+"/"),m0(o,t,n,"",function(c){return c})):o!=null&&(AT(o)&&(o=Xre(o,n+(!o.key||s&&s.key===o.key?"":(""+o.key).replace(wj,"$&/")+"/")+e)),t.push(o)),1;if(s=0,r=r===""?".":r+":",bj(e))for(var a=0;a<e.length;a++){i=ea;var l=r+v1(i,a);s+=m0(i,t,n,l,o)}else if(l=Kre(e),typeof l=="function")for(e=l.call(e),a=0;!(i=e.next()).done;)i=i.value,l=r+v1(i,a++),s+=m0(i,t,n,l,o);else if(i==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="object Object"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return s}function wv(e,t,n){if(e==null)return e;var r=,o=0;return m0(e,r,"","",function(i){return t.call(n,i,o++)}),r}function Qre(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(n){(e._status===0||e._status===-1)&&(e._status=1,e._result=n)},function(n){(e._status===0||e._status===-1)&&(e._status=2,e._result=n)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var po={current:null},g0={transition:null},Jre={ReactCurrentDispatcher:po,ReactCurrentBatchConfig:g0,ReactCurrentOwner:IT};function c6(){throw Error("act(...) is not supported in production builds of React.")}Dt.Children={map:wv,forEach:function(e,t,n){wv(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return wv(e,function(){t++}),t},toArray:function(e){return wv(e,function(t){return t})||},only:function(e){if(!AT(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};Dt.Component=Gf;Dt.Fragment=zre;Dt.Profiler=Bre;Dt.PureComponent=kT;Dt.StrictMode=Hre;Dt.Suspense=Gre;Dt.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=Jre;Dt.act=c6;Dt.cloneElement=function(e,t,n){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var r=r6({},e.props),o=e.key,i=e.ref,s=e._owner;if(t!=null){if(t.ref!==void 0&&(i=t.ref,s=IT.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var a=e.type.defaultProps;for(l in t)s6.call(t,l)&&!a6.hasOwnProperty(l)&&(rl=tl===void 0&&a!==void 0?al:tl)}var l=arguments.length-2;if(l===1)r.children=n;else if(1<l){a=Array(l);for(var c=0;c<l;c++)ac=argumentsc+2;r.children=a}return{$$typeof:Bm,type:e.type,key:o,ref:i,props:r,_owner:s}};Dt.createContext=function(e){return e={$$typeof:Ure,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},e.Provider={$$typeof:Wre,_context:e},e.Consumer=e};Dt.createElement=l6;Dt.createFactory=function(e){var t=l6.bind(null,e);return t.type=e,t};Dt.createRef=function(){return{current:null}};Dt.forwardRef=function(e){return{$$typeof:qre,render:e}};Dt.isValidElement=AT;Dt.lazy=function(e){return{$$typeof:Yre,_payload:{_status:-1,_result:e},_init:Qre}};Dt.memo=function(e,t){return{$$typeof:Vre,type:e,compare:t===void 0?null:t}};Dt.startTransition=function(e){var t=g0.transition;g0.transition={};try{e()}finally{g0.transition=t}};Dt.unstable_act=c6;Dt.useCallback=function(e,t){return po.current.useCallback(e,t)};Dt.useContext=function(e){return po.current.useContext(e)};Dt.useDebugValue=function(){};Dt.useDeferredValue=function(e){return po.current.useDeferredValue(e)};Dt.useEffect=function(e,t){return po.current.useEffect(e,t)};Dt.useId=function(){return po.current.useId()};Dt.useImperativeHandle=function(e,t,n){return po.current.useImperativeHandle(e,t,n)};Dt.useInsertionEffect=function(e,t){return po.current.useInsertionEffect(e,t)};Dt.useLayoutEffect=function(e,t){return po.current.useLayoutEffect(e,t)};Dt.useMemo=function(e,t){return po.current.useMemo(e,t)};Dt.useReducer=function(e,t,n){return po.current.useReducer(e,t,n)};Dt.useRef=function(e){return po.current.useRef(e)};Dt.useState=function(e){return po.current.useState(e)};Dt.useSyncExternalStore=function(e,t,n){return po.current.useSyncExternalStore(e,t,n)};Dt.useTransition=function(){return po.current.useTransition()};Dt.version="18.3.1";t6.exports=Dt;var y=t6.exports;const Pe=qf(y),u6=Dre({__proto__:null,default:Pe},y);/** + * @license React + * react-jsx-runtime.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var eoe=y,toe=Symbol.for("react.element"),noe=Symbol.for("react.fragment"),roe=Object.prototype.hasOwnProperty,ooe=eoe.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,ioe={key:!0,ref:!0,__self:!0,__source:!0};function d6(e,t,n){var r,o={},i=null,s=null;n!==void 0&&(i=""+n),t.key!==void 0&&(i=""+t.key),t.ref!==void 0&&(s=t.ref);for(r in t)roe.call(t,r)&&!ioe.hasOwnProperty(r)&&(or=tr);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)or===void 0&&(or=tr);return{$$typeof:toe,type:e,key:i,ref:s,props:o,_owner:ooe.current}}dx.Fragment=noe;dx.jsx=d6;dx.jsxs=d6;e6.exports=dx;var h=e6.exports,mN={},f6={exports:{}},ci={},h6={exports:{}},p6={};/** + * @license React + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(e){function t(G,z){var K=G.length;G.push(z);e:for(;0<K;){var Q=K-1>>>1,re=GQ;if(0<o(re,z))GQ=z,GK=re,K=Q;else break e}}function n(G){return G.length===0?null:G0}function r(G){if(G.length===0)return null;var z=G0,K=G.pop();if(K!==z){G0=K;e:for(var Q=0,re=G.length,ae=re>>>1;Q<ae;){var de=2*(Q+1)-1,Ne=Gde,ye=de+1,fe=Gye;if(0>o(Ne,K))ye<re&&0>o(fe,Ne)?(GQ=fe,Gye=K,Q=ye):(GQ=Ne,Gde=K,Q=de);else if(ye<re&&0>o(fe,K))GQ=fe,Gye=K,Q=ye;else break e}}return z}function o(G,z){var K=G.sortIndex-z.sortIndex;return K!==0?K:G.id-z.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var s=Date,a=s.now();e.unstable_now=function(){return s.now()-a}}var l=,c=,d=1,f=null,p=3,g=!1,v=!1,b=!1,_=typeof setTimeout=="function"?setTimeout:null,x=typeof clearTimeout=="function"?clearTimeout:null,w=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function C(G){for(var z=n(c);z!==null;){if(z.callback===null)r(c);else if(z.startTime<=G)r(c),z.sortIndex=z.expirationTime,t(l,z);else break;z=n(c)}}function E(G){if(b=!1,C(G),!v)if(n(l)!==null)v=!0,$(R);else{var z=n(c);z!==null&&W(E,z.startTime-G)}}function R(G,z){v=!1,b&&(b=!1,x(k),k=-1),g=!0;var K=p;try{for(C(z),f=n(l);f!==null&&(!(f.expirationTime>z)||G&&!j());){var Q=f.callback;if(typeof Q=="function"){f.callback=null,p=f.priorityLevel;var re=Q(f.expirationTime<=z);z=e.unstable_now(),typeof re=="function"?f.callback=re:f===n(l)&&r(l),C(z)}else r(l);f=n(l)}if(f!==null)var ae=!0;else{var de=n(c);de!==null&&W(E,de.startTime-z),ae=!1}return ae}finally{f=null,p=K,g=!1}}var P=!1,N=null,k=-1,I=5,O=-1;function j(){return!(e.unstable_now()-O<I)}function H(){if(N!==null){var G=e.unstable_now();O=G;var z=!0;try{z=N(!0,G)}finally{z?q():(P=!1,N=null)}}else P=!1}var q;if(typeof w=="function")q=function(){w(H)};else if(typeof MessageChannel<"u"){var M=new MessageChannel,B=M.port2;M.port1.onmessage=H,q=function(){B.postMessage(null)}}else q=function(){_(H,0)};function $(G){N=G,P||(P=!0,q())}function W(G,z){k=_(function(){G(e.unstable_now())},z)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(G){G.callback=null},e.unstable_continueExecution=function(){v||g||(v=!0,$(R))},e.unstable_forceFrameRate=function(G){0>G||125<G?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):I=0<G?Math.floor(1e3/G):5},e.unstable_getCurrentPriorityLevel=function(){return p},e.unstable_getFirstCallbackNode=function(){return n(l)},e.unstable_next=function(G){switch(p){case 1:case 2:case 3:var z=3;break;default:z=p}var K=p;p=z;try{return G()}finally{p=K}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(G,z){switch(G){case 1:case 2:case 3:case 4:case 5:break;default:G=3}var K=p;p=G;try{return z()}finally{p=K}},e.unstable_scheduleCallback=function(G,z,K){var Q=e.unstable_now();switch(typeof K=="object"&&K!==null?(K=K.delay,K=typeof K=="number"&&0<K?Q+K:Q):K=Q,G){case 1:var re=-1;break;case 2:re=250;break;case 5:re=1073741823;break;case 4:re=1e4;break;default:re=5e3}return re=K+re,G={id:d++,callback:z,priorityLevel:G,startTime:K,expirationTime:re,sortIndex:-1},K>Q?(G.sortIndex=K,t(c,G),n(l)===null&&G===n(c)&&(b?(x(k),k=-1):b=!0,W(E,K-Q))):(G.sortIndex=re,t(l,G),v||g||(v=!0,$(R))),G},e.unstable_shouldYield=j,e.unstable_wrapCallback=function(G){var z=p;return function(){var K=p;p=z;try{return G.apply(this,arguments)}finally{p=K}}}})(p6);h6.exports=p6;var soe=h6.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var aoe=y,ii=soe;function Re(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args="+encodeURIComponent(argumentsn);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var m6=new Set,Qp={};function Bu(e,t){gf(e,t),gf(e+"Capture",t)}function gf(e,t){for(Qpe=t,e=0;e<t.length;e++)m6.add(te)}var Xa=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),gN=Object.prototype.hasOwnProperty,loe=/^:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040*$/,Sj={},_j={};function coe(e){return gN.call(_j,e)?!0:gN.call(Sj,e)?!1:loe.test(e)?_je=!0:(Sje=!0,!1)}function uoe(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function doe(e,t,n,r){if(t===null||typeof t>"u"||uoe(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function mo(e,t,n,r,o,i,s){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=s}var Fr={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Fre=new mo(e,0,!1,e,null,!1,!1)});"acceptCharset","accept-charset","className","class","htmlFor","for","httpEquiv","http-equiv".forEach(function(e){var t=e0;Frt=new mo(t,1,!1,e1,null,!1,!1)});"contentEditable","draggable","spellCheck","value".forEach(function(e){Fre=new mo(e,2,!1,e.toLowerCase(),null,!1,!1)});"autoReverse","externalResourcesRequired","focusable","preserveAlpha".forEach(function(e){Fre=new mo(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Fre=new mo(e,3,!1,e.toLowerCase(),null,!1,!1)});"checked","multiple","muted","selected".forEach(function(e){Fre=new mo(e,3,!0,e,null,!1,!1)});"capture","download".forEach(function(e){Fre=new mo(e,4,!1,e,null,!1,!1)});"cols","rows","size","span".forEach(function(e){Fre=new mo(e,6,!1,e,null,!1,!1)});"rowSpan","start".forEach(function(e){Fre=new mo(e,5,!1,e.toLowerCase(),null,!1,!1)});var MT=/\-:(a-z)/g;function jT(e){return e1.toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(MT,jT);Frt=new mo(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(MT,jT);Frt=new mo(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});"xml:base","xml:lang","xml:space".forEach(function(e){var t=e.replace(MT,jT);Frt=new mo(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});"tabIndex","crossOrigin".forEach(function(e){Fre=new mo(e,1,!1,e.toLowerCase(),null,!1,!1)});Fr.xlinkHref=new mo("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);"src","href","action","formAction".forEach(function(e){Fre=new mo(e,1,!1,e.toLowerCase(),null,!0,!0)});function LT(e,t,n,r){var o=Fr.hasOwnProperty(t)?Frt:null;(o!==null?o.type!==0:r||!(2<t.length)||t0!=="o"&&t0!=="O"||t1!=="n"&&t1!=="N")&&(doe(t,n,o,r)&&(n=null),r||o===null?coe(t)&&(n===null?e.removeAttribute(t):e.setAttribute(t,""+n)):o.mustUseProperty?eo.propertyName=n===null?o.type===3?!1:"":n:(t=o.attributeName,r=o.attributeNamespace,n===null?e.removeAttribute(t):(o=o.type,n=o===3||o===4&&n===!0?"":""+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}var sl=aoe.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,Sv=Symbol.for("react.element"),Od=Symbol.for("react.portal"),Dd=Symbol.for("react.fragment"),OT=Symbol.for("react.strict_mode"),vN=Symbol.for("react.profiler"),g6=Symbol.for("react.provider"),v6=Symbol.for("react.context"),DT=Symbol.for("react.forward_ref"),yN=Symbol.for("react.suspense"),xN=Symbol.for("react.suspense_list"),FT=Symbol.for("react.memo"),ql=Symbol.for("react.lazy"),y6=Symbol.for("react.offscreen"),Cj=Symbol.iterator;function Xh(e){return e===null||typeof e!="object"?null:(e=Cj&&eCj||e"@@iterator",typeof e=="function"?e:null)}var zn=Object.assign,y1;function hp(e){if(y1===void 0)try{throw Error()}catch(n){var t=n.stack.trim().match(/\n( *(at )?)/);y1=t&&t1||""}return` +`+y1+e}var x1=!1;function b1(e,t){if(!e||x1)return"";x1=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,)}catch(c){var r=c}Reflect.construct(e,,t)}else{try{t.call()}catch(c){r=c}e.call(t.prototype)}else{try{throw Error()}catch(c){r=c}e()}}catch(c){if(c&&r&&typeof c.stack=="string"){for(var o=c.stack.split(` +`),i=r.stack.split(` +`),s=o.length-1,a=i.length-1;1<=s&&0<=a&&os!==ia;)a--;for(;1<=s&&0<=a;s--,a--)if(os!==ia){if(s!==1||a!==1)do if(s--,a--,0>a||os!==ia){var l=` +`+os.replace(" at new "," at ");return e.displayName&&l.includes("<anonymous>")&&(l=l.replace("<anonymous>",e.displayName)),l}while(1<=s&&0<=a);break}}}finally{x1=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?hp(e):""}function foe(e){switch(e.tag){case 5:return hp(e.type);case 16:return hp("Lazy");case 13:return hp("Suspense");case 19:return hp("SuspenseList");case 0:case 2:case 15:return e=b1(e.type,!1),e;case 11:return e=b1(e.type.render,!1),e;case 1:return e=b1(e.type,!0),e;default:return""}}function bN(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Dd:return"Fragment";case Od:return"Portal";case vN:return"Profiler";case OT:return"StrictMode";case yN:return"Suspense";case xN:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case v6:return(e.displayName||"Context")+".Consumer";case g6:return(e._context.displayName||"Context")+".Provider";case DT:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case FT:return t=e.displayName||null,t!==null?t:bN(e.type)||"Memo";case ql:t=e._payload,e=e._init;try{return bN(e(t))}catch{}}return null}function hoe(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return bN(t);case 8:return t===OT?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function dc(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function x6(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function poe(e){var t=x6(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+et;if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var o=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(s){r=""+s,i.call(this,s)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(s){r=""+s},stopTracking:function(){e._valueTracker=null,delete et}}}}function _v(e){e._valueTracker||(e._valueTracker=poe(e))}function b6(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=x6(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function U0(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function wN(e,t){var n=t.checked;return zn({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ej(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=dc(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function w6(e,t){t=t.checked,t!=null&<(e,"checked",t,!1)}function SN(e,t){w6(e,t);var n=dc(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?_N(e,t.type,n):t.hasOwnProperty("defaultValue")&&_N(e,t.type,dc(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Nj(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function _N(e,t,n){(t!=="number"||U0(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var pp=Array.isArray;function Jd(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t"$"+no=!0;for(n=0;n<e.length;n++)o=t.hasOwnProperty("$"+en.value),en.selected!==o&&(en.selected=o),o&&r&&(en.defaultSelected=!0)}else{for(n=""+dc(n),t=null,o=0;o<e.length;o++){if(eo.value===n){eo.selected=!0,r&&(eo.defaultSelected=!0);return}t!==null||eo.disabled||(t=eo)}t!==null&&(t.selected=!0)}}function CN(e,t){if(t.dangerouslySetInnerHTML!=null)throw Error(Re(91));return zn({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue})}function Rj(e,t){var n=t.value;if(n==null){if(n=t.children,t=t.defaultValue,n!=null){if(t!=null)throw Error(Re(92));if(pp(n)){if(1<n.length)throw Error(Re(93));n=n0}t=n}t==null&&(t=""),n=t}e._wrapperState={initialValue:dc(n)}}function S6(e,t){var n=dc(t.value),r=dc(t.defaultValue);n!=null&&(n=""+n,n!==e.value&&(e.value=n),t.defaultValue==null&&e.defaultValue!==n&&(e.defaultValue=n)),r!=null&&(e.defaultValue=""+r)}function Tj(e){var t=e.textContent;t===e._wrapperState.initialValue&&t!==""&&t!==null&&(e.value=t)}function _6(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function EN(e,t){return e==null||e==="http://www.w3.org/1999/xhtml"?_6(t):e==="http://www.w3.org/2000/svg"&&t==="foreignObject"?"http://www.w3.org/1999/xhtml":e}var Cv,C6=function(e){return typeof MSApp<"u"&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction(function(){return e(t,n,r,o)})}:e}(function(e,t){if(e.namespaceURI!=="http://www.w3.org/2000/svg"||"innerHTML"in e)e.innerHTML=t;else{for(Cv=Cv||document.createElement("div"),Cv.innerHTML="<svg>"+t.valueOf().toString()+"</svg>",t=Cv.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Jp(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var jp={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},moe="Webkit","ms","Moz","O";Object.keys(jp).forEach(function(e){moe.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),jpt=jpe})});function E6(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||jp.hasOwnProperty(e)&&jpe?(""+t).trim():t+"px"}function N6(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,o=E6(n,tn,r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,o):en=o}}var goe=zn({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function NN(e,t){if(t){if(goee&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(Re(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(Re(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(Re(61))}if(t.style!=null&&typeof t.style!="object")throw Error(Re(62))}}function RN(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var TN=null;function $T(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var kN=null,ef=null,tf=null;function kj(e){if(e=qm(e)){if(typeof kN!="function")throw Error(Re(280));var t=e.stateNode;t&&(t=gx(t),kN(e.stateNode,e.type,t))}}function R6(e){ef?tf?tf.push(e):tf=e:ef=e}function T6(){if(ef){var e=ef,t=tf;if(tf=ef=null,kj(e),t)for(e=0;e<t.length;e++)kj(te)}}function k6(e,t){return e(t)}function P6(){}var w1=!1;function I6(e,t,n){if(w1)return e(t,n);w1=!0;try{return k6(e,t,n)}finally{w1=!1,(ef!==null||tf!==null)&&(P6(),T6())}}function em(e,t){var n=e.stateNode;if(n===null)return null;var r=gx(n);if(r===null)return null;n=rt;e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(e=e.type,r=!(e==="button"||e==="input"||e==="select"||e==="textarea")),e=!r;break e;default:e=!1}if(e)return null;if(n&&typeof n!="function")throw Error(Re(231,t,typeof n));return n}var PN=!1;if(Xa)try{var Zh={};Object.defineProperty(Zh,"passive",{get:function(){PN=!0}}),window.addEventListener("test",Zh,Zh),window.removeEventListener("test",Zh,Zh)}catch{PN=!1}function voe(e,t,n,r,o,i,s,a,l){var c=Array.prototype.slice.call(arguments,3);try{t.apply(n,c)}catch(d){this.onError(d)}}var Lp=!1,q0=null,G0=!1,IN=null,yoe={onError:function(e){Lp=!0,q0=e}};function xoe(e,t,n,r,o,i,s,a,l){Lp=!1,q0=null,voe.apply(yoe,arguments)}function boe(e,t,n,r,o,i,s,a,l){if(xoe.apply(this,arguments),Lp){if(Lp){var c=q0;Lp=!1,q0=null}else throw Error(Re(198));G0||(G0=!0,IN=c)}}function Wu(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do t=e,t.flags&4098&&(n=t.return),e=t.return;while(e)}return t.tag===3?n:null}function A6(e){if(e.tag===13){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function Pj(e){if(Wu(e)!==e)throw Error(Re(188))}function woe(e){var t=e.alternate;if(!t){if(t=Wu(e),t===null)throw Error(Re(188));return t!==e?null:e}for(var n=e,r=t;;){var o=n.return;if(o===null)break;var i=o.alternate;if(i===null){if(r=o.return,r!==null){n=r;continue}break}if(o.child===i.child){for(i=o.child;i;){if(i===n)return Pj(o),e;if(i===r)return Pj(o),t;i=i.sibling}throw Error(Re(188))}if(n.return!==r.return)n=o,r=i;else{for(var s=!1,a=o.child;a;){if(a===n){s=!0,n=o,r=i;break}if(a===r){s=!0,r=o,n=i;break}a=a.sibling}if(!s){for(a=i.child;a;){if(a===n){s=!0,n=i,r=o;break}if(a===r){s=!0,r=i,n=o;break}a=a.sibling}if(!s)throw Error(Re(189))}}if(n.alternate!==r)throw Error(Re(190))}if(n.tag!==3)throw Error(Re(188));return n.stateNode.current===n?e:t}function M6(e){return e=woe(e),e!==null?j6(e):null}function j6(e){if(e.tag===5||e.tag===6)return e;for(e=e.child;e!==null;){var t=j6(e);if(t!==null)return t;e=e.sibling}return null}var L6=ii.unstable_scheduleCallback,Ij=ii.unstable_cancelCallback,Soe=ii.unstable_shouldYield,_oe=ii.unstable_requestPaint,Qn=ii.unstable_now,Coe=ii.unstable_getCurrentPriorityLevel,zT=ii.unstable_ImmediatePriority,O6=ii.unstable_UserBlockingPriority,V0=ii.unstable_NormalPriority,Eoe=ii.unstable_LowPriority,D6=ii.unstable_IdlePriority,fx=null,na=null;function Noe(e){if(na&&typeof na.onCommitFiberRoot=="function")try{na.onCommitFiberRoot(fx,e,void 0,(e.current.flags&128)===128)}catch{}}var cs=Math.clz32?Math.clz32:koe,Roe=Math.log,Toe=Math.LN2;function koe(e){return e>>>=0,e===0?32:31-(Roe(e)/Toe|0)|0}var Ev=64,Nv=4194304;function mp(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Y0(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,o=e.suspendedLanes,i=e.pingedLanes,s=n&268435455;if(s!==0){var a=s&~o;a!==0?r=mp(a):(i&=s,i!==0&&(r=mp(i)))}else s=n&~o,s!==0?r=mp(s):i!==0&&(r=mp(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&o)&&(o=r&-r,i=t&-t,o>=i||o===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0<t;)n=31-cs(t),o=1<<n,r|=en,t&=~o;return r}function Poe(e,t){switch(e){case 1:case 2:case 4:return t+250;case 8:case 16:case 32:case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return-1;case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ioe(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,o=e.expirationTimes,i=e.pendingLanes;0<i;){var s=31-cs(i),a=1<<s,l=os;l===-1?(!(a&n)||a&r)&&(os=Poe(a,t)):l<=t&&(e.expiredLanes|=a),i&=~a}}function AN(e){return e=e.pendingLanes&-1073741825,e!==0?e:e&1073741824?1073741824:0}function F6(){var e=Ev;return Ev<<=1,!(Ev&4194240)&&(Ev=64),e}function S1(e){for(var t=,n=0;31>n;n++)t.push(e);return t}function Wm(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-cs(t),et=n}function Aoe(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0<n;){var o=31-cs(n),i=1<<o;to=0,ro=-1,eo=-1,n&=~i}}function HT(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-cs(n),o=1<<r;o&t|er&t&&(er|=t),n&=~o}}var on=0;function $6(e){return e&=-e,1<e?4<e?e&268435455?16:536870912:4:1}var z6,BT,H6,B6,W6,MN=!1,Rv=,ec=null,tc=null,nc=null,tm=new Map,nm=new Map,Yl=,Moe="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" ");function Aj(e,t){switch(e){case"focusin":case"focusout":ec=null;break;case"dragenter":case"dragleave":tc=null;break;case"mouseover":case"mouseout":nc=null;break;case"pointerover":case"pointerout":tm.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":nm.delete(t.pointerId)}}function Qh(e,t,n,r,o,i){return e===null||e.nativeEvent!==i?(e={blockedOn:t,domEventName:n,eventSystemFlags:r,nativeEvent:i,targetContainers:o},t!==null&&(t=qm(t),t!==null&&BT(t)),e):(e.eventSystemFlags|=r,t=e.targetContainers,o!==null&&t.indexOf(o)===-1&&t.push(o),e)}function joe(e,t,n,r,o){switch(t){case"focusin":return ec=Qh(ec,e,t,n,r,o),!0;case"dragenter":return tc=Qh(tc,e,t,n,r,o),!0;case"mouseover":return nc=Qh(nc,e,t,n,r,o),!0;case"pointerover":var i=o.pointerId;return tm.set(i,Qh(tm.get(i)||null,e,t,n,r,o)),!0;case"gotpointercapture":return i=o.pointerId,nm.set(i,Qh(nm.get(i)||null,e,t,n,r,o)),!0}return!1}function U6(e){var t=uu(e.target);if(t!==null){var n=Wu(t);if(n!==null){if(t=n.tag,t===13){if(t=A6(n),t!==null){e.blockedOn=t,W6(e.priority,function(){H6(n)});return}}else if(t===3&&n.stateNode.current.memoizedState.isDehydrated){e.blockedOn=n.tag===3?n.stateNode.containerInfo:null;return}}}e.blockedOn=null}function v0(e){if(e.blockedOn!==null)return!1;for(var t=e.targetContainers;0<t.length;){var n=jN(e.domEventName,e.eventSystemFlags,t0,e.nativeEvent);if(n===null){n=e.nativeEvent;var r=new n.constructor(n.type,n);TN=r,n.target.dispatchEvent(r),TN=null}else return t=qm(n),t!==null&&BT(t),e.blockedOn=n,!1;t.shift()}return!0}function Mj(e,t,n){v0(e)&&n.delete(t)}function Loe(){MN=!1,ec!==null&&v0(ec)&&(ec=null),tc!==null&&v0(tc)&&(tc=null),nc!==null&&v0(nc)&&(nc=null),tm.forEach(Mj),nm.forEach(Mj)}function Jh(e,t){e.blockedOn===t&&(e.blockedOn=null,MN||(MN=!0,ii.unstable_scheduleCallback(ii.unstable_NormalPriority,Loe)))}function rm(e){function t(o){return Jh(o,e)}if(0<Rv.length){Jh(Rv0,e);for(var n=1;n<Rv.length;n++){var r=Rvn;r.blockedOn===e&&(r.blockedOn=null)}}for(ec!==null&&Jh(ec,e),tc!==null&&Jh(tc,e),nc!==null&&Jh(nc,e),tm.forEach(t),nm.forEach(t),n=0;n<Yl.length;n++)r=Yln,r.blockedOn===e&&(r.blockedOn=null);for(;0<Yl.length&&(n=Yl0,n.blockedOn===null);)U6(n),n.blockedOn===null&&Yl.shift()}var nf=sl.ReactCurrentBatchConfig,K0=!0;function Ooe(e,t,n,r){var o=on,i=nf.transition;nf.transition=null;try{on=1,WT(e,t,n,r)}finally{on=o,nf.transition=i}}function Doe(e,t,n,r){var o=on,i=nf.transition;nf.transition=null;try{on=4,WT(e,t,n,r)}finally{on=o,nf.transition=i}}function WT(e,t,n,r){if(K0){var o=jN(e,t,n,r);if(o===null)A1(e,t,r,X0,n),Aj(e,r);else if(joe(o,e,t,n,r))r.stopPropagation();else if(Aj(e,r),t&4&&-1<Moe.indexOf(e)){for(;o!==null;){var i=qm(o);if(i!==null&&z6(i),i=jN(e,t,n,r),i===null&&A1(e,t,r,X0,n),i===o)break;o=i}o!==null&&r.stopPropagation()}else A1(e,t,r,null,n)}}var X0=null;function jN(e,t,n,r){if(X0=null,e=$T(r),e=uu(e),e!==null)if(t=Wu(e),t===null)e=null;else if(n=t.tag,n===13){if(e=A6(t),e!==null)return e;e=null}else if(n===3){if(t.stateNode.current.memoizedState.isDehydrated)return t.tag===3?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null);return X0=e,null}function q6(e){switch(e){case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 1;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"toggle":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 4;case"message":switch(Coe()){case zT:return 1;case O6:return 4;case V0:case Eoe:return 16;case D6:return 536870912;default:return 16}default:return 16}}var Ql=null,UT=null,y0=null;function G6(){if(y0)return y0;var e,t=UT,n=t.length,r,o="value"in Ql?Ql.value:Ql.textContent,i=o.length;for(e=0;e<n&&te===oe;e++);var s=n-e;for(r=1;r<=s&&tn-r===oi-r;r++);return y0=o.slice(e,1<r?1-r:void 0)}function x0(e){var t=e.keyCode;return"charCode"in e?(e=e.charCode,e===0&&t===13&&(e=13)):e=t,e===10&&(e=13),32<=e||e===13?e:0}function Tv(){return!0}function jj(){return!1}function ui(e){function t(n,r,o,i,s){this._reactName=n,this._targetInst=o,this.type=r,this.nativeEvent=i,this.target=s,this.currentTarget=null;for(var a in e)e.hasOwnProperty(a)&&(n=ea,thisa=n?n(i):ia);return this.isDefaultPrevented=(i.defaultPrevented!=null?i.defaultPrevented:i.returnValue===!1)?Tv:jj,this.isPropagationStopped=jj,this}return zn(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var n=this.nativeEvent;n&&(n.preventDefault?n.preventDefault():typeof n.returnValue!="unknown"&&(n.returnValue=!1),this.isDefaultPrevented=Tv)},stopPropagation:function(){var n=this.nativeEvent;n&&(n.stopPropagation?n.stopPropagation():typeof n.cancelBubble!="unknown"&&(n.cancelBubble=!0),this.isPropagationStopped=Tv)},persist:function(){},isPersistent:Tv}),t}var Vf={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},qT=ui(Vf),Um=zn({},Vf,{view:0,detail:0}),Foe=ui(Um),_1,C1,ep,hx=zn({},Um,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:GT,button:0,buttons:0,relatedTarget:function(e){return e.relatedTarget===void 0?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==ep&&(ep&&e.type==="mousemove"?(_1=e.screenX-ep.screenX,C1=e.screenY-ep.screenY):C1=_1=0,ep=e),_1)},movementY:function(e){return"movementY"in e?e.movementY:C1}}),Lj=ui(hx),$oe=zn({},hx,{dataTransfer:0}),zoe=ui($oe),Hoe=zn({},Um,{relatedTarget:0}),E1=ui(Hoe),Boe=zn({},Vf,{animationName:0,elapsedTime:0,pseudoElement:0}),Woe=ui(Boe),Uoe=zn({},Vf,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),qoe=ui(Uoe),Goe=zn({},Vf,{data:0}),Oj=ui(Goe),Voe={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Yoe={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Koe={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function Xoe(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):(e=Koee)?!!te:!1}function GT(){return Xoe}var Zoe=zn({},Um,{key:function(e){if(e.key){var t=Voee.key||e.key;if(t!=="Unidentified")return t}return e.type==="keypress"?(e=x0(e),e===13?"Enter":String.fromCharCode(e)):e.type==="keydown"||e.type==="keyup"?Yoee.keyCode||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:GT,charCode:function(e){return e.type==="keypress"?x0(e):0},keyCode:function(e){return e.type==="keydown"||e.type==="keyup"?e.keyCode:0},which:function(e){return e.type==="keypress"?x0(e):e.type==="keydown"||e.type==="keyup"?e.keyCode:0}}),Qoe=ui(Zoe),Joe=zn({},hx,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),Dj=ui(Joe),eie=zn({},Um,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:GT}),tie=ui(eie),nie=zn({},Vf,{propertyName:0,elapsedTime:0,pseudoElement:0}),rie=ui(nie),oie=zn({},hx,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0}),iie=ui(oie),sie=9,13,27,32,VT=Xa&&"CompositionEvent"in window,Op=null;Xa&&"documentMode"in document&&(Op=document.documentMode);var aie=Xa&&"TextEvent"in window&&!Op,V6=Xa&&(!VT||Op&&8<Op&&11>=Op),Fj=" ",$j=!1;function Y6(e,t){switch(e){case"keyup":return sie.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function K6(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Fd=!1;function lie(e,t){switch(e){case"compositionend":return K6(t);case"keypress":return t.which!==32?null:($j=!0,Fj);case"textInput":return e=t.data,e===Fj&&$j?null:e;default:return null}}function cie(e,t){if(Fd)return e==="compositionend"||!VT&&Y6(e,t)?(e=G6(),y0=UT=Ql=null,Fd=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return V6&&t.locale!=="ko"?null:t.data;default:return null}}var uie={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function zj(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t==="input"?!!uiee.type:t==="textarea"}function X6(e,t,n,r){R6(r),t=Z0(t,"onChange"),0<t.length&&(n=new qT("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var Dp=null,om=null;function die(e){aH(e,0)}function px(e){var t=Hd(e);if(b6(t))return e}function fie(e,t){if(e==="change")return t}var Z6=!1;if(Xa){var N1;if(Xa){var R1="oninput"in document;if(!R1){var Hj=document.createElement("div");Hj.setAttribute("oninput","return;"),R1=typeof Hj.oninput=="function"}N1=R1}else N1=!1;Z6=N1&&(!document.documentMode||9<document.documentMode)}function Bj(){Dp&&(Dp.detachEvent("onpropertychange",Q6),om=Dp=null)}function Q6(e){if(e.propertyName==="value"&&px(om)){var t=;X6(t,om,e,$T(e)),I6(die,t)}}function hie(e,t,n){e==="focusin"?(Bj(),Dp=t,om=n,Dp.attachEvent("onpropertychange",Q6)):e==="focusout"&&Bj()}function pie(e){if(e==="selectionchange"||e==="keyup"||e==="keydown")return px(om)}function mie(e,t){if(e==="click")return px(t)}function gie(e,t){if(e==="input"||e==="change")return px(t)}function vie(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var hs=typeof Object.is=="function"?Object.is:vie;function im(e,t){if(hs(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++){var o=nr;if(!gN.call(t,o)||!hs(eo,to))return!1}return!0}function Wj(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function Uj(e,t){var n=Wj(e);e=0;for(var r;n;){if(n.nodeType===3){if(r=e+n.textContent.length,e<=t&&r>=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Wj(n)}}function J6(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?J6(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function eH(){for(var e=window,t=U0();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=U0(e.document)}return t}function YT(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function yie(e){var t=eH(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&J6(n.ownerDocument.documentElement,n)){if(r!==null&&YT(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var o=n.textContent.length,i=Math.min(r.start,o);r=r.end===void 0?i:Math.min(r.end,o),!e.extend&&i>r&&(o=r,r=i,i=o),o=Uj(n,i);var s=Uj(n,r);o&&s&&(e.rangeCount!==1||e.anchorNode!==o.node||e.anchorOffset!==o.offset||e.focusNode!==s.node||e.focusOffset!==s.offset)&&(t=t.createRange(),t.setStart(o.node,o.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(s.node,s.offset)):(t.setEnd(s.node,s.offset),e.addRange(t)))}}for(t=,e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n<t.length;n++)e=tn,e.element.scrollLeft=e.left,e.element.scrollTop=e.top}}var xie=Xa&&"documentMode"in document&&11>=document.documentMode,$d=null,LN=null,Fp=null,ON=!1;function qj(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ON||$d==null||$d!==U0(r)||(r=$d,"selectionStart"in r&&YT(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Fp&&im(Fp,r)||(Fp=r,r=Z0(LN,"onSelect"),0<r.length&&(t=new qT("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=$d)))}function kv(e,t){var n={};return ne.toLowerCase()=t.toLowerCase(),n"Webkit"+e="webkit"+t,n"Moz"+e="moz"+t,n}var zd={animationend:kv("Animation","AnimationEnd"),animationiteration:kv("Animation","AnimationIteration"),animationstart:kv("Animation","AnimationStart"),transitionend:kv("Transition","TransitionEnd")},T1={},tH={};Xa&&(tH=document.createElement("div").style,"AnimationEvent"in window||(delete zd.animationend.animation,delete zd.animationiteration.animation,delete zd.animationstart.animation),"TransitionEvent"in window||delete zd.transitionend.transition);function mx(e){if(T1e)return T1e;if(!zde)return e;var t=zde,n;for(n in t)if(t.hasOwnProperty(n)&&n in tH)return T1e=tn;return e}var nH=mx("animationend"),rH=mx("animationiteration"),oH=mx("animationstart"),iH=mx("transitionend"),sH=new Map,Gj="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function xc(e,t){sH.set(e,t),Bu(t,e)}for(var k1=0;k1<Gj.length;k1++){var P1=Gjk1,bie=P1.toLowerCase(),wie=P10.toUpperCase()+P1.slice(1);xc(bie,"on"+wie)}xc(nH,"onAnimationEnd");xc(rH,"onAnimationIteration");xc(oH,"onAnimationStart");xc("dblclick","onDoubleClick");xc("focusin","onFocus");xc("focusout","onBlur");xc(iH,"onTransitionEnd");gf("onMouseEnter","mouseout","mouseover");gf("onMouseLeave","mouseout","mouseover");gf("onPointerEnter","pointerout","pointerover");gf("onPointerLeave","pointerout","pointerover");Bu("onChange","change click focusin focusout input keydown keyup selectionchange".split(" "));Bu("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" "));Bu("onBeforeInput","compositionend","keypress","textInput","paste");Bu("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" "));Bu("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" "));Bu("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var gp="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Sie=new Set("cancel close invalid load scroll toggle".split(" ").concat(gp));function Vj(e,t,n){var r=e.type||"unknown-event";e.currentTarget=n,boe(r,t,void 0,e),e.currentTarget=null}function aH(e,t){t=(t&4)!==0;for(var n=0;n<e.length;n++){var r=en,o=r.event;r=r.listeners;e:{var i=void 0;if(t)for(var s=r.length-1;0<=s;s--){var a=rs,l=a.instance,c=a.currentTarget;if(a=a.listener,l!==i&&o.isPropagationStopped())break e;Vj(o,a,c),i=l}else for(s=0;s<r.length;s++){if(a=rs,l=a.instance,c=a.currentTarget,a=a.listener,l!==i&&o.isPropagationStopped())break e;Vj(o,a,c),i=l}}}if(G0)throw e=IN,G0=!1,IN=null,e}function wn(e,t){var n=tHN;n===void 0&&(n=tHN=new Set);var r=e+"__bubble";n.has(r)||(lH(t,e,2,!1),n.add(r))}function I1(e,t,n){var r=0;t&&(r|=4),lH(n,e,r,t)}var Pv="_reactListening"+Math.random().toString(36).slice(2);function sm(e){if(!ePv){ePv=!0,m6.forEach(function(n){n!=="selectionchange"&&(Sie.has(n)||I1(n,!1,e),I1(n,!0,e))});var t=e.nodeType===9?e:e.ownerDocument;t===null||tPv||(tPv=!0,I1("selectionchange",!1,t))}}function lH(e,t,n,r){switch(q6(t)){case 1:var o=Ooe;break;case 4:o=Doe;break;default:o=WT}n=o.bind(null,t,n,e),o=void 0,!PN||t!=="touchstart"&&t!=="touchmove"&&t!=="wheel"||(o=!0),r?o!==void 0?e.addEventListener(t,n,{capture:!0,passive:o}):e.addEventListener(t,n,!0):o!==void 0?e.addEventListener(t,n,{passive:o}):e.addEventListener(t,n,!1)}function A1(e,t,n,r,o){var i=r;if(!(t&1)&&!(t&2)&&r!==null)e:for(;;){if(r===null)return;var s=r.tag;if(s===3||s===4){var a=r.stateNode.containerInfo;if(a===o||a.nodeType===8&&a.parentNode===o)break;if(s===4)for(s=r.return;s!==null;){var l=s.tag;if((l===3||l===4)&&(l=s.stateNode.containerInfo,l===o||l.nodeType===8&&l.parentNode===o))return;s=s.return}for(;a!==null;){if(s=uu(a),s===null)return;if(l=s.tag,l===5||l===6){r=i=s;continue e}a=a.parentNode}}r=r.return}I6(function(){var c=i,d=$T(n),f=;e:{var p=sH.get(e);if(p!==void 0){var g=qT,v=e;switch(e){case"keypress":if(x0(n)===0)break e;case"keydown":case"keyup":g=Qoe;break;case"focusin":v="focus",g=E1;break;case"focusout":v="blur",g=E1;break;case"beforeblur":case"afterblur":g=E1;break;case"click":if(n.button===2)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":g=Lj;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":g=zoe;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":g=tie;break;case nH:case rH:case oH:g=Woe;break;case iH:g=rie;break;case"scroll":g=Foe;break;case"wheel":g=iie;break;case"copy":case"cut":case"paste":g=qoe;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":g=Dj}var b=(t&4)!==0,_=!b&&e==="scroll",x=b?p!==null?p+"Capture":null:p;b=;for(var w=c,C;w!==null;){C=w;var E=C.stateNode;if(C.tag===5&&E!==null&&(C=E,x!==null&&(E=em(w,x),E!=null&&b.push(am(w,E,C)))),_)break;w=w.return}0<b.length&&(p=new g(p,v,null,n,d),f.push({event:p,listeners:b}))}}if(!(t&7)){e:{if(p=e==="mouseover"||e==="pointerover",g=e==="mouseout"||e==="pointerout",p&&n!==TN&&(v=n.relatedTarget||n.fromElement)&&(uu(v)||vZa))break e;if((g||p)&&(p=d.window===d?d:(p=d.ownerDocument)?p.defaultView||p.parentWindow:window,g?(v=n.relatedTarget||n.toElement,g=c,v=v?uu(v):null,v!==null&&(_=Wu(v),v!==_||v.tag!==5&&v.tag!==6)&&(v=null)):(g=null,v=c),g!==v)){if(b=Lj,E="onMouseLeave",x="onMouseEnter",w="mouse",(e==="pointerout"||e==="pointerover")&&(b=Dj,E="onPointerLeave",x="onPointerEnter",w="pointer"),_=g==null?p:Hd(g),C=v==null?p:Hd(v),p=new b(E,w+"leave",g,n,d),p.target=_,p.relatedTarget=C,E=null,uu(d)===c&&(b=new b(x,w+"enter",v,n,d),b.target=C,b.relatedTarget=_,E=b),_=E,g&&v)t:{for(b=g,x=v,w=0,C=b;C;C=Cd(C))w++;for(C=0,E=x;E;E=Cd(E))C++;for(;0<w-C;)b=Cd(b),w--;for(;0<C-w;)x=Cd(x),C--;for(;w--;){if(b===x||x!==null&&b===x.alternate)break t;b=Cd(b),x=Cd(x)}b=null}else b=null;g!==null&&Yj(f,p,g,b,!1),v!==null&&_!==null&&Yj(f,_,v,b,!0)}}e:{if(p=c?Hd(c):window,g=p.nodeName&&p.nodeName.toLowerCase(),g==="select"||g==="input"&&p.type==="file")var R=fie;else if(zj(p))if(Z6)R=gie;else{R=pie;var P=hie}else(g=p.nodeName)&&g.toLowerCase()==="input"&&(p.type==="checkbox"||p.type==="radio")&&(R=mie);if(R&&(R=R(e,c))){X6(f,R,n,d);break e}P&&P(e,p,c),e==="focusout"&&(P=p._wrapperState)&&P.controlled&&p.type==="number"&&_N(p,"number",p.value)}switch(P=c?Hd(c):window,e){case"focusin":(zj(P)||P.contentEditable==="true")&&($d=P,LN=c,Fp=null);break;case"focusout":Fp=LN=$d=null;break;case"mousedown":ON=!0;break;case"contextmenu":case"mouseup":case"dragend":ON=!1,qj(f,n,d);break;case"selectionchange":if(xie)break;case"keydown":case"keyup":qj(f,n,d)}var N;if(VT)e:{switch(e){case"compositionstart":var k="onCompositionStart";break e;case"compositionend":k="onCompositionEnd";break e;case"compositionupdate":k="onCompositionUpdate";break e}k=void 0}else Fd?Y6(e,n)&&(k="onCompositionEnd"):e==="keydown"&&n.keyCode===229&&(k="onCompositionStart");k&&(V6&&n.locale!=="ko"&&(Fd||k!=="onCompositionStart"?k==="onCompositionEnd"&&Fd&&(N=G6()):(Ql=d,UT="value"in Ql?Ql.value:Ql.textContent,Fd=!0)),P=Z0(c,k),0<P.length&&(k=new Oj(k,e,null,n,d),f.push({event:k,listeners:P}),N?k.data=N:(N=K6(n),N!==null&&(k.data=N)))),(N=aie?lie(e,n):cie(e,n))&&(c=Z0(c,"onBeforeInput"),0<c.length&&(d=new Oj("onBeforeInput","beforeinput",null,n,d),f.push({event:d,listeners:c}),d.data=N))}aH(f,t)})}function am(e,t,n){return{instance:e,listener:t,currentTarget:n}}function Z0(e,t){for(var n=t+"Capture",r=;e!==null;){var o=e,i=o.stateNode;o.tag===5&&i!==null&&(o=i,i=em(e,n),i!=null&&r.unshift(am(e,i,o)),i=em(e,t),i!=null&&r.push(am(e,i,o))),e=e.return}return r}function Cd(e){if(e===null)return null;do e=e.return;while(e&&e.tag!==5);return e||null}function Yj(e,t,n,r,o){for(var i=t._reactName,s=;n!==null&&n!==r;){var a=n,l=a.alternate,c=a.stateNode;if(l!==null&&l===r)break;a.tag===5&&c!==null&&(a=c,o?(l=em(n,i),l!=null&&s.unshift(am(n,l,a))):o||(l=em(n,i),l!=null&&s.push(am(n,l,a)))),n=n.return}s.length!==0&&e.push({event:t,listeners:s})}var _ie=/\r\n?/g,Cie=/\u0000|\uFFFD/g;function Kj(e){return(typeof e=="string"?e:""+e).replace(_ie,` +`).replace(Cie,"")}function Iv(e,t,n){if(t=Kj(t),Kj(e)!==t&&n)throw Error(Re(425))}function Q0(){}var DN=null,FN=null;function $N(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var zN=typeof setTimeout=="function"?setTimeout:void 0,Eie=typeof clearTimeout=="function"?clearTimeout:void 0,Xj=typeof Promise=="function"?Promise:void 0,Nie=typeof queueMicrotask=="function"?queueMicrotask:typeof Xj<"u"?function(e){return Xj.resolve(null).then(e).catch(Rie)}:zN;function Rie(e){setTimeout(function(){throw e})}function M1(e,t){var n=t,r=0;do{var o=n.nextSibling;if(e.removeChild(n),o&&o.nodeType===8)if(n=o.data,n==="/$"){if(r===0){e.removeChild(o),rm(t);return}r--}else n!=="$"&&n!=="$?"&&n!=="$!"||r++;n=o}while(n);rm(t)}function rc(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function Zj(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"){if(t===0)return e;t--}else n==="/$"&&t++}e=e.previousSibling}return null}var Yf=Math.random().toString(36).slice(2),Ys="__reactFiber$"+Yf,lm="__reactProps$"+Yf,Za="__reactContainer$"+Yf,HN="__reactEvents$"+Yf,Tie="__reactListeners$"+Yf,kie="__reactHandles$"+Yf;function uu(e){var t=eYs;if(t)return t;for(var n=e.parentNode;n;){if(t=nZa||nYs){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=Zj(e);e!==null;){if(n=eYs)return n;e=Zj(e)}return t}e=n,n=e.parentNode}return null}function qm(e){return e=eYs||eZa,!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Hd(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(Re(33))}function gx(e){return elm||null}var BN=,Bd=-1;function bc(e){return{current:e}}function Nn(e){0>Bd||(e.current=BNBd,BNBd=null,Bd--)}function xn(e,t){Bd++,BNBd=e.current,e.current=t}var fc={},eo=bc(fc),ko=bc(!1),Tu=fc;function vf(e,t){var n=e.type.contextTypes;if(!n)return fc;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o={},i;for(i in n)oi=ti;return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function Po(e){return e=e.childContextTypes,e!=null}function J0(){Nn(ko),Nn(eo)}function Qj(e,t,n){if(eo.current!==fc)throw Error(Re(168));xn(eo,t),xn(ko,n)}function cH(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var o in r)if(!(o in t))throw Error(Re(108,hoe(e)||"Unknown",o));return zn({},n,r)}function ey(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||fc,Tu=eo.current,xn(eo,e),xn(ko,ko.current),!0}function Jj(e,t,n){var r=e.stateNode;if(!r)throw Error(Re(169));n?(e=cH(e,t,Tu),r.__reactInternalMemoizedMergedChildContext=e,Nn(ko),Nn(eo),xn(eo,e)):Nn(ko),xn(ko,n)}var Fa=null,vx=!1,j1=!1;function uH(e){Fa===null?Fa=e:Fa.push(e)}function Pie(e){vx=!0,uH(e)}function wc(){if(!j1&&Fa!==null){j1=!0;var e=0,t=on;try{var n=Fa;for(on=1;e<n.length;e++){var r=ne;do r=r(!0);while(r!==null)}Fa=null,vx=!1}catch(o){throw Fa!==null&&(Fa=Fa.slice(e+1)),L6(zT,wc),o}finally{on=t,j1=!1}}return null}var Wd=,Ud=0,ty=null,ny=0,Ri=,Ti=0,ku=null,$a=1,za="";function nu(e,t){WdUd++=ny,WdUd++=ty,ty=e,ny=t}function dH(e,t,n){RiTi++=$a,RiTi++=za,RiTi++=ku,ku=e;var r=$a;e=za;var o=32-cs(r)-1;r&=~(1<<o),n+=1;var i=32-cs(t)+o;if(30<i){var s=o-o%5;i=(r&(1<<s)-1).toString(32),r>>=s,o-=s,$a=1<<32-cs(t)+o|n<<o|r,za=i+e}else $a=1<<i|n<<o|r,za=e}function KT(e){e.return!==null&&(nu(e,1),dH(e,1,0))}function XT(e){for(;e===ty;)ty=Wd--Ud,WdUd=null,ny=Wd--Ud,WdUd=null;for(;e===ku;)ku=Ri--Ti,RiTi=null,za=Ri--Ti,RiTi=null,$a=Ri--Ti,RiTi=null}var Jo=null,Qo=null,Mn=!1,ns=null;function fH(e,t){var n=ki(5,null,null,0);n.elementType="DELETED",n.stateNode=t,n.return=e,t=e.deletions,t===null?(e.deletions=n,e.flags|=16):t.push(n)}function eL(e,t){switch(e.tag){case 5:var n=e.type;return t=t.nodeType!==1||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t,t!==null?(e.stateNode=t,Jo=e,Qo=rc(t.firstChild),!0):!1;case 6:return t=e.pendingProps===""||t.nodeType!==3?null:t,t!==null?(e.stateNode=t,Jo=e,Qo=null,!0):!1;case 13:return t=t.nodeType!==8?null:t,t!==null?(n=ku!==null?{id:$a,overflow:za}:null,e.memoizedState={dehydrated:t,treeContext:n,retryLane:1073741824},n=ki(18,null,null,0),n.stateNode=t,n.return=e,e.child=n,Jo=e,Qo=null,!0):!1;default:return!1}}function WN(e){return(e.mode&1)!==0&&(e.flags&128)===0}function UN(e){if(Mn){var t=Qo;if(t){var n=t;if(!eL(e,t)){if(WN(e))throw Error(Re(418));t=rc(n.nextSibling);var r=Jo;t&&eL(e,t)?fH(r,n):(e.flags=e.flags&-4097|2,Mn=!1,Jo=e)}}else{if(WN(e))throw Error(Re(418));e.flags=e.flags&-4097|2,Mn=!1,Jo=e}}}function tL(e){for(e=e.return;e!==null&&e.tag!==5&&e.tag!==3&&e.tag!==13;)e=e.return;Jo=e}function Av(e){if(e!==Jo)return!1;if(!Mn)return tL(e),Mn=!0,!1;var t;if((t=e.tag!==3)&&!(t=e.tag!==5)&&(t=e.type,t=t!=="head"&&t!=="body"&&!$N(e.type,e.memoizedProps)),t&&(t=Qo)){if(WN(e))throw hH(),Error(Re(418));for(;t;)fH(e,t),t=rc(t.nextSibling)}if(tL(e),e.tag===13){if(e=e.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(Re(317));e:{for(e=e.nextSibling,t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="/$"){if(t===0){Qo=rc(e.nextSibling);break e}t--}else n!=="$"&&n!=="$!"&&n!=="$?"||t++}e=e.nextSibling}Qo=null}}else Qo=Jo?rc(e.stateNode.nextSibling):null;return!0}function hH(){for(var e=Qo;e;)e=rc(e.nextSibling)}function yf(){Qo=Jo=null,Mn=!1}function ZT(e){ns===null?ns=e:ns.push(e)}var Iie=sl.ReactCurrentBatchConfig;function tp(e,t,n){if(e=n.ref,e!==null&&typeof e!="function"&&typeof e!="object"){if(n._owner){if(n=n._owner,n){if(n.tag!==1)throw Error(Re(309));var r=n.stateNode}if(!r)throw Error(Re(147,e));var o=r,i=""+e;return t!==null&&t.ref!==null&&typeof t.ref=="function"&&t.ref._stringRef===i?t.ref:(t=function(s){var a=o.refs;s===null?delete ai:ai=s},t._stringRef=i,t)}if(typeof e!="string")throw Error(Re(284));if(!n._owner)throw Error(Re(290,e))}return e}function Mv(e,t){throw e=Object.prototype.toString.call(t),Error(Re(31,e==="object Object"?"object with keys {"+Object.keys(t).join(", ")+"}":e))}function nL(e){var t=e._init;return t(e._payload)}function pH(e){function t(x,w){if(e){var C=x.deletions;C===null?(x.deletions=w,x.flags|=16):C.push(w)}}function n(x,w){if(!e)return null;for(;w!==null;)t(x,w),w=w.sibling;return null}function r(x,w){for(x=new Map;w!==null;)w.key!==null?x.set(w.key,w):x.set(w.index,w),w=w.sibling;return x}function o(x,w){return x=ac(x,w),x.index=0,x.sibling=null,x}function i(x,w,C){return x.index=C,e?(C=x.alternate,C!==null?(C=C.index,C<w?(x.flags|=2,w):C):(x.flags|=2,w)):(x.flags|=1048576,w)}function s(x){return e&&x.alternate===null&&(x.flags|=2),x}function a(x,w,C,E){return w===null||w.tag!==6?(w=H1(C,x.mode,E),w.return=x,w):(w=o(w,C),w.return=x,w)}function l(x,w,C,E){var R=C.type;return R===Dd?d(x,w,C.props.children,E,C.key):w!==null&&(w.elementType===R||typeof R=="object"&&R!==null&&R.$$typeof===ql&&nL(R)===w.type)?(E=o(w,C.props),E.ref=tp(x,w,C),E.return=x,E):(E=N0(C.type,C.key,C.props,null,x.mode,E),E.ref=tp(x,w,C),E.return=x,E)}function c(x,w,C,E){return w===null||w.tag!==4||w.stateNode.containerInfo!==C.containerInfo||w.stateNode.implementation!==C.implementation?(w=B1(C,x.mode,E),w.return=x,w):(w=o(w,C.children||),w.return=x,w)}function d(x,w,C,E,R){return w===null||w.tag!==7?(w=wu(C,x.mode,E,R),w.return=x,w):(w=o(w,C),w.return=x,w)}function f(x,w,C){if(typeof w=="string"&&w!==""||typeof w=="number")return w=H1(""+w,x.mode,C),w.return=x,w;if(typeof w=="object"&&w!==null){switch(w.$$typeof){case Sv:return C=N0(w.type,w.key,w.props,null,x.mode,C),C.ref=tp(x,null,w),C.return=x,C;case Od:return w=B1(w,x.mode,C),w.return=x,w;case ql:var E=w._init;return f(x,E(w._payload),C)}if(pp(w)||Xh(w))return w=wu(w,x.mode,C,null),w.return=x,w;Mv(x,w)}return null}function p(x,w,C,E){var R=w!==null?w.key:null;if(typeof C=="string"&&C!==""||typeof C=="number")return R!==null?null:a(x,w,""+C,E);if(typeof C=="object"&&C!==null){switch(C.$$typeof){case Sv:return C.key===R?l(x,w,C,E):null;case Od:return C.key===R?c(x,w,C,E):null;case ql:return R=C._init,p(x,w,R(C._payload),E)}if(pp(C)||Xh(C))return R!==null?null:d(x,w,C,E,null);Mv(x,C)}return null}function g(x,w,C,E,R){if(typeof E=="string"&&E!==""||typeof E=="number")return x=x.get(C)||null,a(w,x,""+E,R);if(typeof E=="object"&&E!==null){switch(E.$$typeof){case Sv:return x=x.get(E.key===null?C:E.key)||null,l(w,x,E,R);case Od:return x=x.get(E.key===null?C:E.key)||null,c(w,x,E,R);case ql:var P=E._init;return g(x,w,C,P(E._payload),R)}if(pp(E)||Xh(E))return x=x.get(C)||null,d(w,x,E,R,null);Mv(w,E)}return null}function v(x,w,C,E){for(var R=null,P=null,N=w,k=w=0,I=null;N!==null&&k<C.length;k++){N.index>k?(I=N,N=null):I=N.sibling;var O=p(x,N,Ck,E);if(O===null){N===null&&(N=I);break}e&&N&&O.alternate===null&&t(x,N),w=i(O,w,k),P===null?R=O:P.sibling=O,P=O,N=I}if(k===C.length)return n(x,N),Mn&&nu(x,k),R;if(N===null){for(;k<C.length;k++)N=f(x,Ck,E),N!==null&&(w=i(N,w,k),P===null?R=N:P.sibling=N,P=N);return Mn&&nu(x,k),R}for(N=r(x,N);k<C.length;k++)I=g(N,x,k,Ck,E),I!==null&&(e&&I.alternate!==null&&N.delete(I.key===null?k:I.key),w=i(I,w,k),P===null?R=I:P.sibling=I,P=I);return e&&N.forEach(function(j){return t(x,j)}),Mn&&nu(x,k),R}function b(x,w,C,E){var R=Xh(C);if(typeof R!="function")throw Error(Re(150));if(C=R.call(C),C==null)throw Error(Re(151));for(var P=R=null,N=w,k=w=0,I=null,O=C.next();N!==null&&!O.done;k++,O=C.next()){N.index>k?(I=N,N=null):I=N.sibling;var j=p(x,N,O.value,E);if(j===null){N===null&&(N=I);break}e&&N&&j.alternate===null&&t(x,N),w=i(j,w,k),P===null?R=j:P.sibling=j,P=j,N=I}if(O.done)return n(x,N),Mn&&nu(x,k),R;if(N===null){for(;!O.done;k++,O=C.next())O=f(x,O.value,E),O!==null&&(w=i(O,w,k),P===null?R=O:P.sibling=O,P=O);return Mn&&nu(x,k),R}for(N=r(x,N);!O.done;k++,O=C.next())O=g(N,x,k,O.value,E),O!==null&&(e&&O.alternate!==null&&N.delete(O.key===null?k:O.key),w=i(O,w,k),P===null?R=O:P.sibling=O,P=O);return e&&N.forEach(function(H){return t(x,H)}),Mn&&nu(x,k),R}function _(x,w,C,E){if(typeof C=="object"&&C!==null&&C.type===Dd&&C.key===null&&(C=C.props.children),typeof C=="object"&&C!==null){switch(C.$$typeof){case Sv:e:{for(var R=C.key,P=w;P!==null;){if(P.key===R){if(R=C.type,R===Dd){if(P.tag===7){n(x,P.sibling),w=o(P,C.props.children),w.return=x,x=w;break e}}else if(P.elementType===R||typeof R=="object"&&R!==null&&R.$$typeof===ql&&nL(R)===P.type){n(x,P.sibling),w=o(P,C.props),w.ref=tp(x,P,C),w.return=x,x=w;break e}n(x,P);break}else t(x,P);P=P.sibling}C.type===Dd?(w=wu(C.props.children,x.mode,E,C.key),w.return=x,x=w):(E=N0(C.type,C.key,C.props,null,x.mode,E),E.ref=tp(x,w,C),E.return=x,x=E)}return s(x);case Od:e:{for(P=C.key;w!==null;){if(w.key===P)if(w.tag===4&&w.stateNode.containerInfo===C.containerInfo&&w.stateNode.implementation===C.implementation){n(x,w.sibling),w=o(w,C.children||),w.return=x,x=w;break e}else{n(x,w);break}else t(x,w);w=w.sibling}w=B1(C,x.mode,E),w.return=x,x=w}return s(x);case ql:return P=C._init,_(x,w,P(C._payload),E)}if(pp(C))return v(x,w,C,E);if(Xh(C))return b(x,w,C,E);Mv(x,C)}return typeof C=="string"&&C!==""||typeof C=="number"?(C=""+C,w!==null&&w.tag===6?(n(x,w.sibling),w=o(w,C),w.return=x,x=w):(n(x,w),w=H1(C,x.mode,E),w.return=x,x=w),s(x)):n(x,w)}return _}var xf=pH(!0),mH=pH(!1),ry=bc(null),oy=null,qd=null,QT=null;function JT(){QT=qd=oy=null}function ek(e){var t=ry.current;Nn(ry),e._currentValue=t}function qN(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function rf(e,t){oy=e,QT=qd=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(No=!0),e.firstContext=null)}function Li(e){var t=e._currentValue;if(QT!==e)if(e={context:e,memoizedValue:t,next:null},qd===null){if(oy===null)throw Error(Re(308));qd=e,oy.dependencies={lanes:0,firstContext:e}}else qd=qd.next=e;return t}var du=null;function tk(e){du===null?du=e:du.push(e)}function gH(e,t,n,r){var o=t.interleaved;return o===null?(n.next=n,tk(t)):(n.next=o.next,o.next=n),t.interleaved=n,Qa(e,r)}function Qa(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Gl=!1;function nk(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function vH(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function qa(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function oc(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Vt&2){var o=r.pending;return o===null?t.next=t:(t.next=o.next,o.next=t),r.pending=t,Qa(e,n)}return o=r.interleaved,o===null?(t.next=t,tk(r)):(t.next=o.next,o.next=t),r.interleaved=t,Qa(e,n)}function b0(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,HT(e,n)}}function rL(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var o=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?o=i=s:i=i.next=s,n=n.next}while(n!==null);i===null?o=i=t:i=i.next=t}else o=i=t;n={baseState:r.baseState,firstBaseUpdate:o,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function iy(e,t,n,r){var o=e.updateQueue;Gl=!1;var i=o.firstBaseUpdate,s=o.lastBaseUpdate,a=o.shared.pending;if(a!==null){o.shared.pending=null;var l=a,c=l.next;l.next=null,s===null?i=c:s.next=c,s=l;var d=e.alternate;d!==null&&(d=d.updateQueue,a=d.lastBaseUpdate,a!==s&&(a===null?d.firstBaseUpdate=c:a.next=c,d.lastBaseUpdate=l))}if(i!==null){var f=o.baseState;s=0,d=c=l=null,a=i;do{var p=a.lane,g=a.eventTime;if((r&p)===p){d!==null&&(d=d.next={eventTime:g,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var v=e,b=a;switch(p=t,g=n,b.tag){case 1:if(v=b.payload,typeof v=="function"){f=v.call(g,f,p);break e}f=v;break e;case 3:v.flags=v.flags&-65537|128;case 0:if(v=b.payload,p=typeof v=="function"?v.call(g,f,p):v,p==null)break e;f=zn({},f,p);break e;case 2:Gl=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,p=o.effects,p===null?o.effects=a:p.push(a))}else g={eventTime:g,lane:p,tag:a.tag,payload:a.payload,callback:a.callback,next:null},d===null?(c=d=g,l=f):d=d.next=g,s|=p;if(a=a.next,a===null){if(a=o.shared.pending,a===null)break;p=a,a=p.next,p.next=null,o.lastBaseUpdate=p,o.shared.pending=null}}while(!0);if(d===null&&(l=f),o.baseState=l,o.firstBaseUpdate=c,o.lastBaseUpdate=d,t=o.shared.interleaved,t!==null){o=t;do s|=o.lane,o=o.next;while(o!==t)}else i===null&&(o.shared.lanes=0);Iu|=s,e.lanes=s,e.memoizedState=f}}function oL(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;t<e.length;t++){var r=et,o=r.callback;if(o!==null){if(r.callback=null,r=n,typeof o!="function")throw Error(Re(191,o));o.call(r)}}}var Gm={},ra=bc(Gm),cm=bc(Gm),um=bc(Gm);function fu(e){if(e===Gm)throw Error(Re(174));return e}function rk(e,t){switch(xn(um,t),xn(cm,e),xn(ra,Gm),e=t.nodeType,e){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:EN(null,"");break;default:e=e===8?t.parentNode:t,t=e.namespaceURI||null,e=e.tagName,t=EN(t,e)}Nn(ra),xn(ra,t)}function bf(){Nn(ra),Nn(cm),Nn(um)}function yH(e){fu(um.current);var t=fu(ra.current),n=EN(t,e.type);t!==n&&(xn(cm,e),xn(ra,n))}function ok(e){cm.current===e&&(Nn(ra),Nn(cm))}var Fn=bc(0);function sy(e){for(var t=e;t!==null;){if(t.tag===13){var n=t.memoizedState;if(n!==null&&(n=n.dehydrated,n===null||n.data==="$?"||n.data==="$!"))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if(t.flags&128)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var L1=;function ik(){for(var e=0;e<L1.length;e++)L1e._workInProgressVersionPrimary=null;L1.length=0}var w0=sl.ReactCurrentDispatcher,O1=sl.ReactCurrentBatchConfig,Pu=0,$n=null,gr=null,Er=null,ay=!1,$p=!1,dm=0,Aie=0;function qr(){throw Error(Re(321))}function sk(e,t){if(t===null)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!hs(en,tn))return!1;return!0}function ak(e,t,n,r,o,i){if(Pu=i,$n=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,w0.current=e===null||e.memoizedState===null?Oie:Die,e=n(r,o),$p){i=0;do{if($p=!1,dm=0,25<=i)throw Error(Re(301));i+=1,Er=gr=null,t.updateQueue=null,w0.current=Fie,e=n(r,o)}while($p)}if(w0.current=ly,t=gr!==null&&gr.next!==null,Pu=0,Er=gr=$n=null,ay=!1,t)throw Error(Re(300));return e}function lk(){var e=dm!==0;return dm=0,e}function Us(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return Er===null?$n.memoizedState=Er=e:Er=Er.next=e,Er}function Oi(){if(gr===null){var e=$n.alternate;e=e!==null?e.memoizedState:null}else e=gr.next;var t=Er===null?$n.memoizedState:Er.next;if(t!==null)Er=t,gr=e;else{if(e===null)throw Error(Re(310));gr=e,e={memoizedState:gr.memoizedState,baseState:gr.baseState,baseQueue:gr.baseQueue,queue:gr.queue,next:null},Er===null?$n.memoizedState=Er=e:Er=Er.next=e}return Er}function fm(e,t){return typeof t=="function"?t(e):t}function D1(e){var t=Oi(),n=t.queue;if(n===null)throw Error(Re(311));n.lastRenderedReducer=e;var r=gr,o=r.baseQueue,i=n.pending;if(i!==null){if(o!==null){var s=o.next;o.next=i.next,i.next=s}r.baseQueue=o=i,n.pending=null}if(o!==null){i=o.next,r=r.baseState;var a=s=null,l=null,c=i;do{var d=c.lane;if((Pu&d)===d)l!==null&&(l=l.next={lane:0,action:c.action,hasEagerState:c.hasEagerState,eagerState:c.eagerState,next:null}),r=c.hasEagerState?c.eagerState:e(r,c.action);else{var f={lane:d,action:c.action,hasEagerState:c.hasEagerState,eagerState:c.eagerState,next:null};l===null?(a=l=f,s=r):l=l.next=f,$n.lanes|=d,Iu|=d}c=c.next}while(c!==null&&c!==i);l===null?s=r:l.next=a,hs(r,t.memoizedState)||(No=!0),t.memoizedState=r,t.baseState=s,t.baseQueue=l,n.lastRenderedState=r}if(e=n.interleaved,e!==null){o=e;do i=o.lane,$n.lanes|=i,Iu|=i,o=o.next;while(o!==e)}else o===null&&(n.lanes=0);returnt.memoizedState,n.dispatch}function F1(e){var t=Oi(),n=t.queue;if(n===null)throw Error(Re(311));n.lastRenderedReducer=e;var r=n.dispatch,o=n.pending,i=t.memoizedState;if(o!==null){n.pending=null;var s=o=o.next;do i=e(i,s.action),s=s.next;while(s!==o);hs(i,t.memoizedState)||(No=!0),t.memoizedState=i,t.baseQueue===null&&(t.baseState=i),n.lastRenderedState=i}returni,r}function xH(){}function bH(e,t){var n=$n,r=Oi(),o=t(),i=!hs(r.memoizedState,o);if(i&&(r.memoizedState=o,No=!0),r=r.queue,ck(_H.bind(null,n,r,e),e),r.getSnapshot!==t||i||Er!==null&&Er.memoizedState.tag&1){if(n.flags|=2048,hm(9,SH.bind(null,n,r,o,t),void 0,null),Nr===null)throw Error(Re(349));Pu&30||wH(n,t,o)}return o}function wH(e,t,n){e.flags|=16384,e={getSnapshot:t,value:n},t=$n.updateQueue,t===null?(t={lastEffect:null,stores:null},$n.updateQueue=t,t.stores=e):(n=t.stores,n===null?t.stores=e:n.push(e))}function SH(e,t,n,r){t.value=n,t.getSnapshot=r,CH(t)&&EH(e)}function _H(e,t,n){return n(function(){CH(t)&&EH(e)})}function CH(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!hs(e,n)}catch{return!0}}function EH(e){var t=Qa(e,1);t!==null&&us(t,e,1,-1)}function iL(e){var t=Us();return typeof e=="function"&&(e=e()),t.memoizedState=t.baseState=e,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:fm,lastRenderedState:e},t.queue=e,e=e.dispatch=Lie.bind(null,$n,e),t.memoizedState,e}function hm(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},t=$n.updateQueue,t===null?(t={lastEffect:null,stores:null},$n.updateQueue=t,t.lastEffect=e.next=e):(n=t.lastEffect,n===null?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e)),e}function NH(){return Oi().memoizedState}function S0(e,t,n,r){var o=Us();$n.flags|=e,o.memoizedState=hm(1|t,n,void 0,r===void 0?null:r)}function yx(e,t,n,r){var o=Oi();r=r===void 0?null:r;var i=void 0;if(gr!==null){var s=gr.memoizedState;if(i=s.destroy,r!==null&&sk(r,s.deps)){o.memoizedState=hm(t,n,i,r);return}}$n.flags|=e,o.memoizedState=hm(1|t,n,i,r)}function sL(e,t){return S0(8390656,8,e,t)}function ck(e,t){return yx(2048,8,e,t)}function RH(e,t){return yx(4,2,e,t)}function TH(e,t){return yx(4,4,e,t)}function kH(e,t){if(typeof t=="function")return e=e(),t(e),function(){t(null)};if(t!=null)return e=e(),t.current=e,function(){t.current=null}}function PH(e,t,n){return n=n!=null?n.concat(e):null,yx(4,4,kH.bind(null,t,e),n)}function uk(){}function IH(e,t){var n=Oi();t=t===void 0?null:t;var r=n.memoizedState;return r!==null&&t!==null&&sk(t,r1)?r0:(n.memoizedState=e,t,e)}function AH(e,t){var n=Oi();t=t===void 0?null:t;var r=n.memoizedState;return r!==null&&t!==null&&sk(t,r1)?r0:(e=e(),n.memoizedState=e,t,e)}function MH(e,t,n){return Pu&21?(hs(n,t)||(n=F6(),$n.lanes|=n,Iu|=n,e.baseState=!0),t):(e.baseState&&(e.baseState=!1,No=!0),e.memoizedState=n)}function Mie(e,t){var n=on;on=n!==0&&4>n?n:4,e(!0);var r=O1.transition;O1.transition={};try{e(!1),t()}finally{on=n,O1.transition=r}}function jH(){return Oi().memoizedState}function jie(e,t,n){var r=sc(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},LH(e))OH(t,n);else if(n=gH(e,t,n,r),n!==null){var o=ho();us(n,e,r,o),DH(n,t,r)}}function Lie(e,t,n){var r=sc(e),o={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(LH(e))OH(t,o);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var s=t.lastRenderedState,a=i(s,n);if(o.hasEagerState=!0,o.eagerState=a,hs(a,s)){var l=t.interleaved;l===null?(o.next=o,tk(t)):(o.next=l.next,l.next=o),t.interleaved=o;return}}catch{}finally{}n=gH(e,t,o,r),n!==null&&(o=ho(),us(n,e,r,o),DH(n,t,r))}}function LH(e){var t=e.alternate;return e===$n||t!==null&&t===$n}function OH(e,t){$p=ay=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function DH(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,HT(e,n)}}var ly={readContext:Li,useCallback:qr,useContext:qr,useEffect:qr,useImperativeHandle:qr,useInsertionEffect:qr,useLayoutEffect:qr,useMemo:qr,useReducer:qr,useRef:qr,useState:qr,useDebugValue:qr,useDeferredValue:qr,useTransition:qr,useMutableSource:qr,useSyncExternalStore:qr,useId:qr,unstable_isNewReconciler:!1},Oie={readContext:Li,useCallback:function(e,t){return Us().memoizedState=e,t===void 0?null:t,e},useContext:Li,useEffect:sL,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat(e):null,S0(4194308,4,kH.bind(null,t,e),n)},useLayoutEffect:function(e,t){return S0(4194308,4,e,t)},useInsertionEffect:function(e,t){return S0(4,2,e,t)},useMemo:function(e,t){var n=Us();return t=t===void 0?null:t,e=e(),n.memoizedState=e,t,e},useReducer:function(e,t,n){var r=Us();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=jie.bind(null,$n,e),r.memoizedState,e},useRef:function(e){var t=Us();return e={current:e},t.memoizedState=e},useState:iL,useDebugValue:uk,useDeferredValue:function(e){return Us().memoizedState=e},useTransition:function(){var e=iL(!1),t=e0;return e=Mie.bind(null,e1),Us().memoizedState=e,t,e},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=$n,o=Us();if(Mn){if(n===void 0)throw Error(Re(407));n=n()}else{if(n=t(),Nr===null)throw Error(Re(349));Pu&30||wH(r,t,n)}o.memoizedState=n;var i={value:n,getSnapshot:t};return o.queue=i,sL(_H.bind(null,r,i,e),e),r.flags|=2048,hm(9,SH.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=Us(),t=Nr.identifierPrefix;if(Mn){var n=za,r=$a;n=(r&~(1<<32-cs(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=dm++,0<n&&(t+="H"+n.toString(32)),t+=":"}else n=Aie++,t=":"+t+"r"+n.toString(32)+":";return e.memoizedState=t},unstable_isNewReconciler:!1},Die={readContext:Li,useCallback:IH,useContext:Li,useEffect:ck,useImperativeHandle:PH,useInsertionEffect:RH,useLayoutEffect:TH,useMemo:AH,useReducer:D1,useRef:NH,useState:function(){return D1(fm)},useDebugValue:uk,useDeferredValue:function(e){var t=Oi();return MH(t,gr.memoizedState,e)},useTransition:function(){var e=D1(fm)0,t=Oi().memoizedState;returne,t},useMutableSource:xH,useSyncExternalStore:bH,useId:jH,unstable_isNewReconciler:!1},Fie={readContext:Li,useCallback:IH,useContext:Li,useEffect:ck,useImperativeHandle:PH,useInsertionEffect:RH,useLayoutEffect:TH,useMemo:AH,useReducer:F1,useRef:NH,useState:function(){return F1(fm)},useDebugValue:uk,useDeferredValue:function(e){var t=Oi();return gr===null?t.memoizedState=e:MH(t,gr.memoizedState,e)},useTransition:function(){var e=F1(fm)0,t=Oi().memoizedState;returne,t},useMutableSource:xH,useSyncExternalStore:bH,useId:jH,unstable_isNewReconciler:!1};function Qi(e,t){if(e&&e.defaultProps){t=zn({},t),e=e.defaultProps;for(var n in e)tn===void 0&&(tn=en);return t}return t}function GN(e,t,n,r){t=e.memoizedState,n=n(r,t),n=n==null?t:zn({},t,n),e.memoizedState=n,e.lanes===0&&(e.updateQueue.baseState=n)}var xx={isMounted:function(e){return(e=e._reactInternals)?Wu(e)===e:!1},enqueueSetState:function(e,t,n){e=e._reactInternals;var r=ho(),o=sc(e),i=qa(r,o);i.payload=t,n!=null&&(i.callback=n),t=oc(e,i,o),t!==null&&(us(t,e,o,r),b0(t,e,o))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=ho(),o=sc(e),i=qa(r,o);i.tag=1,i.payload=t,n!=null&&(i.callback=n),t=oc(e,i,o),t!==null&&(us(t,e,o,r),b0(t,e,o))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=ho(),r=sc(e),o=qa(n,r);o.tag=2,t!=null&&(o.callback=t),t=oc(e,o,r),t!==null&&(us(t,e,r,n),b0(t,e,r))}};function aL(e,t,n,r,o,i,s){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(r,i,s):t.prototype&&t.prototype.isPureReactComponent?!im(n,r)||!im(o,i):!0}function FH(e,t,n){var r=!1,o=fc,i=t.contextType;return typeof i=="object"&&i!==null?i=Li(i):(o=Po(t)?Tu:eo.current,r=t.contextTypes,i=(r=r!=null)?vf(e,o):fc),t=new t(n,i),e.memoizedState=t.state!==null&&t.state!==void 0?t.state:null,t.updater=xx,e.stateNode=t,t._reactInternals=e,r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=o,e.__reactInternalMemoizedMaskedChildContext=i),t}function lL(e,t,n,r){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(n,r),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&xx.enqueueReplaceState(t,t.state,null)}function VN(e,t,n,r){var o=e.stateNode;o.props=n,o.state=e.memoizedState,o.refs={},nk(e);var i=t.contextType;typeof i=="object"&&i!==null?o.context=Li(i):(i=Po(t)?Tu:eo.current,o.context=vf(e,i)),o.state=e.memoizedState,i=t.getDerivedStateFromProps,typeof i=="function"&&(GN(e,t,i,n),o.state=e.memoizedState),typeof t.getDerivedStateFromProps=="function"||typeof o.getSnapshotBeforeUpdate=="function"||typeof o.UNSAFE_componentWillMount!="function"&&typeof o.componentWillMount!="function"||(t=o.state,typeof o.componentWillMount=="function"&&o.componentWillMount(),typeof o.UNSAFE_componentWillMount=="function"&&o.UNSAFE_componentWillMount(),t!==o.state&&xx.enqueueReplaceState(o,o.state,null),iy(e,n,o,r),o.state=e.memoizedState),typeof o.componentDidMount=="function"&&(e.flags|=4194308)}function wf(e,t){try{var n="",r=t;do n+=foe(r),r=r.return;while(r);var o=n}catch(i){o=` +Error generating stack: `+i.message+` +`+i.stack}return{value:e,source:t,stack:o,digest:null}}function $1(e,t,n){return{value:e,source:null,stack:n??null,digest:t??null}}function YN(e,t){try{console.error(t.value)}catch(n){setTimeout(function(){throw n})}}var $ie=typeof WeakMap=="function"?WeakMap:Map;function $H(e,t,n){n=qa(-1,n),n.tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){uy||(uy=!0,oR=r),YN(e,t)},n}function zH(e,t,n){n=qa(-1,n),n.tag=3;var r=e.type.getDerivedStateFromError;if(typeof r=="function"){var o=t.value;n.payload=function(){return r(o)},n.callback=function(){YN(e,t)}}var i=e.stateNode;return i!==null&&typeof i.componentDidCatch=="function"&&(n.callback=function(){YN(e,t),typeof r!="function"&&(ic===null?ic=new Set(this):ic.add(this));var s=t.stack;this.componentDidCatch(t.value,{componentStack:s!==null?s:""})}),n}function cL(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new $ie;var o=new Set;r.set(t,o)}else o=r.get(t),o===void 0&&(o=new Set,r.set(t,o));o.has(n)||(o.add(n),e=Jie.bind(null,e,t,n),t.then(e,e))}function uL(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function dL(e,t,n,r,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(t=qa(-1,1),t.tag=2,oc(n,t,1))),n.lanes|=1),e)}var zie=sl.ReactCurrentOwner,No=!1;function uo(e,t,n,r){t.child=e===null?mH(t,null,n,r):xf(t,e.child,n,r)}function fL(e,t,n,r,o){n=n.render;var i=t.ref;return rf(t,o),r=ak(e,t,n,r,i,o),n=lk(),e!==null&&!No?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Ja(e,t,o)):(Mn&&n&&KT(t),t.flags|=1,uo(e,t,r,o),t.child)}function hL(e,t,n,r,o){if(e===null){var i=n.type;return typeof i=="function"&&!yk(i)&&i.defaultProps===void 0&&n.compare===null&&n.defaultProps===void 0?(t.tag=15,t.type=i,HH(e,t,i,r,o)):(e=N0(n.type,null,r,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(i=e.child,!(e.lanes&o)){var s=i.memoizedProps;if(n=n.compare,n=n!==null?n:im,n(s,r)&&e.ref===t.ref)return Ja(e,t,o)}return t.flags|=1,e=ac(i,r),e.ref=t.ref,e.return=t,t.child=e}function HH(e,t,n,r,o){if(e!==null){var i=e.memoizedProps;if(im(i,r)&&e.ref===t.ref)if(No=!1,t.pendingProps=r=i,(e.lanes&o)!==0)e.flags&131072&&(No=!0);else return t.lanes=e.lanes,Ja(e,t,o)}return KN(e,t,n,r,o)}function BH(e,t,n){var r=t.pendingProps,o=r.children,i=e!==null?e.memoizedState:null;if(r.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},xn(Vd,qo),qo|=n;else{if(!(n&1073741824))return e=i!==null?i.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,xn(Vd,qo),qo|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},r=i!==null?i.baseLanes:n,xn(Vd,qo),qo|=r}else i!==null?(r=i.baseLanes|n,t.memoizedState=null):r=n,xn(Vd,qo),qo|=r;return uo(e,t,o,n),t.child}function WH(e,t){var n=t.ref;(e===null&&n!==null||e!==null&&e.ref!==n)&&(t.flags|=512,t.flags|=2097152)}function KN(e,t,n,r,o){var i=Po(n)?Tu:eo.current;return i=vf(t,i),rf(t,o),n=ak(e,t,n,r,i,o),r=lk(),e!==null&&!No?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Ja(e,t,o)):(Mn&&r&&KT(t),t.flags|=1,uo(e,t,n,o),t.child)}function pL(e,t,n,r,o){if(Po(n)){var i=!0;ey(t)}else i=!1;if(rf(t,o),t.stateNode===null)_0(e,t),FH(t,n,r),VN(t,n,r,o),r=!0;else if(e===null){var s=t.stateNode,a=t.memoizedProps;s.props=a;var l=s.context,c=n.contextType;typeof c=="object"&&c!==null?c=Li(c):(c=Po(n)?Tu:eo.current,c=vf(t,c));var d=n.getDerivedStateFromProps,f=typeof d=="function"||typeof s.getSnapshotBeforeUpdate=="function";f||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(a!==r||l!==c)&&lL(t,s,r,c),Gl=!1;var p=t.memoizedState;s.state=p,iy(t,r,s,o),l=t.memoizedState,a!==r||p!==l||ko.current||Gl?(typeof d=="function"&&(GN(t,n,d,r),l=t.memoizedState),(a=Gl||aL(t,n,a,r,p,l,c))?(f||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(t.flags|=4194308)):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=l),s.props=r,s.state=l,s.context=c,r=a):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),r=!1)}else{s=t.stateNode,vH(e,t),a=t.memoizedProps,c=t.type===t.elementType?a:Qi(t.type,a),s.props=c,f=t.pendingProps,p=s.context,l=n.contextType,typeof l=="object"&&l!==null?l=Li(l):(l=Po(n)?Tu:eo.current,l=vf(t,l));var g=n.getDerivedStateFromProps;(d=typeof g=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(a!==f||p!==l)&&lL(t,s,r,l),Gl=!1,p=t.memoizedState,s.state=p,iy(t,r,s,o);var v=t.memoizedState;a!==f||p!==v||ko.current||Gl?(typeof g=="function"&&(GN(t,n,g,r),v=t.memoizedState),(c=Gl||aL(t,n,c,r,p,v,l)||!1)?(d||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(r,v,l),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(r,v,l)),typeof s.componentDidUpdate=="function"&&(t.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof s.componentDidUpdate!="function"||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=v),s.props=r,s.state=v,s.context=l,r=c):(typeof s.componentDidUpdate!="function"||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=1024),r=!1)}return XN(e,t,n,r,i,o)}function XN(e,t,n,r,o,i){WH(e,t);var s=(t.flags&128)!==0;if(!r&&!s)return o&&Jj(t,n,!1),Ja(e,t,i);r=t.stateNode,zie.current=t;var a=s&&typeof n.getDerivedStateFromError!="function"?null:r.render();return t.flags|=1,e!==null&&s?(t.child=xf(t,e.child,null,i),t.child=xf(t,null,a,i)):uo(e,t,a,i),t.memoizedState=r.state,o&&Jj(t,n,!0),t.child}function UH(e){var t=e.stateNode;t.pendingContext?Qj(e,t.pendingContext,t.pendingContext!==t.context):t.context&&Qj(e,t.context,!1),rk(e,t.containerInfo)}function mL(e,t,n,r,o){return yf(),ZT(o),t.flags|=256,uo(e,t,n,r),t.child}var ZN={dehydrated:null,treeContext:null,retryLane:0};function QN(e){return{baseLanes:e,cachePool:null,transitions:null}}function qH(e,t,n){var r=t.pendingProps,o=Fn.current,i=!1,s=(t.flags&128)!==0,a;if((a=s)||(a=e!==null&&e.memoizedState===null?!1:(o&2)!==0),a?(i=!0,t.flags&=-129):(e===null||e.memoizedState!==null)&&(o|=1),xn(Fn,o&1),e===null)return UN(t),e=t.memoizedState,e!==null&&(e=e.dehydrated,e!==null)?(t.mode&1?e.data==="$!"?t.lanes=8:t.lanes=1073741824:t.lanes=1,null):(s=r.children,e=r.fallback,i?(r=t.mode,i=t.child,s={mode:"hidden",children:s},!(r&1)&&i!==null?(i.childLanes=0,i.pendingProps=s):i=Sx(s,r,0,null),e=wu(e,r,n,null),i.return=t,e.return=t,i.sibling=e,t.child=i,t.child.memoizedState=QN(n),t.memoizedState=ZN,e):dk(t,s));if(o=e.memoizedState,o!==null&&(a=o.dehydrated,a!==null))return Hie(e,t,s,r,a,o,n);if(i){i=r.fallback,s=t.mode,o=e.child,a=o.sibling;var l={mode:"hidden",children:r.children};return!(s&1)&&t.child!==o?(r=t.child,r.childLanes=0,r.pendingProps=l,t.deletions=null):(r=ac(o,l),r.subtreeFlags=o.subtreeFlags&14680064),a!==null?i=ac(a,i):(i=wu(i,s,n,null),i.flags|=2),i.return=t,r.return=t,r.sibling=i,t.child=r,r=i,i=t.child,s=e.child.memoizedState,s=s===null?QN(n):{baseLanes:s.baseLanes|n,cachePool:null,transitions:s.transitions},i.memoizedState=s,i.childLanes=e.childLanes&~n,t.memoizedState=ZN,r}return i=e.child,e=i.sibling,r=ac(i,{mode:"visible",children:r.children}),!(t.mode&1)&&(r.lanes=n),r.return=t,r.sibling=null,e!==null&&(n=t.deletions,n===null?(t.deletions=e,t.flags|=16):n.push(e)),t.child=r,t.memoizedState=null,r}function dk(e,t){return t=Sx({mode:"visible",children:t},e.mode,0,null),t.return=e,e.child=t}function jv(e,t,n,r){return r!==null&&ZT(r),xf(t,e.child,null,n),e=dk(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function Hie(e,t,n,r,o,i,s){if(n)return t.flags&256?(t.flags&=-257,r=$1(Error(Re(422))),jv(e,t,s,r)):t.memoizedState!==null?(t.child=e.child,t.flags|=128,null):(i=r.fallback,o=t.mode,r=Sx({mode:"visible",children:r.children},o,0,null),i=wu(i,o,s,null),i.flags|=2,r.return=t,i.return=t,r.sibling=i,t.child=r,t.mode&1&&xf(t,e.child,null,s),t.child.memoizedState=QN(s),t.memoizedState=ZN,i);if(!(t.mode&1))return jv(e,t,s,null);if(o.data==="$!"){if(r=o.nextSibling&&o.nextSibling.dataset,r)var a=r.dgst;return r=a,i=Error(Re(419)),r=$1(i,r,void 0),jv(e,t,s,r)}if(a=(s&e.childLanes)!==0,No||a){if(r=Nr,r!==null){switch(s&-s){case 4:o=2;break;case 16:o=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:o=32;break;case 536870912:o=268435456;break;default:o=0}o=o&(r.suspendedLanes|s)?0:o,o!==0&&o!==i.retryLane&&(i.retryLane=o,Qa(e,o),us(r,e,o,-1))}return vk(),r=$1(Error(Re(421))),jv(e,t,s,r)}return o.data==="$?"?(t.flags|=128,t.child=e.child,t=ese.bind(null,e),o._reactRetry=t,null):(e=i.treeContext,Qo=rc(o.nextSibling),Jo=t,Mn=!0,ns=null,e!==null&&(RiTi++=$a,RiTi++=za,RiTi++=ku,$a=e.id,za=e.overflow,ku=t),t=dk(t,r.children),t.flags|=4096,t)}function gL(e,t,n){e.lanes|=t;var r=e.alternate;r!==null&&(r.lanes|=t),qN(e.return,t,n)}function z1(e,t,n,r,o){var i=e.memoizedState;i===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:o}:(i.isBackwards=t,i.rendering=null,i.renderingStartTime=0,i.last=r,i.tail=n,i.tailMode=o)}function GH(e,t,n){var r=t.pendingProps,o=r.revealOrder,i=r.tail;if(uo(e,t,r.children,n),r=Fn.current,r&2)r=r&1|2,t.flags|=128;else{if(e!==null&&e.flags&128)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&gL(e,n,t);else if(e.tag===19)gL(e,n,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(xn(Fn,r),!(t.mode&1))t.memoizedState=null;else switch(o){case"forwards":for(n=t.child,o=null;n!==null;)e=n.alternate,e!==null&&sy(e)===null&&(o=n),n=n.sibling;n=o,n===null?(o=t.child,t.child=null):(o=n.sibling,n.sibling=null),z1(t,!1,o,n,i);break;case"backwards":for(n=null,o=t.child,t.child=null;o!==null;){if(e=o.alternate,e!==null&&sy(e)===null){t.child=o;break}e=o.sibling,o.sibling=n,n=o,o=e}z1(t,!0,n,null,i);break;case"together":z1(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function _0(e,t){!(t.mode&1)&&e!==null&&(e.alternate=null,t.alternate=null,t.flags|=2)}function Ja(e,t,n){if(e!==null&&(t.dependencies=e.dependencies),Iu|=t.lanes,!(n&t.childLanes))return null;if(e!==null&&t.child!==e.child)throw Error(Re(153));if(t.child!==null){for(e=t.child,n=ac(e,e.pendingProps),t.child=n,n.return=t;e.sibling!==null;)e=e.sibling,n=n.sibling=ac(e,e.pendingProps),n.return=t;n.sibling=null}return t.child}function Bie(e,t,n){switch(t.tag){case 3:UH(t),yf();break;case 5:yH(t);break;case 1:Po(t.type)&&ey(t);break;case 4:rk(t,t.stateNode.containerInfo);break;case 10:var r=t.type._context,o=t.memoizedProps.value;xn(ry,r._currentValue),r._currentValue=o;break;case 13:if(r=t.memoizedState,r!==null)return r.dehydrated!==null?(xn(Fn,Fn.current&1),t.flags|=128,null):n&t.child.childLanes?qH(e,t,n):(xn(Fn,Fn.current&1),e=Ja(e,t,n),e!==null?e.sibling:null);xn(Fn,Fn.current&1);break;case 19:if(r=(n&t.childLanes)!==0,e.flags&128){if(r)return GH(e,t,n);t.flags|=128}if(o=t.memoizedState,o!==null&&(o.rendering=null,o.tail=null,o.lastEffect=null),xn(Fn,Fn.current),r)break;return null;case 22:case 23:return t.lanes=0,BH(e,t,n)}return Ja(e,t,n)}var VH,JN,YH,KH;VH=function(e,t){for(var n=t.child;n!==null;){if(n.tag===5||n.tag===6)e.appendChild(n.stateNode);else if(n.tag!==4&&n.child!==null){n.child.return=n,n=n.child;continue}if(n===t)break;for(;n.sibling===null;){if(n.return===null||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}};JN=function(){};YH=function(e,t,n,r){var o=e.memoizedProps;if(o!==r){e=t.stateNode,fu(ra.current);var i=null;switch(n){case"input":o=wN(e,o),r=wN(e,r),i=;break;case"select":o=zn({},o,{value:void 0}),r=zn({},r,{value:void 0}),i=;break;case"textarea":o=CN(e,o),r=CN(e,r),i=;break;default:typeof o.onClick!="function"&&typeof r.onClick=="function"&&(e.onclick=Q0)}NN(n,r);var s;n=null;for(c in o)if(!r.hasOwnProperty(c)&&o.hasOwnProperty(c)&&oc!=null)if(c==="style"){var a=oc;for(s in a)a.hasOwnProperty(s)&&(n||(n={}),ns="")}else c!=="dangerouslySetInnerHTML"&&c!=="children"&&c!=="suppressContentEditableWarning"&&c!=="suppressHydrationWarning"&&c!=="autoFocus"&&(Qp.hasOwnProperty(c)?i||(i=):(i=i||).push(c,null));for(c in r){var l=rc;if(a=o!=null?oc:void 0,r.hasOwnProperty(c)&&l!==a&&(l!=null||a!=null))if(c==="style")if(a){for(s in a)!a.hasOwnProperty(s)||l&&l.hasOwnProperty(s)||(n||(n={}),ns="");for(s in l)l.hasOwnProperty(s)&&as!==ls&&(n||(n={}),ns=ls)}else n||(i||(i=),i.push(c,n)),n=l;else c==="dangerouslySetInnerHTML"?(l=l?l.__html:void 0,a=a?a.__html:void 0,l!=null&&a!==l&&(i=i||).push(c,l)):c==="children"?typeof l!="string"&&typeof l!="number"||(i=i||).push(c,""+l):c!=="suppressContentEditableWarning"&&c!=="suppressHydrationWarning"&&(Qp.hasOwnProperty(c)?(l!=null&&c==="onScroll"&&wn("scroll",e),i||a===l||(i=)):(i=i||).push(c,l))}n&&(i=i||).push("style",n);var c=i;(t.updateQueue=c)&&(t.flags|=4)}};KH=function(e,t,n,r){n!==r&&(t.flags|=4)};function np(e,t){if(!Mn)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;t!==null;)t.alternate!==null&&(n=t),t=t.sibling;n===null?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;n!==null;)n.alternate!==null&&(r=n),n=n.sibling;r===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:r.sibling=null}}function Gr(e){var t=e.alternate!==null&&e.alternate.child===e.child,n=0,r=0;if(t)for(var o=e.child;o!==null;)n|=o.lanes|o.childLanes,r|=o.subtreeFlags&14680064,r|=o.flags&14680064,o.return=e,o=o.sibling;else for(o=e.child;o!==null;)n|=o.lanes|o.childLanes,r|=o.subtreeFlags,r|=o.flags,o.return=e,o=o.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function Wie(e,t,n){var r=t.pendingProps;switch(XT(t),t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Gr(t),null;case 1:return Po(t.type)&&J0(),Gr(t),null;case 3:return r=t.stateNode,bf(),Nn(ko),Nn(eo),ik(),r.pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),(e===null||e.child===null)&&(Av(t)?t.flags|=4:e===null||e.memoizedState.isDehydrated&&!(t.flags&256)||(t.flags|=1024,ns!==null&&(aR(ns),ns=null))),JN(e,t),Gr(t),null;case 5:ok(t);var o=fu(um.current);if(n=t.type,e!==null&&t.stateNode!=null)YH(e,t,n,r,o),e.ref!==t.ref&&(t.flags|=512,t.flags|=2097152);else{if(!r){if(t.stateNode===null)throw Error(Re(166));return Gr(t),null}if(e=fu(ra.current),Av(t)){r=t.stateNode,n=t.type;var i=t.memoizedProps;switch(rYs=t,rlm=i,e=(t.mode&1)!==0,n){case"dialog":wn("cancel",r),wn("close",r);break;case"iframe":case"object":case"embed":wn("load",r);break;case"video":case"audio":for(o=0;o<gp.length;o++)wn(gpo,r);break;case"source":wn("error",r);break;case"img":case"image":case"link":wn("error",r),wn("load",r);break;case"details":wn("toggle",r);break;case"input":Ej(r,i),wn("invalid",r);break;case"select":r._wrapperState={wasMultiple:!!i.multiple},wn("invalid",r);break;case"textarea":Rj(r,i),wn("invalid",r)}NN(n,i),o=null;for(var s in i)if(i.hasOwnProperty(s)){var a=is;s==="children"?typeof a=="string"?r.textContent!==a&&(i.suppressHydrationWarning!==!0&&Iv(r.textContent,a,e),o="children",a):typeof a=="number"&&r.textContent!==""+a&&(i.suppressHydrationWarning!==!0&&Iv(r.textContent,a,e),o="children",""+a):Qp.hasOwnProperty(s)&&a!=null&&s==="onScroll"&&wn("scroll",r)}switch(n){case"input":_v(r),Nj(r,i,!0);break;case"textarea":_v(r),Tj(r);break;case"select":case"option":break;default:typeof i.onClick=="function"&&(r.onclick=Q0)}r=o,t.updateQueue=r,r!==null&&(t.flags|=4)}else{s=o.nodeType===9?o:o.ownerDocument,e==="http://www.w3.org/1999/xhtml"&&(e=_6(n)),e==="http://www.w3.org/1999/xhtml"?n==="script"?(e=s.createElement("div"),e.innerHTML="<script><\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),eYs=t,elm=r,VH(e,t,!1,!1),t.stateNode=e;e:{switch(s=RN(n,r),n){case"dialog":wn("cancel",e),wn("close",e),o=r;break;case"iframe":case"object":case"embed":wn("load",e),o=r;break;case"video":case"audio":for(o=0;o<gp.length;o++)wn(gpo,e);o=r;break;case"source":wn("error",e),o=r;break;case"img":case"image":case"link":wn("error",e),wn("load",e),o=r;break;case"details":wn("toggle",e),o=r;break;case"input":Ej(e,r),o=wN(e,r),wn("invalid",e);break;case"option":o=r;break;case"select":e._wrapperState={wasMultiple:!!r.multiple},o=zn({},r,{value:void 0}),wn("invalid",e);break;case"textarea":Rj(e,r),o=CN(e,r),wn("invalid",e);break;default:o=r}NN(n,o),a=o;for(i in a)if(a.hasOwnProperty(i)){var l=ai;i==="style"?N6(e,l):i==="dangerouslySetInnerHTML"?(l=l?l.__html:void 0,l!=null&&C6(e,l)):i==="children"?typeof l=="string"?(n!=="textarea"||l!=="")&&Jp(e,l):typeof l=="number"&&Jp(e,""+l):i!=="suppressContentEditableWarning"&&i!=="suppressHydrationWarning"&&i!=="autoFocus"&&(Qp.hasOwnProperty(i)?l!=null&&i==="onScroll"&&wn("scroll",e):l!=null&<(e,i,l,s))}switch(n){case"input":_v(e),Nj(e,r,!1);break;case"textarea":_v(e),Tj(e);break;case"option":r.value!=null&&e.setAttribute("value",""+dc(r.value));break;case"select":e.multiple=!!r.multiple,i=r.value,i!=null?Jd(e,!!r.multiple,i,!1):r.defaultValue!=null&&Jd(e,!!r.multiple,r.defaultValue,!0);break;default:typeof o.onClick=="function"&&(e.onclick=Q0)}switch(n){case"button":case"input":case"select":case"textarea":r=!!r.autoFocus;break e;case"img":r=!0;break e;default:r=!1}}r&&(t.flags|=4)}t.ref!==null&&(t.flags|=512,t.flags|=2097152)}return Gr(t),null;case 6:if(e&&t.stateNode!=null)KH(e,t,e.memoizedProps,r);else{if(typeof r!="string"&&t.stateNode===null)throw Error(Re(166));if(n=fu(um.current),fu(ra.current),Av(t)){if(r=t.stateNode,n=t.memoizedProps,rYs=t,(i=r.nodeValue!==n)&&(e=Jo,e!==null))switch(e.tag){case 3:Iv(r.nodeValue,n,(e.mode&1)!==0);break;case 5:e.memoizedProps.suppressHydrationWarning!==!0&&Iv(r.nodeValue,n,(e.mode&1)!==0)}i&&(t.flags|=4)}else r=(n.nodeType===9?n:n.ownerDocument).createTextNode(r),rYs=t,t.stateNode=r}return Gr(t),null;case 13:if(Nn(Fn),r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(Mn&&Qo!==null&&t.mode&1&&!(t.flags&128))hH(),yf(),t.flags|=98560,i=!1;else if(i=Av(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(Re(318));if(i=t.memoizedState,i=i!==null?i.dehydrated:null,!i)throw Error(Re(317));iYs=t}else yf(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Gr(t),i=!1}else ns!==null&&(aR(ns),ns=null),i=!0;if(!i)return t.flags&65536?t:null}return t.flags&128?(t.lanes=n,t):(r=r!==null,r!==(e!==null&&e.memoizedState!==null)&&r&&(t.child.flags|=8192,t.mode&1&&(e===null||Fn.current&1?xr===0&&(xr=3):vk())),t.updateQueue!==null&&(t.flags|=4),Gr(t),null);case 4:return bf(),JN(e,t),e===null&&sm(t.stateNode.containerInfo),Gr(t),null;case 10:return ek(t.type._context),Gr(t),null;case 17:return Po(t.type)&&J0(),Gr(t),null;case 19:if(Nn(Fn),i=t.memoizedState,i===null)return Gr(t),null;if(r=(t.flags&128)!==0,s=i.rendering,s===null)if(r)np(i,!1);else{if(xr!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(s=sy(e),s!==null){for(t.flags|=128,np(i,!1),r=s.updateQueue,r!==null&&(t.updateQueue=r,t.flags|=4),t.subtreeFlags=0,r=n,n=t.child;n!==null;)i=n,e=r,i.flags&=14680066,s=i.alternate,s===null?(i.childLanes=0,i.lanes=e,i.child=null,i.subtreeFlags=0,i.memoizedProps=null,i.memoizedState=null,i.updateQueue=null,i.dependencies=null,i.stateNode=null):(i.childLanes=s.childLanes,i.lanes=s.lanes,i.child=s.child,i.subtreeFlags=0,i.deletions=null,i.memoizedProps=s.memoizedProps,i.memoizedState=s.memoizedState,i.updateQueue=s.updateQueue,i.type=s.type,e=s.dependencies,i.dependencies=e===null?null:{lanes:e.lanes,firstContext:e.firstContext}),n=n.sibling;return xn(Fn,Fn.current&1|2),t.child}e=e.sibling}i.tail!==null&&Qn()>Sf&&(t.flags|=128,r=!0,np(i,!1),t.lanes=4194304)}else{if(!r)if(e=sy(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),np(i,!0),i.tail===null&&i.tailMode==="hidden"&&!s.alternate&&!Mn)return Gr(t),null}else 2*Qn()-i.renderingStartTime>Sf&&n!==1073741824&&(t.flags|=128,r=!0,np(i,!1),t.lanes=4194304);i.isBackwards?(s.sibling=t.child,t.child=s):(n=i.last,n!==null?n.sibling=s:t.child=s,i.last=s)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=Qn(),t.sibling=null,n=Fn.current,xn(Fn,r?n&1|2:n&1),t):(Gr(t),null);case 22:case 23:return gk(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?qo&1073741824&&(Gr(t),t.subtreeFlags&6&&(t.flags|=8192)):Gr(t),null;case 24:return null;case 25:return null}throw Error(Re(156,t.tag))}function Uie(e,t){switch(XT(t),t.tag){case 1:return Po(t.type)&&J0(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return bf(),Nn(ko),Nn(eo),ik(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return ok(t),null;case 13:if(Nn(Fn),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(Re(340));yf()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Nn(Fn),null;case 4:return bf(),null;case 10:return ek(t.type._context),null;case 22:case 23:return gk(),null;case 24:return null;default:return null}}var Lv=!1,Kr=!1,qie=typeof WeakSet=="function"?WeakSet:Set,He=null;function Gd(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Vn(e,t,r)}else n.current=null}function eR(e,t,n){try{n()}catch(r){Vn(e,t,r)}}var vL=!1;function Gie(e,t){if(DN=K0,e=eH(),YT(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var o=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var s=0,a=-1,l=-1,c=0,d=0,f=e,p=null;t:for(;;){for(var g;f!==n||o!==0&&f.nodeType!==3||(a=s+o),f!==i||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(g=f.firstChild)!==null;)p=f,f=g;for(;;){if(f===e)break t;if(p===n&&++c===o&&(a=s),p===i&&++d===r&&(l=s),(g=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=g}n=a===-1||l===-1?null:{start:a,end:l}}else n=null}n=n||{start:0,end:0}}else n=null;for(FN={focusedElem:e,selectionRange:n},K0=!1,He=t;He!==null;)if(t=He,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,He=e;else for(;He!==null;){t=He;try{var v=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(v!==null){var b=v.memoizedProps,_=v.memoizedState,x=t.stateNode,w=x.getSnapshotBeforeUpdate(t.elementType===t.type?b:Qi(t.type,b),_);x.__reactInternalSnapshotBeforeUpdate=w}break;case 3:var C=t.stateNode.containerInfo;C.nodeType===1?C.textContent="":C.nodeType===9&&C.documentElement&&C.removeChild(C.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(Re(163))}}catch(E){Vn(t,t.return,E)}if(e=t.sibling,e!==null){e.return=t.return,He=e;break}He=t.return}return v=vL,vL=!1,v}function zp(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var o=r=r.next;do{if((o.tag&e)===e){var i=o.destroy;o.destroy=void 0,i!==void 0&&eR(t,n,i)}o=o.next}while(o!==r)}}function bx(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function tR(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function XH(e){var t=e.alternate;t!==null&&(e.alternate=null,XH(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete tYs,delete tlm,delete tHN,delete tTie,delete tkie)),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function ZH(e){return e.tag===5||e.tag===3||e.tag===4}function yL(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||ZH(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function nR(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Q0));else if(r!==4&&(e=e.child,e!==null))for(nR(e,t,n),e=e.sibling;e!==null;)nR(e,t,n),e=e.sibling}function rR(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(rR(e,t,n),e=e.sibling;e!==null;)rR(e,t,n),e=e.sibling}var Mr=null,Ji=!1;function Ol(e,t,n){for(n=n.child;n!==null;)QH(e,t,n),n=n.sibling}function QH(e,t,n){if(na&&typeof na.onCommitFiberUnmount=="function")try{na.onCommitFiberUnmount(fx,n)}catch{}switch(n.tag){case 5:Kr||Gd(n,t);case 6:var r=Mr,o=Ji;Mr=null,Ol(e,t,n),Mr=r,Ji=o,Mr!==null&&(Ji?(e=Mr,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Mr.removeChild(n.stateNode));break;case 18:Mr!==null&&(Ji?(e=Mr,n=n.stateNode,e.nodeType===8?M1(e.parentNode,n):e.nodeType===1&&M1(e,n),rm(e)):M1(Mr,n.stateNode));break;case 4:r=Mr,o=Ji,Mr=n.stateNode.containerInfo,Ji=!0,Ol(e,t,n),Mr=r,Ji=o;break;case 0:case 11:case 14:case 15:if(!Kr&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){o=r=r.next;do{var i=o,s=i.destroy;i=i.tag,s!==void 0&&(i&2||i&4)&&eR(n,t,s),o=o.next}while(o!==r)}Ol(e,t,n);break;case 1:if(!Kr&&(Gd(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){Vn(n,t,a)}Ol(e,t,n);break;case 21:Ol(e,t,n);break;case 22:n.mode&1?(Kr=(r=Kr)||n.memoizedState!==null,Ol(e,t,n),Kr=r):Ol(e,t,n);break;default:Ol(e,t,n)}}function xL(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new qie),t.forEach(function(r){var o=tse.bind(null,e,r);n.has(r)||(n.add(r),r.then(o,o))})}}function Ki(e,t){var n=t.deletions;if(n!==null)for(var r=0;r<n.length;r++){var o=nr;try{var i=e,s=t,a=s;e:for(;a!==null;){switch(a.tag){case 5:Mr=a.stateNode,Ji=!1;break e;case 3:Mr=a.stateNode.containerInfo,Ji=!0;break e;case 4:Mr=a.stateNode.containerInfo,Ji=!0;break e}a=a.return}if(Mr===null)throw Error(Re(160));QH(i,s,o),Mr=null,Ji=!1;var l=o.alternate;l!==null&&(l.return=null),o.return=null}catch(c){Vn(o,t,c)}}if(t.subtreeFlags&12854)for(t=t.child;t!==null;)JH(t,e),t=t.sibling}function JH(e,t){var n=e.alternate,r=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:if(Ki(t,e),Hs(e),r&4){try{zp(3,e,e.return),bx(3,e)}catch(b){Vn(e,e.return,b)}try{zp(5,e,e.return)}catch(b){Vn(e,e.return,b)}}break;case 1:Ki(t,e),Hs(e),r&512&&n!==null&&Gd(n,n.return);break;case 5:if(Ki(t,e),Hs(e),r&512&&n!==null&&Gd(n,n.return),e.flags&32){var o=e.stateNode;try{Jp(o,"")}catch(b){Vn(e,e.return,b)}}if(r&4&&(o=e.stateNode,o!=null)){var i=e.memoizedProps,s=n!==null?n.memoizedProps:i,a=e.type,l=e.updateQueue;if(e.updateQueue=null,l!==null)try{a==="input"&&i.type==="radio"&&i.name!=null&&w6(o,i),RN(a,s);var c=RN(a,i);for(s=0;s<l.length;s+=2){var d=ls,f=ls+1;d==="style"?N6(o,f):d==="dangerouslySetInnerHTML"?C6(o,f):d==="children"?Jp(o,f):LT(o,d,f,c)}switch(a){case"input":SN(o,i);break;case"textarea":S6(o,i);break;case"select":var p=o._wrapperState.wasMultiple;o._wrapperState.wasMultiple=!!i.multiple;var g=i.value;g!=null?Jd(o,!!i.multiple,g,!1):p!==!!i.multiple&&(i.defaultValue!=null?Jd(o,!!i.multiple,i.defaultValue,!0):Jd(o,!!i.multiple,i.multiple?:"",!1))}olm=i}catch(b){Vn(e,e.return,b)}}break;case 6:if(Ki(t,e),Hs(e),r&4){if(e.stateNode===null)throw Error(Re(162));o=e.stateNode,i=e.memoizedProps;try{o.nodeValue=i}catch(b){Vn(e,e.return,b)}}break;case 3:if(Ki(t,e),Hs(e),r&4&&n!==null&&n.memoizedState.isDehydrated)try{rm(t.containerInfo)}catch(b){Vn(e,e.return,b)}break;case 4:Ki(t,e),Hs(e);break;case 13:Ki(t,e),Hs(e),o=e.child,o.flags&8192&&(i=o.memoizedState!==null,o.stateNode.isHidden=i,!i||o.alternate!==null&&o.alternate.memoizedState!==null||(pk=Qn())),r&4&&xL(e);break;case 22:if(d=n!==null&&n.memoizedState!==null,e.mode&1?(Kr=(c=Kr)||d,Ki(t,e),Kr=c):Ki(t,e),Hs(e),r&8192){if(c=e.memoizedState!==null,(e.stateNode.isHidden=c)&&!d&&e.mode&1)for(He=e,d=e.child;d!==null;){for(f=He=d;He!==null;){switch(p=He,g=p.child,p.tag){case 0:case 11:case 14:case 15:zp(4,p,p.return);break;case 1:Gd(p,p.return);var v=p.stateNode;if(typeof v.componentWillUnmount=="function"){r=p,n=p.return;try{t=r,v.props=t.memoizedProps,v.state=t.memoizedState,v.componentWillUnmount()}catch(b){Vn(r,n,b)}}break;case 5:Gd(p,p.return);break;case 22:if(p.memoizedState!==null){wL(f);continue}}g!==null?(g.return=p,He=g):wL(f)}d=d.sibling}e:for(d=null,f=e;;){if(f.tag===5){if(d===null){d=f;try{o=f.stateNode,c?(i=o.style,typeof i.setProperty=="function"?i.setProperty("display","none","important"):i.display="none"):(a=f.stateNode,l=f.memoizedProps.style,s=l!=null&&l.hasOwnProperty("display")?l.display:null,a.style.display=E6("display",s))}catch(b){Vn(e,e.return,b)}}}else if(f.tag===6){if(d===null)try{f.stateNode.nodeValue=c?"":f.memoizedProps}catch(b){Vn(e,e.return,b)}}else if((f.tag!==22&&f.tag!==23||f.memoizedState===null||f===e)&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===e)break e;for(;f.sibling===null;){if(f.return===null||f.return===e)break e;d===f&&(d=null),f=f.return}d===f&&(d=null),f.sibling.return=f.return,f=f.sibling}}break;case 19:Ki(t,e),Hs(e),r&4&&xL(e);break;case 21:break;default:Ki(t,e),Hs(e)}}function Hs(e){var t=e.flags;if(t&2){try{e:{for(var n=e.return;n!==null;){if(ZH(n)){var r=n;break e}n=n.return}throw Error(Re(160))}switch(r.tag){case 5:var o=r.stateNode;r.flags&32&&(Jp(o,""),r.flags&=-33);var i=yL(e);rR(e,i,o);break;case 3:case 4:var s=r.stateNode.containerInfo,a=yL(e);nR(e,a,s);break;default:throw Error(Re(161))}}catch(l){Vn(e,e.return,l)}e.flags&=-3}t&4096&&(e.flags&=-4097)}function Vie(e,t,n){He=e,eB(e)}function eB(e,t,n){for(var r=(e.mode&1)!==0;He!==null;){var o=He,i=o.child;if(o.tag===22&&r){var s=o.memoizedState!==null||Lv;if(!s){var a=o.alternate,l=a!==null&&a.memoizedState!==null||Kr;a=Lv;var c=Kr;if(Lv=s,(Kr=l)&&!c)for(He=o;He!==null;)s=He,l=s.child,s.tag===22&&s.memoizedState!==null?SL(o):l!==null?(l.return=s,He=l):SL(o);for(;i!==null;)He=i,eB(i),i=i.sibling;He=o,Lv=a,Kr=c}bL(e)}else o.subtreeFlags&8772&&i!==null?(i.return=o,He=i):bL(e)}}function bL(e){for(;He!==null;){var t=He;if(t.flags&8772){var n=t.alternate;try{if(t.flags&8772)switch(t.tag){case 0:case 11:case 15:Kr||bx(5,t);break;case 1:var r=t.stateNode;if(t.flags&4&&!Kr)if(n===null)r.componentDidMount();else{var o=t.elementType===t.type?n.memoizedProps:Qi(t.type,n.memoizedProps);r.componentDidUpdate(o,n.memoizedState,r.__reactInternalSnapshotBeforeUpdate)}var i=t.updateQueue;i!==null&&oL(t,i,r);break;case 3:var s=t.updateQueue;if(s!==null){if(n=null,t.child!==null)switch(t.child.tag){case 5:n=t.child.stateNode;break;case 1:n=t.child.stateNode}oL(t,s,n)}break;case 5:var a=t.stateNode;if(n===null&&t.flags&4){n=a;var l=t.memoizedProps;switch(t.type){case"button":case"input":case"select":case"textarea":l.autoFocus&&n.focus();break;case"img":l.src&&(n.src=l.src)}}break;case 6:break;case 4:break;case 12:break;case 13:if(t.memoizedState===null){var c=t.alternate;if(c!==null){var d=c.memoizedState;if(d!==null){var f=d.dehydrated;f!==null&&rm(f)}}}break;case 19:case 17:case 21:case 22:case 23:case 25:break;default:throw Error(Re(163))}Kr||t.flags&512&&tR(t)}catch(p){Vn(t,t.return,p)}}if(t===e){He=null;break}if(n=t.sibling,n!==null){n.return=t.return,He=n;break}He=t.return}}function wL(e){for(;He!==null;){var t=He;if(t===e){He=null;break}var n=t.sibling;if(n!==null){n.return=t.return,He=n;break}He=t.return}}function SL(e){for(;He!==null;){var t=He;try{switch(t.tag){case 0:case 11:case 15:var n=t.return;try{bx(4,t)}catch(l){Vn(t,n,l)}break;case 1:var r=t.stateNode;if(typeof r.componentDidMount=="function"){var o=t.return;try{r.componentDidMount()}catch(l){Vn(t,o,l)}}var i=t.return;try{tR(t)}catch(l){Vn(t,i,l)}break;case 5:var s=t.return;try{tR(t)}catch(l){Vn(t,s,l)}}}catch(l){Vn(t,t.return,l)}if(t===e){He=null;break}var a=t.sibling;if(a!==null){a.return=t.return,He=a;break}He=t.return}}var Yie=Math.ceil,cy=sl.ReactCurrentDispatcher,fk=sl.ReactCurrentOwner,Ai=sl.ReactCurrentBatchConfig,Vt=0,Nr=null,lr=null,Or=0,qo=0,Vd=bc(0),xr=0,pm=null,Iu=0,wx=0,hk=0,Hp=null,Eo=null,pk=0,Sf=1/0,Oa=null,uy=!1,oR=null,ic=null,Ov=!1,Jl=null,dy=0,Bp=0,iR=null,C0=-1,E0=0;function ho(){return Vt&6?Qn():C0!==-1?C0:C0=Qn()}function sc(e){return e.mode&1?Vt&2&&Or!==0?Or&-Or:Iie.transition!==null?(E0===0&&(E0=F6()),E0):(e=on,e!==0||(e=window.event,e=e===void 0?16:q6(e.type)),e):1}function us(e,t,n,r){if(50<Bp)throw Bp=0,iR=null,Error(Re(185));Wm(e,n,r),(!(Vt&2)||e!==Nr)&&(e===Nr&&(!(Vt&2)&&(wx|=n),xr===4&&Kl(e,Or)),Io(e,r),n===1&&Vt===0&&!(t.mode&1)&&(Sf=Qn()+500,vx&&wc()))}function Io(e,t){var n=e.callbackNode;Ioe(e,t);var r=Y0(e,e===Nr?Or:0);if(r===0)n!==null&&Ij(n),e.callbackNode=null,e.callbackPriority=0;else if(t=r&-r,e.callbackPriority!==t){if(n!=null&&Ij(n),t===1)e.tag===0?Pie(_L.bind(null,e)):uH(_L.bind(null,e)),Nie(function(){!(Vt&6)&&wc()}),n=null;else{switch($6(r)){case 1:n=zT;break;case 4:n=O6;break;case 16:n=V0;break;case 536870912:n=D6;break;default:n=V0}n=lB(n,tB.bind(null,e))}e.callbackPriority=t,e.callbackNode=n}}function tB(e,t){if(C0=-1,E0=0,Vt&6)throw Error(Re(327));var n=e.callbackNode;if(of()&&e.callbackNode!==n)return null;var r=Y0(e,e===Nr?Or:0);if(r===0)return null;if(r&30||r&e.expiredLanes||t)t=fy(e,r);else{t=r;var o=Vt;Vt|=2;var i=rB();(Nr!==e||Or!==t)&&(Oa=null,Sf=Qn()+500,bu(e,t));do try{Zie();break}catch(a){nB(e,a)}while(!0);JT(),cy.current=i,Vt=o,lr!==null?t=0:(Nr=null,Or=0,t=xr)}if(t!==0){if(t===2&&(o=AN(e),o!==0&&(r=o,t=sR(e,o))),t===1)throw n=pm,bu(e,0),Kl(e,r),Io(e,Qn()),n;if(t===6)Kl(e,r);else{if(o=e.current.alternate,!(r&30)&&!Kie(o)&&(t=fy(e,r),t===2&&(i=AN(e),i!==0&&(r=i,t=sR(e,i))),t===1))throw n=pm,bu(e,0),Kl(e,r),Io(e,Qn()),n;switch(e.finishedWork=o,e.finishedLanes=r,t){case 0:case 1:throw Error(Re(345));case 2:ru(e,Eo,Oa);break;case 3:if(Kl(e,r),(r&130023424)===r&&(t=pk+500-Qn(),10<t)){if(Y0(e,0)!==0)break;if(o=e.suspendedLanes,(o&r)!==r){ho(),e.pingedLanes|=e.suspendedLanes&o;break}e.timeoutHandle=zN(ru.bind(null,e,Eo,Oa),t);break}ru(e,Eo,Oa);break;case 4:if(Kl(e,r),(r&4194240)===r)break;for(t=e.eventTimes,o=-1;0<r;){var s=31-cs(r);i=1<<s,s=ts,s>o&&(o=s),r&=~i}if(r=o,r=Qn()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Yie(r/1960))-r,10<r){e.timeoutHandle=zN(ru.bind(null,e,Eo,Oa),r);break}ru(e,Eo,Oa);break;case 5:ru(e,Eo,Oa);break;default:throw Error(Re(329))}}}return Io(e,Qn()),e.callbackNode===n?tB.bind(null,e):null}function sR(e,t){var n=Hp;return e.current.memoizedState.isDehydrated&&(bu(e,t).flags|=256),e=fy(e,t),e!==2&&(t=Eo,Eo=n,t!==null&&aR(t)),e}function aR(e){Eo===null?Eo=e:Eo.push.apply(Eo,e)}function Kie(e){for(var t=e;;){if(t.flags&16384){var n=t.updateQueue;if(n!==null&&(n=n.stores,n!==null))for(var r=0;r<n.length;r++){var o=nr,i=o.getSnapshot;o=o.value;try{if(!hs(i(),o))return!1}catch{return!1}}}if(n=t.child,t.subtreeFlags&16384&&n!==null)n.return=t,t=n;else{if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}function Kl(e,t){for(t&=~hk,t&=~wx,e.suspendedLanes|=t,e.pingedLanes&=~t,e=e.expirationTimes;0<t;){var n=31-cs(t),r=1<<n;en=-1,t&=~r}}function _L(e){if(Vt&6)throw Error(Re(327));of();var t=Y0(e,0);if(!(t&1))return Io(e,Qn()),null;var n=fy(e,t);if(e.tag!==0&&n===2){var r=AN(e);r!==0&&(t=r,n=sR(e,r))}if(n===1)throw n=pm,bu(e,0),Kl(e,t),Io(e,Qn()),n;if(n===6)throw Error(Re(345));return e.finishedWork=e.current.alternate,e.finishedLanes=t,ru(e,Eo,Oa),Io(e,Qn()),null}function mk(e,t){var n=Vt;Vt|=1;try{return e(t)}finally{Vt=n,Vt===0&&(Sf=Qn()+500,vx&&wc())}}function Au(e){Jl!==null&&Jl.tag===0&&!(Vt&6)&&of();var t=Vt;Vt|=1;var n=Ai.transition,r=on;try{if(Ai.transition=null,on=1,e)return e()}finally{on=r,Ai.transition=n,Vt=t,!(Vt&6)&&wc()}}function gk(){qo=Vd.current,Nn(Vd)}function bu(e,t){e.finishedWork=null,e.finishedLanes=0;var n=e.timeoutHandle;if(n!==-1&&(e.timeoutHandle=-1,Eie(n)),lr!==null)for(n=lr.return;n!==null;){var r=n;switch(XT(r),r.tag){case 1:r=r.type.childContextTypes,r!=null&&J0();break;case 3:bf(),Nn(ko),Nn(eo),ik();break;case 5:ok(r);break;case 4:bf();break;case 13:Nn(Fn);break;case 19:Nn(Fn);break;case 10:ek(r.type._context);break;case 22:case 23:gk()}n=n.return}if(Nr=e,lr=e=ac(e.current,null),Or=qo=t,xr=0,pm=null,hk=wx=Iu=0,Eo=Hp=null,du!==null){for(t=0;t<du.length;t++)if(n=dut,r=n.interleaved,r!==null){n.interleaved=null;var o=r.next,i=n.pending;if(i!==null){var s=i.next;i.next=o,r.next=s}n.pending=r}du=null}return e}function nB(e,t){do{var n=lr;try{if(JT(),w0.current=ly,ay){for(var r=$n.memoizedState;r!==null;){var o=r.queue;o!==null&&(o.pending=null),r=r.next}ay=!1}if(Pu=0,Er=gr=$n=null,$p=!1,dm=0,fk.current=null,n===null||n.return===null){xr=1,pm=t,lr=null;break}e:{var i=e,s=n.return,a=n,l=t;if(t=Or,a.flags|=32768,l!==null&&typeof l=="object"&&typeof l.then=="function"){var c=l,d=a,f=d.tag;if(!(d.mode&1)&&(f===0||f===11||f===15)){var p=d.alternate;p?(d.updateQueue=p.updateQueue,d.memoizedState=p.memoizedState,d.lanes=p.lanes):(d.updateQueue=null,d.memoizedState=null)}var g=uL(s);if(g!==null){g.flags&=-257,dL(g,s,a,i,t),g.mode&1&&cL(i,c,t),t=g,l=c;var v=t.updateQueue;if(v===null){var b=new Set;b.add(l),t.updateQueue=b}else v.add(l);break e}else{if(!(t&1)){cL(i,c,t),vk();break e}l=Error(Re(426))}}else if(Mn&&a.mode&1){var _=uL(s);if(_!==null){!(_.flags&65536)&&(_.flags|=256),dL(_,s,a,i,t),ZT(wf(l,a));break e}}i=l=wf(l,a),xr!==4&&(xr=2),Hp===null?Hp=i:Hp.push(i),i=s;do{switch(i.tag){case 3:i.flags|=65536,t&=-t,i.lanes|=t;var x=$H(i,l,t);rL(i,x);break e;case 1:a=l;var w=i.type,C=i.stateNode;if(!(i.flags&128)&&(typeof w.getDerivedStateFromError=="function"||C!==null&&typeof C.componentDidCatch=="function"&&(ic===null||!ic.has(C)))){i.flags|=65536,t&=-t,i.lanes|=t;var E=zH(i,a,t);rL(i,E);break e}}i=i.return}while(i!==null)}iB(n)}catch(R){t=R,lr===n&&n!==null&&(lr=n=n.return);continue}break}while(!0)}function rB(){var e=cy.current;return cy.current=ly,e===null?ly:e}function vk(){(xr===0||xr===3||xr===2)&&(xr=4),Nr===null||!(Iu&268435455)&&!(wx&268435455)||Kl(Nr,Or)}function fy(e,t){var n=Vt;Vt|=2;var r=rB();(Nr!==e||Or!==t)&&(Oa=null,bu(e,t));do try{Xie();break}catch(o){nB(e,o)}while(!0);if(JT(),Vt=n,cy.current=r,lr!==null)throw Error(Re(261));return Nr=null,Or=0,xr}function Xie(){for(;lr!==null;)oB(lr)}function Zie(){for(;lr!==null&&!Soe();)oB(lr)}function oB(e){var t=aB(e.alternate,e,qo);e.memoizedProps=e.pendingProps,t===null?iB(e):lr=t,fk.current=null}function iB(e){var t=e;do{var n=t.alternate;if(e=t.return,t.flags&32768){if(n=Uie(n,t),n!==null){n.flags&=32767,lr=n;return}if(e!==null)e.flags|=32768,e.subtreeFlags=0,e.deletions=null;else{xr=6,lr=null;return}}else if(n=Wie(n,t,qo),n!==null){lr=n;return}if(t=t.sibling,t!==null){lr=t;return}lr=t=e}while(t!==null);xr===0&&(xr=5)}function ru(e,t,n){var r=on,o=Ai.transition;try{Ai.transition=null,on=1,Qie(e,t,n,r)}finally{Ai.transition=o,on=r}return null}function Qie(e,t,n,r){do of();while(Jl!==null);if(Vt&6)throw Error(Re(327));n=e.finishedWork;var o=e.finishedLanes;if(n===null)return null;if(e.finishedWork=null,e.finishedLanes=0,n===e.current)throw Error(Re(177));e.callbackNode=null,e.callbackPriority=0;var i=n.lanes|n.childLanes;if(Aoe(e,i),e===Nr&&(lr=Nr=null,Or=0),!(n.subtreeFlags&2064)&&!(n.flags&2064)||Ov||(Ov=!0,lB(V0,function(){return of(),null})),i=(n.flags&15990)!==0,n.subtreeFlags&15990||i){i=Ai.transition,Ai.transition=null;var s=on;on=1;var a=Vt;Vt|=4,fk.current=null,Gie(e,n),JH(n,e),yie(FN),K0=!!DN,FN=DN=null,e.current=n,Vie(n),_oe(),Vt=a,on=s,Ai.transition=i}else e.current=n;if(Ov&&(Ov=!1,Jl=e,dy=o),i=e.pendingLanes,i===0&&(ic=null),Noe(n.stateNode),Io(e,Qn()),t!==null)for(r=e.onRecoverableError,n=0;n<t.length;n++)o=tn,r(o.value,{componentStack:o.stack,digest:o.digest});if(uy)throw uy=!1,e=oR,oR=null,e;return dy&1&&e.tag!==0&&of(),i=e.pendingLanes,i&1?e===iR?Bp++:(Bp=0,iR=e):Bp=0,wc(),null}function of(){if(Jl!==null){var e=$6(dy),t=Ai.transition,n=on;try{if(Ai.transition=null,on=16>e?16:e,Jl===null)var r=!1;else{if(e=Jl,Jl=null,dy=0,Vt&6)throw Error(Re(331));var o=Vt;for(Vt|=4,He=e.current;He!==null;){var i=He,s=i.child;if(He.flags&16){var a=i.deletions;if(a!==null){for(var l=0;l<a.length;l++){var c=al;for(He=c;He!==null;){var d=He;switch(d.tag){case 0:case 11:case 15:zp(8,d,i)}var f=d.child;if(f!==null)f.return=d,He=f;else for(;He!==null;){d=He;var p=d.sibling,g=d.return;if(XH(d),d===c){He=null;break}if(p!==null){p.return=g,He=p;break}He=g}}}var v=i.alternate;if(v!==null){var b=v.child;if(b!==null){v.child=null;do{var _=b.sibling;b.sibling=null,b=_}while(b!==null)}}He=i}}if(i.subtreeFlags&2064&&s!==null)s.return=i,He=s;else e:for(;He!==null;){if(i=He,i.flags&2048)switch(i.tag){case 0:case 11:case 15:zp(9,i,i.return)}var x=i.sibling;if(x!==null){x.return=i.return,He=x;break e}He=i.return}}var w=e.current;for(He=w;He!==null;){s=He;var C=s.child;if(s.subtreeFlags&2064&&C!==null)C.return=s,He=C;else e:for(s=w;He!==null;){if(a=He,a.flags&2048)try{switch(a.tag){case 0:case 11:case 15:bx(9,a)}}catch(R){Vn(a,a.return,R)}if(a===s){He=null;break e}var E=a.sibling;if(E!==null){E.return=a.return,He=E;break e}He=a.return}}if(Vt=o,wc(),na&&typeof na.onPostCommitFiberRoot=="function")try{na.onPostCommitFiberRoot(fx,e)}catch{}r=!0}return r}finally{on=n,Ai.transition=t}}return!1}function CL(e,t,n){t=wf(n,t),t=$H(e,t,1),e=oc(e,t,1),t=ho(),e!==null&&(Wm(e,1,t),Io(e,t))}function Vn(e,t,n){if(e.tag===3)CL(e,e,n);else for(;t!==null;){if(t.tag===3){CL(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof r.componentDidCatch=="function"&&(ic===null||!ic.has(r))){e=wf(n,e),e=zH(t,e,1),t=oc(t,e,1),e=ho(),t!==null&&(Wm(t,1,e),Io(t,e));break}}t=t.return}}function Jie(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),t=ho(),e.pingedLanes|=e.suspendedLanes&n,Nr===e&&(Or&n)===n&&(xr===4||xr===3&&(Or&130023424)===Or&&500>Qn()-pk?bu(e,0):hk|=n),Io(e,t)}function sB(e,t){t===0&&(e.mode&1?(t=Nv,Nv<<=1,!(Nv&130023424)&&(Nv=4194304)):t=1);var n=ho();e=Qa(e,t),e!==null&&(Wm(e,t,n),Io(e,n))}function ese(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),sB(e,n)}function tse(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,o=e.memoizedState;o!==null&&(n=o.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(Re(314))}r!==null&&r.delete(t),sB(e,n)}var aB;aB=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ko.current)No=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return No=!1,Bie(e,t,n);No=!!(e.flags&131072)}else No=!1,Mn&&t.flags&1048576&&dH(t,ny,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;_0(e,t),e=t.pendingProps;var o=vf(t,eo.current);rf(t,n),o=ak(null,t,r,e,o,n);var i=lk();return t.flags|=1,typeof o=="object"&&o!==null&&typeof o.render=="function"&&o.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Po(r)?(i=!0,ey(t)):i=!1,t.memoizedState=o.state!==null&&o.state!==void 0?o.state:null,nk(t),o.updater=xx,t.stateNode=o,o._reactInternals=t,VN(t,r,e,n),t=XN(null,t,r,!0,i,n)):(t.tag=0,Mn&&i&&KT(t),uo(null,t,o,n),t=t.child),t;case 16:r=t.elementType;e:{switch(_0(e,t),e=t.pendingProps,o=r._init,r=o(r._payload),t.type=r,o=t.tag=rse(r),e=Qi(r,e),o){case 0:t=KN(null,t,r,e,n);break e;case 1:t=pL(null,t,r,e,n);break e;case 11:t=fL(null,t,r,e,n);break e;case 14:t=hL(null,t,r,Qi(r.type,e),n);break e}throw Error(Re(306,r,""))}return t;case 0:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:Qi(r,o),KN(e,t,r,o,n);case 1:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:Qi(r,o),pL(e,t,r,o,n);case 3:e:{if(UH(t),e===null)throw Error(Re(387));r=t.pendingProps,i=t.memoizedState,o=i.element,vH(e,t),iy(t,r,null,n);var s=t.memoizedState;if(r=s.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){o=wf(Error(Re(423)),t),t=mL(e,t,r,n,o);break e}else if(r!==o){o=wf(Error(Re(424)),t),t=mL(e,t,r,n,o);break e}else for(Qo=rc(t.stateNode.containerInfo.firstChild),Jo=t,Mn=!0,ns=null,n=mH(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(yf(),r===o){t=Ja(e,t,n);break e}uo(e,t,r,n)}t=t.child}return t;case 5:return yH(t),e===null&&UN(t),r=t.type,o=t.pendingProps,i=e!==null?e.memoizedProps:null,s=o.children,$N(r,o)?s=null:i!==null&&$N(r,i)&&(t.flags|=32),WH(e,t),uo(e,t,s,n),t.child;case 6:return e===null&&UN(t),null;case 13:return qH(e,t,n);case 4:return rk(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=xf(t,null,r,n):uo(e,t,r,n),t.child;case 11:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:Qi(r,o),fL(e,t,r,o,n);case 7:return uo(e,t,t.pendingProps,n),t.child;case 8:return uo(e,t,t.pendingProps.children,n),t.child;case 12:return uo(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,o=t.pendingProps,i=t.memoizedProps,s=o.value,xn(ry,r._currentValue),r._currentValue=s,i!==null)if(hs(i.value,s)){if(i.children===o.children&&!ko.current){t=Ja(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var a=i.dependencies;if(a!==null){s=i.child;for(var l=a.firstContext;l!==null;){if(l.context===r){if(i.tag===1){l=qa(-1,n&-n),l.tag=2;var c=i.updateQueue;if(c!==null){c=c.shared;var d=c.pending;d===null?l.next=l:(l.next=d.next,d.next=l),c.pending=l}}i.lanes|=n,l=i.alternate,l!==null&&(l.lanes|=n),qN(i.return,n,t),a.lanes|=n;break}l=l.next}}else if(i.tag===10)s=i.type===t.type?null:i.child;else if(i.tag===18){if(s=i.return,s===null)throw Error(Re(341));s.lanes|=n,a=s.alternate,a!==null&&(a.lanes|=n),qN(s,n,t),s=i.sibling}else s=i.child;if(s!==null)s.return=i;else for(s=i;s!==null;){if(s===t){s=null;break}if(i=s.sibling,i!==null){i.return=s.return,s=i;break}s=s.return}i=s}uo(e,t,o.children,n),t=t.child}return t;case 9:return o=t.type,r=t.pendingProps.children,rf(t,n),o=Li(o),r=r(o),t.flags|=1,uo(e,t,r,n),t.child;case 14:return r=t.type,o=Qi(r,t.pendingProps),o=Qi(r.type,o),hL(e,t,r,o,n);case 15:return HH(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:Qi(r,o),_0(e,t),t.tag=1,Po(r)?(e=!0,ey(t)):e=!1,rf(t,n),FH(t,r,o),VN(t,r,o,n),XN(null,t,r,!0,e,n);case 19:return GH(e,t,n);case 22:return BH(e,t,n)}throw Error(Re(156,t.tag))};function lB(e,t){return L6(e,t)}function nse(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ki(e,t,n,r){return new nse(e,t,n,r)}function yk(e){return e=e.prototype,!(!e||!e.isReactComponent)}function rse(e){if(typeof e=="function")return yk(e)?1:0;if(e!=null){if(e=e.$$typeof,e===DT)return 11;if(e===FT)return 14}return 2}function ac(e,t){var n=e.alternate;return n===null?(n=ki(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function N0(e,t,n,r,o,i){var s=2;if(r=e,typeof e=="function")yk(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Dd:return wu(n.children,o,i,t);case OT:s=8,o|=8;break;case vN:return e=ki(12,n,t,o|2),e.elementType=vN,e.lanes=i,e;case yN:return e=ki(13,n,t,o),e.elementType=yN,e.lanes=i,e;case xN:return e=ki(19,n,t,o),e.elementType=xN,e.lanes=i,e;case y6:return Sx(n,o,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case g6:s=10;break e;case v6:s=9;break e;case DT:s=11;break e;case FT:s=14;break e;case ql:s=16,r=null;break e}throw Error(Re(130,e==null?e:typeof e,""))}return t=ki(s,n,t,o),t.elementType=e,t.type=r,t.lanes=i,t}function wu(e,t,n,r){return e=ki(7,e,r,t),e.lanes=n,e}function Sx(e,t,n,r){return e=ki(22,e,r,t),e.elementType=y6,e.lanes=n,e.stateNode={isHidden:!1},e}function H1(e,t,n){return e=ki(6,e,null,t),e.lanes=n,e}function B1(e,t,n){return t=ki(4,e.children!==null?e.children:,e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function ose(e,t,n,r,o){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=S1(0),this.expirationTimes=S1(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=S1(0),this.identifierPrefix=r,this.onRecoverableError=o,this.mutableSourceEagerHydrationData=null}function xk(e,t,n,r,o,i,s,a,l){return e=new ose(e,t,n,a,l),t===1?(t=1,i===!0&&(t|=8)):t=0,i=ki(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},nk(i),e}function ise(e,t,n){var r=3<arguments.length&&arguments3!==void 0?arguments3:null;return{$$typeof:Od,key:r==null?null:""+r,children:e,containerInfo:t,implementation:n}}function cB(e){if(!e)return fc;e=e._reactInternals;e:{if(Wu(e)!==e||e.tag!==1)throw Error(Re(170));var t=e;do{switch(t.tag){case 3:t=t.stateNode.context;break e;case 1:if(Po(t.type)){t=t.stateNode.__reactInternalMemoizedMergedChildContext;break e}}t=t.return}while(t!==null);throw Error(Re(171))}if(e.tag===1){var n=e.type;if(Po(n))return cH(e,n,t)}return t}function uB(e,t,n,r,o,i,s,a,l){return e=xk(n,r,!0,e,o,i,s,a,l),e.context=cB(null),n=e.current,r=ho(),o=sc(n),i=qa(r,o),i.callback=t??null,oc(n,i,o),e.current.lanes=o,Wm(e,o,r),Io(e,r),e}function _x(e,t,n,r){var o=t.current,i=ho(),s=sc(o);return n=cB(n),t.context===null?t.context=n:t.pendingContext=n,t=qa(i,s),t.payload={element:e},r=r===void 0?null:r,r!==null&&(t.callback=r),e=oc(o,t,s),e!==null&&(us(e,o,s,i),b0(e,o,s)),s}function hy(e){if(e=e.current,!e.child)return null;switch(e.child.tag){case 5:return e.child.stateNode;default:return e.child.stateNode}}function EL(e,t){if(e=e.memoizedState,e!==null&&e.dehydrated!==null){var n=e.retryLane;e.retryLane=n!==0&&n<t?n:t}}function bk(e,t){EL(e,t),(e=e.alternate)&&EL(e,t)}function sse(){return null}var dB=typeof reportError=="function"?reportError:function(e){console.error(e)};function wk(e){this._internalRoot=e}Cx.prototype.render=wk.prototype.render=function(e){var t=this._internalRoot;if(t===null)throw Error(Re(409));_x(e,t,null,null)};Cx.prototype.unmount=wk.prototype.unmount=function(){var e=this._internalRoot;if(e!==null){this._internalRoot=null;var t=e.containerInfo;Au(function(){_x(null,e,null,null)}),tZa=null}};function Cx(e){this._internalRoot=e}Cx.prototype.unstable_scheduleHydration=function(e){if(e){var t=B6();e={blockedOn:null,target:e,priority:t};for(var n=0;n<Yl.length&&t!==0&&t<Yln.priority;n++);Yl.splice(n,0,e),n===0&&U6(e)}};function Sk(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11)}function Ex(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11&&(e.nodeType!==8||e.nodeValue!==" react-mount-point-unstable "))}function NL(){}function ase(e,t,n,r,o){if(o){if(typeof r=="function"){var i=r;r=function(){var c=hy(s);i.call(c)}}var s=uB(t,r,e,0,null,!1,!1,"",NL);return e._reactRootContainer=s,eZa=s.current,sm(e.nodeType===8?e.parentNode:e),Au(),s}for(;o=e.lastChild;)e.removeChild(o);if(typeof r=="function"){var a=r;r=function(){var c=hy(l);a.call(c)}}var l=xk(e,0,!1,null,null,!1,!1,"",NL);return e._reactRootContainer=l,eZa=l.current,sm(e.nodeType===8?e.parentNode:e),Au(function(){_x(t,l,n,r)}),l}function Nx(e,t,n,r,o){var i=n._reactRootContainer;if(i){var s=i;if(typeof o=="function"){var a=o;o=function(){var l=hy(s);a.call(l)}}_x(t,s,e,o)}else s=ase(n,t,e,o,r);return hy(s)}z6=function(e){switch(e.tag){case 3:var t=e.stateNode;if(t.current.memoizedState.isDehydrated){var n=mp(t.pendingLanes);n!==0&&(HT(t,n|1),Io(t,Qn()),!(Vt&6)&&(Sf=Qn()+500,wc()))}break;case 13:Au(function(){var r=Qa(e,1);if(r!==null){var o=ho();us(r,e,1,o)}}),bk(e,1)}};BT=function(e){if(e.tag===13){var t=Qa(e,134217728);if(t!==null){var n=ho();us(t,e,134217728,n)}bk(e,134217728)}};H6=function(e){if(e.tag===13){var t=sc(e),n=Qa(e,t);if(n!==null){var r=ho();us(n,e,t,r)}bk(e,t)}};B6=function(){return on};W6=function(e,t){var n=on;try{return on=e,t()}finally{on=n}};kN=function(e,t,n){switch(t){case"input":if(SN(e,n),t=n.name,n.type==="radio"&&t!=null){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll("inputname="+JSON.stringify(""+t)+'type="radio"'),t=0;t<n.length;t++){var r=nt;if(r!==e&&r.form===e.form){var o=gx(r);if(!o)throw Error(Re(90));b6(r),SN(r,o)}}}break;case"textarea":S6(e,n);break;case"select":t=n.value,t!=null&&Jd(e,!!n.multiple,t,!1)}};k6=mk;P6=Au;var lse={usingClientEntryPoint:!1,Events:qm,Hd,gx,R6,T6,mk},rp={findFiberByHostInstance:uu,bundleType:0,version:"18.3.1",rendererPackageName:"react-dom"},cse={bundleType:rp.bundleType,version:rp.version,rendererPackageName:rp.rendererPackageName,rendererConfig:rp.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setErrorHandler:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:sl.ReactCurrentDispatcher,findHostInstanceByFiber:function(e){return e=M6(e),e===null?null:e.stateNode},findFiberByHostInstance:rp.findFiberByHostInstance||sse,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null,reconcilerVersion:"18.3.1-next-f1338f8080-20240426"};if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"){var Dv=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!Dv.isDisabled&&Dv.supportsFiber)try{fx=Dv.inject(cse),na=Dv}catch{}}ci.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=lse;ci.createPortal=function(e,t){var n=2<arguments.length&&arguments2!==void 0?arguments2:null;if(!Sk(t))throw Error(Re(200));return ise(e,t,null,n)};ci.createRoot=function(e,t){if(!Sk(e))throw Error(Re(299));var n=!1,r="",o=dB;return t!=null&&(t.unstable_strictMode===!0&&(n=!0),t.identifierPrefix!==void 0&&(r=t.identifierPrefix),t.onRecoverableError!==void 0&&(o=t.onRecoverableError)),t=xk(e,1,!1,null,null,n,!1,r,o),eZa=t.current,sm(e.nodeType===8?e.parentNode:e),new wk(t)};ci.findDOMNode=function(e){if(e==null)return null;if(e.nodeType===1)return e;var t=e._reactInternals;if(t===void 0)throw typeof e.render=="function"?Error(Re(188)):(e=Object.keys(e).join(","),Error(Re(268,e)));return e=M6(t),e=e===null?null:e.stateNode,e};ci.flushSync=function(e){return Au(e)};ci.hydrate=function(e,t,n){if(!Ex(t))throw Error(Re(200));return Nx(null,e,t,!0,n)};ci.hydrateRoot=function(e,t,n){if(!Sk(e))throw Error(Re(405));var r=n!=null&&n.hydratedSources||null,o=!1,i="",s=dB;if(n!=null&&(n.unstable_strictMode===!0&&(o=!0),n.identifierPrefix!==void 0&&(i=n.identifierPrefix),n.onRecoverableError!==void 0&&(s=n.onRecoverableError)),t=uB(t,null,e,1,n??null,o,!1,i,s),eZa=t.current,sm(e),r)for(e=0;e<r.length;e++)n=re,o=n._getVersion,o=o(n._source),t.mutableSourceEagerHydrationData==null?t.mutableSourceEagerHydrationData=n,o:t.mutableSourceEagerHydrationData.push(n,o);return new Cx(t)};ci.render=function(e,t,n){if(!Ex(t))throw Error(Re(200));return Nx(null,e,t,!1,n)};ci.unmountComponentAtNode=function(e){if(!Ex(e))throw Error(Re(40));return e._reactRootContainer?(Au(function(){Nx(null,null,e,!1,function(){e._reactRootContainer=null,eZa=null})}),!0):!1};ci.unstable_batchedUpdates=mk;ci.unstable_renderSubtreeIntoContainer=function(e,t,n,r){if(!Ex(n))throw Error(Re(200));if(e==null||e._reactInternals===void 0)throw Error(Re(38));return Nx(e,t,n,!1,r)};ci.version="18.3.1-next-f1338f8080-20240426";function fB(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(fB)}catch(e){console.error(e)}}fB(),f6.exports=ci;var al=f6.exports;const hB=qf(al);var RL=al;mN.createRoot=RL.createRoot,mN.hydrateRoot=RL.hydrateRoot;function tr(e){if(typeof e=="string"||typeof e=="number")return""+e;let t="";if(Array.isArray(e))for(let n=0,r;n<e.length;n++)(r=tr(en))!==""&&(t+=(t&&" ")+r);else for(let n in e)en&&(t+=(t&&" ")+n);return t}var use={value:()=>{}};function Rx(){for(var e=0,t=arguments.length,n={},r;e<t;++e){if(!(r=argumentse+"")||r in n||/\s./.test(r))throw new Error("illegal type: "+r);nr=}return new R0(n)}function R0(e){this._=e}function dse(e,t){return e.trim().split(/^|\s+/).map(function(n){var r="",o=n.indexOf(".");if(o>=0&&(r=n.slice(o+1),n=n.slice(0,o)),n&&!t.hasOwnProperty(n))throw new Error("unknown type: "+n);return{type:n,name:r}})}R0.prototype=Rx.prototype={constructor:R0,on:function(e,t){var n=this._,r=dse(e+"",n),o,i=-1,s=r.length;if(arguments.length<2){for(;++i<s;)if((o=(e=ri).type)&&(o=fse(no,e.name)))return o;return}if(t!=null&&typeof t!="function")throw new Error("invalid callback: "+t);for(;++i<s;)if(o=(e=ri).type)no=TL(no,e.name,t);else if(t==null)for(o in n)no=TL(no,e.name,null);return this},copy:function(){var e={},t=this._;for(var n in t)en=tn.slice();return new R0(e)},call:function(e,t){if((o=arguments.length-2)>0)for(var n=new Array(o),r=0,o,i;r<o;++r)nr=argumentsr+2;if(!this._.hasOwnProperty(e))throw new Error("unknown type: "+e);for(i=this._e,r=0,o=i.length;r<o;++r)ir.value.apply(t,n)},apply:function(e,t,n){if(!this._.hasOwnProperty(e))throw new Error("unknown type: "+e);for(var r=this._e,o=0,i=r.length;o<i;++o)ro.value.apply(t,n)}};function fse(e,t){for(var n=0,r=e.length,o;n<r;++n)if((o=en).name===t)return o.value}function TL(e,t,n){for(var r=0,o=e.length;r<o;++r)if(er.name===t){er=use,e=e.slice(0,r).concat(e.slice(r+1));break}return n!=null&&e.push({name:t,value:n}),e}var lR="http://www.w3.org/1999/xhtml";const kL={svg:"http://www.w3.org/2000/svg",xhtml:lR,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function Tx(e){var t=e+="",n=t.indexOf(":");return n>=0&&(t=e.slice(0,n))!=="xmlns"&&(e=e.slice(n+1)),kL.hasOwnProperty(t)?{space:kLt,local:e}:e}function hse(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return n===lR&&t.documentElement.namespaceURI===lR?t.createElement(e):t.createElementNS(n,e)}}function pse(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function pB(e){var t=Tx(e);return(t.local?pse:hse)(t)}function mse(){}function _k(e){return e==null?mse:function(){return this.querySelector(e)}}function gse(e){typeof e!="function"&&(e=_k(e));for(var t=this._groups,n=t.length,r=new Array(n),o=0;o<n;++o)for(var i=to,s=i.length,a=ro=new Array(s),l,c,d=0;d<s;++d)(l=id)&&(c=e.call(l,l.__data__,d,i))&&("__data__"in l&&(c.__data__=l.__data__),ad=c);return new si(r,this._parents)}function vse(e){return e==null?:Array.isArray(e)?e:Array.from(e)}function yse(){return}function mB(e){return e==null?yse:function(){return this.querySelectorAll(e)}}function xse(e){return function(){return vse(e.apply(this,arguments))}}function bse(e){typeof e=="function"?e=xse(e):e=mB(e);for(var t=this._groups,n=t.length,r=,o=,i=0;i<n;++i)for(var s=ti,a=s.length,l,c=0;c<a;++c)(l=sc)&&(r.push(e.call(l,l.__data__,c,s)),o.push(l));return new si(r,o)}function gB(e){return function(){return this.matches(e)}}function vB(e){return function(t){return t.matches(e)}}var wse=Array.prototype.find;function Sse(e){return function(){return wse.call(this.children,e)}}function _se(){return this.firstElementChild}function Cse(e){return this.select(e==null?_se:Sse(typeof e=="function"?e:vB(e)))}var Ese=Array.prototype.filter;function Nse(){return Array.from(this.children)}function Rse(e){return function(){return Ese.call(this.children,e)}}function Tse(e){return this.selectAll(e==null?Nse:Rse(typeof e=="function"?e:vB(e)))}function kse(e){typeof e!="function"&&(e=gB(e));for(var t=this._groups,n=t.length,r=new Array(n),o=0;o<n;++o)for(var i=to,s=i.length,a=ro=,l,c=0;c<s;++c)(l=ic)&&e.call(l,l.__data__,c,i)&&a.push(l);return new si(r,this._parents)}function yB(e){return new Array(e.length)}function Pse(){return new si(this._enter||this._groups.map(yB),this._parents)}function py(e,t){this.ownerDocument=e.ownerDocument,this.namespaceURI=e.namespaceURI,this._next=null,this._parent=e,this.__data__=t}py.prototype={constructor:py,appendChild:function(e){return this._parent.insertBefore(e,this._next)},insertBefore:function(e,t){return this._parent.insertBefore(e,t)},querySelector:function(e){return this._parent.querySelector(e)},querySelectorAll:function(e){return this._parent.querySelectorAll(e)}};function Ise(e){return function(){return e}}function Ase(e,t,n,r,o,i){for(var s=0,a,l=t.length,c=i.length;s<c;++s)(a=ts)?(a.__data__=is,rs=a):ns=new py(e,is);for(;s<l;++s)(a=ts)&&(os=a)}function Mse(e,t,n,r,o,i,s){var a,l,c=new Map,d=t.length,f=i.length,p=new Array(d),g;for(a=0;a<d;++a)(l=ta)&&(pa=g=s.call(l,l.__data__,a,t)+"",c.has(g)?oa=l:c.set(g,l));for(a=0;a<f;++a)g=s.call(e,ia,a,i)+"",(l=c.get(g))?(ra=l,l.__data__=ia,c.delete(g)):na=new py(e,ia);for(a=0;a<d;++a)(l=ta)&&c.get(pa)===l&&(oa=l)}function jse(e){return e.__data__}function Lse(e,t){if(!arguments.length)return Array.from(this,jse);var n=t?Mse:Ase,r=this._parents,o=this._groups;typeof e!="function"&&(e=Ise(e));for(var i=o.length,s=new Array(i),a=new Array(i),l=new Array(i),c=0;c<i;++c){var d=rc,f=oc,p=f.length,g=Ose(e.call(d,d&&d.__data__,c,r)),v=g.length,b=ac=new Array(v),_=sc=new Array(v),x=lc=new Array(p);n(d,f,b,_,x,g,t);for(var w=0,C=0,E,R;w<v;++w)if(E=bw){for(w>=C&&(C=w+1);!(R=_C)&&++C<v;);E._next=R||null}}return s=new si(s,r),s._enter=a,s._exit=l,s}function Ose(e){return typeof e=="object"&&"length"in e?e:Array.from(e)}function Dse(){return new si(this._exit||this._groups.map(yB),this._parents)}function Fse(e,t,n){var r=this.enter(),o=this,i=this.exit();return typeof e=="function"?(r=e(r),r&&(r=r.selection())):r=r.append(e+""),t!=null&&(o=t(o),o&&(o=o.selection())),n==null?i.remove():n(i),r&&o?r.merge(o).order():o}function $se(e){for(var t=e.selection?e.selection():e,n=this._groups,r=t._groups,o=n.length,i=r.length,s=Math.min(o,i),a=new Array(o),l=0;l<s;++l)for(var c=nl,d=rl,f=c.length,p=al=new Array(f),g,v=0;v<f;++v)(g=cv||dv)&&(pv=g);for(;l<o;++l)al=nl;return new si(a,this._parents)}function zse(){for(var e=this._groups,t=-1,n=e.length;++t<n;)for(var r=et,o=r.length-1,i=ro,s;--o>=0;)(s=ro)&&(i&&s.compareDocumentPosition(i)^4&&i.parentNode.insertBefore(s,i),i=s);return this}function Hse(e){e||(e=Bse);function t(f,p){return f&&p?e(f.__data__,p.__data__):!f-!p}for(var n=this._groups,r=n.length,o=new Array(r),i=0;i<r;++i){for(var s=ni,a=s.length,l=oi=new Array(a),c,d=0;d<a;++d)(c=sd)&&(ld=c);l.sort(t)}return new si(o,this._parents).order()}function Bse(e,t){return e<t?-1:e>t?1:e>=t?0:NaN}function Wse(){var e=arguments0;return arguments0=this,e.apply(null,arguments),this}function Use(){return Array.from(this)}function qse(){for(var e=this._groups,t=0,n=e.length;t<n;++t)for(var r=et,o=0,i=r.length;o<i;++o){var s=ro;if(s)return s}return null}function Gse(){let e=0;for(const t of this)++e;return e}function Vse(){return!this.node()}function Yse(e){for(var t=this._groups,n=0,r=t.length;n<r;++n)for(var o=tn,i=0,s=o.length,a;i<s;++i)(a=oi)&&e.call(a,a.__data__,i,o);return this}function Kse(e){return function(){this.removeAttribute(e)}}function Xse(e){return function(){this.removeAttributeNS(e.space,e.local)}}function Zse(e,t){return function(){this.setAttribute(e,t)}}function Qse(e,t){return function(){this.setAttributeNS(e.space,e.local,t)}}function Jse(e,t){return function(){var n=t.apply(this,arguments);n==null?this.removeAttribute(e):this.setAttribute(e,n)}}function eae(e,t){return function(){var n=t.apply(this,arguments);n==null?this.removeAttributeNS(e.space,e.local):this.setAttributeNS(e.space,e.local,n)}}function tae(e,t){var n=Tx(e);if(arguments.length<2){var r=this.node();return n.local?r.getAttributeNS(n.space,n.local):r.getAttribute(n)}return this.each((t==null?n.local?Xse:Kse:typeof t=="function"?n.local?eae:Jse:n.local?Qse:Zse)(n,t))}function xB(e){return e.ownerDocument&&e.ownerDocument.defaultView||e.document&&e||e.defaultView}function nae(e){return function(){this.style.removeProperty(e)}}function rae(e,t,n){return function(){this.style.setProperty(e,t,n)}}function oae(e,t,n){return function(){var r=t.apply(this,arguments);r==null?this.style.removeProperty(e):this.style.setProperty(e,r,n)}}function iae(e,t,n){return arguments.length>1?this.each((t==null?nae:typeof t=="function"?oae:rae)(e,t,n??"")):_f(this.node(),e)}function _f(e,t){return e.style.getPropertyValue(t)||xB(e).getComputedStyle(e,null).getPropertyValue(t)}function sae(e){return function(){delete thise}}function aae(e,t){return function(){thise=t}}function lae(e,t){return function(){var n=t.apply(this,arguments);n==null?delete thise:thise=n}}function cae(e,t){return arguments.length>1?this.each((t==null?sae:typeof t=="function"?lae:aae)(e,t)):this.node()e}function bB(e){return e.trim().split(/^|\s+/)}function Ck(e){return e.classList||new wB(e)}function wB(e){this._node=e,this._names=bB(e.getAttribute("class")||"")}wB.prototype={add:function(e){var t=this._names.indexOf(e);t<0&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var t=this._names.indexOf(e);t>=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function SB(e,t){for(var n=Ck(e),r=-1,o=t.length;++r<o;)n.add(tr)}function _B(e,t){for(var n=Ck(e),r=-1,o=t.length;++r<o;)n.remove(tr)}function uae(e){return function(){SB(this,e)}}function dae(e){return function(){_B(this,e)}}function fae(e,t){return function(){(t.apply(this,arguments)?SB:_B)(this,e)}}function hae(e,t){var n=bB(e+"");if(arguments.length<2){for(var r=Ck(this.node()),o=-1,i=n.length;++o<i;)if(!r.contains(no))return!1;return!0}return this.each((typeof t=="function"?fae:t?uae:dae)(n,t))}function pae(){this.textContent=""}function mae(e){return function(){this.textContent=e}}function gae(e){return function(){var t=e.apply(this,arguments);this.textContent=t??""}}function vae(e){return arguments.length?this.each(e==null?pae:(typeof e=="function"?gae:mae)(e)):this.node().textContent}function yae(){this.innerHTML=""}function xae(e){return function(){this.innerHTML=e}}function bae(e){return function(){var t=e.apply(this,arguments);this.innerHTML=t??""}}function wae(e){return arguments.length?this.each(e==null?yae:(typeof e=="function"?bae:xae)(e)):this.node().innerHTML}function Sae(){this.nextSibling&&this.parentNode.appendChild(this)}function _ae(){return this.each(Sae)}function Cae(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function Eae(){return this.each(Cae)}function Nae(e){var t=typeof e=="function"?e:pB(e);return this.select(function(){return this.appendChild(t.apply(this,arguments))})}function Rae(){return null}function Tae(e,t){var n=typeof e=="function"?e:pB(e),r=t==null?Rae:typeof t=="function"?t:_k(t);return this.select(function(){return this.insertBefore(n.apply(this,arguments),r.apply(this,arguments)||null)})}function kae(){var e=this.parentNode;e&&e.removeChild(this)}function Pae(){return this.each(kae)}function Iae(){var e=this.cloneNode(!1),t=this.parentNode;return t?t.insertBefore(e,this.nextSibling):e}function Aae(){var e=this.cloneNode(!0),t=this.parentNode;return t?t.insertBefore(e,this.nextSibling):e}function Mae(e){return this.select(e?Aae:Iae)}function jae(e){return arguments.length?this.property("__data__",e):this.node().__data__}function Lae(e){return function(t){e.call(this,t,this.__data__)}}function Oae(e){return e.trim().split(/^|\s+/).map(function(t){var n="",r=t.indexOf(".");return r>=0&&(n=t.slice(r+1),t=t.slice(0,r)),{type:t,name:n}})}function Dae(e){return function(){var t=this.__on;if(t){for(var n=0,r=-1,o=t.length,i;n<o;++n)i=tn,(!e.type||i.type===e.type)&&i.name===e.name?this.removeEventListener(i.type,i.listener,i.options):t++r=i;++r?t.length=r:delete this.__on}}}function Fae(e,t,n){return function(){var r=this.__on,o,i=Lae(t);if(r){for(var s=0,a=r.length;s<a;++s)if((o=rs).type===e.type&&o.name===e.name){this.removeEventListener(o.type,o.listener,o.options),this.addEventListener(o.type,o.listener=i,o.options=n),o.value=t;return}}this.addEventListener(e.type,i,n),o={type:e.type,name:e.name,value:t,listener:i,options:n},r?r.push(o):this.__on=o}}function $ae(e,t,n){var r=Oae(e+""),o,i=r.length,s;if(arguments.length<2){var a=this.node().__on;if(a){for(var l=0,c=a.length,d;l<c;++l)for(o=0,d=al;o<i;++o)if((s=ro).type===d.type&&s.name===d.name)return d.value}return}for(a=t?Fae:Dae,o=0;o<i;++o)this.each(a(ro,t,n));return this}function CB(e,t,n){var r=xB(e),o=r.CustomEvent;typeof o=="function"?o=new o(t,n):(o=r.document.createEvent("Event"),n?(o.initEvent(t,n.bubbles,n.cancelable),o.detail=n.detail):o.initEvent(t,!1,!1)),e.dispatchEvent(o)}function zae(e,t){return function(){return CB(this,e,t)}}function Hae(e,t){return function(){return CB(this,e,t.apply(this,arguments))}}function Bae(e,t){return this.each((typeof t=="function"?Hae:zae)(e,t))}function*Wae(){for(var e=this._groups,t=0,n=e.length;t<n;++t)for(var r=et,o=0,i=r.length,s;o<i;++o)(s=ro)&&(yield s)}var EB=null;function si(e,t){this._groups=e,this._parents=t}function Vm(){return new si(document.documentElement,EB)}function Uae(){return this}si.prototype=Vm.prototype={constructor:si,select:gse,selectAll:bse,selectChild:Cse,selectChildren:Tse,filter:kse,data:Lse,enter:Pse,exit:Dse,join:Fse,merge:$se,selection:Uae,order:zse,sort:Hse,call:Wse,nodes:Use,node:qse,size:Gse,empty:Vse,each:Yse,attr:tae,style:iae,property:cae,classed:hae,text:vae,html:wae,raise:_ae,lower:Eae,append:Nae,insert:Tae,remove:Pae,clone:Mae,datum:jae,on:$ae,dispatch:Bae,Symbol.iterator:Wae};function Ko(e){return typeof e=="string"?new si(document.querySelector(e),document.documentElement):new si(e,EB)}function qae(e){let t;for(;t=e.sourceEvent;)e=t;return e}function es(e,t){if(e=qae(e),t===void 0&&(t=e.currentTarget),t){var n=t.ownerSVGElement||t;if(n.createSVGPoint){var r=n.createSVGPoint();return r.x=e.clientX,r.y=e.clientY,r=r.matrixTransform(t.getScreenCTM().inverse()),r.x,r.y}if(t.getBoundingClientRect){var o=t.getBoundingClientRect();returne.clientX-o.left-t.clientLeft,e.clientY-o.top-t.clientTop}}returne.pageX,e.pageY}const Gae={passive:!1},mm={capture:!0,passive:!1};function W1(e){e.stopImmediatePropagation()}function sf(e){e.preventDefault(),e.stopImmediatePropagation()}function NB(e){var t=e.document.documentElement,n=Ko(e).on("dragstart.drag",sf,mm);"onselectstart"in t?n.on("selectstart.drag",sf,mm):(t.__noselect=t.style.MozUserSelect,t.style.MozUserSelect="none")}function RB(e,t){var n=e.document.documentElement,r=Ko(e).on("dragstart.drag",null);t&&(r.on("click.drag",sf,mm),setTimeout(function(){r.on("click.drag",null)},0)),"onselectstart"in n?r.on("selectstart.drag",null):(n.style.MozUserSelect=n.__noselect,delete n.__noselect)}const Fv=e=>()=>e;function cR(e,{sourceEvent:t,subject:n,target:r,identifier:o,active:i,x:s,y:a,dx:l,dy:c,dispatch:d}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:o,enumerable:!0,configurable:!0},active:{value:i,enumerable:!0,configurable:!0},x:{value:s,enumerable:!0,configurable:!0},y:{value:a,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:c,enumerable:!0,configurable:!0},_:{value:d}})}cR.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function Vae(e){return!e.ctrlKey&&!e.button}function Yae(){return this.parentNode}function Kae(e,t){return t??{x:e.x,y:e.y}}function Xae(){return navigator.maxTouchPoints||"ontouchstart"in this}function TB(){var e=Vae,t=Yae,n=Kae,r=Xae,o={},i=Rx("start","drag","end"),s=0,a,l,c,d,f=0;function p(E){E.on("mousedown.drag",g).filter(r).on("touchstart.drag",_).on("touchmove.drag",x,Gae).on("touchend.drag touchcancel.drag",w).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function g(E,R){if(!(d||!e.call(this,E,R))){var P=C(this,t.call(this,E,R),E,R,"mouse");P&&(Ko(E.view).on("mousemove.drag",v,mm).on("mouseup.drag",b,mm),NB(E.view),W1(E),c=!1,a=E.clientX,l=E.clientY,P("start",E))}}function v(E){if(sf(E),!c){var R=E.clientX-a,P=E.clientY-l;c=R*R+P*P>f}o.mouse("drag",E)}function b(E){Ko(E.view).on("mousemove.drag mouseup.drag",null),RB(E.view,c),sf(E),o.mouse("end",E)}function _(E,R){if(e.call(this,E,R)){var P=E.changedTouches,N=t.call(this,E,R),k=P.length,I,O;for(I=0;I<k;++I)(O=C(this,N,E,R,PI.identifier,PI))&&(W1(E),O("start",E,PI))}}function x(E){var R=E.changedTouches,P=R.length,N,k;for(N=0;N<P;++N)(k=oRN.identifier)&&(sf(E),k("drag",E,RN))}function w(E){var R=E.changedTouches,P=R.length,N,k;for(d&&clearTimeout(d),d=setTimeout(function(){d=null},500),N=0;N<P;++N)(k=oRN.identifier)&&(W1(E),k("end",E,RN))}function C(E,R,P,N,k,I){var O=i.copy(),j=es(I||P,R),H,q,M;if((M=n.call(E,new cR("beforestart",{sourceEvent:P,target:p,identifier:k,active:s,x:j0,y:j1,dx:0,dy:0,dispatch:O}),N))!=null)return H=M.x-j0||0,q=M.y-j1||0,function B($,W,G){var z=j,K;switch($){case"start":ok=B,K=s++;break;case"end":delete ok,--s;case"drag":j=es(G||W,R),K=s;break}O.call($,E,new cR($,{sourceEvent:W,subject:M,target:p,identifier:k,active:K,x:j0+H,y:j1+q,dx:j0-z0,dy:j1-z1,dispatch:O}),N)}}return p.filter=function(E){return arguments.length?(e=typeof E=="function"?E:Fv(!!E),p):e},p.container=function(E){return arguments.length?(t=typeof E=="function"?E:Fv(E),p):t},p.subject=function(E){return arguments.length?(n=typeof E=="function"?E:Fv(E),p):n},p.touchable=function(E){return arguments.length?(r=typeof E=="function"?E:Fv(!!E),p):r},p.on=function(){var E=i.on.apply(i,arguments);return E===i?p:E},p.clickDistance=function(E){return arguments.length?(f=(E=+E)*E,p):Math.sqrt(f)},p}function Ek(e,t,n){e.prototype=t.prototype=n,n.constructor=e}function kB(e,t){var n=Object.create(e.prototype);for(var r in t)nr=tr;return n}function Ym(){}var gm=.7,my=1/gm,af="\\s*(+-?\\d+)\\s*",vm="\\s*(+-?(?:\\d*\\.)?\\d+(?:eE+-?\\d+)?)\\s*",oa="\\s*(+-?(?:\\d*\\.)?\\d+(?:eE+-?\\d+)?)%\\s*",Zae=/^#(0-9a-f{3,8})$/,Qae=new RegExp(`^rgb\\(${af},${af},${af}\\)$`),Jae=new RegExp(`^rgb\\(${oa},${oa},${oa}\\)$`),ele=new RegExp(`^rgba\\(${af},${af},${af},${vm}\\)$`),tle=new RegExp(`^rgba\\(${oa},${oa},${oa},${vm}\\)$`),nle=new RegExp(`^hsl\\(${vm},${oa},${oa}\\)$`),rle=new RegExp(`^hsla\\(${vm},${oa},${oa},${vm}\\)$`),PL={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};Ek(Ym,Mu,{copy(e){return Object.assign(new this.constructor,this,e)},displayable(){return this.rgb().displayable()},hex:IL,formatHex:IL,formatHex8:ole,formatHsl:ile,formatRgb:AL,toString:AL});function IL(){return this.rgb().formatHex()}function ole(){return this.rgb().formatHex8()}function ile(){return PB(this).formatHsl()}function AL(){return this.rgb().formatRgb()}function Mu(e){var t,n;return e=(e+"").trim().toLowerCase(),(t=Zae.exec(e))?(n=t1.length,t=parseInt(t1,16),n===6?ML(t):n===3?new Ro(t>>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?$v(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?$v(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=Qae.exec(e))?new Ro(t1,t2,t3,1):(t=Jae.exec(e))?new Ro(t1*255/100,t2*255/100,t3*255/100,1):(t=ele.exec(e))?$v(t1,t2,t3,t4):(t=tle.exec(e))?$v(t1*255/100,t2*255/100,t3*255/100,t4):(t=nle.exec(e))?OL(t1,t2/100,t3/100,1):(t=rle.exec(e))?OL(t1,t2/100,t3/100,t4):PL.hasOwnProperty(e)?ML(PLe):e==="transparent"?new Ro(NaN,NaN,NaN,0):null}function ML(e){return new Ro(e>>16&255,e>>8&255,e&255,1)}function $v(e,t,n,r){return r<=0&&(e=t=n=NaN),new Ro(e,t,n,r)}function sle(e){return e instanceof Ym||(e=Mu(e)),e?(e=e.rgb(),new Ro(e.r,e.g,e.b,e.opacity)):new Ro}function uR(e,t,n,r){return arguments.length===1?sle(e):new Ro(e,t,n,r??1)}function Ro(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}Ek(Ro,uR,kB(Ym,{brighter(e){return e=e==null?my:Math.pow(my,e),new Ro(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?gm:Math.pow(gm,e),new Ro(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new Ro(Su(this.r),Su(this.g),Su(this.b),gy(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:jL,formatHex:jL,formatHex8:ale,formatRgb:LL,toString:LL}));function jL(){return`#${hu(this.r)}${hu(this.g)}${hu(this.b)}`}function ale(){return`#${hu(this.r)}${hu(this.g)}${hu(this.b)}${hu((isNaN(this.opacity)?1:this.opacity)*255)}`}function LL(){const e=gy(this.opacity);return`${e===1?"rgb(":"rgba("}${Su(this.r)}, ${Su(this.g)}, ${Su(this.b)}${e===1?")":`, ${e})`}`}function gy(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function Su(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function hu(e){return e=Su(e),(e<16?"0":"")+e.toString(16)}function OL(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new os(e,t,n,r)}function PB(e){if(e instanceof os)return new os(e.h,e.s,e.l,e.opacity);if(e instanceof Ym||(e=Mu(e)),!e)return new os;if(e instanceof os)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,o=Math.min(t,n,r),i=Math.max(t,n,r),s=NaN,a=i-o,l=(i+o)/2;return a?(t===i?s=(n-r)/a+(n<r)*6:n===i?s=(r-t)/a+2:s=(t-n)/a+4,a/=l<.5?i+o:2-i-o,s*=60):a=l>0&&l<1?0:s,new os(s,a,l,e.opacity)}function lle(e,t,n,r){return arguments.length===1?PB(e):new os(e,t,n,r??1)}function os(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}Ek(os,lle,kB(Ym,{brighter(e){return e=e==null?my:Math.pow(my,e),new os(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?gm:Math.pow(gm,e),new os(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,o=2*n-r;return new Ro(U1(e>=240?e-240:e+120,o,r),U1(e,o,r),U1(e<120?e+240:e-120,o,r),this.opacity)},clamp(){return new os(DL(this.h),zv(this.s),zv(this.l),gy(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=gy(this.opacity);return`${e===1?"hsl(":"hsla("}${DL(this.h)}, ${zv(this.s)*100}%, ${zv(this.l)*100}%${e===1?")":`, ${e})`}`}}));function DL(e){return e=(e||0)%360,e<0?e+360:e}function zv(e){return Math.max(0,Math.min(1,e||0))}function U1(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}const Nk=e=>()=>e;function cle(e,t){return function(n){return e+n*t}}function ule(e,t,n){return e=Math.pow(e,n),t=Math.pow(t,n)-e,n=1/n,function(r){return Math.pow(e+r*t,n)}}function dle(e){return(e=+e)==1?IB:function(t,n){return n-t?ule(t,n,e):Nk(isNaN(t)?n:t)}}function IB(e,t){var n=t-e;return n?cle(e,n):Nk(isNaN(e)?t:e)}const vy=function e(t){var n=dle(t);function r(o,i){var s=n((o=uR(o)).r,(i=uR(i)).r),a=n(o.g,i.g),l=n(o.b,i.b),c=IB(o.opacity,i.opacity);return function(d){return o.r=s(d),o.g=a(d),o.b=l(d),o.opacity=c(d),o+""}}return r.gamma=e,r}(1);function fle(e,t){t||(t=);var n=e?Math.min(t.length,e.length):0,r=t.slice(),o;return function(i){for(o=0;o<n;++o)ro=eo*(1-i)+to*i;return r}}function hle(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function ple(e,t){var n=t?t.length:0,r=e?Math.min(n,e.length):0,o=new Array(r),i=new Array(n),s;for(s=0;s<r;++s)os=Wp(es,ts);for(;s<n;++s)is=ts;return function(a){for(s=0;s<r;++s)is=os(a);return i}}function mle(e,t){var n=new Date;return e=+e,t=+t,function(r){return n.setTime(e*(1-r)+t*r),n}}function Gs(e,t){return e=+e,t=+t,function(n){return e*(1-n)+t*n}}function gle(e,t){var n={},r={},o;(e===null||typeof e!="object")&&(e={}),(t===null||typeof t!="object")&&(t={});for(o in t)o in e?no=Wp(eo,to):ro=to;return function(i){for(o in n)ro=no(i);return r}}var dR=/-+?(?:\d+\.?\d*|\.?\d+)(?:eE-+?\d+)?/g,q1=new RegExp(dR.source,"g");function vle(e){return function(){return e}}function yle(e){return function(t){return e(t)+""}}function AB(e,t){var n=dR.lastIndex=q1.lastIndex=0,r,o,i,s=-1,a=,l=;for(e=e+"",t=t+"";(r=dR.exec(e))&&(o=q1.exec(t));)(i=o.index)>n&&(i=t.slice(n,i),as?as+=i:a++s=i),(r=r0)===(o=o0)?as?as+=o:a++s=o:(a++s=null,l.push({i:s,x:Gs(r,o)})),n=q1.lastIndex;return n<t.length&&(i=t.slice(n),as?as+=i:a++s=i),a.length<2?l0?yle(l0.x):vle(t):(t=l.length,function(c){for(var d=0,f;d<t;++d)a(f=ld).i=f.x(c);return a.join("")})}function Wp(e,t){var n=typeof t,r;return t==null||n==="boolean"?Nk(t):(n==="number"?Gs:n==="string"?(r=Mu(t))?(t=r,vy):AB:t instanceof Mu?vy:t instanceof Date?mle:hle(t)?fle:Array.isArray(t)?ple:typeof t.valueOf!="function"&&typeof t.toString!="function"||isNaN(t)?gle:Gs)(e,t)}var FL=180/Math.PI,fR={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};function MB(e,t,n,r,o,i){var s,a,l;return(s=Math.sqrt(e*e+t*t))&&(e/=s,t/=s),(l=e*n+t*r)&&(n-=e*l,r-=t*l),(a=Math.sqrt(n*n+r*r))&&(n/=a,r/=a,l/=a),e*r<t*n&&(e=-e,t=-t,l=-l,s=-s),{translateX:o,translateY:i,rotate:Math.atan2(t,e)*FL,skewX:Math.atan(l)*FL,scaleX:s,scaleY:a}}var Hv;function xle(e){const t=new(typeof DOMMatrix=="function"?DOMMatrix:WebKitCSSMatrix)(e+"");return t.isIdentity?fR:MB(t.a,t.b,t.c,t.d,t.e,t.f)}function ble(e){return e==null||(Hv||(Hv=document.createElementNS("http://www.w3.org/2000/svg","g")),Hv.setAttribute("transform",e),!(e=Hv.transform.baseVal.consolidate()))?fR:(e=e.matrix,MB(e.a,e.b,e.c,e.d,e.e,e.f))}function jB(e,t,n,r){function o(c){return c.length?c.pop()+" ":""}function i(c,d,f,p,g,v){if(c!==f||d!==p){var b=g.push("translate(",null,t,null,n);v.push({i:b-4,x:Gs(c,f)},{i:b-2,x:Gs(d,p)})}else(f||p)&&g.push("translate("+f+t+p+n)}function s(c,d,f,p){c!==d?(c-d>180?d+=360:d-c>180&&(c+=360),p.push({i:f.push(o(f)+"rotate(",null,r)-2,x:Gs(c,d)})):d&&f.push(o(f)+"rotate("+d+r)}function a(c,d,f,p){c!==d?p.push({i:f.push(o(f)+"skewX(",null,r)-2,x:Gs(c,d)}):d&&f.push(o(f)+"skewX("+d+r)}function l(c,d,f,p,g,v){if(c!==f||d!==p){var b=g.push(o(g)+"scale(",null,",",null,")");v.push({i:b-4,x:Gs(c,f)},{i:b-2,x:Gs(d,p)})}else(f!==1||p!==1)&&g.push(o(g)+"scale("+f+","+p+")")}return function(c,d){var f=,p=;return c=e(c),d=e(d),i(c.translateX,c.translateY,d.translateX,d.translateY,f,p),s(c.rotate,d.rotate,f,p),a(c.skewX,d.skewX,f,p),l(c.scaleX,c.scaleY,d.scaleX,d.scaleY,f,p),c=d=null,function(g){for(var v=-1,b=p.length,_;++v<b;)f(_=pv).i=_.x(g);return f.join("")}}}var wle=jB(xle,"px, ","px)","deg)"),Sle=jB(ble,", ",")",")"),_le=1e-12;function $L(e){return((e=Math.exp(e))+1/e)/2}function Cle(e){return((e=Math.exp(e))-1/e)/2}function Ele(e){return((e=Math.exp(2*e))-1)/(e+1)}const T0=function e(t,n,r){function o(i,s){var a=i0,l=i1,c=i2,d=s0,f=s1,p=s2,g=d-a,v=f-l,b=g*g+v*v,_,x;if(b<_le)x=Math.log(p/c)/t,_=function(N){returna+N*g,l+N*v,c*Math.exp(t*N*x)};else{var w=Math.sqrt(b),C=(p*p-c*c+r*b)/(2*c*n*w),E=(p*p-c*c-r*b)/(2*p*n*w),R=Math.log(Math.sqrt(C*C+1)-C),P=Math.log(Math.sqrt(E*E+1)-E);x=(P-R)/t,_=function(N){var k=N*x,I=$L(R),O=c/(n*w)*(I*Ele(t*k+R)-Cle(R));returna+O*g,l+O*v,c*I/$L(t*k+R)}}return _.duration=x*1e3*t/Math.SQRT2,_}return o.rho=function(i){var s=Math.max(.001,+i),a=s*s,l=a*a;return e(s,a,l)},o}(Math.SQRT2,2,4);var Cf=0,vp=0,op=0,LB=1e3,yy,yp,xy=0,ju=0,kx=0,ym=typeof performance=="object"&&performance.now?performance:Date,OB=typeof window=="object"&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(e){setTimeout(e,17)};function Rk(){return ju||(OB(Nle),ju=ym.now()+kx)}function Nle(){ju=0}function by(){this._call=this._time=this._next=null}by.prototype=DB.prototype={constructor:by,restart:function(e,t,n){if(typeof e!="function")throw new TypeError("callback is not a function");n=(n==null?Rk():+n)+(t==null?0:+t),!this._next&&yp!==this&&(yp?yp._next=this:yy=this,yp=this),this._call=e,this._time=n,hR()},stop:function(){this._call&&(this._call=null,this._time=1/0,hR())}};function DB(e,t,n){var r=new by;return r.restart(e,t,n),r}function Rle(){Rk(),++Cf;for(var e=yy,t;e;)(t=ju-e._time)>=0&&e._call.call(void 0,t),e=e._next;--Cf}function zL(){ju=(xy=ym.now())+kx,Cf=vp=0;try{Rle()}finally{Cf=0,kle(),ju=0}}function Tle(){var e=ym.now(),t=e-xy;t>LB&&(kx-=t,xy=e)}function kle(){for(var e,t=yy,n,r=1/0;t;)t._call?(r>t._time&&(r=t._time),e=t,t=t._next):(n=t._next,t._next=null,t=e?e._next=n:yy=n);yp=e,hR(r)}function hR(e){if(!Cf){vp&&(vp=clearTimeout(vp));var t=e-ju;t>24?(e<1/0&&(vp=setTimeout(zL,e-ym.now()-kx)),op&&(op=clearInterval(op))):(op||(xy=ym.now(),op=setInterval(Tle,LB)),Cf=1,OB(zL))}}function HL(e,t,n){var r=new by;return t=t==null?0:+t,r.restart(o=>{r.stop(),e(o+t)},t,n),r}var Ple=Rx("start","end","cancel","interrupt"),Ile=,FB=0,BL=1,pR=2,k0=3,WL=4,mR=5,P0=6;function Px(e,t,n,r,o,i){var s=e.__transition;if(!s)e.__transition={};else if(n in s)return;Ale(e,n,{name:t,index:r,group:o,on:Ple,tween:Ile,time:i.time,delay:i.delay,duration:i.duration,ease:i.ease,timer:null,state:FB})}function Tk(e,t){var n=vs(e,t);if(n.state>FB)throw new Error("too late; already scheduled");return n}function la(e,t){var n=vs(e,t);if(n.state>k0)throw new Error("too late; already running");return n}function vs(e,t){var n=e.__transition;if(!n||!(n=nt))throw new Error("transition not found");return n}function Ale(e,t,n){var r=e.__transition,o;rt=n,n.timer=DB(i,0,n.time);function i(c){n.state=BL,n.timer.restart(s,n.delay,n.time),n.delay<=c&&s(c-n.delay)}function s(c){var d,f,p,g;if(n.state!==BL)return l();for(d in r)if(g=rd,g.name===n.name){if(g.state===k0)return HL(s);g.state===WL?(g.state=P0,g.timer.stop(),g.on.call("interrupt",e,e.__data__,g.index,g.group),delete rd):+d<t&&(g.state=P0,g.timer.stop(),g.on.call("cancel",e,e.__data__,g.index,g.group),delete rd)}if(HL(function(){n.state===k0&&(n.state=WL,n.timer.restart(a,n.delay,n.time),a(c))}),n.state=pR,n.on.call("start",e,e.__data__,n.index,n.group),n.state===pR){for(n.state=k0,o=new Array(p=n.tween.length),d=0,f=-1;d<p;++d)(g=n.tweend.value.call(e,e.__data__,n.index,n.group))&&(o++f=g);o.length=f+1}}function a(c){for(var d=c<n.duration?n.ease.call(null,c/n.duration):(n.timer.restart(l),n.state=mR,1),f=-1,p=o.length;++f<p;)of.call(e,d);n.state===mR&&(n.on.call("end",e,e.__data__,n.index,n.group),l())}function l(){n.state=P0,n.timer.stop(),delete rt;for(var c in r)return;delete e.__transition}}function I0(e,t){var n=e.__transition,r,o,i=!0,s;if(n){t=t==null?null:t+"";for(s in n){if((r=ns).name!==t){i=!1;continue}o=r.state>pR&&r.state<mR,r.state=P0,r.timer.stop(),r.on.call(o?"interrupt":"cancel",e,e.__data__,r.index,r.group),delete ns}i&&delete e.__transition}}function Mle(e){return this.each(function(){I0(this,e)})}function jle(e,t){var n,r;return function(){var o=la(this,e),i=o.tween;if(i!==n){r=n=i;for(var s=0,a=r.length;s<a;++s)if(rs.name===t){r=r.slice(),r.splice(s,1);break}}o.tween=r}}function Lle(e,t,n){var r,o;if(typeof n!="function")throw new Error;return function(){var i=la(this,e),s=i.tween;if(s!==r){o=(r=s).slice();for(var a={name:t,value:n},l=0,c=o.length;l<c;++l)if(ol.name===t){ol=a;break}l===c&&o.push(a)}i.tween=o}}function Ole(e,t){var n=this._id;if(e+="",arguments.length<2){for(var r=vs(this.node(),n).tween,o=0,i=r.length,s;o<i;++o)if((s=ro).name===e)return s.value;return null}return this.each((t==null?jle:Lle)(n,e,t))}function kk(e,t,n){var r=e._id;return e.each(function(){var o=la(this,r);(o.value||(o.value={}))t=n.apply(this,arguments)}),function(o){return vs(o,r).valuet}}function $B(e,t){var n;return(typeof t=="number"?Gs:t instanceof Mu?vy:(n=Mu(t))?(t=n,vy):AB)(e,t)}function Dle(e){return function(){this.removeAttribute(e)}}function Fle(e){return function(){this.removeAttributeNS(e.space,e.local)}}function $le(e,t,n){var r,o=n+"",i;return function(){var s=this.getAttribute(e);return s===o?null:s===r?i:i=t(r=s,n)}}function zle(e,t,n){var r,o=n+"",i;return function(){var s=this.getAttributeNS(e.space,e.local);return s===o?null:s===r?i:i=t(r=s,n)}}function Hle(e,t,n){var r,o,i;return function(){var s,a=n(this),l;return a==null?void this.removeAttribute(e):(s=this.getAttribute(e),l=a+"",s===l?null:s===r&&l===o?i:(o=l,i=t(r=s,a)))}}function Ble(e,t,n){var r,o,i;return function(){var s,a=n(this),l;return a==null?void this.removeAttributeNS(e.space,e.local):(s=this.getAttributeNS(e.space,e.local),l=a+"",s===l?null:s===r&&l===o?i:(o=l,i=t(r=s,a)))}}function Wle(e,t){var n=Tx(e),r=n==="transform"?Sle:$B;return this.attrTween(e,typeof t=="function"?(n.local?Ble:Hle)(n,r,kk(this,"attr."+e,t)):t==null?(n.local?Fle:Dle)(n):(n.local?zle:$le)(n,r,t))}function Ule(e,t){return function(n){this.setAttribute(e,t.call(this,n))}}function qle(e,t){return function(n){this.setAttributeNS(e.space,e.local,t.call(this,n))}}function Gle(e,t){var n,r;function o(){var i=t.apply(this,arguments);return i!==r&&(n=(r=i)&&qle(e,i)),n}return o._value=t,o}function Vle(e,t){var n,r;function o(){var i=t.apply(this,arguments);return i!==r&&(n=(r=i)&&Ule(e,i)),n}return o._value=t,o}function Yle(e,t){var n="attr."+e;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(t==null)return this.tween(n,null);if(typeof t!="function")throw new Error;var r=Tx(e);return this.tween(n,(r.local?Gle:Vle)(r,t))}function Kle(e,t){return function(){Tk(this,e).delay=+t.apply(this,arguments)}}function Xle(e,t){return t=+t,function(){Tk(this,e).delay=t}}function Zle(e){var t=this._id;return arguments.length?this.each((typeof e=="function"?Kle:Xle)(t,e)):vs(this.node(),t).delay}function Qle(e,t){return function(){la(this,e).duration=+t.apply(this,arguments)}}function Jle(e,t){return t=+t,function(){la(this,e).duration=t}}function ece(e){var t=this._id;return arguments.length?this.each((typeof e=="function"?Qle:Jle)(t,e)):vs(this.node(),t).duration}function tce(e,t){if(typeof t!="function")throw new Error;return function(){la(this,e).ease=t}}function nce(e){var t=this._id;return arguments.length?this.each(tce(t,e)):vs(this.node(),t).ease}function rce(e,t){return function(){var n=t.apply(this,arguments);if(typeof n!="function")throw new Error;la(this,e).ease=n}}function oce(e){if(typeof e!="function")throw new Error;return this.each(rce(this._id,e))}function ice(e){typeof e!="function"&&(e=gB(e));for(var t=this._groups,n=t.length,r=new Array(n),o=0;o<n;++o)for(var i=to,s=i.length,a=ro=,l,c=0;c<s;++c)(l=ic)&&e.call(l,l.__data__,c,i)&&a.push(l);return new el(r,this._parents,this._name,this._id)}function sce(e){if(e._id!==this._id)throw new Error;for(var t=this._groups,n=e._groups,r=t.length,o=n.length,i=Math.min(r,o),s=new Array(r),a=0;a<i;++a)for(var l=ta,c=na,d=l.length,f=sa=new Array(d),p,g=0;g<d;++g)(p=lg||cg)&&(fg=p);for(;a<r;++a)sa=ta;return new el(s,this._parents,this._name,this._id)}function ace(e){return(e+"").trim().split(/^|\s+/).every(function(t){var n=t.indexOf(".");return n>=0&&(t=t.slice(0,n)),!t||t==="start"})}function lce(e,t,n){var r,o,i=ace(t)?Tk:la;return function(){var s=i(this,e),a=s.on;a!==r&&(o=(r=a).copy()).on(t,n),s.on=o}}function cce(e,t){var n=this._id;return arguments.length<2?vs(this.node(),n).on.on(e):this.each(lce(n,e,t))}function uce(e){return function(){var t=this.parentNode;for(var n in this.__transition)if(+n!==e)return;t&&t.removeChild(this)}}function dce(){return this.on("end.remove",uce(this._id))}function fce(e){var t=this._name,n=this._id;typeof e!="function"&&(e=_k(e));for(var r=this._groups,o=r.length,i=new Array(o),s=0;s<o;++s)for(var a=rs,l=a.length,c=is=new Array(l),d,f,p=0;p<l;++p)(d=ap)&&(f=e.call(d,d.__data__,p,a))&&("__data__"in d&&(f.__data__=d.__data__),cp=f,Px(cp,t,n,p,c,vs(d,n)));return new el(i,this._parents,t,n)}function hce(e){var t=this._name,n=this._id;typeof e!="function"&&(e=mB(e));for(var r=this._groups,o=r.length,i=,s=,a=0;a<o;++a)for(var l=ra,c=l.length,d,f=0;f<c;++f)if(d=lf){for(var p=e.call(d,d.__data__,f,l),g,v=vs(d,n),b=0,_=p.length;b<_;++b)(g=pb)&&Px(g,t,n,b,p,v);i.push(p),s.push(d)}return new el(i,s,t,n)}var pce=Vm.prototype.constructor;function mce(){return new pce(this._groups,this._parents)}function gce(e,t){var n,r,o;return function(){var i=_f(this,e),s=(this.style.removeProperty(e),_f(this,e));return i===s?null:i===n&&s===r?o:o=t(n=i,r=s)}}function zB(e){return function(){this.style.removeProperty(e)}}function vce(e,t,n){var r,o=n+"",i;return function(){var s=_f(this,e);return s===o?null:s===r?i:i=t(r=s,n)}}function yce(e,t,n){var r,o,i;return function(){var s=_f(this,e),a=n(this),l=a+"";return a==null&&(l=a=(this.style.removeProperty(e),_f(this,e))),s===l?null:s===r&&l===o?i:(o=l,i=t(r=s,a))}}function xce(e,t){var n,r,o,i="style."+t,s="end."+i,a;return function(){var l=la(this,e),c=l.on,d=l.valuei==null?a||(a=zB(t)):void 0;(c!==n||o!==d)&&(r=(n=c).copy()).on(s,o=d),l.on=r}}function bce(e,t,n){var r=(e+="")=="transform"?wle:$B;return t==null?this.styleTween(e,gce(e,r)).on("end.style."+e,zB(e)):typeof t=="function"?this.styleTween(e,yce(e,r,kk(this,"style."+e,t))).each(xce(this._id,e)):this.styleTween(e,vce(e,r,t),n).on("end.style."+e,null)}function wce(e,t,n){return function(r){this.style.setProperty(e,t.call(this,r),n)}}function Sce(e,t,n){var r,o;function i(){var s=t.apply(this,arguments);return s!==o&&(r=(o=s)&&wce(e,s,n)),r}return i._value=t,i}function _ce(e,t,n){var r="style."+(e+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(t==null)return this.tween(r,null);if(typeof t!="function")throw new Error;return this.tween(r,Sce(e,t,n??""))}function Cce(e){return function(){this.textContent=e}}function Ece(e){return function(){var t=e(this);this.textContent=t??""}}function Nce(e){return this.tween("text",typeof e=="function"?Ece(kk(this,"text",e)):Cce(e==null?"":e+""))}function Rce(e){return function(t){this.textContent=e.call(this,t)}}function Tce(e){var t,n;function r(){var o=e.apply(this,arguments);return o!==n&&(t=(n=o)&&Rce(o)),t}return r._value=e,r}function kce(e){var t="text";if(arguments.length<1)return(t=this.tween(t))&&t._value;if(e==null)return this.tween(t,null);if(typeof e!="function")throw new Error;return this.tween(t,Tce(e))}function Pce(){for(var e=this._name,t=this._id,n=HB(),r=this._groups,o=r.length,i=0;i<o;++i)for(var s=ri,a=s.length,l,c=0;c<a;++c)if(l=sc){var d=vs(l,t);Px(l,e,n,c,s,{time:d.time+d.delay+d.duration,delay:0,duration:d.duration,ease:d.ease})}return new el(r,this._parents,e,n)}function Ice(){var e,t,n=this,r=n._id,o=n.size();return new Promise(function(i,s){var a={value:s},l={value:function(){--o===0&&i()}};n.each(function(){var c=la(this,r),d=c.on;d!==e&&(t=(e=d).copy(),t._.cancel.push(a),t._.interrupt.push(a),t._.end.push(l)),c.on=t}),o===0&&i()})}var Ace=0;function el(e,t,n,r){this._groups=e,this._parents=t,this._name=n,this._id=r}function HB(){return++Ace}var Ma=Vm.prototype;el.prototype={constructor:el,select:fce,selectAll:hce,selectChild:Ma.selectChild,selectChildren:Ma.selectChildren,filter:ice,merge:sce,selection:mce,transition:Pce,call:Ma.call,nodes:Ma.nodes,node:Ma.node,size:Ma.size,empty:Ma.empty,each:Ma.each,on:cce,attr:Wle,attrTween:Yle,style:bce,styleTween:_ce,text:Nce,textTween:kce,remove:dce,tween:Ole,delay:Zle,duration:ece,ease:nce,easeVarying:oce,end:Ice,Symbol.iterator:MaSymbol.iterator};function Mce(e){return((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2}var jce={time:null,delay:0,duration:250,ease:Mce};function Lce(e,t){for(var n;!(n=e.__transition)||!(n=nt);)if(!(e=e.parentNode))throw new Error(`transition ${t} not found`);return n}function Oce(e){var t,n;e instanceof el?(t=e._id,e=e._name):(t=HB(),(n=jce).time=Rk(),e=e==null?null:e+"");for(var r=this._groups,o=r.length,i=0;i<o;++i)for(var s=ri,a=s.length,l,c=0;c<a;++c)(l=sc)&&Px(l,e,t,c,s,n||Lce(l,t));return new el(r,this._parents,e,t)}Vm.prototype.interrupt=Mle;Vm.prototype.transition=Oce;const Bv=e=>()=>e;function Dce(e,{sourceEvent:t,target:n,transform:r,dispatch:o}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:o}})}function Ha(e,t,n){this.k=e,this.x=t,this.y=n}Ha.prototype={constructor:Ha,scale:function(e){return e===1?this:new Ha(this.k*e,this.x,this.y)},translate:function(e,t){return e===0&t===0?this:new Ha(this.k,this.x+this.k*e,this.y+this.k*t)},apply:function(e){returne0*this.k+this.x,e1*this.k+this.y},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return(e0-this.x)/this.k,(e1-this.y)/this.k},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Ix=new Ha(1,0,0);BB.prototype=Ha.prototype;function BB(e){for(;!e.__zoom;)if(!(e=e.parentNode))return Ix;return e.__zoom}function G1(e){e.stopImmediatePropagation()}function ip(e){e.preventDefault(),e.stopImmediatePropagation()}function Fce(e){return(!e.ctrlKey||e.type==="wheel")&&!e.button}function $ce(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e,e.hasAttribute("viewBox")?(e=e.viewBox.baseVal,e.x,e.y,e.x+e.width,e.y+e.height):0,0,e.width.baseVal.value,e.height.baseVal.value):0,0,e.clientWidth,e.clientHeight}function UL(){return this.__zoom||Ix}function zce(e){return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function Hce(){return navigator.maxTouchPoints||"ontouchstart"in this}function Bce(e,t,n){var r=e.invertX(t00)-n00,o=e.invertX(t10)-n10,i=e.invertY(t01)-n01,s=e.invertY(t11)-n11;return e.translate(o>r?(r+o)/2:Math.min(0,r)||Math.max(0,o),s>i?(i+s)/2:Math.min(0,i)||Math.max(0,s))}function WB(){var e=Fce,t=$ce,n=Bce,r=zce,o=Hce,i=0,1/0,s=-1/0,-1/0,1/0,1/0,a=250,l=T0,c=Rx("start","zoom","end"),d,f,p,g=500,v=150,b=0,_=10;function x(M){M.property("__zoom",UL).on("wheel.zoom",k,{passive:!1}).on("mousedown.zoom",I).on("dblclick.zoom",O).filter(o).on("touchstart.zoom",j).on("touchmove.zoom",H).on("touchend.zoom touchcancel.zoom",q).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}x.transform=function(M,B,$,W){var G=M.selection?M.selection():M;G.property("__zoom",UL),M!==G?R(M,B,$,W):G.interrupt().each(function(){P(this,arguments).event(W).start().zoom(null,typeof B=="function"?B.apply(this,arguments):B).end()})},x.scaleBy=function(M,B,$,W){x.scaleTo(M,function(){var G=this.__zoom.k,z=typeof B=="function"?B.apply(this,arguments):B;return G*z},$,W)},x.scaleTo=function(M,B,$,W){x.transform(M,function(){var G=t.apply(this,arguments),z=this.__zoom,K=$==null?E(G):typeof $=="function"?$.apply(this,arguments):$,Q=z.invert(K),re=typeof B=="function"?B.apply(this,arguments):B;return n(C(w(z,re),K,Q),G,s)},$,W)},x.translateBy=function(M,B,$,W){x.transform(M,function(){return n(this.__zoom.translate(typeof B=="function"?B.apply(this,arguments):B,typeof $=="function"?$.apply(this,arguments):$),t.apply(this,arguments),s)},null,W)},x.translateTo=function(M,B,$,W,G){x.transform(M,function(){var z=t.apply(this,arguments),K=this.__zoom,Q=W==null?E(z):typeof W=="function"?W.apply(this,arguments):W;return n(Ix.translate(Q0,Q1).scale(K.k).translate(typeof B=="function"?-B.apply(this,arguments):-B,typeof $=="function"?-$.apply(this,arguments):-$),z,s)},W,G)};function w(M,B){return B=Math.max(i0,Math.min(i1,B)),B===M.k?M:new Ha(B,M.x,M.y)}function C(M,B,$){var W=B0-$0*M.k,G=B1-$1*M.k;return W===M.x&&G===M.y?M:new Ha(M.k,W,G)}function E(M){return(+M00+ +M10)/2,(+M01+ +M11)/2}function R(M,B,$,W){M.on("start.zoom",function(){P(this,arguments).event(W).start()}).on("interrupt.zoom end.zoom",function(){P(this,arguments).event(W).end()}).tween("zoom",function(){var G=this,z=arguments,K=P(G,z).event(W),Q=t.apply(G,z),re=$==null?E(Q):typeof $=="function"?$.apply(G,z):$,ae=Math.max(Q10-Q00,Q11-Q01),de=G.__zoom,Ne=typeof B=="function"?B.apply(G,z):B,ye=l(de.invert(re).concat(ae/de.k),Ne.invert(re).concat(ae/Ne.k));return function(fe){if(fe===1)fe=Ne;else{var Z=ye(fe),oe=ae/Z2;fe=new Ha(oe,re0-Z0*oe,re1-Z1*oe)}K.zoom(null,fe)}})}function P(M,B,$){return!$&&M.__zooming||new N(M,B)}function N(M,B){this.that=M,this.args=B,this.active=0,this.sourceEvent=null,this.extent=t.apply(M,B),this.taps=0}N.prototype={event:function(M){return M&&(this.sourceEvent=M),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(M,B){return this.mouse&&M!=="mouse"&&(this.mouse1=B.invert(this.mouse0)),this.touch0&&M!=="touch"&&(this.touch01=B.invert(this.touch00)),this.touch1&&M!=="touch"&&(this.touch11=B.invert(this.touch10)),this.that.__zoom=B,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(M){var B=Ko(this.that).datum();c.call(M,this.that,new Dce(M,{sourceEvent:this.sourceEvent,target:x,transform:this.that.__zoom,dispatch:c}),B)}};function k(M,...B){if(!e.apply(this,arguments))return;var $=P(this,B).event(M),W=this.__zoom,G=Math.max(i0,Math.min(i1,W.k*Math.pow(2,r.apply(this,arguments)))),z=es(M);if($.wheel)($.mouse00!==z0||$.mouse01!==z1)&&($.mouse1=W.invert($.mouse0=z)),clearTimeout($.wheel);else{if(W.k===G)return;$.mouse=z,W.invert(z),I0(this),$.start()}ip(M),$.wheel=setTimeout(K,v),$.zoom("mouse",n(C(w(W,G),$.mouse0,$.mouse1),$.extent,s));function K(){$.wheel=null,$.end()}}function I(M,...B){if(p||!e.apply(this,arguments))return;var $=M.currentTarget,W=P(this,B,!0).event(M),G=Ko(M.view).on("mousemove.zoom",re,!0).on("mouseup.zoom",ae,!0),z=es(M,$),K=M.clientX,Q=M.clientY;NB(M.view),G1(M),W.mouse=z,this.__zoom.invert(z),I0(this),W.start();function re(de){if(ip(de),!W.moved){var Ne=de.clientX-K,ye=de.clientY-Q;W.moved=Ne*Ne+ye*ye>b}W.event(de).zoom("mouse",n(C(W.that.__zoom,W.mouse0=es(de,$),W.mouse1),W.extent,s))}function ae(de){G.on("mousemove.zoom mouseup.zoom",null),RB(de.view,W.moved),ip(de),W.event(de).end()}}function O(M,...B){if(e.apply(this,arguments)){var $=this.__zoom,W=es(M.changedTouches?M.changedTouches0:M,this),G=$.invert(W),z=$.k*(M.shiftKey?.5:2),K=n(C(w($,z),W,G),t.apply(this,B),s);ip(M),a>0?Ko(this).transition().duration(a).call(R,K,W,M):Ko(this).call(x.transform,K,W,M)}}function j(M,...B){if(e.apply(this,arguments)){var $=M.touches,W=$.length,G=P(this,B,M.changedTouches.length===W).event(M),z,K,Q,re;for(G1(M),K=0;K<W;++K)Q=$K,re=es(Q,this),re=re,this.__zoom.invert(re),Q.identifier,G.touch0?!G.touch1&&G.touch02!==re2&&(G.touch1=re,G.taps=0):(G.touch0=re,z=!0,G.taps=1+!!d);d&&(d=clearTimeout(d)),z&&(G.taps<2&&(f=re0,d=setTimeout(function(){d=null},g)),I0(this),G.start())}}function H(M,...B){if(this.__zooming){var $=P(this,B).event(M),W=M.changedTouches,G=W.length,z,K,Q,re;for(ip(M),z=0;z<G;++z)K=Wz,Q=es(K,this),$.touch0&&$.touch02===K.identifier?$.touch00=Q:$.touch1&&$.touch12===K.identifier&&($.touch10=Q);if(K=$.that.__zoom,$.touch1){var ae=$.touch00,de=$.touch01,Ne=$.touch10,ye=$.touch11,fe=(fe=Ne0-ae0)*fe+(fe=Ne1-ae1)*fe,Z=(Z=ye0-de0)*Z+(Z=ye1-de1)*Z;K=w(K,Math.sqrt(fe/Z)),Q=(ae0+Ne0)/2,(ae1+Ne1)/2,re=(de0+ye0)/2,(de1+ye1)/2}else if($.touch0)Q=$.touch00,re=$.touch01;else return;$.zoom("touch",n(C(K,Q,re),$.extent,s))}}function q(M,...B){if(this.__zooming){var $=P(this,B).event(M),W=M.changedTouches,G=W.length,z,K;for(G1(M),p&&clearTimeout(p),p=setTimeout(function(){p=null},g),z=0;z<G;++z)K=Wz,$.touch0&&$.touch02===K.identifier?delete $.touch0:$.touch1&&$.touch12===K.identifier&&delete $.touch1;if($.touch1&&!$.touch0&&($.touch0=$.touch1,delete $.touch1),$.touch0)$.touch01=this.__zoom.invert($.touch00);else if($.end(),$.taps===2&&(K=es(K,this),Math.hypot(f0-K0,f1-K1)<_)){var Q=Ko(this).on("dblclick.zoom");Q&&Q.apply(this,arguments)}}}return x.wheelDelta=function(M){return arguments.length?(r=typeof M=="function"?M:Bv(+M),x):r},x.filter=function(M){return arguments.length?(e=typeof M=="function"?M:Bv(!!M),x):e},x.touchable=function(M){return arguments.length?(o=typeof M=="function"?M:Bv(!!M),x):o},x.extent=function(M){return arguments.length?(t=typeof M=="function"?M:Bv(+M00,+M01,+M10,+M11),x):t},x.scaleExtent=function(M){return arguments.length?(i0=+M0,i1=+M1,x):i0,i1},x.translateExtent=function(M){return arguments.length?(s00=+M00,s10=+M10,s01=+M01,s11=+M11,x):s00,s01,s10,s11},x.constrain=function(M){return arguments.length?(n=M,x):n},x.duration=function(M){return arguments.length?(a=+M,x):a},x.interpolate=function(M){return arguments.length?(l=M,x):l},x.on=function(){var M=c.on.apply(c,arguments);return M===c?x:M},x.clickDistance=function(M){return arguments.length?(b=(M=+M)*M,x):Math.sqrt(b)},x.tapDistance=function(M){return arguments.length?(_=+M,x):_},x}const sa={error001:()=>"React Flow: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001",error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:e=>`Node type "${e}" not found. Using fallback type "default".`,error004:()=>"The React Flow parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:e=>`The old edge with id=${e} does not exist.`,error009:e=>`Marker type "${e}" doesn't exist.`,error008:(e,{id:t,sourceHandle:n,targetHandle:r})=>`Couldn't create edge for ${e} handle id: "${e==="source"?n:r}", edge id: ${t}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:e=>`Edge type "${e}" not found. Using fallback type "default".`,error012:e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,error013:(e="react")=>`It seems that you haven't loaded the styles. Please import '@xyflow/${e}/dist/style.css' or base.css to make sure everything is working properly.`,error014:()=>"useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.",error015:()=>"It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs."},xm=Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY,Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY,UB="Enter"," ","Escape",qB={"node.a11yDescription.default":"Press enter or space to select a node. Press delete to remove it and escape to cancel.","node.a11yDescription.keyboardDisabled":"Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.","node.a11yDescription.ariaLiveMessage":({direction:e,x:t,y:n})=>`Moved selected node ${e}. New position, x: ${t}, y: ${n}`,"edge.a11yDescription.default":"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.","controls.ariaLabel":"Control Panel","controls.zoomIn.ariaLabel":"Zoom In","controls.zoomOut.ariaLabel":"Zoom Out","controls.fitView.ariaLabel":"Fit View","controls.interactive.ariaLabel":"Toggle Interactivity","minimap.ariaLabel":"Mini Map","handle.ariaLabel":"Handle"};var Ef;(function(e){e.Strict="strict",e.Loose="loose"})(Ef||(Ef={}));var _u;(function(e){e.Free="free",e.Vertical="vertical",e.Horizontal="horizontal"})(_u||(_u={}));var bm;(function(e){e.Partial="partial",e.Full="full"})(bm||(bm={}));const GB={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null};var Xl;(function(e){e.Bezier="default",e.Straight="straight",e.Step="step",e.SmoothStep="smoothstep",e.SimpleBezier="simplebezier"})(Xl||(Xl={}));var wm;(function(e){e.Arrow="arrow",e.ArrowClosed="arrowclosed"})(wm||(wm={}));var Ve;(function(e){e.Left="left",e.Top="top",e.Right="right",e.Bottom="bottom"})(Ve||(Ve={}));const qL={Ve.Left:Ve.Right,Ve.Right:Ve.Left,Ve.Top:Ve.Bottom,Ve.Bottom:Ve.Top};function VB(e){return e===null?null:e?"valid":"invalid"}const YB=e=>"id"in e&&"source"in e&&"target"in e,Wce=e=>"id"in e&&"position"in e&&!("source"in e)&&!("target"in e),Pk=e=>"id"in e&&"internals"in e&&!("source"in e)&&!("target"in e),Km=(e,t=0,0)=>{const{width:n,height:r}=ll(e),o=e.origin??t,i=n*o0,s=r*o1;return{x:e.position.x-i,y:e.position.y-s}},Uce=(e,t={nodeOrigin:0,0})=>{if(e.length===0)return{x:0,y:0,width:0,height:0};const n=e.reduce((r,o)=>{const i=typeof o=="string";let s=!t.nodeLookup&&!i?o:void 0;t.nodeLookup&&(s=i?t.nodeLookup.get(o):Pk(o)?o:t.nodeLookup.get(o.id));const a=s?wy(s,t.nodeOrigin):{x:0,y:0,x2:0,y2:0};return Ax(r,a)},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return Mx(n)},Kf=(e,t={})=>{if(e.size===0)return{x:0,y:0,width:0,height:0};let n={x:1/0,y:1/0,x2:-1/0,y2:-1/0};return e.forEach(r=>{if(t.filter===void 0||t.filter(r)){const o=wy(r);n=Ax(n,o)}}),Mx(n)},Ik=(e,t,n,r,o=0,0,1,i=!1,s=!1)=>{const a={...Xm(t,n,r,o),width:t.width/o,height:t.height/o},l=;for(const c of e.values()){const{measured:d,selectable:f=!0,hidden:p=!1}=c;if(s&&!f||p)continue;const g=d.width??c.width??c.initialWidth??null,v=d.height??c.height??c.initialHeight??null,b=Sm(a,Rf(c)),_=(g??0)*(v??0),x=i&&b>0;(!c.internals.handleBounds||x||b>=_||c.dragging)&&l.push(c)}return l},qce=(e,t)=>{const n=new Set;return e.forEach(r=>{n.add(r.id)}),t.filter(r=>n.has(r.source)||n.has(r.target))};function Gce(e,t){const n=new Map,r=t!=null&&t.nodes?new Set(t.nodes.map(o=>o.id)):null;return e.forEach(o=>{o.measured.width&&o.measured.height&&((t==null?void 0:t.includeHiddenNodes)||!o.hidden)&&(!r||r.has(o.id))&&n.set(o.id,o)}),n}async function Vce({nodes:e,width:t,height:n,panZoom:r,minZoom:o,maxZoom:i},s){if(e.size===0)return Promise.resolve(!0);const a=Gce(e,s),l=Kf(a),c=Ak(l,t,n,(s==null?void 0:s.minZoom)??o,(s==null?void 0:s.maxZoom)??i,(s==null?void 0:s.padding)??.1);return await r.setViewport(c,{duration:s==null?void 0:s.duration,ease:s==null?void 0:s.ease,interpolate:s==null?void 0:s.interpolate}),Promise.resolve(!0)}function KB({nodeId:e,nextPosition:t,nodeLookup:n,nodeOrigin:r=0,0,nodeExtent:o,onError:i}){const s=n.get(e),a=s.parentId?n.get(s.parentId):void 0,{x:l,y:c}=a?a.internals.positionAbsolute:{x:0,y:0},d=s.origin??r;let f=s.extent||o;if(s.extent==="parent"&&!s.expandParent)if(!a)i==null||i("005",sa.error005());else{const g=a.measured.width,v=a.measured.height;g&&v&&(f=l,c,l+g,c+v)}else a&&Tf(s.extent)&&(f=s.extent00+l,s.extent01+c,s.extent10+l,s.extent11+c);const p=Tf(f)?Lu(t,f,s.measured):t;return(s.measured.width===void 0||s.measured.height===void 0)&&(i==null||i("015",sa.error015())),{position:{x:p.x-l+(s.measured.width??0)*d0,y:p.y-c+(s.measured.height??0)*d1},positionAbsolute:p}}async function Yce({nodesToRemove:e=,edgesToRemove:t=,nodes:n,edges:r,onBeforeDelete:o}){const i=new Set(e.map(p=>p.id)),s=;for(const p of n){if(p.deletable===!1)continue;const g=i.has(p.id),v=!g&&p.parentId&&s.find(b=>b.id===p.parentId);(g||v)&&s.push(p)}const a=new Set(t.map(p=>p.id)),l=r.filter(p=>p.deletable!==!1),d=qce(s,l);for(const p of l)a.has(p.id)&&!d.find(v=>v.id===p.id)&&d.push(p);if(!o)return{edges:d,nodes:s};const f=await o({nodes:s,edges:d});return typeof f=="boolean"?f?{edges:d,nodes:s}:{edges:,nodes:}:f}const Nf=(e,t=0,n=1)=>Math.min(Math.max(e,t),n),Lu=(e={x:0,y:0},t,n)=>({x:Nf(e.x,t00,t10-((n==null?void 0:n.width)??0)),y:Nf(e.y,t01,t11-((n==null?void 0:n.height)??0))});function XB(e,t,n){const{width:r,height:o}=ll(n),{x:i,y:s}=n.internals.positionAbsolute;return Lu(e,i,s,i+r,s+o,t)}const GL=(e,t,n)=>e<t?Nf(Math.abs(e-t),1,t)/t:e>n?-Nf(Math.abs(e-n),1,t)/t:0,ZB=(e,t,n=15,r=40)=>{const o=GL(e.x,r,t.width-r)*n,i=GL(e.y,r,t.height-r)*n;returno,i},Ax=(e,t)=>({x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x2,t.x2),y2:Math.max(e.y2,t.y2)}),gR=({x:e,y:t,width:n,height:r})=>({x:e,y:t,x2:e+n,y2:t+r}),Mx=({x:e,y:t,x2:n,y2:r})=>({x:e,y:t,width:n-e,height:r-t}),Rf=(e,t=0,0)=>{var o,i;const{x:n,y:r}=Pk(e)?e.internals.positionAbsolute:Km(e,t);return{x:n,y:r,width:((o=e.measured)==null?void 0:o.width)??e.width??e.initialWidth??0,height:((i=e.measured)==null?void 0:i.height)??e.height??e.initialHeight??0}},wy=(e,t=0,0)=>{var o,i;const{x:n,y:r}=Pk(e)?e.internals.positionAbsolute:Km(e,t);return{x:n,y:r,x2:n+(((o=e.measured)==null?void 0:o.width)??e.width??e.initialWidth??0),y2:r+(((i=e.measured)==null?void 0:i.height)??e.height??e.initialHeight??0)}},QB=(e,t)=>Mx(Ax(gR(e),gR(t))),Sm=(e,t)=>{const n=Math.max(0,Math.min(e.x+e.width,t.x+t.width)-Math.max(e.x,t.x)),r=Math.max(0,Math.min(e.y+e.height,t.y+t.height)-Math.max(e.y,t.y));return Math.ceil(n*r)},VL=e=>ls(e.width)&&ls(e.height)&&ls(e.x)&&ls(e.y),ls=e=>!isNaN(e)&&isFinite(e),Kce=(e,t)=>{},jx=(e,t=1,1)=>({x:t0*Math.round(e.x/t0),y:t1*Math.round(e.y/t1)}),Xm=({x:e,y:t},n,r,o,i=!1,s=1,1)=>{const a={x:(e-n)/o,y:(t-r)/o};return i?jx(a,s):a},Sy=({x:e,y:t},n,r,o)=>({x:e*o+n,y:t*o+r});function Ed(e,t){if(typeof e=="number")return Math.floor((t-t/(1+e))*.5);if(typeof e=="string"&&e.endsWith("px")){const n=parseFloat(e);if(!Number.isNaN(n))return Math.floor(n)}if(typeof e=="string"&&e.endsWith("%")){const n=parseFloat(e);if(!Number.isNaN(n))return Math.floor(t*n*.01)}return console.error(`React Flow The padding value "${e}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}function Xce(e,t,n){if(typeof e=="string"||typeof e=="number"){const r=Ed(e,n),o=Ed(e,t);return{top:r,right:o,bottom:r,left:o,x:o*2,y:r*2}}if(typeof e=="object"){const r=Ed(e.top??e.y??0,n),o=Ed(e.bottom??e.y??0,n),i=Ed(e.left??e.x??0,t),s=Ed(e.right??e.x??0,t);return{top:r,right:s,bottom:o,left:i,x:i+s,y:r+o}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}function Zce(e,t,n,r,o,i){const{x:s,y:a}=Sy(e,t,n,r),{x:l,y:c}=Sy({x:e.x+e.width,y:e.y+e.height},t,n,r),d=o-l,f=i-c;return{left:Math.floor(s),top:Math.floor(a),right:Math.floor(d),bottom:Math.floor(f)}}const Ak=(e,t,n,r,o,i)=>{const s=Xce(i,t,n),a=(t-s.x)/e.width,l=(n-s.y)/e.height,c=Math.min(a,l),d=Nf(c,r,o),f=e.x+e.width/2,p=e.y+e.height/2,g=t/2-f*d,v=n/2-p*d,b=Zce(e,g,v,d,t,n),_={left:Math.min(b.left-s.left,0),top:Math.min(b.top-s.top,0),right:Math.min(b.right-s.right,0),bottom:Math.min(b.bottom-s.bottom,0)};return{x:g-_.left+_.right,y:v-_.top+_.bottom,zoom:d}},_y=()=>{var e;return typeof navigator<"u"&&((e=navigator==null?void 0:navigator.userAgent)==null?void 0:e.indexOf("Mac"))>=0};function Tf(e){return e!==void 0&&e!=="parent"}function ll(e){var t,n;return{width:((t=e.measured)==null?void 0:t.width)??e.width??e.initialWidth??0,height:((n=e.measured)==null?void 0:n.height)??e.height??e.initialHeight??0}}function Mk(e){var t,n;return(((t=e.measured)==null?void 0:t.width)??e.width??e.initialWidth)!==void 0&&(((n=e.measured)==null?void 0:n.height)??e.height??e.initialHeight)!==void 0}function JB(e,t={width:0,height:0},n,r,o){const i={...e},s=r.get(n);if(s){const a=s.origin||o;i.x+=s.internals.positionAbsolute.x-(t.width??0)*a0,i.y+=s.internals.positionAbsolute.y-(t.height??0)*a1}return i}function YL(e,t){if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0}function Qce(){let e,t;return{promise:new Promise((r,o)=>{e=r,t=o}),resolve:e,reject:t}}function Jce(e){return{...qB,...e||{}}}function Up(e,{snapGrid:t=0,0,snapToGrid:n=!1,transform:r,containerBounds:o}){const{x:i,y:s}=Zs(e),a=Xm({x:i-((o==null?void 0:o.left)??0),y:s-((o==null?void 0:o.top)??0)},r),{x:l,y:c}=n?jx(a,t):a;return{xSnapped:l,ySnapped:c,...a}}const jk=e=>({width:e.offsetWidth,height:e.offsetHeight}),e8=e=>{var t;return((t=e==null?void 0:e.getRootNode)==null?void 0:t.call(e))||(window==null?void 0:window.document)},eue="INPUT","SELECT","TEXTAREA";function t8(e){var r,o;const t=((o=(r=e.composedPath)==null?void 0:r.call(e))==null?void 0:o0)||e.target;return(t==null?void 0:t.nodeType)!==1?!1:eue.includes(t.nodeName)||t.hasAttribute("contenteditable")||!!t.closest(".nokey")}const n8=e=>"clientX"in e,Zs=(e,t)=>{var i,s;const n=n8(e),r=n?e.clientX:(i=e.touches)==null?void 0:i0.clientX,o=n?e.clientY:(s=e.touches)==null?void 0:s0.clientY;return{x:r-((t==null?void 0:t.left)??0),y:o-((t==null?void 0:t.top)??0)}},KL=(e,t,n,r,o)=>{const i=t.querySelectorAll(`.${e}`);return!i||!i.length?null:Array.from(i).map(s=>{const a=s.getBoundingClientRect();return{id:s.getAttribute("data-handleid"),type:e,nodeId:o,position:s.getAttribute("data-handlepos"),x:(a.left-n.left)/r,y:(a.top-n.top)/r,...jk(s)}})};function r8({sourceX:e,sourceY:t,targetX:n,targetY:r,sourceControlX:o,sourceControlY:i,targetControlX:s,targetControlY:a}){const l=e*.125+o*.375+s*.375+n*.125,c=t*.125+i*.375+a*.375+r*.125,d=Math.abs(l-e),f=Math.abs(c-t);returnl,c,d,f}function Wv(e,t){return e>=0?.5*e:t*25*Math.sqrt(-e)}function XL({pos:e,x1:t,y1:n,x2:r,y2:o,c:i}){switch(e){case Ve.Left:returnt-Wv(t-r,i),n;case Ve.Right:returnt+Wv(r-t,i),n;case Ve.Top:returnt,n-Wv(n-o,i);case Ve.Bottom:returnt,n+Wv(o-n,i)}}function Lk({sourceX:e,sourceY:t,sourcePosition:n=Ve.Bottom,targetX:r,targetY:o,targetPosition:i=Ve.Top,curvature:s=.25}){consta,l=XL({pos:n,x1:e,y1:t,x2:r,y2:o,c:s}),c,d=XL({pos:i,x1:r,y1:o,x2:e,y2:t,c:s}),f,p,g,v=r8({sourceX:e,sourceY:t,targetX:r,targetY:o,sourceControlX:a,sourceControlY:l,targetControlX:c,targetControlY:d});return`M${e},${t} C${a},${l} ${c},${d} ${r},${o}`,f,p,g,v}function o8({sourceX:e,sourceY:t,targetX:n,targetY:r}){const o=Math.abs(n-e)/2,i=n<e?n+o:n-o,s=Math.abs(r-t)/2,a=r<t?r+s:r-s;returni,a,o,s}function tue({sourceNode:e,targetNode:t,selected:n=!1,zIndex:r,elevateOnSelect:o=!1}){if(r!==void 0)return r;const i=o&&n?1e3:0,s=Math.max(e.parentId?e.internals.z:0,t.parentId?t.internals.z:0);return i+s}function nue({sourceNode:e,targetNode:t,width:n,height:r,transform:o}){const i=Ax(wy(e),wy(t));i.x===i.x2&&(i.x2+=1),i.y===i.y2&&(i.y2+=1);const s={x:-o0/o2,y:-o1/o2,width:n/o2,height:r/o2};return Sm(s,Mx(i))>0}const rue=({source:e,sourceHandle:t,target:n,targetHandle:r})=>`xy-edge__${e}${t||""}-${n}${r||""}`,oue=(e,t)=>t.some(n=>n.source===e.source&&n.target===e.target&&(n.sourceHandle===e.sourceHandle||!n.sourceHandle&&!e.sourceHandle)&&(n.targetHandle===e.targetHandle||!n.targetHandle&&!e.targetHandle)),iue=(e,t)=>{if(!e.source||!e.target)return t;let n;return YB(e)?n={...e}:n={...e,id:rue(e)},oue(n,t)?t:(n.sourceHandle===null&&delete n.sourceHandle,n.targetHandle===null&&delete n.targetHandle,t.concat(n))};function i8({sourceX:e,sourceY:t,targetX:n,targetY:r}){consto,i,s,a=o8({sourceX:e,sourceY:t,targetX:n,targetY:r});return`M ${e},${t}L ${n},${r}`,o,i,s,a}const ZL={Ve.Left:{x:-1,y:0},Ve.Right:{x:1,y:0},Ve.Top:{x:0,y:-1},Ve.Bottom:{x:0,y:1}},sue=({source:e,sourcePosition:t=Ve.Bottom,target:n})=>t===Ve.Left||t===Ve.Right?e.x<n.x?{x:1,y:0}:{x:-1,y:0}:e.y<n.y?{x:0,y:1}:{x:0,y:-1},QL=(e,t)=>Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2));function aue({source:e,sourcePosition:t=Ve.Bottom,target:n,targetPosition:r=Ve.Top,center:o,offset:i,stepPosition:s}){const a=ZLt,l=ZLr,c={x:e.x+a.x*i,y:e.y+a.y*i},d={x:n.x+l.x*i,y:n.y+l.y*i},f=sue({source:c,sourcePosition:t,target:d}),p=f.x!==0?"x":"y",g=fp;let v=,b,_;const x={x:0,y:0},w={x:0,y:0},,,C,E=o8({sourceX:e.x,sourceY:e.y,targetX:n.x,targetY:n.y});if(ap*lp===-1){p==="x"?(b=o.x??c.x+(d.x-c.x)*s,_=o.y??(c.y+d.y)/2):(b=o.x??(c.x+d.x)/2,_=o.y??c.y+(d.y-c.y)*s);const P={x:b,y:c.y},{x:b,y:d.y},N={x:c.x,y:_},{x:d.x,y:_};ap===g?v=p==="x"?P:N:v=p==="x"?N:P}else{const P={x:c.x,y:d.y},N={x:d.x,y:c.y};if(p==="x"?v=a.x===g?N:P:v=a.y===g?P:N,t===r){const H=Math.abs(ep-np);if(H<=i){const q=Math.min(i-1,i-H);ap===g?xp=(cp>ep?-1:1)*q:wp=(dp>np?-1:1)*q}}if(t!==r){const H=p==="x"?"y":"x",q=ap===lH,M=cH>dH,B=cH<dH;(ap===1&&(!q&&M||q&&B)||ap!==1&&(!q&&B||q&&M))&&(v=p==="x"?P:N)}const k={x:c.x+x.x,y:c.y+x.y},I={x:d.x+w.x,y:d.y+w.y},O=Math.max(Math.abs(k.x-v0.x),Math.abs(I.x-v0.x)),j=Math.max(Math.abs(k.y-v0.y),Math.abs(I.y-v0.y));O>=j?(b=(k.x+I.x)/2,_=v0.y):(b=v0.x,_=(k.y+I.y)/2)}returne,{x:c.x+x.x,y:c.y+x.y},...v,{x:d.x+w.x,y:d.y+w.y},n,b,_,C,E}function lue(e,t,n,r){const o=Math.min(QL(e,t)/2,QL(t,n)/2,r),{x:i,y:s}=t;if(e.x===i&&i===n.x||e.y===s&&s===n.y)return`L${i} ${s}`;if(e.y===s){const c=e.x<n.x?-1:1,d=e.y<n.y?1:-1;return`L ${i+o*c},${s}Q ${i},${s} ${i},${s+o*d}`}const a=e.x<n.x?1:-1,l=e.y<n.y?-1:1;return`L ${i},${s+o*l}Q ${i},${s} ${i+o*a},${s}`}function vR({sourceX:e,sourceY:t,sourcePosition:n=Ve.Bottom,targetX:r,targetY:o,targetPosition:i=Ve.Top,borderRadius:s=5,centerX:a,centerY:l,offset:c=20,stepPosition:d=.5}){constf,p,g,v,b=aue({source:{x:e,y:t},sourcePosition:n,target:{x:r,y:o},targetPosition:i,center:{x:a,y:l},offset:c,stepPosition:d});returnf.reduce((x,w,C)=>{let E="";return C>0&&C<f.length-1?E=lue(fC-1,w,fC+1,s):E=`${C===0?"M":"L"}${w.x} ${w.y}`,x+=E,x},""),p,g,v,b}function JL(e){var t;return e&&!!(e.internals.handleBounds||(t=e.handles)!=null&&t.length)&&!!(e.measured.width||e.width||e.initialWidth)}function cue(e){var f;const{sourceNode:t,targetNode:n}=e;if(!JL(t)||!JL(n))return null;const r=t.internals.handleBounds||eO(t.handles),o=n.internals.handleBounds||eO(n.handles),i=tO((r==null?void 0:r.source)??,e.sourceHandle),s=tO(e.connectionMode===Ef.Strict?(o==null?void 0:o.target)??:((o==null?void 0:o.target)??).concat((o==null?void 0:o.source)??),e.targetHandle);if(!i||!s)return(f=e.onError)==null||f.call(e,"008",sa.error008(i?"target":"source",{id:e.id,sourceHandle:e.sourceHandle,targetHandle:e.targetHandle})),null;const a=(i==null?void 0:i.position)||Ve.Bottom,l=(s==null?void 0:s.position)||Ve.Top,c=_m(t,i,a),d=_m(n,s,l);return{sourceX:c.x,sourceY:c.y,targetX:d.x,targetY:d.y,sourcePosition:a,targetPosition:l}}function eO(e){if(!e)return null;const t=,n=;for(const r of e)r.width=r.width??1,r.height=r.height??1,r.type==="source"?t.push(r):r.type==="target"&&n.push(r);return{source:t,target:n}}function _m(e,t,n=Ve.Left,r=!1){const o=((t==null?void 0:t.x)??0)+e.internals.positionAbsolute.x,i=((t==null?void 0:t.y)??0)+e.internals.positionAbsolute.y,{width:s,height:a}=t??ll(e);if(r)return{x:o+s/2,y:i+a/2};switch((t==null?void 0:t.position)??n){case Ve.Top:return{x:o+s/2,y:i};case Ve.Right:return{x:o+s,y:i+a/2};case Ve.Bottom:return{x:o+s/2,y:i+a};case Ve.Left:return{x:o,y:i+a/2}}}function tO(e,t){return e&&(t?e.find(n=>n.id===t):e0)||null}function yR(e,t){return e?typeof e=="string"?e:`${t?`${t}__`:""}${Object.keys(e).sort().map(r=>`${r}=${er}`).join("&")}`:""}function uue(e,{id:t,defaultColor:n,defaultMarkerStart:r,defaultMarkerEnd:o}){const i=new Set;return e.reduce((s,a)=>(a.markerStart||r,a.markerEnd||o.forEach(l=>{if(l&&typeof l=="object"){const c=yR(l,t);i.has(c)||(s.push({id:c,color:l.color||n,...l}),i.add(c))}}),s),).sort((s,a)=>s.id.localeCompare(a.id))}function due(e,t,n,r,o){let i=.5;o==="start"?i=0:o==="end"&&(i=1);let s=(e.x+e.width*i)*t.zoom+t.x,e.y*t.zoom+t.y-r,a=-100*i,-100;switch(n){case Ve.Right:s=(e.x+e.width)*t.zoom+t.x+r,(e.y+e.height*i)*t.zoom+t.y,a=0,-100*i;break;case Ve.Bottom:s1=(e.y+e.height)*t.zoom+t.y+r,a1=0;break;case Ve.Left:s=e.x*t.zoom+t.x-r,(e.y+e.height*i)*t.zoom+t.y,a=-100,-100*i;break}return`translate(${s0}px, ${s1}px) translate(${a0}%, ${a1}%)`}const Ok={nodeOrigin:0,0,nodeExtent:xm,elevateNodesOnSelect:!0,defaults:{}},fue={...Ok,checkEquality:!0};function Dk(e,t){const n={...e};for(const r in t)tr!==void 0&&(nr=tr);return n}function hue(e,t,n){const r=Dk(Ok,n);for(const o of e.values())if(o.parentId)Fk(o,e,t,r);else{const i=Km(o,r.nodeOrigin),s=Tf(o.extent)?o.extent:r.nodeExtent,a=Lu(i,s,ll(o));o.internals.positionAbsolute=a}}function xR(e,t,n,r){var l,c;const o=Dk(fue,r);let i=e.length>0;const s=new Map(t),a=o!=null&&o.elevateNodesOnSelect?1e3:0;t.clear(),n.clear();for(const d of e){let f=s.get(d.id);if(o.checkEquality&&d===(f==null?void 0:f.internals.userNode))t.set(d.id,f);else{const p=Km(d,o.nodeOrigin),g=Tf(d.extent)?d.extent:o.nodeExtent,v=Lu(p,g,ll(d));f={...o.defaults,...d,measured:{width:(l=d.measured)==null?void 0:l.width,height:(c=d.measured)==null?void 0:c.height},internals:{positionAbsolute:v,handleBounds:d.measured?f==null?void 0:f.internals.handleBounds:void 0,z:s8(d,a),userNode:d}},t.set(d.id,f)}(f.measured===void 0||f.measured.width===void 0||f.measured.height===void 0)&&!f.hidden&&(i=!1),d.parentId&&Fk(f,t,n,r)}return i}function pue(e,t){if(!e.parentId)return;const n=t.get(e.parentId);n?n.set(e.id,e):t.set(e.parentId,new Map(e.id,e))}function Fk(e,t,n,r){const{elevateNodesOnSelect:o,nodeOrigin:i,nodeExtent:s}=Dk(Ok,r),a=e.parentId,l=t.get(a);if(!l){console.warn(`Parent node ${a} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);return}pue(e,n);const c=o?1e3:0,{x:d,y:f,z:p}=mue(e,l,i,s,c),{positionAbsolute:g}=e.internals,v=d!==g.x||f!==g.y;(v||p!==e.internals.z)&&t.set(e.id,{...e,internals:{...e.internals,positionAbsolute:v?{x:d,y:f}:g,z:p}})}function s8(e,t){return(ls(e.zIndex)?e.zIndex:0)+(e.selected?t:0)}function mue(e,t,n,r,o){const{x:i,y:s}=t.internals.positionAbsolute,a=ll(e),l=Km(e,n),c=Tf(e.extent)?Lu(l,e.extent,a):l;let d=Lu({x:i+c.x,y:s+c.y},r,a);e.extent==="parent"&&(d=XB(d,a,t));const f=s8(e,o),p=t.internals.z??0;return{x:d.x,y:d.y,z:p>=f?p+1:f}}function $k(e,t,n,r=0,0){var s;const o=,i=new Map;for(const a of e){const l=t.get(a.parentId);if(!l)continue;const c=((s=i.get(a.parentId))==null?void 0:s.expandedRect)??Rf(l),d=QB(c,a.rect);i.set(a.parentId,{expandedRect:d,parent:l})}return i.size>0&&i.forEach(({expandedRect:a,parent:l},c)=>{var C;const d=l.internals.positionAbsolute,f=ll(l),p=l.origin??r,g=a.x<d.x?Math.round(Math.abs(d.x-a.x)):0,v=a.y<d.y?Math.round(Math.abs(d.y-a.y)):0,b=Math.max(f.width,Math.round(a.width)),_=Math.max(f.height,Math.round(a.height)),x=(b-f.width)*p0,w=(_-f.height)*p1;(g>0||v>0||x||w)&&(o.push({id:c,type:"position",position:{x:l.position.x-g+x,y:l.position.y-v+w}}),(C=n.get(c))==null||C.forEach(E=>{e.some(R=>R.id===E.id)||o.push({id:E.id,type:"position",position:{x:E.position.x+g,y:E.position.y+v}})})),(f.width<a.width||f.height<a.height||g||v)&&o.push({id:c,type:"dimensions",setAttributes:!0,dimensions:{width:b+(g?p0*g-x:0),height:_+(v?p1*v-w:0)}})}),o}function gue(e,t,n,r,o,i){const s=r==null?void 0:r.querySelector(".xyflow__viewport");let a=!1;if(!s)return{changes:,updatedInternals:a};const l=,c=window.getComputedStyle(s),{m22:d}=new window.DOMMatrixReadOnly(c.transform),f=;for(const p of e.values()){const g=t.get(p.id);if(!g)continue;if(g.hidden){t.set(g.id,{...g,internals:{...g.internals,handleBounds:void 0}}),a=!0;continue}const v=jk(p.nodeElement),b=g.measured.width!==v.width||g.measured.height!==v.height;if(!!(v.width&&v.height&&(b||!g.internals.handleBounds||p.force))){const x=p.nodeElement.getBoundingClientRect(),w=Tf(g.extent)?g.extent:i;let{positionAbsolute:C}=g.internals;g.parentId&&g.extent==="parent"?C=XB(C,v,t.get(g.parentId)):w&&(C=Lu(C,w,v));const E={...g,measured:v,internals:{...g.internals,positionAbsolute:C,handleBounds:{source:KL("source",p.nodeElement,x,d,g.id),target:KL("target",p.nodeElement,x,d,g.id)}}};t.set(g.id,E),g.parentId&&Fk(E,t,n,{nodeOrigin:o}),a=!0,b&&(l.push({id:g.id,type:"dimensions",dimensions:v}),g.expandParent&&g.parentId&&f.push({id:g.id,parentId:g.parentId,rect:Rf(E,o)}))}}if(f.length>0){const p=$k(f,t,n,o);l.push(...p)}return{changes:l,updatedInternals:a}}async function vue({delta:e,panZoom:t,transform:n,translateExtent:r,width:o,height:i}){if(!t||!e.x&&!e.y)return Promise.resolve(!1);const s=await t.setViewportConstrained({x:n0+e.x,y:n1+e.y,zoom:n2},0,0,o,i,r),a=!!s&&(s.x!==n0||s.y!==n1||s.k!==n2);return Promise.resolve(a)}function nO(e,t,n,r,o,i){let s=o;const a=r.get(s)||new Map;r.set(s,a.set(n,t)),s=`${o}-${e}`;const l=r.get(s)||new Map;if(r.set(s,l.set(n,t)),i){s=`${o}-${e}-${i}`;const c=r.get(s)||new Map;r.set(s,c.set(n,t))}}function a8(e,t,n){e.clear(),t.clear();for(const r of n){const{source:o,target:i,sourceHandle:s=null,targetHandle:a=null}=r,l={edgeId:r.id,source:o,target:i,sourceHandle:s,targetHandle:a},c=`${o}-${s}--${i}-${a}`,d=`${i}-${a}--${o}-${s}`;nO("source",l,d,e,o,s),nO("target",l,c,e,i,a),t.set(r.id,r)}}function l8(e,t){if(!e.parentId)return!1;const n=t.get(e.parentId);return n?n.selected?!0:l8(n,t):!1}function rO(e,t,n){var o;let r=e;do{if((o=r==null?void 0:r.matches)!=null&&o.call(r,t))return!0;if(r===n)return!1;r=r==null?void 0:r.parentElement}while(r);return!1}function yue(e,t,n,r){const o=new Map;for(consti,sof e)if((s.selected||s.id===r)&&(!s.parentId||!l8(s,e))&&(s.draggable||t&&typeof s.draggable>"u")){const a=e.get(i);a&&o.set(i,{id:i,position:a.position||{x:0,y:0},distance:{x:n.x-a.internals.positionAbsolute.x,y:n.y-a.internals.positionAbsolute.y},extent:a.extent,parentId:a.parentId,origin:a.origin,expandParent:a.expandParent,internals:{positionAbsolute:a.internals.positionAbsolute||{x:0,y:0}},measured:{width:a.measured.width??0,height:a.measured.height??0}})}return o}function V1({nodeId:e,dragItems:t,nodeLookup:n,dragging:r=!0}){var s,a,l;const o=;for(constc,dof t){const f=(s=n.get(c))==null?void 0:s.internals.userNode;f&&o.push({...f,position:d.position,dragging:r})}if(!e)returno0,o;const i=(a=n.get(e))==null?void 0:a.internals.userNode;returni?{...i,position:((l=t.get(e))==null?void 0:l.position)||i.position,dragging:r}:o0,o}function xue({onNodeMouseDown:e,getStoreItems:t,onDragStart:n,onDrag:r,onDragStop:o}){let i={x:null,y:null},s=0,a=new Map,l=!1,c={x:0,y:0},d=null,f=!1,p=null,g=!1,v=!1;function b({noDragClassName:x,handleSelector:w,domNode:C,isSelectable:E,nodeId:R,nodeClickDistance:P=0}){p=Ko(C);function N({x:j,y:H},q){const{nodeLookup:M,nodeExtent:B,snapGrid:$,snapToGrid:W,nodeOrigin:G,onNodeDrag:z,onSelectionDrag:K,onError:Q,updateNodePositions:re}=t();i={x:j,y:H};let ae=!1,de={x:0,y:0,x2:0,y2:0};if(a.size>1&&B){const Ne=Kf(a);de=gR(Ne)}for(constNe,yeof a){if(!M.has(Ne))continue;let fe={x:j-ye.distance.x,y:H-ye.distance.y};W&&(fe=jx(fe,$));let Z=B00,B01,B10,B11;if(a.size>1&&B&&!ye.extent){const{positionAbsolute:xe}=ye.internals,ge=xe.x-de.x+B00,pe=xe.x+ye.measured.width-de.x2+B10,he=xe.y-de.y+B01,we=xe.y+ye.measured.height-de.y2+B11;Z=ge,he,pe,we}const{position:oe,positionAbsolute:ce}=KB({nodeId:Ne,nextPosition:fe,nodeLookup:M,nodeExtent:Z,nodeOrigin:G,onError:Q});ae=ae||ye.position.x!==oe.x||ye.position.y!==oe.y,ye.position=oe,ye.internals.positionAbsolute=ce}if(v=v||ae,!!ae&&(re(a,!0),q&&(r||z||!R&&K))){constNe,ye=V1({nodeId:R,dragItems:a,nodeLookup:M});r==null||r(q,a,Ne,ye),z==null||z(q,Ne,ye),R||K==null||K(q,ye)}}async function k(){if(!d)return;const{transform:j,panBy:H,autoPanSpeed:q,autoPanOnNodeDrag:M}=t();if(!M){l=!1,cancelAnimationFrame(s);return}constB,$=ZB(c,d,q);(B!==0||$!==0)&&(i.x=(i.x??0)-B/j2,i.y=(i.y??0)-$/j2,await H({x:B,y:$})&&N(i,null)),s=requestAnimationFrame(k)}function I(j){var ae;const{nodeLookup:H,multiSelectionActive:q,nodesDraggable:M,transform:B,snapGrid:$,snapToGrid:W,selectNodesOnDrag:G,onNodeDragStart:z,onSelectionDragStart:K,unselectNodesAndEdges:Q}=t();f=!0,(!G||!E)&&!q&&R&&((ae=H.get(R))!=null&&ae.selected||Q()),E&&G&&R&&(e==null||e(R));const re=Up(j.sourceEvent,{transform:B,snapGrid:$,snapToGrid:W,containerBounds:d});if(i=re,a=yue(H,M,re,R),a.size>0&&(n||z||!R&&K)){constde,Ne=V1({nodeId:R,dragItems:a,nodeLookup:H});n==null||n(j.sourceEvent,a,de,Ne),z==null||z(j.sourceEvent,de,Ne),R||K==null||K(j.sourceEvent,Ne)}}const O=TB().clickDistance(P).on("start",j=>{const{domNode:H,nodeDragThreshold:q,transform:M,snapGrid:B,snapToGrid:$}=t();d=(H==null?void 0:H.getBoundingClientRect())||null,g=!1,v=!1,q===0&&I(j),i=Up(j.sourceEvent,{transform:M,snapGrid:B,snapToGrid:$,containerBounds:d}),c=Zs(j.sourceEvent,d)}).on("drag",j=>{const{autoPanOnNodeDrag:H,transform:q,snapGrid:M,snapToGrid:B,nodeDragThreshold:$,nodeLookup:W}=t(),G=Up(j.sourceEvent,{transform:q,snapGrid:M,snapToGrid:B,containerBounds:d});if((j.sourceEvent.type==="touchmove"&&j.sourceEvent.touches.length>1||R&&!W.has(R))&&(g=!0),!g){if(!l&&H&&f&&(l=!0,k()),!f){const z=G.xSnapped-(i.x??0),K=G.ySnapped-(i.y??0);Math.sqrt(z*z+K*K)>$&&I(j)}(i.x!==G.xSnapped||i.y!==G.ySnapped)&&a&&f&&(c=Zs(j.sourceEvent,d),N(G,j.sourceEvent))}}).on("end",j=>{if(!(!f||g)&&(l=!1,f=!1,cancelAnimationFrame(s),a.size>0)){const{nodeLookup:H,updateNodePositions:q,onNodeDragStop:M,onSelectionDragStop:B}=t();if(v&&(q(a,!1),v=!1),o||M||!R&&B){const$,W=V1({nodeId:R,dragItems:a,nodeLookup:H,dragging:!1});o==null||o(j.sourceEvent,a,$,W),M==null||M(j.sourceEvent,$,W),R||B==null||B(j.sourceEvent,W)}}}).filter(j=>{const H=j.target;return!j.button&&(!x||!rO(H,`.${x}`,C))&&(!w||rO(H,w,C))});p.call(O)}function _(){p==null||p.on(".drag",null)}return{update:b,destroy:_}}function bue(e,t,n){const r=,o={x:e.x-n,y:e.y-n,width:n*2,height:n*2};for(const i of t.values())Sm(o,Rf(i))>0&&r.push(i);return r}const wue=250;function Sue(e,t,n,r){var a,l;let o=,i=1/0;const s=bue(e,n,t+wue);for(const c of s){const d=...((a=c.internals.handleBounds)==null?void 0:a.source)??,...((l=c.internals.handleBounds)==null?void 0:l.target)??;for(const f of d){if(r.nodeId===f.nodeId&&r.type===f.type&&r.id===f.id)continue;const{x:p,y:g}=_m(c,f,f.position,!0),v=Math.sqrt(Math.pow(p-e.x,2)+Math.pow(g-e.y,2));v>t||(v<i?(o={...f,x:p,y:g},i=v):v===i&&o.push({...f,x:p,y:g}))}}if(!o.length)return null;if(o.length>1){const c=r.type==="source"?"target":"source";return o.find(d=>d.type===c)??o0}return o0}function c8(e,t,n,r,o,i=!1){var c,d,f;const s=r.get(e);if(!s)return null;const a=o==="strict"?(c=s.internals.handleBounds)==null?void 0:ct:...((d=s.internals.handleBounds)==null?void 0:d.source)??,...((f=s.internals.handleBounds)==null?void 0:f.target)??,l=(n?a==null?void 0:a.find(p=>p.id===n):a==null?void 0:a0)??null;return l&&i?{...l,..._m(s,l,l.position,!0)}:l}function u8(e,t){return e||(t!=null&&t.classList.contains("target")?"target":t!=null&&t.classList.contains("source")?"source":null)}function _ue(e,t){let n=null;return t?n=!0:e&&!t&&(n=!1),n}const d8=()=>!0;function Cue(e,{connectionMode:t,connectionRadius:n,handleId:r,nodeId:o,edgeUpdaterType:i,isTarget:s,domNode:a,nodeLookup:l,lib:c,autoPanOnConnect:d,flowId:f,panBy:p,cancelConnection:g,onConnectStart:v,onConnect:b,onConnectEnd:_,isValidConnection:x=d8,onReconnectEnd:w,updateConnection:C,getTransform:E,getFromHandle:R,autoPanSpeed:P,dragThreshold:N=1}){const k=e8(e.target);let I=0,O;const{x:j,y:H}=Zs(e),q=k==null?void 0:k.elementFromPoint(j,H),M=u8(i,q),B=a==null?void 0:a.getBoundingClientRect();let $=!1;if(!B||!M)return;const W=c8(o,M,r,l,t);if(!W)return;let G=Zs(e,B),z=!1,K=null,Q=!1,re=null;function ae(){if(!d||!B)return;constxe,ge=ZB(G,B,P);p({x:xe,y:ge}),I=requestAnimationFrame(ae)}const de={...W,nodeId:o,type:M,position:W.position},Ne=l.get(o);let fe={inProgress:!0,isValid:null,from:_m(Ne,de,Ve.Left,!0),fromHandle:de,fromPosition:de.position,fromNode:Ne,to:G,toHandle:null,toPosition:qLde.position,toNode:null};function Z(){$=!0,C(fe),v==null||v(e,{nodeId:o,handleId:r,handleType:M})}N===0&&Z();function oe(xe){if(!$){const{x:we,y:Ie}=Zs(xe),Ce=we-j,Me=Ie-H;if(!(Ce*Ce+Me*Me>N*N))return;Z()}if(!R()||!de){ce(xe);return}const ge=E();G=Zs(xe,B),O=Sue(Xm(G,ge,!1,1,1),n,l,de),z||(ae(),z=!0);const pe=f8(xe,{handle:O,connectionMode:t,fromNodeId:o,fromHandleId:r,fromType:s?"target":"source",isValidConnection:x,doc:k,lib:c,flowId:f,nodeLookup:l});re=pe.handleDomNode,K=pe.connection,Q=_ue(!!O,pe.isValid);const he={...fe,isValid:Q,to:pe.toHandle&&Q?Sy({x:pe.toHandle.x,y:pe.toHandle.y},ge):G,toHandle:pe.toHandle,toPosition:Q&&pe.toHandle?pe.toHandle.position:qLde.position,toNode:pe.toHandle?l.get(pe.toHandle.nodeId):null};Q&&O&&fe.toHandle&&he.toHandle&&fe.toHandle.type===he.toHandle.type&&fe.toHandle.nodeId===he.toHandle.nodeId&&fe.toHandle.id===he.toHandle.id&&fe.to.x===he.to.x&&fe.to.y===he.to.y||(C(he),fe=he)}function ce(xe){if($){(O||re)&&K&&Q&&(b==null||b(K));const{inProgress:ge,...pe}=fe,he={...pe,toPosition:fe.toHandle?fe.toPosition:null};_==null||_(xe,he),i&&(w==null||w(xe,he))}g(),cancelAnimationFrame(I),z=!1,Q=!1,K=null,re=null,k.removeEventListener("mousemove",oe),k.removeEventListener("mouseup",ce),k.removeEventListener("touchmove",oe),k.removeEventListener("touchend",ce)}k.addEventListener("mousemove",oe),k.addEventListener("mouseup",ce),k.addEventListener("touchmove",oe),k.addEventListener("touchend",ce)}function f8(e,{handle:t,connectionMode:n,fromNodeId:r,fromHandleId:o,fromType:i,doc:s,lib:a,flowId:l,isValidConnection:c=d8,nodeLookup:d}){const f=i==="target",p=t?s.querySelector(`.${a}-flow__handledata-id="${l}-${t==null?void 0:t.nodeId}-${t==null?void 0:t.id}-${t==null?void 0:t.type}"`):null,{x:g,y:v}=Zs(e),b=s.elementFromPoint(g,v),_=b!=null&&b.classList.contains(`${a}-flow__handle`)?b:p,x={handleDomNode:_,isValid:!1,connection:null,toHandle:null};if(_){const w=u8(void 0,_),C=_.getAttribute("data-nodeid"),E=_.getAttribute("data-handleid"),R=_.classList.contains("connectable"),P=_.classList.contains("connectableend");if(!C||!w)return x;const N={source:f?C:r,sourceHandle:f?E:o,target:f?r:C,targetHandle:f?o:E};x.connection=N;const I=R&&P&&(n===Ef.Strict?f&&w==="source"||!f&&w==="target":C!==r||E!==o);x.isValid=I&&c(N),x.toHandle=c8(C,w,E,d,n,!0)}return x}const bR={onPointerDown:Cue,isValid:f8};function Eue({domNode:e,panZoom:t,getTransform:n,getViewScale:r}){const o=Ko(e);function i({translateExtent:a,width:l,height:c,zoomStep:d=10,pannable:f=!0,zoomable:p=!0,inversePan:g=!1}){const v=C=>{const E=n();if(C.sourceEvent.type!=="wheel"||!t)return;const R=-C.sourceEvent.deltaY*(C.sourceEvent.deltaMode===1?.05:C.sourceEvent.deltaMode?1:.002)*d,P=E2*Math.pow(2,R);t.scaleTo(P)};let b=0,0;const _=C=>{(C.sourceEvent.type==="mousedown"||C.sourceEvent.type==="touchstart")&&(b=C.sourceEvent.clientX??C.sourceEvent.touches0.clientX,C.sourceEvent.clientY??C.sourceEvent.touches0.clientY)},x=C=>{const E=n();if(C.sourceEvent.type!=="mousemove"&&C.sourceEvent.type!=="touchmove"||!t)return;const R=C.sourceEvent.clientX??C.sourceEvent.touches0.clientX,C.sourceEvent.clientY??C.sourceEvent.touches0.clientY,P=R0-b0,R1-b1;b=R;const N=r()*Math.max(E2,Math.log(E2))*(g?-1:1),k={x:E0-P0*N,y:E1-P1*N},I=0,0,l,c;t.setViewportConstrained({x:k.x,y:k.y,zoom:E2},I,a)},w=WB().on("start",_).on("zoom",f?x:null).on("zoom.wheel",p?v:null);o.call(w,{})}function s(){o.on("zoom",null)}return{update:i,destroy:s,pointer:es}}const Nue=(e,t)=>e.x!==t.x||e.y!==t.y||e.zoom!==t.k,Lx=e=>({x:e.x,y:e.y,zoom:e.k}),Y1=({x:e,y:t,zoom:n})=>Ix.translate(e,t).scale(n),Yd=(e,t)=>e.target.closest(`.${t}`),h8=(e,t)=>t===2&&Array.isArray(e)&&e.includes(2),Rue=e=>((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2,K1=(e,t=0,n=Rue,r=()=>{})=>{const o=typeof t=="number"&&t>0;return o||r(),o?e.transition().duration(t).ease(n).on("end",r):e},p8=e=>{const t=e.ctrlKey&&_y()?10:1;return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*t};function Tue({zoomPanValues:e,noWheelClassName:t,d3Selection:n,d3Zoom:r,panOnScrollMode:o,panOnScrollSpeed:i,zoomOnPinch:s,onPanZoomStart:a,onPanZoom:l,onPanZoomEnd:c}){return d=>{if(Yd(d,t))return!1;d.preventDefault(),d.stopImmediatePropagation();const f=n.property("__zoom").k||1;if(d.ctrlKey&&s){const _=es(d),x=p8(d),w=f*Math.pow(2,x);r.scaleTo(n,w,_,d);return}const p=d.deltaMode===1?20:1;let g=o===_u.Vertical?0:d.deltaX*p,v=o===_u.Horizontal?0:d.deltaY*p;!_y()&&d.shiftKey&&o!==_u.Vertical&&(g=d.deltaY*p,v=0),r.translateBy(n,-(g/f)*i,-(v/f)*i,{internal:!0});const b=Lx(n.property("__zoom"));clearTimeout(e.panScrollTimeout),e.isPanScrolling||(e.isPanScrolling=!0,a==null||a(d,b)),e.isPanScrolling&&(l==null||l(d,b),e.panScrollTimeout=setTimeout(()=>{c==null||c(d,b),e.isPanScrolling=!1},150))}}function kue({noWheelClassName:e,preventScrolling:t,d3ZoomHandler:n}){return function(r,o){const i=r.type==="wheel",s=!t&&i&&!r.ctrlKey,a=Yd(r,e);if(r.ctrlKey&&i&&a&&r.preventDefault(),s||a)return null;r.preventDefault(),n.call(this,r,o)}}function Pue({zoomPanValues:e,onDraggingChange:t,onPanZoomStart:n}){return r=>{var i,s,a;if((i=r.sourceEvent)!=null&&i.internal)return;const o=Lx(r.transform);e.mouseButton=((s=r.sourceEvent)==null?void 0:s.button)||0,e.isZoomingOrPanning=!0,e.prevViewport=o,((a=r.sourceEvent)==null?void 0:a.type)==="mousedown"&&t(!0),n&&(n==null||n(r.sourceEvent,o))}}function Iue({zoomPanValues:e,panOnDrag:t,onPaneContextMenu:n,onTransformChange:r,onPanZoom:o}){return i=>{var s,a;e.usedRightMouseButton=!!(n&&h8(t,e.mouseButton??0)),(s=i.sourceEvent)!=null&&s.sync||r(i.transform.x,i.transform.y,i.transform.k),o&&!((a=i.sourceEvent)!=null&&a.internal)&&(o==null||o(i.sourceEvent,Lx(i.transform)))}}function Aue({zoomPanValues:e,panOnDrag:t,panOnScroll:n,onDraggingChange:r,onPanZoomEnd:o,onPaneContextMenu:i}){return s=>{var a;if(!((a=s.sourceEvent)!=null&&a.internal)&&(e.isZoomingOrPanning=!1,i&&h8(t,e.mouseButton??0)&&!e.usedRightMouseButton&&s.sourceEvent&&i(s.sourceEvent),e.usedRightMouseButton=!1,r(!1),o&&Nue(e.prevViewport,s.transform))){const l=Lx(s.transform);e.prevViewport=l,clearTimeout(e.timerId),e.timerId=setTimeout(()=>{o==null||o(s.sourceEvent,l)},n?150:0)}}}function Mue({zoomActivationKeyPressed:e,zoomOnScroll:t,zoomOnPinch:n,panOnDrag:r,panOnScroll:o,zoomOnDoubleClick:i,userSelectionActive:s,noWheelClassName:a,noPanClassName:l,lib:c}){return d=>{var v;const f=e||t,p=n&&d.ctrlKey;if(d.button===1&&d.type==="mousedown"&&(Yd(d,`${c}-flow__node`)||Yd(d,`${c}-flow__edge`)))return!0;if(!r&&!f&&!o&&!i&&!n||s||Yd(d,a)&&d.type==="wheel"||Yd(d,l)&&(d.type!=="wheel"||o&&d.type==="wheel"&&!e)||!n&&d.ctrlKey&&d.type==="wheel")return!1;if(!n&&d.type==="touchstart"&&((v=d.touches)==null?void 0:v.length)>1)return d.preventDefault(),!1;if(!f&&!o&&!p&&d.type==="wheel"||!r&&(d.type==="mousedown"||d.type==="touchstart")||Array.isArray(r)&&!r.includes(d.button)&&d.type==="mousedown")return!1;const g=Array.isArray(r)&&r.includes(d.button)||!d.button||d.button<=1;return(!d.ctrlKey||d.type==="wheel")&&g}}function jue({domNode:e,minZoom:t,maxZoom:n,paneClickDistance:r,translateExtent:o,viewport:i,onPanZoom:s,onPanZoomStart:a,onPanZoomEnd:l,onDraggingChange:c}){const d={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{x:0,y:0,zoom:0},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},f=e.getBoundingClientRect(),p=WB().clickDistance(!ls(r)||r<0?0:r).scaleExtent(t,n).translateExtent(o),g=Ko(e).call(p);C({x:i.x,y:i.y,zoom:Nf(i.zoom,t,n)},0,0,f.width,f.height,o);const v=g.on("wheel.zoom"),b=g.on("dblclick.zoom");p.wheelDelta(p8);function _(H,q){return g?new Promise(M=>{p==null||p.interpolate((q==null?void 0:q.interpolate)==="linear"?Wp:T0).transform(K1(g,q==null?void 0:q.duration,q==null?void 0:q.ease,()=>M(!0)),H)}):Promise.resolve(!1)}function x({noWheelClassName:H,noPanClassName:q,onPaneContextMenu:M,userSelectionActive:B,panOnScroll:$,panOnDrag:W,panOnScrollMode:G,panOnScrollSpeed:z,preventScrolling:K,zoomOnPinch:Q,zoomOnScroll:re,zoomOnDoubleClick:ae,zoomActivationKeyPressed:de,lib:Ne,onTransformChange:ye}){B&&!d.isZoomingOrPanning&&w();const Z=$&&!de&&!B?Tue({zoomPanValues:d,noWheelClassName:H,d3Selection:g,d3Zoom:p,panOnScrollMode:G,panOnScrollSpeed:z,zoomOnPinch:Q,onPanZoomStart:a,onPanZoom:s,onPanZoomEnd:l}):kue({noWheelClassName:H,preventScrolling:K,d3ZoomHandler:v});if(g.on("wheel.zoom",Z,{passive:!1}),!B){const ce=Pue({zoomPanValues:d,onDraggingChange:c,onPanZoomStart:a});p.on("start",ce);const xe=Iue({zoomPanValues:d,panOnDrag:W,onPaneContextMenu:!!M,onPanZoom:s,onTransformChange:ye});p.on("zoom",xe);const ge=Aue({zoomPanValues:d,panOnDrag:W,panOnScroll:$,onPaneContextMenu:M,onPanZoomEnd:l,onDraggingChange:c});p.on("end",ge)}const oe=Mue({zoomActivationKeyPressed:de,panOnDrag:W,zoomOnScroll:re,panOnScroll:$,zoomOnDoubleClick:ae,zoomOnPinch:Q,userSelectionActive:B,noPanClassName:q,noWheelClassName:H,lib:Ne});p.filter(oe),ae?g.on("dblclick.zoom",b):g.on("dblclick.zoom",null)}function w(){p.on("zoom",null)}async function C(H,q,M){const B=Y1(H),$=p==null?void 0:p.constrain()(B,q,M);return $&&await _($),new Promise(W=>W($))}async function E(H,q){const M=Y1(H);return await _(M,q),new Promise(B=>B(M))}function R(H){if(g){const q=Y1(H),M=g.property("__zoom");(M.k!==H.zoom||M.x!==H.x||M.y!==H.y)&&(p==null||p.transform(g,q,null,{sync:!0}))}}function P(){const H=g?BB(g.node()):{x:0,y:0,k:1};return{x:H.x,y:H.y,zoom:H.k}}function N(H,q){return g?new Promise(M=>{p==null||p.interpolate((q==null?void 0:q.interpolate)==="linear"?Wp:T0).scaleTo(K1(g,q==null?void 0:q.duration,q==null?void 0:q.ease,()=>M(!0)),H)}):Promise.resolve(!1)}function k(H,q){return g?new Promise(M=>{p==null||p.interpolate((q==null?void 0:q.interpolate)==="linear"?Wp:T0).scaleBy(K1(g,q==null?void 0:q.duration,q==null?void 0:q.ease,()=>M(!0)),H)}):Promise.resolve(!1)}function I(H){p==null||p.scaleExtent(H)}function O(H){p==null||p.translateExtent(H)}function j(H){const q=!ls(H)||H<0?0:H;p==null||p.clickDistance(q)}return{update:x,destroy:w,setViewport:E,setViewportConstrained:C,getViewport:P,scaleTo:N,scaleBy:k,setScaleExtent:I,setTranslateExtent:O,syncViewport:R,setClickDistance:j}}var kf;(function(e){e.Line="line",e.Handle="handle"})(kf||(kf={}));function Lue({width:e,prevWidth:t,height:n,prevHeight:r,affectsX:o,affectsY:i}){const s=e-t,a=n-r,l=s>0?1:s<0?-1:0,a>0?1:a<0?-1:0;return s&&o&&(l0=l0*-1),a&&i&&(l1=l1*-1),l}function Oue(e){const t=e.includes("right")||e.includes("left"),n=e.includes("bottom")||e.includes("top"),r=e.includes("left"),o=e.includes("top");return{isHorizontal:t,isVertical:n,affectsX:r,affectsY:o}}function Dl(e,t){return Math.max(0,t-e)}function Fl(e,t){return Math.max(0,e-t)}function Uv(e,t,n){return Math.max(0,t-e,e-n)}function oO(e,t){return e?!t:t}function Due(e,t,n,r,o,i,s,a){let{affectsX:l,affectsY:c}=t;const{isHorizontal:d,isVertical:f}=t,p=d&&f,{xSnapped:g,ySnapped:v}=n,{minWidth:b,maxWidth:_,minHeight:x,maxHeight:w}=r,{x:C,y:E,width:R,height:P,aspectRatio:N}=e;let k=Math.floor(d?g-e.pointerX:0),I=Math.floor(f?v-e.pointerY:0);const O=R+(l?-k:k),j=P+(c?-I:I),H=-i0*R,q=-i1*P;let M=Uv(O,b,_),B=Uv(j,x,w);if(s){let G=0,z=0;l&&k<0?G=Dl(C+k+H,s00):!l&&k>0&&(G=Fl(C+O+H,s10)),c&&I<0?z=Dl(E+I+q,s01):!c&&I>0&&(z=Fl(E+j+q,s11)),M=Math.max(M,G),B=Math.max(B,z)}if(a){let G=0,z=0;l&&k>0?G=Fl(C+k,a00):!l&&k<0&&(G=Dl(C+O,a10)),c&&I>0?z=Fl(E+I,a01):!c&&I<0&&(z=Dl(E+j,a11)),M=Math.max(M,G),B=Math.max(B,z)}if(o){if(d){const G=Uv(O/N,x,w)*N;if(M=Math.max(M,G),s){let z=0;!l&&!c||l&&!c&&p?z=Fl(E+q+O/N,s11)*N:z=Dl(E+q+(l?k:-k)/N,s01)*N,M=Math.max(M,z)}if(a){let z=0;!l&&!c||l&&!c&&p?z=Dl(E+O/N,a11)*N:z=Fl(E+(l?k:-k)/N,a01)*N,M=Math.max(M,z)}}if(f){const G=Uv(j*N,b,_)/N;if(B=Math.max(B,G),s){let z=0;!l&&!c||c&&!l&&p?z=Fl(C+j*N+H,s10)/N:z=Dl(C+(c?I:-I)*N+H,s00)/N,B=Math.max(B,z)}if(a){let z=0;!l&&!c||c&&!l&&p?z=Dl(C+j*N,a10)/N:z=Fl(C+(c?I:-I)*N,a00)/N,B=Math.max(B,z)}}}I=I+(I<0?B:-B),k=k+(k<0?M:-M),o&&(p?O>j*N?I=(oO(l,c)?-k:k)/N:k=(oO(l,c)?-I:I)*N:d?(I=k/N,c=l):(k=I*N,l=c));const $=l?C+k:C,W=c?E+I:E;return{width:R+(l?-k:k),height:P+(c?-I:I),x:i0*k*(l?-1:1)+$,y:i1*I*(c?-1:1)+W}}const m8={width:0,height:0,x:0,y:0},Fue={...m8,pointerX:0,pointerY:0,aspectRatio:1};function $ue(e){return0,0,e.measured.width,e.measured.height}function zue(e,t,n){const r=t.position.x+e.position.x,o=t.position.y+e.position.y,i=e.measured.width??0,s=e.measured.height??0,a=n0*i,l=n1*s;returnr-a,o-l,r+i-a,o+s-l}function Hue({domNode:e,nodeId:t,getStoreItems:n,onChange:r,onEnd:o}){const i=Ko(e);function s({controlPosition:l,boundaries:c,keepAspectRatio:d,resizeDirection:f,onResizeStart:p,onResize:g,onResizeEnd:v,shouldResize:b}){let _={...m8},x={...Fue};const w=Oue(l);let C,E=null,R=,P,N,k;const I=TB().on("start",O=>{const{nodeLookup:j,transform:H,snapGrid:q,snapToGrid:M,nodeOrigin:B,paneDomNode:$}=n();if(C=j.get(t),!C)return;E=($==null?void 0:$.getBoundingClientRect())??null;const{xSnapped:W,ySnapped:G}=Up(O.sourceEvent,{transform:H,snapGrid:q,snapToGrid:M,containerBounds:E});_={width:C.measured.width??0,height:C.measured.height??0,x:C.position.x??0,y:C.position.y??0},x={..._,pointerX:W,pointerY:G,aspectRatio:_.width/_.height},P=void 0,C.parentId&&(C.extent==="parent"||C.expandParent)&&(P=j.get(C.parentId),N=P&&C.extent==="parent"?$ue(P):void 0),R=,k=void 0;for(constz,Kof j)if(K.parentId===t&&(R.push({id:z,position:{...K.position},extent:K.extent}),K.extent==="parent"||K.expandParent)){const Q=zue(K,C,K.origin??B);k?k=Math.min(Q00,k00),Math.min(Q01,k01),Math.max(Q10,k10),Math.max(Q11,k11):k=Q}p==null||p(O,{..._})}).on("drag",O=>{const{transform:j,snapGrid:H,snapToGrid:q,nodeOrigin:M}=n(),B=Up(O.sourceEvent,{transform:j,snapGrid:H,snapToGrid:q,containerBounds:E}),$=;if(!C)return;const{x:W,y:G,width:z,height:K}=_,Q={},re=C.origin??M,{width:ae,height:de,x:Ne,y:ye}=Due(x,w,B,c,d,re,N,k),fe=ae!==z,Z=de!==K,oe=Ne!==W&&fe,ce=ye!==G&&Z;if(!oe&&!ce&&!fe&&!Z)return;if((oe||ce||re0===1||re1===1)&&(Q.x=oe?Ne:_.x,Q.y=ce?ye:_.y,_.x=Q.x,_.y=Q.y,R.length>0)){const he=Ne-W,we=ye-G;for(const Ie of R)Ie.position={x:Ie.position.x-he+re0*(ae-z),y:Ie.position.y-we+re1*(de-K)},$.push(Ie)}if((fe||Z)&&(Q.width=fe&&(!f||f==="horizontal")?ae:_.width,Q.height=Z&&(!f||f==="vertical")?de:_.height,_.width=Q.width,_.height=Q.height),P&&C.expandParent){const he=re0*(Q.width??0);Q.x&&Q.x<he&&(_.x=he,x.x=x.x-(Q.x-he));const we=re1*(Q.height??0);Q.y&&Q.y<we&&(_.y=we,x.y=x.y-(Q.y-we))}const xe=Lue({width:_.width,prevWidth:z,height:_.height,prevHeight:K,affectsX:w.affectsX,affectsY:w.affectsY}),ge={..._,direction:xe};(b==null?void 0:b(O,ge))!==!1&&(g==null||g(O,ge),r(Q,$))}).on("end",O=>{v==null||v(O,{..._}),o==null||o({..._})});i.call(I)}function a(){i.on(".drag",null)}return{update:s,destroy:a}}var g8={exports:{}},v8={},y8={exports:{}},x8={};/** + * @license React + * use-sync-external-store-shim.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Pf=y;function Bue(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var Wue=typeof Object.is=="function"?Object.is:Bue,Uue=Pf.useState,que=Pf.useEffect,Gue=Pf.useLayoutEffect,Vue=Pf.useDebugValue;function Yue(e,t){var n=t(),r=Uue({inst:{value:n,getSnapshot:t}}),o=r0.inst,i=r1;return Gue(function(){o.value=n,o.getSnapshot=t,X1(o)&&i({inst:o})},e,n,t),que(function(){return X1(o)&&i({inst:o}),e(function(){X1(o)&&i({inst:o})})},e),Vue(n),n}function X1(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!Wue(e,n)}catch{return!0}}function Kue(e,t){return t()}var Xue=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?Kue:Yue;x8.useSyncExternalStore=Pf.useSyncExternalStore!==void 0?Pf.useSyncExternalStore:Xue;y8.exports=x8;var Zue=y8.exports;/** + * @license React + * use-sync-external-store-shim/with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Ox=y,Que=Zue;function Jue(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var ede=typeof Object.is=="function"?Object.is:Jue,tde=Que.useSyncExternalStore,nde=Ox.useRef,rde=Ox.useEffect,ode=Ox.useMemo,ide=Ox.useDebugValue;v8.useSyncExternalStoreWithSelector=function(e,t,n,r,o){var i=nde(null);if(i.current===null){var s={hasValue:!1,value:null};i.current=s}else s=i.current;i=ode(function(){function l(g){if(!c){if(c=!0,d=g,g=r(g),o!==void 0&&s.hasValue){var v=s.value;if(o(v,g))return f=v}return f=g}if(v=f,ede(d,g))return v;var b=r(g);return o!==void 0&&o(v,b)?(d=g,v):(d=g,f=b)}var c=!1,d,f,p=n===void 0?null:n;returnfunction(){return l(t())},p===null?void 0:function(){return l(p())}},t,n,r,o);var a=tde(e,i0,i1);return rde(function(){s.hasValue=!0,s.value=a},a),ide(a),a};g8.exports=v8;var sde=g8.exports;const ade=qf(sde),lde={},iO=e=>{let t;const n=new Set,r=(d,f)=>{const p=typeof d=="function"?d(t):d;if(!Object.is(p,t)){const g=t;t=f??(typeof p!="object"||p===null)?p:Object.assign({},t,p),n.forEach(v=>v(t,g))}},o=()=>t,l={setState:r,getState:o,getInitialState:()=>c,subscribe:d=>(n.add(d),()=>n.delete(d)),destroy:()=>{(lde?"production":void 0)!=="production"&&console.warn("DEPRECATED The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},c=t=e(r,o,l);return l},cde=e=>e?iO(e):iO,{useDebugValue:ude}=Pe,{useSyncExternalStoreWithSelector:dde}=ade,fde=e=>e;function b8(e,t=fde,n){const r=dde(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return ude(r),r}const sO=(e,t)=>{const n=cde(e),r=(o,i=t)=>b8(n,o,i);return Object.assign(r,n),r},hde=(e,t)=>e?sO(e,t):sO;function Rn(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(constr,oof e)if(!Object.is(o,t.get(r)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const r of e)if(!t.has(r))return!1;return!0}const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(const r of n)if(!Object.prototype.hasOwnProperty.call(t,r)||!Object.is(er,tr))return!1;return!0}const Dx=y.createContext(null),pde=Dx.Provider,w8=sa.error001();function kt(e,t){const n=y.useContext(Dx);if(n===null)throw new Error(w8);return b8(n,e,t)}function jn(){const e=y.useContext(Dx);if(e===null)throw new Error(w8);return y.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),e)}const aO={display:"none"},mde={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},S8="react-flow__node-desc",_8="react-flow__edge-desc",gde="react-flow__aria-live",vde=e=>e.ariaLiveMessage,yde=e=>e.ariaLabelConfig;function xde({rfId:e}){const t=kt(vde);return h.jsx("div",{id:`${gde}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:mde,children:t})}function bde({rfId:e,disableKeyboardA11y:t}){const n=kt(yde);return h.jsxs(h.Fragment,{children:h.jsx("div",{id:`${S8}-${e}`,style:aO,children:t?n"node.a11yDescription.default":n"node.a11yDescription.keyboardDisabled"}),h.jsx("div",{id:`${_8}-${e}`,style:aO,children:n"edge.a11yDescription.default"}),!t&&h.jsx(xde,{rfId:e})})}const Zm=y.forwardRef(({position:e="top-left",children:t,className:n,style:r,...o},i)=>{const s=`${e}`.split("-");return h.jsx("div",{className:tr("react-flow__panel",n,...s),style:r,ref:i,...o,children:t})});Zm.displayName="Panel";function wde({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:h.jsx(Zm,{position:t,className:"react-flow__attribution","data-message":"Please only hide this attribution when you are subscribed to React Flow Pro: https://pro.reactflow.dev",children:h.jsx("a",{href:"https://reactflow.dev",target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const Sde=e=>{const t=,n=;for(const,rof e.nodeLookup)r.selected&&t.push(r.internals.userNode);for(const,rof e.edgeLookup)r.selected&&n.push(r);return{selectedNodes:t,selectedEdges:n}},qv=e=>e.id;function _de(e,t){return Rn(e.selectedNodes.map(qv),t.selectedNodes.map(qv))&&Rn(e.selectedEdges.map(qv),t.selectedEdges.map(qv))}function Cde({onSelectionChange:e}){const t=jn(),{selectedNodes:n,selectedEdges:r}=kt(Sde,_de);return y.useEffect(()=>{const o={nodes:n,edges:r};e==null||e(o),t.getState().onSelectionChangeHandlers.forEach(i=>i(o))},n,r,e),null}const Ede=e=>!!e.onSelectionChangeHandlers;function Nde({onSelectionChange:e}){const t=kt(Ede);return e||t?h.jsx(Cde,{onSelectionChange:e}):null}const C8=0,0,Rde={x:0,y:0,zoom:1},Tde="nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraggable","autoPanOnNodeFocus","nodesConnectable","nodesFocusable","edgesFocusable","edgesReconnectable","elevateNodesOnSelect","elevateEdgesOnSelect","minZoom","maxZoom","nodeExtent","onNodesChange","onEdgesChange","elementsSelectable","connectionMode","snapGrid","snapToGrid","translateExtent","connectOnClick","defaultEdgeOptions","fitView","fitViewOptions","onNodesDelete","onEdgesDelete","onDelete","onNodeDrag","onNodeDragStart","onNodeDragStop","onSelectionDrag","onSelectionDragStart","onSelectionDragStop","onMoveStart","onMove","onMoveEnd","noPanClassName","nodeOrigin","autoPanOnConnect","autoPanOnNodeDrag","onError","connectionRadius","isValidConnection","selectNodesOnDrag","nodeDragThreshold","connectionDragThreshold","onBeforeDelete","debug","autoPanSpeed","paneClickDistance","ariaLabelConfig",lO=...Tde,"rfId",kde=e=>({setNodes:e.setNodes,setEdges:e.setEdges,setMinZoom:e.setMinZoom,setMaxZoom:e.setMaxZoom,setTranslateExtent:e.setTranslateExtent,setNodeExtent:e.setNodeExtent,reset:e.reset,setDefaultNodesAndEdges:e.setDefaultNodesAndEdges,setPaneClickDistance:e.setPaneClickDistance}),cO={translateExtent:xm,nodeOrigin:C8,minZoom:.5,maxZoom:2,elementsSelectable:!0,noPanClassName:"nopan",rfId:"1",paneClickDistance:0};function Pde(e){const{setNodes:t,setEdges:n,setMinZoom:r,setMaxZoom:o,setTranslateExtent:i,setNodeExtent:s,reset:a,setDefaultNodesAndEdges:l,setPaneClickDistance:c}=kt(kde,Rn),d=jn();y.useEffect(()=>(l(e.defaultNodes,e.defaultEdges),()=>{f.current=cO,a()}),);const f=y.useRef(cO);return y.useEffect(()=>{for(const p of lO){const g=ep,v=f.currentp;g!==v&&(typeof ep>"u"||(p==="nodes"?t(g):p==="edges"?n(g):p==="minZoom"?r(g):p==="maxZoom"?o(g):p==="translateExtent"?i(g):p==="nodeExtent"?s(g):p==="paneClickDistance"?c(g):p==="ariaLabelConfig"?d.setState({ariaLabelConfig:Jce(g)}):p==="fitView"?d.setState({fitViewQueued:g}):p==="fitViewOptions"?d.setState({fitViewOptions:g}):d.setState({p:g})))}f.current=e},lO.map(p=>ep)),null}function uO(){return typeof window>"u"||!window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)")}function Ide(e){var r;constt,n=y.useState(e==="system"?null:e);return y.useEffect(()=>{if(e!=="system"){n(e);return}const o=uO(),i=()=>n(o!=null&&o.matches?"dark":"light");return i(),o==null||o.addEventListener("change",i),()=>{o==null||o.removeEventListener("change",i)}},e),t!==null?t:(r=uO())!=null&&r.matches?"dark":"light"}const dO=typeof document<"u"?document:null;function Cm(e=null,t={target:dO,actInsideInputWithModifier:!0}){constn,r=y.useState(!1),o=y.useRef(!1),i=y.useRef(new Set()),s,a=y.useMemo(()=>{if(e!==null){const c=(Array.isArray(e)?e:e).filter(f=>typeof f=="string").map(f=>f.replace("+",` +`).replace(` + +`,` ++`).split(` +`)),d=c.reduce((f,p)=>f.concat(...p),);returnc,d}return,},e);return y.useEffect(()=>{const l=(t==null?void 0:t.target)??dO,c=(t==null?void 0:t.actInsideInputWithModifier)??!0;if(e!==null){const d=g=>{var _,x;if(o.current=g.ctrlKey||g.metaKey||g.shiftKey||g.altKey,(!o.current||o.current&&!c)&&t8(g))return!1;const b=hO(g.code,a);if(i.current.add(gb),fO(s,i.current,!1)){const w=((x=(_=g.composedPath)==null?void 0:_.call(g))==null?void 0:x0)||g.target,C=(w==null?void 0:w.nodeName)==="BUTTON"||(w==null?void 0:w.nodeName)==="A";t.preventDefault!==!1&&(o.current||!C)&&g.preventDefault(),r(!0)}},f=g=>{const v=hO(g.code,a);fO(s,i.current,!0)?(r(!1),i.current.clear()):i.current.delete(gv),g.key==="Meta"&&i.current.clear(),o.current=!1},p=()=>{i.current.clear(),r(!1)};return l==null||l.addEventListener("keydown",d),l==null||l.addEventListener("keyup",f),window.addEventListener("blur",p),window.addEventListener("contextmenu",p),()=>{l==null||l.removeEventListener("keydown",d),l==null||l.removeEventListener("keyup",f),window.removeEventListener("blur",p),window.removeEventListener("contextmenu",p)}}},e,r),n}function fO(e,t,n){return e.filter(r=>n||r.length===t.size).some(r=>r.every(o=>t.has(o)))}function hO(e,t){return t.includes(e)?"code":"key"}const Ade=()=>{const e=jn();return y.useMemo(()=>({zoomIn:t=>{const{panZoom:n}=e.getState();return n?n.scaleBy(1.2,{duration:t==null?void 0:t.duration}):Promise.resolve(!1)},zoomOut:t=>{const{panZoom:n}=e.getState();return n?n.scaleBy(1/1.2,{duration:t==null?void 0:t.duration}):Promise.resolve(!1)},zoomTo:(t,n)=>{const{panZoom:r}=e.getState();return r?r.scaleTo(t,{duration:n==null?void 0:n.duration}):Promise.resolve(!1)},getZoom:()=>e.getState().transform2,setViewport:async(t,n)=>{const{transform:r,o,i,panZoom:s}=e.getState();return s?(await s.setViewport({x:t.x??r,y:t.y??o,zoom:t.zoom??i},n),Promise.resolve(!0)):Promise.resolve(!1)},getViewport:()=>{constt,n,r=e.getState().transform;return{x:t,y:n,zoom:r}},setCenter:async(t,n,r)=>e.getState().setCenter(t,n,r),fitBounds:async(t,n)=>{const{width:r,height:o,minZoom:i,maxZoom:s,panZoom:a}=e.getState(),l=Ak(t,r,o,i,s,(n==null?void 0:n.padding)??.1);return a?(await a.setViewport(l,{duration:n==null?void 0:n.duration,ease:n==null?void 0:n.ease,interpolate:n==null?void 0:n.interpolate}),Promise.resolve(!0)):Promise.resolve(!1)},screenToFlowPosition:(t,n={})=>{const{transform:r,snapGrid:o,snapToGrid:i,domNode:s}=e.getState();if(!s)return t;const{x:a,y:l}=s.getBoundingClientRect(),c={x:t.x-a,y:t.y-l},d=n.snapGrid??o,f=n.snapToGrid??i;return Xm(c,r,f,d)},flowToScreenPosition:t=>{const{transform:n,domNode:r}=e.getState();if(!r)return t;const{x:o,y:i}=r.getBoundingClientRect(),s=Sy(t,n);return{x:s.x+o,y:s.y+i}}}),)};function E8(e,t){const n=,r=new Map,o=;for(const i of e)if(i.type==="add"){o.push(i);continue}else if(i.type==="remove"||i.type==="replace")r.set(i.id,i);else{const s=r.get(i.id);s?s.push(i):r.set(i.id,i)}for(const i of t){const s=r.get(i.id);if(!s){n.push(i);continue}if(s0.type==="remove")continue;if(s0.type==="replace"){n.push({...s0.item});continue}const a={...i};for(const l of s)Mde(l,a);n.push(a)}return o.length&&o.forEach(i=>{i.index!==void 0?n.splice(i.index,0,{...i.item}):n.push({...i.item})}),n}function Mde(e,t){switch(e.type){case"select":{t.selected=e.selected;break}case"position":{typeof e.position<"u"&&(t.position=e.position),typeof e.dragging<"u"&&(t.dragging=e.dragging);break}case"dimensions":{typeof e.dimensions<"u"&&(t.measured??(t.measured={}),t.measured.width=e.dimensions.width,t.measured.height=e.dimensions.height,e.setAttributes&&((e.setAttributes===!0||e.setAttributes==="width")&&(t.width=e.dimensions.width),(e.setAttributes===!0||e.setAttributes==="height")&&(t.height=e.dimensions.height))),typeof e.resizing=="boolean"&&(t.resizing=e.resizing);break}}}function N8(e,t){return E8(e,t)}function R8(e,t){return E8(e,t)}function ou(e,t){return{id:e,type:"select",selected:t}}function Kd(e,t=new Set,n=!1){const r=;for(consto,iof e){const s=t.has(o);!(i.selected===void 0&&!s)&&i.selected!==s&&(n&&(i.selected=s),r.push(ou(i.id,s)))}return r}function pO({items:e=,lookup:t}){var o;const n=,r=new Map(e.map(i=>i.id,i));for(consti,sof e.entries()){const a=t.get(s.id),l=((o=a==null?void 0:a.internals)==null?void 0:o.userNode)??a;l!==void 0&&l!==s&&n.push({id:s.id,item:s,type:"replace"}),l===void 0&&n.push({item:s,type:"add",index:i})}for(constiof t)r.get(i)===void 0&&n.push({id:i,type:"remove"});return n}function mO(e){return{id:e.id,type:"remove"}}const gO=e=>Wce(e),jde=e=>YB(e);function T8(e){return y.forwardRef(e)}const Lde=typeof window<"u"?y.useLayoutEffect:y.useEffect;function vO(e){constt,n=y.useState(BigInt(0)),r=y.useState(()=>Ode(()=>n(o=>o+BigInt(1))));return Lde(()=>{const o=r.get();o.length&&(e(o),r.reset())},t),r}function Ode(e){let t=;return{get:()=>t,reset:()=>{t=},push:n=>{t.push(n),e()}}}const k8=y.createContext(null);function Dde({children:e}){const t=jn(),n=y.useCallback(a=>{const{nodes:l=,setNodes:c,hasDefaultNodes:d,onNodesChange:f,nodeLookup:p,fitViewQueued:g}=t.getState();let v=l;for(const _ of a)v=typeof _=="function"?_(v):_;const b=pO({items:v,lookup:p});d&&c(v),b.length>0?f==null||f(b):g&&window.requestAnimationFrame(()=>{const{fitViewQueued:_,nodes:x,setNodes:w}=t.getState();_&&w(x)})},),r=vO(n),o=y.useCallback(a=>{const{edges:l=,setEdges:c,hasDefaultEdges:d,onEdgesChange:f,edgeLookup:p}=t.getState();let g=l;for(const v of a)g=typeof v=="function"?v(g):v;d?c(g):f&&f(pO({items:g,lookup:p}))},),i=vO(o),s=y.useMemo(()=>({nodeQueue:r,edgeQueue:i}),);return h.jsx(k8.Provider,{value:s,children:e})}function Fde(){const e=y.useContext(k8);if(!e)throw new Error("useBatchContext must be used within a BatchProvider");return e}const $de=e=>!!e.panZoom;function Qm(){const e=Ade(),t=jn(),n=Fde(),r=kt($de),o=y.useMemo(()=>{const i=f=>t.getState().nodeLookup.get(f),s=f=>{n.nodeQueue.push(f)},a=f=>{n.edgeQueue.push(f)},l=f=>{var x,w;const{nodeLookup:p,nodeOrigin:g}=t.getState(),v=gO(f)?f:p.get(f.id),b=v.parentId?JB(v.position,v.measured,v.parentId,p,g):v.position,_={...v,position:b,width:((x=v.measured)==null?void 0:x.width)??v.width,height:((w=v.measured)==null?void 0:w.height)??v.height};return Rf(_)},c=(f,p,g={replace:!1})=>{s(v=>v.map(b=>{if(b.id===f){const _=typeof p=="function"?p(b):p;return g.replace&&gO(_)?_:{...b,..._}}return b}))},d=(f,p,g={replace:!1})=>{a(v=>v.map(b=>{if(b.id===f){const _=typeof p=="function"?p(b):p;return g.replace&&jde(_)?_:{...b,..._}}return b}))};return{getNodes:()=>t.getState().nodes.map(f=>({...f})),getNode:f=>{var p;return(p=i(f))==null?void 0:p.internals.userNode},getInternalNode:i,getEdges:()=>{const{edges:f=}=t.getState();return f.map(p=>({...p}))},getEdge:f=>t.getState().edgeLookup.get(f),setNodes:s,setEdges:a,addNodes:f=>{const p=Array.isArray(f)?f:f;n.nodeQueue.push(g=>...g,...p)},addEdges:f=>{const p=Array.isArray(f)?f:f;n.edgeQueue.push(g=>...g,...p)},toObject:()=>{const{nodes:f=,edges:p=,transform:g}=t.getState(),v,b,_=g;return{nodes:f.map(x=>({...x})),edges:p.map(x=>({...x})),viewport:{x:v,y:b,zoom:_}}},deleteElements:async({nodes:f=,edges:p=})=>{const{nodes:g,edges:v,onNodesDelete:b,onEdgesDelete:_,triggerNodeChanges:x,triggerEdgeChanges:w,onDelete:C,onBeforeDelete:E}=t.getState(),{nodes:R,edges:P}=await Yce({nodesToRemove:f,edgesToRemove:p,nodes:g,edges:v,onBeforeDelete:E}),N=P.length>0,k=R.length>0;if(N){const I=P.map(mO);_==null||_(P),w(I)}if(k){const I=R.map(mO);b==null||b(R),x(I)}return(k||N)&&(C==null||C({nodes:R,edges:P})),{deletedNodes:R,deletedEdges:P}},getIntersectingNodes:(f,p=!0,g)=>{const v=VL(f),b=v?f:l(f),_=g!==void 0;return b?(g||t.getState().nodes).filter(x=>{const w=t.getState().nodeLookup.get(x.id);if(w&&!v&&(x.id===f.id||!w.internals.positionAbsolute))return!1;const C=Rf(_?x:w),E=Sm(C,b);return p&&E>0||E>=C.width*C.height||E>=b.width*b.height}):},isNodeIntersecting:(f,p,g=!0)=>{const b=VL(f)?f:l(f);if(!b)return!1;const _=Sm(b,p);return g&&_>0||_>=b.width*b.height},updateNode:c,updateNodeData:(f,p,g={replace:!1})=>{c(f,v=>{const b=typeof p=="function"?p(v):p;return g.replace?{...v,data:b}:{...v,data:{...v.data,...b}}},g)},updateEdge:d,updateEdgeData:(f,p,g={replace:!1})=>{d(f,v=>{const b=typeof p=="function"?p(v):p;return g.replace?{...v,data:b}:{...v,data:{...v.data,...b}}},g)},getNodesBounds:f=>{const{nodeLookup:p,nodeOrigin:g}=t.getState();return Uce(f,{nodeLookup:p,nodeOrigin:g})},getHandleConnections:({type:f,id:p,nodeId:g})=>{var v;return Array.from(((v=t.getState().connectionLookup.get(`${g}-${f}${p?`-${p}`:""}`))==null?void 0:v.values())??)},getNodeConnections:({type:f,handleId:p,nodeId:g})=>{var v;return Array.from(((v=t.getState().connectionLookup.get(`${g}${f?p?`-${f}-${p}`:`-${f}`:""}`))==null?void 0:v.values())??)},fitView:async f=>{const p=t.getState().fitViewResolver??Qce();return t.setState({fitViewQueued:!0,fitViewOptions:f,fitViewResolver:p}),n.nodeQueue.push(g=>...g),p.promise}}},);return y.useMemo(()=>({...o,...e,viewportInitialized:r}),r)}const yO=e=>e.selected,zde=typeof window<"u"?window:void 0;function Hde({deleteKeyCode:e,multiSelectionKeyCode:t}){const n=jn(),{deleteElements:r}=Qm(),o=Cm(e,{actInsideInputWithModifier:!1}),i=Cm(t,{target:zde});y.useEffect(()=>{if(o){const{edges:s,nodes:a}=n.getState();r({nodes:a.filter(yO),edges:s.filter(yO)}),n.setState({nodesSelectionActive:!1})}},o),y.useEffect(()=>{n.setState({multiSelectionActive:i})},i)}function Bde(e){const t=jn();y.useEffect(()=>{const n=()=>{var o,i;if(!e.current)return!1;const r=jk(e.current);(r.height===0||r.width===0)&&((i=(o=t.getState()).onError)==null||i.call(o,"004",sa.error004())),t.setState({width:r.width||500,height:r.height||500})};if(e.current){n(),window.addEventListener("resize",n);const r=new ResizeObserver(()=>n());return r.observe(e.current),()=>{window.removeEventListener("resize",n),r&&e.current&&r.unobserve(e.current)}}},)}const Fx={position:"absolute",width:"100%",height:"100%",top:0,left:0},Wde=e=>({userSelectionActive:e.userSelectionActive,lib:e.lib});function Ude({onPaneContextMenu:e,zoomOnScroll:t=!0,zoomOnPinch:n=!0,panOnScroll:r=!1,panOnScrollSpeed:o=.5,panOnScrollMode:i=_u.Free,zoomOnDoubleClick:s=!0,panOnDrag:a=!0,defaultViewport:l,translateExtent:c,minZoom:d,maxZoom:f,zoomActivationKeyCode:p,preventScrolling:g=!0,children:v,noWheelClassName:b,noPanClassName:_,onViewportChange:x,isControlledViewport:w,paneClickDistance:C}){const E=jn(),R=y.useRef(null),{userSelectionActive:P,lib:N}=kt(Wde,Rn),k=Cm(p),I=y.useRef();Bde(R);const O=y.useCallback(j=>{x==null||x({x:j0,y:j1,zoom:j2}),w||E.setState({transform:j})},x,w);return y.useEffect(()=>{if(R.current){I.current=jue({domNode:R.current,minZoom:d,maxZoom:f,translateExtent:c,viewport:l,paneClickDistance:C,onDraggingChange:M=>E.setState({paneDragging:M}),onPanZoomStart:(M,B)=>{const{onViewportChangeStart:$,onMoveStart:W}=E.getState();W==null||W(M,B),$==null||$(B)},onPanZoom:(M,B)=>{const{onViewportChange:$,onMove:W}=E.getState();W==null||W(M,B),$==null||$(B)},onPanZoomEnd:(M,B)=>{const{onViewportChangeEnd:$,onMoveEnd:W}=E.getState();W==null||W(M,B),$==null||$(B)}});const{x:j,y:H,zoom:q}=I.current.getViewport();return E.setState({panZoom:I.current,transform:j,H,q,domNode:R.current.closest(".react-flow")}),()=>{var M;(M=I.current)==null||M.destroy()}}},),y.useEffect(()=>{var j;(j=I.current)==null||j.update({onPaneContextMenu:e,zoomOnScroll:t,zoomOnPinch:n,panOnScroll:r,panOnScrollSpeed:o,panOnScrollMode:i,zoomOnDoubleClick:s,panOnDrag:a,zoomActivationKeyPressed:k,preventScrolling:g,noPanClassName:_,userSelectionActive:P,noWheelClassName:b,lib:N,onTransformChange:O})},e,t,n,r,o,i,s,a,k,g,_,P,b,N,O),h.jsx("div",{className:"react-flow__renderer",ref:R,style:Fx,children:v})}const qde=e=>({userSelectionActive:e.userSelectionActive,userSelectionRect:e.userSelectionRect});function Gde(){const{userSelectionActive:e,userSelectionRect:t}=kt(qde,Rn);return e&&t?h.jsx("div",{className:"react-flow__selection react-flow__container",style:{width:t.width,height:t.height,transform:`translate(${t.x}px, ${t.y}px)`}}):null}const Z1=(e,t)=>n=>{n.target===t.current&&(e==null||e(n))},Vde=e=>({userSelectionActive:e.userSelectionActive,elementsSelectable:e.elementsSelectable,connectionInProgress:e.connection.inProgress,dragging:e.paneDragging});function Yde({isSelecting:e,selectionKeyPressed:t,selectionMode:n=bm.Full,panOnDrag:r,selectionOnDrag:o,onSelectionStart:i,onSelectionEnd:s,onPaneClick:a,onPaneContextMenu:l,onPaneScroll:c,onPaneMouseEnter:d,onPaneMouseMove:f,onPaneMouseLeave:p,children:g}){const v=jn(),{userSelectionActive:b,elementsSelectable:_,dragging:x,connectionInProgress:w}=kt(Vde,Rn),C=_&&(e||b),E=y.useRef(null),R=y.useRef(),P=y.useRef(new Set),N=y.useRef(new Set),k=y.useRef(!1),I=y.useRef(!1),O=W=>{if(k.current||w){k.current=!1;return}a==null||a(W),v.getState().resetSelectedElements(),v.setState({nodesSelectionActive:!1})},j=W=>{if(Array.isArray(r)&&(r!=null&&r.includes(2))){W.preventDefault();return}l==null||l(W)},H=c?W=>c(W):void 0,q=W=>{var re,ae;const{resetSelectedElements:G,domNode:z}=v.getState();if(R.current=z==null?void 0:z.getBoundingClientRect(),!_||!e||W.button!==0||W.target!==E.current||!R.current)return;(ae=(re=W.target)==null?void 0:re.setPointerCapture)==null||ae.call(re,W.pointerId),I.current=!0,k.current=!1;const{x:K,y:Q}=Zs(W.nativeEvent,R.current);G(),v.setState({userSelectionRect:{width:0,height:0,startX:K,startY:Q,x:K,y:Q}}),i==null||i(W)},M=W=>{const{userSelectionRect:G,transform:z,nodeLookup:K,edgeLookup:Q,connectionLookup:re,triggerNodeChanges:ae,triggerEdgeChanges:de,defaultEdgeOptions:Ne}=v.getState();if(!R.current||!G)return;k.current=!0;const{x:ye,y:fe}=Zs(W.nativeEvent,R.current),{startX:Z,startY:oe}=G,ce={startX:Z,startY:oe,x:ye<Z?ye:Z,y:fe<oe?fe:oe,width:Math.abs(ye-Z),height:Math.abs(fe-oe)},xe=P.current,ge=N.current;P.current=new Set(Ik(K,ce,z,n===bm.Partial,!0).map(he=>he.id)),N.current=new Set;const pe=(Ne==null?void 0:Ne.selectable)??!0;for(const he of P.current){const we=re.get(he);if(we)for(const{edgeId:Ie}of we.values()){const Ce=Q.get(Ie);Ce&&(Ce.selectable??pe)&&N.current.add(Ie)}}if(!YL(xe,P.current)){const he=Kd(K,P.current,!0);ae(he)}if(!YL(ge,N.current)){const he=Kd(Q,N.current);de(he)}v.setState({userSelectionRect:ce,userSelectionActive:!0,nodesSelectionActive:!1})},B=W=>{var z,K;if(W.button!==0||!I.current)return;(K=(z=W.target)==null?void 0:z.releasePointerCapture)==null||K.call(z,W.pointerId);const{userSelectionRect:G}=v.getState();!b&&G&&W.target===E.current&&(O==null||O(W)),v.setState({userSelectionActive:!1,userSelectionRect:null,nodesSelectionActive:P.current.size>0}),s==null||s(W),(t||o)&&(k.current=!1),I.current=!1},$=r===!0||Array.isArray(r)&&r.includes(0);return h.jsxs("div",{className:tr("react-flow__pane",{draggable:$,dragging:x,selection:e}),onClick:C?void 0:Z1(O,E),onContextMenu:Z1(j,E),onWheel:Z1(H,E),onPointerEnter:C?void 0:d,onPointerDown:C?q:f,onPointerMove:C?M:f,onPointerUp:C?B:void 0,onPointerLeave:p,ref:E,style:Fx,children:g,h.jsx(Gde,{})})}function wR({id:e,store:t,unselect:n=!1,nodeRef:r}){const{addSelectedNodes:o,unselectNodesAndEdges:i,multiSelectionActive:s,nodeLookup:a,onError:l}=t.getState(),c=a.get(e);if(!c){l==null||l("012",sa.error012(e));return}t.setState({nodesSelectionActive:!1}),c.selected?(n||c.selected&&s)&&(i({nodes:c,edges:}),requestAnimationFrame(()=>{var d;return(d=r==null?void 0:r.current)==null?void 0:d.blur()})):o(e)}function P8({nodeRef:e,disabled:t=!1,noDragClassName:n,handleSelector:r,nodeId:o,isSelectable:i,nodeClickDistance:s}){const a=jn(),l,c=y.useState(!1),d=y.useRef();return y.useEffect(()=>{d.current=xue({getStoreItems:()=>a.getState(),onNodeMouseDown:f=>{wR({id:f,store:a,nodeRef:e})},onDragStart:()=>{c(!0)},onDragStop:()=>{c(!1)}})},),y.useEffect(()=>{var f,p;if(t)(f=d.current)==null||f.destroy();else if(e.current)return(p=d.current)==null||p.update({noDragClassName:n,handleSelector:r,domNode:e.current,isSelectable:i,nodeId:o,nodeClickDistance:s}),()=>{var g;(g=d.current)==null||g.destroy()}},n,r,t,i,e,o),l}const Kde=e=>t=>t.selected&&(t.draggable||e&&typeof t.draggable>"u");function I8(){const e=jn();return y.useCallback(n=>{const{nodeExtent:r,snapToGrid:o,snapGrid:i,nodesDraggable:s,onError:a,updateNodePositions:l,nodeLookup:c,nodeOrigin:d}=e.getState(),f=new Map,p=Kde(s),g=o?i0:5,v=o?i1:5,b=n.direction.x*g*n.factor,_=n.direction.y*v*n.factor;for(const,xof c){if(!p(x))continue;let w={x:x.internals.positionAbsolute.x+b,y:x.internals.positionAbsolute.y+_};o&&(w=jx(w,i));const{position:C,positionAbsolute:E}=KB({nodeId:x.id,nextPosition:w,nodeLookup:c,nodeExtent:r,nodeOrigin:d,onError:a});x.position=C,x.internals.positionAbsolute=E,f.set(x.id,x)}l(f)},)}const zk=y.createContext(null),Xde=zk.Provider;zk.Consumer;const Hk=()=>y.useContext(zk),Zde=e=>({connectOnClick:e.connectOnClick,noPanClassName:e.noPanClassName,rfId:e.rfId}),Qde=(e,t,n)=>r=>{const{connectionClickStartHandle:o,connectionMode:i,connection:s}=r,{fromHandle:a,toHandle:l,isValid:c}=s,d=(l==null?void 0:l.nodeId)===e&&(l==null?void 0:l.id)===t&&(l==null?void 0:l.type)===n;return{connectingFrom:(a==null?void 0:a.nodeId)===e&&(a==null?void 0:a.id)===t&&(a==null?void 0:a.type)===n,connectingTo:d,clickConnecting:(o==null?void 0:o.nodeId)===e&&(o==null?void 0:o.id)===t&&(o==null?void 0:o.type)===n,isPossibleEndHandle:i===Ef.Strict?(a==null?void 0:a.type)!==n:e!==(a==null?void 0:a.nodeId)||t!==(a==null?void 0:a.id),connectionInProcess:!!a,clickConnectionInProcess:!!o,valid:d&&c}};function Jde({type:e="source",position:t=Ve.Top,isValidConnection:n,isConnectable:r=!0,isConnectableStart:o=!0,isConnectableEnd:i=!0,id:s,onConnect:a,children:l,className:c,onMouseDown:d,onTouchStart:f,...p},g){var B,$;const v=s||null,b=e==="target",_=jn(),x=Hk(),{connectOnClick:w,noPanClassName:C,rfId:E}=kt(Zde,Rn),{connectingFrom:R,connectingTo:P,clickConnecting:N,isPossibleEndHandle:k,connectionInProcess:I,clickConnectionInProcess:O,valid:j}=kt(Qde(x,v,e),Rn);x||($=(B=_.getState()).onError)==null||$.call(B,"010",sa.error010());const H=W=>{const{defaultEdgeOptions:G,onConnect:z,hasDefaultEdges:K}=_.getState(),Q={...G,...W};if(K){const{edges:re,setEdges:ae}=_.getState();ae(iue(Q,re))}z==null||z(Q),a==null||a(Q)},q=W=>{if(!x)return;const G=n8(W.nativeEvent);if(o&&(G&&W.button===0||!G)){const z=_.getState();bR.onPointerDown(W.nativeEvent,{autoPanOnConnect:z.autoPanOnConnect,connectionMode:z.connectionMode,connectionRadius:z.connectionRadius,domNode:z.domNode,nodeLookup:z.nodeLookup,lib:z.lib,isTarget:b,handleId:v,nodeId:x,flowId:z.rfId,panBy:z.panBy,cancelConnection:z.cancelConnection,onConnectStart:z.onConnectStart,onConnectEnd:z.onConnectEnd,updateConnection:z.updateConnection,onConnect:H,isValidConnection:n||z.isValidConnection,getTransform:()=>_.getState().transform,getFromHandle:()=>_.getState().connection.fromHandle,autoPanSpeed:z.autoPanSpeed,dragThreshold:z.connectionDragThreshold})}G?d==null||d(W):f==null||f(W)},M=W=>{const{onClickConnectStart:G,onClickConnectEnd:z,connectionClickStartHandle:K,connectionMode:Q,isValidConnection:re,lib:ae,rfId:de,nodeLookup:Ne,connection:ye}=_.getState();if(!x||!K&&!o)return;if(!K){G==null||G(W.nativeEvent,{nodeId:x,handleId:v,handleType:e}),_.setState({connectionClickStartHandle:{nodeId:x,type:e,id:v}});return}const fe=e8(W.target),Z=n||re,{connection:oe,isValid:ce}=bR.isValid(W.nativeEvent,{handle:{nodeId:x,id:v,type:e},connectionMode:Q,fromNodeId:K.nodeId,fromHandleId:K.id||null,fromType:K.type,isValidConnection:Z,flowId:de,doc:fe,lib:ae,nodeLookup:Ne});ce&&oe&&H(oe);const xe=structuredClone(ye);delete xe.inProgress,xe.toPosition=xe.toHandle?xe.toHandle.position:null,z==null||z(W,xe),_.setState({connectionClickStartHandle:null})};return h.jsx("div",{"data-handleid":v,"data-nodeid":x,"data-handlepos":t,"data-id":`${E}-${x}-${v}-${e}`,className:tr("react-flow__handle",`react-flow__handle-${t}`,"nodrag",C,c,{source:!b,target:b,connectable:r,connectablestart:o,connectableend:i,clickconnecting:N,connectingfrom:R,connectingto:P,valid:j,connectionindicator:r&&(!I||k)&&(I||O?i:o)}),onMouseDown:q,onTouchStart:q,onClick:w?M:void 0,ref:g,...p,children:l})}const If=y.memo(T8(Jde));function efe({data:e,isConnectable:t,sourcePosition:n=Ve.Bottom}){return h.jsxs(h.Fragment,{children:e==null?void 0:e.label,h.jsx(If,{type:"source",position:n,isConnectable:t})})}function tfe({data:e,isConnectable:t,targetPosition:n=Ve.Top,sourcePosition:r=Ve.Bottom}){return h.jsxs(h.Fragment,{children:h.jsx(If,{type:"target",position:n,isConnectable:t}),e==null?void 0:e.label,h.jsx(If,{type:"source",position:r,isConnectable:t})})}function nfe(){return null}function rfe({data:e,isConnectable:t,targetPosition:n=Ve.Top}){return h.jsxs(h.Fragment,{children:h.jsx(If,{type:"target",position:n,isConnectable:t}),e==null?void 0:e.label})}const Cy={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}},xO={input:efe,default:tfe,output:rfe,group:nfe};function ofe(e){var t,n,r,o;return e.internals.handleBounds===void 0?{width:e.width??e.initialWidth??((t=e.style)==null?void 0:t.width),height:e.height??e.initialHeight??((n=e.style)==null?void 0:n.height)}:{width:e.width??((r=e.style)==null?void 0:r.width),height:e.height??((o=e.style)==null?void 0:o.height)}}const ife=e=>{const{width:t,height:n,x:r,y:o}=Kf(e.nodeLookup,{filter:i=>!!i.selected});return{width:ls(t)?t:null,height:ls(n)?n:null,userSelectionActive:e.userSelectionActive,transformString:`translate(${e.transform0}px,${e.transform1}px) scale(${e.transform2}) translate(${r}px,${o}px)`}};function sfe({onSelectionContextMenu:e,noPanClassName:t,disableKeyboardA11y:n}){const r=jn(),{width:o,height:i,transformString:s,userSelectionActive:a}=kt(ife,Rn),l=I8(),c=y.useRef(null);if(y.useEffect(()=>{var p;n||(p=c.current)==null||p.focus({preventScroll:!0})},n),P8({nodeRef:c}),a||!o||!i)return null;const d=e?p=>{const g=r.getState().nodes.filter(v=>v.selected);e(p,g)}:void 0,f=p=>{Object.prototype.hasOwnProperty.call(Cy,p.key)&&(p.preventDefault(),l({direction:Cyp.key,factor:p.shiftKey?4:1}))};return h.jsx("div",{className:tr("react-flow__nodesselection","react-flow__container",t),style:{transform:s},children:h.jsx("div",{ref:c,className:"react-flow__nodesselection-rect",onContextMenu:d,tabIndex:n?void 0:-1,onKeyDown:n?void 0:f,style:{width:o,height:i}})})}const bO=typeof window<"u"?window:void 0,afe=e=>({nodesSelectionActive:e.nodesSelectionActive,userSelectionActive:e.userSelectionActive});function A8({children:e,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:o,onPaneContextMenu:i,onPaneScroll:s,paneClickDistance:a,deleteKeyCode:l,selectionKeyCode:c,selectionOnDrag:d,selectionMode:f,onSelectionStart:p,onSelectionEnd:g,multiSelectionKeyCode:v,panActivationKeyCode:b,zoomActivationKeyCode:_,elementsSelectable:x,zoomOnScroll:w,zoomOnPinch:C,panOnScroll:E,panOnScrollSpeed:R,panOnScrollMode:P,zoomOnDoubleClick:N,panOnDrag:k,defaultViewport:I,translateExtent:O,minZoom:j,maxZoom:H,preventScrolling:q,onSelectionContextMenu:M,noWheelClassName:B,noPanClassName:$,disableKeyboardA11y:W,onViewportChange:G,isControlledViewport:z}){const{nodesSelectionActive:K,userSelectionActive:Q}=kt(afe),re=Cm(c,{target:bO}),ae=Cm(b,{target:bO}),de=ae||k,Ne=ae||E,ye=d&&de!==!0,fe=re||Q||ye;return Hde({deleteKeyCode:l,multiSelectionKeyCode:v}),h.jsx(Ude,{onPaneContextMenu:i,elementsSelectable:x,zoomOnScroll:w,zoomOnPinch:C,panOnScroll:Ne,panOnScrollSpeed:R,panOnScrollMode:P,zoomOnDoubleClick:N,panOnDrag:!re&&de,defaultViewport:I,translateExtent:O,minZoom:j,maxZoom:H,zoomActivationKeyCode:_,preventScrolling:q,noWheelClassName:B,noPanClassName:$,onViewportChange:G,isControlledViewport:z,paneClickDistance:a,children:h.jsxs(Yde,{onSelectionStart:p,onSelectionEnd:g,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:o,onPaneContextMenu:i,onPaneScroll:s,panOnDrag:de,isSelecting:!!fe,selectionMode:f,selectionKeyPressed:re,selectionOnDrag:ye,children:e,K&&h.jsx(sfe,{onSelectionContextMenu:M,noPanClassName:$,disableKeyboardA11y:W})})})}A8.displayName="FlowRenderer";const lfe=y.memo(A8),cfe=e=>t=>e?Ik(t.nodeLookup,{x:0,y:0,width:t.width,height:t.height},t.transform,!0).map(n=>n.id):Array.from(t.nodeLookup.keys());function ufe(e){return kt(y.useCallback(cfe(e),e),Rn)}const dfe=e=>e.updateNodeInternals;function ffe(){const e=kt(dfe),t=y.useState(()=>typeof ResizeObserver>"u"?null:new ResizeObserver(n=>{const r=new Map;n.forEach(o=>{const i=o.target.getAttribute("data-id");r.set(i,{id:i,nodeElement:o.target,force:!0})}),e(r)}));return y.useEffect(()=>()=>{t==null||t.disconnect()},t),t}function hfe({node:e,nodeType:t,hasDimensions:n,resizeObserver:r}){const o=jn(),i=y.useRef(null),s=y.useRef(null),a=y.useRef(e.sourcePosition),l=y.useRef(e.targetPosition),c=y.useRef(t),d=n&&!!e.internals.handleBounds;return y.useEffect(()=>{i.current&&!e.hidden&&(!d||s.current!==i.current)&&(s.current&&(r==null||r.unobserve(s.current)),r==null||r.observe(i.current),s.current=i.current)},d,e.hidden),y.useEffect(()=>()=>{s.current&&(r==null||r.unobserve(s.current),s.current=null)},),y.useEffect(()=>{if(i.current){const f=c.current!==t,p=a.current!==e.sourcePosition,g=l.current!==e.targetPosition;(f||p||g)&&(c.current=t,a.current=e.sourcePosition,l.current=e.targetPosition,o.getState().updateNodeInternals(new Map(e.id,{id:e.id,nodeElement:i.current,force:!0})))}},e.id,t,e.sourcePosition,e.targetPosition),i}function pfe({id:e,onClick:t,onMouseEnter:n,onMouseMove:r,onMouseLeave:o,onContextMenu:i,onDoubleClick:s,nodesDraggable:a,elementsSelectable:l,nodesConnectable:c,nodesFocusable:d,resizeObserver:f,noDragClassName:p,noPanClassName:g,disableKeyboardA11y:v,rfId:b,nodeTypes:_,nodeClickDistance:x,onError:w}){const{node:C,internals:E,isParent:R}=kt(Z=>{const oe=Z.nodeLookup.get(e),ce=Z.parentLookup.has(e);return{node:oe,internals:oe.internals,isParent:ce}},Rn);let P=C.type||"default",N=(_==null?void 0:_P)||xOP;N===void 0&&(w==null||w("003",sa.error003(P)),P="default",N=(_==null?void 0:_.default)||xO.default);const k=!!(C.draggable||a&&typeof C.draggable>"u"),I=!!(C.selectable||l&&typeof C.selectable>"u"),O=!!(C.connectable||c&&typeof C.connectable>"u"),j=!!(C.focusable||d&&typeof C.focusable>"u"),H=jn(),q=Mk(C),M=hfe({node:C,nodeType:P,hasDimensions:q,resizeObserver:f}),B=P8({nodeRef:M,disabled:C.hidden||!k,noDragClassName:p,handleSelector:C.dragHandle,nodeId:e,isSelectable:I,nodeClickDistance:x}),$=I8();if(C.hidden)return null;const W=ll(C),G=ofe(C),z=I||k||t||n||r||o,K=n?Z=>n(Z,{...E.userNode}):void 0,Q=r?Z=>r(Z,{...E.userNode}):void 0,re=o?Z=>o(Z,{...E.userNode}):void 0,ae=i?Z=>i(Z,{...E.userNode}):void 0,de=s?Z=>s(Z,{...E.userNode}):void 0,Ne=Z=>{const{selectNodesOnDrag:oe,nodeDragThreshold:ce}=H.getState();I&&(!oe||!k||ce>0)&&wR({id:e,store:H,nodeRef:M}),t&&t(Z,{...E.userNode})},ye=Z=>{if(!(t8(Z.nativeEvent)||v)){if(UB.includes(Z.key)&&I){const oe=Z.key==="Escape";wR({id:e,store:H,unselect:oe,nodeRef:M})}else if(k&&C.selected&&Object.prototype.hasOwnProperty.call(Cy,Z.key)){Z.preventDefault();const{ariaLabelConfig:oe}=H.getState();H.setState({ariaLiveMessage:oe"node.a11yDescription.ariaLiveMessage"({direction:Z.key.replace("Arrow","").toLowerCase(),x:~~E.positionAbsolute.x,y:~~E.positionAbsolute.y})}),$({direction:CyZ.key,factor:Z.shiftKey?4:1})}}},fe=()=>{var he;if(v||!((he=M.current)!=null&&he.matches(":focus-visible")))return;const{transform:Z,width:oe,height:ce,autoPanOnNodeFocus:xe,setCenter:ge}=H.getState();if(!xe)return;Ik(new Map(e,C),{x:0,y:0,width:oe,height:ce},Z,!0).length>0||ge(C.position.x+W.width/2,C.position.y+W.height/2,{zoom:Z2})};return h.jsx("div",{className:tr("react-flow__node",`react-flow__node-${P}`,{g:k},C.className,{selected:C.selected,selectable:I,parent:R,draggable:k,dragging:B}),ref:M,style:{zIndex:E.z,transform:`translate(${E.positionAbsolute.x}px,${E.positionAbsolute.y}px)`,pointerEvents:z?"all":"none",visibility:q?"visible":"hidden",...C.style,...G},"data-id":e,"data-testid":`rf__node-${e}`,onMouseEnter:K,onMouseMove:Q,onMouseLeave:re,onContextMenu:ae,onClick:Ne,onDoubleClick:de,onKeyDown:j?ye:void 0,tabIndex:j?0:void 0,onFocus:j?fe:void 0,role:C.ariaRole??(j?"group":void 0),"aria-roledescription":"node","aria-describedby":v?void 0:`${S8}-${b}`,"aria-label":C.ariaLabel,...C.domAttributes,children:h.jsx(Xde,{value:e,children:h.jsx(N,{id:e,data:C.data,type:P,positionAbsoluteX:E.positionAbsolute.x,positionAbsoluteY:E.positionAbsolute.y,selected:C.selected??!1,selectable:I,draggable:k,deletable:C.deletable??!0,isConnectable:O,sourcePosition:C.sourcePosition,targetPosition:C.targetPosition,dragging:B,dragHandle:C.dragHandle,zIndex:E.z,parentId:C.parentId,...W})})})}const mfe=e=>({nodesDraggable:e.nodesDraggable,nodesConnectable:e.nodesConnectable,nodesFocusable:e.nodesFocusable,elementsSelectable:e.elementsSelectable,onError:e.onError});function M8(e){const{nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:o,onError:i}=kt(mfe,Rn),s=ufe(e.onlyRenderVisibleElements),a=ffe();return h.jsx("div",{className:"react-flow__nodes",style:Fx,children:s.map(l=>h.jsx(pfe,{id:l,nodeTypes:e.nodeTypes,nodeExtent:e.nodeExtent,onClick:e.onNodeClick,onMouseEnter:e.onNodeMouseEnter,onMouseMove:e.onNodeMouseMove,onMouseLeave:e.onNodeMouseLeave,onContextMenu:e.onNodeContextMenu,onDoubleClick:e.onNodeDoubleClick,noDragClassName:e.noDragClassName,noPanClassName:e.noPanClassName,rfId:e.rfId,disableKeyboardA11y:e.disableKeyboardA11y,resizeObserver:a,nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:o,nodeClickDistance:e.nodeClickDistance,onError:i},l))})}M8.displayName="NodeRenderer";const gfe=y.memo(M8);function vfe(e){return kt(y.useCallback(n=>{if(!e)return n.edges.map(o=>o.id);const r=;if(n.width&&n.height)for(const o of n.edges){const i=n.nodeLookup.get(o.source),s=n.nodeLookup.get(o.target);i&&s&&nue({sourceNode:i,targetNode:s,width:n.width,height:n.height,transform:n.transform})&&r.push(o.id)}return r},e),Rn)}const yfe=({color:e="none",strokeWidth:t=1})=>h.jsx("polyline",{style:{stroke:e,strokeWidth:t},strokeLinecap:"round",strokeLinejoin:"round",fill:"none",points:"-5,-4 0,0 -5,4"}),xfe=({color:e="none",strokeWidth:t=1})=>h.jsx("polyline",{style:{stroke:e,fill:e,strokeWidth:t},strokeLinecap:"round",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4 -5,-4"}),wO={wm.Arrow:yfe,wm.ArrowClosed:xfe};function bfe(e){const t=jn();return y.useMemo(()=>{var o,i;return Object.prototype.hasOwnProperty.call(wO,e)?wOe:((i=(o=t.getState()).onError)==null||i.call(o,"009",sa.error009(e)),null)},e)}const wfe=({id:e,type:t,color:n,width:r=12.5,height:o=12.5,markerUnits:i="strokeWidth",strokeWidth:s,orient:a="auto-start-reverse"})=>{const l=bfe(t);return l?h.jsx("marker",{className:"react-flow__arrowhead",id:e,markerWidth:`${r}`,markerHeight:`${o}`,viewBox:"-10 -10 20 20",markerUnits:i,orient:a,refX:"0",refY:"0",children:h.jsx(l,{color:n,strokeWidth:s})}):null},j8=({defaultColor:e,rfId:t})=>{const n=kt(i=>i.edges),r=kt(i=>i.defaultEdgeOptions),o=y.useMemo(()=>uue(n,{id:t,defaultColor:e,defaultMarkerStart:r==null?void 0:r.markerStart,defaultMarkerEnd:r==null?void 0:r.markerEnd}),n,r,t,e);return o.length?h.jsx("svg",{className:"react-flow__marker","aria-hidden":"true",children:h.jsx("defs",{children:o.map(i=>h.jsx(wfe,{id:i.id,type:i.type,color:i.color,width:i.width,height:i.height,markerUnits:i.markerUnits,strokeWidth:i.strokeWidth,orient:i.orient},i.id))})}):null};j8.displayName="MarkerDefinitions";var Sfe=y.memo(j8);function L8({x:e,y:t,label:n,labelStyle:r,labelShowBg:o=!0,labelBgStyle:i,labelBgPadding:s=2,4,labelBgBorderRadius:a=2,children:l,className:c,...d}){constf,p=y.useState({x:1,y:0,width:0,height:0}),g=tr("react-flow__edge-textwrapper",c),v=y.useRef(null);return y.useEffect(()=>{if(v.current){const b=v.current.getBBox();p({x:b.x,y:b.y,width:b.width,height:b.height})}},n),n?h.jsxs("g",{transform:`translate(${e-f.width/2} ${t-f.height/2})`,className:g,visibility:f.width?"visible":"hidden",...d,children:o&&h.jsx("rect",{width:f.width+2*s0,x:-s0,y:-s1,height:f.height+2*s1,className:"react-flow__edge-textbg",style:i,rx:a,ry:a}),h.jsx("text",{className:"react-flow__edge-text",y:f.height/2,dy:"0.3em",ref:v,style:r,children:n}),l}):null}L8.displayName="EdgeText";const _fe=y.memo(L8);function $x({path:e,labelX:t,labelY:n,label:r,labelStyle:o,labelShowBg:i,labelBgStyle:s,labelBgPadding:a,labelBgBorderRadius:l,interactionWidth:c=20,...d}){return h.jsxs(h.Fragment,{children:h.jsx("path",{...d,d:e,fill:"none",className:tr("react-flow__edge-path",d.className)}),c&&h.jsx("path",{d:e,fill:"none",strokeOpacity:0,strokeWidth:c,className:"react-flow__edge-interaction"}),r&&ls(t)&&ls(n)?h.jsx(_fe,{x:t,y:n,label:r,labelStyle:o,labelShowBg:i,labelBgStyle:s,labelBgPadding:a,labelBgBorderRadius:l}):null})}function SO({pos:e,x1:t,y1:n,x2:r,y2:o}){return e===Ve.Left||e===Ve.Right?.5*(t+r),n:t,.5*(n+o)}function O8({sourceX:e,sourceY:t,sourcePosition:n=Ve.Bottom,targetX:r,targetY:o,targetPosition:i=Ve.Top}){consts,a=SO({pos:n,x1:e,y1:t,x2:r,y2:o}),l,c=SO({pos:i,x1:r,y1:o,x2:e,y2:t}),d,f,p,g=r8({sourceX:e,sourceY:t,targetX:r,targetY:o,sourceControlX:s,sourceControlY:a,targetControlX:l,targetControlY:c});return`M${e},${t} C${s},${a} ${l},${c} ${r},${o}`,d,f,p,g}function D8(e){return y.memo(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,sourcePosition:s,targetPosition:a,label:l,labelStyle:c,labelShowBg:d,labelBgStyle:f,labelBgPadding:p,labelBgBorderRadius:g,style:v,markerEnd:b,markerStart:_,interactionWidth:x})=>{constw,C,E=O8({sourceX:n,sourceY:r,sourcePosition:s,targetX:o,targetY:i,targetPosition:a}),R=e.isInternal?void 0:t;return h.jsx($x,{id:R,path:w,labelX:C,labelY:E,label:l,labelStyle:c,labelShowBg:d,labelBgStyle:f,labelBgPadding:p,labelBgBorderRadius:g,style:v,markerEnd:b,markerStart:_,interactionWidth:x})})}const Cfe=D8({isInternal:!1}),F8=D8({isInternal:!0});Cfe.displayName="SimpleBezierEdge";F8.displayName="SimpleBezierEdgeInternal";function $8(e){return y.memo(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,label:s,labelStyle:a,labelShowBg:l,labelBgStyle:c,labelBgPadding:d,labelBgBorderRadius:f,style:p,sourcePosition:g=Ve.Bottom,targetPosition:v=Ve.Top,markerEnd:b,markerStart:_,pathOptions:x,interactionWidth:w})=>{constC,E,R=vR({sourceX:n,sourceY:r,sourcePosition:g,targetX:o,targetY:i,targetPosition:v,borderRadius:x==null?void 0:x.borderRadius,offset:x==null?void 0:x.offset,stepPosition:x==null?void 0:x.stepPosition}),P=e.isInternal?void 0:t;return h.jsx($x,{id:P,path:C,labelX:E,labelY:R,label:s,labelStyle:a,labelShowBg:l,labelBgStyle:c,labelBgPadding:d,labelBgBorderRadius:f,style:p,markerEnd:b,markerStart:_,interactionWidth:w})})}const z8=$8({isInternal:!1}),H8=$8({isInternal:!0});z8.displayName="SmoothStepEdge";H8.displayName="SmoothStepEdgeInternal";function B8(e){return y.memo(({id:t,...n})=>{var o;const r=e.isInternal?void 0:t;return h.jsx(z8,{...n,id:r,pathOptions:y.useMemo(()=>{var i;return{borderRadius:0,offset:(i=n.pathOptions)==null?void 0:i.offset}},(o=n.pathOptions)==null?void 0:o.offset)})})}const Efe=B8({isInternal:!1}),W8=B8({isInternal:!0});Efe.displayName="StepEdge";W8.displayName="StepEdgeInternal";function U8(e){return y.memo(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,label:s,labelStyle:a,labelShowBg:l,labelBgStyle:c,labelBgPadding:d,labelBgBorderRadius:f,style:p,markerEnd:g,markerStart:v,interactionWidth:b})=>{const_,x,w=i8({sourceX:n,sourceY:r,targetX:o,targetY:i}),C=e.isInternal?void 0:t;return h.jsx($x,{id:C,path:_,labelX:x,labelY:w,label:s,labelStyle:a,labelShowBg:l,labelBgStyle:c,labelBgPadding:d,labelBgBorderRadius:f,style:p,markerEnd:g,markerStart:v,interactionWidth:b})})}const Nfe=U8({isInternal:!1}),q8=U8({isInternal:!0});Nfe.displayName="StraightEdge";q8.displayName="StraightEdgeInternal";function G8(e){return y.memo(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,sourcePosition:s=Ve.Bottom,targetPosition:a=Ve.Top,label:l,labelStyle:c,labelShowBg:d,labelBgStyle:f,labelBgPadding:p,labelBgBorderRadius:g,style:v,markerEnd:b,markerStart:_,pathOptions:x,interactionWidth:w})=>{constC,E,R=Lk({sourceX:n,sourceY:r,sourcePosition:s,targetX:o,targetY:i,targetPosition:a,curvature:x==null?void 0:x.curvature}),P=e.isInternal?void 0:t;return h.jsx($x,{id:P,path:C,labelX:E,labelY:R,label:l,labelStyle:c,labelShowBg:d,labelBgStyle:f,labelBgPadding:p,labelBgBorderRadius:g,style:v,markerEnd:b,markerStart:_,interactionWidth:w})})}const Rfe=G8({isInternal:!1}),V8=G8({isInternal:!0});Rfe.displayName="BezierEdge";V8.displayName="BezierEdgeInternal";const _O={default:V8,straight:q8,step:W8,smoothstep:H8,simplebezier:F8},CO={sourceX:null,sourceY:null,targetX:null,targetY:null,sourcePosition:null,targetPosition:null},Tfe=(e,t,n)=>n===Ve.Left?e-t:n===Ve.Right?e+t:e,kfe=(e,t,n)=>n===Ve.Top?e-t:n===Ve.Bottom?e+t:e,EO="react-flow__edgeupdater";function NO({position:e,centerX:t,centerY:n,radius:r=10,onMouseDown:o,onMouseEnter:i,onMouseOut:s,type:a}){return h.jsx("circle",{onMouseDown:o,onMouseEnter:i,onMouseOut:s,className:tr(EO,`${EO}-${a}`),cx:Tfe(t,r,e),cy:kfe(n,r,e),r,stroke:"transparent",fill:"transparent"})}function Pfe({isReconnectable:e,reconnectRadius:t,edge:n,sourceX:r,sourceY:o,targetX:i,targetY:s,sourcePosition:a,targetPosition:l,onReconnect:c,onReconnectStart:d,onReconnectEnd:f,setReconnecting:p,setUpdateHover:g}){const v=jn(),b=(E,R)=>{if(E.button!==0)return;const{autoPanOnConnect:P,domNode:N,isValidConnection:k,connectionMode:I,connectionRadius:O,lib:j,onConnectStart:H,onConnectEnd:q,cancelConnection:M,nodeLookup:B,rfId:$,panBy:W,updateConnection:G}=v.getState(),z=R.type==="target",K=(ae,de)=>{p(!1),f==null||f(ae,n,R.type,de)},Q=ae=>c==null?void 0:c(n,ae),re=(ae,de)=>{p(!0),d==null||d(E,n,R.type),H==null||H(ae,de)};bR.onPointerDown(E.nativeEvent,{autoPanOnConnect:P,connectionMode:I,connectionRadius:O,domNode:N,handleId:R.id,nodeId:R.nodeId,nodeLookup:B,isTarget:z,edgeUpdaterType:R.type,lib:j,flowId:$,cancelConnection:M,panBy:W,isValidConnection:k,onConnect:Q,onConnectStart:re,onConnectEnd:q,onReconnectEnd:K,updateConnection:G,getTransform:()=>v.getState().transform,getFromHandle:()=>v.getState().connection.fromHandle,dragThreshold:v.getState().connectionDragThreshold})},_=E=>b(E,{nodeId:n.target,id:n.targetHandle??null,type:"target"}),x=E=>b(E,{nodeId:n.source,id:n.sourceHandle??null,type:"source"}),w=()=>g(!0),C=()=>g(!1);return h.jsxs(h.Fragment,{children:(e===!0||e==="source")&&h.jsx(NO,{position:a,centerX:r,centerY:o,radius:t,onMouseDown:_,onMouseEnter:w,onMouseOut:C,type:"source"}),(e===!0||e==="target")&&h.jsx(NO,{position:l,centerX:i,centerY:s,radius:t,onMouseDown:x,onMouseEnter:w,onMouseOut:C,type:"target"})})}function Ife({id:e,edgesFocusable:t,edgesReconnectable:n,elementsSelectable:r,onClick:o,onDoubleClick:i,onContextMenu:s,onMouseEnter:a,onMouseMove:l,onMouseLeave:c,reconnectRadius:d,onReconnect:f,onReconnectStart:p,onReconnectEnd:g,rfId:v,edgeTypes:b,noPanClassName:_,onError:x,disableKeyboardA11y:w}){let C=kt(ge=>ge.edgeLookup.get(e));const E=kt(ge=>ge.defaultEdgeOptions);C=E?{...E,...C}:C;let R=C.type||"default",P=(b==null?void 0:bR)||_OR;P===void 0&&(x==null||x("011",sa.error011(R)),R="default",P=(b==null?void 0:b.default)||_O.default);const N=!!(C.focusable||t&&typeof C.focusable>"u"),k=typeof f<"u"&&(C.reconnectable||n&&typeof C.reconnectable>"u"),I=!!(C.selectable||r&&typeof C.selectable>"u"),O=y.useRef(null),j,H=y.useState(!1),q,M=y.useState(!1),B=jn(),{zIndex:$,sourceX:W,sourceY:G,targetX:z,targetY:K,sourcePosition:Q,targetPosition:re}=kt(y.useCallback(ge=>{const pe=ge.nodeLookup.get(C.source),he=ge.nodeLookup.get(C.target);if(!pe||!he)return{zIndex:C.zIndex,...CO};const we=cue({id:e,sourceNode:pe,targetNode:he,sourceHandle:C.sourceHandle||null,targetHandle:C.targetHandle||null,connectionMode:ge.connectionMode,onError:x});return{zIndex:tue({selected:C.selected,zIndex:C.zIndex,sourceNode:pe,targetNode:he,elevateOnSelect:ge.elevateEdgesOnSelect}),...we||CO}},C.source,C.target,C.sourceHandle,C.targetHandle,C.selected,C.zIndex),Rn),ae=y.useMemo(()=>C.markerStart?`url('#${yR(C.markerStart,v)}')`:void 0,C.markerStart,v),de=y.useMemo(()=>C.markerEnd?`url('#${yR(C.markerEnd,v)}')`:void 0,C.markerEnd,v);if(C.hidden||W===null||G===null||z===null||K===null)return null;const Ne=ge=>{var Ie;const{addSelectedEdges:pe,unselectNodesAndEdges:he,multiSelectionActive:we}=B.getState();I&&(B.setState({nodesSelectionActive:!1}),C.selected&&we?(he({nodes:,edges:C}),(Ie=O.current)==null||Ie.blur()):pe(e)),o&&o(ge,C)},ye=i?ge=>{i(ge,{...C})}:void 0,fe=s?ge=>{s(ge,{...C})}:void 0,Z=a?ge=>{a(ge,{...C})}:void 0,oe=l?ge=>{l(ge,{...C})}:void 0,ce=c?ge=>{c(ge,{...C})}:void 0,xe=ge=>{var pe;if(!w&&UB.includes(ge.key)&&I){const{unselectNodesAndEdges:he,addSelectedEdges:we}=B.getState();ge.key==="Escape"?((pe=O.current)==null||pe.blur(),he({edges:C})):we(e)}};return h.jsx("svg",{style:{zIndex:$},children:h.jsxs("g",{className:tr("react-flow__edge",`react-flow__edge-${R}`,C.className,_,{selected:C.selected,animated:C.animated,inactive:!I&&!o,updating:j,selectable:I}),onClick:Ne,onDoubleClick:ye,onContextMenu:fe,onMouseEnter:Z,onMouseMove:oe,onMouseLeave:ce,onKeyDown:N?xe:void 0,tabIndex:N?0:void 0,role:C.ariaRole??(N?"group":"img"),"aria-roledescription":"edge","data-id":e,"data-testid":`rf__edge-${e}`,"aria-label":C.ariaLabel===null?void 0:C.ariaLabel||`Edge from ${C.source} to ${C.target}`,"aria-describedby":N?`${_8}-${v}`:void 0,ref:O,...C.domAttributes,children:!q&&h.jsx(P,{id:e,source:C.source,target:C.target,type:C.type,selected:C.selected,animated:C.animated,selectable:I,deletable:C.deletable??!0,label:C.label,labelStyle:C.labelStyle,labelShowBg:C.labelShowBg,labelBgStyle:C.labelBgStyle,labelBgPadding:C.labelBgPadding,labelBgBorderRadius:C.labelBgBorderRadius,sourceX:W,sourceY:G,targetX:z,targetY:K,sourcePosition:Q,targetPosition:re,data:C.data,style:C.style,sourceHandleId:C.sourceHandle,targetHandleId:C.targetHandle,markerStart:ae,markerEnd:de,pathOptions:"pathOptions"in C?C.pathOptions:void 0,interactionWidth:C.interactionWidth}),k&&h.jsx(Pfe,{edge:C,isReconnectable:k,reconnectRadius:d,onReconnect:f,onReconnectStart:p,onReconnectEnd:g,sourceX:W,sourceY:G,targetX:z,targetY:K,sourcePosition:Q,targetPosition:re,setUpdateHover:H,setReconnecting:M})})})}const Afe=e=>({edgesFocusable:e.edgesFocusable,edgesReconnectable:e.edgesReconnectable,elementsSelectable:e.elementsSelectable,connectionMode:e.connectionMode,onError:e.onError});function Y8({defaultMarkerColor:e,onlyRenderVisibleElements:t,rfId:n,edgeTypes:r,noPanClassName:o,onReconnect:i,onEdgeContextMenu:s,onEdgeMouseEnter:a,onEdgeMouseMove:l,onEdgeMouseLeave:c,onEdgeClick:d,reconnectRadius:f,onEdgeDoubleClick:p,onReconnectStart:g,onReconnectEnd:v,disableKeyboardA11y:b}){const{edgesFocusable:_,edgesReconnectable:x,elementsSelectable:w,onError:C}=kt(Afe,Rn),E=vfe(t);return h.jsxs("div",{className:"react-flow__edges",children:h.jsx(Sfe,{defaultColor:e,rfId:n}),E.map(R=>h.jsx(Ife,{id:R,edgesFocusable:_,edgesReconnectable:x,elementsSelectable:w,noPanClassName:o,onReconnect:i,onContextMenu:s,onMouseEnter:a,onMouseMove:l,onMouseLeave:c,onClick:d,reconnectRadius:f,onDoubleClick:p,onReconnectStart:g,onReconnectEnd:v,rfId:n,onError:C,edgeTypes:r,disableKeyboardA11y:b},R))})}Y8.displayName="EdgeRenderer";const Mfe=y.memo(Y8),jfe=e=>`translate(${e.transform0}px,${e.transform1}px) scale(${e.transform2})`;function Lfe({children:e}){const t=kt(jfe);return h.jsx("div",{className:"react-flow__viewport xyflow__viewport react-flow__container",style:{transform:t},children:e})}function Ofe(e){const t=Qm(),n=y.useRef(!1);y.useEffect(()=>{!n.current&&t.viewportInitialized&&e&&(setTimeout(()=>e(t),1),n.current=!0)},e,t.viewportInitialized)}const Dfe=e=>{var t;return(t=e.panZoom)==null?void 0:t.syncViewport};function Ffe(e){const t=kt(Dfe),n=jn();return y.useEffect(()=>{e&&(t==null||t(e),n.setState({transform:e.x,e.y,e.zoom}))},e,t),null}function $fe(e){return e.connection.inProgress?{...e.connection,to:Xm(e.connection.to,e.transform)}:{...e.connection}}function zfe(e){return $fe}function Hfe(e){const t=zfe();return kt(t,Rn)}const Bfe=e=>({nodesConnectable:e.nodesConnectable,isValid:e.connection.isValid,inProgress:e.connection.inProgress,width:e.width,height:e.height});function Wfe({containerStyle:e,style:t,type:n,component:r}){const{nodesConnectable:o,width:i,height:s,isValid:a,inProgress:l}=kt(Bfe,Rn);return!(i&&o&&l)?null:h.jsx("svg",{style:e,width:i,height:s,className:"react-flow__connectionline react-flow__container",children:h.jsx("g",{className:tr("react-flow__connection",VB(a)),children:h.jsx(K8,{style:t,type:n,CustomComponent:r,isValid:a})})})}const K8=({style:e,type:t=Xl.Bezier,CustomComponent:n,isValid:r})=>{const{inProgress:o,from:i,fromNode:s,fromHandle:a,fromPosition:l,to:c,toNode:d,toHandle:f,toPosition:p}=Hfe();if(!o)return;if(n)return h.jsx(n,{connectionLineType:t,connectionLineStyle:e,fromNode:s,fromHandle:a,fromX:i.x,fromY:i.y,toX:c.x,toY:c.y,fromPosition:l,toPosition:p,connectionStatus:VB(r),toNode:d,toHandle:f});let g="";const v={sourceX:i.x,sourceY:i.y,sourcePosition:l,targetX:c.x,targetY:c.y,targetPosition:p};switch(t){case Xl.Bezier:g=Lk(v);break;case Xl.SimpleBezier:g=O8(v);break;case Xl.Step:g=vR({...v,borderRadius:0});break;case Xl.SmoothStep:g=vR(v);break;default:g=i8(v)}return h.jsx("path",{d:g,fill:"none",className:"react-flow__connection-path",style:e})};K8.displayName="ConnectionLine";const Ufe={};function RO(e=Ufe){y.useRef(e),jn(),y.useEffect(()=>{},e)}function qfe(){jn(),y.useRef(!1),y.useEffect(()=>{},)}function X8({nodeTypes:e,edgeTypes:t,onInit:n,onNodeClick:r,onEdgeClick:o,onNodeDoubleClick:i,onEdgeDoubleClick:s,onNodeMouseEnter:a,onNodeMouseMove:l,onNodeMouseLeave:c,onNodeContextMenu:d,onSelectionContextMenu:f,onSelectionStart:p,onSelectionEnd:g,connectionLineType:v,connectionLineStyle:b,connectionLineComponent:_,connectionLineContainerStyle:x,selectionKeyCode:w,selectionOnDrag:C,selectionMode:E,multiSelectionKeyCode:R,panActivationKeyCode:P,zoomActivationKeyCode:N,deleteKeyCode:k,onlyRenderVisibleElements:I,elementsSelectable:O,defaultViewport:j,translateExtent:H,minZoom:q,maxZoom:M,preventScrolling:B,defaultMarkerColor:$,zoomOnScroll:W,zoomOnPinch:G,panOnScroll:z,panOnScrollSpeed:K,panOnScrollMode:Q,zoomOnDoubleClick:re,panOnDrag:ae,onPaneClick:de,onPaneMouseEnter:Ne,onPaneMouseMove:ye,onPaneMouseLeave:fe,onPaneScroll:Z,onPaneContextMenu:oe,paneClickDistance:ce,nodeClickDistance:xe,onEdgeContextMenu:ge,onEdgeMouseEnter:pe,onEdgeMouseMove:he,onEdgeMouseLeave:we,reconnectRadius:Ie,onReconnect:Ce,onReconnectStart:Me,onReconnectEnd:ze,noDragClassName:Ye,noWheelClassName:Ht,noPanClassName:Ft,disableKeyboardA11y:rt,nodeExtent:Ue,rfId:Te,viewport:bt,onViewportChange:jt}){return RO(e),RO(t),qfe(),Ofe(n),Ffe(bt),h.jsx(lfe,{onPaneClick:de,onPaneMouseEnter:Ne,onPaneMouseMove:ye,onPaneMouseLeave:fe,onPaneContextMenu:oe,onPaneScroll:Z,paneClickDistance:ce,deleteKeyCode:k,selectionKeyCode:w,selectionOnDrag:C,selectionMode:E,onSelectionStart:p,onSelectionEnd:g,multiSelectionKeyCode:R,panActivationKeyCode:P,zoomActivationKeyCode:N,elementsSelectable:O,zoomOnScroll:W,zoomOnPinch:G,zoomOnDoubleClick:re,panOnScroll:z,panOnScrollSpeed:K,panOnScrollMode:Q,panOnDrag:ae,defaultViewport:j,translateExtent:H,minZoom:q,maxZoom:M,onSelectionContextMenu:f,preventScrolling:B,noDragClassName:Ye,noWheelClassName:Ht,noPanClassName:Ft,disableKeyboardA11y:rt,onViewportChange:jt,isControlledViewport:!!bt,children:h.jsxs(Lfe,{children:h.jsx(Mfe,{edgeTypes:t,onEdgeClick:o,onEdgeDoubleClick:s,onReconnect:Ce,onReconnectStart:Me,onReconnectEnd:ze,onlyRenderVisibleElements:I,onEdgeContextMenu:ge,onEdgeMouseEnter:pe,onEdgeMouseMove:he,onEdgeMouseLeave:we,reconnectRadius:Ie,defaultMarkerColor:$,noPanClassName:Ft,disableKeyboardA11y:rt,rfId:Te}),h.jsx(Wfe,{style:b,type:v,component:_,containerStyle:x}),h.jsx("div",{className:"react-flow__edgelabel-renderer"}),h.jsx(gfe,{nodeTypes:e,onNodeClick:r,onNodeDoubleClick:i,onNodeMouseEnter:a,onNodeMouseMove:l,onNodeMouseLeave:c,onNodeContextMenu:d,nodeClickDistance:xe,onlyRenderVisibleElements:I,noPanClassName:Ft,noDragClassName:Ye,disableKeyboardA11y:rt,nodeExtent:Ue,rfId:Te}),h.jsx("div",{className:"react-flow__viewport-portal"})})})}X8.displayName="GraphView";const Gfe=y.memo(X8),TO=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:o,height:i,fitView:s,fitViewOptions:a,minZoom:l=.5,maxZoom:c=2,nodeOrigin:d,nodeExtent:f}={})=>{const p=new Map,g=new Map,v=new Map,b=new Map,_=r??t??,x=n??e??,w=d??0,0,C=f??xm;a8(v,b,_);const E=xR(x,p,g,{nodeOrigin:w,nodeExtent:C,elevateNodesOnSelect:!1});let R=0,0,1;if(s&&o&&i){const P=Kf(p,{filter:O=>!!((O.width||O.initialWidth)&&(O.height||O.initialHeight))}),{x:N,y:k,zoom:I}=Ak(P,o,i,l,c,(a==null?void 0:a.padding)??.1);R=N,k,I}return{rfId:"1",width:0,height:0,transform:R,nodes:x,nodesInitialized:E,nodeLookup:p,parentLookup:g,edges:_,edgeLookup:b,connectionLookup:v,onNodesChange:null,onEdgesChange:null,hasDefaultNodes:n!==void 0,hasDefaultEdges:r!==void 0,panZoom:null,minZoom:l,maxZoom:c,translateExtent:xm,nodeExtent:C,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionMode:Ef.Strict,domNode:null,paneDragging:!1,noPanClassName:"nopan",nodeOrigin:w,nodeDragThreshold:1,connectionDragThreshold:1,snapGrid:15,15,snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesReconnectable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,elevateEdgesOnSelect:!1,selectNodesOnDrag:!0,multiSelectionActive:!1,fitViewQueued:s??!1,fitViewOptions:a,fitViewResolver:null,connection:{...GB},connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:"",autoPanOnConnect:!0,autoPanOnNodeDrag:!0,autoPanOnNodeFocus:!0,autoPanSpeed:15,connectionRadius:20,onError:Kce,isValidConnection:void 0,onSelectionChangeHandlers:,lib:"react",debug:!1,ariaLabelConfig:qB}},Vfe=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:o,height:i,fitView:s,fitViewOptions:a,minZoom:l,maxZoom:c,nodeOrigin:d,nodeExtent:f})=>hde((p,g)=>{async function v(){const{nodeLookup:b,panZoom:_,fitViewOptions:x,fitViewResolver:w,width:C,height:E,minZoom:R,maxZoom:P}=g();_&&(await Vce({nodes:b,width:C,height:E,panZoom:_,minZoom:R,maxZoom:P},x),w==null||w.resolve(!0),p({fitViewResolver:null}))}return{...TO({nodes:e,edges:t,width:o,height:i,fitView:s,fitViewOptions:a,minZoom:l,maxZoom:c,nodeOrigin:d,nodeExtent:f,defaultNodes:n,defaultEdges:r}),setNodes:b=>{const{nodeLookup:_,parentLookup:x,nodeOrigin:w,elevateNodesOnSelect:C,fitViewQueued:E}=g(),R=xR(b,_,x,{nodeOrigin:w,nodeExtent:f,elevateNodesOnSelect:C,checkEquality:!0});E&&R?(v(),p({nodes:b,nodesInitialized:R,fitViewQueued:!1,fitViewOptions:void 0})):p({nodes:b,nodesInitialized:R})},setEdges:b=>{const{connectionLookup:_,edgeLookup:x}=g();a8(_,x,b),p({edges:b})},setDefaultNodesAndEdges:(b,_)=>{if(b){const{setNodes:x}=g();x(b),p({hasDefaultNodes:!0})}if(_){const{setEdges:x}=g();x(_),p({hasDefaultEdges:!0})}},updateNodeInternals:b=>{const{triggerNodeChanges:_,nodeLookup:x,parentLookup:w,domNode:C,nodeOrigin:E,nodeExtent:R,debug:P,fitViewQueued:N}=g(),{changes:k,updatedInternals:I}=gue(b,x,w,C,E,R);I&&(hue(x,w,{nodeOrigin:E,nodeExtent:R}),N?(v(),p({fitViewQueued:!1,fitViewOptions:void 0})):p({}),(k==null?void 0:k.length)>0&&(P&&console.log("React Flow: trigger node changes",k),_==null||_(k)))},updateNodePositions:(b,_=!1)=>{const x=,w=,{nodeLookup:C,triggerNodeChanges:E}=g();for(constR,Pof b){const N=C.get(R),k=!!(N!=null&&N.expandParent&&(N!=null&&N.parentId)&&(P!=null&&P.position)),I={id:R,type:"position",position:k?{x:Math.max(0,P.position.x),y:Math.max(0,P.position.y)}:P.position,dragging:_};k&&N.parentId&&x.push({id:R,parentId:N.parentId,rect:{...P.internals.positionAbsolute,width:P.measured.width??0,height:P.measured.height??0}}),w.push(I)}if(x.length>0){const{parentLookup:R,nodeOrigin:P}=g(),N=$k(x,C,R,P);w.push(...N)}E(w)},triggerNodeChanges:b=>{const{onNodesChange:_,setNodes:x,nodes:w,hasDefaultNodes:C,debug:E}=g();if(b!=null&&b.length){if(C){const R=N8(b,w);x(R)}E&&console.log("React Flow: trigger node changes",b),_==null||_(b)}},triggerEdgeChanges:b=>{const{onEdgesChange:_,setEdges:x,edges:w,hasDefaultEdges:C,debug:E}=g();if(b!=null&&b.length){if(C){const R=R8(b,w);x(R)}E&&console.log("React Flow: trigger edge changes",b),_==null||_(b)}},addSelectedNodes:b=>{const{multiSelectionActive:_,edgeLookup:x,nodeLookup:w,triggerNodeChanges:C,triggerEdgeChanges:E}=g();if(_){const R=b.map(P=>ou(P,!0));C(R);return}C(Kd(w,new Set(...b),!0)),E(Kd(x))},addSelectedEdges:b=>{const{multiSelectionActive:_,edgeLookup:x,nodeLookup:w,triggerNodeChanges:C,triggerEdgeChanges:E}=g();if(_){const R=b.map(P=>ou(P,!0));E(R);return}E(Kd(x,new Set(...b))),C(Kd(w,new Set,!0))},unselectNodesAndEdges:({nodes:b,edges:_}={})=>{const{edges:x,nodes:w,nodeLookup:C,triggerNodeChanges:E,triggerEdgeChanges:R}=g(),P=b||w,N=_||x,k=P.map(O=>{const j=C.get(O.id);return j&&(j.selected=!1),ou(O.id,!1)}),I=N.map(O=>ou(O.id,!1));E(k),R(I)},setMinZoom:b=>{const{panZoom:_,maxZoom:x}=g();_==null||_.setScaleExtent(b,x),p({minZoom:b})},setMaxZoom:b=>{const{panZoom:_,minZoom:x}=g();_==null||_.setScaleExtent(x,b),p({maxZoom:b})},setTranslateExtent:b=>{var _;(_=g().panZoom)==null||_.setTranslateExtent(b),p({translateExtent:b})},setPaneClickDistance:b=>{var _;(_=g().panZoom)==null||_.setClickDistance(b)},resetSelectedElements:()=>{const{edges:b,nodes:_,triggerNodeChanges:x,triggerEdgeChanges:w,elementsSelectable:C}=g();if(!C)return;const E=_.reduce((P,N)=>N.selected?...P,ou(N.id,!1):P,),R=b.reduce((P,N)=>N.selected?...P,ou(N.id,!1):P,);x(E),w(R)},setNodeExtent:b=>{const{nodes:_,nodeLookup:x,parentLookup:w,nodeOrigin:C,elevateNodesOnSelect:E,nodeExtent:R}=g();b00===R00&&b01===R01&&b10===R10&&b11===R11||(xR(_,x,w,{nodeOrigin:C,nodeExtent:b,elevateNodesOnSelect:E,checkEquality:!1}),p({nodeExtent:b}))},panBy:b=>{const{transform:_,width:x,height:w,panZoom:C,translateExtent:E}=g();return vue({delta:b,panZoom:C,transform:_,translateExtent:E,width:x,height:w})},setCenter:async(b,_,x)=>{const{width:w,height:C,maxZoom:E,panZoom:R}=g();if(!R)return Promise.resolve(!1);const P=typeof(x==null?void 0:x.zoom)<"u"?x.zoom:E;return await R.setViewport({x:w/2-b*P,y:C/2-_*P,zoom:P},{duration:x==null?void 0:x.duration,ease:x==null?void 0:x.ease,interpolate:x==null?void 0:x.interpolate}),Promise.resolve(!0)},cancelConnection:()=>{p({connection:{...GB}})},updateConnection:b=>{p({connection:b})},reset:()=>p({...TO()})}},Object.is);function Z8({initialNodes:e,initialEdges:t,defaultNodes:n,defaultEdges:r,initialWidth:o,initialHeight:i,initialMinZoom:s,initialMaxZoom:a,initialFitViewOptions:l,fitView:c,nodeOrigin:d,nodeExtent:f,children:p}){constg=y.useState(()=>Vfe({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:o,height:i,fitView:c,minZoom:s,maxZoom:a,fitViewOptions:l,nodeOrigin:d,nodeExtent:f}));return h.jsx(pde,{value:g,children:h.jsx(Dde,{children:p})})}function Yfe({children:e,nodes:t,edges:n,defaultNodes:r,defaultEdges:o,width:i,height:s,fitView:a,fitViewOptions:l,minZoom:c,maxZoom:d,nodeOrigin:f,nodeExtent:p}){return y.useContext(Dx)?h.jsx(h.Fragment,{children:e}):h.jsx(Z8,{initialNodes:t,initialEdges:n,defaultNodes:r,defaultEdges:o,initialWidth:i,initialHeight:s,fitView:a,initialFitViewOptions:l,initialMinZoom:c,initialMaxZoom:d,nodeOrigin:f,nodeExtent:p,children:e})}const Kfe={width:"100%",height:"100%",overflow:"hidden",position:"relative",zIndex:0};function Xfe({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,className:o,nodeTypes:i,edgeTypes:s,onNodeClick:a,onEdgeClick:l,onInit:c,onMove:d,onMoveStart:f,onMoveEnd:p,onConnect:g,onConnectStart:v,onConnectEnd:b,onClickConnectStart:_,onClickConnectEnd:x,onNodeMouseEnter:w,onNodeMouseMove:C,onNodeMouseLeave:E,onNodeContextMenu:R,onNodeDoubleClick:P,onNodeDragStart:N,onNodeDrag:k,onNodeDragStop:I,onNodesDelete:O,onEdgesDelete:j,onDelete:H,onSelectionChange:q,onSelectionDragStart:M,onSelectionDrag:B,onSelectionDragStop:$,onSelectionContextMenu:W,onSelectionStart:G,onSelectionEnd:z,onBeforeDelete:K,connectionMode:Q,connectionLineType:re=Xl.Bezier,connectionLineStyle:ae,connectionLineComponent:de,connectionLineContainerStyle:Ne,deleteKeyCode:ye="Backspace",selectionKeyCode:fe="Shift",selectionOnDrag:Z=!1,selectionMode:oe=bm.Full,panActivationKeyCode:ce="Space",multiSelectionKeyCode:xe=_y()?"Meta":"Control",zoomActivationKeyCode:ge=_y()?"Meta":"Control",snapToGrid:pe,snapGrid:he,onlyRenderVisibleElements:we=!1,selectNodesOnDrag:Ie,nodesDraggable:Ce,autoPanOnNodeFocus:Me,nodesConnectable:ze,nodesFocusable:Ye,nodeOrigin:Ht=C8,edgesFocusable:Ft,edgesReconnectable:rt,elementsSelectable:Ue=!0,defaultViewport:Te=Rde,minZoom:bt=.5,maxZoom:jt=2,translateExtent:vn=xm,preventScrolling:dr=!0,nodeExtent:On,defaultMarkerColor:Sr="#b1b1b7",zoomOnScroll:kn=!0,zoomOnPinch:fn=!0,panOnScroll:sn=!1,panOnScrollSpeed:Tr=.5,panOnScrollMode:ga=_u.Free,zoomOnDoubleClick:_s=!0,panOnDrag:Cs=!0,onPaneClick:zi,onPaneMouseEnter:dl,onPaneMouseMove:kr,onPaneMouseLeave:fl,onPaneScroll:nd,onPaneContextMenu:mh,paneClickDistance:rd=0,nodeClickDistance:gh=0,children:od,onReconnect:vh,onReconnectStart:Hi,onReconnectEnd:nr,onEdgeContextMenu:$r,onEdgeDoubleClick:Es,onEdgeMouseEnter:fi,onEdgeMouseMove:zr,onEdgeMouseLeave:Ns,reconnectRadius:Rs=10,onNodesChange:id,onEdgesChange:sd,noDragClassName:yh="nodrag",noWheelClassName:xh="nowheel",noPanClassName:hi="nopan",fitView:Ic,fitViewOptions:Ts,connectOnClick:ad,attributionPosition:pi,proOptions:Kn,defaultEdgeOptions:Bn,elevateNodesOnSelect:Wn,elevateEdgesOnSelect:Bi,disableKeyboardA11y:Hr=!1,autoPanOnConnect:mi,autoPanOnNodeDrag:ld,autoPanSpeed:Ac,connectionRadius:hl,isValidConnection:Wi,onError:pl,style:ml,id:gl,nodeDragThreshold:Mc,connectionDragThreshold:jc,viewport:Lc,onViewportChange:vl,width:fr,height:Oc,colorMode:yl="light",debug:Dc,onScroll:ks,ariaLabelConfig:xl,...cd},bh){const va=gl||"1",Fc=Ide(yl),$c=y.useCallback(zc=>{zc.currentTarget.scrollTo({top:0,left:0,behavior:"instant"}),ks==null||ks(zc)},ks);return h.jsx("div",{"data-testid":"rf__wrapper",...cd,onScroll:$c,style:{...ml,...Kfe},ref:bh,className:tr("react-flow",o,Fc),id:gl,role:"application",children:h.jsxs(Yfe,{nodes:e,edges:t,width:fr,height:Oc,fitView:Ic,fitViewOptions:Ts,minZoom:bt,maxZoom:jt,nodeOrigin:Ht,nodeExtent:On,children:h.jsx(Gfe,{onInit:c,onNodeClick:a,onEdgeClick:l,onNodeMouseEnter:w,onNodeMouseMove:C,onNodeMouseLeave:E,onNodeContextMenu:R,onNodeDoubleClick:P,nodeTypes:i,edgeTypes:s,connectionLineType:re,connectionLineStyle:ae,connectionLineComponent:de,connectionLineContainerStyle:Ne,selectionKeyCode:fe,selectionOnDrag:Z,selectionMode:oe,deleteKeyCode:ye,multiSelectionKeyCode:xe,panActivationKeyCode:ce,zoomActivationKeyCode:ge,onlyRenderVisibleElements:we,defaultViewport:Te,translateExtent:vn,minZoom:bt,maxZoom:jt,preventScrolling:dr,zoomOnScroll:kn,zoomOnPinch:fn,zoomOnDoubleClick:_s,panOnScroll:sn,panOnScrollSpeed:Tr,panOnScrollMode:ga,panOnDrag:Cs,onPaneClick:zi,onPaneMouseEnter:dl,onPaneMouseMove:kr,onPaneMouseLeave:fl,onPaneScroll:nd,onPaneContextMenu:mh,paneClickDistance:rd,nodeClickDistance:gh,onSelectionContextMenu:W,onSelectionStart:G,onSelectionEnd:z,onReconnect:vh,onReconnectStart:Hi,onReconnectEnd:nr,onEdgeContextMenu:$r,onEdgeDoubleClick:Es,onEdgeMouseEnter:fi,onEdgeMouseMove:zr,onEdgeMouseLeave:Ns,reconnectRadius:Rs,defaultMarkerColor:Sr,noDragClassName:yh,noWheelClassName:xh,noPanClassName:hi,rfId:va,disableKeyboardA11y:Hr,nodeExtent:On,viewport:Lc,onViewportChange:vl}),h.jsx(Pde,{nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:g,onConnectStart:v,onConnectEnd:b,onClickConnectStart:_,onClickConnectEnd:x,nodesDraggable:Ce,autoPanOnNodeFocus:Me,nodesConnectable:ze,nodesFocusable:Ye,edgesFocusable:Ft,edgesReconnectable:rt,elementsSelectable:Ue,elevateNodesOnSelect:Wn,elevateEdgesOnSelect:Bi,minZoom:bt,maxZoom:jt,nodeExtent:On,onNodesChange:id,onEdgesChange:sd,snapToGrid:pe,snapGrid:he,connectionMode:Q,translateExtent:vn,connectOnClick:ad,defaultEdgeOptions:Bn,fitView:Ic,fitViewOptions:Ts,onNodesDelete:O,onEdgesDelete:j,onDelete:H,onNodeDragStart:N,onNodeDrag:k,onNodeDragStop:I,onSelectionDrag:B,onSelectionDragStart:M,onSelectionDragStop:$,onMove:d,onMoveStart:f,onMoveEnd:p,noPanClassName:hi,nodeOrigin:Ht,rfId:va,autoPanOnConnect:mi,autoPanOnNodeDrag:ld,autoPanSpeed:Ac,onError:pl,connectionRadius:hl,isValidConnection:Wi,selectNodesOnDrag:Ie,nodeDragThreshold:Mc,connectionDragThreshold:jc,onBeforeDelete:K,paneClickDistance:rd,debug:Dc,ariaLabelConfig:xl}),h.jsx(Nde,{onSelectionChange:q}),od,h.jsx(wde,{proOptions:Kn,position:pi}),h.jsx(bde,{rfId:va,disableKeyboardA11y:Hr})})})}var Zfe=T8(Xfe);const Qfe=e=>({x:e.transform0,y:e.transform1,zoom:e.transform2});function Jfe(){return kt(Qfe,Rn)}function ehe(e){constt,n=y.useState(e),r=y.useCallback(o=>n(i=>N8(o,i)),);returnt,n,r}function the(e){constt,n=y.useState(e),r=y.useCallback(o=>n(i=>R8(o,i)),);returnt,n,r}const nhe=e=>t=>{if(!e.includeHiddenNodes)return t.nodesInitialized;if(t.nodeLookup.size===0)return!1;for(const,{internals:n}of t.nodeLookup)if(n.handleBounds===void 0||!Mk(n.userNode))return!1;return!0};function rhe(e={includeHiddenNodes:!1}){return kt(nhe(e))}function ohe({dimensions:e,lineWidth:t,variant:n,className:r}){return h.jsx("path",{strokeWidth:t,d:`M${e0/2} 0 V${e1} M0 ${e1/2} H${e0}`,className:tr("react-flow__background-pattern",n,r)})}function ihe({radius:e,className:t}){return h.jsx("circle",{cx:e,cy:e,r:e,className:tr("react-flow__background-pattern","dots",t)})}var lc;(function(e){e.Lines="lines",e.Dots="dots",e.Cross="cross"})(lc||(lc={}));const she={lc.Dots:1,lc.Lines:1,lc.Cross:6},ahe=e=>({transform:e.transform,patternId:`pattern-${e.rfId}`});function Q8({id:e,variant:t=lc.Dots,gap:n=20,size:r,lineWidth:o=1,offset:i=0,color:s,bgColor:a,style:l,className:c,patternClassName:d}){const f=y.useRef(null),{transform:p,patternId:g}=kt(ahe,Rn),v=r||shet,b=t===lc.Dots,_=t===lc.Cross,x=Array.isArray(n)?n:n,n,w=x0*p2||1,x1*p2||1,C=v*p2,E=Array.isArray(i)?i:i,i,R=_?C,C:w,P=E0*p2||1+R0/2,E1*p2||1+R1/2,N=`${g}${e||""}`;return h.jsxs("svg",{className:tr("react-flow__background",c),style:{...l,...Fx,"--xy-background-color-props":a,"--xy-background-pattern-color-props":s},ref:f,"data-testid":"rf__background",children:h.jsx("pattern",{id:N,x:p0%w0,y:p1%w1,width:w0,height:w1,patternUnits:"userSpaceOnUse",patternTransform:`translate(-${P0},-${P1})`,children:b?h.jsx(ihe,{radius:C/2,className:d}):h.jsx(ohe,{dimensions:R,lineWidth:o,variant:t,className:d})}),h.jsx("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:`url(#${N})`})})}Q8.displayName="Background";const lhe=y.memo(Q8);function che(){return h.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32",children:h.jsx("path",{d:"M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"})})}function uhe(){return h.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 5",children:h.jsx("path",{d:"M0 0h32v4.2H0z"})})}function dhe(){return h.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 30",children:h.jsx("path",{d:"M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"})})}function fhe(){return h.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:h.jsx("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"})})}function hhe(){return h.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:h.jsx("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"})})}function Gv({children:e,className:t,...n}){return h.jsx("button",{type:"button",className:tr("react-flow__controls-button",t),...n,children:e})}const phe=e=>({isInteractive:e.nodesDraggable||e.nodesConnectable||e.elementsSelectable,minZoomReached:e.transform2<=e.minZoom,maxZoomReached:e.transform2>=e.maxZoom,ariaLabelConfig:e.ariaLabelConfig});function J8({style:e,showZoom:t=!0,showFitView:n=!0,showInteractive:r=!0,fitViewOptions:o,onZoomIn:i,onZoomOut:s,onFitView:a,onInteractiveChange:l,className:c,children:d,position:f="bottom-left",orientation:p="vertical","aria-label":g}){const v=jn(),{isInteractive:b,minZoomReached:_,maxZoomReached:x,ariaLabelConfig:w}=kt(phe,Rn),{zoomIn:C,zoomOut:E,fitView:R}=Qm(),P=()=>{C(),i==null||i()},N=()=>{E(),s==null||s()},k=()=>{R(o),a==null||a()},I=()=>{v.setState({nodesDraggable:!b,nodesConnectable:!b,elementsSelectable:!b}),l==null||l(!b)},O=p==="horizontal"?"horizontal":"vertical";return h.jsxs(Zm,{className:tr("react-flow__controls",O,c),position:f,style:e,"data-testid":"rf__controls","aria-label":g??w"controls.ariaLabel",children:t&&h.jsxs(h.Fragment,{children:h.jsx(Gv,{onClick:P,className:"react-flow__controls-zoomin",title:w"controls.zoomIn.ariaLabel","aria-label":w"controls.zoomIn.ariaLabel",disabled:x,children:h.jsx(che,{})}),h.jsx(Gv,{onClick:N,className:"react-flow__controls-zoomout",title:w"controls.zoomOut.ariaLabel","aria-label":w"controls.zoomOut.ariaLabel",disabled:_,children:h.jsx(uhe,{})})}),n&&h.jsx(Gv,{className:"react-flow__controls-fitview",onClick:k,title:w"controls.fitView.ariaLabel","aria-label":w"controls.fitView.ariaLabel",children:h.jsx(dhe,{})}),r&&h.jsx(Gv,{className:"react-flow__controls-interactive",onClick:I,title:w"controls.interactive.ariaLabel","aria-label":w"controls.interactive.ariaLabel",children:b?h.jsx(hhe,{}):h.jsx(fhe,{})}),d})}J8.displayName="Controls";const mhe=y.memo(J8);function ghe({id:e,x:t,y:n,width:r,height:o,style:i,color:s,strokeColor:a,strokeWidth:l,className:c,borderRadius:d,shapeRendering:f,selected:p,onClick:g}){const{background:v,backgroundColor:b}=i||{},_=s||v||b;return h.jsx("rect",{className:tr("react-flow__minimap-node",{selected:p},c),x:t,y:n,rx:d,ry:d,width:r,height:o,style:{fill:_,stroke:a,strokeWidth:l},shapeRendering:f,onClick:g?x=>g(x,e):void 0})}const vhe=y.memo(ghe),yhe=e=>e.nodes.map(t=>t.id),Q1=e=>e instanceof Function?e:()=>e;function xhe({nodeStrokeColor:e,nodeColor:t,nodeClassName:n="",nodeBorderRadius:r=5,nodeStrokeWidth:o,nodeComponent:i=vhe,onClick:s}){const a=kt(yhe,Rn),l=Q1(t),c=Q1(e),d=Q1(n),f=typeof window>"u"||window.chrome?"crispEdges":"geometricPrecision";return h.jsx(h.Fragment,{children:a.map(p=>h.jsx(whe,{id:p,nodeColorFunc:l,nodeStrokeColorFunc:c,nodeClassNameFunc:d,nodeBorderRadius:r,nodeStrokeWidth:o,NodeComponent:i,onClick:s,shapeRendering:f},p))})}function bhe({id:e,nodeColorFunc:t,nodeStrokeColorFunc:n,nodeClassNameFunc:r,nodeBorderRadius:o,nodeStrokeWidth:i,shapeRendering:s,NodeComponent:a,onClick:l}){const{node:c,x:d,y:f,width:p,height:g}=kt(v=>{const{internals:b}=v.nodeLookup.get(e),_=b.userNode,{x,y:w}=b.positionAbsolute,{width:C,height:E}=ll(_);return{node:_,x,y:w,width:C,height:E}},Rn);return!c||c.hidden||!Mk(c)?null:h.jsx(a,{x:d,y:f,width:p,height:g,style:c.style,selected:!!c.selected,className:r(c),color:t(c),borderRadius:o,strokeColor:n(c),strokeWidth:i,shapeRendering:s,onClick:l,id:c.id})}const whe=y.memo(bhe);var She=y.memo(xhe);const _he=200,Che=150,Ehe=e=>!e.hidden,Nhe=e=>{const t={x:-e.transform0/e.transform2,y:-e.transform1/e.transform2,width:e.width/e.transform2,height:e.height/e.transform2};return{viewBB:t,boundingRect:e.nodeLookup.size>0?QB(Kf(e.nodeLookup,{filter:Ehe}),t):t,rfId:e.rfId,panZoom:e.panZoom,translateExtent:e.translateExtent,flowWidth:e.width,flowHeight:e.height,ariaLabelConfig:e.ariaLabelConfig}},Rhe="react-flow__minimap-desc";function eW({style:e,className:t,nodeStrokeColor:n,nodeColor:r,nodeClassName:o="",nodeBorderRadius:i=5,nodeStrokeWidth:s,nodeComponent:a,bgColor:l,maskColor:c,maskStrokeColor:d,maskStrokeWidth:f,position:p="bottom-right",onClick:g,onNodeClick:v,pannable:b=!1,zoomable:_=!1,ariaLabel:x,inversePan:w,zoomStep:C=10,offsetScale:E=5}){const R=jn(),P=y.useRef(null),{boundingRect:N,viewBB:k,rfId:I,panZoom:O,translateExtent:j,flowWidth:H,flowHeight:q,ariaLabelConfig:M}=kt(Nhe,Rn),B=(e==null?void 0:e.width)??_he,$=(e==null?void 0:e.height)??Che,W=N.width/B,G=N.height/$,z=Math.max(W,G),K=z*B,Q=z*$,re=E*z,ae=N.x-(K-N.width)/2-re,de=N.y-(Q-N.height)/2-re,Ne=K+re*2,ye=Q+re*2,fe=`${Rhe}-${I}`,Z=y.useRef(0),oe=y.useRef();Z.current=z,y.useEffect(()=>{if(P.current&&O)return oe.current=Eue({domNode:P.current,panZoom:O,getTransform:()=>R.getState().transform,getViewScale:()=>Z.current}),()=>{var pe;(pe=oe.current)==null||pe.destroy()}},O),y.useEffect(()=>{var pe;(pe=oe.current)==null||pe.update({translateExtent:j,width:H,height:q,inversePan:w,pannable:b,zoomStep:C,zoomable:_})},b,_,w,C,j,H,q);const ce=g?pe=>{var Ie;consthe,we=((Ie=oe.current)==null?void 0:Ie.pointer(pe))||0,0;g(pe,{x:he,y:we})}:void 0,xe=v?y.useCallback((pe,he)=>{const we=R.getState().nodeLookup.get(he).internals.userNode;v(pe,we)},):void 0,ge=x??M"minimap.ariaLabel";return h.jsx(Zm,{position:p,style:{...e,"--xy-minimap-background-color-props":typeof l=="string"?l:void 0,"--xy-minimap-mask-background-color-props":typeof c=="string"?c:void 0,"--xy-minimap-mask-stroke-color-props":typeof d=="string"?d:void 0,"--xy-minimap-mask-stroke-width-props":typeof f=="number"?f*z:void 0,"--xy-minimap-node-background-color-props":typeof r=="string"?r:void 0,"--xy-minimap-node-stroke-color-props":typeof n=="string"?n:void 0,"--xy-minimap-node-stroke-width-props":typeof s=="number"?s:void 0},className:tr("react-flow__minimap",t),"data-testid":"rf__minimap",children:h.jsxs("svg",{width:B,height:$,viewBox:`${ae} ${de} ${Ne} ${ye}`,className:"react-flow__minimap-svg",role:"img","aria-labelledby":fe,ref:P,onClick:ce,children:ge&&h.jsx("title",{id:fe,children:ge}),h.jsx(She,{onClick:xe,nodeColor:r,nodeStrokeColor:n,nodeBorderRadius:i,nodeClassName:o,nodeStrokeWidth:s,nodeComponent:a}),h.jsx("path",{className:"react-flow__minimap-mask",d:`M${ae-re},${de-re}h${Ne+re*2}v${ye+re*2}h${-Ne-re*2}z + M${k.x},${k.y}h${k.width}v${k.height}h${-k.width}z`,fillRule:"evenodd",pointerEvents:"none"})})})}eW.displayName="MiniMap";const The=y.memo(eW),khe=e=>t=>e?`${Math.max(1/t.transform2,1)}`:void 0,Phe={kf.Line:"right",kf.Handle:"bottom-right"};function Ihe({nodeId:e,position:t,variant:n=kf.Handle,className:r,style:o=void 0,children:i,color:s,minWidth:a=10,minHeight:l=10,maxWidth:c=Number.MAX_VALUE,maxHeight:d=Number.MAX_VALUE,keepAspectRatio:f=!1,resizeDirection:p,autoScale:g=!0,shouldResize:v,onResizeStart:b,onResize:_,onResizeEnd:x}){const w=Hk(),C=typeof e=="string"?e:w,E=jn(),R=y.useRef(null),P=n===kf.Handle,N=kt(y.useCallback(khe(P&&g),P,g),Rn),k=y.useRef(null),I=t??Phen;y.useEffect(()=>{if(!(!R.current||!C))return k.current||(k.current=Hue({domNode:R.current,nodeId:C,getStoreItems:()=>{const{nodeLookup:j,transform:H,snapGrid:q,snapToGrid:M,nodeOrigin:B,domNode:$}=E.getState();return{nodeLookup:j,transform:H,snapGrid:q,snapToGrid:M,nodeOrigin:B,paneDomNode:$}},onChange:(j,H)=>{const{triggerNodeChanges:q,nodeLookup:M,parentLookup:B,nodeOrigin:$}=E.getState(),W=,G={x:j.x,y:j.y},z=M.get(C);if(z&&z.expandParent&&z.parentId){const K=z.origin??$,Q=j.width??z.measured.width??0,re=j.height??z.measured.height??0,ae={id:z.id,parentId:z.parentId,rect:{width:Q,height:re,...JB({x:j.x??z.position.x,y:j.y??z.position.y},{width:Q,height:re},z.parentId,M,K)}},de=$k(ae,M,B,$);W.push(...de),G.x=j.x?Math.max(K0*Q,j.x):void 0,G.y=j.y?Math.max(K1*re,j.y):void 0}if(G.x!==void 0&&G.y!==void 0){const K={id:C,type:"position",position:{...G}};W.push(K)}if(j.width!==void 0&&j.height!==void 0){const Q={id:C,type:"dimensions",resizing:!0,setAttributes:p?p==="horizontal"?"width":"height":!0,dimensions:{width:j.width,height:j.height}};W.push(Q)}for(const K of H){const Q={...K,type:"position"};W.push(Q)}q(W)},onEnd:({width:j,height:H})=>{const q={id:C,type:"dimensions",resizing:!1,dimensions:{width:j,height:H}};E.getState().triggerNodeChanges(q)}})),k.current.update({controlPosition:I,boundaries:{minWidth:a,minHeight:l,maxWidth:c,maxHeight:d},keepAspectRatio:f,resizeDirection:p,onResizeStart:b,onResize:_,onResizeEnd:x,shouldResize:v}),()=>{var j;(j=k.current)==null||j.destroy()}},I,a,l,c,d,f,b,_,x,v);const O=I.split("-");return h.jsx("div",{className:tr("react-flow__resize-control","nodrag",...O,n,r),ref:R,style:{...o,scale:N,...s&&{P?"backgroundColor":"borderColor":s}},children:i})}y.memo(Ihe);const Ahe=e=>{var t;return(t=e.domNode)==null?void 0:t.querySelector(".react-flow__renderer")};function Mhe({children:e}){const t=kt(Ahe);return t?al.createPortal(e,t):null}const jhe=(e,t)=>(e==null?void 0:e.internals.positionAbsolute.x)!==(t==null?void 0:t.internals.positionAbsolute.x)||(e==null?void 0:e.internals.positionAbsolute.y)!==(t==null?void 0:t.internals.positionAbsolute.y)||(e==null?void 0:e.measured.width)!==(t==null?void 0:t.measured.width)||(e==null?void 0:e.measured.height)!==(t==null?void 0:t.measured.height)||(e==null?void 0:e.selected)!==(t==null?void 0:t.selected)||(e==null?void 0:e.internals.z)!==(t==null?void 0:t.internals.z),Lhe=(e,t)=>{if(e.size!==t.size)return!1;for(constn,rof e)if(jhe(r,t.get(n)))return!1;return!0},Ohe=e=>({x:e.transform0,y:e.transform1,zoom:e.transform2,selectedNodesCount:e.nodes.filter(t=>t.selected).length});function Dhe({nodeId:e,children:t,className:n,style:r,isVisible:o,position:i=Ve.Top,offset:s=10,align:a="center",...l}){var R;const c=Hk(),d=y.useCallback(P=>(Array.isArray(e)?e:e||c||"").reduce((I,O)=>{const j=P.nodeLookup.get(O);return j&&I.set(j.id,j),I},new Map),e,c),f=kt(d,Lhe),{x:p,y:g,zoom:v,selectedNodesCount:b}=kt(Ohe,Rn);if(!(typeof o=="boolean"?o:f.size===1&&((R=f.values().next().value)==null?void 0:R.selected)&&b===1)||!f.size)return null;const x=Kf(f),w=Array.from(f.values()),C=Math.max(...w.map(P=>P.internals.z+1)),E={position:"absolute",transform:due(x,{x:p,y:g,zoom:v},i,s,a),zIndex:C,...r};return h.jsx(Mhe,{children:h.jsx("div",{style:E,className:tr("react-flow__node-toolbar",n),...l,"data-id":w.reduce((P,N)=>`${P}${N.id} `,"").trim(),children:t})})}var tW={exports:{}},nW={};/** + * @license React + * use-sync-external-store-with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Jm=y;function Fhe(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var $he=typeof Object.is=="function"?Object.is:Fhe,zhe=Jm.useSyncExternalStore,Hhe=Jm.useRef,Bhe=Jm.useEffect,Whe=Jm.useMemo,Uhe=Jm.useDebugValue;nW.useSyncExternalStoreWithSelector=function(e,t,n,r,o){var i=Hhe(null);if(i.current===null){var s={hasValue:!1,value:null};i.current=s}else s=i.current;i=Whe(function(){function l(g){if(!c){if(c=!0,d=g,g=r(g),o!==void 0&&s.hasValue){var v=s.value;if(o(v,g))return f=v}return f=g}if(v=f,$he(d,g))return v;var b=r(g);return o!==void 0&&o(v,b)?(d=g,v):(d=g,f=b)}var c=!1,d,f,p=n===void 0?null:n;returnfunction(){return l(t())},p===null?void 0:function(){return l(p())}},t,n,r,o);var a=zhe(e,i0,i1);return Bhe(function(){s.hasValue=!0,s.value=a},a),Uhe(a),a};tW.exports=nW;var qhe=tW.exports;function Ghe(e){e()}function Vhe(){let e=null,t=null;return{clear(){e=null,t=null},notify(){Ghe(()=>{let n=e;for(;n;)n.callback(),n=n.next})},get(){const n=;let r=e;for(;r;)n.push(r),r=r.next;return n},subscribe(n){let r=!0;const o=t={callback:n,next:null,prev:t};return o.prev?o.prev.next=o:e=o,function(){!r||e===null||(r=!1,o.next?o.next.prev=o.prev:t=o.prev,o.prev?o.prev.next=o.next:e=o.next)}}}}var kO={notify(){},get:()=>};function Yhe(e,t){let n,r=kO,o=0,i=!1;function s(b){d();const _=r.subscribe(b);let x=!1;return()=>{x||(x=!0,_(),f())}}function a(){r.notify()}function l(){v.onStateChange&&v.onStateChange()}function c(){return i}function d(){o++,n||(n=e.subscribe(l),r=Vhe())}function f(){o--,n&&o===0&&(n(),n=void 0,r.clear(),r=kO)}function p(){i||(i=!0,d())}function g(){i&&(i=!1,f())}const v={addNestedSub:s,notifyNestedSubs:a,handleChangeWrapper:l,isSubscribed:c,trySubscribe:p,tryUnsubscribe:g,getListeners:()=>r};return v}var Khe=()=>typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Xhe=Khe(),Zhe=()=>typeof navigator<"u"&&navigator.product==="ReactNative",Qhe=Zhe(),Jhe=()=>Xhe||Qhe?y.useLayoutEffect:y.useEffect,epe=Jhe(),J1=Symbol.for("react-redux-context"),eS=typeof globalThis<"u"?globalThis:{};function tpe(){if(!y.createContext)return{};const e=eSJ1??(eSJ1=new Map);let t=e.get(y.createContext);return t||(t=y.createContext(null),e.set(y.createContext,t)),t}var hc=tpe();function npe(e){const{children:t,context:n,serverState:r,store:o}=e,i=y.useMemo(()=>{const l=Yhe(o);return{store:o,subscription:l,getServerState:r?()=>r:void 0}},o,r),s=y.useMemo(()=>o.getState(),o);epe(()=>{const{subscription:l}=i;return l.onStateChange=l.notifyNestedSubs,l.trySubscribe(),s!==o.getState()&&l.notifyNestedSubs(),()=>{l.tryUnsubscribe(),l.onStateChange=void 0}},i,s);const a=n||hc;return y.createElement(a.Provider,{value:i},t)}var rpe=npe;function Bk(e=hc){return function(){return y.useContext(e)}}var rW=Bk();function oW(e=hc){const t=e===hc?rW:Bk(e),n=()=>{const{store:r}=t();return r};return Object.assign(n,{withTypes:()=>n}),n}var ope=oW();function ipe(e=hc){const t=e===hc?ope:oW(e),n=()=>t().dispatch;return Object.assign(n,{withTypes:()=>n}),n}var iW=ipe(),spe=(e,t)=>e===t;function ape(e=hc){const t=e===hc?rW:Bk(e),n=(r,o={})=>{const{equalityFn:i=spe}=typeof o=="function"?{equalityFn:o}:o,s=t(),{store:a,subscription:l,getServerState:c}=s;y.useRef(!0);const d=y.useCallback({r.name(p){return r(p)}}r.name,r),f=qhe.useSyncExternalStoreWithSelector(l.addNestedSub,a.getState,c||a.getState,d,i);return y.useDebugValue(f),f};return Object.assign(n,{withTypes:()=>n}),n}var zx=ape();function Ar(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var lpe=typeof Symbol=="function"&&Symbol.observable||"@@observable",PO=lpe,tS=()=>Math.random().toString(36).substring(7).split("").join("."),cpe={INIT:`@@redux/INIT${tS()}`,REPLACE:`@@redux/REPLACE${tS()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${tS()}`},Ey=cpe;function Wk(e){if(typeof e!="object"||e===null)return!1;let t=e;for(;Object.getPrototypeOf(t)!==null;)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||Object.getPrototypeOf(e)===null}function sW(e,t,n){if(typeof e!="function")throw new Error(Ar(2));if(typeof t=="function"&&typeof n=="function"||typeof n=="function"&&typeof arguments3=="function")throw new Error(Ar(0));if(typeof t=="function"&&typeof n>"u"&&(n=t,t=void 0),typeof n<"u"){if(typeof n!="function")throw new Error(Ar(1));return n(sW)(e,t)}let r=e,o=t,i=new Map,s=i,a=0,l=!1;function c(){s===i&&(s=new Map,i.forEach((_,x)=>{s.set(x,_)}))}function d(){if(l)throw new Error(Ar(3));return o}function f(_){if(typeof _!="function")throw new Error(Ar(4));if(l)throw new Error(Ar(5));let x=!0;c();const w=a++;return s.set(w,_),function(){if(x){if(l)throw new Error(Ar(6));x=!1,c(),s.delete(w),i=null}}}function p(_){if(!Wk(_))throw new Error(Ar(7));if(typeof _.type>"u")throw new Error(Ar(8));if(typeof _.type!="string")throw new Error(Ar(17));if(l)throw new Error(Ar(9));try{l=!0,o=r(o,_)}finally{l=!1}return(i=s).forEach(w=>{w()}),_}function g(_){if(typeof _!="function")throw new Error(Ar(10));r=_,p({type:Ey.REPLACE})}function v(){const _=f;return{subscribe(x){if(typeof x!="object"||x===null)throw new Error(Ar(11));function w(){const E=x;E.next&&E.next(d())}return w(),{unsubscribe:_(w)}},PO(){return this}}}return p({type:Ey.INIT}),{dispatch:p,subscribe:f,getState:d,replaceReducer:g,PO:v}}function upe(e){Object.keys(e).forEach(t=>{const n=et;if(typeof n(void 0,{type:Ey.INIT})>"u")throw new Error(Ar(12));if(typeof n(void 0,{type:Ey.PROBE_UNKNOWN_ACTION()})>"u")throw new Error(Ar(13))})}function dpe(e){const t=Object.keys(e),n={};for(let i=0;i<t.length;i++){const s=ti;typeof es=="function"&&(ns=es)}const r=Object.keys(n);let o;try{upe(n)}catch(i){o=i}return function(s={},a){if(o)throw o;let l=!1;const c={};for(let d=0;d<r.length;d++){const f=rd,p=nf,g=sf,v=p(g,a);if(typeof v>"u")throw a&&a.type,new Error(Ar(14));cf=v,l=l||v!==g}return l=l||r.length!==Object.keys(s).length,l?c:s}}function Ny(...e){return e.length===0?t=>t:e.length===1?e0:e.reduce((t,n)=>(...r)=>t(n(...r)))}function fpe(...e){return t=>(n,r)=>{const o=t(n,r);let i=()=>{throw new Error(Ar(15))};const s={getState:o.getState,dispatch:(l,...c)=>i(l,...c)},a=e.map(l=>l(s));return i=Ny(...a)(o.dispatch),{...o,dispatch:i}}}function aW(e){return Wk(e)&&"type"in e&&typeof e.type=="string"}var lW=Symbol.for("immer-nothing"),IO=Symbol.for("immer-draftable"),ai=Symbol.for("immer-state");function is(e,...t){throw new Error(`Immer minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var Af=Object.getPrototypeOf;function Ou(e){return!!e&&!!eai}function tl(e){var t;return e?cW(e)||Array.isArray(e)||!!eIO||!!((t=e.constructor)!=null&&tIO)||Bx(e)||Wx(e):!1}var hpe=Object.prototype.constructor.toString();function cW(e){if(!e||typeof e!="object")return!1;const t=Af(e);if(t===null)return!0;const n=Object.hasOwnProperty.call(t,"constructor")&&t.constructor;return n===Object?!0:typeof n=="function"&&Function.toString.call(n)===hpe}function Ry(e,t){Hx(e)===0?Reflect.ownKeys(e).forEach(n=>{t(n,en,e)}):e.forEach((n,r)=>t(r,n,e))}function Hx(e){const t=eai;return t?t.type_:Array.isArray(e)?1:Bx(e)?2:Wx(e)?3:0}function SR(e,t){return Hx(e)===2?e.has(t):Object.prototype.hasOwnProperty.call(e,t)}function uW(e,t,n){const r=Hx(e);r===2?e.set(t,n):r===3?e.add(n):et=n}function ppe(e,t){return e===t?e!==0||1/e===1/t:e!==e&&t!==t}function Bx(e){return e instanceof Map}function Wx(e){return e instanceof Set}function iu(e){return e.copy_||e.base_}function _R(e,t){if(Bx(e))return new Map(e);if(Wx(e))return new Set(e);if(Array.isArray(e))return Array.prototype.slice.call(e);const n=cW(e);if(t===!0||t==="class_only"&&!n){const r=Object.getOwnPropertyDescriptors(e);delete rai;let o=Reflect.ownKeys(r);for(let i=0;i<o.length;i++){const s=oi,a=rs;a.writable===!1&&(a.writable=!0,a.configurable=!0),(a.get||a.set)&&(rs={configurable:!0,writable:!0,enumerable:a.enumerable,value:es})}return Object.create(Af(e),r)}else{const r=Af(e);if(r!==null&&n)return{...e};const o=Object.create(r);return Object.assign(o,e)}}function Uk(e,t=!1){return Ux(e)||Ou(e)||!tl(e)||(Hx(e)>1&&(e.set=e.add=e.clear=e.delete=mpe),Object.freeze(e),t&&Object.entries(e).forEach((n,r)=>Uk(r,!0))),e}function mpe(){is(2)}function Ux(e){return Object.isFrozen(e)}var gpe={};function Du(e){const t=gpee;return t||is(0,e),t}var Em;function dW(){return Em}function vpe(e,t){return{drafts_:,parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0}}function AO(e,t){t&&(Du("Patches"),e.patches_=,e.inversePatches_=,e.patchListener_=t)}function CR(e){ER(e),e.drafts_.forEach(ype),e.drafts_=null}function ER(e){e===Em&&(Em=e.parent_)}function MO(e){return Em=vpe(Em,e)}function ype(e){const t=eai;t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function jO(e,t){t.unfinalizedDrafts_=t.drafts_.length;const n=t.drafts_0;return e!==void 0&&e!==n?(nai.modified_&&(CR(t),is(4)),tl(e)&&(e=Ty(t,e),t.parent_||ky(t,e)),t.patches_&&Du("Patches").generateReplacementPatches_(nai.base_,e,t.patches_,t.inversePatches_)):e=Ty(t,n,),CR(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==lW?e:void 0}function Ty(e,t,n){if(Ux(t))return t;const r=tai;if(!r)return Ry(t,(o,i)=>LO(e,r,t,o,i,n)),t;if(r.scope_!==e)return t;if(!r.modified_)return ky(e,r.base_,!0),r.base_;if(!r.finalized_){r.finalized_=!0,r.scope_.unfinalizedDrafts_--;const o=r.copy_;let i=o,s=!1;r.type_===3&&(i=new Set(o),o.clear(),s=!0),Ry(i,(a,l)=>LO(e,r,o,a,l,n,s)),ky(e,o,!1),n&&e.patches_&&Du("Patches").generatePatches_(r,n,e.patches_,e.inversePatches_)}return r.copy_}function LO(e,t,n,r,o,i,s){if(Ou(o)){const a=i&&t&&t.type_!==3&&!SR(t.assigned_,r)?i.concat(r):void 0,l=Ty(e,o,a);if(uW(n,r,l),Ou(l))e.canAutoFreeze_=!1;else return}else s&&n.add(o);if(tl(o)&&!Ux(o)){if(!e.immer_.autoFreeze_&&e.unfinalizedDrafts_<1)return;Ty(e,o),(!t||!t.scope_.parent_)&&typeof r!="symbol"&&Object.prototype.propertyIsEnumerable.call(n,r)&&ky(e,o)}}function ky(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&Uk(t,n)}function xpe(e,t){const n=Array.isArray(e),r={type_:n?1:0,scope_:t?t.scope_:dW(),modified_:!1,finalized_:!1,assigned_:{},parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1};let o=r,i=qk;n&&(o=r,i=Nm);const{revoke:s,proxy:a}=Proxy.revocable(o,i);return r.draft_=a,r.revoke_=s,a}var qk={get(e,t){if(t===ai)return e;const n=iu(e);if(!SR(n,t))return bpe(e,n,t);const r=nt;return e.finalized_||!tl(r)?r:r===nS(e.base_,t)?(rS(e),e.copy_t=RR(r,e)):r},has(e,t){return t in iu(e)},ownKeys(e){return Reflect.ownKeys(iu(e))},set(e,t,n){const r=fW(iu(e),t);if(r!=null&&r.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){const o=nS(iu(e),t),i=o==null?void 0:oai;if(i&&i.base_===n)return e.copy_t=n,e.assigned_t=!1,!0;if(ppe(n,o)&&(n!==void 0||SR(e.base_,t)))return!0;rS(e),NR(e)}return e.copy_t===n&&(n!==void 0||t in e.copy_)||Number.isNaN(n)&&Number.isNaN(e.copy_t)||(e.copy_t=n,e.assigned_t=!0),!0},deleteProperty(e,t){return nS(e.base_,t)!==void 0||t in e.base_?(e.assigned_t=!1,rS(e),NR(e)):delete e.assigned_t,e.copy_&&delete e.copy_t,!0},getOwnPropertyDescriptor(e,t){const n=iu(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{writable:!0,configurable:e.type_!==1||t!=="length",enumerable:r.enumerable,value:nt}},defineProperty(){is(11)},getPrototypeOf(e){return Af(e.base_)},setPrototypeOf(){is(12)}},Nm={};Ry(qk,(e,t)=>{Nme=function(){return arguments0=arguments00,t.apply(this,arguments)}});Nm.deleteProperty=function(e,t){return Nm.set.call(this,e,t,void 0)};Nm.set=function(e,t,n){return qk.set.call(this,e0,t,n,e0)};function nS(e,t){const n=eai;return(n?iu(n):e)t}function bpe(e,t,n){var o;const r=fW(t,n);return r?"value"in r?r.value:(o=r.get)==null?void 0:o.call(e.draft_):void 0}function fW(e,t){if(!(t in e))return;let n=Af(e);for(;n;){const r=Object.getOwnPropertyDescriptor(n,t);if(r)return r;n=Af(n)}}function NR(e){e.modified_||(e.modified_=!0,e.parent_&&NR(e.parent_))}function rS(e){e.copy_||(e.copy_=_R(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var wpe=class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.produce=(t,n,r)=>{if(typeof t=="function"&&typeof n!="function"){const i=n;n=t;const s=this;return function(l=i,...c){return s.produce(l,d=>n.call(this,d,...c))}}typeof n!="function"&&is(6),r!==void 0&&typeof r!="function"&&is(7);let o;if(tl(t)){const i=MO(this),s=RR(t,void 0);let a=!0;try{o=n(s),a=!1}finally{a?CR(i):ER(i)}return AO(i,r),jO(o,i)}else if(!t||typeof t!="object"){if(o=n(t),o===void 0&&(o=t),o===lW&&(o=void 0),this.autoFreeze_&&Uk(o,!0),r){const i=,s=;Du("Patches").generateReplacementPatches_(t,o,i,s),r(i,s)}return o}else is(1,t)},this.produceWithPatches=(t,n)=>{if(typeof t=="function")return(s,...a)=>this.produceWithPatches(s,l=>t(l,...a));let r,o;returnthis.produce(t,n,(s,a)=>{r=s,o=a}),r,o},typeof(e==null?void 0:e.autoFreeze)=="boolean"&&this.setAutoFreeze(e.autoFreeze),typeof(e==null?void 0:e.useStrictShallowCopy)=="boolean"&&this.setUseStrictShallowCopy(e.useStrictShallowCopy)}createDraft(e){tl(e)||is(8),Ou(e)&&(e=Spe(e));const t=MO(this),n=RR(e,void 0);return nai.isManual_=!0,ER(t),n}finishDraft(e,t){const n=e&&eai;(!n||!n.isManual_)&&is(9);const{scope_:r}=n;return AO(r,t),jO(void 0,r)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}applyPatches(e,t){let n;for(n=t.length-1;n>=0;n--){const o=tn;if(o.path.length===0&&o.op==="replace"){e=o.value;break}}n>-1&&(t=t.slice(n+1));const r=Du("Patches").applyPatches_;return Ou(e)?r(e,t):this.produce(e,o=>r(o,t))}};function RR(e,t){const n=Bx(e)?Du("MapSet").proxyMap_(e,t):Wx(e)?Du("MapSet").proxySet_(e,t):xpe(e,t);return(t?t.scope_:dW()).drafts_.push(n),n}function Spe(e){return Ou(e)||is(10,e),hW(e)}function hW(e){if(!tl(e)||Ux(e))return e;const t=eai;let n;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=_R(e,t.scope_.immer_.useStrictShallowCopy_)}else n=_R(e,!0);return Ry(n,(r,o)=>{uW(n,r,hW(o))}),t&&(t.finalized_=!1),n}var li=new wpe,pW=li.produce;li.produceWithPatches.bind(li);li.setAutoFreeze.bind(li);li.setUseStrictShallowCopy.bind(li);li.applyPatches.bind(li);li.createDraft.bind(li);li.finishDraft.bind(li);function _pe(e,t=`expected a function, instead received ${typeof e}`){if(typeof e!="function")throw new TypeError(t)}function Cpe(e,t=`expected an object, instead received ${typeof e}`){if(typeof e!="object")throw new TypeError(t)}function Epe(e,t="expected all items to be functions, instead received the following types: "){if(!e.every(n=>typeof n=="function")){const n=e.map(r=>typeof r=="function"?`function ${r.name||"unnamed"}()`:typeof r).join(", ");throw new TypeError(`${t}${n}`)}}var OO=e=>Array.isArray(e)?e:e;function Npe(e){const t=Array.isArray(e0)?e0:e;return Epe(t,"createSelector expects all input-selectors to be functions, but received the following types: "),t}function Rpe(e,t){const n=,{length:r}=e;for(let o=0;o<r;o++)n.push(eo.apply(null,t));return n}var Tpe=class{constructor(e){this.value=e}deref(){return this.value}},kpe=typeof WeakRef<"u"?WeakRef:Tpe,Ppe=0,DO=1;function Vv(){return{s:Ppe,v:void 0,o:null,p:null}}function mW(e,t={}){let n=Vv();const{resultEqualityCheck:r}=t;let o,i=0;function s(){var f;let a=n;const{length:l}=arguments;for(let p=0,g=l;p<g;p++){const v=argumentsp;if(typeof v=="function"||typeof v=="object"&&v!==null){let b=a.o;b===null&&(a.o=b=new WeakMap);const _=b.get(v);_===void 0?(a=Vv(),b.set(v,a)):a=_}else{let b=a.p;b===null&&(a.p=b=new Map);const _=b.get(v);_===void 0?(a=Vv(),b.set(v,a)):a=_}}const c=a;let d;if(a.s===DO)d=a.v;else if(d=e.apply(null,arguments),i++,r){const p=((f=o==null?void 0:o.deref)==null?void 0:f.call(o))??o;p!=null&&r(p,d)&&(d=p,i!==0&&i--),o=typeof d=="object"&&d!==null||typeof d=="function"?new kpe(d):d}return c.s=DO,c.v=d,d}return s.clearCache=()=>{n=Vv(),s.resetResultsCount()},s.resultsCount=()=>i,s.resetResultsCount=()=>{i=0},s}function Ipe(e,...t){const n=typeof e=="function"?{memoize:e,memoizeOptions:t}:e,r=(...o)=>{let i=0,s=0,a,l={},c=o.pop();typeof c=="object"&&(l=c,c=o.pop()),_pe(c,`createSelector expects an output function after the inputs, but received: ${typeof c}`);const d={...n,...l},{memoize:f,memoizeOptions:p=,argsMemoize:g=mW,argsMemoizeOptions:v=}=d,b=OO(p),_=OO(v),x=Npe(o),w=f(function(){return i++,c.apply(null,arguments)},...b),C=g(function(){s++;const R=Rpe(x,arguments);return a=w.apply(null,R),a},..._);return Object.assign(C,{resultFunc:c,memoizedResultFunc:w,dependencies:x,dependencyRecomputations:()=>s,resetDependencyRecomputations:()=>{s=0},lastResult:()=>a,recomputations:()=>i,resetRecomputations:()=>{i=0},memoize:f,argsMemoize:g})};return Object.assign(r,{withTypes:()=>r}),r}var Nt=Ipe(mW),Ape=Object.assign((e,t=Nt)=>{Cpe(e,`createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof e}`);const n=Object.keys(e),r=n.map(i=>ei);return t(r,(...i)=>i.reduce((s,a,l)=>(snl=a,s),{}))},{withTypes:()=>Ape});function gW(e){return({dispatch:n,getState:r})=>o=>i=>typeof i=="function"?i(n,r,e):o(i)}var Mpe=gW(),jpe=gW,Lpe=typeof window<"u"&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__:function(){if(arguments.length!==0)return typeof arguments0=="object"?Ny:Ny.apply(null,arguments)},Ope=e=>e&&typeof e.match=="function";function Ga(e,t){function n(...r){if(t){let o=t(...r);if(!o)throw new Error(ei(0));return{type:e,payload:o.payload,..."meta"in o&&{meta:o.meta},..."error"in o&&{error:o.error}}}return{type:e,payload:r0}}return n.toString=()=>`${e}`,n.type=e,n.match=r=>aW(r)&&r.type===e,n}var vW=class xp extends Array{constructor(...t){super(...t),Object.setPrototypeOf(this,xp.prototype)}static getSymbol.species(){return xp}concat(...t){return super.concat.apply(this,t)}prepend(...t){return t.length===1&&Array.isArray(t0)?new xp(...t0.concat(this)):new xp(...t.concat(this))}};function FO(e){return tl(e)?pW(e,()=>{}):e}function Yv(e,t,n){return e.has(t)?e.get(t):e.set(t,n(t)).get(t)}function Dpe(e){return typeof e=="boolean"}var Fpe=()=>function(t){const{thunk:n=!0,immutableCheck:r=!0,serializableCheck:o=!0,actionCreatorCheck:i=!0}=t??{};let s=new vW;return n&&(Dpe(n)?s.push(Mpe):s.push(jpe(n.extraArgument))),s},$pe="RTK_autoBatch",$O=e=>t=>{setTimeout(t,e)},zpe=(e={type:"raf"})=>t=>(...n)=>{const r=t(...n);let o=!0,i=!1,s=!1;const a=new Set,l=e.type==="tick"?queueMicrotask:e.type==="raf"?typeof window<"u"&&window.requestAnimationFrame?window.requestAnimationFrame:$O(10):e.type==="callback"?e.queueNotification:$O(e.timeout),c=()=>{s=!1,i&&(i=!1,a.forEach(d=>d()))};return Object.assign({},r,{subscribe(d){const f=()=>o&&d(),p=r.subscribe(f);return a.add(d),()=>{p(),a.delete(d)}},dispatch(d){var f;try{return o=!((f=d==null?void 0:d.meta)!=null&&f$pe),i=!o,i&&(s||(s=!0,l(c))),r.dispatch(d)}finally{o=!0}}})},Hpe=e=>function(n){const{autoBatch:r=!0}=n??{};let o=new vW(e);return r&&o.push(zpe(typeof r=="object"?r:void 0)),o};function Bpe(e){const t=Fpe(),{reducer:n=void 0,middleware:r,devTools:o=!0,preloadedState:i=void 0,enhancers:s=void 0}=e||{};let a;if(typeof n=="function")a=n;else if(Wk(n))a=dpe(n);else throw new Error(ei(1));let l;typeof r=="function"?l=r(t):l=t();let c=Ny;o&&(c=Lpe({trace:!1,...typeof o=="object"&&o}));const d=fpe(...l),f=Hpe(d);let p=typeof s=="function"?s(f):f();const g=c(...p);return sW(a,i,g)}function yW(e){const t={},n=;let r;const o={addCase(i,s){const a=typeof i=="string"?i:i.type;if(!a)throw new Error(ei(28));if(a in t)throw new Error(ei(29));return ta=s,o},addMatcher(i,s){return n.push({matcher:i,reducer:s}),o},addDefaultCase(i){return r=i,o}};return e(o),t,n,r}function Wpe(e){return typeof e=="function"}function Upe(e,t){letn,r,o=yW(t),i;if(Wpe(e))i=()=>FO(e());else{const a=FO(e);i=()=>a}function s(a=i(),l){let c=nl.type,...r.filter(({matcher:d})=>d(l)).map(({reducer:d})=>d);return c.filter(d=>!!d).length===0&&(c=o),c.reduce((d,f)=>{if(f)if(Ou(d)){const g=f(d,l);return g===void 0?d:g}else{if(tl(d))return pW(d,p=>f(p,l));{const p=f(d,l);if(p===void 0){if(d===null)return d;throw Error("A case reducer on a non-draftable value must not return undefined")}return p}}return d},a)}return s.getInitialState=i,s}var qpe=(e,t)=>Ope(e)?e.match(t):e(t);function qx(...e){return t=>e.some(n=>qpe(n,t))}var Gpe="ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW",xW=(e=21)=>{let t="",n=e;for(;n--;)t+=GpeMath.random()*64|0;return t},Vpe="name","message","stack","code",oS=class{constructor(e,t){Le(this,"_type");this.payload=e,this.meta=t}},zO=class{constructor(e,t){Le(this,"_type");this.payload=e,this.meta=t}},Ype=e=>{if(typeof e=="object"&&e!==null){const t={};for(const n of Vpe)typeof en=="string"&&(tn=en);return t}return{message:String(e)}},HO="External signal was aborted",Kpe=(()=>{function e(t,n,r){const o=Ga(t+"/fulfilled",(l,c,d,f)=>({payload:l,meta:{...f||{},arg:d,requestId:c,requestStatus:"fulfilled"}})),i=Ga(t+"/pending",(l,c,d)=>({payload:void 0,meta:{...d||{},arg:c,requestId:l,requestStatus:"pending"}})),s=Ga(t+"/rejected",(l,c,d,f,p)=>({payload:f,error:(r&&r.serializeError||Ype)(l||"Rejected"),meta:{...p||{},arg:d,requestId:c,rejectedWithValue:!!f,requestStatus:"rejected",aborted:(l==null?void 0:l.name)==="AbortError",condition:(l==null?void 0:l.name)==="ConditionError"}}));function a(l,{signal:c}={}){return(d,f,p)=>{const g=r!=null&&r.idGenerator?r.idGenerator(l):xW(),v=new AbortController;let b,_;function x(C){_=C,v.abort()}c&&(c.aborted?x(HO):c.addEventListener("abort",()=>x(HO),{once:!0}));const w=async function(){var R,P;let C;try{let N=(R=r==null?void 0:r.condition)==null?void 0:R.call(r,l,{getState:f,extra:p});if(Zpe(N)&&(N=await N),N===!1||v.signal.aborted)throw{name:"ConditionError",message:"Aborted due to condition callback returning false."};const k=new Promise((I,O)=>{b=()=>{O({name:"AbortError",message:_||"Aborted"})},v.signal.addEventListener("abort",b)});d(i(g,l,(P=r==null?void 0:r.getPendingMeta)==null?void 0:P.call(r,{requestId:g,arg:l},{getState:f,extra:p}))),C=await Promise.race(k,Promise.resolve(n(l,{dispatch:d,getState:f,extra:p,requestId:g,signal:v.signal,abort:x,rejectWithValue:(I,O)=>new oS(I,O),fulfillWithValue:(I,O)=>new zO(I,O)})).then(I=>{if(I instanceof oS)throw I;return I instanceof zO?o(I.payload,g,l,I.meta):o(I,g,l)}))}catch(N){C=N instanceof oS?s(null,g,l,N.payload,N.meta):s(N,g,l)}finally{b&&v.signal.removeEventListener("abort",b)}return r&&!r.dispatchConditionRejection&&s.match(C)&&C.meta.condition||d(C),C}();return Object.assign(w,{abort:x,requestId:g,arg:l,unwrap(){return w.then(Xpe)}})}}return Object.assign(a,{pending:i,rejected:s,fulfilled:o,settled:qx(s,o),typePrefix:t})}return e.withTypes=()=>e,e})();function Xpe(e){if(e.meta&&e.meta.rejectedWithValue)throw e.payload;if(e.error)throw e.error;return e.payload}function Zpe(e){return e!==null&&typeof e=="object"&&typeof e.then=="function"}var Qpe=Symbol.for("rtk-slice-createasyncthunk");function Jpe(e,t){return`${e}/${t}`}function eme({creators:e}={}){var n;const t=(n=e==null?void 0:e.asyncThunk)==null?void 0:nQpe;return function(o){const{name:i,reducerPath:s=i}=o;if(!i)throw new Error(ei(11));const a=(typeof o.reducers=="function"?o.reducers(nme()):o.reducers)||{},l=Object.keys(a),c={sliceCaseReducersByName:{},sliceCaseReducersByType:{},actionCreators:{},sliceMatchers:},d={addCase(E,R){const P=typeof E=="string"?E:E.type;if(!P)throw new Error(ei(12));if(P in c.sliceCaseReducersByType)throw new Error(ei(13));return c.sliceCaseReducersByTypeP=R,d},addMatcher(E,R){return c.sliceMatchers.push({matcher:E,reducer:R}),d},exposeAction(E,R){return c.actionCreatorsE=R,d},exposeCaseReducer(E,R){return c.sliceCaseReducersByNameE=R,d}};l.forEach(E=>{const R=aE,P={reducerName:E,type:Jpe(i,E),createNotation:typeof o.reducers=="function"};ome(R)?sme(P,R,d,t):rme(P,R,d)});function f(){constE={},R=,P=void 0=typeof o.extraReducers=="function"?yW(o.extraReducers):o.extraReducers,N={...E,...c.sliceCaseReducersByType};return Upe(o.initialState,k=>{for(let I in N)k.addCase(I,NI);for(let I of c.sliceMatchers)k.addMatcher(I.matcher,I.reducer);for(let I of R)k.addMatcher(I.matcher,I.reducer);P&&k.addDefaultCase(P)})}const p=E=>E,g=new Map,v=new WeakMap;let b;function _(E,R){return b||(b=f()),b(E,R)}function x(){return b||(b=f()),b.getInitialState()}function w(E,R=!1){function P(k){let I=kE;return typeof I>"u"&&R&&(I=Yv(v,P,x)),I}function N(k=p){const I=Yv(g,R,()=>new WeakMap);return Yv(I,k,()=>{const O={};for(constj,Hof Object.entries(o.selectors??{}))Oj=tme(H,k,()=>Yv(v,k,x),R);return O})}return{reducerPath:E,getSelectors:N,get selectors(){return N(P)},selectSlice:P}}const C={name:i,reducer:_,actions:c.actionCreators,caseReducers:c.sliceCaseReducersByName,getInitialState:x,...w(s),injectInto(E,{reducerPath:R,...P}={}){const N=R??s;return E.inject({reducerPath:N,reducer:_},P),{...C,...w(N,!0)}}};return C}}function tme(e,t,n,r){function o(i,...s){let a=t(i);return typeof a>"u"&&r&&(a=n()),e(a,...s)}return o.unwrapped=e,o}var Sc=eme();function nme(){function e(t,n){return{_reducerDefinitionType:"asyncThunk",payloadCreator:t,...n}}return e.withTypes=()=>e,{reducer(t){return Object.assign({t.name(...n){return t(...n)}}t.name,{_reducerDefinitionType:"reducer"})},preparedReducer(t,n){return{_reducerDefinitionType:"reducerWithPrepare",prepare:t,reducer:n}},asyncThunk:e}}function rme({type:e,reducerName:t,createNotation:n},r,o){let i,s;if("reducer"in r){if(n&&!ime(r))throw new Error(ei(17));i=r.reducer,s=r.prepare}else i=r;o.addCase(e,i).exposeCaseReducer(t,i).exposeAction(t,s?Ga(e,s):Ga(e))}function ome(e){return e._reducerDefinitionType==="asyncThunk"}function ime(e){return e._reducerDefinitionType==="reducerWithPrepare"}function sme({type:e,reducerName:t},n,r,o){if(!o)throw new Error(ei(18));const{payloadCreator:i,fulfilled:s,pending:a,rejected:l,settled:c,options:d}=n,f=o(e,i,d);r.exposeAction(t,f),s&&r.addCase(f.fulfilled,s),a&&r.addCase(f.pending,a),l&&r.addCase(f.rejected,l),c&&r.addMatcher(f.settled,c),r.exposeCaseReducer(t,{fulfilled:s||Kv,pending:a||Kv,rejected:l||Kv,settled:c||Kv})}function Kv(){}var ame="task",bW="listener",wW="completed",Gk="cancelled",lme=`task-${Gk}`,cme=`task-${wW}`,TR=`${bW}-${Gk}`,ume=`${bW}-${wW}`,Gx=class{constructor(e){Le(this,"name","TaskAbortError");Le(this,"message");this.code=e,this.message=`${ame} ${Gk} (reason: ${e})`}},Vk=(e,t)=>{if(typeof e!="function")throw new TypeError(ei(32))},Py=()=>{},SW=(e,t=Py)=>(e.catch(t),e),_W=(e,t)=>(e.addEventListener("abort",t,{once:!0}),()=>e.removeEventListener("abort",t)),Cu=(e,t)=>{const n=e.signal;n.aborted||("reason"in n||Object.defineProperty(n,"reason",{enumerable:!0,value:t,configurable:!0,writable:!0}),e.abort(t))},Eu=e=>{if(e.aborted){const{reason:t}=e;throw new Gx(t)}};function CW(e,t){let n=Py;return new Promise((r,o)=>{const i=()=>o(new Gx(e.reason));if(e.aborted){i();return}n=_W(e,i),t.finally(()=>n()).then(r,o)}).finally(()=>{n=Py})}var dme=async(e,t)=>{try{return await Promise.resolve(),{status:"ok",value:await e()}}catch(n){return{status:n instanceof Gx?"cancelled":"rejected",error:n}}finally{t==null||t()}},Iy=e=>t=>SW(CW(e,t).then(n=>(Eu(e),n))),EW=e=>{const t=Iy(e);return n=>t(new Promise(r=>setTimeout(r,n)))},{assign:lf}=Object,BO={},Vx="listenerMiddleware",fme=(e,t)=>{const n=r=>_W(e,()=>Cu(r,e.reason));return(r,o)=>{Vk(r);const i=new AbortController;n(i);const s=dme(async()=>{Eu(e),Eu(i.signal);const a=await r({pause:Iy(i.signal),delay:EW(i.signal),signal:i.signal});return Eu(i.signal),a},()=>Cu(i,cme));return o!=null&&o.autoJoin&&t.push(s.catch(Py)),{result:Iy(e)(s),cancel(){Cu(i,lme)}}}},hme=(e,t)=>{const n=async(r,o)=>{Eu(t);let i=()=>{};const a=new Promise((l,c)=>{let d=e({predicate:r,effect:(f,p)=>{p.unsubscribe(),l(f,p.getState(),p.getOriginalState())}});i=()=>{d(),c()}});o!=null&&a.push(new Promise(l=>setTimeout(l,o,null)));try{const l=await CW(t,Promise.race(a));return Eu(t),l}finally{i()}};return(r,o)=>SW(n(r,o))},NW=e=>{let{type:t,actionCreator:n,matcher:r,predicate:o,effect:i}=e;if(t)o=Ga(t).match;else if(n)t=n.type,o=n.match;else if(r)o=r;else if(!o)throw new Error(ei(21));return Vk(i),{predicate:o,type:t,effect:i}},RW=lf(e=>{const{type:t,predicate:n,effect:r}=NW(e);return{id:xW(),effect:r,type:t,predicate:n,pending:new Set,unsubscribe:()=>{throw new Error(ei(22))}}},{withTypes:()=>RW}),WO=(e,t)=>{const{type:n,effect:r,predicate:o}=NW(t);return Array.from(e.values()).find(i=>(typeof n=="string"?i.type===n:i.predicate===o)&&i.effect===r)},kR=e=>{e.pending.forEach(t=>{Cu(t,TR)})},pme=e=>()=>{e.forEach(kR),e.clear()},UO=(e,t,n)=>{try{e(t,n)}catch(r){setTimeout(()=>{throw r},0)}},TW=lf(Ga(`${Vx}/add`),{withTypes:()=>TW}),mme=Ga(`${Vx}/removeAll`),kW=lf(Ga(`${Vx}/remove`),{withTypes:()=>kW}),gme=(...e)=>{console.error(`${Vx}/error`,...e)},PW=(e={})=>{const t=new Map,{extra:n,onError:r=gme}=e;Vk(r);const o=d=>(d.unsubscribe=()=>t.delete(d.id),t.set(d.id,d),f=>{d.unsubscribe(),f!=null&&f.cancelActive&&kR(d)}),i=d=>{const f=WO(t,d)??RW(d);return o(f)};lf(i,{withTypes:()=>i});const s=d=>{const f=WO(t,d);return f&&(f.unsubscribe(),d.cancelActive&&kR(f)),!!f};lf(s,{withTypes:()=>s});const a=async(d,f,p,g)=>{const v=new AbortController,b=hme(i,v.signal),_=;try{d.pending.add(v),await Promise.resolve(d.effect(f,lf({},p,{getOriginalState:g,condition:(x,w)=>b(x,w).then(Boolean),take:b,delay:EW(v.signal),pause:Iy(v.signal),extra:n,signal:v.signal,fork:fme(v.signal,_),unsubscribe:d.unsubscribe,subscribe:()=>{t.set(d.id,d)},cancelActiveListeners:()=>{d.pending.forEach((x,w,C)=>{x!==v&&(Cu(x,TR),C.delete(x))})},cancel:()=>{Cu(v,TR),d.pending.delete(v)},throwIfCancelled:()=>{Eu(v.signal)}})))}catch(x){x instanceof Gx||UO(r,x,{raisedBy:"effect"})}finally{await Promise.all(_),Cu(v,ume),d.pending.delete(v)}},l=pme(t);return{middleware:d=>f=>p=>{if(!aW(p))return f(p);if(TW.match(p))return i(p.payload);if(mme.match(p)){l();return}if(kW.match(p))return s(p.payload);let g=d.getState();const v=()=>{if(g===BO)throw new Error(ei(23));return g};let b;try{if(b=f(p),t.size>0){const _=d.getState(),x=Array.from(t.values());for(const w of x){let C=!1;try{C=w.predicate(p,_,g)}catch(E){C=!1,UO(r,E,{raisedBy:"predicate"})}C&&a(w,p,d,v)}}}finally{g=BO}return b},startListening:i,stopListening:s,clearListeners:l}};function ei(e){return`Minified Redux Toolkit error #${e}; visit https://redux-toolkit.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var Ay={exports:{}};/** + * @license + * Lodash <https://lodash.com/> + * Copyright OpenJS Foundation and other contributors <https://openjsf.org/> + * Released under MIT license <https://lodash.com/license> + * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */Ay.exports;(function(e,t){(function(){var n,r="4.17.21",o=200,i="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",s="Expected a function",a="Invalid `variable` option passed into `_.template`",l="__lodash_hash_undefined__",c=500,d="__lodash_placeholder__",f=1,p=2,g=4,v=1,b=2,_=1,x=2,w=4,C=8,E=16,R=32,P=64,N=128,k=256,I=512,O=30,j="...",H=800,q=16,M=1,B=2,$=3,W=1/0,G=9007199254740991,z=17976931348623157e292,K=NaN,Q=4294967295,re=Q-1,ae=Q>>>1,de="ary",N,"bind",_,"bindKey",x,"curry",C,"curryRight",E,"flip",I,"partial",R,"partialRight",P,"rearg",k,Ne="object Arguments",ye="object Array",fe="object AsyncFunction",Z="object Boolean",oe="object Date",ce="object DOMException",xe="object Error",ge="object Function",pe="object GeneratorFunction",he="object Map",we="object Number",Ie="object Null",Ce="object Object",Me="object Promise",ze="object Proxy",Ye="object RegExp",Ht="object Set",Ft="object String",rt="object Symbol",Ue="object Undefined",Te="object WeakMap",bt="object WeakSet",jt="object ArrayBuffer",vn="object DataView",dr="object Float32Array",On="object Float64Array",Sr="object Int8Array",kn="object Int16Array",fn="object Int32Array",sn="object Uint8Array",Tr="object Uint8ClampedArray",ga="object Uint16Array",_s="object Uint32Array",Cs=/\b__p \+= '';/g,zi=/\b(__p \+=) '' \+/g,dl=/(__e\(.*?\)|\b__t\)) \+\n'';/g,kr=/&(?:amp|lt|gt|quot|#39);/g,fl=/&<>"'/g,nd=RegExp(kr.source),mh=RegExp(fl.source),rd=/<%-(\s\S+?)%>/g,gh=/<%(\s\S+?)%>/g,od=/<%=(\s\S+?)%>/g,vh=/\.|\(?:^\*|("')(?:(?!\1)^\\|\\.)*?\1)\/,Hi=/^\w*$/,nr=/^.\+|\(?:(-?\d+(?:\.\d+)?)|("')((?:(?!\2)^\\|\\.)*?)\2)\|(?=(?:\.|\\)(?:\.|\\|$))/g,$r=/\\^$.*+?()\{}|/g,Es=RegExp($r.source),fi=/^\s+/,zr=/\s/,Ns=/\{(?:\n\/\* \wrapped with .+\ \*\/)?\n?/,Rs=/\{\n\/\* \wrapped with (.+)\ \*/,id=/,? & /,sd=/^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f+/g,yh=/()=,{}\\\/\s/,xh=/\\(\\)?/g,hi=/\$\{(^\\}*(?:\\.^\\}*)*)\}/g,Ic=/\w*$/,Ts=/^-+0x0-9a-f+$/i,ad=/^0b01+$/i,pi=/^\object .+?Constructor\$/,Kn=/^0o0-7+$/i,Bn=/^(?:0|1-9\d*)$/,Wn=/\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f/g,Bi=/($^)/,Hr=/'\n\r\u2028\u2029\\/g,mi="\\ud800-\\udfff",ld="\\u0300-\\u036f",Ac="\\ufe20-\\ufe2f",hl="\\u20d0-\\u20ff",Wi=ld+Ac+hl,pl="\\u2700-\\u27bf",ml="a-z\\xdf-\\xf6\\xf8-\\xff",gl="\\xac\\xb1\\xd7\\xf7",Mc="\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf",jc="\\u2000-\\u206f",Lc=" \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",vl="A-Z\\xc0-\\xd6\\xd8-\\xde",fr="\\ufe0e\\ufe0f",Oc=gl+Mc+jc+Lc,yl="'’",Dc=""+mi+"",ks=""+Oc+"",xl=""+Wi+"",cd="\\d+",bh=""+pl+"",va=""+ml+"",Fc="^"+mi+Oc+cd+pl+ml+vl+"",$c="\\ud83c\\udffb-\\udfff",zc="(?:"+xl+"|"+$c+")",ud="^"+mi+"",bl="(?:\\ud83c\\udde6-\\uddff){2}",wl="\\ud800-\\udbff\\udc00-\\udfff",Sl=""+vl+"",dd="\\u200d",Tg="(?:"+va+"|"+Fc+")",ww="(?:"+Sl+"|"+Fc+")",kg="(?:"+yl+"(?:d|ll|m|re|s|t|ve))?",ya="(?:"+yl+"(?:D|LL|M|RE|S|T|VE))?",Hc=zc+"?",fd=""+fr+"?",Bc="(?:"+dd+"(?:"+ud,bl,wl.join("|")+")"+fd+Hc+")*",Sw="\\d*(?:1st|2nd|3rd|(?!123)\\dth)(?=\\b|A-Z_)",_l="\\d*(?:1ST|2ND|3RD|(?!123)\\dTH)(?=\\b|a-z_)",Pg=fd+Hc+Bc,Ig="(?:"+bh,bl,wl.join("|")+")"+Pg,wh="(?:"+ud+xl+"?",xl,bl,wl,Dc.join("|")+")",Sh=RegExp(yl,"g"),_h=RegExp(xl,"g"),Ps=RegExp($c+"(?="+$c+")|"+wh+Pg,"g"),Cl=RegExp(Sl+"?"+va+"+"+kg+"(?="+ks,Sl,"$".join("|")+")",ww+"+"+ya+"(?="+ks,Sl+Tg,"$".join("|")+")",Sl+"?"+Tg+"+"+kg,Sl+"+"+ya,_l,Sw,cd,Ig.join("|"),"g"),Ag=RegExp(""+dd+mi+Wi+fr+""),Mg=/a-zA-Z|A-Z{2}a-z|0-9a-zA-Z|a-zA-Z0-9|^a-zA-Z0-9 /,El="Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout",Nl=-1,an={};andr=anOn=anSr=ankn=anfn=ansn=anTr=anga=an_s=!0,anNe=anye=anjt=anZ=anvn=anoe=anxe=ange=anhe=anwe=anCe=anYe=anHt=anFt=anTe=!1;var nn={};nnNe=nnye=nnjt=nnvn=nnZ=nnoe=nndr=nnOn=nnSr=nnkn=nnfn=nnhe=nnwe=nnCe=nnYe=nnHt=nnFt=nnrt=nnsn=nnTr=nnga=nn_s=!0,nnxe=nnge=nnTe=!1;var hn={À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"},yn={"&":"&","<":"<",">":">",'"':""","'":"'"},Is={"&":"&","<":"<",">":">",""":'"',"'":"'"},hr={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},rr=parseFloat,or=parseInt,ln=typeof rs=="object"&&rs&&rs.Object===Object&&rs,Rl=typeof self=="object"&&self&&self.Object===Object&&self,Un=ln||Rl||Function("return this")(),Ch=t&&!t.nodeType&&t,Br=Ch&&!0&&e&&!e.nodeType&&e,Fo=Br&&Br.exports===Ch,Eh=Fo&&ln.process,vo=function(){try{var ee=Br&&Br.require&&Br.require("util").types;return ee||Eh&&Eh.binding&&Eh.binding("util")}catch{}}(),jg=vo&&vo.isArrayBuffer,Lg=vo&&vo.isDate,gi=vo&&vo.isMap,Ui=vo&&vo.isRegExp,As=vo&&vo.isSet,xa=vo&&vo.isTypedArray;function ro(ee,le,se){switch(se.length){case 0:return ee.call(le);case 1:return ee.call(le,se0);case 2:return ee.call(le,se0,se1);case 3:return ee.call(le,se0,se1,se2)}return ee.apply(le,se)}function vi(ee,le,se,Ae){for(var Ke=-1,ft=ee==null?0:ee.length;++Ke<ft;){var Gt=eeKe;le(Ae,Gt,se(Gt),ee)}return Ae}function yo(ee,le){for(var se=-1,Ae=ee==null?0:ee.length;++se<Ae&&le(eese,se,ee)!==!1;);return ee}function Og(ee,le){for(var se=ee==null?0:ee.length;se--&&le(eese,se,ee)!==!1;);return ee}function Nh(ee,le){for(var se=-1,Ae=ee==null?0:ee.length;++se<Ae;)if(!le(eese,se,ee))return!1;return!0}function xo(ee,le){for(var se=-1,Ae=ee==null?0:ee.length,Ke=0,ft=;++se<Ae;){var Gt=eese;le(Gt,se,ee)&&(ftKe++=Gt)}return ft}function Ms(ee,le){var se=ee==null?0:ee.length;return!!se&&wa(ee,le,0)>-1}function Rh(ee,le,se){for(var Ae=-1,Ke=ee==null?0:ee.length;++Ae<Ke;)if(se(le,eeAe))return!0;return!1}function cn(ee,le){for(var se=-1,Ae=ee==null?0:ee.length,Ke=Array(Ae);++se<Ae;)Kese=le(eese,se,ee);return Ke}function js(ee,le){for(var se=-1,Ae=le.length,Ke=ee.length;++se<Ae;)eeKe+se=lese;return ee}function qi(ee,le,se,Ae){var Ke=-1,ft=ee==null?0:ee.length;for(Ae&&ft&&(se=ee++Ke);++Ke<ft;)se=le(se,eeKe,Ke,ee);return se}function Ls(ee,le,se,Ae){var Ke=ee==null?0:ee.length;for(Ae&&Ke&&(se=ee--Ke);Ke--;)se=le(se,eeKe,Ke,ee);return se}function ba(ee,le){for(var se=-1,Ae=ee==null?0:ee.length;++se<Ae;)if(le(eese,se,ee))return!0;return!1}var Dg=pd("length");function Th(ee){return ee.split("")}function kh(ee){return ee.match(sd)||}function hd(ee,le,se){var Ae;return se(ee,function(Ke,ft,Gt){if(le(Ke,ft,Gt))return Ae=ft,!1}),Ae}function Wc(ee,le,se,Ae){for(var Ke=ee.length,ft=se+(Ae?1:-1);Ae?ft--:++ft<Ke;)if(le(eeft,ft,ee))return ft;return-1}function wa(ee,le,se){return le===le?$e(ee,le,se):Wc(ee,Ph,se)}function Fg(ee,le,se,Ae){for(var Ke=se-1,ft=ee.length;++Ke<ft;)if(Ae(eeKe,le))return Ke;return-1}function Ph(ee){return ee!==ee}function Ih(ee,le){var se=ee==null?0:ee.length;return se?Os(ee,le)/se:K}function pd(ee){return function(le){return le==null?n:leee}}function Ah(ee){return function(le){return ee==null?n:eele}}function Mh(ee,le,se,Ae,Ke){return Ke(ee,function(ft,Gt,St){se=Ae?(Ae=!1,ft):le(se,ft,Gt,St)}),se}function $g(ee,le){var se=ee.length;for(ee.sort(le);se--;)eese=eese.value;return ee}function Os(ee,le){for(var se,Ae=-1,Ke=ee.length;++Ae<Ke;){var ft=le(eeAe);ft!==n&&(se=se===n?ft:se+ft)}return se}function Tl(ee,le){for(var se=-1,Ae=Array(ee);++se<ee;)Aese=le(se);return Ae}function ir(ee,le){return cn(le,function(se){returnse,eese})}function jh(ee){return ee&&ee.slice(0,wt(ee)+1).replace(fi,"")}function qt(ee){return function(le){return ee(le)}}function md(ee,le){return cn(le,function(se){return eese})}function Sa(ee,le){return ee.has(le)}function kl(ee,le){for(var se=-1,Ae=ee.length;++se<Ae&&wa(le,eese,0)>-1;);return se}function zg(ee,le){for(var se=ee.length;se--&&wa(le,eese,0)>-1;);return se}function _w(ee,le){for(var se=ee.length,Ae=0;se--;)eese===le&&++Ae;return Ae}var Lh=Ah(hn),A=Ah(yn);function D(ee){return"\\"+hree}function V(ee,le){return ee==null?n:eele}function Y(ee){return Ag.test(ee)}function te(ee){return Mg.test(ee)}function ie(ee){for(var le,se=;!(le=ee.next()).done;)se.push(le.value);return se}function ue(ee){var le=-1,se=Array(ee.size);return ee.forEach(function(Ae,Ke){se++le=Ke,Ae}),se}function Se(ee,le){return function(se){return ee(le(se))}}function Ee(ee,le){for(var se=-1,Ae=ee.length,Ke=0,ft=;++se<Ae;){var Gt=eese;(Gt===le||Gt===d)&&(eese=d,ftKe++=se)}return ft}function je(ee){var le=-1,se=Array(ee.size);return ee.forEach(function(Ae){se++le=Ae}),se}function De(ee){var le=-1,se=Array(ee.size);return ee.forEach(function(Ae){se++le=Ae,Ae}),se}function $e(ee,le,se){for(var Ae=se-1,Ke=ee.length;++Ae<Ke;)if(eeAe===le)return Ae;return-1}function st(ee,le,se){for(var Ae=se+1;Ae--;)if(eeAe===le)return Ae;return Ae}function It(ee){return Y(ee)?qe(ee):Dg(ee)}function lt(ee){return Y(ee)?pn(ee):Th(ee)}function wt(ee){for(var le=ee.length;le--&&zr.test(ee.charAt(le)););return le}var Qe=Ah(Is);function qe(ee){for(var le=Ps.lastIndex=0;Ps.test(ee);)++le;return le}function pn(ee){return ee.match(Ps)||}function Pn(ee){return ee.match(Cl)||}var sr=function ee(le){le=le==null?Un:Bt.defaults(Un.Object(),le,Bt.pick(Un,El));var se=le.Array,Ae=le.Date,Ke=le.Error,ft=le.Function,Gt=le.Math,St=le.Object,oo=le.RegExp,yi=le.String,un=le.TypeError,_r=se.prototype,_a=ft.prototype,qn=St.prototype,Uc=le"__core-js_shared__",Hg=_a.toString,rn=qn.hasOwnProperty,kZ=0,TA=function(){var u=/^.+$/.exec(Uc&&Uc.keys&&Uc.keys.IE_PROTO||"");return u?"Symbol(src)_1."+u:""}(),Bg=qn.toString,PZ=Hg.call(St),IZ=Un._,AZ=oo("^"+Hg.call(rn).replace($r,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\)/g,"$1.*?")+"$"),Wg=Fo?le.Buffer:n,Pl=le.Symbol,Ug=le.Uint8Array,kA=Wg?Wg.allocUnsafe:n,qg=Se(St.getPrototypeOf,St),PA=St.create,IA=qn.propertyIsEnumerable,Gg=_r.splice,AA=Pl?Pl.isConcatSpreadable:n,Oh=Pl?Pl.iterator:n,qc=Pl?Pl.toStringTag:n,Vg=function(){try{var u=Xc(St,"defineProperty");return u({},"",{}),u}catch{}}(),MZ=le.clearTimeout!==Un.clearTimeout&&le.clearTimeout,jZ=Ae&&Ae.now!==Un.Date.now&&Ae.now,LZ=le.setTimeout!==Un.setTimeout&&le.setTimeout,Yg=Gt.ceil,Kg=Gt.floor,Cw=St.getOwnPropertySymbols,OZ=Wg?Wg.isBuffer:n,MA=le.isFinite,DZ=_r.join,FZ=Se(St.keys,St),pr=Gt.max,Wr=Gt.min,$Z=Ae.now,zZ=le.parseInt,jA=Gt.random,HZ=_r.reverse,Ew=Xc(le,"DataView"),Dh=Xc(le,"Map"),Nw=Xc(le,"Promise"),gd=Xc(le,"Set"),Fh=Xc(le,"WeakMap"),$h=Xc(St,"create"),Xg=Fh&&new Fh,vd={},BZ=Zc(Ew),WZ=Zc(Dh),UZ=Zc(Nw),qZ=Zc(gd),GZ=Zc(Fh),Zg=Pl?Pl.prototype:n,zh=Zg?Zg.valueOf:n,LA=Zg?Zg.toString:n;function F(u){if(Gn(u)&&!dt(u)&&!(u instanceof $t)){if(u instanceof xi)return u;if(rn.call(u,"__wrapped__"))return OM(u)}return new xi(u)}var yd=function(){function u(){}return function(m){if(!Dn(m))return{};if(PA)return PA(m);u.prototype=m;var S=new u;return u.prototype=n,S}}();function Qg(){}function xi(u,m){this.__wrapped__=u,this.__actions__=,this.__chain__=!!m,this.__index__=0,this.__values__=n}F.templateSettings={escape:rd,evaluate:gh,interpolate:od,variable:"",imports:{_:F}},F.prototype=Qg.prototype,F.prototype.constructor=F,xi.prototype=yd(Qg.prototype),xi.prototype.constructor=xi;function $t(u){this.__wrapped__=u,this.__actions__=,this.__dir__=1,this.__filtered__=!1,this.__iteratees__=,this.__takeCount__=Q,this.__views__=}function VZ(){var u=new $t(this.__wrapped__);return u.__actions__=bo(this.__actions__),u.__dir__=this.__dir__,u.__filtered__=this.__filtered__,u.__iteratees__=bo(this.__iteratees__),u.__takeCount__=this.__takeCount__,u.__views__=bo(this.__views__),u}function YZ(){if(this.__filtered__){var u=new $t(this);u.__dir__=-1,u.__filtered__=!0}else u=this.clone(),u.__dir__*=-1;return u}function KZ(){var u=this.__wrapped__.value(),m=this.__dir__,S=dt(u),T=m<0,L=S?u.length:0,U=aJ(0,L,this.__views__),X=U.start,J=U.end,ne=J-X,me=T?J:X-1,ve=this.__iteratees__,be=ve.length,Oe=0,We=Wr(ne,this.__takeCount__);if(!S||!T&&L==ne&&We==ne)return iM(u,this.__actions__);var et=;e:for(;ne--&&Oe<We;){me+=m;for(var _t=-1,tt=ume;++_t<be;){var Lt=ve_t,Wt=Lt.iteratee,Ho=Lt.type,ao=Wt(tt);if(Ho==B)tt=ao;else if(!ao){if(Ho==M)continue e;break e}}etOe++=tt}return et}$t.prototype=yd(Qg.prototype),$t.prototype.constructor=$t;function Gc(u){var m=-1,S=u==null?0:u.length;for(this.clear();++m<S;){var T=um;this.set(T0,T1)}}function XZ(){this.__data__=$h?$h(null):{},this.size=0}function ZZ(u){var m=this.has(u)&&delete this.__data__u;return this.size-=m?1:0,m}function QZ(u){var m=this.__data__;if($h){var S=mu;return S===l?n:S}return rn.call(m,u)?mu:n}function JZ(u){var m=this.__data__;return $h?mu!==n:rn.call(m,u)}function eQ(u,m){var S=this.__data__;return this.size+=this.has(u)?0:1,Su=$h&&m===n?l:m,this}Gc.prototype.clear=XZ,Gc.prototype.delete=ZZ,Gc.prototype.get=QZ,Gc.prototype.has=JZ,Gc.prototype.set=eQ;function Ca(u){var m=-1,S=u==null?0:u.length;for(this.clear();++m<S;){var T=um;this.set(T0,T1)}}function tQ(){this.__data__=,this.size=0}function nQ(u){var m=this.__data__,S=Jg(m,u);if(S<0)return!1;var T=m.length-1;return S==T?m.pop():Gg.call(m,S,1),--this.size,!0}function rQ(u){var m=this.__data__,S=Jg(m,u);return S<0?n:mS1}function oQ(u){return Jg(this.__data__,u)>-1}function iQ(u,m){var S=this.__data__,T=Jg(S,u);return T<0?(++this.size,S.push(u,m)):ST1=m,this}Ca.prototype.clear=tQ,Ca.prototype.delete=nQ,Ca.prototype.get=rQ,Ca.prototype.has=oQ,Ca.prototype.set=iQ;function Ea(u){var m=-1,S=u==null?0:u.length;for(this.clear();++m<S;){var T=um;this.set(T0,T1)}}function sQ(){this.size=0,this.__data__={hash:new Gc,map:new(Dh||Ca),string:new Gc}}function aQ(u){var m=dv(this,u).delete(u);return this.size-=m?1:0,m}function lQ(u){return dv(this,u).get(u)}function cQ(u){return dv(this,u).has(u)}function uQ(u,m){var S=dv(this,u),T=S.size;return S.set(u,m),this.size+=S.size==T?0:1,this}Ea.prototype.clear=sQ,Ea.prototype.delete=aQ,Ea.prototype.get=lQ,Ea.prototype.has=cQ,Ea.prototype.set=uQ;function Vc(u){var m=-1,S=u==null?0:u.length;for(this.__data__=new Ea;++m<S;)this.add(um)}function dQ(u){return this.__data__.set(u,l),this}function fQ(u){return this.__data__.has(u)}Vc.prototype.add=Vc.prototype.push=dQ,Vc.prototype.has=fQ;function Gi(u){var m=this.__data__=new Ca(u);this.size=m.size}function hQ(){this.__data__=new Ca,this.size=0}function pQ(u){var m=this.__data__,S=m.delete(u);return this.size=m.size,S}function mQ(u){return this.__data__.get(u)}function gQ(u){return this.__data__.has(u)}function vQ(u,m){var S=this.__data__;if(S instanceof Ca){var T=S.__data__;if(!Dh||T.length<o-1)return T.push(u,m),this.size=++S.size,this;S=this.__data__=new Ea(T)}return S.set(u,m),this.size=S.size,this}Gi.prototype.clear=hQ,Gi.prototype.delete=pQ,Gi.prototype.get=mQ,Gi.prototype.has=gQ,Gi.prototype.set=vQ;function OA(u,m){var S=dt(u),T=!S&&Qc(u),L=!S&&!T&&Ll(u),U=!S&&!T&&!L&&Sd(u),X=S||T||L||U,J=X?Tl(u.length,yi):,ne=J.length;for(var me in u)(m||rn.call(u,me))&&!(X&&(me=="length"||L&&(me=="offset"||me=="parent")||U&&(me=="buffer"||me=="byteLength"||me=="byteOffset")||ka(me,ne)))&&J.push(me);return J}function DA(u){var m=u.length;return m?uDw(0,m-1):n}function yQ(u,m){return fv(bo(u),Yc(m,0,u.length))}function xQ(u){return fv(bo(u))}function Rw(u,m,S){(S!==n&&!Vi(um,S)||S===n&&!(m in u))&&Na(u,m,S)}function Hh(u,m,S){var T=um;(!(rn.call(u,m)&&Vi(T,S))||S===n&&!(m in u))&&Na(u,m,S)}function Jg(u,m){for(var S=u.length;S--;)if(Vi(uS0,m))return S;return-1}function bQ(u,m,S,T){return Il(u,function(L,U,X){m(T,L,S(L),X)}),T}function FA(u,m){return u&&Fs(m,Cr(m),u)}function wQ(u,m){return u&&Fs(m,So(m),u)}function Na(u,m,S){m=="__proto__"&&Vg?Vg(u,m,{configurable:!0,enumerable:!0,value:S,writable:!0}):um=S}function Tw(u,m){for(var S=-1,T=m.length,L=se(T),U=u==null;++S<T;)LS=U?n:l1(u,mS);return L}function Yc(u,m,S){return u===u&&(S!==n&&(u=u<=S?u:S),m!==n&&(u=u>=m?u:m)),u}function bi(u,m,S,T,L,U){var X,J=m&f,ne=m&p,me=m&g;if(S&&(X=L?S(u,T,L,U):S(u)),X!==n)return X;if(!Dn(u))return u;var ve=dt(u);if(ve){if(X=cJ(u),!J)return bo(u,X)}else{var be=Ur(u),Oe=be==ge||be==pe;if(Ll(u))return lM(u,J);if(be==Ce||be==Ne||Oe&&!L){if(X=ne||Oe?{}:RM(u),!J)return ne?QQ(u,wQ(X,u)):ZQ(u,FA(X,u))}else{if(!nnbe)return L?u:{};X=uJ(u,be,J)}}U||(U=new Gi);var We=U.get(u);if(We)return We;U.set(u,X),nj(u)?u.forEach(function(tt){X.add(bi(tt,m,S,tt,u,U))}):ej(u)&&u.forEach(function(tt,Lt){X.set(Lt,bi(tt,m,S,Lt,u,U))});var et=me?ne?Yw:Vw:ne?So:Cr,_t=ve?n:et(u);return yo(_t||u,function(tt,Lt){_t&&(Lt=tt,tt=uLt),Hh(X,Lt,bi(tt,m,S,Lt,u,U))}),X}function SQ(u){var m=Cr(u);return function(S){return $A(S,u,m)}}function $A(u,m,S){var T=S.length;if(u==null)return!T;for(u=St(u);T--;){var L=ST,U=mL,X=uL;if(X===n&&!(L in u)||!U(X))return!1}return!0}function zA(u,m,S){if(typeof u!="function")throw new un(s);return Yh(function(){u.apply(n,S)},m)}function Bh(u,m,S,T){var L=-1,U=Ms,X=!0,J=u.length,ne=,me=m.length;if(!J)return ne;S&&(m=cn(m,qt(S))),T?(U=Rh,X=!1):m.length>=o&&(U=Sa,X=!1,m=new Vc(m));e:for(;++L<J;){var ve=uL,be=S==null?ve:S(ve);if(ve=T||ve!==0?ve:0,X&&be===be){for(var Oe=me;Oe--;)if(mOe===be)continue e;ne.push(ve)}else U(m,be,T)||ne.push(ve)}return ne}var Il=hM(Ds),HA=hM(Pw,!0);function _Q(u,m){var S=!0;return Il(u,function(T,L,U){return S=!!m(T,L,U),S}),S}function ev(u,m,S){for(var T=-1,L=u.length;++T<L;){var U=uT,X=m(U);if(X!=null&&(J===n?X===X&&!zo(X):S(X,J)))var J=X,ne=U}return ne}function CQ(u,m,S,T){var L=u.length;for(S=pt(S),S<0&&(S=-S>L?0:L+S),T=T===n||T>L?L:pt(T),T<0&&(T+=L),T=S>T?0:oj(T);S<T;)uS++=m;return u}function BA(u,m){var S=;return Il(u,function(T,L,U){m(T,L,U)&&S.push(T)}),S}function Pr(u,m,S,T,L){var U=-1,X=u.length;for(S||(S=fJ),L||(L=);++U<X;){var J=uU;m>0&&S(J)?m>1?Pr(J,m-1,S,T,L):js(L,J):T||(LL.length=J)}return L}var kw=pM(),WA=pM(!0);function Ds(u,m){return u&&kw(u,m,Cr)}function Pw(u,m){return u&&WA(u,m,Cr)}function tv(u,m){return xo(m,function(S){return Pa(uS)})}function Kc(u,m){m=Ml(m,u);for(var S=0,T=m.length;u!=null&&S<T;)u=u$s(mS++);return S&&S==T?u:n}function UA(u,m,S){var T=m(u);return dt(u)?T:js(T,S(u))}function io(u){return u==null?u===n?Ue:Ie:qc&&qc in St(u)?sJ(u):xJ(u)}function Iw(u,m){return u>m}function EQ(u,m){return u!=null&&rn.call(u,m)}function NQ(u,m){return u!=null&&m in St(u)}function RQ(u,m,S){return u>=Wr(m,S)&&u<pr(m,S)}function Aw(u,m,S){for(var T=S?Rh:Ms,L=u0.length,U=u.length,X=U,J=se(U),ne=1/0,me=;X--;){var ve=uX;X&&m&&(ve=cn(ve,qt(m))),ne=Wr(ve.length,ne),JX=!S&&(m||L>=120&&ve.length>=120)?new Vc(X&&ve):n}ve=u0;var be=-1,Oe=J0;e:for(;++be<L&&me.length<ne;){var We=vebe,et=m?m(We):We;if(We=S||We!==0?We:0,!(Oe?Sa(Oe,et):T(me,et,S))){for(X=U;--X;){var _t=JX;if(!(_t?Sa(_t,et):T(uX,et,S)))continue e}Oe&&Oe.push(et),me.push(We)}}return me}function TQ(u,m,S,T){return Ds(u,function(L,U,X){m(T,S(L),U,X)}),T}function Wh(u,m,S){m=Ml(m,u),u=IM(u,m);var T=u==null?u:u$s(Si(m));return T==null?n:ro(T,u,S)}function qA(u){return Gn(u)&&io(u)==Ne}function kQ(u){return Gn(u)&&io(u)==jt}function PQ(u){return Gn(u)&&io(u)==oe}function Uh(u,m,S,T,L){return u===m?!0:u==null||m==null||!Gn(u)&&!Gn(m)?u!==u&&m!==m:IQ(u,m,S,T,Uh,L)}function IQ(u,m,S,T,L,U){var X=dt(u),J=dt(m),ne=X?ye:Ur(u),me=J?ye:Ur(m);ne=ne==Ne?Ce:ne,me=me==Ne?Ce:me;var ve=ne==Ce,be=me==Ce,Oe=ne==me;if(Oe&&Ll(u)){if(!Ll(m))return!1;X=!0,ve=!1}if(Oe&&!ve)return U||(U=new Gi),X||Sd(u)?CM(u,m,S,T,L,U):oJ(u,m,ne,S,T,L,U);if(!(S&v)){var We=ve&&rn.call(u,"__wrapped__"),et=be&&rn.call(m,"__wrapped__");if(We||et){var _t=We?u.value():u,tt=et?m.value():m;return U||(U=new Gi),L(_t,tt,S,T,U)}}return Oe?(U||(U=new Gi),iJ(u,m,S,T,L,U)):!1}function AQ(u){return Gn(u)&&Ur(u)==he}function Mw(u,m,S,T){var L=S.length,U=L,X=!T;if(u==null)return!U;for(u=St(u);L--;){var J=SL;if(X&&J2?J1!==uJ0:!(J0in u))return!1}for(;++L<U;){J=SL;var ne=J0,me=une,ve=J1;if(X&&J2){if(me===n&&!(ne in u))return!1}else{var be=new Gi;if(T)var Oe=T(me,ve,ne,u,m,be);if(!(Oe===n?Uh(ve,me,v|b,T,be):Oe))return!1}}return!0}function GA(u){if(!Dn(u)||pJ(u))return!1;var m=Pa(u)?AZ:pi;return m.test(Zc(u))}function MQ(u){return Gn(u)&&io(u)==Ye}function jQ(u){return Gn(u)&&Ur(u)==Ht}function LQ(u){return Gn(u)&&yv(u.length)&&!!anio(u)}function VA(u){return typeof u=="function"?u:u==null?_o:typeof u=="object"?dt(u)?XA(u0,u1):KA(u):mj(u)}function jw(u){if(!Vh(u))return FZ(u);var m=;for(var S in St(u))rn.call(u,S)&&S!="constructor"&&m.push(S);return m}function OQ(u){if(!Dn(u))return yJ(u);var m=Vh(u),S=;for(var T in u)T=="constructor"&&(m||!rn.call(u,T))||S.push(T);return S}function Lw(u,m){return u<m}function YA(u,m){var S=-1,T=wo(u)?se(u.length):;return Il(u,function(L,U,X){T++S=m(L,U,X)}),T}function KA(u){var m=Xw(u);return m.length==1&&m02?kM(m00,m01):function(S){return S===u||Mw(S,u,m)}}function XA(u,m){return Qw(u)&&TM(m)?kM($s(u),m):function(S){var T=l1(S,u);return T===n&&T===m?c1(S,u):Uh(m,T,v|b)}}function nv(u,m,S,T,L){u!==m&&kw(m,function(U,X){if(L||(L=new Gi),Dn(U))DQ(u,m,X,S,nv,T,L);else{var J=T?T(e1(u,X),U,X+"",u,m,L):n;J===n&&(J=U),Rw(u,X,J)}},So)}function DQ(u,m,S,T,L,U,X){var J=e1(u,S),ne=e1(m,S),me=X.get(ne);if(me){Rw(u,S,me);return}var ve=U?U(J,ne,S+"",u,m,X):n,be=ve===n;if(be){var Oe=dt(ne),We=!Oe&&Ll(ne),et=!Oe&&!We&&Sd(ne);ve=ne,Oe||We||et?dt(J)?ve=J:Xn(J)?ve=bo(J):We?(be=!1,ve=lM(ne,!0)):et?(be=!1,ve=cM(ne,!0)):ve=:Kh(ne)||Qc(ne)?(ve=J,Qc(J)?ve=ij(J):(!Dn(J)||Pa(J))&&(ve=RM(ne))):be=!1}be&&(X.set(ne,ve),L(ve,ne,T,U,X),X.delete(ne)),Rw(u,S,ve)}function ZA(u,m){var S=u.length;if(S)return m+=m<0?S:0,ka(m,S)?um:n}function QA(u,m,S){m.length?m=cn(m,function(U){return dt(U)?function(X){return Kc(X,U.length===1?U0:U)}:U}):m=_o;var T=-1;m=cn(m,qt(Je()));var L=YA(u,function(U,X,J){var ne=cn(m,function(me){return me(U)});return{criteria:ne,index:++T,value:U}});return $g(L,function(U,X){return XQ(U,X,S)})}function FQ(u,m){return JA(u,m,function(S,T){return c1(u,T)})}function JA(u,m,S){for(var T=-1,L=m.length,U={};++T<L;){var X=mT,J=Kc(u,X);S(J,X)&&qh(U,Ml(X,u),J)}return U}function $Q(u){return function(m){return Kc(m,u)}}function Ow(u,m,S,T){var L=T?Fg:wa,U=-1,X=m.length,J=u;for(u===m&&(m=bo(m)),S&&(J=cn(u,qt(S)));++U<X;)for(var ne=0,me=mU,ve=S?S(me):me;(ne=L(J,ve,ne,T))>-1;)J!==u&&Gg.call(J,ne,1),Gg.call(u,ne,1);return u}function eM(u,m){for(var S=u?m.length:0,T=S-1;S--;){var L=mS;if(S==T||L!==U){var U=L;ka(L)?Gg.call(u,L,1):zw(u,L)}}return u}function Dw(u,m){return u+Kg(jA()*(m-u+1))}function zQ(u,m,S,T){for(var L=-1,U=pr(Yg((m-u)/(S||1)),0),X=se(U);U--;)XT?U:++L=u,u+=S;return X}function Fw(u,m){var S="";if(!u||m<1||m>G)return S;do m%2&&(S+=u),m=Kg(m/2),m&&(u+=u);while(m);return S}function Rt(u,m){return t1(PM(u,m,_o),u+"")}function HQ(u){return DA(_d(u))}function BQ(u,m){var S=_d(u);return fv(S,Yc(m,0,S.length))}function qh(u,m,S,T){if(!Dn(u))return u;m=Ml(m,u);for(var L=-1,U=m.length,X=U-1,J=u;J!=null&&++L<U;){var ne=$s(mL),me=S;if(ne==="__proto__"||ne==="constructor"||ne==="prototype")return u;if(L!=X){var ve=Jne;me=T?T(ve,ne,J):n,me===n&&(me=Dn(ve)?ve:ka(mL+1)?:{})}Hh(J,ne,me),J=Jne}return u}var tM=Xg?function(u,m){return Xg.set(u,m),u}:_o,WQ=Vg?function(u,m){return Vg(u,"toString",{configurable:!0,enumerable:!1,value:d1(m),writable:!0})}:_o;function UQ(u){return fv(_d(u))}function wi(u,m,S){var T=-1,L=u.length;m<0&&(m=-m>L?0:L+m),S=S>L?L:S,S<0&&(S+=L),L=m>S?0:S-m>>>0,m>>>=0;for(var U=se(L);++T<L;)UT=uT+m;return U}function qQ(u,m){var S;return Il(u,function(T,L,U){return S=m(T,L,U),!S}),!!S}function rv(u,m,S){var T=0,L=u==null?T:u.length;if(typeof m=="number"&&m===m&&L<=ae){for(;T<L;){var U=T+L>>>1,X=uU;X!==null&&!zo(X)&&(S?X<=m:X<m)?T=U+1:L=U}return L}return $w(u,m,_o,S)}function $w(u,m,S,T){var L=0,U=u==null?0:u.length;if(U===0)return 0;m=S(m);for(var X=m!==m,J=m===null,ne=zo(m),me=m===n;L<U;){var ve=Kg((L+U)/2),be=S(uve),Oe=be!==n,We=be===null,et=be===be,_t=zo(be);if(X)var tt=T||et;else me?tt=et&&(T||Oe):J?tt=et&&Oe&&(T||!We):ne?tt=et&&Oe&&!We&&(T||!_t):We||_t?tt=!1:tt=T?be<=m:be<m;tt?L=ve+1:U=ve}return Wr(U,re)}function nM(u,m){for(var S=-1,T=u.length,L=0,U=;++S<T;){var X=uS,J=m?m(X):X;if(!S||!Vi(J,ne)){var ne=J;UL++=X===0?0:X}}return U}function rM(u){return typeof u=="number"?u:zo(u)?K:+u}function $o(u){if(typeof u=="string")return u;if(dt(u))return cn(u,$o)+"";if(zo(u))return LA?LA.call(u):"";var m=u+"";return m=="0"&&1/u==-W?"-0":m}function Al(u,m,S){var T=-1,L=Ms,U=u.length,X=!0,J=,ne=J;if(S)X=!1,L=Rh;else if(U>=o){var me=m?null:nJ(u);if(me)return je(me);X=!1,L=Sa,ne=new Vc}else ne=m?:J;e:for(;++T<U;){var ve=uT,be=m?m(ve):ve;if(ve=S||ve!==0?ve:0,X&&be===be){for(var Oe=ne.length;Oe--;)if(neOe===be)continue e;m&&ne.push(be),J.push(ve)}else L(ne,be,S)||(ne!==J&&ne.push(be),J.push(ve))}return J}function zw(u,m){return m=Ml(m,u),u=IM(u,m),u==null||delete u$s(Si(m))}function oM(u,m,S,T){return qh(u,m,S(Kc(u,m)),T)}function ov(u,m,S,T){for(var L=u.length,U=T?L:-1;(T?U--:++U<L)&&m(uU,U,u););return S?wi(u,T?0:U,T?U+1:L):wi(u,T?U+1:0,T?L:U)}function iM(u,m){var S=u;return S instanceof $t&&(S=S.value()),qi(m,function(T,L){return L.func.apply(L.thisArg,js(T,L.args))},S)}function Hw(u,m,S){var T=u.length;if(T<2)return T?Al(u0):;for(var L=-1,U=se(T);++L<T;)for(var X=uL,J=-1;++J<T;)J!=L&&(UL=Bh(UL||X,uJ,m,S));return Al(Pr(U,1),m,S)}function sM(u,m,S){for(var T=-1,L=u.length,U=m.length,X={};++T<L;){var J=T<U?mT:n;S(X,uT,J)}return X}function Bw(u){return Xn(u)?u:}function Ww(u){return typeof u=="function"?u:_o}function Ml(u,m){return dt(u)?u:Qw(u,m)?u:LM(Jt(u))}var GQ=Rt;function jl(u,m,S){var T=u.length;return S=S===n?T:S,!m&&S>=T?u:wi(u,m,S)}var aM=MZ||function(u){return Un.clearTimeout(u)};function lM(u,m){if(m)return u.slice();var S=u.length,T=kA?kA(S):new u.constructor(S);return u.copy(T),T}function Uw(u){var m=new u.constructor(u.byteLength);return new Ug(m).set(new Ug(u)),m}function VQ(u,m){var S=m?Uw(u.buffer):u.buffer;return new u.constructor(S,u.byteOffset,u.byteLength)}function YQ(u){var m=new u.constructor(u.source,Ic.exec(u));return m.lastIndex=u.lastIndex,m}function KQ(u){return zh?St(zh.call(u)):{}}function cM(u,m){var S=m?Uw(u.buffer):u.buffer;return new u.constructor(S,u.byteOffset,u.length)}function uM(u,m){if(u!==m){var S=u!==n,T=u===null,L=u===u,U=zo(u),X=m!==n,J=m===null,ne=m===m,me=zo(m);if(!J&&!me&&!U&&u>m||U&&X&&ne&&!J&&!me||T&&X&&ne||!S&&ne||!L)return 1;if(!T&&!U&&!me&&u<m||me&&S&&L&&!T&&!U||J&&S&&L||!X&&L||!ne)return-1}return 0}function XQ(u,m,S){for(var T=-1,L=u.criteria,U=m.criteria,X=L.length,J=S.length;++T<X;){var ne=uM(LT,UT);if(ne){if(T>=J)return ne;var me=ST;return ne*(me=="desc"?-1:1)}}return u.index-m.index}function dM(u,m,S,T){for(var L=-1,U=u.length,X=S.length,J=-1,ne=m.length,me=pr(U-X,0),ve=se(ne+me),be=!T;++J<ne;)veJ=mJ;for(;++L<X;)(be||L<U)&&(veSL=uL);for(;me--;)veJ++=uL++;return ve}function fM(u,m,S,T){for(var L=-1,U=u.length,X=-1,J=S.length,ne=-1,me=m.length,ve=pr(U-J,0),be=se(ve+me),Oe=!T;++L<ve;)beL=uL;for(var We=L;++ne<me;)beWe+ne=mne;for(;++X<J;)(Oe||L<U)&&(beWe+SX=uL++);return be}function bo(u,m){var S=-1,T=u.length;for(m||(m=se(T));++S<T;)mS=uS;return m}function Fs(u,m,S,T){var L=!S;S||(S={});for(var U=-1,X=m.length;++U<X;){var J=mU,ne=T?T(SJ,uJ,J,S,u):n;ne===n&&(ne=uJ),L?Na(S,J,ne):Hh(S,J,ne)}return S}function ZQ(u,m){return Fs(u,Zw(u),m)}function QQ(u,m){return Fs(u,EM(u),m)}function iv(u,m){return function(S,T){var L=dt(S)?vi:bQ,U=m?m():{};return L(S,u,Je(T,2),U)}}function xd(u){return Rt(function(m,S){var T=-1,L=S.length,U=L>1?SL-1:n,X=L>2?S2:n;for(U=u.length>3&&typeof U=="function"?(L--,U):n,X&&so(S0,S1,X)&&(U=L<3?n:U,L=1),m=St(m);++T<L;){var J=ST;J&&u(m,J,T,U)}return m})}function hM(u,m){return function(S,T){if(S==null)return S;if(!wo(S))return u(S,T);for(var L=S.length,U=m?L:-1,X=St(S);(m?U--:++U<L)&&T(XU,U,X)!==!1;);return S}}function pM(u){return function(m,S,T){for(var L=-1,U=St(m),X=T(m),J=X.length;J--;){var ne=Xu?J:++L;if(S(Une,ne,U)===!1)break}return m}}function JQ(u,m,S){var T=m&_,L=Gh(u);function U(){var X=this&&this!==Un&&this instanceof U?L:u;return X.apply(T?S:this,arguments)}return U}function mM(u){return function(m){m=Jt(m);var S=Y(m)?lt(m):n,T=S?S0:m.charAt(0),L=S?jl(S,1).join(""):m.slice(1);return Tu()+L}}function bd(u){return function(m){return qi(hj(fj(m).replace(Sh,"")),u,"")}}function Gh(u){return function(){var m=arguments;switch(m.length){case 0:return new u;case 1:return new u(m0);case 2:return new u(m0,m1);case 3:return new u(m0,m1,m2);case 4:return new u(m0,m1,m2,m3);case 5:return new u(m0,m1,m2,m3,m4);case 6:return new u(m0,m1,m2,m3,m4,m5);case 7:return new u(m0,m1,m2,m3,m4,m5,m6)}var S=yd(u.prototype),T=u.apply(S,m);return Dn(T)?T:S}}function eJ(u,m,S){var T=Gh(u);function L(){for(var U=arguments.length,X=se(U),J=U,ne=wd(L);J--;)XJ=argumentsJ;var me=U<3&&X0!==ne&&XU-1!==ne?:Ee(X,ne);if(U-=me.length,U<S)return bM(u,m,sv,L.placeholder,n,X,me,n,n,S-U);var ve=this&&this!==Un&&this instanceof L?T:u;return ro(ve,this,X)}return L}function gM(u){return function(m,S,T){var L=St(m);if(!wo(m)){var U=Je(S,3);m=Cr(m),S=function(J){return U(LJ,J,L)}}var X=u(m,S,T);return X>-1?LU?mX:X:n}}function vM(u){return Ta(function(m){var S=m.length,T=S,L=xi.prototype.thru;for(u&&m.reverse();T--;){var U=mT;if(typeof U!="function")throw new un(s);if(L&&!X&&uv(U)=="wrapper")var X=new xi(,!0)}for(T=X?T:S;++T<S;){U=mT;var J=uv(U),ne=J=="wrapper"?Kw(U):n;ne&&Jw(ne0)&&ne1==(N|C|R|k)&&!ne4.length&&ne9==1?X=Xuv(ne0).apply(X,ne3):X=U.length==1&&Jw(U)?XJ():X.thru(U)}return function(){var me=arguments,ve=me0;if(X&&me.length==1&&dt(ve))return X.plant(ve).value();for(var be=0,Oe=S?mbe.apply(this,me):ve;++be<S;)Oe=mbe.call(this,Oe);return Oe}})}function sv(u,m,S,T,L,U,X,J,ne,me){var ve=m&N,be=m&_,Oe=m&x,We=m&(C|E),et=m&I,_t=Oe?n:Gh(u);function tt(){for(var Lt=arguments.length,Wt=se(Lt),Ho=Lt;Ho--;)WtHo=argumentsHo;if(We)var ao=wd(tt),Bo=_w(Wt,ao);if(T&&(Wt=dM(Wt,T,L,We)),U&&(Wt=fM(Wt,U,X,We)),Lt-=Bo,We&&Lt<me){var Zn=Ee(Wt,ao);return bM(u,m,sv,tt.placeholder,S,Wt,Zn,J,ne,me-Lt)}var Yi=be?S:this,Aa=Oe?Yiu:u;return Lt=Wt.length,J?Wt=bJ(Wt,J):et&&Lt>1&&Wt.reverse(),ve&&ne<Lt&&(Wt.length=ne),this&&this!==Un&&this instanceof tt&&(Aa=_t||Gh(Aa)),Aa.apply(Yi,Wt)}return tt}function yM(u,m){return function(S,T){return TQ(S,u,m(T),{})}}function av(u,m){return function(S,T){var L;if(S===n&&T===n)return m;if(S!==n&&(L=S),T!==n){if(L===n)return T;typeof S=="string"||typeof T=="string"?(S=$o(S),T=$o(T)):(S=rM(S),T=rM(T)),L=u(S,T)}return L}}function qw(u){return Ta(function(m){return m=cn(m,qt(Je())),Rt(function(S){var T=this;return u(m,function(L){return ro(L,T,S)})})})}function lv(u,m){m=m===n?" ":$o(m);var S=m.length;if(S<2)return S?Fw(m,u):m;var T=Fw(m,Yg(u/It(m)));return Y(m)?jl(lt(T),0,u).join(""):T.slice(0,u)}function tJ(u,m,S,T){var L=m&_,U=Gh(u);function X(){for(var J=-1,ne=arguments.length,me=-1,ve=T.length,be=se(ve+ne),Oe=this&&this!==Un&&this instanceof X?U:u;++me<ve;)beme=Tme;for(;ne--;)beme++=arguments++J;return ro(Oe,L?S:this,be)}return X}function xM(u){return function(m,S,T){return T&&typeof T!="number"&&so(m,S,T)&&(S=T=n),m=Ia(m),S===n?(S=m,m=0):S=Ia(S),T=T===n?m<S?1:-1:Ia(T),zQ(m,S,T,u)}}function cv(u){return function(m,S){return typeof m=="string"&&typeof S=="string"||(m=_i(m),S=_i(S)),u(m,S)}}function bM(u,m,S,T,L,U,X,J,ne,me){var ve=m&C,be=ve?X:n,Oe=ve?n:X,We=ve?U:n,et=ve?n:U;m|=ve?R:P,m&=~(ve?P:R),m&w||(m&=-4);var _t=u,m,L,We,be,et,Oe,J,ne,me,tt=S.apply(n,_t);return Jw(u)&&AM(tt,_t),tt.placeholder=T,MM(tt,u,m)}function Gw(u){var m=Gtu;return function(S,T){if(S=_i(S),T=T==null?0:Wr(pt(T),292),T&&MA(S)){var L=(Jt(S)+"e").split("e"),U=m(L0+"e"+(+L1+T));return L=(Jt(U)+"e").split("e"),+(L0+"e"+(+L1-T))}return m(S)}}var nJ=gd&&1/je(new gd(,-0))1==W?function(u){return new gd(u)}:p1;function wM(u){return function(m){var S=Ur(m);return S==he?ue(m):S==Ht?De(m):ir(m,u(m))}}function Ra(u,m,S,T,L,U,X,J){var ne=m&x;if(!ne&&typeof u!="function")throw new un(s);var me=T?T.length:0;if(me||(m&=-97,T=L=n),X=X===n?X:pr(pt(X),0),J=J===n?J:pt(J),me-=L?L.length:0,m&P){var ve=T,be=L;T=L=n}var Oe=ne?n:Kw(u),We=u,m,S,T,L,ve,be,U,X,J;if(Oe&&vJ(We,Oe),u=We0,m=We1,S=We2,T=We3,L=We4,J=We9=We9===n?ne?0:u.length:pr(We9-me,0),!J&&m&(C|E)&&(m&=-25),!m||m==_)var et=JQ(u,m,S);else m==C||m==E?et=eJ(u,m,J):(m==R||m==(_|R))&&!L.length?et=tJ(u,m,S,T):et=sv.apply(n,We);var _t=Oe?tM:AM;return MM(_t(et,We),u,m)}function SM(u,m,S,T){return u===n||Vi(u,qnS)&&!rn.call(T,S)?m:u}function _M(u,m,S,T,L,U){return Dn(u)&&Dn(m)&&(U.set(m,u),nv(u,m,n,_M,U),U.delete(m)),u}function rJ(u){return Kh(u)?n:u}function CM(u,m,S,T,L,U){var X=S&v,J=u.length,ne=m.length;if(J!=ne&&!(X&&ne>J))return!1;var me=U.get(u),ve=U.get(m);if(me&&ve)return me==m&&ve==u;var be=-1,Oe=!0,We=S&b?new Vc:n;for(U.set(u,m),U.set(m,u);++be<J;){var et=ube,_t=mbe;if(T)var tt=X?T(_t,et,be,m,u,U):T(et,_t,be,u,m,U);if(tt!==n){if(tt)continue;Oe=!1;break}if(We){if(!ba(m,function(Lt,Wt){if(!Sa(We,Wt)&&(et===Lt||L(et,Lt,S,T,U)))return We.push(Wt)})){Oe=!1;break}}else if(!(et===_t||L(et,_t,S,T,U))){Oe=!1;break}}return U.delete(u),U.delete(m),Oe}function oJ(u,m,S,T,L,U,X){switch(S){case vn:if(u.byteLength!=m.byteLength||u.byteOffset!=m.byteOffset)return!1;u=u.buffer,m=m.buffer;case jt:return!(u.byteLength!=m.byteLength||!U(new Ug(u),new Ug(m)));case Z:case oe:case we:return Vi(+u,+m);case xe:return u.name==m.name&&u.message==m.message;case Ye:case Ft:return u==m+"";case he:var J=ue;case Ht:var ne=T&v;if(J||(J=je),u.size!=m.size&&!ne)return!1;var me=X.get(u);if(me)return me==m;T|=b,X.set(u,m);var ve=CM(J(u),J(m),T,L,U,X);return X.delete(u),ve;case rt:if(zh)return zh.call(u)==zh.call(m)}return!1}function iJ(u,m,S,T,L,U){var X=S&v,J=Vw(u),ne=J.length,me=Vw(m),ve=me.length;if(ne!=ve&&!X)return!1;for(var be=ne;be--;){var Oe=Jbe;if(!(X?Oe in m:rn.call(m,Oe)))return!1}var We=U.get(u),et=U.get(m);if(We&&et)return We==m&&et==u;var _t=!0;U.set(u,m),U.set(m,u);for(var tt=X;++be<ne;){Oe=Jbe;var Lt=uOe,Wt=mOe;if(T)var Ho=X?T(Wt,Lt,Oe,m,u,U):T(Lt,Wt,Oe,u,m,U);if(!(Ho===n?Lt===Wt||L(Lt,Wt,S,T,U):Ho)){_t=!1;break}tt||(tt=Oe=="constructor")}if(_t&&!tt){var ao=u.constructor,Bo=m.constructor;ao!=Bo&&"constructor"in u&&"constructor"in m&&!(typeof ao=="function"&&ao instanceof ao&&typeof Bo=="function"&&Bo instanceof Bo)&&(_t=!1)}return U.delete(u),U.delete(m),_t}function Ta(u){return t1(PM(u,n,$M),u+"")}function Vw(u){return UA(u,Cr,Zw)}function Yw(u){return UA(u,So,EM)}var Kw=Xg?function(u){return Xg.get(u)}:p1;function uv(u){for(var m=u.name+"",S=vdm,T=rn.call(vd,m)?S.length:0;T--;){var L=ST,U=L.func;if(U==null||U==u)return L.name}return m}function wd(u){var m=rn.call(F,"placeholder")?F:u;return m.placeholder}function Je(){var u=F.iteratee||f1;return u=u===f1?VA:u,arguments.length?u(arguments0,arguments1):u}function dv(u,m){var S=u.__data__;return hJ(m)?Stypeof m=="string"?"string":"hash":S.map}function Xw(u){for(var m=Cr(u),S=m.length;S--;){var T=mS,L=uT;mS=T,L,TM(L)}return m}function Xc(u,m){var S=V(u,m);return GA(S)?S:n}function sJ(u){var m=rn.call(u,qc),S=uqc;try{uqc=n;var T=!0}catch{}var L=Bg.call(u);return T&&(m?uqc=S:delete uqc),L}var Zw=Cw?function(u){return u==null?:(u=St(u),xo(Cw(u),function(m){return IA.call(u,m)}))}:m1,EM=Cw?function(u){for(var m=;u;)js(m,Zw(u)),u=qg(u);return m}:m1,Ur=io;(Ew&&Ur(new Ew(new ArrayBuffer(1)))!=vn||Dh&&Ur(new Dh)!=he||Nw&&Ur(Nw.resolve())!=Me||gd&&Ur(new gd)!=Ht||Fh&&Ur(new Fh)!=Te)&&(Ur=function(u){var m=io(u),S=m==Ce?u.constructor:n,T=S?Zc(S):"";if(T)switch(T){case BZ:return vn;case WZ:return he;case UZ:return Me;case qZ:return Ht;case GZ:return Te}return m});function aJ(u,m,S){for(var T=-1,L=S.length;++T<L;){var U=ST,X=U.size;switch(U.type){case"drop":u+=X;break;case"dropRight":m-=X;break;case"take":m=Wr(m,u+X);break;case"takeRight":u=pr(u,m-X);break}}return{start:u,end:m}}function lJ(u){var m=u.match(Rs);return m?m1.split(id):}function NM(u,m,S){m=Ml(m,u);for(var T=-1,L=m.length,U=!1;++T<L;){var X=$s(mT);if(!(U=u!=null&&S(u,X)))break;u=uX}return U||++T!=L?U:(L=u==null?0:u.length,!!L&&yv(L)&&ka(X,L)&&(dt(u)||Qc(u)))}function cJ(u){var m=u.length,S=new u.constructor(m);return m&&typeof u0=="string"&&rn.call(u,"index")&&(S.index=u.index,S.input=u.input),S}function RM(u){return typeof u.constructor=="function"&&!Vh(u)?yd(qg(u)):{}}function uJ(u,m,S){var T=u.constructor;switch(m){case jt:return Uw(u);case Z:case oe:return new T(+u);case vn:return VQ(u,S);case dr:case On:case Sr:case kn:case fn:case sn:case Tr:case ga:case _s:return cM(u,S);case he:return new T;case we:case Ft:return new T(u);case Ye:return YQ(u);case Ht:return new T;case rt:return KQ(u)}}function dJ(u,m){var S=m.length;if(!S)return u;var T=S-1;return mT=(S>1?"& ":"")+mT,m=m.join(S>2?", ":" "),u.replace(Ns,`{ +/* wrapped with `+m+` */ +`)}function fJ(u){return dt(u)||Qc(u)||!!(AA&&u&&uAA)}function ka(u,m){var S=typeof u;return m=m??G,!!m&&(S=="number"||S!="symbol"&&Bn.test(u))&&u>-1&&u%1==0&&u<m}function so(u,m,S){if(!Dn(S))return!1;var T=typeof m;return(T=="number"?wo(S)&&ka(m,S.length):T=="string"&&m in S)?Vi(Sm,u):!1}function Qw(u,m){if(dt(u))return!1;var S=typeof u;return S=="number"||S=="symbol"||S=="boolean"||u==null||zo(u)?!0:Hi.test(u)||!vh.test(u)||m!=null&&u in St(m)}function hJ(u){var m=typeof u;return m=="string"||m=="number"||m=="symbol"||m=="boolean"?u!=="__proto__":u===null}function Jw(u){var m=uv(u),S=Fm;if(typeof S!="function"||!(m in $t.prototype))return!1;if(u===S)return!0;var T=Kw(S);return!!T&&u===T0}function pJ(u){return!!TA&&TA in u}var mJ=Uc?Pa:g1;function Vh(u){var m=u&&u.constructor,S=typeof m=="function"&&m.prototype||qn;return u===S}function TM(u){return u===u&&!Dn(u)}function kM(u,m){return function(S){return S==null?!1:Su===m&&(m!==n||u in St(S))}}function gJ(u){var m=gv(u,function(T){return S.size===c&&S.clear(),T}),S=m.cache;return m}function vJ(u,m){var S=u1,T=m1,L=S|T,U=L<(_|x|N),X=T==N&&S==C||T==N&&S==k&&u7.length<=m8||T==(N|k)&&m7.length<=m8&&S==C;if(!(U||X))return u;T&_&&(u2=m2,L|=S&_?0:w);var J=m3;if(J){var ne=u3;u3=ne?dM(ne,J,m4):J,u4=ne?Ee(u3,d):m4}return J=m5,J&&(ne=u5,u5=ne?fM(ne,J,m6):J,u6=ne?Ee(u5,d):m6),J=m7,J&&(u7=J),T&N&&(u8=u8==null?m8:Wr(u8,m8)),u9==null&&(u9=m9),u0=m0,u1=L,u}function yJ(u){var m=;if(u!=null)for(var S in St(u))m.push(S);return m}function xJ(u){return Bg.call(u)}function PM(u,m,S){return m=pr(m===n?u.length-1:m,0),function(){for(var T=arguments,L=-1,U=pr(T.length-m,0),X=se(U);++L<U;)XL=Tm+L;L=-1;for(var J=se(m+1);++L<m;)JL=TL;return Jm=S(X),ro(u,this,J)}}function IM(u,m){return m.length<2?u:Kc(u,wi(m,0,-1))}function bJ(u,m){for(var S=u.length,T=Wr(m.length,S),L=bo(u);T--;){var U=mT;uT=ka(U,S)?LU:n}return u}function e1(u,m){if(!(m==="constructor"&&typeof um=="function")&&m!="__proto__")return um}var AM=jM(tM),Yh=LZ||function(u,m){return Un.setTimeout(u,m)},t1=jM(WQ);function MM(u,m,S){var T=m+"";return t1(u,dJ(T,wJ(lJ(T),S)))}function jM(u){var m=0,S=0;return function(){var T=$Z(),L=q-(T-S);if(S=T,L>0){if(++m>=H)return arguments0}else m=0;return u.apply(n,arguments)}}function fv(u,m){var S=-1,T=u.length,L=T-1;for(m=m===n?T:m;++S<m;){var U=Dw(S,L),X=uU;uU=uS,uS=X}return u.length=m,u}var LM=gJ(function(u){var m=;return u.charCodeAt(0)===46&&m.push(""),u.replace(nr,function(S,T,L,U){m.push(L?U.replace(xh,"$1"):T||S)}),m});function $s(u){if(typeof u=="string"||zo(u))return u;var m=u+"";return m=="0"&&1/u==-W?"-0":m}function Zc(u){if(u!=null){try{return Hg.call(u)}catch{}try{return u+""}catch{}}return""}function wJ(u,m){return yo(de,function(S){var T="_."+S0;m&S1&&!Ms(u,T)&&u.push(T)}),u.sort()}function OM(u){if(u instanceof $t)return u.clone();var m=new xi(u.__wrapped__,u.__chain__);return m.__actions__=bo(u.__actions__),m.__index__=u.__index__,m.__values__=u.__values__,m}function SJ(u,m,S){(S?so(u,m,S):m===n)?m=1:m=pr(pt(m),0);var T=u==null?0:u.length;if(!T||m<1)return;for(var L=0,U=0,X=se(Yg(T/m));L<T;)XU++=wi(u,L,L+=m);return X}function _J(u){for(var m=-1,S=u==null?0:u.length,T=0,L=;++m<S;){var U=um;U&&(LT++=U)}return L}function CJ(){var u=arguments.length;if(!u)return;for(var m=se(u-1),S=arguments0,T=u;T--;)mT-1=argumentsT;return js(dt(S)?bo(S):S,Pr(m,1))}var EJ=Rt(function(u,m){return Xn(u)?Bh(u,Pr(m,1,Xn,!0)):}),NJ=Rt(function(u,m){var S=Si(m);return Xn(S)&&(S=n),Xn(u)?Bh(u,Pr(m,1,Xn,!0),Je(S,2)):}),RJ=Rt(function(u,m){var S=Si(m);return Xn(S)&&(S=n),Xn(u)?Bh(u,Pr(m,1,Xn,!0),n,S):});function TJ(u,m,S){var T=u==null?0:u.length;return T?(m=S||m===n?1:pt(m),wi(u,m<0?0:m,T)):}function kJ(u,m,S){var T=u==null?0:u.length;return T?(m=S||m===n?1:pt(m),m=T-m,wi(u,0,m<0?0:m)):}function PJ(u,m){return u&&u.length?ov(u,Je(m,3),!0,!0):}function IJ(u,m){return u&&u.length?ov(u,Je(m,3),!0):}function AJ(u,m,S,T){var L=u==null?0:u.length;return L?(S&&typeof S!="number"&&so(u,m,S)&&(S=0,T=L),CQ(u,m,S,T)):}function DM(u,m,S){var T=u==null?0:u.length;if(!T)return-1;var L=S==null?0:pt(S);return L<0&&(L=pr(T+L,0)),Wc(u,Je(m,3),L)}function FM(u,m,S){var T=u==null?0:u.length;if(!T)return-1;var L=T-1;return S!==n&&(L=pt(S),L=S<0?pr(T+L,0):Wr(L,T-1)),Wc(u,Je(m,3),L,!0)}function $M(u){var m=u==null?0:u.length;return m?Pr(u,1):}function MJ(u){var m=u==null?0:u.length;return m?Pr(u,W):}function jJ(u,m){var S=u==null?0:u.length;return S?(m=m===n?1:pt(m),Pr(u,m)):}function LJ(u){for(var m=-1,S=u==null?0:u.length,T={};++m<S;){var L=um;TL0=L1}return T}function zM(u){return u&&u.length?u0:n}function OJ(u,m,S){var T=u==null?0:u.length;if(!T)return-1;var L=S==null?0:pt(S);return L<0&&(L=pr(T+L,0)),wa(u,m,L)}function DJ(u){var m=u==null?0:u.length;return m?wi(u,0,-1):}var FJ=Rt(function(u){var m=cn(u,Bw);return m.length&&m0===u0?Aw(m):}),$J=Rt(function(u){var m=Si(u),S=cn(u,Bw);return m===Si(S)?m=n:S.pop(),S.length&&S0===u0?Aw(S,Je(m,2)):}),zJ=Rt(function(u){var m=Si(u),S=cn(u,Bw);return m=typeof m=="function"?m:n,m&&S.pop(),S.length&&S0===u0?Aw(S,n,m):});function HJ(u,m){return u==null?"":DZ.call(u,m)}function Si(u){var m=u==null?0:u.length;return m?um-1:n}function BJ(u,m,S){var T=u==null?0:u.length;if(!T)return-1;var L=T;return S!==n&&(L=pt(S),L=L<0?pr(T+L,0):Wr(L,T-1)),m===m?st(u,m,L):Wc(u,Ph,L,!0)}function WJ(u,m){return u&&u.length?ZA(u,pt(m)):n}var UJ=Rt(HM);function HM(u,m){return u&&u.length&&m&&m.length?Ow(u,m):u}function qJ(u,m,S){return u&&u.length&&m&&m.length?Ow(u,m,Je(S,2)):u}function GJ(u,m,S){return u&&u.length&&m&&m.length?Ow(u,m,n,S):u}var VJ=Ta(function(u,m){var S=u==null?0:u.length,T=Tw(u,m);return eM(u,cn(m,function(L){return ka(L,S)?+L:L}).sort(uM)),T});function YJ(u,m){var S=;if(!(u&&u.length))return S;var T=-1,L=,U=u.length;for(m=Je(m,3);++T<U;){var X=uT;m(X,T,u)&&(S.push(X),L.push(T))}return eM(u,L),S}function n1(u){return u==null?u:HZ.call(u)}function KJ(u,m,S){var T=u==null?0:u.length;return T?(S&&typeof S!="number"&&so(u,m,S)?(m=0,S=T):(m=m==null?0:pt(m),S=S===n?T:pt(S)),wi(u,m,S)):}function XJ(u,m){return rv(u,m)}function ZJ(u,m,S){return $w(u,m,Je(S,2))}function QJ(u,m){var S=u==null?0:u.length;if(S){var T=rv(u,m);if(T<S&&Vi(uT,m))return T}return-1}function JJ(u,m){return rv(u,m,!0)}function eee(u,m,S){return $w(u,m,Je(S,2),!0)}function tee(u,m){var S=u==null?0:u.length;if(S){var T=rv(u,m,!0)-1;if(Vi(uT,m))return T}return-1}function nee(u){return u&&u.length?nM(u):}function ree(u,m){return u&&u.length?nM(u,Je(m,2)):}function oee(u){var m=u==null?0:u.length;return m?wi(u,1,m):}function iee(u,m,S){return u&&u.length?(m=S||m===n?1:pt(m),wi(u,0,m<0?0:m)):}function see(u,m,S){var T=u==null?0:u.length;return T?(m=S||m===n?1:pt(m),m=T-m,wi(u,m<0?0:m,T)):}function aee(u,m){return u&&u.length?ov(u,Je(m,3),!1,!0):}function lee(u,m){return u&&u.length?ov(u,Je(m,3)):}var cee=Rt(function(u){return Al(Pr(u,1,Xn,!0))}),uee=Rt(function(u){var m=Si(u);return Xn(m)&&(m=n),Al(Pr(u,1,Xn,!0),Je(m,2))}),dee=Rt(function(u){var m=Si(u);return m=typeof m=="function"?m:n,Al(Pr(u,1,Xn,!0),n,m)});function fee(u){return u&&u.length?Al(u):}function hee(u,m){return u&&u.length?Al(u,Je(m,2)):}function pee(u,m){return m=typeof m=="function"?m:n,u&&u.length?Al(u,n,m):}function r1(u){if(!(u&&u.length))return;var m=0;return u=xo(u,function(S){if(Xn(S))return m=pr(S.length,m),!0}),Tl(m,function(S){return cn(u,pd(S))})}function BM(u,m){if(!(u&&u.length))return;var S=r1(u);return m==null?S:cn(S,function(T){return ro(m,n,T)})}var mee=Rt(function(u,m){return Xn(u)?Bh(u,m):}),gee=Rt(function(u){return Hw(xo(u,Xn))}),vee=Rt(function(u){var m=Si(u);return Xn(m)&&(m=n),Hw(xo(u,Xn),Je(m,2))}),yee=Rt(function(u){var m=Si(u);return m=typeof m=="function"?m:n,Hw(xo(u,Xn),n,m)}),xee=Rt(r1);function bee(u,m){return sM(u||,m||,Hh)}function wee(u,m){return sM(u||,m||,qh)}var See=Rt(function(u){var m=u.length,S=m>1?um-1:n;return S=typeof S=="function"?(u.pop(),S):n,BM(u,S)});function WM(u){var m=F(u);return m.__chain__=!0,m}function _ee(u,m){return m(u),u}function hv(u,m){return m(u)}var Cee=Ta(function(u){var m=u.length,S=m?u0:0,T=this.__wrapped__,L=function(U){return Tw(U,u)};return m>1||this.__actions__.length||!(T instanceof $t)||!ka(S)?this.thru(L):(T=T.slice(S,+S+(m?1:0)),T.__actions__.push({func:hv,args:L,thisArg:n}),new xi(T,this.__chain__).thru(function(U){return m&&!U.length&&U.push(n),U}))});function Eee(){return WM(this)}function Nee(){return new xi(this.value(),this.__chain__)}function Ree(){this.__values__===n&&(this.__values__=rj(this.value()));var u=this.__index__>=this.__values__.length,m=u?n:this.__values__this.__index__++;return{done:u,value:m}}function Tee(){return this}function kee(u){for(var m,S=this;S instanceof Qg;){var T=OM(S);T.__index__=0,T.__values__=n,m?L.__wrapped__=T:m=T;var L=T;S=S.__wrapped__}return L.__wrapped__=u,m}function Pee(){var u=this.__wrapped__;if(u instanceof $t){var m=u;return this.__actions__.length&&(m=new $t(this)),m=m.reverse(),m.__actions__.push({func:hv,args:n1,thisArg:n}),new xi(m,this.__chain__)}return this.thru(n1)}function Iee(){return iM(this.__wrapped__,this.__actions__)}var Aee=iv(function(u,m,S){rn.call(u,S)?++uS:Na(u,S,1)});function Mee(u,m,S){var T=dt(u)?Nh:_Q;return S&&so(u,m,S)&&(m=n),T(u,Je(m,3))}function jee(u,m){var S=dt(u)?xo:BA;return S(u,Je(m,3))}var Lee=gM(DM),Oee=gM(FM);function Dee(u,m){return Pr(pv(u,m),1)}function Fee(u,m){return Pr(pv(u,m),W)}function $ee(u,m,S){return S=S===n?1:pt(S),Pr(pv(u,m),S)}function UM(u,m){var S=dt(u)?yo:Il;return S(u,Je(m,3))}function qM(u,m){var S=dt(u)?Og:HA;return S(u,Je(m,3))}var zee=iv(function(u,m,S){rn.call(u,S)?uS.push(m):Na(u,S,m)});function Hee(u,m,S,T){u=wo(u)?u:_d(u),S=S&&!T?pt(S):0;var L=u.length;return S<0&&(S=pr(L+S,0)),xv(u)?S<=L&&u.indexOf(m,S)>-1:!!L&&wa(u,m,S)>-1}var Bee=Rt(function(u,m,S){var T=-1,L=typeof m=="function",U=wo(u)?se(u.length):;return Il(u,function(X){U++T=L?ro(m,X,S):Wh(X,m,S)}),U}),Wee=iv(function(u,m,S){Na(u,S,m)});function pv(u,m){var S=dt(u)?cn:YA;return S(u,Je(m,3))}function Uee(u,m,S,T){return u==null?:(dt(m)||(m=m==null?:m),S=T?n:S,dt(S)||(S=S==null?:S),QA(u,m,S))}var qee=iv(function(u,m,S){uS?0:1.push(m)},function(){return,});function Gee(u,m,S){var T=dt(u)?qi:Mh,L=arguments.length<3;return T(u,Je(m,4),S,L,Il)}function Vee(u,m,S){var T=dt(u)?Ls:Mh,L=arguments.length<3;return T(u,Je(m,4),S,L,HA)}function Yee(u,m){var S=dt(u)?xo:BA;return S(u,vv(Je(m,3)))}function Kee(u){var m=dt(u)?DA:HQ;return m(u)}function Xee(u,m,S){(S?so(u,m,S):m===n)?m=1:m=pt(m);var T=dt(u)?yQ:BQ;return T(u,m)}function Zee(u){var m=dt(u)?xQ:UQ;return m(u)}function Qee(u){if(u==null)return 0;if(wo(u))return xv(u)?It(u):u.length;var m=Ur(u);return m==he||m==Ht?u.size:jw(u).length}function Jee(u,m,S){var T=dt(u)?ba:qQ;return S&&so(u,m,S)&&(m=n),T(u,Je(m,3))}var ete=Rt(function(u,m){if(u==null)return;var S=m.length;return S>1&&so(u,m0,m1)?m=:S>2&&so(m0,m1,m2)&&(m=m0),QA(u,Pr(m,1),)}),mv=jZ||function(){return Un.Date.now()};function tte(u,m){if(typeof m!="function")throw new un(s);return u=pt(u),function(){if(--u<1)return m.apply(this,arguments)}}function GM(u,m,S){return m=S?n:m,m=u&&m==null?u.length:m,Ra(u,N,n,n,n,n,m)}function VM(u,m){var S;if(typeof m!="function")throw new un(s);return u=pt(u),function(){return--u>0&&(S=m.apply(this,arguments)),u<=1&&(m=n),S}}var o1=Rt(function(u,m,S){var T=_;if(S.length){var L=Ee(S,wd(o1));T|=R}return Ra(u,T,m,S,L)}),YM=Rt(function(u,m,S){var T=_|x;if(S.length){var L=Ee(S,wd(YM));T|=R}return Ra(m,T,u,S,L)});function KM(u,m,S){m=S?n:m;var T=Ra(u,C,n,n,n,n,n,m);return T.placeholder=KM.placeholder,T}function XM(u,m,S){m=S?n:m;var T=Ra(u,E,n,n,n,n,n,m);return T.placeholder=XM.placeholder,T}function ZM(u,m,S){var T,L,U,X,J,ne,me=0,ve=!1,be=!1,Oe=!0;if(typeof u!="function")throw new un(s);m=_i(m)||0,Dn(S)&&(ve=!!S.leading,be="maxWait"in S,U=be?pr(_i(S.maxWait)||0,m):U,Oe="trailing"in S?!!S.trailing:Oe);function We(Zn){var Yi=T,Aa=L;return T=L=n,me=Zn,X=u.apply(Aa,Yi),X}function et(Zn){return me=Zn,J=Yh(Lt,m),ve?We(Zn):X}function _t(Zn){var Yi=Zn-ne,Aa=Zn-me,gj=m-Yi;return be?Wr(gj,U-Aa):gj}function tt(Zn){var Yi=Zn-ne,Aa=Zn-me;return ne===n||Yi>=m||Yi<0||be&&Aa>=U}function Lt(){var Zn=mv();if(tt(Zn))return Wt(Zn);J=Yh(Lt,_t(Zn))}function Wt(Zn){return J=n,Oe&&T?We(Zn):(T=L=n,X)}function Ho(){J!==n&&aM(J),me=0,T=ne=L=J=n}function ao(){return J===n?X:Wt(mv())}function Bo(){var Zn=mv(),Yi=tt(Zn);if(T=arguments,L=this,ne=Zn,Yi){if(J===n)return et(ne);if(be)return aM(J),J=Yh(Lt,m),We(ne)}return J===n&&(J=Yh(Lt,m)),X}return Bo.cancel=Ho,Bo.flush=ao,Bo}var nte=Rt(function(u,m){return zA(u,1,m)}),rte=Rt(function(u,m,S){return zA(u,_i(m)||0,S)});function ote(u){return Ra(u,I)}function gv(u,m){if(typeof u!="function"||m!=null&&typeof m!="function")throw new un(s);var S=function(){var T=arguments,L=m?m.apply(this,T):T0,U=S.cache;if(U.has(L))return U.get(L);var X=u.apply(this,T);return S.cache=U.set(L,X)||U,X};return S.cache=new(gv.Cache||Ea),S}gv.Cache=Ea;function vv(u){if(typeof u!="function")throw new un(s);return function(){var m=arguments;switch(m.length){case 0:return!u.call(this);case 1:return!u.call(this,m0);case 2:return!u.call(this,m0,m1);case 3:return!u.call(this,m0,m1,m2)}return!u.apply(this,m)}}function ite(u){return VM(2,u)}var ste=GQ(function(u,m){m=m.length==1&&dt(m0)?cn(m0,qt(Je())):cn(Pr(m,1),qt(Je()));var S=m.length;return Rt(function(T){for(var L=-1,U=Wr(T.length,S);++L<U;)TL=mL.call(this,TL);return ro(u,this,T)})}),i1=Rt(function(u,m){var S=Ee(m,wd(i1));return Ra(u,R,n,m,S)}),QM=Rt(function(u,m){var S=Ee(m,wd(QM));return Ra(u,P,n,m,S)}),ate=Ta(function(u,m){return Ra(u,k,n,n,n,m)});function lte(u,m){if(typeof u!="function")throw new un(s);return m=m===n?m:pt(m),Rt(u,m)}function cte(u,m){if(typeof u!="function")throw new un(s);return m=m==null?0:pr(pt(m),0),Rt(function(S){var T=Sm,L=jl(S,0,m);return T&&js(L,T),ro(u,this,L)})}function ute(u,m,S){var T=!0,L=!0;if(typeof u!="function")throw new un(s);return Dn(S)&&(T="leading"in S?!!S.leading:T,L="trailing"in S?!!S.trailing:L),ZM(u,m,{leading:T,maxWait:m,trailing:L})}function dte(u){return GM(u,1)}function fte(u,m){return i1(Ww(m),u)}function hte(){if(!arguments.length)return;var u=arguments0;return dt(u)?u:u}function pte(u){return bi(u,g)}function mte(u,m){return m=typeof m=="function"?m:n,bi(u,g,m)}function gte(u){return bi(u,f|g)}function vte(u,m){return m=typeof m=="function"?m:n,bi(u,f|g,m)}function yte(u,m){return m==null||$A(u,m,Cr(m))}function Vi(u,m){return u===m||u!==u&&m!==m}var xte=cv(Iw),bte=cv(function(u,m){return u>=m}),Qc=qA(function(){return arguments}())?qA:function(u){return Gn(u)&&rn.call(u,"callee")&&!IA.call(u,"callee")},dt=se.isArray,wte=jg?qt(jg):kQ;function wo(u){return u!=null&&yv(u.length)&&!Pa(u)}function Xn(u){return Gn(u)&&wo(u)}function Ste(u){return u===!0||u===!1||Gn(u)&&io(u)==Z}var Ll=OZ||g1,_te=Lg?qt(Lg):PQ;function Cte(u){return Gn(u)&&u.nodeType===1&&!Kh(u)}function Ete(u){if(u==null)return!0;if(wo(u)&&(dt(u)||typeof u=="string"||typeof u.splice=="function"||Ll(u)||Sd(u)||Qc(u)))return!u.length;var m=Ur(u);if(m==he||m==Ht)return!u.size;if(Vh(u))return!jw(u).length;for(var S in u)if(rn.call(u,S))return!1;return!0}function Nte(u,m){return Uh(u,m)}function Rte(u,m,S){S=typeof S=="function"?S:n;var T=S?S(u,m):n;return T===n?Uh(u,m,n,S):!!T}function s1(u){if(!Gn(u))return!1;var m=io(u);return m==xe||m==ce||typeof u.message=="string"&&typeof u.name=="string"&&!Kh(u)}function Tte(u){return typeof u=="number"&&MA(u)}function Pa(u){if(!Dn(u))return!1;var m=io(u);return m==ge||m==pe||m==fe||m==ze}function JM(u){return typeof u=="number"&&u==pt(u)}function yv(u){return typeof u=="number"&&u>-1&&u%1==0&&u<=G}function Dn(u){var m=typeof u;return u!=null&&(m=="object"||m=="function")}function Gn(u){return u!=null&&typeof u=="object"}var ej=gi?qt(gi):AQ;function kte(u,m){return u===m||Mw(u,m,Xw(m))}function Pte(u,m,S){return S=typeof S=="function"?S:n,Mw(u,m,Xw(m),S)}function Ite(u){return tj(u)&&u!=+u}function Ate(u){if(mJ(u))throw new Ke(i);return GA(u)}function Mte(u){return u===null}function jte(u){return u==null}function tj(u){return typeof u=="number"||Gn(u)&&io(u)==we}function Kh(u){if(!Gn(u)||io(u)!=Ce)return!1;var m=qg(u);if(m===null)return!0;var S=rn.call(m,"constructor")&&m.constructor;return typeof S=="function"&&S instanceof S&&Hg.call(S)==PZ}var a1=Ui?qt(Ui):MQ;function Lte(u){return JM(u)&&u>=-G&&u<=G}var nj=As?qt(As):jQ;function xv(u){return typeof u=="string"||!dt(u)&&Gn(u)&&io(u)==Ft}function zo(u){return typeof u=="symbol"||Gn(u)&&io(u)==rt}var Sd=xa?qt(xa):LQ;function Ote(u){return u===n}function Dte(u){return Gn(u)&&Ur(u)==Te}function Fte(u){return Gn(u)&&io(u)==bt}var $te=cv(Lw),zte=cv(function(u,m){return u<=m});function rj(u){if(!u)return;if(wo(u))return xv(u)?lt(u):bo(u);if(Oh&&uOh)return ie(uOh());var m=Ur(u),S=m==he?ue:m==Ht?je:_d;return S(u)}function Ia(u){if(!u)return u===0?u:0;if(u=_i(u),u===W||u===-W){var m=u<0?-1:1;return m*z}return u===u?u:0}function pt(u){var m=Ia(u),S=m%1;return m===m?S?m-S:m:0}function oj(u){return u?Yc(pt(u),0,Q):0}function _i(u){if(typeof u=="number")return u;if(zo(u))return K;if(Dn(u)){var m=typeof u.valueOf=="function"?u.valueOf():u;u=Dn(m)?m+"":m}if(typeof u!="string")return u===0?u:+u;u=jh(u);var S=ad.test(u);return S||Kn.test(u)?or(u.slice(2),S?2:8):Ts.test(u)?K:+u}function ij(u){return Fs(u,So(u))}function Hte(u){return u?Yc(pt(u),-G,G):u===0?u:0}function Jt(u){return u==null?"":$o(u)}var Bte=xd(function(u,m){if(Vh(m)||wo(m)){Fs(m,Cr(m),u);return}for(var S in m)rn.call(m,S)&&Hh(u,S,mS)}),sj=xd(function(u,m){Fs(m,So(m),u)}),bv=xd(function(u,m,S,T){Fs(m,So(m),u,T)}),Wte=xd(function(u,m,S,T){Fs(m,Cr(m),u,T)}),Ute=Ta(Tw);function qte(u,m){var S=yd(u);return m==null?S:FA(S,m)}var Gte=Rt(function(u,m){u=St(u);var S=-1,T=m.length,L=T>2?m2:n;for(L&&so(m0,m1,L)&&(T=1);++S<T;)for(var U=mS,X=So(U),J=-1,ne=X.length;++J<ne;){var me=XJ,ve=ume;(ve===n||Vi(ve,qnme)&&!rn.call(u,me))&&(ume=Ume)}return u}),Vte=Rt(function(u){return u.push(n,_M),ro(aj,n,u)});function Yte(u,m){return hd(u,Je(m,3),Ds)}function Kte(u,m){return hd(u,Je(m,3),Pw)}function Xte(u,m){return u==null?u:kw(u,Je(m,3),So)}function Zte(u,m){return u==null?u:WA(u,Je(m,3),So)}function Qte(u,m){return u&&Ds(u,Je(m,3))}function Jte(u,m){return u&&Pw(u,Je(m,3))}function ene(u){return u==null?:tv(u,Cr(u))}function tne(u){return u==null?:tv(u,So(u))}function l1(u,m,S){var T=u==null?n:Kc(u,m);return T===n?S:T}function nne(u,m){return u!=null&&NM(u,m,EQ)}function c1(u,m){return u!=null&&NM(u,m,NQ)}var rne=yM(function(u,m,S){m!=null&&typeof m.toString!="function"&&(m=Bg.call(m)),um=S},d1(_o)),one=yM(function(u,m,S){m!=null&&typeof m.toString!="function"&&(m=Bg.call(m)),rn.call(u,m)?um.push(S):um=S},Je),ine=Rt(Wh);function Cr(u){return wo(u)?OA(u):jw(u)}function So(u){return wo(u)?OA(u,!0):OQ(u)}function sne(u,m){var S={};return m=Je(m,3),Ds(u,function(T,L,U){Na(S,m(T,L,U),T)}),S}function ane(u,m){var S={};return m=Je(m,3),Ds(u,function(T,L,U){Na(S,L,m(T,L,U))}),S}var lne=xd(function(u,m,S){nv(u,m,S)}),aj=xd(function(u,m,S,T){nv(u,m,S,T)}),cne=Ta(function(u,m){var S={};if(u==null)return S;var T=!1;m=cn(m,function(U){return U=Ml(U,u),T||(T=U.length>1),U}),Fs(u,Yw(u),S),T&&(S=bi(S,f|p|g,rJ));for(var L=m.length;L--;)zw(S,mL);return S});function une(u,m){return lj(u,vv(Je(m)))}var dne=Ta(function(u,m){return u==null?{}:FQ(u,m)});function lj(u,m){if(u==null)return{};var S=cn(Yw(u),function(T){returnT});return m=Je(m),JA(u,S,function(T,L){return m(T,L0)})}function fne(u,m,S){m=Ml(m,u);var T=-1,L=m.length;for(L||(L=1,u=n);++T<L;){var U=u==null?n:u$s(mT);U===n&&(T=L,U=S),u=Pa(U)?U.call(u):U}return u}function hne(u,m,S){return u==null?u:qh(u,m,S)}function pne(u,m,S,T){return T=typeof T=="function"?T:n,u==null?u:qh(u,m,S,T)}var cj=wM(Cr),uj=wM(So);function mne(u,m,S){var T=dt(u),L=T||Ll(u)||Sd(u);if(m=Je(m,4),S==null){var U=u&&u.constructor;L?S=T?new U::Dn(u)?S=Pa(U)?yd(qg(u)):{}:S={}}return(L?yo:Ds)(u,function(X,J,ne){return m(S,X,J,ne)}),S}function gne(u,m){return u==null?!0:zw(u,m)}function vne(u,m,S){return u==null?u:oM(u,m,Ww(S))}function yne(u,m,S,T){return T=typeof T=="function"?T:n,u==null?u:oM(u,m,Ww(S),T)}function _d(u){return u==null?:md(u,Cr(u))}function xne(u){return u==null?:md(u,So(u))}function bne(u,m,S){return S===n&&(S=m,m=n),S!==n&&(S=_i(S),S=S===S?S:0),m!==n&&(m=_i(m),m=m===m?m:0),Yc(_i(u),m,S)}function wne(u,m,S){return m=Ia(m),S===n?(S=m,m=0):S=Ia(S),u=_i(u),RQ(u,m,S)}function Sne(u,m,S){if(S&&typeof S!="boolean"&&so(u,m,S)&&(m=S=n),S===n&&(typeof m=="boolean"?(S=m,m=n):typeof u=="boolean"&&(S=u,u=n)),u===n&&m===n?(u=0,m=1):(u=Ia(u),m===n?(m=u,u=0):m=Ia(m)),u>m){var T=u;u=m,m=T}if(S||u%1||m%1){var L=jA();return Wr(u+L*(m-u+rr("1e-"+((L+"").length-1))),m)}return Dw(u,m)}var _ne=bd(function(u,m,S){return m=m.toLowerCase(),u+(S?dj(m):m)});function dj(u){return u1(Jt(u).toLowerCase())}function fj(u){return u=Jt(u),u&&u.replace(Wn,Lh).replace(_h,"")}function Cne(u,m,S){u=Jt(u),m=$o(m);var T=u.length;S=S===n?T:Yc(pt(S),0,T);var L=S;return S-=m.length,S>=0&&u.slice(S,L)==m}function Ene(u){return u=Jt(u),u&&mh.test(u)?u.replace(fl,A):u}function Nne(u){return u=Jt(u),u&&Es.test(u)?u.replace($r,"\\$&"):u}var Rne=bd(function(u,m,S){return u+(S?"-":"")+m.toLowerCase()}),Tne=bd(function(u,m,S){return u+(S?" ":"")+m.toLowerCase()}),kne=mM("toLowerCase");function Pne(u,m,S){u=Jt(u),m=pt(m);var T=m?It(u):0;if(!m||T>=m)return u;var L=(m-T)/2;return lv(Kg(L),S)+u+lv(Yg(L),S)}function Ine(u,m,S){u=Jt(u),m=pt(m);var T=m?It(u):0;return m&&T<m?u+lv(m-T,S):u}function Ane(u,m,S){u=Jt(u),m=pt(m);var T=m?It(u):0;return m&&T<m?lv(m-T,S)+u:u}function Mne(u,m,S){return S||m==null?m=0:m&&(m=+m),zZ(Jt(u).replace(fi,""),m||0)}function jne(u,m,S){return(S?so(u,m,S):m===n)?m=1:m=pt(m),Fw(Jt(u),m)}function Lne(){var u=arguments,m=Jt(u0);return u.length<3?m:m.replace(u1,u2)}var One=bd(function(u,m,S){return u+(S?"_":"")+m.toLowerCase()});function Dne(u,m,S){return S&&typeof S!="number"&&so(u,m,S)&&(m=S=n),S=S===n?Q:S>>>0,S?(u=Jt(u),u&&(typeof m=="string"||m!=null&&!a1(m))&&(m=$o(m),!m&&Y(u))?jl(lt(u),0,S):u.split(m,S)):}var Fne=bd(function(u,m,S){return u+(S?" ":"")+u1(m)});function $ne(u,m,S){return u=Jt(u),S=S==null?0:Yc(pt(S),0,u.length),m=$o(m),u.slice(S,S+m.length)==m}function zne(u,m,S){var T=F.templateSettings;S&&so(u,m,S)&&(m=n),u=Jt(u),m=bv({},m,T,SM);var L=bv({},m.imports,T.imports,SM),U=Cr(L),X=md(L,U),J,ne,me=0,ve=m.interpolate||Bi,be="__p += '",Oe=oo((m.escape||Bi).source+"|"+ve.source+"|"+(ve===od?hi:Bi).source+"|"+(m.evaluate||Bi).source+"|$","g"),We="//# sourceURL="+(rn.call(m,"sourceURL")?(m.sourceURL+"").replace(/\s/g," "):"lodash.templateSources"+ ++Nl+"")+` +`;u.replace(Oe,function(tt,Lt,Wt,Ho,ao,Bo){return Wt||(Wt=Ho),be+=u.slice(me,Bo).replace(Hr,D),Lt&&(J=!0,be+=`' + +__e(`+Lt+`) + +'`),ao&&(ne=!0,be+=`'; +`+ao+`; +__p += '`),Wt&&(be+=`' + +((__t = (`+Wt+`)) == null ? '' : __t) + +'`),me=Bo+tt.length,tt}),be+=`'; +`;var et=rn.call(m,"variable")&&m.variable;if(!et)be=`with (obj) { +`+be+` +} +`;else if(yh.test(et))throw new Ke(a);be=(ne?be.replace(Cs,""):be).replace(zi,"$1").replace(dl,"$1;"),be="function("+(et||"obj")+`) { +`+(et?"":`obj || (obj = {}); +`)+"var __t, __p = ''"+(J?", __e = _.escape":"")+(ne?`, __j = Array.prototype.join; +function print() { __p += __j.call(arguments, '') } +`:`; +`)+be+`return __p +}`;var _t=pj(function(){return ft(U,We+"return "+be).apply(n,X)});if(_t.source=be,s1(_t))throw _t;return _t}function Hne(u){return Jt(u).toLowerCase()}function Bne(u){return Jt(u).toUpperCase()}function Wne(u,m,S){if(u=Jt(u),u&&(S||m===n))return jh(u);if(!u||!(m=$o(m)))return u;var T=lt(u),L=lt(m),U=kl(T,L),X=zg(T,L)+1;return jl(T,U,X).join("")}function Une(u,m,S){if(u=Jt(u),u&&(S||m===n))return u.slice(0,wt(u)+1);if(!u||!(m=$o(m)))return u;var T=lt(u),L=zg(T,lt(m))+1;return jl(T,0,L).join("")}function qne(u,m,S){if(u=Jt(u),u&&(S||m===n))return u.replace(fi,"");if(!u||!(m=$o(m)))return u;var T=lt(u),L=kl(T,lt(m));return jl(T,L).join("")}function Gne(u,m){var S=O,T=j;if(Dn(m)){var L="separator"in m?m.separator:L;S="length"in m?pt(m.length):S,T="omission"in m?$o(m.omission):T}u=Jt(u);var U=u.length;if(Y(u)){var X=lt(u);U=X.length}if(S>=U)return u;var J=S-It(T);if(J<1)return T;var ne=X?jl(X,0,J).join(""):u.slice(0,J);if(L===n)return ne+T;if(X&&(J+=ne.length-J),a1(L)){if(u.slice(J).search(L)){var me,ve=ne;for(L.global||(L=oo(L.source,Jt(Ic.exec(L))+"g")),L.lastIndex=0;me=L.exec(ve);)var be=me.index;ne=ne.slice(0,be===n?J:be)}}else if(u.indexOf($o(L),J)!=J){var Oe=ne.lastIndexOf(L);Oe>-1&&(ne=ne.slice(0,Oe))}return ne+T}function Vne(u){return u=Jt(u),u&&nd.test(u)?u.replace(kr,Qe):u}var Yne=bd(function(u,m,S){return u+(S?" ":"")+m.toUpperCase()}),u1=mM("toUpperCase");function hj(u,m,S){return u=Jt(u),m=S?n:m,m===n?te(u)?Pn(u):kh(u):u.match(m)||}var pj=Rt(function(u,m){try{return ro(u,n,m)}catch(S){return s1(S)?S:new Ke(S)}}),Kne=Ta(function(u,m){return yo(m,function(S){S=$s(S),Na(u,S,o1(uS,u))}),u});function Xne(u){var m=u==null?0:u.length,S=Je();return u=m?cn(u,function(T){if(typeof T1!="function")throw new un(s);returnS(T0),T1}):,Rt(function(T){for(var L=-1;++L<m;){var U=uL;if(ro(U0,this,T))return ro(U1,this,T)}})}function Zne(u){return SQ(bi(u,f))}function d1(u){return function(){return u}}function Qne(u,m){return u==null||u!==u?m:u}var Jne=vM(),ere=vM(!0);function _o(u){return u}function f1(u){return VA(typeof u=="function"?u:bi(u,f))}function tre(u){return KA(bi(u,f))}function nre(u,m){return XA(u,bi(m,f))}var rre=Rt(function(u,m){return function(S){return Wh(S,u,m)}}),ore=Rt(function(u,m){return function(S){return Wh(u,S,m)}});function h1(u,m,S){var T=Cr(m),L=tv(m,T);S==null&&!(Dn(m)&&(L.length||!T.length))&&(S=m,m=u,u=this,L=tv(m,Cr(m)));var U=!(Dn(S)&&"chain"in S)||!!S.chain,X=Pa(u);return yo(L,function(J){var ne=mJ;uJ=ne,X&&(u.prototypeJ=function(){var me=this.__chain__;if(U||me){var ve=u(this.__wrapped__),be=ve.__actions__=bo(this.__actions__);return be.push({func:ne,args:arguments,thisArg:u}),ve.__chain__=me,ve}return ne.apply(u,js(this.value(),arguments))})}),u}function ire(){return Un._===this&&(Un._=IZ),this}function p1(){}function sre(u){return u=pt(u),Rt(function(m){return ZA(m,u)})}var are=qw(cn),lre=qw(Nh),cre=qw(ba);function mj(u){return Qw(u)?pd($s(u)):$Q(u)}function ure(u){return function(m){return u==null?n:Kc(u,m)}}var dre=xM(),fre=xM(!0);function m1(){return}function g1(){return!1}function hre(){return{}}function pre(){return""}function mre(){return!0}function gre(u,m){if(u=pt(u),u<1||u>G)return;var S=Q,T=Wr(u,Q);m=Je(m),u-=Q;for(var L=Tl(T,m);++S<u;)m(S);return L}function vre(u){return dt(u)?cn(u,$s):zo(u)?u:bo(LM(Jt(u)))}function yre(u){var m=++kZ;return Jt(u)+m}var xre=av(function(u,m){return u+m},0),bre=Gw("ceil"),wre=av(function(u,m){return u/m},1),Sre=Gw("floor");function _re(u){return u&&u.length?ev(u,_o,Iw):n}function Cre(u,m){return u&&u.length?ev(u,Je(m,2),Iw):n}function Ere(u){return Ih(u,_o)}function Nre(u,m){return Ih(u,Je(m,2))}function Rre(u){return u&&u.length?ev(u,_o,Lw):n}function Tre(u,m){return u&&u.length?ev(u,Je(m,2),Lw):n}var kre=av(function(u,m){return u*m},1),Pre=Gw("round"),Ire=av(function(u,m){return u-m},0);function Are(u){return u&&u.length?Os(u,_o):0}function Mre(u,m){return u&&u.length?Os(u,Je(m,2)):0}return F.after=tte,F.ary=GM,F.assign=Bte,F.assignIn=sj,F.assignInWith=bv,F.assignWith=Wte,F.at=Ute,F.before=VM,F.bind=o1,F.bindAll=Kne,F.bindKey=YM,F.castArray=hte,F.chain=WM,F.chunk=SJ,F.compact=_J,F.concat=CJ,F.cond=Xne,F.conforms=Zne,F.constant=d1,F.countBy=Aee,F.create=qte,F.curry=KM,F.curryRight=XM,F.debounce=ZM,F.defaults=Gte,F.defaultsDeep=Vte,F.defer=nte,F.delay=rte,F.difference=EJ,F.differenceBy=NJ,F.differenceWith=RJ,F.drop=TJ,F.dropRight=kJ,F.dropRightWhile=PJ,F.dropWhile=IJ,F.fill=AJ,F.filter=jee,F.flatMap=Dee,F.flatMapDeep=Fee,F.flatMapDepth=$ee,F.flatten=$M,F.flattenDeep=MJ,F.flattenDepth=jJ,F.flip=ote,F.flow=Jne,F.flowRight=ere,F.fromPairs=LJ,F.functions=ene,F.functionsIn=tne,F.groupBy=zee,F.initial=DJ,F.intersection=FJ,F.intersectionBy=$J,F.intersectionWith=zJ,F.invert=rne,F.invertBy=one,F.invokeMap=Bee,F.iteratee=f1,F.keyBy=Wee,F.keys=Cr,F.keysIn=So,F.map=pv,F.mapKeys=sne,F.mapValues=ane,F.matches=tre,F.matchesProperty=nre,F.memoize=gv,F.merge=lne,F.mergeWith=aj,F.method=rre,F.methodOf=ore,F.mixin=h1,F.negate=vv,F.nthArg=sre,F.omit=cne,F.omitBy=une,F.once=ite,F.orderBy=Uee,F.over=are,F.overArgs=ste,F.overEvery=lre,F.overSome=cre,F.partial=i1,F.partialRight=QM,F.partition=qee,F.pick=dne,F.pickBy=lj,F.property=mj,F.propertyOf=ure,F.pull=UJ,F.pullAll=HM,F.pullAllBy=qJ,F.pullAllWith=GJ,F.pullAt=VJ,F.range=dre,F.rangeRight=fre,F.rearg=ate,F.reject=Yee,F.remove=YJ,F.rest=lte,F.reverse=n1,F.sampleSize=Xee,F.set=hne,F.setWith=pne,F.shuffle=Zee,F.slice=KJ,F.sortBy=ete,F.sortedUniq=nee,F.sortedUniqBy=ree,F.split=Dne,F.spread=cte,F.tail=oee,F.take=iee,F.takeRight=see,F.takeRightWhile=aee,F.takeWhile=lee,F.tap=_ee,F.throttle=ute,F.thru=hv,F.toArray=rj,F.toPairs=cj,F.toPairsIn=uj,F.toPath=vre,F.toPlainObject=ij,F.transform=mne,F.unary=dte,F.union=cee,F.unionBy=uee,F.unionWith=dee,F.uniq=fee,F.uniqBy=hee,F.uniqWith=pee,F.unset=gne,F.unzip=r1,F.unzipWith=BM,F.update=vne,F.updateWith=yne,F.values=_d,F.valuesIn=xne,F.without=mee,F.words=hj,F.wrap=fte,F.xor=gee,F.xorBy=vee,F.xorWith=yee,F.zip=xee,F.zipObject=bee,F.zipObjectDeep=wee,F.zipWith=See,F.entries=cj,F.entriesIn=uj,F.extend=sj,F.extendWith=bv,h1(F,F),F.add=xre,F.attempt=pj,F.camelCase=_ne,F.capitalize=dj,F.ceil=bre,F.clamp=bne,F.clone=pte,F.cloneDeep=gte,F.cloneDeepWith=vte,F.cloneWith=mte,F.conformsTo=yte,F.deburr=fj,F.defaultTo=Qne,F.divide=wre,F.endsWith=Cne,F.eq=Vi,F.escape=Ene,F.escapeRegExp=Nne,F.every=Mee,F.find=Lee,F.findIndex=DM,F.findKey=Yte,F.findLast=Oee,F.findLastIndex=FM,F.findLastKey=Kte,F.floor=Sre,F.forEach=UM,F.forEachRight=qM,F.forIn=Xte,F.forInRight=Zte,F.forOwn=Qte,F.forOwnRight=Jte,F.get=l1,F.gt=xte,F.gte=bte,F.has=nne,F.hasIn=c1,F.head=zM,F.identity=_o,F.includes=Hee,F.indexOf=OJ,F.inRange=wne,F.invoke=ine,F.isArguments=Qc,F.isArray=dt,F.isArrayBuffer=wte,F.isArrayLike=wo,F.isArrayLikeObject=Xn,F.isBoolean=Ste,F.isBuffer=Ll,F.isDate=_te,F.isElement=Cte,F.isEmpty=Ete,F.isEqual=Nte,F.isEqualWith=Rte,F.isError=s1,F.isFinite=Tte,F.isFunction=Pa,F.isInteger=JM,F.isLength=yv,F.isMap=ej,F.isMatch=kte,F.isMatchWith=Pte,F.isNaN=Ite,F.isNative=Ate,F.isNil=jte,F.isNull=Mte,F.isNumber=tj,F.isObject=Dn,F.isObjectLike=Gn,F.isPlainObject=Kh,F.isRegExp=a1,F.isSafeInteger=Lte,F.isSet=nj,F.isString=xv,F.isSymbol=zo,F.isTypedArray=Sd,F.isUndefined=Ote,F.isWeakMap=Dte,F.isWeakSet=Fte,F.join=HJ,F.kebabCase=Rne,F.last=Si,F.lastIndexOf=BJ,F.lowerCase=Tne,F.lowerFirst=kne,F.lt=$te,F.lte=zte,F.max=_re,F.maxBy=Cre,F.mean=Ere,F.meanBy=Nre,F.min=Rre,F.minBy=Tre,F.stubArray=m1,F.stubFalse=g1,F.stubObject=hre,F.stubString=pre,F.stubTrue=mre,F.multiply=kre,F.nth=WJ,F.noConflict=ire,F.noop=p1,F.now=mv,F.pad=Pne,F.padEnd=Ine,F.padStart=Ane,F.parseInt=Mne,F.random=Sne,F.reduce=Gee,F.reduceRight=Vee,F.repeat=jne,F.replace=Lne,F.result=fne,F.round=Pre,F.runInContext=ee,F.sample=Kee,F.size=Qee,F.snakeCase=One,F.some=Jee,F.sortedIndex=XJ,F.sortedIndexBy=ZJ,F.sortedIndexOf=QJ,F.sortedLastIndex=JJ,F.sortedLastIndexBy=eee,F.sortedLastIndexOf=tee,F.startCase=Fne,F.startsWith=$ne,F.subtract=Ire,F.sum=Are,F.sumBy=Mre,F.template=zne,F.times=gre,F.toFinite=Ia,F.toInteger=pt,F.toLength=oj,F.toLower=Hne,F.toNumber=_i,F.toSafeInteger=Hte,F.toString=Jt,F.toUpper=Bne,F.trim=Wne,F.trimEnd=Une,F.trimStart=qne,F.truncate=Gne,F.unescape=Vne,F.uniqueId=yre,F.upperCase=Yne,F.upperFirst=u1,F.each=UM,F.eachRight=qM,F.first=zM,h1(F,function(){var u={};return Ds(F,function(m,S){rn.call(F.prototype,S)||(uS=m)}),u}(),{chain:!1}),F.VERSION=r,yo("bind","bindKey","curry","curryRight","partial","partialRight",function(u){Fu.placeholder=F}),yo("drop","take",function(u,m){$t.prototypeu=function(S){S=S===n?1:pr(pt(S),0);var T=this.__filtered__&&!m?new $t(this):this.clone();return T.__filtered__?T.__takeCount__=Wr(S,T.__takeCount__):T.__views__.push({size:Wr(S,Q),type:u+(T.__dir__<0?"Right":"")}),T},$t.prototypeu+"Right"=function(S){return this.reverse()u(S).reverse()}}),yo("filter","map","takeWhile",function(u,m){var S=m+1,T=S==M||S==$;$t.prototypeu=function(L){var U=this.clone();return U.__iteratees__.push({iteratee:Je(L,3),type:S}),U.__filtered__=U.__filtered__||T,U}}),yo("head","last",function(u,m){var S="take"+(m?"Right":"");$t.prototypeu=function(){return thisS(1).value()0}}),yo("initial","tail",function(u,m){var S="drop"+(m?"":"Right");$t.prototypeu=function(){return this.__filtered__?new $t(this):thisS(1)}}),$t.prototype.compact=function(){return this.filter(_o)},$t.prototype.find=function(u){return this.filter(u).head()},$t.prototype.findLast=function(u){return this.reverse().find(u)},$t.prototype.invokeMap=Rt(function(u,m){return typeof u=="function"?new $t(this):this.map(function(S){return Wh(S,u,m)})}),$t.prototype.reject=function(u){return this.filter(vv(Je(u)))},$t.prototype.slice=function(u,m){u=pt(u);var S=this;return S.__filtered__&&(u>0||m<0)?new $t(S):(u<0?S=S.takeRight(-u):u&&(S=S.drop(u)),m!==n&&(m=pt(m),S=m<0?S.dropRight(-m):S.take(m-u)),S)},$t.prototype.takeRightWhile=function(u){return this.reverse().takeWhile(u).reverse()},$t.prototype.toArray=function(){return this.take(Q)},Ds($t.prototype,function(u,m){var S=/^(?:filter|find|map|reject)|While$/.test(m),T=/^(?:head|last)$/.test(m),L=FT?"take"+(m=="last"?"Right":""):m,U=T||/^find/.test(m);L&&(F.prototypem=function(){var X=this.__wrapped__,J=T?1:arguments,ne=X instanceof $t,me=J0,ve=ne||dt(X),be=function(Lt){var Wt=L.apply(F,js(Lt,J));return T&&Oe?Wt0:Wt};ve&&S&&typeof me=="function"&&me.length!=1&&(ne=ve=!1);var Oe=this.__chain__,We=!!this.__actions__.length,et=U&&!Oe,_t=ne&&!We;if(!U&&ve){X=_t?X:new $t(this);var tt=u.apply(X,J);return tt.__actions__.push({func:hv,args:be,thisArg:n}),new xi(tt,Oe)}return et&&_t?u.apply(this,J):(tt=this.thru(be),et?T?tt.value()0:tt.value():tt)})}),yo("pop","push","shift","sort","splice","unshift",function(u){var m=_ru,S=/^(?:push|sort|unshift)$/.test(u)?"tap":"thru",T=/^(?:pop|shift)$/.test(u);F.prototypeu=function(){var L=arguments;if(T&&!this.__chain__){var U=this.value();return m.apply(dt(U)?U:,L)}return thisS(function(X){return m.apply(dt(X)?X:,L)})}}),Ds($t.prototype,function(u,m){var S=Fm;if(S){var T=S.name+"";rn.call(vd,T)||(vdT=),vdT.push({name:m,func:S})}}),vdsv(n,x).name={name:"wrapper",func:n},$t.prototype.clone=VZ,$t.prototype.reverse=YZ,$t.prototype.value=KZ,F.prototype.at=Cee,F.prototype.chain=Eee,F.prototype.commit=Nee,F.prototype.next=Ree,F.prototype.plant=kee,F.prototype.reverse=Pee,F.prototype.toJSON=F.prototype.valueOf=F.prototype.value=Iee,F.prototype.first=F.prototype.head,Oh&&(F.prototypeOh=Tee),F},Bt=sr();Br?((Br.exports=Bt)._=Bt,Ch._=Bt):Un._=Bt}).call(rs)})(Ay,Ay.exports);var Yx=Ay.exports;function IW(e){const t=e.nb_ipid>0,n=e.nb_opid>0;return!t&&n?"source":t&&!n?"sink":"filter"}function vme(e){return IW(e)==="source"}const Yk={video:"#3b82f6",audio:"#10b981",text:"#f59e0b",file:"#E11D48"},yme={video:"Video",audio:"Audio",text:"Text",file:"File"},xme={video:"text-debug",audio:"text-info",text:"text-warning",file:"text-danger"},bme={video:"border-l-blue-500/60",audio:"border-l-emerald-500/60",text:"border-l-amber-500/60",file:"border-l-slate-500/60"},wme=e=>Yke,Kk=e=>{const t=e.toLowerCase();return t==="visual"||t==="video"?"video":t==="audio"?"audio":t==="text"?"text":"file"},Sme=e=>{const t=Kk(e);return bmet},_me=e=>{const t=Kk(e);return Ykt},Cme=e=>{const t=Kk(e);return xmet},AW=e=>{const t=new Set;return e.opid&&Object.values(e.opid).forEach(n=>{n.stream_type&&t.add(n.stream_type.toLowerCase())}),t.size===0&&e.ipid&&Object.values(e.ipid).forEach(n=>{n.stream_type&&t.add(n.stream_type.toLowerCase())}),t.has("visual")?"video":t.has("audio")?"audio":t.has("text")?"text":(t.has("file"),"file")},Eme=(e,t)=>{const n=e.find(o=>o.idx===t);if(!n)return{name:`Filter ${t}`,streamTypeColor:"#4CC9F0",streamType:"file",streamTypeLabel:"Unknown"};const r=AW(n);return{name:n.name,streamTypeColor:Ykr,streamType:r,streamTypeLabel:ymer}};function Nme(e,t,n,r){const o=n.find(a=>a.id===e.idx.toString()),i=AW(e);let s=150+t*300;if(r){const a=(d,f=new Set)=>{if(f.has(d.idx)||(f.add(d.idx),vme(d)))return 0;let p=0;return d.ipid&&Object.values(d.ipid).forEach(g=>{if(g.source_idx!==void 0&&g.source_idx!==null){const v=r.find(b=>b.idx===g.source_idx);if(v&&!f.has(v.idx)){const b=a(v,new Set(f));p=Math.max(p,b)}}}),p+1};s=150+...r.sort((d,f)=>{const p=a(d),g=a(f);return p!==g?p-g:d.idx-f.idx}).findIndex(d=>d.idx===e.idx)*300}return{id:e.idx.toString(),type:"gpacer",data:{label:e.name,filterType:i,...e},position:(o==null?void 0:o.position)||{x:s,y:100},className:` ${o!=null&&o.selected?"ring-2 ring-offset-2 ring-blue-500 shadow-lg scale-105":""}`,selected:o==null?void 0:o.selected,style:{width:220,height:"auto"}}}function Rme(e,t){const n=;return e.forEach(r=>{r.ipid&&Object.entries(r.ipid).forEach((o,i,s)=>{var a;if(i.source_idx!==void 0&&i.source_idx!==null){const l=`${i.source_idx}-${r.idx}-${s}`,c=t.find(_=>_.id===l),d=((a=i.stream_type)==null?void 0:a.toLowerCase())||"",f=d==="visual"?"video":d==="audio"?"audio":d==="text"?"text":"file",p=wme(f),g=e.find(_=>_.idx===i.source_idx);let v;if(g!=null&&g.opid&&(i.source_pid?v=i.source_pid:Object.keys(g.opid).length===1?v=Object.keys(g.opid)0:v=Object.keys(g.opid).find(x=>x===o||x.includes(o)||o.includes(x))||Object.keys(g.opid)0),i.virtual||!1)return;n.push({id:l,source:i.source_idx.toString(),target:r.idx.toString(),sourceHandle:v,targetHandle:o,type:"simplebezier",data:{filterType:f},animated:!0,style:{stroke:p,strokeWidth:3,opacity:.9},markerEnd:{type:wm.ArrowClosed,color:p},selected:c==null?void 0:c.selected})}})}),n}function Tme(e,t=){return e.map((n,r)=>Nme(n,r,t,e))}const kme={filters:,nodes:,edges:,isLoading:!1,error:null,redraw:!1,selectedNodeId:null,initialTab:null,pendingFilterOpen:null,lastUpdate:Date.now(),selectedFilterDetails:null},qO=500,MW=Sc({name:"graph",initialState:kme,reducers:{setLoading(e,t){e.isLoading=t.payload},setError(e,t){e.error=t.payload,e.isLoading=!1},updateGraphData:{reducer(e,t){e.filters=,e.nodes=,e.edges=,e.filters=t.payload;const n=Tme(t.payload,),r=Rme(t.payload,);e.nodes.length=0,e.edges.length=0,n.forEach(o=>e.nodes.push(o)),r.forEach(o=>e.edges.push(o)),e.lastUpdate=Date.now()},prepare:Yx.throttle(e=>({payload:e,meta:{throttle:qO}}),qO)},updateLayout(e,t){e.nodes=e.nodes.map(n=>{const r=t.payload.nodes.find(o=>o.id===n.id);return r?{...n,position:r.position}:n}),e.edges=t.payload.edges},setSelectedNode(e,t){e.selectedNodeId!==t.payload&&(e.selectedNodeId=t.payload)},clearSelectedNode(e){e.selectedNodeId=null},clearGraph(e){e.filters=,e.nodes=,e.edges=,e.selectedNodeId=null,e.selectedFilterDetails=null,e.error=null,e.isLoading=!1,e.pendingFilterOpen=null,e.initialTab=null},setFilterDetails:(e,t)=>{console.log("GraphSlice Updating filter details:",t.payload),e.selectedFilterDetails=t.payload},clearFilterDetails:e=>{e.selectedFilterDetails=null},setSelectedFilterDetails:(e,t)=>{e.selectedFilterDetails=t.payload,console.log("DETAILS DU FILTRE SÉLECTIONNÉ :",t.payload)},setInitialTab:(e,t)=>{e.initialTab=t.payload},clearInitialTab:e=>{e.initialTab=null},requestFilterOpen:(e,t)=>{e.pendingFilterOpen=t.payload,e.initialTab=t.payload.initialTab},clearPendingFilterOpen:e=>{e.pendingFilterOpen=null}}}),{setLoading:My,setError:A0,updateGraphData:Pme,updateLayout:O4e,setSelectedNode:Ime,clearSelectedNode:Ame,clearGraph:Mme,setFilterDetails:jW,clearFilterDetails:D4e,setSelectedFilterDetails:F4e,setInitialTab:$4e,clearInitialTab:jme,requestFilterOpen:LW,clearPendingFilterOpen:Lme}=MW.actions,Ome=(e,t)=>{const n=e.graph.filters.find(r=>r.idx.toString()===t);return n?n.name:""},Dme=MW.reducer;var At=(e=>(e.QUIET="quiet",e.ERROR="error",e.WARNING="warning",e.INFO="info",e.DEBUG="debug",e))(At||{}),Ot=(e=>(e.AUDIO="audio",e.CACHE="cache",e.CODEC="codec",e.CODING="coding",e.COMPOSE="compose",e.CONSOLE="console",e.CONTAINER="container",e.CORE="core",e.CTIME="comptime",e.DASH="dash",e.FILTER="filter",e.HTTP="http",e.INTERACT="interact",e.MEDIA="media",e.MEM="mem",e.MMIO="mmio",e.MODULE="module",e.MUTEX="mutex",e.NETWORK="network",e.PARSER="parser",e.RMTWS="rmtws",e.ROUTE="route",e.RTI="rti",e.RTP="rtp",e.SCENE="scene",e.SCHED="sched",e.SCRIPT="script",e.ALL="all",e))(Ot||{});const jr={quiet:0,error:1,warning:2,info:3,debug:4},Fme={getNumericValue:e=>jre,needsBackendCall:(e,t)=>jrt>jre,canUseFrontendFiltering:(e,t)=>jrt<=jre,compare:(e,t)=>jre-jrt};function $me(e,t){if(e==null)return"N/A";switch(t){case"bool":case"boolean":return e?"true":"false";case"frac":case"lfrac":case"fraction":return typeof e=="object"&&"n"in e&&"d"in e?`${e.n}/${e.d}`:String(e);case"sint":case"uint":case"lsint":case"luint":return e.toString();case"flt":case"dbl":case"double":case"float":return typeof e=="number"?e.toFixed(3).replace(/\.?0+$/,""):e.toString();case"str":case"cstr":case"string":case"name":return e;case"4cc":return e;case"pfmt":case"pixfmt":case"afmt":case"audiofmt":case"pcmfmt":return e;case"cprm":case"cicp_colr_prim":case"ctfc":case"cicp_colr_transfer":case"cmxc":case"cicp_colr_matrix":return e;case"v2di":case"v2d":case"v3di":case"v4di":case"v2il":return Array.isArray(e)?`${e.join(", ")}`:JSON.stringify(e);case"strl":case"4ccl":case"uintl":case"sintl":return Array.isArray(e)?e.join(", "):JSON.stringify(e);case"mem":case"cmem":return e instanceof ArrayBuffer?`${e.byteLength} bytes`:typeof e=="object"&&e!==null?"byteLength"in e?`${e.byteLength} bytes`:"length"in e?`${e.length} bytes`:Array.isArray(e)&&e.length<=16?`${e.map(n=>typeof n=="number"?n.toString(16).padStart(2,"0"):"??").join(" ")}`:"<binary data>":String(e);case"ptr":return typeof e=="number"?`0x${e.toString(16).toUpperCase()}`:String(e);default:return typeof e=="object"?JSON.stringify(e):String(e)}}var Lr=(e=>(e.DISCONNECTED="DISCONNECTED",e.CONNECTING="CONNECTING",e.CONNECTED="CONNECTED",e.RECONNECTING="RECONNECTING",e.ERROR="ERROR",e))(Lr||{}),ss=(e=>(e.SESSION_STATS="session_stats",e.FILTER_STATS="filter_stats",e.CPU_STATS="cpu_stats",e.LOGS="logs",e.FILTER_ARGS_DETAILS="filter_args_details",e.ALL_FILTERS="all_filters",e.BUSY_STATE="busy_state",e.GRAPH_RECALCULATION_EVENTS="graph_recalculation_events",e.FILTER_ARGUMENT_UPDATES="filter_argument_updates",e))(ss||{}),_n=(e=>(e.GRAPH="graph-monitor",e.METRICS="system-metrics",e.LOGS="logs-monitor",e.FILTERSESSION="session-filter-monitor",e))(_n||{});const Kx=0,_c=1,Xf=2,OW=4;function GO(e){return()=>e}function zme(e){e()}function DW(e,t){return n=>e(t(n))}function VO(e,t){return()=>e(t)}function Hme(e,t){return n=>e(t,n)}function Xk(e){return e!==void 0}function Bme(...e){return()=>{e.map(zme)}}function Zf(){}function Xx(e,t){return t(e),e}function Wme(e,t){return t(e)}function Tn(...e){return e}function tn(e,t){return e(_c,t)}function vt(e,t){e(Kx,t)}function Zk(e){e(Xf)}function An(e){return e(OW)}function Ge(e,t){return tn(e,Hme(t,Kx))}function ds(e,t){const n=e(_c,r=>{n(),t(r)});return n}function YO(e){let t,n;return r=>o=>{t=o,n&&clearTimeout(n),n=setTimeout(()=>{r(t)},e)}}function FW(e,t){return e===t}function Cn(e=FW){let t;return n=>r=>{e(t,r)||(t=r,n(r))}}function it(e){return t=>n=>{e(n)&&t(n)}}function Fe(e){return t=>DW(t,e)}function Vs(e){return t=>()=>{t(e)}}function _e(e,...t){const n=Ume(...t);return(r,o)=>{switch(r){case Xf:Zk(e);return;case _c:return tn(e,n(o))}}}function Qs(e,t){return n=>r=>{n(t=e(t,r))}}function Fu(e){return t=>n=>{e>0?e--:t(n)}}function Ba(e){let t=null,n;return r=>o=>{t=o,!n&&(n=setTimeout(()=>{n=void 0,r(t)},e))}}function Et(...e){const t=new Array(e.length);let n=0,r=null;const o=Math.pow(2,e.length)-1;return e.forEach((i,s)=>{const a=Math.pow(2,s);tn(i,l=>{const c=n;n=n|a,ts=l,c!==o&&n===o&&r&&(r(),r=null)})}),i=>s=>{const a=()=>{i(s.concat(t))};n===o?a():r=a}}function Ume(...e){return t=>e.reduceRight(Wme,t)}function qme(e){let t,n;const r=()=>t==null?void 0:t();return function(o,i){switch(o){case _c:return i?n===i?void 0:(r(),n=i,t=tn(e,i),t):(r(),Zf);case Xf:r(),n=null;return}}}function ke(e){let t=e;const n=Ut();return(r,o)=>{switch(r){case Kx:t=o;break;case _c:{o(t);break}case OW:return t}return n(r,o)}}function Qr(e,t){return Xx(ke(t),n=>Ge(e,n))}function Ut(){const e=;return(t,n)=>{switch(t){case Kx:e.slice().forEach(r=>{r(n)});return;case Xf:e.splice(0,e.length);return;case _c:return e.push(n),()=>{const r=e.indexOf(n);r>-1&&e.splice(r,1)}}}}function ti(e){return Xx(Ut(),t=>Ge(e,t))}function Kt(e,t=,{singleton:n}={singleton:!0}){return{constructor:e,dependencies:t,id:Gme(),singleton:n}}const Gme=()=>Symbol();function Vme(e){const t=new Map,n=({constructor:r,dependencies:o,id:i,singleton:s})=>{if(s&&t.has(i))return t.get(i);const a=r(o.map(l=>n(l)));return s&&t.set(i,a),a};return n(e)}function cr(...e){const t=Ut(),n=new Array(e.length);let r=0;const o=Math.pow(2,e.length)-1;return e.forEach((i,s)=>{const a=Math.pow(2,s);tn(i,l=>{ns=l,r=r|a,r===o&&vt(t,n)})}),function(i,s){switch(i){case Xf:{Zk(t);return}case _c:return r===o&&s(n),tn(t,s)}}}function ot(e,t=FW){return _e(e,Cn(t))}function PR(...e){return function(t,n){switch(t){case Xf:return;case _c:return Bme(...e.map(r=>tn(r,n)))}}}var Ao=(e=>(ee.DEBUG=0="DEBUG",ee.INFO=1="INFO",ee.WARN=2="WARN",ee.ERROR=3="ERROR",e))(Ao||{});const Yme={0:"debug",3:"error",1:"log",2:"warn"},Kme=()=>typeof globalThis>"u"?window:globalThis,Cc=Kt(()=>{const e=ke(3);return{log:ke((t,n,r=1)=>{var o;const i=(o=Kme().VIRTUOSO_LOG_LEVEL)!=null?o:An(e);r>=i&&consoleYmer("%creact-virtuoso: %c%s %o","color: #0253b3; font-weight: bold","color: initial",t,n)}),logLevel:e}},,{singleton:!0});function Uu(e,t,n){return Qk(e,t,n).callbackRef}function Qk(e,t,n){const r=Pe.useRef(null);let o=s=>{};const i=Pe.useMemo(()=>typeof ResizeObserver<"u"?new ResizeObserver(s=>{const a=()=>{const l=s0.target;l.offsetParent!==null&&e(l)};n?a():requestAnimationFrame(a)}):null,e,n);return o=s=>{s&&t?(i==null||i.observe(s),r.current=s):(r.current&&(i==null||i.unobserve(r.current)),r.current=null)},{callbackRef:o,ref:r}}function Xme(e,t,n,r,o,i,s,a,l){const c=Pe.useCallback(d=>{const f=Zme(d.children,t,a?"offsetWidth":"offsetHeight",o);let p=d.parentElement;for(;!p.dataset.virtuosoScroller;)p=p.parentElement;const g=p.lastElementChild.dataset.viewportType==="window";let v;g&&(v=p.ownerDocument.defaultView);const b=s?a?s.scrollLeft:s.scrollTop:g?a?v.scrollX||v.document.documentElement.scrollLeft:v.scrollY||v.document.documentElement.scrollTop:a?p.scrollLeft:p.scrollTop,_=s?a?s.scrollWidth:s.scrollHeight:g?a?v.document.documentElement.scrollWidth:v.document.documentElement.scrollHeight:a?p.scrollWidth:p.scrollHeight,x=s?a?s.offsetWidth:s.offsetHeight:g?a?v.innerWidth:v.innerHeight:a?p.offsetWidth:p.offsetHeight;r({scrollHeight:_,scrollTop:Math.max(b,0),viewportHeight:x}),i==null||i(a?KO("column-gap",getComputedStyle(d).columnGap,o):KO("row-gap",getComputedStyle(d).rowGap,o)),f!==null&&e(f)},e,t,o,i,s,r,a);return Qk(c,n,l)}function Zme(e,t,n,r){const o=e.length;if(o===0)return null;const i=;for(let s=0;s<o;s++){const a=e.item(s);if(a.dataset.index===void 0)continue;const l=parseInt(a.dataset.index),c=parseFloat(a.dataset.knownSize),d=t(a,n);if(d===0&&r("Zero-sized element, this should not happen",{child:a},Ao.ERROR),d===c)continue;const f=ii.length-1;i.length===0||f.size!==d||f.endIndex!==l-1?i.push({endIndex:l,size:d,startIndex:l}):ii.length-1.endIndex++}return i}function KO(e,t,n){return t!=="normal"&&!(t!=null&&t.endsWith("px"))&&n(`${e} was not resolved to pixel value correctly`,t,Ao.WARN),t==="normal"?0:parseInt(t??"0",10)}function $W(e,t,n){const r=Pe.useRef(null),o=Pe.useCallback(l=>{if(!(l!=null&&l.offsetParent))return;const c=l.getBoundingClientRect(),d=c.width;let f,p;if(t){const g=t.getBoundingClientRect(),v=c.top-g.top;p=g.height-Math.max(0,v),f=v+t.scrollTop}else{const g=s.current.ownerDocument.defaultView;p=g.innerHeight-Math.max(0,c.top),f=c.top+g.scrollY}r.current={offsetTop:f,visibleHeight:p,visibleWidth:d},e(r.current)},e,t),{callbackRef:i,ref:s}=Qk(o,!0,n),a=Pe.useCallback(()=>{o(s.current)},o,s);return Pe.useEffect(()=>{var l;if(t){t.addEventListener("scroll",a);const c=new ResizeObserver(()=>{requestAnimationFrame(a)});return c.observe(t),()=>{t.removeEventListener("scroll",a),c.unobserve(t)}}else{const c=(l=s.current)==null?void 0:l.ownerDocument.defaultView;return c==null||c.addEventListener("scroll",a),c==null||c.addEventListener("resize",a),()=>{c==null||c.removeEventListener("scroll",a),c==null||c.removeEventListener("resize",a)}}},a,t,s),i}const go=Kt(()=>{const e=Ut(),t=Ut(),n=ke(0),r=Ut(),o=ke(0),i=Ut(),s=Ut(),a=ke(0),l=ke(0),c=ke(0),d=ke(0),f=Ut(),p=Ut(),g=ke(!1),v=ke(!1),b=ke(!1);return Ge(_e(e,Fe(({scrollTop:_})=>_)),t),Ge(_e(e,Fe(({scrollHeight:_})=>_)),s),Ge(t,o),{deviation:n,fixedFooterHeight:c,fixedHeaderHeight:l,footerHeight:d,headerHeight:a,horizontalDirection:v,scrollBy:p,scrollContainerState:e,scrollHeight:s,scrollingInProgress:g,scrollTo:f,scrollTop:t,skipAnimationFrameInResizeObserver:b,smoothScrollTargetReached:r,statefulScrollTop:o,viewportHeight:i}},,{singleton:!0}),Rm={lvl:0};function zW(e,t){const n=e.length;if(n===0)return;let{index:r,value:o}=t(e0);const i=;for(let s=1;s<n;s++){const{index:a,value:l}=t(es);i.push({end:a-1,start:r,value:o}),r=a,o=l}return i.push({end:1/0,start:r,value:o}),i}function dn(e){return e===Rm}function Tm(e,t){if(!dn(e))return t===e.k?e.v:t<e.k?Tm(e.l,t):Tm(e.r,t)}function ps(e,t,n="k"){if(dn(e))return-1/0,void 0;if(Number(en)===t)returne.k,e.v;if(Number(en)<t){const r=ps(e.r,t,n);return r0===-1/0?e.k,e.v:r}return ps(e.l,t,n)}function Xo(e,t,n){return dn(e)?WW(t,n,1):t===e.k?vr(e,{k:t,v:n}):t<e.k?XO(vr(e,{l:Xo(e.l,t,n)})):XO(vr(e,{r:Xo(e.r,t,n)}))}function cf(){return Rm}function uf(e,t,n){if(dn(e))return;const r=ps(e,t)0;return Qme(AR(e,r,n))}function IR(e,t){if(dn(e))return Rm;const{k:n,l:r,r:o}=e;if(t===n){if(dn(r))return o;if(dn(o))return r;{consti,s=BW(r);return M0(vr(e,{k:i,l:HW(r),v:s}))}}else return t<n?M0(vr(e,{l:IR(r,t)})):M0(vr(e,{r:IR(o,t)}))}function pu(e){return dn(e)?:...pu(e.l),{k:e.k,v:e.v},...pu(e.r)}function AR(e,t,n){if(dn(e))return;const{k:r,l:o,r:i,v:s}=e;let a=;return r>t&&(a=a.concat(AR(o,t,n))),r>=t&&r<=n&&a.push({k:r,v:s}),r<=n&&(a=a.concat(AR(i,t,n))),a}function M0(e){const{l:t,lvl:n,r}=e;if(r.lvl>=n-1&&t.lvl>=n-1)return e;if(n>r.lvl+1){if(iS(t))return UW(vr(e,{lvl:n-1}));if(!dn(t)&&!dn(t.r))return vr(t.r,{l:vr(t,{r:t.r.l}),lvl:n,r:vr(e,{l:t.r.r,lvl:n-1})});throw new Error("Unexpected empty nodes")}else{if(iS(e))return MR(vr(e,{lvl:n-1}));if(!dn(r)&&!dn(r.l)){const o=r.l,i=iS(o)?r.lvl-1:r.lvl;return vr(o,{l:vr(e,{lvl:n-1,r:o.l}),lvl:o.lvl+1,r:MR(vr(r,{l:o.r,lvl:i}))})}else throw new Error("Unexpected empty nodes")}}function vr(e,t){return WW(t.k!==void 0?t.k:e.k,t.v!==void 0?t.v:e.v,t.lvl!==void 0?t.lvl:e.lvl,t.l!==void 0?t.l:e.l,t.r!==void 0?t.r:e.r)}function HW(e){return dn(e.r)?e.l:M0(vr(e,{r:HW(e.r)}))}function iS(e){return dn(e)||e.lvl>e.r.lvl}function BW(e){return dn(e.r)?e.k,e.v:BW(e.r)}function WW(e,t,n,r=Rm,o=Rm){return{k:e,l:r,lvl:n,r:o,v:t}}function XO(e){return MR(UW(e))}function UW(e){const{l:t}=e;return!dn(t)&&t.lvl===e.lvl?vr(t,{r:vr(e,{l:t.r})}):e}function MR(e){const{lvl:t,r:n}=e;return!dn(n)&&!dn(n.r)&&n.lvl===t&&n.r.lvl===t?vr(n,{l:vr(e,{r:n.l}),lvl:t+1}):e}function Qme(e){return zW(e,({k:t,v:n})=>({index:t,value:n}))}function qW(e,t){return!!(e&&e.startIndex===t.startIndex&&e.endIndex===t.endIndex)}function km(e,t){return!!(e&&e0===t0&&e1===t1)}const Jk=Kt(()=>({recalcInProgress:ke(!1)}),,{singleton:!0});function GW(e,t,n){return ejy(e,t,n)}function jy(e,t,n,r=0){let o=e.length-1;for(;r<=o;){const i=Math.floor((r+o)/2),s=ei,a=n(s,t);if(a===0)return i;if(a===-1){if(o-r<2)return i-1;o=i-1}else{if(o===r)return i;r=i+1}}throw new Error(`Failed binary finding record in array - ${e.join(",")}, searched for ${t}`)}function Jme(e,t,n,r){const o=jy(e,t,r),i=jy(e,n,r,o);return e.slice(o,i+1)}function pc(e,t){return Math.round(e.getBoundingClientRect()t)}function Zx(e){return!dn(e.groupOffsetTree)}function eP({index:e},t){return t===e?0:t<e?-1:1}function ege(){return{groupIndices:,groupOffsetTree:cf(),lastIndex:0,lastOffset:0,lastSize:0,offsetTree:,sizeTree:cf()}}function tge(e,t){let n=dn(e)?0:1/0;for(const r of t){const{endIndex:o,size:i,startIndex:s}=r;if(n=Math.min(n,s),dn(e)){e=Xo(e,0,i);continue}const a=uf(e,s-1,o+1);if(a.some(lge(r)))continue;let l=!1,c=!1;for(const{end:d,start:f,value:p}of a)l?(o>=f||i===p)&&(e=IR(e,f)):(c=p!==i,l=!0),d>o&&o>=f&&p!==i&&(e=Xo(e,o+1,p));c&&(e=Xo(e,s,i))}returne,n}function nge(e){return typeof e.groupIndex<"u"}function rge({offset:e},t){return t===e?0:t<e?-1:1}function Pm(e,t,n){if(t.length===0)return 0;const{index:r,offset:o,size:i}=GW(t,e,eP),s=e-r,a=i*s+(s-1)*n+o;return a>0?a+n:a}function VW(e,t){if(!Zx(t))return e;let n=0;for(;t.groupIndicesn<=e+n;)n++;return e+n}function YW(e,t,n){if(nge(e))return t.groupIndicese.groupIndex+1;{const r=e.index==="LAST"?n:e.index;let o=VW(r,t);return o=Math.max(0,o,Math.min(n,o)),o}}function oge(e,t,n,r=0){return r>0&&(t=Math.max(t,GW(e,r,eP).offset)),zW(Jme(e,t,n,rge),age)}function ige(e,t,n,r,o){t.length>0&&r("received item sizes",t,Ao.DEBUG);const i=e.sizeTree;let s=i,a=0;if(n.length>0&&dn(i)&&t.length===2){const p=t0.size,g=t1.size;s=n.reduce((v,b)=>Xo(Xo(v,b,p),b+1,g),s)}elses,a=tge(s,t);if(s===i)return e;const{lastIndex:l,lastOffset:c,lastSize:d,offsetTree:f}=jR(e.offsetTree,a,s,o);return{groupIndices:n,groupOffsetTree:n.reduce((p,g)=>Xo(p,g,Pm(g,f,o)),cf()),lastIndex:l,lastOffset:c,lastSize:d,offsetTree:f,sizeTree:s}}function sge(e){return pu(e).map(({k:t,v:n},r,o)=>{const i=or+1;return{endIndex:i?i.k-1:1/0,size:n,startIndex:t}})}function ZO(e,t){let n=0,r=0;for(;n<e;)n+=tr+1-tr-1,r++;return r-(n===e?0:1)}function jR(e,t,n,r){let o=e,i=0,s=0,a=0,l=0;if(t!==0){l=jy(o,t-1,eP),a=ol.offset;const c=ps(n,t-1);i=c0,s=c1,o.length&&ol.size===ps(n,t)1&&(l-=1),o=o.slice(0,l+1)}else o=;for(const{start:c,value:d}of uf(n,t,1/0)){const f=c-i,p=f*s+a+f*r;o.push({index:c,offset:p,size:d}),i=c,a=p,s=d}return{lastIndex:i,lastOffset:a,lastSize:s,offsetTree:o}}function age(e){return{index:e.index,value:e}}function lge(e){const{endIndex:t,size:n,startIndex:r}=e;return o=>o.start===r&&(o.end===t||o.end===1/0)&&o.value===n}const cge={offsetHeight:"height",offsetWidth:"width"},ca=Kt(({log:e},{recalcInProgress:t})=>{const n=Ut(),r=Ut(),o=Qr(r,0),i=Ut(),s=Ut(),a=ke(0),l=ke(),c=ke(void 0),d=ke(void 0),f=ke(void 0),p=ke(void 0),g=ke((N,k)=>pc(N,cgek)),v=ke(void 0),b=ke(0),_=ege(),x=Qr(_e(n,Et(l,e,b),Qs(ige,_),Cn()),_),w=Qr(_e(l,Cn(),Qs((N,k)=>({current:k,prev:N.current}),{current:,prev:}),Fe(({prev:N})=>N)),);Ge(_e(l,it(N=>N.length>0),Et(x,b),Fe((N,k,I)=>{const O=N.reduce((j,H,q)=>Xo(j,H,Pm(H,k.offsetTree,I)||q),cf());return{...k,groupIndices:N,groupOffsetTree:O}})),x),Ge(_e(r,Et(x),it((N,{lastIndex:k})=>N<k),Fe((N,{lastIndex:k,lastSize:I})=>{endIndex:k,size:I,startIndex:N})),n),Ge(c,d);const C=Qr(_e(c,Fe(N=>N===void 0)),!0);Ge(_e(d,it(N=>N!==void 0&&dn(An(x).sizeTree)),Fe(N=>{const k=An(f),I=An(l).length>0;return k?I?{endIndex:0,size:k,startIndex:0},{endIndex:1,size:N,startIndex:1}::{endIndex:0,size:N,startIndex:0}})),n),Ge(_e(p,it(N=>N!==void 0&&N.length>0&&dn(An(x).sizeTree)),Fe(N=>{const k=;let I=N0,O=0;for(let j=1;j<N.length;j++){const H=Nj;H!==I&&(k.push({endIndex:j-1,size:I,startIndex:O}),I=H,O=j)}return k.push({endIndex:N.length-1,size:I,startIndex:O}),k})),n),Ge(_e(l,Et(f,d),it((,N,k)=>N!==void 0&&k!==void 0),Fe((N,k,I)=>{const O=;for(let j=0;j<N.length;j++){const H=Nj,q=Nj+1;O.push({startIndex:H,endIndex:H,size:k}),q!==void 0&&O.push({startIndex:H+1,endIndex:q-1,size:I})}return O})),n);const E=ti(_e(n,Et(x),Qs(({sizes:N},k,I)=>({changed:I!==N,sizes:I}),{changed:!1,sizes:_}),Fe(N=>N.changed)));tn(_e(a,Qs((N,k)=>({diff:N.prev-k,prev:k}),{diff:0,prev:0}),Fe(N=>N.diff)),N=>{const{groupIndices:k}=An(x);if(N>0)vt(t,!0),vt(i,N+ZO(N,k));else if(N<0){const I=An(w);I.length>0&&(N-=ZO(-N,I)),vt(s,N)}}),tn(_e(a,Et(e)),(N,k)=>{N<0&&k("`firstItemIndex` prop should not be set to less than zero. If you don't know the total count, just use a very high value",{firstItemIndex:a},Ao.ERROR)});const R=ti(i);Ge(_e(i,Et(x),Fe((N,k)=>{const I=k.groupIndices.length>0,O=,j=k.lastSize;if(I){const H=Tm(k.sizeTree,0);let q=0,M=0;for(;q<N;){const $=k.groupIndicesM,W=k.groupIndices.length===M+1?1/0:k.groupIndicesM+1-$-1;O.push({endIndex:$,size:H,startIndex:$}),O.push({endIndex:$+1+W-1,size:j,startIndex:$+1}),M++,q+=W+1}const B=pu(k.sizeTree);return q!==N&&B.shift(),B.reduce(($,{k:W,v:G})=>{let z=$.ranges;return $.prevSize!==0&&(z=...$.ranges,{endIndex:W+N-1,size:$.prevSize,startIndex:$.prevIndex}),{prevIndex:W+N,prevSize:G,ranges:z}},{prevIndex:N,prevSize:0,ranges:O}).ranges}return pu(k.sizeTree).reduce((H,{k:q,v:M})=>({prevIndex:q+N,prevSize:M,ranges:...H.ranges,{endIndex:q+N-1,size:H.prevSize,startIndex:H.prevIndex}}),{prevIndex:0,prevSize:j,ranges:}).ranges})),n);const P=ti(_e(s,Et(x,b),Fe((N,{offsetTree:k},I)=>{const O=-N;return Pm(O,k,I)})));return Ge(_e(s,Et(x,b),Fe((N,k,I)=>{if(k.groupIndices.length>0){if(dn(k.sizeTree))return k;let O=cf();const j=An(w);let H=0,q=0,M=0;for(;H<-N;){M=jq;const B=jq+1-M-1;q++,H+=B+1}if(O=pu(k.sizeTree).reduce((B,{k:$,v:W})=>Xo(B,Math.max(0,$+N),W),O),H!==-N){const B=Tm(k.sizeTree,M);O=Xo(O,0,B);const $=ps(k.sizeTree,-N+1)1;O=Xo(O,1,$)}return{...k,sizeTree:O,...jR(k.offsetTree,0,O,I)}}else{const O=pu(k.sizeTree).reduce((j,{k:H,v:q})=>Xo(j,Math.max(0,H+N),q),cf());return{...k,sizeTree:O,...jR(k.offsetTree,0,O,I)}}})),x),{beforeUnshiftWith:R,data:v,defaultItemSize:d,firstItemIndex:a,fixedItemSize:c,fixedGroupSize:f,gap:b,groupIndices:l,heightEstimates:p,itemSize:g,listRefresh:E,shiftWith:s,shiftWithOffset:P,sizeRanges:n,sizes:x,statefulTotalCount:o,totalCount:r,trackItemSizes:C,unshiftWith:i}},Tn(Cc,Jk),{singleton:!0});function uge(e){return e.reduce((t,n)=>(t.groupIndices.push(t.totalCount),t.totalCount+=n+1,t),{groupIndices:,totalCount:0})}const KW=Kt(({groupIndices:e,sizes:t,totalCount:n},{headerHeight:r,scrollTop:o})=>{const i=Ut(),s=Ut(),a=ti(_e(i,Fe(uge)));return Ge(_e(a,Fe(l=>l.totalCount)),n),Ge(_e(a,Fe(l=>l.groupIndices)),e),Ge(_e(cr(o,t,r),it((l,c)=>Zx(c)),Fe((l,c,d)=>ps(c.groupOffsetTree,Math.max(l-d,0),"v")0),Cn(),Fe(l=>l)),s),{groupCounts:i,topItemsIndexes:s}},Tn(ca,go)),Ec=Kt(({log:e})=>{const t=ke(!1),n=ti(_e(t,it(r=>r),Cn()));return tn(t,r=>{r&&An(e)("props updated",{},Ao.DEBUG)}),{didMount:n,propsReady:t}},Tn(Cc),{singleton:!0}),dge=typeof document<"u"&&"scrollBehavior"in document.documentElement.style;function XW(e){const t=typeof e=="number"?{index:e}:e;return t.align||(t.align="start"),(!t.behavior||!dge)&&(t.behavior="auto"),t.offset||(t.offset=0),t}const eg=Kt(({gap:e,listRefresh:t,sizes:n,totalCount:r},{fixedFooterHeight:o,fixedHeaderHeight:i,footerHeight:s,headerHeight:a,scrollingInProgress:l,scrollTo:c,smoothScrollTargetReached:d,viewportHeight:f},{log:p})=>{const g=Ut(),v=Ut(),b=ke(0);let _=null,x=null,w=null;function C(){_&&(_(),_=null),w&&(w(),w=null),x&&(clearTimeout(x),x=null),vt(l,!1)}return Ge(_e(g,Et(n,f,r,b,a,s,p),Et(e,i,o),Fe((E,R,P,N,k,I,O,j,H,q,M)=>{const B=XW(E),{align:$,behavior:W,offset:G}=B,z=N-1,K=YW(B,R,z);let Q=Pm(K,R.offsetTree,H)+I;$==="end"?(Q+=q+ps(R.sizeTree,K)1-P+M,K===z&&(Q+=O)):$==="center"?Q+=(q+ps(R.sizeTree,K)1-P+M)/2:Q-=k,G&&(Q+=G);const re=ae=>{C(),ae?(j("retrying to scroll to",{location:E},Ao.DEBUG),vt(g,E)):(vt(v,!0),j("list did not change, scroll successful",{},Ao.DEBUG))};if(C(),W==="smooth"){let ae=!1;w=tn(t,de=>{ae=ae||de}),_=ds(d,()=>{re(ae)})}else _=ds(_e(t,fge(150)),re);return x=setTimeout(()=>{C()},1200),vt(l,!0),j("scrolling from index to",{behavior:W,index:K,top:Q},Ao.DEBUG),{behavior:W,top:Q}})),c),{scrollTargetReached:v,scrollToIndex:g,topListHeight:b}},Tn(ca,go,Cc),{singleton:!0});function fge(e){return t=>{const n=setTimeout(()=>{t(!1)},e);return r=>{r&&(t(!0),clearTimeout(n))}}}function tP(e,t){e==0?t():requestAnimationFrame(()=>{tP(e-1,t)})}function nP(e,t){const n=t-1;return typeof e=="number"?e:e.index==="LAST"?n:e.index}const tg=Kt(({defaultItemSize:e,listRefresh:t,sizes:n},{scrollTop:r},{scrollTargetReached:o,scrollToIndex:i},{didMount:s})=>{const a=ke(!0),l=ke(0),c=ke(!0);return Ge(_e(s,Et(l),it((d,f)=>!!f),Vs(!1)),a),Ge(_e(s,Et(l),it((d,f)=>!!f),Vs(!1)),c),tn(_e(cr(t,s),Et(a,n,e,c),it((,d,f,{sizeTree:p},g,v)=>d&&(!dn(p)||Xk(g))&&!f&&!v),Et(l)),(,d)=>{ds(o,()=>{vt(c,!0)}),tP(4,()=>{ds(r,()=>{vt(a,!0)}),vt(i,d)})}),{initialItemFinalLocationReached:c,initialTopMostItemIndex:l,scrolledToInitialItem:a}},Tn(ca,go,eg,Ec),{singleton:!0});function ZW(e,t){return Math.abs(e-t)<1.01}const Im="up",qp="down",hge="none",pge={atBottom:!1,notAtBottomBecause:"NOT_SHOWING_LAST_ITEM",state:{offsetBottom:0,scrollHeight:0,scrollTop:0,viewportHeight:0}},mge=0,ng=Kt(({footerHeight:e,headerHeight:t,scrollBy:n,scrollContainerState:r,scrollTop:o,viewportHeight:i})=>{const s=ke(!1),a=ke(!0),l=Ut(),c=Ut(),d=ke(4),f=ke(mge),p=Qr(_e(PR(_e(ot(o),Fu(1),Vs(!0)),_e(ot(o),Fu(1),Vs(!1),YO(100))),Cn()),!1),g=Qr(_e(PR(_e(n,Vs(!0)),_e(n,Vs(!1),YO(200))),Cn()),!1);Ge(_e(cr(ot(o),ot(f)),Fe((w,C)=>w<=C),Cn()),a),Ge(_e(a,Ba(50)),c);const v=ti(_e(cr(r,ot(i),ot(t),ot(e),ot(d)),Qs((w,{scrollHeight:C,scrollTop:E},R,P,N,k)=>{const I=E+R-C>-k,O={scrollHeight:C,scrollTop:E,viewportHeight:R};if(I){let H,q;return E>w.state.scrollTop?(H="SCROLLED_DOWN",q=w.state.scrollTop-E):(H="SIZE_DECREASED",q=w.state.scrollTop-E||w.scrollTopDelta),{atBottom:!0,atBottomBecause:H,scrollTopDelta:q,state:O}}let j;return O.scrollHeight>w.state.scrollHeight?j="SIZE_INCREASED":R<w.state.viewportHeight?j="VIEWPORT_HEIGHT_DECREASING":E<w.state.scrollTop?j="SCROLLING_UPWARDS":j="NOT_FULLY_SCROLLED_TO_LAST_ITEM_BOTTOM",{atBottom:!1,notAtBottomBecause:j,state:O}},pge),Cn((w,C)=>w&&w.atBottom===C.atBottom))),b=Qr(_e(r,Qs((w,{scrollHeight:C,scrollTop:E,viewportHeight:R})=>{if(ZW(w.scrollHeight,C))return{changed:!1,jump:0,scrollHeight:C,scrollTop:E};{const P=C-(E+R)<1;return w.scrollTop!==E&&P?{changed:!0,jump:w.scrollTop-E,scrollHeight:C,scrollTop:E}:{changed:!0,jump:0,scrollHeight:C,scrollTop:E}}},{changed:!1,jump:0,scrollHeight:0,scrollTop:0}),it(w=>w.changed),Fe(w=>w.jump)),0);Ge(_e(v,Fe(w=>w.atBottom)),s),Ge(_e(s,Ba(50)),l);const _=ke(qp);Ge(_e(r,Fe(({scrollTop:w})=>w),Cn(),Qs((w,C)=>An(g)?{direction:w.direction,prevScrollTop:C}:{direction:C<w.prevScrollTop?Im:qp,prevScrollTop:C},{direction:qp,prevScrollTop:0}),Fe(w=>w.direction)),_),Ge(_e(r,Ba(50),Vs(hge)),_);const x=ke(0);return Ge(_e(p,it(w=>!w),Vs(0)),x),Ge(_e(o,Ba(100),Et(p),it((w,C)=>C),Qs((w,C,E)=>C,E,0,0),Fe((w,C)=>C-w)),x),{atBottomState:v,atBottomStateChange:l,atBottomThreshold:d,atTopStateChange:c,atTopThreshold:f,isAtBottom:s,isAtTop:a,isScrolling:p,lastJumpDueToItemResize:b,scrollDirection:_,scrollVelocity:x}},Tn(go)),Am="top",Mm="bottom",QO="none";function JO(e,t,n){return typeof e=="number"?n===Im&&t===Am||n===qp&&t===Mm?e:0:n===Im?t===Am?e.main:e.reverse:t===Mm?e.main:e.reverse}function eD(e,t){var n;return typeof e=="number"?e:(n=et)!=null?n:0}const rP=Kt(({deviation:e,fixedHeaderHeight:t,headerHeight:n,scrollTop:r,viewportHeight:o})=>{const i=Ut(),s=ke(0),a=ke(0),l=ke(0),c=Qr(_e(cr(ot(r),ot(o),ot(n),ot(i,km),ot(l),ot(s),ot(t),ot(e),ot(a)),Fe((d,f,p,g,v,b,_,x,w,C)=>{const E=d-w,R=_+x,P=Math.max(p-E,0);let N=QO;const k=eD(C,Am),I=eD(C,Mm);return g-=w,g+=p+x,v+=p+x,v-=w,g>d+R-k&&(N=Im),v<d-P+f+I&&(N=qp),N!==QO?Math.max(E-p-JO(b,Am,N)-k,0),E-P-x+f+JO(b,Mm,N)+I:null}),it(d=>d!=null),Cn(km)),0,0);return{increaseViewportBy:a,listBoundary:i,overscan:l,topListHeight:s,visibleRange:c}},Tn(go),{singleton:!0});function gge(e,t,n){if(Zx(t)){const r=VW(e,t);return{index:ps(t.groupOffsetTree,r)0,offset:0,size:0},{data:n==null?void 0:n0,index:r,offset:0,size:0}}return{data:n==null?void 0:n0,index:e,offset:0,size:0}}const sS={bottom:0,firstItemIndex:0,items:,offsetBottom:0,offsetTop:0,top:0,topItems:,topListHeight:0,totalCount:0};function j0(e,t,n,r,o,i){const{lastIndex:s,lastOffset:a,lastSize:l}=o;let c=0,d=0;if(e.length>0){c=e0.offset;const b=ee.length-1;d=b.offset+b.size}const f=n-s,p=a+f*l+(f-1)*r,g=c,v=p-d;return{bottom:d,firstItemIndex:i,items:tD(e,o,i),offsetBottom:v,offsetTop:c,top:g,topItems:tD(t,o,i),topListHeight:t.reduce((b,_)=>_.size+b,0),totalCount:n}}function QW(e,t,n,r,o,i){let s=0;if(n.groupIndices.length>0)for(const d of n.groupIndices){if(d-s>=e)break;s++}const a=e+s,l=nP(t,a),c=Array.from({length:a}).map((d,f)=>({data:if+l,index:f+l,offset:0,size:0}));return j0(c,,a,o,n,r)}function tD(e,t,n){if(e.length===0)return;if(!Zx(t))return e.map(c=>({...c,index:c.index+n,originalIndex:c.index}));const r=e0.index,o=ee.length-1.index,i=,s=uf(t.groupOffsetTree,r,o);let a,l=0;for(const c of e){(!a||a.end<c.index)&&(a=s.shift(),l=t.groupIndices.indexOf(a.start));let d;c.index===a.start?d={index:l,type:"group"}:d={groupIndex:l,index:c.index-(l+1)+n},i.push({...d,data:c.data,offset:c.offset,originalIndex:c.index,size:c.size})}return i}function nD(e,t){var n;return e===void 0?0:typeof e=="number"?e:(n=et)!=null?n:0}const qu=Kt(({data:e,firstItemIndex:t,gap:n,sizes:r,totalCount:o},i,{listBoundary:s,topListHeight:a,visibleRange:l},{initialTopMostItemIndex:c,scrolledToInitialItem:d},{topListHeight:f},p,{didMount:g},{recalcInProgress:v})=>{const b=ke(),_=ke(0),x=Ut(),w=ke(0);Ge(i.topItemsIndexes,b);const C=Qr(_e(cr(g,v,ot(l,km),ot(o),ot(r),ot(c),d,ot(b),ot(t),ot(n),ot(w),e),it((N,k,,I,,,,,,,,O)=>{const j=O&&O.length!==I;return N&&!k&&!j}),Fe((,,N,k,I,O,j,H,q,M,B,$,W)=>{var G,z,K,Q;const re=O,{offsetTree:ae,sizeTree:de}=re,Ne=An(_);if(I===0)return{...sS,totalCount:I};if(N===0&&k===0)return Ne===0?{...sS,totalCount:I}:QW(Ne,j,O,M,B,W||);if(dn(de))return Ne>0?null:j0(gge(nP(j,I),re,W),,I,B,re,M);const ye=;if(q.length>0){const pe=q0,he=qq.length-1;let we=0;for(const Ie of uf(de,pe,he)){const Ce=Ie.value,Me=Math.max(Ie.start,pe),ze=Math.min(Ie.end,he);for(let Ye=Me;Ye<=ze;Ye++)ye.push({data:W==null?void 0:WYe,index:Ye,offset:we,size:Ce}),we+=Ce}}if(!H)return j0(,ye,I,B,re,M);const fe=q.length>0?qq.length-1+1:0,Z=oge(ae,N,k,fe);if(Z.length===0)return null;const oe=I-1,ce=Xx(,pe=>{for(const he of Z){const we=he.value;let Ie=we.offset,Ce=he.start;const Me=we.size;if(we.offset<N){Ce+=Math.floor((N-we.offset+B)/(Me+B));const Ye=Ce-he.start;Ie+=Ye*Me+Ye*B}Ce<fe&&(Ie+=(fe-Ce)*Me,Ce=fe);const ze=Math.min(he.end,oe);for(let Ye=Ce;Ye<=ze&&!(Ie>=k);Ye++)pe.push({data:W==null?void 0:WYe,index:Ye,offset:Ie,size:Me}),Ie+=Me+B}}),xe=nD($,Am),ge=nD($,Mm);if(ce.length>0&&(xe>0||ge>0)){const pe=ce0,he=cece.length-1;if(xe>0&&pe.index>fe){const we=Math.min(xe,pe.index-fe),Ie=;let Ce=pe.offset;for(let Me=pe.index-1;Me>=pe.index-we;Me--){const ze=(z=(G=uf(de,Me,Me)0)==null?void 0:G.value)!=null?z:pe.size;Ce-=ze+B,Ie.unshift({data:W==null?void 0:WMe,index:Me,offset:Ce,size:ze})}ce.unshift(...Ie)}if(ge>0&&he.index<oe){const we=Math.min(ge,oe-he.index);let Ie=he.offset+he.size+B;for(let Ce=he.index+1;Ce<=he.index+we;Ce++){const Me=(Q=(K=uf(de,Ce,Ce)0)==null?void 0:K.value)!=null?Q:he.size;ce.push({data:W==null?void 0:WCe,index:Ce,offset:Ie,size:Me}),Ie+=Me+B}}}return j0(ce,ye,I,B,re,M)}),it(N=>N!==null),Cn()),sS);Ge(_e(e,it(Xk),Fe(N=>N==null?void 0:N.length)),o),Ge(_e(C,Fe(N=>N.topListHeight)),f),Ge(f,a),Ge(_e(C,Fe(N=>N.top,N.bottom)),s),Ge(_e(C,Fe(N=>N.items)),x);const E=ti(_e(C,it(({items:N})=>N.length>0),Et(o,e),it(({items:N},k)=>NN.length-1.originalIndex===k-1),Fe((,N,k)=>N-1,k),Cn(km),Fe((N)=>N))),R=ti(_e(C,Ba(200),it(({items:N,topItems:k})=>N.length>0&&N0.originalIndex===k.length),Fe(({items:N})=>N0.index),Cn())),P=ti(_e(C,it(({items:N})=>N.length>0),Fe(({items:N})=>{let k=0,I=N.length-1;for(;Nk.type==="group"&&k<I;)k++;for(;NI.type==="group"&&I>k;)I--;return{endIndex:NI.index,startIndex:Nk.index}}),Cn(qW)));return{endReached:E,initialItemCount:_,itemsRendered:x,listState:C,minOverscanItemCount:w,rangeChanged:P,startReached:R,topItemsIndexes:b,...p}},Tn(ca,KW,rP,tg,eg,ng,Ec,Jk),{singleton:!0}),JW=Kt(({fixedFooterHeight:e,fixedHeaderHeight:t,footerHeight:n,headerHeight:r},{listState:o})=>{const i=Ut(),s=Qr(_e(cr(n,e,r,t,o),Fe((a,l,c,d,f)=>a+l+c+d+f.offsetBottom+f.bottom)),0);return Ge(ot(s),i),{totalListHeight:s,totalListHeightChanged:i}},Tn(go,qu),{singleton:!0}),vge=Kt(({viewportHeight:e},{totalListHeight:t})=>{const n=ke(!1),r=Qr(_e(cr(n,e,t),it((o)=>o),Fe((,o,i)=>Math.max(0,o-i)),Ba(0),Cn()),0);return{alignToBottom:n,paddingTopAddition:r}},Tn(go,JW),{singleton:!0}),e9=Kt(()=>({context:ke(null)})),yge=({itemBottom:e,itemTop:t,locationParams:{align:n,behavior:r,...o},viewportBottom:i,viewportTop:s})=>t<s?{...o,align:n??"start",behavior:r}:e>i?{...o,align:n??"end",behavior:r}:null,t9=Kt(({gap:e,sizes:t,totalCount:n},{fixedFooterHeight:r,fixedHeaderHeight:o,headerHeight:i,scrollingInProgress:s,scrollTop:a,viewportHeight:l},{scrollToIndex:c})=>{const d=Ut();return Ge(_e(d,Et(t,l,n,i,o,r,a),Et(e),Fe((f,p,g,v,b,_,x,w,C)=>{const{align:E,behavior:R,calculateViewLocation:P=yge,done:N,...k}=f,I=YW(f,p,v-1),O=Pm(I,p.offsetTree,C)+b+_,j=O+ps(p.sizeTree,I)1,H=w+_,q=w+g-x,M=P({itemBottom:j,itemTop:O,locationParams:{align:E,behavior:R,...k},viewportBottom:q,viewportTop:H});return M?N&&ds(_e(s,it(B=>!B),Fu(An(s)?1:2)),N):N==null||N(),M}),it(f=>f!==null)),c),{scrollIntoView:d}},Tn(ca,go,eg,qu,Cc),{singleton:!0});function rD(e){return e?e==="smooth"?"smooth":"auto":!1}const xge=(e,t)=>typeof e=="function"?rD(e(t)):t&&rD(e),bge=Kt(({listRefresh:e,totalCount:t,fixedItemSize:n,data:r},{atBottomState:o,isAtBottom:i},{scrollToIndex:s},{scrolledToInitialItem:a},{didMount:l,propsReady:c},{log:d},{scrollingInProgress:f},{context:p},{scrollIntoView:g})=>{const v=ke(!1),b=Ut();let _=null;function x(R){vt(s,{align:"end",behavior:R,index:"LAST"})}tn(_e(cr(_e(ot(t),Fu(1)),l),Et(ot(v),i,a,f),Fe((R,P,N,k,I,O)=>{let j=P&&I,H="auto";return j&&(H=xge(N,k||O),j=j&&!!H),{followOutputBehavior:H,shouldFollow:j,totalCount:R}}),it(({shouldFollow:R})=>R)),({followOutputBehavior:R,totalCount:P})=>{_&&(_(),_=null),An(n)?requestAnimationFrame(()=>{An(d)("following output to ",{totalCount:P},Ao.DEBUG),x(R)}):_=ds(e,()=>{An(d)("following output to ",{totalCount:P},Ao.DEBUG),x(R),_=null})});function w(R){const P=ds(o,N=>{R&&!N.atBottom&&N.notAtBottomBecause==="SIZE_INCREASED"&&!_&&(An(d)("scrolling to bottom due to increased size",{},Ao.DEBUG),x("auto"))});setTimeout(P,100)}tn(_e(cr(ot(v),t,c),it((R,,P)=>R&&P),Qs(({value:R},,P)=>({refreshed:R===P,value:P}),{refreshed:!1,value:0}),it(({refreshed:R})=>R),Et(v,t)),(,R)=>{An(a)&&w(R!==!1)}),tn(b,()=>{w(An(v)!==!1)}),tn(cr(ot(v),o),(R,P)=>{R&&!P.atBottom&&P.notAtBottomBecause==="VIEWPORT_HEIGHT_DECREASING"&&x("auto")});const C=ke(null),E=Ut();return Ge(PR(_e(ot(r),Fe(R=>{var P;return(P=R==null?void 0:R.length)!=null?P:0})),_e(ot(t))),E),tn(_e(cr(_e(E,Fu(1)),l),Et(ot(C),a,f,p),Fe((R,P,N,k,I,O)=>P&&k&&(N==null?void 0:N({context:O,totalCount:R,scrollingInProgress:I}))),it(R=>!!R),Ba(0)),R=>{_&&(_(),_=null),An(n)?requestAnimationFrame(()=>{An(d)("scrolling into view",{}),vt(g,R)}):_=ds(e,()=>{An(d)("scrolling into view",{}),vt(g,R),_=null})}),{autoscrollToBottom:b,followOutput:v,scrollIntoViewOnChange:C}},Tn(ca,ng,eg,tg,Ec,Cc,go,e9,t9)),wge=Kt(({data:e,firstItemIndex:t,gap:n,sizes:r},{initialTopMostItemIndex:o},{initialItemCount:i,listState:s},{didMount:a})=>(Ge(_e(a,Et(i),it((,l)=>l!==0),Et(o,r,t,n,e),Fe((,l,c,d,f,p,g=)=>QW(l,c,d,f,p,g))),s),{}),Tn(ca,tg,qu,Ec),{singleton:!0}),Sge=Kt(({didMount:e},{scrollTo:t},{listState:n})=>{const r=ke(0);return tn(_e(e,Et(r),it((,o)=>o!==0),Fe((,o)=>({top:o}))),o=>{ds(_e(n,Fu(1),it(i=>i.items.length>1)),()=>{requestAnimationFrame(()=>{vt(t,o)})})}),{initialScrollTop:r}},Tn(Ec,go,qu),{singleton:!0}),n9=Kt(({scrollVelocity:e})=>{const t=ke(!1),n=Ut(),r=ke(!1);return Ge(_e(e,Et(r,t,n),it((o,i)=>!!i),Fe((o,i,s,a)=>{const{enter:l,exit:c}=i;if(s){if(c(o,a))return!1}else if(l(o,a))return!0;return s}),Cn()),t),tn(_e(cr(t,e,n),Et(r)),(o,i,s,a)=>{o&&a&&a.change&&a.change(i,s)}),{isSeeking:t,scrollSeekConfiguration:r,scrollSeekRangeChanged:n,scrollVelocity:e}},Tn(ng),{singleton:!0}),oP=Kt(({scrollContainerState:e,scrollTo:t})=>{const n=Ut(),r=Ut(),o=Ut(),i=ke(!1),s=ke(void 0);return Ge(_e(cr(n,r),Fe(({scrollHeight:a,scrollTop:l,viewportHeight:c},{offsetTop:d})=>({scrollHeight:a,scrollTop:Math.max(0,l-d),viewportHeight:c}))),e),Ge(_e(t,Et(r),Fe((a,{offsetTop:l})=>({...a,top:a.top+l}))),o),{customScrollParent:s,useWindowScroll:i,windowScrollContainerState:n,windowScrollTo:o,windowViewportRect:r}},Tn(go)),_ge=Kt(({sizeRanges:e,sizes:t},{headerHeight:n,scrollTop:r},{initialTopMostItemIndex:o},{didMount:i},{useWindowScroll:s,windowScrollContainerState:a,windowViewportRect:l})=>{const c=Ut(),d=ke(void 0),f=ke(null),p=ke(null);return Ge(a,f),Ge(l,p),tn(_e(c,Et(t,r,s,f,p,n)),(g,v,b,_,x,w,C)=>{const E=sge(v.sizeTree);_&&x!==null&&w!==null&&(b=x.scrollTop-w.offsetTop),b-=C,g({ranges:E,scrollTop:b})}),Ge(_e(d,it(Xk),Fe(Cge)),o),Ge(_e(i,Et(d),it((,g)=>g!==void 0),Cn(),Fe((,g)=>g.ranges)),e),{getState:c,restoreStateFrom:d}},Tn(ca,go,tg,Ec,oP));function Cge(e){return{align:"start",index:0,offset:e.scrollTop}}const Ege=Kt(({topItemsIndexes:e})=>{const t=ke(0);return Ge(_e(t,it(n=>n>=0),Fe(n=>Array.from({length:n}).map((r,o)=>o))),e),{topItemCount:t}},Tn(qu));function r9(e){let t=!1,n;return()=>(t||(t=!0,n=e()),n)}const Nge=r9(()=>/iP(ad|od|hone)/i.test(navigator.userAgent)&&/WebKit/i.test(navigator.userAgent)),Rge=Kt(({deviation:e,scrollBy:t,scrollingInProgress:n,scrollTop:r},{isAtBottom:o,isScrolling:i,lastJumpDueToItemResize:s,scrollDirection:a},{listState:l},{beforeUnshiftWith:c,gap:d,shiftWithOffset:f,sizes:p},{log:g},{recalcInProgress:v})=>{const b=ti(_e(l,Et(s),Qs((,x,w,C,{bottom:E,items:R,offsetBottom:P,totalCount:N},k)=>{const I=E+P;let O=0;return w===N&&x.length>0&&R.length>0&&(R0.originalIndex===0&&x0.originalIndex===0||(O=I-C,O!==0&&(O+=k))),O,R,N,I},0,,0,0),it((x)=>x!==0),Et(r,a,n,o,g,v),it((,x,w,C,,,E)=>!E&&!C&&x!==0&&w===Im),Fe((x,,,,,w)=>(w("Upward scrolling compensation",{amount:x},Ao.DEBUG),x))));function _(x){x>0?(vt(t,{behavior:"auto",top:-x}),vt(e,0)):(vt(e,0),vt(t,{behavior:"auto",top:-x}))}return tn(_e(b,Et(e,i)),(x,w,C)=>{C&&Nge()?vt(e,w-x):_(-x)}),tn(_e(cr(Qr(i,!1),e,v),it((x,w,C)=>!x&&!C&&w!==0),Fe((x,w)=>w),Ba(1)),_),Ge(_e(f,Fe(x=>({top:-x}))),t),tn(_e(c,Et(p,d),Fe((x,{groupIndices:w,lastSize:C,sizeTree:E},R)=>{function P(N){return N*(C+R)}if(w.length===0)return P(x);{let N=0;const k=Tm(E,0);let I=0,O=0;for(;I<x;){I++,N+=k;let j=w.length===O+1?1/0:wO+1-wO-1;I+j>x&&(N-=k,j=x-I+1),I+=j,N+=P(j),O++}return N}})),x=>{vt(e,x),requestAnimationFrame(()=>{vt(t,{top:x}),requestAnimationFrame(()=>{vt(e,0),vt(v,!1)})})}),{deviation:e}},Tn(go,ng,qu,ca,Cc,Jk)),Tge=Kt((e,t,n,r,o,i,s,a,l,c,d)=>({...e,...t,...n,...r,...o,...i,...s,...a,...l,...c,...d}),Tn(rP,wge,Ec,n9,JW,Sge,vge,oP,t9,Cc,e9)),o9=Kt(({data:e,defaultItemSize:t,firstItemIndex:n,fixedItemSize:r,fixedGroupSize:o,gap:i,groupIndices:s,heightEstimates:a,itemSize:l,sizeRanges:c,sizes:d,statefulTotalCount:f,totalCount:p,trackItemSizes:g},{initialItemFinalLocationReached:v,initialTopMostItemIndex:b,scrolledToInitialItem:_},x,w,C,E,{scrollToIndex:R},P,{topItemCount:N},{groupCounts:k},I)=>{const{listState:O,minOverscanItemCount:j,topItemsIndexes:H,rangeChanged:q,...M}=E;return Ge(q,I.scrollSeekRangeChanged),Ge(_e(I.windowViewportRect,Fe(B=>B.visibleHeight)),x.viewportHeight),{data:e,defaultItemHeight:t,firstItemIndex:n,fixedItemHeight:r,fixedGroupHeight:o,gap:i,groupCounts:k,heightEstimates:a,initialItemFinalLocationReached:v,initialTopMostItemIndex:b,scrolledToInitialItem:_,sizeRanges:c,topItemCount:N,topItemsIndexes:H,totalCount:p,...C,groupIndices:s,itemSize:l,listState:O,minOverscanItemCount:j,scrollToIndex:R,statefulTotalCount:f,trackItemSizes:g,rangeChanged:q,...M,...I,...x,sizes:d,...w}},Tn(ca,tg,go,_ge,bge,qu,eg,Rge,Ege,KW,Tge));function kge(e,t){const n={},r={};let o=0;const i=e.length;for(;o<i;)reo=1,o+=1;for(const s in t)Object.hasOwn(r,s)||(ns=ts);return n}const Xv=typeof document<"u"?Pe.useLayoutEffect:Pe.useEffect;function i9(e,t,n){const r=Object.keys(t.required||{}),o=Object.keys(t.optional||{}),i=Object.keys(t.methods||{}),s=Object.keys(t.events||{}),a=Pe.createContext({});function l(_,x){_.propsReady&&vt(_.propsReady,!1);for(const w of r){const C=_t.requiredw;vt(C,xw)}for(const w of o)if(w in x){const C=_t.optionalw;vt(C,xw)}_.propsReady&&vt(_.propsReady,!0)}function c(_){return i.reduce((x,w)=>(xw=C=>{const E=_t.methodsw;vt(E,C)},x),{})}function d(_){return s.reduce((x,w)=>(xw=qme(_t.eventsw),x),{})}const f=Pe.forwardRef((_,x)=>{const{children:w,...C}=_,E=Pe.useState(()=>Xx(Vme(e),N=>{l(N,C)})),R=Pe.useState(VO(d,E));Xv(()=>{for(const N of s)N in C&&tn(RN,CN);return()=>{Object.values(R).map(Zk)}},C,R,E),Xv(()=>{l(E,C)}),Pe.useImperativeHandle(x,GO(c(E)));const P=n;return h.jsx(a.Provider,{value:E,children:n?h.jsx(P,{...kge(...r,...o,...s,C),children:w}):w})}),p=_=>{const x=Pe.useContext(a);return Pe.useCallback(w=>{vt(x_,w)},x,_)},g=_=>{const x=Pe.useContext(a)_,w=Pe.useCallback(C=>tn(x,C),x);return Pe.useSyncExternalStore(w,()=>An(x),()=>An(x))},v=_=>{const x=Pe.useContext(a)_,w,C=Pe.useState(VO(An,x));return Xv(()=>tn(x,E=>{E!==w&&C(GO(E))}),x,w),w},b=Pe.version.startsWith("18")?g:v;return{Component:f,useEmitter:(_,x)=>{const w=Pe.useContext(a)_;Xv(()=>tn(w,x),x,w)},useEmitterValue:b,usePublisher:p}}const s9=Pe.createContext(void 0),a9=Pe.createContext(void 0),l9=typeof document<"u"?Pe.useLayoutEffect:Pe.useEffect;function aS(e){return"self"in e}function Pge(e){return"body"in e}function c9(e,t,n,r=Zf,o,i){const s=Pe.useRef(null),a=Pe.useRef(null),l=Pe.useRef(null),c=Pe.useCallback(p=>{let g,v,b;const _=p.target;if(Pge(_)||aS(_)){const w=aS(_)?_:_.defaultView;b=i?w.scrollX:w.scrollY,g=i?w.document.documentElement.scrollWidth:w.document.documentElement.scrollHeight,v=i?w.innerWidth:w.innerHeight}else b=i?_.scrollLeft:_.scrollTop,g=i?_.scrollWidth:_.scrollHeight,v=i?_.offsetWidth:_.offsetHeight;const x=()=>{e({scrollHeight:g,scrollTop:Math.max(b,0),viewportHeight:v})};p.suppressFlushSync?x():hB.flushSync(x),a.current!==null&&(b===a.current||b<=0||b===g-v)&&(a.current=null,t(!0),l.current&&(clearTimeout(l.current),l.current=null))},e,t,i);Pe.useEffect(()=>{const p=o||s.current;return r(o||s.current),c({suppressFlushSync:!0,target:p}),p.addEventListener("scroll",c,{passive:!0}),()=>{r(null),p.removeEventListener("scroll",c)}},s,c,n,r,o);function d(p){const g=s.current;if(!g||(i?"offsetWidth"in g&&g.offsetWidth===0:"offsetHeight"in g&&g.offsetHeight===0))return;const v=p.behavior==="smooth";let b,_,x;aS(g)?(_=Math.max(pc(g.document.documentElement,i?"width":"height"),i?g.document.documentElement.scrollWidth:g.document.documentElement.scrollHeight),b=i?g.innerWidth:g.innerHeight,x=i?window.scrollX:window.scrollY):(_=gi?"scrollWidth":"scrollHeight",b=pc(g,i?"width":"height"),x=gi?"scrollLeft":"scrollTop");const w=_-b;if(p.top=Math.ceil(Math.max(Math.min(w,p.top),0)),ZW(b,_)||p.top===x){e({scrollHeight:_,scrollTop:x,viewportHeight:b}),v&&t(!0);return}v?(a.current=p.top,l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{l.current=null,a.current=null,t(!0)},1e3)):a.current=null,i&&(p={behavior:p.behavior,left:p.top}),g.scrollTo(p)}function f(p){i&&(p={behavior:p.behavior,left:p.top}),s.current.scrollBy(p)}return{scrollByCallback:f,scrollerRef:s,scrollToCallback:d}}const lS="-webkit-sticky",oD="sticky",iP=r9(()=>{if(typeof document>"u")return oD;const e=document.createElement("div");return e.style.position=lS,e.style.position===lS?lS:oD});function sP(e){return e}const Ige=Kt(()=>{const e=ke(a=>`Item ${a}`),t=ke(a=>`Group ${a}`),n=ke({}),r=ke(sP),o=ke("div"),i=ke(Zf),s=(a,l=null)=>Qr(_e(n,Fe(c=>ca),Cn()),l);return{components:n,computeItemKey:r,EmptyPlaceholder:s("EmptyPlaceholder"),FooterComponent:s("Footer"),GroupComponent:s("Group","div"),groupContent:t,HeaderComponent:s("Header"),HeaderFooterTag:o,ItemComponent:s("Item","div"),itemContent:e,ListComponent:s("List","div"),ScrollerComponent:s("Scroller","div"),scrollerRef:i,ScrollSeekPlaceholder:s("ScrollSeekPlaceholder"),TopItemListComponent:s("TopItemList")}}),Age=Kt((e,t)=>({...e,...t}),Tn(o9,Ige)),Mge=({height:e})=>h.jsx("div",{style:{height:e}}),jge={overflowAnchor:"none",position:iP(),zIndex:1},u9={overflowAnchor:"none"},Lge={...u9,display:"inline-block",height:"100%"},iD=Pe.memo(function({showTopList:e=!1}){const t=ct("listState"),n=Pi("sizeRanges"),r=ct("useWindowScroll"),o=ct("customScrollParent"),i=Pi("windowScrollContainerState"),s=Pi("scrollContainerState"),a=o||r?i:s,l=ct("itemContent"),c=ct("context"),d=ct("groupContent"),f=ct("trackItemSizes"),p=ct("itemSize"),g=ct("log"),v=Pi("gap"),b=ct("horizontalDirection"),{callbackRef:_}=Xme(n,p,f,e?Zf:a,g,v,o,b,ct("skipAnimationFrameInResizeObserver")),x,w=Pe.useState(0);aP("deviation",M=>{x!==M&&w(M)});const C=ct("EmptyPlaceholder"),E=ct("ScrollSeekPlaceholder")||Mge,R=ct("ListComponent"),P=ct("ItemComponent"),N=ct("GroupComponent"),k=ct("computeItemKey"),I=ct("isSeeking"),O=ct("groupIndices").length>0,j=ct("alignToBottom"),H=ct("initialItemFinalLocationReached"),q=e?{}:{boxSizing:"border-box",...b?{display:"inline-block",height:"100%",marginLeft:x!==0?x:j?"auto":0,paddingLeft:t.offsetTop,paddingRight:t.offsetBottom,whiteSpace:"nowrap"}:{marginTop:x!==0?x:j?"auto":0,paddingBottom:t.offsetBottom,paddingTop:t.offsetTop},...H?{}:{visibility:"hidden"}};return!e&&t.totalCount===0&&C?h.jsx(C,{...Xr(C,c)}):h.jsx(R,{...Xr(R,c),"data-testid":e?"virtuoso-top-item-list":"virtuoso-item-list",ref:_,style:q,children:(e?t.topItems:t.items).map(M=>{const B=M.originalIndex,$=k(B+t.firstItemIndex,M.data,c);return I?y.createElement(E,{...Xr(E,c),height:M.size,index:M.index,key:$,type:M.type||"item",...M.type==="group"?{}:{groupIndex:M.groupIndex}}):M.type==="group"?y.createElement(N,{...Xr(N,c),"data-index":B,"data-item-index":M.index,"data-known-size":M.size,key:$,style:jge},d(M.index,c)):y.createElement(P,{...Xr(P,c),...$ge(P,M.data),"data-index":B,"data-item-group-index":M.groupIndex,"data-item-index":M.index,"data-known-size":M.size,key:$,style:b?Lge:u9},O?l(M.index,M.groupIndex,M.data,c):l(M.index,M.data,c))})})}),Oge={height:"100%",outline:"none",overflowY:"auto",position:"relative",WebkitOverflowScrolling:"touch"},Dge={outline:"none",overflowX:"auto",position:"relative"},Qx=e=>({height:"100%",position:"absolute",top:0,width:"100%",...e?{display:"flex",flexDirection:"column"}:{}}),Fge={position:iP(),top:0,width:"100%",zIndex:1};function Xr(e,t){if(typeof e!="string")return{context:t}}function $ge(e,t){return{item:typeof e=="string"?void 0:t}}const zge=Pe.memo(function(){const e=ct("HeaderComponent"),t=Pi("headerHeight"),n=ct("HeaderFooterTag"),r=Uu(Pe.useMemo(()=>i=>{t(pc(i,"height"))},t),!0,ct("skipAnimationFrameInResizeObserver")),o=ct("context");return e?h.jsx(n,{ref:r,children:h.jsx(e,{...Xr(e,o)})}):null}),Hge=Pe.memo(function(){const e=ct("FooterComponent"),t=Pi("footerHeight"),n=ct("HeaderFooterTag"),r=Uu(Pe.useMemo(()=>i=>{t(pc(i,"height"))},t),!0,ct("skipAnimationFrameInResizeObserver")),o=ct("context");return e?h.jsx(n,{ref:r,children:h.jsx(e,{...Xr(e,o)})}):null});function d9({useEmitter:e,useEmitterValue:t,usePublisher:n}){return Pe.memo(function({children:r,style:o,context:i,...s}){const a=n("scrollContainerState"),l=t("ScrollerComponent"),c=n("smoothScrollTargetReached"),d=t("scrollerRef"),f=t("horizontalDirection")||!1,{scrollByCallback:p,scrollerRef:g,scrollToCallback:v}=c9(a,c,l,d,void 0,f);return e("scrollTo",v),e("scrollBy",p),h.jsx(l,{"data-testid":"virtuoso-scroller","data-virtuoso-scroller":!0,ref:g,style:{...f?Dge:Oge,...o},tabIndex:0,...s,...Xr(l,i),children:r})})}function f9({useEmitter:e,useEmitterValue:t,usePublisher:n}){return Pe.memo(function({children:r,style:o,context:i,...s}){const a=n("windowScrollContainerState"),l=t("ScrollerComponent"),c=n("smoothScrollTargetReached"),d=t("totalListHeight"),f=t("deviation"),p=t("customScrollParent"),g=Pe.useRef(null),v=t("scrollerRef"),{scrollByCallback:b,scrollerRef:_,scrollToCallback:x}=c9(a,c,l,v,p);return l9(()=>{var w;return _.current=p||((w=g.current)==null?void 0:w.ownerDocument.defaultView),()=>{_.current=null}},_,p),e("windowScrollTo",x),e("scrollBy",b),h.jsx(l,{ref:g,"data-virtuoso-scroller":!0,style:{position:"relative",...o,...d!==0?{height:d+f}:{}},...s,...Xr(l,i),children:r})})}const Bge=({children:e})=>{const t=Pe.useContext(s9),n=Pi("viewportHeight"),r=Pi("fixedItemHeight"),o=ct("alignToBottom"),i=ct("horizontalDirection"),s=Pe.useMemo(()=>DW(n,l=>pc(l,i?"width":"height")),n,i),a=Uu(s,!0,ct("skipAnimationFrameInResizeObserver"));return Pe.useEffect(()=>{t&&(n(t.viewportHeight),r(t.itemHeight))},t,n,r),h.jsx("div",{"data-viewport-type":"element",ref:a,style:Qx(o),children:e})},Wge=({children:e})=>{const t=Pe.useContext(s9),n=Pi("windowViewportRect"),r=Pi("fixedItemHeight"),o=ct("customScrollParent"),i=$W(n,o,ct("skipAnimationFrameInResizeObserver")),s=ct("alignToBottom");return Pe.useEffect(()=>{t&&(r(t.itemHeight),n({offsetTop:0,visibleHeight:t.viewportHeight,visibleWidth:100}))},t,n,r),h.jsx("div",{"data-viewport-type":"window",ref:i,style:Qx(s),children:e})},Uge=({children:e})=>{const t=ct("TopItemListComponent")||"div",n=ct("headerHeight"),r={...Fge,marginTop:`${n}px`},o=ct("context");return h.jsx(t,{style:r,...Xr(t,o),children:e})},qge=Pe.memo(function(e){const t=ct("useWindowScroll"),n=ct("topItemsIndexes").length>0,r=ct("customScrollParent"),o=ct("context");return h.jsxs(r||t?Yge:Vge,{...e,context:o,children:n&&h.jsx(Uge,{children:h.jsx(iD,{showTopList:!0})}),h.jsxs(r||t?Wge:Bge,{children:h.jsx(zge,{}),h.jsx(iD,{}),h.jsx(Hge,{})})})}),{Component:Gge,useEmitter:aP,useEmitterValue:ct,usePublisher:Pi}=i9(Age,{required:{},optional:{restoreStateFrom:"restoreStateFrom",context:"context",followOutput:"followOutput",scrollIntoViewOnChange:"scrollIntoViewOnChange",itemContent:"itemContent",groupContent:"groupContent",overscan:"overscan",increaseViewportBy:"increaseViewportBy",minOverscanItemCount:"minOverscanItemCount",totalCount:"totalCount",groupCounts:"groupCounts",topItemCount:"topItemCount",firstItemIndex:"firstItemIndex",initialTopMostItemIndex:"initialTopMostItemIndex",components:"components",atBottomThreshold:"atBottomThreshold",atTopThreshold:"atTopThreshold",computeItemKey:"computeItemKey",defaultItemHeight:"defaultItemHeight",fixedGroupHeight:"fixedGroupHeight",fixedItemHeight:"fixedItemHeight",heightEstimates:"heightEstimates",itemSize:"itemSize",scrollSeekConfiguration:"scrollSeekConfiguration",headerFooterTag:"HeaderFooterTag",data:"data",initialItemCount:"initialItemCount",initialScrollTop:"initialScrollTop",alignToBottom:"alignToBottom",useWindowScroll:"useWindowScroll",customScrollParent:"customScrollParent",scrollerRef:"scrollerRef",logLevel:"logLevel",horizontalDirection:"horizontalDirection",skipAnimationFrameInResizeObserver:"skipAnimationFrameInResizeObserver"},methods:{scrollToIndex:"scrollToIndex",scrollIntoView:"scrollIntoView",scrollTo:"scrollTo",scrollBy:"scrollBy",autoscrollToBottom:"autoscrollToBottom",getState:"getState"},events:{isScrolling:"isScrolling",endReached:"endReached",startReached:"startReached",rangeChanged:"rangeChanged",atBottomStateChange:"atBottomStateChange",atTopStateChange:"atTopStateChange",totalListHeightChanged:"totalListHeightChanged",itemsRendered:"itemsRendered",groupIndices:"groupIndices"}},qge),Vge=d9({useEmitter:aP,useEmitterValue:ct,usePublisher:Pi}),Yge=f9({useEmitter:aP,useEmitterValue:ct,usePublisher:Pi}),Kge=Gge,Xge=Kt(()=>{const e=ke(c=>h.jsxs("td",{children:"Item $",c})),t=ke(null),n=ke(c=>h.jsxs("td",{colSpan:1e3,children:"Group ",c})),r=ke(null),o=ke(null),i=ke({}),s=ke(sP),a=ke(Zf),l=(c,d=null)=>Qr(_e(i,Fe(f=>fc),Cn()),d);return{components:i,computeItemKey:s,context:t,EmptyPlaceholder:l("EmptyPlaceholder"),FillerRow:l("FillerRow"),fixedFooterContent:o,fixedHeaderContent:r,itemContent:e,groupContent:n,ScrollerComponent:l("Scroller","div"),scrollerRef:a,ScrollSeekPlaceholder:l("ScrollSeekPlaceholder"),TableBodyComponent:l("TableBody","tbody"),TableComponent:l("Table","table"),TableFooterComponent:l("TableFoot","tfoot"),TableHeadComponent:l("TableHead","thead"),TableRowComponent:l("TableRow","tr"),GroupComponent:l("Group","tr")}});Tn(o9,Xge);iP();const sD={bottom:0,itemHeight:0,items:,itemWidth:0,offsetBottom:0,offsetTop:0,top:0},Zge={bottom:0,itemHeight:0,items:{index:0},itemWidth:0,offsetBottom:0,offsetTop:0,top:0},{ceil:aD,floor:Ly,max:Gp,min:cS,round:lD}=Math;function cD(e,t,n){return Array.from({length:t-e+1}).map((r,o)=>({data:n===null?null:no+e,index:o+e}))}function Qge(e){return{...Zge,items:e}}function Zv(e,t){return e&&e.width===t.width&&e.height===t.height}function Jge(e,t){return e&&e.column===t.column&&e.row===t.row}const eve=Kt(({increaseViewportBy:e,listBoundary:t,overscan:n,visibleRange:r},{footerHeight:o,headerHeight:i,scrollBy:s,scrollContainerState:a,scrollTo:l,scrollTop:c,smoothScrollTargetReached:d,viewportHeight:f},p,g,{didMount:v,propsReady:b},{customScrollParent:_,useWindowScroll:x,windowScrollContainerState:w,windowScrollTo:C,windowViewportRect:E},R)=>{const P=ke(0),N=ke(0),k=ke(sD),I=ke({height:0,width:0}),O=ke({height:0,width:0}),j=Ut(),H=Ut(),q=ke(0),M=ke(null),B=ke({column:0,row:0}),$=Ut(),W=Ut(),G=ke(!1),z=ke(0),K=ke(!0),Q=ke(!1),re=ke(!1);tn(_e(v,Et(z),it((Z,oe)=>!!oe)),()=>{vt(K,!1)}),tn(_e(cr(v,K,O,I,z,Q),it((Z,oe,ce,xe,,ge)=>Z&&!oe&&ce.height!==0&&xe.height!==0&&!ge)),(,,,,Z)=>{vt(Q,!0),tP(1,()=>{vt(j,Z)}),ds(_e(c),()=>{vt(t,0,0),vt(K,!0)})}),Ge(_e(W,it(Z=>Z!=null&&Z.scrollTop>0),Vs(0)),N),tn(_e(v,Et(W),it((,Z)=>Z!=null)),(,Z)=>{Z&&(vt(I,Z.viewport),vt(O,Z.item),vt(B,Z.gap),Z.scrollTop>0&&(vt(G,!0),ds(_e(c,Fu(1)),oe=>{vt(G,!1)}),vt(l,{top:Z.scrollTop})))}),Ge(_e(I,Fe(({height:Z})=>Z)),f),Ge(_e(cr(ot(I,Zv),ot(O,Zv),ot(B,(Z,oe)=>Z&&Z.column===oe.column&&Z.row===oe.row),ot(c)),Fe((Z,oe,ce,xe)=>({gap:ce,item:oe,scrollTop:xe,viewport:Z}))),$),Ge(_e(cr(ot(P),r,ot(B,Jge),ot(O,Zv),ot(I,Zv),ot(M),ot(N),ot(G),ot(K),ot(z)),it((,,,,,,,Z)=>!Z),Fe((Z,oe,ce,xe,ge,pe,he,we,,Ie,Ce)=>{const{column:Me,row:ze}=xe,{height:Ye,width:Ht}=ge,{width:Ft}=pe;if(we===0&&(Z===0||Ft===0))return sD;if(Ht===0){const Sr=nP(Ce,Z),kn=Sr+Math.max(we-1,0);return Qge(cD(Sr,kn,he))}const rt=h9(Ft,Ht,Me);let Ue,Te;Ie?oe===0&&ce===0&&we>0?(Ue=0,Te=we-1):(Ue=rt*Ly((oe+ze)/(Ye+ze)),Te=rt*aD((ce+ze)/(Ye+ze))-1,Te=cS(Z-1,Gp(Te,rt-1)),Ue=cS(Te,Gp(0,Ue))):(Ue=0,Te=-1);const bt=cD(Ue,Te,he),{bottom:jt,top:vn}=uD(pe,xe,ge,bt),dr=aD(Z/rt),On=dr*Ye+(dr-1)*ze-jt;return{bottom:jt,itemHeight:Ye,items:bt,itemWidth:Ht,offsetBottom:On,offsetTop:vn,top:vn}})),k),Ge(_e(M,it(Z=>Z!==null),Fe(Z=>Z.length)),P),Ge(_e(cr(I,O,k,B),it((Z,oe,{items:ce})=>ce.length>0&&oe.height!==0&&Z.height!==0),Fe((Z,oe,{items:ce},xe)=>{const{bottom:ge,top:pe}=uD(Z,xe,oe,ce);returnpe,ge}),Cn(km)),t);const ae=ke(!1);Ge(_e(c,Et(ae),Fe((Z,oe)=>oe||Z!==0)),ae);const de=ti(_e(cr(k,P),it(({items:Z})=>Z.length>0),Et(ae),it((Z,oe,ce)=>{const xe=Z.itemsZ.items.length-1.index===oe-1;return(ce||Z.bottom>0&&Z.itemHeight>0&&Z.offsetBottom===0&&Z.items.length===oe)&&xe}),Fe((,Z)=>Z-1),Cn())),Ne=ti(_e(ot(k),it(({items:Z})=>Z.length>0&&Z0.index===0),Vs(0),Cn())),ye=ti(_e(ot(k),Et(G),it(({items:Z},oe)=>Z.length>0&&!oe),Fe(({items:Z})=>({endIndex:ZZ.length-1.index,startIndex:Z0.index})),Cn(qW),Ba(0)));Ge(ye,g.scrollSeekRangeChanged),Ge(_e(j,Et(I,O,P,B),Fe((Z,oe,ce,xe,ge)=>{const pe=XW(Z),{align:he,behavior:we,offset:Ie}=pe;let Ce=pe.index;Ce==="LAST"&&(Ce=xe-1),Ce=Gp(0,Ce,cS(xe-1,Ce));let Me=LR(oe,ge,ce,Ce);return he==="end"?Me=lD(Me-oe.height+ce.height):he==="center"&&(Me=lD(Me-oe.height/2+ce.height/2)),Ie&&(Me+=Ie),{behavior:we,top:Me}})),l);const fe=Qr(_e(k,Fe(Z=>Z.offsetBottom+Z.bottom)),0);return Ge(_e(E,Fe(Z=>({height:Z.visibleHeight,width:Z.visibleWidth}))),I),{customScrollParent:_,data:M,deviation:q,footerHeight:o,gap:B,headerHeight:i,increaseViewportBy:e,initialItemCount:N,itemDimensions:O,overscan:n,restoreStateFrom:W,scrollBy:s,scrollContainerState:a,scrollHeight:H,scrollTo:l,scrollToIndex:j,scrollTop:c,smoothScrollTargetReached:d,totalCount:P,useWindowScroll:x,viewportDimensions:I,windowScrollContainerState:w,windowScrollTo:C,windowViewportRect:E,...g,gridState:k,horizontalDirection:re,initialTopMostItemIndex:z,totalListHeight:fe,...p,endReached:de,propsReady:b,rangeChanged:ye,startReached:Ne,stateChanged:$,stateRestoreInProgress:G,...R}},Tn(rP,go,ng,n9,Ec,oP,Cc));function h9(e,t,n){return Gp(1,Ly((e+n)/(Ly(t)+n)))}function uD(e,t,n,r){const{height:o}=n;if(o===void 0||r.length===0)return{bottom:0,top:0};const i=LR(e,t,n,r0.index);return{bottom:LR(e,t,n,rr.length-1.index)+o,top:i}}function LR(e,t,n,r){const o=h9(e.width,n.width,t.column),i=Ly(r/o),s=i*n.height+Gp(0,i-1)*t.row;return s>0?s+t.row:s}const tve=Kt(()=>{const e=ke(f=>`Item ${f}`),t=ke({}),n=ke(null),r=ke("virtuoso-grid-item"),o=ke("virtuoso-grid-list"),i=ke(sP),s=ke("div"),a=ke(Zf),l=(f,p=null)=>Qr(_e(t,Fe(g=>gf),Cn()),p),c=ke(!1),d=ke(!1);return Ge(ot(d),c),{components:t,computeItemKey:i,context:n,FooterComponent:l("Footer"),HeaderComponent:l("Header"),headerFooterTag:s,itemClassName:r,ItemComponent:l("Item","div"),itemContent:e,listClassName:o,ListComponent:l("List","div"),readyStateChanged:c,reportReadyState:d,ScrollerComponent:l("Scroller","div"),scrollerRef:a,ScrollSeekPlaceholder:l("ScrollSeekPlaceholder","div")}}),nve=Kt((e,t)=>({...e,...t}),Tn(eve,tve)),rve=Pe.memo(function(){const e=In("gridState"),t=In("listClassName"),n=In("itemClassName"),r=In("itemContent"),o=In("computeItemKey"),i=In("isSeeking"),s=Ii("scrollHeight"),a=In("ItemComponent"),l=In("ListComponent"),c=In("ScrollSeekPlaceholder"),d=In("context"),f=Ii("itemDimensions"),p=Ii("gap"),g=In("log"),v=In("stateRestoreInProgress"),b=Ii("reportReadyState"),_=Uu(Pe.useMemo(()=>x=>{const w=x.parentElement.parentElement.scrollHeight;s(w);const C=x.firstChild;if(C){const{height:E,width:R}=C.getBoundingClientRect();f({height:E,width:R})}p({column:dD("column-gap",getComputedStyle(x).columnGap,g),row:dD("row-gap",getComputedStyle(x).rowGap,g)})},s,f,p,g),!0,!1);return l9(()=>{e.itemHeight>0&&e.itemWidth>0&&b(!0)},e),v?null:h.jsx(l,{className:t,ref:_,...Xr(l,d),"data-testid":"virtuoso-item-list",style:{paddingBottom:e.offsetBottom,paddingTop:e.offsetTop},children:e.items.map(x=>{const w=o(x.index,x.data,d);return i?h.jsx(c,{...Xr(c,d),height:e.itemHeight,index:x.index,width:e.itemWidth},w):y.createElement(a,{...Xr(a,d),className:n,"data-index":x.index,key:w},r(x.index,x.data,d))})})}),ove=Pe.memo(function(){const e=In("HeaderComponent"),t=Ii("headerHeight"),n=In("headerFooterTag"),r=Uu(Pe.useMemo(()=>i=>{t(pc(i,"height"))},t),!0,!1),o=In("context");return e?h.jsx(n,{ref:r,children:h.jsx(e,{...Xr(e,o)})}):null}),ive=Pe.memo(function(){const e=In("FooterComponent"),t=Ii("footerHeight"),n=In("headerFooterTag"),r=Uu(Pe.useMemo(()=>i=>{t(pc(i,"height"))},t),!0,!1),o=In("context");return e?h.jsx(n,{ref:r,children:h.jsx(e,{...Xr(e,o)})}):null}),sve=({children:e})=>{const t=Pe.useContext(a9),n=Ii("itemDimensions"),r=Ii("viewportDimensions"),o=Uu(Pe.useMemo(()=>i=>{r(i.getBoundingClientRect())},r),!0,!1);return Pe.useEffect(()=>{t&&(r({height:t.viewportHeight,width:t.viewportWidth}),n({height:t.itemHeight,width:t.itemWidth}))},t,r,n),h.jsx("div",{ref:o,style:Qx(!1),children:e})},ave=({children:e})=>{const t=Pe.useContext(a9),n=Ii("windowViewportRect"),r=Ii("itemDimensions"),o=In("customScrollParent"),i=$W(n,o,!1);return Pe.useEffect(()=>{t&&(r({height:t.itemHeight,width:t.itemWidth}),n({offsetTop:0,visibleHeight:t.viewportHeight,visibleWidth:t.viewportWidth}))},t,n,r),h.jsx("div",{ref:i,style:Qx(!1),children:e})},lve=Pe.memo(function({...e}){const t=In("useWindowScroll"),n=In("customScrollParent"),r=n||t?uve:cve,o=n||t?ave:sve,i=In("context");return h.jsx(r,{...e,...Xr(r,i),children:h.jsxs(o,{children:h.jsx(ove,{}),h.jsx(rve,{}),h.jsx(ive,{})})})}),{useEmitter:p9,useEmitterValue:In,usePublisher:Ii}=i9(nve,{optional:{context:"context",totalCount:"totalCount",overscan:"overscan",itemContent:"itemContent",components:"components",computeItemKey:"computeItemKey",data:"data",initialItemCount:"initialItemCount",scrollSeekConfiguration:"scrollSeekConfiguration",headerFooterTag:"headerFooterTag",listClassName:"listClassName",itemClassName:"itemClassName",useWindowScroll:"useWindowScroll",customScrollParent:"customScrollParent",scrollerRef:"scrollerRef",logLevel:"logLevel",restoreStateFrom:"restoreStateFrom",initialTopMostItemIndex:"initialTopMostItemIndex",increaseViewportBy:"increaseViewportBy"},methods:{scrollTo:"scrollTo",scrollBy:"scrollBy",scrollToIndex:"scrollToIndex"},events:{isScrolling:"isScrolling",endReached:"endReached",startReached:"startReached",rangeChanged:"rangeChanged",atBottomStateChange:"atBottomStateChange",atTopStateChange:"atTopStateChange",stateChanged:"stateChanged",readyStateChanged:"readyStateChanged"}},lve),cve=d9({useEmitter:p9,useEmitterValue:In,usePublisher:Ii}),uve=f9({useEmitter:p9,useEmitterValue:In,usePublisher:Ii});function dD(e,t,n){return t!=="normal"&&!(t!=null&&t.endsWith("px"))&&n(`${e} was not resolved to pixel value correctly`,t,Ao.WARN),t==="normal"?0:parseInt(t??"0",10)}var m9={color:void 0,size:void 0,className:void 0,style:void 0,attr:void 0},fD=Pe.createContext&&Pe.createContext(m9),dve="attr","size","title";function fve(e,t){if(e==null)return{};var n=hve(e,t),r,o;if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o<i.length;o++)r=io,!(t.indexOf(r)>=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(nr=er)}return n}function hve(e,t){if(e==null)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;nr=er}return n}function Oy(){return Oy=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=argumentst;for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(er=nr)}return e},Oy.apply(this,arguments)}function hD(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function Dy(e){for(var t=1;t<arguments.length;t++){var n=argumentst!=null?argumentst:{};t%2?hD(Object(n),!0).forEach(function(r){pve(e,r,nr)}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):hD(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function pve(e,t,n){return t=mve(t),t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function mve(e){var t=gve(e,"string");return typeof t=="symbol"?t:t+""}function gve(e,t){if(typeof e!="object"||!e)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function g9(e){return e&&e.map((t,n)=>Pe.createElement(t.tag,Dy({key:n},t.attr),g9(t.child)))}function Be(e){return t=>Pe.createElement(vve,Oy({attr:Dy({},e.attr)},t),g9(e.child))}function vve(e){var t=n=>{var{attr:r,size:o,title:i}=e,s=fve(e,dve),a=o||n.size||"1em",l;return n.className&&(l=n.className),e.className&&(l=(l?l+" ":"")+e.className),Pe.createElement("svg",Oy({stroke:"currentColor",fill:"currentColor",strokeWidth:"0"},n.attr,r,s,{className:l,style:Dy(Dy({color:e.color||n.color},n.style),e.style),height:a,width:a,xmlns:"http://www.w3.org/2000/svg"}),i&&Pe.createElement("title",null,i),e.children)};return fD!==void 0?Pe.createElement(fD.Consumer,null,n=>t(n)):t(m9)}function yve(e){return Be({attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:{tag:"path",attr:{d:"M8.01266 4.56502C8.75361 4.16876 9.5587 4 11.1411 4H12.8589C14.4413 4 15.2464 4.16876 15.9873 4.56502C16.6166 4.90155 17.0985 5.38342 17.435 6.01266C17.8312 6.75361 18 7.5587 18 9.14111V14.8589C18 16.4413 17.8312 17.2464 17.435 17.9873C17.0985 18.6166 16.6166 19.0985 15.9873 19.435C15.2464 19.8312 14.4413 20 12.8589 20H11.1411C9.5587 20 8.75361 19.8312 8.01266 19.435C7.38342 19.0985 6.90155 18.6166 6.56502 17.9873C6.16876 17.2464 6 16.4413 6 14.8589V9.14111C6 7.5587 6.16876 6.75361 6.56502 6.01266C6.90155 5.38342 7.38342 4.90155 8.01266 4.56502ZM12.8589 2H11.1411C9.12721 2 8.04724 2.27848 7.06946 2.8014C6.09168 3.32432 5.32432 4.09168 4.8014 5.06946C4.27848 6.04724 4 7.12721 4 9.14111V14.8589C4 16.8728 4.27848 17.9528 4.8014 18.9305C5.32432 19.9083 6.09168 20.6757 7.06946 21.1986C8.04724 21.7215 9.12721 22 11.1411 22H12.8589C14.8728 22 15.9528 21.7215 16.9305 21.1986C17.9083 20.6757 18.6757 19.9083 19.1986 18.9305C19.7215 17.9528 20 16.8728 20 14.8589V9.14111C20 7.12721 19.7215 6.04724 19.1986 5.06946C18.6757 4.09168 17.9083 3.32432 16.9305 2.8014C15.9528 2.27848 14.8728 2 12.8589 2ZM13 6H11V11H13V6ZM7.75781 13.758L12.0005 18.0006L16.2431 13.758L14.8289 12.3438L12.0005 15.1722L9.17203 12.3438L7.75781 13.758Z"},child:}})(e)}function xve(e){return Be({attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:{tag:"path",attr:{d:"M2.04932 12.9999H7.52725C7.70624 16.2688 8.7574 19.3053 10.452 21.8809C5.98761 21.1871 2.5001 17.5402 2.04932 12.9999ZM2.04932 10.9999C2.5001 6.45968 5.98761 2.81276 10.452 2.11902C8.7574 4.69456 7.70624 7.73111 7.52725 10.9999H2.04932ZM21.9506 10.9999H16.4726C16.2936 7.73111 15.2425 4.69456 13.5479 2.11902C18.0123 2.81276 21.4998 6.45968 21.9506 10.9999ZM21.9506 12.9999C21.4998 17.5402 18.0123 21.1871 13.5479 21.8809C15.2425 19.3053 16.2936 16.2688 16.4726 12.9999H21.9506ZM9.53068 12.9999H14.4692C14.2976 15.7828 13.4146 18.3732 11.9999 20.5915C10.5852 18.3732 9.70229 15.7828 9.53068 12.9999ZM9.53068 10.9999C9.70229 8.21709 10.5852 5.62672 11.9999 3.40841C13.4146 5.62672 14.2976 8.21709 14.4692 10.9999H9.53068Z"},child:}})(e)}function bve(e){return Be({attr:{viewBox:"0 0 24 24",fill:"currentColor"},child:{tag:"path",attr:{d:"M6.92893 0.514648L21.0711 14.6568L19.6569 16.071L15.834 12.2486L15 13.4999V21.9999H9V13.4999L4 5.99993H3V3.99993L7.585 3.99965L5.51472 1.92886L6.92893 0.514648ZM9.585 5.99965L6.4037 5.99993L11 12.8944V19.9999H13V12.8944L14.392 10.8066L9.585 5.99965ZM21 3.99993V5.99993H20L18.085 8.87193L16.643 7.42893L17.5963 5.99993H15.213L13.213 3.99993H21Z"},child:}})(e)}function wve(e){return Be({attr:{viewBox:"0 0 24 24"},child:{tag:"path",attr:{fill:"none",d:"M0 0h24v24H0z"},child:},{tag:"path",attr:{fillRule:"evenodd",d:"M16 9V4h1c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z"},child:}})(e)}function Sve(e){return Be({attr:{viewBox:"0 0 24 24"},child:{tag:"path",attr:{fill:"none",d:"M0 0h24v24H0z"},child:},{tag:"path",attr:{d:"M14 4v5c0 1.12.37 2.16 1 3H9c.65-.86 1-1.9 1-3V4h4m3-2H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3V4h1c.55 0 1-.45 1-1s-.45-1-1-1z"},child:}})(e)}function _ve(e){return Be({attr:{viewBox:"0 0 24 24"},child:{tag:"path",attr:{fill:"none",d:"M0 0h24v24H0z"},child:},{tag:"path",attr:{d:"M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61 1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42A8.962 8.962 0 0 0 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9a8.994 8.994 0 0 0 7.03-14.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"},child:}})(e)}const Hn=()=>iW(),Xe=zx,Cve="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";let Eve=e=>crypto.getRandomValues(new Uint8Array(e)),Nve=(e,t,n)=>{let r=(2<<Math.log2(e.length-1))-1,o=-~(1.6*r*t/e.length);return(i=t)=>{let s="";for(;;){let a=n(o),l=o|0;for(;l--;)if(s+=eal&r||"",s.length>=i)return s}}},Rve=(e,t=21)=>Nve(e,t|0,Eve),Tve=(e=21)=>{let t="",n=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)t+=Cvene&63;return t};const kve="6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz",pD=Rve(kve,10),br=e=>e?`${e}_${pD()}`:pD();function v9(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)et&&(n=v9(et))&&(r&&(r+=" "),r+=n)}else for(n in e)en&&(r&&(r+=" "),r+=n);return r}function y9(){for(var e,t,n=0,r="",o=arguments.length;n<o;n++)(e=argumentsn)&&(t=v9(e))&&(r&&(r+=" "),r+=t);return r}const lP="-",Pve=e=>{const t=Ave(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:s=>{const a=s.split(lP);return a0===""&&a.length!==1&&a.shift(),x9(a,t)||Ive(s)},getConflictingClassGroupIds:(s,a)=>{const l=ns||;return a&&rs?...l,...rs:l}}},x9=(e,t)=>{var s;if(e.length===0)return t.classGroupId;const n=e0,r=t.nextPart.get(n),o=r?x9(e.slice(1),r):void 0;if(o)return o;if(t.validators.length===0)return;const i=e.join(lP);return(s=t.validators.find(({validator:a})=>a(i)))==null?void 0:s.classGroupId},mD=/^\(.+)\$/,Ive=e=>{if(mD.test(e)){const t=mD.exec(e)1,n=t==null?void 0:t.substring(0,t.indexOf(":"));if(n)return"arbitrary.."+n}},Ave=e=>{const{theme:t,prefix:n}=e,r={nextPart:new Map,validators:};return jve(Object.entries(e.classGroups),n).forEach((i,s)=>{OR(s,r,i,t)}),r},OR=(e,t,n,r)=>{e.forEach(o=>{if(typeof o=="string"){const i=o===""?t:gD(t,o);i.classGroupId=n;return}if(typeof o=="function"){if(Mve(o)){OR(o(r),t,n,r);return}t.validators.push({validator:o,classGroupId:n});return}Object.entries(o).forEach((i,s)=>{OR(s,gD(t,i),n,r)})})},gD=(e,t)=>{let n=e;return t.split(lP).forEach(r=>{n.nextPart.has(r)||n.nextPart.set(r,{nextPart:new Map,validators:}),n=n.nextPart.get(r)}),n},Mve=e=>e.isThemeGetter,jve=(e,t)=>t?e.map((n,r)=>{const o=r.map(i=>typeof i=="string"?t+i:typeof i=="object"?Object.fromEntries(Object.entries(i).map((s,a)=>t+s,a)):i);returnn,o}):e,Lve=e=>{if(e<1)return{get:()=>{},set:()=>{}};let t=0,n=new Map,r=new Map;const o=(i,s)=>{n.set(i,s),t++,t>e&&(t=0,r=n,n=new Map)};return{get(i){let s=n.get(i);if(s!==void 0)return s;if((s=r.get(i))!==void 0)return o(i,s),s},set(i,s){n.has(i)?n.set(i,s):o(i,s)}}},b9="!",Ove=e=>{const{separator:t,experimentalParseClassName:n}=e,r=t.length===1,o=t0,i=t.length,s=a=>{const l=;let c=0,d=0,f;for(let _=0;_<a.length;_++){let x=a_;if(c===0){if(x===o&&(r||a.slice(_,_+i)===t)){l.push(a.slice(d,_)),d=_+i;continue}if(x==="/"){f=_;continue}}x===""?c++:x===""&&c--}const p=l.length===0?a:a.substring(d),g=p.startsWith(b9),v=g?p.substring(1):p,b=f&&f>d?f-d:void 0;return{modifiers:l,hasImportantModifier:g,baseClassName:v,maybePostfixModifierPosition:b}};return n?a=>n({className:a,parseClassName:s}):s},Dve=e=>{if(e.length<=1)return e;const t=;let n=;return e.forEach(r=>{r0===""?(t.push(...n.sort(),r),n=):n.push(r)}),t.push(...n.sort()),t},Fve=e=>({cache:Lve(e.cacheSize),parseClassName:Ove(e),...Pve(e)}),$ve=/\s+/,zve=(e,t)=>{const{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:o}=t,i=,s=e.trim().split($ve);let a="";for(let l=s.length-1;l>=0;l-=1){const c=sl,{modifiers:d,hasImportantModifier:f,baseClassName:p,maybePostfixModifierPosition:g}=n(c);let v=!!g,b=r(v?p.substring(0,g):p);if(!b){if(!v){a=c+(a.length>0?" "+a:a);continue}if(b=r(p),!b){a=c+(a.length>0?" "+a:a);continue}v=!1}const _=Dve(d).join(":"),x=f?_+b9:_,w=x+b;if(i.includes(w))continue;i.push(w);const C=o(b,v);for(let E=0;E<C.length;++E){const R=CE;i.push(x+R)}a=c+(a.length>0?" "+a:a)}return a};function Hve(){let e=0,t,n,r="";for(;e<arguments.length;)(t=argumentse++)&&(n=w9(t))&&(r&&(r+=" "),r+=n);return r}const w9=e=>{if(typeof e=="string")return e;let t,n="";for(let r=0;r<e.length;r++)er&&(t=w9(er))&&(n&&(n+=" "),n+=t);return n};function Bve(e,...t){let n,r,o,i=s;function s(l){const c=t.reduce((d,f)=>f(d),e());return n=Fve(c),r=n.cache.get,o=n.cache.set,i=a,a(l)}function a(l){const c=r(l);if(c)return c;const d=zve(l,n);return o(l,d),d}return function(){return i(Hve.apply(null,arguments))}}const bn=e=>{const t=n=>ne||;return t.isThemeGetter=!0,t},S9=/^\(?:(a-z-+):)?(.+)\$/i,Wve=/^\d+\/\d+$/,Uve=new Set("px","full","screen"),qve=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,Gve=/\d+(%|px|r?em|sdl?v(hwib|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,Vve=/^(rgba?|hsla?|hwb|(ok)?(lab|lch))\(.+\)$/,Yve=/^(inset_)?-?((\d+)?\.?(\d+)a-z+|0)_-?((\d+)?\.?(\d+)a-z+|0)/,Kve=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,ja=e=>df(e)||Uve.has(e)||Wve.test(e),$l=e=>Qf(e,"length",r0e),df=e=>!!e&&!Number.isNaN(Number(e)),uS=e=>Qf(e,"number",df),sp=e=>!!e&&Number.isInteger(Number(e)),Xve=e=>e.endsWith("%")&&df(e.slice(0,-1)),mt=e=>S9.test(e),zl=e=>qve.test(e),Zve=new Set("length","size","percentage"),Qve=e=>Qf(e,Zve,_9),Jve=e=>Qf(e,"position",_9),e0e=new Set("image","url"),t0e=e=>Qf(e,e0e,i0e),n0e=e=>Qf(e,"",o0e),ap=()=>!0,Qf=(e,t,n)=>{const r=S9.exec(e);return r?r1?typeof t=="string"?r1===t:t.has(r1):n(r2):!1},r0e=e=>Gve.test(e)&&!Vve.test(e),_9=()=>!1,o0e=e=>Yve.test(e),i0e=e=>Kve.test(e),s0e=()=>{const e=bn("colors"),t=bn("spacing"),n=bn("blur"),r=bn("brightness"),o=bn("borderColor"),i=bn("borderRadius"),s=bn("borderSpacing"),a=bn("borderWidth"),l=bn("contrast"),c=bn("grayscale"),d=bn("hueRotate"),f=bn("invert"),p=bn("gap"),g=bn("gradientColorStops"),v=bn("gradientColorStopPositions"),b=bn("inset"),_=bn("margin"),x=bn("opacity"),w=bn("padding"),C=bn("saturate"),E=bn("scale"),R=bn("sepia"),P=bn("skew"),N=bn("space"),k=bn("translate"),I=()=>"auto","contain","none",O=()=>"auto","hidden","clip","visible","scroll",j=()=>"auto",mt,t,H=()=>mt,t,q=()=>"",ja,$l,M=()=>"auto",df,mt,B=()=>"bottom","center","left","left-bottom","left-top","right","right-bottom","right-top","top",$=()=>"solid","dashed","dotted","double","none",W=()=>"normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity",G=()=>"start","end","center","between","around","evenly","stretch",z=()=>"","0",mt,K=()=>"auto","avoid","all","avoid-page","page","left","right","column",Q=()=>df,mt;return{cacheSize:500,separator:":",theme:{colors:ap,spacing:ja,$l,blur:"none","",zl,mt,brightness:Q(),borderColor:e,borderRadius:"none","","full",zl,mt,borderSpacing:H(),borderWidth:q(),contrast:Q(),grayscale:z(),hueRotate:Q(),invert:z(),gap:H(),gradientColorStops:e,gradientColorStopPositions:Xve,$l,inset:j(),margin:j(),opacity:Q(),padding:H(),saturate:Q(),scale:Q(),sepia:z(),skew:Q(),space:H(),translate:H()},classGroups:{aspect:{aspect:"auto","square","video",mt},container:"container",columns:{columns:zl},"break-after":{"break-after":K()},"break-before":{"break-before":K()},"break-inside":{"break-inside":"auto","avoid","avoid-page","avoid-column"},"box-decoration":{"box-decoration":"slice","clone"},box:{box:"border","content"},display:"block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden",float:{float:"right","left","none","start","end"},clear:{clear:"left","right","both","none","start","end"},isolation:"isolate","isolation-auto","object-fit":{object:"contain","cover","fill","none","scale-down"},"object-position":{object:...B(),mt},overflow:{overflow:O()},"overflow-x":{"overflow-x":O()},"overflow-y":{"overflow-y":O()},overscroll:{overscroll:I()},"overscroll-x":{"overscroll-x":I()},"overscroll-y":{"overscroll-y":I()},position:"static","fixed","absolute","relative","sticky",inset:{inset:b},"inset-x":{"inset-x":b},"inset-y":{"inset-y":b},start:{start:b},end:{end:b},top:{top:b},right:{right:b},bottom:{bottom:b},left:{left:b},visibility:"visible","invisible","collapse",z:{z:"auto",sp,mt},basis:{basis:j()},"flex-direction":{flex:"row","row-reverse","col","col-reverse"},"flex-wrap":{flex:"wrap","wrap-reverse","nowrap"},flex:{flex:"1","auto","initial","none",mt},grow:{grow:z()},shrink:{shrink:z()},order:{order:"first","last","none",sp,mt},"grid-cols":{"grid-cols":ap},"col-start-end":{col:"auto",{span:"full",sp,mt},mt},"col-start":{"col-start":M()},"col-end":{"col-end":M()},"grid-rows":{"grid-rows":ap},"row-start-end":{row:"auto",{span:sp,mt},mt},"row-start":{"row-start":M()},"row-end":{"row-end":M()},"grid-flow":{"grid-flow":"row","col","dense","row-dense","col-dense"},"auto-cols":{"auto-cols":"auto","min","max","fr",mt},"auto-rows":{"auto-rows":"auto","min","max","fr",mt},gap:{gap:p},"gap-x":{"gap-x":p},"gap-y":{"gap-y":p},"justify-content":{justify:"normal",...G()},"justify-items":{"justify-items":"start","end","center","stretch"},"justify-self":{"justify-self":"auto","start","end","center","stretch"},"align-content":{content:"normal",...G(),"baseline"},"align-items":{items:"start","end","center","baseline","stretch"},"align-self":{self:"auto","start","end","center","stretch","baseline"},"place-content":{"place-content":...G(),"baseline"},"place-items":{"place-items":"start","end","center","baseline","stretch"},"place-self":{"place-self":"auto","start","end","center","stretch"},p:{p:w},px:{px:w},py:{py:w},ps:{ps:w},pe:{pe:w},pt:{pt:w},pr:{pr:w},pb:{pb:w},pl:{pl:w},m:{m:_},mx:{mx:_},my:{my:_},ms:{ms:_},me:{me:_},mt:{mt:_},mr:{mr:_},mb:{mb:_},ml:{ml:_},"space-x":{"space-x":N},"space-x-reverse":"space-x-reverse","space-y":{"space-y":N},"space-y-reverse":"space-y-reverse",w:{w:"auto","min","max","fit","svw","lvw","dvw",mt,t},"min-w":{"min-w":mt,t,"min","max","fit"},"max-w":{"max-w":mt,t,"none","full","min","max","fit","prose",{screen:zl},zl},h:{h:mt,t,"auto","min","max","fit","svh","lvh","dvh"},"min-h":{"min-h":mt,t,"min","max","fit","svh","lvh","dvh"},"max-h":{"max-h":mt,t,"min","max","fit","svh","lvh","dvh"},size:{size:mt,t,"auto","min","max","fit"},"font-size":{text:"base",zl,$l},"font-smoothing":"antialiased","subpixel-antialiased","font-style":"italic","not-italic","font-weight":{font:"thin","extralight","light","normal","medium","semibold","bold","extrabold","black",uS},"font-family":{font:ap},"fvn-normal":"normal-nums","fvn-ordinal":"ordinal","fvn-slashed-zero":"slashed-zero","fvn-figure":"lining-nums","oldstyle-nums","fvn-spacing":"proportional-nums","tabular-nums","fvn-fraction":"diagonal-fractions","stacked-fractions",tracking:{tracking:"tighter","tight","normal","wide","wider","widest",mt},"line-clamp":{"line-clamp":"none",df,uS},leading:{leading:"none","tight","snug","normal","relaxed","loose",ja,mt},"list-image":{"list-image":"none",mt},"list-style-type":{list:"none","disc","decimal",mt},"list-style-position":{list:"inside","outside"},"placeholder-color":{placeholder:e},"placeholder-opacity":{"placeholder-opacity":x},"text-alignment":{text:"left","center","right","justify","start","end"},"text-color":{text:e},"text-opacity":{"text-opacity":x},"text-decoration":"underline","overline","line-through","no-underline","text-decoration-style":{decoration:...$(),"wavy"},"text-decoration-thickness":{decoration:"auto","from-font",ja,$l},"underline-offset":{"underline-offset":"auto",ja,mt},"text-decoration-color":{decoration:e},"text-transform":"uppercase","lowercase","capitalize","normal-case","text-overflow":"truncate","text-ellipsis","text-clip","text-wrap":{text:"wrap","nowrap","balance","pretty"},indent:{indent:H()},"vertical-align":{align:"baseline","top","middle","bottom","text-top","text-bottom","sub","super",mt},whitespace:{whitespace:"normal","nowrap","pre","pre-line","pre-wrap","break-spaces"},break:{break:"normal","words","all","keep"},hyphens:{hyphens:"none","manual","auto"},content:{content:"none",mt},"bg-attachment":{bg:"fixed","local","scroll"},"bg-clip":{"bg-clip":"border","padding","content","text"},"bg-opacity":{"bg-opacity":x},"bg-origin":{"bg-origin":"border","padding","content"},"bg-position":{bg:...B(),Jve},"bg-repeat":{bg:"no-repeat",{repeat:"","x","y","round","space"}},"bg-size":{bg:"auto","cover","contain",Qve},"bg-image":{bg:"none",{"gradient-to":"t","tr","r","br","b","bl","l","tl"},t0e},"bg-color":{bg:e},"gradient-from-pos":{from:v},"gradient-via-pos":{via:v},"gradient-to-pos":{to:v},"gradient-from":{from:g},"gradient-via":{via:g},"gradient-to":{to:g},rounded:{rounded:i},"rounded-s":{"rounded-s":i},"rounded-e":{"rounded-e":i},"rounded-t":{"rounded-t":i},"rounded-r":{"rounded-r":i},"rounded-b":{"rounded-b":i},"rounded-l":{"rounded-l":i},"rounded-ss":{"rounded-ss":i},"rounded-se":{"rounded-se":i},"rounded-ee":{"rounded-ee":i},"rounded-es":{"rounded-es":i},"rounded-tl":{"rounded-tl":i},"rounded-tr":{"rounded-tr":i},"rounded-br":{"rounded-br":i},"rounded-bl":{"rounded-bl":i},"border-w":{border:a},"border-w-x":{"border-x":a},"border-w-y":{"border-y":a},"border-w-s":{"border-s":a},"border-w-e":{"border-e":a},"border-w-t":{"border-t":a},"border-w-r":{"border-r":a},"border-w-b":{"border-b":a},"border-w-l":{"border-l":a},"border-opacity":{"border-opacity":x},"border-style":{border:...$(),"hidden"},"divide-x":{"divide-x":a},"divide-x-reverse":"divide-x-reverse","divide-y":{"divide-y":a},"divide-y-reverse":"divide-y-reverse","divide-opacity":{"divide-opacity":x},"divide-style":{divide:$()},"border-color":{border:o},"border-color-x":{"border-x":o},"border-color-y":{"border-y":o},"border-color-s":{"border-s":o},"border-color-e":{"border-e":o},"border-color-t":{"border-t":o},"border-color-r":{"border-r":o},"border-color-b":{"border-b":o},"border-color-l":{"border-l":o},"divide-color":{divide:o},"outline-style":{outline:"",...$()},"outline-offset":{"outline-offset":ja,mt},"outline-w":{outline:ja,$l},"outline-color":{outline:e},"ring-w":{ring:q()},"ring-w-inset":"ring-inset","ring-color":{ring:e},"ring-opacity":{"ring-opacity":x},"ring-offset-w":{"ring-offset":ja,$l},"ring-offset-color":{"ring-offset":e},shadow:{shadow:"","inner","none",zl,n0e},"shadow-color":{shadow:ap},opacity:{opacity:x},"mix-blend":{"mix-blend":...W(),"plus-lighter","plus-darker"},"bg-blend":{"bg-blend":W()},filter:{filter:"","none"},blur:{blur:n},brightness:{brightness:r},contrast:{contrast:l},"drop-shadow":{"drop-shadow":"","none",zl,mt},grayscale:{grayscale:c},"hue-rotate":{"hue-rotate":d},invert:{invert:f},saturate:{saturate:C},sepia:{sepia:R},"backdrop-filter":{"backdrop-filter":"","none"},"backdrop-blur":{"backdrop-blur":n},"backdrop-brightness":{"backdrop-brightness":r},"backdrop-contrast":{"backdrop-contrast":l},"backdrop-grayscale":{"backdrop-grayscale":c},"backdrop-hue-rotate":{"backdrop-hue-rotate":d},"backdrop-invert":{"backdrop-invert":f},"backdrop-opacity":{"backdrop-opacity":x},"backdrop-saturate":{"backdrop-saturate":C},"backdrop-sepia":{"backdrop-sepia":R},"border-collapse":{border:"collapse","separate"},"border-spacing":{"border-spacing":s},"border-spacing-x":{"border-spacing-x":s},"border-spacing-y":{"border-spacing-y":s},"table-layout":{table:"auto","fixed"},caption:{caption:"top","bottom"},transition:{transition:"none","all","","colors","opacity","shadow","transform",mt},duration:{duration:Q()},ease:{ease:"linear","in","out","in-out",mt},delay:{delay:Q()},animate:{animate:"none","spin","ping","pulse","bounce",mt},transform:{transform:"","gpu","none"},scale:{scale:E},"scale-x":{"scale-x":E},"scale-y":{"scale-y":E},rotate:{rotate:sp,mt},"translate-x":{"translate-x":k},"translate-y":{"translate-y":k},"skew-x":{"skew-x":P},"skew-y":{"skew-y":P},"transform-origin":{origin:"center","top","top-right","right","bottom-right","bottom","bottom-left","left","top-left",mt},accent:{accent:"auto",e},appearance:{appearance:"none","auto"},cursor:{cursor:"auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",mt},"caret-color":{caret:e},"pointer-events":{"pointer-events":"none","auto"},resize:{resize:"none","y","x",""},"scroll-behavior":{scroll:"auto","smooth"},"scroll-m":{"scroll-m":H()},"scroll-mx":{"scroll-mx":H()},"scroll-my":{"scroll-my":H()},"scroll-ms":{"scroll-ms":H()},"scroll-me":{"scroll-me":H()},"scroll-mt":{"scroll-mt":H()},"scroll-mr":{"scroll-mr":H()},"scroll-mb":{"scroll-mb":H()},"scroll-ml":{"scroll-ml":H()},"scroll-p":{"scroll-p":H()},"scroll-px":{"scroll-px":H()},"scroll-py":{"scroll-py":H()},"scroll-ps":{"scroll-ps":H()},"scroll-pe":{"scroll-pe":H()},"scroll-pt":{"scroll-pt":H()},"scroll-pr":{"scroll-pr":H()},"scroll-pb":{"scroll-pb":H()},"scroll-pl":{"scroll-pl":H()},"snap-align":{snap:"start","end","center","align-none"},"snap-stop":{snap:"normal","always"},"snap-type":{snap:"none","x","y","both"},"snap-strictness":{snap:"mandatory","proximity"},touch:{touch:"auto","none","manipulation"},"touch-x":{"touch-pan":"x","left","right"},"touch-y":{"touch-pan":"y","up","down"},"touch-pz":"touch-pinch-zoom",select:{select:"none","text","all","auto"},"will-change":{"will-change":"auto","scroll","contents","transform",mt},fill:{fill:e,"none"},"stroke-w":{stroke:ja,$l,uS},stroke:{stroke:e,"none"},sr:"sr-only","not-sr-only","forced-color-adjust":{"forced-color-adjust":"auto","none"}},conflictingClassGroups:{overflow:"overflow-x","overflow-y",overscroll:"overscroll-x","overscroll-y",inset:"inset-x","inset-y","start","end","top","right","bottom","left","inset-x":"right","left","inset-y":"top","bottom",flex:"basis","grow","shrink",gap:"gap-x","gap-y",p:"px","py","ps","pe","pt","pr","pb","pl",px:"pr","pl",py:"pt","pb",m:"mx","my","ms","me","mt","mr","mb","ml",mx:"mr","ml",my:"mt","mb",size:"w","h","font-size":"leading","fvn-normal":"fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction","fvn-ordinal":"fvn-normal","fvn-slashed-zero":"fvn-normal","fvn-figure":"fvn-normal","fvn-spacing":"fvn-normal","fvn-fraction":"fvn-normal","line-clamp":"display","overflow",rounded:"rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl","rounded-s":"rounded-ss","rounded-es","rounded-e":"rounded-se","rounded-ee","rounded-t":"rounded-tl","rounded-tr","rounded-r":"rounded-tr","rounded-br","rounded-b":"rounded-br","rounded-bl","rounded-l":"rounded-tl","rounded-bl","border-spacing":"border-spacing-x","border-spacing-y","border-w":"border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l","border-w-x":"border-w-r","border-w-l","border-w-y":"border-w-t","border-w-b","border-color":"border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l","border-color-x":"border-color-r","border-color-l","border-color-y":"border-color-t","border-color-b","scroll-m":"scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml","scroll-mx":"scroll-mr","scroll-ml","scroll-my":"scroll-mt","scroll-mb","scroll-p":"scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl","scroll-px":"scroll-pr","scroll-pl","scroll-py":"scroll-pt","scroll-pb",touch:"touch-x","touch-y","touch-pz","touch-x":"touch","touch-y":"touch","touch-pz":"touch"},conflictingClassGroupModifiers:{"font-size":"leading"}}},a0e=Bve(s0e);function nt(...e){return a0e(y9(e))}var Ei;class cP{constructor(t){Le(this,"data",{});Le(this,"blockNotifications",!1);yj(this,Ei,{});this.data=structuredClone(t)}get hasSubscribers(){return Object.keys(zs(this,Ei)).length>0&&Object.values(zs(this,Ei)).some(t=>Object.keys(t.subscribers).length>0)}subscribe(t,n={}){const r={debounce:150,immediate:!0,...n};let o=zs(this,Ei)r.debounce;o||(o={subscribers:{},messages:,lastSent:-1/0},zs(this,Ei)r.debounce=o);const i=br();return o.subscribersi=t,r.immediate&&t(this.data),()=>{delete o.subscribersi,Object.keys(o.subscribers).length===0&&delete zs(this,Ei)r.debounce}}getSnapshot(){return this.data}notify(t,n){this.blockNotifications||(t&&Object.values(zs(this,Ei)).forEach(r=>{r.messages.push({type:t,timestamp:performance.now(),extraData:n??{}})}),Object.values(zs(this,Ei)).forEach(r=>{r.timeout&&clearTimeout(r.timeout)}),Object.keys(zs(this,Ei)).forEach(r=>{const o=zs(this,Ei)Number(r);o.timeout=setTimeout(()=>{const i=o.messages.filter(s=>s.timestamp>o.lastSent);Object.values(o.subscribers).forEach(s=>{s(this.data,i.length===0?void 0:i.map(a=>a.type),i.length===0?void 0:i.map(a=>a.extraData))}),o.lastSent=performance.now(),o.messages=},Number(r))}))}}Ei=new WeakMap;class l0e extends cP{constructor(){super({width:0,height:0,timestamp:0});Le(this,"resizeObserver",null);Le(this,"activeElements",new Map);Le(this,"isResizing",!1);Le(this,"resizeEndTimer",null);this.initializeResizeObserver()}initializeResizeObserver(){this.resizeObserver=new ResizeObserver(n=>{this.isResizing||(this.isResizing=!0,this.notify("resize_start")),this.resizeEndTimer&&clearTimeout(this.resizeEndTimer);for(const r of n){const{width:o,height:i}=r.contentRect;this.updateData({width:o,height:i,timestamp:Date.now()}),this.notify("resize_update")}this.resizeEndTimer=setTimeout(()=>{this.isResizing=!1,this.notify("resize_end"),this.resizeEndTimer=null},150)})}observeElement(n,r){if(!this.resizeObserver)return"";const o=r||`element_${Date.now()}_${Math.random()}`;return this.activeElements.set(n,o),this.resizeObserver.observe(n),o}unobserveElement(n){this.resizeObserver&&(this.resizeObserver.unobserve(n),this.activeElements.delete(n))}subscribeToResize(n,r=16){return this.subscribe(n,{debounce:r})}destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeEndTimer&&(clearTimeout(this.resizeEndTimer),this.resizeEndTimer=null),this.activeElements.clear()}updateData(n){this.data={...this.data,...n}}}const dS=new l0e,c0e=()=>({observeElement:(e,t)=>dS.observeElement(e,t),unobserveElement:e=>dS.unobserveElement(e),subscribe:(e,t)=>dS.subscribeToResize(e,t)}),u0e=()=>({startTransform:r=>{r.style.willChange="transform",r.style.transformOrigin="top left"},updateTransform:(r,o,i)=>{r.style.transform=`scale(${o}, ${i})`},commitResize:(r,o,i)=>{r.style.transform="",r.style.willChange="",r.style.width=`${o}px`,r.style.height=`${i}px`}}),Jx=(e={})=>{const{onResize:t,onResizeStart:n,onResizeEnd:r,debounce:o=16,throttle:i=!0,useTransform:s=!1}=e,a=y.useRef(null),{observeElement:l,unobserveElement:c,subscribe:d}=c0e(),{startTransform:f,updateTransform:p,commitResize:g}=u0e(),v=y.useRef(!1),b=y.useRef(null),_=y.useCallback((x,w)=>{if(w){if(w.includes("resize_start")&&(v.current=!0,s&&a.current&&(b.current={width:x.width,height:x.height},f(a.current)),n==null||n()),w.includes("resize_update"))if(s&&a.current&&b.current){const C=x.width/b.current.width,E=x.height/b.current.height;p(a.current,C,E)}else t&&(i&&v.current?t(x.width,x.height):i||t(x.width,x.height));w.includes("resize_end")&&(v.current=!1,s&&a.current&&(g(a.current,x.width,x.height),b.current=null),r==null||r(),t&&!s&&t(x.width,x.height))}},t,n,r,i);return y.useEffect(()=>{if(!a.current)return;const x=a.current,w=d(_,o);return()=>{c(x),w()}},l,c,d,_,o),{ref:a,isResizing:v.current}};function uP(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"},child:}})(e)}function d0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M12 5v14"},child:},{tag:"path",attr:{d:"m19 12-7 7-7-7"},child:}})(e)}function f0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"m21 16-4 4-4-4"},child:},{tag:"path",attr:{d:"M17 20V4"},child:},{tag:"path",attr:{d:"m3 8 4-4 4 4"},child:},{tag:"path",attr:{d:"M7 4v16"},child:}})(e)}function h0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"m5 12 7-7 7 7"},child:},{tag:"path",attr:{d:"M12 19V5"},child:}})(e)}function dP(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M20 6 9 17l-5-5"},child:}})(e)}function p0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"m6 9 6 6 6-6"},child:}})(e)}function m0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"m9 18 6-6-6-6"},child:}})(e)}function g0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"circle",attr:{cx:"12",cy:"12",r:"10"},child:}})(e)}function C9(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"circle",attr:{cx:"12",cy:"12",r:"10"},child:},{tag:"polyline",attr:{points:"12 6 12 12 16 14"},child:}})(e)}function v0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"},child:},{tag:"path",attr:{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"},child:}})(e)}function y0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"ellipse",attr:{cx:"12",cy:"5",rx:"9",ry:"3"},child:},{tag:"path",attr:{d:"M3 5V19A9 3 0 0 0 21 19V5"},child:},{tag:"path",attr:{d:"M3 12A9 3 0 0 0 21 12"},child:}})(e)}function x0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"},child:},{tag:"polyline",attr:{points:"7 10 12 15 17 10"},child:},{tag:"line",attr:{x1:"12",x2:"12",y1:"15",y2:"3"},child:}})(e)}function E9(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"},child:},{tag:"circle",attr:{cx:"12",cy:"12",r:"3"},child:}})(e)}function N9(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"},child:},{tag:"path",attr:{d:"M14 2v4a2 2 0 0 0 2 2h4"},child:},{tag:"path",attr:{d:"M10 9H8"},child:},{tag:"path",attr:{d:"M16 13H8"},child:},{tag:"path",attr:{d:"M16 17H8"},child:}})(e)}function b0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"},child:},{tag:"path",attr:{d:"M14 2v4a2 2 0 0 0 2 2h4"},child:}})(e)}function Fy(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"18",height:"18",x:"3",y:"3",rx:"2"},child:},{tag:"path",attr:{d:"M7 3v18"},child:},{tag:"path",attr:{d:"M3 7.5h4"},child:},{tag:"path",attr:{d:"M3 12h18"},child:},{tag:"path",attr:{d:"M3 16.5h4"},child:},{tag:"path",attr:{d:"M17 3v18"},child:},{tag:"path",attr:{d:"M17 7.5h4"},child:},{tag:"path",attr:{d:"M17 16.5h4"},child:}})(e)}function w0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"m12 14 4-4"},child:},{tag:"path",attr:{d:"M3.34 19a10 10 0 1 1 17.32 0"},child:}})(e)}function S0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"line",attr:{x1:"22",x2:"2",y1:"12",y2:"12"},child:},{tag:"path",attr:{d:"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"},child:},{tag:"line",attr:{x1:"6",x2:"6.01",y1:"16",y2:"16"},child:},{tag:"line",attr:{x1:"10",x2:"10.01",y1:"16",y2:"16"},child:}})(e)}function DR(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"circle",attr:{cx:"12",cy:"12",r:"10"},child:},{tag:"path",attr:{d:"M12 16v-4"},child:},{tag:"path",attr:{d:"M12 8h.01"},child:}})(e)}function _0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"7",height:"7",x:"3",y:"3",rx:"1"},child:},{tag:"rect",attr:{width:"7",height:"7",x:"3",y:"14",rx:"1"},child:},{tag:"path",attr:{d:"M14 4h7"},child:},{tag:"path",attr:{d:"M14 9h7"},child:},{tag:"path",attr:{d:"M14 15h7"},child:},{tag:"path",attr:{d:"M14 20h7"},child:}})(e)}function C0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"m9 10 2 2 4-4"},child:},{tag:"rect",attr:{width:"20",height:"14",x:"2",y:"3",rx:"2"},child:},{tag:"path",attr:{d:"M12 17v4"},child:},{tag:"path",attr:{d:"M8 21h8"},child:}})(e)}function E0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"20",height:"14",x:"2",y:"3",rx:"2"},child:},{tag:"line",attr:{x1:"8",x2:"16",y1:"21",y2:"21"},child:},{tag:"line",attr:{x1:"12",x2:"12",y1:"17",y2:"21"},child:}})(e)}function fP(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M9 18V5l12-2v13"},child:},{tag:"circle",attr:{cx:"6",cy:"18",r:"3"},child:},{tag:"circle",attr:{cx:"18",cy:"16",r:"3"},child:}})(e)}function N0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M3 9h18v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9Z"},child:},{tag:"path",attr:{d:"m3 9 2.45-4.9A2 2 0 0 1 7.24 3h9.52a2 2 0 0 1 1.8 1.1L21 9"},child:},{tag:"path",attr:{d:"M12 3v6"},child:}})(e)}function R0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"18",height:"18",x:"3",y:"3",rx:"2"},child:},{tag:"path",attr:{d:"M9 3v18"},child:},{tag:"path",attr:{d:"m16 15-3-3 3-3"},child:}})(e)}function T0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"18",height:"18",x:"3",y:"3",rx:"2"},child:},{tag:"path",attr:{d:"M9 3v18"},child:}})(e)}function $y(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{x:"14",y:"4",width:"4",height:"16",rx:"1"},child:},{tag:"rect",attr:{x:"6",y:"4",width:"4",height:"16",rx:"1"},child:}})(e)}function hP(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"polygon",attr:{points:"6 3 20 12 6 21 6 3"},child:}})(e)}function k0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M9 2v6"},child:},{tag:"path",attr:{d:"M15 2v6"},child:},{tag:"path",attr:{d:"M12 17v5"},child:},{tag:"path",attr:{d:"M5 8h14"},child:},{tag:"path",attr:{d:"M6 11V8h12v3a6 6 0 1 1-12 0Z"},child:}})(e)}function FR(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M5 12h14"},child:},{tag:"path",attr:{d:"M12 5v14"},child:}})(e)}function P0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"},child:},{tag:"path",attr:{d:"M3 3v5h5"},child:}})(e)}function I0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"},child:},{tag:"path",attr:{d:"M21 3v5h-5"},child:}})(e)}function A0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"},child:},{tag:"path",attr:{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"},child:},{tag:"path",attr:{d:"M7 3v4a1 1 0 0 0 1 1h7"},child:}})(e)}function pP(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"},child:},{tag:"circle",attr:{cx:"12",cy:"12",r:"3"},child:}})(e)}function M0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"circle",attr:{cx:"18",cy:"5",r:"3"},child:},{tag:"circle",attr:{cx:"6",cy:"12",r:"3"},child:},{tag:"circle",attr:{cx:"18",cy:"19",r:"3"},child:},{tag:"line",attr:{x1:"8.59",x2:"15.42",y1:"13.51",y2:"17.49"},child:},{tag:"line",attr:{x1:"15.41",x2:"8.59",y1:"6.51",y2:"10.49"},child:}})(e)}function j0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{width:"18",height:"18",x:"3",y:"3",rx:"2"},child:},{tag:"path",attr:{d:"M8 8h8v8"},child:},{tag:"path",attr:{d:"m8 16 8-8"},child:}})(e)}function L0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M3 6h18"},child:},{tag:"path",attr:{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"},child:},{tag:"path",attr:{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"},child:},{tag:"line",attr:{x1:"10",x2:"10",y1:"11",y2:"17"},child:},{tag:"line",attr:{x1:"14",x2:"14",y1:"11",y2:"17"},child:}})(e)}function zy(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"},child:}})(e)}function O0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"},child:},{tag:"polyline",attr:{points:"17 8 12 3 7 8"},child:},{tag:"line",attr:{x1:"12",x2:"12",y1:"3",y2:"15"},child:}})(e)}function R9(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M12 20h.01"},child:},{tag:"path",attr:{d:"M8.5 16.429a5 5 0 0 1 7 0"},child:},{tag:"path",attr:{d:"M5 12.859a10 10 0 0 1 5.17-2.69"},child:},{tag:"path",attr:{d:"M19 12.859a10 10 0 0 0-2.007-1.523"},child:},{tag:"path",attr:{d:"M2 8.82a15 15 0 0 1 4.177-2.643"},child:},{tag:"path",attr:{d:"M22 8.82a15 15 0 0 0-11.288-3.764"},child:},{tag:"path",attr:{d:"m2 2 20 20"},child:}})(e)}function eb(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M18 6 6 18"},child:},{tag:"path",attr:{d:"m6 6 12 12"},child:}})(e)}function D0e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"path",attr:{d:"M12 20l-3 1v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v1.5"},child:},{tag:"path",attr:{d:"M19.001 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"},child:},{tag:"path",attr:{d:"M19.001 15.5v1.5"},child:},{tag:"path",attr:{d:"M19.001 21v1.5"},child:},{tag:"path",attr:{d:"M22.032 17.25l-1.299 .75"},child:},{tag:"path",attr:{d:"M17.27 20l-1.3 .75"},child:},{tag:"path",attr:{d:"M15.97 17.25l1.3 .75"},child:},{tag:"path",attr:{d:"M20.733 20l1.3 .75"},child:}})(e)}const bp={_n.FILTERSESSION:D0e,_n.GRAPH:M0e,_n.METRICS:w0e,_n.LOGS:N9},tb=e=>e.widgets.activeWidgets;Nt(tb,(e,t)=>t,(e,t)=>e.some(n=>n.type===_n.FILTERSESSION&&n.isDetached===!0&&n.detachedFilterIdx===t));Nt(tb,(e,t)=>t,(e,t)=>{const n=e.find(r=>r.type===_n.FILTERSESSION&&r.isDetached===!0&&r.detachedFilterIdx===t);return n?n.id:null});Nt(tb,e=>e.filter(t=>t.type===_n.FILTERSESSION&&t.isDetached===!0).length);Nt(tb,e=>e.filter(t=>t.type===_n.FILTERSESSION&&t.isDetached===!0));const F0e={isMaximized:!1,isMinimized:!1,settings:{}},T9=e=>e.widgets.activeWidgets,$0e=e=>e.widgets.savedLayouts,z0e=e=>e.widgets.currentLayout,H0e=e=>e.widgets.configs,B0e=()=>Nt(H0e,(e,t)=>t,(e,t)=>et??F0e),fS={base:"flex items-center justify-between px-4 py-2 bg-monitor-app border-b border-gray-800 transition-colors hover:bg-gray-650",title:"flex items-center gap-2",actions:"flex items-center gap-2"},hS={base:"p-1 hover:bg-gray-600 rounded text-gray-400 hover:text-white cursor-pointer no-drag z-50"},k9=({id:e,children:t,className:n="",customActions:r,statusBadge:o})=>{const i=Hn(),s,a=y.useState(!1),l=Xe(E=>E.widgets.activeWidgets.find(R=>R.id===e)),c=y.useMemo(()=>l!=null&&l.type&&bpl.type||null,l==null?void 0:l.type),{ref:d}=Jx({onResizeStart:()=>a(!0),onResizeEnd:()=>a(!1),debounce:16,throttle:!0}),f=y.useMemo(()=>B0e(),),p=Xe(E=>f(E,e)),{isMaximized:g,isMinimized:v}=p,b=y.useCallback(()=>{i(ELe(e))},i,e),_=y.useCallback(()=>{i(CLe(e))},i,e),x=y.useCallback(()=>{i(NLe(e))},i,e),w=y.useCallback(E=>{E.stopPropagation(),i(_Le(e))},i,e),C=Pe.useMemo(()=>"flex flex-col bg-foreground/10 overflow-hidden rounded-sm widget-anim",g?"fixed inset-4 z-50 w-auto h-auto":"",v?"h-12":"h-full",s?"is-interacting pointer-events-none":"",n.filter(Boolean).join(" "),g,v,s,n);return h.jsxs("div",{ref:d,className:C,children:h.jsxs("div",{className:`${fS.base} cursor-grab widget-drag-handle bg-gray-900/70 flex justify-center`,children:h.jsxs("div",{className:fS.title,children:c&&Pe.createElement(c,{className:"w-4 h-4 text-secondary font-ui"}),h.jsx("h3",{className:"text-base font-medium font-ui text-secondary",children:l==null?void 0:l.title}),o&&h.jsx("div",{className:"min-w-32 ",children:o})}),h.jsxs("div",{className:`${fS.actions} no-drag`,children:r&&h.jsx("div",{className:"flex items-center gap-2 mr-2 border-r border-gray-600 pr-2",children:r}),!g&&!v&&h.jsx("button",{onClick:b,className:hS.base,title:"Minimize",type:"button",children:h.jsxs("svg",{className:"w-4 h-4",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",children:"statusBadge",h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M20 12H4"})})}),!g&&h.jsx("button",{onClick:_,className:hS.base,title:"Maximize",type:"button",children:h.jsx("svg",{className:"w-4 h-4",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"})})}),g&&h.jsx("button",{onClick:x,className:hS.base,title:"Restore",type:"button",children:h.jsx(P0e,{className:"w-4 h-4"})}),h.jsx("button",{onClick:w,title:"Close widget",type:"button",children:h.jsx(eb,{className:"w-4 h-4"})})})}),!v&&h.jsx("div",{className:`flex-1 bg-monitor-surface overflow-auto no-drag gpu-optimized ${s?"contain-layout contain-style":""}`,children:t})})};k9.displayName="WidgetWrapper";const Js=Pe.memo(k9);class lp{static formatForSend(t){return t.startsWith("json:")?t:`json:${t}`}static parseReceived(t){return t.startsWith("json:")?JSON.parse(t.substring(5)):t.startsWith("{")?JSON.parse(t):t}static createDataView(t){const r=new TextEncoder().encode(JSON.stringify(t));return new DataView(r.buffer)}static decodeDataView(t){return new TextDecoder().decode(t.buffer)}}const W0e=1,U0e=5e3;let pS=0;function q0e(){return pS=(pS+1)%Number.MAX_VALUE,pS.toString()}const mS=new Map,vD=e=>{if(mS.has(e))return;const t=setTimeout(()=>{mS.delete(e),Vp({type:"REMOVE_TOAST",toastId:e})},U0e);mS.set(e,t)},G0e=(e,t)=>{switch(t.type){case"ADD_TOAST":return{...e,toasts:t.toast,...e.toasts.slice(0,W0e)};case"UPDATE_TOAST":return{...e,toasts:e.toasts.map(n=>n.id===t.toast.id?{...n,...t.toast}:n)};case"DISMISS_TOAST":{const{toastId:n}=t;return n?vD(n):e.toasts.forEach(r=>{vD(r.id)}),{...e,toasts:e.toasts.map(r=>r.id===n||n===void 0?{...r,open:!1}:r)}}case"REMOVE_TOAST":return t.toastId===void 0?{...e,toasts:}:{...e,toasts:e.toasts.filter(n=>n.id!==t.toastId)}}},L0=;let O0={toasts:};function Vp(e){O0=G0e(O0,e),L0.forEach(t=>{t(O0)})}function Hy(e){const t=q0e(),n=o=>Vp({type:"UPDATE_TOAST",toast:{...o,id:t}}),r=()=>Vp({type:"DISMISS_TOAST",toastId:t});return Vp({type:"ADD_TOAST",toast:{...e,id:t,open:!0,onOpenChange:o=>{o||r()}}}),{id:t,dismiss:r,update:n}}function P9(){conste,t=y.useState(O0);return y.useEffect(()=>(L0.push(t),()=>{const n=L0.indexOf(t);n>-1&&L0.splice(n,1)}),e),{...e,toast:Hy,dismiss:n=>Vp({type:"DISMISS_TOAST",toastId:n})}}const gS={show:e=>Hy(e)};class vS{static onConnected(t){gS.show({title:"Connexion established",description:`Connected to ${t}`,variant:"default"})}static onDisconnected(t,n){gS.show({title:"Connexion closed",description:t||"",variant:n?"default":"destructive"})}static onError(){gS.show({title:"WebSocket error",description:"An error occurred",variant:"destructive"})}}const I9="KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3NlbGYub25tZXNzYWdlPWE9Pntjb25zdHtpZDp0LGRhdGE6cn09YS5kYXRhO3RyeXtsZXQgcztpZihyIGluc3RhbmNlb2YgQXJyYXlCdWZmZXI/cz1uZXcgVGV4dERlY29kZXIoKS5kZWNvZGUocik6cz1yLHMuc3RhcnRzV2l0aCgianNvbjoiKXx8cy5zdGFydHNXaXRoKCJ7Iikpe2NvbnN0IG89cy5zdGFydHNXaXRoKCJqc29uOiIpP3Muc3Vic3RyaW5nKDUpOnMsbD1KU09OLnBhcnNlKG8pLGM9cy5zdGFydHNXaXRoKCJqc29uOiIpPyJqc29uOiI6Impzb24iLGQ9e2lkOnQsaGFuZGxlcktleTpjLHBhcnNlZERhdGE6bH07c2VsZi5wb3N0TWVzc2FnZShkKTtyZXR1cm59bGV0IGU9Il9fZGVmYXVsdF9fIjtzLmxlbmd0aD49NCYmKGU9cy5zdWJzdHJpbmcoMCw0KSk7Y29uc3Qgbj17aWQ6dCxoYW5kbGVyS2V5OmUscGFyc2VkRGF0YTpzfTtzZWxmLnBvc3RNZXNzYWdlKG4pfWNhdGNoKHMpe2NvbnN0IGU9e2lkOnQsaGFuZGxlcktleToiX19kZWZhdWx0X18iLHBhcnNlZERhdGE6bnVsbCxlcnJvcjpzIGluc3RhbmNlb2YgRXJyb3I/cy5tZXNzYWdlOiJQYXJzZSBlcnJvciJ9O3NlbGYucG9zdE1lc3NhZ2UoZSl9fX0pKCk7Cg==",V0e=e=>Uint8Array.from(atob(e),t=>t.charCodeAt(0)),yD=typeof self<"u"&&self.Blob&&new Blob(V0e(I9),{type:"text/javascript;charset=utf-8"});function Y0e(e){let t;try{if(t=yD&&(self.URL||self.webkitURL).createObjectURL(yD),!t)throw"";const n=new Worker(t,{name:e==null?void 0:e.name});return n.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(t)}),n}catch{return new Worker("data:text/javascript;base64,"+I9,{name:e==null?void 0:e.name})}finally{t&&(self.URL||self.webkitURL).revokeObjectURL(t)}}class K0e{constructor(){Le(this,"socket",null);Le(this,"messageHandlers",{});Le(this,"parserWorker",null);Le(this,"messageId",0);this.initParserWorker()}initParserWorker(){try{this.parserWorker=new Y0e({name:"wsParserWorker"}),this.parserWorker.onmessage=t=>{const{handlerKey:n,parsedData:r,error:o}=t.data;o&&console.error("WebSocket Worker Parse error:",o);const i=lp.createDataView(r);this.callMessageHandlers(n,i)},this.parserWorker.onerror=t=>{console.error("WebSocket Worker Error:",t),this.parserWorker=null}}catch{console.warn("WebSocket Worker not available, using inline parsing"),this.parserWorker=null}}isConnected(){return this.socket!==null&&this.socket.readyState===WebSocket.OPEN}connect(t){return new Promise((n,r)=>{try{if(this.socket&&this.socket.readyState===WebSocket.OPEN){n();return}if(this.socket&&this.socket.readyState===WebSocket.CONNECTING){const o=()=>{var s,a;(s=this.socket)==null||s.removeEventListener("open",o),(a=this.socket)==null||a.removeEventListener("error",i),n()},i=s=>{var a,l;(a=this.socket)==null||a.removeEventListener("open",o),(l=this.socket)==null||l.removeEventListener("error",i),r(s)};this.socket.addEventListener("open",o),this.socket.addEventListener("error",i);return}this.socket&&(this.socket.close(),this.socket=null),this.socket=new WebSocket(t),this.socket.binaryType="arraybuffer",this.socket.onopen=()=>{vS.onConnected(t),this.callMessageHandlers("__OnConnect__",new DataView(new ArrayBuffer(0))),n()},this.socket.onmessage=o=>{if(this.parserWorker){const i=++this.messageId;this.parserWorker.postMessage({id:i,data:o.data});return}this.parseMessageInline(o.data)},this.socket.onclose=o=>{vS.onDisconnected(o.reason,o.wasClean),this.socket=null,this.callMessageHandlers("__OnDisconnect__",new DataView(new ArrayBuffer(0)))},this.socket.onerror=o=>{vS.onError(),r(o)}}catch(o){r(o)}})}disconnect(){if(this.socket){try{this.socket.close(1e3,"Client disconnecting")}catch(t){console.error("WebSocket Error during disconnect:",t)}this.socket=null}}parseMessageInline(t){try{let n;if(t instanceof ArrayBuffer){const o=new DataView(t);n=lp.decodeDataView(o)}else n=t;if(n.startsWith("json:")||n.startsWith("{")){const o=lp.parseReceived(n),i=lp.createDataView(o),s=n.startsWith("json:")?"json:":"json";this.callMessageHandlers(s,i);return}const r=new DataView(t instanceof ArrayBuffer?t:new TextEncoder().encode(n).buffer);if(r.byteLength>=4){const o=String.fromCharCode(r.getInt8(0),r.getInt8(1),r.getInt8(2),r.getInt8(3)),i=this.messageHandlerso;if(i&&i.length>0){i.forEach(s=>s(this,r));return}}this.callMessageHandlers("__default__",r)}catch{const n=t instanceof ArrayBuffer?new DataView(t):new DataView(new TextEncoder().encode(String(t)).buffer);this.callMessageHandlers("__default__",n)}}send(t){if(!this.isConnected())throw new Error("Cannot send message: WebSocket is not connected");try{const n=lp.formatForSend(t);this.socket.send(n)}catch(n){throw console.error("WebSocket Error sending message:",n),n}}addConnectHandler(t){this.addMessageHandler("__OnConnect__",t)}addDisconnectHandler(t){this.addMessageHandler("__OnDisconnect__",t)}addDefaultMessageHandler(t){this.addMessageHandler("__default__",t)}addJsonMessageHandler(t){this.addMessageHandler("json:",t),this.addMessageHandler("json",t)}addMessageHandler(t,n){t in this.messageHandlers||(this.messageHandlerst=),this.messageHandlerst.push(n)}callMessageHandlers(t,n){const r=this.messageHandlerst;if(!r||r.length===0){if(t!=="__default__"){const o=this.messageHandlers.__default__;o&&o.forEach(i=>{try{i(this,n)}catch(s){console.error("WebSocket Error in default handler:",s)}})}return}r.forEach(o=>{try{o(this,n)}catch(i){console.error(`WebSocket Error in handler for ${t}:`,i)}})}}class X0e{constructor(t){Le(this,"reconnectTimeout",null);Le(this,"isConnecting",!1);Le(this,"notificationHandlers",{});Le(this,"address",null);Le(this,"endOfSession",!1);this.ws=t}setNotificationHandlers(t){this.notificationHandlers=t}setAddress(t){this.address=t}async connect(t){var r,o,i,s,a,l;const n=t||this.address;if(!n)throw new Error("ConnectionManager No address provided");if(!this.isConnecting&&!this.ws.isConnected()){this.address=n,this.isConnecting=!0,this.endOfSession=!1,Zo.dispatch(My(!0));try{await this.ws.connect(n),(o=(r=this.notificationHandlers).onConnectionStatus)==null||o.call(r,!0),this.onConnectionSuccess()}catch(c){throw this.isConnecting=!1,Zo.dispatch(My(!1)),(s=(i=this.notificationHandlers).onError)==null||s.call(i,c),(l=(a=this.notificationHandlers).onConnectionStatus)==null||l.call(a,!1),c}}}disconnect(){this.cleanup(),this.ws.disconnect()}isConnected(){return this.ws.isConnected()}handleDisconnect(){console.log("ConnectionManager Connection lost - no automatic reconnection"),this.cleanup(),this.endOfSession?Zo.dispatch(A0(null)):Zo.dispatch(A0("Connection to GPAC server lost")),this.endOfSession=!1}onConnectionSuccess(){this.isConnecting=!1,Zo.dispatch(A0(null))}stopReconnection(){this.cleanup()}markEndOfSession(){this.endOfSession=!0}cleanup(){this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.isConnecting=!1}}class Mf extends cP{updateData(t){this.data=t}updateDataAndNotify(t,n,r){this.updateData(t),this.notify(n,r)}}var To=(e=>(e.GET_ALL_FILTERS="get_all_filters",e.FILTER_ARGS_DETAILS="filter_args_details",e.STOP_FILTER_ARGS="stop_filter_args",e.UPDATE_ARG="update_arg",e.SUBSCRIBE_SESSION="subscribe_session",e.UNSUBSCRIBE_SESSION="unsubscribe_session",e.SUBSCRIBE_FILTER_STATS="subscribe_filter",e.UNSUBSCRIBE_FILTER_STATS="unsubscribe_filter",e.SUBSCRIBE_CPU_STATS="subscribe_cpu_stats",e.UNSUBSCRIBE_CPU_STATS="unsubscribe_cpu_stats",e.SUBSCRIBE_LOGS="subscribe_logs",e.UNSUBSCRIBE_LOGS="unsubscribe_logs",e.UPDATE_LOG_LEVEL="update_log_level",e.GET_LOG_STATUS="get_log_status",e.GET_PNG="get_png",e.GET_COMMAND_LINE="get_command_line",e))(To||{});class Z0e{constructor(t,n){Le(this,"pendingSessionSubscribe",null);Le(this,"pendingSessionUnsubscribe",null);Le(this,"sessionAutoUnsubscribeTimeout",null);Le(this,"sessionStatsSubscribable",new Mf());this.dependencies=t,this.isLoaded=n}ensureLoaded(){if(!this.isLoaded())throw new Error("Service not loaded");return!0}async subscribeToSession(){return this.ensureLoaded(),this.pendingSessionSubscribe?this.pendingSessionSubscribe:(this.pendingSessionSubscribe=(async()=>{try{await this.dependencies.send({type:To.SUBSCRIBE_SESSION,id:br()})}finally{this.pendingSessionSubscribe=null}})(),this.pendingSessionSubscribe)}async unsubscribeFromSession(){return this.ensureLoaded(),this.pendingSessionUnsubscribe?this.pendingSessionUnsubscribe:(this.pendingSessionUnsubscribe=(async()=>{try{await this.dependencies.send({type:To.UNSUBSCRIBE_SESSION,id:br()})}finally{this.pendingSessionUnsubscribe=null}})(),this.pendingSessionUnsubscribe)}handleSessionStats(t){this.sessionStatsSubscribable.updateDataAndNotify(t)}subscribeToSessionStats(t){this.sessionAutoUnsubscribeTimeout&&(clearTimeout(this.sessionAutoUnsubscribeTimeout),this.sessionAutoUnsubscribeTimeout=null);const n=!this.sessionStatsSubscribable.hasSubscribers,r=this.sessionStatsSubscribable.subscribe(o=>{o&&t(o)},{immediate:!0});return n&&this.subscribeToSession().catch(o=>{}),()=>{r(),this.sessionStatsSubscribable.hasSubscribers||(this.sessionAutoUnsubscribeTimeout&&clearTimeout(this.sessionAutoUnsubscribeTimeout),this.sessionAutoUnsubscribeTimeout=setTimeout(()=>{this.sessionAutoUnsubscribeTimeout=null,this.sessionStatsSubscribable.hasSubscribers||this.unsubscribeFromSession().catch(o=>{})},100))}}cleanup(){this.sessionAutoUnsubscribeTimeout&&(clearTimeout(this.sessionAutoUnsubscribeTimeout),this.sessionAutoUnsubscribeTimeout=null)}}class Q0e{constructor(){Le(this,"lastUpdateTimes",new Map);Le(this,"pendingCallbacks",new Map)}throttle(t,n,r,o){const i=Date.now(),s=this.lastUpdateTimes.get(t)||0,a=i-s;if(a>=r){this.lastUpdateTimes.set(t,i),n(o);return}const l=this.pendingCallbacks.get(t);l&&clearTimeout(l);const c=r-a,d=setTimeout(()=>{this.lastUpdateTimes.set(t,Date.now()),this.pendingCallbacks.delete(t),n(o)},c);this.pendingCallbacks.set(t,d)}clear(){this.pendingCallbacks.forEach(t=>clearTimeout(t)),this.pendingCallbacks.clear(),this.lastUpdateTimes.clear()}clearMessageType(t){const n=this.pendingCallbacks.get(t);n&&(clearTimeout(n),this.pendingCallbacks.delete(t)),this.lastUpdateTimes.delete(t)}}const J0e=500;class eye{constructor(t,n){Le(this,"pendingFilterSubscribeRequests",new Map);Le(this,"pendingFilterUnsubscribeRequests",new Map);Le(this,"messageThrottler",new Q0e);Le(this,"filterAutoUnsubscribeTimeouts",new Map);Le(this,"filterStatsSubscribableMap",new Map);this.dependencies=t,this.isLoaded=n}ensureLoaded(){if(!this.isLoaded())throw new Error("Service not loaded");return!0}async subscribeToFilterStats(t){this.ensureLoaded();const n=this.pendingFilterSubscribeRequests.get(t);if(n)return n;const r=(async()=>{try{await this.dependencies.send({type:To.SUBSCRIBE_FILTER_STATS,id:br(),idx:t})}finally{this.pendingFilterSubscribeRequests.delete(t)}})();return this.pendingFilterSubscribeRequests.set(t,r),r}async unsubscribeFromFilterStats(t){this.ensureLoaded();const n=this.pendingFilterUnsubscribeRequests.get(t);if(n)return n;const r=(async()=>{try{await this.dependencies.send({type:To.UNSUBSCRIBE_FILTER_STATS,id:br(),idx:t})}finally{this.pendingFilterUnsubscribeRequests.delete(t)}})();return this.pendingFilterUnsubscribeRequests.set(t,r),r}handleFilterStatsUpdate(t){const n=t.idx,r=this.filterStatsSubscribableMap.get(n);r&&this.messageThrottler.throttle(`filter_stats_${n}`,o=>{r.updateDataAndNotify(o)},J0e,t)}subscribeToFilterStatsUpdates(t,n){const r=this.filterAutoUnsubscribeTimeouts.get(t);r&&(clearTimeout(r),this.filterAutoUnsubscribeTimeouts.delete(t));let o=this.filterStatsSubscribableMap.get(t);const i=!o;if(!o){const a={idx:t,status:"unknown",bytes_done:0,bytes_sent:0,pck_sent:0,pck_done:0,time:0,nb_ipid:0,nb_opid:0};o=new Mf(a),this.filterStatsSubscribableMap.set(t,o)}const s=o.subscribe(a=>{a&&n(a)},{immediate:!1});return i&&this.subscribeToFilterStats(t).catch(()=>{}),()=>{s();const a=this.filterStatsSubscribableMap.get(t);if(a&&!a.hasSubscribers){const l=this.filterAutoUnsubscribeTimeouts.get(t);l&&clearTimeout(l);const c=setTimeout(()=>{this.filterAutoUnsubscribeTimeouts.delete(t);const d=this.filterStatsSubscribableMap.get(t);d&&!d.hasSubscribers&&(this.filterStatsSubscribableMap.delete(t),this.unsubscribeFromFilterStats(t).catch(()=>{}))},100);this.filterAutoUnsubscribeTimeouts.set(t,c)}}}cleanup(){this.filterAutoUnsubscribeTimeouts.forEach(t=>clearTimeout(t)),this.filterAutoUnsubscribeTimeouts.clear()}}class tye{constructor(){Le(this,"pendingLogs",);Le(this,"flushScheduled",!1);Le(this,"timeoutId",null);Le(this,"handler",null);Le(this,"FLUSH_INTERVAL",500)}addLogBatch(t){this.pendingLogs.push(t),this.flushScheduled||(this.flushScheduled=!0,this.timeoutId=setTimeout(()=>this.flush(),this.FLUSH_INTERVAL))}flush(){if(this.pendingLogs.length===0||!this.handler){this.flushScheduled=!1;return}const t=;for(const n of this.pendingLogs)n.logs&&t.push(...n.logs);this.pendingLogs=,this.flushScheduled=!1,t.length>0&&this.handler(t)}registerLogHandler(t){this.handler=t}clear(){this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null),this.pendingLogs=,this.flushScheduled=!1}}class nye{constructor(t,n){Le(this,"pendingCPUStatsSubscribe",null);Le(this,"pendingCPUStatsUnsubscribe",null);Le(this,"cpuAutoUnsubscribeTimeout",null);Le(this,"cpuStatsSubscribable",new Mf());this.dependencies=t,this.isLoaded=n}ensureLoaded(){if(!this.isLoaded())throw new Error("Service not loaded");return!0}async subscribeToCPUStats(){return this.ensureLoaded(),this.pendingCPUStatsSubscribe?this.pendingCPUStatsSubscribe:(this.pendingCPUStatsSubscribe=(async()=>{try{await this.dependencies.send({type:To.SUBSCRIBE_CPU_STATS,id:br()})}finally{this.pendingCPUStatsSubscribe=null}})(),this.pendingCPUStatsSubscribe)}async unsubscribeFromCPUStats(){return this.ensureLoaded(),this.pendingCPUStatsUnsubscribe?this.pendingCPUStatsUnsubscribe:(this.pendingCPUStatsUnsubscribe=(async()=>{try{await this.dependencies.send({type:To.UNSUBSCRIBE_CPU_STATS,id:br()})}finally{this.pendingCPUStatsUnsubscribe=null}})(),this.pendingCPUStatsUnsubscribe)}handleCPUStats(t){t&&this.cpuStatsSubscribable.updateDataAndNotify(t)}subscribeToCPUStatsUpdates(t){this.cpuAutoUnsubscribeTimeout&&(clearTimeout(this.cpuAutoUnsubscribeTimeout),this.cpuAutoUnsubscribeTimeout=null);const n=!this.cpuStatsSubscribable.hasSubscribers,r=this.cpuStatsSubscribable.subscribe(o=>{o&&o.length>0&&t(oo.length-1)},{immediate:!1});return n&&this.subscribeToCPUStats().catch(()=>{}),()=>{r(),this.cpuStatsSubscribable.hasSubscribers||(this.cpuAutoUnsubscribeTimeout&&clearTimeout(this.cpuAutoUnsubscribeTimeout),this.cpuAutoUnsubscribeTimeout=setTimeout(()=>{this.cpuAutoUnsubscribeTimeout=null,this.cpuStatsSubscribable.hasSubscribers||this.unsubscribeFromCPUStats().catch(()=>{})},100))}}cleanup(){this.cpuAutoUnsubscribeTimeout&&(clearTimeout(this.cpuAutoUnsubscribeTimeout),this.cpuAutoUnsubscribeTimeout=null)}}class rye{constructor(t,n){Le(this,"pendingFilterArgsSubscribeRequests",new Map);Le(this,"pendingFilterArgsUnsubscribeRequests",new Map);Le(this,"filterArgsSubscribables",new Map);this.dependencies=t,this.isLoaded=n}ensureLoaded(){if(!this.isLoaded())throw new Error("Service not loaded");return!0}async subscribeToFilterArgs(t){this.ensureLoaded();const n=this.pendingFilterArgsSubscribeRequests.get(t);if(n)return n;const r=(async()=>{try{await this.dependencies.send({type:To.FILTER_ARGS_DETAILS,id:br(),idx:t})}finally{this.pendingFilterArgsSubscribeRequests.delete(t)}})();return this.pendingFilterArgsSubscribeRequests.set(t,r),r}async unsubscribeFromFilterArgs(t){this.ensureLoaded();const n=this.pendingFilterArgsUnsubscribeRequests.get(t);if(n)return n;const r=(async()=>{try{await this.dependencies.send({type:To.STOP_FILTER_ARGS,id:br(),idx:t})}finally{this.pendingFilterArgsUnsubscribeRequests.delete(t)}})();return this.pendingFilterArgsUnsubscribeRequests.set(t,r),r}handleFilterArgs(t){if(!t.filter||t.filter.idx===void 0)return;const n=t.filter.idx,r=this.filterArgsSubscribables.get(n);r&&t.filter.gpac_args&&r.updateDataAndNotify(t.filter.gpac_args)}async updateFilterArg(t,n,r,o){this.ensureLoaded();try{this.log(`Updating argument '${r}' for filter ${n} (idx=${t}) to value: ${o}`),await this.dependencies.send({type:To.UPDATE_ARG,id:br(),idx:t,name:n,argName:r,newValue:o}),this.log(`Successfully updated argument '${r}' for filter ${n} (idx=${t})`)}catch(i){const s=i instanceof Error?i.message:String(i);throw this.log(`Error updating filter argument '${r}' for ${n} (idx=${t}): ${s}`,"stderr"),i}}handleUpdateArgResponse(t){}log(t,n="stdout"){n==="stderr"?console.error(t):console.log(t)}subscribeToFilterArgsDetails(t,n,r=1e3){this.ensureLoaded();let o=this.filterArgsSubscribables.get(t);const i=!o;o||(o=new Mf(),this.filterArgsSubscribables.set(t,o));const s=o.subscribe(n,{immediate:!1});return i&&this.subscribeToFilterArgs(t),()=>{s(),o.hasSubscribers||this.filterArgsSubscribables.delete(t)}}cleanup(){this.filterArgsSubscribables.clear()}}const A9="dmFyIG49T2JqZWN0LmRlZmluZVByb3BlcnR5O3ZhciByPShlLHQsaCk9PnQgaW4gZT9uKGUsdCx7ZW51bWVyYWJsZTohMCxjb25maWd1cmFibGU6ITAsd3JpdGFibGU6ITAsdmFsdWU6aH0pOmVbdF09aDt2YXIgbD0oZSx0LGgpPT5yKGUsdHlwZW9mIHQhPSJzeW1ib2wiP3QrIiI6dCxoKTsoZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7Y2xhc3MgZntjb25zdHJ1Y3Rvcigpe2wodGhpcywiYnVmZmVyIixbXSk7bCh0aGlzLCJmbHVzaFRpbWVvdXQiLG51bGwpO2wodGhpcywidG90YWxQcm9jZXNzZWQiLDApO2wodGhpcywidG90YWxTZW50IiwwKTtsKHRoaXMsImZsdXNoSW50ZXJ2YWxJZCIsbnVsbCk7dGhpcy5zdGFydEZsdXNoVGltZXIoKX1hZGRMb2dzKHMpe2lmKHRoaXMudG90YWxQcm9jZXNzZWQrPXMubGVuZ3RoLHRoaXMuYnVmZmVyLmxlbmd0aD4xMDApe3RoaXMuYnVmZmVyPXRoaXMuYnVmZmVyLnNsaWNlKC0xMDAvMik7cmV0dXJufXRoaXMuYnVmZmVyLnB1c2goLi4ucyksdGhpcy5idWZmZXIubGVuZ3RoPj01MDAmJnRoaXMuZmx1c2goKX1mbHVzaCgpe2lmKHRoaXMuYnVmZmVyLmxlbmd0aD09PTApcmV0dXJuO2NvbnN0IHM9dGhpcy5idWZmZXIuc3BsaWNlKDAsNTAwKTt0aGlzLnRvdGFsU2VudCs9cy5sZW5ndGgsc2VsZi5wb3N0TWVzc2FnZSh7dHlwZToiUFJPQ0VTU0VEX0xPR1MiLGxvZ3M6cyxjb3VudDpzLmxlbmd0aH0pLHRoaXMuYnVmZmVyLmxlbmd0aD4wJiZ0aGlzLnNjaGVkdWxlRmx1c2goKX1zY2hlZHVsZUZsdXNoKCl7dGhpcy5mbHVzaFRpbWVvdXR8fCh0aGlzLmZsdXNoVGltZW91dD1zZXRUaW1lb3V0KCgpPT57dGhpcy5mbHVzaCgpLHRoaXMuZmx1c2hUaW1lb3V0PW51bGx9LDIwMCkpfXN0YXJ0Rmx1c2hUaW1lcigpe3RoaXMuZmx1c2hJbnRlcnZhbElkPXNldEludGVydmFsKCgpPT57dGhpcy5idWZmZXIubGVuZ3RoPjAmJiF0aGlzLmZsdXNoVGltZW91dCYmdGhpcy5mbHVzaCgpfSwyMDApfWNsZWFudXAoKXt0aGlzLmZsdXNoSW50ZXJ2YWxJZCYmKGNsZWFySW50ZXJ2YWwodGhpcy5mbHVzaEludGVydmFsSWQpLHRoaXMuZmx1c2hJbnRlcnZhbElkPW51bGwpLHRoaXMuZmx1c2hUaW1lb3V0JiYoY2xlYXJUaW1lb3V0KHRoaXMuZmx1c2hUaW1lb3V0KSx0aGlzLmZsdXNoVGltZW91dD1udWxsKSx0aGlzLmJ1ZmZlcj1bXX19Y29uc3QgaT1uZXcgZjtzZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLHU9Pntjb25zdHt0eXBlOnN9PXUuZGF0YTtzd2l0Y2gocyl7Y2FzZSJQUk9DRVNTX0xPR1MiOmkuYWRkTG9ncyh1LmRhdGEubG9ncyk7YnJlYWs7Y2FzZSJDTEVBTlVQIjppLmNsZWFudXAoKTticmVhaztkZWZhdWx0OmNvbnNvbGUud2FybigiW0xvZ1dvcmtlcl0gVW5rbm93biBtZXNzYWdlIHR5cGU6IixzKX19KX0pKCk7Cg==",oye=e=>Uint8Array.from(atob(e),t=>t.charCodeAt(0)),xD=typeof self<"u"&&self.Blob&&new Blob(oye(A9),{type:"text/javascript;charset=utf-8"});function iye(e){let t;try{if(t=xD&&(self.URL||self.webkitURL).createObjectURL(xD),!t)throw"";const n=new Worker(t,{name:e==null?void 0:e.name});return n.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(t)}),n}catch{return new Worker("data:text/javascript;base64,"+A9,{name:e==null?void 0:e.name})}finally{t&&(self.URL||self.webkitURL).revokeObjectURL(t)}}class M9{constructor(t,n){Le(this,"worker",null);Le(this,"subscribers",new Set);this.serviceName=t,this.responseType=n,this.initWorker()}validateInput(t){return!0}initWorker(){try{this.worker=this.createWorker(),this.worker.onmessage=t=>{const{type:n}=t.data;if(n===this.responseType){const r=this.extractData(t.data);this.notifySubscribers(r)}},this.worker.onerror=t=>{console.error(`${this.serviceName} Worker error:`,t)}}catch(t){console.error(`${this.serviceName} Failed to create worker:`,t)}}notifySubscribers(t){this.subscribers.forEach(n=>{try{n(t)}catch(r){console.error(`${this.serviceName} Callback error:`,r)}})}process(t){!this.worker||!this.validateInput(t)||this.worker.postMessage(this.createMessage(t))}subscribe(t){return this.subscribers.add(t),()=>{this.subscribers.delete(t)}}cleanup(){if(this.worker)try{this.worker.postMessage({type:"CLEANUP"})}catch(t){console.warn(`${this.serviceName} Failed to send cleanup message:`,t)}}destroy(){this.cleanup(),this.worker&&(this.worker.terminate(),this.worker=null),this.subscribers.clear()}}class sye extends M9{constructor(){super("LogWorkerService","PROCESSED_LOGS")}createWorker(){return new iye({name:"logWorker"})}extractData(t){return t.logs}createMessage(t){return{type:"PROCESS_LOGS",logs:t}}validateInput(t){return t.length>0}processLogs(t){this.process(t)}}const bD=new sye;class aye{constructor(t,n,r){Le(this,"pendingLogSubscribe",null);Le(this,"pendingLogUnsubscribe",null);Le(this,"logAutoUnsubscribeTimeout",null);Le(this,"isSubscribed",!1);Le(this,"logEntriesSubscribable",new Mf());Le(this,"logStatusSubscribable",new Mf(null));Le(this,"workerUnsubscribe",null);this.dependencies=t,this.isLoaded=n,this.callbacks=r}ensureLoaded(){if(!this.isLoaded())throw new Error("Service not loaded");return!0}async subscribeToLogs(t="all@quiet"){return this.ensureLoaded(),this.isSubscribed?this.updateLogLevel(t):this.pendingLogSubscribe?this.pendingLogSubscribe:(this.pendingLogSubscribe=(async()=>{var n;try{await this.dependencies.send({type:To.SUBSCRIBE_LOGS,id:br(),logLevel:t}),this.isSubscribed=!0,(n=this.callbacks)!=null&&n.onLogSubscriptionChange&&this.callbacks.onLogSubscriptionChange(!0)}finally{this.pendingLogSubscribe=null}})(),this.pendingLogSubscribe)}async unsubscribeFromLogs(){return this.ensureLoaded(),this.pendingLogUnsubscribe?this.pendingLogUnsubscribe:(this.pendingLogUnsubscribe=(async()=>{var t;try{await this.dependencies.send({type:To.UNSUBSCRIBE_LOGS,id:br()}),this.isSubscribed=!1,(t=this.callbacks)!=null&&t.onLogSubscriptionChange&&this.callbacks.onLogSubscriptionChange(!1)}finally{this.pendingLogUnsubscribe=null}})(),this.pendingLogUnsubscribe)}async updateLogLevel(t){this.ensureLoaded(),await this.dependencies.send({type:To.UPDATE_LOG_LEVEL,id:br(),logLevel:t})}async getLogStatus(){this.ensureLoaded(),await this.dependencies.send({type:To.GET_LOG_STATUS,id:br()})}handleLogBatch(t){var n;(n=this.callbacks)!=null&&n.onLogsUpdate&&this.callbacks.onLogsUpdate(t)}handleLogHistory(t){var n;this.logEntriesSubscribable.updateDataAndNotify(t),(n=this.callbacks)!=null&&n.onLogsUpdate?this.callbacks.onLogsUpdate(t):console.log("LogHandler No onLogsUpdate callback available for history")}handleLogStatus(t){this.logStatusSubscribable.updateDataAndNotify(t)}handleLogConfigChanged(t){const n=this.logStatusSubscribable.getSnapshot();if(n){const r={...n,logLevel:t};this.logStatusSubscribable.updateDataAndNotify(r)}}subscribeToLogEntries(t,n="all@warning"){this.logAutoUnsubscribeTimeout&&(clearTimeout(this.logAutoUnsubscribeTimeout),this.logAutoUnsubscribeTimeout=null);const r=!this.logEntriesSubscribable.hasSubscribers;this.workerUnsubscribe||(this.workerUnsubscribe=bD.subscribe(i=>{this.logEntriesSubscribable.updateDataAndNotify(i)}));const o=this.logEntriesSubscribable.subscribe(i=>{i&&t(i)},{immediate:!0});return r&&this.subscribeToLogs(n).catch(i=>{}),()=>{o(),this.logEntriesSubscribable.hasSubscribers||(this.logAutoUnsubscribeTimeout&&clearTimeout(this.logAutoUnsubscribeTimeout),this.workerUnsubscribe&&(this.workerUnsubscribe(),this.workerUnsubscribe=null),this.logAutoUnsubscribeTimeout=setTimeout(()=>{this.logAutoUnsubscribeTimeout=null,this.logEntriesSubscribable.hasSubscribers||this.unsubscribeFromLogs().catch(i=>{})},100))}}subscribeToLogStatus(t){return this.logStatusSubscribable.subscribe(n=>t(n),{immediate:!0})}cleanup(){this.logAutoUnsubscribeTimeout&&(clearTimeout(this.logAutoUnsubscribeTimeout),this.logAutoUnsubscribeTimeout=null),this.workerUnsubscribe&&(this.workerUnsubscribe(),this.workerUnsubscribe=null),bD.cleanup()}}class lye{constructor(t){Le(this,"dependencies");Le(this,"pendingRequests",new Map);this.dependencies=t}handleIpidPropsResponse(t){var o;const n=`${t.filterIdx}-${t.ipidIdx}`,r=this.pendingRequests.get(n);if(!r){console.warn("PidPropsHandler Received unexpected response:",t);return}if(clearTimeout(r.timeout),this.pendingRequests.delete(n),(o=t.properties)!=null&&o.error){r.reject(new Error(t.properties.error));return}r.resolve(t.properties)}async fetchIpidProps(t,n){const r=`${t}-${n}`,o=this.pendingRequests.get(r);return o?new Promise((i,s)=>{const a=o.resolve,l=o.reject;o.resolve=c=>{a(c),i(c)},o.reject=c=>{l(c),s(c)}}):new Promise((i,s)=>{const a=setTimeout(()=>{this.pendingRequests.delete(r),s(new Error(`Timeout fetching IPID props (filter: ${t}, pid: ${n})`))},3e3);this.pendingRequests.set(r,{resolve:i,reject:s,timeout:a}),this.dependencies.send({message:"get_ipid_props",filterIdx:t,ipidIdx:n})})}cleanup(){this.pendingRequests.forEach(t=>clearTimeout(t.timeout)),this.pendingRequests.clear()}}class cye{constructor(t){Le(this,"dependencies");Le(this,"commandLine",null);Le(this,"callbacks",);this.dependencies=t}handleCommandLineResponse(t){this.commandLine=t.commandLine,this.callbacks.forEach(n=>n(t.commandLine)),this.callbacks=}async fetch(){return this.commandLine!==null?this.commandLine:new Promise(t=>{this.callbacks.push(t),this.callbacks.length===1&&this.dependencies.send({message:"get_command_line"})})}}class uye{constructor(t,n,r,o,i,s){Le(this,"sessionStatsHandler");Le(this,"filterStatsHandler");Le(this,"cpuStatsHandler");Le(this,"filterArgsHandler");Le(this,"logHandler");Le(this,"pidPropsHandler");Le(this,"commandLineHandler");Le(this,"messageBatcher");this.notificationHandlers=n,this.callbacks=r,this.dependencies=o,this.onMessage=i,this.isLoaded=s,this.messageBatcher=new tye,this.sessionStatsHandler=new Z0e(o,s||(()=>!0)),this.filterStatsHandler=new eye(o,s||(()=>!0)),this.cpuStatsHandler=new nye(o,s||(()=>!0)),this.filterArgsHandler=new rye(o,s||(()=>!0)),this.logHandler=new aye(o,s||(()=>!0),r),this.pidPropsHandler=new lye(o),this.commandLineHandler=new cye(o),this.messageBatcher.registerLogHandler(a=>{this.logHandler.handleLogBatch(a)})}getSessionStatsHandler(){return this.sessionStatsHandler}getFilterStatsHandler(){return this.filterStatsHandler}getCPUStatsHandler(){return this.cpuStatsHandler}getFilterArgsHandler(){return this.filterArgsHandler}getLogHandler(){return this.logHandler}getPidPropsHandler(){return this.pidPropsHandler}getCommandLineHandler(){return this.commandLineHandler}handleJsonMessage(t,n){try{const r=new TextDecoder().decode(n.buffer),o=JSON.parse(r);this.processGpacMessage(o)}catch{}}handleDefaultMessage(t,n){try{const r=new TextDecoder().decode(n.buffer);if(r.startsWith("{")){const o=JSON.parse(r);this.processGpacMessage(o)}}catch{}}processGpacMessage(t){var n;if(t.message){switch(t.message){case"filters":this.handleFiltersMessage(t);break;case"update":this.handleUpdateMessage(t);break;case"details":this.handleDetailsMessage(t);break;case"session_stats":this.handleSessionStatsMessage(t);break;case"cpu_stats":this.handleCpuStatsMessage(t);break;case"filter_stats":this.handleFilterStatsMessage(t);break;case"log_batch":this.handleLogBatchMessage(t);break;case"log_history":this.handleLogHistoryMessage(t);break;case"log_status":this.handleLogStatusMessage(t);break;case"log_config_changed":this.handleLogConfigChangedMessage(t);break;case"update_arg_response":this.handleUpdateArgResponseMessage(t);break;case"ipid_props_response":this.handleIpidPropsResponseMessage(t);break;case"command_line_response":this.commandLineHandler.handleCommandLineResponse(t);break;case"session_end":this.handleSessionEnd(t);break}(n=this.onMessage)==null||n.call(this,t)}}handleFiltersMessage(t){this.callbacks.onSetLoading(!1),this.callbacks.onUpdateGraphData(t.filters),t.filters&&t.filters.forEach(n=>{var r,o;(o=(r=this.notificationHandlers).onFilterUpdate)==null||o.call(r,n)})}handleUpdateMessage(t){Array.isArray(t.filters)&&this.callbacks.onUpdateGraphData(t.filters)}handleDetailsMessage(t){t.filter&&this.filterArgsHandler.handleFilterArgs(t)}handleSessionStatsMessage(t){t.stats&&Array.isArray(t.stats)&&(this.sessionStatsHandler.handleSessionStats(t.stats),this.callbacks.onUpdateSessionStats(t.stats))}handleCpuStatsMessage(t){t.stats&&this.cpuStatsHandler.handleCPUStats(t.stats)}handleFilterStatsMessage(t){t.idx!==void 0&&this.filterStatsHandler.handleFilterStatsUpdate(t)}handleLogBatchMessage(t){t.logs&&Array.isArray(t.logs)&&this.messageBatcher.addLogBatch(t)}handleLogHistoryMessage(t){t.logs&&Array.isArray(t.logs)&&this.logHandler.handleLogHistory(t.logs)}handleLogStatusMessage(t){t.status&&this.logHandler.handleLogStatus(t.status)}handleLogConfigChangedMessage(t){t.logLevel&&this.logHandler.handleLogConfigChanged(t.logLevel)}handleUpdateArgResponseMessage(t){this.filterArgsHandler.handleUpdateArgResponse(t)}handleIpidPropsResponseMessage(t){this.pidPropsHandler.handleIpidPropsResponse(t)}handleSessionEnd(t){this.dependencies.markEndOfSession(),this.dependencies.stopReconnection(),this.cleanup(),this.callbacks.onSessionEnd&&this.callbacks.onSessionEnd(t)}ensureConnected(){if(!this.dependencies.isConnected())throw new Error("WebSocket client is not connected");return!0}static generateMessageId(){return br()}cleanup(){this.messageBatcher.clear(),this.cpuStatsHandler.cleanup(),this.sessionStatsHandler.cleanup(),this.filterStatsHandler.cleanup(),this.logHandler.cleanup(),this.pidPropsHandler.cleanup()}}class dye{constructor(){Le(this,"currentFilterId",null);Le(this,"messageHandlers",new Set);Le(this,"status",Lr.DISCONNECTED)}setCurrentFilterId(t){this.currentFilterId=t}getCurrentFilterId(){return this.currentFilterId}connect(){throw new Error("Connect method should be implemented by the orchestrating service")}disconnect(){this.status=Lr.DISCONNECTED,this.messageHandlers.clear()}send(){throw new Error("Send method should be implemented by the orchestrating service")}registerHandler(t){return this.messageHandlers.add(t),()=>this.unregisterHandler(t)}unregisterHandler(t){this.messageHandlers.delete(t)}getStatus(){return this.status}isConnected(){return this.status===Lr.CONNECTED}setStatus(t){this.status=t,this.messageHandlers.forEach(n=>{var r;(r=n.onStatusChange)==null||r.call(n,t)})}notifyHandlers(t){this.messageHandlers.forEach(n=>{var r,o;try{(r=n.onMessage)==null||r.call(n,t)}catch(i){console.error("GpacCoreService Handler error:",i),(o=n.onError)==null||o.call(n,i instanceof Error?i:new Error(String(i)))}})}}const fye={mode:"session",sessionStats:{},previousSessionStats:{},selectedFilterId:null,lastUpdate:null,isLoading:!1,subscribedComponents:,isSubscribed:!1},j9=Sc({name:"sessionStats",initialState:fye,reducers:{updateSessionStats:(e,t)=>{e.previousSessionStats=e.sessionStats;const n={};t.payload.forEach(r=>{const o=e.sessionStatsr.idx.toString(),i=r.is_eos||(o==null?void 0:o.is_eos)||!1;nr.idx.toString()={...r,is_eos:i}}),e.sessionStats=n,e.lastUpdate=Date.now(),e.isLoading=!1},switchToSessionMode:e=>{e.mode="session",e.selectedFilterId=null,e.isLoading=!0},switchToFilterMode:(e,t)=>{e.mode="filter",e.selectedFilterId=t.payload,e.isLoading=!0},setLoading:(e,t)=>{e.isLoading=t.payload},clearSessionStats:e=>{e.sessionStats={},e.lastUpdate=null},subscribeToSessionStats:(e,t)=>{e.subscribedComponents.includes(t.payload)||e.subscribedComponents.push(t.payload),e.isSubscribed=e.subscribedComponents.length>0},unsubscribeFromSessionStats:(e,t)=>{e.subscribedComponents=e.subscribedComponents.filter(n=>n!==t.payload),e.isSubscribed=e.subscribedComponents.length>0,e.isSubscribed||(e.sessionStats={},e.lastUpdate=null)},resetSessionStats:e=>{e.sessionStats={},e.lastUpdate=null,e.isLoading=!1}}}),{updateSessionStats:hye,switchToSessionMode:z4e,switchToFilterMode:H4e,setLoading:B4e,clearSessionStats:W4e,subscribeToSessionStats:U4e,unsubscribeFromSessionStats:q4e,resetSessionStats:G4e}=j9.actions,pye=j9.reducer,mye="gpac-logs-config",gye=()=>{try{const e=localStorage.getItem(mye),t=e?JSON.parse(e):{};return{currentTool:t.currentTool||Ot.FILTER,levelsByTool:t.levelsByTool||{},defaultAllLevel:t.defaultAllLevel||At.QUIET,visibleToolsFilter:t.visibleToolsFilter||,buffers:{},maxEntriesPerTool:500,isSubscribed:!1,highlightedLogId:null,uiFilter:null,viewMode:"perTool",timestampMode:"relative",lastSentConfig:{levelsByTool:{},defaultAllLevel:null},alertsByFilterKey:{}}}catch{return{currentTool:Ot.FILTER,levelsByTool:{},defaultAllLevel:At.QUIET,visibleToolsFilter:,buffers:{},maxEntriesPerTool:500,isSubscribed:!1,highlightedLogId:null,uiFilter:null,viewMode:"perTool",timestampMode:"relative",lastSentConfig:{levelsByTool:{},defaultAllLevel:null},alertsByFilterKey:{}}}},vye=()=>{const e=gye();return Object.values(Ot).forEach(t=>{e.bufferst=}),e},yye=vye(),xye={setTool:(e,t)=>{e.currentTool=t.payload},setToolLevel:(e,t)=>{const{tool:n,level:r}=t.payload;e.levelsByTooln=r},setDefaultAllLevel:(e,t)=>{const n=t.payload;e.defaultAllLevel=n,e.levelsByTool={}},toggleToolInVisibleFilter:(e,t)=>{const n=t.payload,r=e.visibleToolsFilter.indexOf(n);r===-1?e.visibleToolsFilter.push(n):e.visibleToolsFilter.splice(r,1)},clearVisibleToolsFilter:e=>{e.visibleToolsFilter=},selectAllToolsInFilter:(e,t)=>{e.visibleToolsFilter=...t.payload},restoreConfig:(e,t)=>{const{currentTool:n,levelsByTool:r,defaultAllLevel:o,visibleToolsFilter:i}=t.payload;n&&(e.currentTool=n),r&&(e.levelsByTool={...e.levelsByTool,...r}),o&&(e.defaultAllLevel=o),i&&(e.visibleToolsFilter=i)},markConfigAsSent:e=>{e.lastSentConfig={levelsByTool:{...e.levelsByTool},defaultAllLevel:e.defaultAllLevel}}},bye={appendLogs:(e,t)=>{const{tool:n,logs:r}=t.payload;if(r.length===0)return;let o=e.buffersn;o||(o=e.buffersn=),o.push(...r);const i=o.length-e.maxEntriesPerTool;i>0&&o.splice(0,i)},appendLogsForAllTools:(e,t)=>{const n=t.payload;if(n.length===0)return;const{buffers:r,maxEntriesPerTool:o,alertsByFilterKey:i}=e,s=new Set;for(let a=0;a<n.length;a++){const l=na,c=l.tool;let d=rc;if(d||(d=rc=),d.push(l),s.add(c),l.level===1||l.level===2||l.level===3){if(l.caller!==null&&l.caller!==void 0){const f=String(l.caller);if||(if={warnings:0,errors:0,info:0}),l.level===1?if.errors+=1:l.level===2?if.warnings+=1:if.info+=1}if(l.thread_id!==void 0){const f=`t:${l.thread_id}`;if||(if={warnings:0,errors:0,info:0}),l.level===1?if.errors+=1:l.level===2?if.warnings+=1:if.info+=1}}}for(const a of s){const l=ra,c=l.length-o;c>0&&l.splice(0,c)}},setMaxEntriesPerTool:(e,t)=>{e.maxEntriesPerTool=t.payload,Object.keys(e.buffers).forEach(n=>{const r=e.buffersn,o=r.length-t.payload;o>0&&r.splice(0,o)})}},wye={setSubscriptionStatus:(e,t)=>{e.isSubscribed=t.payload},setHighlightedLog:(e,t)=>{e.highlightedLogId=t.payload},setUIFilter:(e,t)=>{e.uiFilter=t.payload,e.viewMode="globalFilter"},clearUIFilter:e=>{e.uiFilter=null,e.viewMode="perTool"},toggleTimestampMode:e=>{e.timestampMode=e.timestampMode==="relative"?"absolute":"relative"}},Sye={clearAllAlerts:e=>{e.alertsByFilterKey={}},clearFilterAlerts:(e,t)=>{delete e.alertsByFilterKeyt.payload}},L9=Sc({name:"logs",initialState:yye,reducers:{...xye,...bye,...wye,...Sye}}),{setTool:wD,setToolLevel:_ye,setDefaultAllLevel:Cye,toggleToolInVisibleFilter:Eye,clearVisibleToolsFilter:Nye,selectAllToolsInFilter:Rye,appendLogs:V4e,appendLogsForAllTools:Tye,setMaxEntriesPerTool:Y4e,setSubscriptionStatus:kye,restoreConfig:K4e,markConfigAsSent:Pye,setHighlightedLog:Iye,setUIFilter:Aye,clearUIFilter:Mye,toggleTimestampMode:jye,clearAllAlerts:X4e,clearFilterAlerts:Z4e}=L9.actions,Lye=L9.reducer,Oye=()=>({onUpdateGraphData:e=>Zo.dispatch(Pme(e)),onSetLoading:e=>Zo.dispatch(My(e)),onUpdateSessionStats:e=>Zo.dispatch(hye(e)),onLogsUpdate:e=>{Zo.dispatch(Tye(e))},onLogSubscriptionChange:e=>Zo.dispatch(kye(e))}),Dye=()=>{Zo.dispatch(jW(null))};class Fye extends cP{constructor(){super({subscribedFilterIdxs:});Le(this,"refCounts",new Map)}updateStateFromRefCounts(){const n=Array.from(this.refCounts.entries()).filter((,r)=>r>0).map((r)=>r).sort((r,o)=>r-o);this.data={subscribedFilterIdxs:n}}addFilter(n){const r=this.refCounts.get(n)??0,o=r+1;this.refCounts.set(n,o),r===0&&(this.updateStateFromRefCounts(),this.notify("FILTER_SUBSCRIPTIONS_CHANGED"))}removeFilter(n){const r=this.refCounts.get(n)??0;if(r===0)return;const o=r-1;o<=0?this.refCounts.delete(n):this.refCounts.set(n,o),o===0&&(this.updateStateFromRefCounts(),this.notify("FILTER_SUBSCRIPTIONS_CHANGED"))}clear(){this.refCounts.size!==0&&(this.refCounts.clear(),this.updateStateFromRefCounts(),this.notify("FILTER_SUBSCRIPTIONS_CHANGED"))}}const D0={setupWebSocketHandlers(e,t,n,r){e.ws.addConnectHandler(()=>{var o;n({type:"get_all_filters"}),(o=t.onConnectionStatusChange)==null||o.call(t,Lr.CONNECTED)}),e.ws.addJsonMessageHandler(e.messageHandler.handleJsonMessage.bind(e.messageHandler)),e.ws.addDefaultMessageHandler(e.messageHandler.handleDefaultMessage.bind(e.messageHandler)),e.ws.addDisconnectHandler(()=>{var o,i;e.isLoaded=!1,e.readyPromise=null,e.messageHandler.cleanup(),(o=t.onConnectionStatusChange)==null||o.call(t,Lr.DISCONNECTED),(i=t.onDisconnect)==null||i.call(t),r()})},async load(e,t,n,r){var o,i;if(e.isLoaded)return!0;try{if((o=t.onConnectionStatusChange)==null||o.call(t,Lr.CONNECTING),await e.connectionManager.connect(n),await new Promise(s=>setTimeout(s,100)),!r())throw new Error("Failed to establish connection");return e.isLoaded=!0,!0}catch(s){throw e.isLoaded=!1,(i=t.onConnectionStatusChange)==null||i.call(t,Lr.ERROR),s}},cleanup(e){e.coreService.setCurrentFilterId(null),e.filterSubscriptionsStore.clear()},disconnect(e){console.log("GpacService Disconnecting service"),D0.cleanup(e),e.messageHandler.cleanup(),e.connectionManager.disconnect(),Dye()}},Nd={async connect(e,t){return e.connectionManager.connect(t)},isConnected(e){return e.connectionManager.isConnected()},isLoaded(e){return e.isLoaded&&e.connectionManager.isConnected()},async ready(e,t,n){return e.readyPromise||(e.readyPromise=n(t).then(()=>{})),e.readyPromise},getStatus(e){return e.coreService.getStatus()}},jd={async send(e,t){return Promise.resolve(jd.sendMessage(e,t))},sendMessage(e,t){if(!e.ws.isConnected())throw new Error("GpacService WebSocket not connected");const n={message:t.type,...t},r=JSON.stringify(n);e.ws.send(r)},registerHandler(e,t){return e.coreService.registerHandler(t)},unregisterHandler(e,t){e.coreService.unregisterHandler(t)},setNotificationHandlers(e,t){e.notificationHandlers=t,e.connectionManager.setNotificationHandlers(t)}},Bs={getFilterDetails(e,t,n){const r=e.coreService.getCurrentFilterId();r!==null&&r!==t&&n({type:"stop_details",idx:r}),e.coreService.setCurrentFilterId(t),n({type:"filter_args_details",id:br(),idx:t})},setCurrentFilterId(e,t){e.coreService.setCurrentFilterId(t)},getCurrentFilterId(e){return e.coreService.getCurrentFilterId()},async subscribeToFilterArgs(e,t){return e.messageHandler.getFilterArgsHandler().subscribeToFilterArgs(t)},unsubscribeFromFilter(e,t,n){const r=parseInt(t,10);isNaN(r)||n({type:"stop_details",idx:r})},async updateFilterArg(e,t,n,r,o,i){if(!i())throw new Error("Service not loaded");return e.messageHandler.getFilterArgsHandler().updateFilterArg(t,n,r,o)},async getPidProps(e,t,n,r){if(!r())throw new Error("Service not loaded");if(typeof t!="number"||typeof n!="number")throw new Error("filterIdx and ipidIdx must be numbers");const o=await e.messageHandler.getPidPropsHandler().fetchIpidProps(t,n);return Object.values(o)},async getCommandLine(e,t){if(!t())throw new Error("Service not loaded");return e.messageHandler.getCommandLineHandler().fetch()},getFilterArgsHandler(e){return e.messageHandler.getFilterArgsHandler()},getFilterSubscriptions(e){return e.filterSubscriptionsStore}},SD={getLogs(e){return e.messageHandler.getLogHandler()},async subscribe(e,t,n,r){if(!r())throw new Error("Service not loaded");const o=br();switch(t.type){case ss.SESSION_STATS:return e.messageHandler.getSessionStatsHandler().subscribeToSessionStats(i=>{n({data:i,timestamp:Date.now(),subscriptionId:o})});case ss.FILTER_STATS:{if(t.filterIdx===void 0)throw new Error("filterIdx is required for FILTER_STATS subscription");const i=t.filterIdx;e.filterSubscriptionsStore.addFilter(i);const s=e.messageHandler.getFilterStatsHandler().subscribeToFilterStatsUpdates(i,a=>{n({data:a,timestamp:Date.now(),subscriptionId:o})});return()=>{e.filterSubscriptionsStore.removeFilter(i),s()}}case ss.CPU_STATS:return e.messageHandler.getCPUStatsHandler().subscribeToCPUStatsUpdates(i=>{n({data:i,timestamp:Date.now(),subscriptionId:o})});case ss.LOGS:return e.messageHandler.getLogHandler().subscribeToLogEntries(i=>{n({data:i,timestamp:Date.now(),subscriptionId:o})},t.logLevel);case ss.FILTER_ARGS_DETAILS:if(typeof t.filterIdx!="number")throw new Error("filterIdx is required for FILTER_ARGS_DETAILS subscription");return e.messageHandler.getFilterArgsHandler().subscribeToFilterArgsDetails(t.filterIdx,i=>{n({data:i,timestamp:Date.now(),subscriptionId:o})},t.interval||1e3);default:throw new Error(`Unsupported subscription type: ${t.type}`)}}},cu=class cu{constructor(){Le(this,"state");Le(this,"callbacks",{});Le(this,"onMessage");Le(this,"onError");Le(this,"onDisconnect");Le(this,"onConnectionStatusChange");const t=new K0e,n=new dye,r=new X0e(t),o=new Fye,i=Oye(),s={isConnected:()=>this.isConnected(),send:l=>this.send(l),stopReconnection:()=>r.stopReconnection(),markEndOfSession:()=>r.markEndOfSession()},a=new uye(()=>!1,{},i,s,l=>{var c;(c=this.onMessage)==null||c.call(this,l),n.notifyHandlers(l)});this.state={ws:t,coreService:n,connectionManager:r,messageHandler:a,notificationHandlers:{},filterSubscriptionsStore:o,isLoaded:!1,readyPromise:null},D0.setupWebSocketHandlers(this.state,this.callbacks,l=>this.sendMessage(l),()=>this.state.connectionManager.handleDisconnect())}static getInstance(){return cu.instance||(cu.instance=new cu),cu.instance}async load(t){return D0.load(this.state,this.callbacks,t,()=>this.isConnected())}async connect(t){return Nd.connect(this.state,t)}async connectService(t){return Nd.connect(this.state,t)}disconnect(){D0.disconnect(this.state)}isConnected(){return Nd.isConnected(this.state)}isLoaded(){return Nd.isLoaded(this.state)}ready(t){return Nd.ready(this.state,t,n=>this.load(n))}getStatus(){return Nd.getStatus(this.state)}send(t){return jd.send(this.state,t)}sendMessage(t){jd.sendMessage(this.state,t)}registerHandler(t){return jd.registerHandler(this.state,t)}unregisterHandler(t){jd.unregisterHandler(this.state,t)}setNotificationHandlers(t){jd.setNotificationHandlers(this.state,t)}getFilterDetails(t){Bs.getFilterDetails(this.state,t,n=>this.sendMessage(n))}setCurrentFilterId(t){Bs.setCurrentFilterId(this.state,t)}getCurrentFilterId(){return Bs.getCurrentFilterId(this.state)}async subscribeToFilterArgs(t){return Bs.subscribeToFilterArgs(this.state,t)}unsubscribeFromFilter(t){Bs.unsubscribeFromFilter(this.state,t,n=>this.sendMessage(n))}async updateFilterArg(t,n,r,o){return Bs.updateFilterArg(this.state,t,n,r,o,()=>this.isLoaded())}getFilterArgsHandler(){return Bs.getFilterArgsHandler(this.state)}get filterSubscriptions(){return Bs.getFilterSubscriptions(this.state)}async getPidProps(t,n){return Bs.getPidProps(this.state,t,n,()=>this.isLoaded())}async getCommandLine(){return Bs.getCommandLine(this.state,()=>this.isLoaded())}get logs(){return SD.getLogs(this.state)}async subscribe(t,n){return SD.subscribe(this.state,t,n,()=>this.isLoaded())}};Le(cu,"instance",null);let $R=cu;const Mo=$R.getInstance(),Do=e=>e.logs,O9=Nt(Do,e=>e.currentTool),Jf=Nt(Do,e=>e.levelsByTool),eh=Nt(Do,e=>e.defaultAllLevel),mP=Nt(Do,e=>e.visibleToolsFilter),D9=Nt(Do,e=>e.uiFilter),$ye=Nt(Do,e=>e.viewMode),F9=Nt(Do,e=>e.timestampMode),zye=Nt(Do,e=>e.isSubscribed),Hye=Nt(Do,e=>{const t={};return Object.entries(e.buffers).forEach((n,r)=>{tn=r.length}),t}),Bye=Nt(Do,e=>{const t={},n=jrAt.ERROR,r=jrAt.WARNING;return Object.entries(e.buffers).forEach((o,i)=>{const s=i.filter(a=>a.level===n||a.level===r).length;to=s}),t}),Wye=Nt(Jf,eh,(e,t)=>{const n=;return n.push(`all@${t}`),Object.entries(e).forEach((o,i)=>{n.push(`${o}@${i}`)}),n.join(",")}),Uye=Nt(Do,e=>{const{levelsByTool:t,defaultAllLevel:n,lastSentConfig:r}=e,o=,i=r.defaultAllLevel===null;return(i||n!==r.defaultAllLevel)&&o.push(`all@${n}`),Object.entries(t).forEach((a,l)=>{const c=r.levelsByToola;i?o.push(`${a}@${l}`):c!==void 0&&l!==c?o.push(`${a}@${l}`):c===void 0&&o.push(`${a}@${l}`)}),i||Object.entries(r.levelsByTool).forEach((a,l)=>{a in t||o.push(`${a}@${n}`)}),o.join(":")}),qye=(e,t)=>!e||!t?!1:e.num!==t.num||e.den!==t.den,$9=e=>e.sessionStats,Gye=Nt($9,e=>e.sessionStats),Vye=Nt($9,e=>e.previousSessionStats),gP=Nt(Gye,Vye,(e,t)=>{const n={};return Object.keys(e).forEach(r=>{const o=er,i=tr;if(!i||o.is_eos){nr=!1;return}const s=o.last_ts_sent&&i.last_ts_sent&&qye(o.last_ts_sent,i.last_ts_sent),a=typeof o.pck_sent=="number"&&typeof i.pck_sent=="number"&&o.pck_sent!==i.pck_sent||typeof o.pck_done=="number"&&typeof i.pck_done=="number"&&o.pck_done!==i.pck_done,l=typeof o.bytes_sent=="number"&&typeof i.bytes_sent=="number"&&o.bytes_sent!==i.bytes_sent||typeof o.bytes_done=="number"&&typeof i.bytes_done=="number"&&o.bytes_done!==i.bytes_done,c=s||a||l;nr=!c}),n}),z9=e=>Nt(gP,t=>te??!1),rg=e=>e.graph,Yye=Nt(rg,gP,(e,t)=>e.nodes.map(n=>{const{status:r,...o}=n.data,i=tn.id??!1;return{...n,data:{...o,isStalled:i}}}),{memoizeOptions:{resultEqualityCheck:Yx.isEqual}}),Kye=Nt(rg,e=>e.edges,{memoizeOptions:{resultEqualityCheck:Yx.isEqual}}),Xye=Nt(rg,e=>e.isLoading),Zye=Nt(rg,e=>e.error);Nt(rg,e=>e.selectedFilterDetails,{memoizeOptions:{resultEqualityCheck:Yx.isEqual}});function Qye(e){const t=;return e.caller!==null&&e.caller!==void 0&&t.push(String(e.caller)),e.thread_id!==void 0&&t.push(`t:${e.thread_id}`),t}const Jye=(e,t)=>{const r={At.QUIET:0,At.ERROR:1,At.WARNING:2,At.INFO:3,At.DEBUG:4}t;return t===At.QUIET&&r===0?e.filter(o=>o.level===0):e.filter(o=>o.level<=r)},H9=Nt(Do,Jf,eh,mP,D9,F9,(e,t,n,r,o,i)=>{let s;const a=o&&(o.levels&&o.levels.length>0||o.filterKeys&&o.filterKeys.length>0);if(a||r&&r.length>1)s=Object.keys(e.buffers).map(d=>e.buffersd||).flat().sort((d,f)=>{const p=i==="absolute"&&d.timestampMs?d.timestampMs:d.timestamp,g=i==="absolute"&&f.timestampMs?f.timestampMs:f.timestamp;return p-g});else{s=e.bufferse.currentTool||;const c=te.currentTool??n;s=Jye(s,c),s=...s.sort((d,f)=>{const p=i==="absolute"&&d.timestampMs?d.timestampMs:d.timestamp,g=i==="absolute"&&f.timestampMs?f.timestampMs:f.timestamp;return p-g})}if(a){if(o.levels&&o.levels.length>0){const c=o.levels.map(d=>jrd);s=s.filter(d=>c.includes(d.level))}o.filterKeys&&o.filterKeys.length>0&&(s=s.filter(c=>Qye(c).some(f=>{var p;return(p=o.filterKeys)==null?void 0:p.includes(f)})))}return s}),exe=Nt(H9,e=>e.filter(t=>t.level===1||t.level===2).length),txe=Nt(O9,Jf,eh,mP,(e,t,n,r)=>({currentTool:e,levelsByTool:t,defaultAllLevel:n,visibleToolsFilter:r})),nxe=()=>Nt(e=>e.filterArgument.updates,(e,t)=>t,(e,t,n)=>n,(e,t,n)=>Array.isArray(n)?n.reduce((r,o)=>{const i=`${t}_${o.name}`,s=ei;return s&&(ro.name=s),r},{}):{}),rxe=Nt(Do,Jf,eh,(e,t,n)=>{const r=new Map,o=n?jrn:jrAt.WARNING;Object.entries(t).forEach((p,g)=>{r.set(p,jrg||o)});const i=jrAt.DEBUG,s=jrAt.ERROR,a=jrAt.WARNING,l=jrAt.INFO;let c=0,d=0,f=0;for(constp,gof Object.entries(e.buffers)){const v=r.get(p)??o;for(const b of g)b.level!==i&&(b.level>v||(b.level===s?c++:b.level===a?d++:b.level===l&&f++))}return{error:c,warning:d,info:f}}),B9=Nt(Do,e=>e.alertsByFilterKey),W9=e=>Nt(B9,t=>te||null),oxe=Nt(B9,e=>{const t=;for(constn,rof Object.entries(e))if(n.startsWith("t:")){const o=parseInt(n.substring(2),10);if(!isNaN(o)){const i=r.errors||0,s=r.warnings||0,a=Math.min(r.info||0,100),l=i+s+a;l>0&&t.push({threadId:o,errors:i,warnings:s,info:a,total:l})}}return t.sort((n,r)=>r.total-n.total)}),U9=e=>e.monitoredFilter,q9=Nt(U9,(e,t)=>t,(e,t)=>{const n=e.dataByFiltert;if(n!=null&&n.network)return{upload:n.network.upload,download:n.network.download}}),ixe=Nt(q9,e=>(e==null?void 0:e.upload)||),sxe=Nt(q9,e=>(e==null?void 0:e.download)||);Nt(U9,e=>e.maxPoints);const G9=e=>e.connections,V9=e=>G9(e).connectionsById,Y9=Nt(V9,e=>Object.values(e)),axe=e=>G9(e).activeConnectionId,nb=e=>{const t=axe(e);return t&&V9(e)t||null};function rb({enabled:e=!0,timeoutMs:t=5e3}={}){constn,r=y.useState(!1),o,i=y.useState(!1),s,a=y.useState(null),l=Xe(nb),c=l==null?void 0:l.id,d=l==null?void 0:l.address;return y.useEffect(()=>{if(!e||!d){r(!1),i(!1),a(null);return}let f=!1;i(!0),a(null);const p=new Promise((g,v)=>setTimeout(()=>v(new Error("Service timeout")),t));return Promise.race(Mo.ready(d),p).then(()=>{f||r(!0)}).catch(g=>{f||a(g instanceof Error?g:new Error("Service failed"))}).finally(()=>{f||i(!1)}),()=>{f=!0}},e,t,c,d),{isReady:n,isLoading:o,error:s}}function lxe(e={}){const{enabled:t=!0,maxEntries:n=2e3}=e,r,o=y.useState(),i,s=y.useState(!1),{isReady:a}=rb({enabled:t}),l=Xe(Wye),c=y.useCallback(p=>{p.length!==0&&o(g=>{const v=g.concat(p);return v.length<=n?v:v.slice(-n)})},n);y.useEffect(()=>{if(!t||!a){o(),s(!1);return}let p=!0;return(async()=>{try{const v=await Mo.subscribe({type:ss.LOGS,logLevel:l},b=>{b.data&&p&&c(b.data)});p?s(!0):v()}catch(v){console.error("useLogs Subscription failed:",v),p&&(o(),s(!1))}})(),()=>{p=!1,s(!1)}},t,a,c,l);const d=y.useMemo(()=>r,r);return{logs:y.useDeferredValue(d),isSubscribed:i}}function cxe(e){constt,n=y.useState(e),r=y.useRef(e),o=y.useRef();return y.useEffect(()=>{if(r.current=e,!(t===e||o.current!==void 0))return o.current=requestAnimationFrame(()=>{n(r.current),o.current=void 0}),()=>{o.current!==void 0&&(cancelAnimationFrame(o.current),o.current=void 0)}},e,t),t}const uxe="gpac-logs-config";function dxe(){const e=Hn(),t=Xe(O9),n=Xe(Jf),r=Xe(eh),o=Xe(mP),i=Xe(H9),s=Xe(txe),a=cxe(i),l=y.useCallback(v=>{e(wD(v))},e),c=y.useCallback((v,b)=>{e(_ye({tool:v,level:b})),e(wD(v)),Hy({title:"Log Level Updated",description:`${v.toUpperCase()} set to ${b.toUpperCase()}`})},e),d=y.useCallback(v=>{e(Cye(v)),Hy({title:"Default Level Updated",description:`Default level for all tools set to ${v.toUpperCase()}`})},e),f=y.useCallback(v=>{e(Eye(v))},e),p=y.useCallback(()=>{e(Nye())},e),g=y.useCallback(v=>{e(Rye(v))},e);return y.useEffect(()=>{localStorage.setItem(uxe,JSON.stringify(s))},s),y.useMemo(()=>({currentTool:t,levelsByTool:n,defaultAllLevel:r,visibleToolsFilter:o,visibleLogs:a,setTool:l,setToolLevel:c,setDefaultAllLevel:d,toggleToolFilter:f,clearFilter:p,selectAllTools:g}),t,n,r,o,a,l,c,d,f,p,g)}function fxe(e){return e.trim()?e.split(":").map(t=>{constn,r=t.split("@");return{tool:n,level:r}}):}function hxe(e,t,n){const r=;let o="";for(const i of e){const{tool:s,level:a}=i,l=s==="all"?t:ns||t;Fme.needsBackendCall(l,a)&&(r.push(`${s}@${a}`),o||(o=`${s}@${a} requires more verbosity than backend's current ${s}@${l}`))}return{needsBackend:r.length>0,backendOnlyChanges:r.join(":"),reason:o}}const pxe=e=>e.logs;function mxe(){const e=Hn(),t=Xe(Uye),n=Xe(zye),r=Xe(Jf),o=Xe(eh),i=Xe(pxe),s=y.useRef(""),a=y.useCallback(async(l,c)=>{try{if(l.trim()==="")return;await Mo.logs.updateLogLevel(l),e(Pye()),s.current=t}catch(d){console.error("useLogsService Failed to update backend config:",d)}},e,t);return y.useEffect(()=>{if(!n||t===s.current)return;const l=fxe(t);if(l.length===0)return;const c=hxe(l,o,i.lastSentConfig.levelsByTool);c.needsBackend?a(c.backendOnlyChanges,c.reason):s.current=t},t,n,r,o,i.lastSentConfig.levelsByTool,a,e),{currentConfig:t,updateConfig:a}}function yt(e,t,{checkForDefaultPrevented:n=!0}={}){return function(o){if(e==null||e(o),n===!1||!o.defaultPrevented)return t==null?void 0:t(o)}}function _D(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}function ob(...e){return t=>{let n=!1;const r=e.map(o=>{const i=_D(o,t);return!n&&typeof i=="function"&&(n=!0),i});if(n)return()=>{for(let o=0;o<r.length;o++){const i=ro;typeof i=="function"?i():_D(eo,null)}}}}function Pt(...e){return y.useCallback(ob(...e),e)}function gxe(e,t){const n=y.createContext(t),r=i=>{const{children:s,...a}=i,l=y.useMemo(()=>a,Object.values(a));return h.jsx(n.Provider,{value:l,children:s})};r.displayName=e+"Provider";function o(i){const s=y.useContext(n);if(s)return s;if(t!==void 0)return t;throw new Error(`\`${i}\` must be used within \`${e}\``)}returnr,o}function to(e,t=){let n=;function r(i,s){const a=y.createContext(s),l=n.length;n=...n,s;const c=f=>{var x;const{scope:p,children:g,...v}=f,b=((x=p==null?void 0:pe)==null?void 0:xl)||a,_=y.useMemo(()=>v,Object.values(v));return h.jsx(b.Provider,{value:_,children:g})};c.displayName=i+"Provider";function d(f,p){var b;const g=((b=p==null?void 0:pe)==null?void 0:bl)||a,v=y.useContext(g);if(v)return v;if(s!==void 0)return s;throw new Error(`\`${f}\` must be used within \`${i}\``)}returnc,d}const o=()=>{const i=n.map(s=>y.createContext(s));return function(a){const l=(a==null?void 0:ae)||i;return y.useMemo(()=>({`__scope${e}`:{...a,e:l}}),a,l)}};return o.scopeName=e,r,vxe(o,...t)}function vxe(...e){const t=e0;if(e.length===1)return t;const n=()=>{const r=e.map(o=>({useScope:o(),scopeName:o.scopeName}));return function(i){const s=r.reduce((a,{useScope:l,scopeName:c})=>{const f=l(i)`__scope${c}`;return{...a,...f}},{});return y.useMemo(()=>({`__scope${t.scopeName}`:s}),s)}};return n.scopeName=t.scopeName,n}function jf(e){const t=yxe(e),n=y.forwardRef((r,o)=>{const{children:i,...s}=r,a=y.Children.toArray(i),l=a.find(bxe);if(l){const c=l.props.children,d=a.map(f=>f===l?y.Children.count(c)>1?y.Children.only(null):y.isValidElement(c)?c.props.children:null:f);return h.jsx(t,{...s,ref:o,children:y.isValidElement(c)?y.cloneElement(c,void 0,d):null})}return h.jsx(t,{...s,ref:o,children:i})});return n.displayName=`${e}.Slot`,n}function yxe(e){const t=y.forwardRef((n,r)=>{const{children:o,...i}=n;if(y.isValidElement(o)){const s=Sxe(o),a=wxe(i,o.props);return o.type!==y.Fragment&&(a.ref=r?ob(r,s):s),y.cloneElement(o,a)}return y.Children.count(o)>1?y.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var K9=Symbol("radix.slottable");function xxe(e){const t=({children:n})=>h.jsx(h.Fragment,{children:n});return t.displayName=`${e}.Slottable`,t.__radixId=K9,t}function bxe(e){return y.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===K9}function wxe(e,t){const n={...t};for(const r in t){const o=er,i=tr;/^onA-Z/.test(r)?o&&i?nr=(...a)=>{const l=i(...a);return o(...a),l}:o&&(nr=o):r==="style"?nr={...o,...i}:r==="className"&&(nr=o,i.filter(Boolean).join(" "))}return{...e,...n}}function Sxe(e){var r,o;let t=(r=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:r.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=(o=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:o.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var _xe="a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul",Ze=_xe.reduce((e,t)=>{const n=jf(`Primitive.${t}`),r=y.forwardRef((o,i)=>{const{asChild:s,...a}=o,l=s?n:t;return typeof window<"u"&&(windowSymbol.for("radix-ui")=!0),h.jsx(l,{...a,ref:i})});return r.displayName=`Primitive.${t}`,{...e,t:r}},{});function ib(e,t){e&&al.flushSync(()=>e.dispatchEvent(t))}function En(e){const t=y.useRef(e);return y.useEffect(()=>{t.current=e}),y.useMemo(()=>(...n)=>{var r;return(r=t.current)==null?void 0:r.call(t,...n)},)}function X9(e,t=globalThis==null?void 0:globalThis.document){const n=En(e);y.useEffect(()=>{const r=o=>{o.key==="Escape"&&n(o)};return t.addEventListener("keydown",r,{capture:!0}),()=>t.removeEventListener("keydown",r,{capture:!0})},n,t)}var Cxe="DismissableLayer",zR="dismissableLayer.update",Exe="dismissableLayer.pointerDownOutside",Nxe="dismissableLayer.focusOutside",CD,Z9=y.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),sb=y.forwardRef((e,t)=>{const{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:o,onFocusOutside:i,onInteractOutside:s,onDismiss:a,...l}=e,c=y.useContext(Z9),d,f=y.useState(null),p=(d==null?void 0:d.ownerDocument)??(globalThis==null?void 0:globalThis.document),,g=y.useState({}),v=Pt(t,N=>f(N)),b=Array.from(c.layers),_=...c.layersWithOutsidePointerEventsDisabled.slice(-1),x=b.indexOf(_),w=d?b.indexOf(d):-1,C=c.layersWithOutsidePointerEventsDisabled.size>0,E=w>=x,R=Txe(N=>{const k=N.target,I=...c.branches.some(O=>O.contains(k));!E||I||(o==null||o(N),s==null||s(N),N.defaultPrevented||a==null||a())},p),P=kxe(N=>{const k=N.target;...c.branches.some(O=>O.contains(k))||(i==null||i(N),s==null||s(N),N.defaultPrevented||a==null||a())},p);return X9(N=>{w===c.layers.size-1&&(r==null||r(N),!N.defaultPrevented&&a&&(N.preventDefault(),a()))},p),y.useEffect(()=>{if(d)return n&&(c.layersWithOutsidePointerEventsDisabled.size===0&&(CD=p.body.style.pointerEvents,p.body.style.pointerEvents="none"),c.layersWithOutsidePointerEventsDisabled.add(d)),c.layers.add(d),ED(),()=>{n&&c.layersWithOutsidePointerEventsDisabled.size===1&&(p.body.style.pointerEvents=CD)}},d,p,n,c),y.useEffect(()=>()=>{d&&(c.layers.delete(d),c.layersWithOutsidePointerEventsDisabled.delete(d),ED())},d,c),y.useEffect(()=>{const N=()=>g({});return document.addEventListener(zR,N),()=>document.removeEventListener(zR,N)},),h.jsx(Ze.div,{...l,ref:v,style:{pointerEvents:C?E?"auto":"none":void 0,...e.style},onFocusCapture:yt(e.onFocusCapture,P.onFocusCapture),onBlurCapture:yt(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:yt(e.onPointerDownCapture,R.onPointerDownCapture)})});sb.displayName=Cxe;var Rxe="DismissableLayerBranch",Q9=y.forwardRef((e,t)=>{const n=y.useContext(Z9),r=y.useRef(null),o=Pt(t,r);return y.useEffect(()=>{const i=r.current;if(i)return n.branches.add(i),()=>{n.branches.delete(i)}},n.branches),h.jsx(Ze.div,{...e,ref:o})});Q9.displayName=Rxe;function Txe(e,t=globalThis==null?void 0:globalThis.document){const n=En(e),r=y.useRef(!1),o=y.useRef(()=>{});return y.useEffect(()=>{const i=a=>{if(a.target&&!r.current){let l=function(){J9(Exe,n,c,{discrete:!0})};const c={originalEvent:a};a.pointerType==="touch"?(t.removeEventListener("click",o.current),o.current=l,t.addEventListener("click",o.current,{once:!0})):l()}else t.removeEventListener("click",o.current);r.current=!1},s=window.setTimeout(()=>{t.addEventListener("pointerdown",i)},0);return()=>{window.clearTimeout(s),t.removeEventListener("pointerdown",i),t.removeEventListener("click",o.current)}},t,n),{onPointerDownCapture:()=>r.current=!0}}function kxe(e,t=globalThis==null?void 0:globalThis.document){const n=En(e),r=y.useRef(!1);return y.useEffect(()=>{const o=i=>{i.target&&!r.current&&J9(Nxe,n,{originalEvent:i},{discrete:!1})};return t.addEventListener("focusin",o),()=>t.removeEventListener("focusin",o)},t,n),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function ED(){const e=new CustomEvent(zR);document.dispatchEvent(e)}function J9(e,t,n,{discrete:r}){const o=n.originalEvent.target,i=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&o.addEventListener(e,t,{once:!0}),r?ib(o,i):o.dispatchEvent(i)}var Pxe=sb,Ixe=Q9,Lo=globalThis!=null&&globalThis.document?y.useLayoutEffect:()=>{},Axe=u6" useId ".trim().toString()||(()=>{}),Mxe=0;function Mi(e){constt,n=y.useState(Axe());return Lo(()=>{n(r=>r??String(Mxe++))},e),e||(t?`radix-${t}`:"")}const jxe="top","right","bottom","left",mc=Math.min,Yo=Math.max,By=Math.round,Qv=Math.floor,ia=e=>({x:e,y:e}),Lxe={left:"right",right:"left",bottom:"top",top:"bottom"},Oxe={start:"end",end:"start"};function HR(e,t,n){return Yo(e,mc(t,n))}function nl(e,t){return typeof e=="function"?e(t):e}function rl(e){return e.split("-")0}function th(e){return e.split("-")1}function vP(e){return e==="x"?"y":"x"}function yP(e){return e==="y"?"height":"width"}const Dxe=new Set("top","bottom");function ea(e){return Dxe.has(rl(e))?"y":"x"}function xP(e){return vP(ea(e))}function Fxe(e,t,n){n===void 0&&(n=!1);const r=th(e),o=xP(e),i=yP(o);let s=o==="x"?r===(n?"end":"start")?"right":"left":r==="start"?"bottom":"top";return t.referencei>t.floatingi&&(s=Wy(s)),s,Wy(s)}function $xe(e){const t=Wy(e);returnBR(e),t,BR(t)}function BR(e){return e.replace(/start|end/g,t=>Oxet)}const ND="left","right",RD="right","left",zxe="top","bottom",Hxe="bottom","top";function Bxe(e,t,n){switch(e){case"top":case"bottom":return n?t?RD:ND:t?ND:RD;case"left":case"right":return t?zxe:Hxe;default:return}}function Wxe(e,t,n,r){const o=th(e);let i=Bxe(rl(e),n==="start",r);return o&&(i=i.map(s=>s+"-"+o),t&&(i=i.concat(i.map(BR)))),i}function Wy(e){return e.replace(/left|right|bottom|top/g,t=>Lxet)}function Uxe(e){return{top:0,right:0,bottom:0,left:0,...e}}function eU(e){return typeof e!="number"?Uxe(e):{top:e,right:e,bottom:e,left:e}}function Uy(e){const{x:t,y:n,width:r,height:o}=e;return{width:r,height:o,top:n,left:t,right:t+r,bottom:n+o,x:t,y:n}}function TD(e,t,n){let{reference:r,floating:o}=e;const i=ea(t),s=xP(t),a=yP(s),l=rl(t),c=i==="y",d=r.x+r.width/2-o.width/2,f=r.y+r.height/2-o.height/2,p=ra/2-oa/2;let g;switch(l){case"top":g={x:d,y:r.y-o.height};break;case"bottom":g={x:d,y:r.y+r.height};break;case"right":g={x:r.x+r.width,y:f};break;case"left":g={x:r.x-o.width,y:f};break;default:g={x:r.x,y:r.y}}switch(th(t)){case"start":gs-=p*(n&&c?-1:1);break;case"end":gs+=p*(n&&c?-1:1);break}return g}const qxe=async(e,t,n)=>{const{placement:r="bottom",strategy:o="absolute",middleware:i=,platform:s}=n,a=i.filter(Boolean),l=await(s.isRTL==null?void 0:s.isRTL(t));let c=await s.getElementRects({reference:e,floating:t,strategy:o}),{x:d,y:f}=TD(c,r,l),p=r,g={},v=0;for(let b=0;b<a.length;b++){const{name:_,fn:x}=ab,{x:w,y:C,data:E,reset:R}=await x({x:d,y:f,initialPlacement:r,placement:p,strategy:o,middlewareData:g,rects:c,platform:s,elements:{reference:e,floating:t}});d=w??d,f=C??f,g={...g,_:{...g_,...E}},R&&v<=50&&(v++,typeof R=="object"&&(R.placement&&(p=R.placement),R.rects&&(c=R.rects===!0?await s.getElementRects({reference:e,floating:t,strategy:o}):R.rects),{x:d,y:f}=TD(c,p,l)),b=-1)}return{x:d,y:f,placement:p,strategy:o,middlewareData:g}};async function jm(e,t){var n;t===void 0&&(t={});const{x:r,y:o,platform:i,rects:s,elements:a,strategy:l}=e,{boundary:c="clippingAncestors",rootBoundary:d="viewport",elementContext:f="floating",altBoundary:p=!1,padding:g=0}=nl(t,e),v=eU(g),_=ap?f==="floating"?"reference":"floating":f,x=Uy(await i.getClippingRect({element:(n=await(i.isElement==null?void 0:i.isElement(_)))==null||n?_:_.contextElement||await(i.getDocumentElement==null?void 0:i.getDocumentElement(a.floating)),boundary:c,rootBoundary:d,strategy:l})),w=f==="floating"?{x:r,y:o,width:s.floating.width,height:s.floating.height}:s.reference,C=await(i.getOffsetParent==null?void 0:i.getOffsetParent(a.floating)),E=await(i.isElement==null?void 0:i.isElement(C))?await(i.getScale==null?void 0:i.getScale(C))||{x:1,y:1}:{x:1,y:1},R=Uy(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:a,rect:w,offsetParent:C,strategy:l}):w);return{top:(x.top-R.top+v.top)/E.y,bottom:(R.bottom-x.bottom+v.bottom)/E.y,left:(x.left-R.left+v.left)/E.x,right:(R.right-x.right+v.right)/E.x}}const Gxe=e=>({name:"arrow",options:e,async fn(t){const{x:n,y:r,placement:o,rects:i,platform:s,elements:a,middlewareData:l}=t,{element:c,padding:d=0}=nl(e,t)||{};if(c==null)return{};const f=eU(d),p={x:n,y:r},g=xP(o),v=yP(g),b=await s.getDimensions(c),_=g==="y",x=_?"top":"left",w=_?"bottom":"right",C=_?"clientHeight":"clientWidth",E=i.referencev+i.referenceg-pg-i.floatingv,R=pg-i.referenceg,P=await(s.getOffsetParent==null?void 0:s.getOffsetParent(c));let N=P?PC:0;(!N||!await(s.isElement==null?void 0:s.isElement(P)))&&(N=a.floatingC||i.floatingv);const k=E/2-R/2,I=N/2-bv/2-1,O=mc(fx,I),j=mc(fw,I),H=O,q=N-bv-j,M=N/2-bv/2+k,B=HR(H,M,q),$=!l.arrow&&th(o)!=null&&M!==B&&i.referencev/2-(M<H?O:j)-bv/2<0,W=$?M<H?M-H:M-q:0;return{g:pg+W,data:{g:B,centerOffset:M-B-W,...$&&{alignmentOffset:W}},reset:$}}}),Vxe=function(e){return e===void 0&&(e={}),{name:"flip",options:e,async fn(t){var n,r;const{placement:o,middlewareData:i,rects:s,initialPlacement:a,platform:l,elements:c}=t,{mainAxis:d=!0,crossAxis:f=!0,fallbackPlacements:p,fallbackStrategy:g="bestFit",fallbackAxisSideDirection:v="none",flipAlignment:b=!0,..._}=nl(e,t);if((n=i.arrow)!=null&&n.alignmentOffset)return{};const x=rl(o),w=ea(a),C=rl(a)===a,E=await(l.isRTL==null?void 0:l.isRTL(c.floating)),R=p||(C||!b?Wy(a):$xe(a)),P=v!=="none";!p&&P&&R.push(...Wxe(a,b,v,E));const N=a,...R,k=await jm(t,_),I=;let O=((r=i.flip)==null?void 0:r.overflows)||;if(d&&I.push(kx),f){const M=Fxe(o,s,E);I.push(kM0,kM1)}if(O=...O,{placement:o,overflows:I},!I.every(M=>M<=0)){var j,H;const M=(((j=i.flip)==null?void 0:j.index)||0)+1,B=NM;if(B&&(!(f==="alignment"?w!==ea(B):!1)||O.every(G=>ea(G.placement)===w?G.overflows0>0:!0)))return{data:{index:M,overflows:O},reset:{placement:B}};let $=(H=O.filter(W=>W.overflows0<=0).sort((W,G)=>W.overflows1-G.overflows1)0)==null?void 0:H.placement;if(!$)switch(g){case"bestFit":{var q;const W=(q=O.filter(G=>{if(P){const z=ea(G.placement);return z===w||z==="y"}return!0}).map(G=>G.placement,G.overflows.filter(z=>z>0).reduce((z,K)=>z+K,0)).sort((G,z)=>G1-z1)0)==null?void 0:q0;W&&($=W);break}case"initialPlacement":$=a;break}if(o!==$)return{reset:{placement:$}}}return{}}}};function kD(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function PD(e){return jxe.some(t=>et>=0)}const Yxe=function(e){return e===void 0&&(e={}),{name:"hide",options:e,async fn(t){const{rects:n}=t,{strategy:r="referenceHidden",...o}=nl(e,t);switch(r){case"referenceHidden":{const i=await jm(t,{...o,elementContext:"reference"}),s=kD(i,n.reference);return{data:{referenceHiddenOffsets:s,referenceHidden:PD(s)}}}case"escaped":{const i=await jm(t,{...o,altBoundary:!0}),s=kD(i,n.floating);return{data:{escapedOffsets:s,escaped:PD(s)}}}default:return{}}}}},tU=new Set("left","top");async function Kxe(e,t){const{placement:n,platform:r,elements:o}=e,i=await(r.isRTL==null?void 0:r.isRTL(o.floating)),s=rl(n),a=th(n),l=ea(n)==="y",c=tU.has(s)?-1:1,d=i&&l?-1:1,f=nl(t,e);let{mainAxis:p,crossAxis:g,alignmentAxis:v}=typeof f=="number"?{mainAxis:f,crossAxis:0,alignmentAxis:null}:{mainAxis:f.mainAxis||0,crossAxis:f.crossAxis||0,alignmentAxis:f.alignmentAxis};return a&&typeof v=="number"&&(g=a==="end"?v*-1:v),l?{x:g*d,y:p*c}:{x:p*c,y:g*d}}const Xxe=function(e){return e===void 0&&(e=0),{name:"offset",options:e,async fn(t){var n,r;const{x:o,y:i,placement:s,middlewareData:a}=t,l=await Kxe(t,e);return s===((n=a.offset)==null?void 0:n.placement)&&(r=a.arrow)!=null&&r.alignmentOffset?{}:{x:o+l.x,y:i+l.y,data:{...l,placement:s}}}}},Zxe=function(e){return e===void 0&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:r,placement:o}=t,{mainAxis:i=!0,crossAxis:s=!1,limiter:a={fn:_=>{let{x,y:w}=_;return{x,y:w}}},...l}=nl(e,t),c={x:n,y:r},d=await jm(t,l),f=ea(rl(o)),p=vP(f);let g=cp,v=cf;if(i){const _=p==="y"?"top":"left",x=p==="y"?"bottom":"right",w=g+d_,C=g-dx;g=HR(w,g,C)}if(s){const _=f==="y"?"top":"left",x=f==="y"?"bottom":"right",w=v+d_,C=v-dx;v=HR(w,v,C)}const b=a.fn({...t,p:g,f:v});return{...b,data:{x:b.x-n,y:b.y-r,enabled:{p:i,f:s}}}}}},Qxe=function(e){return e===void 0&&(e={}),{options:e,fn(t){const{x:n,y:r,placement:o,rects:i,middlewareData:s}=t,{offset:a=0,mainAxis:l=!0,crossAxis:c=!0}=nl(e,t),d={x:n,y:r},f=ea(o),p=vP(f);let g=dp,v=df;const b=nl(a,t),_=typeof b=="number"?{mainAxis:b,crossAxis:0}:{mainAxis:0,crossAxis:0,...b};if(l){const C=p==="y"?"height":"width",E=i.referencep-i.floatingC+_.mainAxis,R=i.referencep+i.referenceC-_.mainAxis;g<E?g=E:g>R&&(g=R)}if(c){var x,w;const C=p==="y"?"width":"height",E=tU.has(rl(o)),R=i.referencef-i.floatingC+(E&&((x=s.offset)==null?void 0:xf)||0)+(E?0:_.crossAxis),P=i.referencef+i.referenceC+(E?0:((w=s.offset)==null?void 0:wf)||0)-(E?_.crossAxis:0);v<R?v=R:v>P&&(v=P)}return{p:g,f:v}}}},Jxe=function(e){return e===void 0&&(e={}),{name:"size",options:e,async fn(t){var n,r;const{placement:o,rects:i,platform:s,elements:a}=t,{apply:l=()=>{},...c}=nl(e,t),d=await jm(t,c),f=rl(o),p=th(o),g=ea(o)==="y",{width:v,height:b}=i.floating;let _,x;f==="top"||f==="bottom"?(_=f,x=p===(await(s.isRTL==null?void 0:s.isRTL(a.floating))?"start":"end")?"left":"right"):(x=f,_=p==="end"?"top":"bottom");const w=b-d.top-d.bottom,C=v-d.left-d.right,E=mc(b-d_,w),R=mc(v-dx,C),P=!t.middlewareData.shift;let N=E,k=R;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(k=C),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(N=w),P&&!p){const O=Yo(d.left,0),j=Yo(d.right,0),H=Yo(d.top,0),q=Yo(d.bottom,0);g?k=v-2*(O!==0||j!==0?O+j:Yo(d.left,d.right)):N=b-2*(H!==0||q!==0?H+q:Yo(d.top,d.bottom))}await l({...t,availableWidth:k,availableHeight:N});const I=await s.getDimensions(a.floating);return v!==I.width||b!==I.height?{reset:{rects:!0}}:{}}}};function ab(){return typeof window<"u"}function nh(e){return nU(e)?(e.nodeName||"").toLowerCase():"#document"}function ni(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function ua(e){var t;return(t=(nU(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function nU(e){return ab()?e instanceof Node||e instanceof ni(e).Node:!1}function ms(e){return ab()?e instanceof Element||e instanceof ni(e).Element:!1}function aa(e){return ab()?e instanceof HTMLElement||e instanceof ni(e).HTMLElement:!1}function ID(e){return!ab()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof ni(e).ShadowRoot}const ebe=new Set("inline","contents");function og(e){const{overflow:t,overflowX:n,overflowY:r,display:o}=gs(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!ebe.has(o)}const tbe=new Set("table","td","th");function nbe(e){return tbe.has(nh(e))}const rbe=":popover-open",":modal";function lb(e){return rbe.some(t=>{try{return e.matches(t)}catch{return!1}})}const obe="transform","translate","scale","rotate","perspective",ibe="transform","translate","scale","rotate","perspective","filter",sbe="paint","layout","strict","content";function bP(e){const t=wP(),n=ms(e)?gs(e):e;return obe.some(r=>nr?nr!=="none":!1)||(n.containerType?n.containerType!=="normal":!1)||!t&&(n.backdropFilter?n.backdropFilter!=="none":!1)||!t&&(n.filter?n.filter!=="none":!1)||ibe.some(r=>(n.willChange||"").includes(r))||sbe.some(r=>(n.contain||"").includes(r))}function abe(e){let t=gc(e);for(;aa(t)&&!Lf(t);){if(bP(t))return t;if(lb(t))return null;t=gc(t)}return null}function wP(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}const lbe=new Set("html","body","#document");function Lf(e){return lbe.has(nh(e))}function gs(e){return ni(e).getComputedStyle(e)}function cb(e){return ms(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function gc(e){if(nh(e)==="html")return e;const t=e.assignedSlot||e.parentNode||ID(e)&&e.host||ua(e);return ID(t)?t.host:t}function rU(e){const t=gc(e);return Lf(t)?e.ownerDocument?e.ownerDocument.body:e.body:aa(t)&&og(t)?t:rU(t)}function Lm(e,t,n){var r;t===void 0&&(t=),n===void 0&&(n=!0);const o=rU(e),i=o===((r=e.ownerDocument)==null?void 0:r.body),s=ni(o);if(i){const a=WR(s);return t.concat(s,s.visualViewport||,og(o)?o:,a&&n?Lm(a):)}return t.concat(o,Lm(o,,n))}function WR(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function oU(e){const t=gs(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const o=aa(e),i=o?e.offsetWidth:n,s=o?e.offsetHeight:r,a=By(n)!==i||By(r)!==s;return a&&(n=i,r=s),{width:n,height:r,$:a}}function SP(e){return ms(e)?e:e.contextElement}function ff(e){const t=SP(e);if(!aa(t))return ia(1);const n=t.getBoundingClientRect(),{width:r,height:o,$:i}=oU(t);let s=(i?By(n.width):n.width)/r,a=(i?By(n.height):n.height)/o;return(!s||!Number.isFinite(s))&&(s=1),(!a||!Number.isFinite(a))&&(a=1),{x:s,y:a}}const cbe=ia(0);function iU(e){const t=ni(e);return!wP()||!t.visualViewport?cbe:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function ube(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==ni(e)?!1:t}function $u(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);const o=e.getBoundingClientRect(),i=SP(e);let s=ia(1);t&&(r?ms(r)&&(s=ff(r)):s=ff(e));const a=ube(i,n,r)?iU(i):ia(0);let l=(o.left+a.x)/s.x,c=(o.top+a.y)/s.y,d=o.width/s.x,f=o.height/s.y;if(i){const p=ni(i),g=r&&ms(r)?ni(r):r;let v=p,b=WR(v);for(;b&&r&&g!==v;){const _=ff(b),x=b.getBoundingClientRect(),w=gs(b),C=x.left+(b.clientLeft+parseFloat(w.paddingLeft))*_.x,E=x.top+(b.clientTop+parseFloat(w.paddingTop))*_.y;l*=_.x,c*=_.y,d*=_.x,f*=_.y,l+=C,c+=E,v=ni(b),b=WR(v)}}return Uy({width:d,height:f,x:l,y:c})}function _P(e,t){const n=cb(e).scrollLeft;return t?t.left+n:$u(ua(e)).left+n}function sU(e,t,n){n===void 0&&(n=!1);const r=e.getBoundingClientRect(),o=r.left+t.scrollLeft-(n?0:_P(e,r)),i=r.top+t.scrollTop;return{x:o,y:i}}function dbe(e){let{elements:t,rect:n,offsetParent:r,strategy:o}=e;const i=o==="fixed",s=ua(r),a=t?lb(t.floating):!1;if(r===s||a&&i)return n;let l={scrollLeft:0,scrollTop:0},c=ia(1);const d=ia(0),f=aa(r);if((f||!f&&!i)&&((nh(r)!=="body"||og(s))&&(l=cb(r)),aa(r))){const g=$u(r);c=ff(r),d.x=g.x+r.clientLeft,d.y=g.y+r.clientTop}const p=s&&!f&&!i?sU(s,l,!0):ia(0);return{width:n.width*c.x,height:n.height*c.y,x:n.x*c.x-l.scrollLeft*c.x+d.x+p.x,y:n.y*c.y-l.scrollTop*c.y+d.y+p.y}}function fbe(e){return Array.from(e.getClientRects())}function hbe(e){const t=ua(e),n=cb(e),r=e.ownerDocument.body,o=Yo(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),i=Yo(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let s=-n.scrollLeft+_P(e);const a=-n.scrollTop;return gs(r).direction==="rtl"&&(s+=Yo(t.clientWidth,r.clientWidth)-o),{width:o,height:i,x:s,y:a}}function pbe(e,t){const n=ni(e),r=ua(e),o=n.visualViewport;let i=r.clientWidth,s=r.clientHeight,a=0,l=0;if(o){i=o.width,s=o.height;const c=wP();(!c||c&&t==="fixed")&&(a=o.offsetLeft,l=o.offsetTop)}return{width:i,height:s,x:a,y:l}}const mbe=new Set("absolute","fixed");function gbe(e,t){const n=$u(e,!0,t==="fixed"),r=n.top+e.clientTop,o=n.left+e.clientLeft,i=aa(e)?ff(e):ia(1),s=e.clientWidth*i.x,a=e.clientHeight*i.y,l=o*i.x,c=r*i.y;return{width:s,height:a,x:l,y:c}}function AD(e,t,n){let r;if(t==="viewport")r=pbe(e,n);else if(t==="document")r=hbe(ua(e));else if(ms(t))r=gbe(t,n);else{const o=iU(e);r={x:t.x-o.x,y:t.y-o.y,width:t.width,height:t.height}}return Uy(r)}function aU(e,t){const n=gc(e);return n===t||!ms(n)||Lf(n)?!1:gs(n).position==="fixed"||aU(n,t)}function vbe(e,t){const n=t.get(e);if(n)return n;let r=Lm(e,,!1).filter(a=>ms(a)&&nh(a)!=="body"),o=null;const i=gs(e).position==="fixed";let s=i?gc(e):e;for(;ms(s)&&!Lf(s);){const a=gs(s),l=bP(s);!l&&a.position==="fixed"&&(o=null),(i?!l&&!o:!l&&a.position==="static"&&!!o&&mbe.has(o.position)||og(s)&&!l&&aU(e,s))?r=r.filter(d=>d!==s):o=a,s=gc(s)}return t.set(e,r),r}function ybe(e){let{element:t,boundary:n,rootBoundary:r,strategy:o}=e;const s=...n==="clippingAncestors"?lb(t)?:vbe(t,this._c):.concat(n),r,a=s0,l=s.reduce((c,d)=>{const f=AD(t,d,o);return c.top=Yo(f.top,c.top),c.right=mc(f.right,c.right),c.bottom=mc(f.bottom,c.bottom),c.left=Yo(f.left,c.left),c},AD(t,a,o));return{width:l.right-l.left,height:l.bottom-l.top,x:l.left,y:l.top}}function xbe(e){const{width:t,height:n}=oU(e);return{width:t,height:n}}function bbe(e,t,n){const r=aa(t),o=ua(t),i=n==="fixed",s=$u(e,!0,i,t);let a={scrollLeft:0,scrollTop:0};const l=ia(0);function c(){l.x=_P(o)}if(r||!r&&!i)if((nh(t)!=="body"||og(o))&&(a=cb(t)),r){const g=$u(t,!0,i,t);l.x=g.x+t.clientLeft,l.y=g.y+t.clientTop}else o&&c();i&&!r&&o&&c();const d=o&&!r&&!i?sU(o,a):ia(0),f=s.left+a.scrollLeft-l.x-d.x,p=s.top+a.scrollTop-l.y-d.y;return{x:f,y:p,width:s.width,height:s.height}}function yS(e){return gs(e).position==="static"}function MD(e,t){if(!aa(e)||gs(e).position==="fixed")return null;if(t)return t(e);let n=e.offsetParent;return ua(e)===n&&(n=n.ownerDocument.body),n}function lU(e,t){const n=ni(e);if(lb(e))return n;if(!aa(e)){let o=gc(e);for(;o&&!Lf(o);){if(ms(o)&&!yS(o))return o;o=gc(o)}return n}let r=MD(e,t);for(;r&&nbe(r)&&yS(r);)r=MD(r,t);return r&&Lf(r)&&yS(r)&&!bP(r)?n:r||abe(e)||n}const wbe=async function(e){const t=this.getOffsetParent||lU,n=this.getDimensions,r=await n(e.floating);return{reference:bbe(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}};function Sbe(e){return gs(e).direction==="rtl"}const _be={convertOffsetParentRelativeRectToViewportRelativeRect:dbe,getDocumentElement:ua,getClippingRect:ybe,getOffsetParent:lU,getElementRects:wbe,getClientRects:fbe,getDimensions:xbe,getScale:ff,isElement:ms,isRTL:Sbe};function cU(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function Cbe(e,t){let n=null,r;const o=ua(e);function i(){var a;clearTimeout(r),(a=n)==null||a.disconnect(),n=null}function s(a,l){a===void 0&&(a=!1),l===void 0&&(l=1),i();const c=e.getBoundingClientRect(),{left:d,top:f,width:p,height:g}=c;if(a||t(),!p||!g)return;const v=Qv(f),b=Qv(o.clientWidth-(d+p)),_=Qv(o.clientHeight-(f+g)),x=Qv(d),C={rootMargin:-v+"px "+-b+"px "+-_+"px "+-x+"px",threshold:Yo(0,mc(1,l))||1};let E=!0;function R(P){const N=P0.intersectionRatio;if(N!==l){if(!E)return s();N?s(!1,N):r=setTimeout(()=>{s(!1,1e-7)},1e3)}N===1&&!cU(c,e.getBoundingClientRect())&&s(),E=!1}try{n=new IntersectionObserver(R,{...C,root:o.ownerDocument})}catch{n=new IntersectionObserver(R,C)}n.observe(e)}return s(!0),i}function uU(e,t,n,r){r===void 0&&(r={});const{ancestorScroll:o=!0,ancestorResize:i=!0,elementResize:s=typeof ResizeObserver=="function",layoutShift:a=typeof IntersectionObserver=="function",animationFrame:l=!1}=r,c=SP(e),d=o||i?...c?Lm(c):,...Lm(t):;d.forEach(x=>{o&&x.addEventListener("scroll",n,{passive:!0}),i&&x.addEventListener("resize",n)});const f=c&&a?Cbe(c,n):null;let p=-1,g=null;s&&(g=new ResizeObserver(x=>{letw=x;w&&w.target===c&&g&&(g.unobserve(t),cancelAnimationFrame(p),p=requestAnimationFrame(()=>{var C;(C=g)==null||C.observe(t)})),n()}),c&&!l&&g.observe(c),g.observe(t));let v,b=l?$u(e):null;l&&_();function _(){const x=$u(e);b&&!cU(b,x)&&n(),b=x,v=requestAnimationFrame(_)}return n(),()=>{var x;d.forEach(w=>{o&&w.removeEventListener("scroll",n),i&&w.removeEventListener("resize",n)}),f==null||f(),(x=g)==null||x.disconnect(),g=null,l&&cancelAnimationFrame(v)}}const Ebe=Xxe,Nbe=Zxe,Rbe=Vxe,Tbe=Jxe,kbe=Yxe,jD=Gxe,Pbe=Qxe,Ibe=(e,t,n)=>{const r=new Map,o={platform:_be,...n},i={...o.platform,_c:r};return qxe(e,t,{...o,platform:i})};var Abe=typeof document<"u",Mbe=function(){},F0=Abe?y.useLayoutEffect:Mbe;function qy(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(typeof e=="function"&&e.toString()===t.toString())return!0;let n,r,o;if(e&&t&&typeof e=="object"){if(Array.isArray(e)){if(n=e.length,n!==t.length)return!1;for(r=n;r--!==0;)if(!qy(er,tr))return!1;return!0}if(o=Object.keys(e),n=o.length,n!==Object.keys(t).length)return!1;for(r=n;r--!==0;)if(!{}.hasOwnProperty.call(t,or))return!1;for(r=n;r--!==0;){const i=or;if(!(i==="_owner"&&e.$$typeof)&&!qy(ei,ti))return!1}return!0}return e!==e&&t!==t}function dU(e){return typeof window>"u"?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function LD(e,t){const n=dU(e);return Math.round(t*n)/n}function xS(e){const t=y.useRef(e);return F0(()=>{t.current=e}),t}function fU(e){e===void 0&&(e={});const{placement:t="bottom",strategy:n="absolute",middleware:r=,platform:o,elements:{reference:i,floating:s}={},transform:a=!0,whileElementsMounted:l,open:c}=e,d,f=y.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),p,g=y.useState(r);qy(p,r)||g(r);constv,b=y.useState(null),_,x=y.useState(null),w=y.useCallback(G=>{G!==P.current&&(P.current=G,b(G))},),C=y.useCallback(G=>{G!==N.current&&(N.current=G,x(G))},),E=i||v,R=s||_,P=y.useRef(null),N=y.useRef(null),k=y.useRef(d),I=l!=null,O=xS(l),j=xS(o),H=xS(c),q=y.useCallback(()=>{if(!P.current||!N.current)return;const G={placement:t,strategy:n,middleware:p};j.current&&(G.platform=j.current),Ibe(P.current,N.current,G).then(z=>{const K={...z,isPositioned:H.current!==!1};M.current&&!qy(k.current,K)&&(k.current=K,al.flushSync(()=>{f(K)}))})},p,t,n,j,H);F0(()=>{c===!1&&k.current.isPositioned&&(k.current.isPositioned=!1,f(G=>({...G,isPositioned:!1})))},c);const M=y.useRef(!1);F0(()=>(M.current=!0,()=>{M.current=!1}),),F0(()=>{if(E&&(P.current=E),R&&(N.current=R),E&&R){if(O.current)return O.current(E,R,q);q()}},E,R,q,O,I);const B=y.useMemo(()=>({reference:P,floating:N,setReference:w,setFloating:C}),w,C),$=y.useMemo(()=>({reference:E,floating:R}),E,R),W=y.useMemo(()=>{const G={position:n,left:0,top:0};if(!$.floating)return G;const z=LD($.floating,d.x),K=LD($.floating,d.y);return a?{...G,transform:"translate("+z+"px, "+K+"px)",...dU($.floating)>=1.5&&{willChange:"transform"}}:{position:n,left:z,top:K}},n,a,$.floating,d.x,d.y);return y.useMemo(()=>({...d,update:q,refs:B,elements:$,floatingStyles:W}),d,q,B,$,W)}const jbe=e=>{function t(n){return{}.hasOwnProperty.call(n,"current")}return{name:"arrow",options:e,fn(n){const{element:r,padding:o}=typeof e=="function"?e(n):e;return r&&t(r)?r.current!=null?jD({element:r.current,padding:o}).fn(n):{}:r?jD({element:r,padding:o}).fn(n):{}}}},hU=(e,t)=>({...Ebe(e),options:e,t}),pU=(e,t)=>({...Nbe(e),options:e,t}),mU=(e,t)=>({...Pbe(e),options:e,t}),gU=(e,t)=>({...Rbe(e),options:e,t}),vU=(e,t)=>({...Tbe(e),options:e,t}),yU=(e,t)=>({...kbe(e),options:e,t}),xU=(e,t)=>({...jbe(e),options:e,t});var Lbe="Arrow",bU=y.forwardRef((e,t)=>{const{children:n,width:r=10,height:o=5,...i}=e;return h.jsx(Ze.svg,{...i,ref:t,width:r,height:o,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?n:h.jsx("polygon",{points:"0,0 30,0 15,10"})})});bU.displayName=Lbe;var wU=bU;function ub(e){constt,n=y.useState(void 0);return Lo(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});const r=new ResizeObserver(o=>{if(!Array.isArray(o)||!o.length)return;const i=o0;let s,a;if("borderBoxSize"in i){const l=i.borderBoxSize,c=Array.isArray(l)?l0:l;s=c.inlineSize,a=c.blockSize}else s=e.offsetWidth,a=e.offsetHeight;n({width:s,height:a})});return r.observe(e,{box:"border-box"}),()=>r.unobserve(e)}else n(void 0)},e),t}var CP="Popper",SU,_U=to(CP),Obe,CU=SU(CP),EU=e=>{const{__scopePopper:t,children:n}=e,r,o=y.useState(null);return h.jsx(Obe,{scope:t,anchor:r,onAnchorChange:o,children:n})};EU.displayName=CP;var NU="PopperAnchor",RU=y.forwardRef((e,t)=>{const{__scopePopper:n,virtualRef:r,...o}=e,i=CU(NU,n),s=y.useRef(null),a=Pt(t,s);return y.useEffect(()=>{i.onAnchorChange((r==null?void 0:r.current)||s.current)}),r?null:h.jsx(Ze.div,{...o,ref:a})});RU.displayName=NU;var EP="PopperContent",Dbe,Fbe=SU(EP),TU=y.forwardRef((e,t)=>{var fe,Z,oe,ce,xe,ge;const{__scopePopper:n,side:r="bottom",sideOffset:o=0,align:i="center",alignOffset:s=0,arrowPadding:a=0,avoidCollisions:l=!0,collisionBoundary:c=,collisionPadding:d=0,sticky:f="partial",hideWhenDetached:p=!1,updatePositionStrategy:g="optimized",onPlaced:v,...b}=e,_=CU(EP,n),x,w=y.useState(null),C=Pt(t,pe=>w(pe)),E,R=y.useState(null),P=ub(E),N=(P==null?void 0:P.width)??0,k=(P==null?void 0:P.height)??0,I=r+(i!=="center"?"-"+i:""),O=typeof d=="number"?d:{top:0,right:0,bottom:0,left:0,...d},j=Array.isArray(c)?c:c,H=j.length>0,q={padding:O,boundary:j.filter(zbe),altBoundary:H},{refs:M,floatingStyles:B,placement:$,isPositioned:W,middlewareData:G}=fU({strategy:"fixed",placement:I,whileElementsMounted:(...pe)=>uU(...pe,{animationFrame:g==="always"}),elements:{reference:_.anchor},middleware:hU({mainAxis:o+k,alignmentAxis:s}),l&&pU({mainAxis:!0,crossAxis:!1,limiter:f==="partial"?mU():void 0,...q}),l&&gU({...q}),vU({...q,apply:({elements:pe,rects:he,availableWidth:we,availableHeight:Ie})=>{const{width:Ce,height:Me}=he.reference,ze=pe.floating.style;ze.setProperty("--radix-popper-available-width",`${we}px`),ze.setProperty("--radix-popper-available-height",`${Ie}px`),ze.setProperty("--radix-popper-anchor-width",`${Ce}px`),ze.setProperty("--radix-popper-anchor-height",`${Me}px`)}}),E&&xU({element:E,padding:a}),Hbe({arrowWidth:N,arrowHeight:k}),p&&yU({strategy:"referenceHidden",...q})}),z,K=IU($),Q=En(v);Lo(()=>{W&&(Q==null||Q())},W,Q);const re=(fe=G.arrow)==null?void 0:fe.x,ae=(Z=G.arrow)==null?void 0:Z.y,de=((oe=G.arrow)==null?void 0:oe.centerOffset)!==0,Ne,ye=y.useState();return Lo(()=>{x&&ye(window.getComputedStyle(x).zIndex)},x),h.jsx("div",{ref:M.setFloating,"data-radix-popper-content-wrapper":"",style:{...B,transform:W?B.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:Ne,"--radix-popper-transform-origin":(ce=G.transformOrigin)==null?void 0:ce.x,(xe=G.transformOrigin)==null?void 0:xe.y.join(" "),...((ge=G.hide)==null?void 0:ge.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:h.jsx(Dbe,{scope:n,placedSide:z,onArrowChange:R,arrowX:re,arrowY:ae,shouldHideArrow:de,children:h.jsx(Ze.div,{"data-side":z,"data-align":K,...b,ref:C,style:{...b.style,animation:W?void 0:"none"}})})})});TU.displayName=EP;var kU="PopperArrow",$be={top:"bottom",right:"left",bottom:"top",left:"right"},PU=y.forwardRef(function(t,n){const{__scopePopper:r,...o}=t,i=Fbe(kU,r),s=$bei.placedSide;return h.jsx("span",{ref:i.onArrowChange,style:{position:"absolute",left:i.arrowX,top:i.arrowY,s:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}i.placedSide,transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}i.placedSide,visibility:i.shouldHideArrow?"hidden":void 0},children:h.jsx(wU,{...o,ref:n,style:{...o.style,display:"block"}})})});PU.displayName=kU;function zbe(e){return e!==null}var Hbe=e=>({name:"transformOrigin",options:e,fn(t){var _,x,w;const{placement:n,rects:r,middlewareData:o}=t,s=((_=o.arrow)==null?void 0:_.centerOffset)!==0,a=s?0:e.arrowWidth,l=s?0:e.arrowHeight,c,d=IU(n),f={start:"0%",center:"50%",end:"100%"}d,p=(((x=o.arrow)==null?void 0:x.x)??0)+a/2,g=(((w=o.arrow)==null?void 0:w.y)??0)+l/2;let v="",b="";return c==="bottom"?(v=s?f:`${p}px`,b=`${-l}px`):c==="top"?(v=s?f:`${p}px`,b=`${r.floating.height+l}px`):c==="right"?(v=`${-l}px`,b=s?f:`${g}px`):c==="left"&&(v=`${r.floating.width+l}px`,b=s?f:`${g}px`),{data:{x:v,y:b}}}});function IU(e){constt,n="center"=e.split("-");returnt,n}var Bbe=EU,Wbe=RU,Ube=TU,qbe=PU,Gbe="Portal",ig=y.forwardRef((e,t)=>{var a;const{container:n,...r}=e,o,i=y.useState(!1);Lo(()=>i(!0),);const s=n||o&&((a=globalThis==null?void 0:globalThis.document)==null?void 0:a.body);return s?hB.createPortal(h.jsx(Ze.div,{...r,ref:t}),s):null});ig.displayName=Gbe;function Vbe(e,t){return y.useReducer((n,r)=>tnr??n,e)}var ys=e=>{const{present:t,children:n}=e,r=Ybe(t),o=typeof n=="function"?n({present:r.isPresent}):y.Children.only(n),i=Pt(r.ref,Kbe(o));return typeof n=="function"||r.isPresent?y.cloneElement(o,{ref:i}):null};ys.displayName="Presence";function Ybe(e){constt,n=y.useState(),r=y.useRef(null),o=y.useRef(e),i=y.useRef("none"),s=e?"mounted":"unmounted",a,l=Vbe(s,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return y.useEffect(()=>{const c=Jv(r.current);i.current=a==="mounted"?c:"none"},a),Lo(()=>{const c=r.current,d=o.current;if(d!==e){const p=i.current,g=Jv(c);e?l("MOUNT"):g==="none"||(c==null?void 0:c.display)==="none"?l("UNMOUNT"):l(d&&p!==g?"ANIMATION_OUT":"UNMOUNT"),o.current=e}},e,l),Lo(()=>{if(t){let c;const d=t.ownerDocument.defaultView??window,f=g=>{const b=Jv(r.current).includes(g.animationName);if(g.target===t&&b&&(l("ANIMATION_END"),!o.current)){const _=t.style.animationFillMode;t.style.animationFillMode="forwards",c=d.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=_)})}},p=g=>{g.target===t&&(i.current=Jv(r.current))};return t.addEventListener("animationstart",p),t.addEventListener("animationcancel",f),t.addEventListener("animationend",f),()=>{d.clearTimeout(c),t.removeEventListener("animationstart",p),t.removeEventListener("animationcancel",f),t.removeEventListener("animationend",f)}}else l("ANIMATION_END")},t,l),{isPresent:"mounted","unmountSuspended".includes(a),ref:y.useCallback(c=>{r.current=c?getComputedStyle(c):null,n(c)},)}}function Jv(e){return(e==null?void 0:e.animationName)||"none"}function Kbe(e){var r,o;let t=(r=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:r.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=(o=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:o.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var Xbe=u6" useInsertionEffect ".trim().toString()||Lo;function xs({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){consto,i,s=Zbe({defaultProp:t,onChange:n}),a=e!==void 0,l=a?e:o;{const d=y.useRef(e!==void 0);y.useEffect(()=>{const f=d.current;f!==a&&console.warn(`${r} is changing from ${f?"controlled":"uncontrolled"} to ${a?"controlled":"uncontrolled"}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),d.current=a},a,r)}const c=y.useCallback(d=>{var f;if(a){const p=Qbe(d)?d(e):d;p!==e&&((f=s.current)==null||f.call(s,p))}else i(d)},a,e,i,s);returnl,c}function Zbe({defaultProp:e,onChange:t}){constn,r=y.useState(e),o=y.useRef(n),i=y.useRef(t);return Xbe(()=>{i.current=t},t),y.useEffect(()=>{var s;o.current!==n&&((s=i.current)==null||s.call(i,n),o.current=n)},n,o),n,r,i}function Qbe(e){return typeof e=="function"}var Jbe=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"}),ewe="VisuallyHidden",db=y.forwardRef((e,t)=>h.jsx(Ze.span,{...e,ref:t,style:{...Jbe,...e.style}}));db.displayName=ewe;var twe=db,fb,Q4e=to("Tooltip",_U),hb=_U(),AU="TooltipProvider",nwe=700,UR="tooltip.open",rwe,NP=fb(AU),MU=e=>{const{__scopeTooltip:t,delayDuration:n=nwe,skipDelayDuration:r=300,disableHoverableContent:o=!1,children:i}=e,s=y.useRef(!0),a=y.useRef(!1),l=y.useRef(0);return y.useEffect(()=>{const c=l.current;return()=>window.clearTimeout(c)},),h.jsx(rwe,{scope:t,isOpenDelayedRef:s,delayDuration:n,onOpen:y.useCallback(()=>{window.clearTimeout(l.current),s.current=!1},),onClose:y.useCallback(()=>{window.clearTimeout(l.current),l.current=window.setTimeout(()=>s.current=!0,r)},r),isPointerInTransitRef:a,onPointerInTransitChange:y.useCallback(c=>{a.current=c},),disableHoverableContent:o,children:i})};MU.displayName=AU;var Om="Tooltip",owe,pb=fb(Om),jU=e=>{const{__scopeTooltip:t,children:n,open:r,defaultOpen:o,onOpenChange:i,disableHoverableContent:s,delayDuration:a}=e,l=NP(Om,e.__scopeTooltip),c=hb(t),d,f=y.useState(null),p=Mi(),g=y.useRef(0),v=s??l.disableHoverableContent,b=a??l.delayDuration,_=y.useRef(!1),x,w=xs({prop:r,defaultProp:o??!1,onChange:N=>{N?(l.onOpen(),document.dispatchEvent(new CustomEvent(UR))):l.onClose(),i==null||i(N)},caller:Om}),C=y.useMemo(()=>x?_.current?"delayed-open":"instant-open":"closed",x),E=y.useCallback(()=>{window.clearTimeout(g.current),g.current=0,_.current=!1,w(!0)},w),R=y.useCallback(()=>{window.clearTimeout(g.current),g.current=0,w(!1)},w),P=y.useCallback(()=>{window.clearTimeout(g.current),g.current=window.setTimeout(()=>{_.current=!0,w(!0),g.current=0},b)},b,w);return y.useEffect(()=>()=>{g.current&&(window.clearTimeout(g.current),g.current=0)},),h.jsx(Bbe,{...c,children:h.jsx(owe,{scope:t,contentId:p,open:x,stateAttribute:C,trigger:d,onTriggerChange:f,onTriggerEnter:y.useCallback(()=>{l.isOpenDelayedRef.current?P():E()},l.isOpenDelayedRef,P,E),onTriggerLeave:y.useCallback(()=>{v?R():(window.clearTimeout(g.current),g.current=0)},R,v),onOpen:E,onClose:R,disableHoverableContent:v,children:n})})};jU.displayName=Om;var qR="TooltipTrigger",LU=y.forwardRef((e,t)=>{const{__scopeTooltip:n,...r}=e,o=pb(qR,n),i=NP(qR,n),s=hb(n),a=y.useRef(null),l=Pt(t,a,o.onTriggerChange),c=y.useRef(!1),d=y.useRef(!1),f=y.useCallback(()=>c.current=!1,);return y.useEffect(()=>()=>document.removeEventListener("pointerup",f),f),h.jsx(Wbe,{asChild:!0,...s,children:h.jsx(Ze.button,{"aria-describedby":o.open?o.contentId:void 0,"data-state":o.stateAttribute,...r,ref:l,onPointerMove:yt(e.onPointerMove,p=>{p.pointerType!=="touch"&&!d.current&&!i.isPointerInTransitRef.current&&(o.onTriggerEnter(),d.current=!0)}),onPointerLeave:yt(e.onPointerLeave,()=>{o.onTriggerLeave(),d.current=!1}),onPointerDown:yt(e.onPointerDown,()=>{o.open&&o.onClose(),c.current=!0,document.addEventListener("pointerup",f,{once:!0})}),onFocus:yt(e.onFocus,()=>{c.current||o.onOpen()}),onBlur:yt(e.onBlur,o.onClose),onClick:yt(e.onClick,o.onClose)})})});LU.displayName=qR;var iwe="TooltipPortal",J4e,swe=fb(iwe,{forceMount:void 0}),Of="TooltipContent",OU=y.forwardRef((e,t)=>{const n=swe(Of,e.__scopeTooltip),{forceMount:r=n.forceMount,side:o="top",...i}=e,s=pb(Of,e.__scopeTooltip);return h.jsx(ys,{present:r||s.open,children:s.disableHoverableContent?h.jsx(DU,{side:o,...i,ref:t}):h.jsx(awe,{side:o,...i,ref:t})})}),awe=y.forwardRef((e,t)=>{const n=pb(Of,e.__scopeTooltip),r=NP(Of,e.__scopeTooltip),o=y.useRef(null),i=Pt(t,o),s,a=y.useState(null),{trigger:l,onClose:c}=n,d=o.current,{onPointerInTransitChange:f}=r,p=y.useCallback(()=>{a(null),f(!1)},f),g=y.useCallback((v,b)=>{const _=v.currentTarget,x={x:v.clientX,y:v.clientY},w=dwe(x,_.getBoundingClientRect()),C=fwe(x,w),E=hwe(b.getBoundingClientRect()),R=mwe(...C,...E);a(R),f(!0)},f);return y.useEffect(()=>()=>p(),p),y.useEffect(()=>{if(l&&d){const v=_=>g(_,d),b=_=>g(_,l);return l.addEventListener("pointerleave",v),d.addEventListener("pointerleave",b),()=>{l.removeEventListener("pointerleave",v),d.removeEventListener("pointerleave",b)}}},l,d,g,p),y.useEffect(()=>{if(s){const v=b=>{const _=b.target,x={x:b.clientX,y:b.clientY},w=(l==null?void 0:l.contains(_))||(d==null?void 0:d.contains(_)),C=!pwe(x,s);w?p():C&&(p(),c())};return document.addEventListener("pointermove",v),()=>document.removeEventListener("pointermove",v)}},l,d,s,c,p),h.jsx(DU,{...e,ref:i})}),lwe,cwe=fb(Om,{isInside:!1}),uwe=xxe("TooltipContent"),DU=y.forwardRef((e,t)=>{const{__scopeTooltip:n,children:r,"aria-label":o,onEscapeKeyDown:i,onPointerDownOutside:s,...a}=e,l=pb(Of,n),c=hb(n),{onClose:d}=l;return y.useEffect(()=>(document.addEventListener(UR,d),()=>document.removeEventListener(UR,d)),d),y.useEffect(()=>{if(l.trigger){const f=p=>{const g=p.target;g!=null&&g.contains(l.trigger)&&d()};return window.addEventListener("scroll",f,{capture:!0}),()=>window.removeEventListener("scroll",f,{capture:!0})}},l.trigger,d),h.jsx(sb,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:i,onPointerDownOutside:s,onFocusOutside:f=>f.preventDefault(),onDismiss:d,children:h.jsxs(Ube,{"data-state":l.stateAttribute,...c,...a,ref:t,style:{...a.style,"--radix-tooltip-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-tooltip-content-available-width":"var(--radix-popper-available-width)","--radix-tooltip-content-available-height":"var(--radix-popper-available-height)","--radix-tooltip-trigger-width":"var(--radix-popper-anchor-width)","--radix-tooltip-trigger-height":"var(--radix-popper-anchor-height)"},children:h.jsx(uwe,{children:r}),h.jsx(lwe,{scope:n,isInside:!0,children:h.jsx(twe,{id:l.contentId,role:"tooltip",children:o||r})})})})});OU.displayName=Of;var FU="TooltipArrow",$U=y.forwardRef((e,t)=>{const{__scopeTooltip:n,...r}=e,o=hb(n);return cwe(FU,n).isInside?null:h.jsx(qbe,{...o,...r,ref:t})});$U.displayName=FU;function dwe(e,t){const n=Math.abs(t.top-e.y),r=Math.abs(t.bottom-e.y),o=Math.abs(t.right-e.x),i=Math.abs(t.left-e.x);switch(Math.min(n,r,o,i)){case i:return"left";case o:return"right";case n:return"top";case r:return"bottom";default:throw new Error("unreachable")}}function fwe(e,t,n=5){const r=;switch(t){case"top":r.push({x:e.x-n,y:e.y+n},{x:e.x+n,y:e.y+n});break;case"bottom":r.push({x:e.x-n,y:e.y-n},{x:e.x+n,y:e.y-n});break;case"left":r.push({x:e.x+n,y:e.y-n},{x:e.x+n,y:e.y+n});break;case"right":r.push({x:e.x-n,y:e.y-n},{x:e.x-n,y:e.y+n});break}return r}function hwe(e){const{top:t,right:n,bottom:r,left:o}=e;return{x:o,y:t},{x:n,y:t},{x:n,y:r},{x:o,y:r}}function pwe(e,t){const{x:n,y:r}=e;let o=!1;for(let i=0,s=t.length-1;i<t.length;s=i++){const a=ti,l=ts,c=a.x,d=a.y,f=l.x,p=l.y;d>r!=p>r&&n<(f-c)*(r-d)/(p-d)+c&&(o=!o)}return o}function mwe(e){const t=e.slice();return t.sort((n,r)=>n.x<r.x?-1:n.x>r.x?1:n.y<r.y?-1:n.y>r.y?1:0),gwe(t)}function gwe(e){if(e.length<=1)return e.slice();const t=;for(let r=0;r<e.length;r++){const o=er;for(;t.length>=2;){const i=tt.length-1,s=tt.length-2;if((i.x-s.x)*(o.y-s.y)>=(i.y-s.y)*(o.x-s.x))t.pop();else break}t.push(o)}t.pop();const n=;for(let r=e.length-1;r>=0;r--){const o=er;for(;n.length>=2;){const i=nn.length-1,s=nn.length-2;if((i.x-s.x)*(o.y-s.y)>=(i.y-s.y)*(o.x-s.x))n.pop();else break}n.push(o)}return n.pop(),t.length===1&&n.length===1&&t0.x===n0.x&&t0.y===n0.y?t:t.concat(n)}var vwe=MU,ywe=jU,xwe=LU,zU=OU,bwe=$U;const HU=vwe,BU=ywe,WU=xwe,UU=bwe,RP=y.forwardRef(({className:e,sideOffset:t=4,side:n="top",...r},o)=>h.jsx(zU,{ref:o,sideOffset:t,side:n,className:nt("z-50 overflow-hidden rounded-md bg-gray-900 border border-gray-700 px-3 py-2 text-sm text-gray-300 animate-in fade-in-0 zoom-in-95 data-state=closed:animate-out data-state=closed:fade-out-0 data-state=closed:zoom-out-95 data-side=bottom:slide-in-from-top-2 data-side=left:slide-in-from-right-2 data-side=right:slide-in-from-left-2 data-side=top:slide-in-from-bottom-2",e),...r}));RP.displayName=zU.displayName;const $0=({content:e,children:t,side:n="top",maxWidth:r="20rem",maxHeight:o="8rem"})=>h.jsx(HU,{children:h.jsxs(BU,{children:h.jsx(WU,{asChild:!0,children:t}),h.jsxs(RP,{side:n,children:h.jsx("div",{className:"overflow-y-auto",style:{maxWidth:r,maxHeight:o},children:e}),h.jsx(UU,{className:"fill-gray-900"})})})});function xt(e,t,{checkForDefaultPrevented:n=!0}={}){return function(o){if(e==null||e(o),n===!1||!o.defaultPrevented)return t==null?void 0:t(o)}}function mb(e){const t=e+"CollectionProvider",n,r=to(t),o,i=n(t,{collectionRef:{current:null},itemMap:new Map}),s=b=>{const{scope:_,children:x}=b,w=Pe.useRef(null),C=Pe.useRef(new Map).current;return h.jsx(o,{scope:_,itemMap:C,collectionRef:w,children:x})};s.displayName=t;const a=e+"CollectionSlot",l=jf(a),c=Pe.forwardRef((b,_)=>{const{scope:x,children:w}=b,C=i(a,x),E=Pt(_,C.collectionRef);return h.jsx(l,{ref:E,children:w})});c.displayName=a;const d=e+"CollectionItemSlot",f="data-radix-collection-item",p=jf(d),g=Pe.forwardRef((b,_)=>{const{scope:x,children:w,...C}=b,E=Pe.useRef(null),R=Pt(_,E),P=i(d,x);return Pe.useEffect(()=>(P.itemMap.set(E,{ref:E,...C}),()=>void P.itemMap.delete(E))),h.jsx(p,{f:"",ref:R,children:w})});g.displayName=d;function v(b){const _=i(e+"CollectionConsumer",b);return Pe.useCallback(()=>{const w=_.collectionRef.current;if(!w)return;const C=Array.from(w.querySelectorAll(`${f}`));return Array.from(_.itemMap.values()).sort((P,N)=>C.indexOf(P.ref.current)-C.indexOf(N.ref.current))},_.collectionRef,_.itemMap)}return{Provider:s,Slot:c,ItemSlot:g},v,r}var wwe=y.createContext(void 0);function sg(e){const t=y.useContext(wwe);return e||t||"ltr"}var Swe="DismissableLayer",GR="dismissableLayer.update",_we="dismissableLayer.pointerDownOutside",Cwe="dismissableLayer.focusOutside",OD,qU=y.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),TP=y.forwardRef((e,t)=>{const{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:o,onFocusOutside:i,onInteractOutside:s,onDismiss:a,...l}=e,c=y.useContext(qU),d,f=y.useState(null),p=(d==null?void 0:d.ownerDocument)??(globalThis==null?void 0:globalThis.document),,g=y.useState({}),v=Pt(t,N=>f(N)),b=Array.from(c.layers),_=...c.layersWithOutsidePointerEventsDisabled.slice(-1),x=b.indexOf(_),w=d?b.indexOf(d):-1,C=c.layersWithOutsidePointerEventsDisabled.size>0,E=w>=x,R=Rwe(N=>{const k=N.target,I=...c.branches.some(O=>O.contains(k));!E||I||(o==null||o(N),s==null||s(N),N.defaultPrevented||a==null||a())},p),P=Twe(N=>{const k=N.target;...c.branches.some(O=>O.contains(k))||(i==null||i(N),s==null||s(N),N.defaultPrevented||a==null||a())},p);return X9(N=>{w===c.layers.size-1&&(r==null||r(N),!N.defaultPrevented&&a&&(N.preventDefault(),a()))},p),y.useEffect(()=>{if(d)return n&&(c.layersWithOutsidePointerEventsDisabled.size===0&&(OD=p.body.style.pointerEvents,p.body.style.pointerEvents="none"),c.layersWithOutsidePointerEventsDisabled.add(d)),c.layers.add(d),DD(),()=>{n&&c.layersWithOutsidePointerEventsDisabled.size===1&&(p.body.style.pointerEvents=OD)}},d,p,n,c),y.useEffect(()=>()=>{d&&(c.layers.delete(d),c.layersWithOutsidePointerEventsDisabled.delete(d),DD())},d,c),y.useEffect(()=>{const N=()=>g({});return document.addEventListener(GR,N),()=>document.removeEventListener(GR,N)},),h.jsx(Ze.div,{...l,ref:v,style:{pointerEvents:C?E?"auto":"none":void 0,...e.style},onFocusCapture:xt(e.onFocusCapture,P.onFocusCapture),onBlurCapture:xt(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:xt(e.onPointerDownCapture,R.onPointerDownCapture)})});TP.displayName=Swe;var Ewe="DismissableLayerBranch",Nwe=y.forwardRef((e,t)=>{const n=y.useContext(qU),r=y.useRef(null),o=Pt(t,r);return y.useEffect(()=>{const i=r.current;if(i)return n.branches.add(i),()=>{n.branches.delete(i)}},n.branches),h.jsx(Ze.div,{...e,ref:o})});Nwe.displayName=Ewe;function Rwe(e,t=globalThis==null?void 0:globalThis.document){const n=En(e),r=y.useRef(!1),o=y.useRef(()=>{});return y.useEffect(()=>{const i=a=>{if(a.target&&!r.current){let l=function(){GU(_we,n,c,{discrete:!0})};const c={originalEvent:a};a.pointerType==="touch"?(t.removeEventListener("click",o.current),o.current=l,t.addEventListener("click",o.current,{once:!0})):l()}else t.removeEventListener("click",o.current);r.current=!1},s=window.setTimeout(()=>{t.addEventListener("pointerdown",i)},0);return()=>{window.clearTimeout(s),t.removeEventListener("pointerdown",i),t.removeEventListener("click",o.current)}},t,n),{onPointerDownCapture:()=>r.current=!0}}function Twe(e,t=globalThis==null?void 0:globalThis.document){const n=En(e),r=y.useRef(!1);return y.useEffect(()=>{const o=i=>{i.target&&!r.current&&GU(Cwe,n,{originalEvent:i},{discrete:!1})};return t.addEventListener("focusin",o),()=>t.removeEventListener("focusin",o)},t,n),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function DD(){const e=new CustomEvent(GR);document.dispatchEvent(e)}function GU(e,t,n,{discrete:r}){const o=n.originalEvent.target,i=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&o.addEventListener(e,t,{once:!0}),r?ib(o,i):o.dispatchEvent(i)}var bS=0;function VU(){y.useEffect(()=>{const e=document.querySelectorAll("data-radix-focus-guard");return document.body.insertAdjacentElement("afterbegin",e0??FD()),document.body.insertAdjacentElement("beforeend",e1??FD()),bS++,()=>{bS===1&&document.querySelectorAll("data-radix-focus-guard").forEach(t=>t.remove()),bS--}},)}function FD(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var wS="focusScope.autoFocusOnMount",SS="focusScope.autoFocusOnUnmount",$D={bubbles:!1,cancelable:!0},kwe="FocusScope",gb=y.forwardRef((e,t)=>{const{loop:n=!1,trapped:r=!1,onMountAutoFocus:o,onUnmountAutoFocus:i,...s}=e,a,l=y.useState(null),c=En(o),d=En(i),f=y.useRef(null),p=Pt(t,b=>l(b)),g=y.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;y.useEffect(()=>{if(r){let b=function(C){if(g.paused||!a)return;const E=C.target;a.contains(E)?f.current=E:Ul(f.current,{select:!0})},_=function(C){if(g.paused||!a)return;const E=C.relatedTarget;E!==null&&(a.contains(E)||Ul(f.current,{select:!0}))},x=function(C){if(document.activeElement===document.body)for(const R of C)R.removedNodes.length>0&&Ul(a)};document.addEventListener("focusin",b),document.addEventListener("focusout",_);const w=new MutationObserver(x);return a&&w.observe(a,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",b),document.removeEventListener("focusout",_),w.disconnect()}}},r,a,g.paused),y.useEffect(()=>{if(a){HD.add(g);const b=document.activeElement;if(!a.contains(b)){const x=new CustomEvent(wS,$D);a.addEventListener(wS,c),a.dispatchEvent(x),x.defaultPrevented||(Pwe(Lwe(YU(a)),{select:!0}),document.activeElement===b&&Ul(a))}return()=>{a.removeEventListener(wS,c),setTimeout(()=>{const x=new CustomEvent(SS,$D);a.addEventListener(SS,d),a.dispatchEvent(x),x.defaultPrevented||Ul(b??document.body,{select:!0}),a.removeEventListener(SS,d),HD.remove(g)},0)}}},a,c,d,g);const v=y.useCallback(b=>{if(!n&&!r||g.paused)return;const _=b.key==="Tab"&&!b.altKey&&!b.ctrlKey&&!b.metaKey,x=document.activeElement;if(_&&x){const w=b.currentTarget,C,E=Iwe(w);C&&E?!b.shiftKey&&x===E?(b.preventDefault(),n&&Ul(C,{select:!0})):b.shiftKey&&x===C&&(b.preventDefault(),n&&Ul(E,{select:!0})):x===w&&b.preventDefault()}},n,r,g.paused);return h.jsx(Ze.div,{tabIndex:-1,...s,ref:p,onKeyDown:v})});gb.displayName=kwe;function Pwe(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(Ul(r,{select:t}),document.activeElement!==n)return}function Iwe(e){const t=YU(e),n=zD(t,e),r=zD(t.reverse(),e);returnn,r}function YU(e){const t=,n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:r=>{const o=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||o?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function zD(e,t){for(const n of e)if(!Awe(n,{upTo:t}))return n}function Awe(e,{upTo:t}){if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1}function Mwe(e){return e instanceof HTMLInputElement&&"select"in e}function Ul(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&Mwe(e)&&t&&e.select()}}var HD=jwe();function jwe(){let e=;return{add(t){const n=e0;t!==n&&(n==null||n.pause()),e=BD(e,t),e.unshift(t)},remove(t){var n;e=BD(e,t),(n=e0)==null||n.resume()}}}function BD(e,t){const n=...e,r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function Lwe(e){return e.filter(t=>t.tagName!=="A")}var kP="Popper",KU,vb=to(kP),Owe,XU=KU(kP),ZU=e=>{const{__scopePopper:t,children:n}=e,r,o=y.useState(null);return h.jsx(Owe,{scope:t,anchor:r,onAnchorChange:o,children:n})};ZU.displayName=kP;var QU="PopperAnchor",JU=y.forwardRef((e,t)=>{const{__scopePopper:n,virtualRef:r,...o}=e,i=XU(QU,n),s=y.useRef(null),a=Pt(t,s),l=y.useRef(null);return y.useEffect(()=>{const c=l.current;l.current=(r==null?void 0:r.current)||s.current,c!==l.current&&i.onAnchorChange(l.current)}),r?null:h.jsx(Ze.div,{...o,ref:a})});JU.displayName=QU;var PP="PopperContent",Dwe,Fwe=KU(PP),e7=y.forwardRef((e,t)=>{var fe,Z,oe,ce,xe,ge;const{__scopePopper:n,side:r="bottom",sideOffset:o=0,align:i="center",alignOffset:s=0,arrowPadding:a=0,avoidCollisions:l=!0,collisionBoundary:c=,collisionPadding:d=0,sticky:f="partial",hideWhenDetached:p=!1,updatePositionStrategy:g="optimized",onPlaced:v,...b}=e,_=XU(PP,n),x,w=y.useState(null),C=Pt(t,pe=>w(pe)),E,R=y.useState(null),P=ub(E),N=(P==null?void 0:P.width)??0,k=(P==null?void 0:P.height)??0,I=r+(i!=="center"?"-"+i:""),O=typeof d=="number"?d:{top:0,right:0,bottom:0,left:0,...d},j=Array.isArray(c)?c:c,H=j.length>0,q={padding:O,boundary:j.filter(zwe),altBoundary:H},{refs:M,floatingStyles:B,placement:$,isPositioned:W,middlewareData:G}=fU({strategy:"fixed",placement:I,whileElementsMounted:(...pe)=>uU(...pe,{animationFrame:g==="always"}),elements:{reference:_.anchor},middleware:hU({mainAxis:o+k,alignmentAxis:s}),l&&pU({mainAxis:!0,crossAxis:!1,limiter:f==="partial"?mU():void 0,...q}),l&&gU({...q}),vU({...q,apply:({elements:pe,rects:he,availableWidth:we,availableHeight:Ie})=>{const{width:Ce,height:Me}=he.reference,ze=pe.floating.style;ze.setProperty("--radix-popper-available-width",`${we}px`),ze.setProperty("--radix-popper-available-height",`${Ie}px`),ze.setProperty("--radix-popper-anchor-width",`${Ce}px`),ze.setProperty("--radix-popper-anchor-height",`${Me}px`)}}),E&&xU({element:E,padding:a}),Hwe({arrowWidth:N,arrowHeight:k}),p&&yU({strategy:"referenceHidden",...q})}),z,K=r7($),Q=En(v);Lo(()=>{W&&(Q==null||Q())},W,Q);const re=(fe=G.arrow)==null?void 0:fe.x,ae=(Z=G.arrow)==null?void 0:Z.y,de=((oe=G.arrow)==null?void 0:oe.centerOffset)!==0,Ne,ye=y.useState();return Lo(()=>{x&&ye(window.getComputedStyle(x).zIndex)},x),h.jsx("div",{ref:M.setFloating,"data-radix-popper-content-wrapper":"",style:{...B,transform:W?B.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:Ne,"--radix-popper-transform-origin":(ce=G.transformOrigin)==null?void 0:ce.x,(xe=G.transformOrigin)==null?void 0:xe.y.join(" "),...((ge=G.hide)==null?void 0:ge.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:h.jsx(Dwe,{scope:n,placedSide:z,onArrowChange:R,arrowX:re,arrowY:ae,shouldHideArrow:de,children:h.jsx(Ze.div,{"data-side":z,"data-align":K,...b,ref:C,style:{...b.style,animation:W?void 0:"none"}})})})});e7.displayName=PP;var t7="PopperArrow",$we={top:"bottom",right:"left",bottom:"top",left:"right"},n7=y.forwardRef(function(t,n){const{__scopePopper:r,...o}=t,i=Fwe(t7,r),s=$wei.placedSide;return h.jsx("span",{ref:i.onArrowChange,style:{position:"absolute",left:i.arrowX,top:i.arrowY,s:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}i.placedSide,transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}i.placedSide,visibility:i.shouldHideArrow?"hidden":void 0},children:h.jsx(wU,{...o,ref:n,style:{...o.style,display:"block"}})})});n7.displayName=t7;function zwe(e){return e!==null}var Hwe=e=>({name:"transformOrigin",options:e,fn(t){var _,x,w;const{placement:n,rects:r,middlewareData:o}=t,s=((_=o.arrow)==null?void 0:_.centerOffset)!==0,a=s?0:e.arrowWidth,l=s?0:e.arrowHeight,c,d=r7(n),f={start:"0%",center:"50%",end:"100%"}d,p=(((x=o.arrow)==null?void 0:x.x)??0)+a/2,g=(((w=o.arrow)==null?void 0:w.y)??0)+l/2;let v="",b="";return c==="bottom"?(v=s?f:`${p}px`,b=`${-l}px`):c==="top"?(v=s?f:`${p}px`,b=`${r.floating.height+l}px`):c==="right"?(v=`${-l}px`,b=s?f:`${g}px`):c==="left"&&(v=`${r.floating.width+l}px`,b=s?f:`${g}px`),{data:{x:v,y:b}}}});function r7(e){constt,n="center"=e.split("-");returnt,n}var IP=ZU,AP=JU,o7=e7,i7=n7;function Bwe(e,t){return y.useReducer((n,r)=>tnr??n,e)}var Nc=e=>{const{present:t,children:n}=e,r=Wwe(t),o=typeof n=="function"?n({present:r.isPresent}):y.Children.only(n),i=Pt(r.ref,Uwe(o));return typeof n=="function"||r.isPresent?y.cloneElement(o,{ref:i}):null};Nc.displayName="Presence";function Wwe(e){constt,n=y.useState(),r=y.useRef(null),o=y.useRef(e),i=y.useRef("none"),s=e?"mounted":"unmounted",a,l=Bwe(s,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return y.useEffect(()=>{const c=e0(r.current);i.current=a==="mounted"?c:"none"},a),Lo(()=>{const c=r.current,d=o.current;if(d!==e){const p=i.current,g=e0(c);e?l("MOUNT"):g==="none"||(c==null?void 0:c.display)==="none"?l("UNMOUNT"):l(d&&p!==g?"ANIMATION_OUT":"UNMOUNT"),o.current=e}},e,l),Lo(()=>{if(t){let c;const d=t.ownerDocument.defaultView??window,f=g=>{const b=e0(r.current).includes(CSS.escape(g.animationName));if(g.target===t&&b&&(l("ANIMATION_END"),!o.current)){const _=t.style.animationFillMode;t.style.animationFillMode="forwards",c=d.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=_)})}},p=g=>{g.target===t&&(i.current=e0(r.current))};return t.addEventListener("animationstart",p),t.addEventListener("animationcancel",f),t.addEventListener("animationend",f),()=>{d.clearTimeout(c),t.removeEventListener("animationstart",p),t.removeEventListener("animationcancel",f),t.removeEventListener("animationend",f)}}else l("ANIMATION_END")},t,l),{isPresent:"mounted","unmountSuspended".includes(a),ref:y.useCallback(c=>{r.current=c?getComputedStyle(c):null,n(c)},)}}function e0(e){return(e==null?void 0:e.animationName)||"none"}function Uwe(e){var r,o;let t=(r=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:r.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=(o=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:o.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var _S="rovingFocusGroup.onEntryFocus",qwe={bubbles:!1,cancelable:!0},ag="RovingFocusGroup",VR,s7,Gwe=mb(ag),Vwe,a7=to(ag,Gwe),Ywe,Kwe=Vwe(ag),l7=y.forwardRef((e,t)=>h.jsx(VR.Provider,{scope:e.__scopeRovingFocusGroup,children:h.jsx(VR.Slot,{scope:e.__scopeRovingFocusGroup,children:h.jsx(Xwe,{...e,ref:t})})}));l7.displayName=ag;var Xwe=y.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,orientation:r,loop:o=!1,dir:i,currentTabStopId:s,defaultCurrentTabStopId:a,onCurrentTabStopIdChange:l,onEntryFocus:c,preventScrollOnEntryFocus:d=!1,...f}=e,p=y.useRef(null),g=Pt(t,p),v=sg(i),b,_=xs({prop:s,defaultProp:a??null,onChange:l,caller:ag}),x,w=y.useState(!1),C=En(c),E=s7(n),R=y.useRef(!1),P,N=y.useState(0);return y.useEffect(()=>{const k=p.current;if(k)return k.addEventListener(_S,C),()=>k.removeEventListener(_S,C)},C),h.jsx(Ywe,{scope:n,orientation:r,dir:v,loop:o,currentTabStopId:b,onItemFocus:y.useCallback(k=>_(k),_),onItemShiftTab:y.useCallback(()=>w(!0),),onFocusableItemAdd:y.useCallback(()=>N(k=>k+1),),onFocusableItemRemove:y.useCallback(()=>N(k=>k-1),),children:h.jsx(Ze.div,{tabIndex:x||P===0?-1:0,"data-orientation":r,...f,ref:g,style:{outline:"none",...e.style},onMouseDown:xt(e.onMouseDown,()=>{R.current=!0}),onFocus:xt(e.onFocus,k=>{const I=!R.current;if(k.target===k.currentTarget&&I&&!x){const O=new CustomEvent(_S,qwe);if(k.currentTarget.dispatchEvent(O),!O.defaultPrevented){const j=E().filter($=>$.focusable),H=j.find($=>$.active),q=j.find($=>$.id===b),B=H,q,...j.filter(Boolean).map($=>$.ref.current);d7(B,d)}}R.current=!1}),onBlur:xt(e.onBlur,()=>w(!1))})})}),c7="RovingFocusGroupItem",u7=y.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,focusable:r=!0,active:o=!1,tabStopId:i,children:s,...a}=e,l=Mi(),c=i||l,d=Kwe(c7,n),f=d.currentTabStopId===c,p=s7(n),{onFocusableItemAdd:g,onFocusableItemRemove:v,currentTabStopId:b}=d;return y.useEffect(()=>{if(r)return g(),()=>v()},r,g,v),h.jsx(VR.ItemSlot,{scope:n,id:c,focusable:r,active:o,children:h.jsx(Ze.span,{tabIndex:f?0:-1,"data-orientation":d.orientation,...a,ref:t,onMouseDown:xt(e.onMouseDown,_=>{r?d.onItemFocus(c):_.preventDefault()}),onFocus:xt(e.onFocus,()=>d.onItemFocus(c)),onKeyDown:xt(e.onKeyDown,_=>{if(_.key==="Tab"&&_.shiftKey){d.onItemShiftTab();return}if(_.target!==_.currentTarget)return;const x=Jwe(_,d.orientation,d.dir);if(x!==void 0){if(_.metaKey||_.ctrlKey||_.altKey||_.shiftKey)return;_.preventDefault();let C=p().filter(E=>E.focusable).map(E=>E.ref.current);if(x==="last")C.reverse();else if(x==="prev"||x==="next"){x==="prev"&&C.reverse();const E=C.indexOf(_.currentTarget);C=d.loop?e1e(C,E+1):C.slice(E+1)}setTimeout(()=>d7(C))}}),children:typeof s=="function"?s({isCurrentTabStop:f,hasTabStop:b!=null}):s})})});u7.displayName=c7;var Zwe={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function Qwe(e,t){return t!=="rtl"?e:e==="ArrowLeft"?"ArrowRight":e==="ArrowRight"?"ArrowLeft":e}function Jwe(e,t,n){const r=Qwe(e.key,n);if(!(t==="vertical"&&"ArrowLeft","ArrowRight".includes(r))&&!(t==="horizontal"&&"ArrowUp","ArrowDown".includes(r)))return Zwer}function d7(e,t=!1){const n=document.activeElement;for(const r of e)if(r===n||(r.focus({preventScroll:t}),document.activeElement!==n))return}function e1e(e,t){return e.map((n,r)=>e(t+r)%e.length)}var t1e=l7,n1e=u7,r1e=function(e){if(typeof document>"u")return null;var t=Array.isArray(e)?e0:e;return t.ownerDocument.body},Rd=new WeakMap,t0=new WeakMap,n0={},CS=0,f7=function(e){return e&&(e.host||f7(e.parentNode))},o1e=function(e,t){return t.map(function(n){if(e.contains(n))return n;var r=f7(n);return r&&e.contains(r)?r:(console.error("aria-hidden",n,"in not contained inside",e,". Doing nothing"),null)}).filter(function(n){return!!n})},i1e=function(e,t,n,r){var o=o1e(t,Array.isArray(e)?e:e);n0n||(n0n=new WeakMap);var i=n0n,s=,a=new Set,l=new Set(o),c=function(f){!f||a.has(f)||(a.add(f),c(f.parentNode))};o.forEach(c);var d=function(f){!f||l.has(f)||Array.prototype.forEach.call(f.children,function(p){if(a.has(p))d(p);else try{var g=p.getAttribute(r),v=g!==null&&g!=="false",b=(Rd.get(p)||0)+1,_=(i.get(p)||0)+1;Rd.set(p,b),i.set(p,_),s.push(p),b===1&&v&&t0.set(p,!0),_===1&&p.setAttribute(n,"true"),v||p.setAttribute(r,"true")}catch(x){console.error("aria-hidden: cannot operate on ",p,x)}})};return d(t),a.clear(),CS++,function(){s.forEach(function(f){var p=Rd.get(f)-1,g=i.get(f)-1;Rd.set(f,p),i.set(f,g),p||(t0.has(f)||f.removeAttribute(r),t0.delete(f)),g||f.removeAttribute(n)}),CS--,CS||(Rd=new WeakMap,Rd=new WeakMap,t0=new WeakMap,n0={})}},MP=function(e,t,n){n===void 0&&(n="data-aria-hidden");var r=Array.from(Array.isArray(e)?e:e),o=r1e(e);return o?(r.push.apply(r,Array.from(o.querySelectorAll("aria-live, script"))),i1e(r,o,n,"aria-hidden")):function(){return null}},Ks=function(){return Ks=Object.assign||function(t){for(var n,r=1,o=arguments.length;r<o;r++){n=argumentsr;for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(ti=ni)}return t},Ks.apply(this,arguments)};function h7(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(nr=er);if(e!=null&&typeof Object.getOwnPropertySymbols=="function")for(var o=0,r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(ro)<0&&Object.prototype.propertyIsEnumerable.call(e,ro)&&(nro=ero);return n}function s1e(e,t,n){if(n||arguments.length===2)for(var r=0,o=t.length,i;r<o;r++)(i||!(r in t))&&(i||(i=Array.prototype.slice.call(t,0,r)),ir=tr);return e.concat(i||Array.prototype.slice.call(t))}var z0="right-scroll-bar-position",H0="width-before-scroll-bar",a1e="with-scroll-bars-hidden",l1e="--removed-body-scroll-bar-size";function ES(e,t){return typeof e=="function"?e(t):e&&(e.current=t),e}function c1e(e,t){var n=y.useState(function(){return{value:e,callback:t,facade:{get current(){return n.value},set current(r){var o=n.value;o!==r&&(n.value=r,n.callback(r,o))}}}})0;return n.callback=t,n.facade}var u1e=typeof window<"u"?y.useLayoutEffect:y.useEffect,WD=new WeakMap;function d1e(e,t){var n=c1e(null,function(r){return e.forEach(function(o){return ES(o,r)})});return u1e(function(){var r=WD.get(n);if(r){var o=new Set(r),i=new Set(e),s=n.current;o.forEach(function(a){i.has(a)||ES(a,null)}),i.forEach(function(a){o.has(a)||ES(a,s)})}WD.set(n,e)},e),n}function f1e(e){return e}function h1e(e,t){t===void 0&&(t=f1e);var n=,r=!1,o={read:function(){if(r)throw new Error("Sidecar: could not `read` from an `assigned` medium. `read` could be used only with `useMedium`.");return n.length?nn.length-1:e},useMedium:function(i){var s=t(i,r);return n.push(s),function(){n=n.filter(function(a){return a!==s})}},assignSyncMedium:function(i){for(r=!0;n.length;){var s=n;n=,s.forEach(i)}n={push:function(a){return i(a)},filter:function(){return n}}},assignMedium:function(i){r=!0;var s=;if(n.length){var a=n;n=,a.forEach(i),s=n}var l=function(){var d=s;s=,d.forEach(i)},c=function(){return Promise.resolve().then(l)};c(),n={push:function(d){s.push(d),c()},filter:function(d){return s=s.filter(d),n}}}};return o}function p1e(e){e===void 0&&(e={});var t=h1e(null);return t.options=Ks({async:!0,ssr:!1},e),t}var p7=function(e){var t=e.sideCar,n=h7(e,"sideCar");if(!t)throw new Error("Sidecar: please provide `sideCar` property to import the right car");var r=t.read();if(!r)throw new Error("Sidecar medium not found");return y.createElement(r,Ks({},n))};p7.isSideCarExport=!0;function m1e(e,t){return e.useMedium(t),p7}var m7=p1e(),NS=function(){},yb=y.forwardRef(function(e,t){var n=y.useRef(null),r=y.useState({onScrollCapture:NS,onWheelCapture:NS,onTouchMoveCapture:NS}),o=r0,i=r1,s=e.forwardProps,a=e.children,l=e.className,c=e.removeScrollBar,d=e.enabled,f=e.shards,p=e.sideCar,g=e.noRelative,v=e.noIsolation,b=e.inert,_=e.allowPinchZoom,x=e.as,w=x===void 0?"div":x,C=e.gapMode,E=h7(e,"forwardProps","children","className","removeScrollBar","enabled","shards","sideCar","noRelative","noIsolation","inert","allowPinchZoom","as","gapMode"),R=p,P=d1e(n,t),N=Ks(Ks({},E),o);return y.createElement(y.Fragment,null,d&&y.createElement(R,{sideCar:m7,removeScrollBar:c,shards:f,noRelative:g,noIsolation:v,inert:b,setCallbacks:i,allowPinchZoom:!!_,lockRef:n,gapMode:C}),s?y.cloneElement(y.Children.only(a),Ks(Ks({},N),{ref:P})):y.createElement(w,Ks({},N,{className:l,ref:P}),a))});yb.defaultProps={enabled:!0,removeScrollBar:!0,inert:!1};yb.classNames={fullWidth:H0,zeroRight:z0};var g1e=function(){if(typeof __webpack_nonce__<"u")return __webpack_nonce__};function v1e(){if(!document)return null;var e=document.createElement("style");e.type="text/css";var t=g1e();return t&&e.setAttribute("nonce",t),e}function y1e(e,t){e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t))}function x1e(e){var t=document.head||document.getElementsByTagName("head")0;t.appendChild(e)}var b1e=function(){var e=0,t=null;return{add:function(n){e==0&&(t=v1e())&&(y1e(t,n),x1e(t)),e++},remove:function(){e--,!e&&t&&(t.parentNode&&t.parentNode.removeChild(t),t=null)}}},w1e=function(){var e=b1e();return function(t,n){y.useEffect(function(){return e.add(t),function(){e.remove()}},t&&n)}},g7=function(){var e=w1e(),t=function(n){var r=n.styles,o=n.dynamic;return e(r,o),null};return t},S1e={left:0,top:0,right:0,gap:0},RS=function(e){return parseInt(e||"",10)||0},_1e=function(e){var t=window.getComputedStyle(document.body),n=te==="padding"?"paddingLeft":"marginLeft",r=te==="padding"?"paddingTop":"marginTop",o=te==="padding"?"paddingRight":"marginRight";returnRS(n),RS(r),RS(o)},C1e=function(e){if(e===void 0&&(e="margin"),typeof window>"u")return S1e;var t=_1e(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t0,top:t1,right:t2,gap:Math.max(0,r-n+t2-t0)}},E1e=g7(),hf="data-scroll-locked",N1e=function(e,t,n,r){var o=e.left,i=e.top,s=e.right,a=e.gap;return n===void 0&&(n="margin"),` + .`.concat(a1e,` { + overflow: hidden `).concat(r,`; + padding-right: `).concat(a,"px ").concat(r,`; + } + body`).concat(hf,` { + overflow: hidden `).concat(r,`; + overscroll-behavior: contain; + `).concat(t&&"position: relative ".concat(r,";"),n==="margin"&&` + padding-left: `.concat(o,`px; + padding-top: `).concat(i,`px; + padding-right: `).concat(s,`px; + margin-left:0; + margin-top:0; + margin-right: `).concat(a,"px ").concat(r,`; + `),n==="padding"&&"padding-right: ".concat(a,"px ").concat(r,";").filter(Boolean).join(""),` + } + + .`).concat(z0,` { + right: `).concat(a,"px ").concat(r,`; + } + + .`).concat(H0,` { + margin-right: `).concat(a,"px ").concat(r,`; + } + + .`).concat(z0," .").concat(z0,` { + right: 0 `).concat(r,`; + } + + .`).concat(H0," .").concat(H0,` { + margin-right: 0 `).concat(r,`; + } + + body`).concat(hf,` { + `).concat(l1e,": ").concat(a,`px; + } +`)},UD=function(){var e=parseInt(document.body.getAttribute(hf)||"0",10);return isFinite(e)?e:0},R1e=function(){y.useEffect(function(){return document.body.setAttribute(hf,(UD()+1).toString()),function(){var e=UD()-1;e<=0?document.body.removeAttribute(hf):document.body.setAttribute(hf,e.toString())}},)},T1e=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,o=r===void 0?"margin":r;R1e();var i=y.useMemo(function(){return C1e(o)},o);return y.createElement(E1e,{styles:N1e(i,!t,o,n?"":"!important")})},YR=!1;if(typeof window<"u")try{var r0=Object.defineProperty({},"passive",{get:function(){return YR=!0,!0}});window.addEventListener("test",r0,r0),window.removeEventListener("test",r0,r0)}catch{YR=!1}var Td=YR?{passive:!1}:!1,k1e=function(e){return e.tagName==="TEXTAREA"},v7=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return nt!=="hidden"&&!(n.overflowY===n.overflowX&&!k1e(e)&&nt==="visible")},P1e=function(e){return v7(e,"overflowY")},I1e=function(e){return v7(e,"overflowX")},qD=function(e,t){var n=t.ownerDocument,r=t;do{typeof ShadowRoot<"u"&&r instanceof ShadowRoot&&(r=r.host);var o=y7(e,r);if(o){var i=x7(e,r),s=i1,a=i2;if(s>a)return!0}r=r.parentNode}while(r&&r!==n.body);return!1},A1e=function(e){var t=e.scrollTop,n=e.scrollHeight,r=e.clientHeight;returnt,n,r},M1e=function(e){var t=e.scrollLeft,n=e.scrollWidth,r=e.clientWidth;returnt,n,r},y7=function(e,t){return e==="v"?P1e(t):I1e(t)},x7=function(e,t){return e==="v"?A1e(t):M1e(t)},j1e=function(e,t){return e==="h"&&t==="rtl"?-1:1},L1e=function(e,t,n,r,o){var i=j1e(e,window.getComputedStyle(t).direction),s=i*r,a=n.target,l=t.contains(a),c=!1,d=s>0,f=0,p=0;do{if(!a)break;var g=x7(e,a),v=g0,b=g1,_=g2,x=b-_-i*v;(v||x)&&y7(e,a)&&(f+=x,p+=v);var w=a.parentNode;a=w&&w.nodeType===Node.DOCUMENT_FRAGMENT_NODE?w.host:w}while(!l&&a!==document.body||l&&(t.contains(a)||t===a));return(d&&Math.abs(f)<1||!d&&Math.abs(p)<1)&&(c=!0),c},o0=function(e){return"changedTouches"in e?e.changedTouches0.clientX,e.changedTouches0.clientY:0,0},GD=function(e){returne.deltaX,e.deltaY},VD=function(e){return e&&"current"in e?e.current:e},O1e=function(e,t){return e0===t0&&e1===t1},D1e=function(e){return` + .block-interactivity-`.concat(e,` {pointer-events: none;} + .allow-interactivity-`).concat(e,` {pointer-events: all;} +`)},F1e=0,kd=;function $1e(e){var t=y.useRef(),n=y.useRef(0,0),r=y.useRef(),o=y.useState(F1e++)0,i=y.useState(g7)0,s=y.useRef(e);y.useEffect(function(){s.current=e},e),y.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var b=s1e(e.lockRef.current,(e.shards||).map(VD),!0).filter(Boolean);return b.forEach(function(_){return _.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),b.forEach(function(_){return _.classList.remove("allow-interactivity-".concat(o))})}}},e.inert,e.lockRef.current,e.shards);var a=y.useCallback(function(b,_){if("touches"in b&&b.touches.length===2||b.type==="wheel"&&b.ctrlKey)return!s.current.allowPinchZoom;var x=o0(b),w=n.current,C="deltaX"in b?b.deltaX:w0-x0,E="deltaY"in b?b.deltaY:w1-x1,R,P=b.target,N=Math.abs(C)>Math.abs(E)?"h":"v";if("touches"in b&&N==="h"&&P.type==="range")return!1;var k=qD(N,P);if(!k)return!0;if(k?R=N:(R=N==="v"?"h":"v",k=qD(N,P)),!k)return!1;if(!r.current&&"changedTouches"in b&&(C||E)&&(r.current=R),!R)return!0;var I=r.current||R;return L1e(I,_,b,I==="h"?C:E)},),l=y.useCallback(function(b){var _=b;if(!(!kd.length||kdkd.length-1!==i)){var x="deltaY"in _?GD(_):o0(_),w=t.current.filter(function(R){return R.name===_.type&&(R.target===_.target||_.target===R.shadowParent)&&O1e(R.delta,x)})0;if(w&&w.should){_.cancelable&&_.preventDefault();return}if(!w){var C=(s.current.shards||).map(VD).filter(Boolean).filter(function(R){return R.contains(_.target)}),E=C.length>0?a(_,C0):!s.current.noIsolation;E&&_.cancelable&&_.preventDefault()}}},),c=y.useCallback(function(b,_,x,w){var C={name:b,delta:_,target:x,should:w,shadowParent:z1e(x)};t.current.push(C),setTimeout(function(){t.current=t.current.filter(function(E){return E!==C})},1)},),d=y.useCallback(function(b){n.current=o0(b),r.current=void 0},),f=y.useCallback(function(b){c(b.type,GD(b),b.target,a(b,e.lockRef.current))},),p=y.useCallback(function(b){c(b.type,o0(b),b.target,a(b,e.lockRef.current))},);y.useEffect(function(){return kd.push(i),e.setCallbacks({onScrollCapture:f,onWheelCapture:f,onTouchMoveCapture:p}),document.addEventListener("wheel",l,Td),document.addEventListener("touchmove",l,Td),document.addEventListener("touchstart",d,Td),function(){kd=kd.filter(function(b){return b!==i}),document.removeEventListener("wheel",l,Td),document.removeEventListener("touchmove",l,Td),document.removeEventListener("touchstart",d,Td)}},);var g=e.removeScrollBar,v=e.inert;return y.createElement(y.Fragment,null,v?y.createElement(i,{styles:D1e(o)}):null,g?y.createElement(T1e,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function z1e(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const H1e=m1e(m7,$1e);var xb=y.forwardRef(function(e,t){return y.createElement(yb,Ks({},e,{ref:t,sideCar:H1e}))});xb.classNames=yb.classNames;var KR="Enter"," ",B1e="ArrowDown","PageUp","Home",b7="ArrowUp","PageDown","End",W1e=...B1e,...b7,U1e={ltr:...KR,"ArrowRight",rtl:...KR,"ArrowLeft"},q1e={ltr:"ArrowLeft",rtl:"ArrowRight"},lg="Menu",Dm,G1e,V1e=mb(lg),Gu,w7=to(lg,V1e,vb,a7),cg=vb(),S7=a7(),_7,Rc=Gu(lg),Y1e,ug=Gu(lg),C7=e=>{const{__scopeMenu:t,open:n=!1,children:r,dir:o,onOpenChange:i,modal:s=!0}=e,a=cg(t),l,c=y.useState(null),d=y.useRef(!1),f=En(i),p=sg(o);return y.useEffect(()=>{const g=()=>{d.current=!0,document.addEventListener("pointerdown",v,{capture:!0,once:!0}),document.addEventListener("pointermove",v,{capture:!0,once:!0})},v=()=>d.current=!1;return document.addEventListener("keydown",g,{capture:!0}),()=>{document.removeEventListener("keydown",g,{capture:!0}),document.removeEventListener("pointerdown",v,{capture:!0}),document.removeEventListener("pointermove",v,{capture:!0})}},),h.jsx(IP,{...a,children:h.jsx(_7,{scope:t,open:n,onOpenChange:f,content:l,onContentChange:c,children:h.jsx(Y1e,{scope:t,onClose:y.useCallback(()=>f(!1),f),isUsingKeyboardRef:d,dir:p,modal:s,children:r})})})};C7.displayName=lg;var K1e="MenuAnchor",jP=y.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e,o=cg(n);return h.jsx(AP,{...o,...r,ref:t})});jP.displayName=K1e;var LP="MenuPortal",X1e,E7=Gu(LP,{forceMount:void 0}),N7=e=>{const{__scopeMenu:t,forceMount:n,children:r,container:o}=e,i=Rc(LP,t);return h.jsx(X1e,{scope:t,forceMount:n,children:h.jsx(Nc,{present:n||i.open,children:h.jsx(ig,{asChild:!0,container:o,children:r})})})};N7.displayName=LP;var ji="MenuContent",Z1e,OP=Gu(ji),R7=y.forwardRef((e,t)=>{const n=E7(ji,e.__scopeMenu),{forceMount:r=n.forceMount,...o}=e,i=Rc(ji,e.__scopeMenu),s=ug(ji,e.__scopeMenu);return h.jsx(Dm.Provider,{scope:e.__scopeMenu,children:h.jsx(Nc,{present:r||i.open,children:h.jsx(Dm.Slot,{scope:e.__scopeMenu,children:s.modal?h.jsx(Q1e,{...o,ref:t}):h.jsx(J1e,{...o,ref:t})})})})}),Q1e=y.forwardRef((e,t)=>{const n=Rc(ji,e.__scopeMenu),r=y.useRef(null),o=Pt(t,r);return y.useEffect(()=>{const i=r.current;if(i)return MP(i)},),h.jsx(DP,{...e,ref:o,trapFocus:n.open,disableOutsidePointerEvents:n.open,disableOutsideScroll:!0,onFocusOutside:xt(e.onFocusOutside,i=>i.preventDefault(),{checkForDefaultPrevented:!1}),onDismiss:()=>n.onOpenChange(!1)})}),J1e=y.forwardRef((e,t)=>{const n=Rc(ji,e.__scopeMenu);return h.jsx(DP,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,disableOutsideScroll:!1,onDismiss:()=>n.onOpenChange(!1)})}),eSe=jf("MenuContent.ScrollLock"),DP=y.forwardRef((e,t)=>{const{__scopeMenu:n,loop:r=!1,trapFocus:o,onOpenAutoFocus:i,onCloseAutoFocus:s,disableOutsidePointerEvents:a,onEntryFocus:l,onEscapeKeyDown:c,onPointerDownOutside:d,onFocusOutside:f,onInteractOutside:p,onDismiss:g,disableOutsideScroll:v,...b}=e,_=Rc(ji,n),x=ug(ji,n),w=cg(n),C=S7(n),E=G1e(n),R,P=y.useState(null),N=y.useRef(null),k=Pt(t,N,_.onContentChange),I=y.useRef(0),O=y.useRef(""),j=y.useRef(0),H=y.useRef(null),q=y.useRef("right"),M=y.useRef(0),B=v?xb:y.Fragment,$=v?{as:eSe,allowPinchZoom:!0}:void 0,W=z=>{var fe,Z;const K=O.current+z,Q=E().filter(oe=>!oe.disabled),re=document.activeElement,ae=(fe=Q.find(oe=>oe.ref.current===re))==null?void 0:fe.textValue,de=Q.map(oe=>oe.textValue),Ne=fSe(de,K,ae),ye=(Z=Q.find(oe=>oe.textValue===Ne))==null?void 0:Z.ref.current;(function oe(ce){O.current=ce,window.clearTimeout(I.current),ce!==""&&(I.current=window.setTimeout(()=>oe(""),1e3))})(K),ye&&setTimeout(()=>ye.focus())};y.useEffect(()=>()=>window.clearTimeout(I.current),),VU();const G=y.useCallback(z=>{var Q,re;return q.current===((Q=H.current)==null?void 0:Q.side)&&pSe(z,(re=H.current)==null?void 0:re.area)},);return h.jsx(Z1e,{scope:n,searchRef:O,onItemEnter:y.useCallback(z=>{G(z)&&z.preventDefault()},G),onItemLeave:y.useCallback(z=>{var K;G(z)||((K=N.current)==null||K.focus(),P(null))},G),onTriggerLeave:y.useCallback(z=>{G(z)&&z.preventDefault()},G),pointerGraceTimerRef:j,onPointerGraceIntentChange:y.useCallback(z=>{H.current=z},),children:h.jsx(B,{...$,children:h.jsx(gb,{asChild:!0,trapped:o,onMountAutoFocus:xt(i,z=>{var K;z.preventDefault(),(K=N.current)==null||K.focus({preventScroll:!0})}),onUnmountAutoFocus:s,children:h.jsx(TP,{asChild:!0,disableOutsidePointerEvents:a,onEscapeKeyDown:c,onPointerDownOutside:d,onFocusOutside:f,onInteractOutside:p,onDismiss:g,children:h.jsx(t1e,{asChild:!0,...C,dir:x.dir,orientation:"vertical",loop:r,currentTabStopId:R,onCurrentTabStopIdChange:P,onEntryFocus:xt(l,z=>{x.isUsingKeyboardRef.current||z.preventDefault()}),preventScrollOnEntryFocus:!0,children:h.jsx(o7,{role:"menu","aria-orientation":"vertical","data-state":U7(_.open),"data-radix-menu-content":"",dir:x.dir,...w,...b,ref:k,style:{outline:"none",...b.style},onKeyDown:xt(b.onKeyDown,z=>{const Q=z.target.closest("data-radix-menu-content")===z.currentTarget,re=z.ctrlKey||z.altKey||z.metaKey,ae=z.key.length===1;Q&&(z.key==="Tab"&&z.preventDefault(),!re&&ae&&W(z.key));const de=N.current;if(z.target!==de||!W1e.includes(z.key))return;z.preventDefault();const ye=E().filter(fe=>!fe.disabled).map(fe=>fe.ref.current);b7.includes(z.key)&&ye.reverse(),uSe(ye)}),onBlur:xt(e.onBlur,z=>{z.currentTarget.contains(z.target)||(window.clearTimeout(I.current),O.current="")}),onPointerMove:xt(e.onPointerMove,Fm(z=>{const K=z.target,Q=M.current!==z.clientX;if(z.currentTarget.contains(K)&&Q){const re=z.clientX>M.current?"right":"left";q.current=re,M.current=z.clientX}}))})})})})})})});R7.displayName=ji;var tSe="MenuGroup",FP=y.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return h.jsx(Ze.div,{role:"group",...r,ref:t})});FP.displayName=tSe;var nSe="MenuLabel",T7=y.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return h.jsx(Ze.div,{...r,ref:t})});T7.displayName=nSe;var Gy="MenuItem",YD="menu.itemSelect",bb=y.forwardRef((e,t)=>{const{disabled:n=!1,onSelect:r,...o}=e,i=y.useRef(null),s=ug(Gy,e.__scopeMenu),a=OP(Gy,e.__scopeMenu),l=Pt(t,i),c=y.useRef(!1),d=()=>{const f=i.current;if(!n&&f){const p=new CustomEvent(YD,{bubbles:!0,cancelable:!0});f.addEventListener(YD,g=>r==null?void 0:r(g),{once:!0}),ib(f,p),p.defaultPrevented?c.current=!1:s.onClose()}};return h.jsx(k7,{...o,ref:l,disabled:n,onClick:xt(e.onClick,d),onPointerDown:f=>{var p;(p=e.onPointerDown)==null||p.call(e,f),c.current=!0},onPointerUp:xt(e.onPointerUp,f=>{var p;c.current||(p=f.currentTarget)==null||p.click()}),onKeyDown:xt(e.onKeyDown,f=>{const p=a.searchRef.current!=="";n||p&&f.key===" "||KR.includes(f.key)&&(f.currentTarget.click(),f.preventDefault())})})});bb.displayName=Gy;var k7=y.forwardRef((e,t)=>{const{__scopeMenu:n,disabled:r=!1,textValue:o,...i}=e,s=OP(Gy,n),a=S7(n),l=y.useRef(null),c=Pt(t,l),d,f=y.useState(!1),p,g=y.useState("");return y.useEffect(()=>{const v=l.current;v&&g((v.textContent??"").trim())},i.children),h.jsx(Dm.ItemSlot,{scope:n,disabled:r,textValue:o??p,children:h.jsx(n1e,{asChild:!0,...a,focusable:!r,children:h.jsx(Ze.div,{role:"menuitem","data-highlighted":d?"":void 0,"aria-disabled":r||void 0,"data-disabled":r?"":void 0,...i,ref:c,onPointerMove:xt(e.onPointerMove,Fm(v=>{r?s.onItemLeave(v):(s.onItemEnter(v),v.defaultPrevented||v.currentTarget.focus({preventScroll:!0}))})),onPointerLeave:xt(e.onPointerLeave,Fm(v=>s.onItemLeave(v))),onFocus:xt(e.onFocus,()=>f(!0)),onBlur:xt(e.onBlur,()=>f(!1))})})})}),rSe="MenuCheckboxItem",P7=y.forwardRef((e,t)=>{const{checked:n=!1,onCheckedChange:r,...o}=e;return h.jsx(L7,{scope:e.__scopeMenu,checked:n,children:h.jsx(bb,{role:"menuitemcheckbox","aria-checked":Vy(n)?"mixed":n,...o,ref:t,"data-state":HP(n),onSelect:xt(o.onSelect,()=>r==null?void 0:r(Vy(n)?!0:!n),{checkForDefaultPrevented:!1})})})});P7.displayName=rSe;var I7="MenuRadioGroup",oSe,iSe=Gu(I7,{value:void 0,onValueChange:()=>{}}),A7=y.forwardRef((e,t)=>{const{value:n,onValueChange:r,...o}=e,i=En(r);return h.jsx(oSe,{scope:e.__scopeMenu,value:n,onValueChange:i,children:h.jsx(FP,{...o,ref:t})})});A7.displayName=I7;var M7="MenuRadioItem",j7=y.forwardRef((e,t)=>{const{value:n,...r}=e,o=iSe(M7,e.__scopeMenu),i=n===o.value;return h.jsx(L7,{scope:e.__scopeMenu,checked:i,children:h.jsx(bb,{role:"menuitemradio","aria-checked":i,...r,ref:t,"data-state":HP(i),onSelect:xt(r.onSelect,()=>{var s;return(s=o.onValueChange)==null?void 0:s.call(o,n)},{checkForDefaultPrevented:!1})})})});j7.displayName=M7;var $P="MenuItemIndicator",L7,sSe=Gu($P,{checked:!1}),O7=y.forwardRef((e,t)=>{const{__scopeMenu:n,forceMount:r,...o}=e,i=sSe($P,n);return h.jsx(Nc,{present:r||Vy(i.checked)||i.checked===!0,children:h.jsx(Ze.span,{...o,ref:t,"data-state":HP(i.checked)})})});O7.displayName=$P;var aSe="MenuSeparator",D7=y.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e;return h.jsx(Ze.div,{role:"separator","aria-orientation":"horizontal",...r,ref:t})});D7.displayName=aSe;var lSe="MenuArrow",F7=y.forwardRef((e,t)=>{const{__scopeMenu:n,...r}=e,o=cg(n);return h.jsx(i7,{...o,...r,ref:t})});F7.displayName=lSe;var zP="MenuSub",cSe,$7=Gu(zP),z7=e=>{const{__scopeMenu:t,children:n,open:r=!1,onOpenChange:o}=e,i=Rc(zP,t),s=cg(t),a,l=y.useState(null),c,d=y.useState(null),f=En(o);return y.useEffect(()=>(i.open===!1&&f(!1),()=>f(!1)),i.open,f),h.jsx(IP,{...s,children:h.jsx(_7,{scope:t,open:r,onOpenChange:f,content:c,onContentChange:d,children:h.jsx(cSe,{scope:t,contentId:Mi(),triggerId:Mi(),trigger:a,onTriggerChange:l,children:n})})})};z7.displayName=zP;var wp="MenuSubTrigger",H7=y.forwardRef((e,t)=>{const n=Rc(wp,e.__scopeMenu),r=ug(wp,e.__scopeMenu),o=$7(wp,e.__scopeMenu),i=OP(wp,e.__scopeMenu),s=y.useRef(null),{pointerGraceTimerRef:a,onPointerGraceIntentChange:l}=i,c={__scopeMenu:e.__scopeMenu},d=y.useCallback(()=>{s.current&&window.clearTimeout(s.current),s.current=null},);return y.useEffect(()=>d,d),y.useEffect(()=>{const f=a.current;return()=>{window.clearTimeout(f),l(null)}},a,l),h.jsx(jP,{asChild:!0,...c,children:h.jsx(k7,{id:o.triggerId,"aria-haspopup":"menu","aria-expanded":n.open,"aria-controls":o.contentId,"data-state":U7(n.open),...e,ref:ob(t,o.onTriggerChange),onClick:f=>{var p;(p=e.onClick)==null||p.call(e,f),!(e.disabled||f.defaultPrevented)&&(f.currentTarget.focus(),n.open||n.onOpenChange(!0))},onPointerMove:xt(e.onPointerMove,Fm(f=>{i.onItemEnter(f),!f.defaultPrevented&&!e.disabled&&!n.open&&!s.current&&(i.onPointerGraceIntentChange(null),s.current=window.setTimeout(()=>{n.onOpenChange(!0),d()},100))})),onPointerLeave:xt(e.onPointerLeave,Fm(f=>{var g,v;d();const p=(g=n.content)==null?void 0:g.getBoundingClientRect();if(p){const b=(v=n.content)==null?void 0:v.dataset.side,_=b==="right",x=_?-5:5,w=p_?"left":"right",C=p_?"right":"left";i.onPointerGraceIntentChange({area:{x:f.clientX+x,y:f.clientY},{x:w,y:p.top},{x:C,y:p.top},{x:C,y:p.bottom},{x:w,y:p.bottom},side:b}),window.clearTimeout(a.current),a.current=window.setTimeout(()=>i.onPointerGraceIntentChange(null),300)}else{if(i.onTriggerLeave(f),f.defaultPrevented)return;i.onPointerGraceIntentChange(null)}})),onKeyDown:xt(e.onKeyDown,f=>{var g;const p=i.searchRef.current!=="";e.disabled||p&&f.key===" "||U1er.dir.includes(f.key)&&(n.onOpenChange(!0),(g=n.content)==null||g.focus(),f.preventDefault())})})})});H7.displayName=wp;var B7="MenuSubContent",W7=y.forwardRef((e,t)=>{const n=E7(ji,e.__scopeMenu),{forceMount:r=n.forceMount,...o}=e,i=Rc(ji,e.__scopeMenu),s=ug(ji,e.__scopeMenu),a=$7(B7,e.__scopeMenu),l=y.useRef(null),c=Pt(t,l);return h.jsx(Dm.Provider,{scope:e.__scopeMenu,children:h.jsx(Nc,{present:r||i.open,children:h.jsx(Dm.Slot,{scope:e.__scopeMenu,children:h.jsx(DP,{id:a.contentId,"aria-labelledby":a.triggerId,...o,ref:c,align:"start",side:s.dir==="rtl"?"left":"right",disableOutsidePointerEvents:!1,disableOutsideScroll:!1,trapFocus:!1,onOpenAutoFocus:d=>{var f;s.isUsingKeyboardRef.current&&((f=l.current)==null||f.focus()),d.preventDefault()},onCloseAutoFocus:d=>d.preventDefault(),onFocusOutside:xt(e.onFocusOutside,d=>{d.target!==a.trigger&&i.onOpenChange(!1)}),onEscapeKeyDown:xt(e.onEscapeKeyDown,d=>{s.onClose(),d.preventDefault()}),onKeyDown:xt(e.onKeyDown,d=>{var g;const f=d.currentTarget.contains(d.target),p=q1es.dir.includes(d.key);f&&p&&(i.onOpenChange(!1),(g=a.trigger)==null||g.focus(),d.preventDefault())})})})})})});W7.displayName=B7;function U7(e){return e?"open":"closed"}function Vy(e){return e==="indeterminate"}function HP(e){return Vy(e)?"indeterminate":e?"checked":"unchecked"}function uSe(e){const t=document.activeElement;for(const n of e)if(n===t||(n.focus(),document.activeElement!==t))return}function dSe(e,t){return e.map((n,r)=>e(t+r)%e.length)}function fSe(e,t,n){const o=t.length>1&&Array.from(t).every(c=>c===t0)?t0:t,i=n?e.indexOf(n):-1;let s=dSe(e,Math.max(i,0));o.length===1&&(s=s.filter(c=>c!==n));const l=s.find(c=>c.toLowerCase().startsWith(o.toLowerCase()));return l!==n?l:void 0}function hSe(e,t){const{x:n,y:r}=e;let o=!1;for(let i=0,s=t.length-1;i<t.length;s=i++){const a=ti,l=ts,c=a.x,d=a.y,f=l.x,p=l.y;d>r!=p>r&&n<(f-c)*(r-d)/(p-d)+c&&(o=!o)}return o}function pSe(e,t){if(!t)return!1;const n={x:e.clientX,y:e.clientY};return hSe(n,t)}function Fm(e){return t=>t.pointerType==="mouse"?e(t):void 0}var mSe=C7,gSe=jP,vSe=N7,ySe=R7,xSe=FP,bSe=T7,wSe=bb,SSe=P7,_Se=A7,CSe=j7,ESe=O7,NSe=D7,RSe=F7,TSe=z7,kSe=H7,PSe=W7,wb="DropdownMenu",ISe,e5e=to(wb,w7),no=w7(),ASe,q7=ISe(wb),G7=e=>{const{__scopeDropdownMenu:t,children:n,dir:r,open:o,defaultOpen:i,onOpenChange:s,modal:a=!0}=e,l=no(t),c=y.useRef(null),d,f=xs({prop:o,defaultProp:i??!1,onChange:s,caller:wb});return h.jsx(ASe,{scope:t,triggerId:Mi(),triggerRef:c,contentId:Mi(),open:d,onOpenChange:f,onOpenToggle:y.useCallback(()=>f(p=>!p),f),modal:a,children:h.jsx(mSe,{...l,open:d,onOpenChange:f,dir:r,modal:a,children:n})})};G7.displayName=wb;var V7="DropdownMenuTrigger",Y7=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,disabled:r=!1,...o}=e,i=q7(V7,n),s=no(n);return h.jsx(gSe,{asChild:!0,...s,children:h.jsx(Ze.button,{type:"button",id:i.triggerId,"aria-haspopup":"menu","aria-expanded":i.open,"aria-controls":i.open?i.contentId:void 0,"data-state":i.open?"open":"closed","data-disabled":r?"":void 0,disabled:r,...o,ref:ob(t,i.triggerRef),onPointerDown:xt(e.onPointerDown,a=>{!r&&a.button===0&&a.ctrlKey===!1&&(i.onOpenToggle(),i.open||a.preventDefault())}),onKeyDown:xt(e.onKeyDown,a=>{r||("Enter"," ".includes(a.key)&&i.onOpenToggle(),a.key==="ArrowDown"&&i.onOpenChange(!0),"Enter"," ","ArrowDown".includes(a.key)&&a.preventDefault())})})})});Y7.displayName=V7;var MSe="DropdownMenuPortal",K7=e=>{const{__scopeDropdownMenu:t,...n}=e,r=no(t);return h.jsx(vSe,{...r,...n})};K7.displayName=MSe;var X7="DropdownMenuContent",Z7=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=q7(X7,n),i=no(n),s=y.useRef(!1);return h.jsx(ySe,{id:o.contentId,"aria-labelledby":o.triggerId,...i,...r,ref:t,onCloseAutoFocus:xt(e.onCloseAutoFocus,a=>{var l;s.current||(l=o.triggerRef.current)==null||l.focus(),s.current=!1,a.preventDefault()}),onInteractOutside:xt(e.onInteractOutside,a=>{const l=a.detail.originalEvent,c=l.button===0&&l.ctrlKey===!0,d=l.button===2||c;(!o.modal||d)&&(s.current=!0)}),style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})});Z7.displayName=X7;var jSe="DropdownMenuGroup",LSe=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(xSe,{...o,...r,ref:t})});LSe.displayName=jSe;var OSe="DropdownMenuLabel",Q7=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(bSe,{...o,...r,ref:t})});Q7.displayName=OSe;var DSe="DropdownMenuItem",J7=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(wSe,{...o,...r,ref:t})});J7.displayName=DSe;var FSe="DropdownMenuCheckboxItem",eq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(SSe,{...o,...r,ref:t})});eq.displayName=FSe;var $Se="DropdownMenuRadioGroup",tq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(_Se,{...o,...r,ref:t})});tq.displayName=$Se;var zSe="DropdownMenuRadioItem",nq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(CSe,{...o,...r,ref:t})});nq.displayName=zSe;var HSe="DropdownMenuItemIndicator",rq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(ESe,{...o,...r,ref:t})});rq.displayName=HSe;var BSe="DropdownMenuSeparator",oq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(NSe,{...o,...r,ref:t})});oq.displayName=BSe;var WSe="DropdownMenuArrow",USe=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(RSe,{...o,...r,ref:t})});USe.displayName=WSe;var qSe=e=>{const{__scopeDropdownMenu:t,children:n,open:r,onOpenChange:o,defaultOpen:i}=e,s=no(t),a,l=xs({prop:r,defaultProp:i??!1,onChange:o,caller:"DropdownMenuSub"});return h.jsx(TSe,{...s,open:a,onOpenChange:l,children:n})},GSe="DropdownMenuSubTrigger",iq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(kSe,{...o,...r,ref:t})});iq.displayName=GSe;var VSe="DropdownMenuSubContent",sq=y.forwardRef((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=no(n);return h.jsx(PSe,{...o,...r,ref:t,style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})});sq.displayName=VSe;var YSe=G7,KSe=Y7,aq=K7,lq=Z7,cq=Q7,uq=J7,dq=eq,XSe=tq,fq=nq,hq=rq,pq=oq,ZSe=qSe,mq=iq,gq=sq;const BP=YSe,WP=KSe,QSe=ZSe,JSe=XSe,vq=y.forwardRef(({className:e,inset:t,children:n,...r},o)=>h.jsxs(mq,{ref:o,className:nt("flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-state=open:bg-accent",t&&"pl-8",e),...r,children:n,h.jsx(m0e,{className:"ml-auto h-4 w-4"})}));vq.displayName=mq.displayName;const yq=y.forwardRef(({className:e,...t},n)=>h.jsx(aq,{children:h.jsx(gq,{ref:n,className:nt("z-50 min-w-8rem overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-state=open:animate-in data-state=closed:animate-out data-state=closed:fade-out-0 data-state=open:fade-in-0 data-state=closed:zoom-out-95 data-state=open:zoom-in-95 data-side=bottom:slide-in-from-top-2 data-side=left:slide-in-from-right-2 data-side=right:slide-in-from-left-2 data-side=top:slide-in-from-bottom-2",e),...t})}));yq.displayName=gq.displayName;const Sb=y.forwardRef(({className:e,sideOffset:t=4,...n},r)=>h.jsx(aq,{children:h.jsx(lq,{ref:r,sideOffset:t,className:nt("z-50 min-w-8rem overflow-hidden rounded-md border bg-gray-800 p-1 text-gray-100 shadow-md data-state=open:animate-in data-state=closed:animate-out data-state=closed:fade-out-0 data-state=open:fade-in-0 data-state=closed:zoom-out-95 data-state=open:zoom-in-95 data-side=bottom:slide-in-from-top-2 data-side=left:slide-in-from-right-2 data-side=right:slide-in-from-left-2 data-side=top:slide-in-from-bottom-2",e),...n})}));Sb.displayName=lq.displayName;const dg=y.forwardRef(({className:e,inset:t,...n},r)=>h.jsx(uq,{ref:r,className:nt("relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-gray-700 focus:text-gray-100 hover:bg-gray-700 hover:text-gray-100 data-disabled:pointer-events-none data-disabled:opacity-50 text-gray-300",t&&"pl-8",e),...n}));dg.displayName=uq.displayName;const e_e=y.forwardRef(({className:e,children:t,checked:n,...r},o)=>h.jsxs(dq,{ref:o,className:nt("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",e),checked:n,...r,children:h.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:h.jsx(hq,{children:h.jsx(dP,{className:"h-4 w-4"})})}),t}));e_e.displayName=dq.displayName;const Sp=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(fq,{ref:r,className:nt("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",e),...n,children:h.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:h.jsx(hq,{children:h.jsx(g0e,{className:"h-2 w-2 fill-current"})})}),t}));Sp.displayName=fq.displayName;const t_e=y.forwardRef(({className:e,inset:t,...n},r)=>h.jsx(cq,{ref:r,className:nt("px-2 py-1.5 text-sm font-semibold",t&&"pl-8",e),...n}));t_e.displayName=cq.displayName;const UP=y.forwardRef(({className:e,...t},n)=>h.jsx(pq,{ref:n,className:nt("-mx-1 my-1 h-px bg-muted",e),...t}));UP.displayName=pq.displayName;const fs=y.forwardRef(({className:e,variant:t="default",size:n="default",...r},o)=>h.jsx("button",{className:nt("inline-flex items-center justify-center rounded-md text-sm font-medium ","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500","disabled:opacity-50 disabled:pointer-events-none",{"bg-orange-800 text-white hover:bg-orange-900":t==="default","bg-red-700 text-white hover:bg-red-800":t==="destructive","bg-transparent border border-gray-600 hover:bg-gray-900/50":t==="outline","bg-transparent hover:bg-gray-700/50":t==="ghost","bg-transparent text-blue-500 underline-offset-4 hover:underline":t==="link"},{"px-4 py-2":n==="default","px-3 py-1 text-xs":n==="sm","px-6 py-3":n==="lg"},e),ref:o,...r}));fs.displayName="Button";function n_e(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm75.31 260.69a16 16 0 1 1-22.62 22.62L256 278.63l-52.69 52.68a16 16 0 0 1-22.62-22.62L233.37 256l-52.68-52.69a16 16 0 0 1 22.62-22.62L256 233.37l52.69-52.68a16 16 0 0 1 22.62 22.62L278.63 256z"},child:}})(e)}function r_e(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"m289.94 256 95-95A24 24 0 0 0 351 127l-95 95-95-95a24 24 0 0 0-34 34l95 95-95 95a24 24 0 1 0 34 34l95-95 95 95a24 24 0 0 0 34-34z"},child:}})(e)}function o_e(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{fill:"none",strokeMiterlimit:"10",strokeWidth:"32",d:"M221.09 64a157.09 157.09 0 1 0 157.09 157.09A157.1 157.1 0 0 0 221.09 64z"},child:},{tag:"path",attr:{fill:"none",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"32",d:"M338.29 338.29 448 448"},child:}})(e)}function i_e(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"circle",attr:{cx:"256",cy:"256",r:"48"},child:},{tag:"path",attr:{d:"m470.39 300-.47-.38-31.56-24.75a16.11 16.11 0 0 1-6.1-13.33v-11.56a16 16 0 0 1 6.11-13.22L469.92 212l.47-.38a26.68 26.68 0 0 0 5.9-34.06l-42.71-73.9a1.59 1.59 0 0 1-.13-.22A26.86 26.86 0 0 0 401 92.14l-.35.13-37.1 14.93a15.94 15.94 0 0 1-14.47-1.29q-4.92-3.1-10-5.86a15.94 15.94 0 0 1-8.19-11.82l-5.59-39.59-.12-.72A27.22 27.22 0 0 0 298.76 26h-85.52a26.92 26.92 0 0 0-26.45 22.39l-.09.56-5.57 39.67a16 16 0 0 1-8.13 11.82 175.21 175.21 0 0 0-10 5.82 15.92 15.92 0 0 1-14.43 1.27l-37.13-15-.35-.14a26.87 26.87 0 0 0-32.48 11.34l-.13.22-42.77 73.95a26.71 26.71 0 0 0 5.9 34.1l.47.38 31.56 24.75a16.11 16.11 0 0 1 6.1 13.33v11.56a16 16 0 0 1-6.11 13.22L42.08 300l-.47.38a26.68 26.68 0 0 0-5.9 34.06l42.71 73.9a1.59 1.59 0 0 1 .13.22 26.86 26.86 0 0 0 32.45 11.3l.35-.13 37.07-14.93a15.94 15.94 0 0 1 14.47 1.29q4.92 3.11 10 5.86a15.94 15.94 0 0 1 8.19 11.82l5.56 39.59.12.72A27.22 27.22 0 0 0 213.24 486h85.52a26.92 26.92 0 0 0 26.45-22.39l.09-.56 5.57-39.67a16 16 0 0 1 8.18-11.82c3.42-1.84 6.76-3.79 10-5.82a15.92 15.92 0 0 1 14.43-1.27l37.13 14.95.35.14a26.85 26.85 0 0 0 32.48-11.34 2.53 2.53 0 0 1 .13-.22l42.71-73.89a26.7 26.7 0 0 0-5.89-34.11zm-134.48-40.24a80 80 0 1 1-83.66-83.67 80.21 80.21 0 0 1 83.66 83.67z"},child:}})(e)}const ol=y.forwardRef(({className:e,type:t,...n},r)=>h.jsx("input",{type:t,className:nt("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",e),ref:r,...n}));ol.displayName="Input";const xq=y.memo(function({onSearchChange:t,debounceMs:n=150,placeholder:r="Search...",className:o,initialValue:i="",autoFocus:s=!1}){consta,l=y.useState(i),c=y.useRef();y.useEffect(()=>(c.current&&clearTimeout(c.current),c.current=setTimeout(()=>{t(a)},n),()=>{c.current&&clearTimeout(c.current)}),a,n,t);const d=y.useCallback(g=>{l(g.target.value)},),f=y.useCallback(()=>{l("")},),p=y.useCallback(g=>{g.key==="Escape"&&(l(""),g.currentTarget.blur())},);return h.jsxs("div",{className:nt("relative flex items-center",o),children:h.jsx(o_e,{className:"absolute left-2.5 h-4 w-4 text-slate-400 pointer-events-none","aria-hidden":"true"}),h.jsx(ol,{type:"text",value:a,onChange:d,onKeyDown:p,placeholder:r,autoFocus:s,className:`pl-8 pr-8 h-8 text-xs bg-slate-900/60 border-slate-700/50 + text-slate-200 placeholder:text-slate-500 + focus-visible:ring-emerald-500/40 focus-visible:border-emerald-500/40`,"aria-label":"Search input"}),a&&h.jsx("button",{type:"button",onClick:f,className:`absolute right-2 h-4 w-4 text-slate-400 hover:text-slate-200 + transition-colors focus:outline-none focus-visible:ring-1 + focus-visible:ring-emerald-500/40 rounded`,"aria-label":"Clear search",children:h.jsx(n_e,{className:"h-4 w-4"})})})});function qP(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"},child:}})(e)}function GP(e){return Be({attr:{viewBox:"0 0 576 512"},child:{tag:"path",attr:{d:"M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"},child:}})(e)}function B0(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"},child:}})(e)}function s_e(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M16 128h416c8.84 0 16-7.16 16-16V48c0-8.84-7.16-16-16-16H16C7.16 32 0 39.16 0 48v64c0 8.84 7.16 16 16 16zm480 80H80c-8.84 0-16 7.16-16 16v64c0 8.84 7.16 16 16 16h416c8.84 0 16-7.16 16-16v-64c0-8.84-7.16-16-16-16zm-64 176H16c-8.84 0-16 7.16-16 16v64c0 8.84 7.16 16 16 16h416c8.84 0 16-7.16 16-16v-64c0-8.84-7.16-16-16-16z"},child:}})(e)}function bq(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"},child:}})(e)}const Yy={Ot.AUDIO:"Audio",Ot.CACHE:"Cache",Ot.CODEC:"Codec",Ot.CODING:"Coding",Ot.COMPOSE:"Compose",Ot.CONSOLE:"Console",Ot.CONTAINER:"Container",Ot.CORE:"Core",Ot.CTIME:"CTime",Ot.DASH:"DASH",Ot.FILTER:"Filter",Ot.HTTP:"HTTP",Ot.INTERACT:"Interact",Ot.MEDIA:"Media",Ot.MEM:"Mem",Ot.MMIO:"MMIO",Ot.MODULE:"Module",Ot.MUTEX:"Mutex",Ot.NETWORK:"Network",Ot.PARSER:"Parser",Ot.RMTWS:"RMTWS",Ot.ROUTE:"Route",Ot.RTI:"RTI",Ot.RTP:"RTP",Ot.SCENE:"Scene",Ot.SCHED:"Sched",Ot.SCRIPT:"Script",Ot.ALL:"All"},VP={At.QUIET:"bg-gray-500",At.ERROR:"bg-red-500",At.WARNING:"bg-yellow-500/60",At.INFO:"bg-green-700/60",At.DEBUG:"bg-blue-400"},KD={At.QUIET:"bg-gray-500 text-gray-100 hover:opacity-80",At.ERROR:"bg-red-500 text-red-100 hover:opacity-80",At.WARNING:"bg-yellow-500/60 text-yellow-100 hover:opacity-80",At.INFO:"bg-green-700/60 text-green-100 hover:opacity-80",At.DEBUG:"bg-blue-400 text-blue-100 hover:opacity-80"},a_e=new Set("mutex@debug","all@debug"),Jc={iconComponents:{0:B0,1:bq,2:GP,3:B0,4:B0},iconClasses:{0:"text-gray-500 shrink-0 mt-1",1:"text-red-500 shrink-0 mt-1",2:"text-yellow-500 shrink-0 mt-1",3:"text-emerald-300/90 shrink-0 mt-1",4:"text-blue-300 shrink-0 mt-1"},styles:{0:"text-gray-500",1:"text-red-500",2:"text-yellow-500",3:"text-emerald-300/90",4:"text-blue-300"},names:{0:"QUIET",1:"ERROR",2:"WARNING",3:"INFO",4:"DEBUG"}},_b=(e,t,n)=>e===Ot.ALL?n:te??n,l_e=e=>Object.values(Ot).sort((t,n)=>{if(t===e)return-1;if(n===e||t===Ot.ALL)return 1;if(n===Ot.ALL)return-1;const r=Yyt,o=Yyn;return!r&&!o?0:r?o?r.localeCompare(o):-1:1}),YP=e=>e.replace("bg-","text-");function KP(e,t,n){return y.useMemo(()=>{if(!t.trim())return e;const r=t.toLowerCase();return e.filter(o=>n(o).some(s=>s.toLowerCase().includes(r)))},e,t,n)}const ht=y.forwardRef(({className:e,variant:t="default",...n},r)=>{const o={status:"border-transparent bg-gray-950 text-primary-foreground shadow hover:bg-primary/80",default:"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",warning:"border-yellow-600 bg-black text-yellow-600 shadow hover:bg-warning/80",destructive:"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",outline:"text-foreground bg-primary",logs:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80 px-1 py-0 text-gray-900 font-normal",success:"bg-green-600/20 text-green-400 border-green-500/30"};return h.jsx("div",{ref:r,className:nt("inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors","focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",ot,e),...n})});ht.displayName="Badge";const c_e=y.memo(function({tool:t,displayName:n,effectiveLevel:r,isCurrentTool:o,isSubMenuOpen:i,onToolNavigate:s,onSubMenuToggle:a,onLevelSelect:l,onMouseLeaveSubMenu:c}){const d=y.useMemo(()=>Object.values(At).filter(f=>{const p=`${t}@${f}`;return!a_e.has(p)}),t);return h.jsxs("div",{className:`flex items-center z-10 px-3 py-2 text-xs + text-slate-200 bg-slate-900 + hover:bg-slate-800/60 `,style:{overflow:"visible"},children:h.jsx("span",{onClick:()=>s(t),className:`w-1/2 px-1 py-1 cursor-pointer + ${o?"text-slate-100 bg-slate-800 rounded-lg font-medium ring-1 ring-emerald-700/40":"text-slate-300 hover:text-slate-100"}`,children:h.jsx("span",{className:"inline-block",children:n})}),h.jsx("div",{className:"w-1/2 flex justify-end",children:h.jsxs(QSe,{open:i,onOpenChange:a,children:h.jsx(vq,{className:"p-0 h-auto",children:h.jsx(ht,{variant:"logs",className:`cursor-pointer + ring-1 ring-slate-700/40 + ${KDr} + `,children:r})}),h.jsx(yq,{className:`z-50 bg-monitor-surface/55 border border-slate-700/50 + shadow-lg shadow-black/40 rounded-md`,onMouseLeave:c,children:d.map(f=>{const p=r===f;return h.jsx(dg,{className:`cursor-pointer text-sm + text-slate-200 hover:bg-slate-800/60 + ${p?"bg-emerald-500/10 text-emerald-300":""}`,onSelect:g=>{g.preventDefault(),l(f)},children:h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("div",{className:`w-3 h-3 rounded-full + ${p?"bg-emerald-400":KDf.split(" ")0}`}),h.jsx("span",{className:"capitalize font-cond",children:f}),p&&h.jsx("span",{className:"ml-auto text-11px text-slate-400",children:"Current"})})},f)})})})})})}),u_e=y.memo(y.forwardRef(function({levelsByTool:t,defaultAllLevel:n,currentTool:r,onToolLevelChange:o,onDefaultAllLevelChange:i,onToolNavigate:s},a){constl,c=y.useState(!1),d,f=y.useState(null),p,g=y.useState(""),v=y.useCallback((R,P)=>{R===Ot.ALL?i(P):o(R,P),f(null)},o,i),b=y.useMemo(()=>l_e(r),r),_=KP(b,p,y.useCallback(R=>R,YyR,)),x=y.useMemo(()=>l?_.map(R=>{const P=_b(R,t,n);return{tool:R,effectiveLevel:P,isCurrentTool:R===r,displayName:YyR}}):,l,_,t,n,r),w=y.useCallback((R,P)=>{v(R,P)},v),C=y.useCallback(R=>{g(R)},),E=y.useCallback(R=>{c(R),R||(g(""),f(null))},);return h.jsxs(BP,{open:l,onOpenChange:E,children:h.jsx(WP,{asChild:!0,children:h.jsx(fs,{ref:a,variant:"ghost",size:"sm",className:`h-7 w-7 p-0 rounded-md + text-slate-300 hover:text-slate-100 + hover:bg-slate-800/60 + focus-visible:ring-2 focus-visible:ring-emerald-500/40 + focus-visible:ring-offset-2 focus-visible:ring-offset-black`,"aria-label":"Open logs settings",children:h.jsx(i_e,{className:"h-5 w-5"})})}),h.jsx(Sb,{align:"end",side:"bottom",sideOffset:6,avoidCollisions:!0,onScroll:()=>f(null),className:` z-20 w-64 max-h-80 overflow-y-auto p-0 + rounded-xl border border-slate-700/50 + bg-monitor-panel/55 + shadow-xl shadow-black/50`,children:h.jsxs("div",{className:"relative font-cond text-slate-200 text-xs",children:h.jsxs("div",{className:`sticky top-0 z-10 w-full flex flex-col + bg-slate-900/95 border-b border-slate-700/50`,children:h.jsx("div",{className:"px-3 py-2 text-sm text-slate-300",children:"Logs Configuration"}),h.jsx("div",{className:"px-3 pb-2",children:h.jsx(xq,{onSearchChange:C,placeholder:"Filter tools...",debounceMs:150})}),h.jsx(UP,{}),h.jsxs("div",{className:"flex items-center justify-between px-8 py-2 text-11px font-medium text-slate-400",style:{background:"linear-gradient(to right, transparent 49%, rgba(148,163,184,.6) 49%, rgba(148,163,184,.6) 51%, transparent 51%)"},children:h.jsx("span",{children:"Tool"}),h.jsx("span",{children:"Level"})})}),l&&x.map(({tool:R,effectiveLevel:P,isCurrentTool:N,displayName:k})=>h.jsx(c_e,{tool:R,displayName:k,effectiveLevel:P,isCurrentTool:N,isSubMenuOpen:d===R,onToolNavigate:()=>s==null?void 0:s(R),onSubMenuToggle:I=>f(I?R:null),onLevelSelect:I=>w(R,I),onMouseLeaveSubMenu:()=>f(null)},R))})})})}));function wq(e){const t=y.useRef({value:e,previous:e});return y.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),e)}var Cb="Checkbox",d_e,t5e=to(Cb),f_e,XP=d_e(Cb);function h_e(e){const{__scopeCheckbox:t,checked:n,children:r,defaultChecked:o,disabled:i,form:s,name:a,onCheckedChange:l,required:c,value:d="on",internal_do_not_use_render:f}=e,p,g=xs({prop:n,defaultProp:o??!1,onChange:l,caller:Cb}),v,b=y.useState(null),_,x=y.useState(null),w=y.useRef(!1),C=v?!!s||!!v.closest("form"):!0,E={checked:p,disabled:i,setChecked:g,control:v,setControl:b,name:a,form:s,value:d,hasConsumerStoppedPropagationRef:w,required:c,defaultChecked:cc(o)?!1:o,isFormControl:C,bubbleInput:_,setBubbleInput:x};return h.jsx(f_e,{scope:t,...E,children:p_e(f)?f(E):r})}var Sq="CheckboxTrigger",_q=y.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:n,...r},o)=>{const{control:i,value:s,disabled:a,checked:l,required:c,setControl:d,setChecked:f,hasConsumerStoppedPropagationRef:p,isFormControl:g,bubbleInput:v}=XP(Sq,e),b=Pt(o,d),_=y.useRef(l);return y.useEffect(()=>{const x=i==null?void 0:i.form;if(x){const w=()=>f(_.current);return x.addEventListener("reset",w),()=>x.removeEventListener("reset",w)}},i,f),h.jsx(Ze.button,{type:"button",role:"checkbox","aria-checked":cc(l)?"mixed":l,"aria-required":c,"data-state":Rq(l),"data-disabled":a?"":void 0,disabled:a,value:s,...r,ref:b,onKeyDown:xt(t,x=>{x.key==="Enter"&&x.preventDefault()}),onClick:xt(n,x=>{f(w=>cc(w)?!0:!w),v&&g&&(p.current=x.isPropagationStopped(),p.current||x.stopPropagation())})})});_q.displayName=Sq;var rh=y.forwardRef((e,t)=>{const{__scopeCheckbox:n,name:r,checked:o,defaultChecked:i,required:s,disabled:a,value:l,onCheckedChange:c,form:d,...f}=e;return h.jsx(h_e,{__scopeCheckbox:n,checked:o,defaultChecked:i,disabled:a,required:s,onCheckedChange:c,name:r,form:d,value:l,internal_do_not_use_render:({isFormControl:p})=>h.jsxs(h.Fragment,{children:h.jsx(_q,{...f,ref:t,__scopeCheckbox:n}),p&&h.jsx(Nq,{__scopeCheckbox:n})})})});rh.displayName=Cb;var Cq="CheckboxIndicator",fg=y.forwardRef((e,t)=>{const{__scopeCheckbox:n,forceMount:r,...o}=e,i=XP(Cq,n);return h.jsx(Nc,{present:r||cc(i.checked)||i.checked===!0,children:h.jsx(Ze.span,{"data-state":Rq(i.checked),"data-disabled":i.disabled?"":void 0,...o,ref:t,style:{pointerEvents:"none",...e.style}})})});fg.displayName=Cq;var Eq="CheckboxBubbleInput",Nq=y.forwardRef(({__scopeCheckbox:e,...t},n)=>{const{control:r,hasConsumerStoppedPropagationRef:o,checked:i,defaultChecked:s,required:a,disabled:l,name:c,value:d,form:f,bubbleInput:p,setBubbleInput:g}=XP(Eq,e),v=Pt(n,g),b=wq(i),_=ub(r);y.useEffect(()=>{const w=p;if(!w)return;const C=window.HTMLInputElement.prototype,R=Object.getOwnPropertyDescriptor(C,"checked").set,P=!o.current;if(b!==i&&R){const N=new Event("click",{bubbles:P});w.indeterminate=cc(i),R.call(w,cc(i)?!1:i),w.dispatchEvent(N)}},p,b,i,o);const x=y.useRef(cc(i)?!1:i);return h.jsx(Ze.input,{type:"checkbox","aria-hidden":!0,defaultChecked:s??x.current,required:a,disabled:l,name:c,value:d,form:f,...t,tabIndex:-1,ref:v,style:{...t.style,..._,position:"absolute",pointerEvents:"none",opacity:0,margin:0,transform:"translateX(-100%)"}})});Nq.displayName=Eq;function p_e(e){return typeof e=="function"}function cc(e){return e==="indeterminate"}function Rq(e){return cc(e)?"indeterminate":e?"checked":"unchecked"}const hg=y.memo(({value:e,className:t="",maxValue:n=100,format:r})=>{const o=r?r(e):e>=n?`+${n}`:String(e);return h.jsx("span",{className:`stat-num ${t}`,children:o})},(e,t)=>e.value===t.value);hg.displayName="StableNumber";const m_e=Pe.memo(({tool:e,levelsByTool:t,defaultAllLevel:n,logCountsByTool:r,currentTool:o,isChecked:i,onToggle:s,onToolSelect:a})=>{const{effectiveLevel:l,bgColor:c,textColor:d,isActive:f,logCount:p,isCritical:g}=y.useMemo(()=>{const v=_b(e,t,n),b=VPv,_=v===At.ERROR||v===At.WARNING;return{effectiveLevel:v,bgColor:b,textColor:YP(b),isActive:e===o,logCount:re||0,isCritical:_}},e,t,n,o,r);return h.jsxs(dg,{className:`flex items-center gap-2 py-1 px-2 ${f?"bg-muted":""}`,onClick:v=>{v.preventDefault(),v.stopPropagation()},children:h.jsx(rh,{checked:i,onCheckedChange:s,className:`h-3 w-3 border rounded flex items-center justify-center ${i?"bg-blue-600 border-blue-600":"bg-gray-700 border-gray-600"}`,onClick:v=>v.stopPropagation(),children:h.jsx(fg,{children:h.jsx(qP,{className:"h-2 w-2 text-white"})})}),h.jsxs("div",{className:"flex items-center justify-between flex-1 min-w-0 cursor-pointer",onClick:()=>a(e),children:h.jsx("span",{className:"font-normal text-xs truncate",children:e.toUpperCase()}),h.jsxs(ht,{variant:"status",className:`text-xs ${d} ml-1 transition-none`,style:{backgroundColor:c},children:l.toUpperCase(),g&&h.jsxs(h.Fragment,{children:"(",h.jsx(hg,{value:p}),")"})})})})}),ZP=y.memo(({icon:e,children:t,className:n=""})=>h.jsxs("div",{className:`flex items-center gap-2 px-3 py-1 rounded-md border border-gray-700 bg-gray-800/80 font-ui ${n}`,children:e&&h.jsx("span",{className:"text-xs",children:e}),t}));ZP.displayName="WidgetStatusBadge";const g_e=Pe.memo(({tool:e,levelsByTool:t,defaultAllLevel:n,logCountsByTool:r})=>{const o=_b(e,t,n),i=VPo,s=YP(i),a=re||0,l=o===At.ERROR||o===At.WARNING;return h.jsxs(dg,{className:"flex items-center gap-2 py-1 px-2 bg-muted",onClick:c=>{c.preventDefault(),c.stopPropagation()},children:h.jsx(rh,{checked:!0,onCheckedChange:()=>{},className:"h-3 w-3 border rounded flex items-center justify-center bg-blue-600 border-blue-600",onClick:c=>c.stopPropagation(),children:h.jsx(fg,{children:h.jsx(qP,{className:"h-2 w-2 text-white"})})}),h.jsxs("div",{className:"flex items-center justify-between flex-1 min-w-0",children:h.jsx("span",{className:"font-normal text-xs truncate",children:e.toUpperCase()}),h.jsxs(ht,{variant:"status",className:`text-xs ${s} ml-1`,style:{backgroundColor:i},title:"Active log(s)",children:o.toUpperCase(),l&&`(${a})`})})})}),v_e=Pe.memo(({currentTool:e,levelsByTool:t,defaultAllLevel:n,visibleLogsCount:r,allLogCountsByTool:o,criticalLogCountsByTool:i,visibleToolsFilter:s,onToolSelect:a,onClearFilter:l,onSelectAllTools:c})=>{const d=Pe.useMemo(()=>{const v=new Set;return Object.keys(o).forEach(b=>{ob>0&&v.add(b)}),Array.from(v).sort()},o),f=s.length>1,p=f,g=Pe.useMemo(()=>{if(s.length>1)return{label:"ALL",effectiveLevel:"",bgColor:"#6b7280",textColor:"text-white",isCritical:!1};const v=_b(e,t,n),b=VPv,_=YP(b),x=v===At.ERROR||v===At.WARNING;return{label:e.toUpperCase(),effectiveLevel:v,bgColor:b,textColor:_,isCritical:x}},e,t,n,s);return h.jsxs(BP,{children:h.jsx(WP,{asChild:!0,children:h.jsx(fs,{variant:"ghost",size:"sm",className:"px-0 py-0",title:"Active logs",children:h.jsx(ZP,{className:"cursor-pointer hover:opacity-80 ",children:h.jsxs("span",{className:"text-sm font-medium text-info",children:g.label,g.effectiveLevel&&` : ${g.effectiveLevel.toUpperCase()}`,g.isCritical&&` (${r})`})})})}),h.jsxs(Sb,{align:"end",className:"w-48 bg-monitor-surface max-h-80 overflow-y-auto border-transparent",children:d.length>0&&h.jsxs(h.Fragment,{children:h.jsxs(dg,{className:"flex items-center gap-2 py-1 px-2",onClick:v=>{v.preventDefault(),v.stopPropagation()},children:h.jsx(rh,{checked:p,onCheckedChange:()=>{f?l==null||l():c==null||c(d)},className:`h-3 w-3 border rounded flex items-center justify-center ${p?"bg-green-600 border-green-600":"bg-gray-700 border-gray-600"}`,onClick:v=>v.stopPropagation(),children:h.jsx(fg,{children:h.jsx(qP,{className:"h-2 w-2 text-white"})})}),h.jsx("span",{className:"text-xs font-medium",children:"ALL"})}),h.jsx(UP,{})}),d.length>0?d.map(v=>{const b=s.length>0?s.includes(v):v===e;return h.jsx(m_e,{tool:v,levelsByTool:t,defaultAllLevel:n,logCountsByTool:i,currentTool:e,isChecked:b,onToggle:()=>{s.length>0&&(l==null||l()),a(v)},onToolSelect:a},v)}):h.jsx(g_e,{tool:e,levelsByTool:t,defaultAllLevel:n,logCountsByTool:i},e)})})}),y_e=e=>{const t=e/1e6,n=Math.floor(t/3600),r=Math.floor(t%3600/60),o=Math.floor(t%60),i=Math.floor(t%1*1e3);return n>0?`${n}h${r.toString().padStart(2,"0")}m`:r>0?`${r}m${o.toString().padStart(2,"0")}s`:`${o}.${i.toString().padStart(3,"0")}s`},x_e=e=>new Date(e).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})+"."+String(e%1e3).padStart(3,"0"),b_e=Pe.memo(({log:e,logId:t,isHighlighted:n,onToggleHighlight:r,timestampMode:o})=>{const i=y.useMemo(()=>{const g=e.level,v=Jc.iconComponentsg||Jc.iconComponents0,b=Jc.iconClassesg||Jc.iconClasses0;return{time:o==="absolute"&&e.timestampMs?x_e(e.timestampMs):y_e(e.timestamp),IconComponent:v,iconClass:b,style:Jc.stylesg||Jc.styles0,name:Jc.namesg||"UNKNOWN"}},e.timestamp,e.timestampMs,e.level,o),s,a=y.useState(!1),l=y.useCallback(()=>a(!0),),c=y.useCallback(()=>a(!1),),d=y.useCallback(()=>{r(n?null:t)},r,n,t),f=y.useMemo(()=>"flex items-start gap-2 mb-1 p-1 rounded hover:bg-gray-800/30 cursor-pointer"+(n?" border-l-4 border-yellow-500 bg-yellow-900/70":""),n),p=y.useMemo(()=>"shrink-0 p-1 rounded mt-1"+(n?" text-yellow-500":" text-gray-400 hover:text-yellow-500"),n);return h.jsxs("div",{className:f,style:{minHeight:"32px"},onMouseEnter:l,onMouseLeave:c,onClick:d,children:h.jsx(i.IconComponent,{className:i.iconClass}),h.jsxs("div",{className:"flex-1 stat overflow-hidden font-mono",children:h.jsxs("div",{className:"flex items-center gap-2 text-xs",children:h.jsx("span",{className:"text-gray-400 shrink-0",children:i.time}),h.jsxs("span",{className:`shrink-0 ${i.style}`,children:"",i.name,""}),h.jsxs("span",{className:"text-gray-300 shrink-0",children:"",e.tool,""}),(e.level===1||e.level===2)&&e.caller!==null&&e.caller!==void 0&&h.jsxs("span",{className:"text-amber-400 shrink-0",children:"Filter: ",e.caller,""})}),h.jsx("div",{className:`text-sm ${i.style} break-words`,children:e.message})}),(s||n)&&h.jsx("div",{className:p,children:n?h.jsx(wve,{className:"w-4 h-4"}):h.jsx(Sve,{className:"w-4 h-4"})})})},(e,t)=>e.log.timestamp===t.log.timestamp&&e.log.message===t.log.message&&e.log.level===t.log.level&&e.log.caller===t.log.caller&&e.isHighlighted===t.isHighlighted&&e.logId===t.logId&&e.timestampMode===t.timestampMode),XD=new WeakMap,TS=e=>{let t=XD.get(e);return t||(t=`${e.timestamp}_${Tve(6)}`,XD.set(e,t)),t},w_e=e=>{var o;const t=e!=null&&e.levels&&e.levels.length===1?e.levels0.toLowerCase():null,n={error:"text-danger",warning:"text-warning",info:"text-emerald-300/90",debug:"text-debug",quiet:"text-muted"},r=((o=e==null?void 0:e.filterKeys)==null?void 0:o.length)===1&&e.filterKeys0.startsWith("t:")?parseInt(e.filterKeys0.substring(2),10)>>>0:null;return h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1 rounded-md border border-gray-700 bg-monitor-panel font-ui",children:h.jsx("span",{className:"text-xs",children:h.jsx(xve,{className:"w-4 h-4"})}),h.jsx("span",{className:`text-xs font-cond ${t?nt:"text-info"}`,children:t?`all@${t}`:"filtered"}),r!==null&&h.jsxs("span",{className:"text-xs font-cond text-blue-400",children:"T",r})})},S_e=Pe.memo(({count:e})=>h.jsxs("div",{className:"text-center text-xs text-gray-500 py-1",children:h.jsx(hg,{value:e})," logs"})),__e=Pe.memo(({id:e})=>{const t=y.useRef(null),n=Hn(),{currentTool:r,levelsByTool:o,defaultAllLevel:i,visibleToolsFilter:s,visibleLogs:a,setTool:l,setToolLevel:c,setDefaultAllLevel:d,toggleToolFilter:f,clearFilter:p,selectAllTools:g}=dxe(),v=Xe($=>$.logs.highlightedLogId),b,_=y.useState(!0),x,w=y.useState(!1),C=Xe(Hye),E=Xe(Bye),R=Xe(exe),P=Xe(D9),N=Xe($ye),k=Xe(F9),I=P&&(P.levels&&P.levels.length>0||P.filterKeys&&P.filterKeys.length>0);lxe({enabled:!0}),mxe();const O=y.useCallback($=>{n(Iye($))},n),j=y.useCallback(()=>{if(v&&t.current&&a.length>0){const $=a.findIndex(W=>TS(W)===v);$!==-1&&t.current.scrollToIndex({index:$,behavior:"smooth",align:"center"})}},v,a),H=y.useCallback(()=>{var $,W;b?($=t.current)==null||$.scrollToIndex({index:0,behavior:"smooth"}):(W=t.current)==null||W.scrollToIndex({index:a.length-1,behavior:"smooth"})},b,a.length),q=y.useCallback(($,W)=>{const G=TS(W);return h.jsx(b_e,{log:W,logId:G,isHighlighted:G===v,onToggleHighlight:O,timestampMode:k})},v,O,k),M=y.useMemo(()=>({Footer:()=>h.jsx(S_e,{count:a.length})}),a.length),B=y.useMemo(()=>N==="globalFilter"&&I?w_e(P):h.jsx(v_e,{currentTool:r,levelsByTool:o,defaultAllLevel:i,visibleLogsCount:R,allLogCountsByTool:C,criticalLogCountsByTool:E,visibleToolsFilter:s,onToolSelect:l,onToggleToolFilter:f,onClearFilter:p,onSelectAllTools:g}),N,P,I,r,o,i,R,C,E,s,l,f,p,g);return h.jsx(Js,{id:e,statusBadge:B,customActions:h.jsxs("div",{className:"flex items-center gap-2",children:I&&h.jsx($0,{content:"Clear UI filter",side:"bottom",children:h.jsx("button",{onClick:()=>n(Mye()),className:"px-2 py-1 text-xs rounded bg-red-900/50 border border-red-800/50 text-red-200 hover:bg-red-900/50",children:h.jsx(bve,{className:"w-4 h-4"})})}),h.jsx($0,{content:k==="relative"?"Switch to absolute time":"Switch to relative time",side:"bottom",children:h.jsx("button",{onClick:()=>n(jye()),className:"px-2 py-1 text-xs rounded bg-gray-700/50 border border-gray-600/50 text-gray-200 hover:bg-gray-700/80",children:h.jsx(_ve,{className:"w-4 h-4"})})}),h.jsx($0,{content:"Configure log levels for each tool",side:"bottom",children:h.jsx(u_e,{levelsByTool:o,defaultAllLevel:i,currentTool:r,onToolLevelChange:c,onDefaultAllLevelChange:d,onToolNavigate:l})})}),children:h.jsx("div",{className:"flex flex-col h-full bg-stat stat",children:h.jsxs("div",{className:"flex-1 relative",children:h.jsxs("div",{className:"absolute bottom-4 right-4 z-20 flex flex-col items-center gap-3 pointer-events-auto",children:h.jsx("button",{onClick:j,className:`px-2 py-1 text-xs rounded border bg-gray-800 border-yellow-600 text-white hover:opacity-80 ${v?"visible":"invisible"}`,title:"Scroll to highlighted log",children:"📌"}),h.jsx(fs,{onClick:H,className:"p-2 rounded-sm shadow-md border-2 hover:scale-105",title:b?"Scroll to top":"Scroll to bottom",children:h.jsx(yve,{className:`w-5 h-5 transition-transform duration-200 ${b?"rotate-180":""}`})})}),h.jsx(Kge,{ref:t,data:a,followOutput:b?"auto":!1,computeItemKey:($,W)=>TS(W),style:{height:"100%",fontFamily:"'Roboto Mono', 'Courier New', monospace",willChange:"transform",contain:"strict"},className:"rounded px-2 py-1 text-sm bg-stat stat",itemContent:q,atBottomStateChange:_,atTopStateChange:w,overscan:20,increaseViewportBy:200,components:M})})})})}),Jn=y.forwardRef(({className:e,...t},n)=>h.jsx("div",{ref:n,className:nt("rounded-lg border bg-card text-card-foreground shadow-sm",e),...t}));Jn.displayName="Card";const Dr=y.forwardRef(({className:e,...t},n)=>h.jsx("div",{ref:n,className:nt("flex flex-col space-y-1.5 p-6",e),...t}));Dr.displayName="CardHeader";const Rr=y.forwardRef(({className:e,...t},n)=>h.jsx("h3",{ref:n,className:nt("text-2xl font-semibold leading-none tracking-tight",e),...t}));Rr.displayName="CardTitle";const C_e=y.forwardRef(({className:e,...t},n)=>h.jsx("p",{ref:n,className:nt("text-sm text-muted-foreground",e),...t}));C_e.displayName="CardDescription";const er=y.forwardRef(({className:e,...t},n)=>h.jsx("div",{ref:n,className:nt("p-6 pt-0",e),...t}));er.displayName="CardContent";const E_e=y.forwardRef(({className:e,...t},n)=>h.jsx("div",{ref:n,className:nt("flex items-center p-6 pt-0",e),...t}));E_e.displayName="CardFooter";const N_e=!0,wr="u-",R_e="uplot",T_e=wr+"hz",k_e=wr+"vt",P_e=wr+"title",I_e=wr+"wrap",A_e=wr+"under",M_e=wr+"over",j_e=wr+"axis",lu=wr+"off",L_e=wr+"select",O_e=wr+"cursor-x",D_e=wr+"cursor-y",F_e=wr+"cursor-pt",$_e=wr+"legend",z_e=wr+"live",H_e=wr+"inline",B_e=wr+"series",W_e=wr+"marker",ZD=wr+"label",U_e=wr+"value",_p="width",Cp="height",cp="top",QD="bottom",Pd="left",kS="right",QP="#000",JD=QP+"0",PS="mousemove",e3="mousedown",IS="mouseup",t3="mouseenter",n3="mouseleave",r3="dblclick",q_e="resize",G_e="scroll",o3="change",Ky="dppxchange",JP="--",oh=typeof window<"u",XR=oh?document:null,pf=oh?window:null,V_e=oh?navigator:null;let Zt,i0;function ZR(){let e=devicePixelRatio;Zt!=e&&(Zt=e,i0&&JR(o3,i0,ZR),i0=matchMedia(`(min-resolution: ${Zt-.001}dppx) and (max-resolution: ${Zt+.001}dppx)`),Nu(o3,i0,ZR),pf.dispatchEvent(new CustomEvent(Ky)))}function Uo(e,t){if(t!=null){let n=e.classList;!n.contains(t)&&n.add(t)}}function QR(e,t){let n=e.classList;n.contains(t)&&n.remove(t)}function Sn(e,t,n){e.stylet=n+"px"}function Xi(e,t,n,r){let o=XR.createElement(e);return t!=null&&Uo(o,t),n!=null&&n.insertBefore(o,r),o}function Ci(e,t){return Xi("div",e,t)}const i3=new WeakMap;function Ws(e,t,n,r,o){let i="translate("+t+"px,"+n+"px)",s=i3.get(e);i!=s&&(e.style.transform=i,i3.set(e,i),t<0||n<0||t>r||n>o?Uo(e,lu):QR(e,lu))}const s3=new WeakMap;function a3(e,t,n){let r=t+n,o=s3.get(e);r!=o&&(s3.set(e,r),e.style.background=t,e.style.borderColor=n)}const l3=new WeakMap;function c3(e,t,n,r){let o=t+""+n,i=l3.get(e);o!=i&&(l3.set(e,o),e.style.height=n+"px",e.style.width=t+"px",e.style.marginLeft=r?-t/2+"px":0,e.style.marginTop=r?-n/2+"px":0)}const eI={passive:!0},Y_e={...eI,capture:!0};function Nu(e,t,n,r){t.addEventListener(e,n,r?Y_e:eI)}function JR(e,t,n,r){t.removeEventListener(e,n,eI)}oh&&ZR();function ts(e,t,n,r){let o;n=n||0,r=r||t.length-1;let i=r<=2147483647;for(;r-n>1;)o=i?n+r>>1:ri((n+r)/2),to<e?n=o:r=o;return e-tn<=tr-e?n:r}function Tq(e){return(n,r,o)=>{let i=-1,s=-1;for(let a=r;a<=o;a++)if(e(na)){i=a;break}for(let a=o;a>=r;a--)if(e(na)){s=a;break}returni,s}}const kq=e=>e!=null,Pq=e=>e!=null&&e>0,Eb=Tq(kq),K_e=Tq(Pq);function X_e(e,t,n,r=0,o=!1){let i=o?K_e:Eb,s=o?Pq:kq;t,n=i(e,t,n);let a=et,l=et;if(t>-1)if(r==1)a=et,l=en;else if(r==-1)a=en,l=et;else for(let c=t;c<=n;c++){let d=ec;s(d)&&(d<a?a=d:d>l&&(l=d))}returna??mn,l??-mn}function Nb(e,t,n,r){let o=f3(e),i=f3(t);e==t&&(o==-1?(e*=n,t/=n):(e/=n,t*=n));let s=n==10?Va:Iq,a=o==1?ri:Ni,l=i==1?Ni:ri,c=a(s(yr(e))),d=l(s(yr(t))),f=Df(n,c),p=Df(n,d);return n==10&&(c<0&&(f=gn(f,-c)),d<0&&(p=gn(p,-d))),r||n==2?(e=f*o,t=p*i):(e=Lq(e,f),t=Rb(t,p)),e,t}function tI(e,t,n,r){let o=Nb(e,t,n,r);return e==0&&(o0=0),t==0&&(o1=0),o}const nI=.1,u3={mode:3,pad:nI},Yp={pad:0,soft:null,mode:0},Z_e={min:Yp,max:Yp};function Xy(e,t,n,r){return Tb(n)?d3(e,t,n):(Yp.pad=n,Yp.soft=r?0:null,Yp.mode=r?3:0,d3(e,t,Z_e))}function Yt(e,t){return e??t}function Q_e(e,t,n){for(t=Yt(t,0),n=Yt(n,e.length-1);t<=n;){if(et!=null)return!0;t++}return!1}function d3(e,t,n){let r=n.min,o=n.max,i=Yt(r.pad,0),s=Yt(o.pad,0),a=Yt(r.hard,-mn),l=Yt(o.hard,mn),c=Yt(r.soft,mn),d=Yt(o.soft,-mn),f=Yt(r.mode,0),p=Yt(o.mode,0),g=t-e,v=Va(g),b=fo(yr(e),yr(t)),_=Va(b),x=yr(_-v);(g<1e-24||x>10)&&(g=0,(e==0||t==0)&&(g=1e-24,f==2&&c!=mn&&(i=0),p==2&&d!=-mn&&(s=0)));let w=g||b||1e3,C=Va(w),E=Df(10,ri(C)),R=w*(g==0?e==0?.1:1:i),P=gn(Lq(e-R,E/10),24),N=e>=c&&(f==1||f==3&&P<=c||f==2&&P>=c)?c:mn,k=fo(a,P<N&&e>=N?N:as(N,P)),I=w*(g==0?t==0?.1:1:s),O=gn(Rb(t+I,E/10),24),j=t<=d&&(p==1||p==3&&O>=d||p==2&&O<=d)?d:-mn,H=as(l,O>j&&t<=j?j:fo(j,O));return k==H&&k==0&&(H=100),k,H}const J_e=new Intl.NumberFormat(oh?V_e.language:"en-US"),rI=e=>J_e.format(e),di=Math,W0=di.PI,yr=di.abs,ri=di.floor,mr=di.round,Ni=di.ceil,as=di.min,fo=di.max,Df=di.pow,f3=di.sign,Va=di.log10,Iq=di.log2,eCe=(e,t=1)=>di.sinh(e)*t,AS=(e,t=1)=>di.asinh(e/t),mn=1/0;function h3(e){return(Va((e^e>>31)-(e>>31))|0)+1}function eT(e,t,n){return as(fo(e,t),n)}function Aq(e){return typeof e=="function"}function zt(e){return Aq(e)?e:()=>e}const tCe=()=>{},Mq=e=>e,jq=(e,t)=>t,nCe=e=>null,p3=e=>!0,m3=(e,t)=>e==t,rCe=/\.\d*?(?=9{6,}|0{6,})/gm,zu=e=>{if(Dq(e)||vc.has(e))return e;const t=`${e}`,n=t.match(rCe);if(n==null)return e;let r=n0.length-1;if(t.indexOf("e-")!=-1){leto,i=t.split("e");return+`${zu(o)}e${i}`}return gn(e,r)};function su(e,t){return zu(gn(zu(e/t))*t)}function Rb(e,t){return zu(Ni(zu(e/t))*t)}function Lq(e,t){return zu(ri(zu(e/t))*t)}function gn(e,t=0){if(Dq(e))return e;let n=10**t,r=e*n*(1+Number.EPSILON);return mr(r)/n}const vc=new Map;function Oq(e){return((""+e).split(".")1||"").length}function $m(e,t,n,r){let o=,i=r.map(Oq);for(let s=t;s<n;s++){let a=yr(s),l=gn(Df(e,s),a);for(let c=0;c<r.length;c++){let d=e==10?+`${rc}e${s}`:rc*l,f=(s>=0?0:a)+(s>=ic?0:ic),p=e==10?d:gn(d,f);o.push(p),vc.set(p,f)}}return o}const Kp={},oI=,Ff=null,null,Vl=Array.isArray,Dq=Number.isInteger,oCe=e=>e===void 0;function g3(e){return typeof e=="string"}function Tb(e){let t=!1;if(e!=null){let n=e.constructor;t=n==null||n==Object}return t}function iCe(e){return e!=null&&typeof e=="object"}const sCe=Object.getPrototypeOf(Uint8Array),Fq="__proto__";function $f(e,t=Tb){let n;if(Vl(e)){let r=e.find(o=>o!=null);if(Vl(r)||t(r)){n=Array(e.length);for(let o=0;o<e.length;o++)no=$f(eo,t)}else n=e.slice()}else if(e instanceof sCe)n=e.slice();else if(t(e)){n={};for(let r in e)r!=Fq&&(nr=$f(er,t))}else n=e;return n}function ar(e){let t=arguments;for(let n=1;n<t.length;n++){let r=tn;for(let o in r)o!=Fq&&(Tb(eo)?ar(eo,$f(ro)):eo=$f(ro))}return e}const aCe=0,lCe=1,cCe=2;function uCe(e,t,n){for(let r=0,o,i=-1;r<t.length;r++){let s=tr;if(s>i){for(o=s-1;o>=0&&eo==null;)eo--=null;for(o=s+1;o<n&&eo==null;)ei=o++=null}}}function dCe(e,t){if(pCe(e)){let s=e0.slice();for(let a=1;a<e.length;a++)s.push(...ea.slice(1));return mCe(s0)||(s=hCe(s)),s}let n=new Set;for(let s=0;s<e.length;s++){let l=es0,c=l.length;for(let d=0;d<c;d++)n.add(ld)}let r=Array.from(n).sort((s,a)=>s-a),o=r0.length,i=new Map;for(let s=0;s<o;s++)i.set(r0s,s);for(let s=0;s<e.length;s++){let a=es,l=a0;for(let c=1;c<a.length;c++){let d=ac,f=Array(o).fill(void 0),p=t?tsc:lCe,g=;for(let v=0;v<d.length;v++){let b=dv,_=i.get(lv);b===null?p!=aCe&&(f_=b,p==cCe&&g.push(_)):f_=b}uCe(f,g,o),r.push(f)}}return r}const fCe=typeof queueMicrotask>"u"?e=>Promise.resolve().then(e):queueMicrotask;function hCe(e){let t=e0,n=t.length,r=Array(n);for(let i=0;i<r.length;i++)ri=i;r.sort((i,s)=>ti-ts);let o=;for(let i=0;i<e.length;i++){let s=ei,a=Array(n);for(let l=0;l<n;l++)al=srl;o.push(a)}return o}function pCe(e){let t=e00,n=t.length;for(let r=1;r<e.length;r++){let o=er0;if(o.length!=n)return!1;if(o!=t){for(let i=0;i<n;i++)if(oi!=ti)return!1}}return!0}function mCe(e,t=100){const n=e.length;if(n<=1)return!0;let r=0,o=n-1;for(;r<=o&&er==null;)r++;for(;o>=r&&eo==null;)o--;if(o<=r)return!0;const i=fo(1,ri((o-r+1)/t));for(let s=er,a=r+i;a<=o;a+=i){const l=ea;if(l!=null){if(l<=s)return!1;s=l}}return!0}const $q="January","February","March","April","May","June","July","August","September","October","November","December",zq="Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday";function Hq(e){return e.slice(0,3)}const gCe=zq.map(Hq),vCe=$q.map(Hq),yCe={MMMM:$q,MMM:vCe,WWWW:zq,WWW:gCe};function up(e){return(e<10?"0":"")+e}function xCe(e){return(e<10?"00":e<100?"0":"")+e}const bCe={YYYY:e=>e.getFullYear(),YY:e=>(e.getFullYear()+"").slice(2),MMMM:(e,t)=>t.MMMMe.getMonth(),MMM:(e,t)=>t.MMMe.getMonth(),MM:e=>up(e.getMonth()+1),M:e=>e.getMonth()+1,DD:e=>up(e.getDate()),D:e=>e.getDate(),WWWW:(e,t)=>t.WWWWe.getDay(),WWW:(e,t)=>t.WWWe.getDay(),HH:e=>up(e.getHours()),H:e=>e.getHours(),h:e=>{let t=e.getHours();return t==0?12:t>12?t-12:t},AA:e=>e.getHours()>=12?"PM":"AM",aa:e=>e.getHours()>=12?"pm":"am",a:e=>e.getHours()>=12?"p":"a",mm:e=>up(e.getMinutes()),m:e=>e.getMinutes(),ss:e=>up(e.getSeconds()),s:e=>e.getSeconds(),fff:e=>xCe(e.getMilliseconds())};function iI(e,t){t=t||yCe;let n=,r=/\{(a-z+)\}|^{+/gi,o;for(;o=r.exec(e);)n.push(o00=="{"?bCeo1:o0);return i=>{let s="";for(let a=0;a<n.length;a++)s+=typeof na=="string"?na:na(i,t);return s}}const wCe=new Intl.DateTimeFormat().resolvedOptions().timeZone;function SCe(e,t){let n;return t=="UTC"||t=="Etc/UTC"?n=new Date(+e+e.getTimezoneOffset()*6e4):t==wCe?n=e:(n=new Date(e.toLocaleString("en-US",{timeZone:t})),n.setMilliseconds(e.getMilliseconds())),n}const Bq=e=>e%1==0,Zy=1,2,2.5,5,_Ce=$m(10,-32,0,Zy),Wq=$m(10,0,32,Zy),CCe=Wq.filter(Bq),au=_Ce.concat(Wq),sI=` +`,Uq="{YYYY}",v3=sI+Uq,qq="{M}/{D}",Ep=sI+qq,s0=Ep+"/{YY}",Gq="{aa}",ECe="{h}:{mm}",Ld=ECe+Gq,y3=sI+Ld,x3=":{ss}",en=null;function Vq(e){let t=e*1e3,n=t*60,r=n*60,o=r*24,i=o*30,s=o*365,l=(e==1?$m(10,0,3,Zy).filter(Bq):$m(10,-3,0,Zy)).concat(t,t*5,t*10,t*15,t*30,n,n*5,n*10,n*15,n*30,r,r*2,r*3,r*4,r*6,r*8,r*12,o,o*2,o*3,o*4,o*5,o*6,o*7,o*8,o*9,o*10,o*15,i,i*2,i*3,i*4,i*6,s,s*2,s*5,s*10,s*25,s*50,s*100);const c=s,Uq,en,en,en,en,en,en,1,o*28,"{MMM}",v3,en,en,en,en,en,1,o,qq,v3,en,en,en,en,en,1,r,"{h}"+Gq,s0,en,Ep,en,en,en,1,n,Ld,s0,en,Ep,en,en,en,1,t,x3,s0+" "+Ld,en,Ep+" "+Ld,en,y3,en,1,e,x3+".{fff}",s0+" "+Ld,en,Ep+" "+Ld,en,y3,en,1;function d(f){return(p,g,v,b,_,x)=>{let w=,C=_>=s,E=_>=i&&_<s,R=f(v),P=gn(R*e,3),N=MS(R.getFullYear(),C?0:R.getMonth(),E||C?1:R.getDate()),k=gn(N*e,3);if(E||C){let I=E?_/i:0,O=C?_/s:0,j=P==k?P:gn(MS(N.getFullYear()+O,N.getMonth()+I,1)*e,3),H=new Date(mr(j/e)),q=H.getFullYear(),M=H.getMonth();for(let B=0;j<=b;B++){let $=MS(q+O*B,M+I*B,1),W=$-f(gn($*e,3));j=gn((+$+W)*e,3),j<=b&&w.push(j)}}else{let I=_>=o?o:_,O=ri(v)-ri(P),j=k+O+Rb(P-k,I);w.push(j);let H=f(j),q=H.getHours()+H.getMinutes()/n+H.getSeconds()/r,M=_/r,B=p.axesg._space,$=x/B;for(;j=gn(j+_,e==1?0:3),!(j>b);)if(M>1){let W=ri(gn(q+M,6))%24,K=f(j).getHours()-W;K>1&&(K=-1),j-=K*r,q=(q+M)%24;let Q=ww.length-1;gn((j-Q)/_,3)*$>=.7&&w.push(j)}else w.push(j)}return w}}returnl,c,d}constNCe,RCe,TCe=Vq(1),kCe,PCe,ICe=Vq(.001);$m(2,-53,53,1);function b3(e,t){return e.map(n=>n.map((r,o)=>o==0||o==8||r==null?r:t(o==1||n8==0?r:n1+r)))}function w3(e,t){return(n,r,o,i,s)=>{let a=t.find(v=>s>=v0)||tt.length-1,l,c,d,f,p,g;return r.map(v=>{let b=e(v),_=b.getFullYear(),x=b.getMonth(),w=b.getDate(),C=b.getHours(),E=b.getMinutes(),R=b.getSeconds(),P=_!=l&&a2||x!=c&&a3||w!=d&&a4||C!=f&&a5||E!=p&&a6||R!=g&&a7||a1;return l=_,c=x,d=w,f=C,p=E,g=R,P(b)})}}function ACe(e,t){let n=iI(t);return(r,o,i,s,a)=>o.map(l=>n(e(l)))}function MS(e,t,n){return new Date(e,t,n)}function S3(e,t){return t(e)}const MCe="{YYYY}-{MM}-{DD} {h}:{mm}{aa}";function _3(e,t){return(n,r,o,i)=>i==null?JP:t(e(r))}function jCe(e,t){let n=e.seriest;return n.width?n.stroke(e,t):n.points.width?n.points.stroke(e,t):null}function LCe(e,t){return e.seriest.fill(e,t)}const OCe={show:!0,live:!0,isolate:!1,mount:tCe,markers:{show:!0,width:2,stroke:jCe,fill:LCe,dash:"solid"},idx:null,idxs:null,values:};function DCe(e,t){let n=e.cursor.points,r=Ci(),o=n.size(e,t);Sn(r,_p,o),Sn(r,Cp,o);let i=o/-2;Sn(r,"marginLeft",i),Sn(r,"marginTop",i);let s=n.width(e,t,o);return s&&Sn(r,"borderWidth",s),r}function FCe(e,t){let n=e.seriest.points;return n._fill||n._stroke}function $Ce(e,t){let n=e.seriest.points;return n._stroke||n._fill}function zCe(e,t){return e.seriest.points.size}const jS=0,0;function HCe(e,t,n){return jS0=t,jS1=n,jS}function a0(e,t,n,r=!0){return o=>{o.button==0&&(!r||o.target==t)&&n(o)}}function LS(e,t,n,r=!0){return o=>{(!r||o.target==t)&&n(o)}}const BCe={show:!0,x:!0,y:!0,lock:!1,move:HCe,points:{one:!1,show:DCe,size:zCe,width:0,stroke:$Ce,fill:FCe},bind:{mousedown:a0,mouseup:a0,click:a0,dblclick:a0,mousemove:LS,mouseleave:LS,mouseenter:LS},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,click:(e,t)=>{t.stopPropagation(),t.stopImmediatePropagation()},_x:!1,_y:!1},focus:{dist:(e,t,n,r,o)=>r-o,prox:-1,bias:0},hover:{skip:void 0,prox:null,bias:0},left:-10,top:-10,idx:null,dataIdx:null,idxs:null,event:null},Yq={show:!0,stroke:"rgba(0,0,0,0.07)",width:2},aI=ar({},Yq,{filter:jq}),Kq=ar({},aI,{size:10}),Xq=ar({},Yq,{show:!1}),lI='12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',Zq="bold "+lI,Qq=1.5,C3={show:!0,scale:"x",stroke:QP,space:50,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:Zq,side:2,grid:aI,ticks:Kq,border:Xq,font:lI,lineGap:Qq,rotate:0},WCe="Value",UCe="Time",E3={show:!0,scale:"x",auto:!1,sorted:1,min:mn,max:-mn,idxs:};function qCe(e,t,n,r,o){return t.map(i=>i==null?"":rI(i))}function GCe(e,t,n,r,o,i,s){let a=,l=vc.get(o)||0;n=s?n:gn(Rb(n,o),l);for(let c=n;c<=r;c=gn(c+o,l))a.push(Object.is(c,-0)?0:c);return a}function tT(e,t,n,r,o,i,s){const a=,l=e.scalese.axest.scale.log,c=l==10?Va:Iq,d=ri(c(n));o=Df(l,d),l==10&&(o=auts(o,au));let f=n,p=o*l;l==10&&(p=auts(p,au));do a.push(f),f=f+o,l==10&&!vc.has(f)&&(f=gn(f,vc.get(o))),f>=p&&(o=f,p=o*l,l==10&&(p=auts(p,au)));while(f<=r);return a}function VCe(e,t,n,r,o,i,s){let l=e.scalese.axest.scale.asinh,c=r>l?tT(e,t,fo(l,n),r,o):l,d=r>=0&&n<=0?0:;return(n<-l?tT(e,t,fo(l,-r),-n,o):l).reverse().map(p=>-p).concat(d,c)}const Jq=/./,YCe=/12357/,KCe=/125/,N3=/1/,nT=(e,t,n,r)=>e.map((o,i)=>t==4&&o==0||i%r==0&&n.test(o.toExponential()o<0?1:0)?o:null);function XCe(e,t,n,r,o){let i=e.axesn,s=i.scale,a=e.scaless,l=e.valToPos,c=i._space,d=l(10,s),f=l(9,s)-d>=c?Jq:l(7,s)-d>=c?YCe:l(5,s)-d>=c?KCe:N3;if(f==N3){let p=yr(l(1,s)-d);if(p<c)return nT(t.slice().reverse(),a.distr,f,Ni(c/p)).reverse()}return nT(t,a.distr,f,1)}function ZCe(e,t,n,r,o){let i=e.axesn,s=i.scale,a=i._space,l=e.valToPos,c=yr(l(1,s)-l(2,s));return c<a?nT(t.slice().reverse(),3,Jq,Ni(a/c)).reverse():t}function QCe(e,t,n,r){return r==null?JP:t==null?"":rI(t)}const R3={show:!0,scale:"y",stroke:QP,space:30,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:Zq,side:3,grid:aI,ticks:Kq,border:Xq,font:lI,lineGap:Qq,rotate:0};function JCe(e,t){let n=3+(e||1)*2;return gn(n*t,3)}function eEe(e,t){let{scale:n,idxs:r}=e.series0,o=e._data0,i=e.valToPos(or0,n,!0),s=e.valToPos(or1,n,!0),a=yr(s-i),l=e.seriest,c=a/(l.points.space*Zt);return r1-r0<=c}const T3={scale:null,auto:!0,sorted:0,min:mn,max:-mn},eG=(e,t,n,r,o)=>o,k3={show:!0,auto:!0,sorted:0,gaps:eG,alpha:1,facets:ar({},T3,{scale:"x"}),ar({},T3,{scale:"y"})},P3={scale:"y",auto:!0,sorted:0,show:!0,spanGaps:!1,gaps:eG,alpha:1,points:{show:eEe,filter:null},values:null,min:mn,max:-mn,idxs:,path:null,clip:null};function tEe(e,t,n,r,o){return n/10}const tG={time:N_e,auto:!0,distr:1,log:10,asinh:1,min:null,max:null,dir:1,ori:0},nEe=ar({},tG,{time:!1,ori:1}),I3={};function nG(e,t){let n=I3e;return n||(n={key:e,plots:,sub(r){n.plots.push(r)},unsub(r){n.plots=n.plots.filter(o=>o!=r)},pub(r,o,i,s,a,l,c){for(let d=0;d<n.plots.length;d++)n.plotsd!=o&&n.plotsd.pub(r,o,i,s,a,l,c)}},e!=null&&(I3e=n)),n}const zf=1,rT=2;function Vu(e,t,n){const r=e.mode,o=e.seriest,i=r==2?e._datat:e._data,s=e.scales,a=e.bbox;let l=i0,c=r==2?i1:it,d=r==2?so.facets0.scale:se.series0.scale,f=r==2?so.facets1.scale:so.scale,p=a.left,g=a.top,v=a.width,b=a.height,_=e.valToPosH,x=e.valToPosV;return d.ori==0?n(o,l,c,d,f,_,x,p,g,v,b,Pb,ih,Ab,oG,sG):n(o,l,c,d,f,x,_,g,p,b,v,Ib,sh,dI,iG,aG)}function cI(e,t){let n=0,r=0,o=Yt(e.bands,oI);for(let i=0;i<o.length;i++){let s=oi;s.series0==t?n=s.dir:s.series1==t&&(s.dir==1?r|=1:r|=2)}returnn,r==1?-1:r==2?1:r==3?2:0}function rEe(e,t,n,r,o){let i=e.mode,s=e.seriest,a=i==2?s.facets1.scale:s.scale,l=e.scalesa;return o==-1?l.min:o==1?l.max:l.distr==3?l.dir==1?l.min:l.max:0}function Ya(e,t,n,r,o,i){return Vu(e,t,(s,a,l,c,d,f,p,g,v,b,_)=>{let x=s.pxRound;const w=c.dir*(c.ori==0?1:-1),C=c.ori==0?ih:sh;let E,R;w==1?(E=n,R=r):(E=r,R=n);let P=x(f(aE,c,b,g)),N=x(p(lE,d,_,v)),k=x(f(aR,c,b,g)),I=x(p(i==1?d.max:d.min,d,_,v)),O=new Path2D(o);return C(O,k,I),C(O,P,I),C(O,P,N),O})}function kb(e,t,n,r,o,i){let s=null;if(e.length>0){s=new Path2D;const a=t==0?Ab:dI;let l=n;for(let f=0;f<e.length;f++){let p=ef;if(p1>p0){let g=p0-l;g>0&&a(s,l,r,g,r+i),l=p1}}let c=n+o-l,d=10;c>0&&a(s,l,r-d/2,c,r+i+d)}return s}function oEe(e,t,n){let r=ee.length-1;r&&r0==t?r1=n:e.push(t,n)}function uI(e,t,n,r,o,i,s){let a=,l=e.length;for(let c=o==1?n:r;c>=n&&c<=r;c+=o)if(tc===null){let f=c,p=c;if(o==1)for(;++c<=r&&tc===null;)p=c;else for(;--c>=n&&tc===null;)p=c;let g=i(ef),v=p==f?g:i(ep),b=f-o;g=s<=0&&b>=0&&b<l?i(eb):g;let x=p+o;v=s>=0&&x>=0&&x<l?i(ex):v,v>=g&&a.push(g,v)}return a}function A3(e){return e==0?Mq:e==1?mr:t=>su(t,e)}function rG(e){let t=e==0?Pb:Ib,n=e==0?(o,i,s,a,l,c)=>{o.arcTo(i,s,a,l,c)}:(o,i,s,a,l,c)=>{o.arcTo(s,i,l,a,c)},r=e==0?(o,i,s,a,l)=>{o.rect(i,s,a,l)}:(o,i,s,a,l)=>{o.rect(s,i,l,a)};return(o,i,s,a,l,c=0,d=0)=>{c==0&&d==0?r(o,i,s,a,l):(c=as(c,a/2,l/2),d=as(d,a/2,l/2),t(o,i+c,s),n(o,i+a,s,i+a,s+l,c),n(o,i+a,s+l,i,s+l,d),n(o,i,s+l,i,s,d),n(o,i,s,i+a,s,c),o.closePath())}}const Pb=(e,t,n)=>{e.moveTo(t,n)},Ib=(e,t,n)=>{e.moveTo(n,t)},ih=(e,t,n)=>{e.lineTo(t,n)},sh=(e,t,n)=>{e.lineTo(n,t)},Ab=rG(0),dI=rG(1),oG=(e,t,n,r,o,i)=>{e.arc(t,n,r,o,i)},iG=(e,t,n,r,o,i)=>{e.arc(n,t,r,o,i)},sG=(e,t,n,r,o,i,s)=>{e.bezierCurveTo(t,n,r,o,i,s)},aG=(e,t,n,r,o,i,s)=>{e.bezierCurveTo(n,t,o,r,s,i)};function lG(e){return(t,n,r,o,i)=>Vu(t,n,(s,a,l,c,d,f,p,g,v,b,_)=>{let{pxRound:x,points:w}=s,C,E;c.ori==0?(C=Pb,E=oG):(C=Ib,E=iG);const R=gn(w.width*Zt,3);let P=(w.size-w.width)/2*Zt,N=gn(P*2,3),k=new Path2D,I=new Path2D,{left:O,top:j,width:H,height:q}=t.bbox;Ab(I,O-N,j-N,H+N*2,q+N*2);const M=B=>{if(lB!=null){let $=x(f(aB,c,b,g)),W=x(p(lB,d,_,v));C(k,$+P,W),E(k,$,W,P,0,W0*2)}};if(i)i.forEach(M);else for(let B=r;B<=o;B++)M(B);return{stroke:R>0?k:null,fill:k,clip:I,flags:zf|rT}})}function cG(e){return(t,n,r,o,i,s)=>{r!=o&&(i!=r&&s!=r&&e(t,n,r),i!=o&&s!=o&&e(t,n,o),e(t,n,s))}}const iEe=cG(ih),sEe=cG(sh);function uG(e){const t=Yt(e==null?void 0:e.alignGaps,0);return(n,r,o,i)=>Vu(n,r,(s,a,l,c,d,f,p,g,v,b,_)=>{o,i=Eb(l,o,i);let x=s.pxRound,w=q=>x(f(q,c,b,g)),C=q=>x(p(q,d,_,v)),E,R;c.ori==0?(E=ih,R=iEe):(E=sh,R=sEe);const P=c.dir*(c.ori==0?1:-1),N={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:zf},k=N.stroke;let I=!1;if(i-o>=b*4){let q=ae=>n.posToVal(ae,c.key,!0),M=null,B=null,$,W,G,z=w(aP==1?o:i),K=w(ao),Q=w(ai),re=q(P==1?K+1:Q-1);for(let ae=P==1?o:i;ae>=o&&ae<=i;ae+=P){let de=aae,ye=(P==1?de<re:de>re)?z:w(de),fe=lae;ye==z?fe!=null?(W=fe,M==null?(E(k,ye,C(W)),$=M=B=W):W<M?M=W:W>B&&(B=W)):fe===null&&(I=!0):(M!=null&&R(k,z,C(M),C(B),C($),C(W)),fe!=null?(W=fe,E(k,ye,C(W)),M=B=$=W):(M=B=null,fe===null&&(I=!0)),z=ye,re=q(z+P))}M!=null&&M!=B&&G!=z&&R(k,z,C(M),C(B),C($),C(W))}else for(let q=P==1?o:i;q>=o&&q<=i;q+=P){let M=lq;M===null?I=!0:M!=null&&E(k,w(aq),C(M))}letj,H=cI(n,r);if(s.fill!=null||j!=0){let q=N.fill=new Path2D(k),M=s.fillTo(n,r,s.min,s.max,j),B=C(M),$=w(ao),W=w(ai);P==-1&&(W,$=$,W),E(q,W,B),E(q,$,B)}if(!s.spanGaps){let q=;I&&q.push(...uI(a,l,o,i,P,w,t)),N.gaps=q=s.gaps(n,r,o,i,q),N.clip=kb(q,c.ori,g,v,b,_)}return H!=0&&(N.band=H==2?Ya(n,r,o,i,k,-1),Ya(n,r,o,i,k,1):Ya(n,r,o,i,k,H)),N})}function aEe(e){const t=Yt(e.align,1),n=Yt(e.ascDesc,!1),r=Yt(e.alignGaps,0),o=Yt(e.extend,!1);return(i,s,a,l)=>Vu(i,s,(c,d,f,p,g,v,b,_,x,w,C)=>{a,l=Eb(f,a,l);let E=c.pxRound,{left:R,width:P}=i.bbox,N=K=>E(v(K,p,w,_)),k=K=>E(b(K,g,C,x)),I=p.ori==0?ih:sh;const O={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:zf},j=O.stroke,H=p.dir*(p.ori==0?1:-1);let q=k(fH==1?a:l),M=N(dH==1?a:l),B=M,$=M;o&&t==-1&&($=R,I(j,$,q)),I(j,M,q);for(let K=H==1?a:l;K>=a&&K<=l;K+=H){let Q=fK;if(Q==null)continue;let re=N(dK),ae=k(Q);t==1?I(j,re,q):I(j,B,ae),I(j,re,ae),q=ae,B=re}let W=B;o&&t==1&&(W=R+P,I(j,W,q));letG,z=cI(i,s);if(c.fill!=null||G!=0){let K=O.fill=new Path2D(j),Q=c.fillTo(i,s,c.min,c.max,G),re=k(Q);I(K,W,re),I(K,$,re)}if(!c.spanGaps){let K=;K.push(...uI(d,f,a,l,H,N,r));let Q=c.width*Zt/2,re=n||t==1?Q:-Q,ae=n||t==-1?-Q:Q;K.forEach(de=>{de0+=re,de1+=ae}),O.gaps=K=c.gaps(i,s,a,l,K),O.clip=kb(K,p.ori,_,x,w,C)}return z!=0&&(O.band=z==2?Ya(i,s,a,l,j,-1),Ya(i,s,a,l,j,1):Ya(i,s,a,l,j,z)),O})}function M3(e,t,n,r,o,i,s=mn){if(e.length>1){let a=null;for(let l=0,c=1/0;l<e.length;l++)if(tl!==void 0){if(a!=null){let d=yr(el-ea);d<c&&(c=d,s=yr(n(el,r,o,i)-n(ea,r,o,i)))}a=l}}return s}function lEe(e){e=e||Kp;const t=Yt(e.size,.6,mn,1),n=e.align||0,r=e.gap||0;let o=e.radius;o=o==null?0,0:typeof o=="number"?o,0:o;const i=zt(o),s=1-t0,a=Yt(t1,mn),l=Yt(t2,1),c=Yt(e.disp,Kp),d=Yt(e.each,g=>{}),{fill:f,stroke:p}=c;return(g,v,b,_)=>Vu(g,v,(x,w,C,E,R,P,N,k,I,O,j)=>{let H=x.pxRound,q=n,M=r*Zt,B=a*Zt,$=l*Zt,W,G;E.ori==0?W,G=i(g,v):G,W=i(g,v);const z=E.dir*(E.ori==0?1:-1);let K=E.ori==0?Ab:dI,Q=E.ori==0?d:(Te,bt,jt,vn,dr,On,Sr)=>{d(Te,bt,jt,dr,vn,Sr,On)},re=Yt(g.bands,oI).find(Te=>Te.series0==v),ae=re!=null?re.dir:0,de=x.fillTo(g,v,x.min,x.max,ae),Ne=H(N(de,R,j,I)),ye,fe,Z,oe=O,ce=H(x.width*Zt),xe=!1,ge=null,pe=null,he=null,we=null;f!=null&&(ce==0||p!=null)&&(xe=!0,ge=f.values(g,v,b,_),pe=new Map,new Set(ge).forEach(Te=>{Te!=null&&pe.set(Te,new Path2D)}),ce>0&&(he=p.values(g,v,b,_),we=new Map,new Set(he).forEach(Te=>{Te!=null&&we.set(Te,new Path2D)})));let{x0:Ie,size:Ce}=c;if(Ie!=null&&Ce!=null){q=1,w=Ie.values(g,v,b,_),Ie.unit==2&&(w=w.map(jt=>g.posToVal(k+jt*O,E.key,!0)));let Te=Ce.values(g,v,b,_);Ce.unit==2?fe=Te0*O:fe=P(Te0,E,O,k)-P(0,E,O,k),oe=M3(w,C,P,E,O,k,oe),Z=oe-fe+M}else oe=M3(w,C,P,E,O,k,oe),Z=oe*s+M,fe=oe-Z;Z<1&&(Z=0),ce>=fe/2&&(ce=0),Z<5&&(H=Mq);let Me=Z>0,ze=oe-Z-(Me?ce:0);fe=H(eT(ze,$,B)),ye=(q==0?fe/2:q==z?0:fe)-q*z*((q==0?M/2:0)+(Me?ce/2:0));const Ye={stroke:null,fill:null,clip:null,band:null,gaps:null,flags:0},Ht=xe?null:new Path2D;let Ft=null;if(re!=null)Ft=g.datare.series1;else{let{y0:Te,y1:bt}=c;Te!=null&&bt!=null&&(C=bt.values(g,v,b,_),Ft=Te.values(g,v,b,_))}let rt=W*fe,Ue=G*fe;for(let Te=z==1?b:_;Te>=b&&Te<=_;Te+=z){let bt=CTe;if(bt==null)continue;if(Ft!=null){let sn=FtTe??0;if(bt-sn==0)continue;Ne=N(sn,R,j,I)}let jt=E.distr!=2||c!=null?wTe:Te,vn=P(jt,E,O,k),dr=N(Yt(bt,de),R,j,I),On=H(vn-ye),Sr=H(fo(dr,Ne)),kn=H(as(dr,Ne)),fn=Sr-kn;if(bt!=null){let sn=bt<0?Ue:rt,Tr=bt<0?rt:Ue;xe?(ce>0&&heTe!=null&&K(we.get(heTe),On,kn+ri(ce/2),fe,fo(0,fn-ce),sn,Tr),geTe!=null&&K(pe.get(geTe),On,kn+ri(ce/2),fe,fo(0,fn-ce),sn,Tr)):K(Ht,On,kn+ri(ce/2),fe,fo(0,fn-ce),sn,Tr),Q(g,v,Te,On-ce/2,kn,fe+ce,fn)}}return ce>0?Ye.stroke=xe?we:Ht:xe||(Ye._fill=x.width==0?x._fill:x._stroke??x._fill,Ye.width=0),Ye.fill=xe?pe:Ht,Ye})}function cEe(e,t){const n=Yt(t==null?void 0:t.alignGaps,0);return(r,o,i,s)=>Vu(r,o,(a,l,c,d,f,p,g,v,b,_,x)=>{i,s=Eb(c,i,s);let w=a.pxRound,C=W=>w(p(W,d,_,v)),E=W=>w(g(W,f,x,b)),R,P,N;d.ori==0?(R=Pb,N=ih,P=sG):(R=Ib,N=sh,P=aG);const k=d.dir*(d.ori==0?1:-1);let I=C(lk==1?i:s),O=I,j=,H=;for(let W=k==1?i:s;W>=i&&W<=s;W+=k)if(cW!=null){let z=lW,K=C(z);j.push(O=K),H.push(E(cW))}const q={stroke:e(j,H,R,N,P,w),fill:null,clip:null,band:null,gaps:null,flags:zf},M=q.stroke;letB,$=cI(r,o);if(a.fill!=null||B!=0){let W=q.fill=new Path2D(M),G=a.fillTo(r,o,a.min,a.max,B),z=E(G);N(W,O,z),N(W,I,z)}if(!a.spanGaps){let W=;W.push(...uI(l,c,i,s,k,C,n)),q.gaps=W=a.gaps(r,o,i,s,W),q.clip=kb(W,d.ori,v,b,_,x)}return $!=0&&(q.band=$==2?Ya(r,o,i,s,M,-1),Ya(r,o,i,s,M,1):Ya(r,o,i,s,M,$)),q})}function uEe(e){return cEe(dEe,e)}function dEe(e,t,n,r,o,i){const s=e.length;if(s<2)return null;const a=new Path2D;if(n(a,e0,t0),s==2)r(a,e1,t1);else{let l=Array(s),c=Array(s-1),d=Array(s-1),f=Array(s-1);for(let p=0;p<s-1;p++)dp=tp+1-tp,fp=ep+1-ep,cp=dp/fp;l0=c0;for(let p=1;p<s-1;p++)cp===0||cp-1===0||cp-1>0!=cp>0?lp=0:(lp=3*(fp-1+fp)/((2*fp+fp-1)/cp-1+(fp+2*fp-1)/cp),isFinite(lp)||(lp=0));ls-1=cs-2;for(let p=0;p<s-1;p++)o(a,ep+fp/3,tp+lp*fp/3,ep+1-fp/3,tp+1-lp+1*fp/3,ep+1,tp+1)}return a}const oT=new Set;function j3(){for(let e of oT)e.syncRect(!0)}oh&&(Nu(q_e,pf,j3),Nu(G_e,pf,j3,!0),Nu(Ky,pf,()=>{Jr.pxRatio=Zt}));const fEe=uG(),hEe=lG();function L3(e,t,n,r){return(r?e0,e1.concat(e.slice(2)):e0.concat(e.slice(1))).map((i,s)=>iT(i,s,t,n))}function pEe(e,t){return e.map((n,r)=>r==0?{}:ar({},t,n))}function iT(e,t,n,r){return ar({},t==0?n:r,e)}function dG(e,t,n){return t==null?Ff:t,n}const mEe=dG;function gEe(e,t,n){return t==null?Ff:Xy(t,n,nI,!0)}function fG(e,t,n,r){return t==null?Ff:Nb(t,n,e.scalesr.log,!1)}const vEe=fG;function hG(e,t,n,r){return t==null?Ff:tI(t,n,e.scalesr.log,!1)}const yEe=hG;function xEe(e,t,n,r,o){let i=fo(h3(e),h3(t)),s=t-e,a=ts(o/r*s,n);do{let l=na,c=r*l/s;if(c>=o&&i+(l<5?vc.get(l):0)<=17)returnl,c}while(++a<n.length);return0,0}function O3(e){let t,n;return e=e.replace(/(\d+)px/,(r,o)=>(t=mr((n=+o)*Zt))+"px"),e,t,n}function bEe(e){e.show&&e.font,e.labelFont.forEach(t=>{let n=gn(t2*Zt,1);t0=t0.replace(/0-9.+px/,n+"px"),t1=n})}function Jr(e,t,n){const r={mode:Yt(e.mode,1)},o=r.mode;function i(A,D,V,Y){let te=D.valToPct(A);return Y+V*(D.dir==-1?1-te:te)}function s(A,D,V,Y){let te=D.valToPct(A);return Y+V*(D.dir==-1?te:1-te)}function a(A,D,V,Y){return D.ori==0?i(A,D,V,Y):s(A,D,V,Y)}r.valToPosH=i,r.valToPosV=s;let l=!1;r.status=0;const c=r.root=Ci(R_e);if(e.id!=null&&(c.id=e.id),Uo(c,e.class),e.title){let A=Ci(P_e,c);A.textContent=e.title}const d=Xi("canvas"),f=r.ctx=d.getContext("2d"),p=Ci(I_e,c);Nu("click",p,A=>{A.target===v&&(hn!=El||yn!=Nl)&&hr.click(r,A)},!0);const g=r.under=Ci(A_e,p);p.appendChild(d);const v=r.over=Ci(M_e,p);e=$f(e);const b=+Yt(e.pxAlign,1),_=A3(b);(e.plugins||).forEach(A=>{A.opts&&(e=A.opts(r,e)||e)});const x=e.ms||.001,w=r.series=o==1?L3(e.series||,E3,P3,!1):pEe(e.series||null,k3),C=r.axes=L3(e.axes||,C3,R3,!0),E=r.scales={},R=r.bands=e.bands||;R.forEach(A=>{A.fill=zt(A.fill||null),A.dir=Yt(A.dir,-1)});const P=o==2?w1.facets0.scale:w0.scale,N={axes:kg,series:bh},k=(e.drawOrder||"axes","series").map(A=>NA);function I(A){const D=A.distr==3?V=>Va(V>0?V:A.clamp(r,V,A.min,A.max,A.key)):A.distr==4?V=>AS(V,A.asinh):A.distr==100?V=>A.fwd(V):V=>V;return V=>{let Y=D(V),{_min:te,_max:ie}=A,ue=ie-te;return(Y-te)/ue}}function O(A){let D=EA;if(D==null){let V=(e.scales||Kp)A||Kp;if(V.from!=null){O(V.from);let Y=ar({},EV.from,V,{key:A});Y.valToPct=I(Y),EA=Y}else{D=EA=ar({},A==P?tG:nEe,V),D.key=A;let Y=D.time,te=D.range,ie=Vl(te);if((A!=P||o==2&&!Y)&&(ie&&(te0==null||te1==null)&&(te={min:te0==null?u3:{mode:1,hard:te0,soft:te0},max:te1==null?u3:{mode:1,hard:te1,soft:te1}},ie=!1),!ie&&Tb(te))){let ue=te;te=(Se,Ee,je)=>Ee==null?Ff:Xy(Ee,je,ue)}D.range=zt(te||(Y?mEe:A==P?D.distr==3?vEe:D.distr==4?yEe:dG:D.distr==3?fG:D.distr==4?hG:gEe)),D.auto=zt(ie?!1:D.auto),D.clamp=zt(D.clamp||tEe),D._min=D._max=null,D.valToPct=I(D)}}}O("x"),O("y"),o==1&&w.forEach(A=>{O(A.scale)}),C.forEach(A=>{O(A.scale)});for(let A in e.scales)O(A);const j=EP,H=j.distr;let q,M;j.ori==0?(Uo(c,T_e),q=i,M=s):(Uo(c,k_e),q=s,M=i);const B={};for(let A in E){let D=EA;(D.min!=null||D.max!=null)&&(BA={min:D.min,max:D.max},D.min=D.max=null)}const $=e.tzDate||(A=>new Date(mr(A/x))),W=e.fmtDate||iI,G=x==1?TCe($):ICe($),z=w3($,b3(x==1?RCe:PCe,W)),K=_3($,S3(MCe,W)),Q=,re=r.legend=ar({},OCe,e.legend),ae=r.cursor=ar({},BCe,{drag:{y:o==2}},e.cursor),de=re.show,Ne=ae.show,ye=re.markers;re.idxs=Q,ye.width=zt(ye.width),ye.dash=zt(ye.dash),ye.stroke=zt(ye.stroke),ye.fill=zt(ye.fill);let fe,Z,oe,ce=,xe=,ge,pe=!1,he={};if(re.live){const A=w1?w1.values:null;pe=A!=null,ge=pe?A(r,1,0):{_:0};for(let D in ge)heD=JP}if(de)if(fe=Xi("table",$_e,c),oe=Xi("tbody",null,fe),re.mount(r,fe),pe){Z=Xi("thead",null,fe,oe);let A=Xi("tr",null,Z);Xi("th",null,A);for(var we in ge)Xi("th",ZD,A).textContent=we}else Uo(fe,H_e),re.live&&Uo(fe,z_e);const Ie={show:!0},Ce={show:!1};function Me(A,D){if(D==0&&(pe||!re.live||o==2))return Ff;let V=,Y=Xi("tr",B_e,oe,oe.childNodesD);Uo(Y,A.class),A.show||Uo(Y,lu);let te=Xi("th",null,Y);if(ye.show){let Se=Ci(W_e,te);if(D>0){let Ee=ye.width(r,D);Ee&&(Se.style.border=Ee+"px "+ye.dash(r,D)+" "+ye.stroke(r,D)),Se.style.background=ye.fill(r,D)}}let ie=Ci(ZD,te);A.label instanceof HTMLElement?ie.appendChild(A.label):ie.textContent=A.label,D>0&&(ye.show||(ie.style.color=A.width>0?ye.stroke(r,D):ye.fill(r,D)),Ye("click",te,Se=>{if(ae._lock)return;Hi(Se);let Ee=w.indexOf(A);if((Se.ctrlKey||Se.metaKey)!=re.isolate){let je=w.some((De,$e)=>$e>0&&$e!=Ee&&De.show);w.forEach((De,$e)=>{$e>0&&Fo($e,je?$e==Ee?Ie:Ce:Ie,!0,qt.setSeries)})}else Fo(Ee,{show:!A.show},!0,qt.setSeries)},!1),Es&&Ye(t3,te,Se=>{ae._lock||(Hi(Se),Fo(w.indexOf(A),xa,!0,qt.setSeries))},!1));for(var ue in ge){let Se=Xi("td",U_e,Y);Se.textContent="--",V.push(Se)}returnY,V}const ze=new Map;function Ye(A,D,V,Y=!0){const te=ze.get(D)||{},ie=ae.bindA(r,D,V,Y);ie&&(Nu(A,D,teA=ie),ze.set(D,te))}function Ht(A,D,V){const Y=ze.get(D)||{};for(let te in Y)(A==null||te==A)&&(JR(te,D,Yte),delete Yte);A==null&&ze.delete(D)}let Ft=0,rt=0,Ue=0,Te=0,bt=0,jt=0,vn=bt,dr=jt,On=Ue,Sr=Te,kn=0,fn=0,sn=0,Tr=0;r.bbox={};let ga=!1,_s=!1,Cs=!1,zi=!1,dl=!1,kr=!1;function fl(A,D,V){(V||A!=r.width||D!=r.height)&&nd(A,D),ya(!1),Cs=!0,_s=!0,_l()}function nd(A,D){r.width=Ft=Ue=A,r.height=rt=Te=D,bt=jt=0,od(),vh();let V=r.bbox;kn=V.left=su(bt*Zt,.5),fn=V.top=su(jt*Zt,.5),sn=V.width=su(Ue*Zt,.5),Tr=V.height=su(Te*Zt,.5)}const mh=3;function rd(){let A=!1,D=0;for(;!A;){D++;let V=Tg(D),Y=ww(D);A=D==mh||V&&Y,A||(nd(r.width,r.height),_s=!0)}}function gh({width:A,height:D}){fl(A,D)}r.setSize=gh;function od(){let A=!1,D=!1,V=!1,Y=!1;C.forEach((te,ie)=>{if(te.show&&te._show){let{side:ue,_size:Se}=te,Ee=ue%2,je=te.label!=null?te.labelSize:0,De=Se+je;De>0&&(Ee?(Ue-=De,ue==3?(bt+=De,Y=!0):V=!0):(Te-=De,ue==0?(jt+=De,A=!0):D=!0))}}),hi0=A,hi1=V,hi2=D,hi3=Y,Ue-=pi1+pi3,bt+=pi3,Te-=pi2+pi0,jt+=pi0}function vh(){let A=bt+Ue,D=jt+Te,V=bt,Y=jt;function te(ie,ue){switch(ie){case 1:return A+=ue,A-ue;case 2:return D+=ue,D-ue;case 3:return V-=ue,V+ue;case 0:return Y-=ue,Y+ue}}C.forEach((ie,ue)=>{if(ie.show&&ie._show){let Se=ie.side;ie._pos=te(Se,ie._size),ie.label!=null&&(ie._lpos=te(Se,ie.labelSize))}})}if(ae.dataIdx==null){let A=ae.hover,D=A.skip=new Set(A.skip??);D.add(void 0);let V=A.prox=zt(A.prox),Y=A.bias??(A.bias=0);ae.dataIdx=(te,ie,ue,Se)=>{if(ie==0)return ue;let Ee=ue,je=V(te,ie,ue,Se)??mn,De=je>=0&&je<mn,$e=j.ori==0?Ue:Te,st=ae.left,It=t0,lt=tie;if(D.has(ltue)){Ee=null;let wt=null,Qe=null,qe;if(Y==0||Y==-1)for(qe=ue;wt==null&&qe-- >0;)D.has(ltqe)||(wt=qe);if(Y==0||Y==1)for(qe=ue;Qe==null&&qe++<lt.length;)D.has(ltqe)||(Qe=qe);if(wt!=null||Qe!=null)if(De){let pn=wt==null?-1/0:q(Itwt,j,$e,0),Pn=Qe==null?1/0:q(ItQe,j,$e,0),sr=st-pn,Bt=Pn-st;sr<=Bt?sr<=je&&(Ee=wt):Bt<=je&&(Ee=Qe)}else Ee=Qe==null?wt:wt==null?Qe:ue-wt<=Qe-ue?wt:Qe}else De&&yr(st-q(Itue,j,$e,0))>je&&(Ee=null);return Ee}}const Hi=A=>{ae.event=A};ae.idxs=Q,ae._lock=!1;let nr=ae.points;nr.show=zt(nr.show),nr.size=zt(nr.size),nr.stroke=zt(nr.stroke),nr.width=zt(nr.width),nr.fill=zt(nr.fill);const $r=r.focus=ar({},e.focus||{alpha:.3},ae.focus),Es=$r.prox>=0,fi=Es&&nr.one;let zr=,Ns=,Rs=;function id(A,D){let V=nr.show(r,D);if(V instanceof HTMLElement)return Uo(V,F_e),Uo(V,A.class),Ws(V,-10,-10,Ue,Te),v.insertBefore(V,zrD),V}function sd(A,D){if(o==1||D>0){let V=o==1&&EA.scale.time,Y=A.value;A.value=V?g3(Y)?_3($,S3(Y,W)):Y||K:Y||QCe,A.label=A.label||(V?UCe:WCe)}if(fi||D>0){A.width=A.width==null?1:A.width,A.paths=A.paths||fEe||nCe,A.fillTo=zt(A.fillTo||rEe),A.pxAlign=+Yt(A.pxAlign,b),A.pxRound=A3(A.pxAlign),A.stroke=zt(A.stroke||null),A.fill=zt(A.fill||null),A._stroke=A._fill=A._paths=A._focus=null;let V=JCe(fo(1,A.width),1),Y=A.points=ar({},{size:V,width:fo(1,V*.2),stroke:A.stroke,space:V*2,paths:hEe,_stroke:null,_fill:null},A.points);Y.show=zt(Y.show),Y.filter=zt(Y.filter),Y.fill=zt(Y.fill),Y.stroke=zt(Y.stroke),Y.paths=zt(Y.paths),Y.pxAlign=A.pxAlign}if(de){let V=Me(A,D);ce.splice(D,0,V0),xe.splice(D,0,V1),re.values.push(null)}if(Ne){Q.splice(D,0,null);let V=null;fi?D==0&&(V=id(A,D)):D>0&&(V=id(A,D)),zr.splice(D,0,V),Ns.splice(D,0,0),Rs.splice(D,0,0)}ir("addSeries",D)}function yh(A,D){D=D??w.length,A=o==1?iT(A,D,E3,P3):iT(A,D,{},k3),w.splice(D,0,A),sd(wD,D)}r.addSeries=yh;function xh(A){if(w.splice(A,1),de){re.values.splice(A,1),xe.splice(A,1);let D=ce.splice(A,1)0;Ht(null,D.firstChild),D.remove()}Ne&&(Q.splice(A,1),zr.splice(A,1)0.remove(),Ns.splice(A,1),Rs.splice(A,1)),ir("delSeries",A)}r.delSeries=xh;const hi=!1,!1,!1,!1;function Ic(A,D){if(A._show=A.show,A.show){let V=A.side%2,Y=EA.scale;Y==null&&(A.scale=V?w1.scale:P,Y=EA.scale);let te=Y.time;A.size=zt(A.size),A.space=zt(A.space),A.rotate=zt(A.rotate),Vl(A.incrs)&&A.incrs.forEach(ue=>{!vc.has(ue)&&vc.set(ue,Oq(ue))}),A.incrs=zt(A.incrs||(Y.distr==2?CCe:te?x==1?NCe:kCe:au)),A.splits=zt(A.splits||(te&&Y.distr==1?G:Y.distr==3?tT:Y.distr==4?VCe:GCe)),A.stroke=zt(A.stroke),A.grid.stroke=zt(A.grid.stroke),A.ticks.stroke=zt(A.ticks.stroke),A.border.stroke=zt(A.border.stroke);let ie=A.values;A.values=Vl(ie)&&!Vl(ie0)?zt(ie):te?Vl(ie)?w3($,b3(ie,W)):g3(ie)?ACe($,ie):ie||z:ie||qCe,A.filter=zt(A.filter||(Y.distr>=3&&Y.log==10?XCe:Y.distr==3&&Y.log==2?ZCe:jq)),A.font=O3(A.font),A.labelFont=O3(A.labelFont),A._size=A.size(r,null,D,0),A._space=A._rotate=A._incrs=A._found=A._splits=A._values=null,A._size>0&&(hiD=!0,A._el=Ci(j_e,p))}}function Ts(A,D,V,Y){lette,ie,ue,Se=V,Ee=D%2,je=0;return Ee==0&&(Se||ie)&&(je=D==0&&!te||D==2&&!ue?mr(C3.size/3):0),Ee==1&&(te||ue)&&(je=D==1&&!ie||D==3&&!Se?mr(R3.size/2):0),je}const ad=r.padding=(e.padding||Ts,Ts,Ts,Ts).map(A=>zt(Yt(A,Ts))),pi=r._padding=ad.map((A,D)=>A(r,D,hi,0));let Kn,Bn=null,Wn=null;const Bi=o==1?w0.idxs:null;let Hr=null,mi=!1;function ld(A,D){if(t=A??,r.data=r._data=t,o==2){Kn=0;for(let V=1;V<w.length;V++)Kn+=tV0.length}else{t.length==0&&(r.data=r._data=t=),Hr=t0,Kn=Hr.length;let V=t;if(H==2){V=t.slice();let Y=V0=Array(Kn);for(let te=0;te<Kn;te++)Yte=te}r._data=t=V}if(ya(!0),ir("setData"),H==2&&(Cs=!0),D!==!1){let V=j;V.auto(r,mi)?Ac():Br(P,V.min,V.max),zi=zi||ae.left>=0,kr=!0,_l()}}r.setData=ld;function Ac(){mi=!0;let A,D;o==1&&(Kn>0?(Bn=Bi0=0,Wn=Bi1=Kn-1,A=t0Bn,D=t0Wn,H==2?(A=Bn,D=Wn):A==D&&(H==3?A,D=Nb(A,A,j.log,!1):H==4?A,D=tI(A,A,j.log,!1):j.time?D=A+mr(86400/x):A,D=Xy(A,D,nI,!0))):(Bn=Bi0=A=null,Wn=Bi1=D=null)),Br(P,A,D)}let hl,Wi,pl,ml,gl,Mc,jc,Lc,vl,fr;function Oc(A,D,V,Y,te,ie){A??(A=JD),V??(V=oI),Y??(Y="butt"),te??(te=JD),ie??(ie="round"),A!=hl&&(f.strokeStyle=hl=A),te!=Wi&&(f.fillStyle=Wi=te),D!=pl&&(f.lineWidth=pl=D),ie!=gl&&(f.lineJoin=gl=ie),Y!=Mc&&(f.lineCap=Mc=Y),V!=ml&&f.setLineDash(ml=V)}function yl(A,D,V,Y){D!=Wi&&(f.fillStyle=Wi=D),A!=jc&&(f.font=jc=A),V!=Lc&&(f.textAlign=Lc=V),Y!=vl&&(f.textBaseline=vl=Y)}function Dc(A,D,V,Y,te=0){if(Y.length>0&&A.auto(r,mi)&&(D==null||D.min==null)){let ie=Yt(Bn,0),ue=Yt(Wn,Y.length-1),Se=V.min==null?X_e(Y,ie,ue,te,A.distr==3):V.min,V.max;A.min=as(A.min,V.min=Se0),A.max=fo(A.max,V.max=Se1)}}const ks={min:null,max:null};function xl(){for(let Y in E){let te=EY;BY==null&&(te.min==null||BP!=null&&te.auto(r,mi))&&(BY=ks)}for(let Y in E){let te=EY;BY==null&&te.from!=null&&Bte.from!=null&&(BY=ks)}BP!=null&&ya(!0);let A={};for(let Y in B){let te=BY;if(te!=null){let ie=AY=$f(EY,iCe);if(te.min!=null)ar(ie,te);else if(Y!=P||o==2)if(Kn==0&&ie.from==null){let ue=ie.range(r,null,null,Y);ie.min=ue0,ie.max=ue1}else ie.min=mn,ie.max=-mn}}if(Kn>0){w.forEach((Y,te)=>{if(o==1){let ie=Y.scale,ue=Bie;if(ue==null)return;let Se=Aie;if(te==0){let Ee=Se.range(r,Se.min,Se.max,ie);Se.min=Ee0,Se.max=Ee1,Bn=ts(Se.min,t0),Wn=ts(Se.max,t0),Wn-Bn>1&&(t0Bn<Se.min&&Bn++,t0Wn>Se.max&&Wn--),Y.min=HrBn,Y.max=HrWn}else Y.show&&Y.auto&&Dc(Se,ue,Y,tte,Y.sorted);Y.idxs0=Bn,Y.idxs1=Wn}else if(te>0&&Y.show&&Y.auto){letie,ue=Y.facets,Se=ie.scale,Ee=ue.scale,je,De=tte,$e=ASe,st=AEe;$e!=null&&Dc($e,BSe,ie,je,ie.sorted),st!=null&&Dc(st,BEe,ue,De,ue.sorted),Y.min=ue.min,Y.max=ue.max}});for(let Y in A){let te=AY,ie=BY;if(te.from==null&&(ie==null||ie.min==null)){let ue=te.range(r,te.min==mn?null:te.min,te.max==-mn?null:te.max,Y);te.min=ue0,te.max=ue1}}}for(let Y in A){let te=AY;if(te.from!=null){let ie=Ate.from;if(ie.min==null)te.min=te.max=null;else{let ue=te.range(r,ie.min,ie.max,Y);te.min=ue0,te.max=ue1}}}let D={},V=!1;for(let Y in A){let te=AY,ie=EY;if(ie.min!=te.min||ie.max!=te.max){ie.min=te.min,ie.max=te.max;let ue=ie.distr;ie._min=ue==3?Va(ie.min):ue==4?AS(ie.min,ie.asinh):ue==100?ie.fwd(ie.min):ie.min,ie._max=ue==3?Va(ie.max):ue==4?AS(ie.max,ie.asinh):ue==100?ie.fwd(ie.max):ie.max,DY=V=!0}}if(V){w.forEach((Y,te)=>{o==2?te>0&&D.y&&(Y._paths=null):DY.scale&&(Y._paths=null)});for(let Y in D)Cs=!0,ir("setScale",Y);Ne&&ae.left>=0&&(zi=kr=!0)}for(let Y in B)BY=null}function cd(A){let D=eT(Bn-1,0,Kn-1),V=eT(Wn+1,0,Kn-1);for(;AD==null&&D>0;)D--;for(;AV==null&&V<Kn-1;)V++;returnD,V}function bh(){if(Kn>0){let A=w.some(D=>D._focus)&&fr!=$r.alpha;A&&(f.globalAlpha=fr=$r.alpha),w.forEach((D,V)=>{if(V>0&&D.show&&(va(V,!1),va(V,!0),D._paths==null)){let Y=fr;fr!=D.alpha&&(f.globalAlpha=fr=D.alpha);let te=o==2?0,tV0.length-1:cd(tV);D._paths=D.paths(r,V,te0,te1),fr!=Y&&(f.globalAlpha=fr=Y)}}),w.forEach((D,V)=>{if(V>0&&D.show){let Y=fr;fr!=D.alpha&&(f.globalAlpha=fr=D.alpha),D._paths!=null&&Fc(V,!1);{let te=D._paths!=null?D._paths.gaps:null,ie=D.points.show(r,V,Bn,Wn,te),ue=D.points.filter(r,V,ie,te);(ie||ue)&&(D.points._paths=D.points.paths(r,V,Bn,Wn,ue),Fc(V,!0))}fr!=Y&&(f.globalAlpha=fr=Y),ir("drawSeries",V)}}),A&&(f.globalAlpha=fr=1)}}function va(A,D){let V=D?wA.points:wA;V._stroke=V.stroke(r,A),V._fill=V.fill(r,A)}function Fc(A,D){let V=D?wA.points:wA,{stroke:Y,fill:te,clip:ie,flags:ue,_stroke:Se=V._stroke,_fill:Ee=V._fill,_width:je=V.width}=V._paths;je=gn(je*Zt,3);let De=null,$e=je%2/2;D&&Ee==null&&(Ee=je>0?"#fff":Se);let st=V.pxAlign==1&&$e>0;if(st&&f.translate($e,$e),!D){let It=kn-je/2,lt=fn-je/2,wt=sn+je,Qe=Tr+je;De=new Path2D,De.rect(It,lt,wt,Qe)}D?ud(Se,je,V.dash,V.cap,Ee,Y,te,ue,ie):$c(A,Se,je,V.dash,V.cap,Ee,Y,te,ue,De,ie),st&&f.translate(-$e,-$e)}function $c(A,D,V,Y,te,ie,ue,Se,Ee,je,De){let $e=!1;Ee!=0&&R.forEach((st,It)=>{if(st.series0==A){let lt=wst.series1,wt=tst.series1,Qe=(lt._paths||Kp).band;Vl(Qe)&&(Qe=st.dir==1?Qe0:Qe1);let qe,pn=null;lt.show&&Qe&&Q_e(wt,Bn,Wn)?(pn=st.fill(r,It)||ie,qe=lt._paths.clip):Qe=null,ud(D,V,Y,te,pn,ue,Se,Ee,je,De,qe,Qe),$e=!0}}),$e||ud(D,V,Y,te,ie,ue,Se,Ee,je,De)}const zc=zf|rT;function ud(A,D,V,Y,te,ie,ue,Se,Ee,je,De,$e){Oc(A,D,V,Y,te),(Ee||je||$e)&&(f.save(),Ee&&f.clip(Ee),je&&f.clip(je)),$e?(Se&zc)==zc?(f.clip($e),De&&f.clip(De),wl(te,ue),bl(A,ie,D)):Se&rT?(wl(te,ue),f.clip($e),bl(A,ie,D)):Se&zf&&(f.save(),f.clip($e),De&&f.clip(De),wl(te,ue),f.restore(),bl(A,ie,D)):(wl(te,ue),bl(A,ie,D)),(Ee||je||$e)&&f.restore()}function bl(A,D,V){V>0&&(D instanceof Map?D.forEach((Y,te)=>{f.strokeStyle=hl=te,f.stroke(Y)}):D!=null&&A&&f.stroke(D))}function wl(A,D){D instanceof Map?D.forEach((V,Y)=>{f.fillStyle=Wi=Y,f.fill(V)}):D!=null&&A&&f.fill(D)}function Sl(A,D,V,Y){let te=CA,ie;if(Y<=0)ie=0,0;else{let ue=te._space=te.space(r,A,D,V,Y),Se=te._incrs=te.incrs(r,A,D,V,Y,ue);ie=xEe(D,V,Se,Y,ue)}return te._found=ie}function dd(A,D,V,Y,te,ie,ue,Se,Ee,je){let De=ue%2/2;b==1&&f.translate(De,De),Oc(Se,ue,Ee,je,Se),f.beginPath();let $e,st,It,lt,wt=te+(Y==0||Y==3?-ie:ie);V==0?(st=te,lt=wt):($e=te,It=wt);for(let Qe=0;Qe<A.length;Qe++)DQe!=null&&(V==0?$e=It=AQe:st=lt=AQe,f.moveTo($e,st),f.lineTo(It,lt));f.stroke(),b==1&&f.translate(-De,-De)}function Tg(A){let D=!0;return C.forEach((V,Y)=>{if(!V.show)return;let te=EV.scale;if(te.min==null){V._show&&(D=!1,V._show=!1,ya(!1));return}else V._show||(D=!1,V._show=!0,ya(!1));let ie=V.side,ue=ie%2,{min:Se,max:Ee}=te,je,De=Sl(Y,Se,Ee,ue==0?Ue:Te);if(De==0)return;let $e=te.distr==2,st=V._splits=V.splits(r,Y,Se,Ee,je,De,$e),It=te.distr==2?st.map(qe=>Hrqe):st,lt=te.distr==2?Hrst1-Hrst0:je,wt=V._values=V.values(r,V.filter(r,It,Y,De,lt),Y,De,lt);V._rotate=ie==2?V.rotate(r,wt,Y,De):0;let Qe=V._size;V._size=Ni(V.size(r,wt,Y,A)),Qe!=null&&V._size!=Qe&&(D=!1)}),D}function ww(A){let D=!0;return ad.forEach((V,Y)=>{let te=V(r,Y,hi,A);te!=piY&&(D=!1),piY=te}),D}function kg(){for(let A=0;A<C.length;A++){let D=CA;if(!D.show||!D._show)continue;let V=D.side,Y=V%2,te,ie,ue=D.stroke(r,A),Se=V==0||V==3?-1:1,Ee,je=D._found;if(D.label!=null){let un=D.labelGap*Se,_r=mr((D._lpos+un)*Zt);yl(D.labelFont0,ue,"center",V==2?cp:QD),f.save(),Y==1?(te=ie=0,f.translate(_r,mr(fn+Tr/2)),f.rotate((V==3?-W0:W0)/2)):(te=mr(kn+sn/2),ie=_r);let _a=Aq(D.label)?D.label(r,A,Ee,je):D.label;f.fillText(_a,te,ie),f.restore()}if(je==0)continue;let De=ED.scale,$e=Y==0?sn:Tr,st=Y==0?kn:fn,It=D._splits,lt=De.distr==2?It.map(un=>Hrun):It,wt=De.distr==2?HrIt1-HrIt0:Ee,Qe=D.ticks,qe=D.border,pn=Qe.show?Qe.size:0,Pn=mr(pn*Zt),sr=mr((D.alignTo==2?D._size-pn-D.gap:D.gap)*Zt),Bt=D._rotate*-W0/180,ee=_(D._pos*Zt),le=(Pn+sr)*Se,se=ee+le;ie=Y==0?se:0,te=Y==1?se:0;let Ae=D.font0,Ke=D.align==1?Pd:D.align==2?kS:Bt>0?Pd:Bt<0?kS:Y==0?"center":V==3?kS:Pd,ft=Bt||Y==1?"middle":V==2?cp:QD;yl(Ae,ue,Ke,ft);let Gt=D.font1*D.lineGap,St=It.map(un=>_(a(un,De,$e,st))),oo=D._values;for(let un=0;un<oo.length;un++){let _r=ooun;if(_r!=null){Y==0?te=Stun:ie=Stun,_r=""+_r;let _a=_r.indexOf(` +`)==-1?_r:_r.split(/\n/gm);for(let qn=0;qn<_a.length;qn++){let Uc=_aqn;Bt?(f.save(),f.translate(te,ie+qn*Gt),f.rotate(Bt),f.fillText(Uc,0,0),f.restore()):f.fillText(Uc,te,ie+qn*Gt)}}}Qe.show&&dd(St,Qe.filter(r,lt,A,je,wt),Y,V,ee,Pn,gn(Qe.width*Zt,3),Qe.stroke(r,A),Qe.dash,Qe.cap);let yi=D.grid;yi.show&&dd(St,yi.filter(r,lt,A,je,wt),Y,Y==0?2:1,Y==0?fn:kn,Y==0?Tr:sn,gn(yi.width*Zt,3),yi.stroke(r,A),yi.dash,yi.cap),qe.show&&dd(ee,1,Y==0?1:0,Y==0?1:2,Y==1?fn:kn,Y==1?Tr:sn,gn(qe.width*Zt,3),qe.stroke(r,A),qe.dash,qe.cap)}ir("drawAxes")}function ya(A){w.forEach((D,V)=>{V>0&&(D._paths=null,A&&(o==1?(D.min=null,D.max=null):D.facets.forEach(Y=>{Y.min=null,Y.max=null})))})}let Hc=!1,fd=!1,Bc=;function Sw(){fd=!1;for(let A=0;A<Bc.length;A++)ir(...BcA);Bc.length=0}function _l(){Hc||(fCe(Ig),Hc=!0)}function Pg(A,D=!1){Hc=!0,fd=D,A(r),Ig(),D&&Bc.length>0&&queueMicrotask(Sw)}r.batch=Pg;function Ig(){if(ga&&(xl(),ga=!1),Cs&&(rd(),Cs=!1),_s){if(Sn(g,Pd,bt),Sn(g,cp,jt),Sn(g,_p,Ue),Sn(g,Cp,Te),Sn(v,Pd,bt),Sn(v,cp,jt),Sn(v,_p,Ue),Sn(v,Cp,Te),Sn(p,_p,Ft),Sn(p,Cp,rt),d.width=mr(Ft*Zt),d.height=mr(rt*Zt),C.forEach(({_el:A,_show:D,_size:V,_pos:Y,side:te})=>{if(A!=null)if(D){let ie=te===3||te===0?V:0,ue=te%2==1;Sn(A,ue?"left":"top",Y-ie),Sn(A,ue?"width":"height",V),Sn(A,ue?"top":"left",ue?jt:bt),Sn(A,ue?"height":"width",ue?Te:Ue),QR(A,lu)}else Uo(A,lu)}),hl=Wi=pl=gl=Mc=jc=Lc=vl=ml=null,fr=1,ba(!0),bt!=vn||jt!=dr||Ue!=On||Te!=Sr){ya(!1);let A=Ue/On,D=Te/Sr;if(Ne&&!zi&&ae.left>=0){ae.left*=A,ae.top*=D,Ps&&Ws(Ps,mr(ae.left),0,Ue,Te),Cl&&Ws(Cl,0,mr(ae.top),Ue,Te);for(let V=0;V<zr.length;V++){let Y=zrV;Y!=null&&(NsV*=A,RsV*=D,Ws(Y,Ni(NsV),Ni(RsV),Ue,Te))}}if(ln.show&&!dl&&ln.left>=0&&ln.width>0){ln.left*=A,ln.width*=A,ln.top*=D,ln.height*=D;for(let V in kh)Sn(Rl,V,lnV)}vn=bt,dr=jt,On=Ue,Sr=Te}ir("setSize"),_s=!1}Ft>0&&rt>0&&(f.clearRect(0,0,d.width,d.height),ir("drawClear"),k.forEach(A=>A()),ir("draw")),ln.show&&dl&&(Un(ln),dl=!1),Ne&&zi&&(qi(null,!0,!1),zi=!1),re.show&&re.live&&kr&&(cn(),kr=!1),l||(l=!0,r.status=1,ir("ready")),mi=!1,Hc=!1}r.redraw=(A,D)=>{Cs=D||!1,A!==!1?Br(P,j.min,j.max):_l()};function wh(A,D){let V=EA;if(V.from==null){if(Kn==0){let Y=V.range(r,D.min,D.max,A);D.min=Y0,D.max=Y1}if(D.min>D.max){let Y=D.min;D.min=D.max,D.max=Y}if(Kn>1&&D.min!=null&&D.max!=null&&D.max-D.min<1e-16)return;A==P&&V.distr==2&&Kn>0&&(D.min=ts(D.min,t0),D.max=ts(D.max,t0),D.min==D.max&&D.max++),BA=D,ga=!0,_l()}}r.setScale=wh;let Sh,_h,Ps,Cl,Ag,Mg,El,Nl,an,nn,hn,yn,Is=!1;const hr=ae.drag;let rr=hr.x,or=hr.y;Ne&&(ae.x&&(Sh=Ci(O_e,v)),ae.y&&(_h=Ci(D_e,v)),j.ori==0?(Ps=Sh,Cl=_h):(Ps=_h,Cl=Sh),hn=ae.left,yn=ae.top);const ln=r.select=ar({show:!0,over:!0,left:0,width:0,top:0,height:0},e.select),Rl=ln.show?Ci(L_e,ln.over?v:g):null;function Un(A,D){if(ln.show){for(let V in A)lnV=AV,V in kh&&Sn(Rl,V,AV);D!==!1&&ir("setSelect")}}r.setSelect=Un;function Ch(A){if(wA.show)de&&QR(ceA,lu);else if(de&&Uo(ceA,lu),Ne){let V=fi?zr0:zrA;V!=null&&Ws(V,-10,-10,Ue,Te)}}function Br(A,D,V){wh(A,{min:D,max:V})}function Fo(A,D,V,Y){D.focus!=null&&ro(A),D.show!=null&&w.forEach((te,ie)=>{ie>0&&(A==ie||A==null)&&(te.show=D.show,Ch(ie),o==2?(Br(te.facets0.scale,null,null),Br(te.facets1.scale,null,null)):Br(te.scale,null,null),_l())}),V!==!1&&ir("setSeries",A,D),Y&&kl("setSeries",r,A,D)}r.setSeries=Fo;function Eh(A,D){ar(RA,D)}function vo(A,D){A.fill=zt(A.fill||null),A.dir=Yt(A.dir,-1),D=D??R.length,R.splice(D,0,A)}function jg(A){A==null?R.length=0:R.splice(A,1)}r.addBand=vo,r.setBand=Eh,r.delBand=jg;function Lg(A,D){wA.alpha=D,Ne&&zrA!=null&&(zrA.style.opacity=D),de&&ceA&&(ceA.style.opacity=D)}let gi,Ui,As;const xa={focus:!0};function ro(A){if(A!=As){let D=A==null,V=$r.alpha!=1;w.forEach((Y,te)=>{if(o==1||te>0){let ie=D||te==0||te==A;Y._focus=D?null:ie,V&&Lg(te,ie?1:$r.alpha)}}),As=A,V&&_l()}}de&&Es&&Ye(n3,fe,A=>{ae._lock||(Hi(A),As!=null&&Fo(null,xa,!0,qt.setSeries))});function vi(A,D,V){let Y=ED;V&&(A=A/Zt-(Y.ori==1?jt:bt));let te=Ue;Y.ori==1&&(te=Te,A=te-A),Y.dir==-1&&(A=te-A);let ie=Y._min,ue=Y._max,Se=A/te,Ee=ie+(ue-ie)*Se,je=Y.distr;return je==3?Df(10,Ee):je==4?eCe(Ee,Y.asinh):je==100?Y.bwd(Ee):Ee}function yo(A,D){let V=vi(A,P,D);return ts(V,t0,Bn,Wn)}r.valToIdx=A=>ts(A,t0),r.posToIdx=yo,r.posToVal=vi,r.valToPos=(A,D,V)=>ED.ori==0?i(A,ED,V?sn:Ue,V?kn:0):s(A,ED,V?Tr:Te,V?fn:0),r.setCursor=(A,D,V)=>{hn=A.left,yn=A.top,qi(null,D,V)};function Og(A,D){Sn(Rl,Pd,ln.left=A),Sn(Rl,_p,ln.width=D)}function Nh(A,D){Sn(Rl,cp,ln.top=A),Sn(Rl,Cp,ln.height=D)}let xo=j.ori==0?Og:Nh,Ms=j.ori==1?Og:Nh;function Rh(){if(de&&re.live)for(let A=o==2?1:0;A<w.length;A++){if(A==0&&pe)continue;let D=re.valuesA,V=0;for(let Y in D)xeAV++.firstChild.nodeValue=DY}}function cn(A,D){if(A!=null&&(A.idxs?A.idxs.forEach((V,Y)=>{QY=V}):oCe(A.idx)||Q.fill(A.idx),re.idx=Q0),de&&re.live){for(let V=0;V<w.length;V++)(V>0||o==1&&!pe)&&js(V,QV);Rh()}kr=!1,D!==!1&&ir("setLegend")}r.setLegend=cn;function js(A,D){let V=wA,Y=A==0&&H==2?Hr:tA,te;pe?te=V.values(r,A,D)??he:(te=V.value(r,D==null?null:YD,A,D),te=te==null?he:{_:te}),re.valuesA=te}function qi(A,D,V){an=hn,nn=yn,hn,yn=ae.move(r,hn,yn),ae.left=hn,ae.top=yn,Ne&&(Ps&&Ws(Ps,mr(hn),0,Ue,Te),Cl&&Ws(Cl,0,mr(yn),Ue,Te));let Y,te=Bn>Wn;gi=mn,Ui=null;let ie=j.ori==0?Ue:Te,ue=j.ori==1?Ue:Te;if(hn<0||Kn==0||te){Y=ae.idx=null;for(let Se=0;Se<w.length;Se++){let Ee=zrSe;Ee!=null&&Ws(Ee,-10,-10,Ue,Te)}Es&&Fo(null,xa,!0,A==null&&qt.setSeries),re.live&&(Q.fill(Y),kr=!0)}else{let Se,Ee,je;o==1&&(Se=j.ori==0?hn:yn,Ee=vi(Se,P),Y=ae.idx=ts(Ee,t0,Bn,Wn),je=q(t0Y,j,ie,0));let De=-10,$e=-10,st=0,It=0,lt=!0,wt="",Qe="";for(let qe=o==2?1:0;qe<w.length;qe++){let pn=wqe,Pn=Qqe,sr=Pn==null?null:o==1?tqePn:tqe1Pn,Bt=ae.dataIdx(r,qe,Y,Ee),ee=Bt==null?null:o==1?tqeBt:tqe1Bt;if(kr=kr||ee!=sr||Bt!=Pn,Qqe=Bt,qe>0&&pn.show){let le=Bt==null?-10:Bt==Y?je:q(o==1?t0Bt:tqe0Bt,j,ie,0),se=ee==null?-10:M(ee,o==1?Epn.scale:Epn.facets1.scale,ue,0);if(Es&&ee!=null){let Ae=j.ori==1?hn:yn,Ke=yr($r.dist(r,qe,Bt,se,Ae));if(Ke<gi){let ft=$r.bias;if(ft!=0){let Gt=vi(Ae,pn.scale),St=ee>=0?1:-1,oo=Gt>=0?1:-1;oo==St&&(oo==1?ft==1?ee>=Gt:ee<=Gt:ft==1?ee<=Gt:ee>=Gt)&&(gi=Ke,Ui=qe)}else gi=Ke,Ui=qe}}if(kr||fi){let Ae,Ke;j.ori==0?(Ae=le,Ke=se):(Ae=se,Ke=le);let ft,Gt,St,oo,yi,un,_r=!0,_a=nr.bbox;if(_a!=null){_r=!1;let qn=_a(r,qe);St=qn.left,oo=qn.top,ft=qn.width,Gt=qn.height}else St=Ae,oo=Ke,ft=Gt=nr.size(r,qe);if(un=nr.fill(r,qe),yi=nr.stroke(r,qe),fi)qe==Ui&&gi<=$r.prox&&(De=St,$e=oo,st=ft,It=Gt,lt=_r,wt=un,Qe=yi);else{let qn=zrqe;qn!=null&&(Nsqe=St,Rsqe=oo,c3(qn,ft,Gt,_r),a3(qn,un,yi),Ws(qn,Ni(St),Ni(oo),Ue,Te))}}}}if(fi){let qe=$r.prox,pn=As==null?gi<=qe:gi>qe||Ui!=As;if(kr||pn){let Pn=zr0;Pn!=null&&(Ns0=De,Rs0=$e,c3(Pn,st,It,lt),a3(Pn,wt,Qe),Ws(Pn,Ni(De),Ni($e),Ue,Te))}}}if(ln.show&&Is)if(A!=null){letSe,Ee=qt.scales,je,De=qt.match,$e,st=A.cursor.sync.scales,It=A.cursor.drag;if(rr=It._x,or=It._y,rr||or){let{left:lt,top:wt,width:Qe,height:qe}=A.select,pn=A.scales$e.ori,Pn=A.posToVal,sr,Bt,ee,le,se,Ae=Se!=null&&je(Se,$e),Ke=Ee!=null&&De(Ee,st);Ae&&rr?(pn==0?(sr=lt,Bt=Qe):(sr=wt,Bt=qe),ee=ESe,le=q(Pn(sr,$e),ee,ie,0),se=q(Pn(sr+Bt,$e),ee,ie,0),xo(as(le,se),yr(se-le))):xo(0,ie),Ke&&or?(pn==1?(sr=lt,Bt=Qe):(sr=wt,Bt=qe),ee=EEe,le=M(Pn(sr,st),ee,ue,0),se=M(Pn(sr+Bt,st),ee,ue,0),Ms(as(le,se),yr(se-le))):Ms(0,ue)}else hd()}else{let Se=yr(an-Ag),Ee=yr(nn-Mg);if(j.ori==1){let st=Se;Se=Ee,Ee=st}rr=hr.x&&Se>=hr.dist,or=hr.y&&Ee>=hr.dist;let je=hr.uni;je!=null?rr&&or&&(rr=Se>=je,or=Ee>=je,!rr&&!or&&(Ee>Se?or=!0:rr=!0)):hr.x&&hr.y&&(rr||or)&&(rr=or=!0);let De,$e;rr&&(j.ori==0?(De=El,$e=hn):(De=Nl,$e=yn),xo(as(De,$e),yr($e-De)),or||Ms(0,ue)),or&&(j.ori==1?(De=El,$e=hn):(De=Nl,$e=yn),Ms(as(De,$e),yr($e-De)),rr||xo(0,ie)),!rr&&!or&&(xo(0,0),Ms(0,0))}if(hr._x=rr,hr._y=or,A==null){if(V){if(md!=null){letSe,Ee=qt.scales;qt.values0=Se!=null?vi(j.ori==0?hn:yn,Se):null,qt.values1=Ee!=null?vi(j.ori==1?hn:yn,Ee):null}kl(PS,r,hn,yn,Ue,Te,Y)}if(Es){let Se=V&&qt.setSeries,Ee=$r.prox;As==null?gi<=Ee&&Fo(Ui,xa,!0,Se):gi>Ee?Fo(null,xa,!0,Se):Ui!=As&&Fo(Ui,xa,!0,Se)}}kr&&(re.idx=Y,cn()),D!==!1&&ir("setCursor")}let Ls=null;Object.defineProperty(r,"rect",{get(){return Ls==null&&ba(!1),Ls}});function ba(A=!1){A?Ls=null:(Ls=v.getBoundingClientRect(),ir("syncRect",Ls))}function Dg(A,D,V,Y,te,ie,ue){ae._lock||Is&&A!=null&&A.movementX==0&&A.movementY==0||(Th(A,D,V,Y,te,ie,ue,!1,A!=null),A!=null?qi(null,!0,!0):qi(D,!0,!1))}function Th(A,D,V,Y,te,ie,ue,Se,Ee){if(Ls==null&&ba(!1),Hi(A),A!=null)V=A.clientX-Ls.left,Y=A.clientY-Ls.top;else{if(V<0||Y<0){hn=-10,yn=-10;return}letje,De=qt.scales,$e=D.cursor.sync,st,It=$e.values,lt,wt=$e.scales,Qe,qe=qt.match,pn=D.axes0.side%2==1,Pn=j.ori==0?Ue:Te,sr=j.ori==1?Ue:Te,Bt=pn?ie:te,ee=pn?te:ie,le=pn?Y:V,se=pn?V:Y;if(lt!=null?V=Qe(je,lt)?a(st,Eje,Pn,0):-10:V=Pn*(le/Bt),wt!=null?Y=qe(De,wt)?a(It,EDe,sr,0):-10:Y=sr*(se/ee),j.ori==1){let Ae=V;V=Y,Y=Ae}}Ee&&(D==null||D.cursor.event.type==PS)&&((V<=1||V>=Ue-1)&&(V=su(V,Ue)),(Y<=1||Y>=Te-1)&&(Y=su(Y,Te))),Se?(Ag=V,Mg=Y,El,Nl=ae.move(r,V,Y)):(hn=V,yn=Y)}const kh={width:0,height:0,left:0,top:0};function hd(){Un(kh,!1)}let Wc,wa,Fg,Ph;function Ih(A,D,V,Y,te,ie,ue){Is=!0,rr=or=hr._x=hr._y=!1,Th(A,D,V,Y,te,ie,ue,!0,!1),A!=null&&(Ye(IS,XR,pd,!1),kl(e3,r,El,Nl,Ue,Te,null));let{left:Se,top:Ee,width:je,height:De}=ln;Wc=Se,wa=Ee,Fg=je,Ph=De}function pd(A,D,V,Y,te,ie,ue){Is=hr._x=hr._y=!1,Th(A,D,V,Y,te,ie,ue,!1,!0);let{left:Se,top:Ee,width:je,height:De}=ln,$e=je>0||De>0,st=Wc!=Se||wa!=Ee||Fg!=je||Ph!=De;if($e&&st&&Un(ln),hr.setScale&&$e&&st){let It=Se,lt=je,wt=Ee,Qe=De;if(j.ori==1&&(It=Ee,lt=De,wt=Se,Qe=je),rr&&Br(P,vi(It,P),vi(It+lt,P)),or)for(let qe in E){let pn=Eqe;qe!=P&&pn.from==null&&pn.min!=mn&&Br(qe,vi(wt+Qe,qe),vi(wt,qe))}hd()}else ae.lock&&(ae._lock=!ae._lock,qi(D,!0,A!=null));A!=null&&(Ht(IS,XR),kl(IS,r,hn,yn,Ue,Te,null))}function Ah(A,D,V,Y,te,ie,ue){if(ae._lock)return;Hi(A);let Se=Is;if(Is){let Ee=!0,je=!0,De=10,$e,st;j.ori==0?($e=rr,st=or):($e=or,st=rr),$e&&st&&(Ee=hn<=De||hn>=Ue-De,je=yn<=De||yn>=Te-De),$e&&Ee&&(hn=hn<El?0:Ue),st&&je&&(yn=yn<Nl?0:Te),qi(null,!0,!0),Is=!1}hn=-10,yn=-10,Q.fill(null),qi(null,!0,!0),Se&&(Is=Se)}function Mh(A,D,V,Y,te,ie,ue){ae._lock||(Hi(A),Ac(),hd(),A!=null&&kl(r3,r,hn,yn,Ue,Te,null))}function $g(){C.forEach(bEe),fl(r.width,r.height,!0)}Nu(Ky,pf,$g);const Os={};Os.mousedown=Ih,Os.mousemove=Dg,Os.mouseup=pd,Os.dblclick=Mh,Os.setSeries=(A,D,V,Y)=>{let te=qt.match2;V=te(r,D,V),V!=-1&&Fo(V,Y,!0,!1)},Ne&&(Ye(e3,v,Ih),Ye(PS,v,Dg),Ye(t3,v,A=>{Hi(A),ba(!1)}),Ye(n3,v,Ah),Ye(r3,v,Mh),oT.add(r),r.syncRect=ba);const Tl=r.hooks=e.hooks||{};function ir(A,D,V){fd?Bc.push(A,D,V):A in Tl&&TlA.forEach(Y=>{Y.call(null,r,D,V)})}(e.plugins||).forEach(A=>{for(let D in A.hooks)TlD=(TlD||).concat(A.hooksD)});const jh=(A,D,V)=>V,qt=ar({key:null,setSeries:!1,filters:{pub:p3,sub:p3},scales:P,w1?w1.scale:null,match:m3,m3,jh,values:null,null},ae.sync);qt.match.length==2&&qt.match.push(jh),ae.sync=qt;const md=qt.key,Sa=nG(md);function kl(A,D,V,Y,te,ie,ue){qt.filters.pub(A,D,V,Y,te,ie,ue)&&Sa.pub(A,D,V,Y,te,ie,ue)}Sa.sub(r);function zg(A,D,V,Y,te,ie,ue){qt.filters.sub(A,D,V,Y,te,ie,ue)&&OsA(null,D,V,Y,te,ie,ue)}r.pub=zg;function _w(){Sa.unsub(r),oT.delete(r),ze.clear(),JR(Ky,pf,$g),c.remove(),fe==null||fe.remove(),ir("destroy")}r.destroy=_w;function Lh(){ir("init",e,t),ld(t||e.data,!1),BP?wh(P,BP):Ac(),dl=ln.show&&(ln.width>0||ln.height>0),zi=kr=!0,fl(e.width,e.height)}return w.forEach(sd),C.forEach(Ic),n?n instanceof HTMLElement?(n.appendChild(c),Lh()):n(r,Lh):Lh(),r}Jr.assign=ar;Jr.fmtNum=rI;Jr.rangeNum=Xy;Jr.rangeLog=Nb;Jr.rangeAsinh=tI;Jr.orient=Vu;Jr.pxRatio=Zt;Jr.join=dCe;Jr.fmtDate=iI,Jr.tzDate=SCe;Jr.sync=nG;{Jr.addGap=oEe,Jr.clipGaps=kb;let e=Jr.paths={points:lG};e.linear=uG,e.stepped=aEe,e.bars=lEe,e.spline=uEe}const fI=y.memo(({data:e,options:t,className:n,onCreate:r,onDestroy:o})=>{const i=y.useRef(null),s=y.useRef(null),a=y.useRef(r),l=y.useRef(o);return y.useEffect(()=>{a.current=r,l.current=o}),y.useEffect(()=>{var f;if(!i.current)return;const c=new Jr(t,e,i.current);s.current=c,(f=a.current)==null||f.call(a,c);const d=i.current.getBoundingClientRect();return d.width>0&&d.height>0&&c.setSize({width:d.width,height:d.height}),()=>{var p;s.current&&((p=l.current)==null||p.call(l,s.current),s.current.destroy(),s.current=null)}},t),y.useEffect(()=>{s.current&&s.current.setData(e)},e),y.useEffect(()=>{const c=()=>{if(!i.current||!s.current)return;const d=i.current.getBoundingClientRect();d.width>0&&d.height>0&&s.current.setSize({width:d.width,height:d.height})};return window.addEventListener("resize",c),()=>window.removeEventListener("resize",c)},),h.jsx("div",{ref:i,className:n})},(e,t)=>e.data===t.data&&e.options===t.options&&e.className===t.className);fI.displayName="UplotChart";const wEe=(e,t,n,r,o,i)=>{consts,a=y.useState(),l=y.useRef(0),c=y.useRef({cpu:-1,memory:-1});return y.useEffect(()=>{if(!n)return;const d=Date.now();d-l.current<i||c.current.cpu===e&&c.current.memory===t||(l.current=d,c.current={cpu:e,memory:t},a(f=>{const p={timestamp:d,cpu_percent:e,memory_mb:t};let v=...f,p;if(o&&o!==1/0){const b=d-o,_=v.findIndex(x=>x.timestamp>=b);_>0&&(v=v.slice(_))}else v.length>r&&(v=v.slice(v.length-r));return v}))},e,t,n,r,o,i),{dataPoints:s}},Hf=e=>{if(e===void 0)return"0 ms";if(e<1e3)return`${e.toFixed(0)} μs`;const t=e/1e3;if(t<1e3)return`${t.toFixed(2)} ms`;const n=t/1e3;if(n<60)return`${n.toFixed(2)} s`;const r=Math.floor(n/60),o=n%60;if(r<60)return`${r}m ${o.toFixed(0)}s`;const i=Math.floor(r/60),s=r%60;return`${i}h ${s}m ${o.toFixed(0)}s`},SEe=e=>e===0?1:e/1e3/1e3,_Ee=()=>new Date().toLocaleTimeString("en-US",{hour12:!1,minute:"2-digit",second:"2-digit"}),D3=e=>{if(e===0)return"0 ms";const t=e/1e3;return t>=1e3?`${(t/1e3).toFixed(1)} s`:`${Math.floor(t)} ms`},F3=e=>{if(e<60)return`${e.toFixed(0)}s`;const t=Math.floor(e/60),n=Math.floor(e%60);if(t<60)return n>0?`${t}m ${n}s`:`${t}m`;const r=Math.floor(t/60),o=t%60;return o>0?`${r}h ${o}m`:`${r}h`},CEe=({memoryYAxisMax:e})=>({width:100,height:100,padding:10,10,5,5,cursor:{show:!0,drag:{x:!1,y:!1},points:{size:8,width:2}},legend:{show:!1,live:!1},hooks:{setCursor:t=>{var c,d;const{left:n=0,top:r=0,idx:o}=t.cursor;if(o==null){const f=t.root.querySelector(".u-tooltip");f&&(f.style.display="none");return}let i=t.root.querySelector(".u-tooltip");i||(i=document.createElement("div"),i.className="u-tooltip",i.style.cssText=` + position: absolute; + background: rgb(2 6 23); + color: rgb(226 232 240); + border: 1px solid hsl(var(--border)); + border-radius: 6px; + padding: 8px 10px; + font-size: 11px; + font-family: monospace; + pointer-events: none; + z-index: 100; + white-space: nowrap; + `,t.root.appendChild(i));const s=F3(t.data0o),a=((c=t.data1o)==null?void 0:c.toFixed(2))||"--",l=((d=t.data2o)==null?void 0:d.toFixed(2))||"--";i.innerHTML=` + <div style="margin-bottom: 4px; color: #6ee7b7;">Time: ${s}</div> + <div style="color: #38bdf8;">Memory: ${a} MB</div> + <div style="color: #ef4444;">CPU: ${l}%</div> + `,i.style.display="block",i.style.left=`${n+15}px`,i.style.top=`${r+15}px`}},series:{label:"Time"},{label:"Memory (MB)",stroke:"#38bdf8",width:2,fill:"rgba(56, 189, 248, 0.15)",scale:"memory",value:(t,n)=>n==null?"--":`${n.toFixed(2)} MB`},{label:"CPU (%)",stroke:"#ef4444",width:2,fill:"rgba(239, 68, 68, 0.15)",scale:"cpu",value:(t,n)=>n==null?"--":`${n.toFixed(2)}%`},scales:{x:{time:!1},memory:{range:0,e},cpu:{range:0,100}},axes:{stroke:"#6ee7b7",grid:{show:!0,stroke:"rgba(110, 231, 183, 0.1)",width:1},ticks:{stroke:"#6ee7b7",size:5,width:1},font:"11px monospace",size:50,values:(t,n)=>n.map(F3)},{scale:"memory",stroke:"#38bdf8",side:3,grid:{show:!0,stroke:"rgba(56, 189, 248, 0.1)",width:1},ticks:{stroke:"#38bdf8",size:5,width:2},font:"11px monospace",size:60,values:(t,n)=>n.map(r=>`${r.toFixed(0)}`)},{scale:"cpu",stroke:"#ef4444",side:1,grid:{show:!1},ticks:{stroke:"#ef4444",size:5,width:2},font:"11px monospace",size:55,values:(t,n)=>n.map(r=>`${r}%`)}});function EEe(e){const t=e.length>0?e0.timestamp:0,n=e.map(s=>(s.timestamp-t)/1e3),r=e.map(s=>s.memory_mb),o=e.map(s=>s.cpu_percent);return{alignedData:n,r,o,relativeSeconds:n,memoryData:r,cpuData:o}}function NEe(e){const r=Math.ceil(e*1.5/50)*50;return Math.max(100,r)}const pG=y.memo(({currentCPUPercent:e,currentMemoryBytes:t,isLive:n,maxPoints:r=400,windowDuration:o})=>{const i=y.useMemo(()=>t/1048576,t),s=y.useMemo(()=>NEe(i),i),{dataPoints:a}=wEe(e,i,n,r,o,150),l=y.useMemo(()=>CEe({memoryYAxisMax:s}),s),c=y.useMemo(()=>{const{alignedData:d}=EEe(a);return d},a);return h.jsxs(Jn,{className:"bg-stat border-transparent h-full flex flex-col",children:h.jsx(Dr,{className:"pb-2 flex-shrink-0",children:h.jsxs(Rr,{className:"flex justify-center items-center gap-2 text-sm stat stat-label",children:h.jsxs("span",{className:"flex items-center gap-1.5",children:h.jsx("span",{className:"w-3 h-0.5",style:{backgroundColor:"#38bdf8"}}),"Memory"}),"/",h.jsxs("span",{className:"flex items-center gap-1.5",children:h.jsx("span",{className:"w-3 h-0.5",style:{backgroundColor:"#ef4444"}}),"CPU"})})}),h.jsx(er,{className:"p-2 flex-1 min-h-0",children:h.jsx("div",{className:"w-full h-full",children:h.jsx(fI,{data:c,options:l,className:"w-full h-full"})})})})});pG.displayName="CpuMemoryChartUplot";const jo=e=>{if(e===0)return"0 B";const t=1024,n="B","KB","MB","GB","TB",r=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/Math.pow(t,r)).toFixed(1))+" "+nr},Xp=e=>e<1e3?e.toString():e<1e6?(e/1e3).toFixed(1)+"K":e<1e9?(e/1e6).toFixed(1)+"M":(e/1e9).toFixed(1)+"G",Xs=e=>e?e>=1e9?`${(e/1e9).toFixed(2)} Gb/s`:e>=1e6?`${(e/1e6).toFixed(2)} Mb/s`:e>=1e3?`${(e/1e3).toFixed(2)} Kb/s`:`${e.toFixed(0)} b/s`:"0 b/s",zm=(e,t=2)=>e?e>=1e6?`${(e/1e6).toFixed(t)} Mpck/s`:e>=1e3?`${(e/1e3).toFixed(t)} Kpck/s`:`${e.toFixed(0)} pck/s`:"0 pck/s",REe=e=>`${e.toFixed(1)}%`,mG=y.memo(({cpuUsage:e=0,memoryBytes:t=0,totalCores:n=0,isLoading:r=!1})=>{const o=y.useMemo(()=>t/1048576,t);return h.jsxs("div",{className:"grid grid-cols-1 gap-2 sm:grid-cols-3",children:h.jsxs(Jn,{className:"bg-stat border-0",children:h.jsx(Dr,{className:"pb-2",children:h.jsx(Rr,{className:"flex items-center gap-2 text-sm stat stat-label",children:"CPU"})}),h.jsx(er,{children:h.jsx("div",{className:"flex items-center gap-2",children:h.jsx("span",{className:"text-xl font-semibold text-info tabular-nums",children:r?"...":REe(e)})})})}),h.jsxs(Jn,{className:"bg-stat border-0",children:h.jsx(Dr,{className:"pb-2",children:h.jsx(Rr,{className:"flex items-center gap-2 text-sm stat stat-label",children:"Memory"})}),h.jsx(er,{children:h.jsxs("div",{className:"flex items-baseline gap-2",children:h.jsx("span",{className:"text-xl font-semibold text-info tabular-nums",children:r?"...":o.toFixed(2)}),h.jsx("span",{className:"text-xs text-muted-foreground",children:"MB"})})})}),h.jsxs(Jn,{className:"bg-stat border-0",children:h.jsx(Dr,{className:"pb-2",children:h.jsx(Rr,{className:"flex items-center gap-2 text-sm stat stat-label",children:"System"})}),h.jsx(er,{children:h.jsxs("div",{className:"flex items-baseline gap-2",children:h.jsx("span",{className:"text-xl font-semibold text-info tabular-nums",children:r?"...":n}),h.jsx("span",{className:"text-xs text-muted-foreground",children:"Cores"})})})})})});mG.displayName="CpuMemoryOverview";function TEe(e=!0,t=150){constn,r=y.useState(),o,i=y.useState(0),s,a=y.useState(0),l,c=y.useState(0),{isReady:d}=rb({enabled:e}),f=y.useRef();f.current=n;const p=y.useRef(v=>{r(w=>...w.slice(-299),v);const b=v.process_cpu_usage||0,_=v.process_memory||0,x=v.nb_cores||0;i(w=>w!==b?b:w),a(w=>w!==_?_:w),c(w=>w!==x?x:w)}),g=y.useCallback(v=>{p.current(v)},);return y.useEffect(()=>{if(!e||!d){n.length>0&&r();return}let v=null,b=!0;return(async()=>{try{const x=await Mo.subscribe({type:ss.CPU_STATS,interval:t},w=>{w.data&&b&&g(w.data)});b?v=x:x()}catch{b&&r()}})(),()=>{b=!1,v&&v()}},e,d,t,g),{stats:n,isSubscribed:n.length>0,currentCPU:o,currentMemory:s,totalCores:l}}const kEe=1e4,PEe={"20s":"20s","1min":"1m","5min":"5m",unlimited:"∞"},gG=e=>{switch(e){case"20s":return 2e4;case"1min":return 6e4;case"5min":return 3e5;case"unlimited":return 1/0;default:return 6e4}},IEe=(e,t)=>{const n=gG(e);return n===1/0?kEe:Math.ceil(n/t)},vG=y.memo(({value:e,onChange:t})=>h.jsxs(BP,{children:h.jsx(WP,{asChild:!0,children:h.jsx(fs,{variant:"ghost",size:"sm",className:"px-0 py-0",children:h.jsx(ZP,{icon:h.jsx(C9,{className:"w-4 h-4 text-info"}),className:"cursor-pointer hover:opacity-80 ",children:h.jsx("span",{className:"text-sm font-medium text-info",children:PEee})})})}),h.jsx(Sb,{align:"end",className:"bg-monitor-surface",children:h.jsxs(JSe,{value:e,onValueChange:n=>t(n),children:h.jsx(Sp,{value:"20s",children:"20 seconds"}),h.jsx(Sp,{value:"1min",children:"1 minute"}),h.jsx(Sp,{value:"5min",children:"5 minutes"}),h.jsx(Sp,{value:"unlimited",children:"Unlimited"})})})}));vG.displayName="CPUHistoryBadge";const AEe=(e,t,n)=>{constr,o=y.useState(()=>localStorage.getItem(e)||t);y.useEffect(()=>{localStorage.setItem(e,r)},r,e);const i=y.useMemo(()=>gG(r),r),s=y.useMemo(()=>IEe(r,n),r,n);return{duration:r,setDuration:o,windowDuration:i,maxPoints:s}},$3=250,MEe="1min",jEe="cpu-history-duration",LEe="container mx-auto flex flex-col gap-2 p-2 h-full",OEe="contain-layout contain-style",yG=Pe.memo(({id:e})=>{constt,n=y.useState(!1),{duration:r,setDuration:o,windowDuration:i,maxPoints:s}=AEe(jEe,MEe,$3),{ref:a}=Jx({onResizeStart:()=>n(!0),onResizeEnd:()=>n(!1),debounce:16,throttle:!0}),l=a,{isSubscribed:c,currentCPU:d,currentMemory:f,totalCores:p}=TEe(!0,$3),g=y.useMemo(()=>({currentCPUPercent:d,currentMemoryPercent:0,currentMemoryProcess:f,totalCores:p,isLoading:!c}),d,f,p,c),v=y.useMemo(()=>`${LEe}${t?` ${OEe}`:""}`,t),b=y.useMemo(()=>h.jsx(vG,{value:r,onChange:o}),r,o);return h.jsx(Js,{id:e,statusBadge:b,children:h.jsxs("div",{ref:l,className:v,children:h.jsx("div",{className:"w-full flex-shrink-0",children:h.jsx(mG,{cpuUsage:g.currentCPUPercent,memoryBytes:g.currentMemoryProcess,totalCores:g.totalCores,isLoading:g.isLoading})}),h.jsx("div",{className:"w-full flex-1 min-h-0",children:h.jsx(pG,{currentCPUPercent:g.currentCPUPercent,currentMemoryBytes:g.currentMemoryProcess,isLive:!t,maxPoints:s,windowDuration:i})})})})});yG.displayName="MetricsMonitor";const Yu=()=>Mo,DEe=()=>{const e=Yu(),{isReady:t}=rb(),n,r=y.useState(null),o,i=y.useState(!0);return y.useEffect(()=>{if(!t)return;let s=!0;return(async()=>{try{const l=await e.getCommandLine();s&&(r(l),i(!1))}catch(l){console.error("useCommandLine Error fetching command line:",l),s&&i(!1)}})(),()=>{s=!1}},e,t),{commandLine:n,isLoading:o}},xG="gpac_monitor_layout_v1",bG="gpac_monitor_last_layout_v1";function FEe(){if(typeof window>"u")return null;try{const e=window.localStorage.getItem(xG);if(!e)return{};const t=JSON.parse(e);return!t||typeof t!="object"?{}:t}catch{return{}}}function wG(e){if(!(typeof window>"u"))try{window.localStorage.setItem(xG,JSON.stringify(e))}catch{}}function $Ee(){if(typeof window>"u")return null;try{return window.localStorage.getItem(bG)}catch{return null}}function SG(e){if(!(typeof window>"u")&&e)try{window.localStorage.setItem(bG,e)}catch{}}const zEe=()=>{const e=Hn(),t=Xe($0e),n=Xe(z0e),r=l=>{e(XI(l))},o=l=>{e(bK(l))},i=l=>{e(wK(l))},s=()=>Object.keys(t),a=l=>tl;return y.useEffect(()=>{wG(t)},t),y.useEffect(()=>{SG(n)},n),{savedLayouts:t,currentLayout:n,save:r,load:o,remove:i,getLayoutNames:s,getLayout:a}};function hI(){const e=Hn(),n=Xe(T9).some(r=>r.type===_n.LOGS);return y.useCallback(r=>{e(Aye(r)),n||e(xK(_n.LOGS))},e,n)}const HEe={sidebarContent:null,isSidebarOpen:!1},_G=Sc({name:"layout",initialState:HEe,reducers:{setSidebarContent:(e,t)=>{e.sidebarContent=t.payload,t.payload!==null&&(e.isSidebarOpen=!0)},toggleSidebar:e=>{e.isSidebarOpen=!e.isSidebarOpen},closeSidebar:e=>{e.sidebarContent=null,e.isSidebarOpen=!1}}}),{setSidebarContent:z3,toggleSidebar:BEe,closeSidebar:pI}=_G.actions,WEe=_G.reducer,Mb=()=>{const e=Hn(),t=Xe(s=>s.layout.sidebarContent),n=t!==null,r=y.useCallback(s=>{e(z3({type:"filter-args",filterIdx:s.idx,filterName:s.name}))},e),o=y.useCallback(s=>{e(z3({type:"pid-props",filterIdx:s.filterIdx,ipidIdx:s.ipidIdx}))},e),i=y.useCallback(()=>{e(pI())},e);return{isOpen:n,sidebarContent:t,openFilterArgs:r,openPIDProps:o,closeSidebar:i}},UEe=()=>{const e=Yu(),t,n=y.useState(()=>e.filterSubscriptions.getSnapshot().subscribedFilterIdxs);return y.useEffect(()=>e.filterSubscriptions.subscribe(o=>{n(o.subscribedFilterIdxs)},{debounce:150,immediate:!0}),e),t};function mI(e){throw new Error('Could not dynamically require "'+e+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}function qEe(){this.__data__=,this.size=0}var GEe=qEe,OS,H3;function ah(){if(H3)return OS;H3=1;function e(t,n){return t===n||t!==t&&n!==n}return OS=e,OS}var VEe=ah();function YEe(e,t){for(var n=e.length;n--;)if(VEe(en0,t))return n;return-1}var jb=YEe,KEe=jb,XEe=Array.prototype,ZEe=XEe.splice;function QEe(e){var t=this.__data__,n=KEe(t,e);if(n<0)return!1;var r=t.length-1;return n==r?t.pop():ZEe.call(t,n,1),--this.size,!0}var JEe=QEe,e2e=jb;function t2e(e){var t=this.__data__,n=e2e(t,e);return n<0?void 0:tn1}var n2e=t2e,r2e=jb;function o2e(e){return r2e(this.__data__,e)>-1}var i2e=o2e,s2e=jb;function a2e(e,t){var n=this.__data__,r=s2e(n,e);return r<0?(++this.size,n.push(e,t)):nr1=t,this}var l2e=a2e,c2e=GEe,u2e=JEe,d2e=n2e,f2e=i2e,h2e=l2e;function lh(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){var r=et;this.set(r0,r1)}}lh.prototype.clear=c2e;lh.prototype.delete=u2e;lh.prototype.get=d2e;lh.prototype.has=f2e;lh.prototype.set=h2e;var Lb=lh,DS,B3;function p2e(){if(B3)return DS;B3=1;var e=Lb;function t(){this.__data__=new e,this.size=0}return DS=t,DS}var FS,W3;function m2e(){if(W3)return FS;W3=1;function e(t){var n=this.__data__,r=n.delete(t);return this.size=n.size,r}return FS=e,FS}var $S,U3;function g2e(){if(U3)return $S;U3=1;function e(t){return this.__data__.get(t)}return $S=e,$S}var zS,q3;function v2e(){if(q3)return zS;q3=1;function e(t){return this.__data__.has(t)}return zS=e,zS}var HS,G3;function CG(){if(G3)return HS;G3=1;var e=typeof rs=="object"&&rs&&rs.Object===Object&&rs;return HS=e,HS}var BS,V3;function bs(){if(V3)return BS;V3=1;var e=CG(),t=typeof self=="object"&&self&&self.Object===Object&&self,n=e||t||Function("return this")();return BS=n,BS}var WS,Y3;function ch(){if(Y3)return WS;Y3=1;var e=bs(),t=e.Symbol;return WS=t,WS}var US,K3;function y2e(){if(K3)return US;K3=1;var e=ch(),t=Object.prototype,n=t.hasOwnProperty,r=t.toString,o=e?e.toStringTag:void 0;function i(s){var a=n.call(s,o),l=so;try{so=void 0;var c=!0}catch{}var d=r.call(s);return c&&(a?so=l:delete so),d}return US=i,US}var qS,X3;function x2e(){if(X3)return qS;X3=1;var e=Object.prototype,t=e.toString;function n(r){return t.call(r)}return qS=n,qS}var GS,Z3;function Ku(){if(Z3)return GS;Z3=1;var e=ch(),t=y2e(),n=x2e(),r="object Null",o="object Undefined",i=e?e.toStringTag:void 0;function s(a){return a==null?a===void 0?o:r:i&&i in Object(a)?t(a):n(a)}return GS=s,GS}var VS,Q3;function Di(){if(Q3)return VS;Q3=1;function e(t){var n=typeof t;return t!=null&&(n=="object"||n=="function")}return VS=e,VS}var YS,J3;function pg(){if(J3)return YS;J3=1;var e=Ku(),t=Di(),n="object AsyncFunction",r="object Function",o="object GeneratorFunction",i="object Proxy";function s(a){if(!t(a))return!1;var l=e(a);return l==r||l==o||l==n||l==i}return YS=s,YS}var b2e=bs(),w2e=b2e"__core-js_shared__",S2e=w2e,KS=S2e,e4=function(){var e=/^.+$/.exec(KS&&KS.keys&&KS.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}();function _2e(e){return!!e4&&e4 in e}var C2e=_2e,XS,t4;function EG(){if(t4)return XS;t4=1;var e=Function.prototype,t=e.toString;function n(r){if(r!=null){try{return t.call(r)}catch{}try{return r+""}catch{}}return""}return XS=n,XS}var E2e=pg(),N2e=C2e,R2e=Di(),T2e=EG(),k2e=/\\^$.*+?()\{}|/g,P2e=/^\object .+?Constructor\$/,I2e=Function.prototype,A2e=Object.prototype,M2e=I2e.toString,j2e=A2e.hasOwnProperty,L2e=RegExp("^"+M2e.call(j2e).replace(k2e,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\)/g,"$1.*?")+"$");function O2e(e){if(!R2e(e)||N2e(e))return!1;var t=E2e(e)?L2e:P2e;return t.test(T2e(e))}var D2e=O2e;function F2e(e,t){return e==null?void 0:et}var $2e=F2e,z2e=D2e,H2e=$2e;function B2e(e,t){var n=H2e(e,t);return z2e(n)?n:void 0}var Xu=B2e,W2e=Xu,U2e=bs(),q2e=W2e(U2e,"Map"),gI=q2e,G2e=Xu,V2e=G2e(Object,"create"),Ob=V2e,n4=Ob;function Y2e(){this.__data__=n4?n4(null):{},this.size=0}var K2e=Y2e;function X2e(e){var t=this.has(e)&&delete this.__data__e;return this.size-=t?1:0,t}var Z2e=X2e,Q2e=Ob,J2e="__lodash_hash_undefined__",eNe=Object.prototype,tNe=eNe.hasOwnProperty;function nNe(e){var t=this.__data__;if(Q2e){var n=te;return n===J2e?void 0:n}return tNe.call(t,e)?te:void 0}var rNe=nNe,oNe=Ob,iNe=Object.prototype,sNe=iNe.hasOwnProperty;function aNe(e){var t=this.__data__;return oNe?te!==void 0:sNe.call(t,e)}var lNe=aNe,cNe=Ob,uNe="__lodash_hash_undefined__";function dNe(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,ne=cNe&&t===void 0?uNe:t,this}var fNe=dNe,hNe=K2e,pNe=Z2e,mNe=rNe,gNe=lNe,vNe=fNe;function uh(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){var r=et;this.set(r0,r1)}}uh.prototype.clear=hNe;uh.prototype.delete=pNe;uh.prototype.get=mNe;uh.prototype.has=gNe;uh.prototype.set=vNe;var yNe=uh,r4=yNe,xNe=Lb,bNe=gI;function wNe(){this.size=0,this.__data__={hash:new r4,map:new(bNe||xNe),string:new r4}}var SNe=wNe;function _Ne(e){var t=typeof e;return t=="string"||t=="number"||t=="symbol"||t=="boolean"?e!=="__proto__":e===null}var CNe=_Ne,ENe=CNe;function NNe(e,t){var n=e.__data__;return ENe(t)?ntypeof t=="string"?"string":"hash":n.map}var Db=NNe,RNe=Db;function TNe(e){var t=RNe(this,e).delete(e);return this.size-=t?1:0,t}var kNe=TNe,PNe=Db;function INe(e){return PNe(this,e).get(e)}var ANe=INe,MNe=Db;function jNe(e){return MNe(this,e).has(e)}var LNe=jNe,ONe=Db;function DNe(e,t){var n=ONe(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this}var FNe=DNe,$Ne=SNe,zNe=kNe,HNe=ANe,BNe=LNe,WNe=FNe;function dh(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){var r=et;this.set(r0,r1)}}dh.prototype.clear=$Ne;dh.prototype.delete=zNe;dh.prototype.get=HNe;dh.prototype.has=BNe;dh.prototype.set=WNe;var vI=dh,ZS,o4;function UNe(){if(o4)return ZS;o4=1;var e=Lb,t=gI,n=vI,r=200;function o(i,s){var a=this.__data__;if(a instanceof e){var l=a.__data__;if(!t||l.length<r-1)return l.push(i,s),this.size=++a.size,this;a=this.__data__=new n(l)}return a.set(i,s),this.size=a.size,this}return ZS=o,ZS}var QS,i4;function Fb(){if(i4)return QS;i4=1;var e=Lb,t=p2e(),n=m2e(),r=g2e(),o=v2e(),i=UNe();function s(a){var l=this.__data__=new e(a);this.size=l.size}return s.prototype.clear=t,s.prototype.delete=n,s.prototype.get=r,s.prototype.has=o,s.prototype.set=i,QS=s,QS}var JS,s4;function yI(){if(s4)return JS;s4=1;function e(t,n){for(var r=-1,o=t==null?0:t.length;++r<o&&n(tr,r,t)!==!1;);return t}return JS=e,JS}var e_,a4;function NG(){if(a4)return e_;a4=1;var e=Xu,t=function(){try{var n=e(Object,"defineProperty");return n({},"",{}),n}catch{}}();return e_=t,e_}var t_,l4;function $b(){if(l4)return t_;l4=1;var e=NG();function t(n,r,o){r=="__proto__"&&e?e(n,r,{configurable:!0,enumerable:!0,value:o,writable:!0}):nr=o}return t_=t,t_}var n_,c4;function zb(){if(c4)return n_;c4=1;var e=$b(),t=ah(),n=Object.prototype,r=n.hasOwnProperty;function o(i,s,a){var l=is;(!(r.call(i,s)&&t(l,a))||a===void 0&&!(s in i))&&e(i,s,a)}return n_=o,n_}var r_,u4;function mg(){if(u4)return r_;u4=1;var e=zb(),t=$b();function n(r,o,i,s){var a=!i;i||(i={});for(var l=-1,c=o.length;++l<c;){var d=ol,f=s?s(id,rd,d,i,r):void 0;f===void 0&&(f=rd),a?t(i,d,f):e(i,d,f)}return i}return r_=n,r_}var o_,d4;function qNe(){if(d4)return o_;d4=1;function e(t,n){for(var r=-1,o=Array(t);++r<t;)or=n(r);return o}return o_=e,o_}var i_,f4;function da(){if(f4)return i_;f4=1;function e(t){return t!=null&&typeof t=="object"}return i_=e,i_}var s_,h4;function GNe(){if(h4)return s_;h4=1;var e=Ku(),t=da(),n="object Arguments";function r(o){return t(o)&&e(o)==n}return s_=r,s_}var a_,p4;function gg(){if(p4)return a_;p4=1;var e=GNe(),t=da(),n=Object.prototype,r=n.hasOwnProperty,o=n.propertyIsEnumerable,i=e(function(){return arguments}())?e:function(s){return t(s)&&r.call(s,"callee")&&!o.call(s,"callee")};return a_=i,a_}var l_,m4;function ur(){if(m4)return l_;m4=1;var e=Array.isArray;return l_=e,l_}var Np={exports:{}},c_,g4;function VNe(){if(g4)return c_;g4=1;function e(){return!1}return c_=e,c_}Np.exports;var v4;function fh(){return v4||(v4=1,function(e,t){var n=bs(),r=VNe(),o=t&&!t.nodeType&&t,i=o&&!0&&e&&!e.nodeType&&e,s=i&&i.exports===o,a=s?n.Buffer:void 0,l=a?a.isBuffer:void 0,c=l||r;e.exports=c}(Np,Np.exports)),Np.exports}var u_,y4;function Hb(){if(y4)return u_;y4=1;var e=9007199254740991,t=/^(?:0|1-9\d*)$/;function n(r,o){var i=typeof r;return o=o??e,!!o&&(i=="number"||i!="symbol"&&t.test(r))&&r>-1&&r%1==0&&r<o}return u_=n,u_}var d_,x4;function xI(){if(x4)return d_;x4=1;var e=9007199254740991;function t(n){return typeof n=="number"&&n>-1&&n%1==0&&n<=e}return d_=t,d_}var f_,b4;function YNe(){if(b4)return f_;b4=1;var e=Ku(),t=xI(),n=da(),r="object Arguments",o="object Array",i="object Boolean",s="object Date",a="object Error",l="object Function",c="object Map",d="object Number",f="object Object",p="object RegExp",g="object Set",v="object String",b="object WeakMap",_="object ArrayBuffer",x="object DataView",w="object Float32Array",C="object Float64Array",E="object Int8Array",R="object Int16Array",P="object Int32Array",N="object Uint8Array",k="object Uint8ClampedArray",I="object Uint16Array",O="object Uint32Array",j={};jw=jC=jE=jR=jP=jN=jk=jI=jO=!0,jr=jo=j_=ji=jx=js=ja=jl=jc=jd=jf=jp=jg=jv=jb=!1;function H(q){return n(q)&&t(q.length)&&!!je(q)}return f_=H,f_}var h_,w4;function Bb(){if(w4)return h_;w4=1;function e(t){return function(n){return t(n)}}return h_=e,h_}var Rp={exports:{}};Rp.exports;var S4;function bI(){return S4||(S4=1,function(e,t){var n=CG(),r=t&&!t.nodeType&&t,o=r&&!0&&e&&!e.nodeType&&e,i=o&&o.exports===r,s=i&&n.process,a=function(){try{var l=o&&o.require&&o.require("util").types;return l||s&&s.binding&&s.binding("util")}catch{}}();e.exports=a}(Rp,Rp.exports)),Rp.exports}var p_,_4;function vg(){if(_4)return p_;_4=1;var e=YNe(),t=Bb(),n=bI(),r=n&&n.isTypedArray,o=r?t(r):e;return p_=o,p_}var m_,C4;function RG(){if(C4)return m_;C4=1;var e=qNe(),t=gg(),n=ur(),r=fh(),o=Hb(),i=vg(),s=Object.prototype,a=s.hasOwnProperty;function l(c,d){var f=n(c),p=!f&&t(c),g=!f&&!p&&r(c),v=!f&&!p&&!g&&i(c),b=f||p||g||v,_=b?e(c.length,String):,x=_.length;for(var w in c)(d||a.call(c,w))&&!(b&&(w=="length"||g&&(w=="offset"||w=="parent")||v&&(w=="buffer"||w=="byteLength"||w=="byteOffset")||o(w,x)))&&_.push(w);return _}return m_=l,m_}var g_,E4;function Wb(){if(E4)return g_;E4=1;var e=Object.prototype;function t(n){var r=n&&n.constructor,o=typeof r=="function"&&r.prototype||e;return n===o}return g_=t,g_}var v_,N4;function TG(){if(N4)return v_;N4=1;function e(t,n){return function(r){return t(n(r))}}return v_=e,v_}var y_,R4;function KNe(){if(R4)return y_;R4=1;var e=TG(),t=e(Object.keys,Object);return y_=t,y_}var x_,T4;function wI(){if(T4)return x_;T4=1;var e=Wb(),t=KNe(),n=Object.prototype,r=n.hasOwnProperty;function o(i){if(!e(i))return t(i);var s=;for(var a in Object(i))r.call(i,a)&&a!="constructor"&&s.push(a);return s}return x_=o,x_}var b_,k4;function cl(){if(k4)return b_;k4=1;var e=pg(),t=xI();function n(r){return r!=null&&t(r.length)&&!e(r)}return b_=n,b_}var w_,P4;function Tc(){if(P4)return w_;P4=1;var e=RG(),t=wI(),n=cl();function r(o){return n(o)?e(o):t(o)}return w_=r,w_}var S_,I4;function XNe(){if(I4)return S_;I4=1;var e=mg(),t=Tc();function n(r,o){return r&&e(o,t(o),r)}return S_=n,S_}var __,A4;function ZNe(){if(A4)return __;A4=1;function e(t){var n=;if(t!=null)for(var r in Object(t))n.push(r);return n}return __=e,__}var C_,M4;function QNe(){if(M4)return C_;M4=1;var e=Di(),t=Wb(),n=ZNe(),r=Object.prototype,o=r.hasOwnProperty;function i(s){if(!e(s))return n(s);var a=t(s),l=;for(var c in s)c=="constructor"&&(a||!o.call(s,c))||l.push(c);return l}return C_=i,C_}var E_,j4;function Zu(){if(j4)return E_;j4=1;var e=RG(),t=QNe(),n=cl();function r(o){return n(o)?e(o,!0):t(o)}return E_=r,E_}var N_,L4;function JNe(){if(L4)return N_;L4=1;var e=mg(),t=Zu();function n(r,o){return r&&e(o,t(o),r)}return N_=n,N_}var Tp={exports:{}};Tp.exports;var O4;function kG(){return O4||(O4=1,function(e,t){var n=bs(),r=t&&!t.nodeType&&t,o=r&&!0&&e&&!e.nodeType&&e,i=o&&o.exports===r,s=i?n.Buffer:void 0,a=s?s.allocUnsafe:void 0;function l(c,d){if(d)return c.slice();var f=c.length,p=a?a(f):new c.constructor(f);return c.copy(p),p}e.exports=l}(Tp,Tp.exports)),Tp.exports}var R_,D4;function PG(){if(D4)return R_;D4=1;function e(t,n){var r=-1,o=t.length;for(n||(n=Array(o));++r<o;)nr=tr;return n}return R_=e,R_}var T_,F4;function IG(){if(F4)return T_;F4=1;function e(t,n){for(var r=-1,o=t==null?0:t.length,i=0,s=;++r<o;){var a=tr;n(a,r,t)&&(si++=a)}return s}return T_=e,T_}var k_,$4;function AG(){if($4)return k_;$4=1;function e(){return}return k_=e,k_}var P_,z4;function SI(){if(z4)return P_;z4=1;var e=IG(),t=AG(),n=Object.prototype,r=n.propertyIsEnumerable,o=Object.getOwnPropertySymbols,i=o?function(s){return s==null?:(s=Object(s),e(o(s),function(a){return r.call(s,a)}))}:t;return P_=i,P_}var I_,H4;function eRe(){if(H4)return I_;H4=1;var e=mg(),t=SI();function n(r,o){return e(r,t(r),o)}return I_=n,I_}var A_,B4;function _I(){if(B4)return A_;B4=1;function e(t,n){for(var r=-1,o=n.length,i=t.length;++r<o;)ti+r=nr;return t}return A_=e,A_}var M_,W4;function Ub(){if(W4)return M_;W4=1;var e=TG(),t=e(Object.getPrototypeOf,Object);return M_=t,M_}var j_,U4;function MG(){if(U4)return j_;U4=1;var e=_I(),t=Ub(),n=SI(),r=AG(),o=Object.getOwnPropertySymbols,i=o?function(s){for(var a=;s;)e(a,n(s)),s=t(s);return a}:r;return j_=i,j_}var L_,q4;function tRe(){if(q4)return L_;q4=1;var e=mg(),t=MG();function n(r,o){return e(r,t(r),o)}return L_=n,L_}var O_,G4;function jG(){if(G4)return O_;G4=1;var e=_I(),t=ur();function n(r,o,i){var s=o(r);return t(r)?s:e(s,i(r))}return O_=n,O_}var D_,V4;function LG(){if(V4)return D_;V4=1;var e=jG(),t=SI(),n=Tc();function r(o){return e(o,n,t)}return D_=r,D_}var F_,Y4;function nRe(){if(Y4)return F_;Y4=1;var e=jG(),t=MG(),n=Zu();function r(o){return e(o,n,t)}return F_=r,F_}var $_,K4;function rRe(){if(K4)return $_;K4=1;var e=Xu,t=bs(),n=e(t,"DataView");return $_=n,$_}var z_,X4;function oRe(){if(X4)return z_;X4=1;var e=Xu,t=bs(),n=e(t,"Promise");return z_=n,z_}var H_,Z4;function OG(){if(Z4)return H_;Z4=1;var e=Xu,t=bs(),n=e(t,"Set");return H_=n,H_}var B_,Q4;function iRe(){if(Q4)return B_;Q4=1;var e=Xu,t=bs(),n=e(t,"WeakMap");return B_=n,B_}var W_,J4;function hh(){if(J4)return W_;J4=1;var e=rRe(),t=gI,n=oRe(),r=OG(),o=iRe(),i=Ku(),s=EG(),a="object Map",l="object Object",c="object Promise",d="object Set",f="object WeakMap",p="object DataView",g=s(e),v=s(t),b=s(n),_=s(r),x=s(o),w=i;return(e&&w(new e(new ArrayBuffer(1)))!=p||t&&w(new t)!=a||n&&w(n.resolve())!=c||r&&w(new r)!=d||o&&w(new o)!=f)&&(w=function(C){var E=i(C),R=E==l?C.constructor:void 0,P=R?s(R):"";if(P)switch(P){case g:return p;case v:return a;case b:return c;case _:return d;case x:return f}return E}),W_=w,W_}var U_,e5;function sRe(){if(e5)return U_;e5=1;var e=Object.prototype,t=e.hasOwnProperty;function n(r){var o=r.length,i=new r.constructor(o);return o&&typeof r0=="string"&&t.call(r,"index")&&(i.index=r.index,i.input=r.input),i}return U_=n,U_}var q_,t5;function DG(){if(t5)return q_;t5=1;var e=bs(),t=e.Uint8Array;return q_=t,q_}var G_,n5;function CI(){if(n5)return G_;n5=1;var e=DG();function t(n){var r=new n.constructor(n.byteLength);return new e(r).set(new e(n)),r}return G_=t,G_}var V_,r5;function aRe(){if(r5)return V_;r5=1;var e=CI();function t(n,r){var o=r?e(n.buffer):n.buffer;return new n.constructor(o,n.byteOffset,n.byteLength)}return V_=t,V_}var Y_,o5;function lRe(){if(o5)return Y_;o5=1;var e=/\w*$/;function t(n){var r=new n.constructor(n.source,e.exec(n));return r.lastIndex=n.lastIndex,r}return Y_=t,Y_}var K_,i5;function cRe(){if(i5)return K_;i5=1;var e=ch(),t=e?e.prototype:void 0,n=t?t.valueOf:void 0;function r(o){return n?Object(n.call(o)):{}}return K_=r,K_}var X_,s5;function FG(){if(s5)return X_;s5=1;var e=CI();function t(n,r){var o=r?e(n.buffer):n.buffer;return new n.constructor(o,n.byteOffset,n.length)}return X_=t,X_}var Z_,a5;function uRe(){if(a5)return Z_;a5=1;var e=CI(),t=aRe(),n=lRe(),r=cRe(),o=FG(),i="object Boolean",s="object Date",a="object Map",l="object Number",c="object RegExp",d="object Set",f="object String",p="object Symbol",g="object ArrayBuffer",v="object DataView",b="object Float32Array",_="object Float64Array",x="object Int8Array",w="object Int16Array",C="object Int32Array",E="object Uint8Array",R="object Uint8ClampedArray",P="object Uint16Array",N="object Uint32Array";function k(I,O,j){var H=I.constructor;switch(O){case g:return e(I);case i:case s:return new H(+I);case v:return t(I,j);case b:case _:case x:case w:case C:case E:case R:case P:case N:return o(I,j);case a:return new H;case l:case f:return new H(I);case c:return n(I);case d:return new H;case p:return r(I)}}return Z_=k,Z_}var Q_,l5;function $G(){if(l5)return Q_;l5=1;var e=Di(),t=Object.create,n=function(){function r(){}return function(o){if(!e(o))return{};if(t)return t(o);r.prototype=o;var i=new r;return r.prototype=void 0,i}}();return Q_=n,Q_}var J_,c5;function zG(){if(c5)return J_;c5=1;var e=$G(),t=Ub(),n=Wb();function r(o){return typeof o.constructor=="function"&&!n(o)?e(t(o)):{}}return J_=r,J_}var eC,u5;function dRe(){if(u5)return eC;u5=1;var e=hh(),t=da(),n="object Map";function r(o){return t(o)&&e(o)==n}return eC=r,eC}var tC,d5;function fRe(){if(d5)return tC;d5=1;var e=dRe(),t=Bb(),n=bI(),r=n&&n.isMap,o=r?t(r):e;return tC=o,tC}var nC,f5;function hRe(){if(f5)return nC;f5=1;var e=hh(),t=da(),n="object Set";function r(o){return t(o)&&e(o)==n}return nC=r,nC}var rC,h5;function pRe(){if(h5)return rC;h5=1;var e=hRe(),t=Bb(),n=bI(),r=n&&n.isSet,o=r?t(r):e;return rC=o,rC}var oC,p5;function HG(){if(p5)return oC;p5=1;var e=Fb(),t=yI(),n=zb(),r=XNe(),o=JNe(),i=kG(),s=PG(),a=eRe(),l=tRe(),c=LG(),d=nRe(),f=hh(),p=sRe(),g=uRe(),v=zG(),b=ur(),_=fh(),x=fRe(),w=Di(),C=pRe(),E=Tc(),R=Zu(),P=1,N=2,k=4,I="object Arguments",O="object Array",j="object Boolean",H="object Date",q="object Error",M="object Function",B="object GeneratorFunction",$="object Map",W="object Number",G="object Object",z="object RegExp",K="object Set",Q="object String",re="object Symbol",ae="object WeakMap",de="object ArrayBuffer",Ne="object DataView",ye="object Float32Array",fe="object Float64Array",Z="object Int8Array",oe="object Int16Array",ce="object Int32Array",xe="object Uint8Array",ge="object Uint8ClampedArray",pe="object Uint16Array",he="object Uint32Array",we={};weI=weO=wede=weNe=wej=weH=weye=wefe=weZ=weoe=wece=we$=weW=weG=wez=weK=weQ=were=wexe=wege=wepe=wehe=!0,weq=weM=weae=!1;function Ie(Ce,Me,ze,Ye,Ht,Ft){var rt,Ue=Me&P,Te=Me&N,bt=Me&k;if(ze&&(rt=Ht?ze(Ce,Ye,Ht,Ft):ze(Ce)),rt!==void 0)return rt;if(!w(Ce))return Ce;var jt=b(Ce);if(jt){if(rt=p(Ce),!Ue)return s(Ce,rt)}else{var vn=f(Ce),dr=vn==M||vn==B;if(_(Ce))return i(Ce,Ue);if(vn==G||vn==I||dr&&!Ht){if(rt=Te||dr?{}:v(Ce),!Ue)return Te?l(Ce,o(rt,Ce)):a(Ce,r(rt,Ce))}else{if(!wevn)return Ht?Ce:{};rt=g(Ce,vn,Ue)}}Ft||(Ft=new e);var On=Ft.get(Ce);if(On)return On;Ft.set(Ce,rt),C(Ce)?Ce.forEach(function(fn){rt.add(Ie(fn,Me,ze,fn,Ce,Ft))}):x(Ce)&&Ce.forEach(function(fn,sn){rt.set(sn,Ie(fn,Me,ze,sn,Ce,Ft))});var Sr=bt?Te?d:c:Te?R:E,kn=jt?void 0:Sr(Ce);return t(kn||Ce,function(fn,sn){kn&&(sn=fn,fn=Cesn),n(rt,sn,Ie(fn,Me,ze,sn,Ce,Ft))}),rt}return oC=Ie,oC}var iC,m5;function mRe(){if(m5)return iC;m5=1;var e=HG(),t=4;function n(r){return e(r,t)}return iC=n,iC}var sC,g5;function EI(){if(g5)return sC;g5=1;function e(t){return function(){return t}}return sC=e,sC}var aC,v5;function gRe(){if(v5)return aC;v5=1;function e(t){return function(n,r,o){for(var i=-1,s=Object(n),a=o(n),l=a.length;l--;){var c=at?l:++i;if(r(sc,c,s)===!1)break}return n}}return aC=e,aC}var lC,y5;function NI(){if(y5)return lC;y5=1;var e=gRe(),t=e();return lC=t,lC}var cC,x5;function RI(){if(x5)return cC;x5=1;var e=NI(),t=Tc();function n(r,o){return r&&e(r,o,t)}return cC=n,cC}var uC,b5;function vRe(){if(b5)return uC;b5=1;var e=cl();function t(n,r){return function(o,i){if(o==null)return o;if(!e(o))return n(o,i);for(var s=o.length,a=r?s:-1,l=Object(o);(r?a--:++a<s)&&i(la,a,l)!==!1;);return o}}return uC=t,uC}var dC,w5;function qb(){if(w5)return dC;w5=1;var e=RI(),t=vRe(),n=t(e);return dC=n,dC}var fC,S5;function Qu(){if(S5)return fC;S5=1;function e(t){return t}return fC=e,fC}var hC,_5;function BG(){if(_5)return hC;_5=1;var e=Qu();function t(n){return typeof n=="function"?n:e}return hC=t,hC}var pC,C5;function WG(){if(C5)return pC;C5=1;var e=yI(),t=qb(),n=BG(),r=ur();function o(i,s){var a=r(i)?e:t;return a(i,n(s))}return pC=o,pC}var mC,E5;function UG(){return E5||(E5=1,mC=WG()),mC}var gC,N5;function yRe(){if(N5)return gC;N5=1;var e=qb();function t(n,r){var o=;return e(n,function(i,s,a){r(i,s,a)&&o.push(i)}),o}return gC=t,gC}var vC,R5;function xRe(){if(R5)return vC;R5=1;var e="__lodash_hash_undefined__";function t(n){return this.__data__.set(n,e),this}return vC=t,vC}var yC,T5;function bRe(){if(T5)return yC;T5=1;function e(t){return this.__data__.has(t)}return yC=e,yC}var xC,k5;function qG(){if(k5)return xC;k5=1;var e=vI,t=xRe(),n=bRe();function r(o){var i=-1,s=o==null?0:o.length;for(this.__data__=new e;++i<s;)this.add(oi)}return r.prototype.add=r.prototype.push=t,r.prototype.has=n,xC=r,xC}var bC,P5;function wRe(){if(P5)return bC;P5=1;function e(t,n){for(var r=-1,o=t==null?0:t.length;++r<o;)if(n(tr,r,t))return!0;return!1}return bC=e,bC}var wC,I5;function GG(){if(I5)return wC;I5=1;function e(t,n){return t.has(n)}return wC=e,wC}var SC,A5;function VG(){if(A5)return SC;A5=1;var e=qG(),t=wRe(),n=GG(),r=1,o=2;function i(s,a,l,c,d,f){var p=l&r,g=s.length,v=a.length;if(g!=v&&!(p&&v>g))return!1;var b=f.get(s),_=f.get(a);if(b&&_)return b==a&&_==s;var x=-1,w=!0,C=l&o?new e:void 0;for(f.set(s,a),f.set(a,s);++x<g;){var E=sx,R=ax;if(c)var P=p?c(R,E,x,a,s,f):c(E,R,x,s,a,f);if(P!==void 0){if(P)continue;w=!1;break}if(C){if(!t(a,function(N,k){if(!n(C,k)&&(E===N||d(E,N,l,c,f)))return C.push(k)})){w=!1;break}}else if(!(E===R||d(E,R,l,c,f))){w=!1;break}}return f.delete(s),f.delete(a),w}return SC=i,SC}var _C,M5;function SRe(){if(M5)return _C;M5=1;function e(t){var n=-1,r=Array(t.size);return t.forEach(function(o,i){r++n=i,o}),r}return _C=e,_C}var CC,j5;function TI(){if(j5)return CC;j5=1;function e(t){var n=-1,r=Array(t.size);return t.forEach(function(o){r++n=o}),r}return CC=e,CC}var EC,L5;function _Re(){if(L5)return EC;L5=1;var e=ch(),t=DG(),n=ah(),r=VG(),o=SRe(),i=TI(),s=1,a=2,l="object Boolean",c="object Date",d="object Error",f="object Map",p="object Number",g="object RegExp",v="object Set",b="object String",_="object Symbol",x="object ArrayBuffer",w="object DataView",C=e?e.prototype:void 0,E=C?C.valueOf:void 0;function R(P,N,k,I,O,j,H){switch(k){case w:if(P.byteLength!=N.byteLength||P.byteOffset!=N.byteOffset)return!1;P=P.buffer,N=N.buffer;case x:return!(P.byteLength!=N.byteLength||!j(new t(P),new t(N)));case l:case c:case p:return n(+P,+N);case d:return P.name==N.name&&P.message==N.message;case g:case b:return P==N+"";case f:var q=o;case v:var M=I&s;if(q||(q=i),P.size!=N.size&&!M)return!1;var B=H.get(P);if(B)return B==N;I|=a,H.set(P,N);var $=r(q(P),q(N),I,O,j,H);return H.delete(P),$;case _:if(E)return E.call(P)==E.call(N)}return!1}return EC=R,EC}var NC,O5;function CRe(){if(O5)return NC;O5=1;var e=LG(),t=1,n=Object.prototype,r=n.hasOwnProperty;function o(i,s,a,l,c,d){var f=a&t,p=e(i),g=p.length,v=e(s),b=v.length;if(g!=b&&!f)return!1;for(var _=g;_--;){var x=p_;if(!(f?x in s:r.call(s,x)))return!1}var w=d.get(i),C=d.get(s);if(w&&C)return w==s&&C==i;var E=!0;d.set(i,s),d.set(s,i);for(var R=f;++_<g;){x=p_;var P=ix,N=sx;if(l)var k=f?l(N,P,x,s,i,d):l(P,N,x,i,s,d);if(!(k===void 0?P===N||c(P,N,a,l,d):k)){E=!1;break}R||(R=x=="constructor")}if(E&&!R){var I=i.constructor,O=s.constructor;I!=O&&"constructor"in i&&"constructor"in s&&!(typeof I=="function"&&I instanceof I&&typeof O=="function"&&O instanceof O)&&(E=!1)}return d.delete(i),d.delete(s),E}return NC=o,NC}var RC,D5;function ERe(){if(D5)return RC;D5=1;var e=Fb(),t=VG(),n=_Re(),r=CRe(),o=hh(),i=ur(),s=fh(),a=vg(),l=1,c="object Arguments",d="object Array",f="object Object",p=Object.prototype,g=p.hasOwnProperty;function v(b,_,x,w,C,E){var R=i(b),P=i(_),N=R?d:o(b),k=P?d:o(_);N=N==c?f:N,k=k==c?f:k;var I=N==f,O=k==f,j=N==k;if(j&&s(b)){if(!s(_))return!1;R=!0,I=!1}if(j&&!I)return E||(E=new e),R||a(b)?t(b,_,x,w,C,E):n(b,_,N,x,w,C,E);if(!(x&l)){var H=I&&g.call(b,"__wrapped__"),q=O&&g.call(_,"__wrapped__");if(H||q){var M=H?b.value():b,B=q?_.value():_;return E||(E=new e),C(M,B,x,w,E)}}return j?(E||(E=new e),r(b,_,x,w,C,E)):!1}return RC=v,RC}var TC,F5;function YG(){if(F5)return TC;F5=1;var e=ERe(),t=da();function n(r,o,i,s,a){return r===o?!0:r==null||o==null||!t(r)&&!t(o)?r!==r&&o!==o:e(r,o,i,s,n,a)}return TC=n,TC}var kC,$5;function NRe(){if($5)return kC;$5=1;var e=Fb(),t=YG(),n=1,r=2;function o(i,s,a,l){var c=a.length,d=c,f=!l;if(i==null)return!d;for(i=Object(i);c--;){var p=ac;if(f&&p2?p1!==ip0:!(p0in i))return!1}for(;++c<d;){p=ac;var g=p0,v=ig,b=p1;if(f&&p2){if(v===void 0&&!(g in i))return!1}else{var _=new e;if(l)var x=l(v,b,g,i,s,_);if(!(x===void 0?t(b,v,n|r,l,_):x))return!1}}return!0}return kC=o,kC}var PC,z5;function KG(){if(z5)return PC;z5=1;var e=Di();function t(n){return n===n&&!e(n)}return PC=t,PC}var IC,H5;function RRe(){if(H5)return IC;H5=1;var e=KG(),t=Tc();function n(r){for(var o=t(r),i=o.length;i--;){var s=oi,a=rs;oi=s,a,e(a)}return o}return IC=n,IC}var AC,B5;function XG(){if(B5)return AC;B5=1;function e(t,n){return function(r){return r==null?!1:rt===n&&(n!==void 0||t in Object(r))}}return AC=e,AC}var MC,W5;function TRe(){if(W5)return MC;W5=1;var e=NRe(),t=RRe(),n=XG();function r(o){var i=t(o);return i.length==1&&i02?n(i00,i01):function(s){return s===o||e(s,o,i)}}return MC=r,MC}var jC,U5;function ph(){if(U5)return jC;U5=1;var e=Ku(),t=da(),n="object Symbol";function r(o){return typeof o=="symbol"||t(o)&&e(o)==n}return jC=r,jC}var LC,q5;function kI(){if(q5)return LC;q5=1;var e=ur(),t=ph(),n=/\.|\(?:^\*|("')(?:(?!\1)^\\|\\.)*?\1)\/,r=/^\w*$/;function o(i,s){if(e(i))return!1;var a=typeof i;return a=="number"||a=="symbol"||a=="boolean"||i==null||t(i)?!0:r.test(i)||!n.test(i)||s!=null&&i in Object(s)}return LC=o,LC}var ZG=vI,kRe="Expected a function";function PI(e,t){if(typeof e!="function"||t!=null&&typeof t!="function")throw new TypeError(kRe);var n=function(){var r=arguments,o=t?t.apply(this,r):r0,i=n.cache;if(i.has(o))return i.get(o);var s=e.apply(this,r);return n.cache=i.set(o,s)||i,s};return n.cache=new(PI.Cache||ZG),n}PI.Cache=ZG;var QG=PI;const PRe=qf(QG);var OC,G5;function IRe(){if(G5)return OC;G5=1;var e=QG,t=500;function n(r){var o=e(r,function(s){return i.size===t&&i.clear(),s}),i=o.cache;return o}return OC=n,OC}var DC,V5;function ARe(){if(V5)return DC;V5=1;var e=IRe(),t=/^.\+|\(?:(-?\d+(?:\.\d+)?)|("')((?:(?!\2)^\\|\\.)*?)\2)\|(?=(?:\.|\\)(?:\.|\\|$))/g,n=/\\(\\)?/g,r=e(function(o){var i=;return o.charCodeAt(0)===46&&i.push(""),o.replace(t,function(s,a,l,c){i.push(l?c.replace(n,"$1"):a||s)}),i});return DC=r,DC}var FC,Y5;function Gb(){if(Y5)return FC;Y5=1;function e(t,n){for(var r=-1,o=t==null?0:t.length,i=Array(o);++r<o;)ir=n(tr,r,t);return i}return FC=e,FC}var $C,K5;function MRe(){if(K5)return $C;K5=1;var e=ch(),t=Gb(),n=ur(),r=ph(),o=e?e.prototype:void 0,i=o?o.toString:void 0;function s(a){if(typeof a=="string")return a;if(n(a))return t(a,s)+"";if(r(a))return i?i.call(a):"";var l=a+"";return l=="0"&&1/a==-1/0?"-0":l}return $C=s,$C}var zC,X5;function JG(){if(X5)return zC;X5=1;var e=MRe();function t(n){return n==null?"":e(n)}return zC=t,zC}var HC,Z5;function Vb(){if(Z5)return HC;Z5=1;var e=ur(),t=kI(),n=ARe(),r=JG();function o(i,s){return e(i)?i:t(i,s)?i:n(r(i))}return HC=o,HC}var BC,Q5;function yg(){if(Q5)return BC;Q5=1;var e=ph();function t(n){if(typeof n=="string"||e(n))return n;var r=n+"";return r=="0"&&1/n==-1/0?"-0":r}return BC=t,BC}var WC,J5;function Yb(){if(J5)return WC;J5=1;var e=Vb(),t=yg();function n(r,o){o=e(o,r);for(var i=0,s=o.length;r!=null&&i<s;)r=rt(oi++);return i&&i==s?r:void 0}return WC=n,WC}var UC,eF;function jRe(){if(eF)return UC;eF=1;var e=Yb();function t(n,r,o){var i=n==null?void 0:e(n,r);return i===void 0?o:i}return UC=t,UC}var qC,tF;function LRe(){if(tF)return qC;tF=1;function e(t,n){return t!=null&&n in Object(t)}return qC=e,qC}var GC,nF;function eV(){if(nF)return GC;nF=1;var e=Vb(),t=gg(),n=ur(),r=Hb(),o=xI(),i=yg();function s(a,l,c){l=e(l,a);for(var d=-1,f=l.length,p=!1;++d<f;){var g=i(ld);if(!(p=a!=null&&c(a,g)))break;a=ag}return p||++d!=f?p:(f=a==null?0:a.length,!!f&&o(f)&&r(g,f)&&(n(a)||t(a)))}return GC=s,GC}var VC,rF;function tV(){if(rF)return VC;rF=1;var e=LRe(),t=eV();function n(r,o){return r!=null&&t(r,o,e)}return VC=n,VC}var YC,oF;function ORe(){if(oF)return YC;oF=1;var e=YG(),t=jRe(),n=tV(),r=kI(),o=KG(),i=XG(),s=yg(),a=1,l=2;function c(d,f){return r(d)&&o(f)?i(s(d),f):function(p){var g=t(p,d);return g===void 0&&g===f?n(p,d):e(f,g,a|l)}}return YC=c,YC}var KC,iF;function nV(){if(iF)return KC;iF=1;function e(t){return function(n){return n==null?void 0:nt}}return KC=e,KC}var XC,sF;function DRe(){if(sF)return XC;sF=1;var e=Yb();function t(n){return function(r){return e(r,n)}}return XC=t,XC}var ZC,aF;function FRe(){if(aF)return ZC;aF=1;var e=nV(),t=DRe(),n=kI(),r=yg();function o(i){return n(i)?e(r(i)):t(i)}return ZC=o,ZC}var QC,lF;function ul(){if(lF)return QC;lF=1;var e=TRe(),t=ORe(),n=Qu(),r=ur(),o=FRe();function i(s){return typeof s=="function"?s:s==null?n:typeof s=="object"?r(s)?t(s0,s1):e(s):o(s)}return QC=i,QC}var JC,cF;function rV(){if(cF)return JC;cF=1;var e=IG(),t=yRe(),n=ul(),r=ur();function o(i,s){var a=r(i)?e:t;return a(i,n(s,3))}return JC=o,JC}var eE,uF;function $Re(){if(uF)return eE;uF=1;var e=Object.prototype,t=e.hasOwnProperty;function n(r,o){return r!=null&&t.call(r,o)}return eE=n,eE}var tE,dF;function oV(){if(dF)return tE;dF=1;var e=$Re(),t=eV();function n(r,o){return r!=null&&t(r,o,e)}return tE=n,tE}var nE,fF;function zRe(){if(fF)return nE;fF=1;var e=wI(),t=hh(),n=gg(),r=ur(),o=cl(),i=fh(),s=Wb(),a=vg(),l="object Map",c="object Set",d=Object.prototype,f=d.hasOwnProperty;function p(g){if(g==null)return!0;if(o(g)&&(r(g)||typeof g=="string"||typeof g.splice=="function"||i(g)||a(g)||n(g)))return!g.length;var v=t(g);if(v==l||v==c)return!g.size;if(s(g))return!e(g).length;for(var b in g)if(f.call(g,b))return!1;return!0}return nE=p,nE}var rE,hF;function iV(){if(hF)return rE;hF=1;function e(t){return t===void 0}return rE=e,rE}var oE,pF;function sV(){if(pF)return oE;pF=1;var e=qb(),t=cl();function n(r,o){var i=-1,s=t(r)?Array(r.length):;return e(r,function(a,l,c){s++i=o(a,l,c)}),s}return oE=n,oE}var iE,mF;function aV(){if(mF)return iE;mF=1;var e=Gb(),t=ul(),n=sV(),r=ur();function o(i,s){var a=r(i)?e:n;return a(i,t(s,3))}return iE=o,iE}var sE,gF;function HRe(){if(gF)return sE;gF=1;function e(t,n,r,o){var i=-1,s=t==null?0:t.length;for(o&&s&&(r=t++i);++i<s;)r=n(r,ti,i,t);return r}return sE=e,sE}var aE,vF;function BRe(){if(vF)return aE;vF=1;function e(t,n,r,o,i){return i(t,function(s,a,l){r=o?(o=!1,s):n(r,s,a,l)}),r}return aE=e,aE}var lE,yF;function lV(){if(yF)return lE;yF=1;var e=HRe(),t=qb(),n=ul(),r=BRe(),o=ur();function i(s,a,l){var c=o(s)?e:r,d=arguments.length<3;return c(s,n(a,4),l,d,t)}return lE=i,lE}var cE,xF;function WRe(){if(xF)return cE;xF=1;var e=Ku(),t=ur(),n=da(),r="object String";function o(i){return typeof i=="string"||!t(i)&&n(i)&&e(i)==r}return cE=o,cE}var uE,bF;function URe(){if(bF)return uE;bF=1;var e=nV(),t=e("length");return uE=t,uE}var dE,wF;function qRe(){if(wF)return dE;wF=1;var e="\\ud800-\\udfff",t="\\u0300-\\u036f",n="\\ufe20-\\ufe2f",r="\\u20d0-\\u20ff",o=t+n+r,i="\\ufe0e\\ufe0f",s="\\u200d",a=RegExp(""+s+e+o+i+"");function l(c){return a.test(c)}return dE=l,dE}var fE,SF;function GRe(){if(SF)return fE;SF=1;var e="\\ud800-\\udfff",t="\\u0300-\\u036f",n="\\ufe20-\\ufe2f",r="\\u20d0-\\u20ff",o=t+n+r,i="\\ufe0e\\ufe0f",s=""+e+"",a=""+o+"",l="\\ud83c\\udffb-\\udfff",c="(?:"+a+"|"+l+")",d="^"+e+"",f="(?:\\ud83c\\udde6-\\uddff){2}",p="\\ud800-\\udbff\\udc00-\\udfff",g="\\u200d",v=c+"?",b=""+i+"?",_="(?:"+g+"(?:"+d,f,p.join("|")+")"+b+v+")*",x=b+v+_,w="(?:"+d+a+"?",a,f,p,s.join("|")+")",C=RegExp(l+"(?="+l+")|"+w+x,"g");function E(R){for(var P=C.lastIndex=0;C.test(R);)++P;return P}return fE=E,fE}var hE,_F;function VRe(){if(_F)return hE;_F=1;var e=URe(),t=qRe(),n=GRe();function r(o){return t(o)?n(o):e(o)}return hE=r,hE}var pE,CF;function YRe(){if(CF)return pE;CF=1;var e=wI(),t=hh(),n=cl(),r=WRe(),o=VRe(),i="object Map",s="object Set";function a(l){if(l==null)return 0;if(n(l))return r(l)?o(l):l.length;var c=t(l);return c==i||c==s?l.size:e(l).length}return pE=a,pE}var mE,EF;function KRe(){if(EF)return mE;EF=1;var e=yI(),t=$G(),n=RI(),r=ul(),o=Ub(),i=ur(),s=fh(),a=pg(),l=Di(),c=vg();function d(f,p,g){var v=i(f),b=v||s(f)||c(f);if(p=r(p,4),g==null){var _=f&&f.constructor;b?g=v?new _::l(f)?g=a(_)?t(o(f)):{}:g={}}return(b?e:n)(f,function(x,w,C){return p(g,x,w,C)}),g}return mE=d,mE}var gE,NF;function XRe(){if(NF)return gE;NF=1;var e=ch(),t=gg(),n=ur(),r=e?e.isConcatSpreadable:void 0;function o(i){return n(i)||t(i)||!!(r&&i&&ir)}return gE=o,gE}var vE,RF;function II(){if(RF)return vE;RF=1;var e=_I(),t=XRe();function n(r,o,i,s,a){var l=-1,c=r.length;for(i||(i=t),a||(a=);++l<c;){var d=rl;o>0&&i(d)?o>1?n(d,o-1,i,s,a):e(a,d):s||(aa.length=d)}return a}return vE=n,vE}var yE,TF;function ZRe(){if(TF)return yE;TF=1;function e(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r0);case 2:return t.call(n,r0,r1);case 3:return t.call(n,r0,r1,r2)}return t.apply(n,r)}return yE=e,yE}var xE,kF;function cV(){if(kF)return xE;kF=1;var e=ZRe(),t=Math.max;function n(r,o,i){return o=t(o===void 0?r.length-1:o,0),function(){for(var s=arguments,a=-1,l=t(s.length-o,0),c=Array(l);++a<l;)ca=so+a;a=-1;for(var d=Array(o+1);++a<o;)da=sa;return do=i(c),e(r,this,d)}}return xE=n,xE}var bE,PF;function QRe(){if(PF)return bE;PF=1;var e=EI(),t=NG(),n=Qu(),r=t?function(o,i){return t(o,"toString",{configurable:!0,enumerable:!1,value:e(i),writable:!0})}:n;return bE=r,bE}var wE,IF;function JRe(){if(IF)return wE;IF=1;var e=800,t=16,n=Date.now;function r(o){var i=0,s=0;return function(){var a=n(),l=t-(a-s);if(s=a,l>0){if(++i>=e)return arguments0}else i=0;return o.apply(void 0,arguments)}}return wE=r,wE}var SE,AF;function uV(){if(AF)return SE;AF=1;var e=QRe(),t=JRe(),n=t(e);return SE=n,SE}var _E,MF;function Kb(){if(MF)return _E;MF=1;var e=Qu(),t=cV(),n=uV();function r(o,i){return n(t(o,i,e),o+"")}return _E=r,_E}var CE,jF;function dV(){if(jF)return CE;jF=1;function e(t,n,r,o){for(var i=t.length,s=r+(o?1:-1);o?s--:++s<i;)if(n(ts,s,t))return s;return-1}return CE=e,CE}var EE,LF;function eTe(){if(LF)return EE;LF=1;function e(t){return t!==t}return EE=e,EE}var NE,OF;function tTe(){if(OF)return NE;OF=1;function e(t,n,r){for(var o=r-1,i=t.length;++o<i;)if(to===n)return o;return-1}return NE=e,NE}var RE,DF;function nTe(){if(DF)return RE;DF=1;var e=dV(),t=eTe(),n=tTe();function r(o,i,s){return i===i?n(o,i,s):e(o,t,s)}return RE=r,RE}var TE,FF;function rTe(){if(FF)return TE;FF=1;var e=nTe();function t(n,r){var o=n==null?0:n.length;return!!o&&e(n,r,0)>-1}return TE=t,TE}var kE,$F;function oTe(){if($F)return kE;$F=1;function e(t,n,r){for(var o=-1,i=t==null?0:t.length;++o<i;)if(r(n,to))return!0;return!1}return kE=e,kE}var PE,zF;function iTe(){if(zF)return PE;zF=1;function e(){}return PE=e,PE}var IE,HF;function sTe(){if(HF)return IE;HF=1;var e=OG(),t=iTe(),n=TI(),r=1/0,o=e&&1/n(new e(,-0))1==r?function(i){return new e(i)}:t;return IE=o,IE}var AE,BF;function aTe(){if(BF)return AE;BF=1;var e=qG(),t=rTe(),n=oTe(),r=GG(),o=sTe(),i=TI(),s=200;function a(l,c,d){var f=-1,p=t,g=l.length,v=!0,b=,_=b;if(d)v=!1,p=n;else if(g>=s){var x=c?null:o(l);if(x)return i(x);v=!1,p=r,_=new e}else _=c?:b;e:for(;++f<g;){var w=lf,C=c?c(w):w;if(w=d||w!==0?w:0,v&&C===C){for(var E=_.length;E--;)if(_E===C)continue e;c&&_.push(C),b.push(w)}else p(_,C,d)||(_!==b&&_.push(C),b.push(w))}return b}return AE=a,AE}var ME,WF;function fV(){if(WF)return ME;WF=1;var e=cl(),t=da();function n(r){return t(r)&&e(r)}return ME=n,ME}var jE,UF;function lTe(){if(UF)return jE;UF=1;var e=II(),t=Kb(),n=aTe(),r=fV(),o=t(function(i){return n(e(i,1,r,!0))});return jE=o,jE}var LE,qF;function cTe(){if(qF)return LE;qF=1;var e=Gb();function t(n,r){return e(r,function(o){return no})}return LE=t,LE}var OE,GF;function hV(){if(GF)return OE;GF=1;var e=cTe(),t=Tc();function n(r){return r==null?:e(r,t(r))}return OE=n,OE}var DE,VF;function Fi(){if(VF)return DE;VF=1;var e;if(typeof mI=="function")try{e={clone:mRe(),constant:EI(),each:UG(),filter:rV(),has:oV(),isArray:ur(),isEmpty:zRe(),isFunction:pg(),isUndefined:iV(),keys:Tc(),map:aV(),reduce:lV(),size:YRe(),transform:KRe(),union:lTe(),values:hV()}}catch{}return e||(e=window._),DE=e,DE}var FE,YF;function AI(){if(YF)return FE;YF=1;var e=Fi();FE=o;var t="\0",n="\0",r="";function o(d){this._isDirected=e.has(d,"directed")?d.directed:!0,this._isMultigraph=e.has(d,"multigraph")?d.multigraph:!1,this._isCompound=e.has(d,"compound")?d.compound:!1,this._label=void 0,this._defaultNodeLabelFn=e.constant(void 0),this._defaultEdgeLabelFn=e.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._childrenn={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}o.prototype._nodeCount=0,o.prototype._edgeCount=0,o.prototype.isDirected=function(){return this._isDirected},o.prototype.isMultigraph=function(){return this._isMultigraph},o.prototype.isCompound=function(){return this._isCompound},o.prototype.setGraph=function(d){return this._label=d,this},o.prototype.graph=function(){return this._label},o.prototype.setDefaultNodeLabel=function(d){return e.isFunction(d)||(d=e.constant(d)),this._defaultNodeLabelFn=d,this},o.prototype.nodeCount=function(){return this._nodeCount},o.prototype.nodes=function(){return e.keys(this._nodes)},o.prototype.sources=function(){var d=this;return e.filter(this.nodes(),function(f){return e.isEmpty(d._inf)})},o.prototype.sinks=function(){var d=this;return e.filter(this.nodes(),function(f){return e.isEmpty(d._outf)})},o.prototype.setNodes=function(d,f){var p=arguments,g=this;return e.each(d,function(v){p.length>1?g.setNode(v,f):g.setNode(v)}),this},o.prototype.setNode=function(d,f){return e.has(this._nodes,d)?(arguments.length>1&&(this._nodesd=f),this):(this._nodesd=arguments.length>1?f:this._defaultNodeLabelFn(d),this._isCompound&&(this._parentd=n,this._childrend={},this._childrennd=!0),this._ind={},this._predsd={},this._outd={},this._sucsd={},++this._nodeCount,this)},o.prototype.node=function(d){return this._nodesd},o.prototype.hasNode=function(d){return e.has(this._nodes,d)},o.prototype.removeNode=function(d){var f=this;if(e.has(this._nodes,d)){var p=function(g){f.removeEdge(f._edgeObjsg)};delete this._nodesd,this._isCompound&&(this._removeFromParentsChildList(d),delete this._parentd,e.each(this.children(d),function(g){f.setParent(g)}),delete this._childrend),e.each(e.keys(this._ind),p),delete this._ind,delete this._predsd,e.each(e.keys(this._outd),p),delete this._outd,delete this._sucsd,--this._nodeCount}return this},o.prototype.setParent=function(d,f){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(e.isUndefined(f))f=n;else{f+="";for(var p=f;!e.isUndefined(p);p=this.parent(p))if(p===d)throw new Error("Setting "+f+" as parent of "+d+" would create a cycle");this.setNode(f)}return this.setNode(d),this._removeFromParentsChildList(d),this._parentd=f,this._childrenfd=!0,this},o.prototype._removeFromParentsChildList=function(d){delete this._childrenthis._parentdd},o.prototype.parent=function(d){if(this._isCompound){var f=this._parentd;if(f!==n)return f}},o.prototype.children=function(d){if(e.isUndefined(d)&&(d=n),this._isCompound){var f=this._childrend;if(f)return e.keys(f)}else{if(d===n)return this.nodes();if(this.hasNode(d))return}},o.prototype.predecessors=function(d){var f=this._predsd;if(f)return e.keys(f)},o.prototype.successors=function(d){var f=this._sucsd;if(f)return e.keys(f)},o.prototype.neighbors=function(d){var f=this.predecessors(d);if(f)return e.union(f,this.successors(d))},o.prototype.isLeaf=function(d){var f;return this.isDirected()?f=this.successors(d):f=this.neighbors(d),f.length===0},o.prototype.filterNodes=function(d){var f=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});f.setGraph(this.graph());var p=this;e.each(this._nodes,function(b,_){d(_)&&f.setNode(_,b)}),e.each(this._edgeObjs,function(b){f.hasNode(b.v)&&f.hasNode(b.w)&&f.setEdge(b,p.edge(b))});var g={};function v(b){var _=p.parent(b);return _===void 0||f.hasNode(_)?(gb=_,_):_ in g?g_:v(_)}return this._isCompound&&e.each(f.nodes(),function(b){f.setParent(b,v(b))}),f},o.prototype.setDefaultEdgeLabel=function(d){return e.isFunction(d)||(d=e.constant(d)),this._defaultEdgeLabelFn=d,this},o.prototype.edgeCount=function(){return this._edgeCount},o.prototype.edges=function(){return e.values(this._edgeObjs)},o.prototype.setPath=function(d,f){var p=this,g=arguments;return e.reduce(d,function(v,b){return g.length>1?p.setEdge(v,b,f):p.setEdge(v,b),b}),this},o.prototype.setEdge=function(){var d,f,p,g,v=!1,b=arguments0;typeof b=="object"&&b!==null&&"v"in b?(d=b.v,f=b.w,p=b.name,arguments.length===2&&(g=arguments1,v=!0)):(d=b,f=arguments1,p=arguments3,arguments.length>2&&(g=arguments2,v=!0)),d=""+d,f=""+f,e.isUndefined(p)||(p=""+p);var _=a(this._isDirected,d,f,p);if(e.has(this._edgeLabels,_))return v&&(this._edgeLabels_=g),this;if(!e.isUndefined(p)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(d),this.setNode(f),this._edgeLabels_=v?g:this._defaultEdgeLabelFn(d,f,p);var x=l(this._isDirected,d,f,p);return d=x.v,f=x.w,Object.freeze(x),this._edgeObjs_=x,i(this._predsf,d),i(this._sucsd,f),this._inf_=x,this._outd_=x,this._edgeCount++,this},o.prototype.edge=function(d,f,p){var g=arguments.length===1?c(this._isDirected,arguments0):a(this._isDirected,d,f,p);return this._edgeLabelsg},o.prototype.hasEdge=function(d,f,p){var g=arguments.length===1?c(this._isDirected,arguments0):a(this._isDirected,d,f,p);return e.has(this._edgeLabels,g)},o.prototype.removeEdge=function(d,f,p){var g=arguments.length===1?c(this._isDirected,arguments0):a(this._isDirected,d,f,p),v=this._edgeObjsg;return v&&(d=v.v,f=v.w,delete this._edgeLabelsg,delete this._edgeObjsg,s(this._predsf,d),s(this._sucsd,f),delete this._infg,delete this._outdg,this._edgeCount--),this},o.prototype.inEdges=function(d,f){var p=this._ind;if(p){var g=e.values(p);return f?e.filter(g,function(v){return v.v===f}):g}},o.prototype.outEdges=function(d,f){var p=this._outd;if(p){var g=e.values(p);return f?e.filter(g,function(v){return v.w===f}):g}},o.prototype.nodeEdges=function(d,f){var p=this.inEdges(d,f);if(p)return p.concat(this.outEdges(d,f))};function i(d,f){df?df++:df=1}function s(d,f){--df||delete df}function a(d,f,p,g){var v=""+f,b=""+p;if(!d&&v>b){var _=v;v=b,b=_}return v+r+b+r+(e.isUndefined(g)?t:g)}function l(d,f,p,g){var v=""+f,b=""+p;if(!d&&v>b){var _=v;v=b,b=_}var x={v,w:b};return g&&(x.name=g),x}function c(d,f){return a(d,f.v,f.w,f.name)}return FE}var $E,KF;function uTe(){return KF||(KF=1,$E="2.1.8"),$E}var zE,XF;function dTe(){return XF||(XF=1,zE={Graph:AI(),version:uTe()}),zE}var HE,ZF;function fTe(){if(ZF)return HE;ZF=1;var e=Fi(),t=AI();HE={write:n,read:i};function n(s){var a={options:{directed:s.isDirected(),multigraph:s.isMultigraph(),compound:s.isCompound()},nodes:r(s),edges:o(s)};return e.isUndefined(s.graph())||(a.value=e.clone(s.graph())),a}function r(s){return e.map(s.nodes(),function(a){var l=s.node(a),c=s.parent(a),d={v:a};return e.isUndefined(l)||(d.value=l),e.isUndefined(c)||(d.parent=c),d})}function o(s){return e.map(s.edges(),function(a){var l=s.edge(a),c={v:a.v,w:a.w};return e.isUndefined(a.name)||(c.name=a.name),e.isUndefined(l)||(c.value=l),c})}function i(s){var a=new t(s.options).setGraph(s.value);return e.each(s.nodes,function(l){a.setNode(l.v,l.value),l.parent&&a.setParent(l.v,l.parent)}),e.each(s.edges,function(l){a.setEdge({v:l.v,w:l.w,name:l.name},l.value)}),a}return HE}var BE,QF;function hTe(){if(QF)return BE;QF=1;var e=Fi();BE=t;function t(n){var r={},o=,i;function s(a){e.has(r,a)||(ra=!0,i.push(a),e.each(n.successors(a),s),e.each(n.predecessors(a),s))}return e.each(n.nodes(),function(a){i=,s(a),i.length&&o.push(i)}),o}return BE}var WE,JF;function pV(){if(JF)return WE;JF=1;var e=Fi();WE=t;function t(){this._arr=,this._keyIndices={}}return t.prototype.size=function(){return this._arr.length},t.prototype.keys=function(){return this._arr.map(function(n){return n.key})},t.prototype.has=function(n){return e.has(this._keyIndices,n)},t.prototype.priority=function(n){var r=this._keyIndicesn;if(r!==void 0)return this._arrr.priority},t.prototype.min=function(){if(this.size()===0)throw new Error("Queue underflow");return this._arr0.key},t.prototype.add=function(n,r){var o=this._keyIndices;if(n=String(n),!e.has(o,n)){var i=this._arr,s=i.length;return on=s,i.push({key:n,priority:r}),this._decrease(s),!0}return!1},t.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var n=this._arr.pop();return delete this._keyIndicesn.key,this._heapify(0),n.key},t.prototype.decrease=function(n,r){var o=this._keyIndicesn;if(r>this._arro.priority)throw new Error("New priority is greater than current priority. Key: "+n+" Old: "+this._arro.priority+" New: "+r);this._arro.priority=r,this._decrease(o)},t.prototype._heapify=function(n){var r=this._arr,o=2*n,i=o+1,s=n;o<r.length&&(s=ro.priority<rs.priority?o:s,i<r.length&&(s=ri.priority<rs.priority?i:s),s!==n&&(this._swap(n,s),this._heapify(s)))},t.prototype._decrease=function(n){for(var r=this._arr,o=rn.priority,i;n!==0&&(i=n>>1,!(ri.priority<o));)this._swap(n,i),n=i},t.prototype._swap=function(n,r){var o=this._arr,i=this._keyIndices,s=on,a=or;on=a,or=s,ia.key=n,is.key=r},WE}var UE,e$;function mV(){if(e$)return UE;e$=1;var e=Fi(),t=pV();UE=r;var n=e.constant(1);function r(i,s,a,l){return o(i,String(s),a||n,l||function(c){return i.outEdges(c)})}function o(i,s,a,l){var c={},d=new t,f,p,g=function(v){var b=v.v!==f?v.v:v.w,_=cb,x=a(v),w=p.distance+x;if(x<0)throw new Error("dijkstra does not allow negative edge weights. Bad edge: "+v+" Weight: "+x);w<_.distance&&(_.distance=w,_.predecessor=f,d.decrease(b,w))};for(i.nodes().forEach(function(v){var b=v===s?0:Number.POSITIVE_INFINITY;cv={distance:b},d.add(v,b)});d.size()>0&&(f=d.removeMin(),p=cf,p.distance!==Number.POSITIVE_INFINITY);)l(f).forEach(g);return c}return UE}var qE,t$;function pTe(){if(t$)return qE;t$=1;var e=mV(),t=Fi();qE=n;function n(r,o,i){return t.transform(r.nodes(),function(s,a){sa=e(r,a,o,i)},{})}return qE}var GE,n$;function gV(){if(n$)return GE;n$=1;var e=Fi();GE=t;function t(n){var r=0,o=,i={},s=;function a(l){var c=il={onStack:!0,lowlink:r,index:r++};if(o.push(l),n.successors(l).forEach(function(p){e.has(i,p)?ip.onStack&&(c.lowlink=Math.min(c.lowlink,ip.index)):(a(p),c.lowlink=Math.min(c.lowlink,ip.lowlink))}),c.lowlink===c.index){var d=,f;do f=o.pop(),if.onStack=!1,d.push(f);while(l!==f);s.push(d)}}return n.nodes().forEach(function(l){e.has(i,l)||a(l)}),s}return GE}var VE,r$;function mTe(){if(r$)return VE;r$=1;var e=Fi(),t=gV();VE=n;function n(r){return e.filter(t(r),function(o){return o.length>1||o.length===1&&r.hasEdge(o0,o0)})}return VE}var YE,o$;function gTe(){if(o$)return YE;o$=1;var e=Fi();YE=n;var t=e.constant(1);function n(o,i,s){return r(o,i||t,s||function(a){return o.outEdges(a)})}function r(o,i,s){var a={},l=o.nodes();return l.forEach(function(c){ac={},acc={distance:0},l.forEach(function(d){c!==d&&(acd={distance:Number.POSITIVE_INFINITY})}),s(c).forEach(function(d){var f=d.v===c?d.w:d.v,p=i(d);acf={distance:p,predecessor:c}})}),l.forEach(function(c){var d=ac;l.forEach(function(f){var p=af;l.forEach(function(g){var v=pc,b=dg,_=pg,x=v.distance+b.distance;x<_.distance&&(_.distance=x,_.predecessor=b.predecessor)})})}),a}return YE}var KE,i$;function vV(){if(i$)return KE;i$=1;var e=Fi();KE=t,t.CycleException=n;function t(r){var o={},i={},s=;function a(l){if(e.has(i,l))throw new n;e.has(o,l)||(il=!0,ol=!0,e.each(r.predecessors(l),a),delete il,s.push(l))}if(e.each(r.sinks(),a),e.size(o)!==r.nodeCount())throw new n;return s}function n(){}return n.prototype=new Error,KE}var XE,s$;function vTe(){if(s$)return XE;s$=1;var e=vV();XE=t;function t(n){try{e(n)}catch(r){if(r instanceof e.CycleException)return!1;throw r}return!0}return XE}var ZE,a$;function yV(){if(a$)return ZE;a$=1;var e=Fi();ZE=t;function t(r,o,i){e.isArray(o)||(o=o);var s=(r.isDirected()?r.successors:r.neighbors).bind(r),a=,l={};return e.each(o,function(c){if(!r.hasNode(c))throw new Error("Graph does not have node: "+c);n(r,c,i==="post",l,s,a)}),a}function n(r,o,i,s,a,l){e.has(s,o)||(so=!0,i||l.push(o),e.each(a(o),function(c){n(r,c,i,s,a,l)}),i&&l.push(o))}return ZE}var QE,l$;function yTe(){if(l$)return QE;l$=1;var e=yV();QE=t;function t(n,r){return e(n,r,"post")}return QE}var JE,c$;function xTe(){if(c$)return JE;c$=1;var e=yV();JE=t;function t(n,r){return e(n,r,"pre")}return JE}var e2,u$;function bTe(){if(u$)return e2;u$=1;var e=Fi(),t=AI(),n=pV();e2=r;function r(o,i){var s=new t,a={},l=new n,c;function d(p){var g=p.v===c?p.w:p.v,v=l.priority(g);if(v!==void 0){var b=i(p);b<v&&(ag=c,l.decrease(g,b))}}if(o.nodeCount()===0)return s;e.each(o.nodes(),function(p){l.add(p,Number.POSITIVE_INFINITY),s.setNode(p)}),l.decrease(o.nodes()0,0);for(var f=!1;l.size()>0;){if(c=l.removeMin(),e.has(a,c))s.setEdge(c,ac);else{if(f)throw new Error("Input graph is not connected: "+o);f=!0}o.nodeEdges(c).forEach(d)}return s}return e2}var t2,d$;function wTe(){return d$||(d$=1,t2={components:hTe(),dijkstra:mV(),dijkstraAll:pTe(),findCycles:mTe(),floydWarshall:gTe(),isAcyclic:vTe(),postorder:yTe(),preorder:xTe(),prim:bTe(),tarjan:gV(),topsort:vV()}),t2}var n2,f$;function STe(){if(f$)return n2;f$=1;var e=dTe();return n2={Graph:e.Graph,json:fTe(),alg:wTe(),version:e.version},n2}var Qy;if(typeof mI=="function")try{Qy=STe()}catch{}Qy||(Qy=window.graphlib);var ws=Qy,r2,h$;function _Te(){if(h$)return r2;h$=1;var e=HG(),t=1,n=4;function r(o){return e(o,t|n)}return r2=r,r2}var o2,p$;function Xb(){if(p$)return o2;p$=1;var e=ah(),t=cl(),n=Hb(),r=Di();function o(i,s,a){if(!r(a))return!1;var l=typeof s;return(l=="number"?t(a)&&n(s,a.length):l=="string"&&s in a)?e(as,i):!1}return o2=o,o2}var i2,m$;function CTe(){if(m$)return i2;m$=1;var e=Kb(),t=ah(),n=Xb(),r=Zu(),o=Object.prototype,i=o.hasOwnProperty,s=e(function(a,l){a=Object(a);var c=-1,d=l.length,f=d>2?l2:void 0;for(f&&n(l0,l1,f)&&(d=1);++c<d;)for(var p=lc,g=r(p),v=-1,b=g.length;++v<b;){var _=gv,x=a_;(x===void 0||t(x,o_)&&!i.call(a,_))&&(a_=p_)}return a});return i2=s,i2}var s2,g$;function ETe(){if(g$)return s2;g$=1;var e=ul(),t=cl(),n=Tc();function r(o){return function(i,s,a){var l=Object(i);if(!t(i)){var c=e(s,3);i=n(i),s=function(f){return c(lf,f,l)}}var d=o(i,s,a);return d>-1?lc?id:d:void 0}}return s2=r,s2}var a2,v$;function NTe(){if(v$)return a2;v$=1;var e=/\s/;function t(n){for(var r=n.length;r--&&e.test(n.charAt(r)););return r}return a2=t,a2}var l2,y$;function RTe(){if(y$)return l2;y$=1;var e=NTe(),t=/^\s+/;function n(r){return r&&r.slice(0,e(r)+1).replace(t,"")}return l2=n,l2}var c2,x$;function TTe(){if(x$)return c2;x$=1;var e=RTe(),t=Di(),n=ph(),r=NaN,o=/^-+0x0-9a-f+$/i,i=/^0b01+$/i,s=/^0o0-7+$/i,a=parseInt;function l(c){if(typeof c=="number")return c;if(n(c))return r;if(t(c)){var d=typeof c.valueOf=="function"?c.valueOf():c;c=t(d)?d+"":d}if(typeof c!="string")return c===0?c:+c;c=e(c);var f=i.test(c);return f||s.test(c)?a(c.slice(2),f?2:8):o.test(c)?r:+c}return c2=l,c2}var u2,b$;function xV(){if(b$)return u2;b$=1;var e=TTe(),t=1/0,n=17976931348623157e292;function r(o){if(!o)return o===0?o:0;if(o=e(o),o===t||o===-t){var i=o<0?-1:1;return i*n}return o===o?o:0}return u2=r,u2}var d2,w$;function kTe(){if(w$)return d2;w$=1;var e=xV();function t(n){var r=e(n),o=r%1;return r===r?o?r-o:r:0}return d2=t,d2}var f2,S$;function PTe(){if(S$)return f2;S$=1;var e=dV(),t=ul(),n=kTe(),r=Math.max;function o(i,s,a){var l=i==null?0:i.length;if(!l)return-1;var c=a==null?0:n(a);return c<0&&(c=r(l+c,0)),e(i,t(s,3),c)}return f2=o,f2}var h2,_$;function ITe(){if(_$)return h2;_$=1;var e=ETe(),t=PTe(),n=e(t);return h2=n,h2}var p2,C$;function bV(){if(C$)return p2;C$=1;var e=II();function t(n){var r=n==null?0:n.length;return r?e(n,1):}return p2=t,p2}var m2,E$;function ATe(){if(E$)return m2;E$=1;var e=NI(),t=BG(),n=Zu();function r(o,i){return o==null?o:e(o,t(i),n)}return m2=r,m2}var g2,N$;function MTe(){if(N$)return g2;N$=1;function e(t){var n=t==null?0:t.length;return n?tn-1:void 0}return g2=e,g2}var v2,R$;function jTe(){if(R$)return v2;R$=1;var e=$b(),t=RI(),n=ul();function r(o,i){var s={};return i=n(i,3),t(o,function(a,l,c){e(s,l,i(a,l,c))}),s}return v2=r,v2}var y2,T$;function MI(){if(T$)return y2;T$=1;var e=ph();function t(n,r,o){for(var i=-1,s=n.length;++i<s;){var a=ni,l=r(a);if(l!=null&&(c===void 0?l===l&&!e(l):o(l,c)))var c=l,d=a}return d}return y2=t,y2}var x2,k$;function LTe(){if(k$)return x2;k$=1;function e(t,n){return t>n}return x2=e,x2}var b2,P$;function OTe(){if(P$)return b2;P$=1;var e=MI(),t=LTe(),n=Qu();function r(o){return o&&o.length?e(o,n,t):void 0}return b2=r,b2}var w2,I$;function wV(){if(I$)return w2;I$=1;var e=$b(),t=ah();function n(r,o,i){(i!==void 0&&!t(ro,i)||i===void 0&&!(o in r))&&e(r,o,i)}return w2=n,w2}var S2,A$;function DTe(){if(A$)return S2;A$=1;var e=Ku(),t=Ub(),n=da(),r="object Object",o=Function.prototype,i=Object.prototype,s=o.toString,a=i.hasOwnProperty,l=s.call(Object);function c(d){if(!n(d)||e(d)!=r)return!1;var f=t(d);if(f===null)return!0;var p=a.call(f,"constructor")&&f.constructor;return typeof p=="function"&&p instanceof p&&s.call(p)==l}return S2=c,S2}var _2,M$;function SV(){if(M$)return _2;M$=1;function e(t,n){if(!(n==="constructor"&&typeof tn=="function")&&n!="__proto__")return tn}return _2=e,_2}var C2,j$;function FTe(){if(j$)return C2;j$=1;var e=mg(),t=Zu();function n(r){return e(r,t(r))}return C2=n,C2}var E2,L$;function $Te(){if(L$)return E2;L$=1;var e=wV(),t=kG(),n=FG(),r=PG(),o=zG(),i=gg(),s=ur(),a=fV(),l=fh(),c=pg(),d=Di(),f=DTe(),p=vg(),g=SV(),v=FTe();function b(_,x,w,C,E,R,P){var N=g(_,w),k=g(x,w),I=P.get(k);if(I){e(_,w,I);return}var O=R?R(N,k,w+"",_,x,P):void 0,j=O===void 0;if(j){var H=s(k),q=!H&&l(k),M=!H&&!q&&p(k);O=k,H||q||M?s(N)?O=N:a(N)?O=r(N):q?(j=!1,O=t(k,!0)):M?(j=!1,O=n(k,!0)):O=:f(k)||i(k)?(O=N,i(N)?O=v(N):(!d(N)||c(N))&&(O=o(k))):j=!1}j&&(P.set(k,O),E(O,k,C,R,P),P.delete(k)),e(_,w,O)}return E2=b,E2}var N2,O$;function zTe(){if(O$)return N2;O$=1;var e=Fb(),t=wV(),n=NI(),r=$Te(),o=Di(),i=Zu(),s=SV();function a(l,c,d,f,p){l!==c&&n(c,function(g,v){if(p||(p=new e),o(g))r(l,c,v,d,a,f,p);else{var b=f?f(s(l,v),g,v+"",l,c,p):void 0;b===void 0&&(b=g),t(l,v,b)}},i)}return N2=a,N2}var R2,D$;function HTe(){if(D$)return R2;D$=1;var e=Kb(),t=Xb();function n(r){return e(function(o,i){var s=-1,a=i.length,l=a>1?ia-1:void 0,c=a>2?i2:void 0;for(l=r.length>3&&typeof l=="function"?(a--,l):void 0,c&&t(i0,i1,c)&&(l=a<3?void 0:l,a=1),o=Object(o);++s<a;){var d=is;d&&r(o,d,s,l)}return o})}return R2=n,R2}var T2,F$;function BTe(){if(F$)return T2;F$=1;var e=zTe(),t=HTe(),n=t(function(r,o,i){e(r,o,i)});return T2=n,T2}var k2,$$;function _V(){if($$)return k2;$$=1;function e(t,n){return t<n}return k2=e,k2}var P2,z$;function WTe(){if(z$)return P2;z$=1;var e=MI(),t=_V(),n=Qu();function r(o){return o&&o.length?e(o,n,t):void 0}return P2=r,P2}var I2,H$;function UTe(){if(H$)return I2;H$=1;var e=MI(),t=ul(),n=_V();function r(o,i){return o&&o.length?e(o,t(i,2),n):void 0}return I2=r,I2}var A2,B$;function qTe(){if(B$)return A2;B$=1;var e=bs(),t=function(){return e.Date.now()};return A2=t,A2}var M2,W$;function GTe(){if(W$)return M2;W$=1;var e=zb(),t=Vb(),n=Hb(),r=Di(),o=yg();function i(s,a,l,c){if(!r(s))return s;a=t(a,s);for(var d=-1,f=a.length,p=f-1,g=s;g!=null&&++d<f;){var v=o(ad),b=l;if(v==="__proto__"||v==="constructor"||v==="prototype")return s;if(d!=p){var _=gv;b=c?c(_,v,g):void 0,b===void 0&&(b=r(_)?_:n(ad+1)?:{})}e(g,v,b),g=gv}return s}return M2=i,M2}var j2,U$;function VTe(){if(U$)return j2;U$=1;var e=Yb(),t=GTe(),n=Vb();function r(o,i,s){for(var a=-1,l=i.length,c={};++a<l;){var d=ia,f=e(o,d);s(f,d)&&t(c,n(d,o),f)}return c}return j2=r,j2}var L2,q$;function YTe(){if(q$)return L2;q$=1;var e=VTe(),t=tV();function n(r,o){return e(r,o,function(i,s){return t(r,s)})}return L2=n,L2}var O2,G$;function KTe(){if(G$)return O2;G$=1;var e=bV(),t=cV(),n=uV();function r(o){return n(t(o,void 0,e),o+"")}return O2=r,O2}var D2,V$;function XTe(){if(V$)return D2;V$=1;var e=YTe(),t=KTe(),n=t(function(r,o){return r==null?{}:e(r,o)});return D2=n,D2}var F2,Y$;function ZTe(){if(Y$)return F2;Y$=1;var e=Math.ceil,t=Math.max;function n(r,o,i,s){for(var a=-1,l=t(e((o-r)/(i||1)),0),c=Array(l);l--;)cs?l:++a=r,r+=i;return c}return F2=n,F2}var $2,K$;function QTe(){if(K$)return $2;K$=1;var e=ZTe(),t=Xb(),n=xV();function r(o){return function(i,s,a){return a&&typeof a!="number"&&t(i,s,a)&&(s=a=void 0),i=n(i),s===void 0?(s=i,i=0):s=n(s),a=a===void 0?i<s?1:-1:n(a),e(i,s,a,o)}}return $2=r,$2}var z2,X$;function JTe(){if(X$)return z2;X$=1;var e=QTe(),t=e();return z2=t,z2}var H2,Z$;function eke(){if(Z$)return H2;Z$=1;function e(t,n){var r=t.length;for(t.sort(n);r--;)tr=tr.value;return t}return H2=e,H2}var B2,Q$;function tke(){if(Q$)return B2;Q$=1;var e=ph();function t(n,r){if(n!==r){var o=n!==void 0,i=n===null,s=n===n,a=e(n),l=r!==void 0,c=r===null,d=r===r,f=e(r);if(!c&&!f&&!a&&n>r||a&&l&&d&&!c&&!f||i&&l&&d||!o&&d||!s)return 1;if(!i&&!a&&!f&&n<r||f&&o&&s&&!i&&!a||c&&o&&s||!l&&s||!d)return-1}return 0}return B2=t,B2}var W2,J$;function nke(){if(J$)return W2;J$=1;var e=tke();function t(n,r,o){for(var i=-1,s=n.criteria,a=r.criteria,l=s.length,c=o.length;++i<l;){var d=e(si,ai);if(d){if(i>=c)return d;var f=oi;return d*(f=="desc"?-1:1)}}return n.index-r.index}return W2=t,W2}var U2,ez;function rke(){if(ez)return U2;ez=1;var e=Gb(),t=Yb(),n=ul(),r=sV(),o=eke(),i=Bb(),s=nke(),a=Qu(),l=ur();function c(d,f,p){f.length?f=e(f,function(b){return l(b)?function(_){return t(_,b.length===1?b0:b)}:b}):f=a;var g=-1;f=e(f,i(n));var v=r(d,function(b,_,x){var w=e(f,function(C){return C(b)});return{criteria:w,index:++g,value:b}});return o(v,function(b,_){return s(b,_,p)})}return U2=c,U2}var q2,tz;function oke(){if(tz)return q2;tz=1;var e=II(),t=rke(),n=Kb(),r=Xb(),o=n(function(i,s){if(i==null)return;var a=s.length;return a>1&&r(i,s0,s1)?s=:a>2&&r(s0,s1,s2)&&(s=s0),t(i,e(s,1),)});return q2=o,q2}var G2,nz;function ike(){if(nz)return G2;nz=1;var e=JG(),t=0;function n(r){var o=++t;return e(r)+o}return G2=n,G2}var V2,rz;function ske(){if(rz)return V2;rz=1;function e(t,n,r){for(var o=-1,i=t.length,s=n.length,a={};++o<i;){var l=o<s?no:void 0;r(a,to,l)}return a}return V2=e,V2}var Y2,oz;function ake(){if(oz)return Y2;oz=1;var e=zb(),t=ske();function n(r,o){return t(r||,o||,e)}return Y2=n,Y2}var Jy;if(typeof mI=="function")try{Jy={cloneDeep:_Te(),constant:EI(),defaults:CTe(),each:UG(),filter:rV(),find:ITe(),flatten:bV(),forEach:WG(),forIn:ATe(),has:oV(),isUndefined:iV(),last:MTe(),map:aV(),mapValues:jTe(),max:OTe(),merge:BTe(),min:WTe(),minBy:UTe(),now:qTe(),pick:XTe(),range:JTe(),reduce:lV(),sortBy:oke(),uniqueId:ike(),values:hV(),zipObject:ake()}}catch{}Jy||(Jy=window._);var Ln=Jy,lke=Zb;function Zb(){var e={};e._next=e._prev=e,this._sentinel=e}Zb.prototype.dequeue=function(){var e=this._sentinel,t=e._prev;if(t!==e)return CV(t),t};Zb.prototype.enqueue=function(e){var t=this._sentinel;e._prev&&e._next&&CV(e),e._next=t._next,t._next._prev=e,t._next=e,e._prev=t};Zb.prototype.toString=function(){for(var e=,t=this._sentinel,n=t._prev;n!==t;)e.push(JSON.stringify(n,cke)),n=n._prev;return""+e.join(", ")+""};function CV(e){e._prev._next=e._next,e._next._prev=e._prev,delete e._next,delete e._prev}function cke(e,t){if(e!=="_next"&&e!=="_prev")return t}var Wa=Ln,uke=ws.Graph,dke=lke,fke=pke,hke=Wa.constant(1);function pke(e,t){if(e.nodeCount()<=1)return;var n=gke(e,t||hke),r=mke(n.graph,n.buckets,n.zeroIdx);return Wa.flatten(Wa.map(r,function(o){return e.outEdges(o.v,o.w)}),!0)}function mke(e,t,n){for(var r=,o=tt.length-1,i=t0,s;e.nodeCount();){for(;s=i.dequeue();)K2(e,t,n,s);for(;s=o.dequeue();)K2(e,t,n,s);if(e.nodeCount()){for(var a=t.length-2;a>0;--a)if(s=ta.dequeue(),s){r=r.concat(K2(e,t,n,s,!0));break}}}return r}function K2(e,t,n,r,o){var i=o?:void 0;return Wa.forEach(e.inEdges(r.v),function(s){var a=e.edge(s),l=e.node(s.v);o&&i.push({v:s.v,w:s.w}),l.out-=a,sT(t,n,l)}),Wa.forEach(e.outEdges(r.v),function(s){var a=e.edge(s),l=s.w,c=e.node(l);c.in-=a,sT(t,n,c)}),e.removeNode(r.v),i}function gke(e,t){var n=new uke,r=0,o=0;Wa.forEach(e.nodes(),function(a){n.setNode(a,{v:a,in:0,out:0})}),Wa.forEach(e.edges(),function(a){var l=n.edge(a.v,a.w)||0,c=t(a),d=l+c;n.setEdge(a.v,a.w,d),o=Math.max(o,n.node(a.v).out+=c),r=Math.max(r,n.node(a.w).in+=c)});var i=Wa.range(o+r+3).map(function(){return new dke}),s=r+1;return Wa.forEach(n.nodes(),function(a){sT(i,s,n.node(a))}),{graph:n,buckets:i,zeroIdx:s}}function sT(e,t,n){n.out?n.in?en.out-n.in+t.enqueue(n):ee.length-1.enqueue(n):e0.enqueue(n)}var mu=Ln,vke=fke,yke={run:xke,undo:wke};function xke(e){var t=e.graph().acyclicer==="greedy"?vke(e,n(e)):bke(e);mu.forEach(t,function(r){var o=e.edge(r);e.removeEdge(r),o.forwardName=r.name,o.reversed=!0,e.setEdge(r.w,r.v,o,mu.uniqueId("rev"))});function n(r){return function(o){return r.edge(o).weight}}}function bke(e){var t=,n={},r={};function o(i){mu.has(r,i)||(ri=!0,ni=!0,mu.forEach(e.outEdges(i),function(s){mu.has(n,s.w)?t.push(s):o(s.w)}),delete ni)}return mu.forEach(e.nodes(),o),t}function wke(e){mu.forEach(e.edges(),function(t){var n=e.edge(t);if(n.reversed){e.removeEdge(t);var r=n.forwardName;delete n.reversed,delete n.forwardName,e.setEdge(t.w,t.v,n,r)}})}var Qt=Ln,EV=ws.Graph,Oo={addDummyNode:NV,simplify:Ske,asNonCompoundGraph:_ke,successorWeights:Cke,predecessorWeights:Eke,intersectRect:Nke,buildLayerMatrix:Rke,normalizeRanks:Tke,removeEmptyRanks:kke,addBorderNode:Pke,maxRank:RV,partition:Ike,time:Ake,notime:Mke};function NV(e,t,n,r){var o;do o=Qt.uniqueId(r);while(e.hasNode(o));return n.dummy=t,e.setNode(o,n),o}function Ske(e){var t=new EV().setGraph(e.graph());return Qt.forEach(e.nodes(),function(n){t.setNode(n,e.node(n))}),Qt.forEach(e.edges(),function(n){var r=t.edge(n.v,n.w)||{weight:0,minlen:1},o=e.edge(n);t.setEdge(n.v,n.w,{weight:r.weight+o.weight,minlen:Math.max(r.minlen,o.minlen)})}),t}function _ke(e){var t=new EV({multigraph:e.isMultigraph()}).setGraph(e.graph());return Qt.forEach(e.nodes(),function(n){e.children(n).length||t.setNode(n,e.node(n))}),Qt.forEach(e.edges(),function(n){t.setEdge(n,e.edge(n))}),t}function Cke(e){var t=Qt.map(e.nodes(),function(n){var r={};return Qt.forEach(e.outEdges(n),function(o){ro.w=(ro.w||0)+e.edge(o).weight}),r});return Qt.zipObject(e.nodes(),t)}function Eke(e){var t=Qt.map(e.nodes(),function(n){var r={};return Qt.forEach(e.inEdges(n),function(o){ro.v=(ro.v||0)+e.edge(o).weight}),r});return Qt.zipObject(e.nodes(),t)}function Nke(e,t){var n=e.x,r=e.y,o=t.x-n,i=t.y-r,s=e.width/2,a=e.height/2;if(!o&&!i)throw new Error("Not possible to find intersection inside of the rectangle");var l,c;return Math.abs(i)*s>Math.abs(o)*a?(i<0&&(a=-a),l=a*o/i,c=a):(o<0&&(s=-s),l=s,c=s*i/o),{x:n+l,y:r+c}}function Rke(e){var t=Qt.map(Qt.range(RV(e)+1),function(){return});return Qt.forEach(e.nodes(),function(n){var r=e.node(n),o=r.rank;Qt.isUndefined(o)||(tor.order=n)}),t}function Tke(e){var t=Qt.min(Qt.map(e.nodes(),function(n){return e.node(n).rank}));Qt.forEach(e.nodes(),function(n){var r=e.node(n);Qt.has(r,"rank")&&(r.rank-=t)})}function kke(e){var t=Qt.min(Qt.map(e.nodes(),function(i){return e.node(i).rank})),n=;Qt.forEach(e.nodes(),function(i){var s=e.node(i).rank-t;ns||(ns=),ns.push(i)});var r=0,o=e.graph().nodeRankFactor;Qt.forEach(n,function(i,s){Qt.isUndefined(i)&&s%o!==0?--r:r&&Qt.forEach(i,function(a){e.node(a).rank+=r})})}function Pke(e,t,n,r){var o={width:0,height:0};return arguments.length>=4&&(o.rank=n,o.order=r),NV(e,"border",o,t)}function RV(e){return Qt.max(Qt.map(e.nodes(),function(t){var n=e.node(t).rank;if(!Qt.isUndefined(n))return n}))}function Ike(e,t){var n={lhs:,rhs:};return Qt.forEach(e,function(r){t(r)?n.lhs.push(r):n.rhs.push(r)}),n}function Ake(e,t){var n=Qt.now();try{return t()}finally{console.log(e+" time: "+(Qt.now()-n)+"ms")}}function Mke(e,t){return t()}var TV=Ln,jke=Oo,Lke={run:Oke,undo:Fke};function Oke(e){e.graph().dummyChains=,TV.forEach(e.edges(),function(t){Dke(e,t)})}function Dke(e,t){var n=t.v,r=e.node(n).rank,o=t.w,i=e.node(o).rank,s=t.name,a=e.edge(t),l=a.labelRank;if(i!==r+1){e.removeEdge(t);var c,d,f;for(f=0,++r;r<i;++f,++r)a.points=,d={width:0,height:0,edgeLabel:a,edgeObj:t,rank:r},c=jke.addDummyNode(e,"edge",d,"_d"),r===l&&(d.width=a.width,d.height=a.height,d.dummy="edge-label",d.labelpos=a.labelpos),e.setEdge(n,c,{weight:a.weight},s),f===0&&e.graph().dummyChains.push(c),n=c;e.setEdge(n,o,{weight:a.weight},s)}}function Fke(e){TV.forEach(e.graph().dummyChains,function(t){var n=e.node(t),r=n.edgeLabel,o;for(e.setEdge(n.edgeObj,r);n.dummy;)o=e.successors(t)0,e.removeNode(t),r.points.push({x:n.x,y:n.y}),n.dummy==="edge-label"&&(r.x=n.x,r.y=n.y,r.width=n.width,r.height=n.height),t=o,n=e.node(t)})}var l0=Ln,Qb={longestPath:$ke,slack:zke};function $ke(e){var t={};function n(r){var o=e.node(r);if(l0.has(t,r))return o.rank;tr=!0;var i=l0.min(l0.map(e.outEdges(r),function(s){return n(s.w)-e.edge(s).minlen}));return(i===Number.POSITIVE_INFINITY||i===void 0||i===null)&&(i=0),o.rank=i}l0.forEach(e.sources(),n)}function zke(e,t){return e.node(t.w).rank-e.node(t.v).rank-e.edge(t).minlen}var ex=Ln,Hke=ws.Graph,tx=Qb.slack,kV=Bke;function Bke(e){var t=new Hke({directed:!1}),n=e.nodes()0,r=e.nodeCount();t.setNode(n,{});for(var o,i;Wke(t,e)<r;)o=Uke(t,e),i=t.hasNode(o.v)?tx(e,o):-tx(e,o),qke(t,e,i);return t}function Wke(e,t){function n(r){ex.forEach(t.nodeEdges(r),function(o){var i=o.v,s=r===i?o.w:i;!e.hasNode(s)&&!tx(t,o)&&(e.setNode(s,{}),e.setEdge(r,s,{}),n(s))})}return ex.forEach(e.nodes(),n),e.nodeCount()}function Uke(e,t){return ex.minBy(t.edges(),function(n){if(e.hasNode(n.v)!==e.hasNode(n.w))return tx(t,n)})}function qke(e,t,n){ex.forEach(e.nodes(),function(r){t.node(r).rank+=n})}var il=Ln,Gke=kV,Vke=Qb.slack,Yke=Qb.longestPath,Kke=ws.alg.preorder,Xke=ws.alg.postorder,Zke=Oo.simplify,Qke=Ju;Ju.initLowLimValues=LI;Ju.initCutValues=jI;Ju.calcCutValue=PV;Ju.leaveEdge=AV;Ju.enterEdge=MV;Ju.exchangeEdges=jV;function Ju(e){e=Zke(e),Yke(e);var t=Gke(e);LI(t),jI(t,e);for(var n,r;n=AV(t);)r=MV(t,e,n),jV(t,e,n,r)}function jI(e,t){var n=Xke(e,e.nodes());n=n.slice(0,n.length-1),il.forEach(n,function(r){Jke(e,t,r)})}function Jke(e,t,n){var r=e.node(n),o=r.parent;e.edge(n,o).cutvalue=PV(e,t,n)}function PV(e,t,n){var r=e.node(n),o=r.parent,i=!0,s=t.edge(n,o),a=0;return s||(i=!1,s=t.edge(o,n)),a=s.weight,il.forEach(t.nodeEdges(n),function(l){var c=l.v===n,d=c?l.w:l.v;if(d!==o){var f=c===i,p=t.edge(l).weight;if(a+=f?p:-p,tPe(e,n,d)){var g=e.edge(n,d).cutvalue;a+=f?-g:g}}}),a}function LI(e,t){arguments.length<2&&(t=e.nodes()0),IV(e,{},1,t)}function IV(e,t,n,r,o){var i=n,s=e.node(r);return tr=!0,il.forEach(e.neighbors(r),function(a){il.has(t,a)||(n=IV(e,t,n,a,r))}),s.low=i,s.lim=n++,o?s.parent=o:delete s.parent,n}function AV(e){return il.find(e.edges(),function(t){return e.edge(t).cutvalue<0})}function MV(e,t,n){var r=n.v,o=n.w;t.hasEdge(r,o)||(r=n.w,o=n.v);var i=e.node(r),s=e.node(o),a=i,l=!1;i.lim>s.lim&&(a=s,l=!0);var c=il.filter(t.edges(),function(d){return l===iz(e,e.node(d.v),a)&&l!==iz(e,e.node(d.w),a)});return il.minBy(c,function(d){return Vke(t,d)})}function jV(e,t,n,r){var o=n.v,i=n.w;e.removeEdge(o,i),e.setEdge(r.v,r.w,{}),LI(e),jI(e,t),ePe(e,t)}function ePe(e,t){var n=il.find(e.nodes(),function(o){return!t.node(o).parent}),r=Kke(e,n);r=r.slice(1),il.forEach(r,function(o){var i=e.node(o).parent,s=t.edge(o,i),a=!1;s||(s=t.edge(i,o),a=!0),t.node(o).rank=t.node(i).rank+(a?s.minlen:-s.minlen)})}function tPe(e,t,n){return e.hasEdge(t,n)}function iz(e,t,n){return n.low<=t.lim&&t.lim<=n.lim}var nPe=Qb,LV=nPe.longestPath,rPe=kV,oPe=Qke,iPe=sPe;function sPe(e){switch(e.graph().ranker){case"network-simplex":sz(e);break;case"tight-tree":lPe(e);break;case"longest-path":aPe(e);break;default:sz(e)}}var aPe=LV;function lPe(e){LV(e),rPe(e)}function sz(e){oPe(e)}var aT=Ln,cPe=uPe;function uPe(e){var t=fPe(e);aT.forEach(e.graph().dummyChains,function(n){for(var r=e.node(n),o=r.edgeObj,i=dPe(e,t,o.v,o.w),s=i.path,a=i.lca,l=0,c=sl,d=!0;n!==o.w;){if(r=e.node(n),d){for(;(c=sl)!==a&&e.node(c).maxRank<r.rank;)l++;c===a&&(d=!1)}if(!d){for(;l<s.length-1&&e.node(c=sl+1).minRank<=r.rank;)l++;c=sl}e.setParent(n,c),n=e.successors(n)0}})}function dPe(e,t,n,r){var o=,i=,s=Math.min(tn.low,tr.low),a=Math.max(tn.lim,tr.lim),l,c;l=n;do l=e.parent(l),o.push(l);while(l&&(tl.low>s||a>tl.lim));for(c=l,l=r;(l=e.parent(l))!==c;)i.push(l);return{path:o.concat(i.reverse()),lca:c}}function fPe(e){var t={},n=0;function r(o){var i=n;aT.forEach(e.children(o),r),to={low:i,lim:n++}}return aT.forEach(e.children(),r),t}var Ua=Ln,lT=Oo,hPe={run:pPe,cleanup:vPe};function pPe(e){var t=lT.addDummyNode(e,"root",{},"_root"),n=mPe(e),r=Ua.max(Ua.values(n))-1,o=2*r+1;e.graph().nestingRoot=t,Ua.forEach(e.edges(),function(s){e.edge(s).minlen*=o});var i=gPe(e)+1;Ua.forEach(e.children(),function(s){OV(e,t,o,i,r,n,s)}),e.graph().nodeRankFactor=o}function OV(e,t,n,r,o,i,s){var a=e.children(s);if(!a.length){s!==t&&e.setEdge(t,s,{weight:0,minlen:n});return}var l=lT.addBorderNode(e,"_bt"),c=lT.addBorderNode(e,"_bb"),d=e.node(s);e.setParent(l,s),d.borderTop=l,e.setParent(c,s),d.borderBottom=c,Ua.forEach(a,function(f){OV(e,t,n,r,o,i,f);var p=e.node(f),g=p.borderTop?p.borderTop:f,v=p.borderBottom?p.borderBottom:f,b=p.borderTop?r:2*r,_=g!==v?1:o-is+1;e.setEdge(l,g,{weight:b,minlen:_,nestingEdge:!0}),e.setEdge(v,c,{weight:b,minlen:_,nestingEdge:!0})}),e.parent(s)||e.setEdge(t,l,{weight:0,minlen:o+is})}function mPe(e){var t={};function n(r,o){var i=e.children(r);i&&i.length&&Ua.forEach(i,function(s){n(s,o+1)}),tr=o}return Ua.forEach(e.children(),function(r){n(r,1)}),t}function gPe(e){return Ua.reduce(e.edges(),function(t,n){return t+e.edge(n).weight},0)}function vPe(e){var t=e.graph();e.removeNode(t.nestingRoot),delete t.nestingRoot,Ua.forEach(e.edges(),function(n){var r=e.edge(n);r.nestingEdge&&e.removeEdge(n)})}var X2=Ln,yPe=Oo,xPe=bPe;function bPe(e){function t(n){var r=e.children(n),o=e.node(n);if(r.length&&X2.forEach(r,t),X2.has(o,"minRank")){o.borderLeft=,o.borderRight=;for(var i=o.minRank,s=o.maxRank+1;i<s;++i)az(e,"borderLeft","_bl",n,o,i),az(e,"borderRight","_br",n,o,i)}}X2.forEach(e.children(),t)}function az(e,t,n,r,o,i){var s={width:0,height:0,rank:i,borderType:t},a=oti-1,l=yPe.addDummyNode(e,"border",s,n);oti=l,e.setParent(l,r),a&&e.setEdge(a,l,{weight:1})}var ta=Ln,wPe={adjust:SPe,undo:_Pe};function SPe(e){var t=e.graph().rankdir.toLowerCase();(t==="lr"||t==="rl")&&DV(e)}function _Pe(e){var t=e.graph().rankdir.toLowerCase();(t==="bt"||t==="rl")&&CPe(e),(t==="lr"||t==="rl")&&(EPe(e),DV(e))}function DV(e){ta.forEach(e.nodes(),function(t){lz(e.node(t))}),ta.forEach(e.edges(),function(t){lz(e.edge(t))})}function lz(e){var t=e.width;e.width=e.height,e.height=t}function CPe(e){ta.forEach(e.nodes(),function(t){Z2(e.node(t))}),ta.forEach(e.edges(),function(t){var n=e.edge(t);ta.forEach(n.points,Z2),ta.has(n,"y")&&Z2(n)})}function Z2(e){e.y=-e.y}function EPe(e){ta.forEach(e.nodes(),function(t){Q2(e.node(t))}),ta.forEach(e.edges(),function(t){var n=e.edge(t);ta.forEach(n.points,Q2),ta.has(n,"x")&&Q2(n)})}function Q2(e){var t=e.x;e.x=e.y,e.y=t}var La=Ln,NPe=RPe;function RPe(e){var t={},n=La.filter(e.nodes(),function(a){return!e.children(a).length}),r=La.max(La.map(n,function(a){return e.node(a).rank})),o=La.map(La.range(r+1),function(){return});function i(a){if(!La.has(t,a)){ta=!0;var l=e.node(a);ol.rank.push(a),La.forEach(e.successors(a),i)}}var s=La.sortBy(n,function(a){return e.node(a).rank});return La.forEach(s,i),o}var Hl=Ln,TPe=kPe;function kPe(e,t){for(var n=0,r=1;r<t.length;++r)n+=PPe(e,tr-1,tr);return n}function PPe(e,t,n){for(var r=Hl.zipObject(n,Hl.map(n,function(c,d){return d})),o=Hl.flatten(Hl.map(t,function(c){return Hl.sortBy(Hl.map(e.outEdges(c),function(d){return{pos:rd.w,weight:e.edge(d).weight}}),"pos")}),!0),i=1;i<n.length;)i<<=1;var s=2*i-1;i-=1;var a=Hl.map(new Array(s),function(){return 0}),l=0;return Hl.forEach(o.forEach(function(c){var d=c.pos+i;ad+=c.weight;for(var f=0;d>0;)d%2&&(f+=ad+1),d=d-1>>1,ad+=c.weight;l+=c.weight*f})),l}var cz=Ln,IPe=APe;function APe(e,t){return cz.map(t,function(n){var r=e.inEdges(n);if(r.length){var o=cz.reduce(r,function(i,s){var a=e.edge(s),l=e.node(s.v);return{sum:i.sum+a.weight*l.order,weight:i.weight+a.weight}},{sum:0,weight:0});return{v:n,barycenter:o.sum/o.weight,weight:o.weight}}else return{v:n}})}var Go=Ln,MPe=jPe;function jPe(e,t){var n={};Go.forEach(e,function(o,i){var s=no.v={indegree:0,in:,out:,vs:o.v,i};Go.isUndefined(o.barycenter)||(s.barycenter=o.barycenter,s.weight=o.weight)}),Go.forEach(t.edges(),function(o){var i=no.v,s=no.w;!Go.isUndefined(i)&&!Go.isUndefined(s)&&(s.indegree++,i.out.push(no.w))});var r=Go.filter(n,function(o){return!o.indegree});return LPe(r)}function LPe(e){var t=;function n(i){return function(s){s.merged||(Go.isUndefined(s.barycenter)||Go.isUndefined(i.barycenter)||s.barycenter>=i.barycenter)&&OPe(i,s)}}function r(i){return function(s){s.in.push(i),--s.indegree===0&&e.push(s)}}for(;e.length;){var o=e.pop();t.push(o),Go.forEach(o.in.reverse(),n(o)),Go.forEach(o.out,r(o))}return Go.map(Go.filter(t,function(i){return!i.merged}),function(i){return Go.pick(i,"vs","i","barycenter","weight")})}function OPe(e,t){var n=0,r=0;e.weight&&(n+=e.barycenter*e.weight,r+=e.weight),t.weight&&(n+=t.barycenter*t.weight,r+=t.weight),e.vs=t.vs.concat(e.vs),e.barycenter=n/r,e.weight=r,e.i=Math.min(t.i,e.i),t.merged=!0}var kp=Ln,DPe=Oo,FPe=$Pe;function $Pe(e,t){var n=DPe.partition(e,function(d){return kp.has(d,"barycenter")}),r=n.lhs,o=kp.sortBy(n.rhs,function(d){return-d.i}),i=,s=0,a=0,l=0;r.sort(zPe(!!t)),l=uz(i,o,l),kp.forEach(r,function(d){l+=d.vs.length,i.push(d.vs),s+=d.barycenter*d.weight,a+=d.weight,l=uz(i,o,l)});var c={vs:kp.flatten(i,!0)};return a&&(c.barycenter=s/a,c.weight=a),c}function uz(e,t,n){for(var r;t.length&&(r=kp.last(t)).i<=n;)t.pop(),e.push(r.vs),n++;return n}function zPe(e){return function(t,n){return t.barycenter<n.barycenter?-1:t.barycenter>n.barycenter?1:e?n.i-t.i:t.i-n.i}}var Zl=Ln,HPe=IPe,BPe=MPe,WPe=FPe,UPe=FV;function FV(e,t,n,r){var o=e.children(t),i=e.node(t),s=i?i.borderLeft:void 0,a=i?i.borderRight:void 0,l={};s&&(o=Zl.filter(o,function(v){return v!==s&&v!==a}));var c=HPe(e,o);Zl.forEach(c,function(v){if(e.children(v.v).length){var b=FV(e,v.v,n,r);lv.v=b,Zl.has(b,"barycenter")&&GPe(v,b)}});var d=BPe(c,n);qPe(d,l);var f=WPe(d,r);if(s&&(f.vs=Zl.flatten(s,f.vs,a,!0),e.predecessors(s).length)){var p=e.node(e.predecessors(s)0),g=e.node(e.predecessors(a)0);Zl.has(f,"barycenter")||(f.barycenter=0,f.weight=0),f.barycenter=(f.barycenter*f.weight+p.order+g.order)/(f.weight+2),f.weight+=2}return f}function qPe(e,t){Zl.forEach(e,function(n){n.vs=Zl.flatten(n.vs.map(function(r){return tr?tr.vs:r}),!0)})}function GPe(e,t){Zl.isUndefined(e.barycenter)?(e.barycenter=t.barycenter,e.weight=t.weight):(e.barycenter=(e.barycenter*e.weight+t.barycenter*t.weight)/(e.weight+t.weight),e.weight+=t.weight)}var Pp=Ln,VPe=ws.Graph,YPe=KPe;function KPe(e,t,n){var r=XPe(e),o=new VPe({compound:!0}).setGraph({root:r}).setDefaultNodeLabel(function(i){return e.node(i)});return Pp.forEach(e.nodes(),function(i){var s=e.node(i),a=e.parent(i);(s.rank===t||s.minRank<=t&&t<=s.maxRank)&&(o.setNode(i),o.setParent(i,a||r),Pp.forEach(en(i),function(l){var c=l.v===i?l.w:l.v,d=o.edge(c,i),f=Pp.isUndefined(d)?0:d.weight;o.setEdge(c,i,{weight:e.edge(l).weight+f})}),Pp.has(s,"minRank")&&o.setNode(i,{borderLeft:s.borderLeftt,borderRight:s.borderRightt}))}),o}function XPe(e){for(var t;e.hasNode(t=Pp.uniqueId("_root")););return t}var ZPe=Ln,QPe=JPe;function JPe(e,t,n){var r={},o;ZPe.forEach(n,function(i){for(var s=e.parent(i),a,l;s;){if(a=e.parent(s),a?(l=ra,ra=s):(l=o,o=s),l&&l!==s){t.setEdge(l,s);return}s=a}})}var uc=Ln,eIe=NPe,tIe=TPe,nIe=UPe,rIe=YPe,oIe=QPe,iIe=ws.Graph,dz=Oo,sIe=aIe;function aIe(e){var t=dz.maxRank(e),n=fz(e,uc.range(1,t+1),"inEdges"),r=fz(e,uc.range(t-1,-1,-1),"outEdges"),o=eIe(e);hz(e,o);for(var i=Number.POSITIVE_INFINITY,s,a=0,l=0;l<4;++a,++l){lIe(a%2?n:r,a%4>=2),o=dz.buildLayerMatrix(e);var c=tIe(e,o);c<i&&(l=0,s=uc.cloneDeep(o),i=c)}hz(e,s)}function fz(e,t,n){return uc.map(t,function(r){return rIe(e,r,n)})}function lIe(e,t){var n=new iIe;uc.forEach(e,function(r){var o=r.graph().root,i=nIe(r,o,n,t);uc.forEach(i.vs,function(s,a){r.node(s).order=a}),oIe(r,n,i.vs)})}function hz(e,t){uc.forEach(t,function(n){uc.forEach(n,function(r,o){e.node(r).order=o})})}var ut=Ln,cIe=ws.Graph,uIe=Oo,dIe={positionX:SIe};function fIe(e,t){var n={};function r(o,i){var s=0,a=0,l=o.length,c=ut.last(i);return ut.forEach(i,function(d,f){var p=pIe(e,d),g=p?e.node(p).order:l;(p||d===c)&&(ut.forEach(i.slice(a,f+1),function(v){ut.forEach(e.predecessors(v),function(b){var _=e.node(b),x=_.order;(x<s||g<x)&&!(_.dummy&&e.node(v).dummy)&&$V(n,b,v)})}),a=f+1,s=g)}),i}return ut.reduce(t,r),n}function hIe(e,t){var n={};function r(i,s,a,l,c){var d;ut.forEach(ut.range(s,a),function(f){d=if,e.node(d).dummy&&ut.forEach(e.predecessors(d),function(p){var g=e.node(p);g.dummy&&(g.order<l||g.order>c)&&$V(n,p,d)})})}function o(i,s){var a=-1,l,c=0;return ut.forEach(s,function(d,f){if(e.node(d).dummy==="border"){var p=e.predecessors(d);p.length&&(l=e.node(p0).order,r(s,c,f,a,l),c=f,a=l)}r(s,c,s.length,l,i.length)}),s}return ut.reduce(t,o),n}function pIe(e,t){if(e.node(t).dummy)return ut.find(e.predecessors(t),function(n){return e.node(n).dummy})}function $V(e,t,n){if(t>n){var r=t;t=n,n=r}var o=et;o||(et=o={}),on=!0}function mIe(e,t,n){if(t>n){var r=t;t=n,n=r}return ut.has(et,n)}function gIe(e,t,n,r){var o={},i={},s={};return ut.forEach(t,function(a){ut.forEach(a,function(l,c){ol=l,il=l,sl=c})}),ut.forEach(t,function(a){var l=-1;ut.forEach(a,function(c){var d=r(c);if(d.length){d=ut.sortBy(d,function(b){return sb});for(var f=(d.length-1)/2,p=Math.floor(f),g=Math.ceil(f);p<=g;++p){var v=dp;ic===c&&l<sv&&!mIe(n,c,v)&&(iv=c,ic=oc=ov,l=sv)}}})}),{root:o,align:i}}function vIe(e,t,n,r,o){var i={},s=yIe(e,t,n,o),a=o?"borderLeft":"borderRight";function l(f,p){for(var g=s.nodes(),v=g.pop(),b={};v;)bv?f(v):(bv=!0,g.push(v),g=g.concat(p(v))),v=g.pop()}function c(f){if=s.inEdges(f).reduce(function(p,g){return Math.max(p,ig.v+s.edge(g))},0)}function d(f){var p=s.outEdges(f).reduce(function(v,b){return Math.min(v,ib.w-s.edge(b))},Number.POSITIVE_INFINITY),g=e.node(f);p!==Number.POSITIVE_INFINITY&&g.borderType!==a&&(if=Math.max(if,p))}return l(c,s.predecessors.bind(s)),l(d,s.successors.bind(s)),ut.forEach(r,function(f){if=inf}),i}function yIe(e,t,n,r){var o=new cIe,i=e.graph(),s=_Ie(i.nodesep,i.edgesep,r);return ut.forEach(t,function(a){var l;ut.forEach(a,function(c){var d=nc;if(o.setNode(d),l){var f=nl,p=o.edge(f,d);o.setEdge(f,d,Math.max(s(e,c,l),p||0))}l=c})}),o}function xIe(e,t){return ut.minBy(ut.values(t),function(n){var r=Number.NEGATIVE_INFINITY,o=Number.POSITIVE_INFINITY;return ut.forIn(n,function(i,s){var a=CIe(e,s)/2;r=Math.max(i+a,r),o=Math.min(i-a,o)}),r-o})}function bIe(e,t){var n=ut.values(t),r=ut.min(n),o=ut.max(n);ut.forEach("u","d",function(i){ut.forEach("l","r",function(s){var a=i+s,l=ea,c;if(l!==t){var d=ut.values(l);c=s==="l"?r-ut.min(d):o-ut.max(d),c&&(ea=ut.mapValues(l,function(f){return f+c}))}})})}function wIe(e,t){return ut.mapValues(e.ul,function(n,r){if(t)return et.toLowerCase()r;var o=ut.sortBy(ut.map(e,r));return(o1+o2)/2})}function SIe(e){var t=uIe.buildLayerMatrix(e),n=ut.merge(fIe(e,t),hIe(e,t)),r={},o;ut.forEach("u","d",function(s){o=s==="u"?t:ut.values(t).reverse(),ut.forEach("l","r",function(a){a==="r"&&(o=ut.map(o,function(f){return ut.values(f).reverse()}));var l=(s==="u"?e.predecessors:e.successors).bind(e),c=gIe(e,o,n,l),d=vIe(e,o,c.root,c.align,a==="r");a==="r"&&(d=ut.mapValues(d,function(f){return-f})),rs+a=d})});var i=xIe(e,r);return bIe(r,i),wIe(r,e.graph().align)}function _Ie(e,t,n){return function(r,o,i){var s=r.node(o),a=r.node(i),l=0,c;if(l+=s.width/2,ut.has(s,"labelpos"))switch(s.labelpos.toLowerCase()){case"l":c=-s.width/2;break;case"r":c=s.width/2;break}if(c&&(l+=n?c:-c),c=0,l+=(s.dummy?t:e)/2,l+=(a.dummy?t:e)/2,l+=a.width/2,ut.has(a,"labelpos"))switch(a.labelpos.toLowerCase()){case"l":c=a.width/2;break;case"r":c=-a.width/2;break}return c&&(l+=n?c:-c),c=0,l}}function CIe(e,t){return e.node(t).width}var Ip=Ln,zV=Oo,EIe=dIe.positionX,NIe=RIe;function RIe(e){e=zV.asNonCompoundGraph(e),TIe(e),Ip.forEach(EIe(e),function(t,n){e.node(n).x=t})}function TIe(e){var t=zV.buildLayerMatrix(e),n=e.graph().ranksep,r=0;Ip.forEach(t,function(o){var i=Ip.max(Ip.map(o,function(s){return e.node(s).height}));Ip.forEach(o,function(s){e.node(s).y=r+i/2}),r+=i+n})}var Tt=Ln,pz=yke,mz=Lke,kIe=iPe,PIe=Oo.normalizeRanks,IIe=cPe,AIe=Oo.removeEmptyRanks,gz=hPe,MIe=xPe,vz=wPe,jIe=sIe,LIe=NIe,yc=Oo,OIe=ws.Graph,DIe=FIe;function FIe(e,t){var n=t&&t.debugTiming?yc.time:yc.notime;n("layout",function(){var r=n(" buildLayoutGraph",function(){return KIe(e)});n(" runLayout",function(){$Ie(r,n)}),n(" updateInputGraph",function(){zIe(e,r)})})}function $Ie(e,t){t(" makeSpaceForEdgeLabels",function(){XIe(e)}),t(" removeSelfEdges",function(){iAe(e)}),t(" acyclic",function(){pz.run(e)}),t(" nestingGraph.run",function(){gz.run(e)}),t(" rank",function(){kIe(yc.asNonCompoundGraph(e))}),t(" injectEdgeLabelProxies",function(){ZIe(e)}),t(" removeEmptyRanks",function(){AIe(e)}),t(" nestingGraph.cleanup",function(){gz.cleanup(e)}),t(" normalizeRanks",function(){PIe(e)}),t(" assignRankMinMax",function(){QIe(e)}),t(" removeEdgeLabelProxies",function(){JIe(e)}),t(" normalize.run",function(){mz.run(e)}),t(" parentDummyChains",function(){IIe(e)}),t(" addBorderSegments",function(){MIe(e)}),t(" order",function(){jIe(e)}),t(" insertSelfEdges",function(){sAe(e)}),t(" adjustCoordinateSystem",function(){vz.adjust(e)}),t(" position",function(){LIe(e)}),t(" positionSelfEdges",function(){aAe(e)}),t(" removeBorderNodes",function(){oAe(e)}),t(" normalize.undo",function(){mz.undo(e)}),t(" fixupEdgeLabelCoords",function(){nAe(e)}),t(" undoCoordinateSystem",function(){vz.undo(e)}),t(" translateGraph",function(){eAe(e)}),t(" assignNodeIntersects",function(){tAe(e)}),t(" reversePoints",function(){rAe(e)}),t(" acyclic.undo",function(){pz.undo(e)})}function zIe(e,t){Tt.forEach(e.nodes(),function(n){var r=e.node(n),o=t.node(n);r&&(r.x=o.x,r.y=o.y,t.children(n).length&&(r.width=o.width,r.height=o.height))}),Tt.forEach(e.edges(),function(n){var r=e.edge(n),o=t.edge(n);r.points=o.points,Tt.has(o,"x")&&(r.x=o.x,r.y=o.y)}),e.graph().width=t.graph().width,e.graph().height=t.graph().height}var HIe="nodesep","edgesep","ranksep","marginx","marginy",BIe={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},WIe="acyclicer","ranker","rankdir","align",UIe="width","height",qIe={width:0,height:0},GIe="minlen","weight","width","height","labeloffset",VIe={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},YIe="labelpos";function KIe(e){var t=new OIe({multigraph:!0,compound:!0}),n=eN(e.graph());return t.setGraph(Tt.merge({},BIe,J2(n,HIe),Tt.pick(n,WIe))),Tt.forEach(e.nodes(),function(r){var o=eN(e.node(r));t.setNode(r,Tt.defaults(J2(o,UIe),qIe)),t.setParent(r,e.parent(r))}),Tt.forEach(e.edges(),function(r){var o=eN(e.edge(r));t.setEdge(r,Tt.merge({},VIe,J2(o,GIe),Tt.pick(o,YIe)))}),t}function XIe(e){var t=e.graph();t.ranksep/=2,Tt.forEach(e.edges(),function(n){var r=e.edge(n);r.minlen*=2,r.labelpos.toLowerCase()!=="c"&&(t.rankdir==="TB"||t.rankdir==="BT"?r.width+=r.labeloffset:r.height+=r.labeloffset)})}function ZIe(e){Tt.forEach(e.edges(),function(t){var n=e.edge(t);if(n.width&&n.height){var r=e.node(t.v),o=e.node(t.w),i={rank:(o.rank-r.rank)/2+r.rank,e:t};yc.addDummyNode(e,"edge-proxy",i,"_ep")}})}function QIe(e){var t=0;Tt.forEach(e.nodes(),function(n){var r=e.node(n);r.borderTop&&(r.minRank=e.node(r.borderTop).rank,r.maxRank=e.node(r.borderBottom).rank,t=Tt.max(t,r.maxRank))}),e.graph().maxRank=t}function JIe(e){Tt.forEach(e.nodes(),function(t){var n=e.node(t);n.dummy==="edge-proxy"&&(e.edge(n.e).labelRank=n.rank,e.removeNode(t))})}function eAe(e){var t=Number.POSITIVE_INFINITY,n=0,r=Number.POSITIVE_INFINITY,o=0,i=e.graph(),s=i.marginx||0,a=i.marginy||0;function l(c){var d=c.x,f=c.y,p=c.width,g=c.height;t=Math.min(t,d-p/2),n=Math.max(n,d+p/2),r=Math.min(r,f-g/2),o=Math.max(o,f+g/2)}Tt.forEach(e.nodes(),function(c){l(e.node(c))}),Tt.forEach(e.edges(),function(c){var d=e.edge(c);Tt.has(d,"x")&&l(d)}),t-=s,r-=a,Tt.forEach(e.nodes(),function(c){var d=e.node(c);d.x-=t,d.y-=r}),Tt.forEach(e.edges(),function(c){var d=e.edge(c);Tt.forEach(d.points,function(f){f.x-=t,f.y-=r}),Tt.has(d,"x")&&(d.x-=t),Tt.has(d,"y")&&(d.y-=r)}),i.width=n-t+s,i.height=o-r+a}function tAe(e){Tt.forEach(e.edges(),function(t){var n=e.edge(t),r=e.node(t.v),o=e.node(t.w),i,s;n.points?(i=n.points0,s=n.pointsn.points.length-1):(n.points=,i=o,s=r),n.points.unshift(yc.intersectRect(r,i)),n.points.push(yc.intersectRect(o,s))})}function nAe(e){Tt.forEach(e.edges(),function(t){var n=e.edge(t);if(Tt.has(n,"x"))switch((n.labelpos==="l"||n.labelpos==="r")&&(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset;break}})}function rAe(e){Tt.forEach(e.edges(),function(t){var n=e.edge(t);n.reversed&&n.points.reverse()})}function oAe(e){Tt.forEach(e.nodes(),function(t){if(e.children(t).length){var n=e.node(t),r=e.node(n.borderTop),o=e.node(n.borderBottom),i=e.node(Tt.last(n.borderLeft)),s=e.node(Tt.last(n.borderRight));n.width=Math.abs(s.x-i.x),n.height=Math.abs(o.y-r.y),n.x=i.x+n.width/2,n.y=r.y+n.height/2}}),Tt.forEach(e.nodes(),function(t){e.node(t).dummy==="border"&&e.removeNode(t)})}function iAe(e){Tt.forEach(e.edges(),function(t){if(t.v===t.w){var n=e.node(t.v);n.selfEdges||(n.selfEdges=),n.selfEdges.push({e:t,label:e.edge(t)}),e.removeEdge(t)}})}function sAe(e){var t=yc.buildLayerMatrix(e);Tt.forEach(t,function(n){var r=0;Tt.forEach(n,function(o,i){var s=e.node(o);s.order=i+r,Tt.forEach(s.selfEdges,function(a){yc.addDummyNode(e,"selfedge",{width:a.label.width,height:a.label.height,rank:s.rank,order:i+ ++r,e:a.e,label:a.label},"_se")}),delete s.selfEdges})})}function aAe(e){Tt.forEach(e.nodes(),function(t){var n=e.node(t);if(n.dummy==="selfedge"){var r=e.node(n.e.v),o=r.x+r.width/2,i=r.y,s=n.x-o,a=r.height/2;e.setEdge(n.e,n.label),e.removeNode(t),n.label.points={x:o+2*s/3,y:i-a},{x:o+5*s/6,y:i-a},{x:o+s,y:i},{x:o+5*s/6,y:i+a},{x:o+2*s/3,y:i+a},n.label.x=n.x,n.label.y=n.y}})}function J2(e,t){return Tt.mapValues(Tt.pick(e,t),Number)}function eN(e){var t={};return Tt.forEach(e,function(n,r){tr.toLowerCase()=n}),t}var c0=Ln,lAe=Oo,cAe=ws.Graph,uAe={debugOrdering:dAe};function dAe(e){var t=lAe.buildLayerMatrix(e),n=new cAe({compound:!0,multigraph:!0}).setGraph({});return c0.forEach(e.nodes(),function(r){n.setNode(r,{label:r}),n.setParent(r,"layer"+e.node(r).rank)}),c0.forEach(e.edges(),function(r){n.setEdge(r.v,r.w,{},r.name)}),c0.forEach(t,function(r,o){var i="layer"+o;n.setNode(i,{rank:"same"}),c0.reduce(r,function(s,a){return n.setEdge(s,a,{style:"invis"}),a})}),n}var fAe="0.8.5",hAe={graphlib:ws,layout:DIe,debug:uAe,util:{time:Oo.time,notime:Oo.notime},version:fAe};const yz=qf(hAe);var HV=(e=>(e.DAGRE="dagre",e))(HV||{});const pAe=(e,t)=>{if(e.length===0||e.some(r=>!r.measured))return e;const n=new yz.graphlib.Graph;return n.setGraph({rankdir:"LR",nodesep:250,ranksep:100}),e.forEach(r=>{var o,i;n.setNode(r.id,{width:((o=r.measured)==null?void 0:o.width)||100,height:((i=r.measured)==null?void 0:i.height)||100})}),t.forEach(r=>{n.setEdge(r.source,r.target,{points:})}),yz.layout(n),e.map(r=>{const o=n.node(r.id);if(!o)return r;const{x:i,y:s,width:a,height:l}=o;return{...r,position:{x:i-a/2,y:s-l/2}}})},mAe=({localNodes:e,localEdges:t,setLocalNodes:n,nodesRef:r,isApplyingLayout:o})=>{consti,s=y.useState(()=>{try{const f=localStorage.getItem("gpacMonitorLayout");if(f)return JSON.parse(f)}catch(f){console.error(f)}return{type:HV.DAGRE,direction:"LR",nodeSeparation:150,rankSeparation:250,respectExistingPositions:!0}}),a=y.useCallback(f=>{if(f.length===0)return;o.current=!0;const p=pAe(f,t);n(p),r.current=p,setTimeout(()=>{o.current=!1},100)},t,n,r,o),l=y.useCallback(()=>{a(e)},e,a),c=y.useCallback(()=>{a(e)},e,a),d=y.useCallback(f=>{e.length!==0&&(e.some(p=>!p.measured)||(s(f),a(e)))},e,a);return y.useEffect(()=>{try{i.type&&localStorage.setItem("gpacMonitorLayout",JSON.stringify(i))}catch(f){console.error(f)}},i),{layoutOptions:i,handleLayoutChange:d,autoLayout:c,applyLayout:l}};function gAe(e,t){return e.map(n=>{const r=t.current.find(o=>o.id===n.id);return r?{...n,position:r.position,selected:r.selected,dragging:r.dragging}:n})}function vAe(e,t){return e.map(n=>{const r=t.current.find(o=>o.id===n.id);return r?{...n,selected:r.selected,animated:r.animated}:n})}const yAe=(e,t)=>{constn,r=y.useState(null),o=Xe(Yye),i=Xe(Kye),s=Xe(Xye),a=Xe(Zye),l=y.useRef(0),c=y.useMemo(()=>gAe(o,e),o,e),d=y.useMemo(()=>vAe(i,t),i,t);return{nodes:c,edges:d,isLoading:s,error:a,connectionError:n,setConnectionError:r,renderCount:l}},xAe=()=>e=>{e(Mme())},bAe=({setConnectionError:e})=>{const t=Hn(),n=Yu(),r=Xe(nb),o,i=y.useState(!1),s=y.useMemo(()=>n,n),a=y.useMemo(()=>({onMessage(){},onStatusChange(f){t(My(f===Lr.CONNECTING)),i(f===Lr.CONNECTED)},onError(f){e(f.message),t(A0(f.message))}}),t,e);y.useEffect(()=>{const f=s.registerHandler(a);return()=>{f()}},s,a);const l=r==null?void 0:r.id,c=r==null?void 0:r.address;return y.useEffect(()=>{if(!c){e("No active connection selected");return}let f=!0;return(async()=>{try{if(n.isConnected()){f&&(e(null),i(!0));return}await n.connectService(c),f&&e(null)}catch(g){if(f){const v=g instanceof Error?g.message:"Unknown connection error";e(v)}}})(),()=>{if(f=!1,n.isConnected())try{n.disconnect()}catch(g){console.error(g)}}},n,e,l,c),{retryConnection:y.useCallback(()=>{if(!c){e("No active connection selected");return}t(xAe()),e(null);try{n.connectService(c).then(()=>{e(null)}).catch(f=>{e(f.message)})}catch(f){const p=f instanceof Error?f.message:"Failed to retry connection";e(p)}},n,t,e,c),isConnected:o}},wAe=({onNodesChange:e,onEdgesChange:t,localEdges:n,nodesRef:r,edgesRef:o,onNodeClick:i})=>{const s=y.useCallback(c=>{e(c),c.forEach(d=>{if(d.type==="position"&&d.position){const f=r.current.findIndex(p=>p.id===d.id);f!==-1&&(r.currentf={...r.currentf,position:d.position})}})},e,r),a=y.useCallback(c=>{t(c),o.current=n},t,n,o),l=y.useCallback((c,d)=>{const f=parseInt(d.id);isNaN(f)||i==null||i(f)},i);return{handleNodesChange:s,handleEdgesChange:a,handleNodeClick:l}},SAe=({nodes:e,error:t,isLoading:n,toast:r})=>(y.useEffect(()=>{e.length>0&&!n&&r({title:"Graph loaded",description:`${e.length} node${e.length!==1?"s":""} have been loaded`,variant:"default"})},e.length,n,r),y.useEffect(()=>{t&&r({title:"Error",description:t,variant:"destructive"})},t,r),{}),_Ae=()=>{const e=Yu(),t,n=y.useState(new Map);return{requestFilterArgs:s=>{e.subscribe({type:ss.FILTER_ARGS_DETAILS,filterIdx:s},a=>{console.log("Filter args received for idx:",s,a.data),n(l=>new Map(l.set(s,a.data)))}),e.subscribeToFilterArgs(s)},getFilterArgs:s=>t.get(s),hasFilterArgs:s=>t.has(s),filterArgs:t}},CAe=()=>{const e=Hn(),{toast:t}=P9(),n=y.useRef(),r=y.useRef(),o=y.useRef(!1),i,s,a=ehe(),l,c,d=the(),{nodes:f,edges:p,isLoading:g,error:v,connectionError:b,setConnectionError:_}=yAe(n,r),x=UEe(),w=y.useMemo(()=>new Set(x),x),{layoutOptions:C,handleLayoutChange:E,autoLayout:R,applyLayout:P}=mAe({localNodes:i,localEdges:l,setLocalNodes:s,nodesRef:n,isApplyingLayout:o}),{retryConnection:N}=bAe({setConnectionError:_}),{getFilterArgs:k,hasFilterArgs:I}=_Ae(),O=y.useCallback(z=>{e(Ime(String(z))),e(LW({filterIdx:z,initialTab:"overview"}))},e),{handleNodesChange:j,handleEdgesChange:H,handleNodeClick:q}=wAe({onNodesChange:a,onEdgesChange:d,localEdges:l,nodesRef:n,edgesRef:r,onNodeClick:O});SAe({nodes:f,error:v,isLoading:g,toast:t}),y.useEffect(()=>{if((f.length>0||p.length>0)&&!o.current){if(n.current===f&&r.current===p)return;s(f),c(p),n.current=f,r.current=p}},f,p,s,c);constM,B=y.useState(!1),$=rhe(),W=y.useMemo(()=>i.map(z=>{var re;const K=(re=z.data)==null?void 0:re.idx,Q=typeof K=="number"&&w.has(K);return{...z,data:{...z.data,isMonitored:Q}}}),i,w);return y.useEffect(()=>{f.length>0&&f.length!==n.current.length&&B(!1)},f.length),y.useEffect(()=>{M||$&&i.length!==0&&(i.some(z=>!z.measured)||i.some(z=>z.data&&z.data.name)&&(R(),B(!0)))},$,i,M,R),{isLoading:g,connectionError:b,retryConnection:N,localNodes:W,localEdges:l,handleNodesChange:j,handleEdgesChange:H,handleNodeClick:q,layoutOptions:C,handleLayoutChange:E,autoLayout:R,applyLayout:P,triggerLayout:()=>{B(!1),R()},getFilterArgs:k,hasFilterArgs:I}},EAe=y.memo(({id:e,sourceX:t,sourceY:n,targetX:r,targetY:o,sourcePosition:i,targetPosition:s,style:a={},markerEnd:l})=>{constc=Lk({sourceX:t,sourceY:n,sourcePosition:i,targetX:r,targetY:o,targetPosition:s});return h.jsx("g",{children:h.jsx("path",{id:e,className:"react-flow__edge-path",d:c,strokeWidth:5,markerEnd:l,style:{strokeOpacity:1,cursor:"pointer",...a},children:h.jsx("title",{children:"Click to see IPID Properties"})})})});EAe.displayName="CustomEdge";var cT={exports:{}};(function(e,t){(function(n,r){{var o=r();e&&e.exports&&(t=e.exports=o),t.randomColor=o}})(rs,function(){var n=null,r={};x();var o=,i=function(N){if(N=N||{},N.seed!==void 0&&N.seed!==null&&N.seed===parseInt(N.seed,10))n=N.seed;else if(typeof N.seed=="string")n=R(N.seed);else{if(N.seed!==void 0&&N.seed!==null)throw new TypeError("The seed value must be an integer or string");n=null}var k,I,O;if(N.count!==null&&N.count!==void 0){for(var j=N.count,H=,q=0;q<N.count;q++)o.push(!1);for(N.count=null;j>H.length;){var M=i(N);n!==null&&(N.seed=n),H.push(M)}return N.count=j,H}return k=s(N),I=a(k,N),O=l(k,I,N),c(k,I,O,N)};function s(N){if(o.length>0){var k=P(N.hue),I=v(k),O=(k1-k0)/o.length,j=parseInt((I-k0)/O);oj===!0?j=(j+2)%o.length:oj=!0;var H=(k0+j*O)%359,q=(k0+(j+1)*O)%359;return k=H,q,I=v(k),I<0&&(I=360+I),I}else{var k=f(N.hue);return I=v(k),I<0&&(I=360+I),I}}function a(N,k){if(k.hue==="monochrome")return 0;if(k.luminosity==="random")return v(0,100);var I=p(N),O=I0,j=I1;switch(k.luminosity){case"bright":O=55;break;case"dark":O=j-10;break;case"light":j=55;break}return v(O,j)}function l(N,k,I){var O=d(N,k),j=100;switch(I.luminosity){case"dark":j=O+20;break;case"light":O=(j+O)/2;break;case"random":O=0,j=100;break}return v(O,j)}function c(N,k){switch(k.format){case"hsvArray":return N;case"hslArray":return E(N);case"hsl":var I=E(N);return"hsl("+I0+", "+I1+"%, "+I2+"%)";case"hsla":var O=E(N),q=k.alpha||Math.random();return"hsla("+O0+", "+O1+"%, "+O2+"%, "+q+")";case"rgbArray":return w(N);case"rgb":var j=w(N);return"rgb("+j.join(", ")+")";case"rgba":var H=w(N),q=k.alpha||Math.random();return"rgba("+H.join(", ")+", "+q+")";default:return b(N)}}function d(N,k){for(var I=g(N).lowerBounds,O=0;O<I.length-1;O++){var j=IO0,H=IO1,q=IO+10,M=IO+11;if(k>=j&&k<=q){var B=(M-H)/(q-j),$=H-B*j;return B*k+$}}return 0}function f(N){if(typeof parseInt(N)=="number"){var k=parseInt(N);if(k<360&&k>0)returnk,k}if(typeof N=="string"){if(rN){var I=rN;if(I.hueRange)return I.hueRange}else if(N.match(/^#?(0-9A-F{3}|0-9A-F{6})$/i)){var O=C(N)0;returnO,O}}return0,360}function p(N){return g(N).saturationRange}function g(N){N>=334&&N<=360&&(N-=360);for(var k in r){var I=rk;if(I.hueRange&&N>=I.hueRange0&&N<=I.hueRange1)return rk}return"Color not found"}function v(N){if(n===null){var k=.618033988749895,I=Math.random();return I+=k,I%=1,Math.floor(N0+I*(N1+1-N0))}else{var O=N1||1,j=N0||0;n=(n*9301+49297)%233280;var H=n/233280;return Math.floor(j+H*(O-j))}}function b(N){var k=w(N);function I(j){var H=j.toString(16);return H.length==1?"0"+H:H}var O="#"+I(k0)+I(k1)+I(k2);return O}function _(N,k,I){var O=I00,j=II.length-10,H=II.length-11,q=I01;rN={hueRange:k,lowerBounds:I,saturationRange:O,j,brightnessRange:H,q}}function x(){_("monochrome",null,0,0,100,0),_("red",-26,18,20,100,30,92,40,89,50,85,60,78,70,70,80,60,90,55,100,50),_("orange",18,46,20,100,30,93,40,88,50,86,60,85,70,70,100,70),_("yellow",46,62,25,100,40,94,50,89,60,86,70,84,80,82,90,80,100,75),_("green",62,178,30,100,40,90,50,85,60,81,70,74,80,64,90,50,100,40),_("blue",178,257,20,100,30,86,40,80,50,74,60,60,70,52,80,44,90,39,100,35),_("purple",257,282,20,100,30,87,40,79,50,70,60,65,70,59,80,52,90,45,100,42),_("pink",282,334,20,100,30,90,40,86,60,84,80,80,90,75,100,73)}function w(N){var k=N0;k===0&&(k=1),k===360&&(k=359),k=k/360;var I=N1/100,O=N2/100,j=Math.floor(k*6),H=k*6-j,q=O*(1-I),M=O*(1-H*I),B=O*(1-(1-H)*I),$=256,W=256,G=256;switch(j){case 0:$=O,W=B,G=q;break;case 1:$=M,W=O,G=q;break;case 2:$=q,W=O,G=B;break;case 3:$=q,W=M,G=O;break;case 4:$=B,W=q,G=O;break;case 5:$=O,W=q,G=M;break}var z=Math.floor($*255),Math.floor(W*255),Math.floor(G*255);return z}function C(N){N=N.replace(/^#/,""),N=N.length===3?N.replace(/(.)/g,"$1$1"):N;var k=parseInt(N.substr(0,2),16)/255,I=parseInt(N.substr(2,2),16)/255,O=parseInt(N.substr(4,2),16)/255,j=Math.max(k,I,O),H=j-Math.min(k,I,O),q=j?H/j:0;switch(j){case k:return60*((I-O)/H%6)||0,q,j;case I:return60*((O-k)/H+2)||0,q,j;case O:return60*((k-I)/H+4)||0,q,j}}function E(N){var k=N0,I=N1/100,O=N2/100,j=(2-I)*O;returnk,Math.round(I*O/(j<1?j:2-j)*1e4)/100,j/2*100}function R(N){for(var k=0,I=0;I!==N.length&&!(k>=Number.MAX_SAFE_INTEGER);I++)k+=N.charCodeAt(I);return k}function P(N){if(isNaN(N)){if(typeof N=="string"){if(rN){var I=rN;if(I.hueRange)return I.hueRange}else if(N.match(/^#?(0-9A-F{3}|0-9A-F{6})$/i)){var O=C(N)0;return g(O).hueRange}}}else{var k=parseInt(N);if(k<360&&k>0)return g(N).hueRange}return0,360}return i})})(cT,cT.exports);var NAe=cT.exports;const RAe=qf(NAe);function OI(){var e=y.useRef(!0);return e.current?(e.current=!1,!0):e.current}var TAe=typeof window<"u";function kAe(e,t){t===void 0&&(t=0);var n=y.useRef(!1),r=y.useRef(),o=y.useRef(e),i=y.useCallback(function(){return n.current},),s=y.useCallback(function(){n.current=!1,r.current&&clearTimeout(r.current),r.current=setTimeout(function(){n.current=!0,o.current()},t)},t),a=y.useCallback(function(){n.current=null,r.current&&clearTimeout(r.current)},);return y.useEffect(function(){o.current=e},e),y.useEffect(function(){return s(),a},t),i,a,s}function DI(e,t,n){t===void 0&&(t=0),n===void 0&&(n=);var r=kAe(e,t),o=r0,i=r1,s=r2;return y.useEffect(s,n),o,i}var PAe=function(e,t){return TAe?window.matchMedia(e).matches:!1},IAe=function(e,t){var n=y.useState(PAe(e)),r=n0,o=n1;return y.useEffect(function(){var i=!0,s=window.matchMedia(e),a=function(){i&&o(!!s.matches)};return s.addEventListener("change",a),o(s.matches),function(){i=!1,s.removeEventListener("change",a)}},e),r};const AAe=e=>e.map(r=>r/255).map(r=>r<=.03928?r/12.92:((r+.055)/1.055)**2.4),xz=e=>{const t=AAe(e),n,r,o=t;return .2126*n+.7152*r+.0722*o},bz=(e,t)=>{const n=xz(e),r=xz(t);return(Math.max(n,r)+.05)/(Math.min(n,r)+.05)},MAe=e=>{const t=bz(e,255,255,255),n=bz(e,0,0,0);return t>n?"#ffffff":"#000000"},BV=e=>{const t=e.data;if(!t)return"filter";const n=typeof t.nb_opid=="number"&&t.nb_opid>0,r=typeof t.nb_ipid=="number"&&t.nb_ipid>0;return!r&&n?"source":r&&!n?"sink":"filter"},jAe=(e,t)=>{const r={source:{min:75,max:150},filter:{min:200,max:280},sink:{min:340,max:360}}e;let o;return r.min<=r.max?o=Math.floor(Math.random()*(r.max-r.min+1))+r.min:o=Math.random()<(360-r.min)/(360-r.min+r.max)?Math.floor(Math.random()*(360-r.min+1))+r.min:Math.floor(Math.random()*(r.max+1)),RAe({luminosity:t,format:"hex",hue:o})},WV=PRe((e,t,n)=>{const r=jAe(t,n),o=parseInt(r.slice(1,3),16),parseInt(r.slice(3,5),16),parseInt(r.slice(5,7),16);returnMAe(o),r},(e,t,n)=>`${e}-${t}-${n}`),LAe=e=>{const t=IAe("(prefers-color-scheme: dark)")?"dark":"light",n=BV(e);return WV(e.id,n,t)},OAe=(e,t)=>{const n=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light",r=BV(e);return WV(e.id,r,n)1},DAe=({filterIdx:e})=>{const t=Hn(),{setNodes:n}=Qm(),r=y.useCallback(i=>{t(LW({filterIdx:e,initialTab:i})),t(Ame()),n(s=>s.map(a=>({...a,selected:!1})))},t,e,n),o=y.useCallback(i=>{i.stopPropagation(),r("inputs")},r);return h.jsx(Dhe,{position:Ve.Bottom,align:"start",offset:8,children:h.jsx("div",{className:"flex items-center gap-1 bg-monitor-panel border border-slate-700 rounded-lg p-1 shadow-lg",children:h.jsxs("button",{onClick:o,className:"flex items-center gap-1.5 px-2 py-1 text-xs font-medium text-slate-200 hover:bg-slate-900 rounded transition-colors",title:"View Inputs",children:h.jsx(_0e,{className:"w-3.5 h-3.5"}),"See IPIDS"})})})},FAe=y.memo(DAe),$Ae=({data:e,selected:t,...n})=>{const{label:r,ipid:o,opid:i,nb_ipid:s,nb_opid:a}=e,l=y.useMemo(()=>IW(e),e),c=y.useMemo(()=>({data:e,position:{x:0,y:0},...n}),e,n),d,f=LAe(c),g=e.isMonitored?"ring-4 ring-red-700/90":t?"ring-2 ring-sky-400":"ring-1 ring-monitor-line",v=s>0?Object.keys(o).map((R,P)=>({id:R,type:"target",position:Ve.Left,index:P})):,b=a>0?Object.keys(i).map((R,P)=>({id:R,type:"source",position:Ve.Right,index:P})):,_=(R,P)=>P===1?"50%":`${R/(P-1)*100}%`,x=y.useMemo(()=>({borderColor:f,backgroundColor:f+"40",borderWidth:"2px"}),f),w=y.useMemo(()=>({backgroundColor:f}),f),C=y.useMemo(()=>({background:f,width:"10px",height:"10px",border:"2px solid white",boxShadow:"0 2px 4px rgba(0,0,0,0.1)"}),f),E=y.useMemo(()=>({color:d}),d);return h.jsxs(h.Fragment,{children:h.jsx(FAe,{filterIdx:e.idx,filterName:e.name}),h.jsxs("div",{className:` + border-2 rounded-md p-4 + ${g} + `,style:x,children:v.map(({id:R,type:P,position:N,index:k})=>h.jsx(If,{id:R,type:P,position:N,style:{...C,top:_(k,v.length),transform:"translateY(-50%)"}},`input-${R}`)),h.jsx("div",{className:"rounded-t-xl -m-4 mb-2 px-4 py-3 shadow-sm",style:w,children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx("div",{className:"flex items-center gap-2 flex-1 min-w-0",children:h.jsx("h3",{className:"font-bold text-sm truncate",style:E,children:r})}),h.jsx("div",{className:"text-xs font-medium px-2 py-1 bg-white/20 rounded-full flex-shrink-0",style:E,title:l==="source"?"Source Filter":l==="sink"?"Sink Filter":"Processing Filter",children:l.toUpperCase()})})}),h.jsxs("div",{className:"node-drag-handle ",children:h.jsxs("div",{className:"flex justify-between items-start mb-2",children:h.jsx("div",{className:"flex-1 text-xs text-gray-600 pr-2",children:s>0&&h.jsxs(h.Fragment,{children:h.jsx("span",{className:"font-medium text-white block text-left",children:"INPUTS"}),h.jsx("div",{className:"mt-1",children:Object.keys(o).map(R=>h.jsx("div",{className:"text-xs text-gray-100 truncate",children:R},R))})})}),h.jsx("div",{className:"flex-1 text-xs text-gray-800 pl-2",children:a>0&&h.jsxs(h.Fragment,{children:h.jsx("span",{className:"font-medium text-white block text-right",children:"OUTPUTS"}),h.jsx("div",{className:"mt-1",children:Object.keys(i).map(R=>h.jsx("div",{className:"text-xs text-gray-100 text-right truncate",children:R},R))})})})}),h.jsxs("div",{className:"text-xs text-gray-100 pt-2 border-t border-gray-300 text-center",children:"IPIDs: ",s," | OPIDs: ",a})}),b.map(({id:R,type:P,position:N,index:k})=>h.jsx(If,{id:R,type:P,position:N,style:{...C,top:_(k,b.length),transform:"translateY(-50%)"}},`output-${R}`))})})},zAe=y.memo($Ae,(e,t)=>e.data.idx===t.data.idx&&e.data.name===t.data.name&&e.data.nb_ipid===t.data.nb_ipid&&e.data.nb_opid===t.data.nb_opid&&e.selected===t.selected&&e.data.isMonitored===t.data.isMonitored&&e.data.isStalled===t.data.isStalled&&JSON.stringify(e.data.ipid)===JSON.stringify(t.data.ipid)&&JSON.stringify(e.data.opid)===JSON.stringify(t.data.opid)),HAe={type:"video",label:"Video",color:"#3b82f6"},{type:"audio",label:"Audio",color:"#10b981"},{type:"text",label:"Text",color:"#f59e0b"},{type:"file",label:"File",color:"#E11D48"},UV=y.memo(()=>h.jsx(Zm,{position:"top-left",className:"m-4",children:h.jsxs("div",{className:`flex flex-col gap-2 px-3 py-2 rounded-lg + bg-gray-900/0 backdrop-blur border border-gray-700 + shadow-lg`,children:h.jsx("div",{className:"text-xs font-cond text-gray-400 mb-1",children:"Stream Types"}),HAe.map(({type:e,label:t,color:n})=>h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("div",{className:"w-8 h-0.5 rounded-full",style:{backgroundColor:n}}),h.jsx("span",{className:"text-xs font-cond text-gray-300",children:t})},e))})}));UV.displayName="GraphLegend";const BAe=()=>{const{setViewport:e}=Qm(),{zoom:t}=Jfe(),n=y.useCallback((o,i)=>{const a=o.currentTarget.getBoundingClientRect(),l=i.x,c=i.y,d=150,f=-l*d+a.width/2,p=-c*d+a.height/2;e({x:f,y:p,zoom:t},{duration:200})},e,t),r=y.useCallback(o=>{const s=o.currentTarget.getBoundingClientRect(),a=(o.clientX-s.left)/s.width,l=(o.clientY-s.top)/s.height,c=Math.max(0,Math.min(1,a)),d=Math.max(0,Math.min(1,l)),f=150,p=-c*f+s.width/2,g=-d*f+s.height/2;e({x:p,y:g,zoom:t},{duration:0})},e,t);return{handleMiniMapClick:n,handleMiniMapDrag:r}},WAe={surface:"#101722",background:"",width:"100%",height:"100%"},UAe={gpacer:zAe},qAe=({nodes:e,edges:t,onNodesChange:n,onEdgesChange:r,onNodeClick:o,isResizing:i=!1})=>{const s=Hn(),{handleMiniMapClick:a,handleMiniMapDrag:l}=BAe(),c=()=>{s(pI())};return h.jsx("div",{style:WAe,className:i?"resize-optimized":"",children:h.jsxs(Zfe,{nodes:e,edges:t,nodeTypes:UAe,onNodesChange:i?()=>{}:n,onEdgesChange:i?()=>{}:r,onNodeClick:i?void 0:o,onPaneClick:i?void 0:c,fitView:!i,minZoom:.01,maxZoom:2,defaultEdgeOptions:{type:"simplebezier",animated:!i,style:{stroke:"#6b7280",strokeWidth:3},ariaLabel:"Clickable edge to see IPID properties"},defaultViewport:{x:0,y:0,zoom:.5},proOptions:{hideAttribution:!0},selectionKeyCode:null,children:h.jsx(lhe,{color:"#4b5563",gap:16}),h.jsx(The,{nodeColor:d=>OAe(d),nodeStrokeWidth:2,nodeStrokeColor:"#374151",maskColor:"rgba(0, 0, 0, 0.5)",className:"bg-gray-900 border border-gray-900 rounded-md w-52 h-36",style:{backgroundColor:"#1f2937",border:"1px solid #374151"},onClick:i?void 0:a,onDrag:i?void 0:l,pannable:!i,zoomable:!i,ariaLabel:"Minimap for graph navigation"}),h.jsx(mhe,{showInteractive:!1,className:"&_button:bg-gray-800 &_button:border-gray-700 &_button:text-white &_button:hover:bg-gray-700"}),h.jsx(UV,{})})})},GAe=y.memo(qAe),VAe=({id:e,message:t})=>h.jsx(Js,{id:e,children:h.jsxs("div",{className:"flex flex-col items-center justify-center h-full p-4",children:h.jsx("div",{className:"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mb-4"}),h.jsx("div",{className:"text-gray-400",children:t||"Chargement..."})})}),qV=({id:e,errorMessage:t,onRetry:n,isLoading:r=!1})=>r?h.jsx(Js,{id:e,children:h.jsx("div",{className:"flex items-center justify-center h-full","aria-busy":"true","aria-label":"Loading data",children:h.jsx("div",{className:"h-8 w-8 animate-spin rounded-full border-2 border-emerald-400/60 border-t-transparent"})})}):h.jsx(Js,{id:e,children:h.jsxs("div",{className:"flex flex-col items-center justify-center h-full p-4",children:h.jsx("div",{className:"text-red-500 mb-4",children:"Connection error"}),h.jsx("div",{className:"text-gray-400 text-center",children:t}),n&&h.jsx("button",{onClick:n,className:"mt-4 px-4 rounded-md py-2 bg-red-900 hover:bg-red-800 ",children:"Retry"})})}),YAe=({id:e,isLoading:t,connectionError:n,retryConnection:r,nodes:o,edges:i,onNodesChange:s,onEdgesChange:a,onNodeClick:l})=>{constc,d=y.useState(!1),{ref:f}=Jx({onResizeStart:()=>d(!0),onResizeEnd:()=>d(!1),debounce:24,throttle:!0}),p=f;return t?h.jsx(VAe,{id:e,message:"Connecting to GPAC..."}):n?h.jsx(qV,{id:e,errorMessage:n,onRetry:r}):h.jsx(Js,{id:e,children:h.jsx("div",{ref:p,className:`relative h-full w-full ${c?"contain-layout contain-style pointer-events-none":""}`,children:h.jsx(GAe,{nodes:o,edges:i,onNodesChange:s,onEdgesChange:a,onNodeClick:l,isResizing:c})})})},KAe=Pe.memo(YAe),XAe=({id:e,config:t})=>{const{isLoading:n,connectionError:r,retryConnection:o,localNodes:i,localEdges:s,handleNodesChange:a,handleEdgesChange:l,handleNodeClick:c,layoutOptions:d,handleLayoutChange:f,autoLayout:p}=CAe();return h.jsx(h.Fragment,{children:h.jsx(KAe,{id:e,config:t,isLoading:n,connectionError:r,retryConnection:o,nodes:i,edges:s,onNodesChange:a,onEdgesChange:l,onNodeClick:c,layoutOptions:d,onLayoutChange:f,onAutoLayout:p})})},ZAe=y.memo(XAe);function QAe(e=!0,t=1e3){constn,r=y.useState(),{isReady:o}=rb({enabled:e}),i=y.useCallback(s=>{r(s.map(a=>({...a})))},);return y.useEffect(()=>{if(!e||!o){r();return}let s=null,a=!0;return(async()=>{try{const c=await Mo.subscribe({type:ss.SESSION_STATS,interval:t},d=>{d.data&&i(d.data)});a?s=c:c()}catch{a&&r()}})(),()=>{a=!1,s&&s()}},e,o,t,i),{stats:n,isSubscribed:n.length>0}}const JAe=(e=!0)=>{const t=Hn(),{stats:n}=QAe(e,1e3),r=Xe(a=>a.graph.isLoading),o=Xe(a=>a.sessionStats.isSubscribed),i=Xe(a=>a.graph.filters),s=y.useCallback(a=>{var l;Mo.unsubscribeFromFilter(a),((l=Mo.getCurrentFilterId())==null?void 0:l.toString())===a&&(t(jW(null)),Mo.setCurrentFilterId(null))},t);return{isLoading:r,handleCloseMonitor:s,sessionStats:n,isSessionSubscribed:o,staticFilters:i,sessionStatsData:n}},GV="KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IE09KHQ9e30pPT57Y29uc3Qgbj1PYmplY3QudmFsdWVzKHQpO2lmKG4ubGVuZ3RoPT09MClyZXR1cm4gMDtjb25zdCBzPW4ucmVkdWNlKChlLGMpPT5lKyhjLmJ1ZmZlcnx8MCksMCkscj1uLnJlZHVjZSgoZSxjKT0+ZSsoYy5tYXhfYnVmZmVyfHwwKSwwKTtyZXR1cm4gcj09PTA/MDpNYXRoLm1pbigxMDAsTWF0aC5yb3VuZChzL3IqMTAwKSl9LFQ9KHQ9MCxuPTApPT57aWYobjw9MClyZXR1cm4gMDtjb25zdCBzPW4vMWU2O3JldHVybiBNYXRoLnJvdW5kKHQvcyl9LF89KHQ9MCxuPTApPT57aWYobjw9MClyZXR1cm4iaWRsZSI7Y29uc3Qgcz1uLzFlMyxyPXQvcyxlPTFlNSxjPTFlNixvPTVlNjtyZXR1cm4gcjxlPyJpZGxlIjpyPGM/ImxvdyI6cjxvPyJtZWRpdW0iOiJoaWdoIn0saz10PT57c3dpdGNoKHQpe2Nhc2UiaGlnaCI6cmV0dXJuImJnLWRhbmdlciI7Y2FzZSJtZWRpdW0iOnJldHVybiJiZy1pbmZvIjtjYXNlImxvdyI6cmV0dXJuImJnLXdhcm5pbmciO2RlZmF1bHQ6cmV0dXJuImJnLXNsYXRlLTUwMC81MCJ9fSxCPXQ9Pntzd2l0Y2godCl7Y2FzZSJoaWdoIjpyZXR1cm4iSGlnaCI7Y2FzZSJtZWRpdW0iOnJldHVybiJNZWRpdW0iO2Nhc2UibG93IjpyZXR1cm4iTG93IjtkZWZhdWx0OnJldHVybiJJZGxlIn19LFM9dD0+e2NvbnN0IG49dC5uYl9pcGlkJiZ0Lm5iX2lwaWQ+MCxzPXQubmJfb3BpZCYmdC5uYl9vcGlkPjA7cmV0dXJuIW4mJnM/InNvdXJjZSI6biYmIXM/InNpbmsiOiJwcm9jZXNzIn0sdj0odD0wKT0+e2lmKHQ9PT0wKXJldHVybiIwIEIiO2NvbnN0IG49MTAyNCxzPVsiQiIsIktCIiwiTUIiLCJHQiIsIlRCIl0scj1NYXRoLmZsb29yKE1hdGgubG9nKHQpL01hdGgubG9nKG4pKTtyZXR1cm5gJHsodC9NYXRoLnBvdyhuLHIpKS50b0ZpeGVkKDIpfSAke3Nbcl19YH0sRT0odD0wKT0+e2lmKHQ9PT0wKXJldHVybiIwbXMiO2lmKHQ8MWUzKXJldHVybmAke3QudG9GaXhlZCgwKX3OvHNgO2NvbnN0IG49dC8xZTM7aWYobjwxZTMpcmV0dXJuYCR7bi50b0ZpeGVkKDApfW1zYDtjb25zdCBzPW4vMWUzO2lmKHM8NjApcmV0dXJuYCR7cy50b0ZpeGVkKDEpfXNgO2NvbnN0IHI9TWF0aC5mbG9vcihzLzYwKSxlPU1hdGguZmxvb3IocyU2MCk7aWYocjw2MCl7Y29uc3QgYT1yLnRvU3RyaW5nKCkucGFkU3RhcnQoMiwiMCIpLHU9ZS50b1N0cmluZygpLnBhZFN0YXJ0KDIsIjAiKTtyZXR1cm5gJHthfToke3V9YH1jb25zdCBjPU1hdGguZmxvb3Ioci82MCksaT0ociU2MCkudG9TdHJpbmcoKS5wYWRTdGFydCgyLCIwIik7cmV0dXJuYCR7Y306JHtpfWhgfSwkPSh0PTApPT50PDFlMz90LnRvU3RyaW5nKCk6dDwxZTY/YCR7KHQvMWUzKS50b0ZpeGVkKDEpfUtgOmAkeyh0LzFlNikudG9GaXhlZCgxKX1NYCx4PXQ9PnQ9PT0wPyIwIHBrdC9zIjp0PDFlMz9gJHt0fSBwa3Qvc2A6YCR7KHQvMWUzKS50b0ZpeGVkKDEpfUsgcGt0L3NgLG09bmV3IE1hcDtzZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLHQ9Pntjb25zdHt0eXBlOm4sZmlsdGVyczpzfT10LmRhdGE7aWYobj09PSJFTlJJQ0hfU1RBVFMiKXtjb25zdCByPXMubWFwKGU9Pntjb25zdCBjPWUuaWR4Pz9lLklEPz9lLm5hbWUsbz1tLmdldChjKSxpPU0oZS5pcGlkKSxhPV8oZS5ieXRlc19kb25lLGUudGltZSksdT1TKGUpLGY9dihlLmJ5dGVzX2RvbmUpLHA9RShlLnRpbWUpLGg9JChlLnBja19kb25lKSxnPWsoYSksbD1CKGEpLGQ9VChlLnBja19kb25lLGUudGltZSkseT14KGQpO2lmKG8mJm8uaWR4PT09ZS5pZHgmJm8ubmFtZT09PWUubmFtZSYmby5lcnJvcnM9PT1lLmVycm9ycyYmby5jb21wdXRlZC5idWZmZXJVc2FnZT09PWkmJm8uY29tcHV0ZWQuYWN0aXZpdHlMZXZlbD09PWEmJm8uY29tcHV0ZWQuYWN0aXZpdHlDb2xvcj09PWcmJm8uY29tcHV0ZWQuYWN0aXZpdHlMYWJlbD09PWwmJm8uY29tcHV0ZWQuc2Vzc2lvblR5cGU9PT11JiZvLmNvbXB1dGVkLmZvcm1hdHRlZEJ5dGVzPT09ZiYmby5jb21wdXRlZC5mb3JtYXR0ZWRUaW1lPT09cCYmby5jb21wdXRlZC5mb3JtYXR0ZWRQYWNrZXRzPT09aCYmby5jb21wdXRlZC5wYWNrZXRSYXRlPT09ZCYmby5jb21wdXRlZC5mb3JtYXR0ZWRQYWNrZXRSYXRlPT09eSlyZXR1cm4gbztjb25zdCBiPXsuLi5lLGNvbXB1dGVkOntidWZmZXJVc2FnZTppLGFjdGl2aXR5TGV2ZWw6YSxhY3Rpdml0eUNvbG9yOmcsYWN0aXZpdHlMYWJlbDpsLHNlc3Npb25UeXBlOnUsZm9ybWF0dGVkQnl0ZXM6Zixmb3JtYXR0ZWRUaW1lOnAsZm9ybWF0dGVkUGFja2V0czpoLHBhY2tldFJhdGU6ZCxmb3JtYXR0ZWRQYWNrZXRSYXRlOnl9fTtyZXR1cm4gbS5zZXQoYyxiKSxifSk7c2VsZi5wb3N0TWVzc2FnZSh7dHlwZToiRU5SSUNIRURfU1RBVFMiLGVucmljaGVkRmlsdGVyczpyfSl9fSl9KSgpOwo=",eMe=e=>Uint8Array.from(atob(e),t=>t.charCodeAt(0)),wz=typeof self<"u"&&self.Blob&&new Blob(eMe(GV),{type:"text/javascript;charset=utf-8"});function tMe(e){let t;try{if(t=wz&&(self.URL||self.webkitURL).createObjectURL(wz),!t)throw"";const n=new Worker(t,{name:e==null?void 0:e.name});return n.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(t)}),n}catch{return new Worker("data:text/javascript;base64,"+GV,{name:e==null?void 0:e.name})}finally{t&&(self.URL||self.webkitURL).revokeObjectURL(t)}}class nMe extends M9{constructor(){super("EnrichedStatsWorkerService","ENRICHED_STATS")}createWorker(){return new tMe({name:"enrichedStatsWorker"})}extractData(t){return t.enrichedFilters}createMessage(t){return{type:"ENRICH_STATS",filters:t}}enrichStats(t){this.process(t)}}const Sz=new nMe;function VV(e){constt,n=y.useState(),r=y.useRef(!1),o=y.useMemo(()=>e.map(i=>`${i.idx}-${i.bytes_done}`).join(","),e);return y.useEffect(()=>{if(e.length===0){n();return}if(r.current)return;r.current=!0,Sz.enrichStats(e);const i=Sz.subscribe(s=>{n(s),r.current=!1});return()=>{i(),r.current=!1}},o),t}const rMe=(e,t)=>{const n=y.useMemo(()=>e.length?{total:e.length,sources:e.filter(o=>{var i;return((i=o.computed)==null?void 0:i.sessionType)==="source"}).length,sinks:e.filter(o=>{var i;return((i=o.computed)==null?void 0:i.sessionType)==="sink"}).length,active:t.filter(o=>{var i;return o.bytes_done&&o.bytes_done>0||o.pck_done&&o.pck_done>0||((i=o.status)==null?void 0:i.toLowerCase().includes("running"))}).length,processing:t.filter(o=>o.pck_done&&o.pck_done>0).length}:{total:0,active:0,sources:0,sinks:0,processing:0},e,t),r=y.useMemo(()=>{if(!t.length)return{totalBytes:0,totalPackets:0,activeFilters:0,systemActivityLevel:"low",dataProcessingActivityLevel:"low"};const o=t.reduce((d,f)=>d+(f.bytes_done||0),0),i=t.reduce((d,f)=>d+(f.pck_done||0),0),s=t.filter(d=>(d.bytes_done||0)>0||(d.pck_done||0)>0).length,a=n.total>0?n.processing/n.total:0,l=a>.7?"high":a>.3?"medium":"low",c=o>1e6?"high":o>1e5?"medium":"low";return{totalBytes:o,totalPackets:i,activeFilters:s,systemActivityLevel:l,dataProcessingActivityLevel:c}},t,n);return{statsCounters:n,systemStats:r}};function oMe(e,t=!0,n=1e3){constr,o=y.useState(null),i,s=y.useState(!1),a=y.useCallback(l=>{o(l),s(!1)},e);return y.useEffect(()=>{if(e===void 0||!t||!Mo.isConnected()){o(null),s(!1);return}let l=null,c=!0;return o(null),s(!0),(async()=>{try{const f=await Mo.subscribe({type:ss.FILTER_STATS,filterIdx:e,interval:n},p=>{p.data&&c&&a(p.data)});c?l=f:f()}catch{c&&(o(null),s(!1))}})(),()=>{c=!1,l&&l()}},e,t,n,a),{stats:r,isLoading:i,isSubscribed:!!r}}const iMe=(e,t)=>{const n=Object.entries(e).filter((r,o)=>o!==null).map((r)=>Number(r));return new Map(n.map(r=>{const o=t.find(i=>i.idx===r);return o?r,o:null}).filter(Boolean))},sMe=(e,t)=>new Map(Array.from(e.entries()).filter((n)=>{var r;return((r=tn)==null?void 0:r.mode)==="inline"})),aMe=e=>{const t=Xe(o=>o.widgets.viewByFilter),n=y.useMemo(()=>iMe(t,e),t,e),r=y.useMemo(()=>sMe(n,t),n,t);return{monitoredFilterMap:n,inlineFilterMap:r}},lMe=e=>{const t=Hn(),{openFilterArgs:n}=Mb(),r=y.useCallback(s=>{t(RLe(s)),e==null||e(`filter-${s}`)},t,e),o=y.useCallback((s,a,l)=>{l.stopPropagation(),t(TLe({idx:s,name:a})),e==null||e("main")},t,e),i=y.useCallback((s,a)=>{a.stopPropagation(),t(kLe(s)),e==null||e("main")},t,e);return{handleCardClick:r,handleDetachTab:o,handleCloseTab:i,handleOpenProperties:n}};var tN="rovingFocusGroup.onEntryFocus",cMe={bubbles:!1,cancelable:!0},xg="RovingFocusGroup",uT,YV,uMe=mb(xg),dMe,KV=to(xg,uMe),fMe,hMe=dMe(xg),XV=y.forwardRef((e,t)=>h.jsx(uT.Provider,{scope:e.__scopeRovingFocusGroup,children:h.jsx(uT.Slot,{scope:e.__scopeRovingFocusGroup,children:h.jsx(pMe,{...e,ref:t})})}));XV.displayName=xg;var pMe=y.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,orientation:r,loop:o=!1,dir:i,currentTabStopId:s,defaultCurrentTabStopId:a,onCurrentTabStopIdChange:l,onEntryFocus:c,preventScrollOnEntryFocus:d=!1,...f}=e,p=y.useRef(null),g=Pt(t,p),v=sg(i),b,_=xs({prop:s,defaultProp:a??null,onChange:l,caller:xg}),x,w=y.useState(!1),C=En(c),E=YV(n),R=y.useRef(!1),P,N=y.useState(0);return y.useEffect(()=>{const k=p.current;if(k)return k.addEventListener(tN,C),()=>k.removeEventListener(tN,C)},C),h.jsx(fMe,{scope:n,orientation:r,dir:v,loop:o,currentTabStopId:b,onItemFocus:y.useCallback(k=>_(k),_),onItemShiftTab:y.useCallback(()=>w(!0),),onFocusableItemAdd:y.useCallback(()=>N(k=>k+1),),onFocusableItemRemove:y.useCallback(()=>N(k=>k-1),),children:h.jsx(Ze.div,{tabIndex:x||P===0?-1:0,"data-orientation":r,...f,ref:g,style:{outline:"none",...e.style},onMouseDown:yt(e.onMouseDown,()=>{R.current=!0}),onFocus:yt(e.onFocus,k=>{const I=!R.current;if(k.target===k.currentTarget&&I&&!x){const O=new CustomEvent(tN,cMe);if(k.currentTarget.dispatchEvent(O),!O.defaultPrevented){const j=E().filter($=>$.focusable),H=j.find($=>$.active),q=j.find($=>$.id===b),B=H,q,...j.filter(Boolean).map($=>$.ref.current);JV(B,d)}}R.current=!1}),onBlur:yt(e.onBlur,()=>w(!1))})})}),ZV="RovingFocusGroupItem",QV=y.forwardRef((e,t)=>{const{__scopeRovingFocusGroup:n,focusable:r=!0,active:o=!1,tabStopId:i,children:s,...a}=e,l=Mi(),c=i||l,d=hMe(ZV,n),f=d.currentTabStopId===c,p=YV(n),{onFocusableItemAdd:g,onFocusableItemRemove:v,currentTabStopId:b}=d;return y.useEffect(()=>{if(r)return g(),()=>v()},r,g,v),h.jsx(uT.ItemSlot,{scope:n,id:c,focusable:r,active:o,children:h.jsx(Ze.span,{tabIndex:f?0:-1,"data-orientation":d.orientation,...a,ref:t,onMouseDown:yt(e.onMouseDown,_=>{r?d.onItemFocus(c):_.preventDefault()}),onFocus:yt(e.onFocus,()=>d.onItemFocus(c)),onKeyDown:yt(e.onKeyDown,_=>{if(_.key==="Tab"&&_.shiftKey){d.onItemShiftTab();return}if(_.target!==_.currentTarget)return;const x=vMe(_,d.orientation,d.dir);if(x!==void 0){if(_.metaKey||_.ctrlKey||_.altKey||_.shiftKey)return;_.preventDefault();let C=p().filter(E=>E.focusable).map(E=>E.ref.current);if(x==="last")C.reverse();else if(x==="prev"||x==="next"){x==="prev"&&C.reverse();const E=C.indexOf(_.currentTarget);C=d.loop?yMe(C,E+1):C.slice(E+1)}setTimeout(()=>JV(C))}}),children:typeof s=="function"?s({isCurrentTabStop:f,hasTabStop:b!=null}):s})})});QV.displayName=ZV;var mMe={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function gMe(e,t){return t!=="rtl"?e:e==="ArrowLeft"?"ArrowRight":e==="ArrowRight"?"ArrowLeft":e}function vMe(e,t,n){const r=gMe(e.key,n);if(!(t==="vertical"&&"ArrowLeft","ArrowRight".includes(r))&&!(t==="horizontal"&&"ArrowUp","ArrowDown".includes(r)))return mMer}function JV(e,t=!1){const n=document.activeElement;for(const r of e)if(r===n||(r.focus({preventScroll:t}),document.activeElement!==n))return}function yMe(e,t){return e.map((n,r)=>e(t+r)%e.length)}var xMe=XV,bMe=QV,Jb="Tabs",wMe,n5e=to(Jb,KV),eY=KV(),SMe,FI=wMe(Jb),tY=y.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,onValueChange:o,defaultValue:i,orientation:s="horizontal",dir:a,activationMode:l="automatic",...c}=e,d=sg(a),f,p=xs({prop:r,onChange:o,defaultProp:i??"",caller:Jb});return h.jsx(SMe,{scope:n,baseId:Mi(),value:f,onValueChange:p,orientation:s,dir:d,activationMode:l,children:h.jsx(Ze.div,{dir:d,"data-orientation":s,...c,ref:t})})});tY.displayName=Jb;var nY="TabsList",rY=y.forwardRef((e,t)=>{const{__scopeTabs:n,loop:r=!0,...o}=e,i=FI(nY,n),s=eY(n);return h.jsx(xMe,{asChild:!0,...s,orientation:i.orientation,dir:i.dir,loop:r,children:h.jsx(Ze.div,{role:"tablist","aria-orientation":i.orientation,...o,ref:t})})});rY.displayName=nY;var oY="TabsTrigger",iY=y.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,disabled:o=!1,...i}=e,s=FI(oY,n),a=eY(n),l=lY(s.baseId,r),c=cY(s.baseId,r),d=r===s.value;return h.jsx(bMe,{asChild:!0,...a,focusable:!o,active:d,children:h.jsx(Ze.button,{type:"button",role:"tab","aria-selected":d,"aria-controls":c,"data-state":d?"active":"inactive","data-disabled":o?"":void 0,disabled:o,id:l,...i,ref:t,onMouseDown:yt(e.onMouseDown,f=>{!o&&f.button===0&&f.ctrlKey===!1?s.onValueChange(r):f.preventDefault()}),onKeyDown:yt(e.onKeyDown,f=>{" ","Enter".includes(f.key)&&s.onValueChange(r)}),onFocus:yt(e.onFocus,()=>{const f=s.activationMode!=="manual";!d&&!o&&f&&s.onValueChange(r)})})})});iY.displayName=oY;var sY="TabsContent",aY=y.forwardRef((e,t)=>{const{__scopeTabs:n,value:r,forceMount:o,children:i,...s}=e,a=FI(sY,n),l=lY(a.baseId,r),c=cY(a.baseId,r),d=r===a.value,f=y.useRef(d);return y.useEffect(()=>{const p=requestAnimationFrame(()=>f.current=!1);return()=>cancelAnimationFrame(p)},),h.jsx(ys,{present:o||d,children:({present:p})=>h.jsx(Ze.div,{"data-state":d?"active":"inactive","data-orientation":a.orientation,role:"tabpanel","aria-labelledby":l,hidden:!p,id:c,tabIndex:0,...s,ref:t,style:{...e.style,animationDuration:f.current?"0s":void 0},children:p&&i})})});aY.displayName=sY;function lY(e,t){return`${e}-trigger-${t}`}function cY(e,t){return`${e}-content-${t}`}var _Me=tY,uY=rY,dY=iY,fY=aY;const hY=_Me,$I=y.forwardRef(({className:e,...t},n)=>h.jsx(uY,{ref:n,className:nt("inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",e),...t}));$I.displayName=uY.displayName;const gu=y.forwardRef(({className:e,...t},n)=>h.jsx(dY,{ref:n,className:nt("inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-muted-foreground hover:text-foreground/80 border-b-2 border-r-2 border-transparent data-state=active:bg-monitor-line data-state=active:text-primary data-state=active:shadow-sm data-state=active:border-gray-600",e),...t}));gu.displayName=dY.displayName;const vu=y.forwardRef(({className:e,...t},n)=>h.jsx(fY,{ref:n,className:nt("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ",e),...t}));vu.displayName=fY.displayName;const CMe=({onValueChange:e,allFilters:t,onCloseTab:n,onDetachTab:r,tabsRef:o})=>{const i=Xe(a=>a.widgets.viewByFilter),s=Object.entries(i).filter((a,l)=>(l==null?void 0:l.mode)==="inline").map((a)=>Number(a));return h.jsxs($I,{className:"sticky top-0 z-50 mb-4 justify-start border-border bg-black/60 backdrop-blur-sm ",ref:o,children:h.jsxs(gu,{value:"main",className:"flex items-center gap-1 data-state=active:text-monitor-active-filter data-state=active:border-b-2 data-state=active:border-monitor-active-filter","data-value":"main",onClick:()=>e("main"),children:h.jsx(C0e,{className:"h-4 w-4"}),h.jsx("span",{children:"Dashboard"})}),s.map(a=>{const l=t.find(c=>c.idx===a);return l?h.jsxs(gu,{value:`filter-${a}`,className:"flex items-center gap-1 data-state=active:text-monitor-active-filter data-state=active:border-b-2 data-state=active:border-monitor-active-filter","data-value":`filter-${a}`,onClick:()=>e(`filter-${a}`),children:h.jsx("span",{children:l.name}),r&&h.jsx("span",{className:"ml-1 inline-flex h-5 w-5 cursor-pointer items-center justify-center rounded-full hover:bg-slate-600",onClick:c=>{c.stopPropagation(),r(a,l.name,c)},title:"Detach as overlay",role:"button",tabIndex:0,onKeyDown:c=>{(c.key==="Enter"||c.key===" ")&&(c.stopPropagation(),r(a,l.name,c))},children:h.jsx(j0e,{})}),h.jsx("span",{className:"ml-1 inline-flex h-4 w-4 cursor-pointer items-center justify-center rounded-full hover:bg-slate-600",onClick:c=>{c.stopPropagation(),n(a,c)},role:"button",tabIndex:0,onKeyDown:c=>{(c.key==="Enter"||c.key===" ")&&(c.stopPropagation(),n(a,c))},children:"×"})},`tab-${a}`):null})})};function EMe(e,t,n){return Math.min(n,Math.max(t,e))}function NMe(e,t){return y.useReducer((n,r)=>tnr??n,e)}var zI="ScrollArea",pY,r5e=to(zI),RMe,$i=pY(zI),mY=y.forwardRef((e,t)=>{const{__scopeScrollArea:n,type:r="hover",dir:o,scrollHideDelay:i=600,...s}=e,a,l=y.useState(null),c,d=y.useState(null),f,p=y.useState(null),g,v=y.useState(null),b,_=y.useState(null),x,w=y.useState(0),C,E=y.useState(0),R,P=y.useState(!1),N,k=y.useState(!1),I=Pt(t,j=>l(j)),O=sg(o);return h.jsx(RMe,{scope:n,type:r,dir:O,scrollHideDelay:i,scrollArea:a,viewport:c,onViewportChange:d,content:f,onContentChange:p,scrollbarX:g,onScrollbarXChange:v,scrollbarXEnabled:R,onScrollbarXEnabledChange:P,scrollbarY:b,onScrollbarYChange:_,scrollbarYEnabled:N,onScrollbarYEnabledChange:k,onCornerWidthChange:w,onCornerHeightChange:E,children:h.jsx(Ze.div,{dir:O,...s,ref:I,style:{position:"relative","--radix-scroll-area-corner-width":x+"px","--radix-scroll-area-corner-height":C+"px",...e.style}})})});mY.displayName=zI;var gY="ScrollAreaViewport",vY=y.forwardRef((e,t)=>{const{__scopeScrollArea:n,children:r,nonce:o,...i}=e,s=$i(gY,n),a=y.useRef(null),l=Pt(t,a,s.onViewportChange);return h.jsxs(h.Fragment,{children:h.jsx("style",{dangerouslySetInnerHTML:{__html:"data-radix-scroll-area-viewport{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}data-radix-scroll-area-viewport::-webkit-scrollbar{display:none}"},nonce:o}),h.jsx(Ze.div,{"data-radix-scroll-area-viewport":"",...i,ref:l,style:{overflowX:s.scrollbarXEnabled?"scroll":"hidden",overflowY:s.scrollbarYEnabled?"scroll":"hidden",...e.style},children:h.jsx("div",{ref:s.onContentChange,style:{minWidth:"100%",display:"table"},children:r})})})});vY.displayName=gY;var fa="ScrollAreaScrollbar",HI=y.forwardRef((e,t)=>{const{forceMount:n,...r}=e,o=$i(fa,e.__scopeScrollArea),{onScrollbarXEnabledChange:i,onScrollbarYEnabledChange:s}=o,a=e.orientation==="horizontal";return y.useEffect(()=>(a?i(!0):s(!0),()=>{a?i(!1):s(!1)}),a,i,s),o.type==="hover"?h.jsx(TMe,{...r,ref:t,forceMount:n}):o.type==="scroll"?h.jsx(kMe,{...r,ref:t,forceMount:n}):o.type==="auto"?h.jsx(yY,{...r,ref:t,forceMount:n}):o.type==="always"?h.jsx(BI,{...r,ref:t}):null});HI.displayName=fa;var TMe=y.forwardRef((e,t)=>{const{forceMount:n,...r}=e,o=$i(fa,e.__scopeScrollArea),i,s=y.useState(!1);return y.useEffect(()=>{const a=o.scrollArea;let l=0;if(a){const c=()=>{window.clearTimeout(l),s(!0)},d=()=>{l=window.setTimeout(()=>s(!1),o.scrollHideDelay)};return a.addEventListener("pointerenter",c),a.addEventListener("pointerleave",d),()=>{window.clearTimeout(l),a.removeEventListener("pointerenter",c),a.removeEventListener("pointerleave",d)}}},o.scrollArea,o.scrollHideDelay),h.jsx(ys,{present:n||i,children:h.jsx(yY,{"data-state":i?"visible":"hidden",...r,ref:t})})}),kMe=y.forwardRef((e,t)=>{const{forceMount:n,...r}=e,o=$i(fa,e.__scopeScrollArea),i=e.orientation==="horizontal",s=tw(()=>l("SCROLL_END"),100),a,l=NMe("hidden",{hidden:{SCROLL:"scrolling"},scrolling:{SCROLL_END:"idle",POINTER_ENTER:"interacting"},interacting:{SCROLL:"interacting",POINTER_LEAVE:"idle"},idle:{HIDE:"hidden",SCROLL:"scrolling",POINTER_ENTER:"interacting"}});return y.useEffect(()=>{if(a==="idle"){const c=window.setTimeout(()=>l("HIDE"),o.scrollHideDelay);return()=>window.clearTimeout(c)}},a,o.scrollHideDelay,l),y.useEffect(()=>{const c=o.viewport,d=i?"scrollLeft":"scrollTop";if(c){let f=cd;const p=()=>{const g=cd;f!==g&&(l("SCROLL"),s()),f=g};return c.addEventListener("scroll",p),()=>c.removeEventListener("scroll",p)}},o.viewport,i,l,s),h.jsx(ys,{present:n||a!=="hidden",children:h.jsx(BI,{"data-state":a==="hidden"?"hidden":"visible",...r,ref:t,onPointerEnter:yt(e.onPointerEnter,()=>l("POINTER_ENTER")),onPointerLeave:yt(e.onPointerLeave,()=>l("POINTER_LEAVE"))})})}),yY=y.forwardRef((e,t)=>{const n=$i(fa,e.__scopeScrollArea),{forceMount:r,...o}=e,i,s=y.useState(!1),a=e.orientation==="horizontal",l=tw(()=>{if(n.viewport){const c=n.viewport.offsetWidth<n.viewport.scrollWidth,d=n.viewport.offsetHeight<n.viewport.scrollHeight;s(a?c:d)}},10);return Bf(n.viewport,l),Bf(n.content,l),h.jsx(ys,{present:r||i,children:h.jsx(BI,{"data-state":i?"visible":"hidden",...o,ref:t})})}),BI=y.forwardRef((e,t)=>{const{orientation:n="vertical",...r}=e,o=$i(fa,e.__scopeScrollArea),i=y.useRef(null),s=y.useRef(0),a,l=y.useState({content:0,viewport:0,scrollbar:{size:0,paddingStart:0,paddingEnd:0}}),c=_Y(a.viewport,a.content),d={...r,sizes:a,onSizesChange:l,hasThumb:c>0&&c<1,onThumbChange:p=>i.current=p,onThumbPointerUp:()=>s.current=0,onThumbPointerDown:p=>s.current=p};function f(p,g){return LMe(p,s.current,a,g)}return n==="horizontal"?h.jsx(PMe,{...d,ref:t,onThumbPositionChange:()=>{if(o.viewport&&i.current){const p=o.viewport.scrollLeft,g=_z(p,a,o.dir);i.current.style.transform=`translate3d(${g}px, 0, 0)`}},onWheelScroll:p=>{o.viewport&&(o.viewport.scrollLeft=p)},onDragScroll:p=>{o.viewport&&(o.viewport.scrollLeft=f(p,o.dir))}}):n==="vertical"?h.jsx(IMe,{...d,ref:t,onThumbPositionChange:()=>{if(o.viewport&&i.current){const p=o.viewport.scrollTop,g=_z(p,a);i.current.style.transform=`translate3d(0, ${g}px, 0)`}},onWheelScroll:p=>{o.viewport&&(o.viewport.scrollTop=p)},onDragScroll:p=>{o.viewport&&(o.viewport.scrollTop=f(p))}}):null}),PMe=y.forwardRef((e,t)=>{const{sizes:n,onSizesChange:r,...o}=e,i=$i(fa,e.__scopeScrollArea),s,a=y.useState(),l=y.useRef(null),c=Pt(t,l,i.onScrollbarXChange);return y.useEffect(()=>{l.current&&a(getComputedStyle(l.current))},l),h.jsx(bY,{"data-orientation":"horizontal",...o,ref:c,sizes:n,style:{bottom:0,left:i.dir==="rtl"?"var(--radix-scroll-area-corner-width)":0,right:i.dir==="ltr"?"var(--radix-scroll-area-corner-width)":0,"--radix-scroll-area-thumb-width":ew(n)+"px",...e.style},onThumbPointerDown:d=>e.onThumbPointerDown(d.x),onDragScroll:d=>e.onDragScroll(d.x),onWheelScroll:(d,f)=>{if(i.viewport){const p=i.viewport.scrollLeft+d.deltaX;e.onWheelScroll(p),EY(p,f)&&d.preventDefault()}},onResize:()=>{l.current&&i.viewport&&s&&r({content:i.viewport.scrollWidth,viewport:i.viewport.offsetWidth,scrollbar:{size:l.current.clientWidth,paddingStart:rx(s.paddingLeft),paddingEnd:rx(s.paddingRight)}})}})}),IMe=y.forwardRef((e,t)=>{const{sizes:n,onSizesChange:r,...o}=e,i=$i(fa,e.__scopeScrollArea),s,a=y.useState(),l=y.useRef(null),c=Pt(t,l,i.onScrollbarYChange);return y.useEffect(()=>{l.current&&a(getComputedStyle(l.current))},l),h.jsx(bY,{"data-orientation":"vertical",...o,ref:c,sizes:n,style:{top:0,right:i.dir==="ltr"?0:void 0,left:i.dir==="rtl"?0:void 0,bottom:"var(--radix-scroll-area-corner-height)","--radix-scroll-area-thumb-height":ew(n)+"px",...e.style},onThumbPointerDown:d=>e.onThumbPointerDown(d.y),onDragScroll:d=>e.onDragScroll(d.y),onWheelScroll:(d,f)=>{if(i.viewport){const p=i.viewport.scrollTop+d.deltaY;e.onWheelScroll(p),EY(p,f)&&d.preventDefault()}},onResize:()=>{l.current&&i.viewport&&s&&r({content:i.viewport.scrollHeight,viewport:i.viewport.offsetHeight,scrollbar:{size:l.current.clientHeight,paddingStart:rx(s.paddingTop),paddingEnd:rx(s.paddingBottom)}})}})}),AMe,xY=pY(fa),bY=y.forwardRef((e,t)=>{const{__scopeScrollArea:n,sizes:r,hasThumb:o,onThumbChange:i,onThumbPointerUp:s,onThumbPointerDown:a,onThumbPositionChange:l,onDragScroll:c,onWheelScroll:d,onResize:f,...p}=e,g=$i(fa,n),v,b=y.useState(null),_=Pt(t,I=>b(I)),x=y.useRef(null),w=y.useRef(""),C=g.viewport,E=r.content-r.viewport,R=En(d),P=En(l),N=tw(f,10);function k(I){if(x.current){const O=I.clientX-x.current.left,j=I.clientY-x.current.top;c({x:O,y:j})}}return y.useEffect(()=>{const I=O=>{const j=O.target;(v==null?void 0:v.contains(j))&&R(O,E)};return document.addEventListener("wheel",I,{passive:!1}),()=>document.removeEventListener("wheel",I,{passive:!1})},C,v,E,R),y.useEffect(P,r,P),Bf(v,N),Bf(g.content,N),h.jsx(AMe,{scope:n,scrollbar:v,hasThumb:o,onThumbChange:En(i),onThumbPointerUp:En(s),onThumbPositionChange:P,onThumbPointerDown:En(a),children:h.jsx(Ze.div,{...p,ref:_,style:{position:"absolute",...p.style},onPointerDown:yt(e.onPointerDown,I=>{I.button===0&&(I.target.setPointerCapture(I.pointerId),x.current=v.getBoundingClientRect(),w.current=document.body.style.webkitUserSelect,document.body.style.webkitUserSelect="none",g.viewport&&(g.viewport.style.scrollBehavior="auto"),k(I))}),onPointerMove:yt(e.onPointerMove,k),onPointerUp:yt(e.onPointerUp,I=>{const O=I.target;O.hasPointerCapture(I.pointerId)&&O.releasePointerCapture(I.pointerId),document.body.style.webkitUserSelect=w.current,g.viewport&&(g.viewport.style.scrollBehavior=""),x.current=null})})})}),nx="ScrollAreaThumb",wY=y.forwardRef((e,t)=>{const{forceMount:n,...r}=e,o=xY(nx,e.__scopeScrollArea);return h.jsx(ys,{present:n||o.hasThumb,children:h.jsx(MMe,{ref:t,...r})})}),MMe=y.forwardRef((e,t)=>{const{__scopeScrollArea:n,style:r,...o}=e,i=$i(nx,n),s=xY(nx,n),{onThumbPositionChange:a}=s,l=Pt(t,f=>s.onThumbChange(f)),c=y.useRef(void 0),d=tw(()=>{c.current&&(c.current(),c.current=void 0)},100);return y.useEffect(()=>{const f=i.viewport;if(f){const p=()=>{if(d(),!c.current){const g=OMe(f,a);c.current=g,a()}};return a(),f.addEventListener("scroll",p),()=>f.removeEventListener("scroll",p)}},i.viewport,d,a),h.jsx(Ze.div,{"data-state":s.hasThumb?"visible":"hidden",...o,ref:l,style:{width:"var(--radix-scroll-area-thumb-width)",height:"var(--radix-scroll-area-thumb-height)",...r},onPointerDownCapture:yt(e.onPointerDownCapture,f=>{const g=f.target.getBoundingClientRect(),v=f.clientX-g.left,b=f.clientY-g.top;s.onThumbPointerDown({x:v,y:b})}),onPointerUp:yt(e.onPointerUp,s.onThumbPointerUp)})});wY.displayName=nx;var WI="ScrollAreaCorner",SY=y.forwardRef((e,t)=>{const n=$i(WI,e.__scopeScrollArea),r=!!(n.scrollbarX&&n.scrollbarY);return n.type!=="scroll"&&r?h.jsx(jMe,{...e,ref:t}):null});SY.displayName=WI;var jMe=y.forwardRef((e,t)=>{const{__scopeScrollArea:n,...r}=e,o=$i(WI,n),i,s=y.useState(0),a,l=y.useState(0),c=!!(i&&a);return Bf(o.scrollbarX,()=>{var f;const d=((f=o.scrollbarX)==null?void 0:f.offsetHeight)||0;o.onCornerHeightChange(d),l(d)}),Bf(o.scrollbarY,()=>{var f;const d=((f=o.scrollbarY)==null?void 0:f.offsetWidth)||0;o.onCornerWidthChange(d),s(d)}),c?h.jsx(Ze.div,{...r,ref:t,style:{width:i,height:a,position:"absolute",right:o.dir==="ltr"?0:void 0,left:o.dir==="rtl"?0:void 0,bottom:0,...e.style}}):null});function rx(e){return e?parseInt(e,10):0}function _Y(e,t){const n=e/t;return isNaN(n)?0:n}function ew(e){const t=_Y(e.viewport,e.content),n=e.scrollbar.paddingStart+e.scrollbar.paddingEnd,r=(e.scrollbar.size-n)*t;return Math.max(r,18)}function LMe(e,t,n,r="ltr"){const o=ew(n),i=o/2,s=t||i,a=o-s,l=n.scrollbar.paddingStart+s,c=n.scrollbar.size-n.scrollbar.paddingEnd-a,d=n.content-n.viewport,f=r==="ltr"?0,d:d*-1,0;return CY(l,c,f)(e)}function _z(e,t,n="ltr"){const r=ew(t),o=t.scrollbar.paddingStart+t.scrollbar.paddingEnd,i=t.scrollbar.size-o,s=t.content-t.viewport,a=i-r,l=n==="ltr"?0,s:s*-1,0,c=EMe(e,l);return CY(0,s,0,a)(c)}function CY(e,t){return n=>{if(e0===e1||t0===t1)return t0;const r=(t1-t0)/(e1-e0);return t0+r*(n-e0)}}function EY(e,t){return e>0&&e<t}var OMe=(e,t=()=>{})=>{let n={left:e.scrollLeft,top:e.scrollTop},r=0;return function o(){const i={left:e.scrollLeft,top:e.scrollTop},s=n.left!==i.left,a=n.top!==i.top;(s||a)&&t(),n=i,r=window.requestAnimationFrame(o)}(),()=>window.cancelAnimationFrame(r)};function tw(e,t){const n=En(e),r=y.useRef(0);return y.useEffect(()=>()=>window.clearTimeout(r.current),),y.useCallback(()=>{window.clearTimeout(r.current),r.current=window.setTimeout(n,t)},n,t)}function Bf(e,t){const n=En(t);Lo(()=>{let r=0;if(e){const o=new ResizeObserver(()=>{cancelAnimationFrame(r),r=window.requestAnimationFrame(n)});return o.observe(e),()=>{window.cancelAnimationFrame(r),o.unobserve(e)}}},e,n)}var NY=mY,DMe=vY,FMe=SY;const RY=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(NY,{ref:r,className:nt("relative overflow-hidden",e),...n,children:h.jsx(DMe,{className:"h-full w-full rounded-inherit",children:t}),h.jsx(TY,{}),h.jsx(FMe,{})}));RY.displayName=NY.displayName;const TY=y.forwardRef(({className:e,orientation:t="vertical",...n},r)=>h.jsx(HI,{ref:r,orientation:t,className:nt("flex touch-none select-none transition-colors",t==="vertical"&&"h-full w-2.5 border-l border-l-transparent p-1px",t==="horizontal"&&"h-2.5 flex-col border-t border-t-transparent p-1px",e),...n,children:h.jsx(wY,{className:"relative flex-1 rounded-full bg-border"})}));TY.displayName=HI.displayName;const kY=y.memo(({isMonitored:e,variant:t="outline",className:n="",title:r="Item is monitored"})=>e?h.jsx(ht,{variant:t,className:`flex h-4 items-center gap-1 px-1 ring-1 ring-red-600/90 text-slate-200/90 ${n}`,title:r,children:h.jsx(E9,{className:"h-3 w-3"})}):null);kY.displayName="MonitoredBadge";const PY=y.memo(({filter:e,onClick:t,isMonitored:n=!1,isDetached:r=!1,isStalled:o=!1})=>{const i=Xe(w=>e.idx!==void 0?W9(String(e.idx))(w):null),s=y.useCallback(()=>{t&&e.idx!==void 0&&t(e.idx)},e.idx,t),{sessionType:a,formattedBytes:l,formattedTime:c,formattedPackets:d,formattedPacketRate:f}=e.computed,p=!!(e.pck_done&&e.pck_done>0),g=!!(e.time&&e.time>0),v=y.useMemo(()=>n||r,n,r),b=y.useMemo(()=>v?"ring-1 ring-red-700/90":"ring-1 ring-transparent hover:ring-monitor-accent/40",v),_=y.useMemo(()=>r?"cursor-not-allowed opacity-60":"cursor-pointer hover:bg-white/4",r),x=a==="source"?"Source":a==="sink"?"Sink":"Process";return h.jsxs("div",{className:`relativeflex flex-col gap-1.5 p-2.5 rounded-lg bg-black/20 border-transparent ${b} ${_}`,onClick:s,children:n&&!r&&h.jsx(kY,{isMonitored:!0,title:"Filter is monitored",className:"absolute -top-0.5 -left-0.5"}),h.jsxs("div",{className:"flex items-center justify-between gap-2",children:h.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:h.jsx("span",{className:"font-ui font-semibold text-sm truncate text-monitor-text-primary",children:e.name}),o&&h.jsx(GP,{className:"h-3.5 w-3.5 text-amber-500 flex-shrink-0",title:"Stalled - no media progress"})}),h.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:h.jsx(ht,{variant:"outline",className:"h-5 px-1.5 text-xs ring-1 ring-monitor-line bg-white/5 text-monitor-text-secondary",children:x}),i&&i.errors>0&&h.jsxs(ht,{variant:"outline",className:`h-5 px-1.5 text-10px uppercase tracking-wide + bg-red-900/20 text-red-300 + border border-red-700/60 + rounded-sm font-semibold`,title:`${i.errors} error(s) in logs`,children:i.errors," ERR"}),i&&i.warnings>0&&h.jsxs(ht,{variant:"outline",className:`h-5 px-1.5 text-10px uppercase tracking-wide + bg-amber-900/20 text-amber-300 + border border-amber-700/60 + rounded-sm font-semibold`,title:`${i.warnings} warning(s) in logs`,children:i.warnings," WARN"}),e.is_eos&&h.jsx(ht,{variant:"outline",className:`h-5 px-1.5 text-10px uppercase tracking-wide + bg-emerald-900/15 text-emerald-300 + border border-emerald-700/60 + rounded-sm`,children:"EOS"})})}),h.jsxs("div",{className:"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs font-mono tabular-nums text-monitor-text-muted",children:p&&h.jsxs(h.Fragment,{children:h.jsxs("span",{title:"Packets processed",children:d," pkt"}),h.jsx("span",{className:"text-monitor-text-subtle",children:"•"})}),g&&h.jsxs(h.Fragment,{children:h.jsx("span",{className:"text-info",title:"Packet rate",children:f}),h.jsx("span",{className:"text-monitor-text-subtle",children:"•"})}),l&&h.jsxs(h.Fragment,{children:h.jsx("span",{title:"Total data processed",children:l}),g&&h.jsx("span",{className:"text-monitor-text-subtle",children:"•"})}),g&&h.jsx("span",{title:"time the filter has been running",children:c}),(e.errors&&e.errors>0||e.current_errors&&e.current_errors>0)&&h.jsxs(h.Fragment,{children:h.jsx("span",{className:"text-monitor-text-subtle",children:"•"}),h.jsxs("span",{className:"text-rose-400",title:"Errors encountered",children:(e.errors||0)+(e.current_errors||0)," err"})})}),h.jsxs("div",{className:"flex items-center justify-end gap-1 text-8px font-mono tabular-nums text-monitor-text-muted",title:"Input PIDs / Output PIDs",children:h.jsx("span",{className:"uppercase tracking-wider",children:"PIDs"}),h.jsxs("span",{children:e.nb_ipid||0,"/",e.nb_opid||0})})})});PY.displayName="FilterStatCard";const Cz=(e,t)=>t.some(n=>n.type===_n.FILTERSESSION&&n.isDetached===!0&&n.detachedFilterIdx===e),nN=(e,t)=>t.has(e),$Me=e=>e.startsWith("filter-")?parseInt(e.replace("filter-",""),10):null,IY=y.memo(({filtersWithLiveStats:e,filtersMatchingCriteria:t,monitoredFilters:n,onCardClick:r,activeWidgets:o=})=>{const i=VV(e),s=zx(gP),a=y.useMemo(()=>t.length,t.length),l=y.useMemo(()=>i.filter(d=>!Cz(d.idx||-1,o)).sort((d,f)=>{const p=nN(d.idx||-1,n),g=nN(f.idx||-1,n);return p&&!g?-1:!p&&g?1:(f.bytes_done||0)-(d.bytes_done||0)}),i,n,o);return h.jsxs("div",{className:"flex flex-col h-full",children:h.jsx("div",{className:"mb-3 flex items-center justify-between",children:h.jsxs("h2",{className:"flex items-center gap-2 text-lg font-semibold text-monitor-text-primary",children:"Filters",h.jsx(ht,{variant:"secondary",className:"ml-1 h-6 px-2 text-sm tabular-nums bg-white/5 ring-1 ring-monitor-line text-emerald-400",children:a})})}),h.jsx(RY,{className:"flex-1",children:a>0?h.jsx("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 md:gap-4 xl:gap-5",children:l.map(c=>{const d=c.idx||-1;return h.jsx(PY,{filter:c,onClick:r,isMonitored:nN(d,n),isDetached:Cz(d,o),isStalled:sd.toString()??!1},c.idx||c.ID||c.name)})}):h.jsx("div",{className:"flex items-center justify-center h-full",children:h.jsx("div",{className:"text-center space-y-4 p-8",children:h.jsx("div",{className:"rounded-lg ring-1 ring-monitor-line bg-monitor-panel p-8 shadow-none",children:h.jsx("h3",{className:"text-lg font-medium mb-2 text-monitor-text-secondary",children:"No filters found"})})})})})})});IY.displayName="FiltersGrid";var UI="Progress",qI=100,zMe,o5e=to(UI),HMe,BMe=zMe(UI),AY=y.forwardRef((e,t)=>{const{__scopeProgress:n,value:r=null,max:o,getValueLabel:i=WMe,...s}=e;(o||o===0)&&!Ez(o)&&console.error(UMe(`${o}`,"Progress"));const a=Ez(o)?o:qI;r!==null&&!Nz(r,a)&&console.error(qMe(`${r}`,"Progress"));const l=Nz(r,a)?r:null,c=ox(l)?i(l,a):void 0;return h.jsx(HMe,{scope:n,value:l,max:a,children:h.jsx(Ze.div,{"aria-valuemax":a,"aria-valuemin":0,"aria-valuenow":ox(l)?l:void 0,"aria-valuetext":c,role:"progressbar","data-state":LY(l,a),"data-value":l??void 0,"data-max":a,...s,ref:t})})});AY.displayName=UI;var MY="ProgressIndicator",jY=y.forwardRef((e,t)=>{const{__scopeProgress:n,...r}=e,o=BMe(MY,n);return h.jsx(Ze.div,{"data-state":LY(o.value,o.max),"data-value":o.value??void 0,"data-max":o.max,...r,ref:t})});jY.displayName=MY;function WMe(e,t){return`${Math.round(e/t*100)}%`}function LY(e,t){return e==null?"indeterminate":e===t?"complete":"loading"}function ox(e){return typeof e=="number"}function Ez(e){return ox(e)&&!isNaN(e)&&e>0}function Nz(e,t){return ox(e)&&!isNaN(e)&&e<=t&&e>=0}function UMe(e,t){return`Invalid prop \`max\` of value \`${e}\` supplied to \`${t}\`. Only numbers greater than 0 are valid max values. Defaulting to \`${qI}\`.`}function qMe(e,t){return`Invalid prop \`value\` of value \`${e}\` supplied to \`${t}\`. The \`value\` prop must be: + - a positive number + - less than the value passed to \`max\` (or ${qI} if no \`max\` prop is set) + - \`null\` or \`undefined\` if the progress is indeterminate. + +Defaulting to \`null\`.`}var OY=AY,GMe=jY;const nw=y.forwardRef(({className:e,value:t,color:n,...r},o)=>h.jsx(OY,{ref:o,className:nt("relative h-4 w-full overflow-hidden rounded-full bg-secondary",e),...r,children:h.jsx(GMe,{className:nt("h-full w-full flex-1 transition-all",n||"bg-primary"),style:{transform:`translateX(-${100-(t||0)}%)`}})}));nw.displayName=OY.displayName;const VMe=e=>{switch(e){case"high":return"bg-red-500 shadow-red-500/50";case"medium":return"bg-yellow-500 shadow-yellow-500/50";case"low":return"bg-green-500 shadow-green-500/50";default:return"bg-gray-500 shadow-gray-500/50"}},YMe=e=>{switch(e){case"sm":return"w-2 h-2";case"md":return"w-3 h-3";case"lg":return"w-4 h-4";default:return"w-3 h-3"}},KMe=({level:e,size:t="md",className:n})=>{const r=VMe(e),o=YMe(t);return h.jsx("div",{className:nt("rounded-full shadow-lg",r,o,n),style:{willChange:"transform",transform:"translateZ(0)"}})};var rw="Popover",DY,i5e=to(rw,vb),bg=vb(),XMe,kc=DY(rw),wg=e=>{const{__scopePopover:t,children:n,open:r,defaultOpen:o,onOpenChange:i,modal:s=!1}=e,a=bg(t),l=y.useRef(null),c,d=y.useState(!1),f,p=xs({prop:r,defaultProp:o??!1,onChange:i,caller:rw});return h.jsx(IP,{...a,children:h.jsx(XMe,{scope:t,contentId:Mi(),triggerRef:l,open:f,onOpenChange:p,onOpenToggle:y.useCallback(()=>p(g=>!g),p),hasCustomAnchor:c,onCustomAnchorAdd:y.useCallback(()=>d(!0),),onCustomAnchorRemove:y.useCallback(()=>d(!1),),modal:s,children:n})})};wg.displayName=rw;var FY="PopoverAnchor",ZMe=y.forwardRef((e,t)=>{const{__scopePopover:n,...r}=e,o=kc(FY,n),i=bg(n),{onCustomAnchorAdd:s,onCustomAnchorRemove:a}=o;return y.useEffect(()=>(s(),()=>a()),s,a),h.jsx(AP,{...i,...r,ref:t})});ZMe.displayName=FY;var $Y="PopoverTrigger",Sg=y.forwardRef((e,t)=>{const{__scopePopover:n,...r}=e,o=kc($Y,n),i=bg(n),s=Pt(t,o.triggerRef),a=h.jsx(Ze.button,{type:"button","aria-haspopup":"dialog","aria-expanded":o.open,"aria-controls":o.contentId,"data-state":UY(o.open),...r,ref:s,onClick:xt(e.onClick,o.onOpenToggle)});return o.hasCustomAnchor?a:h.jsx(AP,{asChild:!0,...i,children:a})});Sg.displayName=$Y;var GI="PopoverPortal",QMe,JMe=DY(GI,{forceMount:void 0}),zY=e=>{const{__scopePopover:t,forceMount:n,children:r,container:o}=e,i=kc(GI,t);return h.jsx(QMe,{scope:t,forceMount:n,children:h.jsx(Nc,{present:n||i.open,children:h.jsx(ig,{asChild:!0,container:o,children:r})})})};zY.displayName=GI;var Wf="PopoverContent",_g=y.forwardRef((e,t)=>{const n=JMe(Wf,e.__scopePopover),{forceMount:r=n.forceMount,...o}=e,i=kc(Wf,e.__scopePopover);return h.jsx(Nc,{present:r||i.open,children:i.modal?h.jsx(tje,{...o,ref:t}):h.jsx(nje,{...o,ref:t})})});_g.displayName=Wf;var eje=jf("PopoverContent.RemoveScroll"),tje=y.forwardRef((e,t)=>{const n=kc(Wf,e.__scopePopover),r=y.useRef(null),o=Pt(t,r),i=y.useRef(!1);return y.useEffect(()=>{const s=r.current;if(s)return MP(s)},),h.jsx(xb,{as:eje,allowPinchZoom:!0,children:h.jsx(HY,{...e,ref:o,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:xt(e.onCloseAutoFocus,s=>{var a;s.preventDefault(),i.current||(a=n.triggerRef.current)==null||a.focus()}),onPointerDownOutside:xt(e.onPointerDownOutside,s=>{const a=s.detail.originalEvent,l=a.button===0&&a.ctrlKey===!0,c=a.button===2||l;i.current=c},{checkForDefaultPrevented:!1}),onFocusOutside:xt(e.onFocusOutside,s=>s.preventDefault(),{checkForDefaultPrevented:!1})})})}),nje=y.forwardRef((e,t)=>{const n=kc(Wf,e.__scopePopover),r=y.useRef(!1),o=y.useRef(!1);return h.jsx(HY,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:i=>{var s,a;(s=e.onCloseAutoFocus)==null||s.call(e,i),i.defaultPrevented||(r.current||(a=n.triggerRef.current)==null||a.focus(),i.preventDefault()),r.current=!1,o.current=!1},onInteractOutside:i=>{var l,c;(l=e.onInteractOutside)==null||l.call(e,i),i.defaultPrevented||(r.current=!0,i.detail.originalEvent.type==="pointerdown"&&(o.current=!0));const s=i.target;((c=n.triggerRef.current)==null?void 0:c.contains(s))&&i.preventDefault(),i.detail.originalEvent.type==="focusin"&&o.current&&i.preventDefault()}})}),HY=y.forwardRef((e,t)=>{const{__scopePopover:n,trapFocus:r,onOpenAutoFocus:o,onCloseAutoFocus:i,disableOutsidePointerEvents:s,onEscapeKeyDown:a,onPointerDownOutside:l,onFocusOutside:c,onInteractOutside:d,...f}=e,p=kc(Wf,n),g=bg(n);return VU(),h.jsx(gb,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:o,onUnmountAutoFocus:i,children:h.jsx(TP,{asChild:!0,disableOutsidePointerEvents:s,onInteractOutside:d,onEscapeKeyDown:a,onPointerDownOutside:l,onFocusOutside:c,onDismiss:()=>p.onOpenChange(!1),children:h.jsx(o7,{"data-state":UY(p.open),role:"dialog",id:p.contentId,...g,...f,ref:t,style:{...f.style,"--radix-popover-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-popover-content-available-width":"var(--radix-popper-available-width)","--radix-popover-content-available-height":"var(--radix-popper-available-height)","--radix-popover-trigger-width":"var(--radix-popper-anchor-width)","--radix-popover-trigger-height":"var(--radix-popper-anchor-height)"}})})})}),BY="PopoverClose",rje=y.forwardRef((e,t)=>{const{__scopePopover:n,...r}=e,o=kc(BY,n);return h.jsx(Ze.button,{type:"button",...r,ref:t,onClick:xt(e.onClick,()=>o.onOpenChange(!1))})});rje.displayName=BY;var oje="PopoverArrow",WY=y.forwardRef((e,t)=>{const{__scopePopover:n,...r}=e,o=bg(n);return h.jsx(i7,{...o,...r,ref:t})});WY.displayName=oje;function UY(e){return e?"open":"closed"}var ije=wg,sje=Sg,aje=zY,qY=_g,GY=WY;const lje=ije,cje=sje,uje=aje,VY=y.forwardRef(({className:e,sideOffset:t=4,side:n="bottom",align:r="center",...o},i)=>h.jsx(uje,{children:h.jsx(qY,{ref:i,sideOffset:t,side:n,align:r,className:nt("z-50 min-w-8rem max-w-20rem overflow-hidden rounded-md border bg-gray-800 p-2 text-gray-100 shadow-md","data-state=open:animate-in data-state=closed:animate-out","data-state=closed:fade-out-0 data-state=open:fade-in-0","data-state=closed:zoom-out-95 data-state=open:zoom-in-95","data-side=bottom:slide-in-from-top-2 data-side=left:slide-in-from-right-2","data-side=right:slide-in-from-left-2 data-side=top:slide-in-from-bottom-2",e),...o})}));VY.displayName=qY.displayName;const dje=y.forwardRef(({className:e,...t},n)=>h.jsx(GY,{ref:n,className:nt("fill-gray-800",e),...t}));dje.displayName=GY.displayName;const fje=()=>{const{commandLine:e,isLoading:t}=DEe(),n,r=y.useState(!1),o=()=>{e&&(navigator.clipboard.writeText(e),r(!0),setTimeout(()=>r(!1),2e3))};return h.jsxs(lje,{children:h.jsx($0,{content:"Show GPAC command line",children:h.jsx(cje,{asChild:!0,children:h.jsx(fs,{variant:"ghost",size:"sm",className:"flex items-center gap-1 text-monitor-text-muted hover:text-monitor-text-primary",children:h.jsx(DR,{className:"h-4 w-4"})})})}),h.jsx(VY,{className:"w-600px max-w-90vw bg-monitor-panel border-monitor-line p-3",align:"end",children:h.jsxs("div",{className:"space-y-2",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx("span",{className:"text-xs font-medium text-monitor-text-muted",children:"Command Line"}),!t&&e&&h.jsx(fs,{size:"sm",variant:"ghost",onClick:o,className:"h-6 px-2",children:n?h.jsx(dP,{className:"h-3 w-3 text-emerald-400"}):h.jsx(v0e,{className:"h-3 w-3"})})}),t?h.jsx("div",{className:"text-monitor-text-muted text-xs",children:"Loading..."}):e?h.jsx("pre",{className:"bg-black/40 p-2 rounded text-xs text-monitor-text-secondary overflow-x-auto font-mono border border-monitor-line/50",children:e}):h.jsx("div",{className:"text-monitor-text-muted text-xs",children:"Not available"})})})})},rN=({title:e,value:t,icon:n,description:r,trend:o,badge:i,progress:s,activityLevel:a,className:l=""})=>{const c=()=>o==="up"?h.jsx(h0e,{className:"h-3.5 w-3.5 text-teal-400/80"}):o==="down"?h.jsx(d0e,{className:"h-3.5 w-3.5 text-rose-400/80"}):h.jsx("div",{className:"h-3.5 w-3.5"});return h.jsx(Jn,{className:`bg-monitor-panel/60 border border-white/0.03 rounded-lg shadow-none hover:bg-monitor-panel/80 h-80px ${l}`,children:h.jsxs(er,{className:"p-4 h-full flex flex-col justify-between",children:h.jsxs("div",{className:"flex items-center justify-between mb-1",children:h.jsx(Rr,{className:"text-11px font-medium text-monitor-text-muted/60 uppercase tracking-wider",children:e}),h.jsxs("div",{className:"flex items-center gap-2 opacity-30",children:a&&h.jsx(KMe,{level:a,size:"sm"}),h.jsx("div",{className:"opacity-70",children:n})})}),h.jsxs("div",{className:"flex items-baseline gap-2 my-auto",children:h.jsx("div",{className:"text-2xl font-bold text-slate-50 tabular-nums leading-none",children:t}),c()}),r&&h.jsx("p",{className:"text-11px text-monitor-text-muted/50 leading-tight",children:r}),i&&!r&&h.jsx(ht,{className:"text-xs bg-teal-500/10 text-teal-400 ring-1 ring-teal-400/20 w-fit",children:i}),s!==void 0&&h.jsx("div",{className:"mt-1",children:h.jsx(nw,{value:s,className:"h-1",color:s>80?"bg-rose-500":s>50?"bg-amber-400":"bg-teal-500"})})})})},hje=({systemStats:e,statsCounters:t,filtersWithLiveStats:n,filtersMatchingCriteria:r,loading:o,monitoredFilters:i,onCardClick:s,refreshInterval:a,activeWidgets:l=})=>h.jsxs("div",{className:"space-y-4",children:h.jsxs("div",{children:h.jsx("div",{className:"flex items-center justify-between mb-3",children:h.jsxs("div",{className:"flex items-center gap-2",children:h.jsxs(ht,{className:"text-xs bg-white/5 ring-1 ring-monitor-line text-monitor-text-secondary",children:"Refresh: ",a}),h.jsx(fje,{})})}),h.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-0",children:h.jsxs("div",{className:"relative",children:h.jsx(rN,{title:"Data Processed",value:jo(e.totalBytes),icon:h.jsx(y0e,{className:"h-4 w-4"}),description:`${Xp(e.totalPackets)} packets`,trend:e.totalBytes>0?"up":"neutral",activityLevel:e.dataProcessingActivityLevel}),h.jsx("div",{className:"hidden lg:block absolute right-0 top-1/2 -translate-y-1/2 h-12 w-px bg-white/0.06"})}),h.jsxs("div",{className:"relative lg:px-4",children:h.jsx(rN,{title:"Processing Activity",value:t.processing,icon:h.jsx(hP,{className:"h-4 w-4"}),description:`${(t.processing/Math.max(t.total,1)*100).toFixed(1)}% active`,trend:t.processing>0?"up":"neutral",activityLevel:e.systemActivityLevel}),h.jsx("div",{className:"hidden lg:block absolute right-0 top-1/2 -translate-y-1/2 h-12 w-px bg-white/0.06"})}),h.jsx("div",{className:"lg:pl-4",children:h.jsx(rN,{title:"Pipeline Status",value:`${t.sources}→${t.sinks}`,icon:h.jsx(uP,{className:"h-4 w-4"}),description:"Sources to sinks",badge:t.processing>0?"Processing":"Idle",trend:t.sources>0&&t.sinks>0?"up":"neutral"})})})}),h.jsx("div",{children:h.jsx(IY,{filtersWithLiveStats:n,filtersMatchingCriteria:r,loading:o,monitoredFilters:i,onCardClick:s,activeWidgets:l})})}),pje=e=>e.stats.disconnected?{status:"Disconnected",variant:"destructive",icon:R9,color:"text-danger"}:e.would_block?{status:"Blocked",variant:"destructive",icon:zy,color:"text-danger"}:e.eos?{status:"End of Stream",variant:"secondary",icon:$y}:e.playing?{status:"Playing",variant:"default",icon:hP,color:"text-info"}:{status:"Idle",variant:"outline",icon:$y},mje=e=>e.eos?{icon:$y,label:"EOS",variant:"outline"}:e.playing?{icon:hP,label:"Playing",variant:"default"}:{icon:$y,label:"Paused",variant:"outline"},YY=(e,t,n)=>{const r=(e==null?void 0:e.toLowerCase())||"";return n&&n.errors>0?{variant:"destructive",color:"text-red-500",bgColor:"bg-red-500/10",label:"Critical"}:t?{variant:"secondary",color:"text-yellow-500",bgColor:"bg-yellow-500/10",label:"Stalled"}:n&&n.warnings>0?{variant:"secondary",color:"text-yellow-500",bgColor:"bg-yellow-500/10",label:"Warning"}:r.includes("error")||r.includes("stop")?{variant:"destructive",color:"text-red-500",bgColor:"bg-red-500/10",label:"Critical"}:r.includes("warning")||r.includes("wait")||r.includes("block")?{variant:"secondary",color:"text-yellow-500",bgColor:"bg-yellow-500/10",label:"Warning"}:{variant:"default",color:"text-green-500",bgColor:"bg-green-500/10",label:"Healthy"}},gje=e=>{const t=;return e.stats.disconnected&&t.push({icon:R9,color:"text-danger",label:"Disconnected"}),e.would_block&&t.push({icon:zy,color:"text-danger",label:"Blocked"}),(e.nb_pck_queued||0)>50&&t.push({icon:C9,color:"text-warning",label:`Queue: ${e.nb_pck_queued}`}),t},vje=e=>e.width&&e.height?"Video":e.samplerate||e.channels?"Audio":e.codec?"Media":"Data",yje=y.memo(({filter:e})=>{const{status:t,type:n,idx:r,time:o}=e,i=zx(z9(r.toString())),s=y.useMemo(()=>YY(t,i),t,i),a=y.useMemo(()=>Hf(o),o);return h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-3",children:h.jsxs(Rr,{className:"flex items-center justify-between text-sm",children:h.jsx("span",{children:"Filter Health"}),h.jsx(ht,{variant:s.variant,className:"text-xs",children:s.label})})}),h.jsxs(er,{className:"space-y-3",children:h.jsx("div",{className:`rounded-lg p-3 ${s.bgColor} `,children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx("span",{className:"text-xs font-mediumt text-muted-foreground",children:"Status"}),h.jsx("span",{className:`text-xs px-2 font-cond tabular-nums ${s.color}`,children:t||"Unknown"})})}),h.jsxs("div",{className:"grid grid-cols-3 gap-3 text-sm",children:h.jsxs("div",{className:"space-y-1",children:h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Type"}),h.jsx("div",{className:"font-medium truncate",children:n||"N/A"})}),h.jsxs("div",{className:"space-y-1",children:h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Index"}),h.jsx("div",{className:"font-medium tabular-nums",children:r})}),h.jsxs("div",{className:"space-y-1",children:h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Uptime"}),h.jsx("div",{className:"font-medium tabular-nums",children:a})})})})})});yje.displayName="FilterHealthCard";const xje=e=>{const{bytes_done:t,bytes_sent:n,pck_done:r,time:o}=e,i=SEe(o),s=t/i,a=r/i,l=n>0?t/n*100:0;return{throughput:`${jo(s)}/s`,packetRate:zm(a),efficiency:`${l.toFixed(1)}%`,efficiencyValue:l}},KY=y.memo(({filter:e})=>{const t=y.useMemo(()=>xje(e),e);return h.jsxs(Jn,{className:"bg-monitor-panel/55 border-0 border-r border-monitor-line/10",children:h.jsx(Dr,{className:"pb-1 pt-2 px-2",children:h.jsx(Rr,{className:"text-xs font-medium",children:"Real-time"})}),h.jsx(er,{className:"p-2",children:h.jsxs("div",{className:"space-y-1",children:h.jsxs("div",{className:"flex justify-between items-baseline",children:h.jsx("div",{className:"text-xs text-muted-foreground",children:"Throughput"}),h.jsx("div",{className:"text-sm font-semibold text-info tabular-nums",children:t.throughput})}),h.jsxs("div",{className:"flex justify-between items-baseline",children:h.jsx("div",{className:"text-xs text-muted-foreground",children:"Packet Rate"}),h.jsx("div",{className:"text-sm font-semibold text-info tabular-nums",children:t.packetRate})})})})})});KY.displayName="RealtimeMetricsCard";const bje=y.memo(({filter:e})=>h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-2",children:h.jsx(Rr,{className:"text-sm",children:"PID Metrics"})}),h.jsx(er,{children:h.jsxs("div",{className:"grid grid-cols-2 gap-4",children:h.jsxs("div",{className:"text-center",children:h.jsx("div",{className:"text-2xl font-bold text-monitor-text-secondary tabular-nums",children:e.nb_ipid}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Input PIDs"})}),h.jsxs("div",{className:"text-center",children:h.jsx("div",{className:"text-2xl font-bold text-monitor-secondary tabular-nums",children:e.nb_opid}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Output PIDs"})})})})}));bje.displayName="PIDMetricsCard";const Vo="text-xs md:text-sm lg:text-sm",Da="text-10px md:text-xs lg:text-xs",Hm="text-9px md:text-10px lg:text-11px",wje="text-sm md:text-base lg:text-base",Xd="text-10px md:text-xs lg:text-xs",Sje=y.memo(({tasks:e,time:t})=>h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-2",children:h.jsxs(Rr,{className:"flex items-center gap-2 text-sm",children:h.jsx(uP,{className:"h-4 w-4"}),"Processing"})}),h.jsxs(er,{className:"space-y-2",children:h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:`${Da} text-muted-foreground stat-label`,children:"Tasks"}),h.jsx("span",{className:`${Vo} font-medium text-info tabular-nums`,children:e||0})}),h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:`${Da} text-muted-foreground stat-label`,children:"Time"}),h.jsx("span",{className:`${Vo} font-medium text-info tabular-nums`,children:Hf(t)})})})}));Sje.displayName="ProcessingCard";const XY=y.memo(({pck_done:e,pck_sent:t,pck_ifce_sent:n})=>h.jsxs(Jn,{className:"bg-monitor-panel/55 border-0 border-r border-monitor-line/10",children:h.jsx(Dr,{className:"pb-1 pt-2 px-2",children:h.jsxs(Rr,{className:"flex items-center gap-1 text-xs font-medium",children:h.jsx(N0e,{className:"h-3 w-3"}),"Packets"})}),h.jsxs(er,{className:"p-2 space-y-1",children:h.jsxs("div",{className:"flex justify-between text-xs",children:h.jsx("span",{className:"text-muted-foreground",children:"Done"}),h.jsx("span",{className:"font-medium text-info tabular-nums",children:Xp(e||0)})}),h.jsxs("div",{className:"flex justify-between text-xs",children:h.jsx("span",{className:"text-muted-foreground",children:"Sent"}),h.jsx("span",{className:"font-medium text-info tabular-nums",children:Xp(t||0)})}),n!==void 0&&h.jsxs("div",{className:"flex justify-between text-xs",children:h.jsx("span",{className:"text-muted-foreground",children:"Interface"}),h.jsx("span",{className:"font-medium text-info tabular-nums",children:Xp(n)})})})}));XY.displayName="PacketsCard";const ZY=y.memo(({bytes_done:e,bytes_sent:t})=>h.jsxs(Jn,{className:"bg-monitor-panel/55 rounded border-0 border-r border-monitor-line/10",children:h.jsx(Dr,{className:"pb-1 pt-2 px-2",children:h.jsxs(Rr,{className:"flex items-center gap-1 text-xs font-medium",children:h.jsx(S0e,{className:"h-3 w-3"}),"Data"})}),h.jsxs(er,{className:"p-2 space-y-1",children:h.jsxs("div",{className:"flex justify-between text-xs",children:h.jsx("span",{className:"text-muted-foreground",children:"Done"}),h.jsx("span",{className:"font-medium text-info tabular-nums",children:jo(e||0)})}),h.jsxs("div",{className:"flex justify-between text-xs",children:h.jsx("span",{className:"text-muted-foreground",children:"Sent"}),h.jsx("span",{className:"font-medium text-info tabular-nums",children:jo(t||0)})})})}));ZY.displayName="DataCard";const _je=y.memo(({name:e,buffer:t,buffer_total:n,codec:r,...o})=>{const i=n>0?t/n*100:0;return h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-2",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx(Rr,{className:"text-sm",children:e}),r&&h.jsx(ht,{variant:"outline",children:r})})}),h.jsxs(er,{className:"space-y-2",children:h.jsxs("div",{className:"grid grid-cols-2 gap-4",children:h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:"text-xs text-muted-foreground stat-label",children:"Buffer"}),h.jsxs("span",{className:"text-sm font-medium text-info tabular-nums",children:jo(t)," / ",jo(n)})}),h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:"text-xs text-muted-foreground stat-label",children:"Usage"}),h.jsxs("span",{className:"text-sm font-medium text-info tabular-nums",children:i.toFixed(1),"%"})})}),Object.entries(o).filter((s,a)=>s!=="parentFilter"&&a!==void 0&&a!==null&&typeof a!="object").slice(0,4).map((s,a)=>h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:"text-xs text-muted-foreground capitalize",children:s.replace("_"," ")}),h.jsx("span",{className:"text-sm font-medium text-info tabular-nums",children:typeof a=="number"?a>1e6?jo(a):a.toLocaleString():String(a)})},s))})})});_je.displayName="PIDDetails";const Cje=(e,t,n,r)=>n||t?{color:"text-danger",status:"Critical",variant:"destructive"}:e/1e3<100||r>100?{color:"text-warning",status:"Warning",variant:"secondary"}:{color:"text-info",status:"Healthy",variant:"default"},Eje=e=>{const t=e.buffer_total&&e.buffer_total>0?e.buffer/e.buffer_total*100:0,n=Cje(e.buffer,e.would_block||!1,e.stats.disconnected||!1,e.nb_pck_queued||0),r=y.useMemo(()=>{key:"connection",label:"Connection",value:e.stats.disconnected?"Disconnected":"Connected",status:e.stats.disconnected?"critical":"healthy",variant:e.stats.disconnected?"destructive":"default",priority:1,show:!0},{key:"blocking",label:"Flow Control",value:e.would_block?"BLOCKED":"Normal",status:e.would_block?"critical":"healthy",variant:e.would_block?"destructive":"secondary",priority:2,show:e.would_block||!1},{key:"queue",label:"Queue",value:`${e.nb_pck_queued||0} packets`,status:(e.nb_pck_queued||0)>50?"warning":"healthy",variant:(e.nb_pck_queued||0)>50?"secondary":"outline",priority:3,show:(e.nb_pck_queued||0)>0},{key:"playback",label:"Stream State",value:e.eos?"End of Stream":e.playing?"Playing":"Paused",status:e.eos?"info":e.playing?"healthy":"warning",variant:e.eos?"outline":e.playing?"default":"secondary",priority:5,show:!0},e);return{bufferUsage:t,overallHealth:n,criticalStates:r}},QY=y.memo(({pidData:e})=>{const{bufferUsage:t,overallHealth:n,criticalStates:r}=Eje(e),i=(s=>s==="Critical"?DR:s==="Warning"?zy:dP)(n.status);return h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-3",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsxs(Rr,{className:"flex items-center gap-2 text-sm",children:h.jsx(i,{className:"h-4 w-4 stat-label"}),"System Health"}),h.jsx(ht,{variant:n.variant,children:n.status})})}),h.jsxs(er,{className:"space-y-4",children:h.jsx("div",{className:"grid grid-cols-1 gap-3",children:r.filter(s=>s.show).sort((s,a)=>s.priority-a.priority).map(s=>h.jsxs("div",{className:"flex items-center justify-between p-2 rounded-md bg-background/50",children:h.jsxs("div",{className:"flex flex-col",children:h.jsx("span",{className:"text-xs font-medium stat-label",children:s.label}),h.jsx("span",{className:"text-sm stat text-info tabular-nums",children:s.value})}),h.jsx(ht,{variant:s.variant,className:"shrink-0",children:s.status==="critical"?"Critical":s.status==="warning"?"Warning":s.status==="info"?"Info":"OK"})},s.key))}),h.jsxs("div",{className:"space-y-2",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx("span",{className:"text-xs text-muted-foreground stat-label",children:"Buffer Level"}),h.jsxs("div",{className:"text-right",children:h.jsx("div",{className:`text-sm font-bold text-info tabular-nums ${n.color}`,children:D3(e.buffer)}),h.jsxs("div",{className:"text-xs text-muted-foreground text-info tabular-nums",children:t.toFixed(1),"%"})})}),h.jsx(nw,{value:t,className:"h-3",style:{"--progress-background":n.status==="Critical"?"#ef4444":n.status==="Warning"?"#f59e0b":"#10b981"}}),h.jsxs("div",{className:"flex justify-between text-xs text-muted-foreground tabular-nums",children:h.jsx("span",{children:"0 ms"}),h.jsx("span",{children:e.buffer_total?D3(e.buffer_total):"0 ms"})}),n.status==="Critical"&&h.jsxs("div",{className:"flex items-center gap-1 text-xs text-red-600 bg-red-50 p-1 rounded",children:h.jsx(zy,{className:"h-3 w-3"}),h.jsx("span",{children:"Buffer critically low - risk of underflow"})}),n.status==="Warning"&&h.jsxs("div",{className:"flex items-center gap-1 text-xs text-orange-600 bg-orange-50 p-1 rounded",children:h.jsx(DR,{className:"h-3 w-3"}),h.jsx("span",{children:"Buffer level requires monitoring"})})})})})});QY.displayName="CriticalPIDStats";const Nje=e=>y.useMemo(()=>{let n;return e.stats.nb_processed&&e.stats.total_process_time&&e.stats.nb_processed>0&&(n=Hf(e.stats.total_process_time/e.stats.nb_processed)),{throughput:{dataBitrate:{average:Xs(e.stats.average_bitrate),max:Xs(e.stats.max_bitrate)},packetRate:{average:zm(e.stats.average_process_rate),max:zm(e.stats.max_process_rate)}},processing:{totalProcessed:Xp(e.stats.nb_processed||0),totalTime:Hf(e.stats.total_process_time),averagePerItem:n}}},e.stats),JY=y.memo(({pidData:e})=>{const t=Nje(e);return h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-4",children:h.jsxs(Rr,{className:"flex items-center gap-2 text-sm",children:h.jsx(uP,{className:"h-4 w-4"}),"Performance Metrics"})}),h.jsxs(er,{className:"space-y-6",children:h.jsxs("div",{className:"space-y-4",children:h.jsx("h3",{className:"text-xs font-semibold uppercase tracking-wide stat-label border-b border-border/50 pb-2",children:"Throughput Performance"}),h.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-6",children:h.jsxs("div",{className:"bg-background/20 rounded-lg p-4 space-y-3",children:h.jsxs("div",{className:"flex items-baseline justify-between",children:h.jsx("span",{className:"font-medium stat-label",children:"Data Rate"}),h.jsx("span",{className:"text-xs stat-label opacity-75",children:"Average"})}),h.jsx("div",{className:"stat text-xl font-bold leading-none text-info tabular-nums",children:t.throughput.dataBitrate.average}),h.jsxs("div",{className:"flex items-center justify-between pt-1 border-t border-border/30",children:h.jsx("span",{className:"text-xs stat-label opacity-60",children:"Peak"}),h.jsx("span",{className:"text-sm font-semibold stat text-info tabular-nums",children:t.throughput.dataBitrate.max})})}),h.jsxs("div",{className:"bg-background/20 rounded-lg p-4 space-y-3",children:h.jsxs("div",{className:"flex items-baseline justify-between",children:h.jsx("span",{className:"font-medium stat-label",children:"Packet Rate"}),h.jsx("span",{className:"text-xs stat-label opacity-75",children:"Average"})}),h.jsx("div",{className:"stat text-xl font-bold leading-none text-info tabular-nums",children:t.throughput.packetRate.average}),h.jsxs("div",{className:"flex items-center justify-between pt-1 border-t border-border/30",children:h.jsx("span",{className:"text-xs stat-label opacity-60",children:"Peak"}),h.jsx("span",{className:"text-sm font-semibold stat text-info tabular-nums",children:t.throughput.packetRate.max})})})})}),h.jsxs("div",{className:"space-y-4",children:h.jsx("h3",{className:"text-xs font-semibold uppercase tracking-wide stat-label border-b border-border/50 pb-2",children:"Processing Statistics"}),h.jsx("div",{className:"bg-background/10 rounded-lg p-4",children:h.jsxs("div",{className:"space-y-3",children:h.jsxs("div",{className:"flex items-center justify-between py-2",children:h.jsx("span",{className:"text-sm stat-label",children:"Total Processed"}),h.jsx("span",{className:"text-lg font-bold stat text-info tabular-nums",children:t.processing.totalProcessed})}),h.jsxs("div",{className:"flex items-center justify-between py-2 border-t border-border/20",children:h.jsx("span",{className:"text-sm stat-label",children:"Total Processing Time"}),h.jsx("span",{className:"text-lg font-bold stat text-info tabular-nums",children:t.processing.totalTime})}),t.processing.averagePerItem&&h.jsxs("div",{className:"flex items-center justify-between py-2 border-t border-border/20",children:h.jsx("span",{className:"text-sm stat-label",children:"Average per Item"}),h.jsx("span",{className:"text-lg font-bold stat text-info tabular-nums",children:t.processing.averagePerItem})})})})})})})});JY.displayName="PerformanceMetrics";const eK=y.createContext(null),tK=({children:e,defaultExpanded:t=})=>{constn,r=y.useState(new Set(t)),o=i=>{r(s=>{const a=new Set(s);return a.has(i)?a.delete(i):a.add(i),a})};return h.jsx(eK.Provider,{value:{expandedItems:n,toggleExpanded:o},children:h.jsx("div",{className:"space-y-2",children:e})})},dT=({title:e,children:t,value:n})=>{const r=y.useContext(eK);if(!r)throw new Error("AccordionItem must be used within Accordion");const{expandedItems:o,toggleExpanded:i}=r,s=o.has(n);return h.jsxs("div",{className:"rounded-lg",children:h.jsxs("button",{onClick:()=>i(n),className:"w-full flex items-center justify-between px-3 py-1.5 text-10px font-medium text-muted-foreground hover:text-ui",children:e,h.jsx(p0e,{className:`w-3 h-3 -transform duration-200 ease-out ${s?"transform rotate-180":""}`})}),h.jsx("div",{className:`overflow-hidden ${s?"max-h-96 opacity-100 pt-1":"max-h-0 opacity-0"}`,children:h.jsx("div",{className:"px-3 pb-2",children:t})})})},Rje=e=>!e||typeof e!="object"||!("num"in e)||!("den"in e)||e.den===0?"N/A":`${(e.num/e.den).toFixed(2)} fps`,Tje=e=>`${(e/1e3).toFixed(0)} kb/s`,kje=e=>`${(e/1e3).toFixed(1)} kHz`,Pje=e=>e.toLocaleString(),Ije=(e,t)=>{var r;if(t==null)return"N/A";const n={fps:o=>Rje(o),bitrate:o=>typeof o=="number"?Tje(o):String(o),duration:o=>typeof o=="number"?Hf(o):String(o),samplerate:o=>typeof o=="number"?kje(o):String(o),timescale:o=>typeof o=="number"?Pje(o):String(o)};return((r=ne)==null?void 0:r.call(n,t))??String(t)},Aje=e=>{if(!e)return"outline";const t=e.toLowerCase();return t.includes("h264")||t.includes("avc")?"default":t.includes("h265")||t.includes("hevc")?"secondary":t.includes("av1")?"default":"outline"},Mje=e=>e.width&&e.height?`${e.width}x${e.height}`:null,jje=e=>e.width&&e.height?Fy:e.samplerate||e.channels?fP:pP,Lje=e=>{const t={codec:e.codec,resolution:Mje(e),pixelformat:e.pixelformat},n={codec:e.codec,samplerate:e.samplerate,channels:e.channels},r={type:e.type,timescale:e.timescale};return{videoParams:t,audioParams:n,technicalParams:r}},oN=e=>Object.values(e).some(t=>t!=null),nK=y.memo(({pidData:e})=>{const t=jje(e),{videoParams:n,audioParams:r,technicalParams:o}=Lje(e),i=oN(n),s=oN(r),a=oN(o),l=(c,d,f,p=!1)=>f==null?null:h.jsxs("div",{className:"flex justify-between items-center py-1",children:h.jsx("span",{className:"text-xs text-muted-foreground capitalize stat-label",children:c.replace("_"," ")}),h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("span",{className:"text-sm font-medium",children:Ije(d,f)}),p&&d==="codec"&&h.jsx(ht,{variant:Aje(f),children:String(f).toUpperCase()})})},d);return h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-2",children:h.jsxs(Rr,{className:"flex items-center gap-2 text-sm",children:h.jsx(t,{className:"h-4 w-4"}),"Multimedia Parameters"})}),h.jsxs(er,{className:"p-3",children:h.jsxs(tK,{defaultExpanded:,children:i&&(e.width||e.height||e.pixelformat)&&h.jsx(dT,{value:"video",title:"Video Parameters",children:h.jsxs("div",{className:"space-y-1",children:h.jsxs("div",{className:"flex items-center gap-2 mb-2",children:h.jsx(Fy,{className:"h-3 w-3"}),h.jsx("span",{className:"text-xs font-medium",children:"Video"}),n.resolution&&h.jsx(ht,{variant:"outline",className:"text-xs",children:n.resolution})}),l("Codec","codec",n.codec,!1),l("Resolution","resolution",n.resolution),l("Pixel Format","pixelformat",n.pixelformat)})}),s&&(e.samplerate||e.channels)&&h.jsx(dT,{value:"audio",title:"Audio Parameters",children:h.jsxs("div",{className:"space-y-1",children:h.jsxs("div",{className:"flex items-center gap-2 mb-2",children:h.jsx(fP,{className:"h-3 w-3"}),h.jsx("span",{className:"text-xs font-medium",children:"Audio"}),r.channels&&h.jsxs(ht,{variant:"outline",className:"text-xs",children:r.channels,"ch"})}),l("Codec","codec",r.codec,!0),l("Sample Rate","samplerate",r.samplerate),l("Channels","channels",r.channels),h.jsxs("div",{className:"flex items-center gap-2 mb-2",children:h.jsx(pP,{className:"h-3 w-3"}),h.jsx("span",{className:"text-xs font-medium",children:"Technical"}),o.type&&h.jsx(ht,{variant:"outline",className:"text-xs",children:o.type})}),l("Type","type",o.type),l("Timescale","timescale",o.timescale)})})}),!i&&!s&&!a&&h.jsx("div",{className:"py-4 text-center text-muted-foreground text-xs",children:"No multimedia parameters available"})})})});nK.displayName="MultimediaParams";const Oje=y.memo(({pidData:e,showAdvanced:t=!0})=>{const n=pje(e),r=vje(e);return h.jsxs("div",{className:"space-y-2 grid grid-cols-1 sm:grid-cols-2 gap-4",children:h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-3",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsxs("div",{className:"flex items-center gap-3",children:h.jsx("div",{className:"flex items-center gap-2",children:h.jsx(Rr,{className:"text-sm",children:e.name})}),h.jsx(ht,{variant:"outline",className:"text-xs",children:r})}),h.jsx(ht,{variant:n.variant,children:n.status})})}),h.jsx(er,{className:"pt-0",children:h.jsxs("div",{className:"grid grid-cols-3 gap-4 text-center",children:h.jsxs("div",{children:h.jsx("div",{className:"text-sm font-medium text-info tabular-nums",children:jo(e.buffer)}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Buffer Used"})}),h.jsxs("div",{children:h.jsx("div",{className:"text-sm font-medium text-info tabular-nums",children:e.buffer_total&&e.buffer_total>0?`${(e.buffer/e.buffer_total*100).toFixed(1)}%`:"0%"}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Usage"})}),h.jsxs("div",{children:h.jsx("div",{className:"text-sm font-medium text-info tabular-nums",children:e.nb_pck_queued||0}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Queued"})})})})}),h.jsx(QY,{pidData:e}),t&&h.jsxs(h.Fragment,{children:(e.stats.average_process_rate||e.stats.average_bitrate||e.stats.nb_processed)&&h.jsx(JY,{pidData:e}),(e.codec||e.width||e.samplerate||e.type)&&h.jsx(nK,{pidData:e})})})});Oje.displayName="PIDStatsOverview";const Dje=e=>e<20||e>80?"text-red-500":e<40||e>60?"text-orange-500":"text-green-500",Fje=e=>e<20||e>80?"#ef4444":e<40||e>60?"#f59e0b":"#10b981",$je=y.memo(({pidData:e,onClick:t})=>{const n=e.max_buffer&&e.max_buffer>0?e.buffer/e.max_buffer*100:0,r=gje(e),o=mje(e),i=o.icon;return h.jsx(Jn,{className:`bg-monitor-panel border-transparent ${t?"cursor-pointer hover:bg-monitor-panel/80 hover:border-border/40":""}`,onClick:t,children:h.jsxs(er,{className:"p-4",children:h.jsxs("div",{className:"flex items-center justify-between mb-3",children:h.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:h.jsx(E0e,{className:"h-3 w-3 text-muted-foreground flex-shrink-0"}),h.jsx("span",{className:"text-sm font-medium truncate",children:e.name})}),h.jsx("div",{className:"flex items-center gap-1",children:h.jsxs(ht,{variant:o.variant,className:"text-xs px-1 py-0",children:h.jsx(i,{className:"h-2 w-2 mr-1"}),o.label})})}),r.length>0&&h.jsxs("div",{className:"flex items-center gap-2 mb-3",children:r.slice(0,2).map((s,a)=>{const l=s.icon;return h.jsxs("div",{className:"flex items-center gap-1",children:h.jsx(l,{className:`h-3 w-3 ${s.color}`}),h.jsx("span",{className:`text-xs ${s.color}`,children:s.label})},a)}),r.length>2&&h.jsxs(ht,{variant:"destructive",className:"text-xs px-1 py-0",children:"+",r.length-2})}),h.jsxs("div",{className:"space-y-2",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx("span",{className:"text-xs text-muted-foreground stat-label",children:"Buffer"}),h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("span",{className:"text-xs text-info tabular-nums",children:jo(e.buffer)}),h.jsxs("span",{className:`text-xs font-medium tabular-nums ${Dje(n)}`,children:n.toFixed(0),"%"})})}),h.jsx(nw,{value:n,className:"h-1",style:{"--progress-background":Fje(n)}})}),h.jsxs("div",{className:"grid grid-cols-2 gap-4 mt-3 text-center",children:h.jsxs("div",{children:h.jsx("div",{className:"text-xs font-medium text-info tabular-nums",children:e.nb_pck_queued||0}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Queued"})}),h.jsxs("div",{children:h.jsx("div",{className:"text-xs font-medium",children:e.codec||e.type||"N/A"}),h.jsx("div",{className:"text-xs text-muted-foreground stat-label",children:"Type"})})})})})});$je.displayName="CompactPIDStats";const Zr={TAB_CONTAINER:"flex flex-col h-full gap-2 p-2",STATUS_BAR:"flex items-center gap-2 px-3 py-2 bg-monitor-panel/40 rounded border-b border-monitor-line/10 text-xs shrink-0",STATUS_SEPARATOR:"text-muted-foreground/50",STATUS_LABEL:"text-muted-foreground",GRID_2_COL:"grid grid-cols-2 gap-2",GRID_AUTO_FIT:"grid grid-cols-repeat(auto-fit,minmax(min(200px,100%),1fr)) gap-2 shrink-0",SPACE_Y_2:"space-y-2",STATUS_BAR_CONTAINER:"bg-background/30 rounded-lg px-3 py-2",STATUS_BAR_CONTENT:"flex items-center justify-between",STATUS_BAR_LEFT:"flex items-center gap-3",STATUS_BAR_RIGHT:"flex items-center gap-1.5"},rK=y.memo(({filter:e,alerts:t})=>{const{status:n,type:r,idx:o,time:i}=e,s=Xe(z9(o.toString())),a=YY(n,s,t),l=Hf(i);return h.jsxs("div",{className:"flex flex-col h-full gap-2 p-2",children:h.jsxs("div",{className:"flex items-center gap-2 px-3 py-2 bg-monitor-panel/40 rounded border-b border-monitor-line/10 text-xs shrink-0",children:h.jsxs("span",{className:"font-medium text-info",children:"",r||"unknown",""}),h.jsxs(ht,{variant:a.variant,className:"text-xs py-0 px-1.5 h-fit",children:"● ",a.label}),h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsxs("span",{className:"text-muted-foreground",children:"Index: ",o}),h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsxs("span",{className:"text-muted-foreground",children:"Uptime:"," ",h.jsx("span",{className:"font-medium tabular-nums",children:l})}),h.jsxs("span",{className:"ml-auto text-muted-foreground/70 text-xs",children:"Live ",h.jsx("span",{className:"text-error ",children:"⏺"})})}),h.jsxs("div",{className:Zr.GRID_AUTO_FIT,children:h.jsxs("div",{className:"flex flex-col gap-2",children:h.jsxs("div",{className:"bg-monitor-panel/60 border-r border-monitor-line/10 rounded p-2",children:h.jsx("div",{className:"text-xs font-medium text-gray-400 mb-1",children:"Health"}),h.jsx("div",{className:`${a.color} text-xs font-medium py-1`,children:n||"Unknown"}),(e.errors&&e.errors>0||e.current_errors&&e.current_errors>0)&&h.jsxs("div",{className:"text-xs text-rose-400 mt-1",title:"Total errors",children:(e.errors||0)+(e.current_errors||0)," errors"})}),h.jsxs("div",{className:"bg-monitor-panel/60 border-0 border-r border-monitor-line/10 rounded p-2",children:h.jsx("div",{className:"text-xs font-medium text-info mb-1",children:"PIDs"}),h.jsxs("div",{className:"text-xs space-y-0.5",children:h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:"text-muted-foreground",children:"Ipid:"}),h.jsx("span",{className:"font-medium",children:e.nb_ipid})}),h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{className:"text-muted-foreground",children:"Opid:"}),h.jsx("span",{className:"font-medium",children:e.nb_opid})})})})}),h.jsx("div",{className:"flex flex-col gap-2",children:h.jsx(KY,{filter:e})}),h.jsxs("div",{className:"flex flex-col gap-2",children:h.jsx(XY,{pck_done:e.pck_done,pck_sent:e.pck_sent,pck_ifce_sent:e.pck_ifce_sent}),h.jsx(ZY,{bytes_done:e.bytes_done,bytes_sent:e.bytes_sent})})}),h.jsx("div",{className:"flex-1 overflow-hidden"})})});rK.displayName="OverviewTab";const zje={dataByFilter:{},maxPoints:300},oK=Sc({name:"monitoredFilter",initialState:zje,reducers:{addNetworkDataPoint:(e,t)=>{const{filterId:n,type:r,point:o}=t.payload;e.dataByFiltern||(e.dataByFiltern={}),e.dataByFiltern.network||(e.dataByFiltern.network={upload:,download:});const s=e.dataByFiltern.networkr;s.push(o),s.length>e.maxPoints&&s.shift()},clearFilterData:(e,t)=>{delete e.dataByFiltert.payload},resetAllData:e=>{e.dataByFilter={}},setMaxPoints:(e,t)=>{e.maxPoints=t.payload}}}),{addNetworkDataPoint:Hje,clearFilterData:s5e,resetAllData:a5e,setMaxPoints:l5e}=oK.actions,Bje=oK.reducer,Rz=({filterId:e,currentBytes:t,refreshInterval:n,type:r})=>{const o=iW(),i=Yu(),s=zx(v=>r==="upload"?ixe(v,e):sxe(v,e)),a=y.useRef(t),l=y.useRef(Date.now()),c=y.useRef(t),d=y.useRef(!1),f=y.useCallback(v=>`${jo(v)}/s`,);y.useEffect(()=>{c.current=t},t);const p=y.useCallback((v,b)=>{const _={time:_Ee(),timestamp:b,value:v};o(Hje({filterId:e,type:r,point:_})),a.current=c.current,l.current=b},o,e,r);y.useEffect(()=>{if(d.current)return;const v=Date.now();if(d.current=!0,s.length>0){const b=ss.length-1;l.current=b.timestamp,a.current=t;return}a.current=t,l.current=v},t,s,o,e,r),y.useEffect(()=>{if(!d.current||!i.isConnected())return;let v=null,b=null;const _=()=>{if(!i.isConnected()){v&&clearInterval(v);return}const x=Date.now(),w=l.current;if(x<=w)return;const C=(x-w)/1e3,R=c.current-a.current,P=Math.max(0,R/C);p(P,x)};return s.length===0?(b=setTimeout(_,100),v=setInterval(_,n),()=>{b&&clearTimeout(b),v&&clearInterval(v)}):(v=setInterval(_,n),()=>{v&&clearInterval(v)})},n,p,i,s.length);const g=y.useCallback(v=>typeof v=="number"?f(v),"Bandwidth":v,"Bandwidth",f);return{dataPoints:s,formatBandwidth:f,tooltipFormatter:g}},Wje=({timeLabelsRef:e,width:t=400,height:n=180})=>({width:t,height:n,padding:10,10,5,5,cursor:{show:!0,drag:{x:!1,y:!1},points:{size:8,width:2}},legend:{show:!1},hooks:{setCursor:r=>{const{left:o=0,top:i=0,idx:s}=r.cursor;let a=r.root.querySelector(".u-tooltip");if(a||(a=document.createElement("div"),a.className="u-tooltip",a.style.cssText=` + position: absolute; + background: rgb(2 6 23); + color: rgb(226 232 240); + border: 1px solid hsl(var(--border)); + border-radius: 6px; + padding: 8px 10px; + font-size: 11px; + font-family: monospace; + pointer-events: none; + z-index: 100; + white-space: nowrap; + `,r.root.appendChild(a)),!a||s==null)return;if(r._lastTooltipIdx===s){a.style.left=`${o+15}px`,a.style.top=`${i+15}px`;return}r._lastTooltipIdx=s;const c=e.currents||"--",d=r.data1s?Xs(r.data1s*8):"--",f=r.data2s?Xs(r.data2s*8):"--";a.innerHTML=` + <div style="margin-bottom: 4px; color: #6ee7b7;">Time: ${c}</div> + <div style="color: #10b981;">Upload: ${d}</div> + <div style="color: #3b82f6;">Download: ${f}</div> + `,a.style.display="block",a.style.left=`${o+15}px`,a.style.top=`${i+15}px`}},series:{label:"Time"},{label:"Upload",stroke:"#10b981",width:.7,value:(r,o)=>o==null?"--":Xs(o*8)},{label:"Download",stroke:"#3b82f6",width:.7,value:(r,o)=>o==null?"--":Xs(o*8)},scales:{x:{time:!1,distr:2},y:{}},axes:{stroke:"#6ee7b7",grid:{show:!0,stroke:"rgba(110, 231, 183, 0.1)",width:1},ticks:{stroke:"#6ee7b7",size:5,width:1},font:"10px monospace",size:50,values:(r,o)=>o.map(i=>{const s=i;return e.currents||""})},{stroke:"#6ee7b7",grid:{show:!0,stroke:"rgba(110, 231, 183, 0.1)",width:1},ticks:{stroke:"#6ee7b7",size:5,width:2},font:"10px monospace",size:80,values:(r,o)=>o.map(i=>Xs(i*8))}}),Uje=500,iK=y.memo(({filterId:e,bytesSent:t,bytesReceived:n,refreshInterval:r=Uje})=>{const o=y.useRef(null),i,s=y.useState({width:400,height:230}),a=y.useRef(),{dataPoints:l}=Rz({filterId:e,currentBytes:t,refreshInterval:r,type:"upload"}),{dataPoints:c}=Rz({filterId:e,currentBytes:n,refreshInterval:r,type:"download"});y.useEffect(()=>{if(!o.current)return;const p=()=>{if(o.current){const{width:v}=o.current.getBoundingClientRect();s({width:v||400,height:230})}};p();const g=new ResizeObserver(p);return g.observe(o.current),()=>{g.disconnect()}},);const d=y.useMemo(()=>Wje({timeLabelsRef:a,width:i.width,height:i.height}),i),f=y.useMemo(()=>{const p=Math.max(l.length,c.length),g=Array.from({length:p},(x,w)=>w),v=g.map(x=>{var w;return((w=lx)==null?void 0:w.value)||0}),b=g.map(x=>{var w;return((w=cx)==null?void 0:w.value)||0});return a.current=g.map(x=>{var w,C;return((w=lx)==null?void 0:w.time)||((C=cx)==null?void 0:C.time)||""}),g,v,b},l,c);return h.jsxs(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(Dr,{className:"pb-1",children:h.jsxs(Rr,{className:"flex justify-center items-center gap-2 text-xs font-medium text-muted-foreground uppercase tracking-wide",children:h.jsx(f0e,{className:"h-4 w-4 opacity-60"}),h.jsxs("span",{className:"flex items-center gap-1.5",children:h.jsx("span",{className:"w-3 h-0.5 rounded-full bg-blue-500"}),"Download"}),"/",h.jsxs("span",{className:"flex items-center gap-1.5",children:h.jsx("span",{className:"w-3 h-0.5 rounded-full bg-emerald-500"}),"Upload"}),h.jsx("span",{className:"opacity-60 normal-case",children:"Mb/s"})})}),h.jsx(er,{className:"pt-0",children:h.jsx("div",{ref:o,style:{width:"100%",height:230,minHeight:230},children:h.jsx(fI,{data:f,options:d,className:"w-full h-full"})})})})});iK.displayName="BandwidthCombinedChart";const qje=(e,t)=>{const n=y.useRef({sent:e.bytesSent,received:e.bytesReceived,packetsSent:e.packetsSent,packetsReceived:e.packetsReceived,timestamp:Date.now()}),r,o=y.useState({currentStats:e,instantRates:{bytesSentRate:0,bytesReceivedRate:0,packetsSentRate:0,packetsReceivedRate:0}});y.useEffect(()=>{if(!(e.bytesSent!==n.current.sent||e.bytesReceived!==n.current.received||e.packetsSent!==n.current.packetsSent||e.packetsReceived!==n.current.packetsReceived)){o(f=>({...f,currentStats:e}));return}const l=Date.now(),c=(l-n.current.timestamp)/1e3;let d=r.instantRates;if(c>0){const f=e.bytesSent-n.current.sent,p=e.bytesReceived-n.current.received,g=e.packetsSent-n.current.packetsSent,v=e.packetsReceived-n.current.packetsReceived;d={bytesSentRate:Math.max(0,f/c),bytesReceivedRate:Math.max(0,p/c),packetsSentRate:Math.max(0,g/c),packetsReceivedRate:Math.max(0,v/c)},n.current={sent:e.bytesSent,received:e.bytesReceived,packetsSent:e.packetsSent,packetsReceived:e.packetsReceived,timestamp:l}}o({currentStats:e,instantRates:d})},e,t,r.instantRates);const i=y.useMemo(()=>({bytesSent:jo(r.currentStats.bytesSent),bytesReceived:jo(r.currentStats.bytesReceived),packetsSent:r.currentStats.packetsSent.toLocaleString(),packetsReceived:r.currentStats.packetsReceived.toLocaleString(),bytesSentRate:Xs(r.instantRates.bytesSentRate*8),bytesReceivedRate:Xs(r.instantRates.bytesReceivedRate*8),packetsSentRate:zm(r.instantRates.packetsSentRate),packetsReceivedRate:zm(r.instantRates.packetsReceivedRate)}),r),s=a=>a>1e7?{level:"High",variant:"default",color:"text-green-600"}:a>1e6?{level:"Medium",variant:"secondary",color:"text-blue-600"}:a>0?{level:"Low",variant:"outline",color:"text-orange-600"}:{level:"Idle",variant:"destructive",color:"text-gray-500"};return{currentStats:r.currentStats,instantRates:r.instantRates,formattedStats:i,getActivityLevel:s}},Gje=1e3,sK=y.memo(({filterId:e,data:t,filterName:n,refreshInterval:r=Gje})=>{const{currentStats:o,instantRates:i,formattedStats:s,getActivityLevel:a}=qje(t,n),l=y.useMemo(()=>a(i.bytesSentRate),i.bytesSentRate,a),c=y.useMemo(()=>a(i.bytesReceivedRate),i.bytesReceivedRate,a);return h.jsxs("div",{className:Zr.TAB_CONTAINER,children:h.jsxs("div",{className:Zr.STATUS_BAR,children:h.jsx("span",{className:"font-medium text-info",children:"Network Activity"}),h.jsx("span",{className:Zr.STATUS_SEPARATOR,children:"·"}),h.jsxs("span",{className:Zr.STATUS_LABEL,children:"Filter: ",n}),h.jsxs("span",{className:"ml-auto text-muted-foreground/70 text-xs",children:"Live ",h.jsx("span",{className:"text-error ",children:"⏺"})})}),h.jsxs("div",{className:Zr.GRID_2_COL,children:h.jsx(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(er,{className:"p-2",children:h.jsxs("div",{className:"space-y-2",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("span",{className:"w-0.5 h-5 rounded-full bg-blue-500"}),h.jsx(x0e,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("span",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wide",children:"Download"})}),h.jsx(ht,{variant:c.variant,className:"text-11px h-5 px-2",children:c.level})}),h.jsx("div",{className:"text-xl font-bold text-monitor-download tabular-nums leading-none",children:s.bytesReceivedRate}),h.jsxs("div",{className:"text-11px text-muted-foreground",children:h.jsx("span",{className:"font-medium",children:s.bytesReceived}),h.jsx("span",{className:"mx-1.5",children:"·"}),h.jsxs("span",{className:"font-medium",children:s.packetsReceived," packets"})})})})}),h.jsx(Jn,{className:"bg-monitor-panel border-transparent",children:h.jsx(er,{className:"p-2",children:h.jsxs("div",{className:"space-y-2",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("span",{className:"w-0.5 h-5 rounded-full bg-emerald-500"}),h.jsx(O0e,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("span",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wide",children:"Upload"})}),h.jsx(ht,{variant:l.variant,className:"text-11px h-5 px-2",children:l.level})}),h.jsx("div",{className:"text-xl font-bold text-emerald-500 tabular-nums leading-none",children:s.bytesSentRate}),h.jsxs("div",{className:"text-11px text-muted-foreground",children:h.jsx("span",{className:"font-medium",children:s.bytesSent}),h.jsx("span",{className:"mx-1.5",children:"·"}),h.jsxs("span",{className:"font-medium",children:s.packetsSent," packets"})})})})})}),h.jsx(iK,{filterId:e,bytesSent:o.bytesSent,bytesReceived:o.bytesReceived,refreshInterval:r})})});sK.displayName="NetworkTab";const aK=e=>{switch(e.toLowerCase()){case"visual":return{icon:Fy,label:"Video",color:"text-debug"};case"audio":return{icon:fP,label:"Audio",color:"text-info"};case"text":return{icon:N9,label:"Text",color:"text-warning"};case"file":return{icon:b0e,label:"File",color:"text-purple-500"};default:return{icon:Fy,label:e||"File",color:"text-gray-500"}}};function Tz(e,t){if(e==null)return null;switch(t){case"bool":if(typeof e=="boolean")return e?"true":"false";if(typeof e=="string"){const n=e.toLowerCase();return n==="true"||n==="1"?"true":"false"}return e?"true":"false";case"uint":case"sint":case"luint":case"lsint":{const n=parseInt(String(e),10);return isNaN(n)?(console.warn(`Invalid integer value: ${e}, using 0`),"0"):String(n)}case"flt":case"dbl":{const n=parseFloat(String(e));return isNaN(n)?(console.warn(`Invalid float value: ${e}, using 0.0`),"0.0"):String(n)}case"frac":case"lfrac":return typeof e=="string"&&e.includes("/")?e.trim():typeof e=="object"&&"num"in e&&"den"in e?`${e.num}/${e.den}`:`${e}/1`;case"strl":case"uintl":case"sintl":case"4ccl":return Array.isArray(e)?e.map(n=>String(n).trim()).join(","):String(e).trim();default:return String(e).trim()}}const Vje=e=>{var t;return(t=e.stats)!=null&&t.disconnected?{text:"Disconnected",variant:"destructive"}:e.would_block?{text:"Blocked",variant:"destructive"}:e.eos?{text:"EOS",variant:"secondary"}:e.playing?{text:"Playing",variant:"default"}:e.eos?null:{text:"Idle",variant:"outline"}},lK=(e,t)=>{const n=e.filter(s=>{var a;return(a=s.stats)==null?void 0:a.disconnected}).length,r=e.filter(s=>s.would_block).length,o=e.filter(s=>s.playing).length,i=e.filter(s=>s.eos).length;return{totalItems:t,totalPids:e.length,errors:n,blocked:r,active:o,eos:i}},Yje=e=>{const t=y.useMemo(()=>e.ipids?Object.entries(e.ipids).map((i,s,a)=>({...s,ipidIdx:a})):,e.ipids),n=y.useMemo(()=>t.reduce((i,s)=>{const a=s.name;ia||(ia={});const l=s.type||"Unknown";return ial||(ial=),ial.push(s),i},{}),t),r=y.useMemo(()=>Object.keys(n),n),o=y.useMemo(()=>lK(t,r.length),t,r.length);return{inputPidsWithIndices:t,groupedInputs:n,inputNames:r,globalStatus:o}};function cK(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"},child:}})(e)}function uK(e){return Be({attr:{viewBox:"0 0 512 512"},child:{tag:"path",attr:{d:"M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"},child:}})(e)}const dK=y.memo(({pid:e})=>e.language||e.role?h.jsxs("div",{className:"flex items-center gap-1",children:e.language&&h.jsx(ht,{variant:"outline",className:`${Hm} px-1.5 py-0 h-4 font-mono uppercase bg-background/40`,children:e.language}),e.role&&h.jsx(ht,{variant:"secondary",className:`${Hm} px-1.5 py-0 h-4 bg-blue-900/30 text-blue-300 border-blue-700/50`,children:e.role})}):null);dK.displayName="PIDMetadataBadges";const VI=y.memo(({pid:e,type:t,filterIdx:n,onOpenProps:r,showPropsButton:o=!0,variant:i="input"})=>{var v,b,_,x,w;const a=y.useMemo(()=>aK(t),t).icon,l=y.useMemo(()=>Sme(t),t),c=y.useMemo(()=>Cme(t),t),d=t.toLowerCase(),f=d==="visual"||d==="video",p=d==="audio",g=i==="output"&&(e.id||e.trackNumber||e.timescale||((v=e.stats)==null?void 0:v.max_process_time));return h.jsxs("div",{className:`bg-monitor-panel rounded-md border-l-2 ${l} hover:bg-background/60 `,children:h.jsxs("div",{className:"flex items-center justify-between px-3 py-2 border-b border-white/5",children:h.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:h.jsx(a,{className:`h-3.5 w-3.5 flex-shrink-0 ${c}`}),h.jsx("span",{className:`${Xd} text-muted-foreground`}),h.jsx("span",{className:`${wje} font-medium truncate`,children:e.name}),e.codec&&h.jsx("span",{className:`${Xd} text-muted-foreground uppercase`,children:e.codec}),i==="output"&&h.jsxs(h.Fragment,{children:f&&e.pixelformat&&h.jsx("span",{className:`${Xd} text-muted-foreground font-mono uppercase`,children:e.pixelformat}),p&&e.samplerate&&h.jsxs("span",{className:`${Xd} text-muted-foreground font-mono`,children:(e.samplerate/1e3).toFixed(0),"kHz"})}),h.jsx(dK,{pid:e})}),h.jsxs("div",{className:"flex items-center gap-1.5",children:o&&i==="input"&&h.jsx(uK,{className:"h-3.5 w-3.5 cursor-pointer text-muted-foreground hover:text-primary ",onClick:()=>r(n,e.ipidIdx),title:"View input properties"}),h.jsx("span",{className:"font-cond",children:"Props"})})}),h.jsx("div",{className:`grid ${i==="output"?"grid-cols-4":"grid-cols-3"} gap-2 px-3 py-2 text-center`,children:i==="input"?h.jsxs(h.Fragment,{children:h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-info tabular-nums`,children:jo(e.buffer)}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Buffer"})}),h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-muted-foreground tabular-nums`,children:e.bitrate||0}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Bitrate"})}),h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-info tabular-nums`,children:e.nb_pck_queued||0}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Queued"})})}):h.jsxs(h.Fragment,{children:h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-info tabular-nums`,children:((_=(b=e.stats)==null?void 0:b.nb_processed)==null?void 0:_.toLocaleString())||0}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Packets"})}),h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-muted-foreground tabular-nums`,children:Xs((x=e.stats)==null?void 0:x.average_bitrate)}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Bitrate"})}),h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-info tabular-nums`,children:e.nb_pck_queued??0}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Queued"})}),h.jsxs("div",{children:h.jsx("div",{className:`${Vo} font-medium text-warning tabular-nums`,children:(w=e.stats)!=null&&w.max_process_time?`${e.stats.max_process_time}µs`:"-"}),h.jsx("div",{className:`${Da} text-muted-foreground`,children:"Peak"})})})}),g&&h.jsx("div",{className:"border-t border-white/5",children:h.jsx(tK,{children:h.jsx(dT,{value:"details",title:"Technical Details",children:h.jsxs("div",{className:`grid grid-cols-2 gap-x-4 gap-y-1 ${Hm} font-mono text-muted-foreground`,children:e.id&&h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{children:"ID:"}),h.jsx("span",{className:"text-info",children:e.id})}),e.trackNumber&&h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{children:"Track:"}),h.jsx("span",{className:"text-info",children:e.trackNumber})}),e.timescale&&h.jsxs("div",{className:"flex justify-between",children:h.jsx("span",{children:"Timescale:"}),h.jsx("span",{className:"text-info",children:e.timescale})})})})})})})});VI.displayName="PIDMetricsCard";const fK=y.memo(({pid:e,filterIdx:t,onOpenProps:n,isEven:r,variant:o="input"})=>{const i=Vje(e),s=(e.type||"Unknown").toLowerCase(),a=s==="visual"||s==="video",l=s==="audio",c=aK(s),d=a&&e.width&&e.height?`${e.width}×${e.height}`:l&&e.channels?`${e.channels}ch`:"—",f=r?"bg-black/10":"bg-black/20",p=o==="output",g=p&&e.type||e.name,v=p?a&&e.pixelformat?e.pixelformat.toUpperCase():null,l&&e.samplerate?`${(e.samplerate/1e3).toFixed(0)}kHz`:null.filter(Boolean).join(" "):null;return h.jsxs("tr",{className:`${f} ${p?"":"hover:bg-black/30 cursor-pointer"}`,onClick:p?void 0:()=>n(t,e.ipidIdx),children:h.jsx("td",{className:"px-2 py-1.5",children:p?h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx(ht,{variant:"secondary",className:`${Hm} px-1.5 py-0 h-5 font-medium ${c.color}`,children:c.label}),v&&h.jsx("span",{className:`${Xd} text-muted-foreground font-mono`,children:v})}):h.jsx("span",{className:`${Vo} font-medium truncate max-w-140px`,children:g})}),h.jsx("td",{className:`${Xd} px-2 py-1.5 text-muted-foreground uppercase`,children:e.codec||"—"}),h.jsx("td",{className:`${Vo} px-2 py-1.5 text-right text-muted-foreground tabular-nums whitespace-nowrap`,children:jo(e.buffer)}),h.jsx("td",{className:`${Vo} px-2 py-1.5 text-right text-info tabular-nums font-medium whitespace-nowrap`,children:e.bitrate||0}),h.jsx("td",{className:`${Vo} px-2 py-1.5 text-right text-muted-foreground tabular-nums whitespace-nowrap`,children:d}),h.jsx("td",{className:"px-2 py-1.5",children:i&&h.jsx(ht,{variant:"secondary",className:`${Hm} px-1 py-0 h-4 font-normal`,children:i.text})}),!p&&h.jsx("td",{className:"px-2 py-1.5 text-center",children:h.jsx(E9,{className:"h-3.5 w-3.5 text-muted-foreground/50 hover:text-primary inline-block",title:"View properties"})})})});fK.displayName="PIDTableRow";const YI=y.memo(({pids:e,filterIdx:t,onOpenProps:n,variant:r="input"})=>h.jsx("div",{className:"overflow-x-auto bg-monitor-app",children:h.jsxs("table",{className:"w-full text-left",children:h.jsx("thead",{children:h.jsxs("tr",{className:"border-b border-white/10 bg-monitor-panel",children:h.jsx("th",{className:"px-2 py-1.5 min-w-120px text-10px font-medium text-muted-foreground uppercase tracking-wide",children:r==="output"?"Stream Type":"Name"}),h.jsx("th",{className:"px-2 py-1.5 min-w-60px text-10px font-medium text-muted-foreground uppercase tracking-wide",children:"Codec"}),h.jsx("th",{className:"px-2 py-1.5 min-w-70px text-right text-10px font-medium text-muted-foreground uppercase tracking-wide",children:"Buffer"}),h.jsx("th",{className:"px-2 py-1.5 min-w-70px text-right text-10px font-medium text-muted-foreground uppercase tracking-wide",children:"Bitrate"}),h.jsx("th",{className:"px-2 py-1.5 min-w-70px text-right text-10px font-medium text-muted-foreground uppercase tracking-wide",children:"Res"}),h.jsx("th",{className:"px-2 py-1.5 min-w-70px text-10px font-medium text-muted-foreground uppercase tracking-wide",children:"Status"}),r==="input"&&h.jsx("th",{className:"px-2 py-1.5 w-10 text-center text-10px font-medium text-muted-foreground uppercase tracking-wide",children:"PROPERTIES"})})}),h.jsx("tbody",{children:e.map((o,i)=>h.jsx(fK,{pid:o,filterIdx:t,onOpenProps:n,isEven:i%2===0,variant:r},`${o.name}-${o.ipidIdx}`))})})}));YI.displayName="PIDTable";const hK=y.memo(({filterData:e,filterName:t,isLoading:n=!1})=>{const{openPIDProps:r}=Mb(),{inputPidsWithIndices:o,groupedInputs:i,inputNames:s,globalStatus:a}=Yje(e),l=y.useCallback((d,f)=>{r({filterIdx:d,ipidIdx:f})},r),c=s.flatMap(d=>Object.entries(id).flatMap((f,p)=>p.map(g=>({pid:g,type:f}))));return h.jsxs("div",{className:Zr.SPACE_Y_2,children:o.length>0&&h.jsx("div",{className:Zr.STATUS_BAR_CONTAINER,children:h.jsxs("div",{className:Zr.STATUS_BAR_CONTENT,children:h.jsxs("div",{className:Zr.STATUS_BAR_LEFT,children:h.jsx("span",{className:"text-xs font-medium",children:"Status"}),h.jsxs("span",{className:"text-xs text-info tabular-nums",children:a.totalPids," stream",a.totalPids>1?"s":""})}),h.jsxs("div",{className:Zr.STATUS_BAR_RIGHT,children:a.errors>0&&h.jsxs(ht,{variant:"destructive",className:"text-10px px-1.5 py-0 h-5 tabular-nums",children:a.errors," Error"}),a.active>0&&h.jsxs(ht,{variant:"default",className:"text-10px px-1.5 py-0 h-5 tabular-nums",children:a.active," Active"}),a.eos>0&&h.jsxs(ht,{variant:"secondary",className:"text-10px px-1.5 py-0 h-5 tabular-nums",children:a.eos," EOS"})})})}),c.length>0?c.length>1?h.jsx(YI,{pids:o,filterIdx:e.idx,onOpenProps:l}):h.jsx("div",{className:"grid grid-cols-repeat(auto-fit,minmax(280px,1fr)) gap-2",children:c.map(({pid:d,type:f})=>h.jsx(VI,{pid:d,type:f,filterIdx:e.idx,onOpenProps:l},`${d.name}-${d.ipidIdx}`))}):n?h.jsx("div",{className:"py-8 flex flex-col items-center justify-center",children:h.jsx("div",{className:"animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"})}):h.jsxs("div",{className:"py-8 text-center text-muted-foreground text-sm",children:"No input PIDs available for ",t})})});hK.displayName="InputsTab";const pK=y.memo(({filterData:e,filterName:t,isLoading:n=!1})=>{const r=y.useMemo(()=>e.opids?Object.entries(e.opids).map((a,l,c)=>({...l,ipidIdx:c})):,e.opids),o=y.useMemo(()=>r.map(a=>({pid:a,type:a.type||"Unknown"})),r),i=lK(r,r.length),s=()=>{};return h.jsxs("div",{className:Zr.SPACE_Y_2,children:r.length>0&&h.jsx("div",{className:Zr.STATUS_BAR_CONTAINER,children:h.jsxs("div",{className:Zr.STATUS_BAR_CONTENT,children:h.jsxs("div",{className:Zr.STATUS_BAR_LEFT,children:h.jsx("span",{className:"text-xs font-medium",children:"Status"}),h.jsxs("span",{className:"text-xs text-info tabular-nums",children:i.totalPids," stream",i.totalPids>1?"s":""})}),h.jsxs("div",{className:Zr.STATUS_BAR_RIGHT,children:i.errors>0&&h.jsxs(ht,{variant:"destructive",className:"text-10px px-1.5 py-0 h-5 tabular-nums",children:i.errors," Error"}),i.blocked>0&&h.jsxs(ht,{variant:"destructive",className:"text-10px px-1.5 py-0 h-5 tabular-nums bg-amber-900/40 text-amber-300 border-amber-700/60",title:"Output PIDs blocked - backpressure active",children:i.blocked," Blocked"}),i.active>0&&h.jsxs(ht,{variant:"default",className:"text-10px px-1.5 py-0 h-5 tabular-nums",children:i.active," Active"}),i.eos>0&&h.jsxs(ht,{variant:"secondary",className:"text-10px px-1.5 py-0 h-5 tabular-nums",children:i.eos," EOS"})})})}),o.length>0?o.length>1?h.jsx(YI,{pids:r,filterIdx:e.idx,onOpenProps:s,variant:"output"}):h.jsx("div",{className:"grid grid-cols-repeat(auto-fit,minmax(280px,1fr)) gap-2",children:o.map(({pid:a,type:l})=>h.jsx(VI,{pid:a,type:l,filterIdx:e.idx,onOpenProps:s,variant:"output"},`${a.name}-${a.ipidIdx}`))}):n?h.jsx("div",{className:"py-8 flex flex-col items-center justify-center",children:h.jsx("div",{className:"animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"})}):h.jsxs("div",{className:"py-8 text-center text-muted-foreground text-sm",children:"No output PIDs available for ",t})})});pK.displayName="OutputsTab";const Kje={idx:0,status:"",bytes_done:0,bytes_sent:0,pck_done:0,pck_sent:0,time:0,nb_ipid:0,nb_opid:0,ipids:{},opids:{}},Xje=y.memo(rK),Zje=y.memo(sK),Qje=y.memo(hK),Jje=y.memo(pK),mK=y.memo(({overviewData:e,networkData:t,inputPids:n,outputPids:r,filterData:o=Kje,onOpenProperties:i,initialTab:s,isLoading:a=!1})=>{constl,c=y.useState(s||"overview"),d=Xe(v=>e.idx!==void 0?W9(String(e.idx))(v):null);y.useEffect(()=>{s&&c(s)},s);const f=y.useMemo(()=>({inputs:n.length,outputs:r.length}),n.length,r.length),p=hI(),g=e.idx!==void 0?String(e.idx):null;return h.jsx("div",{className:"flex flex-col gap-2",children:h.jsxs(hY,{value:l,onValueChange:c,className:"w-full",children:h.jsxs("div",{className:"sticky backdrop-blur-sm top-0 z-10 bg-background/60 pb-2 space-y-2",children:h.jsxs("div",{className:"flex justify-stretch items-center gap-4",children:h.jsx("h2",{className:"text-lg font-semibold text-monitor-active-filter",children:e.name}),d&&d.errors>0&&h.jsxs(ht,{variant:"outline",onClick:()=>{g&&p({levels:At.ERROR,filterKeys:g})},className:`h-5 px-1.5 text-10px uppercase tracking-wide + bg-red-900/20 text-red-300 cursor-pointer + border border-red-700/60 + rounded-sm font-semibold`,title:`${d.errors} error(s) in logs`,children:d.errors," ERR"}),d&&d.warnings>0&&h.jsxs(ht,{variant:"outline",onClick:()=>{g&&p({levels:At.WARNING,filterKeys:g})},className:`h-5 px-1.5 text-10px uppercase tracking-wide + bg-amber-900/20 text-amber-300 cursor-pointer + border border-amber-700/60 + rounded-sm font-semibold`,title:`${d.warnings} warning(s) in logs`,children:d.warnings," WARN"}),h.jsx(fs,{variant:"outline",size:"sm",onClick:i,className:"h-7 px-2 py-0",title:"Display filter properties",children:h.jsx(pP,{className:"h-5 w-5"})})}),h.jsxs($I,{className:"h-8 justify-start w-full",children:h.jsx(gu,{value:"overview",className:"h-7 px-3 font-medium data-state=active:text-monitor-active-tab data-state=active:border-b-2 data-state=active:border-monitor-active-tab",children:"Overview"}),h.jsx(gu,{value:"network",className:"h-7 px-3 font-medium data-state=active:text-monitor-active-tab data-state=active:border-b-2 data-state=active:border-monitor-active-tab",children:"Network"}),h.jsxs(gu,{value:"inputs",className:"h-7 px-3 font-medium data-state=active:text-monitor-active-tab data-state=active:border-b-2 data-state=active:border-monitor-active-tab",children:"Inputs (",f.inputs,")"}),h.jsxs(gu,{value:"outputs",className:"h-7 px-3 font-medium data-state=active:text-monitor-active-tab data-state=active:border-b-2 data-state=active:border-monitor-active-tab",children:"Outputs (",f.outputs,")"})})}),h.jsx(vu,{value:"overview",children:h.jsx(Xje,{filter:e,alerts:d})}),h.jsx(vu,{value:"network",className:"data-state=inactive:hidden",children:h.jsx(Zje,{filterId:e.idx.toString(),data:t,filterName:e.name,refreshInterval:1e3})}),h.jsx(vu,{value:"inputs",children:h.jsx(Qje,{filterData:o,filterName:e.name,isLoading:a})}),h.jsx(vu,{value:"outputs",children:h.jsx(Jje,{filterData:o,filterName:e.name,isLoading:a})})})})},(e,t)=>{var s,a,l,c;const n=((s=e.filterData)==null?void 0:s.idx)===((a=t.filterData)==null?void 0:a.idx)&&((l=e.filterData)==null?void 0:l.status)===((c=t.filterData)==null?void 0:c.status),r=e.overviewData.name===t.overviewData.name&&e.overviewData.idx===t.overviewData.idx,o=e.networkData===t.networkData||e.networkData.bytesSent===t.networkData.bytesSent&&e.networkData.bytesReceived===t.networkData.bytesReceived,i=e.inputPids.length===t.inputPids.length&&e.outputPids.length===t.outputPids.length;return n&&r&&o&&i});mK.displayName="DetailedStatsView";const eLe=({overviewData:e,networkData:t,inputPids:n,outputPids:r,filterData:o,onBack:i,onOpenProperties:s,initialTab:a,isLoading:l=!1})=>{const c={overviewData:e,networkData:t,inputPids:n,outputPids:r,onBack:i,onOpenProperties:s,initialTab:a,isLoading:l,...o&&{filterData:o}};return h.jsx(mK,{...c})},tLe=({monitoredFilters:e,activeTab:t,onCardClick:n,onOpenProperties:r})=>h.jsx(h.Fragment,{children:Array.from(e.entries()).map((o,i)=>{const s=t===`filter-${o}`;return h.jsx(nLe,{idx:o,filter:i,isActive:s,onCardClick:n,onOpenProperties:r},`filter-${o}`)})}),gK=({filter:e,isActive:t,onCardClick:n,onOpenProperties:r})=>{const o=Hn(),i=y.useRef(null),s=y.useRef(null),a=Zo.getState().graph.initialTab;t&&a!==null&&a!==s.current&&(i.current=a,s.current=a),y.useEffect(()=>{i.current&&(o(jme()),s.current=null)},o);const{stats:l,isLoading:c}=oMe(e.idx,t,1e3),d=c||t&&(!l||l.idx!==e.idx),f=y.useMemo(()=>({...e,...l}),e,l),p=y.useMemo(()=>({overviewData:{idx:f.idx,name:f.name,type:f.type,status:f.status,time:f.time,pck_done:f.pck_done,pck_sent:f.pck_sent,bytes_done:f.bytes_done,bytes_sent:f.bytes_sent,nb_ipid:f.nb_ipid,nb_opid:f.nb_opid},networkData:{bytesSent:f.bytes_sent||0,bytesReceived:f.bytes_done||0,packetsSent:f.pck_sent||0,packetsReceived:f.pck_done||0},buffersData:{name:f.name,inputBuffers:,totalBufferInfo:{totalBuffer:0,totalCapacity:0,averageUsage:0}},inputPids:l!=null&&l.ipids?Object.values(l.ipids):,outputPids:l!=null&&l.opids?Object.values(l.opids):}),f,l),g=()=>{n(-1)},v=()=>{r(e)};return h.jsx("div",{className:"flex-1 p-4",children:h.jsx(eLe,{...p,filterData:l,onBack:g,onOpenProperties:v,initialTab:i.current||void 0,isLoading:d})})},nLe=e=>h.jsx(vu,{value:`filter-${e.idx}`,className:"flex-1 p-4",children:h.jsx(gK,{...e})});function rLe(e,t){return e.map(n=>{const r=t.find(o=>o.idx===n.idx);return{...n,ipid:Object.fromEntries(Object.entries(n.ipid).map((o,i)=>o,{...i,buffer:0,buffer_total:0})),opid:Object.fromEntries(Object.entries(n.opid).map((o,i)=>o,{...i,buffer:0,buffer_total:0})),status:(r==null?void 0:r.status)||n.status,bytes_done:(r==null?void 0:r.bytes_done)||0,bytes_sent:(r==null?void 0:r.bytes_sent)||0,pck_done:(r==null?void 0:r.pck_done)||0,pck_sent:(r==null?void 0:r.pck_sent)||0,time:(r==null?void 0:r.time)||0,tasks:0,errors:0,is_eos:(r==null?void 0:r.is_eos)??!1}})}const oLe=,vK=Pe.memo(({id:e,isDetached:t,detachedFilterIdx:n})=>{const r=Hn(),o,i=y.useState("main"),s,a=y.useState(!1),l=y.useRef(null),{ref:c}=Jx({onResizeStart:()=>a(!0),onResizeEnd:()=>a(!1),debounce:20,throttle:!0}),d=c,f=y.useMemo(()=>o==="main",o),{isLoading:p,sessionStats:g,staticFilters:v}=JAe(f),b=y.useMemo(()=>v.length===0||s?:rLe(v,g),v,g,s),_=VV(b),{statsCounters:x,systemStats:w}=rMe(_,g),{monitoredFilterMap:C,inlineFilterMap:E}=aMe(_),{handleCardClick:R,handleDetachTab:P,handleCloseTab:N,handleOpenProperties:k}=lMe(i),{closeSidebar:I,sidebarContent:O}=Mb(),j=Xe($=>$.graph.pendingFilterOpen);y.useEffect(()=>{j&&(R(j.filterIdx),r(Lme()))},j,R,r),y.useEffect(()=>{O&&$Me(o)!==O.filterIdx&&I()},o,O,I);const H=y.useCallback(()=>{},),q=y.useCallback(()=>{},),M=s?H:i,B=s?q:R;if(t&&n!==void 0){const $=_.find(W=>W.idx===n);return $?h.jsx(Js,{id:e,children:h.jsx("div",{className:"h-full",children:h.jsx(gK,{idx:$.idx,filter:$,isActive:!0,onCardClick:R,onOpenProperties:k})})}):h.jsx(Js,{id:e,children:h.jsx("div",{className:"flex items-center justify-center h-full",children:h.jsxs("p",{className:"text-monitor-text-muted",children:"Filter ",n," not found"})})})}return p&&v.length===0?h.jsx(qV,{id:e,isLoading:!0}):!p&&v.length===0?h.jsx(Js,{id:e,children:h.jsxs("div",{className:"flex flex-col items-center justify-center h-full p-4 text-monitor-text-secondary",children:h.jsx("p",{className:"text-monitor-text-primary",children:"No filters available"}),h.jsx("p",{className:"text-sm mt-2 text-monitor-text-muted",children:"Waiting for graph construction..."})})}):h.jsx(Js,{id:e,children:h.jsx("div",{ref:d,className:`h-full ${s?"contain-layout contain-style":""}`,children:h.jsxs(hY,{value:o,onValueChange:M,className:"flex-1 flex flex-col",children:h.jsx(CMe,{activeTab:o,onValueChange:i,allFilters:_,onCloseTab:N,onDetachTab:P,tabsRef:l}),h.jsx(vu,{value:"main",className:`flex-1 p-4 ${s?"pointer-events-none":""}`,children:h.jsx(hje,{systemStats:w,statsCounters:x,filtersWithLiveStats:_,filtersMatchingCriteria:_,loading:p||s,monitoredFilters:C,onCardClick:B,refreshInterval:"1s",activeWidgets:oLe})}),h.jsx(tLe,{monitoredFilters:E,activeTab:o,onCardClick:B,onOpenProperties:k})})})})});vK.displayName="MultiFilterMonitor";const ow={_n.FILTERSESSION:{type:_n.FILTERSESSION,title:"Session Filters",icon:bp_n.FILTERSESSION,component:vK,defaultSize:{w:18,h:5},defaultPosition:{x:6,y:0},defaultZIndex:1e3,description:"Manage session filters effectively.",enabled:!0},_n.GRAPH:{type:_n.GRAPH,title:"Pipeline Graph",icon:bp_n.GRAPH,component:ZAe,defaultSize:{w:16,h:7},defaultPosition:{x:0,y:6},defaultZIndex:1004,description:"Visualize the processing pipeline graph.",enabled:!0},_n.METRICS:{type:_n.METRICS,title:"System Metrics",icon:bp_n.METRICS,component:yG,defaultSize:{w:6,h:5},defaultPosition:{x:0,y:0},defaultZIndex:1001,description:"Display real-time system performance metrics.",enabled:!0},_n.LOGS:{type:_n.LOGS,title:"System Logs",icon:bp_n.LOGS,component:__e,defaultSize:{w:8,h:7},defaultPosition:{x:16,y:6},defaultZIndex:1002,description:"View and filter system logs.",enabled:!0}},KI=e=>{const t=owe;if(!t||!t.enabled)return null;const n=br(e),{w:r,h:o}=t.defaultSize,{x:i=0,y:s=0}=t.defaultPosition||{};return{id:n,type:e,title:t.title,x:i,y:s,w:r,h:o,fixedPosition:t.fixedPosition}},iLe=()=>Object.values(ow),sLe=e=>owe,aLe={isMaximized:!1,isMinimized:!1,settings:{}};function lLe(){const e=Object.values(ow).filter(n=>n.enabled).map(n=>KI(n.type)).filter(Boolean),t=Object.fromEntries(e.map(n=>n.id,{...aLe}));return{activeWidgets:e,configs:t,savedLayouts:{},viewByFilter:{},currentLayout:void 0}}const cLe=(()=>{const e=lLe();if(typeof window>"u")return e;const t=FEe();if(!t||Object.keys(t).length===0)return e;const n=$Ee(),r=n&&tn?n:"default",o=tr;return o?{...e,activeWidgets:o.widgets,configs:o.configs,savedLayouts:t,currentLayout:r}:{...e,savedLayouts:t}})(),uLe=(e,t)=>{const n=KI(t.payload);if(!(!n||e.activeWidgets.some(o=>o.type===n.type))){if(n.fixedPosition&&e.currentLayout){const o=e.savedLayoutse.currentLayout,i=o==null?void 0:o.widgets.find(s=>s.type===n.type);i&&(n.x=i.x,n.y=i.y)}e.activeWidgets.push(n)}},dLe=(e,t)=>{const n=t.payload;if(e.activeWidgets=e.activeWidgets.filter(r=>r.id!==n),e.configsn){const{n:r,...o}=e.configs;e.configs=o}for(constr,oof Object.entries(e.viewByFilter))(o==null?void 0:o.widgetId)===n&&delete e.viewByFilterNumber(r)},fLe=(e,t)=>{e.configst.payload&&(e.configst.payload={...e.configst.payload,isMaximized:!0,isMinimized:!1})},hLe=(e,t)=>{e.configst.payload&&(e.configst.payload={...e.configst.payload,isMaximized:!1,isMinimized:!0})},pLe=(e,t)=>{e.configst.payload&&(e.configst.payload={...e.configst.payload,isMaximized:!1,isMinimized:!1})},mLe=(e,t)=>{const n=e.activeWidgets.find(r=>r.id===t.payload.id);n&&!n.fixedPosition&&(n.x=t.payload.x,n.y=t.payload.y,n.w=t.payload.w,n.h=t.payload.h)},gLe=(e,t)=>{const n=t.payload,o=e.activeWidgets.filter(s=>!s.isDetached&&!s.detachedFilterIdx).map(s=>({...s})),i={};o.forEach(s=>{const a=e.configss.id;a&&(is.id={...a,settings:{...a.settings||{}}})}),e.savedLayoutsn={name:n,widgets:o,configs:i,createdAt:new Date().toISOString()},e.currentLayout=n},vLe=(e,t)=>{const n=t.payload,r=e.savedLayoutsn;if(!r)return;e.activeWidgets=r.widgets.map(i=>({...i}));const o={};Object.entries(r.configs).forEach((i,s)=>{oi={...s,settings:{...s.settings||{}}}}),e.configs=o,e.currentLayout=n},yLe=(e,t)=>{const n=t.payload;delete e.savedLayoutsn},xLe=(e,t)=>{const n=t.payload,r=e.viewByFiltern;(r==null?void 0:r.mode)==="detached"&&r.widgetId&&(e.activeWidgets=e.activeWidgets.filter(o=>o.id!==r.widgetId),delete e.configsr.widgetId),e.viewByFiltern={mode:"inline"}},bLe=(e,t)=>{const{idx:n,name:r}=t.payload,o=KI(_n.FILTERSESSION);if(!o)return;o.isDetached=!0,o.detachedFilterIdx=n,o.title=r;const s=e.activeWidgets.filter(v=>!v.isDetached).reduce((v,b)=>Math.max(v,b.y+b.h),0),a=Object.values(e.viewByFilter).filter(v=>(v==null?void 0:v.mode)==="detached").length,l=24,c=6,d=6,f=Math.floor(l/c),p=Math.floor(a/f),g=a%f;o.x=g*c,o.y=s+p*(d+1),o.w=c,o.h=d,e.activeWidgets.push(o),e.configso.id={isMaximized:!1,isMinimized:!1,settings:{}},e.viewByFiltern={mode:"detached",widgetId:o.id}},wLe=(e,t)=>{const n=t.payload,r=e.viewByFiltern;(r==null?void 0:r.mode)==="detached"&&r.widgetId&&(e.activeWidgets=e.activeWidgets.filter(o=>o.id!==r.widgetId),delete e.configsr.widgetId,e.viewByFiltern={mode:"inline"})},SLe=(e,t)=>{const n=t.payload,r=e.viewByFiltern;r&&(r.mode==="detached"&&r.widgetId&&(e.activeWidgets=e.activeWidgets.filter(o=>o.id!==r.widgetId),delete e.configsr.widgetId),delete e.viewByFiltern)},yK=Sc({name:"widgets",initialState:cLe,reducers:{addWidget:uLe,removeWidget:dLe,maximizeWidget:fLe,minimizeWidget:hLe,restoreWidget:pLe,updateWidgetPosition:mLe,saveLayout:gLe,loadLayout:vLe,deleteLayout:yLe,openFilterInline:xLe,detachFilter:bLe,attachFilter:wLe,closeFilter:SLe}}),{addWidget:xK,removeWidget:_Le,maximizeWidget:CLe,minimizeWidget:ELe,restoreWidget:NLe,updateWidgetPosition:kz,saveLayout:XI,loadLayout:bK,deleteLayout:wK,openFilterInline:RLe,detachFilter:TLe,attachFilter:c5e,closeFilter:kLe}=yK.actions,PLe=yK.reducer,ZI=PW();ZI.startListening({matcher:qx(XI,wK),effect:(e,t)=>{const n=t.getState();wG(n.widgets.savedLayouts)}});ZI.startListening({matcher:qx(XI,bK),effect:(e,t)=>{const n=t.getState();SG(n.widgets.currentLayout)}});const ILe={updates:{}},SK=Sc({name:"filterArgument",initialState:ILe,reducers:{setArgumentUpdateStatus:(e,t)=>{const n=`${t.payload.filterId}_${t.payload.name}`;e.updatesn=t.payload},clearArgumentUpdate:(e,t)=>{const n=`${t.payload.filterId}_${t.payload.name}`;delete e.updatesn}},extraReducers:e=>{e.addCase(ix.pending,(t,n)=>{const{filterId:r,argName:o,argValue:i}=n.meta.arg,s=`${r}_${o}`;t.updatess={filterId:r,name:o,value:i,status:"pending"}})}}),{setArgumentUpdateStatus:Pz,clearArgumentUpdate:u5e}=SK.actions,ix=Kpe("filterArgument/updateFilterArgument",async({filterId:e,argName:t,argValue:n},{dispatch:r,getState:o})=>{const i=Ome(o(),e);if(!i)throw new Error(`Filter with ID ${e} not found`);await Mo.updateFilterArg(parseInt(e),i,t,n),r(Pz({filterId:e,name:t,value:n,status:"success"})),setTimeout(()=>{r(Pz({filterId:e,name:t,value:n,status:"idle"}))},2e3)}),ALe=SK.reducer,_K="gpac-connections",fT="gpac-active-connection",Zd={id:"default",name:"Default",address:"ws://localhost:6363",type:"local",status:Lr.DISCONNECTED},MLe=e=>e.reduce((t,n)=>(tn.id=n,t),{}),jLe=e=>Object.values(e),LLe=()=>{try{const e=localStorage.getItem(_K);return e?MLe(JSON.parse(e)):{Zd.id:Zd}}catch{return{Zd.id:Zd}}},OLe=()=>{try{return localStorage.getItem(fT)||Zd.id}catch{return Zd.id}},DLe=e=>{try{localStorage.setItem(_K,JSON.stringify(jLe(e)))}catch(t){console.error("connectionsSlice Save failed:",t)}},FLe=e=>{try{e?localStorage.setItem(fT,e):localStorage.removeItem(fT)}catch(t){console.error("connectionsSlice Save active ID failed:",t)}},$Le={connectionsById:LLe(),activeConnectionId:OLe()},CK=Sc({name:"connections",initialState:$Le,reducers:{addConnection:(e,t)=>{e.connectionsByIdt.payload.id=t.payload},removeConnection:(e,t)=>{delete e.connectionsByIdt.payload,e.activeConnectionId===t.payload&&(e.activeConnectionId=null)},updateConnection:(e,t)=>{e.connectionsByIdt.payload.id={...e.connectionsByIdt.payload.id,...t.payload}},setActiveConnection:(e,t)=>{e.activeConnectionId=t.payload},updateConnectionStatus:(e,t)=>{const n=e.connectionsByIdt.payload.id;n&&(n.status=t.payload.status)},clearConnections:e=>{e.connectionsById={},e.activeConnectionId=null}}}),{addConnection:EK,removeConnection:NK,updateConnection:RK,setActiveConnection:TK,updateConnectionStatus:zLe,clearConnections:HLe}=CK.actions,BLe=CK.reducer,QI=PW();QI.startListening({matcher:qx(EK,RK,NK,HLe),effect:(e,t)=>{const n=t.getState();DLe(n.connections.connectionsById)}});QI.startListening({actionCreator:TK,effect:e=>{FLe(e.payload)}});const Zo=Bpe({reducer:{graph:Dme,filterArgument:ALe,logs:Lye,widgets:PLe,sessionStats:pye,layout:WEe,monitoredFilter:Bje,connections:BLe},middleware:e=>e({serializableCheck:{ignoredActions:"graph/updateGraphData","graph/updateNodeData"}}).prepend(QI.middleware).prepend(ZI.middleware)});var kK={exports:{}},Cg={},hT={exports:{}};(function(e,t){(function(n,r){r(t)})(rs,function(n){function r(Z){return function(ce,xe,ge,pe,he,we,Ie){return Z(ce,xe,Ie)}}function o(Z){return function(ce,xe,ge,pe){if(!ce||!xe||typeof ce!="object"||typeof xe!="object")return Z(ce,xe,ge,pe);var he=pe.get(ce),we=pe.get(xe);if(he&&we)return he===xe&&we===ce;pe.set(ce,xe),pe.set(xe,ce);var Ie=Z(ce,xe,ge,pe);return pe.delete(ce),pe.delete(xe),Ie}}function i(Z,oe){var ce={};for(var xe in Z)cexe=Zxe;for(var xe in oe)cexe=oexe;return ce}function s(Z){return Z.constructor===Object||Z.constructor==null}function a(Z){return typeof Z.then=="function"}function l(Z,oe){return Z===oe||Z!==Z&&oe!==oe}var c="object Arguments",d="object Boolean",f="object Date",p="object RegExp",g="object Map",v="object Number",b="object Object",_="object Set",x="object String",w=Object.prototype.toString;function C(Z){var oe=Z.areArraysEqual,ce=Z.areDatesEqual,xe=Z.areMapsEqual,ge=Z.areObjectsEqual,pe=Z.areRegExpsEqual,he=Z.areSetsEqual,we=Z.createIsNestedEqual,Ie=we(Ce);function Ce(Me,ze,Ye){if(Me===ze)return!0;if(!Me||!ze||typeof Me!="object"||typeof ze!="object")return Me!==Me&&ze!==ze;if(s(Me)&&s(ze))return ge(Me,ze,Ie,Ye);var Ht=Array.isArray(Me),Ft=Array.isArray(ze);if(Ht||Ft)return Ht===Ft&&oe(Me,ze,Ie,Ye);var rt=w.call(Me);return rt!==w.call(ze)?!1:rt===f?ce(Me,ze,Ie,Ye):rt===p?pe(Me,ze,Ie,Ye):rt===g?xe(Me,ze,Ie,Ye):rt===_?he(Me,ze,Ie,Ye):rt===b||rt===c?a(Me)||a(ze)?!1:ge(Me,ze,Ie,Ye):rt===d||rt===v||rt===x?l(Me.valueOf(),ze.valueOf()):!1}return Ce}function E(Z,oe,ce,xe){var ge=Z.length;if(oe.length!==ge)return!1;for(;ge-- >0;)if(!ce(Zge,oege,ge,ge,Z,oe,xe))return!1;return!0}var R=o(E);function P(Z,oe){return l(Z.valueOf(),oe.valueOf())}function N(Z,oe,ce,xe){var ge=Z.size===oe.size;if(!ge)return!1;if(!Z.size)return!0;var pe={},he=0;return Z.forEach(function(we,Ie){if(ge){var Ce=!1,Me=0;oe.forEach(function(ze,Ye){!Ce&&!peMe&&(Ce=ce(Ie,Ye,he,Me,Z,oe,xe)&&ce(we,ze,Ie,Ye,Z,oe,xe))&&(peMe=!0),Me++}),he++,ge=Ce}}),ge}var k=o(N),I="_owner",O=Object.prototype.hasOwnProperty;function j(Z,oe,ce,xe){var ge=Object.keys(Z),pe=ge.length;if(Object.keys(oe).length!==pe)return!1;for(var he;pe-- >0;){if(he=gepe,he===I){var we=!!Z.$$typeof,Ie=!!oe.$$typeof;if((we||Ie)&&we!==Ie)return!1}if(!O.call(oe,he)||!ce(Zhe,oehe,he,he,Z,oe,xe))return!1}return!0}var H=o(j);function q(Z,oe){return Z.source===oe.source&&Z.flags===oe.flags}function M(Z,oe,ce,xe){var ge=Z.size===oe.size;if(!ge)return!1;if(!Z.size)return!0;var pe={};return Z.forEach(function(he,we){if(ge){var Ie=!1,Ce=0;oe.forEach(function(Me,ze){!Ie&&!peCe&&(Ie=ce(he,Me,we,ze,Z,oe,xe))&&(peCe=!0),Ce++}),ge=Ie}}),ge}var B=o(M),$=Object.freeze({areArraysEqual:E,areDatesEqual:P,areMapsEqual:N,areObjectsEqual:j,areRegExpsEqual:q,areSetsEqual:M,createIsNestedEqual:r}),W=Object.freeze({areArraysEqual:R,areDatesEqual:P,areMapsEqual:k,areObjectsEqual:H,areRegExpsEqual:q,areSetsEqual:B,createIsNestedEqual:r}),G=C($);function z(Z,oe){return G(Z,oe,void 0)}var K=C(i($,{createIsNestedEqual:function(){return l}}));function Q(Z,oe){return K(Z,oe,void 0)}var re=C(W);function ae(Z,oe){return re(Z,oe,new WeakMap)}var de=C(i(W,{createIsNestedEqual:function(){return l}}));function Ne(Z,oe){return de(Z,oe,new WeakMap)}function ye(Z){return C(i($,Z($)))}function fe(Z){var oe=C(i(W,Z(W)));return function(ce,xe,ge){return ge===void 0&&(ge=new WeakMap),oe(ce,xe,ge)}}n.circularDeepEqual=ae,n.circularShallowEqual=Ne,n.createCustomCircularEqual=fe,n.createCustomEqual=ye,n.deepEqual=z,n.sameValueZeroEqual=l,n.shallowEqual=Q,Object.defineProperty(n,"__esModule",{value:!0})})})(hT,hT.exports);var JI=hT.exports,pT={exports:{}};function PK(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)et&&(n=PK(et))&&(r&&(r+=" "),r+=n)}else for(n in e)en&&(r&&(r+=" "),r+=n);return r}function Iz(){for(var e,t,n=0,r="",o=arguments.length;n<o;n++)(e=argumentsn)&&(t=PK(e))&&(r&&(r+=" "),r+=t);return r}pT.exports=Iz,pT.exports.clsx=Iz;var iw=pT.exports,Mt={},WLe=function(t,n,r){return t===n?!0:t.className===n.className&&r(t.style,n.style)&&t.width===n.width&&t.autoSize===n.autoSize&&t.cols===n.cols&&t.draggableCancel===n.draggableCancel&&t.draggableHandle===n.draggableHandle&&r(t.verticalCompact,n.verticalCompact)&&r(t.compactType,n.compactType)&&r(t.layout,n.layout)&&r(t.margin,n.margin)&&r(t.containerPadding,n.containerPadding)&&t.rowHeight===n.rowHeight&&t.maxRows===n.maxRows&&t.isBounded===n.isBounded&&t.isDraggable===n.isDraggable&&t.isResizable===n.isResizable&&t.allowOverlap===n.allowOverlap&&t.preventCollision===n.preventCollision&&t.useCSSTransforms===n.useCSSTransforms&&t.transformScale===n.transformScale&&t.isDroppable===n.isDroppable&&r(t.resizeHandles,n.resizeHandles)&&r(t.resizeHandle,n.resizeHandle)&&t.onLayoutChange===n.onLayoutChange&&t.onDragStart===n.onDragStart&&t.onDrag===n.onDrag&&t.onDragStop===n.onDragStop&&t.onResizeStart===n.onResizeStart&&t.onResize===n.onResize&&t.onResizeStop===n.onResizeStop&&t.onDrop===n.onDrop&&r(t.droppingItem,n.droppingItem)&&r(t.innerRef,n.innerRef)};Object.defineProperty(Mt,"__esModule",{value:!0});Mt.bottom=eA;Mt.childrenEqual=GLe;Mt.cloneLayout=IK;Mt.cloneLayoutItem=Ru;Mt.collides=sw;Mt.compact=MK;Mt.compactItem=jK;Mt.compactType=sOe;Mt.correctBounds=LK;Mt.fastPositionEqual=VLe;Mt.fastRGLPropsEqual=void 0;Mt.getAllCollisions=OK;Mt.getFirstCollision=yu;Mt.getLayoutItem=tA;Mt.getStatics=nA;Mt.modifyLayout=AK;Mt.moveElement=Mp;Mt.moveElementAwayFromCollision=gT;Mt.noop=void 0;Mt.perc=KLe;Mt.resizeItemInDirection=tOe;Mt.setTopLeft=rOe;Mt.setTransform=nOe;Mt.sortLayoutItems=lA;Mt.sortLayoutItemsByColRow=HK;Mt.sortLayoutItemsByRowCol=zK;Mt.synchronizeLayoutWithChildren=oOe;Mt.validateLayout=iOe;Mt.withLayoutItem=qLe;var Az=JI,Ap=ULe(y);function ULe(e){return e&&e.__esModule?e:{default:e}}function eA(e){let t=0,n;for(let r=0,o=e.length;r<o;r++)n=er.y+er.h,n>t&&(t=n);return t}function IK(e){const t=Array(e.length);for(let n=0,r=e.length;n<r;n++)tn=Ru(en);return t}function AK(e,t){const n=Array(e.length);for(let r=0,o=e.length;r<o;r++)t.i===er.i?nr=t:nr=er;return n}function qLe(e,t,n){let r=tA(e,t);return r?(r=n(Ru(r)),e=AK(e,r),e,r):e,null}function Ru(e){return{w:e.w,h:e.h,x:e.x,y:e.y,i:e.i,minW:e.minW,maxW:e.maxW,minH:e.minH,maxH:e.maxH,moved:!!e.moved,static:!!e.static,isDraggable:e.isDraggable,isResizable:e.isResizable,resizeHandles:e.resizeHandles,isBounded:e.isBounded}}function GLe(e,t){return(0,Az.deepEqual)(Ap.default.Children.map(e,n=>n==null?void 0:n.key),Ap.default.Children.map(t,n=>n==null?void 0:n.key))&&(0,Az.deepEqual)(Ap.default.Children.map(e,n=>n==null?void 0:n.props"data-grid"),Ap.default.Children.map(t,n=>n==null?void 0:n.props"data-grid"))}Mt.fastRGLPropsEqual=WLe;function VLe(e,t){return e.left===t.left&&e.top===t.top&&e.width===t.width&&e.height===t.height}function sw(e,t){return!(e.i===t.i||e.x+e.w<=t.x||e.x>=t.x+t.w||e.y+e.h<=t.y||e.y>=t.y+t.h)}function MK(e,t,n,r){const o=nA(e),i=lA(e,t),s=Array(e.length);for(let a=0,l=i.length;a<l;a++){let c=Ru(ia);c.static||(c=jK(o,c,t,n,i,r),o.push(c)),se.indexOf(ia)=c,c.moved=!1}return s}const YLe={x:"w",y:"h"};function mT(e,t,n,r){const o=YLer;tr+=1;const i=e.map(s=>s.i).indexOf(t.i);for(let s=i+1;s<e.length;s++){const a=es;if(!a.static){if(a.y>t.y+t.h)break;sw(t,a)&&mT(e,a,n+to,r)}}tr=n}function jK(e,t,n,r,o,i){const s=n==="vertical",a=n==="horizontal";if(s)for(t.y=Math.min(eA(e),t.y);t.y>0&&!yu(e,t);)t.y--;else if(a)for(;t.x>0&&!yu(e,t);)t.x--;let l;for(;(l=yu(e,t))&&!(n===null&&i);)if(a?mT(o,t,l.x+l.w,"x"):mT(o,t,l.y+l.h,"y"),a&&t.x+t.w>r)for(t.x=r-t.w,t.y++;t.x>0&&!yu(e,t);)t.x--;return t.y=Math.max(t.y,0),t.x=Math.max(t.x,0),t}function LK(e,t){const n=nA(e);for(let r=0,o=e.length;r<o;r++){const i=er;if(i.x+i.w>t.cols&&(i.x=t.cols-i.w),i.x<0&&(i.x=0,i.w=t.cols),!i.static)n.push(i);else for(;yu(n,i);)i.y++}return e}function tA(e,t){for(let n=0,r=e.length;n<r;n++)if(en.i===t)return en}function yu(e,t){for(let n=0,r=e.length;n<r;n++)if(sw(en,t))return en}function OK(e,t){return e.filter(n=>sw(n,t))}function nA(e){return e.filter(t=>t.static)}function Mp(e,t,n,r,o,i,s,a,l){if(t.static&&t.isDraggable!==!0||t.y===r&&t.x===n)return e;`${t.i}${String(n)}${String(r)}${t.x}${t.y}`;const c=t.x,d=t.y;typeof n=="number"&&(t.x=n),typeof r=="number"&&(t.y=r),t.moved=!0;let f=lA(e,s);(s==="vertical"&&typeof r=="number"?d>=r:s==="horizontal"&&typeof n=="number"?c>=n:!1)&&(f=f.reverse());const g=OK(f,t),v=g.length>0;if(v&&l)return IK(e);if(v&&i)return`${t.i}`,t.x=c,t.y=d,t.moved=!1,e;for(let b=0,_=g.length;b<_;b++){const x=gb;`${t.i}${t.x}${t.y}${x.i}${x.x}${x.y}`,!x.moved&&(x.static?e=gT(e,x,t,o,s):e=gT(e,t,x,o,s))}return e}function gT(e,t,n,r,o,i){const s=o==="horizontal",a=o==="vertical",l=t.static;if(r){r=!1;const f={x:s?Math.max(t.x-n.w,0):n.x,y:a?Math.max(t.y-n.h,0):n.y,w:n.w,h:n.h,i:"-1"},p=yu(e,f),g=p&&p.y+p.h>t.y,v=p&&t.x+t.w>p.x;if(p){if(g&&a)return Mp(e,n,void 0,t.y+1,r,l,o);if(g&&o==null)return t.y=n.y,n.y=n.y+n.h,e;if(v&&s)return Mp(e,t,n.x,void 0,r,l,o)}else return`${n.i}${f.x}${f.y}`,Mp(e,n,s?f.x:void 0,a?f.y:void 0,r,l,o)}const c=s?n.x+1:void 0,d=a?n.y+1:void 0;return c==null&&d==null?e:Mp(e,n,s?n.x+1:void 0,a?n.y+1:void 0,r,l,o)}function KLe(e){return e*100+"%"}const DK=(e,t,n,r)=>e+n>r?t:n,FK=(e,t,n)=>e<0?t:n,$K=e=>Math.max(0,e),rA=e=>Math.max(0,e),oA=(e,t,n)=>{let{left:r,height:o,width:i}=t;const s=e.top-(o-e.height);return{left:r,width:i,height:FK(s,e.height,o),top:rA(s)}},iA=(e,t,n)=>{let{top:r,left:o,height:i,width:s}=t;return{top:r,height:i,width:DK(e.left,e.width,s,n),left:$K(o)}},sA=(e,t,n)=>{let{top:r,height:o,width:i}=t;const s=e.left-(i-e.width);return{height:o,width:s<0?e.width:DK(e.left,e.width,i,n),top:rA(r),left:$K(s)}},aA=(e,t,n)=>{let{top:r,left:o,height:i,width:s}=t;return{width:s,left:o,height:FK(r,e.height,i),top:rA(r)}},XLe=function(){return oA(arguments.length<=0?void 0:arguments0,iA(...arguments))},ZLe=function(){return oA(arguments.length<=0?void 0:arguments0,sA(...arguments))},QLe=function(){return aA(arguments.length<=0?void 0:arguments0,iA(...arguments))},JLe=function(){return aA(arguments.length<=0?void 0:arguments0,sA(...arguments))},eOe={n:oA,ne:XLe,e:iA,se:QLe,s:aA,sw:JLe,w:sA,nw:ZLe};function tOe(e,t,n,r){const o=eOee;return o?o(t,{...t,...n},r):n}function nOe(e){let{top:t,left:n,width:r,height:o}=e;const i=`translate(${n}px,${t}px)`;return{transform:i,WebkitTransform:i,MozTransform:i,msTransform:i,OTransform:i,width:`${r}px`,height:`${o}px`,position:"absolute"}}function rOe(e){let{top:t,left:n,width:r,height:o}=e;return{top:`${t}px`,left:`${n}px`,width:`${r}px`,height:`${o}px`,position:"absolute"}}function lA(e,t){return t==="horizontal"?HK(e):t==="vertical"?zK(e):e}function zK(e){return e.slice(0).sort(function(t,n){return t.y>n.y||t.y===n.y&&t.x>n.x?1:t.y===n.y&&t.x===n.x?0:-1})}function HK(e){return e.slice(0).sort(function(t,n){return t.x>n.x||t.x===n.x&&t.y>n.y?1:-1})}function oOe(e,t,n,r,o){e=e||;const i=;Ap.default.Children.forEach(t,a=>{if((a==null?void 0:a.key)==null)return;const l=tA(e,String(a.key)),c=a.props"data-grid";l&&c==null?i.push(Ru(l)):c?i.push(Ru({...c,i:a.key})):i.push(Ru({w:1,h:1,x:0,y:eA(i),i:String(a.key)}))});const s=LK(i,{cols:n});return o?s:MK(s,r,n)}function iOe(e){let t=arguments.length>1&&arguments1!==void 0?arguments1:"Layout";const n="x","y","w","h";if(!Array.isArray(e))throw new Error(t+" must be an array!");for(let r=0,o=e.length;r<o;r++){const i=er;for(let s=0;s<n.length;s++){const a=ns,l=ia;if(typeof l!="number"||Number.isNaN(l))throw new Error(`ReactGridLayout: ${t}${r}.${a} must be a number! Received: ${l} (${typeof l})`)}if(typeof i.i<"u"&&typeof i.i!="string")throw new Error(`ReactGridLayout: ${t}${r}.i must be a string! Received: ${i.i} (${typeof i.i})`)}}function sOe(e){const{verticalCompact:t,compactType:n}=e||{};return t===!1?null:n}const aOe=()=>{};Mt.noop=aOe;var ha={};Object.defineProperty(ha,"__esModule",{value:!0});ha.calcGridColWidth=aw;ha.calcGridItemPosition=lOe;ha.calcGridItemWHPx=vT;ha.calcWH=uOe;ha.calcXY=cOe;ha.clamp=xu;function aw(e){const{margin:t,containerPadding:n,containerWidth:r,cols:o}=e;return(r-t0*(o-1)-n0*2)/o}function vT(e,t,n){return Number.isFinite(e)?Math.round(t*e+Math.max(0,e-1)*n):e}function lOe(e,t,n,r,o,i){const{margin:s,containerPadding:a,rowHeight:l}=e,c=aw(e),d={};return i&&i.resizing?(d.width=Math.round(i.resizing.width),d.height=Math.round(i.resizing.height)):(d.width=vT(r,c,s0),d.height=vT(o,l,s1)),i&&i.dragging?(d.top=Math.round(i.dragging.top),d.left=Math.round(i.dragging.left)):i&&i.resizing&&typeof i.resizing.top=="number"&&typeof i.resizing.left=="number"?(d.top=Math.round(i.resizing.top),d.left=Math.round(i.resizing.left)):(d.top=Math.round((l+s1)*n+a1),d.left=Math.round((c+s0)*t+a0)),d}function cOe(e,t,n,r,o){const{margin:i,containerPadding:s,cols:a,rowHeight:l,maxRows:c}=e,d=aw(e);let f=Math.round((n-s0)/(d+i0)),p=Math.round((t-s1)/(l+i1));return f=xu(f,0,a-r),p=xu(p,0,c-o),{x:f,y:p}}function uOe(e,t,n,r,o,i){const{margin:s,maxRows:a,cols:l,rowHeight:c}=e,d=aw(e);let f=Math.round((t+s0)/(d+s0)),p=Math.round((n+s1)/(c+s1)),g=xu(f,0,l-r),v=xu(p,0,a-o);return"sw","w","nw".indexOf(i)!==-1&&(g=xu(f,0,l)),"nw","n","ne".indexOf(i)!==-1&&(v=xu(p,0,a)),{w:g,h:v}}function xu(e,t,n){return Math.max(Math.min(e,n),t)}var lw={},BK={exports:{}},dOe="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED",fOe=dOe,hOe=fOe;function WK(){}function UK(){}UK.resetWarningCache=WK;var pOe=function(){function e(r,o,i,s,a,l){if(l!==hOe){var c=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw c.name="Invariant Violation",c}}e.isRequired=e;function t(){return e}var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:UK,resetWarningCache:WK};return n.PropTypes=n,n};BK.exports=pOe();var Pc=BK.exports,cw={exports:{}},qK={},Yn={},pa={};Object.defineProperty(pa,"__esModule",{value:!0});pa.dontSetMe=xOe;pa.findInArray=mOe;pa.int=yOe;pa.isFunction=gOe;pa.isNum=vOe;function mOe(e,t){for(let n=0,r=e.length;n<r;n++)if(t.apply(t,en,n,e))return en}function gOe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="object Function"}function vOe(e){return typeof e=="number"&&!isNaN(e)}function yOe(e){return parseInt(e,10)}function xOe(e,t,n){if(et)return new Error(`Invalid prop ${t} passed to ${n} - do not set this, set it on the child.`)}var ed={};Object.defineProperty(ed,"__esModule",{value:!0});ed.browserPrefixToKey=VK;ed.browserPrefixToStyle=bOe;ed.default=void 0;ed.getPrefix=GK;const iN="Moz","Webkit","O","ms";function GK(){var n,r;let e=arguments.length>0&&arguments0!==void 0?arguments0:"transform";if(typeof window>"u")return"";const t=(r=(n=window.document)==null?void 0:n.documentElement)==null?void 0:r.style;if(!t||e in t)return"";for(let o=0;o<iN.length;o++)if(VK(e,iNo)in t)return iNo;return""}function VK(e,t){return t?`${t}${wOe(e)}`:e}function bOe(e,t){return t?`-${t.toLowerCase()}-${e}`:e}function wOe(e){let t="",n=!0;for(let r=0;r<e.length;r++)n?(t+=er.toUpperCase(),n=!1):er==="-"?n=!0:t+=er;return t}ed.default=GK();Object.defineProperty(Yn,"__esModule",{value:!0});Yn.addClassName=XK;Yn.addEvent=_Oe;Yn.addUserSelectStyles=jOe;Yn.createCSSTransform=POe;Yn.createSVGTransform=IOe;Yn.getTouch=AOe;Yn.getTouchIdentifier=MOe;Yn.getTranslation=cA;Yn.innerHeight=ROe;Yn.innerWidth=TOe;Yn.matchesSelector=KK;Yn.matchesSelectorAndParentsTo=SOe;Yn.offsetXYFromParent=kOe;Yn.outerHeight=EOe;Yn.outerWidth=NOe;Yn.removeClassName=ZK;Yn.removeEvent=COe;Yn.scheduleRemoveUserSelectStyles=LOe;var oi=pa,Mz=YK(ed);function YK(e,t){if(typeof WeakMap=="function")var n=new WeakMap,r=new WeakMap;return(YK=function(o,i){if(!i&&o&&o.__esModule)return o;var s,a,l={__proto__:null,default:o};if(o===null||typeof o!="object"&&typeof o!="function")return l;if(s=i?r:n){if(s.has(o))return s.get(o);s.set(o,l)}for(const c in o)c!=="default"&&{}.hasOwnProperty.call(o,c)&&((a=(s=Object.defineProperty)&&Object.getOwnPropertyDescriptor(o,c))&&(a.get||a.set)?s(l,c,a):lc=oc);return l})(e,t)}let u0="";function KK(e,t){return u0||(u0=(0,oi.findInArray)("matches","webkitMatchesSelector","mozMatchesSelector","msMatchesSelector","oMatchesSelector",function(n){return(0,oi.isFunction)(en)})),(0,oi.isFunction)(eu0)?eu0(t):!1}function SOe(e,t,n){let r=e;do{if(KK(r,t))return!0;if(r===n)return!1;r=r.parentNode}while(r);return!1}function _Oe(e,t,n,r){if(!e)return;const o={capture:!0,...r};e.addEventListener?e.addEventListener(t,n,o):e.attachEvent?e.attachEvent("on"+t,n):e"on"+t=n}function COe(e,t,n,r){if(!e)return;const o={capture:!0,...r};e.removeEventListener?e.removeEventListener(t,n,o):e.detachEvent?e.detachEvent("on"+t,n):e"on"+t=null}function EOe(e){let t=e.clientHeight;const n=e.ownerDocument.defaultView.getComputedStyle(e);return t+=(0,oi.int)(n.borderTopWidth),t+=(0,oi.int)(n.borderBottomWidth),t}function NOe(e){let t=e.clientWidth;const n=e.ownerDocument.defaultView.getComputedStyle(e);return t+=(0,oi.int)(n.borderLeftWidth),t+=(0,oi.int)(n.borderRightWidth),t}function ROe(e){let t=e.clientHeight;const n=e.ownerDocument.defaultView.getComputedStyle(e);return t-=(0,oi.int)(n.paddingTop),t-=(0,oi.int)(n.paddingBottom),t}function TOe(e){let t=e.clientWidth;const n=e.ownerDocument.defaultView.getComputedStyle(e);return t-=(0,oi.int)(n.paddingLeft),t-=(0,oi.int)(n.paddingRight),t}function kOe(e,t,n){const o=t===t.ownerDocument.body?{left:0,top:0}:t.getBoundingClientRect(),i=(e.clientX+t.scrollLeft-o.left)/n,s=(e.clientY+t.scrollTop-o.top)/n;return{x:i,y:s}}function POe(e,t){const n=cA(e,t,"px");return{(0,Mz.browserPrefixToKey)("transform",Mz.default):n}}function IOe(e,t){return cA(e,t,"")}function cA(e,t,n){let{x:r,y:o}=e,i=`translate(${r}${n},${o}${n})`;if(t){const s=`${typeof t.x=="string"?t.x:t.x+n}`,a=`${typeof t.y=="string"?t.y:t.y+n}`;i=`translate(${s}, ${a})`+i}return i}function AOe(e,t){return e.targetTouches&&(0,oi.findInArray)(e.targetTouches,n=>t===n.identifier)||e.changedTouches&&(0,oi.findInArray)(e.changedTouches,n=>t===n.identifier)}function MOe(e){if(e.targetTouches&&e.targetTouches0)return e.targetTouches0.identifier;if(e.changedTouches&&e.changedTouches0)return e.changedTouches0.identifier}function jOe(e){if(!e)return;let t=e.getElementById("react-draggable-style-el");t||(t=e.createElement("style"),t.type="text/css",t.id="react-draggable-style-el",t.innerHTML=`.react-draggable-transparent-selection *::-moz-selection {all: inherit;} +`,t.innerHTML+=`.react-draggable-transparent-selection *::selection {all: inherit;} +`,e.getElementsByTagName("head")0.appendChild(t)),e.body&&XK(e.body,"react-draggable-transparent-selection")}function LOe(e){window.requestAnimationFrame?window.requestAnimationFrame(()=>{jz(e)}):jz(e)}function jz(e){if(e)try{if(e.body&&ZK(e.body,"react-draggable-transparent-selection"),e.selection)e.selection.empty();else{const t=(e.defaultView||window).getSelection();t&&t.type!=="Caret"&&t.removeAllRanges()}}catch{}}function XK(e,t){e.classList?e.classList.add(t):e.className.match(new RegExp(`(?:^|\\s)${t}(?!\\S)`))||(e.className+=` ${t}`)}function ZK(e,t){e.classList?e.classList.remove(t):e.className=e.className.replace(new RegExp(`(?:^|\\s)${t}(?!\\S)`,"g"),"")}var ma={};Object.defineProperty(ma,"__esModule",{value:!0});ma.canDragX=FOe;ma.canDragY=$Oe;ma.createCoreData=HOe;ma.createDraggableData=BOe;ma.getBoundPosition=OOe;ma.getControlPosition=zOe;ma.snapToGrid=DOe;var Wo=pa,Qd=Yn;function OOe(e,t,n){if(!e.props.bounds)returnt,n;let{bounds:r}=e.props;r=typeof r=="string"?r:WOe(r);const o=uA(e);if(typeof r=="string"){const{ownerDocument:i}=o,s=i.defaultView;let a;if(r==="parent"?a=o.parentNode:a=o.getRootNode().querySelector(r),!(a instanceof s.HTMLElement))throw new Error('Bounds selector "'+r+'" could not find an element.');const l=a,c=s.getComputedStyle(o),d=s.getComputedStyle(l);r={left:-o.offsetLeft+(0,Wo.int)(d.paddingLeft)+(0,Wo.int)(c.marginLeft),top:-o.offsetTop+(0,Wo.int)(d.paddingTop)+(0,Wo.int)(c.marginTop),right:(0,Qd.innerWidth)(l)-(0,Qd.outerWidth)(o)-o.offsetLeft+(0,Wo.int)(d.paddingRight)-(0,Wo.int)(c.marginRight),bottom:(0,Qd.innerHeight)(l)-(0,Qd.outerHeight)(o)-o.offsetTop+(0,Wo.int)(d.paddingBottom)-(0,Wo.int)(c.marginBottom)}}return(0,Wo.isNum)(r.right)&&(t=Math.min(t,r.right)),(0,Wo.isNum)(r.bottom)&&(n=Math.min(n,r.bottom)),(0,Wo.isNum)(r.left)&&(t=Math.max(t,r.left)),(0,Wo.isNum)(r.top)&&(n=Math.max(n,r.top)),t,n}function DOe(e,t,n){const r=Math.round(t/e0)*e0,o=Math.round(n/e1)*e1;returnr,o}function FOe(e){return e.props.axis==="both"||e.props.axis==="x"}function $Oe(e){return e.props.axis==="both"||e.props.axis==="y"}function zOe(e,t,n){const r=typeof t=="number"?(0,Qd.getTouch)(e,t):null;if(typeof t=="number"&&!r)return null;const o=uA(n),i=n.props.offsetParent||o.offsetParent||o.ownerDocument.body;return(0,Qd.offsetXYFromParent)(r||e,i,n.props.scale)}function HOe(e,t,n){const r=!(0,Wo.isNum)(e.lastX),o=uA(e);return r?{node:o,deltaX:0,deltaY:0,lastX:t,lastY:n,x:t,y:n}:{node:o,deltaX:t-e.lastX,deltaY:n-e.lastY,lastX:e.lastX,lastY:e.lastY,x:t,y:n}}function BOe(e,t){const n=e.props.scale;return{node:t.node,x:e.state.x+t.deltaX/n,y:e.state.y+t.deltaY/n,deltaX:t.deltaX/n,deltaY:t.deltaY/n,lastX:e.state.x,lastY:e.state.y}}function WOe(e){return{left:e.left,top:e.top,right:e.right,bottom:e.bottom}}function uA(e){const t=e.findDOMNode();if(!t)throw new Error("<DraggableCore>: Unmounted during event!");return t}var uw={},dw={};Object.defineProperty(dw,"__esModule",{value:!0});dw.default=UOe;function UOe(){}Object.defineProperty(uw,"__esModule",{value:!0});uw.default=void 0;var sN=QK(y),lo=dA(Pc),qOe=dA(al),Vr=Yn,Bl=ma,aN=pa,dp=dA(dw);function dA(e){return e&&e.__esModule?e:{default:e}}function QK(e,t){if(typeof WeakMap=="function")var n=new WeakMap,r=new WeakMap;return(QK=function(o,i){if(!i&&o&&o.__esModule)return o;var s,a,l={__proto__:null,default:o};if(o===null||typeof o!="object"&&typeof o!="function")return l;if(s=i?r:n){if(s.has(o))return s.get(o);s.set(o,l)}for(const c in o)c!=="default"&&{}.hasOwnProperty.call(o,c)&&((a=(s=Object.defineProperty)&&Object.getOwnPropertyDescriptor(o,c))&&(a.get||a.set)?s(l,c,a):lc=oc);return l})(e,t)}function co(e,t,n){return(t=GOe(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function GOe(e){var t=VOe(e,"string");return typeof t=="symbol"?t:t+""}function VOe(e,t){if(typeof e!="object"||!e)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}const Zi={touch:{start:"touchstart",move:"touchmove",stop:"touchend"},mouse:{start:"mousedown",move:"mousemove",stop:"mouseup"}};let Wl=Zi.mouse,fw=class extends sN.Component{constructor(){super(...arguments),co(this,"dragging",!1),co(this,"lastX",NaN),co(this,"lastY",NaN),co(this,"touchIdentifier",null),co(this,"mounted",!1),co(this,"handleDragStart",t=>{if(this.props.onMouseDown(t),!this.props.allowAnyClick&&typeof t.button=="number"&&t.button!==0)return!1;const n=this.findDOMNode();if(!n||!n.ownerDocument||!n.ownerDocument.body)throw new Error("<DraggableCore> not mounted on DragStart!");const{ownerDocument:r}=n;if(this.props.disabled||!(t.target instanceof r.defaultView.Node)||this.props.handle&&!(0,Vr.matchesSelectorAndParentsTo)(t.target,this.props.handle,n)||this.props.cancel&&(0,Vr.matchesSelectorAndParentsTo)(t.target,this.props.cancel,n))return;t.type==="touchstart"&&!this.props.allowMobileScroll&&t.preventDefault();const o=(0,Vr.getTouchIdentifier)(t);this.touchIdentifier=o;const i=(0,Bl.getControlPosition)(t,o,this);if(i==null)return;const{x:s,y:a}=i,l=(0,Bl.createCoreData)(this,s,a);(0,dp.default)("DraggableCore: handleDragStart: %j",l),(0,dp.default)("calling",this.props.onStart),!(this.props.onStart(t,l)===!1||this.mounted===!1)&&(this.props.enableUserSelectHack&&(0,Vr.addUserSelectStyles)(r),this.dragging=!0,this.lastX=s,this.lastY=a,(0,Vr.addEvent)(r,Wl.move,this.handleDrag),(0,Vr.addEvent)(r,Wl.stop,this.handleDragStop))}),co(this,"handleDrag",t=>{const n=(0,Bl.getControlPosition)(t,this.touchIdentifier,this);if(n==null)return;let{x:r,y:o}=n;if(Array.isArray(this.props.grid)){let a=r-this.lastX,l=o-this.lastY;if(a,l=(0,Bl.snapToGrid)(this.props.grid,a,l),!a&&!l)return;r=this.lastX+a,o=this.lastY+l}const i=(0,Bl.createCoreData)(this,r,o);if((0,dp.default)("DraggableCore: handleDrag: %j",i),this.props.onDrag(t,i)===!1||this.mounted===!1){try{this.handleDragStop(new MouseEvent("mouseup"))}catch{const l=document.createEvent("MouseEvents");l.initMouseEvent("mouseup",!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null),this.handleDragStop(l)}return}this.lastX=r,this.lastY=o}),co(this,"handleDragStop",t=>{if(!this.dragging)return;const n=(0,Bl.getControlPosition)(t,this.touchIdentifier,this);if(n==null)return;let{x:r,y:o}=n;if(Array.isArray(this.props.grid)){let l=r-this.lastX||0,c=o-this.lastY||0;l,c=(0,Bl.snapToGrid)(this.props.grid,l,c),r=this.lastX+l,o=this.lastY+c}const i=(0,Bl.createCoreData)(this,r,o);if(this.props.onStop(t,i)===!1||this.mounted===!1)return!1;const a=this.findDOMNode();a&&this.props.enableUserSelectHack&&(0,Vr.scheduleRemoveUserSelectStyles)(a.ownerDocument),(0,dp.default)("DraggableCore: handleDragStop: %j",i),this.dragging=!1,this.lastX=NaN,this.lastY=NaN,a&&((0,dp.default)("DraggableCore: Removing handlers"),(0,Vr.removeEvent)(a.ownerDocument,Wl.move,this.handleDrag),(0,Vr.removeEvent)(a.ownerDocument,Wl.stop,this.handleDragStop))}),co(this,"onMouseDown",t=>(Wl=Zi.mouse,this.handleDragStart(t))),co(this,"onMouseUp",t=>(Wl=Zi.mouse,this.handleDragStop(t))),co(this,"onTouchStart",t=>(Wl=Zi.touch,this.handleDragStart(t))),co(this,"onTouchEnd",t=>(Wl=Zi.touch,this.handleDragStop(t)))}componentDidMount(){this.mounted=!0;const t=this.findDOMNode();t&&(0,Vr.addEvent)(t,Zi.touch.start,this.onTouchStart,{passive:!1})}componentWillUnmount(){this.mounted=!1;const t=this.findDOMNode();if(t){const{ownerDocument:n}=t;(0,Vr.removeEvent)(n,Zi.mouse.move,this.handleDrag),(0,Vr.removeEvent)(n,Zi.touch.move,this.handleDrag),(0,Vr.removeEvent)(n,Zi.mouse.stop,this.handleDragStop),(0,Vr.removeEvent)(n,Zi.touch.stop,this.handleDragStop),(0,Vr.removeEvent)(t,Zi.touch.start,this.onTouchStart,{passive:!1}),this.props.enableUserSelectHack&&(0,Vr.scheduleRemoveUserSelectStyles)(n)}}findDOMNode(){var t,n,r;return(t=this.props)!=null&&t.nodeRef?(r=(n=this.props)==null?void 0:n.nodeRef)==null?void 0:r.current:qOe.default.findDOMNode(this)}render(){return sN.cloneElement(sN.Children.only(this.props.children),{onMouseDown:this.onMouseDown,onMouseUp:this.onMouseUp,onTouchEnd:this.onTouchEnd})}};uw.default=fw;co(fw,"displayName","DraggableCore");co(fw,"propTypes",{allowAnyClick:lo.default.bool,allowMobileScroll:lo.default.bool,children:lo.default.node.isRequired,disabled:lo.default.bool,enableUserSelectHack:lo.default.bool,offsetParent:function(e,t){if(et&&et.nodeType!==1)throw new Error("Draggable's offsetParent must be a DOM Node.")},grid:lo.default.arrayOf(lo.default.number),handle:lo.default.string,cancel:lo.default.string,nodeRef:lo.default.object,onStart:lo.default.func,onDrag:lo.default.func,onStop:lo.default.func,onMouseDown:lo.default.func,scale:lo.default.number,className:aN.dontSetMe,style:aN.dontSetMe,transform:aN.dontSetMe});co(fw,"defaultProps",{allowAnyClick:!1,allowMobileScroll:!1,disabled:!1,enableUserSelectHack:!0,onStart:function(){},onDrag:function(){},onStop:function(){},onMouseDown:function(){},scale:1});(function(e){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"DraggableCore",{enumerable:!0,get:function(){return l.default}}),e.default=void 0;var t=f(y),n=d(Pc),r=d(al),o=iw,i=Yn,s=ma,a=pa,l=d(uw),c=d(dw);function d(x){return x&&x.__esModule?x:{default:x}}function f(x,w){if(typeof WeakMap=="function")var C=new WeakMap,E=new WeakMap;return(f=function(R,P){if(!P&&R&&R.__esModule)return R;var N,k,I={__proto__:null,default:R};if(R===null||typeof R!="object"&&typeof R!="function")return I;if(N=P?E:C){if(N.has(R))return N.get(R);N.set(R,I)}for(const O in R)O!=="default"&&{}.hasOwnProperty.call(R,O)&&((k=(N=Object.defineProperty)&&Object.getOwnPropertyDescriptor(R,O))&&(k.get||k.set)?N(I,O,k):IO=RO);return I})(x,w)}function p(){return p=Object.assign?Object.assign.bind():function(x){for(var w=1;w<arguments.length;w++){var C=argumentsw;for(var E in C)({}).hasOwnProperty.call(C,E)&&(xE=CE)}return x},p.apply(null,arguments)}function g(x,w,C){return(w=v(w))in x?Object.defineProperty(x,w,{value:C,enumerable:!0,configurable:!0,writable:!0}):xw=C,x}function v(x){var w=b(x,"string");return typeof w=="symbol"?w:w+""}function b(x,w){if(typeof x!="object"||!x)return x;var C=xSymbol.toPrimitive;if(C!==void 0){var E=C.call(x,w);if(typeof E!="object")return E;throw new TypeError("@@toPrimitive must return a primitive value.")}return(w==="string"?String:Number)(x)}class _ extends t.Component{static getDerivedStateFromProps(w,C){let{position:E}=w,{prevPropsPosition:R}=C;return E&&(!R||E.x!==R.x||E.y!==R.y)?((0,c.default)("Draggable: getDerivedStateFromProps %j",{position:E,prevPropsPosition:R}),{x:E.x,y:E.y,prevPropsPosition:{...E}}):null}constructor(w){super(w),g(this,"onDragStart",(C,E)=>{if((0,c.default)("Draggable: onDragStart: %j",E),this.props.onStart(C,(0,s.createDraggableData)(this,E))===!1)return!1;this.setState({dragging:!0,dragged:!0})}),g(this,"onDrag",(C,E)=>{if(!this.state.dragging)return!1;(0,c.default)("Draggable: onDrag: %j",E);const R=(0,s.createDraggableData)(this,E),P={x:R.x,y:R.y,slackX:0,slackY:0};if(this.props.bounds){const{x:k,y:I}=P;P.x+=this.state.slackX,P.y+=this.state.slackY;constO,j=(0,s.getBoundPosition)(this,P.x,P.y);P.x=O,P.y=j,P.slackX=this.state.slackX+(k-P.x),P.slackY=this.state.slackY+(I-P.y),R.x=P.x,R.y=P.y,R.deltaX=P.x-this.state.x,R.deltaY=P.y-this.state.y}if(this.props.onDrag(C,R)===!1)return!1;this.setState(P)}),g(this,"onDragStop",(C,E)=>{if(!this.state.dragging||this.props.onStop(C,(0,s.createDraggableData)(this,E))===!1)return!1;(0,c.default)("Draggable: onDragStop: %j",E);const P={dragging:!1,slackX:0,slackY:0};if(!!this.props.position){const{x:k,y:I}=this.props.position;P.x=k,P.y=I}this.setState(P)}),this.state={dragging:!1,dragged:!1,x:w.position?w.position.x:w.defaultPosition.x,y:w.position?w.position.y:w.defaultPosition.y,prevPropsPosition:{...w.position},slackX:0,slackY:0,isElementSVG:!1},w.position&&!(w.onDrag||w.onStop)&&console.warn("A `position` was applied to this <Draggable>, without drag handlers. This will make this component effectively undraggable. Please attach `onDrag` or `onStop` handlers so you can adjust the `position` of this element.")}componentDidMount(){typeof window.SVGElement<"u"&&this.findDOMNode()instanceof window.SVGElement&&this.setState({isElementSVG:!0})}componentWillUnmount(){this.state.dragging&&this.setState({dragging:!1})}findDOMNode(){var w,C;return((C=(w=this.props)==null?void 0:w.nodeRef)==null?void 0:C.current)??r.default.findDOMNode(this)}render(){const{axis:w,bounds:C,children:E,defaultPosition:R,defaultClassName:P,defaultClassNameDragging:N,defaultClassNameDragged:k,position:I,positionOffset:O,scale:j,...H}=this.props;let q={},M=null;const $=!!!I||this.state.dragging,W=I||R,G={x:(0,s.canDragX)(this)&&$?this.state.x:W.x,y:(0,s.canDragY)(this)&&$?this.state.y:W.y};this.state.isElementSVG?M=(0,i.createSVGTransform)(G,O):q=(0,i.createCSSTransform)(G,O);const z=(0,o.clsx)(E.props.className||"",P,{N:this.state.dragging,k:this.state.dragged});return t.createElement(l.default,p({},H,{onStart:this.onDragStart,onDrag:this.onDrag,onStop:this.onDragStop}),t.cloneElement(t.Children.only(E),{className:z,style:{...E.props.style,...q},transform:M}))}}e.default=_,g(_,"displayName","Draggable"),g(_,"propTypes",{...l.default.propTypes,axis:n.default.oneOf("both","x","y","none"),bounds:n.default.oneOfType(n.default.shape({left:n.default.number,right:n.default.number,top:n.default.number,bottom:n.default.number}),n.default.string,n.default.oneOf(!1)),defaultClassName:n.default.string,defaultClassNameDragging:n.default.string,defaultClassNameDragged:n.default.string,defaultPosition:n.default.shape({x:n.default.number,y:n.default.number}),positionOffset:n.default.shape({x:n.default.oneOfType(n.default.number,n.default.string),y:n.default.oneOfType(n.default.number,n.default.string)}),position:n.default.shape({x:n.default.number,y:n.default.number}),className:a.dontSetMe,style:a.dontSetMe,transform:a.dontSetMe}),g(_,"defaultProps",{...l.default.defaultProps,axis:"both",bounds:!1,defaultClassName:"react-draggable",defaultClassNameDragging:"react-draggable-dragging",defaultClassNameDragged:"react-draggable-dragged",defaultPosition:{x:0,y:0},scale:1})})(qK);const{default:JK,DraggableCore:YOe}=qK;cw.exports=JK;cw.exports.default=JK;cw.exports.DraggableCore=YOe;var eX=cw.exports,hw={exports:{}},Eg={},fA={};fA.__esModule=!0;fA.cloneElement=eDe;var KOe=XOe(y);function XOe(e){return e&&e.__esModule?e:{default:e}}function Lz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function Oz(e){for(var t=1;t<arguments.length;t++){var n=argumentst!=null?argumentst:{};t%2?Lz(Object(n),!0).forEach(function(r){ZOe(e,r,nr)}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Lz(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function ZOe(e,t,n){return t=QOe(t),t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function QOe(e){var t=JOe(e,"string");return typeof t=="symbol"?t:String(t)}function JOe(e,t){if(typeof e!="object"||e===null)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function eDe(e,t){return t.style&&e.props.style&&(t.style=Oz(Oz({},e.props.style),t.style)),t.className&&e.props.className&&(t.className=e.props.className+" "+t.className),KOe.default.cloneElement(e,t)}var Ng={};Ng.__esModule=!0;Ng.resizableProps=void 0;var gt=tDe(Pc);function tDe(e){return e&&e.__esModule?e:{default:e}}var nDe={axis:gt.default.oneOf("both","x","y","none"),className:gt.default.string,children:gt.default.element.isRequired,draggableOpts:gt.default.shape({allowAnyClick:gt.default.bool,cancel:gt.default.string,children:gt.default.node,disabled:gt.default.bool,enableUserSelectHack:gt.default.bool,offsetParent:gt.default.node,grid:gt.default.arrayOf(gt.default.number),handle:gt.default.string,nodeRef:gt.default.object,onStart:gt.default.func,onDrag:gt.default.func,onStop:gt.default.func,onMouseDown:gt.default.func,scale:gt.default.number}),height:function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)nr=argumentsr;var o=n0;if(o.axis==="both"||o.axis==="y"){var i;return(i=gt.default.number).isRequired.apply(i,n)}return gt.default.number.apply(gt.default,n)},handle:gt.default.oneOfType(gt.default.node,gt.default.func),handleSize:gt.default.arrayOf(gt.default.number),lockAspectRatio:gt.default.bool,maxConstraints:gt.default.arrayOf(gt.default.number),minConstraints:gt.default.arrayOf(gt.default.number),onResizeStop:gt.default.func,onResizeStart:gt.default.func,onResize:gt.default.func,resizeHandles:gt.default.arrayOf(gt.default.oneOf("s","w","e","n","sw","nw","se","ne")),transformScale:gt.default.number,width:function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)nr=argumentsr;var o=n0;if(o.axis==="both"||o.axis==="x"){var i;return(i=gt.default.number).isRequired.apply(i,n)}return gt.default.number.apply(gt.default,n)}};Ng.resizableProps=nDe;Eg.__esModule=!0;Eg.default=void 0;var fp=aDe(y),rDe=eX,oDe=fA,iDe=Ng,sDe="children","className","draggableOpts","width","height","handle","handleSize","lockAspectRatio","axis","minConstraints","maxConstraints","onResize","onResizeStop","onResizeStart","resizeHandles","transformScale";function tX(e){if(typeof WeakMap!="function")return null;var t=new WeakMap,n=new WeakMap;return(tX=function(o){return o?n:t})(e)}function aDe(e,t){if(e&&e.__esModule)return e;if(e===null||typeof e!="object"&&typeof e!="function")return{default:e};var n=tX(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(i!=="default"&&Object.prototype.hasOwnProperty.call(e,i)){var s=o?Object.getOwnPropertyDescriptor(e,i):null;s&&(s.get||s.set)?Object.defineProperty(r,i,s):ri=ei}return r.default=e,n&&n.set(e,r),r}function yT(){return yT=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=argumentst;for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(er=nr)}return e},yT.apply(this,arguments)}function lDe(e,t){if(e==null)return{};var n={},r=Object.keys(e),o,i;for(i=0;i<r.length;i++)o=ri,!(t.indexOf(o)>=0)&&(no=eo);return n}function Dz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function lN(e){for(var t=1;t<arguments.length;t++){var n=argumentst!=null?argumentst:{};t%2?Dz(Object(n),!0).forEach(function(r){cDe(e,r,nr)}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Dz(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function cDe(e,t,n){return t=uDe(t),t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function uDe(e){var t=dDe(e,"string");return typeof t=="symbol"?t:String(t)}function dDe(e,t){if(typeof e!="object"||e===null)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function fDe(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,xT(e,t)}function xT(e,t){return xT=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(r,o){return r.__proto__=o,r},xT(e,t)}var hA=function(e){fDe(t,e);function t(){for(var r,o=arguments.length,i=new Array(o),s=0;s<o;s++)is=argumentss;return r=e.call.apply(e,this.concat(i))||this,r.handleRefs={},r.lastHandleRect=null,r.slack=null,r}var n=t.prototype;return n.componentWillUnmount=function(){this.resetData()},n.resetData=function(){this.lastHandleRect=this.slack=null},n.runConstraints=function(o,i){var s=this.props,a=s.minConstraints,l=s.maxConstraints,c=s.lockAspectRatio;if(!a&&!l&&!c)returno,i;if(c){var d=this.props.width/this.props.height,f=o-this.props.width,p=i-this.props.height;Math.abs(f)>Math.abs(p*d)?i=o/d:o=i*d}var g=o,v=i,b=this.slack||0,0,_=b0,x=b1;return o+=_,i+=x,a&&(o=Math.max(a0,o),i=Math.max(a1,i)),l&&(o=Math.min(l0,o),i=Math.min(l1,i)),this.slack=_+(g-o),x+(v-i),o,i},n.resizeHandler=function(o,i){var s=this;return function(a,l){var c=l.node,d=l.deltaX,f=l.deltaY;o==="onResizeStart"&&s.resetData();var p=(s.props.axis==="both"||s.props.axis==="x")&&i!=="n"&&i!=="s",g=(s.props.axis==="both"||s.props.axis==="y")&&i!=="e"&&i!=="w";if(!(!p&&!g)){var v=i0,b=ii.length-1,_=c.getBoundingClientRect();if(s.lastHandleRect!=null){if(b==="w"){var x=_.left-s.lastHandleRect.left;d+=x}if(v==="n"){var w=_.top-s.lastHandleRect.top;f+=w}}s.lastHandleRect=_,b==="w"&&(d=-d),v==="n"&&(f=-f);var C=s.props.width+(p?d/s.props.transformScale:0),E=s.props.height+(g?f/s.props.transformScale:0),R=s.runConstraints(C,E);C=R0,E=R1;var P=C!==s.props.width||E!==s.props.height,N=typeof s.propso=="function"?s.propso:null,k=o==="onResize"&&!P;N&&!k&&(a.persist==null||a.persist(),N(a,{node:c,size:{width:C,height:E},handle:i})),o==="onResizeStop"&&s.resetData()}}},n.renderResizeHandle=function(o,i){var s=this.props.handle;if(!s)return fp.createElement("span",{className:"react-resizable-handle react-resizable-handle-"+o,ref:i});if(typeof s=="function")return s(o,i);var a=typeof s.type=="string",l=lN({ref:i},a?{}:{handleAxis:o});return fp.cloneElement(s,l)},n.render=function(){var o=this,i=this.props,s=i.children,a=i.className,l=i.draggableOpts;i.width,i.height,i.handle,i.handleSize,i.lockAspectRatio,i.axis,i.minConstraints,i.maxConstraints,i.onResize,i.onResizeStop,i.onResizeStart;var c=i.resizeHandles;i.transformScale;var d=lDe(i,sDe);return(0,oDe.cloneElement)(s,lN(lN({},d),{},{className:(a?a+" ":"")+"react-resizable",children:.concat(s.props.children,c.map(function(f){var p,g=(p=o.handleRefsf)!=null?p:o.handleRefsf=fp.createRef();return fp.createElement(rDe.DraggableCore,yT({},l,{nodeRef:g,key:"resizableHandle-"+f,onStop:o.resizeHandler("onResizeStop",f),onStart:o.resizeHandler("onResizeStart",f),onDrag:o.resizeHandler("onResize",f)}),o.renderResizeHandle(f,g))}))}))},t}(fp.Component);Eg.default=hA;hA.propTypes=iDe.resizableProps;hA.defaultProps={axis:"both",handleSize:20,20,lockAspectRatio:!1,minConstraints:20,20,maxConstraints:1/0,1/0,resizeHandles:"se",transformScale:1};var pw={};pw.__esModule=!0;pw.default=void 0;var cN=vDe(y),hDe=nX(Pc),pDe=nX(Eg),mDe=Ng,gDe="handle","handleSize","onResize","onResizeStart","onResizeStop","draggableOpts","minConstraints","maxConstraints","lockAspectRatio","axis","width","height","resizeHandles","style","transformScale";function nX(e){return e&&e.__esModule?e:{default:e}}function rX(e){if(typeof WeakMap!="function")return null;var t=new WeakMap,n=new WeakMap;return(rX=function(o){return o?n:t})(e)}function vDe(e,t){if(e&&e.__esModule)return e;if(e===null||typeof e!="object"&&typeof e!="function")return{default:e};var n=rX(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(i!=="default"&&Object.prototype.hasOwnProperty.call(e,i)){var s=o?Object.getOwnPropertyDescriptor(e,i):null;s&&(s.get||s.set)?Object.defineProperty(r,i,s):ri=ei}return r.default=e,n&&n.set(e,r),r}function bT(){return bT=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=argumentst;for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(er=nr)}return e},bT.apply(this,arguments)}function Fz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function sx(e){for(var t=1;t<arguments.length;t++){var n=argumentst!=null?argumentst:{};t%2?Fz(Object(n),!0).forEach(function(r){yDe(e,r,nr)}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Fz(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function yDe(e,t,n){return t=xDe(t),t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function xDe(e){var t=bDe(e,"string");return typeof t=="symbol"?t:String(t)}function bDe(e,t){if(typeof e!="object"||e===null)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function wDe(e,t){if(e==null)return{};var n={},r=Object.keys(e),o,i;for(i=0;i<r.length;i++)o=ri,!(t.indexOf(o)>=0)&&(no=eo);return n}function SDe(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,wT(e,t)}function wT(e,t){return wT=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(r,o){return r.__proto__=o,r},wT(e,t)}var oX=function(e){SDe(t,e);function t(){for(var r,o=arguments.length,i=new Array(o),s=0;s<o;s++)is=argumentss;return r=e.call.apply(e,this.concat(i))||this,r.state={width:r.props.width,height:r.props.height,propsWidth:r.props.width,propsHeight:r.props.height},r.onResize=function(a,l){var c=l.size;r.props.onResize?(a.persist==null||a.persist(),r.setState(c,function(){return r.props.onResize&&r.props.onResize(a,l)})):r.setState(c)},r}t.getDerivedStateFromProps=function(o,i){return i.propsWidth!==o.width||i.propsHeight!==o.height?{width:o.width,height:o.height,propsWidth:o.width,propsHeight:o.height}:null};var n=t.prototype;return n.render=function(){var o=this.props,i=o.handle,s=o.handleSize;o.onResize;var a=o.onResizeStart,l=o.onResizeStop,c=o.draggableOpts,d=o.minConstraints,f=o.maxConstraints,p=o.lockAspectRatio,g=o.axis;o.width,o.height;var v=o.resizeHandles,b=o.style,_=o.transformScale,x=wDe(o,gDe);return cN.createElement(pDe.default,{axis:g,draggableOpts:c,handle:i,handleSize:s,height:this.state.height,lockAspectRatio:p,maxConstraints:f,minConstraints:d,onResizeStart:a,onResize:this.onResize,onResizeStop:l,resizeHandles:v,transformScale:_,width:this.state.width},cN.createElement("div",bT({},x,{style:sx(sx({},b),{},{width:this.state.width+"px",height:this.state.height+"px"})})))},t}(cN.Component);pw.default=oX;oX.propTypes=sx(sx({},mDe.resizableProps),{},{children:hDe.default.element});hw.exports=function(){throw new Error("Don't instantiate Resizable directly! Use require('react-resizable').Resizable")};hw.exports.Resizable=Eg.default;hw.exports.ResizableBox=pw.default;var _De=hw.exports,Ka={};Object.defineProperty(Ka,"__esModule",{value:!0});Ka.resizeHandleType=Ka.resizeHandleAxesType=Ka.default=void 0;var Ct=iX(Pc),CDe=iX(y);function iX(e){return e&&e.__esModule?e:{default:e}}const EDe=Ka.resizeHandleAxesType=Ct.default.arrayOf(Ct.default.oneOf("s","w","e","n","sw","nw","se","ne")),NDe=Ka.resizeHandleType=Ct.default.oneOfType(Ct.default.node,Ct.default.func);Ka.default={className:Ct.default.string,style:Ct.default.object,width:Ct.default.number,autoSize:Ct.default.bool,cols:Ct.default.number,draggableCancel:Ct.default.string,draggableHandle:Ct.default.string,verticalCompact:function(e){e.verticalCompact},compactType:Ct.default.oneOf("vertical","horizontal"),layout:function(e){var t=e.layout;t!==void 0&&Mt.validateLayout(t,"layout")},margin:Ct.default.arrayOf(Ct.default.number),containerPadding:Ct.default.arrayOf(Ct.default.number),rowHeight:Ct.default.number,maxRows:Ct.default.number,isBounded:Ct.default.bool,isDraggable:Ct.default.bool,isResizable:Ct.default.bool,allowOverlap:Ct.default.bool,preventCollision:Ct.default.bool,useCSSTransforms:Ct.default.bool,transformScale:Ct.default.number,isDroppable:Ct.default.bool,resizeHandles:EDe,resizeHandle:NDe,onLayoutChange:Ct.default.func,onDragStart:Ct.default.func,onDrag:Ct.default.func,onDragStop:Ct.default.func,onResizeStart:Ct.default.func,onResize:Ct.default.func,onResizeStop:Ct.default.func,onDrop:Ct.default.func,droppingItem:Ct.default.shape({i:Ct.default.string.isRequired,w:Ct.default.number.isRequired,h:Ct.default.number.isRequired}),children:function(e,t){const n=et,r={};CDe.default.Children.forEach(n,function(o){if((o==null?void 0:o.key)!=null){if(ro.key)throw new Error('Duplicate child key "'+o.key+'" found! This will cause problems in ReactGridLayout.');ro.key=!0}})},innerRef:Ct.default.any};Object.defineProperty(lw,"__esModule",{value:!0});lw.default=void 0;var Id=pA(y),$z=al,Xt=pA(Pc),RDe=eX,TDe=_De,Ad=Mt,Ir=ha,zz=Ka,kDe=pA(iw);function pA(e){return e&&e.__esModule?e:{default:e}}function qs(e,t,n){return(t=PDe(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function PDe(e){var t=IDe(e,"string");return typeof t=="symbol"?t:t+""}function IDe(e,t){if(typeof e!="object"||!e)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}class mA extends Id.default.Component{constructor(){super(...arguments),qs(this,"state",{resizing:null,dragging:null,className:""}),qs(this,"elementRef",Id.default.createRef()),qs(this,"onDragStart",(t,n)=>{let{node:r}=n;const{onDragStart:o,transformScale:i}=this.props;if(!o)return;const s={top:0,left:0},{offsetParent:a}=r;if(!a)return;const l=a.getBoundingClientRect(),c=r.getBoundingClientRect(),d=c.left/i,f=l.left/i,p=c.top/i,g=l.top/i;s.left=d-f+a.scrollLeft,s.top=p-g+a.scrollTop,this.setState({dragging:s});const{x:v,y:b}=(0,Ir.calcXY)(this.getPositionParams(),s.top,s.left,this.props.w,this.props.h);return o.call(this,this.props.i,v,b,{e:t,node:r,newPosition:s})}),qs(this,"onDrag",(t,n,r)=>{let{node:o,deltaX:i,deltaY:s}=n;const{onDrag:a}=this.props;if(!a)return;if(!this.state.dragging)throw new Error("onDrag called before onDragStart.");let l=this.state.dragging.top+s,c=this.state.dragging.left+i;const{isBounded:d,i:f,w:p,h:g,containerWidth:v}=this.props,b=this.getPositionParams();if(d){const{offsetParent:C}=o;if(C){const{margin:E,rowHeight:R}=this.props,P=C.clientHeight-(0,Ir.calcGridItemWHPx)(g,R,E1);l=(0,Ir.clamp)(l,0,P);const N=(0,Ir.calcGridColWidth)(b),k=v-(0,Ir.calcGridItemWHPx)(p,N,E0);c=(0,Ir.clamp)(c,0,k)}}const _={top:l,left:c};r?this.setState({dragging:_}):(0,$z.flushSync)(()=>{this.setState({dragging:_})});const{x,y:w}=(0,Ir.calcXY)(b,l,c,p,g);return a.call(this,f,x,w,{e:t,node:o,newPosition:_})}),qs(this,"onDragStop",(t,n)=>{let{node:r}=n;const{onDragStop:o}=this.props;if(!o)return;if(!this.state.dragging)throw new Error("onDragEnd called before onDragStart.");const{w:i,h:s,i:a}=this.props,{left:l,top:c}=this.state.dragging,d={top:c,left:l};this.setState({dragging:null});const{x:f,y:p}=(0,Ir.calcXY)(this.getPositionParams(),c,l,i,s);return o.call(this,a,f,p,{e:t,node:r,newPosition:d})}),qs(this,"onResizeStop",(t,n,r)=>this.onResizeHandler(t,n,r,"onResizeStop")),qs(this,"onResizeStart",(t,n,r)=>this.onResizeHandler(t,n,r,"onResizeStart")),qs(this,"onResize",(t,n,r)=>this.onResizeHandler(t,n,r,"onResize"))}shouldComponentUpdate(t,n){if(this.props.children!==t.children||this.props.droppingPosition!==t.droppingPosition)return!0;const r=(0,Ir.calcGridItemPosition)(this.getPositionParams(this.props),this.props.x,this.props.y,this.props.w,this.props.h,this.state),o=(0,Ir.calcGridItemPosition)(this.getPositionParams(t),t.x,t.y,t.w,t.h,n);return!(0,Ad.fastPositionEqual)(r,o)||this.props.useCSSTransforms!==t.useCSSTransforms}componentDidMount(){this.moveDroppingItem({})}componentDidUpdate(t){this.moveDroppingItem(t)}moveDroppingItem(t){const{droppingPosition:n}=this.props;if(!n)return;const r=this.elementRef.current;if(!r)return;const o=t.droppingPosition||{left:0,top:0},{dragging:i}=this.state,s=i&&n.left!==o.left||n.top!==o.top;if(!i)this.onDragStart(n.e,{node:r,deltaX:n.left,deltaY:n.top});else if(s){const a=n.left-i.left,l=n.top-i.top;this.onDrag(n.e,{node:r,deltaX:a,deltaY:l},!0)}}getPositionParams(){let t=arguments.length>0&&arguments0!==void 0?arguments0:this.props;return{cols:t.cols,containerPadding:t.containerPadding,containerWidth:t.containerWidth,margin:t.margin,maxRows:t.maxRows,rowHeight:t.rowHeight}}createStyle(t){const{usePercentages:n,containerWidth:r,useCSSTransforms:o}=this.props;let i;return o?i=(0,Ad.setTransform)(t):(i=(0,Ad.setTopLeft)(t),n&&(i.left=(0,Ad.perc)(t.left/r),i.width=(0,Ad.perc)(t.width/r))),i}mixinDraggable(t,n){return Id.default.createElement(RDe.DraggableCore,{disabled:!n,onStart:this.onDragStart,onDrag:this.onDrag,onStop:this.onDragStop,handle:this.props.handle,cancel:".react-resizable-handle"+(this.props.cancel?","+this.props.cancel:""),scale:this.props.transformScale,nodeRef:this.elementRef},t)}curryResizeHandler(t,n){return(r,o)=>n(r,o,t)}mixinResizable(t,n,r){const{cols:o,minW:i,minH:s,maxW:a,maxH:l,transformScale:c,resizeHandles:d,resizeHandle:f}=this.props,p=this.getPositionParams(),g=(0,Ir.calcGridItemPosition)(p,0,0,o,0).width,v=(0,Ir.calcGridItemPosition)(p,0,0,i,s),b=(0,Ir.calcGridItemPosition)(p,0,0,a,l),_=v.width,v.height,x=Math.min(b.width,g),Math.min(b.height,1/0);return Id.default.createElement(TDe.Resizable,{draggableOpts:{disabled:!r},className:r?void 0:"react-resizable-hide",width:n.width,height:n.height,minConstraints:_,maxConstraints:x,onResizeStop:this.curryResizeHandler(n,this.onResizeStop),onResizeStart:this.curryResizeHandler(n,this.onResizeStart),onResize:this.curryResizeHandler(n,this.onResize),transformScale:c,resizeHandles:d,handle:f},t)}onResizeHandler(t,n,r,o){let{node:i,size:s,handle:a}=n;const l=this.propso;if(!l)return;const{x:c,y:d,i:f,maxH:p,minH:g,containerWidth:v}=this.props,{minW:b,maxW:_}=this.props;let x=s;i&&(x=(0,Ad.resizeItemInDirection)(a,r,s,v),(0,$z.flushSync)(()=>{this.setState({resizing:o==="onResizeStop"?null:x})}));let{w,h:C}=(0,Ir.calcWH)(this.getPositionParams(),x.width,x.height,c,d,a);w=(0,Ir.clamp)(w,Math.max(b,1),_),C=(0,Ir.clamp)(C,g,p),l.call(this,f,w,C,{e:t,node:i,size:x,handle:a})}render(){const{x:t,y:n,w:r,h:o,isDraggable:i,isResizable:s,droppingPosition:a,useCSSTransforms:l}=this.props,c=(0,Ir.calcGridItemPosition)(this.getPositionParams(),t,n,r,o,this.state),d=Id.default.Children.only(this.props.children);let f=Id.default.cloneElement(d,{ref:this.elementRef,className:(0,kDe.default)("react-grid-item",d.props.className,this.props.className,{static:this.props.static,resizing:!!this.state.resizing,"react-draggable":i,"react-draggable-dragging":!!this.state.dragging,dropping:!!a,cssTransforms:l}),style:{...this.props.style,...d.props.style,...this.createStyle(c)}});return f=this.mixinResizable(f,c,s),f=this.mixinDraggable(f,i),f}}lw.default=mA;qs(mA,"propTypes",{children:Xt.default.element,cols:Xt.default.number.isRequired,containerWidth:Xt.default.number.isRequired,rowHeight:Xt.default.number.isRequired,margin:Xt.default.array.isRequired,maxRows:Xt.default.number.isRequired,containerPadding:Xt.default.array.isRequired,x:Xt.default.number.isRequired,y:Xt.default.number.isRequired,w:Xt.default.number.isRequired,h:Xt.default.number.isRequired,minW:function(e,t){const n=et;if(typeof n!="number")return new Error("minWidth not Number");if(n>e.w||n>e.maxW)return new Error("minWidth larger than item width/maxWidth")},maxW:function(e,t){const n=et;if(typeof n!="number")return new Error("maxWidth not Number");if(n<e.w||n<e.minW)return new Error("maxWidth smaller than item width/minWidth")},minH:function(e,t){const n=et;if(typeof n!="number")return new Error("minHeight not Number");if(n>e.h||n>e.maxH)return new Error("minHeight larger than item height/maxHeight")},maxH:function(e,t){const n=et;if(typeof n!="number")return new Error("maxHeight not Number");if(n<e.h||n<e.minH)return new Error("maxHeight smaller than item height/minHeight")},i:Xt.default.string.isRequired,resizeHandles:zz.resizeHandleAxesType,resizeHandle:zz.resizeHandleType,onDragStop:Xt.default.func,onDragStart:Xt.default.func,onDrag:Xt.default.func,onResizeStop:Xt.default.func,onResizeStart:Xt.default.func,onResize:Xt.default.func,isDraggable:Xt.default.bool.isRequired,isResizable:Xt.default.bool.isRequired,isBounded:Xt.default.bool.isRequired,static:Xt.default.bool,useCSSTransforms:Xt.default.bool.isRequired,transformScale:Xt.default.number,className:Xt.default.string,handle:Xt.default.string,cancel:Xt.default.string,droppingPosition:Xt.default.shape({e:Xt.default.object.isRequired,left:Xt.default.number.isRequired,top:Xt.default.number.isRequired})});qs(mA,"defaultProps",{className:"",cancel:"",handle:"",minH:1,minW:1,maxH:1/0,maxW:1/0,transformScale:1});Object.defineProperty(Cg,"__esModule",{value:!0});Cg.default=void 0;var eu=sX(y),uN=JI,ADe=gA(iw),at=Mt,MDe=ha,Hz=gA(lw),jDe=gA(Ka);function gA(e){return e&&e.__esModule?e:{default:e}}function sX(e,t){if(typeof WeakMap=="function")var n=new WeakMap,r=new WeakMap;return(sX=function(o,i){if(!i&&o&&o.__esModule)return o;var s,a,l={__proto__:null,default:o};if(o===null||typeof o!="object"&&typeof o!="function")return l;if(s=i?r:n){if(s.has(o))return s.get(o);s.set(o,l)}for(const c in o)c!=="default"&&{}.hasOwnProperty.call(o,c)&&((a=(s=Object.defineProperty)&&Object.getOwnPropertyDescriptor(o,c))&&(a.get||a.set)?s(l,c,a):lc=oc);return l})(e,t)}function Yr(e,t,n){return(t=LDe(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function LDe(e){var t=ODe(e,"string");return typeof t=="symbol"?t:t+""}function ODe(e,t){if(typeof e!="object"||!e)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}const Bz="react-grid-layout";let aX=!1;try{aX=/firefox/i.test(navigator.userAgent)}catch{}class mw extends eu.Component{constructor(){super(...arguments),Yr(this,"state",{activeDrag:null,layout:(0,at.synchronizeLayoutWithChildren)(this.props.layout,this.props.children,this.props.cols,(0,at.compactType)(this.props),this.props.allowOverlap),mounted:!1,oldDragItem:null,oldLayout:null,oldResizeItem:null,resizing:!1,droppingDOMNode:null,children:}),Yr(this,"dragEnterCounter",0),Yr(this,"onDragStart",(t,n,r,o)=>{let{e:i,node:s}=o;const{layout:a}=this.state,l=(0,at.getLayoutItem)(a,t);if(!l)return;const c={w:l.w,h:l.h,x:l.x,y:l.y,placeholder:!0,i:t};return this.setState({oldDragItem:(0,at.cloneLayoutItem)(l),oldLayout:a,activeDrag:c}),this.props.onDragStart(a,l,l,null,i,s)}),Yr(this,"onDrag",(t,n,r,o)=>{let{e:i,node:s}=o;const{oldDragItem:a}=this.state;let{layout:l}=this.state;const{cols:c,allowOverlap:d,preventCollision:f}=this.props,p=(0,at.getLayoutItem)(l,t);if(!p)return;const g={w:p.w,h:p.h,x:p.x,y:p.y,placeholder:!0,i:t};l=(0,at.moveElement)(l,p,n,r,!0,f,(0,at.compactType)(this.props),c,d),this.props.onDrag(l,a,p,g,i,s),this.setState({layout:d?l:(0,at.compact)(l,(0,at.compactType)(this.props),c),activeDrag:g})}),Yr(this,"onDragStop",(t,n,r,o)=>{let{e:i,node:s}=o;if(!this.state.activeDrag)return;const{oldDragItem:a}=this.state;let{layout:l}=this.state;const{cols:c,preventCollision:d,allowOverlap:f}=this.props,p=(0,at.getLayoutItem)(l,t);if(!p)return;l=(0,at.moveElement)(l,p,n,r,!0,d,(0,at.compactType)(this.props),c,f);const v=f?l:(0,at.compact)(l,(0,at.compactType)(this.props),c);this.props.onDragStop(v,a,p,null,i,s);const{oldLayout:b}=this.state;this.setState({activeDrag:null,layout:v,oldDragItem:null,oldLayout:null}),this.onLayoutMaybeChanged(v,b)}),Yr(this,"onResizeStart",(t,n,r,o)=>{let{e:i,node:s}=o;const{layout:a}=this.state,l=(0,at.getLayoutItem)(a,t);l&&(this.setState({oldResizeItem:(0,at.cloneLayoutItem)(l),oldLayout:this.state.layout,resizing:!0}),this.props.onResizeStart(a,l,l,null,i,s))}),Yr(this,"onResize",(t,n,r,o)=>{let{e:i,node:s,size:a,handle:l}=o;const{oldResizeItem:c}=this.state,{layout:d}=this.state,{cols:f,preventCollision:p,allowOverlap:g}=this.props;let v=!1,b,_,x;constw,C=(0,at.withLayoutItem)(d,t,R=>{let P;return _=R.x,x=R.y,"sw","w","nw","n","ne".indexOf(l)!==-1&&("sw","nw","w".indexOf(l)!==-1&&(_=R.x+(R.w-n),n=R.x!==_&&_<0?R.w:n,_=_<0?0:_),"ne","n","nw".indexOf(l)!==-1&&(x=R.y+(R.h-r),r=R.y!==x&&x<0?R.h:r,x=x<0?0:x),v=!0),p&&!g&&(P=(0,at.getAllCollisions)(d,{...R,w:n,h:r,x:_,y:x}).filter(k=>k.i!==R.i).length>0,P&&(x=R.y,r=R.h,_=R.x,n=R.w,v=!1)),R.w=n,R.h=r,R});if(!C)return;b=w,v&&(b=(0,at.moveElement)(w,C,_,x,!0,this.props.preventCollision,(0,at.compactType)(this.props),f,g));const E={w:C.w,h:C.h,x:C.x,y:C.y,static:!0,i:t};this.props.onResize(b,c,C,E,i,s),this.setState({layout:g?b:(0,at.compact)(b,(0,at.compactType)(this.props),f),activeDrag:E})}),Yr(this,"onResizeStop",(t,n,r,o)=>{let{e:i,node:s}=o;const{layout:a,oldResizeItem:l}=this.state,{cols:c,allowOverlap:d}=this.props,f=(0,at.getLayoutItem)(a,t),p=d?a:(0,at.compact)(a,(0,at.compactType)(this.props),c);this.props.onResizeStop(p,l,f,null,i,s);const{oldLayout:g}=this.state;this.setState({activeDrag:null,layout:p,oldResizeItem:null,oldLayout:null,resizing:!1}),this.onLayoutMaybeChanged(p,g)}),Yr(this,"onDragOver",t=>{var w;if(t.preventDefault(),t.stopPropagation(),aX&&!((w=t.nativeEvent.target)!=null&&w.classList.contains(Bz)))return!1;const{droppingItem:n,onDropDragOver:r,margin:o,cols:i,rowHeight:s,maxRows:a,width:l,containerPadding:c,transformScale:d}=this.props,f=r==null?void 0:r(t);if(f===!1)return this.state.droppingDOMNode&&this.removeDroppingPlaceholder(),!1;const p={...n,...f},{layout:g}=this.state,v=t.currentTarget.getBoundingClientRect(),b=t.clientX-v.left,_=t.clientY-v.top,x={left:b/d,top:_/d,e:t};if(this.state.droppingDOMNode){if(this.state.droppingPosition){const{left:C,top:E}=this.state.droppingPosition;(C!=b||E!=_)&&this.setState({droppingPosition:x})}}else{const C={cols:i,margin:o,maxRows:a,rowHeight:s,containerWidth:l,containerPadding:c||o},E=(0,MDe.calcXY)(C,_,b,p.w,p.h);this.setState({droppingDOMNode:eu.createElement("div",{key:p.i}),droppingPosition:x,layout:...g,{...p,x:E.x,y:E.y,static:!1,isDraggable:!0}})}}),Yr(this,"removeDroppingPlaceholder",()=>{const{droppingItem:t,cols:n}=this.props,{layout:r}=this.state,o=(0,at.compact)(r.filter(i=>i.i!==t.i),(0,at.compactType)(this.props),n,this.props.allowOverlap);this.setState({layout:o,droppingDOMNode:null,activeDrag:null,droppingPosition:void 0})}),Yr(this,"onDragLeave",t=>{t.preventDefault(),t.stopPropagation(),this.dragEnterCounter--,this.dragEnterCounter===0&&this.removeDroppingPlaceholder()}),Yr(this,"onDragEnter",t=>{t.preventDefault(),t.stopPropagation(),this.dragEnterCounter++}),Yr(this,"onDrop",t=>{t.preventDefault(),t.stopPropagation();const{droppingItem:n}=this.props,{layout:r}=this.state,o=r.find(i=>i.i===n.i);this.dragEnterCounter=0,this.removeDroppingPlaceholder(),this.props.onDrop(r,o,t)})}componentDidMount(){this.setState({mounted:!0}),this.onLayoutMaybeChanged(this.state.layout,this.props.layout)}static getDerivedStateFromProps(t,n){let r;return n.activeDrag?null:(!(0,uN.deepEqual)(t.layout,n.propsLayout)||t.compactType!==n.compactType?r=t.layout:(0,at.childrenEqual)(t.children,n.children)||(r=n.layout),r?{layout:(0,at.synchronizeLayoutWithChildren)(r,t.children,t.cols,(0,at.compactType)(t),t.allowOverlap),compactType:t.compactType,children:t.children,propsLayout:t.layout}:null)}shouldComponentUpdate(t,n){return this.props.children!==t.children||!(0,at.fastRGLPropsEqual)(this.props,t,uN.deepEqual)||this.state.activeDrag!==n.activeDrag||this.state.mounted!==n.mounted||this.state.droppingPosition!==n.droppingPosition}componentDidUpdate(t,n){if(!this.state.activeDrag){const r=this.state.layout,o=n.layout;this.onLayoutMaybeChanged(r,o)}}containerHeight(){if(!this.props.autoSize)return;const t=(0,at.bottom)(this.state.layout),n=this.props.containerPadding?this.props.containerPadding1:this.props.margin1;return t*this.props.rowHeight+(t-1)*this.props.margin1+n*2+"px"}onLayoutMaybeChanged(t,n){n||(n=this.state.layout),(0,uN.deepEqual)(n,t)||this.props.onLayoutChange(t)}placeholder(){const{activeDrag:t}=this.state;if(!t)return null;const{width:n,cols:r,margin:o,containerPadding:i,rowHeight:s,maxRows:a,useCSSTransforms:l,transformScale:c}=this.props;return eu.createElement(Hz.default,{w:t.w,h:t.h,x:t.x,y:t.y,i:t.i,className:`react-grid-placeholder ${this.state.resizing?"placeholder-resizing":""}`,containerWidth:n,cols:r,margin:o,containerPadding:i||o,maxRows:a,rowHeight:s,isDraggable:!1,isResizable:!1,isBounded:!1,useCSSTransforms:l,transformScale:c},eu.createElement("div",null))}processGridItem(t,n){if(!t||!t.key)return;const r=(0,at.getLayoutItem)(this.state.layout,String(t.key));if(!r)return null;const{width:o,cols:i,margin:s,containerPadding:a,rowHeight:l,maxRows:c,isDraggable:d,isResizable:f,isBounded:p,useCSSTransforms:g,transformScale:v,draggableCancel:b,draggableHandle:_,resizeHandles:x,resizeHandle:w}=this.props,{mounted:C,droppingPosition:E}=this.state,R=typeof r.isDraggable=="boolean"?r.isDraggable:!r.static&&d,P=typeof r.isResizable=="boolean"?r.isResizable:!r.static&&f,N=r.resizeHandles||x,k=R&&p&&r.isBounded!==!1;return eu.createElement(Hz.default,{containerWidth:o,cols:i,margin:s,containerPadding:a||s,maxRows:c,rowHeight:l,cancel:b,handle:_,onDragStop:this.onDragStop,onDragStart:this.onDragStart,onDrag:this.onDrag,onResizeStart:this.onResizeStart,onResize:this.onResize,onResizeStop:this.onResizeStop,isDraggable:R,isResizable:P,isBounded:k,useCSSTransforms:g&&C,usePercentages:!C,transformScale:v,w:r.w,h:r.h,x:r.x,y:r.y,i:r.i,minH:r.minH,minW:r.minW,maxH:r.maxH,maxW:r.maxW,static:r.static,droppingPosition:n?E:void 0,resizeHandles:N,resizeHandle:w},t)}render(){const{className:t,style:n,isDroppable:r,innerRef:o}=this.props,i=(0,ADe.default)(Bz,t),s={height:this.containerHeight(),...n};return eu.createElement("div",{ref:o,className:i,style:s,onDrop:r?this.onDrop:at.noop,onDragLeave:r?this.onDragLeave:at.noop,onDragEnter:r?this.onDragEnter:at.noop,onDragOver:r?this.onDragOver:at.noop},eu.Children.map(this.props.children,a=>this.processGridItem(a)),r&&this.state.droppingDOMNode&&this.processGridItem(this.state.droppingDOMNode,!0),this.placeholder())}}Cg.default=mw;Yr(mw,"displayName","ReactGridLayout");Yr(mw,"propTypes",jDe.default);Yr(mw,"defaultProps",{autoSize:!0,cols:12,className:"",style:{},draggableHandle:"",draggableCancel:"",containerPadding:null,rowHeight:150,maxRows:1/0,layout:,margin:10,10,isBounded:!1,isDraggable:!0,isResizable:!0,allowOverlap:!1,isDroppable:!1,useCSSTransforms:!0,transformScale:1,verticalCompact:!0,compactType:"vertical",preventCollision:!1,droppingItem:{i:"__dropping-elem__",h:1,w:1},resizeHandles:"se",onLayoutChange:at.noop,onDragStart:at.noop,onDrag:at.noop,onDragStop:at.noop,onResizeStart:at.noop,onResize:at.noop,onResizeStop:at.noop,onDrop:at.noop,onDropDragOver:at.noop});var gw={},td={};Object.defineProperty(td,"__esModule",{value:!0});td.findOrGenerateResponsiveLayout=$De;td.getBreakpointFromWidth=DDe;td.getColsFromBreakpoint=FDe;td.sortBreakpoints=vA;var d0=Mt;function DDe(e,t){const n=vA(e);let r=n0;for(let o=1,i=n.length;o<i;o++){const s=no;t>es&&(r=s)}return r}function FDe(e,t){if(!te)throw new Error("ResponsiveReactGridLayout: `cols` entry for breakpoint "+e+" is missing!");return te}function $De(e,t,n,r,o,i){if(en)return(0,d0.cloneLayout)(en);let s=er;const a=vA(t),l=a.slice(a.indexOf(n));for(let c=0,d=l.length;c<d;c++){const f=lc;if(ef){s=ef;break}}return s=(0,d0.cloneLayout)(s||),(0,d0.compact)((0,d0.correctBounds)(s,{cols:o}),i,o)}function vA(e){return Object.keys(e).sort(function(n,r){return en-er})}Object.defineProperty(gw,"__esModule",{value:!0});gw.default=void 0;var Wz=cX(y),Co=lX(Pc),dN=JI,mf=Mt,tu=td,zDe=lX(Cg);function lX(e){return e&&e.__esModule?e:{default:e}}function cX(e,t){if(typeof WeakMap=="function")var n=new WeakMap,r=new WeakMap;return(cX=function(o,i){if(!i&&o&&o.__esModule)return o;var s,a,l={__proto__:null,default:o};if(o===null||typeof o!="object"&&typeof o!="function")return l;if(s=i?r:n){if(s.has(o))return s.get(o);s.set(o,l)}for(const c in o)c!=="default"&&{}.hasOwnProperty.call(o,c)&&((a=(s=Object.defineProperty)&&Object.getOwnPropertyDescriptor(o,c))&&(a.get||a.set)?s(l,c,a):lc=oc);return l})(e,t)}function ST(){return ST=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=argumentst;for(var r in n)({}).hasOwnProperty.call(n,r)&&(er=nr)}return e},ST.apply(null,arguments)}function ax(e,t,n){return(t=HDe(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function HDe(e){var t=BDe(e,"string");return typeof t=="symbol"?t:t+""}function BDe(e,t){if(typeof e!="object"||!e)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}const Uz=e=>Object.prototype.toString.call(e);function f0(e,t){return e==null?null:Array.isArray(e)?e:et}class yA extends Wz.Component{constructor(){super(...arguments),ax(this,"state",this.generateInitialState()),ax(this,"onLayoutChange",t=>{this.props.onLayoutChange(t,{...this.props.layouts,this.state.breakpoint:t})})}generateInitialState(){const{width:t,breakpoints:n,layouts:r,cols:o}=this.props,i=(0,tu.getBreakpointFromWidth)(n,t),s=(0,tu.getColsFromBreakpoint)(i,o),a=this.props.verticalCompact===!1?null:this.props.compactType;return{layout:(0,tu.findOrGenerateResponsiveLayout)(r,n,i,i,s,a),breakpoint:i,cols:s}}static getDerivedStateFromProps(t,n){if(!(0,dN.deepEqual)(t.layouts,n.layouts)){const{breakpoint:r,cols:o}=n;return{layout:(0,tu.findOrGenerateResponsiveLayout)(t.layouts,t.breakpoints,r,r,o,t.compactType),layouts:t.layouts}}return null}componentDidUpdate(t){(this.props.width!=t.width||this.props.breakpoint!==t.breakpoint||!(0,dN.deepEqual)(this.props.breakpoints,t.breakpoints)||!(0,dN.deepEqual)(this.props.cols,t.cols))&&this.onWidthChange(t)}onWidthChange(t){const{breakpoints:n,cols:r,layouts:o,compactType:i}=this.props,s=this.props.breakpoint||(0,tu.getBreakpointFromWidth)(this.props.breakpoints,this.props.width),a=this.state.breakpoint,l=(0,tu.getColsFromBreakpoint)(s,r),c={...o};if(a!==s||t.breakpoints!==n||t.cols!==r){a in c||(ca=(0,mf.cloneLayout)(this.state.layout));let p=(0,tu.findOrGenerateResponsiveLayout)(c,n,s,a,l,i);p=(0,mf.synchronizeLayoutWithChildren)(p,this.props.children,l,i,this.props.allowOverlap),cs=p,this.props.onBreakpointChange(s,l),this.props.onLayoutChange(p,c),this.setState({breakpoint:s,layout:p,cols:l})}const d=f0(this.props.margin,s),f=f0(this.props.containerPadding,s);this.props.onWidthChange(this.props.width,d,l,f)}render(){const{breakpoint:t,breakpoints:n,cols:r,layouts:o,margin:i,containerPadding:s,onBreakpointChange:a,onLayoutChange:l,onWidthChange:c,...d}=this.props;return Wz.createElement(zDe.default,ST({},d,{margin:f0(i,this.state.breakpoint),containerPadding:f0(s,this.state.breakpoint),onLayoutChange:this.onLayoutChange,layout:this.state.layout,cols:this.state.cols}))}}gw.default=yA;ax(yA,"propTypes",{breakpoint:Co.default.string,breakpoints:Co.default.object,allowOverlap:Co.default.bool,cols:Co.default.object,margin:Co.default.oneOfType(Co.default.array,Co.default.object),containerPadding:Co.default.oneOfType(Co.default.array,Co.default.object),layouts(e,t){if(Uz(et)!=="object Object")throw new Error("Layout property must be an object. Received: "+Uz(et));Object.keys(et).forEach(n=>{if(!(n in e.breakpoints))throw new Error("Each key in layouts must align with a key in breakpoints.");(0,mf.validateLayout)(e.layoutsn,"layouts."+n)})},width:Co.default.number.isRequired,onBreakpointChange:Co.default.func,onLayoutChange:Co.default.func,onWidthChange:Co.default.func});ax(yA,"defaultProps",{breakpoints:{lg:1200,md:996,sm:768,xs:480,xxs:0},cols:{lg:12,md:10,sm:6,xs:4,xxs:2},containerPadding:{lg:null,md:null,sm:null,xs:null,xxs:null},layouts:{},margin:10,10,allowOverlap:!1,onBreakpointChange:mf.noop,onLayoutChange:mf.noop,onWidthChange:mf.noop});var xA={},uX=function(){if(typeof Map<"u")return Map;function e(t,n){var r=-1;return t.some(function(o,i){return o0===n?(r=i,!0):!1}),r}return function(){function t(){this.__entries__=}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(n){var r=e(this.__entries__,n),o=this.__entries__r;return o&&o1},t.prototype.set=function(n,r){var o=e(this.__entries__,n);~o?this.__entries__o1=r:this.__entries__.push(n,r)},t.prototype.delete=function(n){var r=this.__entries__,o=e(r,n);~o&&r.splice(o,1)},t.prototype.has=function(n){return!!~e(this.__entries__,n)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(n,r){r===void 0&&(r=null);for(var o=0,i=this.__entries__;o<i.length;o++){var s=io;n.call(r,s1,s0)}},t}()}(),_T=typeof window<"u"&&typeof document<"u"&&window.document===document,lx=function(){return typeof global<"u"&&global.Math===Math?global:typeof self<"u"&&self.Math===Math?self:typeof window<"u"&&window.Math===Math?window:Function("return this")()}(),WDe=function(){return typeof requestAnimationFrame=="function"?requestAnimationFrame.bind(lx):function(e){return setTimeout(function(){return e(Date.now())},1e3/60)}}(),UDe=2;function qDe(e,t){var n=!1,r=!1,o=0;function i(){n&&(n=!1,e()),r&&a()}function s(){WDe(i)}function a(){var l=Date.now();if(n){if(l-o<UDe)return;r=!0}else n=!0,r=!1,setTimeout(s,t);o=l}return a}var GDe=20,VDe="top","right","bottom","left","width","height","size","weight",YDe=typeof MutationObserver<"u",KDe=function(){function e(){this.connected_=!1,this.mutationEventsAdded_=!1,this.mutationsObserver_=null,this.observers_=,this.onTransitionEnd_=this.onTransitionEnd_.bind(this),this.refresh=qDe(this.refresh.bind(this),GDe)}return e.prototype.addObserver=function(t){~this.observers_.indexOf(t)||this.observers_.push(t),this.connected_||this.connect_()},e.prototype.removeObserver=function(t){var n=this.observers_,r=n.indexOf(t);~r&&n.splice(r,1),!n.length&&this.connected_&&this.disconnect_()},e.prototype.refresh=function(){var t=this.updateObservers_();t&&this.refresh()},e.prototype.updateObservers_=function(){var t=this.observers_.filter(function(n){return n.gatherActive(),n.hasActive()});return t.forEach(function(n){return n.broadcastActive()}),t.length>0},e.prototype.connect_=function(){!_T||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),YDe?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!_T||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var n=t.propertyName,r=n===void 0?"":n,o=VDe.some(function(i){return!!~r.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),dX=function(e,t){for(var n=0,r=Object.keys(t);n<r.length;n++){var o=rn;Object.defineProperty(e,o,{value:to,enumerable:!1,writable:!1,configurable:!0})}return e},Uf=function(e){var t=e&&e.ownerDocument&&e.ownerDocument.defaultView;return t||lx},fX=vw(0,0,0,0);function cx(e){return parseFloat(e)||0}function qz(e){for(var t=,n=1;n<arguments.length;n++)tn-1=argumentsn;return t.reduce(function(r,o){var i=e"border-"+o+"-width";return r+cx(i)},0)}function XDe(e){for(var t="top","right","bottom","left",n={},r=0,o=t;r<o.length;r++){var i=or,s=e"padding-"+i;ni=cx(s)}return n}function ZDe(e){var t=e.getBBox();return vw(0,0,t.width,t.height)}function QDe(e){var t=e.clientWidth,n=e.clientHeight;if(!t&&!n)return fX;var r=Uf(e).getComputedStyle(e),o=XDe(r),i=o.left+o.right,s=o.top+o.bottom,a=cx(r.width),l=cx(r.height);if(r.boxSizing==="border-box"&&(Math.round(a+i)!==t&&(a-=qz(r,"left","right")+i),Math.round(l+s)!==n&&(l-=qz(r,"top","bottom")+s)),!e3e(e)){var c=Math.round(a+i)-t,d=Math.round(l+s)-n;Math.abs(c)!==1&&(a-=c),Math.abs(d)!==1&&(l-=d)}return vw(o.left,o.top,a,l)}var JDe=function(){return typeof SVGGraphicsElement<"u"?function(e){return e instanceof Uf(e).SVGGraphicsElement}:function(e){return e instanceof Uf(e).SVGElement&&typeof e.getBBox=="function"}}();function e3e(e){return e===Uf(e).document.documentElement}function t3e(e){return _T?JDe(e)?ZDe(e):QDe(e):fX}function n3e(e){var t=e.x,n=e.y,r=e.width,o=e.height,i=typeof DOMRectReadOnly<"u"?DOMRectReadOnly:Object,s=Object.create(i.prototype);return dX(s,{x:t,y:n,width:r,height:o,top:n,right:t+r,bottom:o+n,left:t}),s}function vw(e,t,n,r){return{x:e,y:t,width:n,height:r}}var r3e=function(){function e(t){this.broadcastWidth=0,this.broadcastHeight=0,this.contentRect_=vw(0,0,0,0),this.target=t}return e.prototype.isActive=function(){var t=t3e(this.target);return this.contentRect_=t,t.width!==this.broadcastWidth||t.height!==this.broadcastHeight},e.prototype.broadcastRect=function(){var t=this.contentRect_;return this.broadcastWidth=t.width,this.broadcastHeight=t.height,t},e}(),o3e=function(){function e(t,n){var r=n3e(n);dX(this,{target:t,contentRect:r})}return e}(),i3e=function(){function e(t,n,r){if(this.activeObservations_=,this.observations_=new uX,typeof t!="function")throw new TypeError("The callback provided as parameter 1 is not a function.");this.callback_=t,this.controller_=n,this.callbackCtx_=r}return e.prototype.observe=function(t){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");if(!(typeof Element>"u"||!(Element instanceof Object))){if(!(t instanceof Uf(t).Element))throw new TypeError('parameter 1 is not of type "Element".');var n=this.observations_;n.has(t)||(n.set(t,new r3e(t)),this.controller_.addObserver(this),this.controller_.refresh())}},e.prototype.unobserve=function(t){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");if(!(typeof Element>"u"||!(Element instanceof Object))){if(!(t instanceof Uf(t).Element))throw new TypeError('parameter 1 is not of type "Element".');var n=this.observations_;n.has(t)&&(n.delete(t),n.size||this.controller_.removeObserver(this))}},e.prototype.disconnect=function(){this.clearActive(),this.observations_.clear(),this.controller_.removeObserver(this)},e.prototype.gatherActive=function(){var t=this;this.clearActive(),this.observations_.forEach(function(n){n.isActive()&&t.activeObservations_.push(n)})},e.prototype.broadcastActive=function(){if(this.hasActive()){var t=this.callbackCtx_,n=this.activeObservations_.map(function(r){return new o3e(r.target,r.broadcastRect())});this.callback_.call(t,n,t),this.clearActive()}},e.prototype.clearActive=function(){this.activeObservations_.splice(0)},e.prototype.hasActive=function(){return this.activeObservations_.length>0},e}(),hX=typeof WeakMap<"u"?new WeakMap:new uX,pX=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=KDe.getInstance(),r=new i3e(t,n,this);hX.set(this,r)}return e}();"observe","unobserve","disconnect".forEach(function(e){pX.prototypee=function(){var t;return(t=hX.get(this))e.apply(t,arguments)}});var s3e=function(){return typeof lx.ResizeObserver<"u"?lx.ResizeObserver:pX}();const a3e=Object.freeze(Object.defineProperty({__proto__:null,default:s3e},Symbol.toStringTag,{value:"Module"})),l3e=Fre(a3e);Object.defineProperty(xA,"__esModule",{value:!0});xA.default=m3e;var h0=mX(y),c3e=bA(Pc),u3e=bA(l3e),d3e=bA(iw);function bA(e){return e&&e.__esModule?e:{default:e}}function mX(e,t){if(typeof WeakMap=="function")var n=new WeakMap,r=new WeakMap;return(mX=function(o,i){if(!i&&o&&o.__esModule)return o;var s,a,l={__proto__:null,default:o};if(o===null||typeof o!="object"&&typeof o!="function")return l;if(s=i?r:n){if(s.has(o))return s.get(o);s.set(o,l)}for(const c in o)c!=="default"&&{}.hasOwnProperty.call(o,c)&&((a=(s=Object.defineProperty)&&Object.getOwnPropertyDescriptor(o,c))&&(a.get||a.set)?s(l,c,a):lc=oc);return l})(e,t)}function CT(){return CT=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=argumentst;for(var r in n)({}).hasOwnProperty.call(n,r)&&(er=nr)}return e},CT.apply(null,arguments)}function Md(e,t,n){return(t=f3e(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):et=n,e}function f3e(e){var t=h3e(e,"string");return typeof t=="symbol"?t:t+""}function h3e(e,t){if(typeof e!="object"||!e)return e;var n=eSymbol.toPrimitive;if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}const p3e="react-grid-layout";function m3e(e){var t;return t=class extends h0.Component{constructor(){super(...arguments),Md(this,"state",{width:1280}),Md(this,"elementRef",h0.createRef()),Md(this,"mounted",!1),Md(this,"resizeObserver",void 0)}componentDidMount(){this.mounted=!0,this.resizeObserver=new u3e.default(o=>{if(this.elementRef.current instanceof HTMLElement){const s=o0.contentRect.width;this.setState({width:s})}});const r=this.elementRef.current;r instanceof HTMLElement&&this.resizeObserver.observe(r)}componentWillUnmount(){this.mounted=!1;const r=this.elementRef.current;r instanceof HTMLElement&&this.resizeObserver.unobserve(r),this.resizeObserver.disconnect()}render(){const{measureBeforeMount:r,...o}=this.props;return r&&!this.mounted?h0.createElement("div",{className:(0,d3e.default)(this.props.className,p3e),style:this.props.style,ref:this.elementRef}):h0.createElement(e,CT({innerRef:this.elementRef},o,this.state))}},Md(t,"defaultProps",{measureBeforeMount:!1}),Md(t,"propTypes",{measureBeforeMount:c3e.default.bool}),t}(function(e){e.exports=Cg.default,e.exports.utils=Mt,e.exports.calculateUtils=ha,e.exports.Responsive=gw.default,e.exports.Responsive.utils=td,e.exports.WidthProvider=xA.default})(kK);var Gz=kK.exports;function gX(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"polyline",attr:{points:"20 6 9 17 4 12"},child:}})(e)}function vX(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"rect",attr:{x:"3",y:"3",width:"18",height:"18",rx:"2",ry:"2"},child:},{tag:"line",attr:{x1:"3",y1:"9",x2:"21",y2:"9"},child:},{tag:"line",attr:{x1:"9",y1:"21",x2:"9",y2:"9"},child:}})(e)}function g3e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"line",attr:{x1:"12",y1:"5",x2:"12",y2:"19"},child:},{tag:"line",attr:{x1:"5",y1:"12",x2:"19",y2:"12"},child:}})(e)}function v3e(e){return Be({attr:{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},child:{tag:"circle",attr:{cx:"12",cy:"12",r:"3"},child:},{tag:"path",attr:{d:"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"},child:}})(e)}const y3e=()=>{conste,t=y.useState(""),{save:n,load:r,remove:o,getLayoutNames:i,currentLayout:s}=zEe(),a=()=>{e.trim()&&(n(e.trim()),t(""))},l=d=>{r(d)},c=d=>{o(d)};return h.jsxs("div",{className:"p-4 space-y-4 bg-monitor-panel",children:h.jsxs("div",{className:"flex gap-2",children:h.jsx(ol,{placeholder:"Layout name",value:e,onChange:d=>t(d.target.value),onKeyDown:d=>d.key==="Enter"&&a()}),h.jsx(fs,{onClick:a,disabled:!e.trim(),children:"Save"})}),h.jsxs("div",{className:"space-y-2",children:h.jsx("h3",{className:"text-sm font-medium",children:"Saved layouts:"}),i().map(d=>{const f=s===d;return h.jsxs("div",{className:"flex gap-2 items-center",children:h.jsxs("span",{className:`flex-1 text-sm ${f?"text-blue-400 font-semibold":"text-muted-foreground text-gray-400"}`,children:d,f&&h.jsx("span",{className:"ml-2 text-xs",children:"(active)"})}),h.jsx(fs,{size:"sm",variant:f?"outline":"default",onClick:()=>l(d),disabled:f,children:f?"Active":"Load"}),h.jsx(fs,{size:"sm",variant:"destructive",onClick:()=>c(d),children:"Delete"})},d)}),i().length===0&&h.jsx("p",{className:"text-sm text-muted-foreground text-gray-400",children:"No saved layouts"})})})},x3e=({type:e,title:t,icon:n})=>{const r=Hn(),i=Xe(T9).some(a=>a.type===e),s=()=>{r(xK(e))};return h.jsxs("button",{onClick:s,"aria-label":`Add ${t} widget to dashboard`,className:` + group w-full flex items-center justify-between p-3 rounded-lg + transition-all duration-150 ease-out + focus:outline-none focus-visible:ring-1 focus-visible:ring-emerald-400/30 + + ${i?"bg-monitor-panel ring-1 ring-emerald-400/25 text-emerald-400":"bg-monitor-panel text-monitor-text-secondary hover:bg-white/4"} + } + `,children:h.jsxs("div",{className:"flex items-center font-cond gap-3",children:h.jsx(n,{className:`w-5 h-5 + ${i?"text-emerald-400":"text-monitor-text-muted group-hover:text-monitor-text-primary"}`}),h.jsx("span",{className:`text-sm font-medium + ${i?"text-monitor-text-primary":"text-monitor-text-secondary group-hover:text-monitor-text-primary"}`,children:t})}),h.jsxs("div",{className:"flex items-center bg-monitor-surface gap-2",children:i&&h.jsxs("div",{className:"flex items-center gap-1 text-xs text-emerald-400",children:h.jsx(gX,{className:"w-3 h-3"}),h.jsx("span",{className:"hidden font-cond sm:inline",children:"Active"})}),h.jsx(g3e,{className:`w-4 h-4 transition-transform group-hover:scale-110 + ${i?"hidden":"text-slate-400 group-hover:text-slate-100"} + `})})})},b3e=({isOpen:e,onToggle:t,onClose:n})=>h.jsxs(wg,{open:e,onOpenChange:r=>r?t():n(),children:h.jsx(Sg,{asChild:!0,children:h.jsxs("button",{onClick:t,className:` + flex items-center gap-2 px-3 py-2 rounded-lg + transition-all duration-200 ease-out + focus:outline-none focus-visible:ring-1 focus-visible:ring-emerald-400/30 + ${e?" bg-monitor-panel/55 text-white ring-1 ring-emerald-400/30":"text-white/70 hover:bg-white/5 hover:text-white"} + `,"aria-label":"Toggle widgets menu","aria-expanded":e,children:h.jsx(vX,{className:"w-4 h-4 text-emerald-400"}),h.jsx("span",{className:"font-small text-ui ",children:"Widgets"}),h.jsx(cK,{className:`w-3 h-3 transition-transform duration-200 ${e?"rotate-180":""}`})})}),h.jsx(_g,{className:` + w-72 bg-monitor-surface ring-1 ring-monitor-line rounded-xl shadow-none p-2 z-50 + data-state=open:animate-popoverShow + data-state=closed:animate-popoverHide + `,sideOffset:5,children:h.jsx("div",{className:"space-y-2",children:iLe().map(r=>h.jsx(x3e,{type:r.type,title:r.title,icon:r.icon,defaultSize:r.defaultSize},r.type))})})}),w3e=()=>{const e=Hn(),t=Xe(nb);y.useEffect(()=>{if(!t)return;const n=r=>{e(zLe({id:t.id,status:r}))};return Mo.onConnectionStatusChange=n,()=>{Mo.onConnectionStatusChange=void 0}},e,t)},yX=e=>{switch(e){case Lr.CONNECTED:return"bg-emerald-600";case Lr.CONNECTING:case Lr.RECONNECTING:return"bg-amber-600";case Lr.ERROR:case Lr.DISCONNECTED:return"bg-rose-600";default:return"bg-gray-600"}};var fN=0;function S3e(){y.useEffect(()=>{const e=document.querySelectorAll("data-radix-focus-guard");return document.body.insertAdjacentElement("afterbegin",e0??Vz()),document.body.insertAdjacentElement("beforeend",e1??Vz()),fN++,()=>{fN===1&&document.querySelectorAll("data-radix-focus-guard").forEach(t=>t.remove()),fN--}},)}function Vz(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var yw="Dialog",xX,h5e=to(yw),_3e,Ss=xX(yw),bX=e=>{const{__scopeDialog:t,children:n,open:r,defaultOpen:o,onOpenChange:i,modal:s=!0}=e,a=y.useRef(null),l=y.useRef(null),c,d=xs({prop:r,defaultProp:o??!1,onChange:i,caller:yw});return h.jsx(_3e,{scope:t,triggerRef:a,contentRef:l,contentId:Mi(),titleId:Mi(),descriptionId:Mi(),open:c,onOpenChange:d,onOpenToggle:y.useCallback(()=>d(f=>!f),d),modal:s,children:n})};bX.displayName=yw;var wX="DialogTrigger",C3e=y.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=Ss(wX,n),i=Pt(t,o.triggerRef);return h.jsx(Ze.button,{type:"button","aria-haspopup":"dialog","aria-expanded":o.open,"aria-controls":o.contentId,"data-state":_A(o.open),...r,ref:i,onClick:yt(e.onClick,o.onOpenToggle)})});C3e.displayName=wX;var wA="DialogPortal",E3e,SX=xX(wA,{forceMount:void 0}),_X=e=>{const{__scopeDialog:t,forceMount:n,children:r,container:o}=e,i=Ss(wA,t);return h.jsx(E3e,{scope:t,forceMount:n,children:y.Children.map(r,s=>h.jsx(ys,{present:n||i.open,children:h.jsx(ig,{asChild:!0,container:o,children:s})}))})};_X.displayName=wA;var ux="DialogOverlay",CX=y.forwardRef((e,t)=>{const n=SX(ux,e.__scopeDialog),{forceMount:r=n.forceMount,...o}=e,i=Ss(ux,e.__scopeDialog);return i.modal?h.jsx(ys,{present:r||i.open,children:h.jsx(R3e,{...o,ref:t})}):null});CX.displayName=ux;var N3e=jf("DialogOverlay.RemoveScroll"),R3e=y.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=Ss(ux,n);return h.jsx(xb,{as:N3e,allowPinchZoom:!0,shards:o.contentRef,children:h.jsx(Ze.div,{"data-state":_A(o.open),...r,ref:t,style:{pointerEvents:"auto",...r.style}})})}),Hu="DialogContent",EX=y.forwardRef((e,t)=>{const n=SX(Hu,e.__scopeDialog),{forceMount:r=n.forceMount,...o}=e,i=Ss(Hu,e.__scopeDialog);return h.jsx(ys,{present:r||i.open,children:i.modal?h.jsx(T3e,{...o,ref:t}):h.jsx(k3e,{...o,ref:t})})});EX.displayName=Hu;var T3e=y.forwardRef((e,t)=>{const n=Ss(Hu,e.__scopeDialog),r=y.useRef(null),o=Pt(t,n.contentRef,r);return y.useEffect(()=>{const i=r.current;if(i)return MP(i)},),h.jsx(NX,{...e,ref:o,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:yt(e.onCloseAutoFocus,i=>{var s;i.preventDefault(),(s=n.triggerRef.current)==null||s.focus()}),onPointerDownOutside:yt(e.onPointerDownOutside,i=>{const s=i.detail.originalEvent,a=s.button===0&&s.ctrlKey===!0;(s.button===2||a)&&i.preventDefault()}),onFocusOutside:yt(e.onFocusOutside,i=>i.preventDefault())})}),k3e=y.forwardRef((e,t)=>{const n=Ss(Hu,e.__scopeDialog),r=y.useRef(!1),o=y.useRef(!1);return h.jsx(NX,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:i=>{var s,a;(s=e.onCloseAutoFocus)==null||s.call(e,i),i.defaultPrevented||(r.current||(a=n.triggerRef.current)==null||a.focus(),i.preventDefault()),r.current=!1,o.current=!1},onInteractOutside:i=>{var l,c;(l=e.onInteractOutside)==null||l.call(e,i),i.defaultPrevented||(r.current=!0,i.detail.originalEvent.type==="pointerdown"&&(o.current=!0));const s=i.target;((c=n.triggerRef.current)==null?void 0:c.contains(s))&&i.preventDefault(),i.detail.originalEvent.type==="focusin"&&o.current&&i.preventDefault()}})}),NX=y.forwardRef((e,t)=>{const{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:o,onCloseAutoFocus:i,...s}=e,a=Ss(Hu,n),l=y.useRef(null),c=Pt(t,l);return S3e(),h.jsxs(h.Fragment,{children:h.jsx(gb,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:o,onUnmountAutoFocus:i,children:h.jsx(sb,{role:"dialog",id:a.contentId,"aria-describedby":a.descriptionId,"aria-labelledby":a.titleId,"data-state":_A(a.open),...s,ref:c,onDismiss:()=>a.onOpenChange(!1)})}),h.jsxs(h.Fragment,{children:h.jsx(P3e,{titleId:a.titleId}),h.jsx(A3e,{contentRef:l,descriptionId:a.descriptionId})})})}),SA="DialogTitle",RX=y.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=Ss(SA,n);return h.jsx(Ze.h2,{id:o.titleId,...r,ref:t})});RX.displayName=SA;var TX="DialogDescription",kX=y.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=Ss(TX,n);return h.jsx(Ze.p,{id:o.descriptionId,...r,ref:t})});kX.displayName=TX;var PX="DialogClose",IX=y.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=Ss(PX,n);return h.jsx(Ze.button,{type:"button",...r,ref:t,onClick:yt(e.onClick,()=>o.onOpenChange(!1))})});IX.displayName=PX;function _A(e){return e?"open":"closed"}var AX="DialogTitleWarning",p5e,MX=gxe(AX,{contentName:Hu,titleName:SA,docsSlug:"dialog"}),P3e=({titleId:e})=>{const t=MX(AX),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users. + +If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component. + +For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return y.useEffect(()=>{e&&(document.getElementById(e)||console.error(n))},n,e),null},I3e="DialogDescriptionWarning",A3e=({contentRef:e,descriptionId:t})=>{const r=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${MX(I3e).contentName}}.`;return y.useEffect(()=>{var i;const o=(i=e.current)==null?void 0:i.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(r))},r,e,t),null},M3e=bX,j3e=_X,jX=CX,LX=EX,OX=RX,DX=kX,L3e=IX;const O3e=M3e,D3e=j3e,FX=y.forwardRef(({className:e,...t},n)=>h.jsx(jX,{ref:n,className:nt("fixed inset-0 z-50 bg-black/80","data-state=open:animate-overlayShow","data-state=closed:animate-overlayHide",e),...t}));FX.displayName=jX.displayName;const $X=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(D3e,{children:h.jsx(FX,{}),h.jsxs(LX,{ref:r,className:nt("fixed left-50% top-50% z-50 grid w-full max-w-lg translate-x--50% translate-y--50% gap-4 border bg-background p-6 text-foreground shadow-lg duration-200 data-state=open:animate-in data-state=closed:animate-out data-state=closed:fade-out-0 data-state=open:fade-in-0 data-state=closed:zoom-out-95 data-state=open:zoom-in-95 data-state=closed:slide-out-to-left-1/2 data-state=closed:slide-out-to-top-48% data-state=open:slide-in-from-left-1/2 data-state=open:slide-in-from-top-48% sm:rounded-lg",e),...n,children:t,h.jsxs(L3e,{className:nt("absolute right-4 top-4 rounded-sm opacity-70","ring-offset-background transition-opacity","hover:opacity-100 focus:outline-none focus:ring-2","focus:ring-gray-400 focus:ring-offset-2","disabled:pointer-events-none"),children:h.jsx(eb,{className:"h-4 w-4"}),h.jsx("span",{className:"sr-only",children:"Close"})})})}));$X.displayName=LX.displayName;const zX=({className:e,...t})=>h.jsx("div",{className:nt("flex flex-col space-y-1.5 text-center sm:text-left",e),...t});zX.displayName="DialogHeader";const HX=y.forwardRef(({className:e,...t},n)=>h.jsx(OX,{ref:n,className:nt("text-2xl font-semibold leading-none tracking-tight",e),...t}));HX.displayName=OX.displayName;const F3e=y.forwardRef(({className:e,...t},n)=>h.jsx(DX,{ref:n,className:nt("text-sm text-muted-foreground",e),...t}));F3e.displayName=DX.displayName;const BX=y.memo(({initialData:e,onSave:t,onCancel:n})=>{constr,o=y.useState((e==null?void 0:e.name)||""),i,s=y.useState((e==null?void 0:e.address)||""),a=y.useCallback(f=>o(f.target.value),),l=y.useCallback(f=>s(f.target.value),),c=y.useCallback(()=>{r&&i&&t({name:r,address:i})},r,i,t),d=!!(r&&i);return h.jsxs("div",{className:"space-y-3 pt-4 border-t border-monitor-line",children:h.jsxs("div",{className:"flex items-center justify-between",children:h.jsx("h3",{className:"text-sm font-semibold text-gray-400",children:e?"EDIT CONNECTION":"ADD CONNECTION"}),h.jsx("button",{onClick:n,className:"p-1 text-gray-400 hover:text-white rounded transition-colors","aria-label":"Cancel",children:h.jsx(eb,{className:"w-4 h-4"})})}),h.jsxs("div",{className:"space-y-3",children:h.jsx(ol,{value:r,onChange:a,placeholder:"Connection name",className:"bg-monitor-panel border-monitor-line text-white"}),h.jsx(ol,{value:i,onChange:l,placeholder:"ws://localhost:6363",className:"bg-monitor-panel border-monitor-line text-white"}),h.jsxs("button",{onClick:c,disabled:!d,className:"w-full flex items-center justify-center gap-2 px-4 py-2 bg-emerald-500/20 text-emerald-400 rounded-lg hover:bg-emerald-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",children:e?h.jsx(A0e,{className:"w-4 h-4"}):h.jsx(FR,{className:"w-4 h-4"}),e?"Save":"Add"})})})});BX.displayName="ConnectionForm";const WX=y.memo(({isOpen:e,onClose:t})=>{const n=Hn(),r=Xe(Y9),o,i=y.useState("list"),s=y.useCallback(()=>i("add"),),a=y.useCallback(p=>i({type:"edit",id:p}),),l=y.useCallback(()=>i("list"),),c=y.useCallback(p=>{const g={...p,type:"local"};typeof o=="object"&&o.type==="edit"?n(RK({id:o.id,...g})):n(EK({id:br("conn"),...g})),i("list")},n,o),d=y.useCallback(p=>n(NK(p)),n),f=typeof o=="object"&&o.type==="edit"?r.find(p=>p.id===o.id):null;return h.jsx(O3e,{open:e,onOpenChange:p=>!p&&t(),children:h.jsxs($X,{className:"bg-monitor-surface border-monitor-line max-w-2xl",children:h.jsx(zX,{children:h.jsx(HX,{className:"text-white",children:"Connections"})}),h.jsxs("div",{className:"space-y-4",children:o==="list"&&h.jsxs(h.Fragment,{children:r.length>0&&h.jsx("div",{className:"space-y-2",children:r.map(p=>h.jsxs("div",{className:"flex items-center gap-3 p-3 rounded-lg bg-monitor-panel/30 border border-monitor-line group",children:h.jsx("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${yX(p.status)}`}),h.jsxs("div",{className:"flex-1 min-w-0",children:h.jsx("div",{className:"font-medium text-white truncate",children:p.name}),h.jsx("div",{className:"text-xs text-gray-500 truncate",children:p.address})}),h.jsxs("div",{className:"flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity",children:h.jsx("button",{onClick:()=>a(p.id),className:"p-2 text-gray-400 hover:text-blue-400 hover:bg-blue-500/10 rounded-lg transition-colors","aria-label":`Edit ${p.name}`,children:h.jsx(FR,{className:"w-4 h-4"})}),h.jsx("button",{onClick:()=>d(p.id),className:"p-2 text-gray-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors","aria-label":`Delete ${p.name}`,children:h.jsx(L0e,{className:"w-4 h-4"})})})},p.id))}),h.jsxs("button",{onClick:s,className:"w-full flex items-center justify-center gap-2 px-4 py-2 bg-emerald-500/20 text-emerald-400 rounded-lg hover:bg-emerald-500/30 transition-colors",children:h.jsx(FR,{className:"w-4 h-4"}),"Add Connection"})}),o!=="list"&&h.jsx(BX,{initialData:f?{name:f.name,address:f.address}:void 0,onSave:c,onCancel:l})})})})});WX.displayName="ManageConnectionsDialog";const UX=y.memo(()=>{const e=Hn(),t=Xe(Y9),n=Xe(nb),r,o=y.useState(!1),i,s=y.useState(!1);w3e();const a=y.useMemo(()=>(n==null?void 0:n.status)===Lr.CONNECTED,n==null?void 0:n.status),l=y.useCallback(f=>{e(TK(f)),o(!1)},e),c=y.useCallback(()=>{o(!1),s(!0)},),d=y.useCallback(()=>{s(!1)},);return h.jsxs(wg,{open:r,onOpenChange:o,children:h.jsx(Sg,{asChild:!0,children:h.jsxs("button",{onClick:()=>o(!r),className:` + flex items-center gap-2 px-3 py-2 rounded-lg + transition-colors duration-200 ease-out + focus:outline-none focus-visible:ring-1 focus-visible:ring-emerald-400/30 + ${r?" bg-monitor-panel/55 text-white ring-1 ring-emerald-400/30":"text-white/70 hover:bg-white/5 hover:text-white"} + `,"aria-label":"Toggle connections menu","aria-expanded":r,children:h.jsx(k0e,{className:"w-4 h-4 text-emerald-400"}),h.jsx("span",{className:"font-xs text-ui hidden sm:inline",children:(n==null?void 0:n.name)||"No connection"}),a&&h.jsx("span",{className:"w-2 h-2 rounded-full bg-emerald-600"}),h.jsx(cK,{className:`w-3 h-3 transition-transform duration-200 ${r?"rotate-180":""}`})})}),h.jsxs(_g,{className:` + w-72 bg-monitor-surface ring-1 ring-monitor-line rounded-xl shadow-none p-2 z-50 + data-state=open:animate-popoverShow + data-state=closed:animate-popoverHide + `,sideOffset:5,children:h.jsx("div",{className:"px-2 py-1.5 text-xs font-semibold text-gray-400",children:"CONNECTIONS"}),t.length===0?h.jsx("div",{className:"px-2 py-4 text-sm text-gray-500 text-center",children:"No connections configured"}):h.jsx("div",{className:"space-y-1",children:t.map(f=>h.jsxs("button",{onClick:()=>l(f.id),className:` + w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm + transition-colors duration-150 + ${(n==null?void 0:n.id)===f.id?"bg-emerald-500/10 text-emerald-400 ring-1 ring-emerald-400/20":"text-gray-300 hover:bg-white/5 hover:text-white"} + `,children:h.jsxs("div",{className:"flex-1 text-left",children:h.jsx("div",{className:"font-medium",children:f.name}),h.jsx("div",{className:"text-xs text-gray-500",children:f.address})}),h.jsxs("div",{className:"flex items-center gap-2",children:(n==null?void 0:n.id)===f.id&&h.jsx(ht,{variant:"success",className:"text-8px px-1.5 py-0 h-4",children:"✓"}),h.jsx("span",{className:`w-2 h-2 rounded-full ${yX(f.status)}`})})},f.id))}),h.jsx("div",{className:"mt-2 pt-2 border-t border-monitor-line",children:h.jsx("button",{onClick:c,className:"w-full px-3 py-2 text-sm text-left text-gray-400 hover:text-white hover:bg-white/5 rounded-lg transition-colors",children:"Manage connections..."})})}),h.jsx(WX,{isOpen:i,onClose:d})})});UX.displayName="ConnectionSelector";const qX=y.memo(({icon:e,title:t,count:n,colorClass:r,disabled:o,onClick:i})=>{const s=o??(n!=null?n<=0:!1);return h.jsxs("button",{onClick:i,disabled:s,title:t,"aria-label":t,className:`flex items-center gap-2 px-3 py-1.5 rounded-md font-ui text-sm transition-none ${s?"opacity-50 cursor-not-allowed":"hover:bg-gray-800/60 cursor-pointer"}`,children:h.jsx(e,{className:`w-3.5 h-3.5 transition-none ${s?"text-gray-600":r}`}),n!=null&&h.jsx(hg,{value:n,className:`font-medium ${s?"text-gray-600":r}`})})},(e,t)=>e.count===t.count&&e.disabled===t.disabled);qX.displayName="LogShortcutButton";const Yz="bg-blue-500/20 text-blue-300","bg-green-500/20 text-green-300","bg-yellow-500/20 text-yellow-300","bg-purple-500/20 text-purple-300","bg-pink-500/20 text-pink-300","bg-indigo-500/20 text-indigo-300","bg-cyan-500/20 text-cyan-300","bg-orange-500/20 text-orange-300";function $3e(e){const t=String(e);let n=0;for(let r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return YzMath.abs(n)%Yz.length}function z3e(e){return e===void 0?"":$3e(e)}const H3e=e=>e>>>0,GX=y.memo(()=>{conste,t=y.useState(!1),n=hI(),r=Xe(oxe),o=y.useCallback(a=>{n({filterKeys:`t:${a}`}),t(!1)},n);if(r.length===0)return null;const i=r.reduce((a,l)=>a+l.total,0),s=a=>a===100?"+100":String(a);return h.jsxs(wg,{open:e,onOpenChange:t,children:h.jsx(Sg,{asChild:!0,children:h.jsxs("button",{className:"flex items-center gap-2 px-3 py-1.5 rounded-md font-ui text-sm hover:bg-gray-800/60 cursor-pointer transition-none",title:`${i} alert(s) across ${r.length} thread(s)`,"aria-label":"Filter logs by thread",children:h.jsx(s_e,{className:"w-3.5 h-3.5 text-blue-400 transition-none"}),h.jsx(hg,{value:r.length,className:"font-medium text-blue-400"})})}),h.jsxs(_g,{align:"end",className:"bg-monitor-panel border-gray-700 rounded-md shadow-lg z-50 min-w-220px max-h-320px overflow-hidden",children:h.jsx("div",{className:"px-3 py-2 text-xs text-gray-400 border-b border-gray-700 font-semibold",children:"Threads with Alerts"}),h.jsx("div",{className:"max-h-280px overflow-y-auto",children:r.map(a=>{const l=z3e(a.threadId),c=H3e(a.threadId);return h.jsxs("button",{onClick:()=>o(a.threadId),className:"w-full px-3 py-2 flex items-center justify-between hover:bg-gray-800 text-left ",children:h.jsx("div",{className:"flex items-center gap-2",children:h.jsxs("span",{className:`px-2 py-0.5 rounded text-xs font-mono transition-none ${l}`,children:"T",c})}),h.jsxs("div",{className:"flex items-center gap-2 text-xs",children:a.errors>0&&h.jsxs("span",{className:"text-red-400 font-medium",children:a.errors," err"}),a.warnings>0&&h.jsxs("span",{className:"text-amber-400 font-medium transition-noney",children:a.warnings," warn"}),a.info!==void 0&&a.info>0&&h.jsxs("span",{className:"text-info font-medium transition-none",children:s(a.info)," info"})})},a.threadId)})})})})});GX.displayName="ThreadFilterDropdown";const B3e={key:"errors",title:"Show error logs",level:At.ERROR,icon:bq,colorClass:"text-danger"},{key:"warnings",title:"Show warning logs",level:At.WARNING,icon:GP,colorClass:"text-warning"},{key:"info",title:"Show info logs",level:At.INFO,icon:B0,colorClass:"text-info"},VX=y.memo(()=>{const e=Xe(rxe),t=hI(),n={errors:e.error,warnings:e.warning,info:e.info};return h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx("span",{className:"text-xs font-medium text-muted uppercase tracking-wider",children:"Logs"}),B3e.map(r=>h.jsx(qX,{icon:r.icon,title:`${nr.key} - ${r.title}`,count:nr.key,colorClass:r.colorClass,onClick:()=>t({levels:r.level})},r.key)),h.jsx("div",{className:"h-4 w-px bg-gray-700"}),h.jsx(GX,{})})});VX.displayName="LogCounters";const W3e=()=>{const e=Hn(),t=Xe(a=>a.layout.isSidebarOpen),n,r=y.useState(!1),o,i=y.useState(!1),s=y.useRef(null);return y.useEffect(()=>{const a=l=>{s.current&&!s.current.contains(l.target)&&r(!1)};return n&&document.addEventListener("mousedown",a),()=>{document.removeEventListener("mousedown",a)}},n),h.jsxs("header",{className:"h-14 bg-monitor-app border-b border-white/10 px-4 text-white/80",children:h.jsxs("div",{className:"h-full flex items-center justify-between",children:h.jsxs("div",{className:"flex items-center gap-6",children:h.jsx("h1",{className:"text-xl font-semibold text-gray-200 font-ui",children:"GPAC Monitor"}),h.jsx("div",{className:"h-6 w-px bg-gray-700"}),h.jsx("button",{onClick:()=>window.location.reload(),className:"p-2 text-gray-400 hover:text-white rounded-lg hover:bg-gray-800 ",title:"Reload page","aria-label":"Reload page",children:h.jsx(I0e,{className:"w-4 h-4"})}),h.jsx("span",{"aria-label":"Connection selector",title:"Connection selector",children:h.jsx(UX,{})}),h.jsx("span",{"aria-label":"Connection selector",title:"Connection selector"}),h.jsx("span",{"aria-label":"Widget selector",title:"Widget selector",children:h.jsx(b3e,{isOpen:o,onToggle:()=>i(!o),onClose:()=>i(!1),"aria-label":"Widget selector"})}),h.jsx("span",{"aria-label":"Log counters",title:"Log counters",children:h.jsx(VX,{})})}),h.jsxs("div",{className:"flex items-center gap-4",children:h.jsxs("button",{onClick:()=>e(BEe()),className:"flex items-center gap-2 px-3 py-2 text-gray-300 font-ui hover:text-white text-sm rounded-lg hover:bg-gray-800 ",title:t?"Close sidebar":"Open sidebar",children:t?h.jsx(R0e,{className:"w-4 h-4"}):h.jsx(T0e,{className:"w-4 h-4"}),h.jsxs("span",{className:"hidden sm:inline",children:t?"Hide":"Show"," Sidebar"})}),h.jsx("div",{className:"h-6 w-px bg-gray-700"}),h.jsxs("button",{onClick:()=>r(!n),className:"flex items-center gap-2 px-3 py-2 text-gray-300 font-ui hover:text-white text-sm rounded-lg hover:bg-gray-800 ",children:h.jsx(vX,{className:"w-4 h-4"}),"Layouts"})})}),n&&h.jsx("div",{ref:s,className:"absolute top-14 right-4 border border-gray-700 rounded-lg shadow-lg z-50 min-w-80",children:h.jsx(y3e,{})})})};var xw="Switch",U3e,m5e=to(xw),q3e,G3e=U3e(xw),YX=y.forwardRef((e,t)=>{const{__scopeSwitch:n,name:r,checked:o,defaultChecked:i,required:s,disabled:a,value:l="on",onCheckedChange:c,form:d,...f}=e,p,g=y.useState(null),v=Pt(t,C=>g(C)),b=y.useRef(!1),_=p?d||!!p.closest("form"):!0,x,w=xs({prop:o,defaultProp:i??!1,onChange:c,caller:xw});return h.jsxs(q3e,{scope:n,checked:x,disabled:a,children:h.jsx(Ze.button,{type:"button",role:"switch","aria-checked":x,"aria-required":s,"data-state":QX(x),"data-disabled":a?"":void 0,disabled:a,value:l,...f,ref:v,onClick:yt(e.onClick,C=>{w(E=>!E),_&&(b.current=C.isPropagationStopped(),b.current||C.stopPropagation())})}),_&&h.jsx(ZX,{control:p,bubbles:!b.current,name:r,value:l,checked:x,required:s,disabled:a,form:d,style:{transform:"translateX(-100%)"}})})});YX.displayName=xw;var KX="SwitchThumb",XX=y.forwardRef((e,t)=>{const{__scopeSwitch:n,...r}=e,o=G3e(KX,n);return h.jsx(Ze.span,{"data-state":QX(o.checked),"data-disabled":o.disabled?"":void 0,...r,ref:t})});XX.displayName=KX;var V3e="SwitchBubbleInput",ZX=y.forwardRef(({__scopeSwitch:e,control:t,checked:n,bubbles:r=!0,...o},i)=>{const s=y.useRef(null),a=Pt(s,i),l=wq(n),c=ub(t);return y.useEffect(()=>{const d=s.current;if(!d)return;const f=window.HTMLInputElement.prototype,g=Object.getOwnPropertyDescriptor(f,"checked").set;if(l!==n&&g){const v=new Event("click",{bubbles:r});g.call(d,n),d.dispatchEvent(v)}},l,n,r),h.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:n,...o,tabIndex:-1,ref:a,style:{...o.style,...c,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});ZX.displayName=V3e;function QX(e){return e?"checked":"unchecked"}var JX=YX,Y3e=XX;const eZ=y.forwardRef(({className:e,...t},n)=>h.jsx(JX,{className:nt("peer inline-flex h-4 w-12 shrink-0 cursor-pointer items-center rounded-md transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-60 data-state=checked:bg-emerald-400/90 data-state=unchecked:bg-rose-600/50",e),...t,ref:n,children:h.jsx(Y3e,{className:nt("pointer-events-none block h-5 w-5 rounded-md bg-slate-300 shadow-md ring-0 transition-transform duration-200 data-state=checked:translate-x-6 data-state=unchecked:translate-x-0.5")})}));eZ.displayName=JX.displayName;function Zp({size:e="md",className:t,...n}){const r={sm:"h-4 w-4 border-2",md:"h-8 w-8 border-2",lg:"h-12 w-12 border-3"};return h.jsx("div",{className:nt("inline-block animate-spin rounded-full border-solid border-current border-r align--0.125em motion-reduce:animate-spin_1.5s_linear_infinite",re,t),role:"status",style:{willChange:"transform",transform:"translateZ(0)"},...n,children:h.jsx("span",{className:"!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 !clip:rect(0,0,0,0)",children:"Loading..."})})}const Kz="h-7 text-xs bg-gray-800/60 border-gray-600/50 hover:bg-gray-700/50 focus:ring-1 focus:ring-blue-500/50 transition-colors",CA=({type:e,value:t,onChange:n,rules:r,debounce:o=!1,debounceMs:i=100,isPending:s=!1})=>{consta,l=y.useState(t??(e==="string"?"":e==="number"?null:!1)),c=OI();DI(()=>{!o||c||a===t||n(a)},i,a),y.useEffect(()=>{o&&l(t??(e==="string"?"":e==="number"?null:!1))},t,e,o);const d=p=>{l(p),o||n(p)};if(e==="boolean"){const p=(r==null?void 0:r.disabled)||s,v=o?!!a:typeof t=="string"?t==="true"||t==="1":!!t;return h.jsxs("div",{className:"flex items-center gap-2",children:h.jsx(eZ,{checked:v,onCheckedChange:d,disabled:p}),s&&h.jsx(Zp,{size:"sm",className:"text-blue-400"})})}if(e==="number"){const p=(r==null?void 0:r.disabled)||s;return h.jsxs("div",{className:"flex items-center gap-2 w-full",children:h.jsx(ol,{type:"number",value:String(o?a??"":t??""),onChange:g=>{const v=g.target.value===""?null:Number(g.target.value);d(v)},min:r==null?void 0:r.min,max:r==null?void 0:r.max,step:r==null?void 0:r.step,disabled:p,className:Kz}),s&&h.jsx(Zp,{size:"sm",className:"text-blue-400"})})}const f=(r==null?void 0:r.disabled)||s;return h.jsxs("div",{className:"flex items-center gap-2 w-full",children:h.jsx(ol,{type:"text",value:o?a:t||"",onChange:p=>d(p.target.value||null),placeholder:r==null?void 0:r.placeholder,disabled:f,className:Kz}),s&&h.jsx(Zp,{size:"sm",className:"text-blue-400"})})},K3e=({value:e,onChange:t,rules:n,argName:r,isPending:o=!1})=>h.jsx(CA,{type:"boolean",value:e,onChange:t,rules:n,argName:r,isPending:o}),X3e=({value:e,onChange:t,rules:n,isPending:r=!1})=>h.jsx(CA,{type:"number",value:e,onChange:t,rules:n,isPending:r}),hN=({value:e,onChange:t,rules:n,enumOptions:r,isPending:o=!1})=>{const i=y.useMemo(()=>r?r.split("|").map(s=>{const a=s.trim();if(a.includes("=")){const,l=a.split("=");return l.trim()}return a}):,r);if(r){const s=i.length>0?i0:void 0,a=e==null?s:String(e),l=(n==null?void 0:n.disabled)||o;return h.jsxs("div",{className:"flex items-center gap-2 w-full",children:h.jsx("select",{value:a,onChange:c=>t(c.target.value),disabled:l,className:nt("w-full h-7 px-2 py-0 rounded text-xs","bg-gray-800/50 border border-gray-600/50","hover:bg-gray-700/50 transition-colors","focus:outline-none focus:ring-1 focus:ring-blue-500/50 focus:border-blue-500/50","disabled:opacity-50 disabled:cursor-not-allowed"),children:i.map((c,d)=>h.jsx("option",{value:c,children:c},d))}),o&&h.jsx(Zp,{size:"sm",className:"text-blue-400"})})}return h.jsx(CA,{type:"string",value:e,onChange:t,rules:n,debounce:!0,isPending:o})},Z3e=({value:e,onChange:t,rules:n,isPending:r=!1})=>{const o=c=>{if(c==null)return0,1;if(typeof c=="number")returnc,1;if(typeof c=="string"){const d=c.split("/");if(d.length===2){const f=parseInt(d0),p=parseInt(d1);if(!isNaN(f)&&!isNaN(p)&&p!==0)returnf,p}else if(d.length===1){const f=parseInt(d0);if(!isNaN(f))returnf,1}}return0,1},i,s=y.useState(o(e)),a=OI();DI(()=>{a||`${i0}/${i1}`===e||(i1===0?t("0"):t(`${i0}/${i1}`))},1e3,i),y.useEffect(()=>{s(o(e))},e);const l=(n==null?void 0:n.disabled)||r;return h.jsxs("div",{className:"flex items-center gap-1.5 w-full",children:h.jsx(ol,{type:"number",value:i0,onChange:c=>{const d=parseInt(c.target.value);isNaN(d)||s(d,i1)},disabled:l,className:"h-7 w-16 text-xs bg-gray-800/60 border-gray-600/50 hover:bg-gray-700/50 focus:ring-1 focus:ring-blue-500/50 transition-colors"}),h.jsx("span",{className:"text-gray-500 text-xs",children:"/"}),h.jsx(ol,{type:"number",value:i1,onChange:c=>{const d=parseInt(c.target.value);isNaN(d)||s(i0,d)},disabled:l,className:"h-7 w-16 text-xs bg-gray-800/60 border-gray-600/50 hover:bg-gray-700/50 focus:ring-1 focus:ring-blue-500/50 transition-colors"}),r&&h.jsx(Zp,{size:"sm",className:"text-info"})})},Q3e=({argument:e,value:t,onChange:n,rules:r,standalone:o=!1,filterId:i,isPending:s=!1})=>{consta,l=y.useState(t),c=OI(),d=Hn();DI(()=>{if(!(c||a===t))try{const v=e.min_max_enum&&(e.min_max_enum.includes("|")||e.min_max_enum.includes("="));let b;v?b=String(a):b=Tz(a,e.type),n(b),e.update&&i&&!o&&d(ix({filterId:i,argName:e.name,argValue:b}))}catch(v){console.error("Error converting value:",v),l(t)}},1e3,a,i,e.update,e.min_max_enum),y.useEffect(()=>{l(t)},t);const f=v=>{l(v)},p=v=>{const b=e.min_max_enum&&(e.min_max_enum.includes("|")||e.min_max_enum.includes("="));let _;b?_=String(v):_=Tz(v,e.type),l(_),n(_),i&&e.update&&!o&&d(ix({filterId:i,argName:e.name,argValue:_}))},g=()=>{const v={value:a,onChange:i&&e.type==="bool"?p:f,rules:{...r,disabled:r==null?void 0:r.disabled},argument:e,isPending:s};switch(e.min_max_enum&&(e.min_max_enum.includes("|")||e.min_max_enum.includes("="))?"str":e.type){case"bool":return h.jsx(K3e,{...v,argName:e.name});case"uint":case"sint":case"luint":case"lsint":case"flt":case"dbl":return h.jsx(X3e,{...v});case"frac":case"lfrac":return h.jsx(Z3e,{...v});case"v2di":case"v2d":case"v3di":case"v4di":return h.jsx(hN,{...v,value:typeof a=="object"&&a!==null?Object.values(a).join("x"):String(a||""),onChange:_=>{if(!_){f(null);return}const x=_.split("x").map(E=>parseFloat(E)||0),w="x","y","z","w",C=x.reduce((E,R,P)=>({...E,wP:R}),{});f(C)}});case"strl":case"uintl":case"sintl":case"4ccl":case"v2il":return h.jsx(hN,{...v,value:Array.isArray(a)?a.join(","):void 0,onChange:_=>f(_?_.split(",").filter(Boolean):null)});case"str":default:return h.jsx(hN,{...v,enumOptions:e.min_max_enum})}};return o?g():h.jsx("div",{className:"w-full",children:g()})},J3e=e=>{const t=e==null?void 0:e.toLowerCase();return t==="expert"?"border-l-2 border-l-red-500":t==="advanced"?"border-l-2 border-l-orange-500":""},tZ=y.memo(({arg:e,updateStatus:t,onValueChange:n})=>{const r=e.type||typeof e.value,o=(t==null?void 0:t.status)==="pending",i=(t==null?void 0:t.status)==="success",s=!!e.update,a=y.useCallback(l=>{n(e.name,l)},e.name,n);return h.jsxs("div",{className:nt("py-2 px-3 duration-150","hover:bg-gray-800/30",J3e(e.hint),o&&"opacity-60"),children:h.jsxs("div",{className:"flex items-center gap-2 mb-2",children:h.jsx("h4",{className:nt("text-xs font-medium truncate flex-1",s?"text-info":"text-slate-300"),children:e.name}),e.desc&&h.jsx(HU,{delayDuration:150,children:h.jsxs(BU,{children:h.jsx(WU,{asChild:!0,children:h.jsx("div",{children:h.jsx(uK,{className:nt("w-3 h-3 cursor-pointer transition-colors",s?"text-slate-300 hover:text-slate-200":"text-gray-500 hover:text-gray-400")})})}),h.jsxs(RP,{side:"left",sideOffset:8,className:"max-w-xs z-100 rounded bg-gray-900 px-2 py-1 text-10px text-gray-200 border border-gray-700",children:h.jsx("p",{children:e.desc}),h.jsx(UU,{className:"fill-gray-900"})})})}),h.jsx("div",{className:"flex gap-1 shrink-0 items-center",children:i&&h.jsx(ht,{variant:"success",className:"text-8px px-1.5 py-0 h-4",children:"✓"})})}),h.jsx("div",{className:"text-xs",children:h.jsx(Q3e,{argument:{name:e.name,type:r,desc:e.desc,hint:e.hint,default:e.default,min_max_enum:e.min_max_enum,update:!!e.update,update_sync:!!e.update_sync},value:(t==null?void 0:t.value)??e.value,onChange:a,rules:{disabled:!e.update||o,min:e.min,max:e.max,step:e.step},isPending:o})})})});tZ.displayName="ArgumentItem";const e4e=({filterId:e,filterArgs:t,showExpert:n=!1,showAdvanced:r=!1,searchQuery:o=""})=>{const i=Hn(),s=y.useMemo(()=>nxe(),),a=Xe(f=>s(f,e.toString(),t)),l=y.useCallback((f,p)=>{const g=t.find(v=>v.name===f);g!=null&&g.update&&i(ix({filterId:e.toString(),argName:f,argValue:p}))},i,e,t),c=y.useMemo(()=>t.filter(f=>{var g;const p=(g=f.hint)==null?void 0:g.toLowerCase();return!(p==="expert"&&!n||p==="advanced"&&!r)}),t,n,r),d=KP(c,o,y.useCallback(f=>f.name,f.desc||"",));return d.length===0&&o?h.jsx("div",{className:"text-center text-monitor-text-muted py-6 text-xs",children:"No arguments match your search."}):h.jsx("div",{className:"divide-y divide-monitor-divider bg-monitor-paneloverflow-y-auto",children:d.map(f=>h.jsx(tZ,{arg:f,updateStatus:a==null?void 0:af.name,onValueChange:l},f.name))})},nZ=y.memo(({property:e})=>h.jsx("div",{className:"px-3 py-2 hover:bg-monitor-hover transition-colors",children:h.jsxs("div",{className:"flex items-start justify-between gap-2",children:h.jsxs("div",{className:"flex-1 min-w-0",children:h.jsx("div",{className:"text-xs font-cond text-monitor-text-primary truncate",children:e.name}),h.jsx("div",{className:"text-xs text-monitor-text-muted mt-0.5",children:e.type})}),h.jsx("div",{className:"text-xs text-info font-cond text-right break-all",children:$me(e.value,e.type)})})}));nZ.displayName="PropertyItem";const rZ=y.memo(({properties:e,searchQuery:t=""})=>{const n=KP(e,t,y.useCallback(r=>r.name,String(r.value??""),));return!e||e.length===0?h.jsx("div",{className:"text-center text-monitor-text-muted py-6 text-xs",children:"Loading properties..."}):n.length===0&&t?h.jsx("div",{className:"text-center text-monitor-text-muted py-6 text-xs",children:"No properties match your search."}):h.jsx("div",{className:"divide-y divide-monitor-divider bg-monitor-panel overflow-y-auto",children:n.map(r=>h.jsx(nZ,{property:r},r.name))})});rZ.displayName="IPIDPropertiesContent";const ET=y.forwardRef(({className:e,...t},n)=>h.jsx(rh,{ref:n,className:nt("peer h-4 w-4 shrink-0 border border-gray-500 rounded-sm transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60 data-state=checked:bg-emerald-400/90 data-state=checked:border-emerald-400",e),...t,children:h.jsx(fg,{className:nt("flex items-center justify-center text-slate-900 font-bold"),children:h.jsx(gX,{className:"h-3 w-3"})})}));ET.displayName=rh.displayName;const Xz=({filterName:e,streamType:t,onClose:n,showExpert:r=!1,showAdvanced:o=!1,onToggleExpert:i,onToggleAdvanced:s,mode:a="filter",onSearchChange:l})=>{const c=t?_me(t):"#4CC9F0";return h.jsxs("div",{className:"bg-monitor-surface border-b border-monitor-line",children:h.jsxs("div",{className:"px-3 pt-3 pb-2 flex items-center justify-between",children:h.jsxs("div",{className:"flex-1 min-w-0",children:h.jsx("h3",{className:"text-sm font-semibold text-monitor-active-filter truncate pb-1 border-b-2 inline-block",style:{borderBottomColor:c},children:e}),t&&h.jsxs("p",{className:"text-xs text-monitor-text-muted mt-0.5",children:"(",t,")"}),h.jsx("div",{className:"flex gap-2 mt-1 text-xs text-monitor-text-muted",children:h.jsx("p",{children:a==="ipid"?"IPID Properties":"Filter Options"})})}),h.jsx("button",{onClick:n,className:"p-1 hover:bg-white/5 rounded shrink-0","aria-label":"Close panel",children:h.jsx(r_e,{className:"w-4 h-4 text-monitor-text-secondary"})})}),a==="filter"&&s&&i&&h.jsxs("div",{className:"px-3 pb-3 flex gap-4 text-xs",children:h.jsxs("label",{className:"flex items-center gap-2 cursor-pointer",children:h.jsx(ET,{checked:o,onCheckedChange:s}),h.jsx("span",{className:"text-amber-400",children:"Advanced"})}),h.jsxs("label",{className:"flex items-center gap-2 cursor-pointer",children:h.jsx(ET,{checked:r,onCheckedChange:i}),h.jsx("span",{className:"text-amber-600",children:"Expert"})})}),l&&h.jsx("div",{className:"px-3 pb-3",children:h.jsx(xq,{onSearchChange:l,placeholder:a==="ipid"?"Filter properties...":"Filter arguments...",debounceMs:150})})})},t4e=(e,t)=>{const n=Yu(),r,o=y.useState();return y.useEffect(()=>{if(e===void 0||t===void 0){o();return}(async()=>{const s=await n.getPidProps(e,t);o(s)})()},e,t,n),r},n4e=e=>{const t=Yu(),n,r=y.useState();return y.useEffect(()=>{if(e===void 0){r();return}return t.getFilterArgsHandler().subscribeToFilterArgsDetails(e,i=>{r(i)})},e,t),n},r4e=()=>{const{sidebarContent:e,closeSidebar:t}=Mb(),n=Xe(_=>_.graph.filters),r=y.useMemo(()=>e?Eme(n,e.filterIdx):null,n,e),o,i=y.useState(!1),s,a=y.useState(!1),l,c=y.useState(""),d=(e==null?void 0:e.type)==="pid-props"?e.filterIdx:void 0,f=(e==null?void 0:e.type)==="pid-props"?e.ipidIdx:void 0,p=(e==null?void 0:e.type)==="filter-args"?e.filterIdx:void 0,g=t4e(d,f),v=n4e(p);y.useEffect(()=>{c("")},e);const b=y.useCallback(_=>{c(_)},);return e?h.jsxs("div",{className:"flex flex-col mt-4 flex-1 bg-monitor-surface border border-monitor-line",children:h.jsx("div",{className:"sticky top-0 z-20 bg-monitor-surface border-b border-monitor-line",children:e.type==="pid-props"?h.jsx(Xz,{filterName:`${(r==null?void 0:r.name)||"Filter"} IPIDs`,filterIdx:e.filterIdx,streamType:r==null?void 0:r.streamType,mode:"ipid",onClose:t,onSearchChange:b}):e.type==="filter-args"?h.jsx(Xz,{filterName:e.filterName,filterIdx:e.filterIdx,streamType:r==null?void 0:r.streamType,mode:"filter",showExpert:o,showAdvanced:s,onToggleExpert:i,onToggleAdvanced:a,onClose:t,onSearchChange:b}):null}),h.jsx("div",{className:"flex-1 overflow-y-auto",children:e.type==="pid-props"?h.jsx(rZ,{properties:g,searchQuery:l}):e.type==="filter-args"?Array.isArray(v)&&v.length>0?h.jsx(e4e,{filterId:e.filterIdx,filterArgs:v,showExpert:o,showAdvanced:s,searchQuery:l}):h.jsx("div",{className:"text-center text-monitor-text-muted py-6 text-xs",children:"Loading arguments..."}):null})}):h.jsxs("div",{className:"flex flex-col bg-monitor-panel items-center justify-center p-4 text-center ring-1 ring-monitor-line rounded-xl mt-4",children:h.jsx(v3e,{className:"w-12 h-12 text-monitor-text-muted mb-3"}),h.jsx("h3",{className:"text-sm font-medium text-monitor-text-secondary mb-1",children:"No selection"}),h.jsx("p",{className:"text-xs text-monitor-text-muted",children:"Click on a filter settings to see properties"})})},o4e=()=>h.jsx("aside",{className:"w-72 bg-monitor-app border-gray-800 h-full flex flex-col bg-opacity-90",role:"complementary","aria-label":"Dashboard widgets sidebar",children:h.jsx("div",{className:"flex-1 overflow-y-auto px-4 pb-4",children:h.jsx(r4e,{})})}),i4e="group fixed left-52 top-12 z-20 will-change-transform rounded-full border border-slate-700/40 ring-1 ring-slate-700/50 shadow-lg bg-slate-800/80 hover:bg-slate-700/80 backdrop-blur-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400/60 motion-safe:transition-transform,background-color motion-safe:duration-200 active:scale-0.98",s4e="absolute inset-0 rounded-full bg-gradient-to-b from-slate-700/70 to-slate-900/70 border border-slate-600/40",a4e="pointer-events-none absolute inset-0 rounded-full ring-2 ring-violet-500/15 group-hover:ring-violet-400/25 motion-safe:transition",l4e="relative h-5 w-5 text-slate-200 transform group-hover:-translate-x-0.5 motion-safe:transition-transform",oZ=y.memo(({onClose:e})=>{const t=y.useRef(e);y.useEffect(()=>{t.current=e},e),y.useEffect(()=>{const r=o=>{var s;(o.metaKey||o.ctrlKey)&&o.key.toLowerCase()==="b"&&(o.preventDefault(),(s=t.current)==null||s.call(t))};return window.addEventListener("keydown",r,{capture:!1}),()=>window.removeEventListener("keydown",r)},);const n=y.useMemo(()=>h.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",className:l4e,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:h.jsx("path",{d:"M15 18l-6-6 6-6"})}),);return h.jsx("button",{onClick:()=>{var r;return(r=t.current)==null?void 0:r.call(t)},className:i4e,"aria-label":"Close sidebar",title:"Close sidebar • Ctrl/⌘ + B","aria-controls":"app-sidebar","aria-expanded":!0,children:h.jsxs("div",{className:"relative h-9 w-9 grid place-items-center rounded-full select-none",children:h.jsx("div",{className:s4e}),h.jsx("span",{className:a4e}),n})})});oZ.displayName="SidebarCloseButton";const c4e=Gz.WidthProvider(Gz.Responsive),u4e=()=>{const e=Hn(),t=Xe(l=>l.widgets.activeWidgets),n=Xe(l=>l.widgets.configs),r=Xe(l=>l.layout.isSidebarOpen),o=y.useRef(!1),i=y.useMemo(()=>{const l=window.innerHeight-64;return Math.floor(l/14.8)},),s=y.useMemo(()=>({lg:t.map(l=>({i:l.id,x:l.x,y:l.y,w:l.w,h:l.h,minW:2,minH:2}))}),t),a=y.useCallback(l=>{const c=sLe(l.type);if(!c)return console.warn(`No definition found for widget type: ${l.type}`),null;const d=c.component;return h.jsx("div",{children:h.jsx(d,{id:l.id,config:nl.id||{isMaximized:!1,isMinimized:!1,settings:{}},isDetached:l.isDetached,detachedFilterIdx:l.detachedFilterIdx})},l.id)},n);return h.jsxs("div",{className:"h-screen bg-main",children:h.jsx("div",{className:"fixed top-0 left-0 right-0 h-16 z-20",children:h.jsx(W3e,{})}),h.jsxs("div",{className:"flex pt-8 h-calc(100vh-4rem)",children:h.jsx("div",{id:"app-sidebar",className:"fixed top-16 bottom-0 left-0 w-72 z-10 bg-slate-800/95 transition-transform duration-300 ease-in-out will-change-transform",style:{transform:r?"translateX(0)":"translateX(-100%)"},children:h.jsx(o4e,{})}),r&&h.jsx(oZ,{onClose:()=>e(pI())}),h.jsx("main",{className:"flex-1 h-full pb-4 pt-4 pl-0 transition-transform duration-300 will-change-transform",style:{transform:r?"translateX(256px)":"translateX(0)",paddingRight:r?"272px":"16px",opacity:o.current?.2:1},children:h.jsx(c4e,{className:"layout h-full",layouts:s,breakpoints:{lg:1200,md:996,sm:768,xs:480,xxs:0},cols:{lg:24,md:24,sm:12,xs:6,xxs:2},rowHeight:i,onDragStart:()=>{o.current=!0},onDragStop:(l,c,d)=>{o.current=!1,e(kz({id:d.i,x:d.x,y:d.y,w:d.w,h:d.h}))},onResizeStart:()=>{o.current=!0},onResizeStop:(l,c,d)=>{o.current=!1,e(kz({id:d.i,x:d.x,y:d.y,w:d.w,h:d.h}))},isDraggable:!0,isResizable:!0,margin:16,16,containerPadding:16,16,draggableCancel:".no-drag",useCSSTransforms:!0,children:t.map(a)})})})})};var EA="ToastProvider",NA,d4e,f4e=mb("Toast"),iZ,g5e=to("Toast",f4e),h4e,bw=iZ(EA),sZ=e=>{const{__scopeToast:t,label:n="Notification",duration:r=5e3,swipeDirection:o="right",swipeThreshold:i=50,children:s}=e,a,l=y.useState(null),c,d=y.useState(0),f=y.useRef(!1),p=y.useRef(!1);return n.trim()||console.error(`Invalid prop \`label\` supplied to \`${EA}\`. Expected non-empty \`string\`.`),h.jsx(NA.Provider,{scope:t,children:h.jsx(h4e,{scope:t,label:n,duration:r,swipeDirection:o,swipeThreshold:i,toastCount:c,viewport:a,onViewportChange:l,onToastAdd:y.useCallback(()=>d(g=>g+1),),onToastRemove:y.useCallback(()=>d(g=>g-1),),isFocusedToastEscapeKeyDownRef:f,isClosePausedRef:p,children:s})})};sZ.displayName=EA;var aZ="ToastViewport",p4e="F8",NT="toast.viewportPause",RT="toast.viewportResume",lZ=y.forwardRef((e,t)=>{const{__scopeToast:n,hotkey:r=p4e,label:o="Notifications ({hotkey})",...i}=e,s=bw(aZ,n),a=d4e(n),l=y.useRef(null),c=y.useRef(null),d=y.useRef(null),f=y.useRef(null),p=Pt(t,f,s.onViewportChange),g=r.join("+").replace(/Key/g,"").replace(/Digit/g,""),v=s.toastCount>0;y.useEffect(()=>{const _=x=>{var C;r.length!==0&&r.every(E=>xE||x.code===E)&&((C=f.current)==null||C.focus())};return document.addEventListener("keydown",_),()=>document.removeEventListener("keydown",_)},r),y.useEffect(()=>{const _=l.current,x=f.current;if(v&&_&&x){const w=()=>{if(!s.isClosePausedRef.current){const P=new CustomEvent(NT);x.dispatchEvent(P),s.isClosePausedRef.current=!0}},C=()=>{if(s.isClosePausedRef.current){const P=new CustomEvent(RT);x.dispatchEvent(P),s.isClosePausedRef.current=!1}},E=P=>{!_.contains(P.relatedTarget)&&C()},R=()=>{_.contains(document.activeElement)||C()};return _.addEventListener("focusin",w),_.addEventListener("focusout",E),_.addEventListener("pointermove",w),_.addEventListener("pointerleave",R),window.addEventListener("blur",w),window.addEventListener("focus",C),()=>{_.removeEventListener("focusin",w),_.removeEventListener("focusout",E),_.removeEventListener("pointermove",w),_.removeEventListener("pointerleave",R),window.removeEventListener("blur",w),window.removeEventListener("focus",C)}}},v,s.isClosePausedRef);const b=y.useCallback(({tabbingDirection:_})=>{const w=a().map(C=>{const E=C.ref.current,R=E,...R4e(E);return _==="forwards"?R:R.reverse()});return(_==="forwards"?w.reverse():w).flat()},a);return y.useEffect(()=>{const _=f.current;if(_){const x=w=>{var R,P,N;const C=w.altKey||w.ctrlKey||w.metaKey;if(w.key==="Tab"&&!C){const k=document.activeElement,I=w.shiftKey;if(w.target===_&&I){(R=c.current)==null||R.focus();return}const H=b({tabbingDirection:I?"backwards":"forwards"}),q=H.findIndex(M=>M===k);pN(H.slice(q+1))?w.preventDefault():I?(P=c.current)==null||P.focus():(N=d.current)==null||N.focus()}};return _.addEventListener("keydown",x),()=>_.removeEventListener("keydown",x)}},a,b),h.jsxs(Ixe,{ref:l,role:"region","aria-label":o.replace("{hotkey}",g),tabIndex:-1,style:{pointerEvents:v?void 0:"none"},children:v&&h.jsx(TT,{ref:c,onFocusFromOutsideViewport:()=>{const _=b({tabbingDirection:"forwards"});pN(_)}}),h.jsx(NA.Slot,{scope:n,children:h.jsx(Ze.ol,{tabIndex:-1,...i,ref:p})}),v&&h.jsx(TT,{ref:d,onFocusFromOutsideViewport:()=>{const _=b({tabbingDirection:"backwards"});pN(_)}})})});lZ.displayName=aZ;var cZ="ToastFocusProxy",TT=y.forwardRef((e,t)=>{const{__scopeToast:n,onFocusFromOutsideViewport:r,...o}=e,i=bw(cZ,n);return h.jsx(db,{"aria-hidden":!0,tabIndex:0,...o,ref:t,style:{position:"fixed"},onFocus:s=>{var c;const a=s.relatedTarget;!((c=i.viewport)!=null&&c.contains(a))&&r()}})});TT.displayName=cZ;var Rg="Toast",m4e="toast.swipeStart",g4e="toast.swipeMove",v4e="toast.swipeCancel",y4e="toast.swipeEnd",uZ=y.forwardRef((e,t)=>{const{forceMount:n,open:r,defaultOpen:o,onOpenChange:i,...s}=e,a,l=xs({prop:r,defaultProp:o??!0,onChange:i,caller:Rg});return h.jsx(ys,{present:n||a,children:h.jsx(w4e,{open:a,...s,ref:t,onClose:()=>l(!1),onPause:En(e.onPause),onResume:En(e.onResume),onSwipeStart:yt(e.onSwipeStart,c=>{c.currentTarget.setAttribute("data-swipe","start")}),onSwipeMove:yt(e.onSwipeMove,c=>{const{x:d,y:f}=c.detail.delta;c.currentTarget.setAttribute("data-swipe","move"),c.currentTarget.style.setProperty("--radix-toast-swipe-move-x",`${d}px`),c.currentTarget.style.setProperty("--radix-toast-swipe-move-y",`${f}px`)}),onSwipeCancel:yt(e.onSwipeCancel,c=>{c.currentTarget.setAttribute("data-swipe","cancel"),c.currentTarget.style.removeProperty("--radix-toast-swipe-move-x"),c.currentTarget.style.removeProperty("--radix-toast-swipe-move-y"),c.currentTarget.style.removeProperty("--radix-toast-swipe-end-x"),c.currentTarget.style.removeProperty("--radix-toast-swipe-end-y")}),onSwipeEnd:yt(e.onSwipeEnd,c=>{const{x:d,y:f}=c.detail.delta;c.currentTarget.setAttribute("data-swipe","end"),c.currentTarget.style.removeProperty("--radix-toast-swipe-move-x"),c.currentTarget.style.removeProperty("--radix-toast-swipe-move-y"),c.currentTarget.style.setProperty("--radix-toast-swipe-end-x",`${d}px`),c.currentTarget.style.setProperty("--radix-toast-swipe-end-y",`${f}px`),l(!1)})})})});uZ.displayName=Rg;varx4e,b4e=iZ(Rg,{onClose(){}}),w4e=y.forwardRef((e,t)=>{const{__scopeToast:n,type:r="foreground",duration:o,open:i,onClose:s,onEscapeKeyDown:a,onPause:l,onResume:c,onSwipeStart:d,onSwipeMove:f,onSwipeCancel:p,onSwipeEnd:g,...v}=e,b=bw(Rg,n),_,x=y.useState(null),w=Pt(t,M=>x(M)),C=y.useRef(null),E=y.useRef(null),R=o||b.duration,P=y.useRef(0),N=y.useRef(R),k=y.useRef(0),{onToastAdd:I,onToastRemove:O}=b,j=En(()=>{var B;(_==null?void 0:_.contains(document.activeElement))&&((B=b.viewport)==null||B.focus()),s()}),H=y.useCallback(M=>{!M||M===1/0||(window.clearTimeout(k.current),P.current=new Date().getTime(),k.current=window.setTimeout(j,M))},j);y.useEffect(()=>{const M=b.viewport;if(M){const B=()=>{H(N.current),c==null||c()},$=()=>{const W=new Date().getTime()-P.current;N.current=N.current-W,window.clearTimeout(k.current),l==null||l()};return M.addEventListener(NT,$),M.addEventListener(RT,B),()=>{M.removeEventListener(NT,$),M.removeEventListener(RT,B)}}},b.viewport,R,l,c,H),y.useEffect(()=>{i&&!b.isClosePausedRef.current&&H(R)},i,R,b.isClosePausedRef,H),y.useEffect(()=>(I(),()=>O()),I,O);const q=y.useMemo(()=>_?vZ(_):null,_);return b.viewport?h.jsxs(h.Fragment,{children:q&&h.jsx(S4e,{__scopeToast:n,role:"status","aria-live":r==="foreground"?"assertive":"polite","aria-atomic":!0,children:q}),h.jsx(x4e,{scope:n,onClose:j,children:al.createPortal(h.jsx(NA.ItemSlot,{scope:n,children:h.jsx(Pxe,{asChild:!0,onEscapeKeyDown:yt(a,()=>{b.isFocusedToastEscapeKeyDownRef.current||j(),b.isFocusedToastEscapeKeyDownRef.current=!1}),children:h.jsx(Ze.li,{role:"status","aria-live":"off","aria-atomic":!0,tabIndex:0,"data-state":i?"open":"closed","data-swipe-direction":b.swipeDirection,...v,ref:w,style:{userSelect:"none",touchAction:"none",...e.style},onKeyDown:yt(e.onKeyDown,M=>{M.key==="Escape"&&(a==null||a(M.nativeEvent),M.nativeEvent.defaultPrevented||(b.isFocusedToastEscapeKeyDownRef.current=!0,j()))}),onPointerDown:yt(e.onPointerDown,M=>{M.button===0&&(C.current={x:M.clientX,y:M.clientY})}),onPointerMove:yt(e.onPointerMove,M=>{if(!C.current)return;const B=M.clientX-C.current.x,$=M.clientY-C.current.y,W=!!E.current,G="left","right".includes(b.swipeDirection),z="left","up".includes(b.swipeDirection)?Math.min:Math.max,K=G?z(0,B):0,Q=G?0:z(0,$),re=M.pointerType==="touch"?10:2,ae={x:K,y:Q},de={originalEvent:M,delta:ae};W?(E.current=ae,p0(g4e,f,de,{discrete:!1})):Zz(ae,b.swipeDirection,re)?(E.current=ae,p0(m4e,d,de,{discrete:!1}),M.target.setPointerCapture(M.pointerId)):(Math.abs(B)>re||Math.abs($)>re)&&(C.current=null)}),onPointerUp:yt(e.onPointerUp,M=>{const B=E.current,$=M.target;if($.hasPointerCapture(M.pointerId)&&$.releasePointerCapture(M.pointerId),E.current=null,C.current=null,B){const W=M.currentTarget,G={originalEvent:M,delta:B};Zz(B,b.swipeDirection,b.swipeThreshold)?p0(y4e,g,G,{discrete:!0}):p0(v4e,p,G,{discrete:!0}),W.addEventListener("click",z=>z.preventDefault(),{once:!0})}})})})}),b.viewport)})}):null}),S4e=e=>{const{__scopeToast:t,children:n,...r}=e,o=bw(Rg,t),i,s=y.useState(!1),a,l=y.useState(!1);return E4e(()=>s(!0)),y.useEffect(()=>{const c=window.setTimeout(()=>l(!0),1e3);return()=>window.clearTimeout(c)},),a?null:h.jsx(ig,{asChild:!0,children:h.jsx(db,{...r,children:i&&h.jsxs(h.Fragment,{children:o.label," ",n})})})},_4e="ToastTitle",dZ=y.forwardRef((e,t)=>{const{__scopeToast:n,...r}=e;return h.jsx(Ze.div,{...r,ref:t})});dZ.displayName=_4e;var C4e="ToastDescription",fZ=y.forwardRef((e,t)=>{const{__scopeToast:n,...r}=e;return h.jsx(Ze.div,{...r,ref:t})});fZ.displayName=C4e;var hZ="ToastAction",pZ=y.forwardRef((e,t)=>{const{altText:n,...r}=e;return n.trim()?h.jsx(gZ,{altText:n,asChild:!0,children:h.jsx(RA,{...r,ref:t})}):(console.error(`Invalid prop \`altText\` supplied to \`${hZ}\`. Expected non-empty \`string\`.`),null)});pZ.displayName=hZ;var mZ="ToastClose",RA=y.forwardRef((e,t)=>{const{__scopeToast:n,...r}=e,o=b4e(mZ,n);return h.jsx(gZ,{asChild:!0,children:h.jsx(Ze.button,{type:"button",...r,ref:t,onClick:yt(e.onClick,o.onClose)})})});RA.displayName=mZ;var gZ=y.forwardRef((e,t)=>{const{__scopeToast:n,altText:r,...o}=e;return h.jsx(Ze.div,{"data-radix-toast-announce-exclude":"","data-radix-toast-announce-alt":r||void 0,...o,ref:t})});function vZ(e){const t=;return Array.from(e.childNodes).forEach(r=>{if(r.nodeType===r.TEXT_NODE&&r.textContent&&t.push(r.textContent),N4e(r)){const o=r.ariaHidden||r.hidden||r.style.display==="none",i=r.dataset.radixToastAnnounceExclude==="";if(!o)if(i){const s=r.dataset.radixToastAnnounceAlt;s&&t.push(s)}else t.push(...vZ(r))}}),t}function p0(e,t,n,{discrete:r}){const o=n.originalEvent.currentTarget,i=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:n});t&&o.addEventListener(e,t,{once:!0}),r?ib(o,i):o.dispatchEvent(i)}var Zz=(e,t,n=0)=>{const r=Math.abs(e.x),o=Math.abs(e.y),i=r>o;return t==="left"||t==="right"?i&&r>n:!i&&o>n};function E4e(e=()=>{}){const t=En(e);Lo(()=>{let n=0,r=0;return n=window.requestAnimationFrame(()=>r=window.requestAnimationFrame(t)),()=>{window.cancelAnimationFrame(n),window.cancelAnimationFrame(r)}},t)}function N4e(e){return e.nodeType===e.ELEMENT_NODE}function R4e(e){const t=,n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:r=>{const o=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||o?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function pN(e){const t=document.activeElement;return e.some(n=>n===t?!0:(n.focus(),document.activeElement!==t))}var T4e=sZ,yZ=lZ,xZ=uZ,bZ=dZ,wZ=fZ,SZ=pZ,_Z=RA;const Qz=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,Jz=y9,k4e=(e,t)=>n=>{var r;if((t==null?void 0:t.variants)==null)return Jz(e,n==null?void 0:n.class,n==null?void 0:n.className);const{variants:o,defaultVariants:i}=t,s=Object.keys(o).map(c=>{const d=n==null?void 0:nc,f=i==null?void 0:ic;if(d===null)return null;const p=Qz(d)||Qz(f);return ocp}),a=n&&Object.entries(n).reduce((c,d)=>{letf,p=d;return p===void 0||(cf=p),c},{}),l=t==null||(r=t.compoundVariants)===null||r===void 0?void 0:r.reduce((c,d)=>{let{class:f,className:p,...g}=d;return Object.entries(g).every(v=>{letb,_=v;return Array.isArray(_)?_.includes({...i,...a}b):{...i,...a}b===_})?...c,f,p:c},);return Jz(e,s,l,n==null?void 0:n.class,n==null?void 0:n.className)},P4e=T4e,CZ=y.forwardRef(({className:e,...t},n)=>h.jsx(yZ,{ref:n,className:nt("fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-420px",e),...t}));CZ.displayName=yZ.displayName;const I4e=k4e("group pointer-events-auto relative flex w-1/2 items-center justify-between space-x-2 overflow-hidden rounded-md border p-3 pr-4 shadow-lg transition-all data-swipe=cancel:translate-x-0 data-swipe=end:translate-x-var(--radix-toast-swipe-end-x) data-swipe=move:translate-x-var(--radix-toast-swipe-move-x) data-swipe=move:transition-none data-state=open:animate-in data-state=closed:animate-out data-swipe=end:animate-out data-state=closed:fade-out-80 data-state=closed:slide-out-to-right-full data-state=open:slide-in-from-top-full data-state=open:sm:slide-in-from-bottom-full",{variants:{variant:{default:"border bg-slate-950 text-foreground",destructive:"destructive group border-destructive bg-red-900 text-gray-950 text-destructive-foreground"}},defaultVariants:{variant:"default"}}),EZ=y.forwardRef(({className:e,variant:t,...n},r)=>h.jsx(xZ,{ref:r,className:nt(I4e({variant:t}),e),...n}));EZ.displayName=xZ.displayName;const A4e=y.forwardRef(({className:e,...t},n)=>h.jsx(SZ,{ref:n,className:nt("inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-.destructive:border-muted/40 group-.destructive:hover:border-destructive/30 group-.destructive:hover:bg-destructive group-.destructive:hover:text-destructive-foreground group-.destructive:focus:ring-destructive",e),...t}));A4e.displayName=SZ.displayName;const NZ=y.forwardRef(({className:e,...t},n)=>h.jsx(_Z,{ref:n,className:nt("absolute right-2 top-2 rounded-md p-1 text-gray-800 opacity-0 transition-opacity hover:text-gray-950 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-.destructive:text-red-300 group-.destructive:hover:text-red-50 group-.destructive:focus:ring-red-400 group-.destructive:focus:ring-offset-red-600",e),"toast-close":"",...t,children:h.jsx(eb,{className:"h-4 w-4"})}));NZ.displayName=_Z.displayName;const RZ=y.forwardRef(({className:e,...t},n)=>h.jsx(bZ,{ref:n,className:nt("text-sm font-semibold",e),...t}));RZ.displayName=bZ.displayName;const TZ=y.forwardRef(({className:e,...t},n)=>h.jsx(wZ,{ref:n,className:nt("text-sm opacity-90",e),...t}));TZ.displayName=wZ.displayName;function M4e(){const{toasts:e}=P9();return h.jsxs(P4e,{children:e.map(function({id:t,title:n,description:r,type:o,...i}){return h.jsxs(EZ,{type:o,...i,children:h.jsxs("div",{className:"grid gap-1",children:n&&h.jsx(RZ,{children:n}),r&&h.jsx(TZ,{children:r})}),h.jsx(NZ,{})},t)}),h.jsx(CZ,{})})}const j4e=()=>h.jsx(rpe,{store:Zo,children:h.jsxs("div",{className:" bg-gray-950 text-white",children:h.jsx(u4e,{}),h.jsx(M4e,{})})});mN.createRoot(document.getElementById("root")).render(h.jsx(y.StrictMode,{children:h.jsx(Z8,{children:h.jsx(j4e,{})})}));</script> + <style rel="stylesheet" crossorigin>*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where(title){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where(type=button),input:where(type=reset),input:where(type=submit){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}type=search{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,role=button{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}hidden:where(:not(hidden=until-found)){display:none}body{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:root{--background: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 0 0% 9%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 6 * % 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 0 0% 98%;--border: 0 0% 89.8%;--input: 0 0% 89.8%;--ring: 0 0% 3.9%;--chart-1: 12 76% 61%;--chart-2: 173 58% 39%;--chart-3: 197 37% 24%;--chart-4: 43 74% 66%;--chart-5: 27 87% 67%;--radius: .5rem;--font-ui: "Inter var", Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Liberation Mono", Consolas, monospace;--media-video: 217 91% 60%;--media-audio: 162 84% 39%;--media-text: 36 92% 53%;--media-file: 350 82% 50%}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.\!widget,.widget{border-radius:.5rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1));--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-secondary{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.input{border-radius:.375rem;--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1));padding:.5rem .75rem}.input:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1));outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-4{top:1rem;right:1rem;bottom:1rem;left:1rem}.-left-0\.5{left:-.125rem}.-top-0\.5{top:-.125rem}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-2{left:.5rem}.left-2\.5{left:.625rem}.left-52{left:13rem}.left-\50\%\{left:50%}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-12{top:3rem}.top-14{top:3.5rem}.top-16{top:4rem}.top-2{top:.5rem}.top-4{top:1rem}.top-\50\%\{top:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\100\{z-index:100}.\!-m-px{margin:-1px!important}.-m-4{margin:-1rem}.m-4{margin:1rem}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.mx-1\.5{margin-left:.375rem;margin-right:.375rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-auto{margin-top:auto;margin-bottom:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.\!h-px{height:1px!important}.h-0\.5{height:.125rem}.h-1{height:.25rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-36{height:9rem}.h-4{height:1rem}.h-40{height:10rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\80px\{height:80px}.h-\calc\(100vh-4rem\)\{height:calc(100vh - 4rem)}.h-auto{height:auto}.h-fit{height:-moz-fit-content;height:fit-content}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-0{max-height:0px}.max-h-80{max-height:20rem}.max-h-96{max-height:24rem}.max-h-\280px\{max-height:280px}.max-h-\320px\{max-height:320px}.max-h-screen{max-height:100vh}.min-h-0{min-height:0px}.\!w-px{width:1px!important}.w-0\.5{width:.125rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-52{width:13rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\600px\{width:600px}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-32{min-width:8rem}.min-w-80{min-width:20rem}.min-w-\120px\{min-width:120px}.min-w-\220px\{min-width:220px}.min-w-\60px\{min-width:60px}.min-w-\70px\{min-width:70px}.min-w-\8rem\{min-width:8rem}.max-w-2xl{max-width:42rem}.max-w-\140px\{max-width:140px}.max-w-\20rem\{max-width:20rem}.max-w-\90vw\{max-width:90vw}.max-w-lg{max-width:32rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\-50\%\{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\-50\%\{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-105{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\repeat\(auto-fit\,minmax\(280px\,1fr\)\)\{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}.grid-cols-\repeat\(auto-fit\,minmax\(min\(200px\,100\%\)\,1fr\)\)\{grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.justify-stretch{justify-content:stretch}.gap-0{gap:0px}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.space-x-2>:not(hidden)~:not(hidden){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-0\.5>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not(hidden)~:not(hidden){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not(hidden)~:not(hidden){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-monitor-divider>:not(hidden)~:not(hidden){border-color:#ffffff1a}.overflow-auto{overflow:auto}.\!overflow-hidden{overflow:hidden!important}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\!whitespace-nowrap{white-space:nowrap!important}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-\inherit\{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.\!border-0{border-width:0px!important}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-r-2{border-right-width:2px}.border-t{border-top-width:1px}.border-solid{border-style:solid}.border-amber-700\/60{border-color:#b4530999}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-blue-700\/50{border-color:#1d4ed880}.border-current{border-color:currentColor}.border-emerald-400\/60{border-color:#34d39999}.border-emerald-700\/60{border-color:#04785799}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity, 1))}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-600\/50{border-color:#4b556380}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-gray-900{--tw-border-opacity: 1;border-color:rgb(17 24 39 / var(--tw-border-opacity, 1))}.border-green-500\/30{border-color:#22c55e4d}.border-green-600{--tw-border-opacity: 1;border-color:rgb(22 163 74 / var(--tw-border-opacity, 1))}.border-monitor-line,.border-monitor-line\/10{border-color:#ffffff1a}.border-monitor-line\/50{border-color:#ffffff80}.border-red-700\/60{border-color:#b91c1c99}.border-red-800\/50{border-color:#991b1b80}.border-slate-600\/40{border-color:#47556966}.border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.border-slate-700\/40{border-color:#33415566}.border-slate-700\/50{border-color:#33415580}.border-transparent{border-color:transparent}.border-white\/10{border-color:#ffffff1a}.border-white\/5{border-color:#ffffff0d}.border-white\/\0\.03\{border-color:#ffffff08}.border-yellow-500{--tw-border-opacity: 1;border-color:rgb(234 179 8 / var(--tw-border-opacity, 1))}.border-yellow-600{--tw-border-opacity: 1;border-color:rgb(202 138 4 / var(--tw-border-opacity, 1))}.border-l-amber-500\/60{border-left-color:#f59e0b99}.border-l-blue-500\/60{border-left-color:#3b82f699}.border-l-emerald-500\/60{border-left-color:#10b98199}.border-l-orange-500{--tw-border-opacity: 1;border-left-color:rgb(249 115 22 / var(--tw-border-opacity, 1))}.border-l-red-500{--tw-border-opacity: 1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-l-slate-500\/60{border-left-color:#64748b99}.border-l-transparent{border-left-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity, 1))}.bg-amber-900\/20{background-color:#78350f33}.bg-amber-900\/40{background-color:#78350f66}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/10{background-color:#0000001a}.bg-black\/20{background-color:#0003}.bg-black\/40{background-color:#0006}.bg-black\/60{background-color:#0009}.bg-black\/80{background-color:#000c}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/20{background-color:#3b82f633}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-blue-900\/30{background-color:#1e3a8a4d}.bg-cyan-500\/20{background-color:#06b6d433}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/10{background-color:#10b9811a}.bg-emerald-500\/20{background-color:#10b98133}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity, 1))}.bg-emerald-900\/15{background-color:#064e3b26}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-700\/50{background-color:#37415180}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-800\/50{background-color:#1f293780}.bg-gray-800\/60{background-color:#1f293799}.bg-gray-800\/80{background-color:#1f2937cc}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-900\/0{background-color:#11182700}.bg-gray-900\/50{background-color:#11182780}.bg-gray-900\/70{background-color:#111827b3}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-600\/20{background-color:#16a34a33}.bg-green-700\/60{background-color:#15803d99}.bg-indigo-500\/20{background-color:#6366f133}.bg-monitor-app{--tw-bg-opacity: 1;background-color:rgb(12 17 23 / var(--tw-bg-opacity, 1))}.bg-monitor-panel{--tw-bg-opacity: 1;background-color:rgb(15 20 27 / var(--tw-bg-opacity, 1))}.bg-monitor-panel\/30{background-color:#0f141b4d}.bg-monitor-panel\/40{background-color:#0f141b66}.bg-monitor-panel\/55{background-color:#0f141b8c}.bg-monitor-panel\/60{background-color:#0f141b99}.bg-monitor-surface{--tw-bg-opacity: 1;background-color:rgb(11 16 22 / var(--tw-bg-opacity, 1))}.bg-monitor-surface\/55{background-color:#0b10168c}.bg-orange-50{--tw-bg-opacity: 1;background-color:rgb(255 247 237 / var(--tw-bg-opacity, 1))}.bg-orange-500\/20{background-color:#f9731633}.bg-orange-800{--tw-bg-opacity: 1;background-color:rgb(154 52 18 / var(--tw-bg-opacity, 1))}.bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.bg-pink-500\/20{background-color:#ec489933}.bg-purple-500\/20{background-color:#a855f733}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-700{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.bg-red-800{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity, 1))}.bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.bg-red-900\/20{background-color:#7f1d1d33}.bg-red-900\/50{background-color:#7f1d1d80}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.bg-rose-600{--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity, 1))}.bg-slate-300{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity, 1))}.bg-slate-500\/50{background-color:#64748b80}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.bg-slate-800\/80{background-color:#1e293bcc}.bg-slate-800\/95{background-color:#1e293bf2}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.bg-slate-900\/60{background-color:#0f172a99}.bg-slate-900\/95{background-color:#0f172af2}.bg-slate-950{--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity, 1))}.bg-teal-500{--tw-bg-opacity: 1;background-color:rgb(20 184 166 / var(--tw-bg-opacity, 1))}.bg-teal-500\/10{background-color:#14b8a61a}.bg-transparent{background-color:transparent}.bg-white\/20{background-color:#fff3}.bg-white\/5{background-color:#ffffff0d}.bg-white\/\0\.06\{background-color:#ffffff0f}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.bg-yellow-500\/20{background-color:#eab30833}.bg-yellow-500\/60{background-color:#eab30899}.bg-yellow-900\/70{background-color:#713f12b3}.bg-opacity-90{--tw-bg-opacity: .9}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.from-slate-700\/70{--tw-gradient-from: rgb(51 65 85 / .7) var(--tw-gradient-from-position);--tw-gradient-to: rgb(51 65 85 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-slate-900\/70{--tw-gradient-to: rgb(15 23 42 / .7) var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.fill-gray-800{fill:#1f2937}.fill-gray-900{fill:#111827}.\!p-0{padding:0!important}.p-0{padding:0}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.p-\1px\{padding:1px}.px-0{padding-left:0;padding-right:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-0{padding-left:0}.pl-2{padding-left:.5rem}.pl-8{padding-left:2rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-\-0\.125em\{vertical-align:-.125em}.font-cond{font-family:Archivo Narrow,Inter,sans-serif}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.font-ui{font-family:Inter,system-ui,sans-serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\10px\{font-size:10px}.text-\11px\{font-size:11px}.text-\8px\{font-size:8px}.text-\9px\{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-blue-100{--tw-text-opacity: 1;color:rgb(219 234 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-300\/90{color:#6ee7b7e6}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-emerald-500{--tw-text-opacity: 1;color:rgb(16 185 129 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-gray-950{--tw-text-opacity: 1;color:rgb(3 7 18 / var(--tw-text-opacity, 1))}.text-green-100{--tw-text-opacity: 1;color:rgb(220 252 231 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-indigo-300{--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity, 1))}.text-monitor-active-filter{--tw-text-opacity: 1;color:rgb(76 201 240 / var(--tw-text-opacity, 1))}.text-monitor-download{--tw-text-opacity: 1;color:rgb(96 129 183 / var(--tw-text-opacity, 1))}.text-monitor-text-muted{--tw-text-opacity: 1;color:rgb(154 163 174 / var(--tw-text-opacity, 1))}.text-monitor-text-muted\/50{color:#9aa3ae80}.text-monitor-text-muted\/60{color:#9aa3ae99}.text-monitor-text-primary{--tw-text-opacity: 1;color:rgb(242 242 242 / var(--tw-text-opacity, 1))}.text-monitor-text-secondary{--tw-text-opacity: 1;color:rgb(199 199 199 / var(--tw-text-opacity, 1))}.text-orange-300{--tw-text-opacity: 1;color:rgb(253 186 116 / var(--tw-text-opacity, 1))}.text-orange-500{--tw-text-opacity: 1;color:rgb(249 115 22 / var(--tw-text-opacity, 1))}.text-orange-600{--tw-text-opacity: 1;color:rgb(234 88 12 / var(--tw-text-opacity, 1))}.text-pink-300{--tw-text-opacity: 1;color:rgb(249 168 212 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-500{--tw-text-opacity: 1;color:rgb(168 85 247 / var(--tw-text-opacity, 1))}.text-red-100{--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-rose-400\/80{color:#fb7185cc}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-200\/90{color:#e2e8f0e6}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-teal-400{--tw-text-opacity: 1;color:rgb(45 212 191 / var(--tw-text-opacity, 1))}.text-teal-400\/80{color:#2dd4bfcc}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-white\/80{color:#fffc}.text-yellow-100{--tw-text-opacity: 1;color:rgb(254 249 195 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-90{opacity:.9}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color: rgb(0 0 0 / .4);--tw-shadow: var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color: rgb(0 0 0 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-gray-500\/50{--tw-shadow-color: rgb(107 114 128 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-green-500\/50{--tw-shadow-color: rgb(34 197 94 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-red-500\/50{--tw-shadow-color: rgb(239 68 68 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-yellow-500\/50{--tw-shadow-color: rgb(234 179 8 / .5);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-blue-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.ring-emerald-400\/20{--tw-ring-color: rgb(52 211 153 / .2)}.ring-emerald-400\/25{--tw-ring-color: rgb(52 211 153 / .25)}.ring-emerald-400\/30{--tw-ring-color: rgb(52 211 153 / .3)}.ring-emerald-700\/40{--tw-ring-color: rgb(4 120 87 / .4)}.ring-monitor-line{--tw-ring-color: #ffffff1a}.ring-red-600\/90{--tw-ring-color: rgb(220 38 38 / .9)}.ring-red-700\/90{--tw-ring-color: rgb(185 28 28 / .9)}.ring-sky-400{--tw-ring-opacity: 1;--tw-ring-color: rgb(56 189 248 / var(--tw-ring-opacity, 1))}.ring-slate-700\/40{--tw-ring-color: rgb(51 65 85 / .4)}.ring-slate-700\/50{--tw-ring-color: rgb(51 65 85 / .5)}.ring-teal-400\/20{--tw-ring-color: rgb(45 212 191 / .2)}.ring-transparent{--tw-ring-color: transparent}.ring-violet-500\/15{--tw-ring-color: rgb(139 92 246 / .15)}.ring-offset-2{--tw-ring-offset-width: 2px}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.\!filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-none{transition-property:none}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.will-change-transform{will-change:transform}.contain-layout{--tw-contain-layout: layout;contain:var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style)}.contain-style{--tw-contain-style: style;contain:var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style)}.stat{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.stat-label{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.bg-stat{background-color:#00000080;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s}.bg-stat:hover{background-color:#0009}.bg-info{background-color:#05966933}.bg-warning{background-color:#d9770633}.bg-danger{background-color:#e11d4833}.text-ui{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-muted{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-debug{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-warning{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-danger,.text-destructive-foreground{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-info{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.bg-info{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-warning{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.stat-num{display:inline-block;text-align:right;font-variant-numeric:tabular-nums;min-width:3.5ch}.\!\clip\:rect\(0\,0\,0\,0\)\{clip:rect(0,0,0,0)!important}html{font-family:var(--font-ui);font-size:14px;line-height:1.35;font-optical-sizing:auto}.metrics,.table td,.gauge .value{font-variant-numeric:tabular-nums slashed-zero;letter-spacing:.1px}.code,.logs{font-family:var(--font-mono);font-size:12.5px;font-variant-ligatures:none}.react-grid-layout.layout{min-height:100%!important}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb{border-radius:.25rem;--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}select,select option{background-color:#0f172a;color:#e2e8f0}select option:checked{background:linear-gradient(#16a34a,#16a34a);background-color:#1e293b}select option:hover{background:linear-gradient(#1e293b,#1e293b);background-color:#34d399}.hover\:bg-secondary:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-slate-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-500::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:bg-black\/30:hover{background-color:#0000004d}.hover\:bg-blue-500\/10:hover{background-color:#3b82f61a}.hover\:bg-emerald-500\/30:hover{background-color:#10b9814d}.hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700\/50:hover{background-color:#37415180}.hover\:bg-gray-700\/80:hover{background-color:#374151cc}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800\/30:hover{background-color:#1f29374d}.hover\:bg-gray-800\/60:hover{background-color:#1f293799}.hover\:bg-gray-900\/50:hover{background-color:#11182780}.hover\:bg-monitor-panel\/80:hover{background-color:#0f141bcc}.hover\:bg-orange-900:hover{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.hover\:bg-red-500\/10:hover{background-color:#ef44441a}.hover\:bg-red-800:hover{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity, 1))}.hover\:bg-red-900\/50:hover{background-color:#7f1d1d80}.hover\:bg-slate-600:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-700\/80:hover{background-color:#334155cc}.hover\:bg-slate-800\/60:hover{background-color:#1e293b99}.hover\:bg-slate-900:hover{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.hover\:text-blue-400:hover{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.hover\:text-gray-100:hover{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-gray-950:hover{--tw-text-opacity: 1;color:rgb(3 7 18 / var(--tw-text-opacity, 1))}.hover\:text-monitor-text-primary:hover{--tw-text-opacity: 1;color:rgb(242 242 242 / var(--tw-text-opacity, 1))}.hover\:text-red-400:hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.hover\:text-slate-100:hover{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.hover\:text-slate-200:hover{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:text-yellow-500:hover{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:opacity-80:hover{opacity:.8}.hover\:text-ui:hover{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus\:border-blue-500\/50:focus{border-color:#3b82f680}.focus\:bg-gray-700:focus{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.focus\:text-gray-100:focus{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.focus\:opacity-100:focus{opacity:1}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-blue-500\/50:focus{--tw-ring-color: rgb(59 130 246 / .5)}.focus\:ring-gray-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:border-emerald-500\/40:focus-visible{border-color:#10b98166}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-emerald-400\/30:focus-visible{--tw-ring-color: rgb(52 211 153 / .3)}.focus-visible\:ring-emerald-500\/40:focus-visible{--tw-ring-color: rgb(16 185 129 / .4)}.focus-visible\:ring-violet-400\/60:focus-visible{--tw-ring-color: rgb(167 139 250 / .6)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.focus-visible\:ring-offset-black:focus-visible{--tw-ring-offset-color: #000}.active\:scale-\0\.98\:active{--tw-scale-x: .98;--tw-scale-y: .98;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}.group:hover .group-hover\:-translate-x-0\.5{--tw-translate-x: -.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:scale-110{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-monitor-text-primary{--tw-text-opacity: 1;color:rgb(242 242 242 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:opacity-100{opacity:1}.group:hover .group-hover\:ring-violet-400\/25{--tw-ring-color: rgb(167 139 250 / .25)}.group.destructive .group-\\.destructive\\:text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.group.destructive .group-\\.destructive\\:hover\:text-red-50:hover{--tw-text-opacity: 1;color:rgb(254 242 242 / var(--tw-text-opacity, 1))}.group.destructive .group-\\.destructive\\:hover\:text-destructive-foreground:hover{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.group.destructive .group-\\.destructive\\:focus\:ring-red-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity, 1))}.group.destructive .group-\\.destructive\\:focus\:ring-offset-red-600:focus{--tw-ring-offset-color: #dc2626}.data-\disabled\\:pointer-events-nonedata-disabled{pointer-events:none}.data-\state\=inactive\\:hiddendata-state=inactive{display:none}.data-\state\=checked\\:translate-x-6data-state=checked{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\state\=unchecked\\:translate-x-0\.5data-state=unchecked{--tw-translate-x: .125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\swipe\=cancel\\:translate-x-0data-swipe=cancel{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\swipe\=end\\:translate-x-\var\(--radix-toast-swipe-end-x\)\data-swipe=end{--tw-translate-x: var(--radix-toast-swipe-end-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\swipe\=move\\:translate-x-\var\(--radix-toast-swipe-move-x\)\data-swipe=move{--tw-translate-x: var(--radix-toast-swipe-move-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes overlayHide{0%{opacity:1}to{opacity:0}}.data-\state\=closed\\:animate-overlayHidedata-state=closed{animation:overlayHide .15s cubic-bezier(.16,1,.5,1)}@keyframes popoverHide{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-8px) scale(.96)}}.data-\state\=closed\\:animate-popoverHidedata-state=closed{animation:popoverHide .2s cubic-bezier(.5,0,.84,0)}@keyframes overlayShow{0%{opacity:0}to{opacity:1}}.data-\state\=open\\:animate-overlayShowdata-state=open{animation:overlayShow .15s cubic-bezier(.16,1,.5,1)}@keyframes popoverShow{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.data-\state\=open\\:animate-popoverShowdata-state=open{animation:popoverShow .3s cubic-bezier(.16,1,.3,1)}.data-\state\=active\\:border-b-2data-state=active{border-bottom-width:2px}.data-\state\=active\\:border-gray-600data-state=active{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.data-\state\=active\\:border-monitor-active-filterdata-state=active{--tw-border-opacity: 1;border-color:rgb(76 201 240 / var(--tw-border-opacity, 1))}.data-\state\=active\\:border-monitor-active-tabdata-state=active{--tw-border-opacity: 1;border-color:rgb(35 137 173 / var(--tw-border-opacity, 1))}.data-\state\=checked\\:border-emerald-400data-state=checked{--tw-border-opacity: 1;border-color:rgb(52 211 153 / var(--tw-border-opacity, 1))}.data-\state\=active\\:bg-monitor-linedata-state=active{background-color:#ffffff1a}.data-\state\=checked\\:bg-emerald-400\/90data-state=checked{background-color:#34d399e6}.data-\state\=unchecked\\:bg-rose-600\/50data-state=unchecked{background-color:#e11d4880}.data-\state\=active\\:text-monitor-active-filterdata-state=active{--tw-text-opacity: 1;color:rgb(76 201 240 / var(--tw-text-opacity, 1))}.data-\state\=active\\:text-monitor-active-tabdata-state=active{--tw-text-opacity: 1;color:rgb(35 137 173 / var(--tw-text-opacity, 1))}.data-\disabled\\:opacity-50data-disabled{opacity:.5}.data-\state\=active\\:shadow-smdata-state=active{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.data-\swipe\=move\\:transition-nonedata-swipe=move{transition-property:none}@media (prefers-reduced-motion: no-preference){.motion-safe\:transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.motion-safe\:transition-\transform\,background-color\{transition-property:transform,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.motion-safe\:transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.motion-safe\:duration-200{transition-duration:.2s}}@media (prefers-reduced-motion: reduce){@keyframes spin{to{transform:rotate(360deg)}}.motion-reduce\:animate-\spin_1\.5s_linear_infinite\{animation:spin 1.5s linear infinite}}@media (min-width: 640px){.sm\:bottom-0{bottom:0}.sm\:right-0{right:0}.sm\:top-auto{top:auto}.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.sm\:justify-end{justify-content:flex-end}.sm\:space-x-2>:not(hidden)~:not(hidden){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:.5rem}.sm\:text-left{text-align:left}}@media (min-width: 768px){.md\:max-w-\420px\{max-width:420px}.md\:gap-4{gap:1rem}.md\:text-\10px\{font-size:10px}.md\:text-base{font-size:1rem;line-height:1.5rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}.md\:text-xs{font-size:.75rem;line-height:1rem}}@media (min-width: 1024px){.lg\:block{display:block}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-4{padding-left:1rem;padding-right:1rem}.lg\:pl-4{padding-left:1rem}.lg\:text-\11px\{font-size:11px}.lg\:text-base{font-size:1rem;line-height:1.5rem}.lg\:text-sm{font-size:.875rem;line-height:1.25rem}.lg\:text-xs{font-size:.75rem;line-height:1rem}}@media (min-width: 1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:gap-5{gap:1.25rem}}.\\&_button\:hover\\:bg-gray-700 button:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.\\&_button\\:border-gray-700 button{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.\\&_button\\:bg-gray-800 button{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.\\&_button\\:text-white button{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.uplot,.uplot *,.uplot *:before,.uplot *:after{box-sizing:border-box}.uplot{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5;width:-moz-min-content;width:min-content}.u-title{text-align:center;font-size:18px;font-weight:700}.u-wrap{position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;position:relative;width:100%;height:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{vertical-align:middle;display:inline-block}.u-legend .u-marker{width:1em;height:1em;margin-right:4px;background-clip:padding-box!important}.u-inline.u-live th:after{content:":";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:#00000012;position:absolute;pointer-events:none}.u-cursor-x,.u-cursor-y{position:absolute;left:0;top:0;pointer-events:none;will-change:transform}.u-hz .u-cursor-x,.u-vt .u-cursor-y{height:100%;border-right:1px dashed #607D8B}.u-hz .u-cursor-y,.u-vt .u-cursor-x{width:100%;border-bottom:1px dashed #607D8B}.u-cursor-pt{position:absolute;top:0;left:0;border-radius:50%;border:0 solid;pointer-events:none;will-change:transform;background-clip:padding-box!important}.u-axis.u-off,.u-select.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-cursor-pt.u-off{display:none}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}.react-grid-layout{position:relative;transition:height .2s ease}.react-grid-item{transition:all .2s ease;transition-property:left,top,width,height}.react-grid-item img{pointer-events:none;-webkit-user-select:none;user-select:none}.react-grid-item.cssTransforms{transition-property:transform,width,height}.react-grid-item.resizing{transition:none;z-index:1;will-change:width,height}.react-grid-item.react-draggable-dragging{transition:none;z-index:3;will-change:transform}.react-grid-item.dropping{visibility:hidden}.react-grid-item.react-grid-placeholder{background:red;opacity:.2;transition-duration:.1s;z-index:2;-webkit-user-select:none;-o-user-select:none;user-select:none}.react-grid-item.react-grid-placeholder.placeholder-resizing{transition:none}.react-grid-item>.react-resizable-handle{position:absolute;width:20px;height:20px}.react-grid-item>.react-resizable-handle:after{content:"";position:absolute;right:3px;bottom:3px;width:5px;height:5px;border-right:2px solid rgba(0,0,0,.4);border-bottom:2px solid rgba(0,0,0,.4)}.react-resizable-hide>.react-resizable-handle{display:none}.react-grid-item>.react-resizable-handle.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;transform:rotate(90deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;transform:rotate(180deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;transform:rotate(270deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-w,.react-grid-item>.react-resizable-handle.react-resizable-handle-e{top:50%;margin-top:-10px;cursor:ew-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-w{left:0;transform:rotate(135deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e{right:0;transform:rotate(315deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-n,.react-grid-item>.react-resizable-handle.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-n{top:0;transform:rotate(225deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-s{bottom:0;transform:rotate(45deg)}.react-resizable{position:relative}.react-resizable-handle{position:absolute;width:20px;height:20px;background-repeat:no-repeat;background-origin:content-box;box-sizing:border-box;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+);background-position:bottom right;padding:0 3px 3px 0}.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;transform:rotate(90deg)}.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;transform:rotate(180deg)}.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;transform:rotate(270deg)}.react-resizable-handle-w,.react-resizable-handle-e{top:50%;margin-top:-10px;cursor:ew-resize}.react-resizable-handle-w{left:0;transform:rotate(135deg)}.react-resizable-handle-e{right:0;transform:rotate(315deg)}.react-resizable-handle-n,.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-resizable-handle-n{top:0;transform:rotate(225deg)}.react-resizable-handle-s{bottom:0;transform:rotate(45deg)}.contain-layout{contain:layout}.contain-style{contain:style}.contain-layout.contain-style{contain:layout style}.widget-anim{contain:layout paint;will-change:auto}.is-interacting .widget-anim{will-change:transform,opacity}.is-interacting,.is-interacting *{transition:none!important;animation:none!important}.will-change-auto{will-change:auto}.pointer-events-none{pointer-events:none}.resize-optimized{backface-visibility:hidden}.is-interacting .resize-optimized{transform:translateZ(0)}.optimize-scroll{overflow-anchor:none;scroll-behavior:auto}.gpu-optimized{backface-visibility:hidden;will-change:auto}.is-interacting .gpu-optimized{transform:translateZ(0);will-change:transform,opacity}.grid-resize-optimized{contain:layout style;will-change:auto}.is-interacting .grid-resize-optimized{will-change:transform,opacity}.flex-resize-optimized{contain:layout;will-change:auto}.is-interacting .flex-resize-optimized{will-change:transform,opacity}</style> + </head> + <body> + <div id="root"></div> + </body> +</html>
View file
gpac-26.02.0.tar.gz/share/scripts/rmt/server.js
Added
@@ -0,0 +1,1151 @@ +// server/server.js +import { Sys as sys5 } from "gpaccore"; + +// server/JSClient/config.js +var DEFAULT_FILTER_FIELDS = + "idx", + "bytes_done", + "bytes_sent", + "pck_sent", + "pck_done", + "time", + "nb_ipid", + "nb_opid", + "errors", + "current_errors" +; +var CPU_STATS_FIELDS = + "total_cpu_usage", + "process_cpu_usage", + "process_memory", + "physical_memory", + "physical_memory_avail", + "gpac_memory", + "thread_count" +; +var FILTER_PROPS_LITE = + "name", + "status", + "bytes_done", + "type", + "ID", + "nb_ipid", + "nb_opid", + "idx", + "itag", + "pck_sent", + "pck_done", + "time", + "current_errors" +; +var FILTER_ARGS_LITE = ; +var PID_PROPS_LITE = ; +var FILTER_SUBSCRIPTION_FIELDS = + "status", + "bytes_done", + "bytes_sent", + "pck_done", + "pck_sent", + "time", + "nb_ipid", + "nb_opid", + "errors", + "current_errors" +; +var UPDATE_INTERVALS = { + SESSION_STATS: 1e3, + FILTER_STATS: 1e3, + CPU_STATS: 250 +}; +var LOG_RETENTION = { + maxHistorySize: 500, + maxHistorySizeVerbose: 2e3, + // Retention ratio by level (percentage to keep when cleaning) + keepRatio: { + error: 1, + // Keep 100% + warning: 0.8, + // 80% + info: 0.2, + // 20% + debug: 0.05 + // 5% + } +}; + +// server/JSClient/Cache/CacheManager.js +function CacheManager() { + this.cache = /* @__PURE__ */ Object.create(null); + this.hits = 0; + this.misses = 0; + this.get = function(key, maxAgeMs) { + const entry = this.cachekey; + if (!entry) { + this.misses++; + return null; + } + const now = Date.now(); + if (now - entry.ts > maxAgeMs) { + delete this.cachekey; + this.misses++; + return null; + } + this.hits++; + return entry.data; + }; + this.set = function(key, data) { + this.cachekey = { data, ts: Date.now() }; + }; + this.getOrSet = function(key, ttlMs, computeStringFn) { + const cached = this.get(key, ttlMs); + if (cached) return cached; + const value = computeStringFn(); + this.set(key, value); + return value; + }; + this.stats = function() { + return { + size: Object.keys(this.cache).length, + hits: this.hits, + misses: this.misses, + keys: Object.keys(this.cache) + }; + }; + this.clear = function(key) { + if (key) { + delete this.cachekey; + } else { + this.cache = /* @__PURE__ */ Object.create(null); + } + }; +} +var cacheManager = new CacheManager(); + +// server/JSClient/Messaging/MessageHandler.js +function MessageHandler(client) { + this.client = client; + this.handleMessage = function(msg, all_clients2) { + console.log("All clients:"); + for (let jc of all_clients2) { + console.log("Client ", jc.id, jc.client.peer_address); + } + console.log("on_client_data on client id ", this.client.id, " len ", msg.length, msg); + console.log("this has peer:", this.client.client.peer_address); + let text = msg; + if (text.startsWith("json:")) { + try { + let jtext = JSON.parse(text.substr(5)); + if (!("message" in jtext)) return; + const handlers = { + "get_all_filters": () => { + print("Sending all filters when ready"); + this.client.filterManager.sendAllFilters(); + }, + "filter_args_details": () => { + let idx = jtext"idx"; + print("Details requested for idx " + idx); + this.client.filterManager.requestDetails(idx); + }, + "stop_filter_args": () => { + let idx = jtext"idx"; + console.log("STOP MESSAGE****", jtext"message"); + print("Details stopped for idx " + idx); + this.client.filterManager.stopDetails(idx); + }, + "subscribe_session": () => { + const interval = jtext"interval" || UPDATE_INTERVALS.SESSION_STATS; + const fields = jtext"fields" || DEFAULT_FILTER_FIELDS; + print(`MessageHandler Subscribing to session (interval: ${interval}ms)`); + this.client.sessionStatsManager.subscribe(interval, fields); + this.client.sessionManager.startMonitoringLoop(); + }, + "unsubscribe_session": () => { + print("Unsubscribing to session"); + this.client.sessionStatsManager.unsubscribe(); + }, + "subscribe_filter": () => { + const idx = jtext.idx; + let interval = jtext.interval || UPDATE_INTERVALS.FILTER_STATS; + let pidScope = jtext.pidScope || "both"; + if (!pidScope) { + pidScope = "both"; + } + print(`MessageHandler Subscribing to filter ${idx} (interval: ${interval}ms), pidScope: ${pidScope}`); + this.client.filterManager.subscribeToFilter(idx, interval, pidScope); + this.client.sessionManager.startMonitoringLoop(); + }, + "unsubscribe_filter": () => { + const idx = jtext.idx; + this.client.filterManager.unsubscribeFromFilter(idx); + }, + "update_arg": () => { + print("Update arguments of "); + print(JSON.stringify(jtext)); + this.client.filterManager.updateArgument(jtext"idx", jtext"name", jtext"argName", jtext"newValue"); + }, + "subscribe_cpu_stats": () => { + const interval = jtext"interval" || UPDATE_INTERVALS.CPU_STATS; + const fields = jtext"fields" || ; + print(`MessageHandler Subscribing to CPU stats (interval: ${interval}ms)`); + this.client.cpuStatsManager.subscribe(interval, fields); + this.client.sessionManager.startMonitoringLoop(); + }, + "unsubscribe_cpu_stats": () => { + print("Unsubscribing to CPU stats"); + this.client.cpuStatsManager.unsubscribe(); + }, + "subscribe_logs": () => { + print("Subscribing to GPAC logs"); + const logLevel = jtext"logLevel" || "all@warning"; + this.client.logManager.subscribe(logLevel); + this.client.sessionManager.startMonitoringLoop(); + }, + "unsubscribe_logs": () => { + print("Unsubscribing from GPAC logs"); + this.client.logManager.unsubscribe(); + }, + "update_log_level": () => { + print("Updating log level"); + const logLevel = jtext"logLevel" || "all@warning"; + this.client.logManager.updateLogLevel(logLevel); + }, + "get_log_status": () => { + print("Getting log status"); + const status = this.client.logManager.getStatus(); + this.client.client.send(JSON.stringify({ + message: "log_status", + status + })); + }, + "get_ipid_props": () => { + print("Getting IPID properties for filter " + jtext"filterIdx" + " PID " + jtext"ipidIdx"); + const props = this.client.pidPropsCollector.collectIpidProps( + jtext"filterIdx", + jtext"ipidIdx" + ); + this.client.client.send(JSON.stringify({ + message: "ipid_props_response", + filterIdx: jtext"filterIdx", + ipidIdx: jtext"ipidIdx", + properties: props + })); + }, + "get_command_line": () => { + print("Getting GPAC command line"); + this.client.commandLineManager.sendCommandLine(); + }, + "get_cache_stats": () => { + print("Getting cache statistics"); + const stats = cacheManager.stats(); + this.client.client.send(JSON.stringify({ + message: "cache_stats", + stats + })); + } + }; + const handler = handlersjtext"message"; + if (handler) { + handler(); + } + } catch (e) { + console.log(e); + } + } + }; +} + +// server/JSClient/Session/SessionStatsManager.js +function SessionStatsManager(client) { + this.client = client; + this.isSubscribed = false; + this.interval = UPDATE_INTERVALS.SESSION_STATS; + this.fields = ; + this.subscribe = function(interval, fields) { + this.isSubscribed = true; + this.interval = interval || UPDATE_INTERVALS.SESSION_STATS; + this.fields = fields || DEFAULT_FILTER_FIELDS; + }; + this.unsubscribe = function() { + this.isSubscribed = false; + }; + this.computeAllPacketsDone = function(filters) { + if (filters.length === 0) return false; + for (const f of filters) { + if (f.nb_ipid === 0) continue; + for (let i = 0; i < f.nb_ipid; i++) { + const eos = f.ipid_props(i, "eos"); + if (!eos) { + return false; + } + } + } + return true; + }; + this.tick = function(now) { + if (!this.isSubscribed) return; + const serialized = cacheManager.getOrSet("session_stats", 50, () => { + const stats = ; + const filters = ; + session.lock_filters(true); + for (let i = 0; i < session.nb_filters; i++) { + const f = session.get_filter(i); + if (f.is_destroyed()) continue; + filters.push(f); + const obj = {}; + for (const field of this.fields) { + objfield = ffield; + } + let allInputsEos = f.nb_ipid > 0; + for (let j = 0; j < f.nb_ipid; j++) { + if (!f.ipid_props(j, "eos")) { + allInputsEos = false; + break; + } + } + obj.is_eos = allInputsEos; + obj.last_ts_sent = f.last_ts_sent || null; + stats.push(obj); + } + const allFiltersEos = this.computeAllPacketsDone(filters); + const all_packets_done = session.last_task && allFiltersEos; + session.lock_filters(false); + return JSON.stringify({ + message: "session_stats", + all_packets_done, + stats + }); + }); + if (this.client.client) { + this.client.client.send(serialized); + } + }; + this.handleSessionEnd = function() { + this.unsubscribe(); + }; +} + +// server/JSClient/Session/SessionManager.js +import { Sys as sys } from "gpaccore"; +function SessionManager(client) { + this.client = client; + this.isMonitoringLoopRunning = false; + this.hasActiveSubscriptions = function() { + return this.client.sessionStatsManager.isSubscribed || this.client.cpuStatsManager.isSubscribed || this.client.logManager.isSubscribed || Object.keys(this.client.filterManager.filterSubscriptions).length > 0; + }; + this.startMonitoringLoop = function() { + if (this.isMonitoringLoopRunning) return; + this.isMonitoringLoopRunning = true; + const processError = session.last_process_error; + if (processError) { + print("SessionManager Process error detected on session:", processError); + this.isMonitoringLoopRunning = false; + return; + } + session.post_task(() => { + const now = sys.clock_us(); + if (session.last_task) { + this.client.cpuStatsManager.tick(now); + this.client.logManager.tick(now); + this.client.filterManager.tick(now); + this.client.sessionStatsManager.tick(now); + try { + this.client.client.send(JSON.stringify({ + message: "session_end", + reason: "completed", + timestamp: now + })); + print("SessionManager Session end message sent"); + } catch (e) { + print("SessionManager Failed to send session_end message:", e); + } + this.client.cpuStatsManager.handleSessionEnd(); + this.client.logManager.handleSessionEnd(); + this.client.filterManager.handleSessionEnd(); + this.client.sessionStatsManager.handleSessionEnd(); + this.isMonitoringLoopRunning = false; + return false; + } + this.client.cpuStatsManager.tick(now); + this.client.logManager.tick(now); + this.client.filterManager.tick(now); + this.client.sessionStatsManager.tick(now); + const shouldContinue = this.hasActiveSubscriptions(); + if (!shouldContinue) this.isMonitoringLoopRunning = false; + let interval = 1e3; + if (this.client.sessionStatsManager.isSubscribed) { + interval = Math.min(interval, this.client.sessionStatsManager.interval); + } + if (this.client.cpuStatsManager.isSubscribed) { + interval = Math.min(interval, this.client.cpuStatsManager.interval); + } + return shouldContinue ? interval : false; + }); + }; +} + +// server/JSClient/filterUtils.js +function gpac_filter_to_object(f, full = false) { + let jsf = {}; + for (let prop in f) { + if (full || FILTER_PROPS_LITE.includes(prop)) + jsfprop = fprop; + } + jsf"gpac_args" = ; + if (full) { + let all_args = f.all_args(true); + for (let arg of all_args) { + if (arg && (full || FILTER_ARGS_LITE.includes(arg.name))) + jsf"gpac_args".push(arg); + } + } + jsf"ipid" = {}; + jsf"opid" = {}; + for (let d = 0; d < f.nb_ipid; d++) { + let pidname = f.ipid_props(d, "name"); + let jspid = {}; + f.ipid_props(d, (name, type, val) => { + if (full || PID_PROPS_LITE.includes(name)) + jspidname = { "type": type, "val": val }; + }); + jspid"buffer" = f.ipid_props(d, "buffer"); + jspid"buffer_total" = f.ipid_props(d, "buffer_total"); + jspid"source_idx" = f.ipid_source(d).idx; + jsf"ipid"pidname = jspid; + } + for (let d = 0; d < f.nb_opid; d++) { + let pidname = f.opid_props(d, "name"); + let jspid = {}; + f.opid_props(d, (name, type, val) => { + if (full || PID_PROPS_LITE.includes(name)) + jspidname = { "type": type, "val": val }; + }); + jspid"buffer" = f.opid_props(d, "buffer"); + jspid"buffer_total" = f.opid_props(d, "buffer_total"); + jsf"opid"pidname = jspid; + } + return jsf; +} +function gpac_filter_to_minimal_object(f) { + const minimalFilters = { + idx: f.idx, + name: f.name, + type: f.type, + status: f.status, + itag: f.itag || null, + ID: f.ID || null, + nb_ipid: f.nb_ipid, + nb_opid: f.nb_opid, + ipid: {}, + opid: {} + }; + for (let i = 0; i < f.nb_ipid; i++) { + const pidName = f.ipid_props(i, "name"); + const streamType = f.ipid_props(i, "StreamType"); + minimalFilters.ipidpidName = { + source_idx: f.ipid_source(i).idx, + stream_type: streamType + }; + } + for (let o = 0; o < f.nb_opid; o++) { + const pidName = f.opid_props(o, "name"); + const streamType = f.opid_props(o, "StreamType"); + minimalFilters.opidpidName = { + stream_type: streamType + }; + } + return minimalFilters; +} +function on_all_connected(cb, draned_once_ref) { + session.post_task(() => { + let local_connected = true; + let all_filters_instances = ; + session.lock_filters(true); + for (let i = 0; i < session.nb_filters; i++) { + const f = session.get_filter(i); + if (f.is_destroyed()) continue; + all_filters_instances.push(f); + } + session.lock_filters(false); + if (local_connected) { + cb(all_filters_instances); + if (draned_once_ref) draned_once_ref.value = true; + return false; + } + return 2e3; + }); +} + +// server/JSClient/Filters/PID/PidDataCollector.js +function PidDataCollector() { + this.collectInputPids = function(filter) { + const ipids = {}; + for (let i = 0; i < filter.nb_ipid; i++) { + const pid = {}; + const originalName = filter.ipid_props(i, "name"); + pid.name = filter.nb_ipid > 1 && originalName ? `${originalName}_${i}` : originalName; + pid.buffer = filter.ipid_props(i, "buffer"); + pid.nb_pck_queued = filter.ipid_props(i, "nb_pck_queued"); + pid.would_block = filter.ipid_props(i, "would_block"); + pid.eos = filter.ipid_props(i, "eos"); + pid.playing = filter.ipid_props(i, "playing"); + pid.timescale = filter.ipid_props(i, "Timescale"); + pid.codec = filter.ipid_props(i, "CodecID"); + pid.type = filter.ipid_props(i, "StreamType"); + pid.width = filter.ipid_props(i, "Width"); + pid.height = filter.ipid_props(i, "Height"); + pid.pixelformat = filter.ipid_props(i, "PixelFormat"); + pid.bitrate = filter.ipid_props(i, "Bitrate"); + pid.samplerate = filter.ipid_props(i, "SampleRate"); + pid.channels = filter.ipid_props(i, "Channels"); + const source = filter.ipid_source(i); + if (source) { + pid.source_idx = source.idx; + } + const stats = filter.ipid_stats(i); + if (stats) { + pid.stats = {}; + pid.stats.disconnected = stats.disconnected; + pid.stats.average_process_rate = stats.average_process_rate; + pid.stats.max_process_rate = stats.max_process_rate; + pid.stats.average_bitrate = stats.average_bitrate; + pid.stats.max_bitrate = stats.max_bitrate; + pid.stats.nb_processed = stats.nb_processed; + pid.stats.max_process_time = stats.max_process_time; + pid.stats.total_process_time = stats.total_process_time; + } + const key = pid.name || `ipid_${i}`; + ipidskey = pid; + } + return ipids; + }; + this.collectOutputPids = function(filter) { + const opids = {}; + for (let i = 0; i < filter.nb_opid; i++) { + const pid = {}; + const originalName = filter.opid_props(i, "name"); + pid.name = filter.nb_opid > 1 && originalName ? `${originalName}_${i}` : originalName; + pid.buffer = filter.opid_props(i, "buffer"); + pid.max_buffer = filter.opid_props(i, "max_buffer"); + pid.nb_pck_queued = filter.opid_props(i, "nb_pck_queued"); + pid.would_block = filter.opid_props(i, "would_block"); + const statsEos = filter.opid_stats(i); + pid.eos_received = statsEos?.eos_received; + pid.playing = filter.opid_props(i, "playing"); + pid.timescale = filter.opid_props(i, "Timescale"); + pid.codec = filter.opid_props(i, "CodecID"); + pid.type = filter.opid_props(i, "StreamType"); + pid.width = filter.opid_props(i, "Width"); + pid.height = filter.opid_props(i, "Height"); + pid.pixelformat = filter.opid_props(i, "PixelFormat"); + pid.samplerate = filter.opid_props(i, "SampleRate"); + pid.channels = filter.opid_props(i, "Channels"); + pid.id = filter.opid_props(i, "ID"); + pid.trackNumber = filter.opid_props(i, "TrackNumber"); + pid.serviceID = filter.opid_props(i, "ServiceID"); + pid.language = filter.opid_props(i, "Language"); + pid.role = filter.opid_props(i, "Role"); + const stats = filter.opid_stats(i); + if (stats) { + pid.stats = {}; + pid.stats.disconnected = stats.disconnected; + pid.stats.average_process_rate = stats.average_process_rate; + pid.stats.max_process_rate = stats.max_process_rate; + pid.stats.average_bitrate = stats.average_bitrate; + pid.stats.max_bitrate = stats.max_bitrate; + pid.stats.nb_processed = stats.nb_processed; + pid.stats.max_process_time = stats.max_process_time; + pid.stats.total_process_time = stats.total_process_time; + pid.stats.last_ts_sent = stats.last_ts_sent; + pid.stats.first_process_time = stats.first_process_time; + } + const key = pid.name || `opid_${i}`; + opidskey = pid; + } + return opids; + }; +} + +// server/JSClient/Filters/ArgumentHandler.js +function ArgumentHandler(client) { + this.client = client; + this.sendDetails = function(idx) { + session.post_task(() => { + let Args = ; + session.lock_filters(true); + for (let i = 0; i < session.nb_filters; i++) { + let f = session.get_filter(i); + if (f.idx == idx) { + const fullObj = gpac_filter_to_object(f, true); + Args = fullObj.gpac_args; + break; + } + } + session.lock_filters(false); + if (this.client.client) { + this.client.client.send(JSON.stringify({ + message: "details", + filter: { + idx, + gpac_args: Args + } + })); + } + return false; + }); + }; + this.updateArgument = function(idx, name, argName, newValue) { + let filter = session.get_filter("" + idx); + if (!filter) { + print("Error: Filter with idx " + idx + " not found"); + return; + } + if (filter.name != name) { + print("Warning: Discrepancy in filter names for idx " + idx + ". Expected '" + name + "', found '" + filter.name + "'. Proceeding with update."); + } + try { + print("Updating filter " + idx + " (" + filter.name + "), argument '" + argName + "' to '" + newValue + "'"); + filter.update(argName, newValue); + print("Successfully updated argument '" + argName + "' for filter " + filter.name + " (idx=" + idx + ")"); + } catch (e) { + print("Error: Failed to update argument: " + e.toString()); + } + }; +} + +// server/JSClient/Filters/FilterManager.js +function FilterManager(client, draned_once_ref) { + this.client = client; + this.draned_once_ref = draned_once_ref; + this.details_needed = {}; + this.filterSubscriptions = {}; + this.lastSentByFilter = {}; + this.pidDataCollector = new PidDataCollector(); + this.argumentHandler = new ArgumentHandler(client); + this.sendAllFilters = function() { + on_all_connected((all_js_filters) => { + print("----- all connected -----"); + const serialized = cacheManager.getOrSet("all_filters", 100, () => { + const minimalFiltersList = all_js_filters.map((f) => { + return gpac_filter_to_minimal_object(f); + }); + print("-------------------------"); + print(JSON.stringify(minimalFiltersList, null, 1)); + return JSON.stringify({ + "message": "filters", + "filters": minimalFiltersList + }); + }); + if (this.client.client) { + this.client.client.send(serialized); + } + session.post_task(() => { + let js_filters = ; + session.lock_filters(true); + for (let i = 0; i < session.nb_filters; i++) { + let f = session.get_filter(i); + js_filters.push(gpac_filter_to_object(f)); + } + session.lock_filters(false); + return false; + }); + }, this.draned_once_ref); + }; + this.requestDetails = function(idx) { + this.details_neededidx = true; + this.argumentHandler.sendDetails(idx); + }; + this.stopDetails = function(idx) { + this.details_neededidx = false; + }; + this.subscribeToFilter = function(idx, interval, pidScope) { + this.filterSubscriptionsidx = { + interval: interval || UPDATE_INTERVALS.FILTER_STATS, + fields: FILTER_SUBSCRIPTION_FIELDS, + pidScope: pidScope || "both" + }; + this.lastSentByFilteridx = 0; + this.client.sessionManager.startMonitoringLoop(); + }; + this.unsubscribeFromFilter = function(idx) { + delete this.filterSubscriptionsidx; + delete this.lastSentByFilteridx; + }; + this.tick = function(now) { + for (const idxStr in this.filterSubscriptions) { + const idx = parseInt(idxStr); + const sub = this.filterSubscriptionsidxStr; + const lastSent = this.lastSentByFilteridxStr || 0; + if (now - lastSent < sub.interval) continue; + const cacheKey = `filter_stats_${idx}`; + const serialized = cacheManager.getOrSet(cacheKey, 50, () => { + session.lock_filters(true); + let fObj = null; + for (let i = 0; i < session.nb_filters; i++) { + const f = session.get_filter(i); + if (f.is_destroyed()) continue; + if (f.idx === idx) { + fObj = f; + break; + } + } + session.lock_filters(false); + if (!fObj) return null; + const payload = { idx }; + for (const field of sub.fields) { + payloadfield = fObjfield; + } + switch (sub.pidScope) { + case "ipid": + payload.ipids = this.pidDataCollector.collectInputPids(fObj); + break; + case "opid": + payload.opids = this.pidDataCollector.collectOutputPids(fObj); + break; + case "both": + payload.ipids = this.pidDataCollector.collectInputPids(fObj); + payload.opids = this.pidDataCollector.collectOutputPids(fObj); + break; + default: + break; + } + return JSON.stringify({ + message: "filter_stats", + ...payload + }); + }); + if (serialized && this.client.client) { + this.client.client.send(serialized); + this.lastSentByFilteridxStr = now; + } + } + }; + this.handleSessionEnd = function() { + this.filterSubscriptions = {}; + this.lastSentByFilter = {}; + }; + this.updateArgument = function(idx, name, argName, newValue) { + this.argumentHandler.updateArgument(idx, name, argName, newValue); + }; +} + +// server/JSClient/Sys/CpuStatsManager.js +import { Sys as sys2 } from "gpaccore"; +function CpuStatsManager(client) { + this.client = client; + this.isSubscribed = false; + this.interval = UPDATE_INTERVALS.CPU_STATS; + this.fields = CPU_STATS_FIELDS; + this.lastSent = 0; + this.subscribe = function(interval, fields) { + this.isSubscribed = true; + this.interval = interval || UPDATE_INTERVALS.CPU_STATS; + this.fields = fields || CPU_STATS_FIELDS; + this.lastSent = 0; + this.client.sessionManager.startMonitoringLoop(); + }; + this.unsubscribe = function() { + this.isSubscribed = false; + }; + this.tick = function(now) { + if (!this.isSubscribed) return; + if (now - this.lastSent < this.interval) return; + const serialized = cacheManager.getOrSet("cpu_stats", 50, () => { + const cpuStats = { + timestamp: now, + total_cpu_usage: sys2.total_cpu_usage, + process_cpu_usage: sys2.process_cpu_usage, + process_memory: sys2.process_memory, + physical_memory: sys2.physical_memory, + physical_memory_avail: sys2.physical_memory_avail, + gpac_memory: sys2.gpac_memory, + nb_cores: sys2.nb_cores, + thread_count: sys2.thread_count, + memory_usage_percent: 0, + process_memory_percent: 0, + gpac_memory_percent: 0, + cpu_efficiency: 0 + }; + if (sys2.physical_memory > 0) { + cpuStats.memory_usage_percent = (sys2.physical_memory - sys2.physical_memory_avail) / sys2.physical_memory * 100; + cpuStats.process_memory_percent = sys2.process_memory / sys2.physical_memory * 100; + cpuStats.gpac_memory_percent = sys2.gpac_memory / sys2.physical_memory * 100; + } + if (sys2.total_cpu_usage > 0) { + cpuStats.cpu_efficiency = sys2.process_cpu_usage / sys2.total_cpu_usage * 100; + } + return JSON.stringify({ + message: "cpu_stats", + stats: cpuStats + }); + }); + if (this.client.client) { + this.client.client.send(serialized); + } + this.lastSent = now; + }; + this.handleSessionEnd = function() { + this.unsubscribe(); + }; +} + +// server/JSClient/Sys/LogManager.js +import { Sys as sys3 } from "gpaccore"; + +// server/JSClient/Sys/Utils/logs.js +function cleanupLogs(logs, maxSize) { + if (logs.length <= maxSize) return logs; + const byLevel = { + error: logs.filter((log) => log.level === "error"), + warning: logs.filter((log) => log.level === "warning"), + info: logs.filter((log) => log.level === "info"), + debug: logs.filter((log) => log.level === "debug") + }; + const toKeep = { + error: Math.ceil(byLevel.error.length * LOG_RETENTION.keepRatio.error), + warning: Math.ceil(byLevel.warning.length * LOG_RETENTION.keepRatio.warning), + info: Math.ceil(byLevel.info.length * LOG_RETENTION.keepRatio.info), + debug: Math.ceil(byLevel.debug.length * LOG_RETENTION.keepRatio.debug) + }; + const kept = + ...byLevel.error.slice(-toKeep.error), + ...byLevel.warning.slice(-toKeep.warning), + ...byLevel.info.slice(-toKeep.info), + ...byLevel.debug.slice(-toKeep.debug) + ; + kept.sort((a, b) => a.timestamp - b.timestamp); + if (kept.length > maxSize) { + return kept.slice(-maxSize); + } + return kept; +} + +// server/JSClient/Sys/LogManager.js +function LogManager(client) { + this.client = client; + this.isSubscribed = false; + this.logLevel = "all@quiet"; + this.logs = ; + this.maxHistorySize = LOG_RETENTION.maxHistorySize; + this.originalLogConfig = null; + this.pendingLogs = ; + this.incomingBuffer = ; + this.batchTimer = null; + this.subscribe = function(logLevel) { + if (this.isSubscribed) { + this.updateLogLevel(logLevel); + return; + } + this.logLevel = logLevel; + this.isSubscribed = true; + try { + this.originalLogConfig = sys3.get_logs(true); + sys3.use_logx = true; + sys3.on_log = (tool, level, message, thread_id, caller) => { + this.handleLog(tool, level, message, thread_id, caller); + }; + sys3.set_logs(this.logLevel); + this.client.sessionManager.startMonitoringLoop(); + } catch (error) { + console.error("LogManager: Failed to start log capturing:", error); + this.isSubscribed = false; + } + }; + this.unsubscribe = function() { + if (!this.isSubscribed) return; + try { + this.flushPendingLogs(); + sys3.on_log = void 0; + if (this.originalLogConfig) { + sys3.set_logs(this.originalLogConfig); + } + this.isSubscribed = false; + this.logs = ; + this.pendingLogs = ; + this.batchTimer = null; + console.log(`LogManager: Client ${this.client.id} unsubscribed from logs`); + } catch (error) { + console.error("LogManager: Failed to stop log capturing:", error); + } + }; + this.handleLog = function(tool, level, message, thread_id, caller) { + const log = { + timestamp: sys3.clock_us(), + timestampMs: Date.now(), + tool, + level, + message: message?.length > 500 ? message.substring(0, 500) + "..." : message, + thread_id, + caller: this.serializeCaller(caller) + }; + this.incomingBuffer.push(log); + }; + this.serializeCaller = function(caller) { + if (!caller || typeof caller !== "object") { + return null; + } + return caller.idx !== void 0 ? caller.idx : caller.name || null; + }; + this.tick = function(now) { + if (!this.isSubscribed) return; + if (this.incomingBuffer.length > 0) { + this.processIncomingLogs(); + } + }; + this.processIncomingLogs = function() { + if (!this.isSubscribed || this.incomingBuffer.length === 0) { + return; + } + const logsToProcess = this.incomingBuffer.splice(0); + for (const log of logsToProcess) { + if (this.logs.length >= this.maxHistorySize) { + this.logs = cleanupLogs(this.logs, this.maxHistorySize); + } + this.logs.push(log); + this.pendingLogs.push(log); + } + const hasDebugLogs = logsToProcess.some((log) => log.level === "debug" || log.level === "info"); + const maxPending = hasDebugLogs ? 20 : 50; + const delay = hasDebugLogs ? 100 : 250; + if (this.pendingLogs.length >= maxPending) { + this.flushPendingLogs(); + return; + } + if (!this.batchTimer) { + this.batchTimer = true; + session.post_task(() => { + if (!this.isSubscribed) return false; + this.flushPendingLogs(); + return false; + }, delay); + } + }; + this.updateLogLevel = function(logLevel) { + if (!this.isSubscribed) return; + const isVerbose = logLevel.includes("debug") || logLevel.includes("info"); + this.maxHistorySize = isVerbose ? LOG_RETENTION.maxHistorySizeVerbose : LOG_RETENTION.maxHistorySize; + try { + this.logs = ; + this.pendingLogs = ; + this.logLevel = logLevel; + sys3.set_logs(logLevel); + this.sendToClient({ + message: "log_config_changed", + logLevel + }); + } catch (error) { + console.error("LogManager: Failed to update log level:", error); + } + }; + this.getStatus = function() { + return { + isSubscribed: this.isSubscribed, + logLevel: this.logLevel, + logCount: this.logs.length, + currentLogConfig: sys3.get_logs() + }; + }; + this.flushPendingLogs = function() { + if (this.pendingLogs.length === 0) { + this.batchTimer = null; + return; + } + this.sendToClient({ + message: "log_batch", + logs: this.pendingLogs + }); + this.pendingLogs = ; + this.batchTimer = null; + }; + this.sendToClient = function(data) { + if (this.client.client && typeof this.client.client.send === "function") { + this.client.client.send(JSON.stringify(data)); + } else { + } + }; + this.forceUnsubscribe = function() { + console.log(`LogManager: Force cleanup for client ${this.client.id}`); + try { + this.flushPendingLogs(); + sys3.on_log = void 0; + if (this.originalLogConfig) { + sys3.set_logs(this.originalLogConfig); + } + this.isSubscribed = false; + this.logs = ; + this.pendingLogs = ; + this.incomingBuffer = ; + this.batchTimer = null; + console.log(`LogManager: Client ${this.client.id} force cleanup completed`); + } catch (error) { + console.error("LogManager: Error during force cleanup:", error); + } + }; + this.handleSessionEnd = function() { + this.forceUnsubscribe(); + }; +} + +// server/JSClient/Filters/PID/PidPropsCollector.js +function PidPropsCollector(client) { + this.client = client; + this.collectIpidProps = function(filterIdx, ipidIdx) { + session.lock_filters(true); + try { + let collectProperty2 = function(prop_name, prop_type, prop_val) { + propertiesprop_name = { + name: prop_name, + type: prop_type, + value: prop_val + }; + }; + var collectProperty = collectProperty2; + const filter = session.get_filter(filterIdx); + if (!filter) { + session.lock_filters(false); + return { error: `Filter ${filterIdx} not found` }; + } + const properties = {}; + filter.ipid_props(ipidIdx, collectProperty2); + session.lock_filters(false); + return properties; + } catch (e) { + session.lock_filters(false); + return { error: `Failed to enumerate IPID ${ipidIdx}: ${e.message}` }; + } + }; +} + +// server/JSClient/CommandLineManager.js +import { Sys as sys4 } from "gpaccore"; +function CommandLineManager(client) { + this.client = client; + this.getCommandLine = function() { + try { + if (typeof sys4 !== "undefined" && sys4.args) { + if (Array.isArray(sys4.args) && sys4.args.length > 0) { + const commandLine = sys4.args.join(" "); + return commandLine; + } + } + } catch (e) { + print("CommandLineManager Error getting command line: " + e); + return null; + } + }; + this.sendCommandLine = function() { + const commandLine = this.getCommandLine(); + if (commandLine) { + this.client.client.send(JSON.stringify({ + message: "command_line_response", + commandLine, + timestamp: Date.now() + })); + print("CommandLineManager Sent command line to client: " + commandLine); + } else { + this.client.client.send(JSON.stringify({ + message: "command_line_response", + commandLine: null, + error: "Could not retrieve command line", + timestamp: Date.now() + })); + print("CommandLineManager Could not retrieve command line"); + } + }; +} + +// server/JSClient/index.js +function JSClient(id, client, all_clients2, draned_once_ref) { + this.id = id; + this.client = client; + this.messageHandler = new MessageHandler(this); + this.sessionStatsManager = new SessionStatsManager(this); + this.sessionManager = new SessionManager(this); + this.filterManager = new FilterManager(this, draned_once_ref); + this.cpuStatsManager = new CpuStatsManager(this); + this.logManager = new LogManager(this); + this.pidPropsCollector = new PidPropsCollector(this); + this.commandLineManager = new CommandLineManager(this); + this.on_client_data = function(msg) { + this.messageHandler.handleMessage(msg, all_clients2); + }; + this.cleanup = function() { + console.log(`JSClient ${this.id}: Starting cleanup`); + try { + if (this.logManager) { + this.logManager.forceUnsubscribe(); + } + if (this.sessionManager && typeof this.sessionManager.cleanup === "function") { + this.sessionManager.cleanup(); + } + if (this.cpuStatsManager && typeof this.cpuStatsManager.cleanup === "function") { + this.cpuStatsManager.cleanup(); + } + console.log(`JSClient ${this.id}: Cleanup completed`); + } catch (error) { + console.error(`JSClient ${this.id}: Error during cleanup:`, error); + } + }; +} + +// server/server.js +var all_clients = ; +var cid = 0; +var filter_uid = 0; +var draned_once = false; +var all_filters = ; +session.reporting(true); +var remove_client = function(client_id) { + for (let i = 0; i < all_clients.length; i++) { + if (all_clientsi.id == client_id) { + all_clients.splice(i, 1); + return; + } + } +}; +session.set_new_filter_fun((f) => { + print("new filter " + f.name); + f.idx = filter_uid++; + f.iname = "" + f.idx; + all_filters.push(f); + console.log("NEW FILTER ITAG " + f.itag); + if (f.itag == "NODISPLAY") + return; + if (draned_once) { + sys5.sleep(100); + } +}); +session.set_del_filter_fun((f) => { + print("delete filter " + f.iname + " " + f.name); + let idx = all_filters.indexOf(f); + if (idx >= 0) + all_filters.splice(idx, 1); + console.log("RM FILTER ITAG " + f.itag); + if (f.itag == "NODISPLAY") + return; + if (draned_once) { + sys5.sleep(100); + } +}); +sys5.rmt_on_new_client = function(client) { + console.log("rmt on client"); + print(typeof client); + let draned_once_ref = { value: draned_once }; + let js_client = new JSClient(++cid, client, all_clients, draned_once_ref); + all_clients.push(js_client); + console.log("New ws client ", js_client.id, " gpac peer ", js_client.client.peer_address); + js_client.client.on_data = (msg) => { + if (typeof msg == "string") + js_client.on_client_data(msg); + else { + let buf = new Uint8Array(msg); + console.log("Got binary message of type", typeof msg, "len ", buf.length, "with data:", buf); + } + }; + js_client.client.on_close = function() { + console.log("ON_CLOSE on client ", js_client.id, " ", client.peer_address); + js_client.cleanup(); + remove_client(js_client.id); + js_client.client = null; + }; + draned_once = draned_once_ref.value; +};
View file
gpac-2.4.0.tar.gz/share/scripts/vout.js -> gpac-26.02.0.tar.gz/share/scripts/vout.js
Changed
@@ -3,7 +3,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2023 + * Copyright (c) Telecom ParisTech 2020-2025 * All rights reserved * * This file is part of GPAC / vout default ui @@ -397,7 +397,7 @@ shortcuts.forEach( (key, index) => { if (use_libcaca && (index>10)) return; - args.push( '' + sys.keyname(key.code) + ': ' + key.desc); + args.push( key.code + ': ' + key.desc); }); if (audio_only) text.fontsize = 20; @@ -499,8 +499,21 @@ let last_ts_f = vout.last_ts_drop; if (!last_ts_f && aout) last_ts_f = aout.last_ts_drop; + if (!last_ts_f) { - vout.update('oltxt', null); + let str = 'No input connected'; + if (use_libcaca) { + vout.update('oltxt', str); + return; + } + text.fontsize = 20; + text.set_text(str); + let mx = new evg.Matrix2D(); + mx.translate(- str.length * text.fontsize/3, -10); + ol_canvas.matrix = mx; + ol_canvas.path = text; + ol_canvas.fill(brush); + vout.update('oldata', ol_buffer); return; } @@ -601,6 +614,23 @@ ol_canvas.fill(brush); brush.set_color('white'); } + + let src = vout ? vout.ipid_source(0) : aout.ipid_source(0); + let hdr = src.ipid_props(0, 'X-From-MABR'); + if (!hdr) hdr = src.ipid_props(0, 'x-from-mabr'); + if (hdr) { + text.fontsize = 16; + let msg = 'MABR ' + ((hdr=='yes') ? 'on' : ((hdr=='no') ? 'off' : hdr)); + text.set_text(msg); + let mx = new evg.Matrix2D(); + mx.translate(ol_width/2-10*msg.length, 5-ol_height/2); + ol_canvas.matrix = mx; + ol_canvas.path = text; + brush.set_color((hdr=='yes') ? 'green' : 'red'); + ol_canvas.fill(brush); + brush.set_color('white'); + } + //we could lock vout to avoid any tearing ... vout.update('oldata', ol_buffer); } @@ -992,20 +1022,20 @@ } let shortcuts = - { "code": GF_KEY_B, "desc": "speeds up by 2"}, - { "code": GF_KEY_V, "desc": "speeds down by 2"}, - { "code": GF_KEY_N, "desc": "normal play speed"}, - { "code": GF_KEY_SPACE, "desc": "pause/resume"}, - { "code": GF_KEY_S, "desc": "step one frame"}, - { "code": GF_KEY_RIGHT, "desc": "seek forward by 1%, 10% if alt down, 30% if ctrl down"}, - { "code": GF_KEY_LEFT, "desc": "seek backward "}, - { "code": GF_KEY_UP, "desc": "seek forward by 30s, 10m if alt down, 30m if ctrl down"}, - { "code": GF_KEY_DOWN, "desc": "seek backward"}, - { "code": GF_KEY_I, "desc": "show info and statistics"}, - { "code": GF_KEY_P, "desc": "show playback info"}, - { "code": GF_KEY_F, "desc": "fullscreen mode"}, - { "code": GF_KEY_M, "desc": "flip video"}, - { "code": GF_KEY_R, "desc": "rotate video by 90 degree"}, + { "code": "B", "desc": "speeds up by 2"}, + { "code": "V", "desc": "speeds down by 2"}, + { "code": "N", "desc": "normal play speed"}, + { "code": "space", "desc": "pause/resume"}, + { "code": "S", "desc": "step one frame"}, + { "code": "Right", "desc": "seek forward by 1%, 10% if alt down, 30% if ctrl down"}, + { "code": "Left", "desc": "seek backward "}, + { "code": "Up", "desc": "seek forward by 30s, 10m if alt down, 30m if ctrl down"}, + { "code": "Down", "desc": "seek backward"}, + { "code": "I", "desc": "show info and statistics"}, + { "code": "P", "desc": "show playback info"}, + { "code": "F", "desc": "fullscreen mode"}, + { "code": "M", "desc": "flip video"}, + { "code": "R", "desc": "rotate video by 90 degree"}, ; function set_speed(speed) @@ -1030,55 +1060,55 @@ } if(evt.window != win_id) return true; - switch (evt.keycode) { - case GF_KEY_B: + switch (sys.keyname(evt.keycode)) { + case 'B': if (evt.keymods & GF_KEY_MOD_SHIFT) speed *= 1.2; else speed *= 2; set_speed(speed); return true; - case GF_KEY_V: + case 'V': if (evt.keymods & GF_KEY_MOD_SHIFT) speed /= 1.2; else speed /= 2; set_speed(speed); return true; - case GF_KEY_N: + case 'N': speed = 1; set_speed(speed); return true; - case GF_KEY_SPACE: + case 'space': paused = !paused; set_speed(paused ? 0 : speed); return true; - case GF_KEY_S: + case 'S': paused = 2; vout.update('step', '1'); if (aout) aout.update('speed', '0'); check_duration(); return true; - case GF_KEY_RIGHT: + case 'Right': if (interactive_scene) return false; do_seek(1, evt.keymods, false); return true; - case GF_KEY_LEFT: + case 'Left': if (interactive_scene) return false; do_seek(-1, evt.keymods, false); return true; - case GF_KEY_UP: + case 'Up': if (interactive_scene) return false; do_seek(1, evt.keymods, true); return true; - case GF_KEY_DOWN: + case 'Down': if (interactive_scene) return false; do_seek(-1, evt.keymods, true); return true; - case GF_KEY_F: + case 'F': if (audio_only) return; fullscreen = !fullscreen; vout.update('fullscreen', ''+fullscreen); return true; - case GF_KEY_H: + case 'H': if (overlay_type==OL_AUTH) break; //hide player if (audio_only && (overlay_type==OL_PLAY)) { @@ -1086,13 +1116,13 @@ } overlay_type=OL_HELP; toggle_overlay(); - //show player player + //show player if (!ol_visible && audio_only) { overlay_type=OL_PLAY; toggle_overlay(); } return true; - case GF_KEY_I: + case 'I': if (overlay_type==OL_AUTH) break; //hide player if (audio_only && (overlay_type==OL_PLAY)) { @@ -1100,13 +1130,13 @@ } overlay_type=OL_STATS; toggle_overlay(); - //show player player + //show player if (!ol_visible && audio_only) { overlay_type=OL_PLAY; toggle_overlay(); } return true; - case GF_KEY_P: + case 'P': if (overlay_type==OL_AUTH) break; //do not untoggle for audio only if (audio_only) return; @@ -1114,7 +1144,7 @@ if (!ol_visible) init_wnd=true; toggle_overlay(); return true; - case GF_KEY_R: + case 'R': if (audio_only) return; rot = vout.get_arg('vrot'); rot++; @@ -1124,7 +1154,7 @@ else if (rot==2) vout.update('vrot', '180'); else vout.update('vrot', '270'); return true; - case GF_KEY_M: + case 'M': if (audio_only) return; flip = vout.get_arg('vflip'); flip++;
View file
gpac-2.4.0.tar.gz/share/scripts/webvtt-renderer.js -> gpac-26.02.0.tar.gz/share/scripts/webvtt-renderer.js
Changed
@@ -259,16 +259,16 @@ stack = stack.slice(0,stackScanDepth); } else { // Tag mismatch! - alert("Tag mismatch when parsing WebVTT cue: "+cueText+ " tag name was " + TagName); + alert("Tag mismatch when parsing WebVTT cue: " + cueText + "\n : tag name was " + TagName); } } else { // Opening Tag // Check whether the tag is valid according to the WebVTT specification // If not, don't allow it (unless the sanitiseCueHTML option is explicitly set to false) - + if (( currentToken.substr(1).match(SRTChunkTimestampParser) || currentToken.match(/^<(v|lang)\s+^>+>/i) || - currentToken.match(/^<ca-zA-Z0-9\-\_\.+>/) || + currentToken.match(/^<ca-zA-Z0-9\-\_\.#\(\)\\+>/) || currentToken.match(/^<(b|i|u|ruby|ruby|rt)>/)) ) { @@ -277,7 +277,7 @@ "rawToken": currentToken, "children": }; - + if (tmpObject.token === "v") { tmpObject.voice = currentToken.match(/^<v\s*(^>+)>/i)1; } else if (tmpObject.token === "c") {
View file
gpac-2.4.0.tar.gz/src/Makefile -> gpac-26.02.0.tar.gz/src/Makefile
Changed
@@ -21,8 +21,15 @@ endif ## libgpac objects gathering: src/utils -LIBGPAC_UTILS=utils/os_divers.o utils/os_file.o utils/list.o utils/bitstream.o utils/constants.o utils/error.o utils/alloc.o utils/url.o utils/configfile.o utils/gltools.o utils/gzio.o utils/xml_parser.o -LIBGPAC_UTILS+=utils/sha1.o utils/sha256.o utils/base_encoding.o utils/math.o utils/os_net.o utils/os_thread.o utils/os_config_init.o utils/cache.o utils/downloader.o utils/utf.o utils/token.o utils/color.o utils/Remotery.o +LIBGPAC_UTILS=utils/os_divers.o utils/os_file.o utils/list.o utils/bitstream.o utils/constants.o utils/error.o utils/alloc.o utils/url.o utils/configfile.o utils/gltools.o utils/gzio.o utils/xml_parser.o utils/xml_bin_custom.o +LIBGPAC_UTILS+=utils/sha1.o utils/sha256.o utils/md5.o utils/base_encoding.o utils/math.o utils/os_net.o utils/os_thread.o utils/os_config_init.o utils/utf.o utils/token.o utils/color.o utils/rmt_ws.o + +LIBGPAC_DOWNLOADER= +ifeq ($(CONFIG_EMSCRIPTEN),yes) +LIBGPAC_DOWNLOADER+=utils/downloader_emscripten.o +else +LIBGPAC_DOWNLOADER+=utils/downloader.o utils/downloader_cache.o utils/downloader_curl.o utils/downloader_hmux.o utils/downloader_nghttp2.o utils/downloader_ngtcp2.o utils/downloader_ssl.o +endif # ifeq ($(DISABLE_PLAYER),no) LIBGPAC_UTILS+=utils/uni_bidi.o utils/unicode.o @@ -91,7 +98,7 @@ LIBGPAC_EVG=evg/ftgrays.o evg/raster3d.o evg/raster_565.o evg/raster_argb.o evg/raster_rgb.o evg/raster_yuv.o evg/stencil.o evg/surface.o ## libgpac objects gathering: src/media tools -LIBGPAC_MEDIATOOLS=media_tools/isom_tools.o media_tools/dash_segmenter.o media_tools/av_parsers.o media_tools/route_dmx.o +LIBGPAC_MEDIATOOLS=media_tools/isom_tools.o media_tools/dash_segmenter.o media_tools/av_parsers.o media_tools/route_dmx.o media_tools/id3.o ifeq ($(DISABLE_AV_PARSERS),no) LIBGPAC_MEDIATOOLS+=media_tools/img.o @@ -146,7 +153,7 @@ LIBGPAC_JSMODS= JSMODS_CFLAGS= ifeq ($(CONFIG_JS),yes) -LIBGPAC_QUICKJS=quickjs/cutils.o quickjs/libbf.o quickjs/libregexp.o quickjs/libunicode.o quickjs/quickjs.o quickjs/quickjs-libc.o +LIBGPAC_QUICKJS=quickjs/cutils.o quickjs/dtoa.o quickjs/libregexp.o quickjs/libunicode.o quickjs/quickjs.o quickjs/quickjs-libc.o LIBGPAC_JSMODS+=jsmods/core.o jsmods/evg.o jsmods/scene_js.o jsmods/storage.o jsmods/webgl.o jsmods/xhr.o endif @@ -188,7 +195,7 @@ LIBGPAC_SCENEMANAGER+=scene_manager/encode_isom.o endif -LIBGPAC_COMPOSITOR=compositor/audio_mixer.o compositor/font_engine.o compositor/mesh.o compositor/mesh_collide.o compositor/mesh_tesselate.o +LIBGPAC_COMPOSITOR=compositor/audio_mixer.o compositor/font_engine.o compositor/mesh.o compositor/mesh_collide.o compositor/mesh_tesselate.o ifeq ($(DISABLE_COMPOSITOR),no) ## libgpac objects gathering: src/compositor @@ -215,14 +222,12 @@ SCENEGRAPH_CFLAGS= MEDIATOOLS_CFLAGS= -LIBGPAC_FILTERS+=filters/bs_agg.o filters/bs_split.o filters/bsrw.o filters/compose.o filters/dasher.o filters/dec_ac52.o filters/dec_bifs.o filters/dec_faad.o filters/dec_img.o filters/dec_j2k.o filters/dec_laser.o filters/dec_mad.o filters/dec_mediacodec.o filters/dec_nvdec.o filters/dec_nvdec_sdk.o filters/dec_odf.o filters/dec_theora.o filters/dec_ttml.o filters/dec_ttxt.o filters/dec_uncv.o filters/dec_vorbis.o filters/dec_vtb.o filters/dec_webvtt.o filters/dec_xvid.o filters/decrypt_cenc_isma.o filters/dmx_avi.o filters/dmx_dash.o filters/dmx_ghi.o filters/dmx_gsf.o filters/dmx_m2ts.o filters/dmx_mpegps.o filters/dmx_nhml.o filters/dmx_nhnt.o filters/dmx_ogg.o filters/dmx_saf.o filters/dmx_vobsub.o filters/enc_jpg.o filters/enc_png.o filters/encrypt_cenc_isma.o filters/evg_rescale.o filters/filelist.o filters/hevcmerge.o filters/hevcsplit.o filters/in_dvb4linux.o filters/in_file.o filters/in_http.o filters/in_pipe.o filters/in_route.o filters/in_rtp.o filters/in_rtp_rtsp.o filters/in_rtp_sdp.o filters/in_rtp_signaling.o filters/in_rtp_stream.o filters/in_sock.o filters/inspect.o filters/io_fcryp.o filters/isoffin_load.o filters/isoffin_read.o filters/isoffin_read_ch.o filters/jsfilter.o filters/load_bt_xmt.o filters/load_svg.o filters/load_text.o filters/mux_avi.o filters/mux_gsf.o filters/mux_isom.o filters/mux_ts.o filters/mux_ogg.o filters/out_audio.o filters/out_file.o filters/out_http.o filters/out_pipe.o filters/out_route.o filters/out_rtp.o filters/out_rtsp.o filters/out_sock.o filters/out_video.o filters/reframer.o filters/reframe_ac3.o filters/reframe_adts.o filters/reframe_latm.o filters/reframe_amr.o filters/reframe_av1.o filters/reframe_flac.o filters/reframe_h263.o filters/reframe_img.o filters/reframe_mhas.o filters/reframe_mp3.o filters/reframe_mpgvid.o filters/reframe_nalu.o filters/reframe_prores.o filters/reframe_qcp.o filters/reframe_rawvid.o filters/reframe_rawpcm.o filters/reframe_truehd.o filters/resample_audio.o filters/restamp.o filters/tileagg.o filters/tilesplit.o filters/tssplit.o filters/ttml_conv.o filters/unit_test_filter.o filters/rewind.o filters/rewrite_adts.o filters/rewrite_mhas.o filters/rewrite_mp4v.o filters/rewrite_nalu.o filters/rewrite_obu.o filters/vflip.o filters/vcrop.o filters/write_generic.o filters/write_nhml.o filters/write_nhnt.o filters/write_qcp.o filters/write_tx3g.o filters/write_vtt.o ../modules/dektec_out/dektec_video_decl.o filters/dec_opensvc.o filters/unframer.o +LIBGPAC_FILTERS+=filters/bs_agg.o filters/bs_split.o filters/bsrw.o filters/compose.o filters/dasher.o filters/dec_ac52.o filters/dec_bifs.o filters/dec_faad.o filters/dec_img.o filters/dec_j2k.o filters/dec_laser.o filters/dec_mad.o filters/dec_mediacodec.o filters/dec_nvdec.o filters/dec_nvdec_sdk.o filters/dec_odf.o filters/dec_theora.o filters/dec_ttml.o filters/dec_ttxt.o filters/dec_uncv.o filters/dec_vorbis.o filters/dec_vtb.o filters/dec_webvtt.o filters/dec_xvid.o filters/decrypt_cenc_isma.o filters/dmx_avi.o filters/dmx_dash.o filters/dmx_ghi.o filters/dmx_gsf.o filters/dmx_m2ts.o filters/dmx_mpegps.o filters/dmx_nhml.o filters/dmx_nhnt.o filters/dmx_ogg.o filters/dmx_saf.o filters/dmx_vobsub.o filters/enc_jpg.o filters/enc_png.o filters/encrypt_cenc_isma.o filters/evg_rescale.o filters/filelist.o filters/hevcmerge.o filters/hevcsplit.o filters/in_dvb4linux.o filters/in_file.o filters/in_http.o filters/in_pipe.o filters/in_route.o filters/in_route_repair.o filters/in_rtp.o filters/in_rtp_rtsp.o filters/in_rtp_sdp.o filters/in_rtp_signaling.o filters/in_rtp_stream.o filters/in_sock.o filters/inspect.o filters/io_fcryp.o filters/isoffin_load.o filters/isoffin_read.o filters/isoffin_read_ch.o filters/jsfilter.o filters/load_bt_xmt.o filters/load_svg.o filters/load_text.o filters/mux_avi.o filters/mux_gsf.o filters/mux_isom.o filters/mux_ts.o filters/mux_ogg.o filters/out_audio.o filters/out_file.o filters/out_http.o filters/out_pipe.o filters/out_route.o filters/out_rtp.o filters/out_rtsp.o filters/out_sock.o filters/out_video.o filters/reframer.o filters/reframe_ac3.o filters/reframe_ac4.o filters/reframe_adts.o filters/reframe_latm.o filters/reframe_amr.o filters/reframe_av1.o filters/reframe_flac.o filters/reframe_h263.o filters/reframe_img.o filters/reframe_mhas.o filters/reframe_mp3.o filters/reframe_mpgvid.o filters/reframe_nalu.o filters/reframe_prores.o filters/reframe_qcp.o filters/reframe_rawvid.o filters/reframe_rawpcm.o filters/reframe_truehd.o filters/resample_audio.o filters/restamp.o filters/tileagg.o filters/tilesplit.o filters/tssplit.o filters/ttml_conv.o filters/unit_test_filter.o filters/rewind.o filters/rewrite_adts.o filters/rewrite_mhas.o filters/rewrite_ac4.o filters/rewrite_mp4v.o filters/rewrite_nalu.o filters/rewrite_obu.o filters/vflip.o filters/vcrop.o filters/write_generic.o filters/write_nhml.o filters/write_nhnt.o filters/write_qcp.o filters/write_tx3g.o filters/write_vtt.o ../modules/dektec_out/dektec_video_decl.o filters/dec_opensvc.o filters/unframer.o filters/dec_scte35.o filters/sei_load.o LIBGPAC_FILTERS_FFMPEG=filters/ff_common.o filters/ff_avf.o filters/ff_dec.o filters/ff_dmx.o filters/ff_enc.o filters/ff_rescale.o filters/ff_mx.o filters/ff_bsf.o -LIBGPAC_FILTERS_LIBCAPTION=filters/dec_cc.o +LIBGPAC_FILTERS_LIBCAPTION=filters/enc_cc.o filters/dec_cc.o LIBGPAC_FILTERS_MPEGHDEC=filters/dec_mpeghdec.o -ifeq ($(CONFIG_EMSCRIPTEN),yes) LIBGPAC_FILTERS+=filters/dec_webcodec.o filters/enc_webcodec.o filters/avin_web.o -endif ifeq ($(STATIC_MODULES),yes) LIBGPAC_FILTERS_OHEVC=filters/dec_openhevc.o @@ -230,15 +235,17 @@ endif ## libgpac objects gathering: -OBJS=$(LIBGPAC_UTILS) $(LIBGPAC_CRYPTO) $(LIBGPAC_SCENE) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ISOM) $(LIBGPAC_ODF) $(LIBGPAC_MEDIATOOLS) $(LIBGPAC_SCENEMANAGER) $(LIBGPAC_COMPOSITOR) $(LIBGPAC_LASER) $(LIBGPAC_EVG) $(LIBGPAC_FILTERS) $(LIBGPAC_FILTERS_FFMPEG) $(LIBGPAC_FILTERS_LIBCAPTION) $(LIBGPAC_FILTERS_MPEGHDEC) $(LIBGPAC_FILTERS_OHEVC) $(LIBGPAC_QUICKJS) $(LIBGPAC_JSMODS) +OBJS=$(LIBGPAC_UTILS) $(LIBGPAC_DOWNLOADER) $(LIBGPAC_CRYPTO) $(LIBGPAC_SCENE) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ISOM) $(LIBGPAC_ODF) $(LIBGPAC_MEDIATOOLS) $(LIBGPAC_SCENEMANAGER) $(LIBGPAC_COMPOSITOR) $(LIBGPAC_LASER) $(LIBGPAC_EVG) $(LIBGPAC_FILTERS) $(LIBGPAC_FILTERS_FFMPEG) $(LIBGPAC_FILTERS_LIBCAPTION) $(LIBGPAC_FILTERS_MPEGHDEC) $(LIBGPAC_FILTERS_OHEVC) $(LIBGPAC_QUICKJS) $(LIBGPAC_JSMODS) +##to improve - we move ngtcp2 first because we likely have a custom SSL +EXTRALIBS:=$(ngtcp2_ldflags) $(nghttp3_ldflags) $(EXTRALIBS) ##include static modules and other deps for libgpac include ../static.mak #todo, we may need to trick ld for library path... -EXTRALIBS+=$(zlib_ldflags) $(opensvc_ldflags) $(ssl_ldflags) $(jpeg_ldflags) $(openjpeg_ldflags) $(png_ldflags) $(mad_ldflags) $(a52_ldflags) $(xvid_ldflags) $(faad_ldflags) -EXTRALIBS+=$(ffmpeg_ldflags) $(ogg_ldflags) $(vorbis_ldflags) $(theora_ldflags) $(nghttp2_ldflags) $(vtb_ldflags) $(caption_ldflags) $(mpeghdec_ldflags) +EXTRALIBS+= $(zlib_ldflags) $(opensvc_ldflags) $(ssl_ldflags) $(jpeg_ldflags) $(openjpeg_ldflags) $(png_ldflags) $(mad_ldflags) $(a52_ldflags) $(xvid_ldflags) $(faad_ldflags) +EXTRALIBS+=$(ffmpeg_ldflags) $(ogg_ldflags) $(vorbis_ldflags) $(theora_ldflags) $(nghttp2_ldflags) $(vtb_ldflags) $(caption_ldflags) $(mpeghdec_ldflags) $(curl_ldflags) ##libgpac library output LIB=libgpac$(DYN_LIB_SUFFIX) @@ -267,7 +274,7 @@ ifeq ($(STATIC_MODULES),yes) ifneq ($(CONFIG_OPENHEVC),no) -ALL_LIBS+=$(openhevc_ldflags) +ALL_LIBS+=$(openhevc_ldflags) -Wl,-Bsymbolic endif endif @@ -329,46 +336,64 @@ filters/dec_mad.o: CFLAGS += $(mad_cflags) filters/dec_mad.o: filters/dec_mad.c +.deps/filters/dec_mad.dep: CFLAGS += $(mad_cflags) filters/dec_ac52.o: CFLAGS += $(a52_cflags) filters/dec_ac52.o: filters/dec_ac52.c +.deps/filters/dec_ac52.dep: CFLAGS += $(a52_cflags) filters/dec_xvid.o: CFLAGS += $(xvid_cflags) filters/dec_xvid.o: filters/dec_xvid.c +.deps/filters/dec_xvid.dep: CFLAGS += $(xvid_cflags) filters/dec_faad.o: CFLAGS += $(faad_cflags) filters/dec_faad.o: filters/dec_faad.c - -filters/dec_faad.o: CFLAGS += $(faad_cflags) -filters/dec_faad.o: filters/dec_faad.c +.deps/filters/dec_faad.dep: CFLAGS += $(faad_cflags) filters/dec_theora.o: CFLAGS += $(ogg_cflags) $(theora_cflags) filters/dec_theora.o: filters/dec_theora.c +.deps/filters/dec_theora.dep: CFLAGS += $(ogg_cflags) $(theora_cflags) filters/dec_vorbis.o: CFLAGS += $(ogg_cflags) $(vorbis_cflags) filters/dec_vorbis.o: filters/dec_vorbis.c +.deps/filters/dec_vorbis.dep: CFLAGS += $(ogg_cflags) $(vorbis_cflags) filters/dec_vtb.o: CFLAGS += $(vtb_cflags) filters/dec_vtb.o: filters/dec_vtb.c +.deps/filters/dec_vtb.dep: CFLAGS += $(vtb_cflags) filters/enc_jpg.o: CFLAGS += $(jpeg_cflags) filters/enc_jpg.o: filters/enc_jpg.c +.deps/filters/enc_jpg.dep: CFLAGS += $(jpeg_cflags) filters/enc_png.o: CFLAGS += $(zlib_cflags) $(png_cflags) filters/enc_png.o: filters/enc_png.c +.deps/filters/enc_png.dep: CFLAGS += $(zlib_cflags) $(png_cflags) media_tools/img.o: CFLAGS += $(jpeg_cflags) $(zlib_cflags) $(png_cflags) media_tools/img.o: media_tools/img.c +.deps/media_tools/img.dep: CFLAGS += $(jpeg_cflags) $(zlib_cflags) $(png_cflags) + +ifeq ($(CONFIG_DARWIN),yes) + +downloader: CFLAGS := $(ngtcp2_cflags) $(nghttp3_cflags) $(nghttp2_cflags) $(ssl_cflags) $(CFLAGS) +downloader: $(LIBGPAC_DOWNLOADER) +.deps/utils/%: CFLAGS := $(ngtcp2_cflags) $(nghttp3_cflags) $(nghttp2_cflags) $(ssl_cflags) $(CFLAGS) + +else + +downloader: CFLAGS += $(nghttp2_cflags) $(ngtcp2_cflags) $(nghttp3_cflags) $(ssl_cflags) +downloader: $(LIBGPAC_DOWNLOADER) +.deps/utils/%: CFLAGS += $(nghttp2_cflags) $(ngtcp2_cflags) $(nghttp3_cflags) $(ssl_cflags) -utils/downloader.o: CFLAGS += $(nghttp2_cflags) $(ssl_cflags) -utils/downloader.o: utils/downloader.c -.deps/utils/downloader.dep: CFLAGS += $(nghttp2_cflags) $(ssl_cflags) +endif crypto/g_crypt_openssl.o: CFLAGS += $(ssl_cflags) crypto/g_crypt_openssl.o: crypto/g_crypt_openssl.c +.deps/crypto/g_crypt_openssl.dep: CFLAGS += $(ssl_cflags) -../bin/gcc/$(LIB): $(LIBGPAC_UTILS) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ODF) $(LIBGPAC_LASER) $(LIBGPAC_ISOM) $(LIBGPAC_SCENEMANAGER) compositor scenegraph media_tools crypto filters filters_ffmpeg filters_libcaption filters_mpeghdec filters_ohevc jsmods $(OBJS) +../bin/gcc/$(LIB): $(LIBGPAC_UTILS) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ODF) $(LIBGPAC_LASER) $(LIBGPAC_ISOM) $(LIBGPAC_SCENEMANAGER) downloader compositor scenegraph media_tools crypto filters filters_ffmpeg filters_libcaption filters_mpeghdec filters_ohevc jsmods $(OBJS) # @echo "OBJS $(OBJS)" # @echo "LIBS $(ALL_LIBS)" @@ -397,6 +422,17 @@ @echo "Libs.private: -lgpac_static $(ALL_LIBS)" >> ../gpac.pc endif + +unit_tests: +ifeq ($(UNIT_TESTS),yes) + $(CC) $(CFLAGS) $(caption_cflags) -Wno-unused-variable -I.. -I$(SRC_PATH)/include -DGPAC_HAVE_CONFIG_H \ + -o ../bin/gcc/unittests \ + ../bin/gcc/unittests.c \ + $(shell find $(SRC_PATH) -path "*/unittests/*.c" | grep -v bin | sort) \ + -Wl,-rpath,$(realpath ../bin/gcc) -L../bin/gcc -lgpac +endif + + dep: DEPS := $(SRCS:%.c=.deps/%.dep)
View file
gpac-2.4.0.tar.gz/src/bifs/field_decode.c -> gpac-26.02.0.tar.gz/src/bifs/field_decode.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / BIFS codec sub-project @@ -920,18 +920,28 @@ /*QP 14 is a special quant mode for IndexFace/Line(2D)Set to quantize the coordonate(2D) child, based on the first field parsed - we must check the type of the node and notfy the QP*/ + we must check the type of the node and notify the QP*/ switch (node_tag) { case TAG_MPEG4_Coordinate: case TAG_MPEG4_Coordinate2D: gf_bifs_dec_qp14_enter(codec, GF_TRUE); } - if (gf_bs_read_int(bs, 1)) { + codec->tree_depth++; + //don't allow too deep trees, will likely result in stack overflow +#define MAX_TREE_DEPTH 1500 + if (codec->tree_depth > MAX_TREE_DEPTH) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("BIFS Maximum tree depth (%u) exceeded, cannot decode\n", MAX_TREE_DEPTH)); + e = GF_NON_COMPLIANT_BITSTREAM; + } + else if (gf_bs_read_int(bs, 1)) { e = gf_bifs_dec_node_mask(codec, bs, new_node, proto ? GF_TRUE : GF_FALSE); } else { e = gf_bifs_dec_node_list(codec, bs, new_node, proto ? GF_TRUE : GF_FALSE); } + codec->tree_depth--; +#undef MAX_TREE_DEPTH + if (codec->coord_stored && reset_qp14) gf_bifs_dec_qp14_reset(codec);
View file
gpac-2.4.0.tar.gz/src/bifs/field_encode.c -> gpac-26.02.0.tar.gz/src/bifs/field_encode.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / BIFS codec sub-project @@ -94,7 +94,7 @@ case GF_SG_VRML_SFSTRING: if (node && (node->sgprivate->tag==TAG_MPEG4_CacheTexture) && (field->fieldIndex<=2)) { u32 size, val; - char buf4096; + char buf2048; char *res_src = NULL; const char *src = ((SFString*)field->far_ptr)->buffer; FILE *f;
View file
gpac-2.4.0.tar.gz/src/bifs/memory_decoder.c -> gpac-26.02.0.tar.gz/src/bifs/memory_decoder.c
Changed
@@ -172,7 +172,7 @@ if (!node) return GF_NON_COMPLIANT_BITSTREAM; /*reset global QP*/ - if (codec->scenegraph->global_qp) { + if (codec->scenegraph->global_qp && codec->scenegraph->global_qp != node) { gf_node_unregister(codec->scenegraph->global_qp, NULL); } codec->ActiveQP = NULL; @@ -1074,4 +1074,3 @@ } #endif /*GPAC_DISABLE_BIFS*/ -
View file
gpac-2.4.0.tar.gz/src/compositor/compositor.c -> gpac-26.02.0.tar.gz/src/compositor/compositor.c
Changed
@@ -332,7 +332,7 @@ gf_sc_ar_send_or_reconfig(compositor->audio_renderer); if (!compositor->vout) return GF_FALSE; - + //frame still pending if (compositor->frame_ifce.user_data) return GF_TRUE; @@ -443,7 +443,7 @@ } } - + #ifndef GPAC_DISABLE_3D return compositor_3d_setup_fbo(evt->setup.width, evt->setup.height, &compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id); #else @@ -512,7 +512,10 @@ compositor->passthrough_pfmt = pfmt; stride=0; stride_uv = 0; - gf_pixel_get_size_info(pfmt, evt->setup.width, evt->setup.height, &compositor->framebuffer_size, &stride, &stride_uv, NULL, NULL); + if (!gf_pixel_get_size_info(pfmt, evt->setup.width, evt->setup.height, &compositor->framebuffer_size, &stride, &stride_uv, NULL, NULL)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Compositor Cannot get proper size info for pixel format %s and hxw %dx%d\n", gf_pixel_fmt_name(pfmt), evt->setup.height, evt->setup.width)); + return GF_NOT_SUPPORTED; + } if (compositor->vout) { gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(pfmt)); @@ -693,6 +696,20 @@ if (!gf_opts_get_key("core", "video-output")) { gf_opts_set_key("core", "video-output", compositor->video_out->module_name); } + //also send a video setup as some drivers may init openGL only then + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.width = 128; + evt.setup.height = 128; + evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE; + +#ifndef GPAC_DISABLE_3D + evt.setup.use_opengl = GF_TRUE; + evt.setup.system_memory = GF_FALSE; + evt.setup.back_buffer = GF_TRUE; +#endif + compositor->video_out->ProcessEvent(compositor->video_out, &evt); gf_filter_register_opengl_provider(compositor->filter, GF_TRUE); return GF_OK; } @@ -1381,6 +1398,8 @@ do_notif = 1; if (w->type!=SVG_NUMBER_PERCENTAGE) { width = FIX2INT(gf_sc_svg_convert_length_to_display(compositor, w) ); + if (width>=GF_INT_MAX) + width = SC_DEF_WIDTH; } else if ((u32) FIX2INT(vb->width)<compositor->video_out->max_screen_width) { width = FIX2INT(vb->width); } else { @@ -1389,6 +1408,8 @@ } if (h->type!=SVG_NUMBER_PERCENTAGE) { height = FIX2INT(gf_sc_svg_convert_length_to_display(compositor, h) ); + if (height>=GF_INT_MAX) + height = SC_DEF_WIDTH; } else if ((u32) FIX2INT(vb->height)<compositor->video_out->max_screen_height) { height = FIX2INT(vb->height); } else { @@ -2761,7 +2782,7 @@ compositor->scene_sampled_clock = compositor->passthrough_txh->last_frame_time + dur; } } - + //it may happen that we have a reconfigure request at this stage, especially if updating one of the textures //forced a relayout - do it right away if (compositor->msg_type) { @@ -4700,7 +4721,7 @@ } } if (is_self) return; - + /*disconnect*/ gf_sc_disconnect(compositor); }
View file
gpac-2.4.0.tar.gz/src/compositor/compositor_2d.c -> gpac-26.02.0.tar.gz/src/compositor/compositor_2d.c
Changed
@@ -728,7 +728,7 @@ } video_src.global_alpha = alpha; - //overlay queing + //overlay queuing if (overlay_type==2) { GF_IRect o_rc; GF_OverlayStack *ol, *first; @@ -900,7 +900,7 @@ /*check if texture is ready - if not pretend we drew it*/ if (!ctx->aspect.fill_texture->data) return GF_TRUE; if (ctx->transform.m0<0) return GF_FALSE; - /*check if the <0 value is due to a flip in he scene description or + /*check if the <0 value is due to a flip in the scene description or due to bifs<->svg... context switching*/ if (ctx->transform.m4<0) { if (!(ctx->flags & CTX_FLIPED_COORDS)) return GF_FALSE; @@ -1362,7 +1362,7 @@ ol = ol->next; continue; } - /*check previsously drawn areas*/ + /*check previously drawn areas*/ for (i=0; i<ol->ra.count; i++) { /*we have drawn something here, don't draw*/ if (gf_irect_inside(&ol->ra.listi.rect, &clip))
View file
gpac-2.4.0.tar.gz/src/compositor/events.c -> gpac-26.02.0.tar.gz/src/compositor/events.c
Changed
@@ -145,8 +145,8 @@ gf_sc_lock(compositor, GF_TRUE); - conv_buf = (u16*)gf_malloc(sizeof(u16)*(len+1)); - len = gf_utf8_mbstowcs(conv_buf, len, &text); + conv_buf = (u16*)gf_malloc(sizeof(u16)*((len/2)*2+2)); + len = gf_utf8_mbstowcs(conv_buf, len+1, &text); if (len == GF_UTF8_FAIL) return GF_IO_ERR; compositor->sel_buffer_alloc += len;
View file
gpac-2.4.0.tar.gz/src/compositor/font_engine.c -> gpac-26.02.0.tar.gz/src/compositor/font_engine.c
Changed
@@ -182,6 +182,7 @@ for (i=0; i<nb_fonts; i++) { char *font_name; const char *opt; + Bool is_def_font=GF_FALSE; u32 weight_diff = 0xFFFFFFFF; GF_Font *best_font = NULL; GF_Font *font = fm->font; @@ -191,24 +192,30 @@ if (!stricmp(font_name, "SERIF")) { opt = gf_opts_get_key("FontCache", "FontSerif"); if (opt) font_name = (char*)opt; + is_def_font = GF_TRUE; } else if (!stricmp(font_name, "SANS") || !stricmp(font_name, "sans-serif")) { opt = gf_opts_get_key("FontCache", "FontSans"); if (opt) font_name = (char*)opt; + is_def_font = GF_TRUE; } else if (!stricmp(font_name, "TYPEWRITER") || !stricmp(font_name, "monospace")) { opt = gf_opts_get_key("FontCache", "FontFixed"); if (opt) font_name = (char*)opt; + is_def_font = GF_TRUE; } - while (font) { + while (font && font_name) { if (fm->wait_font_load && font->not_loaded && !check_only && !stricmp(font->name, font_name)) { GF_Font *a_font = NULL; if (font->get_alias) a_font = font->get_alias(font->udta); if (!a_font || a_font->not_loaded) return font; } - if ((check_only || !font->not_loaded) && font->name && !stricmp(font->name, font_name)) { + if ((check_only || !font->not_loaded) && font->name + //def font may have a style concatenated if only fonts with styles other than none/italic/bold are present + && (!stricmp(font->name, font_name) || (is_def_font && strncmp(font->name, font_name, strlen(font_name)))) + ) { s32 fw; s32 w; u32 diff;
View file
gpac-2.4.0.tar.gz/src/compositor/gl_inc.h -> gpac-26.02.0.tar.gz/src/compositor/gl_inc.h
Changed
@@ -633,7 +633,7 @@ GLDECL(void, glUniformMatrix3x4fv, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) ) GLDECL(void, glUniformMatrix4x3fv, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) ) GLDECL(void, glGetProgramiv, (GLuint program, GLenum pname, GLint *params) ) -GLDECL(void, glGetProgramInfoLog, (GLuint program, GLsizei maxLength, GLsizei *length, char *infoLog) ) +GLDECL(void, glGetProgramInfoLog, (GLuint program, GLsizei maxLength, GLsizei *length, char *infoLog) ) GLDECL(void, glGetShaderInfoLog, (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog) ) GLDECL(void, glGetShaderSource, (GLuint shader, GLsizei bufSize, GLsizei *length, char *source) ) @@ -672,8 +672,8 @@ #ifndef GPAC_CONFIG_ANDROID GLDECL(void, glEnableVertexAttribArray, (GLuint index) ) GLDECL(void, glDisableVertexAttribArray, (GLuint index) ) -GLDECL(void, glVertexAttribPointer, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) ) -GLDECL(void, glVertexAttribIPointer, (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) ) +GLDECL(void, glVertexAttribPointer, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) ) +GLDECL(void, glVertexAttribIPointer, (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) ) GLDECL(GLint, glGetAttribLocation, (GLuint prog, const char *name) ) GLDECL(void, glBindFramebuffer, (GLenum target, GLuint framebuffer))
View file
gpac-2.4.0.tar.gz/src/compositor/media_object.c -> gpac-26.02.0.tar.gz/src/compositor/media_object.c
Changed
@@ -382,10 +382,12 @@ } v = gf_filter_pid_get_property(mo->odm->pid, GF_PROP_PID_SAR); - if (v) { + if (v && (v->value.frac.num>0)) { u32 n_par = (v->value.frac.num) << 16 | (v->value.frac.den); if (mo->pixel_ar && (mo->pixel_ar!=n_par)) changed = GF_TRUE; mo->pixel_ar = n_par; + } else { + mo->pixel_ar = 0; } v = gf_filter_pid_get_property(mo->odm->pid, GF_PROP_PID_SRD);
View file
gpac-2.4.0.tar.gz/src/compositor/mpeg4_geometry_ifs2d.c -> gpac-26.02.0.tar.gz/src/compositor/mpeg4_geometry_ifs2d.c
Changed
@@ -267,6 +267,8 @@ return; } if (!ifs2D->coord) return; + if ((gf_node_get_tag(ifs2D->coord) != TAG_MPEG4_Coordinate) && (gf_node_get_tag(ifs2D->coord) != TAG_MPEG4_Coordinate2D)) + return; ifs2d_check_changes(node, stack, tr_state);
View file
gpac-2.4.0.tar.gz/src/compositor/mpeg4_viewport.c -> gpac-26.02.0.tar.gz/src/compositor/mpeg4_viewport.c
Changed
@@ -246,6 +246,8 @@ default: return; } + if (!rc.width || !rc.height) + return; sx = gf_divfix(rc.width, rc_bckup.width); sy = gf_divfix(rc.height, rc_bckup.height);
View file
gpac-2.4.0.tar.gz/src/compositor/nodes_stacks.h -> gpac-26.02.0.tar.gz/src/compositor/nodes_stacks.h
Changed
@@ -81,7 +81,7 @@ /*special user-modif of viewport/viewpoint: if stack is not NULL, binding is only performed in this stack - otherwise, binding is performed on all stack*/ + otherwise, binding is performed on all stack*/ void Bindable_SetSetBindEx(GF_Node *bindable, Bool val, GF_List *stack);
View file
gpac-2.4.0.tar.gz/src/compositor/object_manager.c -> gpac-26.02.0.tar.gz/src/compositor/object_manager.c
Changed
@@ -554,7 +554,7 @@ if (!role_set) { const GF_PropertyValue *prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_ROLE); - if (prop && prop->value.string && !strcmp(prop->value.string, "main")) { + if (prop && prop->value.string_list.nb_items && !strcmp(prop->value.string_list.vals0, "main")) { odm->addon->addon_type = GF_ADDON_TYPE_MAIN; } } @@ -573,8 +573,8 @@ } } else if (odm->parentscene) { const GF_PropertyValue *prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_ROLE); - if (prop && prop->value.string && !strncmp(prop->value.string, "ambi", 4)) { - odm->ambi_ch_id = atoi(prop->value.string + 4); + if (prop && prop->value.string_list.nb_items && !strncmp(prop->value.string_list.vals0, "ambi", 4)) { + odm->ambi_ch_id = atoi(prop->value.string_list.vals0 + 4); if (odm->ambi_ch_id > odm->parentscene->ambisonic_type) odm->parentscene->ambisonic_type = odm->ambi_ch_id; } @@ -2058,7 +2058,7 @@ prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); if (prop) info->height = prop->value.uint; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (prop) info->par = (prop->value.frac.num) << 16 | (prop->value.frac.den); + if (prop && (prop->value.frac.num>0)) info->par = (prop->value.frac.num) << 16 | (prop->value.frac.den); break; case GF_STREAM_AUDIO: prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
View file
gpac-2.4.0.tar.gz/src/compositor/scene.c -> gpac-26.02.0.tar.gz/src/compositor/scene.c
Changed
@@ -2155,6 +2155,13 @@ return; } + //if same service jump at current media time + if (scene->selected_service_id == odm->ServiceID) + scene->root_od->media_start_time = gf_clock_media_time(odm->ck); + else + scene->root_od->media_start_time = 0; + scene->selected_service_id = odm->ServiceID; + if (odm->type == GF_STREAM_AUDIO) { M_AudioClip *ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1"); if (!ac) return; @@ -2192,9 +2199,8 @@ mt->url.vals0.url = gf_strdup(url); } mt->startTime = gf_scene_get_time(scene); - gf_node_changed((GF_Node *)mt, NULL); if (odm->mo) gf_scene_force_size_to_video(scene, odm->mo); - scene->selected_service_id = odm->ServiceID; + gf_node_changed((GF_Node *)mt, NULL); return; } @@ -3223,7 +3229,7 @@ GF_AddonMedia *prev_addon = gf_list_get(scene->declared_addons, i); //we are adding a non splicing point: discard all previously declared addons if (!addon->is_splicing - //this is a splicing point, discard all previsously declared splicing addons + //this is a splicing point, discard all previously declared splicing addons || prev_addon->is_splicing ) { gf_scene_reset_addon(prev_addon, GF_TRUE);
View file
gpac-2.4.0.tar.gz/src/compositor/svg_font.c -> gpac-26.02.0.tar.gz/src/compositor/svg_font.c
Changed
@@ -376,7 +376,7 @@ GF_SAFEALLOC(st, SVG_GlyphStack); if (!st) return; utf8 = (u8 *) *atts.unicode; - len = gf_utf8_mbstowcs(utf_name, 200, (const char **) &utf8); + len = gf_utf8_mbstowcs(utf_name, sizeof(utf_name), (const char **) &utf8); if (len == GF_UTF8_FAIL) return; /*this is a single glyph*/ if (len==1) {
View file
gpac-2.4.0.tar.gz/src/compositor/texturing_gl.c -> gpac-26.02.0.tar.gz/src/compositor/texturing_gl.c
Changed
@@ -469,7 +469,7 @@ case GF_PIXEL_YUV_10: case GF_PIXEL_YUV422: case GF_PIXEL_YUV422_10: - case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_NV21: case GF_PIXEL_NV21_10: @@ -530,7 +530,7 @@ p = gf_filter_pid_get_property(txh->stream->odm->pid, GF_PROP_PID_COLR_MX); if (p) cmx = p->value.uint; } - + txh->tx_io->tx.pbo_state = (txh->compositor->gl_caps.pbo && txh->compositor->pbo) ? GF_GL_PBO_BOTH : GF_GL_PBO_NONE; if (txh->tx_io->conv_format) { stride = txh->tx_io->conv_stride; @@ -616,7 +616,7 @@ case GF_PIXEL_YUV_10: case GF_PIXEL_YUV422: case GF_PIXEL_YUV422_10: - case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_NV12: case GF_PIXEL_NV12_10: @@ -677,7 +677,7 @@ case GF_PIXEL_YUV_10: case GF_PIXEL_YUV422: case GF_PIXEL_YUV422_10: - case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_NV21: case GF_PIXEL_NV12: @@ -780,8 +780,13 @@ } } if (!pData) { - if (!txh->compositor->last_error) + if (!txh->compositor->last_error) { txh->compositor->last_error = GF_NOT_SUPPORTED; + if (!txh->compositor->player) { + gf_filter_abort(txh->compositor->filter); + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("Compositor Failed to fetch hardware texture data - try specifying `:drv=yes`\n")); + } + } return 0; } @@ -866,7 +871,7 @@ GL_CHECK_ERR() gf_gl_txw_upload(&txh->tx_io->tx, data, txh->frame_ifce); GL_CHECK_ERR() - + #endif push_time = gf_sys_clock() - push_time; @@ -1222,17 +1227,14 @@ return 0; if (!txh->stream || txh->data || txh->frame_ifce) { - gf_rmt_begin_gl(gf_sc_texture_push_image); glGetError(); res = gf_sc_texture_push_image(txh, 0, 0); - gf_rmt_end_gl(); glGetError(); if (!res) return 0; } skip_push: - gf_rmt_begin_gl(gf_sc_texture_enable); glGetError(); if (bounds && txh->compute_gradient_matrix) { @@ -1264,7 +1266,6 @@ tx_bind(txh); } - gf_rmt_end_gl(); glGetError(); return 1;
View file
gpac-2.4.0.tar.gz/src/compositor/visual_manager_3d_gl.c -> gpac-26.02.0.tar.gz/src/compositor/visual_manager_3d_gl.c
Changed
@@ -127,7 +127,7 @@ if (CHECK_GL_EXT("EXT_unpack_subimage") ) { compositor->gl_caps.gles2_unpack = 1; } - + if (!has_gl_context) return; @@ -1329,7 +1329,7 @@ glHint(GL_POINT_SMOOTH, GL_DONT_CARE); glHint(GL_LINE_SMOOTH, GL_DONT_CARE); glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE); - + glDisable( GL_MULTISAMPLE_ARB); */ } @@ -2489,7 +2489,7 @@ yuv_mode = 2; break; } - + glUniform1i(loc, yuv_mode); } GL_CHECK_ERR() @@ -3169,7 +3169,6 @@ GL_CHECK_ERR() - gf_rmt_begin_gl(visual_3d_mesh_paint); glGetError(); GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("V3D Drawing mesh %p\n", mesh)); @@ -3215,7 +3214,6 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("V3D Done drawing mesh %p\n", mesh)); - gf_rmt_end_gl(); glGetError(); }
View file
gpac-2.4.0.tar.gz/src/crypto/g_crypt_openssl.c -> gpac-26.02.0.tar.gz/src/crypto/g_crypt_openssl.c
Changed
@@ -193,7 +193,7 @@ /* ECB */ typedef struct { - AES_KEY key; + AES_KEY enc_key, dec_key; } Openssl_ctx_ecb; /** CBC STUFF **/ @@ -216,8 +216,8 @@ void gf_set_key_openssl_ecb(GF_Crypt* td, void *key) { Openssl_ctx_ecb* ctx = (Openssl_ctx_ecb*)td->context; - AES_set_encrypt_key(key, 128, &(ctx->key)); - AES_set_decrypt_key(key, 128, &(ctx->key)); + AES_set_encrypt_key(key, 128, &(ctx->enc_key)); + AES_set_decrypt_key(key, 128, &(ctx->dec_key)); } GF_Err gf_crypt_set_IV_openssl_ecb(GF_Crypt* td, const u8 *iv, u32 iv_size) @@ -242,7 +242,7 @@ } for (iteration = 0; iteration < numberOfIterations; ++iteration) { - AES_ecb_encrypt(plaintext + iteration*AES_BLOCK_SIZE, plaintext + iteration*AES_BLOCK_SIZE, &ctx->key, aes_crypt_type); + AES_ecb_encrypt(plaintext + iteration*AES_BLOCK_SIZE, plaintext + iteration*AES_BLOCK_SIZE, aes_crypt_type ? &ctx->enc_key : &ctx->dec_key, aes_crypt_type); } return GF_OK; }
View file
gpac-2.4.0.tar.gz/src/evg/rast_soft.h -> gpac-26.02.0.tar.gz/src/evg/rast_soft.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / software 2D rasterizer @@ -290,6 +290,11 @@ GF_Err evg_surface_clear_565(GF_EVGSurface *_this, GF_IRect rc, GF_Color col); +void evg_332_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx); +void evg_332_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx); +void evg_332_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx); +GF_Err evg_surface_clear_332(GF_EVGSurface *surf, GF_IRect rc, GF_Color col); + void evg_444_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx); void evg_444_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx); void evg_444_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx); @@ -348,6 +353,8 @@ void evg_555_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf); void evg_444_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf); void evg_444_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf); +void evg_332_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf); +void evg_332_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf); #include <limits.h> @@ -553,7 +560,7 @@ u32 max_lines; TPos min_ex, max_ex, min_ey, max_ey; TCoord ex, ey; - TPos x, y, last_ey; + TPos x, y, last_ey; TArea area; int cover; u32 idx1, idx2;
View file
gpac-2.4.0.tar.gz/src/evg/raster_565.c -> gpac-26.02.0.tar.gz/src/evg/raster_565.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / software 2D rasterizer @@ -658,4 +658,194 @@ return GF_OK; } + +/* + RGB 332 part +*/ + +static void write_332(u8 *dst, u8 r, u8 g, u8 b) +{ + dst0 = (r & 0xE0) | ((g >> 3) & 0x1C) | ((b>>6)&0x3); +} + +static void overmask_332(u8 *dst, u32 src, u32 alpha) +{ + u32 resr, resg, resb; + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + s32 dstr = (dst0 & 0xE0); + s32 dstg = (dst0 & 0x1C) << 3; + s32 dstb = (dst1 & 0x3) << 6; + + srca = mul255(srca, alpha); + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_332(dst, resr, resg, resb); +} + +static void overmask_332_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) +{ + u32 resr, resg, resb; + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + while (count) { + s32 dstr = dst0 & 0xE0; + s32 dstg = (dst0 & 0x1C) << 3; + s32 dstb = (dst0 & 0x3) << 6; + + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_332(dst, resr, resg, resb); + + dst += dst_pitch_x; + count--; + } +} + + +void evg_332_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 r = GF_COL_R(col); + u8 g = GF_COL_G(col); + u8 b = GF_COL_B(col); + write_332(dst, r, g, b); +} + +void evg_332_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_332(dst, col, coverage); +} + +void evg_332_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) +{ + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + u8 *dst = surf->pixels + y * surf->pitch_y; + u8 r, g, b; + s32 i, x; + u32 len; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + col_no_a = col&0x00FFFFFF; + for (i=0; i<count; i++) { + x = spansi.x * surf->pitch_x; + len = spansi.len; + if (spansi.coverage != 0xFF) { + a = mul255(0xFF, spansi.coverage); + fin = (a<<24) | col_no_a; + overmask_332_const_run(fin, dst+x, surf->pitch_x, len); + } else { + while (len--) { + write_332(dst+x, r, g, b); + x+=surf->pitch_x; + } + } + } +} + +void evg_332_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + s32 i; + + a = (col>>24)&0xFF; + col_no_a = col & 0x00FFFFFF; + + + if (surf->get_alpha) { + for (i=0; i<count; i++) { + u32 j; + for (j=0; j<spansi.len; j++) { + s32 x = spansi.x + j; + u8 aa = surf->get_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spansi.coverage); + fin = (fin<<24) | col_no_a; + overmask_332_const_run(fin, dst + x*surf->pitch_x, surf->pitch_x, 1); + } + } + } else { + for (i=0; i<count; i++) { + fin = mul255(a, spansi.coverage); + fin = (fin<<24) | col_no_a; + overmask_332_const_run(fin, dst + spansi.x*surf->pitch_x, surf->pitch_x, spansi.len); + } + } +} + + +void evg_332_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; i<count; i++) { + u8 spanalpha, col_a; + s32 x; + u32 len; + u32 *col; + len = spansi.len; + col = surf->fill_run(surf->sten, rctx, &spansi, y); + spanalpha = spansi.coverage; + x = spansi.x * surf->pitch_x; + while (len--) { + col_a = GF_COL_A(*col); + if (col_a) { + u32 _col = *col; + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_332(dst+x, _col, spanalpha); + } else { + write_332(dst+x, GF_COL_R(_col), GF_COL_G(_col), GF_COL_B(_col) ); + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_332(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u32 x, y, w, h, sx, sy; + u8 r, g, b; + u8 *data_o; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + data_o = NULL; + for (y=0; y<h; y++) { + u8 *data = surf->pixels + (sy+y) * surf->pitch_y + surf->pitch_x*sx; + if (!y) { + data_o = data; + for (x=0; x<w; x++) { + write_332(data, r, g, b); + data += surf->pitch_x; + } + } else { + memcpy(data, data_o, w * surf->pitch_x); + } + } + return GF_OK; +} #endif // GPAC_DISABLE_EVG
View file
gpac-2.4.0.tar.gz/src/evg/raster_rgb.c -> gpac-26.02.0.tar.gz/src/evg/raster_rgb.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / software 2D rasterizer module @@ -107,7 +107,7 @@ col_no_a = col & 0x00FFFFFF; for (i=0; i<count; i++) { u32 a, fin, len; - char *p; + u8 *p; len = spansi.len; p = dst + spansi.x * surf->pitch_x; @@ -228,10 +228,10 @@ grey part */ -static void overmask_grey(u32 src, char *dst, u32 alpha, u32 grey_type) +static void overmask_grey(u32 src, u8 *dst, u32 alpha, u32 grey_type) { s32 srca = (src >> 24) & 0xff; - u32 srcc; + s32 srcc; s32 dstc = *dst; if (grey_type==0) srcc = (src >> 16) & 0xff; @@ -243,7 +243,7 @@ *dst = mul255(srca, srcc - dstc) + dstc; } -static void overmask_grey_const_run(u8 srca, u8 srcc, char *dst, s32 dst_pitch_x, u32 count) +static void overmask_grey_const_run(u8 srca, u8 srcc, u8 *dst, s32 dst_pitch_x, u32 count) { while (count) { u8 dstc = *(dst); @@ -283,7 +283,7 @@ for (i=0; i<count; i++) { u32 a, len; - char *p; + u8 *p; len = spansi.len; p = dst + spansi.x * surf->pitch_x; @@ -382,7 +382,7 @@ else r = GF_COL_B(col); for (y = 0; y < h; y++) { - char *data = surf ->pixels + (y + sy) * st + surf->pitch_x*sx; + u8 *data = surf ->pixels + (y + sy) * st + surf->pitch_x*sx; memset(data, r, w*surf->pitch_x); } return GF_OK;
View file
gpac-2.4.0.tar.gz/src/evg/raster_yuv.c -> gpac-26.02.0.tar.gz/src/evg/raster_yuv.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2025 * All rights reserved * * This file is part of GPAC / software 2D rasterizer module @@ -261,7 +261,7 @@ YUV420p part */ -static void overmask_yuv420p(u8 col_a, u8 cy, char *dst, u32 alpha) +static void overmask_yuv420p(u8 col_a, u8 cy, u8 *dst, u32 alpha) { s32 srca = col_a; u32 srcc = cy; @@ -300,7 +300,7 @@ a += surf_uv_alphai + surf_uv_alphai+1; if (a) { - char *s_ptr_u, *s_ptr_v; + u8 *s_ptr_u, *s_ptr_v; a /= 4; @@ -323,7 +323,7 @@ void evg_yuv420p_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { - char *pY = surf->pixels; + u8 *pY = surf->pixels; u8 *surf_uv_alpha; s32 i; u8 cy, cu, cv; @@ -347,7 +347,7 @@ for (i=0; i<count; i++) { u32 a; - char *s_pY; + u8 *s_pY; u32 len; len = spansi.len; s_pY = pY + spansi.x; @@ -373,7 +373,7 @@ void evg_yuv420p_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { u32 a; - char *pY = surf->pixels; + u8 *pY = surf->pixels; u8 *surf_uv_alpha; s32 i; u8 cy, cu, cv; @@ -400,7 +400,7 @@ u32 fin, j; for (j=0; j<spansi.len; j++) { s32 x = spansi.x + j; - char *s_pY = pY + x; + u8 *s_pY = pY + x; u8 aa = surf->get_alpha(surf->get_alpha_udta, a, x, y); fin = mul255(aa, spansi.coverage); @@ -411,7 +411,7 @@ } } else { for (i=0; i<count; i++) { - char *s_pY; + u8 *s_pY; u32 fin, len; len = spansi.len; s_pY = pY + spansi.x; @@ -512,7 +512,7 @@ void evg_yuv420p_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { s32 i; - char *pY = surf->pixels; + u8 *pY = surf->pixels; u8 *surf_uv_alpha; Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; @@ -531,7 +531,7 @@ for (i=0; i<count; i++) { u8 spanalpha, col_a; - char *s_pY; + u8 *s_pY; short x; u32 *p_col; u32 len; @@ -623,7 +623,7 @@ void evg_nv12_flush_uv_const(GF_EVGSurface *surf, EVGRasterCtx *rctx, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) { u32 i, a; - char *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; pU += y/2 * surf->pitch_y; for (i=0; i<surf->width; i+=2) { @@ -635,7 +635,7 @@ a += surf_uv_alphai + surf_uv_alphai+1; if (a) { - char *s_ptr; + u8 *s_ptr; a /= 4; s_ptr = pU + i; @@ -660,7 +660,7 @@ void evg_nv12_flush_uv_var(GF_EVGSurface *surf, EVGRasterCtx *rctx, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) { u32 i; - char *pU; + u8 *pU; pU = surf->pixels + surf->height *surf->pitch_y; pU += y/2 * surf->pitch_y; @@ -735,9 +735,9 @@ s32 i; u8 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; - char *pY = surf->pixels; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pU_first; + u8 *pY = surf->pixels; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pU_first; pY += rc.y * surf->pitch_y; pU += rc.y/2 * surf->pitch_y; @@ -785,8 +785,8 @@ void evg_yuv422p_flush_uv_const(GF_EVGSurface *surf, EVGRasterCtx *rctx, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) { u32 i, a; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV; pU += y * surf->pitch_y/2; pV = pU + surf->height * surf->pitch_y/2; @@ -796,7 +796,7 @@ a = rctx->uv_alphai + rctx->uv_alphai+1; if (a) { - char *s_ptr_u, *s_ptr_v; + u8 *s_ptr_u, *s_ptr_v; a /= 2; s_ptr_u = pU + i / 2; @@ -819,7 +819,7 @@ void evg_yuv422p_flush_uv_var(GF_EVGSurface *surf, EVGRasterCtx *rctx, u8 *surf_uv_alpha, s32 _cu, s32 _cv, s32 y) { u32 i; - char *pU, *pV; + u8 *pU, *pV; pU = surf->pixels + surf->height *surf->pitch_y; pU += y * surf->pitch_y/2; pV = pU + surf->height * surf->pitch_y/2; @@ -877,9 +877,9 @@ s32 i; u8 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; - char *pY = surf->pixels; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV; + u8 *pY = surf->pixels; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV; pY += rc.y * surf->pitch_y; pU += rc.y/2 * surf->pitch_y/2; @@ -916,7 +916,7 @@ void evg_yuv444p_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { - char *pY, *pU, *pV; + u8 *pY, *pU, *pV; s32 i; u8 cy, cu, cv; @@ -930,7 +930,7 @@ for (i=0; i<count; i++) { u32 a; - char *s_pY, *s_pU, *s_pV; + u8 *s_pY, *s_pU, *s_pV; u32 len; len = spansi.len; s_pY = pY + spansi.x; @@ -958,7 +958,7 @@ void evg_yuv444p_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { u32 a; - char *pY, *pU, *pV; + u8 *pY, *pU, *pV; s32 i; u8 cy, cu, cv; @@ -975,7 +975,7 @@ for (i=0; i<count; i++) { u32 j; for (j=0; j<spansi.len; j++) { - char *s_pY, *s_pU, *s_pV; + u8 *s_pY, *s_pU, *s_pV; u32 fin; s32 x = spansi.x + j; s_pY = pY + x; @@ -991,7 +991,7 @@ } } else { for (i=0; i<count; i++) { - char *s_pY, *s_pU, *s_pV; + u8 *s_pY, *s_pU, *s_pV; u32 fin, len; len = spansi.len; s_pY = pY + spansi.x; @@ -1009,7 +1009,7 @@ void evg_yuv444p_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { s32 i; - char *pY, *pU, *pV; + u8 *pY, *pU, *pV; pY = surf->pixels + y * surf->pitch_y; pU = pY + surf->height*surf->pitch_y; @@ -1019,7 +1019,7 @@ u8 spanalpha, col_a; u32 len; u32 *p_col; - char *s_pY, *s_pU, *s_pV; + u8 *s_pY, *s_pU, *s_pV; len = spansi.len; p_col = surf->fill_run(surf->sten, rctx, &spansi, y); spanalpha = spansi.coverage; @@ -1060,9 +1060,9 @@ s32 i; u8 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; - char *pY = surf->pixels; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV; + u8 *pY = surf->pixels; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV; pY += rc.y * surf->pitch_y; pU += rc.y * surf->pitch_y; @@ -1097,7 +1097,7 @@ YUYV part */ -static void overmask_yuvy(char *dst, u8 c, u32 alpha) +static void overmask_yuvy(u8 *dst, u8 c, u32 alpha) { s32 srca = alpha; u32 srcc = c; @@ -1106,7 +1106,7 @@ } void evg_yuyv_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { - char *pY; + u8 *pY; s32 i; u8 cy, cu, cv; @@ -1118,7 +1118,7 @@ cv = GF_COL_B(surf->fill_col); for (i=0; i<count; i++) { - char *s_pY; + u8 *s_pY; u32 len; len = spansi.len; //get start of yuyv block: devide x by 2, multiply by 4 (two Y pix packed in 4 bytes) @@ -1164,7 +1164,7 @@ void evg_yuyv_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { - char *pY; + u8 *pY; s32 i; u8 cy, cu, cv, a; @@ -1175,7 +1175,7 @@ a = GF_COL_A(surf->fill_col); for (i=0; i<count; i++) { - char *s_pY; + u8 *s_pY; u32 fin, len; len = spansi.len; s_pY = pY + (spansi.x/2) * 4; @@ -1210,14 +1210,14 @@ void evg_yuyv_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf, EVGRasterCtx *rctx) { s32 i; - char *pY; + u8 *pY; pY = surf->pixels + y * surf->pitch_y; for (i=0; i<count; i++) { u8 spanalpha, col_a; u32 *p_col; - char *s_pY; + u8 *s_pY; u32 len, x; len = spansi.len; s_pY = pY + (spansi.x/2) * 4; @@ -1297,8 +1297,8 @@ s32 j; u8 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; - char *o_pY; - char *pY = surf->pixels; + u8 *o_pY; + u8 *pY = surf->pixels; pY += rc.y * surf->pitch_y; pY += (rc.x/2) * 4; @@ -1306,7 +1306,7 @@ o_pY = pY; for (i = 0; i <(u32)rc.height; i++) { if (!i) { - char *dst = pY; + u8 *dst = pY; for (j = 0; j < rc.width/2; j++) { dstsurf->idx_y1 = cy; dstsurf->idx_u = cb; @@ -1385,8 +1385,8 @@ u32 i, a; u16 *surf_uv_alpha_even = (u16 *) rctx->uv_alpha; u16 *surf_uv_alpha_odd = (u16 *) _surf_uv_alpha; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV; pU += y/2 * surf->pitch_y/2; pV = pU + surf->height/2 * surf->pitch_y/2; @@ -1853,9 +1853,9 @@ u16 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; u16 *s_pY, *s_pU; - char *pY = surf->pixels; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pU_first, *pY_first; + u8 *pY = surf->pixels; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pU_first, *pY_first; pY += rc.y * surf->pitch_y; pU += rc.y/2 * surf->pitch_y; @@ -1918,8 +1918,8 @@ void evg_yuv422p_10_flush_uv_const(GF_EVGSurface *surf, EVGRasterCtx *rctx, u8 *_surf_uv_alpha, s32 cu, s32 cv, s32 y) { u32 i, a; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV; u16 *surf_uv_alpha = (u16 *) rctx->uv_alpha; pU += y * surf->pitch_y/2; pV = pU + surf->height * surf->pitch_y/2; @@ -2012,10 +2012,10 @@ u8 _cy, _cb, _cr; u16 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; - char *pY = surf->pixels; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV; - char *o_pY, *o_pU, *o_pV; + u8 *pY = surf->pixels; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV; + u8 *o_pY, *o_pU, *o_pV; pY += rc.y * surf->pitch_y; pU += rc.y/2 * surf->pitch_y/2; @@ -2220,9 +2220,9 @@ u8 _cy, _cb, _cr; u16 cy, cb, cr; GF_EVGSurface *surf = (GF_EVGSurface *)_surf; - char *pY = surf->pixels; - char *pU = surf->pixels + surf->height *surf->pitch_y; - char *pV, *o_pY, *o_pU, *o_pV; + u8 *pY = surf->pixels; + u8 *pU = surf->pixels + surf->height *surf->pitch_y; + u8 *pV, *o_pY, *o_pU, *o_pV; pY += rc.y * surf->pitch_y; pU += rc.y * surf->pitch_y;
View file
gpac-2.4.0.tar.gz/src/evg/stencil.c -> gpac-26.02.0.tar.gz/src/evg/stencil.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / software 2D rasterizer module @@ -67,6 +67,7 @@ if (_this->pos0>=0) { if(_this->pos0>0) { end = FIX2INT(gf_mulfix(_this->pos0, maxPos)); + end = MIN(end, EVGGRADIENTMAXINTPOS); for (i=0; i<= end; i++) { _this->precomputed_argbi = _this->col0; } @@ -78,6 +79,7 @@ start = FIX2INT(gf_mulfix(pos, maxPos)); pos = MIN(_this->posc+1, FIX_ONE); end = FIX2INT(gf_mulfix(pos, maxPos)); + end = MIN(end, EVGGRADIENTMAXINTPOS); diff = end-start; if (diff) { @@ -1782,6 +1784,14 @@ u8 *pix = _this->pixels + y * _this->stride + _this->Bpp*x; return GF_COL_ARGB(0xFF, *(pix+2) & 0xFF, * (pix+1) & 0xFF, *pix & 0xFF); } +u32 get_pix_332(EVG_Texture *_this, u32 x, u32 y, EVGRasterCtx *ctx) +{ + u8 *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + u32 r = pix0>>5; + u32 g = (pix0>>2)&0x7; + u32 b = pix0&0x3; + return GF_COL_ARGB(0xFF, (r << 5), (g << 5), (b << 6)); +} u32 get_pix_444(EVG_Texture *_this, u32 x, u32 y, EVGRasterCtx *ctx) { u8 *pix = _this->pixels + y * _this->stride + _this->Bpp*x; @@ -2295,6 +2305,9 @@ case GF_PIXEL_RGB_444: _this->tx_get_pixel = get_pix_444; return; + case GF_PIXEL_RGB_332: + _this->tx_get_pixel = get_pix_332; + return; case GF_PIXEL_RGB_555: _this->tx_get_pixel = get_pix_555; return; @@ -2516,6 +2529,9 @@ case GF_PIXEL_BGR: _this->Bpp = 3; break; + case GF_PIXEL_RGB_332: + _this->Bpp = 1; + break; case GF_PIXEL_RGB_555: case GF_PIXEL_RGB_565: case GF_PIXEL_RGB_444:
View file
gpac-2.4.0.tar.gz/src/evg/surface.c -> gpac-26.02.0.tar.gz/src/evg/surface.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / software 2D rasterizer module @@ -370,6 +370,9 @@ if (!probe_only) surf->is_transparent = GF_TRUE; break; + case GF_PIXEL_RGB_332: + BPP = 1; + break; case GF_PIXEL_RGB_444: case GF_PIXEL_RGB_555: case GF_PIXEL_RGB_565: @@ -603,6 +606,8 @@ return evg_surface_clear_555(surf, clear, color); case GF_PIXEL_RGB_565: return evg_surface_clear_565(surf, clear, color); + case GF_PIXEL_RGB_332: + return evg_surface_clear_332(surf, clear, color); /*YUV formats*/ case GF_PIXEL_YUV: @@ -927,6 +932,21 @@ surf->fill_single_a = evg_555_fill_single_a; } break; + case GF_PIXEL_RGB_332: + if (use_const) { + if (a!=0xFF) { + surf->fill_spans = (EVG_SpanFunc) evg_332_fill_const_a; + } else { + surf->fill_spans = (EVG_SpanFunc) evg_332_fill_const; + } + } else { + surf->fill_spans = (EVG_SpanFunc) evg_332_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_332_fill_single; + surf->fill_single_a = evg_332_fill_single_a; + } + break; case GF_PIXEL_YVU: surf->swap_uv = GF_TRUE; case GF_PIXEL_YUV: @@ -1387,7 +1407,7 @@ surf->sten = sten1; surf->update_run = NULL; - /*setup ft raster calllbacks*/ + /*setup ft raster callbacks*/ if (!setup_grey_callback(surf, GF_FALSE, sten2 ? GF_TRUE : GF_FALSE)) return GF_OK; get_surface_world_matrix(surf, &mat); @@ -1520,7 +1540,7 @@ u32 max_gray; if (!surf || !surf->ext3d) return GF_BAD_PARAM; - /*setup ft raster calllbacks*/ + /*setup ft raster callbacks*/ if (!setup_grey_callback(surf, GF_TRUE, GF_FALSE)) return GF_OK; if (surf->useClipper) { @@ -1555,7 +1575,7 @@ u32 max_gray; if (!surf || !surf->ext3d) return GF_BAD_PARAM; - /*setup ft raster calllbacks*/ + /*setup ft raster callbacks*/ if (!setup_grey_callback(surf, GF_TRUE, GF_FALSE)) return GF_OK; if (surf->useClipper) {
View file
gpac-2.4.0.tar.gz/src/export.cpp -> gpac-26.02.0.tar.gz/src/export.cpp
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC @@ -69,14 +69,29 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sys_set_cfg_option) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_localized) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_process_id) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_check_process_id) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_create_lockfile) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_set_console_code) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_format_help) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_word_match) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_set_callback) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_log) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_send) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_enable_sampling) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_sampling_enabled) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_enable_rmtws) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_enable_userws) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_rmtws) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_userws) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_get_settings) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_set_on_new_client_cbk) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_get_on_new_client_task) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_get_peer_address) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_client_send_to_ws) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_client_set_on_data_cbk) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_client_get_on_data_task) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_client_set_on_del_cbk) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_client_get_on_del_task) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rmt_client_get_rmt) ) + + +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_solve_path) ) #ifdef GPAC_ENABLE_COVERAGE #pragma comment (linker, EXPORT_SYMBOL(gf_sys_is_cov_mode) ) @@ -126,7 +141,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_log_check_error) ) #endif -#pragma comment (linker, EXPORT_SYMBOL(gf_fsize) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fileio_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fileio_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fileio_get_udta) ) @@ -137,6 +151,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_fileio_tag_main_thread) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fileio_is_main_thread) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fileio_set_write_state) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_register_delete_proc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_unregister_delete_proc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_from_url) ) #pragma comment (linker, EXPORT_SYMBOL(gf_set_progress) ) #pragma comment (linker, EXPORT_SYMBOL(gf_set_progress_callback) ) @@ -151,6 +168,18 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_ftell) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fread) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fgets) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fsize) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fd_fsize) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fgetc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fputc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fputs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fprintf) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_vfprintf) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fflush) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_feof) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_ferror) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fd_open) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_file_exists) ) #pragma comment (linker, EXPORT_SYMBOL(gf_file_basename) ) @@ -176,6 +205,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_timestamp_greater) ) #pragma comment (linker, EXPORT_SYMBOL(gf_timestamp_greater_or_equal) ) #pragma comment (linker, EXPORT_SYMBOL(gf_timestamp_equal) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_format_duration) ) /* Memory */ #ifdef GPAC_MEMORY_TRACKING @@ -273,7 +303,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_bit_position) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_content_no_truncate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_vluimsbf5) ) - #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u16_le) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u32_le) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_truncate) ) @@ -284,7 +313,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_emulation_byte_removed ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_utf8 ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_write_utf8 ) ) - +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_reassign_buffer)) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_is_overflow)) /* Thread */ #pragma comment (linker, EXPORT_SYMBOL(gf_th_new) ) @@ -327,6 +357,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_local_ip) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_local_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_remote_address) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_remote_address_port) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_setup_multicast) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_is_multicast_address) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_receive_no_select) ) @@ -346,6 +377,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_net_set_ntp_shift) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_get_utc_ts) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_ntp_to_utc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_net_ntp_add_usec) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_net_reload_netcap) ) #pragma comment (linker, EXPORT_SYMBOL(gf_errno_str) ) #pragma comment (linker, EXPORT_SYMBOL(gf_get_next_pow2) ) @@ -460,6 +494,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_get_header) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_get_header_sizes_and_times) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_enum_headers) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_set_max_rate) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_get_max_rate) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_is_regulated) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_set_data_rate) ) @@ -696,9 +733,10 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_odf_get_text_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_get_laser_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_encode_ui_config) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sl_depacketize) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_sl_depacketize) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_size) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_write_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_new) ) @@ -724,17 +762,47 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_write_bs) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_del ) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_write_bs) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_avs3v_cfg_read) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_avs3v_cfg_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_avs3v_cfg_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_avs3v_cfg_write_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_avs3v_cfg_del ) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_read) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_read_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_write_bs) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_odf_dump_txtcfg) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_opus_cfg_parse)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_opus_cfg_parse_bs)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_opus_cfg_write)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_opus_cfg_write_bs)) + + +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac3_cfg_parse)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac3_cfg_parse_bs)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac3_cfg_write)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac3_cfg_write_bs)) + #pragma comment (linker, EXPORT_SYMBOL(gf_odf_dovi_cfg_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_dovi_cfg_read_bs)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_dovi_cfg_write_bs)) + + +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_parse)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_parse_bs)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_write)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_write_bs)) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_size) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_deep_copy) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_ac4_cfg_clean_list) ) + #ifndef GPAC_MINIMAL_ODF @@ -777,11 +845,13 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_close) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_delete) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_write_callback) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_can_access_movie)) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_moov_first) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_parse) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_find_child) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_box) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_xml_metadata_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_dims_description) ) @@ -835,6 +905,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sample_padding) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_info_ex) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_flags) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_for_media_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_for_movie_time) ) @@ -878,6 +949,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_default_sync_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_3gp_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac3_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac4_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_truehd_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_set_streaming_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_layout_info) ) @@ -931,6 +1003,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_find_od_id_for_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_apple_get_tag) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_apple_enum_tag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_apple_enum_tag_ex) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_wma_enum_tag) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_enum_udta_keys) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_media_data_size) ) @@ -979,6 +1052,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_max_sample_delta) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_mpegh_compatible_profiles) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_av1_config_get ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_avs3v_config_get ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_mvc_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_extract_meta_item_get_cenc_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_subtitle_get_mime) ) @@ -988,12 +1062,16 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_unused_box_bytes) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_clean_aperture) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_dovi_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_iamf_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_opus_config_get_desc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_pcm_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_lpcm_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_enum_track_references) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_text_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_track_referenced) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_external_track) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_switch_source) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_min_negative_cts_offset)) # ifndef GPAC_DISABLE_ISOM_DUMP #pragma comment (linker, EXPORT_SYMBOL(gf_isom_dump) ) @@ -1039,6 +1117,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_enabled) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_flags) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_id) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_force_track_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_rewrite_track_dependencies) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample_shadow) ) @@ -1107,6 +1186,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_desc_to_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_generic_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_bitrate) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_bitrate_ex) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_pl_indications) ) @@ -1155,6 +1235,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_estimate_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_meta_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_meta_item) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_meta_item2) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_meta_item_memory) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_meta_item) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_meta_primary_item) ) @@ -1192,6 +1273,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_apply_box_patch) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac3_config_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac3_config_update) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac4_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac4_config_update) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_tmcd_config_new) ) @@ -1201,6 +1284,35 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_vvc_set_inband_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_subtitle_set_mime) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_index) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_external_track) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_cenc_set_pssh)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_cenc_protection)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_piff_allocate_storage)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_cenc_allocate_storage)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_track_cenc_add_sample_info)) + +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_ctts_v1)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_forced_text)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sample_group_in_traf)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_reset_alt_brands_ex)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_mvc_config_update)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_xml_metadata_description)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_stxt_description)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_webvtt_description)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_mpha_description)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_truehd_config_new)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_oma_protection)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_adobe_protection)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_generic_protection)) + +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_cenc_senc_box)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_cenc_seig_sample_group)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_sample_description_from_template)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_y3d_info)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_movie_duration)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_track_from_template)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_stsd_templates)) + #ifndef GPAC_DISABLE_ISOM_HINTING @@ -1241,6 +1353,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_traf_base_media_decode_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_next_moof_number) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_next_moof_number) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_enable_mfra)) #endif #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -1275,6 +1388,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_av1_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_av1_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_avs3v_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_avs3v_config_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_equal) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_write_header) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_close_fragments) ) @@ -1507,6 +1622,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_avc_read_sps_bs ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_avc_read_pps_bs ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_avc_hevc_get_chroma_format_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_avcc_use_extensions)) #pragma comment (linker, EXPORT_SYMBOL(gf_hevc_read_vps) ) #pragma comment (linker, EXPORT_SYMBOL(gf_hevc_read_vps_ex) ) @@ -1553,6 +1669,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sha1_csum) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sha1_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sha256_csum) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_md5_csum) ) #ifndef GPAC_DISABLE_AV_PARSERS #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_parser_new) ) @@ -1576,6 +1693,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_m4a_write_config_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4a_parse_config) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_ac3_parser)) #pragma comment (linker, EXPORT_SYMBOL(gf_ac3_parser_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_eac3_parser_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_eac3_parser) ) @@ -1583,6 +1702,10 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_ac3_get_surround_channels) ) #pragma comment (linker, EXPORT_SYMBOL(gf_ac3_get_bitrate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_eac3_get_chan_loc_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_ac3_get_channel_layout) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_ac4_parser_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_ac4_parser) ) #ifndef GPAC_DISABLE_OGG #pragma comment (linker, EXPORT_SYMBOL(gf_vorbis_parse_header) ) @@ -2113,6 +2236,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_init_smooth_from_dom) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_complete_from_dom) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_get_segment_start_time_with_timescale) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mpd_resolve_subnumber) ) #endif /*GPAC_DISABLE_MPEG2TS*/ @@ -2217,7 +2341,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dash_switch_quality) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_time_shift_buffer_depth) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_segment_mime) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_segment_init_url) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_segment_init_keys) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_select) ) @@ -2280,7 +2403,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_set_visible_rect) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_utc_drift_estimate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_algo) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_route_ast_shift) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_mcast_ast_shift) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_suggested_presentation_delay) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_ignore_xlink) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_num_components) ) @@ -2335,6 +2458,11 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_itags_enum_tags) ) #pragma comment (linker, EXPORT_SYMBOL(gf_id3_get_genre) ) #pragma comment (linker, EXPORT_SYMBOL(gf_id3_get_genre_tag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_tag_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_tag_free) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_to_bitstream) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_list_to_bitstream) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_from_bitstream) ) #pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_bit_depth) ) #pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_name) ) @@ -2349,8 +2477,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_enum) ) #pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_parse) ) #pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_get_dolby_chanmap) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_get_dolby_chanmap_from_layout) ) #pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_to_isobmf) ) - +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_sname) ) #pragma comment (linker, EXPORT_SYMBOL(gf_stream_type_name) ) @@ -2371,6 +2500,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_pixel_fmt_to_qt_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_pixel_is_wide_depth) ) #pragma comment (linker, EXPORT_SYMBOL(gf_pixel_fmt_probe) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_pixel_get_downsampling) ) #pragma comment (linker, EXPORT_SYMBOL(gf_codecid_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_codecid_enum) ) @@ -2387,23 +2517,31 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_props_get_type_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_get_type_desc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_parse_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_dump) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_dump_val) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_dump_alloc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_get_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_get_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_4cc_get_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_4cc_get_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_sanity_check) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_type_is_enum) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_parse_enum) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_parse_value) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_enum_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_enum_all_names) ) #pragma comment (linker, EXPORT_SYMBOL(gf_props_reset_single) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_equal) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_equal_strict) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_new_defaults) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_load_filter) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_process_link_directive) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_parse_filter_graph) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_parse_filter_graph_str) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_run) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_stop) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_print_stats) ) @@ -2424,6 +2562,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_fs_load_destination) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_post_user_task ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_post_user_task_main ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_post_user_task_delay ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_abort ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_is_last_task ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_in_final_flush) ) @@ -2432,6 +2571,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_abort ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_lock ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_lock_all) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_log_tag) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_print_all_connections ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_check_filter_register_cap ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_set_max_resolution_chain_length ) ) @@ -2455,6 +2595,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_fs_is_supported_source) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_set_filter_creation_callback) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_rt_udta) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_check_filter) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_set_external_gl_provider) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fs_print_debug_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_send_update ) ) @@ -2518,6 +2659,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_netcap_id ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_name ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_set_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_status ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_bytes_done ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_reset_source ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_register_opengl_provider) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_request_opengl) ) @@ -2543,7 +2686,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_meta_set_instances ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_meta_get_instances ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_probe_link ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_probe_links ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_possible_destinations ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_class_hint ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_discard ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_ref ) ) @@ -2566,6 +2711,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_timescale ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_sap ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_sap ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_switch_frame ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_switch_frame ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_roll_info ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_roll_info ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_interlaced ) ) @@ -2629,6 +2776,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_is_eos ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_eos ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_enum_properties ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_enum_info ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_would_block ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_is_sparse ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_query_buffer_duration ) ) @@ -2672,6 +2820,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_negotiate_property ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_negotiate_property_str) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_negotiate_property_dyn) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_owner) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_source_filter) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_enum_destinations) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_discard_block) ) @@ -2688,6 +2837,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_bind_dash_algo_callbacks) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_bind_ffdmx_callbacks) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_filter_bind_httpout_callbacks) ) #pragma comment (linker, EXPORT_SYMBOL(gf_httpout_send_request) ) @@ -2701,3 +2852,16 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_cicp_color_primaries_all_names) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cicp_color_transfer_all_names) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cicp_color_matrix_all_names) ) + + +#pragma comment (linker, EXPORT_SYMBOL(gf_cenc_key_info_get_iv_size)) +#pragma comment (linker, EXPORT_SYMBOL(gf_cenc_validate_key_info)) + +/*** + Exported symbols from gpac/internal + +There is no gaurantee that these symbols won't be dropped in the future + +*/ +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_parse_ex)) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_parse_root_box))
View file
gpac-2.4.0.tar.gz/src/filter_core/filter.c -> gpac-26.02.0.tar.gz/src/filter_core/filter.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2026 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -59,6 +59,7 @@ || !strncmp(path, "udpu://", 7) || !strncmp(path, "rtp://", 6) || !strncmp(path, "route://", 8) + || !strncmp(path, "mabr://", 7) ) { char *sep2 = res ? strchr(res+1, ':') : NULL; char *sep3 = res ? strchr(res+1, '/') : NULL; @@ -295,6 +296,13 @@ u32 i; if (!fsess) return NULL; + //check if this is a sink, if so move to GF_FILTER_ARG_EXPLICIT_SINK (for force_demux setup) + if (src_args && (arg_type == GF_FILTER_ARG_EXPLICIT)) { + char *dst_arg = strstr(src_args, "dst"); + if (dst_arg && (dst_arg3==fsess->sep_name)) + arg_type = GF_FILTER_ARG_EXPLICIT_SINK; + } + GF_SAFEALLOC(filter, GF_Filter); if (!filter) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to alloc filter for %s\n", freg->name)); @@ -523,11 +531,13 @@ { //flush all pending pid init requests if (filter->has_pending_pids && !filter->deferred_link) { - filter->has_pending_pids=GF_FALSE; while (gf_fq_count(filter->pending_pids)) { GF_FilterPid *pid=gf_fq_pop(filter->pending_pids); gf_filter_pid_post_init_task(filter, pid); } + //reset has_pending_pids once init tasks are posted, otherwise we could be in a state with no pending pids + //and no init task pending which could break check_connection_pending in ultithreaded mode + filter->has_pending_pids = GF_FALSE; } } @@ -557,6 +567,8 @@ if (filter->freg->initialize) { GF_Err e; FSESS_CHECK_THREAD(filter) + + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); if (((arg_type==GF_FILTER_ARG_EXPLICIT_SOURCE) || (arg_type==GF_FILTER_ARG_EXPLICIT_SINK)) && !filter->orig_args) { filter->orig_args = (char *)args; e = filter->freg->initialize(filter); @@ -564,12 +576,21 @@ } else { e = filter->freg->initialize(filter); } + gf_logs_thread_untag(filter); + if (e) return e; } if ((filter->freg->flags & GF_FS_REG_SCRIPT) && filter->freg->update_arg) { GF_Err e; gf_filter_parse_args(filter, args, arg_type, GF_TRUE); + char *next_args = strchr(args, filter->session->sep_args); + filter->orig_args = (char*)next_args; + + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); e = filter->freg->update_arg(filter, NULL, NULL); + gf_logs_thread_untag(filter); + + filter->orig_args = NULL; if (e) return e; } @@ -639,14 +660,7 @@ #endif //may happen when a filter is removed from the chain - if (filter->postponed_packets) { - while (gf_list_count(filter->postponed_packets)) { - GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); - gf_filter_packet_destroy(pck); - } - gf_list_del(filter->postponed_packets); - filter->postponed_packets = NULL; - } + gf_filter_reset_pending_packets(filter); //delete output pids before the packet reservoir while (gf_list_count(filter->output_pids)) { @@ -654,13 +668,19 @@ } gf_list_del(filter->output_pids); + //delete input pids not yet destroyed (may happen upon setup failure) + while (gf_list_count(filter->input_pids)) { + gf_filter_pid_inst_del(gf_list_pop_back(filter->input_pids)); + } + gf_list_del(filter->input_pids); + + gf_list_del(filter->blacklisted); gf_list_del(filter->destination_filters); gf_list_del(filter->destination_links); gf_list_del(filter->source_filters); gf_list_del(filter->temp_input_pids); - gf_list_del(filter->input_pids); gf_fq_del(filter->tasks, task_del); gf_fq_del(filter->pending_pids, NULL); @@ -770,6 +790,7 @@ } gf_free( (void *) filter->forced_caps); } + gf_filter_sess_reset_graph(filter->session, filter->freg); gf_free( (char *) filter->freg->name); gf_free( (void *) filter->freg); } @@ -811,6 +832,21 @@ } GF_EXPORT +const char * gf_filter_get_status(GF_Filter *filter) +{ + gf_assert(filter); + return filter->status_str ? (const char *) filter->status_str : "" ; +} + +GF_EXPORT +u64 gf_filter_get_bytes_done(GF_Filter *filter) +{ + gf_assert(filter); + return filter->nb_bytes_processed ; +} + + +GF_EXPORT void gf_filter_reset_source(GF_Filter *filter) { if (filter && filter->source_ids) { @@ -1125,30 +1161,6 @@ } } -Bool filter_solve_gdocs(const char *url, char szPathGF_MAX_PATH) -{ - char *path; -#ifdef WIN32 - path = getenv("HOMEPATH"); -#elif defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS) - path = (char *) gf_opts_get_key("core", "docs-dir"); -#else - path = getenv("HOME"); -#endif - - if (path && path0) { - strncpy(szPath, path, GF_MAX_PATH-1); - szPathGF_MAX_PATH-1 = 0; - u32 len = (u32) strlen(szPath); - if ((szPathlen-1=='/') || (szPathlen-1=='\\')) - szPathlen-1=0; - - strncat(szPath, url+6, GF_MAX_PATH-strlen(szPath)-1); - return GF_TRUE; - } - return GF_FALSE; -} - GF_PropertyValue gf_filter_parse_prop_solve_env_var(GF_FilterSession *fs, GF_Filter *f, u32 type, const char *name, const char *value, const char *enum_values) { char szPathGF_MAX_PATH; @@ -1172,8 +1184,8 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to query GPAC shared resource directory location\n")); } } - else if (!strnicmp(value, "$GDOCS", 6)) { - if (filter_solve_gdocs(value, szPath)) { + else if (!strnicmp(value, "$GDOCS", 6) || !strnicmp(value, "$GCFG", 5)) { + if (gf_sys_solve_path(value, szPath)) { value = szPath; } else { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to query GPAC user document directory location\n")); @@ -1239,7 +1251,9 @@ } //if no update function consider the arg OK if (filter->freg->update_arg) { + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); e = filter->freg->update_arg(filter, arg_name, &argv); + gf_logs_thread_untag(filter); } if (e==GF_OK) { if (!is_meta) @@ -1318,6 +1332,7 @@ if (filter->logs) { if (filter->logs->tools) gf_free(filter->logs->tools); if (filter->logs->levels) gf_free(filter->logs->levels); + gf_log_pop_extra(filter->logs); gf_free(filter->logs); } GF_SAFEALLOC(filter->logs, GF_LogExtra); @@ -1331,6 +1346,7 @@ u32 i, level = 0; char *l_str=NULL; char *l_tool=NULL; + char *l_strict=NULL; char *sep = strchr(logs, '@'); char *next = sep ? strchr(sep, ':') : NULL; @@ -1341,6 +1357,8 @@ sep0 = 0; l_str = sep+1; l_tool = logs; + l_strict = strstr(l_str, "+strict"); + if (l_strict) l_strict0 = 0; } else { l_tool = "all"; } @@ -1351,7 +1369,10 @@ else if (!strcmp(l_str, "debug")) level = GF_LOG_DEBUG; else if (!strcmp(l_str, "quiet")) level = GF_LOG_QUIET; else if (!strcmp(l_str, "ncl") || !strcmp(l_str, "cl")) { - if (!next) break; + if (!next) { + if (l_strict) l_strict0 = '+'; + break; + } logs = next+1; } else if (!strcmp(l_str, "strict")) { lf->strict = GF_TRUE; @@ -1359,6 +1380,10 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Unsupported log level %s, ignoring\n", l_str)); l_tool=NULL; } + if (l_strict) { + lf->strict = GF_TRUE; + l_strict0 = '+'; + } while (l_tool) { char *n_tool = strchr(l_tool, ':'); @@ -1433,6 +1458,7 @@ flen++; arg += flen; } + gf_sys_mark_arg_used(i, GF_TRUE); if (sep) { len = (u32) (sep - (arg)); @@ -1521,8 +1547,10 @@ ap = gf_props_parse_value(GF_PROP_UINT, "FBD", opt, NULL, filter->session->sep_list); filter->pid_decode_buffer_max_us = ap.value.uint; } +#ifndef GPAC_DISABLE_LOG opt = gf_opts_get_key(sec_name, "LT"); if (opt) filter_parse_logs(filter, opt); +#endif } //ifce (used by socket and other filters), use core default @@ -1567,7 +1595,10 @@ memset(&argv, 0, sizeof(GF_PropertyValue)); argv.type = GF_PROP_STRING; argv.value.string = (char *) arg_val; + + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); filter->freg->update_arg(filter, arg_name, &argv); + gf_logs_thread_untag(filter); } if (!gf_sys_has_filter_global_meta_args() //allow -- syntax as well @@ -1623,7 +1654,9 @@ } #undef META_MAX_ARG + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); e = filter->freg->update_arg(filter, szArg + len, &argv); + gf_logs_thread_untag(filter); if (e) { GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Error assigning argument %s to filter %s: %s\n", szArg, filter->name, gf_errno_str(e) )); } @@ -1741,6 +1774,7 @@ || !strncmp(args+4, "atsc://", 7) || !strncmp(args+4, "gfio://", 7) || !strncmp(args+4, "route://", 8) + || !strncmp(args+4, "mabr://", 7) ) ) { internal_url = GF_TRUE; @@ -1751,6 +1785,7 @@ || !strncmp(args+4, "udpu://", 7) || !strncmp(args+4, "rtp://", 6) || !strncmp(args+4, "route://", 8) + || !strncmp(args+4, "mabr://", 7) ) { char *sep2 = sep ? strchr(sep+1, ':') : NULL; char *sep3 = sep ? strchr(sep+1, '/') : NULL; @@ -1830,6 +1865,7 @@ if ((u32) (sep - args)>=3) { prev_date = sep-3; if (prev_date0=='T') {} + else if (prev_date0=='C') { prev_date ++; } else if (prev_date1=='T') { prev_date ++; } else { prev_date = NULL; } } @@ -1889,6 +1925,7 @@ if (sep) { escaped = (sep1 == filter->session->sep_args) ? NULL : strstr(sep, szEscape); if (escaped && xml_start && (escaped>xml_start)) escaped = NULL; + if ((u32) (escaped-sep)>2) escaped = NULL; //if we have a :gfopt: or :gfloc: set without :gpac: on a source, consider this as a valid escape pattern if (check_url_esc && !escaped && !strncmp(args, szSrc, 4) && (!strncmp(sep, ":gfopt:", 7) ||!strncmp(sep, ":gfloc:", 7))) escaped = sep; @@ -1927,6 +1964,7 @@ else if (!strncmp(args+4, "tcp://", 6)) file_exists = GF_TRUE; else if (!strncmp(args+4, "udp://", 6)) file_exists = GF_TRUE; else if (!strncmp(args+4, "route://", 8)) file_exists = GF_TRUE; + else if (!strncmp(args+4, "mabr://", 7)) file_exists = GF_TRUE; else file_exists = gf_file_exists(args+4); if (!file_exists) { @@ -1979,29 +2017,37 @@ if (for_script) f_args = filter->instance_args; + Bool is_my_arg = GF_FALSE; + u32 count_enum_val = 0; + const char *restricted=NULL; + Bool reverse_bool = GF_FALSE; + const GF_FilterArgs *save_a = NULL; + while (filter->filter_udta && f_args) { - Bool is_my_arg = GF_FALSE; - Bool reverse_bool = GF_FALSE; - const char *restricted=NULL; + const GF_FilterArgs *a = &f_argsi; i++; if (!a || !a->arg_name) break; - if (!strcmp(a->arg_name, szArg)) + if (!strcmp(a->arg_name, szArg)) { is_my_arg = GF_TRUE; - else if ( (szArg0==filter->session->sep_neg) && !strcmp(a->arg_name, szArg+1)) { + save_a = a; + } else if ((szArg0==filter->session->sep_neg) && !strcmp(a->arg_name, szArg+1)) { is_my_arg = GF_TRUE; reverse_bool = GF_TRUE; + save_a = a; } - //little optim here, if no value check if argument nale is exactly one of the possible enums - else if (!value && a->min_max_enum && strchr(a->min_max_enum, '|') && strstr(a->min_max_enum, szArg)) { + //little optim here: if no value provided, check if argument name is exactly one of the possible enums + //only do this for explicit filters, not for inheritance + else if (a->min_max_enum && strchr(a->min_max_enum, '|') && strstr(a->min_max_enum, szArg)) { const char *enums = a->min_max_enum; while (enums) { if (!strncmp(enums, szArg, len)) { char c = enumslen; if (!c || (c=='|')) { - is_my_arg = GF_TRUE; + count_enum_val++; value = szArg; + save_a = a; break; } } @@ -2011,36 +2057,44 @@ } } - if (is_my_arg) restricted = gf_opts_get_key_restricted(szSecName, a->arg_name); + if (is_my_arg || count_enum_val>1) break; + } + + if(is_my_arg && count_enum_val>0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Ambiguous argument %s in filter %s: both an argument and an enum value share the name \"%s\", ignoring\n", szArg, filter->freg->name, szArg)); + } else if(count_enum_val>1) { + //only warn for explicit filters + if (!filter->dynamic_filter) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Argument %s of filter %s is ambiguous (multiple enum arguments have \"%s\" as possible value), ignoring\n", szArg, filter->freg->name, szArg)); + } + } else if (is_my_arg || count_enum_val==1) { + restricted = gf_opts_get_key_restricted(szSecName, save_a->arg_name); + found=GF_TRUE; if (restricted) { GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Argument %s of filter %s is restricted to %s by system-wide configuration, ignoring\n", szArg, filter->freg->name, restricted)); - found=GF_TRUE; - break; - } - - if (is_my_arg) { + } else { GF_PropertyValue argv; - found=GF_TRUE; - - argv = gf_filter_parse_prop_solve_env_var(filter->session, filter, (a->flags & GF_FS_ARG_META) ? GF_PROP_STRING : a->arg_type, a->arg_name, value, a->min_max_enum); + argv = gf_filter_parse_prop_solve_env_var(filter->session, filter, (save_a->flags & GF_FS_ARG_META) ? GF_PROP_STRING : save_a->arg_type, save_a->arg_name, value, save_a->min_max_enum); if (reverse_bool && (argv.type==GF_PROP_BOOL)) argv.value.boolean = !argv.value.boolean; if (argv.type != GF_PROP_FORBIDDEN) { - if (!for_script && (a->offset_in_private>=0)) { - gf_filter_set_arg(filter, a, &argv); + if (!for_script && (save_a->offset_in_private>=0)) { + gf_filter_set_arg(filter, save_a, &argv); } else if (filter->freg->update_arg) { FSESS_CHECK_THREAD(filter) - filter->freg->update_arg(filter, a->arg_name, &argv); + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); + filter->freg->update_arg(filter, save_a->arg_name, &argv); + gf_logs_thread_untag(filter); opaque_arg = GF_FALSE; if ((argv.type==GF_PROP_STRING) || (argv.type==GF_PROP_STRING_LIST)) gf_props_reset_single(&argv); } } - break; } } + GF_Filter *meta_filter = NULL; if (!strlen(szArg)) { found = GF_TRUE; @@ -2146,6 +2200,12 @@ GF_PropertyValue res = gf_filter_parse_prop_solve_env_var(filter->session, filter, GF_PROP_STRING_LIST, "ccp", value, NULL); filter->skip_cids = res.value.string_list; } else { + if (filter->skip_cids.vals) { + GF_PropertyValue prop; + prop.value.string_list = filter->skip_cids; + prop.type = GF_PROP_STRING_LIST; + gf_props_reset_single(&prop); + } filter->skip_cids.nb_items = 1; filter->skip_cids.vals = gf_malloc(sizeof(char*)); filter->skip_cids.vals0 = gf_strdup("AUTO"); @@ -2176,7 +2236,7 @@ } //temporary filter else if (!strcmp("_GFTMP", szArg)) { - filter->removed = GF_TRUE; + filter->removed = 1; found = GF_TRUE; internal_arg = GF_TRUE; } @@ -2258,7 +2318,9 @@ if (for_script || !(filter->freg->flags&GF_FS_REG_SCRIPT) ) { GF_PropertyValue argv = gf_props_parse_value(GF_PROP_STRING, szArg, value, NULL, filter->session->sep_list); FSESS_CHECK_THREAD(filter) + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); e = filter->freg->update_arg(filter, szArg, &argv); + gf_logs_thread_untag(filter); if (argv.value.string) gf_free(argv.value.string); //opaque arg not found for meta, report it if ((e==GF_NOT_FOUND) && opaque_arg) { @@ -2272,8 +2334,8 @@ } } } - - if (!internal_arg && (!has_meta_args || !opaque_arg) && !opts_optional) + //push non-internal args - optional args are skipped if not found + if (!internal_arg && (!has_meta_args || !opaque_arg) && (found || !opts_optional)) gf_fs_push_arg(filter->session, szArg, found, GF_ARGTYPE_LOCAL, meta_filter, NULL); skip_arg: @@ -2361,7 +2423,9 @@ gf_filter_set_arg(filter, a, &argv); } else if (filter->freg->update_arg) { FSESS_CHECK_THREAD(filter) + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); filter->freg->update_arg(filter, a->arg_name, &argv); + gf_logs_thread_untag(filter); gf_props_reset_single(&argv); } } else { @@ -2557,6 +2621,13 @@ } } + //PID is already a relink target for destination, silently ignore - this may happen when reconfigure tasks are triggered + //before the relinking is done + if (src_pidinst == filter_dst->swap_pidinst_dst) { + gf_fs_check_graph_load(cur_filter->session, GF_FALSE); + return; + } + if (!link_from_pid) { gf_fs_check_graph_load(cur_filter->session, GF_FALSE); @@ -2569,7 +2640,7 @@ gf_filter_pid_send_event_internal(ipid, &evt, GF_TRUE); from_pidinst->filter->session->last_connect_error = reason; } - gf_fs_post_task(cur_filter->session, gf_filter_pid_disconnect_task, from_pidinst->filter, from_pidinst->pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(cur_filter->session, from_pidinst->filter, from_pidinst->pid); return; } //detach the pidinst, and relink from the new input pid @@ -2587,7 +2658,12 @@ gf_assert(filter); if (!filter_dst) { - GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Internal error, lost destination for pid %s in filter %s while negotiating caps !!\n", pid->name, filter->name)); + //if no destinations, the filter was removed while negotiating + //this happens for example when doing 'src enc_audio enc_video VIDEOONLY_DST' + //the removal of enc_audio is triggered after potential capacity negotiation with an adaptation filter + if (pid->num_destinations) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Internal error, lost destination for pid %s in filter %s while negotiating caps !!\n", pid->name, filter->name)); + } return; } @@ -2670,7 +2746,7 @@ gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); } if (dst_pidi) { - gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, dst_pidi->filter, dst_pidi->pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(filter->session, dst_pidi->filter, dst_pidi->pid); } return; } @@ -2689,6 +2765,9 @@ new_f->swap_pidinst_dst = dst_pidi; //keep track of the pidinst being detached from the source filter new_f->swap_pidinst_src = src_pidi; + //remember the new filter for the swap - cf gf_filter_pid_connect_task + if (src_pidi) + src_pidi->swap_source = new_f; new_f->swap_needs_init = GF_TRUE; new_f->swap_pending = GF_TRUE; } @@ -2708,7 +2787,9 @@ if (is_new_chain) { //mark this filter has having pid connection pending to prevent packet dispatch until the connection is done + gf_mx_p(pid->filter->tasks_mx); safe_int_inc(&pid->filter->out_pid_connection_pending); + gf_mx_v(pid->filter->tasks_mx); gf_filter_pid_post_connect_task(new_f, pid); } else { gf_fs_post_task(filter->session, gf_filter_pid_reconfigure_task, filter_dst, pid, "pidinst_reconfigure", NULL); @@ -2727,6 +2808,8 @@ if (filter->is_pid_adaptation_filter) { //do not remove from destination_filters, needed for end of pid_init task if (!filter->dst_filter) filter->dst_filter = gf_list_get(filter->destination_filters, 0); + //in case the adaptation filter is not defining an explicit stream type or codec type + if (!filter->dst_filter && filter->cap_dst_filter) filter->dst_filter = filter->cap_dst_filter; gf_assert(filter->dst_filter); gf_assert(filter->num_input_pids==1); } @@ -2734,7 +2817,9 @@ pid->caps_negotiate = filter->caps_negotiate; filter->caps_negotiate = NULL; if (filter->freg->reconfigure_output) { + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); e = filter->freg->reconfigure_output(filter, pid); + gf_logs_thread_untag(filter); } else { //happens for decoders e = GF_OK; @@ -2752,7 +2837,7 @@ //success ! if (src_pid->adapters_blacklist) { - gf_list_del(pid->adapters_blacklist); + gf_list_del(src_pid->adapters_blacklist); src_pid->adapters_blacklist = NULL; } gf_assert(pid->caps_negotiate->reference_count); @@ -2790,9 +2875,16 @@ else if (pid->num_destinations==gf_list_count(pid->caps_negotiate_pidi_list) && pid->caps_negotiate_direct) reconfig_direct = GF_TRUE; + if (!force_afchain_insert && reconfig_direct && !gf_filter_pid_caps_negociate_match(pid, filter->freg)) { + reconfig_direct = GF_FALSE; + } + //we cannot reconfigure output if more than one destination if (reconfig_direct && filter->freg->reconfigure_output && !force_afchain_insert) { - GF_Err e = filter->freg->reconfigure_output(filter, pid); + GF_Err e; + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); + e = filter->freg->reconfigure_output(filter, pid); + gf_logs_thread_untag(filter); if (e) { if (filter->is_pid_adaptation_filter) { GF_FilterPidInst *src_pidi = gf_list_get(filter->input_pids, 0); @@ -2881,7 +2973,11 @@ gf_assert(filter->process_task_queued); if (safe_int_dec(&filter->process_task_queued) == 0) { //we have pending packets, auto-post and requeue - if (filter->pending_packets && filter->num_input_pids) { + if (filter->pending_packets && filter->num_input_pids + && (!filter->num_output_pids || filter->nb_pids_playing) + //do NOT do this if running in prevent play mode and blocking mode, as user is likely expecting fs_run() to return until the play event is sent + && !filter->session->non_blocking && !(filter->session->flags & GF_FS_FLAG_PREVENT_PLAY) + ) { safe_int_inc(&filter->process_task_queued); task->requeue_request = GF_TRUE; } @@ -2968,6 +3064,7 @@ filter->session->last_process_error = e; filter->nb_errors ++; + filter->nb_current_errors++; if (!filter->nb_consecutive_errors) filter->time_at_first_error = gf_sys_clock_high_res(); filter->nb_consecutive_errors ++; @@ -3000,6 +3097,7 @@ filter->nb_consecutive_errors = 0; filter->nb_pck_io = 0; } + filter->nb_current_errors = 0; } if (kill_filter) { @@ -3073,7 +3171,7 @@ if (!skip_block_mode && filter->would_block && (filter->would_block + filter->num_out_pids_not_connected == filter->num_output_pids ) ) { gf_mx_p(task->filter->tasks_mx); //it may happen that by the time we get the lock, the filter has been unblocked by another thread. If so, don't skip task - if (filter->would_block) { + if (filter->would_block && (filter->would_block + filter->num_out_pids_not_connected == filter->num_output_pids ) ) { filter->nb_tasks_done--; task->filter->process_task_queued = 0; GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s blocked, skipping process\n", filter->name)); @@ -3131,10 +3229,11 @@ } GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s process\n", filter->name)); - gf_rmt_begin_hash(filter->name, GF_RMT_AGGREGATE, &filter->rmt_hash); filter->in_process_callback = GF_TRUE; + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); + #ifdef GPAC_MEMORY_TRACKING if (filter->session->check_allocs) e = gf_filter_process_check_alloc(filter); @@ -3142,8 +3241,8 @@ #endif e = filter->freg->process(filter); + gf_logs_thread_untag(filter); filter->in_process_callback = GF_FALSE; - gf_rmt_end(); GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s process done\n", filter->name)); //flush all pending pid init requests following the call to init @@ -3157,11 +3256,15 @@ //if eos but we still have pending packets or process tasks queued, move to GF_OK so that //we evaluate the blocking state if (e==GF_EOS) { - if (filter->postponed_packets) { + if (filter->postponed_packets && filter->num_input_pids) { e = GF_OK; - } else if (filter->process_task_queued) { - e = GF_OK; - force_block_state_check = GF_TRUE; + } else { + gf_mx_p(filter->tasks_mx); + if (filter->process_task_queued) { + e = GF_OK; + force_block_state_check = GF_TRUE; + } + gf_mx_v(filter->tasks_mx); } } @@ -3213,6 +3316,7 @@ //last task for filter but pending packets and not blocking, requeue in main scheduler else if ((filter->would_block < filter->num_output_pids) && filter->pending_packets + && (filter->nb_pids_playing>0) && (gf_fq_count(filter->tasks)<=1) ) { //prune eos packets that could still be present @@ -3269,6 +3373,7 @@ filter->in_process = GF_TRUE; filter->in_process_callback = GF_TRUE; + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); #ifdef GPAC_MEMORY_TRACKING if (filter->session->check_allocs) @@ -3277,6 +3382,7 @@ #endif e = filter->freg->process(filter); + gf_logs_thread_untag(filter); filter->in_process_callback = GF_FALSE; filter->in_process = GF_FALSE; @@ -3393,16 +3499,17 @@ if (use_direct_dispatch) { safe_int_inc(&filter->process_task_queued); - gf_fs_post_task_ex(filter->session, gf_filter_process_task, filter, NULL, "process", NULL, GF_FALSE, GF_FALSE, GF_TRUE, TASK_TYPE_NONE); + gf_fs_post_task_ex(filter->session, gf_filter_process_task, filter, NULL, "process", NULL, GF_FALSE, GF_FALSE, GF_TRUE, TASK_TYPE_NONE, 0); } else if (safe_int_inc(&filter->process_task_queued) <= 1) { GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s added to scheduler\n", filter->name)); - gf_fs_post_task(filter->session, gf_filter_process_task, filter, NULL, "process", NULL); +// gf_fs_post_task(filter->session, gf_filter_process_task, filter, NULL, "process", NULL); + gf_fs_post_task_ex(filter->session, gf_filter_process_task, filter, NULL, "process", NULL, GF_FALSE, GF_FALSE, GF_FALSE, TASK_TYPE_NONE, 0); } else { GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s skip post process task\n", filter->name)); gf_assert(filter->session->run_status || filter->session->in_final_flush || filter->disabled - || filter->scheduled_for_next_task + || (filter->scheduled_for_next_task==GF_FILTER_SCHEDULED) || filter->session->direct_mode || gf_fq_count(filter->tasks) ); @@ -3463,9 +3570,14 @@ { if (!filter) return; if (!source_filter) return; + Bool detach = (filter->disabled && !on_setup_error && source_filter->on_setup_error) ? GF_TRUE : GF_FALSE; source_filter->on_setup_error = on_setup_error; source_filter->on_setup_error_filter = filter; source_filter->on_setup_error_udta = udta; + + if (detach) { + gf_filter_post_remove(filter); + } } struct _gf_filter_setup_failure @@ -3490,7 +3602,10 @@ if (!f->finalized && f->freg->finalize) { FSESS_CHECK_THREAD(f) + + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); f->freg->finalize(f); + gf_logs_thread_tag_del(f); } gf_mx_p(f->session->filters_mx); @@ -3499,6 +3614,9 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s task failure callback on already removed filter!\n", f->name)); } + //we will detach output pids, so drop any pending packets before + gf_filter_reset_pending_packets(f); + gf_mx_v(f->session->filters_mx); gf_mx_p(f->tasks_mx); @@ -3512,9 +3630,21 @@ u32 j; GF_FilterPid *pid = gf_list_pop_back(f->output_pids); for (j=0; j<pid->num_destinations; j++) { - GF_FilterPid *pidinst = gf_list_get(pid->destinations, j); - pidinst->pid = NULL; + GF_FilterPidInst *pidinst = gf_list_get(pid->destinations, j); + //pid instance already detached, remove it + if (!pidinst->filter) { + gf_list_rem(pid->destinations, j); + pid->num_destinations--; + j--; + gf_filter_pid_inst_check_delete(pidinst); + } + //marked as detached + else { + pidinst->pid = NULL; + } } + gf_list_reset(pid->destinations); + gf_filter_pid_del(pid); } gf_mx_v(f->tasks_mx); //avoid destruction of the current task (ourselves) @@ -3601,7 +3731,7 @@ filter->single_source = NULL; //post a pid_delete task to also trigger removal of the filter if needed - gf_fs_post_task(filter->session, gf_filter_pid_inst_delete_task, a_filter, a_pidi->pid, "pid_inst_delete", a_pidi); + gf_fs_post_pid_instance_delete_task(filter->session, a_filter, a_pidi->pid, a_pidi); } gf_mx_v(filter->tasks_mx); if (reason) @@ -3612,7 +3742,9 @@ if (notif_filter->setup_notified) return; notif_filter->setup_notified = GF_TRUE; - GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter %s failed to setup: %s\n", notif_filter->name, gf_error_to_string(reason))); + if (reason<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter %s failed to setup: %s\n", notif_filter->name, gf_error_to_string(reason))); + } gf_filter_notification_failure(notif_filter, reason, GF_TRUE); //if we used the source, also send a notif failure on the filter (to trigger removal) @@ -3638,7 +3770,7 @@ u32 count = gf_fq_count(f->tasks); //do not destroy filters if tasks for this filter are pending or some ref packets are still present - if (f->out_pid_connection_pending || f->detach_pid_tasks_pending || f->nb_ref_packets) { + if (f->out_pid_connection_pending || f->detach_pid_tasks_pending || f->nb_ref_packets || f->nb_shared_packets_out) { task->requeue_request = GF_TRUE; return; } @@ -3656,13 +3788,17 @@ return; } GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s destruction task\n", f->name)); + safe_int_dec(&f->session->remove_tasks); //avoid destruction of the current task gf_fq_pop(f->tasks); if (f->freg->finalize) { FSESS_CHECK_THREAD(f) + + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); f->freg->finalize(f); + gf_logs_thread_tag_del(f); } gf_mx_p(f->session->filters_mx); @@ -3695,9 +3831,9 @@ gf_assert(!filter->swap_pidinst_src); gf_assert(!filter->finalized); filter->finalized = GF_TRUE; - + safe_int_inc(&filter->session->remove_tasks); //post remove task ON THE FILTER, otherwise we might end up having 2 threads on the active filter - gf_fs_post_task_ex(filter->session, gf_filter_remove_task, filter, NULL, "filter_destroy", NULL, GF_FALSE, filter->session->force_main_thread_tasks, GF_FALSE, TASK_TYPE_NONE); + gf_fs_post_task_ex(filter->session, gf_filter_remove_task, filter, NULL, "filter_destroy", NULL, GF_FALSE, filter->session->force_main_thread_tasks, GF_FALSE, TASK_TYPE_NONE, 0); } static void gf_filter_tag_remove(GF_Filter *filter, GF_Filter *source_filter, GF_Filter *until_filter, Bool keep_end_connections) @@ -3752,7 +3888,7 @@ if (!mark_only && (!keep_end_connections || (pidi->filter != until_filter)) ) { //unlock filter before posting remove task on other filter if (do_unlock) gf_mx_v(filter->tasks_mx); - gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, pidi->filter, pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(filter->session, pidi->filter, pid); do_unlock = gf_mx_try_lock(filter->tasks_mx); } } @@ -3772,6 +3908,9 @@ if (filter==until_filter) return; + //we no longer watch setup error + filter->on_setup_error = NULL; + if (until_filter) { //check if filter has not been removed s32 res = gf_list_find(until_filter->session->filters, filter); @@ -3781,6 +3920,12 @@ } else { GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Disconnecting filter %s from session\n", filter->name)); } + //lock before the loop for multithreaded cases: + //1- gf_fs_post_disconnect_task can be called, triggering a filter_post_remove + //2- the filter_remove could be executed before we reach the end of the loop, hence crash + //By locking, we'll force filter_post_remove to wait + gf_mx_p(filter->tasks_mx); + //get all dest pids, post disconnect and mark filters as removed gf_assert(!filter->removed); filter->removed = 1; @@ -3797,11 +3942,10 @@ if (keep_end_connections && (pidi->filter == until_filter)) { } else { - gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, pidi->filter, pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(filter->session, pidi->filter, pid); } } } - gf_mx_p(filter->tasks_mx); if (!filter->num_output_pids && !filter->num_input_pids) { gf_filter_post_remove(filter); @@ -3842,17 +3986,11 @@ gf_filter_remove_internal(src_filter, filter, GF_FALSE); } -static void gf_filter_remove_local(GF_Filter *filter, GF_FSTask *task); -static void gf_filter_remove_reschedule(GF_FSTask *task) -{ - gf_filter_remove_local(task->filter, task); -} - -static void gf_filter_remove_local(GF_Filter *filter, GF_FSTask *task) +static void gf_filter_remove_local(GF_FSTask *task) { u32 i; Bool has_pending=GF_FALSE; - if (!filter) return; + GF_Filter *filter = task->filter; gf_mx_p(filter->tasks_mx); //check the sources for filter does not have any pending PID init task or PID configure task @@ -3868,24 +4006,24 @@ } if (has_pending) { - if (task) { - task->can_swap = GF_TRUE; - task->requeue_request = GF_TRUE; - } else { - gf_fs_post_task(filter->session, gf_filter_remove_reschedule, filter, NULL, "filter_remove", NULL); - } + task->can_swap = GF_TRUE; + task->requeue_request = GF_TRUE; gf_mx_v(filter->tasks_mx); return; } + safe_int_dec(&filter->session->remove_tasks); + Bool can_unload = GF_TRUE; //disconnect all output pids, this will remove all filters up the chain if no more inputs and outputs for (i=0; i<filter->num_output_pids; i++) { GF_FilterPid *pid = gf_list_get(filter->output_pids, i); gf_filter_pid_remove(pid); + can_unload = GF_FALSE; } //locate source filter(s) for (i=0; i<filter->num_input_pids; i++) { GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, i); + can_unload = GF_FALSE; //fanout, only disconnect this pid instance if (pidi->pid->num_destinations>1) { //post STOP and disconnect @@ -3893,7 +4031,7 @@ GF_FEVT_INIT(fevt, GF_FEVT_STOP, (GF_FilterPid *) pidi); gf_filter_pid_send_event((GF_FilterPid *) pidi, &fevt); - gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, filter, pidi->pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(filter->session, filter, pidi->pid); } //this is a source for the chain else if (!pidi->pid->filter->num_input_pids) { @@ -3901,28 +4039,39 @@ } //otherwise walk down the chain if we have one-to-one else if (pidi->pid->filter->num_output_pids==1) { + //PID will be removed, set discard right away to: + //- release any shared packet on this PID + //- trash any GF_PCK_CMD_PID_REM packet to decrement session->remove_tasks + gf_filter_pid_set_discard((GF_FilterPid*)pidi, GF_TRUE); + //set marked_for_removal to force filter_pid_remove() to post task and not use packet queue + pidi->pid->filter->marked_for_removal = GF_TRUE; gf_filter_remove(pidi->pid->filter); } else { GF_FilterEvent fevt; //source filter still active, mark output pid as not connected, send a stop and post disconnect gf_assert(pidi->pid->num_destinations==1); - pidi->pid->not_connected=1; - pidi->pid->filter->num_out_pids_not_connected++; + pidi->pid->not_connected = 1; GF_FEVT_INIT(fevt, GF_FEVT_STOP, (GF_FilterPid *) pidi); fevt.play.initial_broadcast_play = 2; gf_filter_pid_send_event((GF_FilterPid *) pidi, &fevt); - gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, filter, pidi->pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(filter->session, filter, pidi->pid); } } filter->sticky = 0; + if (can_unload && !filter->removed && !filter->finalized) { + gf_filter_post_remove(filter); + } + filter->removed = 1; gf_mx_v(filter->tasks_mx); } GF_EXPORT void gf_filter_remove(GF_Filter *filter) { + if (!filter) return; + safe_int_inc(&filter->session->remove_tasks); //always post a task for remove, this allows users to do remove() followed by add filter() without triggering stops - gf_fs_post_task(filter->session, gf_filter_remove_reschedule, filter, NULL, "filter_remove", NULL); + gf_fs_post_task(filter->session, gf_filter_remove_local, filter, NULL, "filter_remove", NULL); } #if 0 @@ -3942,7 +4091,7 @@ for (j=0; j<pid->num_destinations; j++) { GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); if (pidi->filter->removed) { - gf_fs_post_task(pidi->filter->session, gf_filter_pid_disconnect_task, pidi->filter, pidi->pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(pidi->filter->session, pidi->filter, pidi->pid); } } } @@ -3958,12 +4107,7 @@ GF_Err e; const GF_FilterArgs *src_arg=NULL; - while (gf_list_count(filter->postponed_packets)) { - GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); - gf_filter_packet_destroy(pck); - } - gf_list_del(filter->postponed_packets); - filter->postponed_packets = NULL; + gf_filter_reset_pending_packets(filter); while (gf_list_count(filter->output_pids)) { GF_FilterPid *pid = gf_list_pop_back(filter->output_pids); @@ -3976,7 +4120,11 @@ if (filter->freg->finalize) { FSESS_CHECK_THREAD(filter) + + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); filter->freg->finalize(filter); + gf_logs_thread_tag_del(filter); + filter->finalized = GF_TRUE; } gf_list_add(filter->blacklisted, (void *)filter->freg); @@ -4002,7 +4150,7 @@ #endif break; } - + reset_filter_args(filter); gf_free(filter->filter_udta); filter->filter_udta = NULL; if (!src_url) return GF_FALSE; @@ -4150,17 +4298,32 @@ } GF_EXPORT -GF_Filter *gf_filter_connect_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err) +GF_Filter *gf_filter_connect_source_internal(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, Bool is_src_add, GF_Err *err) { GF_Filter *filter_src; - const char *args; + GF_Filter *src_orig=NULL; + const char *args = NULL; char *full_args = NULL; if (!filter) { if (err) *err = GF_BAD_PARAM; return NULL; } + if (is_src_add) { + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, 0); + src_orig = (pidi && pidi->pid) ? pidi->pid->filter : NULL; + while (src_orig && src_orig->num_input_pids) { + src_orig = pidi->pid->filter; + pidi = gf_list_get(src_orig->input_pids, 0); + } + if (!src_orig) { + if (err) *err = GF_BAD_PARAM; + return NULL; + } + args = inherit_args ? gf_filter_get_args_stripped(filter->session, src_orig->orig_args, GF_FALSE) : NULL; + } else { + args = inherit_args ? gf_filter_get_dst_args(filter) : NULL; + } - args = inherit_args ? gf_filter_get_dst_args(filter) : NULL; if (args) { char *rem_opts = {"FID", "SID", "N", "RSID", "clone", "DL", NULL}; char szSep10; @@ -4209,12 +4372,21 @@ if (gf_filter_url_is_filter(filter, url, NULL)) { filter_src = gf_fs_load_filter(filter->session, url, err); } else { - filter_src = gf_fs_load_source_dest_internal(filter->session, url, NULL, parent_url, err, NULL, filter, GF_TRUE, GF_TRUE, NULL, NULL); + filter_src = gf_fs_load_source_dest_internal(filter->session, url, NULL, parent_url, err, NULL, is_src_add ? NULL : filter, GF_TRUE, is_src_add ? GF_FALSE : GF_TRUE, NULL, NULL); } if (full_args) gf_free(full_args); if (!filter_src) return NULL; + if (src_orig) { + gf_filter_set_id(filter_src, src_orig->id); + gf_filter_set_name(filter_src, src_orig->name); + filter_src->require_source_id = src_orig->require_source_id; + filter_src->subsource_id = src_orig->subsource_id; + filter_src->subsession_id = src_orig->subsession_id; + return filter_src; + } + gf_mx_p(filter->tasks_mx); if (!filter->source_filters) filter->source_filters = gf_list_new(); @@ -4224,6 +4396,17 @@ } GF_EXPORT +GF_Filter *gf_filter_connect_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err) +{ + return gf_filter_connect_source_internal(filter, url, parent_url, inherit_args, GF_FALSE, err); +} +GF_EXPORT +GF_Filter *gf_filter_add_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err) +{ + return gf_filter_connect_source_internal(filter, url, parent_url, inherit_args, GF_TRUE, err); +} + +GF_EXPORT GF_Filter *gf_filter_connect_destination(GF_Filter *filter, const char *url, GF_Err *err) { if (!filter) return NULL; @@ -4474,6 +4657,41 @@ return (filter->session->blocking_mode==GF_FS_NOBLOCK) ? GF_FALSE : GF_TRUE; } +static void filter_guess_file_ext(GF_FilterSession *sess, GF_FilterPid *pid, const char *for_mime) +{ + u32 i, j, count = gf_list_count(sess->registry); + for (i=0; i<count; i++) { + const GF_FilterRegister *reg = gf_list_get(sess->registry, i); + const char *mime=NULL; + const char *ext=NULL; + + for (j=0; j<reg->nb_caps; j++) { + char szExt20; + const GF_FilterCapability *cap = ®->capsj; + if ((cap->val.type != GF_PROP_NAME) + && (cap->val.type != GF_PROP_STRING) + && (cap->val.type != GF_PROP_STRING_NO_COPY) + ) { + continue; + } + if (reg->capsj.code == GF_PROP_PID_FILE_EXT) ext = cap->val.value.string; + else if (reg->capsj.code == GF_PROP_PID_MIME) mime = cap->val.value.string; + else continue; + + if (!mime || !ext) continue; + if (!strstr(mime, for_mime)) continue; + char *sep = strchr(ext, '|'); + u32 len = (u32) strlen(ext); + if (sep) len = (u32) (sep - ext); + if (len>19) len=19; + strncpy(szExt, ext, len); + szExtlen = 0; + gf_filter_pid_set_property(pid, GF_PROP_PID_FILE_EXT, &PROP_STRING(szExt)); + return; + } + } +} + GF_EXPORT GF_Err gf_filter_pid_raw_new(GF_Filter *filter, const char *url, const char *local_file, const char *mime_type, const char *fext, const u8 *probe_data, u32 probe_size, Bool trust_mime, GF_FilterPid **out_pid) { @@ -4493,8 +4711,7 @@ gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); - if (local_file) - gf_filter_pid_set_property(pid, GF_PROP_PID_FILEPATH, &PROP_STRING(local_file)); + gf_filter_pid_set_property(pid, GF_PROP_PID_FILEPATH, local_file ? &PROP_STRING(local_file) : NULL); if (url) { @@ -4539,15 +4756,18 @@ if (ext) ext++; if (ext) { - char *s = strchr(ext, '#'); - if (s) s0 = 0; + char *s1 = strchr(ext, '#'); + char *s2 = strchr(ext, '?'); + if (s1) s10 = 0; + if (s2) s20 = 0; strncpy(tmp_ext, ext, 20); tmp_ext20 = 0; strlwr(tmp_ext); gf_filter_pid_set_property(pid, GF_PROP_PID_FILE_EXT, &PROP_STRING(tmp_ext)); ext_len = (u32) strlen(tmp_ext); - if (s) s0 = '#'; + if (s1) s10 = '#'; + if (s2) s20 = '?'; } } } @@ -4571,6 +4791,7 @@ for (k=0;k<freg->nb_caps && !ext_not_trusted && ext_len; k++) { const char *value; const GF_FilterCapability *cap = &freg->capsk; + if (cap->flags & GF_CAPFLAG_RECONFIG) break; if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) continue; if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; if (cap->code != GF_PROP_PID_FILE_EXT) continue; @@ -4625,12 +4846,40 @@ gf_filter_pid_set_property(pid, GF_PROP_PID_MIME, &PROP_STRING( tmp_ext )); //we have a mime, disable extension checking pid->ext_not_trusted = GF_TRUE; + if (!ext_len && !fext) { + filter_guess_file_ext(filter->session, pid, mime_type); + } } return GF_OK; } GF_EXPORT +GF_Err gf_filter_pid_raw_gmem(GF_Filter *filter, const char *url, GF_FilterPid **out_pid) +{ + u8 *mem_address; + u32 mem_size; + if (!filter || !url || !out_pid) return GF_BAD_PARAM; + + GF_Err e = gf_blob_get(url, &mem_address, &mem_size, NULL); + if (e) return e; + + GF_FilterPacket *opck; + e = gf_filter_pid_raw_new(filter, NULL, NULL, NULL, NULL, mem_address, mem_size, GF_FALSE, out_pid); + if (e) { + gf_blob_release(url); + return e; + } + gf_filter_pid_set_property(*out_pid, GF_PROP_PID_URL, &PROP_STRING("NULL")); + opck = gf_filter_pck_new_shared(*out_pid, mem_address, mem_size, NULL); + gf_filter_pck_set_sap(opck, GF_FILTER_SAP_1); + gf_filter_pck_send(opck); + gf_filter_pid_set_eos(*out_pid); + gf_blob_release(url); + return GF_OK; +} + +GF_EXPORT const char *gf_filter_probe_data(GF_Filter *filter, u8 *data, u32 size, GF_FilterProbeScore *pscore) { u32 i, count; @@ -4940,35 +5189,54 @@ static Bool gf_filter_has_pid_connection_pending_internal(GF_Filter *filter, GF_Filter *stop_at_filter) { u32 i, j; + Bool has_c_pending = GF_FALSE; if (filter == stop_at_filter) return GF_FALSE; - if (filter->has_pending_pids) return GF_TRUE; - if (filter->in_pid_connection_pending) return GF_TRUE; - if (filter->out_pid_connection_pending) return GF_TRUE; + gf_mx_p(filter->tasks_mx); - if (!filter->num_output_pids) { + if (filter->has_pending_pids) { + has_c_pending = GF_TRUE; + } else if (filter->in_pid_connection_pending) { + has_c_pending = GF_TRUE; + } else if (filter->out_pid_connection_pending) { + has_c_pending = GF_TRUE; + } else if (!filter->num_output_pids) { if (!filter->act_as_sink && !filter->multi_sink_target) { if (filter->forced_caps) { - if (gf_filter_has_out_caps(filter->forced_caps, filter->nb_forced_caps)) - return GF_TRUE; + if (gf_filter_has_out_caps(filter->forced_caps, filter->nb_forced_caps)) { + has_c_pending = GF_TRUE; + } } else { - if (gf_filter_has_out_caps(filter->freg->caps, filter->freg->nb_caps)) - return GF_TRUE; + if (gf_filter_has_out_caps(filter->freg->caps, filter->freg->nb_caps)) { + has_c_pending = GF_TRUE; + } } } - return GF_FALSE; } - - for (i=0; i<filter->num_output_pids; i++) { - GF_FilterPid *pid = gf_list_get(filter->output_pids, i); - if (pid->init_task_pending) return GF_TRUE; - for (j=0; j<pid->num_destinations; j++) { - GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); - Bool res = gf_filter_has_pid_connection_pending_internal(pidi->filter, stop_at_filter); - if (res) return GF_TRUE; + else { + for (i=0; i<filter->num_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + if (pid->init_task_pending) { + has_c_pending = GF_TRUE; + break; + } + //unlock in case another thread is handling a pid init task for this filter + gf_mx_v(filter->tasks_mx); + for (j=0; j<pid->num_destinations; j++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + Bool res = gf_filter_has_pid_connection_pending_internal(pidi->filter, stop_at_filter); + if (res) { + has_c_pending = GF_TRUE; + break; + } + } + gf_mx_p(filter->tasks_mx); + if (has_c_pending) + break; } } - return GF_FALSE; + gf_mx_v(filter->tasks_mx); + return has_c_pending; } GF_EXPORT @@ -5020,6 +5288,9 @@ filter->status_percent = percent; filter->report_updated = GF_TRUE; + if ((s32)percent<0) + return GF_OK; + memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_PROGRESS; evt.progress.progress_type = 3; @@ -5058,11 +5329,22 @@ filter->instance_description = new_desc ? gf_strdup(new_desc) : NULL; return GF_OK; } +GF_Err gf_filter_set_class_hint(GF_Filter *filter, GF_ClassTypeHint class_hint) +{ + if (!filter) return GF_BAD_PARAM; + filter->instance_class_hint = class_hint; + return GF_OK; +} GF_EXPORT const char *gf_filter_get_description(GF_Filter *filter) { return filter ? filter->instance_description : NULL; } +GF_EXPORT +GF_ClassTypeHint gf_filter_get_class_hint(GF_Filter *filter) +{ + return filter ? filter->instance_class_hint : 0; +} GF_Err gf_filter_set_version(GF_Filter *filter, const char *new_desc) { @@ -5133,9 +5415,15 @@ GF_Filter *gf_filter_load_filter(GF_Filter *filter, const char *name, GF_Err *err_code) { if (!filter) return NULL; - return gf_fs_load_filter(filter->session, name, err_code); + GF_Filter *f = gf_fs_load_filter(filter->session, name, err_code); + if (!f) return NULL; + //do not allow implicit cloning when loading a filter from another filter + if (!strstr(name, "clone")) + f->clonable = GF_FILTER_NO_CLONE; + return f; } + GF_EXPORT Bool gf_filter_end_of_session(GF_Filter *filter) { @@ -5150,10 +5438,6 @@ return GF_FALSE; } -/*! checks if the some PID connection tasks are still pending at the session level -\param filter target filter -\return GF_TRUE if some connection tasks are pending, GF_FALSE otherwise -*/ GF_EXPORT Bool gf_filter_connections_pending(GF_Filter *filter) { @@ -5168,6 +5452,7 @@ u32 j; GF_Filter *f = gf_list_get(filter->session->filters, i); if (!f || f->removed || f->finalized) continue; + if (f->subsession_id != filter->subsession_id) continue; gf_mx_v(filter->session->filters_mx); gf_mx_p(f->tasks_mx); @@ -5243,6 +5528,8 @@ if (for_pid) { if (PID_IS_INPUT(for_pid)) return GF_BAD_PARAM; } + if (!filter->num_output_pids) + return GF_EOS; //in case we had pending output pids if (filter->deferred_link) { filter->deferred_link = GF_FALSE; @@ -5279,15 +5566,26 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to push cap on non custom filter %s\n", filter->freg->name)); return GF_BAD_PARAM; } + if (flags & GF_CAPFLAG_IN_BUNDLE) { + if (!value) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set cap without value on custom filter %s\n", filter->name)); + return GF_BAD_PARAM; + } + } else { + flags = 0; + } caps = (GF_FilterCapability *)filter->forced_caps; nb_caps = filter->nb_forced_caps; caps = gf_realloc(caps, sizeof(GF_FilterCapability)*(nb_caps+1) ); if (!caps) return GF_OUT_OF_MEM; + memset(&capsnb_caps, 0, sizeof(GF_FilterCapability)); capsnb_caps.code = code; - capsnb_caps.val = *value; + if (value) { + capsnb_caps.val = *value; + capsnb_caps.flags = flags; + } capsnb_caps.name = name ? gf_strdup(name) : NULL; capsnb_caps.priority = priority; - capsnb_caps.flags = flags; filter->nb_forced_caps++; filter->forced_caps = caps; filter->nb_forced_bundles = filter->nb_forced_caps ? gf_filter_caps_bundle_count(filter->forced_caps, filter->nb_forced_caps) : 0; @@ -5586,8 +5884,7 @@ } #endif -GF_EXPORT -GF_Err gf_filter_probe_link(GF_Filter *filter, u32 opid_idx, const char *fname, char **res_chain) +static GF_Err gf_filter_probe_link_internal(GF_Filter *filter, u32 opid_idx, const char *fname, Bool all_links, char **res_chain) { char *fdesc=NULL; char szFmt20; @@ -5595,6 +5892,7 @@ GF_Err e; GF_FilterPid *opid=NULL; GF_FilterSession *fs; + GF_List *tmp_blacklist; if (!filter || !fname || !res_chain) return GF_BAD_PARAM; *res_chain = NULL; fs = filter->session; @@ -5625,27 +5923,58 @@ gf_fs_lock_filters(fs, GF_FALSE); return e; } + tmp_blacklist = gf_list_new(); + while (1) { + GF_LinkInfo link_info; + const GF_FilterRegister *last_freg = NULL; + GF_List *fchain = gf_filter_pid_compute_link(opid, new_f, tmp_blacklist, &link_info); + if (!fchain) break; + if (*res_chain && (*res_chain)0) { + gf_dynstrcat(res_chain, "|", NULL); + } + if (all_links) { + char szTmp20; + sprintf(szTmp, "%u;%u,", link_info.distance, link_info.priority); + gf_dynstrcat(res_chain, szTmp, NULL); + } - GF_List *fchain = gf_filter_pid_compute_link(opid, new_f); - if (fchain) { u32 i, count = gf_list_count(fchain); for (i=0; i<count; i+=2) { const GF_FilterRegister *freg = gf_list_get(fchain, i); - gf_dynstrcat(res_chain, freg->name, ","); + if ((i+2==count) && (freg == new_f->freg)) + break; + gf_dynstrcat(res_chain, freg->name, i ? "," : NULL); + last_freg = freg; } gf_list_del(fchain); - } else { - e = GF_FILTER_NOT_FOUND; + if (! *res_chain) *res_chain = gf_strdup(""); + if (!last_freg) break; + gf_list_add(tmp_blacklist, (void*)last_freg); + if (!all_links) break; } + gf_list_del(tmp_blacklist); + gf_list_del_item(fs->filters, new_f); if (!new_f->finalized && new_f->freg->finalize) { new_f->freg->finalize(new_f); } gf_filter_del(new_f); gf_fs_lock_filters(fs, GF_FALSE); - return e; + if (*res_chain) return GF_OK; + return GF_FILTER_NOT_FOUND; +} + +GF_EXPORT +GF_Err gf_filter_probe_links(GF_Filter *filter, u32 opid_idx, const char *fname, char **res_chain) +{ + return gf_filter_probe_link_internal(filter, opid_idx, fname, GF_TRUE, res_chain); } +GF_EXPORT +GF_Err gf_filter_probe_link(GF_Filter *filter, u32 opid_idx, const char *fname, char **res_chain) +{ + return gf_filter_probe_link_internal(filter, opid_idx, fname, GF_FALSE, res_chain); +} GF_EXPORT @@ -5657,6 +5986,8 @@ if (opid_idx>=0) { opid = gf_list_get(filter->output_pids, opid_idx); if (opid==NULL) return GF_BAD_PARAM; + } else { + if (!filter->num_output_pids) return GF_FILTER_NOT_FOUND; } *res_list = NULL; count = gf_list_count(filter->session->links); @@ -5676,7 +6007,7 @@ opid = gf_list_get(filter->output_pids, k); if (!opid) break; - u8 priority=0; + s16 priority=0; u32 dst_bundle_idx; //check path weight for the given dst cap - we MUST give the target cap otherwise we might get a default match to another cap u32 path_weight = gf_filter_pid_caps_match(opid, src->freg, NULL, &priority, &dst_bundle_idx, opid->filter->dst_filter, edge->dst_cap_idx); @@ -5689,5 +6020,18 @@ gf_dynstrcat(res_list, src->freg->name, ","); } } + if (! *res_list) return GF_FILTER_NOT_FOUND; return GF_OK; } + +GF_EXPORT +void gf_filter_log_tag(GF_Filter *filter, Bool is_untag) +{ + if (!filter) return; + + if (is_untag) + gf_logs_thread_untag(filter); + else + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); +} +
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_pck.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_pck.c
Changed
@@ -56,11 +56,11 @@ //restore internal props flags of packet pck_dst->info.flags |= iflags; - if (!pck_src->props || pck_dst->is_dangling) { + if (!pck_src->props || (!pck_dst->pid && !pck_src->pid)) { return GF_OK; } if (!pck_dst->props) { - pck_dst->props = gf_props_new(pck_dst->pid->filter); + pck_dst->props = gf_props_new(pck_dst->pid ? pck_dst->pid->filter : pck_src->pid->filter); if (!pck_dst->props) return GF_OUT_OF_MEM; } @@ -129,7 +129,7 @@ closest = pck_enum_state.closest; } - //stop allocating after a while - TODO we for sur can design a better algo... + //stop allocating after a while - TODO we for sure can design a better algo... max_reservoir_size = pid->num_destinations ? 10 : 1; //if pid is file, force 1 max if (!pck && (pid->stream_type==GF_STREAM_FILE)) @@ -216,6 +216,14 @@ } } cached_pck->data_length = data_length; + if (cached_pck->props) { + GF_PropertyMap *props = cached_pck->props; + cached_pck->props=NULL; + gf_assert(props->reference_count); + if (safe_int_dec(&props->reference_count) == 0) { + gf_props_del(props); + } + } return cached_pck; } @@ -343,7 +351,7 @@ ref = ref->reference; } } - + if (max_ref>1) { u8 *data_new; if (dangling_packet) { @@ -376,7 +384,7 @@ dst->data_length = pck_source->data_length; dst->reference = pck_source; dst->pck = dst; - dst->is_dangling = 1; + dst->is_dangling = 2; //cf gf_filter_pck_new_ref for these safe_int_inc(&pck_source->reference_count); @@ -402,6 +410,13 @@ } GF_EXPORT +GF_FilterPacket *gf_filter_pck_dangling_clone(GF_FilterPacket *pck_source, GF_FilterPacket *cached_pck) +{ + return gf_filter_pck_new_clone_internal(NULL, pck_source, NULL, GF_TRUE, GF_TRUE, cached_pck); +} + + +GF_EXPORT GF_FilterPacket *gf_filter_pck_new_clone(GF_FilterPid *pid, GF_FilterPacket *pck_source, u8 **data) { return gf_filter_pck_new_clone_internal(pid, pck_source, data, GF_FALSE, GF_FALSE, NULL); @@ -430,6 +445,10 @@ { GF_FilterPacket *pck; + if (!pid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to allocate a packet on a NULL PID\n")); + return NULL; + } if (PID_IS_INPUT(pid)) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to allocate a packet on an input PID in filter %s\n", pid->filter->name)); return NULL; @@ -462,7 +481,6 @@ GF_FilterPacket *gf_filter_pck_new_shared(GF_FilterPid *pid, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct) { return gf_filter_pck_new_shared_internal(pid, data, data_size, destruct, GF_FALSE); - } GF_EXPORT @@ -850,7 +868,11 @@ #endif - if (!pck->src_filter) return GF_BAD_PARAM; + if (!pck->src_filter || (pck->src_filter->removed==1)) { + gf_filter_pck_discard(pck); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Packet sent on already removed filter, ignoring\n")); + return GF_OK; + } #ifdef GPAC_MEMORY_TRACKING if (pck->pid->filter->nb_process_since_reset) @@ -863,8 +885,13 @@ if (PCK_IS_INPUT(pck)) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to dispatch input packet on output PID in filter %s\n", pck->pid->filter->name)); + gf_filter_pck_discard(pck); return GF_BAD_PARAM; } + if (pid->not_connected) { + gf_filter_pck_discard(pck); + return GF_OK; + } is_cmd_pck = (pck->info.flags & GF_PCK_CMD_MASK); @@ -880,8 +907,6 @@ pid->filter->nb_pck_io++; - gf_rmt_begin(pck_send, GF_RMT_AGGREGATE); - cktype = ( pck->info.flags & GF_PCK_CKTYPE_MASK) >> GF_PCK_CKTYPE_POS; //send from filter, update flags @@ -959,7 +984,6 @@ pck->pid->filter->session->nb_realloc_pck += (nb_reallocs - prev_nb_reallocs); } #endif - gf_rmt_end(); return GF_PENDING_PACKET; } //now dispatched @@ -1120,9 +1144,9 @@ Bool post_task=GF_FALSE; GF_FilterPacketInstance *inst; GF_FilterPidInst *dst = gf_list_get(pck->pid->destinations, i); - if (!dst->filter || dst->filter->finalized || (dst->filter->removed==1) || !dst->filter->freg->process) continue; + if (!dst->filter || dst->filter->finalized || (dst->filter->removed==1) || !dst->filter->freg->process || dst->in_swap) continue; - if (dst->discard_inputs==1) { + if (dst->discard_inputs==GF_PIDI_DISCARD_ON) { //in discard input mode, we drop all input packets but trigger reconfigure as they happen if ((pck->info.flags & GF_PCKF_PROPS_CHANGED) && (dst->props != pck->pid_props)) { //unassign old property list and set the new one @@ -1144,7 +1168,7 @@ //in which a previously blacklisted filter (failing (re)configure for previous state) could //now work, eg moving from formatA to formatB then back to formatA gf_list_reset(dst->filter->blacklisted); - dst->discard_inputs = 2; + dst->discard_inputs = GF_PIDI_DISCARD_RCFG; //and post a reconfigure task gf_fs_post_task(dst->filter->session, gf_filter_pid_reconfigure_task_discard, dst->filter, (GF_FilterPid *)dst, "pidinst_reconfigure", NULL); //keep packets, they will be trashed if we are still in discard when executing gf_filter_pid_reconfigure_task_discard @@ -1152,6 +1176,14 @@ continue; } } + //ignore flush packets if destination requires full blocks and block is in progress + if (dst->requires_full_data_block && !dst->last_block_ended && (pck->info.flags & GF_PCKF_IS_FLUSH)) { + continue; + } + //stop forwarding clock packets when in EOS + if (dst->is_end_of_stream && cktype) { + continue; + } inst = gf_fq_pop(pck->pid->filter->pcks_inst_reservoir); if (!inst) { @@ -1269,7 +1301,7 @@ if (inst->pck->pid_props) { safe_int_inc(&inst->pck->pid_props->reference_count); } - + gf_assert(pck->reference_count); safe_int_dec(&pck->reference_count); safe_int_inc(&inst->pck->reference_count); @@ -1301,7 +1333,7 @@ safe_int64_add(&dst->buffer_duration, us_duration); } safe_int_inc(&dst->filter->pending_packets); -// + gf_fq_add(dst->packets, inst); post_task = GF_TRUE; } @@ -1315,7 +1347,7 @@ } //make sure we lock the tasks mutex before getting the packet count, otherwise we might end up with a wrong number of packets - //if one thread consumes one packet while the dispatching thread (the caller here) is still upddating the state for that pid + //if one thread consumes one packet while the dispatching thread (the caller here) is still updating the state for that pid gf_mx_p(pid->filter->tasks_mx); u32 nb_pck = gf_fq_count(dst->packets); //update buffer occupancy before dispatching the task - if target pid is processed before we are done disptching his packet, pid buffer occupancy @@ -1327,7 +1359,7 @@ //otherwise we would have max_buffer_time=1ms (default) and a single AU dispatched would block unless speed is AU_DUR_ms/1ms ... if (us_duration && pid->max_buffer_time && (pid->max_buffer_time<us_duration)) pid->max_buffer_time = us_duration; - + gf_mx_v(pid->filter->tasks_mx); //post process task @@ -1357,19 +1389,23 @@ } gf_filter_packet_destroy(pck); } - gf_rmt_end(); return GF_OK; } GF_EXPORT GF_Err gf_filter_pck_send(GF_FilterPacket *pck) { + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to send a received packet in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + if (!pck || !pck->pid) return GF_BAD_PARAM; + //dangling packet if (pck->is_dangling) { gf_filter_pck_discard(pck); return GF_OK; } - gf_assert(pck->pid); return gf_filter_pck_send_internal(pck, GF_TRUE); } @@ -1481,6 +1517,33 @@ } //get true packet pointer pck=pck->pck; + + if (prop_4cc && value && pck->pid && pck->pid->filter->session->check_props) { + u32 ptype = gf_props_4cc_get_type(prop_4cc); + u8 c = prop_4cc>>24; + if ((c>='A') && (c<='Z')) { + if (gf_props_get_base_type(ptype) != gf_props_get_base_type(value->type)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Assigning packet property %s of type %s in filter %s but expecting %s\n", + gf_props_4cc_get_name(prop_4cc), + gf_props_get_type_name(value->type), + pck->pid->filter->freg->name, + gf_props_get_type_name(ptype) + )); + if (gf_sys_is_test_mode()) + exit(5); + } + u32 flags = gf_props_4cc_get_flags(prop_4cc); + if (!(flags & GF_PROP_FLAG_PCK)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Assigning packet property %s in filter %s but this is a PID property\n", + gf_props_4cc_get_name(prop_4cc), + pck->pid->filter->freg->name + )); + if (gf_sys_is_test_mode()) + exit(5); + } + } + } + hash = gf_props_hash_djb2(prop_4cc, prop_name ? prop_name : dyn_name); if (!pck->props) { @@ -1489,7 +1552,7 @@ gf_props_remove_property(pck->props, hash, prop_4cc, prop_name ? prop_name : dyn_name); } if (!value) return GF_OK; - + return gf_props_insert_property(pck->props, hash, prop_4cc, prop_name, dyn_name, value); } @@ -1557,12 +1620,25 @@ #else #define pck_check_prop(_a, _b, _str, c) c #endif + GF_EXPORT const GF_PropertyValue *gf_filter_pck_get_property(GF_FilterPacket *_pck, u32 prop_4cc) { //get true packet pointer GF_FilterPacket *pck = _pck->pck; if (!pck->props) return NULL; + if (pck->pid && pck->pid->filter->session->check_props && gf_props_4cc_get_type(prop_4cc)) { + u32 flags = gf_props_4cc_get_flags(prop_4cc); + if (!(flags & GF_PROP_FLAG_PCK)) { + GF_Filter *f = (_pck == pck) ? pck->pid->filter : ((GF_FilterPacketInstance*)_pck)->pid->filter; + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Fetching packet property %s in filter %s but this is a PID property\n", + gf_props_4cc_get_name(prop_4cc), + f->freg->name + )); + if (gf_sys_is_test_mode()) + exit(5); + } + } return pck_check_prop(_pck, prop_4cc, NULL, gf_props_get_property(pck->props, prop_4cc, NULL)); } @@ -1673,6 +1749,25 @@ } GF_EXPORT +GF_Err gf_filter_pck_set_switch_frame(GF_FilterPacket *pck, Bool is_switch_frame) +{ + PCK_SETTER_CHECK("switch_frame") + pck->info.flags &= ~GF_PCKF_IS_SWITCH_FRAME; + if (is_switch_frame) { + pck->info.flags |= GF_PCKF_IS_SWITCH_FRAME; + } + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_pck_get_switch_frame(GF_FilterPacket *pck) +{ + gf_assert(pck); + //get true packet pointer + return (Bool) (pck->pck->info.flags & GF_PCKF_IS_SWITCH_FRAME) ? GF_TRUE : GF_FALSE; +} + +GF_EXPORT GF_Err gf_filter_pck_set_roll_info(GF_FilterPacket *pck, s16 roll_count) { PCK_SETTER_CHECK("ROLL") @@ -1867,7 +1962,7 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reallocate input packet on output PID in filter %s\n", pck->pid->filter->name)); return GF_BAD_PARAM; } - if (! pck->src_filter) { + if (! pck->src_filter && (pck->is_dangling!=1)) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reallocate an already sent packet in filter %s\n", pck->pid->filter->name)); return GF_BAD_PARAM; } @@ -1882,7 +1977,8 @@ pck->alloc_size = pck->data_length + nb_bytes_to_add; pck->data = gf_realloc(pck->data, pck->alloc_size); #ifdef GPAC_MEMORY_TRACKING - pck->pid->filter->session->nb_realloc_pck++; + if (!pck->is_dangling) + pck->pid->filter->session->nb_realloc_pck++; #endif } pck->info.byte_offset = GF_FILTER_NO_BO;
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_pid.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_pid.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -56,23 +56,35 @@ { gf_assert(pidinst); gf_filter_pid_inst_reset(pidinst); + if (pidinst->pid) { + gf_list_del_item(pidinst->pid->destinations, pidinst); + pidinst->pid->num_destinations = gf_list_count(pidinst->pid->destinations); + } + if (pidinst->filter) { + gf_list_del_item(pidinst->filter->input_pids, pidinst); + pidinst->filter->num_input_pids = gf_list_count(pidinst->filter->input_pids); + } gf_fq_del(pidinst->packets, (gf_destruct_fun) pcki_del); gf_mx_del(pidinst->pck_mx); gf_list_del(pidinst->pck_reassembly); if (pidinst->props) { gf_assert(pidinst->props->reference_count); - gf_mx_p(pidinst->pid->filter->tasks_mx); + //pidinst->pid may be NULL upon filter setup error + if (pidinst->pid) + gf_mx_p(pidinst->pid->filter->tasks_mx); //not in parent pid, may happen when reattaching a pid inst to a different pid //in this case do NOT delete the props - if (gf_list_find(pidinst->pid->properties, pidinst->props)>=0) { + if (!pidinst->pid || gf_list_find(pidinst->pid->properties, pidinst->props)>=0) { if (safe_int_dec(&pidinst->props->reference_count) == 0) { //see \ref gf_filter_pid_merge_properties_internal for mutex - gf_list_del_item(pidinst->pid->properties, pidinst->props); + if (pidinst->pid) + gf_list_del_item(pidinst->pid->properties, pidinst->props); gf_props_del(pidinst->props); } } - gf_mx_v(pidinst->pid->filter->tasks_mx); + if (pidinst->pid) + gf_mx_v(pidinst->pid->filter->tasks_mx); } #ifdef GPAC_ENABLE_DEBUG if (pidinst->prop_dump) { @@ -87,6 +99,17 @@ gf_free(pidinst); } +void gf_filter_pid_inst_check_delete(GF_FilterPidInst *pidinst) +{ + //we posted a configure_task_discard on this PID, mark as "to be destroyed" and let thet task destroy the pid + if (pidinst->discard_inputs==GF_PIDI_DISCARD_RCFG) { + pidinst->discard_inputs = GF_PIDI_DISCARD_RCFG_DELETE; + } else { + gf_filter_pid_inst_del(pidinst); + } +} + + static GF_FilterPidInst *gf_filter_pid_inst_new(GF_Filter *filter, GF_FilterPid *pid) { GF_FilterPidInst *pidinst; @@ -119,11 +142,14 @@ } //if we are in end of stream state and done with all packets, stay blocked if (pid->has_seen_eos && !pid->nb_buffer_unit && !pid->eos_keepalive) { + //lock before reading would_block otherwise we may increase it too much if changed by another thread + gf_mx_p(pid->filter->tasks_mx); if (!pid->would_block) { safe_int_inc(&pid->would_block); safe_int_inc(&pid->filter->would_block); gf_assert(pid->filter->would_block + pid->filter->num_out_pids_not_connected <= pid->filter->num_output_pids); } + gf_mx_v(pid->filter->tasks_mx); return; } @@ -209,10 +235,12 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s PID %s connected to decoder %s, but dependent stream %s connected to %s - switching pid destination\n", a_pid->filter->name, a_pid->name, a_pidi->filter->name, pidi->pid->name, pidi->filter->name)); //disconnect this pid instance from its current decoder - gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, a_pidi->filter, a_pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(filter->session, a_pidi->filter, a_pid); //reconnect this pid instance to the new decoder + gf_mx_p(pid->filter->tasks_mx); safe_int_inc(&pid->filter->out_pid_connection_pending); + gf_mx_v(pid->filter->tasks_mx); gf_filter_pid_post_connect_task(pidi->filter, a_pid); } @@ -368,7 +396,7 @@ _t->requeue_request = GF_TRUE; \ _t->schedule_next_time = gf_sys_clock_high_res() + 50; \ -void gf_filter_pid_inst_delete_task(GF_FSTask *task) +static void gf_filter_pid_inst_delete_task(GF_FSTask *task) { GF_FilterPid *pid = task->pid; GF_FilterPidInst *pidinst = task->udta; @@ -431,6 +459,8 @@ pid->buffer_duration = 0; } + safe_int_dec(&filter->session->remove_tasks); + gf_assert(pid->filter == filter); if (pid_still_alive) @@ -438,6 +468,7 @@ //some more destinations on pid, update blocking state if (pid->num_destinations || pid->init_task_pending) { + //no need to lock here, the lock is done in check_unblock / would_block if (pid->would_block) gf_filter_pid_check_unblock(pid); else @@ -463,20 +494,35 @@ pid->removed = GF_TRUE; //filter still active and has no input, check if there are no more output pids valid. If so, remove filter - if (!gf_list_count(filter->input_pids) && !filter->finalized) { + if (!gf_list_count(filter->input_pids) && !filter->finalized + //make sure we don't have any remove pid packets pending + && !filter->pid_rem_packet_pending + ) { u32 i, nb_opid_rem=0; for (i=0; i<filter->num_output_pids; i++) { GF_FilterPid *apid = gf_list_get(filter->output_pids, i); if (apid->removed) nb_opid_rem++; } if (gf_list_count(filter->output_pids)==nb_opid_rem) { - gf_filter_post_remove(filter); + //filter is being watched by another filter for setup error (and potentially other tasks), do not delete but mark as disabled + if (filter->on_setup_error) { + filter->disabled = GF_FILTER_DISABLED; + } else { + gf_filter_post_remove(filter); + } } } gf_mx_v(filter->tasks_mx); } +void gf_fs_post_pid_instance_delete_task(GF_FilterSession *session, GF_Filter *filter, GF_FilterPid *pid, GF_FilterPidInst *pidinst) +{ + safe_int_inc(&session->remove_tasks); + gf_fs_post_task(session, gf_filter_pid_inst_delete_task, filter, pid, "pid_inst_delete", pidinst); +} + + static void gf_filter_pid_inst_swap_delete(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPidInst *pidinst, GF_FilterPidInst *dst_swapinst) { u32 i, j; @@ -508,7 +554,7 @@ if (filter->detached_pid_inst && (gf_list_find(filter->detached_pid_inst, pidinst)>=0) ) return; - gf_filter_pid_inst_del(pidinst); + gf_filter_pid_inst_check_delete(pidinst); if (filter->num_input_pids) return; //we still have other pid instances registered for chain reconfigure, don't discard the filter @@ -626,6 +672,9 @@ dst->is_end_of_stream = src->is_end_of_stream; dst->nb_eos_signaled = src->nb_eos_signaled; dst->buffer_duration = src->buffer_duration; + src->buffer_duration = 0; + src->in_swap = GF_TRUE; + dst->nb_clocks_signaled = src->nb_clocks_signaled; //switch previous src property map to this new pid (this avoids rewriting props of already dispatched packets) @@ -679,7 +728,7 @@ gf_list_del_item(src->pid->destinations, src); src->pid->num_destinations = gf_list_count(src->pid->destinations); - gf_filter_pid_inst_del(src); + gf_filter_pid_inst_check_delete(src); filter->swap_pidinst_dst = NULL; filter->swap_pidinst_src = NULL; @@ -692,7 +741,8 @@ gf_assert(!src->filter->swap_pidinst_dst); src->filter->swap_pidinst_dst = filter->swap_pidinst_dst; src->filter->swap_pending = GF_TRUE; - gf_fs_post_task(filter->session, gf_filter_pid_inst_swap_delete_task, src->filter, src->pid, "pid_inst_delete", src); + src->swap_source = NULL; + gf_fs_post_task(filter->session, gf_filter_pid_inst_swap_delete_task, src->filter, src->pid, "pid_inst_swap_delete", src); } } @@ -712,6 +762,7 @@ gf_assert(pidinst->filter->nb_main_thread_forced); safe_int_dec(&pidinst->filter->nb_main_thread_forced); } + safe_int_dec(&pidinst->filter->pending_packets); } count = gf_list_count(pidinst->pck_reassembly); for (i=0; i<count; i++) { @@ -721,6 +772,7 @@ gf_assert(pidinst->filter->nb_main_thread_forced); safe_int_dec(&pidinst->filter->nb_main_thread_forced); } + safe_int_dec(&pidinst->filter->pending_packets); } pidinst->filter = NULL; } @@ -782,6 +834,8 @@ gf_assert(filter->freg->configure_pid); if (filter->finalized) { + if (ctype == GF_PID_CONF_REMOVE) return GF_OK; + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Trying to configure PID %s in finalized filter %s\n", pid->name, filter->name)); if (ctype==GF_PID_CONF_CONNECT) { gf_assert(pid->filter->out_pid_connection_pending); @@ -799,6 +853,7 @@ //reattach new filter and pid pidinst->filter = filter; pidinst->pid = pid; + pidinst->in_swap = GF_FALSE; gf_assert(!pidinst->props); @@ -836,6 +891,7 @@ //first connection of this PID to this filter if (!pidinst) { if (ctype != GF_PID_CONF_CONNECT) { + if (filter->removed) return GF_OK; GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Trying to disconnect PID %s not present in filter %s inputs\n", pid->name, filter->name)); return GF_SERVICE_ERROR; } @@ -890,7 +946,9 @@ #endif GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s reconfigure\n", pidinst->filter->name, pidinst->pid->name)); + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); e = filter->freg->configure_pid(filter, (GF_FilterPid*) pidinst, (ctype==GF_PID_CONF_REMOVE) ? GF_TRUE : GF_FALSE); + gf_logs_thread_untag(filter); #ifdef GPAC_MEMORY_TRACKING if (filter->session->check_allocs) { @@ -929,10 +987,14 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to reconfigure PID %s:%s in filter %s: %s\n", pid->filter->name, pid->name, filter->name, gf_error_to_string(e) )); filter->session->last_connect_error = e; - } else { + } + //do not relink dst if one relink already pending + else if (!pidinst->detach_pending) { GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Failed to reconfigure PID %s:%s in filter %s: %s, reloading filter graph\n", pid->filter->name, pid->name, filter->name, gf_error_to_string(e) )); gf_list_add(pid->filter->blacklisted, (void *) filter->freg); gf_filter_relink_dst(pidinst, e); + } else { + e = GF_OK; } } else { @@ -942,7 +1004,9 @@ filter->num_input_pids = gf_list_count(filter->input_pids); if (!filter->num_input_pids) filter->single_source = NULL; + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); filter->freg->configure_pid(filter, (GF_FilterPid *) pidinst, GF_TRUE); + gf_logs_thread_untag(filter); gf_mx_v(filter->tasks_mx); gf_mx_p(pidinst->pid->filter->tasks_mx); @@ -970,7 +1034,7 @@ gf_mx_v(pid->filter->tasks_mx); //destroy pid instance - gf_filter_pid_inst_del(pidinst); + gf_filter_pid_inst_check_delete(pidinst); pidinst = NULL; gf_mx_v(pid->filter->tasks_mx); } @@ -1029,6 +1093,8 @@ if (!filter->num_input_pids && !filter->num_output_pids) { remove_filter = GF_TRUE; } + } else if (ctype==GF_PID_CONF_RECONFIG) { + gf_fs_post_pid_instance_delete_task(filter->session, pidinst->pid->filter, pidinst->pid, pidinst); } } else if (filter->has_out_caps) { Bool unload_filter = GF_TRUE; @@ -1042,10 +1108,12 @@ GF_FilterPidInst *a_pidinst = gf_list_pop_back(filter->input_pids); FSESS_CHECK_THREAD(filter) filter->num_input_pids--; + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); filter->freg->configure_pid(filter, (GF_FilterPid *) a_pidinst, GF_TRUE); + gf_logs_thread_untag(filter); gf_filter_pid_post_init_task(a_pidinst->pid->filter, a_pidinst->pid); - gf_fs_post_task(filter->session, gf_filter_pid_inst_delete_task, a_pidinst->pid->filter, a_pidinst->pid, "pid_inst_delete", a_pidinst); + gf_fs_post_pid_instance_delete_task(filter->session, a_pidinst->pid->filter, a_pidinst->pid, a_pidinst); unload_filter = GF_FALSE; } @@ -1116,7 +1184,10 @@ if (pid->filter->freg->process_event) { GF_FilterEvent evt; GF_FEVT_INIT(evt, GF_FEVT_CONNECT_FAIL, pid); + + gf_logs_thread_tag(pid->filter, GF_LOG_TAG_FILTER); pid->filter->freg->process_event(pid->filter, &evt); + gf_logs_thread_untag(pid->filter); } filter->session->last_connect_error = e; } @@ -1171,10 +1242,20 @@ if (!filter->num_input_pids && !filter->sticky) { gf_filter_reset_pending_packets(filter); filter->removed = 1; + //if all outputs are marked as removed and disconnected, post destroy + //this happens when explicitly removing filters which disconnect outputs before disconnecting inputs + u32 nb_rem = 0; + for (i=0; i<filter->num_output_pids; i++) { + GF_FilterPid *opid = gf_list_get(filter->output_pids, i); + if (opid->removed && !opid->num_destinations) + nb_rem++; + } + if (nb_rem && (nb_rem==filter->num_output_pids)) + gf_filter_post_remove(filter); } //post a pid_delete task to also trigger removal of the filter if needed if (pidinst) - gf_fs_post_task(filter->session, gf_filter_pid_inst_delete_task, pid->filter, pid, "pid_inst_delete", pidinst); + gf_fs_post_pid_instance_delete_task(filter->session, pid->filter, pid, pidinst); return e; } @@ -1215,7 +1296,7 @@ //new pid instance creating a fanout on an already playing pid: force discarding input packets until we have a PLAY if (!is_pid_swap && new_pid_inst && !filter->is_pid_adaptation_filter && pid->is_playing && pid->nb_pck_sent && (pid->num_destinations>1)) - pidinst->discard_inputs = GF_TRUE; + pidinst->discard_inputs = GF_PIDI_DISCARD_ON; } //once all pid have been (re)connected, update any internal caps gf_filter_pid_update_caps(pid); @@ -1226,6 +1307,49 @@ { GF_Filter *filter = task->filter; GF_FilterSession *fsess = filter->session; + + //special case: it may happen that multiple relinks between A and B take place in sequence + //before intermediate new filters get connected + //ex: when discarding packets and multiple reconfigurations were queued - cf issue 3290 + //we keep track in swap_source of the last relinking operation and discard all non-matching filters + //which correspond to past configurations + if (filter->swap_pidinst_src && (filter->swap_pidinst_src->swap_source!=filter)) { + filter->swap_pidinst_dst = NULL; + filter->swap_pidinst_src = NULL; + gf_assert(task->pid->filter->out_pid_connection_pending); + safe_int_dec(&task->pid->filter->out_pid_connection_pending); + gf_filter_post_remove(filter); + return; + } + /* special case: the target filter or pid has been marked for removal since the task was posted + ignore the task - if a pid swaping was in place, remove pid from filter and delete it + */ + if (task->pid->pid->removed || task->filter->finalized || task->filter->removed) { + u32 i, count = gf_list_count(filter->detached_pid_inst); + for (i=0; i<count; i++) { + GF_FilterPidInst *pidinst = gf_list_get(filter->detached_pid_inst, i); + if (pidinst->filter !=filter) continue; + gf_list_rem(filter->detached_pid_inst, i); + //reattach new filter and pid + pidinst->filter = filter; + pidinst->pid = task->pid; + safe_int_dec(&pidinst->detach_pending); + //delete pid + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); + filter->freg->configure_pid(filter, (GF_FilterPid*) pidinst, GF_TRUE); + gf_logs_thread_untag(filter); + gf_filter_pid_inst_del(pidinst); + break; + } + if (!gf_list_count(filter->detached_pid_inst)) { + gf_list_del(filter->detached_pid_inst); + filter->detached_pid_inst = NULL; + } + gf_assert(task->pid->filter->out_pid_connection_pending); + safe_int_dec(&task->pid->filter->out_pid_connection_pending); + return; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s pid %s connecting to %s (%p)\n", task->pid->pid->filter->name, task->pid->pid->name, task->filter->name, filter)); //filter will require a new instance, clone it unless user-instantiated filter @@ -1300,32 +1424,79 @@ task->pid->pid->pid_info_changed = GF_FALSE; } - if (pidi->discard_inputs==2) { + //check if we need to discard + Bool destroy_pidinst = GF_FALSE; + if (pidi->discard_inputs==GF_PIDI_DISCARD_RCFG_DELETE) { + pidi->discard_inputs = GF_PIDI_DISCARD_RCFG; + destroy_pidinst = GF_TRUE; + } + + if (pidi->discard_inputs==GF_PIDI_DISCARD_RCFG) { gf_filter_aggregate_packets(pidi); while (gf_filter_pid_get_packet((GF_FilterPid *) pidi)) { gf_filter_pid_drop_packet((GF_FilterPid *) pidi); } //move back to regular discard - pidi->discard_inputs = 1; + pidi->discard_inputs = GF_PIDI_DISCARD_ON; } + + if (destroy_pidinst) + gf_filter_pid_inst_del(pidi); + } -void gf_filter_pid_disconnect_task(GF_FSTask *task) + +static void gf_filter_pid_disconnect_task(GF_FSTask *task) { + safe_int_dec(&task->filter->session->remove_tasks); + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s disconnect from %s\n", task->pid->pid->filter->name, task->pid->pid->name, task->filter->name)); - gf_filter_pid_configure(task->filter, task->pid->pid, GF_PID_CONF_REMOVE); + + //note that a filter can be marked as removed but PID not yet removed (when disconnecting a chain) + //we need to disconnect it in this case + if (!task->filter->removed || !task->pid->pid->removed) + gf_filter_pid_configure(task->filter, task->pid->pid, GF_PID_CONF_REMOVE); gf_mx_p(task->filter->tasks_mx); //if the filter has no more connected ins and outs, remove it - if (task->filter->removed && !gf_list_count(task->filter->output_pids) && !gf_list_count(task->filter->input_pids)) { - Bool direct_mode = task->filter->session->direct_mode; - gf_filter_post_remove(task->filter); - if (direct_mode) { - gf_mx_v(task->filter->tasks_mx); - task->filter = NULL; - return; + if (task->filter->removed + && !task->filter->finalized + && !gf_list_count(task->filter->output_pids) + && !gf_list_count(task->filter->input_pids) + //make sure we don't have any remove pid packets pending + && !task->filter->pid_rem_packet_pending + ) { + if (task->filter->on_setup_error) { + task->filter->disabled = GF_FILTER_DISABLED; + } else { + Bool direct_mode = task->filter->session->direct_mode; + gf_filter_post_remove(task->filter); + if (direct_mode) { + gf_mx_v(task->filter->tasks_mx); + //release filter removal prevention on both source and destination + safe_int_dec(&task->pid->pid->filter->detach_pid_tasks_pending); + safe_int_dec(&task->filter->detach_pid_tasks_pending); + task->filter = NULL; + return; + } } } gf_mx_v(task->filter->tasks_mx); + //release filter removal prevention on both source and destination + safe_int_dec(&task->pid->pid->filter->detach_pid_tasks_pending); + safe_int_dec(&task->filter->detach_pid_tasks_pending); +} + +void gf_fs_post_disconnect_task(GF_FilterSession *session, GF_Filter *filter, GF_FilterPid *pid) +{ + //if source or dest filters are finalized or session is being destroyed mode, do not disconnect + if (filter->finalized || pid->pid->filter->finalized || (session->run_status!=GF_OK)) return; + + safe_int_inc(&session->remove_tasks); + //prevent filter removal on both source and destination + safe_int_inc(&pid->pid->filter->detach_pid_tasks_pending); + safe_int_inc(&filter->detach_pid_tasks_pending); + + gf_fs_post_task(session, gf_filter_pid_disconnect_task, filter, pid, "pidinst_disconnect", NULL); } static void gf_filter_pid_detach_task_ex(GF_FSTask *task, Bool no_flush) @@ -1560,6 +1731,7 @@ { char *psep; u32 comp_type=0; + u32 stream_type=0; Bool is_neg = GF_FALSE; const GF_PropertyEntry *pent; const GF_PropertyEntry *pent_val=NULL; @@ -1575,7 +1747,7 @@ if (pent) { u32 matched=0; u32 type=0; - u32 ptype = pent->prop.value.uint; + stream_type = pent->prop.value.uint; if (!strnicmp(frag_name, "audio", 5)) { matched=5; @@ -1599,22 +1771,22 @@ pent = gf_filter_pid_get_property_entry(src_pid, GF_PROP_PID_ISOM_HANDLER); if (pent && (pent->prop.value.uint == gf_4cc_parse(frag_name)) ) { matched=4; - type = ptype; + type = stream_type; } } } //stream is encrypted and desired type is not, get original stream type - if ((ptype == GF_STREAM_ENCRYPTED) && type && (type != GF_STREAM_ENCRYPTED) ) { + if ((stream_type == GF_STREAM_ENCRYPTED) && type && (type != GF_STREAM_ENCRYPTED) ) { pent = gf_filter_pid_get_property_entry(src_pid, GF_PROP_PID_ORIG_STREAM_TYPE); - if (pent) ptype = pent->prop.value.uint; + if (pent) stream_type = pent->prop.value.uint; } if (matched && - ( (!is_neg && (type != ptype)) || (is_neg && (type == ptype)) ) + ( (!is_neg && (type != stream_type)) || (is_neg && (type == stream_type)) ) ) { //special case: if we request a non-file stream but the pid is a file, we will need a demux to //move from file to A/V/... streams, so we accept any #MEDIA from file streams - if (ptype == GF_STREAM_FILE) { + if (stream_type == GF_STREAM_FILE) { *prop_not_found = GF_TRUE; return GF_TRUE; } @@ -1674,17 +1846,29 @@ } } + //no prop, no '=' separator this is #PIDNAME addressing not solved in filter_source_id_match if (!psep) { - *prop_not_found = GF_TRUE; - GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("PID addressing %s not recognized, ignoring and assuming match\n", frag_name )); - return GF_TRUE; + if (!strcmp(frag_name, src_pid->name)) return GF_TRUE; + //if pid is from a source filter (no inputs) of type file, allow further connection as PIDNAME is likely the name of a demuxed PID + if ((stream_type == GF_STREAM_FILE) && !src_pid->filter->num_input_pids) { + return GF_TRUE; + } + //otherwise walk up the chain for 1->1 filters and check PID names - this allows using the filename as PIDNAME on demuxed PIDs + GF_Filter *src_f = src_pid->filter; + if (src_f->num_input_pids==1) { + GF_FilterPidInst *pidi = gf_list_get(src_f->input_pids, 0); + if (!strcmp(frag_name, pidi->pid->name)) return GF_TRUE; + src_f = pidi->pid->filter; + } + *prop_not_found = GF_FALSE; + return GF_FALSE; } Bool is_equal = GF_FALSE; Bool use_not_equal = GF_FALSE; GF_PropertyValue prop_val; u32 p4cc = 0; - char c=psep0; + char c = psep0; psep0 = 0; pent=NULL; @@ -1713,6 +1897,12 @@ //check for built-in property p4cc = gf_props_get_id(frag_name); + //if pid is from a source filter (no inputs) of type file, allow further connection as PIDNAME is likely the name of a demuxed PID + if ((p4cc==GF_PROP_PID_STREAM_TYPE) && (stream_type == GF_STREAM_FILE) && !src_pid->filter->num_input_pids) { + psep0 = c; + return GF_TRUE; + } + if (!p4cc && (strlen(frag_name)==4)) p4cc = GF_4CC(frag_name0, frag_name1, frag_name2, frag_name3); @@ -1817,7 +2007,7 @@ return is_equal; } -static Bool filter_source_id_match(GF_FilterPid *src_pid, const char *id, GF_Filter *dst_filter, Bool *pid_excluded, Bool *needs_clone) +Bool filter_source_id_match(GF_FilterPid *src_pid, const char *src_filter_id, GF_Filter *dst_filter, Bool *pid_excluded, Bool *needs_clone, const char *ext_source_ids) { const char *source_ids; char *resolved_source_ids = NULL; @@ -1826,15 +2016,17 @@ Bool has_default_match; Bool is_pid_excluded; *pid_excluded = GF_FALSE; - if (!dst_filter->source_ids) - return GF_TRUE; - if (!id) - return GF_FALSE; + if (dst_filter) { + if (!dst_filter->source_ids) + return GF_TRUE; + if (!src_filter_id) + return GF_FALSE; + } sourceid_reassign: - source_ids = resolved_source_ids ? resolved_source_ids : dst_filter->source_ids; + source_ids = resolved_source_ids ? resolved_source_ids : (dst_filter ? dst_filter->source_ids : ext_source_ids); if (!first_pass) { - gf_assert(dst_filter->dynamic_source_ids); + gf_assert(dst_filter && dst_filter->dynamic_source_ids); source_ids = dst_filter->dynamic_source_ids; } has_default_match = GF_FALSE; @@ -1871,7 +2063,11 @@ if (source_ids0=='*') { } // id does not match else { - Bool res = strncmp(id, source_ids, sublen) ? GF_FALSE : GF_TRUE; + Bool res; + if (src_filter_id) + res = strncmp(src_filter_id, source_ids, sublen) ? GF_FALSE : GF_TRUE; + else + res = sublen ? GF_FALSE : GF_TRUE; if (use_neg) res = !res; if (!res) { source_ids += len+1; @@ -1904,23 +2100,34 @@ if (! filter_pid_check_fragment(src_pid, frag_name, &local_pid_excluded, &needs_resolve, &prop_not_found, prop_dump_buffer)) { if (needs_resolve) { - if (first_pass) { + if (first_pass && dst_filter) { char *sid = resolved_source_ids ? resolved_source_ids : dst_filter->source_ids; char *frag_sep = strchr(frag_name, dst_filter->session->sep_name); gf_assert(frag_sep); + u32 frag_sep_len = (u32) (frag_sep-frag_name+1); if (next_frag) next_frag0 = src_pid->filter->session->sep_frag; char *new_source_ids = gf_malloc(sizeof(char) * (strlen(sid) + strlen(prop_dump_buffer)+1)); - u32 clen = (u32) (1+frag_sep - sid); + u32 clen = (u32) sublen + frag_sep_len + 1; strncpy(new_source_ids, sid, clen); new_source_idsclen=0; strcat(new_source_ids, prop_dump_buffer); if (next_frag) strcat(new_source_ids, next_frag); - if (resolved_source_ids) gf_free(resolved_source_ids); + if (resolved_source_ids) { + + // avoid infinte loop if new_source_ids has not changed + if ( !strcmp(resolved_source_ids, new_source_ids) ) { + gf_free(new_source_ids); + break; + } + + gf_free(resolved_source_ids); + } resolved_source_ids = new_source_ids; if (frag_clone) gf_free(frag_clone); goto sourceid_reassign; + } } else { @@ -1933,6 +2140,8 @@ //remember we succeed because PID has no matching property if (!prop_not_found) all_frags_not_found = GF_FALSE; + else if (ext_source_ids) + all_matched = GF_FALSE; } if (!next_frag) break; @@ -1963,14 +2172,14 @@ if (!result) { if (resolved_source_ids) gf_free(resolved_source_ids); - if (dst_filter->dynamic_source_ids && first_pass) { + if (dst_filter && dst_filter->dynamic_source_ids && first_pass) { first_pass = GF_FALSE; goto sourceid_reassign; } *pid_excluded = is_pid_excluded; return GF_FALSE; } - if (resolved_source_ids) { + if (resolved_source_ids && dst_filter) { if (!dst_filter->dynamic_source_ids) { dst_filter->dynamic_source_ids = dst_filter->source_ids; dst_filter->source_ids = resolved_source_ids; @@ -2006,7 +2215,7 @@ } for (i=0; i<parent->num_input_pids; i++) { GF_FilterPidInst *pidi = gf_list_get(parent->input_pids, i); - if (gf_filter_in_parent_chain(pidi->pid->filter, filter)) { + if (pidi->pid && gf_filter_in_parent_chain(pidi->pid->filter, filter)) { gf_mx_v(parent->tasks_mx); return GF_TRUE; } @@ -2034,7 +2243,7 @@ } -Bool gf_filter_pid_caps_match(GF_FilterPid *src_pid_or_ipid, const GF_FilterRegister *freg, GF_Filter *filter_inst, u8 *priority, u32 *dst_bundle_idx, GF_Filter *dst_filter, s32 for_bundle_idx) +Bool gf_filter_pid_caps_match(GF_FilterPid *src_pid_or_ipid, const GF_FilterRegister *freg, GF_Filter *filter_inst, s16 *priority, u32 *dst_bundle_idx, GF_Filter *dst_filter, s32 for_bundle_idx) { u32 i=0; u32 cur_bundle_start = 0; @@ -2045,6 +2254,7 @@ Bool mime_matched = GF_FALSE; Bool has_file_ext_cap = GF_FALSE; Bool ext_not_trusted; + Bool is_fake = GF_FALSE; GF_FilterPid *src_pid = src_pid_or_ipid->pid; const GF_FilterCapability *in_caps; u32 nb_in_caps; @@ -2095,10 +2305,18 @@ if (!in_caps) return GF_TRUE; + + const GF_PropertyValue *p = gf_filter_pid_get_property(src_pid_or_ipid, GF_PROP_PID_FAKE); + if (p && p->value.boolean) { + is_fake = GF_TRUE; + } + //check all input caps of dst filter for (i=0; i<nb_in_caps; i++) { const GF_PropertyValue *pid_cap=NULL; const GF_FilterCapability *cap = &in_capsi; + if (cap->flags & GF_CAPFLAG_RECONFIG) break; + Bool has_cap_fake = GF_FALSE; /*end of cap bundle*/ if (i && !(cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { @@ -2138,6 +2356,8 @@ //no match for this cap, go on until new one or end if (!all_caps_matched) continue; + if (is_fake && (cap->code==GF_PROP_PID_FAKE)) has_cap_fake = GF_TRUE; + if (cap->code) { pid_cap = gf_filter_pid_get_property_first(src_pid_or_ipid, cap->code); @@ -2180,6 +2400,12 @@ continue; } + //only check for cap presence + if (cap->flags & GF_CAPFLAG_PRESENT) { + if (!pid_cap) + all_caps_matched = GF_FALSE; + continue; + } //we found a property of that type and it is equal if (pid_cap) { @@ -2190,6 +2416,7 @@ //this could be optimized by not checking several times the same cap for (j=0; j<nb_in_caps; j++) { const GF_FilterCapability *a_cap = &in_capsj; + if (a_cap->flags & GF_CAPFLAG_RECONFIG) break; if ((j>cur_bundle_start) && ! (a_cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { break; @@ -2244,9 +2471,13 @@ if (ext_not_trusted && prop_equal && (cap->code==GF_PROP_PID_MIME)) mime_matched = GF_TRUE; } - else if (! (cap->flags & (GF_CAPFLAG_EXCLUDED | GF_CAPFLAG_OPTIONAL) ) ) { + else if (! (cap->flags & (GF_CAPFLAG_EXCLUDED | GF_CAPFLAG_OPTIONAL) ) + || (cap->flags & GF_CAPFLAG_PRESENT) + ) { all_caps_matched=GF_FALSE; } + + if (is_fake && !has_cap_fake) all_caps_matched = GF_FALSE; } if (has_file_ext_cap && ext_not_trusted && !mime_matched) @@ -2266,6 +2497,7 @@ u32 i, nb_bundles = 0, num_in_bundle=0; for (i=0; i<nb_caps; i++) { const GF_FilterCapability *cap = &capsi; + if (cap->flags & GF_CAPFLAG_RECONFIG) break; if (! (cap->flags & GF_CAPFLAG_IN_BUNDLE)) { if (num_in_bundle) nb_bundles++; num_in_bundle=0; @@ -2339,6 +2571,8 @@ const GF_FilterCapability *a_cap = &caps0; u32 cap_flags = a_cap->flags; caps++; + if (cap_flags & GF_CAPFLAG_RECONFIG) break; + //move to next bundle if (!(cap_flags & GF_CAPFLAG_IN_BUNDLE)) { cur_idx++; @@ -2505,14 +2739,20 @@ u32 nb_matched = 0; u32 nb_caps_in_dst_bundle = dst_cap ? dst_cap->nb_vals : 0; + //check all input caps of dst filter, count ones that are matched for (j=0; j<nb_caps_in_dst_bundle; j++) { const GF_FilterCapability *in_cap = dst_cap->valsj; u32 in_cap_flags = in_cap->flags; register Bool in_cap_flags_excl = in_cap_flags & GF_CAPFLAG_EXCLUDED; + if ((out_cap_flags & GF_CAPFLAG_PRESENT) && (in_cap_flags & GF_CAPFLAG_PRESENT)) { + prop_matched = GF_TRUE; + if (in_cap_flags & GF_CAPFLAG_LOADED_FILTER) + cap_loaded_filter_only = 1; + } //we found a property of that type , check if equal equal - if (in_cap_flags_excl && !out_cap_flags_excl) { + else if (in_cap_flags_excl && !out_cap_flags_excl) { Bool prop_equal; //special case for excluded output caps marked for loaded filter only and optional: //always consider no match, the actual pid link resolving will sort this out @@ -2631,7 +2871,7 @@ GF_EXPORT Bool gf_filter_pid_check_caps(GF_FilterPid *_pid) { - u8 priority; + s16 priority; Bool res; GF_Filter *on_filter; if (PID_IS_OUTPUT(_pid)) return GF_FALSE; @@ -2669,15 +2909,16 @@ static Bool gf_filter_out_caps_solved_by_connection(const GF_FilterRegister *freg, u32 bundle_idx) { u32 i, k, cur_bundle_idx = 0; - u32 nb_out_caps=0; + u32 nb_out_caps=0; for (i=0; i<freg->nb_caps; i++) { u32 nb_caps = 0; - u32 cap_bundle_idx = 0; + u32 cap_bundle_idx = 0; const GF_FilterCapability *cap = &freg->capsi; + if (cap->flags & GF_CAPFLAG_RECONFIG) break; if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) { cur_bundle_idx++; if (cur_bundle_idx>bundle_idx) return GF_FALSE; - continue; + continue; } if (!(cap->flags & GF_CAPFLAG_STATIC) && (bundle_idx>cur_bundle_idx)) continue; if (!(cap->flags & GF_CAPFLAG_OUTPUT)) continue; @@ -2686,10 +2927,11 @@ for (k=0; k<freg->nb_caps; k++) { const GF_FilterCapability *acap = &freg->capsk; - if (!(acap->flags & GF_CAPFLAG_IN_BUNDLE)) { - cap_bundle_idx++; - continue; - } + if (acap->flags & GF_CAPFLAG_RECONFIG) break; + if (!(acap->flags & GF_CAPFLAG_IN_BUNDLE)) { + cap_bundle_idx++; + continue; + } if (!(acap->flags & GF_CAPFLAG_OUTPUT)) continue; if (acap->flags & GF_CAPFLAG_OPTIONAL) continue; if (!(acap->flags & GF_CAPFLAG_STATIC) && (cap_bundle_idx!=bundle_idx) ) continue; @@ -2703,8 +2945,8 @@ if (nb_caps>1) return GF_TRUE; } - if (nb_caps && !(cap->flags & GF_CAPFLAG_EXCLUDED)) - nb_out_caps++; + if (nb_caps && !(cap->flags & GF_CAPFLAG_EXCLUDED)) + nb_out_caps++; } if (!nb_out_caps) return GF_TRUE; @@ -2741,7 +2983,7 @@ note that it is still possible to use a mux or demux in the chain, but they have to be explicitly loaded */ - if ((rlevel>1) && (dst_stream_type==GF_STREAM_FILE)) + if ((rlevel>1) && (dst_stream_type==GF_STREAM_FILE) && !(reg_desc->freg->flags&GF_FS_REG_HIDE_WEIGHT)) return 0; reg_desc->in_edges_enabling = 1; @@ -2760,7 +3002,6 @@ //if source is not edge origin and edge is only valid for explicitly loaded filters, disable edge if (edge->loaded_filter_only && (edge->src_reg->freg != pid->filter->freg) ) { edge->status = EDGE_STATUS_DISABLED; - edge->disabled_depth = rlevel+1; continue; } @@ -2804,7 +3045,6 @@ } else { edge->status = EDGE_STATUS_DISABLED; - edge->disabled_depth = rlevel+1; continue; } } @@ -2822,7 +3062,6 @@ //otherwise the subgraph doesn't match our source reg, mark as disaled and never test again else if (res==0) { edge->status = EDGE_STATUS_DISABLED; - edge->disabled_depth = rlevel+1; } } reg_desc->in_edges_enabling = 0; @@ -3082,7 +3321,7 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s sources: ", reg_dst->freg->name)); for (i=0; i<reg_dst->nb_edges; i++) { GF_FilterRegEdge *edge = ®_dst->edgesi; - GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d(%d),%d,%d->%d)", edge->src_reg->freg->name, edge->status, edge->disabled_depth, edge->weight, edge->src_cap_idx, edge->dst_cap_idx)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d,%d,%d->%d)", edge->src_reg->freg->name, edge->status, edge->weight, edge->src_cap_idx, edge->dst_cap_idx)); } GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\n")); @@ -3093,14 +3332,43 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s sources: ", rdesc->freg->name)); for (j=0; j<rdesc->nb_edges; j++) { GF_FilterRegEdge *edge = &rdesc->edgesj; - GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d(%d),%d,%d->%d)", edge->src_reg->freg->name, edge->status, edge->disabled_depth, edge->weight, edge->src_cap_idx, edge->dst_cap_idx)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d,%d,%d->%d)", edge->src_reg->freg->name, edge->status, edge->weight, edge->src_cap_idx, edge->dst_cap_idx)); } GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\n")); } } #endif -static void gf_filter_pid_resolve_link_dijkstra(GF_FilterPid *pid, GF_Filter *dst, const char *prefRegister, Bool reconfigurable_only, GF_List *out_reg_chain) +Bool gf_filter_pid_caps_negociate_match(GF_FilterPid *pid, const GF_FilterRegister *freg) +{ + u32 idx=0; + Bool all_match = GF_TRUE; + //check all negotiated caps, and make sure they are supported by the filter + while (1) { + u32 j, p4cc; + const char *pname; + const GF_PropertyValue * p = gf_props_enum_property(pid->caps_negotiate, &idx, &p4cc, &pname); + if (!p) break; + j=0; + Bool found=GF_FALSE; + for (j=0; j<freg->nb_caps; j++) { + const GF_FilterCapability *cap = &freg->capsj; + if (! (cap->flags & GF_CAPFLAG_RECONFIG)) continue; + if ((cap->code == p4cc) + || (cap->name && pname && !strcmp(cap->name, pname)) + ) { + found = GF_TRUE; + break; + } + } + if (!found) { + all_match = GF_FALSE; + } + } + return all_match; +} + +static void gf_filter_pid_resolve_link_dijkstra(GF_FilterPid *pid, GF_Filter *dst, const char *prefRegister, Bool reconfigurable_only, GF_List *tmp_blacklist, GF_LinkInfo *link_info, GF_List *out_reg_chain) { GF_FilterRegDesc *reg_dst, *result; u32 orig_nb_bundles=0; @@ -3141,15 +3409,19 @@ //1: select all eligible filters for the graph resolution: exclude sources, sinks, explicits, blacklisted and not reconfigurable if we reconfigure count = gf_list_count(fsess->links); for (i=0; i<count; i++) { - u32 j; + u32 j, reg_flags; Bool disable_filter = GF_FALSE; Bool reconf_only = reconfigurable_only; GF_FilterRegDesc *reg_desc = gf_list_get(fsess->links, i); const GF_FilterRegister *freg = reg_desc->freg; + if (tmp_blacklist && (gf_list_find(tmp_blacklist, (void*)freg)>=0)) + continue; if (check_codec_id_raw) { Bool has_raw_out=GF_FALSE, has_non_raw_in=GF_FALSE; for (j=0; j<freg->nb_caps; j++) { + if (freg->capsj.flags & GF_CAPFLAG_RECONFIG) + break; if (!(freg->capsj.flags & GF_CAPFLAG_IN_BUNDLE)) continue; if (freg->capsj.code!=GF_PROP_PID_CODECID) continue; @@ -3178,13 +3450,18 @@ if (freg == pid->filter->freg) result = reg_desc; + //if reconfigure onlu (pid prop adaptation), check filter even if explicit only + reg_flags = freg->flags; + if (reconf_only && freg->reconfigure_output) + reg_flags &= ~GF_FS_REG_EXPLICIT_ONLY; + //don't add source filters except if PID is from source if (!freg->configure_pid && (freg!=pid->filter->freg)) { gf_assert(freg != dst->freg); disable_filter = GF_TRUE; } //freg shall be instantiated - else if ((freg->flags & (GF_FS_REG_EXPLICIT_ONLY|GF_FS_REG_SCRIPT|GF_FS_REG_CUSTOM)) && (freg != pid->filter->freg) && (freg != dst->freg) ) { + else if ((reg_flags & (GF_FS_REG_EXPLICIT_ONLY|GF_FS_REG_SCRIPT|GF_FS_REG_CUSTOM)) && (freg != pid->filter->freg) && (freg != dst->freg) ) { gf_assert(freg != dst->freg); disable_filter = GF_TRUE; } @@ -3192,11 +3469,6 @@ else if ((freg != dst->freg) && !reg_desc->has_output) { disable_filter = GF_TRUE; } - //we only want reconfigurable output filters - else if (reconf_only && !freg->reconfigure_output && (freg != dst->freg)) { - gf_assert(freg != dst->freg); - disable_filter = GF_TRUE; - } //blacklisted filter else if (gf_list_find(pid->filter->blacklisted, (void *) freg)>=0) { //this commented because not true for multi-pids inputs (tiling) to a decoder @@ -3211,12 +3483,20 @@ gf_assert(freg != dst->freg); disable_filter = GF_TRUE; } + //we only want reconfigurable output filters + else if (reconf_only && (freg != dst->freg)) { + if (!freg->reconfigure_output || !pid->caps_negotiate) + disable_filter = GF_TRUE; + else { + if (!gf_filter_pid_caps_negociate_match(pid, freg)) + disable_filter = GF_TRUE; + } + } //reset edge status for (j=0; j<reg_desc->nb_edges; j++) { GF_FilterRegEdge *edge = ®_desc->edgesj; - edge->disabled_depth = 0; if (disable_filter) { edge->status = EDGE_STATUS_DISABLED; continue; @@ -3225,7 +3505,7 @@ //connection from source, disable edge if pid caps mismatch if (edge->src_reg->freg == pid->filter->freg) { - u8 priority=0; + s16 priority=0; u32 dst_bundle_idx; //check path weight for the given dst cap - we MUST give the target cap otherwise we might get a default match to another cap path_weight = gf_filter_pid_caps_match(pid, freg, NULL, &priority, &dst_bundle_idx, pid->filter->dst_filter, edge->dst_cap_idx); @@ -3281,7 +3561,7 @@ //connection from source, disable edge if pid caps mismatch if (edge->src_reg->freg == pid->filter->freg) { - u8 priority=0; + s16 priority=0; u32 dst_bundle_idx; path_weight = gf_filter_pid_caps_match(pid, dst->freg, dst, &priority, &dst_bundle_idx, pid->filter->dst_filter, -1); if (!path_weight) { @@ -3401,7 +3681,7 @@ //compute distances for (i=0; i<current_node->nb_edges; i++) { - u8 priority=0; + s16 priority=0; GF_FilterRegEdge *redge = ¤t_node->edgesi; u32 dist; Bool do_switch = GF_FALSE; @@ -3451,10 +3731,22 @@ dijkstra_time_us = gf_sys_clock_high_res() - start_time_us; GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filters Dijkstra: sorted filters in "LLU" us, Dijkstra done in "LLU" us on %d nodes %d edges\n", sort_time_us, dijkstra_time_us, dijsktra_node_count, dijsktra_edge_count)); + if (link_info) { + link_info->distance = 0; + link_info->priority = 0; + } if (result && result->destination) { GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filters Dijkstra result: %s(%d)", result->freg->name, result->cap_idx)); + if (link_info) { + link_info->distance += result->dist; + link_info->priority += result->priority; + } result = result->destination; while (result->destination) { + if (link_info) { + link_info->distance += result->dist; + link_info->priority += result->priority; + } GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d)", result->freg->name, result->cap_idx )); gf_list_add(out_reg_chain, (void *) result->freg); gf_list_add(out_reg_chain, (void *) &result->freg->capsresult->cap_idx); @@ -3510,7 +3802,7 @@ concat_reg(pid->filter->session, prefRegister, szForceReg, dst->dst_args); gf_mx_p(fsess->links_mx); - gf_filter_pid_resolve_link_dijkstra(pid, dst, prefRegister, reconfigurable_only, filter_chain); + gf_filter_pid_resolve_link_dijkstra(pid, dst, prefRegister, reconfigurable_only, NULL, NULL, filter_chain); gf_mx_v(fsess->links_mx); count = gf_list_count(filter_chain); @@ -3561,6 +3853,10 @@ const char *args = pid->filter->orig_args ? pid->filter->orig_args : pid->filter->src_args; GF_FilterPid *a_pid = pid; GF_Filter *prev_af; + Bool use_step_link = GF_FALSE; + if (!min_chain_len) { + use_step_link = gf_opts_get_bool("core", "step-link"); + } if (skip_if_in_filter_list) { gf_assert(skipped); @@ -3626,12 +3922,14 @@ } dst_args = dst->src_args ? dst->src_args : dst->orig_args; - + //gather source args, but only until previous explicit filter while (a_pid) { GF_FilterPidInst *pidi; args = a_pid->filter->src_args; if (!args) args = a_pid->filter->orig_args; if (args) break; + if (!a_pid->filter->dynamic_filter) + break; gf_mx_p(a_pid->filter->tasks_mx); pidi = gf_list_get(a_pid->filter->input_pids, 0); gf_mx_v(a_pid->filter->tasks_mx); @@ -3675,6 +3973,9 @@ cur_bundle = 0; for (k=0; k<freg->nb_caps; k++) { cap = &freg->capsk; + if (cap->flags & GF_CAPFLAG_RECONFIG) + break; + if (cur_bundle==bundle_idx) { cap_idx = k; break; @@ -3683,9 +3984,10 @@ cur_bundle++; } } + //if first filter has multiple possible outputs, don't bother loading the entire chain since it is likely wrong //(eg demuxers, we don't know yet what's in the file) - if (!i && gf_filter_out_caps_solved_by_connection(freg, bundle_idx)) { + if (!i && (use_step_link || gf_filter_out_caps_solved_by_connection(freg, bundle_idx))) { load_first_only = GF_TRUE; } else if (i) { Bool break_chain = GF_FALSE; @@ -3714,6 +4016,25 @@ break; } } + //if first in new chain is the same as one of the existing output relink the existing output and do not load chain + //this avoids loading multiple times the same filters instead of using fanouts on dynamically loaded filter + //see examples in #2851 + if (!i && pid->num_destinations) { + u32 pidx; + GF_Filter *relink_dest_f = NULL; + for (pidx=0; pidx<pid->num_destinations; pidx++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, pidx); + if (pidi->filter->dynamic_filter && pidi->filter->freg == freg) { + relink_dest_f = pidi->filter; + break; + } + } + if (relink_dest_f) { + if (skipped) *skipped = GF_TRUE; + gf_filter_reconnect_output(relink_dest_f, NULL); + break; + } + } GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\t%s\n", freg->name)); @@ -3728,6 +4049,7 @@ if (dst_is_sink) { for (u32 cidx=0; cidx<freg->nb_caps; cidx++) { const GF_FilterCapability *a_cap = &freg->capscidx; + if (a_cap->flags & GF_CAPFLAG_RECONFIG) break; if (!(a_cap->flags & GF_CAPFLAG_IN_BUNDLE)) continue; if (!(a_cap->flags & GF_CAPFLAG_OUTPUT)) continue; if (a_cap->flags & GF_CAPFLAG_EXCLUDED) continue; @@ -3766,8 +4088,11 @@ af->source_ids = gf_strdup(dst->source_ids); //remember our target filter - if (prev_af) + if (prev_af) { gf_list_add(prev_af->destination_filters, af); + //we added dst to destination_filters (cf below) but we still want to resolve the link to the next in chain, so set dst_filter + prev_af->dst_filter = af; + } //last in chain, add dst if (i+2==count) { @@ -3834,8 +4159,9 @@ return chain_len; } +static Bool gf_filter_pid_needs_explicit_resolution(GF_FilterPid *pid, GF_Filter *dst); -GF_List *gf_filter_pid_compute_link(GF_FilterPid *pid, GF_Filter *dst) +GF_List *gf_filter_pid_compute_link(GF_FilterPid *pid, GF_Filter *dst, GF_List *tmp_blacklist, GF_LinkInfo *link_info) { GF_FilterSession *fsess = pid->filter->session; GF_List *filter_chain; @@ -3844,6 +4170,8 @@ if (!fsess->max_resolve_chain_len) return NULL; if (!dst) return NULL; + if (gf_filter_pid_needs_explicit_resolution(pid, dst)) + return NULL; filter_chain = gf_list_new(); @@ -3866,7 +4194,7 @@ concat_reg(pid->filter->session, prefRegister, szForceReg, dst->dst_args); gf_mx_p(fsess->links_mx); - gf_filter_pid_resolve_link_dijkstra(pid, dst, prefRegister, GF_FALSE, filter_chain); + gf_filter_pid_resolve_link_dijkstra(pid, dst, prefRegister, GF_FALSE, tmp_blacklist, link_info, filter_chain); gf_mx_v(fsess->links_mx); if (!gf_list_count(filter_chain)) { gf_list_del(filter_chain); @@ -4062,6 +4390,14 @@ goto skip_arg; } + if (value && !strcmp(value, "")) { + if (p4cc) + gf_filter_pid_set_property(pid, p4cc, NULL); + else + gf_filter_pid_set_property_str(pid, name, NULL); + goto skip_arg; + } + if (prop_type != GF_PROP_FORBIDDEN) { GF_PropertyValue p; p.type = GF_PROP_FORBIDDEN; @@ -4124,6 +4460,11 @@ if (p.type == GF_PROP_FORBIDDEN) { p = gf_props_parse_value(prop_type, name, value, NULL, sep_list); + //we don't allow passing const data as PID properties as we have now way of checking the described memory + if (p.type == GF_PROP_CONST_DATA) { + p.type = GF_PROP_FORBIDDEN; + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Const data (%s) is not allowed as user-assigned PID property, ignoring\n", value)); + } } if (p.type != GF_PROP_FORBIDDEN) { @@ -4180,8 +4521,10 @@ reset_prop = GF_TRUE; } } + if (reset_prop) { + gf_props_reset_single(&p); + } gf_filter_pid_set_property_dyn(pid, name, &p); - if (reset_prop) gf_props_reset_single(&p); } if (value_next_list) value_next_list0 = sep_list; @@ -4234,7 +4577,7 @@ pid->request_property_map = req_map_bck; } -static const char *gf_filter_last_id_in_chain(GF_Filter *filter, Bool ignore_first) +const char *gf_filter_last_id_in_chain(GF_Filter *filter, Bool ignore_first) { u32 i; const char *id; @@ -4278,7 +4621,7 @@ safe_int_inc(& src_pid->filter->nb_caps_renegotiate ); //disconnect source pid from filter - this will unload the filter itself - gf_fs_post_task(src_pid->filter->session, gf_filter_pid_disconnect_task, pid->filter, src_pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(src_pid->filter->session, pid->filter, src_pid); } @@ -4287,6 +4630,7 @@ u32 i; const GF_FilterCapability *caps; u32 nb_caps; + Bool in_is_unframed_encrypted = GF_FALSE; Bool dst_has_raw_cid_in = GF_FALSE; const GF_PropertyValue *stream_type = gf_filter_pid_get_property_first(pid, GF_PROP_PID_STREAM_TYPE); if (!stream_type) return GF_TRUE; @@ -4295,26 +4639,55 @@ if (stream_type->value.uint==GF_STREAM_ENCRYPTED) { stream_type = gf_filter_pid_get_property_first(pid, GF_PROP_PID_ORIG_STREAM_TYPE); if (!stream_type) return GF_TRUE; + const GF_PropertyValue *unf = gf_filter_pid_get_property_first(pid, GF_PROP_PID_UNFRAMED); + if (unf && unf->value.boolean) + in_is_unframed_encrypted = GF_TRUE; } caps = dst->forced_caps ? dst->forced_caps : dst->freg->caps; nb_caps = dst->forced_caps ? dst->nb_forced_caps : dst->freg->nb_caps; + s32 out_stream_type=0; for (i=0; i<nb_caps; i++) { const GF_FilterCapability *cap = &capsi; - if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; + //for implicit filter, check if output stream type differs from PID stream type + if (!(dst->freg->flags & GF_FS_REG_EXPLICIT_ONLY) + && (cap->flags & GF_CAPFLAG_OUTPUT) + && (cap->code == GF_PROP_PID_STREAM_TYPE) + ) { + switch (cap->val.value.uint) { + case GF_STREAM_FILE: + case GF_STREAM_ENCRYPTED: + break; + default: + if (!out_stream_type) out_stream_type = cap->val.value.uint; + //multiple output types + else if (out_stream_type!=cap->val.value.uint) out_stream_type = -1; + break; + } + } + if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; if (cap->code != GF_PROP_PID_CODECID) continue; if (cap->val.value.uint==GF_CODECID_RAW) dst_has_raw_cid_in = GF_TRUE; } + //not file, not encrypted and mismatch of stream type, we need explicit filter + if ((out_stream_type>0) && (out_stream_type!=stream_type->value.uint)) + return GF_TRUE; - + Bool has_excluded_nomatch=GF_FALSE; for (i=0; i<nb_caps; i++) { const GF_FilterCapability *cap = &capsi; if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; if (cap->code != GF_PROP_PID_STREAM_TYPE) continue; + + if (cap->flags & GF_CAPFLAG_EXCLUDED) { + if (cap->val.value.uint==stream_type->value.uint) return GF_TRUE; + has_excluded_nomatch=GF_TRUE; + } + //output type is file or same media type, allow looking for filter chains if ((cap->val.value.uint==GF_STREAM_FILE) || (cap->val.value.uint==stream_type->value.uint)) return GF_FALSE; //allow text|scene|video -> raw video for dynamic compositor @@ -4329,6 +4702,10 @@ } } } + if (has_excluded_nomatch) return GF_FALSE; + //if input is unframed and encrypted, allow implicit filter for reframing + if (in_is_unframed_encrypted) return GF_FALSE; + //no mathing type found, we will need an explicit filter to solve this link (ie the link will be to the explicit filter) return GF_TRUE; } @@ -4437,17 +4814,18 @@ u32 num_pass=0; GF_List *loaded_filters = NULL; GF_List *linked_dest_filters = NULL; - GF_List *force_link_resolutions = NULL; - GF_List *possible_linked_resolutions = NULL; + GF_List *force_link_resolutions = NULL; + GF_List *possible_linked_resolutions = NULL; GF_Filter *filter = task->filter; GF_FilterPid *pid = task->pid; GF_Filter *dynamic_filter_clone = NULL; Bool filter_found_but_pid_excluded = GF_FALSE; Bool possible_link_found_implicit_mode = GF_FALSE; + Bool is_fake = GF_FALSE; u32 pid_is_file = 0; const char *filter_id; - if (pid->destroyed || pid->removed) { + if (pid->destroyed || pid->removed || pid->filter->finalized || pid->filter->removed) { gf_assert(pid->init_task_pending); safe_int_dec(&pid->init_task_pending); return; @@ -4480,6 +4858,11 @@ pid_is_file = 1; } + const GF_PropertyValue *p = gf_filter_pid_get_property(pid, GF_PROP_PID_FAKE); + if (p && p->value.boolean) { + is_fake = GF_TRUE; + } + //get filter ID: //this is a source - since we may have inserted filters in the middle (demuxers typically), get the last explicitly loaded ID in the chain if (filter->subsource_id) { @@ -4504,7 +4887,7 @@ linked_dest_filters = gf_list_new(); force_link_resolutions = gf_list_new(); - possible_linked_resolutions = gf_list_new(); + possible_linked_resolutions = gf_list_new(); GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s:%s init\n", pid->filter->name, pid->name)); @@ -4567,6 +4950,7 @@ if (!filter_dst->freg->configure_pid) continue; if (filter_dst->finalized || filter_dst->removed || filter_dst->disabled || filter_dst->marked_for_removal || filter_dst->no_inputs) continue; if (filter_dst->target_filter == pid->filter) continue; + if (filter_dst == pid->filter) continue; //PID requires a sourceID on target filter and none is set, ignore if (pid->require_source_id && !filter_dst->source_ids) { GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s requires source ID, not set for filter %s\n", pid->name, filter_dst->name)); @@ -4622,11 +5006,20 @@ u32 j; Bool already_linked = GF_FALSE; for (j=0; j<pid->num_destinations; j++) { + //check direct connections GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); if (pidi->filter == filter_dst) { already_linked=GF_TRUE; break; } + //check indirect connections + gf_mx_v(filter->session->filters_mx); + in_parent_chain = gf_filter_in_parent_chain(filter_dst, pidi->filter); + RELOCK_FILTER_LIST + if (in_parent_chain) { + already_linked=GF_TRUE; + break; + } } if (already_linked) continue; } @@ -4640,13 +5033,19 @@ s32 ours = gf_list_find(pid->filter->destination_filters, filter_dst); if (ours<0) { ours = num_pass ? gf_list_del_item(pid->filter->destination_links, filter_dst) : -1; - if (!filter_dst->source_ids && (ours<0)) { + if ((ours<0) && ( + //no source ID on filter, exclude + !filter_dst->source_ids + //dst is already linked and cannot accept more inputs, don't try to connect (this would trigger a clone) + //this is typically needed with defer linking when we reconnect outputs of a filter already connected + || (filter_dst->num_input_pids && !filter_dst->max_extra_pids) + )) { GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has destination filters, filter %s not one of them\n", pid->name, filter_dst->name)); continue; } pid->filter->dst_filter = NULL; - } else { + } else if (!pid->filter->dst_filter) { filter_dst->in_link_resolution = 0; pid->filter->dst_filter = filter_dst; //for mux->output case, the filter ID may be NULL but we still want to link @@ -4728,6 +5127,10 @@ if (pid->link_flags & PID_DISABLE_CLONE) continue; + //filter requires source IDs, do not allow cloning + if (filter_dst->require_source_id) + continue; + //explicitly clonable but caps don't match, don't connect to it if (!gf_filter_pid_caps_match(pid, filter_dst->freg, filter_dst, NULL, NULL, pid->filter->dst_filter, -1)) { GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s caps does not match clonable filter %s\n", pid->name, filter_dst->name)); @@ -4821,11 +5224,11 @@ gf_list_del_item(force_link_resolutions, filter_dst); for (k=0; k<gf_list_count(filter_dst->destination_links); k++) { GF_Filter *a_dst = gf_list_get(filter_dst->destination_links, k); - gf_list_del_item(force_link_resolutions, a_dst); + gf_list_del_item(force_link_resolutions, a_dst); } for (k=0; k<gf_list_count(filter_dst->destination_filters); k++) { GF_Filter *a_dst = gf_list_get(filter_dst->destination_filters, k); - gf_list_del_item(force_link_resolutions, a_dst); + gf_list_del_item(force_link_resolutions, a_dst); } continue; } @@ -4883,7 +5286,7 @@ if (filter_id) { if (filter_dst->source_ids) { Bool pid_excluded=GF_FALSE; - if (!filter_source_id_match(pid, filter_id, filter_dst, &pid_excluded, &needs_clone)) { + if (!filter_source_id_match(pid, filter_id, filter_dst, &pid_excluded, &needs_clone, NULL)) { Bool not_ours=GF_TRUE; //if filter is a dynamic one with an ID set, fetch ID from previous filter in chain //this is need for cases such as "-i source filterFoo @ -o live.mpd": @@ -4893,7 +5296,7 @@ //which is the filter ID of filterFoo if (filter->dynamic_filter && filter->id) { const char *src_filter_id = gf_filter_last_id_in_chain(filter, GF_TRUE); - if (filter_source_id_match(pid, src_filter_id, filter_dst, &pid_excluded, &needs_clone)) { + if (filter_source_id_match(pid, src_filter_id, filter_dst, &pid_excluded, &needs_clone, NULL)) { not_ours = GF_FALSE; } } @@ -4914,6 +5317,10 @@ use_explicit_link = GF_TRUE; } //if no source ID on the dst filter, this means the dst filter accepts any possible connections from out filter + else if (filter->subsession_id != filter_dst->subsession_id) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s and filter %s not in same subsession and no links directive\n", pid->name, filter_dst->name)); + continue; + } #if 0 //this is now checked above regardless of whether a filterID is set //unless prevented for this pid @@ -4933,7 +5340,7 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s does not match filter %s source ID\n", pid->name, filter_dst->name)); continue; } - if (!filter_source_id_match(pid, "*", filter_dst, &pid_excluded, &needs_clone)) { + if (!filter_source_id_match(pid, "*", filter_dst, &pid_excluded, &needs_clone, NULL)) { if (pid_excluded && !num_pass) filter_found_but_pid_excluded = GF_TRUE; GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s is excluded by filter %s source ID\n", pid->name, filter_dst->name)); continue; @@ -4994,11 +5401,12 @@ GF_Err e; GF_Filter *f = gf_fs_load_filter(filter->session, "reframer", &e); gf_filter_set_id(f, filter_dst->id); - gf_filter_set_name(f, filter_dst->name); + if (strcmp(filter_dst->name, filter_dst->freg->name)) + gf_filter_set_name(f, filter_dst->name); if (filter_dst->source_ids) f->source_ids = gf_strdup(filter_dst->source_ids); f->cloned_from = filter_dst; f->max_extra_pids = 0; - filter_dst->filter_skiped = GF_TRUE; + filter_dst->filter_skipped = GF_TRUE; filter_dst = f; } } @@ -5006,6 +5414,8 @@ //we have a match, check if caps are OK cap_matched = gf_filter_pid_caps_match(pid, filter_dst->freg, filter_dst, NULL, NULL, pid->filter->dst_filter, -1); + if (!cap_matched && is_fake) continue; + //dst filter forces demuxing, pid is file and caps matched, do not test and do not activate link resolution //if can_try_link_resolution is still false at end of pass one, we will insert a reframer if (cap_matched && filter_dst->force_demux && pid_is_file) { @@ -5017,6 +5427,15 @@ pid_is_file = 2; continue; } + //a connection to a sink dst has already been made requiring a filter chain and this pid is from a source filter + //we force demultiplexing - this avoids (see #3207) the following: + //gpac -i HTTP1 -i HTTP2 -o HTPOUT/live.mpd + //in which case HTTP2->httpout could match directly as httpout can accept any FILE connection + else if (is_sink && cap_matched && (filter_dst->force_demux==3) && !pid->filter->num_input_pids + ) { + pid_is_file = 2; + continue; + } can_try_link_resolution = GF_TRUE; @@ -5056,9 +5475,25 @@ //otherwise we could link directly to destination (due to caps mismatch) while a valid path could be found to a previously //specified filter //see testsuite restamp-fps - if (!num_pass && possible_link_found_implicit_mode - && (is_sink || !filter_dst->dynamic_filter || !(filter_dst->freg->flags & GF_FS_REG_DYNAMIC_REUSE)) ) { - cap_matched = GF_FALSE; + if (!num_pass && possible_link_found_implicit_mode && cap_matched + && (is_sink || !filter_dst->dynamic_filter || !(filter_dst->freg->flags & GF_FS_REG_DYNAMIC_REUSE)) + ) { + Bool do_skip = GF_TRUE; + //if the tested destination filter accepts several pid: + //- check if this filter's destination links contain one of the possible destinations + //- if so check if the tested destination filter has the possible destination as a target + //- if so accept linking to that filter + //this is needed when solving muxers in-middle of a chain, eg -i MP4 tsgendts -o TS + u32 dstidx, nb_imp_dst = filter_dst->max_extra_pids ? gf_list_count(possible_linked_resolutions) : 0; + for (dstidx=0; dstidx<nb_imp_dst; dstidx++) { + GF_Filter *possible_dst = gf_list_get(possible_linked_resolutions, dstidx); + if (gf_list_find(filter->destination_links, possible_dst)<0) continue; + if (gf_list_find(filter_dst->destination_filters, possible_dst)<0) continue; + do_skip = GF_FALSE; + break; + } + if (do_skip) + cap_matched = GF_FALSE; } if (!cap_matched) { @@ -5093,12 +5528,12 @@ } } if (!num_pass) { - //we have an explicit link instruction so we must try dynamic link even if we connect to another filter - //is_sink set, same thing (implicit mode only, force link to sink) + //we have an explicit link instruction so we must try dynamic link even if we connect to another filter + //is_sink set, same thing (implicit mode only, force link to sink) if (filter_dst->source_ids || (is_sink && !implicit_link_found)) { - gf_list_add(force_link_resolutions, filter_dst); - //! filter is an alias, prevent linking to the filter being aliased - if (filter_dst->multi_sink_target) { + gf_list_add(force_link_resolutions, filter_dst); + //! filter is an alias, prevent linking to the filter being aliased + if (filter_dst->multi_sink_target) { gf_list_del_item(force_link_resolutions, filter_dst->multi_sink_target); gf_list_add(linked_dest_filters, filter_dst->multi_sink_target); } @@ -5142,8 +5577,8 @@ safe_int_dec(&pid->init_task_pending); if (loaded_filters) gf_list_del(loaded_filters); gf_list_del(linked_dest_filters); - gf_list_del(force_link_resolutions); - gf_list_del(possible_linked_resolutions); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); return; } @@ -5158,8 +5593,8 @@ safe_int_dec(&pid->init_task_pending); if (loaded_filters) gf_list_del(loaded_filters); gf_list_del(linked_dest_filters); - gf_list_del(force_link_resolutions); - gf_list_del(possible_linked_resolutions); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); return; } //we might had it wrong solving the chain initially, break the chain @@ -5182,8 +5617,8 @@ safe_int_dec(&pid->init_task_pending); if (loaded_filters) gf_list_del(loaded_filters); gf_list_del(linked_dest_filters); - gf_list_del(force_link_resolutions); - gf_list_del(possible_linked_resolutions); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); return; } else { continue; @@ -5195,6 +5630,10 @@ } } + //demux was needed on a filter using alias (httpout, routeout, ...), force a demux for all further resolution + if (is_sink && !filter_dst->force_demux && filter_dst->freg->use_alias) + filter_dst->force_demux = 3; + //in implicit link, if target is not here push it (we have no SID/FID to solve that later) if ((filter->session->flags & GF_FS_FLAG_IMPLICIT_MODE) && !gf_list_count(new_f->destination_filters) @@ -5222,8 +5661,8 @@ gf_assert(pid->pid->filter->freg != filter_dst->freg); } - safe_int_inc(&pid->filter->out_pid_connection_pending); gf_mx_p(filter_dst->tasks_mx); + safe_int_inc(&pid->filter->out_pid_connection_pending); gf_list_add(filter_dst->temp_input_pids, pid); if (pid->filter != filter_dst->single_source) filter_dst->single_source = NULL; @@ -5261,7 +5700,7 @@ } if (pid->link_flags & PID_FORCE_SINGLE) break; - } + } if (!num_pass) { u32 i, k; @@ -5323,21 +5762,42 @@ num_pass = 1; goto restart; } - //we must do the second pass if a filter has an explicit link set through source ID + + //special case: if we found a destination, ignore any force_link filter that is an alias filter and has a matching sourceID + //This is needed because a filter calling gf_filter_set_source on a alias filter + //will never modify the sourceID of the original (non-alias) filter + //eg ... dasher -> scte35dec(injected) -> httpout(alias) + if (!num_pass && found_dest && pid->filter->id && gf_list_count(force_link_resolutions)) { + u32 i=0; + for (i=0; i<gf_list_count(force_link_resolutions); i++) { + Bool pid_excluded=GF_FALSE; + Bool needs_clone=GF_FALSE; + GF_Filter *force_link = gf_list_get(force_link_resolutions, i); + if (force_link->source_ids + && force_link->freg + && force_link->freg->use_alias + && filter_source_id_match(pid, pid->filter->id, force_link, &pid_excluded, &needs_clone, NULL) + ) { + gf_list_rem(force_link_resolutions, i); + i--; + } + } + } + //we must do the second pass if a filter has an explicit link set through source ID if (!num_pass && gf_list_count(force_link_resolutions)) { num_pass = 1; goto restart; } - //connection task posted, nothing left to do + //connection task posted, nothing left to do if (found_dest) { gf_assert(pid->init_task_pending); safe_int_dec(&pid->init_task_pending); gf_mx_v(filter->session->filters_mx); pid->filter->disabled = GF_FILTER_ENABLED; gf_list_del(linked_dest_filters); - gf_list_del(force_link_resolutions); - gf_list_del(possible_linked_resolutions); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); gf_fs_check_graph_load(filter->session, GF_FALSE); if (pid->not_connected) { pid->not_connected = 0; @@ -5400,8 +5860,8 @@ gf_fs_check_graph_load(filter->session, GF_FALSE); gf_list_del(linked_dest_filters); - gf_list_del(force_link_resolutions); - gf_list_del(possible_linked_resolutions); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); gf_mx_v(filter->session->filters_mx); if (pid->num_destinations && !pid->not_connected) { @@ -5417,6 +5877,12 @@ pid->is_sparse = 0; } + if (is_fake) { + pid->not_connected = 1; + gf_filter_pid_set_eos(pid); + safe_int_dec(&pid->init_task_pending); + return; + } GF_FilterEvent evt; if (filter_found_but_pid_excluded) { //PID was not included in explicit connection lists @@ -5434,11 +5900,16 @@ return; } - GF_LOG(pid->not_connected_ok ? GF_LOG_DEBUG : GF_LOG_WARNING, GF_LOG_FILTER, ("No filter chain found for PID %s in filter %s to any loaded filters - NOT CONNECTED\n", pid->name, pid->filter->name)); - + if (!(filter->session->flags & GF_FS_FLAG_FORCE_DEFER_LINK) && !filter->deferred_link) { + const char *msg = (pid->filter->num_input_pids==1) ? "Will try to remove" : "NOT CONNECTED"; + GF_LOG((pid->filter->is_pid_adaptation_filter || pid->not_connected_ok) ? GF_LOG_DEBUG : GF_LOG_WARNING, GF_LOG_FILTER, ("No filter chain found for PID %s in filter %s to any loaded filters - %s\n", pid->name, pid->filter->name, msg)); + } if (pid->filter->freg->process_event) { GF_FEVT_INIT(evt, GF_FEVT_CONNECT_FAIL, pid); + + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); pid->filter->freg->process_event(filter, &evt); + gf_logs_thread_untag(filter); } pid->not_connected = 1; } @@ -5451,6 +5922,7 @@ gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); gf_filter_pid_set_eos(pid); + Bool remove_filter = GF_FALSE; if (!pid->not_connected_ok && !parent_chain_has_dyn_pids(pid->filter) && (pid->filter->num_out_pids_not_connected == pid->filter->num_output_pids) @@ -5460,6 +5932,7 @@ if (can_reassign_filter) { gf_filter_setup_failure(pid->filter, GF_FILTER_NOT_FOUND); } + if (pid->filter->num_input_pids==1) remove_filter = GF_TRUE; } if (!filter_found_but_pid_excluded && !pid->not_connected_ok && !filter->session->max_resolve_chain_len) { @@ -5468,7 +5941,19 @@ gf_assert(pid->init_task_pending); safe_int_dec(&pid->init_task_pending); - return; + + if (remove_filter) { + while (1) { + GF_FilterPidInst *pidi = gf_list_get(pid->filter->input_pids, 0); + if (!pidi->pid) break; + gf_fs_post_disconnect_task(filter->session, pid->filter, pidi->pid); + if ((pidi->pid->filter->num_input_pids==1) && (pidi->pid->filter->num_output_pids==1)) { + pid = pidi->pid; + continue; + } + break; + } + } } void gf_filter_pid_post_connect_task(GF_Filter *filter, GF_FilterPid *pid) @@ -5479,9 +5964,14 @@ gf_assert(pid->filter->freg != filter->freg); } gf_assert(filter->freg->configure_pid); + gf_assert(!pid->filter->finalized); + gf_assert(!pid->filter->removed); + gf_assert(!pid->removed); safe_int_inc(&filter->session->pid_connect_tasks_pending); + gf_mx_p(filter->tasks_mx); safe_int_inc(&filter->in_pid_connection_pending); - gf_fs_post_task_ex(filter->session, gf_filter_pid_connect_task, filter, pid, "pid_connect", NULL, GF_TRUE, GF_FALSE, GF_FALSE, TASK_TYPE_NONE); + gf_mx_v(filter->tasks_mx); + gf_fs_post_task_ex(filter->session, gf_filter_pid_connect_task, filter, pid, "pid_connect", NULL, GF_TRUE, GF_FALSE, GF_FALSE, TASK_TYPE_NONE, 0); } @@ -5496,7 +5986,7 @@ //for complex chains involving a lot of filters (typically gui loading icons) Bool force_main_thread = GF_TRUE; - gf_fs_post_task_ex(filter->session, gf_filter_pid_init_task, filter, pid, "pid_init", NULL, GF_FALSE, force_main_thread, GF_FALSE, TASK_TYPE_NONE); + gf_fs_post_task_ex(filter->session, gf_filter_pid_init_task, filter, pid, "pid_init", NULL, GF_FALSE, force_main_thread, GF_FALSE, TASK_TYPE_NONE, 0); } GF_EXPORT @@ -5517,8 +6007,14 @@ { char szName30; GF_FilterPid *pid; + if (!filter) return NULL; + gf_mx_p(filter->tasks_mx); + GF_SAFEALLOC(pid, GF_FilterPid); - if (!pid) return NULL; + if (!pid) { + gf_mx_v(filter->tasks_mx); + return NULL; + } pid->filter = filter; pid->destinations = gf_list_new(); pid->properties = gf_list_new(); @@ -5537,7 +6033,6 @@ filter->has_pending_pids = GF_TRUE; gf_fq_add(filter->pending_pids, pid); - gf_mx_p(filter->tasks_mx); //by default copy properties if only one input pid if (filter->num_input_pids==1) { GF_FilterPid *pidi = gf_list_get(filter->input_pids, 0); @@ -5548,6 +6043,7 @@ return pid; } +GF_NOT_EXPORTED void gf_filter_pid_del(GF_FilterPid *pid) { GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s destruction (%p)\n", pid->filter->name, pid->name, pid)); @@ -5657,6 +6153,31 @@ } if (prop_4cc) { + if (value && pid->filter->session->check_props) { + u32 ptype = gf_props_4cc_get_type(prop_4cc); + u8 c = prop_4cc>>24; + if ((c>='A') && (c<='Z')) { + if (gf_props_get_base_type(ptype) != gf_props_get_base_type(value->type)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Assigning PID property %s of type %s in filter %s but expecting %s\n", + gf_props_4cc_get_name(prop_4cc), + gf_props_get_type_name(value->type), + pid->filter->freg->name, + gf_props_get_type_name(ptype) + )); + if (gf_sys_is_test_mode()) + exit(5); + } + u32 flags = gf_props_4cc_get_flags(prop_4cc); + if (flags & GF_PROP_FLAG_PCK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Assigning PID property %s in filter %s but this is a packet property\n", + gf_props_4cc_get_name(prop_4cc), + pid->filter->freg->name + )); + if (gf_sys_is_test_mode()) + exit(5); + } + } + } oldp = gf_filter_pid_get_property(pid, prop_4cc); } else { oldp = gf_filter_pid_get_property_str(pid, prop_name ? prop_name : dyn_name); @@ -5903,12 +6424,29 @@ #define pid_check_prop(_a, _b, _str, c) c #endif +static void check_prop_type(GF_FilterPid *pid, u32 prop_4cc) +{ + u32 flags = gf_props_4cc_get_flags(prop_4cc); + if (flags & GF_PROP_FLAG_PCK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Fetching PID property %s in filter %s but this is a packet property\n", + gf_props_4cc_get_name(prop_4cc), + pid->filter->freg->name + )); + if (gf_sys_is_test_mode()) + exit(5); + } +} + GF_EXPORT const GF_PropertyValue *gf_filter_pid_get_property(GF_FilterPid *pid, u32 prop_4cc) { GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_FALSE); if (!map) return NULL; + + if (pid->filter->session->check_props && gf_props_4cc_get_type(prop_4cc)) { + check_prop_type(pid, prop_4cc); + } return pid_check_prop(pid, prop_4cc, NULL, gf_props_get_property(map, prop_4cc, NULL) ); } @@ -5917,6 +6455,10 @@ GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_TRUE); if (!map) return NULL; + + if (pid->filter->session->check_props && gf_props_4cc_get_type(prop_4cc)) { + check_prop_type(pid, prop_4cc); + } return pid_check_prop(pid, prop_4cc, NULL, gf_props_get_property(map, prop_4cc, NULL) ); } @@ -5970,6 +6512,9 @@ prop_ent = gf_props_get_property_entry(map, prop_4cc, prop_name); if (prop_ent) goto exit; } + //detached + if (!pid->pid) goto exit; + if (pid->pid->infos) { prop_ent = gf_props_get_property_entry(pid->pid->infos, prop_4cc, prop_name); if (prop_ent) goto exit; @@ -6058,6 +6603,10 @@ *idx = cur_idx; return prop; } + if (!pid->filter->num_input_pids) { + *idx = cur_idx; + return NULL; + } nb_in_pid = cur_idx; cur_idx = *idx - nb_in_pid; //remember we checked that pid to avoid counting it twice (demuxers...) @@ -6065,7 +6614,9 @@ gf_list_add(*list, pid); } - if (!pid->filter->num_input_pids) return NULL; + if (!pid->filter->num_input_pids) { + return NULL; + } gf_mx_p(pid->filter->tasks_mx); for (i=0; i<pid->filter->num_input_pids; i++) { @@ -6089,6 +6640,7 @@ cur_idx = *idx - nb_in_pid; } gf_mx_v(pid->filter->tasks_mx); + *idx = nb_in_pid; return NULL; } GF_EXPORT @@ -6283,7 +6835,9 @@ safe_int_dec(&pcki->pid->nb_eos_signaled); is_internal = GF_TRUE; } else if (ctype == GF_PCK_CMD_PID_REM) { - gf_fs_post_task(pidi->filter->session, gf_filter_pid_disconnect_task, pidi->filter, pidi->pid, "pidinst_disconnect", NULL); + safe_int_dec(&pidi->filter->session->remove_tasks); + safe_int_dec(&pidi->pid->filter->pid_rem_packet_pending); + gf_fs_post_disconnect_task(pidi->filter->session, pidi->filter, pidi->pid); is_internal = GF_TRUE; } @@ -6356,7 +6910,8 @@ } } } - if (!skip_props) { + //pidinst->filter may be null if graph is being reconfigured + if (!skip_props && pidinst->filter) { if (do_notif) { GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s property changed at this packet, triggering reconfigure\n", pidinst->pid->filter->name, pidinst->pid->name)); @@ -6444,7 +6999,9 @@ //the following may fail when some filters use threading on their own //FSESS_CHECK_THREAD(pidinst->filter) + gf_logs_thread_tag(pidinst->filter, GF_LOG_TAG_FILTER); res = pidinst->filter->freg->process_event(pidinst->filter, &evt); + gf_logs_thread_untag(pidinst->filter); } if (!res) { @@ -6664,6 +7221,10 @@ GF_FilterPacketInstance *pcki; GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + if (!pid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to discard a packet on a NULL PID\n")); + return; + } if (PID_IS_OUTPUT(pid)) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to discard a packet on an output PID in filter %s\n", pid->filter->name)); return; @@ -6673,7 +7234,13 @@ //remove pck instance pcki = gf_fq_pop(pidinst->packets); - +#if 0 + if ((pcki == (void *)0x000060300004a9b0) + && !( + (pcki->pck->info.flags & GF_PCK_CMD_MASK) || ((pcki->pck->info.flags & GF_PCK_CKTYPE_MASK) >> GF_PCK_CKTYPE_POS) + )) + pcki=pcki; +#endif if (!pcki) { if (pidinst->filter && !pidinst->filter->finalized && !pidinst->discard_packets) { GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Attempt to discard a packet already discarded in filter %s\n", pid->filter->name)); @@ -6681,7 +7248,6 @@ return; } - gf_rmt_begin(pck_drop, GF_RMT_AGGREGATE); pck = pcki->pck; //move to source pid pid = pid->pid; @@ -6702,13 +7268,14 @@ //make sure we lock the tasks mutex before getting the packet count, otherwise we might end up with a wrong number of packets - //if one thread (the caller here) consumes one packet while the dispatching thread is still upddating the state for that pid + //if one thread (the caller here) consumes one packet while the dispatching thread is still updating the state for that pid gf_mx_p(pid->filter->tasks_mx); nb_pck = gf_fq_count(pidinst->packets); - if (!nb_pck) { - safe_int64_sub(&pidinst->buffer_duration, pidinst->buffer_duration); - } else if (pck->info.duration && (pck->info.flags & GF_PCKF_BLOCK_START) && timescale) { + //always substract duration, do not reset to 0 if no more packets for multithreaded cases: + //we may have 0 packets in the queue but the buffer_duration can already include the next packet to be added (multithreaded mode) + //since we don't lock the mutex when updating duration + if (pck->info.duration && (pck->info.flags & GF_PCKF_BLOCK_START) && timescale) { s64 d = gf_timestamp_rescale(pck->info.duration, timescale, 1000000); if (d > pidinst->buffer_duration) { GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Corrupted buffer level in PID instance %s (%s -> %s), dropping packet duration "LLD" us greater than buffer duration "LLU" us\n", pid->name, pid->filter->name, pidinst->filter ? pidinst->filter->name : "disconnected", d, pidinst->buffer_duration)); @@ -6801,7 +7368,6 @@ gf_filter_forward_clock(pidinst->filter); } - gf_rmt_end(); } GF_EXPORT @@ -6851,8 +7417,7 @@ } -GF_EXPORT -void gf_filter_pid_set_eos(GF_FilterPid *pid) +static void gf_filter_pid_set_eos_internal(GF_FilterPid *pid, Bool is_flush) { GF_FilterPacket *pck; //allow NULL as input (most filters blindly call set_eos on output even if no output) @@ -6878,6 +7443,8 @@ } gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); pck->pck->info.flags |= GF_PCK_CMD_PID_EOS; + if (is_flush) + pck->pck->info.flags |= GF_PCKF_IS_FLUSH; gf_filter_pck_send(pck); } @@ -6894,6 +7461,12 @@ } GF_EXPORT +void gf_filter_pid_set_eos(GF_FilterPid *pid) +{ + gf_filter_pid_set_eos_internal(pid, GF_FALSE); +} + +GF_EXPORT const GF_PropertyValue *gf_filter_pid_enum_properties(GF_FilterPid *pid, u32 *idx, u32 *prop_4cc, const char **prop_name) { GF_PropertyMap *props; @@ -6935,6 +7508,8 @@ //input pid(s) are being flushed, prevent blocking if (pid->filter->in_force_flush) return GF_FALSE; + if (pid->not_connected) + return GF_FALSE; gf_mx_p(pid->filter->tasks_mx); //either block according to the number of dispatched units (decoder output) or to the requested buffer duration @@ -7158,8 +7733,10 @@ case GF_FEVT_CONNECT_FAIL: return "CONNECT_FAIL"; case GF_FEVT_FILE_DELETE: return "FILE_DELETE"; case GF_FEVT_PLAY_HINT: return "PLAY_HINT"; - case GF_FEVT_ENCODE_HINTS: return "ENCODE_HINTS"; + case GF_FEVT_TRANSPORT_HINTS: return "TRANSPORT_HINTS"; case GF_FEVT_NTP_REF: return "NTP_REF"; + case GF_FEVT_NETWORK_HINT: return "NETWORK_HINT"; + case GF_FEVT_DASH_QUALITY_SELECT: return "QUALITY_SELECT"; default: return "UNKNOWN"; } @@ -7232,71 +7809,123 @@ #define TO_REFSTRING(_v) _v ? (GF_RefString *) (_v - offsetof(GF_RefString, string)) : NULL -static GF_RefString *evt_get_refstr(GF_FilterEvent *evt) +static Bool evt_get_refstr(GF_FilterEvent *evt, u32 r_idx, GF_RefString **ref_str) { if (evt->base.type == GF_FEVT_FILE_DELETE) { - return TO_REFSTRING(evt->file_del.url); + if (r_idx) return GF_FALSE; + *ref_str = TO_REFSTRING(evt->file_del.url); + return GF_TRUE; } if (evt->base.type == GF_FEVT_SOURCE_SWITCH) { - return TO_REFSTRING(evt->seek.source_switch); + if (r_idx) return GF_FALSE; + *ref_str = TO_REFSTRING(evt->seek.source_switch); + return GF_TRUE; } if (evt->base.type == GF_FEVT_SEGMENT_SIZE) { - return TO_REFSTRING(evt->seg_size.seg_url); + if (!r_idx) + *ref_str = TO_REFSTRING(evt->seg_size.seg_url); + else if (r_idx==1) + *ref_str = TO_REFSTRING(evt->seg_size.base64_version); + else + return GF_FALSE; + return GF_TRUE; } - return NULL; + if (evt->base.type == GF_FEVT_DASH_QUALITY_SELECT) { + if (!r_idx) + *ref_str = TO_REFSTRING(evt->dash_select.period_id); + else if (r_idx==1) + *ref_str = TO_REFSTRING(evt->dash_select.rep_id); + else + return GF_FALSE; + return GF_TRUE; + } + return GF_FALSE; } static GF_FilterEvent *dup_evt(GF_FilterEvent *evt) { GF_FilterEvent *an_evt; - GF_RefString *rstr = evt_get_refstr(evt); an_evt = gf_malloc(sizeof(GF_FilterEvent)); memcpy(an_evt, evt, sizeof(GF_FilterEvent)); - if (rstr) { - safe_int_inc(&rstr->ref_count); + u32 i=0; + while (1) { + GF_RefString *rstr; + if (!evt_get_refstr(evt, i, &rstr)) break; + if (rstr) + safe_int_inc(&rstr->ref_count); + i++; } return an_evt; } static void free_evt(GF_FilterEvent *evt) { - GF_RefString *rstr = evt_get_refstr(evt); - if (rstr) { - gf_assert(rstr->ref_count); - if (safe_int_dec(&rstr->ref_count) == 0) { - gf_free(rstr); + u32 i=0; + while (1) { + GF_RefString *rstr; + if (!evt_get_refstr(evt, i, &rstr)) break; + if (rstr) { + gf_assert(rstr->ref_count); + if (safe_int_dec(&rstr->ref_count) == 0) { + gf_free(rstr); + } } + i++; } gf_free(evt); } static GF_FilterEvent *init_evt(GF_FilterEvent *evt) { - char **url_addr_src = NULL; - char **url_addr_dst = NULL; GF_FilterEvent *an_evt = gf_malloc(sizeof(GF_FilterEvent)); memcpy(an_evt, evt, sizeof(GF_FilterEvent)); - - if (evt->base.type==GF_FEVT_FILE_DELETE) { - url_addr_src = (char **) &evt->file_del.url; - url_addr_dst = (char **) &an_evt->file_del.url; - } else if (evt->base.type==GF_FEVT_SOURCE_SWITCH) { - url_addr_src = (char **) &evt->seek.source_switch; - url_addr_dst = (char **) &an_evt->seek.source_switch; - } else if (evt->base.type==GF_FEVT_SEGMENT_SIZE) { - url_addr_src = (char **) &evt->seg_size.seg_url; - url_addr_dst = (char **) &an_evt->seg_size.seg_url; - } - if (url_addr_src) { - char *url = *url_addr_src; - if (!url) { - *url_addr_dst = NULL; + u32 i=0; + while (1) { + char **url_addr_src = NULL; + char **url_addr_dst = NULL; + if (evt->base.type==GF_FEVT_FILE_DELETE) { + if (i) break; + url_addr_src = (char **) &evt->file_del.url; + url_addr_dst = (char **) &an_evt->file_del.url; + } else if (evt->base.type==GF_FEVT_SOURCE_SWITCH) { + if (i) break; + url_addr_src = (char **) &evt->seek.source_switch; + url_addr_dst = (char **) &an_evt->seek.source_switch; + } else if (evt->base.type==GF_FEVT_SEGMENT_SIZE) { + if (!i) { + url_addr_src = (char **) &evt->seg_size.seg_url; + url_addr_dst = (char **) &an_evt->seg_size.seg_url; + } else if (i==1) { + url_addr_src = (char **) &evt->seg_size.base64_version; + url_addr_dst = (char **) &an_evt->seg_size.base64_version; + } else { + break; + } + } else if (evt->base.type==GF_FEVT_DASH_QUALITY_SELECT) { + if (!i) { + url_addr_src = (char **) &evt->dash_select.period_id; + url_addr_dst = (char **) &an_evt->dash_select.period_id; + } else if (i==1) { + url_addr_src = (char **) &evt->dash_select.rep_id; + url_addr_dst = (char **) &an_evt->dash_select.rep_id; + } else { + break; + } } else { - u32 len = (u32) strlen(url); - GF_RefString *rstr = gf_malloc(sizeof(GF_RefString) + sizeof(char)*len); - rstr->ref_count=1; - strcpy( (char *) &rstr->string0, url); - *url_addr_dst = (char *) &rstr->string0; + break; } + if (url_addr_src) { + char *url = *url_addr_src; + if (!url) { + *url_addr_dst = NULL; + } else { + u32 len = (u32) strlen(url); + GF_RefString *rstr = gf_malloc(sizeof(GF_RefString) + sizeof(char)*len); + rstr->ref_count=1; + strcpy( (char *) &rstr->string0, url); + *url_addr_dst = (char *) &rstr->string0; + } + } + i++; } return an_evt; } @@ -7407,6 +8036,7 @@ //update number of playing/paused pids for (i=0; i<pid->num_destinations; i++) { GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); + if (!pidi) continue; if (pidi->is_playing) nb_playing++; if (pidi->is_paused) nb_paused++; } @@ -7519,7 +8149,10 @@ if (f->freg->process_event) { FSESS_CHECK_THREAD(f) + + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); canceled = f->freg->process_event(f, evt); + gf_logs_thread_untag(f); } if (!canceled && (evt->base.type==GF_FEVT_STOP) && evt->play.forced_dash_segment_switch) { GF_FilterPidInst *pid_inst = gf_list_get(f->input_pids, 0); @@ -7655,6 +8288,9 @@ GF_FilterPidInst *pid_inst = gf_list_get(f->input_pids, i); GF_FilterPid *pid = pid_inst->pid; if (!pid) continue; + //PID instance is about to be removed due to chain swap, ignore event + if (pid_inst->swap_source) + continue; if (dispatched_filters) { if (gf_list_find(dispatched_filters, pid_inst->pid->filter) >=0 ) @@ -7673,6 +8309,8 @@ safe_int_inc(&pid->filter->num_events_queued); + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s queuing downstream event %s\n", pid->filter->name, pid->pid->name, gf_filter_event_name(evt->base.type))); + gf_fs_post_task_class(pid->filter->session, gf_filter_pid_send_event_downstream, pid->filter, task->pid ? (GF_FilterPid *) pid_inst : NULL, "downstream_event", an_evt, TASK_TYPE_EVENT); } gf_mx_v(f->tasks_mx); @@ -7693,7 +8331,13 @@ return; } - canceled = f->freg->process_event ? f->freg->process_event(f, evt) : GF_FALSE; + if (f->freg->process_event) { + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); + canceled = f->freg->process_event(f, evt); + gf_logs_thread_untag(f); + } else { + canceled = GF_FALSE; + } if (!canceled) { for (i=0; i<f->num_output_pids; i++) { GF_FilterPid *apid = gf_list_get(f->output_pids, i); @@ -7734,7 +8378,7 @@ upstream = GF_TRUE; } - GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s queuing %s event %s\n", pid->pid->filter->name, pid->pid->name, upstream ? "upstream" : "downstream", gf_filter_event_name(evt->base.type) )); + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s queuing %s event %s\n", pid->pid->filter->name, pid->pid->name, upstream ? "upstream" : "downstream", gf_filter_event_name(evt->base.type))); if (upstream) { u32 i, j; @@ -7897,6 +8541,10 @@ if (an_evt->base.on_pid) { safe_int_inc(&an_evt->base.on_pid->filter->num_events_queued); } + + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s queuing %s event %s\n", filter->name, upstream ? "upstream" : "downstream", gf_filter_event_name(evt->base.type) )); + if (upstream) gf_fs_post_task_class(filter->session, gf_filter_pid_send_event_upstream, filter, an_evt->base.on_pid, "upstream_event", an_evt, TASK_TYPE_EVENT); else @@ -7917,7 +8565,10 @@ if (pid->pid->filter->freg->process_event) { if (evt->base.on_pid) evt->base.on_pid = evt->base.on_pid->pid; FSESS_CHECK_THREAD(pid->pid->filter) + + gf_logs_thread_tag(pid->pid->filter, GF_LOG_TAG_FILTER); pid->pid->filter->freg->process_event(pid->pid->filter, evt); + gf_logs_thread_untag(pid->pid->filter); } } @@ -7935,14 +8586,14 @@ GF_EXPORT Bool gf_filter_pid_share_origin(GF_FilterPid *pid, GF_FilterPid *other_pid) { - if (!pid || !other_pid) return GF_FALSE; - pid = pid->pid; - other_pid = other_pid->pid; - if (gf_filter_in_parent_chain(pid->filter, other_pid->filter)) - return GF_TRUE; - if (gf_filter_in_parent_chain(other_pid->filter, pid->filter)) - return GF_TRUE; - return GF_FALSE; + if (!pid || !other_pid) return GF_FALSE; + pid = pid->pid; + other_pid = other_pid->pid; + if (gf_filter_in_parent_chain(pid->filter, other_pid->filter)) + return GF_TRUE; + if (gf_filter_in_parent_chain(other_pid->filter, pid->filter)) + return GF_TRUE; + return GF_FALSE; } static void filter_pid_inst_collect_stats(GF_FilterPidInst *pidi, GF_FilterPidStatistics *stats) @@ -8133,8 +8784,8 @@ GF_FilterPacket *pck; if (PID_IS_INPUT(pid)) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Removing PID input filter (%s:%s) not allowed\n", pid->filter->name, pid->pid->name)); + return; } - GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s removed output PID %s\n", pid->filter->name, pid->pid->name)); if (pid->filter->removed) { return; @@ -8142,12 +8793,14 @@ if (pid->removed) { return; } + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s removed output PID %s\n", pid->filter->name, pid->pid->name)); + pid->removed = GF_TRUE; if (pid->filter->marked_for_removal || (pid->has_seen_eos && !pid->nb_buffer_unit)) { u32 i; for (i=0; i<pid->num_destinations; i++) { GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); - gf_fs_post_task(pidi->filter->session, gf_filter_pid_disconnect_task, pidi->filter, pidi->pid, "pidinst_disconnect", NULL); + gf_fs_post_disconnect_task(pidi->filter->session, pidi->filter, pidi->pid); } return; } @@ -8159,6 +8812,8 @@ return; } gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + safe_int_inc(&pid->filter->session->remove_tasks); + safe_int_inc(&pid->filter->pid_rem_packet_pending); pck->pck->info.flags |= GF_PCK_CMD_PID_REM; gf_filter_pck_send(pck); } @@ -8223,6 +8878,8 @@ return; } pid = pid->pid; + //detached + if (!pid) return; for (i=0; i<pid->filter->num_output_pids; i++) { GF_FilterPid *apid = gf_list_get(pid->filter->output_pids, i); if (!clear_all && (pid != apid)) continue; @@ -8336,6 +8993,7 @@ } for (k=dst->cap_idx_at_resolution; k<dst->freg->nb_caps; k++) { const GF_FilterCapability *cap = &dst->freg->capsk; + if (cap->flags & GF_CAPFLAG_RECONFIG) return NULL; if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) return NULL; if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; @@ -8458,7 +9116,7 @@ } sep = strchr(name+1, '$'); if (!sep) { - GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter broken file template expecting $KEYWORD$, couldn't find second '$'\n", szTemplate)); + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter broken file template `%s` expecting $KEYWORD$, couldn't find second '$'\n", szTemplate)); strcpy(szFinalName, szTemplate); return GF_BAD_PARAM; } @@ -8480,14 +9138,17 @@ value = file_idx; has_val = GF_TRUE; } else if (!strcmp(name, "URL")) { - if (!filename) - prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_URL); + if (!filename) { + prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_FILEALIAS); + if (!prop_val) prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_URL); + } is_file_str = GF_TRUE; } else if (!strcmp(name, "File")) { if (!filename) { - prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_FILEPATH); + prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_FILEALIAS); + if (!prop_val) prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_FILEPATH); //if filepath is a gmem:// wrapped, don't use it ! - if (prop_val && !strncmp(prop_val->value.string, "gmem://", 7)) + if (prop_val && prop_val->value.string && !strncmp(prop_val->value.string, "gmem://", 7)) prop_val = NULL; if (!prop_val) @@ -8637,10 +9298,11 @@ szFinalNamek = '$'; k++; name++; - + //allow $;$ to delimit $$ (may be required with some shells) + if ((name0==';') && (name1=='$')) + name++; continue; - } @@ -8729,6 +9391,10 @@ if (!sep) break; sep0 = '$'; name = sep+1; + //allow $;$ to delimit $$ (may be required with some shells) + if ((name0==';') && (name1=='$')) + name++; + } szFinalNamek = 0; return GF_OK; @@ -8768,7 +9434,7 @@ if (!gf_fq_count(pidi->packets) && !pid->pid->filter->postponed_packets) pidi->is_end_of_stream = pid->pid->has_seen_eos; } - pidi->discard_inputs = discard_on ? 1 : 0; + pidi->discard_inputs = discard_on ? GF_PIDI_DISCARD_ON : GF_PIDI_DISCARD_OFF; return GF_OK; } @@ -8982,6 +9648,12 @@ } GF_EXPORT +GF_Filter *gf_filter_pid_get_owner(GF_FilterPid *pid) +{ + return pid->filter; +} + +GF_EXPORT GF_Filter *gf_filter_pid_get_source_filter(GF_FilterPid *pid) { if (PID_IS_OUTPUT(pid)) { @@ -9093,11 +9765,14 @@ #include <gpac/internal/media_dev.h> GF_Err rfc_6381_get_codec_aac(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size, Bool force_sbr); +GF_Err dolby_get_codec_ac4(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size); +GF_Err rfc_6381_get_codec_iamf(char *szCodec, GF_IAConfig *cfg); GF_Err rfc_6381_get_codec_m4v(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size); GF_Err rfc_6381_get_codec_avc(char *szCodec, u32 subtype, GF_AVCConfig *avcc); GF_Err rfc_6381_get_codec_hevc(char *szCodec, u32 subtype, GF_HEVCConfig *hvcc); GF_Err rfc_6381_get_codec_av1(char *szCodec, u32 subtype, GF_AV1Config *av1c, COLR colr); GF_Err rfc_6381_get_codec_vpx(char *szCodec, u32 subtype, GF_VPConfig *vpcc, COLR colr); +GF_Err rfc_6381_get_codec_avs3v(char *szCodec, u32 subtype, GF_AVS3VConfig *av3c, COLR colr); GF_Err rfc_6381_get_codec_dolby_vision(char *szCodec, u32 subtype, GF_DOVIDecoderConfigurationRecord *dovi); GF_Err rfc_6381_get_codec_vvc(char *szCodec, u32 subtype, GF_VVCConfig *vvcc); GF_Err rfc_6381_get_codec_mpegha(char *szCodec, u32 subtype, u8 *dsi, u32 dsi_size, s32 pl); @@ -9111,6 +9786,7 @@ u32 subtype=0, subtype_src=0, codec_id, stream_type; s32 mha_pl=-1; Bool is_tile_base = GF_FALSE; + Bool use_scal_refs = GF_FALSE; const GF_PropertyValue *p, *dcd, *dcd_enh, *dovi, *codec; COLR colr; @@ -9132,6 +9808,9 @@ dcd = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); dcd_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + p = gf_filter_pid_get_property_str(pid, "isom:scal"); + if (p && p->value.uint_list.nb_items) use_scal_refs = GF_TRUE; + // If colour information is supplied in the colr box, and also in the video bitstream, the box takes precedence { const GF_PropertyValue *p1 = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_PRIMARIES), @@ -9221,10 +9900,18 @@ case GF_CODECID_AAC_MPEG2_SSRP: case GF_CODECID_USAC: return rfc_6381_get_codec_aac(szCodec, codec_id, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0, force_sbr); + case GF_CODECID_AC4: + return dolby_get_codec_ac4(szCodec, codec_id, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0); + case GF_CODECID_IAMF: { + GF_IAConfig *cfg = gf_odf_iamf_cfg_read(dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0); + GF_Err e = rfc_6381_get_codec_iamf(szCodec, cfg); + gf_odf_iamf_cfg_del(cfg); + return e; + } case GF_CODECID_MPEG4_PART2: return rfc_6381_get_codec_m4v(szCodec, codec_id, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0); - break; + case GF_CODECID_SVC: case GF_CODECID_MVC: if (dcd_enh) dcd = dcd_enh; @@ -9250,20 +9937,28 @@ return GF_OK; case GF_CODECID_LHVC: - subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1; - //fallthrough case GF_CODECID_HEVC_TILES: - if (!subtype) subtype = GF_ISOM_SUBTYPE_HVT1; - if (!dcd && tile_base_dcd) dcd = tile_base_dcd; - - //fallthrough case GF_CODECID_HEVC: + if (codec_id==GF_CODECID_HEVC_TILES) { + if (!subtype) subtype = GF_ISOM_SUBTYPE_HVT1; + if (!dcd && tile_base_dcd) dcd = tile_base_dcd; + } else if (codec_id==GF_CODECID_LHVC) { + if (!dcd || !dcd_enh) { + subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1; + } + } + if (!subtype) { if (is_tile_base) { subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2; } else if (dcd_enh) { if (dcd) { - subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2; + //if scal track refs are found, assume extractors are present and use hev2/hvc2 + if (use_scal_refs) + subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2; + //otherwise use backward compatible signaling + else + subtype = force_inband ? GF_ISOM_SUBTYPE_HEV1 : GF_ISOM_SUBTYPE_HVC1; } else { subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1; } @@ -9272,9 +9967,20 @@ } } if (dcd || dcd_enh) { - GF_HEVCConfig *hvcc = dcd ? gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE) : NULL; + GF_HEVCConfig *hvcc; + if (dcd) + hvcc = gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE); + else + hvcc = gf_odf_hevc_cfg_read(dcd_enh->value.data.ptr, dcd_enh->value.data.size, GF_TRUE); + if (hvcc) { - GF_Err e = rfc_6381_get_codec_hevc(szCodec, subtype, hvcc); + GF_Err e; + if (dcd) { + e = rfc_6381_get_codec_hevc(szCodec, subtype, hvcc); + } else { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + e = GF_OK; + } gf_odf_hevc_cfg_del(hvcc); return e; } @@ -9327,6 +10033,23 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("RFC6381 Cannot find VPX config, using default %s\n", szCodec)); return GF_OK; + case GF_CODECID_AVS3_VIDEO: + if (!subtype) subtype = GF_ISOM_SUBTYPE_AVS3; + + if (dcd) { + GF_AVS3VConfig *av3c = gf_odf_avs3v_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + if (av3c) { + GF_Err e = rfc_6381_get_codec_avs3v(szCodec, subtype, av3c, colr); + gf_odf_avs3v_cfg_del(av3c); + return e; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("RFC6381 AVS3 Video config not conformant\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("RFC6381 Cannot find AVS3 Video config, using default %s\n", szCodec)); + return GF_OK; + case GF_CODECID_MHAS: subtype = subtype_src ? subtype_src : GF_ISOM_SUBTYPE_MH3D_MHM1; if (!dcd) { @@ -9416,7 +10139,7 @@ if (pid->eos_keepalive) return; - gf_filter_pid_set_eos(pid); + gf_filter_pid_set_eos_internal(pid, GF_TRUE); //set keepalive once eos has been called pid->eos_keepalive = GF_TRUE; }
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_props.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_props.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -89,6 +89,79 @@ return GF_FALSE; } +static void parse_data_format(GF_PropertyValue *p, char list_sep_char, const char *name, const char *value, const char *prefix, const char *scode, u32 size) +{ + u32 nb_items=1; + Bool is_string = GF_FALSE; + char *str_val = NULL; + u32 str_val_len = 0; + const char *v = value; + while (1) { + char *sep = strchr(v, list_sep_char); + if (!sep) break; + nb_items++; + v = sep+1; + } + if (!strcmp(scode, "%s")) + is_string = GF_TRUE; + else + p->value.data.ptr = gf_malloc(nb_items*size); + + u32 res = 0; + if (is_string || p->value.data.ptr) { + char *vdup = gf_strdup(value); + const char *v = vdup; + nb_items = 0; + while (1) { + u32 lres; + char *sep = strchr(v, list_sep_char); + if (sep) sep0 = 0; + + if (!strcmp(prefix, "u8@")) { + u32 _val; + lres = sscanf(v, scode, &_val); + if (lres) p->value.data.ptrnb_items = (u8) _val; + } + else if (!strcmp(prefix, "s8@")) { + u32 _val; + lres = sscanf(v, scode, &_val); + if (lres) p->value.data.ptrnb_items = (s8) _val; + } else if (is_string) { + //copy also the trailing 0 + u32 slen = 1 + (u32) strlen(v); + str_val = gf_realloc(str_val, str_val_len+slen); + memcpy(str_val+str_val_len, str_val, slen); + str_val_len += slen; + lres = 1; + } else { + lres = sscanf(value, scode, &p->value.data.ptrnb_items*size); + } + nb_items++; + res += lres; + if (!sep) break; + if (!lres) break; + v = sep+1; + } + gf_free(vdup); + } + if (str_val) + p->value.data.ptr = str_val; + + if (res==nb_items) { + p->value.data.size = str_val ? str_val_len : (nb_items*size); + p->type = GF_PROP_DATA; + } else { + if (p->value.data.ptr) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Invalid value %s for %s syntax of property %s, parsed only %d for %d items\n", value, prefix, name, res, nb_items)); + gf_free(p->value.data.ptr); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to allocate data for value %s%s of property %s\n", prefix, value, name)); + } + p->value.data.size = 0; + p->value.data.ptr = NULL; + p->type=GF_PROP_FORBIDDEN; + } +} static Bool parse_time(const char *str, u64 *val) { u32 h=0, m=0, s=0, ms=0; @@ -113,6 +186,7 @@ return GF_TRUE; } +GF_EXPORT GF_PropertyValue gf_props_parse_value(u32 type, const char *name, const char *value, const char *enum_values, char list_sep_char) { GF_PropertyValue p; @@ -176,7 +250,11 @@ } if (!value && !enum_values) { - GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for unsigned int arg %s - using 0\n", value, name)); + //patch for ka option, used as Bool in pin/sockin/... but as enum in flist, triggering this error when piping playlist + //fortunately in pipe mode of playlist, flist.ka is not used + if (strcmp(name, "ka")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for unsigned int arg %s - using 0\n", value, name)); + } p.value.uint = 0; break; } @@ -511,6 +589,30 @@ p.value.data.size = 0; p.type=GF_PROP_FORBIDDEN; } + } else if (!strnicmp(value, "u8@", 3) ) { + parse_data_format(&p, list_sep_char, name, value+3, "u8@", "%c", 1); + } else if (!strnicmp(value, "s8@", 3) ) { + parse_data_format(&p, list_sep_char, name, value+3, "s8@", "%c", 1); + } else if (!strnicmp(value, "u16@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "u16@", "%hu", 2); + } else if (!strnicmp(value, "s16@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "s16@", "%hd", 2); + } else if (!strnicmp(value, "u32@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "u32@", "%u", 4); + } else if (!strnicmp(value, "s32@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "s32@", "%d", 4); + } else if (!strnicmp(value, "u64@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "u64@", LLU, 8); + } else if (!strnicmp(value, "s64@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "s64@", LLD, 8); + } else if (!strnicmp(value, "flt@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "flt@", "%f", 4); + } else if (!strnicmp(value, "dbl@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "dbl@", "%g", 8); + } else if (!strnicmp(value, "hex@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "hex@", "%X", 4); + } else if (!strnicmp(value, "str@", 4) ) { + parse_data_format(&p, list_sep_char, name, value+4, "str@", "%s", 4); } else { p.value.data.size = (u32) strlen(value); if (p.value.data.size) @@ -535,6 +637,14 @@ u32 len=0; char *nv; char *sep = strchr(v, list_sep_char); + while (sep && sep1 == list_sep_char) { + // skip doubled separator + size_t len = strlen(sep+1); + memmove(sep, sep+1, len); + seplen = 0; + sep = strchr(sep+1, list_sep_char); + } + if (sep && is_xml) { char *xml_end = strchr(v, '>'); len = (u32) (sep - v); @@ -669,6 +779,9 @@ case GF_PROP_4CC_LIST: case GF_PROP_UINT_LIST: return GF_PROP_UINT_LIST; + case GF_PROP_STRING_LIST: + case GF_PROP_STRING_LIST_COPY: + return GF_PROP_STRING_LIST; default: //we declare const as UINT in caps if (gf_props_type_is_enum(type)) @@ -804,10 +917,12 @@ } return GF_FALSE; } +GF_EXPORT Bool gf_props_equal(const GF_PropertyValue *p1, const GF_PropertyValue *p2) { return gf_props_equal_internal(p1, p2, GF_FALSE); } +GF_EXPORT Bool gf_props_equal_strict(const GF_PropertyValue *p1, const GF_PropertyValue *p2) { return gf_props_equal_internal(p1, p2, GF_TRUE); @@ -1256,6 +1371,9 @@ if (!filter_prop || filter_prop(cbk, prop->p4cc, prop->pname, &prop->prop)) { safe_int_inc(&prop->reference_count); + //remove existing property if any + gf_props_remove_property(dst_props, gf_props_hash_djb2(prop->p4cc, prop->pname), prop->p4cc, prop->pname); + #if GF_PROPS_HASHTABLE_SIZE if (!dst_props->hash_tableidx) { dst_props->hash_tableidx = gf_props_get_list(dst_props); @@ -1360,6 +1478,7 @@ {GF_PROP_VEC3I, "v3di", "3D 32-bit integer vector"}, {GF_PROP_VEC4I, "v4di", "4D 32-bit integer vector"}, {GF_PROP_STRING_LIST, "strl", "UTF-8 string list"}, + {GF_PROP_STRING_LIST_COPY, "strl", "UTF-8 string list"}, {GF_PROP_UINT_LIST, "uintl", "unsigned 32 bit integer list"}, {GF_PROP_4CC_LIST, "4ccl", "four-character codes list"}, {GF_PROP_SINT_LIST, "sintl", "signed 32 bit integer list"}, @@ -1430,8 +1549,8 @@ DEC_PROP( GF_PROP_PID_TILE_BASE, "TileBase", "Tile base stream", GF_PROP_BOOL), DEC_PROP( GF_PROP_PID_TILE_ID, "TileID", "ID of the tile for hvt1/hvt2 PIDs", GF_PROP_UINT), DEC_PROP( GF_PROP_PID_LANGUAGE, "Language", "Language code: ISO639 2/3 character code or RFC 4646", GF_PROP_NAME), - DEC_PROP_F( GF_PROP_PID_SERVICE_NAME, "ServiceName", "Name of parent service", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_SERVICE_PROVIDER, "ServiceProvider", "Provider of parent service", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_SERVICE_NAME, "ServiceName", "Name of parent service, signled as PID info", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_SERVICE_PROVIDER, "ServiceProvider", "Provider of parent service, signled as PID info", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP( GF_PROP_PID_STREAM_TYPE, "StreamType", "Media stream type", GF_PROP_UINT), DEC_PROP_F( GF_PROP_PID_SUBTYPE, "StreamSubtype", "Media subtype 4CC (auxiliary, pic sequence, etc ..), matches ISOM handler type", GF_PROP_4CC, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ISOM_SUBTYPE, "ISOMSubtype", "ISOM media subtype 4CC (avc1 avc2...)", GF_PROP_4CC, GF_PROP_FLAG_GSF_REM), @@ -1441,7 +1560,9 @@ DEC_PROP( GF_PROP_PID_UNFRAMED, "Unframed", "The media data is not framed, i.e. each packet is not a complete AU/frame or is not in internal format (e.g. annexB for avc/hevc, adts for aac)", GF_PROP_BOOL), DEC_PROP( GF_PROP_PID_UNFRAMED_FULL_AU, "UnframedAU", "The unframed media still has correct AU boundaries: one packet is one full AU, but the packet format might not be the internal one (e.g. annexB for avc/hevc, adts for aac)", GF_PROP_BOOL), DEC_PROP( GF_PROP_PID_UNFRAMED_LATM, "LATM", "Media is unframed AAC in LATM format", GF_PROP_BOOL), - DEC_PROP( GF_PROP_PID_DURATION, "Duration", "Media duration (a negative value means an estimated duration based on rate)", GF_PROP_FRACTION64), + DEC_PROP( GF_PROP_PID_UNFRAMED_SRT, "USRT", "Media is unframed SRT, header is in payload with 0 start time", GF_PROP_BOOL), + DEC_PROP( GF_PROP_PID_DURATION, "Duration", "Media duration", GF_PROP_FRACTION64), + DEC_PROP( GF_PROP_PID_DURATION_AVG, "EstimatedDuration", "Media duration is an estimated duration based on rate", GF_PROP_BOOL), DEC_PROP_F( GF_PROP_PID_NB_FRAMES, "NumFrames", "Number of frames in the stream", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_FRAME_OFFSET, "FrameOffset", "Index of first frame in the stream (used for reporting)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP( GF_PROP_PID_FRAME_SIZE, "ConstantFrameSize", "Size of the frames for constant frame size streams", GF_PROP_UINT), @@ -1449,7 +1570,7 @@ DEC_PROP_F( GF_PROP_PID_TIMESHIFT_TIME, "TimeshiftTime", "Time in the timeshift buffer in seconds - changes are signaled through PID info (no reconfigure)", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_TIMESHIFT_STATE, "TimeshiftState", "State of timeshift buffer: 0 is OK, 1 is underflow, 2 is overflow - changes are signaled through PID info (no reconfigure)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP( GF_PROP_PID_TIMESCALE, "Timescale", "Media timescale (a timestamp delta of N is N/timescale seconds)", GF_PROP_UINT), - DEC_PROP_F( GF_PROP_PID_PROFILE_LEVEL, "ProfileLevel", "MPEG-4 profile and level", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_PROFILE_LEVEL, "ProfileLevel", "Profile and level indication", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP( GF_PROP_PID_DECODER_CONFIG, "DecoderConfig", "Decoder configuration data", GF_PROP_DATA), DEC_PROP( GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, "DecoderConfigEnhancement", "Decoder configuration data of the enhancement layer(s). Also used by 3GPP/Apple text streams to give the full sample description table used in SDP.", GF_PROP_DATA), DEC_PROP( GF_PROP_PID_DSI_SUPERSET, "DSISuperset", "Decoder config is a superset of previous decoder config", GF_PROP_BOOL), @@ -1474,7 +1595,7 @@ DEC_PROP( GF_PROP_PID_BIT_DEPTH_UV, "BitDepthChroma", "Bit depth for chroma components", GF_PROP_UINT), DEC_PROP( GF_PROP_PID_FPS, "FPS", "Video framerate", GF_PROP_FRACTION), DEC_PROP( GF_PROP_PID_INTERLACED, "Interlaced", "Video is interlaced", GF_PROP_BOOL), - DEC_PROP( GF_PROP_PID_SAR, "SAR", "Sample (i.e. pixel) aspect ratio", GF_PROP_FRACTION), + DEC_PROP( GF_PROP_PID_SAR, "SAR", "Sample (i.e. pixel) aspect ratio (negative values mean no SAR and removal of info in containers)", GF_PROP_FRACTION), DEC_PROP( GF_PROP_PID_WIDTH_MAX, "MaxWidth", "Maximum width (video / text / graphics) of all enhancement layers", GF_PROP_UINT), DEC_PROP( GF_PROP_PID_HEIGHT_MAX, "MaxHeight", "Maximum height (video / text / graphics) of all enhancement layers", GF_PROP_UINT), DEC_PROP( GF_PROP_PID_ZORDER, "ZOrder", "Z-order of the video, from 0 (first) to max int (last)", GF_PROP_SINT), @@ -1510,6 +1631,7 @@ DEC_PROP_F( GF_PROP_PID_REMOTE_URL, "RemoteURL", "Remote URL of source - used for MPEG-4 systems", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_REDIRECT_URL, "RedirectURL", "Redirection URL of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_FILEPATH, "SourcePath", "Path of source file on file system", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_FILEALIAS, "FileAlias", "Alias name for source file, replace $URL$ and $File$ in templates", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_MIME, "MIMEType", "MIME type of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_FILE_EXT, "Extension", "File extension of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_FILE_CACHED, "Cached", "File is completely cached", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), @@ -1548,7 +1670,7 @@ DEC_PROP( GF_PROP_PID_ENCRYPTED, "Encrypted", "Packets for the stream are by default encrypted (however the encryption state is carried in packet crypt flags) - changes are signaled through PID info change (no reconfigure)", GF_PROP_BOOL), DEC_PROP( GF_PROP_PID_OMA_PREVIEW_RANGE, "OMAPreview", "OMA Preview range ", GF_PROP_LUINT), - DEC_PROP( GF_PROP_PID_CENC_PSSH, "CENC_PSSH", "PSSH blob for CENC, formatted as (u32)NbSystems (bin128)SystemID(u32)version(u32)KID_count (bin128)keyID (u32)priv_size(char*priv_size)priv_data", GF_PROP_DATA), + DEC_PROP_F( GF_PROP_PID_CENC_PSSH, "CENC_PSSH", "PSSH blob for CENC, formatted as (u32)NbSystems (bin128)SystemID(u32)version(u32)KID_count (bin128)keyID (u32)priv_size(char*priv_size)priv_data", GF_PROP_DATA, 0), DEC_PROP_F( GF_PROP_PCK_CENC_SAI, "CENC_SAI", "CENC SAI for the packet, formatted as (char(IV_Size))IV(u16)NbSubSamples (u16)ClearBytes(u32)CryptedBytes", GF_PROP_DATA, GF_PROP_FLAG_PCK), DEC_PROP( GF_PROP_PID_CENC_KEY_INFO, "KeyInfo", "Multi key info formatted as:\n `is_mkey(u8);\nnb_keys(u16);\n\n\tIV_size(u8);\n\tKID(bin128);\n\tif (!IV_size) {;\n\t\tconst_IV_size(u8);\n\t\tconstIV(const_IV_size);\n}\n\n`", GF_PROP_DATA), DEC_PROP( GF_PROP_PID_CENC_PATTERN, "CENCPattern", "CENC crypt pattern, CENC pattern, skip as frac.num crypt as frac.den", GF_PROP_FRACTION), @@ -1558,7 +1680,7 @@ "- 1: a clear clone of the sample description is created, inserted before the CENC sample description\n" "- 2: a clear clone of the sample description is created, inserted after the CENC sample description", GF_PROP_UINT), DEC_PROP( GF_PROP_PID_AMR_MODE_SET, "AMRModeSet", "ModeSet for AMR and AMR-WideBand", GF_PROP_UINT), - DEC_PROP( GF_PROP_PCK_SUBS, "SubSampleInfo", "Binary blob describing N subsamples of the sample, formatted as N (u32)flags(u32)size(u32)codec_param(u8)priority(u8) discardable. Subsamples for a given flag MUST appear in order, however flags can be interleaved", GF_PROP_DATA), + DEC_PROP_F( GF_PROP_PCK_SUBS, "SubSampleInfo", "Binary blob describing N subsamples of the sample, formatted as N (u32)flags(u32)size(u32)codec_param(u8)priority(u8) discardable. Subsamples for a given flag MUST appear in order, however flags can be interleaved", GF_PROP_DATA, GF_PROP_FLAG_PCK), DEC_PROP( GF_PROP_PID_MAX_NALU_SIZE, "NALUMaxSize", "Max size of NAL units in stream - changes are signaled through PID info change (no reconfigure)", GF_PROP_UINT), DEC_PROP_F( GF_PROP_PCK_FILENUM, "FileNumber", "Index of file when dumping to files", GF_PROP_UINT, GF_PROP_FLAG_PCK), DEC_PROP_F( GF_PROP_PCK_FILENAME, "FileName", "Name of output file when dumping / dashing. Must be set on first packet belonging to new file", GF_PROP_STRING, GF_PROP_FLAG_PCK), @@ -1566,7 +1688,7 @@ DEC_PROP_F( GF_PROP_PCK_FILESUF, "FileSuffix", "File suffix name, replacement for $FS$ in tile templates", GF_PROP_STRING, GF_PROP_FLAG_PCK), DEC_PROP_F( GF_PROP_PCK_EODS, "EODS", "End of DASH segment", GF_PROP_BOOL, GF_PROP_FLAG_PCK), DEC_PROP_F( GF_PROP_PCK_CUE_START, "CueStart", "Set on packets marking the beginning of a DASH/HLS segment for cue-driven segmentation - see dasher help", GF_PROP_BOOL, GF_PROP_FLAG_PCK), - DEC_PROP_F( GF_PROP_PCK_MEDIA_TIME, "MediaTime", "Corresponding media time of the parent packet (0 being the origin)", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_MEDIA_TIME, "MediaTime", "Corresponding media time of the parent packet (0 being the origin)", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM | GF_PROP_FLAG_PCK), DEC_PROP_F( GF_PROP_PID_MAX_FRAME_SIZE, "MaxFrameSize", "Max size of frame in stream - changes are signaled through PID info change (no reconfigure)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_AVG_FRAME_SIZE, "AvgFrameSize", "Average size of frame in stream (ISOBMFF only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_MAX_TS_DELTA, "MaxTSDelta", "Maximum DTS delta between frames (ISOBMFF only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), @@ -1574,8 +1696,7 @@ DEC_PROP_F( GF_PROP_PID_CONSTANT_DURATION, "ConstantDuration", "Constant duration of samples, 0 means variable duration (ISOBMFF only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ISOM_TRACK_TEMPLATE, "TrackTemplate", "ISOBMFF serialized track box for this PID, without any sample info (empty stbl and empty dref)", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ISOM_TREX_TEMPLATE, "TrexTemplate", "ISOBMFF serialized trex box for this PID", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_ISOM_STSD_TEMPLATE, "STSDTemplate", "ISOBMFF serialized sample description box (stsd entry) for this PID", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), - + DEC_PROP_F( GF_PROP_PID_ISOM_STSD_TEMPLATE, "STSDTemplate", "ISOBMFF serialized sample description entry for this PID", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ISOM_UDTA, "MovieUserData", "ISOBMFF serialized moov UDTA and other moov-level boxes (list) for this PID", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ISOM_HANDLER, "HandlerName", "ISOBMFF track handler name", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ISOM_TRACK_FLAGS, "TrackFlags", "ISOBMFF track header flags", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), @@ -1587,14 +1708,21 @@ DEC_PROP_F( GF_PROP_PID_PERIOD_START, "PStart", "DASH Period start - cf dasher help", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_PERIOD_DUR, "PDur", "DASH Period duration - cf dasher help", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_REP_ID, "Representation", "ID of DASH representation", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_AS_ID, "ASID", "ID of parent DASH AS", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_AS_ID, "ASID", "ID of parent DASH Adaptation Set", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_SSR, "SSR", "ID of Adaptation Set:\n" + "- same value as ASID: regular SSR not used for cross-AS switching\n" + "- ID of another AdaptationSet: enable cross-AS switching between this AS and the referenced one\n" + "- negative value: LL-HLS compatability mode", GF_PROP_SINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_AS_QUERY, "ASQuery", "Query string to add to this Adaptation Set when requesting segments", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_MUX_SRC, "MuxSrc", "Name of mux source(s), set by dasher to direct its outputs", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_DASH_MODE, "DashMode", "DASH mode to be used by multiplexer if any, set by dasher. 0 is no DASH, 1 is regular DASH, 2 is VoD", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_INIT_BASE64, "InitBase64", "Indicate that multiplexer should send the base64 encoded version of the init segment", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_FORCE_SEG_SYNC, "SegSync", "Indicate segment must be completely flushed before sending segment/fragment size events", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_DASH_DUR, "DashDur", "DASH target segment duration in seconds", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_DASH_MULTI_PID, NULL, "Pointer to the GF_List of input PIDs for multi-stsd entries segments, set by dasher", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_DASH_MULTI_PID_IDX, NULL, "1-based index of PID in the multi PID list, set by dasher", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_DASH_MULTI_TRACK, NULL, "Pointer to the GF_List of input PIDs for multi-tracks segments, set by dasher", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_FDUR, "FragDur", "DASH target fragment duration in seconds", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_MULTI_PID, "DashMultiPid", "Pointer to the GF_List of input PIDs for multi-stsd entries segments, set by dasher", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_MULTI_PID_IDX, "DashMultiPidIdx", "1-based index of PID in the multi PID list, set by dasher", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_MULTI_TRACK, "DashMultiTrack", "Pointer to the GF_List of input PIDs for multi-tracks segments, set by dasher", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ROLE, "Role", "List of roles for this PID, where each role string can be a DASH role, a `URN:role-value` or any other string (this will throw a warning and use a custom URI for the role)", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_PERIOD_DESC, "PDesc", "List of descriptors for the DASH period containing this PID", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_AS_COND_DESC, "ASDesc", "List of conditional descriptors for the DASH AdaptationSet containing this PID. If a PID with the same property type but different value is found, the PIDs will be in different AdaptationSets", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), @@ -1607,6 +1735,7 @@ DEC_PROP_F( GF_PROP_PID_CLAMP_DUR, "ClampDur", "Max media duration to process from PID in DASH mode", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_HLS_PLAYLIST, "HLSPL", "Name of the HLS variant playlist for this media", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_HLS_GROUPID, "HLSGroup", "Name of HLS Group of a stream", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_HLS_GROUP_REND, "HLSRend", "List of HLS group allowed in group rendition - when not set, all groups are allowed", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_HLS_FORCE_INF, "HLSForce", "Force writing EXT-X-STREAM-INF if stream is in a rendition group, value is the name of associated groups (can be empty)", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_HLS_EXT_MASTER, "HLSMExt", "List of extensions to add to the master playlist for this PID", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_HLS_EXT_VARIANT, "HLSVExt", "List of extensions to add to the variant playlist for this PID", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), @@ -1618,16 +1747,19 @@ DEC_PROP_F( GF_PROP_PID_UDP, "RequireReorder", "PID packets come from source with losses and reordering happening (UDP)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_PRIMARY_ITEM, "Primary", "Primary item in ISOBMFF", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_DASH_FWD, "DFMode", "DASH forward mode is used for this PID. If 2, the manifest is also carried in packet propery", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_DASH_MANIFEST, "DFManifest", "Value of manifest in forward mode", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_HLS_VARIANT, "DFVariant", "Value of variant playlist in forward mode", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_HLS_VARIANT_NAME, "DFVariantName", "Value of variant playlist name in forward mode", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_DASH_PERIOD_START, "DFPStart", "Value of active period start time in forward mode", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_DASH_MANIFEST, "DFManifest", "Value of manifest in forward mode", GF_PROP_STRING, GF_PROP_FLAG_PCK| GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_HLS_VARIANT, "DFVariant", "Value of variant playlist in forward mode", GF_PROP_STRING_LIST, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_HLS_VARIANT_NAME, "DFVariantName", "Value of variant playlist name in forward mode", GF_PROP_STRING_LIST, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_PERIOD_START, "DFPStart", "Value of active period start time in ms in forward mode", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_DASH_PERIOD_START, "DFPckPStart", "Indicate new period start (only set on first packets of non-first periods)", GF_PROP_BOOL, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP( GF_PROP_PID_HLS_KMS, "HLSKey", "URI, KEYFORMAT and KEYFORMATVERSIONS for HLS full segment encryption creation, Key URI otherwise ( decoding and sample-AES)", GF_PROP_STRING), DEC_PROP( GF_PROP_PID_HLS_IV, "HLSIV", "Init Vector for HLS decode", GF_PROP_DATA), DEC_PROP( GF_PROP_PID_CLEARKEY_URI, "CKUrl", "URL for ClearKey licence server", GF_PROP_STRING), DEC_PROP_F( GF_PROP_PID_COLR_PRIMARIES, "ColorPrimaries", "Color primaries", GF_PROP_CICP_COL_PRIM, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_COLR_TRANSFER, "ColorTransfer", "Color transfer characteristics", GF_PROP_CICP_COL_TFC, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_COLR_TRANSFER_ALT, "ColorTransferAlternative", "Alternative Color transfer characteristics", GF_PROP_CICP_COL_TFC, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_COLR_MX, "ColorMatrix", "Color matrix coefficient", GF_PROP_CICP_COL_MX, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_COLR_RANGE, "FullRange", "Color full range flag", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_COLR_CHROMAFMT, "Chroma", "Chroma format (see ISO/IEC 23001-8 / 23091-2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), @@ -1643,8 +1775,10 @@ DEC_PROP_F( GF_PROP_PCK_FRAG_START, "FragStart", "Packet is a fragment start (value 1) or a segment start (value 2)", GF_PROP_UINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PCK_FRAG_RANGE, "FragRange", "Start and end position in bytes of fragment if packet is a fragment or segment start", GF_PROP_FRACTION64, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_FRAG_TFDT, "FragTFDT", "Decode time of first packet in fragmentt", GF_PROP_LUINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_SIDX_RANGE, "SIDXRange", "Start and end position in bytes of sidx if packet is a fragment or segment start", GF_PROP_FRACTION64, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_FRAG_TFDT, "FragTFDT", "Decode time of first packet in fragment", GF_PROP_LUINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_SIDX_RANGE, "SIDXRange", "Start and end position in bytes of sidx in segment if any", GF_PROP_FRACTION64, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PID_VOD_SIDX_RANGE, "VODSIDXRange", "Start and end position in bytes of root sidx", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PCK_MOOF_TEMPLATE, "MoofTemplate", "Serialized moof box corresponding to the start of a movie fragment or segment (with styp and optionally sidx)", GF_PROP_DATA, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), @@ -1660,15 +1794,15 @@ DEC_PROP_F( GF_PROP_PID_VIEW_IDX, "ViewIdx", "View index for multiview (1 being left)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_ORIG_FRAG_URL, "FragURL", "Fragment URL (without '#') of original URL (used by some filters to set the property on media PIDs)", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_ROUTE_IP, "ROUTEIP", "ROUTE session IP address", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_ROUTE_PORT, "ROUTEPort", "ROUTE session port number", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_ROUTE_NAME, "ROUTEName", "Name (location) of raw file to advertise in ROUTE session", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_ROUTE_CAROUSEL, "ROUTECarousel", "Carousel period in seconds of raw file in ROUTE session", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_ROUTE_SENDTIME, "ROUTEUpload", "Upload time in seconds of raw file in ROUTE session", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_MCAST_IP, "MCASTIP", "session Multicast IP address for ROUTE/MABR", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_MCAST_PORT, "MCASTPort", "session port number for ROUTE/MABR", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_MCAST_NAME, "MCASTName", "Name (location) of raw file to advertise in ROUTE/MABR session", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_MCAST_CAROUSEL, "MCASTCarousel", "Carousel period in seconds of raw file or low-latency manifest/init segments for ROUTE/MABR sessions", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_MCAST_SENDTIME, "MCASTUpload", "Upload time in seconds of raw files for ROUTE/MABR sessions", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_STEREO_TYPE, "Stereo", "Stereo type of video", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_PROJECTION_TYPE, "Projection", "Projection type of video", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_VR_POSE, "InitalPose", "Initial pose for 360 video, in degrees expressed as 16.16 bits (x is yaw, y is pitch, z is roll)", GF_PROP_VEC3I, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_VR_POSE, "InitialPose", "Initial pose for 360 video, in degrees expressed as 16.16 bits (x is yaw, y is pitch, z is roll)", GF_PROP_VEC3I, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_CUBE_MAP_PAD, "CMPad", "Number of pixels to pad from edge of each face in cube map", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_EQR_CLAMP, "EQRClamp", "Clamping of frame for EQR as 0.32 fixed point (x is top, y is bottom, z is left and w is right)", GF_PROP_VEC4I, GF_PROP_FLAG_GSF_REM), @@ -1676,32 +1810,89 @@ DEC_PROP( GF_PROP_PID_SCENE_NODE, "SceneNode", "PID is a scene node decoder (AFX BitWrapper in BIFS)", GF_PROP_BOOL), DEC_PROP( GF_PROP_PID_ORIG_CRYPT_SCHEME, "OrigCryptoScheme", "Original crypto scheme on a decrypted PID", GF_PROP_4CC), DEC_PROP_F( GF_PROP_PID_TIMESHIFT_SEGS, "TSBSegs", "Time shift in number of segments for HAS streams, only set by dashin and dasher filters", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_IS_MANIFEST, "IsManifest", "PID is a HAS manifest (MSB=1 if live)\n" + DEC_PROP_F( GF_PROP_PID_IS_MANIFEST, "IsManifest", "PID is a HAS manifest (bit 9 set to 1 if live), lower 8 bits value can be\n" "- 0: not a manifest\n" "- 1: DASH manifest\n" "- 2: HLS manifest\n" "- 3: GHI(X) manifest", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_SPARSE, "Sparse", "PID has potentially empty times between packets", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_CHARSET, "CharSet", "Character set for input text PID", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_FORCED_SUB, "ForcedSub", "PID or Packet is forced sub\n" - "0: not forced\n" - "1: forced frame\n" - "2: all frames are forced (PID only)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_FORCED_SUB, "ForcedSub", "PID forced sub\n" + "- 0: not forced\n" + "- 1: some frames are forced\n" + "- 2: all frames are forced", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_CHAP_TIMES, "ChapTimes", "Chapter start times", GF_PROP_UINT_LIST, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_CHAP_NAMES, "ChapNames", "Chapter names", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_IS_CHAP, "IsChap", "Subtitle PID is chapter (for QT-like chapters)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_SKIP_BEGIN, "SkipBegin", "Amount of media to skip from beginning of packet in PID timescale", GF_PROP_UINT, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PCK_SKIP_BEGIN, "SkipBegin", "Amount of media to skip from beginning of packet in PID timescale (when set o PID, indicate packets with skip will be present)", GF_PROP_UINT, GF_PROP_FLAG_PCK), DEC_PROP_F( GF_PROP_PCK_SKIP_PRES, "SkipPres", "Packet and any following with CTS greater than this packet shall not be presented (used by reframer to create edit lists)", GF_PROP_BOOL, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PCK_ORIG_DUR, "OriginalDuration", "Elapsed time (.num) and original duration (.den, 0 if last copy of packet) for redundant packets", GF_PROP_FRACTION, GF_PROP_FLAG_PCK), - DEC_PROP_F( GF_PROP_PCK_HLS_REF, "HLSRef", "HLS playlist reference, gives a unique ID identifying media mux, and indicated in packets carrying child playlists", GF_PROP_LUINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PID_LLHLS, "LLHLS", "HLS low latency mode", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), - DEC_PROP_F( GF_PROP_PCK_HLS_FRAG_NUM, "LLHLSFragNum", "LLHLS fragment number", GF_PROP_UINT, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PID_HAS_SKIP_BEGIN, "HasSkipBegin", "Indicate if PID will carry packets with `SkipBegin` properties", GF_PROP_BOOL, 0), + + + DEC_PROP_F( GF_PROP_PID_HLS_REF, "HLSRef", "HLS playlist reference, gives a unique ID identifying media mux, and indicated in packets carrying child playlists", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PCK_HLS_REF, "PckHLSRef", "Same as `HLSRef` but carried on packets for ROUTE/MABR file transfer", GF_PROP_LUINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + + + DEC_PROP_F( GF_PROP_PID_LLHAS_MODE, "LLHAS", "DASH/HLS low latency mode", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_LLHAS_FRAG_NUM, "LLHASFragNum", "DASH-SSR/LLHLS fragment number", GF_PROP_UINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_DOWNLOAD_SESSION, "DownloadSession", "Pointer to download session", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PID_HAS_TEMI, "HasTemi", "TEMI present flag", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PCK_XPS_MASK, "XPSMask", "Parameter set mask", GF_PROP_UINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), DEC_PROP_F( GF_PROP_PCK_END_RANGE, "RangeEnd", "Signal packet is the last in the desired play range", GF_PROP_BOOL, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PCK_ID, "RefID", "packet identifier for dependency (usually POC for video)", GF_PROP_SINT, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PCK_REFS, "Refs", "list of packet identifier this packet depends on", GF_PROP_SINT_LIST, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PCK_UDTA, "UDTA", "User data for the packet", GF_PROP_POINTER, GF_PROP_FLAG_PCK | GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_TIMECODE, "Timecode", "First timecode extracted from SEI (if present)", GF_PROP_DATA, GF_PROP_FLAG_PCK), + + DEC_PROP_F( GF_PROP_PID_DOLBY_VISION, "DOVI", "DolbyVision configuration", GF_PROP_DATA, 0), + DEC_PROP_F( GF_PROP_PID_OUTPATH, "OutPath", "Output file name of PID used by some filters creating additional raw PIDs", GF_PROP_STRING, 0), + DEC_PROP_F( GF_PROP_PID_ADOBE_CRYPT_META, "ACrypMeta", "Meta-data for Adobe encryption", GF_PROP_DATA, 0), + DEC_PROP_F( GF_PROP_PID_CENC_HAS_ROLL, "HasCRoll", "Indicates if key roll is used in CENC", GF_PROP_BOOL, 0), + DEC_PROP_F( GF_PROP_PID_ISOM_STSD_ALL_TEMPLATES, "STSDAllTemplates", "ISOBMFF serialized sample description box for this PID", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_ISOM_STSD_TEMPLATE_IDX, "STSDTemplateIdx", "Index of corresponding `STSDAllTemplates`", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP( GF_PROP_PID_PREMUX_STREAM_TYPE, "PremuxType", "Main streamtype of the PID before mux, only used for ROUTE/MABR setup", GF_PROP_UINT), + DEC_PROP_F( GF_PROP_PID_CODEC_MERGEABLE, "CodecMerge", "Indicate the PID can be merged with other streams with same value for single decoding (HEVC only for now)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_FILE_REL, "RelativePath", "Indicate the packet file name uses relative path", GF_PROP_BOOL, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_CLEARKEY_KID, "ClearKeyID", "Key ID for ClearKey scheme", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PID_DASH_SPARSE, "DashSparse", "indicate DASH segments are generated in sparse mode (from context)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_DASH_DEP_GROUP, "DashDepGroup", "indicate DASH dependency group ID", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_SCTE35_PID, "SC35Ref", "PID has SCTE35 information carried on indicated PID number", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_NO_INIT, "NoInit", "PID does not use any init segment in DASH (file forward mode of dasher, only used for ROUTE/MABR)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_FORCE_UNFRAME, "ForceUnframe", "force creation of rewriter filter (only used for forcing reparse of NALU-based codecs)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_META_DEMUX_CODEC_ID, "MetaCodecID", "identifier for meta codecs (FFmpeg, ...)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_META_DEMUX_CODEC_NAME, "MetaCodecName", "Name used by for meta codecs (FFmpeg, ...)", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_META_DEMUX_OPAQUE, "MetaCodecOpaque", "Internal property used for meta demuxers ( FFmpeg, ...) codec opaque data", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PCK_MPD_SEGSTART, "HASSegStart", "Start time of segment for ROUTE/MABR scheduling", GF_PROP_FRACTION64, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_SPLIT_START, "SplitStart", "split start time of packet in PID timescale, for index-based dashing", GF_PROP_UINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_SPLIT_END, "SplitEnd", "split end time of packet in PID timescale, for index-based dashing", GF_PROP_UINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_INIT_NAME, "InitName", "Name of init segment when dashing, used for ROUTE/MABR", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PCK_SEG_URL, "SegURL", "URL of source segment (when forwarding fragment boundaries)", GF_PROP_STRING, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PCK_CENC_PSSH, "DynPSSH", "PSSH blob for CENC, same format as `CENC_PSSH`, used when using master key and roll keys, signaled on first packet of segment where the PSSH changes", GF_PROP_DATA, GF_PROP_FLAG_PCK), + + DEC_PROP_F( GF_PROP_PCK_LLHAS_TEMPLATE, "LLHASTemplate", "Template for DASH-SSR and LLHLS sub-segments", GF_PROP_STRING, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PCK_PARTIAL_REPAIR, "PartialRepair", "indicate the mux data in the associated data is parsable but contains errors (only set on corrupted packets)", GF_PROP_BOOL, GF_PROP_FLAG_PCK), + DEC_PROP_F( GF_PROP_PID_SEI_LOADED, "SEILoaded", "indicate that PID is extracting SEI/inband data from packets", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PID_FAKE, "Fake", "Indicate a stream present in the source but not delivered as a PID", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_CONTENT_LIGHT_LEVEL, "ContentLightLevel", "Content light level, payload of clli box (see ISO/IEC 14496-12), can be set as a list of 2 integers in fragment declaration (e.g. \"=max_cll,max_pic_avg_ll\")", GF_PROP_DATA, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_MASTER_DISPLAY_COLOUR, "MasterDisplayColour", "Master display colour info, payload of mdcv box (see ISO/IEC 14496-12), can be set as a list of 10 integers in fragment declaration (e.g. \"=dpx0,dpy0,dpx1,dpy1,dpx2,dpy2,wpx,wpy,max,min\")", GF_PROP_DATA, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_SEI_LOADED, "SEILoaded", "indicate that packet has SEI/inband data in its properties", GF_PROP_BOOL, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PCK_ORIGINAL_PTS, "OriginalPTS", "indicate original PTS or PCR when remapping M2TS PCR", GF_PROP_LUINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + DEC_PROP_F( GF_PROP_PCK_ORIGINAL_DTS, "OriginalDTS", "indicate original DTS when remapping M2TS PCR", GF_PROP_LUINT, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PID_MABR_URLS, "MABRBaseURLs", "optionnal URLs for MABR - if first is `none`source server is not declared as repair server", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM), + + DEC_PROP_F( GF_PROP_PCK_FORCED_SUB, "Forced", "indicate packet is a forced subtitle", GF_PROP_BOOL, GF_PROP_FLAG_PCK|GF_PROP_FLAG_GSF_REM), + }; static u32 gf_num_props = sizeof(GF_BuiltInProps) / sizeof(GF_BuiltInProperty); @@ -1775,14 +1966,34 @@ return GF_PROP_FORBIDDEN; } -Bool gf_props_4cc_check_props() +GF_EXPORT +Bool gf_props_sanity_check() { Bool res = GF_TRUE; u32 i, j; for (i=0; i<gf_num_props; i++) { + if (! GF_BuiltInPropsi.name) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Property %s has no name\n", gf_4cc_to_str(GF_BuiltInPropsi.type) )); + res = GF_FALSE; + } +#ifndef GPAC_DISABLE_DOC + if (! GF_BuiltInPropsi.description) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Property %s has no description\n", gf_4cc_to_str(GF_BuiltInPropsi.type) )); + res = GF_FALSE; + } +#endif for (j=i+1; j<gf_num_props; j++) { if (GF_BuiltInPropsi.type==GF_BuiltInPropsj.type) { - GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Property %s and %s have the same code type %s\n", GF_BuiltInPropsi.name, GF_BuiltInPropsj.name, gf_4cc_to_str(GF_BuiltInPropsi.type) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Property %s and %s have the same code %s\n", GF_BuiltInPropsi.name, GF_BuiltInPropsj.name, gf_4cc_to_str(GF_BuiltInPropsi.type) )); + res = GF_FALSE; + } + if (GF_BuiltInPropsi.name && GF_BuiltInPropsj.name && !strcmp(GF_BuiltInPropsi.name, GF_BuiltInPropsj.name)) { + //we allow same name for properties assigned to PIDs or Packets + u32 f1 = GF_BuiltInPropsi.flags & GF_PROP_FLAG_PCK; + u32 f2 = GF_BuiltInPropsj.flags & GF_PROP_FLAG_PCK; + if (f1 != f2) continue; + + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Property %s and %s have the same name %s\n", gf_4cc_to_str(GF_BuiltInPropsi.type), gf_4cc_to_str(GF_BuiltInPropsj.type), GF_BuiltInPropsi.name)); res = GF_FALSE; } } @@ -1993,7 +2204,6 @@ else if (att->value.uint == GF_PLAYBACK_MODE_FASTFORWARD) return "forward"; else return "none"; - case GF_PROP_PCK_SENDER_NTP: case GF_PROP_PCK_RECEIVER_NTP: case GF_PROP_PCK_UTC_TIME: @@ -2017,6 +2227,19 @@ } return dump; + case GF_PROP_PID_CHANNEL_LAYOUT: + if (!gf_sys_is_test_mode()) { + u32 cicp = gf_audio_fmt_get_cicp_from_layout(att->value.longuint); + const char *name = gf_audio_fmt_get_cicp_name(cicp); + if (name) return name; + } + return gf_props_dump_val(att, dump, dump_data_mode, NULL); + + case GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT: + //for tx3d SDP config only + if (gf_utf8_is_legal(att->value.data.ptr, att->value.data.size)) + return (const char *) att->value.data.ptr; + //fallthrough default: if (att->type==GF_PROP_UINT) { u32 type = gf_props_4cc_get_type(p4cc);
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_queue.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_queue.c
Changed
@@ -73,7 +73,7 @@ if (q->use_mx) return q; - //lock-free mode, create dummuy slot for head + //lock-free mode, create dummy slot for head GF_SAFEALLOC(q->head, GF_LFQItem); if (!q->head) { gf_free(q); @@ -81,7 +81,7 @@ } q->tail = q->head; - //lock-free mode, create dummuy slot for reservoir head + //lock-free mode, create dummy slot for reservoir head GF_SAFEALLOC(q->res_head, GF_LFQItem); if (!q->res_head) { gf_free(q->head); @@ -97,7 +97,7 @@ { GF_LFQItem *it = q->head; //first item is dummy if lock-free mode, doesn't hold a valid pointer - if (! q->use_mx) it->data=NULL; + if (!q->use_mx) it->data = NULL; while (it) { GF_LFQItem *ptr = it; @@ -144,7 +144,7 @@ next = NULL; *prev_head = NULL; - while (1 ) { + while (1) { GF_LFQItem *tail; head = *head_ptr; tail = *tail_ptr;
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_register.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_register.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -63,6 +63,8 @@ REG_DEC(xviddec) REG_DEC(j2kdec) REG_DEC(rfac3) +REG_DEC(rfac4) +REG_DEC(ufac4) REG_DEC(a52dec) REG_DEC(rfamr) REG_DEC(oggdmx) @@ -147,6 +149,7 @@ REG_DEC(jsf) REG_DEC(tssplit) +REG_DEC(tsgendts) REG_DEC(httpout) REG_DEC(uncvdec) @@ -158,13 +161,12 @@ REG_DEC(mcdec) #endif -#if defined(GPAC_CONFIG_EMSCRIPTEN) +#if !defined(GPAC_CONFIG_IOS) && !defined(GPAC_CONFIG_ANDROID) REG_DEC(wcdec) REG_DEC(wcenc) REG_DEC(webgrab) #endif - REG_DEC(rfflac) REG_DEC(rfprores) REG_DEC(bsrw) @@ -188,10 +190,14 @@ REG_DEC(ttml2srt) REG_DEC(unframer) REG_DEC(writeuf) +REG_DEC(ttmlmerge) REG_DEC(ghidmx) REG_DEC(evgs) REG_DEC(ccdec) +REG_DEC(ccenc) +REG_DEC(scte35dec) REG_DEC(mpeghdec) +REG_DEC(seiload) typedef const GF_FilterRegister *(*filter_reg_fun)(GF_FilterSession *session); @@ -224,6 +230,8 @@ REG_IT(xviddec), REG_IT(j2kdec), REG_IT(rfac3), + REG_IT(rfac4), + REG_IT(ufac4), REG_IT(a52dec), REG_IT(rfamr), REG_IT(oggdmx), @@ -313,6 +321,7 @@ REG_IT(rfmhas), REG_IT(rfprores), REG_IT(tssplit), + REG_IT(tsgendts), REG_IT(bsrw), REG_IT(bssplit), REG_IT(bsagg), @@ -345,12 +354,16 @@ REG_IT(oggmx), REG_IT(unframer), REG_IT(writeuf), + REG_IT(ttmlmerge), REG_IT(uncvdec), REG_IT(ghidmx), REG_IT(evgs), REG_IT(ccdec), + REG_IT(ccenc), + REG_IT(scte35dec), + REG_IT(seiload), -#if defined(GPAC_CONFIG_EMSCRIPTEN) +#if !defined(GPAC_CONFIG_IOS) && !defined(GPAC_CONFIG_ANDROID) REG_IT(wcdec), REG_IT(wcenc), REG_IT(webgrab),
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_session.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_session.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2026 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -58,7 +58,7 @@ gf_fs_sema_io(fsess, GF_TRUE, GF_TRUE); } nb_tasks = 1; - //no active threads, count number of tasks. If no posted tasks we are likely at the end of the session, don't block, rather use a sem_wait + //no active threads, count number of tasks. If no posted tasks we are likely at the end of the session, don't block, rather use a sem_wait if (!fsess->active_threads) nb_tasks = gf_fq_count(fsess->main_thread_tasks) + gf_fq_count(fsess->tasks); @@ -160,13 +160,13 @@ } #ifdef GPAC_HAS_QJS - if (fs->on_evt_task && jsfs_on_event(fs, evt)) + if (fs->jstasks && jsfs_on_event(fs, evt)) return GF_TRUE; #endif if (evt->type==GF_EVENT_AUTHORIZATION) { #ifdef GPAC_HAS_QJS - if (fs->on_auth_task && jsfs_on_auth(fs, evt)) + if (fs->jstasks && jsfs_on_auth(fs, evt)) return GF_TRUE; #endif @@ -213,10 +213,6 @@ u32 i; GF_FilterSession *fsess, *a_sess; - //safety check: all built-in properties shall have unique 4CCs - if (gf_sys_is_test_mode() && ! gf_props_4cc_check_props()) - return NULL; - GF_SAFEALLOC(fsess, GF_FilterSession); if (!fsess) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to alloc media session\n")); @@ -335,11 +331,6 @@ fsess->pcks_refprops_reservoir = gf_fq_new(fsess->props_mx); } - -#ifndef GPAC_DISABLE_REMOTERY - sprintf(fsess->main_th.rmt_name, "FSThread0"); -#endif - if (!fsess->filters || !fsess->tasks) { GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to alloc media session\n")); fsess->run_status = GF_OUT_OF_MEM; @@ -358,9 +349,6 @@ GF_SessionThread *sess_thread; GF_SAFEALLOC(sess_thread, GF_SessionThread); if (!sess_thread) continue; -#ifndef GPAC_DISABLE_REMOTERY - sprintf(sess_thread->rmt_name, "FSThread%d", i+1); -#endif sprintf(szName, "gf_fs_th_%d", i+1); sess_thread->th = gf_th_new(szName); if (!sess_thread->th) { @@ -409,6 +397,8 @@ fsess->default_pid_buffer_max_units = gf_opts_get_int("core", "buffer-units"); fsess->max_resolve_chain_len = 6; fsess->auto_inc_nums = gf_list_new(); + if (gf_opts_get_bool("core", "check-props")) + fsess->check_props = GF_TRUE; if (nb_threads) fsess->links_mx = gf_mx_new("FilterRegistryGraph"); @@ -443,6 +433,8 @@ } } + gf_logs_thread_tag(fsess, GF_LOG_TAG_FILTERSESSION); + #ifdef GF_FS_ENABLE_LOCALES fsess->uri_relocators = gf_list_new(); fsess->locales.relocate_uri = fs_check_locales; @@ -459,9 +451,10 @@ if (session->flags & GF_FS_FLAG_NO_ARG_CHECK) return; - //ignore any meta argument reported (found or not) that is not already present + //ignore any meta argument reported unfound that is not already present //if sub_opt_name, we must create an entry - if (!sub_opt_name && (type==GF_ARGTYPE_META_REPORTING)) { + //if meta and found, remember the option was valid as it could get pushed by an inheritance chain + if (!sub_opt_name && (type==GF_ARGTYPE_META_REPORTING) && !was_found) { create_if_not_found = GF_FALSE; } if (!session->parsed_args) session->parsed_args = gf_list_new(); @@ -616,7 +609,7 @@ GF_Err gf_fs_set_separators(GF_FilterSession *session, const char *separator_set) { if (!session) return GF_BAD_PARAM; - if (separator_set && (strlen(separator_set)<5)) return GF_BAD_PARAM; + if (separator_set && (strlen(separator_set)<6)) return GF_BAD_PARAM; if (separator_set) { session->sep_args = separator_set0; @@ -624,12 +617,14 @@ session->sep_frag = separator_set2; session->sep_list = separator_set3; session->sep_neg = separator_set4; + session->sep_link = separator_set5; } else { session->sep_args = ':'; session->sep_name = '='; session->sep_frag = '#'; session->sep_list = ','; session->sep_neg = '!'; + session->sep_link = '@'; } return GF_OK; } @@ -737,12 +732,12 @@ gf_assert(fsess->run_status != GF_OK); if (fsess->filters) { u32 i, pass, count=gf_list_count(fsess->filters); - //first pass: disconnect all filters, since some may have references to property maps or packets + //first pass: disconnect all filters, since some may have references to property maps or packets for (i=0; i<count; i++) { u32 j; GF_Filter *filter = gf_list_get(fsess->filters, i); filter->process_th_id = 0; - filter->scheduled_for_next_task = GF_TRUE; + filter->scheduled_for_next_task = GF_FILTER_SCHEDULED; if (filter->detached_pid_inst) { while (gf_list_count(filter->detached_pid_inst)) { @@ -753,21 +748,15 @@ filter->detached_pid_inst = NULL; } - if (filter->postponed_packets) { - while (gf_list_count(filter->postponed_packets)) { - GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); - gf_filter_packet_destroy(pck); - } - gf_list_del(filter->postponed_packets); - filter->postponed_packets = NULL; - } + gf_filter_reset_pending_packets(filter); + gf_mx_p(filter->tasks_mx); for (j=0; j<filter->num_input_pids; j++) { GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, j); gf_filter_pid_inst_reset(pidi); } gf_mx_v(filter->tasks_mx); - filter->scheduled_for_next_task = GF_FALSE; + filter->scheduled_for_next_task = GF_FILTER_NOT_SCHEDULED; } //second pass, finalize all for (pass=0; pass<2; pass++) { @@ -781,7 +770,10 @@ if (filter->freg->finalize && !filter->finalized) { filter->finalized = GF_TRUE; FSESS_CHECK_THREAD(filter) + + gf_logs_thread_tag(filter, GF_LOG_TAG_FILTER); filter->freg->finalize(filter); + gf_logs_thread_tag_del(filter); } } if (!has_scripts) break; @@ -900,6 +892,7 @@ #endif if (fsess->blacklist) gf_free(fsess->blacklist); + gf_logs_thread_untag(fsess); gf_free(fsess); GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Session destroyed\n")); } @@ -929,7 +922,7 @@ } #endif -void gf_fs_post_task_ex(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, Bool is_configure, Bool force_main_thread, Bool force_direct_call, GF_TaskClassType class_type) +void gf_fs_post_task_ex(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, Bool is_configure, Bool force_main_thread, Bool force_direct_call, GF_TaskClassType class_type, u32 delay_ms) { GF_FSTask *task; Bool notified = GF_FALSE; @@ -942,6 +935,8 @@ && (!filter || !filter->in_process) && fsess->tasks_in_process && (gf_th_id()==fsess->main_th.th_id) + && (class_type!=TASK_TYPE_EVENT) + && !delay_ms ) { GF_FSTask atask; u64 task_time = gf_sys_clock_high_res(); @@ -952,24 +947,26 @@ atask.log_name = log_name; atask.udta = udta; GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread 0 task#%d %p executing Filter %s::%s (%d tasks pending)\n", fsess->main_th.nb_tasks, &atask, filter ? filter->name : "none", log_name, fsess->tasks_pending)); - if (filter) - filter->scheduled_for_next_task = GF_TRUE; + if (filter && !filter->scheduled_for_next_task) + filter->scheduled_for_next_task = GF_FILTER_DIRECT_SCHEDULED; task_fun(&atask); filter = atask.filter; if (filter) { filter->time_process += gf_sys_clock_high_res() - task_time; - filter->scheduled_for_next_task = GF_FALSE; + if (filter->scheduled_for_next_task == GF_FILTER_DIRECT_SCHEDULED) + filter->scheduled_for_next_task = GF_FILTER_NOT_SCHEDULED; filter->nb_tasks_done++; } - if (!atask.requeue_request) + if (!atask.requeue_request) { return; + } //asked to requeue the task, post it } /*this was a gf_filter_process_task request but direct call could not be done or requeue is requested. process_task_queued was incremented by caller without checking for existing process task - If the task was not treated, dec / inc will give the same state, undo process_task_queued increment - - If the task was requeued, dec will undo the increment done when requeing the task in gf_filter_check_pending_tasks + - If the task was requeued, dec will undo the increment done when requeuing the task in gf_filter_check_pending_tasks In both cases, inc will redo the same logic as in gf_filter_post_process_task_internal, not creating task if gf_filter_process_task is already scheduled for the filter @@ -1008,7 +1005,7 @@ gf_mx_p(filter->tasks_mx); //no tasks and not scheduled - if (! filter->scheduled_for_next_task && !gf_fq_count(filter->tasks)) { + if ((filter->scheduled_for_next_task!=GF_FILTER_SCHEDULED) && !gf_fq_count(filter->tasks)) { notified = task->notified = GF_TRUE; if (!force_main_thread) @@ -1035,11 +1032,14 @@ gf_fq_add(filter->tasks, task); gf_mx_v(filter->tasks_mx); - GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u Posted task %p Filter %s::%s (%d (%d) pending, %d process tasks) on %s task list\n", gf_th_id(), task, filter->name, task->log_name, fsess->tasks_pending, gf_fq_count(filter->tasks), filter->process_task_queued, task->notified ? (force_main_thread ? "main" : "secondary") : "filter")); + GF_LOG(GF_LOG_INFO, GF_LOG_SCHEDULER, ("Thread %u Posted task %p Filter %s::%s (%d (%d) pending, %d process tasks) on %s task list\n", gf_th_id(), task, filter->name, task->log_name, fsess->tasks_pending, gf_fq_count(filter->tasks), filter->process_task_queued, task->notified ? (force_main_thread ? "main" : "secondary") : "filter")); } else { task->notified = notified = GF_TRUE; task->force_main = force_main_thread; - GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u Posted filter-less task %s (%d pending) on secondary task list\n", gf_th_id(), task->log_name, fsess->tasks_pending)); + GF_LOG(GF_LOG_INFO, GF_LOG_SCHEDULER, ("Thread %u Posted filter-less task %s (%d pending) on secondary task list\n", gf_th_id(), task->log_name, fsess->tasks_pending)); + } + if (delay_ms) { + task->schedule_next_time = gf_sys_clock_high_res() + 1000*delay_ms; } //WARNING, do not use task->notified since the task may have been posted to the filter task list and may already have been swapped @@ -1053,7 +1053,7 @@ gf_assert(task->run_task); if (filter) { GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u posting filter task, scheduled_for_next_task %d\n", gf_th_id(), filter->scheduled_for_next_task)); - gf_assert(!filter->scheduled_for_next_task); + gf_assert(filter->scheduled_for_next_task!=GF_FILTER_SCHEDULED); } //notify/count tasks posted on the main task or regular task lists @@ -1071,12 +1071,12 @@ void gf_fs_post_task(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta) { - gf_fs_post_task_ex(fsess, task_fun, filter, pid, log_name, udta, GF_FALSE, GF_FALSE, GF_FALSE, TASK_TYPE_NONE); + gf_fs_post_task_ex(fsess, task_fun, filter, pid, log_name, udta, GF_FALSE, GF_FALSE, GF_FALSE, TASK_TYPE_NONE, 0); } void gf_fs_post_task_class(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, GF_TaskClassType class_id) { - gf_fs_post_task_ex(fsess, task_fun, filter, pid, log_name, udta, GF_FALSE, GF_FALSE, GF_FALSE, class_id); + gf_fs_post_task_ex(fsess, task_fun, filter, pid, log_name, udta, GF_FALSE, GF_FALSE, GF_FALSE, class_id, 0); } Bool gf_fs_check_filter_register_cap_ex(const GF_FilterRegister *f_reg, u32 incode, GF_PropertyValue *cap_input, u32 outcode, GF_PropertyValue *cap_output, Bool exact_match_only, Bool out_cap_excluded) @@ -1088,6 +1088,8 @@ u32 has_exclude_cid_out = 0; for (j=0; j<f_reg->nb_caps; j++) { const GF_FilterCapability *cap = &f_reg->capsj; + if (cap->flags & GF_CAPFLAG_RECONFIG) break; + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) { //CID not excluded, raw in present and CID explicit match or not included in excluded set if (!exclude_cid_out && has_raw_in && (has_cid_match || (!exact_match_only && has_exclude_cid_out) ) ) { @@ -1422,6 +1424,21 @@ } } } + if (file_exists) { + FILE *f = gf_fopen(szPath, "r"); + char szVal1000; + szVal0 = szVal999 = 0; + if (f) { + gf_fread(szVal, 999, f); + gf_fclose(f); + } else { + file_exists = GF_FALSE; + } + if (strstr(szVal, "import") && strstr(szVal, "from") ) {} + else if (strstr(szVal, "filter.") || strstr(szVal, "session.") ) {} + else + file_exists = GF_FALSE; + } if (file_exists) { if (probe_only) { @@ -1445,6 +1462,251 @@ return gf_fs_load_filter_internal(fsess, name, err_code, NULL); } +GF_EXPORT +GF_Err gf_fs_process_link_directive(char *link, GF_Filter *filter, GF_List *loaded_filters, char *ext_link) +{ + char *link_prev_filter_ext = NULL; + GF_Filter *link_from; + Bool reverse_order = GF_FALSE; + s32 link_filter_idx = -1; + + if (!filter) { + u32 idx=0, count = gf_list_count(loaded_filters); + if (!ext_link || !count) return GF_BAD_PARAM; + ext_link0 = 0; + if (link1 == filter->session->sep_link) { + idx = atoi(link+2); + } else { + idx = atoi(link+1); + if (count - 1 < idx) return GF_BAD_PARAM; + idx = count-1-idx; + } + ext_link0 = filter->session->sep_link; + filter = gf_list_get(loaded_filters, idx); + link = ext_link; + } + + char *ext = strchr(link, filter->session->sep_frag); + if (ext) { + ext0 = 0; + link_prev_filter_ext = ext+1; + } + if (strlen(link)>1) { + if (link1 == filter->session->sep_link ) { + reverse_order = GF_TRUE; + link++; + } + link_filter_idx = 0; + if (strlen(link)>1) { + u32 res; + if (sscanf(link+1, "%u", &res)==1) link_filter_idx = (s32) res; + else { + link_filter_idx = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Link filter index must be an unsigned integer (got %s), using 0\n", link+1)); + } + if (link_filter_idx < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Wrong filter index %d, must be positive\n", link_filter_idx)); + return GF_BAD_PARAM; + } + } + } else { + link_filter_idx = 0; + } + if (ext) ext0 = filter->session->sep_frag; + + if (reverse_order) + link_from = gf_list_get(loaded_filters, link_filter_idx); + else + link_from = gf_list_get(loaded_filters, gf_list_count(loaded_filters)-1-link_filter_idx); + + if (!link_from) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Wrong filter index @%d\n", link_filter_idx)); + return GF_BAD_PARAM; + } + gf_filter_set_source(filter, link_from, link_prev_filter_ext); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_fs_parse_filter_graph(GF_FilterSession *fsess, int argc, char *argv, GF_List **out_loaded_filters, GF_List **out_links_directive) +{ + if (!fsess || !argv || (argc<1)) return GF_BAD_PARAM; + + GF_Err e = GF_OK; + int i; + Bool has_xopt = GF_FALSE; + GF_List *loaded_filters = NULL; + GF_List *links_directive = NULL; + + Bool prev_filter_is_sink = 0; + u32 current_subsession_id = 0; + Bool prev_filter_is_not_source = 0; + u32 current_source_id = 0; + + if (out_loaded_filters) loaded_filters = *out_loaded_filters; + loaded_filters = gf_list_new(); + if (!loaded_filters) return GF_OUT_OF_MEM; + + if (out_links_directive) links_directive = *out_links_directive; + links_directive = gf_list_new(); + if (!links_directive) { + gf_list_del(loaded_filters); + return GF_OUT_OF_MEM; + } + + for (i=0; i<argc; i++) { + GF_Filter *filter=NULL; + Bool is_simple=GF_FALSE; + Bool f_loaded = GF_FALSE; + char *arg = argvi; + + if (!strcmp(arg, "-src") || !strcmp(arg, "-i")) { + filter = gf_fs_load_source(fsess, argvi+1, NULL, NULL, &e); + arg = argvi+1; + i++; + f_loaded = GF_TRUE; + } else if (!strcmp(arg, "-dst") || !strcmp(arg, "-o")) { + filter = gf_fs_load_destination(fsess, argvi+1, NULL, NULL, &e); + arg = argvi+1; + i++; + f_loaded = GF_TRUE; + } + //appart from the above src/dst, other args starting with - are not filters + else if (arg0=='-') { + if (!strcmp(arg, "-xopt")) has_xopt = GF_TRUE; + continue; + } + if (!f_loaded && !has_xopt) { + if (arg0 == fsess->sep_link) { + char *next_sep = NULL; + if (arg1==fsess->sep_link) { + next_sep = strchr(arg+2, fsess->sep_link); + } else { + next_sep = strchr(arg+1, fsess->sep_link); + } + if (next_sep) { + e = gf_fs_process_link_directive(arg, NULL, loaded_filters, next_sep); + if (e) goto exit; + continue; + } + gf_list_add(links_directive, arg); + continue; + } + + if (!strncmp(arg, "src=", 4) ) { + filter = gf_fs_load_source(fsess, arg+4, NULL, NULL, &e); + } else if (!strncmp(arg, "dst=", 4) ) { + filter = gf_fs_load_destination(fsess, arg+4, NULL, NULL, &e); + } else { + e = GF_EOS; + char *need_gfio = strstr(arg, "@gfi://"); + if (!need_gfio) need_gfio = strstr(arg, "@gfo://"); + if (need_gfio) { + e = GF_NOT_SUPPORTED; + goto exit; + } else { + filter = gf_fs_load_filter(fsess, arg, &e); + } + is_simple=GF_TRUE; + if (!filter && has_xopt) + continue; + } + } + + if (!filter) { + if (has_xopt) + continue; + if (!e) e = GF_FILTER_NOT_FOUND; + + if (e!=GF_FILTER_NOT_FOUND) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to load filter%s \"%s\": %s\n", is_simple ? "" : " for", arg, gf_error_to_string(e) )); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to find filter%s \"%s\"\n", is_simple ? "" : " for", arg)); + } + goto exit; + } + + if (!(fsess->flags & GF_FS_FLAG_NO_IMPLICIT)) + gf_filter_tag_subsession(filter, current_subsession_id, current_source_id); + + while (gf_list_count(links_directive)) { + char *link = gf_list_pop_front(links_directive); + e = gf_fs_process_link_directive(link, filter, loaded_filters, NULL); + if (e) goto exit; + } + gf_list_add(loaded_filters, filter); + + //implicit mode, check changes of source and sinks + if (!(fsess->flags & GF_FS_FLAG_NO_IMPLICIT)) { + if (gf_filter_is_source(filter)) { + if (prev_filter_is_not_source) { + current_source_id++; + gf_filter_tag_subsession(filter, current_subsession_id, current_source_id); + } + prev_filter_is_not_source = 0; + } else { + prev_filter_is_not_source = 1; + } + + if (gf_filter_is_sink(filter)) { + prev_filter_is_sink = GF_TRUE; + } + else if (prev_filter_is_sink && gf_filter_is_source(filter)) { + prev_filter_is_sink = GF_FALSE; + current_subsession_id++; + current_source_id=0; + prev_filter_is_not_source = 0; + gf_filter_tag_subsession(filter, current_subsession_id, current_source_id); + } + } + } + +exit: + if (!out_loaded_filters) gf_list_del(loaded_filters); + if (!out_links_directive) gf_list_del(links_directive); + + return e; +} + +GF_EXPORT +GF_Err gf_fs_parse_filter_graph_str(GF_FilterSession *fsess, char *graph_str, GF_List **out_loaded_filters, GF_List **out_links_directive) +{ + if (!graph_str) return GF_BAD_PARAM; + + char **argv = NULL; + int argc = 0; + char *token = graph_str; + char *end = NULL; + + while (*token) { + // Skip leading whitespace + while (*token && isspace(*token)) token++; + + // Handle quoted strings + if (*token == '"' || *token == '\'') { + char quote = *token++; + end = token; + while (*end && *end != quote) end++; + if (*end) *end++ = '\0'; + } else { + end = token; + while (*end && !isspace(*end)) end++; + if (*end) *end++ = '\0'; + } + + if (*token) { + argv = gf_realloc(argv, sizeof(char *) * (argc + 1)); + if (!argv) return GF_OUT_OF_MEM; + argvargc++ = token; + } + token = end; + } + + GF_Err result = gf_fs_parse_filter_graph(fsess, argc, (char **)argv, out_loaded_filters, out_links_directive); + gf_free(argv); + return result; +} + static void print_task(u32 *taskn, GF_FSTask *task, Bool for_filter) { (*taskn)++; @@ -1479,6 +1741,7 @@ break; case TASK_TYPE_SETUP: fprintf(stderr, " SetupFailure"); break; case TASK_TYPE_USER: fprintf(stderr, " UserData"); break; + case TASK_TYPE_NONE: break; } fprintf(stderr, "\n"); @@ -1562,15 +1825,26 @@ struct __pck_size_info pcki; memset(&pcki, 0, sizeof(struct __pck_size_info)); pcki.nb_packets = gf_list_count(f->postponed_packets); + u32 nb_in_eos = 0, nb_out_eos = 0; for (j=0; j<f->num_input_pids; j++) { - u32 k=0; - GF_FilterPidInst *pidi = gf_list_get(f->input_pids, k); + GF_FilterPidInst *pidi = gf_list_get(f->input_pids, j); gf_fq_enum(pidi->packets, gather_pck_size, &pcki); + if (pidi->is_end_of_stream) nb_in_eos++; + } + for (j=0; j<f->num_output_pids; j++) { + GF_FilterPid *pid = gf_list_get(f->output_pids, j); + if (pid->has_seen_eos) nb_out_eos++; } if (pcki.nb_packets) fprintf(stderr, " %d packets to process on %d input PIDs "LLU" KBytes\n", pcki.nb_packets, f->num_input_pids, pcki.all_size/1000); if (f->ref_bytes) fprintf(stderr, " "LLU" KBytes of detached packets in destinations\n", f->ref_bytes/1000); + if (nb_in_eos) + fprintf(stderr, " %u in PIDs in EOS", nb_in_eos); + if (nb_out_eos) + fprintf(stderr, " %u out PIDs have seen EOS", nb_out_eos); + if (nb_in_eos || nb_out_eos) + fprintf(stderr, "\n"); } gf_mx_v(fsess->filters_mx); } @@ -1644,10 +1918,11 @@ if (fsess->non_blocking) { GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Main thread proc enter\n")); } -#ifdef GPAC_CONFIG_EMSCRIPTEN } else { +#ifdef GPAC_CONFIG_EMSCRIPTEN GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %s proc enter\n", sys_thid)); #endif + gf_logs_thread_tag(sess_thread, GF_LOG_TAG_FILTERSESSION_THREAD); } //first time we enter the thread proc @@ -1660,17 +1935,8 @@ } #endif -#ifndef GPAC_DISABLE_REMOTERY - gf_rmt_set_thread_name(sess_thread->rmt_name); -#endif } -#ifndef GPAC_DISABLE_REMOTERY - sess_thread->rmt_tasks=40; -#endif - - gf_rmt_begin(fs_thread, 0); - safe_int_inc(&fsess->active_threads); while (1) { @@ -1687,29 +1953,19 @@ gf_fs_print_debug_info(fsess, fsess->dbg_flags ? fsess->dbg_flags : GF_FS_DEBUG_ALL); } -#ifndef GPAC_DISABLE_REMOTERY - sess_thread->rmt_tasks--; - if (!sess_thread->rmt_tasks) { - gf_rmt_end(); - gf_rmt_begin(fs_thread, 0); - sess_thread->rmt_tasks=40; - } -#endif - #if defined(GPAC_CONFIG_EMSCRIPTEN) && !defined(GPAC_DISABLE_THREADS) - if (flush_main_blocking) + if (flush_main_blocking) { emscripten_main_thread_process_queued_calls(); + } #endif safe_int_dec(&fsess->active_threads); if (!skip_next_sema_wait && (current_filter==NULL)) { - gf_rmt_begin(sema_wait, GF_RMT_AGGREGATE); GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %s Waiting scheduler %s semaphore\n", sys_thid, use_main_sema ? "main" : "secondary")); //wait for something to be done gf_fs_sema_io(fsess, GF_FALSE, use_main_sema); consecutive_filter_tasks = 0; - gf_rmt_end(); } safe_int_inc(&fsess->active_threads); skip_next_sema_wait = GF_FALSE; @@ -1801,7 +2057,7 @@ break; } if (current_filter) { - current_filter->scheduled_for_next_task = GF_FALSE; + current_filter->scheduled_for_next_task = GF_FILTER_NOT_SCHEDULED; current_filter->process_th_id = 0; gf_assert(current_filter->in_process); current_filter->in_process = GF_FALSE; @@ -2119,7 +2375,7 @@ next_task_schedule_time = 0; if (current_filter) { - current_filter->scheduled_for_next_task = GF_TRUE; + current_filter->scheduled_for_next_task = GF_FILTER_SCHEDULED; gf_assert(!current_filter->in_process); current_filter->in_process = GF_TRUE; current_filter->process_th_id = gf_th_id(); @@ -2213,7 +2469,7 @@ //requeue task gf_fq_add(current_filter->tasks, task); - //ans swap task for later requeing + //ans swap task for later requeuing if (next_task) task = next_task; } //otherwise (can't swap) keep task first in the list @@ -2226,7 +2482,7 @@ #endif } else { //no requeue, filter no longer scheduled and drop task - current_filter->scheduled_for_next_task = GF_FALSE; + current_filter->scheduled_for_next_task = GF_FILTER_NOT_SCHEDULED; //drop task from filter task list gf_fq_pop(current_filter->tasks); @@ -2253,7 +2509,7 @@ if (!requeue && !gf_fq_count(current_filter->tasks)) { current_filter->process_th_id = 0; current_filter->in_process = GF_FALSE; - current_filter->scheduled_for_next_task = GF_FALSE; + current_filter->scheduled_for_next_task = GF_FILTER_NOT_SCHEDULED; gf_mx_v(current_filter->tasks_mx); #ifndef GPAC_DISABLE_LOG gf_log_pop_extra(current_filter->logs); @@ -2301,7 +2557,7 @@ gf_fq_add(fsess->main_thread_tasks, task); #ifndef GPAC_DISABLE_THREADS - //FIXME, we sometimes miss a sema notfiy resulting in secondary tasks being locked + //FIXME, we sometimes miss a sema notify resulting in secondary tasks being locked //until we find the cause, notify secondary sema if non-main-thread tasks are scheduled and we are the only task in main if (use_main_sema && (thid==0) && fsess->threads && (gf_fq_count(fsess->main_thread_tasks)==1) && gf_fq_count(fsess->tasks)) { gf_fs_sema_io(fsess, GF_TRUE, GF_FALSE); @@ -2361,16 +2617,13 @@ //no main thread, return - if (!thid && fsess->non_blocking && !current_filter && !fsess->pid_connect_tasks_pending) { - gf_rmt_end(); + if (!thid && fsess->non_blocking && !fsess->remove_tasks && !current_filter && !fsess->pid_connect_tasks_pending) { GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Main thread proc exit\n")); safe_int_dec(&fsess->active_threads); return 0; } } - gf_rmt_end(); - safe_int_dec(&fsess->active_threads); //no main thread, return if (!thid && fsess->non_blocking) { @@ -2387,6 +2640,9 @@ safe_int_inc(&fsess->nb_threads_stopped); + if (thid) + gf_logs_thread_tag_del(sess_thread); + if (!fsess->run_status) fsess->run_status = GF_EOS; @@ -2432,7 +2688,7 @@ nb_threads = gf_list_count(fsess->threads); for (i=0;i<nb_threads; i++) { GF_SessionThread *sess_th = gf_list_get(fsess->threads, i); - if ( gf_th_run(sess_th->th, (gf_thread_run) gf_fs_thread_proc, sess_th) ==GF_OK) { + if ( gf_th_run(sess_th->th, (gf_thread_run) gf_fs_thread_proc, sess_th) == GF_OK ) { #ifdef GPAC_CONFIG_EMSCRIPTEN if (fsess->non_blocking) { safe_int_inc(&fsess->pending_threads); @@ -2472,7 +2728,10 @@ GF_FilterEvent evt; GF_FEVT_INIT(evt, GF_FEVT_STOP, task->pid); + gf_logs_thread_tag(task->pid->filter, GF_LOG_TAG_FILTER); task->pid->filter->freg->process_event(task->pid->filter, &evt); + gf_logs_thread_untag(task->pid->filter); + gf_filter_pid_set_eos(task->pid); task->pid->filter->disabled = GF_FILTER_DISABLED; safe_int_dec(&task->pid->filter->abort_pending); @@ -2525,7 +2784,7 @@ //if the PID has a codecid set (demuxed pid, e.g. ffavin or other grabbers), do not force STOP on its destinations p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); if (p) continue; - + for (k=0; k<pid->num_destinations; k++) { Bool force_disable = GF_TRUE; GF_FilterPidInst *pidi = gf_list_get(pid->destinations, k); @@ -2551,7 +2810,10 @@ GF_FilterEvent evt; GF_FEVT_INIT(evt, GF_FEVT_STOP, opid); + gf_logs_thread_tag(opid->filter, GF_LOG_TAG_FILTER); opid->filter->freg->process_event(opid->filter, &evt); + gf_logs_thread_untag(opid->filter); + gf_filter_pid_set_eos(opid); } } else { @@ -2755,7 +3017,10 @@ for (k=0; k<ipids; k++) { GF_FilterPidInst *pid = gf_list_get(f->input_pids, k); if (!pid->pid) continue; - if (pid->requires_full_data_block && (pid->nb_reagg_pck != pid->pid->nb_pck_sent) ) { + const GF_PropertyValue *p = gf_filter_pid_get_property(pid->pid, GF_PROP_PID_FAKE); + if (p && p->value.boolean) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* input PID %s: Fake\n", pid->pid->name)); + } else if (pid->requires_full_data_block && (pid->nb_reagg_pck != pid->pid->nb_pck_sent) ) { GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* input PID %s: %d frames (%d packets) received\n", pid->pid->name, pid->nb_reagg_pck, pid->pid->nb_pck_sent)); } else { GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* input PID %s: %d packets received\n", pid->pid->name, pid->pid->nb_pck_sent)); @@ -2764,7 +3029,12 @@ #ifndef GPAC_DISABLE_LOG for (k=0; k<opids; k++) { GF_FilterPid *pid = gf_list_get(f->output_pids, k); - GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* output PID %s: %d packets sent\n", pid->name, pid->nb_pck_sent)); + const GF_PropertyValue *p = gf_filter_pid_get_property(pid, GF_PROP_PID_FAKE); + if (p && p->value.boolean) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* output PID %s: Fake\n", pid->name)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* output PID %s: %d packets sent\n", pid->name, pid->nb_pck_sent)); + } } if (f->nb_errors) { GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t%d errors while processing\n", f->nb_errors)); @@ -2998,7 +3268,7 @@ //only dump not connected ones if (f->num_input_pids || f->num_output_pids || f->multi_sink_target || f->nb_tasks_done) continue; if (f->disabled==GF_FILTER_DISABLED_HIDE) continue; - if (f->filter_skiped) continue; + if (f->filter_skipped) continue; if (ignore_sinks) { Bool has_outputs; @@ -3059,7 +3329,7 @@ for (i=0; i<count; i++) { GF_Filter *f = gf_list_get(fsess->filters, i); if (f->multi_sink_target) continue; - if (f->filter_skiped) continue; + if (f->filter_skipped) continue; if (gf_list_find(filters_done, f)>=0) continue; if (f->disabled==GF_FILTER_DISABLED_HIDE) continue; if (!has_undefined) { @@ -3079,6 +3349,7 @@ u32 idx = 0; const char *argname; u32 argtype; + Bool first=GF_TRUE; while (1) { Bool found = GF_FALSE; @@ -3103,7 +3374,11 @@ } if (found) continue; - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Arg %s set but not used\n", argname)); + if (first) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("\nWarning: the following arguments have been set but not used:\n")); + first=GF_FALSE; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("%s\n", argname)); } } @@ -3153,10 +3428,12 @@ sep = strchr(name, fsess->sep_name); if (sep) sep0 = 0; } +#ifndef GPAC_DISABLE_LOG if (!strcmp(name, "LT")) { filter_parse_logs(filter, val); return; } +#endif //find arg and check if it is only a sync update - if so do it now i=0; @@ -3194,14 +3471,29 @@ gf_fs_post_task(fsess, gf_filter_update_arg_task, filter, NULL, "update_arg", upd); } -static GF_FilterProbeScore probe_meta_check_builtin_format(GF_FilterSession *fsess, GF_FilterRegister *freg, const char *url, const char *mime, char *fargs) +static GF_FilterProbeScore probe_meta_check_builtin_format(GF_FilterSession *fsess, GF_FilterRegister *freg, const char *url, const char *_mime, char *fargs) { - char szExt100; - const char *ext = gf_file_ext_start(url); + char szExt100, s_ext20, szMime100; + const char *mime = NULL; + const char *ext = NULL; + const char *_ext = gf_file_ext_start(url); u32 len=0, i, j, count = gf_list_count(fsess->registry); - if (ext) { - ext++; - len = (u32) strlen(ext); + + //lowercase ext + if (_ext) { + _ext++; + strncpy(s_ext, _ext, 19); + s_ext19=0; + strlwr(s_ext); + len = (u32) strlen(s_ext); + ext = s_ext; + } + //lowercase mime in case it is provided through external means + if (_mime) { + strncpy(szMime, _mime, 99); + szMime99 = 0; + strlwr(szMime); + mime = szMime; } //check in filter args if we have a format set, in which case replace URL ext by the given format if (fargs) { @@ -3224,6 +3516,7 @@ if (len>99) len=99; strncpy(szExt, ext_arg, len); szExtlen = 0; + strlwr(szExt); ext = szExt; } } @@ -3302,10 +3595,14 @@ GF_Filter *f; GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); if (!pidi->filter) continue; - if (pidi->filter->act_as_sink && pidi->filter->freg->use_alias - && pidi->filter->freg->use_alias(pidi->filter, url, mime_type) - ) { - return pidi->filter; + if (pidi->filter->act_as_sink && pidi->filter->freg->use_alias) { + + gf_logs_thread_tag(pidi->filter, GF_LOG_TAG_FILTER); + if (pidi->filter->freg->use_alias(pidi->filter, url, mime_type)) { + gf_logs_thread_untag(pidi->filter); + return pidi->filter; + } + gf_logs_thread_untag(pidi->filter); } //recursovely walk towards the sink f = locate_alias_sink(pidi->filter, url, mime_type); @@ -3315,8 +3612,6 @@ return NULL; } -Bool filter_solve_gdocs(const char *url, char szPathGF_MAX_PATH); - GF_Filter *gf_fs_load_source_dest_internal(GF_FilterSession *fsess, const char *url, const char *user_args, const char *parent_url, GF_Err *err, GF_Filter *filter, GF_Filter *dst_filter, Bool for_source, Bool no_args_inherit, Bool *probe_only, const GF_FilterRegister **probe_reg) { GF_FilterProbeScore score = GF_FPROBE_NOT_SUPPORTED; @@ -3356,6 +3651,7 @@ if (sep) sep0 = 0; mime_type = szMime; } + strlwr(szMime); sprintf(szForceExt, "%cext=", fsess->sep_args); char *ext = strstr(url, szForceExt); if (ext) { @@ -3367,6 +3663,7 @@ } else { szForceExt0 = 0; } + strlwr(szForceExt); } sURL = NULL; if (filter) { @@ -3375,8 +3672,8 @@ char szSolvedPathGF_MAX_PATH; Bool is_local; - if (!strncmp(url, "$GDOCS", 6)) { - if (filter_solve_gdocs(url, szSolvedPath)) + if (!strncmp(url, "$GDOCS", 6) || !strncmp(url, "$GCFG", 5)) { + if (gf_sys_solve_path(url, szSolvedPath)) url = szSolvedPath; } /*used by GUIs scripts to skip URL concatenation*/ @@ -3548,7 +3845,7 @@ user_args_len = user_args ? (u32) strlen(user_args) : 0; args = gf_malloc(sizeof(char)*5); - + sprintf(args, "%s%c", for_source ? "src" : "dst", fsess->sep_name); //path is using ':' and has options specified, inject :gpac before first option if (sep && needs_escape) { @@ -3588,24 +3885,27 @@ for (i=0; i<fcount; i++) { GF_Filter *f = gf_list_get(fsess->filters, i); if (f->freg != candidate_freg) continue; + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); if (f->freg->use_alias(f, sURL, mime_type)) { + gf_logs_thread_untag(f); alias_for_filter = f; break; } + gf_logs_thread_untag(f); } } if (!filter) { filter = gf_filter_new(fsess, candidate_freg, args, NULL, arg_type, err, alias_for_filter, GF_FALSE); } else { - //destroy underlying JS object - gf_filter_new_finalize always reassign it to JS_UNDEFINED + //destroy underlying JS object - gf_filter_new_finalize always reassign it to JS_UNDEFINED #ifdef GPAC_HAS_QJS - jsfs_on_filter_destroyed(filter); + jsfs_on_filter_destroyed(filter); #endif if (filter->session->on_filter_create_destroy) filter->session->on_filter_create_destroy(filter->session->rt_udta, filter, GF_TRUE); - - filter->freg = candidate_freg; + + filter->freg = candidate_freg; e = gf_filter_new_finalize(filter, args, arg_type); if (err) *err = e; } @@ -4047,10 +4347,18 @@ } #endif -static GF_DownloadManager *gf_fs_get_download_manager(GF_FilterSession *fs) +GF_DownloadManager *gf_fs_get_download_manager(GF_FilterSession *fs) { #ifdef GPAC_USE_DOWNLOADER if (!fs->download_manager) { + +#ifdef GPAC_CONFIG_EMSCRIPTEN + if (!fs->non_blocking) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Fetch cannot be used in blocking mode as it requires returning to main/worker JS event loop\n")); + return NULL; + } +#endif + fs->download_manager = gf_dm_new(fs); #ifndef GPAC_DISABLE_NETWORK @@ -4103,7 +4411,7 @@ if (!fs) return GF_BAD_PARAM; e = fs->last_connect_error; fs->last_connect_error = GF_OK; - return e; + return e<0 ? e : GF_OK; } GF_EXPORT @@ -4122,9 +4430,7 @@ void *callback; Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms); Bool (*task_execute_filter) (GF_Filter *filter, void *callback, u32 *reschedule_ms); -#ifndef GPAC_DISABLE_REMOTERY - rmtU32 rmt_hash; -#endif + } GF_UserTask; static void gf_fs_user_task(GF_FSTask *task) @@ -4133,9 +4439,6 @@ GF_UserTask *utask = (GF_UserTask *)task->udta; task->schedule_next_time = 0; -#ifndef GPAC_DISABLE_REMOTERY - gf_rmt_begin_hash(task->log_name, GF_RMT_AGGREGATE, &utask->rmt_hash); -#endif if (utask->task_execute) { task->requeue_request = utask->task_execute(utask->fsess, utask->callback, &reschedule_ms); } else if (task->filter) { @@ -4143,7 +4446,6 @@ } else { task->requeue_request = 0; } - gf_rmt_end(); //if no requeue request or if we are in final flush, don't re-execute if (!task->requeue_request || utask->fsess->in_final_flush) { gf_free(utask); @@ -4158,7 +4460,7 @@ } -static GF_Err gf_fs_post_user_task_internal(GF_FilterSession *fsess, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name, Bool force_main) +static GF_Err gf_fs_post_user_task_internal(GF_FilterSession *fsess, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name, Bool force_main, u32 delay_ms) { GF_UserTask *utask; char *_log_name; @@ -4170,20 +4472,26 @@ utask->task_execute = task_execute; //dup mem for user task _log_name = gf_strdup(log_name ? log_name : "user_task"); - gf_fs_post_task_ex(fsess, gf_fs_user_task, NULL, NULL, _log_name, utask, GF_FALSE, force_main, GF_FALSE, TASK_TYPE_USER); + gf_fs_post_task_ex(fsess, gf_fs_user_task, NULL, NULL, _log_name, utask, GF_FALSE, force_main, GF_FALSE, TASK_TYPE_USER, delay_ms); return GF_OK; } GF_EXPORT GF_Err gf_fs_post_user_task(GF_FilterSession *fsess, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name) { - return gf_fs_post_user_task_internal(fsess, task_execute, udta_callback, log_name, fsess->force_main_thread_tasks); + return gf_fs_post_user_task_internal(fsess, task_execute, udta_callback, log_name, fsess->force_main_thread_tasks, 0); +} + +GF_EXPORT +GF_Err gf_fs_post_user_task_delay(GF_FilterSession *fsess, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name, u32 delay_ms) +{ + return gf_fs_post_user_task_internal(fsess, task_execute, udta_callback, log_name, fsess->force_main_thread_tasks, delay_ms); } GF_EXPORT GF_Err gf_fs_post_user_task_main(GF_FilterSession *fsess, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name) { - return gf_fs_post_user_task_internal(fsess, task_execute, udta_callback, log_name, GF_TRUE); + return gf_fs_post_user_task_internal(fsess, task_execute, udta_callback, log_name, GF_TRUE, 0); } GF_EXPORT @@ -4282,7 +4590,7 @@ stats->filter = f; stats->filter_alias = f->multi_sink_target; if (f->multi_sink_target) return GF_OK; - + stats->percent = f->status_percent>10000 ? -1 : (s32) f->status_percent; stats->status = f->status_str; stats->nb_pck_processed = f->nb_pck_processed; @@ -4630,7 +4938,11 @@ if (evt->base.type==GF_FEVT_USER) { if (f->freg->process_event && f->event_target) { gf_mx_p(f->tasks_mx); + + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); f->freg->process_event(f, evt); + gf_logs_thread_untag(f); + gf_mx_v(f->tasks_mx); ret = GF_TRUE; } @@ -4656,7 +4968,10 @@ if (!f->event_target) continue; gf_mx_p(f->tasks_mx); + gf_logs_thread_tag(f, GF_LOG_TAG_FILTER); canceled = f->freg->process_event(f, evt); + gf_logs_thread_untag(f); + gf_mx_v(f->tasks_mx); ret = GF_TRUE; if (canceled) break; @@ -4839,6 +5154,18 @@ gf_mx_v(fsess->filters_mx); } +GF_EXPORT +Bool gf_fs_check_filter(GF_FilterSession *fs, GF_Filter *filter) +{ + if (!fs) return GF_FALSE; + s32 res=-1; + gf_mx_p(fs->filters_mx); + res = gf_list_find(fs->filters, filter); + gf_mx_v(fs->filters_mx); + if (res<0) return GF_FALSE; + if (filter->removed) return GF_FALSE; + return GF_TRUE; +} #ifdef GPAC_CONFIG_EMSCRIPTEN void gf_fs_force_non_blocking(GF_FilterSession *fs)
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_session.h -> gpac-26.02.0.tar.gz/src/filter_core/filter_session.h
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2026 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -107,8 +107,6 @@ const GF_PropertyValue *gf_props_enum_property(GF_PropertyMap *props, u32 *io_idx, u32 *prop_4cc, const char **prop_name); -Bool gf_props_4cc_check_props(); - void gf_props_del_property(GF_PropertyEntry *it); @@ -192,11 +190,14 @@ //2 bits for crypt type GF_PCK_CRYPT_POS = 15, GF_PCK_CRYPT_MASK = 0x3 << GF_PCK_CRYPT_POS, - //2 bits for crypt type + //2 bits for command type GF_PCK_CMD_POS = 13, GF_PCK_CMD_MASK = 0x3 << GF_PCK_CMD_POS, GF_PCKF_FORCE_MAIN = 1<<12, - //RESERVED bits 8,11 + //only valid when GF_PCK_CMD_PID_EOS is set + GF_PCKF_IS_FLUSH = 1<<11, + GF_PCKF_IS_SWITCH_FRAME = 1<<10, + //RESERVED bits 8,9 //2 bits for is_leading GF_PCK_ISLEADING_POS = 6, @@ -258,8 +259,8 @@ struct __gf_filter_pck *reference; GF_FilterFrameInterface *frame_ifce; - - // properties applying to this packet + + //properties applying to this packet GF_PropertyMap *props; //pid properties applying to this packet GF_PropertyMap *pid_props; @@ -267,8 +268,8 @@ //for shared memory packets: 0: cloned mem, 1: read/write mem from source filter, 2: read-only mem from filter //note that packets with frame_ifce are always considered as read-only memory u8 filter_owns_mem; + //0: regular packet, 1: dangling packet with copied mem, 2: dangling packet with shared mem u8 is_dangling; - }; /*! @@ -276,6 +277,19 @@ */ typedef void (*gf_fs_task_callback)(GF_FSTask *task); +//task type used to free up resources when a filter task is being canceled (configure error) +typedef enum +{ + //no free required + TASK_TYPE_NONE=0, + //task udta is a GF_FilterEvent + TASK_TYPE_EVENT, + //task udta is a struct _gf_filter_setup_failure (simple free needed) + TASK_TYPE_SETUP, + //task udta is a GF_UserTask structure (simple free needed), and task logname shall be freed + TASK_TYPE_USER, +} GF_TaskClassType; + struct __gf_fs_task { //flag set for tasks registered with main task list, eg having incremented the task_pending counter. @@ -296,30 +310,17 @@ GF_FilterPid *pid; const char *log_name; void *udta; - u32 class_type; + GF_TaskClassType class_type; u32 thid; }; void gf_fs_post_task(GF_FilterSession *fsess, gf_fs_task_callback fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta); -//task type used to free up resources when a filter task is being canceled (configure error) -typedef enum -{ - //no free required - TASK_TYPE_NONE=0, - //task udta is a GF_FilterEvent - TASK_TYPE_EVENT, - //task udta is a struct _gf_filter_setup_failure (simple free needed) - TASK_TYPE_SETUP, - //task udta is a GF_UserTask structure (simple free needed), and task logname shall be freed - TASK_TYPE_USER, -} GF_TaskClassType; - /* extended version of gf_fs_post_task force_direct_call shall only be true for gf_filter_process_task */ -void gf_fs_post_task_ex(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, Bool is_configure, Bool force_main_thread, Bool force_direct_call, GF_TaskClassType class_type); +void gf_fs_post_task_ex(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, Bool is_configure, Bool force_main_thread, Bool force_direct_call, GF_TaskClassType class_type, u32 delay_ms); void gf_fs_post_task_class(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, GF_TaskClassType class_type); @@ -340,11 +341,6 @@ u64 run_time; u64 active_time; -#ifndef GPAC_DISABLE_REMOTERY - u32 rmt_tasks; - char rmt_name20; -#endif - } GF_SessionThread; typedef enum { @@ -397,6 +393,7 @@ volatile Bool in_main_sem_wait; volatile u32 active_threads; + volatile u32 remove_tasks; //if more than one thread, this mutex protects access to loaded filters list, to avoid concurrent calls to destruct and //filter testing (graph resolution, update sending, non thread-safe graph traversal...) GF_Mutex *filters_mx; @@ -492,7 +489,7 @@ GF_List *parsed_args; - char sep_args, sep_name, sep_frag, sep_list, sep_neg; + char sep_args, sep_name, sep_frag, sep_list, sep_neg, sep_link; char *blacklist; Bool init_done; @@ -507,7 +504,6 @@ #ifdef GPAC_HAS_QJS struct JSContext *js_ctx; GF_List *jstasks; - struct __jsfs_task *new_f_task, *del_f_task, *on_evt_task, *on_auth_task; #endif gf_fs_on_filter_creation on_filter_create_destroy; @@ -529,6 +525,7 @@ u32 dbg_flags; + Bool check_props; }; #ifdef GPAC_HAS_QJS @@ -581,6 +578,16 @@ GF_FILTER_DISABLED_HIDE, } GF_FilterDisableType; +typedef enum +{ + //filter is not scheduled + GF_FILTER_NOT_SCHEDULED = 0, + //filter is scheduled by main scheduler + GF_FILTER_SCHEDULED, + //filter is scheduled by a direct dispatch call + GF_FILTER_DIRECT_SCHEDULED, +} GF_FilterScheduledType; + //#define DEBUG_BLOCKMODE struct __gf_filter @@ -599,7 +606,7 @@ char *dynamic_source_ids; char *restricted_source_id; - + //parent media session GF_FilterSession *session; @@ -633,7 +640,7 @@ GF_FilterQueue *tasks; //set to true when the filter is present or to be added in the main task list //this variable is unset in a zone protected by task_mx - volatile Bool scheduled_for_next_task; + volatile GF_FilterScheduledType scheduled_for_next_task; //set to true when the filter is being processed by a thread volatile Bool in_process; u32 process_th_id, restrict_th_idx; @@ -674,7 +681,7 @@ //!this mutex protects: //- the filter task queue, when reordering tasks for later processing while other threads try to post to the filter task queue //- the list of input pid and output pid destinations, which can be added from different threads for a same pid (fan-out) - //-the blocking state of the filter + //- the blocking state of the filter GF_Mutex *tasks_mx; //list of output pids to be configured @@ -692,6 +699,7 @@ volatile u32 detach_pid_tasks_pending; volatile u32 nb_shared_packets_out; volatile u32 abort_pending; + volatile u32 pid_rem_packet_pending; GF_List *postponed_packets; //list of blacklisted filtered registries @@ -725,6 +733,10 @@ u64 nb_hw_pck_sent; //number of processing errors in the lifetime of the filter u32 nb_errors; + //number of consecutive errors, reset at each successfull process - only used for logs + //Difference with nb_consecutive_errors: nb_consecutive_errors is reset whenever there is a packet IO + //to avoid killing a filter were all process() lead to error due to input bitstream + u32 nb_current_errors; //number of bytes sent by this filter u64 nb_bytes_sent; @@ -824,15 +836,12 @@ //for encoder filters, set to the corresponding stream type - used to discard filters during the resolution u32 encoder_codec_id; GF_PropStringList skip_cids; - Bool filter_skiped; + Bool filter_skipped; Bool act_as_sink; Bool require_source_id; -#ifndef GPAC_DISABLE_REMOTERY - rmtU32 rmt_hash; -#endif - //signals tha pid info has changed, to notify the filter chain + //signals that pid info has changed, to notify the filter chain Bool pid_info_changed; //set to 1 when one or more input pid to the filter is on end of state, set to 2 if the filter dispatch a packet while in this state @@ -857,6 +866,7 @@ Bool report_updated; char *instance_description, *instance_version, *instance_author, *instance_help; + GF_ClassTypeHint instance_class_hint; GF_FilterArgs *instance_args; GF_Filter *multi_sink_target; @@ -900,10 +910,12 @@ GF_Filter *gf_fs_load_source_dest_internal(GF_FilterSession *fsess, const char *url, const char *args, const char *parent_url, GF_Err *err, GF_Filter *filter, GF_Filter *dst_filter, Bool for_source, Bool no_args_inherit, Bool *probe_only, const GF_FilterRegister **probe_reg); -void gf_filter_pid_inst_delete_task(GF_FSTask *task); +void gf_fs_post_pid_instance_delete_task(GF_FilterSession *session, GF_Filter *filter, GF_FilterPid *pid, GF_FilterPidInst *pidinst); + void gf_filter_pid_inst_reset(GF_FilterPidInst *pidinst); void gf_filter_pid_inst_del(GF_FilterPidInst *pidinst); +void gf_filter_pid_inst_check_delete(GF_FilterPidInst *pidinst); void gf_filter_forward_clock(GF_Filter *filter); @@ -925,6 +937,18 @@ GF_EventPropagateType recursive; } GF_FilterUpdate; +typedef enum +{ + //no discard of input packets + GF_PIDI_DISCARD_OFF = 0, + //discard of input packets + GF_PIDI_DISCARD_ON, + //temporary mode to discard inputs but process all reconfiguration packets + GF_PIDI_DISCARD_RCFG, + //temporary mode to discard inputs but process all reconfiguration packets and delete the PID instance + //at the end of the reconfigure task + GF_PIDI_DISCARD_RCFG_DELETE, +} GF_PidInstDiscardMode; //structure for input pids, in order to handle fan-outs of a pid into several filters struct __gf_filter_pid_inst @@ -949,9 +973,7 @@ volatile u32 discard_packets; Bool force_reconfig; - - //set by filter - u32 discard_inputs; + GF_PidInstDiscardMode discard_inputs; //amount of media data in us in the packet queue - concurrent inc/dec volatile s64 buffer_duration; @@ -975,13 +997,15 @@ Bool keepalive_signaled; Bool is_playing, is_paused; u8 play_queued, stop_queued; - + volatile u32 nb_eos_signaled; Bool is_encoder_input; Bool is_decoder_input; GF_PropertyMap *reconfig_pid_props; - + + GF_Filter *swap_source; + Bool in_swap; //clock handling by the consumer: the clock values are not automatically dispatched to the output pids and are kept //available as regular packets in the input pid Bool handles_clock_references; @@ -1045,7 +1069,7 @@ volatile u32 nb_shared_packets_out; GF_PropertyMap *infos; - + //set whenever an eos packet is dispatched, reset whenever a regular packet is dispatched Bool has_seen_eos; Bool eos_keepalive; @@ -1083,7 +1107,7 @@ u32 playback_speed_scaler; GF_Fraction64 last_ts_sent; - + Bool initial_play_done; Bool is_playing; void *udta; @@ -1104,7 +1128,6 @@ volatile u32 num_pidinst_del_pending; u32 link_flags; - }; @@ -1123,11 +1146,14 @@ void gf_filter_pid_reconfigure_task(GF_FSTask *task); void gf_filter_pid_reconfigure_task_discard(GF_FSTask *task); void gf_filter_update_arg_task(GF_FSTask *task); -void gf_filter_pid_disconnect_task(GF_FSTask *task); void gf_filter_remove_task(GF_FSTask *task); void gf_filter_pid_detach_task(GF_FSTask *task); void gf_filter_pid_detach_task_no_flush(GF_FSTask *task); +//disconnect this pid instance from its current decoder +void gf_fs_post_disconnect_task(GF_FilterSession *session, GF_Filter *filter, GF_FilterPid *pid); + + u32 gf_filter_caps_bundle_count(const GF_FilterCapability *caps, u32 nb_caps); void gf_filter_set_id(GF_Filter *filter, const char *ID); @@ -1135,6 +1161,8 @@ void gf_filter_check_pending_pids(GF_Filter *filter); +Bool gf_filter_pid_caps_negociate_match(GF_FilterPid *pid, const GF_FilterRegister *freg); + typedef struct { u32 code; @@ -1181,7 +1209,7 @@ GF_Filter *gf_filter_pid_resolve_link_for_caps(GF_FilterPid *pid, GF_Filter *dst, Bool check_reconfig_only); u32 gf_filter_pid_resolve_link_length(GF_FilterPid *pid, GF_Filter *dst); -Bool gf_filter_pid_caps_match(GF_FilterPid *src_pid, const GF_FilterRegister *freg, GF_Filter *filter_inst, u8 *priority, u32 *dst_bundle_idx, GF_Filter *dst_filter, s32 for_bundle_idx); +Bool gf_filter_pid_caps_match(GF_FilterPid *src_pid, const GF_FilterRegister *freg, GF_Filter *filter_inst, s16 *priority, u32 *dst_bundle_idx, GF_Filter *dst_filter, s32 for_bundle_idx); void gf_filter_relink_dst(GF_FilterPidInst *pidinst, GF_Err reason); @@ -1228,9 +1256,9 @@ u16 dst_cap_idx; u8 weight; u8 status; - u8 priority; u8 loaded_filter_only; - u32 disabled_depth; + s16 priority; + //stream type of the output cap of src. Might be: // -1 if multiple stream types are defined in the cap (demuxers, encoders/decoders bundles) // 0 if not specified @@ -1247,7 +1275,7 @@ struct __freg_desc *destination; u32 cap_idx; GF_BundleCache *bundle_cache; - u8 priority; + s16 priority; u8 in_edges_enabling; u8 has_input; //cache value of gf_filter_has_in_caps u8 has_output; //cache value of gf_filter_has_out_caps @@ -1276,16 +1304,30 @@ Bool gf_filter_update_arg_apply(GF_Filter *filter, const char *arg_name, const char *arg_value, Bool is_sync_call); +typedef struct +{ + u32 distance; + u32 priority; +} GF_LinkInfo; -GF_List *gf_filter_pid_compute_link(GF_FilterPid *pid, GF_Filter *dst); +GF_List *gf_filter_pid_compute_link(GF_FilterPid *pid, GF_Filter *dst, GF_List *tmp_blacklist, GF_LinkInfo *link_info); GF_PropertyValue gf_filter_parse_prop_solve_env_var(GF_FilterSession *fs, GF_Filter *f, u32 type, const char *name, const char *value, const char *enum_values); //check if item can be added to a reservoir queue, returns GF_TRUE if not added Bool gf_fq_res_add(GF_FilterQueue *fq, void *item); -#endif //_GF_FILTER_SESSION_H_ - +Bool filter_source_id_match(GF_FilterPid *src_pid, const char *id, GF_Filter *dst_filter, Bool *pid_excluded, Bool *needs_clone, const char *source_ids); +const char *gf_filter_last_id_in_chain(GF_Filter *filter, Bool ignore_first); +enum { + GF_LOG_TAG_FILTERSESSION = 0, + GF_LOG_TAG_FILTERSESSION_THREAD = 1, + GF_LOG_TAG_FILTER = 2 +}; +void gf_logs_thread_tag(void *tag_val, u32 tag_type); +void gf_logs_thread_untag(void *tag_val); +void gf_logs_thread_tag_del(void *tag_val); +#endif //_GF_FILTER_SESSION_H_
View file
gpac-2.4.0.tar.gz/src/filter_core/filter_session_js.c -> gpac-26.02.0.tar.gz/src/filter_core/filter_session_js.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2026 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -33,58 +33,81 @@ static GF_Err gf_fs_load_script_ex(GF_FilterSession *fs, const char *jsfile, JSContext *in_ctx, GF_Filter *for_filter); +enum { + GF_JSFS_TASK_USER = 0, + GF_JSFS_TASK_FILTER_NEW, + GF_JSFS_TASK_FILTER_DEL, + GF_JSFS_TASK_EVENT, + GF_JSFS_TASK_AUTHENTICATION, + GF_JSFS_TASK_REMOVE, +}; + static JSClassID fs_class_id; typedef struct __jsfs_task { JSValue fun; JSValue _obj; - - //for event callback, we allow 2 user-defined functions - JSValue fun2; - JSValue _obj2; - u32 type; + u64 id; + //the parent JS context of the task, may be different from FilterSession JS context JSContext *ctx; } JSFS_Task; +typedef struct +{ + GF_FilterSession *fs; + Bool owns_api; +} JSFS_FilterSession; + +#ifndef GPAC_DISABLE_THREADS +void gf_mx_toggle_log(GF_Mutex *mx, Bool nolog); +#endif + static void jsfs_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { - GF_FilterSession *fs = JS_GetOpaque(val, fs_class_id); - if (fs) { - u32 i, count=gf_list_count(fs->jstasks); - for (i=0; i<count; i++) { + JSFS_FilterSession *fsjs = JS_GetOpaque(val, fs_class_id); + //only perform GC for the object marked as owning the API, otherwise we would + //mark the objects in the JS task list as many times as there are contexts + if (fsjs && fsjs->fs && fsjs->owns_api) { + GF_FilterSession *fs = fsjs->fs; + u32 i, count=gf_list_count(fs->jstasks); + for (i=0; i<count; i++) { JSFS_Task *task = gf_list_get(fs->jstasks, i); - JS_MarkValue(rt, task->fun, mark_func); - JS_MarkValue(rt, task->_obj, mark_func); - if (!JS_IsUndefined(task->_obj2)) { - JS_MarkValue(rt, task->fun2, mark_func); - JS_MarkValue(rt, task->_obj2, mark_func); - } + JS_MarkValue(rt, task->fun, mark_func); + JS_MarkValue(rt, task->_obj, mark_func); } + +#ifndef GPAC_DISABLE_THREADS + gf_mx_toggle_log(fs->filters_mx, GF_TRUE); +#endif gf_fs_lock_filters(fs, GF_TRUE); count = gf_list_count(fs->filters); - for (i=0; i<count; i++) { + for (i=0; i<count; i++) { GF_Filter *f = gf_list_get(fs->filters, i); if (!JS_IsUndefined(f->jsval)) JS_MarkValue(rt, f->jsval, mark_func); } gf_fs_lock_filters(fs, GF_FALSE); - } +#ifndef GPAC_DISABLE_THREADS + gf_mx_toggle_log(fs->filters_mx, GF_FALSE); +#endif + } } #define GF_FS_FLAG_USER_SESSION (1<<29) static void jsfs_finalizer(JSRuntime *rt, JSValue val) { - GF_FilterSession *fs = JS_GetOpaque(val, fs_class_id); - if (!fs) return; + JSFS_FilterSession *fsjs = JS_GetOpaque(val, fs_class_id); + if (!fsjs) return; - if (fs->flags & GF_FS_FLAG_USER_SESSION ) { - gf_fs_del(fs); + if (fsjs->fs && (fsjs->fs->flags & GF_FS_FLAG_USER_SESSION) ) { + gf_fs_del(fsjs->fs); } + gf_free(fsjs); } static JSClassDef fs_class = { - "FilterSession", + "FilterSession", .gc_mark = jsfs_mark, .finalizer = jsfs_finalizer }; @@ -92,20 +115,20 @@ static JSClassID fs_f_class_id; static JSClassDef fs_f_class = { - "Filter", + "Filter", }; enum { JSFS_NB_FILTERS = 0, + JSFS_TYPE, JSFS_LAST_TASK, JSFS_HTTP_MAX_RATE, JSFS_HTTP_RATE, - JSFS_RMT_SAMPLING, JSFS_CONNECTED, JSFS_LAST_PROCESS_ERR, JSFS_LAST_CONNECT_ERR, - JSFS_PATH + JSFS_PATH, }; GF_Filter *jsff_get_filter(JSContext *c, JSValue this_val) @@ -115,35 +138,32 @@ GF_FilterSession *jsff_get_session(JSContext *c, JSValue this_val) { - return JS_GetOpaque(this_val, fs_class_id); + JSFS_FilterSession *fsjs = JS_GetOpaque(this_val, fs_class_id); + if (!fsjs) return NULL; + return fsjs->fs; } static JSValue jsfs_new_filter_obj(JSContext *ctx, GF_Filter *f); -static void jsfs_exec_task_custom(JSFS_Task *task, const char *text, GF_Filter *new_filter, GF_Filter *del_filter) +static void jsfs_exec_task_custom(JSFS_Task *task, const char *text, GF_Filter *for_filter) { JSValue ret, arg; gf_js_lock(task->ctx, GF_TRUE); if (text) { arg = JS_NewString(task->ctx, text); - } else if (new_filter) { - arg = jsfs_new_filter_obj(task->ctx, new_filter); - } else { - gf_assert(del_filter); - arg = JS_DupValue(task->ctx, del_filter->jsval); + } else if (task->type==GF_JSFS_TASK_FILTER_NEW) { + gf_assert(for_filter); + arg = jsfs_new_filter_obj(task->ctx, for_filter); + } else if (task->type==GF_JSFS_TASK_FILTER_DEL) { + gf_assert(for_filter); + arg = JS_DupValue(task->ctx, for_filter->jsval); } ret = JS_Call(task->ctx, task->fun, task->_obj, 1, &arg); JS_FreeValue(task->ctx, arg); - if (del_filter) { - JS_SetOpaque(del_filter->jsval, NULL); - JS_FreeValue(task->ctx, del_filter->jsval); - del_filter->jsval = JS_UNDEFINED; - } - if (JS_IsException(ret)) { js_dump_error(task->ctx); } @@ -151,23 +171,27 @@ js_std_loop(task->ctx); gf_js_lock(task->ctx, GF_FALSE); } - -static void jsfs_rmt_user_callback(void *udta, const char* text) +static void jsfs_exec_tasks_custom(GF_FilterSession *fs, u32 task_type, const char *text, GF_Filter *for_filter) { - JSFS_Task *task = udta; - if (!task) return; - jsfs_exec_task_custom(task, text, NULL, NULL); + u32 i=0; + JSFS_Task *task; + while ((task = gf_list_enum(fs->jstasks, &i))) { + if (task->type != task_type) continue; + jsfs_exec_task_custom(task, text, for_filter); + } } static JSValue jsfs_prop_get(JSContext *ctx, JSValueConst this_val, int magic) { - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); switch (magic) { case JSFS_NB_FILTERS: return JS_NewInt32(ctx, gf_fs_get_filters_count(fs)); + case JSFS_TYPE: + return JS_NewString(ctx, "FilterSession"); case JSFS_LAST_TASK: return gf_fs_is_last_task(fs) ? JS_TRUE : JS_FALSE; case JSFS_CONNECTED: @@ -186,8 +210,6 @@ return JS_NewInt32(ctx, gf_dm_get_global_rate(fs->download_manager) ); #endif return JS_NULL; - case JSFS_RMT_SAMPLING: - return JS_NewBool(ctx, gf_sys_profiler_sampling_enabled() ); case JSFS_LAST_CONNECT_ERR: return JS_NewInt32(ctx, gf_fs_get_last_process_error(fs) ); case JSFS_LAST_PROCESS_ERR: @@ -200,7 +222,7 @@ static JSValue jsfs_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) { - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); @@ -214,9 +236,6 @@ } #endif break; - case JSFS_RMT_SAMPLING: - gf_sys_profiler_enable_sampling(JS_ToBool(ctx, value) ? GF_TRUE : GF_FALSE); - break; } return JS_UNDEFINED; } @@ -227,28 +246,33 @@ s32 ret_val; Bool do_free=GF_TRUE; JSFS_Task *task = udta; - gf_js_lock(task->ctx, GF_TRUE); - ret = JS_Call(task->ctx, task->fun, task->_obj, 0, NULL); - *timeout_ms = 0; - if (JS_IsException(ret)) { - js_dump_error(task->ctx); - } - else if (JS_IsBool(ret)) { - if (JS_ToBool(task->ctx, ret)) - do_free = GF_FALSE; - } - else if (JS_IsInteger(ret)) { - JS_ToInt32(task->ctx, (int*)&ret_val, ret); - if (ret_val>=0) { - *timeout_ms = ret_val; - do_free = GF_FALSE; + if (task->type == GF_JSFS_TASK_REMOVE) { + do_free = GF_TRUE; + } else if (task->type == GF_JSFS_TASK_USER) { + gf_js_lock(task->ctx, GF_TRUE); + ret = JS_Call(task->ctx, task->fun, task->_obj, 0, NULL); + + *timeout_ms = 0; + if (JS_IsException(ret)) { + js_dump_error(task->ctx); + } + else if (JS_IsBool(ret)) { + if (JS_ToBool(task->ctx, ret)) + do_free = GF_FALSE; + } + else if (JS_IsInteger(ret)) { + JS_ToInt32(task->ctx, (int*)&ret_val, ret); + if (ret_val>=0) { + *timeout_ms = ret_val; + do_free = GF_FALSE; + } } - } - JS_FreeValue(task->ctx, ret); - js_std_loop(task->ctx); - gf_js_lock(task->ctx, GF_FALSE); + JS_FreeValue(task->ctx, ret); + js_std_loop(task->ctx); + gf_js_lock(task->ctx, GF_FALSE); + } if (do_free) { JS_FreeValue(task->ctx, task->fun); @@ -263,34 +287,43 @@ static JSValue jsfs_post_task(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSFS_Task *task; + u32 delay=0; const char *tname = NULL; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs || !argc) return GF_JS_EXCEPTION(ctx); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); + if (!fs || !argc) return GF_JS_EXCEPTION(ctx); if (!JS_IsFunction(ctx, argv0) ) return GF_JS_EXCEPTION(ctx); GF_SAFEALLOC(task, JSFS_Task); if (!task) return GF_JS_EXCEPTION(ctx); task->ctx = ctx; + task->id = (u64)(uintptr_t)task; + task->type = GF_JSFS_TASK_USER; if (argc>1) { - tname = JS_ToCString(ctx, argv1); + if (JS_IsString(argv1)) { + tname = JS_ToCString(ctx, argv1); + if (tname && (argc>2)) + JS_ToInt32(ctx, &delay, argv2); + } else { + JS_ToInt32(ctx, &delay, argv1); + } } task->fun = JS_DupValue(ctx, argv0); task->_obj = JS_DupValue(ctx, this_val); gf_list_add(fs->jstasks, task); - gf_fs_post_user_task(fs, jsfs_task_exec, task, tname ? tname : "task"); + gf_fs_post_user_task_delay(fs, jsfs_task_exec, task, tname ? tname : "task", delay); if (tname) JS_FreeCString(ctx, tname); - return JS_UNDEFINED; + return JS_NewInt64(ctx, task->id); } static JSValue jsfs_abort(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { u32 flush_type = GF_FS_FLUSH_NONE; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs) return GF_JS_EXCEPTION(ctx); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); + if (!fs) return GF_JS_EXCEPTION(ctx); if (argc) { JS_ToInt32(ctx, &flush_type, argv0); } @@ -300,8 +333,8 @@ static JSValue jsfs_lock_filters(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { Bool do_lock; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs || !argc) return GF_JS_EXCEPTION(ctx); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); + if (!fs || !argc) return GF_JS_EXCEPTION(ctx); if (JS_IsBool(argv0)) do_lock = JS_ToBool(ctx, argv0); else return GF_JS_EXCEPTION(ctx); @@ -309,45 +342,10 @@ return JS_UNDEFINED; } -Bool gf_sys_enable_remotery(Bool start, Bool is_shutdown); - -static JSValue jsfs_enable_rmt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs) return GF_JS_EXCEPTION(ctx); - gf_sys_enable_remotery(GF_TRUE, GF_FALSE); - return JS_UNDEFINED; -} - -static JSValue jsfs_rmt_log(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - const char *msg; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs ||!argc) return GF_JS_EXCEPTION(ctx); - msg = JS_ToCString(ctx, argv0); - if (!msg) return GF_JS_EXCEPTION(ctx); - gf_sys_profiler_log(msg); - JS_FreeCString(ctx, msg); - return JS_UNDEFINED; -} - -static JSValue jsfs_rmt_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - const char *msg; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs ||!argc) return GF_JS_EXCEPTION(ctx); - msg = JS_ToCString(ctx, argv0); - if (!msg) return GF_JS_EXCEPTION(ctx); - gf_sys_profiler_send(msg); - JS_FreeCString(ctx, msg); - return JS_UNDEFINED; -} - - void jsfs_on_filter_created(GF_Filter *new_filter) { - if (!new_filter->session->new_f_task) return; - jsfs_exec_task_custom(new_filter->session->new_f_task, NULL, new_filter, NULL); + if (!new_filter->session->jstasks) return; + jsfs_exec_tasks_custom(new_filter->session, GF_JSFS_TASK_FILTER_NEW, NULL, new_filter); } void jsfs_on_filter_destroyed(GF_Filter *del_filter) @@ -355,67 +353,67 @@ if (! JS_IsUndefined(del_filter->jsval)) { void *p = JS_GetOpaque(del_filter->jsval, fs_f_class_id); if (!p) return; - if (del_filter->session->del_f_task) { - jsfs_exec_task_custom(del_filter->session->del_f_task, NULL, NULL, del_filter); - } else { - JSRuntime *gf_js_get_rt(); - JSRuntime *rt = gf_js_get_rt(); - if (rt) { - gf_js_lock(NULL, GF_TRUE); - JS_FreeValueRT(rt, del_filter->jsval); - gf_js_lock(NULL, GF_FALSE); - } + + + if (del_filter->session->jstasks) { + jsfs_exec_tasks_custom(del_filter->session, GF_JSFS_TASK_FILTER_DEL, NULL, del_filter); } + JSRuntime *gf_js_get_rt(); + JSRuntime *rt = gf_js_get_rt(); + if (!rt) return; + + gf_js_lock(NULL, GF_TRUE); + JS_SetOpaque(del_filter->jsval, NULL); + JS_FreeValueRT(rt, del_filter->jsval); del_filter->jsval = JS_UNDEFINED; + gf_js_lock(NULL, GF_FALSE); } } Bool jsfs_on_event(GF_FilterSession *fs, GF_Event *evt) { GF_FilterEvent fevt; - Bool pass2=GF_FALSE; JSValue fun, obj; JSValue js_init_evt_obj(JSContext *ctx, const GF_FilterEvent *evt); JSValue arg, ret; Bool res; - gf_assert(fs->on_evt_task); - gf_js_lock(fs->on_evt_task->ctx, GF_TRUE); + u32 idx=0; + JSFS_Task *task; + while ((task = gf_list_enum(fs->jstasks, &idx))) { + if (task->type != GF_JSFS_TASK_EVENT) continue; - memset(&fevt, 0, sizeof(GF_FilterEvent)); - fevt.user_event.event = *evt; - fevt.base.type = GF_FEVT_USER; + gf_js_lock(task->ctx, GF_TRUE); - fun = fs->on_evt_task->fun; - obj = fs->on_evt_task->_obj; + memset(&fevt, 0, sizeof(GF_FilterEvent)); + fevt.user_event.event = *evt; + fevt.base.type = GF_FEVT_USER; + fun = task->fun; + obj = task->_obj; -retry: - arg = js_init_evt_obj(fs->on_evt_task->ctx, &fevt); - ret = JS_Call(fs->on_evt_task->ctx, fun, obj, 1, &arg); - JS_SetOpaque(arg, NULL); - JS_FreeValue(fs->on_evt_task->ctx, arg); + arg = js_init_evt_obj(task->ctx, &fevt); + ret = JS_Call(task->ctx, fun, obj, 1, &arg); + JS_SetOpaque(arg, NULL); + JS_FreeValue(task->ctx, arg); - if (JS_IsException(ret)) { - js_dump_error(fs->on_evt_task->ctx); - } - fevt.user_event.event.type = evt->type; - *evt = fevt.user_event.event; - res = JS_ToBool(fs->on_evt_task->ctx, ret) ? GF_TRUE : GF_FALSE; - if (!res && (evt->type==GF_EVENT_COPY_TEXT) && evt->clipboard.text) { - gf_free(evt->clipboard.text); - evt->clipboard.text = NULL; - } - JS_FreeValue(fs->on_evt_task->ctx, ret); - if (!pass2 && !res && !JS_IsUndefined(fs->on_evt_task->_obj2)) { - pass2 = GF_TRUE; - fun = fs->on_evt_task->fun2; - obj = fs->on_evt_task->_obj2; - goto retry; - } - js_std_loop(fs->on_evt_task->ctx); - gf_js_lock(fs->on_evt_task->ctx, GF_FALSE); - return res; + if (JS_IsException(ret)) { + js_dump_error(task->ctx); + } + fevt.user_event.event.type = evt->type; + *evt = fevt.user_event.event; + res = JS_ToBool(task->ctx, ret) ? GF_TRUE : GF_FALSE; + if (!res && (evt->type==GF_EVENT_COPY_TEXT) && evt->clipboard.text) { + gf_free(evt->clipboard.text); + evt->clipboard.text = NULL; + } + JS_FreeValue(task->ctx, ret); + js_std_loop(task->ctx); + gf_js_lock(task->ctx, GF_FALSE); + + if (res) return GF_TRUE; + } + return GF_FALSE; } typedef struct @@ -428,16 +426,16 @@ static void jsf_auth_finalizer(JSRuntime *rt, JSValue val) { JSFAuthContext *actx = JS_GetOpaque(val, jsf_auth_class_id); - if (!actx) return; + if (!actx) return; if (actx->on_usr_pass) { actx->on_usr_pass(actx->async_usr_data, NULL, NULL, GF_FALSE); } - gf_free(actx); + gf_free(actx); } static JSClassDef jsf_auth_class = { - "AsyncAuth", - .finalizer = jsf_auth_finalizer + "AsyncAuth", + .finalizer = jsf_auth_finalizer }; static JSValue js_auth_done(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -472,163 +470,121 @@ Bool jsfs_on_auth(GF_FilterSession *fs, GF_Event *evt) { JSValue args5, ret, obj; - Bool res = GF_TRUE; + u32 nb_ok=0; JSContext *ctx; - gf_assert(fs->on_auth_task); - ctx = fs->on_auth_task->ctx; - gf_js_lock(ctx, GF_TRUE); + u32 idx=0; + JSFS_Task *task; + while ((task = gf_list_enum(fs->jstasks, &idx))) { + if (task->type != GF_JSFS_TASK_AUTHENTICATION) continue; + ctx = task->ctx; + gf_js_lock(ctx, GF_TRUE); + + JSFAuthContext *actx; + GF_SAFEALLOC(actx, JSFAuthContext); + if (!actx) { + gf_js_lock(ctx, GF_FALSE); + continue; + } + actx->on_usr_pass = evt->auth.on_usr_pass; + actx->async_usr_data = evt->auth.async_usr_data; + obj = JS_NewObjectClass(ctx, jsf_auth_class_id); + JS_SetOpaque(obj, actx); - JSFAuthContext *actx; - GF_SAFEALLOC(actx, JSFAuthContext); - if (!actx) { - gf_js_lock(ctx, GF_FALSE); - return GF_FALSE; - } - actx->on_usr_pass = evt->auth.on_usr_pass; - actx->async_usr_data = evt->auth.async_usr_data; - obj = JS_NewObjectClass(ctx, jsf_auth_class_id); - JS_SetOpaque(obj, actx); + args0 = JS_NewString(ctx, evt->auth.site_url); + args1 = JS_NewString(ctx, evt->auth.user); + args2 = JS_NewString(ctx, evt->auth.password); + args3 = JS_NewBool(ctx, evt->auth.secure); + args4 = obj; - args0 = JS_NewString(ctx, evt->auth.site_url); - args1 = JS_NewString(ctx, evt->auth.user); - args2 = JS_NewString(ctx, evt->auth.password); - args3 = JS_NewBool(ctx, evt->auth.secure); - args4 = obj; + ret = JS_Call(ctx, task->fun, task->_obj, 5, args); - ret = JS_Call(ctx, fs->on_auth_task->fun, fs->on_auth_task->_obj, 5, args); + JS_FreeValue(ctx, args0); + JS_FreeValue(ctx, args1); + JS_FreeValue(ctx, args2); + JS_FreeValue(ctx, args3); + JS_FreeValue(ctx, args4); - JS_FreeValue(ctx, args0); - JS_FreeValue(ctx, args1); - JS_FreeValue(ctx, args2); - JS_FreeValue(ctx, args3); - JS_FreeValue(ctx, args4); + if (JS_IsException(ret)) { + js_dump_error(ctx); + } else { + nb_ok++; + } + JS_FreeValue(ctx, ret); - if (JS_IsException(ret)) { - js_dump_error(ctx); - res = GF_FALSE; + js_std_loop(ctx); + gf_js_lock(ctx, GF_FALSE); } - JS_FreeValue(ctx, ret); - - js_std_loop(ctx); - gf_js_lock(ctx, GF_FALSE); - return res; + return nb_ok ? GF_TRUE : GF_FALSE; } static JSValue jsfs_set_fun_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 cbk_type) { JSFS_Task *task = NULL; - u32 i, count; + u64 rem_task_id=0; Bool is_rem = GF_FALSE; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs || !argc) return GF_JS_EXCEPTION(ctx); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); + if (!fs || !argc) return GF_JS_EXCEPTION(ctx); - if (JS_IsNull(argv0)) + if (cbk_type==GF_JSFS_TASK_REMOVE) { is_rem = GF_TRUE; - else if (!JS_IsFunction(ctx, argv0) ) + if (JS_ToInt64(ctx, &rem_task_id, argv0)) + return GF_JS_EXCEPTION(ctx); + } else if (!JS_IsFunction(ctx, argv0) ) { return GF_JS_EXCEPTION(ctx); - - if (cbk_type==2) { - task = fs->new_f_task; - } else if (cbk_type==3) { - task = fs->del_f_task; - } else if (cbk_type==4) { - task = fs->on_evt_task; - } else if (cbk_type==5) { - task = fs->on_auth_task; - } else { - count = gf_list_count(fs->jstasks); - for (i=0; i<count; i++) { - task = gf_list_get(fs->jstasks, i); - if (task->type==1) break; - task = NULL; - } } + if (is_rem) { - if (task) { - JS_FreeValue(ctx, task->fun); - JS_FreeValue(ctx, task->_obj); - if (!JS_IsUndefined(task->_obj2)) { - JS_FreeValue(ctx, task->fun2); - JS_FreeValue(ctx, task->_obj2); - } - gf_list_del_item(fs->jstasks, task); - gf_free(task); + u32 idx=0; + while ((task = gf_list_enum(fs->jstasks, &idx))) { + if (task->id != rem_task_id) continue; + break; } - if (cbk_type == 1) - gf_sys_profiler_set_callback(task, NULL); - else if (cbk_type == 2) - fs->new_f_task = NULL; - else if (cbk_type == 3) - fs->del_f_task = NULL; - else if (cbk_type == 4) - fs->on_evt_task = NULL; - else if (cbk_type == 5) - fs->on_auth_task = NULL; + if (!task) + return JS_UNDEFINED; + //do not delete user tasks right away as they are in the scheduler ! + if (task->type == GF_JSFS_TASK_USER) { + task->type = GF_JSFS_TASK_REMOVE; + return JS_UNDEFINED; + } + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + gf_list_del_item(fs->jstasks, task); + gf_free(task); return JS_UNDEFINED; } - if (task) { - if ((cbk_type == 4) && JS_IsUndefined(task->fun2)) { - task->fun2 = task->fun; - task->_obj2 = task->_obj; - task->fun = JS_UNDEFINED; - task->_obj = JS_UNDEFINED; - } else { - JS_FreeValue(ctx, task->fun); - JS_FreeValue(ctx, task->_obj); - } - } else { - GF_SAFEALLOC(task, JSFS_Task); - if (!task) return GF_JS_EXCEPTION(ctx); - gf_list_add(fs->jstasks, task); - task->type = cbk_type; - task->ctx = ctx; - task->fun2 = JS_UNDEFINED; - task->_obj2 = JS_UNDEFINED; - } + GF_SAFEALLOC(task, JSFS_Task); + if (!task) return GF_JS_EXCEPTION(ctx); + gf_list_add(fs->jstasks, task); + task->type = cbk_type; + task->ctx = ctx; + task->id = (u64)(uintptr_t)task; task->fun = JS_DupValue(ctx, argv0); task->_obj = JS_DupValue(ctx, this_val); - - if (cbk_type == 1) { - gf_sys_profiler_set_callback(task, jsfs_rmt_user_callback); -#ifdef GPAC_ENABLE_COVERAGE - if (gf_sys_is_cov_mode()) { - jsfs_rmt_user_callback(task, "test"); - } -#endif - } - else if (cbk_type == 2) - fs->new_f_task = task; - else if (cbk_type == 3) - fs->del_f_task = task; - else if (cbk_type == 4) - fs->on_evt_task = task; - else if (cbk_type == 5) - fs->on_auth_task = task; - return JS_UNDEFINED; + return JS_NewInt64(ctx, task->id); } -static JSValue jsfs_set_rmt_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - return jsfs_set_fun_callback(ctx, this_val, argc, argv, 1); -} static JSValue jsfs_set_new_filter_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - return jsfs_set_fun_callback(ctx, this_val, argc, argv, 2); + return jsfs_set_fun_callback(ctx, this_val, argc, argv, GF_JSFS_TASK_FILTER_NEW); } static JSValue jsfs_set_del_filter_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - return jsfs_set_fun_callback(ctx, this_val, argc, argv, 3); + return jsfs_set_fun_callback(ctx, this_val, argc, argv, GF_JSFS_TASK_FILTER_DEL); } static JSValue jsfs_set_event_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - return jsfs_set_fun_callback(ctx, this_val, argc, argv, 4); + return jsfs_set_fun_callback(ctx, this_val, argc, argv, GF_JSFS_TASK_EVENT); } static JSValue jsfs_set_auth_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - return jsfs_set_fun_callback(ctx, this_val, argc, argv, 5); + return jsfs_set_fun_callback(ctx, this_val, argc, argv, GF_JSFS_TASK_AUTHENTICATION); +} +static JSValue jsfs_remove_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsfs_set_fun_callback(ctx, this_val, argc, argv, GF_JSFS_TASK_REMOVE); } enum @@ -651,6 +607,7 @@ JSFF_BYTES_SENT, JSFF_NB_TASKS, JSFF_NB_ERRORS, + JSFF_NB_CURRENT_ERRORS, JSFF_REPORT_UPDATED, JSFF_CLASS, JSFF_CODEC, @@ -718,6 +675,8 @@ return JS_NewInt64(ctx, f->nb_tasks_done); case JSFF_NB_ERRORS: return JS_NewInt64(ctx, f->nb_errors); + case JSFF_NB_CURRENT_ERRORS: + return JS_NewInt64(ctx, f->nb_current_errors); case JSFF_REPORT_UPDATED: val_b = f->report_updated; f->report_updated = GF_FALSE; @@ -875,7 +834,11 @@ if (!prop) break; if (prop->type==GF_PROP_POINTER) continue; - args0 = JS_NewString(ctx, prop_name ? prop_name : gf_props_4cc_get_name(prop_4cc) ); + const char* cc_name = gf_props_4cc_get_name(prop_4cc); + if (!prop_name && !cc_name) + continue; + + args0 = JS_NewString(ctx, prop_name ? prop_name : cc_name ); args1 = JS_NewString(ctx, gf_props_get_type_name(prop->type) ); if (prop_4cc) { args2 = jsf_NewPropTranslate(ctx, prop, prop_4cc); @@ -1177,7 +1140,7 @@ static JSValue jsfs_filter_args(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { char *name = NULL; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs || !argc) return GF_JS_EXCEPTION(ctx); name = (char *)JS_ToCString(ctx, argv0); @@ -1200,7 +1163,7 @@ static JSValue jsfs_run_sess(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); if (!(fs->flags & GF_FS_FLAG_USER_SESSION)) return GF_JS_EXCEPTION(ctx); @@ -1212,7 +1175,7 @@ static JSValue jsfs_stop_sess(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); if (!(fs->flags & GF_FS_FLAG_USER_SESSION)) return GF_JS_EXCEPTION(ctx); @@ -1222,7 +1185,7 @@ } static JSValue jsfs_print_connections(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); gf_fs_print_connections(fs); return JS_UNDEFINED; @@ -1230,7 +1193,7 @@ static JSValue jsfs_print_stats(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); gf_fs_print_stats(fs); return JS_UNDEFINED; @@ -1409,7 +1372,7 @@ gf_fs_lock_filters(f->session, GF_FALSE); f->dst_filter = new_f; - + //reconnect outputs of source gf_filter_reconnect_output((GF_Filter *) f, opid); @@ -1463,6 +1426,48 @@ gf_free(res); return ret; } + +JSContext *jsf_custom_filter_context(GF_Filter *f); +JSValue jsf_custom_filter_obj(GF_Filter *f); + +static Bool jsff_on_setup_error(GF_Filter *src_f, void *on_setup_error_udta, GF_Err e) +{ + GF_Filter *f = (GF_Filter *) on_setup_error_udta; + JSContext *ctx = f ? jsf_custom_filter_context(f) : NULL; + if (!ctx) return GF_TRUE; + if (JS_IsUndefined(f->jsval)) return GF_TRUE; + + gf_js_lock(ctx, GF_TRUE); + JSValue this_obj = jsf_custom_filter_obj(f); //no dupValue here + + JSValue fun = JS_GetPropertyStr(ctx, this_obj, "on_setup_error"); + JSValue args2; + args0 = jsfs_new_filter_obj(ctx, src_f); + args1 = JS_NewInt32(ctx, e); + JSValue ret = JS_Call(ctx, fun, this_obj, 2, args); + if (JS_IsException(ret)) js_dump_error(ctx); + JS_FreeValue(ctx, args0); + JS_FreeValue(ctx, args1); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, fun); + js_std_loop(ctx); + gf_js_lock(ctx, GF_FALSE); + return GF_TRUE; +} + +static JSValue jsff_watch_setup_failure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Filter *f = jsf_custom_filter_opaque(ctx, this_val); + if (!f || !argc) + return GF_JS_EXCEPTION(ctx); + + GF_Filter *src_f = JS_GetOpaque(argv0, fs_f_class_id); + if (!src_f) return GF_JS_EXCEPTION(ctx); + + gf_filter_set_setup_failure_callback(f, src_f, jsff_on_setup_error, f); + return JS_UNDEFINED; +} + static Bool jsfs_get_filter_args(JSContext *ctx, GF_FilterSession *fs, GF_FilterSession **meta_fs, char *regname, GF_Filter *finst, JSValue args); static void del_meta_fs(GF_FilterSession *metafs); @@ -1518,7 +1523,7 @@ } JSValue res; - GF_List *fchain = gf_filter_pid_compute_link(opid, new_f); + GF_List *fchain = gf_filter_pid_compute_link(opid, new_f, NULL, 0); if (fchain) { res = JS_NewArray(ctx); u32 i, count = gf_list_count(fchain); @@ -1683,6 +1688,7 @@ JS_CGETSET_MAGIC_DEF_ENUM("bytes_sent", jsfs_f_prop_get, NULL, JSFF_BYTES_SENT), JS_CGETSET_MAGIC_DEF_ENUM("tasks", jsfs_f_prop_get, NULL, JSFF_NB_TASKS), JS_CGETSET_MAGIC_DEF_ENUM("errors", jsfs_f_prop_get, NULL, JSFF_NB_ERRORS), + JS_CGETSET_MAGIC_DEF_ENUM("current_errors", jsfs_f_prop_get, NULL, JSFF_NB_CURRENT_ERRORS), JS_CGETSET_MAGIC_DEF_ENUM("report_updated", jsfs_f_prop_get, NULL, JSFF_REPORT_UPDATED), JS_CGETSET_MAGIC_DEF_ENUM("class", jsfs_f_prop_get, NULL, JSFF_CLASS), JS_CGETSET_MAGIC_DEF_ENUM("streamtype", jsfs_f_prop_get, NULL, JSFF_STREAMTYPE), @@ -1726,10 +1732,10 @@ { u32 idx; GF_Filter *f = NULL; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); - if (!fs || !argc) return GF_JS_EXCEPTION(ctx); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); + if (!fs || !argc) return GF_JS_EXCEPTION(ctx); - if (JS_IsString(argv0)) { + if (JS_IsString(argv0)) { const char *iname = JS_ToCString(ctx, argv0); if (iname) { u32 i, count; @@ -1763,7 +1769,7 @@ Bool relative_to_script = GF_FALSE; Bool is_source = GF_FALSE; GF_Filter *link_from = NULL; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs || !argc) return GF_JS_EXCEPTION(ctx); @@ -1775,6 +1781,10 @@ link_from = JS_GetOpaque(argv1, fs_f_class_id); if (argc>2) { link_args = JS_ToCString(ctx, argv2); + if (link_args && !link_args0) { + JS_FreeCString(ctx, link_args); + link_args = NULL; + } } if (argc>3) { relative_to_script = JS_ToBool(ctx, argv3); @@ -1811,16 +1821,18 @@ if (link_args) JS_FreeCString(ctx, link_args); gf_fs_lock_filters(fs, GF_FALSE); - return jsfs_new_filter_obj(ctx, new_f); + JSValue fobj = jsfs_new_filter_obj(ctx, new_f); + return fobj; } static JSValue jsfs_fire_event(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { Bool upstream = GF_FALSE; + Bool force = GF_FALSE; GF_Filter *f = NULL; Bool ret=GF_FALSE; GF_FilterEvent *jsf_get_event(JSContext *ctx, JSValueConst this_val); - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); GF_FilterEvent *evt; if (!fs || !argc) return GF_JS_EXCEPTION(ctx); @@ -1831,15 +1843,21 @@ if (argc>1) { f = jsff_get_filter(ctx, argv1); if (argc>2) upstream = JS_ToBool(ctx, argv2); + if (argc>3) force = JS_ToBool(ctx, argv3); + } + if (force) { + gf_filter_send_event(f, evt, upstream); + ret = GF_TRUE; + } else { + ret = gf_fs_fire_event(fs, f, evt, upstream); } - ret = gf_fs_fire_event(fs, f, evt, upstream); return JS_NewBool(ctx, ret); } static JSValue jsfs_reporting(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { Bool report_on; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs || !argc) return GF_JS_EXCEPTION(ctx); report_on = JS_ToBool(ctx, argv0); @@ -1855,7 +1873,7 @@ GF_Err e; u32 flags=0; const char *name = NULL; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs) return GF_JS_EXCEPTION(ctx); if (argc) { name = JS_ToCString(ctx, argv0); @@ -1868,13 +1886,18 @@ if (name) JS_FreeCString(ctx, name); if (!f) return js_throw_err(ctx, e); - return jsfilter_initialize_custom(f, ctx); + JSValue ret = jsfilter_initialize_custom(f, ctx); + if (JS_IsException(ret) || JS_IsNull(ret) ) return ret; + //custom filters can monitor sources using this function + JS_SetPropertyStr(ctx, ret, "watch_setup_failure", JS_NewCFunction(ctx, jsff_watch_setup_failure, "watch_setup_failure", 1)); + return ret; } static JSValue jsfs_remove_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_Filter *to_remove=NULL; - GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_Filter *src_filter=NULL; + GF_FilterSession *fs = jsff_get_session(ctx, this_val); if (!fs || !argc) return GF_JS_EXCEPTION(ctx); if (!JS_IsObject(argv0) ) return GF_JS_EXCEPTION(ctx); @@ -1885,38 +1908,43 @@ if (!to_remove) return GF_JS_EXCEPTION(ctx); + if (argc>1) { + src_filter = JS_GetOpaque(argv1, fs_f_class_id); + if (!src_filter) + src_filter = jsf_custom_filter_opaque(ctx, argv1); + } + if (src_filter && !src_filter->num_input_pids) { + gf_filter_remove_src(to_remove, src_filter); + } gf_filter_remove(to_remove); return JS_UNDEFINED; } static const JSCFunctionListEntry fs_funcs = { - JS_CGETSET_MAGIC_DEF_ENUM("nb_filters", jsfs_prop_get, NULL, JSFS_NB_FILTERS), - JS_CGETSET_MAGIC_DEF_ENUM("last_task", jsfs_prop_get, NULL, JSFS_LAST_TASK), + JS_CGETSET_MAGIC_DEF("type", jsfs_prop_get, NULL, JSFS_TYPE), + JS_CGETSET_MAGIC_DEF_ENUM("nb_filters", jsfs_prop_get, NULL, JSFS_NB_FILTERS), + JS_CGETSET_MAGIC_DEF_ENUM("last_task", jsfs_prop_get, NULL, JSFS_LAST_TASK), JS_CGETSET_MAGIC_DEF("http_max_bitrate", jsfs_prop_get, jsfs_prop_set, JSFS_HTTP_MAX_RATE), JS_CGETSET_MAGIC_DEF("http_bitrate", jsfs_prop_get, NULL, JSFS_HTTP_RATE), - JS_CGETSET_MAGIC_DEF("rmt_sampling", jsfs_prop_get, jsfs_prop_set, JSFS_RMT_SAMPLING), JS_CGETSET_MAGIC_DEF("connected", jsfs_prop_get, NULL, JSFS_CONNECTED), JS_CGETSET_MAGIC_DEF("last_process_error", jsfs_prop_get, NULL, JSFS_LAST_PROCESS_ERR), JS_CGETSET_MAGIC_DEF("last_connect_error", jsfs_prop_get, NULL, JSFS_LAST_CONNECT_ERR), JS_CGETSET_MAGIC_DEF("jspath", jsfs_prop_get, NULL, JSFS_PATH), - JS_CFUNC_DEF("post_task", 0, jsfs_post_task), - JS_CFUNC_DEF("abort", 0, jsfs_abort), - JS_CFUNC_DEF("get_filter", 0, jsfs_get_filter), - JS_CFUNC_DEF("lock_filters", 0, jsfs_lock_filters), - JS_CFUNC_DEF("enable_rmt", 0, jsfs_enable_rmt), - JS_CFUNC_DEF("rmt_send", 0, jsfs_rmt_send), - JS_CFUNC_DEF("rmt_log", 0, jsfs_rmt_log), - JS_CFUNC_DEF("set_rmt_fun", 0, jsfs_set_rmt_fun), - JS_CFUNC_DEF("set_new_filter_fun", 0, jsfs_set_new_filter_fun), - JS_CFUNC_DEF("set_del_filter_fun", 0, jsfs_set_del_filter_fun), - JS_CFUNC_DEF("set_event_fun", 0, jsfs_set_event_fun), - JS_CFUNC_DEF("add_filter", 0, jsfs_add_filter), - JS_CFUNC_DEF("fire_event", 0, jsfs_fire_event), - JS_CFUNC_DEF("reporting", 0, jsfs_reporting), - JS_CFUNC_DEF("new_filter", 0, jsfs_new_filter), - JS_CFUNC_DEF("remove_filter", 0, jsfs_remove_filter), - JS_CFUNC_DEF("set_auth_fun", 0, jsfs_set_auth_fun), + JS_CFUNC_DEF("post_task", 0, jsfs_post_task), + JS_CFUNC_DEF("abort", 0, jsfs_abort), + JS_CFUNC_DEF("get_filter", 0, jsfs_get_filter), + JS_CFUNC_DEF("lock_filters", 0, jsfs_lock_filters), + JS_CFUNC_DEF("set_new_filter_fun", 0, jsfs_set_new_filter_fun), + JS_CFUNC_DEF("set_del_filter_fun", 0, jsfs_set_del_filter_fun), + JS_CFUNC_DEF("set_event_fun", 0, jsfs_set_event_fun), + JS_CFUNC_DEF("add_filter", 0, jsfs_add_filter), + JS_CFUNC_DEF("fire_event", 0, jsfs_fire_event), + JS_CFUNC_DEF("reporting", 0, jsfs_reporting), + JS_CFUNC_DEF("new_filter", 0, jsfs_new_filter), + JS_CFUNC_DEF("remove_filter", 0, jsfs_remove_filter), + JS_CFUNC_DEF("set_auth_fun", 0, jsfs_set_auth_fun), + JS_CFUNC_DEF("remove_callback", 0, jsfs_remove_callback), JS_CFUNC_DEF("filter_args", 0, jsfs_filter_args), JS_CFUNC_DEF("run", 0, jsfs_run_sess), JS_CFUNC_DEF("stop", 0, jsfs_stop_sess), @@ -1946,10 +1974,13 @@ static JSValue session_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue anobj; - GF_FilterSession *fs; + JSFS_FilterSession *fsjs; + GF_SAFEALLOC(fsjs, JSFS_FilterSession) + if (!fsjs) + return GF_JS_EXCEPTION(ctx); if (!argc) { - fs = gf_fs_new_defaults(0); + fsjs->fs = gf_fs_new_defaults(0); } else if (JS_IsObject(argv0)) { s32 nb_threads=0; u32 sched_type=0; @@ -1975,19 +2006,21 @@ blacklist = JS_ToCString(ctx, val); JS_FreeValue(ctx, val); - fs = gf_fs_new(nb_threads, sched_type, flags, blacklist); + fsjs->fs = gf_fs_new(nb_threads, sched_type, flags, blacklist); JS_FreeCString(ctx, blacklist); } else { return GF_JS_EXCEPTION(ctx); } - fs->flags |= GF_FS_FLAG_USER_SESSION; + fsjs->fs->flags |= GF_FS_FLAG_USER_SESSION; anobj = JS_NewObjectClass(ctx, fs_class_id); if (JS_IsException(anobj)) { - gf_fs_del(fs); + gf_fs_del(fsjs->fs); + gf_free(fsjs); return anobj; } - JS_SetOpaque(anobj, fs); + + JS_SetOpaque(anobj, fsjs); return anobj; } GF_Err gf_fs_load_js_api(JSContext *c, GF_FilterSession *fs) @@ -1996,9 +2029,8 @@ JSRuntime *rt; JSValue global_obj; - if (fs->js_ctx) { - GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("JSFS FilterSession API already loaded by another script, cannot load twice\n")); - return GF_NOT_SUPPORTED; + if (fs->js_ctx == c) { + return GF_OK; } rt = JS_GetRuntime(c); @@ -2007,7 +2039,7 @@ js_load_constants(c, global_obj); #define DEF_CONST( _val ) \ - JS_SetPropertyStr(c, global_obj, #_val, JS_NewInt32(c, _val)); + JS_SetPropertyStr(c, global_obj, #_val, JS_NewInt32(c, _val)); DEF_CONST(GF_FS_FLUSH_NONE) DEF_CONST(GF_FS_FLUSH_ALL) @@ -2017,25 +2049,31 @@ fs->jstasks = gf_list_new(); if (!fs->jstasks) return GF_OUT_OF_MEM; } - + JSFS_FilterSession *fsjs; + GF_SAFEALLOC(fsjs, JSFS_FilterSession) + if (!fsjs) return GF_OUT_OF_MEM; + //remmeber if this is the object owning the API (for GC) + fsjs->owns_api = (fs->js_ctx && (fs->js_ctx != c)) ? GF_FALSE : GF_TRUE; //initialize filter class and create a single filter object in global scope - JS_NewClassID(&fs_class_id); - JS_NewClass(rt, fs_class_id, &fs_class); + if (!fs_class_id) { + JS_NewClassID(&fs_class_id); + JS_NewClass(rt, fs_class_id, &fs_class); - JS_NewClassID(&fs_f_class_id); - JS_NewClass(rt, fs_f_class_id, &fs_f_class); + JS_NewClassID(&fs_f_class_id); + JS_NewClass(rt, fs_f_class_id, &fs_f_class); - JS_NewClassID(&jsf_auth_class_id); - JS_NewClass(rt, jsf_auth_class_id, &jsf_auth_class); + JS_NewClassID(&jsf_auth_class_id); + JS_NewClass(rt, jsf_auth_class_id, &jsf_auth_class); + } JSValue proto = JS_NewObjectClass(c, jsf_auth_class_id); JS_SetPropertyFunctionList(c, proto, jsf_auth_funcs, countof(jsf_auth_funcs)); JS_SetClassProto(c, jsf_auth_class_id, proto); - fs_obj = JS_NewObjectClass(c, fs_class_id); - JS_SetPropertyFunctionList(c, fs_obj, fs_funcs, countof(fs_funcs)); - JS_SetOpaque(fs_obj, fs); + JS_SetPropertyFunctionList(c, fs_obj, fs_funcs, countof(fs_funcs)); + fsjs->fs = fs; + JS_SetOpaque(fs_obj, fsjs); JS_SetPropertyStr(c, global_obj, "session", fs_obj); //filtersession constructor @@ -2044,29 +2082,63 @@ JS_SetClassProto(c, fs_class_id, proto); JSValue ctor = JS_NewCFunction2(c, session_constructor, "FilterSession", 1, JS_CFUNC_constructor, 0); JS_SetPropertyStr(c, global_obj, "FilterSession", ctor); - /*JS_SetModuleExport(c, m, "FilterSession", ctor); + /*JS_SetModuleExport(c, m, "FilterSession", ctor); */ - JS_FreeValue(c, global_obj); - return GF_OK; + JS_FreeValue(c, global_obj); + return fsjs->owns_api ? GF_OK : GF_EOS; } static GF_Err gf_fs_load_script_ex(GF_FilterSession *fs, const char *jsfile, JSContext *in_ctx, GF_Filter *for_filter) { +#ifndef GPAC_HAS_QJS + return GF_NOT_SUPPORTED; +#else GF_Err e; - JSValue global_obj; + JSValue global_obj; u8 *buf; u32 buf_len; + char szFilePathGF_MAX_PATH; + const char *js_file_path; u32 flags = JS_EVAL_TYPE_GLOBAL; - JSValue ret; - JSContext *ctx; - + JSValue ret; + Bool skip_modules = GF_FALSE; + JSContext *ctx = NULL; if (!fs) return GF_BAD_PARAM; - if (!in_ctx) { - if (fs->js_ctx) return GF_NOT_SUPPORTED; -#ifdef GPAC_HAS_QJS + //load script + szFilePath0 = 0; + if (!strncmp(jsfile, "$GSHARE/", 8)) { + if (gf_opts_default_shared_directory(szFilePath)) { + strcat(szFilePath, jsfile + 7); + e = gf_file_load_data(szFilePath, &buf, &buf_len); + } else { + e = GF_URL_ERROR; + } + } else { + e = gf_file_load_data(jsfile, &buf, &buf_len); + + } + if (e) { + if (e!=GF_URL_ERROR) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("JSF Error loading script file %s: %s\n", jsfile, gf_error_to_string(e) )); + } + return e; + } + + js_file_path = szFilePath0 ? szFilePath : jsfile; + + if (in_ctx) { + ctx = in_ctx; + } else if (fs->js_ctx) { + ctx = fs->js_ctx; + skip_modules = GF_TRUE; + global_obj = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(fs->js_ctx, global_obj, "_gpac_log_name", JS_NewString(fs->js_ctx, gf_file_basename(jsfile) ) ); + JS_SetPropertyStr(fs->js_ctx, global_obj, "_gpac_script_src", JS_NewString(fs->js_ctx, js_file_path ) ); + JS_FreeValue(fs->js_ctx, global_obj); + } else { ctx = gf_js_create_context(); if (!ctx) { GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("JSF Failed to load QuickJS context\n")); @@ -2079,33 +2151,13 @@ fs->js_ctx = ctx; JS_SetPropertyStr(fs->js_ctx, global_obj, "_gpac_log_name", JS_NewString(fs->js_ctx, gf_file_basename(jsfile) ) ); - JS_SetPropertyStr(fs->js_ctx, global_obj, "_gpac_script_src", JS_NewString(fs->js_ctx, jsfile ) ); + JS_SetPropertyStr(fs->js_ctx, global_obj, "_gpac_script_src", JS_NewString(fs->js_ctx, js_file_path ) ); JS_FreeValue(fs->js_ctx, global_obj); - } else { - ctx = in_ctx; - } - - //load script - if (!strncmp(jsfile, "$GSHARE/", 8)) { - char szPathGF_MAX_PATH; - if (gf_opts_default_shared_directory(szPath)) { - strcat(szPath, jsfile + 7); - e = gf_file_load_data(szPath, &buf, &buf_len); - } else { - e = GF_NOT_FOUND; - } - } else { - e = gf_file_load_data(jsfile, &buf, &buf_len); - } - if (e) { - if (e!=GF_NOT_FOUND) { - GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("JSF Error loading script file %s: %s\n", jsfile, gf_error_to_string(e) )); - } - return e; } if (in_ctx || (!gf_opts_get_bool("core", "no-js-mods") && JS_DetectModule((char *)buf, buf_len))) { - qjs_init_all_modules(ctx, GF_FALSE, GF_FALSE); + if (!skip_modules) + qjs_init_all_modules(ctx, GF_FALSE, GF_FALSE); flags = JS_EVAL_TYPE_MODULE; } @@ -2113,22 +2165,19 @@ JS_SetPropertyStr(ctx, global, "parent_filter", for_filter ? jsfs_new_filter_obj(ctx, for_filter) : JS_NULL); JS_FreeValue(ctx, global); - ret = JS_Eval(ctx, (char *)buf, buf_len, jsfile, flags); + ret = JS_Eval(ctx, (char *)buf, buf_len, js_file_path, flags); gf_free(buf); if (JS_IsException(ret)) { GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("JSF Error loading script %s\n", jsfile)); - js_dump_error(ctx); + js_dump_error(ctx); JS_FreeValue(ctx, ret); return GF_BAD_PARAM; } JS_FreeValue(ctx, ret); js_std_loop(ctx); return GF_OK; -#else - return GF_NOT_SUPPORTED; #endif - } GF_EXPORT @@ -2140,14 +2189,6 @@ void gf_fs_unload_script(GF_FilterSession *fs, void *js_ctx) { u32 i, count=gf_list_count(fs->jstasks); - - gf_js_lock(js_ctx, GF_TRUE); - fs->new_f_task = NULL; - fs->del_f_task = NULL; - fs->on_evt_task = NULL; - fs->on_auth_task = NULL; - gf_js_lock(js_ctx, GF_FALSE); - for (i=0; i<count; i++) { JSFS_Task *task = gf_list_get(fs->jstasks, i); if (js_ctx && (task->ctx != js_ctx)) @@ -2156,10 +2197,6 @@ gf_js_lock(js_ctx, GF_TRUE); JS_FreeValue(task->ctx, task->fun); JS_FreeValue(task->ctx, task->_obj); - if (!JS_IsUndefined(task->_obj2)) { - JS_FreeValue(task->ctx, task->fun2); - JS_FreeValue(task->ctx, task->_obj2); - } gf_js_lock(js_ctx, GF_FALSE); gf_free(task); @@ -2167,15 +2204,22 @@ i--; count--; } - if (fs->js_ctx) { - gf_js_lock(fs->js_ctx, GF_TRUE); - JSValue global_obj = JS_GetGlobalObject(fs->js_ctx); - JSValue fsobj = JS_GetPropertyStr(fs->js_ctx, global_obj, "session"); + + if (fs->js_ctx || js_ctx) { + JSContext *c = fs->js_ctx ? fs->js_ctx : js_ctx; + gf_js_lock(c, GF_TRUE); + JSValue global_obj = JS_GetGlobalObject(c); + JSValue fsobj = JS_GetPropertyStr(c, global_obj, "session"); + //detach since GC is likely not done now + JSFS_FilterSession *jsfs = JS_GetOpaque(fsobj, fs_class_id); + if (jsfs) gf_free(jsfs); JS_SetOpaque(fsobj, NULL); - JS_SetPropertyStr(fs->js_ctx, global_obj, "session", JS_NULL); - JS_FreeValue(fs->js_ctx, global_obj); - JS_FreeValue(fs->js_ctx, fsobj); - gf_js_lock(fs->js_ctx, GF_FALSE); + JS_SetPropertyStr(c, global_obj, "session", JS_NULL); + JS_FreeValue(c, global_obj); + JS_FreeValue(c, fsobj); + gf_js_lock(c, GF_FALSE); + } + if (fs->js_ctx) { gf_js_delete_context(fs->js_ctx); fs->js_ctx = NULL; } @@ -2210,6 +2254,32 @@ gf_mx_v(filter->session->filters_mx); } +JSValue gf_fs_get_script_data(JSContext *ctx, void *udta, u32 tag_type) +{ + if (tag_type == GF_LOG_TAG_FILTER) { + GF_Filter *f = (GF_Filter *)udta; + return JS_DupValue(ctx, f->jsval); + } + if ((tag_type == GF_LOG_TAG_FILTERSESSION) || (tag_type == GF_LOG_TAG_FILTERSESSION_THREAD)) { + GF_FilterSession *fs = NULL; + if (tag_type == GF_LOG_TAG_FILTERSESSION_THREAD) { + GF_SessionThread *sess_thread = (GF_SessionThread *) udta; + fs = sess_thread->fsess; + } else { + fs = (GF_FilterSession *)udta; + } + if (!fs) return JS_NULL; + + if (fs->js_ctx) { + JSValue global_obj = JS_GetGlobalObject(ctx); + JSValue fsobj = JS_GetPropertyStr(ctx, global_obj, "session"); + JS_FreeValue(ctx, global_obj); + return fsobj; + } + } + return JS_NULL; +} + #else GF_EXPORT GF_Err gf_fs_load_script(GF_FilterSession *fs, const char *jsfile) @@ -2226,6 +2296,3 @@ } #endif - - -
View file
gpac-2.4.0.tar.gz/src/filters/avin_web.c -> gpac-26.02.0.tar.gz/src/filters/avin_web.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / Camera/Mic/Canvas grabber filter @@ -27,7 +27,8 @@ #include <gpac/internal/media_dev.h> #include <gpac/constants.h> -#if defined(GPAC_CONFIG_EMSCRIPTEN) +#ifndef GPAC_DISABLE_WEBCODEC + #include <gpac/network.h> typedef struct @@ -56,6 +57,8 @@ GF_List *audio_pcks; } GF_WebGrab; +#if defined(GPAC_CONFIG_EMSCRIPTEN) + EM_JS(int, webgrab_next_video, (int wg_ctx), { let c = libgpac._to_webgrab(wg_ctx); @@ -113,7 +116,7 @@ static GF_Err webgrab_process(GF_Filter *filter) { - GF_WebGrab *ctx = gf_filter_get_udta(filter); + GF_WebGrab *ctx = gf_filter_get_udta(filter); if (ctx->init_err) return ctx->init_err; if (!ctx->init_play_done) return GF_OK; @@ -146,7 +149,7 @@ static Bool webgrab_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { - GF_WebGrab *ctx = gf_filter_get_udta(filter); + GF_WebGrab *ctx = gf_filter_get_udta(filter); switch (evt->base.type) { case GF_FEVT_PLAY: if (evt->base.on_pid==ctx->vpid) { @@ -189,7 +192,7 @@ if (!c || !c._frame || !dst_pck) return; //setup dst - let ab = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let ab = new Uint8Array(HEAPU8.buffer, buf, buf_size); let frame = c._frame; c._frame = null; frame.copyTo(ab).then( layout => { @@ -275,7 +278,7 @@ if (!c || !c._frame) return; //setup dst - let ab = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let ab = new Uint8Array(HEAPU8.buffer, buf, buf_size); c._frame.copyTo(ab, { planeIndex: plane_index }); }) @@ -344,15 +347,15 @@ if (typeof libgpac._to_webgrab != 'function') { libgpac._web_grabs = ; libgpac._to_webgrab = (ctx) => { - for (let i=0; i<libgpac._web_grabs.length; i++) { - if (libgpac._web_grabsi._wg_ctx==ctx) return libgpac._web_grabsi; - } - return null; + for (let i=0; i<libgpac._web_grabs.length; i++) { + if (libgpac._web_grabsi._wg_ctx==ctx) return libgpac._web_grabsi; + } + return null; }; - libgpac._on_wgrab_error = libgpac.cwrap('webgrab_on_error', null, 'number', 'number', 'string'); - libgpac._on_wgrab_video_frame = libgpac.cwrap('webgrab_on_video_frame', null, 'number', 'number', 'number', 'string', 'bigint'); - libgpac._on_wgrab_audio_data = libgpac.cwrap('webgrab_on_audio_data', null, 'number', 'number', 'number', 'string', 'number', 'bigint'); - libgpac._on_wgrab_frame_copy = libgpac.cwrap('webgrab_on_frame_copy', null, 'number', 'number', 'number'); + libgpac._on_wgrab_error = cwrap('webgrab_on_error', null, 'number', 'number', 'string'); + libgpac._on_wgrab_video_frame = cwrap('webgrab_on_video_frame', null, 'number', 'number', 'number', 'string', 'bigint'); + libgpac._on_wgrab_audio_data = cwrap('webgrab_on_audio_data', null, 'number', 'number', 'number', 'string', 'number', 'bigint'); + libgpac._on_wgrab_frame_copy = cwrap('webgrab_on_frame_copy', null, 'number', 'number', 'number'); } let c = libgpac._to_webgrab(wg_ctx); @@ -361,7 +364,7 @@ libgpac._web_grabs.push(c); } if (canv_id) { - let canvas_id = libgpac.UTF8ToString(canv_id); + let canvas_id = UTF8ToString(canv_id); c.canvas = document.getElementById(canvas_id); c.keep_alpha = alpha; if (!c.canvas) { @@ -428,19 +431,19 @@ { Bool use_video=GF_FALSE, use_audio=GF_FALSE; char *canvas_id=NULL; - GF_WebGrab *ctx = gf_filter_get_udta(filter); - ctx->filter = filter; + GF_WebGrab *ctx = gf_filter_get_udta(filter); + ctx->filter = filter; - if (!ctx->src) return GF_BAD_PARAM; + if (!ctx->src) return GF_BAD_PARAM; - if (!strcmp(ctx->src, "video://")) use_video = GF_TRUE; + if (!strcmp(ctx->src, "video://")) use_video = GF_TRUE; else if (!strncmp(ctx->src, "video://", 8)) { canvas_id = ctx->src+8; ctx->is_canvas = GF_TRUE; - } - else if (!strcmp(ctx->src, "audio://")) use_audio = GF_TRUE; - else if (!strcmp(ctx->src, "av://")) use_video = use_audio = GF_TRUE; - else + } + else if (!strcmp(ctx->src, "audio://")) use_audio = GF_TRUE; + else if (!strcmp(ctx->src, "av://")) use_video = use_audio = GF_TRUE; + else return GF_BAD_PARAM; gf_filter_prevent_blocking(filter, GF_TRUE); @@ -448,7 +451,7 @@ ctx->video_pcks = gf_list_new(); ctx->audio_pcks = gf_list_new(); - return GF_OK; + return GF_OK; } EM_JS(int, wgrab_del, (int wg_ctx), { @@ -469,7 +472,7 @@ void webgrab_finalize(GF_Filter *filter) { - GF_WebGrab *ctx = gf_filter_get_udta(filter); + GF_WebGrab *ctx = gf_filter_get_udta(filter); wgrab_del(EM_CAST_PTR ctx); while (gf_list_count(ctx->video_pcks)) { GF_FilterPacket *pck = gf_list_pop_front(ctx->video_pcks); @@ -491,6 +494,8 @@ if (!strncmp(url, "canvas://", 9)) return GF_FPROBE_MAYBE_SUPPORTED; return GF_FPROBE_NOT_SUPPORTED; } +#endif + static GF_FilterCapability WebGrabCaps = { @@ -514,9 +519,17 @@ {0} }; +#ifndef GPAC_CONFIG_EMSCRIPTEN +static GF_Err webgrab_process_dummy(GF_Filter *filter) +{ + return GF_NOT_SUPPORTED; +} +#endif + + GF_FilterRegister GF_WebGrabRegister = { .name = "webgrab", - GF_FS_SET_DESCRIPTION("Frame grabber for web audio and video") + GF_FS_SET_DESCRIPTION("Web-based AV capture") GF_FS_SET_HELP("This filter grabs audio and video streams MediaStreamTrackProcessor of the browser\n" "\n" "Supported URL schemes:\n" @@ -530,17 +543,24 @@ //getUserMedia only available on main thread .flags = GF_FS_REG_MAIN_THREAD|GF_FS_REG_ASYNC_BLOCK, .private_size = sizeof(GF_WebGrab), +#if defined(GPAC_CONFIG_EMSCRIPTEN) .initialize = webgrab_initialize, .finalize = webgrab_finalize, .process = webgrab_process, .process_event = webgrab_process_event, .probe_url = webgrab_probe_url, +#else + .process = webgrab_process_dummy, +#endif + .hint_class_type = GF_FS_CLASS_MM_IO }; +#endif //GPAC_DISABLE_WEBCODEC const GF_FilterRegister *webgrab_register(GF_FilterSession *session) { - +#ifndef GPAC_DISABLE_WEBCODEC +#if defined(GPAC_CONFIG_EMSCRIPTEN) int has_media_track_processor = EM_ASM_INT({ if (typeof MediaStreamTrackProcessor == 'undefined') return 0; return 1; @@ -549,6 +569,13 @@ GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("WebGrab No MediaStreamTrackProcessor support\n")); return NULL; } +#else + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + GF_WebGrabRegister.version = "! Warning: Web APIs NOT AVAILABLE IN THIS BUILD !"; +#endif return &GF_WebGrabRegister; -} +#else + return NULL; #endif +}
View file
gpac-2.4.0.tar.gz/src/filters/base_filter_example.c -> gpac-26.02.0.tar.gz/src/filters/base_filter_example.c
Changed
@@ -38,7 +38,7 @@ static void example_filter_finalize(GF_Filter *filter) { - //peform any finalyze routine needed, including potential free in the filter context + //perform any finalize routine needed, including potential free in the filter context //if not needed, set the filter_finalize to NULL } static GF_Err example_filter_process(GF_Filter *filter)
View file
gpac-2.4.0.tar.gz/src/filters/bs_agg.c -> gpac-26.02.0.tar.gz/src/filters/bs_agg.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022-2023 + * Copyright (c) Telecom ParisTech 2022-2024 * All rights reserved * * This file is part of GPAC / compressed split bitstream aggregator filter @@ -261,7 +261,7 @@ static GF_Err vvc_hevc_rewrite_pid_config(BSAggCtx *ctx, BSAggOut *pctx) { - GF_Err e; + GF_Err e = GF_OK; u32 i, count; Bool is_vvc = GF_FALSE; GF_HEVCConfig *hvcc_out = NULL; @@ -961,7 +961,7 @@ GF_FilterRegister BSAggRegister = { .name = "bsagg", - GF_FS_SET_DESCRIPTION("Compressed layered bitstream aggregator") + GF_FS_SET_DESCRIPTION("Layered bitstream aggregator") GF_FS_SET_HELP("This filter aggregates layers and sublayers into a single output PID.\n" "\n" "The filter supports AVC|H264, HEVC and VVC stream reconstruction, and is passthrough for other codec types.\n" @@ -981,7 +981,8 @@ .initialize = bs_agg_initialize, .finalize = bs_agg_finalize, .configure_pid = bs_agg_configure_pid, - .process = bs_agg_process + .process = bs_agg_process, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *bsagg_register(GF_FilterSession *session) @@ -994,4 +995,3 @@ return NULL; } #endif -
View file
gpac-2.4.0.tar.gz/src/filters/bs_split.c -> gpac-26.02.0.tar.gz/src/filters/bs_split.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022-2023 + * Copyright (c) Telecom ParisTech 2022-2024 * All rights reserved * * This file is part of GPAC / compressed bitstream splitter filter @@ -1016,7 +1016,7 @@ static GF_Err nalu_split_packet(BSSplitCtx *ctx, BSSplitIn *pctx, GF_FilterPacket *pck, u32 codec_type) { u32 size, pck_size, min_nal_size; - GF_Err e; + GF_Err e = GF_OK; u64 pck_ts; Bool has_svc_prefix = GF_FALSE; const u8 *data = gf_filter_pck_get_data(pck, &pck_size); @@ -1434,7 +1434,7 @@ GF_FilterRegister BSSplitRegister = { .name = "bssplit", - GF_FS_SET_DESCRIPTION("Compressed layered bitstream splitter") + GF_FS_SET_DESCRIPTION("Layered bitstream splitter") GF_FS_SET_HELP("This filter splits input stream by layers and sublayers\n" "\n" "The filter supports AVC|H264, HEVC and VVC stream splitting and is pass-through for other codec types.\n" @@ -1477,7 +1477,8 @@ .initialize = bs_split_initialize, .finalize = bs_split_finalize, .configure_pid = bs_split_configure_pid, - .process = bs_split_process + .process = bs_split_process, + .hint_class_type = GF_FS_CLASS_STREAM }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/bsrw.c -> gpac-26.02.0.tar.gz/src/filters/bsrw.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2023 + * Copyright (c) Telecom ParisTech 2020-2025 * All rights reserved * * This file is part of GPAC / compressed bitstream metadata rewrite filter @@ -33,6 +33,15 @@ typedef struct _bsrw_pid_ctx BSRWPid; typedef struct _bsrw_ctx GF_BSRWCtx; +GF_OPT_ENUM (BsrwTimecodeMode, + BSRW_TC_NONE=0, + BSRW_TC_REMOVE, + BSRW_TC_INSERT, + BSRW_TC_SHIFT, + BSRW_TC_CONSTANT, + BSRW_TC_UTC +); + struct _bsrw_pid_ctx { GF_FilterPid *ipid, *opid; @@ -42,11 +51,17 @@ GF_Err (*rewrite_packet)(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck); s32 prev_cprim, prev_ctfc, prev_cmx, prev_sar; + GF_Fraction fps; u32 nalu_size_length; + u64 drop_change_cts; + u32 tc_drop_count; + Bool tc_dropped; + #ifndef GPAC_DISABLE_AV_PARSERS GF_VUIInfo vui; + AVCState *avc; #endif Bool rewrite_vui; }; @@ -57,9 +72,17 @@ s32 m4vpl, prof, lev, pcomp, pidc, pspace, gpcflags; s32 cprim, ctfc, cmx, vidfmt; Bool rmsei, fullrange, novsi, novuitiming; + GF_PropUIntList seis; GF_List *pids; Bool reconfigure; + + Bool tcsc_inferred; + Bool tcdf; + char *tcxs, *tcxe, *tcsc; + GF_TimeCode tcxs_val, tcxe_val, tcsc_val; + BsrwTimecodeMode tc; + u64 last_tc_utc_now; }; static GF_Err none_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) @@ -110,107 +133,484 @@ return GF_OK; } +static Bool bsrw_manipulate_tc(GF_FilterPacket *pck, GF_BSRWCtx *ctx, BSRWPid *pctx, GF_TimeCode *tc_in, GF_TimeCode *tc_out) +{ + gf_assert(tc_out); + if (ctx->tc == BSRW_TC_NONE) return GF_FALSE; + + //get the current timecode components + u64 cts = gf_timestamp_rescale(gf_filter_pck_get_cts(pck), (u64) pctx->fps.den * gf_filter_pck_get_timescale(pck), pctx->fps.num); + u64 n_frames = (cts * pctx->fps.den) % pctx->fps.num; + n_frames /= pctx->fps.den; + cts = cts * pctx->fps.den / pctx->fps.num; + u8 seconds = cts % 60; + cts /= 60; + u8 minutes = cts % 60; + cts /= 60; + u8 hours = (u8) cts; + + //get the current timecode + GF_TimeCode now = {0}; + if (!tc_in) { + now.n_frames = (u16) n_frames; + now.seconds = seconds; + now.minutes = minutes; + now.hours = hours; + } else { + memcpy(&now, tc_in, sizeof(GF_TimeCode)); + } + + //check if we are within the timecode manipulation range + Bool tc_change = GF_TRUE; + if (ctx->tcxs && gf_timecode_less(&now, &ctx->tcxs_val)) + tc_change = GF_FALSE; + if (ctx->tcxe && gf_timecode_greater(&now, &ctx->tcxe_val)) + tc_change = GF_FALSE; + if (!tc_change) return GF_FALSE; + + //check if we are infering `tcsc` from the first timecode + if (ctx->tcsc && strstr(ctx->tcsc, "first") && !ctx->tcsc_inferred) { + if (!tc_in) return GF_FALSE; + memcpy(&ctx->tcsc_val, tc_in, sizeof(GF_TimeCode)); + ctx->tcsc_inferred = GF_TRUE; + } + + //check few more constraints + if (ctx->tc == BSRW_TC_SHIFT || ctx->tc == BSRW_TC_CONSTANT) { + if (!tc_in) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("BSRW Cannot shift timecode without input timecode\n")); + return GF_FALSE; + } + } + + //get max fps + Float fps = (Float) pctx->fps.num / pctx->fps.den; + u32 max_fps = (u32) gf_ceil(fps); + + //reset the output timecode + memset(tc_out, 0, sizeof(GF_TimeCode)); + tc_out->max_fps = (Float) max_fps; + tc_out->counting_type = ctx->tcdf ? 4 : 0; + + //apply the timecode manipulation + switch (ctx->tc) + { + case BSRW_TC_REMOVE: + break; + case BSRW_TC_INSERT: + if (!ctx->tcsc_inferred) { + tc_out->n_frames = now.n_frames; + tc_out->seconds = now.seconds; + tc_out->minutes = now.minutes; + tc_out->hours = now.hours; + break; + } else { + //reset now, we will overwrite what we have + now.n_frames = (u16) n_frames; + now.seconds = seconds; + now.minutes = minutes; + now.hours = hours; + } + //fallthrough + case BSRW_TC_SHIFT: { + // Handle rollover for frames first + s32 frame_adjustment = ctx->tcsc_val.negative ? -ctx->tcsc_val.n_frames : ctx->tcsc_val.n_frames; + s32 total_frames = now.n_frames + frame_adjustment; + tc_out->n_frames = (total_frames + pctx->fps.num) % pctx->fps.num; + s32 second_carry = total_frames / pctx->fps.num; + if (total_frames < 0 && total_frames % pctx->fps.num != 0) { + second_carry--; + } + + // Handle rollover for seconds + s32 second_adjustment = ctx->tcsc_val.negative ? -ctx->tcsc_val.seconds : ctx->tcsc_val.seconds; + s32 total_seconds = now.seconds + second_adjustment + second_carry; + tc_out->seconds = (total_seconds + 60) % 60; + s32 minute_carry = total_seconds / 60; + if (total_seconds < 0 && total_seconds % 60 != 0) { + minute_carry--; + } + + // Handle rollover for minutes + s32 minute_adjustment = ctx->tcsc_val.negative ? -ctx->tcsc_val.minutes : ctx->tcsc_val.minutes; + s32 total_minutes = now.minutes + minute_adjustment + minute_carry; + tc_out->minutes = (total_minutes + 60) % 60; + s32 hour_carry = total_minutes / 60; + if (total_minutes < 0 && total_minutes % 60 != 0) { + hour_carry--; + } + + // Handle rollover for hours (assuming 24-hour format) + s32 hour_adjustment = ctx->tcsc_val.negative ? -ctx->tcsc_val.hours : ctx->tcsc_val.hours; + s32 total_hours = now.hours + hour_adjustment + hour_carry; + tc_out->hours = (total_hours + 24) % 24; + break; + } + case BSRW_TC_CONSTANT: + tc_out->n_frames = ctx->tcsc_val.n_frames; + tc_out->seconds = ctx->tcsc_val.seconds; + tc_out->minutes = ctx->tcsc_val.minutes; + tc_out->hours = ctx->tcsc_val.hours; + break; + case BSRW_TC_UTC: { + u64 now = 0; + //check sender NTP on packet + const GF_PropertyValue *date = gf_filter_pck_get_property(pck, GF_PROP_PCK_SENDER_NTP); + if (date) now = gf_net_ntp_to_utc(date->value.longuint); + //otherwise check UTC date mapping + else if ((date = gf_filter_pck_get_property(pck, GF_PROP_PCK_UTC_TIME))) { + now = date->value.longuint; + } else { + //otherwise use the current time + now = gf_net_get_utc(); + //safety check: two consecutives gf_net_get_utc() calls could return the same value + if (now == ctx->last_tc_utc_now) { + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("BSRW system call for getting provided the same value (" LLU "), increment by 1 frame. Consider adding a reframer with 'rt' option in your graph.\n", now)); + now += gf_timestamp_rescale(1, max_fps, 1000)+1; + } + ctx->last_tc_utc_now = now; + } + + //get tm struct + time_t utc_now = (time_t) (now / 1000); + struct tm *tm = gf_gmtime(&utc_now); + + //convert to timecode + tc_out->n_frames = (u16) gf_timestamp_rescale(now % 1000, 1000, max_fps); + tc_out->seconds = (u8) tm->tm_sec; + tc_out->minutes = (u8) tm->tm_min; + tc_out->hours = (u8) tm->tm_hour; + break; + } + + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("BSRW Unsupported timecode mode\n")); + return GF_FALSE; + } + + if (ctx->tcdf) { + u32 frame_drift = (u32) gf_ceil(60 * (max_fps - fps)); + + //apply existing drop frame rollover + if (pctx->tc_drop_count) { + s32 drop_adj = (gf_filter_pck_get_cts(pck) < pctx->drop_change_cts) ? -(s32)frame_drift : 0; + tc_out->n_frames += pctx->tc_drop_count + drop_adj; + tc_out->seconds += tc_out->n_frames / max_fps; + tc_out->n_frames %= max_fps; + tc_out->minutes += tc_out->seconds / 60; + tc_out->seconds %= 60; + tc_out->hours += tc_out->minutes / 60; + tc_out->minutes %= 60; + tc_out->hours %= 24; + } + + if (tc_out->minutes % 10 && tc_out->seconds == 0) { + if (frame_drift > 0 && !pctx->tc_dropped) { + //rewind to 0th frame + pctx->drop_change_cts = gf_filter_pck_get_cts(pck); + pctx->drop_change_cts -= tc_out->n_frames * gf_filter_pck_get_duration(pck); + + //apply the drop frame adjustment + tc_out->n_frames += frame_drift; + pctx->tc_drop_count += frame_drift; + pctx->tc_dropped = GF_TRUE; + } + if (tc_out->n_frames == frame_drift) + tc_out->drop_frame = 1; + } else if (tc_out->seconds == 2) { + //clear the flag when safe + pctx->tc_dropped = GF_FALSE; + } + } + + return GF_TRUE; +} + static GF_Err nalu_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck, u32 codec_type) { - Bool is_sei; - u32 size, pck_size, final_size; - GF_FilterPacket *dst; + Bool is_sei = GF_FALSE; u8 *output; + u32 pck_size; const u8 *data = gf_filter_pck_get_data(pck, &pck_size); if (!data) return gf_filter_pck_forward(pck, pctx->opid); + GF_BitStream *bs = gf_bs_new(data, pck_size, GF_BITSTREAM_READ); + if (!bs) return GF_OUT_OF_MEM; - final_size = 0; - size=0; - while (size<pck_size) { + while (gf_bs_available(bs)) { u8 nal_type=0; - u32 nal_hdr = pctx->nalu_size_length; - u32 nal_size = 0; - while (nal_hdr) { - nal_size |= datasize; - size++; - nal_hdr--; - if (!nal_hdr) break; - nal_size<<=8; - } - is_sei = GF_FALSE; + u32 nal_size = gf_bs_read_int(bs, 8*pctx->nalu_size_length); + u64 pos = gf_bs_get_position(bs); //AVC if (codec_type==0) { - nal_type = datasize & 0x1F; - if (nal_type == GF_AVC_NALU_SEI) is_sei = GF_TRUE; + //populate the avc state + if (ctx->tc) { + GF_BitStream *bs_nal = gf_bs_new(data + pos, nal_size, GF_BITSTREAM_READ); + s32 res = gf_avc_parse_nalu(bs_nal, pctx->avc); + gf_bs_del(bs_nal); + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("BSRW failed to parse AVC NALU\n")); + gf_bs_del(bs); + return GF_NON_COMPLIANT_BITSTREAM; + } + gf_bs_seek(bs, pos); + } + + nal_type = gf_bs_read_u8(bs) & 0x1F; + if (nal_type == GF_AVC_NALU_SEI) + is_sei = GF_TRUE; } //HEVC else if (codec_type==1) { - nal_type = (datasize & 0x7E) >> 1; + nal_type = (gf_bs_read_u8(bs) & 0x7E) >> 1; if ((nal_type == GF_HEVC_NALU_SEI_PREFIX) || (nal_type == GF_HEVC_NALU_SEI_SUFFIX)) is_sei = GF_TRUE; } //VVC else if (codec_type==2) { - nal_type = datasize+1 >> 3; + gf_bs_skip_bytes(bs, 1); + nal_type = gf_bs_read_u8(bs) >> 3; if ((nal_type == GF_VVC_NALU_SEI_PREFIX) || (nal_type == GF_VVC_NALU_SEI_SUFFIX)) is_sei = GF_TRUE; } - if (!is_sei) { - final_size += nal_size+pctx->nalu_size_length; - } - size += nal_size; + gf_bs_seek(bs, pos + nal_size); } - if (final_size == pck_size) + if (!is_sei && ctx->tc <= BSRW_TC_REMOVE) { + gf_bs_del(bs); return gf_filter_pck_forward(pck, pctx->opid); + } - dst = gf_filter_pck_new_alloc(pctx->opid, final_size, &output); - if (!dst) return GF_OUT_OF_MEM; +#ifdef GPAC_DISABLE_AV_PARSERS + gf_bs_del(bs); + return GF_NOT_SUPPORTED; +#endif - gf_filter_pck_merge_properties(pck, dst); + u32 tc_sei_type = codec_type == 0 ? 1 : 136; + SEI_Filter sei_filter = { + .is_whitelist = !ctx->rmsei, + .seis = ctx->seis, + .extra_filter = 0 + }; + + GF_BitStream *bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!bs_w) { + gf_bs_del(bs); + return GF_OUT_OF_MEM; + } - size=0; - while (size<pck_size) { - u8 nal_type=0; - u32 nal_hdr = pctx->nalu_size_length; - u32 nal_size = 0; - while (nal_hdr) { - nal_size |= datasize; - size++; - nal_hdr--; - if (!nal_hdr) break; - nal_size<<=8; + //get the existing timecode + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_TIMECODE); + GF_TimeCode *tc_in = p ? (GF_TimeCode*) p->value.data.ptr : NULL; + + GF_TimeCode tc_out; + Bool tc_change = bsrw_manipulate_tc(pck, ctx, pctx, tc_in, &tc_out); + sei_filter.extra_filter = tc_change ? -(s32)tc_sei_type : 0; + if (tc_change && ctx->tc > BSRW_TC_REMOVE) { + gf_bs_write_int(bs_w, 0, 8*pctx->nalu_size_length); + + if (codec_type == 0) { + gf_bs_write_int(bs_w, 0, 1); + gf_bs_write_int(bs_w, 0, 2); + gf_bs_write_int(bs_w, GF_AVC_NALU_SEI, 5); + } else if (codec_type == 1) { + gf_bs_write_int(bs_w, 0, 1); + gf_bs_write_int(bs_w, GF_HEVC_NALU_SEI_PREFIX, 6); + gf_bs_write_int(bs_w, 0, 6); + gf_bs_write_int(bs_w, 1, 3); } + + //write SEI type + gf_bs_write_int(bs_w, tc_sei_type, 8); + + //save position for size + u64 size_pos = gf_bs_get_position(bs_w); + gf_bs_write_int(bs_w, 0, 8); + + if (codec_type == 0) { + int sps_id = pctx->avc->sps_active_idx; + AVC_SPS *sps = &pctx->avc->spssps_id; + if (sps->vui.nal_hrd_parameters_present_flag || sps->vui.vcl_hrd_parameters_present_flag) { + gf_bs_write_int(bs_w, 0, 1 + sps->vui.hrd.cpb_removal_delay_length_minus1); + gf_bs_write_int(bs_w, 0, 1 + sps->vui.hrd.dpb_output_delay_length_minus1); + } + gf_bs_write_int(bs_w, 0/*pic_struct*/, 4); + gf_bs_write_int(bs_w, 1/*clock_timestamp_flag*/, 1); + gf_bs_write_int(bs_w, 0/*ct_type*/, 2); + gf_bs_write_int(bs_w, 0/*nuit_field_based_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.counting_type/*counting_type*/, 5); + gf_bs_write_int(bs_w, 1/*full_timestamp_flag*/, 1); + gf_bs_write_int(bs_w, 0/*discontinuity_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.drop_frame ? 1 : 0/*cnt_dropped_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.n_frames, 8); + gf_bs_write_int(bs_w, tc_out.seconds, 6); + gf_bs_write_int(bs_w, tc_out.minutes, 6); + gf_bs_write_int(bs_w, tc_out.hours, 5); + } else { + gf_bs_write_int(bs_w, 1/*num_clock_ts*/, 2); + gf_bs_write_int(bs_w, 1/*clock_timestamp_flag*/, 1); + gf_bs_write_int(bs_w, 0/*units_field_based_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.counting_type/*counting_type*/, 5); + gf_bs_write_int(bs_w, 1/*full_timestamp_flag*/, 1); + gf_bs_write_int(bs_w, 0/*discontinuity_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.drop_frame ? 1 : 0/*cnt_dropped_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.n_frames, 9); + gf_bs_write_int(bs_w, tc_out.seconds, 6); + gf_bs_write_int(bs_w, tc_out.minutes, 6); + gf_bs_write_int(bs_w, tc_out.hours, 5); + gf_bs_write_int(bs_w, 0/*time_offset_length*/, 5); + } + + //align to byte boundary + gf_bs_align(bs_w); + + //store the payload size + u64 pos = gf_bs_get_position(bs_w); + u64 sei_size = pos - size_pos - 1; + + //trailing bits + gf_bs_write_int(bs_w, 0x80, 8); + + //write the SEI size + pos = gf_bs_get_position(bs_w); + gf_bs_seek(bs_w, size_pos); + gf_bs_write_int(bs_w, (u32) sei_size, 8); + + //write the NAL size + u32 nal_size = (u32)pos - pctx->nalu_size_length; + gf_bs_seek(bs_w, 0); + gf_bs_write_int(bs_w, nal_size, 8*pctx->nalu_size_length); + gf_bs_seek(bs_w, pos); + } + + u32 rw_sei_size = 0; + u8 *rw_sei_payload = NULL; + gf_bs_seek(bs, 0); + while (gf_bs_available(bs)) { + u8 nal_type=0; + u32 nal_size = gf_bs_read_int(bs, 8*pctx->nalu_size_length); + u64 payload_pos = gf_bs_get_position(bs); is_sei = GF_FALSE; //AVC if (codec_type==0) { - nal_type = datasize & 0x1F; + nal_type = gf_bs_read_u8(bs) & 0x1F; if (nal_type == GF_AVC_NALU_SEI) is_sei = GF_TRUE; } //HEVC else if (codec_type==1) { - nal_type = (datasize & 0x7E) >> 1; + nal_type = (gf_bs_read_u8(bs) & 0x7E) >> 1; if ((nal_type == GF_HEVC_NALU_SEI_PREFIX) || (nal_type == GF_HEVC_NALU_SEI_SUFFIX)) is_sei = GF_TRUE; } //VVC else if (codec_type==2) { - nal_type = datasize+1 >> 3; + gf_bs_skip_bytes(bs, 1); + nal_type = gf_bs_read_u8(bs) >> 3; if ((nal_type == GF_VVC_NALU_SEI_PREFIX) || (nal_type == GF_VVC_NALU_SEI_SUFFIX)) is_sei = GF_TRUE; } + //allocate the temporary storage + rw_sei_size = nal_size; + if (rw_sei_payload) rw_sei_payload = gf_realloc(rw_sei_payload, rw_sei_size); + else rw_sei_payload = gf_malloc(rw_sei_size); + + //copy the NAL payload + gf_bs_seek(bs, payload_pos); + gf_bs_read_data(bs, rw_sei_payload, rw_sei_size); + + //reformat the SEI if (is_sei) { - size += nal_size; - continue; + switch (codec_type) + { + case 0: + rw_sei_size = gf_avc_reformat_sei(rw_sei_payload, rw_sei_size, GF_TRUE, NULL, &sei_filter); + break; + case 1: + rw_sei_size = gf_hevc_reformat_sei(rw_sei_payload, rw_sei_size, GF_TRUE, &sei_filter); + break; + case 2: + rw_sei_size = gf_vvc_reformat_sei(rw_sei_payload, rw_sei_size, GF_TRUE, &sei_filter); + break; + default: + break; + } + } + + // write the new NAL + if (rw_sei_size) { + gf_bs_write_int(bs_w, rw_sei_size, 8*pctx->nalu_size_length); + gf_bs_write_data(bs_w, rw_sei_payload, rw_sei_size); } - memcpy(output, &datasize-pctx->nalu_size_length, pctx->nalu_size_length+nal_size); - output += pctx->nalu_size_length+nal_size; - size += nal_size; } + + pck_size = (u32) gf_bs_get_position(bs_w); + GF_FilterPacket *dst = gf_filter_pck_new_alloc(pctx->opid, pck_size, &output); + if (!dst) return GF_OUT_OF_MEM; + gf_filter_pck_merge_properties(pck, dst); + + if (tc_change) { + if (ctx->tc == BSRW_TC_REMOVE) + gf_filter_pck_set_property(dst, GF_PROP_PCK_TIMECODE, NULL); + else + gf_filter_pck_set_property(dst, GF_PROP_PCK_TIMECODE, &PROP_DATA((u8*)&tc_out, sizeof(GF_TimeCode))); + } + + //copy the new data + gf_bs_seek(bs_w, 0); + gf_bs_read_data(bs_w, output, pck_size); + + //cleanup + gf_free(rw_sei_payload); + gf_bs_del(bs_w); + gf_bs_del(bs); + return gf_filter_pck_send(dst); } static GF_Err avc_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) { + if (ctx->tc) { + #ifdef GPAC_DISABLE_AV_PARSERS + return GF_NOT_SUPPORTED; + #endif + + //parse the sps/pps + if (pctx->avc) + goto finish; + + const GF_PropertyValue *prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_DECODER_CONFIG); + if (!prop) return GF_NOT_SUPPORTED; + + GF_AVCConfig *avcc = gf_odf_avc_cfg_read(prop->value.data.ptr, prop->value.data.size); + if (!avcc) return GF_NOT_SUPPORTED; + + GF_SAFEALLOC(pctx->avc, AVCState); + for (u32 i=0; i<gf_list_count(avcc->sequenceParameterSets); ++i) { + GF_NALUFFParam *slc = gf_list_get(avcc->sequenceParameterSets, i); + s32 idx = gf_avc_read_sps(slc->data, slc->size, pctx->avc, 0, NULL); + if (idx < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("BSRW failed to parse AVC SPS\n")); + gf_odf_avc_cfg_del(avcc); + return GF_NOT_SUPPORTED; + } + } + for (u32 i=0; i<gf_list_count(avcc->pictureParameterSets); ++i) { + GF_NALUFFParam *slc = gf_list_get(avcc->pictureParameterSets, i); + s32 idx = gf_avc_read_pps(slc->data, slc->size, pctx->avc); + if (idx < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("BSRW failed to parse AVC PPS\n")); + gf_odf_avc_cfg_del(avcc); + return GF_NOT_SUPPORTED; + } + } + + gf_odf_avc_cfg_del(avcc); + } + +finish: return nalu_rewrite_packet(ctx, pctx, pck, 0); } static GF_Err hevc_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) @@ -222,6 +622,133 @@ return nalu_rewrite_packet(ctx, pctx, pck, 2); } +static GF_Err av1_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) +{ + u32 pck_size; + const u8 *data = gf_filter_pck_get_data(pck, &pck_size); + if (!data) + return gf_filter_pck_forward(pck, pctx->opid); + +#ifdef GPAC_DISABLE_AV_PARSERS + return GF_NOT_SUPPORTED; +#endif + + GF_BitStream *bs = gf_bs_new(data, pck_size, GF_BITSTREAM_READ); + if (!bs) return GF_OUT_OF_MEM; + + //probe data + if (gf_media_probe_iamf(bs) || gf_media_probe_ivf(bs) || gf_media_aom_probe_annexb(bs)) { + gf_bs_del(bs); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("BSRW Timecode manipulation is only supported for AV1 Section 5 bitstreams\n")); + return gf_filter_pck_forward(pck, pctx->opid); + } + + //get the existing timecode + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_TIMECODE); + GF_TimeCode *tc_in = p ? (GF_TimeCode*) p->value.data.ptr : NULL; + + GF_TimeCode tc_out; + Bool tc_change = bsrw_manipulate_tc(pck, ctx, pctx, tc_in, &tc_out); + if (!tc_change) { + gf_bs_del(bs); + return gf_filter_pck_forward(pck, pctx->opid); + } + + GF_BitStream *bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!bs_w) { + gf_bs_del(bs); + return GF_OUT_OF_MEM; + } + + //insert timecode metadata + if (ctx->tc > BSRW_TC_REMOVE) { + gf_bs_write_u8(bs_w, 0x2a); + gf_av1_leb128_write(bs_w, 6/*8+39 bits*/); + gf_av1_leb128_write(bs_w, OBU_METADATA_TYPE_TIMECODE); + gf_bs_write_int(bs_w, tc_out.counting_type/*counting_type*/, 5); + gf_bs_write_int(bs_w, 1/*full_timestamp_flag*/, 1); + gf_bs_write_int(bs_w, 0/*discontinuity_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.drop_frame ? 1 : 0/*cnt_dropped_flag*/, 1); + gf_bs_write_int(bs_w, tc_out.n_frames, 9); + gf_bs_write_int(bs_w, tc_out.seconds, 6); + gf_bs_write_int(bs_w, tc_out.minutes, 6); + gf_bs_write_int(bs_w, tc_out.hours, 5); + gf_bs_write_int(bs_w, 0/*time_offset_length*/, 5); + gf_bs_align(bs_w); + } + + //remove existing timecode metadata + gf_bs_seek(bs, 0); + while (gf_bs_available(bs)) { + u64 to_copy; + u64 pos = gf_bs_get_position(bs); + + //read header + ObuType obu_type = OBU_RESERVED_0; + Bool obu_extension_flag = GF_FALSE, obu_has_size_field = GF_FALSE; + u8 tid = 0, sid = 0; + GF_Err e = gf_av1_parse_obu_header(bs, &obu_type, &obu_extension_flag, &obu_has_size_field, &tid, &sid); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("BSRW Error parsing AV1 OBU header, forwarding packet\n")); + gf_bs_del(bs_w); + gf_bs_del(bs); + return gf_filter_pck_forward(pck, pctx->opid); + } + + // read size + u64 obu_size = pck_size; + if (obu_has_size_field) { + obu_size = gf_av1_leb128_read(bs, NULL); + if (obu_size > pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("BSRW OBU size exceeds packet size, forwarding packet\n")); + gf_bs_del(bs_w); + gf_bs_del(bs); + return gf_filter_pck_forward(pck, pctx->opid); + } + } + u32 hdr_size = (u32)(gf_bs_get_position(bs) - pos); + + //check if timecode metadata + Bool is_metadata = obu_type == OBU_METADATA; + if (!is_metadata) goto transfer; + u64 metadata_type = gf_av1_leb128_read(bs, NULL); + if (metadata_type != OBU_METADATA_TYPE_TIMECODE) goto transfer; + + //skip timecode metadata + gf_bs_seek(bs, pos + hdr_size + obu_size); + continue; + + transfer: + gf_bs_seek(bs, pos); + to_copy = hdr_size + obu_size; + while (to_copy--) { + u32 byte = gf_bs_read_u8(bs); + gf_bs_write_u8(bs_w, byte); + } + } + + //send packet + u8* output = NULL; + pck_size = (u32) gf_bs_get_position(bs_w); + GF_FilterPacket *dst = gf_filter_pck_new_alloc(pctx->opid, pck_size, &output); + gf_filter_pck_merge_properties(pck, dst); + + if (ctx->tc == BSRW_TC_REMOVE) + gf_filter_pck_set_property(dst, GF_PROP_PCK_TIMECODE, NULL); + else + gf_filter_pck_set_property(dst, GF_PROP_PCK_TIMECODE, &PROP_DATA((u8*)&tc_out, sizeof(GF_TimeCode))); + + //copy the new data + gf_bs_seek(bs_w, 0); + gf_bs_read_data(bs_w, output, pck_size); + + //cleanup + gf_bs_del(bs_w); + gf_bs_del(bs); + + return gf_filter_pck_send(dst); +} + #ifndef GPAC_DISABLE_AV_PARSERS static void update_props(BSRWPid *pctx, GF_VUIInfo *vui) { @@ -250,6 +777,12 @@ u32 dsi_size; const GF_PropertyValue *prop; + if (ctx->tc) { + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_SEI_LOADED); + if (!prop) + gf_filter_pid_negotiate_property(pctx->ipid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE)); + } + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_DECODER_CONFIG); if (!prop) return GF_OK; pctx->reconfigure = GF_FALSE; @@ -297,7 +830,7 @@ gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); - if (ctx->rmsei) { + if (ctx->rmsei || ctx->seis.nb_items || ctx->tc) { pctx->rewrite_packet = avc_rewrite_packet; } else { pctx->rewrite_packet = none_rewrite_packet; @@ -305,6 +838,31 @@ return GF_OK; } +static GF_Err reconfigure_alternative_transfer_characteristic(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + // skip if not applicable + const GF_PropertyValue *prop; + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_COLR_TRANSFER_ALT); + if (!prop) return GF_OK; + Bool rm_alt_trc_sei=GF_FALSE; + if(ctx->seis.nb_items > 0){ + // atc SEI explicitly listed + for (u32 i = 0; i < ctx->seis.nb_items; i++) { + if (ctx->seis.valsi == 147) { + rm_alt_trc_sei = ctx->rmsei; + break; + } + } + } else { + // explicit removal + rm_alt_trc_sei = ctx->rmsei; + } + if (rm_alt_trc_sei){ + return gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_COLR_TRANSFER_ALT, NULL); + } + return GF_OK; +} + static GF_Err hevc_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) { GF_HEVCConfig *hvcc; @@ -313,6 +871,12 @@ u32 dsi_size; const GF_PropertyValue *prop; + if (ctx->tc) { + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_SEI_LOADED); + if (!prop) + gf_filter_pid_negotiate_property(pctx->ipid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE)); + } + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_DECODER_CONFIG); if (!prop) return GF_OK; pctx->reconfigure = GF_FALSE; @@ -334,7 +898,7 @@ if (ctx->pidc>=0) hvcc->profile_idc = ctx->pidc; if (ctx->pspace>=0) hvcc->profile_space = ctx->pspace; if (ctx->gpcflags>=0) hvcc->general_profile_compatibility_flags = ctx->gpcflags; - + e = reconfigure_alternative_transfer_characteristic(ctx, pctx); gf_odf_hevc_cfg_write(hvcc, &dsi, &dsi_size); pctx->nalu_size_length = hvcc->nal_unit_size; @@ -343,7 +907,7 @@ gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); - if (ctx->rmsei) { + if (ctx->rmsei || ctx->seis.nb_items || ctx->tc) { pctx->rewrite_packet = hevc_rewrite_packet; } else { pctx->rewrite_packet = none_rewrite_packet; @@ -376,7 +940,7 @@ if (ctx->pidc>=0) vvcc->general_profile_idc = ctx->pidc; if (ctx->lev>=0) vvcc->general_level_idc = ctx->pidc; - + e = reconfigure_alternative_transfer_characteristic(ctx, pctx); gf_odf_vvc_cfg_write(vvcc, &dsi, &dsi_size); pctx->nalu_size_length = vvcc->nal_unit_size; @@ -385,7 +949,7 @@ gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); - if (ctx->rmsei) { + if (ctx->rmsei || ctx->seis.nb_items) { pctx->rewrite_packet = vvc_rewrite_packet; } else { pctx->rewrite_packet = none_rewrite_packet; @@ -396,6 +960,24 @@ #endif /*GPAC_DISABLE_AV_PARSERS*/ } +static GF_Err av1_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + const GF_PropertyValue *prop; + + if (ctx->tc) { + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_SEI_LOADED); + if (!prop) + gf_filter_pid_negotiate_property(pctx->ipid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE)); + } + + if (ctx->tc) { + pctx->rewrite_packet = av1_rewrite_packet; + } else { + pctx->rewrite_packet = none_rewrite_packet; + } + return GF_OK; +} + static GF_Err none_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) { pctx->reconfigure = GF_FALSE; @@ -425,6 +1007,12 @@ #endif /*GPAC_DISABLE_AV_PARSERS*/ pctx->rewrite_vui = GF_TRUE; + if (ctx->tc) { + if (pctx->codec_id == GF_CODECID_AVC) { + pctx->vui.enable_pic_struct = ctx->tc != BSRW_TC_REMOVE; + return; + } + } if (ctx->sar.num>=0) return; if ((s32) ctx->sar.den>=0) return; if (ctx->cmx>-1) return; @@ -525,6 +1113,10 @@ if (! gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + gf_fatal_assert(prop); + u32 codec_id = prop->value.uint; + if (!pctx) { GF_SAFEALLOC(pctx, BSRWPid); if (!pctx) return GF_OUT_OF_MEM; @@ -535,12 +1127,11 @@ gf_list_add(ctx->pids, pctx); pctx->opid = gf_filter_pid_new(filter); if (!pctx->opid) return GF_OUT_OF_MEM; + pctx->codec_id = codec_id; init_vui(ctx, pctx); } - prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); - gf_fatal_assert(prop); - switch (prop->value.uint) { + switch (codec_id) { case GF_CODECID_AVC: case GF_CODECID_SVC: case GF_CODECID_MVC: @@ -558,6 +1149,9 @@ case GF_CODECID_MPEG4_PART2: pctx->rewrite_pid_config = m4v_rewrite_pid_config; break; + case GF_CODECID_AV1: + pctx->rewrite_pid_config = av1_rewrite_pid_config; + break; case GF_CODECID_AP4H: case GF_CODECID_AP4X: case GF_CODECID_APCH: @@ -574,8 +1168,25 @@ break; } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (prop) pctx->fps = prop->value.frac; + if (!pctx->fps.num || !pctx->fps.den) { + pctx->fps.num = 25; + pctx->fps.den = 1; + } + + if (ctx->tcdf) { + Float fps = (Float) pctx->fps.num / pctx->fps.den; + u32 max_fps = (u32) gf_ceil(fps); + u32 frame_drift = (u32) gf_ceil(60 * (max_fps - fps)); + if (!frame_drift) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("BSRW Requested to use NTSC drop-frame timecode, but fps does not impose any drift. Disabling drop-frame timecode\n")); + ctx->tcdf = GF_FALSE; + } + } + gf_filter_pid_copy_properties(pctx->opid, pctx->ipid); - pctx->codec_id = prop->value.uint; + pctx->codec_id = codec_id; pctx->reconfigure = GF_FALSE; gf_filter_pid_set_framing_mode(pid, GF_TRUE); //rewrite asap - waiting for first packet could lead to issues further down the chain, especially for movie fragments @@ -626,11 +1237,51 @@ return GF_OK; } +//copied and simplified from reframer to keep the same syntax +static GF_Err bsrw_parse_date(const char *date_in, GF_TimeCode *tc_out) +{ + char* date = (char*) date_in; + if (!date_in || !tc_out) + return GF_BAD_PARAM; + + if (date0 == '-') { + tc_out->negative = 1; + date++; + } + + u8 h, m, s; + u16 n_frames; + if (sscanf(date, "TC%hhu:%hhu:%hhu:%hu", &h, &m, &s, &n_frames) != 4) + return GF_BAD_PARAM; + + tc_out->hours = h; + tc_out->minutes = m; + tc_out->seconds = s; + tc_out->n_frames = n_frames; + return GF_OK; +} + static GF_Err bsrw_initialize(GF_Filter *filter) { GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); ctx->pids = gf_list_new(); + GF_Err e = GF_OK; + if (ctx->tcxs) e |= bsrw_parse_date(ctx->tcxs, &ctx->tcxs_val); + if (ctx->tcxe) e |= bsrw_parse_date(ctx->tcxe, &ctx->tcxe_val); + if (ctx->tcsc && !strstr(ctx->tcsc, "first")) { + e |= bsrw_parse_date(ctx->tcsc, &ctx->tcsc_val); + ctx->tcsc_inferred = GF_TRUE; + } + if (e) return e; + + if (ctx->tc == BSRW_TC_SHIFT || ctx->tc == BSRW_TC_CONSTANT) { + if (!ctx->tcsc) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("BSRW Timecode manipulation mode requires `tcsc` to be set\n")); + return GF_BAD_PARAM; + } + } + #ifdef GPAC_ENABLE_COVERAGE bsrw_update_arg(filter, NULL, NULL); #endif @@ -641,6 +1292,7 @@ GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); while (gf_list_count(ctx->pids)) { BSRWPid *pctx = gf_list_pop_back(ctx->pids); + if (pctx->avc) gf_free(pctx->avc); gf_free(pctx); } gf_list_del(ctx->pids); @@ -652,7 +1304,7 @@ ///do not change order of the first 3 { OFFS(cprim), "color primaries according to ISO/IEC 23001-8 / 23091-2", GF_PROP_CICP_COL_PRIM, "-1", NULL, GF_FS_ARG_UPDATE}, { OFFS(ctfc), "color transfer characteristics according to ISO/IEC 23001-8 / 23091-2", GF_PROP_CICP_COL_TFC, "-1", NULL, GF_FS_ARG_UPDATE}, - { OFFS(cmx), "color matrix coeficients according to ISO/IEC 23001-8 / 23091-2", GF_PROP_CICP_COL_MX, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(cmx), "color matrix coefficients according to ISO/IEC 23001-8 / 23091-2", GF_PROP_CICP_COL_MX, "-1", NULL, GF_FS_ARG_UPDATE}, { OFFS(sar), "aspect ratio to rewrite", GF_PROP_FRACTION, "-1/-1", NULL, GF_FS_ARG_UPDATE}, { OFFS(m4vpl), "set ProfileLevel for MPEG-4 video part two", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, { OFFS(fullrange), "video full range flag", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, @@ -664,8 +1316,20 @@ { OFFS(pidc), "profile IDC for HEVC and VVC", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, { OFFS(pspace), "profile space for HEVC", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, { OFFS(gpcflags), "general compatibility flags for HEVC", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(tcxs), "timecode manipulation start", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + { OFFS(tcxe), "timecode manipulation end", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + { OFFS(tcdf), "use NTSC drop-frame counting for timecodes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(tcsc), "timecode constant for use with shift/constant modes", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + { OFFS(tc), "timecode manipulation mode\n" + "- none: do not change anything\n" + "- remove: remove timecodes\n" + "- insert: insert timecodes based on cts or `tcsc` (if provided)\n" + "- shift: shift timecodes based by `tcsc`\n" + "- constant: overwrite timecodes with `tcsc`\n" + "- utc: insert timecodes based on the utc time on the packet or the current time", GF_PROP_UINT, "none", "none|remove|insert|shift|constant|utc", GF_FS_ARG_UPDATE}, + { OFFS(seis), "list of SEI message types (4,137,144,...). When used with `rmsei`, this serves as a blacklist. If left empty, all SEIs will be removed. Otherwise, it serves as a whitelist", GF_PROP_UINT_LIST, NULL, NULL, GF_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, { OFFS(rmsei), "remove SEI messages from bitstream for AVC|H264, HEVC and VVC", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, - { OFFS(vidfmt), "video format for AVC|H264, HEVC and VVC", GF_PROP_UINT, "-1", "component|pal|ntsc|secam|mac|undef", GF_FS_ARG_UPDATE}, + { OFFS(vidfmt), "video format for AVC|H264, HEVC and VVC", GF_PROP_SINT, "-1", "component|pal|ntsc|secam|mac|undef", GF_FS_ARG_UPDATE}, {0} }; @@ -698,12 +1362,11 @@ CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), #endif - }; GF_FilterRegister BSRWRegister = { .name = "bsrw", - GF_FS_SET_DESCRIPTION("Compressed bitstream rewriter") + GF_FS_SET_DESCRIPTION("Bitstream metadata rewriter") GF_FS_SET_HELP("This filter rewrites some metadata of various bitstream formats.\n" "The filter can currently modify the following properties in video bitstreams:\n" "- MPEG-4 Visual:\n" @@ -714,6 +1377,9 @@ " - profile, level, profile compatibility\n" " - video format, video fullrange\n" " - color primaries, transfer characteristics and matrix coefficients (or remove all info)\n" + " - (AVC|HEVC) timecode" + "- AV1:\n" + " - timecode\n" "- ProRes:\n" " - sample aspect ratio\n" " - color primaries, transfer characteristics and matrix coefficients\n" @@ -726,6 +1392,29 @@ "- VVC: profile IDC, general profile and level indication\n" " \n" "The filter will work in passthrough mode for all other codecs and media types.\n" + "# Timecode Manipulation\n" + "One can optionally set the -tcxs() and -tcxe() to define the start and end of timecode manipulation. By default, the filter will process all packets.\n" + "Some modes require you to define -tcsc(). This follows the same format as the timecode itself (-'TC'HH:MM:SS:FF). The use of negative values is only meaningful in the `shift` mode. It's also possible to set -tcsc() to `first` to infer the value from the first timecode when timecode manipulation starts. In this case, unless a timecode is found, the filter will not perform any operation.\n" + "## Modes\n" + "Timecode manipulation has four modes and they all have their own operating nuances.\n" + "### Remove\n" + "Remove all timecodes from the bitstream.\n" + "### Insert\n" + "Insert timecodes based on the CTS. If -tcsc() is set, it will be used as timecode offset.\n" + "This mode will overwrite existing timecodes (if any).\n" + "### Shift\n" + "Shift all timecodes by the value defined in -tcsc().\n" + "This mode will only modify timecodes if they exists, no new timecode will be inserted.\n" + "### Constant\n" + "Set all timecodes to the value defined in -tcsc().\n" + "Again, this mode wouldn't insert new timecodes.\n" + "### UTC\n" + "Uses the `SenderNTP` property, `UTC` property on the packet, or the current UTC time to set the timecode.\n" + "This mode will overwrite existing timecodes (if any).\n" + "## Examples\n" + "EX gpac -i in.mp4 bsrw:tc=insert dst\n" + "EX gpac -i in.mp4 bsrw:tc=insert:tcsc=TC00:00:10:00 dst\n" + "EX gpac -i in.mp4 bsrw:tc=shift:tcsc=TC00:00:10:00:tcxs=TC00:01:00:00 dst\n" ) .private_size = sizeof(GF_BSRWCtx), .max_extra_pids = 0xFFFFFFFF, @@ -736,7 +1425,8 @@ .finalize = bsrw_finalize, .configure_pid = bsrw_configure_pid, .process = bsrw_process, - .update_arg = bsrw_update_arg + .update_arg = bsrw_update_arg, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *bsrw_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/compose.c -> gpac-26.02.0.tar.gz/src/filters/compose.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / compositor filter @@ -127,6 +127,7 @@ gf_filter_abort(filter); } else if (!ret && ctx->vfr && !ctx->check_eos_state && !nb_sys_streams_active && ctx->scene_sampled_clock && !ctx->validator_mode) { ctx->check_eos_state = 1; + ctx->last_check_pass = 0; if (!ctx->validator_mode) ctx->force_next_frame_redraw = GF_TRUE; } @@ -233,7 +234,8 @@ pid = ctx->vout = gf_filter_pid_new(ctx->filter); gf_filter_pid_set_name(pid, "vout"); //compositor initiated for RT playback, vout pid may not be connected - gf_filter_pid_set_loose_connect(pid); + if (ctx->player) + gf_filter_pid_set_loose_connect(pid); gf_filter_pid_set_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL) ); @@ -1027,8 +1029,8 @@ "", GF_PROP_UINT, "none", "none|walk|fly|pan|game|slide|exam|orbit|vr", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, { OFFS(linegl), "indicate that outlining shall be done through OpenGL pen width rather than vectorial outlining", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, { OFFS(epow2), "emulate power-of-2 textures for OpenGL (old hardware). Ignored if OpenGL rectangular texture extension is enabled\n" - "- yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations\n" - "- no: video is resized to a power of 2 texture when mapping to a shape", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + "- true: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations\n" + "- false: video is resized to a power of 2 texture when mapping to a shape", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, { OFFS(paa), "indicate whether polygon antialiasing should be used in full antialiasing mode. If not set, only lines and points antialiasing are used", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, { OFFS(bcull), "indicate whether backface culling shall be disable or not\n" "- on: enables backface culling\n" @@ -1153,6 +1155,13 @@ CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_PIXFMT, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_WIDTH, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_HEIGHT, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_SAMPLE_RATE, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_NUM_CHANNELS, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_AUDIO_FORMAT, 0), }; @@ -1201,6 +1210,7 @@ "- If the URL gives less views than rendered, the views will be repeated\n" "- If the URL gives more views than rendered, the extra views will be ignored\n" "\n" + "\n" "The compositor can act as a source filter when the -src() option is explicitly set, independently from the operating mode:\n" "EX gpac compositor:src=source.mp4 vout\n" "\n" @@ -1221,6 +1231,7 @@ .reconfigure_output = compose_reconfig_output, .update_arg = compose_update_arg, .probe_url = compose_probe_url, + .hint_class_type = GF_FS_CLASS_AV }; const GF_FilterRegister *compositor_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dasher.c -> gpac-26.02.0.tar.gz/src/filters/dasher.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2024 + * Copyright (c) Telecom ParisTech 2018-2026 * All rights reserved * * This file is part of GPAC / MPEG-DASH/HLS segmenter @@ -40,12 +40,12 @@ { GF_List *streams; - //period element we will fill + //DASH Period XML element we will fill GF_MPD_Period *period; } GF_DasherPeriod; -enum -{ +//bitstream switching modes +GF_OPT_ENUM (DasherBSSwitchingMode, DASHER_BS_SWITCH_DEF=0, DASHER_BS_SWITCH_OFF, DASHER_BS_SWITCH_ON, @@ -54,7 +54,7 @@ DASHER_BS_SWITCH_BOTH, DASHER_BS_SWITCH_FORCE, DASHER_BS_SWITCH_MULTI, -}; +); typedef enum { @@ -66,30 +66,29 @@ DASHER_UTCREF_INBAND, } DasherUTCTimingType; -enum -{ +// NTP clock handling as a packet property +GF_OPT_ENUM (DasherNTPClockMode, DASHER_NTP_REM=0, DASHER_NTP_YES, DASHER_NTP_KEEP, -}; +); -enum -{ +// Sample Access Point signalling (SAP 1 also being called 'sync' as it has been called in ISOBMFF) +GF_OPT_ENUM (DasherSAPStrictMode, DASHER_SAP_OFF=0, DASHER_SAP_SIG, DASHER_SAP_ON, DASHER_SAP_INTRA_ONLY, -}; +); -enum -{ +// Values for the 'sbound' option +GF_OPT_ENUM (DasherTSSHandlingMode, DASHER_BOUNDS_OUT=0, DASHER_BOUNDS_CLOSEST, DASHER_BOUNDS_IN, -}; +); -enum -{ +GF_OPT_ENUM (DasherMuxType, DASHER_MUX_ISOM=0, DASHER_MUX_TS, DASHER_MUX_MKV, @@ -97,15 +96,17 @@ DASHER_MUX_OGG, DASHER_MUX_RAW, DASHER_MUX_AUTO, -}; +); + +// MPEG-H 3D Audio flags +GF_OPT_ENUM (DasherAdaptSetGenMode, -enum -{ DASHER_MPHA_NO=0, DASHER_MPHA_COMP_ONLY, - DASHER_MPHA_ALL -}; + DASHER_MPHA_ALL, +); +// DASHer 'forward_mode' option values enum { DASHER_FWD_NO = 0, @@ -123,33 +124,34 @@ DASHER_SYNC_PRESENT, }; -enum -{ + +GF_OPT_ENUM (DasherCMAFMode, DASHER_CMAF_NONE=0, DASHER_CMAF_CMFC, - DASHER_CMAF_CMF2 -}; + DASHER_CMAF_CMF2, +); -enum -{ +GF_OPT_ENUM (DasherDefaultKIDInjection, DASHER_DEFKID_OFF=0, DASHER_DEFKID_ON, - DASHER_DEFKID_AUTO -}; + DASHER_DEFKID_AUTO, +); -enum -{ +// Period switching +GF_OPT_ENUM (DasherPeriodSwitchMode, DASHER_PSWITCH_SINGLE=0, DASHER_PSWITCH_FORCE, - DASHER_PSWITCH_STSD -}; -enum -{ + DASHER_PSWITCH_STSD, +); + +// Segment force sync +GF_OPT_ENUM (DasherWaitLastPktCtrl, DASHER_SEGSYNC_NO=0, DASHER_SEGSYNC_YES, - DASHER_SEGSYNC_AUTO -}; + DASHER_SEGSYNC_AUTO, +); +// Index mode as used from a GHI (GPAC HTTP Streaming index) demuxer enum { IDXMODE_NONE=0, @@ -160,6 +162,24 @@ IDXMODE_SEG, }; +// 'sflush' option (ex 'force_flush' option) +GF_OPT_ENUM (DasherSegFlushMode, + SFLUSH_OFF=0, + SFLUSH_SINGLE, + SFLUSH_END, +); + +enum +{ + AC4_OTHER_CONTENT = 0, + AC4_IMMERSIVE_STEREO, + AC4_IMMERSIVE_STEREO_ATMOS, + AC4_CHANNEL_BASED_CONTENT, + AC4_CHANNEL_BASED_IMMERSIVE_CONTENT, + AC4_OBJECT_BASED_CONTENT, + AC4_OBJECT_BASED_AJOC_CONTENT, +}; + //these are not exported for now //get destination name by index char *gf_filter_pid_get_destination_ex(GF_FilterPid *pid, u32 dst_idx); @@ -172,23 +192,29 @@ typedef struct { - u32 bs_switch, profile, spd, cp, ntp; + DasherBSSwitchingMode bs_switch; + GF_DashProfile profile; + u32 spd; + DasherNTPClockMode ntp; + GF_DASH_ContentLocationMode cp; s32 subs_sidx; s32 buf, timescale; - Bool sfile, sseg, no_sar, mix_codecs, stl, tpl, align, sap, no_frag_def, sidx, split, hlsc, strict_cues, force_flush, last_seg_merge; - u32 mha_compat; - u32 strict_sap; - u32 pssh; - u32 cmaf; - u32 dkid; + Bool sfile, sseg, no_sar, mix_codecs, stl, tpl, align, sap, no_frag_def, sidx, split, hlsc, strict_cues, force_flush, last_seg_merge, keep_ts, base64; + DasherAdaptSetGenMode mha_compat; + DasherSegFlushMode sflush; + DasherSAPStrictMode strict_sap; + GF_DASHPSSHMode pssh; + DasherCMAFMode cmaf; + DasherDefaultKIDInjection dkid; GF_Fraction segdur; - u32 dmode; + GF_DashDynamicMode dmode; char *template; char *segext; char *initext; - u32 muxtype; + DasherMuxType muxtype; Bool rawsub; char *profX; + char *query; Double asto; char *ast; char *state; @@ -199,22 +225,25 @@ Bool check_dur, skip_seg, loop, reschedule, scope_deps, keep_src, tpl_force, keep_segs; Double refresh, tsb, subdur; u64 *_p_gentime, *_p_mpdtime; - Bool cmpd, dual, sreg; + Bool cmpd, dual, segcts, sreg, ttml_agg, evte_agg; char *styp; Bool sigfrag; - u32 sbound, pswitch; + DasherTSSHandlingMode sbound; + DasherPeriodSwitchMode pswitch; char *utcs; char *mname; char *hlsdrm; char *ckurl; GF_PropStringList hlsx; - u32 llhls; + GF_DashHLSLowLatencyType llhls; + Bool hlsiv; //inherited from mp4mx GF_Fraction cdur; Bool ll_preload_hint, ll_rend_rep; Bool gencues, force_init, gxns; Double ll_part_hb; - u32 hls_absu, seg_sync; + GF_DashAbsoluteURLMode hls_absu; + DasherWaitLastPktCtrl seg_sync; Bool hls_ap; //internal @@ -246,6 +275,7 @@ Bool on_demand_done; Bool subdur_done; char *out_path; + char *out_path_alt; GF_Err setup_failure; @@ -263,7 +293,9 @@ Bool post_play_events; Bool force_period_switch; + GF_Fraction64 period_switch_cts; Bool period_not_ready; + Bool period_pck_sent; Bool check_connections; //-1 forces report update, otherwise this is a packet count @@ -271,7 +303,8 @@ Bool purge_segments; - Bool is_playing; + u32 nb_playing; + Bool use_mabr; Bool no_seg_dur; @@ -307,14 +340,10 @@ Bool move_to_static; Bool explicit_mode; -} GF_DasherCtx; + Bool inband_event; -typedef enum -{ - DASHER_HDR_NONE=0, - DASHER_HDR_PQ10, - DASHER_HDR_HLG, -} DasherHDRType; + Bool has_pid_removed; +} GF_DasherCtx; typedef struct _dash_stream { @@ -338,9 +367,13 @@ char *hls_vp_name; u32 nb_surround, nb_lfe, atmos_complexity_type; u64 ch_layout; + u32 ch_mask; + u8 ac4_content_type; GF_PropVec4i srd; - DasherHDRType hdr_type; + u32 color_primaries, color_transfer_characteristics, color_matrix, color_transfer_characteristics_alt; Bool sscale; + Bool skip_sap; + char *init_base_64; //TODO: get the values for all below u32 view_id; @@ -406,7 +439,7 @@ u64 seg_start_time; Bool split_set_names; Bool skip_tpl_reuse; - u64 max_period_dur; + u64 max_period_dur, current_max_period_dur; GF_Filter *dst_filter; @@ -479,7 +512,7 @@ GF_Fraction64 duration; GF_List *packet_queue; u32 nb_sap_in_queue; - u32 sbound; + DasherTSSHandlingMode sbound; u32 request_period_switch; @@ -515,6 +548,7 @@ u64 frag_start_offset, frag_first_ftdt; u32 tpl_use_time; + Bool last_stl_is_ll; } GF_DashStream; static void dasher_flush_segment(GF_DasherCtx *ctx, GF_DashStream *ds, Bool is_last_in_period); @@ -561,12 +595,18 @@ #endif -static void dasher_check_outpath(GF_DasherCtx *ctx) +static void dasher_ensure_outpath(GF_DasherCtx *ctx) { if (!ctx->out_path) { ctx->out_path = gf_filter_pid_get_destination(ctx->opid); - if (!ctx->out_path) return; + if (!ctx->out_path) { + //special case when connecting to filters without a dst set (eg custom filters), get output name from mname + GF_Filter *adst = gf_filter_pid_enum_destinations(ctx->opid, 0); + if (adst && ctx->mname) ctx->out_path = gf_strdup(ctx->mname); + return; + } + // output manifest name for ATSC3 if (ctx->mname) { char *sep = strstr(ctx->out_path, "://"); if (sep) { @@ -582,7 +622,7 @@ if (ctx->opid) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_URL, &PROP_STRING(ctx->out_path) ); if (ctx->opid_alt) - gf_filter_pid_set_property(ctx->opid_alt, GF_PROP_PID_URL, &PROP_STRING(ctx->out_path) ); + gf_filter_pid_set_property(ctx->opid_alt, GF_PROP_PID_URL, &PROP_STRING(ctx->out_path_alt) ); } @@ -661,7 +701,6 @@ if (a_ds == ds) continue; if (gf_list_find(a_ds->complementary_streams, ds)>=0) { - bitrate += dasher_get_dep_bitrate(ctx, a_ds); } } @@ -733,11 +772,9 @@ s32 res = gf_list_find(ctx->current_period->streams, ds); //force end of segment if stream is not yet done and in current period if ((res>=0) && !ds->done && !ds->seg_done) { - GF_DashStream *base_ds; - - base_ds = ds->muxed_base ? ds->muxed_base : ds; + GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; if (is_new_period_request) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher New period requested during PID %s reconfiguration\n", gf_filter_pid_get_name(ds->ipid) )); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher New period requested for PID %s\n", gf_filter_pid_get_name(ds->ipid) )); } else { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher PID %s config changed during active period, forcing period switch\n", gf_filter_pid_get_name(ds->ipid) )); } @@ -747,7 +784,7 @@ e = GF_BAD_PARAM; goto exit; } - base_ds->nb_comp_done ++; + base_ds->nb_comp_done++; ds->first_cts_in_next_seg = ds->est_first_cts_in_next_seg; if (base_ds->nb_comp_done == base_ds->nb_comp) { @@ -755,6 +792,12 @@ } ctx->force_period_switch = GF_TRUE; + if (!ctx->period_switch_cts.den + || gf_timestamp_less(ds->period_continuity_next_cts, ds->timescale, ctx->period_switch_cts.num, ctx->period_switch_cts.den) + ) { + ctx->period_switch_cts.num = ds->period_continuity_next_cts; + ctx->period_switch_cts.den = ds->timescale; + } dasher_update_period_duration(ctx, GF_TRUE); } //remove stream from period @@ -798,11 +841,10 @@ if (inject_in_period) { gf_list_add(ctx->current_period->streams, ds); ds->period = ctx->current_period; - dasher_setup_period(filter, ctx, ds); + e = dasher_setup_period(filter, ctx, ds); //force a MPD publish asap if (ctx->dmode != GF_DASH_STATIC) ctx->mpd->publishTime = 0; - e = GF_OK; goto exit; } } @@ -810,7 +852,6 @@ ds->period = ctx->next_period; exit: - ds->stl = ctx->stl; if (ctx->sigfrag) { const GF_PropertyValue *p = gf_filter_pid_get_property_str(ds->ipid, "source_template"); @@ -846,17 +887,18 @@ } } -static void dasher_send_encode_hints(GF_DasherCtx *ctx, GF_DashStream *ds) +static void dasher_send_transport_hints(GF_DasherCtx *ctx, GF_DashStream *ds) { - if (!ctx->sfile && !ds->stl && !ctx->use_cues) { + //send transport hints even if segment timeline is used + if (!ctx->sfile && !ctx->use_cues) { GF_FilterEvent evt; - GF_FEVT_INIT(evt, GF_FEVT_ENCODE_HINTS, ds->ipid) + GF_FEVT_INIT(evt, GF_FEVT_TRANSPORT_HINTS, ds->ipid) if (!ds->dash_dur.num) dasher_get_dash_dur(ctx, ds); switch (ctx->from_index) { case IDXMODE_NONE: - evt.encode_hints.intra_period = ds->dash_dur; + evt.transport_hints.seg_duration = ds->dash_dur; break; case IDXMODE_SEG: case IDXMODE_CHILD: @@ -864,9 +906,10 @@ case IDXMODE_ALL: case IDXMODE_INIT: case IDXMODE_MANIFEST: - evt.encode_hints.gen_dsi_only = GF_TRUE; + evt.transport_hints.gen_dsi_only = GF_TRUE; break; } + evt.transport_hints.wait_seg_boundary = ctx->segcts; gf_filter_pid_send_event(ds->ipid, &evt); } @@ -883,13 +926,54 @@ return GF_FALSE; } +static Bool dasher_merge_prop(void *cbk, u32 prop_4cc, const char *prop_name, const GF_PropertyValue *src_prop) +{ + GF_DasherCtx *ctx = (GF_DasherCtx *) cbk; + + switch (prop_4cc) { + case GF_PROP_PID_FILE_EXT: + case GF_PROP_PID_MIME: + case GF_PROP_PID_DASH_SEGMENTS: + case GF_PROP_PID_HLS_REF: + case GF_PROP_PID_REP_ID: + case GF_PROP_PID_DASH_DUR: + case GF_PROP_PID_PERIOD_ID: + case GF_PROP_PID_AS_ID: + case GF_PROP_PID_ID: + case GF_PROP_PID_DEPENDENCY_ID: + case GF_PROP_PID_DASH_SPARSE: + case GF_PROP_PID_MUX_SRC: + case GF_PROP_PID_DASH_MODE: + case GF_PROP_PID_FORCE_SEG_SYNC: + case GF_PROP_PID_NO_INIT: + case GF_PROP_PID_PREMUX_STREAM_TYPE: + case GF_PROP_PID_TEMPLATE: + case GF_PROP_PID_BITRATE: + case GF_PROP_PID_INIT_NAME: + case GF_PROP_PID_CODEC: + case GF_PROP_PID_DASH_MULTI_PID: + case GF_PROP_PID_DASH_MULTI_PID_IDX: + case GF_PROP_PID_DASH_MULTI_TRACK: + case GF_PROP_PID_LLHAS_MODE: + case GF_PROP_PID_TIMESHIFT_SEGS: + case GF_PROP_PID_TIMESCALE: + case GF_PROP_PID_DASH_CUE: + return GF_FALSE; + case GF_PROP_PID_CENC_PSSH: + if (ctx->pssh == GF_DASH_PSSH_MPD) return GF_FALSE; + return GF_TRUE; + } + return GF_TRUE; +} + static GF_Err dasher_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { Bool period_switch = GF_FALSE; + Bool is_reconfigure = GF_FALSE; const GF_PropertyValue *p, *dsi=NULL; u32 dc_crc, dc_enh_crc; GF_Err e; - GF_DashStream *ds; + GF_DashStream *ds=NULL; Bool old_period_switch; u32 prev_stream_type; Bool new_period_request = GF_FALSE; @@ -903,10 +987,24 @@ if (ds->dyn_bitrate) dasher_update_bitrate(ctx, ds); gf_list_del_item(ctx->pids, ds); gf_list_del_item(ctx->current_period->streams, ds); + + // ds can be pointed to in other dash streams in the muxed_base member + u32 stream_nb = gf_list_count(ctx->current_period->streams); + for (u32 i=0; i<stream_nb; i++) { + GF_DashStream *ds2 = gf_list_get(ctx->current_period->streams, i); + if (ds && ds2 && ds2->muxed_base == ds) { + ds2->muxed_base = NULL; + } + //we also may need to swap the set ds + if (ds2->set->udta==ds) + ds2->set->udta = ds2; + } + if (ctx->next_period) gf_list_del_item(ctx->next_period->streams, ds); dasher_reset_stream(filter, ds, GF_TRUE); gf_free(ds); + ctx->has_pid_removed = GF_TRUE; } return GF_OK; } @@ -963,7 +1061,8 @@ gf_free(out_path); break; } - gf_free(out_path); + if (ctx->out_path_alt) gf_free(ctx->out_path_alt); + ctx->out_path_alt = out_path; //reset any sourceID given in the dst_arg and assign sourceID to be the dasher filter gf_filter_reset_source(ctx->alt_dst); @@ -996,7 +1095,7 @@ //for routeout gf_filter_pid_set_property(opid, GF_PROP_PID_PREMUX_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); - dasher_check_outpath(ctx); + dasher_ensure_outpath(ctx); p = gf_filter_pid_caps_query(pid, GF_PROP_PID_FILE_EXT); if (p) { @@ -1028,8 +1127,9 @@ gf_filter_pid_disable_clone(opid); //override URL/path when loaded dynamically in case output filter(s) do not set a manifest name - gf_filter_pid_set_property(opid, GF_PROP_PID_URL, ctx->out_path ? &PROP_STRING(ctx->out_path) : NULL ); - gf_filter_pid_set_property(opid, GF_PROP_PID_FILEPATH, ctx->out_path ? &PROP_STRING(ctx->out_path) : NULL ); + char *path = (opid==ctx->opid_alt) ? ctx->out_path_alt : ctx->out_path; + gf_filter_pid_set_property(opid, GF_PROP_PID_URL, path ? &PROP_STRING(path) : NULL ); + gf_filter_pid_set_property(opid, GF_PROP_PID_FILEPATH, path ? &PROP_STRING(path) : NULL ); } if (!strcmp(segext, "m3u8")) { @@ -1069,7 +1169,7 @@ manifest_type = 1; } if (!gf_sys_is_test_mode() && (ctx->dmode>=GF_DASH_DYNAMIC)) - manifest_type |= 0x80000000; + manifest_type |= (1<<8); gf_filter_pid_set_property(opid, GF_PROP_PID_IS_MANIFEST, &PROP_UINT(manifest_type)); } @@ -1096,10 +1196,10 @@ /*initial connection and we already have sent play event, send a PLAY on this new PID TODO: we need to send STOP/PLAY depending on period */ - if (ctx->is_playing) { + if (ctx->nb_playing) { GF_FilterEvent evt; - dasher_send_encode_hints(ctx, ds); + dasher_send_transport_hints(ctx, ds); GF_FEVT_INIT(evt, GF_FEVT_PLAY, ds->ipid); evt.play.speed = 1.0; @@ -1112,6 +1212,8 @@ gf_filter_pid_copy_properties(ds->opid, pid); gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_CUE, &PROP_STRING("inband") ); } + } else { + is_reconfigure = GF_TRUE; } gf_filter_pid_set_framing_mode(pid, GF_TRUE); @@ -1160,10 +1262,11 @@ #define CHECK_PROP_PROP(_type, _mem, _e) \ p = gf_filter_pid_get_property(pid, _type); \ if (!p && (_e<=0) ) return _e; \ - if (p != _mem) period_switch = GF_TRUE;\ + if (p && _mem && !gf_props_equal(p, _mem)) period_switch = GF_TRUE;\ + else if (!p && _mem) period_switch = GF_TRUE; \ + else if (p && !_mem) period_switch = GF_TRUE; \ _mem = p; \ - prev_stream_type = ds->stream_type; CHECK_PROP(GF_PROP_PID_STREAM_TYPE, ds->stream_type, GF_NOT_SUPPORTED) @@ -1177,6 +1280,7 @@ } ds->tile_base = GF_FALSE; + ds->skip_sap = GF_FALSE; if (ds->stream_type != GF_STREAM_FILE) { u32 prev_bitrate = ds->bitrate; @@ -1187,24 +1291,16 @@ if (prev_stream_type==ds->stream_type) period_switch = GF_FALSE; - CHECK_PROP(GF_PROP_PID_BITRATE, ds->bitrate, GF_EOS) - if (!ds->bitrate && prev_bitrate) { - ds->bitrate = prev_bitrate; - period_switch = GF_FALSE; - } - if (ds->bitrate && period_switch) { - //allow 20% variation in bitrate, otherwise force period switch - if ((ds->bitrate <= 120 * prev_bitrate / 100) && (ds->bitrate >= 80 * prev_bitrate / 100)) { - period_switch = GF_FALSE; - } - } + //ignore bitrate changes for period switch detection as this may change quite a lot when loading from playlists + p = gf_filter_pid_get_property(pid, GF_PROP_PID_BITRATE); + ds->bitrate = p ? p->value.uint : prev_bitrate; CHECK_PROP(GF_PROP_PID_CODECID, ds->codec_id, GF_NOT_SUPPORTED) CHECK_PROP(GF_PROP_PID_TIMESCALE, ds->timescale, GF_NOT_SUPPORTED) if (!ds->timescale) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Dasher Input PID %s has no timescale, cannot dash\n", gf_filter_pid_get_name(pid) )); - return GF_NON_COMPLIANT_BITSTREAM; + return GF_FILTER_NOT_SUPPORTED; } if (ds->stream_type==GF_STREAM_VISUAL) { @@ -1212,7 +1308,7 @@ CHECK_PROP(GF_PROP_PID_HEIGHT, ds->height, GF_EOS) //don't return if not defined CHECK_PROP_FRAC(GF_PROP_PID_SAR, ds->sar, GF_EOS) - if (!ds->sar.num) ds->sar.num = ds->sar.den = 1; + if (ds->sar.num<=0) ds->sar.num = ds->sar.den = 1; CHECK_PROP_FRAC(GF_PROP_PID_FPS, ds->fps, GF_EOS) @@ -1252,6 +1348,15 @@ CHECK_PROP(GF_PROP_PID_SAMPLE_RATE, ds->sr, GF_EOS) CHECK_PROP(GF_PROP_PID_NUM_CHANNELS, ds->nb_ch, GF_EOS) CHECK_PROPL(GF_PROP_PID_CHANNEL_LAYOUT, ds->ch_layout, GF_EOS) + + switch (ds->codec_id) { + case GF_CODECID_USAC: + case GF_CODECID_MHAS: + case GF_CODECID_MPHA: + break; + default: + ds->skip_sap = GF_TRUE; + } } old_period_switch = period_switch; @@ -1369,7 +1474,6 @@ CHECK_PROP_PROP(GF_PROP_PID_REP_DESC, ds->p_rep_desc, GF_EOS) CHECK_PROP_PROP(GF_PROP_PID_BASE_URL, ds->p_base_url, GF_EOS) CHECK_PROP_PROP(GF_PROP_PID_ROLE, ds->p_role, GF_EOS) - CHECK_PROP_STR(GF_PROP_PID_HLS_PLAYLIST, ds->hls_vp_name, GF_EOS) CHECK_PROP_BOOL(GF_PROP_PID_SINGLE_SCALE, ds->sscale, GF_EOS) //if manifest generation mode with template and no template at PID or filter level, switch to main profile @@ -1431,6 +1535,9 @@ if (p && p->value.lfrac.den) ds->clamped_dur = p->value.lfrac; //HLS variant playlist is allowed to use templates, resolve + char *old_hls_vp_name = ds->hls_vp_name; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HLS_PLAYLIST); + ds->hls_vp_name = (p && p->value.string) ? gf_strdup(p->value.string) : NULL; if (ds->hls_vp_name) { char szTempNameGF_MAX_PATH, szFinalNameGF_MAX_PATH; e = gf_filter_pid_resolve_file_template(ds->ipid, ds->hls_vp_name, szTempName, 0, NULL); @@ -1443,53 +1550,68 @@ gf_free(ds->hls_vp_name); ds->hls_vp_name = gf_strdup(szFinalName); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher Failed to solve HLS variant playlist template %s: %s - will use default\n", ds->hls_vp_name, gf_error_to_string(e))); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Failed to solve HLS variant playlist template %s: %s - will use default\n", ds->hls_vp_name, gf_error_to_string(e))); gf_free(ds->hls_vp_name); ds->hls_vp_name = NULL; } } + //we cannot use the CHECK_PROP_STR macro since this might use template and hls_vp_name is the resolved version + if (old_hls_vp_name) { + if (!ds->hls_vp_name || strcmp(ds->hls_vp_name, old_hls_vp_name)) + period_switch = GF_TRUE; + gf_free(old_hls_vp_name); + } else if (ds->hls_vp_name) { + period_switch = GF_TRUE; + } + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_PRIMARIES); + if(p){ + ds->color_primaries = p->value.uint; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_TRANSFER); + if(p){ + ds->color_transfer_characteristics = p->value.uint; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_TRANSFER_ALT); + if(p){ + ds->color_transfer_characteristics_alt = p->value.uint; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_MX); + if(p){ + ds->color_matrix = p->value.uint; + } + - //HDR #if !defined(GPAC_DISABLE_AV_PARSERS) if (dsi) { if (ds->codec_id == GF_CODECID_LHVC || ds->codec_id == GF_CODECID_HEVC_TILES || ds->codec_id == GF_CODECID_HEVC) { GF_HEVCConfig* hevccfg = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); if (hevccfg) { - Bool is_interlaced; - HEVCState hevc; - HEVC_SPS* sps; - memset(&hevc, 0, sizeof(HEVCState)); - gf_hevc_parse_ps(hevccfg, &hevc, GF_HEVC_NALU_VID_PARAM); - gf_hevc_parse_ps(hevccfg, &hevc, GF_HEVC_NALU_SEQ_PARAM); - sps = &hevc.spshevc.sps_active_idx; - if (sps && sps->colour_description_present_flag) { - DasherHDRType old_hdr_type = ds->hdr_type; - if (sps->colour_primaries == 9 && sps->matrix_coeffs == 9) { - if (sps->transfer_characteristic == 14) ds->hdr_type = DASHER_HDR_HLG; //TODO: parse alternative_transfer_characteristics SEI - if (sps->transfer_characteristic == 16) ds->hdr_type = DASHER_HDR_PQ10; - } - if (old_hdr_type != ds->hdr_type) period_switch = GF_TRUE; + Bool is_interlaced = hevccfg->interlaced_source_flag ? GF_TRUE : GF_FALSE; + if (ds->interlaced != is_interlaced){ + ds->interlaced = is_interlaced; + period_switch = GF_TRUE; } - is_interlaced = hevccfg->interlaced_source_flag ? GF_TRUE : GF_FALSE; - if (ds->interlaced != is_interlaced) period_switch = GF_TRUE; - ds->interlaced = is_interlaced; - gf_odf_hevc_cfg_del(hevccfg); } } else if (ds->codec_id == GF_CODECID_AVC || ds->codec_id == GF_CODECID_SVC || ds->codec_id == GF_CODECID_MVC) { - AVCState avc; GF_AVCConfig* avccfg = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); if (avccfg) { GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(avccfg->sequenceParameterSets, 0); if (sl) { s32 idx; - memset(&avc, 0, sizeof(AVCState)); - idx = gf_avc_read_sps(sl->data, sl->size, &avc, 0, NULL); - if (idx>=0) { - Bool is_interlaced = avc.spsidx.frame_mbs_only_flag ? GF_FALSE : GF_TRUE; - if (ds->interlaced != is_interlaced) period_switch = GF_TRUE; - ds->interlaced = is_interlaced; + AVCState *avc_state; + GF_SAFEALLOC(avc_state, AVCState); + if (avc_state) { + idx = gf_avc_read_sps(sl->data, sl->size, avc_state, 0, NULL); + if (idx>=0) { + Bool is_interlaced = avc_state->spsidx.frame_mbs_only_flag ? GF_FALSE : GF_TRUE; + if (ds->interlaced != is_interlaced) period_switch = GF_TRUE; + ds->interlaced = is_interlaced; + } + gf_free(avc_state); } } gf_odf_avc_cfg_del(avccfg); @@ -1500,8 +1622,8 @@ if (ds->stream_type==GF_STREAM_AUDIO) { u32 _sr=0, _nb_ch=0; -#ifndef GPAC_DISABLE_AV_PARSERS switch (ds->codec_id) { +#ifndef GPAC_DISABLE_AV_PARSERS case GF_CODECID_AAC_MPEG4: case GF_CODECID_AAC_MPEG2_MP: case GF_CODECID_AAC_MPEG2_LCP: @@ -1524,8 +1646,8 @@ case GF_CODECID_AC3: case GF_CODECID_EAC3: if (dsi) { - GF_AC3Config ac3; - gf_odf_ac3_config_parse(dsi->value.data.ptr, dsi->value.data.size, (ds->codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3); + GF_AC3Config ac3 = {0}; + gf_odf_ac3_cfg_parse(dsi->value.data.ptr, dsi->value.data.size, (ds->codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3); ds->nb_lfe = ac3.streams0.lfon ? 1 : 0; ds->nb_surround = gf_ac3_get_surround_channels(ac3.streams0.acmod); @@ -1534,13 +1656,61 @@ if (ac3.streams0.nb_dep_sub) { _nb_ch += gf_eac3_get_chan_loc_count(ac3.streams0.chan_loc); } - if (ds->nb_lfe) _nb_ch++; + if (ds->nb_lfe) _nb_ch++; + ds->ch_layout = gf_ac3_get_channel_layout(&ac3); + } + break; + case GF_CODECID_AC4: + if (dsi) { + GF_AC4Config ac4 = {0}; + gf_odf_ac4_cfg_parse(dsi->value.data.ptr, dsi->value.data.size, &ac4); + GF_AC4PresentationV1* p = (GF_AC4PresentationV1*)gf_list_get(ac4.stream.presentations, 0); + if (p) { + ds->ch_mask = p->presentation_channel_mask_v1; + _nb_ch = gf_ac4_dolby_channel_count_from_channel_mask_v1(ds->ch_mask); + // Dolby AC-4 in MPEG-DASH for Online Delivery Specification 2.5.1 presentation_version of immersive stereo content is 2 + if (p->presentation_version == 2) { + ds->ac4_content_type = AC4_IMMERSIVE_STEREO; + if (p->dolby_atmos_indicator) { + ds->ac4_content_type = AC4_IMMERSIVE_STEREO_ATMOS; + } + } else if (p->presentation_version == 1) { + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.10.10 b_presentation_channel_coded indicates if the presentation is channel-based (1) or object-based (0) + if (p->b_presentation_channel_coded == 1) { + ds->ac4_content_type = AC4_CHANNEL_BASED_CONTENT; + if (p->dsi_presentation_ch_mode == 15 || + (p->dsi_presentation_ch_mode >= 11 && p->dsi_presentation_ch_mode <= 14 && p->pres_top_channel_pairs > 0)) { + ds->ac4_content_type = AC4_CHANNEL_BASED_IMMERSIVE_CONTENT; + } + } else { + ds->ac4_content_type = AC4_OBJECT_BASED_CONTENT; + GF_AC4SubStreamGroupV1 *sg = gf_list_get(p->substream_groups, 0); + if (sg && sg->substreams) { + GF_AC4SubStream *ss = gf_list_get(sg->substreams, 0); + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.8 b_ajoc indicates that the substream is coded using the A-JOC coding tool + if (ss && ss->b_ajoc == 1) { + ds->ac4_content_type = AC4_OBJECT_BASED_AJOC_CONTENT; + _nb_ch = ss->n_umx_objects_minus1 + 2; + } + } + } + } + } + gf_odf_ac4_cfg_clean_list(&ac4); } break; - } #endif + case GF_CODECID_MHAS: + case GF_CODECID_MPHA: + if (!ds->ch_layout && dsi && (dsi->value.data.size>3)) { + u8 ref_layout = dsi->value.data.ptr2; + ds->ch_layout = gf_audio_fmt_get_layout_from_cicp(ref_layout); + } + break; + } if (_sr > ds->sr) ds->sr = _sr; if (_nb_ch > ds->nb_ch) ds->nb_ch = _nb_ch; + if ((ds->codec_id == GF_CODECID_EAC3 || ds->codec_id == GF_CODECID_AC4) && _nb_ch != 0) ds->nb_ch = _nb_ch; } @@ -1645,6 +1815,10 @@ if (!ds->rep && (gf_list_find(ctx->current_period->streams, ds)>=0)) period_switch = GF_FALSE; + //some demux update properties before sending the first packet, don't switch if we didn't send any packet + if (is_reconfigure && !ds->nb_pck) { + period_switch = GF_FALSE; + } old_period_switch = period_switch; period_switch = GF_FALSE; CHECK_PROP_STR(GF_PROP_PID_PERIOD_ID, ds->period_id, GF_EOS) @@ -1762,24 +1936,9 @@ if (!period_switch) { if (ds->opid) { - gf_filter_pid_copy_properties(ds->opid, pid); - //for route out - if (ctx->do_m3u8) - gf_filter_pid_set_property(ds->opid, GF_PROP_PCK_HLS_REF, &PROP_LONGUINT( ds->hls_ref_id ) ); - if (ctx->llhls) - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_LLHLS, &PROP_UINT(ctx->llhls) ); - if (ds->rep && ds->rep->segment_template) - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_TEMPLATE, &PROP_STRING(ds->rep->segment_template->media)); - else if (ds->set && ds->set->segment_template) - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_TEMPLATE, &PROP_STRING(ds->set->segment_template->media)); - if (ctx->do_m3u8) - gf_filter_pid_set_property(ds->opid, GF_PROP_PCK_HLS_REF, &PROP_LONGUINT( ds->hls_ref_id ) ); - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_REP_ID, &PROP_STRING( ds->rep_id ) ); - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_DUR, &PROP_FRAC( ds->dash_dur ) ); - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_PREMUX_STREAM_TYPE, &PROP_UINT(ds->stream_type) ); - //end route - if (ctx->gencues) - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_CUE, &PROP_STRING("inband") ); + //merge props, filtering out props setup by dasher + //do NOT copy properties as this will reset properties setup at PID creation + gf_filter_pid_merge_properties(ds->opid, pid, dasher_merge_prop, ctx); } if (ds->rep) dasher_update_rep(ctx, ds); @@ -2060,7 +2219,7 @@ { char sres4; u32 i; - /* Output canonical UIID form */ + /* Output canonical UUID form */ strcpy(res, ""); for (i=0; i<4; i++) { sprintf(sres, "%02x", URNi); strcat(res, sres); } strcat(res, "-"); @@ -2362,7 +2521,6 @@ ds->rep->width = ds->width; ds->rep->height = ds->height; - if (!ds->rep->sar) { GF_SAFEALLOC(ds->rep->sar, GF_MPD_Fractional); } @@ -2384,6 +2542,7 @@ else if (ds->stream_type==GF_STREAM_AUDIO) { Bool use_cicp = GF_FALSE; Bool use_dolbyx = GF_FALSE; + Bool use_ac4 = GF_FALSE; Bool use_dtshd = GF_FALSE; Bool use_dtsx = GF_FALSE; GF_MPD_Descriptor *desc; @@ -2405,16 +2564,32 @@ if (ctx->profile > GF_DASH_PROFILE_FULL) { use_dolbyx = GF_TRUE; } + //ETSI TS 102 366 section I.1.2.1: AC3 and EAC3 should use CICP + use_cicp = GF_TRUE; + } + if (ds->codec_id==GF_CODECID_AC4) { + use_ac4 = GF_TRUE; } if (use_dolbyx) { - u32 cicp_layout = 0; - if (ds->ch_layout) - cicp_layout = gf_audio_fmt_get_cicp_from_layout(ds->ch_layout); - if (!cicp_layout) - cicp_layout = gf_audio_fmt_get_cicp_layout(ds->nb_ch, ds->nb_surround, ds->nb_lfe); - - sprintf(value, "%X", gf_audio_fmt_get_dolby_chanmap(cicp_layout) ); + u32 chanmap=0; + if (ds->ch_layout) { + chanmap = gf_audio_fmt_get_dolby_chanmap_from_layout(ds->ch_layout); + } else { + u32 cicp_layout = gf_audio_fmt_get_cicp_layout(ds->nb_ch, ds->nb_surround, ds->nb_lfe); + chanmap = gf_audio_fmt_get_dolby_chanmap(cicp_layout); + } + sprintf(value, "%X", chanmap); desc = gf_mpd_descriptor_new(NULL, "tag:dolby.com,2014:dash:audio_channel_configuration:2011", value); + } else if (use_ac4) { + // ETSI TS 103 190-2 V1.3.1 (2025-07) G.3.3 + if (ds->ch_mask == 0 || ds->ch_mask == 0x800000) { + sprintf(value, "%06X", 0x800000); + desc = gf_mpd_descriptor_new(NULL, "tag:dolby.com,2015:dash:audio_channel_configuration:2015", value); + } + else { + sprintf(value, "%06X", ds->ch_mask); + desc = gf_mpd_descriptor_new(NULL, "tag:dolby.com,2015:dash:audio_channel_configuration:2015", value); + } } else if (use_dtshd) { sprintf(value, "%d", ds->nb_ch); desc = gf_mpd_descriptor_new(NULL, "tag:dts.com,2014:dash:audio_channel_configuration:2012", value); @@ -2425,7 +2600,14 @@ sprintf(value, "%d", ds->nb_ch); desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", value); } else { - sprintf(value, "%d", gf_audio_fmt_get_cicp_layout(ds->nb_ch, ds->nb_surround, ds->nb_lfe)); + u32 val1 = ds->ch_layout ? gf_audio_fmt_get_cicp_from_layout(ds->ch_layout) : 255; + u32 val2 = gf_audio_fmt_get_cicp_layout(ds->nb_ch, ds->nb_surround, ds->nb_lfe); + if (val1==255) val1 = val2; + + if (val2 && (val1!=val2)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Mismatch between channel layout and channels %d/%d.%d, using layout\n", ds->nb_ch, ds->nb_surround, ds->nb_lfe)); + } + sprintf(value, "%u", val1); desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:ChannelConfiguration", value); } @@ -2477,8 +2659,26 @@ ds->rep->playback.udta = ds; if (ds->tci) ds->rep->crypto_type = 1; - else - ds->rep->crypto_type = ds->is_encrypted ? 2 : 0; + else { + if (!ds->is_encrypted) ds->rep->crypto_type = 0; + else { + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_PROTECTION_SCHEME_TYPE); + ds->rep->crypto_type = 2; + if (p && (p->value.uint == GF_4CC('c','e','n','c'))) { + if ((ctx->muxtype != DASHER_MUX_ISOM) && (ctx->muxtype != DASHER_MUX_AUTO)) { + ctx->in_error = GF_TRUE; + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher CENC protection cannot be used with non-ISOBMF mux format\n")); + } else { + ds->rep->crypto_type = 3; + } + } else if (p && (p->value.uint == GF_4CC('s','a','e','s'))) { + if (ctx->muxtype != DASHER_MUX_TS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher SAES protection cannot be used with non-MPEG2TS mux format\n")); + ctx->in_error = GF_TRUE; + } + } + } + } dasher_update_rep(ctx, ds); ds->rep->streamtype = ds->stream_type; @@ -2546,6 +2746,7 @@ p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ID); ds->rep->trackID = p ? p->value.uint : 0; + // GHIX (=XML GHI) if (ctx->do_index==2) { if (!ds->rep->x_children) ds->rep->x_children = gf_list_new(); u32 idx=0; @@ -2574,7 +2775,12 @@ case GF_PROP_PID_CHAP_TIMES: case GF_PROP_PID_CHAP_NAMES: case GF_PROP_PID_ISOM_UDTA: + case GF_PROP_PID_SEI_LOADED: continue; + case GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT: + //for text streams, only used for SDP config by tx3g and we don't need it + if (ds->stream_type==GF_STREAM_TEXT) + continue; default: break; } @@ -2676,7 +2882,7 @@ const char *lang1, *lang2; const GF_PropertyValue *p1, *p2; - //in all forward mode we don't rewrite the manifest, make each source file a single as + //in all forward mode we don't rewrite the manifest, make each source file a single adaptation set if (ctx->forward_mode==DASHER_FWD_ALL) return GF_FALSE; @@ -2830,6 +3036,26 @@ } } +static void dasher_add_inband_event(GF_DashStream *ds) +{ + GF_MPD_Inband_Event *nielsen_event; + GF_MPD_Inband_Event *custom_event; + if(ds->stream_type == GF_STREAM_AUDIO) { + GF_SAFEALLOC(custom_event, GF_MPD_Inband_Event); + custom_event->scheme_id_uri = gf_strdup("https://aomedia.org/emsg/ID3"); + custom_event->value = gf_strdup("https://aomedia.org/emsg/ID3"); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("Dasher inserting inband event with scheme: %s and value: %s\n", custom_event->scheme_id_uri, custom_event->value)) + + GF_SAFEALLOC(nielsen_event, GF_MPD_Inband_Event); + nielsen_event->scheme_id_uri = gf_strdup("https://aomedia.org/emsg/ID3"); + nielsen_event->value = gf_strdup("www.nielsen.com:id3:v1"); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("Dasher inserting inband event with scheme: %s and value: %s\n", nielsen_event->scheme_id_uri, nielsen_event->value)) + + gf_list_add(ds->set->inband_event, nielsen_event); + gf_list_add(ds->set->inband_event, custom_event); + } +} + static void dasher_setup_set_defaults(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) { u32 i, count; @@ -2871,8 +3097,8 @@ char *uri=NULL; //all roles defined by dash 5th edition if (!strcmp(role, "caption") || !strcmp(role, "subtitle") || !strcmp(role, "main") - || !strcmp(role, "alternate") || !strcmp(role, "supplementary") || !strcmp(role, "commentary") - || !strcmp(role, "dub") || !strcmp(role, "description") || !strcmp(role, "sign") + || !strcmp(role, "alternate") || !strcmp(role, "supplementary") || !strcmp(role, "commentary") + || !strcmp(role, "dub") || !strcmp(role, "description") || !strcmp(role, "sign") || !strcmp(role, "metadata") || !strcmp(role, "enhanced-audio-inteligibility") || !strcmp(role, "emergency") || !strcmp(role, "forced-subtitle") || !strcmp(role, "easyreader") || !strcmp(role, "karaoke") @@ -2927,31 +3153,36 @@ gf_list_add(set->supplemental_properties, desc); } } - //set HDR - if (ds->hdr_type > DASHER_HDR_NONE) { - char value256; - GF_MPD_Descriptor* desc; - sprintf(value, "9"); + + // ETSI TS 103 285 v1.4.1 - 5.2.5 Colour format and transfer characteristics signalling + // TODO: add "urn:dvb:dash:profile:dvb-dash:2017" to profiles_string + char value256; + GF_MPD_Descriptor* desc; + if (ds->color_primaries > GF_COLOR_PRIM_UNSPECIFIED){ + sprintf(value, "%d", ds->color_primaries); desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:ColourPrimaries", value); gf_list_add(set->essential_properties, desc); - sprintf(value, "9"); - desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:MatrixCoefficients", value); - gf_list_add(set->essential_properties, desc); - - if (ds->hdr_type==DASHER_HDR_PQ10) { - sprintf(value, "16"); + } + if (ds->color_transfer_characteristics > GF_COLOR_TRC_UNSPECIFIED){ + sprintf(value, "%d", ds->color_transfer_characteristics); desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:TransferCharacteristics", value); gf_list_add(set->essential_properties, desc); - } + } + if (ds->color_matrix > GF_COLOR_MX_UNSPECIFIED){ + sprintf(value, "%d", ds->color_matrix); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:MatrixCoefficients", value); + gf_list_add(set->essential_properties, desc); - if (ds->hdr_type == DASHER_HDR_HLG) { - sprintf(value, "14"); - desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:TransferCharacteristics", value); - gf_list_add(set->essential_properties, desc); - sprintf(value, "18"); + } + if (ds->color_transfer_characteristics_alt > GF_COLOR_TRC_UNSPECIFIED){ + sprintf(value, "%d", ds->color_transfer_characteristics_alt); desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:TransferCharacteristics", value); gf_list_add(set->supplemental_properties, desc); - } + } + + //add custom inband event in manifest + if (ctx->inband_event) { + dasher_add_inband_event(ds); } } if (ctx->check_main_role && !main_role_set) { @@ -2976,7 +3207,7 @@ } } -static void dasher_check_bitstream_swicthing(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) +static void dasher_check_bitstream_switching(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) { u32 i, j, count; Bool use_inband = ((ctx->bs_switch==DASHER_BS_SWITCH_INBAND) || (ctx->bs_switch==DASHER_BS_SWITCH_INBAND_PPS) || (ctx->bs_switch==DASHER_BS_SWITCH_BOTH)) ? GF_TRUE : GF_FALSE; @@ -3166,6 +3397,13 @@ sep0 = 0; trailer_args = strstr(dst_args, szKey); } + //remove trailing argument separator + u32 dlen = (u32) strlen(szDST); + while (dlen && (szDSTdlen-1 == sep_args)) { + szDSTdlen-1 = 0; + dlen--; + } + //look for frag arg sprintf(szKey, "%cfrag", sep_args); if (strstr(dst_args, szKey)) has_frag = GF_TRUE; @@ -3382,6 +3620,16 @@ } } +static void dasher_inject_scte35_processor(GF_Filter *filter, GF_DashStream *ds, char *szSRC) { + GF_Err e; + GF_Filter *scte35dec = gf_filter_load_filter(filter, "scte35dec", &e); + gf_filter_set_source(scte35dec, filter, NULL); + + sprintf(szSRC, "MuxSrc%cdasher_%p", gf_filter_get_sep(filter, GF_FS_SEP_NAME), ds->dst_filter); + gf_filter_reset_source(ds->dst_filter); + gf_filter_set_source(ds->dst_filter, scte35dec, szSRC); +} + static void dasher_open_pid(GF_Filter *filter, GF_DasherCtx *ctx, GF_DashStream *ds, GF_List *multi_pids, Bool init_trashed) { GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; @@ -3458,9 +3706,19 @@ } //for route out if (ctx->do_m3u8) - gf_filter_pid_set_property(ds->opid, GF_PROP_PCK_HLS_REF, &PROP_LONGUINT( ds->hls_ref_id ) ); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_HLS_REF, &PROP_LONGUINT( ds->hls_ref_id ) ); gf_filter_pid_set_property(ds->opid, GF_PROP_PID_REP_ID, &PROP_STRING( ds->rep_id ) ); gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_DUR, &PROP_FRAC( ds->dash_dur ) ); + + if (ctx->current_period->period->ID) + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_PERIOD_ID, &PROP_STRING( ctx->current_period->period->ID ) ); + if (ds->owns_set && ctx->use_mabr) { + if (!ds->as_id) { + ds->as_id = gf_list_find(ctx->current_period->streams, ds)+1; + ds->set->id = ds->as_id; + } + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_AS_ID, &PROP_UINT( ds->as_id ) ); + } //end route_out gf_filter_pid_require_source_id(ds->opid); @@ -3495,6 +3753,8 @@ case DASHER_SEGSYNC_NO: break; } + if (ctx->base64) + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_INIT_BASE64, &PROP_BOOL(GF_TRUE) ); if (init_trashed) gf_filter_pid_set_property(ds->opid, GF_PROP_PID_NO_INIT, &PROP_BOOL(GF_TRUE)); @@ -3516,7 +3776,7 @@ gf_filter_pid_set_property(ds->opid, GF_PROP_PID_TEMPLATE, &PROP_STRING(ds->set->segment_template->media)); gf_filter_pid_set_property(ds->opid, GF_PROP_PID_BITRATE, &PROP_UINT(ds->bitrate)); - gf_filter_pid_set_property(ds->opid, GF_PROP_PCK_FILENAME, &PROP_STRING(ds->init_seg)); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_INIT_NAME, &PROP_STRING(ds->init_seg)); if (ds->rep && ds->rep->codecs) gf_filter_pid_set_property(ds->opid, GF_PROP_PID_CODEC, &PROP_STRING(ds->rep->codecs)); @@ -3555,8 +3815,14 @@ } } + if (ds->set->ssr_mode) { // SSR=sub-segment representation + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_LLHAS_MODE, &PROP_UINT((ds->set->ssr_mode==1) ? GF_LLHAS_PARTS : GF_LLHAS_SUBSEG) ); + } else if (!ctx->llhls) { + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_LLHAS_MODE, NULL); + } + if (ctx->llhls) { - gf_filter_pid_set_property(ds->opid, GF_PROP_PID_LLHLS, &PROP_UINT(ctx->llhls) ); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_LLHAS_MODE, &PROP_UINT(ctx->llhls) ); } if ((ctx->dmode > GF_DASH_STATIC) && (ctx->tsb>=0) && !ctx->keep_segs) { @@ -3566,6 +3832,21 @@ } else { gf_filter_pid_set_property(ds->opid, GF_PROP_PID_TIMESHIFT_SEGS, NULL); } + + //inject ttml agg filter + if (ctx->ttml_agg && !ctx->rawsub && (ds->codec_id==GF_CODECID_SUBS_XML)) { + GF_Err e; + GF_Filter *ttml_agg = gf_filter_load_filter(filter, "ttmlmerge", &e); + gf_filter_set_source(ttml_agg, filter, NULL); + + sprintf(szSRC, "MuxSrc%cdasher_%p", gf_filter_get_sep(filter, GF_FS_SEP_NAME), ds->dst_filter); + gf_filter_reset_source(ds->dst_filter); + gf_filter_set_source(ds->dst_filter, ttml_agg, szSRC); + } + + //inject scte35dec filter + if (ctx->evte_agg && (ds->codec_id==GF_CODECID_SCTE35 || ds->codec_id==GF_CODECID_EVTE)) + dasher_inject_scte35_processor(filter, ds, szSRC); } static void dasher_set_content_components(GF_DashStream *ds) @@ -3864,7 +4145,7 @@ if (is_bs_switching) { ctx->next_pid_id_in_period++; //except for base tile track where we force using input PID ID - //to avoid messing up sabt/tbas references + //to avoid messing up sabt/tbas references (SHVC tile dependent tracks) if (ds->tile_base) { ds->pid_id = ds->id; if (ctx->next_pid_id_in_period <= ds->pid_id) @@ -3987,9 +4268,6 @@ ds->inband_params = 2; } - //if bitstream switching and templating, only set for the first one - if (i && set->bitstream_switching && ds->stl && single_template) continue; - if (!set_timescale) set_timescale = ds->timescale; if (ctx->timescale<0) ds->mpd_timescale = ds->timescale; @@ -4002,6 +4280,40 @@ } } + //update startNumber based on first cts + if (ctx->segcts) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(ds->ipid); + if (pck) { + u64 seg_dur = gf_timestamp_rescale(ds->dash_dur.num, ds->dash_dur.den, ds->timescale); + u64 cdur = gf_timestamp_rescale(ctx->cdur.num, ctx->cdur.den, ds->timescale); + u64 cts = gf_filter_pck_get_cts(pck); + cts += ds->presentation_time_offset; + if (ds->timescale != ds->mpd_timescale) { + seg_dur = gf_timestamp_rescale(seg_dur, ds->timescale, ds->mpd_timescale); + cdur = gf_timestamp_rescale(cdur, ds->timescale, ds->mpd_timescale); + cts = gf_timestamp_rescale(cts, ds->timescale, ds->mpd_timescale); + } + + //decide on start number + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_START_NUMBER); + ds->startNumber = p ? p->value.uint : 0; + ds->startNumber += (u32) gf_floor((Double)cts / seg_dur) + 1; + + //set the fragment sequence number + ds->moof_sn = ds->startNumber; + if (ctx->cdur.num>0 && ctx->cdur.den>0) { + u32 chunk_per_segment = (u32) gf_floor((Double)seg_dur / cdur); + ds->moof_sn = (ds->startNumber - 1) * chunk_per_segment + 1; + + //add the sn offset within the segment + Float progress_in_seg = (Float) (cts % seg_dur) / (Float) seg_dur; + ds->moof_sn += (u32) gf_floor(progress_in_seg * chunk_per_segment); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher failed to get first cts for PID %s\n", gf_filter_pid_get_name(ds->ipid))); + } + } + if (ds->nb_repeat && !ctx->loop) { if (split_set_names) { sprintf(szDASHSuffix, "%sp%d_", szSetFileSuffix, ds->nb_repeat+1); @@ -4014,7 +4326,7 @@ use_dash_suffix = GF_TRUE; } //we need dash suffix in template, but the template may be user-provided without dash suffix. If so add it - //we don't add suffic if we have $RepresentationID or $Path set, we assume the user knows what he's doing + //we don't add suffix if we have $RepresentationID or $Path set, we assume the user knows what (s)he's doing if (!ctx->tpl_force && use_dash_suffix && !strstr(szTemplate, "$FS$") && !strstr(szTemplate, "$RepresentationID$") && !strstr(szTemplate, "$Path=")) { strcat(szTemplate, "$FS$"); } @@ -4157,6 +4469,7 @@ //get final segment template with path resolution - output file name is NULL, we already have solved this gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_TEMPLATE_WITH_PATH, is_bs_switch, szSegmentName, ds->rep_id, NULL, szDASHTemplate, seg_ext, 0, 0, 0, ds->stl, ctx->tpl_force); + if (ds->seg_template) gf_free(ds->seg_template); ds->seg_template = gf_strdup(szSegmentName); //get final segment template - output file name is NULL, we already have solved this @@ -4297,7 +4610,7 @@ if (ctx->forward_mode) { u32 k, nb_pids = gf_list_count(ctx->pids); char *src = NULL; - const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PCK_FILENAME); + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_INIT_NAME); if (!p || !p->value.string) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Couldn't fetch source URL in forward mode, cannot forward\n")); @@ -4311,7 +4624,7 @@ if (ds == a_ds) continue; if (!a_ds->dst_filter) continue; - p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PCK_FILENAME); + p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_INIT_NAME); if (!p || !p->value.string) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Couldn't fetch source URL in forward mode, cannot forward\n")); ctx->in_error = GF_TRUE; @@ -4335,6 +4648,7 @@ } } + if (ds->init_seg) gf_free(ds->init_seg); ds->init_seg = gf_strdup(szInitSegmentFilename); //we use segment template @@ -4383,6 +4697,8 @@ seg_template->media = dasher_cat_mpd_url(ctx, ds, szSegmentName); if (ds->idx_template) seg_template->index = dasher_cat_mpd_url(ctx, ds, szIndexSegmentName); + if (set->ssr_mode && !strstr(seg_template->media, "$SubNumber")) + gf_dynstrcat(&seg_template->media, "$SubNumber$", "."); seg_template->timescale = ds->mpd_timescale; seg_template->start_number = start_number; @@ -4400,7 +4716,7 @@ dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, (skip_init_type==DASH_INITSEG_NONE) ? DASH_INITSEG_NONE : (set->bitstream_switching ? DASH_INITSEG_SKIP : DASH_INITSEG_PRESENT)); } - //first rep in set and no bs switching or mutliple templates, create segment template at rep level + //first rep in set and no bs switching or multiple templates, create segment template at rep level else if (i || !single_template) { GF_SAFEALLOC(seg_template, GF_MPD_SegmentTemplate); if (seg_template) { @@ -4421,6 +4737,8 @@ seg_template->media = dasher_cat_mpd_url(ctx, ds, szSegmentName); if (ds->idx_template) seg_template->index = dasher_cat_mpd_url(ctx, ds, szIndexSegmentName); + if (set->ssr_mode && !strstr(seg_template->media, "$SubNumber")) + gf_dynstrcat(&seg_template->media, "$SubNumber$", "."); seg_template->duration = seg_duration; seg_template->timescale = ds->mpd_timescale; seg_template->start_number = start_number; @@ -4510,6 +4828,7 @@ u64 start_time = stl_e->start_time + stl_e->duration; gf_list_rem(stl->entries, 0); gf_free(stl_e); + stl_e = gf_list_get(stl->entries, 0); if (!stl_e) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher No timeline entry after currently removed segment, cannot update start time\n" )); @@ -4523,6 +4842,43 @@ } } +static void send_file_delete(GF_DasherCtx *ctx, GF_DashStream *ds, const char *filename, const char *filepath, s32 part_idx) +{ + GF_FilterEvent anevt; + char szPathGF_MAX_PATH; + GF_FEVT_INIT(anevt, GF_FEVT_FILE_DELETE, ds->opid); + + //for gfio we signal the filename relative to the parent gfio manifest + //we could build a gfio for the segment or part but that would require user apps to track too many gfio files + //which we want to avoid + if (!strncmp(filepath, "gfio://", 7)) { + char *hls_sep = (ctx->do_m3u8 && ds->hls_vp_name) ? strrchr(ds->hls_vp_name, '/') : NULL; + //special case for HLS variant in dedicated folder, the ctx filename is relative to the variant + //but we need relative to the manifest + if (hls_sep) { + char *path = gf_url_concatenate(ds->hls_vp_name, filename); + if (part_idx>=0) + sprintf(szPath, "%s@%s.%u", ctx->out_path, path ? path : filename, part_idx); + else + sprintf(szPath, "%s@%s", ctx->out_path, path ? path : filename); + if (path) gf_free(path); + } else { + if (part_idx>=0) + sprintf(szPath, "%s@%s.%u", ctx->out_path, filename, part_idx); + else + sprintf(szPath, "%s@%s", ctx->out_path, filename); + } + anevt.file_del.url = szPath; + } else { + if (part_idx>=0) + sprintf(szPath, "%s.%u", filepath, part_idx); + else + sprintf(szPath, "%s", filepath); + anevt.file_del.url = szPath; + } + gf_filter_pid_send_event(ds->opid, &anevt); +} + static void dasher_purge_segments(GF_DasherCtx *ctx, u64 *period_dur) { Double min_valid_mpd_time; @@ -4550,6 +4906,12 @@ if (!ds->rep) continue; if (!ds->rep->state_seg_list) continue; + Double max_ptime = (Double) ds->max_period_dur; + max_ptime /= 1000; + //don't try to suppress last segment + if (min_valid_mpd_time < max_ptime) + max_ptime = min_valid_mpd_time; + ds->rep->tsb_first_entry = 0; u32 state_idx=0; while (1) { @@ -4563,8 +4925,8 @@ time = (Double) sctx->time; time /= ds->mpd_timescale; dur = (Double) sctx->dur; - dur/= ds->timescale; - if (time + dur >= min_valid_mpd_time) { + dur /= ds->timescale; + if (time + dur >= max_ptime) { if (ctx->keep_segs) { ds->rep->tsb_first_entry = state_idx; @@ -4585,12 +4947,18 @@ continue; } if (sctx->filepath) { - GF_FilterEvent evt; GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher removing segment %s\n", sctx->filename ? sctx->filename : sctx->filepath)); - GF_FEVT_INIT(evt, GF_FEVT_FILE_DELETE, ds->opid); - evt.file_del.url = sctx->filepath; - gf_filter_pid_send_event(ds->opid, &evt); + send_file_delete(ctx, ds, sctx->filename, sctx->filepath, -1); + + //purge LLHLS frags + if (sctx->frags && (sctx->llhls_mode==GF_DASH_LL_HLS_SF || ds->set->ssr_mode)) { + u32 k; + for (k=0; k<sctx->nb_frags; k++) { + s32 part_idx = k + ((ds->set->ssr_mode || !gf_sys_is_test_mode()) ? 0 : 1); + send_file_delete(ctx, ds, sctx->filename, sctx->filepath, part_idx); + } + } gf_free(sctx->filepath); } @@ -4636,6 +5004,8 @@ gf_fatal_assert(gf_list_find(ds->pending_segment_states, sctx)<0); if (sctx->filename) gf_free(sctx->filename); if (sctx->hls_key_uri) gf_free(sctx->hls_key_uri); + if (sctx->frags) gf_free(sctx->frags); + if (sctx->llhas_template) gf_free(sctx->llhas_template); gf_free(sctx); gf_list_rem(ds->rep->state_seg_list, 0); } @@ -4663,11 +5033,14 @@ u64 pdur = 0; u64 min_dur = 0; u64 p_start=0; + Bool all_done=GF_TRUE; GF_MPD_Period *prev_p = NULL; count = gf_list_count(ctx->current_period->streams); for (i=0; i<count; i++) { GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); if (ds->muxed_base) continue; + if (!ds->done) + all_done = GF_FALSE; if (ds->xlink && (ds->stream_type==GF_STREAM_FILE) ) { pdur = (u32) (1000*(s64)ds->period_dur.num / ds->period_dur.den); @@ -4700,13 +5073,15 @@ pdur = ctx->current_period->period->duration; } - if (!ctx->check_dur) { + if (!ctx->check_dur && all_done) { s32 diff = (s32) ((s64) pdur - (s64) min_dur); if (ABS(diff)>2000) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Adaptation sets in period are of unequal duration min %g max %g seconds\n", ((Double)min_dur)/1000, ((Double)pdur)/1000)); } } - + //if multiple reps and not done yet, use minimum duration as some reps could be ahead of others by one segment + if (!all_done && (count>1)) + pdur = min_dur; dasher_purge_segments(ctx, &pdur); if (ctx->current_period->period && !ctx->index_media_duration) { @@ -4803,7 +5178,7 @@ gf_filter_pck_set_property(pck, GF_PROP_PCK_HLS_REF, &PROP_LONGUINT( ds->hls_ref_id ) ); } if (is_rel_url) - gf_filter_pck_set_property(pck, GF_PROP_PID_FILE_REL, &PROP_BOOL( GF_TRUE ) ); + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILE_REL, &PROP_BOOL( GF_TRUE ) ); gf_filter_pck_send(pck); } @@ -5119,6 +5494,7 @@ case GF_PROP_PID_PLAYBACK_MODE: case GF_PROP_PID_CHAP_TIMES: case GF_PROP_PID_CHAP_NAMES: + case GF_PROP_PID_SEI_LOADED: continue; default: break; @@ -5301,6 +5677,8 @@ ctx->mpd->m3u8_use_repid = GF_TRUE; tmp = gf_file_temp(NULL); + if (!tmp) return GF_IO_ERR; + if (do_m3u8) { GF_M3U8WriteMode mode = GF_M3U8_WRITE_ALL; if (ctx->from_index==IDXMODE_MANIFEST) mode = GF_M3U8_WRITE_MASTER; @@ -5315,7 +5693,7 @@ ctx->mpd->hls_abs_url = ctx->hls_absu; ctx->mpd->hls_audio_primary = ctx->hls_ap; - if (ctx->llhls==3) + if (ctx->llhls==GF_DASH_LL_HLS_BRSF) ctx->mpd->force_llhls_mode = m3u8_second_pass ? 2 : 1; else ctx->mpd->force_llhls_mode = 0; @@ -5468,7 +5846,7 @@ if (do_m3u8 && for_mpd_only) { continue; } - if ((ctx->llhls==3) && do_m3u8) + if ((ctx->llhls==GF_DASH_LL_HLS_BRSF) && do_m3u8) ctx->mpd->force_llhls_mode = 1; e = dasher_write_and_send_manifest(ctx, last_period_dur, do_m3u8, GF_FALSE, opid, NULL); if (e) return e; @@ -5512,7 +5890,7 @@ Bool do_free = GF_FALSE; if (rep->m3u8_name) { - outfile = (char *) rep->m3u8_name; + outfile = rep->m3u8_name; if (ctx->out_path && (ctx->from_index<=IDXMODE_ALL) && !ctx->explicit_mode) { outfile = gf_url_concatenate(ctx->out_path, rep->m3u8_name); do_free = GF_TRUE; @@ -5542,7 +5920,7 @@ } } - if ((ctx->llhls==3) && !m3u8_second_pass && ctx->out_path) { + if ((ctx->llhls==GF_DASH_LL_HLS_BRSF) && !m3u8_second_pass && ctx->out_path) { char *sep; char szAltNameGF_MAX_PATH; strcpy(szAltName, ctx->out_path); @@ -5587,7 +5965,6 @@ { //we do not remove the destination filter, it will be removed automatically once all remove_pids are called //removing it explicitly will discard the upper chain and any packets not yet processed - ds->dst_filter = NULL; if (ds->seg_template) gf_free(ds->seg_template); if (ds->idx_template) gf_free(ds->idx_template); @@ -5597,6 +5974,9 @@ if (ds->multi_tracks) gf_list_del(ds->multi_tracks); ds->multi_tracks = NULL; + if (ds->init_base_64) gf_free(ds->init_base_64); + ds->init_base_64 = NULL; + if (ds->pending_segment_urls) gf_list_del(ds->pending_segment_urls); ds->pending_segment_urls = NULL; if (ds->pending_segment_states) gf_list_del(ds->pending_segment_states); @@ -5630,7 +6010,6 @@ #ifndef GPAC_DISABLE_CRYPTO if (ds->cinfo) gf_crypt_info_del(ds->cinfo); #endif - return; } ds->init_seg = ds->seg_template = ds->idx_template = NULL; @@ -5675,9 +6054,10 @@ GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); if (!ds->rep) continue; if (!ds->rep->dasher_ctx) continue; - if (ds->done == 1) { + if (!ctx->sflush && (ds->done == 1)) { ds->rep->dasher_ctx->done = 1; } else { + ds->rep->dasher_ctx->done = 0; //store all dynamic parameters of the rep ds->rep->dasher_ctx->last_pck_idx = ds->nb_pck; ds->seek_to_pck = ds->nb_pck; @@ -5742,8 +6122,9 @@ ds->rep->dasher_ctx->multi_pids = ds->multi_pids ? GF_TRUE : GF_FALSE; ds->rep->dasher_ctx->dash_dur = ds->dash_dur; - if (strcmp(ds->period_id, DEFAULT_PERIOD_ID)) - ds->rep->dasher_ctx->period_id = ds->period_id; + if (strcmp(ds->period_id, DEFAULT_PERIOD_ID)) { + ds->rep->dasher_ctx->period_id = gf_strdup(ds->period_id); + } ds->rep->dasher_ctx->owns_set = (ds->set->udta == ds) ? GF_TRUE : GF_FALSE; @@ -5762,24 +6143,37 @@ gf_dynstrcat(&ds->rep->dasher_ctx->mux_pids, szMuxPID, NULL); } - } } -static GF_DashStream *dasher_get_stream(GF_DasherCtx *ctx, const char *src_url, u32 original_pid, u32 pid_id) +static GF_DashStream *dasher_get_stream(GF_DasherCtx *ctx, const char *src_url, u32 original_pid, const char *original_period_id, const char *original_rep_id) { + GF_DashStream *possible_match = NULL; u32 i, count = gf_list_count(ctx->pids); for (i=0; i<count; i++) { GF_DashStream *ds = gf_list_get(ctx->pids, i); - if (pid_id && (ds->pid_id==pid_id)) return ds; - if (src_url && ds->src_url && !strcmp(ds->src_url, src_url) && (ds->id == original_pid) ) return ds; + if (ds->id != original_pid) continue; + const char *period_id = ds->period_id; + if (ds->period_id && !strcmp(ds->period_id, DEFAULT_PERIOD_ID)) + period_id = NULL; + + if ((!period_id && !original_period_id) + || (period_id && original_period_id && !strcmp(period_id, original_period_id)) + ) { + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_REP_ID); + if (original_rep_id && p && p->value.string && !strcmp(original_rep_id, p->value.string)) { + return ds; + } + } + if (src_url && ds->src_url && !strcmp(ds->src_url, src_url) ) possible_match = ds; } - return NULL; + return possible_match; } -static GF_Err dasher_reload_muxed_comp(GF_DasherCtx *ctx, GF_DashStream *base_ds, char *mux_pids, Bool check_only) +static GF_Err dasher_reload_muxed_comp(GF_DasherCtx *ctx, GF_DashStream *base_ds, GF_MPD_Representation *base_rep_ctx, Bool check_only) { GF_Err e = GF_OK; + char *mux_pids = base_rep_ctx->dasher_ctx->mux_pids; while (mux_pids) { u32 pid_id; GF_DashStream *ds; @@ -5787,7 +6181,7 @@ if (sep) sep0 = 0; pid_id = atoi(mux_pids); - ds = dasher_get_stream(ctx, base_ds->src_url, pid_id, 0); + ds = dasher_get_stream(ctx, base_ds->src_url, pid_id, base_rep_ctx->dasher_ctx->period_id, base_rep_ctx->id); if (ds) { if (!check_only) { if (ds->rep) gf_mpd_representation_free(ds->rep); @@ -5870,10 +6264,12 @@ } //do a first pass to detect any potential changes in input config, if so consider the period over. + GF_MPD_Period *first_active_p = NULL; nb_p = gf_list_count(ctx->mpd->periods); for (i=0; i<nb_p; i++) { u32 nb_done_in_period = 0; u32 nb_remain_in_period = 0; + GF_List *restore_streams = gf_list_new(); GF_MPD_Period *p = gf_list_get(ctx->mpd->periods, i); nb_as = gf_list_count(p->adaptation_sets); for (j=0; j<nb_as; j++) { @@ -5884,16 +6280,24 @@ char *p_id; GF_MPD_Representation *rep = gf_list_get(set->representations, k); if (! rep->dasher_ctx) continue; + if (!rep->mime_type && set->mime_type) + rep->mime_type = gf_strdup(set->mime_type); //ensure we have the same settings - if not consider the dash stream has been resetup for a new period - ds = dasher_get_stream(ctx, rep->dasher_ctx->src_url, rep->dasher_ctx->source_pid, 0); + ds = dasher_get_stream(ctx, rep->dasher_ctx->src_url, rep->dasher_ctx->source_pid, rep->dasher_ctx->period_id, rep->id); + if (ds && (gf_list_find(restore_streams, ds)>=0)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Input PID pointing to multiple representations when reloading context %s, invalid state\n", ctx->state)); + ds = NULL; + ctx->in_error = GF_TRUE; + } if (!ds) { rep->dasher_ctx->done = 1; nb_done_in_period++; - if (rep->dasher_ctx->last_dyn_period_id >= ctx->last_dyn_period_id) - ctx->last_dyn_period_id = 1 + rep->dasher_ctx->last_dyn_period_id; + if (rep->dasher_ctx->last_dyn_period_id > ctx->last_dyn_period_id) + ctx->last_dyn_period_id = rep->dasher_ctx->last_dyn_period_id; continue; } + gf_list_add(restore_streams, ds); if (rep->dasher_ctx->done) { nb_done_in_period++; @@ -5925,7 +6329,7 @@ } //check we can reload muxed components - if not consider this source as removed if (rep->dasher_ctx->mux_pids) { - e = dasher_reload_muxed_comp(ctx, ds, rep->dasher_ctx->mux_pids, GF_TRUE); + e = dasher_reload_muxed_comp(ctx, ds, rep, GF_TRUE); if (e) { rep->dasher_ctx->done = 1; nb_done_in_period++; @@ -5935,9 +6339,11 @@ nb_remain_in_period++; } } + gf_list_del(restore_streams); + if (nb_remain_in_period) { - gf_assert(i+1==nb_p); last_period_active = GF_TRUE; + if (!first_active_p) first_active_p = p; } else if (nb_done_in_period && ctx->subdur ) { //we are done but we loop the entire streams @@ -5953,7 +6359,7 @@ } if (!last_period_active) return GF_OK; - ctx->current_period->period = gf_list_last(ctx->mpd->periods); + ctx->current_period->period = first_active_p; gf_list_reset(ctx->current_period->streams); gf_list_del(ctx->next_period->streams); ctx->next_period->streams = gf_list_clone(ctx->pids); @@ -5967,19 +6373,20 @@ ctx->current_period->period->duration = 0; } + u32 as_idx; nb_as = gf_list_count(ctx->current_period->period->adaptation_sets); - for (j=0; j<nb_as; j++) { + for (as_idx=0; as_idx<nb_as; as_idx++) { GF_DashStream *set_ds = NULL; GF_List *multi_pids = NULL; Bool use_multi_pid_init = GF_FALSE; - GF_MPD_AdaptationSet *set = gf_list_get(ctx->current_period->period->adaptation_sets, j); + GF_MPD_AdaptationSet *set = gf_list_get(ctx->current_period->period->adaptation_sets, as_idx); nb_rep = gf_list_count(set->representations); for (k=0; k<nb_rep; k++) { GF_DashStream *ds; GF_MPD_Representation *rep = gf_list_get(set->representations, k); if (! rep->dasher_ctx) continue; - ds = dasher_get_stream(ctx, rep->dasher_ctx->src_url, rep->dasher_ctx->source_pid, 0); + ds = dasher_get_stream(ctx, rep->dasher_ctx->src_url, rep->dasher_ctx->source_pid, rep->dasher_ctx->period_id, rep->id); if (!ds) continue; //restore everything @@ -6061,6 +6468,10 @@ if (ds->rep) gf_mpd_representation_free(ds->rep); ds->rep = rep; + + if (ds->rep_id) gf_free(ds->rep_id); + ds->rep_id = gf_strdup(rep->id); + ds->set = set; rep->playback.udta = ds; if (ds->owns_set) @@ -6088,13 +6499,13 @@ } if (rep->dasher_ctx->mux_pids) { - e = dasher_reload_muxed_comp(ctx, ds, rep->dasher_ctx->mux_pids, GF_FALSE); + e = dasher_reload_muxed_comp(ctx, ds, rep, GF_FALSE); if (e) return e; } } if (!set_ds) { - gf_assert(0); - //in case some edits of the ocntext broke everything, just ignore + //this may be null if one of the input is not in the same period + //also in case some edits of the context broke everything, just ignore continue; } set_ds->nb_rep = gf_list_count(set->representations); @@ -6241,7 +6652,7 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Failed to get download manager, cannot sync to remote UTC clock\n")); return; } - ctx->utc_sess = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_MEMORY_CACHE, NULL, NULL, &e); + ctx->utc_sess = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_MEMORY_CACHE|GF_NETIO_SESSION_NO_PROXY, NULL, NULL, &e); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Failed to create session for remote UTC source %s: %s - local clock will be used instead\n", url, gf_error_to_string(e) )); return; @@ -6297,7 +6708,7 @@ } gf_blob_release(cache_name); - //not match, try http date + //no match, try http date if (!ctx->utc_timing_type) { const char *hdr = gf_dm_sess_get_header(ctx->utc_sess, "Date"); if (hdr) { @@ -6338,7 +6749,7 @@ if (!ctx->gencues) { if (!ctx->out_path) { - dasher_check_outpath(ctx); + dasher_ensure_outpath(ctx); } if (ctx->current_period->period) { if (ctx->dyn_rate) @@ -6429,7 +6840,7 @@ } } - //filter out PIDs not for this period + //filter out PIDs which don't belong to this period count = gf_list_count(ctx->current_period->streams); period_id = NULL; for (i=0; i<count; i++) { @@ -6481,7 +6892,7 @@ gf_filter_pid_set_discard(ds->ipid, GF_FALSE); - dasher_send_encode_hints(ctx, ds); + dasher_send_transport_hints(ctx, ds); GF_FEVT_INIT(evt, GF_FEVT_PLAY, ds->ipid); evt.play.speed = 1.0; @@ -6567,12 +6978,15 @@ } //assign period ID if none specified - if (strcmp(period_id, DEFAULT_PERIOD_ID)) + if (strcmp(period_id, DEFAULT_PERIOD_ID)) { + if (ctx->current_period->period->ID) gf_free(ctx->current_period->period->ID); ctx->current_period->period->ID = gf_strdup(period_id); + } //assign ID if dynamic - if dash_ctx also assign ID since we could have moved from dynamic to static else if (!ctx->current_period->period->ID && ((ctx->dmode != GF_MPD_TYPE_STATIC) || ctx->state) ) { char szPName50; sprintf(szPName, "DID%d", ctx->last_dyn_period_id + 1); + if (ctx->current_period->period->ID) gf_free(ctx->current_period->period->ID); ctx->current_period->period->ID = gf_strdup(szPName); } @@ -6599,7 +7013,102 @@ if (ctx->period_not_ready) return GF_OK; - return dasher_setup_period(filter, ctx, NULL); + GF_Err e = dasher_setup_period(filter, ctx, NULL); + //update previous period duration and current period start now, otherwise we may have a wrong AST on the first segment(s) + //hence warnings and possible remove + dasher_update_period_duration(ctx, GF_TRUE); + return e; +} + +//set SSR (sub-segment representation) related descriptors +static GF_Err dasher_setup_ssr(GF_DasherCtx *ctx) +{ + u32 i, count; + GF_Err e=GF_OK; + const GF_PropertyValue *p; + struct ssr_map { + u32 main_as; + u32 tune_in_as; + }; + GF_List *ssr_mappings = gf_list_new(); + if (!ssr_mappings) return GF_OUT_OF_MEM; + + count = gf_list_count(ctx->current_period->streams); + for (i=0; i<count; i++) { + GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); + if (!ds->owns_set) continue; + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_SSR); + if (p && p->value.sint >= 0) { + struct ssr_map *map = gf_malloc(sizeof(struct ssr_map)); + if (!map) { + e = GF_OUT_OF_MEM; + break; + } + map->main_as = p->value.sint; + map->tune_in_as = ds->as_id; + gf_list_add(ssr_mappings, map); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher Stream %s, tune-in ASID set to %d\n", ds->src_url, map->tune_in_as)); + } + } + if (e) count = 0; + + for (i=0; i<count; i++) { + u32 j; + GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); + if (!ds->owns_set) continue; + + GF_MPD_Descriptor *desc_ssr = NULL; + GF_MPD_Descriptor *desc_ass = NULL; // adaptation-set-switching + + // Check if this AS has tune-in AS + struct ssr_map *map = NULL; + for (j = 0; j < gf_list_count(ssr_mappings); j++) { + map = gf_list_get(ssr_mappings, j); + if (map->main_as == ds->as_id) break; + else map = NULL; + } + + char value256; + if (map != NULL) { + sprintf(value, "%d", map->tune_in_as); + desc_ass = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:adaptation-set-switching:2016", value); + if (!desc_ass) e = GF_OUT_OF_MEM; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher Stream %s, ASID %d is the main AS of %d\n", ds->src_url, ds->as_id, map->tune_in_as)); + } + + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_SSR); + if (p) { + if (p->value.sint < 0) { + ds->set->ssr_mode = 1; + // LL-HLS compatibility mode + desc_ssr = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:ssr:2023", NULL); + if (!desc_ssr) e = GF_OUT_OF_MEM; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher Stream %s, ASID %d is using SSR with LL-HLS compatibility mode\n", ds->src_url, ds->as_id)); + } else { + ds->set->ssr_mode = 2; + sprintf(value, "%d", p->value.sint); + desc_ssr = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:ssr:2023", value); + if (!desc_ssr) e = GF_OUT_OF_MEM; + desc_ass = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:adaptation-set-switching:2016", value); + if (!desc_ass) e = GF_OUT_OF_MEM; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher Stream %s, ASID %d is the tune-in AS\n", ds->src_url, ds->as_id)); + } + } + + if (desc_ssr != NULL) gf_list_add(ds->set->essential_properties, desc_ssr); + if (desc_ass != NULL) gf_list_add(ds->set->supplemental_properties, desc_ass); + + if (desc_ssr) + ctx->store_seg_states = GF_TRUE; + + if (e) break; + } + while (gf_list_count(ssr_mappings)) { + struct ssr_map *map = gf_list_pop_back(ssr_mappings); + gf_free(map); + } + gf_list_del(ssr_mappings); + return e; } static GF_Err dasher_setup_period(GF_Filter *filter, GF_DasherCtx *ctx, GF_DashStream *inject_ds) @@ -6765,7 +7274,7 @@ ds->nb_comp++; if (ctx->bs_switch==DASHER_BS_SWITCH_MULTI) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Bitstream Swicthing mode \"multi\" is not supported with multiplexed representations, disabling bitstream switching\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Bitstream Switching mode \"multi\" is not supported with multiplexed representations, disabling bitstream switching\n")); ctx->bs_switch = DASHER_BS_SWITCH_OFF; } if (!ds->rep->codecs || !strstr(ds->rep->codecs, a_ds->rep->codecs)) { @@ -6812,7 +7321,7 @@ if (ctx->sigfrag) { Bool has_rep_conflict = GF_FALSE; - //make sure all representation have unique ids + //make sure all representations have unique ids for (i=0; i<count; i++) { u32 nb_changed=0; GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); @@ -6850,12 +7359,15 @@ if (max_adur.num * min_adur.den != min_adur.num * max_adur.den) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Audio streams in the period have different durations (min "LLU"/"LLD", max "LLU"/"LLD"), may result in bad synchronization while looping\n", min_adur.num, min_adur.den, max_adur.num, max_adur.den)); } - for (i=0; i<count; i++) { - GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); - if (ds->duration.num * max_adur.den > max_adur.num * ds->duration.den) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Input %s: max audio duration "LLU"/"LLD" in the period is less than duration "LLU"/"LLD", clamping will happen\n", ds->src_url, max_adur.num, max_adur.den, ds->duration.num, ds->duration.den )); + //if flush forced do NOT adjust clamp duration + if (!ctx->sflush) { + for (i=0; i<count; i++) { + GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); + if (ds->duration.num * max_adur.den > max_adur.num * ds->duration.den) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Input %s: max audio duration "LLU"/"LLD" in the period is less than duration "LLU"/"LLD", clamping will happen\n", ds->src_url, max_adur.num, max_adur.den, ds->duration.num, ds->duration.den )); + } + ds->clamped_dur = max_adur; } - ds->clamped_dur = max_adur; } } @@ -6903,13 +7415,16 @@ //not setup, create new AS ds->set = gf_mpd_adaptation_set_new(); ds->owns_set = GF_TRUE; - //only set hls intra for visual stream if we have know for sure + //only set hls intra for visual stream if we know for sure if ((ds->stream_type==GF_STREAM_VISUAL) && (ds->sync_points_type==DASHER_SYNC_NONE)) { ds->set->intra_only = GF_TRUE; } if (ctx->llhls) { ds->set->use_hls_ll = GF_TRUE; - if (ctx->cdur.den) + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DASH_FDUR); + if (p && p->value.frac.den) + ds->set->hls_ll_target_frag_dur = ((Double)p->value.frac.num) / p->value.frac.den; + else if (ctx->cdur.den) ds->set->hls_ll_target_frag_dur = ((Double)ctx->cdur.num) / ctx->cdur.den; } ds->set->udta = ds; @@ -7061,7 +7576,7 @@ //init UTC reference time for dynamic if (!ctx->mpd->availabilityStartTime && (ctx->dmode!=GF_MPD_TYPE_STATIC) && !inject_ds) { - u64 dash_start_date = ctx->ast ? gf_net_parse_date(ctx->ast) : 0; + u64 dash_start_date = ctx->ast ? (1+gf_net_parse_date(ctx->ast)) : 0; if (ctx->utc_timing_type != DASHER_UTCREF_NONE) { if (!gf_list_count(ctx->mpd->utc_timings) ) { @@ -7111,11 +7626,10 @@ ctx->mpd->gpac_init_ntp_ms = gf_net_get_ntp_ms(); ctx->mpd->availabilityStartTime = dasher_get_utc(ctx); - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher MPD Availability start time initialized to "LLU" ms\n", ctx->mpd->availabilityStartTime)); - - if (dash_start_date && (dash_start_date < ctx->mpd->availabilityStartTime)) { + if (dash_start_date && (dash_start_date-1 < ctx->mpd->availabilityStartTime)) { u64 start_date_sec_ntp, secs; Double ms; + dash_start_date -= 1; //recompute NTP init time matching the required ast secs = dash_start_date/1000; start_date_sec_ntp = (u32) secs; @@ -7133,6 +7647,52 @@ } else if (dash_start_date) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher specified AST %s seems in the future, ignoring it\n", ctx->ast)); } + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Dasher MPD Availability start time initialized to "LLU" ms\n", ctx->mpd->availabilityStartTime)); + } + + GF_Err e = dasher_setup_ssr(ctx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher error setting up SSR: %s\n", ctx->ast)); + return e; + } + + //set query parameters if requested + for (i = 0; i < count; i++) { + GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i); + if (!ds->owns_set) continue; + prop = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_AS_QUERY); + + if (ctx->query || (prop && prop->value.string)) { + GF_MPD_Descriptor *desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:urlparam:2014", NULL); + if (!desc) continue; + char *as_query = ctx->query; + if (prop && prop->value.string) as_query = prop->value.string; + + desc->x_attributes = gf_list_new(); + desc->x_children = gf_list_new(); + + if (desc->x_attributes) { + GF_XMLAttribute *attr = gf_xml_dom_create_attribute("xmlns:up", "urn:mpeg:dash:schema:urlparam:2014"); + if (attr) gf_list_add(desc->x_attributes, attr); + } + + if (desc->x_children) { + GF_XMLNode *qinfo = gf_xml_dom_node_new("up", "UrlQueryInfo"); + if (qinfo) { + qinfo->attributes = gf_list_new(); + if (qinfo->attributes) { + GF_XMLAttribute *att; + att = gf_xml_dom_create_attribute("queryTemplate", "$querypart$"); + if (att) gf_list_add(qinfo->attributes, att); + att = gf_xml_dom_create_attribute("queryString", as_query); + if (att) gf_list_add(qinfo->attributes, att); + } + gf_list_add(desc->x_children, qinfo); + } + } + + gf_list_add(ds->set->essential_properties, desc); + } } //setup adaptation sets bitstream switching @@ -7142,7 +7702,7 @@ if (inject_ds && (ds != inject_ds)) continue; //check bitstream switching - dasher_check_bitstream_swicthing(ctx, ds->set); + dasher_check_bitstream_switching(ctx, ds->set); //setup AS defaults, roles and co dasher_setup_set_defaults(ctx, ds->set); //setup sources, templates & co @@ -7170,7 +7730,7 @@ //in sbound=0 mode, if stream has sync and non-sync and uses skip samples, allow spliting //slightly before - typically needed for audio with sync points (usac, mpegh) where the segment duration is set //to the intra interval, we need to take into account the skip samples - if (!ctx->sbound && !ds->cues && (ds->sync_points_type==DASHER_SYNC_PRESENT) + if (!ctx->sbound && !ds->cues && (ds->sync_points_type==DASHER_SYNC_PRESENT) && ds->stream_type==GF_STREAM_AUDIO && (ds->pts_minus_cts<0) && (ds->next_seg_start > (u32) -ds->pts_minus_cts) ) { ds->next_seg_start -= (u32) -ds->pts_minus_cts; @@ -7179,7 +7739,7 @@ ds->adjusted_next_seg_start = ds->next_seg_start; ds->segment_started = GF_FALSE; ds->seg_number = ds->startNumber; - ds->first_cts = ds->first_dts = ds->max_period_dur = 0; + ds->first_cts = ds->first_dts = ds->max_period_dur = ds->current_max_period_dur = 0; //simulate N loops of the source if (ctx->nb_secs_to_discard) { @@ -7193,13 +7753,14 @@ ds->ts_offset += nb_skip*seg_dur; ds->seg_number += nb_skip; - ds->max_period_dur = ds->cumulated_dur; + ds->max_period_dur = ds->current_max_period_dur = ds->cumulated_dur; ds->adjusted_next_seg_start += ds->ts_offset; ds->next_seg_start += ds->ts_offset; } } ctx->nb_secs_to_discard = 0; + ctx->period_pck_sent = GF_FALSE; if (ctx->state) dasher_context_update_period_start(ctx); @@ -7207,7 +7768,7 @@ return GF_OK; } -static void dasher_insert_timeline_entry(GF_DasherCtx *ctx, GF_DashStream *ds) +static void dasher_insert_timeline_entry(GF_DasherCtx *ctx, GF_DashStream *ds, Bool is_ll_anouncement) { GF_MPD_SegmentTimelineEntry *s; u64 duration, pto, prev_patch_dur=0; @@ -7218,7 +7779,8 @@ //we only store segment timeline for the main component in the representation if (ds->muxed_base) return; - if (ds->rep && ds->rep->state_seg_list) { + + if (ds->rep && ds->rep->state_seg_list && !is_ll_anouncement) { GF_DASH_SegmentContext *sctx = gf_list_last(ds->rep->state_seg_list); if (sctx) sctx->dur = ds->first_cts_in_next_seg - ds->first_cts_in_seg; @@ -7226,16 +7788,21 @@ //we only use segment timeline with templates if (!ds->stl && !ctx->do_index) return; - if (gf_list_find(ds->set->representations, ds->rep)==0) is_first = GF_TRUE; - if (ds->first_cts_in_next_seg > ds->first_cts_in_seg) + if (gf_list_find(ds->set->representations, ds->rep)==0) + is_first = GF_TRUE; + + if (ds->first_cts_in_next_seg > ds->first_cts_in_seg) { duration = ds->first_cts_in_next_seg - ds->first_cts_in_seg; - else { + } + //low latency live edge, use target duration + else if (is_ll_anouncement) { + duration = gf_timestamp_rescale(ds->dash_dur.num, ds->dash_dur.den, ds->mpd_timescale); + } else { duration = 0; - gf_assert(0); } //handle sap time adjustment (first_cts_in_seg is the SAP cts, we may have lower cts whith sap 2 or 3) - if (ds->min_cts_in_seg_plus_one && (ds->min_cts_in_seg_plus_one-1 < ds->first_cts_in_seg)) { + if (!is_ll_anouncement && ds->min_cts_in_seg_plus_one && (ds->min_cts_in_seg_plus_one-1 < ds->first_cts_in_seg)) { prev_patch_dur = ds->first_cts_in_seg - (ds->min_cts_in_seg_plus_one-1); if (ds->timescale != ds->mpd_timescale) prev_patch_dur = gf_timestamp_rescale(prev_patch_dur, ds->timescale, ds->mpd_timescale); @@ -7250,15 +7817,17 @@ pto = gf_timestamp_rescale(pto, ds->timescale, ds->mpd_timescale); } - seg_align = (ds->set->segment_alignment || ds->set->subsegment_alignment) ? GF_TRUE : GF_FALSE; - //not first and segment alignment, ignore - if (!is_first && seg_align) { - return; - } if (ctx->do_index) { GF_MPD_SegmentURL *surl = gf_list_last(ds->rep->segment_list->segment_URLs); surl->duration = duration; } + + seg_align = (ds->set->segment_alignment || ds->set->subsegment_alignment) ? GF_TRUE : GF_FALSE; + //not first and segment alignment, ignore + if (!is_first && seg_align && !is_ll_anouncement) { + return; + } + if (!ds->stl) return; //no segment alignment store in each rep @@ -7331,10 +7900,28 @@ } } + //live edge, always inject an entry and remember we just did + if (is_ll_anouncement) { + GF_SAFEALLOC(s, GF_MPD_SegmentTimelineEntry); + if (!s) return; + + s->start_time = ds->seg_start_time + pto; + s->duration = (u32) duration; + gf_list_add(tl->entries, s); + ds->last_stl_is_ll = GF_TRUE; + return; + } + //purge live edge entry + else if (ds->last_stl_is_ll) { + s = gf_list_pop_back(tl->entries); + if (s) gf_free(s); + ds->last_stl_is_ll = GF_FALSE; + } + //append to previous entry if possible s = gf_list_last(tl->entries); - if (prev_patch_dur) { + if (s && prev_patch_dur) { u32 nb_ent = gf_list_count(tl->entries); //split entry if (s->repeat_count) { @@ -7362,9 +7949,15 @@ } } + GF_DASH_SegmentContext *sctx = gf_list_last(ds->rep->state_seg_list); + //add to last entry ONLY if not keeping segments if (!ctx->keep_segs && s && (s->duration == duration) && (s->start_time + (s->repeat_count+1) * s->duration == ds->seg_start_time + pto)) { s->repeat_count++; + if (sctx) { + sctx->stl_start = s->start_time; + sctx->stl_rcount = s->repeat_count; + } return; } @@ -7374,6 +7967,10 @@ s->start_time = ds->seg_start_time + pto; s->duration = (u32) duration; gf_list_add(tl->entries, s); + if (sctx) { + sctx->stl_start = s->start_time; + sctx->stl_rcount = s->repeat_count; + } } static void dasher_copy_segment_timelines(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) @@ -7382,12 +7979,14 @@ u32 i, j, count, nb_s; //get as level segment timeline, set it to NULL, reassign it to first rep and clone for other reps if (ctx->tpl) { - gf_assert(set->segment_template->segment_timeline); src_tl = set->segment_template->segment_timeline; + //may be already NULL when reloading context + if (!src_tl) return; set->segment_template->segment_timeline = NULL; } else { - gf_assert(set->segment_list->segment_timeline); src_tl = set->segment_list->segment_timeline; + //may be already NULL when reloading context + if (!src_tl) return; set->segment_list->segment_timeline = NULL; } nb_s = gf_list_count(src_tl->entries); @@ -7434,11 +8033,73 @@ s->duration = src_s->duration; s->repeat_count = src_s->repeat_count; s->start_time = src_s->start_time; + s->nb_parts = src_s->nb_parts; gf_list_add(tl->entries, s); } } } +static void dasher_set_timeline_parts(GF_DasherCtx *ctx, GF_DashStream *ds, GF_DASH_SegmentContext *sctx) +{ + if (!ds->set) return; + if (!ds->set->segment_template && !ds->rep->segment_template) return; + GF_MPD_SegmentTimeline *stl = ds->set->segment_template->segment_timeline; + if (!stl) stl = ds->rep->segment_template->segment_timeline; + u32 nb_parts = sctx->nb_frags; + + GF_MPD_SegmentTimelineEntry *ent = NULL; + u32 i, nb_entries = gf_list_count(stl->entries); + for (i=nb_entries; i>0; i--) { + ent = gf_list_get(stl->entries, i-1); + if (ent->start_time == sctx->stl_start) break; + ent = NULL; + } + if (!ent) + return; + + //first seg in timeline entry + if (!ent->nb_parts) { + ent->nb_parts = nb_parts; + return; + } + //same as prev + if (ent->nb_parts == nb_parts) { + return; + } + //different parts, figure out if we must clone timelines: + //if repeat_count is different from what we recorded when adding the entry, we have already edited the entry + //and we need to split segment timelines + // (unless a magic tool in the spec allows for same t, d, r but different k, couldn't find any) + if (!ent->repeat_count || (ent->repeat_count != sctx->stl_rcount)) { + if (stl == ds->rep->segment_template->segment_timeline) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Error in segmenter, timeline entry should not have parts set\n")); + return; + } + dasher_copy_segment_timelines(ctx, ds->set); + stl = ds->rep->segment_template->segment_timeline; + gf_assert(stl); + //locate entry in new list and reset all nb_parts of future ones + nb_entries = gf_list_count(stl->entries); + for (i=nb_entries; i>0; i--) { + ent = gf_list_get(stl->entries, i-1); + if (ent->start_time == sctx->stl_start) break; + ent->nb_parts = 0; + ent = NULL; + } + if (ent) + ent->nb_parts = nb_parts; + return; + } + + GF_MPD_SegmentTimelineEntry *new_ent; + GF_SAFEALLOC(new_ent, GF_MPD_SegmentTimelineEntry); + new_ent->start_time = ent->start_time + ent->duration * (1+ent->repeat_count); + new_ent->duration = ent->duration; + ent->repeat_count--; + new_ent->nb_parts = nb_parts; + gf_list_add(stl->entries, new_ent); +} + static void dasher_flush_segment(GF_DasherCtx *ctx, GF_DashStream *ds, Bool is_last_in_period) { u32 i, count; @@ -7503,15 +8164,17 @@ if (!base_ds->done && !base_ds->stl && ctx->tpl && !ctx->cues && !ctx->forward_mode && !is_last_in_period) { if (2 * seg_duration * ds->dash_dur.den < ds->dash_dur.num) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Segment %d duration %g less than half DASH duration, consider reencoding or using segment timeline\n", ds->seg_number, seg_duration)); } else if (2 * seg_duration * ds->dash_dur.den > 3 * ds->dash_dur.num) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Segment %d duration %g more than 3/2 DASH duration, consider reencoding or using segment timeline\n", ds->seg_number, seg_duration)); } } - dasher_insert_timeline_entry(ctx, base_ds); + dasher_insert_timeline_entry(ctx, base_ds, GF_FALSE); + + //store max duration in period at end of segment + ds->max_period_dur = ds->current_max_period_dur; - if (ctx->do_m3u8) { + if (ctx->do_m3u8 && !(ds->stream_type == GF_STREAM_TEXT && ctx->rawsub)) { u64 segdur = base_ds->first_cts_in_next_seg - ds->first_cts_in_seg; if (gf_timestamp_less(base_ds->rep->hls_max_seg_dur.num, base_ds->rep->hls_max_seg_dur.den, segdur, base_ds->timescale)) { s64 diff = gf_timestamp_rescale(base_ds->rep->hls_max_seg_dur.num, base_ds->rep->hls_max_seg_dur.den, 1000); @@ -7631,7 +8294,8 @@ ds_done = base_ds; else if (base_ds->nb_comp_done==base_ds->nb_comp) ds_not_done = base_ds; - if (!base_ds->done && base_ds->seg_done) { + //update next_seg_start/adjusted_next_seg_start/seg_number even if done, needed for dasher state context + if (base_ds->seg_done) { base_ds->seg_done = GF_FALSE; base_ds->nb_comp_done = 0; @@ -7760,6 +8424,11 @@ } else { cts = 0; } + } + /*special cases when pts offset is not signaled and SAP2 is used, first CTS is higher than the CTS of the few following frame + we will still disptach withthe source cts, but we use 0 for these frames to avoid crazy timings */ + else if ((s64) cts < (s64) ds->first_cts) { + cts = 0; } else { cts -= ds->first_cts; } @@ -7770,7 +8439,7 @@ { Bool no_concat; GF_DASH_SegmentContext *seg_state=NULL; - char szSegmentNameGF_MAX_PATH, szSegmentFullPathGF_MAX_PATH, szIndexNameGF_MAX_PATH; + char szSegmentNameGF_MAX_PATH, szSegmentFullPathGF_MAX_PATH, szIndexNameGF_MAX_PATH, szLLHASTemplateGF_MAX_PATH; GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; if (ctx->forward_mode) { @@ -7840,6 +8509,10 @@ gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(ntpts)); } else if (ctx->ntp==DASHER_NTP_REM) { gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, NULL); + } else if (ctx->ntp==DASHER_NTP_KEEP) { + const GF_PropertyValue *v = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_SENDER_NTP); + if (v) + gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, v); } if (!ctx->gencues) { gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENUM, NULL ); @@ -7855,7 +8528,7 @@ gf_filter_pck_set_property(pck, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE)); if (ds->set_period_switch) { ds->set_period_switch = GF_FALSE; - gf_filter_pck_set_property(pck, GF_PROP_PID_DASH_PERIOD_START, &PROP_LONGUINT(0) ); + gf_filter_pck_set_property(pck, GF_PROP_PCK_DASH_PERIOD_START, &PROP_BOOL(GF_TRUE) ); } } } @@ -7874,6 +8547,9 @@ if (ds->last_min_segment_start_time > ctx->min_segment_start_time) ctx->min_segment_start_time = ds->last_min_segment_start_time; + if (ctx->asto>0) + dasher_insert_timeline_entry(ctx, base_ds, GF_TRUE); + if (ctx->store_seg_states) { char *kms_uri; const GF_PropertyValue *p; @@ -7888,6 +8564,12 @@ p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_HLS_GROUPID); if (p) ds->rep->groupID = p->value.string; + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_HLS_GROUP_REND); + if (p) { + ds->rep->nb_group_ids_rend = p->value.string_list.nb_items; + ds->rep->group_ids_rend = (const char**) p->value.string_list.vals; + } + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_HLS_FORCE_INF); if (p) ds->rep->hls_forced = p->value.string; @@ -7912,7 +8594,41 @@ } } ds->rep->nb_chan = ds->nb_ch; - ds->rep->m3u8_name = ds->hls_vp_name; + + // Dolby Digital Plus Online Delivery Playback System Development Guide version 1.5 + // For Dolby Atmos content, the number of decodable objects followed by a slash (/) and then JOC + if (ds->codec_id == GF_CODECID_EAC3 && ds->atmos_complexity_type) { + ds->rep->nb_chan = 0; + sprintf(ds->rep->str_chan, "%d/JOC", ds->atmos_complexity_type); + } + + if (ds->codec_id == GF_CODECID_AC4) { + switch(ds->ac4_content_type) { + // Dolby AC-4 and HTTP Live Streaming Specification 1 November 2021 4.3 + case AC4_IMMERSIVE_STEREO: + ds->rep->nb_chan = 0; + sprintf(ds->rep->str_chan, "2/IMSA"); + break; + case AC4_IMMERSIVE_STEREO_ATMOS: + ds->rep->nb_chan = 0; + sprintf(ds->rep->str_chan, "2/IMSA,ATMOS"); + break; + case AC4_CHANNEL_BASED_IMMERSIVE_CONTENT: + ds->rep->nb_chan = 0; + sprintf(ds->rep->str_chan, "%d/IMSA", ds->nb_ch); + break; + case AC4_OBJECT_BASED_AJOC_CONTENT: + ds->rep->nb_chan = 0; + sprintf(ds->rep->str_chan, "%d/JOC", ds->nb_ch); + break; + default: + break; + } + } + + //we need a copy when flushing MPD after a period switch + if (ds->rep->m3u8_name) gf_free(ds->rep->m3u8_name); + ds->rep->m3u8_name = ds->hls_vp_name ? gf_strdup(ds->hls_vp_name) : NULL; if (ds->fps.den) { ds->rep->fps = ds->fps.num; ds->rep->fps /= ds->fps.den; @@ -7922,7 +8638,8 @@ if (!seg_state) return; seg_state->time = ds->seg_start_time; seg_state->seg_num = ds->seg_number; - seg_state->llhls_mode = ctx->llhls; + if (ctx->llhls) seg_state->llhls_mode = ctx->llhls; + else if (ds->set->ssr_mode) seg_state->llhls_mode = GF_DASH_LL_HLS_BRSF; ds->current_seg_state = seg_state; seg_state->encrypted = GF_FALSE; @@ -7953,9 +8670,9 @@ } } //we need a hard copy as the pid may reconfigure before we flush the segment - if (kms_uri) { + if (kms_uri || ((ctx->muxtype==DASHER_MUX_TS) && (ds->rep->crypto_type!=3)) ) { //insert IV if not mp4 - if (!ds->tci && !strstr(kms_uri, "IV=") && (ctx->muxtype!=DASHER_MUX_ISOM)) { + if (!ds->tci && ctx->hlsiv && (!kms_uri || !strstr(kms_uri, "IV=")) && (ctx->muxtype!=DASHER_MUX_ISOM)) { p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_CENC_KEY_INFO); if (p && (p->value.data.size==37)) { char *kms_iv=NULL; @@ -7968,18 +8685,24 @@ sprintf(szVal, "%02X", ivi); strcat(szIV, szVal); } - if (!strstr(kms_uri, "URI=")) { + if (kms_uri && !strstr(kms_uri, "URI=")) { gf_dynstrcat(&kms_iv, "URI=\"", NULL); gf_dynstrcat(&kms_iv, kms_uri, NULL); gf_dynstrcat(&kms_iv, "\"", NULL); - } else { + } else if (kms_uri) { gf_dynstrcat(&kms_iv, kms_uri, NULL); + } else { + if (!ds->rep->def_kms_used) { + ds->rep->def_kms_used = 1; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("HLS Missing key URI in one or more keys - will use dummy one URI=\"gpac:hls:key:locator:null\"\n")); + } + gf_dynstrcat(&kms_iv, "URI=\"gpac:hls:key:locator:null\"", NULL); } gf_dynstrcat(&kms_iv, szIV, ","); seg_state->hls_key_uri = kms_iv; } } - if (!seg_state->hls_key_uri) { + if (kms_uri && !seg_state->hls_key_uri) { if (!strstr(kms_uri, "URI=")) { gf_dynstrcat(&seg_state->hls_key_uri, "URI=\"", NULL); gf_dynstrcat(&seg_state->hls_key_uri, kms_uri, NULL); @@ -7993,7 +8716,7 @@ gf_list_add(ds->rep->state_seg_list, seg_state); if (ctx->sigfrag) { const GF_PropertyValue *frag_range = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_FRAG_RANGE); - const GF_PropertyValue *frag_url = gf_filter_pck_get_property(in_pck, GF_PROP_PID_URL); + const GF_PropertyValue *frag_url = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_SEG_URL); const GF_PropertyValue *frag_name = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_FILENAME); if (frag_url && frag_name) { @@ -8058,13 +8781,12 @@ if (ctx->sigfrag) { Bool has_root_sidx = GF_TRUE; - const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PCK_SIDX_RANGE); + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_VOD_SIDX_RANGE); if (!p) { p = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_SIDX_RANGE); has_root_sidx = GF_FALSE; } - if (p) { if (ds->rep->segment_base && !ds->rep->segment_base->index_range) { GF_SAFEALLOC(ds->rep->segment_base->index_range, GF_MPD_ByteRange); @@ -8093,7 +8815,7 @@ return; } - if (ctx->sfile) { + if (ctx->sfile) { // single file for all segments GF_MPD_SegmentURL *seg_url; gf_assert(ds->rep->segment_list); @@ -8108,7 +8830,7 @@ if (ctx->sigfrag) { const GF_PropertyValue *frag_range = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_FRAG_RANGE); - const GF_PropertyValue *frag_url = gf_filter_pck_get_property(in_pck, GF_PROP_PID_URL); + const GF_PropertyValue *frag_url = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_SEG_URL); const GF_PropertyValue *frag_name = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_FILENAME); if (frag_url && frag_name) { seg_url->media = dasher_strip_base(ctx, ds, frag_url->value.string, frag_name->value.string); @@ -8169,6 +8891,7 @@ } } + szLLHASTemplate0 = 0; if (!ctx->forward_mode) { /*get final segment template - output file name is NULL, we already have solved this in source_setup segment time must be PTO-adjusted !*/ @@ -8179,6 +8902,14 @@ gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_SEGMENT, ds->set->bitstream_switching, szSegmentName, base_ds->rep_id, NULL, base_ds->seg_template, NULL, base_ds->seg_start_time + pto, base_ds->rep->bandwidth, base_ds->seg_number, ds->stl, ctx->tpl_force); + //generate LLHAS template for SubNumber even in LLHLS + if (ds->set->ssr_mode || (ctx->llhls>1)) { + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_SEGMENT_SUBNUMBER, ds->set->bitstream_switching, szLLHASTemplate, base_ds->rep_id, NULL, base_ds->seg_template, NULL, base_ds->seg_start_time + pto, base_ds->rep->bandwidth, base_ds->seg_number, ds->stl, ctx->tpl_force); + + //no template used + if (szLLHASTemplate0 && !strstr(szLLHASTemplate, "$SubNumber")) + szLLHASTemplate0 = 0; + } } @@ -8220,6 +8951,8 @@ if (seg_state && !ctx->sigfrag) { seg_state->filepath = gf_strdup(szSegmentFullPath); seg_state->filename = gf_strdup(szSegmentName); + if (szLLHASTemplate0) + seg_state->llhas_template = gf_strdup(szLLHASTemplate); } if (ds->rep->segment_list && (ctx->forward_mode!=DASHER_FWD_ALL) && !ctx->gencues) { @@ -8258,8 +8991,11 @@ seg_url->index = dasher_cat_mpd_url(ctx, ds, szIndexName); } } - if (pck) + if (pck) { gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENAME, &PROP_STRING(szSegmentFullPath) ); + if (szLLHASTemplate0) + gf_filter_pck_set_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE, &PROP_STRING(szLLHASTemplate) ); + } } static Bool dasher_check_loop(GF_DasherCtx *ctx, GF_DashStream *ds) @@ -8291,13 +9027,13 @@ max_ts_offset = 0; max_ts_scale = 1; - //check all input media duration + //check all input media durations for (i=0; i<count; i++) { GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i); //one pid is waiting for loop while another has done its subdur and won't process any new segment until the next subdur call, which //will never happen since the first PID waits for loop. We must force early generation in this case - if (a_ds->subdur_done) { + if (a_ds->subdur_done && !ctx->sflush) { a_ds->subdur_done = GF_FALSE; //remember the max period dur before this forced segment generation a_ds->subdur_forced_use_period_dur = a_ds->max_period_dur; @@ -8322,17 +9058,19 @@ for (i=0; i<count; i++) { GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i); - if (a_ds->subdur_done) - continue; - - ts_offset = gf_timestamp_rescale(max_ts_offset, max_ts_scale, a_ds->timescale); - - a_ds->ts_offset = ts_offset; - if (a_ds->done) continue; - if (a_ds->ts_offset > a_ds->est_next_dts) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Looping streams of unequal duration, inserting "LLU" us of timestamp delay in pid %s from %s\n", ((a_ds->ts_offset - a_ds->est_next_dts) * 1000000) / a_ds->timescale, gf_filter_pid_get_name(a_ds->ipid), a_ds->src_url)); + //if we loop (subdur set and not done) we must update ts offset + //if keep_ts is not set, we will get called with the same source so we also need to update the timestamp + if (!ctx->keep_ts || (!a_ds->subdur_done && !a_ds->done && ctx->subdur) ) { + ts_offset = gf_timestamp_rescale(max_ts_offset, max_ts_scale, a_ds->timescale); + a_ds->ts_offset = ts_offset; + //no loop if flushed is forced + if (!ctx->sflush && (a_ds->ts_offset > a_ds->est_next_dts)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Looping streams of unequal duration, inserting "LLU" us of timestamp delay in pid %s from %s\n", ((a_ds->ts_offset - a_ds->est_next_dts) * 1000000) / a_ds->timescale, gf_filter_pid_get_name(a_ds->ipid), a_ds->src_url)); + } } + if (a_ds->subdur_done || a_ds->done) continue; + a_ds->seek_to_pck = 0; a_ds->nb_pck = 0; a_ds->clamp_done = GF_FALSE; @@ -8347,7 +9085,7 @@ gf_filter_pid_set_discard(a_ds->ipid, GF_FALSE); - dasher_send_encode_hints(ctx, ds); + dasher_send_transport_hints(ctx, ds); GF_FEVT_INIT(evt, GF_FEVT_PLAY, a_ds->ipid); evt.play.speed = 1.0; @@ -8571,8 +9309,8 @@ pck = gf_filter_pck_new_alloc(ds->opid, 0, &data); } - gf_filter_pck_set_dts(pck, ds->first_cts_in_seg); - gf_filter_pck_set_cts(pck, ds->first_cts_in_seg); + gf_filter_pck_set_dts(pck, ds->first_cts_in_seg+ds->first_cts); + gf_filter_pck_set_cts(pck, ds->first_cts_in_seg+ds->first_cts); gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); //we don't assign a duration @@ -8581,8 +9319,10 @@ dasher_mark_segment_start(ctx, ds, pck, NULL); ds->segment_started = GF_TRUE; - if (pck) + if (pck) { gf_filter_pck_send(pck); + ctx->period_pck_sent = GF_TRUE; + } if (ctx->do_index) { GF_MPD_SegmentURL *s = gf_list_last(ds->rep->segment_list->segment_URLs); @@ -8600,10 +9340,11 @@ u32 i, count, nb_init, has_init, nb_reg_done; GF_DasherCtx *ctx = gf_filter_get_udta(filter); GF_Err e; - Bool seg_done = GF_FALSE; + Bool force_flush_manifest = GF_FALSE; u32 nb_seg_waiting = 0; u32 nb_seg_active = 0; + ctx->has_pid_removed = GF_FALSE; if (ctx->in_error) { gf_filter_abort(filter); return GF_SERVICE_ERROR; @@ -8628,7 +9369,7 @@ //streams in period are not all ready, wait for them if (ctx->period_not_ready) { Bool is_eos; - //potpone until no pending connections, otherwise we may add input streams in the wrong period + //postpone until no pending connections, otherwise we may add input streams in the wrong period if (gf_filter_connections_pending(filter)) return GF_OK; @@ -8647,7 +9388,8 @@ if (ctx->is_eos) return GF_EOS; - if (ctx->setup_failure) return ctx->setup_failure; + if (ctx->setup_failure) + return ctx->setup_failure; count = gf_list_count(ctx->current_period->streams); if (!ctx->min_cts_period.den) { @@ -8660,14 +9402,18 @@ } GF_FilterPacket *pck = gf_filter_pid_get_packet(ds->ipid); if (!pck) continue; + if (ctx->has_pid_removed) return GF_OK; + u64 ts = gf_filter_pck_get_cts(pck); - //only adjust if delay is negative (skip), otherwise (delay) keep mints as is. - //Not doing so will set the rep PTO to the delay, canceling the delay ... - if (ds->pts_minus_cts<0) - ts = ts + ds->pts_minus_cts; - if (!min_ts || gf_timestamp_less(ts, ds->timescale, min_ts, min_timescale)) { - min_ts = ts; - min_timescale = ds->timescale; + if (ts != GF_FILTER_NO_TS) { + //only adjust if delay is negative (skip), otherwise (delay) keep min ts as is. + //Not doing so will set the rep PTO to the delay, canceling the delay ... + if (ds->pts_minus_cts<0) + ts = ts + ds->pts_minus_cts; + if (!min_ts || gf_timestamp_less(ts, ds->timescale, min_ts, min_timescale)) { + min_ts = ts; + min_timescale = ds->timescale; + } } num_ready++; if (gf_filter_pid_would_block(ds->ipid)) num_blocked++; @@ -8716,6 +9462,10 @@ if (!ds->request_period_switch) { gf_assert(ds->period == ctx->current_period); pck = gf_filter_pid_get_packet(ds->ipid); + + //pid removal while fetching, abort current process + if (ctx->has_pid_removed) return GF_OK; + //we may change period after a packet fetch (reconfigure of input pid) if ((ds->period != ctx->current_period) || ds->request_period_switch) { //in closest mode, flush queue @@ -8804,8 +9554,9 @@ break; } - if (ds->clamp_done) ds_is_done=GF_TRUE; - else if (gf_filter_pid_is_eos(ds->ipid)) { + if (ds->clamp_done) { + ds_is_done=GF_TRUE; + } else if (gf_filter_pid_is_eos(ds->ipid)) { if (gf_filter_pid_is_flush_eos(ds->ipid)) { if (ds->segment_started && !ds->seg_done) { ds->seg_done = GF_TRUE; @@ -8852,11 +9603,12 @@ } - if (ctx->loop && dasher_check_loop(ctx, ds)) { + if ((ctx->loop || ctx->sflush) && dasher_check_loop(ctx, ds)) { if (ctx->subdur) break; //loop on the entire source, consider the stream not done for segment flush - ds_done = 0; + if (ctx->sflush != SFLUSH_END) + ds_done = 0; } ds->clamp_done = GF_FALSE; @@ -8868,8 +9620,12 @@ if (!ds->done) ds->done = ds_done; ds->seg_done = GF_TRUE; - seg_done = GF_TRUE; + force_flush_manifest = GF_TRUE; ds->first_cts_in_next_seg = ds->est_first_cts_in_next_seg; + if ((ds->stream_type==GF_STREAM_TEXT) && !ds->muxed_base && (ds->first_cts_in_next_seg == ds->first_cts_in_seg)) { + u64 segdur = gf_timestamp_rescale(ds->dash_dur.num, ds->dash_dur.den, ds->timescale); + if (segdur) ds->first_cts_in_next_seg = ds->first_cts_in_seg + segdur; + } ds->est_first_cts_in_next_seg = 0; if (base_ds->nb_comp_done < base_ds->nb_comp) { base_ds->nb_comp_done ++; @@ -8947,7 +9703,7 @@ if (!sap_type && (ds->sync_points_type != DASHER_SYNC_PRESENT)) { ds->sync_points_type = DASHER_SYNC_PRESENT; - //cf setup_period: in sbound=0 mode, if stream has sync and non-sync and uses skip samples, allow spliting + //cf setup_period: in sbound=0 mode, if stream has sync and non-sync and uses skip samples, allow splitting //slightly before - typically needed for audio with sync points (usac, mpegh) where the segment duration is set //to the intra interval, we need to take into account the skip samples if (!ctx->sbound && !ds->cues @@ -8974,10 +9730,15 @@ if (!ds->presentation_time_offset) ds->presentation_time_offset = cts + 1; - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Representation not initialized, dropping non-SAP1/2 packet CTS "LLU"/%d\n", cts, ds->timescale)); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Representation not initialized, dropping non-SAP1/2 packet CTS "LLU"/%u\n", cts, ds->timescale)); dasher_drop_input(ctx, ds, GF_FALSE); + //reset ast until we get SAP + ctx->mpd->availabilityStartTime = 0; break; } + //ast was reset, resetup + if (!ctx->mpd->availabilityStartTime) + ctx->mpd->availabilityStartTime = dasher_get_utc(ctx); set_start_with_sap = ctx->sseg ? base_ds->set->subsegment_starts_with_sap : base_ds->set->starts_with_sap; if (!ds->muxed_base) { @@ -9007,11 +9768,11 @@ } } else if (set_start_with_sap != sap_type) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Segments do not start with the same SAP types: set initialized with %d but first packet got %d - bitstream will not be compliant\n", set_start_with_sap, sap_type)); + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Segments do not start with the same SAP types: set initialized with %u but first packet got %u - bitstream will not be compliant\n", set_start_with_sap, sap_type)); } check_ts = cts; - //in case we droped frames + //in case we dropped frames if (ds->presentation_time_offset) check_ts = ds->presentation_time_offset - 1; ds->presentation_time_offset = 0; @@ -9073,11 +9834,18 @@ dts -= ds->first_dts; if (ctx->sreg && ctx->mpd->gpac_mpd_time && gf_timestamp_greater(dts, ds->timescale, ctx->mpd->gpac_mpd_time, 1000)) { - nb_reg_done++; - break; + if (!gf_filter_pid_has_seen_eos(ds->ipid)) { + //if we're close to EOS, do not regulate as this could trigger a infinite wait between text streams and non-text streams + nb_reg_done++; + break; + } } dur = o_dur = gf_filter_pck_get_duration(pck); + if (dur > 600 * ds->timescale) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Packet with suspicious duration %g seconds, clamping to 10 min!\n", ((Double)o_dur)/ds->timescale )); + dur = o_dur = ds->timescale; + } pcont_cts += dur; if (ds->period_continuity_next_cts < pcont_cts) ds->period_continuity_next_cts = pcont_cts; @@ -9153,14 +9921,24 @@ else if (base_ds->forced_period_switch) { ds->seg_done = GF_TRUE; dasher_inject_eods(ctx, ds, GF_FALSE); - seg_done = GF_TRUE; + force_flush_manifest = GF_TRUE; dasher_stream_period_changed(filter, ctx, ds, GF_FALSE); i--; count--; break; } - //force flush mode, segment is done upon eos - else if (ctx->force_flush) { + //period switch in progress, do not dash more than requested + else if (ctx->force_period_switch && ctx->period_switch_cts.den) { + //period_switch_cts is in original cts (pcont_cts) + if (gf_timestamp_greater_or_equal(pcont_cts, ds->timescale, ctx->period_switch_cts.num, ctx->period_switch_cts.den)) { + dasher_stream_period_changed(filter, ctx, ds, GF_TRUE); + i--; + count--; + break; + } + } + //flush for entire input, segment is done upon eos + else if (ctx->sflush==SFLUSH_SINGLE) { } //source-driven fragmentation check for segment start else if (ctx->sigfrag) { @@ -9178,7 +9956,7 @@ if (p && p->value.boolean) { u32 size; gf_filter_pck_get_data(pck, &size); - if (base_ds->segment_started) { + if (ds->segment_started) { seg_over = GF_TRUE; if (ds == base_ds) { base_ds->adjusted_next_seg_start = cts; @@ -9266,8 +10044,9 @@ } if (is_cue_split) { - if (!sap_type) { - GF_LOG(ctx->strict_cues ? GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("DASH cue found (sn %d - dts "LLD" - cts "LLD") for PID %s but packet %d is not RAP !\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck)); + Bool switch_frame = gf_filter_pck_get_switch_frame(pck); + if (!sap_type && !switch_frame) { + GF_LOG(ctx->strict_cues ? GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("DASH cue found (sn %d - dts "LLD" - cts "LLD") for PID %s but packet %d is not a RAP nor a switch frame!\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck)); if (ctx->strict_cues) { gf_filter_pid_drop_packet(ds->ipid); gf_filter_pid_set_discard(ds->ipid, GF_TRUE); @@ -9298,7 +10077,6 @@ ds->set->starts_with_sap = sap_type; } - seg_over = GF_TRUE; if (ds == base_ds) { base_ds->adjusted_next_seg_start = cts; @@ -9319,6 +10097,8 @@ else if ( (base_ds->force_rep_end && gf_timestamp_greater_or_equal(cts, ds->timescale, base_ds->force_rep_end, base_ds->timescale) ) || (base_ds->clamped_dur.num && (cts + o_dur > ds->ts_offset + base_ds->clamped_dur.num * ds->timescale / base_ds->clamped_dur.den)) + //for non-audio stream, also check by duration (mostly for sap2 visual streams with broken timing) + || ((base_ds->clamped_dur.num && ds->stream_type!=GF_STREAM_AUDIO) && (dts - ds->ts_offset >= base_ds->clamped_dur.num * ds->timescale / base_ds->clamped_dur.den)) ) { if (!base_ds->period->period->duration && base_ds->force_rep_end) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Inputs duration do not match, %s truncated to %g duration\n", ds->src_url, ((Double)base_ds->force_rep_end)/base_ds->timescale )); @@ -9397,8 +10177,9 @@ } //we exceed segment duration - if segment was started, check if we need to stop segment //if segment was not started we insert the packet anyway - else if (!ds->sbound && ds->segment_started && gf_timestamp_greater_or_equal(cts + check_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale) ) { - + else if (!ds->sbound && ds->segment_started + && gf_timestamp_greater_or_equal(cts + check_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale) + ) { //we have a base (muxed rep) and it is not yet done, and we exceed estimated next seg start on base //wait for the base to be done as the next seg estimate may change if next segment duration is quite @@ -9411,8 +10192,7 @@ if (! ctx->sap) { seg_over = GF_TRUE; } - else if ((ds->stream_type==GF_STREAM_AUDIO) - && gf_timestamp_equal(cts + check_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale) + else if (ds->skip_sap && gf_timestamp_equal(cts + check_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale) ) { } @@ -9448,7 +10228,7 @@ ds->set->starts_with_sap = sap_type; } - //if sap2, silently move startWithSAP to 2 if previsouly 0,1 or 2 + //if sap2, silently move startWithSAP to 2 if previously 0, 1 or 2 if (sap_type == GF_FILTER_SAP_2) { if (ctx->sseg) ds->set->subsegment_starts_with_sap = MAX(ds->set->subsegment_starts_with_sap, sap_type); @@ -9520,7 +10300,9 @@ if (base_ds->nb_comp_done == base_ds->nb_comp) { dasher_flush_segment(ctx, base_ds, GF_FALSE); - seg_done = GF_TRUE; + //do not flush manifest in DASH+ssr until we get at least one fragment + if (!base_ds->set->ssr_mode) + force_flush_manifest = GF_TRUE; } break; } @@ -9536,8 +10318,8 @@ ds->est_first_cts_in_next_seg = ncts; ncts = gf_timestamp_rescale(ncts, ds->timescale, 1000); - if (ncts>base_ds->max_period_dur) - base_ds->max_period_dur = ncts; + if (ncts > base_ds->current_max_period_dur) + base_ds->current_max_period_dur = ncts; ds->last_cts = cts + (split_dur ? split_dur : dur); ds->last_dts = dts; @@ -9592,7 +10374,15 @@ dst = NULL; if (!ctx->do_index && !ctx->index_media_duration) { dst = gf_filter_pck_new_ref(ds->opid, 0, 0, pck); - if (!dst) return GF_OUT_OF_MEM; + if (!dst) { + if (ds && ds->rep) { + if (ds->set) + gf_list_del_item(ds->set->representations, ds->rep); + gf_mpd_representation_free(ds->rep); + ds->rep = NULL; + } + return GF_OUT_OF_MEM; + } //merge all props gf_filter_pck_merge_properties(pck, dst); @@ -9630,13 +10420,19 @@ gf_assert(gf_filter_pck_get_duration(pck) > split_dur_next); ds->rep->segment_list->use_split_dur = GF_TRUE; } + //low latency live edge has been added, flush MPD + if (ds->stl && ctx->asto) + force_flush_manifest = GF_TRUE; } //prev packet was split if (is_packet_split) { u64 diff=0; u8 dep_flags = gf_filter_pck_get_dependency_flags(pck); u64 ts = gf_filter_pck_get_cts(pck); + if (ts != GF_FILTER_NO_TS) { + //add ts_offset as cts already has ts_offset included (when using state) + ts += ds->ts_offset; cts += ds->first_cts; gf_assert(cts >= ts); diff = cts - ts; @@ -9644,7 +10440,12 @@ cts = ds->last_cts; } if (dst) { - gf_filter_pck_set_cts(dst, cts + ds->ts_offset); + GF_Fraction pck_orig_dur; + pck_orig_dur.num = (s32) split_dur_next; + pck_orig_dur.den = split_dur ? gf_filter_pck_get_duration(pck) : 0; + gf_filter_pck_set_property(dst, GF_PROP_PCK_ORIG_DUR, &PROP_FRAC(pck_orig_dur)); + + gf_filter_pck_set_cts(dst, cts); ts = gf_filter_pck_get_dts(pck); if (ts != GF_FILTER_NO_TS) @@ -9664,9 +10465,17 @@ //if split, adjust duration - this may happen on a split packet, if it covered 3 or more segments if (split_dur) { + GF_Fraction pck_orig_dur; u32 cumulated_split_dur = split_dur; - if (dst) + if (dst) { gf_filter_pck_set_duration(dst, split_dur); + + //original dur + pck_orig_dur.num = ds->split_dur_next; + pck_orig_dur.den = dur; + gf_filter_pck_set_property(dst, GF_PROP_PCK_ORIG_DUR, &PROP_FRAC(pck_orig_dur)); + } + //adjust dur cumulated_split_dur += (u32) (cts - orig_cts); gf_assert( dur > split_dur); @@ -9676,7 +10485,7 @@ } //remove NTP - if (dst && (ctx->ntp != DASHER_NTP_KEEP)) + if (dst && (ctx->ntp == DASHER_NTP_REM)) gf_filter_pck_set_property(dst, GF_PROP_PCK_SENDER_NTP, NULL); //change packet times @@ -9709,9 +10518,12 @@ ds->rep->first_tfdt_plus_one = 1 + gf_filter_pck_get_dts(dst); ds->rep->first_tfdt_timescale = ds->timescale; } + //send packet - if (dst) + if (dst) { gf_filter_pck_send(dst); + ctx->period_pck_sent = GF_TRUE; + } if (ctx->update_report>=0) ctx->update_report++; @@ -9758,7 +10570,7 @@ dasher_format_report(filter, ctx); - if (seg_done) { + if (force_flush_manifest) { Bool update_period = GF_FALSE; Bool update_manifest = GF_FALSE; if (ctx->purge_segments) update_period = GF_TRUE; @@ -9773,7 +10585,7 @@ else if (ctx->do_m3u8) { update_manifest = GF_TRUE; } - //we have a minimum ipdate period + //we have a minimum update period else if (ctx->mpd->minimum_update_period) { u64 diff = dasher_get_utc(ctx) - ctx->mpd->publishTime; if (diff >= ctx->mpd->minimum_update_period) @@ -9853,13 +10665,22 @@ } ctx->is_eos = GF_TRUE; gf_filter_pid_set_eos(ctx->opid); + + //Warn if no packets emitted but only on regular modes: + //no GHI generation + //no manifest-only generation + //no init-seg or manifest generation from GHI + if (!ctx->period_pck_sent && !ctx->do_index && !ctx->sigfrag + && (!ctx->from_index || (ctx->from_index==IDXMODE_SEG)) + ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Error: EOS found but no data sent\n")); + } } } return e; } - static void dasher_resume_subdur(GF_Filter *filter, GF_DasherCtx *ctx) { GF_FilterEvent evt; @@ -9873,7 +10694,7 @@ ds->rep = NULL; if ((ds->done==1) && !ctx->subdur && ctx->loop) {} else if (ds->reschedule) { - //we possibly dispatched end of stream on all outputs, we need to force unblockink to get called again + //we possibly dispatched end of stream on all outputs, we need to force unblocking to get called again gf_filter_pid_discard_block(ds->opid); continue; } @@ -9887,7 +10708,7 @@ GF_FEVT_INIT(evt, GF_FEVT_STOP, ds->ipid); gf_filter_pid_send_event(ds->ipid, &evt); - dasher_send_encode_hints(ctx, ds); + dasher_send_transport_hints(ctx, ds); GF_FEVT_INIT(evt, GF_FEVT_PLAY, ds->ipid); evt.play.speed = 1.0; if (!ctx->subdur || !ctx->loop) { @@ -9928,7 +10749,7 @@ return; if (!ctx->store_seg_states) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Received fragment size info event but no associated segment state\n")); + GF_LOG(ctx->do_m3u8 ? GF_LOG_ERROR : GF_LOG_DEBUG, GF_LOG_DASH, ("Dasher Received LL-HLS fragment size info event but no segment state\n")); return; } for (i=0; i<count; i++) { @@ -9948,11 +10769,14 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Received segment size info event but no pending segments\n")); return; } - sctx->frags = gf_realloc(sctx->frags, sizeof (GF_DASH_FragmentContext) * (sctx->nb_frags+1)); - if (!sctx->frags) { + void *new_frags = gf_realloc(sctx->frags, sizeof (GF_DASH_FragmentContext) * (sctx->nb_frags+1)); + if (!new_frags) { + gf_free(sctx->frags); + sctx->frags = NULL; sctx->nb_frags = 0; return; } + sctx->frags = (GF_DASH_FragmentContext *)new_frags; sctx->fragssctx->nb_frags.size = evt->frag_size.size; sctx->fragssctx->nb_frags.offset = evt->frag_size.offset; if (evt->frag_size.duration.den) { @@ -9963,9 +10787,29 @@ sctx->fragssctx->nb_frags.independent = evt->frag_size.independent; sctx->nb_frags++; + if (evt->frag_size.is_last) { sctx->llhls_done = GF_TRUE; - } else { + // SSR mode, set @k to the number of parts + if (ds->set->ssr_mode) { + if (ctx->stl) { + dasher_set_timeline_parts(ctx, ds, sctx); + } else { + GF_MPD_SegmentTemplate *tpl = ds->set->segment_template ? ds->set->segment_template : ds->rep->segment_template; + if (tpl) { + if (!tpl->nb_parts || (tpl->nb_parts != sctx->nb_frags)) { + if (tpl->nb_parts && !ds->done) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher PID %s uses SSR but varying number of sub-segments %d vs %d previously, consider re-encoding or using segment timeline\n", gf_filter_pid_get_name(ds->ipid), tpl->nb_parts, sctx->nb_frags)); + } + tpl->nb_parts = MAX(tpl->nb_parts, sctx->nb_frags); + } + } + } + } + ctx->force_hls_ll_manifest = GF_TRUE; + } + //if HLS or DASH without SSR, flush now. If dash with ssr we wait for last subsegment + else if (!ds->set->ssr_mode || ctx->do_m3u8) { ctx->force_hls_ll_manifest = GF_TRUE; } } @@ -9994,35 +10838,27 @@ return GF_TRUE; } + if (evt->base.type == GF_FEVT_NETWORK_HINT) { + if (evt->net_hint.sink_type == GF_4CC('M','A','B','R')) { + ctx->use_mabr = GF_TRUE; + } + } + if (evt->base.type == GF_FEVT_PLAY) { - ctx->is_playing = GF_TRUE; - if (!ctx->sfile && !ctx->stl && !ctx->use_cues) { - GF_FilterEvent anevt; - GF_FEVT_INIT(anevt, GF_FEVT_ENCODE_HINTS, NULL) - count = gf_list_count(ctx->pids); - for (i=0; i<count; i++) { - GF_DashStream *ds = gf_list_get(ctx->pids, i); - anevt.base.on_pid = ds->ipid; - switch (ctx->from_index) { - case IDXMODE_NONE: - anevt.encode_hints.intra_period = ds->dash_dur; - break; - case IDXMODE_SEG: - case IDXMODE_CHILD: - break; - case IDXMODE_ALL: - case IDXMODE_INIT: - case IDXMODE_MANIFEST: - anevt.encode_hints.gen_dsi_only = GF_TRUE; - break; - } - gf_filter_pid_send_event(ds->ipid, &anevt); - } + ctx->nb_playing++; + if (ctx->nb_playing>1) return GF_TRUE; + + //send transport hints even if segment timeline is used + count = gf_list_count(ctx->pids); + for (i=0; i<count; i++) { + GF_DashStream *ds = gf_list_get(ctx->pids, i); + dasher_send_transport_hints(ctx, ds); } return GF_FALSE; } if (evt->base.type == GF_FEVT_STOP) { - ctx->is_playing = GF_FALSE; + ctx->nb_playing--; + if (ctx->nb_playing) return GF_TRUE; return GF_FALSE; } @@ -10044,10 +10880,37 @@ if (ds->muxed_base) ds = ds->muxed_base; + if (evt->seg_size.is_init && evt->seg_size.base64_version) { + if (!ds->init_base_64) { + ds->init_base_64 = gf_strdup(evt->seg_size.base64_version); + u32 k; + for (k=0; k<count; k++) { + GF_DashStream *a_ds = gf_list_get(ctx->pids, k); + if (!a_ds->rep) continue; + if ((a_ds != ds) && (a_ds->muxed_base != ds)) continue; + a_ds->rep->init_base64 = ds->init_base_64; + + char **init_url_ptr = NULL; + if (a_ds->set->segment_template && a_ds->set->segment_template->initialization) { + init_url_ptr = &a_ds->set->segment_template->initialization; + } else if (a_ds->rep->segment_template && a_ds->rep->segment_template->initialization) { + init_url_ptr = &a_ds->rep->segment_template->initialization; + } + if (init_url_ptr) { + gf_free(*init_url_ptr); + *init_url_ptr = gf_strdup("data:"); + gf_dynstrcat(init_url_ptr, a_ds->rep->mime_type ? a_ds->rep->mime_type : "video/mp4" , NULL); + gf_dynstrcat(init_url_ptr, ";base64,", NULL); + gf_dynstrcat(init_url_ptr, ds->init_base_64, NULL); + } + } + } + } + if (ctx->store_seg_states && !evt->seg_size.is_init) { GF_DASH_SegmentContext *sctx = gf_list_pop_front(ds->pending_segment_states); if (!sctx || !ctx->nb_seg_url_pending) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Broken muxer, received segment size info event but no pending segments\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Broken muxer (PID#%u %s), received segment size info event but no pending segments\n", i, gf_filter_pid_get_name(ds->ipid))); return GF_TRUE; } ctx->nb_seg_url_pending--; @@ -10070,18 +10933,14 @@ break; //send file delete events - if (prev_sctx->llhls_mode>1) { + if (prev_sctx->llhls_mode==GF_DASH_LL_HLS_SF) { u32 k; for (k=0; k<prev_sctx->nb_frags; k++) { - GF_FilterEvent anevt; - char szPathGF_MAX_PATH; - sprintf(szPath, "%s.%d", prev_sctx->filepath, k+1); - GF_FEVT_INIT(anevt, GF_FEVT_FILE_DELETE, ds->opid); - anevt.file_del.url = szPath; - gf_filter_pid_send_event(ds->opid, &anevt); + s32 part_idx = k + (ds->set->ssr_mode || !gf_sys_is_test_mode() ? 0 : 1); + send_file_delete(ctx, ds, prev_sctx->filename, prev_sctx->filepath, part_idx); } } - prev_sctx->llhls_mode = 0; + prev_sctx->llhls_mode = GF_DASH_LL_HLS_OFF; } ctx->force_hls_ll_manifest = GF_TRUE; } @@ -10162,7 +11021,7 @@ GF_MPD_SegmentURL *url = gf_list_pop_front(ds->pending_segment_urls); if (!url || !ctx->nb_seg_url_pending) { if (!ds->done) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Broken muxer, received segment size info event but no pending segments\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Dasher Broken muxer (PID#%u %s), received segment size info event but no pending segments\n", i, gf_filter_pid_get_name(ds->ipid))); } return GF_TRUE; } @@ -10362,6 +11221,10 @@ if (!ctx->initext && (ctx->muxtype==DASHER_MUX_AUTO)) ctx->muxtype = DASHER_MUX_ISOM; + if (ctx->force_flush) { + ctx->sflush = SFLUSH_SINGLE; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher Option `force_flush` is deprecated and will soon be removed, use `sflush=single` instead\n")); + } if ((ctx->segdur.num <= 0) || !ctx->segdur.den) { ctx->segdur.num = 1; ctx->segdur.den = 1; @@ -10398,7 +11261,7 @@ ctx->sfile = GF_TRUE; } if (ctx->gencues) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher `sigfrag` and `gencues` options cannot be used together, disabling gencies\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Dasher `sigfrag` and `gencues` options cannot be used together, disabling gencues\n")); ctx->gencues = GF_FALSE; } } @@ -10459,6 +11322,7 @@ gf_list_del(ctx->next_period->streams); gf_free(ctx->next_period); if (ctx->out_path) gf_free(ctx->out_path); + if (ctx->out_path_alt) gf_free(ctx->out_path_alt); gf_list_del(ctx->postponed_pids); #ifndef GPAC_DISABLE_CRYPTO if (ctx->cinfo) gf_crypt_info_del(ctx->cinfo); @@ -10490,7 +11354,7 @@ CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, MPD_EXTS), CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, MPD_MIMES), {0}, - //anything else (not file, not AV and framed) in compressed format result in manifest PID + //anything else (not file, not AV and framed) in compressed format results in manifest PID //we cannot handle RAW format for such streams as these are in-memory data (scene graph, decoded text, etc ..) CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), @@ -10502,7 +11366,7 @@ CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, MPD_EXTS), CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, MPD_MIMES), {0}, - //anything else (not file and framed) result in media pids not file + //anything else (not file and framed) results in media pids not file CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_CODECID, GF_CODECID_NONE), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_UNFRAMED, GF_TRUE), @@ -10539,7 +11403,7 @@ "- on: enables it if same decoder configuration is possible\n" "- inband: moves decoder config inband if possible\n" "- both: inband and outband parameter sets\n" - "- pps: moves PPS and APS inband, keep VPS,SPS and DCI out of band (used for VVC RPR)\n" + "- pps: moves PPS and APS inband, keep VPS, SPS and DCI out of band (used for VVC RPR)\n" "- force: enables it even if only one representation\n" "- multi: uses multiple stsd entries in ISOBMFF", GF_PROP_UINT, "def", "def|off|on|inband|pps|both|force|multi", GF_FS_ARG_HINT_ADVANCED}, { OFFS(template), "template string to use to generate segment name", GF_PROP_STRING, NULL, NULL, 0}, @@ -10552,9 +11416,9 @@ "- webm: uses WebM format\n" "- ogg: uses OGG format\n" "- raw: uses raw media format (disables multiplexed representations)\n" - "- auto: guess format based on extension, default to mp4 if no extension", GF_PROP_UINT, "auto", "mp4|ts|mkv|webm|ogg|raw|auto", 0}, + "- auto: guesses format based on extension, defaults to mp4 if no extension is provided", GF_PROP_UINT, "auto", "mp4|ts|mkv|webm|ogg|raw|auto", 0}, { OFFS(rawsub), "use raw subtitle format instead of encapsulating in container", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_ADVANCED}, - { OFFS(asto), "availabilityStartTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(asto), "availabilityTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(profile), "target DASH profile. This will set default option values to ensure conformance to the desired profile. For MPEG-2 TS, only main and live are used, others default to main\n" "- auto: turns profile to live for dynamic and full for non-dynamic\n" "- live: DASH live profile, using segment template\n" @@ -10567,6 +11431,7 @@ "- dashif.ll: DASH IF low-latency profile (set UTC server to time.akamai.com if none set)" "", GF_PROP_UINT, "auto", "auto|live|onDemand|main|full|hbbtv1.5.live|dashavc264.live|dashavc264.onDemand|dashif.ll", 0 }, { OFFS(profX), "list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used. If starting with `+`, the profile string by default is erased and `+` is skipped", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(query), "query parameters to append for segment requests (Annex I)", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED }, { OFFS(cp), "content protection element location\n" "- set: in adaptation set element\n" "- rep: in representation element\n" @@ -10594,25 +11459,27 @@ { OFFS(refresh), "refresh rate for dynamic manifests, in seconds (a negative value sets the MPD duration, value 0 uses dash duration)", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(tsb), "time-shift buffer depth in seconds (a negative value means infinity)", GF_PROP_DOUBLE, "30", NULL, 0}, { OFFS(keep_segs), "do not delete segments no longer in time-shift buffer", GF_PROP_BOOL, "false", NULL, 0}, - { OFFS(subdur), "maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(ast), "set start date (as xs:date, e.g. YYYY-MM-DDTHH:MM:SSZ) for live mode. Default is now. !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !!", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(state), "path to file used to store/reload state info when simulating live. This is stored as a valid MPD with GPAC XML extensions", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(keep_ts), "do not shift timestamp when reloading a context", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(loop), "loop sources when dashing with subdur and state. If not set, a new period is created once the sources are over", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(subdur), "maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(split), "enable cloning samples for text/metadata/scene description streams, marking further clones as redundant", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(hlsc), "insert clock reference in variant playlist in live HLS", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(cues), "set cue file", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(strict_cues), "strict mode for cues, complains if splitting is not on SAP type 1/2/3 or if unused cue is found", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(strict_sap), "strict mode for sap\n" - "- off: ignore SAP types for PID other than video, enforcing _startsWithSAP=1_\n" - "- sig: same as -off() but keep _startsWithSAP_ to the true SAP value\n" + "- off: ignore SAP types for PID other than video, enforcing `AdaptationSet@startsWithSAP=1`\n" + "- sig: same as `-off` but keep `AdaptationSet@startsWithSAP` to the true SAP value\n" "- on: warn if any PID uses SAP 3 or 4 and switch to FULL profile\n" "- intra: ignore SAP types greater than 3 on all media types" , GF_PROP_UINT, "off", "off|sig|on|intra", GF_FS_ARG_HINT_EXPERT}, - { OFFS(subs_sidx), "number of subsegments per sidx. negative value disables sidx. Only used to inherit sidx option of destination", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(subs_sidx), "number of subsegments per sidx. Negative value disables sidx. Only used to inherit sidx option of destination", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(cmpd), "skip line feed and spaces in MPD XML for compactness", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(styp), "indicate the 4CC to use for styp boxes when using ISOBMFF output", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(dual), "indicate to produce both MPD and M3U files", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(segcts), "compute the segment number by dividing the first CTS by -segdur()", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(sigfrag), "use manifest generation only mode", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(_p_gentime), "pointer to u64 holding the ntp clock in ms of next DASH generation in live mode", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, { OFFS(_p_mpdtime), "pointer to u64 holding the mpd time in ms of the last generated segment", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, @@ -10626,7 +11493,10 @@ "- otherwise in dynamic mode without context, do not generate segments ahead of time", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(scope_deps), "scope PID dependencies to be within source. If disabled, PID dependencies will be checked across all input PIDs regardless of their sources", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(utcs), "URL to use as time server / UTCTiming source. Special value `inband` enables inband UTC (same as publishTime), special prefix `xsd@` uses xsDateTime schemeURI rather than ISO", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, - { OFFS(force_flush), "force generating a single segment for each input. This can be useful in batch mode when average source duration is known and used as segment duration but actual duration may sometimes be greater", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(sflush), "segment flush mode - see filter help:\n" + "- off: no specific actions\n" + "- single: force generating a single segment for each input\n" + "- end: skip loop detection and clamp duration adjustment at end of input, used for state mode", GF_PROP_UINT, "off", "off|single|end", GF_FS_ARG_HINT_EXPERT}, { OFFS(last_seg_merge), "force merging last segment if less than half the target duration", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(mha_compat), "adaptation set generation mode for compatible MPEG-H Audio profile\n" "- no: only generate the adaptation set for the main profile\n" @@ -10642,10 +11512,11 @@ { OFFS(cdur), "chunk duration for fragmentation modes", GF_PROP_FRACTION, "-1/1", NULL, GF_FS_ARG_HINT_HIDE}, { OFFS(hlsdrm), "cryp file info for HLS full segment encryption", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(hlsx), "list of string to append to master HLS header before variants with `'#foo','#bar=val'` added as `#foo \\n #bar=val`", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(hlsiv), "inject IV in variant HLS playlist`", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(ll_preload_hint), "inject preload hint for LL-HLS", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(ll_rend_rep), "inject rendition reports for LL-HLS", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(ll_part_hb), "user-defined part hold-back for LLHLS, negative value means 3 times max part duration in session", GF_PROP_DOUBLE, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, - { OFFS(ckurl), "set the ClearKey URL common to all encrypted streams (overriden by `CKUrl` pid property)", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(ckurl), "set the ClearKey URL common to all encrypted streams (overridden by `CKUrl` pid property)", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(hls_absu), "use absolute url in HLS generation using first URL in base()\n" "- no: do not use absolute URL\n" @@ -10667,7 +11538,7 @@ , GF_PROP_UINT, "no", "no|cmfc|cmf2", GF_FS_ARG_HINT_ADVANCED}, { OFFS(pswitch), "period switch control mode\n" "- single: change period if PID configuration changes\n" - "- force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or add insertion not using periodID)\n" + "- force: force period switch at each PID reconfiguration instead of absorbing PID reconfiguration (for splicing or ad insertion not using periodID)\n" "- stsd: change period if PID configuration changes unless new configuration was advertised in initial config", GF_PROP_UINT, "single", "single|force|stsd", GF_FS_ARG_HINT_EXPERT}, { OFFS(chain), "URL of next MPD for regular chaining", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(chain_fbk), "URL of fallback MPD", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -10681,6 +11552,12 @@ "- auto: default KID only injected if no key roll is detected (as per DASH-IF guidelines)" , GF_PROP_UINT, "auto", "off|on|auto", GF_FS_ARG_HINT_EXPERT}, { OFFS(tpl_force), "use template string as is without trying to add extension or solve conflicts in names", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(inband_event), "insert a default inband event stream in the DASH manifest", GF_PROP_BOOL, "false", NULL, 0 }, + { OFFS(ttml_agg), "force aggregation of TTML samples of a DASH segment into a single sample", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(evte_agg), "force aggregation of Event Track samples of a DASH segment into a single sample", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + + { OFFS(force_flush), "deprecated - use sflush instead", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(base64), "embed init segments in manifests as base64", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, {0} }; @@ -10688,7 +11565,7 @@ GF_FilterRegister DasherRegister = { .name = "dasher", - GF_FS_SET_DESCRIPTION("DASH and HLS segmenter") + GF_FS_SET_DESCRIPTION("DASH & HLS segmenter") GF_FS_SET_HELP( "This filter provides segmentation and manifest generation for MPEG-DASH and HLS formats.\n" "The segmenter currently supports:\n" @@ -10703,7 +11580,7 @@ "If you need per-frame real-time regulation on non-real-time inputs, insert a reframer(reframer) before to perform real-time regulation.\n" "EX gpac -i file.mp4 reframer:rt=on -o live.mpd:dmode=dynamic\n" "## Template strings\n" -"The segmenter uses templates to derive output file names, regardless of the DASH mode (even when templates are not used). " +"The segmenter uses templates to derive output file names and folder, regardless of the DASH mode (even when templates are not used). " "The default one is `$File$_dash` for ondemand and single file modes, and `$File$_$Number$` for separate segment files\n" "EX template=Great_$File$_$Width$_$Number$\n" "If input is `foo.mp4` with `640x360` video resolution, this will resolve in `Great_foo_640_$Number$` for the DASH template.\n" @@ -10715,6 +11592,7 @@ "- $RepresentationID$: replaced by representation name\n" "- $Time$: replaced by segment start time\n" "- $Bandwidth$: replaced by representation bandwidth.\n" +"- $SubNumber%%0Nd$: replaced by the segment number in the segment sequence, possibly prefixed with 0\n" "Note: these strings are not replaced in the manifest templates elements.\n" "\n" "Additional replacement strings (not DASH, not generic GPAC replacements but may occur multiple times in template):\n" @@ -10728,6 +11606,8 @@ "- $FS$ (FileSuffix): replaced by `_trackN` in case the input is an AV multiplex, or kept empty otherwise\n" "Note: these strings are replaced in the manifest templates elements.\n" "\n" +"Other properties can also be set, see below.\n" +"\n" "## PID assignment and configuration\n" "To assign PIDs into periods and adaptation sets and configure the session, the segmenter looks for the following properties on each input PID:\n" "- `Representation`: assigns representation ID to input PID. If not set, the default behavior is to have each media component in different adaptation sets. Setting the `Representation` allows explicit multiplexing of the source(s)\n" @@ -10799,6 +11679,8 @@ "This may result in temporary mismatches between segment/part size currently received versus size as advertized in manifest.\n" "When -seg_sync() is enabled, the segmenter will wait for the last byte of the fragment/segment to be pushed before announcing a new segment in the manifest(s). This can however slightly increase the latency in MPEG-DASH low-latency.\n" "\n" +"When -sflush() is set to `single`, segmentation is skipped and a single segment is generated per input.\n" +"\n" "## Dynamic (real-time live) Mode\n" "The dasher does not perform real-time regulation by default.\n" "For regular segmentation, you should enable segment regulation -sreg() if your sources are not real-time.\n" @@ -10891,7 +11773,7 @@ "This mode can be used to pre-segment the streams for later processing that must take place before final dashing.\n" "EX gpac -i source.mp4 dasher:gencues cecrypt:cfile=roll_seg.xml -o live.mpd\n" "This will allow the encrypter to locate dash boundaries and roll keys at segment boundaries.\n" -"EX gpac -i s1.mp4 -i s2.mp4:#CryptInfo=clear:#Period=3 -i s3.mp4:#Period=3 dasher:gencues cecrypt:cfile=roll_period.xml -o live.mpd\n" +"EX gpac -i s1.mp4 -i s2.mp4:#CryptInfo=clear:#Period=2 -i s3.mp4:#Period=3 dasher:gencues cecrypt:cfile=roll_period.xml -o live.mpd\n" "If the DRM file uses `keyRoll=period`, this will generate:\n" "- first period crypted with one key\n" "- second period clear\n" @@ -10904,6 +11786,32 @@ "This will trash the manifest and open `mypipe` as destination for the muxer result.\n" "Warning: Options for segment destination cannot be set through the -template(), global options must be used.\n" "\n" +"## Batch Operations\n" +"The segmentation can be performed in multiple calls using a DASH context set with -state().\n" +"Between calls, the PIDs are reassigned by checking that the PID ID match between the calls and:\n" +"- the input file names match between the calls\n" +"- or the representation ID (and period ID if specified) match between the calls\n" +"\n" +"If a PID is not matched, it will be assigned to a new period.\n" +"\n" +"The default behaviour assume that the same inputs are used for segmentation and rebuilds a contiguous timeline at each new file start.\n" +"If the inputs change but form a continuous timeline, -keep_ts)() must be used to skip timeline reconstruction.\n" +"\n" +"The inputs will be segmented for a duration of -subdur() if set, otherwise the input media duration.\n" +"When inputs are over, they are restarted if -loop() is set otherwise a new period is created.\n" +"To avoid this behaviour, the -sflush() option should be set to `end` or `single`, indicating that further sources for the same representations will be added in subsequent calls. When -sflush() is not `off`, the -loop() option is ignored.\n" +"\n" +"EX gpac -i SRC -o dash.mpd:segdur=2:state=CTX && gpac -i SRC -o dash.mpd:segdur=2:state=CTX\n" +"This will generate all dash segments for `SRC` (last one possibly shorter) and create a new period at end of input.\n" +"EX gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:loop\n" +"This will generate all dash segments for `SRC` and restart `SRC` to fill-up last segment.\n" +"EX gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end && gpac -i SRC -o dash.mpd:segdur=2:state=CTX:sflush=end\n" +"This will generate all dash segments for `SRC` without looping/closing the period at end of input. Timestamps in the second call will be rewritten to be contiguous with timestamp at end of first call.\n" +"EX gpac -i SRC1 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts && gpac -i SRC2 -o dash.mpd:segdur=2:state=CTX:sflush=end:keep_ts\n" +"This will generate all dash segments for `SRC1` without looping/closing the period at end of input, then for `SRC2`. Timestamps of the sources will not be rewritten.\n" +"\n" +"Note: The default behaviour of MP4Box `-dash-ctx` option is to set the -loop() to true.\n" +"\n" "## Output redirecting\n" "When loaded implicitly during link resolution, the dasher will only link its outputs to the target sink\n" "EX gpac -i SRC -o URL1:OPTS1 -o URL2:OPTS1\n" @@ -10937,6 +11845,7 @@ "- DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example\n" "- LLHLS: identifies LLHLS is used; the multiplexer must send fragment size events back to the dasher, and set `LLHLSFragNum` on the first packet of each fragment\n" "- SegSync: indicates that fragments/segments must be completely flushed before sending back size events\n" +"- InitBase64: indicates that the base64-encoded init segment must be set in the init segment size event\n" ) .private_size = sizeof(GF_DasherCtx), .args = DasherArgs, @@ -10947,6 +11856,7 @@ .configure_pid = dasher_configure_pid, .process = dasher_process, .process_event = dasher_process_event, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/dec_ac52.c -> gpac-26.02.0.tar.gz/src/filters/dec_ac52.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2021 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AC3 liba52 decoder filter @@ -286,7 +286,8 @@ SETCAPS(A52DecCaps), .configure_pid = a52dec_configure_pid, .process = a52dec_process, - .finalize = a52dec_finalize + .finalize = a52dec_finalize, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_bifs.c -> gpac-26.02.0.tar.gz/src/filters/dec_bifs.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / BIFS decoder filter @@ -277,6 +277,7 @@ .process = bifs_dec_process, .configure_pid = bifs_dec_configure_pid, .process_event = bifs_dec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif //!defined(GPAC_DISABLE_BIFS) && !defined(GPAC_DISABLE_COMPOSITOR)
View file
gpac-2.4.0.tar.gz/src/filters/dec_cc.c -> gpac-26.02.0.tar.gz/src/filters/dec_cc.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / Closed captions decode filter @@ -62,10 +62,22 @@ { GF_FilterPid *ipid; GF_FilterPid *opid; + + // override gf_filter_*() calls for testability + GF_FilterPacket* (*pck_new_alloc)(GF_FilterPid *pid, u32 data_size, u8 **data); + GF_Err (*pck_truncate)(GF_FilterPacket *pck, u32 size); + GF_Err (*pck_send)(GF_FilterPacket *pck); + + u32 field; u32 cctype; u32 nalu_size_len; GF_List *cc_queue; + /*aggregation mode for dispatch*/ + u32 agg; + u8 txtdata2/*double for aggregation*/*CAPTION_FRAME_TEXT_BYTES+1; + u32 txtlen; + u32 timescale; #ifdef GPAC_HAS_LIBCAPTION caption_frame_t *ccframe; @@ -182,9 +194,96 @@ return GF_OK; } +static GF_Err ccdec_post(CCDecCtx *ctx, u32 size, u64 ts) +{ + u8 *output; + GF_FilterPacket *pck = ctx->pck_new_alloc(ctx->opid, size+1, &output); + if (!pck) return GF_OUT_OF_MEM; + memcpy(output, ctx->txtdata, size); + outputsize = 0; + ctx->pck_truncate(pck, size); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_cts(pck, ctx->last_ts_plus_one-1); + gf_filter_pck_set_duration(pck, (u32) (ts - (ctx->last_ts_plus_one-1))); + ctx->last_ts_plus_one = ts+1; + + ctx->pck_send(pck); + + return GF_OK; +} + u32 gf_m4v_parser_get_obj_type(GF_M4VParser *m4v); void gf_m4v_parser_set_inspect(GF_M4VParser *m4v); +static int find_last_separator(const char *input) +{ + const char *space = strrchr(input, ' '); + const char *newline = strrchr(input, '\n'); + if (!space && !newline) + return -1; + if (!space || space < newline) + return (int) (newline-input+1)/*include the '\n'*/; + else + return (int) (space-input); +} + +static Bool same_crc(CCDecCtx *ctx, u32 size, u64 ts) +{ + u32 crc = gf_crc_32(ctx->txtdata+ctx->txtlen, size); + if (crc!=ctx->cc_last_crc) { + ctx->cc_last_crc = crc; + } else { + size=0; + } + if (!size) { + ctx->last_ts_plus_one = ts+1; + return GF_TRUE; + } + return GF_FALSE; +} + +static GF_Err text_aggregate_and_post(CCDecCtx *ctx, u32 size, u64 ts) +{ + // look for overlaps if case we aggregate: we couldn't rely on libcaption's popon/painton/rollup reliably + Bool overlap = GF_FALSE; + if (ctx->agg>0 && ctx->txtlen>0) { + if (!strncmp(ctx->txtdata, ctx->txtdata+ctx->txtlen, ctx->txtlen)) { + memmove(ctx->txtdata, ctx->txtdata+ctx->txtlen, size+1); + overlap = GF_TRUE; + } else if (size>1) { + char last = ctx->txtdatactx->txtlen; + ctx->txtdatactx->txtlen = 0; + ccdec_post(ctx, ctx->txtlen, ts); + ctx->txtdata0 = last; + memmove(ctx->txtdata+1, ctx->txtdata+ctx->txtlen+1, size/*includes termination*/); + ctx->txtlen = 0; + } + } + + if (same_crc(ctx, size, ts)) + return GF_OK; + + if (ctx->agg == 0) { + // no aggregation: dispatch now + return ccdec_post(ctx, size, ts); + } else { + int len = -1; + if ( (len = find_last_separator(ctx->txtdata+ctx->txtlen)) >= 0 ) { + GF_Err e = ccdec_post(ctx, ctx->txtlen+len, ts); + if (e) return e; + } + + if (overlap) { + ctx->txtlen = size; + } else { + ctx->txtlen += size; + } + } + + return GF_OK; +} + static GF_Err ccdec_flush_queue(CCDecCtx *ctx) { CCItem *cc = gf_list_pop_front(ctx->cc_queue); @@ -214,10 +313,10 @@ int valid; cea708_cc_type_t type; uint16_t cc_data = cea708_cc_data(&scc.user_data, i, &valid, &type); + Bool use_field1 = (ctx->field == 1 && (cc_type_ntsc_cc_field_1 == type)); + Bool use_field2 = (ctx->field == 2 && (cc_type_ntsc_cc_field_2 == type)); - if (valid - && ((cc_type_ntsc_cc_field_1 == type) || (cc_type_ntsc_cc_field_2 == type)) - ) { + if (valid && (use_field1 || use_field2)) { status = libcaption_status_update(status, caption_frame_decode(ctx->ccframe, cc_data, timestamp)); if (status == LIBCAPTION_READY) { dump_frame = GF_TRUE; @@ -229,32 +328,9 @@ if (!dump_frame) return GF_OK; - u8 txtdataCAPTION_FRAME_TEXT_BYTES+1; - u32 size = (u32) caption_frame_to_text(ctx->ccframe, txtdata); - u32 crc = gf_crc_32(txtdata, size); - if (crc!=ctx->cc_last_crc) { - ctx->cc_last_crc = crc; - } else { - size=0; - } - if (!size) { - ctx->last_ts_plus_one = ts+1; - return GF_OK; - } - u8 *output; - GF_FilterPacket *pck = gf_filter_pck_new_alloc(ctx->opid, size+1, &output); - if (!pck) return GF_OUT_OF_MEM; - memcpy(output, txtdata, size); - outputsize = 0; - gf_filter_pck_truncate(pck, size); - gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); - gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); - gf_filter_pck_set_cts(pck, ctx->last_ts_plus_one-1); - gf_filter_pck_set_duration(pck, (u32) (ts - ctx->last_ts_plus_one+1)); - ctx->last_ts_plus_one = ts+1; - - gf_filter_pck_send(pck); - return GF_OK; + u32 size = (u32) caption_frame_to_text(ctx->ccframe, ctx->txtdata+ctx->txtlen); + + return text_aggregate_and_post(ctx, size, ts); } static GF_Err ccdec_queue_data(CCDecCtx *ctx, u64 ts, u8 *data, u32 max_size, Bool m2v, Bool keep_data) @@ -322,6 +398,14 @@ return GF_OK; } +static void ccdec_flush(CCDecCtx *ctx) +{ + if (ctx->agg == 0) + return; + + if (strlen(ctx->txtdata)) + ccdec_post(ctx, (u32) strlen(ctx->txtdata), ctx->last_ts_plus_one); +} GF_Err ccdec_process(GF_Filter *filter) { @@ -336,6 +420,7 @@ while (gf_list_count(ctx->cc_queue)) { ccdec_flush_queue(ctx); } + ccdec_flush(ctx); gf_filter_pid_set_eos(ctx->opid); return GF_EOS; } @@ -482,7 +567,7 @@ i=0; if (sei_type == 4) { - //queuue + //queue u32 pos = (u32) gf_bs_get_position(ctx->bs); u32 country_code = gf_bs_read_u8(ctx->bs); i++; @@ -524,6 +609,9 @@ static GF_Err ccdec_initialize(GF_Filter *filter) { CCDecCtx *ctx = gf_filter_get_udta(filter); + ctx->pck_new_alloc = gf_filter_pck_new_alloc; + ctx->pck_truncate = gf_filter_pck_truncate; + ctx->pck_send = gf_filter_pck_send; ctx->cc_queue = gf_list_new(); if (!ctx->cc_queue) return GF_OUT_OF_MEM; return GF_OK; @@ -572,6 +660,16 @@ CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), }; +#define OFFS(_n) #_n, offsetof(CCDecCtx, _n) +static const GF_FilterArgs CCDecArgs = +{ + { OFFS(field), "field to decode", GF_PROP_UINT, "1", NULL, 0}, + { OFFS(agg), "output aggregation mode\n" + "- none: forward data as decoded (default)\n" + "- word: aggregate words (separated by a space)", GF_PROP_UINT, "none", "none|word", 0}, + {0} +}; + GF_FilterRegister CCDecRegister = { .name = "ccdec", GF_FS_SET_DESCRIPTION("Closed-Caption decoder") @@ -579,6 +677,7 @@ "Supported video media types are MPEG2, AVC, HEVC, VVC and AV1 streams.\n" "\nOnly a subset of CEA 608/708 is supported.") .private_size = sizeof(CCDecCtx), + .args = CCDecArgs, .flags = GF_FS_REG_EXPLICIT_ONLY, SETCAPS(CCDecCaps), .initialize = ccdec_initialize, @@ -586,6 +685,7 @@ .process = ccdec_process, .configure_pid = ccdec_configure_pid, .process_event = ccdec_process_event, + .hint_class_type = GF_FS_CLASS_SUBTITLE }; const GF_FilterRegister *ccdec_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dec_faad.c -> gpac-26.02.0.tar.gz/src/filters/dec_faad.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AAC FAAD2 decoder filter @@ -459,6 +459,7 @@ .configure_pid = faaddec_configure_pid, .finalize = faaddec_finalize, .process = faaddec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_img.c -> gpac-26.02.0.tar.gz/src/filters/dec_img.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / libjpeg and libpng decoder filter @@ -66,6 +66,7 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_RAW )); //declare a default pixel format - this avoids bad reconfigurations of ffavf gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, & PROP_UINT( GF_PIXEL_RGB )); + ctx->pixel_format = GF_PIXEL_RGB; if (ctx->codecid==GF_CODECID_JPEG) { gf_filter_set_name(filter, "imgdec:libjpeg"); @@ -172,6 +173,7 @@ SETCAPS(ImgDecCaps), .configure_pid = imgdec_configure_pid, .process = imgdec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; const GF_FilterRegister *imgdec_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dec_j2k.c -> gpac-26.02.0.tar.gz/src/filters/dec_j2k.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2022 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / openjpeg2k decoder filter @@ -90,6 +90,7 @@ return GF_NOT_SUPPORTED; p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); +skip_dsi: if (p && p->value.data.ptr && p->value.data.size) { GF_BitStream *bs; u32 d4cc; @@ -117,6 +118,8 @@ } gf_bs_skip_bytes(bs, bsize-8); } + } else if (d4cc==GF_4CC('j','2','k','H')) { + dsi_ok=GF_FALSE; } else { dsi_ok=GF_TRUE; } @@ -128,9 +131,10 @@ } gf_bs_del(bs); + //unrecognized DSI, setup from PID info if (!dsi_ok) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("OpenJPEG Broken decoder config in j2k stream, cannot decode\n")); - return GF_NON_COMPLIANT_BITSTREAM; + p = NULL; + goto skip_dsi; } ctx->out_size = ctx->width * ctx->height * ctx->nb_comp /* * ctx->bpp / 8 */; @@ -231,46 +235,46 @@ static OPJ_SIZE_T j2kdec_stream_read(void *out_buffer, OPJ_SIZE_T nb_bytes, void *user_data) { - OJP2Frame *frame = user_data; - u32 remain; - if (frame->pos == frame->len) return (OPJ_SIZE_T)-1; - remain = frame->len - frame->pos; - if (nb_bytes > remain) nb_bytes = remain; - memcpy(out_buffer, frame->data + frame->pos, nb_bytes); - frame->pos += (u32) nb_bytes; - return nb_bytes; + OJP2Frame *frame = user_data; + u32 remain; + if (frame->pos == frame->len) return (OPJ_SIZE_T)-1; + remain = frame->len - frame->pos; + if (nb_bytes > remain) nb_bytes = remain; + memcpy(out_buffer, frame->data + frame->pos, nb_bytes); + frame->pos += (u32) nb_bytes; + return nb_bytes; } static OPJ_OFF_T j2kdec_stream_skip(OPJ_OFF_T nb_bytes, void *user_data) { - OJP2Frame *frame = user_data; - if (!user_data) return 0; - - if (nb_bytes < 0) { - if (frame->pos == 0) return (OPJ_SIZE_T)-1; - if (nb_bytes + (s32) frame->pos < 0) { - nb_bytes = -frame->pos; - } - } else { - u32 remain; - if (frame->pos == frame->len) { - return (OPJ_SIZE_T)-1; - } - remain = frame->len - frame->pos; - if (nb_bytes > remain) { - nb_bytes = remain; - } - } - frame->pos += (u32) nb_bytes; - return nb_bytes; + OJP2Frame *frame = user_data; + if (!user_data) return 0; + + if (nb_bytes < 0) { + if (frame->pos == 0) return (OPJ_SIZE_T)-1; + if (nb_bytes + (s32) frame->pos < 0) { + nb_bytes = -frame->pos; + } + } else { + u32 remain; + if (frame->pos == frame->len) { + return (OPJ_SIZE_T)-1; + } + remain = frame->len - frame->pos; + if (nb_bytes > remain) { + nb_bytes = remain; + } + } + frame->pos += (u32) nb_bytes; + return nb_bytes; } static OPJ_BOOL j2kdec_stream_seek(OPJ_OFF_T nb_bytes, void *user_data) { - OJP2Frame *frame = user_data; - if (nb_bytes < 0 || nb_bytes > frame->pos) return OPJ_FALSE; - frame->pos = (u32)nb_bytes; - return OPJ_TRUE; + OJP2Frame *frame = user_data; + if (nb_bytes < 0 || nb_bytes > frame->pos) return OPJ_FALSE; + frame->pos = (u32)nb_bytes; + return OPJ_TRUE; } #endif @@ -279,7 +283,7 @@ { u32 i, w, wr, h, hr, wh, size, pf; u8 *data, *buffer; - opj_dparameters_t parameters; /* decompression parameters */ + opj_dparameters_t *parameters; /* decompression parameters */ #if OPENJP2 s32 res; opj_codec_t *codec = NULL; @@ -313,8 +317,10 @@ start_offset = 8; } + GF_SAFEALLOC(parameters, opj_dparameters_t); + if (!parameters) return GF_OUT_OF_MEM; /* set decoding parameters to default values */ - opj_set_default_decoder_parameters(¶meters); + opj_set_default_decoder_parameters(parameters); #if OPENJP2 codec = opj_create_decompress(OPJ_CODEC_J2K); @@ -325,17 +331,17 @@ if (res) res = opj_set_warning_handler(codec, warning_callback, NULL); if (res) res = opj_set_error_handler(codec, error_callback, NULL); - if (res) res = opj_setup_decoder(codec, ¶meters); + if (res) res = opj_setup_decoder(codec, parameters); stream = opj_stream_default_create(OPJ_STREAM_READ); - opj_stream_set_read_function(stream, j2kdec_stream_read); - opj_stream_set_skip_function(stream, j2kdec_stream_skip); - opj_stream_set_seek_function(stream, j2kdec_stream_seek); - ojp2frame.data = data+start_offset; - ojp2frame.len = size-start_offset; - ojp2frame.pos = 0; - opj_stream_set_user_data(stream, &ojp2frame, NULL); - opj_stream_set_user_data_length(stream, ojp2frame.len); + opj_stream_set_read_function(stream, j2kdec_stream_read); + opj_stream_set_skip_function(stream, j2kdec_stream_skip); + opj_stream_set_seek_function(stream, j2kdec_stream_seek); + ojp2frame.data = data+start_offset; + ojp2frame.len = size-start_offset; + ojp2frame.pos = 0; + opj_stream_set_user_data(stream, &ojp2frame, NULL); + opj_stream_set_user_data_length(stream, ojp2frame.len); if (res) res = opj_read_header(stream, codec, &image); if (res) res = opj_set_decode_area(codec, image, 0, 0, image->x1, image->y1); @@ -363,13 +369,14 @@ opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, stderr); /* setup the decoder decoding parameters using the current image and user parameters */ - opj_setup_decoder(dinfo, ¶meters); + opj_setup_decoder(dinfo, parameters); cio = opj_cio_open((opj_common_ptr)dinfo, data+start_offset, size-start_offset); /* decode the stream and fill the image structure */ image = opj_decode_with_info(dinfo, cio, &cinfo); #endif + gf_free(parameters); if (!image) { #if OPENJP2 opj_stream_destroy(stream); @@ -616,6 +623,7 @@ .initialize = j2kdec_initialize, .configure_pid = j2kdec_configure_pid, .process = j2kdec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_laser.c -> gpac-26.02.0.tar.gz/src/filters/dec_laser.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / LASeR decoder filter @@ -260,6 +260,7 @@ .process = lsrdec_process, .configure_pid = lsrdec_configure_pid, .process_event = lsrdec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif //!defined(GPAC_DISABLE_LASER) && !defined(GPAC_DISABLE_COMPOSITOR)
View file
gpac-2.4.0.tar.gz/src/filters/dec_mad.c -> gpac-26.02.0.tar.gz/src/filters/dec_mad.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2022 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / MP3 libmad decoder filter @@ -366,6 +366,7 @@ .finalize = maddec_finalize, .configure_pid = maddec_configure_pid, .process = maddec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_mediacodec.c -> gpac-26.02.0.tar.gz/src/filters/dec_mediacodec.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / mediacodec decoder filter @@ -104,20 +104,20 @@ #if 0 u8 sdkInt() { - char sdk_str3 = "0"; - //__system_property_get("ro.build.version.sdk", sdk_str, "0"); - return atoi(sdk_str); + char sdk_str3 = "0"; + //__system_property_get("ro.build.version.sdk", sdk_str, "0"); + return atoi(sdk_str); } #endif void mcdec_init_media_format(GF_MCDecCtx *ctx, AMediaFormat *format) { - AMediaFormat_setString(ctx->format, AMEDIAFORMAT_KEY_MIME, ctx->mime); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_WIDTH, ctx->width); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_HEIGHT, ctx->height); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_STRIDE, ctx->stride); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, ctx->width * ctx->height); + AMediaFormat_setString(ctx->format, AMEDIAFORMAT_KEY_MIME, ctx->mime); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_WIDTH, ctx->width); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_HEIGHT, ctx->height); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_STRIDE, ctx->stride); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, ctx->width * ctx->height); } @@ -200,7 +200,7 @@ break; } ctx->frame_size_changed = GF_TRUE; - return GF_OK; + return GF_OK; } @@ -322,25 +322,25 @@ } ctx->frame_size_changed = GF_TRUE; - return GF_OK; + return GF_OK; } static GF_Err mcdec_init_decoder(GF_MCDecCtx *ctx) { - GF_Err err; + GF_Err err; - ctx->format = AMediaFormat_new(); + ctx->format = AMediaFormat_new(); - if(!ctx->format) { + if(!ctx->format) { GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaFormat_new failed\n")); - return GF_FILTER_NOT_FOUND; - } + return GF_FILTER_NOT_FOUND; + } ctx->sar.num = ctx->sar.den = 0; - ctx->pix_fmt = GF_PIXEL_NV12; + ctx->pix_fmt = GF_PIXEL_NV12; - switch (ctx->codecid) { + switch (ctx->codecid) { case GF_CODECID_AVC : err = mcdec_init_avc_dec(ctx); break; @@ -352,17 +352,17 @@ break; default: return GF_NOT_SUPPORTED; - } + } - if (err != GF_OK) { - return err; - } + if (err != GF_OK) { + return err; + } - ctx->dequeue_timeout = 5000; - ctx->stride = ctx->width; - mcdec_init_media_format(ctx, ctx->format); + ctx->dequeue_timeout = 5000; + ctx->stride = ctx->width; + mcdec_init_media_format(ctx, ctx->format); - if (!ctx->codec) { + if (!ctx->codec) { char *decoder_name = mcdec_find_decoder(ctx->mime, ctx->width, ctx->height, &ctx->is_adaptive); if(!decoder_name) return GF_PROFILE_NOT_SUPPORTED; @@ -370,13 +370,13 @@ gf_free(decoder_name); } - if (!ctx->codec) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaCodec_createDecoderByType failed\n")); - return GF_FILTER_NOT_FOUND; - } + if (!ctx->codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaCodec_createDecoderByType failed\n")); + return GF_FILTER_NOT_FOUND; + } - if (ctx->disable_gl) { - ctx->surface_rendering = 0; + if (ctx->disable_gl) { + ctx->surface_rendering = 0; } else if (!ctx->window) { if(mcdec_create_surface(ctx->tex_id, &ctx->window, &ctx->surface_rendering, &ctx->surfaceTex) != GF_OK) return GF_BAD_PARAM; @@ -385,16 +385,16 @@ //TODO add support for crypto if( AMediaCodec_configure(ctx->codec, ctx->format, (ctx->surface_rendering) ? ctx->window : NULL, NULL, 0) != AMEDIA_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaCodec_configure failed\n")); - return GF_BAD_PARAM; - } + return GF_BAD_PARAM; + } - if( AMediaCodec_start(ctx->codec) != AMEDIA_OK){ - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC , ("MCDec AMediaCodec_start failed\n")); - return GF_BAD_PARAM; - } + if( AMediaCodec_start(ctx->codec) != AMEDIA_OK){ + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC , ("MCDec AMediaCodec_start failed\n")); + return GF_BAD_PARAM; + } ctx->inputEOS = GF_FALSE; - ctx->outputEOS = GF_FALSE; + ctx->outputEOS = GF_FALSE; GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("MCDec Video size: %d x %d\n", ctx->width, ctx->height)); @@ -552,7 +552,7 @@ Bool do_reset=GF_FALSE; u32 i; GF_FilterPid *base_pid = NULL; - GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); if (is_remove) { gf_list_del_item(ctx->streams, pid); @@ -639,22 +639,22 @@ ctx->codecid = codecid; - if (do_reset && ctx->codec) { - AMediaCodec_delete(ctx->codec); - ctx->codec = NULL; - mcdec_reset_ps_list(ctx->SPSs); - mcdec_reset_ps_list(ctx->VPSs); - mcdec_reset_ps_list(ctx->PPSs); + if (do_reset && ctx->codec) { + AMediaCodec_delete(ctx->codec); + ctx->codec = NULL; + mcdec_reset_ps_list(ctx->SPSs); + mcdec_reset_ps_list(ctx->VPSs); + mcdec_reset_ps_list(ctx->PPSs); - if (ctx->format) AMediaFormat_delete(ctx->format); - ctx->format = NULL; + if (ctx->format) AMediaFormat_delete(ctx->format); + ctx->format = NULL; } - if (!ctx->tex_id) - glGenTextures(1, &ctx->tex_id); + if (!ctx->tex_id) + glGenTextures(1, &ctx->tex_id); //check AVC config - if (ctx->codecid == GF_CODECID_AVC) { + if (ctx->codecid == GF_CODECID_AVC) { GF_AVCConfig *cfg; GF_NALUFFParam *slc; @@ -683,7 +683,7 @@ ctx->nalu_size_length = cfg->nal_unit_size; gf_odf_avc_cfg_del(cfg); - } + } else if (codecid == GF_CODECID_MPEG4_PART2) { GF_M4VDecSpecInfo vcfg; gf_m4v_get_config(dcd->value.data.ptr, dcd->value.data.size, &vcfg); @@ -691,21 +691,18 @@ ctx->height = vcfg.height; ctx->out_size = ctx->width*ctx->height*3/2; ctx->pix_fmt = GF_PIXEL_NV12; - } + } else if (codecid == GF_CODECID_HEVC) { ctx->mime = "video/hevc"; - GF_HEVCConfig *hvcc; - GF_NALUFFParam *sl; - HEVCState hevc; - u32 j; - - memset(&hevc, 0, sizeof(HEVCState)); + GF_HEVCConfig *hvcc; + GF_NALUFFParam *sl; + u32 j; - hvcc = gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE); - if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; - ctx->nalu_size_length = hvcc->nal_unit_size; + hvcc = gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + ctx->nalu_size_length = hvcc->nal_unit_size; - for (i=0; i< gf_list_count(hvcc->param_array); i++) { + for (i=0; i< gf_list_count(hvcc->param_array); i++) { GF_NALUFFParamArray *ar = (GF_NALUFFParamArray *)gf_list_get(hvcc->param_array, i); for (j=0; j< gf_list_count(ar->nalus); j++) { sl = (GF_NALUFFParam *)gf_list_get(ar->nalus, j); @@ -718,19 +715,19 @@ mcdec_register_hevc_param_set(ctx, sl->data, sl->size, MCDEC_PPS); } } - } + } //activate first VPS/SPS/PPS by default - sl = gf_list_get(ctx->VPSs, 0); - if (sl) ctx->active_vps = sl->id; + sl = gf_list_get(ctx->VPSs, 0); + if (sl) ctx->active_vps = sl->id; - sl = gf_list_get(ctx->SPSs, 0); - if (sl) ctx->active_sps = sl->id; + sl = gf_list_get(ctx->SPSs, 0); + if (sl) ctx->active_sps = sl->id; - sl = gf_list_get(ctx->PPSs, 0); - if (sl) ctx->active_pps = sl->id; + sl = gf_list_get(ctx->PPSs, 0); + if (sl) ctx->active_pps = sl->id; - gf_odf_hevc_cfg_del(hvcc); - } + gf_odf_hevc_cfg_del(hvcc); + } if (ctx->codec) return GF_OK; return mcdec_init_decoder(ctx); } @@ -756,6 +753,7 @@ u32 i; u8 *ptr = inBuffer; u32 nal_size; + u32 nb_nalsize_zero=0; GF_Err e = GF_OK; GF_BitStream *bs = NULL; @@ -777,6 +775,15 @@ for (i = 0; i<ctx->nalu_size_length; i++) { nal_size = (nal_size << 8) + ((u8)ptri); } + if (!nal_size) { + if (nb_nalsize_zero) { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + nb_nalsize_zero++; + } else { + nb_nalsize_zero=0; + } ptr += ctx->nalu_size_length; gf_bs_write_u32(bs, 1); @@ -946,16 +953,16 @@ ssize_t inIndex = AMediaCodec_dequeueInputBuffer(ctx->codec, ctx->dequeue_timeout); if (inIndex >= 0) { - size_t inSize; - u32 flags = 0; - Bool do_free = GF_TRUE; + size_t inSize; + u32 flags = 0; + Bool do_free = GF_TRUE; u8 *in_data=NULL; u32 in_data_size=0; - u8 *buffer = (u8 *)AMediaCodec_getInputBuffer(ctx->codec, inIndex, &inSize); + u8 *buffer = (u8 *)AMediaCodec_getInputBuffer(ctx->codec, inIndex, &inSize); - //rewrite input from isobmf to annexB - this will write the param sets if needed - if (in_buffer) { - mcdec_rewrite_annex_b(ctx, in_buffer, in_buffer_size, &in_data, &in_data_size); + //rewrite input from isobmf to annexB - this will write the param sets if needed + if (in_buffer) { + mcdec_rewrite_annex_b(ctx, in_buffer, in_buffer_size, &in_data, &in_data_size); if (!in_data) { in_data = in_buffer; @@ -964,38 +971,38 @@ } } - if (in_data_size > inSize) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("MCDec The returned buffer is too small\n")); - if (do_free) gf_free(in_data); - return GF_OK; - } + if (in_data_size > inSize) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("MCDec The returned buffer is too small\n")); + if (do_free) gf_free(in_data); + return GF_OK; + } - if (in_data_size) { + if (in_data_size) { memcpy(buffer, in_data, in_data_size); gf_filter_pid_drop_packet(ref_pid); - if (do_free) gf_free(in_data); + if (do_free) gf_free(in_data); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("MCDec AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM input\n")); + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("MCDec AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM input\n")); ctx->inputEOS = GF_TRUE; flags = AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM; - } + } if (AMediaCodec_queueInputBuffer(ctx->codec, inIndex, 0, in_data_size, cts, flags) != AMEDIA_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaCodec_queueInputBuffer failed\n")); return GF_BAD_PARAM; - } - mcdec_buffer_available = GF_TRUE; + } + mcdec_buffer_available = GF_TRUE; GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("MCDec AMediaCodec_queueInputBuffer OK, %d bytes queued (in EOS %d)\n", in_data_size, ctx->inputEOS)); - } - } + } + } - if (!ctx->outputEOS) { + if (!ctx->outputEOS) { u32 width, height, stride; AMediaCodecBufferInfo info; - ctx->outIndex = AMediaCodec_dequeueOutputBuffer(ctx->codec, &info, ctx->dequeue_timeout); + ctx->outIndex = AMediaCodec_dequeueOutputBuffer(ctx->codec, &info, ctx->dequeue_timeout); - switch(ctx->outIndex) { + switch(ctx->outIndex) { case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("MCDec AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED\n")); ctx->format = AMediaCodec_getOutputFormat(ctx->codec); @@ -1043,7 +1050,7 @@ if (e) return e; break; } - } + } if (ctx->outputEOS) gf_filter_pid_set_eos(ctx->opid); return GF_OK; @@ -1163,8 +1170,8 @@ GF_Err mcdec_initialize(GF_Filter *filter) { - GF_MCDecCtx *ctx = gf_filter_get_udta(filter); - ctx->filter = filter; + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + ctx->filter = filter; ctx->frames_res = gf_list_new(); ctx->streams = gf_list_new(); @@ -1177,37 +1184,37 @@ ctx->active_sps = -1; ctx->active_pps = -1; - return GF_OK; + return GF_OK; } void mcdec_finalize(GF_Filter *filter) { - GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); - if (ctx->format && AMediaFormat_delete(ctx->format) != AMEDIA_OK) { + if (ctx->format && AMediaFormat_delete(ctx->format) != AMEDIA_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaFormat_delete failed\n")); - } + } - if (ctx->codec && AMediaCodec_delete(ctx->codec) != AMEDIA_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaCodec_delete failed\n")); - } + if (ctx->codec && AMediaCodec_delete(ctx->codec) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("MCDec AMediaCodec_delete failed\n")); + } - if(ctx->window) { + if(ctx->window) { ANativeWindow_release(ctx->window); ctx->window = NULL; - } + } - if (ctx->surfaceTex.texture_id) + if (ctx->surfaceTex.texture_id) mcdec_delete_surface(ctx->surfaceTex); - if(ctx->tex_id) - glDeleteTextures (1, &ctx->tex_id); + if(ctx->tex_id) + glDeleteTextures (1, &ctx->tex_id); - mcdec_reset_ps_list(ctx->SPSs); + mcdec_reset_ps_list(ctx->SPSs); gf_list_del(ctx->SPSs); - mcdec_reset_ps_list(ctx->PPSs); + mcdec_reset_ps_list(ctx->PPSs); gf_list_del(ctx->PPSs); - mcdec_reset_ps_list(ctx->VPSs); + mcdec_reset_ps_list(ctx->VPSs); gf_list_del(ctx->VPSs); while (gf_list_count(ctx->frames_res) ) { @@ -1271,6 +1278,7 @@ #endif .configure_pid = mcdec_configure_pid, .process = mcdec_process, + .hint_class_type = GF_FS_CLASS_DECODER };
View file
gpac-2.4.0.tar.gz/src/filters/dec_mediacodec.h -> gpac-26.02.0.tar.gz/src/filters/dec_mediacodec.h
Changed
@@ -120,7 +120,7 @@ GF_Err mcdec_create_surface(GLuint tex_id, ANativeWindow ** window, Bool * surface_rendering, GF_MCDecSurfaceTexture * surfaceTex); GF_Err mcdec_delete_surface(GF_MCDecSurfaceTexture surfaceTex); -char * mcdec_find_decoder(const char * mime, u32 width, u32 height, Bool * is_adaptive); +char * mcdec_find_decoder(const char * mime, u32 width, u32 height, Bool * is_adaptive); u32 mcdec_exit_callback(void * param); GF_Err mcdec_update_surface(GF_MCDecSurfaceTexture surfaceTex);
View file
gpac-2.4.0.tar.gz/src/filters/dec_mpeghdec.c -> gpac-26.02.0.tar.gz/src/filters/dec_mpeghdec.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / MPEG-H Audio decoder using IIS MPEGHDecoder filter @@ -344,6 +344,7 @@ .finalize = mpegh_dec_finalize, .process = mpegh_dec_process, .process_event = mpegh_dec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; const GF_FilterRegister *mpeghdec_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dec_nvdec.c -> gpac-26.02.0.tar.gz/src/filters/dec_nvdec.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / NVidia Hardware decoder filter @@ -51,25 +51,29 @@ typedef struct _nv_dec_inst NVDecInstance; -typedef enum -{ +GF_OPT_ENUM (NVDecUnloadMode, + DEC_UNLOAD_NO = 0, + DEC_UNLOAD_DESTROY, + DEC_UNLOAD_REUSE, +); + +GF_OPT_ENUM (NVDecFrameMode, NVDEC_COPY = 0, NVDEC_SINGLE, - NVDEC_GL -} NVDecFrameMode ; + NVDEC_GL, +); -typedef enum -{ +GF_OPT_ENUM (NVDecVideoMode, NVDEC_CUVID = 0, NVDEC_CUDA, - NVDEC_DXVA -} NVDecVideoMode; + NVDEC_DXVA, +); typedef struct _nv_dec_ctx { - u32 unload; - NVDecFrameMode fmode; + NVDecUnloadMode unload; NVDecVideoMode vmode; + NVDecFrameMode fmode; u32 num_surfaces; GF_FilterPid *ipid, *opid; @@ -600,7 +604,7 @@ } ctx->ipid = NULL; - if (ctx->unload == 2) { + if (ctx->unload == DEC_UNLOAD_REUSE) { global_nb_loaded_nvdec--; if (ctx->dec_inst) { gf_assert(global_unactive_decoders); @@ -742,7 +746,7 @@ else ctx->prefer_dec_mode = cudaVideoCreate_PreferCUVID; - if (ctx->unload == 2) { + if (ctx->unload == DEC_UNLOAD_REUSE) { global_nb_loaded_nvdec++; if (!global_inst_mutex ) global_inst_mutex = gf_mx_new("NVDecGlobal"); gf_mx_p(global_inst_mutex); @@ -770,7 +774,7 @@ NVDecFrame *f = gf_list_pop_back(ctx->frames); gf_list_add(ctx->frames_res, f); } - if (ctx->unload == 2) { + if (ctx->unload == DEC_UNLOAD_REUSE) { if (ctx->dec_inst) { gf_assert(global_unactive_decoders); gf_mx_p(global_inst_mutex); @@ -790,7 +794,9 @@ } else { if (ctx->dec_inst) { nvdec_destroy_decoder(ctx->dec_inst); - ctx->dec_inst=0; + if (ctx->dec_inst->cu_parser) cuvidDestroyVideoParser(ctx->dec_inst->cu_parser); + gf_free(ctx->dec_inst); + ctx->dec_inst = NULL; } ctx->needs_resetup = 1; ctx->dec_create_error = CUDA_SUCCESS; @@ -807,7 +813,7 @@ memset(f, 0, sizeof(NVDecFrame)); gf_list_add(ctx->frames_res, f); } - if (ctx->unload == 2) { + if (ctx->unload == DEC_UNLOAD_REUSE) { if (ctx->dec_inst) { gf_assert(global_unactive_decoders); gf_mx_p(global_inst_mutex); @@ -824,7 +830,7 @@ } ctx->needs_resetup = 1; ctx->dec_create_error = CUDA_SUCCESS; - } else if (ctx->unload == 1) { + } else if (ctx->unload == DEC_UNLOAD_DESTROY) { if (ctx->dec_inst) { nvdec_destroy_decoder(ctx->dec_inst); } @@ -937,6 +943,7 @@ } if (data && ctx->nal_size_length) { + u32 nb_nalsize_zero=0; GF_BitStream *bs = gf_bs_new(ctx->nal_buffer, ctx->nal_buffer_alloc, GF_BITSTREAM_WRITE_DYN); if (!bs) return GF_OUT_OF_MEM; @@ -953,6 +960,12 @@ nal_size = (nal_size << 8) + ((u8)datai); } data += ctx->nal_size_length; + if (!nal_size) { + if (nb_nalsize_zero) break; + nb_nalsize_zero++; + } else { + nb_nalsize_zero=0; + } if (pck_size < nal_size + ctx->nal_size_length) break; @@ -1531,6 +1544,7 @@ nvdec_destroy_decoder(ctx->dec_inst); if (ctx->dec_inst->cu_parser) cuvidDestroyVideoParser(ctx->dec_inst->cu_parser); gf_free(ctx->dec_inst); + ctx->dec_inst = NULL; } while (gf_list_count(ctx->frames)) { @@ -1607,7 +1621,8 @@ .args = NVDecArgs, .configure_pid = nvdec_configure_pid, .process = nvdec_process, - .process_event = nvdec_process_event + .process_event = nvdec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; static void nvdec_register_free(GF_FilterSession *session, GF_FilterRegister *freg)
View file
gpac-2.4.0.tar.gz/src/filters/dec_odf.c -> gpac-26.02.0.tar.gz/src/filters/dec_odf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / OD decoder filter @@ -511,6 +511,7 @@ .process = odf_dec_process, .configure_pid = odf_dec_configure_pid, .process_event = odf_dec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; const GF_FilterRegister *odfdec_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dec_openhevc.c -> gpac-26.02.0.tar.gz/src/filters/dec_openhevc.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010-2023 + * Copyright (c) Telecom ParisTech 2010-2024 * All rights reserved * * This file is part of GPAC / OpenHEVC decoder filter @@ -43,7 +43,7 @@ typedef struct { GF_FilterPid *ipid; - u32 cfg_crc; + u32 cfg_crc, enh_cfg_crc; u32 id; u32 dep_id; u32 codec_id; @@ -318,7 +318,7 @@ static GF_Err ohevcdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { - u32 i, dep_id=0, id=0, cfg_crc=0, codecid; + u32 i, dep_id=0, id=0, cfg_crc=0, enh_cfg_crc=0, codecid; Bool has_scalable = GF_FALSE; Bool is_sublayer = GF_FALSE; u8 *patched_dsi=NULL; @@ -379,12 +379,16 @@ cfg_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); } dsi_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + if (dsi_enh && dsi_enh->value.data.ptr && dsi_enh->value.data.size) { + enh_cfg_crc = gf_crc_32(dsi_enh->value.data.ptr, dsi_enh->value.data.size); + } stream = NULL; //check if this is an update for (i=0; i<ctx->nb_streams; i++) { if (ctx->streamsi.ipid == pid) { - if (ctx->streamsi.cfg_crc == cfg_crc) return GF_OK; + if ((ctx->streamsi.cfg_crc == cfg_crc) && (ctx->streamsi.enh_cfg_crc == enh_cfg_crc)) + return GF_OK; if (ctx->codec && (ctx->streamsi.codec_id != codecid)) { //we are already instantiated, flush all frames and reconfig ctx->reconfig_pending = GF_TRUE; @@ -392,6 +396,7 @@ } ctx->streamsi.codec_id = codecid; ctx->streamsi.cfg_crc = cfg_crc; + ctx->streamsi.enh_cfg_crc = enh_cfg_crc; stream = &ctx->streamsi; break; } @@ -481,18 +486,22 @@ #ifdef OPENHEVC_HAS_AVC_BASE if (codecid==GF_CODECID_AVC) { GF_AVCConfig *avcc = NULL; - AVCState avc; - memset(&avc, 0, sizeof(AVCState)); + AVCState *avc_state; + GF_SAFEALLOC(avc_state, AVCState); + if (!avc_state) return GF_OUT_OF_MEM; avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); - if (!avcc) return GF_NON_COMPLIANT_BITSTREAM; + if (!avcc) { + gf_free(avc_state); + return GF_NON_COMPLIANT_BITSTREAM; + } ctx->avc_nalu_size_length = avcc->nal_unit_size; for (i=0; i< gf_list_count(avcc->sequenceParameterSets); i++) { GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(avcc->sequenceParameterSets, i); - s32 idx = gf_avc_read_sps(sl->data, sl->size, &avc, 0, NULL); - ctx->width = MAX(avc.spsidx.width, ctx->width); - ctx->height = MAX(avc.spsidx.height, ctx->height); + s32 idx = gf_avc_read_sps(sl->data, sl->size, avc_state, 0, NULL); + ctx->width = MAX(avc_state->spsidx.width, ctx->width); + ctx->height = MAX(avc_state->spsidx.height, ctx->height); ctx->luma_bpp = avcc->luma_bit_depth; ctx->chroma_bpp = avcc->chroma_bit_depth; ctx->chroma_format_idc = avcc->chroma_format; @@ -510,20 +519,25 @@ } } gf_odf_avc_cfg_del(avcc); + gf_free(avc_state); } else #endif { GF_HEVCConfig *hvcc = NULL; GF_HEVCConfig *hvcc_enh = NULL; - HEVCState hevc; + HEVCState *hvc_state; u32 j; GF_List *SPSs=NULL, *PPSs=NULL, *VPSs=NULL; - memset(&hevc, 0, sizeof(HEVCState)); - hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); - if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return GF_OUT_OF_MEM; + hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + if (!hvcc) { + gf_free(hvc_state); + return GF_NON_COMPLIANT_BITSTREAM; + } ctx->hevc_nalu_size_length = hvcc->nal_unit_size; if (dsi_enh) { @@ -546,12 +560,12 @@ u16 hdr = sl->data0 << 8 | sl->data1; if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { - idx = gf_hevc_read_sps(sl->data, sl->size, &hevc); - ctx->width = MAX(hevc.spsidx.width, ctx->width); - ctx->height = MAX(hevc.spsidx.height, ctx->height); - ctx->luma_bpp = MAX(hevc.spsidx.bit_depth_luma, ctx->luma_bpp); - ctx->chroma_bpp = MAX(hevc.spsidx.bit_depth_chroma, ctx->chroma_bpp); - ctx->chroma_format_idc = hevc.spsidx.chroma_format_idc; + idx = gf_hevc_read_sps(sl->data, sl->size, hvc_state); + ctx->width = MAX(hvc_state->spsidx.width, ctx->width); + ctx->height = MAX(hvc_state->spsidx.height, ctx->height); + ctx->luma_bpp = MAX(hvc_state->spsidx.bit_depth_luma, ctx->luma_bpp); + ctx->chroma_bpp = MAX(hvc_state->spsidx.bit_depth_chroma, ctx->chroma_bpp); + ctx->chroma_format_idc = hvc_state->spsidx.chroma_format_idc; if (hdr & 0x1f8) { ctx->nb_layers ++; @@ -559,15 +573,15 @@ SPSs = ar->nalus; } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { - s32 vps_id = gf_hevc_read_vps(sl->data, sl->size, &hevc); + s32 vps_id = gf_hevc_read_vps(sl->data, sl->size, hvc_state); //multiview - if ((vps_id>=0) && (hevc.vpsvps_id.scalability_mask1)) { + if ((vps_id>=0) && (hvc_state->vpsvps_id.scalability_mask1)) { ctx->is_multiview = GF_TRUE; } VPSs = ar->nalus; } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { - gf_hevc_read_pps(sl->data, sl->size, &hevc); + gf_hevc_read_pps(sl->data, sl->size, hvc_state); PPSs = ar->nalus; } } @@ -587,6 +601,7 @@ gf_odf_hevc_cfg_write(hvcc, &patched_dsi, &patched_dsi_size); } gf_odf_hevc_cfg_del(hvcc); + gf_free(hvc_state); } } @@ -1420,8 +1435,9 @@ .process_event = ohevcdec_process_event, .flags = GF_FS_REG_BLOCK_MAIN, .max_extra_pids = (HEVC_MAX_STREAMS-1), - //by default take over FFMPEG - .priority = 100 + //by default take over FFmpeg + .priority = 100, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif // defined(GPAC_HAS_OPENHEVC) && !defined(GPAC_DISABLE_AV_PARSERS)
View file
gpac-2.4.0.tar.gz/src/filters/dec_opensvc.c -> gpac-26.02.0.tar.gz/src/filters/dec_opensvc.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010-2023 + * Copyright (c) Telecom ParisTech 2010-2024 * All rights reserved * * This file is part of GPAC / OpenSVC Decoder filter @@ -575,6 +575,16 @@ } gf_list_del(ctx->src_packets); } +#else +static GF_Err osvcdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + return GF_NOT_SUPPORTED; +} +static GF_Err osvcdec_process(GF_Filter *filter) +{ + return GF_NOT_SUPPORTED; +} +#endif //GPAC_HAS_OPENSVC static const GF_FilterCapability OSVCDecCaps = { @@ -589,24 +599,28 @@ .name = "osvcdec", GF_FS_SET_DESCRIPTION("OpenSVC decoder") GF_FS_SET_HELP("This filter decodes scalable AVC|H264 streams through OpenSVC library.") - .private_size = sizeof(GF_OSVCDecCtx), + SETCAPS(OSVCDecCaps), - .initialize = osvcdec_initialize, - .finalize = osvcdec_finalize, .configure_pid = osvcdec_configure_pid, .process = osvcdec_process, +#ifdef GPAC_HAS_OPENSVC + .private_size = sizeof(GF_OSVCDecCtx), + .initialize = osvcdec_initialize, + .finalize = osvcdec_finalize, .process_event = osvcdec_process_event, .max_extra_pids = (SVC_MAX_STREAMS-1), - .priority = 255 +#endif + .priority = 255, + .hint_class_type = GF_FS_CLASS_DECODER }; -#endif //GPAC_HAS_OPENSVC - const GF_FilterRegister *osvcdec_register(GF_FilterSession *session) { #ifdef GPAC_HAS_OPENSVC return &OSVCDecRegister; #else - return NULL; + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + return &OSVCDecRegister; #endif }
View file
gpac-26.02.0.tar.gz/src/filters/dec_scte35.c
Added
@@ -0,0 +1,939 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Romain Bouqueau + * Copyright (c) Motion Spell 2024 + * All rights reserved + * + * This file is part of GPAC / SCTE35 property decode filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/filters.h> +#include <gpac/internal/isomedia_dev.h> + +#define IS_SEGMENTED (ctx->sampdur.den && ctx->sampdur.num>0) +#define IS_PASSTHRU (ctx->mode == 1) + +typedef struct { + u64 dts; + GF_EventMessageBox *emib; +} Event; + +GF_OPT_ENUM (SCTE35DecDataMode, + PROP=0, // scte35 data is carried as a property + RAW, // packet data contains the scte35 payload (m2ts section) + BOX, // packet data contains the emib/emeb boxes +); + +typedef struct { + GF_FilterPid *ipid; + GF_FilterPid *opid; + + SCTE35DecDataMode data_mode; + u64 clock; + + // options + u32 mode; + GF_Fraction sampdur; + + // override gf_filter_*() calls for testability + GF_FilterPacket* (*pck_new_shared)(GF_FilterPid *pid, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct); + GF_FilterPacket* (*pck_new_alloc)(GF_FilterPid *pid, u32 data_size, u8 **data); + GF_Err (*pck_send)(GF_FilterPacket *pck); + + // events ordered by dispatch time + GF_List *ordered_events; + u32 last_event_id; + + // used to compute immediate dispatch event duration + u32 timescale; + u32 last_pck_dur; + s64 last_dispatched_dts; + Bool last_dispatched_dts_init; + + // used to segment empty boxes + u64 orig_ts; + Bool seg_setup; + u32 segnum; + u8 emeb_box8; + + // when called from the dasher + GF_FilterPacket *dash_pck; + Bool is_dash; +} SCTE35DecCtx; + +static GF_Err scte35dec_initialize_internal(SCTE35DecCtx *ctx) +{ + ctx->ordered_events = gf_list_new(); + if (!ctx->ordered_events) return GF_OUT_OF_MEM; + + GF_Box *emeb = gf_isom_box_new(GF_ISOM_BOX_TYPE_EMEB); + if (!emeb) return GF_OUT_OF_MEM; + GF_Err e = gf_isom_box_size((GF_Box*)emeb); + if (e) return e; + GF_BitStream *bs = gf_bs_new(ctx->emeb_box, sizeof(ctx->emeb_box), GF_BITSTREAM_WRITE); + if (!bs) { + e = GF_OUT_OF_MEM; + goto exit; + } + e = gf_isom_box_write((GF_Box*)emeb, bs); + +exit: + gf_bs_del(bs); + gf_isom_box_del(emeb); + return e; +} + +static GF_Err scte35dec_initialize(GF_Filter *filter) +{ + SCTE35DecCtx *ctx = gf_filter_get_udta(filter); + ctx->pck_new_shared = gf_filter_pck_new_shared; + ctx->pck_new_alloc = gf_filter_pck_new_alloc; + ctx->pck_send = gf_filter_pck_send; + return scte35dec_initialize_internal(ctx); +} + +static void scte35dec_finalize_internal(SCTE35DecCtx *ctx) +{ + for (u32 i=0; i<gf_list_count(ctx->ordered_events); i++) { + Event *evt = gf_list_get(ctx->ordered_events, i); + gf_isom_box_del((GF_Box*)evt->emib); + gf_free(evt); + } + gf_list_del(ctx->ordered_events); + if (ctx->dash_pck) { + gf_filter_pck_unref(ctx->dash_pck); + ctx->dash_pck = NULL; + } +} + +static void scte35dec_finalize(GF_Filter *filter) +{ + SCTE35DecCtx *ctx = gf_filter_get_udta(filter); + scte35dec_finalize_internal(ctx); +} + +static GF_Err scte35dec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + SCTE35DecCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + GF_FilterPid *out_pid = gf_filter_pid_get_udta(pid); + if (out_pid == ctx->opid) + ctx->opid = NULL; + if (out_pid) + gf_filter_pid_remove(out_pid); + return GF_OK; + } + + if (!gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + if (!ctx->opid) return GF_OUT_OF_MEM; + gf_filter_pid_set_udta(pid, ctx->opid); + ctx->last_event_id = gf_rand(); + } + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + if (IS_PASSTHRU) return GF_OK; + + const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_CODECID); + if (p) { + if (p->value.uint == GF_CODECID_SCTE35) + ctx->data_mode = RAW; + else if (p->value.uint == GF_CODECID_EVTE) + ctx->data_mode = BOX; + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_METADATA) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_EVTE) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, &PROP_BOOL(GF_FALSE) ); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE); + // set a huge sampdur/segdur as the dasher will pilot segmentation + if (p && p->value.uint) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_DUR); + if (p) + ctx->sampdur = p->value.frac; + } + + return GF_OK; +} + +static Bool scte35dec_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + if (evt->base.type==GF_FEVT_TRANSPORT_HINTS) { + if (evt->transport_hints.flags & GF_TRANSPORT_HINTS_SAW_ENCODER) { + // this is a pass-through event, ignore it + return GF_FALSE; + } + + SCTE35DecCtx *ctx = gf_filter_get_udta(filter); + if (evt->transport_hints.seg_duration.den && evt->transport_hints.seg_duration.num) { + ctx->sampdur = evt->transport_hints.seg_duration; + } + + //send the event upstream (in case any other filter is interested in it) + GF_FilterEvent new_evt = *evt; + new_evt.base.on_pid = ctx->ipid; + new_evt.transport_hints.flags |= GF_TRANSPORT_HINTS_SAW_ENCODER; + gf_filter_pid_send_event(ctx->ipid, &new_evt); + return GF_TRUE; + } + return GF_FALSE; +} + +static void scte35dec_send_pck(SCTE35DecCtx *ctx, GF_FilterPacket *pck, u64 dts, u32 dur) +{ + if (IS_SEGMENTED) { + if (ctx->dash_pck) { + gf_filter_pck_merge_properties(ctx->dash_pck, pck); + gf_filter_pck_unref(ctx->dash_pck); + ctx->dash_pck = NULL; + } else if (ctx->is_dash) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Unaligned segment at dts="LLU".\n", dts)); + } + } + + if (dur > 0) { + gf_filter_pck_set_duration(pck, dur); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Scte35Dec Send dts="LLU" dur=%u\n", dts, dur)); + gf_filter_pck_set_dts(pck, dts); + ctx->last_dispatched_dts = IS_SEGMENTED ? dts : dts + dur; + ctx->last_pck_dur = dur; + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + ctx->pck_send(pck); + ctx->clock = ctx->last_dispatched_dts + ctx->last_pck_dur; +} + +static GF_Err scte35dec_flush_emeb(SCTE35DecCtx *ctx, u64 dts, u32 dur) +{ + GF_FilterPacket *seg_emeb = ctx->pck_new_shared(ctx->opid, ctx->emeb_box, sizeof(ctx->emeb_box), NULL); + if (!seg_emeb) return GF_OUT_OF_MEM; + + scte35dec_send_pck(ctx, seg_emeb, dts, dur != GF_UINT_MAX ? dur : (u32)(dts - ctx->last_dispatched_dts)); + return GF_OK; +} + +static void scte35dec_schedule(SCTE35DecCtx *ctx, u64 dts, GF_EventMessageBox *emib) +{ + Event *evt_new; + GF_SAFEALLOC(evt_new, Event); + evt_new->dts = dts; + evt_new->emib = emib; + + for (u32 i=0; i<gf_list_count(ctx->ordered_events); i++) { + Event *evt_i = gf_list_get(ctx->ordered_events, i); + if (evt_i->dts + evt_i->emib->presentation_time_delta > dts + emib->presentation_time_delta) { + gf_list_insert(ctx->ordered_events, evt_new, i); + return; + } + } + gf_list_add(ctx->ordered_events, evt_new); +} + +static u32 compute_emib_duration(u64 dts, u64 evt_dts, u32 max_dur, u32 evt_dur) +{ + if (dts < evt_dts) { + return (u32) MIN(evt_dts - dts, max_dur); + } else if (max_dur != GF_UINT_MAX && evt_dur > max_dur) { + return max_dur; + } else { + return evt_dur; + } +} + +static GF_Err scte35dec_flush_emib(SCTE35DecCtx *ctx, u64 dts, u32 max_dur) +{ + gf_fatal_assert(gf_list_count(ctx->ordered_events) > 0); + + GF_Err e = GF_OK; + Event *evt; + while ( (evt = gf_list_pop_front(ctx->ordered_events)) ) { + u32 evt_dur = evt->emib->event_duration == 0xFFFFFFFF ? 1 : evt->emib->event_duration; + if (evt->dts + evt->emib->presentation_time_delta + evt_dur > dts) { + u8 *output = NULL; + GF_FilterPacket *pck_dst = ctx->pck_new_alloc(ctx->opid, (u32) evt->emib->size, &output); + if (!pck_dst) { + e = GF_OUT_OF_MEM; + goto exit; + } + + GF_BitStream *bs = gf_bs_new(output, evt->emib->size, GF_BITSTREAM_WRITE); + e = gf_isom_box_write((GF_Box*)evt->emib, bs); + gf_bs_del(bs); + if (e) goto exit; + + u32 emib_dur = compute_emib_duration(dts, evt->dts+evt->emib->presentation_time_delta, max_dur, evt_dur); + u64 emib_dts = IS_SEGMENTED ? evt->dts : dts; + scte35dec_send_pck(ctx, pck_dst, emib_dts, emib_dur); + + evt->dts += emib_dur; + dts += emib_dur; + if (evt->emib->presentation_time_delta <= 0) + if (evt->emib->event_duration != GF_UINT_MAX) + evt->emib->event_duration -= emib_dur; + evt->emib->presentation_time_delta -= emib_dur; + if (evt->emib->presentation_time_delta < 0) + evt->emib->presentation_time_delta = 0; + if (max_dur != GF_UINT_MAX) + max_dur -= emib_dur; + } + + if (!IS_SEGMENTED || + (evt->emib->presentation_time_delta <= 0 && (evt->emib->event_duration == GF_UINT_MAX || evt->emib->event_duration <= 0)) || + dts >= evt->dts + evt->emib->presentation_time_delta + evt_dur) { + // we're done with the event + gf_isom_box_del((GF_Box*)evt->emib); + gf_free(evt); + } else { + scte35dec_schedule(ctx, evt->dts, evt->emib); + gf_free(evt); + if (max_dur != GF_UINT_MAX && max_dur > 0) + continue; // still time within time scope: re-schedule and continue to process + else + break; // process later + } + } + +exit: + if (e) { + gf_isom_box_del((GF_Box*)evt->emib); + gf_free(evt); + } + return e; +} + +static GF_Err scte35_insert_emeb_before_emib(SCTE35DecCtx *ctx, Event *first_evt, u64 timestamp, u64 dur) +{ + if (dur == GF_UINT_MAX) dur = first_evt->dts - timestamp; + gf_assert(timestamp + dur >= first_evt->dts); + GF_Err e = scte35dec_flush_emeb(ctx, timestamp, (u32) dur); + ctx->clock = timestamp + dur; + return e; +} + +static GF_Err scte35dec_push_box(SCTE35DecCtx *ctx, const u64 ts, const u32 dur) +{ + if (gf_list_count(ctx->ordered_events) == 0) + return scte35dec_flush_emeb(ctx, ts, dur); + + GF_Err e = GF_OK; + Event *first_evt = gf_list_get(ctx->ordered_events, 0); + u64 curr_ts = ts; + u64 curr_dur = dur; + if (IS_SEGMENTED) { + u64 segdur = ctx->sampdur.num * ctx->timescale / ctx->sampdur.den; + gf_assert(segdur == dur); + // pre-signal events in each segment + if (curr_ts < first_evt->dts) { + u64 emeb_dur = MIN(first_evt->dts - curr_ts, segdur); + e = scte35_insert_emeb_before_emib(ctx, first_evt, curr_ts, emeb_dur); + if (e) return e; + curr_dur -= emeb_dur; + curr_ts += emeb_dur; + } + } else { + // immediate dispatch: jump directly to event + if (ts < first_evt->dts + first_evt->emib->presentation_time_delta) { + u64 emeb_dur = first_evt->dts + first_evt->emib->presentation_time_delta - ts; + e = scte35_insert_emeb_before_emib(ctx, first_evt, ts, emeb_dur); + if (e) return e; + curr_ts = first_evt->dts + first_evt->emib->presentation_time_delta; + curr_dur -= emeb_dur; + } + } + + e = scte35dec_flush_emib(ctx, curr_ts, (u32) curr_dur); + if (e) return e; + + if (IS_SEGMENTED && ctx->clock < ts + dur) { + // complete the segment with an empty box + return scte35dec_flush_emeb(ctx, ctx->clock, (u32) (ts + dur - ctx->clock)); + } + + return GF_OK; +} + +static void scte35dec_flush(SCTE35DecCtx *ctx) +{ + if (IS_PASSTHRU) { + return; + } else if (IS_SEGMENTED) { + if (!gf_list_count(ctx->ordered_events) && ctx->clock == ctx->last_dispatched_dts + ctx->last_pck_dur) + return; //nothing to flush + + scte35dec_push_box(ctx, ctx->segnum * ctx->sampdur.num * ctx->timescale / ctx->sampdur.den, ctx->sampdur.num * ctx->timescale / ctx->sampdur.den); + ctx->segnum++; + } else { + scte35dec_push_box(ctx, ctx->last_dispatched_dts, ctx->last_pck_dur); + } +} + +static GF_Err new_segment(SCTE35DecCtx *ctx) +{ + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec New segment at DTS %d/%u (%lf). Flushing the previous one.\n", + ctx->clock, ctx->timescale, (double)ctx->clock/ctx->timescale)); + u64 dts = ctx->orig_ts + ctx->segnum * ctx->sampdur.num * ctx->timescale / ctx->sampdur.den; + if (ctx->segnum == 0) // first segment: adjust last_dispatched_dts to a previous fictive segment + ctx->last_dispatched_dts = ctx->orig_ts - ctx->sampdur.num * ctx->timescale / ctx->sampdur.den; + ctx->segnum++; + ctx->clock = dts; + return scte35dec_push_box(ctx, dts, (u32) ( ctx->segnum * ctx->sampdur.num * ctx->timescale / ctx->sampdur.den - (dts - ctx->orig_ts)) ); +} + +static u64 scte35dec_parse_splice_time(GF_BitStream *bs) +{ + Bool time_specified_flag = gf_bs_read_int(bs, 1); + if (time_specified_flag == 1) { + /*reserved = */gf_bs_read_int(bs, 6); + return /*pts_time =*/ gf_bs_read_long_int(bs, 33); + } else { + /*reserved = */gf_bs_read_int(bs, 7); + return 0; + } +} + +static Bool scte35dec_get_timing(const u8 *data, u32 size, u64 *pts, u64 *dur, u32 *splice_event_id, Bool *needs_idr) +{ + Bool ret = GF_FALSE; + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + + // splice_info_section() : the full MPEG2-TS Section is in here + u8 table_id = gf_bs_read_u8(bs); + if (table_id != 0xFC) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Invalid splice_info_section() table_id. Abort parsing.\n")); + goto exit; + } + /*Bool section_syntax_indicator = */gf_bs_read_int(bs, 1); + /*Bool private_indicator = */gf_bs_read_int(bs, 1); + /*u8 sap_type = */gf_bs_read_int(bs, 2); + u32 section_length = gf_bs_read_int(bs, 12); + if (section_length + 3 != gf_bs_get_size(bs)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Invalid section length %d\n", section_length)); + goto exit; + } + + /*u8 protocol_version = */gf_bs_read_u8(bs); + Bool encrypted_packet = gf_bs_read_int(bs, 1); + /*u8 encryption_algorithm = */gf_bs_read_int(bs, 6); + u64 pts_adjustment = gf_bs_read_long_int(bs, 33); + + if (encrypted_packet) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Encrypted packet, not supported (pts_adjustment="LLU")\n", pts_adjustment)); + goto exit; + } + + /*u8 cw_index = */gf_bs_read_u8(bs); + /*u32 tier = */gf_bs_read_int(bs, 12); + + u32 splice_command_length = gf_bs_read_int(bs, 12); + if (splice_command_length > gf_bs_available(bs)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Bitstream too short (" LLU " bytes) while parsing splice command (%u bytes)\n", + gf_bs_available(bs), splice_command_length)); + goto exit; + } + + u8 splice_command_type = gf_bs_read_u8(bs); + switch(splice_command_type) { + case 0x05: //splice_insert() + { + u64 splice_time = 0; + *splice_event_id = gf_bs_read_u32(bs); + Bool splice_event_cancel_indicator = gf_bs_read_int(bs, 1); + /*reserved = */gf_bs_read_int(bs, 7); + if (splice_event_cancel_indicator == 0) { + /*Bool out_of_network_indicator = */gf_bs_read_int(bs, 1); + Bool program_splice_flag = gf_bs_read_int(bs, 1); + Bool duration_flag = gf_bs_read_int(bs, 1); + Bool splice_immediate_flag = gf_bs_read_int(bs, 1); + /*reserved = */gf_bs_read_int(bs, 4); + + if ((program_splice_flag == 1) && (splice_immediate_flag == 0)) { + splice_time = scte35dec_parse_splice_time(bs); + *pts = splice_time + pts_adjustment; + } + + if (program_splice_flag == 0) { + u32 i; + u32 component_count = gf_bs_read_u8(bs); + for (i=0; i<component_count; i++) { + /*u8 component_tag = */gf_bs_read_u8(bs); + if (splice_immediate_flag == 0) { + gf_assert(*pts == 0); // we've never encounter multi component streams + splice_time = scte35dec_parse_splice_time(bs); + *pts = splice_time + pts_adjustment; + } + } + } + if (duration_flag == 1) { + //break_duration() + /*Bool auto_return = */gf_bs_read_int(bs, 1); + /*reserved = */gf_bs_read_int(bs, 6); + *dur = gf_bs_read_long_int(bs, 33); + } + + *needs_idr = GF_TRUE; + // truncated parsing: we make the assumption that there is only one command (which is the case from M2TS section sources) + } + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec Found splice_insert() (*splice_event_id=%u, pts_adjustment="LLU", dur=%u, splice_time="LLU")\n", + *splice_event_id, pts_adjustment, *dur, splice_time)); + } + ret = GF_TRUE; + goto exit; + case 0x06: //time_signal() + { + u64 splice_time = scte35dec_parse_splice_time(bs); + *pts = splice_time + pts_adjustment; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec Found time_signal() for PTS="LLU" (splice_time="LLU", pts_adjustment="LLU")\n", + *pts, splice_time, pts_adjustment)); + } + ret = GF_TRUE; + break; + case 0x00: //splice_null() + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec Found splice_null()\n")); + gf_bs_skip_bytes(bs, splice_command_length); + gf_bs_align(bs); + break; + default: + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec Found splice_command_type=0x%02X length=%d pts_adjustment="LLU"\n", + splice_command_type, splice_command_length, pts_adjustment)); + goto exit; + } + + u16 descriptor_loop_length = gf_bs_read_u16(bs); + u32 descriptor_loop_start_pos = (u32) gf_bs_get_position(bs); + u32 descriptor_start_pos = (u32) gf_bs_get_position(bs); + while (descriptor_start_pos - descriptor_loop_start_pos < descriptor_loop_length) { + u8 splice_descriptor_tag = gf_bs_read_u8(bs); + u8 descriptor_length = gf_bs_read_u8(bs); + + if (descriptor_length > gf_bs_available(bs)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Bitstream too short while parsing descriptor (%u bytes)\n", descriptor_length)); + goto exit; + } + + /*u32 identifier = */gf_bs_read_u32(bs); + switch (splice_descriptor_tag) { + case 0x02: //segmentation_descriptor() + { + /*u32 segmentation_event_id = */gf_bs_read_u32(bs); + Bool segmentation_event_cancel_indicator = gf_bs_read_int(bs, 1); + /*Bool segmentation_event_id_compliance_indicator = */gf_bs_read_int(bs, 1); + /*reserved = */gf_bs_read_int(bs, 6); + + if (segmentation_event_cancel_indicator == 0) { + Bool program_segmentation_flag = gf_bs_read_int(bs, 1); + Bool segmentation_duration_flag = gf_bs_read_int(bs, 1); + Bool delivery_not_restricted_flag = gf_bs_read_int(bs, 1); + + if (delivery_not_restricted_flag == 0) { + /*u8 web_delivery_allowed_flag = */gf_bs_read_int(bs, 1); + /*u8 no_regional_blackout_flag = */gf_bs_read_int(bs, 1); + /*u8 archive_allowed_flag = */gf_bs_read_int(bs, 1); + /*u8 device_restrictions = */gf_bs_read_int(bs, 2); + } else { + /*reserved = */gf_bs_read_int(bs, 5); + } + + if (program_segmentation_flag == 0) { //deprecated + u8 component_count = gf_bs_read_u8(bs); + for (u8 i=0; i<component_count; i++) + gf_bs_skip_bytes(bs, 48); + } + + if (segmentation_duration_flag == 1) { + /*u64 segmentation_duration = */gf_bs_read_long_int(bs, 40); + } + + /*u8 segmentation_upid_type = */gf_bs_read_u8(bs); + u8 segmentation_upid_length = gf_bs_read_u8(bs); + gf_bs_skip_bytes(bs, segmentation_upid_length); + u8 segmentation_type_id = gf_bs_read_u8(bs); + /*u8 segment_num = */gf_bs_read_u8(bs); + /*u8 segments_expected = */gf_bs_read_u8(bs); + + switch (segmentation_type_id) + { + case 0x10: + *needs_idr = GF_TRUE; + break; + case 0x34: + *needs_idr = GF_TRUE; + case 0x30: + case 0x32: + case 0x36: + case 0x38: + case 0x3A: + case 0x44: + case 0x46: + /*u8 sub_segment_num = */gf_bs_read_u8(bs); + /*u8 sub_segments_expected = */gf_bs_read_u8(bs); + break; + default: + break; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec Found segmentation_descriptor() segmentation_type_id=%u (needs_idr=%u)\n", segmentation_type_id, *needs_idr)); + } + } + break; + default: + gf_bs_skip_bytes(bs, MAX(0, descriptor_length - 4)); + break; + } + + descriptor_start_pos += descriptor_length + 2; + gf_bs_seek(bs, descriptor_start_pos); + + ret = GF_TRUE; // found something + } + +exit: + gf_bs_del(bs); + return ret; +} + +static void scte35dec_process_timing(SCTE35DecCtx *ctx, u64 dts, u32 timescale, u32 dur) +{ + // handle internal clock, timescale and duration + if (!ctx->last_dispatched_dts_init) { + ctx->timescale = timescale; + ctx->last_pck_dur = dur; + ctx->last_dispatched_dts_init = GF_TRUE; + ctx->clock = dts; + + if (IS_SEGMENTED) { + if (ctx->sampdur.num * ctx->timescale % ctx->sampdur.den) + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec timescale(%u) can't express segment duration(%u/%u).\n", timescale, ctx->sampdur.num, ctx->sampdur.den)); + + ctx->last_dispatched_dts = dts - dur; + } + } else if (!IS_SEGMENTED) { + ctx->last_pck_dur = dts + dur - ctx->last_dispatched_dts; + } + + if (IS_SEGMENTED) { + // check if we moved forward by more than one segment (which may happen with sparse streams/no heartbeat/non-prop data_mode) + while ((dts - ctx->clock) * ctx->sampdur.den >= ctx->sampdur.num * ctx->timescale) { + ctx->segnum = 1 + (u32) (ctx->clock * ctx->sampdur.den / (ctx->sampdur.num * ctx->timescale) ); + u32 segdur = ctx->sampdur.num * ctx->timescale / ctx->sampdur.den; + segdur = (u32) MIN(dts - ctx->clock * segdur, segdur); + scte35dec_push_box(ctx, ctx->clock, segdur); + } + } + + ctx->clock = MAX(ctx->clock, dts); +} + +static GF_Err scte35dec_process_emsg(SCTE35DecCtx *ctx, const u8 *data, u32 size, u64 dts) +{ + u64 pts = 0; + u64 dur = (u64) -1; + Bool needs_idr = GF_FALSE; + // parsing is incomplete so we only check the first splice command ... + if (!scte35dec_get_timing(data, size, &pts, &dur, &ctx->last_event_id, &needs_idr)) + return GF_OK; // no data to process + + GF_EventMessageBox *emib = (GF_EventMessageBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_EMIB); + if (!emib) return GF_OUT_OF_MEM; + + // set values according to SCTE 214-3 2015 + emib->presentation_time_delta = pts - dts; + if (pts < ctx->clock && !IS_SEGMENTED) + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec event overlap detected in immediate dispatch mode (not segmented)\n")); + emib->event_duration = (u32) dur; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Scte35Dec detected pts="LLU" (delta="LLU") dur=%u at dts="LLU"\n", pts, pts-dts, dur, dts)); + emib->event_id = ctx->last_event_id++; + emib->scheme_id_uri = gf_strdup("urn:scte:scte35:2013:bin"); + emib->value = gf_strdup("1001"); + emib->message_data_size = size; + emib->message_data = gf_malloc(emib->message_data_size); + if (!emib->message_data) return GF_OUT_OF_MEM; + memcpy(emib->message_data, data, emib->message_data_size); + + GF_Err e = gf_isom_box_size((GF_Box*)emib); + if (e) { + gf_isom_box_del((GF_Box*)emib); + return e; + } + + if (!IS_PASSTHRU || (IS_PASSTHRU && needs_idr)) + scte35dec_schedule(ctx, dts, emib); + else + gf_isom_box_del((GF_Box*)emib); + + return GF_OK; +} + +static Bool scte35dec_is_splice_point(SCTE35DecCtx *ctx, u64 cts) +{ + Event *evt = gf_list_get(ctx->ordered_events, 0); + if (!evt) return GF_FALSE; + Bool is_splice = (evt->dts + evt->emib->presentation_time_delta == cts); + if (is_splice) { + Event *evt = gf_list_pop_front(ctx->ordered_events); + gf_isom_box_del((GF_Box*)evt->emib); + gf_free(evt); + } + return is_splice; +} + +static GF_Err scte35dec_process_dispatch(SCTE35DecCtx *ctx, u64 dts, u32 dur) +{ + if (!IS_SEGMENTED) { + u32 event_count = gf_list_count(ctx->ordered_events); + if (!event_count) + return GF_OK; // no event: nothing to do + + // immediate dispatch: recompute times + gf_assert(event_count <= 1); + GF_Err e = scte35dec_push_box(ctx, + ctx->last_dispatched_dts, // from last pck send + dts + dur); // until the end of event + gf_list_rem_last(ctx->ordered_events); + return e; + } else { + if (!ctx->seg_setup) { + ctx->seg_setup = GF_TRUE; + ctx->orig_ts = ctx->clock; + ctx->segnum = 0; + } + + // segmented: we can only flush at the end of the segment + if (ctx->clock < ctx->orig_ts) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("Scte35Dec timestamps not increasing monotonously, resetting segmentation state !\n")); + ctx->orig_ts = ctx->clock; + ctx->segnum = 0; + } else { + GF_Fraction64 ts_diff = { ctx->clock - ctx->orig_ts, ctx->timescale }; + if ((s64) ((ts_diff.num + dur) * ctx->sampdur.den) >= (s64) ( (ctx->segnum+1) * ctx->sampdur.num * ts_diff.den)) + return new_segment(ctx); + } + } + + return GF_OK; +} + +static GF_Err scte35dec_process_passthrough(SCTE35DecCtx *ctx, GF_FilterPacket *pck) +{ + GF_FilterPacket *dst_pck = gf_filter_pck_new_clone(ctx->opid, pck, NULL); + if (!dst_pck) + return GF_OUT_OF_MEM; + + u64 cts = gf_filter_pck_get_cts(pck); + if (scte35dec_is_splice_point(ctx, cts)) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec Detected splice point at cts=" LLU " - adding cue start property\n", cts)); + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE)); + } + + return ctx->pck_send(dst_pck); +} + +static const u8 *scte35dec_pck_get_data(SCTE35DecCtx *ctx, GF_FilterPacket *pck, u32 *size, Bool *own) +{ + const u8 *data = NULL; + + if (ctx->data_mode != PROP) { + data = gf_filter_pck_get_data(pck, size); // RAW data_mode + + if (ctx->data_mode == BOX) { + GF_BitStream *bs = gf_bs_new(data, *size, GF_BITSTREAM_READ); + + // not RAW: reset + data = NULL; + *size = 0; + + // parse boxes + while (gf_bs_available(bs) > 0) { + GF_Box *a = NULL; + GF_Err e = gf_isom_box_parse(&a, bs); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec parsing data boxes failed\n")); + break; //don't parse any further + } + if (a->type == GF_ISOM_BOX_TYPE_EMIB) { + if (data && *size) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Scte35Dec detected two 'emib' boxes: switching filter to passthru mode.\n")); + ctx->mode = 1; + gf_isom_box_del(a); + data = NULL; + break; + } + + GF_EventMessageBox *emib = (GF_EventMessageBox*)a; + data = emib->message_data; + *size = emib->message_data_size; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Scte35Dec detected 'emib' box (size=%u))\n", *size)); + + if (ctx->mode == 0 && emib->scheme_id_uri && strcmp(emib->scheme_id_uri, "urn:scte:scte35:2013:bin")) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("Scte35Dec detected 'emib' box with unsupported scheme_id_uri \"%s\": switching filter to passthru mode.\n", emib->scheme_id_uri)); + ctx->mode = 1; + gf_isom_box_del(a); + data = NULL; + break; + } + + *own = GF_TRUE; + emib->message_data = NULL; + emib->message_data_size = 0; + } + gf_isom_box_del(a); + } + gf_bs_del(bs); + } + } else { + const GF_PropertyValue *emsg = gf_filter_pck_get_property_str(pck, "scte35"); + if (emsg && (emsg->type == GF_PROP_DATA) && emsg->value.data.ptr) { + data = emsg->value.data.ptr; + *size = emsg->value.data.size; + } + } + + return data; +} + +static GF_Err scte35dec_process(GF_Filter *filter) +{ + SCTE35DecCtx *ctx = gf_filter_get_udta(filter); + + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + scte35dec_flush(ctx); + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + u64 dts = gf_filter_pck_get_dts(pck); + if (dts == GF_FILTER_NO_TS) { + u32 size = 0; + gf_filter_pck_get_data(pck, &size); + if (!size) { + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_EODS); + if (p && p->value.boolean) { + GF_Err e = scte35dec_process_dispatch(ctx, ctx->clock, 0); + gf_filter_pck_forward(pck, ctx->opid); + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + } + + dts = ctx->last_dispatched_dts + ctx->last_pck_dur; + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Packet with no DTS. Inferring value "LLU".\n", dts)); + } + u32 dur = gf_filter_pck_get_duration(pck); + //GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Scte35Dec Processing packet at dts="LLU" dur=%u\n", dts, dur)); + scte35dec_process_timing(ctx, dts, gf_filter_pck_get_timescale(pck), dur); + + u32 size = 0; + Bool own = GF_FALSE; + const u8 *data = scte35dec_pck_get_data(ctx, pck, &size, &own); + if (data && size && !IS_PASSTHRU) { + GF_Err e = scte35dec_process_emsg(ctx, data, size, dts); + if (own) + gf_free((void*)data); + if (e) + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("Scte35Dec Detected error while processing 'emsg' at dts="LLU"\n", dts)); + } + + GF_Err e; + if (IS_PASSTHRU) { + e = scte35dec_process_passthrough(ctx, pck); + } else { + if (gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM)) { + //DASH: remember first pck of segment + if (ctx->dash_pck) + gf_filter_pck_unref(ctx->dash_pck); + ctx->dash_pck = pck; + ctx->is_dash = GF_TRUE; + gf_filter_pck_ref_props(&ctx->dash_pck); + } + + e = scte35dec_process_dispatch(ctx, dts, dur); + } + + gf_filter_pid_drop_packet(ctx->ipid); + + return e; +} + +static const GF_FilterCapability SCTE35DecCaps = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_METADATA), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_EVTE), + + {0}, + + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_METADATA), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_SCTE35), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_EVTE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), +}; + +#define OFFS(_n) #_n, offsetof(SCTE35DecCtx, _n) +static const GF_FilterArgs SCTE35DecArgs = +{ + { OFFS(mode), "mode to operate in\n" + "- 23001-18: extract SCTE-35 markers as emib/emeb boxes for Event Tracks\n" + "- passthrough: pass-through mode adding cue start property on splice points", GF_PROP_UINT, "23001-18", "23001-18|passthrough", 0}, + { OFFS(sampdur), "segmentation duration in seconds. Default value 0 only flushes when content changes", GF_PROP_FRACTION, "0/1", NULL, 0}, + {0} +}; + +GF_FilterRegister SCTE35DecRegister = { + .name = "scte35dec", + GF_FS_SET_DESCRIPTION("SCTE35 decoder") + GF_FS_SET_HELP("This filter writes the SCTE-35 markers attached as properties to audio and video\n" + "packets or inside a dedicated stream, as 23001-18 'emib' boxes. It also creates\n" + "empty 'emeb' box in between following segmentation as hinted by the graph.") + .private_size = sizeof(SCTE35DecCtx), + .args = SCTE35DecArgs, + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS(SCTE35DecCaps), + .process = scte35dec_process, + .process_event = scte35dec_process_event, + .configure_pid = scte35dec_configure_pid, + .initialize = scte35dec_initialize, + .finalize = scte35dec_finalize, + .hint_class_type = GF_FS_CLASS_DECODER +}; + +const GF_FilterRegister *scte35dec_register(GF_FilterSession *session) +{ + return &SCTE35DecRegister; +}
View file
gpac-2.4.0.tar.gz/src/filters/dec_theora.c -> gpac-26.02.0.tar.gz/src/filters/dec_theora.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2021 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / XIPH Theora decoder filter @@ -303,6 +303,7 @@ .finalize = theoradec_finalize, .configure_pid = theoradec_configure_pid, .process = theoradec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_ttml.c -> gpac-26.02.0.tar.gz/src/filters/dec_ttml.c
Changed
@@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2023 + * Copyright (c) Telecom ParisTech 2020-2024 * All rights reserved * * This file is part of GPAC / TTML decoder filter @@ -580,7 +580,8 @@ .process = ttmldec_process, .configure_pid = ttmldec_configure_pid, .process_event = ttmldec_process_event, - .update_arg = ttmldec_update_arg + .update_arg = ttmldec_update_arg, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_ttxt.c -> gpac-26.02.0.tar.gz/src/filters/dec_ttxt.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / 3GPP/MPEG4 text renderer filter @@ -601,7 +601,7 @@ layout to handle new lines and proper scrolling*/ } TTDTextChunk; -static void ttd_new_text_chunk(GF_TTXTDec *ctx, GF_TextSampleDescriptor *tsd, M_Form *form, u16 *utf16_txt, TTDTextChunk *tc) +static void ttd_new_text_chunk(GF_TTXTDec *ctx, GF_TextSampleDescriptor *tsd, M_Form *form, u16 *utf16_txt, u32 utf_alloc_size, TTDTextChunk *tc) { GF_Node *txt_model, *n2, *txt_material; M_Text *text; @@ -730,9 +730,8 @@ if (i+1==tc->end_char) i++; if (i!=start_char) { - char szLine5000; u32 len; - s16 wsChunk5000, *sp; + s16 *sp; /*splitting lines, duplicate node*/ @@ -755,17 +754,23 @@ if (tc->has_blink && txt_material) gf_list_add(ctx->blink_nodes, txt_material); - memcpy(wsChunk, &utf16_txtstart_char, sizeof(s16)*(i-start_char)); - wsChunki-start_char = 0; - sp = &wsChunk0; - len = gf_utf8_wcstombs(szLine, 5000, (const unsigned short **) &sp); - if (len == GF_UTF8_FAIL) len = 0; - szLinelen = 0; - if (len && (szLinelen-1=='\r')) - szLinelen-1 = 0; - - gf_sg_vrml_mf_append(&text->string, GF_SG_VRML_MFSTRING, (void **) &st); - st->buffer = gf_strdup(szLine); + s16 *wsChunk = gf_malloc(sizeof(s16)*utf_alloc_size); + char *szLine = gf_malloc(utf_alloc_size*2); + if (wsChunk && szLine) { + memcpy(wsChunk, &utf16_txtstart_char, sizeof(s16)*(i-start_char)); + wsChunki-start_char = 0; + sp = &wsChunk0; + len = gf_utf8_wcstombs(szLine, utf_alloc_size*2, (const unsigned short **) &sp); + if (len == GF_UTF8_FAIL) len = 0; + szLinelen = 0; + if (len && (szLinelen-1=='\r')) + szLinelen-1 = 0; + + gf_sg_vrml_mf_append(&text->string, GF_SG_VRML_MFSTRING, (void **) &st); + st->buffer = gf_strdup(szLine); + } + if (szLine) gf_free(szLine); + if (wsChunk) gf_free(wsChunk); } start_char = i+1; if (new_line) { @@ -856,7 +861,6 @@ GF_BoxRecord br; M_Material2D *n; M_Form *form; - u16 utf16_text5000; u32 char_offset, char_count; GF_List *chunks; TTDTextChunk *tc; @@ -1020,6 +1024,7 @@ ctx->tr_scroll = NULL; } + u16 *utf16_text = gf_malloc(sizeof(u16) * ((txt->len/2)*2 + 4) ); if (is_utf_16) { memcpy((char *) utf16_text, txt->text, sizeof(char) * txt->len); ((char *) utf16_text)txt->len = 0; @@ -1027,7 +1032,7 @@ char_count = txt->len / 2; } else { char *p = txt->text; - char_count = gf_utf8_mbstowcs(utf16_text, 2500, (const char **) &p); + char_count = gf_utf8_mbstowcs(utf16_text, txt->len+1, (const char **) &p); if (char_count == GF_UTF8_FAIL) char_count = 0; } @@ -1090,10 +1095,11 @@ while (gf_list_count(chunks)) { tc = (TTDTextChunk*)gf_list_get(chunks, 0); gf_list_rem(chunks, 0); - ttd_new_text_chunk(ctx, td, form, utf16_text, tc); + ttd_new_text_chunk(ctx, td, form, utf16_text, (u32) sizeof(u16)*(txt->len+1), tc); gf_free(tc); } gf_list_del(chunks); + gf_free(utf16_text); if (form->groupsIndex.count && (form->groupsIndex.valsform->groupsIndex.count-1 != -1)) ttd_add_line(form); @@ -1330,7 +1336,11 @@ if (ctx->static_text) ctx->txt_static_alloc = pck_size+1; else return GF_OUT_OF_MEM; } - memcpy(ctx->static_text, pck_data, pck_size); + if (pck_data) + memcpy(ctx->static_text, pck_data, pck_size); + else if (pck_size) + return GF_IO_ERR; + ctx->static_textpck_size = 0; static_txts.text = ctx->static_text; static_txts.len = pck_size; @@ -1511,7 +1521,7 @@ .name = "ttxtdec", GF_FS_SET_DESCRIPTION("TTXT/TX3G decoder") GF_FS_SET_HELP("This filter decodes TTXT/TX3G streams into a BIFS scene graph of the compositor filter.\n" - "The TTXT documentation is available at https://wiki.gpac.io/TTXT-Format-Documentation\n" + "The TTXT documentation is available at https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation\n" "\n" "In stand-alone rendering (no associated video), the filter will use:\n" "- `Width` and `Height` properties of input pid if any\n" @@ -1528,6 +1538,7 @@ .process = ttd_process, .configure_pid = ttd_configure_pid, .process_event = ttd_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif @@ -1540,6 +1551,3 @@ return NULL; #endif } - - -
View file
gpac-2.4.0.tar.gz/src/filters/dec_uncv.c -> gpac-26.02.0.tar.gz/src/filters/dec_uncv.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / uncv pixel format translator filter @@ -159,12 +159,10 @@ static void read_pixel_interleave_comp_yuv_420(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); static void read_pixel_interleave_comp_yuv(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); -static void read_pixel_interleave_comp(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); static void read_pixel_interleave_pixel(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); static void read_pixel_interleave_mixed(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); static void read_pixel_interleave_multiy(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); static void read_pixel_interleave_comp(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); -static void read_pixel_interleave_comp(UNCVDecCtx *ctx, UNCVConfig *config, u32 x, u32 y, u8 *output, u32 offset); static void uncv_del(UNCVConfig *cfg) { @@ -739,6 +737,7 @@ } //block size, figure out how many blocks u32 nb_bits = 0; + u32 nb_blocks_in_pattern = 0; for (u32 i=0; i<ctx->tile_width; i++) { for (u32 c=0; c<clen; c+=2) { u32 cbits = comp_bitsc; @@ -750,17 +749,20 @@ if (nb_bits + cbits > ctx->blocksize_bits) { nb_bits=0; + nb_blocks_in_pattern++; size += config->block_size; //if first comp in block is first comp, pattern is done + //compute number of remaining patterns and skip whole patterns if (c==0) { - u32 nb_pix = i+1; - u32 nb_blocks = 1; - while ((nb_blocks+1) * nb_pix < ctx->tile_width) - nb_blocks++; + u32 nb_pix_in_pattern = i; + u32 nb_patterns = 1; + while ((nb_patterns+1) * nb_pix_in_pattern < ctx->tile_width) + nb_patterns++; - size *= nb_blocks; + size = config->block_size * nb_blocks_in_pattern * nb_patterns; //jump to last non-full pattern - i += nb_pix * (nb_blocks-1); + i = nb_pix_in_pattern * nb_patterns; + nb_blocks_in_pattern = 0; } } nb_bits += cbits; @@ -1322,7 +1324,7 @@ return (u8) c; } -static void uncv_pull_block(UNCVDecCtx *ctx, UNCVConfig *config, BSRead *bsr, u32 comp_idx) +static void uncv_pull_block(UNCVDecCtx *ctx, UNCVConfig *config, BSRead *bsr, u32 comp_idx, u32 x) { u32 i, bits=0, nb_vals=0, bk_idx=0; @@ -1347,8 +1349,14 @@ if (i==config->nb_comps) { //interleave mode with pixel size, do not loop over components if (config->pixel_size) break; + //end of line + if ((x+1) % ctx->tile_width == 0) break; i=0; + x++; } + } else { + //end of line + if ((x+bk_idx) % ctx->tile_width == 0) break; } } @@ -1426,7 +1434,7 @@ bsr->loaded_comps = 0; if (!bsr->loaded_comps) - uncv_pull_block(ctx, config, bsr, comp->comp_idx); + uncv_pull_block(ctx, config, bsr, comp->comp_idx, x); BlockComp *bcomp = &bsr->block_comps bsr->first_comp_idx ; @@ -1680,6 +1688,7 @@ .finalize = uncvdec_finalize, .configure_pid = uncvdec_configure_pid, .process = uncvdec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; const GF_FilterRegister *uncvdec_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dec_vorbis.c -> gpac-26.02.0.tar.gz/src/filters/dec_vorbis.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / XIPH Vorbis decoder filter @@ -310,6 +310,7 @@ .finalize = vorbisdec_finalize, .configure_pid = vorbisdec_configure_pid, .process = vorbisdec_process, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_vtb.c -> gpac-26.02.0.tar.gz/src/filters/dec_vtb.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / VideoToolBox decoder filter @@ -104,10 +104,10 @@ int vtb_type; VTDecompressionSessionRef vtb_session; - CMFormatDescriptionRef fmt_desc; + CMFormatDescriptionRef fmt_desc; - GF_List *frames, *frames_res; - GF_FilterPacket *cur_pck; + GF_List *frames, *frames_res; + GF_FilterPacket *cur_pck; GF_Mutex *mx; u8 chroma_format, luma_bit_depth, chroma_bit_depth; Bool frame_size_changed; @@ -189,7 +189,7 @@ u64 cts, dts; gf_assert(ctx->cur_pck); - if (!image) { + if (!image) { if (status != kCVReturnSuccess) { ctx->last_error = GF_NON_COMPLIANT_BITSTREAM; ctx->nb_consecutive_errors++; @@ -204,11 +204,11 @@ } return; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("VTB No output buffer\n")); - return; - } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("VTB No output buffer\n")); + return; + } if (gf_filter_pck_get_seek_flag(ctx->cur_pck) ) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("VTB Frame marked as seek, not dispatching - status %d\n", status)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("VTB Frame marked as seek, not dispatching - status %d\n", status)); return; } @@ -287,35 +287,35 @@ static CFDictionaryRef vtbdec_create_buffer_attributes(GF_VTBDecCtx *ctx, OSType pix_fmt) { - CFMutableDictionaryRef buffer_attributes; - CFMutableDictionaryRef surf_props; - CFNumberRef w; - CFNumberRef h; - CFNumberRef pixel_fmt; - - w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->width); - h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->height); - pixel_fmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt); - - buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - surf_props = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFMutableDictionaryRef buffer_attributes; + CFMutableDictionaryRef surf_props; + CFNumberRef w; + CFNumberRef h; + CFNumberRef pixel_fmt; + + w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->width); + h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->height); + pixel_fmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt); + + buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + surf_props = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w); - CFRelease(w); - CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h); - CFRelease(h); - CFDictionarySetValue(buffer_attributes, kCVPixelBufferPixelFormatTypeKey, pixel_fmt); - CFRelease(pixel_fmt); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w); + CFRelease(w); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h); + CFRelease(h); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferPixelFormatTypeKey, pixel_fmt); + CFRelease(pixel_fmt); #ifdef VTB_GL_TEXTURE if (ctx->use_gl_textures) CFDictionarySetValue(buffer_attributes, GF_kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); #endif - CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfacePropertiesKey, surf_props); - CFRelease(surf_props); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfacePropertiesKey, surf_props); + CFRelease(surf_props); - return buffer_attributes; + return buffer_attributes; } static GF_Err vtbdec_init_decoder(GF_Filter *filter, GF_VTBDecCtx *ctx) @@ -323,8 +323,8 @@ CFMutableDictionaryRef dec_dsi, dec_type; CFMutableDictionaryRef dsi; VTDecompressionOutputCallbackRecord cbacks; - CFDictionaryRef buffer_attribs; - OSStatus status; + CFDictionaryRef buffer_attribs; + OSStatus status; OSType kColorSpace; const GF_PropertyValue *p; CFDataRef data = NULL; @@ -335,7 +335,7 @@ const char *codec_name = NULL; w = h = 0; - dec_dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + dec_dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); switch (ctx->ofmt) { case GF_PIXEL_YUV: @@ -368,7 +368,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); switch (ctx->codecid) { - case GF_CODECID_AVC: + case GF_CODECID_AVC: if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs)) { s32 idx; u32 i; @@ -486,9 +486,9 @@ gf_free(dsi_data); } - break; + break; - case GF_CODECID_HEVC: + case GF_CODECID_HEVC: if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) && gf_list_count(ctx->VPSs)) { s32 idx; u32 i; @@ -634,7 +634,7 @@ gf_free(dsi_data); } - break; + break; case GF_CODECID_MPEG2_SIMPLE: case GF_CODECID_MPEG2_MAIN: @@ -643,7 +643,7 @@ case GF_CODECID_MPEG2_HIGH: case GF_CODECID_MPEG2_422: - ctx->vtb_type = 'mp2v'; //kCMVideoCodecType_MPEG2Video; + ctx->vtb_type = 'mp2v'; //kCMVideoCodecType_MPEG2Video; codec_name = "MPEG2"; if (!ctx->width || !ctx->height) { ctx->init_mpeg12 = GF_TRUE; @@ -651,7 +651,7 @@ } ctx->init_mpeg12 = GF_FALSE; ctx->reconfig_needed = GF_FALSE; - break; + break; case GF_CODECID_MPEG1: ctx->vtb_type = 'mp1v'; //kCMVideoCodecType_MPEG1Video; @@ -663,7 +663,7 @@ ctx->init_mpeg12 = GF_FALSE; ctx->reconfig_needed = GF_FALSE; break; - case GF_CODECID_MPEG4_PART2 : + case GF_CODECID_MPEG4_PART2 : { char *vosh = NULL; u32 vosh_size = 0; @@ -715,8 +715,8 @@ ctx->skip_mpeg4_vosh = GF_TRUE; return GF_OK; } - break; - } + break; + } case GF_CODECID_H263: case GF_CODECID_S263: ctx->reorder_probe = 0; @@ -752,20 +752,20 @@ default : ctx->reconfig_needed = GF_FALSE; return GF_NOT_SUPPORTED; - } + } //not yet ready if (! ctx->width || !ctx->height) return GF_OK; - /*status = */CMVideoFormatDescriptionCreate(kCFAllocatorDefault, ctx->vtb_type, ctx->width, ctx->height, dec_dsi, &ctx->fmt_desc); + /*status = */CMVideoFormatDescriptionCreate(kCFAllocatorDefault, ctx->vtb_type, ctx->width, ctx->height, dec_dsi, &ctx->fmt_desc); - if (!ctx->fmt_desc) { + if (!ctx->fmt_desc) { if (dec_dsi) CFRelease(dec_dsi); - return GF_NON_COMPLIANT_BITSTREAM; - } + return GF_NON_COMPLIANT_BITSTREAM; + } buffer_attribs = vtbdec_create_buffer_attributes(ctx, kColorSpace); cbacks.decompressionOutputCallback = vtbdec_on_frame; - cbacks.decompressionOutputRefCon = ctx; + cbacks.decompressionOutputRefCon = ctx; status = 1; if (!ctx->disable_hw) { @@ -787,27 +787,27 @@ if (dec_dsi) CFRelease(dec_dsi); - if (buffer_attribs) - CFRelease(buffer_attribs); - - switch (status) { - case kVTVideoDecoderNotAvailableNowErr: - case kVTVideoDecoderUnsupportedDataFormatErr: - return GF_NOT_SUPPORTED; - case kVTVideoDecoderMalfunctionErr: - return GF_IO_ERR; - case kVTVideoDecoderBadDataErr : - case -8969: - return GF_NOT_SUPPORTED; + if (buffer_attribs) + CFRelease(buffer_attribs); + + switch (status) { + case kVTVideoDecoderNotAvailableNowErr: + case kVTVideoDecoderUnsupportedDataFormatErr: + return GF_NOT_SUPPORTED; + case kVTVideoDecoderMalfunctionErr: + return GF_IO_ERR; + case kVTVideoDecoderBadDataErr : + case -8969: + return GF_NOT_SUPPORTED; case kVTPixelTransferNotSupportedErr: case kVTCouldNotFindVideoDecoderErr: return GF_NOT_SUPPORTED; - case 0: - break; - default: + case 0: + break; + default: return GF_NOT_SUPPORTED; - } + } //good to go ! ctx->stride = ctx->width; @@ -1247,11 +1247,12 @@ ctx->VPSs = NULL; } -static GF_Err vtbdec_parse_nal_units(GF_Filter *filter, GF_VTBDecCtx *ctx, char *inBuffer, u32 inBufferLength, char **out_buffer, u32 *out_size) +static GF_Err vtbdec_parse_nal_units(GF_Filter *filter, GF_VTBDecCtx *ctx, char *inBuffer, u32 inBufferLength, u64 dts, char **out_buffer, u32 *out_size) { u32 i, sc_size=0; char *ptr = inBuffer; u32 nal_size; + u32 nb_nal_size_zero = 0; GF_Err e = GF_OK; Bool reassign_bs = GF_TRUE; Bool check_reconfig = GF_FALSE; @@ -1277,13 +1278,17 @@ u8 nal_type, nal_hdr; if (ctx->nalu_size_length) { + if (inBufferLength<ctx->nalu_size_length) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error parsing NAL in sample DTS "LLU": sizeLength %u but %u bytes only in payload\n", dts, ctx->nalu_size_length, inBufferLength)); + break; + } nal_size = 0; for (i=0; i<ctx->nalu_size_length; i++) { nal_size = (nal_size<<8) + ((u8) ptri); } if (nal_size > inBufferLength) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error parsing NAL: size indicated %u but %u bytes only in payload\n", nal_size, inBufferLength)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error parsing NAL in sample DTS "LLU": size indicated %u but %u bytes only in payload\n", dts, nal_size, inBufferLength)); break; } ptr += ctx->nalu_size_length; @@ -1291,20 +1296,24 @@ nal_size = gf_media_nalu_next_start_code((const u8 *) ptr, inBufferLength, &sc_size); } - if (nal_size==0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error parsing NAL: size 0 shall never happen\n", nal_size)); - - if (ctx->nalu_size_length) { - if (inBufferLength < ctx->nalu_size_length) break; - inBufferLength -= ctx->nalu_size_length; - } else { - if (!sc_size || (inBufferLength < sc_size)) break; - inBufferLength -= sc_size; - ptr += sc_size; - } - continue; - } - + if (nal_size==0) { + //two consecutive nalsize 0 abirts + if (nb_nal_size_zero) break; + nb_nal_size_zero++; + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error parsing NAL in sample DTS "LLU": size 0 shall never happen\n", dts)); + + if (ctx->nalu_size_length) { + if (inBufferLength < ctx->nalu_size_length) break; + inBufferLength -= ctx->nalu_size_length; + } else { + if (!sc_size || (inBufferLength < sc_size)) break; + inBufferLength -= sc_size; + ptr += sc_size; + } + continue; + } + nb_nal_size_zero = 0; + if (ctx->is_avc) { if (!ctx->nal_bs) ctx->nal_bs = gf_bs_new(ptr, nal_size, GF_BITSTREAM_READ); else gf_bs_reassign_buffer(ctx->nal_bs, ptr, nal_size); @@ -1443,7 +1452,7 @@ static GF_Err vtbdec_flush_frame(GF_Filter *filter, GF_VTBDecCtx *ctx) { GF_VTBHWFrame *vtbframe; - OSStatus status; + OSStatus status; OSType type; if (ctx->no_copy) return vtbdec_send_output_frame(filter, ctx); @@ -1455,13 +1464,13 @@ status = CVPixelBufferLockBaseAddress(vtbframe->frame, kCVPixelBufferLock_ReadOnly); - if (status != kCVReturnSuccess) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error locking frame data\n")); + if (status != kCVReturnSuccess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Error locking frame data\n")); gf_mx_p(ctx->mx); gf_list_add(ctx->frames_res, vtbframe); gf_mx_v(ctx->mx); - return GF_IO_ERR; - } + return GF_IO_ERR; + } type = CVPixelBufferGetPixelFormatType(vtbframe->frame); @@ -1473,7 +1482,7 @@ || (type==kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) || (type==kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) ) { - u32 i, j, nb_planes = (u32) CVPixelBufferGetPlaneCount(vtbframe->frame); + u32 i, j, nb_planes = (u32) CVPixelBufferGetPlaneCount(vtbframe->frame); u8 *dst; u32 stride = (u32) CVPixelBufferGetBytesPerRowOfPlane(vtbframe->frame, 0); @@ -1522,7 +1531,7 @@ vtbframe->pck_src = NULL; gf_filter_pck_send(dst_pck); } - CVPixelBufferUnlockBaseAddress(vtbframe->frame, kCVPixelBufferLock_ReadOnly); + CVPixelBufferUnlockBaseAddress(vtbframe->frame, kCVPixelBufferLock_ReadOnly); gf_mx_p(ctx->mx); gf_list_add(ctx->frames_res, vtbframe); gf_mx_v(ctx->mx); @@ -1531,9 +1540,9 @@ static GF_Err vtbdec_process(GF_Filter *filter) { - OSStatus status; - CMSampleBufferRef sample = NULL; - CMBlockBufferRef block_buffer = NULL; + OSStatus status; + CMSampleBufferRef sample = NULL; + CMBlockBufferRef block_buffer = NULL; char *in_data=NULL; u32 in_data_size; char *in_buffer; @@ -1657,7 +1666,7 @@ //Always parse AVC data , remove SPS/PPS/... and reconfig if needed if (ctx->is_annex_b || ctx->nalu_size_length) { - e = vtbdec_parse_nal_units(filter, ctx, in_buffer, in_buffer_size, &in_data, &in_data_size); + e = vtbdec_parse_nal_units(filter, ctx, in_buffer, in_buffer_size, gf_filter_pck_get_dts(pck) , &in_data, &in_data_size); if (e) { gf_filter_pid_drop_packet(ref_pid); return e; @@ -1702,14 +1711,16 @@ status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, in_data, in_data_size, kCFAllocatorNull, NULL, 0, in_data_size, 0, &block_buffer); if (status || (block_buffer == NULL) ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Failed to allocate block buffer of %d bytes\n", in_data_size)); + if (in_data_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Failed to allocate block buffer of %d bytes\n", in_data_size)); + } gf_filter_pid_drop_packet(ref_pid); return GF_IO_ERR; } status = CMSampleBufferCreate(kCFAllocatorDefault, block_buffer, TRUE, NULL, NULL, ctx->fmt_desc, 1, 0, NULL, 0, NULL, &sample); - if (status || (sample==NULL)) { + if (status || (sample==NULL)) { if (block_buffer) CFRelease(block_buffer); @@ -1720,8 +1731,8 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("VTB Decoding frame DTS "LLU" ms\n", min_dts)); ctx->cur_pck = pck; ctx->last_error = GF_OK; - status = VTDecompressionSessionDecodeFrame(ctx->vtb_session, sample, 0, NULL, 0); - if (!status) + status = VTDecompressionSessionDecodeFrame(ctx->vtb_session, sample, 0, NULL, 0); + if (!status) status = VTDecompressionSessionWaitForAsynchronousFrames(ctx->vtb_session); @@ -1773,11 +1784,11 @@ #endif if (f->frame) { - CVPixelBufferRelease(f->frame); - f->frame = NULL; - } + CVPixelBufferRelease(f->frame); + f->frame = NULL; + } - safe_int_dec(&f->ctx->decoded_frames_pending); + safe_int_dec(&f->ctx->decoded_frames_pending); gf_mx_p(f->ctx->mx); gf_list_add(f->ctx->frames_res, f); gf_mx_v(f->ctx->mx); @@ -1785,7 +1796,7 @@ GF_Err vtbframe_get_plane(GF_FilterFrameInterface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) { - OSStatus status; + OSStatus status; GF_Err e; GF_VTBHWFrame *f = (GF_VTBHWFrame *)frame->user_data; if (! outPlane || !outStride) return GF_BAD_PARAM; @@ -1801,7 +1812,7 @@ } e = GF_OK; - if (CVPixelBufferIsPlanar(f->frame)) { + if (CVPixelBufferIsPlanar(f->frame)) { *outPlane = CVPixelBufferGetBaseAddressOfPlane(f->frame, plane_idx); if (*outPlane) *outStride = (u32) CVPixelBufferGetBytesPerRowOfPlane(f->frame, plane_idx); @@ -1835,7 +1846,7 @@ GF_Err vtbframe_get_gl_texture(GF_FilterFrameInterface *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix) { - OSStatus status=kCVReturnSuccess; + OSStatus status=kCVReturnSuccess; GLenum target_fmt; u32 w, h; GF_CVGLTextureREF *outTexture=NULL; @@ -1874,7 +1885,7 @@ f->locked = GF_TRUE; } - if (CVPixelBufferIsPlanar(f->frame)) { + if (CVPixelBufferIsPlanar(f->frame)) { w = (u32) CVPixelBufferGetPlaneCount(f->frame); if (plane_idx >= (u32) CVPixelBufferGetPlaneCount(f->frame)) { GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("VTB Wrong plane index\n")); @@ -2008,7 +2019,7 @@ #ifdef VTB_GL_TEXTURE if (ctx->cache_texture) { CFRelease(ctx->cache_texture); - } + } #endif if (ctx->frames) { @@ -2092,6 +2103,7 @@ .configure_pid = vtbdec_configure_pid, .process = vtbdec_process, .process_event = vtbdec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; #else
View file
gpac-2.4.0.tar.gz/src/filters/dec_webcodec.c -> gpac-26.02.0.tar.gz/src/filters/dec_webcodec.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / WebCodec decoder filter @@ -26,7 +26,7 @@ #include <gpac/internal/media_dev.h> #include <gpac/constants.h> -#if defined(GPAC_CONFIG_EMSCRIPTEN) +#ifndef GPAC_DISABLE_WEBCODEC typedef struct { @@ -48,6 +48,8 @@ char szCodecRFC6381_CODEC_NAME_SIZE_MAX; } GF_WCDecCtx; +#if defined(GPAC_CONFIG_EMSCRIPTEN) + GF_EXPORT void wcdec_on_error(GF_WCDecCtx *ctx, int state, char *msg) { @@ -66,7 +68,7 @@ } EM_JS(int, wcdec_init, (int wc_ctx, int _codec_str, int width, int height, int sample_rate, int num_channels, int dsi, int dsi_size), { - let codec_str = _codec_str ? libgpac.UTF8ToString(_codec_str) : null; + let codec_str = _codec_str ? UTF8ToString(_codec_str) : null; let config = {}; config.codec = codec_str; let dec_class = null; @@ -80,7 +82,7 @@ dec_class = AudioDecoder; } if (dsi_size) { - config.description = new Uint8Array(libgpac.HEAPU8.buffer, dsi, dsi_size); + config.description = new Uint8Array(HEAPU8.buffer, dsi, dsi_size); } if (typeof libgpac._to_webdec != 'function') { @@ -91,11 +93,11 @@ } return null; }; - libgpac._on_wcdec_error = libgpac.cwrap('wcdec_on_error', null, 'number', 'number', 'string'); - libgpac._on_wcdec_frame = libgpac.cwrap('wcdec_on_video', null, 'number', 'bigint', 'string', 'number', 'number'); - libgpac._on_wcdec_audio = libgpac.cwrap('wcdec_on_audio', null, 'number', 'bigint', 'string', 'number', 'number', 'number'); - libgpac._on_wcdec_flush = libgpac.cwrap('wcdec_on_flush', null, 'number'); - libgpac._on_wcdec_frame_copy = libgpac.cwrap('wcdec_on_frame_copy', null, 'number', 'number', 'number'); + libgpac._on_wcdec_error = cwrap('wcdec_on_error', null, 'number', 'number', 'string'); + libgpac._on_wcdec_frame = cwrap('wcdec_on_video', null, 'number', 'bigint', 'string', 'number', 'number'); + libgpac._on_wcdec_audio = cwrap('wcdec_on_audio', null, 'number', 'bigint', 'string', 'number', 'number', 'number'); + libgpac._on_wcdec_flush = cwrap('wcdec_on_flush', null, 'number'); + libgpac._on_wcdec_frame_copy = cwrap('wcdec_on_frame_copy', null, 'number', 'number', 'number'); } let c = libgpac._to_webdec(wc_ctx); if (!c) { @@ -256,7 +258,7 @@ const chunk = new EncodedVideoChunk({ timestamp: Number(ts), type: key ? "key" : "delta", - data: new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size) + data: new Uint8Array(HEAPU8.buffer, buf, buf_size) }); c.dec.decode(chunk); }) @@ -267,7 +269,7 @@ const chunk = new EncodedAudioChunk({ timestamp: Number(ts), type: key ? "key" : "delta", - data: new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size) + data: new Uint8Array(HEAPU8.buffer, buf, buf_size) }); c.dec.decode(chunk); }) @@ -277,7 +279,7 @@ if (!c || !c._frame || !dst_pck) return; //setup dst - let ab = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let ab = new Uint8Array(HEAPU8.buffer, buf, buf_size); let frame = c._frame; c._frame = null; frame.copyTo(ab).then( layout => { @@ -381,7 +383,7 @@ if (!c || !c._frame) return; //setup dst - let ab = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let ab = new Uint8Array(HEAPU8.buffer, buf, buf_size); c._frame.copyTo(ab, { planeIndex: plane_index }); }) @@ -618,6 +620,17 @@ CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), }; +#else +static GF_Err wcdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + return GF_NOT_SUPPORTED; +} +static GF_Err wcdec_process(GF_Filter *filter) +{ + return GF_NOT_SUPPORTED; +} +#endif + static GF_FilterCapability WCDecCapsAV = { CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), @@ -635,7 +648,7 @@ static const GF_FilterArgs WCDecArgs = { - { OFFS(queued), "Maximum number of packets to queue in webcodec instance", GF_PROP_UINT, "10", NULL, 0}, + { OFFS(queued), "maximum number of packets to queue in webcodec instance", GF_PROP_UINT, "10", NULL, 0}, {0} }; @@ -647,16 +660,22 @@ SETCAPS(WCDecCapsAV), .flags = GF_FS_REG_SINGLE_THREAD|GF_FS_REG_ASYNC_BLOCK, .private_size = sizeof(GF_WCDecCtx), +#if defined(GPAC_CONFIG_EMSCRIPTEN) .initialize = wcdec_initialize, .finalize = wcdec_finalize, + .process_event = wcdec_process_event, +#endif .configure_pid = wcdec_configure_pid, .process = wcdec_process, - .process_event = wcdec_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; +#endif //GPAC_DISABLE_WEBCODEC const GF_FilterRegister *wcdec_register(GF_FilterSession *session) { +#ifndef GPAC_DISABLE_WEBCODEC +#if defined(GPAC_CONFIG_EMSCRIPTEN) int has_webv_decode = EM_ASM_INT({ if (typeof VideoDecoder == 'undefined') return 0; return 1; @@ -678,6 +697,13 @@ GF_WCDecCtxRegister.nb_caps = sizeof(WCDecCapsV)/sizeof(GF_FilterCapability); } GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("WebDec AudioDecoder %d - VideoDecoder %d\n", has_weba_decode, has_webv_decode)); +#else + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + GF_WCDecCtxRegister.version = "! Warning: WebCodec NOT AVAILABLE IN THIS BUILD !"; +#endif return &GF_WCDecCtxRegister; -} +#else + return NULL; #endif +}
View file
gpac-2.4.0.tar.gz/src/filters/dec_webvtt.c -> gpac-26.02.0.tar.gz/src/filters/dec_webvtt.c
Changed
@@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Cyril Concolato - Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2013-2023 + * Copyright (c) Telecom ParisTech 2013-2024 * All rights reserved * * This file is part of GPAC / WebVTT decoder filter @@ -587,7 +587,8 @@ .process = vttd_process, .configure_pid = vttd_configure_pid, .process_event = vttd_process_event, - .update_arg = vtt_update_arg + .update_arg = vtt_update_arg, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/dec_xvid.c -> gpac-26.02.0.tar.gz/src/filters/dec_xvid.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / MPEG-4 visual p2 xvid decoder filter @@ -459,7 +459,8 @@ .process = xviddec_process, //use low priorty, below ffmpeg one, so that hardware decs/other native impl in gpac can take over if needed //don't use lowest one since we use this for scalable codecs - .priority = 100 + .priority = 100, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/decrypt_cenc_isma.c -> gpac-26.02.0.tar.gz/src/filters/decrypt_cenc_isma.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / CENC and ISMA decrypt filter @@ -48,27 +48,26 @@ DECRYPT_STATE_PLAY, }; -enum -{ +GF_OPT_ENUM (CENCDecDecryptMode, DECRYPT_FULL=0, DECRYPT_NOKEY, DECRYPT_SKIP, DECRYPT_PAD0, DECRYPT_PAD1, DECRYPT_PADSC, -}; +); typedef struct { GF_Crypt *crypt; bin128 key; - u32 key_valid; + Bool key_valid; } CENCDecKey; typedef struct { const char *cfile; - u32 decrypt; + CENCDecDecryptMode decrypt; GF_PropUIntList drop_keys; GF_PropStringList kids; GF_PropStringList keys; @@ -130,6 +129,7 @@ u32 codec_id; Bool force_hls_iv; + Bool hls_ignore_iv; Bool gpac_master_leaf; bin128 master_key; @@ -406,7 +406,7 @@ in_data = gf_filter_pck_get_data(in_pck, &data_size); gf_bs_reassign_buffer(ctx->bs_r, in_data, data_size); - + if (cstr->selective_encryption) { if (gf_bs_read_int(ctx->bs_r, 1)) is_encrypted=GF_TRUE; gf_bs_read_int(ctx->bs_r, 7); @@ -761,7 +761,7 @@ memset(cstr->crypts, 0, sizeof(CENCDecKey)); } memcpy(cstr->crypts0.key, cstr->keys0, sizeof(bin128)); - cstr->crypts0.key_valid = 1; + cstr->crypts0.key_valid = GF_TRUE; } else { cstr->state = DECRYPT_STATE_ERROR; } @@ -815,6 +815,7 @@ #ifdef GPAC_USE_DOWNLOADER GF_DownloadManager *dm = gf_filter_get_download_manager(ctx->filter); + if (!dm) return GF_SERVICE_ERROR; cstr->sess = gf_dm_sess_new(dm, ck_url, 0, ck_http_io, cstr, &e); if (e) return e; ctx->pending_keys++; @@ -859,7 +860,7 @@ memset(cstr->crypts, 0, sizeof(CENCDecKey)); } memcpy(cstr->crypts0.key, cstr->keys0, sizeof(bin128)); - cstr->crypts0.key_valid = 1; + cstr->crypts0.key_valid = GF_TRUE; } else { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("CENC/HLS Invalid key size, greater than 16 bytes\n")) cstr->state = DECRYPT_STATE_ERROR; @@ -886,7 +887,8 @@ cstr->is_hls = GF_TRUE; //copy IV - memcpy(cstr->hls_IV, key_IV, sizeof(bin128)); + if (!cstr->hls_ignore_iv) + memcpy(cstr->hls_IV, key_IV, sizeof(bin128)); //switch key if needed IV if (cstr->hls_key_url && key_url && !strcmp(cstr->hls_key_url, key_url)) { if (cstr->crypt_init) @@ -912,6 +914,7 @@ cstr->KID_count = 1; cstr->keys = (bin128 *)gf_realloc(cstr->keys, cstr->KID_count*sizeof(bin128)); + cstr->hls_ignore_iv = GF_FALSE; if (!strncmp(key_url, "urn:gpac:keys:value:", 20)) { u32 i; u8 *key_data = (u8 *) cstr->keys0; @@ -936,7 +939,12 @@ else if (gf_url_is_local(key_url)) { FILE *fkey = gf_fopen(key_url, "rb"); if (!fkey) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("CENC/HLS key %s not found\n", key_url)) + //using DRM config file, ignore IV from stream + if (cstr->keys) { + cstr->hls_ignore_iv = GF_TRUE; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("CENC/HLS key %s not found\n", key_url)) + } return GF_URL_ERROR; } else { u32 read = (u32) gf_fread(cstr->keys0, 16, fkey); @@ -952,6 +960,7 @@ GF_Err e = GF_SERVICE_ERROR; #ifdef GPAC_USE_DOWNLOADER GF_DownloadManager *dm = gf_filter_get_download_manager(ctx->filter); + if (!dm) return GF_SERVICE_ERROR; GF_DownloadSession *sess = gf_dm_sess_new(dm, key_url, GF_NETIO_SESSION_NOT_CACHED, hls_kms_io, cstr, &e); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("CENC/HLS Failed to setup download session for key %s: %s\n", key_url, gf_error_to_string(e))) @@ -969,7 +978,7 @@ memset(cstr->crypts, 0, sizeof(CENCDecKey)); } memcpy(cstr->crypts0.key, cstr->keys0, sizeof(bin128)); - cstr->crypts0.key_valid = 1; + cstr->crypts0.key_valid = GF_TRUE; return GF_OK; } @@ -1182,7 +1191,7 @@ ) return GF_NOT_SUPPORTED; - if (scheme_version != 0x00010000) { + if ((scheme_type != GF_HLS_SAMPLE_AES_SCHEME) && (scheme_version != 0x00010000)) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("CENC/ISMA Invalid scheme version %08X for scheme type %s, results might be wrong\n", scheme_version, gf_4cc_to_str(scheme_type) )); } @@ -1408,10 +1417,13 @@ if (prop || ctx->cinfo) { e = cenc_dec_setup_cenc(ctx, cstr, scheme_type, scheme_version, scheme_uri, kms_uri); if (e) return e; - if (!cstr->cenc_ki && cstr->nb_crypts && cstr->crypts0.crypt && !cstr->crypts0.key_valid) { - bin128 IV; + if (!cstr->cenc_ki && cstr->nb_crypts && cstr->KID_count && !cstr->crypts0.key_valid) { memcpy(cstr->crypts0.key, cstr->keys0, 16); - cenc_dec_push_iv(cstr, 0, IV, 0, 16, cstr->hls_IV); + cstr->crypts0.key_valid = GF_TRUE; + if (cstr->crypts0.crypt) { + bin128 IV; + cenc_dec_push_iv(cstr, 0, IV, 0, 16, cstr->hls_IV); + } } return GF_OK; } @@ -1502,7 +1514,7 @@ //if (!ctx->nb_allow_play) return GF_AUTHENTICATION_FAILURE; //ctx->nb_allow_play--; - /*open decrypter - we do NOT initialize decrypter; it wil be done when we decrypt the first crypted sample*/ + /*open decrypter - we do NOT initialize decrypter; it will be done when we decrypt the first crypted sample*/ for (i=0; i<cstr->nb_crypts; i++) { gf_assert(!cstr->cryptsi.crypt); if (cstr->is_cenc) @@ -1589,8 +1601,6 @@ return GF_OK; } -u8 key_info_get_iv_size(const u8 *key_info, u32 nb_keys, u32 idx, u8 *const_iv_size, const u8 **const_iv); - static GFINLINE void cenc_decrypt_block(GF_CENCDecCtx *ctx, GF_Crypt *crypt, Bool valid_key, u8 *data, u32 size) { if (!valid_key) { @@ -1631,7 +1641,7 @@ return GF_SERVICE_ERROR; } - prop = gf_filter_pck_get_property(in_pck, GF_PROP_PID_CENC_PSSH); + prop = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_CENC_PSSH); if (prop && (prop->type==GF_PROP_DATA) && prop->value.data.ptr) { cenc_dec_load_pssh(ctx, cstr, prop, GF_TRUE, NULL); } @@ -1684,7 +1694,7 @@ e = GF_NON_COMPLIANT_BITSTREAM; goto exit; } - IV_size = key_info_get_iv_size(cstr->cenc_ki->value.data.ptr, cstr->cenc_ki->value.data.size, kidx, NULL, NULL); + IV_size = gf_cenc_key_info_get_iv_size(cstr->cenc_ki->value.data.ptr, cstr->cenc_ki->value.data.size, kidx, NULL, NULL); if (!IV_size) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("CENC invalid SAI multikey with IV size 0\n" )); e = GF_NON_COMPLIANT_BITSTREAM; @@ -1702,7 +1712,7 @@ for (k=0; k<cstr->multikey; k++) { u8 const_iv_size; const u8 *const_iv=NULL; - u8 IV_size = key_info_get_iv_size(cstr->cenc_ki->value.data.ptr, cstr->cenc_ki->value.data.size, k+1, &const_iv_size, &const_iv); + u8 IV_size = gf_cenc_key_info_get_iv_size(cstr->cenc_ki->value.data.ptr, cstr->cenc_ki->value.data.size, k+1, &const_iv_size, &const_iv); if (IV_size) continue; memset(IV, 0, sizeof(char)*17); e = cenc_dec_push_iv(cstr, k, IV, 0, const_iv_size, const_iv); @@ -1801,7 +1811,7 @@ bytes_encrypted_data = gf_bs_read_u32(ctx->bs_r); if (kidx) { - key_info_get_iv_size(cstr->cenc_ki->value.data.ptr, cstr->cenc_ki->value.data.size, kidx, &const_iv_size, &const_iv); + gf_cenc_key_info_get_iv_size(cstr->cenc_ki->value.data.ptr, cstr->cenc_ki->value.data.size, kidx, &const_iv_size, &const_iv); kidx-=1; } //to clarify in the spec: kidx 0 should be allowed for clear subsamples @@ -1958,6 +1968,12 @@ if (e) goto exit; memcpy(cstr->hls_IV, cstr->cenc_ki->value.data.ptr + 21, 16); } else { + //no KID sent, typically seen when decrypting SAES MPEG-2 TS without a HLS manifest + //we default to first key specified in DRM config + if ((cstr->nb_crypts==1) && !cstr->crypts0.key_valid && !cstr->inband_keys) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("HLS_SAES No KID signaled, using first key supplied\n" ) ); + cstr->crypts0.key_valid = GF_TRUE; + } e = cenc_dec_push_iv(cstr, 0, cstr->hls_IV, 16, 0, NULL); if (e) goto exit; } @@ -2511,7 +2527,7 @@ "\n" "For HLS, key is retrieved according to the key URI in the manifest.\n" "Otherwise, the filter uses a configuration file.\n" - "The syntax is available at https://wiki.gpac.io/Common-Encryption\n" + "The syntax is available at https://wiki.gpac.io/xmlformats/Common-Encryption\n" "The DRM config file can be set per PID using the property `DecryptInfo` (highest priority), `CryptInfo` (lower priority) " "or set at the filter level using -cfile() (lowest priority).\n" "When the file is set per PID, the first `CryptInfo` with the same ID is used, otherwise the first `CryptInfo` is used." @@ -2526,8 +2542,8 @@ .initialize = cenc_dec_initialize, .finalize = cenc_dec_finalize, .process = cenc_dec_process, - .process_event = cenc_dec_process_event - //for now only one PID per CENC decryptor instance, could be further optimized + .process_event = cenc_dec_process_event, + .hint_class_type = GF_FS_CLASS_CRYPTO }; #endif /*GPAC_DISABLE_CRYPTO*/
View file
gpac-2.4.0.tar.gz/src/filters/dmx_avi.c -> gpac-26.02.0.tar.gz/src/filters/dmx_avi.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / AVI demuxer filter @@ -606,7 +606,7 @@ pc = (u32) ( file_offset * 100 / ctx->file_size); } - if (st->audio_bps) { + if (st->audio_bps && st->nb_channels && (size < GF_INT_MAX/8)) { u32 nb_samples = (8*size) / (st->audio_bps * st->nb_channels); gf_filter_pck_set_cts(dst_pck, st->audio_ts); gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); @@ -634,7 +634,7 @@ if (gf_filter_reporting_enabled(filter)) { char szStatus1024; u32 v_pc=0; - if (ctx->v_in_use) { + if (ctx->v_in_use && ctx->nb_frames) { v_pc = ctx->cur_frame * 100; v_pc /= ctx->nb_frames; } @@ -733,6 +733,7 @@ .process = avidmx_process, .process_event = avidmx_process_event, .probe_data = avidmx_probe_data, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; #endif // GPAC_DISABLE_AVILIB
View file
gpac-2.4.0.tar.gz/src/filters/dmx_dash.c -> gpac-26.02.0.tar.gz/src/filters/dmx_dash.c
Changed
@@ -29,6 +29,7 @@ #ifndef GPAC_DISABLE_DASHIN #include <gpac/dash.h> +#include <gpac/network.h> #ifdef GPAC_HAS_QJS #include "../quickjs/quickjs.h" @@ -39,22 +40,20 @@ #define DASHIN_FILE_EXT "mpd|m3u8|3gm|ism" -enum -{ +GF_OPT_ENUM (GF_DASHSegmentForwardMode, DFWD_OFF = 0, DFWD_FILE, //all modes below forward frames, not files DFWD_SBOUND, DFWD_SBOUND_MANIFEST, -}; +); -enum -{ +GF_OPT_ENUM (GF_DASHPlayoutBufferMode, BMIN_NO = 0, BMIN_AUTO, BMIN_MPD, -}; +); enum { FLAG_PERIOD_SWITCH = 1, @@ -62,24 +61,34 @@ FLAG_FIRST_IN_SEG = 1<<2, }; + + +GF_OPT_ENUM (GF_DASHBaseURLControlMode, + BURL_STRIP = 0, + BURL_KEEP, + BURL_INJECT, +); + typedef struct { //opts - s32 shift_utc, spd, route_shift; + s32 shift_utc, spd, mcast_shift; u32 max_buffer, tiles_rate, segstore, delay40X, exp_threshold, switch_count, bwcheck; s32 auto_switch; s32 init_timeshift; - Bool server_utc, screen_res, aggressive, speedadapt, fmodefwd, skip_lqt, llhls_merge, filemode, chain_mode, asloop; - u32 forward; + Bool server_utc, screen_res, aggressive, speedadapt, fmodefwd, skip_lqt, llhls_merge, filemode, asloop; + u32 chain_mode; + GF_DASHCrossASMode xas; + GF_DASHSegmentForwardMode forward; GF_PropUIntList debug_as; GF_DASHInitialSelectionMode start_with; GF_DASHTileAdaptationMode tile_mode; char *algo; Bool max_res, abort; - u32 use_bmin; + GF_DASHPlayoutBufferMode use_bmin; char *query; Bool noxlink, split_as, noseek, groupsel, bsmerge; - u32 lowlat; + GF_DASHLowLatencyMode lowlat; GF_FilterPid *mpd_pid; GF_Filter *filter; @@ -100,6 +109,7 @@ /*max width & height in all active representations*/ u32 width, height; + u32 service_id; Double seek_request; Double media_start_range; @@ -135,6 +145,9 @@ Bool load_file; GF_FileIO *fio; GF_FilterPacket *mpd_pck_ref; + + GF_DASHBaseURLControlMode keep_burl; // Option to control <BaseURL> + char *relative_url; // Relative string to inject before <BaseURL> if keep_base_url is set to inject } GF_DASHDmxCtx; typedef struct @@ -185,6 +198,7 @@ char *current_url; Bool url_changed; + u64 queue_ntp_ts; } GF_DASHGroup; static void dashdmx_notify_group_quality(GF_DASHDmxCtx *ctx, GF_DASHGroup *group); @@ -237,6 +251,12 @@ is_end = GF_TRUE; gf_filter_pck_set_framing(ref, is_start, is_end); + + if (group->queue_ntp_ts) { + gf_filter_pck_set_property(ref, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(group->queue_ntp_ts ) ); + gf_filter_pck_set_property(ref, GF_PROP_PCK_RECEIVER_NTP, &PROP_LONGUINT(gf_net_get_ntp_ts() ) ); + group->queue_ntp_ts = 0; + } is_filemode = GF_TRUE; } else { const GF_PropertyValue *p; @@ -274,9 +294,11 @@ if (seg_name) { gf_filter_pck_set_property(ref, GF_PROP_PCK_FILENAME, &PROP_STRING(seg_name) ); gf_filter_pck_set_property(ref, GF_PROP_PCK_FILENUM, &PROP_UINT(seg_number) ); + gf_filter_pck_set_property(ref, GF_PROP_PCK_MPD_SEGSTART, &PROP_FRAC64(seg_time)); + if (group->url_changed && group->current_url) { gf_filter_pck_set_property(ref, GF_PROP_PCK_FRAG_RANGE, NULL); - gf_filter_pck_set_property(ref, GF_PROP_PID_URL, &PROP_STRING(group->current_url)); + gf_filter_pck_set_property(ref, GF_PROP_PCK_SEG_URL, &PROP_STRING(group->current_url)); group->url_changed = GF_FALSE; } @@ -306,7 +328,7 @@ u32 flags = gf_filter_pid_get_udta_flags(out_pid); if (flags & FLAG_PERIOD_SWITCH) { gf_filter_pid_set_udta_flags(out_pid, flags & ~FLAG_PERIOD_SWITCH); - gf_filter_pck_set_property(ref, GF_PROP_PID_DASH_PERIOD_START, &PROP_LONGUINT(0) ); + gf_filter_pck_set_property(ref, GF_PROP_PCK_DASH_PERIOD_START, &PROP_BOOL(GF_TRUE) ); } gf_filter_pck_send(ref); return; @@ -714,7 +736,7 @@ } if (group_idx<-1) { - flags |= GF_NETIO_SESSION_MEMORY_CACHE; + flags |= GF_NETIO_SESSION_MEMORY_CACHE|GF_NETIO_SESSION_NO_PROXY; } else { if (!ctx->segstore) flags |= GF_NETIO_SESSION_MEMORY_CACHE; if (persistent) flags |= GF_NETIO_SESSION_PERSISTENT; @@ -789,11 +811,19 @@ } const char *dashdmx_io_get_header_value(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *header_name) { + const char *hdr; #ifdef GPAC_USE_DOWNLOADER - return gf_dm_sess_get_header((GF_DownloadSession *)session, header_name); -#else - return NULL; + hdr = gf_dm_sess_get_header((GF_DownloadSession *)session, header_name); + if (hdr) return hdr; #endif + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; + const GF_PropertyValue *p = gf_filter_pid_get_property_str(ctx->mpd_pid, header_name); + if (p) return p->value.string; + GF_PropertyEntry *pe=NULL; + p = gf_filter_pid_get_info_str(ctx->mpd_pid, header_name, &pe); + gf_filter_release_property(pe); + if (p) return p->value.string; + return NULL; } u64 dashdmx_io_get_utc_start_time(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) { @@ -979,16 +1009,30 @@ #endif } -void dashdmx_io_manifest_updated(GF_DASHFileIO *dashio, const char *manifest_name, const char *cache_url, s32 group_idx) +void process_base_url(char *manifest_payload, u32 manifest_payload_len, GF_DASHBaseURLControlMode keep_base_url, const char *relative_url) { - u8 *manifest_payload; - u32 manifest_payload_len; - GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; - - if (gf_file_load_data(cache_url, &manifest_payload, &manifest_payload_len) == GF_OK) { - u8 *output; - - //strip baseURL since we are recording, links are already resolved + if (keep_base_url == BURL_KEEP) { + // Do nothing, keep BaseURL as is + return; + } else if (keep_base_url == BURL_INJECT) { + char *man_pay_start = manifest_payload; + while (1) { + u32 end_len, offset; + char *base_url_start = strstr(man_pay_start, "<BaseURL>"); + if (!base_url_start) break; + char *base_url_end = strstr(base_url_start, "</BaseURL>"); + if (!base_url_end) break; + offset = 10; + while (base_url_endoffset == '\n') + offset++; + end_len = (u32) strlen(base_url_end + offset); + u32 inject_len = (u32) strlen(relative_url); + memmove(base_url_start + 9 + inject_len, base_url_start + 9, end_len); + memcpy(base_url_start + 9, relative_url, inject_len); + man_pay_start = base_url_start + 9 + inject_len + end_len; + } + } else { + // Default is to strip the BaseURL char *man_pay_start = manifest_payload; while (1) { u32 end_len, offset; @@ -1000,17 +1044,33 @@ offset = 10; while (base_url_endoffset == '\n') offset++; - end_len = (u32) strlen(base_url_end+offset); - memmove(base_url_start, base_url_end+offset, end_len); - base_url_startend_len=0; + end_len = (u32) strlen(base_url_end + offset); + memmove(base_url_start, base_url_end + offset, end_len); + base_url_startend_len = 0; man_pay_start = base_url_start; } + } +} + + +void dashdmx_io_manifest_updated(GF_DASHFileIO *dashio, const char *manifest_name, const char *cache_url, s32 group_idx) +{ + u8 *manifest_payload; + u32 manifest_payload_len; + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; + + if (gf_file_load_data(cache_url, &manifest_payload, &manifest_payload_len) == GF_OK) { + u8 *output; + + + // Process <BaseURL> based on the keep_base_url option + process_base_url(manifest_payload,manifest_payload_len, ctx->keep_burl, ctx->relative_url); if ((ctx->forward==DFWD_FILE) && ctx->output_mpd_pid) { //for routeout u32 manifest_type = gf_dash_is_m3u8(ctx->dash) ? 2 : 1; - if (!gf_sys_is_test_mode() && gf_dash_is_dynamic_mpd(ctx->dash)) - manifest_type |= 0x80000000; + if (gf_dash_is_dynamic_mpd(ctx->dash)) + manifest_type |= (1<<8); gf_filter_pid_set_property(ctx->output_mpd_pid, GF_PROP_PID_IS_MANIFEST, &PROP_UINT(manifest_type)); @@ -1144,7 +1204,11 @@ break; j++; if (desc_scheme && !strcmp(desc_scheme, "urn:mpeg:dash:srd:2014")) { + } else if (desc_scheme && !strcmp(desc_scheme, "urn:mpeg:dash:ssr:2023")) { } else if (desc_scheme && !strcmp(desc_scheme, "http://dashif.org/guidelines/trickmode")) { + } else if (desc_scheme && !strcmp(desc_scheme, "urn:mpeg:mpegB:cicp:ColourPrimaries") ) { + } else if (desc_scheme && !strcmp(desc_scheme, "urn:mpeg:mpegB:cicp:TransferCharacteristics") ) { + } else if (desc_scheme && !strcmp(desc_scheme, "urn:mpeg:mpegB:cicp:MatrixCoefficients") ) { } else { playable = GF_FALSE; break; @@ -1163,12 +1227,12 @@ continue; } - mime = gf_dash_group_get_segment_mime(ctx->dash, i); - init_segment = gf_dash_group_get_segment_init_url(ctx->dash, i, &start_range, &end_range); + init_segment = gf_dash_group_get_segment_init_url(ctx->dash, i, &start_range, &end_range, &mime); e = dashdmx_load_source(ctx, i, mime, init_segment, start_range, end_range); if (e != GF_OK) { gf_dash_group_select(ctx->dash, i, GF_FALSE); + nb_groups_selected--; } else { u32 w, h; /*connect our media service*/ @@ -1191,6 +1255,7 @@ if (!nb_groups_selected) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASHDmx No groups selectable, not playing !\n")); + return GF_SERVICE_ERROR; } return GF_OK; } @@ -1244,13 +1309,28 @@ GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx); if (!group) { - group_idx = gf_dash_group_has_dependent_group(ctx->dash, group_idx); - group = gf_dash_get_group_udta(ctx->dash, group_idx); + s32 dep_group_idx = gf_dash_group_has_dependent_group(ctx->dash, group_idx); + group = gf_dash_get_group_udta(ctx->dash, dep_group_idx); } //do not notify HAS status (selected qualities & co) right away, we are still potentially processing packets from previous segment(s) if (group) group->notify_quality_change = GF_TRUE; + + GF_FilterEvent sel_evt; + GF_FEVT_INIT(sel_evt, GF_FEVT_DASH_QUALITY_SELECT, ctx->mpd_pid); + sel_evt.dash_select.service_id = ctx->service_id; + sel_evt.dash_select.as_id = gf_dash_group_get_as_id(ctx->dash, group_idx); + sel_evt.dash_select.period_id = gf_dash_get_period_id(ctx->dash); + + u32 i, count = gf_dash_group_get_num_qualities(ctx->dash, group_idx); + for (i=0; i<count; i++) { + GF_DASHQualityInfo qinfo; + gf_dash_group_get_quality_info(ctx->dash, group_idx, i, &qinfo); + sel_evt.dash_select.rep_id = qinfo.hls_variant_url ? qinfo.hls_variant_url : qinfo.ID; + sel_evt.dash_select.select_type = qinfo.is_selected ? 0 : (qinfo.disabled ? 2 : 1); + gf_filter_pid_send_event(ctx->mpd_pid, &sel_evt); + } return GF_OK; } if (dash_evt==GF_DASH_EVENT_TIMESHIFT_UPDATE) { @@ -1529,6 +1609,11 @@ e = gf_dynstrcat(q_desc, szInfo, "::"); if (e) return; } + if (qinfo->ssr) { + snprintf(szInfo, 500, "ssr=%d", qinfo->ssr); + e = gf_dynstrcat(q_desc, szInfo, "::"); + if (e) return; + } } const char *gf_dash_group_get_clearkey_uri(GF_DashClient *dash, u32 group_idx, bin128 *def_kid); @@ -1633,8 +1718,7 @@ stream_type = GF_STREAM_VISUAL; } else if (qinfo.sample_rate || qinfo.nb_channels) { stream_type = GF_STREAM_AUDIO; - } else if (strstr(qinfo.mime, "text") - || strstr(qinfo.codec, "vtt") + } else if (strstr(qinfo.codec, "vtt") || strstr(qinfo.codec, "srt") || strstr(qinfo.codec, "text") || strstr(qinfo.codec, "tx3g") @@ -1642,11 +1726,13 @@ || strstr(qinfo.codec, "stpp") ) { stream_type = GF_STREAM_TEXT; - } - if (qinfo.mime) { - if (!strncmp(qinfo.mime, "video/", 6)) stream_type = GF_STREAM_VISUAL; - else if (!strncmp(qinfo.mime, "audio/", 6)) stream_type = GF_STREAM_AUDIO; - if (!strncmp(qinfo.mime, "text/", 5)) stream_type = GF_STREAM_TEXT; + } else if (qinfo.mime) { + if (!strncmp(qinfo.mime, "video/", 6)) + stream_type = GF_STREAM_VISUAL; + else if (!strncmp(qinfo.mime, "audio/", 6)) + stream_type = GF_STREAM_AUDIO; + else if (!strncmp(qinfo.mime, "text/", 5)) + stream_type = GF_STREAM_TEXT; } dashdm_format_qinfo(&qdesc, &qinfo); @@ -1708,7 +1794,7 @@ //for routeout gf_filter_pid_set_property(opid, GF_PROP_PID_PREMUX_STREAM_TYPE, &PROP_UINT(stream_type) ); - gf_filter_pid_set_property(opid, GF_PROP_PCK_HLS_REF, &PROP_LONGUINT( (u64) 1+group->idx) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_HLS_REF, &PROP_LONGUINT( (u64) 1+group->idx) ); if (!gf_dash_group_has_init_segment(ctx->dash, group_idx)) { gf_filter_pid_set_property(opid, GF_PROP_PID_NO_INIT, &PROP_BOOL(GF_TRUE) ); @@ -1724,6 +1810,8 @@ tsb *= timescale; tsb /= segdur; tsb /= 1000; //tsb given in ms + } else if (gf_dash_get_max_segment_duration(ctx->dash)) { + tsb /= gf_dash_get_max_segment_duration(ctx->dash); } else { tsb = 0; } @@ -1741,8 +1829,13 @@ title = NULL; gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_ROLE, 0, NULL, NULL, &title); - if (title) - gf_filter_pid_set_property(opid, GF_PROP_PID_ROLE, &PROP_STRING(title) ); + if (title) { + GF_PropertyValue pr; + pr.type = GF_PROP_STRING_LIST_COPY; + pr.value.string_list.nb_items = 1; + pr.value.string_list.vals = (char **) &title; + gf_filter_pid_set_property(opid, GF_PROP_PID_ROLE, &pr); + } title = NULL; gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_ACCESSIBILITY, 0, NULL, NULL, &title); @@ -1855,7 +1948,7 @@ gf_dash_group_next_seg_info(ctx->dash, group->idx, group->current_dependent_rep_idx, NULL, NULL, NULL, NULL, &str); if (str) { - gf_filter_pid_set_property(opid, GF_PROP_PCK_FILENAME, &PROP_STRING(str) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_INIT_NAME, &PROP_STRING(str) ); } //forward representation ID so that dasher will match muxed streams and identify streams in the MPD @@ -2006,6 +2099,8 @@ gf_filter_pid_send_event(ctx->mpd_pid, &evt); gf_filter_post_process_task(filter); } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); + if (p) ctx->service_id = p->value.uint; return GF_OK; } else if (ctx->mpd_pid == pid) { return GF_OK; @@ -2195,7 +2290,7 @@ static GF_Err dashdmx_initialize_js(GF_DASHDmxCtx *dashctx, char *jsfile) { - JSContext *ctx; + JSContext *ctx; JSValue global_obj, ret; u8 *buf; u32 buf_len; @@ -2214,7 +2309,7 @@ JS_SetContextOpaque(ctx, dashctx); dashctx->owns_context = GF_TRUE; - global_obj = JS_GetGlobalObject(ctx); + global_obj = JS_GetGlobalObject(ctx); js_load_constants(ctx, global_obj); dashctx->js_ctx = ctx; @@ -2233,13 +2328,13 @@ if (JS_IsException(ret)) { GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("DASHDmx Error loading script %s\n", jsfile)); - js_dump_error(dashctx->js_ctx); + js_dump_error(dashctx->js_ctx); JS_FreeValue(dashctx->js_ctx, ret); JS_FreeValue(dashctx->js_ctx, global_obj); return GF_BAD_PARAM; } JS_FreeValue(dashctx->js_ctx, ret); - JS_FreeValue(dashctx->js_ctx, global_obj); + JS_FreeValue(dashctx->js_ctx, global_obj); dashctx->rate_fun = JS_GetPropertyStr(ctx, dashctx->js_obj, "rate_adaptation"); if (! JS_IsFunction(ctx, dashctx->rate_fun)) { @@ -2394,7 +2489,7 @@ gf_dash_set_algo(ctx->dash, algo); gf_dash_set_utc_shift(ctx->dash, ctx->shift_utc); gf_dash_set_suggested_presentation_delay(ctx->dash, ctx->spd); - gf_dash_set_route_ast_shift(ctx->dash, ctx->route_shift); + gf_dash_set_mcast_ast_shift(ctx->dash, ctx->mcast_shift); gf_dash_enable_utc_drift_compensation(ctx->dash, ctx->server_utc); gf_dash_set_tile_adaptation_mode(ctx->dash, ctx->tile_mode, ctx->tiles_rate); @@ -2413,6 +2508,7 @@ gf_dash_disable_low_quality_tiles(ctx->dash, ctx->skip_lqt); gf_dash_set_chaining_mode(ctx->dash, ctx->chain_mode); gf_dash_set_auto_switch(ctx->dash, ctx->auto_switch, ctx->asloop); + gf_dash_enable_cross_as_switch(ctx->dash, ctx->xas); //in test mode, we disable seeking inside the segment: this initial seek range is dependent from tune-in time and would lead to different start range //at each run, possibly breaking all tests @@ -2631,7 +2727,7 @@ /*don't seek if this command is the first PLAY request of objects declared by the subservice, unless start range is not default one (0) */ if (!ctx->nb_playing) { - if (!initial_play || (fevt->play.start_range>1.0)) { + if (!initial_play || (fevt->play.start_range>0.2)) { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASHDmx Received Play command on group %d\n", group->idx)); @@ -2691,6 +2787,7 @@ gf_filter_pid_send_event(ipid, &src_evt); gf_filter_post_process_task(filter); + //cancel the event return GF_TRUE; @@ -2891,7 +2988,7 @@ p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_SIZE, &pe); if (p) file_size = p->value.longuint; - p = gf_filter_get_info_str(group->seg_filter_src, "x-route", &pe); + p = gf_filter_get_info_str(group->seg_filter_src, "x-mcast", &pe); if (p && p->value.string && !strcmp(p->value.string, "yes")) { broadcast_flag = GF_TRUE; } @@ -3071,6 +3168,8 @@ group->signal_seg_name = (ctx->forward==DFWD_FILE) ? GF_TRUE : GF_FALSE; group->init_switch_seg_sent = GF_TRUE; + if (ctx->forward) + group->queue_ntp_ts = gf_net_get_ntp_ts(); gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE); return; } @@ -3111,6 +3210,7 @@ group->current_url = gf_strdup(next_url); group->url_changed = GF_TRUE; } + group->queue_ntp_ts = gf_net_get_ntp_ts(); } GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL); @@ -3171,7 +3271,7 @@ if (group->eos_detected) check_eos = GF_TRUE; } - if (!ctx->mpd_pid) + if (!ctx->mpd_pid || ctx->in_error) return GF_EOS; //this needs further testing @@ -3536,7 +3636,7 @@ { OFFS(shift_utc), "shift DASH UTC clock in ms", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(spd), "suggested presentation delay in ms", GF_PROP_SINT, "-I", NULL, GF_FS_ARG_HINT_EXPERT}, - { OFFS(route_shift), "shift ROUTE requests time by given ms", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mcast_shift), "shift requests time by given ms for multicast sources", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(server_utc), "use `ServerUTC` or `Date` HTTP headers instead of local UTC", GF_PROP_BOOL, "yes", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(screen_res), "use screen resolution in selection phase", GF_PROP_BOOL, "yes", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(init_timeshift), "set initial timeshift in ms (if >0) or in per-cent of timeshift buffer (if <0)", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -3578,12 +3678,21 @@ { OFFS(skip_lqt), "disable decoding of tiles with highest degradation hints (not visible, not gazed at) for debug purposes", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(llhls_merge), "merge LL-HLS byte range parts into a single open byte range request", GF_PROP_BOOL, "yes", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(groupsel), "select groups based on language (by default all playable groups are exposed)", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(xas), "enable cross adaptation set switching (disabled if -split_as() is set)\n" + "- no: disabled\n" + "- codec: switching across sets only allowed for same codec\n" + "- all: switching across sets allowed across any representation types", GF_PROP_UINT, "codec", "no|codec|all", GF_FS_ARG_HINT_ADVANCED}, { OFFS(chain_mode), "MPD chaining mode\n" "- off: do not use MPD chaining\n" "- on: use MPD chaining once over, fallback if MPD load failure\n" "- error: use MPD chaining once over or if error (MPD or segment download)", GF_PROP_UINT, "on", "off|on|error", GF_FS_ARG_HINT_ADVANCED}, { OFFS(asloop), "when auto switch is enabled, iterates back and forth from highest to lowest qualities", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(bsmerge), "allow merging of video bitstreams (only HEVC for now)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(keep_burl), "control BaseURL in manifest\n" + "- strip: strip BaseURL (default)\n" + "- keep: keep BaseURL\n" + "- inject: inject local relative URL before BaseURL value specified by relative_url option", GF_PROP_UINT, "strip", "strip|keep|inject", GF_FS_ARG_HINT_EXPERT}, + { OFFS(relative_url), "relative string to inject before BaseURL when keep_base_url is set to inject", GF_PROP_STRING, "./", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -3596,6 +3705,8 @@ CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, DASHIN_MIMES), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), {0}, //accept any stream but files, framed @@ -3607,7 +3718,7 @@ GF_FilterRegister DASHDmxRegister = { .name = "dashin", - GF_FS_SET_DESCRIPTION("MPEG-DASH and HLS client") + GF_FS_SET_DESCRIPTION("DASH & HLS client") GF_FS_SET_HELP("This filter reads MPEG-DASH, HLS and MS Smooth manifests.\n" "\n" "# Regular mode\n" @@ -3681,6 +3792,7 @@ .probe_data = dashdmx_probe_data, //we accept as many input pids as loaded by the session .max_extra_pids = (u32) -1, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/dmx_ghi.c -> gpac-26.02.0.tar.gz/src/filters/dmx_ghi.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / GHI demuxer filter @@ -70,7 +70,7 @@ GF_Filter *filter_src; - char *rep_id, *res_url; + char *rep_id, *res_url, *check_res_url; u32 track_id, pid_timescale, mpd_timescale, sample_duration, first_frag_start_offset; s32 first_cts_offset; u32 props_size, props_offset, rep_flags, nb_segs; @@ -258,6 +258,8 @@ st = NULL; continue; } + //make sure this comes from the same source, otherwise we could match tracks with same IDs in different files - cf #2840 + if (!st->check_res_url || strcmp(st->check_res_url, url->value.string)) continue; if (!st->track_id) break; if (st->track_id == p_id->value.uint) break; st = NULL; @@ -268,6 +270,7 @@ } if (st->inactive) { GF_FilterEvent evt; + gf_filter_pid_set_discard(pid, GF_TRUE); GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); evt.play.initial_broadcast_play = 2; gf_filter_pid_send_event(pid, &evt); @@ -1035,6 +1038,11 @@ break; } } + if (!st->check_res_url) { + const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_URL); + st->check_res_url = gf_url_concatenate (p ? p->value.string : "./", st->res_url); + } + if (st->inactive) continue; nb_active++; if (st->filter_src) continue; @@ -1050,8 +1058,8 @@ ghi_dmx_declare_opid_xml(filter, ctx, st); continue; } - const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_URL); - char *args = gf_url_concatenate (p ? p->value.string : "./", st->res_url); + + char *args = gf_strdup(st->check_res_url); if (st->first_frag_start_offset) { char szRange100; @@ -1106,7 +1114,7 @@ GF_Err ghi_dmx_process(GF_Filter *filter) { GHIDmxCtx *ctx = gf_filter_get_udta(filter); - u32 i, count; + u32 i, count, nb_inactive=0; GF_FilterPacket *pck; if (!ctx->init) { @@ -1116,13 +1124,17 @@ count = gf_list_count(ctx->streams); for (i=0; i<count; i++) { GHIStream *st = gf_list_get(ctx->streams, i); - if (st->inactive || !st->ipid || st->empty_seg) continue; - + if (st->inactive || !st->ipid || st->empty_seg) { + nb_inactive++; + continue; + } GF_FilterPid *opid = gf_list_get(st->opids, 0); pck = gf_filter_pid_get_packet(st->ipid); if (!pck) { - if (gf_filter_pid_is_eos(st->ipid)) + if (gf_filter_pid_is_eos(st->ipid)) { + nb_inactive++; gf_filter_pid_set_eos(opid); + } continue; } u64 dts = gf_filter_pck_get_dts(pck); @@ -1165,7 +1177,7 @@ gf_filter_pid_drop_packet(st->ipid); } - return GF_OK; + return (nb_inactive==count) ? GF_EOS : GF_OK; } GF_Err ghi_dmx_initialize(GF_Filter *filter) @@ -1224,6 +1236,7 @@ gf_list_del(st->opids); if (st->rep_id) gf_free(st->rep_id); if (st->res_url) gf_free(st->res_url); + if (st->check_res_url) gf_free(st->check_res_url); gf_free(st); } gf_list_del(ctx->streams); @@ -1316,7 +1329,7 @@ "The filter outputs are PIDs using framed packets marked with segment boundaries and can be chained to other filters before entering the dasher (e.g. for encryption, transcode...).\n" "\n" "If representation IDs are not assigned during index creation, they default to the 1-based index of the source. You can check them using:\n" - "EX: `gpac -i src.ghi inspect:full`\n" + "EX gpac -i src.ghi inspect:full\n" "\n" "# Muxed Representations\n" "The filter can be used to generate muxed representations, either at manifest generation time or when generating a segment.\n" @@ -1337,7 +1350,7 @@ "Indexing supports fragmented and non-fragmented MP4, MPEG-2 TS and seekable inputs.\n" "- It is recommended to use fragmented MP4 as input format since this greatly reduces file loading times.\n" "- If non-fragmented MP4 are used, it is recommended to use single-track files to decrease the movie box size and speedup parsing.\n" - "- MPEG-2 TS sources will be slower since they require PES reframing and AU reformating, resulting in more IOs than with mp4.\n" + "- MPEG-2 TS sources will be slower since they require PES reframing and AU reformatting, resulting in more IOs than with mp4.\n" "- other seekable sources will likely be slower (seeking, reframing) and are not recommended.\n" "\n" ) @@ -1353,6 +1366,7 @@ .probe_data = ghi_dmx_probe_data, //we accept as many input pids as loaded by the session .max_extra_pids = (u32) -1, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; const GF_FilterRegister *ghidmx_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dmx_gsf.c -> gpac-26.02.0.tar.gz/src/filters/dmx_gsf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / GPAC stream format reader filter @@ -112,7 +112,7 @@ Bool corrupted; Bool file_pids; - Bool stop_pending; + Bool stop_pending, pid_pending; } GSF_DemuxCtx; @@ -172,13 +172,15 @@ switch (evt->base.type) { case GF_FEVT_PLAY: + ctx->stop_pending = GF_FALSE; + if (ctx->pid_pending) + ctx->pid_pending--; if (ctx->nb_playing && (ctx->start_range == evt->play.start_range)) { ctx->nb_playing++; return GF_TRUE; } ctx->nb_playing++; ctx->wait_for_play = GF_FALSE; - ctx->stop_pending = GF_FALSE; if (! ctx->is_file) { return GF_FALSE; @@ -328,6 +330,11 @@ case GF_PROP_NAME: p->type = GF_PROP_STRING_NO_COPY; len = gsfdmx_read_vlen(bs); + if (len >= 0x1000000) { + p->value.string = NULL; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid length in string property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } p->value.string = gf_malloc(sizeof(char)*(len+1)); gf_bs_read_data(bs, p->value.string, len); p->value.stringlen=0; @@ -339,6 +346,11 @@ p->type = GF_PROP_DATA_NO_COPY; p->value.data.size = gsfdmx_read_vlen(bs); if (!p->value.data.size) return GF_NON_COMPLIANT_BITSTREAM; + if (p->value.data.size >= 0x1000000) { + p->value.data.size = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid length in data property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } p->value.data.ptr = gf_malloc(sizeof(char) * p->value.data.size); gf_bs_read_data(bs, p->value.data.ptr, p->value.data.size); break; @@ -346,9 +358,24 @@ case GF_PROP_STRING_LIST: len2 = gsfdmx_read_vlen(bs); p->value.string_list.nb_items = len2; + if (p->value.string_list.nb_items >= 0x1000000) { + p->value.string_list.nb_items = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid length in string list property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } p->value.string_list.vals = gf_malloc(sizeof(char*) * len2); for (i=0; i<len2; i++) { len = gsfdmx_read_vlen(bs); + if (len >= 0x1000000) { + for (u32 j=0; j<i; j++) { + gf_free(p->value.string_list.valsj); + p->value.string_list.valsj = NULL; + } + p->value.string_list.nb_items = 0; + gf_free(p->value.string_list.vals); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid string length in string list property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } char *str = gf_malloc(sizeof(char)*(len+1)); gf_bs_read_data(bs, str, len); strlen = 0; @@ -359,6 +386,11 @@ case GF_PROP_UINT_LIST: case GF_PROP_SINT_LIST: p->value.uint_list.nb_items = len = gsfdmx_read_vlen(bs); + if (p->value.uint_list.nb_items >= 0x1000000) { + p->value.uint_list.nb_items = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid length in list property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } p->value.uint_list.vals = gf_malloc(sizeof(u32)*len); for (i=0; i<len; i++) { p->value.uint_list.valsi = gsfdmx_read_vlen(bs); @@ -366,6 +398,11 @@ break; case GF_PROP_4CC_LIST: p->value.uint_list.nb_items = len = gsfdmx_read_vlen(bs); + if (p->value.uint_list.nb_items >= 0x1000000) { + p->value.uint_list.nb_items = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid length in 4CC list property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } p->value.uint_list.vals = gf_malloc(sizeof(u32)*len); for (i=0; i<len; i++) { p->value.uint_list.valsi = gf_bs_read_u32(bs); @@ -373,6 +410,11 @@ break; case GF_PROP_VEC2I_LIST: p->value.v2i_list.nb_items = len = gsfdmx_read_vlen(bs); + if (p->value.v2i_list.nb_items >= 0x1000000) { + p->value.v2i_list.nb_items = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux invalid length in vec2i list property\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } p->value.v2i_list.vals = gf_malloc(sizeof(GF_PropVec2i)*len); for (i=0; i<len; i++) { p->value.v2i_list.valsi.x = gsfdmx_read_vlen(bs); @@ -411,6 +453,7 @@ gst->idx = idx; gf_list_add(ctx->streams, gst); gst->opid = gf_filter_pid_new(filter); + ctx->pid_pending++; return gst; } @@ -498,12 +541,12 @@ GF_PropertyValue p; u32 len = gsfdmx_read_vlen(bs); - if (len >= GF_UINT_MAX-1) + if (len >=0x1000000) return GF_BAD_PARAM; char *pname = gf_malloc(sizeof(char)*(len+1)); - gf_bs_read_data(bs, pname, len); - pnamelen=0; + u32 read = gf_bs_read_data(bs, pname, len); + pnameread=0; memset(&p, 0, sizeof(GF_PropertyValue)); p.type = gf_bs_read_u8(bs); @@ -516,6 +559,16 @@ if (is_info_update) gf_filter_pid_set_info_dyn(gst->opid, pname, &p); else gf_filter_pid_set_property_dyn(gst->opid, pname, &p); gf_free(pname); + switch (p.type) { + case GF_PROP_STRING_LIST: + case GF_PROP_DATA: + case GF_PROP_DATA_NO_COPY: + case GF_PROP_CONST_DATA: + break; + default: + gf_props_reset_single(&p); + break; + } } return GF_OK; } @@ -576,6 +629,11 @@ len = gsfdmx_read_vlen(bs); if (len) { Bool wrongm=GF_FALSE; + if (len>0x100000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux wrong magic word size %u in stream config\n", len)); + ctx->tune_error = GF_TRUE; + return GF_NOT_SUPPORTED; + } char *magic = gf_malloc(sizeof(char)*len); gf_bs_read_data(bs, magic, len); @@ -614,9 +672,18 @@ if ((frame_sn>=0) || pck_frag) { while (( gpck = gf_list_enum(gst->packets, &i))) { if (gpck->frame_sn == frame_sn) { - gf_assert(gpck->pck_type == pkt_type); - gf_assert(gpck->full_block_size == frame_size); - + if ((gpck->pck_type == pkt_type) && (gpck->full_block_size == frame_size)) + break; + if (gpck->pck) { + gf_filter_pck_discard(gpck->pck); + gpck->pck=NULL; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("GSFDemux Corrupted packet SN %u - discarding\n", frame_sn)); + gf_list_rem(gst->packets, i-1); + gsfdmx_pck_reset(gpck); + if (gf_list_find(ctx->pck_res, gpck) == -1) + gf_list_add(ctx->pck_res, gpck); + gpck = NULL; break; } gpck = NULL; @@ -637,7 +704,8 @@ gpck->pck = gf_filter_pck_new_alloc(gst->opid, frame_size, &gpck->output); if (!gpck->pck) { gsfdmx_pck_reset(gpck); - gf_list_add(ctx->pck_res, gpck); + if (gf_list_find(ctx->pck_res, gpck) == -1) + gf_list_add(ctx->pck_res, gpck); return NULL; } memset(gpck->output, (u8) ctx->pad, sizeof(char) * gpck->full_block_size); @@ -751,7 +819,7 @@ u8 tsdiffmode = gf_bs_read_int(bs, 2); u8 sap = gf_bs_read_int(bs, 3); - u8 crypt = gf_bs_read_int(bs, 2); + u8 pck_crypt = gf_bs_read_int(bs, 2); u8 has_dep = gf_bs_read_int(bs, 1); u8 has_4cc_props = gf_bs_read_int(bs, 1); u8 has_ext = gf_bs_read_int(bs, 1); @@ -848,12 +916,23 @@ while (nb_props) { GF_Err e; GF_PropertyValue p; - char *pname; + char *pname=NULL; memset(&p, 0, sizeof(GF_PropertyValue)); u32 len = gsfdmx_read_vlen(bs); - pname = gf_malloc(sizeof(char)*(len+1) ); - gf_bs_read_data(bs, pname, len); - pnamelen = 0; + u32 read=0; + if (len<=0x1000000) { + pname = gf_malloc(sizeof(char)*(len+1) ); + if (pname) { + read = gf_bs_read_data(bs, pname, len); + } + } + if (!pname) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux Invalid property size %d\n", len )); + gf_filter_pck_discard(gpck->pck); + gpck->pck = NULL; + return GF_NON_COMPLIANT_BITSTREAM; + } + pnameread = 0; p.type = gf_bs_read_u8(bs); if (p.type==GF_PROP_FORBIDDEN) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux Wrong GPAC property type for property %s\n", pname )); @@ -905,7 +984,7 @@ if (has_dep) gf_filter_pck_set_dependency_flags(gpck->pck, dep_flags); if (cktype) gf_filter_pck_set_clock_type(gpck->pck, cktype); if (seek) gf_filter_pck_set_seek_flag(gpck->pck, seek); - if (crypt) gf_filter_pck_set_crypt_flags(gpck->pck, crypt); + if (pck_crypt) gf_filter_pck_set_crypt_flags(gpck->pck, pck_crypt); if (sap) gf_filter_pck_set_sap(gpck->pck, sap); if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) gf_filter_pck_set_roll_info(gpck->pck, roll); @@ -942,7 +1021,9 @@ } } gsfdmx_pck_reset(gpck); - gf_list_add(ctx->pck_res, gpck); + if (gf_list_find(ctx->pck_res, gpck) == -1) + gf_list_add(ctx->pck_res, gpck); + } if (is_flush && gst->opid) gf_filter_pid_remove(gst->opid); @@ -1028,13 +1109,19 @@ gpck->pck = NULL; break; default: + if (gpck->pck) { + gf_filter_pck_discard(gpck->pck); + gpck->pck=NULL; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("GSFDemux unknown packet type %d ignoring\n", gpck->pck_type)); e = GF_OK; break; } gf_list_rem(gst->packets, 0); gsfdmx_pck_reset(gpck); - gf_list_add(ctx->pck_res, gpck); + if (gf_list_find(ctx->pck_res, gpck) == -1) + gf_list_add(ctx->pck_res, gpck); if (e>GF_OK) e = GF_OK; if (e) return e; } @@ -1172,7 +1259,7 @@ } if (append) { - if (block_offset + pck_len > gpck->full_block_size) { + if ( (block_offset >= GF_UINT_MAX - pck_len) || (block_offset + pck_len > gpck->full_block_size) ) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFDemux packet fragment out of bounds of current frame (offset %d size %d max size %d)\n", block_offset, pck_len, gpck->full_block_size)); e = GF_NON_COMPLIANT_BITSTREAM; } else { @@ -1188,7 +1275,8 @@ gf_list_del_item(gst->packets, gpck); if (gpck->pck) gf_filter_pck_discard(gpck->pck); gsfdmx_pck_reset(gpck); - gf_list_add(ctx->pck_res, gpck); + if (gf_list_find(ctx->pck_res, gpck) == -1) + gf_list_add(ctx->pck_res, gpck); } } } @@ -1208,7 +1296,7 @@ memmove(ctx->buffer, ctx->buffer+last_pck_end, sizeof(char) * (ctx->buf_size-last_pck_end)); ctx->buf_size -= last_pck_end; } - if (ctx->stop_pending) { + if (ctx->stop_pending && !ctx->pid_pending) { GF_FilterEvent evt; ctx->stop_pending = GF_FALSE; GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->ipid); @@ -1335,6 +1423,7 @@ while (gf_list_count(ctx->pck_res)) { GSF_Packet *gsp = gf_list_pop_back(ctx->pck_res); + gf_list_del_item(ctx->pck_res, gsp); if (gsp->frags) gf_free(gsp->frags); gf_free(gsp); } @@ -1400,6 +1489,7 @@ .process = gsfdmx_process, .process_event = gsfdmx_process_event, .probe_data = gsfdmx_probe_data, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER };
View file
gpac-2.4.0.tar.gz/src/filters/dmx_m2ts.c -> gpac-26.02.0.tar.gz/src/filters/dmx_m2ts.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2025 * All rights reserved * * This file is part of GPAC / M2TS demux filter @@ -32,6 +32,7 @@ #include <gpac/mpegts.h> #include <gpac/thread.h> #include <gpac/internal/media_dev.h> +#include <gpac/id3.h> typedef struct { char *fragment; @@ -40,12 +41,27 @@ u32 pid; } GF_M2TSDmxCtx_Prog; +typedef enum +{ + M2TS_TEMI_INFO, + M2TS_ID3, + M2TS_SCTE35 +} GF_M2TS_PropType; + +#define GF_M2TS_PROP \ + GF_M2TS_PropType type; \ + u32 len; \ + u8 *data; \ + +typedef struct { + GF_M2TS_PROP +} GF_M2TS_Prop; + typedef struct { + GF_M2TS_PROP u32 timeline_id; Bool is_loc; - u32 len; - u8 *data; -} GF_TEMIInfo; +} GF_M2TS_Prop_TEMIInfo; enum { @@ -53,15 +69,22 @@ DMX_TUNE_INIT, DMX_TUNE_WAIT_PROGS, DMX_TUNE_WAIT_SEEK, - }; +GF_OPT_ENUM(UnknownPesMode, + UPES_MODE_NO = 0, + UPES_MODE_INFO, + UPES_MODE_ALL +); + typedef struct { //opts const char *temi_url; - Bool dsmcc, seeksrc, sigfrag, dvbtxt; + Bool dsmcc, seeksrc, sigfrag, dvbtxt, mappcr; + UnknownPesMode upes; Double index; + u32 analyze; GF_Filter *filter; GF_FilterPid *ipid; @@ -90,8 +113,18 @@ Bool is_dash; u32 nb_stopped_at_init; + + u32 logflags; + u32 forward_for; } GF_M2TSDmxCtx; +static void m2tsdmx_prop_free(GF_M2TS_Prop *prop) { + + if (prop->type == M2TS_ID3) { + gf_id3_tag_free((GF_ID3_TAG*) prop->data); + } + gf_free(prop->data); +} static void m2tsdmx_estimate_duration(GF_M2TSDmxCtx *ctx, GF_M2TS_ES *stream) { @@ -149,6 +182,7 @@ for (i=0; i<nb_streams; i++) { GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i); gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION_AVG, &PROP_BOOL(GF_TRUE) ); } } } @@ -181,11 +215,11 @@ if (for_pid && (es->user != for_pid)) continue; //TODO, translate non standard character maps to UTF8 //we for now comment in test mode to avoid non UTF characters in text dumps - if (isalnum(sdt->service0) || !gf_sys_is_test_mode()) - gf_filter_pid_set_property((GF_FilterPid *)es->user, GF_PROP_PID_SERVICE_NAME, &PROP_STRING(sdt->service ) ); + if ((sdt && sdt->service && isalnum(sdt->service0)) || !gf_sys_is_test_mode()) + gf_filter_pid_set_info((GF_FilterPid *)es->user, GF_PROP_PID_SERVICE_NAME, &PROP_STRING(sdt->service ) ); - if (isalnum(sdt->provider0) || !gf_sys_is_test_mode()) - gf_filter_pid_set_property((GF_FilterPid *)es->user, GF_PROP_PID_SERVICE_PROVIDER, &PROP_STRING( sdt->provider ) ); + if ((sdt && sdt->provider && isalnum(sdt->provider0)) || !gf_sys_is_test_mode()) + gf_filter_pid_set_info((GF_FilterPid *)es->user, GF_PROP_PID_SERVICE_PROVIDER, &PROP_STRING( sdt->provider ) ); } } } @@ -194,11 +228,13 @@ { u32 i, count, codecid=0, stype=0, orig_stype=0; GF_FilterPid *opid; + u32 fake_stream = 0; Bool m4sys_stream = GF_FALSE; Bool m4sys_iod_stream = GF_FALSE; Bool has_scal_layer = GF_FALSE; Bool unframed = GF_FALSE; Bool unframed_latm = GF_FALSE; + Bool unframed_srt = GF_FALSE; char szName20; const char *stname; if (stream->user) return; @@ -206,10 +242,10 @@ if (stream->flags & GF_M2TS_GPAC_CODEC_ID) { codecid = stream->stream_type; stype = gf_codecid_type(codecid); - if (stream->gpac_meta_dsi) + if ((stream->flags & GF_M2TS_ES_IS_PES) && stream->gpac_meta_dsi) stype = stream->gpac_meta_dsi4; if (!stype) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSDmx Unrecognized gpac codec %s - ignoring pid\n", gf_4cc_to_str(codecid) )); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSDmx Unrecognized gpac codec %s - ignoring pid %u\n", gf_4cc_to_str(codecid) , stream->pid)); return; } } else { @@ -336,6 +372,27 @@ stype = GF_STREAM_AUDIO; codecid = GF_CODECID_OPUS; break; + case GF_M2TS_VIDEO_AVS2: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_AVS2_VIDEO; + break; + case GF_M2TS_AUDIO_AVS2: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_AVS2_AUDIO; + break; + case GF_M2TS_VIDEO_AVS3: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_AVS3_VIDEO; + break; + case GF_M2TS_AUDIO_AVS3: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_AVS3_AUDIO; + break; + case GF_M2TS_AUDIO_AC4: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_AC4; + unframed = GF_TRUE; + break; case GF_M2TS_SYSTEMS_MPEG4_SECTIONS: ((GF_M2TS_ES*)stream)->flags |= GF_M2TS_ES_SEND_REPEATED_SECTIONS; //fallthrough @@ -354,7 +411,6 @@ if (!esd) return; break; case GF_M2TS_METADATA_PES: - case GF_M2TS_METADATA_ID3_HLS: stype = GF_STREAM_METADATA; codecid = GF_CODECID_SIMPLE_TEXT; break; @@ -386,17 +442,55 @@ stream->flags |= GF_M2TS_ES_FULL_AU; break; case GF_M2TS_DVB_TELETEXT: - if (!ctx->dvbtxt) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSDmx DVB teletext pid skipped, use --dvbtxt to enable\n", stream->stream_type)); + if (!ctx->dvbtxt && (ctx->upes!=1)) { + if (!(ctx->logflags & 1)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSDmx DVB teletext stream(s) skipped, use --dvbtxt to enable\n", stream->stream_type)); + ctx->logflags|=1; + } return; } stype = GF_STREAM_TEXT; codecid = GF_CODECID_DVB_TELETEXT; stream->flags |= GF_M2TS_ES_FULL_AU; break; + case GF_M2TS_METADATA_SRT: + stype = GF_STREAM_TEXT; + codecid = GF_CODECID_SUBS_TEXT; + unframed = GF_TRUE; + unframed_srt = GF_TRUE; + break; + case GF_M2TS_METADATA_TEXT: + stype = GF_STREAM_TEXT; + codecid = GF_CODECID_SIMPLE_TEXT; + unframed = GF_TRUE; + break; + + case GF_M2TS_METADATA_ID3_HLS: + case GF_M2TS_METADATA_ID3_KLVA: + stype = GF_STREAM_METADATA; + codecid = GF_CODECID_NONE; + fake_stream = 1; + break; + case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: + stype = GF_STREAM_METADATA; + codecid = GF_CODECID_SCTE35; + stream->flags |= GF_M2TS_ES_IS_SECTION|GF_M2TS_ES_FULL_AU; + fake_stream = 2; + break; default: - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSDmx Stream type 0x%02X not supported - ignoring pid\n", stream->stream_type)); - return; + //GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("M2TSDmx Stream type 0x%02X not supported - ignoring pid 0x%x\n", stream->stream_type, stream->pid)); + if (!ctx->upes) { + if (!(ctx->logflags & 2)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSDmx Unknown stream(s) skipped, use --upes to enable\n", stream->stream_type)); + ctx->logflags|=2; + } + gf_m2ts_set_pes_framing((GF_M2TS_PES *)stream, GF_M2TS_PES_FRAMING_SKIP_NO_RESET); + return; + } + codecid = GF_4CC('M','2','T', stream->stream_type); + if (ctx->upes==UPES_MODE_INFO) + fake_stream = 2; + break; } } @@ -457,6 +551,7 @@ gf_filter_pid_set_property(opid, GF_PROP_PID_UNFRAMED, unframed ? &PROP_BOOL(GF_TRUE) : NULL); gf_filter_pid_set_property(opid, GF_PROP_PID_UNFRAMED_LATM, unframed_latm ? &PROP_BOOL(GF_TRUE) : NULL ); + gf_filter_pid_set_property(opid, GF_PROP_PID_UNFRAMED_SRT, unframed_srt ? &PROP_BOOL(GF_TRUE) : NULL ); if (orig_stype) { gf_filter_pid_set_property(opid, GF_PROP_PID_ORIG_STREAM_TYPE, &PROP_UINT(orig_stype) ); @@ -485,8 +580,10 @@ u32 dsi_len = gf_bs_read_u32(bs); if (dsi_len) { u32 pos = (u32) gf_bs_get_position(bs); - gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(stream->gpac_meta_dsi+pos, dsi_len) ); - gf_bs_skip_bytes(bs, dsi_len); + if (pos < stream->gpac_meta_dsi_size && dsi_len < stream->gpac_meta_dsi_size-pos) { + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(stream->gpac_meta_dsi+pos, dsi_len) ); + gf_bs_skip_bytes(bs, dsi_len); + } } else { gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, NULL); } @@ -508,14 +605,42 @@ gf_filter_pid_set_property(opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(stream->program->number) ); - if ((stream->flags&GF_M2TS_ES_IS_PES) && stream->lang) { - char szLang4; - szLang0 = (stream->lang>>16) & 0xFF; - szLang1 = (stream->lang>>8) & 0xFF; - szLang2 = stream->lang & 0xFF; - szLang3 = 0; - if (szLang2==' ') szLang2 = 0; - gf_filter_pid_set_property(opid, GF_PROP_PID_LANGUAGE, &PROP_STRING(szLang) ); + if (stream->flags&GF_M2TS_ES_IS_PES) { + if (stream->lang) { + char szLang4; + szLang0 = (stream->lang>>16) & 0xFF; + szLang1 = (stream->lang>>8) & 0xFF; + szLang2 = stream->lang & 0xFF; + szLang3 = 0; + if (szLang2==' ') szLang2 = 0; + gf_filter_pid_set_property(opid, GF_PROP_PID_LANGUAGE, &PROP_STRING(szLang) ); + } + u32 nb_roles=0; + if (stream->audio_flags & (GF_M2TS_AUDIO_DESCRIPTION|GF_M2TS_AUDIO_SUB_DESCRIPTION)) nb_roles++; + if (stream->audio_flags & GF_M2TS_AUDIO_HEARING_IMPAIRED) nb_roles++; + if (nb_roles) { + GF_PropertyValue roles; + roles.type = GF_PROP_STRING_LIST; + roles.value.string_list.nb_items = nb_roles; + roles.value.string_list.vals = gf_malloc(sizeof(char*)*nb_roles); + nb_roles=0; + if (stream->audio_flags & (GF_M2TS_AUDIO_DESCRIPTION|GF_M2TS_AUDIO_SUB_DESCRIPTION)) { + roles.value.string_list.valsnb_roles = gf_strdup("description"); + nb_roles++; + } + if (stream->audio_flags & GF_M2TS_AUDIO_HEARING_IMPAIRED) { + roles.value.string_list.valsnb_roles = gf_strdup("enhanced-audio-intelligibility"); + nb_roles++; + } + gf_filter_pid_set_property(opid, GF_PROP_PID_ROLE, &roles); + } + //we don't demux scrambled PIDs, declare them as fake + if (stream->is_protected) { + gf_filter_pid_set_property(opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_ENCRYPTED) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_ORIG_STREAM_TYPE, &PROP_UINT(stype) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_PROTECTION_SCHEME_TYPE, &PROP_UINT(GF_4CC('d','v','b','c')) ); + fake_stream = GF_TRUE; + } } if (codecid == GF_CODECID_DVB_SUBS) { char szLang4; @@ -531,10 +656,14 @@ dsi4 = stream->sub.type; gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(dsi, 5)); } + if (codecid == GF_CODECID_AVS3_VIDEO) { + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(stream->avs3_video_descriptor, 10)); + } if (ctx->duration.num>1) { gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); gf_filter_pid_set_property(opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD ) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION_AVG, &PROP_BOOL(GF_TRUE) ); } /*indicate our coding dependencies if any*/ if (!m4sys_stream) { @@ -563,27 +692,67 @@ gf_filter_pid_set_property(opid, GF_PROP_PID_DOLBY_VISION, NULL); } + if (fake_stream) { + gf_filter_pid_set_property(opid, GF_PROP_PID_FAKE, &PROP_BOOL(GF_TRUE) ); + if (fake_stream==2) { + gf_m2ts_set_pes_framing((GF_M2TS_PES *)stream, GF_M2TS_PES_FRAMING_SKIP_NO_RESET); + return; + } + } + m2tsdmx_update_sdt(ctx->ts, opid); gf_m2ts_set_pes_framing((GF_M2TS_PES *)stream, GF_M2TS_PES_FRAMING_DEFAULT); } +static void m2tsdmx_setup_scte35(GF_M2TSDmxCtx *ctx, GF_M2TS_Program *prog) +{ + u32 count = gf_list_count(prog->streams); + for (u32 i=0; i<count; i++) { + GF_M2TS_ES *es_scte35 = gf_list_get(prog->streams, i); + if (es_scte35->pid==prog->pmt_pid) continue; + if (es_scte35->flags & GF_M2TS_GPAC_CODEC_ID) continue; + if (es_scte35->stream_type == GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS) { + //declare static property on the first video pid to signal scte35 presence + //and avoid later dynamic downstream filters' (e.g. muxers) reconfigurations + for (u32 j=0; j<count; j++) { + GF_M2TS_ES *es = gf_list_get(prog->streams, j); + if (!es->user) continue; + const GF_PropertyValue *p = gf_filter_pid_get_property(es->user, GF_PROP_PID_STREAM_TYPE); + if (!p) continue; + if (p->value.uint == GF_STREAM_VISUAL) { + gf_filter_pid_set_property(es->user, GF_PROP_PID_SCTE35_PID, &PROP_UINT(es_scte35->pid) ); + return; + } + } + } + } +} + static void m2tsdmx_setup_program(GF_M2TSDmxCtx *ctx, GF_M2TS_Program *prog) { u32 i, count; - + Bool do_ignore = GF_TRUE; count = gf_list_count(prog->streams); for (i=0; i<count; i++) { GF_M2TS_PES *es = gf_list_get(prog->streams, i); + if (!ctx->forward_for || (es->pid==ctx->forward_for)) do_ignore = GF_FALSE; + if (es->pid==prog->pmt_pid) continue; if (! (es->flags & GF_M2TS_ES_IS_PES)) continue; if (es->stream_type == GF_M2TS_VIDEO_HEVC_TEMPORAL ) continue; if (es->depends_on_pid ) { prog->is_scalable = GF_TRUE; - break; } } + if (do_ignore) { + for (i=0; i<count; i++) { + GF_M2TS_PES *es = gf_list_get(prog->streams, i); + gf_m2ts_set_pes_framing(es, GF_M2TS_PES_FRAMING_SKIP); + } + return; + } for (i=0; i<count; i++) { u32 ncount; @@ -599,28 +768,105 @@ count--; } } + + m2tsdmx_setup_scte35(ctx, prog); } -static void m2tdmx_merge_temi(GF_FilterPid *pid, GF_M2TS_ES *stream, GF_FilterPacket *pck) +static void m2tdmx_merge_props(GF_FilterPid *pid, GF_M2TS_ES *stream, GF_FilterPacket *pck) { if (stream->props) { + Bool insert_immediately = GF_TRUE; + GF_List *id3_tag_list = NULL; + char szID100; while (gf_list_count(stream->props)) { - GF_TEMIInfo *t = gf_list_pop_front(stream->props); - snprintf(szID, 100, "%s:%d", t->is_loc ? "temi_l" : "temi_t", t->timeline_id); + GF_M2TS_Prop *p = gf_list_pop_front(stream->props); + insert_immediately = GF_TRUE; + switch(p->type) { + case M2TS_TEMI_INFO: { + GF_M2TS_Prop_TEMIInfo *t = (GF_M2TS_Prop_TEMIInfo*)p; + snprintf(szID, 100, "%s:%d", t->is_loc ? "temi_l" : "temi_t", t->timeline_id); + + if (!(stream->flags & GF_M2TS_ES_TEMI_INFO)) { + stream->flags |= GF_M2TS_ES_TEMI_INFO; + gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_TEMI, &PROP_BOOL(GF_TRUE) ); + } + break; + } + case M2TS_SCTE35: + snprintf(szID, 100, "scte35"); + break; + case M2TS_ID3: { + insert_immediately = GF_FALSE; + if (!id3_tag_list) { + id3_tag_list = gf_list_new(); + } - gf_filter_pck_set_property_dyn(pck, szID, &PROP_DATA_NO_COPY(t->data, t->len)); - gf_free(t); + // transfer ownership to the ID3 tag to the list + gf_list_add(id3_tag_list, p->data); + break; + } + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("M2TSDmx unknown property %d - skipping\n", p->type) ); + gf_free(p); + continue; + } + + if (insert_immediately) { + gf_filter_pck_set_property_dyn(pck, szID, &PROP_DATA_NO_COPY(p->data, p->len)); + } + + gf_free(p); } + + if (id3_tag_list) { + snprintf(szID, 100, "id3"); + + // Serialize all tags using a single bitstream + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + GF_Err err = gf_id3_list_to_bitstream(id3_tag_list, bs); + if (err != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("M2TSDmx Error serializing list of ID3 tags: %s\n", gf_error_to_string(err))); + } + + u8 *data_ptr; + u32 data_length; + + gf_bs_get_content(bs, &data_ptr, &data_length); + gf_filter_pck_set_property_dyn(pck, szID, &PROP_DATA_NO_COPY(data_ptr, data_length)); + + // free resources + gf_bs_del(bs); + GF_ID3_TAG *tag = gf_list_pop_front(id3_tag_list); + while(tag) { + gf_id3_tag_free(tag); + gf_free(tag); + tag = gf_list_pop_front(id3_tag_list); + } + gf_list_del(id3_tag_list); + } + gf_list_del(stream->props); stream->props = NULL; + } +} - if (!(stream->flags & GF_M2TS_ES_TEMI_INFO)) { - stream->flags |= GF_M2TS_ES_TEMI_INFO; - gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_TEMI, &PROP_BOOL(GF_TRUE) ); - } - +static GFINLINE u64 m2tsdmx_translate_ts(GF_M2TSDmxCtx *ctx, GF_M2TS_Program *prog, u64 inTS) +{ + if (!ctx->mappcr) return inTS; + //we may have a TS already looped while the PCR still hasn't (eg because vbv) + if ((prog->last_pcr_value > 9*GF_M2TS_MAX_PCR/10) && (inTS < GF_M2TS_MAX_PCR_90K/10)) + inTS += GF_M2TS_MAX_PCR_90K; + + gf_assert((s64) inTS + prog->pcr_base_offset/300 >= 0); + //we may dispatch a PES received before a PCR loop because we dispatch pes once the full packet is received + //the TS will be in old base but the PCR in the new one (pcr_base_offset incremented) + if ((GF_M2TS_MAX_PCR_90K < 20000 + inTS) && (prog->last_pcr_value<27000000)) { + gf_assert(prog->pcr_base_offset >= GF_M2TS_MAX_PCR); + return inTS + prog->pcr_base_offset/300 - GF_M2TS_MAX_PCR_90K; } + return inTS + prog->pcr_base_offset/300; } static void m2tsdmx_send_packet(GF_M2TSDmxCtx *ctx, GF_M2TS_PES_PCK *pck) @@ -642,6 +888,7 @@ //skip dataID and stream ID if (pck->stream->stream_type==GF_M2TS_DVB_SUBTITLE) { + if (len<=2) return; ptr+=2; len-=2; } @@ -683,6 +930,9 @@ #endif } + if (ptr-pck->data >= pck->data_len || len > (pck->data_len-(ptr-pck->data))) { + return; + } dst_pck = gf_filter_pck_new_alloc(opid, len, &data); if (!dst_pck) return; @@ -694,9 +944,16 @@ if (pck->flags & GF_M2TS_PES_PCK_RAP) sap_type = GF_FILTER_SAP_1; - gf_filter_pck_set_cts(dst_pck, pck->PTS); + u64 ts = m2tsdmx_translate_ts(ctx, pck->stream->program, pck->PTS); + gf_filter_pck_set_cts(dst_pck, ts); + if (ts != pck->PTS) + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_ORIGINAL_PTS, &PROP_LONGUINT(pck->PTS) ); + if (pck->DTS != pck->PTS) { - gf_filter_pck_set_dts(dst_pck, pck->DTS); + ts = m2tsdmx_translate_ts(ctx, pck->stream->program, pck->DTS); + gf_filter_pck_set_dts(dst_pck, ts); + if (ts != pck->DTS) + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_ORIGINAL_DTS, &PROP_LONGUINT(pck->PTS) ); } gf_filter_pck_set_sap(dst_pck, sap_type); @@ -718,7 +975,7 @@ } } } - m2tdmx_merge_temi(opid, (GF_M2TS_ES *)pck->stream, dst_pck); + m2tdmx_merge_props(opid, (GF_M2TS_ES *)pck->stream, dst_pck); if (pck->stream->is_seg_start) { pck->stream->is_seg_start = GF_FALSE; @@ -770,7 +1027,7 @@ /*depacketize SL Header*/ if (((GF_M2TS_ES*)pck->stream)->slcfg) { - gf_sl_depacketize(slc, &slh, pck->data, pck->data_len, &slh_len); + gf_odf_sl_depacketize(slc, &slh, pck->data, pck->data_len, &slh_len); slh.m2ts_version_number_plus_one = pck->version_number + 1; } else { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("M2TSDmx MPEG-4 SL-packetized stream without SLConfig assigned - ignoring packet\n") ); @@ -786,6 +1043,7 @@ if (slc->useAccessUnitEndFlag && slh.accessUnitEndFlag) end = GF_TRUE; gf_filter_pck_set_framing(dst_pck, start, end); + //DO NOT remap to PCR, 4on2 is not using PCR if (slc->useTimestampsFlag && slh.decodingTimeStampFlag) gf_filter_pck_set_dts(dst_pck, slh.decodingTimeStamp); @@ -797,7 +1055,7 @@ gf_filter_pck_set_carousel_version(dst_pck, pck->version_number); - m2tdmx_merge_temi(opid, pck->stream, dst_pck); + m2tdmx_merge_props(opid, pck->stream, dst_pck); if (pck->stream->is_seg_start) { pck->stream->is_seg_start = GF_FALSE; gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE)); @@ -935,7 +1193,7 @@ case GF_M2TS_EVT_PES_PCR: if (ctx->mux_tune_state) break; { - u64 pcr; + u64 pcr, opcr; Bool map_time = GF_FALSE; GF_M2TS_PES_PCK *pck = ((GF_M2TS_PES_PCK *) param); Bool discontinuity = ( ((GF_M2TS_PES_PCK *) param)->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0; @@ -950,8 +1208,14 @@ } //we forward the PCR on each pid - pcr = ((GF_M2TS_PES_PCK *) param)->PTS; + opcr = pcr = ((GF_M2TS_PES_PCK *) param)->PTS; + if (ctx->mappcr) { + gf_assert((s64)pcr + pck->stream->program->pcr_base_offset >= 0); + pcr += pck->stream->program->pcr_base_offset; + } pcr /= 300; + opcr /= 300; + count = gf_list_count(pck->stream->program->streams); for (i=0; i<count; i++) { GF_FilterPacket *dst_pck; @@ -962,6 +1226,9 @@ if (!dst_pck) continue; gf_filter_pck_set_cts(dst_pck, pcr); + if (pcr != opcr) + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_ORIGINAL_PTS, &PROP_LONGUINT(opcr) ); + gf_filter_pck_set_clock_type(dst_pck, discontinuity ? GF_FILTER_CLOCK_PCR_DISC : GF_FILTER_CLOCK_PCR); if (pck->stream->is_seg_start) { pck->stream->is_seg_start = GF_FALSE; @@ -1017,6 +1284,7 @@ GF_M2TS_ES * stream = gf_list_get(prog->streams, j); if (stream->user) { gf_filter_pid_set_property(stream->user, GF_PROP_PID_DURATION, & PROP_FRAC64_INT(duration, 1000) ); + gf_filter_pid_set_property(stream->user, GF_PROP_PID_DURATION_AVG, &PROP_BOOL(GF_TRUE) ); } } } @@ -1030,7 +1298,7 @@ u32 len; GF_BitStream *bs; GF_M2TS_ES *es=NULL; - GF_TEMIInfo *t; + GF_M2TS_Prop_TEMIInfo *t; if ((temi_l->pid<8192) && (ctx->ts->esstemi_l->pid)) { es = ctx->ts->esstemi_l->pid; } @@ -1038,7 +1306,7 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("M2TSDmx TEMI location not assigned to a given PID, not supported\n")); break; } - GF_SAFEALLOC(t, GF_TEMIInfo); + GF_SAFEALLOC(t, GF_M2TS_Prop_TEMIInfo); if (!t) break; t->timeline_id = temi_l->timeline_id; t->is_loc = GF_TRUE; @@ -1072,7 +1340,7 @@ { GF_M2TS_TemiTimecodeDescriptor *temi_t = (GF_M2TS_TemiTimecodeDescriptor*)param; GF_BitStream *bs; - GF_TEMIInfo *t; + GF_M2TS_Prop_TEMIInfo *t; GF_M2TS_ES *es=NULL; if ((temi_t->pid<8192) && (ctx->ts->esstemi_t->pid)) { es = ctx->ts->esstemi_t->pid; @@ -1081,8 +1349,9 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("M2TSDmx TEMI timing not assigned to a given PID, not supported\n")); break; } - GF_SAFEALLOC(t, GF_TEMIInfo); + GF_SAFEALLOC(t, GF_M2TS_Prop_TEMIInfo); if (!t) break; + t->type = M2TS_TEMI_INFO; t->timeline_id = temi_t->timeline_id; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -1106,19 +1375,140 @@ gf_list_add(es->props, t); } break; + case GF_M2TS_EVT_ID3: + { + GF_M2TS_PES_PCK *pck = (GF_M2TS_PES_PCK*)param; + GF_M2TS_Prop *t; + u32 count = gf_list_count(pck->stream->program->streams); + for (i=0; i<count; i++) { + GF_M2TS_PES *es = gf_list_get(pck->stream->program->streams, i); + if (!es->user) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("M2TSDmx ID3 metadata not assigned to a given PID, not supported\n")); + continue; + } + + // attach ID3 markers to audio + GF_FilterPid *opid = (GF_FilterPid *)es->user; + const GF_PropertyValue *p = gf_filter_pid_get_property(opid, GF_PROP_PID_STREAM_TYPE); + if (!p) return; + if (p->value.uint != GF_STREAM_AUDIO) + continue; + + GF_SAFEALLOC(t, GF_M2TS_Prop); + if (!t) break; + t->type = M2TS_ID3; + + GF_ID3_TAG *id3_tag_ptr = NULL; + GF_SAFEALLOC(id3_tag_ptr, GF_ID3_TAG); + if (!id3_tag_ptr) { + gf_free(t); + break; + } + + if (gf_id3_tag_new(id3_tag_ptr, 90000, pck->PTS, pck->data, pck->data_len) != GF_OK) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("M2TSDMx Error creating ID3 tag")); + gf_free(id3_tag_ptr); + gf_free(t); + break; + } + + // data will point to the first byte of the ID3 tag struct. See m2tdmx_merge_props + // for the serialization process and m2tsdmx_prop_free for freeing up prop resources + t->data = (u8*)id3_tag_ptr; + + if (!es->props) { + es->props = gf_list_new(); + } + gf_list_add(es->props, t); + } + } + break; + case GF_M2TS_EVT_SCTE35_SPLICE_INFO: + { + GF_M2TS_SL_PCK *pck = (GF_M2TS_SL_PCK*)param; + GF_BitStream *bs; + GF_M2TS_Prop *t; + + //for now all SCTE35 must be associated with a stream + if (!pck->stream) return; + + // convey SCTE35 splice info to all streams of the program + u32 count = gf_list_count(pck->stream->program->streams); + for (i=0; i<count; i++) { + GF_M2TS_PES *es = gf_list_get(pck->stream->program->streams, i); + if (!es->user) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("M2TSDmx SCTE35 section not assigned to a given PID, not supported\n")); + continue; + } + + // attach SCTE35 info to video only + GF_FilterPid *opid = (GF_FilterPid *)es->user; + const GF_PropertyValue *p = gf_filter_pid_get_property(opid, GF_PROP_PID_STREAM_TYPE); + if (!p) return; + if (p->value.uint != GF_STREAM_VISUAL) + continue; + + GF_SAFEALLOC(t, GF_M2TS_Prop); + if (!t) break; + t->type = M2TS_SCTE35; + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + // ANSI/SCTE 67 2017 (13.1.1.3): "the entire SCTE 35 splice_info_section starting at the table_id and ending with the CRC_32" + gf_bs_write_data(bs, pck->data, pck->data_len); + gf_bs_get_content(bs, &t->data, &t->len); + gf_bs_del(bs); + + if (!es->props) { + es->props = gf_list_new(); + } + gf_list_add(es->props, t); + + // send SCTE35 info only to the first video pid + break; + } + } + break; case GF_M2TS_EVT_STREAM_REMOVED: { GF_M2TS_ES *es = (GF_M2TS_ES *)param; if (es && es->props) { while (gf_list_count(es->props)) { - GF_TEMIInfo *t = gf_list_pop_back(es->props); - gf_free(t->data); + GF_M2TS_Prop *t = gf_list_pop_back(es->props); + m2tsdmx_prop_free(t); gf_free(t); } gf_list_del(es->props); } } break; + case GF_M2TS_EVT_SECTION: + case GF_M2TS_EVT_SECTION_UPDATE: + { + GF_M2TS_GenericSectionInfo *sec_info = (GF_M2TS_GenericSectionInfo *)param; + if (!sec_info->stream || !sec_info->stream->user) break; + GF_FilterPid *opid = (GF_FilterPid *)sec_info->stream->user; + if (!opid) break; + if (!sec_info->section_data_len) break; + u8 *output; + GF_FilterPacket *pck = gf_filter_pck_new_alloc(opid, sec_info->section_data_len, &output); + if (!pck) return; + memcpy(output, sec_info->section_data, sec_info->section_data_len); + gf_filter_pck_set_framing(pck, + sec_info->section_idx ? GF_FALSE : GF_TRUE, + (sec_info->section_idx+1==sec_info->num_sections) ? GF_TRUE : GF_FALSE + ); + + u64 pts = m2tsdmx_translate_ts(ctx, sec_info->stream->program, sec_info->pts); + gf_filter_pck_set_cts(pck, pts); + if (pts != sec_info->pts) + gf_filter_pck_set_property(pck, GF_PROP_PCK_ORIGINAL_PTS, &PROP_LONGUINT(sec_info->pts) ); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_property_str(pck, "table", &PROP_UINT(sec_info->table_id) ); + gf_filter_pck_set_property_str(pck, "table_ex", &PROP_UINT(sec_info->ex_table_id) ); + gf_filter_pck_set_property_str(pck, "version", &PROP_UINT(sec_info->version_number) ); + gf_filter_pck_send(pck); + } + break; } } @@ -1189,6 +1579,10 @@ gf_filter_pid_send_event(pid, &evt); } } + if (!ctx->ipid) { + p = gf_filter_pid_get_property_str(pid, "filter_pid"); + if (p) ctx->forward_for = p->value.uint; + } ctx->ipid = pid; return GF_OK; } @@ -1302,15 +1696,15 @@ } ctx->nb_stop_pending = 0; + ctx->map_time_on_prog_id = pes->program->number; + ctx->media_start_range = is_source_seek ? 0 : com->play.start_range; + //not file, don't cancel the event if (!ctx->is_file) { ctx->initial_play_done = GF_TRUE; return GF_FALSE; } - ctx->map_time_on_prog_id = pes->program->number; - ctx->media_start_range = is_source_seek ? 0 : com->play.start_range; - if (is_source_seek) { file_pos = com->play.hint_start_offset; } @@ -1320,6 +1714,9 @@ file_pos /= ctx->duration.num; if (file_pos > ctx->file_size) return GF_TRUE; } + //round down to packet boundary + file_pos /= ctx->ts->prefix_present ? 192 : 188; + file_pos *= ctx->ts->prefix_present ? 192 : 188; if (!ctx->initial_play_done) { ctx->initial_play_done = GF_TRUE; @@ -1386,6 +1783,8 @@ if (ctx->dsmcc) { gf_m2ts_demux_dmscc_init(ctx->ts); } + if (ctx->analyze) + ctx->mappcr = GF_FALSE; return GF_OK; } @@ -1398,10 +1797,13 @@ } +#define M2TS_MAX_LOOPS 50 + static GF_Err m2tsdmx_process(GF_Filter *filter) { GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); GF_FilterPacket *pck; + u32 nb_loops=M2TS_MAX_LOOPS; Bool check_block = GF_TRUE; const char *data; u32 size; @@ -1449,7 +1851,10 @@ if (ctx->nb_playing) { gf_filter_ask_rt_reschedule(filter, 0); } - if (ctx->nb_stopped_at_init==nb_streams) { + if ((ctx->nb_stopped_at_init==nb_streams) + //this can happen if outputs are all blocking and a stop was issued + || (ctx->nb_stop_pending==nb_streams) + ) { gf_filter_pid_set_discard(ctx->ipid, GF_TRUE); return GF_EOS; } @@ -1471,7 +1876,10 @@ gf_filter_pid_send_event(ctx->ipid, &fevt); ctx->mux_tune_state = DMX_TUNE_DONE; gf_m2ts_reset_parsers(ctx->ts); - } else { + } + //don't run more than max_loops as we could end up blocking until eos in direct dispatch mode + else if (nb_loops) { + nb_loops--; goto restart; } return GF_OK; @@ -1500,6 +1908,8 @@ CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_PRIVATE_SCENE), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + //allow connections from tsgendts + CAP_UINT(GF_CAPS_INPUT_OPT|GF_CAPFLAG_PRESENT, GF_PROP_PID_TIMESCALE, 0) }; #define OFFS(_n) #_n, offsetof(GF_M2TSDmxCtx, _n) @@ -1510,7 +1920,15 @@ { OFFS(seeksrc), "seek local source file back to origin once all programs are setup", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(sigfrag), "signal segment boundaries on output packets for DASH or HLS sources", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(dvbtxt), "export DVB teletext streams", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(upes), "keep unknown PES streams\n" + "- no: ignored the streams\n" + "- info: declare the stream as fake (no data forward), turns on dvbtxt\n" + "- full: declare the stream and sends data", GF_PROP_UINT, "no", "no|info|full", GF_FS_ARG_HINT_EXPERT}, + + { OFFS(mappcr), "remap PCR and timestamps into continuous timeline", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(analyze), "skip PCR remapping - shall only be used with inspect filter analyze mode!", GF_PROP_UINT, "off", "off|on|bs|full", GF_FS_ARG_HINT_HIDE}, {0} }; @@ -1534,6 +1952,7 @@ .process = m2tsdmx_process, .process_event = m2tsdmx_process_event, .probe_data = m2tsdmx_probe_data, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER };
View file
gpac-2.4.0.tar.gz/src/filters/dmx_mpegps.c -> gpac-26.02.0.tar.gz/src/filters/dmx_mpegps.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / MPEG Program Stream demuxer filter @@ -488,7 +488,8 @@ .process_event = m2psdmx_process_event, .probe_data = m2psdmx_probe_data, //this filter is not very reliable, prefer ffmpeg when available - .priority = 255 + .priority = 255, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; #endif // GPAC_DISABLE_MPEG2PS
View file
gpac-2.4.0.tar.gz/src/filters/dmx_nhml.c -> gpac-26.02.0.tar.gz/src/filters/dmx_nhml.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / NHML demuxer filter @@ -319,7 +319,11 @@ j=0; while ( (bs_child = (GF_XMLNode *)gf_list_enum(childnode->content, &j))) { if (bs_child->type) continue; - if (!stricmp(bs_child->name, "BS")) has_bs = GF_TRUE; + if (!stricmp(bs_child->name, "BS") || + !stricmp(bs_child->name, "SCTE35") || + !stricmp(bs_child->name, "EventMessageEmptyBox") || + !stricmp(bs_child->name, "EventMessageInstanceBox")) + has_bs = GF_TRUE; } @@ -496,8 +500,8 @@ ctx->samp_buffer_size = (u32) (breaker.to_pos - breaker.from_pos); - if (ctx->samp_buffer_alloc < ctx->samp_buffer_size) { - ctx->samp_buffer_alloc = ctx->samp_buffer_size; + if (ctx->samp_buffer_alloc < ctx->samp_buffer_size+1) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size+1; ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); } gf_fseek(xml, breaker.from_pos, SEEK_SET); @@ -505,7 +509,8 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("NHMLDmx Failed to read samp->dataLength\n")); } e = GF_OK; - + ctx->samp_bufferctx->samp_buffer_size=0; + exit: if (xml) gf_fclose(xml); while (gf_list_count(breaker.id_stack)) { @@ -578,13 +583,14 @@ *dict = (char*)gf_malloc(sizeof(char) * ctx->samp_buffer_size); memcpy(*dict, ctx->samp_buffer, ctx->samp_buffer_size); } - if (ctx->samp_buffer_alloc < stream.total_out) { - ctx->samp_buffer_alloc = (u32) (stream.total_out*2); - ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, ctx->samp_buffer_alloc * sizeof(char)); + if (!ctx->samp_buffer || ctx->samp_buffer_alloc < stream.total_out + MAX(1, offset)) { + ctx->samp_buffer_alloc = (u32) MAX( (stream.total_out*2) + 1, offset + stream.total_out ) ; + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); } memcpy(ctx->samp_buffer + offset, ctx->zlib_buffer, sizeof(char)*stream.total_out); ctx->samp_buffer_size = (u32) (offset + stream.total_out); + ctx->samp_bufferctx->samp_buffer_size=0; deflateEnd(&stream); return GF_OK; @@ -1355,6 +1361,8 @@ if (stricmp(node->name, ctx->is_dims ? "DIMSUnit" : "NHNTSample") ) { if (!strcmp(node->name, "NHNTReconfig")) { nhmldmx_config_output(filter, ctx, node); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("NHMLDmx Unknown XML node %s in %s - ignoring\n", node->name, ctx->is_dims ? "DIMSStream" : "NHNTStream")); } continue; } @@ -1477,7 +1485,10 @@ if (!stricmp(childnode->name, "SAI")) { has_sai_child = GF_TRUE; } - if (!stricmp(childnode->name, "BS")) { + if (!stricmp(childnode->name, "BS") || + !stricmp(childnode->name, "SCTE35") || + !stricmp(childnode->name, "EventMessageEmptyBox") || + !stricmp(childnode->name, "EventMessageInstanceBox")) { has_subbs = GF_TRUE; } if (!stricmp(childnode->name, "Properties")) { @@ -1496,9 +1507,9 @@ char *content = gf_xml_dom_serialize(node, GF_TRUE, GF_FALSE); ctx->samp_buffer_size = 3 + (u32) strlen(content); - if (ctx->samp_buffer_alloc < ctx->samp_buffer_size) { - ctx->samp_buffer_alloc = ctx->samp_buffer_size; - ctx->samp_buffer = gf_realloc(ctx->samp_buffer, ctx->samp_buffer_size); + if (ctx->samp_buffer_alloc < ctx->samp_buffer_size+1) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size+1; + ctx->samp_buffer = gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); } nhml_get_bs(&ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_size, GF_BITSTREAM_WRITE); gf_bs_write_u16(ctx->bs_w, ctx->samp_buffer_size - 2); @@ -1513,8 +1524,8 @@ char *start = strchr(base_data, ','); if (start) { u32 len = (u32)strlen(start+1); - if (len > ctx->samp_buffer_alloc) { - ctx->samp_buffer_alloc = len; + if (len + 1 > ctx->samp_buffer_alloc) { + ctx->samp_buffer_alloc = len+1; ctx->samp_buffer = gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); } ctx->samp_buffer_size = gf_base64_decode(start, len, ctx->samp_buffer, ctx->samp_buffer_alloc); @@ -1553,9 +1564,9 @@ if (ctx->is_dims) { u32 read; - if (ctx->samp_buffer_size + 3 > ctx->samp_buffer_alloc) { - ctx->samp_buffer_alloc = ctx->samp_buffer_size + 3; - ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char) * ctx->samp_buffer_alloc); + if (ctx->samp_buffer_size + 4 > ctx->samp_buffer_alloc) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size + 4; + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); } nhml_get_bs(&ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_alloc, GF_BITSTREAM_WRITE); gf_bs_write_u16(ctx->bs_w, ctx->samp_buffer_size+1); @@ -1571,13 +1582,24 @@ append = GF_TRUE; } else { u32 read; - if (ctx->samp_buffer_alloc < ctx->samp_buffer_size) { - ctx->samp_buffer_alloc = ctx->samp_buffer_size; - ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char) * ctx->samp_buffer_alloc); + if (ctx->samp_buffer_size >= GF_UINT_MAX-1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("NHMLDmx Failed to read sample %d: invalid size %u\n", ctx->sample_num, ctx->samp_buffer_size)); + e = GF_NON_COMPLIANT_BITSTREAM; } - read = (u32) gf_fread(ctx->samp_buffer, ctx->samp_buffer_size, f); - if (ctx->samp_buffer_size != read) { - GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("NHMLDmx Failed to fully read sample %d: dataLength %d read %d\n", ctx->sample_num, ctx->samp_buffer_size, read)); + else { + if (ctx->samp_buffer_alloc < ctx->samp_buffer_size + 1) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size + 1; + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); + } + if (ctx->samp_buffer) { + read = (u32) gf_fread(ctx->samp_buffer, ctx->samp_buffer_size, f); + if (ctx->samp_buffer_size != read) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("NHMLDmx Failed to fully read sample %d: dataLength %d read %d\n", ctx->sample_num, ctx->samp_buffer_size, read)); + } + } + else { + e = GF_NON_COMPLIANT_BITSTREAM; + } } } } else { @@ -1585,8 +1607,9 @@ } if (f && close) gf_fclose(f); } - if (e) return e; + if (ctx->samp_buffer) + ctx->samp_bufferctx->samp_buffer_size=0; //child BS present, parse them if (has_subbs) { @@ -1601,12 +1624,13 @@ gf_bs_get_content(bs_tmp, &output, &ctx->samp_buffer_size); gf_bs_del(bs_tmp); - if (ctx->samp_buffer_size > ctx->samp_buffer_alloc) { - ctx->samp_buffer_alloc = ctx->samp_buffer_size; - ctx->samp_buffer = gf_realloc(ctx->samp_buffer, ctx->samp_buffer_size); + if (ctx->samp_buffer_size + 1 > ctx->samp_buffer_alloc) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size+1; + ctx->samp_buffer = gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); } memcpy(ctx->samp_buffer, output, ctx->samp_buffer_size); gf_free(output); + ctx->samp_bufferctx->samp_buffer_size = 0; } if (!ctx->samp_buffer_size) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("NHMLDmx No media file associated with sample %d!\n", ctx->sample_num)); @@ -1690,6 +1714,54 @@ return GF_OK; } +GF_Err nhmldmx_update_document(GF_Filter *filter, GF_NHMLDmxCtx *ctx) +{ + GF_FilterPacket *pck; + GF_Err e; + char *szImpName, *szSampleName; + + szImpName = ctx->is_dims ? "DIMS" : "NHML"; + szSampleName = ctx->is_dims ? "DIMSUnit" : "NHNTSample"; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) return GF_OK; + + const u8 *data; + u32 size; + data = gf_filter_pck_get_data(pck, &size); + GF_FileIO *fio = gf_fileio_from_mem(NULL, data, size); + const char *url = gf_fileio_url(fio); + + GF_DOMParser *parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(parser, url, NULL, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("NHMLDmx Error parsing %s update: Line %d - %s\n", szImpName, gf_xml_dom_get_line(parser), gf_xml_dom_get_error(parser) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + + GF_XMLNode *root = gf_xml_dom_get_root(parser); + if (!root) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("NHMLDmx Error parsing %s update - no root node found\n", szImpName )); + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (stricmp(root->name, szSampleName)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("NHMLDmx Error parsing %s update - \"%s\" sample expected, got \"%s\"", szImpName, szSampleName, root->name)); + return GF_NON_COMPLIANT_BITSTREAM; + } + + //update the root node + gf_xml_dom_append_child(ctx->root, gf_xml_dom_node_clone(root)); + gf_xml_dom_del(parser); + gf_fclose((FILE*)fio); + + // Update the child index + u32 new_idx = MAX(gf_list_count(ctx->root->content) - 1, 0); + ctx->current_child_idx = MIN(new_idx, ctx->current_child_idx); + + return GF_OK; +} + GF_Err nhmldmx_process(GF_Filter *filter) { GF_NHMLDmxCtx *ctx = gf_filter_get_udta(filter); @@ -1704,40 +1776,45 @@ gf_assert(end); } - - //need init ? - switch (ctx->parsing_state) { - case 0: + if (ctx->parsing_state == 0) { e = nhmldmx_init_parsing(filter, ctx); if (e) { - ctx->parsing_state = 3; - return e; + gf_filter_abort(filter); + goto eos; } ctx->parsing_state = 1; - //fall-through - case 1: - if (!ctx->is_playing) return GF_OK; + } - e = nhmldmx_send_sample(filter, ctx); - if (e) return e; - break; - //EOS - case 2: - default: - if (pck) { - gf_filter_pid_drop_packet(ctx->ipid); - if (ctx->fio) { - gf_fclose((FILE*) ctx->fio); - ctx->fio = NULL; - } - } - if (ctx->opid) { - gf_filter_pid_set_eos(ctx->opid); - return GF_EOS; + if (!ctx->is_playing) + return GF_OK; + + if (ctx->parsing_state == 2) { + e = nhmldmx_update_document(filter, ctx); + if (e) goto eos; + ctx->parsing_state = 1; + } + + e = nhmldmx_send_sample(filter, ctx); + if (e) return e; + if (ctx->parsing_state == 1) + return GF_OK; //samples haven't been consumed yet + + if (pck) { + gf_filter_pid_drop_packet(ctx->ipid); + if (ctx->fio) { + gf_fclose((FILE*) ctx->fio); + ctx->fio = NULL; } - break; } + if (gf_filter_pid_is_eos(ctx->ipid)) + goto eos; + return GF_OK; + +eos: + ctx->parsing_state = 3; + if (ctx->opid) gf_filter_pid_set_eos(ctx->opid); + return e == GF_OK ? GF_EOS : e; } GF_Err nhmldmx_initialize(GF_Filter *filter) @@ -1788,7 +1865,7 @@ .name = "nhmlr", GF_FS_SET_DESCRIPTION("NHML reader") GF_FS_SET_HELP("This filter reads NHML files/data to produce a media PID and frames.\n" - "NHML documentation is available at https://wiki.gpac.io/NHML-Format\n") + "NHML documentation is available at https://wiki.gpac.io/xmlformats/NHML-Format\n") .private_size = sizeof(GF_NHMLDmxCtx), .flags = GF_FS_REG_USE_SYNC_READ, .args = GF_NHMLDmxArgs, @@ -1797,7 +1874,8 @@ SETCAPS(NHMLDmxCaps), .configure_pid = nhmldmx_configure_pid, .process = nhmldmx_process, - .process_event = nhmldmx_process_event + .process_event = nhmldmx_process_event, + .hint_class_type = GF_FS_CLASS_TOOL }; const GF_FilterRegister *nhmlr_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dmx_nhnt.c -> gpac-26.02.0.tar.gz/src/filters/dmx_nhnt.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / NHNT demuxer filter @@ -449,7 +449,7 @@ dst_pck = gf_filter_pck_new_alloc(ctx->opid, len, &output); if (!dst_pck) return GF_OUT_OF_MEM; - + res = (u32) gf_fread(output, len, ctx->mdia); if (res != len) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("NHNT Read failure, expecting %d bytes got %d", len, res)); @@ -509,7 +509,7 @@ .name = "nhntr", GF_FS_SET_DESCRIPTION("NHNT reader") GF_FS_SET_HELP("This filter reads NHNT files/data to produce a media PID and frames.\n" - "NHNT documentation is available at https://wiki.gpac.io/NHNT-Format\n") + "NHNT documentation is available at https://wiki.gpac.io/xmlformats/NHNT-Format\n") .private_size = sizeof(GF_NHNTDmxCtx), .flags = GF_FS_REG_USE_SYNC_READ, .args = GF_NHNTDmxArgs, @@ -518,7 +518,8 @@ SETCAPS(NHNTDmxCaps), .configure_pid = nhntdmx_configure_pid, .process = nhntdmx_process, - .process_event = nhntdmx_process_event + .process_event = nhntdmx_process_event, + .hint_class_type = GF_FS_CLASS_TOOL }; const GF_FilterRegister *nhntr_register(GF_FilterSession *session) @@ -531,4 +532,3 @@ return NULL; } #endif // GPAC_DISABLE_NHNTR -
View file
gpac-2.4.0.tar.gz/src/filters/dmx_ogg.c -> gpac-26.02.0.tar.gz/src/filters/dmx_ogg.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2022 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / XIPH OGG demux filter @@ -231,6 +231,7 @@ //opus DSI is formatted as box (ffmpeg compat) we might want to change that to avoid the box header if (st->info.type==GF_CODECID_OPUS) { + if (st->dsi_bs) gf_bs_del(st->dsi_bs); st->dsi_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_odf_opus_cfg_write_bs(st->opus_cfg, st->dsi_bs); st->info.nb_chan = st->opus_cfg->OutputChannelCount; @@ -316,9 +317,13 @@ st->parse_headers = st->info.num_init_headers; switch (st->info.type) { case GF_CODECID_VORBIS: + if (st->vorbis_parser) + gf_free(st->vorbis_parser); GF_SAFEALLOC(st->vorbis_parser, GF_VorbisParser); break; case GF_CODECID_OPUS: + if (st->opus_cfg) + gf_free(st->opus_cfg); GF_SAFEALLOC(st->opus_cfg, GF_OpusConfig); break; default: @@ -409,7 +414,7 @@ recompute_ts = 0; max_gran = 0; while (1) { - char buf10000; + char buf2000; while (ogg_sync_pageout(&oy, &oggpage) != 1 ) { char *buffer; u32 bytes; @@ -417,7 +422,7 @@ if (gf_feof(stream)) break; - bytes = (u32) gf_fread(buf, 10000, stream); + bytes = (u32) gf_fread(buf, 2000, stream); if (!bytes) break; buffer = ogg_sync_buffer(&oy, bytes); memcpy(buffer, buf, bytes); @@ -813,7 +818,7 @@ gf_bs_write_u16(st->dsi_bs, oggpacket.bytes); gf_bs_write_data(st->dsi_bs, (char *) oggpacket.packet, oggpacket.bytes); } - + st->parse_headers--; if (!st->parse_headers) { st->got_headers = GF_TRUE; @@ -878,14 +883,14 @@ } dst_pck = gf_filter_pck_new_alloc(st->opid, oggpacket.bytes, &output); if (!dst_pck) return GF_OUT_OF_MEM; - + memcpy(output, (char *) oggpacket.packet, oggpacket.bytes); gf_filter_pck_set_cts(dst_pck, st->recomputed_ts); //compat with old arch (keep same hashes), to remove once dropping it if (!gf_sys_old_arch_compat()) { gf_filter_pck_set_duration(dst_pck, block_size); } - + if (st->info.type == GF_CODECID_VORBIS) { gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); } else if (st->info.type == GF_CODECID_OPUS) { @@ -987,6 +992,7 @@ .process = oggdmx_process, .process_event = oggdmx_process_event, .probe_data = oggdmx_probe_data, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER, }; #endif // !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) @@ -1000,4 +1006,3 @@ #endif } -
View file
gpac-2.4.0.tar.gz/src/filters/dmx_saf.c -> gpac-26.02.0.tar.gz/src/filters/dmx_saf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / SAF demuxer filter @@ -233,7 +233,6 @@ GF_BitStream *bs; GF_Fraction64 dur; const GF_PropertyValue *p; - StreamInfo si1024; FILE *stream; @@ -259,6 +258,7 @@ bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); ctx->file_size = gf_bs_get_size(bs); + StreamInfo *si = NULL; dur.num = 0; dur.den = 1000; nb_streams=0; @@ -279,9 +279,12 @@ gf_bs_read_u16(bs); ts_res = gf_bs_read_u24(bs); au_size -= 5; - sinb_streams.stream_id = stream_id; - sinb_streams.ts_res = ts_res; - nb_streams++; + si = gf_realloc(si, sizeof(StreamInfo)*(nb_streams+1)); + if (si) { + sinb_streams.stream_id = stream_id; + sinb_streams.ts_res = ts_res; + nb_streams++; + } } } if (ts_res && (au_type==4)) { @@ -291,6 +294,7 @@ } gf_bs_skip_bytes(bs, au_size); } + gf_free(si); gf_bs_del(bs); gf_fclose(stream); @@ -463,7 +467,8 @@ .configure_pid = safdmx_configure_pid, .process = safdmx_process, .process_event = safdmx_process_event, - .probe_data = safdmx_probe_data + .probe_data = safdmx_probe_data, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; const GF_FilterRegister *safdmx_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/dmx_vobsub.c -> gpac-26.02.0.tar.gz/src/filters/dmx_vobsub.c
Changed
@@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * - * This file is part of GPAC / NHNT demuxer filter + * This file is part of GPAC / vobsub demuxer filter * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -37,7 +37,7 @@ typedef struct { //opts - Bool blankframe; + Bool blankframe, keepempty; GF_FilterPid *idx_pid, *sub_pid; GF_Filter *sub_filter; @@ -219,12 +219,15 @@ for (i=0; i<ctx->vobsub->num_langs; i++) { vobsub_pos *pos = (vobsub_pos*)gf_list_last(ctx->vobsub->langsi.subpos); - if ((u64) ctx->duration.num < pos->start*90) + if (pos && ((u64) ctx->duration.num < pos->start*90)) ctx->duration.num = (s64) (pos->start*90); } ctx->duration.den = 90000; for (i=0; i<ctx->vobsub->num_langs; i++) { + //ignore empty tracks + if (!ctx->keepempty && !gf_list_count(ctx->vobsub->langsi.subpos)) + continue; GF_FilterPid *opid = gf_filter_pid_new(filter); //copy properties from idx pid @@ -444,6 +447,7 @@ static const GF_FilterArgs GF_VOBSubDmxArgs = { { OFFS(blankframe), "force inserting a blank frame if first subpic is not at 0", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(keepempty), "declare VobSub tracks with no frames", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, {0} }; @@ -460,7 +464,7 @@ GF_FilterRegister VOBSubDmxRegister = { .name = "vobsubdmx", - GF_FS_SET_DESCRIPTION("VobSub parser") + GF_FS_SET_DESCRIPTION("VobSub demultiplexer") GF_FS_SET_HELP("This filter parses VobSub files/data to produce media PIDs and frames.") .private_size = sizeof(GF_VOBSubDmxCtx), .flags = GF_FS_REG_USE_SYNC_READ, @@ -471,7 +475,8 @@ .configure_pid = vobsubdmx_configure_pid, .process = vobsubdmx_process, .probe_data = vobsubdmx_probe_data, - .process_event = vobsubdmx_process_event + .process_event = vobsubdmx_process_event, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; #endif
View file
gpac-26.02.0.tar.gz/src/filters/enc_cc.c
Added
@@ -0,0 +1,658 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Deniz Ugur + * Copyright (c) Motion Spell 2025 + * All rights reserved + * + * This file is part of GPAC / Closed captions encoder filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/avparse.h> +#include <gpac/filters.h> +#include <gpac/internal/media_dev.h> +#include <gpac/tools.h> + +#ifdef GPAC_HAS_LIBCAPTION +#include <caption/mpeg.h> + +#if defined(WIN32) || defined(_WIN32_WCE) +#if !defined(__GNUC__) +#pragma comment(lib, "libcaption") +#endif +#endif + +#define GPAC_TX3G_DATA_OFFSET (2) + +enum { + CCTYPE_UNK = 0, + CCTYPE_AVC, + CCTYPE_HEVC, + CCTYPE_VVC, +}; + +typedef struct +{ + u64 cts; + u32 dur; + char *text; + Bool is_clear; +} CCItem; + +typedef struct +{ + GF_FilterPid *vipid; // video input pid + GF_FilterPid *sipid; // subtitle input pid + GF_FilterPid *opid; + u32 v_ts, s_ts; + + Bool is_cc_eos; + u32 cctype; + caption_frame_t *ccframe; + sei_t *sei; + + GF_Fraction vb_time; + u32 nalu_size_len; + + GF_List *cc_queue; + GF_List *frame_queue; +} CCEncCtx; + + +GF_Err ccenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPid *out_pid; + const GF_PropertyValue *prop; + + if (is_remove) { + out_pid = gf_filter_pid_get_udta(pid); + if (out_pid == ctx->opid) + ctx->opid = NULL; + if (out_pid) + gf_filter_pid_remove(out_pid); + return GF_OK; + } + + if (!gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + if (!ctx->opid) return GF_OUT_OF_MEM; + gf_filter_pid_set_udta(pid, ctx->opid); + } + + // get the codec id + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop) return GF_BAD_PARAM; + u32 codec_id = prop->value.uint; + + // get the stream type + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop) return GF_BAD_PARAM; + u32 stream_type = prop->value.uint; + + if (stream_type == GF_STREAM_TEXT) { + ctx->sipid = pid; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + ctx->s_ts = prop ? prop->value.uint : 1000; + return GF_OK; + } + + ctx->vipid = pid; + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + ctx->v_ts = prop ? prop->value.uint : 1000; + + ctx->cctype = CCTYPE_UNK; + switch (codec_id) { + case GF_CODECID_AVC: + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (prop) { + GF_AVCConfig *avcc = gf_odf_avc_cfg_read(prop->value.data.ptr, prop->value.data.size); + if (avcc) { + ctx->nalu_size_len = avcc->nal_unit_size; + gf_odf_avc_cfg_del(avcc); + ctx->cctype = CCTYPE_AVC; + } else { + return GF_NON_COMPLIANT_BITSTREAM; + } + } else { + return GF_OK; + } + break; + case GF_CODECID_HEVC: + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (prop) { + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(prop->value.data.ptr, prop->value.data.size, GF_FALSE); + if (hvcc) { + ctx->nalu_size_len = hvcc->nal_unit_size; + gf_odf_hevc_cfg_del(hvcc); + ctx->cctype = CCTYPE_HEVC; + } else { + return GF_NON_COMPLIANT_BITSTREAM; + } + } else { + return GF_OK; + } + break; + case GF_CODECID_VVC: + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (prop) { + GF_VVCConfig *vvcc = gf_odf_vvc_cfg_read(prop->value.data.ptr, prop->value.data.size); + if (vvcc) { + ctx->nalu_size_len = vvcc->nal_unit_size; + gf_odf_vvc_cfg_del(vvcc); + ctx->cctype = CCTYPE_VVC; + } else { + return GF_NON_COMPLIANT_BITSTREAM; + } + } else { + return GF_OK; + } + break; + } + + if (ctx->cctype == CCTYPE_UNK) return GF_NOT_SUPPORTED; + return GF_OK; +} + +static GF_Err ccenc_enqueue_cc_clear(GF_Filter *filter, u64 cts) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + + // Find the insertion point + u32 pos = 0; + CCItem *item = NULL; + while ((item = gf_list_enum(ctx->cc_queue, &pos))) { + // new caption would clear the previous one + if (item->cts == cts) return GF_OK; + if (item->cts > cts) break; + } + + // Allocate a new CC item + CCItem *cc; + GF_SAFEALLOC(cc, CCItem); + if (!cc) return GF_OUT_OF_MEM; + cc->cts = cts; + cc->is_clear = GF_TRUE; + + // Add the CC item to the queue + gf_list_insert(ctx->cc_queue, cc, pos - 1); + return GF_OK; +} + +static GF_Err ccenc_enqueue_cc(GF_Filter *filter, GF_FilterPacket *pck) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + + u32 size; + const u8 *data = gf_filter_pck_get_data(pck, &size); + gf_assert(size >= GPAC_TX3G_DATA_OFFSET); + u16 len = (data0 << 8) | data1; + if (!len) return GF_OK; + + // Allocate a new CC item + CCItem *cc; + GF_SAFEALLOC(cc, CCItem); + if (!cc) return GF_OUT_OF_MEM; + cc->cts = gf_filter_pck_get_cts(pck); + cc->dur = gf_filter_pck_get_duration(pck); + cc->is_clear = GF_FALSE; + + // Create null-terminated text from the subtitle data + cc->text = gf_malloc(len + 1); + if (!cc->text) { + gf_free(cc); + return GF_OUT_OF_MEM; + } + memcpy(cc->text, data + GPAC_TX3G_DATA_OFFSET, len); + memset(cc->text + len, 0, 1); + + if (len > SCREEN_COLS * SCREEN_ROWS) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("ccenc Caption at CTS=%llu exceeds maximum length of %u bytes. Truncating\n", cc->cts, SCREEN_COLS * SCREEN_ROWS)); + cc->text(SCREEN_COLS * SCREEN_ROWS) - 1 = 0; + } + + // If there is a clear command with the same timestamp, remove it + u32 pos = 0; + CCItem *item = NULL; + while ((item = gf_list_enum(ctx->cc_queue, &pos))) { + if (item->cts == cc->cts && item->is_clear) { + gf_list_rem(ctx->cc_queue, pos - 1); + gf_free(item); + break; + } + } + + // Add the CC item to the queue + gf_list_add(ctx->cc_queue, cc); + return GF_OK; +} + +static void ccenc_pair(GF_Filter *filter, GF_FilterPacket *vpck, CCItem *cc) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + GF_Err err = GF_OK; + GF_BitStream *bs = NULL; + libcaption_stauts_t status; + u8 *sei_data = NULL; + u32 size; + + // forward the video if we haven't yet received any captions + if (!ctx->sei && !cc) { + gf_filter_pck_forward(vpck, ctx->opid); + gf_filter_pck_unref(vpck); + return; + } + +#define CHECK_OOM(_x) if (!_x) { err = GF_OUT_OF_MEM; goto error; } + + if (cc) { + // Create the caption frame + if (!cc->is_clear) { + if (!ctx->ccframe) GF_SAFEALLOC(ctx->ccframe, caption_frame_t); + caption_frame_from_text(ctx->ccframe, (const utf8_char_t *) cc->text); + gf_free(cc->text); + } + + // Create the SEI from the caption frame + if (!ctx->sei) GF_SAFEALLOC(ctx->sei, sei_t); + sei_free(ctx->sei); // also inits the sei + ctx->sei->timestamp = cc->cts / (double) ctx->s_ts; + status = cc->is_clear ? sei_from_caption_clear(ctx->sei) : sei_from_caption_frame(ctx->sei, ctx->ccframe); + if (status != LIBCAPTION_OK) { + err = GF_BAD_PARAM; + goto error; + } + } + + // Render the SEI + sei_data = gf_malloc(sei_render_size(ctx->sei)); + CHECK_OOM(sei_data); + size_t sei_render_size = sei_render(ctx->sei, sei_data); + + // Add EBP to the SEI + // sei_render_size includes nal_type (1 byte) + u32 nb_bytes_to_add = gf_media_nalu_emulation_bytes_add_count(sei_data + 1, (u32) sei_render_size - 1); + u32 sei_payload_size = (u32) sei_render_size - 1 + nb_bytes_to_add; + u8 *sei_data_with_epb = gf_malloc(sei_payload_size); + CHECK_OOM(sei_data_with_epb); + gf_media_nalu_add_emulation_bytes(sei_data + 1, sei_data_with_epb, (u32) sei_render_size - 1); + gf_free(sei_data); + + // Prepare the NALU writer + u8 nhdr_type_len = (ctx->cctype == CCTYPE_HEVC || ctx->cctype == CCTYPE_VVC) ? 2 : 1; + size_t nal_size = ctx->nalu_size_len + nhdr_type_len + sei_payload_size; + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + CHECK_OOM(bs); + + // Write the NALU header + gf_bs_write_int(bs, (u32) nal_size - ctx->nalu_size_len, ctx->nalu_size_len * 8); + + // Write the NALU type + if (ctx->cctype == CCTYPE_HEVC) { + gf_bs_write_int(bs, 0, 1); + gf_bs_write_int(bs, GF_HEVC_NALU_SEI_PREFIX, 6); + gf_bs_write_int(bs, 0, 6); + gf_bs_write_int(bs, 1, 3); + } else if (ctx->cctype == CCTYPE_VVC) { + gf_bs_write_int(bs, 0, 1); + gf_bs_write_int(bs, GF_VVC_NALU_SEI_PREFIX, 6); + gf_bs_write_int(bs, 0, 6); + gf_bs_write_int(bs, 1, 3); + } else { + gf_bs_write_int(bs, 0, 1); + gf_bs_write_int(bs, 0, 2); + gf_bs_write_int(bs, GF_AVC_NALU_SEI, 5); + } + + // Write the SEI + gf_bs_write_data(bs, sei_data_with_epb, sei_payload_size); + gf_free(sei_data_with_epb); + + // Write rest of the video data + const u8 *vdata = gf_filter_pck_get_data(vpck, &size); + gf_bs_write_data(bs, vdata, size); + + // Check the size + u32 new_size = (u32) nal_size + size; + gf_assert(new_size == gf_bs_get_position(bs)); + + // Create the new video packet + u8 *new_data = NULL; + GF_FilterPacket *new_vpck = gf_filter_pck_new_alloc(ctx->opid, new_size, &new_data); + CHECK_OOM(new_vpck); + gf_filter_pck_merge_properties(vpck, new_vpck); + + // Copy the data + u8 *bs_content = NULL; + gf_bs_get_content(bs, &bs_content, &size); + gf_assert(size == new_size); + memcpy(new_data, bs_content, new_size); + gf_free(bs_content); + + // Send the new packet + u64 min_dts = gf_list_count(ctx->frame_queue) ? gf_filter_pck_get_dts(gf_list_get(ctx->frame_queue, 0)) : GF_UINT64_MAX; + u64 cur_dts = gf_filter_pck_get_dts(new_vpck); + if (cur_dts > min_dts) { + // place the new packet in the queue, sorted + // next flush will forward it + u32 pos = 0; + GF_FilterPacket *item = NULL; + while ((item = gf_list_enum(ctx->frame_queue, &pos))) + if (gf_filter_pck_get_dts(item) > cur_dts) break; + gf_list_insert(ctx->frame_queue, new_vpck, pos - 1); + gf_filter_pck_ref(&new_vpck); // so that unreffing wouldn't error + } else + gf_filter_pck_send(new_vpck); + + // Enqueue clear command + // it is assumed that cts+dur is not after any subsequent ccs + if (cc && !cc->is_clear) + ccenc_enqueue_cc_clear(filter, cc->cts + cc->dur); + +#undef CHECK_OOM + +error: + if (bs) gf_bs_del(bs); + + // if we have an error, just forward the video frame + if (err != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("ccenc Error encountered while encoding caption, forwarding video frame: %s\n", gf_error_to_string(err))); + gf_filter_pck_forward(vpck, ctx->opid); + } + + gf_filter_pck_unref(vpck); + if (cc) gf_free(cc); +} + +static void ccenc_forward_video(GF_Filter *filter, GF_FilterPacket *vpck) +{ + // pairing with NULL repeats the previous caption + ccenc_pair(filter, vpck, NULL); + // no need to unref vpck, it's done in ccenc_pair +} + +static Bool ccenc_can_use_frame(GF_Filter *filter, GF_FilterPacket *vpck) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + if (gf_list_count(ctx->frame_queue) <= 1) return GF_FALSE; + + u64 newest_dts = gf_filter_pck_get_dts(gf_list_last(ctx->frame_queue)); + u64 current_dts = gf_filter_pck_get_dts(vpck); + return gf_timestamp_greater_or_equal(newest_dts - current_dts, ctx->v_ts, ctx->vb_time.num, ctx->vb_time.den); +} + +static void ccenc_flush(GF_Filter *filter, Bool full_flush) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + if (gf_list_count(ctx->cc_queue) == 0 && gf_list_count(ctx->frame_queue) == 0) return; + +retry: + // try to pair video and subtitle data, gracefully + while (gf_list_count(ctx->cc_queue) && gf_list_count(ctx->frame_queue)) { + GF_FilterPacket *vpck = gf_list_get(ctx->frame_queue, 0); + CCItem *ccitem = gf_list_get(ctx->cc_queue, 0); + + // if we have a video frame with cts lower than the minimum subtitle cts, send it + u64 last_video_cts = gf_filter_pck_get_cts(vpck); + u64 last_subtitle_cts = ccitem->cts; + if (gf_timestamp_less(last_video_cts, ctx->v_ts, last_subtitle_cts, ctx->s_ts)) { + // impossible to pair, send video frame + GF_FilterPacket *vpck = gf_list_pop_front(ctx->frame_queue); + ccenc_forward_video(filter, vpck); + continue; + } + + // check if it's exact match + if (gf_timestamp_equal(last_video_cts, ctx->v_ts, last_subtitle_cts, ctx->s_ts)) { + gf_list_del_item(ctx->cc_queue, ccitem); + ccenc_pair(filter, gf_list_pop_front(ctx->frame_queue), ccitem); + continue; + } + + // check if we have enough video frames to match the caption to a video frame + if (!ccenc_can_use_frame(filter, vpck) && !full_flush) + break; + + // try to find the closest video frame that can be paired with the caption + // at this point, it's guaranteed that the video frame is ahead of the subtitle frame + Bool found = GF_FALSE; + u32 pos = 1; + GF_FilterPacket *candidate_vpck = vpck; + u64 target_cc_cts = gf_timestamp_rescale(last_subtitle_cts, ctx->s_ts, ctx->v_ts); + u64 delta_cts = gf_filter_pck_get_cts(candidate_vpck) - target_cc_cts; + while (!found) { + GF_FilterPacket *next_vpck = gf_list_get(ctx->frame_queue, pos); + if (!next_vpck) break; + u64 next_delta_cts = gf_filter_pck_get_cts(next_vpck) - target_cc_cts; + + // with b-frame reordering, delta can decrease but not increase + if (next_delta_cts <= delta_cts) { + candidate_vpck = next_vpck; + delta_cts = next_delta_cts; + pos++; + } else + found = GF_TRUE; + } + + // if we found a candidate, pair it with the caption + if (found || full_flush) { + gf_list_del_item(ctx->frame_queue, candidate_vpck); + gf_list_del_item(ctx->cc_queue, ccitem); + ccenc_pair(filter, candidate_vpck, ccitem); + } else { + // we couldn't pair this video frame with a subtitle frame gracefully + break; + } + } + + // if there are no more video frames, there is nothing to do + if (gf_list_count(ctx->frame_queue) == 0) return; + + // flush the video frames based on buffer time + if (ccenc_can_use_frame(filter, gf_list_get(ctx->frame_queue, 0))) { + // we couldn't pair this video frame with a subtitle frame + GF_FilterPacket *vpck = gf_list_pop_front(ctx->frame_queue); + ccenc_forward_video(filter, vpck); + + // we might be able to pair the next video frame with a subtitle frame + goto retry; + } + + if (full_flush) { + // we've tried to pair all video frames with subtitle frames, but we still have video frames left + while (gf_list_count(ctx->frame_queue)) { + GF_FilterPacket *vpck = gf_list_pop_front(ctx->frame_queue); + ccenc_forward_video(filter, vpck); + } + } +} + +GF_Err ccenc_process(GF_Filter *filter) +{ + GF_Err err; + CCEncCtx *ctx = gf_filter_get_udta(filter); + + if (gf_filter_connections_pending(filter)) + return GF_OK; + + if (!ctx->vipid || !ctx->sipid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("ccenc Missing %s input\n", ctx->vipid ? "subtitle" : "video")); + return GF_BAD_PARAM; + } + + // check if we have subtitle data + GF_FilterPacket *spck = gf_filter_pid_get_packet(ctx->sipid); + if (spck) { + err = ccenc_enqueue_cc(filter, spck); + gf_filter_pid_drop_packet(ctx->sipid); + if (err != GF_OK) return err; + } else if (gf_filter_pid_is_eos(ctx->sipid)) { + ctx->is_cc_eos = GF_TRUE; + } + + // check if we have video data + GF_FilterPacket *vpck = gf_filter_pid_get_packet(ctx->vipid); + if (vpck) { + // if no more subtitle data, we can just forward video data + if (ctx->is_cc_eos && gf_list_count(ctx->cc_queue) == 0) { + ccenc_flush(filter, GF_TRUE); + gf_filter_pck_forward(vpck, ctx->opid); + gf_filter_pid_drop_packet(ctx->vipid); + return GF_OK; + } + + gf_filter_pck_ref(&vpck); + gf_list_add(ctx->frame_queue, vpck); + gf_filter_pid_drop_packet(ctx->vipid); + } else if (gf_filter_pid_is_eos(ctx->vipid)) { + ccenc_flush(filter, GF_TRUE); + if (gf_list_count(ctx->cc_queue) > 0) { + Bool should_warn = GF_FALSE; + while (gf_list_count(ctx->cc_queue)) { + CCItem *cc = gf_list_pop_back(ctx->cc_queue); + if (!cc->is_clear) { + should_warn = GF_TRUE; + gf_free(cc->text); + } + gf_free(cc); + } + if (should_warn) + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("ccenc EOS reached on video input, but subtitle data is still available. Discarding remaining subtitle data\n")); + } + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + + // Try to flush after we tried to get both video and subtitle data + ccenc_flush(filter, GF_FALSE); + + // if we couldn't process the current "blocking" video frame, forward it + if (vpck && gf_list_last(ctx->frame_queue) == vpck) { + if (gf_filter_pck_is_blocking_ref(vpck)) + ccenc_forward_video(filter, gf_list_pop_back(ctx->frame_queue)); + } + + return GF_OK; +} + +static GF_Err ccenc_initialize(GF_Filter *filter) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + ctx->is_cc_eos = GF_FALSE; + ctx->cc_queue = gf_list_new(); + ccenc_enqueue_cc_clear(filter, 0); + if (!ctx->cc_queue) return GF_OUT_OF_MEM; + ctx->frame_queue = gf_list_new(); + if (!ctx->frame_queue) return GF_OUT_OF_MEM; + return GF_OK; +} + +static void ccenc_finalize(GF_Filter *filter) +{ + CCEncCtx *ctx = gf_filter_get_udta(filter); + + if (gf_list_count(ctx->cc_queue) > 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("ccenc Finalizing with %u CC items left in the queue\n", gf_list_count(ctx->cc_queue))); + // free all CC items in the queue + u32 pos = 0; + CCItem *cc = NULL; + while ((cc = gf_list_enum(ctx->cc_queue, &pos))) { + if (cc->text) gf_free(cc->text); + gf_free(cc); + } + } + gf_list_del(ctx->cc_queue); + + if (gf_list_count(ctx->frame_queue) > 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("ccenc Finalizing with %u video frames left in the queue\n", gf_list_count(ctx->frame_queue))); + // free all video frames in the queue + u32 pos = 0; + GF_FilterPacket *vpck = NULL; + while ((vpck = gf_list_enum(ctx->frame_queue, &pos))) + gf_filter_pck_unref(vpck); + } + gf_list_del(ctx->frame_queue); + + if (ctx->ccframe) gf_free(ctx->ccframe); + if (ctx->sei) { + sei_free(ctx->sei); + gf_free(ctx->sei); + } +} + +static const GF_FilterCapability CCEncCaps = { + // supported video input + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VVC), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + { 0 }, + // supported subtitle input + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_TX3G) +}; + +#define OFFS(_n) #_n, offsetof(CCEncCtx, _n) +static const GF_FilterArgs CCEncArgs = { + { OFFS(vb_time), "time to hold video packets if no caption is available", GF_PROP_FRACTION, "1/1", NULL, GF_FS_ARG_HINT_EXPERT }, + { 0 } +}; + +GF_FilterRegister CCEncRegister = { + .name = "ccenc", + GF_FS_SET_DESCRIPTION("Closed-Caption encoder") + GF_FS_SET_HELP("This filter encodes Closed Captions to the video stream.\n" + "Supported video media types are MPEG2, AVC, HEVC, VVC and AV1 streams.\n" + "\nOnly a subset of CEA 608/708 is supported.") + .private_size = sizeof(CCEncCtx), + .max_extra_pids = 1, + .args = CCEncArgs, + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS(CCEncCaps), + .initialize = ccenc_initialize, + .finalize = ccenc_finalize, + .process = ccenc_process, + .configure_pid = ccenc_configure_pid, + .hint_class_type = GF_FS_CLASS_SUBTITLE +}; + +const GF_FilterRegister *ccenc_register(GF_FilterSession *session) +{ + return &CCEncRegister; +} +#else //GPAC_HAS_LIBCAPTION +const GF_FilterRegister *ccenc_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_CCDEC
View file
gpac-2.4.0.tar.gz/src/filters/enc_jpg.c -> gpac-26.02.0.tar.gz/src/filters/enc_jpg.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2021 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / libjpeg encoder filter @@ -403,6 +403,7 @@ .initialize = jpgenc_initialize, .configure_pid = jpgenc_configure_pid, .process = jpgenc_process, + .hint_class_type = GF_FS_CLASS_ENCODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/enc_png.c -> gpac-26.02.0.tar.gz/src/filters/enc_png.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2021 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / libpng encoder filter @@ -385,6 +385,7 @@ SETCAPS(PNGEncCaps), .configure_pid = pngenc_configure_pid, .process = pngenc_process, + .hint_class_type = GF_FS_CLASS_ENCODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/enc_webcodec.c -> gpac-26.02.0.tar.gz/src/filters/enc_webcodec.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / WebCodec encoder filter @@ -27,7 +27,7 @@ #include <gpac/internal/media_dev.h> #include <gpac/constants.h> -#if defined(GPAC_CONFIG_EMSCRIPTEN) +#ifndef GPAC_DISABLE_WEBCODEC typedef struct { @@ -55,6 +55,8 @@ char *pname; } GF_WCEncCtx; +#if defined(GPAC_CONFIG_EMSCRIPTEN) + GF_EXPORT void wcenc_on_error(GF_WCEncCtx *ctx, int state, char *msg) { @@ -70,7 +72,7 @@ } EM_JS(int, wcenc_init, (int wc_ctx, int _codec_str, int bitrate, int width, int height, double FPS, int realtime, int sample_rate, int num_channels), { - let codec_str = _codec_str ? libgpac.UTF8ToString(_codec_str) : null; + let codec_str = _codec_str ? UTF8ToString(_codec_str) : null; let config = {}; config.codec = codec_str; let enc_class = null; @@ -96,15 +98,15 @@ if (typeof libgpac._to_webenc != 'function') { libgpac._web_encs = ; libgpac._to_webenc = (ctx) => { - for (let i=0; i<libgpac._web_encs.length; i++) { - if (libgpac._web_encsi._wc_ctx==ctx) return libgpac._web_encsi; - } - return null; + for (let i=0; i<libgpac._web_encs.length; i++) { + if (libgpac._web_encsi._wc_ctx==ctx) return libgpac._web_encsi; + } + return null; }; - libgpac._on_wcenc_error = libgpac.cwrap('wcenc_on_error', null, 'number', 'number', 'string'); - libgpac._on_wcenc_config = libgpac.cwrap('wcenc_on_config', null, 'number', 'number'); - libgpac._on_wcenc_frame = libgpac.cwrap('wcenc_on_frame', null, 'number', 'bigint', 'number', 'number', 'number'); - libgpac._on_wcenc_flush = libgpac.cwrap('wcenc_on_flush', null, 'number'); + libgpac._on_wcenc_error = cwrap('wcenc_on_error', null, 'number', 'number', 'string'); + libgpac._on_wcenc_config = cwrap('wcenc_on_config', null, 'number', 'number'); + libgpac._on_wcenc_frame = cwrap('wcenc_on_frame', null, 'number', 'bigint', 'number', 'number', 'number'); + libgpac._on_wcenc_flush = cwrap('wcenc_on_flush', null, 'number'); } enc_class.isConfigSupported(config).then( supported => { if (supported.supported) { @@ -151,7 +153,7 @@ const GF_PropertyValue *p; u32 streamtype; char *codec_par=NULL; - GF_WCEncCtx *ctx = gf_filter_get_udta(filter); + GF_WCEncCtx *ctx = gf_filter_get_udta(filter); if (is_remove) { if (ctx->opid) { @@ -370,10 +372,10 @@ EM_JS(int, wcenc_encode_frame, (int wc_ctx, u32 w, u32 h, u32 uv_h, int _format, u64 ts, u32 dur, u32 planes, u32 stride1, u32 stride2, u32 sap, int buf, u32 buf_size), { let c = libgpac._to_webenc(wc_ctx); if (!c || !buf || !_format) return; - let format = libgpac.UTF8ToString(_format); + let format = UTF8ToString(_format); //setup source frame - let ab = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let ab = new Uint8Array(HEAPU8.buffer, buf, buf_size); let vbinit = { format: format, layout: , @@ -405,7 +407,7 @@ EM_JS(int, wcenc_encode_audio, (int wc_ctx, u32 sr, u32 ch, u32 frames, int _format, u64 ts, int buf, u32 buf_size), { let c = libgpac._to_webenc(wc_ctx); if (!c || !buf || !_format) return; - let format = libgpac.UTF8ToString(_format); + let format = UTF8ToString(_format); //setup source frame let adinit = { @@ -414,7 +416,7 @@ numberOfChannels: ch, numberOfFrames: frames, timestamp: Number(ts), - data: new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size) + data: new Uint8Array(HEAPU8.buffer, buf, buf_size) }; let adata = new AudioData(adinit); c.enc.encode(adata); @@ -543,7 +545,7 @@ return; } //setup dst - let dst = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let dst = new Uint8Array(HEAPU8.buffer, buf, buf_size); dst.set(c.decoderConfig); }) @@ -569,7 +571,7 @@ return; } //setup dst - let dst = new Uint8Array(libgpac.HEAPU8.buffer, buf, buf_size); + let dst = new Uint8Array(HEAPU8.buffer, buf, buf_size); c.chunk.copyTo(dst); }) @@ -623,10 +625,10 @@ GF_Err wcenc_initialize(GF_Filter *filter) { - GF_WCEncCtx *ctx = gf_filter_get_udta(filter); - ctx->filter = filter; + GF_WCEncCtx *ctx = gf_filter_get_udta(filter); + ctx->filter = filter; ctx->src_pcks = gf_list_new(); - return GF_OK; + return GF_OK; } EM_JS(int, wcenc_del, (int wc_ctx), { @@ -643,10 +645,10 @@ void wcenc_finalize(GF_Filter *filter) { - GF_WCEncCtx *ctx = gf_filter_get_udta(filter); - wcenc_del(EM_CAST_PTR ctx); + GF_WCEncCtx *ctx = gf_filter_get_udta(filter); + wcenc_del(EM_CAST_PTR ctx); - while (gf_list_count(ctx->src_pcks)) { + while (gf_list_count(ctx->src_pcks)) { GF_FilterPacket *pck = gf_list_pop_back(ctx->src_pcks); gf_filter_pck_unref(pck); } @@ -669,6 +671,17 @@ CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), }; +#else + +static GF_Err wcenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + return GF_NOT_SUPPORTED; +} +static GF_Err wcenc_process(GF_Filter *filter) +{ + return GF_NOT_SUPPORTED; +} +#endif static GF_FilterCapability WCEncCapsAV = { @@ -704,17 +717,22 @@ .args = WCEncArgs, SETCAPS(WCEncCapsAV), .flags = GF_FS_REG_SINGLE_THREAD|GF_FS_REG_ASYNC_BLOCK, +#if defined(GPAC_CONFIG_EMSCRIPTEN) .private_size = sizeof(GF_WCEncCtx), .initialize = wcenc_initialize, .finalize = wcenc_finalize, +#endif .configure_pid = wcenc_configure_pid, .process = wcenc_process, + .hint_class_type = GF_FS_CLASS_ENCODER }; +#endif //GPAC_DISABLE_WEBCODEC const GF_FilterRegister *wcenc_register(GF_FilterSession *session) { - +#ifndef GPAC_DISABLE_WEBCODEC +#if defined(GPAC_CONFIG_EMSCRIPTEN) int has_webv_encode = EM_ASM_INT({ if (typeof VideoEncoder == 'undefined') return 0; return 1; @@ -736,6 +754,13 @@ GF_WCEncCtxRegister.nb_caps = sizeof(WCEncCapsV)/sizeof(GF_FilterCapability); } GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("WebEnc AudioEncoder %d - VideoEncoder %d\n", has_weba_encode, has_webv_encode)); +#else + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + GF_WCEncCtxRegister.version = "! Warning: WebCodec NOT AVAILABLE IN THIS BUILD !"; +#endif return &GF_WCEncCtxRegister; -} +#else + return NULL; #endif +}
View file
gpac-2.4.0.tar.gz/src/filters/encrypt_cenc_isma.c -> gpac-26.02.0.tar.gz/src/filters/encrypt_cenc_isma.c
Changed
@@ -49,6 +49,7 @@ CENC_FULL_SAMPLE=1, /*below types may have several ranges (clear/encrypted) per sample*/ + CENC_AC4, /*Dolby, ac4 subsample encryption */ CENC_AVC, /*AVC, nalu-based*/ CENC_HEVC, /*HEVC, nalu-based*/ CENC_AV1, /*AV1, OBU-based*/ @@ -66,6 +67,11 @@ typedef struct { + u32 clear, encrypted; +} OBURange; + +typedef struct +{ Bool passthrough; GF_CryptInfo *cinfo; @@ -110,14 +116,19 @@ Bool rap_roll, warned_clear; #ifndef GPAC_DISABLE_AV_PARSERS + AC4State *ac4_state; AVCState *avc_state; HEVCState *hevc_state; AV1State *av1_state; + OBURange *av1_vpx_ranges; //AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS; + GF_VPConfig *vp9_cfg; + u32 *vpx_frame_sizes; //VP9_MAX_FRAMES_IN_SUPERFRAME; VVCState *vvc_state; #endif Bool slice_header_clear; + GF_CryptNonVCL non_vcl_encrypted; GF_PropUIntList mkey_indices; @@ -132,8 +143,8 @@ { //options const char *cfile; - Bool allc, bk_stats; - + Bool allc, bk_stats, bk_skip; + //internal GF_CryptInfo *cinfo; @@ -167,7 +178,14 @@ case GF_CODECID_HEVC: case GF_CODECID_LHVC: if (p) { - GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, (cstr->codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + GF_HEVCConfig *hvcc; + if ((cstr->codec_id==GF_CODECID_LHVC) + && !gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT)) + { + hvcc = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, GF_TRUE); + } else { + hvcc = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, GF_FALSE); + } cstr->nalu_size_length = hvcc ? hvcc->nal_unit_size : 0; if (hvcc) gf_odf_hevc_cfg_del(hvcc); } @@ -467,17 +485,17 @@ cypherMode = 1; } else if (!strcmp(att->name, "cypherKey")) { e = gf_bin128_parse(att->value, cypherKey); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("CENC Cannnot parse cypherKey\n")); - break; - } + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("CENC Cannnot parse cypherKey\n")); + break; + } has_key = GF_TRUE; } else if (!strcmp(att->name, "cypherIV")) { e = gf_bin128_parse(att->value, cypherIV); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("CENC Cannnot parse cypherIV\n")); - break; - } + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("CENC Cannnot parse cypherIV\n")); + break; + } has_IV = GF_TRUE; } else if (!strcmp(att->name, "cypherOffset")) { cypherOffset = atoi(att->value); @@ -616,6 +634,10 @@ gf_free(cstr->av1_state); cstr->av1_state = NULL; } + if (cstr->av1_vpx_ranges) { + gf_free(cstr->av1_vpx_ranges); + cstr->av1_vpx_ranges = NULL; + } if (cstr->avc_state) { gf_free(cstr->avc_state); cstr->avc_state = NULL; @@ -632,6 +654,15 @@ gf_odf_vp_cfg_del(cstr->vp9_cfg); cstr->vp9_cfg = NULL; } + if (cstr->vpx_frame_sizes) { + gf_free(cstr->vpx_frame_sizes); + cstr->vpx_frame_sizes = NULL; + } + if (cstr->ac4_state) { + if (cstr->ac4_state->config) gf_odf_ac4_cfg_del(cstr->ac4_state->config); + gf_free(cstr->ac4_state); + cstr->ac4_state = NULL; + } #endif } @@ -643,10 +674,11 @@ GF_AVCConfig *avccfg; GF_HEVCConfig *hevccfg; GF_VVCConfig *vvccfg; - const GF_PropertyValue *p; + const GF_PropertyValue *p, *dsi_enh=NULL; p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG); - if (!p) p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + dsi_enh = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + if (!p) p = dsi_enh; if (p) { dsi_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); @@ -660,6 +692,7 @@ if (cstr->is_saes) { cstr->crypt_byte_block = 1; cstr->skip_byte_block = 9; + cstr->non_vcl_encrypted = GF_CRYPT_NONVCL_CLEAR_NONE; //cypher all NALUs } @@ -697,6 +730,9 @@ case GF_CODECID_AAC_MPEG2_SSRP: allow_saes=GF_TRUE; break; + case GF_CODECID_AC4: + cenc_codec = CENC_AC4; + break; } if (cstr->is_saes && !allow_saes) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("CENCCrypt HLS Sample-AES not supported for codec %s\n", gf_codecid_name(cstr->codec_id) )); @@ -723,6 +759,20 @@ case CENC_AV1: GF_SAFEALLOC(cstr->av1_state, AV1State); if (!cstr->av1_state) return GF_OUT_OF_MEM; + if (!cstr->av1_vpx_ranges) cstr->av1_vpx_ranges = gf_malloc(sizeof(OBURange) * AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS); + if (!cstr->av1_vpx_ranges) return GF_OUT_OF_MEM; + break; + case CENC_VPX: + if (!cstr->vpx_frame_sizes) cstr->vpx_frame_sizes = gf_malloc(sizeof(u32) * VP9_MAX_FRAMES_IN_SUPERFRAME); + if (!cstr->vpx_frame_sizes) return GF_OUT_OF_MEM; + if (!cstr->av1_vpx_ranges) cstr->av1_vpx_ranges = gf_malloc(sizeof(OBURange) * AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS); + if (!cstr->av1_vpx_ranges) return GF_OUT_OF_MEM; + break; + case CENC_AC4: + GF_SAFEALLOC(cstr->ac4_state, AC4State); + if (!cstr->ac4_state) return GF_OUT_OF_MEM; + GF_SAFEALLOC(cstr->ac4_state->config, GF_AC4Config); + if (!cstr->ac4_state->config) return GF_OUT_OF_MEM; break; #endif } @@ -752,6 +802,15 @@ } else { cstr->slice_header_clear = GF_TRUE; } + + cstr->non_vcl_encrypted = cstr->tci->allow_encrypted_nonVCLs; + if (cstr->slice_header_clear && cstr->non_vcl_encrypted!=GF_CRYPT_NONVCL_CLEAR_ALL) { + if (cstr->non_vcl_encrypted!=GF_CRYPT_NONVCL_CLEAR_SEI_AUD) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("CENCCrypt Slice Header is clear: forcing all non-VCL NALUs in clear too\n") ); + } + cstr->non_vcl_encrypted = GF_CRYPT_NONVCL_CLEAR_ALL; + } + #ifndef GPAC_DISABLE_AV_PARSERS if (avccfg) { for (i=0; i<gf_list_count(avccfg->sequenceParameterSets); i++) { @@ -762,7 +821,19 @@ GF_NALUFFParam *slc = gf_list_get(avccfg->pictureParameterSets, i); gf_avc_read_pps(slc->data, slc->size, cstr->avc_state); } - + gf_odf_avc_cfg_del(avccfg); + } + //load scalable param sets if any + avccfg = dsi_enh ? gf_odf_avc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size) : NULL; + if (avccfg) { + for (i=0; i<gf_list_count(avccfg->sequenceParameterSets); i++) { + GF_NALUFFParam *slc = gf_list_get(avccfg->sequenceParameterSets, i); + gf_avc_read_sps(slc->data, slc->size, cstr->avc_state, 0, NULL); + } + for (i=0; i<gf_list_count(avccfg->pictureParameterSets); i++) { + GF_NALUFFParam *slc = gf_list_get(avccfg->pictureParameterSets, i); + gf_avc_read_pps(slc->data, slc->size, cstr->avc_state); + } gf_odf_avc_cfg_del(avccfg); } #else @@ -782,13 +853,25 @@ if (!p) return GF_OK; - hevccfg = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, (cstr->codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + if ((cstr->codec_id==GF_CODECID_LHVC) && !dsi_enh) { + hevccfg = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, GF_TRUE); + } else { + hevccfg = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, GF_FALSE); + } if (hevccfg) cstr->nalu_size_length = hevccfg->nal_unit_size; #if !defined(GPAC_DISABLE_AV_PARSERS) gf_hevc_parse_ps(hevccfg, cstr->hevc_state, GF_HEVC_NALU_VID_PARAM); gf_hevc_parse_ps(hevccfg, cstr->hevc_state, GF_HEVC_NALU_SEQ_PARAM); gf_hevc_parse_ps(hevccfg, cstr->hevc_state, GF_HEVC_NALU_PIC_PARAM); + //load scalable param sets if any + if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); + hevccfg = dsi_enh ? gf_odf_hevc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size, GF_TRUE) : NULL; + if (hevccfg) { + gf_hevc_parse_ps(hevccfg, cstr->hevc_state, GF_HEVC_NALU_VID_PARAM); + gf_hevc_parse_ps(hevccfg, cstr->hevc_state, GF_HEVC_NALU_SEQ_PARAM); + gf_hevc_parse_ps(hevccfg, cstr->hevc_state, GF_HEVC_NALU_PIC_PARAM); + } #endif //mandatory for HEVC @@ -834,6 +917,14 @@ gf_vvc_parse_ps(vvccfg, cstr->vvc_state, GF_VVC_NALU_VID_PARAM); gf_vvc_parse_ps(vvccfg, cstr->vvc_state, GF_VVC_NALU_SEQ_PARAM); gf_vvc_parse_ps(vvccfg, cstr->vvc_state, GF_VVC_NALU_PIC_PARAM); + //load scalable param sets if any + if (vvccfg) gf_odf_vvc_cfg_del(vvccfg); + vvccfg = dsi_enh ? gf_odf_vvc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size) : NULL; + if (vvccfg) { + gf_vvc_parse_ps(vvccfg, cstr->vvc_state, GF_VVC_NALU_VID_PARAM); + gf_vvc_parse_ps(vvccfg, cstr->vvc_state, GF_VVC_NALU_SEQ_PARAM); + gf_vvc_parse_ps(vvccfg, cstr->vvc_state, GF_VVC_NALU_PIC_PARAM); + } #endif //mandatory for VVC @@ -847,7 +938,11 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("CENCCrypt Missing NALU length size, assuming 4\n") ); } break; - + case CENC_AC4: + if(!p) + return GF_OK; + gf_odf_ac4_cfg_parse(p->value.data.ptr, p->value.data.size, cstr->ac4_state->config); + break; default: break; } @@ -878,7 +973,7 @@ const GF_PropertyValue *prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_STREAM_TYPE); if (prop && prop->value.uint != GF_STREAM_VISUAL) { if (cstr->skip_byte_block) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("\nCENC Using cbcs pattern mode on-video track is disabled in GPAC, using whole-block full encryption\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("\nCENC Using cbcs pattern mode on non-video track is disabled in GPAC, using whole-block full encryption\n")); cstr->skip_byte_block = 0; } } @@ -1263,7 +1358,7 @@ } gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_STSD_MODE, &PROP_UINT(tci->force_clear_stsd_idx) ); } - + if (cstr->passthrough) return GF_OK; gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_ENCRYPTED) ); @@ -1397,7 +1492,7 @@ memcpy(output+isma_hdr_size, data, sizeof(char)*size); gf_filter_pck_merge_properties(pck, dst_pck); - + /*isma e&a stores AVC1 in AVC/H264 annex B bitstream fashion, with 0x00000001 start codes*/ if (cstr->nalu_size_length) { u32 done = 0; @@ -1436,7 +1531,9 @@ gf_crypt_decrypt(cstr->keys0.crypt, dummy, remain); } } - gf_crypt_encrypt(cstr->keys0.crypt, output+isma_hdr_size, size); + if (!ctx->bk_skip) + gf_crypt_encrypt(cstr->keys0.crypt, output+isma_hdr_size, size); + cstr->prev_pck_encrypted = GF_TRUE; cstr->num_block_crypted += size/16; } else { @@ -1524,7 +1621,9 @@ memset(output+adobe_hdr_size+size, padding_bytes, sizeof(char)*padding_bytes); - gf_crypt_encrypt(cstr->keys0.crypt, output+adobe_hdr_size, len); + if (!ctx->bk_skip) + gf_crypt_encrypt(cstr->keys0.crypt, output+adobe_hdr_size, len); + cstr->num_block_crypted += len/16; /*write encrypted AU header*/ @@ -1598,31 +1697,43 @@ } #ifndef GPAC_DISABLE_AV_PARSERS +// 0: VCL, 1: SEI+AUD, 2: other non-VCL +static int avc_is_vcl_or_sei(u8 nal_type) +{ + switch (nal_type) { + case GF_AVC_NALU_NON_IDR_SLICE: + case GF_AVC_NALU_DP_A_SLICE: + case GF_AVC_NALU_DP_B_SLICE: + case GF_AVC_NALU_DP_C_SLICE: + case GF_AVC_NALU_IDR_SLICE: + case GF_AVC_NALU_SLICE_AUX: + case GF_AVC_NALU_SVC_SLICE: + return 0; + case GF_AVC_NALU_SEI: + case GF_AVC_NALU_ACCESS_UNIT: + return 1; + default: + return 2; + } +} + //parses slice header and returns its size static u32 cenc_get_clear_bytes(GF_CENCStream *cstr, GF_BitStream *plaintext_bs, char *samp_data, u32 nal_size, u32 bytes_in_nalhr) { u32 clear_bytes = 0; + u32 nal_start = (u32) gf_bs_get_position(plaintext_bs); if (cstr->slice_header_clear) { - u32 nal_start = (u32) gf_bs_get_position(plaintext_bs); if (cstr->cenc_codec==CENC_AVC) { u32 ntype; + gf_assert(cstr->is_saes || cstr->non_vcl_encrypted == GF_CRYPT_NONVCL_CLEAR_ALL); gf_avc_parse_nalu(plaintext_bs, cstr->avc_state); ntype = cstr->avc_state->last_nal_type_parsed; - switch (ntype) { - case GF_AVC_NALU_NON_IDR_SLICE: - case GF_AVC_NALU_DP_A_SLICE: - case GF_AVC_NALU_DP_B_SLICE: - case GF_AVC_NALU_DP_C_SLICE: - case GF_AVC_NALU_IDR_SLICE: - case GF_AVC_NALU_SLICE_AUX: - case GF_AVC_NALU_SVC_SLICE: + if (avc_is_vcl_or_sei(ntype) == 0) { gf_bs_align(plaintext_bs); clear_bytes = (u32) gf_bs_get_position(plaintext_bs) - nal_start; - break; - default: + } else { clear_bytes = nal_size; - break; } if (cstr->is_saes) { if ((ntype == GF_AVC_NALU_NON_IDR_SLICE) || (ntype == GF_AVC_NALU_IDR_SLICE)) { @@ -1631,35 +1742,56 @@ clear_bytes = nal_size; } } - + goto exit; //we're done } else if (cstr->cenc_codec==CENC_HEVC) { u8 ntype, ntid, nlid; cstr->hevc_state->full_slice_header_parse = GF_TRUE; -// gf_hevc_parse_nalu(samp_data + nal_start, nal_size, cstr->hevc_state, &ntype, &ntid, &nlid); gf_hevc_parse_nalu_bs(plaintext_bs, cstr->hevc_state, &ntype, &ntid, &nlid); if (ntype<=GF_HEVC_NALU_SLICE_CRA) { clear_bytes = cstr->hevc_state->s_info.payload_start_offset - nal_start; } else { clear_bytes = nal_size; } + goto exit; } else if (cstr->cenc_codec==CENC_VVC) { u8 ntype, ntid, nlid; cstr->vvc_state->parse_mode = 1; -// gf_vvc_parse_nalu(samp_data + nal_start, nal_size, cstr->vvc_state, &ntype, &ntid, &nlid); gf_vvc_parse_nalu_bs(plaintext_bs, cstr->vvc_state, &ntype, &ntid, &nlid); if (ntype <= GF_VVC_NALU_SLICE_GDR) { clear_bytes = cstr->vvc_state->s_info.payload_start_offset - nal_start; } else { clear_bytes = nal_size; } + goto exit; } - //reset EPB removal and seek to start of nal - gf_bs_enable_emulation_byte_removal(plaintext_bs, GF_FALSE); + + //seek to start of nal gf_bs_seek(plaintext_bs, nal_start); - } else { - clear_bytes = bytes_in_nalhr; } + + // avc1 CTR CENC edition 1: choose which non-VCL are encrypted + if (cstr->cenc_codec==CENC_AVC) { + const u32 nal_type = gf_bs_peek_bits(plaintext_bs, 8, 0) & 0x1F; + if (cstr->non_vcl_encrypted == GF_CRYPT_NONVCL_CLEAR_ALL) { + if (avc_is_vcl_or_sei(nal_type) != 0) { + clear_bytes = nal_size; + goto exit; + } + } else if (cstr->non_vcl_encrypted == GF_CRYPT_NONVCL_CLEAR_SEI_AUD) { + if (avc_is_vcl_or_sei(nal_type) == 1) { + clear_bytes = nal_size; + goto exit; + } + } + } + + //default + clear_bytes = bytes_in_nalhr; + +exit: + //reset EPB removal and seek to start of nal gf_bs_enable_emulation_byte_removal(plaintext_bs, GF_FALSE); + gf_bs_seek(plaintext_bs, nal_start); return clear_bytes; } #endif @@ -1714,7 +1846,6 @@ } if (!dst_pck) return GF_OUT_OF_MEM; - gf_filter_pck_merge_properties(pck, dst_pck); gf_filter_pck_set_crypt_flags(dst_pck, GF_FILTER_PCK_CRYPT); if (!ctx->bs_r) ctx->bs_r = gf_bs_new(data, pck_size, GF_BITSTREAM_READ); @@ -1744,10 +1875,6 @@ #ifndef GPAC_DISABLE_AV_PARSERS ObuType obut = 0; u32 num_frames_in_superframe = 0, superframe_index_size = 0; - u32 frame_sizesVP9_MAX_FRAMES_IN_SUPERFRAME; - struct { - int clear, encrypted; - } rangesAV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS; u64 obu_size = 0; u32 hdr_size = 0; #else @@ -1793,14 +1920,14 @@ } else { nb_ranges = cstr->av1_state->frame_state.nb_tiles_in_obu; - ranges0.clear = cstr->av1_state->frame_state.tiles0.obu_start_offset; - ranges0.encrypted = cstr->av1_state->frame_state.tiles0.size; + cstr->av1_vpx_ranges0.clear = cstr->av1_state->frame_state.tiles0.obu_start_offset; + cstr->av1_vpx_ranges0.encrypted = cstr->av1_state->frame_state.tiles0.size; for (i = 1; i < nb_ranges; ++i) { - rangesi.clear = cstr->av1_state->frame_state.tilesi.obu_start_offset - (cstr->av1_state->frame_state.tilesi - 1.obu_start_offset + cstr->av1_state->frame_state.tilesi - 1.size); - rangesi.encrypted = cstr->av1_state->frame_state.tilesi.size; + cstr->av1_vpx_rangesi.clear = cstr->av1_state->frame_state.tilesi.obu_start_offset - (cstr->av1_state->frame_state.tilesi - 1.obu_start_offset + cstr->av1_state->frame_state.tilesi - 1.size); + cstr->av1_vpx_rangesi.encrypted = cstr->av1_state->frame_state.tilesi.size; } - clear_bytes = ranges0.clear; - nalu_size = clear_bytes + ranges0.encrypted; + clear_bytes = cstr->av1_vpx_ranges0.clear; + nalu_size = clear_bytes + cstr->av1_vpx_ranges0.encrypted; /* A subsample SHALL be created for each tile even if less than 16 bytes see https://github.com/AOMediaCodec/av1-isobmff/pull/116#discussion_r340176740 @@ -1834,7 +1961,7 @@ } pos = gf_bs_get_position(ctx->bs_r); - e = gf_vp9_parse_superframe(ctx->bs_r, pck_size, &num_frames_in_superframe, frame_sizes, &superframe_index_size); + e = gf_vp9_parse_superframe(ctx->bs_r, pck_size, &num_frames_in_superframe, cstr->vpx_frame_sizes, &superframe_index_size); if (e) return e; gf_bs_seek(ctx->bs_r, pos); @@ -1850,10 +1977,10 @@ return e; } - rangesi.clear = (int)(gf_bs_get_position(ctx->bs_r) - pos2); - rangesi.encrypted = frame_sizesi - rangesi.clear; + cstr->av1_vpx_rangesi.clear = (int)(gf_bs_get_position(ctx->bs_r) - pos2); + cstr->av1_vpx_rangesi.encrypted = cstr->vpx_frame_sizesi - cstr->av1_vpx_rangesi.clear; - gf_bs_seek(ctx->bs_r, pos2 + frame_sizesi); + gf_bs_seek(ctx->bs_r, pos2 + cstr->vpx_frame_sizesi); } if (gf_bs_get_position(ctx->bs_r) + superframe_index_size != pos + pck_size) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("CENC Inconsistent VP9 size %u (parsed "LLU") at DTS "LLU". Re-import raw VP9/IVF for more details.\n", @@ -1861,19 +1988,19 @@ } gf_bs_seek(ctx->bs_r, pos); - clear_bytes = ranges0.clear; - gf_assert(frame_sizes0 == ranges0.clear + ranges0.encrypted); - nalu_size = frame_sizes0; + clear_bytes = cstr->av1_vpx_ranges0.clear; + gf_assert(cstr->vpx_frame_sizes0 == cstr->av1_vpx_ranges0.clear + cstr->av1_vpx_ranges0.encrypted); + nalu_size = cstr->vpx_frame_sizes0; //final superframe index must be in clear if (superframe_index_size > 0) { - rangesnb_ranges.clear = superframe_index_size; - rangesnb_ranges.encrypted = 0; + cstr->av1_vpx_rangesnb_ranges.clear = superframe_index_size; + cstr->av1_vpx_rangesnb_ranges.encrypted = 0; nb_ranges++; } //not clearly defined in the spec (so we do the same as in AV1 which is more clearly defined): - if (frame_sizes0 - clear_bytes >= 16) { + if (cstr->vpx_frame_sizes0 - clear_bytes >= 16) { //A subsample SHALL be created for each tile >= 16 bytes. If previous range had encrypted bytes, create a new one, otherwise merge in prev if (prev_entry_bytes_crypt) { if (!nb_subsamples) gf_bs_write_int(sai_bs, 0, nb_subsamples_bits); @@ -1892,6 +2019,15 @@ clear_bytes = nalu_size; } break; + case CENC_AC4: + pos = gf_bs_get_position(ctx->bs_r); + if (!gf_ac4_parser_bs(ctx->bs_r, cstr->ac4_state->config, GF_TRUE, GF_TRUE)) + return GF_NON_COMPLIANT_BITSTREAM; + gf_bs_seek(ctx->bs_r, pos); + + nalu_size = pck_size; + clear_bytes = cstr->ac4_state->config->toc_size; + break; default: //used by cbcs clear_bytes = 0; @@ -2025,7 +2161,11 @@ while (res) { u32 to_crypt = (res >= (u32) (16*cstr->crypt_byte_block)) ? 16*cstr->crypt_byte_block : res; - e = gf_crypt_encrypt(cstr->keyskey_idx.crypt, output+pos, to_crypt); + if (ctx->bk_skip) + e = GF_OK; + else + e = gf_crypt_encrypt(cstr->keyskey_idx.crypt, output+pos, to_crypt); + cstr->num_block_crypted += to_crypt/16; if (res >= (u32) (16 * (cstr->crypt_byte_block + cstr->skip_byte_block))) { @@ -2041,7 +2181,10 @@ //clear_bytes_at_end is 0 unless NALU-based cbcs without pattern (not defined in CENC) //in this case, we must only encrypt a multiple of 16-byte blocks u32 to_crypt = nalu_size - clear_bytes - clear_bytes_at_end; - e = gf_crypt_encrypt(cstr->keyskey_idx.crypt, output+cur_pos, to_crypt); + if (ctx->bk_skip) + e = GF_OK; + else + e = gf_crypt_encrypt(cstr->keyskey_idx.crypt, output+cur_pos, to_crypt); cstr->num_block_crypted += to_crypt/16; } } @@ -2113,16 +2256,16 @@ #endif switch (cstr->cenc_codec) { case CENC_AV1: - clear_bytes = rangesrange_idx.clear; - nalu_size = clear_bytes + rangesrange_idx.encrypted; + clear_bytes = cstr->av1_vpx_rangesrange_idx.clear; + nalu_size = clear_bytes + cstr->av1_vpx_rangesrange_idx.encrypted; break; case CENC_VPX: if (nb_ranges > 1) { - clear_bytes = rangesrange_idx.clear; - nalu_size = clear_bytes + rangesrange_idx.encrypted; + clear_bytes = cstr->av1_vpx_rangesrange_idx.clear; + nalu_size = clear_bytes + cstr->av1_vpx_rangesrange_idx.encrypted; } else { /*last*/ - nalu_size = clear_bytes = rangesrange_idx.clear; - gf_assert(rangesrange_idx.encrypted == 0); + nalu_size = clear_bytes = cstr->av1_vpx_rangesrange_idx.clear; + gf_assert(cstr->av1_vpx_rangesrange_idx.encrypted == 0); } break; default: @@ -2135,7 +2278,10 @@ //CTR full sample else if (cstr->ctr_mode) { gf_bs_skip_bytes(ctx->bs_r, pck_size); - e = gf_crypt_encrypt(cstr->keys0.crypt, output, pck_size); + if (ctx->bk_skip) + e = GF_OK; + else + e = gf_crypt_encrypt(cstr->keys0.crypt, output, pck_size); cstr->num_block_crypted += pck_size/16; } //CBC full sample with padding @@ -2153,7 +2299,10 @@ if (pck_size >= 16) { u32 to_crypt = pck_size - clear_header - clear_trailing; - gf_crypt_encrypt(cstr->keys0.crypt, output+clear_header, to_crypt); + if (ctx->bk_skip) + e = GF_OK; + else + e = gf_crypt_encrypt(cstr->keys0.crypt, output+clear_header, to_crypt); cstr->num_block_crypted += to_crypt/16; } gf_bs_skip_bytes(ctx->bs_r, pck_size); @@ -2164,7 +2313,7 @@ return e; } } - + if (prev_entry_bytes_clear || prev_entry_bytes_crypt) { if (!nb_subsamples) gf_bs_write_int(sai_bs, 0, nb_subsamples_bits); nb_subsamples++; @@ -2389,7 +2538,7 @@ u32 pssh_len; gf_bs_get_content(bs, &pssh, &pssh_len); gf_bs_del(bs); - gf_filter_pck_set_property(dst_pck, GF_PROP_PID_CENC_PSSH, &PROP_DATA_NO_COPY(pssh, pssh_len) ); + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_CENC_PSSH, &PROP_DATA_NO_COPY(pssh, pssh_len) ); } } @@ -2449,7 +2598,7 @@ GF_FilterPacket *dst_pck; dst_pck = gf_filter_pck_new_ref(cstr->opid, 0, 0, pck); if (!dst_pck) return GF_OUT_OF_MEM; - + gf_filter_pck_merge_properties(pck, dst_pck); if (force_clear && !cstr->tci->force_clear_stsd_idx) @@ -2544,7 +2693,7 @@ new_idx = (cstr->nb_segments / cstr->tci->keyRoll) % nb_keys; } } else if (cstr->tci->roll_type == GF_KEYROLL_PERIODS) { - const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PID_DASH_PERIOD_START); + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_DASH_PERIOD_START); if (p) { cstr->nb_periods++; new_idx = (cstr->nb_periods / cstr->tci->keyRoll) % nb_keys; @@ -2696,6 +2845,7 @@ { OFFS(cfile), "crypt file location", GF_PROP_STRING, NULL, NULL, 0}, { OFFS(allc), "throw error if no DRM config file is found for a PID", GF_PROP_BOOL, NULL, NULL, 0}, { OFFS(bk_stats), "print number of encrypted blocks to stdout upon exit", GF_PROP_BOOL, NULL, NULL, 0}, + { OFFS(bk_skip), "skip encryption but performs all other tasks (test mode)", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -2703,7 +2853,7 @@ .name = "cecrypt", GF_FS_SET_DESCRIPTION("CENC encryptor") GF_FS_SET_HELP("The CENC encryptor supports CENC, ISMA and Adobe encryption. It uses a DRM config file for declaring keys.\n" - "The syntax is available at https://wiki.gpac.io/Common-Encryption\n" + "The syntax is available at https://wiki.gpac.io/xmlformats/Common-Encryption\n" "The DRM config file can be set per PID using the property `CryptInfo`, or set at the filter level using -cfile().\n" "When the DRM config file is set per PID, the first `CrypTrack` in the DRM config file with the same ID is used, otherwise the first `CrypTrack` is used (regardless of the `CrypTrack` ID).\n" "When the DRM config file is set globally (not per PID), the first `CrypTrack` in the DRM config file with the same ID is used, otherwise the first `CrypTrack` with ID 0 or not set is used.\n" @@ -2718,8 +2868,8 @@ .configure_pid = cenc_enc_configure_pid, .initialize = cenc_enc_initialize, .finalize = cenc_enc_finalize, - .process = cenc_enc_process - + .process = cenc_enc_process, + .hint_class_type = GF_FS_CLASS_CRYPTO }; #endif /*GPAC_DISABLE_CRYPTO*/
View file
gpac-2.4.0.tar.gz/src/filters/evg_rescale.c -> gpac-26.02.0.tar.gz/src/filters/evg_rescale.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2026 * All rights reserved * * This file is part of GPAC / EVG rescaler filter @@ -28,12 +28,11 @@ #ifndef GPAC_DISABLE_EVG #include <gpac/evg.h> -enum -{ +GF_OPT_ENUM (EVGScaleAspectRatioMode, EVGS_KEEPAR_OFF=0, EVGS_KEEPAR_FULL, EVGS_KEEPAR_NOSRC, -}; +); typedef struct { @@ -42,7 +41,7 @@ u32 ofmt, nbth; Bool ofr, hq; char *padclr; - u32 keepar; + EVGScaleAspectRatioMode keepar; GF_Fraction osar; //internal data @@ -233,7 +232,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); if (p) ofmt = p->value.uint; p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p) sar = p->value.frac; + if (p && (p->value.frac.num>0)) sar = p->value.frac; else sar.den = sar.num = 1; p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_RANGE); @@ -454,7 +453,11 @@ if (p) ctx->osize.y = p->value.uint; p = gf_filter_pid_caps_query(pid, GF_PROP_PID_PIXFMT); - if (p) ctx->ofmt = p->value.uint; + if (p && (ctx->ofmt != p->value.uint)) { + //reset input stride to force reconfig of output + ctx->i_stride = ctx->i_stride_uv = 0; + ctx->ofmt = p->value.uint; + } return evgs_configure_pid(filter, ctx->ipid, GF_FALSE); } @@ -481,7 +484,11 @@ static const GF_FilterCapability EVGSCaps = { CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), - CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW) + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_WIDTH, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_HEIGHT, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_PIXFMT, 0), }; @@ -492,21 +499,21 @@ "## Output size assignment\n" "If -osize() is {0,0}, the output dimensions will be set to the input size, and input aspect ratio will be ignored.\n" "\n" - "If -osize() is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If -keepar=nosrc(), input sample aspect ratio is ignored.\n" + "If -osize() is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If -keepar() = `nosrc`, input sample aspect ratio is ignored.\n" "## Aspect Ratio and Sample Aspect Ratio\n" "When output sample aspect ratio is set, the output dimensions are divided by the output sample aspect ratio.\n" "EX evgs:osize=288x240:osar=3/2\n" "The output dimensions will be 192x240.\n" "\n" - "When aspect ratio is not kept (-keepar=off()):\n" + "When aspect ratio is not kept (-keepar() = `off`):\n" "- source is resampled to desired dimensions\n" "- if output aspect ratio is not set, output will use source sample aspect ratio\n" "\n" - "When aspect ratio is partially kept (-keepar=nosrc()):\n" + "When aspect ratio is partially kept (-keepar() = `nosrc`):\n" "- resampling is done on the input data without taking input sample aspect ratio into account\n" - "- if output sample aspect ratio is not set (-osar=0/N()), source aspect ratio is forwarded to output.\n" + "- if output sample aspect ratio is not set (-osar() = `0/N`), source aspect ratio is forwarded to output.\n" "\n" - "When aspect ratio is fully kept (-keepar=full()), output aspect ratio is force to 1/1 if not set.\n" + "When aspect ratio is fully kept (-keepar() = `full`), output aspect ratio is force to 1/1 if not set.\n" "\n" "When sample aspect ratio is kept, the filter will:\n" "- center the rescaled input frame on the output frame\n" @@ -523,6 +530,7 @@ .reconfigure_output = evgs_reconfigure_output, //use low priority in case we have other scalers (ffsws is faster) .priority = 128, + .hint_class_type = GF_FS_CLASS_AV }; #endif //GPAC_DISABLE_EVG
View file
gpac-2.4.0.tar.gz/src/filters/ff_avf.c -> gpac-26.02.0.tar.gz/src/filters/ff_avf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom Paris 2019-2023 + * Copyright (c) Telecom Paris 2019-2025 * All rights reserved * * This file is part of GPAC / ffmpeg avfilter filter @@ -46,6 +46,7 @@ AVFilterContext *io_filter_ctx; GF_FilterPid *io_pid; u32 timescale, width, height, sr, nb_ch, bps, bpp; + GF_Fraction fps; Bool planar; u32 pfmt; //ffmpeg pixel or audio format u64 ch_layout; //ffmpeg channel layout @@ -68,6 +69,8 @@ GF_List *ipids; GF_List *opids; + GF_List *src_packets; + AVFilterGraph *filter_graph; char *filter_desc; @@ -111,8 +114,8 @@ if (avpid->width) { avf = avfilter_get_by_name("buffer"); snprintf(args, sizeof(args), - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - avpid->width, avpid->height, avpid->pfmt, 1, avpid->timescale, avpid->sar.num, avpid->sar.den); + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:frame_rate=%d/%d:pixel_aspect=%d/%d", + avpid->width, avpid->height, avpid->pfmt, 1, avpid->timescale, avpid->fps.num, avpid->fps.den, avpid->sar.num, avpid->sar.den); } else { avf = avfilter_get_by_name("abuffer"); snprintf(args, sizeof(args), @@ -121,10 +124,14 @@ } //destroy filter (will remove from graph) if (avpid->io_filter_ctx) avfilter_free(avpid->io_filter_ctx); - avpid->io_filter_ctx = NULL; - ret = avfilter_graph_create_filter(&avpid->io_filter_ctx, avf, pid_name, args, NULL, ctx->filter_graph); - if (ret<0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to create filter graph: %s\n", av_err2str(ret) )); + avpid->io_filter_ctx = avfilter_graph_alloc_filter(ctx->filter_graph, avf, pid_name); + if (!avpid->io_filter_ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to create filter graph\n")); + return GF_OUT_OF_MEM; + } + ret = avfilter_init_str(avpid->io_filter_ctx, args); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to initialize filter graph: %s\n", av_err2str(ret) )); return GF_BAD_PARAM; } return GF_OK; @@ -210,10 +217,10 @@ if (nb_outputs==1) sprintf(szName, "out"); - ret = avfilter_graph_create_filter(&opid->io_filter_ctx, avf, szName, NULL, NULL, ctx->filter_graph); - if (ret<0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to create %s filter: %s\n", avf->name, av_err2str(ret) )); - return GF_BAD_PARAM; + opid->io_filter_ctx = avfilter_graph_alloc_filter(ctx->filter_graph, avf, szName); + if (!opid->io_filter_ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to create %s filter\n", avf->name)); + return GF_OUT_OF_MEM; } if (opid->is_video) { if (ctx->pfmt) { @@ -240,10 +247,14 @@ if (ctx->ch) { ret = av_opt_set_bin(opid->io_filter_ctx, "channels", (uint8_t*)&ctx->ch, sizeof(ctx->ch), AV_OPT_SEARCH_CHILDREN); if (ret < 0) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFAVF Fail to set %s audio sample rate: %s\n", avf->name, av_err2str(ret) )); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFAVF Fail to set %s audio channel layout: %s\n", avf->name, av_err2str(ret) )); } } - + } + ret = avfilter_init_str(opid->io_filter_ctx, NULL); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to initialize %s filter: %s\n", avf->name, av_err2str(ret) )); + return GF_BAD_PARAM; } io->name = av_strdup(szName); io->filter_ctx = opid->io_filter_ctx; @@ -296,6 +307,7 @@ ctx->ipids = gf_list_new(); ctx->opids = gf_list_new(); + ctx->src_packets = gf_list_new(); ctx->frame = av_frame_alloc(); ffmpeg_setup_logs(GF_LOG_MEDIA); @@ -470,12 +482,12 @@ avfilter_inout_free(&inputs); if (ret < 0) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to parse filter description: %s\nFilter description was %s\n", av_err2str(ret), ctx->filter_desc)); - return ctx->in_error = GF_BAD_PARAM; + return ctx->in_error = GF_BAD_PARAM; } ret = avfilter_graph_config(ctx->filter_graph, NULL); if (ret < 0) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFAVF Fail to validate filter graph: %s\n", av_err2str(ret) )); - return ctx->in_error = GF_BAD_PARAM; + return ctx->in_error = GF_BAD_PARAM; } if (ctx->dump) @@ -489,6 +501,7 @@ GF_Err e = GF_OK; u32 i, count, nb_eos; GF_FFAVFilterCtx *ctx = (GF_FFAVFilterCtx *) gf_filter_get_udta(filter); + Bool can_merge_props = gf_list_count(ctx->opids) == 1 && gf_list_count(ctx->ipids) == 1; if (ctx->in_error) return ctx->in_error; @@ -572,6 +585,12 @@ } } + //keep ref to source properties + if (pck && can_merge_props) { + gf_filter_pck_ref_props(&pck); + gf_list_add(ctx->src_packets, pck); + } + if (frame_ok) { u64 cts = gf_filter_pck_get_cts(pck); ctx->frame->pts = cts; @@ -632,7 +651,7 @@ AVFrame *frame = av_frame_alloc(); ret = av_buffersink_get_frame(opid->io_filter_ctx, frame); - if (ret < 0) { + if (ret < 0) { if (ret == AVERROR_EOF) { if (ctx->flush_state==2) { nb_eos++; @@ -648,8 +667,8 @@ } av_frame_free(&frame); break; - } - if (opid->is_video) { + } + if (opid->is_video) { u8 *buffer; u32 j; GF_FilterPacket *pck; @@ -660,10 +679,48 @@ else { update_props = GF_FALSE; } + + //ensure out_size is correct + if (update_props) { + opid->gf_pfmt = ffmpeg_pixfmt_to_gpac(frame->format, GF_FALSE); + opid->pfmt = frame->format; + opid->width = frame->width; + opid->height = frame->height; + opid->tb_num = opid->io_filter_ctx->inputs0->time_base.num; + opid->stride = 0; + opid->stride_uv = 0; + opid->bpp = gf_pixel_get_bytes_per_pixel(opid->gf_pfmt); + gf_pixel_get_size_info(opid->gf_pfmt, opid->width, opid->height, &opid->out_size, &opid->stride, &opid->stride_uv, NULL, &opid->uv_height); + if ((opid->gf_pfmt==GF_PIXEL_YUV444) + || (opid->gf_pfmt==GF_PIXEL_YUV444_10) + || (opid->gf_pfmt==GF_PIXEL_NV12) + || (opid->gf_pfmt==GF_PIXEL_NV21) + ) { + opid->uv_width = opid->width; + } else if (opid->uv_height) { + opid->uv_width = opid->width/2; + } else { + opid->uv_width = 0; + } + } + + //allocate packet + pck = gf_filter_pck_new_alloc(opid->io_pid, opid->out_size, &buffer); + if (!pck) return GF_OUT_OF_MEM; + + //merge properties from source if any + if (gf_list_count(ctx->src_packets) && can_merge_props) { + GF_FilterPacket *src_pck = gf_list_pop_front(ctx->src_packets); + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, pck); + gf_filter_pck_unref(src_pck); + } + } + + //update properties if (update_props) { gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_WIDTH, &PROP_UINT(frame->width)); gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_HEIGHT, &PROP_UINT(frame->height)); - opid->gf_pfmt = ffmpeg_pixfmt_to_gpac(frame->format, GF_FALSE); if (ffmpeg_pixfmt_is_fullrange(frame->format)) { gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_COLR_RANGE, &PROP_BOOL(GF_TRUE)); } else { @@ -677,29 +734,17 @@ gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STRIDE_UV, NULL); gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(opid->io_filter_ctx->inputs0->time_base.den) ); - gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(opid->io_filter_ctx->inputs0->time_base.den, opid->io_filter_ctx->inputs0->time_base.num) ); - - opid->width = frame->width; - opid->height = frame->height; - opid->pfmt = frame->format; - opid->tb_num = opid->io_filter_ctx->inputs0->time_base.num; - opid->stride = 0; - opid->stride_uv = 0; - opid->bpp = gf_pixel_get_bytes_per_pixel(opid->gf_pfmt); - gf_pixel_get_size_info(opid->gf_pfmt, opid->width, opid->height, &opid->out_size, &opid->stride, &opid->stride_uv, NULL, &opid->uv_height); - if ((opid->gf_pfmt==GF_PIXEL_YUV444) || (opid->gf_pfmt==GF_PIXEL_YUV444_10)) { - opid->uv_width = opid->width; - } else if (opid->uv_height) { - opid->uv_width = opid->width/2; + AVRational fps = av_buffersink_get_frame_rate(opid->io_filter_ctx); + if (fps.num && fps.den) { + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(fps.num, fps.den)); } else { - opid->uv_width = 0; + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(opid->io_filter_ctx->inputs0->time_base.den, opid->io_filter_ctx->inputs0->time_base.num)); } + if (ctx->nb_a_out+ctx->nb_v_out>1) { gf_filter_pid_set_property_str(opid->io_pid, "ffid", &PROP_STRING(opid->io_filter_ctx->name)); } } - pck = gf_filter_pck_new_alloc(opid->io_pid, opid->out_size, &buffer); - if (!pck) return GF_OUT_OF_MEM; for (j=0; j<opid->height; j++) { memcpy(buffer + j*opid->stride, frame->data0 + j*frame->linesize0, opid->width*opid->bpp); @@ -722,9 +767,13 @@ memcpy(buffer + j*opid->stride, frame->data3 + j*frame->linesize3, opid->width*opid->bpp); } } +#if (LIBAVFORMAT_VERSION_MAJOR < 62) if (frame->interlaced_frame) gf_filter_pck_set_interlaced(pck, frame->top_field_first ? 1 : 2); - +#else + if (frame->flags & AV_FRAME_FLAG_INTERLACED) + gf_filter_pck_set_interlaced(pck, frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST ? 1 : 2); +#endif gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); gf_filter_pck_set_cts(pck, frame->pts * opid->tb_num); gf_filter_pck_send(pck); @@ -745,6 +794,25 @@ else { update_props = GF_FALSE; } + + out_size = 0; + for (j=0; j<8; j++) { + if (!frame->linesizej) break; + out_size += frame->linesizej; + } + + pck = gf_filter_pck_new_alloc(opid->io_pid, out_size, &buffer); + if (!pck) return GF_OUT_OF_MEM; + + //merge properties from source if any + if (gf_list_count(ctx->src_packets) && can_merge_props) { + GF_FilterPacket *src_pck = gf_list_pop_front(ctx->src_packets); + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, pck); + gf_filter_pck_unref(src_pck); + } + } + if (update_props) { #ifdef FFMPEG_OLD_CHLAYOUT u32 nb_ch = frame->channels; @@ -771,14 +839,6 @@ gf_filter_pid_set_property_str(opid->io_pid, "ffid", &PROP_STRING(opid->io_filter_ctx->name)); } } - out_size = 0; - for (j=0; j<8; j++) { - if (!frame->linesizej) break; - out_size += frame->linesizej; - } - - pck = gf_filter_pck_new_alloc(opid->io_pid, out_size, &buffer); - if (!pck) return GF_OUT_OF_MEM; for (j=0; j<8; j++) { if (!frame->linesizej) break; @@ -842,7 +902,7 @@ if (!pid_ctx) { GF_SAFEALLOC(pid_ctx, GF_FFAVPid); if (!pid_ctx) return GF_OUT_OF_MEM; - + pid_ctx->io_pid = pid; gf_filter_pid_set_udta(pid, pid_ctx); gf_list_add(ctx->ipids, pid_ctx); @@ -859,7 +919,7 @@ if (streamtype==GF_STREAM_VISUAL) { u32 width, height, pix_fmt, gf_pfmt; - GF_Fraction sar={1,1}; + GF_Fraction sar={1,1}, fps={0, 1}; p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); if (!p) return GF_OK; //not ready yet width = p->value.uint; @@ -873,8 +933,11 @@ gf_pfmt = p->value.uint; pix_fmt = ffmpeg_pixfmt_from_gpac(gf_pfmt, GF_FALSE); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p && (p->value.frac.num>0) && p->value.frac.den) fps = p->value.frac; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p && p->value.frac.num && p->value.frac.den) sar = p->value.frac; + if (p && (p->value.frac.num>0) && p->value.frac.den) sar = p->value.frac; pid_ctx->stride = pid_ctx->stride_uv = 0; p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); @@ -898,6 +961,7 @@ pid_ctx->height = height; pid_ctx->pfmt = pix_fmt; pid_ctx->timescale = timebase.den; + pid_ctx->fps = fps; pid_ctx->sar = sar; } else if (streamtype==GF_STREAM_AUDIO) { u64 ch_layout=0; @@ -989,6 +1053,11 @@ gf_free(opid); } gf_list_del(ctx->opids); + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); if (ctx->filter_desc) gf_free(ctx->filter_desc); if (ctx->frame) av_frame_free(&ctx->frame); } @@ -1082,16 +1151,16 @@ GF_FilterRegister FFAVFilterRegister = { .name = "ffavf", .version = LIBAVFILTER_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG AVFilter") + GF_FS_SET_DESCRIPTION("FFmpeg AV Filter") GF_FS_SET_HELP("This filter provides libavfilter raw audio and video tools.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details\n" "To list all supported avfilters for your GPAC build, use `gpac -h ffavf:*`.\n" "\n" "# Declaring a filter\n" "The filter loads a filter or a filter chain description from the -f() option.\n" "EX ffavf:f=showspectrum\n" "\n" - "Unlike other FFMPEG bindings in GPAC, this filter does not parse other libavfilter options, you must specify them directly in the filter chain, and the -f() option will have to be escaped.\n" + "Unlike other FFmpeg bindings in GPAC, this filter does not parse other libavfilter options, you must specify them directly in the filter chain, and the -f() option will have to be escaped.\n" "EX ffavf::f=showspectrum=size=320x320 or ffavf::f=showspectrum=size=320x320::pfmt=rgb\n" "EX ffavf::f=anullsrc=channel_layout=5.1:sample_rate=48000\n" "\n" @@ -1137,6 +1206,7 @@ .process = ffavf_process, .process_event = ffavf_process_event, .update_arg = ffavf_update_arg, + .hint_class_type = GF_FS_CLASS_AV }; #define OFFS(_n) #_n, offsetof(GF_FFAVFilterCtx, _n)
View file
gpac-2.4.0.tar.gz/src/filters/ff_bsf.c -> gpac-26.02.0.tar.gz/src/filters/ff_bsf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom Paris 2022 + * Copyright (c) Telecom Paris 2022-2024 * All rights reserved * * This file is part of GPAC / ffmpeg avbitstreamfilter filter @@ -407,9 +407,9 @@ GF_FilterRegister FFBSFRegister = { .name = "ffbsf", .version = LIBAVUTIL_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG BitStream filter") + GF_FS_SET_DESCRIPTION("FFmpeg bitstream filter") GF_FS_SET_HELP("This filter provides bitstream filters (BSF) for compressed audio and video formats.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details\n" "To list all supported bitstream filters for your GPAC build, use `gpac -h ffbsf:*`.\n" "\n" "Several BSF may be specified in -f() for different coding types. BSF not matching the coding type are silently ignored.\n" @@ -429,7 +429,8 @@ .configure_pid = ffbsf_configure_pid, .process = ffbsf_process, .update_arg = ffbsf_update_arg, - .process_event = ffbsf_process_event + .process_event = ffbsf_process_event, + .hint_class_type = GF_FS_CLASS_STREAM }; #define OFFS(_n) #_n, offsetof(GF_FFBSFCtx, _n)
View file
gpac-2.4.0.tar.gz/src/filters/ff_common.c -> gpac-26.02.0.tar.gz/src/filters/ff_common.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / common ffmpeg filters @@ -67,6 +67,7 @@ const char *ff_name; u32 gpac_p4cc; u32 gpac_tag; + Bool is_info; } GF_FF_TAGREG; static const GF_FF_TAGREG FF2GPAC_Tags = @@ -85,8 +86,8 @@ {"genre", 0, GF_ISOM_ITUNE_GENRE}, {"language", GF_PROP_PID_LANGUAGE, 0}, {"performer", 0, GF_ISOM_ITUNE_PERFORMER}, - {"service_name", GF_PROP_PID_SERVICE_NAME, 0}, - {"service_provider", GF_PROP_PID_SERVICE_PROVIDER, 0}, + {"service_name", GF_PROP_PID_SERVICE_NAME, 0, GF_TRUE}, + {"service_provider", GF_PROP_PID_SERVICE_PROVIDER, 0, GF_TRUE}, {"title", 0, GF_ISOM_ITUNE_NAME}, {"track", 0, GF_ISOM_ITUNE_TRACK}, {NULL, 0, 0} @@ -97,9 +98,14 @@ const GF_PropertyValue *p; u32 i=0; while (FF2GPAC_Tagsi.ff_name) { + GF_PropertyEntry *pe=NULL; p = NULL; if (FF2GPAC_Tagsi.gpac_p4cc) { - p = gf_filter_pid_get_property(pid, FF2GPAC_Tagsi.gpac_p4cc); + if (FF2GPAC_Tagsi.is_info) { + p = gf_filter_pid_get_info(pid, FF2GPAC_Tagsi.gpac_p4cc, &pe); + } else { + p = gf_filter_pid_get_property(pid, FF2GPAC_Tagsi.gpac_p4cc); + } } else { const char *name = gf_itags_get_name(FF2GPAC_Tagsi.gpac_tag); if (name) @@ -116,6 +122,7 @@ break; } } + gf_filter_release_property(pe); i++; } p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_HANDLER); @@ -163,7 +170,11 @@ continue; } if (FF2GPAC_Tagsi.gpac_p4cc) { - gf_filter_pid_set_property(pid, FF2GPAC_Tagsi.gpac_p4cc, &PROP_STRING(ent->value) ); + if (FF2GPAC_Tagsi.is_info) { + gf_filter_pid_set_info(pid, FF2GPAC_Tagsi.gpac_p4cc, &PROP_STRING(ent->value) ); + } else { + gf_filter_pid_set_property(pid, FF2GPAC_Tagsi.gpac_p4cc, &PROP_STRING(ent->value) ); + } } else { const char *name = gf_itags_get_name(FF2GPAC_Tagsi.gpac_tag); if (name) @@ -235,6 +246,7 @@ {AV_PIX_FMT_RGB444, GF_PIXEL_RGB_444}, {AV_PIX_FMT_RGB555, GF_PIXEL_RGB_555}, {AV_PIX_FMT_RGB565, GF_PIXEL_RGB_565}, + {AV_PIX_FMT_RGB8, GF_PIXEL_RGB_332}, {AV_PIX_FMT_RGBA, GF_PIXEL_RGBA}, {AV_PIX_FMT_ARGB, GF_PIXEL_ARGB}, {AV_PIX_FMT_ABGR, GF_PIXEL_ABGR}, @@ -256,7 +268,7 @@ i++; } if (!no_warn && (pfmt!=AV_PIX_FMT_NONE)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFMPEG Unmapped GPAC pixel format %s, patch welcome\n", gf_4cc_to_str(pfmt) )); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFmpeg Unmapped GPAC pixel format %s, patch welcome\n", gf_4cc_to_str(pfmt) )); } return AV_PIX_FMT_NONE; } @@ -266,7 +278,7 @@ const AVPixFmtDescriptor *ffdesc = av_pix_fmt_desc_get(pfmt); if (!ffdesc) { if (!no_warn && (pfmt!=AV_PIX_FMT_NONE)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFMPEG Unrecognized FFMPEG pixel format %d\n", pfmt )); + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFmpeg Unrecognized FFmpeg pixel format %d\n", pfmt )); } return 0; } @@ -277,7 +289,7 @@ i++; } if (!no_warn && (pfmt!=AV_PIX_FMT_NONE)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFMPEG Unmapped FFMPEG pixel format %s, patch welcome\n", ffdesc->name)); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFmpeg Unmapped FFmpeg pixel format %s, patch welcome\n", ffdesc->name)); } return 0; } @@ -340,7 +352,7 @@ return FF2GPAC_AudioFormatsi.ff_sf; i++; } - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFMPEG Unmapped GPAC audio format %s, patch welcome\n", gf_4cc_to_str(sfmt) )); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFmpeg Unmapped GPAC audio format %s, patch welcome\n", gf_4cc_to_str(sfmt) )); return 0; } @@ -352,7 +364,7 @@ return FF2GPAC_AudioFormatsi.gpac_sf; i++; } - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFMPEG Unmapped FFMPEG audio format %d, patch welcome\n", sfmt )); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFmpeg Unmapped FFmpeg audio format %d, patch welcome\n", sfmt )); return 0; } @@ -531,6 +543,7 @@ {AV_CODEC_ID_DTS, GF_CODECID_DTS_CA, 0}, {AV_CODEC_ID_DTS, GF_CODECID_DTS_EXPRESS_LBR, 0}, {AV_CODEC_ID_ALAC, GF_CODECID_ALAC, 0}, + {AV_CODEC_ID_DNXHD, GF_CODECID_DNXHD, 0}, {0} }; @@ -554,7 +567,7 @@ if (c) return c->id; } - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("FFMPEG Unmapped GPAC codec %s\n", gf_codecid_name(codec_id) )); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("FFmpeg Unmapped GPAC codec %s\n", gf_codecid_name(codec_id) )); return 0; } @@ -566,7 +579,7 @@ return FF2GPAC_CodecIDsi.gpac_codec_id; i++; } - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("FFMPEG Unmapped FFMPEG codec ID %s\n", avcodec_get_name(codec_id) )); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("FFmpeg Unmapped FFmpeg codec ID %s\n", avcodec_get_name(codec_id) )); return 0; } @@ -606,7 +619,7 @@ return FF2GPAC_StreamTypesi.ff_st; i++; } - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFMPEG Unmapped GPAC stream type %s, assuming data\n", gf_stream_type_name(streamtype) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFmpeg Unmapped GPAC stream type %s, assuming data\n", gf_stream_type_name(streamtype) )); return AVMEDIA_TYPE_DATA; } @@ -619,7 +632,7 @@ return FF2GPAC_StreamTypesi.gpac_st; i++; } - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFMPEG Unmapped FFMPEG stream type %d, assuming data\n", streamtype )); + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FFmpeg Unmapped FFmpeg stream type %d, assuming data\n", streamtype )); return GF_STREAM_METADATA; } @@ -731,7 +744,9 @@ ffmpeg_init = GF_TRUE; #ifndef GPAC_DISABLE_LOG - av_log_set_callback(&ff_log_callback); + if (!gf_opts_get_bool("temp", "disable_ffmpeg_log_harness")) { + av_log_set_callback(&ff_log_callback); + } #endif } @@ -832,7 +847,6 @@ break; #if LIBAVCODEC_VERSION_MAJOR >= 57 case AV_OPT_TYPE_UINT64: -// case AV_OPT_TYPE_UINT: arg.arg_type = GF_PROP_LUINT; sprintf(szDef, LLU, opt->default_val.i64); arg.arg_default_val = gf_strdup(szDef); @@ -847,6 +861,20 @@ arg.arg_default_val = gf_strdup(opt->default_val.i64 ? "true" : "false"); break; #endif + +#if AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, 0) >= AV_VERSION_INT(59,17, 0) + case AV_OPT_TYPE_UINT: + arg.arg_type = GF_PROP_UINT; + sprintf(szDef, "%u", (u32) opt->default_val.i64); + arg.arg_default_val = gf_strdup(szDef); + if (opt->max>=(Double) GF_INT_MAX) + sprintf(szDef, "%u-I", (u32) opt->min); + else + sprintf(szDef, "%u-%u", (u32) opt->min, (u32) opt->max); + arg.min_max_enum = gf_strdup(szDef); + break; +#endif + case AV_OPT_TYPE_FLOAT: arg.arg_type = GF_PROP_FLOAT; sprintf(szDef, "%g", opt->default_val.dbl); @@ -922,7 +950,7 @@ break; #endif default: - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFMPEG Unknown ffmpeg option type %d\n", opt->type)); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FFmpeg Unknown ffmpeg option type %d\n", type)); break; } return arg; @@ -1469,7 +1497,7 @@ par_arg->arg_desc = par_arg->arg_desc ? gf_strdup(par_arg->arg_desc) : NULL; par_arg->flags |= GF_FS_ARG_META_ALLOC; } - gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_name, "\n - "); + gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_name, "\n- "); gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_desc, ": "); if (an_arg.arg_default_val) @@ -1700,7 +1728,10 @@ while (ctx->av_class->option) { const struct AVOption *opt = &ctx->av_class->optionidx; if (!opt || !opt->name) break; - if (opt->name && !strcmp(opt->name, de->key) && (!stricmp(de->value, "true") || !stricmp(de->value, "yes") || !stricmp(de->value, "1") )) { + if ((opt->name && !strcmp(opt->name, de->key) && (!stricmp(de->value, "true") || !stricmp(de->value, "yes") || !stricmp(de->value, "1") )) + + || (opt->unit && !strcmp(de->key, opt->unit) && !strcmp(opt->name, de->value)) + ) { if (opt->unit && !strcmp(opt->unit, "flags")) ctx->flags |= (int) opt->default_val.i64; else if (opt->unit && !strcmp(opt->unit, "flags2")) @@ -1826,23 +1857,23 @@ gf_bs_del(bs); if (!flac_dsi || !flac_dsi_size) return GF_NON_COMPLIANT_BITSTREAM; *dsi_out_size = flac_dsi_size; - *dsi_out = av_malloc(sizeof(char) * (flac_dsi_size) ); + *dsi_out = av_malloc(sizeof(char) * (flac_dsi_size) + AV_INPUT_BUFFER_PADDING_SIZE); if (! *dsi_out) return GF_OUT_OF_MEM; memcpy(*dsi_out, flac_dsi, flac_dsi_size); } else if (gpac_codec_id==GF_CODECID_OPUS) { *dsi_out_size = dsi_in_size+8; - *dsi_out = av_malloc(sizeof(char) * (dsi_in_size+8) ); + *dsi_out = av_malloc(sizeof(char) * (dsi_in_size+8) + AV_INPUT_BUFFER_PADDING_SIZE); if (! *dsi_out) return GF_OUT_OF_MEM; memcpy(*dsi_out, "OpusHead", 8); memcpy(*dsi_out+8, dsi_in, dsi_in_size); } else if ((gpac_codec_id==GF_CODECID_SMPTE_VC1) && (dsi_in_size>7)) { *dsi_out_size = dsi_in_size-7; - *dsi_out = av_malloc(sizeof(char) * (dsi_in_size-7) ); + *dsi_out = av_malloc(sizeof(char) * (dsi_in_size-7) + AV_INPUT_BUFFER_PADDING_SIZE); if (! *dsi_out) return GF_OUT_OF_MEM; memcpy(*dsi_out, dsi_in+7, dsi_in_size-7); } else { *dsi_out_size = dsi_in_size; - *dsi_out = av_malloc(sizeof(char) * dsi_in_size); + *dsi_out = av_malloc(sizeof(char) * dsi_in_size + AV_INPUT_BUFFER_PADDING_SIZE); if (! *dsi_out) return GF_OUT_OF_MEM; memcpy(*dsi_out, dsi_in, dsi_in_size); } @@ -2078,7 +2109,7 @@ } p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p) { + if (p && (p->value.frac.num>0)) { codecpar->sample_aspect_ratio.num = p->value.frac.num; codecpar->sample_aspect_ratio.den = p->value.frac.den; } @@ -2163,7 +2194,7 @@ gf_filter_pid_set_property(opid, GF_PROP_PID_SAR, &PROP_FRAC_INT(codecpar->sample_aspect_ratio.num, codecpar->sample_aspect_ratio.den)); } //not supported by all versions of ffmpeg - if (!gf_sys_is_test_mode()) { + if (codecpar->width && !gf_sys_is_test_mode()) { if (codecpar->color_range==AVCOL_RANGE_JPEG) gf_filter_pid_set_property(opid, GF_PROP_PID_COLR_RANGE, &PROP_BOOL(GF_TRUE)); else if (codecpar->color_range==AVCOL_RANGE_MPEG) @@ -2175,7 +2206,7 @@ if (codecpar->color_trc) gf_filter_pid_set_property(opid, GF_PROP_PID_COLR_TRANSFER, &PROP_UINT(codecpar->color_trc)); - if (codecpar->color_space) + if (codecpar->color_space!=AVCOL_SPC_UNSPECIFIED) gf_filter_pid_set_property(opid, GF_PROP_PID_COLR_MX, &PROP_UINT(codecpar->color_space)); if (codecpar->chroma_location) @@ -2272,10 +2303,10 @@ codecctx->thread_count = 0; codecctx->thread_type = 0; if (num_threads>0) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("Using FFMPEG threads on main thread would deadlock, disabling threading (use -threads=1 to have one extra gpac thread)\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("Using FFmpeg threads on main thread would deadlock, disabling threading (use -threads=1 to have one extra gpac thread)\n")); } } else { - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("FFMPEG threads (%d type %d) enabled\n", num_threads, codecctx->thread_type)); + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("FFmpeg threads (%d type %d) enabled\n", num_threads, codecctx->thread_type)); } #elif defined(GPAC_CONFIG_EMSCRIPTEN) //no thread support in build, disable ffmpeg threading
View file
gpac-2.4.0.tar.gz/src/filters/ff_dec.c -> gpac-26.02.0.tar.gz/src/filters/ff_dec.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / ffmpeg decode filter @@ -241,11 +241,11 @@ gf_filter_pid_drop_packet(ctx->in_pid); return GF_OK; } - //we don't own the codec and we're in end of stream, don't try to decode (the context might have been closed) - if (!pck && !ctx->owns_context) { - gf_filter_pid_set_eos(ctx->out_pid); - return GF_EOS; - } + //we don't own the codec and we're in end of stream, don't try to decode (the context might have been closed) + if (!pck && !ctx->owns_context) { + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } restart: @@ -451,9 +451,9 @@ gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); } - //rewrite dts and pts to PTS value - gf_filter_pck_set_dts(dst_pck, out_cts); - gf_filter_pck_set_cts(dst_pck, out_cts); + //rewrite dts and pts to PTS value + gf_filter_pck_set_dts(dst_pck, out_cts); + gf_filter_pck_set_cts(dst_pck, out_cts); ff_pfmt = ctx->decoder->pix_fmt; if (ff_pfmt==AV_PIX_FMT_YUVJ420P) { @@ -479,6 +479,11 @@ dst_stride0 = 4*ctx->width; pix_out = AV_PIX_FMT_RGBA; break; + case GF_PIXEL_BGRA: + dst_planes0 = (uint8_t *)out_buffer; + dst_stride0 = 4*ctx->width; + pix_out = AV_PIX_FMT_BGRA; + break; case GF_PIXEL_YUV: case GF_PIXEL_YUV_10: dst_planes0 = (uint8_t *)out_buffer; @@ -535,8 +540,13 @@ gf_filter_pck_set_seek_flag(dst_pck, GF_FALSE); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) if (frame->interlaced_frame) gf_filter_pck_set_interlaced(dst_pck, frame->top_field_first ? 2 : 1); +#else + if (frame->flags & AV_FRAME_FLAG_INTERLACED) + gf_filter_pck_set_interlaced(dst_pck, frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST ? 2 : 1); +#endif gf_filter_pck_send(dst_pck); @@ -1119,9 +1129,9 @@ else { u32 codec_id, ff_codectag=0; - if (!ctx->owns_context) { - ctx->decoder = NULL; - } + if (!ctx->owns_context) { + ctx->decoder = NULL; + } if (ctx->decoder) { codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, NULL); //same codec, same config, don't reinit @@ -1136,7 +1146,7 @@ } } - + //we could further optimize by detecting we have the same codecid and injecting the extradata //but this is not 100% reliable, and will require parsing AVC/HEVC config //since this seems to work properly with decoder close/open, we keep it as is @@ -1345,7 +1355,7 @@ } //if SAR is given ignore sar detection prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (prop) { + if (prop && (prop->value.frac.num>0)) { ctx->sar.num = 0; ctx->sar.den = 0; } else if (ctx->sar.num) { @@ -1502,19 +1512,20 @@ GF_FilterRegister FFDecodeRegister = { .name = "ffdec", .version = LIBAVCODEC_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG decoder") - GF_FS_SET_HELP("This filter decodes audio and video streams using FFMPEG.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details.\n" + GF_FS_SET_DESCRIPTION("FFmpeg decoder") + GF_FS_SET_HELP("This filter decodes audio and video streams using FFmpeg.\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details.\n" "To list all supported decoders for your GPAC build, use `gpac -h ffdec:*`.\n" "\n" "Options can be passed from prompt using `--OPT=VAL`\n" + "Decoder flags can be passed directly as `:FLAGNAME`.\n" "The default threading mode is to let libavcodec decide how many threads to use. To enforce single thread, use `--threads=1`\n" "\n" "# Codec Map\n" - "The -ffcmap() option allows specifying FFMPEG codecs for codecs not supported by GPAC.\n" + "The -ffcmap() option allows specifying FFmpeg codecs for codecs not supported by GPAC.\n" "Each entry in the list is formatted as `GID@name` or `GID@+name`, with:\n" "- GID: 4CC or 32 bit identifier of codec ID, as indicated by `gpac -i source inspect:full`\n" - "- name: FFMPEG codec name\n" + "- name: FFmpeg codec name\n" "- `+': is set and extra data is set and formatted as an ISOBMFF box, removes box header\n" "\n" "EX gpac -i source.mp4 --ffcmap=BKV1@binkvideo vout\n" @@ -1531,8 +1542,8 @@ .flags = GF_FS_REG_META|GF_FS_REG_BLOCK_MAIN, //use middle priorty, so that hardware decs/other native impl in gpac can take over if needed //don't use lowest one since we use this for scalable codecs - .priority = 128 - + .priority = 128, + .hint_class_type = GF_FS_CLASS_DECODER };
View file
gpac-2.4.0.tar.gz/src/filters/ff_dmx.c -> gpac-26.02.0.tar.gz/src/filters/ff_dmx.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / ffmpeg demux filter @@ -28,6 +28,7 @@ #ifdef GPAC_HAS_FFMPEG #include "ff_common.h" +#include "gpac/internal/ff_dmx.h" //for NTP clock #include <gpac/network.h> @@ -38,13 +39,12 @@ #define FFMPEG_NO_DOVI #endif -enum -{ +GF_OPT_ENUM(GF_FFDemuxRawFrameCopyMode, COPY_NO, COPY_A, COPY_V, - COPY_AV -}; + COPY_AV, +); typedef struct { @@ -60,12 +60,13 @@ typedef struct { //options - const char *src; + const char *src, *ext, *mime; u32 block_size; - u32 copy, probes; + GF_FFDemuxRawFrameCopyMode copy; + u32 probes; Bool sclock; const char *fmt, *dev; - Bool reparse; + Bool reparse, proto; //internal data const char *fname; @@ -73,6 +74,7 @@ Bool raw_data; //input file + Bool src_as_avf; AVFormatContext *demuxer; //demux options AVDictionary *options; @@ -105,6 +107,10 @@ FILE *gfio; GF_Fraction fps_forced; + //for direct ffdmx and AVFormatContext connection + void *rt_udta; + GF_FFDemuxCallbackFn on_pkt; + //for ffdmx used as filter on http or file input //we must buffer enough data so that calls to read_packet() does not abort in the middle of a packet GF_FilterPid *ipid; @@ -115,6 +121,12 @@ Bool in_eos, first_block; s64 seek_offset; u64 seek_ms; + + //for ffdmx in proto mode + GF_FilterPid *opid; + GF_PropVec2i mwait; + u64 rcv_time_diff, last_pck_time; + } GF_FFDemuxCtx; static void ffdmx_finalize(GF_Filter *filter) @@ -135,7 +147,7 @@ av_dict_free(&ctx->options); if (ctx->probe_times) gf_free(ctx->probe_times); - if (ctx->demuxer) { + if (ctx->demuxer && !ctx->src_as_avf) { avformat_close_input(&ctx->demuxer); avformat_free_context(ctx->demuxer); } @@ -145,6 +157,12 @@ } if (ctx->gfio) gf_fclose(ctx->gfio); if (ctx->strbuf) gf_free(ctx->strbuf); +#if (LIBAVFORMAT_VERSION_MAJOR >= 59) + if (ctx->pkt) { + av_packet_free(&ctx->pkt); + ctx->pkt = NULL; + } +#endif return; } @@ -450,8 +468,84 @@ AVPacket *pkt; PidCtx *pctx; int res; + GF_FFDemuxCallbackRet avf_ret = GF_FFDMX_OK; GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx *) gf_filter_get_udta(filter); + if (ctx->proto) { + u64 rcv_time = gf_sys_clock_high_res(); + u32 nb_pck = 100; + while (nb_pck) { + u8 const_data1880; + u8 *data = const_data; + u32 data_size = 1880; + GF_FilterPacket *pck = NULL; + if (ctx->opid) { + pck = gf_filter_pck_new_alloc(ctx->opid, ctx->block_size, &data); + if (!pck) return GF_OUT_OF_MEM; + data_size = ctx->block_size; + } + + int size = avio_read_partial(ctx->avio_ctx, data, data_size); + if (!size) + size = ctx->avio_ctx->error; + + if (size<0) { + if (ctx->avio_ctx->error == AVERROR(EAGAIN)) { + if (pck) gf_filter_pck_discard(pck); + //looks like some proto handlers set EOF when no packets and avio_read* will not attempt to read when flag is set + ctx->avio_ctx->eof_reached = 0; + + u64 sleep_for = 2*ctx->rcv_time_diff/3000; + if (sleep_for > ctx->mwait.y) sleep_for = ctx->mwait.y; + if (sleep_for < ctx->mwait.x) sleep_for = ctx->mwait.x; + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("FFDMX empty (got %u pck) - sleeping for "LLU" ms\n", nb_pck, sleep_for )); + gf_filter_ask_rt_reschedule(filter, (u32) sleep_for*1000); + return GF_OK; + } + if (ctx->avio_ctx->eof_reached) { + if (pck) gf_filter_pck_discard(pck); + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + if (ctx->avio_ctx->error) { + if (pck) gf_filter_pck_discard(pck); + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("FFDMX Read error %s - aborting\n", av_err2str(ctx->avio_ctx->error))); + return GF_IO_ERR; + } + } + if (rcv_time) { + if (ctx->last_pck_time) { + ctx->rcv_time_diff = rcv_time - ctx->last_pck_time; + } + ctx->last_pck_time = rcv_time; + rcv_time = 0; + } + + if (!ctx->opid) { + GF_Err e = gf_filter_pid_raw_new(filter, ctx->src, NULL, ctx->mime, ctx->ext, data, size, GF_FALSE, &ctx->opid); + if (e) { + if (pck) gf_filter_pck_discard(pck); + gf_filter_setup_failure(filter, e); + return e; + } + } + if (pck) { + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + gf_filter_pck_truncate(pck, size); + } else { + u8 *output; + pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + if (!pck) return GF_OUT_OF_MEM; + memcpy(output, data, size); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_FALSE); + } + gf_filter_pck_send(pck); + nb_pck--; + } + return GF_OK; + } + restart: if (ctx->ipid) { e = ffdmx_flush_input(filter, ctx); @@ -492,22 +586,48 @@ sample_time = gf_sys_clock_high_res(); - FF_INIT_PCK(ctx, pkt) - pkt->side_data = NULL; - pkt->side_data_elems = 0; + if (ctx->src_as_avf) { + // Request a packet from the callback + if (!ctx->on_pkt) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("%s No callback set for packet retrieval\n", ctx->fname)); + return GF_BAD_PARAM; + } + + // Receive a packet (if possible) + res = avf_ret = ctx->on_pkt(ctx->rt_udta, &pkt); + if (pkt == NULL && avf_ret == GF_FFDMX_OK) + return GF_OK; + } else { + FF_INIT_PCK(ctx, pkt) + pkt->side_data = NULL; + pkt->side_data_elems = 0; - pkt->stream_index = -1; + pkt->stream_index = -1; + res = av_read_frame(ctx->demuxer, pkt); + } /*EOF*/ - res = av_read_frame(ctx->demuxer, pkt); - if (res < 0) { + if (res < 0 || avf_ret == GF_FFDMX_EOS) { if (!ctx->in_eos && (ctx->strbuf_size>ctx->strbuf_offset) && (res == AVERROR(EAGAIN))) return GF_OK; - FF_FREE_PCK(pkt); + if (!ctx->src_as_avf) + FF_FREE_PCK(pkt); + if (!ctx->raw_data) { for (i=0; i<ctx->nb_streams; i++) { - if (ctx->pids_ctxi.pid) gf_filter_pid_set_eos(ctx->pids_ctxi.pid); + PidCtx *pctx = &ctx->pids_ctxi; + if (!pctx->pid) continue; + + if (pctx->pck_queue) { + while (gf_list_count(pctx->pck_queue)) { + GF_FilterPacket *pck_q = gf_list_pop_front(pctx->pck_queue); + gf_filter_pck_send(pck_q); + } + gf_list_del(pctx->pck_queue); + pctx->pck_queue = NULL; + } + gf_filter_pid_set_eos(ctx->pids_ctxi.pid); } return GF_EOS; } @@ -549,7 +669,7 @@ } if (ctx->stop_seen && ! gf_filter_pid_is_playing( pctx->pid ) ) { FF_FREE_PCK(pkt); - return GF_OK; + return GF_OK; } if (ctx->raw_data && (ctx->probe_frames<ctx->probes) ) { if (pkt->stream_index==ctx->audio_idx) { @@ -707,12 +827,15 @@ ts = (pctx->fake_dts_plus_one-1 - pctx->fake_dts_orig + pkt->dts + pctx->ts_offset-1) * stream->time_base.num; gf_filter_pck_set_dts(pck_dst, ts); if (!pctx->fake_dts_set) { + //this is NOT a PID delay, CTS=0 means 0, we simply dispatch in negctts mode +#if 0 if (pctx->fake_dts_plus_one) { s64 offset = pctx->fake_dts_plus_one-1; offset -= pctx->fake_dts_orig; if (offset) gf_filter_pid_set_property(pctx->pid, GF_PROP_PID_DELAY, &PROP_LONGSINT( -offset) ); } +#endif pctx->fake_dts_set = GF_TRUE; if (pctx->pck_queue) { while (gf_list_count(pctx->pck_queue)) { @@ -793,6 +916,12 @@ goto restart; } + // we might have more packets from the avf source + if (ctx->src_as_avf && ctx->on_pkt && ctx->on_pkt(ctx->rt_udta, NULL) == GF_FFDMX_HAS_MORE) { + // we got a packet, restart to process it + goto restart; + } + //we don't demux an input, only rely on session to schedule the filter return GF_OK; } @@ -820,6 +949,7 @@ GF_VVCConfig *vvcc; GF_AV1Config *av1c; GF_VPConfig *vpxc; + GF_AVS3VConfig *av3c; if (!dsi_size) dsi = NULL; @@ -827,7 +957,7 @@ //force reframer for the following formats if no DSI is found case GF_CODECID_AC3: case GF_CODECID_EAC3: - if (dsi && (gf_odf_ac3_config_parse(dsi, dsi_size, (gpac_codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3) == GF_OK)) + if (dsi && (gf_odf_ac3_cfg_parse(dsi, dsi_size, (gpac_codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3) == GF_OK)) return 0; return 1; @@ -878,6 +1008,13 @@ return 0; } return 1; + case GF_CODECID_AVS3_AUDIO: + av3c = dsi ? gf_odf_avs3v_cfg_read(dsi, dsi_size) : NULL; + if (av3c) { + gf_odf_avs3v_cfg_del(av3c); + return 0; + } + return 1; //force reframer for the following formats regardless of DSI and drop it case GF_CODECID_MPEG1: case GF_CODECID_MPEG2_422: @@ -1255,9 +1392,15 @@ } gf_filter_pid_set_property(pid, GF_PROP_PID_MUX_INDEX, &PROP_UINT(i+1)); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) for (j=0; j<(u32) stream->nb_side_data; j++) { ffdmx_parse_side_data(&stream->side_dataj, pid); } +#else + for (j=0; j<(u32) stream->codecpar->nb_coded_side_data; j++) { + ffdmx_parse_side_data(&stream->codecpar->coded_side_dataj, pid); + } +#endif if (ctx->demuxer->nb_chapters) { GF_PropertyValue p; @@ -1282,7 +1425,7 @@ names.valsj = gf_strdup(ent->value); } } - if (!names.valsj) names.valsj = gf_strdup("Unknwon"); + if (!names.valsj) names.valsj = gf_strdup("Unknown"); } p.type = GF_PROP_UINT_LIST; p.value.uint_list = times; @@ -1360,8 +1503,35 @@ GF_LOG(GF_LOG_DEBUG, ctx->log_class, ("%s opening file %s - av_in %08x\n", ctx->fname, ctx->src, av_in)); - ctx->demuxer = avformat_alloc_context(); - ffmpeg_set_mx_dmx_flags(ctx->options, ctx->demuxer); + if (ctx->proto) { + //special mode: open protocol and bypass demuxer + AVDictionary *opts = NULL; + int ret = avio_open2(&ctx->avio_ctx, ctx->src, AVIO_FLAG_READ|AVIO_FLAG_NONBLOCK|AVIO_FLAG_DIRECT, NULL, &opts); + av_dict_free(&opts); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Failed to open protocol URL %s, cannot run\n", ctx->src)); + return GF_SERVICE_ERROR; + } + return GF_OK; + } + + if (!strncmp(ctx->src, "avf://", 6)) { + // We'll use the AVFormatContext* inside ctx->src + ctx->demuxer = (AVFormatContext *)(uintptr_t) strtoull(ctx->src + 6, NULL, 16); + if (!ctx->demuxer) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("%s Invalid AVFormatContext pointer %s\n", ctx->fname, ctx->src)); + return GF_URL_ERROR; + } + if (ctx->demuxer->av_class->version != LIBAVUTIL_VERSION_INT) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("%s AVFormatContext pointer %s is not the same version as the current libavutil: compiled %08x, running %08x\n", + ctx->fname, ctx->src, LIBAVUTIL_VERSION_INT, ctx->demuxer->av_class->version)); + return GF_NOT_SUPPORTED; + } + ctx->src_as_avf = GF_TRUE; + } else { + ctx->demuxer = avformat_alloc_context(); + ffmpeg_set_mx_dmx_flags(ctx->options, ctx->demuxer); + } url = ctx->src; if (!strncmp(ctx->src, "gfio://", 7)) { @@ -1386,9 +1556,13 @@ } AVDictionary *options = NULL; - av_dict_copy(&options, ctx->options, 0); - - res = avformat_open_input(&ctx->demuxer, url, FF_IFMT_CAST av_in, &options); + if (!ctx->src_as_avf) { + av_dict_copy(&options, ctx->options, 0); + res = avformat_open_input(&ctx->demuxer, url, FF_IFMT_CAST av_in, &options); + } else { + // The format is already open + goto finish; + } switch (res) { case 0: @@ -1470,6 +1644,8 @@ if (options) av_dict_free(&options); return e; } + +finish: GF_LOG(GF_LOG_DEBUG, ctx->log_class, ("%s file %s opened - %d streams\n", ctx->fname, ctx->src, ctx->demuxer->nb_streams)); ffmpeg_report_options(filter, options, ctx->options); @@ -1672,6 +1848,7 @@ if (!strncmp(url, "audio://", 8)) return GF_FPROBE_NOT_SUPPORTED; if (!strncmp(url, "av://", 5)) return GF_FPROBE_NOT_SUPPORTED; if (!strncmp(url, "pipe://", 7)) return GF_FPROBE_NOT_SUPPORTED; + if (!strncmp(url, "avf://", 6)) return GF_FPROBE_SUPPORTED; const char *ext = gf_file_ext_start(url); if (ext) { @@ -1739,19 +1916,29 @@ CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), CAP_BOOL(GF_CAPS_OUTPUT,GF_PROP_PID_FORCE_UNFRAME, GF_TRUE), CAP_BOOL(GF_CAPS_OUTPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + //for raw protocol access + CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), }; GF_FilterRegister FFDemuxRegister = { .name = "ffdmx", .version=LIBAVFORMAT_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG demultiplexer") - GF_FS_SET_HELP("This filter demultiplexes an input file or open a source protocol using FFMPEG.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details.\n" + GF_FS_SET_DESCRIPTION("FFmpeg demultiplexer") + GF_FS_SET_HELP("This filter demultiplexes an input file or open a source protocol using FFmpeg.\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details.\n" "To list all supported demultiplexers for your GPAC build, use `gpac -h ffdmx:*`.\n" "This will list both supported input formats and protocols.\n" "Input protocols are listed with `Description: Input protocol`, and the subclass name identifies the protocol scheme.\n" "For example, if `ffdmx:rtmp` is listed as input protocol, this means `rtmp://` source URLs are supported.\n" + "\n" + "# Raw protocol mode\n" + "The -proto() flag will disable FFmpeg demuxer and use GPAC instead. Default format is probed from initial data but can be set using -ext() or -mime() if probing is disabled.\n" + "EX gpac -i srt://127.0.0.1:1234:gpac:proto inspect" + "This will use the SRT protocol handler but GPAC demultiplexer\n" + "\n" + "In this mode, the filter uses the time between the last two received packets to estimates how often it should check for inputs. The maximum and minimum times to wait between two calls is given by the -mwait() option. The maximum time may need to be reduced for very high bitrates sources.\n" ) .private_size = sizeof(GF_FFDemuxCtx), SETCAPS(FFDmxCaps), @@ -1763,8 +1950,8 @@ .probe_data = ffdmx_probe_data, .process_event = ffdmx_process_event, .flags = GF_FS_REG_META | GF_FS_REG_USE_SYNC_READ, - .priority = 128 - + .priority = 128, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; @@ -1774,6 +1961,11 @@ { OFFS(reparse), "force reparsing of stream content (AVC,HEVC,VVC,AV1 only for now)", GF_PROP_BOOL, "false", NULL, 0}, { OFFS(block_size), "block size used to read file when using GFIO context", GF_PROP_UINT, "4096", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(strbuf_min), "internal buffer size when demuxing from GPAC's input stream", GF_PROP_UINT, "1MB", NULL, GF_ARG_HINT_EXPERT}, + { OFFS(proto), "use protocol handler only and bypass FFmpeg demuxer", GF_PROP_BOOL, "false", NULL, GF_ARG_HINT_ADVANCED}, + { OFFS(mwait), "set min and max wait times in ms to avoid too frequent polling in proto mode", GF_PROP_VEC2I, "1x30", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ext), "indicate file extension of data in raw protocol mode", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mime), "indicate mime type of data in raw protocol mode", GF_PROP_STRING, NULL, NULL, 0}, + { "*", -1, "any possible options defined for AVFormatContext and sub-classes. See `gpac -hx ffdmx` and `gpac -hx ffdmx:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, {0} }; @@ -1786,6 +1978,23 @@ return ffmpeg_build_register(session, &FFDemuxRegister, FFDemuxArgs, FFDMX_STATIC_ARGS, FF_REG_TYPE_DEMUX); } +GF_EXPORT +GF_Err gf_filter_bind_ffdmx_callbacks(GF_Filter *filter, void *udta, GF_FFDemuxCallbackFn on_pkt) +{ + if (!gf_filter_is_instance_of(filter, &FFDemuxRegister)) + return GF_BAD_PARAM; + GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx*) gf_filter_get_udta(filter); + + if (on_pkt) { + ctx->on_pkt = on_pkt; + ctx->rt_udta = udta; + } else { + ctx->on_pkt = NULL; + ctx->rt_udta = udta; + } + return GF_OK; +} + //we define a dedicated registry for demuxing a GPAC pid using ffmpeg, not doing so can create wrong link resolutions //disabling GPAC demuxers static const GF_FilterCapability FFPidDmxCaps = @@ -1830,7 +2039,7 @@ const GF_FilterRegister FFDemuxPidRegister = { .name = "ffdmxpid", .version=LIBAVFORMAT_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG demultiplexer") + GF_FS_SET_DESCRIPTION("FFmpeg demultiplexer") GF_FS_SET_HELP("Alias of ffdmx for GPAC pid demultiplexing, same options as ffdmx.\n") .private_size = sizeof(GF_FFDemuxCtx), SETCAPS(FFPidDmxCaps), @@ -1842,6 +2051,7 @@ .process_event = ffdmx_process_event, .flags = GF_FS_REG_META, .args = FFDemuxPidArgs, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER, //also set lower priority .priority = 128 }; @@ -1901,7 +2111,7 @@ dev_fmt = NULL; } #else - //not supported for old FFMPEG versions + //not supported for old FFmpeg versions #endif } #if (LIBAVCODEC_VERSION_MAJOR >= 58) && (LIBAVCODEC_VERSION_MINOR>=20) @@ -2133,9 +2343,9 @@ GF_FilterRegister FFAVInRegister = { .name = "ffavin", .version = LIBAVDEVICE_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG AV Capture") - GF_FS_SET_HELP("Reads from audio/video capture devices using FFMPEG.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details.\n" + GF_FS_SET_DESCRIPTION("FFmpeg AV capture") + GF_FS_SET_HELP("Reads from audio/video capture devices using FFmpeg.\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details.\n" "To list all supported grabbers for your GPAC build, use `gpac -h ffavin:*`.\n" "\n" "# Device identification\n" @@ -2166,6 +2376,7 @@ .probe_url = ffavin_probe_url, .process_event = ffdmx_process_event, .flags = GF_FS_REG_META, + .hint_class_type = GF_FS_CLASS_MM_IO }; @@ -2201,51 +2412,43 @@ static void ffavin_enum_devices(const char *dev_name, Bool is_audio) { const AVInputFormat *fmt; - AVFormatContext *ctx; - if (!dev_name) return; - fmt = av_find_input_format(dev_name); + if (!dev_name) return; + fmt = (const AVInputFormat *) av_find_input_format(dev_name); if (!fmt) return; if (!fmt || !fmt->priv_class || !AV_IS_INPUT_DEVICE(fmt->priv_class->category)) { return; } - ctx = avformat_alloc_context(); - if (!ctx) return; - ctx->iformat = (AVInputFormat *)fmt; - if (ctx->iformat->priv_data_size > 0) { - ctx->priv_data = av_mallocz(ctx->iformat->priv_data_size); - if (!ctx->priv_data) { - avformat_free_context(ctx); - return; - } - if (ctx->iformat->priv_class) { - *(const AVClass**)ctx->priv_data = ctx->iformat->priv_class; - av_opt_set_defaults(ctx->priv_data); - } - } else { - ctx->priv_data = NULL; - } AVDeviceInfoList *dev_list = NULL; - - AVDictionary *tmp = NULL; - av_dict_set(&tmp, "list_devices", "1", 0); - av_opt_set_dict2(ctx, &tmp, AV_OPT_SEARCH_CHILDREN); - if (tmp) - av_dict_free(&tmp); - - int res = avdevice_list_devices(ctx, &dev_list); +#if LIBAVDEVICE_VERSION_MAJOR<59 + int res = avdevice_list_input_sources((AVInputFormat *)fmt, dev_name, NULL, &dev_list); +#else + int res = avdevice_list_input_sources(fmt, dev_name, NULL, &dev_list); +#endif if (res<0) { //device doesn't implement avdevice_list_devices, try loading the context using "list_devices=1" option if (-res == ENOSYS) { + AVFormatContext *ctx = avformat_alloc_context(); + if (!ctx) return; + AVDictionary *opts = NULL; av_dict_set(&opts, "list_devices", "1", 0); res = avformat_open_input(&ctx, "dummy", FF_IFMT_CAST fmt, &opts); if (opts) av_dict_free(&opts); + +#if !defined(__DARWIN__) && !defined(__APPLE__) + // FIXME: no-op, permission issues on macOS Sonoma+ + if (res>=0) avdevice_list_devices(ctx, &dev_list); +#endif + + if (res>=0) avformat_close_input(&ctx); + avformat_free_context(ctx); } - } else if (!res && dev_list->nb_devices) { + } + if (!res && dev_list && dev_list->nb_devices) { if (!dev_desc) { gf_dynstrcat(&dev_desc, "# Detected devices\n", NULL); } @@ -2262,7 +2465,6 @@ } if (dev_list) avdevice_free_list_devices(&dev_list); - avformat_free_context(ctx); } static void ffavin_log_none(void *avcl, int level, const char *fmt, va_list vl)
View file
gpac-2.4.0.tar.gz/src/filters/ff_enc.c -> gpac-26.02.0.tar.gz/src/filters/ff_enc.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / ffmpeg encode filter @@ -47,6 +47,7 @@ char *c; Bool ls, rld; u32 pfmt; + s32 round; GF_Fraction fintra; Bool rc; @@ -66,8 +67,9 @@ u32 nb_frames_out, nb_frames_in; u64 time_spent; + u64 orig_cts_plus_one; - Bool low_delay; + u32 low_delay_mode; GF_Err (*process)(GF_Filter *filter, struct _gf_ffenc_ctx *ctx); //gpac one @@ -76,6 +78,8 @@ u32 flush_done; //frame used by both video and audio encoder AVFrame *frame; + //ffmpeg codecid forced by output + u32 forced_ffcid; //encoding buffer - we allocate ENC_BUF_ALLOC_SAFE+WxH for the video (some image codecs in ffmpeg require more than WxH for headers), ENC_BUF_ALLOC_SAFE+nb_ch*samplerate for the audio //this should be enough to hold any lossless compression formats @@ -112,6 +116,7 @@ GF_BitStream *sdbs; + GF_FilterPacket *reconfig_from_pck; Bool reconfig_pending; Bool infmt_negotiate; Bool remap_ts; @@ -193,6 +198,7 @@ if (!ctx->c) return GF_OK; + ctx->forced_ffcid = AV_CODEC_ID_NONE; //first look by name, to handle cases such as "aac" vs "vo_aacenc" ctx->force_codec = avcodec_find_encoder_by_name(ctx->c); if (ctx->force_codec) { @@ -264,13 +270,19 @@ } } else { gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(ctx->codecid) ); + if (ctx->codecid==GF_CODECID_FFMPEG) { + if (ctx->encoder) + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_META_DEMUX_CODEC_ID, &PROP_UINT(ctx->encoder->codec->id) ); + else if (ctx->forced_ffcid) + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_META_DEMUX_CODEC_ID, &PROP_UINT(ctx->forced_ffcid) ); + } } gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_ISOM_SUBTYPE, NULL); gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_PROFILE_LEVEL, NULL); ctx->gen_dsi = GF_FALSE; switch (ctx->codecid) { - //reframe all these codecs for proper ISOBMFF+DSI formating + //reframe all these codecs for proper ISOBMFF+DSI formatting case GF_CODECID_AVC: case GF_CODECID_HEVC: case GF_CODECID_VVC: @@ -334,6 +346,17 @@ } +static GFINLINE void ffenc_set_deps(GF_FilterPacket *dst_pck, AVPacket *pkt) +{ + //reset dependency flags to unknown + u8 flags = 0; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (pkt->flags & AV_PKT_FLAG_DISPOSABLE) { + flags = 0x8; + } +#endif + gf_filter_pck_set_dependency_flags(dst_pck, flags); +} static u64 ffenc_get_cts(GF_FFEncodeCtx *ctx, GF_FilterPacket *pck) { @@ -496,9 +519,25 @@ force_intra = 2; } + //don't repeat encoder reconfiguration if we already forced one + if (force_intra == 2) { + if (ctx->reconfig_from_pck == pck) { + force_intra = 1; + ctx->reconfig_from_pck = NULL; + } else { + ctx->reconfig_from_pck = pck; + } + } + //check if we need to force a closed gop if (pck && (ctx->fintra.den && (ctx->fintra.num>0)) && !ctx->force_reconfig) { u64 cts = ffenc_get_cts(ctx, pck); + //if we have a first encoded frame, use it as our anchor point and reset + //we only do this on first setup + if (ctx->orig_cts_plus_one) { + cts = ctx->orig_cts_plus_one - 1; + ctx->orig_cts_plus_one = 0; + } if (!ctx->fintra_setup) { ctx->fintra_setup = GF_TRUE; ctx->orig_ts = cts; @@ -518,8 +557,13 @@ } } } - if (!ctx->nb_frames_in) + + if (!ctx->nb_frames_in) { force_intra = 0; + //remember timing of first encoded frame + if (pck) + ctx->orig_cts_plus_one = ffenc_get_cts(ctx, pck) + 1; + } if (force_intra) { if (ctx->args_updated) { @@ -588,12 +632,25 @@ } if (pck) { ilaced = gf_filter_pck_get_interlaced(pck); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) if (!ilaced) { ctx->frame->interlaced_frame = 0; } else { ctx->frame->interlaced_frame = 1; ctx->frame->top_field_first = (ilaced==2) ? 1 : 0; } +#else + if (!ilaced) { + ctx->frame->flags &= ~AV_FRAME_FLAG_INTERLACED; + } else { + ctx->frame->flags |= AV_FRAME_FLAG_INTERLACED; + if (ilaced==2) { + ctx->frame->flags |= AV_FRAME_FLAG_TOP_FIELD_FIRST; + } else { + ctx->frame->flags &= ~AV_FRAME_FLAG_TOP_FIELD_FIRST; + } + } +#endif ctx->frame->pts = ffenc_get_cts(ctx, pck); ctx->frame->_avf_dur = gf_filter_pck_get_duration(pck); } @@ -706,7 +763,7 @@ ctx->reconfig_pending = GF_FALSE; ctx->force_reconfig = GF_FALSE; GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("FFEnc codec flush done, triggering reconfiguration\n")); - avcodec_close(ctx->encoder); + avcodec_free_context(&ctx->encoder); ctx->encoder = NULL; ctx->setup_failed = 0; e = ffenc_configure_pid_ex(filter, ctx->in_pid, GF_FALSE, GF_TRUE); @@ -739,15 +796,34 @@ return GF_OK; } + src_pck = NULL; + count = gf_list_count(ctx->src_packets); + for (i=0; i<count; i++) { + src_pck = gf_list_get(ctx->src_packets, i); + u64 cts = ffenc_get_cts(ctx, src_pck); + if (ctx->remap_ts) { + SCALE_TS(cts); + UNSCALE_TS(cts); + } + if (cts == pkt->pts) + break; + src_pck = NULL; + } + ctx->nb_frames_out++; if (ctx->init_cts_setup) { ctx->init_cts_setup = GF_FALSE; if (ctx->frame->pts != pkt->pts) { - //check shift in PTS - most of the time this is 0 (ffmpeg does not restamp video pts) - ctx->ts_shift = (s64) ctx->cts_first_frame_plus_one - 1 - (s64) pkt->pts; + //first frame out is not first frame in (SAP 2) + if (src_pck && (gf_list_find(ctx->src_packets, src_pck)>0) && (pkt->dts<0)) { + ctx->ts_shift = - (s64) pkt->dts; + } else { + //check shift in PTS - most of the time this is 0 (ffmpeg does not restamp video pts) + ctx->ts_shift = (s64) ctx->cts_first_frame_plus_one - 1 - (s64) pkt->pts; - //check shift in DTS - ctx->ts_shift += (s64) ctx->cts_first_frame_plus_one - 1 - (s64) pkt->dts; + //check shift in DTS + ctx->ts_shift += (s64) ctx->cts_first_frame_plus_one - 1 - (s64) pkt->dts; + } } //if ts_shift>0, this means we have a skip @@ -758,20 +834,6 @@ } } - src_pck = NULL; - count = gf_list_count(ctx->src_packets); - for (i=0; i<count; i++) { - src_pck = gf_list_get(ctx->src_packets, i); - u64 cts = ffenc_get_cts(ctx, src_pck); - if (ctx->remap_ts) { - SCALE_TS(cts); - UNSCALE_TS(cts); - } - if (cts == pkt->pts) - break; - src_pck = NULL; - } - offset = 0; to_copy = size = pkt->size; @@ -889,16 +951,13 @@ //since we send the output to our reframers we should be fine if (pkt->flags & AV_PKT_FLAG_KEY) { gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("FFEnc frame %d is SAP\n", ctx->nb_frames_out)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("FFEnc frame %d is SAP\n", ctx->nb_frames_out-1)); } else gf_filter_pck_set_sap(dst_pck, 0); -#if LIBAVCODEC_VERSION_MAJOR >= 58 - if (pkt->flags & AV_PKT_FLAG_DISPOSABLE) { - gf_filter_pck_set_dependency_flags(dst_pck, 0x8); - } -#endif + ffenc_set_deps(dst_pck, pkt); + gf_filter_pck_send(dst_pck); av_packet_free_side_data(pkt); @@ -1335,6 +1394,7 @@ gf_filter_pck_set_sap(dst_pck, 0); gf_filter_pck_set_duration(dst_pck, (u32) pkt->duration); + ffenc_set_deps(dst_pck, pkt); gf_filter_pck_send(dst_pck); @@ -1400,6 +1460,11 @@ prop = gf_filter_pid_caps_query(pid, GF_PROP_PID_CODECID); if (prop) { ctx->codecid = prop->value.uint; + if (ctx->codecid==GF_CODECID_FFMPEG) { + prop = gf_filter_pid_caps_query(pid, GF_PROP_PID_META_DEMUX_CODEC_ID); + if (!prop) return GF_NOT_SUPPORTED; + ctx->forced_ffcid = prop->value.uint; + } } else if (!ctx->codecid && ctx->c) { ctx->codecid = gf_codecid_parse(ctx->c); if (!ctx->codecid) { @@ -1414,6 +1479,13 @@ } if (!ctx->codecid && !desired_codec) { + if (gf_filter_is_dynamic(filter)) { + ctx->in_pid = pid; + if (!ctx->out_pid) { + ctx->out_pid = gf_filter_pid_new(filter); + } + return GF_OK; + } GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("FFEnc No codecid specified\n" )); return GF_BAD_PARAM; } @@ -1432,7 +1504,10 @@ } if (ctx->codecid) { - codec_id = ffmpeg_codecid_from_gpac(ctx->codecid, &ff_codectag); + if (ctx->forced_ffcid) + codec_id = ctx->forced_ffcid; + else + codec_id = ffmpeg_codecid_from_gpac(ctx->codecid, &ff_codectag); if (codec_id) { if (desired_codec && desired_codec->id==codec_id) codec = desired_codec; @@ -1664,6 +1739,26 @@ ctx->infmt_negotiate = GF_TRUE; } else { ctx->infmt_negotiate = GF_FALSE; + u32 downsample_w=0, downsample_h=0; + if ((ctx->round==1) || (ctx->round==-1)) + gf_pixel_get_downsampling(ffmpeg_pixfmt_to_gpac(ctx->pixel_fmt, GF_FALSE), &downsample_w, &downsample_h); + else if (ctx->round>0) + downsample_h = downsample_w = ctx->round; + else if (ctx->round<0) + downsample_h = downsample_w = (u32) -ctx->round; + + if (downsample_w && (ctx->width % downsample_w)) { + u32 w = (ctx->width/downsample_w) * downsample_w; + if (ctx->round>0) w+=downsample_w; + gf_filter_pid_negotiate_property(ctx->in_pid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + ctx->infmt_negotiate = GF_TRUE; + } + if (downsample_h && (ctx->height % downsample_h)) { + u32 h = (ctx->height/downsample_h) * downsample_h; + if (ctx->round>0) h+=downsample_h; + gf_filter_pid_negotiate_property(ctx->in_pid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); + ctx->infmt_negotiate = GF_TRUE; + } } } else { u32 change_input_sr = 0; @@ -1779,7 +1874,7 @@ ctx->encoder->width = ctx->width; ctx->encoder->height = ctx->height; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (prop) { + if (prop && (prop->value.frac.num>0)) { ctx->encoder->sample_aspect_ratio.num = prop->value.frac.num; ctx->encoder->sample_aspect_ratio.den = prop->value.frac.den; } else { @@ -1845,12 +1940,16 @@ } } - if (ctx->low_delay) { + if (ctx->low_delay_mode==1) { av_dict_set(&ctx->options, "profile", "baseline", 0); av_dict_set(&ctx->options, "preset", "ultrafast", 0); av_dict_set(&ctx->options, "tune", "zerolatency", 0); if (ctx->codecid==GF_CODECID_AVC) { - av_dict_set(&ctx->options, "x264opts", "no-mbtree:sliced-threads:sync-lookahead=0", 0); + if (av_opt_find((void*)&codec->priv_class, "x264-params", NULL, 0, 0) != NULL) { + av_dict_set(&ctx->options, "x264-params", "no-mbtree=1:sliced-threads=1:sync-lookahead=0", 0); + } else { + av_dict_set(&ctx->options, "x264opts", "no-mbtree:sliced-threads:sync-lookahead=0", 0); + } } #if LIBAVCODEC_VERSION_MAJOR >= 58 ctx->encoder->flags |= AV_CODEC_FLAG_LOW_DELAY; @@ -2101,15 +2200,26 @@ if (!strcmp(arg_name, "global_header")) return GF_OK; else if (!strcmp(arg_name, "local_header")) return GF_OK; - else if (!strcmp(arg_name, "low_delay")) ctx->low_delay = GF_TRUE; + //activate opts for low delay + else if (!strcmp(arg_name, "flags") + && arg_val && arg_val->value.string && strstr(arg_val->value.string, "low_delay") + && !ctx->low_delay_mode + ) + ctx->low_delay_mode = 1; + //activate opts for low delay + else if (!strcmp(arg_name, "low_delay")) { + ctx->low_delay_mode = 1; + gf_filter_report_meta_option(filter, "low_delay", 1, NULL); + } //remap some options else if (!strcmp(arg_name, "bitrate") || !strcmp(arg_name, "rate")) arg_name = "b"; // else if (!strcmp(arg_name, "gop")) arg_name = "g"; //disable low delay if these options are set - else if (!strcmp(arg_name, "x264opts")) ctx->low_delay = GF_FALSE; - else if (!strcmp(arg_name, "profile")) ctx->low_delay = GF_FALSE; - else if (!strcmp(arg_name, "preset")) ctx->low_delay = GF_FALSE; - else if (!strcmp(arg_name, "tune")) ctx->low_delay = GF_FALSE; + else if (!strcmp(arg_name, "x264opts")) ctx->low_delay_mode = 2; + else if (!strcmp(arg_name, "x264-params")) ctx->low_delay_mode = 2; + else if (!strcmp(arg_name, "profile")) ctx->low_delay_mode = 2; + else if (!strcmp(arg_name, "preset")) ctx->low_delay_mode = 2; + else if (!strcmp(arg_name, "tune")) ctx->low_delay_mode = 2; if (!strcmp(arg_name, "g") || !strcmp(arg_name, "gop")) ctx->gop_size = arg_val->value.string ? atoi(arg_val->value.string) : 25; @@ -2175,50 +2285,90 @@ static Bool ffenc_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { GF_FFEncodeCtx *ctx = gf_filter_get_udta(filter); - if (evt->base.type==GF_FEVT_ENCODE_HINTS) { - if (evt->encode_hints.gen_dsi_only) { + if (evt->base.type==GF_FEVT_TRANSPORT_HINTS) { + if (evt->transport_hints.flags & GF_TRANSPORT_HINTS_SAW_ENCODER) { + // this is a pass-through event, ignore it + return GF_FALSE; + } + + if (evt->transport_hints.gen_dsi_only) { ctx->generate_dsi_only = GF_TRUE; } - else if ((ctx->fintra.num<0) && evt->encode_hints.intra_period.den && evt->encode_hints.intra_period.num) { - ctx->fintra = evt->encode_hints.intra_period; + //change in fintra + else if (ctx->fintra.num * evt->transport_hints.seg_duration.den != ctx->fintra.den * evt->transport_hints.seg_duration.num) { + ctx->fintra = evt->transport_hints.seg_duration; + ctx->fintra_setup = GF_FALSE; if (!ctx->rc || (gf_list_count(ctx->src_packets) && !ctx->force_reconfig)) { ctx->reconfig_pending = GF_TRUE; ctx->force_reconfig = GF_TRUE; } } + + //send the event upstream (in case any other filter is interested in it) + GF_FilterEvent new_evt = *evt; + new_evt.base.on_pid = ctx->in_pid; + new_evt.transport_hints.flags |= GF_TRANSPORT_HINTS_SAW_ENCODER; + gf_filter_pid_send_event(ctx->in_pid, &new_evt); return GF_TRUE; } + else if (evt->base.type==GF_FEVT_STOP) { + ctx->nb_frames_in = ctx->nb_frames_out = 0; + } return GF_FALSE; } +static GF_Err ffenc_reconfigure_output(GF_Filter *filter, GF_FilterPid *pid) +{ + const GF_PropertyValue *p; + GF_FFEncodeCtx *ctx = gf_filter_get_udta(filter); + if (ctx->out_pid != pid) return GF_BAD_PARAM; + + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_CODECID); + if (p) ctx->codecid = p->value.uint; + + ctx->forced_ffcid = AV_CODEC_ID_NONE; + if (ctx->codecid==GF_CODECID_FFMPEG) { + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_META_DEMUX_CODEC_ID); + if (p) ctx->forced_ffcid = p->value.uint; + } + return ffenc_configure_pid_ex(filter, ctx->in_pid, GF_FALSE, GF_FALSE); +} + static const GF_FilterCapability FFEncodeCaps = { - CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_TILE_BASE, GF_TRUE), //some video encoding dumps in unframe mode, we declare the pid property at runtime {0}, - CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_CODECID, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_META_DEMUX_CODEC_ID, 0), }; GF_FilterRegister FFEncodeRegister = { .name = "ffenc", .version=LIBAVCODEC_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG encoder") - GF_FS_SET_HELP("This filter encodes audio and video streams using FFMPEG.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details.\n" + GF_FS_SET_DESCRIPTION("FFmpeg encoder") + GF_FS_SET_HELP("This filter encodes audio and video streams using FFmpeg.\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details.\n" "To list all supported encoders for your GPAC build, use `gpac -h ffenc:*`.\n" "\n" "The filter will try to resolve the codec name in -c() against a libavcodec codec name (e.g. `libx264`) and use it if found.\n" "If not found, it will consider the name to be a GPAC codec name and find a codec for it. In that case, if no pixel format is given, codecs will be enumerated to find a matching pixel format.\n" "\n" "Options can be passed from prompt using `--OPT=VAL` (global options) or appending `::OPT=VAL` to the desired encoder filter.\n" + "Encoder flags can be passed directly as `:FLAGNAME`.\n" + "\n" + "Note\n" + "Setting the `:low_delay` flag will set by default `profile=baseline`, `preset=ultrafast` and `tune=zerolatency` options as well as `x264-params` for AVC|H264. If one or more of these options are set as filter arguments, no defaulting is used for all these options.\n" "\n" "The filter will look for property `TargetRate` on input PID to set the desired bitrate per PID.\n" "\n" @@ -2243,9 +2393,11 @@ .process = ffenc_process, .process_event = ffenc_process_event, .update_arg = ffenc_update_arg, + .reconfigure_output = ffenc_reconfigure_output, .flags = GF_FS_REG_META | GF_FS_REG_TEMP_INIT | GF_FS_REG_BLOCK_MAIN, //use middle priority in case we have other encoders - .priority = 128 + .priority = 128, + .hint_class_type = GF_FS_CLASS_ENCODER }; #define OFFS(_n) #_n, offsetof(GF_FFEncodeCtx, _n) @@ -2259,6 +2411,12 @@ { OFFS(ls), "log stats", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(rc), "reset encoder when forcing intra frame (some encoders might not support intra frame forcing)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(rld), "force reloading of encoder when arguments are updated", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, + { OFFS(round), "round video up or down\n" + "- 0: no rounding\n" + "- 1: round up to match codec YUF format requirements\n" + "- -1: round down to match codec YUF format requirements\n" + "- other: round to lower (negative value) or higher (positive value), for example CTU size" + , GF_PROP_SINT, "1", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, { "*", -1, "any possible options defined for AVCodecContext and sub-classes. see `gpac -hx ffenc` and `gpac -hx ffenc:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, {0}
View file
gpac-2.4.0.tar.gz/src/filters/ff_mx.c -> gpac-26.02.0.tar.gz/src/filters/ff_mx.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom Paris 2019-2023 + * Copyright (c) Telecom Paris 2019-2025 * All rights reserved * * This file is part of GPAC / ffmpeg muxer filter @@ -81,7 +81,8 @@ char *dst, *mime, *ffmt, *ext; Double start, speed; u32 block_size; - Bool nodisc, ffiles, noinit, keepts; + Bool nodisc, ffiles, noinit, keepts, proto; + u32 psleep; GF_Fraction ileave; AVFormatContext *muxer; @@ -112,6 +113,10 @@ #else AVPacket *pkt; #endif + + //for protocol-only caps override + GF_FilterCapability proto_caps3; + u32 pck_offset; } GF_FFMuxCtx; static GF_Err ffmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); @@ -247,7 +252,7 @@ } -#if (LIBAVFORMAT_VERSION_MAJOR <= 59) +#if (LIBAVFORMAT_VERSION_MAJOR < 61) static int ffavio_write_packet(void *opaque, uint8_t *buf, int buf_size) #else static int ffavio_write_packet(void *opaque, const uint8_t *buf, int buf_size) @@ -351,6 +356,43 @@ if (sep && strchr(sep+1, '$')) use_templates = GF_TRUE; + if (ctx->proto) { + //special mode: open protocol and bypass muxer + AVDictionary *opts = NULL; + int ret = avio_open2(&ctx->avio_ctx, url, AVIO_FLAG_WRITE|AVIO_FLAG_NONBLOCK|AVIO_FLAG_DIRECT, NULL, &opts); + av_dict_free(&opts); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Failed to open protocol URL %s, cannot run\n", url)); + return GF_SERVICE_ERROR; + } + + u32 cap_idx=1; + ctx->proto_caps0.code = GF_PROP_PID_STREAM_TYPE; + ctx->proto_caps0.val = PROP_UINT(GF_STREAM_FILE); + ctx->proto_caps0.flags = GF_CAPS_INPUT_STATIC; + if (ctx->mime) { + ctx->proto_caps1.code = GF_PROP_PID_MIME; + ctx->proto_caps1.val = PROP_STRING(ctx->mime); + ctx->proto_caps1.flags = GF_CAPS_INPUT_STATIC; + cap_idx++; + } + const char *fmt = ctx->ext; + if (!fmt && !ctx->mime) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("FFMux Format not specified, defaulting to MPEG-2 TS - try setting `ext=` to set GPAC mux format\n", ctx->dst)); + fmt = "ts"; + } + if (fmt) { + ctx->proto_capscap_idx.code = GF_PROP_PID_FILE_EXT; + ctx->proto_capscap_idx.val = PROP_STRING(fmt); + ctx->proto_capscap_idx.flags = GF_CAPS_INPUT_STATIC; + cap_idx++; + } + + gf_filter_override_caps(filter, ctx->proto_caps, cap_idx); + gf_filter_set_max_extra_input_pids(filter, 0); + return GF_OK; + } + ofmt = av_guess_format(ctx->ext ? ctx->ext : ctx->ffmt, url, ctx->mime); //if protocol is present, we may fail at guessing the format if (!ofmt && !ctx->ffmt) { @@ -364,7 +406,13 @@ if (len>19) len=19; strncpy(szProto, url, len); szProtolen = 0; - ofmt = av_guess_format(szProto, url, ctx->mime); + if (strncpy(szProto, "srt", len)) { + ofmt = av_guess_format("mpegts", url, ctx->mime); + } else if (strncpy(szProto, "rtmp", len)) { + ofmt = av_guess_format("flv", url, ctx->mime); + } else { + ofmt = av_guess_format(szProto, url, ctx->mime); + } } if (!ofmt) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Failed to guess output format for %s, cannot run\n", ctx->dst)); @@ -499,20 +547,20 @@ } ctx->muxer->pb->seekable = 0; - if (ctx->muxer->oformat->priv_class && ctx->muxer->priv_data) - av_opt_set(ctx->muxer->priv_data, "mpegts_flags", "+resend_headers", 0); + if (ctx->muxer->oformat->priv_class && ctx->muxer->priv_data) + av_opt_set(ctx->muxer->priv_data, "mpegts_flags", "+resend_headers", 0); - if (ctx->ffiles) { - AVDictionary *options = NULL; - av_dict_copy(&options, ctx->options, 0); - av_dict_set(&options, "fflags", "-autobsf", 0); - res = avformat_write_header(ctx->muxer, &options); + if (ctx->ffiles) { + AVDictionary *options = NULL; + av_dict_copy(&options, ctx->options, 0); + av_dict_set(&options, "fflags", "-autobsf", 0); + res = avformat_write_header(ctx->muxer, &options); if (options) av_dict_free(&options); - if (res < 0) { + if (res < 0) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Fail to configure segment %s - error %s\n", seg_name, av_err2str(res) )); return GF_IO_ERR; } - } + } ctx->status = FFMX_STATE_HDR_DONE; return GF_OK; } @@ -690,18 +738,73 @@ } } - static GF_Err ffmx_process(GF_Filter *filter) { GF_Err e = GF_OK; GF_FFMuxCtx *ctx = (GF_FFMuxCtx *) gf_filter_get_udta(filter); u32 nb_done, nb_segs_done, nb_suspended, i, nb_pids = gf_filter_get_ipid_count(filter); + if (ctx->proto) { + //special protocol mode: bypass muxer + for (i=0; i<nb_pids; i++) { + GF_FilterPid *ipid = gf_filter_get_ipid(filter, i); + while (1) { + GF_FilterPacket *ipck = gf_filter_pid_get_packet(ipid); + if (!ipck) { + if (gf_filter_pid_is_eos(ipid)) { + avio_flush(ctx->avio_ctx); + if (ctx->psleep) { + gf_filter_ask_rt_reschedule(filter, ctx->psleep*1000); + ctx->psleep = 0; + return GF_OK; + } + return GF_EOS; + } + break; + } + + int size = 0; + u8 *data = (u8 *) gf_filter_pck_get_data(ipck, &size); + u32 to_write = size - ctx->pck_offset; + if ((s32) to_write > ctx->avio_ctx->buffer_size) + to_write = ctx->avio_ctx->buffer_size; + + avio_write(ctx->avio_ctx, data + ctx->pck_offset, to_write); + + if (ctx->avio_ctx->error == AVERROR(EAGAIN)) { + ctx->avio_ctx->error = 0; + ctx->avio_ctx->eof_reached = 0; + gf_filter_ask_rt_reschedule(filter, 1000); + return GF_OK; + } + if (ctx->avio_ctx->eof_reached) { + gf_filter_pid_drop_packet(ipid); + gf_filter_abort(filter); + return GF_EOS; + } + if (ctx->avio_ctx->error) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("FFMX Write error %s - aborting\n", av_err2str(ctx->avio_ctx->error))); + gf_filter_abort(filter); + return GF_IO_ERR; + } + //it looks like ctx->avio_ctx->error is handled internally in FFMpeg + //so a reset here would not trigger an error an the next call + + ctx->pck_offset += to_write; + if (ctx->pck_offset == size) { + ctx->pck_offset = 0; + gf_filter_pid_drop_packet(ipid); + } + } + } + return GF_OK; + } + if (ctx->status<FFMX_STATE_HDR_DONE) { Bool all_ready = GF_TRUE; Bool needs_reinit = (ctx->status==FFMX_STATE_ALLOC) ? GF_TRUE : GF_FALSE; - //potpone until no pending connections so that we don't write header before all streams are declared + //postpone until no pending connections so that we don't write header before all streams are declared if (gf_filter_connections_pending(filter)) return GF_OK; @@ -952,14 +1055,14 @@ //dovi_meta.h not exported in old releases, just redefine typedef struct { - u8 dv_version_major; - u8 dv_version_minor; - u8 dv_profile; - u8 dv_level; - u8 rpu_present_flag; - u8 el_present_flag; - u8 bl_present_flag; - u8 dv_bl_signal_compatibility_id; + u8 dv_version_major; + u8 dv_version_minor; + u8 dv_profile; + u8 dv_level; + u8 rpu_present_flag; + u8 el_present_flag; + u8 bl_present_flag; + u8 dv_bl_signal_compatibility_id; } Ref_FFAVDoviRecord; @@ -1000,11 +1103,26 @@ return GF_NOT_SUPPORTED; } + const AVCodec *c = NULL; + Bool stream_ready = GF_TRUE; + codec_id = GF_CODECID_NONE; + + if (ctx->proto) { + if (streamtype!=GF_STREAM_FILE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Cannot use proto with non-file format (got %s)\n", gf_stream_type_name(streamtype))); + return GF_NOT_SUPPORTED; + } + ff_st = 0; + ff_codec_id = 0; + check_disc = GF_FALSE; + goto setup_stream; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); if (!p) return GF_NOT_SUPPORTED; codec_id = p->value.uint; - Bool stream_ready = GF_TRUE; + stream_ready = GF_TRUE; p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); if (!p) { //we must wait for decoder config to be present for these codecs @@ -1092,10 +1210,13 @@ ff_codec_id = AV_CODEC_ID_PCM_F64BE; break; default: - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Unmapped raw audio format %s to FFMPEG, patch welcome\n", gf_audio_fmt_name(p->value.uint) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Unmapped raw audio format %s to FFmpeg, patch welcome\n", gf_audio_fmt_name(p->value.uint) )); return GF_NOT_SUPPORTED; } } + } else if (codec_id==GF_CODECID_FFMPEG) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_META_DEMUX_CODEC_ID); + ff_codec_id = p ? p->value.uint : AV_CODEC_ID_NONE; } else { ff_codec_id = ffmpeg_codecid_from_gpac(codec_id, &ff_codec_tag); } @@ -1111,13 +1232,42 @@ #endif if (!res) { + //try negotiating to default codec of container + enum AVCodecID ff_codec = AV_CODEC_ID_NONE; + switch (streamtype) { + case GF_STREAM_VISUAL: + ff_codec = ctx->muxer->oformat->video_codec; + break; + case GF_STREAM_AUDIO: + ff_codec = ctx->muxer->oformat->audio_codec; + break; + case GF_STREAM_TEXT: + ff_codec = ctx->muxer->oformat->subtitle_codec; + break; + } + if (ff_codec != AV_CODEC_ID_NONE) { + u32 codec_id = ffmpeg_codecid_to_gpac(ff_codec); + if (codec_id!=GF_CODECID_NONE) { + gf_filter_pid_negotiate_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(codec_id)); + return GF_OK; + } else { + gf_filter_pid_negotiate_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_FFMPEG)); + gf_filter_pid_negotiate_property(pid, GF_PROP_PID_META_DEMUX_CODEC_ID, &PROP_UINT(ff_codec)); + return GF_OK; + } + } + } + + if (!res) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("FFMux Codec %s not supported in container %s\n", gf_codecid_name(codec_id), ctx->muxer->oformat->name)); return GF_NOT_SUPPORTED; } - const AVCodec *c = avcodec_find_decoder(ff_codec_id); + c = avcodec_find_decoder(ff_codec_id); if (!c) return GF_NOT_SUPPORTED; +setup_stream: + if (!st) { GF_FilterEvent evt; @@ -1133,6 +1283,11 @@ gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "FFMux"); gf_filter_pid_send_event(pid, &evt); + if (ctx->proto) { + st->ready = GF_TRUE; + ctx->status = FFMX_STATE_TRAILER_DONE; + return GF_OK; + } } st->ready = stream_ready; @@ -1330,7 +1485,14 @@ u8 *data = av_malloc(sizeof(u32) * 9); if (data) { memcpy(data, p->value.uint_list.vals, sizeof(u32)*9); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) av_stream_add_side_data(st->stream, AV_PKT_DATA_DISPLAYMATRIX, data, 32*9); +#else + av_packet_side_data_add(&st->stream->codecpar->coded_side_data, + &st->stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX, + data, 32 * 9, 0); +#endif } } #if (LIBAVCODEC_VERSION_MAJOR>58) @@ -1340,7 +1502,14 @@ u8 *data = av_malloc(p->value.data.size); if (data) { memcpy(data, p->value.data.ptr, p->value.data.size); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) av_stream_add_side_data(st->stream, AV_PKT_DATA_ICC_PROFILE, data, p->value.data.size); +#else + av_packet_side_data_add(&st->stream->codecpar->coded_side_data, + &st->stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_ICC_PROFILE, + data, p->value.data.size, 0); +#endif } } //clli @@ -1351,7 +1520,14 @@ if (data) { data->MaxCLL = gf_bs_read_u16(bs); data->MaxFALL = gf_bs_read_u16(bs); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) av_stream_add_side_data(st->stream, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (u8*) data, sizeof(AVContentLightMetadata)); +#else + av_packet_side_data_add(&st->stream->codecpar->coded_side_data, + &st->stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_CONTENT_LIGHT_LEVEL, + (u8*) data, sizeof(AVContentLightMetadata), 0); +#endif } gf_bs_del(bs); } @@ -1385,9 +1561,16 @@ data->max_luminance.den = luma_den; data->min_luminance.num = gf_bs_read_u32(bs); data->min_luminance.den = luma_den; +#if (LIBAVFORMAT_VERSION_MAJOR < 62) av_stream_add_side_data(st->stream, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, (u8*) data, sizeof(AVMasteringDisplayMetadata)); - } - gf_bs_del(bs); +#else + av_packet_side_data_add(&st->stream->codecpar->coded_side_data, + &st->stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_MASTERING_DISPLAY_METADATA, + (u8*) data, sizeof(AVMasteringDisplayMetadata), 0); +#endif + } + gf_bs_del(bs); } //dolby vision p = gf_filter_pid_get_property(pid, GF_PROP_PID_DOLBY_VISION); @@ -1403,7 +1586,14 @@ data->el_present_flag = gf_bs_read_int(bs, 1); data->bl_present_flag = gf_bs_read_int(bs, 1); data->dv_bl_signal_compatibility_id = gf_bs_read_int(bs, 4); +#if (LIBAVFORMAT_VERSION_MAJOR < 62) av_stream_add_side_data(st->stream, AV_PKT_DATA_DOVI_CONF, (u8*) data, sizeof(Ref_FFAVDoviRecord)); +#else + av_packet_side_data_add(&st->stream->codecpar->coded_side_data, + &st->stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_DOVI_CONF, + (u8*) data, sizeof(Ref_FFAVDoviRecord), 0); +#endif } gf_bs_del(bs); } @@ -1461,7 +1651,7 @@ gf_list_del(ctx->streams); if (ctx->avio_ctx) { if (ctx->avio_ctx->buffer) av_freep(&ctx->avio_ctx->buffer); - av_freep(&ctx->avio_ctx); + avio_context_free(&ctx->avio_ctx); } if (ctx->gfio) gf_fclose(ctx->gfio); return; @@ -1521,10 +1711,10 @@ GF_FilterRegister FFMuxRegister = { .name = "ffmx", .version = LIBAVFORMAT_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG multiplexer") + GF_FS_SET_DESCRIPTION("FFmpeg multiplexer") - GF_FS_SET_HELP("Multiplexes files and open output protocols using FFMPEG.\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details.\n" + GF_FS_SET_HELP("Multiplexes files and open output protocols using FFmpeg.\n" + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details.\n" "To list all supported multiplexers for your GPAC build, use `gpac -h ffmx:*`." "This will list both supported output formats and protocols.\n" "Output protocols are listed with `Description: Output protocol`, and the subclass name identifies the protocol scheme.\n" @@ -1537,6 +1727,11 @@ "The filter watches the property `FileNumber` on incoming packets to create new files.\n" "\n" "All PID properties prefixed with `meta:` will be added as metadata.\n" + "\n" + "The -proto() flag will disable FFmpeg muxer and use GPAC instead. Default format is MPEG-2 TS and can be specified using -ext() or -mime().\n" + "EX gpac -i SRC -o srt://127.0.0.1:1234:gpac:proto:ext=mp4:frag" + "This will use the SRT protocol handler but GPAC m2ts multiplexer or mp4 muxer if `ext=mp4:frag` is set\n" + "\n" ) .private_size = sizeof(GF_FFMuxCtx), SETCAPS(FFMuxCaps), @@ -1548,10 +1743,9 @@ .probe_url = ffmx_probe_url, .flags = GF_FS_REG_META, .max_extra_pids = (u32) -1, - - //use lowest priorty, so that we still use our default built-in muxers - .priority = 255 + .priority = 255, + .hint_class_type = GF_FS_CLASS_MULTIPLEXER }; #define OFFS(_n) #_n, offsetof(GF_FFMuxCtx, _n) @@ -1569,6 +1763,9 @@ { OFFS(ffmt), "force ffmpeg output format for the given URL", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(block_size), "block size used to read file when using avio context", GF_PROP_UINT, "4096", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(keepts), "do not shift input timeline back to 0", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(proto), "use protocol only: do not try to mux (useful when sending a SRT stream with remuxing)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(psleep), "in protocol only mode, sleep for given amount of ms before EOS (do not kill connection right away for some protocols)", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(ext), "force ffmpeg output format for the given URL", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_HIDE}, { "*", -1, "any possible options defined for AVFormatContext and sub-classes (see `gpac -hx ffmx` and `gpac -hx ffmx:*`)", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, {0}
View file
gpac-2.4.0.tar.gz/src/filters/ff_rescale.c -> gpac-26.02.0.tar.gz/src/filters/ff_rescale.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / ffmpeg video rescaler filter @@ -27,15 +27,19 @@ #ifdef GPAC_HAS_FFMPEG +#ifdef GPAC_CONFIG_WIN32 + //avoid warning in bswap.h +#define av_bswap64 +#endif + #include "ff_common.h" #include <gpac/evg.h> -enum -{ +GF_OPT_ENUM (GF_FFSWScaleAspectRatioMode, FFSWS_KEEPAR_OFF=0, FFSWS_KEEPAR_FULL, FFSWS_KEEPAR_NOSRC, -}; +); typedef struct { @@ -49,7 +53,7 @@ //internal data Bool initialized; char *padclr; - u32 keepar; + GF_FFSWScaleAspectRatioMode keepar; GF_Fraction osar; GF_FilterPid *ipid, *opid; @@ -613,7 +617,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); if (p) ofmt = p->value.uint; p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p) sar = p->value.frac; + if (p && (p->value.frac.num>0)) sar = p->value.frac; else sar.den = sar.num = 1; p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_RANGE); @@ -631,35 +635,8 @@ ctx->passthrough = GF_FALSE; - Bool downsample_w=GF_FALSE, downsample_h=GF_FALSE; - switch (ofmt) { - case GF_PIXEL_YUV: - case GF_PIXEL_YVU: - case GF_PIXEL_YUV_10: - case GF_PIXEL_NV12: - case GF_PIXEL_NV21: - case GF_PIXEL_NV12_10: - case GF_PIXEL_NV21_10: - case GF_PIXEL_YUVA: - case GF_PIXEL_YUVD: - downsample_h=GF_TRUE; - //fallthrough - case GF_PIXEL_YUV422: - case GF_PIXEL_YUV422_10: - case GF_PIXEL_UYVY: - case GF_PIXEL_VYUY: - case GF_PIXEL_YUYV: - case GF_PIXEL_YVYU: - case GF_PIXEL_UYVY_10: - case GF_PIXEL_VYUY_10: - case GF_PIXEL_YUYV_10: - case GF_PIXEL_YVYU_10: - downsample_w = GF_TRUE; - break; - - default: - break; - } + u32 downsample_w, downsample_h; + gf_pixel_get_downsampling(ofmt, &downsample_w, &downsample_h); u32 scale_w = w; if ((ctx->keepar == FFSWS_KEEPAR_FULL) && (sar.num > (s32) sar.den)) { @@ -1040,33 +1017,37 @@ static const GF_FilterCapability FFSWSCaps = { CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), - CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW) + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_WIDTH, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_HEIGHT, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_PIXFMT, 0), }; GF_FilterRegister FFSWSRegister = { .name = "ffsws", .version=LIBSWSCALE_IDENT, - GF_FS_SET_DESCRIPTION("FFMPEG video rescaler") - GF_FS_SET_HELP("This filter rescales raw video data using FFMPEG to the specified size and pixel format.\n" + GF_FS_SET_DESCRIPTION("FFmpeg video rescaler") + GF_FS_SET_HELP("This filter rescales raw video data using FFmpeg to the specified size and pixel format.\n" "## Output size assignment\n" "If -osize() is {0,0}, the output dimensions will be set to the input size, and input aspect ratio will be ignored.\n" "\n" - "If -osize() is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If -keepar=nosrc(), input sample aspect ratio is ignored.\n" + "If -osize() is {0,H} (resp. {W,0}), the output width (resp. height) will be set to respect input aspect ratio. If -keepar() = `nosrc`, input sample aspect ratio is ignored.\n" "## Aspect Ratio and Sample Aspect Ratio\n" "When output sample aspect ratio is set, the output dimensions are divided by the output sample aspect ratio.\n" "EX ffsws:osize=288x240:osar=3/2\n" "The output dimensions will be 192x240.\n" "\n" - "When aspect ratio is not kept (-keepar=off()):\n" + "When aspect ratio is not kept (-keepar() = `off`):\n" "- source is resampled to desired dimensions\n" "- if output aspect ratio is not set, output will use source sample aspect ratio\n" "\n" - "When aspect ratio is partially kept (-keepar=nosrc()):\n" + "When aspect ratio is partially kept (-keepar() = `nosrc`):\n" "- resampling is done on the input data without taking input sample aspect ratio into account\n" - "- if output sample aspect ratio is not set (-osar=0/N()), source aspect ratio is forwarded to output.\n" + "- if output sample aspect ratio is not set (-osar() = `0/N`), source aspect ratio is forwarded to output.\n" "\n" - "When aspect ratio is fully kept (-keepar=full()), output aspect ratio is force to 1/1 if not set.\n" + "When aspect ratio is fully kept (-keepar() = `full`), output aspect ratio is force to 1/1 if not set.\n" "\n" "When sample aspect ratio is kept, the filter will:\n" "- center the rescaled input frame on the output frame\n" @@ -1077,7 +1058,7 @@ "- for gauss -p1() tunes the exponent and thus cutoff frequency\n" "- for lanczos -p1() tunes the width of the window function\n" "\n" - "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details") + "See FFmpeg documentation (https://ffmpeg.org/documentation.html) for more details") .private_size = sizeof(GF_FFSWScaleCtx), .args = FFSWSArgs, .configure_pid = ffsws_configure_pid, @@ -1087,6 +1068,7 @@ .finalize = ffsws_finalize, .process = ffsws_process, .reconfigure_output = ffsws_reconfigure_output, + .hint_class_type = GF_FS_CLASS_AV }; #else
View file
gpac-2.4.0.tar.gz/src/filters/filelist.c -> gpac-26.02.0.tar.gz/src/filters/filelist.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / file concatenator filter @@ -63,7 +63,7 @@ //in output timescale u64 cts_o, dts_o; Bool single_frame; - Bool is_eos; + Bool is_eos, detached; u64 dts_sub; u64 first_dts_plus_one; u64 prev_max_dts, prev_cts_o, prev_dts_o; @@ -71,7 +71,7 @@ Bool skip_dts_init; u32 play_state; - Bool send_cue; + Bool send_cue, send_period_switch; s32 delay, initial_delay; RawAudioInfo ra_info, splice_ra_info; @@ -95,14 +95,13 @@ u64 file_size; } FileListEntry; -enum -{ +GF_OPT_ENUM (GF_FileListFileSortMode, FL_SORT_NONE=0, FL_SORT_NAME, FL_SORT_SIZE, FL_SORT_DATE, FL_SORT_DATEX, -}; +); enum { @@ -116,13 +115,12 @@ FL_SPLICE_AFTER, }; -enum -{ +GF_OPT_ENUM (GF_FileListForceRawMode, FL_RAW_AV=0, FL_RAW_AUDIO, FL_RAW_VIDEO, - FL_RAW_NO -}; + FL_RAW_NO, +); enum @@ -134,10 +132,10 @@ typedef struct { //opts - Bool revert, sigcues, fdel, keepts, flush; - u32 raw; + Bool revert, sigcues, sigperiods, fdel, keepts, flush; + GF_FileListForceRawMode raw; s32 floop; - u32 fsort; + GF_FileListFileSortMode fsort; u32 ka; u64 timeout; GF_PropStringList srcs; @@ -211,9 +209,16 @@ //for isobmf cat mode in sigfrag char *rel_url, *abs_url, *init_url; + Bool src_has_seen_eos; GF_PropUIntList chap_times; GF_PropStringList chap_names; + + Bool is_gfio; + GF_FileIO *fio; + char *dyn_pl_data; + char *temp_base_url; + } GF_FileListCtx; static const GF_FilterCapability FileListCapsSrc = @@ -269,6 +274,16 @@ static void filelist_start_ipid(GF_FileListCtx *ctx, FileListPid *iopid, u32 prev_timescale, Bool is_reassign) { + //PID is stopped, send a play/stop sequence to reset all buffers and ignore + if (iopid->play_state==FLIST_STATE_STOP) { + iopid->is_eos = GF_TRUE; + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, iopid->ipid); + gf_filter_pid_send_event(iopid->ipid, &evt); + GF_FEVT_INIT(evt, GF_FEVT_STOP, iopid->ipid); + gf_filter_pid_send_event(iopid->ipid, &evt); + return; + } iopid->is_eos = GF_FALSE; if (is_reassign && !ctx->do_cat) { @@ -276,12 +291,12 @@ //if we reattached the input, we must send a play request gf_filter_pid_init_play_event(iopid->ipid, &evt, ctx->start, 1.0, "FileList"); gf_filter_pid_send_event(iopid->ipid, &evt); - iopid->skip_dts_init = GF_FALSE; - if (iopid->play_state==FLIST_STATE_STOP) { - GF_FEVT_INIT(evt, GF_FEVT_STOP, iopid->ipid) - gf_filter_pid_send_event(iopid->ipid, &evt); - iopid->skip_dts_init = GF_TRUE; - } + iopid->skip_dts_init = GF_FALSE; + if (iopid->play_state==FLIST_STATE_STOP) { + GF_FEVT_INIT(evt, GF_FEVT_STOP, iopid->ipid) + gf_filter_pid_send_event(iopid->ipid, &evt); + iopid->skip_dts_init = GF_TRUE; + } } //and convert back cts/dts offsets to output timescale @@ -295,7 +310,7 @@ } else { iopid->cts_o = 0; } - + if (is_reassign && prev_timescale) { u64 dts, cts; @@ -393,6 +408,7 @@ return GF_NOT_SUPPORTED; ctx->file_pid = pid; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); //check multithreaded FileIO restrictions p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); @@ -441,6 +457,7 @@ } gf_list_add(ctx->io_pids, iopid); iopid->send_cue = ctx->sigcues; + iopid->send_period_switch = ctx->sigperiods; iopid->play_state = FLIST_STATE_WAIT_PLAY; first_config = GF_TRUE; } @@ -451,6 +468,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); if (!p) return GF_NOT_SUPPORTED; iopid->stream_type = p->value.uint; + iopid->detached = GF_FALSE; } gf_filter_pid_set_framing_mode(pid, GF_TRUE); @@ -461,7 +479,6 @@ p = gf_filter_pid_get_property(iopid->ipid, GF_PROP_PID_URL); if (p && p->value.string) src_url = gf_strdup(p->value.string); - } opid = iopid->opid; @@ -646,9 +663,9 @@ iopid = gf_list_get(ctx->io_pids, i); if (!iopid->ipid) continue; - //only send on non connected inputs or on the one matching the pid event - if (iopid->opid && (iopid->opid != evt->base.on_pid)) - continue; + //only send on non connected inputs or on the one matching the pid event + if (iopid->opid && (iopid->opid != evt->base.on_pid)) + continue; fevt.base.on_pid = iopid->ipid; if (evt->base.type==GF_FEVT_PLAY) { @@ -658,9 +675,32 @@ } else if (evt->base.type==GF_FEVT_STOP) { iopid->play_state = FLIST_STATE_STOP; iopid->is_eos = GF_TRUE; + //reset all timing info + iopid->dts_sub = 0; + iopid->first_dts_plus_one = 0; + iopid->prev_max_dts = iopid->prev_cts_o = iopid->prev_dts_o = 0; + iopid->max_cts = iopid->max_dts = 0; + iopid->cts_o = iopid->dts_o = 0; + iopid->skip_dts_init = 0; } gf_filter_pid_send_event(iopid->ipid, &fevt); } + //restart of playlist after EOS, reinit timing + if ((evt->base.type==GF_FEVT_PLAY) && ctx->is_eos) { + ctx->is_eos = GF_FALSE; + ctx->load_next = GF_TRUE; + ctx->last_url_crc = 0; + ctx->file_list_idx = ctx->revert ? gf_list_count(ctx->file_list) : -1; + ctx->cts_offset.num = ctx->cts_offset.den = 0; + ctx->dts_offset = ctx->cts_offset; + ctx->prev_cts_offset = ctx->cts_offset; + ctx->prev_dts_offset = ctx->cts_offset; + ctx->wait_dts_plus_one = ctx->cts_offset; + ctx->dts_sub_plus_one = ctx->cts_offset; + ctx->sync_init_time = 0;; + gf_filter_post_process_task(filter); + } + //and cancel return GF_TRUE; } @@ -670,9 +710,11 @@ { char *res_url = NULL; char *sep, *o_url = szURL; - if (ctx->file_path) { + //do NOT concatenate if source is GFIO + if (ctx->file_path && !ctx->is_gfio) { res_url = gf_url_concatenate(ctx->file_path, szURL); - szURL = res_url; + if (res_url) + szURL = res_url; } //we use default session separator set in filelist sep = gf_url_colon_suffix(szURL, '='); @@ -832,7 +874,12 @@ return GF_TRUE; } - if (ctx->wait_update_start || is_splice_update) { + Bool check_ftime = GF_TRUE; + //don't check for file time if we use a GFIO or a file-io mapping from source packets + if (ctx->fio) check_ftime = GF_FALSE; + else if (ctx->is_gfio) check_ftime = GF_FALSE; + + if ((ctx->wait_update_start && check_ftime) || is_splice_update) { u64 last_modif_time = gf_file_modification_time(ctx->file_path); if (ctx->last_file_modif_time >= last_modif_time) { if (!is_splice_update) { @@ -853,6 +900,8 @@ chap_name0=0; f = gf_fopen(ctx->file_path, "rt"); + if (!f) return GF_FALSE; + while (f) { char *l = gf_fgets(szURL, GF_MAX_PATH, f); if (!l || (gf_feof(f) && !szURL0) ) { @@ -1009,6 +1058,10 @@ strncpy(chap_name, aval, 1023); chap_name1023=0; } + } else if (!strcmp(args, "base_url")) { + if (ctx->temp_base_url) gf_free(ctx->temp_base_url); + ctx->temp_base_url = aval && aval0 ? gf_strdup(aval) : NULL; + } else if (!strcmp(args, "replace") || !strcmp(args, "purge")) { } else { if (!ctx->unknown_params || !strstr(ctx->unknown_params, args)) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FileList Unrecognized directive %s, ignoring\n", args)); @@ -1139,6 +1192,15 @@ ctx->do_cat = do_cat; ctx->skip_sync = no_sync; + if (ctx->temp_base_url) { + char *res_url = gf_url_concatenate(ctx->temp_base_url, szURL); + if (res_url) { + strncpy(szURL, res_url, GF_MAX_PATH-2); + szURLGF_MAX_PATH-1=0; + gf_free(res_url); + } + } + if (!ctx->floop) ctx->do_del = (do_del || ctx->fdel) ? GF_TRUE : GF_FALSE; ctx->start_range = start_range; @@ -1189,6 +1251,7 @@ char szURLGF_MAX_PATH; Bool next_url_ok; + ctx->src_has_seen_eos = GF_FALSE; next_url_ok = filelist_next_url(filter, ctx, szURL, GF_FALSE); if (!next_url_ok && ctx->ka) { @@ -1248,7 +1311,7 @@ ctx->load_next = GF_FALSE; - if (! next_url_ok) { + if (!next_url_ok) { if (ctx->splice_state==FL_SPLICE_ACTIVE) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FileList No next URL for splice but splice period still active, resuming splice with possible broken coding dependencies!\n")); ctx->wait_splice_end = GF_TRUE; @@ -1270,6 +1333,17 @@ iopid->ipid = NULL; } ctx->is_eos = GF_TRUE; + if (ctx->file_pid) { + GF_FilterEvent evt; + //force a play/stop on source to stop filters with keepalive (pipe in) + GF_FEVT_INIT(evt, GF_FEVT_PLAY, ctx->file_pid); + gf_filter_pid_send_event(ctx->file_pid, &evt); + GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->file_pid); + gf_filter_pid_send_event(ctx->file_pid, &evt); + + //abort since we may have pending packets if we found the EOS while source was playing (pipe in) + gf_filter_abort(filter); + } return GF_EOS; } for (i=0; i<gf_list_count(ctx->io_pids); i++) { @@ -1386,7 +1460,8 @@ f = gf_filter_load_filter(filter, url, &e); if (f) gf_filter_require_source_id(f); } else { - fsrc = gf_filter_connect_source(filter, url, ctx->file_path, GF_FALSE, &e); + //no parent path if gfio + fsrc = gf_filter_connect_source(filter, url, ctx->is_gfio ? NULL : ctx->file_path, GF_FALSE, &e); if (fsrc) { gf_filter_set_setup_failure_callback(filter, fsrc, filelist_on_filter_setup_error, filter); @@ -1406,7 +1481,6 @@ return GF_SERVICE_ERROR; } if (is_filter_chain) { - if (!filters) filters = gf_list_new(); if (!gf_list_count(filters)) gf_list_add(filters, fsrc); @@ -1523,8 +1597,8 @@ GF_FilterSAPType sap; GF_FilterPid *ipid; Bool is_raw_audio; - gf_assert(ctx->splice_ctrl); - gf_assert(ctx->splice_state); + gf_fatal_assert(ctx->splice_ctrl); + gf_fatal_assert(ctx->splice_state); ipid = ctx->splice_ctrl->splice_ipid ? ctx->splice_ctrl->splice_ipid : ctx->splice_ctrl->ipid; is_raw_audio = ctx->splice_ctrl->splice_ipid ? ctx->splice_ctrl->splice_ra_info.is_raw : ctx->splice_ctrl->ra_info.is_raw; @@ -1904,10 +1978,14 @@ iopid->send_cue = GF_FALSE; gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE)); } + if (iopid->send_period_switch) { + iopid->send_period_switch = GF_FALSE; + gf_filter_pid_set_property_str(iopid->opid, "period_switch", &PROP_BOOL(GF_TRUE)); + } } if (ctx->sigfrag_mode && ctx->abs_url) { - gf_filter_pck_set_property(dst_pck, GF_PROP_PID_URL, &PROP_STRING(ctx->abs_url)); + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_SEG_URL, &PROP_STRING(ctx->abs_url)); if (ctx->rel_url) { gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_FILENAME, &PROP_STRING(ctx->rel_url)); } @@ -1940,35 +2018,100 @@ pck = gf_filter_pid_get_packet(ctx->file_pid); if (pck) { gf_filter_pck_get_framing(pck, &start, &end); - gf_filter_pid_drop_packet(ctx->file_pid); - if (end) { - const GF_PropertyValue *p; - Bool is_first = GF_TRUE; - FILE *f=NULL; - p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_FILEPATH); - if (p) { - char *frag; - if (ctx->file_path) { - gf_free(ctx->file_path); - is_first = GF_FALSE; + if (!end) { + gf_filter_pid_drop_packet(ctx->file_pid); + return GF_SERVICE_ERROR; + } + + const GF_PropertyValue *p; + Bool is_first = GF_TRUE; + FILE *f=NULL; + p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_FILEPATH); + if (!p) { + p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_URL); + if (p && p->value.string && !strncmp(p->value.string, "gfio://", 7)) { + ctx->is_gfio = GF_TRUE; + } else { + p = NULL; + } + } + if (p) { + char *frag; + if (ctx->file_path) { + gf_free(ctx->file_path); + is_first = GF_FALSE; + } + ctx->file_path = gf_strdup(p->value.string); + frag = strchr(ctx->file_path, '#'); + if (frag) { + frag0 = 0; + ctx->frag_url = gf_strdup(frag+1); + } + f = gf_fopen(ctx->file_path, "rt"); + } else { + const u8 *data; + u32 size, len; + Bool replace = GF_FALSE; + data = gf_filter_pck_get_data(pck, &size); + p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_URL); + if (ctx->fio) gf_fclose((FILE*) ctx->fio); + //full replacement + if (data && (size>=8) && !strncmp(data, "#replace", 8)) { + replace = GF_TRUE; + if (ctx->dyn_pl_data) gf_free(ctx->dyn_pl_data); + ctx->dyn_pl_data = NULL; + } + //look for #purge in existing playlist + if (ctx->dyn_pl_data) { + char *purge_start = strstr(ctx->dyn_pl_data, "purge"); + while (purge_start) { + char *purge_next = strstr(purge_start, "purge"); + if (!purge_next) break; + purge_start = purge_next; } - ctx->file_path = gf_strdup(p->value.string); - frag = strchr(ctx->file_path, '#'); - if (frag) { - frag0 = 0; - ctx->frag_url = gf_strdup(frag+1); + if (purge_start) { + purge_start = gf_strdup(purge_start); + gf_free(ctx->dyn_pl_data); + ctx->dyn_pl_data = purge_start; } - f = gf_fopen(ctx->file_path, "rt"); + } else { + replace = GF_TRUE; } - if (!f) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FileList Unable to open file %s\n", ctx->file_path ? ctx->file_path : "no source path")); - return GF_SERVICE_ERROR; + //concatenate playlists + len = ctx->dyn_pl_data ? (u32) strlen(ctx->dyn_pl_data) : 0; + ctx->dyn_pl_data = gf_realloc(ctx->dyn_pl_data, size+len+1); + memcpy(ctx->dyn_pl_data+len, data, size); + size += len; + ctx->dyn_pl_datasize = 0; + + if (ctx->fio) gf_fclose((FILE*) ctx->fio); + ctx->fio = gf_fileio_from_mem(p ? p->value.string : NULL, ctx->dyn_pl_data, size); + if (ctx->file_path) gf_free(ctx->file_path); + ctx->file_path = gf_strdup( gf_fileio_url(ctx->fio) ); + p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_URL); + + ctx->ka = 1000; + if (replace) { + //reset last url crc we want to reload everything + ctx->last_url_crc = 0; + //full playlist reload + is_first = GF_TRUE; } else { - gf_fclose(f); - ctx->load_next = is_first; + is_first = GF_FALSE; } } + gf_filter_pid_drop_packet(ctx->file_pid); + + if (!f && !ctx->fio) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("FileList Unable to open file %s\n", ctx->file_path ? ctx->file_path : "no source path")); + return GF_SERVICE_ERROR; + } else { + if (f) gf_fclose(f); + ctx->load_next = is_first; + } + } else if (ctx->dyn_pl_data && gf_filter_pid_is_eos(ctx->file_pid) && !gf_filter_pid_is_flush_eos(ctx->file_pid)) { + ctx->ka = 0; } } if (ctx->is_eos) @@ -1996,24 +2139,38 @@ GF_FilterPacket *pck; u64 dts; iopid = gf_list_get(ctx->io_pids, i); - if (!iopid->ipid) { + if (!iopid->ipid) { if (ctx->src_error) { nb_eos++; continue; } - if (iopid->opid) gf_filter_pid_set_eos(iopid->opid); - if (iopid->opid_aux) gf_filter_pid_set_eos(iopid->opid_aux); - return GF_OK; + if (iopid->opid && !iopid->detached) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FileList Output PID %s no longer has an associated input, signaling EOS\n", gf_filter_pid_get_name(iopid->opid) )); + gf_filter_pid_set_eos(iopid->opid); + } + if (iopid->opid_aux && !iopid->detached) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("FileList Output PID %s no longer has an associated input, signaling EOS\n", gf_filter_pid_get_name(iopid->opid_aux) )); + gf_filter_pid_set_eos(iopid->opid_aux); + } + iopid->detached = GF_TRUE; + + nb_eos++; + continue; } - if (iopid->skip_dts_init) continue; - pck = gf_filter_pid_get_packet(iopid->ipid); + if (iopid->skip_dts_init) continue; + pck = gf_filter_pid_get_packet(iopid->ipid); if (!pck) { - if (gf_filter_pid_is_eos(iopid->ipid) || (iopid->play_state==FLIST_STATE_STOP)) { + //still waiting for EOS on source + if (gf_filter_pid_is_eos(iopid->ipid)) { nb_eos++; continue; } + //PID is stopped, don't check for EOS + if (iopid->play_state==FLIST_STATE_STOP) + continue; + if ((iopid->stream_type==GF_STREAM_AUDIO) || (iopid->stream_type==GF_STREAM_VISUAL)) nb_not_ready_av++; else @@ -2089,12 +2246,14 @@ ctx->sync_init_time = 0; ctx->src_error = GF_FALSE; if (nb_eos) { + //all sources in EOS before initializing clock, likely broken source, load next if (nb_eos==count) { //force load ctx->load_next = GF_TRUE; //avoid recursive call goto restart; } + //wait for all sources to be in EOS return GF_OK; } @@ -2146,31 +2305,31 @@ for (i=0; i<count; i++) { iopid = gf_list_get(ctx->io_pids, i); if (!iopid->ipid) { - iopid->splice_ready = GF_TRUE; + iopid->splice_ready = GF_TRUE; nb_inactive++; continue; } if (iopid->play_state==FLIST_STATE_WAIT_PLAY) continue; - if (iopid->play_state==FLIST_STATE_STOP) { + if (iopid->play_state==FLIST_STATE_STOP) { nb_stop++; - //in case the input still dispatch packets, drop them - while (1) { - GF_FilterPacket *pck = gf_filter_pid_get_packet(iopid->ipid); - if (!pck) break; - gf_filter_pid_drop_packet(iopid->ipid); - } - while (iopid->splice_ipid) { - GF_FilterPacket *pck = gf_filter_pid_get_packet(iopid->splice_ipid); - if (!pck) break; - gf_filter_pid_drop_packet(iopid->splice_ipid); - } - nb_done++; - iopid->splice_ready = GF_TRUE; - continue; - } + //in case the input still dispatch packets, drop them + while (1) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(iopid->ipid); + if (!pck) break; + gf_filter_pid_drop_packet(iopid->ipid); + } + while (iopid->splice_ipid) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(iopid->splice_ipid); + if (!pck) break; + gf_filter_pid_drop_packet(iopid->splice_ipid); + } + nb_done++; + iopid->splice_ready = GF_TRUE; + continue; + } if (iopid->is_eos) { - iopid->splice_ready = GF_TRUE; + iopid->splice_ready = GF_TRUE; nb_done++; continue; } @@ -2188,6 +2347,7 @@ iopid->splice_ready = GF_TRUE; } else { iopid->is_eos = GF_TRUE; + ctx->src_has_seen_eos = GF_TRUE; if (ctx->splice_state==FL_SPLICE_ACTIVE) purge_splice = GF_TRUE; } @@ -2197,8 +2357,9 @@ nb_done++; break; } - - if (gf_filter_pid_would_block(iopid->opid) && (!iopid->opid_aux || gf_filter_pid_would_block(iopid->opid_aux))) { + //if EOS has been seen on one input, do not regulate as the consumer(s) could wait for the next packet in + //the next file on one of the EOS stream (eg dasher consumer) + if (!ctx->src_has_seen_eos && gf_filter_pid_would_block(iopid->opid) && (!iopid->opid_aux || gf_filter_pid_would_block(iopid->opid_aux))) { break; } @@ -2590,7 +2751,6 @@ } - if ((nb_inactive!=count) && (nb_done+nb_inactive==count)) { //compute max cts and dts GF_Fraction64 max_cts, max_dts; @@ -2600,7 +2760,8 @@ if (gf_filter_end_of_session(filter) || (nb_stop + nb_inactive == count) ) { for (i=0; i<count; i++) { iopid = gf_list_get(ctx->io_pids, i); - gf_filter_pid_set_eos(iopid->opid); + if (iopid->play_state!=FLIST_STATE_STOP) + gf_filter_pid_set_eos(iopid->opid); } ctx->is_eos = GF_TRUE; return GF_EOS; @@ -2613,7 +2774,9 @@ u64 ts; iopid = gf_list_get(ctx->io_pids, i); iopid->send_cue = ctx->sigcues; + iopid->send_period_switch = ctx->sigperiods; if (!iopid->ipid) continue; + if (iopid->play_state==FLIST_STATE_STOP) continue; iopid->prev_max_dts = iopid->max_dts; iopid->prev_cts_o = iopid->cts_o; iopid->prev_dts_o = iopid->dts_o; @@ -2910,6 +3073,11 @@ p.type = GF_PROP_STRING_LIST; p.value.string_list = ctx->chap_names; gf_props_reset_single(&p); + + if (ctx->fio) gf_fclose((FILE*) ctx->fio); + if (ctx->dyn_pl_data) gf_free(ctx->dyn_pl_data); + if (ctx->temp_base_url) gf_free(ctx->temp_base_url); + } static const char *filelist_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) @@ -2944,7 +3112,7 @@ if (!c) return NULL; if ( isalnum(c)) continue; //valid URL chars plus backslash for win path - if (strchr("-._~:/?#@!$&'()*+,;%=\\", c)) { + if (strchr("-._~:/?#@!$&'()*+,;%=\\ ", c)) { line_empty = GF_FALSE; continue; } @@ -2987,6 +3155,7 @@ , GF_PROP_UINT, "no", "no|name|size|date|datex", 0}, { OFFS(sigcues), "inject `CueStart` property at each source begin (new or repeated) for DASHing", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sigperiods), "ask for a new DASH Period at each source begin ; useful when media timing needs to be reset at loops (TTML, ...)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(fdel), "delete source files after processing in playlist mode (does not delete the playlist)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(keepts), "keep initial timestamps unmodified (no reset to 0)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(raw), "force input AV streams to be in raw format\n" @@ -3048,9 +3217,12 @@ "Playlist refreshing will abort:\n" "- if the input playlist has a line not ending with a LF `(\\n)` character, in order to avoid asynchronous issues when reading the playlist.\n" "- if the input playlist has not been modified for the -timeout() option value (infinite by default).\n" + "\n" + "Note: When the source playlist is a GFIO object, URLs inside the playlist are NOT translated into GFIO objects.\n" + "\n" "## Playlist directives\n" "A playlist directive line can contain zero or more directives, separated with space. The following directives are supported:\n" - "- repeat=N: repeats `N` times the content (hence played N+1).\n" + "- repeat=N: repeats `N` times the content (hence played N+1), infinite loop if negative.\n" "- start=T: tries to play the file from start time `T` seconds (double format only). This may not work with some files/formats not supporting seeking.\n" "- stop=T: stops source playback after `T` seconds (double format only). This works on any source (implemented independently from seek support).\n" "- cat: specifies that the following entry should be concatenated to the previous source rather than opening a new source. This can optionally specify a byte range if desired, otherwise the full file is concatenated.\n" @@ -3066,6 +3238,11 @@ "- mark: only inject marker for the splice period and do not load any replacement content (cf below).\n" "- sprops=STR: assigns properties described in `STR` to all PIDs of the main content during a splice (cf below). `STR` is formatted according to `gpac -h doc` using the default parameter set.\n" "- chap=NAME: assigns chapter name at the start of next URL (filter always removes source chapter names).\n" + "- base_url=PATH: overrides base URL for all following entries in the playlist. To reset, use an empty string.\n" + "\n" + "When the playlist is transmitted as packets (pipes, sockets, ...), the following directives also apply:\n" + "- replace: replaces the entire playlist content with new packet payload, otherwise concatenate (payload must start with `#replace`).\n" + "- purge: avoids having the playlist continuously growing by removing all inactive content before previous `#purge` directive, evaluated at each new packet only (payload must start with `#purge`).\n" "\n" "The following global options (applying to the filter, not the sources) may also be set in the playlist:\n" "- ka=N: force -ka() option to `N` millisecond refresh.\n" @@ -3159,7 +3336,8 @@ .configure_pid = filelist_configure_pid, .process = filelist_process, .process_event = filelist_process_event, - .probe_data = filelist_probe_data + .probe_data = filelist_probe_data, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *flist_register(GF_FilterSession *session) @@ -3172,4 +3350,3 @@ return NULL; } #endif // GPAC_DISABLE_FLIST -
View file
gpac-2.4.0.tar.gz/src/filters/hevcmerge.c -> gpac-26.02.0.tar.gz/src/filters/hevcmerge.c
Changed
@@ -4,7 +4,7 @@ * Authors: Jean Le Feuvre * Yacine Mathurin Boubacar Aziakou * Samir Mustapha - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / HEVC tile merger filter @@ -1688,6 +1688,7 @@ .process = hevcmerge_process, .process_event = hevcmerge_process_event, .max_extra_pids = -1, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *hevcmerge_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/hevcsplit.c -> gpac-26.02.0.tar.gz/src/filters/hevcsplit.c
Changed
@@ -4,7 +4,7 @@ * Authors: Jean Le Feuvre * Yacine Mathurin Boubacar Aziakou * Samir Mustapha - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / HEVC tile split and rewrite filter @@ -982,7 +982,7 @@ GF_FilterRegister HEVCSplitRegister = { .name = "hevcsplit", - GF_FS_SET_DESCRIPTION("HEVC tile splitter") + GF_FS_SET_DESCRIPTION("HEVC Tile extractor") GF_FS_SET_HELP("This filter splits a motion-constrained tiled HEVC PID into N independent HEVC PIDs.\n" "Use hevcmerge filter to merge initially motion-constrained tiled HEVC PID in a single output.") .private_size = sizeof(GF_HEVCSplitCtx), @@ -994,6 +994,7 @@ .args = HEVCSplitArgs, .configure_pid = hevcsplit_configure_pid, .process = hevcsplit_process, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister* hevcsplit_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/in_dvb4linux.c -> gpac-26.02.0.tar.gz/src/filters/in_dvb4linux.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / DVB4Linux input filter @@ -28,242 +28,590 @@ #include <gpac/constants.h> #include <gpac/network.h> -#ifndef WIN32 -//#define GPAC_HAS_LINUX_DVB -//#define GPAC_SIM_LINUX_DVB -#endif - - #ifdef GPAC_HAS_LINUX_DVB #include <fcntl.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <unistd.h> +#include <errno.h> -#ifndef GPAC_SIM_LINUX_DVB #include <linux/dvb/dmx.h> #include <linux/dvb/frontend.h> -#endif +#include <linux/dvb/version.h> typedef struct { //options const char *src; const char *chcfg; - u32 block_size; + u32 block_size, csleep; + const char *dev; + u32 idx, csidx, timeout; + GF_PropStringList chans; //only one output pid declared GF_FilterPid *pid; + Bool playing; u32 freq; - u16 vpid; - u16 apid; - -#ifndef GPAC_SIM_LINUX_DVB - fe_spectral_inversion_t specInv; - fe_modulation_t modulation; - fe_bandwidth_t bandwidth; - fe_transmit_mode_t TransmissionMode; - fe_guard_interval_t guardInterval; - fe_code_rate_t HP_CodeRate; - fe_code_rate_t LP_CodeRate; - fe_hierarchy_t hierarchy; -#endif - - int demux_fd; + u32 main_pid; + int demux, frontend, demux_fd; char *block; + u32 tune_start_time; } GF_DVBLinuxCtx; +#define MAX_DEV_LEN 255 -static GF_Err dvblin_tune(GF_DVBLinuxCtx *ctx) +typedef struct { - FILE *chanfile; - char line255; -#ifndef GPAC_SIM_LINUX_DVB - int demux1, front1; - struct dmx_pes_filter_params pesFilterParams; - struct dvb_frontend_parameters frp; - char chan_name_t255; - char freq_str255, inv255, bw255, lcr255, hier255, cr255, - mod255, transm255, gi255, apid_str255, vpid_str255; - const char *chan_conf = ":%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::"; -#endif - char *chan_name; - char *tmp; - char frontend_name100, demux_name100, dvr_name100; - u32 adapter_num; + u32 frequency; + u32 main_pid; + char adapter_pathMAX_DEV_LEN; + u32 frontend_idx; + + fe_delivery_system_t sys_type; + fe_spectral_inversion_t inversion; + fe_code_rate_t fec; + fe_modulation_t modulation; - if (!ctx->src) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("DVB4Lin Missing URL\n")); - return GF_BAD_PARAM; + //DVB-T + fe_bandwidth_t bandwidth; + fe_code_rate_t fec_hp; + fe_transmit_mode_t transmission_mode; + fe_guard_interval_t guard_interval; + fe_hierarchy_t hierarchy; + + //DVB-S + u32 dibseq_csidx; + fe_rolloff_t rolloff; + u32 symbol_rate; + Bool pol_v_r; + Bool hiband; +} GF_DVBParams; + +static void dvblin_stop(GF_DVBLinuxCtx *ctx) +{ + if (ctx->demux_fd>=0) close(ctx->demux_fd); + ctx->demux_fd = -1; + if (ctx->frontend>=0) close(ctx->frontend); + ctx->frontend = -1; + if (ctx->demux>=0) close(ctx->demux); + ctx->demux = -1; + //reset freq as frontend is closed + ctx->freq = 0; +} + +static GF_Err dvblin_get_channel_params(GF_DVBLinuxCtx *ctx, char *chan_name, GF_DVBParams *params) +{ + char vpid_str255, apid_str255; + Bool fwd_all=GF_FALSE, use_freq=GF_FALSE; + u32 for_chan_idx=0, chan_idx=0; + Bool freq_found = GF_FALSE; + + memset(params, 0, sizeof(GF_DVBParams)); + ctx->freq = 0; + + params->frontend_idx = ctx->idx; + strncpy(params->adapter_path, ctx->dev, MAX_DEV_LEN-1); + params->adapter_pathMAX_DEV_LEN-1 = 0; + params->dibseq_csidx = ctx->csidx; + + FILE *chanfile = gf_fopen(ctx->chcfg, "rb"); + if (!chanfile) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Error opening channels configuration file\n")); + return GF_IO_ERR; } - if (!ctx->chcfg) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("DVB4Lin Missing channels config file\n")); - return GF_BAD_PARAM; + if (chan_name0=='+') { + chan_name++; + fwd_all = GF_TRUE; + } + if (chan_name0=='@') { + chan_name++; + fwd_all = GF_TRUE; + use_freq = GF_TRUE; + } + if (chan_name0=='=') { + chan_name++; + for_chan_idx = atoi(chan_name); } - chanfile = gf_fopen(ctx->chcfg, "rb"); - if (!chanfile) return GF_BAD_PARAM; - chan_name = (char *) ctx->src+6; // 6 = strlen("dvb://") + while (!gf_feof(chanfile)) { + char line255; + if ( gf_fgets(line, 255, chanfile) == NULL) break; + + if (line0=='\r') continue; + if (line0=='\n') continue; + //parse commands + if (line0 == '#') { + char *delim, *cfg = strstr(line, "dev="); + if (cfg) { + cfg+=4; + delim = strchr(cfg, ' '); + if (delim) delim0 = 0; + strncpy(params->adapter_path, cfg, MAX_DEV_LEN-1); + params->adapter_pathMAX_DEV_LEN-1 = 0; + if (delim) delim0 = ' '; + } + cfg = strstr(line, "idx="); + if (cfg) { + cfg+=4; + delim = strchr(cfg, ' '); + if (delim) delim0 = 0; + params->frontend_idx = atoi(cfg); + if (delim) delim0 = ' '; + } + cfg = strstr(line, "csidx="); + if (cfg) { + cfg+=6; + delim = strchr(cfg, ' '); + if (delim) delim0 = 0; + params->dibseq_csidx = atoi(cfg); + if (delim) delim0 = ' '; + } + continue; + } + //extract channel name and frequency + char *freq_str = strchr(line, ':'); + if (!freq_str) continue; + freq_str0 = 0; + freq_str = freq_str+1; + char *settings = strchr(freq_str, ':'); + if (!settings) continue; + settings0 = 0; + settings++; + + u32 old_freq = ctx->freq; + if (!gf_strict_atoui(freq_str, &ctx->freq)) continue; + if (!ctx->freq) continue; + //tune on channel or frequency index + if (for_chan_idx) { + if (use_freq) { + if (ctx->freq == old_freq) + continue; + chan_idx++; + } else { + chan_idx++; + } + if (for_chan_idx > chan_idx) continue; + if (for_chan_idx < chan_idx) break; + } + //tune on freq + else if (use_freq) { + if (strcmp(chan_name, freq_str)) continue; + } + //tune on name + else { + if (strnicmp(chan_name, line, strlen(chan_name))) continue; + } - // support for multiple frontends - tmp = strchr(chan_name, '@'); - if (tmp) { - adapter_num = atoi(tmp+1); - tmp0 = 0; - } else { - adapter_num = 0; - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("Channel name %s\n", chan_name)); + //DVB-T + if (!strncmp(settings, "INVERSION_", 10)) { + u32 k; + char inv255, bw255, lcr255, hier255, cr255, mod255, transm255, gi255; + const char *chan_conf_dvbt = "%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::%255^::"; + + sscanf(settings, chan_conf_dvbt, inv, bw, lcr, cr, mod, transm, gi, hier, vpid_str, apid_str); + + //Inversion + if(! strcmp(inv, "INVERSION_ON")) params->inversion = INVERSION_ON; + else if(! strcmp(inv, "INVERSION_OFF")) params->inversion = INVERSION_OFF; + else params->inversion = INVERSION_AUTO; + + for (k=0; k<2; k++) { + u32 *p_cr = k ? ¶ms->fec_hp : ¶ms->fec; + char *use_cr = k ? cr : lcr; + + if (!strcmp(use_cr, "FEC_1_2")) *p_cr = FEC_1_2; + else if(!strcmp(use_cr, "FEC_2_3")) *p_cr = FEC_2_3; + else if(!strcmp(use_cr, "FEC_3_4")) *p_cr = FEC_3_4; + else if(!strcmp(use_cr, "FEC_4_5")) *p_cr = FEC_4_5; + else if(!strcmp(use_cr, "FEC_6_7")) *p_cr = FEC_6_7; + else if(!strcmp(use_cr, "FEC_8_9")) *p_cr = FEC_8_9; + else if(!strcmp(use_cr, "FEC_5_6")) *p_cr = FEC_5_6; + else if(!strcmp(use_cr, "FEC_7_8")) *p_cr = FEC_7_8; + else if(!strcmp(use_cr, "FEC_NONE")) *p_cr = FEC_NONE; + else *p_cr = FEC_AUTO; + } + //Modulation + if(! strcmp(mod, "QAM_128")) params->modulation = QAM_128; + else if(! strcmp(mod, "QAM_256")) params->modulation = QAM_256; + else if(! strcmp(mod, "QAM_64")) params->modulation = QAM_64; + else if(! strcmp(mod, "QAM_32")) params->modulation = QAM_32; + else if(! strcmp(mod, "QAM_16")) params->modulation = QAM_16; + //Bandwidth + if(! strcmp(bw, "BANDWIDTH_6_MHZ")) params->bandwidth = BANDWIDTH_6_MHZ; + else if(! strcmp(bw, "BANDWIDTH_7_MHZ")) params->bandwidth = BANDWIDTH_7_MHZ; + else if(! strcmp(bw, "BANDWIDTH_8_MHZ")) params->bandwidth = BANDWIDTH_8_MHZ; + //Transmission Mode + if(! strcmp(transm, "TRANSMISSION_MODE_2K")) params->transmission_mode = TRANSMISSION_MODE_2K; + else if(! strcmp(transm, "TRANSMISSION_MODE_8K")) params->transmission_mode = TRANSMISSION_MODE_8K; + //Guard Interval + if(! strcmp(gi, "GUARD_INTERVAL_1_32")) params->guard_interval = GUARD_INTERVAL_1_32; + else if(! strcmp(gi, "GUARD_INTERVAL_1_16")) params->guard_interval = GUARD_INTERVAL_1_16; + else if(! strcmp(gi, "GUARD_INTERVAL_1_8")) params->guard_interval = GUARD_INTERVAL_1_8; + else params->guard_interval = GUARD_INTERVAL_1_4; + //Hierarchy + if(! strcmp(hier, "HIERARCHY_1")) params->hierarchy = HIERARCHY_1; + else if(! strcmp(hier, "HIERARCHY_2")) params->hierarchy = HIERARCHY_2; + else if(! strcmp(hier, "HIERARCHY_4")) params->hierarchy = HIERARCHY_4; + else if(! strcmp(hier, "HIERARCHY_AUTO")) params->hierarchy = HIERARCHY_AUTO; + else params->hierarchy = HIERARCHY_NONE; + + params->sys_type = SYS_DVBT; + params->frequency = ctx->freq; + freq_found = GF_TRUE; + break; + } - while(!gf_feof(chanfile)) { - if ( gf_fgets(line, 255, chanfile) != NULL) { - if (line0=='#') continue; - if (line0=='\r') continue; - if (line0=='\n') continue; + //DVB-S + if (!strncmp(settings, "v", 1) || !strncmp(settings, "h", 1)) { + char pol, sat_name255; + u32 fec, mod, rolloff, dvbs_type, symbol_rate; + const char *chan_conf_dvbs = "%cC%uM%uO%uS%d:%255^::%u:%255^::%255^::\n"; + sscanf(settings, chan_conf_dvbs, &pol, &fec, &mod, &rolloff, &dvbs_type, sat_name, &symbol_rate, vpid_str, apid_str); -#ifndef GPAC_SIM_LINUX_DVB - strncpy(chan_name_t, line, index(line, ':')-line); - chan_name_t254 = 0; - - if (strncmp(chan_name,chan_name_t,strlen(chan_name))==0) { - sscanf(strstr(line,":"), chan_conf, freq_str, inv, bw, lcr, cr, mod, transm, gi, hier, apid_str, vpid_str); - ctx->freq = (uint32_t) atoi(freq_str); - ctx->apid = (uint16_t) atoi(apid_str); - ctx->vpid = (uint16_t) atoi(vpid_str); - //Inversion - if(! strcmp(inv, "INVERSION_ON")) ctx->specInv = INVERSION_ON; - else if(! strcmp(inv, "INVERSION_OFF")) ctx->specInv = INVERSION_OFF; - else ctx->specInv = INVERSION_AUTO; - //LP Code Rate - if(! strcmp(lcr, "FEC_1_2")) ctx->LP_CodeRate =FEC_1_2; - else if(! strcmp(lcr, "FEC_2_3")) ctx->LP_CodeRate =FEC_2_3; - else if(! strcmp(lcr, "FEC_3_4")) ctx->LP_CodeRate =FEC_3_4; - else if(! strcmp(lcr, "FEC_4_5")) ctx->LP_CodeRate =FEC_4_5; - else if(! strcmp(lcr, "FEC_6_7")) ctx->LP_CodeRate =FEC_6_7; - else if(! strcmp(lcr, "FEC_8_9")) ctx->LP_CodeRate =FEC_8_9; - else if(! strcmp(lcr, "FEC_5_6")) ctx->LP_CodeRate =FEC_5_6; - else if(! strcmp(lcr, "FEC_7_8")) ctx->LP_CodeRate =FEC_7_8; - else if(! strcmp(lcr, "FEC_NONE")) ctx->LP_CodeRate =FEC_NONE; - else ctx->LP_CodeRate =FEC_AUTO; - //HP Code Rate - if(! strcmp(cr, "FEC_1_2")) ctx->HP_CodeRate =FEC_1_2; - else if(! strcmp(cr, "FEC_2_3")) ctx->HP_CodeRate =FEC_2_3; - else if(! strcmp(cr, "FEC_3_4")) ctx->HP_CodeRate =FEC_3_4; - else if(! strcmp(cr, "FEC_4_5")) ctx->HP_CodeRate =FEC_4_5; - else if(! strcmp(cr, "FEC_6_7")) ctx->HP_CodeRate =FEC_6_7; - else if(! strcmp(cr, "FEC_8_9")) ctx->HP_CodeRate =FEC_8_9; - else if(! strcmp(cr, "FEC_5_6")) ctx->HP_CodeRate =FEC_5_6; - else if(! strcmp(cr, "FEC_7_8")) ctx->HP_CodeRate =FEC_7_8; - else if(! strcmp(cr, "FEC_NONE")) ctx->HP_CodeRate =FEC_NONE; - else ctx->HP_CodeRate =FEC_AUTO; - //Modulation - if(! strcmp(mod, "QAM_128")) ctx->modulation = QAM_128; - else if(! strcmp(mod, "QAM_256")) ctx->modulation = QAM_256; - else if(! strcmp(mod, "QAM_64")) ctx->modulation = QAM_64; - else if(! strcmp(mod, "QAM_32")) ctx->modulation = QAM_32; - else if(! strcmp(mod, "QAM_16")) ctx->modulation = QAM_16; - //Bandwidth - if(! strcmp(bw, "BANDWIDTH_6_MHZ")) ctx->bandwidth = BANDWIDTH_6_MHZ; - else if(! strcmp(bw, "BANDWIDTH_7_MHZ")) ctx->bandwidth = BANDWIDTH_7_MHZ; - else if(! strcmp(bw, "BANDWIDTH_8_MHZ")) ctx->bandwidth = BANDWIDTH_8_MHZ; - //Transmission Mode - if(! strcmp(transm, "TRANSMISSION_MODE_2K")) ctx->TransmissionMode = TRANSMISSION_MODE_2K; - else if(! strcmp(transm, "TRANSMISSION_MODE_8K")) ctx->TransmissionMode = TRANSMISSION_MODE_8K; - //Guard Interval - if(! strcmp(gi, "GUARD_INTERVAL_1_32")) ctx->guardInterval = GUARD_INTERVAL_1_32; - else if(! strcmp(gi, "GUARD_INTERVAL_1_16")) ctx->guardInterval = GUARD_INTERVAL_1_16; - else if(! strcmp(gi, "GUARD_INTERVAL_1_8")) ctx->guardInterval = GUARD_INTERVAL_1_8; - else ctx->guardInterval = GUARD_INTERVAL_1_4; - //Hierarchy - if(! strcmp(hier, "HIERARCHY_1")) ctx->hierarchy = HIERARCHY_1; - else if(! strcmp(hier, "HIERARCHY_2")) ctx->hierarchy = HIERARCHY_2; - else if(! strcmp(hier, "HIERARCHY_4")) ctx->hierarchy = HIERARCHY_4; - else if(! strcmp(hier, "HIERARCHY_AUTO")) ctx->hierarchy = HIERARCHY_AUTO; - else ctx->hierarchy = HIERARCHY_NONE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("DVB S/S2 parameters: freq %u pol %c FEC %u mod %u rolloff %u rate %u stype %u\n", + ctx->freq, pol, fec, mod, rolloff, symbol_rate, dvbs_type)); +#if DVB_API_VERSION >= 5 + params->sys_type = dvbs_type ? SYS_DVBS2 : SYS_DVBS; +#else + params->sys_type = SYS_DVBS; +#endif + switch (pol) { + case 'h': + case 'l': + params->pol_v_r = GF_FALSE; + break; + case 'v': + case 'r': + params->pol_v_r = GF_TRUE; break; + default: + continue; } + switch (fec) { + case 12: params->fec = FEC_1_2; break; + case 23: params->fec = FEC_2_3; break; + case 34: params->fec = FEC_3_4; break; + case 35: params->fec = FEC_3_5; break; + case 45: params->fec = FEC_4_5; break; + case 56: params->fec = FEC_5_6; break; + case 67: params->fec = FEC_6_7; break; + case 78: params->fec = FEC_7_8; break; + case 89: params->fec = FEC_8_9; break; + case 910: params->fec = FEC_4_5; break; + case 0: params->fec = FEC_NONE; break; + default: + continue; + } + switch (mod) { + case 16: params->modulation = QAM_16; break; + case 32: params->modulation = QAM_32; break; + case 64: params->modulation = QAM_64; break; + case 128: params->modulation = QAM_128; break; + case 256: params->modulation = QAM_256; break; +#if 0 + case 512: params->modulation = QAM_512; break; + case 1024: params->modulation = QAM_1024; break; + case 4096: params->modulation = QAM_4096; break; #endif + case 998: params->modulation = QAM_AUTO; break; + case 2: params->modulation = QPSK; break; + case 5: params->modulation = PSK_8; break; + case 6: params->modulation = APSK_16; break; + case 7: params->modulation = APSK_32; break; + case 10: params->modulation = VSB_8; break; + case 11: params->modulation = VSB_16; break; + case 12: params->modulation = DQPSK; break; + default: + continue; + } + + switch (rolloff) { + case 20: params->rolloff = ROLLOFF_20; break; + case 25: params->rolloff = ROLLOFF_25; break; + case 30: params->rolloff = ROLLOFF_35; break; + default: params->rolloff = ROLLOFF_AUTO; break; + } + if (ctx->freq < 11700) { + params->frequency = abs(ctx->freq*1000 - 9750000); + params->hiband = GF_FALSE; + //tone_mode = SEC_TONE_OFF; + } else { + params->frequency = (ctx->freq * 1000)-10600000; + params->hiband = GF_TRUE; + //tone_mode = SEC_TONE_ON; + } + params->symbol_rate = symbol_rate * 1000; + params->inversion = INVERSION_AUTO; + freq_found = GF_TRUE; + break; } + //todo, cable and ATSC - no test env yet + //todo, other config formats ? } gf_fclose(chanfile); - sprintf(frontend_name, "/dev/dvb/adapter%d/frontend0", adapter_num); - sprintf(demux_name, "/dev/dvb/adapter%d/demux0", adapter_num); - sprintf(dvr_name, "/dev/dvb/adapter%d/dvr0", adapter_num); + if (!freq_found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot find channel configuration for %s\n", ctx->src+6)); + ctx->freq = 0; + return GF_URL_ERROR; + } -#ifndef GPAC_SIM_LINUX_DVB - // Open frontend - if((front1 = open(frontend_name,O_RDWR|O_NONBLOCK)) < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Cannot open frontend %s.\n", frontend_name)); + params->main_pid = 0; + if (!vpid_str0 && apid_str0) + strcpy(vpid_str, apid_str); + + if (!fwd_all && vpid_str0) { + char *s = strchr(vpid_str, '='); + if (s) s0 = 0; + s = strchr(vpid_str, '+'); + if (s) s0 = 0; + params->main_pid = (u16) atoi(vpid_str); + } + return GF_OK; +} + +#if DVB_API_VERSION >= 5 +#define MAX_DVB_PROPS 15 + +#define DVB_SET_PROP(_cmd, _val) {\ + gf_fatal_assert(num_dvb_props+1<MAX_DVB_PROPS); \ + dvb_propsnum_dvb_props.cmd = _cmd;\ + dvb_propsnum_dvb_props.u.data = _val; \ + num_dvb_props++;\ + } + + +static GF_Err send_diseqc(GF_DVBLinuxCtx *ctx, struct dvb_diseqc_master_cmd *cmd, fe_sec_voltage_t voltage, fe_sec_tone_mode_t tone_mode, fe_sec_mini_cmd_t mini_c) +{ + if (ioctl(ctx->frontend, FE_SET_TONE, SEC_TONE_OFF) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot set tone off\n")); return GF_IO_ERR; - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("Frontend %s opened.\n", frontend_name)); } - // Open demuxes - if ((demux1=open(demux_name, O_RDWR|O_NONBLOCK)) < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Cannot open demux %s\n", demux_name)); + + if (ioctl(ctx->frontend, FE_SET_VOLTAGE, voltage) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot set voltage\n")); return GF_IO_ERR; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Demux %s opened.\n", demux_name)); } - // Set FrontendParameters - DVB-T - frp.frequency = ctx->freq; - frp.inversion = ctx->specInv; - frp.u.ofdm.bandwidth = ctx->bandwidth; - frp.u.ofdm.code_rate_HP = ctx->HP_CodeRate; - frp.u.ofdm.code_rate_LP = ctx->LP_CodeRate; - frp.u.ofdm.constellation = ctx->modulation; - frp.u.ofdm.transmission_mode = ctx->TransmissionMode; - frp.u.ofdm.guard_interval = ctx->guardInterval; - frp.u.ofdm.hierarchy_information = ctx->hierarchy; - // Set frontend - if (ioctl(front1, FE_SET_FRONTEND, &frp) < 0) { + gf_sleep(ctx->csleep); + + if (ioctl(ctx->frontend, FE_DISEQC_SEND_MASTER_CMD, cmd) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot send diseqc master command\n")); + return GF_IO_ERR; + } + gf_sleep(ctx->csleep); + + if (ioctl(ctx->frontend, FE_DISEQC_SEND_BURST, mini_c) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot send diseqc mini command\n")); return GF_IO_ERR; } + gf_sleep(ctx->csleep); - // Set dumex - pesFilterParams.pid = 0x2000; // Linux-DVB API take PID=2000 for FULL/RAW TS flag - pesFilterParams.input = DMX_IN_FRONTEND; - pesFilterParams.output = DMX_OUT_TS_TAP; - pesFilterParams.pes_type = DMX_PES_OTHER; - pesFilterParams.flags = DMX_IMMEDIATE_START; - if (ioctl(demux1, DMX_SET_PES_FILTER, &pesFilterParams) < 0) { + if (ioctl(ctx->frontend, FE_SET_TONE, tone_mode) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot set tone\n")); return GF_IO_ERR; } - /* The following code differs from mplayer and alike because the device is opened in blocking mode */ - if ((ctx->demux_fd = open(dvr_name, O_RDONLY/*|O_NONBLOCK*/)) < 0) { + gf_sleep(ctx->csleep); + return GF_OK; +} +#endif // DVB_API_VERSION >= 5 + + +static GF_Err dvblin_tune(GF_DVBLinuxCtx *ctx) +{ + Bool is_path=GF_TRUE; + char dev_pathGF_MAX_PATH; + GF_DVBParams params; +#if DVB_API_VERSION >= 5 + struct dtv_property dvb_propsMAX_DVB_PROPS; + u32 num_dvb_props = 0; + struct dtv_properties dvb_cfg; +#else + struct dvb_frontend_parameters frp; + memset(&frp, 0, sizeof(struct dvb_frontend_parameters)); +#endif + + + + GF_Err e = dvblin_get_channel_params(ctx, (char *) ctx->src+6, ¶ms); + if (e) return e; + + is_path = strchr(params.adapter_path, '/') ? GF_TRUE : GF_FALSE; + + sprintf(dev_path, "%s%s/frontend%d", is_path ? "" : "/dev/dvb/adapter", params.adapter_path, params.frontend_idx); + + // Open frontend + if((ctx->frontend = open(dev_path,O_RDWR|O_NONBLOCK)) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot open frontend %s\n", dev_path)); return GF_IO_ERR; } + +#if DVB_API_VERSION >= 5 + DVB_SET_PROP(DTV_CLEAR, 0); + DVB_SET_PROP(DTV_DELIVERY_SYSTEM, params.sys_type); + DVB_SET_PROP(DTV_FREQUENCY, params.frequency); + DVB_SET_PROP(DTV_MODULATION, params.modulation); + DVB_SET_PROP(DTV_INVERSION, params.inversion); +#else + frp.frequency = ctx->freq; + frp.inversion = params.inversion; +#endif + + + if ((params.sys_type == SYS_DVBS) +#if DVB_API_VERSION >= 5 + || (params.sys_type == SYS_DVBS2) +#endif + ) { +#if DVB_API_VERSION >= 5 + GF_Err e; + struct dvb_diseqc_master_cmd cmd; + fe_sec_voltage_t voltage = params.pol_v_r ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; + fe_sec_tone_mode_t tone_mode = params.hiband ? SEC_TONE_ON : SEC_TONE_OFF; + fe_sec_mini_cmd_t mini_cmd = (params.dibseq_csidx % 2) ? SEC_MINI_B : SEC_MINI_A; + + cmd.msg_len = 4; + cmd.msg0 = 0xe0; //cmd type: first from master + cmd.msg1 = 0x10; //adress type: any + cmd.msg2 = 0x39; //for uncommitted switches + cmd.msg3 = 0xf0; + + e = send_diseqc(ctx, &cmd, voltage, tone_mode, mini_cmd); + if (e) { + dvblin_stop(ctx); + return e; + } + + cmd.msg_len = 4; + cmd.msg2 = 0x38; //for committed switches + cmd.msg3 = 0xf0; + cmd.msg3 |= (params.dibseq_csidx * 4) & 0x0f; + cmd.msg3 |= (voltage == SEC_VOLTAGE_18) ? 2 : 0; + cmd.msg3 |= (tone_mode == SEC_TONE_ON) ? 1 : 0; + + e = send_diseqc(ctx, &cmd, voltage, tone_mode, mini_cmd); + if (e) { + dvblin_stop(ctx); + return e; + } +fprintf(stderr, "Using csidx %u v18 %u toneOn %u freq %u (%u)\n", params.dibseq_csidx, (voltage == SEC_VOLTAGE_18) ? 1 : 0, (tone_mode == SEC_TONE_ON) ? 1 : 0, ctx->freq, params.frequency); + + DVB_SET_PROP(DTV_SYMBOL_RATE, params.symbol_rate); + DVB_SET_PROP(DTV_INNER_FEC, params.fec); + DVB_SET_PROP(DTV_ROLLOFF, params.rolloff); + DVB_SET_PROP(DTV_PILOT, PILOT_AUTO); +#else + frp.u.qpsk.symbol_rate = params.symbol_rate; + frp.u.qpsk.fec_inner = params.fec; +#endif + } + //DVB-T for now + else if ((params.sys_type == SYS_DVBT) +#if DVB_API_VERSION >= 5 + || (params.sys_type == SYS_DVBT2) +#endif + ) { +fprintf(stderr, "TNT using dev %s (%u) type %u freq %u mod %u bw %u fec %u %u tr %u guard %u hierachy %u\n", + params.adapter_path, params.frontend_idx, params.sys_type, params.frequency, params.modulation, params.bandwidth, + params.fec_hp, params.fec, params.transmission_mode, params.guard_interval, params.hierarchy + ); + +#if DVB_API_VERSION >= 5 + u32 bw_hz = 0; + if (params.bandwidth == BANDWIDTH_6_MHZ) bw_hz = 6000000; + else if (params.bandwidth == BANDWIDTH_7_MHZ) bw_hz = 7000000; + else if (params.bandwidth == BANDWIDTH_8_MHZ) bw_hz = 8000000; + + DVB_SET_PROP(DTV_BANDWIDTH_HZ, bw_hz); + DVB_SET_PROP(DTV_CODE_RATE_HP, params.fec_hp); + DVB_SET_PROP(DTV_CODE_RATE_LP, params.fec); + DVB_SET_PROP(DTV_TRANSMISSION_MODE, params.transmission_mode); + DVB_SET_PROP(DTV_GUARD_INTERVAL, params.guard_interval); + DVB_SET_PROP(DTV_HIERARCHY, params.hierarchy); +#else + frp.u.ofdm.bandwidth = params.bandwidth; + frp.u.ofdm.code_rate_HP = params.fec_hp; + frp.u.ofdm.code_rate_LP = params.fec; + frp.u.ofdm.constellation = params.modulation; + frp.u.ofdm.transmission_mode = params.transmission_mode; + frp.u.ofdm.guard_interval = params.guard_interval; + frp.u.ofdm.hierarchy_information = params.hierarchy; +#endif + } else { + dvblin_stop(ctx); + return GF_NOT_SUPPORTED; + } + + int ret_val; +#if DVB_API_VERSION >= 5 + DVB_SET_PROP(DTV_TUNE, 0); + dvb_cfg.num = num_dvb_props; + dvb_cfg.props = dvb_props; + ret_val = ioctl(ctx->frontend, FE_SET_PROPERTY, &dvb_cfg); +#else + ret_val = ioctl(ctx->frontend, FE_SET_FRONTEND, &frp); #endif + if (ret_val < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot tune frontend %s\n", dev_path)); + dvblin_stop(ctx); + return GF_IO_ERR; + } + // Open demuxer + sprintf(dev_path, "%s%s/demux%d", is_path ? "" : "/dev/dvb/adapter", params.adapter_path, params.frontend_idx); + if ((ctx->demux = open(dev_path, O_RDWR|O_NONBLOCK)) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot open demux %s\n", dev_path)); + dvblin_stop(ctx); + return GF_IO_ERR; + } + + + // Set filter to none (we always want the full TS for now) + struct dmx_pes_filter_params pes_filter; + memset(&pes_filter, 0, sizeof(struct dmx_pes_filter_params)); + pes_filter.pid = 0x2000; + pes_filter.input = DMX_IN_FRONTEND; + pes_filter.output = DMX_OUT_TS_TAP; + pes_filter.pes_type = DMX_PES_OTHER; + pes_filter.flags = DMX_IMMEDIATE_START; + + if (ioctl(ctx->demux, DMX_SET_PES_FILTER, &pes_filter) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot set pes filter params\n")); + return GF_IO_ERR; + } + //open recorder + sprintf(dev_path, "%s%s/dvr%d", is_path ? "" : "/dev/dvb/adapter", params.adapter_path, params.frontend_idx); + if ((ctx->demux_fd = open(dev_path, O_RDONLY|O_NONBLOCK)) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Cannot open demux file descriptor %s\n", dev_path)); + dvblin_stop(ctx); + return GF_IO_ERR; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("DVB Tune-in done for demux %s\n", dev_path)); + ctx->tune_start_time = gf_sys_clock(); return GF_OK; } +#if 0 static u32 gf_dvblin_get_freq_from_url(GF_DVBLinuxCtx *ctx, const char *url) { - FILE *chcfgig_file; + FILE *chc_file; char line255, *tmp, *channel_name; - u32 freq; - /* get rid of trailing @ */ - tmp = strchr(url, '@'); - if (tmp) tmp0 = 0; - channel_name = (char *)url+6; - chcfgig_file = gf_fopen(ctx->chcfg, "rb"); - if (!chcfgig_file) return GF_BAD_PARAM; + chc_file = gf_fopen(ctx->chcfg, "rb"); + if (!chc_file) return 0; freq = 0; - while(!gf_feof(chcfgig_file)) { - if ( gf_fgets(line, 255, chcfgig_file) != NULL) { + while(!gf_feof(chc_file)) { + if ( gf_fgets(line, 255, chc_file) != NULL) { if (line0=='#') continue; if (line0=='\r') continue; if (line0=='\n') continue; @@ -280,70 +628,39 @@ } } } + gf_fclose(chc_file); return freq; } +#endif GF_Err dvblin_setup_demux(GF_DVBLinuxCtx *ctx) { GF_Err e = GF_OK; - if (!ctx->chcfg) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("DVB4Lin Missing channels config file\n")); - return GF_BAD_PARAM; - } - if (strnicmp(ctx->src, "dvb://", 6)) return GF_NOT_SUPPORTED; - +#if 0 + //not supported yet if ((ctx->freq != 0) && (ctx->freq == gf_dvblin_get_freq_from_url(ctx, ctx->src)) ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("M2TSDemux Tuner already tuned to that frequency\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("DVB Tuner already tuned to that frequency\n")); return GF_OK; } +#endif e = dvblin_tune(ctx); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("M2TSDemux Unable to tune to frequency\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Unable to tune to frequency\n")); return GF_SERVICE_ERROR; } return GF_OK; } - - -GF_Err dvblin_initialize(GF_Filter *filter) -{ - GF_Err e = GF_OK; - GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); - - if (!ctx || !ctx->src) return GF_BAD_PARAM; - e = dvblin_setup_demux(ctx); - - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("DVBLinux Failed to open %s\n", ctx->src)); - gf_filter_setup_failure(filter, e); - return GF_URL_ERROR; - } - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("DVBLinux opening %s\n", ctx->src)); - - ctx->block = gf_malloc(ctx->block_size +1); - return GF_OK; -} - void dvblin_finalize(GF_Filter *filter) { GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); -#ifndef GPAC_SIM_LINUX_DVB - if (ctx->demux_fd) close(ctx->demux_fd); -#endif + dvblin_stop(ctx); if (ctx->block) gf_free(ctx->block); } -GF_FilterProbeScore dvblin_probe_url(const char *url, const char *mime_type) -{ - if (!strnicmp(url, "dvb://", 6)) return GF_FPROBE_SUPPORTED; - return GF_FPROBE_NOT_SUPPORTED; -} - - static Bool dvblin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); @@ -353,13 +670,14 @@ switch (evt->base.type) { case GF_FEVT_PLAY: + if (ctx->playing) return GF_TRUE; dvblin_setup_demux(ctx); + ctx->playing = GF_TRUE; + gf_filter_post_process_task(filter); return GF_TRUE; case GF_FEVT_STOP: -#ifndef GPAC_SIM_LINUX_DVB - if (ctx->demux_fd) close(ctx->demux_fd); - ctx->demux_fd = 0; -#endif + dvblin_stop(ctx); + ctx->playing = GF_FALSE; return GF_TRUE; default: break; @@ -370,21 +688,87 @@ static GF_Err dvblin_process(GF_Filter *filter) { GF_FilterPacket *dst_pck; + Bool first; + u32 nb_pck=50; u8 *out_data; GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); - if (!ctx->freq) return GF_EOS; + if (!ctx->freq || !ctx->playing) return GF_EOS; -#ifndef GPAC_SIM_LINUX_DVB - u32 nb_read = read(ctx->demux_fd, ctx->block, ctx->block_size); - if (!nb_read) return GF_OK; -#endif +restart: + first=GF_FALSE; + + s32 nb_read = read(ctx->demux_fd, ctx->block, ctx->block_size); + if (nb_read<=0) { + if ((errno == EAGAIN) || (errno==EINTR)) { + if (ctx->tune_start_time && (gf_sys_clock() - ctx->tune_start_time >= ctx->timeout)) { + gf_filter_setup_failure(filter, GF_IP_UDP_TIMEOUT); + dvblin_stop(ctx); + return GF_EOS; + } + + gf_filter_ask_rt_reschedule(filter, 5000); + return GF_OK; + } + return GF_IO_ERR; + } + ctx->tune_start_time=0; + if (!ctx->pid) { + GF_Err e = gf_filter_pid_raw_new(filter, ctx->src, GF_FALSE, "video/mp2t", "ts", ctx->block, nb_read, GF_TRUE, &ctx->pid); + if (e) { + gf_filter_setup_failure(filter, e); + return e; + } + first = GF_TRUE; + if (ctx->main_pid) { + gf_filter_pid_set_property_str(ctx->pid, "filter_pid", &PROP_UINT(ctx->main_pid)); + } + } dst_pck = gf_filter_pck_new_alloc(ctx->pid, nb_read, &out_data); if (!dst_pck) return GF_OUT_OF_MEM; memcpy(out_data, ctx->block, nb_read); - gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_framing(dst_pck, first, GF_FALSE); gf_filter_pck_send(dst_pck); + + nb_pck--; + if (nb_pck) + goto restart; + return GF_OK; +} + +GF_FilterProbeScore dvblin_probe_url(const char *url, const char *mime_type) +{ + if (!strnicmp(url, "dvb://", 6)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static GF_Err dvbin_list_channels(GF_FilterRegister *for_reg, void *_for_ctx); + +GF_Err dvblin_initialize(GF_Filter *filter) +{ + GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->src) return GF_BAD_PARAM; + if (strnicmp(ctx->src, "dvb://", 6)) return GF_BAD_PARAM; + + if (!gf_file_exists(ctx->chcfg)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("DVB Missing channel configuration file %s\n", ctx->chcfg)); + return GF_BAD_PARAM; + } + if (!stricmp(ctx->src, "dvb://@chlist")) { + ctx->freq = 0; + return dvbin_list_channels(NULL, ctx); + } + + ctx->demux = ctx->frontend = ctx->demux_fd = -1; + GF_Err e = dvblin_setup_demux(ctx); + + if (e) return e; + + ctx->block = gf_malloc(ctx->block_size +1); + //auto play + ctx->playing = GF_TRUE; return GF_OK; } @@ -395,7 +779,6 @@ } #endif //GPAC_HAS_LINUX_DVB - #ifdef GPAC_HAS_LINUX_DVB #define OFFS(_n) #_n, offsetof(GF_DVBLinuxCtx, _n) #else @@ -405,47 +788,160 @@ static const GF_FilterArgs DVBLinuxArgs = { { OFFS(src), "URL of source content", GF_PROP_NAME, NULL, NULL, 0}, - { OFFS(block_size), "block size used to read file", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED}, - { OFFS(chcfg), "path to channels.conf file", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read device", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(chcfg), "path to channels configuration file", GF_PROP_NAME, "$GCFG/channels.conf", NULL, 0}, + { OFFS(dev), "path to DVB adapter - if first character is a number, this is the device index", GF_PROP_STRING, "0", NULL, 0}, + { OFFS(idx), "frontend index", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(timeout), "timeout in ms before tune failure", GF_PROP_UINT, "5000", NULL, 0}, + { OFFS(csleep), "config sleep in ms between DiSEqC commands", GF_PROP_UINT, "15", NULL, 0}, + { OFFS(csidx), "committed switch index for DiSEqC", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(chans), "list of all channels, only pupulated for dvb://@chlist URL", GF_PROP_STRING_LIST, NULL, NULL, 0}, {0} }; +static const GF_FilterCapability DVBLinuxCaps = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + GF_FilterRegister DVBLinuxRegister = { .name = "dvbin", GF_FS_SET_DESCRIPTION("DVB for Linux") - GF_FS_SET_HELP("Experimental DVB support for linux, requires a channel config file through -chcfg()\n" - " \n" - "The URL syntax is `dvb://CHANNAME@FRONTEND`, with:\n" - " - CHANNAME: the channel name as listed in the channel config file\n" - " - frontend: the index of the DVB adapter to use (optional, default is 0)\n" + GF_FS_SET_HELP("This filter reads raw MPEG-2 TS from DVB-T/T2 and DVB-S/S2 cards on linux.\n" + "\n" + "The URL scheme used is `dvb://` with the following syntaxes:\n" + "- `dvb://CHAN`: tunes to channel `CHAN` in the channel configuration file.\n" + "- `dvb://+CHAN`: tunes to multiplex contaning channel `CHAN` and expose all programs.\n" + "- `dvb://=N`: tunes to the `N-th` channel in the channel configuration file.\n" + "- `dvb://@FREQ`: tunes to frequency `FREQ` and exposes all channels in multiplex.\n" + "- `dvb://@=N`: tunes to `N-th` frequency and exposes all channels in multiplex.\n" + "- `dvb://@chlist`: populates the -chans() option with available channels in the configuration file and do nothing else.\n" + "\n" + "When tuning by channel name `CHAN`, the first entry in the channel configuration file starting with `CHAN` will be used.\n" + "\n" + "The channel configuration file is set through -chcfg(). The expected format is VDR as produced by `w_scan`, with a syntax extended for comment lines, starting with `#`.\n" + "Within a comment line, the following keywords can be used to override defaults:\n" + " - `dev=N`: set the adapter index (`N` integer) or full path (`N` string).\n" + " - `idx=K`: set the frontend index `K`.\n" + " - `csidx=S`: set the committed switch index for DiSEqC.\n" + "\n" + "To view the default channels, use `gpac -hx dvbin`.\n" ) .args = DVBLinuxArgs, + SETCAPS(DVBLinuxCaps), #ifdef GPAC_HAS_LINUX_DVB - .private_size = sizeof(GF_DVBLinuxCtx), .initialize = dvblin_initialize, + .private_size = sizeof(GF_DVBLinuxCtx), .finalize = dvblin_finalize, .process = dvblin_process, .process_event = dvblin_process_event, - .probe_url = dvblin_probe_url + .probe_url = dvblin_probe_url, #else .process = dvblin_process, #endif + .hint_class_type = GF_FS_CLASS_MM_IO }; +#if !defined(GPAC_DISABLE_DOC) +void dvbin_cleanreg(GF_FilterSession *session, GF_FilterRegister *freg) +{ + gf_free((char*)freg->help); +} +#endif + +#if defined(GPAC_HAS_LINUX_DVB) || !defined(GPAC_DISABLE_DOC) +static GF_Err dvbin_list_channels(GF_FilterRegister *for_reg, void *_for_ctx) +{ +#if defined(GPAC_HAS_LINUX_DVB) + GF_DVBLinuxCtx *for_ctx = _for_ctx; +#endif + + FILE *chcfg = NULL; + if (for_reg) { + //list all available channels + char szPATHGF_MAX_PATH; + gf_sys_solve_path("$GCFG/channels.conf", szPATH); + + chcfg = gf_fopen(szPATH, "rb"); + } +#if defined(GPAC_HAS_LINUX_DVB) + else { + chcfg = gf_fopen(for_ctx->chcfg, "rb"); + } +#endif + if (!chcfg) return GF_URL_ERROR; + + char *all_channels = NULL; + while(!gf_feof(chcfg)) { + char line255; + if ( gf_fgets(line, 255, chcfg) == NULL) break; + if (line0=='#') continue; + if (line0=='\r') continue; + if (line0=='\n') continue; + + char *s1 = strchr(line, ':'); + if (!s1) continue; + char *s2 = strchr(s1+1, ':'); + if (!s2) continue; + s10 = s20 = 0; + s2 = strchr(line, '|'); + if (s2) s20 = 0; + s2 = strchr(line, ';'); + if (s2) s20 = 0; + + if (for_reg) { + if (!all_channels) { + all_channels = gf_strdup(DVBLinuxRegister.help); + gf_dynstrcat(&all_channels, "\nAvailable channels in default config:\n", NULL); + } + gf_dynstrcat(&all_channels, "* ", NULL); + gf_dynstrcat(&all_channels, line, NULL); + gf_dynstrcat(&all_channels, " (freq ", NULL); + gf_dynstrcat(&all_channels, s1+1, NULL); + gf_dynstrcat(&all_channels, ")\n", NULL); + continue; + } + +#if defined(GPAC_HAS_LINUX_DVB) + for_ctx->chans.vals = gf_realloc(for_ctx->chans.vals, sizeof(char **)*(for_ctx->chans.nb_items+1)); + for_ctx->chans.valsfor_ctx->chans.nb_items = gf_strdup(line); + for_ctx->chans.nb_items++; +#endif + } + gf_fclose(chcfg); + + if (all_channels) { + DVBLinuxRegister.help = all_channels; + DVBLinuxRegister.register_free = dvbin_cleanreg; + } + return GF_OK; +} +#endif + const GF_FilterRegister *dvbin_register(GF_FilterSession *session) { -#if !defined(GPAC_HAS_LINUX_DVB) || !defined(GPAC_SIM_LINUX_DVB) - if (!gf_opts_get_bool("temp", "gendoc")) +#if !defined(GPAC_HAS_LINUX_DVB) + if (!gf_opts_get_bool("temp", "gendoc") && !gf_opts_get_bool("temp", "helponly")) return NULL; #ifdef GPAC_CONFIG_EMSCRIPTEN return NULL; #endif DVBLinuxRegister.version = "! Warning: DVB4Linux NOT AVAILABLE IN THIS BUILD !"; -#else + +#endif + if (gf_opts_get_bool("temp", "get_proto_schemes")) { gf_opts_set_key("temp_in_proto", DVBLinuxRegister.name, "dvb"); } + if (!gf_opts_get_bool("temp", "helpexpert")) { + return &DVBLinuxRegister; + } + +#ifndef GPAC_DISABLE_DOC + dvbin_list_channels(&DVBLinuxRegister, NULL); #endif + return &DVBLinuxRegister; } +
View file
gpac-2.4.0.tar.gz/src/filters/in_file.c -> gpac-26.02.0.tar.gz/src/filters/in_file.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / generic FILE input filter @@ -29,11 +29,6 @@ #ifndef GPAC_DISABLE_FIN -#ifdef GPAC_HAS_FD -#include <unistd.h> -#include <sys/stat.h> -#include <fcntl.h> -#endif enum{ FILE_RAND_NONE=0, @@ -52,6 +47,7 @@ u32 block_size; GF_PropData pck; GF_Fraction64 range; + GF_Fraction ptime; //only one output pid declared GF_FilterPid *pid; @@ -72,7 +68,6 @@ Bool no_failure; } GF_FileInCtx; - static GF_Err filein_initialize_ex(GF_Filter *filter) { GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter); @@ -85,12 +80,25 @@ if (!ctx || (!ctx->src && !ctx->pck.size) ) return GF_BAD_PARAM; + if (!strncmp(ctx->src, "gmem://", 7)) { + GF_Err e = gf_filter_pid_raw_gmem(filter, ctx->src, &ctx->pid); + ctx->is_end = GF_TRUE; + return e; + } + if (ctx->pck.size) { GF_FilterPacket *opck; GF_Err e = gf_filter_pid_raw_new(filter, NULL, NULL, NULL, NULL, ctx->pck.ptr, ctx->pck.size, GF_FALSE, &ctx->pid); if (e) return e; gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_URL, &PROP_STRING("NULL")); + if (ctx->ptime.den && (ctx->ptime.num>=0)) + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->ptime.den)); opck = gf_filter_pck_new_shared(ctx->pid, ctx->pck.ptr, ctx->pck.size, NULL); + if (ctx->ptime.den && (ctx->ptime.num>=0)) { + gf_filter_pck_set_dts(opck, ctx->ptime.num); + gf_filter_pck_set_cts(opck, ctx->ptime.num); + } + gf_filter_pck_set_sap(opck, GF_FILTER_SAP_1); gf_filter_pck_send(opck); gf_filter_pid_set_eos(ctx->pid); ctx->is_end = GF_TRUE; @@ -166,7 +174,7 @@ if ((ctx->fd<0) && strncmp(src, "gfio://", 7) && !gf_opts_get_bool("core", "no-fd") && (!prev_url || strncmp(prev_url, "gfio://", 7)) ) { - ctx->fd = open(src, O_RDONLY); + ctx->fd = gf_fd_open(src, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR); } else #endif if (!ctx->file) { @@ -204,9 +212,7 @@ #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - struct stat sb; - fstat(ctx->fd, &sb); - ctx->file_size = sb.st_size; + ctx->file_size = gf_fd_fsize(ctx->fd); } else #endif ctx->file_size = gf_fsize(ctx->file); @@ -230,7 +236,7 @@ } #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - lseek(ctx->fd, ctx->file_pos, SEEK_SET); + lseek_64(ctx->fd, ctx->file_pos, SEEK_SET); } else #endif gf_fseek(ctx->file, ctx->file_pos, SEEK_SET); @@ -293,8 +299,11 @@ return GF_FPROBE_SUPPORTED; return GF_FPROBE_NOT_SUPPORTED; } - if (strstr(src, "://")) + if (strstr(src, "://")) { + if (!strnicmp(url, "gmem://", 7) ) + return GF_FPROBE_SUPPORTED; return GF_FPROBE_NOT_SUPPORTED; + } //strip any fragment identifier @@ -348,7 +357,7 @@ #ifdef GPAC_HAS_FD res=0; if (ctx->fd>=0) { - res = lseek(ctx->fd, evt->seek.start_offset, SEEK_SET); + res = lseek_64(ctx->fd, evt->seek.start_offset, SEEK_SET); if (res>=0) res = 0; } else #endif @@ -697,7 +706,7 @@ char szStatus1024, *szSrc; szSrc = gf_file_basename(ctx->src); - sprintf(szStatus, "%s: % 16"LLD_SUF" /% 16"LLD_SUF" (%02.02f)", szSrc, (s64) ctx->file_pos, (s64) ctx->file_size, ((Double)ctx->file_pos*100.0)/ctx->file_size); + snprintf(szStatus, sizeof(szStatus), "%s: % 16"LLD_SUF" /% 16"LLD_SUF" (%02.02f)", szSrc, (s64) ctx->file_pos, (s64) ctx->file_size, ((Double)ctx->file_pos*100.0)/ctx->file_size); gf_filter_update_status(filter, (u32) (ctx->file_pos*10000/ctx->file_size), szStatus); } @@ -720,12 +729,13 @@ { OFFS(ext), "override file extension", GF_PROP_NAME, NULL, NULL, 0}, { OFFS(mime), "set file mime type", GF_PROP_NAME, NULL, NULL, 0}, { OFFS(pck), "data to use instead of file", GF_PROP_DATA, NULL, NULL, 0}, + { OFFS(ptime), "timing for data packet, ignored if den is 0", GF_PROP_FRACTION, "0/0", NULL, 0}, {0} }; static const GF_FilterCapability FileInCaps = { - CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), }; GF_FilterRegister FileInRegister = { @@ -740,6 +750,12 @@ "The special file name `randsc` is used to generate random data with `0x000001` start-code prefix.\n" "\n" "The filter handles both files and GF_FileIO objects as input URL.\n" + "\n" + "## Packet Injecting\n" + "The filter can be used to inject a single packet instead of a file using -pck() option.\n" + "No specific properties are attached, except a timescale if -ptime() is set.\n" + "EX gpac fin:pck=str@\"My Sample Text\":ptime=2500/100:#CodecID=stxt:#StreamType=text\n" + "This will declare the PID as WebVTT and send a single packet with payload `My Sample Text` and a timestamp value of 25 second.\n" ) .private_size = sizeof(GF_FileInCtx), .args = FileInArgs, @@ -749,7 +765,8 @@ .finalize = filein_finalize, .process = filein_process, .process_event = filein_process_event, - .probe_url = filein_probe_url + .probe_url = filein_probe_url, + .hint_class_type = GF_FS_CLASS_NETWORK_IO }; @@ -766,4 +783,3 @@ return NULL; } #endif // GPAC_DISABLE_FIN -
View file
gpac-2.4.0.tar.gz/src/filters/in_http.c -> gpac-26.02.0.tar.gz/src/filters/in_http.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / HTTP input filter using GPAC http stack @@ -31,8 +31,7 @@ #include <gpac/constants.h> #include <gpac/download.h> -typedef enum -{ +GF_OPT_ENUM (GF_HTTPInStoreMode, GF_HTTPIN_STORE_AUTO=0, GF_HTTPIN_STORE_DISK, GF_HTTPIN_STORE_DISK_KEEP, @@ -40,7 +39,7 @@ GF_HTTPIN_STORE_MEM_KEEP, GF_HTTPIN_STORE_NONE, GF_HTTPIN_STORE_NONE_KEEP, -} GF_HTTPInStoreMode; +); enum { @@ -53,7 +52,7 @@ { //options char *src; - u32 block_size; + u32 block_size, idelay; GF_HTTPInStoreMode cache; GF_Fraction64 range; char *ext; @@ -82,6 +81,7 @@ GF_Err last_state; Bool is_source_switch; Bool prev_was_init_segment; + u32 start_time; } GF_HTTPInCtx; static void httpin_notify_error(GF_Filter *filter, GF_HTTPInCtx *ctx, GF_Err e) @@ -106,9 +106,8 @@ if (!ctx || !ctx->src) return GF_BAD_PARAM; ctx->dm = gf_filter_get_download_manager(filter); -#ifndef GPAC_CONFIG_EMSCRIPTEN if (!ctx->dm) return GF_SERVICE_ERROR; -#endif + ctx->block = gf_malloc(ctx->block_size +1); flags = GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_PERSISTENT; @@ -135,10 +134,20 @@ server = strstr(ctx->src, "://"); if (server) server += 3; - if (server && strstr(server, "://")) { + + //for base64 segment embedding + if (server && !strncmp(server, "gmem://", 7)) { + GF_Err e = gf_filter_pid_raw_gmem(filter, server, &ctx->pid); + ctx->is_end = GF_TRUE; + return e; + } + if (server && strncmp(ctx->src, "http://gmcast", 13) && strstr(server, "://")) { ctx->is_end = GF_TRUE; return gf_filter_pid_raw_new(filter, server, server, NULL, NULL, NULL, 0, GF_FALSE, &ctx->pid); } + if (ctx->idelay) { + ctx->start_time = gf_sys_clock(); + } ctx->sess = gf_dm_sess_new(ctx->dm, ctx->src, flags, NULL, NULL, &e); if (e) { @@ -181,11 +190,26 @@ if (ctx->cached) gf_fclose(ctx->cached); } +#ifndef GPAC_DISABLE_NETWORK +Bool gf_dm_can_handle_url(const char *url); +#endif static GF_FilterProbeScore httpin_probe_url(const char *url, const char *mime_type) { if (!strnicmp(url, "http://", 7) ) return GF_FPROBE_SUPPORTED; if (!strnicmp(url, "https://", 8) ) return GF_FPROBE_SUPPORTED; if (!strnicmp(url, "gmem://", 7) ) return GF_FPROBE_SUPPORTED; + +#ifndef GPAC_DISABLE_NETWORK + if (!strnicmp(url, "file://", 7) ) return GF_FPROBE_NOT_SUPPORTED; + //libcurl handling of RTSP has lower priority + if (!strnicmp(url, "rtsp://", 7) ) return GF_FPROBE_MAYBE_SUPPORTED; + if (gf_dm_can_handle_url(url)) { + //TODO: investigate why curl support of rtmp seems problematic, lower priority in favor of ffdmx + if (!strnicmp(url, "rtmp://", 7) ) return GF_FPROBE_MAYBE_NOT_SUPPORTED; + return GF_FPROBE_SUPPORTED; + } +#endif + return GF_FPROBE_NOT_SUPPORTED; } @@ -204,6 +228,7 @@ static Bool httpin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { GF_Err e; + char *prev_url = NULL; GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter); if (evt->base.on_pid && (evt->base.on_pid != ctx->pid)) return GF_FALSE; @@ -212,6 +237,9 @@ //we only check PLAY for full_file_only hint case GF_FEVT_PLAY: ctx->full_file_only = evt->play.full_file_only; + if (ctx->pid) { + gf_filter_pid_set_info_str(ctx->pid, "aborted", NULL); + } //do NOT reset is_end to false, restarting the session is always done via a source_seek event return GF_TRUE; case GF_FEVT_STOP: @@ -223,6 +251,10 @@ gf_dm_sess_abort(ctx->sess); gf_dm_sess_del(ctx->sess); ctx->sess = NULL; + + if (ctx->pid) { + gf_filter_pid_set_info_str(ctx->pid, "aborted", &PROP_BOOL(GF_TRUE)); + } } httpin_set_eos(ctx); } @@ -255,13 +287,18 @@ return GF_TRUE; case GF_FEVT_SOURCE_SWITCH: if (evt->seek.source_switch) { - gf_fatal_assert(ctx->is_end); - gf_fatal_assert(!ctx->pck_out); + if (!ctx->is_end || ctx->pck_out) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPIn Scheduling error: switch to %s requested but input %s still in progress\n", gf_file_basename(evt->seek.source_switch), gf_file_basename(ctx->src) )); + + gf_filter_notification_failure(filter, GF_BAD_PARAM, GF_FALSE); + return GF_TRUE; + } if (ctx->src && ctx->sess && (ctx->cache!=GF_HTTPIN_STORE_DISK_KEEP) && !ctx->prev_was_init_segment) { gf_dm_delete_cached_file_entry_session(ctx->sess, ctx->src, GF_FALSE); } GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPIn Switch from %s to %s\n", gf_file_basename(ctx->src), gf_file_basename(evt->seek.source_switch) )); - if (ctx->src) gf_free(ctx->src); + + prev_url = ctx->src; ctx->src = gf_strdup(evt->seek.source_switch); } else { if (!ctx->is_end) { @@ -286,6 +323,8 @@ //handle isobmff:// url if (!strncmp(ctx->src, "isobmff://", 10)) { GF_FilterPacket *pck; + if (prev_url) gf_free(prev_url); + gf_filter_pid_raw_new(filter, ctx->src, ctx->src, NULL, NULL, NULL, 0, GF_FALSE, &ctx->pid); ctx->is_end = GF_TRUE; ctx->prev_was_init_segment = GF_TRUE; @@ -311,6 +350,7 @@ } ctx->nb_read = 0; ctx->last_state = GF_OK; + if (prev_url) gf_free(prev_url); return GF_TRUE; } ctx->last_state = GF_OK; @@ -334,18 +374,19 @@ } if (!e && (evt->seek.start_offset || evt->seek.end_offset)) - e = gf_dm_sess_set_range(ctx->sess, evt->seek.start_offset, evt->seek.end_offset, GF_TRUE); - - if (e) { + e = gf_dm_sess_set_range(ctx->sess, evt->seek.start_offset, evt->seek.end_offset, GF_TRUE); + + if (e) { //use info and not error, as source switch is done by dashin and can be scheduled too early in live cases //but recovered later, so we let DASH report the error GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPIn Cannot resetup session from URL %s: %s\n", ctx->src, gf_error_to_string(e) ) ); httpin_notify_error(filter, ctx, e); ctx->is_end = GF_TRUE; if (ctx->src) gf_free(ctx->src); - ctx->src = NULL; + ctx->src = prev_url; return GF_TRUE; } + if (prev_url) gf_free(prev_url); ctx->nb_read = ctx->file_size = 0; ctx->do_reconfigure = GF_TRUE; ctx->is_end = GF_FALSE; @@ -384,13 +425,21 @@ if (!ctx->pid) { if (ctx->nb_read) - return GF_SERVICE_ERROR; + return GF_SERVICE_ERROR; } else { //TODO: go on fetching data to cache even when not consuming, and reread from cache if (gf_filter_pid_would_block(ctx->pid)) return GF_OK; } + if (ctx->start_time) { + u32 diff = gf_sys_clock() - ctx->start_time; + if (diff < ctx->idelay) { + gf_filter_ask_rt_reschedule(filter, 1000*diff); + return GF_OK; + } + ctx->start_time=0; + } is_start = ctx->nb_read ? GF_FALSE : GF_TRUE; ctx->is_end = GF_FALSE; @@ -460,7 +509,7 @@ } } } - gf_blob_release(cached); + gf_blob_release(cached); } //we read from network else { @@ -472,7 +521,7 @@ gf_dm_sess_get_stats(ctx->sess, NULL, NULL, NULL, NULL, &bytes_per_sec, NULL); gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(8*bytes_per_sec) ); } - gf_filter_ask_rt_reschedule(filter, 1000); + gf_filter_ask_rt_reschedule(filter, gf_dm_sess_is_regulated(ctx->sess) ? 100000 : 1000); return GF_OK; } if (! ctx->nb_read) @@ -498,18 +547,26 @@ } gf_dm_sess_get_stats(ctx->sess, NULL, NULL, &total_size, &bytes_done, &bytes_per_sec, &net_status); - //wait until we have some data to declare the pid - if ((e!= GF_EOS) && !nb_read) { - gf_filter_ask_rt_reschedule(filter, 1000); - return GF_OK; - } + //special case for no data when first source - be silent after source switch giving 0 bytes + if (!ctx->initial_ack_done && (e==GF_EOS) && !nb_read && !ctx->nb_read && !total_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPIn No data in stream\n")); + httpin_notify_error(filter, ctx, GF_EOS); + ctx->is_end = GF_TRUE; + return GF_EOS; + } if (!ctx->pid || ctx->do_reconfigure) { u32 idx; GF_Err cfg_e; - const char *hname, *hval; - const char *cached = gf_dm_sess_get_cache_name(ctx->sess); + const char *hname, *hval, *cached; + //wait until we have some data to declare the pid + if ((e!= GF_EOS) && !nb_read) { + gf_filter_ask_rt_reschedule(filter, 1000); + return GF_OK; + } + + cached = gf_dm_sess_get_cache_name(ctx->sess); ctx->do_reconfigure = GF_FALSE; if ((e==GF_EOS) && cached) { @@ -523,7 +580,8 @@ e = GF_OK; } gf_assert(! (b_flags&GF_BLOB_IN_TRANSFER)); - memcpy(ctx->block, b_data, b_size); + if (b_data) + memcpy(ctx->block, b_data, b_size); nb_read = b_size; gf_blob_release(cached); } else { @@ -549,6 +607,17 @@ /*in test mode don't expose http headers (they contain date/version/etc)*/ if (! gf_sys_is_test_mode()) { + //remove old headers + idx=0; + while (1) { + u32 p4cc; + const char *pname; + const GF_PropertyValue *p = gf_filter_pid_enum_properties(ctx->pid, &idx, &p4cc, &pname); + if (!p) break; + if (p4cc) continue; + gf_filter_pid_set_property_str(ctx->pid, pname, NULL); + idx--; + } idx = 0; while (gf_dm_sess_enum_headers(ctx->sess, &idx, &hname, &hval) == GF_OK) { gf_filter_pid_set_property_dyn(ctx->pid, (char *) hname, & PROP_STRING(hval)); @@ -567,10 +636,14 @@ gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(ctx->file_size ? ctx->file_size : bytes_done) ); } } - byte_offset = ctx->nb_read; + //nb_read may be 0 and error = GF_OK to signal data has been patched somewhere on the reception buffer but not in a contiguous area since last fetch + //we send empty packets in this case for filters using the underlying cache object directly (eg isobmf demux) + //but only if we started dispatching bytes ctx->nb_read += nb_read; + if (!ctx->nb_read) return GF_OK; + if (ctx->file_size && (ctx->nb_read==ctx->file_size)) { if (net_status!=GF_NETIO_DATA_EXCHANGE) ctx->is_end = GF_TRUE; @@ -621,7 +694,7 @@ { OFFS(block_size), "block size used to read file", GF_PROP_UINT, "100000", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(cache), "set cache mode\n" "- auto: cache to disk if content length is known, no cache otherwise\n" - "- disk: cache to disk, discard once session is no longer used\n" + "- disk: cache to disk, discard once session is no longer used\n" "- keep: cache to disk and keep\n" "- mem: stores to memory, discard once session is no longer used\n" "- mem_keep: stores to memory, keep after session is reassigned but move to `mem` after first download\n" @@ -632,6 +705,7 @@ { OFFS(ext), "override file extension", GF_PROP_NAME, NULL, NULL, 0}, { OFFS(mime), "set file mime type", GF_PROP_NAME, NULL, NULL, 0}, { OFFS(blockio), "use blocking IO", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(idelay), "delay first request by the given number of ms", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -651,22 +725,88 @@ "\n" "Note: Unless disabled at session level (see -no-probe(CORE) ), file extensions are usually ignored and format probing is done on the first data block.") .private_size = sizeof(GF_HTTPInCtx), - .flags = GF_FS_REG_USE_SYNC_READ, +#ifdef GPAC_CONFIG_EMSCRIPTEN + .flags = GF_FS_REG_MAIN_THREAD, +#endif .args = HTTPInArgs, SETCAPS(HTTPInCaps), .initialize = httpin_initialize, .finalize = httpin_finalize, .process = httpin_process, .process_event = httpin_process_event, - .probe_url = httpin_probe_url + .probe_url = httpin_probe_url, + .hint_class_type = GF_FS_CLASS_NETWORK_IO }; +#ifdef GPAC_HAS_CURL +#include <curl/curl.h> + + +static void httpin_reg_free(GF_FilterSession *session, struct __gf_filter_register *freg) +{ + gf_free((char*)freg->help); +} + +#endif + const GF_FilterRegister *httpin_register(GF_FilterSession *session) { if (gf_opts_get_bool("temp", "get_proto_schemes")) { - gf_opts_set_key("temp_in_proto", HTTPInRegister.name, "http,https,gmem"); + char *all_protos = gf_strdup("http,https,gmem"); +#ifdef GPAC_HAS_CURL + curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); + u32 i=0; + while (ver && ver->protocolsi) { + if (!strcmp(ver->protocolsi, "file")) {} + else if (!strcmp(ver->protocolsi, "http")) {} + else if (!strcmp(ver->protocolsi, "https")) {} + else { + gf_dynstrcat(&all_protos, ver->protocolsi, ","); + } + i++; + } +#endif + gf_opts_set_key("temp_in_proto", HTTPInRegister.name, all_protos); + gf_free(all_protos); + } + + +#ifdef GPAC_HAS_CURL + if (!gf_opts_get_bool("temp", "helpexpert")) + return &HTTPInRegister; + + char *help = gf_strdup(HTTPInRegister.help); + gf_dynstrcat(&help, "\n## libCURL Support\n", NULL); + gf_dynstrcat(&help, "This build supports using libcurl for HTTP and other protocol downloads."\ + " For http(s), the default behaviour is to use GPAC and can be overridden using the option -curl(core).\n", NULL); + gf_dynstrcat(&help, "Session parameters can be set using the `curl` configuration section, eg `-cfg=curl:FTPPORT=222`.\n", NULL); + gf_dynstrcat(&help, "The key `curl:trace=yes` can be set to log all CURL activity using logs `http@debug`.\n\n", NULL); + gf_dynstrcat(&help, "Libcurl version: ", NULL); + curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); + gf_dynstrcat(&help, ver->version, NULL); + gf_dynstrcat(&help, "\nLibcurl options (name and value type):\n", NULL); + const struct curl_easyoption *opt = curl_easy_option_next(NULL); + while (opt) { + const char *otype=NULL; + switch (opt->type) { + case CURLOT_LONG: otype = "int"; break; + case CURLOT_VALUES: otype = "unsigned int"; break; + case CURLOT_OFF_T: otype = "unsigned long long"; break; + case CURLOT_STRING: otype = "string"; break; + default: break; + } + if (otype) { + gf_dynstrcat(&help, opt->name, "- "); + gf_dynstrcat(&help, otype, ": "); + gf_dynstrcat(&help, "\n", NULL); + } + opt = curl_easy_option_next(opt); } + HTTPInRegister.help = help; + HTTPInRegister.register_free = httpin_reg_free; +#endif + return &HTTPInRegister; } #else @@ -675,4 +815,3 @@ return NULL; } #endif // GPAC_USE_DOWNLOADER -
View file
gpac-2.4.0.tar.gz/src/filters/in_pipe.c -> gpac-26.02.0.tar.gz/src/filters/in_pipe.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / pipe input filter @@ -109,6 +109,7 @@ if (!strcmp(ctx->src, "-") || !strcmp(ctx->src, "stdin")) { ctx->is_stdin = GF_TRUE; ctx->mkp = GF_FALSE; + if (!ctx->timeout) ctx->timeout = 10000; #ifdef WIN32 _setmode(_fileno(stdin), _O_BINARY); #endif @@ -370,7 +371,11 @@ ctx->last_active_ms = now; } else if (now - ctx->last_active_ms > ctx->timeout) { GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("PipeIn Timeout detected after %d ms, aborting\n", now - ctx->last_active_ms )); - gf_filter_pid_set_eos(ctx->pid); + if (ctx->pid) { + gf_filter_pid_set_eos(ctx->pid); + } else { + gf_filter_setup_failure(filter, GF_SERVICE_ERROR); + } ctx->is_end = GF_TRUE; return GF_EOS; } @@ -515,6 +520,13 @@ //signal flush if (ctx->sigflush && ctx->pid) { + //sigflush is ignored if not a packet reassembly is in process, we force closing the packet + u8 *output; + GF_FilterPacket *pck = gf_filter_pck_new_alloc(ctx->pid, 0, &output); + if (pck) { + gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE); + gf_filter_pck_send(pck); + } gf_filter_pid_send_flush(ctx->pid); } //reset for longer reschedule time @@ -668,7 +680,7 @@ GF_FilterRegister PipeInRegister = { .name = "pin", - GF_FS_SET_DESCRIPTION("pipe input") + GF_FS_SET_DESCRIPTION("Pipe input") GF_FS_SET_HELP( "This filter handles generic input pipes (mono-directional) in blocking or non blocking mode.\n" "Warning: Input pipes cannot seek.\n" "Data format of the pipe may be specified using extension (either in file name or through -ext()) or MIME type through -mime().\n" @@ -679,6 +691,7 @@ "EX gpac -i - vout\n" "EX gpac -i stdin vout\n" "\n" + "When reading from stdin, the default timeout() is 10 seconds.\n" "# Named pipes\n" "The filter can handle reading from named pipes. The associated protocol scheme is `pipe://` when loaded as a generic input (e.g. `-i pipe://URL` where URL is a relative or absolute pipe name).\n" "On Windows hosts, the default pipe prefix is `\\\\.\\pipe\\gpac\\` if no prefix is set.\n" @@ -719,7 +732,8 @@ .finalize = pipein_finalize, .process = pipein_process, .process_event = pipein_process_event, - .probe_url = pipein_probe_url + .probe_url = pipein_probe_url, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/in_route.c -> gpac-26.02.0.tar.gz/src/filters/in_route.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / ROUTE (ATSC3, DVB-I) input filter @@ -23,302 +23,39 @@ * */ -#include <gpac/filters.h> -#include <gpac/route.h> -#include <gpac/network.h> -#include <gpac/thread.h> +#include "in_route.h" #ifndef GPAC_DISABLE_ROUTE -typedef struct -{ - u32 sid; - u32 tsi; - GF_FilterPid *opid; -} TSI_Output; - -typedef struct -{ - GF_FilterPid *opid; - char *seg_name; -} SegInfo; - -enum -{ - ROUTEIN_REPAIR_NO = 0, - ROUTEIN_REPAIR_SIMPLE, - ROUTEIN_REPAIR_STRICT, - ROUTEIN_REPAIR_FULL, -}; - -typedef struct -{ - //options - char *src, *ifce, *odir; - Bool gcache, kc, skipr, reorder, fullseg; - u32 buffer, timeout, stats, max_segs, tsidbg, rtimeout, nbcached, repair; - s32 tunein, stsi; - - //internal - GF_Filter *filter; - GF_DownloadManager *dm; - - char *clock_init_seg; - GF_ROUTEDmx *route_dmx; - u32 tune_service_id; - - u32 sync_tsi, last_toi; - - u32 start_time, tune_time, last_timeout; - GF_FilterPid *opid; - GF_List *tsi_outs; - - u32 nb_stats; - GF_List *received_seg_names; - - u32 nb_playing; - Bool initial_play_forced; -} ROUTEInCtx; - - -static Bool routein_repair_segment_ts(ROUTEInCtx *ctx, GF_ROUTEEventFileInfo *finfo) -{ - u32 i, pos; - Bool drop_if_first = GF_FALSE; - u8 *data = finfo->blob->data; - - - pos = 0; - for (i=0; i<finfo->nb_frags; i++) { - u32 start_range = finfo->fragsi.offset; - u32 end_range = finfo->fragsi.size; - - //if we missed first 4 packets, we cannot rely on PAT/PMT being present in the rest of the segment - //we could further check this at the demux level, but for now we drop the segment - if (!i && (start_range>4*188)) - drop_if_first = GF_TRUE; - - end_range += start_range; - //reset all missed byte ranges as padding packets - start_range -= pos; - while (start_range % 188) start_range++; - while (pos<start_range) { - datapos = 0x47; - datapos+1 = 0x1F; - datapos+2 = 0xFF; - datapos+3 = 0x10; - pos += 188; - } - //end range not aligned with a packet start, rewind position to prev packet start - while (end_range % 188) end_range--; - pos = end_range; - } - //and patch all end packets - while (pos<finfo->blob->size) { - datapos = 0x47; - datapos+1 = 0x1F; - datapos+2 = 0xFF; - datapos+3 = 0x10; - pos += 188; - } - //remove corrupted flag - finfo->blob->flags = 0; - return drop_if_first; -} - -//top boxes we look for in segments -static const char *top_codes = {"styp", "emsg", "prft", "moof", "mdat", "free", "sidx", "ssix"}; -static u32 nb_top_codes = GF_ARRAY_LENGTH(top_codes); - -static u32 next_top_level_box(GF_ROUTEEventFileInfo *finfo, u8 *data, u32 size, u32 *cur_pos, u32 *box_size) -{ - u32 pos = *cur_pos; - u32 cur_frag = 0; - while (cur_frag < finfo->nb_frags) { - //in range, can go - if ((finfo->fragscur_frag.offset <= pos) && (finfo->fragscur_frag.offset + finfo->fragscur_frag.size > pos)) { - break; - } - //before range, adjust pos - if (finfo->fragscur_frag.offset > pos) { - pos = finfo->fragscur_frag.offset; - break; - } - //after range, go to next - cur_frag++; - //current pos is outside last valid range, no more top-level boxes to parse - if (cur_frag==finfo->nb_frags) - return 0; - } - - while (pos + 8 < size) { - u32 i; - u32 type_idx = 0; - u32 first_box = 0; - u32 first_box_size = 0; - for (i=0; i<nb_top_codes; i++) { - if ((datapos==top_codesi0) && (datapos+1==top_codesi1) && (datapos+2==top_codesi2) && (datapos+3==top_codesi3)) { - first_box = pos; - type_idx = i; - break; - } - } - //we need at least 4 bytes size header - if (first_box<4) { - pos++; - continue; - } - first_box_size = GF_4CC(datafirst_box-4, datafirst_box-3, datafirst_box-2, datafirst_box-1); - if (first_box_size<8) { - pos++; - continue; - } - *cur_pos = first_box-4; - *box_size = first_box_size; - return GF_4CC(top_codestype_idx0, top_codestype_idx1, top_codestype_idx2, top_codestype_idx3); - } - return 0; -} - -static void routein_repair_segment_isobmf(ROUTEInCtx *ctx, GF_ROUTEEventFileInfo *finfo) -{ - u8 *data = finfo->blob->data; - u32 size = finfo->blob->size; - u32 pos = 0; - u32 prev_moof_pos = 0; - //walk through all possible top-level boxes in order - //if box completely in a received byte range, keep as is - //if mdat or free box, keep as is - //otherwise change box type to free - while ((u64)pos + 8 < size) { - u32 i; - Bool is_mdat = GF_FALSE; - Bool box_complete = GF_FALSE; - u32 prev_pos = pos; - u32 box_size = 0; - u32 type = next_top_level_box(finfo, data, size, &pos, &box_size); - //no more top-level found, patch from current pos until end of payload - if (!type) { - u32 remain = size - pos; - gf_assert(remain); - if (remain<8) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to patch end of corrupted segment, segment size not big enough to hold the final box header, something really corrupted in source data\n")); - return; - } - datapos = (remain>>24) & 0xFF; - datapos+1 = (remain>>16) & 0xFF; - datapos+2 = (remain>>8) & 0xFF; - datapos+3 = (remain) & 0xFF; - datapos+4 = 'f'; - datapos+5 = 'r'; - datapos+6 = 'e'; - datapos+7 = 'e'; - //remove corrupted flag - finfo->blob->flags = 0; - return; - } - //we missed a box header, insert one at previous pos, indicating a free box !! - if (pos > prev_pos) { - u32 missed_size = pos - prev_pos; - dataprev_pos = (missed_size>>24) & 0xFF; - dataprev_pos+1 = (missed_size>>16) & 0xFF; - dataprev_pos+2 = (missed_size>>8) & 0xFF; - dataprev_pos+3 = (missed_size) & 0xFF; - dataprev_pos+4 = 'f'; - dataprev_pos+5 = 'r'; - dataprev_pos+6 = 'e'; - dataprev_pos+7 = 'e'; - } - if (type == GF_4CC('f','r','e','e')) { - box_complete = GF_TRUE; - } else if (type == GF_4CC('m','d','a','t')) { - if (ctx->repair != ROUTEIN_REPAIR_STRICT) { - box_complete = GF_TRUE; - } else { - is_mdat = GF_TRUE; - } - } else if (type == GF_4CC('m','o','o','f')) { - prev_moof_pos = pos; - } - - if (!box_complete) { - //box is only partially received - for (i=0; i<finfo->nb_frags; i++) { - if (pos + box_size < finfo->fragsi.offset) - break; - if ((pos >= finfo->fragsi.offset) && (pos+box_size<=finfo->fragsi.offset + finfo->fragsi.size)) { - box_complete = GF_TRUE; - break; - } - } - } - if (box_complete) { - pos += box_size; - continue; - } - //incomplete mdat (strict mode), discard previous moof - if (is_mdat) { - dataprev_moof_pos+4 = 'f'; - dataprev_moof_pos+5 = 'r'; - dataprev_moof_pos+6 = 'e'; - dataprev_moof_pos+7 = 'e'; - } - //incomplete box, move to free (not changing size) - datapos+4 = 'f'; - datapos+5 = 'r'; - datapos+6 = 'e'; - datapos+7 = 'e'; - pos += box_size; - } - //remove corrupted flag - finfo->blob->flags = 0; -} - -static Bool routein_repair_segment(ROUTEInCtx *ctx, GF_ROUTEEventFileInfo *finfo) -{ - Bool drop_if_first = GF_FALSE; - if (ctx->repair==ROUTEIN_REPAIR_NO) - return GF_FALSE; - - if (finfo->blob->mx) - gf_mx_p(finfo->blob->mx); - - if (strstr(finfo->filename, ".ts") || strstr(finfo->filename, ".m2ts")) { - drop_if_first = routein_repair_segment_ts(ctx, finfo); - } else { - routein_repair_segment_isobmf(ctx, finfo); - } - - if (finfo->blob->mx) - gf_mx_v(finfo->blob->mx); - - return drop_if_first; -} static GF_FilterProbeScore routein_probe_url(const char *url, const char *mime) { if (!strnicmp(url, "atsc://", 7)) return GF_FPROBE_SUPPORTED; if (!strnicmp(url, "route://", 8)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "mabr://", 7)) return GF_FPROBE_SUPPORTED; return GF_FPROBE_NOT_SUPPORTED; } static void routein_finalize(GF_Filter *filter) { + u32 i; ROUTEInCtx *ctx = gf_filter_get_udta(filter); #ifdef GPAC_ENABLE_COVERAGE - if (gf_sys_is_cov_mode()) - gf_route_dmx_purge_objects(ctx->route_dmx, 1); + if (gf_sys_is_cov_mode()) + gf_route_dmx_purge_objects(ctx->route_dmx, 1); #endif - - if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); + + if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); if (ctx->route_dmx) gf_route_dmx_del(ctx->route_dmx); if (ctx->tsi_outs) { while (gf_list_count(ctx->tsi_outs)) { TSI_Output *tsio = gf_list_pop_back(ctx->tsi_outs); + gf_list_del(tsio->pending_repairs); + if (tsio->dash_rep_id) gf_free(tsio->dash_rep_id); gf_free(tsio); } gf_list_del(ctx->tsi_outs); @@ -336,90 +73,265 @@ } gf_list_del(ctx->received_seg_names); } + if (!ctx->seg_repair_reservoir && ctx->seg_repair_queue) + ctx->seg_repair_reservoir = gf_list_new(); + gf_list_transfer(ctx->seg_repair_reservoir, ctx->seg_repair_queue); + gf_list_del(ctx->seg_repair_queue); + while (gf_list_count(ctx->repair_servers)) { + RouteRepairServer *tmp = gf_list_pop_back(ctx->repair_servers); + if (tmp->service_id) gf_free(tmp->url); + gf_free(tmp); + } + gf_list_del(ctx->repair_servers); + while (gf_list_count(ctx->seg_repair_reservoir)) { + RepairSegmentInfo *rsi = gf_list_pop_back(ctx->seg_repair_reservoir); + if (!ctx->seg_range_reservoir && rsi->ranges) + ctx->seg_range_reservoir = gf_list_new(); + gf_list_transfer(ctx->seg_range_reservoir, rsi->ranges); + gf_list_del(rsi->ranges); + if (rsi->filename) gf_free(rsi->filename); + gf_free(rsi); + } + gf_list_del(ctx->seg_repair_reservoir); + + while (gf_list_count(ctx->seg_range_reservoir)) { + RouteRepairRange *rr = gf_list_pop_back(ctx->seg_range_reservoir); + gf_free(rr); + } + gf_list_del(ctx->seg_range_reservoir); + + while (gf_list_count(ctx->sample_deps_reservoir)) { + SampleDepInfo *sr = gf_list_pop_back(ctx->sample_deps_reservoir); + if (sr->refs) gf_free(sr->refs); + gf_free(sr); + } + gf_list_del(ctx->sample_deps_reservoir); + + for (i=0; i<ctx->max_sess; i++) { + if (ctx->http_repair_sessions && ctx->http_repair_sessionsi.dld) + gf_dm_sess_del(ctx->http_repair_sessionsi.dld); + } + gf_free(ctx->http_repair_sessions); } static void push_seg_info(ROUTEInCtx *ctx, GF_FilterPid *pid, GF_ROUTEEventFileInfo *finfo) { - if (ctx->received_seg_names) { - SegInfo *si; - GF_SAFEALLOC(si, SegInfo); - if (!si) return; - si->opid = pid; - si->seg_name = gf_strdup(finfo->filename); - gf_list_add(ctx->received_seg_names, si); - } - while (gf_list_count(ctx->received_seg_names) > ctx->max_segs) { - GF_FilterEvent evt; - SegInfo *si = gf_list_pop_front(ctx->received_seg_names); - GF_FEVT_INIT(evt, GF_FEVT_FILE_DELETE, si->opid); - evt.file_del.url = si->seg_name; - gf_filter_pid_send_event(si->opid, &evt); - gf_free(si->seg_name); - gf_free(si); - } + if (ctx->received_seg_names) { + SegInfo *si; + GF_SAFEALLOC(si, SegInfo); + if (!si) return; + si->opid = pid; + si->seg_name = gf_strdup(finfo->filename); + gf_list_add(ctx->received_seg_names, si); + } + while (gf_list_count(ctx->received_seg_names) > ctx->max_segs) { + GF_FilterEvent evt; + SegInfo *si = gf_list_pop_front(ctx->received_seg_names); + GF_FEVT_INIT(evt, GF_FEVT_FILE_DELETE, si->opid); + evt.file_del.url = si->seg_name; + gf_filter_pid_send_event(si->opid, &evt); + gf_free(si->seg_name); + gf_free(si); + } } -static void routein_send_file(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 evt_type) +static void routein_cleanup_objects(ROUTEInCtx *ctx, u32 service_id) { - if (!ctx->kc || !(finfo->blob->flags & GF_BLOB_CORRUPTED)) { - u8 *output; - char *ext; - GF_FilterPid *pid, **p_pid; - GF_FilterPacket *pck; - TSI_Output *tsio = NULL; - - p_pid = &ctx->opid; - if (finfo->tsi && ctx->stsi) { - u32 i, count = gf_list_count(ctx->tsi_outs); - for (i=0; i<count; i++) { - tsio = gf_list_get(ctx->tsi_outs, i); - if ((tsio->sid==service_id) && (tsio->tsi==finfo->tsi)) { - break; - } - tsio=NULL; - } - if (!tsio) { - GF_SAFEALLOC(tsio, TSI_Output); - if (!tsio) return; + while (gf_route_dmx_get_object_count(ctx->route_dmx, service_id)>1) { + if (! gf_route_dmx_remove_first_object(ctx->route_dmx, service_id)) + break; + } - tsio->tsi = finfo->tsi; - tsio->sid = service_id; - gf_list_add(ctx->tsi_outs, tsio); - } - p_pid = &tsio->opid; +} - if ((evt_type==GF_ROUTE_EVT_FILE) || (evt_type==GF_ROUTE_EVT_MPD)) { - if (ctx->skipr && !finfo->updated) return; - } +TSI_Output *routein_get_tsio(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo) +{ + TSI_Output *tsio; + if (!finfo->tsi || !ctx->stsi) return NULL; + u32 i, count = gf_list_count(ctx->tsi_outs); + for (i=0; i<count; i++) { + tsio = gf_list_get(ctx->tsi_outs, i); + if (tsio->sid!=service_id) continue; + if (tsio->tsi!=finfo->tsi) continue; + if (!tsio->dash_rep_id && !finfo->dash_rep_id) + return tsio; + if (!tsio->dash_rep_id || !finfo->dash_rep_id) continue; + if (!strcmp(tsio->dash_rep_id, finfo->dash_rep_id)) + return tsio; + } + GF_SAFEALLOC(tsio, TSI_Output); + if (!tsio) return NULL; + + tsio->tsi = finfo->tsi; + tsio->sid = service_id; + tsio->dash_rep_id = finfo->dash_rep_id ? gf_strdup(finfo->dash_rep_id) : NULL; + tsio->pending_repairs = gf_list_new(); + if (ctx->tunein==-3) tsio->delete_first = GF_TRUE; + + gf_list_add(ctx->tsi_outs, tsio); + return tsio; +} + +static void routein_send_file(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo, GF_ROUTEEventType evt_type) +{ + u8 *output; + char *ext; + GF_FilterPid *pid, **p_pid; + GF_FilterPacket *pck; + TSI_Output *tsio = NULL; + + p_pid = &ctx->opid; + if (finfo && finfo->tsi) { + //for non-segment data, do not forward corrupted files or repeated files if not asked for it + if ((evt_type==GF_ROUTE_EVT_FILE) || (evt_type==GF_ROUTE_EVT_MPD) || (evt_type==GF_ROUTE_EVT_HLS_VARIANT)) { + if (ctx->skipr && !finfo->updated) return; + if (finfo->blob->flags & GF_BLOB_CORRUPTED) return; } - pid = *p_pid; - if (!pid) { - pid = gf_filter_pid_new(ctx->filter); - (*p_pid) = pid; - gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + if (ctx->stsi) { + tsio = routein_get_tsio(ctx, service_id, finfo); + p_pid = &tsio->opid; } + } + pid = *p_pid; + + if (!pid) { + pid = gf_filter_pid_new(ctx->filter); + (*p_pid) = pid; + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + } + + if (!tsio || (tsio->current_toi != finfo->toi)) { gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(tsio ? tsio->tsi : service_id)); gf_filter_pid_set_property(pid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(service_id)); - gf_filter_pid_set_property(pid, GF_PROP_PID_URL, &PROP_STRING(finfo->filename)); - ext = gf_file_ext_start(finfo->filename); + if (!finfo) return; + + gf_assert(finfo->filename); + const char *repair_base_uri, *repair_server, *filename=finfo->filename; + gf_route_dmx_get_repair_info(ctx->route_dmx, service_id, &repair_base_uri, &repair_server); + if (repair_base_uri) { + u32 repair_base_uri_len = (u32) strlen(repair_base_uri); + if (!strncmp(finfo->filename, repair_base_uri, repair_base_uri_len)) { + filename += repair_base_uri_len+1; + } + } + //special case for init segments not sent with the service but sent on the config multicast + //we don't have a service associated hence no base URI, assume the name is URI/path/to/file and strip URI part + else if (!service_id) { + char *sep = strchr(finfo->filename, ':'); + if (sep && !strncmp(sep, "://", 3)) sep += 3; + else if (sep) sep++; + if (sep) sep = strchr(sep, '/'); + if (sep) filename = sep+1; + } + + gf_filter_pid_set_property(pid, GF_PROP_PID_URL, &PROP_STRING(filename)); + ext = gf_file_ext_start(filename); gf_filter_pid_set_property(pid, GF_PROP_PID_FILE_EXT, &PROP_STRING(ext ? (ext+1) : "*" )); + if (tsio) { + tsio->current_toi = finfo->toi; + tsio->bytes_sent = 0; + + if (finfo->dash_period_id) gf_filter_pid_set_property(pid, GF_PROP_PID_PERIOD_ID, &PROP_STRING(finfo->dash_period_id)); + if (finfo->dash_as_id>=0) gf_filter_pid_set_property(pid, GF_PROP_PID_AS_ID, &PROP_UINT(finfo->dash_as_id)); + if (finfo->dash_rep_id) gf_filter_pid_set_property(pid, GF_PROP_PID_REP_ID, &PROP_STRING(finfo->dash_rep_id)); + } + if (repair_server) { + GF_PropertyValue rs; + rs.type = GF_PROP_STRING_LIST_COPY; + rs.value.string_list.nb_items = 1; + rs.value.string_list.vals = (char**)&repair_server; + gf_filter_pid_set_property(pid, GF_PROP_PID_MABR_URLS, &rs); + } else { + gf_filter_pid_set_property(pid, GF_PROP_PID_MABR_URLS, NULL); + } + } + //if we split TSIs we need to signal corrupted packets + if (!tsio && ctx->kc && (finfo->blob->flags & (GF_BLOB_CORRUPTED|GF_BLOB_PARTIAL_REPAIR) )) { + routein_cleanup_objects(ctx, service_id); + return; + } + +/* + //uncomment to disable progressive dispatch + if (tsio && (evt_type==GF_ROUTE_EVT_DYN_SEG_FRAG)) + return; +*/ + + u32 to_write = finfo->blob->size; + //check progressive mode state when repair is on + if ((evt_type>=GF_ROUTE_EVT_FILE) && ctx->repair) { + //we are progressive, so we shall never be called with missing start + gf_assert(finfo->frags0.offset == 0); + if (evt_type != GF_ROUTE_EVT_DYN_SEG_FRAG) { + //full file, we shall have a single fragment with same size as the file + gf_assert(finfo->nb_frags == 1); + //in iso repair we may skip patching of last frag if mdat is incomplete but full header is present + if (!ctx->riso) { + gf_assert(finfo->frags0.size == finfo->blob->size); + gf_assert(finfo->frags0.size == finfo->total_size); + } + } else if (tsio) { + //we can only disptach from first block + to_write = finfo->frags0.size; + } + } - pck = gf_filter_pck_new_alloc(pid, finfo->blob->size, &output); + u32 offset=0; + if (tsio && tsio->bytes_sent) { + if (tsio->bytes_sent > to_write) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Invalid progressive dispatch %u bytes sent but %u max\n", ctx->log_name, tsio->bytes_sent , to_write )); + //ignored in release, a broken file might be dispatched (typically truncation of file after repair) + gf_assert(0); + return; + } + offset = tsio->bytes_sent; + to_write = to_write - tsio->bytes_sent; + } + Bool is_end = GF_FALSE; + if (to_write) { + pck = gf_filter_pck_new_alloc(pid, to_write, &output); if (pck) { - memcpy(output, finfo->blob->data, finfo->blob->size); - if (finfo->blob->flags & GF_BLOB_CORRUPTED) gf_filter_pck_set_corrupted(pck, GF_TRUE); + memcpy(output, finfo->blob->data + offset, to_write); + if (finfo->blob->flags & (GF_BLOB_CORRUPTED|GF_BLOB_PARTIAL_REPAIR)) { + gf_filter_pck_set_corrupted(pck, GF_TRUE); + if (finfo->blob->flags & GF_BLOB_PARTIAL_REPAIR) + gf_filter_pck_set_property(pck, GF_PROP_PCK_PARTIAL_REPAIR, &PROP_BOOL(GF_TRUE) ); + } + + Bool start = offset==0 ? GF_TRUE : GF_FALSE; + is_end = (evt_type==GF_ROUTE_EVT_DYN_SEG_FRAG) ? GF_FALSE : GF_TRUE; + gf_filter_pck_set_framing(pck, start, is_end); + if (tsio && start) + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENUM, &PROP_STRING(finfo->filename)); + gf_filter_pck_send(pck); } - - if (ctx->max_segs && (evt_type==GF_ROUTE_EVT_DYN_SEG)) - push_seg_info(ctx, pid, finfo); - } + if (tsio) tsio->bytes_sent += to_write; + } else if (evt_type!=GF_ROUTE_EVT_DYN_SEG_FRAG) { + if (tsio && tsio->bytes_sent) { + pck = gf_filter_pck_new_alloc(pid, 0, &output); + if (finfo->blob->flags & (GF_BLOB_CORRUPTED|GF_BLOB_PARTIAL_REPAIR)) { + gf_filter_pck_set_corrupted(pck, GF_TRUE); + if (finfo->blob->flags & GF_BLOB_PARTIAL_REPAIR) + gf_filter_pck_set_property(pck, GF_PROP_PCK_PARTIAL_REPAIR, &PROP_BOOL(GF_TRUE) ); + } - while (gf_route_dmx_get_object_count(ctx->route_dmx, service_id)>1) { - if (! gf_route_dmx_remove_first_object(ctx->route_dmx, service_id)) - break; + gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE); + gf_filter_pck_send(pck); + is_end = GF_TRUE; + } + } + //release current TOI in case we have data from next segment being progressively dispatched + if (tsio && is_end) { + tsio->current_toi = 0; + tsio->bytes_sent = 0; } + + if (ctx->max_segs && (evt_type==GF_ROUTE_EVT_DYN_SEG)) + push_seg_info(ctx, pid, finfo); + + routein_cleanup_objects(ctx, service_id); } static void routein_write_to_disk(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 evt_type) @@ -429,25 +341,25 @@ if (!finfo->blob) return; - if ((finfo->blob->flags & GF_BLOB_CORRUPTED) && !ctx->kc) + if ((finfo->blob->flags & GF_BLOB_CORRUPTED) && !ctx->kc) { + routein_cleanup_objects(ctx, service_id); return; - + } + sprintf(szPath, "%s/service%d/%s", ctx->odir, service_id, finfo->filename); out = gf_fopen(szPath, "wb"); if (!out) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to create MPD file %s\n", service_id, szPath )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Service %d failed to create MPD file %s\n", ctx->log_name, service_id, szPath )); } else { u32 bytes = (u32) gf_fwrite(finfo->blob->data, finfo->blob->size, out); gf_fclose(out); if (bytes != finfo->blob->size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to write file %s: %d written for %d total\n", service_id, finfo->filename, bytes, finfo->blob->size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Service %d failed to write file %s: %d written for %d total\n", ctx->log_name, service_id, finfo->filename, bytes, finfo->blob->size)); } } - while (gf_route_dmx_get_object_count(ctx->route_dmx, service_id)>1) { - if (! gf_route_dmx_remove_first_object(ctx->route_dmx, service_id)) - break; - } + + routein_cleanup_objects(ctx, service_id); if (ctx->max_segs && (evt_type==GF_ROUTE_EVT_DYN_SEG)) { gf_list_add(ctx->received_seg_names, gf_strdup(szPath)); @@ -462,54 +374,37 @@ } } - - -void routein_on_event(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo) +void routein_on_event_file(ROUTEInCtx *ctx, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo, Bool is_defer_repair, Bool drop_if_first) { char szPathGF_MAX_PATH; - ROUTEInCtx *ctx = (ROUTEInCtx *)udta; + char *mime; u32 nb_obj; Bool is_init = GF_TRUE; - Bool drop_if_first = GF_FALSE; Bool is_loop = GF_FALSE; DownloadedCacheEntry cache_entry; - - //events without finfo - if (evt==GF_ROUTE_EVT_SERVICE_FOUND) { - if (!ctx->tune_time) ctx->tune_time = gf_sys_clock(); - return; - } - if (evt==GF_ROUTE_EVT_SERVICE_SCAN) { - if (ctx->tune_service_id && !gf_route_dmx_find_atsc3_service(ctx->route_dmx, ctx->tune_service_id)) { - - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Asked to tune to service %d but no such service, tuning to first one\n", ctx->tune_service_id)); - - ctx->tune_service_id = 0; - gf_route_atsc3_tune_in(ctx->route_dmx, (u32) -2, GF_TRUE); - } - return; - } - if (!finfo) - return; - - //events without finfo->blob - if (evt==GF_ROUTE_EVT_FILE_DELETE) { - if (ctx->gcache) { - sprintf(szPath, "http://groute/service%d/%s", evt_param, finfo->filename); - gf_dm_add_cache_entry(ctx->dm, szPath, NULL, 0, 0, "video/mp4", GF_FALSE, 0); - } - return; - } - - if (!finfo->blob) - return; + ctx->evt_interrupt = GF_TRUE; + + gf_assert(finfo->blob); + gf_mx_p(finfo->blob->mx); + //set blob flags + if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) + finfo->blob->flags |= GF_BLOB_IN_TRANSFER; + else + finfo->blob->flags &= ~GF_BLOB_IN_TRANSFER; + + if (finfo->partial==GF_LCTO_PARTIAL_ANY) + finfo->blob->flags |= GF_BLOB_CORRUPTED; + else + finfo->blob->flags &= ~GF_BLOB_CORRUPTED; + gf_mx_v(finfo->blob->mx); cache_entry = finfo->udta; szPath0 = 0; switch (evt) { case GF_ROUTE_EVT_MPD: + case GF_ROUTE_EVT_HLS_VARIANT: if (!ctx->tune_time) ctx->tune_time = gf_sys_clock(); - + if (ctx->odir) { routein_write_to_disk(ctx, evt_param, finfo, evt); break; @@ -518,39 +413,46 @@ routein_send_file(ctx, evt_param, finfo, evt); break; } - - if (!ctx->opid) { - ctx->opid = gf_filter_pid_new(ctx->filter); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + sprintf(szPath, "http://gmcast/service%d/%s", evt_param, finfo->filename); + mime = finfo->mime ? (char*)finfo->mime : "application/dash+xml"; + //also set x-mcast header to all manifest and variant + //if a clock info is present, also add it + cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->blob, 0, 0, mime, GF_TRUE, 0); + if (ctx->clock_init_seg) { + char szHdrGF_MAX_PATH; + sprintf(szHdr, "x-mcast: yes\r\nx-mcast-first-seg: %s\r\n", ctx->clock_init_seg); + gf_dm_force_headers(ctx->dm, cache_entry, szHdr); + } else { + gf_dm_force_headers(ctx->dm, cache_entry, "x-mcast: yes\r\n"); } - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(evt_param)); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(evt_param)); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("mpd")); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("application/dash+xml")); - - sprintf(szPath, "http://groute/service%d/%s", evt_param, finfo->filename); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_REDIRECT_URL, &PROP_STRING(szPath)); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_URL, &PROP_STRING(szPath)); - cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->blob, 0, 0, "application/dash+xml", GF_TRUE, 0); + if (evt==GF_ROUTE_EVT_MPD) { + char *fext = finfo->filename ? gf_file_ext_start(finfo->filename) : NULL; + if (fext) fext++; + else fext = "mpd"; - sprintf(szPath, "x-route: %d\r\n", evt_param); - gf_dm_force_headers(ctx->dm, cache_entry, szPath); - gf_route_dmx_set_service_udta(ctx->route_dmx, evt_param, cache_entry); - - ctx->sync_tsi = 0; - ctx->last_toi = 0; - if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); - ctx->clock_init_seg = NULL; - ctx->tune_service_id = evt_param; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(ctx->filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(evt_param)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(evt_param)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING(fext)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mime)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_REDIRECT_URL, &PROP_STRING(szPath)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_URL, &PROP_STRING(szPath)); + //remember the cache entry + gf_route_dmx_set_service_udta(ctx->route_dmx, evt_param, cache_entry); + ctx->sync_tsi = 0; + ctx->last_toi = 0; + ctx->tune_service_id = evt_param; + } break; case GF_ROUTE_EVT_DYN_SEG: - //corrupted file, try to repair - if (finfo->blob->flags & GF_BLOB_CORRUPTED) { - drop_if_first = routein_repair_segment(ctx, finfo); + if (!finfo->channel_hint) { + routein_check_type(ctx, finfo, evt_param); } - if (ctx->odir) { routein_write_to_disk(ctx, evt_param, finfo, evt); break; @@ -559,50 +461,51 @@ routein_send_file(ctx, evt_param, finfo, evt); break; } + //reset of clock sync is done at each cache discard, for other case reset at each new file + if (!ctx->gcache && ctx->clock_init_seg) { + gf_free(ctx->clock_init_seg); + ctx->clock_init_seg = NULL; + } //fallthrough case GF_ROUTE_EVT_DYN_SEG_FRAG: - //for now we only push complete files - if (!ctx->gcache) { + if (!finfo->channel_hint) { + routein_check_type(ctx, finfo, evt_param); + } + + //for now we only write complete files + if (ctx->odir) { break; } + //no cache, write complete files unless stsi is set (for low latency file forwarding) + if (!ctx->gcache) { + if (ctx->stsi) + routein_send_file(ctx, evt_param, finfo, evt); + break; + } -#if 0 - //couldn't repair or this is a fragment - if ((finfo->blob->flags & GF_BLOB_CORRUPTED) && !ctx->kc) { - - //force updating the cache entry since we may have reallocated the data buffer - sprintf(szPath, "http://groute/service%d/%s", evt_param, finfo->filename); - if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) { - cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->blob, 0, 0, "video/mp4", GF_FALSE, finfo->download_ms); - } else { - if (ctx->fullseg) - break; - cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->blob, 0, 0, "video/mp4", GF_FALSE, finfo->download_ms); - } - //don't break yet, we want to signal the clock - } -#endif - if (!ctx->clock_init_seg - //if full seg push of previsously advertized init, reset x-route-ll header + //if full seg push of previously advertized init, reset x-mcast-ll header || ((evt==GF_ROUTE_EVT_DYN_SEG) && !strcmp(ctx->clock_init_seg, finfo->filename)) ) { + //store current seg if LL mode or full seg - MPD cache entry may still be null + //if MPD is sent after segment in the broadcast + if (!ctx->clock_init_seg && ((evt==GF_ROUTE_EVT_DYN_SEG) || ctx->llmode)) + ctx->clock_init_seg = gf_strdup(finfo->filename); + DownloadedCacheEntry mpd_cache_entry = gf_route_dmx_get_service_udta(ctx->route_dmx, evt_param); if (mpd_cache_entry) { - if (!ctx->clock_init_seg) - ctx->clock_init_seg = gf_strdup(finfo->filename); - sprintf(szPath, "x-route: %d\r\nx-route-first-seg: %s\r\n", evt_param, ctx->clock_init_seg); + sprintf(szPath, "x-mcast: yes\r\nx-mcast-first-seg: %s\r\n", ctx->clock_init_seg); if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) - strcat(szPath, "x-route-ll: yes\r\n"); + strcat(szPath, "x-mcast-ll: yes\r\n"); gf_dm_force_headers(ctx->dm, mpd_cache_entry, szPath); szPath0 = 0; } } if ((finfo->blob->flags & GF_BLOB_CORRUPTED) && !ctx->kc) - break; - + break; + is_init = GF_FALSE; if (!ctx->sync_tsi) { ctx->sync_tsi = finfo->tsi; @@ -610,16 +513,16 @@ if (drop_if_first) { break; } - } else if (ctx->sync_tsi == finfo->tsi) { - if (ctx->last_toi > finfo->toi) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Loop detected on service %d for TSI %u: prev TOI %u this toi %u\n", ctx->tune_service_id, finfo->tsi, ctx->last_toi, finfo->toi)); + } else if (!is_defer_repair && (ctx->sync_tsi == finfo->tsi)) { + if (ctx->cloop && (ctx->last_toi > finfo->toi + 100)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Loop detected on service %d for TSI %u: prev TOI %u this toi %u\n", ctx->log_name, ctx->tune_service_id, finfo->tsi, ctx->last_toi, finfo->toi)); gf_route_dmx_purge_objects(ctx->route_dmx, evt_param); is_loop = GF_TRUE; if (cache_entry) { if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); ctx->clock_init_seg = gf_strdup(finfo->filename); - sprintf(szPath, "x-route: %d\r\nx-route-first-seg: %s\r\nx-route-loop: yes\r\n", evt_param, ctx->clock_init_seg); + sprintf(szPath, "x-mcast: yes\r\nx-mcast-first-seg: %s\r\nx-mcast-loop: yes\r\n", ctx->clock_init_seg); gf_dm_force_headers(ctx->dm, cache_entry, szPath); szPath0 = 0; } @@ -641,25 +544,25 @@ if ((finfo->blob->flags & GF_BLOB_CORRUPTED) && !ctx->kc) return; + if (!ctx->llmode && (evt==GF_ROUTE_EVT_DYN_SEG_FRAG)) + return; if (!cache_entry) { - sprintf(szPath, "http://groute/service%d/%s", evt_param, finfo->filename); - + sprintf(szPath, "http://gmcast/service%d/%s", evt_param, finfo->filename); //we copy over the init segment, but only share the data pointer for segments - cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->blob, 0, 0, "video/mp4", is_init ? GF_TRUE : GF_FALSE, finfo->download_ms); + cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->blob, 0, 0, finfo->mime ? finfo->mime : "video/mp4", is_init ? GF_TRUE : GF_FALSE, finfo->download_ms); if (cache_entry) { - gf_dm_force_headers(ctx->dm, cache_entry, "x-route: yes\r\n"); + gf_dm_force_headers(ctx->dm, cache_entry, "x-mcast: yes\r\n"); finfo->udta = cache_entry; } } - - if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Pushing fragment from file %s to cache\n", finfo->filename)); + + if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) { break; - } - - - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Pushing file %s to cache\n", finfo->filename)); + } + finfo->blob->flags &=~ GF_BLOB_IN_TRANSFER; + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Pushing file %s to cache\n", ctx->log_name, finfo->filename)); if (ctx->max_segs && (evt==GF_ROUTE_EVT_DYN_SEG)) push_seg_info(ctx, ctx->opid, finfo); @@ -680,12 +583,53 @@ } } +void routein_on_event(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo) +{ + ROUTEInCtx *ctx = (ROUTEInCtx *)udta; + ctx->evt_interrupt = GF_TRUE; + + //events without finfo + if (evt==GF_ROUTE_EVT_SERVICE_FOUND) { + if (!ctx->tune_time) ctx->tune_time = gf_sys_clock(); + //special case when not using cache, create output pid to announce service ID asap + if (ctx->stsi) { + routein_send_file(ctx, evt_param, NULL, evt); + } + return; + } + if (evt==GF_ROUTE_EVT_SERVICE_SCAN) { + if (ctx->tune_service_id && !gf_route_dmx_find_atsc3_service(ctx->route_dmx, ctx->tune_service_id)) { + + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Asked to tune to service %d but no such service, tuning to first one\n", ctx->log_name, ctx->tune_service_id)); + + ctx->tune_service_id = 0; + gf_route_atsc3_tune_in(ctx->route_dmx, (u32) -2, GF_TRUE); + } + return; + } + if (!finfo) + return; + + if (evt==GF_ROUTE_EVT_FILE_DELETE) { + routein_repair_mark_file(ctx, evt_param, finfo->filename, GF_TRUE); + return; + } + + //partial, try to repair + if (ctx->repair && (finfo->partial || ctx->stsi)) { + //blob flags are set there + routein_queue_repair(ctx, evt, evt_param, finfo); + } else { + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + } +} + static Bool routein_local_cache_probe(void *par, char *url, Bool is_destroy) { ROUTEInCtx *ctx = (ROUTEInCtx *)par; u32 sid=0; char *subr; - if (strncmp(url, "http://groute/service", 21)) return GF_FALSE; + if (strncmp(url, "http://gmcast/service", 21)) return GF_FALSE; subr = strchr(url+21, '/'); subr0 = 0; @@ -694,16 +638,25 @@ //this is not a thread-safe callback (typically called from httpin filter) gf_filter_lock(ctx->filter, GF_TRUE); if (is_destroy) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Cache releasing object %s\n", ctx->log_name, url)); gf_route_dmx_remove_object_by_name(ctx->route_dmx, sid, subr+1, GF_TRUE); + //for non real-time netcap, we may need to reschedule processing + gf_filter_post_process_task(ctx->filter); + if (ctx->clock_init_seg) { + gf_free(ctx->clock_init_seg); + ctx->clock_init_seg = NULL; + } } else if (sid && (sid != ctx->tune_service_id)) { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Request on service %d but tuned on service %d, retuning\n", sid, ctx->tune_service_id)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Request on service %d but tuned on service %d, retuning\n", ctx->log_name, sid, ctx->tune_service_id)); ctx->tune_service_id = sid; ctx->sync_tsi = 0; ctx->last_toi = 0; if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); ctx->clock_init_seg = NULL; - gf_route_atsc3_tune_in(ctx->route_dmx, sid, GF_TRUE); + gf_route_atsc3_tune_in(ctx->route_dmx, sid, GF_TRUE); } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Cache accessing object %s\n", ctx->log_name, url)); + routein_repair_mark_file(ctx, sid, subr+1, GF_FALSE); //mark object as in-use to prevent it from being discarded gf_route_dmx_force_keep_object_by_name(ctx->route_dmx, sid, subr+1); } @@ -711,42 +664,70 @@ return GF_TRUE; } -static void routein_set_eos(GF_Filter *filter) +static void routein_set_eos(GF_Filter *filter, ROUTEInCtx *ctx, Bool no_reset) { u32 i, nb_out = gf_filter_get_opid_count(filter); for (i=0; i<nb_out; i++) { GF_FilterPid *opid = gf_filter_get_opid(filter, i); if (opid) gf_filter_pid_set_eos(opid); } + if (ctx->opid) { + gf_filter_pid_set_info_str(ctx->opid, "x-mcast-over", &PROP_STRING("yes") ); + } + if (!no_reset) + gf_route_dmx_reset_all(ctx->route_dmx); } static GF_Err routein_process(GF_Filter *filter) { + GF_Err e; + u32 resched = 50000; ROUTEInCtx *ctx = gf_filter_get_udta(filter); - if (!ctx->nb_playing) - return GF_EOS; + if (!ctx->nb_playing) { + e = routein_do_repair(ctx); + if (e==GF_IP_NETWORK_EMPTY) { + gf_filter_ask_rt_reschedule(filter, 4000); + return GF_OK; + } + return e; + } + + ctx->evt_interrupt = GF_FALSE; + + gf_route_dmx_check_timeouts(ctx->route_dmx); while (1) { - GF_Err e = gf_route_dmx_process(ctx->route_dmx); + e = gf_route_dmx_process(ctx->route_dmx); if (e == GF_IP_NETWORK_EMPTY) { - if (ctx->tune_time) { + if (!ctx->ka && ctx->tune_time) { if (!ctx->last_timeout) ctx->last_timeout = gf_sys_clock(); else { u32 diff = gf_sys_clock() - ctx->last_timeout; if (diff > ctx->timeout) { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE No data for %d ms, aborting\n", diff)); - routein_set_eos(filter); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s No data for %u ms, aborting\n", ctx->log_name, diff)); + routein_set_eos(filter, ctx, GF_FALSE); return GF_EOS; } } } - gf_filter_ask_rt_reschedule(filter, 1000); + //with decent buffer size >=50kB we should sustain at least 80 mbps per multicast stream with 5ms reschedule + if (gf_route_dmx_has_active_multicast(ctx->route_dmx)) + resched = 5000; break; } else if (!e) { ctx->last_timeout = 0; + if (!ctx->tune_time) ctx->start_time = gf_sys_clock(); + + if (ctx->evt_interrupt) break; + //uncomment these to slow down demuxer (useful when debugging low latency mode) +// gf_filter_ask_rt_reschedule(filter, 10000); +// break; } else if (e==GF_EOS) { - routein_set_eos(filter); + e = routein_do_repair(ctx); + //this only happens when reading from pcap, do not reset route demuxer as we want to parse all segments present in capture + if (e == GF_EOS) + routein_set_eos(filter, ctx, GF_TRUE); return e; } else { break; @@ -755,13 +736,19 @@ if (!ctx->tune_time) { u32 diff = gf_sys_clock() - ctx->start_time; if (diff>ctx->timeout) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE No data for %d ms, aborting\n", diff)); - gf_filter_setup_failure(filter, GF_SERVICE_ERROR); - routein_set_eos(filter); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s No data for %u ms, aborting\n", ctx->log_name, diff)); + gf_filter_setup_failure(filter, GF_IP_UDP_TIMEOUT); + routein_set_eos(filter, ctx, GF_FALSE); return GF_EOS; } } + GF_Err e_repair = routein_do_repair(ctx); + if ((e_repair==GF_IP_NETWORK_EMPTY) && (e == GF_IP_NETWORK_EMPTY)) + gf_filter_ask_rt_reschedule(filter, resched); + else if ((e_repair==GF_IP_NETWORK_EMPTY) || (e == GF_IP_NETWORK_EMPTY)) + gf_filter_ask_rt_reschedule(filter, 4000); + if (ctx->stats) { u32 now = gf_sys_clock() - ctx->start_time; if (now >= ctx->nb_stats*ctx->stats) { @@ -785,20 +772,55 @@ } } } - return GF_OK; } +RouteRepairServer *routein_push_repair_server(ROUTEInCtx *ctx, const char *url, u32 service_id) +{ + RouteRepairServer *server = NULL; + if (service_id) { + u32 i; + for (i=0;i<gf_list_count(ctx->repair_servers); i++) { + server = gf_list_get(ctx->repair_servers, i); + if (server->service_id==service_id) { + if (!strcmp(server->url, url)) return server; + gf_list_rem(ctx->repair_servers, i); + i--; + gf_free(server->url); + } + } + } + + GF_SAFEALLOC(server, RouteRepairServer); + server->accept_ranges = RANGE_SUPPORT_PROBE; + server->service_id = service_id; + server->support_h2 = GF_TRUE; + server->url = service_id ? gf_strdup(url) : (char*)url; + if (!ctx->repair_servers) ctx->repair_servers = gf_list_new(); + gf_list_add(ctx->repair_servers, server); + return server; +} static GF_Err routein_initialize(GF_Filter *filter) { Bool is_atsc = GF_TRUE; + Bool is_mabr = GF_FALSE; + u32 prot_offset=0; ROUTEInCtx *ctx = gf_filter_get_udta(filter); ctx->filter = filter; if (!ctx->src) return GF_BAD_PARAM; + ctx->log_name = "ATSC3"; + if (!strncmp(ctx->src, "route://", 8)) { is_atsc = GF_FALSE; + prot_offset = 8; + ctx->log_name = "ROUTE"; + } else if (!strncmp(ctx->src, "mabr://", 7)){ + is_atsc = GF_FALSE; + is_mabr = GF_TRUE; + prot_offset = 7; + ctx->log_name = "DVB-MABR"; } else if (strcmp(ctx->src, "atsc://")) return GF_BAD_PARAM; @@ -810,8 +832,7 @@ ctx->dm = gf_filter_get_download_manager(filter); if (!ctx->dm) return GF_SERVICE_ERROR; gf_dm_set_localcache_provider(ctx->dm, routein_local_cache_probe, ctx); - } else { - //for now progressive dispatch is only possible when populating cache + } else if (!ctx->stsi) { ctx->fullseg = GF_TRUE; } if (!ctx->nbcached) @@ -822,9 +843,9 @@ } else { char *sep, *root; u32 port; - sep = strrchr(ctx->src+8, ':'); + sep = strrchr(ctx->src+prot_offset, ':'); if (!sep) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing port number\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing port number\n", ctx->log_name)); return GF_BAD_PARAM; } sep0 = 0; @@ -833,19 +854,34 @@ port = atoi(sep+1); if (root) root0 = '/'; - if (!gf_sk_is_multicast_address(ctx->src+8)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE %s is not a multicast address\n", ctx->src)); - sep0 = ':'; - return GF_BAD_PARAM; + if (!gf_sk_is_multicast_address(ctx->src+prot_offset)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s %s is not a multicast address\n", ctx->log_name, ctx->src)); } - ctx->route_dmx = gf_route_dmx_new_ex(ctx->src+8, port, ctx->ifce, ctx->buffer, gf_filter_get_netcap_id(filter), routein_on_event, ctx); + + if (is_mabr) + ctx->route_dmx = gf_dvb_mabr_dmx_new(ctx->src+prot_offset, port, ctx->ifce, ctx->buffer, gf_filter_get_netcap_id(filter), routein_on_event, ctx); + else + ctx->route_dmx = gf_route_dmx_new_ex(ctx->src+prot_offset, port, ctx->ifce, ctx->buffer, gf_filter_get_netcap_id(filter), routein_on_event, ctx); sep0 = ':'; } if (!ctx->route_dmx) return GF_SERVICE_ERROR; - - gf_route_set_allow_progressive_dispatch(ctx->route_dmx, !ctx->fullseg); - gf_route_set_reorder(ctx->route_dmx, ctx->reorder, ctx->rtimeout); + //not using cache, we must dispatch in a progressive way for now. + //TODO: add repair to allow out of order disptach + if (!ctx->gcache && ctx->llmode) { + ctx->llmode = GF_FALSE; + } + if (ctx->gcache) ctx->stsi = GF_FALSE; + + //if llmode do out of order + //if split TSI with repair, we need out of order dispatch: because in tune-in the first segment is partial + //it may not be advertized until timeout/end in progressive mode which could happen after next segment start of reception + if (ctx->llmode || (ctx->stsi && ctx->repair)) { + gf_route_set_dispatch_mode(ctx->route_dmx, GF_ROUTE_DISPATCH_OUT_OF_ORDER); + } else { + gf_route_set_dispatch_mode(ctx->route_dmx, ctx->fullseg ? GF_ROUTE_DISPATCH_FULL : GF_ROUTE_DISPATCH_PROGRESSIVE); + } + gf_route_dmx_set_reorder(ctx->route_dmx, ctx->reorder, ctx->rtimeout); if (ctx->tsidbg) { gf_route_dmx_debug_tsi(ctx->route_dmx, ctx->tsidbg); @@ -853,15 +889,16 @@ if (ctx->tunein>0) ctx->tune_service_id = ctx->tunein; - if (is_atsc) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE ATSC 3.0 Tunein started\n")); + if (is_atsc || is_mabr) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Tunein started\n", ctx->log_name)); if (ctx->tune_service_id) - gf_route_atsc3_tune_in(ctx->route_dmx, ctx->tune_service_id, GF_FALSE); + gf_route_atsc3_tune_in(ctx->route_dmx, ctx->tune_service_id, GF_FALSE); else - gf_route_atsc3_tune_in(ctx->route_dmx, (u32) ctx->tunein, GF_TRUE); + gf_route_atsc3_tune_in(ctx->route_dmx, (u32) ctx->tunein, GF_TRUE); } ctx->start_time = gf_sys_clock(); + if (ctx->minrecv>100) ctx->minrecv = 100; if (ctx->stsi) ctx->tsi_outs = gf_list_new(); if (ctx->max_segs) @@ -869,6 +906,33 @@ ctx->nb_playing = 1; ctx->initial_play_forced = GF_TRUE; + if (ctx->repair_urls.nb_items > 0) { + u32 i; + ctx->repair = ROUTEIN_REPAIR_FULL; + for (i=0; i<ctx->repair_urls.nb_items; i++) { + routein_push_repair_server(ctx, ctx->repair_urls.valsi, 0); + } + } + + if ((ctx->repair >= ROUTEIN_REPAIR_FULL) || ctx->stsi) { + if (!ctx->max_sess) ctx->max_sess = 1; + //we need at least one session in fast repair mode + else if (ctx->repair < ROUTEIN_REPAIR_FULL) ctx->max_sess = 1; + + ctx->http_repair_sessions = gf_malloc(sizeof(RouteRepairSession)*ctx->max_sess); + memset(ctx->http_repair_sessions, 0, sizeof(RouteRepairSession)*ctx->max_sess); + + ctx->seg_repair_queue = gf_list_new(); + ctx->seg_repair_reservoir = gf_list_new(); + ctx->seg_range_reservoir = gf_list_new(); + } else { + ctx->max_sess = 0; + if (ctx->riso >= REPAIR_ISO_SIMPLE) { + ctx->seg_repair_queue = gf_list_new(); + ctx->seg_repair_reservoir = gf_list_new(); + ctx->seg_range_reservoir = gf_list_new(); + } + } return GF_OK; } @@ -879,8 +943,12 @@ if (!ctx->initial_play_forced) ctx->nb_playing++; ctx->initial_play_forced = GF_FALSE; - } else { + } else if (evt->base.type==GF_FEVT_STOP) { ctx->nb_playing--; + } else if (evt->base.type==GF_FEVT_DASH_QUALITY_SELECT) { + if (!ctx->dynsel) return GF_TRUE; + + gf_route_dmx_mark_active_quality(ctx->route_dmx, evt->dash_select.service_id, evt->dash_select.period_id, evt->dash_select.as_id, evt->dash_select.rep_id, (evt->dash_select.select_type==GF_QUALITY_SELECTED) ? GF_TRUE : GF_FALSE); } return GF_TRUE; } @@ -891,10 +959,14 @@ { OFFS(src), "URL of source content", GF_PROP_NAME, NULL, NULL, 0}, { OFFS(ifce), "default interface to use for multicast. If NULL, the default system interface will be used", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(gcache), "indicate the files should populate GPAC HTTP cache", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, - { OFFS(tunein), "service ID to bootstrap on for ATSC 3.0 mode (0 means tune to no service, -1 tune all services -2 means tune on first service found)", GF_PROP_SINT, "-2", NULL, 0}, + { OFFS(tunein), "service ID to bootstrap on. Special values:\n" + "- 0: tune to no service\n" + "- -1: tune all services\n" + "- -2: tune on first service found\n" + "- -3: detect all services and do not join multicast", GF_PROP_SINT, "-2", NULL, 0}, { OFFS(buffer), "receive buffer size to use in bytes", GF_PROP_UINT, "0x80000", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(timeout), "timeout in ms after which tunein fails", GF_PROP_UINT, "5000", NULL, 0}, - { OFFS(nbcached), "number of segments to keep in cache per service", GF_PROP_UINT, "8", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(nbcached), "number of segments to keep in cache per service", GF_PROP_UINT, "8", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(kc), "keep corrupted file", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(skipr), "skip repeated files (ignored in cache mode)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(stsi), "define one output PID per tsi/serviceID (ignored in cache mode)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, @@ -902,15 +974,30 @@ { OFFS(tsidbg), "gather only objects with given TSI (debug)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(max_segs), "maximum number of segments to keep on disk", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(odir), "output directory for standalone mode", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, - { OFFS(reorder), "ignore order flag in ROUTE/LCT packets, avoiding considering object done when TOI changes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, - { OFFS(rtimeout), "default timeout in ms to wait when gathering out-of-order packets", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(reorder), "consider packets are not always in order - if false, this will evaluate an LCT object as done when TOI changes", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cloop), "check for loops based on TOI (used for capture replay)", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(rtimeout), "default timeout in µs to wait when gathering out-of-order packets", GF_PROP_UINT, "500000", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(fullseg), "only dispatch full segments in cache mode (always true for other modes)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(repair), "repair mode for corrupted files\n" "- no: no repair is performed\n" "- simple: simple repair is performed (incomplete `mdat` boxes will be kept)\n" "- strict: incomplete mdat boxes will be lost as well as preceding `moof` boxes\n" - "- full: HTTP-based repair, not yet implemented" - , GF_PROP_UINT, "simple", "no|simple|strict|full", GF_FS_ARG_HINT_EXPERT}, + "- full: HTTP-based repair of all lost packets" + , GF_PROP_UINT, "strict", "no|simple|strict|full", GF_FS_ARG_HINT_EXPERT}, + { OFFS(repair_urls), "repair servers urls - if set, `repair` is set to `full`", GF_PROP_STRING_LIST, NULL, NULL, 0}, + { OFFS(max_sess), "max number of concurrent HTTP repair sessions", GF_PROP_UINT, "1", NULL, 0}, + { OFFS(llmode), "enable low-latency access", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(dynsel), "dynamically enable and disable multicast groups based on their selection state", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(range_merge), "merge ranges in HTTP repair if distant from less than given amount of bytes", GF_PROP_UINT, "10000", NULL, 0}, + { OFFS(minrecv), "redownload full file in HTTP repair if received bytes is less than given percentage of file size, 0 means complete file redownload if any error", GF_PROP_UINT, "20", NULL, 0}, + { OFFS(riso), "advanced options for ISOBMFF HTTP repair\n" + "- none: use regular http repair (sequential repair)\n" + "- simple: first repair all non-mdat boxes then repair mdat in order\n" + "- partial: only repair all non-mdat moxes, leaving holes in mdat\n" + "- deps: same as simple and repair only samples depended upon by other samples\n" + "- depx: same as deps but do not hide moof of incomplete mdat (tests only)", GF_PROP_UINT, "none", "none|simple|partial|deps|depx", 0}, + { OFFS(ka), "keep service alive if multicast is down", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(chkiso), "check isobmf structure after repair (debug)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -922,57 +1009,68 @@ GF_FilterRegister ROUTEInRegister = { .name = "routein", - GF_FS_SET_DESCRIPTION("ROUTE input") + GF_FS_SET_DESCRIPTION("MABR & ROUTE input") #ifndef GPAC_DISABLE_DOC - .help = "This filter is a receiver for ROUTE sessions (ATSC 3.0 and generic ROUTE).\n" + .help = "This filter is a receiver for file delivery over multicast. It currently supports ATSC 3.0, generic ROUTE and DVB-MABR flute.\n" "- ATSC 3.0 mode is identified by the URL `atsc://`.\n" "- Generic ROUTE mode is identified by the URL `route://IP:PORT`.\n" + "- DVB-MABR mode is identified by the URL `mabr://IP:PORT` pointing to the bootstrap FLUTE channel carrying the multicast gateway configuration.\n" "\n" "The filter can work in cached mode, source mode or standalone mode.\n" "# Cached mode\n" - "The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using `http://groute/serviceN/` as service root, `N being the ROUTE service ID.\n" + "The cached mode is the default filter behavior. It populates GPAC HTTP Cache with the received files, using `http://gmcast/serviceN/` as service root, `N being the multicast service ID.\n" "In cached mode, repeated files are always pushed to cache.\n" "The maximum number of media segment objects in cache per service is defined by -nbcached(); this is a safety used to force object removal in case DASH client timing is wrong and some files are never requested at cache level.\n" " \n" "The cached MPD is assigned the following headers:\n" - "- `x-route`: integer value, indicates the ROUTE service ID.\n" - "- `x-route-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast.\n" - "- `x-route-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling).\n" - "- `x-route-loop`: boolean value, if yes indicates a loop in the service has been detected (usually pcap replay loop).\n" + "- `x-mcast`: boolean value, if `yes` indicates the file comes from a multicast.\n" + "- `x-mcast-first-seg`: string value, indicates the name of the first segment (completely or currently being) retrieved from the broadcast.\n" + "- `x-mcast-ll`: boolean value, if yes indicates that the indicated first segment is currently being received (low latency signaling).\n" + "- `x-mcast-loop`: boolean value, if yes indicates a loop (e.g. pcap replay) in the service has been detected - only checked if -cloop() is set.\n" " \n" "The cached files are assigned the following headers:\n" - "- `x-route`: boolean value, if yes indicates the file comes from an ROUTE session.\n" + "- `x-mcast`: boolean value, if `yes` indicates the file comes from a multicast.\n" "\n" "If -max_segs() is set, file deletion event will be triggered in the filter chain.\n" "\n" "# Source mode\n" "In source mode, the filter outputs files on a single output PID of type `file`. " "The files are dispatched once fully received, the output PID carries a sequence of complete files. Repeated files are not sent unless requested.\n" - "If needed, one PID per TSI can be used rather than a single PID. This avoids mixing files of different mime types on the same PID (e.g. HAS manifest and ISOBMFF).\n" "EX gpac -i atsc://gcache=false -o $ServiceID$/$File$:dynext\n" "This will grab the files and forward them as output PIDs, consumed by the fout(fout) filter.\n" "\n" + "If needed, one PID per TSI can be used rather than a single PID using -stsi(). This avoids mixing files of different mime types on the same PID (e.g. HAS manifest and ISOBMFF).\n" + "In this mode, each packet starting a new file carries the file name as a property. If -repair() is enabled in this mode, progressive dispatch of files will be done.\n" + "\n" "If -max_segs() is set, file deletion event will be triggered in the filter chain.\n" + "Note: The -nbcached() option is ignored in this mode.\n" "\n" "# Standalone mode\n" "In standalone mode, the filter does not produce any output PID and writes received files to the -odir() directory.\n" "EX gpac -i atsc://:odir=output\n" "This will grab the files and write them to `output` directory.\n" "\n" + "In this mode, files are always written once completely recieved, regardless of the -repair() option.\n" + "\n" "If -max_segs() is set, old files will be deleted.\n" + "Note: The -nbcached() option is ignored in this mode.\n" "\n" "# File Repair\n" - "In case of losses or incomplete segment reception (during tune-in), the files are patched as follows:\n" + "In case of losses or incomplete segment reception (during tune-in or HTTP partial repair), the files are patched as follows:\n" "- MPEG-2 TS: all lost ranges are adjusted to 188-bytes boundaries, and transformed into NULL TS packets.\n" - "- ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in `free` boxes, except mdat kept as is if -repair() is set to simple.\n" + "- ISOBMFF: all top-level boxes are scanned, and incomplete boxes are transformed in `free` boxes, except `mdat`:\n" + " - if `repair=simple`, `mdat` is kept if incomplete (broken file),\n" + " - if `repair=strict`, `mdat` is moved to `free` if incomplete and the preceeding `moof` is also moved to `free`.\n" "\n" "If -kc() option is set, corrupted files will be kept. If -fullseg() is not set and files are only partially received, they will be kept.\n" "\n" + "Note: A partially patched segment is no longer considered corrupted and will be dispatched regardless of -kc().\n" + "\n" "# Interface setup\n" "On some systems (OSX), when using VM packet replay, you may need to force multicast routing on your local interface.\n" "For ATSC, you will have to do this for the base signaling multicast (224.0.23.60):\n" "EX route add -net 224.0.23.60/32 -interface vboxnet0\n" - "Then for each ROUTE service in the multicast:\n" + "Then for each multicast service in the multicast:\n" "EX route add -net 239.255.1.4/32 -interface vboxnet0\n" "", #endif //GPAC_DISABLE_DOC @@ -983,13 +1081,14 @@ SETCAPS(ROUTEInCaps), .process = routein_process, .process_event = routein_process_event, - .probe_url = routein_probe_url + .probe_url = routein_probe_url, + .hint_class_type = GF_FS_CLASS_NETWORK_IO }; const GF_FilterRegister *routein_register(GF_FilterSession *session) { if (gf_opts_get_bool("temp", "get_proto_schemes")) { - gf_opts_set_key("temp_in_proto", ROUTEInRegister.name, "atsc,route"); + gf_opts_set_key("temp_in_proto", ROUTEInRegister.name, "atsc,route,mabr"); } return &ROUTEInRegister; } @@ -1002,4 +1101,3 @@ } #endif /* GPAC_DISABLE_ROUTE */ -
View file
gpac-26.02.0.tar.gz/src/filters/in_route.h
Added
@@ -0,0 +1,235 @@ + /* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2025 + * All rights reserved + * + * This file is part of GPAC / ROUTE (ATSC3, DVB-I) input filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/filters.h> +#include <gpac/route.h> +#include <gpac/network.h> +#include <gpac/download.h> +#include <gpac/thread.h> + +#ifndef IN_ROUTE_H +#define IN_ROUTE_H + +#ifndef GPAC_DISABLE_ROUTE + +enum +{ + TSIO_FILE_PROGRESS = 1, + TSIO_REPAIR_SCHEDULED = (1<<1) +}; + +typedef struct +{ + u32 sid; + u32 tsi; + GF_FilterPid *opid; + //TOI of file being received - moved back to 0 once file is done being dispatched + u32 current_toi; + u32 bytes_sent; + char *dash_rep_id; + GF_List *pending_repairs; + u32 flags_progress; + Bool delete_first; +} TSI_Output; + +typedef struct +{ + GF_FilterPid *opid; + char *seg_name; +} SegInfo; + +GF_OPT_ENUM (ROUTEInRepairMode, + ROUTEIN_REPAIR_NO = 0, + ROUTEIN_REPAIR_SIMPLE, + ROUTEIN_REPAIR_STRICT, + ROUTEIN_REPAIR_FULL +); + +GF_OPT_ENUM (ROUTEInRepairISO, + REPAIR_ISO_NO = 0, + REPAIR_ISO_SIMPLE, + REPAIR_ISO_PARTIAL, + REPAIR_ISO_DEPS, + REPAIR_ISO_DEPX, +); + +typedef struct _route_repair_seg_info RepairSegmentInfo; + +typedef struct +{ + u32 br_start; + u32 br_end; + u32 bytes_recv; + u8 is_open; + u8 priority; +} RouteRepairRange; + +typedef enum +{ + RANGE_SUPPORT_NO = 0, + RANGE_SUPPORT_PROBE, + RANGE_SUPPORT_YES, +} RouteServerRangeSupport; + +typedef struct +{ + //allocated only if service_id is not 0 + char *url; + u32 service_id; + RouteServerRangeSupport accept_ranges; + Bool support_h2; + u32 latency; +} RouteRepairServer; + +#define REPAIR_BUF_SIZE 50000 +typedef struct +{ + GF_DownloadSession *dld; + RepairSegmentInfo *current_si; + + RouteRepairRange *range; + RouteRepairServer *server; + u32 initial_retry, retry_in; + char http_bufREPAIR_BUF_SIZE; +} RouteRepairSession; + +typedef struct +{ + //options + char *src, *ifce, *odir; + Bool gcache, kc, skipr, reorder, fullseg, cloop, llmode, dynsel, ka, chkiso; + u32 buffer, timeout, stats, max_segs, tsidbg, rtimeout, nbcached; + ROUTEInRepairISO riso; + ROUTEInRepairMode repair; + u32 max_sess, range_merge, minrecv; + s32 tunein, stsi; + GF_PropStringList repair_urls; + + //internal + GF_Filter *filter; + GF_DownloadManager *dm; + + char *clock_init_seg; + GF_ROUTEDmx *route_dmx; + u32 tune_service_id; + + u32 sync_tsi, last_toi; + + u32 start_time, tune_time, last_timeout; + GF_FilterPid *opid; + GF_List *tsi_outs; + + u32 nb_stats; + GF_List *received_seg_names; + + u32 nb_playing; + Bool initial_play_forced; + Bool evt_interrupt; + + RouteRepairSession *http_repair_sessions; + + GF_List *seg_repair_queue; + GF_List *seg_repair_reservoir; + GF_List *seg_range_reservoir; + GF_List *sample_deps_reservoir; + GF_List *repair_servers; + + Bool has_data; + const char *log_name; +} ROUTEInCtx; + +enum +{ + REPAIR_ISO_STATUS_DONE = 0, + REPAIR_ISO_STATUS_INIT, + REPAIR_ISO_STATUS_PATCH_TOP_LEVEL, + REPAIR_ISO_STATUS_PATCH_MDAT, +}; + +enum +{ + //sample is drop + SAMPLE_DROP = 1, + //sample is drop due to a reference being dropped + SAMPLE_DROP_DEP = 1<<1 +}; + +typedef struct +{ + u32 sample_id; + u32 nb_refs, nb_refs_alloc; + u32 *refs; + u32 start_range; + u32 end_range; + u64 dts; + u16 gop_id; + //0: missing range, 1: MABR received OK, 2: completed by HTTP repair + u8 valid; + //one of the above flags + u8 drop; + u32 num_direct_dependencies, num_indirect_dependencies; +} SampleDepInfo; + +struct _route_repair_seg_info +{ + //copy of finfo event, valid until associated object is removed + GF_ROUTEEventFileInfo finfo; + //copy of filename which is not guaranteed to be kept outside the event callback + char *filename; + GF_ROUTEEventType evt; + u32 service_id; + Bool removed; + u32 pending; + GF_List *ranges; + u32 nb_errors; + u32 nb_bytes_repaired; + TSI_Output *tsio; + + u32 isox_state; + + //set to true if repair session is over but kept in list for TSIO reordering purposes + Bool done; + + u32 max_dep_per_sample; + GF_List *sample_deps; +}; + +RouteRepairServer *routein_push_repair_server(ROUTEInCtx *ctx, const char *url, u32 service_id); + + +void routein_repair_mark_file(ROUTEInCtx *ctx, u32 service_id, const char *filename, Bool is_delete); +void routein_queue_repair(ROUTEInCtx *ctx, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo); + +void routein_on_event_file(ROUTEInCtx *ctx, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo, Bool is_defer_repair, Bool drop_if_first); + +//return GF_EOS if nothing active, GF_OK otherwise +GF_Err routein_do_repair(ROUTEInCtx *ctx); +void routein_check_type(ROUTEInCtx *ctx, GF_ROUTEEventFileInfo *finfo, u32 service_id); + +TSI_Output *routein_get_tsio(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo); + +#endif /* GPAC_DISABLE_ROUTE */ + +#endif //#define IN_ROUTE_H
View file
gpac-26.02.0.tar.gz/src/filters/in_route_repair.c
Added
@@ -0,0 +1,2087 @@ + /* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2025 + * All rights reserved + * + * This file is part of GPAC / ROUTE (ATSC3, DVB-I) input filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "in_route.h" +#include <gpac/mpegts.h> +#include <gpac/isomedia.h> +#include "../utils/downloader.h" + +#ifndef GPAC_DISABLE_ROUTE + +static void repair_session_dequeue(ROUTEInCtx *ctx, RepairSegmentInfo *rsi); + +static void update_first_frag(GF_ROUTEEventFileInfo *finfo) +{ + //first bytes of segment have been patched (first seg only), update frag size and offset + //and update blob if needed + gf_mx_p(finfo->blob->mx); + finfo->frags0.size += finfo->frags0.offset; + finfo->frags0.offset = 0; + finfo->blob->last_modification_time = gf_sys_clock_high_res(); + if (finfo->blob->size < finfo->frags0.size) + finfo->blob->size = finfo->frags0.size; + gf_mx_v(finfo->blob->mx); +} + +//patch TS file, replacing all 188 bytes packets overlaping a gap by padding packets +static Bool routein_repair_segment_ts_local(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo, Bool repair_start_only) +{ + u32 i, pos; + Bool drop_if_first = GF_FALSE; + u8 *data = finfo->blob->data; + u32 patch_first_range_size = 0; + + + if (repair_start_only) { + patch_first_range_size = finfo->frags0.offset; + } + + pos = 0; + for (i=0; i<finfo->nb_frags; i++) { + u32 start_range = finfo->fragsi.offset; + u32 end_range = finfo->fragsi.size; + + //if we missed first 4 packets, we cannot rely on PAT/PMT being present in the rest of the segment + //we could further check this at the demux level, but for now we drop the segment + if (!i && (start_range>4*188)) + drop_if_first = GF_TRUE; + + end_range += start_range; + //reset all missed byte ranges as padding packets + start_range -= pos; + while (start_range % 188) start_range++; + while (pos<start_range) { + datapos = 0x47; + datapos+1 = 0x1F; + datapos+2 = 0xFF; + datapos+3 = 0x10; + pos += 188; + } + //end range not aligned with a packet start, rewind position to prev packet start + while (end_range % 188) end_range--; + pos = end_range; + + if (patch_first_range_size && (pos>=patch_first_range_size)) { + update_first_frag(finfo); + return GF_FALSE; + } + } + //and patch all end packets + while (pos<finfo->blob->size) { + datapos = 0x47; + datapos+1 = 0x1F; + datapos+2 = 0xFF; + datapos+3 = 0x10; + pos += 188; + } + //remove corrupted flag + finfo->partial = GF_LCTO_PARTIAL_NONE; + gf_route_dmx_patch_frag_info(ctx->route_dmx, service_id, finfo, 0, finfo->blob->size); + return drop_if_first; +} + +//top boxes we look for in segments, by rough order of frequency +static const u32 top_codes = { + GF_4CC('m', 'o', 'o', 'f'), + GF_4CC('m', 'd', 'a', 't'), + GF_4CC('p', 'r', 'f', 't'), + GF_4CC('e', 'm', 's', 'g'), + GF_4CC('s', 't', 'y', 'p'), + GF_4CC('f', 'r', 'e', 'e'), + GF_4CC('s', 'i', 'd', 'x'), + GF_4CC('s', 's', 'i', 'x') +}; +static u32 nb_top_codes = GF_ARRAY_LENGTH(top_codes); + +static u32 next_top_level_box(GF_ROUTEEventFileInfo *finfo, u8 *data, u32 size, Bool check_start, u32 *cur_pos, u32 *box_size) +{ + u32 pos = *cur_pos; + u32 frag_start = 0; + u32 frag_end = 0; + u32 cur_frag = 0; + while (cur_frag < finfo->nb_frags) { + //in range, can go + if ((finfo->fragscur_frag.offset <= pos) && (finfo->fragscur_frag.offset + finfo->fragscur_frag.size > pos)) { + frag_start = finfo->fragscur_frag.offset; + frag_end = frag_start + finfo->fragscur_frag.size; + break; + } + //before range, adjust pos + if (finfo->fragscur_frag.offset > pos) { + if (check_start) return 0; + frag_start = finfo->fragscur_frag.offset; + frag_end = frag_start + finfo->fragscur_frag.size; + pos = frag_start; + break; + } + //after range, go to next + cur_frag++; + //current pos is outside last valid range, no more top-level boxes to parse + if (cur_frag==finfo->nb_frags) + return 0; + } + //we must have 4 valid bytes for size + if (pos < frag_start + 4) pos = frag_start+4; + + //we cannot look outside of fragment + while (pos + 8 < frag_end) { + u32 i; + u32 first_box = 0; + u32 first_box_size = 0; + + //look for our top-level codes + u32 box_code = GF_4CC(datapos, datapos+1, datapos+2, datapos+3); + for (i=0; i<nb_top_codes; i++) { + if (box_code == top_codesi) { + first_box = pos; + break; + } + } + //not found + if (!first_box) { + pos++; + continue; + } + first_box_size = GF_4CC(datafirst_box-4, datafirst_box-3, datafirst_box-2, datafirst_box-1); + if (first_box_size<8) { + pos++; + continue; + } + *cur_pos = first_box-4; + *box_size = first_box_size; + return box_code; + } + return 0; +} + +#define SAFETY_ERASE_BYTES 32 +//patch ISOBMFF file, replacing all top-level boxes overlaping a gap by free boxes +static void routein_repair_segment_isobmf_local(ROUTEInCtx *ctx, u32 service_id, GF_ROUTEEventFileInfo *finfo, Bool repair_start_only) +{ + u8 *data = finfo->blob->data; + u32 size = finfo->blob->size; + u32 partial_status = GF_LCTO_PARTIAL_NONE; + u32 pos = 0; + u32 prev_moof_pos = 0; + u32 prev_mdat_pos = 0; + u32 last_box_size = 0; + u32 nb_patches = 0; + Bool was_partial = finfo->partial!=GF_LCTO_PARTIAL_NONE; + u32 patch_first_range_size = 0; + + if (repair_start_only) { + size = finfo->frags0.offset + finfo->frags0.size; + patch_first_range_size = finfo->frags0.offset; + } + + /* walk through all possible top-level boxes in order + - if box completely in a received byte range, keep as is + - if incomplete mdat: + - if strict mode, move to free and move previous moof to free as well + - otherwise keep as is + - if incomplete moof, move to free + - if hole between two known boxes (some box headers where partially or totally lost), inject free box + - when injecting a box, if no mdat was detected after the last moof, move the moof to free + + Whenever moving a moof to free and no mdat was detected after the moof; we memset to 0 (at most) SAFETY_ERASE_BYTES bytes following the box header + This avoids GPAC libisomedia trying to recover a free box into a moof box, which is done by checking for mfhd presence + */ + while ((u64)pos + 8 < size) { + u32 i; + Bool is_mdat = GF_FALSE; + Bool box_complete = GF_FALSE; + u32 prev_pos = pos; + u32 box_size = 0; + + if (patch_first_range_size && (pos+8 >= patch_first_range_size)) { + update_first_frag(finfo); + return; + } + + u32 type = next_top_level_box(finfo, data, size, GF_FALSE, &pos, &box_size); + //no more top-level found, patch from current pos until end of payload + if (!type) { + //first top-level not present in first range to repair, wa cannot patch now + if (!pos && repair_start_only) { + return; + } + if (patch_first_range_size) { + gf_route_dmx_patch_frag_info(ctx->route_dmx, service_id, finfo, 0, size); + return; + } + nb_patches++; + if (!finfo->total_size && prev_pos && last_box_size) { + gf_route_dmx_patch_blob_size(ctx->route_dmx, service_id, finfo, prev_pos+last_box_size); + size = prev_pos+last_box_size; + data = finfo->blob->data; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching file size to %u\n", finfo->filename, size)); + } + gf_assert(size > pos); + + u32 remain = size - pos; + if (remain<8) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to patch end of corrupted segment, segment size not big enough to hold the final box header, something really corrupted in source data\n")); + size -= remain; + gf_route_dmx_patch_blob_size(ctx->route_dmx, service_id, finfo, size); + if (finfo->blob) + finfo->blob->flags |= GF_BLOB_CORRUPTED; + + gf_assert(finfo->total_size); + goto exit; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s injecting last free box pos %u size %u\n", finfo->filename, pos, remain)); + datapos = (remain>>24) & 0xFF; + datapos+1 = (remain>>16) & 0xFF; + datapos+2 = (remain>>8) & 0xFF; + datapos+3 = (remain) & 0xFF; + datapos+4 = 'f'; + datapos+5 = 'r'; + datapos+6 = 'e'; + datapos+7 = 'e'; + //in case the whole file was lost + if (!finfo->total_size) { + gf_route_dmx_patch_blob_size(ctx->route_dmx, service_id, finfo, pos+remain); + size = prev_pos+last_box_size; + data = finfo->blob->data; + } + remain-=8; + if (remain>SAFETY_ERASE_BYTES) remain = SAFETY_ERASE_BYTES; + memset(data+pos+8, 0, remain); + + //we have a previous moof but no mdat header after, consider we completely lost the fragment + //so reset moof to free and erase mvhd + if (prev_moof_pos) { + dataprev_moof_pos+4 = 'f'; + dataprev_moof_pos+5 = 'r'; + dataprev_moof_pos+6 = 'e'; + dataprev_moof_pos+7 = 'e'; + memset(data+prev_moof_pos+8, 0, 16); + prev_moof_pos = 0; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching last moof (pos %u) to free and erase mvhd\n", finfo->filename, pos, prev_moof_pos)); + } + gf_assert(finfo->total_size); + goto exit; + } + last_box_size = box_size; + + //we missed a box header, insert one at previous pos, indicating a free box !! + if (pos > prev_pos) { + u32 missed_size = pos - prev_pos; + + //we might have detected a wrong box + if (missed_size<8) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR File %s detected box %s at pos %u but not enough bytes since previous box end at pos %u - ignoring and looking further\n", finfo->filename, gf_4cc_to_str(type), pos, prev_pos)); + pos = prev_pos+8; + continue; + } + nb_patches++; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s injecting mid-stream free box between pos %u and pos %u size %u\n", finfo->filename, prev_pos, pos, missed_size)); + + dataprev_pos = (missed_size>>24) & 0xFF; + dataprev_pos+1 = (missed_size>>16) & 0xFF; + dataprev_pos+2 = (missed_size>>8) & 0xFF; + dataprev_pos+3 = (missed_size) & 0xFF; + dataprev_pos+4 = 'f'; + dataprev_pos+5 = 'r'; + dataprev_pos+6 = 'e'; + dataprev_pos+7 = 'e'; + missed_size -= 8; + if (missed_size>SAFETY_ERASE_BYTES) missed_size = SAFETY_ERASE_BYTES; + memset(data+prev_pos+8, 0, missed_size); + + //we have a previous moof but no mdat header anywere, consider we completely lost the fragment + //so reset moof to free and erase mvhd + if (prev_moof_pos) { + dataprev_moof_pos+4 = 'f'; + dataprev_moof_pos+5 = 'r'; + dataprev_moof_pos+6 = 'e'; + dataprev_moof_pos+7 = 'e'; + memset(data+prev_moof_pos+8, 0, 16); + prev_moof_pos = 0; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching last moof (pos %u) to free and erase mvhd\n", finfo->filename, pos, prev_moof_pos)); + } + } + + if (type == GF_4CC('f','r','e','e')) { + //don't check / patch for free + box_complete = GF_TRUE; + } else if (type == GF_4CC('m','d','a','t')) { + if (ctx->repair != ROUTEIN_REPAIR_STRICT) { + box_complete = GF_TRUE; + } else { + is_mdat = GF_TRUE; + prev_mdat_pos = pos; + } + } else if (type == GF_4CC('m','o','o','f')) { + prev_moof_pos = pos; + } + + //check if we are indeed in a recevied range + if (!box_complete) { + for (i=0; i<finfo->nb_frags; i++) { + if (pos + box_size < finfo->fragsi.offset) + break; + if ((pos >= finfo->fragsi.offset) && (pos+box_size<=finfo->fragsi.offset + finfo->fragsi.size)) { + box_complete = GF_TRUE; + break; + } + } + } + if (box_complete) { + //mdat completely received, reset mdat pos and moof (we assume a single mdat per moof) + if (is_mdat) { + prev_mdat_pos = 0; + prev_moof_pos = 0; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s complete box %s pos %u size %u\n", finfo->filename, gf_4cc_to_str(type), pos, box_size)); + pos += box_size; + continue; + } + //incomplete mdat (strict mode), discard previous moof + if (is_mdat) { + if (prev_moof_pos) { + dataprev_moof_pos+4 = 'f'; + dataprev_moof_pos+5 = 'r'; + dataprev_moof_pos+6 = 'e'; + dataprev_moof_pos+7 = 'e'; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching mdat (pos %u size %u) and last moof (pos %u) to free\n", finfo->filename, pos, box_size, prev_moof_pos)); + prev_moof_pos = 0; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching mdat (pos %u size %u) to free\n", finfo->filename, pos, box_size)); + } + datapos+4 = 'f'; + datapos+5 = 'r'; + datapos+6 = 'e'; + datapos+7 = 'e'; + u32 max_s = MAX(size, finfo->total_size); + if (pos+box_size>max_s) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching mdat size from %u to %u (truncated file)\n", finfo->filename, pos, box_size, max_s - pos)); + box_size = max_s - pos; + datapos = (box_size>>24) & 0xFF; + datapos+1 = (box_size>>16) & 0xFF; + datapos+2 = (box_size>>8) & 0xFF; + datapos+3 = (box_size) & 0xFF; + } + nb_patches++; + } else { + //incomplete box, move to free and erase begining of payload + datapos+4 = 'f'; + datapos+5 = 'r'; + datapos+6 = 'e'; + datapos+7 = 'e'; + nb_patches++; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s erasing incomplete box %s payload - size %u pos %u\n", finfo->filename, gf_4cc_to_str(type), box_size, pos)); + + u32 erase_size = box_size-8; + if (erase_size>SAFETY_ERASE_BYTES) erase_size = SAFETY_ERASE_BYTES; + memset(data+pos+8, 0, MIN(erase_size, size-pos-8)); + } + pos += box_size; + } + + if (patch_first_range_size) { + return; + } + + //check if file had no known size and last fragment ends on our last box + if (!finfo->total_size && (finfo->fragsfinfo->nb_frags-1.offset + finfo->fragsfinfo->nb_frags-1.size == pos)) { + if (finfo->nb_frags==1) was_partial = GF_FALSE; + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR File %s unknown size, patching to last known box end %u\n", finfo->filename, pos)); + finfo->total_size = pos; + } + //file size unknown, truncate blob to our last box end + if (!finfo->total_size) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching file size to %u\n", finfo->filename, pos)); + gf_route_dmx_patch_blob_size(ctx->route_dmx, service_id, finfo, pos); + data = finfo->blob->data; + size = pos; + //if prev mdat was not completly received, patch mdat & moof + if (ctx->repair == ROUTEIN_REPAIR_STRICT) { + + if (prev_moof_pos) { + dataprev_moof_pos+4 = 'f'; + dataprev_moof_pos+5 = 'r'; + dataprev_moof_pos+6 = 'e'; + dataprev_moof_pos+7 = 'e'; + nb_patches++; + //moof without mdat, consider the fragment lost + if (!prev_mdat_pos) { + memset(data+prev_moof_pos+8, 0, 16); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching last moof (pos %u) to free and erasing\n", finfo->filename, pos, prev_moof_pos)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching last moof (pos %u) to free\n", finfo->filename, pos, prev_moof_pos)); + } + } + + if (prev_mdat_pos) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s patching mdat (pos %u) to free\n", finfo->filename, pos)); + dataprev_mdat_pos+4 = 'f'; + dataprev_mdat_pos+5 = 'r'; + dataprev_mdat_pos+6 = 'e'; + dataprev_mdat_pos+7 = 'e'; + nb_patches++; + } + } + } + if (pos>finfo->total_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR File %s is corrupted, invalid last top-level position %u vs size %u\n", finfo->filename, pos, finfo->total_size)); + partial_status = GF_LCTO_PARTIAL_ANY; + was_partial = GF_FALSE; //suppress logs below + } else { + gf_assert(pos == finfo->total_size); + } + +exit: + if ((ctx->repair == ROUTEIN_REPAIR_STRICT) && was_partial && !nb_patches) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR File %s was partially received but no modifications during fast-repair, please report to GPAC devs\n", finfo->filename)); + } + if (was_partial) { + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR File %s fast-repair done (%d patches)\n", finfo->filename, nb_patches)); + } + //remove corrupted flag + finfo->partial = partial_status; + + //in gcache mode, we keep ranges in order to identify corrupted ISOBMF samples after repair - whether they are dispatched is governed by keepc option of isobmf demuxer + //in other modes (dispatch to PID or write to file) we currently reset the info + //WARNING: if removing this, asserts in routein_send_file will need to be removed + if (!ctx->gcache) { + gf_route_dmx_patch_frag_info(ctx->route_dmx, service_id, finfo, 0, finfo->total_size); + gf_assert(finfo->nb_frags == 1); + } +} + +static RouteRepairRange *queue_repair_range(ROUTEInCtx *ctx, RepairSegmentInfo *rsi, u32 start_range, u32 end_range) +{ + RouteRepairRange *rr = gf_list_pop_back(ctx->seg_range_reservoir); + if (!rr) { + GF_SAFEALLOC(rr, RouteRepairRange); + if (!rr) { + rsi->nb_errors++; + return NULL; + } + } else { + memset(rr, 0, sizeof(RouteRepairRange)); + } + rr->br_start = start_range; + rr->br_end = end_range; + if (!end_range) + gf_assert(!rsi->finfo.total_size); + + if (end_range) { + gf_assert(rr->br_end >= rr->br_start); + } + gf_list_add(rsi->ranges, rr); + return rr; +} + +static void route_repair_build_ranges_full(ROUTEInCtx *ctx, RepairSegmentInfo *rsi, GF_ROUTEEventFileInfo *finfo) +{ + u32 i, nb_bytes_ok=0; + u32 bytes_overlap=0; + RouteRepairRange *prev_br = NULL; + + //unused for now - goal is to adjust range priorities based on upper-chain buffer levels +#if 0 + //collect decoder stats, or if not found direct output + if (ctx->opid && !rsi->tsio) { + GF_FilterPidStatistics stats; + GF_Err e = gf_filter_pid_get_statistics(ctx->opid, &stats, GF_STATS_DECODER_SINK); + if (e) e = gf_filter_pid_get_statistics(ctx->opid, &stats, GF_STATS_SINK); + if (!e) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR Repairing segment %s - buffer status: %d ms (%u for rebuffer %u max buffer)\n", finfo->filename, (u32) (stats.buffer_time/1000) , (u32) (stats.min_playout_time/1000), (u32) (stats.max_buffer_time/1000) )); + } + } +#endif + + //we missed the whole file ! + if (!finfo->nb_frags) { + //rsi->finfo.total_size can be 0 or not (announce received but no fragments received) + queue_repair_range(ctx, rsi, 0, rsi->finfo.total_size); + return; + } + + //compute byte range - max ranges to repair: if N interval received, at max N+1 interval losts + //TODO, select byte range priorities & co, check if we want multiple byte ranges?? + for (i=0; i<=finfo->nb_frags; i++) { + u32 br_start = 0, br_end = 0; + if (i<finfo->nb_frags) + nb_bytes_ok += finfo->fragsi.size; + + // first range + if (!i) { + br_end = finfo->fragsi.offset; + } + //middle ranges + else if (i < finfo->nb_frags) { + br_start = finfo->fragsi-1.offset + rsi->finfo.fragsi-1.size; + br_end = finfo->fragsi.offset; + } + //last range + else if (finfo->total_size) { + br_start = finfo->fragsfinfo->nb_frags-1.offset + finfo->fragsfinfo->nb_frags-1.size; + br_end = finfo->total_size; + } + + //this was correctly received ! + if (br_end <= br_start) continue; + //merge small byte ranges + if (prev_br && (prev_br->br_end + ctx->range_merge > br_start)) { + bytes_overlap += br_start - prev_br->br_end; + prev_br->br_end = br_end; + if (!br_end) + gf_assert(finfo->total_size == 0); + continue; + } + + RouteRepairRange *rr = queue_repair_range(ctx, rsi, br_start, br_end); + if (!rr) continue; + prev_br = rr; + } + //we missed the end !! + if (finfo->total_size==0) { + u32 br_start = finfo->fragsfinfo->nb_frags-1.offset + finfo->fragsfinfo->nb_frags-1.size; + + if (prev_br && (prev_br->br_end + ctx->range_merge > br_start)) { + bytes_overlap += br_start - prev_br->br_end; + prev_br->br_end = 0; + } else { + RouteRepairRange *rr = queue_repair_range(ctx, rsi, br_start, 0); + if (!rr) return; + } + } + //no losses - should not happen, frags should be merged by routedmx + if (!gf_list_count(rsi->ranges)) return; + + if (!ctx->minrecv || (finfo->total_size && (nb_bytes_ok * 100 < finfo->total_size * ctx->minrecv))) { + RouteRepairRange *rr; + while (gf_list_count(rsi->ranges)>1) { + rr = gf_list_pop_back(rsi->ranges); + gf_free(rr); + } + rr = gf_list_get(rsi->ranges, 0); + memset(rr, 0, sizeof(RouteRepairRange)); + rr->br_start = 0; + rr->br_end = 0; + rsi->finfo.total_size = 0; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s too many bytes lost (%u %%), redownloading full file\n", rsi->finfo.filename, finfo->total_size ? (u32) (nb_bytes_ok*100/finfo->total_size) : 100 )); + } + else if (bytes_overlap) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s merging repair ranges, downloading %u bytes already received\n", rsi->finfo.filename, bytes_overlap)); + } +} + +static u32 get_dependent_samples(GF_List *sample_deps, SampleDepInfo *for_sample, SampleDepInfo *ref_sample, GF_List *done) +{ + u32 nb_deps, i, j, count = gf_list_count(sample_deps); + + if (gf_list_find(done, for_sample)>=0) return 0; + gf_list_add(done, for_sample); + if (ref_sample==for_sample) + ref_sample->num_direct_dependencies = 0; + + nb_deps = 0; + for (i=0;i<count;i++) { + SampleDepInfo *sd = gf_list_get(sample_deps, i); + if (sd == for_sample) continue; + if (sd->drop) continue; + if (sd->gop_id != ref_sample->gop_id) continue; + for (j=0; j<sd->nb_refs; j++) { + if (sd->refsj == for_sample->sample_id) { + if (ref_sample==for_sample) + ref_sample->num_direct_dependencies ++; + if (gf_list_find(done, sd)>=0) continue; + nb_deps++; + nb_deps += get_dependent_samples(sample_deps, sd, ref_sample, done); + } + } + } + return nb_deps; +} + +static void update_num_dependent_samples(GF_List *sample_deps, SampleDepInfo *for_sample) +{ + GF_List *done = gf_list_new(); + for_sample->num_indirect_dependencies = get_dependent_samples(sample_deps, for_sample, for_sample, done); + gf_list_del(done); +} + +static void routein_repair_get_isobmf_deps(ROUTEInCtx *ctx, RepairSegmentInfo *rsi) +{ + GF_ISOFile *file; + u64 BytesMissing; + u32 i; + char szBlobPath100; + + GF_Err e = gf_isom_open_progressive_ex("isobmff://4cc=none", 0, 0, 0, &file, &BytesMissing, NULL); + if (e) return; + + sprintf(szBlobPath, "gmem://%p", rsi->finfo.blob); + e = gf_isom_open_segment(file, szBlobPath, 0, 0, 0); + if (e) { + gf_isom_delete(file); + return; + } + u32 ID, nb_refs, count; + const u32 *refs; + count = gf_isom_get_sample_count(file, 1); + if (gf_isom_get_sample_references(file, 1, 1, &ID, &nb_refs, &refs)!=GF_OK) { + gf_isom_delete(file); + return; + } + + if (!rsi->sample_deps) rsi->sample_deps = gf_list_new(); + + GF_ISOSample static_sample; + u32 gop_id=0; + rsi->max_dep_per_sample = 0; + + for (i=0; i<count; i++) { + u64 offset; + SampleDepInfo *r = gf_list_pop_back(ctx->sample_deps_reservoir); + if (!r) { + GF_SAFEALLOC(r, SampleDepInfo); + if (!r) continue; + } else { + u32 *refs = r->refs; + u32 alloc_refs = r->nb_refs_alloc; + memset(r, 0, sizeof(SampleDepInfo)); + r->nb_refs_alloc = alloc_refs; + r->refs = refs; + } + gf_list_add(rsi->sample_deps, r); + + memset(&static_sample, 0, sizeof(static_sample)); + GF_ISOSample *samp = gf_isom_get_sample_info_ex(file, 1, i+1, NULL, &offset, &static_sample); + if (!samp) break; + r->start_range = offset; + r->end_range = offset + samp->dataLength; + r->dts = samp->DTS+i; + + gf_isom_get_sample_references(file, 1, i+1, &r->sample_id, (u32 *) &r->nb_refs, &refs); + if (!r->nb_refs) gop_id ++; + r->gop_id = gop_id; + if (r->sample_id==0xFFFFFFFF) { + r->nb_refs = -1; + continue; + } + if (refs && (r->nb_refs>0)) { + if (r->nb_refs_alloc < r->nb_refs) { + r->refs = gf_realloc(r->refs, sizeof(u32)*r->nb_refs); + r->nb_refs_alloc = r->nb_refs; + if (!r->refs) { + r->nb_refs = -1; + continue; + } + } + memcpy(r->refs, refs, sizeof(u32)*r->nb_refs); + } + } + gf_isom_delete(file); + + for (i=0; i<count; i++) { + SampleDepInfo *r = gf_list_get(rsi->sample_deps, i); + update_num_dependent_samples(rsi->sample_deps, r); + if (r->num_direct_dependencies > rsi->max_dep_per_sample) + rsi->max_dep_per_sample = r->num_direct_dependencies; + } + //sort by decreasing number of indirect dependencies - the first items will be the most important ones to fix + GF_List *res = gf_list_new(); + for (i=0; i<count; i++) { + SampleDepInfo *r = gf_list_get(rsi->sample_deps, i); + u32 j, scount = gf_list_count(res); + for (j=0; j<scount; j++) { + SampleDepInfo *elt = gf_list_get(res, j); + //note that we currently don't sort by gops, so if 2 gops in the segment the intra from both GOPs will be patched first + //we could further optimize this especially in playback modes + if (elt->nb_refs > r->nb_refs) { + gf_list_insert(res, r, j); + r = NULL; + break; + } + if (elt->num_direct_dependencies < r->num_direct_dependencies) { + gf_list_insert(res, r, j); + r = NULL; + break; + } + } + if (r) + gf_list_add(res, r); + } + gf_list_del(rsi->sample_deps); + rsi->sample_deps = res; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR Sample dependencies:\n")); + for (i=0; i<count; i++) { + SampleDepInfo *r = gf_list_get(rsi->sample_deps, i); + u32 j; + for (j=0; j<rsi->finfo.nb_frags; j++) { + GF_LCTFragInfo *frag = &rsi->finfo.fragsj; + if ((r->start_range>=frag->offset) && (r->end_range<=frag->offset+frag->size)) { + r->valid = 1; + break; + } + if (r->end_range<frag->offset) break; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR Sample %d (GID %u) has %u DD %u ID (%u refs) - complete %d\n", r->sample_id, r->gop_id, r->num_direct_dependencies, r->num_indirect_dependencies, r->nb_refs, r->valid )); + } + return; +} + +static void drop_sample_ref(GF_List *sample_deps, SampleDepInfo *ref, Bool primary) +{ + u32 i, j, count; + if (ref->drop) return; + + count = gf_list_count(sample_deps); + ref->drop |= SAMPLE_DROP; + if (!primary) ref->drop |= SAMPLE_DROP_DEP; + for (i=0; i<count; i++) { + SampleDepInfo *sdi = gf_list_get(sample_deps, i); + if (sdi->gop_id != ref->gop_id) continue; + for (j=0; j<sdi->nb_refs; j++) { + if (sdi->refsj == ref->sample_id) { + drop_sample_ref(sample_deps, sdi, GF_FALSE); + break; + } + } + } +} + +GF_Err gf_route_dmx_add_frag_hole(GF_ROUTEDmx *routedmx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 br_start, u32 br_size); + +static void route_repair_build_ranges_isobmf(ROUTEInCtx *ctx, RepairSegmentInfo *rsi, GF_ROUTEEventFileInfo *finfo) +{ + u32 i, count; + Bool use_repair = (ctx->repair==ROUTEIN_REPAIR_FULL) ? GF_TRUE : GF_FALSE; + + if ((finfo->nb_frags==1) && !finfo->frags0.offset && (finfo->frags0.size==finfo->total_size)) { + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + //we failed at repairing something, abort + if (rsi->nb_errors) { + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + + if (!finfo->nb_frags) { + if (use_repair && finfo->total_size) { + queue_repair_range(ctx, rsi, 0, finfo->total_size); + } else { + rsi->nb_errors++; + } + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + + if ((rsi->isox_state==REPAIR_ISO_STATUS_INIT) && finfo->total_size && use_repair) { + u32 nb_bytes_ok=0; + for (i=0; i<finfo->nb_frags; i++) { + nb_bytes_ok += finfo->fragsi.size; + } + if (!ctx->minrecv || ((nb_bytes_ok * 100 < finfo->total_size * ctx->minrecv))) { + queue_repair_range(ctx, rsi, 0, finfo->total_size); + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + } + if (rsi->isox_state <= REPAIR_ISO_STATUS_PATCH_TOP_LEVEL) { + u32 patch_start=0; + u32 patch_box_size=0; + u32 patch_box_type=0; + u32 pos=0; + u32 prev_bpos=0; + u32 prev_btype=0; + u32 prev_bsize=0; + u8 *data = finfo->blob->data; + u32 size = finfo->blob->size; + + while (pos < size) { + u32 bsize, j; + u32 btype = next_top_level_box(finfo, data, size, GF_TRUE, &pos, &bsize); + //no valid bytes to read box header, need to patch previous box end or iniial range + if (!btype) { + if (!pos) { + gf_assert(finfo->frags0.offset || (finfo->frags0.size<8)); + patch_box_size = finfo->frags0.offset; + if (patch_box_size>500) patch_box_size = 500; + break; + } + gf_assert(prev_btype); + gf_assert(prev_bsize); + patch_start = prev_bpos; + patch_box_size = prev_bsize; + patch_box_type = prev_btype; + break; + } + prev_btype = btype; + prev_bpos = pos; + prev_bsize = bsize; + //box is mdat/idat, go to next + if ((btype==GF_4CC('m','d','a','t')) || (btype==GF_4CC('i','d','a','t'))) { + pos += bsize; + continue; + } + //check we have the full box + Bool box_complete = GF_FALSE; + for (j=0; j<finfo->nb_frags; j++) { + GF_LCTFragInfo *frag = &finfo->fragsj; + +/* + if (patch_start && (patch_start+patch_box_size>=frag->offset) && (patch_start+patch_box_size+8<=frag->offset+frag->size)) { + next_box_valid = GF_TRUE; + break; + } +*/ + //not in a valid range + if (pos<frag->offset) continue; + //box is in a received range + if ((pos >= frag->offset) && (pos + bsize <= frag->offset + frag->size)) { + box_complete = GF_TRUE; + break; + } + if (pos>frag->offset+frag->size) continue; + + //remember patch start (end of fragment) + if (pos >= frag->offset) { + patch_start = pos; + patch_box_size = bsize; + patch_box_type = btype; + //break; + } + } + if (!box_complete) break; + pos += bsize; + continue; + } + if (patch_box_size) { + u32 patch_end = 0; + //mdat, patch end of box only + if ((patch_box_type==GF_4CC('m','d','a','t')) || (patch_box_type==GF_4CC('i','d','a','t'))) { + patch_start += patch_box_size; + patch_end = patch_start + 500; + } else { + //include header of next box (full size) but skip UUID + patch_end = patch_start + patch_box_size + 16; + } + if (finfo->total_size && (patch_end >= finfo->total_size)) + patch_end = finfo->total_size; + + //issue a single byte range + for (i=0; i<finfo->nb_frags; i++) { + GF_LCTFragInfo *frag = &finfo->fragsi; + if ((patch_start>=frag->offset) && (patch_start<frag->offset+frag->size)) + patch_start = frag->offset+frag->size; + + if ((patch_start>=frag->offset+frag->size) && (patch_start<frag->offset+frag->size+1500)) + patch_start = frag->offset+frag->size; + + if ((patch_end>=frag->offset) && (patch_end<frag->offset+frag->size)) { + patch_end = frag->offset; + break; + } + if ((patch_end<frag->offset) && (patch_end+1500>=frag->offset)) { + patch_end = frag->offset; + break; + } + } + //we missed a top-level box, cannot go any further if no repair + if (!use_repair) { + rsi->nb_errors++; + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + + RouteRepairRange *rr = queue_repair_range(ctx, rsi, patch_start, patch_end); + if (!rr) { + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR Patching box %s patch start %u end %u\n", gf_4cc_to_str(patch_box_type), patch_start, patch_end )); + rsi->isox_state = REPAIR_ISO_STATUS_PATCH_TOP_LEVEL; + return; + } + if (!finfo->total_size) { + if (!use_repair) { + rsi->nb_errors++; + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + //we miss the end, patch with open byte-range from blob size until end + RouteRepairRange *rr = queue_repair_range(ctx, rsi, size, 0); + if (!rr) { + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + rsi->isox_state = REPAIR_ISO_STATUS_PATCH_TOP_LEVEL; + return; + } + if (rsi->isox_state == REPAIR_ISO_STATUS_PATCH_TOP_LEVEL) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR All top-level boxes patched for %s\n", finfo->filename)); + } + //simple mode, patch mdats in order + if (ctx->riso==REPAIR_ISO_SIMPLE) { + if (use_repair) + route_repair_build_ranges_full(ctx, rsi, finfo); + else + rsi->nb_errors++; + + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + //partial mode, we are done + if (ctx->riso==REPAIR_ISO_PARTIAL) { + //not done yet, signal a final patch + if (!finfo->total_size || (finfo->nb_frags>1)) + rsi->nb_errors = 1; + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + rsi->isox_state = REPAIR_ISO_STATUS_PATCH_MDAT; + routein_repair_get_isobmf_deps(ctx, rsi); + if (!gf_list_count(rsi->sample_deps)) { + //no sample deps, repair everything + if (use_repair) + route_repair_build_ranges_full(ctx, rsi, finfo); + else + rsi->nb_errors++; + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + //fallthrough + } + gf_assert(rsi->sample_deps); + //get first sample dep + SampleDepInfo *sdi = NULL; + count = gf_list_count(rsi->sample_deps); + for (i=0; i<count; i++) { + sdi = gf_list_get(rsi->sample_deps, i); + if (sdi->valid || sdi->drop) { + sdi = NULL; + continue; + } + //no repair, drop sample + if (!use_repair) { + drop_sample_ref(rsi->sample_deps, sdi, GF_TRUE); + sdi = NULL; + continue; + } + + //drop rules, to refine + if ( + //if 0 or 1 indirect deps, drop + (sdi->num_indirect_dependencies<=1) + //if not "as important" as other refs, drop +// || (sdi->num_direct_dependencies + 1 < rsi->max_dep_per_sample) + ) { + drop_sample_ref(rsi->sample_deps, sdi, GF_TRUE); + sdi = NULL; + continue; + } + break; + } + if (sdi) { + Bool has_patch=GF_FALSE; + sdi->valid = 2; + u32 prev_frag_end=0; + //get range(s) + for (i=0;i<rsi->finfo.nb_frags; i++) { + GF_LCTFragInfo *frag = &rsi->finfo.fragsi; + u32 start_patch = sdi->start_range; + u32 end_patch = sdi->end_range; + //part before a hole + if (start_patch<frag->offset) { + if (start_patch<prev_frag_end) + start_patch = prev_frag_end; + + if (end_patch>frag->offset) + end_patch = frag->offset; + } else { + start_patch = 0; + end_patch = 0; + } + prev_frag_end = frag->offset + frag->size; + if (!start_patch && !end_patch) continue; + + RouteRepairRange *rr = queue_repair_range(ctx, rsi, start_patch, end_patch); + if (!rr) { + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + has_patch = GF_TRUE; + if (end_patch <= frag->offset+frag->size) + break; + } + //final patch - sample exceeds end of last range + if (sdi->end_range>prev_frag_end) { + u32 start_patch = sdi->start_range; + if (start_patch<prev_frag_end) + start_patch = prev_frag_end; + else if (sdi->start_range - prev_frag_end < 1500) + start_patch = prev_frag_end; + u32 end_patch = finfo->total_size ? sdi->end_range : 0; + + RouteRepairRange *rr = queue_repair_range(ctx, rsi, start_patch, end_patch); + if (!rr) { + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + return; + } + has_patch = GF_TRUE; + } + if (has_patch) return; + } + gf_assert(sdi==NULL); + + //we are done patching mdat + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR MDAT patching done:\n")); + + //reset the frag info to complete file and add holes for removed samples + gf_assert(rsi->finfo.total_size); + gf_route_dmx_patch_frag_info(ctx->route_dmx, rsi->service_id, &rsi->finfo, 0, rsi->finfo.total_size); + + for (i=0; i<count; i++) { + sdi = gf_list_get(rsi->sample_deps, i); + u8 drop = sdi->drop; + gf_assert(!drop || (rsi->tsio->bytes_sent <= sdi->start_range)); + + if (drop) { + //reset data + u32 s_size = sdi->end_range-sdi->start_range; + u8 *s_data = rsi->finfo.blob->data+sdi->start_range; + memset(s_data, 0, s_size); + + //add a fake NAL size length on 4 bytes (crude, to refine) + s_size-=4; + s_data0 = s_size>>24; + s_data1 = s_size>>16; + s_data2 = s_size>>8; + s_data3 = s_size&0xFF; + + //in depx mode, don't hide moof when patching mdat - usually breaks decoders + if (ctx->riso == REPAIR_ISO_DEPS) { + //add hole and trigger final local repair to hide moofs + gf_route_dmx_add_frag_hole(ctx->route_dmx, rsi->service_id, &rsi->finfo, sdi->start_range, sdi->end_range-sdi->start_range); + rsi->nb_errors++; + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("\tSample %d (GID %u): DTS "LLU": valid %u - dropped: %u\n", sdi->sample_id, sdi->gop_id, sdi->dts, sdi->valid, sdi->drop)); + } + rsi->isox_state = REPAIR_ISO_STATUS_DONE; + gf_mx_p(rsi->finfo.blob->mx); + rsi->finfo.blob->flags &= ~GF_BLOB_RANGE_IN_TRANSFER; + gf_mx_v(rsi->finfo.blob->mx); +} + + +enum { + ROUTE_FTYPE_UNKNOWN=0, + ROUTE_FTYPE_M2TS, + ROUTE_FTYPE_ISOBMF, +}; + +static u32 guess_file_type(GF_ROUTEEventFileInfo *finfo, u8 *data, u32 size) +{ + const char *ext = gf_file_ext_start(finfo->filename); + if (ext) { + if (!stricmp(ext, ".ts")) return ROUTE_FTYPE_M2TS; + if (!stricmp(ext, ".m2ts")) return ROUTE_FTYPE_M2TS; + if (!stricmp(ext, ".mp4")) return ROUTE_FTYPE_ISOBMF; + if (!stricmp(ext, ".m4s")) return ROUTE_FTYPE_ISOBMF; + if (!stricmp(ext, ".mp4s")) return ROUTE_FTYPE_ISOBMF; + } + if (gf_m2ts_probe_data(data, size)) return ROUTE_FTYPE_M2TS; + u32 pos=0, bsize; + u32 btype = next_top_level_box(finfo, data, size, GF_TRUE, &pos, &bsize); + if (btype) return ROUTE_FTYPE_ISOBMF; + return ROUTE_FTYPE_UNKNOWN; +} + +void routein_check_type(ROUTEInCtx *ctx, GF_ROUTEEventFileInfo *finfo, u32 service_id) +{ + finfo->channel_hint = guess_file_type(finfo, finfo->blob->data, finfo->blob->size); + if (finfo->channel_hint) { + gf_route_dmx_set_object_hint(ctx->route_dmx, service_id, finfo->tsi, finfo->toi, finfo->channel_hint); + } +} + +void routein_check_isobmf(ROUTEInCtx *ctx, GF_ROUTEEventFileInfo *finfo) +{ + u32 pos = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR CHKISO on file %s\n", finfo->filename )); + u8 *data = finfo->blob->data; + while (pos < finfo->total_size) { + u32 bsize = GF_4CC(datapos, datapos+1, datapos+2, datapos+3); + u32 btype = GF_4CC(datapos+4, datapos+5, datapos+6, datapos+7); + switch (btype) { + case GF_4CC('f','t','y','p'): + case GF_4CC('m','o','o','v'): + case GF_4CC('m','e','t','a'): + case GF_4CC('s','t','y','p'): + case GF_4CC('m','o','o','f'): + case GF_4CC('f','r','e','e'): + case GF_4CC('m','d','a','t'): + case GF_4CC('e','m','s','g'): + case GF_4CC('s','i','d','x'): + case GF_4CC('s','s','i','x'): + case GF_4CC('p','c','r','b'): + case GF_4CC('p','r','f','t'): + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR CHKISO: Found top-level %s at position %u size %u\n", gf_4cc_to_str(btype), pos, bsize )); + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("REPAIR CHKISO: Unknown top-level %s at position %u size %u\n", gf_4cc_to_str(btype), pos, bsize )); + } + pos += bsize; + if (!bsize) { + pos = finfo->total_size; + break; + } + } + if (!finfo->nb_frags) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("REPAIR CHKISO: File lost\n")); + } + else if (pos!=finfo->total_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("REPAIR CHKISO: Invalid top-level box size\n")); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR CHKISO: Recovered file OK: %s size %u\n", finfo->filename, finfo->total_size)); + if (!ctx->gcache) { + gf_assert(finfo->nb_frags == 1); + gf_assert(finfo->frags0.offset == 0); + //in iso repair we may skip patching of last frag if mdat is incomplete but full header is present + if (!ctx->riso) + gf_assert(finfo->frags0.size == finfo->total_size); + } + } +} + + +static Bool routein_repair_local(ROUTEInCtx *ctx, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo, Bool start_only) +{ + Bool drop_if_first = GF_FALSE; + Bool in_transfer = GF_FALSE; + //remove corrupted flags and set in_transfer to avoid dispatch when using internal cache + if (finfo->blob->mx) { + gf_mx_p(finfo->blob->mx); + finfo->blob->flags &= ~GF_BLOB_CORRUPTED; + if (finfo->blob->flags & GF_BLOB_IN_TRANSFER) in_transfer = GF_TRUE; + else finfo->blob->flags |= GF_BLOB_IN_TRANSFER; + } + //file received with no losses + if ((finfo->nb_frags==1) && !finfo->frags0.offset && (finfo->frags0.size == finfo->total_size)) { + if (finfo->blob->mx) { + finfo->blob->flags &= ~GF_BLOB_IN_TRANSFER; + gf_mx_v(finfo->blob->mx); + } + return GF_FALSE; + } + + if (!start_only) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR File %s (TSI=%u, TOI=%u) corrupted, patching\n", finfo->filename, finfo->tsi, finfo->toi)); + } + + if (!finfo->channel_hint) { + routein_check_type(ctx, finfo, evt_param); + } + switch (finfo->channel_hint) { + case ROUTE_FTYPE_M2TS: + drop_if_first = routein_repair_segment_ts_local(ctx, evt_param, finfo, start_only); + break; + case ROUTE_FTYPE_ISOBMF: + routein_repair_segment_isobmf_local(ctx, evt_param, finfo, start_only); + if (ctx->chkiso) + routein_check_isobmf(ctx, finfo); + break; + default: + break; + } + + if (finfo->blob->mx) { + finfo->blob->flags |= GF_BLOB_PARTIAL_REPAIR; + if (!in_transfer) + finfo->blob->flags &= ~GF_BLOB_IN_TRANSFER; + gf_mx_v(finfo->blob->mx); + } + return drop_if_first; +} + +u32 routein_get_max_dispatch_len(GF_ROUTEEventFileInfo *finfo) +{ + u32 max_len = 0; + //figure out largest safest size from start + if (finfo->channel_hint==ROUTE_FTYPE_M2TS) { + max_len = finfo->frags0.size / 188; + max_len *= 188; + } else if (finfo->channel_hint==ROUTE_FTYPE_ISOBMF) { + u32 btype, bsize, cur_pos=0; + u32 valid_bytes=0; + u32 true_size = finfo->frags0.size; + while (1) { + btype = next_top_level_box(finfo, finfo->blob->data, true_size, GF_TRUE, &cur_pos, &bsize); + if (!btype) break; + //never send a box until completed + if ((u64) cur_pos + (u64) bsize > (u64) true_size) + break; + + cur_pos+=bsize; + //always break at moof - if we had a moof+mdat valid, we'll stop at the mdat end + if (btype != GF_4CC('m','o','o','f')) { + valid_bytes = cur_pos; + } + } + max_len = valid_bytes; + } + return max_len; +} + +Bool gf_route_dmx_get_object_info(void *lct_obj, GF_ROUTEEventFileInfo *finfo); + +static GF_BlobRangeStatus routein_check_blob_range(GF_Blob *blob, Bool check_when_complete, u64 start_offset, u32 *io_size) +{ + u32 i, size; + GF_ROUTEEventFileInfo finfo; + assert(io_size); + if (!gf_route_dmx_get_object_info(blob->range_udta, &finfo)) + return GF_BLOB_RANGE_CORRUPTED; + + //blob completed, do not check byte ranges + if (!check_when_complete && !(blob->flags & GF_BLOB_IN_TRANSFER)) { + if (blob->flags & GF_BLOB_CORRUPTED) return GF_BLOB_RANGE_CORRUPTED; + return GF_BLOB_RANGE_VALID; + } + + //get maximum number of bytes that will not be modified by file patcher + //this works even when we re-download the complete file: we will then overwrite bytes already sent in the blob but will not resend them + u32 max_size = routein_get_max_dispatch_len(&finfo); + + size = *io_size; + *io_size = 0; + gf_mx_p(blob->mx); + for (i=0; i<finfo.nb_frags; i++) { + GF_LCTFragInfo *frag = &finfo.fragsi; + if ((frag->offset<=start_offset) && (start_offset+size <= frag->offset + frag->size)) { + if (finfo.channel_hint && (size + start_offset>max_size)) { + size = (max_size > start_offset) ? (max_size - start_offset) : 0; + } + *io_size = size; + gf_mx_v(blob->mx); + return GF_BLOB_RANGE_VALID; + } + //start is in fragment but exceeds it + if ((frag->offset <= start_offset) && (start_offset <= frag->offset + frag->size)) { + size = (u32) (frag->offset + frag->size - start_offset); + if (finfo.channel_hint && (size + start_offset>max_size)) { + size = (max_size > start_offset) ? (max_size - start_offset) : 0; + } + *io_size = size; + break; + } + } + gf_mx_v(blob->mx); + if (blob->flags & GF_BLOB_IN_TRANSFER) + return GF_BLOB_RANGE_IN_TRANSFER; + return GF_BLOB_RANGE_CORRUPTED; +} + + +void routein_queue_repair(ROUTEInCtx *ctx, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo) +{ + u32 i, count; + u32 fast_repair = 0; + + //TODO handle late data - keep log = warning as reminder + if (evt==GF_ROUTE_EVT_LATE_DATA) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Late data patching not yet implemented !\n")); + return; + } + + //assign range checker if not done yet for object + if (!finfo->blob->range_valid) { + finfo->blob->range_valid = routein_check_blob_range; + } + + if (ctx->repair==ROUTEIN_REPAIR_NO) { + //no repair, we only dispatch full files + if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) return; + + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + return; + } + //not using gcache nor split stsi, we must dispatch only full files + if (!ctx->gcache && !ctx->stsi && (evt==GF_ROUTE_EVT_DYN_SEG_FRAG)) + return; + + //figure out if we have queued repair on this TSI + TSI_Output *tsio = routein_get_tsio(ctx, evt_param, finfo); + Bool can_flush_fragment = ctx->gcache ? GF_TRUE : GF_FALSE; + + if (tsio) { + if (tsio->delete_first) { + if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) { + return; + } else if (evt==GF_ROUTE_EVT_DYN_SEG) { + tsio->delete_first = GF_FALSE; + return; + } + } + if (!tsio->flags_progress && !gf_list_count(tsio->pending_repairs)) { + //if not same TOI, do not fast flush fragment (progressive) + if (!tsio->current_toi || (tsio->current_toi==finfo->toi)) + can_flush_fragment = GF_TRUE; + } + } + if (evt<GF_ROUTE_EVT_DYN_SEG) { + fast_repair = 0; + } + //patch start of file only at tune-in for LL mode, otherwise use regular route repair + else if (ctx->llmode && !finfo->first_toi_received && (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) + && (finfo->partial==GF_LCTO_PARTIAL_ANY) + && finfo->nb_frags + && finfo->frags0.offset + && !tsio + ) { + fast_repair = 2; + } + //for non-http base repair, check if we can push + else if (ctx->repair<ROUTEIN_REPAIR_FULL) { + if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) { + //we use cache, we can push + if (!ctx->stsi || !tsio) { + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + return; + } + //TSIO, must dispatch in order + if (!can_flush_fragment || finfo->frags0.offset) { + //remember we have a file in progress so that we don't dispatch packets from following file + tsio->flags_progress |= TSIO_FILE_PROGRESS; + return; + } + + u32 true_size = finfo->frags0.size; + finfo->frags0.size = routein_get_max_dispatch_len(finfo); + if (finfo->frags0.size > tsio->bytes_sent) + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + finfo->frags0.size = true_size; + + //remember we have a file in progress so that we don't dispatch packets from following file + tsio->flags_progress |= TSIO_FILE_PROGRESS; + gf_assert(tsio->bytes_sent<=finfo->frags0.size); + return; + } + if (ctx->riso >= REPAIR_ISO_SIMPLE) + fast_repair = 0; + else + fast_repair = 1; + } + + //nothing pending + if (!finfo->partial && can_flush_fragment) { + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + return; + } + //no tsio and no errors, we can forward + else if (!tsio && !finfo->partial) { + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + return; + } + //fast repair and we can flush + if (fast_repair && can_flush_fragment) { + Bool drop_if_first = routein_repair_local(ctx, evt, evt_param, finfo, (fast_repair==2) ? GF_TRUE : GF_FALSE); + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, drop_if_first); + return; + } + + //for now we only trigger file repair + if (evt==GF_ROUTE_EVT_DYN_SEG_FRAG) { + if (!tsio) + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + //in stsi mode no cache with http repair (other modes are tested above): + // push fragment if no pending and data is contiguous with prev + //this ensures we push data asap in the output pids + else if ((finfo->partial!=GF_LCTO_PARTIAL_ANY) && can_flush_fragment) { + u32 true_size = finfo->frags0.size; + finfo->frags0.size = routein_get_max_dispatch_len(finfo); + if (finfo->frags0.size > tsio->bytes_sent) + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + + finfo->frags0.size = true_size; + } else { + //remember we have a file in progress so that we don't dispatch packets from following file + tsio->flags_progress |= TSIO_FILE_PROGRESS; + } + return; + } + + //check if not already queued (for when we will allow repair mid-segments) + count = gf_list_count(ctx->seg_repair_queue); + for (i=0; i<count; i++) { + RepairSegmentInfo *rsi = gf_list_get(ctx->seg_repair_queue, i); + if (!rsi->done && (rsi->finfo.tsi==finfo->tsi) && (rsi->finfo.toi==finfo->toi)) { + //remember event type (fragment or segment) + rsi->evt = evt; + if (ctx->riso!=REPAIR_ISO_NO) + rsi->isox_state = REPAIR_ISO_STATUS_INIT; + return; + } + } + + //queue up our repair + RepairSegmentInfo *rsi = gf_list_pop_back(ctx->seg_repair_reservoir); + if (!rsi) { + GF_SAFEALLOC(rsi, RepairSegmentInfo); + rsi->ranges = gf_list_new(); + } + + if (!rsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("REPAIR Failed to allocate repair entry %s (TSI=%u, TOI=%u)\n", finfo->filename, finfo->tsi, finfo->toi)); + if (tsio) tsio->flags_progress &= ~ TSIO_FILE_PROGRESS; + + //do a local file repair + if (can_flush_fragment && (evt==GF_ROUTE_EVT_DYN_SEG)) { + u32 repair = ctx->repair; + ctx->repair = ROUTEIN_REPAIR_STRICT; + routein_repair_local(ctx, evt, evt_param, finfo, GF_FALSE); + ctx->repair = repair; + + routein_on_event_file(ctx, evt, evt_param, finfo, GF_FALSE, GF_FALSE); + } + return; + } + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR Queue repair for object %s (TSI=%u, TOI=%u)\n", finfo->filename, finfo->tsi, finfo->toi)); + + rsi->evt = evt; + rsi->service_id = evt_param; + rsi->finfo = *finfo; + rsi->filename = gf_strdup(finfo->filename); + rsi->finfo.filename = rsi->filename; + rsi->tsio = tsio; + rsi->nb_bytes_repaired = 0; + + if (finfo->partial) { + //HTTP repair, build ranges + if (!fast_repair) { + if (ctx->riso!=REPAIR_ISO_NO) { + rsi->isox_state = REPAIR_ISO_STATUS_INIT; + route_repair_build_ranges_isobmf(ctx, rsi, finfo); + if (ctx->repair<ROUTEIN_REPAIR_FULL) { + gf_list_add(ctx->seg_repair_queue, rsi); + if (tsio) gf_list_add(tsio->pending_repairs, rsi); + repair_session_dequeue(ctx, rsi); + return; + } + } else { + route_repair_build_ranges_full(ctx, rsi, finfo); + } + } + //direct repair, still queue but mark as in error + else { + rsi->nb_errors = 1; + } + } + + if (rsi->tsio && (rsi->tsio->current_toi==finfo->toi)) { + gf_assert(finfo->frags0.offset==0 || !rsi->tsio->bytes_sent); + gf_assert(rsi->tsio->bytes_sent<=finfo->frags0.size); + } + + //mark blob as in transfer and corrupted - this will be unmarked when doing the final dispatch + gf_mx_p(finfo->blob->mx); + finfo->blob->flags |= GF_BLOB_IN_TRANSFER|GF_BLOB_CORRUPTED; + gf_mx_v(finfo->blob->mx); + if (rsi->tsio) { + if (rsi->tsio->current_toi==finfo->toi) { + gf_assert(finfo->frags0.offset==0 || !rsi->tsio->bytes_sent); + gf_assert(rsi->tsio->bytes_sent<=finfo->frags0.size); + } + rsi->tsio->flags_progress &= ~ TSIO_FILE_PROGRESS; + rsi->tsio->flags_progress |= TSIO_REPAIR_SCHEDULED; + + gf_route_dmx_force_keep_object(ctx->route_dmx, rsi->service_id, finfo->tsi, finfo->toi, GF_TRUE); + //inject by start time + Bool found=GF_FALSE; + count = gf_list_count(rsi->tsio->pending_repairs); + for (i=0; i<count; i++) { + RepairSegmentInfo *a_rsi = gf_list_get(rsi->tsio->pending_repairs, i); + if (finfo->start_time < a_rsi->finfo.start_time) { + gf_list_insert(rsi->tsio->pending_repairs, rsi, i); + found = GF_TRUE; + break; + } + } + if (!found) + gf_list_add(rsi->tsio->pending_repairs, rsi); + } + + if (!ctx->seg_repair_queue) + ctx->seg_repair_queue = gf_list_new(); + + //inject by start time + count = gf_list_count(ctx->seg_repair_queue); + for (i=0; i<count; i++) { + RepairSegmentInfo *a_rsi = gf_list_get(ctx->seg_repair_queue, i); + if (finfo->start_time < a_rsi->finfo.start_time) { + gf_list_insert(ctx->seg_repair_queue, rsi, i); + return; + } + } + gf_list_add(ctx->seg_repair_queue, rsi); +} + + +static void repair_session_dequeue(ROUTEInCtx *ctx, RepairSegmentInfo *rsi) +{ + Bool unprotect; + TSI_Output *tsio; + +restart: + unprotect=GF_FALSE; + tsio = rsi->tsio; + + //done with this repair, remove force_keep flag from object + if (tsio) { + gf_assert(gf_list_find(tsio->pending_repairs, rsi) == 0); + unprotect = GF_TRUE; + } + //remove before calling route_on_event, as it may trigger a delete + gf_list_del_item(ctx->seg_repair_queue, rsi); + + + if (!rsi->removed) { + //do a local file repair if we still have errors ? + if (rsi->nb_errors) { + switch (rsi->evt) { + case GF_ROUTE_EVT_DYN_SEG: + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR File %s still has error, doing a final local repair\n", rsi->finfo.filename)); + u32 repair = ctx->repair; + ctx->repair = ROUTEIN_REPAIR_STRICT; + routein_repair_local(ctx, rsi->evt, rsi->service_id, &rsi->finfo, GF_FALSE); + ctx->repair = repair; + rsi->nb_errors = 0; + break; + case GF_ROUTE_EVT_FILE: + case GF_ROUTE_EVT_MPD: + case GF_ROUTE_EVT_HLS_VARIANT: + rsi->finfo.partial = GF_LCTO_PARTIAL_ANY; + break; + default: + break; + } + } else if (ctx->chkiso) { + routein_check_isobmf(ctx, &rsi->finfo); + } + + //flush + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR Repair done for object %s (TSI=%u, TOI=%u) redownloaded %u bytes (file size %u)%s\n", rsi->finfo.filename, rsi->finfo.tsi, rsi->finfo.toi, rsi->nb_bytes_repaired, rsi->finfo.total_size, rsi->nb_errors ? " - errors remain" : "")); + + routein_on_event_file(ctx, rsi->evt, rsi->service_id, &rsi->finfo, GF_TRUE, GF_FALSE); + } + + //done with this repair, remove force_keep flag from object + if (unprotect) { + gf_route_dmx_force_keep_object(ctx->route_dmx, rsi->service_id, rsi->finfo.tsi, rsi->finfo.toi, GF_FALSE); + gf_list_del_item(tsio->pending_repairs, rsi); + if (!gf_list_count(tsio->pending_repairs)) + tsio->flags_progress &= ~TSIO_REPAIR_SCHEDULED; + } + + GF_List *bck = rsi->ranges; + if (rsi->filename) gf_free(rsi->filename); + if (rsi->sample_deps) { + if (!ctx->sample_deps_reservoir) ctx->sample_deps_reservoir = gf_list_new(); + while (gf_list_count(rsi->sample_deps)) { + gf_list_add(ctx->sample_deps_reservoir, gf_list_pop_back(rsi->sample_deps)); + } + gf_list_del(rsi->sample_deps); + } + memset(rsi, 0, sizeof(RepairSegmentInfo)); + rsi->ranges = bck; + gf_list_add(ctx->seg_repair_reservoir, rsi); + + + if (!tsio) return; + //get next in list, if existing and done or if removed, dequeue + rsi = gf_list_get(tsio->pending_repairs, 0); + if (rsi && (rsi->done || (rsi->removed && !rsi->pending))) + goto restart; +} + +static void repair_session_done(ROUTEInCtx *ctx, RouteRepairSession *rsess, GF_Err res_code) +{ + RepairSegmentInfo *rsi = rsess->current_si; + if (!rsi) return; + + if (rsess->range) { + //notify routedmx we have received a byte range + if (!rsi->removed && rsess->range->bytes_recv) { + + //we issued the open-range request for br_start-1, so we have one more byte + if (rsess->range->is_open) rsess->range->bytes_recv --; + + u64 patch_end = rsess->range->br_start + rsess->range->bytes_recv; + + if (rsess->range->br_end && (patch_end > rsess->range->br_end)) { + patch_end = rsess->range->br_end; + } else if (patch_end < rsess->range->br_end) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR Incomplete byte range in file %s: end offset %u but last byte received %u received byte range end %u\n", rsi->finfo.filename, rsess->range->br_end, patch_end)); + } + gf_route_dmx_patch_frag_info(ctx->route_dmx, rsi->service_id, &rsi->finfo, rsess->range->br_start, (u32) patch_end); + } + + //figure out total size if indicated by server - otherwise it is 0 + if ((res_code==GF_EOS) && !rsi->finfo.total_size) { + rsi->finfo.total_size = gf_dm_sess_get_resource_size(rsess->dld); + + if (!rsi->finfo.total_size && rsess->range->is_open && rsess->range->br_end) { + rsi->finfo.total_size = rsess->range->br_end; + } + //we have a total size ! + if (rsi->finfo.total_size) { + gf_route_dmx_patch_blob_size(ctx->route_dmx, rsi->service_id, &rsi->finfo, rsi->finfo.total_size); + } + //if last request, re-issue a new one + else if (! gf_list_count(rsi->ranges)) { + GF_ROUTEEventFileInfo *finfo = &rsess->current_si->finfo; + //we still don't have the file size, do another round of patching + u32 start_br = 0; + if (finfo->nb_frags) { + start_br = finfo->fragsfinfo->nb_frags-1.offset + finfo->fragsfinfo->nb_frags-1.size; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s total size still unknown, re-issuing an open range from %u\n", rsi->finfo.filename, start_br )); + queue_repair_range(ctx, rsess->current_si, start_br, 0); + } + } else if (res_code==GF_EOS) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("REPAIR File %s total size %u after http patch\n", rsi->finfo.filename, rsi->finfo.total_size)); + } + + gf_list_add(ctx->seg_range_reservoir, rsess->range); + if (!res_code && rsess->server && (rsess->server->accept_ranges == RANGE_SUPPORT_PROBE)) + rsess->server->accept_ranges = RANGE_SUPPORT_YES; + } + + rsess->initial_retry = 0; + //always reset even if we have pending byte ranges, so that each repair session fetches the most urgent download + rsess->current_si = NULL; + rsess->range = NULL; + if (res_code<0) { + rsi->nb_errors++; + } + + gf_assert(rsi->pending); + rsi->pending--; + if (rsi->pending) return; + + if (rsi->isox_state>=REPAIR_ISO_STATUS_PATCH_TOP_LEVEL) { + route_repair_build_ranges_isobmf(ctx, rsi, &rsi->finfo); + return; + } + + if (rsi->removed) { + gf_list_transfer(ctx->seg_range_reservoir, rsi->ranges); + } + + if (gf_list_count(rsi->ranges)) { + //if TSIO notify ranges when received + if (rsi->tsio && !rsi->nb_errors && !rsi->finfo.frags0.offset && + ((rsi->evt==GF_ROUTE_EVT_DYN_SEG_FRAG) || (rsi->evt==GF_ROUTE_EVT_DYN_SEG)) + ) { + gf_mx_p(rsi->finfo.blob->mx); + GF_LCTObjectPartial partial = rsi->finfo.partial; + u32 blob_flags = rsi->finfo.blob->flags; + rsi->finfo.partial = GF_LCTO_PARTIAL_NONE; + rsi->finfo.blob->flags = GF_BLOB_IN_TRANSFER; + //notify as DYN_SEG_FRAG not as DYN_SEG + routein_on_event_file(ctx, GF_ROUTE_EVT_DYN_SEG_FRAG, rsi->service_id, &rsi->finfo, GF_TRUE, GF_FALSE); + + rsi->finfo.partial = partial; + rsi->finfo.blob->flags = blob_flags; + gf_mx_v(rsi->finfo.blob->mx); + } + return; + } + gf_mx_p(rsi->finfo.blob->mx); + if (!rsi->nb_errors) { + gf_assert(rsi->finfo.nb_frags == 1); + rsi->finfo.partial = GF_LCTO_PARTIAL_NONE; + rsi->finfo.blob->flags &= ~GF_BLOB_CORRUPTED; + } + //not a fragment, remove in-transfer flag + if (rsi->evt!=GF_ROUTE_EVT_DYN_SEG_FRAG) + rsi->finfo.blob->flags &= ~GF_BLOB_IN_TRANSFER; + gf_mx_v(rsi->finfo.blob->mx); + + //make sure we dequeue in order + if (rsi->tsio) { + s32 idx = gf_list_find(rsi->tsio->pending_repairs, rsi); + //not first in list, do not dequeue now + //this happens if multiple repair sessions are activated, they will likely not finish at the same time + if (idx > 0) { + rsi->done = GF_TRUE; + return; + } + } + repair_session_dequeue(ctx, rsi); +} + +static void repair_session_run(ROUTEInCtx *ctx, RouteRepairSession *rsess) +{ + GF_Err e; + RepairSegmentInfo *rsi; + u32 offset, nb_read; + +restart: + rsi = rsess->current_si; + if (!rsi) { + RouteRepairRange *rr = NULL; + u32 i, count; + RouteRepairServer* repair_server = NULL; + char *url = NULL; + + if (rsess->retry_in && (gf_sys_clock()<rsess->retry_in)) return; + rsess->retry_in = 0; + + count = gf_list_count(ctx->seg_repair_queue); + for (i=0; i<count;i++) { + u32 j, nb_ranges; + rsi = gf_list_get(ctx->seg_repair_queue, i); + //over or no longer active + if (rsi->done || rsi->removed) continue; + + nb_ranges = gf_list_count(rsi->ranges); + //no more ranges, done with session + //this happens when enqueued repair had no losses when using progressive dispatch + if (!nb_ranges) { + rsess->current_si = rsi; + rsi->pending++; + repair_session_done(ctx, rsess, GF_OK); + rsess->current_si = NULL; + goto restart; + } + + //if TSIO, always dequeue in order + if (rsi->tsio) { + rr = gf_list_get(rsi->ranges, 0); + } else { + for (j=0; j<nb_ranges; j++) { + rr = gf_list_get(rsi->ranges, j); + //todo check priority + if (rr) break; + } + } + + if (rr) { + //we may have resolved the final resource size in a previous repair, check if we can cancel this range + if (!rr->br_end && rr->br_start && (rr->br_start == rsi->finfo.total_size)) { + gf_list_del_item(rsi->ranges, rr); + gf_list_add(ctx->seg_range_reservoir, rr); + i--; + rsi=NULL; + continue; + } + break; + } + rsi = NULL; + } + if (!rsi) return; + rsess->current_si = rsi; + rsi->pending++; + rsess->range = rr; + gf_list_del_item(rsi->ranges, rr); + gf_assert(rsi->finfo.filename); + gf_assert(rsi->finfo.filename0); + + const char *repair_base_uri, *repair_server_url; + gf_route_dmx_get_repair_info(ctx->route_dmx, rsi->service_id, &repair_base_uri, &repair_server_url); + u32 repair_base_uri_len = repair_base_uri ? (u32) strlen(repair_base_uri) : 0; + Bool has_repair_server = GF_FALSE; + + for (i=0; i< gf_list_count(ctx->repair_servers); i++) { + repair_server = gf_list_get(ctx->repair_servers, i); + if (repair_server->url && repair_server_url && !strcmp(repair_server->url, repair_server_url)) + has_repair_server = GF_TRUE; + if (!repair_server->url || !repair_server->accept_ranges) { + repair_server = NULL; + continue; + } + break; + } + if (!repair_server && !has_repair_server && repair_server_url) { + repair_server = routein_push_repair_server(ctx, repair_server_url, rsi->service_id); + if (repair_server && !repair_server->accept_ranges) repair_server = NULL; + } + + if (repair_server) { + const char *filename_start = NULL; + if (repair_base_uri && !strncmp(rsi->finfo.filename, repair_base_uri, repair_base_uri_len)) { + filename_start = rsi->finfo.filename + repair_base_uri_len+1; + } + if (!filename_start) { + char *sep = strstr(rsi->finfo.filename, "://"); + if (sep) { + filename_start = rsi->finfo.filename; + } else { + sep = strchr(rsi->finfo.filename, '/'); + filename_start = sep ? sep : rsi->finfo.filename; + } + } + url = gf_url_concatenate(repair_server->url, filename_start); + } + + + if (!url) { + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("REPAIR Failed to find an adequate repair server for %s - Repair abort \n", rsi->finfo.filename)); + rsi->nb_errors++; + repair_session_done(ctx, rsess, e); + return; + } + + if (!rsess->dld) { + GF_DownloadManager *dm = gf_filter_get_download_manager(ctx->filter); + rsess->dld = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_NOT_CACHED | GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_PERSISTENT, NULL, NULL, &e); + if (rsess->dld) { + gf_dm_sess_set_netcap_id(rsess->dld, "__ignore"); + gf_dm_sess_set_timeout(rsess->dld, 1); + } + } else { + e = gf_dm_sess_setup_from_url(rsess->dld, url, GF_FALSE); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("REPAIR Failed to setup download session for %s: %s\n", rsi->finfo.filename, gf_error_to_string(e))); + gf_free(url); + repair_session_done(ctx, rsess, e); + return; + } + rsess->server = repair_server; + if (rsess->range->br_end) { + if (rsess->current_si->finfo.total_size) + gf_assert(rsess->current_si->finfo.total_size >= rsess->range->br_end); + + gf_dm_sess_set_range(rsess->dld, rsess->range->br_start, rsess->range->br_end-1, GF_TRUE); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR Queue request for %s byte range %u-%u with local total size %u - nb frags %u - %u queued ranges\n", url, rsess->range->br_start, rsess->range->br_end-1, rsess->current_si->finfo.total_size, rsess->current_si->finfo.nb_frags, gf_list_count(rsess->current_si->ranges) )); + } else { + u32 start_r = rr->br_start; + if (!rr->br_start) { + rr->is_open = 0; + rsess->current_si->finfo.total_size = 0; + } else { + //add one more byte, so that if the resource is complete (we missed last 0-byte chunk) we issue + //a request falling into the file range + start_r -= 1; + rr->is_open = 1; + } + gf_dm_sess_set_range(rsess->dld, start_r, 0, GF_TRUE); + //current_si->finfo.total_size can be 0 or not, as we may have resolved the file size in a previous request + + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR Queue request for %s open byte range %u- with local total size %u - nb frags %u - %u queued ranges\n", url, start_r, rsess->current_si->finfo.total_size, rsess->current_si->finfo.nb_frags, gf_list_count(rsess->current_si->ranges) )); + } + gf_free(url); + ctx->has_data = GF_TRUE; + } + +refetch: + offset = rsess->range->br_start + rsess->range->bytes_recv; + //we requested one more byte, adjust offset + if (rsess->range->is_open) offset -= 1; + nb_read=0; + if (rsi->removed) { + e = GF_URL_REMOVED; + gf_dm_sess_abort(rsess->dld); + } else { + e = gf_dm_sess_fetch_data(rsess->dld, rsess->http_buf, REPAIR_BUF_SIZE, &nb_read); + if (e==GF_IP_NETWORK_EMPTY) return; + if (e==GF_IO_BYTE_RANGE_NOT_SUPPORTED) { + u32 now = gf_sys_clock(); + u32 error_type = 0; + u32 filesize = gf_dm_sess_get_resource_size(rsess->dld); + //it is quite possible that the server replied 200 on an open end-range request when the segment size is unknown + if (!rsess->range->br_end) { + //in case we lost a FDT with content-length=0 + if (filesize==rsess->range->br_start) { + error_type = 1; + } else { + //200 instead of 206 on server supporting byte-ranges: the content size is not yet known, cancel and postpone + //note: we're either in probe mode or know the server accepts ranges so we can't figure out if the server doesn't accept + //by postponing we should get an answer soon... + error_type = 2; + } + } else if (rsess->server->accept_ranges==RANGE_SUPPORT_PROBE) { + rsess->server->accept_ranges = RANGE_SUPPORT_NO; + } else { + //200 instead of 206 on server supporting byte-ranges: the content size is not yet known, cancel and postpone + error_type = 2; + } + if (error_type==2) { + if (! rsess->initial_retry) rsess->initial_retry = now; + else if (rsess->initial_retry + 1000 < now) { + error_type = 3; + } + } + + if (e) { + if (!error_type) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR Server \"%s\" does not support byte range requests: Server is blacklisted for partial repair \n", rsess->server->url)); + } + gf_dm_sess_abort(rsess->dld); + gf_dm_sess_del(rsess->dld); + rsess->dld = NULL; + rsi->pending--; + rsess->current_si = NULL; + if ((error_type==1) || (error_type==3)) { + gf_list_add(ctx->seg_range_reservoir, rsess->range); + if (error_type==3) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("REPAIR Server \"%s\" failed to give byte range after %u ms, aborting repair\n", rsess->server->url, now - rsess->initial_retry)); + rsess->current_si->nb_errors++; + rsess->initial_retry = 0; + } + } else { + //try with another server if there is one + gf_list_add(rsi->ranges, rsess->range); + } + rsess->range = NULL; + if (error_type==2) { + rsess->retry_in = gf_sys_clock() + 100; + return; + } + goto restart; + } + } + } + + rsi->nb_bytes_repaired += nb_read; + + //open end range - we should always get 1 byte minimum given how we build the open range + if (!rsess->range->br_end) { + if (e>=GF_OK) { + u32 res_size = gf_dm_sess_get_resource_size(rsess->dld); + gf_assert(!rsess->current_si->finfo.total_size || !res_size || (res_size == rsess->current_si->finfo.total_size) ); + + //we are in progress, just patch the blob size to allow for further dispatch (todo) + if (e==GF_OK) { + GF_Err patch_e = gf_route_dmx_patch_blob_size(ctx->route_dmx, rsess->current_si->service_id, &rsess->current_si->finfo, res_size); + if (patch_e) + e = patch_e; + } + //we are done + else { + rsess->range->br_end = res_size; + //no new bytes (we requested one less), no need to patch + if ((rsess->range->bytes_recv==1) || (!rsess->range->bytes_recv && (nb_read==1))) { + rsess->range->bytes_recv = 0; + nb_read = 0; + } + + //we issued an open byte range but the server didn't know the file size at the time of the reply + //queue another repair below + if (!rsess->range->br_end) { + } else { + if (rsess->range->br_end < rsess->current_si->finfo.blob->size) { + e = GF_REMOTE_SERVICE_ERROR; + } else { + // !! keep finfo.total_size untouched until end of download + u32 prev_size = rsess->current_si->finfo.total_size; + GF_Err patch_e = gf_route_dmx_patch_blob_size(ctx->route_dmx, rsess->current_si->service_id, &rsess->current_si->finfo, rsess->range->br_end); + rsess->current_si->finfo.total_size = prev_size; + if (patch_e) + e = patch_e; + } + } + } + } else { + //if we have an error and the content start was the first byte after the known file size, consider we got the final range + u64 res_size = gf_dm_sess_get_resource_size(rsess->dld); + if (res_size && (res_size == rsess->range->br_start)) { + rsess->range->br_end = rsess->range->br_start; + e = GF_EOS; + } + } + } + if (rsess->range->br_end && (offset + nb_read > rsess->range->br_end)) { + e = GF_REMOTE_SERVICE_ERROR; + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR Repair got more bytes (%u) than requested (%u), aborting !\n", nb_read, rsess->range->br_end-offset )); + } + + if (nb_read && (e>=GF_OK)) { + gf_mx_p(rsi->finfo.blob->mx); + if (rsess->current_si->finfo.blob->size < offset + nb_read) { + // !! keep finfo.total_size untouched until end of download + u32 prev_size = rsess->current_si->finfo.total_size; + gf_route_dmx_patch_blob_size(ctx->route_dmx, rsess->current_si->service_id, &rsess->current_si->finfo, offset + nb_read); + rsess->current_si->finfo.total_size = prev_size; + } + memcpy(rsi->finfo.blob->data + offset, rsess->http_buf, nb_read); + gf_mx_v(rsi->finfo.blob->mx); + rsess->range->bytes_recv += nb_read; + ctx->has_data = GF_TRUE; + //for now, fragment info is updated once the session is done + //flush buffer until network empty + if (e==GF_OK) goto refetch; + } + if (e==GF_OK) return; + + //abort session + if (e<GF_OK) + gf_dm_sess_abort(rsess->dld); + + repair_session_done(ctx, rsess, e); + if (e<0) return; + + goto restart; + +} + +GF_Err routein_do_repair(ROUTEInCtx *ctx) +{ + u32 i, nb_active=0; + ctx->has_data = GF_FALSE; + for (i=0; i<ctx->max_sess; i++) { + RouteRepairSession *rsess = &ctx->http_repair_sessionsi; + repair_session_run(ctx, rsess); + if (rsess->current_si) nb_active++; + } + if (!nb_active) return GF_EOS; + if (ctx->has_data) return GF_OK; + return GF_IP_NETWORK_EMPTY; +} + + +void routein_repair_mark_file(ROUTEInCtx *ctx, u32 service_id, const char *filename, Bool is_delete) +{ + u32 i, count = gf_list_count(ctx->seg_repair_queue); + for (i=0; i<count; i++) { + RepairSegmentInfo *rsi = gf_list_get(ctx->seg_repair_queue, i); + if (!rsi->done && (rsi->service_id==service_id) && !strcmp(rsi->finfo.filename, filename)) { + //we don't cancel sessions now, this should be done in session_done + if (is_delete) { + //log is set as warning for now as this is work in progress + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("REPAIR Repair canceled for object %s (TSI=%u, TOI=%u)\n", rsi->finfo.filename, rsi->finfo.tsi, rsi->finfo.toi)); + rsi->removed = GF_TRUE; + } else { + //TODO: decide if we need to be more agressive ? + } + return; + } + } +} + + +#endif /* GPAC_DISABLE_ROUTE */
View file
gpac-2.4.0.tar.gz/src/filters/in_rtp.c -> gpac-26.02.0.tar.gz/src/filters/in_rtp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / RTP/RTSP input filter @@ -494,7 +494,7 @@ if (gf_rtsp_session_reset(ctx->session->session, 1)<10) { #ifdef GPAC_HAS_SSL if (gf_rtsp_session_needs_ssl(ctx->session->session) ) { - gf_rtsp_set_ssl_ctx(ctx->session->session, gf_dm_ssl_init(ctx->dm, 0) ); + gf_rtsp_set_ssl_ctx(ctx->session->session, gf_dm_ssl_init(ctx->dm, GF_TRUE) ); } #endif e = GF_OK; @@ -863,7 +863,7 @@ gf_rtsp_session_needs_ssl(ctx->session->session); #endif - GF_Err e = gf_rtsp_set_ssl_ctx(ctx->session->session, gf_dm_ssl_init(ctx->dm, 0) ); + GF_Err e = gf_rtsp_set_ssl_ctx(ctx->session->session, gf_dm_ssl_init(ctx->dm, GF_TRUE) ); if (e) return e; #else return GF_NOT_SUPPORTED; @@ -922,9 +922,9 @@ static const GF_FilterCapability RTPInCaps = { - CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), - CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "sdp"), - CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/sdp"), + CAP_UINT(GF_CAPS_INPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT_STATIC, GF_PROP_PID_FILE_EXT, "sdp"), + CAP_STRING(GF_CAPS_INPUT_STATIC, GF_PROP_PID_MIME, "application/sdp"), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), @@ -1004,7 +1004,8 @@ .process = rtpin_process, .process_event = rtpin_process_event, .probe_url = rtpin_probe_url, - .probe_data = rtpin_probe_data + .probe_data = rtpin_probe_data, + .hint_class_type = GF_FS_CLASS_NETWORK_IO }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/in_rtp.h -> gpac-26.02.0.tar.gz/src/filters/in_rtp.h
Changed
@@ -43,12 +43,11 @@ typedef struct _rtsp_session GF_RTPInRTSP; typedef struct __rtpin_stream GF_RTPInStream; -enum -{ +GF_OPT_ENUM (GF_RTPInRTSP_Mode, RTP_TRANSPORT_AUTO=0, RTP_TRANSPORT_TCP_ONLY, RTP_TRANSPORT_UDP_ONLY, -}; +); enum { @@ -72,7 +71,7 @@ u32 udp_timeout, rtcp_timeout, stats; Bool forceagg; /*transport mode. 0 is udp, 1 is tcp, 3 is tcp if unreliable media */ - u32 transport; + GF_RTPInRTSP_Mode transport; s32 max_sleep, loss_rate; Bool rtcpsync; GF_PropStringList ssm, ssmx; @@ -388,7 +387,7 @@ void rtpin_satip_get_server_ip(const char *sURL, char *Server); #ifdef GPAC_HAS_SSL -void *gf_dm_ssl_init(GF_DownloadManager *dm, u32 mode); +void *gf_dm_ssl_init(GF_DownloadManager *dm, Bool no_quic); GF_Err gf_rtsp_set_ssl_ctx(GF_RTSPSession *sess, void *ssl_CTX); Bool gf_rtsp_session_needs_ssl(GF_RTSPSession *sess); #endif
View file
gpac-2.4.0.tar.gz/src/filters/in_rtp_rtsp.c -> gpac-26.02.0.tar.gz/src/filters/in_rtp_rtsp.c
Changed
@@ -109,7 +109,7 @@ #ifdef GPAC_HAS_SSL if (gf_rtsp_session_needs_ssl(sess->session) ) { - gf_rtsp_set_ssl_ctx(sess->session, gf_dm_ssl_init(sess->rtpin->dm, 0) ); + gf_rtsp_set_ssl_ctx(sess->session, gf_dm_ssl_init(sess->rtpin->dm, GF_TRUE) ); } #endif return; @@ -226,6 +226,8 @@ if (!control) return NULL; if (!strcmp(control, "*")) control = (char *) rtp->src; + if (!control) return NULL; + if (gf_rtsp_is_my_session(rtp->session->session, control)) return rtp->session; return NULL; } @@ -345,8 +347,11 @@ if (has_aggregated_control) in_session->flags |= RTSP_AGG_CONTROL; } else if (stream->control) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTSP Cannot locate session by control string %s\n", stream->control)); gf_free(stream->control); stream->control = NULL; + stream->status = RTP_Unavailable; + return GF_NON_COMPLIANT_BITSTREAM; } stream->rtsp = in_session; gf_list_add(rtp->streams, stream);
View file
gpac-2.4.0.tar.gz/src/filters/in_rtp_sdp.c -> gpac-26.02.0.tar.gz/src/filters/in_rtp_sdp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / RTP/RTSP input filter @@ -232,6 +232,8 @@ sl_map->rvc_config_size = 0; } } else if (static_map) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_FILE_EXT, NULL); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_FILEPATH, NULL); if (static_map->stream_type) gf_filter_pid_set_property(stream->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(static_map->stream_type) ); if (static_map->codec_id)
View file
gpac-2.4.0.tar.gz/src/filters/in_sock.c -> gpac-26.02.0.tar.gz/src/filters/in_sock.c
Changed
@@ -70,6 +70,7 @@ u32 reorder_delay; #endif GF_PropStringList ssm, ssmx; + GF_PropVec2i mwait; GF_SockInClient sock_c; GF_List *clients; @@ -82,6 +83,8 @@ GF_SockGroup *active_sockets; u32 last_rcv_time; u32 last_timeout_sec; + + u64 rcv_time_diff, last_pck_time; } GF_SockInCtx; @@ -95,7 +98,10 @@ GF_SockInCtx *ctx = (GF_SockInCtx *) gf_filter_get_udta(filter); if (!ctx || !ctx->src) return GF_BAD_PARAM; - + if ((ctx->mwait.y < ctx->mwait.x) || (ctx->mwait.x<0) || (ctx->mwait.y<0)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("SockIn Invalid `mwait`, max %d must be greater than min %d\n", ctx->mwait.y, ctx->mwait.x)); + return GF_IO_ERR; + } ctx->active_sockets = gf_sk_group_new(); if (!ctx->active_sockets) return GF_OUT_OF_MEM; @@ -282,7 +288,7 @@ static GF_Err sockin_read_client(GF_Filter *filter, GF_SockInCtx *ctx, GF_SockInClient *sock_c) { - u32 nb_read, pos; + u32 nb_read, pos, nb_pck=100; u64 now; GF_Err e; GF_FilterPacket *dst_pck; @@ -298,6 +304,8 @@ } if (!sock_c->start_time) sock_c->start_time = gf_sys_clock_high_res(); + +refetch: pos = 0; nb_read=0; while (pos < ctx->block_size) { @@ -436,7 +444,11 @@ sock_c->nb_bytes = 0; } - return GF_OK; + if (e || (!ctx->is_udp && ctx->block)) return e; + nb_pck--; + if (nb_pck) goto refetch; + + return e; } static GF_Err sockin_check_eos(GF_Filter *filter, GF_SockInCtx *ctx) @@ -491,11 +503,14 @@ return GF_OK; } } else if (!gf_list_count(ctx->clients)) { - gf_filter_ask_rt_reschedule(filter, 1000); + gf_filter_ask_rt_reschedule(filter, 5000); return GF_OK; } - - gf_filter_ask_rt_reschedule(filter, 1000); + u64 sleep_for = 2*ctx->rcv_time_diff/3000; + if (sleep_for > ctx->mwait.y) sleep_for = ctx->mwait.y; + if (sleep_for < ctx->mwait.x) sleep_for = ctx->mwait.x; + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("SockIn empty - sleeping for "LLU" ms\n", sleep_for )); + gf_filter_ask_rt_reschedule(filter, (u32) (sleep_for*1000) ); return GF_OK; } else if ((e==GF_IP_CONNECTION_CLOSED) || (e==GF_EOS)) { @@ -510,9 +525,14 @@ ctx->last_rcv_time = 0; if (gf_sk_group_sock_is_set(ctx->active_sockets, ctx->sock_c.socket, GF_SK_SELECT_READ)) { + u64 rcv_time = gf_sys_clock_high_res(); + if (ctx->last_pck_time) { + ctx->rcv_time_diff = rcv_time - ctx->last_pck_time; + } + ctx->last_pck_time = rcv_time; if (!ctx->listen) { e = sockin_read_client(filter, ctx, &ctx->sock_c); - gf_filter_ask_rt_reschedule(filter, 1); + if (e==GF_IP_NETWORK_EMPTY) return GF_OK; return e; } @@ -612,6 +632,7 @@ { OFFS(mime), "indicate mime type of udp data", GF_PROP_STRING, NULL, NULL, 0}, { OFFS(block), "set blocking mode for socket(s)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(timeout), "set timeout in ms for UDP socket(s), 0 to disable timeout", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mwait), "set min and max wait times in ms to avoid too frequent polling", GF_PROP_VEC2I, "1x30", NULL, GF_FS_ARG_HINT_ADVANCED}, #ifndef GPAC_DISABLE_STREAMING { OFFS(reorder_pck), "number of packets delay for RTP reordering (M2TS over RTP) ", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -655,6 +676,9 @@ #ifdef GPAC_CONFIG_DARWIN "\nOn OSX with VM packet replay you will need to force multicast routing, e.g. `route add -net 239.255.1.4/32 -interface vboxnet0`" #endif + "\n" + "# Time Regulation\n" + "The filter uses the time between the last two received packets to estimates how often it should check for inputs. The maximum and minimum times to wait between two calls is given by the -mwait() option. The maximum time may need to be reduced for very high bitrates sources.\n" "" , #endif //GPAC_DISABLE_DOC @@ -665,7 +689,8 @@ .finalize = sockin_finalize, .process = sockin_process, .process_event = sockin_process_event, - .probe_url = sockin_probe_url + .probe_url = sockin_probe_url, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/inspect.c -> gpac-26.02.0.tar.gz/src/filters/inspect.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / inspection filter @@ -28,9 +28,12 @@ #include <gpac/list.h> #include <gpac/xml.h> #include <gpac/internal/media_dev.h> +#include <gpac/internal/isomedia_dev.h> #ifndef GPAC_DISABLE_INSPECT +static u32 inspect_log_tool = GF_LOG_APP; + typedef struct { GF_FilterPid *src_pid; @@ -41,7 +44,7 @@ u8 init_pid_config_done; u64 pck_for_config; u64 prev_dts, prev_cts, init_ts; - u32 codec_id; + u32 codec_id, service_id; u32 stream_type; #ifndef GPAC_DISABLE_AV_PARSERS @@ -62,6 +65,7 @@ Bool is_adobe_protected; Bool is_cenc_protected; Bool aborted; + Bool is_fake; GF_Fraction tmcd_rate; u32 tmcd_flags; @@ -74,18 +78,34 @@ u32 buf_start_time; u64 last_pcr; GF_FilterClockType last_clock_type; + + char *stat_codecs; + GF_FilterSAPType start_with_sap; + u32 timescale; + u64 total_dur; + u64 last_sap_cts; + u32 nb_sap1, nb_sap2, nb_sap3, nb_sap4; + u32 min_sap_diff, max_sap_diff, avg_sap_diff; + u64 nb_bytes; + s64 max_ctso, min_ctso; + Bool has_disc, has_corr; + u8 has_crypted; + u32 nb_recfg; + u32 max_sap_size, avg_sap_size, avg_nosap_size; + u32 nb_nosaps; + u32 constant_dur, max_dur; + u64 first_dts, first_cts; + u32 bytes_in_wnd, max_rate; } PidCtx; -enum -{ +GF_OPT_ENUM (GF_InspectDumpMode, INSPECT_MODE_PCK=0, INSPECT_MODE_BLOCK, INSPECT_MODE_REFRAME, - INSPECT_MODE_RAW -}; + INSPECT_MODE_RAW, +); -enum -{ +GF_OPT_ENUM (GF_InspectSkipPropsMode, INSPECT_TEST_NO=0, INSPECT_TEST_NOPROP, INSPECT_TEST_NETWORK, @@ -93,29 +113,29 @@ INSPECT_TEST_ENCODE, INSPECT_TEST_ENCX, INSPECT_TEST_NOCRC, - INSPECT_TEST_NOBR -}; + INSPECT_TEST_NOBR, +); -enum -{ +GF_OPT_ENUM (GF_InspectSampleAnalyzeMode, INSPECT_ANALYZE_OFF=0, INSPECT_ANALYZE_ON, INSPECT_ANALYZE_BS, INSPECT_ANALYZE_BS_BITS, -}; +); typedef struct { - u32 mode; + u32 mode, timeout; Bool interleave; Bool dump_data; Bool deep; + Bool stats; char *log; char *fmt; - u32 analyze; + GF_InspectSampleAnalyzeMode analyze; Bool props, hdr, allp, info, pcr, xml, full; Double speed, start; - u32 test; + GF_InspectSkipPropsMode test; GF_Fraction dur; Bool crc, dtype; Bool fftmcd; @@ -129,6 +149,9 @@ Bool is_prober, probe_done, hdr_done, dump_pck; Bool args_updated; Bool has_seen_eos; + + u32 last_config_time; + } GF_InspectCtx; static void format_duration(s64 dur, u64 timescale, FILE *dump, Bool skip_name); @@ -140,10 +163,10 @@ va_list list; if (dump == NULL) { #ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_APP, GF_LOG_INFO) ) { - gf_log_lt(GF_LOG_INFO, GF_LOG_APP); + if (gf_log_tool_level_on(inspect_log_tool, GF_LOG_INFO) ) { + gf_log_lt(GF_LOG_INFO, inspect_log_tool); va_start(list, fmt); - gf_log_va_list(GF_LOG_INFO, GF_LOG_APP, fmt, list); + gf_log_va_list(GF_LOG_INFO, inspect_log_tool, fmt, list); va_end(list); } #endif @@ -184,6 +207,12 @@ inspect_printf(dump, " %s 0x%08X", _name, _val);\ } +#define DUMP_ATT_BOOL(_name, _val) if (_val == 0) { \ + DUMP_ATT_STR(_name, "False")\ + } else {\ + DUMP_ATT_STR(_name, "True")\ + } + #define DUMP_ATT_FRAC(_name, _val) if (ctx->xml) { \ inspect_printf(dump, " %s=\"%d/%u\"", _name, _val.num, _val.den);\ } else {\ @@ -551,15 +580,64 @@ } } +static void dump_unregistered_sei(FILE *dump, GF_BitStream *bs, u32 sei_size) +{ + /* original timecode unregistered SEI UUID */ + bin128 tmcd_uuid = { + 0x42, 0x45, 0x3a, 0x42, 0x3b, 0xaa, 0xa6, 0xf1, + 0xbb, 0x3b, 0x13, 0x42, 0x0e, 0x08, 0xc7, 0x62 + }; + + //uuid + u32 i; + bin128 uuid = {0}; + inspect_printf(dump, " uuid=\"0x"); + for (i=0; i<16; i++) { + uuidi = gf_bs_read_u8(bs); + inspect_printf(dump, "%02x", uuidi); + } + inspect_printf(dump, "\""); + + //payload + u32 nb_read = 0; + u8 *payload = gf_malloc(sei_size - 16); + if (!payload) return; + inspect_printf(dump, " payload=\"0x"); + for (i=0; i<sei_size - 16 && gf_bs_available(bs); i++) { + payloadi = gf_bs_read_u8(bs); + inspect_printf(dump, "%02x", payloadi); + nb_read++; + } + inspect_printf(dump, "\""); + + if (nb_read < sei_size - 16) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Not enough data in unregistered SEI\n")); + goto finish; + } + + if (!memcmp(uuid, tmcd_uuid, 16)) { + if (nb_read < 4) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Not enough data in timecode unregistered SEI\n")); + goto finish; + } + inspect_printf(dump, " timecode=\""); + inspect_printf(dump, "%02u:%02u:%02u:%02u", payload3, payload2, payload1, payload0); + inspect_printf(dump, "\""); + } + +finish: + gf_free(payload); +} + static u32 dump_udta_m2v(FILE *dump, u8 *data, u32 sei_size) { - u32 udta_id = 0; + u32 udta_id = 0; if (sei_size < 4) return 1; udta_id = GF_4CC(data0, data1, data2, data3); - u32 udta_code = 0; + u32 udta_code = 0; inspect_printf(dump, " udta_id=\"%s\"", gf_4cc_to_str(udta_id)); @@ -767,8 +845,9 @@ dump_time_code_hevc(dump, bs); } else if (sei_type == 4) { dump_t35(dump, bs, sei_size); - } - else if (avc && (sei_type==6)) { + } else if (sei_type == 5) { + dump_unregistered_sei(dump, bs, sei_size); + } else if (avc && (sei_type==6)) { u32 frame_cnt = gf_bs_read_ue(bs); inspect_printf(dump, " frame_count=\"%u\"", frame_cnt); } @@ -787,8 +866,72 @@ } } +static void inspect_dump_crypt(FILE *dump, u32 nal_size, u8 *sai_buffer, u32 sai_buffer_size, u32 nalu_offset) +{ + GF_BitStream *bs = gf_bs_new(sai_buffer, sai_buffer_size, GF_BITSTREAM_READ); + u8 iv_size = 0; + Bool multi_key=GF_FALSE; + u32 nb_iv_init=0; + u32 i, nb_subs; + +restart: + if (iv_size && !multi_key) gf_bs_skip_bytes(bs, iv_size); + nb_subs = gf_bs_read_u16(bs); + if (!multi_key && (gf_bs_available(bs) == nb_subs*6)) { + + } else if (!multi_key && (iv_size<16)) { + gf_bs_seek(bs, 0); + iv_size += 8; + goto restart; + } else { + //multikey + if (!multi_key) { + iv_size=0; + multi_key = GF_TRUE; + gf_bs_seek(bs, 0); + nb_subs = gf_bs_read_u16(bs); + } + nb_iv_init = nb_subs; + gf_bs_skip_bytes(bs, nb_iv_init*(2+iv_size)); + nb_subs = gf_bs_read_u32(bs); + if (gf_bs_available(bs) == nb_subs*8) { + } else if (iv_size<16) { + gf_bs_seek(bs, 0); + iv_size += 8; + goto restart; + } else { + inspect_printf(dump, "encrypted=\"unsupported multikey\" "); + gf_bs_del(bs); + return; + } + } + + u32 offset=0; + for (i=0; i<nb_subs; i++) { + if (multi_key) gf_bs_read_u16(bs); + u32 clear = gf_bs_read_u16(bs); + u32 crypt = gf_bs_read_u32(bs); + if ((nalu_offset>=offset) && (nalu_offset+nal_size <= offset+clear)) { + inspect_printf(dump, "encrypted=\"no\" "); + gf_bs_del(bs); + return; + } + if ((nalu_offset>=offset) && (nalu_offset+nal_size <= offset+clear+crypt)) { + inspect_printf(dump, "encrypted=\"yes\" "); + gf_bs_del(bs); + return; + } + offset += clear+crypt; + if (nalu_offset+nal_size<offset) break; + } + + inspect_printf(dump, "encrypted=\"no\" "); + gf_bs_del(bs); +} + + -static void gf_inspect_dump_nalu_internal(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, u32 full_bs_dump, PidCtx *pctx) +static void gf_inspect_dump_nalu_internal(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, GF_InspectSampleAnalyzeMode full_bs_dump, PidCtx *pctx, u8 *sai_buffer, u32 sai_buffer_size, u32 nalu_offset) { s32 res = 0; u8 type, nal_ref_idc; @@ -796,14 +939,17 @@ u8 track_ref_index; s8 sample_offset; u32 data_offset, data_size; + Bool full_parse = GF_FALSE; s32 idx; InspectLogCbk lcbk; GF_BitStream *bs = NULL; const char *nal_name; - if (full_bs_dump<INSPECT_ANALYZE_BS) + if (full_bs_dump<INSPECT_ANALYZE_BS) { + if (!gf_sys_is_test_mode() && full_bs_dump) + full_parse = GF_TRUE; full_bs_dump = 0; - else { + } else { lcbk.dump = dump; lcbk.dump_bits = full_bs_dump==INSPECT_ANALYZE_BS_BITS ? GF_TRUE : GF_FALSE; } @@ -815,6 +961,10 @@ if (dump_crc) inspect_printf(dump, "crc=\"%u\" ", gf_crc_32(ptr, ptr_size) ); + if (sai_buffer && sai_buffer_size) { + inspect_dump_crypt(dump, ptr_size+nalh_size, sai_buffer, sai_buffer_size, nalu_offset); + } + if (hevc) { if (ptr_size<=1) { inspect_printf(dump, "error=\"invalid nal size 1\"/>\n"); @@ -836,6 +986,8 @@ res = gf_hevc_parse_nalu_bs(bs, hevc, &type, &temporal_id, &quality_id); } else { bs = NULL; + if (full_parse) + hevc->full_slice_header_parse = GF_TRUE; res = gf_hevc_parse_nalu(ptr, ptr_size, hevc, &type, &temporal_id, &quality_id); inspect_printf(dump, "code=\"%d\"", type); } @@ -1075,6 +1227,15 @@ inspect_printf(dump, " slice_type=\"%d\"", hevc->s_info.slice_type); } } + if (!gf_sys_is_test_mode() && (type < GF_HEVC_NALU_VID_PARAM) && hevc->s_info.nb_reference_pocs) { + u32 i; + inspect_printf(dump, " POC=\"%d\" referencePOCs=\"", hevc->s_info.poc); + for (i=0; i<hevc->s_info.nb_reference_pocs; i++) { + if (i) inspect_printf(dump, " "); + inspect_printf(dump, "%d", hevc->s_info.reference_pocsi); + } + inspect_printf(dump, "\""); + } if (!full_bs_dump) inspect_printf(dump, " layer_id=\"%d\" temporal_id=\"%d\"", quality_id, temporal_id); @@ -1244,6 +1405,16 @@ gf_bs_set_logger(bs, NULL, NULL); } + if (!gf_sys_is_test_mode() && (type <= GF_VVC_NALU_SLICE_GDR) && vvc->s_info.nb_reference_pocs) { + u32 i; + inspect_printf(dump, " POC=\"%d\" referencePOCs=\"", vvc->s_info.poc); + for (i=0; i<vvc->s_info.nb_reference_pocs; i++) { + if (i) inspect_printf(dump, " "); + inspect_printf(dump, "%d", vvc->s_info.reference_pocsi); + } + inspect_printf(dump, "\""); + } + if ((type == GF_VVC_NALU_SEI_PREFIX) || (type == GF_VVC_NALU_SEI_SUFFIX)) { inspect_printf(dump, ">\n"); if (pctx) { @@ -1476,10 +1647,10 @@ } GF_EXPORT -void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted) +void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, u8 *sai_buffer, u32 sai_buffer_size, u32 sample_offset) { if (!dump) return; - gf_inspect_dump_nalu_internal(dump, ptr, ptr_size, is_svc, hevc, avc, vvc, nalh_size, dump_crc, is_encrypted, inspect_get_analyze_mode(), NULL); + gf_inspect_dump_nalu_internal(dump, ptr, ptr_size, is_svc, hevc, avc, vvc, nalh_size, dump_crc, is_encrypted, inspect_get_analyze_mode(), NULL, sai_buffer, sai_buffer_size, sample_offset); } static void av1_dump_tile(FILE *dump, u32 idx, AV1Tile *tile) @@ -1487,10 +1658,10 @@ inspect_printf(dump, " <Tile number=\"%d\" start=\"%d\" size=\"%d\"/>\n", idx, tile->obu_start_offset, tile->size); } -static u64 gf_inspect_dump_obu_internal(FILE *dump, AV1State *av1, u8 *obu_ptr, u64 obu_ptr_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc, PidCtx *pctx, u32 full_dump) +static u64 gf_inspect_dump_obu_internal(FILE *dump, AV1State *av1, u8 *obu_ptr, u64 obu_ptr_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc, PidCtx *pctx, GF_InspectSampleAnalyzeMode full_dump, u8 *sai_buffer, u32 sai_buffer_size, u32 offset_in_sample) { //when the pid context is not set, obu_size (which includes the header size in gpac) must be set - if (!pctx && (obu_size <= 2)) + if (!pctx && (obu_size <= 1)) return obu_size; if (pctx) { @@ -1505,7 +1676,6 @@ } gf_av1_parse_obu(pctx->bs, &obu_type, &obu_size, &hdr_size, pctx->av1_state); - if (full_dump>=INSPECT_ANALYZE_BS) { gf_bs_set_logger(pctx->bs, NULL, NULL); } else { @@ -1523,6 +1693,10 @@ inspect_printf(dump, " size=\""LLU"\" type=\"%s\" header_size=\"%d\" ", obu_size, gf_av1_get_obu_name(obu_type), hdr_size); + if (sai_buffer && sai_buffer_size) { + inspect_dump_crypt(dump, obu_size, sai_buffer, sai_buffer_size, offset_in_sample); + } + if (!full_dump) { inspect_printf(dump, "has_size_field=\"%d\" has_ext=\"%d\" temporalID=\"%d\" spatialID=\"%d\" ", av1->obu_has_size_field, av1->obu_extension_flag, av1->temporal_id , av1->spatial_id); } @@ -1596,7 +1770,8 @@ case OBU_METADATA: if (obu_ptr_length>hdr_size) { GF_BitStream *bs = gf_bs_new(obu_ptr+hdr_size, obu_ptr_length-hdr_size, GF_BITSTREAM_READ); - u32 metadata_type = (u32)gf_av1_leb128_read(bs, NULL); + u8 nb_bytes = 0; + ObuMetadataType metadata_type = (ObuMetadataType)gf_av1_leb128_read(bs, &nb_bytes); DUMP_OBU_INT2("metadata_type", metadata_type); switch (metadata_type) { case OBU_METADATA_TYPE_TIMECODE: @@ -1611,6 +1786,10 @@ case OBU_METADATA_TYPE_HDR_MDCV: dump_mdcv(dump, bs, GF_FALSE); break; + case OBU_METADATA_TYPE_PRIVATE_TIMECODE_SIMPLE: + case OBU_METADATA_TYPE_PRIVATE_TIMECODE_SIMPLE_BIS: + dump_unregistered_sei(dump, bs, obu_size - hdr_size - nb_bytes); + break; default: break; } @@ -1628,10 +1807,10 @@ } GF_EXPORT -void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu_ptr, u64 obu_ptr_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc) +void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu_ptr, u64 obu_ptr_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc, u8 *sai_buffer, u32 sai_buffer_size, u32 offset_in_sample) { if (!dump) return; - gf_inspect_dump_obu_internal(dump, av1, obu_ptr, obu_ptr_length, obu_type, obu_size, hdr_size, dump_crc, NULL, inspect_get_analyze_mode()); + gf_inspect_dump_obu_internal(dump, av1, obu_ptr, obu_ptr_length, obu_type, obu_size, hdr_size, dump_crc, NULL, inspect_get_analyze_mode(), sai_buffer, sai_buffer_size, offset_in_sample); } static void gf_inspect_dump_prores_internal(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx) @@ -1808,6 +1987,7 @@ if (self_delimited) { if (pck_offset+pckh.packet_size >= size) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Opus Not enough data to parse next self-delimited packet!\n")); + break; } pck_offset += pckh.packet_size; } @@ -1818,7 +1998,7 @@ void gf_inspect_dump_opus(FILE *dump, u8 *ptr, u64 size, u32 channel_count, Bool dump_crc) { if (!dump) return; - gf_inspect_dump_opus_internal(dump, ptr, (u32) size, channel_count, dump_crc, NULL); + gf_inspect_dump_opus_internal(dump, ptr, (u32) size, channel_count, dump_crc, NULL); } enum { @@ -1968,14 +2148,20 @@ #endif -static void finalize_dump(GF_InspectCtx *ctx, u32 streamtype, Bool concat) +static void finalize_dump(GF_InspectCtx *ctx, u32 streamtype, Bool concat, u32 for_service_id) { char szLine1025; u32 i, count = gf_list_count(ctx->src_pids); + for (i=0; i<count; i++) { PidCtx *pctx = gf_list_get(ctx->src_pids, i); //already done if (!pctx->tmp) continue; + + //if service ID, dump by service + if (for_service_id) { + if (for_service_id!=pctx->service_id) continue; + } //not our streamtype if (streamtype && (pctx->stream_type!=streamtype)) continue; @@ -1985,7 +2171,7 @@ u32 read = (u32) gf_fread(szLine, 1024, pctx->tmp); if (ctx->dump_log) { szLine1024 = 0; - GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s", szLine)); + GF_LOG(GF_LOG_INFO, inspect_log_tool, ("%s", szLine)); } else { if (gf_fwrite(szLine, read, ctx->dump) != read) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Inspect failed to concatenate trace: %s\n", gf_error_to_string(GF_IO_ERR))); @@ -2001,6 +2187,19 @@ } } +static void inspect_dump_stats(GF_InspectCtx *ctx); + +static u32 next_service_to_dump(GF_InspectCtx *ctx) +{ + u32 i, count = gf_list_count(ctx->src_pids); + for (i=0; i<count; i++) { + PidCtx *pctx = gf_list_get(ctx->src_pids, i); + if (!pctx->tmp) continue; + return pctx->service_id; + } + return 0; +} + static void inspect_finalize(GF_Filter *filter) { Bool concat=GF_FALSE; @@ -2026,14 +2225,19 @@ else if (!ctx->interleave) concat=GF_TRUE; } - - if (!ctx->interleave && ctx->dump) { - finalize_dump(ctx, GF_STREAM_AUDIO, concat); - finalize_dump(ctx, GF_STREAM_VISUAL, concat); - finalize_dump(ctx, GF_STREAM_SCENE, concat); - finalize_dump(ctx, GF_STREAM_OD, concat); - finalize_dump(ctx, GF_STREAM_TEXT, concat); - finalize_dump(ctx, 0, concat); + if (ctx->stats) { + inspect_dump_stats(ctx); + } else if (!ctx->interleave && ctx->dump) { + while (1) { + u32 for_service_id = next_service_to_dump(ctx); + finalize_dump(ctx, GF_STREAM_AUDIO, concat, for_service_id); + finalize_dump(ctx, GF_STREAM_VISUAL, concat, for_service_id); + finalize_dump(ctx, GF_STREAM_SCENE, concat, for_service_id); + finalize_dump(ctx, GF_STREAM_OD, concat, for_service_id); + finalize_dump(ctx, GF_STREAM_TEXT, concat, for_service_id); + finalize_dump(ctx, 0, concat, for_service_id); + if (!for_service_id) break; + } } } @@ -2167,6 +2371,333 @@ } } +static void scte35_parse_splice_time(GF_InspectCtx *ctx, FILE *dump, GF_BitStream *bs) +{ + inspect_printf(dump, " <SpliceTime"); + Bool time_specified_flag = gf_bs_read_int(bs, 1); + if (time_specified_flag == 1) { + /*reserved = */gf_bs_read_int(bs, 6); + u64 pts_time = gf_bs_read_long_int(bs, 33); + DUMP_ATT_LLU("ptsTime", pts_time); + } else { + /*reserved = */gf_bs_read_int(bs, 7); + } + inspect_printf(dump, "/>\n"); +} + +static void scte35_parse_segmentation_descriptor(FILE *dump, GF_BitStream *bs) +{ + inspect_printf(dump, " <SegmentationDescriptor"); + + inspect_printf(dump, " segmentationEventId=\"%u\"", gf_bs_read_u32(bs)); + Bool segmentation_event_cancel_indicator = gf_bs_read_int(bs, 1); + inspect_printf(dump, " segmentationEventCancelIndicator=\"%u\"", segmentation_event_cancel_indicator); + inspect_printf(dump, " segmentationEventIdComplianceIndicator=\"%u\"", gf_bs_read_int(bs, 1)); + gf_bs_read_int(bs, 6); //reserved + if (segmentation_event_cancel_indicator == 0) { + u32 program_segmentation_flag = gf_bs_read_int(bs, 1); + inspect_printf(dump, " programSegmentationFlag=\"%u\"", program_segmentation_flag); + u32 segmentation_duration_flag = gf_bs_read_int(bs, 1); + inspect_printf(dump, " segmentationDurationFlag=\"%u\"", segmentation_duration_flag); + u32 delivery_not_restricted_flag = gf_bs_read_int(bs, 1); + inspect_printf(dump, " deliveryNotRestrictedFlag=\"%u\"", delivery_not_restricted_flag); + if (delivery_not_restricted_flag == 0) { + inspect_printf(dump, " webDeliveryAllowedFlag=\"%u\"", gf_bs_read_int(bs, 1)); + inspect_printf(dump, " noRegionalBlackoutFlag=\"%u\"", gf_bs_read_int(bs, 1)); + inspect_printf(dump, " archiveAllowedFlag=\"%u\"", gf_bs_read_int(bs, 1)); + inspect_printf(dump, " deviceRestrictions=\"%u\"", gf_bs_read_int(bs, 2)); + } else { + gf_bs_read_int(bs, 5); //reserved + } + if (program_segmentation_flag == 0) { + u8 component_count = gf_bs_read_u8(bs); + inspect_printf(dump, " componentCount=\"%u\"", component_count); + for (u8 i=0; i<component_count; i++) { + inspect_printf(dump, " componentTag=\"%u\"", gf_bs_read_u8(bs)); + gf_bs_read_int(bs, 7); //reserved + inspect_printf(dump, " componentTag=\"%u\"", gf_bs_read_u8(bs)); + inspect_printf(dump, " ptsOffset=\""LLU"\"", gf_bs_read_long_int(bs, 33)); + } + } + if (segmentation_duration_flag == 1) { + inspect_printf(dump, " segmentationDuration=\""LLU"\"", gf_bs_read_long_int(bs, 40)); + } + + u8 segmentation_upid_type = gf_bs_read_u8(bs); + u8 segmentation_upid_length = gf_bs_read_u8(bs); + u64 segmentation_upid_pos = gf_bs_get_position(bs); + gf_bs_skip_bytes(bs, segmentation_upid_length); + + u8 segmentation_type_id = gf_bs_read_u8(bs); + inspect_printf(dump, " segmentationTypeId=\"%u\"", segmentation_type_id); + inspect_printf(dump, " segmentNum=\"%u\"", gf_bs_read_u8(bs)); + inspect_printf(dump, " segmentsExpected=\"%u\"", gf_bs_read_u8(bs)); + if (segmentation_type_id == 0x34 || segmentation_type_id == 0x30 || segmentation_type_id == 0x32 + || segmentation_type_id == 0x36 || segmentation_type_id == 0x38 || segmentation_type_id == 0x3A + || segmentation_type_id == 0x44 || segmentation_type_id == 0x46) { + inspect_printf(dump, " subSegmentNum=\"%u\"", gf_bs_read_u8(bs)); + inspect_printf(dump, " subSegmentsExpected=\"%u\"", gf_bs_read_u8(bs)); + } + + inspect_printf(dump, ">\n"); + + if (segmentation_upid_type != 0 && segmentation_upid_length) { + // jump back to SegmentUpid to dump values + u64 pos = gf_bs_get_position(bs); + gf_bs_seek(bs, segmentation_upid_pos); + + inspect_printf(dump, " <SegmentationUpid segmentationUpidType=\"%u\">", segmentation_upid_type); + for (u8 i=0; i<segmentation_upid_length; ++i) + inspect_printf(dump, "%02X", gf_bs_read_u8(bs)); + inspect_printf(dump, "</SegmentationUpid>\n"); + +#if 0 //TODO: identify segmentationUpidType as per the example below (we don't have any sample): +<SegmentationDescriptor +segmentationDuration="2700000" +segmentationEventId="1207959671" +segmentationEventCancelIndicator="0" +segmentationTypeId="48" segmentNum="1" +segmentsExpected="1" +> +<!-- EIDR of Video Service (Cartoon Network) --> +<SegmentationUpid segmentationUpidType="10">14778BE5E3F6000000000000</SegmentationUpid> +<!-- EIDR of Content (TV/Movie) (Lord of the Rings: The Fellowship of the Ring) --> +<SegmentationUpid segmentationUpidType="10">1478E030107BC08ABF93AC79</SegmentationUpid> +<!-- Ad Id (ABCD238Q000H) --> +<SegmentationUpid segmentationUpidType="3">414243443233385130303048</SegmentationUpid> +</SegmentationDescriptor> +#endif + + gf_bs_seek(bs, pos); + } + + inspect_printf(dump, " </SegmentationDescriptor>\n"); + } else { + inspect_printf(dump, "/>\n"); + } +} + +static u8 scte35_parse_splice_descriptor(FILE *dump, GF_BitStream *bs) +{ + if (gf_bs_available(bs) < 2) + return 0; + + u8 splice_descriptor_tag = gf_bs_read_u8(bs); + u8 descriptor_length = gf_bs_read_u8(bs); + if (descriptor_length < 4 || descriptor_length > 254) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Inspect SCTE-35 splice descriptor: invalid descriptor_length=%u\n", descriptor_length)); + return 0; + } + if (gf_bs_available(bs) < descriptor_length) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Inspect not enough bits to parse SCTE-35 splice descriptor\n")); + return 0; + } + + u32 identifier = gf_bs_read_u32(bs); + if (identifier != 0x43554549/*"CUEI"*/) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Inspect unexpected SCTE-35 splice descriptor identifier \"%s\" instead of \"CUEI\". Skipping.\n", gf_4cc_to_str(identifier))); + return 0; + } + + //inspect_printf(dump, " <SpliceDescriptor spliceDescriptorTag=\"%u\" identifier=\"%s\"", splice_descriptor_tag, gf_4cc_to_str(identifier)); + + if (splice_descriptor_tag == 0x02) { + //inspect_printf(dump, ">\n"); + scte35_parse_segmentation_descriptor(dump, bs); + //inspect_printf(dump, " </SpliceDescriptor>\n"); + } else { + //inspect_printf(dump, "/>\n"); + } + + return descriptor_length+2; +} + +static void scte35_dump(GF_InspectCtx *ctx, FILE *dump, GF_BitStream *bs) +{ + inspect_printf(dump, " <SpliceInfoSection xmlns=\"http://www.scte.org/schemas/35\""); + + u8 table_id = gf_bs_read_u8(bs); + Bool section_syntax_indicator = gf_bs_read_int(bs, 1); + Bool private_indicator = gf_bs_read_int(bs, 1); + + u8 sap_type = gf_bs_read_int(bs, 2); + DUMP_ATT_U("sapType", sap_type); + + int section_length = gf_bs_read_int(bs, 12); + + if ((table_id != 0xFC) || (section_syntax_indicator != 0) || (private_indicator != 0) || (section_length + 3 != gf_bs_get_size(bs))) { + DUMP_ATT_STR("error", "invalid section"); + inspect_printf(dump, "/>\n"); + return; + } + + u8 protocol_version = gf_bs_read_u8(bs); + DUMP_ATT_U("protocolVersion", protocol_version); + Bool encrypted_packet = gf_bs_read_int(bs, 1); + //DUMP_ATT_U("encryptedPacket", encrypted_packet); //should be an Element, cf xsd + u8 encryption_algorithm = gf_bs_read_int(bs, 6); + u64 pts_adjustment = gf_bs_read_long_int(bs, 33); + DUMP_ATT_LLU("ptsAdjustment", pts_adjustment); + + if (encrypted_packet) { + const char* enc_algos4 = {"No encryption", "DES - ECB mode", "DES - CBC mode", "Triple DES EDE3 - ECB mode" }; + if (encryption_algorithm < sizeof(enc_algos)/sizeof(enc_algos0)) { + DUMP_ATT_STR("encryptionAlgorithm", enc_algosencryption_algorithm); + } else if (encryption_algorithm < 32) { + DUMP_ATT_STR("encryptionAlgorithm", "Reserved"); + } else { + DUMP_ATT_STR("encryptionAlgorithm", "User private"); + } + //early exit + goto exit; + } + + /*u8 cw_index = */gf_bs_read_u8(bs); + int tier = gf_bs_read_int(bs, 12); + DUMP_ATT_D("tier", tier); + + inspect_printf(dump, ">\n"); + + int splice_command_length = gf_bs_read_int(bs, 12); + u8 splice_command_type = gf_bs_read_u8(bs); + u64 pos = gf_bs_get_position(bs); + + switch(splice_command_type) { + case 0x05: //splice_insert() + { + inspect_printf(dump, " <SpliceInsert"); + u32 splice_event_id = gf_bs_read_u32(bs); + DUMP_ATT_U("spliceEventId", splice_event_id); + Bool splice_event_cancel_indicator = gf_bs_read_int(bs, 1); + DUMP_ATT_BOOL("spliceEventCancelIndicator", splice_event_cancel_indicator); + /*reserved = */gf_bs_read_int(bs, 7); + if (splice_event_cancel_indicator == 0) { + Bool out_of_network_indicator = gf_bs_read_int(bs, 1); + DUMP_ATT_BOOL("outOfNetworkIndicator", out_of_network_indicator); + Bool program_splice_flag = gf_bs_read_int(bs, 1); + DUMP_ATT_BOOL("programSpliceFlag", program_splice_flag); + Bool duration_flag = gf_bs_read_int(bs, 1); + DUMP_ATT_BOOL("durationFlag", duration_flag); + Bool splice_immediate_flag = gf_bs_read_int(bs, 1); + DUMP_ATT_BOOL("spliceImmediateFlag", splice_immediate_flag); + /*reserved = */gf_bs_read_int(bs, 4); + inspect_printf(dump, ">\n"); + + if ((program_splice_flag == 1) && (splice_immediate_flag == 0)) { + scte35_parse_splice_time(ctx, dump, bs); + } + + if (program_splice_flag == 0) { + u8 component_count = gf_bs_read_u8(bs); + DUMP_ATT_U("componentCount", component_count); + for (int i=0; i<component_count; i++) { + inspect_printf(dump, " <Program>\n"); + /*u8 component_tag = */gf_bs_read_u8(bs); + if (splice_immediate_flag == 0) { + scte35_parse_splice_time(ctx, dump, bs); + } + inspect_printf(dump, " </Program>\n"); + } + } + if (duration_flag == GF_TRUE) { + //break_duration() + inspect_printf(dump, " <BreakDuration"); + /*Bool auto_return = */gf_bs_read_int(bs, 1); + /*reserved = */gf_bs_read_int(bs, 6); + u64 duration = gf_bs_read_long_int(bs, 33); + DUMP_ATT_LLU("duration", duration); + inspect_printf(dump, "/>\n"); + } + /*u16 unique_program_id = */gf_bs_read_u16(bs); + /*u8 avail_num = */gf_bs_read_u8(bs); + /*u8 avails_expected = */gf_bs_read_u8(bs); + } else { + inspect_printf(dump, "/>\n"); + } + inspect_printf(dump, " </SpliceInsert>\n"); + + gf_bs_seek(bs, pos + splice_command_length); + } + break; + case 0x06: //time_signal() + inspect_printf(dump, " <TimeSignal>\n"); + scte35_parse_splice_time(ctx, dump, bs); + inspect_printf(dump, " </TimeSignal>\n"); + break; + case 0x00: //splice_null() + inspect_printf(dump, " <Null/>\n"); + DUMP_ATT_STR("splice_command_type", "null"); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("Inspect skip SCTE-35 splice null command\n")); + gf_bs_skip_bytes(bs, splice_command_length); + break; + case 0x04: //splice_schedule() + inspect_printf(dump, " <SpliceSchedule/>\n"); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("Inspect skip SCTE-35 splice schedule command\n")); + gf_bs_skip_bytes(bs, splice_command_length); + break; + case 0x07: //bandwidth_reservation() + inspect_printf(dump, " <BandwidthReservation/>\n"); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("Inspect skip SCTE-35 splice bandwidth reservation command\n")); + gf_bs_skip_bytes(bs, splice_command_length); + break; + case 0xff: //private_command() + inspect_printf(dump, " <PrivateCommand/>\n"); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("Inspect skip SCTE-35 splice private command\n")); + gf_bs_skip_bytes(bs, splice_command_length); + break; + default: + inspect_printf(dump, " <Unknown/>\n"); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Inspect skip unknown SCTE-35 splice command 0x%X\n", splice_command_type)); + gf_bs_skip_bytes(bs, splice_command_length); + break; + } + + gf_assert(gf_bs_get_position(bs) == pos + splice_command_length); + pos += splice_command_length; + + int descriptor_loop_length = gf_bs_read_int(bs, 16); + u32 descriptor_start_pos = (u32) gf_bs_get_position(bs); + while ( (descriptor_start_pos < pos + descriptor_loop_length) ) { + u8 len = scte35_parse_splice_descriptor(dump, bs); + if (len == 0) + break; + descriptor_start_pos += len; + gf_bs_seek(bs, descriptor_start_pos); + } + +exit: + inspect_printf(dump, " </SpliceInfoSection>\n"); +} + +void scte35_dump_xml(FILE *dump, GF_BitStream *bs) +{ + GF_InspectCtx ctx = {0}; + ctx.xml = GF_TRUE; + scte35_dump(&ctx, dump, bs); +} + +static void dump_scte35_info_m2ts_section(GF_InspectCtx *ctx, PidCtx *pctx, FILE *dump, const char *pname, const GF_PropertyValue *att) +{ + if (ctx->xml) { + inspect_printf(dump, " <SCTE35>\n"); + } else { + inspect_printf(dump, " SCTE35"); + } + + if (!pctx->bs) + pctx->bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, att->value.data.ptr, att->value.data.size); + + scte35_dump(ctx, dump, pctx->bs); + + if (ctx->xml) { + inspect_printf(dump, " </SCTE35>\n"); + } else { + inspect_printf(dump, "\n"); + } +} + #ifndef GPAC_DISABLE_AV_PARSERS static void gf_inspect_dump_truehd_frame(FILE *dump, GF_BitStream *bs) { @@ -2213,6 +2744,13 @@ } switch (p4cc) { + case GF_PROP_PCK_TIMECODE: + //dump raw timecode when inspecting the + if (!ctx->analyze && ctx->props && (!ctx->fmt || !strstr(ctx->fmt, "$tmcd$"))) + break; + return; + case GF_PROP_PCK_SEI_LOADED: + case GF_PROP_PID_SEI_LOADED: case GF_PROP_PID_DOWNLOAD_SESSION: case GF_PROP_PID_MUX_INDEX: case GF_PROP_PCK_END_RANGE: @@ -2224,6 +2762,7 @@ case GF_PROP_PID_CENC_HAS_ROLL: case GF_PROP_PID_DSI_SUPERSET: case GF_PROP_PID_PREMUX_STREAM_TYPE: + case GF_PROP_PID_DURATION_AVG: if (gf_sys_is_test_mode()) return; break; @@ -2385,6 +2924,12 @@ inspect_printf(dump, " %s=\"%s\"", pname_no_space, gf_props_dump(p4cc, att, szDump, (GF_PropDumpDataMode) ctx->dump_data)); } gf_free(pname_no_space); + } else if (!p4cc && !strncmp(pname, "scte35", 6)) { + dump_scte35_info_m2ts_section(ctx, pctx, dump, pname, att); + /*} else if (!p4cc && !strncmp(pname, "temi_l", 6)) { + dump_temi_loc(ctx, pctx, dump, pname, att); + } else if (!p4cc && !strncmp(pname, "temi_t", 6)) { + dump_temi_time(ctx, pctx, dump, pname, att);*/ } else { inspect_printf(dump, " %s=\"%s\"", pname ? pname : gf_4cc_to_str(p4cc), gf_props_dump(p4cc, att, szDump, (GF_PropDumpDataMode) ctx->dump_data)); } @@ -2392,12 +2937,18 @@ if (ctx->dtype) { inspect_printf(dump, "\t%s (%s): ", pname ? pname : gf_4cc_to_str(p4cc), gf_props_get_type_name(att->type)); } else { - if (!p4cc && !strncmp(pname, "temi_l", 6)) + if (!p4cc && !strncmp(pname, "scte35", 6)) { + dump_scte35_info_m2ts_section(ctx, pctx, dump, pname, att); + return; + } else if (!p4cc && !strncmp(pname, "temi_l", 6)) { dump_temi_loc(ctx, pctx, dump, pname, att); - else if (!p4cc && !strncmp(pname, "temi_t", 6)) + return; + } else if (!p4cc && !strncmp(pname, "temi_t", 6)) { dump_temi_time(ctx, pctx, dump, pname, att); - else - inspect_printf(dump, "\t%s: ", pname ? pname : gf_4cc_to_str(p4cc)); + return; + } + + inspect_printf(dump, "\t%s: ", pname ? pname : gf_4cc_to_str(p4cc)); } if ((att->type==GF_PROP_UINT_LIST) || (att->type==GF_PROP_4CC_LIST)) { @@ -2416,7 +2967,7 @@ if (k) inspect_printf(dump, ", "); inspect_printf(dump, "%s", (const char *) att->value.string_list.valsk); } - }else{ + } else { inspect_printf(dump, "%s", gf_props_dump(p4cc, att, szDump, (GF_PropDumpDataMode) ctx->dump_data) ); } if ((p4cc==GF_PROP_PID_DURATION) && !gf_sys_is_test_mode()) { @@ -2666,6 +3217,16 @@ else if (!strcmp(key, "fn")) { inspect_printf(dump, "%s", gf_filter_get_name(filter) ); } + else if (!strcmp(key, "tmcd")) { + const GF_PropertyValue *prop = gf_filter_pck_get_property(pck, GF_PROP_PCK_TIMECODE); + if (prop && prop->value.data.size) { + char tcBuf100; + GF_TimeCode *tc = (GF_TimeCode *) prop->value.data.ptr; + inspect_printf(dump, "%s", gf_format_timecode(tc, tcBuf)); + } else { + inspect_printf(dump, "N/A"); + } + } else { const GF_PropertyValue *prop = NULL; u32 prop_4cc = gf_props_get_id(key); @@ -2905,11 +3466,32 @@ inspect_format_tmcd_internal(data, size, pctx->tmcd_flags, pctx->tmcd_rate.num, pctx->tmcd_rate.den, pctx->tmcd_fpt, NULL, pctx->bs, ctx->fftmcd, dump); } +static void inspect_dump_boxes(GF_InspectCtx *ctx, PidCtx *pctx, const u8 *data, u32 size, FILE *dump) +{ + if (ctx->dump) { + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + GF_Err e = GF_OK; + while (gf_bs_available(bs) > 0) { + GF_Box *a = NULL; + e = gf_isom_box_parse(&a, bs); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Inspect Event Track: error while parsing data boxes\n")); + break; //don't parse any further + } + gf_isom_box_dump(a, dump); + data += a->size; + gf_isom_box_del(a); + a=NULL; + } + gf_bs_del(bs); + } +} + static void inspect_dump_vpx(GF_InspectCtx *ctx, FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx, u32 vpversion) { GF_Err e; Bool key_frame = GF_FALSE; - u32 width = 0, height = 0, renderWidth, renderHeight; + u32 width = 0, height = 0, renderWidth=0, renderHeight=0; u32 num_frames_in_superframe = 0, superframe_index_size = 0, i = 0; u32 frame_sizesVP9_MAX_FRAMES_IN_SUPERFRAME; gf_bs_reassign_buffer(pctx->bs, ptr, frame_size); @@ -3046,6 +3628,7 @@ inspect_printf(dump, "\n"); } + static void inspect_dump_packet(GF_InspectCtx *ctx, FILE *dump, GF_FilterPacket *pck, u32 pid_idx, u64 pck_num, PidCtx *pctx) { u32 idx=0, size, sap; @@ -3053,6 +3636,7 @@ u8 dflags = 0; GF_FilterClockType ck_type; GF_FilterFrameInterface *fifce=NULL; + const GF_PropertyValue *p; Bool start, end; u8 *data; @@ -3085,7 +3669,7 @@ if (ts==GF_FILTER_NO_TS) inspect_printf(dump, " PCR=\"N/A\""); else inspect_printf(dump, " PCR=\""LLU"\" ", ts ); if (ck_type!=GF_FILTER_CLOCK_PCR) inspect_printf(dump, " discontinuity=\"true\""); - inspect_printf(dump, "/>"); + inspect_printf(dump, "/>\n"); } else { if (ts==GF_FILTER_NO_TS) inspect_printf(dump, " PCR N/A"); else inspect_printf(dump, " PCR%s "LLU"\n", (ck_type==GF_FILTER_CLOCK_PCR) ? "" : " discontinuity", ts ); @@ -3184,8 +3768,8 @@ DUMP_ATT_X("CRC32", gf_crc_32(data, size) ) } if (ctx->xml) { - if (!ctx->props) goto props_done; - + if (!ctx->props) + goto props_done; } else { inspect_printf(dump, "\n"); } @@ -3198,6 +3782,9 @@ if (!p) break; if (idx==0) inspect_printf(dump, "properties:\n"); + //SCTE35 requires a specific description (e.g. a XML Element): process after Packet is fully described + if (!prop_4cc && !strncmp(prop_name, "scte35", 6)) continue; + inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx); } @@ -3211,6 +3798,18 @@ } inspect_printf(dump, ">\n"); + // special props requiring specific description (e.g. a XML Element) + idx=0; + while (1) { + u32 prop_4cc; + const char *prop_name; + p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (prop_4cc || strncmp(prop_name, "scte35", 6)) continue; + + inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx); + } + #ifndef GPAC_DISABLE_AV_PARSERS if (pctx->hevc_state || pctx->avc_state || pctx->vvc_state) { idx=1; @@ -3227,7 +3826,16 @@ size--; } } - while (size) { + u32 sample_offset=0; + u8 *sai_buffer=NULL; + u32 sai_buffer_size=0; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_SAI); + if (p) { + sai_buffer = p->value.data.ptr; + sai_buffer_size = p->value.data.size; + } + + while (size && pctx->nalu_size_length) { if (size < pctx->nalu_size_length) { inspect_printf(dump, " <!-- NALU is corrupted: nalu_size_length is %u but only %d remains -->\n", pctx->nalu_size_length, size); break; @@ -3240,20 +3848,30 @@ break; } else { inspect_printf(dump, " <NALU size=\"%d\" ", nal_size); - gf_inspect_dump_nalu_internal(dump, data, nal_size, pctx->has_svcc ? 1 : 0, pctx->hevc_state, pctx->avc_state, pctx->vvc_state, pctx->nalu_size_length, ctx->crc, pctx->is_cenc_protected, ctx->analyze, pctx); + gf_inspect_dump_nalu_internal(dump, data, nal_size, pctx->has_svcc ? 1 : 0, pctx->hevc_state, pctx->avc_state, pctx->vvc_state, pctx->nalu_size_length, ctx->crc, pctx->is_cenc_protected, ctx->analyze, pctx, sai_buffer, sai_buffer_size, sample_offset); } idx++; data += nal_size; size -= nal_size + pctx->nalu_size_length; + sample_offset += nal_size + pctx->nalu_size_length; } } else if (pctx->av1_state) { + u32 sample_offset=0; gf_bs_reassign_buffer(pctx->bs, data, size); + u8 *sai_buffer=NULL; + u32 sai_buffer_size=0; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_SAI); + if (p) { + sai_buffer = p->value.data.ptr; + sai_buffer_size = p->value.data.size; + } + while (size) { ObuType obu_type = 0; u64 obu_size = 0; u32 hdr_size = 0; - obu_size = gf_inspect_dump_obu_internal(dump, pctx->av1_state, (char *) data, size, obu_type, obu_size, hdr_size, ctx->crc, pctx, ctx->analyze); + obu_size = gf_inspect_dump_obu_internal(dump, pctx->av1_state, (char *) data, size, obu_type, obu_size, hdr_size, ctx->crc, pctx, ctx->analyze, sai_buffer, sai_buffer_size, sample_offset); if (obu_size > size) { inspect_printf(dump, " <!-- OBU is corrupted: size is %d but only %d remains -->\n", (u32) obu_size, size); @@ -3266,6 +3884,7 @@ data += obu_size; size -= (u32)obu_size; idx++; + sample_offset += obu_size; } } else { u32 hdr, pos, fsize, i; @@ -3301,6 +3920,25 @@ case GF_CODECID_TMCD: inspect_dump_tmcd(ctx, pctx, (char *) data, size, dump); break; + case GF_CODECID_EVTE: + inspect_dump_boxes(ctx, pctx, (char *) data, size, dump); + break; + case GF_CODECID_SCTE35: + { + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + inspect_printf(dump, " <SCTE35>\n"); + scte35_dump(ctx, dump, bs); + inspect_printf(dump, " </SCTE35>\n"); + gf_bs_del(bs); + } + break; + case GF_CODECID_TX3G: + if (size < 2) { + inspect_printf(dump, "<!-- Invalid TX3G -->\n"); + break; + } + data += 2; + size -= 2; case GF_CODECID_SUBS_TEXT: case GF_CODECID_META_TEXT: case GF_CODECID_SIMPLE_TEXT: @@ -3405,7 +4043,7 @@ for (i=0; i<gf_list_count(arr); i++) {\ slc = gf_list_get(arr, i);\ inspect_printf(dump, " <NALU size=\"%d\" ", slc->size);\ - gf_inspect_dump_nalu_internal(dump, slc->data, slc->size, _is_svc, pctx->hevc_state, pctx->avc_state, pctx->vvc_state, nalh_size, ctx->crc, GF_FALSE, ctx->analyze, pctx);\ + gf_inspect_dump_nalu_internal(dump, slc->data, slc->size, _is_svc, pctx->hevc_state, pctx->avc_state, pctx->vvc_state, nalh_size, ctx->crc, GF_FALSE, ctx->analyze, pctx, NULL, 0, 0);\ }\ inspect_printf(dump, " </%sArray>\n", name);\ }\ @@ -3441,7 +4079,6 @@ static void format_duration(s64 dur, u64 timescale, FILE *dump, Bool skip_name) { - u32 h, m, s, ms; const char *name = "duration"; if (dur==-1) { inspect_printf(dump, " duration unknown"); @@ -3455,35 +4092,15 @@ name = "estimated duration"; } - dur = (u64) (( ((Double) (s64) dur)/timescale)*1000); - h = (u32) (dur / 3600000); - m = (u32) (dur/ 60000) - h*60; - s = (u32) (dur/1000) - h*3600 - m*60; - ms = (u32) (dur) - h*3600000 - m*60000 - s*1000; + char szDur100; + gf_format_duration(dur, (u32) timescale, szDur); if (skip_name) inspect_printf(dump, " ("); else inspect_printf(dump, " %s ", name); - if (h<=24) { - if (h) - inspect_printf(dump, "%02d:%02d:%02d.%03d", h, m, s, ms); - else - inspect_printf(dump, "%02d:%02d.%03d", m, s, ms); - } else { - u32 d = (u32) (dur / 3600000 / 24); - h = (u32) (dur/3600000)-24*d; - if (d<=365) { - inspect_printf(dump, "%d Days, %02d:%02d:%02d.%03d", d, h, m, s, ms); - } else { - u32 y=0; - while (d>365) { - y++; - d-=365; - if (y%4) d--; - } - inspect_printf(dump, "%d Years %d Days, %02d:%02d:%02d.%03d", y, d, h, m, s, ms); - } - } + + inspect_printf(dump, "%s", szDur); + if (skip_name) inspect_printf(dump, ")"); } @@ -3492,9 +4109,10 @@ static void inspect_dump_pid_as_info(GF_InspectCtx *ctx, FILE *dump, GF_FilterPid *pid, u32 pid_idx, Bool is_connect, Bool is_remove, u64 pck_for_config, Bool is_info, PidCtx *pctx) { char szCodecRFC6381_CODEC_NAME_SIZE_MAX; - const GF_PropertyValue *p, *dsi, *dsi_enh; + const GF_PropertyValue *p, *dsi, *dsi_enh, *sr; Bool is_raw=GF_FALSE; Bool is_protected=GF_FALSE; + Bool is_unknown=GF_FALSE; u32 codec_id=0; if (!ctx->dump_log && !dump) return; @@ -3505,7 +4123,12 @@ inspect_printf(dump, "PID"); p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); if (!p) p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); - if (p) inspect_printf(dump, " %d", p->value.uint); + if (p) { + if (!gf_sys_is_test_mode()) + inspect_printf(dump, " %u ID %d", pid_idx, p->value.uint); + else + inspect_printf(dump, " %d", p->value.uint); + } if (is_remove) { inspect_printf(dump, " removed\n"); @@ -3520,16 +4143,20 @@ is_protected = GF_TRUE; p = gf_filter_pid_get_property(pid, GF_PROP_PID_ORIG_STREAM_TYPE); } - if (p) + if (p) { inspect_printf(dump, " %s", gf_stream_type_short_name(p->value.uint)); + if (p->value.uint==GF_STREAM_UNKNOWN) is_unknown = GF_TRUE; + } } p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); if (p) { + GF_PropertyEntry *pe=NULL; inspect_printf(dump, " service %d", p->value.uint); - p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_NAME); - if (p) inspect_printf(dump, " \"%s\"", p->value.string); - p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_PROVIDER); - if (p) inspect_printf(dump, " (%s)", p->value.string); + p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_NAME, &pe); + if (p && p->value.string && p->value.string0) inspect_printf(dump, " \"%s\"", p->value.string); + p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_PROVIDER, &pe); + if (p && p->value.string && p->value.string0) inspect_printf(dump, " (%s)", p->value.string); + gf_filter_release_property(pe); } p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLED); @@ -3545,10 +4172,17 @@ if (p && stricmp(p->value.string, "und")) inspect_printf(dump, " language \"%s\"", p->value.string); p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); - if (p) format_duration((s64) p->value.lfrac.num, (u32) p->value.lfrac.den, dump, GF_FALSE); + if (p) { + sr = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION_AVG); + if (sr && sr->value.boolean) inspect_printf(dump, " estimated"); + format_duration((s64) p->value.lfrac.num, (u32) p->value.lfrac.den, dump, GF_FALSE); + } + sr = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); - if (p) inspect_printf(dump, " timescale %d", p->value.uint); + if (p && (!sr || (sr->value.uint != p->value.uint))) + inspect_printf(dump, " timescale %d", p->value.uint); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); if (p) inspect_printf(dump, " delay "LLD, p->value.longsint); @@ -3578,7 +4212,7 @@ } p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p && p->value.frac.num!=p->value.frac.den) + if (p && (p->value.frac.num>0) && p->value.frac.num!=p->value.frac.den) inspect_printf(dump, " SAR %d/%u", p->value.frac.num, p->value.frac.den); else inspect_printf(dump, " SAR 1/1"); @@ -3587,15 +4221,15 @@ if (p) inspect_printf(dump, " raw format %s", gf_pixel_fmt_name(p->value.uint) ); } } - p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); - if (p) { - inspect_printf(dump, " %d Hz", p->value.uint); + + if (sr) { + inspect_printf(dump, " %d Hz", sr->value.uint); p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); if (p) { inspect_printf(dump, " %s", gf_audio_fmt_get_layout_name(p->value.longuint)); } else { p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); - if (p) inspect_printf(dump, " %d channels", p->value.uint); + if (p) inspect_printf(dump, " %d chan", p->value.uint); } if (is_raw) { p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); @@ -3620,6 +4254,13 @@ inspect_printf(dump, " %d/%d pattern", p->value.frac.num, p->value.frac.den); } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ROLE); + if (p && p->value.string_list.nb_items) { + char *urn_sep = strrchr(p->value.string_list.vals0, ':'); + if (!urn_sep) urn_sep = p->value.string_list.vals0; + inspect_printf(dump, " role %s", urn_sep); + } + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); dsi_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); if (is_raw) { @@ -3627,9 +4268,17 @@ return; } - inspect_printf(dump, " codec"); - if (szCodec0 && strcmp(szCodec, "unkn")) - inspect_printf(dump, " %s", szCodec); + if (codec_id) { + inspect_printf(dump, " codec"); + if (szCodec0 && strcmp(szCodec, "unkn")) { + inspect_printf(dump, " %s", szCodec); + } else { + if ((codec_id & 0xFFFFFF00) == GF_4CC('M','2','T', 0)) { + inspect_printf(dump, " m2ts_type 0x%02X", (codec_id&0xFF)); + codec_id = 0; + } + } + } #ifndef GPAC_DISABLE_AV_PARSERS if ((codec_id==GF_CODECID_HEVC) || (codec_id==GF_CODECID_LHVC) || (codec_id==GF_CODECID_HEVC_TILES)) { @@ -3637,7 +4286,7 @@ HEVCState *hvcs = NULL; GF_HEVCConfig *hvcc=NULL; if (dsi) { - hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (!dsi_enh && (codec_id==GF_CODECID_LHVC)) ? GF_TRUE : GF_FALSE); if (dsi_enh) { GF_SAFEALLOC(hvcs, HEVCState); for (i=0; i<gf_list_count(hvcc->param_array); i++) { @@ -3753,6 +4402,9 @@ gf_odf_av1_cfg_del(av1c); } } + else if (codec_id==GF_CODECID_AVS3_VIDEO) { + inspect_printf(dump, " AVS3"); + } else if ((codec_id==GF_CODECID_AAC_MPEG4) || (codec_id==GF_CODECID_AAC_MPEG2_MP) || (codec_id==GF_CODECID_AAC_MPEG2_LCP) || (codec_id==GF_CODECID_AAC_MPEG2_SSRP)) { if (dsi) { const char *name, *sep; @@ -3799,9 +4451,9 @@ } else { p = gf_filter_pid_get_property(pid, GF_PROP_PID_META_DEMUX_CODEC_ID); if (p && (p->type==GF_PROP_UINT)) codec_id = p->value.uint; - inspect_printf(dump, " FFMPEG %d", codec_id); + inspect_printf(dump, " FFmpeg %d", codec_id); } - } else { + } else if (codec_id) { inspect_printf(dump, " %s", gf_codecid_name(codec_id)); } p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROFILE_LEVEL); @@ -3843,11 +4495,13 @@ if (dsi && (codec_id==GF_CODECID_EAC3)) { GF_AC3Config ac3cfg; - gf_odf_ac3_config_parse(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE, &ac3cfg); + gf_odf_ac3_cfg_parse(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE, &ac3cfg); if (ac3cfg.atmos_ec3_ext) inspect_printf(dump, " Atmos (CIT %d)", ac3cfg.complexity_index_type); } + if (pctx->is_fake) + inspect_printf(dump, (is_unknown || is_protected) ? " ignored" : " fake"); inspect_printf(dump, "\n"); } @@ -3868,6 +4522,17 @@ if (ctx->test==INSPECT_TEST_NOPROP) return; if (!ctx->dump_log && !dump) return; + if (!pid) return; + + if (ctx->stats) { + char szCodecRFC6381_CODEC_NAME_SIZE_MAX; + if (gf_filter_pid_get_rfc_6381_codec_string(pid, szCodec, GF_FALSE, GF_FALSE, NULL, NULL)==GF_OK) { + if (!pctx->stat_codecs || !strstr(pctx->stat_codecs, szCodec)) + gf_dynstrcat(&pctx->stat_codecs, szCodec, ","); + } + pctx->nb_recfg++; + return; + } if (!ctx->full) { inspect_dump_pid_as_info(ctx, dump, pid, pid_idx, is_connect, is_remove, pck_for_config, is_info, pctx); @@ -3971,10 +4636,6 @@ if (!dsi) is_enh = GF_TRUE; case GF_CODECID_AVC: case GF_CODECID_AVC_PS: - if (!dsi && !dsi_enh) { - inspect_printf(dump, "/>\n"); - return; - } #ifndef GPAC_DISABLE_AV_PARSERS inspect_reset_parsers(pctx, &pctx->avc_state); @@ -3983,6 +4644,10 @@ if (!pctx->avc_state) return; } #endif + if (!dsi && !dsi_enh) { + inspect_printf(dump, "/>\n"); + return; + } inspect_printf(dump, ">\n"); inspect_printf(dump, "<AVCParameterSets>\n"); if (dsi) { @@ -4021,11 +4686,6 @@ is_enh = GF_TRUE; case GF_CODECID_HEVC: case GF_CODECID_HEVC_TILES: - if (!dsi && !dsi_enh) { - inspect_printf(dump, "/>\n"); - return; - } - #ifndef GPAC_DISABLE_AV_PARSERS inspect_reset_parsers(pctx, &pctx->hevc_state); @@ -4035,6 +4695,12 @@ } #endif + if (!dsi && !dsi_enh) { + inspect_printf(dump, "/>\n"); + return; + } + + if (dsi) { if (is_enh && !dsi_enh) { lhcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE); @@ -4090,11 +4756,6 @@ case GF_CODECID_VVC: case GF_CODECID_VVC_SUBPIC: - if (!dsi) { - inspect_printf(dump, "/>\n"); - return; - } - #ifndef GPAC_DISABLE_AV_PARSERS inspect_reset_parsers(pctx, &pctx->vvc_state); @@ -4103,6 +4764,10 @@ if (!pctx->vvc_state) return; } #endif + if (!dsi) { + inspect_printf(dump, "/>\n"); + return; + } vvcC = gf_odf_vvc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); if (vvcC) @@ -4156,20 +4821,22 @@ inspect_printf(dump, ">\n"); inspect_printf(dump, " <OBUConfig>\n"); - idx = 1; - for (i=0; i<gf_list_count(pctx->av1_state->config->obu_array); i++) { - ObuType obu_type=0; - u64 obu_size = 0; - u32 hdr_size = 0; - GF_AV1_OBUArrayEntry *obu = gf_list_get(pctx->av1_state->config->obu_array, i); + if (pctx && pctx->av1_state && pctx->av1_state->config) { + idx = 1; + for (i=0; i<gf_list_count(pctx->av1_state->config->obu_array); i++) { + ObuType obu_type=0; + u64 obu_size = 0; + u32 hdr_size = 0; + GF_AV1_OBUArrayEntry *obu = gf_list_get(pctx->av1_state->config->obu_array, i); - if (!pctx->bs) - pctx->bs = gf_bs_new((const u8 *) obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); - else - gf_bs_reassign_buffer(pctx->bs, (const u8 *)obu->obu, (u32) obu->obu_length); + if (!pctx->bs) + pctx->bs = gf_bs_new((const u8 *) obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, (const u8 *)obu->obu, (u32) obu->obu_length); - gf_inspect_dump_obu_internal(dump, pctx->av1_state, (char*)obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, ctx->crc, pctx, ctx->analyze); - idx++; + gf_inspect_dump_obu_internal(dump, pctx->av1_state, (char*)obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, ctx->crc, pctx, ctx->analyze, NULL, 0, 0); + idx++; + } } #endif inspect_printf(dump, " </OBUConfig>\n"); @@ -4205,6 +4872,8 @@ case GF_CODECID_MPEG2_PART3: case GF_CODECID_MPEG_AUDIO_L1: case GF_CODECID_TMCD: + case GF_CODECID_EVTE: + case GF_CODECID_SCTE35: inspect_printf(dump, "/>\n"); return; case GF_CODECID_SUBS_XML: @@ -4220,6 +4889,24 @@ } inspect_printf(dump, "\n </XMLTextConfig>\n"); break; + case GF_CODECID_TX3G: { + GF_TextSampleDescriptor *tx3g = NULL; + if (dsi) tx3g = gf_odf_tx3g_read(dsi->value.data.ptr, dsi->value.data.size); + if (!tx3g) { + inspect_printf(dump, "/>\n"); + return; + } + inspect_printf(dump, ">\n"); + inspect_printf(dump, " <TextConfig displayFlags=\"%d\" horizontalJustification=\"%d\" verticalJustification=\"%d\" backgroundColor=\"%d\">\n", tx3g->displayFlags, tx3g->horiz_justif, tx3g->vert_justif, tx3g->back_color); + inspect_printf(dump, " <BoxRecord top=\"%d\" left=\"%d\" bottom=\"%d\" right=\"%d\"/>\n", tx3g->default_pos.top, tx3g->default_pos.left, tx3g->default_pos.bottom, tx3g->default_pos.right); + inspect_printf(dump, " <StyleRecord startCharOffset=\"%d\" endCharOffset=\"%d\" fontID=\"%d\" styleFlags=\"%d\" fontSize=\"%d\" textColor=\"%d\"/>\n", tx3g->default_style.startCharOffset, tx3g->default_style.endCharOffset, tx3g->default_style.fontID, tx3g->default_style.style_flags, tx3g->default_style.font_size, tx3g->default_style.text_color); + for (i=0; i<tx3g->font_count; i++) { + inspect_printf(dump, " <FontRecord fontID=\"%d\" name=\"%s\"/>\n", tx3g->fontsi.fontID, tx3g->fontsi.fontName); + } + inspect_printf(dump, " </TextConfig>\n"); + gf_odf_desc_del((GF_Descriptor *)tx3g); + break; + } case GF_CODECID_WEBVTT: dsi_is_text = GF_TRUE; case GF_CODECID_SUBS_TEXT: @@ -4464,6 +5151,147 @@ inspect_printf(dump, "</%s>\n", elt_name); } +static void inspect_stats_packet(GF_InspectCtx *ctx, PidCtx *pctx, GF_FilterPacket *pck) +{ + GF_FilterSAPType sap = gf_filter_pck_get_sap(pck); + u64 dts, cts; + dts = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + if (dts==GF_FILTER_NO_TS) dts = cts; + + u32 dur = gf_filter_pck_get_duration(pck); + pctx->total_dur += dur; + if (pctx->pck_num==1) { + pctx->start_with_sap = sap; + pctx->timescale = gf_filter_pck_get_timescale(pck); + pctx->constant_dur = dur; + pctx->first_dts = dts; + pctx->first_cts = cts; + } else if (pctx->constant_dur != dur) { + pctx->constant_dur = 0; + } + + if (dur>pctx->max_dur) pctx->max_dur = dur; + u32 size; + gf_filter_pck_get_data(pck, &size); + switch (sap) { + case GF_FILTER_SAP_1: pctx->nb_sap1++; break; + case GF_FILTER_SAP_2: pctx->nb_sap2++; break; + case GF_FILTER_SAP_3: pctx->nb_sap3++; break; + case GF_FILTER_SAP_4: + case GF_FILTER_SAP_4_PROL: + pctx->nb_sap4++; + break; + default: break; + } + pctx->nb_bytes += size; + s64 ts_diff = (s64) cts - (s64) dts; + if (pctx->max_ctso < ts_diff) pctx->max_ctso = ts_diff; + if (pctx->min_ctso > ts_diff) pctx->min_ctso = ts_diff; + + if (gf_filter_pck_get_corrupted(pck)) + pctx->has_corr = GF_TRUE; + if (gf_filter_pck_get_clock_type(pck)==GF_FILTER_CLOCK_PCR_DISC) + pctx->has_disc = GF_TRUE; + if (gf_filter_pck_get_crypt_flags(pck) & GF_FILTER_PCK_CRYPT) + pctx->has_crypted |= 1<<2; + else + pctx->has_crypted |= 1<<1; + + if (dts!=GF_FILTER_NO_TS) { + if (!pctx->prev_dts) { + pctx->prev_dts = dts; + } else if (dts - pctx->prev_dts > pctx->timescale) { + u64 rate = pctx->bytes_in_wnd*8; + rate *= pctx->timescale ; + rate /= dts - pctx->prev_dts; + if (pctx->max_rate < (u32) rate) + pctx->max_rate = (u32) rate; + pctx->prev_dts = dts; + pctx->bytes_in_wnd = 0; + } + pctx->bytes_in_wnd += size; + } + + if (!sap || (sap>GF_FILTER_SAP_3)) { + pctx->avg_nosap_size += size; + pctx->nb_nosaps ++; + return; + } + if (!pctx->last_sap_cts) { + pctx->last_sap_cts = cts; + } else { + u32 sap_diff = (u32) (cts - pctx->last_sap_cts); + if (!pctx->min_sap_diff || (sap_diff<pctx->min_sap_diff)) pctx->min_sap_diff = sap_diff; + if (!pctx->max_sap_diff || (sap_diff>pctx->max_sap_diff)) pctx->max_sap_diff = sap_diff; + pctx->avg_sap_diff += sap_diff; + pctx->last_sap_cts = cts; + } + pctx->avg_sap_size += size; + if (size>pctx->max_sap_size) pctx->max_sap_size = size; +} + +static void inspect_dump_stats(GF_InspectCtx *ctx) +{ + u32 i, count = gf_list_count(ctx->src_pids); + ctx->stats=0; + for (i=0; i<count; i++) { + PidCtx *pctx = gf_list_get(ctx->src_pids, i); + //dump as usual + inspect_dump_pid(ctx, ctx->dump, pctx->src_pid, i+1, GF_TRUE, GF_FALSE, 0, GF_FALSE, pctx); + + inspect_printf(ctx->dump, "\tNb Frames: %u\n", pctx->pck_num); + if (!pctx->constant_dur) + inspect_printf(ctx->dump, "\tFrame Rate: VFR - frame duration avg %d max %d", pctx->total_dur/pctx->pck_num, pctx->max_dur); + else + inspect_printf(ctx->dump, "\tFrame Rate: CFR - frame duration %d", pctx->constant_dur); + inspect_printf(ctx->dump, "\n"); + inspect_printf(ctx->dump, "\tCumulated Duration: "LLU, pctx->total_dur); + format_duration(pctx->total_dur-pctx->first_cts, pctx->timescale, ctx->dump, GF_TRUE); + inspect_printf(ctx->dump, "\n"); + if ((pctx->first_dts==GF_FILTER_NO_TS) && (pctx->first_cts==GF_FILTER_NO_TS)) + inspect_printf(ctx->dump, "\tFirst packet: No initial timestamps\n"); + else + inspect_printf(ctx->dump, "\tFirst packet: DTS "LLU" CTS "LLU"\n", pctx->first_dts, pctx->first_cts); + if (pctx->max_ctso || pctx->min_ctso) { + inspect_printf(ctx->dump, "\tCTS offset: min %d max %d\n", pctx->min_ctso, pctx->max_ctso); + } + + u32 nb_saps = pctx->nb_sap1+pctx->nb_sap2+pctx->nb_sap3; + inspect_printf(ctx->dump, "\tSAP frames: %u - types", nb_saps); + if (pctx->nb_nosaps) inspect_printf(ctx->dump, " 0(%u)", pctx->nb_nosaps); + if (pctx->nb_sap1) inspect_printf(ctx->dump, " 1(%u)", pctx->nb_sap1); + if (pctx->nb_sap2) inspect_printf(ctx->dump, " 2(%u)", pctx->nb_sap2); + if (pctx->nb_sap3) inspect_printf(ctx->dump, " 3(%u)", pctx->nb_sap3); + if (pctx->nb_sap4) inspect_printf(ctx->dump, " 4(%u)", pctx->nb_sap4); + inspect_printf(ctx->dump, "\n"); + inspect_printf(ctx->dump, "\tStart with SAP: %d\n", pctx->start_with_sap); + + if (pctx->nb_nosaps) + inspect_printf(ctx->dump, "\tAverage non-SAP frame size: %d bytes\n", pctx->avg_nosap_size / pctx->nb_nosaps); + if (nb_saps) { + inspect_printf(ctx->dump, "\tAverage SAP frame size: %d bytes\n", pctx->avg_sap_size / nb_saps); + inspect_printf(ctx->dump, "\tMax SAP frame size: %d bytes\n", pctx->max_sap_size); + if (nb_saps>2) + inspect_printf(ctx->dump, "\tSAP time diff: min %u max %u avg %u\n", pctx->min_sap_diff, pctx->max_sap_diff, pctx->avg_sap_diff/(nb_saps-2)); + } + + + inspect_printf(ctx->dump, "\tTotal size: "LLU" bytes\n", pctx->nb_bytes); + if (!pctx->total_dur) continue; + u64 br = pctx->nb_bytes; + br *= 8*pctx->timescale; + br /= pctx->total_dur; + if (br>10000000) + inspect_printf(ctx->dump, "\tBitrate: avg "LLU" max "LLU" mbps\n", br/1000000, pctx->max_rate/1000000); + else if (br>10000) + inspect_printf(ctx->dump, "\tBitrate: avg "LLU" max "LLU" kbps\n", br/1000, pctx->max_rate/1000); + else + inspect_printf(ctx->dump, "\tBitrate: avg "LLU" max "LLU" bps\n", br, pctx->max_rate); + } + ctx->stats=1; +} + static GF_Err inspect_process(GF_Filter *filter) { u32 i, count, nb_done=0, nb_hdr_done=0; @@ -4499,11 +5327,16 @@ GF_FilterPacket *pck = NULL; pck = pctx->src_pid ? gf_filter_pid_get_packet(pctx->src_pid) : NULL; - if (pctx->init_pid_config_done) + if (pctx->init_pid_config_done) { + nb_hdr_done++; + } else if (!ctx->deep && !ctx->allp && !ctx->fmt + && (gf_sys_clock() - ctx->last_config_time >= ctx->timeout+ctx->buffer) + ) { nb_hdr_done++; + } if (!pck) { - if (pctx->src_pid && !gf_filter_pid_is_eos(pctx->src_pid)) + if (!pctx->is_fake && pctx->src_pid && !gf_filter_pid_is_eos(pctx->src_pid)) continue; else ctx->has_seen_eos = GF_TRUE; @@ -4556,8 +5389,9 @@ pctx->pck_for_config++; pctx->pck_num++; - if (ctx->dump_pck) { - + if (ctx->stats) { + inspect_stats_packet(ctx, pctx, pck); + } else if (ctx->dump_pck) { if (ctx->is_prober) { nb_done++; } else { @@ -4607,7 +5441,7 @@ return GF_OK; } -static GF_Err inspect_config_input(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +static GF_Err inspect_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { GF_FilterEvent evt; PidCtx *pctx; @@ -4616,6 +5450,7 @@ GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); if (!ctx->src_pids) ctx->src_pids = gf_list_new(); + ctx->last_config_time = gf_sys_clock(); pctx = gf_filter_pid_get_udta(pid); if (pctx) { @@ -4640,6 +5475,10 @@ pctx->stream_type = p ? p->value.uint : 0; p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); pctx->codec_id = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); + pctx->service_id = p ? p->value.uint : 0; + if (gf_sys_is_test_mode()) + pctx->service_id = 0; if (!ctx->buffer) { pctx->buffer_done = GF_TRUE; @@ -4659,7 +5498,30 @@ pctx->buf_start_time = gf_sys_clock(); } - + //Load SEIs for: + //- tmcd dump + //- props with no analyze + if ((ctx->fmt && strstr(ctx->fmt, "$tmcd$")) + || (!ctx->analyze && ctx->props && !gf_sys_is_test_mode() ) + ) { + switch (pctx->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + case GF_CODECID_VVC: + case GF_CODECID_AV1: + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SEI_LOADED); + if (!p) { + //if unframed and our inspect mode is not framed, do not require SEI_LOAD (we don't want a reframer to be inserted) + p = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED); + if (!p || (ctx->mode==INSPECT_MODE_REFRAME)) { + gf_filter_pid_negotiate_property(pid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); + } + } + } + } w = h = sr = ch = 0; p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); @@ -4679,7 +5541,12 @@ Bool insert = GF_FALSE; PidCtx *actx = gf_list_get(ctx->src_pids, i); - if (pctx->codec_id < actx->codec_id) { + if (pctx->service_id && actx->service_id) { + if (pctx->service_id == actx->service_id) + insert = GF_TRUE; + else + continue; + } else if (pctx->codec_id < actx->codec_id) { insert = GF_TRUE; } //same codec ID, sort by increasing width/height/samplerate/channels @@ -4754,6 +5621,9 @@ if (ctx->pcr) gf_filter_pid_set_clock_mode(pid, GF_TRUE); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FAKE); + pctx->is_fake = (p && p->value.boolean) ? GF_TRUE : GF_FALSE; + if (!ctx->deep) gf_filter_post_process_task(filter); return GF_OK; @@ -4765,6 +5635,7 @@ CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), {0}, + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE) }; static const GF_FilterCapability InspecterReframeCaps = @@ -4774,6 +5645,17 @@ CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), {0}, + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE) +}; + +static const GF_FilterCapability InspecterRawCaps = +{ + //accept any stream but files, framed + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "*"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "*"), + {0}, + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE) }; static GF_Err inspect_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) @@ -4788,8 +5670,18 @@ const char *name = gf_filter_get_name(filter); GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); - if (ctx->log && !strcmp(ctx->log, "GLOG")) - ctx->dump_log = GF_TRUE; + if (ctx->log) { + if (!strcmp(ctx->log, "GLOG")) { + ctx->dump_log = GF_TRUE; + inspect_log_tool = GF_LOG_APP; + } else { + u32 ltool = gf_log_parse_tool(ctx->log); + if (ltool!=GF_LOG_TOOL_UNDEFINED) { + ctx->dump_log = GF_TRUE; + inspect_log_tool = ltool; + } + } + } if (name && !strcmp(name, "probe") ) { ctx->is_prober = GF_TRUE; @@ -4805,20 +5697,32 @@ if (!ctx->log) return GF_BAD_PARAM; + if (ctx->stats) { + ctx->analyze = GF_FALSE; + ctx->allp = GF_TRUE; + ctx->deep = GF_FALSE; + } if (ctx->analyze) { ctx->xml = GF_TRUE; + ctx->mode = INSPECT_MODE_REFRAME; } if (ctx->xml || ctx->analyze || gf_sys_is_test_mode() || ctx->fmt) { ctx->full = GF_TRUE; } - if (!ctx->full) { + if (!ctx->full && (ctx->mode!=INSPECT_MODE_RAW)) { + ctx->mode = INSPECT_MODE_REFRAME; + } + + //force reframer if working with "tmcd" + if (ctx->fmt && strstr(ctx->fmt, "$tmcd$") && (ctx->mode!=INSPECT_MODE_RAW)) { ctx->mode = INSPECT_MODE_REFRAME; } switch (ctx->mode) { case INSPECT_MODE_RAW: + gf_filter_override_caps(filter, InspecterRawCaps, sizeof(InspecterRawCaps)/sizeof(GF_FilterCapability) ); break; case INSPECT_MODE_REFRAME: gf_filter_override_caps(filter, InspecterReframeCaps, sizeof(InspecterReframeCaps)/sizeof(GF_FilterCapability) ); @@ -4873,14 +5777,15 @@ "- _any: target file path and name\n" "- stderr: dump to stderr\n" "- stdout: dump to stdout\n" - "- GLOG: use GPAC logs `app@info\n" + "- GLOG: use GPAC logs `app@info`\n" + "- TL: use GPAC log tool `TL` at level `info`\n" "- null: silent mode", GF_PROP_STRING, #ifdef GPAC_CONFIG_ANDROID "GLOG" #else "stdout" #endif - , "_any|stderr|stdout|GLOG|null", 0}, + , "_any|stderr|stdout|GLOG|TL|null", 0}, { OFFS(mode), "dump mode\n" "- pck: dump full packet\n" "- blk: dump packets before reconstruction\n" @@ -4911,6 +5816,8 @@ { OFFS(buffer), "set playback buffer in ms", GF_PROP_UINT, "0", NULL, GF_ARG_HINT_EXPERT}, { OFFS(mbuffer), "set max buffer occupancy in ms. If less than buffer, use buffer", GF_PROP_UINT, "0", NULL, 0}, { OFFS(rbuffer), "rebuffer trigger in ms. If 0 or more than buffer, disable rebuffering", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(stats), "compute statistics for PIDs", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(timeout), "timeout in ms when doing simple inspection in case no packets are received on some PIDs", GF_PROP_UINT, "5000", NULL, GF_ARG_HINT_EXPERT}, { OFFS(test), "skip predefined set of properties, used for test mode\n" "- no: no properties skipped\n" @@ -4929,15 +5836,17 @@ { CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + {0}, + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE) }; const GF_FilterRegister InspectRegister = { .name = "inspect", - GF_FS_SET_DESCRIPTION("Inspect packets") + GF_FS_SET_DESCRIPTION("Packet inspector") GF_FS_SET_HELP("The inspect filter can be used to dump PID and packets. It may also be used to check parts of payload of the packets.\n" "\n" "The default options inspect only PID changes.\n" - "If -full() is not set, -mode=frame() is forced and PID properties are formatted in human-readable form, one PID per line.\n" + "If -full() is not set, -mode() is forced to `frame` and PID properties are formatted in human-readable form, one PID per line.\n" "Otherwise, all properties are dumped.\n" "Note: specifying -xml(), -analyze(), -fmt() or using `-for-test` will force -full() to true.\n" "\n" @@ -4951,6 +5860,7 @@ "- cts: composition time stamp in stream timescale, N/A if not available\n" "- dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available\n" "- ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available\n" + "- tmcd: timecode as provided in SEI, N/A if not available (requires reframer)\n" "- dur: duration in stream timescale\n" "- frame: framing status\n" " - interface: complete AU, interface object (no size info). Typically a GL texture\n" @@ -5015,8 +5925,9 @@ .finalize = inspect_finalize, .process = inspect_process, .process_event = inspect_process_event, - .configure_pid = inspect_config_input, + .configure_pid = inspect_configure_pid, .update_arg = inspect_update_arg, + .hint_class_type = GF_FS_CLASS_TOOL }; static const GF_FilterCapability ProberCaps = @@ -5058,7 +5969,7 @@ const GF_FilterRegister ProbeRegister = { .name = "probe", - GF_FS_SET_DESCRIPTION("Probe source") + GF_FS_SET_DESCRIPTION("Source prober") GF_FS_SET_HELP("The Probe filter is used by applications (typically `MP4Box`) to query demultiplexed PIDs (audio, video, ...) available in a source chain.\n\n" "The filter outputs the number of input PIDs in the file specified by -log().\n" "It is up to the app developer to query input PIDs of the prober and take appropriated decisions.") @@ -5070,7 +5981,8 @@ SETCAPS(ProberCaps), .finalize = inspect_finalize, .process = inspect_process, - .configure_pid = inspect_config_input, + .configure_pid = inspect_configure_pid, + .hint_class_type = GF_FS_CLASS_TOOL, }; const GF_FilterRegister *inspect_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/io_fcryp.c -> gpac-26.02.0.tar.gz/src/filters/io_fcryp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2021-2023 + * Copyright (c) Telecom ParisTech 2021-2024 * All rights reserved * * This file is part of GPAC / file crypt/decrypt for full segment encryption filter @@ -117,6 +117,10 @@ //forward failure, but do not send a setup failure (gf_filter_setup_failure) which would remove this filter //we let the final user (dashdmx) decide what to do gf_filter_notification_failure(f, err, GF_FALSE); + //we however abort the filter + GF_CryptFileCtx *ctx = (GF_CryptFileCtx *) gf_filter_get_udta(f); + ctx->in_error = err; + gf_filter_abort(f); return GF_FALSE; } @@ -244,9 +248,14 @@ //use a threaded session if (!ctx->key_sess) { GF_DownloadManager *dm = gf_filter_get_download_manager(filter); + if (!dm) { + ctx->in_error = GF_NOT_SUPPORTED; + ctx->reload_key_state = KEY_STATE_NONE; + return ctx->in_error; + } ctx->key_sess = gf_dm_sess_new(dm, ctx->key_url, GF_NETIO_SESSION_NOT_CACHED, cryptfin_net_io, ctx, &e); } else { - e = gf_dm_sess_setup_from_url(ctx->key_sess, ctx->key_url, GF_TRUE); + e = gf_dm_sess_setup_from_url(ctx->key_sess, ctx->key_url, GF_FALSE); } if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("CryptFile Failed to setup download session for key %s: %s\n", ctx->key_url, gf_error_to_string(e))) @@ -374,7 +383,8 @@ .finalize = cryptfile_finalize, .configure_pid = cryptfile_configure_pid, .process = cryptfin_process, - .probe_url = cryptfile_probe_url + .probe_url = cryptfile_probe_url, + .hint_class_type = GF_FS_CLASS_CRYPTO }; @@ -650,7 +660,8 @@ .finalize = cryptfile_finalize, .configure_pid = cryptfile_configure_pid, .process = cryptfout_process, - .probe_url = cryptfile_probe_url + .probe_url = cryptfile_probe_url, + .hint_class_type = GF_FS_CLASS_CRYPTO };
View file
gpac-2.4.0.tar.gz/src/filters/isoffin.h -> gpac-26.02.0.tar.gz/src/filters/isoffin.h
Changed
@@ -41,38 +41,45 @@ //#define DASH_USE_PULL -enum -{ +GF_OPT_ENUM (ISOMReaderScalableTileLoadMode, MP4DMX_SPLIT=0, MP4DMX_SPLIT_EXTRACTORS, MP4DMX_SINGLE, -}; +); -enum -{ +GF_OPT_ENUM (ISOMReaderParamSetsExtractMode, MP4DMX_XPS_AUTO=0, MP4DMX_XPS_KEEP, MP4DMX_XPS_REMOVE, -}; +); + +GF_OPT_ENUM (ISOMReaderEditListMode, + EDITS_AUTO=0, + EDITS_NO, + EDITS_STRICT, +); typedef struct { //options char *src, *initseg; Bool allt, itt, itemid; - u32 smode, edits; + ISOMReaderScalableTileLoadMode smode; + ISOMReaderEditListMode edits; u32 stsd; Bool expart; - Bool alltk; + Bool alltk, keepc; u32 frame_size; char* tkid; - Bool analyze; - u32 xps_check; + u32 analyze; + Bool norw; + ISOMReaderParamSetsExtractMode xps_check; char *catseg; Bool sigfrag; Bool nocrypt, strtxt, lightp; u32 nodata; u32 mstore_purge, mstore_samples, mstore_size; + s32 ctso; //internal @@ -91,6 +98,7 @@ //fragmented file to be refreshed before processing it Bool refresh_fragmented; Bool input_is_stop; + Bool was_aborted; u64 missing_bytes, last_size; Bool seg_name_changed; @@ -109,6 +117,7 @@ u32 has_pending_segments, nb_force_flush; Bool disconnected; + Bool in_is_eos; Bool no_order_check; u32 moov_not_loaded; Bool invalid_segment; @@ -129,6 +138,11 @@ u64 last_min_offset; GF_Err in_error; Bool force_fetch; + + u32 extkid, orig_id; + GF_ISOFile *extkmov; + u32 extk_flags; + Bool extk; } ISOMReader; typedef struct @@ -147,10 +161,11 @@ GF_ISOSample *sample; u64 sample_data_offset, last_valid_sample_data_offset; GF_Err last_state; - Bool sap_3; + Bool sap_3, switch_frame; GF_ISOSampleRollType sap_4_type; s32 roll; u32 xps_mask; + u32 cts_offset; u32 sample_num, sample_last; s64 ts_offset;
View file
gpac-2.4.0.tar.gz/src/filters/isoffin_load.c -> gpac-26.02.0.tar.gz/src/filters/isoffin_load.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISOBMFF reader filter @@ -139,25 +139,691 @@ } } -static void isor_declare_track(ISOMReader *read, ISOMChannel *ch, u32 track, u32 stsd_idx, u32 streamtype, Bool use_iod) +#if 0 +static u32 isor_get_udta_count(ISOMReader *read, ISOMChannel *ch) { - u32 w, h, sr, nb_ch, nb_bps, codec_id, depends_on_id, esid, avg_rate, max_rate, buffer_size, sample_count, max_size, base_track, audio_fmt, pix_fmt; - GF_ESD *an_esd; - const char *mime, *encoding, *stxtcfg, *namespace, *schemaloc, *mime_cfg; + u32 count = 0; + if (read->extkid) { + count = gf_isom_get_udta_count(read->extkmov, read->extkid); + if (read->extk_flags & GF_ISOM_EXTK_NO_UDTA) return count; + } + count += gf_isom_get_udta_count(read->mov, ch->track); + return count; + +} + +static GF_Err isor_get_udta_type(ISOMReader *read, ISOMChannel *ch, u32 udta_idx, u32 *UserDataType, bin128 *UUID) +{ + if (read->extkid) { + u32 count = gf_isom_get_udta_count(read->extkmov, read->extkid); + if (udta_idx<=count) + return gf_isom_get_udta_type(read->extkmov, read->extkid, udta_idx, UserDataType, UUID); + if (read->extk_flags & GF_ISOM_EXTK_NO_UDTA) return count; + udta_idx -= count; + } + return gf_isom_get_udta_type(read->mov, ch->track, udta_idx, UserDataType, UUID); +} + +static u32 isor_get_user_data_count(ISOMReader *read, ISOMChannel *ch, u32 UserDataType, bin128 UUID) +{ + u32 count = 0; + if (read->extkid) { + count = gf_isom_get_user_data_count(read->extkmov, read->extkid, UserDataType, UUID); + if (read->extk_flags & GF_ISOM_EXTK_NO_UDTA) return count; + } + count += gf_isom_get_user_data_count(read->mov, ch->track, UserDataType, UUID); + return count; +} + +static GF_Err isor_get_user_data(ISOMReader *read, ISOMChannel *ch, u32 UserDataType, bin128 UUID, u32 UserDataIndex, u8 **userData, u32 *userDataSize) +{ + if (read->extkid) { + u32 count = gf_isom_get_user_data_count(read->extkmov, read->extkid, UserDataType, UUID); + if (UserDataIndex<=count) + return gf_isom_get_user_data(read->extkmov, read->extkid, UserDataType, UUID, UserDataIndex, userData, userDataSize); + + if (read->extk_flags & GF_ISOM_EXTK_NO_UDTA) return GF_BAD_PARAM; + UserDataIndex -= count; + } + return gf_isom_get_user_data(read->mov, ch->track, UserDataType, UUID, UserDataIndex, userData, userDataSize); +} +#endif + +static ISOMChannel *isor_setup_channel(ISOMReader *read, u32 track, u32 streamtype, Bool use_iod, u32 esid, u32 depends_on_id, u32 ocr_es_id, Bool set_lang) +{ + ISOMChannel *ch=NULL; + u32 base_track; + //first setup, creation of PID and channel + Bool use_sidx_dur = GF_FALSE; + Bool external_base = GF_FALSE; + Bool has_scalable_layers = GF_FALSE; + GF_FilterPid *pid; + GF_Err e; + Bool use_lhvc = GF_FALSE; + Bool use_tx3g=GF_FALSE; #if !defined(GPAC_DISABLE_ISOM_WRITE) u8 *tk_template; u32 tk_template_size; #endif + + gf_isom_get_reference(read->mov, track, GF_ISOM_REF_BASE, 1, &base_track); + if (read->extkid) base_track=0; + + //pass on all configs to detect scalability presence and TX3G as we need to export some vars for all configs + for (u32 stsd_idx=1; stsd_idx<=gf_isom_get_sample_description_count(read->mov, track); stsd_idx++) { + u32 m_subtype = gf_isom_get_media_subtype(read->mov, track, stsd_idx); + if (m_subtype == GF_ISOM_SUBTYPE_TX3G) use_tx3g = GF_TRUE; + + if (base_track) { + u32 base_subtype=0; + if (read->smode==MP4DMX_SINGLE) + depends_on_id = 0; + + switch (m_subtype) { + case GF_ISOM_SUBTYPE_LHV1: + case GF_ISOM_SUBTYPE_LHE1: + use_lhvc = GF_TRUE; + base_subtype = gf_isom_get_media_subtype(read->mov, base_track, stsd_idx); + switch (base_subtype) { + case GF_ISOM_SUBTYPE_HVC1: + case GF_ISOM_SUBTYPE_HEV1: + case GF_ISOM_SUBTYPE_HVC2: + case GF_ISOM_SUBTYPE_HEV2: + break; + default: + external_base=GF_TRUE; + break; + } + } + if (external_base) { + depends_on_id = gf_isom_get_track_id(read->mov, base_track); + has_scalable_layers = GF_TRUE; + } else { + switch (gf_isom_get_hevc_lhvc_type(read->mov, track, stsd_idx)) { + case GF_ISOM_HEVCTYPE_HEVC_LHVC: + case GF_ISOM_HEVCTYPE_LHVC_ONLY: + has_scalable_layers = GF_TRUE; + break; + //this is likely temporal sublayer of base + case GF_ISOM_HEVCTYPE_HEVC_ONLY: + has_scalable_layers = GF_FALSE; + if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SCAL)<=0) { + depends_on_id = gf_isom_get_track_id(read->mov, base_track); + } + break; + default: + break; + } + } + } else { + switch (gf_isom_get_hevc_lhvc_type(read->mov, track, stsd_idx)) { + case GF_ISOM_HEVCTYPE_HEVC_LHVC: + case GF_ISOM_HEVCTYPE_LHVC_ONLY: + if (!read->extkid) + has_scalable_layers = GF_TRUE; + break; + default: + break; + } + + if (!has_scalable_layers) { + u32 i; + GF_ISOTrackID track_id = gf_isom_get_track_id(read->mov, track); + for (i=0; i<gf_isom_get_track_count(read->mov); i++) { + if (gf_isom_get_reference_count(read->mov, i+1, GF_ISOM_REF_BASE)>=0) { + GF_ISOTrackID tkid; + gf_isom_get_reference_ID(read->mov, i+1, GF_ISOM_REF_BASE, 1, &tkid); + if (tkid==track_id) { + has_scalable_layers = GF_TRUE; + break; + } + } + } + } + } + } + + if (base_track && !ocr_es_id) { + ocr_es_id = gf_isom_get_track_id(read->mov, base_track); + } + if (!ocr_es_id) ocr_es_id = esid; + + //OK declare PID + pid = gf_filter_pid_new(read->filter); + if (read->pid) + gf_filter_pid_copy_properties(pid, read->pid); + + gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(esid)); + gf_filter_pid_set_property(pid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(ocr_es_id)); + if (depends_on_id && (depends_on_id != esid)) + gf_filter_pid_set_property(pid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(depends_on_id)); + + if (read->extkid || (gf_isom_get_track_count(read->mov)>1)) { + char szPName101; + szPName100=0; + const char *szST = gf_stream_type_name(streamtype); + snprintf(szPName, 100, "%c%d", szST0, esid); + gf_filter_pid_set_name(pid, szPName); + } + + //MPEG-4 systems present + if (use_iod) + gf_filter_pid_set_property(pid, GF_PROP_PID_ESID, &PROP_UINT(esid)); + + if (gf_isom_is_track_in_root_od(read->mov, track) && !read->lightp) { + switch (streamtype) { + case GF_STREAM_SCENE: + case GF_STREAM_OD: + gf_filter_pid_set_property(pid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE)); + break; + } + } + + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(streamtype)); + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT( gf_isom_get_media_timescale(read->mov, track) ) ); + + if (!gf_sys_is_test_mode()) + gf_filter_pid_set_property(pid, GF_PROP_PID_TRACK_NUM, &PROP_UINT(track) ); + + //create our channel + ch = isor_create_channel(read, pid, track, 0, (use_lhvc) ? GF_TRUE : GF_FALSE); + + if (set_lang) { + char *lang=NULL; + if (read->extkid) { + gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(read->orig_id) ); + gf_isom_get_media_language(read->extkmov, read->extkid, &lang); + } + if (!lang) + gf_isom_get_media_language(read->mov, track, &lang); + + if (lang) { + gf_filter_pid_set_property(pid, GF_PROP_PID_LANGUAGE, &PROP_STRING( lang )); + gf_free(lang); + } + } + + ch->streamType = streamtype; +// ch->clock_id = ocr_es_id; + + if (!read->lightp) { + if (has_scalable_layers) + gf_filter_pid_set_property(pid, GF_PROP_PID_SCALABLE, &PROP_BOOL(GF_TRUE)); + + if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SABT)>0) { + gf_filter_pid_set_property(pid, GF_PROP_PID_TILE_BASE, &PROP_BOOL(GF_TRUE)); + } + else if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SUBPIC)>0) { + gf_filter_pid_set_property(pid, GF_PROP_PID_TILE_BASE, &PROP_BOOL(GF_TRUE)); + } + + + if (!use_lhvc) + isor_export_ref(read, ch, GF_ISOM_REF_SCAL, "isom:scal"); + isor_export_ref(read, ch, GF_ISOM_REF_SABT, "isom:sabt"); + isor_export_ref(read, ch, GF_ISOM_REF_TBAS, "isom:tbas"); + isor_export_ref(read, ch, GF_ISOM_REF_SUBPIC, "isom:subp"); + } + + if (read->lightp) { + ch->duration = gf_isom_get_track_duration_orig(read->mov, ch->track); + } else { + ch->duration = gf_isom_get_track_duration(read->mov, ch->track); + } + if (!ch->duration) { + ch->duration = gf_isom_get_duration(read->mov); + } + u32 sample_count = gf_isom_get_sample_count(read->mov, ch->track); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + if (read->frag_type && !read->input_loaded) { + u32 ts; + u64 dur; + if (gf_isom_get_sidx_duration(read->mov, &dur, &ts)==GF_OK) { + dur *= read->timescale; + dur /= ts; + ch->duration = dur; + use_sidx_dur = GF_TRUE; + sample_count = 0; + } + } +#endif + + if (!read->mem_load_mode || ch->duration) { + //if no edit list (whether complex or simple TS offset) and no sidx, use media duration + if (!ch->has_edit_list && !use_sidx_dur && !ch->ts_offset) { + //no specific edit list type but edit present, use the duration in the edit + if (gf_isom_get_edits_count(read->mov, ch->track)) { + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->timescale)); + } else { + u64 dur = gf_isom_get_media_duration(read->mov, ch->track); + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(dur, ch->timescale)); + } + } + //otherwise trust track duration + else { + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->timescale)); + } + gf_filter_pid_set_property(pid, GF_PROP_PID_NB_FRAMES, &PROP_UINT(sample_count)); + } + + if (sample_count && (streamtype==GF_STREAM_VISUAL)) { + u64 mdur = gf_isom_get_media_duration(read->mov, track); + //if ts_offset is negative (skip), update media dur before computing fps + if (!gf_sys_old_arch_compat()) { + u32 sdur = gf_isom_get_avg_sample_delta(read->mov, ch->track); + if (sdur) { + mdur = sdur; + } else { + if (ch->ts_offset<0) + mdur -= (u32) -ch->ts_offset; + mdur /= sample_count; + } + } else { + mdur /= sample_count; + } + gf_filter_pid_set_property(pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(ch->timescale, (u32) mdur)); + } + + Double track_dur = (Double) (s64) ch->duration; + track_dur /= read->timescale; + //move channel duration in media timescale + ch->duration = (u64) (track_dur * ch->timescale); + + + //set stream subtype + u32 mtype = gf_isom_get_media_type(read->mov, track); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_SUBTYPE, &PROP_4CC(mtype) ); + + if (!read->mem_load_mode) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_MEDIA_DATA_SIZE, &PROP_LONGUINT(gf_isom_get_media_data_size(read->mov, track) ) ); + } + //in no cache mode, depending on fetch speed we may have fetched a fragment or not, resulting in has_rap set + //always for HAS_SYNC to false + else if (gf_sys_is_test_mode() && !sample_count) { + gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(GF_FALSE) ); + } + + if (read->lightp) goto props_done; + + u32 cst_size = gf_isom_get_constant_sample_size(read->mov, track); + if (cst_size) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_FRAME_SIZE, &PROP_UINT(cst_size)); + + //mem mode, cannot read backwards + if (read->mem_load_mode) { + const GF_PropertyValue *p = gf_filter_pid_get_property(read->pid, GF_PROP_PID_PLAYBACK_MODE); + if (!p) + gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } else { + gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_REWIND) ); + } + + GF_PropertyValue brands; + brands.type = GF_PROP_4CC_LIST; + u32 major_brand=0; + gf_isom_get_brand_info(read->mov, &major_brand, NULL, &brands.value.uint_list.nb_items); + brands.value.uint_list.vals = (u32 *) gf_isom_get_brands(read->mov); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_BRANDS, &brands); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_MBRAND, &PROP_4CC(major_brand) ); + + //we cannot expose average size/dur in mem mode with fragmented files (sample_count=0) + u32 max_size=0; + if (sample_count) { + max_size = gf_isom_get_max_sample_size(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_FRAME_SIZE, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_avg_sample_size(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_AVG_FRAME_SIZE, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_max_sample_delta(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_TS_DELTA, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_max_sample_cts_offset(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_CTS_OFFSET, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_constant_sample_duration(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_CONSTANT_DURATION, &PROP_UINT(max_size) ); + } + + + u32 media_pl=0; + if (streamtype==GF_STREAM_VISUAL) { + media_pl = gf_isom_get_pl_indication(read->mov, GF_ISOM_PL_VISUAL); + } else if (streamtype==GF_STREAM_AUDIO) { + media_pl = gf_isom_get_pl_indication(read->mov, GF_ISOM_PL_AUDIO); + } + if (media_pl && (media_pl!=0xFF) ) gf_filter_pid_set_property(pid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(media_pl) ); + +#if !defined(GPAC_DISABLE_ISOM_WRITE) + e = gf_isom_get_track_template(read->mov, ch->track, &tk_template, &tk_template_size); + if (e == GF_OK) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Failed to serialize track box: %s\n", gf_error_to_string(e) )); + } + + e = gf_isom_get_trex_template(read->mov, ch->track, &tk_template, &tk_template_size); + if (e == GF_OK) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TREX_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } + + e = gf_isom_get_raw_user_data(read->mov, &tk_template, &tk_template_size); + if (e==GF_OK) { + if (tk_template_size) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_UDTA, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Failed to serialize moov UDTA box: %s\n", gf_error_to_string(e) )); + } +#endif + + GF_Fraction64 moov_time; + moov_time.num = gf_isom_get_duration(read->mov); + moov_time.den = gf_isom_get_timescale(read->mov); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_MOVIE_TIME, &PROP_FRAC64(moov_time) ); + + + u32 i, w, h; + s32 tx, ty; + s16 l; + if (read->extkmov) + gf_isom_get_track_layout_info(read->extkmov, read->extkid, &w, &h, &tx, &ty, &l); + else + gf_isom_get_track_layout_info(read->mov, ch->track, &w, &h, &tx, &ty, &l); + if (w && h) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_TRANS_X, &PROP_SINT(tx) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_TRANS_Y, &PROP_SINT(ty) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ZORDER, &PROP_SINT(l) ); + } + if (use_tx3g) { + u32 m_w = w; + u32 m_h = h; + GF_ISOFile *srcf = read->extkmov ? read->extkmov : read->mov; + for (i=0; i<gf_isom_get_track_count(read->mov); i++) { + switch (gf_isom_get_media_type(srcf, i+1)) { + case GF_ISOM_MEDIA_SCENE: + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: + gf_isom_get_track_layout_info(srcf, i+1, &w, &h, &tx, &ty, &l); + if (w>m_w) m_w = w; + if (h>m_h) m_h = h; + break; + default: + break; + } + } + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH_MAX, &PROP_UINT(m_w) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT_MAX, &PROP_UINT(m_h) ); + char *tx3g_config_sdp = NULL; + for (i=0; i<gf_isom_get_sample_description_count(read->mov, ch->track); i++) { + u8 *tx3g; + u32 l1; + u32 tx3g_len, len; + e = gf_isom_text_get_encoded_tx3g(read->mov, ch->track, i+1, GF_RTP_TX3G_SIDX_OFFSET, &tx3g, &tx3g_len); + if (e==GF_OK) { + char buffer2000; + len = gf_base64_encode(tx3g, tx3g_len, buffer, 2000); + gf_free(tx3g); + bufferlen = 0; + + l1 = tx3g_config_sdp ? (u32) strlen(tx3g_config_sdp) : 0; + tx3g_config_sdp = gf_realloc(tx3g_config_sdp, len+3+l1); + tx3g_config_sdpl1 = 0; + if (i) strcat(tx3g_config_sdp, ", "); + strcat(tx3g_config_sdp, buffer); + } + } + if (tx3g_config_sdp) { + u32 tx3g_config_len = (u32) strlen(tx3g_config_sdp)+1; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, &PROP_DATA_NO_COPY(tx3g_config_sdp, tx3g_config_len) ); + } + } + + u32 idx=0; + u32 pass = read->extkmov ? 0 : 1; + while (1) { + u32 data_len, int_val2, flags; + u64 int_val; + const char *name=NULL, *mean=NULL; + u32 locale=0; + const u8 *data; + GF_ISOiTunesTag itag; + u32 itype = 0; + s32 tag_idx; + + if (!pass) { + e = gf_isom_apple_enum_tag(read->extkmov, idx, &itag, &data, &data_len, &int_val, &int_val2, &flags); + if (e) { + //ignore source meta + if (read->extk_flags & GF_ISOM_EXTK_NO_META) break; + pass=1; + idx=0; + continue; + } + } else { + e = gf_isom_apple_enum_tag_ex(read->mov, idx, &itag, &data, &data_len, &int_val, &int_val2, &flags, &mean, &name, &locale); + if (e) break; + } + idx++; + + //do not expose tool + if (!gf_sys_is_test_mode() && (itag == GF_ISOM_ITUNE_TOOL)) + continue; + + if (!data || !data_len) continue; + + tag_idx = gf_itags_find_by_itag(itag); + if (tag_idx>=0) + itype = gf_itags_get_type(tag_idx); + + char *dyname = NULL; + if (name || mean) { + gf_dynstrcat(&dyname, "cust_", NULL); + if (name) gf_dynstrcat(&dyname, name, NULL); + gf_dynstrcat(&dyname, "@", NULL); + if (mean) gf_dynstrcat(&dyname, mean, NULL); + } + if (!name) + name = gf_itags_get_name(tag_idx); + + switch (itype) { + case GF_ITAG_BOOL: + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_BOOL((Bool) int_val ) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_BOOL((Bool) int_val ) ); + break; + case GF_ITAG_INT8: + case GF_ITAG_INT16: + case GF_ITAG_INT32: + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_UINT((u32) int_val ) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_UINT((u32) int_val ) ); + break; + case GF_ITAG_INT64: + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_LONGUINT(int_val) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_LONGUINT(int_val) ); + break; + case GF_ITAG_FRAC8: + case GF_ITAG_FRAC6: + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_FRAC_INT((s32) int_val, int_val2) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_FRAC_INT((s32) int_val, int_val2) ); + break; + case GF_ITAG_FILE: + if (!data || !data_len) break; + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_DATA((u8 *)data, data_len) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_DATA((u8 *)data, data_len) ); + break; + default: + if (gf_utf8_is_legal(data, data_len)) { + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_STRING(data) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_STRING(data) ); + } else { + if (dyname) + gf_filter_pid_set_property_dyn(ch->pid, dyname, &PROP_DATA((u8 *)data, data_len) ); + else + gf_filter_pid_set_property_str(ch->pid, name, &PROP_DATA((u8 *)data, data_len) ); + } + break; + } + + if (dyname) + gf_free(dyname); + } + + if (gf_sys_old_arch_compat()) { + Bool gf_isom_has_time_offset_table(GF_ISOFile *the_file, u32 trackNumber); + if (gf_isom_has_time_offset_table(read->mov, ch->track)) + gf_filter_pid_set_property_str(ch->pid, "isom_force_ctts", &PROP_BOOL(GF_TRUE) ); + } + if (read->nodata==2) + gf_filter_pid_set_property_str(ch->pid, "nodata", &PROP_BOOL(GF_TRUE) ); + + if (!gf_sys_is_test_mode()) { + u32 nb_udta, alt_grp=0; + const char *hdlr = NULL; + gf_isom_get_handler_name(read->mov, ch->track, &hdlr); + if (hdlr) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_HANDLER, &PROP_STRING(hdlr)); + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_FLAGS, &PROP_UINT( gf_isom_get_track_flags(read->mov, ch->track) )); + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_FLAGS, &PROP_UINT( gf_isom_get_track_flags(read->mov, ch->track) )); + + gf_isom_get_track_switch_group_count(read->mov, ch->track, &alt_grp, NULL); + if (alt_grp) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_ALT_GROUP, &PROP_UINT( alt_grp )); + + + if (streamtype==GF_STREAM_VISUAL) { + GF_PropertyValue p; + u32 vals9; + memset(vals, 0, sizeof(u32)*9); + memset(&p, 0, sizeof(GF_PropertyValue)); + p.type = GF_PROP_SINT_LIST; + p.value.uint_list.nb_items = 9; + p.value.uint_list.vals = vals; + if (read->extkmov) + gf_isom_get_track_matrix(read->extkmov, read->extkid, vals); + else + gf_isom_get_track_matrix(read->mov, ch->track, vals); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_MATRIX, &p); + } + + + nb_udta = gf_isom_get_udta_count(read->mov, ch->track); + if (nb_udta) { + for (i=0; i<nb_udta; i++) { + u32 j, type, nb_items; + bin128 uuid; + gf_isom_get_udta_type(read->mov, ch->track, i+1, &type, &uuid); + if (type==GF_ISOM_BOX_TYPE_KIND) continue; + nb_items = gf_isom_get_user_data_count(read->mov, ch->track, type, uuid); + //we only export 4CC udta boxes + if (!type) continue; + + for (j=0; j<nb_items; j++) { + char szName31; + u8 *udta=NULL; + u32 udta_size; + gf_isom_get_user_data(read->mov, ch->track, type, uuid, j+1, &udta, &udta_size); + if (!udta || !udta_size) continue; + if (nb_items>1) + snprintf(szName, 30, "udta_%s_%d", gf_4cc_to_str(type), j+1); + else + snprintf(szName, 30, "udta_%s", gf_4cc_to_str(type)); + szName30=0; + if (gf_utf8_is_legal(udta, udta_size)) { + if (!udtaudta_size-1) { + gf_filter_pid_set_property_dyn(ch->pid, szName, &PROP_STRING_NO_COPY(udta)); + } else { + char *data = gf_malloc(udta_size+1); + memcpy(data, udta, udta_size); + dataudta_size=0; + gf_filter_pid_set_property_dyn(ch->pid, szName, &PROP_STRING_NO_COPY(data)); + gf_free(udta); + } + } else { + gf_filter_pid_set_property_dyn(ch->pid, szName, &PROP_DATA_NO_COPY(udta, udta_size)); + } + } + } + } + GF_PropertyValue kinds; + kinds.type = GF_PROP_STRING_LIST; + kinds.value.string_list.nb_items = nb_udta = gf_isom_get_user_data_count(read->mov, ch->track, GF_ISOM_BOX_TYPE_KIND, NULL); + if (nb_udta) { + kinds.value.string_list.vals = gf_malloc(sizeof(char*)*nb_udta); + memset(kinds.value.string_list.vals, 0, sizeof(char*)*nb_udta); + for (i=0; i<nb_udta; i++) { + char *scheme=NULL, *val=NULL; + gf_isom_get_track_kind(read->mov, ch->track, i, &scheme, &val); + if (scheme) gf_dynstrcat(&kinds.value.string_list.valsi, scheme, NULL); + gf_dynstrcat(&kinds.value.string_list.valsi, val ? val : "", scheme ? ":" : NULL); + if (scheme) gf_free(scheme); + if (val) gf_free(val); + } + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ROLE, &kinds); + } + + + //delcare track groups + u32 idx=0; + while (1) { + char szTK100; + u32 track_group_type, track_group_id; + if (!gf_isom_enum_track_group(read->extkmov ? read->extkmov : read->mov, + read->extkmov ? read->extkid : ch->track, + &idx, &track_group_type, &track_group_id) + ) + break; + sprintf(szTK, "tkgp_%s", gf_4cc_to_str(track_group_type)); + gf_filter_pid_set_property_dyn(ch->pid, szTK, &PROP_SINT(track_group_id)); + } + } + +props_done: + + if (read->sigfrag) { +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + u64 start, end; + if (gf_isom_get_root_sidx_offsets(read->mov, &start, &end)) { + if (end) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_VOD_SIDX_RANGE, &PROP_FRAC64_INT(start , end)); + } +#endif + if (!read->frag_type) { + gf_filter_pid_set_property_str(ch->pid, "nofrag", &PROP_BOOL(GF_TRUE)); + } + } + return ch; +} + +static void isor_declare_track(ISOMReader *read, ISOMChannel *ch, u32 track, u32 stsd_idx, u32 streamtype, Bool use_iod) +{ + u32 w, h, sr, nb_ch, nb_bps, codec_id, depends_on_id, esid, avg_rate, max_rate, buffer_size, audio_fmt, pix_fmt; + GF_ESD *an_esd; + const char *mime, *encoding, *stxtcfg, *namespace, *schemaloc, *mime_cfg; GF_Language *lang_desc = NULL; - Bool external_base=GF_FALSE; - Bool has_scalable_layers = GF_FALSE; u8 *dsi = NULL, *enh_dsi = NULL; u32 dsi_size = 0, enh_dsi_size = 0; - Double track_dur=0; u32 srd_id=0, srd_indep=0, srd_x=0, srd_y=0, srd_w=0, srd_h=0; u32 base_tile_track=0; + u64 ch_layout=0; Bool srd_full_frame=GF_FALSE; - u32 mtype, m_subtype; + u32 m_subtype; GF_GenericSampleDescription *udesc = NULL; GF_Err e; u32 ocr_es_id; @@ -271,7 +937,7 @@ if (base_tile_track) { depends_on_id = gf_isom_get_track_id(read->mov, base_tile_track); } - gf_isom_get_tile_info(read->mov, track, 1, NULL, &srd_id, &srd_indep, &srd_full_frame, &srd_x, &srd_y, &srd_w, &srd_h); + gf_isom_get_tile_info(read->mov, track, stsd_idx, NULL, &srd_id, &srd_indep, &srd_full_frame, &srd_x, &srd_y, &srd_w, &srd_h); break; case GF_ISOM_SUBTYPE_TEXT: case GF_ISOM_SUBTYPE_TX3G: @@ -413,7 +1079,18 @@ return; } codec_id = GF_CODECID_VVC_SUBPIC; - gf_isom_get_tile_info(read->mov, track, 1, NULL, &srd_id, &srd_indep, &srd_full_frame, &srd_x, &srd_y, &srd_w, &srd_h); + gf_isom_get_tile_info(read->mov, track, stsd_idx, NULL, &srd_id, &srd_indep, &srd_full_frame, &srd_x, &srd_y, &srd_w, &srd_h); + break; + + case GF_ISOM_SUBTYPE_AVS3: + { + GF_AVS3VConfig *avs3cfg = gf_isom_avs3v_config_get(read->mov, track, stsd_idx); + if (avs3cfg) { + gf_odf_avs3v_cfg_write(avs3cfg, &dsi, &dsi_size); + gf_odf_avs3v_cfg_del(avs3cfg); + } + codec_id = GF_CODECID_AVS3_VIDEO; + } break; case GF_ISOM_SUBTYPE_AC3: @@ -423,6 +1100,9 @@ codec_id = (m_subtype==GF_ISOM_SUBTYPE_AC3) ? GF_CODECID_AC3 : GF_CODECID_EAC3; if (ac3cfg) { gf_odf_ac3_cfg_write(ac3cfg, &dsi, &dsi_size); + if (!gf_sys_is_test_mode()) { + ch_layout = gf_ac3_get_channel_layout(ac3cfg); + } gf_free(ac3cfg); } else { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Track %d missing AC3/EC3 configuration !\n", track)); @@ -430,6 +1110,18 @@ } break; + case GF_ISOM_SUBTYPE_AC4: + { + GF_AC4Config *ac4cfg = gf_isom_ac4_config_get(read->mov, track, stsd_idx); + codec_id = GF_ISOM_SUBTYPE_AC4; + if (ac4cfg) { + gf_odf_ac4_cfg_write(ac4cfg, &dsi, &dsi_size); + gf_odf_ac4_cfg_del(ac4cfg); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Track %d missing AC4 configuration !\n", track)); + } + } + break; case GF_ISOM_SUBTYPE_MLPA: { u32 fmt, prate; @@ -457,7 +1149,12 @@ load_default = GF_TRUE; break; - case GF_4CC('G','M','C','W'): + case GF_4CC('G','M','C','W'): + codec_id = m_subtype; + load_default = GF_TRUE; + break; + + case GF_4CC('e','v','t','e'): codec_id = m_subtype; load_default = GF_TRUE; break; @@ -538,572 +1235,62 @@ //first setup, creation of PID and channel if (!ch) { - Bool use_sidx_dur = GF_FALSE; - GF_FilterPid *pid; first_config = GF_TRUE; + ch = isor_setup_channel(read, track, streamtype, use_iod, esid, depends_on_id, ocr_es_id, lang_desc ? GF_TRUE : GF_FALSE); + } - gf_isom_get_reference(read->mov, track, GF_ISOM_REF_BASE, 1, &base_track); - - if (base_track) { - u32 base_subtype=0; - if (read->smode==MP4DMX_SINGLE) - depends_on_id = 0; - - switch (m_subtype) { - case GF_ISOM_SUBTYPE_LHV1: - case GF_ISOM_SUBTYPE_LHE1: - base_subtype = gf_isom_get_media_subtype(read->mov, base_track, stsd_idx); - switch (base_subtype) { - case GF_ISOM_SUBTYPE_HVC1: - case GF_ISOM_SUBTYPE_HEV1: - case GF_ISOM_SUBTYPE_HVC2: - case GF_ISOM_SUBTYPE_HEV2: - break; - default: - external_base=GF_TRUE; - break; - } - } - if (external_base) { - depends_on_id = gf_isom_get_track_id(read->mov, base_track); - has_scalable_layers = GF_TRUE; - } else { - switch (gf_isom_get_hevc_lhvc_type(read->mov, track, stsd_idx)) { - case GF_ISOM_HEVCTYPE_HEVC_LHVC: - case GF_ISOM_HEVCTYPE_LHVC_ONLY: - has_scalable_layers = GF_TRUE; - break; - //this is likely temporal sublayer of base - case GF_ISOM_HEVCTYPE_HEVC_ONLY: - has_scalable_layers = GF_FALSE; - if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SCAL)<=0) { - depends_on_id = gf_isom_get_track_id(read->mov, base_track); - } - break; - default: - break; - } - } - } else { - switch (gf_isom_get_hevc_lhvc_type(read->mov, track, stsd_idx)) { - case GF_ISOM_HEVCTYPE_HEVC_LHVC: - case GF_ISOM_HEVCTYPE_LHVC_ONLY: - has_scalable_layers = GF_TRUE; - break; - default: - break; - } - - if (!has_scalable_layers) { - u32 i; - GF_ISOTrackID track_id = gf_isom_get_track_id(read->mov, track); - for (i=0; i<gf_isom_get_track_count(read->mov); i++) { - if (gf_isom_get_reference_count(read->mov, i+1, GF_ISOM_REF_BASE)>=0) { - GF_ISOTrackID tkid; - gf_isom_get_reference_ID(read->mov, i+1, GF_ISOM_REF_BASE, 1, &tkid); - if (tkid==track_id) { - has_scalable_layers = GF_TRUE; - break; - } - } - } - } - } - - if (base_track && !ocr_es_id) { - ocr_es_id = gf_isom_get_track_id(read->mov, base_track); - } - if (!ocr_es_id) ocr_es_id = esid; - - //OK declare PID - pid = gf_filter_pid_new(read->filter); - if (read->pid) - gf_filter_pid_copy_properties(pid, read->pid); - - gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(esid)); - gf_filter_pid_set_property(pid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(ocr_es_id)); - if (depends_on_id && (depends_on_id != esid)) - gf_filter_pid_set_property(pid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(depends_on_id)); - - if (gf_isom_get_track_count(read->mov)>1) { - char szPName1024; - const char *szST = gf_stream_type_name(streamtype); - sprintf(szPName, "%c%d", szST0, esid); - gf_filter_pid_set_name(pid, szPName); - } - - //MPEG-4 systems present - if (use_iod) - gf_filter_pid_set_property(pid, GF_PROP_PID_ESID, &PROP_UINT(esid)); - - if (gf_isom_is_track_in_root_od(read->mov, track) && !read->lightp) { - switch (streamtype) { - case GF_STREAM_SCENE: - case GF_STREAM_OD: - gf_filter_pid_set_property(pid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE)); - break; - } - } - - gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(streamtype)); - gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT( gf_isom_get_media_timescale(read->mov, track) ) ); - - if (!gf_sys_is_test_mode()) - gf_filter_pid_set_property(pid, GF_PROP_PID_TRACK_NUM, &PROP_UINT(track) ); - - //Dolby Vision - check for any video type - GF_DOVIDecoderConfigurationRecord *dovi = gf_isom_dovi_config_get(read->mov, track, stsd_idx); - if (dovi) { - u8 *data = NULL; - u32 size = 0; - GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_odf_dovi_cfg_write_bs(dovi, bs); - gf_bs_get_content(bs, &data, &size); - gf_filter_pid_set_property(pid, GF_PROP_PID_DOLBY_VISION, &PROP_DATA_NO_COPY(data, size)); - gf_bs_del(bs); - gf_odf_dovi_cfg_del(dovi); - - if (gf_isom_get_reference_count(read->mov, track, GF_4CC('v','d','e','p'))) { - GF_ISOTrackID ref_id=0; - gf_isom_get_reference_ID(read->mov, track, GF_4CC('v','d','e','p'), 1, &ref_id); - if (ref_id) gf_filter_pid_set_property(pid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(ref_id)); - } - } - - //create our channel - ch = isor_create_channel(read, pid, track, 0, (codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); - - if (lang_desc) { - char *lang=NULL; - gf_isom_get_media_language(read->mov, track, &lang); - //s32 idx = gf_lang_find(lang); - gf_filter_pid_set_property(pid, GF_PROP_PID_LANGUAGE, &PROP_STRING( lang )); - if (lang) gf_free(lang); - gf_odf_desc_del((GF_Descriptor *)lang_desc); - lang_desc = NULL; - } - - ch->streamType = streamtype; -// ch->clock_id = ocr_es_id; - - if (!read->lightp) { - if (has_scalable_layers) - gf_filter_pid_set_property(pid, GF_PROP_PID_SCALABLE, &PROP_BOOL(GF_TRUE)); - - if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SABT)>0) { - gf_filter_pid_set_property(pid, GF_PROP_PID_TILE_BASE, &PROP_BOOL(GF_TRUE)); - } - else if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SUBPIC)>0) { - gf_filter_pid_set_property(pid, GF_PROP_PID_TILE_BASE, &PROP_BOOL(GF_TRUE)); - } - - if (srd_w && srd_h) { - gf_filter_pid_set_property(pid, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(srd_x, srd_y) ); - if (base_tile_track) { - gf_isom_get_visual_info(read->mov, base_tile_track, stsd_idx, &w, &h); - if (w && h) { - gf_filter_pid_set_property(pid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(w, h) ); - } - } - } else { - u8 *srdg=NULL; - u32 srdg_s=0; - gf_isom_get_user_data(read->mov, track, GF_ISOM_UDTA_GPAC_SRD, NULL, 1, &srdg, &srdg_s); - if (srdg && srdg_s>=21) { - GF_BitStream *bs = gf_bs_new(srdg, srdg_s, GF_BITSTREAM_READ); - gf_bs_skip_bytes(bs, 5); - srd_x = (s32) gf_bs_read_u32(bs); - srd_y = (s32) gf_bs_read_u32(bs); - srd_w = gf_bs_read_u32(bs); - srd_h = gf_bs_read_u32(bs); - gf_bs_del(bs); - gf_filter_pid_set_property(pid, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(srd_x, srd_y) ); - gf_filter_pid_set_property(pid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(srd_w, srd_h) ); - } - if (srdg) gf_free(srdg); - } - - - if (codec_id !=GF_CODECID_LHVC) - isor_export_ref(read, ch, GF_ISOM_REF_SCAL, "isom:scal"); - isor_export_ref(read, ch, GF_ISOM_REF_SABT, "isom:sabt"); - isor_export_ref(read, ch, GF_ISOM_REF_TBAS, "isom:tbas"); - isor_export_ref(read, ch, GF_ISOM_REF_SUBPIC, "isom:subp"); - } - - if (read->lightp) { - ch->duration = gf_isom_get_track_duration_orig(read->mov, ch->track); - } else { - ch->duration = gf_isom_get_track_duration(read->mov, ch->track); - } - if (!ch->duration) { - ch->duration = gf_isom_get_duration(read->mov); - } - sample_count = gf_isom_get_sample_count(read->mov, ch->track); + if (codec_id==GF_CODECID_TMCD) { + u32 tmcd_flags=0, tmcd_fps_num=0, tmcd_fps_den=0, tmcd_fpt=0; + gf_isom_get_tmcd_config(read->mov, track, stsd_idx, &tmcd_flags, &tmcd_fps_num, &tmcd_fps_den, &tmcd_fpt); + gf_filter_pid_set_property_str(ch->pid, "tmcd:flags", &PROP_UINT(tmcd_flags) ); + gf_filter_pid_set_property_str(ch->pid, "tmcd:framerate", &PROP_FRAC_INT(tmcd_fps_num, tmcd_fps_den) ); + gf_filter_pid_set_property_str(ch->pid, "tmcd:frames_per_tick", &PROP_UINT(tmcd_fpt) ); + } -#ifndef GPAC_DISABLE_ISOM_FRAGMENTS - if (read->frag_type && !read->input_loaded) { - u32 ts; - u64 dur; - if (gf_isom_get_sidx_duration(read->mov, &dur, &ts)==GF_OK) { - dur *= read->timescale; - dur /= ts; - ch->duration = dur; - use_sidx_dur = GF_TRUE; - sample_count = 0; - } - } -#endif + //Dolby Vision - check for any video type + GF_DOVIDecoderConfigurationRecord *dovi = gf_isom_dovi_config_get(read->mov, track, stsd_idx); + if (dovi) { + u8 *data = NULL; + u32 size = 0; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_odf_dovi_cfg_write_bs(dovi, bs); + gf_bs_get_content(bs, &data, &size); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DOLBY_VISION, &PROP_DATA_NO_COPY(data, size)); + gf_bs_del(bs); + gf_odf_dovi_cfg_del(dovi); - if (!read->mem_load_mode || ch->duration) { - //if no edit list (whether complex or simple TS offset) and no sidx, use media duration - if (!ch->has_edit_list && !use_sidx_dur && !ch->ts_offset) { - //no specific edit list type but edit present, use the duration in the edit - if (gf_isom_get_edits_count(read->mov, ch->track)) { - gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->timescale)); - } else { - u64 dur = gf_isom_get_media_duration(read->mov, ch->track); - gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(dur, ch->timescale)); - } - } - //otherwise trust track duration - else { - gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->timescale)); - } - gf_filter_pid_set_property(pid, GF_PROP_PID_NB_FRAMES, &PROP_UINT(sample_count)); + if (gf_isom_get_reference_count(read->mov, track, GF_4CC('v','d','e','p'))) { + GF_ISOTrackID ref_id=0; + gf_isom_get_reference_ID(read->mov, track, GF_4CC('v','d','e','p'), 1, &ref_id); + if (ref_id) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(ref_id)); } + } - if (sample_count && (streamtype==GF_STREAM_VISUAL)) { - u64 mdur = gf_isom_get_media_duration(read->mov, track); - //if ts_offset is negative (skip), update media dur before computing fps - if (!gf_sys_old_arch_compat()) { - u32 sdur = gf_isom_get_avg_sample_delta(read->mov, ch->track); - if (sdur) { - mdur = sdur; - } else { - if (ch->ts_offset<0) - mdur -= (u32) -ch->ts_offset; - mdur /= sample_count; + if (!read->lightp) { + if (srd_w && srd_h) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(srd_x, srd_y) ); + if (base_tile_track) { + gf_isom_get_visual_info(read->mov, base_tile_track, stsd_idx, &w, &h); + if (w && h) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(w, h) ); } - } else { - mdur /= sample_count; } - gf_filter_pid_set_property(pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(ch->timescale, (u32) mdur)); - } - - track_dur = (Double) (s64) ch->duration; - track_dur /= read->timescale; - //move channel duration in media timescale - ch->duration = (u64) (track_dur * ch->timescale); - - - //set stream subtype - mtype = gf_isom_get_media_type(read->mov, track); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_SUBTYPE, &PROP_4CC(mtype) ); - - if (!read->mem_load_mode) { - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_MEDIA_DATA_SIZE, &PROP_LONGUINT(gf_isom_get_media_data_size(read->mov, track) ) ); - } - //in no cache mode, depending on fetch speed we may have fetched a fragment or not, resulting in has_rap set - //always for HAS_SYNC to false - else if (gf_sys_is_test_mode() && !sample_count) { - gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(GF_FALSE) ); - } - - if (read->lightp) goto props_done; - - w = gf_isom_get_constant_sample_size(read->mov, track); - if (w) - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_FRAME_SIZE, &PROP_UINT(w)); - - //mem mode, cannot read backwards - if (read->mem_load_mode) { - const GF_PropertyValue *p = gf_filter_pid_get_property(read->pid, GF_PROP_PID_PLAYBACK_MODE); - if (!p) - gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); - } else { - gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_REWIND) ); - } - - GF_PropertyValue brands; - brands.type = GF_PROP_4CC_LIST; - u32 major_brand=0; - gf_isom_get_brand_info(read->mov, &major_brand, NULL, &brands.value.uint_list.nb_items); - brands.value.uint_list.vals = (u32 *) gf_isom_get_brands(read->mov); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_BRANDS, &brands); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_MBRAND, &PROP_4CC(major_brand) ); - - //we cannot expose average size/dur in mem mode with fragmented files (sample_count=0) - if (sample_count) { - max_size = gf_isom_get_max_sample_size(read->mov, ch->track); - if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_FRAME_SIZE, &PROP_UINT(max_size) ); - - max_size = gf_isom_get_avg_sample_size(read->mov, ch->track); - if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_AVG_FRAME_SIZE, &PROP_UINT(max_size) ); - - max_size = gf_isom_get_max_sample_delta(read->mov, ch->track); - if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_TS_DELTA, &PROP_UINT(max_size) ); - - max_size = gf_isom_get_max_sample_cts_offset(read->mov, ch->track); - if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_CTS_OFFSET, &PROP_UINT(max_size) ); - - max_size = gf_isom_get_constant_sample_duration(read->mov, ch->track); - if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_CONSTANT_DURATION, &PROP_UINT(max_size) ); - } - - - u32 media_pl=0; - if (streamtype==GF_STREAM_VISUAL) { - media_pl = gf_isom_get_pl_indication(read->mov, GF_ISOM_PL_VISUAL); - } else if (streamtype==GF_STREAM_AUDIO) { - media_pl = gf_isom_get_pl_indication(read->mov, GF_ISOM_PL_AUDIO); - } - if (media_pl && (media_pl!=0xFF) ) gf_filter_pid_set_property(pid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(media_pl) ); - -#if !defined(GPAC_DISABLE_ISOM_WRITE) - e = gf_isom_get_track_template(read->mov, ch->track, &tk_template, &tk_template_size); - if (e == GF_OK) { - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Failed to serialize track box: %s\n", gf_error_to_string(e) )); - } - - e = gf_isom_get_trex_template(read->mov, ch->track, &tk_template, &tk_template_size); - if (e == GF_OK) { - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TREX_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); - } - - e = gf_isom_get_raw_user_data(read->mov, &tk_template, &tk_template_size); - if (e==GF_OK) { - if (tk_template_size) - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_UDTA, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Failed to serialize moov UDTA box: %s\n", gf_error_to_string(e) )); - } -#endif - - GF_Fraction64 moov_time; - moov_time.num = gf_isom_get_duration(read->mov); - moov_time.den = gf_isom_get_timescale(read->mov); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_MOVIE_TIME, &PROP_FRAC64(moov_time) ); - - - u32 i; - s32 tx, ty; - s16 l; - gf_isom_get_track_layout_info(read->mov, ch->track, &w, &h, &tx, &ty, &l); - if (w && h) { - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_TRANS_X, &PROP_SINT(tx) ); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_TRANS_Y, &PROP_SINT(ty) ); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ZORDER, &PROP_SINT(l) ); - } - if (codec_id==GF_CODECID_TX3G) { - u32 m_w = w; - u32 m_h = h; - for (i=0; i<gf_isom_get_track_count(read->mov); i++) { - switch (gf_isom_get_media_type(read->mov, i+1)) { - case GF_ISOM_MEDIA_SCENE: - case GF_ISOM_MEDIA_VISUAL: - case GF_ISOM_MEDIA_AUXV: - case GF_ISOM_MEDIA_PICT: - gf_isom_get_track_layout_info(read->mov, i+1, &w, &h, &tx, &ty, &l); - if (w>m_w) m_w = w; - if (h>m_h) m_h = h; - break; - default: - break; - } - } - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH_MAX, &PROP_UINT(m_w) ); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT_MAX, &PROP_UINT(m_h) ); - char *tx3g_config_sdp = NULL; - for (i=0; i<gf_isom_get_sample_description_count(read->mov, ch->track); i++) { - u8 *tx3g; - u32 l1; - u32 tx3g_len, len; - e = gf_isom_text_get_encoded_tx3g(read->mov, ch->track, i+1, GF_RTP_TX3G_SIDX_OFFSET, &tx3g, &tx3g_len); - if (e==GF_OK) { - char buffer2000; - len = gf_base64_encode(tx3g, tx3g_len, buffer, 2000); - gf_free(tx3g); - bufferlen = 0; - - l1 = tx3g_config_sdp ? (u32) strlen(tx3g_config_sdp) : 0; - tx3g_config_sdp = gf_realloc(tx3g_config_sdp, len+3+l1); - tx3g_config_sdpl1 = 0; - if (i) strcat(tx3g_config_sdp, ", "); - strcat(tx3g_config_sdp, buffer); - } - } - if (tx3g_config_sdp) { - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, &PROP_STRING_NO_COPY(tx3g_config_sdp) ); - } - } - - u32 idx=0; - while (1) { - u32 data_len, int_val2, flags; - u64 int_val; - const char *name; - const u8 *data; - GF_ISOiTunesTag itag; - u32 itype = 0; - s32 tag_idx; - - e = gf_isom_apple_enum_tag(read->mov, idx, &itag, &data, &data_len, &int_val, &int_val2, &flags); - if (e) break; - idx++; - - //do not expose tool - if (!gf_sys_is_test_mode() && (itag == GF_ISOM_ITUNE_TOOL)) - continue; - - tag_idx = gf_itags_find_by_itag(itag); - if (tag_idx>=0) - itype = gf_itags_get_type(tag_idx); - - name = gf_itags_get_name(tag_idx); - switch (itype) { - case GF_ITAG_BOOL: - gf_filter_pid_set_property_str(ch->pid, name, &PROP_BOOL((Bool) int_val ) ); - break; - case GF_ITAG_INT8: - case GF_ITAG_INT16: - case GF_ITAG_INT32: - gf_filter_pid_set_property_str(ch->pid, name, &PROP_UINT((u32) int_val ) ); - break; - case GF_ITAG_INT64: - gf_filter_pid_set_property_str(ch->pid, name, &PROP_LONGUINT(int_val) ); - break; - case GF_ITAG_FRAC8: - case GF_ITAG_FRAC6: - gf_filter_pid_set_property_str(ch->pid, name, &PROP_FRAC_INT((s32) int_val, int_val2) ); - break; - case GF_ITAG_FILE: - if (data && data_len) - gf_filter_pid_set_property_str(ch->pid, name, &PROP_DATA((u8 *)data, data_len) ); - break; - default: - if (data && data_len) { - if (gf_utf8_is_legal(data, data_len)) - gf_filter_pid_set_property_str(ch->pid, name, &PROP_STRING(data) ); - else - gf_filter_pid_set_property_str(ch->pid, name, &PROP_DATA((u8 *)data, data_len) ); - } - break; - } - } - - if (codec_id==GF_CODECID_TMCD) { - u32 tmcd_flags=0, tmcd_fps_num=0, tmcd_fps_den=0, tmcd_fpt=0; - gf_isom_get_tmcd_config(read->mov, track, stsd_idx, &tmcd_flags, &tmcd_fps_num, &tmcd_fps_den, &tmcd_fpt); - gf_filter_pid_set_property_str(ch->pid, "tmcd:flags", &PROP_UINT(tmcd_flags) ); - gf_filter_pid_set_property_str(ch->pid, "tmcd:framerate", &PROP_FRAC_INT(tmcd_fps_num, tmcd_fps_den) ); - gf_filter_pid_set_property_str(ch->pid, "tmcd:frames_per_tick", &PROP_UINT(tmcd_fpt) ); - - } - - if (gf_sys_old_arch_compat()) { - Bool gf_isom_has_time_offset_table(GF_ISOFile *the_file, u32 trackNumber); - if (gf_isom_has_time_offset_table(read->mov, ch->track)) - gf_filter_pid_set_property_str(ch->pid, "isom_force_ctts", &PROP_BOOL(GF_TRUE) ); - } - if (read->nodata==2) - gf_filter_pid_set_property_str(ch->pid, "nodata", &PROP_BOOL(GF_TRUE) ); - - if (!gf_sys_is_test_mode()) { - u32 nb_udta, alt_grp=0; - const char *hdlr = NULL; - gf_isom_get_handler_name(read->mov, ch->track, &hdlr); - if (hdlr) - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_HANDLER, &PROP_STRING(hdlr)); - - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_FLAGS, &PROP_UINT( gf_isom_get_track_flags(read->mov, ch->track) )); - - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_FLAGS, &PROP_UINT( gf_isom_get_track_flags(read->mov, ch->track) )); - - gf_isom_get_track_switch_group_count(read->mov, ch->track, &alt_grp, NULL); - if (alt_grp) - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_ALT_GROUP, &PROP_UINT( alt_grp )); - - - if (streamtype==GF_STREAM_VISUAL) { - GF_PropertyValue p; - u32 vals9; - memset(vals, 0, sizeof(u32)*9); - memset(&p, 0, sizeof(GF_PropertyValue)); - p.type = GF_PROP_SINT_LIST; - p.value.uint_list.nb_items = 9; - p.value.uint_list.vals = vals; - gf_isom_get_track_matrix(read->mov, ch->track, vals); - gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_MATRIX, &p); - } - - - nb_udta = gf_isom_get_udta_count(read->mov, ch->track); - if (nb_udta) { - for (i=0; i<nb_udta; i++) { - u32 j, type, nb_items; - bin128 uuid; - gf_isom_get_udta_type(read->mov, ch->track, i+1, &type, &uuid); - nb_items = gf_isom_get_user_data_count(read->mov, ch->track, type, uuid); - //we only export 4CC udta boxes - if (!type) continue; - - for (j=0; j<nb_items; j++) { - char szName31; - u8 *udta=NULL; - u32 udta_size; - gf_isom_get_user_data(read->mov, ch->track, type, uuid, j+1, &udta, &udta_size); - if (!udta || !udta_size) continue; - if (nb_items>1) - snprintf(szName, 30, "udta_%s_%d", gf_4cc_to_str(type), j+1); - else - snprintf(szName, 30, "udta_%s", gf_4cc_to_str(type)); - szName30=0; - if (gf_utf8_is_legal(udta, udta_size)) { - if (!udtaudta_size-1) { - gf_filter_pid_set_property_dyn(ch->pid, szName, &PROP_STRING_NO_COPY(udta)); - } else { - char *data = gf_malloc(udta_size+1); - memcpy(data, udta, udta_size); - dataudta_size=0; - gf_filter_pid_set_property_dyn(ch->pid, szName, &PROP_STRING_NO_COPY(data)); - gf_free(udta); - } - } else { - gf_filter_pid_set_property_dyn(ch->pid, szName, &PROP_DATA_NO_COPY(udta, udta_size)); - } - } - } - } - - //delcare track groups - u32 idx=0; - while (1) { - char szTK100; - u32 track_group_type, track_group_id; - if (!gf_isom_enum_track_group(read->mov, ch->track, &idx, &track_group_type, &track_group_id)) break; - sprintf(szTK, "tkgp_%s", gf_4cc_to_str(track_group_type)); - gf_filter_pid_set_property_dyn(ch->pid, szTK, &PROP_SINT(track_group_id)); - } - } - -props_done: - - if (read->sigfrag) { -#ifndef GPAC_DISABLE_ISOM_FRAGMENTS - u64 start, end; - if (gf_isom_get_root_sidx_offsets(read->mov, &start, &end)) { - if (end) - gf_filter_pid_set_property(ch->pid, GF_PROP_PCK_SIDX_RANGE, &PROP_FRAC64_INT(start , end)); - } -#endif - if (!read->frag_type) { - gf_filter_pid_set_property_str(ch->pid, "nofrag", &PROP_BOOL(GF_TRUE)); + u8 *srdg=NULL; + u32 srdg_s=0; + gf_isom_get_user_data(read->mov, track, GF_ISOM_UDTA_GPAC_SRD, NULL, 1, &srdg, &srdg_s); + if (srdg && srdg_s>=21) { + GF_BitStream *bs = gf_bs_new(srdg, srdg_s, GF_BITSTREAM_READ); + gf_bs_skip_bytes(bs, 5); + srd_x = (s32) gf_bs_read_u32(bs); + srd_y = (s32) gf_bs_read_u32(bs); + srd_w = gf_bs_read_u32(bs); + srd_h = gf_bs_read_u32(bs); + gf_bs_del(bs); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(srd_x, srd_y) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(srd_w, srd_h) ); } + if (srdg) gf_free(srdg); } } @@ -1199,7 +1386,7 @@ gf_filter_pid_set_property_str(ch->pid, "isom:modification_date", &PROP_LONGUINT(modif_date)); } - isor_get_chapters(read->mov, ch->pid); + isor_get_chapters(read->extkmov ? read->extkmov : read->mov, ch->pid); if (!gf_sys_is_test_mode()) { Bool has_roll=GF_FALSE; @@ -1239,7 +1426,7 @@ if (dsi) { ch->dsi_crc = gf_crc_32(dsi, dsi_size); //strip box header for these codecs - if (codec_id==GF_CODECID_SMPTE_VC1) { + if (codec_id==GF_CODECID_SMPTE_VC1 && dsi_size > 8) { gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(dsi+8, dsi_size-8)); gf_free(dsi); dsi=NULL; @@ -1348,9 +1535,9 @@ u64 lay = gf_audio_fmt_get_layout_from_cicp(layout.definedLayout); gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(lay)); } - + } else if (ch_layout) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ch_layout)); } - } if (first_config ) { @@ -1385,6 +1572,8 @@ if (!avg_rate) { if (first_config && ch->duration) { + Double track_dur = (Double) (s64) ch->duration; //in media timescale + track_dur /= ch->timescale; u64 avgrate = 8 * gf_isom_get_media_data_size(read->mov, ch->track); avgrate = (u64) (avgrate / track_dur); gf_filter_pid_set_property(ch->pid, GF_PROP_PID_BITRATE, &PROP_UINT((u32) avgrate)); @@ -1421,8 +1610,8 @@ #if !defined(GPAC_DISABLE_ISOM_WRITE) - tk_template=NULL; - tk_template_size=0; + u8 *tk_template = NULL; + u32 tk_template_size = 0; e = gf_isom_get_stsd_template(read->mov, ch->track, stsd_idx, &tk_template, &tk_template_size); if (e == GF_OK) { gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_STSD_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); @@ -1493,7 +1682,7 @@ } } else if (codec_id==GF_CODECID_DTS_X) { GF_UDTSConfig cfg; - if (gf_isom_get_udts_config(ch->owner->mov, ch->track, 1, &cfg) == GF_OK) { + if (gf_isom_get_udts_config(ch->owner->mov, ch->track, stsd_idx, &cfg) == GF_OK) { u64 ch_layout = cfg.ChannelMask; gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ch_layout)); } @@ -1552,6 +1741,8 @@ if (streamtype==GF_STREAM_VISUAL) { u32 cwn, cwd, chn, chd, cxn, cxd, cyn, cyd; + cwn=cwd=chn=chd=cxn=cxd=cyn=cyd=0; + gf_isom_get_clean_aperture(ch->owner->mov, ch->track, ch->last_sample_desc_index ? ch->last_sample_desc_index : 1, &cwn, &cwd, &chn, &chd, &cxn, &cxd, &cyn, &cyd); if (cwd && chd && cxd && cyd) { @@ -1609,7 +1800,7 @@ const u8 *tag; u32 tlen; u32 i, count, j, track_id; - Bool highest_stream; + Bool highest_stream, use_extk=GF_FALSE;; Bool single_media_found = GF_FALSE; Bool use_iod = GF_FALSE; Bool tk_found = GF_FALSE; @@ -1627,8 +1818,36 @@ count = gf_isom_get_track_count(read->mov); for (i=0; i<count; i++) { u32 mtype, m_subtype, streamtype, stsd_idx; - - mtype = gf_isom_get_media_type(read->mov, i+1); + Bool is_extk; + u32 extk_id; + const char *extk_loc; + + is_extk = gf_isom_is_external_track(read->mov, i+1, &extk_id, &mtype, NULL, &extk_loc); + if (!is_extk) mtype = gf_isom_get_media_type(read->mov, i+1); + + if (read->extkmov) { + u32 ext_tk = 0; + u32 ref_type, ref_id, flags; + if (read->extkid) ext_tk = gf_isom_get_track_by_id(read->extkmov, read->extkid); + else ext_tk = 1; + + if (is_extk) continue; + if (!ext_tk) continue; + + is_extk = gf_isom_is_external_track(read->extkmov, ext_tk, &ref_id, &ref_type, &flags, NULL); + if (!is_extk) continue; + if (mtype != ref_type) continue; + if (ref_id && (ref_id != gf_isom_get_track_id(read->mov, i+1))) continue; + is_extk = GF_FALSE; + read->extk_flags = flags; + read->orig_id = read->extkid; + read->extkid = ext_tk; + //we have an edit list in source + if (flags & GF_ISOM_EXTK_EDTS_SKIP) { + GF_Err gf_isom_merge_external_edit(GF_ISOFile *dst, u32 dst_track, GF_ISOFile *src, u32 src_track); + gf_isom_merge_external_edit(read->mov, i+1, read->extkmov, ext_tk); + } + } if (read->tkid) { u32 for_id=0; @@ -1651,6 +1870,7 @@ tk_found = GF_TRUE; } + switch (mtype) { case GF_ISOM_MEDIA_AUDIO: streamtype = GF_STREAM_AUDIO; @@ -1719,6 +1939,34 @@ } } + if (is_extk) { + char szExt100; + if (!read->extk || !extk_loc) { + continue; + } + char *par_url = read->src; + if (!par_url && read->pid) { + const GF_PropertyValue *p = gf_filter_pid_get_property(read->pid, GF_PROP_PID_URL); + if (p) par_url = p->value.string; + } + char *loc = NULL; + gf_dynstrcat(&loc, extk_loc, NULL); + char sep_a = gf_filter_get_sep(read->filter, GF_FS_SEP_ARGS); + char sep_n = gf_filter_get_sep(read->filter, GF_FS_SEP_NAME); + sprintf(szExt, "%cgpac%cextkmov%c%p", sep_a, sep_a, sep_n, read->mov); + gf_dynstrcat(&loc, szExt, NULL); + sprintf(szExt, "%cextkid%c%u", sep_a, sep_n, gf_isom_get_track_id(read->mov, i+1)); + gf_dynstrcat(&loc, szExt, NULL); + + gf_filter_add_source(read->filter, loc, par_url, GF_TRUE, &e); + tk_found = GF_TRUE; + use_extk = GF_TRUE; + gf_free(loc); + if (read->tkid) + break; + continue; + } + stsd_idx = read->stsd ? read->stsd : 1; //some subtypes are not declared as readable objects m_subtype = gf_isom_get_media_subtype(read->mov, i+1, stsd_idx); @@ -1757,7 +2005,6 @@ if ((read->smode==MP4DMX_SINGLE) && (gf_isom_get_media_type(read->mov, i+1) == GF_ISOM_MEDIA_VISUAL) && !highest_stream) continue; - isor_declare_track(read, NULL, i+1, stsd_idx, streamtype, use_iod); if (read->tkid) @@ -1767,6 +2014,7 @@ if (!read->tkid) { /*declare image items*/ count = gf_isom_get_meta_item_count(read->mov, GF_TRUE, 0); + if (read->extkmov) count=0; for (i=0; i<count; i++) { if (! isor_declare_item_properties(read, NULL, i+1)) continue; @@ -1780,8 +2028,11 @@ } } if (! gf_list_count(read->channels)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia No suitable tracks in file\n")); - return GF_NOT_SUPPORTED; + if (!use_extk) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia No suitable tracks in file\n")); + return GF_NOT_SUPPORTED; + } + gf_filter_abort(read->filter); } /*if cover art, declare a video pid*/
View file
gpac-2.4.0.tar.gz/src/filters/isoffin_read.c -> gpac-26.02.0.tar.gz/src/filters/isoffin_read.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / ISOBMFF reader filter @@ -30,12 +30,6 @@ #include <gpac/crypt_tools.h> #include <gpac/media_tools.h> -enum -{ - EDITS_AUTO=0, - EDITS_NO, - EDITS_STRICT -}; ISOMChannel *isor_get_channel(ISOMReader *reader, GF_FilterPid *pid) { @@ -183,6 +177,12 @@ gf_filter_setup_failure(filter, e); e = GF_FILTER_NOT_SUPPORTED; } + //we loaded an init segment and no associated PID yet (we use initseg opt), prepare for fragment pushing + if (read->frag_type && !read->pid && read->mov) { + //reset offset since the first byte we will received will be at offset 0 in our internal buffer (we don't copy init segment) + gf_isom_reset_data_offset(read->mov, NULL); + read->mem_load_mode = 2; + } return e; } @@ -212,6 +212,32 @@ read->pid = NULL; } +static void isoffin_set_channel_ctso(ISOMReader *read, ISOMChannel *ch) +{ + s32 min_neg_cts_offset = gf_isom_get_min_negative_cts_offset(ch->owner->mov, ch->track, (read->ctso==-2) ? GF_ISOM_MIN_NEGCTTS_SAMPLES : GF_ISOM_MIN_NEGCTTS_ANY); + if (min_neg_cts_offset>=0) return; + if (ch->has_edit_list) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia Track has complex edit list, cannot use ctso option (try adding `edits=no` option to demuxer)\n")); + read->ctso = 0; + return; + } + + if (read->ctso<0) { + ch->cts_offset = MAX(ch->cts_offset, (u32) -min_neg_cts_offset); + ch->has_edit_list = GF_FALSE; + } else { + if (-min_neg_cts_offset > read->ctso) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia requested CTS offset %d less than %d in track, adjusting\n")); + ch->cts_offset = (u32) -min_neg_cts_offset; + } else { + ch->cts_offset = (u32) read->ctso; + } + } + //send PID delay + ch->ts_offset = - (s32) ch->cts_offset; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DELAY, &PROP_LONGSINT( ch->ts_offset) ); +} + static GF_Err isoffin_reconfigure(GF_Filter *filter, ISOMReader *read, const char *next_url) { const GF_PropertyValue *prop; @@ -226,6 +252,7 @@ if (prop && prop->value.boolean) read->input_loaded = GF_TRUE; + read->in_is_eos = GF_FALSE; read->refresh_fragmented = GF_FALSE; read->full_segment_flush = GF_TRUE; GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("IsoMedia reconfigure triggered, URL %s\n", next_url)); @@ -264,6 +291,10 @@ u32 nb_samples = gf_isom_get_sample_count(read->mov, ch->track); gf_filter_pid_set_property(ch->pid, GF_PROP_PID_NB_FRAMES, &PROP_UINT(nb_samples)); } + if (read->ctso) { + isoffin_set_channel_ctso(read, ch); + } + } #ifndef GPAC_DISABLE_LOG @@ -322,10 +353,10 @@ return GF_OK; default: if (!read->mov) { - return GF_NOT_SUPPORTED; + return GF_NOT_SUPPORTED; } - e = GF_ISOM_INVALID_FILE; - break; + e = GF_ISOM_INVALID_FILE; + break; } gf_filter_post_process_task(filter); @@ -334,7 +365,7 @@ if (e<0) { count = gf_list_count(read->channels); - read->invalid_segment = GF_TRUE; + read->invalid_segment = GF_TRUE; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS gf_isom_release_segment(read->mov, 1); //error opening the segment, reset everything ... @@ -342,12 +373,12 @@ #endif for (i=0; i<count; i++) { ISOMChannel *ch = gf_list_get(read->channels, i); - if (ch) { - ch->sample_num = 0; - ch->eos_sent = 0; - } + if (ch) { + ch->sample_num = 0; + ch->eos_sent = 0; + } } - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("IsoMedia Error opening current segment %s: %s\n", next_url, gf_error_to_string(e) )); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("IsoMedia Error opening current segment %s: %s\n", next_url, gf_error_to_string(e) )); return GF_OK; } //segment is the first in our cache, we may need a refresh @@ -358,11 +389,15 @@ } isor_check_producer_ref_time(read); + prop = gf_filter_pid_get_property_str(read->pid, "X-From-MABR"); + for (i=0; i<count; i++) { ISOMChannel *ch = gf_list_get(read->channels, i); ch->last_state = GF_OK; ch->eos_sent = 0; + if (ch->pid) + gf_filter_pid_set_property_str(ch->pid, "X-From-MABR", prop); //old code from master, currently no longer used //in filters we don't use extractors for the time being, we only do implicit reconstruction at the decoder side @@ -433,10 +468,16 @@ if (!prop || !prop->value.string) { if (!read->mem_load_mode) read->mem_load_mode = 1; + if (!read->pid) read->pid = pid; read->input_loaded = GF_FALSE; return GF_OK; } + //we started with a base64 embedding of init segment, but now have an associated file, leave mem-load mode + else if (read->mem_load_mode) { + read->mem_load_mode = 0; + gf_isom_reset_data_offset(read->mov, NULL); + } if (read->pid && prop->value.string) { const char *next_url = prop->value.string; @@ -748,6 +789,8 @@ else ch->has_edit_list = 0; } + if (read->ctso) + isoffin_set_channel_ctso(read, ch); ch->has_rap = (gf_isom_has_sync_points(ch->owner->mov, ch->track)==1) ? 1 : 0; gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(ch->has_rap) ); @@ -846,6 +889,7 @@ //try to locate sync after current time in base resume_at = base->static_sample ? gf_timestamp_rescale(base->static_sample->DTS, base->timescale, ch->timescale) : 0; e = gf_isom_get_sample_for_media_time(ch->owner->mov, ch->track, resume_at, &sample_desc_index, GF_ISOM_SEARCH_SYNC_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); + //found, rewind so that next fetch is the sync if (e==GF_OK) { ch->sample = NULL; @@ -854,6 +898,12 @@ else if (e==GF_EOS) { e = gf_isom_get_sample_for_media_time(ch->owner->mov, ch->track, resume_at, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); } + //trash sample - do not free data, it was dispatched as a filter packet + if (ch->static_sample && ch->static_sample->data) { + ch->static_sample->data = NULL; + ch->static_sample->dataLength = 0; + ch->static_sample->alloc_size = 0; + } //unknown state, realign sample num with base if (e<0) { ch->sample_num = base->sample_num; @@ -919,19 +969,25 @@ switch (evt->base.type) { case GF_FEVT_PLAY: + //reset to FALSE since we now play + read->input_is_stop = GF_FALSE; + if (ch->skip_next_play) { ch->skip_next_play = 0; return GF_TRUE; } is_byte_range = (evt->play.hint_start_offset || evt->play.hint_end_offset) ? GF_TRUE : GF_FALSE; + //if we are in dash mode and first pid to play, force input to be unloaded + //not doing so could trigger EOS before seeing the first bytes of the input + if (!read->nb_playing && evt->play.no_byterange_forward) + read->input_loaded = GF_FALSE; + isor_reset_reader(ch); ch->eos_sent = 0; ch->speed = is_byte_range ? 1 : evt->play.speed; ch->initial_play_seen = 1; read->reset_frag_state = 1; - //it can happen that input_is_stop is still TRUE because we did not get called back after the stop - reset to FALSE since we now play - read->input_is_stop = GF_FALSE; if (read->frag_type) read->frag_type = 1; @@ -994,6 +1050,25 @@ ch->sample_last = evt->play.to_pck; ch->sap_only = evt->play.drop_non_ref ? 1 : 0; + if (!read->nb_playing && !ch->sample_num && (evt->play.timestamp_based==3)) { + GF_Err e; + u32 sample_num, didx; + /*take care of seeking out of the track range*/ + if (!read->frag_type && (ch->duration < ch->start)) { + e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, ch->duration, &didx, GF_ISOM_SEARCH_SYNC_BACKWARD, NULL, &sample_num, NULL); + } else { + e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, ch->start, &didx, GF_ISOM_SEARCH_SYNC_BACKWARD, NULL, &sample_num, NULL); + } + if (!e) { + GF_ISOSample s={0}; + gf_isom_get_sample_info_ex(read->mov, ch->track, sample_num, NULL, NULL, &s); + ch->start = s.DTS+s.CTS_Offset; + start_range = (Double) ch->start; + start_range /= ch->timescale; + ch->sample_num = sample_num; + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("IsoMedia Starting channel playback "LLD" to "LLD" (%g to %g)\n", ch->start, ch->end, start_range, evt->play.end_range)); } else { ch->end = 0; @@ -1043,7 +1118,9 @@ } else if (evt->play.no_byterange_forward) { //new segment will be loaded, reset gf_isom_reset_tables(read->mov, GF_TRUE); - gf_isom_reset_data_offset(read->mov, NULL); + //do NOT reset offsets if not in mem mode, otherwise we could reparse boxes already parsed during initialization + if (read->mem_load_mode) + gf_isom_reset_data_offset(read->mov, NULL); read->refresh_fragmented = GF_TRUE; read->mem_blob.size = 0; //send play event @@ -1082,15 +1159,15 @@ u64 data_offset; GF_Err e; u64 time; - ch = gf_list_get(read->channels, i); + ISOMChannel *ach = gf_list_get(read->channels, i); mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD; - time = (u64) (evt->play.start_range * ch->timescale); + time = (u64) (evt->play.start_range * ach->timescale); /*take care of seeking out of the track range*/ - if (!read->frag_type && (ch->duration < time)) { - e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, ch->duration, &sample_desc_index, mode, NULL, &sample_num, &data_offset); + if (!read->frag_type && (ach->duration < time)) { + e = gf_isom_get_sample_for_movie_time(read->mov, ach->track, ach->duration, &sample_desc_index, mode, NULL, &sample_num, &data_offset); } else { - e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, time, &sample_desc_index, mode, NULL, &sample_num, &data_offset); + e = gf_isom_get_sample_for_movie_time(read->mov, ach->track, time, &sample_desc_index, mode, NULL, &sample_num, &data_offset); } if ((e == GF_OK) && (data_offset<max_offset)) max_offset = data_offset; @@ -1101,7 +1178,8 @@ //send a seek request read->is_partial_download = GF_TRUE; read->wait_for_source = GF_TRUE; - read->refresh_fragmented = GF_TRUE; + if (read->frag_type) + read->refresh_fragmented = GF_TRUE; GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, read->pid); fevt.seek.start_offset = max_offset; @@ -1117,7 +1195,12 @@ } } } - + //activate first channel - if input is loaded and we canceled the event, remember we may no longer receive eos signals from source + //this happens because the last playing track may have send a STOP to the source but we here no longer send play + //do not enter EOS if input PID is not yet assigned (may happen with initseg option) + if (!read->nb_playing) { + read->in_is_eos = (read->input_loaded && cancel_event && read->pid) ? GF_TRUE : GF_FALSE; + } read->nb_playing++; //trigger play on all "disconnected" channels @@ -1150,7 +1233,9 @@ case GF_FEVT_STOP: if (read->nb_playing) read->nb_playing--; + //reset everything but don't mark as to init isor_reset_reader(ch); + ch->to_init = 0; //stop is due to a deconnection, mark channel as not active if (evt->play.initial_broadcast_play==2) @@ -1191,6 +1276,7 @@ { u64 bytes_missing; GF_Err e; + if (!data_size) return; if (!read->mem_url) { read->mem_url = gf_blob_register(&read->mem_blob); @@ -1326,6 +1412,7 @@ if (read->in_error) return read->in_error; + read->was_aborted = GF_FALSE; if (read->pid) { Bool fetch_input = GF_TRUE; @@ -1353,6 +1440,16 @@ } break; } +#if !defined(GPAC_DISABLE_NETWORK) || defined(GPAC_CONFIG_EMSCRIPTEN) + if (read->is_partial_download && read->wait_for_source && !read->mem_load_mode) { + const GF_PropertyValue *prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_DOWNLOAD_SESSION); + if (prop && prop->type==GF_PROP_POINTER) { + const char *new_url = gf_dm_sess_get_cache_name(prop->value.ptr); + if (new_url) + gf_isom_switch_source(read->mov, new_url); + } + } +#endif read->wait_for_source = GF_FALSE; if (read->mem_load_mode) { @@ -1372,8 +1469,14 @@ } if (gf_filter_pid_is_eos(read->pid)) { if (!gf_filter_pid_is_flush_eos(read->pid)) { + GF_PropertyEntry *pe=NULL; read->input_loaded = GF_TRUE; in_is_eos = GF_TRUE; + //check if aborted info was set, in which case the source was excplicitly canceled by the dash client + //this avoids throwing warnings and errors if we miss a sample + const GF_PropertyValue *p = gf_filter_pid_get_info_str(read->pid, "aborted", &pe); + if (p && p->value.boolean) read->was_aborted = GF_TRUE; + gf_filter_release_property(pe); } else { in_is_flush = GF_TRUE; } @@ -1381,29 +1484,29 @@ if (read->input_is_stop) { read->input_loaded = GF_TRUE; in_is_eos = GF_TRUE; - read->input_is_stop = GF_FALSE; + read->was_aborted = GF_TRUE; } if (!read->frag_type && read->input_loaded) { in_is_eos = GF_TRUE; } - //segment is invalid, wait for eos on input an send eos on all channels - if (read->invalid_segment) { - if (!in_is_eos) return GF_OK; - read->invalid_segment = GF_FALSE; - - for (i=0; i<count; i++) { - ISOMChannel *ch = gf_list_get(read->channels, i); - if (!ch->playing) { - continue; - } - if (!ch->eos_sent) { - ch->eos_sent = 1; - gf_filter_pid_set_eos(ch->pid); - } - } - read->eos_signaled = GF_TRUE; - return GF_EOS; - } + //segment is invalid, wait for eos on input an send eos on all channels + if (read->invalid_segment) { + if (!in_is_eos) return GF_OK; + read->invalid_segment = GF_FALSE; + + for (i=0; i<count; i++) { + ISOMChannel *ch = gf_list_get(read->channels, i); + if (!ch->playing) { + continue; + } + if (!ch->eos_sent) { + ch->eos_sent = 1; + gf_filter_pid_set_eos(ch->pid); + } + } + read->eos_signaled = GF_TRUE; + return GF_EOS; + } } else if (read->extern_mov) { in_is_eos = GF_TRUE; read->input_loaded = GF_TRUE; @@ -1414,6 +1517,8 @@ read->moov_not_loaded = GF_FALSE; return isoffin_setup(filter, read, in_is_eos); } + if (read->in_is_eos) + in_is_eos = GF_TRUE; if (read->refresh_fragmented) { const GF_PropertyValue *prop; @@ -1437,15 +1542,17 @@ e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, new_url); if (e && (e != GF_ISOM_INCOMPLETE_FILE)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("IsoMedia Failed to refresh current segment: %s\n", gf_error_to_string(e) )); + if (!gf_filter_end_of_session(filter)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("IsoMedia Failed to refresh current segment: %s\n", gf_error_to_string(e) )); + } read->refresh_fragmented = GF_FALSE; } else { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("IsoMedia Refreshing current segment at UTC "LLU" - "LLU" bytes still missing - input is EOS %d\n", gf_net_get_utc(), bytesMissing, in_is_eos)); } #endif - if (!read->refresh_fragmented && (e==GF_ISOM_INCOMPLETE_FILE)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("IsoMedia Incomplete Segment received - "LLU" bytes missing but EOF found\n", bytesMissing )); + if (!read->refresh_fragmented && !read->was_aborted && (e==GF_ISOM_INCOMPLETE_FILE) && !gf_filter_end_of_session(filter)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("IsoMedia Incomplete Segment received on PID %s - "LLU" bytes missing but EOF found\n", gf_filter_pid_get_name(read->pid), bytesMissing )); } #ifndef GPAC_DISABLE_LOG @@ -1460,8 +1567,11 @@ if (!read->frag_type) read->refresh_fragmented = GF_FALSE; } + } else { + isor_check_producer_ref_time(read); } + u32 all_pck_sent=0; for (i=0; i<count; i++) { u8 *data; u32 nb_pck=50; @@ -1498,12 +1608,19 @@ isor_reader_release_sample(ch); continue; } + if (ch->sample) { u32 sample_dur; u8 dep_flags; u8 *subs_buf; u32 subs_buf_size; GF_FilterPacket *pck; + + if (!read->keepc && ch->sample->corrupted) { + isor_reader_release_sample(ch); + continue; + } + if (ch->needs_pid_reconfig) { isor_update_channel_config(ch); ch->needs_pid_reconfig = 0; @@ -1542,8 +1659,8 @@ memcpy(data, ch->sample->data, ch->sample->dataLength); } gf_filter_pck_set_dts(pck, ch->dts); - gf_filter_pck_set_cts(pck, ch->cts); - if (ch->sample->IsRAP==-1) { + gf_filter_pck_set_cts(pck, ch->cts + ch->cts_offset); + if (ch->sample->IsRAP==RAP_REDUNDANT) { gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); ch->redundant = 1; } else { @@ -1557,6 +1674,10 @@ gf_filter_pck_set_roll_info(pck, ch->roll); } + if (ch->switch_frame) { + gf_filter_pck_set_switch_frame(pck, GF_TRUE); + } + sample_dur = ch->sample->duration; if (ch->sample->nb_pack) sample_dur *= ch->sample->nb_pack; @@ -1567,6 +1688,20 @@ if (ch->xps_mask && !gf_filter_pck_get_sap(pck) ) { gf_filter_pck_set_property(pck, GF_PROP_PCK_XPS_MASK, &PROP_UINT(ch->xps_mask) ); } + if (!ch->item_id) { + u32 ID, nb_refs=0; + const u32 *refs=NULL; + if (gf_isom_get_sample_references(read->mov, ch->track, ch->sample_num, &ID, &nb_refs, &refs)==GF_OK) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_ID, &PROP_SINT(ID)); + if (refs && nb_refs) { + GF_PropertyValue p; + p.type = GF_PROP_SINT_LIST; + p.value.sint_list.nb_items = nb_refs; + p.value.sint_list.vals = (u32*) refs; + gf_filter_pck_set_property(pck, GF_PROP_PCK_REFS, &p); + } + } + } dep_flags = ch->isLeading; dep_flags <<= 2; @@ -1578,6 +1713,8 @@ if (dep_flags) gf_filter_pck_set_dependency_flags(pck, dep_flags); + if (ch->sample->corrupted) + gf_filter_pck_set_corrupted(pck, GF_TRUE); gf_filter_pck_set_crypt_flags(pck, ch->pck_encrypted ? GF_FILTER_PCK_CRYPT : 0); gf_filter_pck_set_seq_num(pck, ch->sample_num); @@ -1646,7 +1783,7 @@ } gf_filter_pck_send(pck); isor_reader_release_sample(ch); - + all_pck_sent++; ch->last_valid_sample_data_offset = ch->sample_data_offset; if (!in_is_flush) nb_pck--; @@ -1678,16 +1815,15 @@ gf_filter_pid_set_info_str(ch->pid, "smooth_tfrf", NULL); ch->last_has_tfrf = 0; } - gf_filter_pid_set_eos(ch->pid); } break; } else if (ch->last_state==GF_ISOM_INVALID_FILE) { - ch->nb_empty_retry++; if (!ch->eos_sent) { ch->eos_sent = 1; read->eos_signaled = GF_TRUE; gf_filter_pid_set_eos(ch->pid); + ch->playing = GF_FALSE; } return ch->last_state; } else { @@ -1720,8 +1856,8 @@ GF_FEVT_INIT(evt, GF_FEVT_STOP, read->pid); gf_filter_pid_send_event(read->pid, &evt); } - - if (!is_active) { + //if no packet sent and no input pid, return EOS (avoids being resceduled as a source) + if (!is_active || (!all_pck_sent && !read->pid)) { return GF_EOS; } @@ -1783,6 +1919,14 @@ "- fake: allocate sample but no data copy", GF_PROP_UINT, "no", "no|yes|fake", GF_FS_ARG_HINT_EXPERT}, { OFFS(lightp), "load minimal set of properties", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(initseg), "local init segment name when input is a single ISOBMFF segment", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(extk), "allow external track loading", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(extkmov), "original mov pointer for external tracks", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(extkid), "original trackID for external tracks", GF_PROP_UINT, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(ctso), "value to add to CTS offset for tracks using negative ctts\n" + "- set to `-1` to use the `cslg` box info or the minimum cts offset present in the track\n" + "- set to `-2` to use the minimum cts offset present in the track (`cslg` ignored)", GF_PROP_SINT, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(norw), "skip reformatting of samples - should only be used when rewriting fragments", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(keepc), "keep corrupted samples (for multicast sources only)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -1803,7 +1947,7 @@ {0}, //also declare generic file output for embedded files (cover art & co), but explicit to skip this cap in chain resolution CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), - CAP_UINT(GF_CAPS_OUTPUT | GF_CAPFLAG_LOADED_FILTER ,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE) + CAP_UINT(GF_CAPS_OUTPUT | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE) }; GF_FilterRegister ISOFFInRegister = { @@ -1842,7 +1986,8 @@ .configure_pid = isoffin_configure_pid, SETCAPS(ISOFFInCaps), .process_event = isoffin_process_event, - .probe_data = isoffin_probe_data + .probe_data = isoffin_probe_data, + .hint_class_type = GF_FS_CLASS_DEMULTIPLEXER }; const GF_FilterRegister *mp4dmx_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/isoffin_read_ch.c -> gpac-26.02.0.tar.gz/src/filters/isoffin_read_ch.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISOBMFF reader filter @@ -299,7 +299,8 @@ ch->sample->IsRAP = RAP; ch->sample->duration = 1000; ch->dts = ch->cts = 1000 * ch->au_seq_num; - gf_isom_extract_meta_item_mem(ch->owner->mov, GF_TRUE, 0, ch->item_id, &ch->sample->data, &ch->sample->dataLength, &ch->static_sample->alloc_size, NULL, GF_FALSE); + GF_Err e = gf_isom_extract_meta_item_mem(ch->owner->mov, GF_TRUE, 0, ch->item_id, &ch->sample->data, &ch->sample->dataLength, &ch->static_sample->alloc_size, NULL, GF_FALSE); + if ((e<0) && ch->sample) ch->sample->corrupted = GF_TRUE; if (ch->is_encrypted && ch->is_cenc) { isor_update_cenc_info(ch, GF_TRUE); @@ -545,11 +546,15 @@ ch->sample_num--; } else { if (ch->to_init && ch->sample_num) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia Failed to fetch initial sample %d for track %d\n", ch->sample_num, ch->track)); - ch->last_state = GF_ISOM_INVALID_FILE; + if (!ch->owner->was_aborted && !gf_filter_end_of_session(ch->owner->filter)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia Failed to fetch initial sample %d for track %d\n", ch->sample_num, ch->track)); + ch->last_state = GF_ISOM_INVALID_FILE; + } else { + ch->last_state = GF_EOS; + } } else { - if (!ch->eos_sent) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia File truncated, aborting read for track %d\n", ch->track)); + if (!ch->eos_sent && !ch->owner->was_aborted && !gf_filter_end_of_session(ch->owner->filter)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("IsoMedia File truncated, aborting read for track %d after %d / %d samples\n", ch->track, ch->sample_num, sample_count)); } ch->last_state = GF_EOS; } @@ -606,12 +611,16 @@ ch->last_state = GF_OK; ch->sap_3 = GF_FALSE; + ch->switch_frame = GF_FALSE; ch->sap_4_type = 0; ch->roll = 0; if (ch->sample) { gf_isom_get_sample_rap_roll_info(ch->owner->mov, ch->track, ch->sample_num, &ch->sap_3, &ch->sap_4_type, &ch->roll); + GF_Err isom_get_sample_switch_frame(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *switch_frame); + isom_get_sample_switch_frame(ch->owner->mov, ch->track, ch->sample_num, &ch->switch_frame); + /*still seeking or not ? 1- when speed is negative, the RAP found is "after" the seek point in playback order since we used backward RAP search: nothing to do 2- otherwise set DTS+CTS to start value @@ -776,7 +785,7 @@ { s32 ps_id; u32 i, count, state=0; - GF_NALUFFParam *sl; + GF_NALUFFParam *sl, *last_sl = NULL; GF_List *list=NULL; if (ch->avcc) { if (nal_type==GF_AVC_NALU_PIC_PARAM) { @@ -861,8 +870,21 @@ count = gf_list_count(list); for (i=0; i<count; i++) { sl = gf_list_get(list, i); + //ID not set yet, assign it and purge all previous PS in list with same ID if (!sl->id) { sl->id = 1 + isor_ps_get_id(nal_type, sl->data, sl->size, ch->avcc ? 1 : 0); + u32 j; + for (j=0; j<i; j++) { + GF_NALUFFParam *prev_sl = gf_list_get(list, j); + if (prev_sl->id == sl->id) { + gf_list_rem(list, j); + gf_free(prev_sl->data); + gf_free(prev_sl); + j--; + i--; + count--; + } + } } if (sl->id != ps_id) { //reset everything whenever we change ID of seq / vps / dci @@ -890,24 +912,31 @@ else if (!ch->xps_mask) { isor_reset_all_ps(ch); break; + } else { + //in case we have several SPS with same ID in the same AU (...), remember last occurence to avoid reallocating + last_sl = sl; } } ch->xps_mask |= state; *needs_reset = 1; if (list) { - GF_SAFEALLOC(sl, GF_NALUFFParam); - if (!sl) return; - sl->data = gf_malloc(sizeof(char)*size); - memcpy(sl->data, data, size); - sl->size = size; - sl->id = ps_id; - gf_list_add(list, sl); + if (!last_sl) { + GF_SAFEALLOC(sl, GF_NALUFFParam); + if (!sl) return; + sl->data = gf_malloc(sizeof(char)*size); + memcpy(sl->data, data, size); + sl->size = size; + sl->id = ps_id; + gf_list_add(list, sl); + } else { + last_sl->data = gf_realloc(last_sl->data, size); + memcpy(last_sl->data, data, size); + last_sl->size = size; + } } } -u8 key_info_get_iv_size(const u8 *key_info, u32 nb_keys, u32 idx, u8 *const_iv_size, const u8 **const_iv); - void isor_sai_bytes_removed(ISOMChannel *ch, u32 pos, u32 removed) { u32 offset = 0; @@ -936,7 +965,7 @@ idx<<=8; idx |= sai_p1; - mk_iv_size = key_info_get_iv_size(ch->cenc_ki->value.data.ptr, ch->cenc_ki->value.data.size, idx, NULL, NULL); + mk_iv_size = gf_cenc_key_info_get_iv_size(ch->cenc_ki->value.data.ptr, ch->cenc_ki->value.data.size, idx, NULL, NULL); mk_iv_size += 2; //idx if (mk_iv_size > remain) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Invalid multi-key CENC SAI, cannot modify first subsample !\n")); @@ -949,7 +978,7 @@ sub_count_size = 4; //32bit sub count } else { - offset = key_info_get_iv_size(ch->cenc_ki->value.data.ptr, ch->cenc_ki->value.data.size, 1, NULL, NULL); + offset = gf_cenc_key_info_get_iv_size(ch->cenc_ki->value.data.ptr, ch->cenc_ki->value.data.size, 1, NULL, NULL); sub_count_size = 2; //16bit sub count } if (sai_size < offset + sub_count_size) return; @@ -967,14 +996,14 @@ if (sai_size<6) return; u32 clear = ((u32) sai0) << 8 | sai1; - u32 crypt = GF_4CC(sai2, sai3, sai4, sai5); + u32 nb_crypt = GF_4CC(sai2, sai3, sai4, sai5); if (cur_pos + clear > pos) { clear -= removed; sai0 = (clear>>8) & 0xFF; sai1 = (clear) & 0xFF; return; } - cur_pos += clear + crypt; + cur_pos += clear + nb_crypt; sai += 6; sai_size-=6; } @@ -1009,7 +1038,7 @@ return; } //analyze mode, do not rewrite - if (ch->owner->analyze) return; + if (ch->owner->analyze || ch->owner->norw) return; //we cannot touch the payload if encrypted but no SAI buffer if (ch->pck_encrypted && !ch->sai_buffer) @@ -1143,7 +1172,7 @@ switch (grp_type) { case GF_4CC('P','S','S','H'): - gf_filter_pck_set_property(pck, GF_PROP_PID_CENC_PSSH, &PROP_DATA_NO_COPY((u8*)grp_data, grp_size) ); + gf_filter_pck_set_property(pck, GF_PROP_PCK_CENC_PSSH, &PROP_DATA_NO_COPY((u8*)grp_data, grp_size) ); break; default: gf_filter_pck_set_property_dyn(pck, szPName, &PROP_DATA_NO_COPY(grp_data, grp_size) ); @@ -1167,7 +1196,6 @@ gf_filter_pck_set_property_dyn(pck, szPName, &PROP_DATA_NO_COPY(sai_data, sai_size) ); } - while (1) { GF_Err gf_isom_pop_emsg(GF_ISOFile *the_file, u8 **emsg_data, u32 *emsg_size); u8 *data=NULL; @@ -1177,7 +1205,6 @@ gf_filter_pck_set_property_str(pck, "emsg", &PROP_DATA_NO_COPY(data, size)); } - }
View file
gpac-2.4.0.tar.gz/src/filters/jsfilter.c -> gpac-26.02.0.tar.gz/src/filters/jsfilter.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2026 * All rights reserved * * This file is part of GPAC / QuickJS bindings for GF_Filter @@ -88,6 +88,7 @@ JSF_FILTER_SEP_ARGS, JSF_FILTER_SEP_NAME, JSF_FILTER_SEP_LIST, + JSF_FILTER_SRC_ARGS, JSF_FILTER_DST_ARGS, JSF_FILTER_DST_NAME, JSF_FILTER_SINKS_DONE, @@ -154,6 +155,7 @@ JSF_PID_EOS, JSF_PID_EOS_SEEN, JSF_PID_EOS_RECEIVED, + JSF_PID_EOS_IS_FLUSH, JSF_PID_WOULD_BLOCK, JSF_PID_SPARSE, JSF_PID_FILTER_NAME, @@ -233,16 +235,16 @@ static void jsf_filter_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { - GF_JSFilterCtx *jsf = JS_GetOpaque(val, jsf_filter_class_id); - if (jsf) { - u32 i; - for (i=0; i<JSF_EVT_LAST_DEFINED; i++) { - JS_MarkValue(rt, jsf->funcsi, mark_func); + GF_JSFilterCtx *jsf = JS_GetOpaque(val, jsf_filter_class_id); + if (jsf) { + u32 i; + for (i=0; i<JSF_EVT_LAST_DEFINED; i++) { + JS_MarkValue(rt, jsf->funcsi, mark_func); } - } + } } static JSClassDef jsf_filter_class = { - "JSFilter", + "JSFilter", .gc_mark = jsf_filter_mark }; @@ -250,44 +252,70 @@ static void jsf_filter_inst_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { - GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(val, jsf_filter_inst_class_id); - if (f_inst) { + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(val, jsf_filter_inst_class_id); + if (f_inst) { JS_MarkValue(rt, f_inst->setup_failure_fun, mark_func); - } + } } static void jsf_filter_inst_finalizer(JSRuntime *rt, JSValue val) { - GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(val, jsf_filter_inst_class_id); - if (!f_inst) return; - JS_FreeValueRT(rt, f_inst->setup_failure_fun); - gf_free(f_inst); + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(val, jsf_filter_inst_class_id); + if (!f_inst) return; + JS_FreeValueRT(rt, f_inst->setup_failure_fun); + gf_free(f_inst); } static JSClassDef jsf_filter_inst_class = { - "FilterInstance", - .finalizer = jsf_filter_inst_finalizer, + "FilterInstance", + .finalizer = jsf_filter_inst_finalizer, .gc_mark = jsf_filter_inst_mark }; static JSClassID jsf_pid_class_id; static JSClassDef jsf_pid_class = { - "FilterPid", + "FilterPid", }; static JSClassID jsf_event_class_id; -static void jsf_evt_finalizer(JSRuntime *rt, JSValue val) +static void jsf_event_reset(GF_FilterEvent *evt) { - GF_FilterEvent *evt = JS_GetOpaque(val, jsf_event_class_id); - if (!evt) return; - if (evt->base.type==GF_FEVT_USER) { + if (evt->base.type == GF_FEVT_SOURCE_SWITCH) { + if (evt->seek.source_switch) gf_free((char *)evt->seek.source_switch); + evt->seek.source_switch = NULL; + } + else if (evt->base.type == GF_FEVT_SEGMENT_SIZE) { + if (evt->seg_size.seg_url) gf_free((char *)evt->seg_size.seg_url); + evt->seg_size.seg_url = NULL; + } + else if (evt->base.type == GF_FEVT_DASH_QUALITY_SELECT) { + if (evt->dash_select.period_id) gf_free((char *)evt->dash_select.period_id); + evt->dash_select.period_id = NULL; + if (evt->dash_select.rep_id) gf_free((char *)evt->dash_select.rep_id); + evt->dash_select.period_id = NULL; + } + else if (evt->base.type==GF_FEVT_USER) { if (evt->user_event.event.type==GF_EVENT_SET_CAPTION) { - if (evt->user_event.event.caption.caption) - gf_free((char *) evt->user_event.event.caption.caption); + if (evt->user_event.event.caption.caption) gf_free((char*)evt->user_event.event.caption.caption); + evt->user_event.event.caption.caption = NULL; + } + if ((evt->user_event.event.type==GF_EVENT_PASTE_TEXT) + || (evt->user_event.event.type==GF_EVENT_COPY_TEXT) + ) { + if (evt->user_event.event.clipboard.text) gf_free((char*)evt->user_event.event.clipboard.text); + evt->user_event.event.clipboard.text = NULL; } } +} + +static void jsf_evt_finalizer(JSRuntime *rt, JSValue val) +{ + GF_FilterEvent *evt = JS_GetOpaque(val, jsf_event_class_id); + if (!evt) return; + //reset all alocated strings (we dup to avoid leaking of JS_ToCString) + jsf_event_reset(evt); gf_free(evt); } static JSClassDef jsf_event_class = { - "FilterEvent", - .finalizer = jsf_evt_finalizer + "FilterEvent", + .finalizer = jsf_evt_finalizer }; static JSClassID jsf_pck_class_id; @@ -303,8 +331,8 @@ static void jsf_pck_finalizer(JSRuntime *rt, JSValue val) { - GF_JSPckCtx *pckctx = JS_GetOpaque(val, jsf_pck_class_id); - if (!pckctx) return; + GF_JSPckCtx *pckctx = JS_GetOpaque(val, jsf_pck_class_id); + if (!pckctx) return; if (!JS_IsUndefined(pckctx->data_ab)) { JS_FreeValueRT(rt, pckctx->data_ab); @@ -320,7 +348,7 @@ return; } - if (pckctx->jspid) + if (pckctx->jspid) pckctx->jspid->pck_head = NULL; /*we only keep a ref for input packet(s)*/ @@ -328,7 +356,7 @@ JS_FreeValueRT(rt, pckctx->jsobj); - if (JS_IsUndefined(pckctx->ref_val) && pckctx->jspid && pckctx->jspid->jsf) { + if (JS_IsUndefined(pckctx->ref_val) && pckctx->jspid && pckctx->jspid->jsf) { gf_list_add(pckctx->jspid->jsf->pck_res, pckctx); memset(pckctx, 0, sizeof(GF_JSPckCtx)); } @@ -336,48 +364,56 @@ static void jsf_filter_pck_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { - GF_JSPckCtx *pckctx = JS_GetOpaque(val, jsf_pck_class_id); - if (!pckctx) return; + GF_JSPckCtx *pckctx = JS_GetOpaque(val, jsf_pck_class_id); + if (!pckctx) return; if (!(pckctx->flags & (GF_JS_PCK_IS_OUTPUT|GF_JS_PCK_IS_DANGLING))) JS_MarkValue(rt, pckctx->jsobj, mark_func); - if (!JS_IsUndefined(pckctx->ref_val)) { + if (!JS_IsUndefined(pckctx->ref_val)) { JS_MarkValue(rt, pckctx->ref_val, mark_func); } - if (!JS_IsUndefined(pckctx->data_ab)) { + if (!JS_IsUndefined(pckctx->data_ab)) { JS_MarkValue(rt, pckctx->data_ab, mark_func); } } static JSClassDef jsf_pck_class = { - "FilterPacket", - .finalizer = jsf_pck_finalizer, + "FilterPacket", + .finalizer = jsf_pck_finalizer, .gc_mark = jsf_filter_pck_mark }; +GF_FilterSession *jsff_get_session(JSContext *c, JSValue this_val); +#ifndef GPAC_DISABLE_FONTS +struct _gf_ft_mgr *gf_fs_get_font_manager(GF_FilterSession *fsess); +#endif +GF_DownloadManager *gf_fs_get_download_manager(GF_FilterSession *fs); + #ifdef GPAC_USE_DOWNLOADER GF_DownloadManager *jsf_get_download_manager(JSContext *c) { GF_JSFilterCtx *jsf; JSValue global = JS_GetGlobalObject(c); - JSValue filter_obj = JS_GetPropertyStr(c, global, "filter"); + JSValue obj = JS_GetPropertyStr(c, global, "filter"); JS_FreeValue(c, global); - if (JS_IsNull(filter_obj) || JS_IsException(filter_obj)) return NULL; - jsf = JS_GetOpaque(filter_obj, jsf_filter_class_id); - JS_FreeValue(c, filter_obj); - if (!jsf) return NULL; - return gf_filter_get_download_manager(jsf->filter); + if (JS_IsNull(obj) || JS_IsException(obj)) return NULL; + jsf = JS_GetOpaque(obj, jsf_filter_class_id); + JS_FreeValue(c, obj); + if (jsf) return gf_filter_get_download_manager(jsf->filter); + + obj = JS_GetPropertyStr(c, global, "session"); + if (JS_IsNull(obj) || JS_IsException(obj)) return NULL; + GF_FilterSession *fs = jsff_get_session(c, obj); + JS_FreeValue(c, obj); + if (fs) return gf_fs_get_download_manager(fs); + + return NULL; } #endif //GPAC_USE_DOWNLOADER -#ifndef GPAC_DISABLE_FONTS -GF_FilterSession *jsff_get_session(JSContext *c, JSValue this_val); -struct _gf_ft_mgr *gf_fs_get_font_manager(GF_FilterSession *fsess); -#endif - struct _gf_ft_mgr *jsf_get_font_manager(JSContext *c) { #ifndef GPAC_DISABLE_FONTS @@ -573,19 +609,19 @@ case GF_PROP_UINT_LIST: res = JS_NewArray(ctx); for (i=0; i<new_val->value.uint_list.nb_items; i++) { - JS_SetPropertyUint32(ctx, res, i, JS_NewInt64(ctx, new_val->value.uint_list.valsi) ); + JS_SetPropertyUint32(ctx, res, i, JS_NewInt64(ctx, new_val->value.uint_list.valsi) ); } return res; case GF_PROP_4CC_LIST: res = JS_NewArray(ctx); for (i=0; i<new_val->value.uint_list.nb_items; i++) { - JS_SetPropertyUint32(ctx, res, i, JS_NewString(ctx, gf_4cc_to_str(new_val->value.uint_list.valsi) ) ); + JS_SetPropertyUint32(ctx, res, i, JS_NewString(ctx, gf_4cc_to_str(new_val->value.uint_list.valsi) ) ); } return res; case GF_PROP_SINT_LIST: res = JS_NewArray(ctx); for (i=0; i<new_val->value.sint_list.nb_items; i++) { - JS_SetPropertyUint32(ctx, res, i, JS_NewInt32(ctx, new_val->value.sint_list.valsi) ); + JS_SetPropertyUint32(ctx, res, i, JS_NewInt32(ctx, new_val->value.sint_list.valsi) ); } return res; case GF_PROP_VEC2I_LIST: @@ -594,13 +630,13 @@ JSValue item = JS_NewObject(ctx); JS_SetPropertyStr(ctx, item, "x", JS_NewInt32(ctx, new_val->value.v2i_list.valsi.x)); JS_SetPropertyStr(ctx, item, "y", JS_NewInt32(ctx, new_val->value.v2i_list.valsi.y)); - JS_SetPropertyUint32(ctx, res, i, item); + JS_SetPropertyUint32(ctx, res, i, item); } return res; case GF_PROP_STRING_LIST: res = JS_NewArray(ctx); for (i=0; i<new_val->value.string_list.nb_items; i++) { - JS_SetPropertyUint32(ctx, res, i, JS_NewString(ctx, new_val->value.string_list.valsi ) ); + JS_SetPropertyUint32(ctx, res, i, JS_NewString(ctx, new_val->value.string_list.valsi ) ); } return res; case GF_PROP_DATA: @@ -736,8 +772,10 @@ const char *str = JS_ToCString(ctx, v); prop->value.string_list.valsi = gf_strdup(str); JS_FreeCString(ctx, str); + prop->type = GF_PROP_STRING_LIST; } else { JS_ToInt32(ctx, &prop->value.uint_list.valsi, v); + prop->type = GF_PROP_UINT_LIST; } JS_FreeValue(ctx, v); } @@ -829,11 +867,23 @@ } } else if (is_frac) { if (is_frac==2) { - prop->type = GF_PROP_FRACTION; - prop->value.frac = frac; + if (type==GF_PROP_FRACTION64) { + prop->type = GF_PROP_FRACTION64; + prop->value.lfrac.num = frac.num; + prop->value.lfrac.den = frac.den; + } else { + prop->type = GF_PROP_FRACTION; + prop->value.frac = frac; + } } else { - prop->type = GF_PROP_FRACTION64; - prop->value.lfrac = frac_l; + if (type==GF_PROP_FRACTION) { + prop->type = GF_PROP_FRACTION; + prop->value.frac.num = (s32) frac_l.num; + prop->value.frac.den = (u32) frac_l.den; + } else { + prop->type = GF_PROP_FRACTION64; + prop->value.lfrac = frac_l; + } } } //try array buffer @@ -896,7 +946,7 @@ JS_FreeValue(ctx, jsf->funcsmagic); jsf->funcsmagic = JS_DupValue(ctx, value); } - return JS_UNDEFINED; + return JS_UNDEFINED; } switch (magic) { case JSF_FILTER_MAX_PIDS: @@ -962,7 +1012,7 @@ gf_filter_require_source_id(jsf->filter); break; } - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_filter_prop_get(JSContext *ctx, JSValueConst this_val, int magic) @@ -985,39 +1035,42 @@ case JSF_FILTER_MAX_PIDS: return JS_NewInt32(ctx, gf_filter_get_max_extra_input_pids(jsf->filter)); case JSF_FILTER_BLOCK_ENABLED: - return JS_NewBool(jsf->ctx, gf_filter_block_enabled(jsf->filter)); + return JS_NewBool(jsf->ctx, gf_filter_block_enabled(jsf->filter)); case JSF_FILTER_OUTPUT_BUFFER: gf_filter_get_output_buffer_max(jsf->filter, &ival, NULL); - return JS_NewInt32(jsf->ctx, ival); + return JS_NewInt32(jsf->ctx, ival); case JSF_FILTER_OUTPUT_PLAYOUT: gf_filter_get_output_buffer_max(jsf->filter, NULL, &ival); - return JS_NewInt32(jsf->ctx, ival); + return JS_NewInt32(jsf->ctx, ival); case JSF_FILTER_SEP_ARGS: szSep1=0; szSep0 = gf_filter_get_sep(jsf->filter, GF_FS_SEP_ARGS); - return JS_NewString(jsf->ctx, szSep); + return JS_NewString(jsf->ctx, szSep); case JSF_FILTER_SEP_NAME: szSep1=0; szSep0 = gf_filter_get_sep(jsf->filter, GF_FS_SEP_NAME); - return JS_NewString(jsf->ctx, szSep); + return JS_NewString(jsf->ctx, szSep); case JSF_FILTER_SEP_LIST: szSep1=0; szSep0 = gf_filter_get_sep(jsf->filter, GF_FS_SEP_LIST); - return JS_NewString(jsf->ctx, szSep); + return JS_NewString(jsf->ctx, szSep); + case JSF_FILTER_SRC_ARGS: + str = (char *) gf_filter_get_src_args(jsf->filter); + return str ? JS_NewString(jsf->ctx, str) : JS_NULL; case JSF_FILTER_DST_ARGS: str = (char *) gf_filter_get_dst_args(jsf->filter); - return str ? JS_NewString(jsf->ctx, str) : JS_NULL; + return str ? JS_NewString(jsf->ctx, str) : JS_NULL; case JSF_FILTER_DST_NAME: str = gf_filter_get_dst_name(jsf->filter); res = str ? JS_NewString(jsf->ctx, str) : JS_NULL; if (str) gf_free(str); return res; case JSF_FILTER_SINKS_DONE: - return JS_NewBool(jsf->ctx, gf_filter_all_sinks_done(jsf->filter) ); + return JS_NewBool(jsf->ctx, gf_filter_all_sinks_done(jsf->filter) ); case JSF_FILTER_REPORTING_ENABLED: - return JS_NewBool(jsf->ctx, gf_filter_reporting_enabled(jsf->filter) ); + return JS_NewBool(jsf->ctx, gf_filter_reporting_enabled(jsf->filter) ); case JSF_FILTER_CAPS_MAX_WIDTH: gf_filter_get_session_caps(jsf->filter, &caps); @@ -1080,7 +1133,7 @@ } } - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_filter_set_arg(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -1094,7 +1147,7 @@ u32 type = 0; Bool is_wildcard=GF_FALSE; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); v = JS_GetPropertyStr(ctx, argv0, "name"); if (!JS_IsUndefined(v)) name = JS_ToCString(ctx, v); @@ -1166,7 +1219,7 @@ JS_FreeCString(ctx, desc); JS_FreeCString(ctx, def); JS_FreeCString(ctx, min_enum); - return JS_UNDEFINED; + return JS_UNDEFINED; } @@ -1181,13 +1234,13 @@ Bool is_optional=GF_FALSE; GF_PropertyValue p; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); jsf->disable_filter = GF_FALSE; memset(&p, 0, sizeof(GF_PropertyValue)); - if (argc) { + if (argc) { u32 prop_type; - JSValue v; + JSValue v; const char *name = NULL; v = JS_GetPropertyStr(ctx, argv0, "id"); @@ -1231,28 +1284,39 @@ } - jsf->caps = gf_realloc(jsf->caps, sizeof(GF_FilterCapability)*(jsf->nb_caps+1)); - memset(&jsf->capsjsf->nb_caps, 0, sizeof(GF_FilterCapability)); + u32 flags = 0; if (prop_id) { - GF_FilterCapability *cap = &jsf->capsjsf->nb_caps; - cap->code = prop_id; - cap->val = p; - cap->flags = GF_CAPFLAG_IN_BUNDLE; + flags = GF_CAPFLAG_IN_BUNDLE; if (is_inputoutput) { - cap->flags |= GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT; + flags |= GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT; } else if (is_output) { - cap->flags |= GF_CAPFLAG_OUTPUT; + flags |= GF_CAPFLAG_OUTPUT; } else { - cap->flags |= GF_CAPFLAG_INPUT; + flags |= GF_CAPFLAG_INPUT; } if (is_excluded) - cap->flags |= GF_CAPFLAG_EXCLUDED; + flags |= GF_CAPFLAG_EXCLUDED; if (is_static) - cap->flags |= GF_CAPFLAG_STATIC; + flags |= GF_CAPFLAG_STATIC; if (is_loaded_filter_only) - cap->flags |= GF_CAPFLAG_LOADED_FILTER; + flags |= GF_CAPFLAG_LOADED_FILTER; if (is_optional) - cap->flags |= GF_CAPFLAG_OPTIONAL; + flags |= GF_CAPFLAG_OPTIONAL; + } + + if (jsf->filter->freg->flags & GF_FS_REG_CUSTOM) { + GF_Err e = gf_filter_push_caps(jsf->filter, prop_id, &p, NULL, flags, 0); + if (e) return GF_JS_EXCEPTION(ctx); + return JS_UNDEFINED; + } + + jsf->caps = gf_realloc(jsf->caps, sizeof(GF_FilterCapability)*(jsf->nb_caps+1)); + memset(&jsf->capsjsf->nb_caps, 0, sizeof(GF_FilterCapability)); + if (prop_id) { + GF_FilterCapability *cap = &jsf->capsjsf->nb_caps; + cap->code = prop_id; + cap->val = p; + cap->flags = flags; } jsf->nb_caps ++; gf_filter_override_caps(jsf->filter, jsf->caps, jsf->nb_caps); @@ -1262,67 +1326,77 @@ static JSValue jsf_filter_set_desc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); const char *str = JS_ToCString(ctx, argv0); - if (!str) return GF_JS_EXCEPTION(ctx); + if (!str) return GF_JS_EXCEPTION(ctx); gf_filter_set_description(jsf->filter, str); JS_FreeCString(ctx, str); - return JS_UNDEFINED; + return JS_UNDEFINED; +} + +static JSValue jsf_filter_set_class_hint(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return GF_JS_EXCEPTION(ctx); + u32 hint; + JS_ToInt32(ctx, &hint, argv0); + gf_filter_set_class_hint(jsf->filter, hint); + return JS_UNDEFINED; } static JSValue jsf_filter_set_version(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); const char *str = JS_ToCString(ctx, argv0); - if (!str) return GF_JS_EXCEPTION(ctx); + if (!str) return GF_JS_EXCEPTION(ctx); gf_filter_set_version(jsf->filter, str); JS_FreeCString(ctx, str); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_filter_set_author(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); const char *str = JS_ToCString(ctx, argv0); - if (!str) return GF_JS_EXCEPTION(ctx); + if (!str) return GF_JS_EXCEPTION(ctx); gf_filter_set_author(jsf->filter, str); JS_FreeCString(ctx, str); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_filter_set_help(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); const char *str = JS_ToCString(ctx, argv0); - if (!str) return GF_JS_EXCEPTION(ctx); + if (!str) return GF_JS_EXCEPTION(ctx); gf_filter_set_help(jsf->filter, str); JS_FreeCString(ctx, str); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_filter_set_name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); - if (jsf->log_name) gf_free(jsf->log_name); - jsf->log_name = NULL; - if (argc) { - JSValue global; + if (!jsf) return GF_JS_EXCEPTION(ctx); + if (jsf->log_name) gf_free(jsf->log_name); + jsf->log_name = NULL; + if (argc) { + JSValue global; const char *str = JS_ToCString(ctx, argv0); if (!str) return GF_JS_EXCEPTION(ctx); - jsf->log_name = gf_strdup(str); + jsf->log_name = gf_strdup(str); JS_FreeCString(ctx, str); gf_filter_set_name(jsf->filter, jsf->log_name); global = JS_GetGlobalObject(ctx); - JS_SetPropertyStr(ctx, global, "_gpac_log_name", JS_NewString(ctx, jsf->log_name)); - JS_FreeValue(ctx, global); - } - return JS_UNDEFINED; + JS_SetPropertyStr(ctx, global, "_gpac_log_name", JS_NewString(ctx, jsf->log_name)); + JS_FreeValue(ctx, global); + } + return JS_UNDEFINED; } @@ -1330,8 +1404,8 @@ { GF_JSPidCtx *pctx; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); - GF_FilterPid *opid = gf_filter_pid_new(jsf->filter); + if (!jsf) return GF_JS_EXCEPTION(ctx); + GF_FilterPid *opid = gf_filter_pid_new(jsf->filter); if (!opid) return GF_JS_EXCEPTION(ctx); jsf->disable_filter = GF_FALSE; @@ -1354,10 +1428,10 @@ Bool upstream = GF_FALSE; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!jsf && !jsfi) return GF_JS_EXCEPTION(ctx); + if (!jsf && !jsfi) return GF_JS_EXCEPTION(ctx); GF_FilterEvent *evt = JS_GetOpaque(argv0, jsf_event_class_id); - if (!evt) return GF_JS_EXCEPTION(ctx); - if (argc>1) { + if (!evt) return GF_JS_EXCEPTION(ctx); + if (argc>1) { upstream = JS_ToBool(ctx, argv1); } @@ -1365,7 +1439,7 @@ gf_filter_send_event(jsf->filter, evt, upstream); else gf_filter_send_event(jsfi->filter, evt, upstream); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_filter_get_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -1377,8 +1451,8 @@ GF_PropertyEntry *pe = NULL; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!jsf && !jsfi) return GF_JS_EXCEPTION(ctx); - name = JS_ToCString(ctx, argv0); + if (!jsf && !jsfi) return GF_JS_EXCEPTION(ctx); + name = JS_ToCString(ctx, argv0); if (!name) return GF_JS_EXCEPTION(ctx); filter = jsf ? jsf->filter : jsfi->filter; @@ -1398,7 +1472,7 @@ res = jsf_NewPropTranslate(ctx, prop, p4cc); } gf_filter_release_property(pe); - return res; + return res; } @@ -1408,8 +1482,8 @@ const char *mime=NULL; JSValue res; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); - mime = JS_ToCString(ctx, argv0); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + mime = JS_ToCString(ctx, argv0); if (!mime) return GF_JS_EXCEPTION(ctx); res = JS_NewBool(ctx, gf_filter_is_supported_mime(jsf->filter, mime)); JS_FreeCString(ctx, mime); @@ -1422,8 +1496,8 @@ const char *parent=NULL; JSValue res; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); - src = JS_ToCString(ctx, argv0); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + src = JS_ToCString(ctx, argv0); if (!src) return GF_JS_EXCEPTION(ctx); if (argc>1) { parent = JS_ToCString(ctx, argv1); @@ -1440,26 +1514,26 @@ static JSValue jsf_filter_update_status(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - const char *status; - u32 pc=0; + const char *status; + u32 pc=0; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || (argc<1)) return GF_JS_EXCEPTION(ctx); + if (!jsf || (argc<1)) return GF_JS_EXCEPTION(ctx); - status = JS_ToCString(ctx, argv0); + status = JS_ToCString(ctx, argv0); if (argc>1) JS_ToInt32(ctx, &pc, argv1); - gf_filter_update_status(jsf->filter, pc, (char*) status); + gf_filter_update_status(jsf->filter, pc, (char*) status); JS_FreeCString(ctx, status); return JS_UNDEFINED; } static JSValue jsf_filter_reschedule_in(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - u32 rt_delay=0; + u32 rt_delay=0; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); - if (argc) { + if (argc) { if (JS_ToInt32(ctx, &rt_delay, argv0)) return GF_JS_EXCEPTION(ctx); @@ -1517,7 +1591,7 @@ { JS_ScriptTask *task; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); jsf->disable_filter = GF_FALSE; if (!JS_IsFunction(ctx, argv0)) return GF_JS_EXCEPTION(ctx); @@ -1542,7 +1616,7 @@ GF_Err e; s32 error_type=0; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); if (JS_ToInt32(ctx, (int *) &e, argv0)) return GF_JS_EXCEPTION(ctx); @@ -1564,7 +1638,7 @@ s64 time_in_us=0; GF_Fraction64 media_timestamp; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || (argc<2)) return GF_JS_EXCEPTION(ctx); + if (!jsf || (argc<2)) return GF_JS_EXCEPTION(ctx); if (JS_ToInt64(ctx, &time_in_us, argv0)) return GF_JS_EXCEPTION(ctx); if (argc==2) { @@ -1592,7 +1666,7 @@ JSValue ret = JS_UNDEFINED; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if ((!jsf && !jsfi) || (argc!=4)) return GF_JS_EXCEPTION(ctx); + if ((!jsf && !jsfi) || (argc!=4)) return GF_JS_EXCEPTION(ctx); filter_id = JS_ToCString(ctx, argv0); arg_name = JS_ToCString(ctx, argv1); @@ -1621,8 +1695,8 @@ Bool inherit_args = GF_FALSE; GF_JSFilterInstanceCtx *f_ctx; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); - url = JS_ToCString(ctx, argv0); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + url = JS_ToCString(ctx, argv0); if (!url) return GF_JS_EXCEPTION(ctx); if ((mode==JSF_FINST_SOURCE) && (argc>1)) { @@ -1681,8 +1755,8 @@ { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); Bool do_lock; - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); - do_lock = JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE; + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + do_lock = JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE; gf_filter_lock(jsf->filter, do_lock); return JS_UNDEFINED; } @@ -1690,29 +1764,29 @@ { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); Bool do_lock; - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); - do_lock = JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE; + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + do_lock = JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE; gf_filter_lock_all(jsf->filter, do_lock); return JS_UNDEFINED; } static JSValue jsf_filter_make_sticky(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); gf_filter_make_sticky(jsf->filter); return JS_UNDEFINED; } static JSValue jsf_filter_prevent_blocking(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); gf_filter_prevent_blocking(jsf->filter, JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE); return JS_UNDEFINED; } static JSValue jsf_filter_block_eos(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); + if (!jsf || !argc) return GF_JS_EXCEPTION(ctx); gf_filter_block_eos(jsf->filter, JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE); return JS_UNDEFINED; } @@ -1721,11 +1795,12 @@ static JSValue jsf_filter_abort(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); - if (!jsf) return GF_JS_EXCEPTION(ctx); + if (!jsf) return GF_JS_EXCEPTION(ctx); gf_filter_abort(jsf->filter); return JS_UNDEFINED; } +GF_Filter *jsff_get_filter(JSContext *c, JSValue this_val); static JSValue jsf_filter_set_source_internal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool use_restricted) @@ -1734,26 +1809,36 @@ const char *source_id=NULL; GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!jsf && !jsfi) return GF_JS_EXCEPTION(ctx); + if (!jsf && !jsfi) return GF_JS_EXCEPTION(ctx); + + GF_Filter *src = NULL; + if (!src) { + GF_JSFilterCtx *f_from = JS_GetOpaque(argv0, jsf_filter_class_id); + if (f_from) src = f_from->filter; + } + if (!src) { + GF_JSFilterInstanceCtx *fi_from = JS_GetOpaque(argv0, jsf_filter_inst_class_id); + if (fi_from) src = fi_from->filter; + } + if (!src) { + src = jsff_get_filter(ctx, argv0); + } - GF_JSFilterCtx *f_from = JS_GetOpaque(argv0, jsf_filter_class_id); - GF_JSFilterInstanceCtx *fi_from = JS_GetOpaque(argv0, jsf_filter_inst_class_id); - if (!f_from && !fi_from) return GF_JS_EXCEPTION(ctx); + if (!src) return GF_JS_EXCEPTION(ctx); - source_id = NULL; - if (argc>1) { + source_id = NULL; + if (argc>1) { source_id = JS_ToCString(ctx, argv1); - if (!source_id) return GF_JS_EXCEPTION(ctx); - if (!source_id0) { + if (source_id && !source_id0) { JS_FreeCString(ctx, source_id); source_id = NULL; } } if (use_restricted) - e = gf_filter_set_source_restricted(jsfi ? jsfi->filter : jsf->filter, fi_from ? fi_from->filter : f_from->filter, source_id); + e = gf_filter_set_source_restricted(jsfi ? jsfi->filter : jsf->filter, src, source_id); else - e = gf_filter_set_source(jsfi ? jsfi->filter : jsf->filter, fi_from ? fi_from->filter : f_from->filter, source_id); + e = gf_filter_set_source(jsfi ? jsfi->filter : jsf->filter, src, source_id); JS_FreeCString(ctx, source_id); if (e) return js_throw_err(ctx, e); @@ -1790,74 +1875,76 @@ static const JSCFunctionListEntry jsf_filter_funcs = { - JS_CGETSET_MAGIC_DEF("initialize", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_INITIALIZE), - JS_CGETSET_MAGIC_DEF("finalize", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_FINALIZE), - JS_CGETSET_MAGIC_DEF("configure_pid", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_CONFIGURE_PID), - JS_CGETSET_MAGIC_DEF("process", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROCESS), - JS_CGETSET_MAGIC_DEF("process_event", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROCESS_EVENT), - JS_CGETSET_MAGIC_DEF("update_arg", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_UPDATE_ARG), - JS_CGETSET_MAGIC_DEF("probe_data", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROBE_DATA), - JS_CGETSET_MAGIC_DEF("probe_url", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROBE_URL), - JS_CGETSET_MAGIC_DEF("reconfigure_output", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_RECONFIGURE_OUTPUT), - JS_CGETSET_MAGIC_DEF("remove_pid", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_REMOVE_PID), - JS_CGETSET_MAGIC_DEF("max_pids", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_MAX_PIDS), - JS_CGETSET_MAGIC_DEF("block_enabled", jsf_filter_prop_get, NULL, JSF_FILTER_BLOCK_ENABLED), - JS_CGETSET_MAGIC_DEF("output_buffer", jsf_filter_prop_get, NULL, JSF_FILTER_OUTPUT_BUFFER), - JS_CGETSET_MAGIC_DEF("output_playout", jsf_filter_prop_get, NULL, JSF_FILTER_OUTPUT_PLAYOUT), - JS_CGETSET_MAGIC_DEF("sep_args", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_ARGS), - JS_CGETSET_MAGIC_DEF("sep_name", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_NAME), - JS_CGETSET_MAGIC_DEF("sep_list", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_LIST), - JS_CGETSET_MAGIC_DEF("dst_args", jsf_filter_prop_get, NULL, JSF_FILTER_DST_ARGS), - JS_CGETSET_MAGIC_DEF("dst_name", jsf_filter_prop_get, NULL, JSF_FILTER_DST_NAME), - JS_CGETSET_MAGIC_DEF("sinks_done", jsf_filter_prop_get, NULL, JSF_FILTER_SINKS_DONE), - JS_CGETSET_MAGIC_DEF("reports_on", jsf_filter_prop_get, NULL, JSF_FILTER_REPORTING_ENABLED), - JS_CGETSET_MAGIC_DEF("max_screen_width", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_WIDTH), - JS_CGETSET_MAGIC_DEF("max_screen_height", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_HEIGHT), - JS_CGETSET_MAGIC_DEF("max_screen_depth", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_DISPLAY_DEPTH), - JS_CGETSET_MAGIC_DEF("max_screen_fps", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_FPS), - JS_CGETSET_MAGIC_DEF("max_screen_views", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_VIEWS), - JS_CGETSET_MAGIC_DEF("max_audio_channels", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_CHANNELS), - JS_CGETSET_MAGIC_DEF("max_audio_samplerate", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_SAMPLERATE), - JS_CGETSET_MAGIC_DEF("max_audio_depth", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_AUDIO_DEPTH), - JS_CGETSET_MAGIC_DEF("events_queued", jsf_filter_prop_get, NULL, JSF_FILTER_NB_EVTS_QUEUED), - JS_CGETSET_MAGIC_DEF("clock_hint_us", jsf_filter_prop_get, NULL, JSF_FILTER_CLOCK_HINT_TIME), - JS_CGETSET_MAGIC_DEF("clock_hint_mediatime", jsf_filter_prop_get, NULL, JSF_FILTER_CLOCK_HINT_MEDIATIME), - JS_CGETSET_MAGIC_DEF("connections_pending", jsf_filter_prop_get, NULL, JSF_FILTER_CONNECTIONS_PENDING), + JS_CGETSET_MAGIC_DEF("initialize", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_INITIALIZE), + JS_CGETSET_MAGIC_DEF("finalize", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_FINALIZE), + JS_CGETSET_MAGIC_DEF("configure_pid", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_CONFIGURE_PID), + JS_CGETSET_MAGIC_DEF("process", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROCESS), + JS_CGETSET_MAGIC_DEF("process_event", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROCESS_EVENT), + JS_CGETSET_MAGIC_DEF("update_arg", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_UPDATE_ARG), + JS_CGETSET_MAGIC_DEF("probe_data", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROBE_DATA), + JS_CGETSET_MAGIC_DEF("probe_url", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROBE_URL), + JS_CGETSET_MAGIC_DEF("reconfigure_output", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_RECONFIGURE_OUTPUT), + JS_CGETSET_MAGIC_DEF("remove_pid", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_REMOVE_PID), + JS_CGETSET_MAGIC_DEF("max_pids", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_MAX_PIDS), + JS_CGETSET_MAGIC_DEF("block_enabled", jsf_filter_prop_get, NULL, JSF_FILTER_BLOCK_ENABLED), + JS_CGETSET_MAGIC_DEF("output_buffer", jsf_filter_prop_get, NULL, JSF_FILTER_OUTPUT_BUFFER), + JS_CGETSET_MAGIC_DEF("output_playout", jsf_filter_prop_get, NULL, JSF_FILTER_OUTPUT_PLAYOUT), + JS_CGETSET_MAGIC_DEF("sep_args", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_ARGS), + JS_CGETSET_MAGIC_DEF("sep_name", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_NAME), + JS_CGETSET_MAGIC_DEF("sep_list", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_LIST), + JS_CGETSET_MAGIC_DEF("src_args", jsf_filter_prop_get, NULL, JSF_FILTER_SRC_ARGS), + JS_CGETSET_MAGIC_DEF("dst_args", jsf_filter_prop_get, NULL, JSF_FILTER_DST_ARGS), + JS_CGETSET_MAGIC_DEF("dst_name", jsf_filter_prop_get, NULL, JSF_FILTER_DST_NAME), + JS_CGETSET_MAGIC_DEF("sinks_done", jsf_filter_prop_get, NULL, JSF_FILTER_SINKS_DONE), + JS_CGETSET_MAGIC_DEF("reports_on", jsf_filter_prop_get, NULL, JSF_FILTER_REPORTING_ENABLED), + JS_CGETSET_MAGIC_DEF("max_screen_width", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_WIDTH), + JS_CGETSET_MAGIC_DEF("max_screen_height", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_HEIGHT), + JS_CGETSET_MAGIC_DEF("max_screen_depth", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_DISPLAY_DEPTH), + JS_CGETSET_MAGIC_DEF("max_screen_fps", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_FPS), + JS_CGETSET_MAGIC_DEF("max_screen_views", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_VIEWS), + JS_CGETSET_MAGIC_DEF("max_audio_channels", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_CHANNELS), + JS_CGETSET_MAGIC_DEF("max_audio_samplerate", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_SAMPLERATE), + JS_CGETSET_MAGIC_DEF("max_audio_depth", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_AUDIO_DEPTH), + JS_CGETSET_MAGIC_DEF("events_queued", jsf_filter_prop_get, NULL, JSF_FILTER_NB_EVTS_QUEUED), + JS_CGETSET_MAGIC_DEF("clock_hint_us", jsf_filter_prop_get, NULL, JSF_FILTER_CLOCK_HINT_TIME), + JS_CGETSET_MAGIC_DEF("clock_hint_mediatime", jsf_filter_prop_get, NULL, JSF_FILTER_CLOCK_HINT_MEDIATIME), + JS_CGETSET_MAGIC_DEF("connections_pending", jsf_filter_prop_get, NULL, JSF_FILTER_CONNECTIONS_PENDING), JS_CGETSET_MAGIC_DEF("iname", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_INAME), - JS_CGETSET_MAGIC_DEF("require_source_id", NULL, jsf_filter_prop_set, JSF_FILTER_REQUIRE_SOURCEID), + JS_CGETSET_MAGIC_DEF("require_source_id", NULL, jsf_filter_prop_set, JSF_FILTER_REQUIRE_SOURCEID), JS_CGETSET_MAGIC_DEF("jspath", jsf_filter_prop_get, NULL, JSF_FILTER_PATH), - JS_CFUNC_DEF("set_desc", 0, jsf_filter_set_desc), - JS_CFUNC_DEF("set_version", 0, jsf_filter_set_version), - JS_CFUNC_DEF("set_author", 0, jsf_filter_set_author), - JS_CFUNC_DEF("set_help", 0, jsf_filter_set_help), - JS_CFUNC_DEF("set_arg", 0, jsf_filter_set_arg), - JS_CFUNC_DEF("set_cap", 0, jsf_filter_set_cap), - JS_CFUNC_DEF("set_name", 0, jsf_filter_set_name), - JS_CFUNC_DEF("new_pid", 0, jsf_filter_new_pid), - JS_CFUNC_DEF("send_event", 0, jsf_filter_send_event), - JS_CFUNC_DEF("get_info", 0, jsf_filter_get_info), - JS_CFUNC_DEF("is_supported_mime", 0, jsf_filter_is_supported_mime), - JS_CFUNC_DEF("update_status", 0, jsf_filter_update_status), - JS_CFUNC_DEF("reschedule", 0, jsf_filter_reschedule_in), - JS_CFUNC_DEF("post_task", 0, jsf_filter_post_task), - JS_CFUNC_DEF("notify_failure", 0, jsf_filter_notify_failure), - JS_CFUNC_DEF("is_supported_source", 0, jsf_filter_is_supported_source), - JS_CFUNC_DEF("hint_clock", 0, jsf_filter_hint_clock), - JS_CFUNC_DEF("send_update", 0, jsf_filter_send_update), - JS_CFUNC_DEF("add_source", 0, jsf_filter_add_source), - JS_CFUNC_DEF("add_destination", 0, jsf_filter_add_dest), - JS_CFUNC_DEF("add_filter", 0, jsf_filter_add_filter), - JS_CFUNC_DEF("lock", 0, jsf_filter_lock), - JS_CFUNC_DEF("lock_all", 0, jsf_filter_lock_all), - JS_CFUNC_DEF("make_sticky", 0, jsf_filter_make_sticky), + JS_CFUNC_DEF("set_desc", 0, jsf_filter_set_desc), + JS_CFUNC_DEF("set_version", 0, jsf_filter_set_version), + JS_CFUNC_DEF("set_author", 0, jsf_filter_set_author), + JS_CFUNC_DEF("set_help", 0, jsf_filter_set_help), + JS_CFUNC_DEF("set_class_hint", 0, jsf_filter_set_class_hint), + JS_CFUNC_DEF("set_arg", 0, jsf_filter_set_arg), + JS_CFUNC_DEF("set_cap", 0, jsf_filter_set_cap), + JS_CFUNC_DEF("set_name", 0, jsf_filter_set_name), + JS_CFUNC_DEF("new_pid", 0, jsf_filter_new_pid), + JS_CFUNC_DEF("send_event", 0, jsf_filter_send_event), + JS_CFUNC_DEF("get_info", 0, jsf_filter_get_info), + JS_CFUNC_DEF("is_supported_mime", 0, jsf_filter_is_supported_mime), + JS_CFUNC_DEF("update_status", 0, jsf_filter_update_status), + JS_CFUNC_DEF("reschedule", 0, jsf_filter_reschedule_in), + JS_CFUNC_DEF("post_task", 0, jsf_filter_post_task), + JS_CFUNC_DEF("notify_failure", 0, jsf_filter_notify_failure), + JS_CFUNC_DEF("is_supported_source", 0, jsf_filter_is_supported_source), + JS_CFUNC_DEF("hint_clock", 0, jsf_filter_hint_clock), + JS_CFUNC_DEF("send_update", 0, jsf_filter_send_update), + JS_CFUNC_DEF("add_source", 0, jsf_filter_add_source), + JS_CFUNC_DEF("add_destination", 0, jsf_filter_add_dest), + JS_CFUNC_DEF("add_filter", 0, jsf_filter_add_filter), + JS_CFUNC_DEF("lock", 0, jsf_filter_lock), + JS_CFUNC_DEF("lock_all", 0, jsf_filter_lock_all), + JS_CFUNC_DEF("make_sticky", 0, jsf_filter_make_sticky), JS_CFUNC_DEF("prevent_blocking", 1, jsf_filter_prevent_blocking), JS_CFUNC_DEF("block_eos", 1, jsf_filter_block_eos), - JS_CFUNC_DEF("abort", 0, jsf_filter_abort), - JS_CFUNC_DEF("set_source", 0, jsf_filter_set_source), - JS_CFUNC_DEF("set_source_restricted", 0, jsf_filter_set_source_restricted), - JS_CFUNC_DEF("reset_source", 0, jsf_filter_reset_source), - JS_CFUNC_DEF("set_blocking", 0, jsf_filter_set_blocking), + JS_CFUNC_DEF("abort", 0, jsf_filter_abort), + JS_CFUNC_DEF("set_source", 0, jsf_filter_set_source), + JS_CFUNC_DEF("set_source_restricted", 0, jsf_filter_set_source_restricted), + JS_CFUNC_DEF("reset_source", 0, jsf_filter_reset_source), + JS_CFUNC_DEF("set_blocking", 0, jsf_filter_set_blocking), }; @@ -1920,7 +2007,7 @@ static JSValue jsf_filter_inst_prop_get(JSContext *ctx, JSValueConst this_val, int magic) { GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!f_inst) return GF_JS_EXCEPTION(ctx); + if (!f_inst) return GF_JS_EXCEPTION(ctx); switch (magic) { case JSFI_NAME: @@ -1937,7 +2024,7 @@ { const char *str; GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!f_inst) return GF_JS_EXCEPTION(ctx); + if (!f_inst) return GF_JS_EXCEPTION(ctx); switch (magic) { case JSFI_SETUP_FAILURE: @@ -1949,7 +2036,7 @@ JS_FreeValue(ctx, f_inst->setup_failure_fun); f_inst->setup_failure_fun = JS_DupValue(ctx, value); } - return JS_UNDEFINED; + return JS_UNDEFINED; case JSFI_INAME: str = JS_ToCString(ctx, value); @@ -1966,8 +2053,8 @@ const char *arg_name=NULL; JSValue res; GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!f_inst || !argc) return GF_JS_EXCEPTION(ctx); - arg_name = JS_ToCString(ctx, argv0); + if (!f_inst || !argc) return GF_JS_EXCEPTION(ctx); + arg_name = JS_ToCString(ctx, argv0); if (!arg_name) return GF_JS_EXCEPTION(ctx); if ((argc>1) && JS_ToBool(ctx, argv1)) { char dumpGF_PROP_DUMP_ARG_SIZE; @@ -1988,15 +2075,15 @@ static JSValue jsf_filter_inst_disable_probe(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!jsfi) return GF_JS_EXCEPTION(ctx); + if (!jsfi) return GF_JS_EXCEPTION(ctx); gf_filter_disable_probe(jsfi->filter); return JS_UNDEFINED; } static JSValue jsf_filter_inst_disable_inputs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); - if (!jsfi) return GF_JS_EXCEPTION(ctx); - gf_filter_disable_inputs(jsfi->filter); + if (!jsfi) return GF_JS_EXCEPTION(ctx); + gf_filter_disable_inputs(jsfi->filter); return JS_UNDEFINED; } @@ -2005,17 +2092,17 @@ JS_CGETSET_MAGIC_DEF("name", jsf_filter_inst_prop_get, NULL, JSFI_NAME), JS_CGETSET_MAGIC_DEF("type", jsf_filter_inst_prop_get, NULL, JSFI_TYPE), JS_CGETSET_MAGIC_DEF("iname", jsf_filter_inst_prop_get, jsf_filter_inst_prop_set, JSFI_INAME), - JS_CFUNC_DEF("send_event", 0, jsf_filter_send_event), - JS_CFUNC_DEF("get_info", 0, jsf_filter_get_info), - JS_CFUNC_DEF("send_update", 0, jsf_filter_send_update), - JS_CFUNC_DEF("set_source", 0, jsf_filter_set_source), - JS_CFUNC_DEF("set_source_restricted", 0, jsf_filter_set_source_restricted), - JS_CFUNC_DEF("remove", 0, jsf_filter_remove), - JS_CFUNC_DEF("has_pid_connections_pending", 0, jsf_filter_has_pid_connections_pending), - JS_CFUNC_DEF("get_arg", 0, jsf_filter_inst_get_arg), - JS_CFUNC_DEF("disable_probe", 0, jsf_filter_inst_disable_probe), - JS_CFUNC_DEF("disable_inputs", 0, jsf_filter_inst_disable_inputs), - JS_CFUNC_DEF("reset_source", 0, jsf_filter_reset_source), + JS_CFUNC_DEF("send_event", 0, jsf_filter_send_event), + JS_CFUNC_DEF("get_info", 0, jsf_filter_get_info), + JS_CFUNC_DEF("send_update", 0, jsf_filter_send_update), + JS_CFUNC_DEF("set_source", 0, jsf_filter_set_source), + JS_CFUNC_DEF("set_source_restricted", 0, jsf_filter_set_source_restricted), + JS_CFUNC_DEF("remove", 0, jsf_filter_remove), + JS_CFUNC_DEF("has_pid_connections_pending", 0, jsf_filter_has_pid_connections_pending), + JS_CFUNC_DEF("get_arg", 0, jsf_filter_inst_get_arg), + JS_CFUNC_DEF("disable_probe", 0, jsf_filter_inst_disable_probe), + JS_CFUNC_DEF("disable_inputs", 0, jsf_filter_inst_disable_inputs), + JS_CFUNC_DEF("reset_source", 0, jsf_filter_reset_source), }; @@ -2025,28 +2112,28 @@ GF_Err e = GF_OK; const char *str=NULL; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx) return GF_JS_EXCEPTION(ctx); switch (magic) { case JSF_PID_NAME: str = JS_ToCString(ctx, value); gf_filter_pid_set_name(pctx->pid, str); - break; + break; case JSF_PID_EOS: if (JS_ToBool(ctx, value)) gf_filter_pid_set_eos(pctx->pid); - break; + break; case JSF_PID_MAX_BUFFER: if (JS_ToInt32(ctx, &ival, value)) return GF_JS_EXCEPTION(ctx); gf_filter_pid_set_max_buffer(pctx->pid, ival); - break; + break; case JSF_PID_LOOSE_CONNECT: if (JS_ToBool(ctx, value)) gf_filter_pid_set_loose_connect(pctx->pid); - break; + break; case JSF_PID_FRAMING_MODE: gf_filter_pid_set_framing_mode(pctx->pid, JS_ToBool(ctx, value) ); - break; + break; case JSF_PID_CLOCK_MODE: gf_filter_pid_set_clock_mode(pctx->pid, JS_ToBool(ctx, value) ); break; @@ -2065,7 +2152,7 @@ if (str) JS_FreeCString(ctx, str); if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pid_get_prop(JSContext *ctx, JSValueConst this_val, int magic) @@ -2073,7 +2160,7 @@ u64 dur; char *str; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx) return GF_JS_EXCEPTION(ctx); switch (magic) { case JSF_PID_NAME: return JS_NewString(ctx, gf_filter_pid_get_name(pctx->pid) ); @@ -2083,6 +2170,8 @@ return JS_NewBool (ctx, gf_filter_pid_has_seen_eos(pctx->pid) ); case JSF_PID_EOS_RECEIVED: return JS_NewBool (ctx, gf_filter_pid_eos_received(pctx->pid) ); + case JSF_PID_EOS_IS_FLUSH: + return JS_NewBool (ctx, gf_filter_pid_is_flush_eos(pctx->pid) ); case JSF_PID_WOULD_BLOCK: return JS_NewBool(ctx, gf_filter_pid_would_block(pctx->pid) ); case JSF_PID_SPARSE: @@ -2092,11 +2181,14 @@ case JSF_PID_FILTER_SRC: return JS_NewString(ctx, gf_filter_pid_get_source_filter_name(pctx->pid) ); case JSF_PID_FILTER_ARGS: - return JS_NewString(ctx, gf_filter_pid_get_args(pctx->pid) ); + str = (char *) gf_filter_pid_get_args(pctx->pid); + return str ? JS_NewString(ctx, str) : JS_NULL; case JSF_PID_FILTER_SRC_ARGS: - return JS_NewString(ctx, gf_filter_pid_orig_src_args(pctx->pid, GF_FALSE) ); + str = (char *) gf_filter_pid_orig_src_args(pctx->pid, GF_FALSE); + return str ? JS_NewString(ctx, str) : JS_NULL; case JSF_PID_FILTER_UNICITY_ARGS: - return JS_NewString(ctx, gf_filter_pid_orig_src_args(pctx->pid, GF_TRUE) ); + str = (char *) gf_filter_pid_orig_src_args(pctx->pid, GF_TRUE); + return str ? JS_NewString(ctx, str) : JS_NULL; case JSF_PID_MAX_BUFFER: return JS_NewInt32(ctx, gf_filter_pid_get_max_buffer(pctx->pid) ); case JSF_PID_BUFFER: @@ -2143,21 +2235,25 @@ case JSF_PID_HAS_DECODER: return JS_NewBool(ctx, gf_filter_pid_has_decoder(pctx->pid) ); } - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pid_send_event(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx) return GF_JS_EXCEPTION(ctx); GF_FilterEvent *evt = JS_GetOpaque(argv0, jsf_event_class_id); - if (!evt) return GF_JS_EXCEPTION(ctx); - evt->base.on_pid = pctx->pid; - if (evt->base.type == GF_FEVT_PLAY) { - gf_filter_pid_init_play_event(pctx->pid, evt, evt->play.start_range, evt->play.speed, pctx->jsf->log_name); + if (!evt) return GF_JS_EXCEPTION(ctx); + evt->base.on_pid = pctx->pid; + if (evt->base.type == GF_FEVT_PLAY) { + GF_FilterEvent anevt; + gf_filter_pid_init_play_event(pctx->pid, &anevt, evt->play.start_range, evt->play.speed, pctx->jsf->log_name); + evt->play.speed = anevt.play.speed; + evt->play.start_range = anevt.play.start_range; + evt->play.end_range = anevt.play.end_range; } gf_filter_pid_send_event(pctx->pid, evt); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pid_enum_properties(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -2168,8 +2264,8 @@ JSValue res; const GF_PropertyValue *prop; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - if (JS_ToInt32(ctx, &idx, argv0)) + if (!pctx) return GF_JS_EXCEPTION(ctx); + if (JS_ToInt32(ctx, &idx, argv0)) return GF_JS_EXCEPTION(ctx); if ((argc>1) && JS_ToBool(ctx, argv1)) { @@ -2182,10 +2278,10 @@ if (!pname) return GF_JS_EXCEPTION(ctx); res = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, res, "name", JS_NewString(ctx, pname)); - JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); - JS_SetPropertyStr(ctx, res, "value", jsf_NewProp(ctx, prop)); - return res; + JS_SetPropertyStr(ctx, res, "name", JS_NewString(ctx, pname)); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + JS_SetPropertyStr(ctx, res, "value", jsf_NewProp(ctx, prop)); + return res; } static JSValue jsf_pid_get_property_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_info) @@ -2195,8 +2291,8 @@ const GF_PropertyValue *prop; GF_PropertyEntry *pe = NULL; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - name = JS_ToCString(ctx, argv0); + if (!pctx) return GF_JS_EXCEPTION(ctx); + name = JS_ToCString(ctx, argv0); if (!name) return GF_JS_EXCEPTION(ctx); if ((argc>1) && JS_ToBool(ctx, argv1)) { if (is_info) { @@ -2235,7 +2331,7 @@ res = jsf_NewPropTranslate(ctx, prop, p4cc); } gf_filter_release_property(pe); - return res; + return res; } static JSValue jsf_pid_get_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -2253,11 +2349,11 @@ GF_FilterPacket *pck; GF_JSPckCtx *pckctx; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - if (!pctx->jsf->filter->in_process) + if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx->jsf->is_custom && !pctx->jsf->filter->in_process_callback) return js_throw_err_msg(ctx, GF_BAD_PARAM, "Filter %s attempt to query packet outside process callback not allowed!\n", pctx->jsf->filter->name); - - pck = gf_filter_pid_get_packet(pctx->pid); + + pck = gf_filter_pid_get_packet(pctx->pid); if (!pck) return JS_NULL; if (pctx->pck_head) { @@ -2281,19 +2377,19 @@ pctx->pck_head = pckctx; JS_SetOpaque(res, pckctx); - return res; + return res; } static JSValue jsf_pid_drop_packet(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPckCtx *pckctx; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - if (!pctx->jsf->filter->in_process) + if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx->jsf->is_custom && !pctx->jsf->filter->in_process_callback) return js_throw_err_msg(ctx, GF_BAD_PARAM, "Filter %s attempt to drop packet outside process callback not allowed!\n", pctx->jsf->filter->name); if (!pctx->pck_head) { if (gf_filter_pid_get_packet_count(pctx->pid)) { - gf_filter_pid_drop_packet(pctx->pid); + gf_filter_pid_drop_packet(pctx->pid); } return JS_UNDEFINED; } @@ -2303,17 +2399,17 @@ pctx->pck_head = NULL; JS_FreeValue(ctx, pckctx->jsobj); pckctx->jsobj = JS_UNDEFINED; - gf_filter_pid_drop_packet(pctx->pid); - return JS_UNDEFINED; + gf_filter_pid_drop_packet(pctx->pid); + return JS_UNDEFINED; } static JSValue jsf_pid_is_filter_in_parents(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); GF_JSFilterCtx *f_ctx = JS_GetOpaque(argv0, jsf_filter_class_id); GF_JSFilterInstanceCtx *fi_ctx = JS_GetOpaque(argv0, jsf_filter_inst_class_id); - if (!f_ctx && !fi_ctx) return GF_JS_EXCEPTION(ctx); + if (!f_ctx && !fi_ctx) return GF_JS_EXCEPTION(ctx); return JS_NewBool(ctx, gf_filter_pid_is_filter_in_parents(pctx->pid, f_ctx ? f_ctx->filter : fi_ctx->filter)); } @@ -2323,42 +2419,42 @@ Bool in_final_flush; u32 max_units, nb_pck, max_dur, dur; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx) return GF_JS_EXCEPTION(ctx); in_final_flush = !gf_filter_pid_get_buffer_occupancy(pctx->pid, &max_units, &nb_pck, &max_dur, &dur); res = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, res, "max_units", JS_NewInt32(ctx, max_units)); - JS_SetPropertyStr(ctx, res, "nb_pck", JS_NewInt32(ctx, nb_pck)); - JS_SetPropertyStr(ctx, res, "max_dur", JS_NewInt32(ctx, max_dur)); - JS_SetPropertyStr(ctx, res, "dur", JS_NewInt32(ctx, dur)); - JS_SetPropertyStr(ctx, res, "final_flush", JS_NewBool(ctx, in_final_flush)); - return res; + JS_SetPropertyStr(ctx, res, "max_units", JS_NewInt32(ctx, max_units)); + JS_SetPropertyStr(ctx, res, "nb_pck", JS_NewInt32(ctx, nb_pck)); + JS_SetPropertyStr(ctx, res, "max_dur", JS_NewInt32(ctx, max_dur)); + JS_SetPropertyStr(ctx, res, "dur", JS_NewInt32(ctx, dur)); + JS_SetPropertyStr(ctx, res, "final_flush", JS_NewBool(ctx, in_final_flush)); + return res; } static JSValue jsf_pid_clear_eos(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); gf_filter_pid_clear_eos(pctx->pid, JS_ToBool(ctx, argv0)); return JS_UNDEFINED; } static JSValue jsf_pid_check_caps(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx) return GF_JS_EXCEPTION(ctx); return JS_NewBool(ctx, gf_filter_pid_check_caps(pctx->pid)); } static JSValue jsf_pid_discard_block(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - gf_filter_pid_discard_block(pctx->pid); + if (!pctx) return GF_JS_EXCEPTION(ctx); + gf_filter_pid_discard_block(pctx->pid); return JS_UNDEFINED; } static JSValue jsf_pid_allow_direct_dispatch(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - gf_filter_pid_allow_direct_dispatch(pctx->pid); + if (!pctx) return GF_JS_EXCEPTION(ctx); + gf_filter_pid_allow_direct_dispatch(pctx->pid); return JS_UNDEFINED; } static JSValue jsf_pid_resolve_file_template(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -2368,15 +2464,15 @@ char szFinalGF_MAX_PATH; const char *templ, *suffix=NULL; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); templ = JS_ToCString(ctx, argv0); if (!templ) - return GF_JS_EXCEPTION(ctx); + return GF_JS_EXCEPTION(ctx); if ((argc>=2) && JS_ToInt32(ctx, &fileidx, argv1)) { JS_FreeCString(ctx, templ); - return GF_JS_EXCEPTION(ctx); + return GF_JS_EXCEPTION(ctx); } if (argc==3) @@ -2386,7 +2482,7 @@ JS_FreeCString(ctx, templ); if (e) - return js_throw_err(ctx, e); + return js_throw_err(ctx, e); return JS_NewString(ctx, szFinal); } @@ -2395,7 +2491,7 @@ const char *name=NULL; const GF_PropertyValue *prop; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); name = JS_ToCString(ctx, argv0); if (!name) return GF_JS_EXCEPTION(ctx); @@ -2427,21 +2523,21 @@ GF_Err e; GF_FilterPidStatistics stats; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); if (JS_ToInt32(ctx, &mode, argv0)) return GF_JS_EXCEPTION(ctx); e = gf_filter_pid_get_statistics(pctx->pid, &stats, mode); if (e) - return js_throw_err(ctx, e); + return js_throw_err(ctx, e); res = JS_NewObject(ctx); #define SET_PROP32(_val)\ - JS_SetPropertyStr(ctx, res, #_val, JS_NewInt32(ctx, stats._val)); + JS_SetPropertyStr(ctx, res, #_val, JS_NewInt32(ctx, stats._val)); #define SET_PROP64(_val)\ - JS_SetPropertyStr(ctx, res, #_val, JS_NewInt64(ctx, stats._val)); + JS_SetPropertyStr(ctx, res, #_val, JS_NewInt64(ctx, stats._val)); #define SET_PROPB(_val)\ - JS_SetPropertyStr(ctx, res, #_val, JS_NewBool(ctx, stats._val)); + JS_SetPropertyStr(ctx, res, #_val, JS_NewBool(ctx, stats._val)); SET_PROPB(disconnected) SET_PROP32(average_process_rate) @@ -2462,7 +2558,7 @@ SET_PROP64(min_playout_time) SET_PROP64(buffer_time) SET_PROP32(nb_buffer_units) - return res; + return res; } void jsf_pck_shared_del(GF_Filter *filter, GF_FilterPid *PID, GF_FilterPacket *pck) @@ -2589,8 +2685,8 @@ GF_JSPckCtx *pckc; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - if (!pctx->jsf->filter->in_process) + if (!pctx) return GF_JS_EXCEPTION(ctx); + if (!pctx->jsf->is_custom && !pctx->jsf->filter->in_process_callback) return js_throw_err_msg(ctx, GF_BAD_PARAM, "Filter %s attempt to create a new packet outside process callback not allowed!\n", pctx->jsf->filter->name); @@ -2601,8 +2697,8 @@ return js_throw_err(ctx, GF_OUT_OF_MEM); } obj = JS_NewObjectClass(ctx, jsf_pck_class_id); - if (JS_IsException(obj)) { - gf_list_add(pctx->jsf->pck_res, pckc); + if (JS_IsException(obj)) { + gf_list_add(pctx->jsf->pck_res, pckc); return GF_JS_EXCEPTION(ctx); } JS_SetOpaque(obj, pckc); @@ -2632,7 +2728,7 @@ if (use_shared) return js_throw_err(ctx, GF_BAD_PARAM); if (JS_ToInt32(ctx, &len, argv0)) { - gf_list_add(pctx->jsf->pck_res, pckc); + gf_list_add(pctx->jsf->pck_res, pckc); return GF_JS_EXCEPTION(ctx); } } else { @@ -2772,12 +2868,12 @@ GF_FilterClockType cktype; JSValue res; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - cktype = gf_filter_pid_get_clock_info(pctx->pid, &val, ×cale); + if (!pctx) return GF_JS_EXCEPTION(ctx); + cktype = gf_filter_pid_get_clock_info(pctx->pid, &val, ×cale); res = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, cktype)); - JS_SetPropertyStr(ctx, res, "timescale", JS_NewInt32(ctx, timescale)); - JS_SetPropertyStr(ctx, res, "value", JS_NewInt64(ctx, val)); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, cktype)); + JS_SetPropertyStr(ctx, res, "timescale", JS_NewInt32(ctx, timescale)); + JS_SetPropertyStr(ctx, res, "value", JS_NewInt64(ctx, val)); return res; } @@ -2789,11 +2885,16 @@ const GF_PropertyValue *the_prop = NULL; const char *name=NULL; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - name = JS_ToCString(ctx, argv0); + if (!pctx) return GF_JS_EXCEPTION(ctx); + name = JS_ToCString(ctx, argv0); if (!name) return GF_JS_EXCEPTION(ctx); - if ((argc>2) && JS_ToBool(ctx, argv2)) { + u32 p4cc = gf_props_get_id(name); + Bool is_str = ((argc>2) && JS_ToBool(ctx, argv2) ) ? GF_TRUE : GF_FALSE; + if (!p4cc && (argc>1)) + is_str = GF_TRUE; + + if (is_str) { if (!JS_IsNull(argv1)) { e = jsf_ToProp(pctx->jsf->filter, ctx, argv1, 0, &prop); JS_FreeCString(ctx, name); @@ -2809,7 +2910,6 @@ e = gf_filter_pid_set_property_dyn(pctx->pid, (char *) name, &prop); } } else { - u32 p4cc = gf_props_get_id(name); JS_FreeCString(ctx, name); if (!p4cc) return GF_JS_EXCEPTION(ctx); if (!JS_IsNull(argv1)) { @@ -2828,10 +2928,11 @@ } } - if (the_prop) + if (the_prop && (prop.type != GF_PROP_STRING_LIST)) gf_props_reset_single(&prop); + if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pid_set_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -2850,9 +2951,9 @@ { Bool do_ignore = GF_TRUE; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - if (argc) do_ignore = JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE; - gf_filter_pid_ignore_blocking(pctx->pid, do_ignore); + if (!pctx) return GF_JS_EXCEPTION(ctx); + if (argc) do_ignore = JS_ToBool(ctx, argv0) ? GF_TRUE : GF_FALSE; + gf_filter_pid_ignore_blocking(pctx->pid, do_ignore); return JS_UNDEFINED; } @@ -2860,12 +2961,12 @@ static JSValue jsf_pid_remove(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - if (pctx->pid) { + if (!pctx) return GF_JS_EXCEPTION(ctx); + if (pctx->pid) { gf_filter_pid_remove(pctx->pid); pctx->pid = NULL; - } - JS_SetOpaque(this_val, NULL); + } + JS_SetOpaque(this_val, NULL); return JS_UNDEFINED; } @@ -2873,8 +2974,8 @@ { GF_Err e; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx) return GF_JS_EXCEPTION(ctx); - e = gf_filter_pid_reset_properties(pctx->pid); + if (!pctx) return GF_JS_EXCEPTION(ctx); + e = gf_filter_pid_reset_properties(pctx->pid); if (e) return js_throw_err(ctx, e); return JS_UNDEFINED; } @@ -2883,12 +2984,12 @@ { GF_Err e; GF_JSPidCtx *pctx_this = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx_this || !argc) return GF_JS_EXCEPTION(ctx); + if (!pctx_this || !argc) return GF_JS_EXCEPTION(ctx); GF_JSPidCtx *pctx_from = JS_GetOpaque(argv0, jsf_pid_class_id); - if (!pctx_from) return GF_JS_EXCEPTION(ctx); - e = gf_filter_pid_copy_properties(pctx_this->pid, pctx_from->pid); + if (!pctx_from) return GF_JS_EXCEPTION(ctx); + e = gf_filter_pid_copy_properties(pctx_this->pid, pctx_from->pid); if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pid_forward(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -2896,72 +2997,99 @@ GF_Err e; GF_JSPckCtx *pckc; GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); - if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); - if (!JS_IsObject(argv0)) return GF_JS_EXCEPTION(ctx); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (!JS_IsObject(argv0)) return GF_JS_EXCEPTION(ctx); pckc = JS_GetOpaque(argv0, jsf_pck_class_id); - if (!pckc || !pckc->pck) return GF_JS_EXCEPTION(ctx); - e = gf_filter_pck_forward(pckc->pck, pctx->pid); + if (!pckc || !pckc->pck) return GF_JS_EXCEPTION(ctx); + e = gf_filter_pck_forward(pckc->pck, pctx->pid); if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } +static JSValue jsf_pid_match_source(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue ret = JS_FALSE; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return GF_JS_EXCEPTION(ctx); + if (pctx->pid == pctx->pid->pid) return GF_JS_EXCEPTION(ctx); + const char *src_id = JS_ToCString(ctx, argv0); + if (!src_id) + return ret; + const char *sid = src_id; + if (!strncmp(src_id, "ipid://", 7)) sid+=7; + + + Bool pid_excluded, needs_clone; + if (filter_source_id_match(pctx->pid, pctx->pid->pid->filter->id, NULL, &pid_excluded, &needs_clone, sid)) { + ret = JS_TRUE; + } else { + const char *src_filter_id = gf_filter_last_id_in_chain(pctx->pid->pid->filter, GF_TRUE); + if (src_filter_id && filter_source_id_match(pctx->pid, src_filter_id, NULL, &pid_excluded, &needs_clone, sid)) { + ret = JS_TRUE; + } + } + JS_FreeCString(ctx, src_id); + return ret; +} static const JSCFunctionListEntry jsf_pid_funcs = { - JS_CGETSET_MAGIC_DEF("name", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_NAME), - JS_CGETSET_MAGIC_DEF("eos", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_EOS), - JS_CGETSET_MAGIC_DEF("eos_seen", jsf_pid_get_prop, NULL, JSF_PID_EOS_SEEN), - JS_CGETSET_MAGIC_DEF("eos_received", jsf_pid_get_prop, NULL, JSF_PID_EOS_RECEIVED), - JS_CGETSET_MAGIC_DEF("would_block", jsf_pid_get_prop, NULL, JSF_PID_WOULD_BLOCK), - JS_CGETSET_MAGIC_DEF("sparse", jsf_pid_get_prop, NULL, JSF_PID_SPARSE), - JS_CGETSET_MAGIC_DEF("filter_name", jsf_pid_get_prop, NULL, JSF_PID_FILTER_NAME), - JS_CGETSET_MAGIC_DEF("src_name", jsf_pid_get_prop, NULL, JSF_PID_FILTER_SRC), - JS_CGETSET_MAGIC_DEF("args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_ARGS), - JS_CGETSET_MAGIC_DEF("src_args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_SRC_ARGS), - JS_CGETSET_MAGIC_DEF("unicity_args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_UNICITY_ARGS), - JS_CGETSET_MAGIC_DEF("max_buffer", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_MAX_BUFFER), - JS_CGETSET_MAGIC_DEF("loose_connect", NULL, jsf_pid_set_prop, JSF_PID_LOOSE_CONNECT), - JS_CGETSET_MAGIC_DEF("framing", NULL, jsf_pid_set_prop, JSF_PID_FRAMING_MODE), - JS_CGETSET_MAGIC_DEF("buffer", jsf_pid_get_prop, NULL, JSF_PID_BUFFER), - JS_CGETSET_MAGIC_DEF("full", jsf_pid_get_prop, NULL, JSF_PID_IS_FULL), - JS_CGETSET_MAGIC_DEF("first_empty", jsf_pid_get_prop, NULL, JSF_PID_FIRST_EMPTY), - JS_CGETSET_MAGIC_DEF("first_cts", jsf_pid_get_prop, NULL, JSF_PID_FIRST_CTS), - JS_CGETSET_MAGIC_DEF("nb_pck_queued", jsf_pid_get_prop, NULL, JSF_PID_NB_PACKETS), - JS_CGETSET_MAGIC_DEF("timescale", jsf_pid_get_prop, NULL, JSF_PID_TIMESCALE), - JS_CGETSET_MAGIC_DEF("clock_mode", NULL, jsf_pid_set_prop, JSF_PID_CLOCK_MODE), - JS_CGETSET_MAGIC_DEF("discard", NULL, jsf_pid_set_prop, JSF_PID_DISCARD), - JS_CGETSET_MAGIC_DEF("src_url", jsf_pid_get_prop, NULL, JSF_PID_SRC_URL), - JS_CGETSET_MAGIC_DEF("dst_url", jsf_pid_get_prop, NULL, JSF_PID_DST_URL), - JS_CGETSET_MAGIC_DEF("require_source_id", NULL, jsf_pid_set_prop, JSF_PID_REQUIRE_SOURCEID), - JS_CGETSET_MAGIC_DEF("recompute_dts", NULL, jsf_pid_set_prop, JSF_PID_RECOMPUTE_DTS), - JS_CGETSET_MAGIC_DEF("min_pck_dur", jsf_pid_get_prop, NULL, JSF_PID_MIN_PCK_DUR), - JS_CGETSET_MAGIC_DEF("playing", jsf_pid_get_prop, NULL, JSF_PID_IS_PLAYING), - JS_CGETSET_MAGIC_DEF("next_ts", jsf_pid_get_prop, NULL, JSF_PID_NEXT_TS), - JS_CGETSET_MAGIC_DEF("has_decoder", jsf_pid_get_prop, NULL, JSF_PID_HAS_DECODER), - JS_CFUNC_DEF("send_event", 0, jsf_pid_send_event), - JS_CFUNC_DEF("enum_properties", 0, jsf_pid_enum_properties), - JS_CFUNC_DEF("get_prop", 0, jsf_pid_get_property), - JS_CFUNC_DEF("get_info", 0, jsf_pid_get_info), - JS_CFUNC_DEF("get_packet", 0, jsf_pid_get_packet), - JS_CFUNC_DEF("drop_packet", 0, jsf_pid_drop_packet), - JS_CFUNC_DEF("is_filter_in_parents", 0, jsf_pid_is_filter_in_parents), - JS_CFUNC_DEF("get_buffer_occupancy", 0, jsf_pid_get_buffer_occupancy), - JS_CFUNC_DEF("clear_eos", 0, jsf_pid_clear_eos), - JS_CFUNC_DEF("check_caps", 0, jsf_pid_check_caps), - JS_CFUNC_DEF("discard_block", 0, jsf_pid_discard_block), - JS_CFUNC_DEF("allow_direct_dispatch", 0, jsf_pid_allow_direct_dispatch), - JS_CFUNC_DEF("resolve_file_template", 0, jsf_pid_resolve_file_template), - JS_CFUNC_DEF("query_caps", 0, jsf_pid_query_caps), - JS_CFUNC_DEF("get_stats", 0, jsf_pid_get_statistics), - JS_CFUNC_DEF("get_clock_info", 0, jsf_pid_get_clock_info), - JS_CFUNC_DEF("set_prop", 0, jsf_pid_set_property), - JS_CFUNC_DEF("set_info", 0, jsf_pid_set_info), - JS_CFUNC_DEF("new_packet", 0, jsf_pid_new_packet), - JS_CFUNC_DEF("remove", 0, jsf_pid_remove), - JS_CFUNC_DEF("reset_props", 0, jsf_pid_reset_props), - JS_CFUNC_DEF("copy_props", 0, jsf_pid_copy_props), - JS_CFUNC_DEF("forward", 0, jsf_pid_forward), - JS_CFUNC_DEF("negotiate_prop", 0, jsf_pid_negotiate_prop), - JS_CFUNC_DEF("ignore_blocking", 0, jsf_pid_ignore_blocking), + JS_CGETSET_MAGIC_DEF("name", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_NAME), + JS_CGETSET_MAGIC_DEF("eos", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_EOS), + JS_CGETSET_MAGIC_DEF("eos_seen", jsf_pid_get_prop, NULL, JSF_PID_EOS_SEEN), + JS_CGETSET_MAGIC_DEF("eos_received", jsf_pid_get_prop, NULL, JSF_PID_EOS_RECEIVED), + JS_CGETSET_MAGIC_DEF("is_flush", jsf_pid_get_prop, NULL, JSF_PID_EOS_IS_FLUSH), + JS_CGETSET_MAGIC_DEF("would_block", jsf_pid_get_prop, NULL, JSF_PID_WOULD_BLOCK), + JS_CGETSET_MAGIC_DEF("sparse", jsf_pid_get_prop, NULL, JSF_PID_SPARSE), + JS_CGETSET_MAGIC_DEF("filter_name", jsf_pid_get_prop, NULL, JSF_PID_FILTER_NAME), + JS_CGETSET_MAGIC_DEF("src_name", jsf_pid_get_prop, NULL, JSF_PID_FILTER_SRC), + JS_CGETSET_MAGIC_DEF("args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_ARGS), + JS_CGETSET_MAGIC_DEF("src_args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_SRC_ARGS), + JS_CGETSET_MAGIC_DEF("unicity_args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_UNICITY_ARGS), + JS_CGETSET_MAGIC_DEF("max_buffer", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_MAX_BUFFER), + JS_CGETSET_MAGIC_DEF("loose_connect", NULL, jsf_pid_set_prop, JSF_PID_LOOSE_CONNECT), + JS_CGETSET_MAGIC_DEF("framing", NULL, jsf_pid_set_prop, JSF_PID_FRAMING_MODE), + JS_CGETSET_MAGIC_DEF("buffer", jsf_pid_get_prop, NULL, JSF_PID_BUFFER), + JS_CGETSET_MAGIC_DEF("full", jsf_pid_get_prop, NULL, JSF_PID_IS_FULL), + JS_CGETSET_MAGIC_DEF("first_empty", jsf_pid_get_prop, NULL, JSF_PID_FIRST_EMPTY), + JS_CGETSET_MAGIC_DEF("first_cts", jsf_pid_get_prop, NULL, JSF_PID_FIRST_CTS), + JS_CGETSET_MAGIC_DEF("nb_pck_queued", jsf_pid_get_prop, NULL, JSF_PID_NB_PACKETS), + JS_CGETSET_MAGIC_DEF("timescale", jsf_pid_get_prop, NULL, JSF_PID_TIMESCALE), + JS_CGETSET_MAGIC_DEF("clock_mode", NULL, jsf_pid_set_prop, JSF_PID_CLOCK_MODE), + JS_CGETSET_MAGIC_DEF("discard", NULL, jsf_pid_set_prop, JSF_PID_DISCARD), + JS_CGETSET_MAGIC_DEF("src_url", jsf_pid_get_prop, NULL, JSF_PID_SRC_URL), + JS_CGETSET_MAGIC_DEF("dst_url", jsf_pid_get_prop, NULL, JSF_PID_DST_URL), + JS_CGETSET_MAGIC_DEF("require_source_id", NULL, jsf_pid_set_prop, JSF_PID_REQUIRE_SOURCEID), + JS_CGETSET_MAGIC_DEF("recompute_dts", NULL, jsf_pid_set_prop, JSF_PID_RECOMPUTE_DTS), + JS_CGETSET_MAGIC_DEF("min_pck_dur", jsf_pid_get_prop, NULL, JSF_PID_MIN_PCK_DUR), + JS_CGETSET_MAGIC_DEF("playing", jsf_pid_get_prop, NULL, JSF_PID_IS_PLAYING), + JS_CGETSET_MAGIC_DEF("next_ts", jsf_pid_get_prop, NULL, JSF_PID_NEXT_TS), + JS_CGETSET_MAGIC_DEF("has_decoder", jsf_pid_get_prop, NULL, JSF_PID_HAS_DECODER), + JS_CFUNC_DEF("send_event", 0, jsf_pid_send_event), + JS_CFUNC_DEF("enum_properties", 0, jsf_pid_enum_properties), + JS_CFUNC_DEF("get_prop", 0, jsf_pid_get_property), + JS_CFUNC_DEF("get_info", 0, jsf_pid_get_info), + JS_CFUNC_DEF("get_packet", 0, jsf_pid_get_packet), + JS_CFUNC_DEF("drop_packet", 0, jsf_pid_drop_packet), + JS_CFUNC_DEF("is_filter_in_parents", 0, jsf_pid_is_filter_in_parents), + JS_CFUNC_DEF("get_buffer_occupancy", 0, jsf_pid_get_buffer_occupancy), + JS_CFUNC_DEF("clear_eos", 0, jsf_pid_clear_eos), + JS_CFUNC_DEF("check_caps", 0, jsf_pid_check_caps), + JS_CFUNC_DEF("discard_block", 0, jsf_pid_discard_block), + JS_CFUNC_DEF("allow_direct_dispatch", 0, jsf_pid_allow_direct_dispatch), + JS_CFUNC_DEF("resolve_file_template", 0, jsf_pid_resolve_file_template), + JS_CFUNC_DEF("query_caps", 0, jsf_pid_query_caps), + JS_CFUNC_DEF("get_stats", 0, jsf_pid_get_statistics), + JS_CFUNC_DEF("get_clock_info", 0, jsf_pid_get_clock_info), + JS_CFUNC_DEF("set_prop", 0, jsf_pid_set_property), + JS_CFUNC_DEF("set_info", 0, jsf_pid_set_info), + JS_CFUNC_DEF("new_packet", 0, jsf_pid_new_packet), + JS_CFUNC_DEF("remove", 0, jsf_pid_remove), + JS_CFUNC_DEF("reset_props", 0, jsf_pid_reset_props), + JS_CFUNC_DEF("copy_props", 0, jsf_pid_copy_props), + JS_CFUNC_DEF("forward", 0, jsf_pid_forward), + JS_CFUNC_DEF("negotiate_prop", 0, jsf_pid_negotiate_prop), + JS_CFUNC_DEF("ignore_blocking", 0, jsf_pid_ignore_blocking), + JS_CFUNC_DEF("match_source", 0, jsf_pid_match_source), }; enum @@ -3007,6 +3135,12 @@ JSF_EVENT_BUFREQ_MAX_PLAYOUT_US, JSF_EVENT_BUFREQ_MIN_PLAYOUT_US, JSF_EVENT_BUFREQ_PID_ONLY, + /*DASH quality select*/ + JSF_EVENT_HASQSEL_SERVICE_ID, + JSF_EVENT_HASQSEL_PERIOD_ID, + JSF_EVENT_HASQSEL_AS_ID, + JSF_EVENT_HASQSEL_REP_ID, + JSF_EVENT_HASQSEL_SELTYPE, JSF_EVENT_USER_TYPE, JSF_EVENT_USER_KEYCODE, @@ -3117,6 +3251,20 @@ return GF_FALSE; } break; + + case GF_FEVT_DASH_QUALITY_SELECT: + switch (magic) { + case JSF_EVENT_HASQSEL_SERVICE_ID: + case JSF_EVENT_HASQSEL_PERIOD_ID: + case JSF_EVENT_HASQSEL_AS_ID: + case JSF_EVENT_HASQSEL_REP_ID: + case JSF_EVENT_HASQSEL_SELTYPE: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_USER: if (magic==JSF_EVENT_USER_TYPE) return GF_TRUE; @@ -3232,15 +3380,22 @@ return GF_FALSE; } +static void to_event_string(JSContext *ctx, JSValue value, char **ptr) +{ + const char *str_src = JS_ToCString(ctx, value); + char *str = gf_strdup(str_src ? str_src : ""); + if (str_src) JS_FreeCString(ctx, str_src); + if (*ptr) gf_free(*ptr); + *ptr = str; +} static JSValue jsf_event_set_prop(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) { GF_Err e = GF_OK; u32 ival; Double dval; - const char *str=NULL; GF_FilterEvent *evt = JS_GetOpaque(this_val, jsf_event_class_id); - if (!evt) return GF_JS_EXCEPTION(ctx); + if (!evt) return GF_JS_EXCEPTION(ctx); if (!jsf_check_evt(evt->base.type, evt->user_event.event.type, magic)) return GF_JS_EXCEPTION(ctx); @@ -3276,8 +3431,7 @@ case JSF_EVENT_END_OFFSET: return JS_ToInt64(ctx, &evt->seek.end_offset, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; case JSF_EVENT_SOURCE_SWITCH: - /*TODO check leak!*/ - evt->seek.source_switch = JS_ToCString(ctx, value); + to_event_string(ctx, value, (char**) &evt->seek.source_switch); return JS_UNDEFINED; case JSF_EVENT_SKIP_CACHE_EXPIRATION: evt->seek.skip_cache_expiration = JS_ToBool(ctx, value); @@ -3286,8 +3440,7 @@ return JS_ToInt32(ctx, &evt->seek.hint_block_size, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; /*segment size*/ case JSF_EVENT_SEG_URL: - /*TODO check leak!*/ - evt->seg_size.seg_url = JS_ToCString(ctx, value); + to_event_string(ctx, value, (char**) &evt->seg_size.seg_url); return JS_UNDEFINED; case JSF_EVENT_SEG_IS_INIT: evt->seg_size.is_init = JS_ToBool(ctx, value) ? 1 : 0; @@ -3328,6 +3481,16 @@ evt->buffer_req.pid_only = JS_ToBool(ctx, value); return JS_UNDEFINED; + case JSF_EVENT_HASQSEL_SERVICE_ID: return JS_ToInt32(ctx, &evt->dash_select.service_id, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; + case JSF_EVENT_HASQSEL_PERIOD_ID: + to_event_string(ctx, value, (char**) &evt->dash_select.period_id); + return JS_UNDEFINED; + case JSF_EVENT_HASQSEL_AS_ID: return JS_ToInt32(ctx, &evt->dash_select.as_id, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; + case JSF_EVENT_HASQSEL_REP_ID: + to_event_string(ctx, value, (char**) &evt->dash_select.rep_id); + return JS_UNDEFINED; + case JSF_EVENT_HASQSEL_SELTYPE: return JS_ToInt32(ctx, (s32 *)&evt->dash_select.select_type, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; + case JSF_EVENT_USER_TYPE: if (JS_ToInt32(ctx, &ival, value)) return GF_JS_EXCEPTION(ctx); evt->user_event.event.type = (u8) ival; @@ -3370,12 +3533,8 @@ return JS_ToInt32(ctx, &evt->user_event.event.mtouch.num_fingers, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; case JSF_EVENT_USER_TEXT: - { - str = JS_ToCString(ctx, value); - evt->user_event.event.clipboard.text = gf_strdup(str ? str : ""); - if (str) JS_FreeCString(ctx, str); + to_event_string(ctx, value, (char**)&evt->user_event.event.clipboard.text); return JS_UNDEFINED; - } case JSF_EVENT_USER_WIDTH: return JS_ToInt32(ctx, &evt->user_event.event.size.width, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; case JSF_EVENT_USER_HEIGHT: return JS_ToInt32(ctx, &evt->user_event.event.size.height, value) ? GF_JS_EXCEPTION(ctx) : JS_UNDEFINED; @@ -3393,24 +3552,17 @@ return JS_UNDEFINED; case JSF_EVENT_USER_CAPTION: - { - str = JS_ToCString(ctx, value); - evt->user_event.event.caption.caption = gf_strdup(str ? str : ""); - if (str) JS_FreeCString(ctx, str); + to_event_string(ctx, value, (char**) &evt->user_event.event.caption.caption); return JS_UNDEFINED; } - } - - if (str) - JS_FreeCString(ctx, str); if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_event_get_prop(JSContext *ctx, JSValueConst this_val, int magic) { GF_FilterEvent *evt = JS_GetOpaque(this_val, jsf_event_class_id); - if (!evt) return GF_JS_EXCEPTION(ctx); + if (!evt) return GF_JS_EXCEPTION(ctx); if (!jsf_check_evt(evt->base.type, evt->user_event.event.type, magic)) return GF_JS_EXCEPTION(ctx); switch (magic) { @@ -3488,6 +3640,13 @@ case JSF_EVENT_BUFREQ_MAX_PLAYOUT_US: return JS_NewInt32(ctx, evt->buffer_req.max_playout_us); case JSF_EVENT_BUFREQ_MIN_PLAYOUT_US: return JS_NewInt32(ctx, evt->buffer_req.min_playout_us); case JSF_EVENT_BUFREQ_PID_ONLY: return JS_NewBool(ctx, evt->buffer_req.pid_only); + /*dash select*/ + case JSF_EVENT_HASQSEL_SERVICE_ID: return JS_NewInt32(ctx, evt->dash_select.service_id); + case JSF_EVENT_HASQSEL_PERIOD_ID: return JS_NewString(ctx, evt->dash_select.period_id); + case JSF_EVENT_HASQSEL_AS_ID: return JS_NewInt32(ctx, evt->dash_select.as_id); + case JSF_EVENT_HASQSEL_REP_ID: return JS_NewString(ctx, evt->dash_select.rep_id); + case JSF_EVENT_HASQSEL_SELTYPE: return JS_NewInt32(ctx, evt->dash_select.select_type); + /*user event*/ case JSF_EVENT_USER_TYPE: return JS_NewInt32(ctx, evt->user_event.event.type); case JSF_EVENT_USER_KEYCODE: return JS_NewInt32(ctx, evt->user_event.event.key.key_code); @@ -3598,7 +3757,7 @@ return JS_NewInt32(ctx, evt->user_event.event.size.window_id); } } - return JS_UNDEFINED; + return JS_UNDEFINED; } GF_FilterEvent *jsf_get_event(JSContext *ctx, JSValueConst this_val) @@ -3609,103 +3768,110 @@ static const JSCFunctionListEntry jsf_event_funcs = { - JS_CGETSET_MAGIC_DEF("type", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_TYPE), - JS_CGETSET_MAGIC_DEF("name", jsf_event_get_prop, NULL, JSF_EVENT_NAME), - /*PLAY event*/ - JS_CGETSET_MAGIC_DEF("start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_START_RANGE), - JS_CGETSET_MAGIC_DEF("speed", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SPEED), - JS_CGETSET_MAGIC_DEF("hw_buffer_reset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HW_BUFFER_RESET), - JS_CGETSET_MAGIC_DEF("initial_broadcast_play", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_INITIAL_BROADCAST_PLAY), - JS_CGETSET_MAGIC_DEF("timestamp_based", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_TIMESTAMP_BASED), - JS_CGETSET_MAGIC_DEF("full_file_only", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FULL_FILE_ONLY), - JS_CGETSET_MAGIC_DEF("forced_dash_segment_switch", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FORCE_DASH_SEG_SWITCH), - JS_CGETSET_MAGIC_DEF("from_pck", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FROM_PCK), + JS_CGETSET_MAGIC_DEF("type", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_TYPE), + JS_CGETSET_MAGIC_DEF("name", jsf_event_get_prop, NULL, JSF_EVENT_NAME), + /*PLAY event*/ + JS_CGETSET_MAGIC_DEF("start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_START_RANGE), + JS_CGETSET_MAGIC_DEF("speed", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SPEED), + JS_CGETSET_MAGIC_DEF("hw_buffer_reset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HW_BUFFER_RESET), + JS_CGETSET_MAGIC_DEF("initial_broadcast_play", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_INITIAL_BROADCAST_PLAY), + JS_CGETSET_MAGIC_DEF("timestamp_based", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_TIMESTAMP_BASED), + JS_CGETSET_MAGIC_DEF("full_file_only", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FULL_FILE_ONLY), + JS_CGETSET_MAGIC_DEF("forced_dash_segment_switch", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FORCE_DASH_SEG_SWITCH), + JS_CGETSET_MAGIC_DEF("from_pck", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FROM_PCK), /*source switch*/ - JS_CGETSET_MAGIC_DEF("start_offset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_START_OFFSET), - JS_CGETSET_MAGIC_DEF("end_offset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_END_OFFSET), - JS_CGETSET_MAGIC_DEF("switch_url", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SOURCE_SWITCH), - JS_CGETSET_MAGIC_DEF("skip_cache_exp", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SKIP_CACHE_EXPIRATION), - JS_CGETSET_MAGIC_DEF("hint_block_size", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HINT_BLOCK_SIZE), + JS_CGETSET_MAGIC_DEF("start_offset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_START_OFFSET), + JS_CGETSET_MAGIC_DEF("end_offset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_END_OFFSET), + JS_CGETSET_MAGIC_DEF("switch_url", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SOURCE_SWITCH), + JS_CGETSET_MAGIC_DEF("skip_cache_exp", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SKIP_CACHE_EXPIRATION), + JS_CGETSET_MAGIC_DEF("hint_block_size", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HINT_BLOCK_SIZE), /*segment size*/ - JS_CGETSET_MAGIC_DEF("seg_url", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SEG_URL), - JS_CGETSET_MAGIC_DEF("is_init", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SEG_IS_INIT), - JS_CGETSET_MAGIC_DEF("media_start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_MEDIA_START_RANGE), - JS_CGETSET_MAGIC_DEF("media_end_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_MEDIA_END_RANGE), - JS_CGETSET_MAGIC_DEF("index_start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_IDX_START_RANGE), - JS_CGETSET_MAGIC_DEF("index_end_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_IDX_END_RANGE), + JS_CGETSET_MAGIC_DEF("seg_url", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SEG_URL), + JS_CGETSET_MAGIC_DEF("is_init", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SEG_IS_INIT), + JS_CGETSET_MAGIC_DEF("media_start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_MEDIA_START_RANGE), + JS_CGETSET_MAGIC_DEF("media_end_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_MEDIA_END_RANGE), + JS_CGETSET_MAGIC_DEF("index_start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_IDX_START_RANGE), + JS_CGETSET_MAGIC_DEF("index_end_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_IDX_END_RANGE), /*quality switch*/ - JS_CGETSET_MAGIC_DEF("up", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_UP), - JS_CGETSET_MAGIC_DEF("dependent_group_index", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_GROUP_IDX), - JS_CGETSET_MAGIC_DEF("q_idx", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_QUALITY_IDX), - JS_CGETSET_MAGIC_DEF("set_tile_mode_plus_one", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_TILE_MODE), - JS_CGETSET_MAGIC_DEF("quality_degradation", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_QUALITY_DEGRADATION), - /*visibility hint*/ - JS_CGETSET_MAGIC_DEF("min_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MIN_X), - JS_CGETSET_MAGIC_DEF("min_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MIN_Y), - JS_CGETSET_MAGIC_DEF("max_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MAX_X), - JS_CGETSET_MAGIC_DEF("max_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MAX_Y), - JS_CGETSET_MAGIC_DEF("is_gaze", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_IS_GAZE), - /*buffer reqs*/ - JS_CGETSET_MAGIC_DEF("max_buffer_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MAX_BUFFER_US), - JS_CGETSET_MAGIC_DEF("max_playout_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MAX_PLAYOUT_US), - JS_CGETSET_MAGIC_DEF("min_playout_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MIN_PLAYOUT_US), - JS_CGETSET_MAGIC_DEF("pid_only", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_PID_ONLY), - /*ui events*/ - JS_CGETSET_MAGIC_DEF("ui_type", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_TYPE), - JS_CGETSET_MAGIC_DEF("keycode", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_KEYCODE), - JS_CGETSET_MAGIC_DEF("keyname", jsf_event_get_prop, NULL, JSF_EVENT_USER_KEYNAME), - JS_CGETSET_MAGIC_DEF("keymods", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_KEYMODS), - JS_CGETSET_MAGIC_DEF("char", jsf_event_get_prop, NULL, JSF_EVENT_USER_TEXT_CHAR), - JS_CGETSET_MAGIC_DEF("mouse_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_X), - JS_CGETSET_MAGIC_DEF("mouse_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_Y), - JS_CGETSET_MAGIC_DEF("wheel", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_WHEEL), - JS_CGETSET_MAGIC_DEF("button", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_BUTTON), - JS_CGETSET_MAGIC_DEF("hwkey", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_HWKEY), - JS_CGETSET_MAGIC_DEF("dropfiles", jsf_event_get_prop, NULL, JSF_EVENT_USER_DROPFILES), - JS_CGETSET_MAGIC_DEF("clipboard", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_TEXT), - JS_CGETSET_MAGIC_DEF("window", jsf_event_get_prop, NULL, JSF_EVENT_USER_WINDOW_ID), - - JS_CGETSET_MAGIC_DEF("mt_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_X), - JS_CGETSET_MAGIC_DEF("mt_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_Y), - JS_CGETSET_MAGIC_DEF("mt_rotate", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_ROTATION), - JS_CGETSET_MAGIC_DEF("mt_pinch", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_PINCH), - JS_CGETSET_MAGIC_DEF("mt_fingers", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_FINGERS), - - JS_CGETSET_MAGIC_DEF("width", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_WIDTH), - JS_CGETSET_MAGIC_DEF("height", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_HEIGHT), - JS_CGETSET_MAGIC_DEF("showtype", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_SHOWTYPE), - JS_CGETSET_MAGIC_DEF("move_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_X), - JS_CGETSET_MAGIC_DEF("move_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_Y), - JS_CGETSET_MAGIC_DEF("move_relative", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_RELATIVE), - JS_CGETSET_MAGIC_DEF("move_alignx", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_ALIGN_X), - JS_CGETSET_MAGIC_DEF("move_aligny", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_ALIGN_Y), - JS_CGETSET_MAGIC_DEF("caption", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_CAPTION), + JS_CGETSET_MAGIC_DEF("up", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_UP), + JS_CGETSET_MAGIC_DEF("dependent_group_index", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_GROUP_IDX), + JS_CGETSET_MAGIC_DEF("q_idx", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_QUALITY_IDX), + JS_CGETSET_MAGIC_DEF("set_tile_mode_plus_one", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_TILE_MODE), + JS_CGETSET_MAGIC_DEF("quality_degradation", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_QUALITY_DEGRADATION), + /*visibility hint*/ + JS_CGETSET_MAGIC_DEF("min_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MIN_X), + JS_CGETSET_MAGIC_DEF("min_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MIN_Y), + JS_CGETSET_MAGIC_DEF("max_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MAX_X), + JS_CGETSET_MAGIC_DEF("max_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MAX_Y), + JS_CGETSET_MAGIC_DEF("is_gaze", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_IS_GAZE), + /*buffer reqs*/ + JS_CGETSET_MAGIC_DEF("max_buffer_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MAX_BUFFER_US), + JS_CGETSET_MAGIC_DEF("max_playout_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MAX_PLAYOUT_US), + JS_CGETSET_MAGIC_DEF("min_playout_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MIN_PLAYOUT_US), + JS_CGETSET_MAGIC_DEF("pid_only", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_PID_ONLY), + /*dash quality select*/ + JS_CGETSET_MAGIC_DEF("service_id", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HASQSEL_SERVICE_ID), + JS_CGETSET_MAGIC_DEF("period_id", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HASQSEL_PERIOD_ID), + JS_CGETSET_MAGIC_DEF("as_id", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HASQSEL_AS_ID), + JS_CGETSET_MAGIC_DEF("rep_id", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HASQSEL_REP_ID), + JS_CGETSET_MAGIC_DEF("select", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HASQSEL_SELTYPE), + + /*ui events*/ + JS_CGETSET_MAGIC_DEF("ui_type", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_TYPE), + JS_CGETSET_MAGIC_DEF("keycode", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_KEYCODE), + JS_CGETSET_MAGIC_DEF("keyname", jsf_event_get_prop, NULL, JSF_EVENT_USER_KEYNAME), + JS_CGETSET_MAGIC_DEF("keymods", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_KEYMODS), + JS_CGETSET_MAGIC_DEF("char", jsf_event_get_prop, NULL, JSF_EVENT_USER_TEXT_CHAR), + JS_CGETSET_MAGIC_DEF("mouse_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_X), + JS_CGETSET_MAGIC_DEF("mouse_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_Y), + JS_CGETSET_MAGIC_DEF("wheel", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_WHEEL), + JS_CGETSET_MAGIC_DEF("button", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_BUTTON), + JS_CGETSET_MAGIC_DEF("hwkey", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_HWKEY), + JS_CGETSET_MAGIC_DEF("dropfiles", jsf_event_get_prop, NULL, JSF_EVENT_USER_DROPFILES), + JS_CGETSET_MAGIC_DEF("clipboard", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_TEXT), + JS_CGETSET_MAGIC_DEF("window", jsf_event_get_prop, NULL, JSF_EVENT_USER_WINDOW_ID), + + JS_CGETSET_MAGIC_DEF("mt_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_X), + JS_CGETSET_MAGIC_DEF("mt_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_Y), + JS_CGETSET_MAGIC_DEF("mt_rotate", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_ROTATION), + JS_CGETSET_MAGIC_DEF("mt_pinch", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_PINCH), + JS_CGETSET_MAGIC_DEF("mt_fingers", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_FINGERS), + + JS_CGETSET_MAGIC_DEF("width", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_WIDTH), + JS_CGETSET_MAGIC_DEF("height", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_HEIGHT), + JS_CGETSET_MAGIC_DEF("showtype", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_SHOWTYPE), + JS_CGETSET_MAGIC_DEF("move_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_X), + JS_CGETSET_MAGIC_DEF("move_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_Y), + JS_CGETSET_MAGIC_DEF("move_relative", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_RELATIVE), + JS_CGETSET_MAGIC_DEF("move_alignx", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_ALIGN_X), + JS_CGETSET_MAGIC_DEF("move_aligny", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_ALIGN_Y), + JS_CGETSET_MAGIC_DEF("caption", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_CAPTION), }; static JSValue jsf_event_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { GF_FilterEvent *evt; - JSValue obj; - s32 type; + JSValue obj; + s32 type; if (argc!=1) return GF_JS_EXCEPTION(ctx); if (JS_ToInt32(ctx, &type, argv0)) return GF_JS_EXCEPTION(ctx); if (!type) - return GF_JS_EXCEPTION(ctx); - obj = JS_NewObjectClass(ctx, jsf_event_class_id); - if (JS_IsException(obj)) return obj; + return GF_JS_EXCEPTION(ctx); + obj = JS_NewObjectClass(ctx, jsf_event_class_id); + if (JS_IsException(obj)) return obj; GF_SAFEALLOC(evt, GF_FilterEvent); - if (!evt) { - JS_FreeValue(ctx, obj); + if (!evt) { + JS_FreeValue(ctx, obj); return js_throw_err(ctx, GF_OUT_OF_MEM); - } - evt->base.type = type; - if (type==GF_FEVT_PLAY) - evt->play.speed = 1.0; - JS_SetOpaque(obj, evt); - return obj; + } + evt->base.type = type; + if (type==GF_FEVT_PLAY) + evt->play.speed = 1.0; + JS_SetOpaque(obj, evt); + return obj; } enum @@ -3747,10 +3913,10 @@ const char *str=NULL; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; - switch (magic) { + switch (magic) { case JSF_PCK_START: gf_filter_pck_get_framing(pck, &a1, &a2); gf_filter_pck_set_framing(pck, JS_ToBool(ctx, value), a2); @@ -3853,7 +4019,7 @@ if (str) JS_FreeCString(ctx, str); if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pck_get_prop(JSContext *ctx, JSValueConst this_val, int magic) @@ -3863,8 +4029,8 @@ u32 ival; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; switch (magic) { case JSF_PCK_START: @@ -3946,18 +4112,18 @@ case JSF_PCK_HAS_PROPERTIES: return JS_NewBool(ctx, gf_filter_pck_has_properties(pck) ); } - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pck_set_readonly(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; gf_filter_pck_set_readonly(pck); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pck_enum_properties(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3968,10 +4134,10 @@ const GF_PropertyValue *prop; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; - if (JS_ToInt32(ctx, &idx, argv0)) + if (JS_ToInt32(ctx, &idx, argv0)) return GF_JS_EXCEPTION(ctx); prop = gf_filter_pck_enum_properties(pck, &idx, &p4cc, &pname); @@ -3980,10 +4146,10 @@ if (!pname) return GF_JS_EXCEPTION(ctx); res = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, res, "name", JS_NewString(ctx, pname)); - JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); - JS_SetPropertyStr(ctx, res, "value", jsf_NewProp(ctx, prop)); - return res; + JS_SetPropertyStr(ctx, res, "name", JS_NewString(ctx, pname)); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + JS_SetPropertyStr(ctx, res, "value", jsf_NewProp(ctx, prop)); + return res; } static JSValue jsf_pck_get_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3993,10 +4159,10 @@ const GF_PropertyValue *prop; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; - name = JS_ToCString(ctx, argv0); + name = JS_ToCString(ctx, argv0); if (!name) return GF_JS_EXCEPTION(ctx); if ((argc>1) && JS_ToBool(ctx, argv1)) { prop = gf_filter_pck_get_property_str(pck, name); @@ -4014,7 +4180,7 @@ if (!prop) return JS_NULL; res = jsf_NewPropTranslate(ctx, prop, p4cc); } - return res; + return res; } static JSValue jsf_pck_ref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -4022,9 +4188,9 @@ Bool is_ref_props = GF_FALSE; GF_JSPckCtx *ref_pckctx; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - if (argc && JS_ToBool(ctx, argv0)) is_ref_props = GF_TRUE; + if (argc && JS_ToBool(ctx, argv0)) is_ref_props = GF_TRUE; ref_pckctx = gf_list_pop_back(pckctx->jspid->jsf->pck_res); if (!ref_pckctx) { @@ -4050,7 +4216,7 @@ { GF_JSPidCtx *jspid; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); if (!(pckctx->flags & GF_JS_PCK_IS_REF)) return js_throw_err_msg(ctx, GF_BAD_PARAM, "Attempt to unref a non-reference packet"); @@ -4069,14 +4235,14 @@ { GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - if (! pckctx->jspid->jsf->filter->in_process) + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + if (!pckctx->jspid->jsf->is_custom && ! pckctx->jspid->jsf->filter->in_process_callback) return js_throw_err_msg(ctx, GF_BAD_PARAM, "Filter %s attempt to send packet outside process callback not allowed!\n", pckctx->jspid->jsf->filter->name); - pck = pckctx->pck; - if (!JS_IsUndefined(pckctx->data_ab)) { - JS_FreeValue(ctx, pckctx->data_ab); - pckctx->data_ab = JS_UNDEFINED; + pck = pckctx->pck; + if (!JS_IsUndefined(pckctx->data_ab)) { + JS_FreeValue(ctx, pckctx->data_ab); + pckctx->data_ab = JS_UNDEFINED; } gf_filter_pck_send(pck); JS_SetOpaque(this_val, NULL); @@ -4092,9 +4258,9 @@ { GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; - pckctx->pck = NULL; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; + pckctx->pck = NULL; gf_filter_pck_discard(pck); return JS_UNDEFINED; } @@ -4106,10 +4272,10 @@ const char *name=NULL; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; - name = JS_ToCString(ctx, argv0); + name = JS_ToCString(ctx, argv0); if (!name) return GF_JS_EXCEPTION(ctx); if ((argc>2) && JS_ToBool(ctx, argv2)) { @@ -4155,7 +4321,7 @@ } } if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pck_append_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -4166,12 +4332,12 @@ GF_Err e; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck || !argc) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck || !argc) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; if (JS_IsString(argv0) || JS_IsInteger(argv0)) { - u32 len; - const char *str = NULL; + u32 len; + const char *str = NULL; if (JS_IsInteger(argv0)) { JS_ToInt32(ctx, &len, argv0); @@ -4219,8 +4385,8 @@ GF_Err e; GF_FilterPacket *pck; GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); - if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); - pck = pckctx->pck; + if (!pckctx || !pckctx->pck) return GF_JS_EXCEPTION(ctx); + pck = pckctx->pck; if (argc) { JS_ToInt32(ctx, &len, argv0); } @@ -4228,7 +4394,7 @@ if (e) return js_throw_err(ctx, e); jsf_pck_detach_ab(ctx, pckctx); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pck_copy_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -4237,11 +4403,11 @@ if (!pck_dst || !pck_dst->pck || !argc) return GF_JS_EXCEPTION(ctx); GF_JSPckCtx *pck_from = JS_GetOpaque(argv0, jsf_pck_class_id); - if (!pck_from || !pck_from->pck) - return GF_JS_EXCEPTION(ctx); - e = gf_filter_pck_merge_properties(pck_from->pck, pck_dst->pck); + if (!pck_from || !pck_from->pck) + return GF_JS_EXCEPTION(ctx); + e = gf_filter_pck_merge_properties(pck_from->pck, pck_dst->pck); if (e) return js_throw_err(ctx, e); - return JS_UNDEFINED; + return JS_UNDEFINED; } static JSValue jsf_pck_clone(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -4256,7 +4422,7 @@ pck_cached = JS_GetOpaque(argv0, jsf_pck_class_id); if (pck_cached && !pck_cached->pck) return GF_JS_EXCEPTION(ctx); - } + } cloned = gf_filter_pck_dangling_copy(pck_src->pck, pck_cached ? pck_cached->pck : NULL); if (!cloned) return js_throw_err(ctx, GF_OUT_OF_MEM); @@ -4278,51 +4444,51 @@ pck_cached->data_ab = JS_UNDEFINED; pck_cached->flags = GF_JS_PCK_IS_DANGLING; JS_SetOpaque(res, pck_cached); - return res; + return res; } static const JSCFunctionListEntry jsf_pck_funcs = { - JS_CGETSET_MAGIC_DEF("start", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_START), - JS_CGETSET_MAGIC_DEF("end", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_END), - JS_CGETSET_MAGIC_DEF("dts", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DTS), - JS_CGETSET_MAGIC_DEF("cts", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CTS), - JS_CGETSET_MAGIC_DEF("dur", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DUR), - JS_CGETSET_MAGIC_DEF("sap", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SAP), - JS_CGETSET_MAGIC_DEF("timescale", jsf_pck_get_prop, NULL, JSF_PCK_TIMESCALE), - JS_CGETSET_MAGIC_DEF("interlaced", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_INTERLACED), - JS_CGETSET_MAGIC_DEF("corrupted", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CORRUPTED), - JS_CGETSET_MAGIC_DEF("seek", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SEEK), - JS_CGETSET_MAGIC_DEF("byte_offset", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_BYTE_OFFSET), - JS_CGETSET_MAGIC_DEF("roll", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_ROLL), - JS_CGETSET_MAGIC_DEF("crypt", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CRYPT), - JS_CGETSET_MAGIC_DEF("clock_type", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CLOCK_TYPE), - JS_CGETSET_MAGIC_DEF("carousel", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CAROUSEL), - JS_CGETSET_MAGIC_DEF("seqnum", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SEQNUM), - JS_CGETSET_MAGIC_DEF("blocking_ref", jsf_pck_get_prop, NULL, JSF_PCK_BLOCKING_REF), - JS_CGETSET_MAGIC_DEF("is_leading", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_IS_LEADING), - JS_CGETSET_MAGIC_DEF("depends_on", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DEPENDS_ON), - JS_CGETSET_MAGIC_DEF("depended_on", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DEPENDED_ON), - JS_CGETSET_MAGIC_DEF("redundant", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_HAS_REDUNDANT), - JS_CGETSET_MAGIC_DEF("size", jsf_pck_get_prop, NULL, JSF_PCK_SIZE), - JS_CGETSET_MAGIC_DEF("data", jsf_pck_get_prop, NULL, JSF_PCK_DATA), - JS_CGETSET_MAGIC_DEF("frame_ifce", jsf_pck_get_prop, NULL, JSF_PCK_FRAME_IFCE), - JS_CGETSET_MAGIC_DEF("frame_ifce_gl", jsf_pck_get_prop, NULL, JSF_PCK_FRAME_IFCE_GL), - JS_CGETSET_MAGIC_DEF("has_properties", jsf_pck_get_prop, NULL, JSF_PCK_HAS_PROPERTIES), - - JS_CFUNC_DEF("set_readonly", 0, jsf_pck_set_readonly), - JS_CFUNC_DEF("enum_properties", 0, jsf_pck_enum_properties), - JS_CFUNC_DEF("get_prop", 0, jsf_pck_get_property), - JS_CFUNC_DEF("ref", 0, jsf_pck_ref), - JS_CFUNC_DEF("unref", 0, jsf_pck_unref), - JS_CFUNC_DEF("send", 0, jsf_pck_send), - JS_CFUNC_DEF("discard", 0, jsf_pck_discard), - JS_CFUNC_DEF("set_prop", 0, jsf_pck_set_property), - JS_CFUNC_DEF("append", 0, jsf_pck_append_data), - JS_CFUNC_DEF("truncate", 0, jsf_pck_truncate), - JS_CFUNC_DEF("copy_props", 0, jsf_pck_copy_props), - JS_CFUNC_DEF("clone", 0, jsf_pck_clone), + JS_CGETSET_MAGIC_DEF("start", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_START), + JS_CGETSET_MAGIC_DEF("end", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_END), + JS_CGETSET_MAGIC_DEF("dts", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DTS), + JS_CGETSET_MAGIC_DEF("cts", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CTS), + JS_CGETSET_MAGIC_DEF("dur", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DUR), + JS_CGETSET_MAGIC_DEF("sap", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SAP), + JS_CGETSET_MAGIC_DEF("timescale", jsf_pck_get_prop, NULL, JSF_PCK_TIMESCALE), + JS_CGETSET_MAGIC_DEF("interlaced", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_INTERLACED), + JS_CGETSET_MAGIC_DEF("corrupted", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CORRUPTED), + JS_CGETSET_MAGIC_DEF("seek", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SEEK), + JS_CGETSET_MAGIC_DEF("byte_offset", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_BYTE_OFFSET), + JS_CGETSET_MAGIC_DEF("roll", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_ROLL), + JS_CGETSET_MAGIC_DEF("crypt", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CRYPT), + JS_CGETSET_MAGIC_DEF("clock_type", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CLOCK_TYPE), + JS_CGETSET_MAGIC_DEF("carousel", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CAROUSEL), + JS_CGETSET_MAGIC_DEF("seqnum", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SEQNUM), + JS_CGETSET_MAGIC_DEF("blocking_ref", jsf_pck_get_prop, NULL, JSF_PCK_BLOCKING_REF), + JS_CGETSET_MAGIC_DEF("is_leading", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_IS_LEADING), + JS_CGETSET_MAGIC_DEF("depends_on", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DEPENDS_ON), + JS_CGETSET_MAGIC_DEF("depended_on", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DEPENDED_ON), + JS_CGETSET_MAGIC_DEF("redundant", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_HAS_REDUNDANT), + JS_CGETSET_MAGIC_DEF("size", jsf_pck_get_prop, NULL, JSF_PCK_SIZE), + JS_CGETSET_MAGIC_DEF("data", jsf_pck_get_prop, NULL, JSF_PCK_DATA), + JS_CGETSET_MAGIC_DEF("frame_ifce", jsf_pck_get_prop, NULL, JSF_PCK_FRAME_IFCE), + JS_CGETSET_MAGIC_DEF("frame_ifce_gl", jsf_pck_get_prop, NULL, JSF_PCK_FRAME_IFCE_GL), + JS_CGETSET_MAGIC_DEF("has_properties", jsf_pck_get_prop, NULL, JSF_PCK_HAS_PROPERTIES), + + JS_CFUNC_DEF("set_readonly", 0, jsf_pck_set_readonly), + JS_CFUNC_DEF("enum_properties", 0, jsf_pck_enum_properties), + JS_CFUNC_DEF("get_prop", 0, jsf_pck_get_property), + JS_CFUNC_DEF("ref", 0, jsf_pck_ref), + JS_CFUNC_DEF("unref", 0, jsf_pck_unref), + JS_CFUNC_DEF("send", 0, jsf_pck_send), + JS_CFUNC_DEF("discard", 0, jsf_pck_discard), + JS_CFUNC_DEF("set_prop", 0, jsf_pck_set_property), + JS_CFUNC_DEF("append", 0, jsf_pck_append_data), + JS_CFUNC_DEF("truncate", 0, jsf_pck_truncate), + JS_CFUNC_DEF("copy_props", 0, jsf_pck_copy_props), + JS_CFUNC_DEF("clone", 0, jsf_pck_clone), }; @@ -4354,26 +4520,33 @@ static GF_Err jsfilter_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { - JSValue ret; GF_Err e = GF_OK; GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); GF_JSPidCtx *pctx; + JSValue ret; if (!jsf) return GF_BAD_PARAM; pctx = gf_filter_pid_get_udta(pid); if (is_remove) { - gf_assert(pctx); + //already removed, don't complain + if (!pctx) return GF_OK; gf_js_lock(jsf->ctx, GF_TRUE); - ret = JS_Call(jsf->ctx, jsf->funcsJSF_EVT_REMOVE_PID, jsf->filter_obj, 1, &pctx->jsobj); - if (JS_IsException(ret)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("%s Error removing pid\n", jsf->log_name)); - js_dump_error(jsf->ctx); - e = GF_BAD_PARAM; + if (!JS_IsFunction(jsf->ctx, jsf->funcsJSF_EVT_REMOVE_PID)) { + e = GF_OK; + } else { + ret = JS_Call(jsf->ctx, jsf->funcsJSF_EVT_REMOVE_PID, jsf->filter_obj, 1, &pctx->jsobj); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("%s Error removing pid\n", jsf->log_name)); + js_dump_error(jsf->ctx); + e = GF_BAD_PARAM; + } + else if (JS_IsInteger(ret)) + JS_ToInt32(jsf->ctx, (int*)&e, ret); + + JS_FreeValue(jsf->ctx, ret); } - else if (JS_IsInteger(ret)) - JS_ToInt32(jsf->ctx, (int*)&e, ret); //reset first packet obj if set if (pctx->pck_head) { @@ -4384,7 +4557,6 @@ pctx->pck_head->jspid = NULL; } } - JS_FreeValue(jsf->ctx, ret); //force cleanup of all refs gf_js_call_gc(jsf->ctx); @@ -4403,7 +4575,7 @@ if (!pctx) { GF_SAFEALLOC(pctx, GF_JSPidCtx); if (!pctx) return GF_OUT_OF_MEM; - + pctx->jsf = jsf; pctx->pid = pid; pctx->jsobj = JS_NewObjectClass(jsf->ctx, jsf_pid_class_id); @@ -4428,16 +4600,16 @@ return e; } +#define DEF_CONST( _val ) \ + JS_SetPropertyStr(ctx, global_obj, #_val, JS_NewInt32(ctx, _val)); + void js_load_constants(JSContext *ctx, JSValue global_obj) { - JSValue val; + JSValue val; - val = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, val, "log", JS_NewCFunction(ctx, js_print, "log", 1)); - JS_SetPropertyStr(ctx, global_obj, "console", val); - -#define DEF_CONST( _val ) \ - JS_SetPropertyStr(ctx, global_obj, #_val, JS_NewInt32(ctx, _val)); + val = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, val, "log", JS_NewCFunction(ctx, js_print, "log", 1)); + JS_SetPropertyStr(ctx, global_obj, "console", val); DEF_CONST(GF_LOG_ERROR) DEF_CONST(GF_LOG_WARNING) @@ -4493,6 +4665,8 @@ DEF_CONST(GF_FEVT_BUFFER_REQ) DEF_CONST(GF_FEVT_CAPS_CHANGE) DEF_CONST(GF_FEVT_CONNECT_FAIL) + DEF_CONST(GF_FEVT_DASH_QUALITY_SELECT) + DEF_CONST(GF_FEVT_USER) DEF_CONST(GF_STATS_LOCAL) @@ -4595,214 +4769,6 @@ DEF_CONST(GF_EVENT_CODEC_SLOW) DEF_CONST(GF_EVENT_CODEC_OK) - DEF_CONST(GF_KEY_UNIDENTIFIED) - DEF_CONST(GF_KEY_ACCEPT) - DEF_CONST(GF_KEY_AGAIN) - DEF_CONST(GF_KEY_ALLCANDIDATES) - DEF_CONST(GF_KEY_ALPHANUM) - DEF_CONST(GF_KEY_ALT) - DEF_CONST(GF_KEY_ALTGRAPH) - DEF_CONST(GF_KEY_APPS) - DEF_CONST(GF_KEY_ATTN) - DEF_CONST(GF_KEY_BROWSERBACK) - DEF_CONST(GF_KEY_BROWSERFAVORITES) - DEF_CONST(GF_KEY_BROWSERFORWARD) - DEF_CONST(GF_KEY_BROWSERHOME) - DEF_CONST(GF_KEY_BROWSERREFRESH) - DEF_CONST(GF_KEY_BROWSERSEARCH) - DEF_CONST(GF_KEY_BROWSERSTOP) - DEF_CONST(GF_KEY_CAPSLOCK) - DEF_CONST(GF_KEY_CLEAR) - DEF_CONST(GF_KEY_CODEINPUT) - DEF_CONST(GF_KEY_COMPOSE) - DEF_CONST(GF_KEY_CONTROL) - DEF_CONST(GF_KEY_CRSEL) - DEF_CONST(GF_KEY_CONVERT) - DEF_CONST(GF_KEY_COPY) - DEF_CONST(GF_KEY_CUT) - DEF_CONST(GF_KEY_DOWN) - DEF_CONST(GF_KEY_END) - DEF_CONST(GF_KEY_ENTER) - DEF_CONST(GF_KEY_ERASEEOF) - DEF_CONST(GF_KEY_EXECUTE) - DEF_CONST(GF_KEY_EXSEL) - DEF_CONST(GF_KEY_F1) - DEF_CONST(GF_KEY_F2) - DEF_CONST(GF_KEY_F3) - DEF_CONST(GF_KEY_F4) - DEF_CONST(GF_KEY_F5) - DEF_CONST(GF_KEY_F6) - DEF_CONST(GF_KEY_F7) - DEF_CONST(GF_KEY_F8) - DEF_CONST(GF_KEY_F9) - DEF_CONST(GF_KEY_F10) - DEF_CONST(GF_KEY_F11) - DEF_CONST(GF_KEY_F12) - DEF_CONST(GF_KEY_F13) - DEF_CONST(GF_KEY_F14) - DEF_CONST(GF_KEY_F15) - DEF_CONST(GF_KEY_F16) - DEF_CONST(GF_KEY_F17) - DEF_CONST(GF_KEY_F18) - DEF_CONST(GF_KEY_F19) - DEF_CONST(GF_KEY_F20) - DEF_CONST(GF_KEY_F21) - DEF_CONST(GF_KEY_F22) - DEF_CONST(GF_KEY_F23) - DEF_CONST(GF_KEY_F24) - DEF_CONST(GF_KEY_FINALMODE) - DEF_CONST(GF_KEY_FIND) - DEF_CONST(GF_KEY_FULLWIDTH) - DEF_CONST(GF_KEY_HALFWIDTH) - DEF_CONST(GF_KEY_HANGULMODE) - DEF_CONST(GF_KEY_HANJAMODE) - DEF_CONST(GF_KEY_HELP) - DEF_CONST(GF_KEY_HIRAGANA) - DEF_CONST(GF_KEY_HOME) - DEF_CONST(GF_KEY_INSERT) - DEF_CONST(GF_KEY_JAPANESEHIRAGANA) - DEF_CONST(GF_KEY_JAPANESEKATAKANA) - DEF_CONST(GF_KEY_JAPANESEROMAJI) - DEF_CONST(GF_KEY_JUNJAMODE) - DEF_CONST(GF_KEY_KANAMODE) - DEF_CONST(GF_KEY_KANJIMODE) - DEF_CONST(GF_KEY_KATAKANA) - DEF_CONST(GF_KEY_LAUNCHAPPLICATION1) - DEF_CONST(GF_KEY_LAUNCHAPPLICATION2) - DEF_CONST(GF_KEY_LAUNCHMAIL) - DEF_CONST(GF_KEY_LEFT) - DEF_CONST(GF_KEY_META) - DEF_CONST(GF_KEY_MEDIANEXTTRACK) - DEF_CONST(GF_KEY_MEDIAPLAYPAUSE) - DEF_CONST(GF_KEY_MEDIAPREVIOUSTRACK) - DEF_CONST(GF_KEY_MEDIASTOP) - DEF_CONST(GF_KEY_MODECHANGE) - DEF_CONST(GF_KEY_NONCONVERT) - DEF_CONST(GF_KEY_NUMLOCK) - DEF_CONST(GF_KEY_PAGEDOWN) - DEF_CONST(GF_KEY_PAGEUP) - DEF_CONST(GF_KEY_PASTE) - DEF_CONST(GF_KEY_PAUSE) - DEF_CONST(GF_KEY_PLAY) - DEF_CONST(GF_KEY_PREVIOUSCANDIDATE) - DEF_CONST(GF_KEY_PRINTSCREEN) - DEF_CONST(GF_KEY_PROCESS) - DEF_CONST(GF_KEY_PROPS) - DEF_CONST(GF_KEY_RIGHT) - DEF_CONST(GF_KEY_ROMANCHARACTERS) - DEF_CONST(GF_KEY_SCROLL) - DEF_CONST(GF_KEY_SELECT) - DEF_CONST(GF_KEY_SELECTMEDIA) - DEF_CONST(GF_KEY_SHIFT) - DEF_CONST(GF_KEY_STOP) - DEF_CONST(GF_KEY_UP) - DEF_CONST(GF_KEY_UNDO) - DEF_CONST(GF_KEY_VOLUMEDOWN) - DEF_CONST(GF_KEY_VOLUMEMUTE) - DEF_CONST(GF_KEY_VOLUMEUP) - DEF_CONST(GF_KEY_WIN) - DEF_CONST(GF_KEY_ZOOM) - DEF_CONST(GF_KEY_BACKSPACE) - DEF_CONST(GF_KEY_TAB) - DEF_CONST(GF_KEY_CANCEL) - DEF_CONST(GF_KEY_ESCAPE) - DEF_CONST(GF_KEY_SPACE) - DEF_CONST(GF_KEY_EXCLAMATION) - DEF_CONST(GF_KEY_QUOTATION) - DEF_CONST(GF_KEY_NUMBER) - DEF_CONST(GF_KEY_DOLLAR) - DEF_CONST(GF_KEY_AMPERSAND) - DEF_CONST(GF_KEY_APOSTROPHE) - DEF_CONST(GF_KEY_LEFTPARENTHESIS) - DEF_CONST(GF_KEY_RIGHTPARENTHESIS) - DEF_CONST(GF_KEY_STAR) - DEF_CONST(GF_KEY_PLUS) - DEF_CONST(GF_KEY_COMMA) - DEF_CONST(GF_KEY_HYPHEN) - DEF_CONST(GF_KEY_FULLSTOP) - DEF_CONST(GF_KEY_SLASH) - DEF_CONST(GF_KEY_0) - DEF_CONST(GF_KEY_1) - DEF_CONST(GF_KEY_2) - DEF_CONST(GF_KEY_3) - DEF_CONST(GF_KEY_4) - DEF_CONST(GF_KEY_5) - DEF_CONST(GF_KEY_6) - DEF_CONST(GF_KEY_7) - DEF_CONST(GF_KEY_8) - DEF_CONST(GF_KEY_9) - DEF_CONST(GF_KEY_COLON) - DEF_CONST(GF_KEY_SEMICOLON) - DEF_CONST(GF_KEY_LESSTHAN) - DEF_CONST(GF_KEY_EQUALS) - DEF_CONST(GF_KEY_GREATERTHAN) - DEF_CONST(GF_KEY_QUESTION) - DEF_CONST(GF_KEY_AT) - DEF_CONST(GF_KEY_A) - DEF_CONST(GF_KEY_B) - DEF_CONST(GF_KEY_C) - DEF_CONST(GF_KEY_D) - DEF_CONST(GF_KEY_E) - DEF_CONST(GF_KEY_F) - DEF_CONST(GF_KEY_G) - DEF_CONST(GF_KEY_H) - DEF_CONST(GF_KEY_I) - DEF_CONST(GF_KEY_J) - DEF_CONST(GF_KEY_K) - DEF_CONST(GF_KEY_L) - DEF_CONST(GF_KEY_M) - DEF_CONST(GF_KEY_N) - DEF_CONST(GF_KEY_O) - DEF_CONST(GF_KEY_P) - DEF_CONST(GF_KEY_Q) - DEF_CONST(GF_KEY_R) - DEF_CONST(GF_KEY_S) - DEF_CONST(GF_KEY_T) - DEF_CONST(GF_KEY_U) - DEF_CONST(GF_KEY_V) - DEF_CONST(GF_KEY_W) - DEF_CONST(GF_KEY_X) - DEF_CONST(GF_KEY_Y) - DEF_CONST(GF_KEY_Z) - DEF_CONST(GF_KEY_LEFTSQUAREBRACKET) - DEF_CONST(GF_KEY_BACKSLASH) - DEF_CONST(GF_KEY_RIGHTSQUAREBRACKET) - DEF_CONST(GF_KEY_CIRCUM) - DEF_CONST(GF_KEY_UNDERSCORE) - DEF_CONST(GF_KEY_GRAVEACCENT) - DEF_CONST(GF_KEY_LEFTCURLYBRACKET) - DEF_CONST(GF_KEY_PIPE) - DEF_CONST(GF_KEY_RIGHTCURLYBRACKET) - DEF_CONST(GF_KEY_DEL) - DEF_CONST(GF_KEY_INVERTEXCLAMATION) - DEF_CONST(GF_KEY_DEADGRAVE) - DEF_CONST(GF_KEY_DEADEACUTE) - DEF_CONST(GF_KEY_DEADCIRCUM) - DEF_CONST(GF_KEY_DEADTILDE) - DEF_CONST(GF_KEY_DEADMACRON) - DEF_CONST(GF_KEY_DEADBREVE) - DEF_CONST(GF_KEY_DEADABOVEDOT) - DEF_CONST(GF_KEY_DEADDIARESIS) - DEF_CONST(GF_KEY_DEADRINGABOVE) - DEF_CONST(GF_KEY_DEADDOUBLEACUTE) - DEF_CONST(GF_KEY_DEADCARON) - DEF_CONST(GF_KEY_DEADCEDILLA) - DEF_CONST(GF_KEY_DEADOGONEK) - DEF_CONST(GF_KEY_DEADIOTA) - DEF_CONST(GF_KEY_EURO) - DEF_CONST(GF_KEY_DEADVOICESOUND) - DEF_CONST(GF_KEY_DEADSEMIVOICESOUND) - DEF_CONST(GF_KEY_CHANNELUP) - DEF_CONST(GF_KEY_CHANNELDOWN) - DEF_CONST(GF_KEY_TEXT) - DEF_CONST(GF_KEY_INFO) - DEF_CONST(GF_KEY_EPG) - DEF_CONST(GF_KEY_RECORD) - DEF_CONST(GF_KEY_BEGINPAGE) - DEF_CONST(GF_KEY_CELL_SOFT1) - DEF_CONST(GF_KEY_CELL_SOFT2) - DEF_CONST(GF_KEY_JOYSTICK) - DEF_CONST(GF_KEY_MOD_SHIFT) DEF_CONST(GF_KEY_MOD_CTRL) DEF_CONST(GF_KEY_MOD_ALT) @@ -4810,20 +4776,33 @@ DEF_CONST(GF_KEY_EXT_LEFT) DEF_CONST(GF_KEY_EXT_RIGHT) + DEF_CONST(GF_FS_CLASS_DEMULTIPLEXER) + DEF_CONST(GF_FS_CLASS_MULTIPLEXER) + DEF_CONST(GF_FS_CLASS_DECODER) + DEF_CONST(GF_FS_CLASS_ENCODER) + DEF_CONST(GF_FS_CLASS_CRYPTO) + DEF_CONST(GF_FS_CLASS_MM_IO) + DEF_CONST(GF_FS_CLASS_NETWORK_IO) + DEF_CONST(GF_FS_CLASS_SUBTITLE) + DEF_CONST(GF_FS_CLASS_AV) + DEF_CONST(GF_FS_CLASS_STREAM) + DEF_CONST(GF_FS_CLASS_FRAMING) + DEF_CONST(GF_FS_CLASS_TOOL) + - JS_SetPropertyStr(ctx, global_obj, "print", JS_NewCFunction(ctx, js_print, "print", 1)); - JS_SetPropertyStr(ctx, global_obj, "alert", JS_NewCFunction(ctx, js_print, "alert", 1)); + JS_SetPropertyStr(ctx, global_obj, "print", JS_NewCFunction(ctx, js_print, "print", 1)); + JS_SetPropertyStr(ctx, global_obj, "alert", JS_NewCFunction(ctx, js_print, "alert", 1)); //initialize filter event class JS_NewClassID(&jsf_event_class_id); JS_NewClass(JS_GetRuntime(ctx), jsf_event_class_id, &jsf_event_class); JSValue evt_proto = JS_NewObjectClass(ctx, jsf_event_class_id); - JS_SetPropertyFunctionList(ctx, evt_proto, jsf_event_funcs, countof(jsf_event_funcs)); - JS_SetClassProto(ctx, jsf_event_class_id, evt_proto); + JS_SetPropertyFunctionList(ctx, evt_proto, jsf_event_funcs, countof(jsf_event_funcs)); + JS_SetClassProto(ctx, jsf_event_class_id, evt_proto); JSValue evt_ctor = JS_NewCFunction2(ctx, jsf_event_constructor, "FilterEvent", 1, JS_CFUNC_constructor, 0); - JS_SetPropertyStr(ctx, global_obj, "FilterEvent", evt_ctor); + JS_SetPropertyStr(ctx, global_obj, "FilterEvent", evt_ctor); } static GF_Err jsfilter_initialize_ex(GF_Filter *filter, JSContext *custom_ctx) @@ -4831,10 +4810,10 @@ u8 *buf; u32 buf_len; u32 flags = JS_EVAL_TYPE_GLOBAL; - JSValue ret; - JSValue global_obj; - u32 i; - JSRuntime *rt; + JSValue ret; + JSValue global_obj; + u32 i; + JSRuntime *rt; GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); if (custom_ctx) { @@ -4880,39 +4859,39 @@ JS_NewClass(rt, jsf_filter_class_id, &jsf_filter_class); jsf->filter_obj = JS_NewObjectClass(jsf->ctx, jsf_filter_class_id); - JS_SetPropertyFunctionList(jsf->ctx, jsf->filter_obj, jsf_filter_funcs, countof(jsf_filter_funcs)); - JS_SetOpaque(jsf->filter_obj, jsf); - if (!custom_ctx) + JS_SetPropertyFunctionList(jsf->ctx, jsf->filter_obj, jsf_filter_funcs, countof(jsf_filter_funcs)); + JS_SetOpaque(jsf->filter_obj, jsf); + if (!custom_ctx) JS_SetPropertyStr(jsf->ctx, global_obj, "filter", jsf->filter_obj); //initialize filter instance class JS_NewClassID(&jsf_filter_inst_class_id); JS_NewClass(rt, jsf_filter_inst_class_id, &jsf_filter_inst_class); JSValue finst_proto = JS_NewObjectClass(jsf->ctx, jsf_filter_inst_class_id); - JS_SetPropertyFunctionList(jsf->ctx, finst_proto, jsf_filter_inst_funcs, countof(jsf_filter_inst_funcs)); - JS_SetClassProto(jsf->ctx, jsf_filter_inst_class_id, finst_proto); + JS_SetPropertyFunctionList(jsf->ctx, finst_proto, jsf_filter_inst_funcs, countof(jsf_filter_inst_funcs)); + JS_SetClassProto(jsf->ctx, jsf_filter_inst_class_id, finst_proto); //initialize filter pid class JS_NewClassID(&jsf_pid_class_id); JS_NewClass(rt, jsf_pid_class_id, &jsf_pid_class); JSValue pid_proto = JS_NewObjectClass(jsf->ctx, jsf_pid_class_id); - JS_SetPropertyFunctionList(jsf->ctx, pid_proto, jsf_pid_funcs, countof(jsf_pid_funcs)); - JS_SetClassProto(jsf->ctx, jsf_pid_class_id, pid_proto); + JS_SetPropertyFunctionList(jsf->ctx, pid_proto, jsf_pid_funcs, countof(jsf_pid_funcs)); + JS_SetClassProto(jsf->ctx, jsf_pid_class_id, pid_proto); //initialize filter packet class JS_NewClassID(&jsf_pck_class_id); JS_NewClass(rt, jsf_pck_class_id, &jsf_pck_class); JSValue pck_proto = JS_NewObjectClass(jsf->ctx, jsf_pck_class_id); - JS_SetPropertyFunctionList(jsf->ctx, pck_proto, jsf_pck_funcs, countof(jsf_pck_funcs)); - JS_SetClassProto(jsf->ctx, jsf_pck_class_id, pck_proto); + JS_SetPropertyFunctionList(jsf->ctx, pck_proto, jsf_pck_funcs, countof(jsf_pck_funcs)); + JS_SetClassProto(jsf->ctx, jsf_pck_class_id, pck_proto); - if (!custom_ctx) + if (!custom_ctx) JS_SetPropertyStr(jsf->ctx, global_obj, "_gpac_log_name", JS_NewString(jsf->ctx, gf_file_basename(jsf->js) ) ); - JS_FreeValue(jsf->ctx, global_obj); + JS_FreeValue(jsf->ctx, global_obj); - if (custom_ctx) return GF_OK; + if (custom_ctx) return GF_OK; //load script @@ -4940,8 +4919,8 @@ } for (i=0; i<JSF_EVT_LAST_DEFINED; i++) { - jsf->funcsi = JS_UNDEFINED; - } + jsf->funcsi = JS_UNDEFINED; + } if (!gf_opts_get_bool("core", "no-js-mods") && JS_DetectModule((char *)buf, buf_len)) { @@ -4961,7 +4940,7 @@ if (JS_IsException(ret)) { GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("JSF Error loading script %s\n", jsf->js)); - js_dump_error(jsf->ctx); + js_dump_error(jsf->ctx); JS_FreeValue(jsf->ctx, ret); return GF_BAD_PARAM; } @@ -5133,7 +5112,7 @@ val = jsf_NewProp(jsf->ctx, new_val); if (!jsf->initialized) { - JS_SetPropertyStr(jsf->ctx, jsf->filter_obj, arg_name, val); + JS_SetPropertyStr(jsf->ctx, jsf->filter_obj, arg_name, val); gf_js_lock(jsf->ctx, GF_FALSE); return GF_OK; } @@ -5144,7 +5123,7 @@ ret = JS_Call(jsf->ctx, jsf->funcsJSF_EVT_UPDATE_ARG, jsf->filter_obj, 2, args); if (JS_IsException(ret)) { GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("%s Error updating arg\n", jsf->log_name)); - js_dump_error(jsf->ctx); + js_dump_error(jsf->ctx); e = GF_BAD_PARAM; } if (JS_IsInteger(ret)) @@ -5227,8 +5206,8 @@ static const GF_FilterCapability JSFilterCaps = { - CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), - CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), }; GF_FilterRegister JSFilterRegister = { @@ -5236,7 +5215,7 @@ GF_FS_SET_DESCRIPTION("JavaScript filter") GF_FS_SET_HELP("This filter runs a javascript file specified in -js() defining a new JavaScript filter.\n" " \n" - "For more information on how to use JS filters, please check https://wiki.gpac.io/jsfilter\n") + "For more information on how to use JS filters, please check https://wiki.gpac.io/Howtos/jsf/jsfilter\n") .private_size = sizeof(GF_JSFilterCtx), .flags = GF_FS_REG_SCRIPT | GF_FS_REG_TEMP_INIT, .args = JSFilterArgs, @@ -5250,6 +5229,7 @@ // .probe_url = jsfilter_probe_url, // .probe_data = jsfilter_probe_data // .reconfigure_output = jsfilter_reconfigure_output + .hint_class_type = GF_FS_CLASS_TOOL }; @@ -5271,8 +5251,8 @@ ((GF_FilterRegister *) filter->freg)->process_event = jsfilter_process_event; // ((GF_FilterRegister *) filter->freg)->reconfigure_output = jsfilter_reconfigure_output; // ((GF_FilterRegister *) filter->freg)->probe_data = jsfilter_probe_data; - //signal reg is script, so we don't free the filter reg caps as with custom filters - ((GF_FilterRegister *) filter->freg)->flags |= GF_FS_REG_SCRIPT; + + //do NOT signal reg is script, only custom (done by caller) return JS_DupValue(ctx, jsf->filter_obj); } @@ -5282,6 +5262,19 @@ if (!jsf || !jsf->is_custom) return NULL; return jsf->filter; } +JSContext *jsf_custom_filter_context(GF_Filter *f) +{ + GF_JSFilterCtx *jsf = (GF_JSFilterCtx *) gf_filter_get_udta(f); + if (!jsf || !jsf->is_custom) return NULL; + return jsf->ctx; +} +JSValue jsf_custom_filter_obj(GF_Filter *f) +{ + GF_JSFilterCtx *jsf = (GF_JSFilterCtx *) gf_filter_get_udta(f); + if (!jsf || !jsf->is_custom) return JS_NULL; + return jsf->filter_obj; +} + #else @@ -5291,4 +5284,3 @@ } #endif -
View file
gpac-2.4.0.tar.gz/src/filters/load_bt_xmt.c -> gpac-26.02.0.tar.gz/src/filters/load_bt_xmt.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / Scene Context loader filter @@ -649,7 +649,18 @@ esd = (GF_ESD*)gf_list_get(od->ESDescriptors, 0); if (!esd) { if (od->URLString) { - ODS_SetupOD(priv->scene, od); + + switch (od->tag) { + case GF_ODF_IOD_TAG: + case GF_ODF_OD_TAG: + case GF_ODF_ISOM_IOD_TAG: + case GF_ODF_ISOM_OD_TAG: + ODS_SetupOD(priv->scene, od); + break; + default: + break; + } + } gf_odf_desc_del((GF_Descriptor *) od); continue; @@ -989,10 +1000,10 @@ GF_FilterRegister CTXLoadRegister = { .name = "btplay", - GF_FS_SET_DESCRIPTION("BT/XMT/X3D loader") + GF_FS_SET_DESCRIPTION("BT/XMT/X3D decoder") GF_FS_SET_HELP("This filter parses MPEG-4 BIFS (BT and XMT), VRML97 and X3D (wrl and XML) files directly into the scene graph of the compositor.\n" "\n" - "When -sax_dur=N() is set, the filter will do a progressive load of the source and cancel current loading when processing time is higher than `N`.\n") + "When -sax_dur() is set to `N`, the filter will do a progressive load of the source and cancel current loading when processing time is higher than `N`.\n") .private_size = sizeof(CTXLoadPriv), .flags = GF_FS_REG_MAIN_THREAD, .args = CTXLoadArgs, @@ -1002,6 +1013,7 @@ .configure_pid = ctxload_configure_pid, .process_event = ctxload_process_event, .probe_data = ctxload_probe_data, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif //defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SCENEGRAPH)
View file
gpac-2.4.0.tar.gz/src/filters/load_svg.c -> gpac-26.02.0.tar.gz/src/filters/load_svg.c
Changed
@@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / SVG loader filter @@ -485,10 +485,10 @@ }; GF_FilterRegister SVGInRegister = { .name = "svgplay", - GF_FS_SET_DESCRIPTION("SVG loader") + GF_FS_SET_DESCRIPTION("SVG decoder") GF_FS_SET_HELP("This filter parses SVG files directly into the scene graph of the compositor.\n" "\n" - "When -sax_dur=N() is set, the filter will do a progressive load of the source and cancel current loading when processing time is higher than `N`.\n") + "When -sax_dur() is set to `N`, the filter will do a progressive load of the source and cancel current loading when processing time is higher than `N`.\n") .private_size = sizeof(SVGIn), .flags = GF_FS_REG_MAIN_THREAD, .args = SVGInArgs, @@ -496,6 +496,7 @@ .process = svgin_process, .configure_pid = svgin_configure_pid, .process_event = svgin_process_event, + .hint_class_type = GF_FS_CLASS_DECODER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/load_text.c -> gpac-26.02.0.tar.gz/src/filters/load_text.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / text import filter @@ -53,20 +53,22 @@ typedef struct __txtin_ctx GF_TXTIn; -enum -{ +GF_OPT_ENUM (GF_TXTInTextStreamMode, STXT_MODE_STXT=0, + STXT_MODE_SBTT, STXT_MODE_TX3G, STXT_MODE_VTT, -}; + STXT_MODE_WEBVTT, +); struct __txtin_ctx { //opts - u32 width, height, txtx, txty, fontsize, stxtmod; + u32 width, height, txtx, txty, fontsize; + GF_TXTInTextStreamMode stxtmod; s32 zorder; const char *fontname, *lang, *ttml_zero; - Bool nodefbox, noflush, webvtt, ttml_embed, no_empty; + Bool nodefbox, noflush, ttml_embed, no_empty; u32 timescale; GF_Fraction fps; Bool ttml_split; @@ -84,19 +86,25 @@ Bool is_loaded; Bool is_setup; + Bool full_file_only; + //set wenever text samples are no longer dispatched due to blocking mode + Bool is_suspended; + Bool in_over; GF_Err (*text_process)(GF_Filter *filter, GF_TXTIn *ctx, GF_FilterPacket *ipck); s32 unicode_type; FILE *src; - Bool is_temp; - GF_FileIO *fio; GF_BitStream *bs_w; Bool first_samp; Bool hdr_parsed; - Bool unframed, simple_text; + //0: PID is unframed (file source) + //1: source is framed (but in "unframe" format), used in text conversion + //2: source is framed SRT with inband headers + u32 pid_framed; + Bool single_text_chunk; //state vars for srt u32 state, default_color; @@ -122,7 +130,6 @@ //TTML state GF_XMLNode *root_working_copy, *body_node; - GF_DOMParser *parser_working_copy; Bool non_compliant_ttml; u32 tick_rate, ttml_fps_num, ttml_fps_den, ttml_sfps; GF_List *ttml_resources; @@ -132,7 +139,6 @@ #ifndef GPAC_DISABLE_SWF_IMPORT //SWF text SWFReader *swf_parse; - Bool do_suspend; #endif Bool vtt_to_tx3g; @@ -143,6 +149,11 @@ Bool forced_sub; u32 has_forced; + + //temp storage when no input file - we need this because all our text parsers rely on FILE + GF_Blob tmp_blob; + u32 tmp_buf_alloc; + char *blob_name; }; typedef struct @@ -238,6 +249,12 @@ ctx->end = count; } +static Bool txtin_check_blocking(GF_TXTIn *ctx) +{ + if (!ctx->pid_framed && gf_filter_pid_would_block(ctx->opid) && !ctx->tmp_blob.size) return GF_TRUE; + return GF_FALSE; +} + static GF_Err gf_text_guess_format(GF_TXTIn *ctx, const char *filename, u32 *fmt) { char szLine2048, *line; @@ -304,19 +321,28 @@ -char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type) +char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type, Bool *io_progress) { u32 i, j, len; + u32 start_pos = (u32) gf_ftell(txt_in); char *sOK; char szLineConv2048; unsigned short *sptr; + Bool in_eof = *io_progress; + *io_progress = GF_FALSE; memset(szLine, 0, sizeof(char)*lineSize); sOK = gf_fgets(szLine, lineSize, txt_in); if (!sOK) return NULL; if (unicode_type<=1) { j=0; + //make sure line is completely loaded, if not indicate transfer is in progress len = (u32) strlen(szLine); + if (!in_eof && len && (szLinelen-1!='\n')) { + gf_fseek(txt_in, start_pos, SEEK_SET); + *io_progress = GF_TRUE; + return NULL; + } for (i=0; i<len; i++) { if (!unicode_type && (szLinei & 0x80)) { /*non UTF8 (likely some win-CP)*/ @@ -409,6 +435,14 @@ strcpy(szLine, szLineConv); /*this is ugly indeed: since input is UTF16-LE, there are many chances the gf_fgets never reads the \0 after a \n*/ if (unicode_type==3) gf_fgetc(txt_in); + + //make sure line is completely loaded, if not indicate transfer is in progress + len = (u32) strlen(szLine); + if (!in_eof && len && (szLinelen-1!='\n')) { + gf_fseek(txt_in, start_pos, SEEK_SET); + *io_progress = GF_TRUE; + return NULL; + } return sOK; } @@ -438,8 +472,9 @@ gf_fseek(ctx->src, 0, SEEK_SET); while (!gf_feof(ctx->src)) { u64 end; + Bool in_progress = GF_TRUE; char szLine2048; - char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type, &in_progress); if (!sOK) break; REM_TRAIL_MARKS(szLine, "\r\n\t ") @@ -595,7 +630,7 @@ GF_TextSampleDescriptor *sd; if (!gen_dsi_only) { - if (!ctx->unframed && !ctx->src) + if (!ctx->pid_framed && !ctx->src) ctx->src = gf_fopen(ctx->file_name, "rb"); if (!ctx->src) return GF_URL_ERROR; @@ -614,10 +649,17 @@ if (!ctx->timescale) ctx->timescale = 1000; OCR_ES_ID = ID = 0; - if (!ctx->unframed) { + if (!ctx->pid_framed) { if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_TX3G) ); + + if (ctx->stxtmod == STXT_MODE_STXT) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_SIMPLE_TEXT) ); + else if (ctx->stxtmod == STXT_MODE_SBTT) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_SUBS_TEXT) ); + else + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_TX3G) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); if (file_size) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(file_size) ); @@ -668,7 +710,7 @@ ctx->samp = gf_isom_new_text_sample(); ctx->prev_end = 0; - if (!ctx->unframed) { + if (!ctx->pid_framed) { ctx->state = 0; ctx->end = ctx->start = 0; ctx->curLine = 0; @@ -683,11 +725,10 @@ { GF_FilterPacket *dst_pck; u8 *pck_data; - u32 size; if (!txt_samp) return; - if ((!txt_samp->text || !txt_samp->len) && ctx->no_empty) + if ((!txt_samp->text || !txt_samp->len) && (ctx->no_empty || (ctx->pid_framed==2))) return; if (ctx->seek_state==2) { @@ -697,18 +738,27 @@ ctx->seek_state = 0; } - size = gf_isom_text_sample_size(txt_samp); + if (!ctx->pid_framed && (ctx->stxtmod <=STXT_MODE_SBTT)) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, txt_samp->len, &pck_data); + if (!dst_pck) return; + if (txt_samp->text) { + memcpy(pck_data, txt_samp->text, txt_samp->len); + } + } else { + u32 size = gf_isom_text_sample_size(txt_samp); - dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &pck_data); - if (!dst_pck) return; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &pck_data); + if (!dst_pck) return; + gf_bs_reassign_buffer(ctx->bs_w, pck_data, size); + gf_isom_text_sample_write_bs(txt_samp, ctx->bs_w); + } ctx->has_forced |= 4; - gf_bs_reassign_buffer(ctx->bs_w, pck_data, size); - gf_isom_text_sample_write_bs(txt_samp, ctx->bs_w); - - ts = gf_timestamp_rescale(ts, 1000, ctx->timescale); - duration = (u32) gf_timestamp_rescale(duration, 1000, ctx->timescale); + if (!ctx->pid_framed) { + ts = gf_timestamp_rescale(ts, 1000, ctx->timescale); + duration = (u32) gf_timestamp_rescale(duration, 1000, ctx->timescale); + } gf_filter_pck_set_sap(dst_pck, is_rap ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); gf_filter_pck_set_cts(dst_pck, ts); gf_filter_pck_set_duration(dst_pck, duration); @@ -721,13 +771,19 @@ u32 i, char_line, j, rem_styles, len; Bool rem_color; char *ptr = szLine; - unsigned short uniLine5000, uniText5000, *sptr; + unsigned short *uniLine, *uniText, *sptr; char szText2048; - len = gf_utf8_mbstowcs(uniLine, 5000, (const char **) &ptr); + len = (u32)(strlen(szLine)/2)*2+2; + uniLine = gf_malloc(sizeof(u16)*len); + uniText = gf_malloc(sizeof(u16)*len); + + len = gf_utf8_mbstowcs(uniLine, len+1, (const char **) &ptr); if (len == GF_UTF8_FAIL) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TXTIn Invalid UTF data (line %d)\n", ctx->curLine)); ctx->state = 0; + if (uniLine) gf_free(uniLine); + if (uniText) gf_free(uniText); return GF_NON_COMPLIANT_BITSTREAM; } @@ -738,12 +794,11 @@ u32 font_style = 0; u32 style_nb_chars = 0; u32 style_def_type = 0; - - if ( (uniLinei=='<') && (uniLinei+2=='>')) { + if ( (i+2<len) && (uniLinei=='<') && (uniLinei+2=='>')) { style_nb_chars = 3; style_def_type = 1; } - else if ( (uniLinei=='<') && (uniLinei+1=='/') && (uniLinei+3=='>')) { + else if ( (i+3<len) && (uniLinei=='<') && (uniLinei+1=='/') && (uniLinei+3=='>')) { style_def_type = 2; style_nb_chars = 4; } @@ -946,12 +1001,16 @@ gf_isom_text_add_text(ctx->samp, szText, len); if (ctx->forced_sub) gf_isom_text_set_forced(ctx->samp, GF_TRUE); *char_l += char_line; + + if (uniLine) gf_free(uniLine); + if (uniText) gf_free(uniText); return GF_OK; } static GF_Err txtin_process_srt(GF_Filter *filter, GF_TXTIn *ctx, GF_FilterPacket *ipck) { u32 sh, sm, ss, sms, eh, em, es, ems, txt_line, char_len; + u64 timestamp; Bool set_start_char, set_end_char; u32 line; char szLine2048; @@ -962,11 +1021,11 @@ if (!ctx->is_setup) { ctx->is_setup = GF_TRUE; GF_Err e = txtin_setup_srt(filter, ctx, GF_FALSE); - if (e || (!ctx->unframed && ctx->file_name)) return e; + if (e || (!ctx->pid_framed && ctx->file_name)) return e; } if (!ctx->opid) return GF_NOT_SUPPORTED; - if (!ctx->unframed && ctx->file_name) { + if (!ctx->pid_framed && ctx->file_name) { if (!ctx->playstate) return GF_OK; else if (ctx->playstate==2) return GF_EOS; } @@ -981,14 +1040,18 @@ } while (1) { + Bool in_progress = ctx->in_over; Bool is_empty = GF_FALSE; - char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type, &in_progress); if (sOK) { REM_TRAIL_MARKS(szLine, "\r\n\t ") if (ctx->unicode_type<=1) is_empty = strlen(szLine) ? GF_FALSE : GF_TRUE; else is_empty = (!szLine0 && !szLine1) ? GF_TRUE : GF_FALSE; + } else if (in_progress) { + ctx->is_suspended = GF_TRUE; + return GF_OK; } if (!sOK || is_empty) { @@ -997,11 +1060,16 @@ u32 pos = (u32) gf_ftell(ctx->src); if (ctx->state) { while (!gf_feof(ctx->src)) { - sOK = gf_text_get_utf8_line(szLine+nb_empty, 2048-nb_empty, ctx->src, ctx->unicode_type); + in_progress = ctx->in_over; + sOK = gf_text_get_utf8_line(szLine+nb_empty, 2048-nb_empty, ctx->src, ctx->unicode_type, &in_progress); if (sOK) REM_TRAIL_MARKS((szLine+nb_empty), "\r\n\t ") if (!sOK) { gf_fseek(ctx->src, pos, SEEK_SET); + if (in_progress) { + ctx->is_suspended = GF_TRUE; + return GF_OK; + } break; } else if (!strlen(szLine+nb_empty)) { nb_empty+=utf_inc; @@ -1036,7 +1104,7 @@ ctx->style.startCharOffset = ctx->style.endCharOffset = 0; gf_isom_text_reset(ctx->samp); - if (!ctx->unframed) + if (!ctx->pid_framed) gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) ); } ctx->state = 0; @@ -1070,15 +1138,22 @@ } } } - ctx->start = (3600*sh + 60*sm + ss)*1000 + sms; + timestamp = (3600*sh + 60*sm + ss)*1000 + sms; + if (ctx->pid_framed!=2) { + ctx->start = timestamp; + } if (ctx->start < ctx->end) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TXTIn Overlapping SRT frame %d - starts "LLD" ms is before end of previous one "LLD" ms - adjusting time stamps\n", ctx->curLine, ctx->start, ctx->end)); ctx->start = ctx->end; } - - ctx->end = (3600*eh + 60*em + es)*1000 + ems; + timestamp = (3600*eh + 60*em + es)*1000 + ems; + if (ctx->pid_framed == 2) { + ctx->end = ctx->start + timestamp; + } else { + ctx->end = timestamp; + } /*make stream start at 0 by inserting a fake AU*/ - if (ctx->first_samp && (ctx->start > 0)) { + if (ctx->first_samp && (ctx->start > 0) && (ctx->pid_framed!=2)) { txtin_process_send_text_sample(ctx, ctx->samp, 0, (u32) ctx->start, GF_TRUE); } ctx->style.style_flags = 0; @@ -1102,18 +1177,25 @@ gf_isom_text_add_text(ctx->samp, "\n", 1); char_len += 1; } - - parse_srt_line(ctx, szLine, &char_len, &set_start_char, &set_end_char); + if (ctx->stxtmod == STXT_MODE_SBTT) { + u32 tlen = (u32) strlen(szLine); + gf_isom_text_add_text(ctx->samp, szLine, tlen); + char_len += tlen; + } else { + parse_srt_line(ctx, szLine, &char_len, &set_start_char, &set_end_char); + } txt_line ++; break; } - if (!ctx->unframed && gf_filter_pid_would_block(ctx->opid)) + if (txtin_check_blocking(ctx)) { + ctx->is_suspended = GF_TRUE; return GF_OK; + } } /*final flush*/ - if (!ctx->unframed && ctx->end && ! ctx->noflush) { + if (!ctx->pid_framed && ctx->end && ! ctx->noflush) { gf_isom_text_reset(ctx->samp); txtin_process_send_text_sample(ctx, ctx->samp, ctx->end, 0, GF_TRUE); ctx->end = 0; @@ -1136,7 +1218,7 @@ static GF_Err gf_webvtt_import_report(void *user, GF_Err e, char *message, const char *line) { - GF_LOG(e ? GF_LOG_WARNING : GF_LOG_INFO, GF_LOG_PARSER, ("TXTIn WebVTT line %s: %s\n", line, message) ); + GF_LOG(e ? GF_LOG_WARNING : GF_LOG_INFO, GF_LOG_PARSER, ("TXTIn WebVTT line \"%s\": %s\n", line, message) ); return e; } @@ -1200,8 +1282,10 @@ gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) ); - if (gf_filter_pid_would_block(ctx->opid) && ctx->file_name) + if (txtin_check_blocking(ctx)) { + ctx->is_suspended = GF_TRUE; gf_webvtt_parser_suspend(ctx->vttparser); + } } static GF_Err txtin_webvtt_setup(GF_Filter *filter, GF_TXTIn *ctx) @@ -1211,7 +1295,7 @@ Bool is_srt; char *ext; - if (!ctx->unframed && !ctx->src) + if (!ctx->pid_framed && !ctx->src) ctx->src = gf_fopen(ctx->file_name, "rb"); if (ctx->opid && (ctx->playstate==2)) return GF_EOS; @@ -1226,7 +1310,7 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Unsupported SRT UTF encoding\n")); return GF_NOT_SUPPORTED; } - if (!ctx->unframed) { + if (!ctx->pid_framed) { ext = gf_file_ext_start(ctx->file_name); is_srt = (ext && !strnicmp(ext, ".srt", 4)) ? GF_TRUE : GF_FALSE; } else { @@ -1237,7 +1321,7 @@ OCR_ES_ID = ID = 0; if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); - if (!ctx->unframed) { + if (!ctx->pid_framed) { gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_WEBVTT) ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); @@ -1256,16 +1340,16 @@ ctx->vttparser = gf_webvtt_parser_new(); - e = gf_webvtt_parser_init(ctx->vttparser, ctx->src, ctx->unicode_type, is_srt, ctx, gf_webvtt_import_report, gf_webvtt_flush_sample, gf_webvtt_import_header); + e = gf_webvtt_parser_init(ctx->vttparser, &ctx->src, ctx->unicode_type, is_srt, ctx, gf_webvtt_import_report, gf_webvtt_flush_sample, gf_webvtt_import_header); if (e != GF_OK) { gf_webvtt_parser_del(ctx->vttparser); ctx->vttparser = NULL; GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn WebVTT parser init error %s\n", gf_error_to_string(e) )); } //get the header - e = gf_webvtt_parser_parse(ctx->vttparser); + e = gf_webvtt_parser_parse_ext(ctx->vttparser, ctx->src, GF_TRUE); - if (!ctx->unframed) + if (!ctx->pid_framed) txtin_probe_duration(ctx); return e; } @@ -1281,7 +1365,7 @@ if (!ctx->is_setup) { ctx->is_setup = GF_TRUE; e = txtin_webvtt_setup(filter, ctx); - if (e || !ctx->unframed) return e; + if (e || !ctx->pid_framed) return e; } if (!ctx->vttparser) return (ctx->playstate==2) ? GF_EOS : GF_NOT_SUPPORTED; @@ -1289,7 +1373,7 @@ ctx->seek_state = 2; gf_webvtt_parser_restart(ctx->vttparser); } - if (ctx->unframed) { + if (ctx->pid_framed) { const GF_PropertyValue *p; const char *vtt_pre=NULL, *vtt_cueid=NULL, *vtt_settings=NULL; p = gf_filter_pck_get_property_str(ipck, "vtt_pre"); @@ -1307,7 +1391,7 @@ if (!ctx->file_name) gf_webvtt_parser_not_done(ctx->vttparser); - e = gf_webvtt_parser_parse(ctx->vttparser); + e = gf_webvtt_parser_parse_ext(ctx->vttparser, ctx->src, ctx->in_over); } if (e < GF_OK) { @@ -1403,7 +1487,7 @@ u64 ts = GF_FILTER_NO_TS; u32 len = (u32) strlen(value); - //tick metrick - cannot be fractional + //tick metric - cannot be fractional if (len && (valuelen-1=='t')) { valuelen-1 = 0; ts = (s64) (atoi(value) * 1000); @@ -1460,34 +1544,64 @@ if (sf) ts += ((s64) 1000 * sf * *ttml_fps_den / *ttml_sfps) / *ttml_fps_num; } - else if (sscanf(value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { - ts = (h*3600 + m*60+s)*1000+ms; - } - else if (sscanf(value, "%u:%u:%u:%u.%u", &h, &m, &s, &f, &sf) == 5) { - ts = (h*3600 + m*60+s)*1000; - if (! *ttml_fps_num) { - GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD time indicates frames but no frame rate set, assuming 25 FPS\n")); - *ttml_fps_num = 25; - *ttml_fps_den = 1; - } - if (! *ttml_sfps) { - GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD time indicates subframes but no subFrameRate set, assuming 1\n")); - *ttml_sfps = 1; - } - ts += ((s64) 1000 * f * *ttml_fps_den) / *ttml_fps_num; - ts += ((s64) 1000 * sf * *ttml_fps_den / *ttml_sfps) / *ttml_fps_num; - } - else if (sscanf(value, "%u:%u:%u:%u", &h, &m, &s, &f) == 4) { - ts = (h*3600 + m*60+s)*1000; - if (! *ttml_fps_num) { - GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD time indicates frames but no frame rate set, assuming 25 FPS\n")); - *ttml_fps_num = 25; - *ttml_fps_den = 1; + else { + u32 nb_val=0; + Bool has_dot=GF_FALSE; + u32 vals6 = {0}; + char *cur = value; + while (cur) { + char sep; + char *next_col = strchr(cur, ':'); + if (!next_col) next_col = strchr(cur, '.'); + if (next_col) { + sep = next_col0; + next_col0 = 0; + } + valsnb_val = atoi(cur); + nb_val++; + if (!next_col) break; + has_dot = (sep=='.') ? GF_TRUE : GF_FALSE; + next_col0 = sep; + cur = next_col+1; + if (nb_val>=6) break; + } + h = vals0; + m = vals1; + s = vals2; + if (nb_val==4) { + ms = vals3; + if (has_dot) { + ts = (h*3600 + m*60+s)*1000+ms; + } else { + ts = (h*3600 + m*60+s)*1000; + if (! *ttml_fps_num) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD time indicates frames but no frame rate set, assuming 25 FPS\n")); + *ttml_fps_num = 25; + *ttml_fps_den = 1; + } + ts += ((s64) 1000 * ms * *ttml_fps_den) / *ttml_fps_num; + } + } else if (nb_val==5) { + ms = vals3; + sf = vals4; + ts = (h*3600 + m*60+s)*1000; + if (! *ttml_fps_num) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD time indicates frames but no frame rate set, assuming 25 FPS\n")); + *ttml_fps_num = 25; + *ttml_fps_den = 1; + } + if (! *ttml_sfps) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD time indicates subframes but no subFrameRate set, assuming 1\n")); + *ttml_sfps = 1; + } + ts += ((s64) 1000 * ms * *ttml_fps_den) / *ttml_fps_num; + ts += ((s64) 1000 * sf * *ttml_fps_den / *ttml_sfps) / *ttml_fps_num; + } else if (nb_val==3) { + ts = (h*3600 + m*60+s)*1000; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TTML EBU-TTD Invalid timestamp %s\n", value)); + ts = (h*3600 + m*60+s)*1000; } - ts += ((s64) 1000 * f * *ttml_fps_den) / *ttml_fps_num; - } - else if (sscanf(value, "%u:%u:%u", &h, &m, &s) == 3) { - ts = (h*3600 + m*60+s)*1000; } return ts; } @@ -1737,6 +1851,7 @@ return GF_OK; } +// modifications in this function should be mirrored in writegen_rewrite_timestamp_ttml() static GF_Err ttml_rewrite_timestamp(GF_TXTIn *ctx, s64 ttml_zero, GF_XMLAttribute *att, s64 *value, Bool *drop) { u64 v; @@ -1754,11 +1869,11 @@ *value -= ttml_zero; v = (u64) (*value / 1000); h = (u32) (v / 3600); - m = (u32) (v - h*60) / 60; + m = (u32) (v - h*3600) / 60; s = (u32) (v - h*3600 - m*60); ms = (*value) % 1000; - snprintf(szTS, 20, "%02d:%02d:%02d.%03d", h, m, s, ms); + snprintf(szTS, 20, "%02u:%02u:%02u.%03u", h, m, s, ms); szTS20 = 0; gf_free(att->value); att->value = gf_strdup(szTS); @@ -1810,7 +1925,7 @@ s64 begin=-1, end=-1; GF_XMLNode *adiv_child = (GF_XMLNode*)gf_list_get(div_node->content, k); if (adiv_child->type) continue; - e = gf_xml_get_element_check_namespace(adiv_child, "p", root->ns); + e = gf_xml_dom_node_check_namespace(adiv_child, "p", root->ns); if (e) continue; p_idx = 0; @@ -1841,7 +1956,7 @@ p_idx = 0; while ( (p_node = (GF_XMLNode*)gf_list_enum(adiv_child->content, &p_idx))) { s64 s_begin=-1, s_end=-1; - e = gf_xml_get_element_check_namespace(p_node, "span", root->ns); + e = gf_xml_dom_node_check_namespace(p_node, "span", root->ns); if (e) continue; u32 span_idx = 0; @@ -1965,7 +2080,9 @@ ctx->is_setup = GF_TRUE; ctx->parser = gf_xml_dom_new(); - e = gf_xml_dom_parse(ctx->parser, ctx->file_name, ttxt_dom_progress, ctx); + //we will reserialize the TTML, so enable passthrough + gf_xml_dom_enable_passthrough(ctx->parser); + e = gf_xml_dom_parse(ctx->parser, ctx->file_name ? ctx->file_name : ctx->blob_name, ttxt_dom_progress, ctx); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Error parsing TTML file: Line %d - %s. Abort.\n", gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser) )); ctx->is_setup = GF_TRUE; @@ -1980,7 +2097,7 @@ } /*look for TTML*/ - if (gf_xml_get_element_check_namespace(root, "tt", NULL) != GF_OK) { + if (gf_xml_dom_node_check_namespace(root, "tt", NULL) != GF_OK) { if (root->ns) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML file not recognized: root element is \"%s:%s\" (check your namespaces)\n", root->ns, root->name)); } else { @@ -2006,7 +2123,7 @@ if (node->type) { continue; } - e = gf_xml_get_element_check_namespace(node, "body", root->ns); + e = gf_xml_dom_node_check_namespace(node, "body", root->ns); if (e == GF_BAD_PARAM) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD ignored \"%s\" node, check your namespaces\n", node->name)); } else if (e == GF_OK) { @@ -2033,7 +2150,7 @@ i=0; while ( (node = (GF_XMLNode*)gf_list_enum(body_node->content, &i))) { if (!node->type) { - e = gf_xml_get_element_check_namespace(node, "div", root->ns); + e = gf_xml_dom_node_check_namespace(node, "div", root->ns); if (e == GF_BAD_PARAM) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD ignored \"%s\" node, check your namespaces\n", node->name)); } @@ -2059,10 +2176,7 @@ gf_filter_pid_set_property_str(ctx->opid, "meta:xmlns", &PROP_STRING(TTML_NAMESPACE) ); /*** body ***/ - ctx->parser_working_copy = gf_xml_dom_new(); - e = gf_xml_dom_parse(ctx->parser_working_copy, ctx->file_name, NULL, NULL); - assert (e == GF_OK); - ctx->root_working_copy = gf_xml_dom_get_root(ctx->parser_working_copy); + ctx->root_working_copy = gf_xml_dom_node_clone(gf_xml_dom_get_root(ctx->parser)); if (!ctx->root_working_copy) return GF_NON_COMPLIANT_BITSTREAM; if (body_node) { @@ -2209,7 +2323,7 @@ if (div_child->type) { continue; } - e = gf_xml_get_element_check_namespace(div_child, "p", root->ns); + e = gf_xml_dom_node_check_namespace(div_child, "p", root->ns); if (e == GF_BAD_PARAM) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD ignored \"%s\" node, check your namespaces\n", div_child->name)); continue; @@ -2252,7 +2366,7 @@ while ( (p_node = (GF_XMLNode*)gf_list_enum(div_child->content, &p_idx))) { u32 span_idx = 0; GF_XMLAttribute *span_att; - e = gf_xml_get_element_check_namespace(p_node, "span", root->ns); + e = gf_xml_dom_node_check_namespace(p_node, "span", root->ns); if (e == GF_BAD_PARAM) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML EBU-TTD ignored \"%s\" node, check your namespaces\n", p_node->name)); } @@ -2448,6 +2562,7 @@ GF_FilterPacket *pck; u8 *pck_data; GF_TXTIn *ctx = (GF_TXTIn *)user; + if (!data || !length) return GF_BAD_PARAM; if (ctx->seek_state==2) { Double ts = (Double) timestamp; @@ -2457,7 +2572,7 @@ } pck = gf_filter_pck_new_alloc(ctx->opid, length, &pck_data); - if (pck) { + if (pck && data && length) { memcpy(pck_data, data, length); gf_filter_pck_set_cts(pck, (u64) (ctx->timescale*timestamp/1000) ); gf_filter_pck_set_sap(pck, isRap ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); @@ -2466,8 +2581,8 @@ gf_filter_pck_send(pck); } - if (gf_filter_pid_would_block(ctx->opid)) - ctx->do_suspend = GF_TRUE; + if (txtin_check_blocking(ctx)) + ctx->is_suspended = GF_TRUE; return GF_OK; } @@ -2559,11 +2674,11 @@ gf_swf_reader_set_user_mode(ctx->swf_parse, ctx, swf_svg_add_iso_sample, swf_svg_add_iso_header); } - ctx->do_suspend = GF_FALSE; + ctx->is_suspended = GF_FALSE; /*parse all tags*/ while (e == GF_OK) { e = swf_parse_tag(ctx->swf_parse); - if (ctx->do_suspend) return GF_OK; + if (ctx->is_suspended) return GF_OK; } if (e==GF_EOS) { if (ctx->swf_parse->finalize) { @@ -2621,9 +2736,15 @@ line = 0; while (1) { - char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); - if (!sOK) break; - + Bool in_progress = ctx->in_over; + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type, &in_progress); + if (!sOK) { + if (in_progress) { + ctx->is_suspended=GF_TRUE; + return GF_OK; + } + break; + } REM_TRAIL_MARKS(szLine, "\r\n\t ") line++; @@ -2636,11 +2757,15 @@ continue; } while (szLinei+1 && szLinei+1!='}') { + if (i>=GF_ARRAY_LENGTH(szTime)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("sub->bifs Bad frame (line %d): expected \"}\" before %d chars after \"{\"\n", line, GF_ARRAY_LENGTH(szTime))); + szTime0 = 0; + goto exit; + } szTimei = szLinei+1; i++; - if (i>=40) break; } - szTimei = 0; + szTimeMIN(i, GF_ARRAY_LENGTH(szTime)-1) = 0; ctx->start = atoi(szTime); if (ctx->start < ctx->end) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TXTIn corrupted SUB frame (line %d) - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps\n", line, ctx->start, ctx->end)); @@ -2653,11 +2778,15 @@ continue; } while (szLinei+1+j && szLinei+1+j!='}') { + if (i>=GF_ARRAY_LENGTH(szTime)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("sub->bifs Bad frame (line %d): expected \"}\" before %d chars after \"{\"\n", line, GF_ARRAY_LENGTH(szTime))); + szTime0 = 0; + goto exit; + } szTimei = szLinei+1+j; i++; - if (i>=40) break; } - szTimei = 0; + szTimeMIN(i, GF_ARRAY_LENGTH(szTime)-1) = 0; ctx->end = atoi(szTime); j+=i+2; @@ -2697,9 +2826,12 @@ gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) ); - if (gf_filter_pid_would_block(ctx->opid)) + if (txtin_check_blocking(ctx)) { + ctx->is_suspended = GF_TRUE; return GF_OK; + } } +exit: /*final flush*/ if (ctx->end && !ctx->noflush) { samp = gf_isom_new_text_sample(); @@ -2712,20 +2844,25 @@ return GF_EOS; } +#define MAX_LINE_SIZE 2048 + +#define LINE_CAT(line, str) (strncat((line), (str), ((size_t)(MAX(0, (int)(MAX_LINE_SIZE-j-1-strlen(str))))))) + static GF_Err gf_text_process_ssa(GF_Filter *filter, GF_TXTIn *ctx, GF_FilterPacket *ipck) { u32 i, j, len, line; + u32 state = 0; GF_TextSample *samp; - char szLine2048, szText2048; + char szLineMAX_LINE_SIZE, szTextMAX_LINE_SIZE; //same setup as for srt if (!ctx->is_setup) { ctx->is_setup = GF_TRUE; GF_Err e = txtin_setup_srt(filter, ctx, GF_FALSE); - if (e || !ctx->unframed) return e; + if (e || !ctx->pid_framed) return e; } if (!ctx->opid) return GF_NOT_SUPPORTED; - if (!ctx->unframed) { + if (!ctx->pid_framed) { if (!ctx->playstate) return GF_OK; else if (ctx->playstate==2) return GF_EOS; } @@ -2738,10 +2875,17 @@ line = 0; while (1) { + Bool in_progress = ctx->in_over; char *start_p, *end_p; u32 eh, em, es, ems, nb_c; - char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); - if (!sOK) break; + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type, &in_progress); + if (!sOK) { + if (in_progress) { + ctx->is_suspended=GF_TRUE; + return GF_OK; + } + break; + } REM_TRAIL_MARKS(szLine, "\r\n\t ") @@ -2749,7 +2893,7 @@ len = (u32) strlen(szLine); if (!len) continue; - if (!ctx->unframed) { + if (!ctx->pid_framed) { if (strncmp(szLine, "Dialogue: ", 10)) continue; start_p = strchr(szLine+10, ','); @@ -2786,13 +2930,26 @@ nb_c=8; } + if (nb_c>=6) + state=0; + if (strstr(szLine, ",Default,") + || strncmp(szLine, "Default", 7) + || strstr(szLine, ",Dialogue,") + || strncmp(szLine, "Dialogue", 8) + ) { + state=1; + } else if (state!=1) { + state = 2; + } + if (state==2) continue; + while (nb_c) { end_p = strchr(start_p, ','); if (!end_p) break; start_p = end_p+1; nb_c--; } - if (nb_c) continue; + if (nb_c || !start_p0) continue; if (ctx->start > ctx->end) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TXTIn corrupted SSA frame (line %d) - ends (at %u ms) before start of current frame (%u ms) - skipping\n", line, ctx->end, ctx->start)); @@ -2820,11 +2977,19 @@ while (1) { char c = start_pi; if (c == 0) { + if (j >= MAX_LINE_SIZE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Line too long\n")); + return GF_BAD_PARAM; + } szTextj = 0; break; } if (c=='\\') { if ((start_pi+1 == 'N') || (start_pi+1 == 'n')) { + if (j >= MAX_LINE_SIZE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Line too long\n")); + return GF_BAD_PARAM; + } szTextj = 0; parse_srt_line(ctx, szText, &char_len, &set_start_char, &set_end_char); @@ -2873,29 +3038,33 @@ i++; if (style==1) { - if (is_end) {strcat(szText, "</i>"); j+=4;} - else {strcat(szText, "<i>"); j+=3;} + if (is_end) {LINE_CAT(szText, "</i>"); j+=4;} + else {LINE_CAT(szText, "<i>"); j+=3;} } else if (style==2) { - if (is_end) {strcat(szText, "</b>"); j+=4;} - else {strcat(szText, "<b>"); j+=3;} + if (is_end) {LINE_CAT(szText, "</b>"); j+=4;} + else {LINE_CAT(szText, "<b>"); j+=3;} } else if (style==3) { - if (is_end) {strcat(szText, "</u>"); j+=4;} - else {strcat(szText, "<u>"); j+=3;} + if (is_end) {LINE_CAT(szText, "</u>"); j+=4;} + else {LINE_CAT(szText, "<u>"); j+=3;} } else if (style==4) { - if (is_end) {strcat(szText, "</font>"); j+=7;} + if (is_end) {LINE_CAT(szText, "</font>"); j+=7;} else { char szFont100; sprintf(szFont, "<font color=\"0x%X\">", color); - strcat(szText, szFont); + LINE_CAT(szText, szFont); j+=(u32) strlen(szFont); } } else if (style==5) { - if (is_end) {strcat(szText, "</strike>"); j+=9;} - else {strcat(szText, "<strike>"); j+=8;} + if (is_end) {LINE_CAT(szText, "</strike>"); j+=9;} + else {LINE_CAT(szText, "<strike>"); j+=8;} } continue; } } + if (j >= MAX_LINE_SIZE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Line too long\n")); + return GF_BAD_PARAM; + } szTextj = c; j++; i++; @@ -2908,11 +3077,13 @@ gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src)) ); - if (gf_filter_pid_would_block(ctx->opid)) + if (txtin_check_blocking(ctx)) { + ctx->is_suspended = GF_TRUE; return GF_OK; + } } /*final flush*/ - if (ctx->end && !ctx->noflush) { + if (ctx->end && !ctx->noflush && !ctx->pid_framed) { samp = gf_isom_new_text_sample(); txtin_process_send_text_sample(ctx, samp, ctx->end, 0, GF_TRUE); gf_isom_delete_text_sample(samp); @@ -2923,7 +3094,7 @@ return GF_EOS; } - +#undef LINE_CAT static u32 ttxt_get_color(char *val) { @@ -2988,7 +3159,7 @@ GF_PropertyValue *dcd; ctx->parser = gf_xml_dom_new(); - e = gf_xml_dom_parse(ctx->parser, ctx->file_name, ttxt_dom_progress, ctx); + e = gf_xml_dom_parse(ctx->parser, ctx->file_name ? ctx->file_name : ctx->blob_name, ttxt_dom_progress, ctx); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Error parsing TTXT file: Line %d - %s\n", gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser))); return e; @@ -3195,7 +3366,9 @@ if (!ctx->is_setup) { ctx->is_setup = GF_TRUE; - return txtin_setup_ttxt(filter, ctx); + GF_Err e = txtin_setup_ttxt(filter, ctx); + if (e) return e; + if (ctx->file_name) return e; } if (!ctx->opid) return GF_NON_COMPLIANT_BITSTREAM; if (!ctx->playstate) return GF_OK; @@ -3396,7 +3569,8 @@ ctx->last_sample_duration = ts; } - if (gf_filter_pid_would_block(ctx->opid)) { + if (txtin_check_blocking(ctx)) { + ctx->is_suspended = GF_TRUE; ctx->cur_child_idx++; return GF_OK; } @@ -3473,7 +3647,7 @@ GF_XMLNode *root=NULL; ctx->parser = gf_xml_dom_new(); - e = gf_xml_dom_parse(ctx->parser, ctx->file_name, ttxt_dom_progress, ctx); + e = gf_xml_dom_parse(ctx->parser, ctx->file_name ? ctx->file_name : ctx->blob_name, ttxt_dom_progress, ctx); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTIn Error parsing TeXML file: Line %d - %s\n", gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser) )); gf_xml_dom_del(ctx->parser); @@ -3879,7 +4053,8 @@ gf_isom_delete_text_sample(samp); } - if (gf_filter_pid_would_block(ctx->opid)) { + if (txtin_check_blocking(ctx)) { + ctx->is_suspended = GF_TRUE; ctx->cur_child_idx++; return GF_OK; } @@ -3922,6 +4097,8 @@ opck = gf_filter_pck_new_ref(ctx->opid, 0, 0, ipck); } if (!opck) return GF_OUT_OF_MEM; + gf_filter_pck_merge_properties(ipck, opck); + gf_filter_pck_set_framing(opck, GF_TRUE, GF_TRUE); gf_filter_pck_set_sap(opck, GF_FILTER_SAP_1); if (gf_filter_pck_get_cts(ipck)==GF_FILTER_NO_TS) { gf_filter_pck_set_dts(opck, 0); @@ -3939,21 +4116,21 @@ return gf_filter_pck_send(opck); } -static GF_Err txtin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); +static GF_Err txtin_configure_pid_ex(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove, u32 force_fmt); static GF_Err txtin_process(GF_Filter *filter) { GF_TXTIn *ctx = gf_filter_get_udta(filter); GF_FilterPacket *pck; GF_Err e; - Bool start, end; u32 size=0; const u8 *data; pck = gf_filter_pid_get_packet(ctx->ipid); if (pck) ctx->is_loaded = GF_FALSE; + //no input and not suspended, check eos if (!pck) { - if (ctx->unframed || !ctx->file_name) { + if (ctx->pid_framed || (!ctx->file_name && !ctx->is_suspended)) { if (gf_filter_pid_is_eos(ctx->ipid)) { if (ctx->end) { #ifndef GPAC_DISABLE_VTT @@ -3975,60 +4152,126 @@ if (!ctx->is_loaded) return GF_OK; } + ctx->is_suspended = GF_FALSE; data = pck ? gf_filter_pck_get_data(pck, &size) : NULL; if (pck && (!data || !size)) { gf_filter_pid_drop_packet(ctx->ipid); return GF_OK; } - if (ctx->is_temp) { - const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_URL); - if (ctx->fio) gf_fclose((FILE*)ctx->fio); - ctx->fio = gf_fileio_from_mem(p ? p->value.string : NULL, data, size); - ctx->is_temp = GF_FALSE; - e = txtin_configure_pid(filter, ctx->ipid, GF_FALSE); - if (e) return e; + if (ctx->fmt==GF_TXTIN_MODE_PROBE) { + GF_Blob b; + memset(&b, 0, sizeof(GF_Blob)); + b.size = size; + b.data = (u8 *)data; + char *url = gf_blob_register(&b); + u32 fmt; + gf_text_guess_format(ctx, url, &fmt); + gf_blob_unregister(&b); + gf_free(url); + if (!fmt) { + gf_filter_pid_drop_packet(ctx->ipid); + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + e = txtin_configure_pid_ex(filter, ctx->ipid, GF_FALSE, fmt); + if (e) { + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + } + + if (ctx->pid_framed) { + ctx->in_over = GF_TRUE; + } else if (pck) { + Bool start=GF_FALSE, end=GF_FALSE; + gf_filter_pck_get_framing(pck, &start, &end); + if (end) ctx->in_over = GF_TRUE; } - if (ctx->unframed || !ctx->file_name) { - if (ctx->simple_text) { + if (ctx->pid_framed || !ctx->file_name) { + if (ctx->single_text_chunk) { e = ctx->text_process(filter, ctx, pck); - gf_filter_pid_drop_packet(ctx->ipid); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); return e; } - const u8 *data; - u32 size; - data = gf_filter_pck_get_data(pck, &size); e = GF_OK; + //append data to our blob if (data && size) { - ctx->src = gf_file_temp(NULL); - if (gf_fwrite(data, size, ctx->src) != size) - e = GF_IO_ERR; - gf_fseek(ctx->src, 0, SEEK_SET); - //init state as parsing SRT payload - if (ctx->unframed) { + if (size+ctx->tmp_blob.size >= ctx->tmp_buf_alloc) { + ctx->tmp_buf_alloc = size+ctx->tmp_blob.size+1; + ctx->tmp_blob.data = gf_realloc(ctx->tmp_blob.data, ctx->tmp_buf_alloc); + if (!ctx->tmp_blob.data) { + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OUT_OF_MEM; + } + } + memcpy(ctx->tmp_blob.data + ctx->tmp_blob.size, data, size); + ctx->tmp_blob.size += size; + ctx->tmp_blob.datactx->tmp_blob.size = 0; + if (!ctx->blob_name) ctx->blob_name = gf_blob_register(&ctx->tmp_blob); + } + if (!ctx->pid_framed && ctx->full_file_only) { + //we need full file to parse, wait end of file + if (pck) { + if (!ctx->in_over) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + ctx->full_file_only = GF_FALSE; + } else { + return GF_OK; + } + } + + ctx->src = gf_file_temp(NULL); + if (!ctx->src) + e = GF_IO_ERR; + else if (gf_fwrite(ctx->tmp_blob.data, ctx->tmp_blob.size, ctx->src) != ctx->tmp_blob.size) + e = GF_IO_ERR; + gf_fseek(ctx->src, 0, SEEK_SET); + //init state as parsing SRT payload + if (ctx->pid_framed) { + ctx->start = gf_filter_pck_get_cts(pck); + ctx->end = ctx->start + gf_filter_pck_get_duration(pck); + if (ctx->pid_framed==2) { + ctx->state = 0; + } else { ctx->state = 2; - ctx->start = gf_filter_pck_get_cts(pck); - ctx->end = ctx->start + gf_filter_pck_get_duration(pck); ctx->curLine = 0; } + } - if (!e) - e = ctx->text_process(filter, ctx, pck); + if (!e) + e = ctx->text_process(filter, ctx, pck); - if (ctx->src) { - gf_fclose(ctx->src); - ctx->src = NULL; + //purge data from our blob + if (ctx->src) { + u32 pos = (u32) gf_ftell(ctx->src); + u32 remain = ctx->tmp_blob.size - pos; + gf_fclose(ctx->src); + ctx->src = NULL; + if (ctx->pid_framed) { + ctx->tmp_blob.size = 0; + } else if (pos) { + memmove(ctx->tmp_blob.data, ctx->tmp_blob.data+pos, remain); + ctx->tmp_blob.size = remain; + ctx->tmp_blob.dataremain = 0; + } else { + //some parsers (XML ones) will not use the file at all, + //if nothing is read, assume first load is done and move to suspended mode + ctx->is_suspended = GF_TRUE; + ctx->is_loaded = GF_TRUE; } - if (e==GF_EOS) e = GF_OK; } - gf_filter_pid_drop_packet(ctx->ipid); + if (e==GF_EOS) e = GF_OK; + + if (pck) gf_filter_pid_drop_packet(ctx->ipid); return e; } if (pck) { - gf_filter_pck_get_framing(pck, &start, &end); - if (!end) { + if (!ctx->in_over) { gf_filter_pid_drop_packet(ctx->ipid); return GF_OK; } @@ -4065,17 +4308,14 @@ #endif if (ctx->parser) gf_xml_dom_del(ctx->parser); ctx->parser = NULL; - if (ctx->parser_working_copy) gf_xml_dom_del(ctx->parser_working_copy); - ctx->parser_working_copy = NULL; + if (ctx->root_working_copy) gf_xml_dom_node_del(ctx->root_working_copy); + ctx->root_working_copy = NULL; } -static GF_Err txtin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +static GF_Err txtin_configure_pid_ex(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove, u32 force_fmt) { GF_Err e; u32 codec_id=0; - Bool gen_ttxt_dsi=GF_FALSE; - Bool gen_webvtt_dsi=GF_FALSE; - Bool use_file = GF_FALSE; const char *src = NULL; GF_TXTIn *ctx = gf_filter_get_udta(filter); const GF_PropertyValue *prop; @@ -4085,20 +4325,28 @@ return GF_OK; } - ctx->unframed = GF_FALSE; - ctx->simple_text = GF_FALSE; + if (force_fmt) { + ctx->fmt = force_fmt; + if (!ctx->opid) + ctx->opid = gf_filter_pid_new(filter); + if (!ctx->timescale) ctx->timescale = 1000; + goto force_format; + } + + ctx->pid_framed = 0; + ctx->single_text_chunk = GF_FALSE; if (! gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); if (ctx->srt_to_tx3g) { - ctx->unframed = GF_TRUE; + ctx->pid_framed = 1; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); ctx->timescale = prop ? prop->value.uint : 1000; } else if (ctx->vtt_to_tx3g) { - ctx->unframed = GF_TRUE; + ctx->pid_framed = 1; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); ctx->timescale = prop ? prop->value.uint : 1000; } @@ -4107,7 +4355,7 @@ || (prop->value.uint==GF_CODECID_SUBS_SSA) )) { codec_id = prop->value.uint; - ctx->unframed = GF_TRUE; + ctx->pid_framed = 1; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); ctx->timescale = prop ? prop->value.uint : 1000; } else if (prop && ( @@ -4115,27 +4363,27 @@ || (prop->value.uint==GF_CODECID_SUBS_TEXT) )) { codec_id = prop->value.uint; - ctx->unframed = GF_TRUE; + ctx->pid_framed = 1; prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); ctx->timescale = prop ? prop->value.uint : 1000; - gf_filter_pid_set_framing_mode(pid, GF_TRUE); + //no timescale, this is a single chunk of text to transform into stxt/vtt/tx3g if (!prop) { - ctx->simple_text = GF_TRUE; - gf_filter_pid_set_framing_mode(pid, GF_TRUE); + ctx->single_text_chunk = GF_TRUE; } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED_SRT); + if (prop && prop->value.boolean) ctx->pid_framed = 2; } else { - //otherwise we must have a file path + //otherwise check we have a file path prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); if (prop && prop->value.string) src = prop->value.string; - if (!src && ctx->fio) - src = gf_fileio_url(ctx->fio); - - use_file = src ? GF_TRUE : GF_FALSE; //check if mime is a vtt, if so default to vtt output - for other inputs, we'll use tx3g as usual prop = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); - if (prop && !strcmp(prop->value.string, "subtitle/vtt")) { - codec_id = GF_CODECID_WEBVTT; + if (prop) { + if (strstr(prop->value.string, "/vtt")) { + codec_id = GF_CODECID_WEBVTT; + } } } @@ -4143,7 +4391,7 @@ GF_FilterEvent fevt; ctx->ipid = pid; - if (use_file) { + if (src) { //we work with full file only, send a play event on source to indicate that GF_FEVT_INIT(fevt, GF_FEVT_PLAY, pid); fevt.play.start_range = 0; @@ -4154,6 +4402,9 @@ ctx->file_name = src ? gf_strdup(src) : NULL; if (!src) gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + } else { + //loading from input pid without any file object, disable duration probing + ctx->index = 0; } } else { if (pid != ctx->ipid) { @@ -4167,18 +4418,15 @@ if (ctx->file_name) gf_free(ctx->file_name); ctx->file_name = gf_strdup(src); } + ctx->is_loaded = GF_FALSE; } - if (use_file) { - if (src) { - //guess type - e = gf_text_guess_format(ctx, ctx->file_name, &ctx->fmt); - if (e) return e; - if (!ctx->fmt) { - GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTLoad Unknown text format for %s\n", ctx->file_name)); - return GF_NOT_SUPPORTED; - } - } else { - ctx->fmt = GF_TXTIN_MODE_PROBE; + if (src) { + //guess type + e = gf_text_guess_format(ctx, ctx->file_name, &ctx->fmt); + if (e) return e; + if (!ctx->fmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("TXTLoad Unknown text format for %s\n", ctx->file_name)); + return GF_NOT_SUPPORTED; } } else { if (ctx->vtt_to_tx3g) @@ -4189,61 +4437,98 @@ ctx->fmt = GF_TXTIN_MODE_WEBVTT; else if (codec_id == GF_CODECID_SUBS_SSA) ctx->fmt = GF_TXTIN_MODE_SSA; - else - ctx->fmt = ctx->simple_text ? GF_TXTIN_MODE_SIMPLE : GF_TXTIN_MODE_SRT; + else if (ctx->single_text_chunk) + ctx->fmt = GF_TXTIN_MODE_SIMPLE; + //if we receive simple text streams in framed mode (one packet==one sub), decide how to translate it + else if (ctx->pid_framed && ((codec_id==GF_CODECID_SIMPLE_TEXT) || (codec_id==GF_CODECID_SUBS_TEXT))) { + if (ctx->stxtmod == STXT_MODE_VTT) { + ctx->fmt = GF_TXTIN_MODE_WEBVTT; + } else if (ctx->stxtmod==STXT_MODE_TX3G) { + ctx->fmt = GF_TXTIN_MODE_SRT; + } else { + ctx->fmt = GF_TXTIN_MODE_SIMPLE; + } + } else { + //we don't know the format - we could probe from mime but let's wait for first packet + ctx->fmt = GF_TXTIN_MODE_PROBE; + return GF_OK; + } + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); if (!ctx->timescale) ctx->timescale = 1000; } - if (ctx->webvtt && (ctx->fmt == GF_TXTIN_MODE_SRT)) - ctx->fmt = GF_TXTIN_MODE_WEBVTT; +force_format: - if (!use_file) { + if (ctx->fmt == GF_TXTIN_MODE_SRT) { + if (ctx->stxtmod==STXT_MODE_VTT) ctx->fmt = GF_TXTIN_MODE_WEBVTT; + } + + if (!src) { gf_filter_pid_copy_properties(ctx->opid, pid); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); - if (!ctx->simple_text) - codec_id = (ctx->fmt == GF_TXTIN_MODE_WEBVTT) ? GF_CODECID_WEBVTT : GF_CODECID_TX3G; - else if (ctx->stxtmod) - codec_id = (ctx->stxtmod==STXT_MODE_VTT) ? GF_CODECID_WEBVTT : GF_CODECID_TX3G; + if (!ctx->single_text_chunk) { + if (ctx->fmt == GF_TXTIN_MODE_WEBVTT) codec_id = GF_CODECID_WEBVTT; + else if (ctx->fmt == GF_TXTIN_MODE_TTML) codec_id = GF_CODECID_SUBS_XML; + //no change in codec id (either simple_text or text_sub) + else if (ctx->fmt == GF_TXTIN_MODE_SIMPLE) {} + else codec_id = GF_CODECID_TX3G; + } + //simple text as vtt + else if (ctx->stxtmod==STXT_MODE_VTT) + codec_id = GF_CODECID_WEBVTT; + //simple text as tx3g + else if (ctx->stxtmod==STXT_MODE_TX3G) + codec_id = GF_CODECID_TX3G; + //otherwise keep codec id from source gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(codec_id) ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + if (!gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); } + Bool gen_ttxt_dsi = GF_FALSE; + Bool gen_webvtt_dsi = GF_FALSE; + ctx->full_file_only = GF_FALSE; switch (ctx->fmt) { case GF_TXTIN_MODE_SRT: ctx->text_process = txtin_process_srt; - if (!ctx->is_setup && ctx->unframed) gen_ttxt_dsi = GF_TRUE; + if (!ctx->is_setup && ctx->pid_framed) gen_ttxt_dsi = GF_TRUE; break; #ifndef GPAC_DISABLE_VTT case GF_TXTIN_MODE_WEBVTT: ctx->text_process = txtin_process_webvtt; - if (!ctx->is_setup && ctx->unframed) gen_webvtt_dsi = GF_TRUE; + if (!ctx->is_setup && ctx->pid_framed) gen_webvtt_dsi = GF_TRUE; break; #endif case GF_TXTIN_MODE_TTXT: ctx->text_process = txtin_process_ttxt; + ctx->full_file_only = GF_TRUE; break; case GF_TXTIN_MODE_TEXML: ctx->text_process = txtin_process_texml; + ctx->full_file_only = GF_TRUE; break; case GF_TXTIN_MODE_SUB: ctx->text_process = gf_text_process_sub; - if (!ctx->is_setup && ctx->unframed) gen_ttxt_dsi = GF_TRUE; + if (!ctx->is_setup && ctx->pid_framed) gen_ttxt_dsi = GF_TRUE; break; case GF_TXTIN_MODE_TTML: ctx->text_process = gf_text_process_ttml; + ctx->full_file_only = GF_TRUE; break; case GF_TXTIN_MODE_SSA: ctx->text_process = gf_text_process_ssa; - if (!ctx->is_setup && ctx->unframed) gen_ttxt_dsi = GF_TRUE; + if (!ctx->is_setup && ctx->pid_framed) gen_ttxt_dsi = GF_TRUE; break; #ifndef GPAC_DISABLE_SWF_IMPORT case GF_TXTIN_MODE_SWF_SVG: ctx->text_process = gf_text_process_swf; + ctx->full_file_only = GF_TRUE; break; #endif case GF_TXTIN_MODE_SIMPLE: @@ -4252,7 +4537,6 @@ else if (ctx->stxtmod==STXT_MODE_VTT) gen_webvtt_dsi = 1; break; case GF_TXTIN_MODE_PROBE: - ctx->is_temp = GF_TRUE; break; default: return GF_BAD_PARAM; @@ -4266,7 +4550,7 @@ } //when translating from unframed srt/vtt to framed tx3g/vtt, the number of input samples will be at most doubled by inserting blank samples - if (ctx->unframed && !ctx->no_empty) { + if (ctx->pid_framed && !ctx->no_empty) { const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_NB_FRAMES); if (p) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NB_FRAMES, &PROP_UINT(p->value.uint*2)); @@ -4275,32 +4559,41 @@ return GF_OK; } - +static GF_Err txtin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + return txtin_configure_pid_ex(filter, pid, is_remove, 0); +} static Bool txtin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { GF_TXTIn *ctx = gf_filter_get_udta(filter); - if (ctx->unframed) return GF_FALSE; + if (ctx->pid_framed) return GF_FALSE; switch (evt->base.type) { case GF_FEVT_PLAY: - if (ctx->playstate==1) return ctx->unframed ? GF_FALSE : GF_TRUE; + //if we have temp storage of packet, post a task and force suspended mode + if (ctx->blob_name) { + gf_filter_post_process_task(filter); + ctx->is_suspended = GF_TRUE; + } + + if (ctx->playstate==1) return ctx->pid_framed ? GF_FALSE : GF_TRUE; if (ctx->playstate==2) { ttxtin_reset(ctx); - if (!ctx->unframed) + if (!ctx->pid_framed) gf_filter_post_process_task(filter); } ctx->playstate = 1; - if ((ctx->start_range < 0.1) && (evt->play.start_range<0.1)) return ctx->unframed ? GF_FALSE : GF_TRUE; + if ((ctx->start_range < 0.1) && (evt->play.start_range<0.1)) return ctx->pid_framed ? GF_FALSE : GF_TRUE; ctx->start_range = evt->play.start_range; ctx->seek_state = 1; //cancel play event if we work with full file - return ctx->unframed ? GF_FALSE : GF_TRUE; + return ctx->pid_framed ? GF_FALSE : GF_TRUE; case GF_FEVT_STOP: ctx->playstate = 2; ctx->is_setup = GF_FALSE; //cancel play event if we work with full file - return ctx->unframed ? GF_FALSE : GF_TRUE; + return ctx->pid_framed ? GF_FALSE : GF_TRUE; default: return GF_FALSE; } @@ -4313,6 +4606,8 @@ GF_TXTIn *ctx = gf_filter_get_udta(filter); ctx->bs_w = gf_bs_new(data, 1, GF_BITSTREAM_WRITE); ctx->has_forced = 2; + //WEBVTT name used for backward compatibility with old :webvtt option + if (ctx->stxtmod==STXT_MODE_WEBVTT) ctx->stxtmod = STXT_MODE_VTT; return GF_OK; } @@ -4322,8 +4617,6 @@ ttxtin_reset(ctx); - if (ctx->fio) gf_fclose((FILE*)ctx->fio); - if (ctx->bs_w) gf_bs_del(ctx->bs_w); if (ctx->text_descs) { @@ -4354,6 +4647,8 @@ gf_list_del(ctx->div_nodes_list); if (ctx->file_name) gf_free(ctx->file_name); + if (ctx->tmp_blob.data) gf_free(ctx->tmp_blob.data); + if (ctx->blob_name) gf_free(ctx->blob_name); } @@ -4457,7 +4752,6 @@ static const GF_FilterArgs TXTInArgs = { - { OFFS(webvtt), "force WebVTT import of SRT files", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(nodefbox), "skip default text box", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(noflush), "skip final sample flush for srt", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(fontname), "default font", GF_PROP_STRING, NULL, NULL, 0}, @@ -4476,10 +4770,12 @@ { OFFS(ttml_zero), "set subtitle zero time for TTML", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(no_empty), "do not send empty samples", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(stxtdur), "duration for simple text", GF_PROP_FRACTION, "1", NULL, GF_FS_ARG_HINT_ADVANCED}, - { OFFS(stxtmod), "simple text stream mode\n" - "- none: declares output PID as simple text stream\n" - "- tx3g: declares output PID as TX3G/Apple stream\n" - "- vtt: declares output PID as WebVTT stream", GF_PROP_UINT, "none", "none|tx3g|vtt", GF_FS_ARG_HINT_EXPERT}, + { OFFS(stxtmod), "text stream mode for simple text streams and SRT inputs\n" + "- stxt: output PID formatted as simple text stream (remove markup in VTT/SRT payload)\n" + "- sbtt: output PID formatted as subtitle text stream (keep markup in VTT/SRT payload)\n" + "- tx3g: output PID formatted as TX3G/Apple stream\n" + "- vtt: output PID formatted as WebVTT stream\n" + "- webvtt: same as vtt (for backward compatiblity", GF_PROP_UINT, "tx3g", "stxt|sbtt|tx3g|vtt|webvtt", GF_FS_ARG_HINT_EXPERT}, { OFFS(index), "indexing window length. If 0, bitstream is not probed for duration. A negative value skips the indexing if the source file is larger than 20M (slows down importers) unless a play with start range > 0 is issued", GF_PROP_DOUBLE, "-1.0", NULL, GF_FS_ARG_HINT_HIDE}, {0} }; @@ -4491,7 +4787,7 @@ "The filter supports the following formats:\n" "- SRT: https://en.wikipedia.org/wiki/SubRip\n" "- WebVTT: https://www.w3.org/TR/webvtt1/\n" - "- TTXT: https://wiki.gpac.io/TTXT-Format-Documentation\n" + "- TTXT: https://wiki.gpac.io/xmlformats/TTXT-Format-Documentation\n" "- QT 3GPP Text XML (TexML): Apple QT6, likely deprecated\n" "- TTML: https://www.w3.org/TR/ttml2/\n" "- SUB: one subtitle per line formatted as `{start_frame}{end_frame}text`\n" @@ -4500,6 +4796,7 @@ "Input files must be in UTF-8 or UTF-16 format, with or without BOM. The internal frame format is: \n" "- WebVTT (and srt if desired): ISO/IEC 14496-30 VTT cues\n" "- TTML: ISO/IEC 14496-30 XML subtitles\n" + "- stxt and sbtt: ISO/IEC 14496-30 text stream and text subtitles\n" "- Others: 3GPP/QT Timed Text\n" "\n" "# TTML Support\n" @@ -4531,7 +4828,7 @@ "EX MP4Box -add test.ttml --ttml_zero=10:00:00 ...\n" "\n" "# Simple Text Support\n" - "The text loader can convert input files in simple text streams of a single packet, by forcing the codec type on the input:" + "The text loader can convert input files in simple text streams of a single packet, by forcing the codec type on the input:\n" "EX gpac -i test.txt:#CodecID=stxt ...\n" "EX gpac fin:pck=\"Text Data\":#CodecID=stxt ...\n" "\n" @@ -4539,6 +4836,11 @@ "In this mode, the -stxtdur() option is used to control the duration of the generated subtitle:\n" "- a positive value always forces the duration\n" "- a negative value forces the duration if input packet duration is not known\n" + "\n" + "# Notes\n" + "When reframing simple text streams from demuxers (e.g. subtitles from MKV), the output format of these streams can be selected using -stxtmod().\n" + "\n" + "When importing SRT, SUB or SSA files, the output format of the PID can be selected using -stxtmod().\n" ) .private_size = sizeof(GF_TXTIn), @@ -4550,7 +4852,8 @@ .process_event = txtin_process_event, .probe_data = txtin_probe_data, .initialize = txtin_initialize, - .finalize = txtin_finalize + .finalize = txtin_finalize, + .hint_class_type = GF_FS_CLASS_SUBTITLE }; @@ -4609,7 +4912,8 @@ .process_event = txtin_process_event, .probe_data = txtin_probe_data, .initialize = vtt2tx3g_initialize, - .finalize = txtin_finalize + .finalize = txtin_finalize, + .hint_class_type = GF_FS_CLASS_SUBTITLE }; const GF_FilterRegister *vtt2tx3g_register(GF_FilterSession *session) @@ -4663,7 +4967,8 @@ .process_event = txtin_process_event, .probe_data = txtin_probe_data, .initialize = rfsrt_initialize, - .finalize = txtin_finalize + .finalize = txtin_finalize, + .hint_class_type = GF_FS_CLASS_FRAMING }; const GF_FilterRegister *rfsrt_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/mux_avi.c -> gpac-26.02.0.tar.gz/src/filters/mux_avi.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / AVI output filter @@ -660,7 +660,8 @@ .finalize = avimux_finalize, .configure_pid = avimux_configure_pid, .process = avimux_process, - .flags = GF_FS_REG_TEMP_INIT + .flags = GF_FS_REG_TEMP_INIT, + .hint_class_type = GF_FS_CLASS_MULTIPLEXER }; static GF_Err avimux_initialize(GF_Filter *filter)
View file
gpac-2.4.0.tar.gz/src/filters/mux_gsf.c -> gpac-26.02.0.tar.gz/src/filters/mux_gsf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / GPAC stream serializer filter @@ -328,7 +328,10 @@ && !gf_props_type_is_enum(prop_type) && (gf_props_get_base_type(prop_type) != gf_props_get_base_type(p->type)) ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFMux Mismatch between property advertised type (%s) and built-in type (%s) for %s, not serializing !\n\tPlease contact GPAC team or the developers of third-party filters used if any (run with -graph)\n", gf_props_get_type_name(p->type), gf_props_get_type_name(prop_type), gf_props_4cc_get_name(prop_4cc))); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("GSFMux Mismatch between property advertised type (%s) and built-in " + "type (%s) for %s, not serializing !\n\tPlease contact the GPAC team or the developers of third-party filters " + "used if any (run with -graph)\n", + gf_props_get_type_name(p->type), gf_props_get_type_name(prop_type), gf_props_4cc_get_name(prop_4cc))); return GF_FALSE; } } @@ -465,6 +468,14 @@ if (prop_name && !strcmp(prop_name, "reframer_rem_edits")) return GF_TRUE; + if (gf_sys_is_test_mode()) { + switch (prop_4cc) { + case GF_PROP_PID_SEI_LOADED: + case GF_PROP_PCK_SEI_LOADED: + return GF_TRUE; + } + } + if (ctx->minp) { u8 flags; if (prop_name) return GF_TRUE; @@ -496,6 +507,7 @@ const char *force_fext=NULL; const char *force_mime=NULL; const char *force_url=NULL; + u32 premux_type=0; u32 nb_4cc_props=0; u32 nb_str_props=0; u32 idx=0; @@ -525,6 +537,7 @@ //file, send mime, url, ext and streamtype if (gst->is_file) { + const GF_PropertyValue *p; GSFMxCtx *alias_ctx = gf_filter_pid_get_alias_udta(gst->pid); if (alias_ctx) { force_fext = gf_file_ext_start(alias_ctx->dst); @@ -532,7 +545,7 @@ force_url = alias_ctx->dst ? alias_ctx->dst : NULL; force_mime = alias_ctx->mime; } else { - const GF_PropertyValue *p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_FILE_EXT); + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_FILE_EXT); force_fext = (p && p->value.string) ? p->value.string : ctx->ext; p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_MIME); force_mime = (p && p->value.string) ? p->value.string : ctx->mime; @@ -546,6 +559,12 @@ nb_4cc_props++; if (force_fext) nb_4cc_props++; + + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_PREMUX_STREAM_TYPE); + if (p) { + premux_type = p->value.uint; + nb_4cc_props++; + } } gf_bs_write_u8(ctx->bs_w, gst->config_version); gsfmx_write_vlen(ctx, nb_4cc_props); @@ -578,6 +597,12 @@ prop.value.string = (char *) force_fext; gsfmx_write_prop(ctx, &prop); } + if (premux_type) { + gf_bs_write_u32(ctx->bs_w, GF_PROP_PID_PREMUX_STREAM_TYPE); + prop.type = GF_PROP_UINT; + prop.value.uint = premux_type; + gsfmx_write_prop(ctx, &prop); + } } @@ -840,8 +865,8 @@ //flags second byte: 3(sap) 2(encrypted) 1(has_sample_deps) 1(has builtin props) 1(has_ext) u8 sap = gf_filter_pck_get_sap(pck); gf_bs_write_int(ctx->bs_w, sap, 3); - u8 crypt = gf_filter_pck_get_crypt_flags(pck); - gf_bs_write_int(ctx->bs_w, crypt, 2); + u8 pck_crypt = gf_filter_pck_get_crypt_flags(pck); + gf_bs_write_int(ctx->bs_w, pck_crypt, 2); u8 depflags = gf_filter_pck_get_dependency_flags(pck); gf_bs_write_int(ctx->bs_w, depflags ? 1 : 0, 1); gf_bs_write_int(ctx->bs_w, nb_4cc_props ? 1 : 0, 1); @@ -1274,7 +1299,7 @@ GF_FilterRegister GSFMxRegister = { .name = "gsfmx", - GF_FS_SET_DESCRIPTION("GSF Multiplexer") + GF_FS_SET_DESCRIPTION("GSF multiplexer") #ifndef GPAC_DISABLE_DOC .help = "This filter provides GSF (__GPAC Serialized Format__) multiplexing.\n" "It serializes the stream states (config/reconfig/info update/remove/eos) and packets of input PIDs. " @@ -1326,7 +1351,8 @@ .configure_pid = gsfmx_configure_pid, .process = gsfmx_process, .process_event = gsfmx_process_event, - .use_alias = gsfmx_use_alias + .use_alias = gsfmx_use_alias, + .hint_class_type = GF_FS_CLASS_MULTIPLEXER };
View file
gpac-2.4.0.tar.gz/src/filters/mux_isom.c -> gpac-26.02.0.tar.gz/src/filters/mux_isom.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2024 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / ISOBMF mux filter @@ -27,6 +27,8 @@ #include <gpac/constants.h> #include <gpac/internal/isomedia_dev.h> #include <gpac/internal/media_dev.h> +#include <gpac/id3.h> +#include <gpac/base_coding.h> #if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MP4MX) @@ -48,7 +50,6 @@ NALU_VVC }; - enum { CENC_NONE=0, @@ -57,21 +58,20 @@ CENC_SETUP_ERROR }; -enum{ +GF_OPT_ENUM (GF_MP4MuxTagInjectionMode, TAG_NONE, TAG_STRICT, - TAG_ALL -}; + TAG_ALL, +); -enum -{ +GF_OPT_ENUM (GF_MP4MuxInbandParamSetMode, XPS_IB_NO = 0, XPS_IB_PPS, XPS_IB_ALL, XPS_IB_BOTH, XPS_IB_MIX, - XPS_IB_AUTO -}; + XPS_IB_AUTO, +); typedef struct { @@ -122,6 +122,8 @@ u32 inband_hdr_size, inband_hdr_non_rap_size; u32 is_nalu; Bool is_av1, is_vpx; + Bool is_avs3v; + Bool is_ac4; Bool fragment_done; s32 ts_delay, negctts_shift; Bool insert_tfdt, probe_min_ctts; @@ -187,7 +189,7 @@ u32 max_cts_samp_dur; u32 w_or_sr, h_or_ch, pf_or_af; - u32 xps_inband; + GF_MP4MuxInbandParamSetMode xps_inband; u8 *dyn_pssh; u32 dyn_pssh_len; @@ -197,18 +199,18 @@ GF_FilterPacket *dgl_copy; u32 all_stsd_crc; + + Bool has_deps; } TrackWriter; -enum -{ +GF_OPT_ENUM (GF_MP4MuxFileStorageMode, MP4MX_MODE_INTER=0, MP4MX_MODE_FLAT, MP4MX_MODE_FASTSTART, MP4MX_MODE_TIGHT, MP4MX_MODE_FRAG, MP4MX_MODE_SFRAG, -}; - +); enum { @@ -217,42 +219,44 @@ MP4MX_DASH_VOD, }; -enum -{ +GF_OPT_ENUM (GF_MP4MuxPRFTMode, + PRFT_OFF=0, + PRFT_SENDER, + PRFT_BOTH +); + +GF_OPT_ENUM (GF_MP4MuxPsshStoreMode, MP4MX_PSSH_MOOV=0, MP4MX_PSSH_MOOF, MP4MX_PSSH_BOTH, MP4MX_PSSH_SKIP, -}; +); -enum -{ - MP4MX_CT_EDIT=0, +GF_OPT_ENUM (GF_MP4MuxCompositionOffsetMode, + MP4MX_CT_AUTO=0, + MP4MX_CT_EDIT, MP4MX_CT_NOEDIT, MP4MX_CT_NEGCTTS, -}; +); -enum -{ +GF_OPT_ENUM (GF_MP4MuxTempStorageMode, MP4MX_VODCACHE_ON=0, MP4MX_VODCACHE_INSERT, MP4MX_VODCACHE_REPLACE, -}; +); -enum -{ +GF_OPT_ENUM (GF_MP4MuxCMAFMode, MP4MX_CMAF_NO=0, MP4MX_CMAF_CMFC, MP4MX_CMAF_CMF2, -}; +); -enum -{ +GF_OPT_ENUM (GF_MP4MuxChapterMode, MP4MX_CHAPM_OFF=0, MP4MX_CHAPM_TRACK, MP4MX_CHAPM_UDTA, - MP4MX_CHAPM_BOTH -}; + MP4MX_CHAPM_BOTH, +); enum { @@ -269,11 +273,14 @@ GF_ISOFile *file; Bool m4sys, dref; GF_Fraction dur; - u32 pack3gp, ctmode; - Bool importer, pack_nal, moof_first, abs_offset, fsap, tfdt_traf, keep_utc, pps_inband; - u32 xps_inband, moovpad; + u32 pack3gp; + GF_MP4MuxCompositionOffsetMode ctmode; + Bool importer, pack_nal, moof_first, abs_offset, fsap, tfdt_traf, keep_utc, pps_inband, rsot; + GF_MP4MuxInbandParamSetMode xps_inband; + u32 moovpad; u32 block_size; - u32 store, tktpl, mudta; + GF_MP4MuxFileStorageMode store; + u32 tktpl, mudta; s32 subs_sidx; GF_Fraction cdur; s32 moovts; @@ -282,8 +289,9 @@ u32 msn, msninc; GF_Fraction64 tfdt; Bool nofragdef, straf, strun, sgpd_traf, noinit; - u32 vodcache; - u32 psshs; + GF_MP4MuxPRFTMode prft; + GF_MP4MuxTempStorageMode vodcache; + GF_MP4MuxPsshStoreMode psshs; u32 trackid; Bool fragdur; Bool btrt; @@ -292,10 +300,11 @@ s32 mediats; GF_AudioSampleEntryImportMode ase; char *styp; + Bool lmsg; Bool sseg; Bool noroll, norap; Bool saio32, tfdt64; - u32 compress; + GF_ISOCompressMode compress; Bool trun_inter; Bool truns_first; char *boxpatch; @@ -304,7 +313,7 @@ Bool mvex; Bool trunv1; u32 sdtp_traf; - u32 cmaf; + GF_MP4MuxCMAFMode cmaf; #ifdef GF_ENABLE_CTRN Bool ctrn; Bool ctrni; @@ -313,9 +322,9 @@ u32 uncv; Bool forcesync, refrag, pad_sparse; Bool force_dv, tsalign, dvsingle, patch_dts; - u32 itags; + GF_MP4MuxTagInjectionMode itags; Double start; - u32 chapm; + GF_MP4MuxChapterMode chapm; //internal @@ -323,6 +332,8 @@ Bool owns_mov; GF_FilterPid *opid; Bool first_pck_sent; + Bool track_removed; + Bool cdur_overwrite; GF_List *tracks; @@ -338,7 +349,7 @@ u32 nb_segs, nb_frags, nb_frags_in_seg; GF_FilterPacket *dst_pck; - char *seg_name; + char *seg_name, *llhas_template; u32 dash_seg_num_plus_one; GF_Fraction64 dash_seg_start; @@ -350,7 +361,8 @@ FILE *tmp_store; u64 flush_size, flush_done; - u32 dash_mode, llhls_mode; + u32 dash_mode, llhas_mode; + Bool send_base64; GF_Fraction dash_dur; Double media_dur; u32 sidx_max_size, sidx_chunk_offset; @@ -404,7 +416,7 @@ u32 seg_flush_state; u32 last_block_in_segment; u64 flush_idx_start_range, flush_idx_end_range; - Bool flush_ll_hls; + Bool flush_llhas; Bool has_def_vid, has_def_aud, has_def_txt; @@ -413,6 +425,10 @@ Bool has_chap_tracks; GF_List *ref_pcks; + + //create id3 sequence + u32 id3_id_sequence; + const GF_PropertyValue *last_id3_processed; } GF_MP4MuxCtx; static void mp4_mux_update_init_edit(GF_MP4MuxCtx *ctx, TrackWriter *tkw, u64 min_ts_service, Bool skip_adjust); @@ -436,7 +452,7 @@ } } ctx->dash_mode = MP4MX_DASH_VOD; - ctx->llhls_mode = 0; + ctx->llhas_mode = GF_LLHAS_NONE; if ((ctx->vodcache==MP4MX_VODCACHE_ON) && !ctx->tmp_store) { ctx->tmp_store = gf_file_temp(NULL); if (!ctx->tmp_store) { @@ -732,7 +748,10 @@ u32 prop_4cc=0; u32 itag; s32 tag_idx; + const char *domain = NULL; + const char *mean = NULL; const char *tag_name=NULL; + char *sep_dom=NULL; const GF_PropertyValue *tag = gf_filter_pid_enum_properties(tkw->ipid, &idx, &prop_4cc, &tag_name); if (!tag) break; @@ -791,23 +810,31 @@ } continue; } else { - if (ctx->itags==TAG_STRICT) - continue; - - if (strnicmp(tag_name, "tag_", 4)) - continue; - - tag_name += 4; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MP4Mux Unrecognized tag %s: %s\n", tag_name, tag->value.string)); + if (!strnicmp(tag_name, "tag_", 4)) { + if (ctx->itags==TAG_STRICT) + continue; + tag_name += 4; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MP4Mux Unrecognized tag %s: %s\n", tag_name, tag->value.string)); - if (strlen(tag_name)==4) { - itag = GF_4CC(tag_name0, tag_name1, tag_name2, tag_name3); - } else if (strlen(tag_name)==3) { - itag = GF_4CC(0xA9, tag_name0, tag_name1, tag_name2); + if (strlen(tag_name)==4) { + itag = GF_4CC(tag_name0, tag_name1, tag_name2, tag_name3); + } else if (strlen(tag_name)==3) { + itag = GF_4CC(0xA9, tag_name0, tag_name1, tag_name2); + } else { + itag = gf_crc_32(tag_name, (u32) strlen(tag_name)); + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("MP4Mux Tag name %s is not a 4CC, using CRC32 %08X as value\n", tag_name, itag)); + } + } else if (!strnicmp(tag_name, "cust_", 4)) { + tag_name += 5; + itag = GF_4CC('c', 'u', 's', 't'); + domain = tag_name; + sep_dom = strchr(tag_name, '@'); + if (sep_dom) { + sep_dom0 = 0; + mean = sep_dom+1; + } } else { - itag = gf_crc_32(tag_name, (u32) strlen(tag_name)); - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("MP4Mux Tag name %s is not a 4CC, using CRC32 %08X as value\n", tag_name, itag)); + continue; } } @@ -819,30 +846,31 @@ case GF_PROP_STRING: case GF_PROP_NAME: len = tag->value.string ? (u32) strlen(tag->value.string) : 0; - e = gf_isom_apple_set_tag(ctx->file, itag, tag->value.string, len, 0, 0); + e = gf_isom_apple_set_tag_ex(ctx->file, itag, tag->value.string, len, 0, 0, domain, mean, 0); break; case GF_PROP_BOOL: - e = gf_isom_apple_set_tag(ctx->file, itag, NULL, 0, tag->value.boolean, 0); + e = gf_isom_apple_set_tag_ex(ctx->file, itag, NULL, 0, tag->value.boolean, 0, domain, mean, 0); break; case GF_PROP_UINT: case GF_PROP_4CC: - e = gf_isom_apple_set_tag(ctx->file, itag, NULL, 0, tag->value.uint, 0); + e = gf_isom_apple_set_tag_ex(ctx->file, itag, NULL, 0, tag->value.uint, 0, domain, mean, 0); break; case GF_PROP_LUINT: - e = gf_isom_apple_set_tag(ctx->file, itag, NULL, 0, tag->value.longuint, 0); + e = gf_isom_apple_set_tag_ex(ctx->file, itag, NULL, 0, tag->value.longuint, 0, domain, mean, 0); break; case GF_PROP_FRACTION: - e = gf_isom_apple_set_tag(ctx->file, itag, NULL, 0, tag->value.frac.num, tag->value.frac.den); + e = gf_isom_apple_set_tag_ex(ctx->file, itag, NULL, 0, tag->value.frac.num, tag->value.frac.den, domain, mean, 0); break; case GF_PROP_DATA: case GF_PROP_CONST_DATA: - e = gf_isom_apple_set_tag(ctx->file, itag, tag->value.data.ptr, tag->value.data.size, 0, 0); + e = gf_isom_apple_set_tag_ex(ctx->file, itag, tag->value.data.ptr, tag->value.data.size, 0, 0, domain, mean, 0); break; default: GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to set tag %s: invalid data format\n", gf_itags_get_name(tag_idx) )); e = GF_OK; break; } + if (sep_dom) sep_dom0 = ','; if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to set tag %s: %s\n", tag_name, gf_error_to_string(e))); @@ -905,6 +933,19 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to set udta %s: %s\n", udta_name, gf_error_to_string(e))); } } + //set kinds + const GF_PropertyValue *role = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ROLE); + if (!role) return; + for (idx=0; idx<role->value.string_list.nb_items; idx++) { + char *scheme_val = role->value.string_list.valsidx; + if (!scheme_val) continue; + char *scheme_sep = strrchr(scheme_val, ':'); + if (scheme_sep) scheme_sep0 = 0; + char *scheme = scheme_sep ? scheme_val : NULL; + char *value = scheme_sep ? scheme_sep+1 : scheme_val; + gf_isom_add_track_kind(ctx->file, tkw->track_num, scheme, value); + if (scheme_sep) scheme_sep0 = ':'; + } } static void update_chap_refs(GF_MP4MuxCtx *ctx) @@ -936,12 +977,15 @@ Bool skip_crypto = GF_FALSE; Bool use_3gpp_config = GF_FALSE; Bool use_ac3_entry = GF_FALSE; + Bool use_ac4_entry = GF_FALSE; Bool use_flac_entry = GF_FALSE; Bool use_avc = GF_FALSE; Bool use_hevc = GF_FALSE; Bool use_vvc = GF_FALSE; Bool use_hvt1 = GF_FALSE; Bool use_av1 = GF_FALSE; + Bool use_avs3v = GF_FALSE; + Bool use_iamf = GF_FALSE; Bool use_vpX = GF_FALSE; Bool use_mj2 = GF_FALSE; Bool use_opus = GF_FALSE; @@ -994,7 +1038,7 @@ GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); GF_AudioSampleEntryImportMode ase_mode = ctx->ase; TrackWriter *tkw; - u32 xps_inband = XPS_IB_NO; + GF_MP4MuxInbandParamSetMode xps_inband = XPS_IB_NO; if (ctx->owns_mov && !ctx->opid) { char *dst; @@ -1115,6 +1159,13 @@ ctx->config_timing = GF_TRUE; ctx->update_report = GF_TRUE; } + if (!ctx->cdur_overwrite) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_FDUR); + if (p && p->value.frac.den) { + ctx->cdur = p->value.frac; + ctx->cdur_overwrite = GF_TRUE; + } + } } } @@ -1205,6 +1256,7 @@ case GF_CODECID_AV1: case GF_CODECID_AC3: case GF_CODECID_EAC3: + case GF_CODECID_AC4: case GF_CODECID_OPUS: case GF_CODECID_TRUEHD: case GF_CODECID_RAW_UNCV: @@ -1278,10 +1330,14 @@ ctx->dash_mode = MP4MX_DASH_ON; } - p = gf_filter_pid_get_property(pid, GF_PROP_PID_LLHLS); - ctx->llhls_mode = p ? p->value.uint : 0; - //insert tfdt in each traf for LL-HLS so that correct timing can be found when doing in-segment tune-in - if (ctx->llhls_mode) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_INIT_BASE64); + ctx->send_base64 = (p && p->value.boolean) ? GF_TRUE : GF_FALSE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_LLHAS_MODE); + ctx->llhas_mode = p ? p->value.uint : GF_LLHAS_NONE; + + //insert tfdt in each traf for LLHAS so that correct timing can be found when doing in-segment tune-in + if (ctx->llhas_mode) { ctx->tfdt_traf = GF_TRUE; ctx->store = MP4MX_MODE_SFRAG; } @@ -1308,6 +1364,16 @@ ctx->fragdur = GF_TRUE; } + if (ctx->dash_mode && ctx->fragdur && !ctx->tfdt_traf) { + const GF_PropertyValue *p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DASH_DUR); + GF_Fraction dash_dur = {0}; + if (p) dash_dur = p->value.frac; + + if (ctx->cdur.num * dash_dur.den < dash_dur.num * ctx->cdur.den) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux DASH mode with multiple fragments per segment but TFDT only set on first fragment of segment, may not be supported by all demuxers. Use `--tfdt_traf` or set CMAF profile `--cmaf=X` if not desired.\n")); + } + } + if (needs_track) { if (ctx->init_movie_done) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux Cannot add track to already finalized movie in fragmented file, will request a new muxer for that track\n")); @@ -1344,6 +1410,7 @@ else if (inc * 5000 == ts * 100) target_timescale = 5000; else if (inc * 60000 == ts * 1001) target_timescale = 60000; else if (inc * 5994 == ts * 100) target_timescale = 60000; + else if (inc * 2997 == ts * 125) target_timescale = 2997; else if (is_prores) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("ProRes Unrecognized frame rate %g\n", ((Double)ts)/inc )); return GF_NON_COMPLIANT_BITSTREAM; @@ -1478,8 +1545,6 @@ gf_isom_update_bitrate(ctx->file, tkw->track_num, 0, 0, 0, 0); } if (!udta_only) { - GF_Err gf_isom_set_track_stsd_templates(GF_ISOFile *movie, u32 trackNumber, u8 *stsd_data, u32 stsd_data_size); - p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_STSD_ALL_TEMPLATES); if (p) { gf_isom_set_track_stsd_templates(ctx->file, tkw->track_num, p->value.data.ptr, p->value.data.size); @@ -1964,6 +2029,11 @@ comp_name = "EAC-3"; use_ac3_entry = GF_TRUE; break; + case GF_CODECID_AC4: + m_subtype = GF_ISOM_SUBTYPE_AC4; + comp_name = "AC-4"; + use_ac4_entry = GF_TRUE; + break; case GF_CODECID_MPHA: if ((m_subtype_src!=GF_ISOM_SUBTYPE_MH3D_MHA1) && (m_subtype_src!=GF_ISOM_SUBTYPE_MH3D_MHA2)) m_subtype = GF_ISOM_SUBTYPE_MH3D_MHA1; @@ -2143,7 +2213,18 @@ use_av1 = GF_TRUE; comp_name = "AOM AV1 Video"; break; - + case GF_CODECID_AVS3_VIDEO: + use_gen_sample_entry = GF_FALSE; + m_subtype = GF_ISOM_SUBTYPE_AVS3; + use_avs3v = GF_TRUE; + comp_name = "AVS 3 Video"; + break; + case GF_CODECID_IAMF: + use_gen_sample_entry = GF_FALSE; + m_subtype = GF_ISOM_SUBTYPE_IAMF; + use_iamf = GF_TRUE; + comp_name = "AOM IAMF Audio"; + break; case GF_CODECID_VP8: use_gen_sample_entry = GF_FALSE; m_subtype = GF_ISOM_SUBTYPE_VP08; @@ -2370,7 +2451,10 @@ break; default: - m_subtype = codec_id; + if (!m_subtype_src) { + m_subtype_src = gf_codecid_4cc_type(codec_id); + } + m_subtype = m_subtype_src ? m_subtype_src : codec_id; unknown_generic = GF_TRUE; use_gen_sample_entry = GF_TRUE; use_m4sys = GF_FALSE; @@ -2425,7 +2509,7 @@ if (ctx->init_movie_done) { if (needs_sample_entry==1) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Cannot create a new sample description entry (codec change) for finalized movie in fragmented mode\n")); - return GF_NOT_SUPPORTED; + return GF_FILTER_NOT_SUPPORTED; } force_mix_xps = GF_TRUE; } else if (ctx->store < MP4MX_MODE_FRAG) { @@ -2474,7 +2558,9 @@ } else if (use_hevc && dsi) { if (tkw->hvcc) gf_odf_hevc_cfg_del(tkw->hvcc); - tkw->hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (codec_id == GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + + tkw->hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, + ((codec_id==GF_CODECID_LHVC) && !enh_dsi) ? GF_TRUE : GF_FALSE); if (enh_dsi) { if (tkw->lvcc) gf_odf_hevc_cfg_del(tkw->lvcc); @@ -2507,7 +2593,7 @@ return GF_OK; } GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Cannot create a new sample description entry (config changed) for finalized movie in fragmented mode\n")); - return GF_NOT_SUPPORTED; + return GF_FILTER_NOT_SUPPORTED; } tkw->xps_inband = xps_inband; @@ -2657,7 +2743,8 @@ return GF_OK; } if (dsi) { - tkw->hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (codec_id == GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + tkw->hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, + ((codec_id==GF_CODECID_LHVC)&&!enh_dsi) ? GF_TRUE : GF_FALSE); } else { tkw->hvcc = gf_odf_hevc_cfg_new(); } @@ -2802,7 +2889,60 @@ } gf_odf_av1_cfg_del(av1c); - } else if (use_vpX) { + } else if (use_avs3v) { + GF_AVS3VConfig *avs3v; + + if (!dsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux No decoder specific info found for AVS 3 Video\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + avs3v = gf_odf_avs3v_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (!avs3v) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to parser AVS 3 Video decoder specific info\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + e = gf_isom_avs3v_config_new(ctx->file, tkw->track_num, avs3v, (char *) src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Error creating new AVS 3 Video sample description: %s\n", gf_error_to_string(e) )); + return e; + } + tkw->is_avs3v = GF_TRUE; + + if (!tkw->has_brands) { + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_ISO4, 1); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_CAV3, GF_TRUE); + } + + gf_odf_avs3v_cfg_del(avs3v); + } else if (use_iamf) { + GF_IAConfig *iacb; + + if (!dsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux No decoder specific info found for IAMF\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + iacb = gf_odf_iamf_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (!iacb) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to parser IAMF decoder specific info\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + e = gf_isom_iamf_config_new(ctx->file, tkw->track_num, iacb, (char *) src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Error creating new IAMF sample description: %s\n", gf_error_to_string(e) )); + return e; + } + + if (!tkw->has_brands) { + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_MP42, 1); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO6, GF_TRUE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_IAMF, GF_TRUE); + } + gf_odf_iamf_cfg_del(iacb); + } + else if (use_vpX) { GF_VPConfig *vpc; if (!dsi) { @@ -2870,7 +3010,7 @@ memset(&ac3cfg, 0, sizeof(GF_AC3Config)); if (dsi) { - gf_odf_ac3_config_parse(dsi->value.data.ptr, dsi->value.data.size, (codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3cfg); + gf_odf_ac3_cfg_parse(dsi->value.data.ptr, dsi->value.data.size, (codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3cfg); } else { if (codec_id==GF_CODECID_EAC3) ac3cfg.is_ec3 = GF_TRUE; } @@ -2880,6 +3020,22 @@ return e; } tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + } else if (use_ac4_entry) { + GF_AC4Config ac4cfg = {0}; + + if (dsi) { + gf_odf_ac4_cfg_parse(dsi->value.data.ptr, dsi->value.data.size, &ac4cfg); + } + + e = gf_isom_ac4_config_new(ctx->file, tkw->track_num, &ac4cfg, (char *)src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Error creating new AC4 audio sample description for stream type %d codecid %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + tkw->is_ac4 = GF_TRUE; + + gf_odf_ac4_cfg_clean_list(&ac4cfg); } else if (use_flac_entry) { e = gf_isom_flac_config_new(ctx->file, tkw->track_num, dsi ? dsi->value.data.ptr : NULL, dsi ? dsi->value.data.size : 0, (char *)src_url, NULL, &tkw->stsd_idx); if (e) { @@ -3080,6 +3236,12 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Error creating new TrueHD Audio sample description: %s\n", gf_error_to_string(e) )); return e; } + } else if (codec_id==GF_CODECID_EVTE) { //EventMessage Track + e = gf_isom_evte_config_new(ctx->file, tkw->track_num, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Error creating new EventMessage Track sample description: %s\n", gf_error_to_string(e) )); + return e; + } } else if (use_gen_sample_entry) { u8 isor_ext_buf14, *gpac_meta_dsi=NULL; u32 len = 0; @@ -3100,7 +3262,7 @@ udesc.lpcm_flags = afmt_flags | (1<<3); //add packed flag //for raw audio, select qt vs isom and set version if (sr && (codec_id==GF_CODECID_RAW)) { - if (ctx->make_qt && (ase_mode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS)) { + if (ctx->make_qt && (ase_mode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_DEFAULT)) { udesc.is_qtff = GF_TRUE; //if extensions or not 'raw ' or 'twos', use v1 if (dsi || @@ -3210,7 +3372,7 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux muxing %s, using generic sample entry with 4CC \"GMCW\" and \"GMCC\" config box\n", gf_codecid_name(codec_id))); udesc.codec_tag = GF_4CC('G', 'M', 'C', 'W'); udesc.ext_box_wrap = GF_4CC('G', 'M', 'C', 'C'); - } else { + } else if (!m_subtype_src) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux muxing unknown codec ID %s, using generic sample entry with 4CC \"%s\"\n", gf_codecid_name(codec_id), gf_4cc_to_str(m_subtype) )); } } @@ -3366,6 +3528,7 @@ case GF_HLS_SAMPLE_AES_SCHEME: tkw->cenc_state = CENC_NEED_SETUP; if (tkw->is_nalu || tkw->is_av1 || tkw->is_vpx) tkw->cenc_subsamples = GF_TRUE; + if (tkw->is_ac4) tkw->cenc_subsamples = GF_TRUE; break; default: GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux Unrecognized protection scheme type %s, using generic signaling\n", gf_4cc_to_str(scheme_type) )); @@ -3390,8 +3553,8 @@ } } else if (!tkw->is_encrypted) { //in case we used track template - gf_isom_remove_samp_enc_box(ctx->file, tkw->track_num); - gf_isom_remove_samp_group_box(ctx->file, tkw->track_num); + gf_isom_remove_cenc_senc_box(ctx->file, tkw->track_num); + gf_isom_remove_cenc_seig_sample_group(ctx->file, tkw->track_num); } if (is_true_pid) { @@ -3410,15 +3573,14 @@ } //check if we have sample-accurate seek info for the pid. If so, enable seek ts checking - p = gf_filter_pid_get_property(pid, GF_PROP_PCK_SKIP_BEGIN); - if (p && p->value.sint) + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HAS_SKIP_BEGIN); + if (p && p->value.boolean) tkw->check_seek_ts = GF_TRUE; p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DURATION); if (p && p->value.lfrac.den) { tkw->pid_dur = p->value.lfrac; - if (tkw->pid_dur.num<0) tkw->pid_dur.num = -tkw->pid_dur.num; } } else if (codec_id==GF_CODECID_HEVC_TILES) { @@ -3494,7 +3656,7 @@ Bool full_range_flag=GF_FALSE; gf_isom_set_visual_info(ctx->file, tkw->track_num, tkw->stsd_idx, width, height); - if (sar.den) { + if (sar.den && (sar.num>0)) { if (sar.num != sar.den) { gf_isom_set_pixel_aspect_ratio(ctx->file, tkw->track_num, tkw->stsd_idx, sar.num, sar.den, GF_FALSE); width = width * sar.num / sar.den; @@ -3654,7 +3816,7 @@ ref_id = gf_isom_get_track_id(ctx->file, tkw_base->track_num); gf_isom_set_track_reference(ctx->file, tkw->track_num, GF_4CC('v','d','e','p'), ref_id); - //dolby requires seperate moof for each track fragment for base and el + //dolby requires separate moof for each track fragment for base and el if (ctx->store>=MP4MX_MODE_FRAG) { ctx->straf = GF_TRUE; } @@ -3692,8 +3854,37 @@ mx7 = 65536*p->value.vec2i.y; gf_isom_set_track_matrix(ctx->file, tkw->track_num, mx); } + } + //set clap + GF_Fraction clap_w, clap_h, clap_x, clap_y; + clap_w.num = 0; + clap_w.den = 0; + clap_h = clap_x = clap_y = clap_w; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CLAP_W); + if (p) clap_w = p->value.frac; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CLAP_H); + if (p) clap_h = p->value.frac; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CLAP_X); + if (p) clap_x = p->value.frac; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CLAP_Y); + if (p) clap_y = p->value.frac; + if (clap_w.num || clap_h.num || clap_x.num || clap_y.num) { + if (!clap_w.num) { + clap_w.num = width; + clap_w.den = 1; + } + if (!clap_h.num) { + clap_h.num = height; + clap_h.den = 1; + } + if (!clap_x.den) clap_x.den=1; + if (!clap_y.den) clap_y.den=1; + + gf_isom_set_clean_aperture(ctx->file, tkw->track_num, tkw->stsd_idx, clap_w.num, clap_w.den, clap_h.num, clap_h.den, clap_x.num, clap_x.den, clap_y.num, clap_y.den); } + } //default for old arch else if (force_tk_layout @@ -3709,6 +3900,11 @@ gf_isom_update_sample_description_from_template(ctx->file, tkw->track_num, tkw->stsd_idx, p->value.data.ptr, p->value.data.size); } + //special case for pasp: if negative values are set, remove pasp box even if present in source template + if (width && (sar.num<0)) { + gf_isom_set_pixel_aspect_ratio(ctx->file, tkw->track_num, tkw->stsd_idx, 0, 0, GF_FALSE); + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHAP_TIMES); const GF_PropertyValue *p2 = gf_filter_pid_get_property(pid, GF_PROP_PID_CHAP_NAMES); if (p && p2 && (p->value.uint_list.nb_items == p2->value.string_list.nb_items)) { @@ -3778,6 +3974,14 @@ } gf_isom_add_sample(ctx->file, ctx->chap_track_num, trak_di, samp); gf_isom_sample_del(&samp); + + if (j+1==p2->value.string_list.nb_items) { + u64 end = gf_timestamp_rescale(tkw->pid_dur.num, tkw->pid_dur.den, 1000); + if (end>start_time) + gf_isom_set_last_sample_duration(ctx->file, ctx->chap_track_num, (u32) (end-start_time)); + else + gf_isom_set_last_sample_duration(ctx->file, ctx->chap_track_num, 1000); + } } } } @@ -3872,18 +4076,20 @@ } #ifndef GPAC_DISABLE_AV_PARSERS if (tkw->svcc) { - AVCState avc; - memset(&avc, 0, sizeof(AVCState)); - count = gf_list_count(tkw->svcc->sequenceParameterSets); - for (i=0; i<count; i++) { - GF_NALUFFParam *sl = gf_list_get(tkw->svcc->sequenceParameterSets, i); - u8 nal_type = sl->data0 & 0x1F; - Bool is_subseq = (nal_type == GF_AVC_NALU_SVC_SUBSEQ_PARAM) ? GF_TRUE : GF_FALSE; - s32 ps_idx = gf_avc_read_sps(sl->data, sl->size, &avc, is_subseq, NULL); - if (ps_idx>=0) { - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("SVC Detected - SSPS ID %d - frame size %d x %d\n", ps_idx-GF_SVC_SSPS_ID_SHIFT, avc.spsps_idx.width, avc.spsps_idx.height )); - + AVCState *avc_state; + GF_SAFEALLOC(avc_state, AVCState); + if (avc_state) { + count = gf_list_count(tkw->svcc->sequenceParameterSets); + for (i=0; i<count; i++) { + GF_NALUFFParam *sl = gf_list_get(tkw->svcc->sequenceParameterSets, i); + u8 nal_type = sl->data0 & 0x1F; + Bool is_subseq = (nal_type == GF_AVC_NALU_SVC_SUBSEQ_PARAM) ? GF_TRUE : GF_FALSE; + s32 ps_idx = gf_avc_read_sps(sl->data, sl->size, avc_state, is_subseq, NULL); + if (ps_idx>=0) { + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("SVC Detected - SSPS ID %d - frame size %d x %d\n", ps_idx-GF_SVC_SSPS_ID_SHIFT, avc_state->spsps_idx.width, avc_state->spsps_idx.height )); + } } + gf_free(avc_state); } } #endif @@ -3910,6 +4116,7 @@ if (is_true_pid && !tkw->nb_samples && !tkw->is_item) { Bool use_negccts = GF_FALSE; Bool remove_edits = GF_FALSE; + Bool has_pid_delay = GF_FALSE; s64 moffset=0; ctx->config_timing = GF_TRUE; ctx->update_report = GF_TRUE; @@ -3921,7 +4128,7 @@ } else { //old arch compat: if we had a simple edit list in source, keep dur and offset //and avoid rewriting it when recomputing edit for b-frames - u64 etime, sdur; + u64 etime=0, sdur=0; GF_ISOEditType etype; gf_isom_get_edit(ctx->file, tkw->track_num, 1, &etime, &sdur, &moffset, &etype); if (!etime && sdur) { @@ -3936,6 +4143,7 @@ p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DELAY); if (p) { + has_pid_delay = GF_TRUE; //media skip if (p->value.longsint < 0) { //if cmf2, remove edits and use negctss @@ -3987,6 +4195,18 @@ gf_isom_remove_edits(ctx->file, tkw->track_num); } } + + if (!has_pid_delay) { + if (ctx->ctmode==MP4MX_CT_NEGCTTS) + use_negccts=GF_TRUE; + else if ((ctx->cmaf==MP4MX_CMAF_CMF2) && (tkw->stream_type==GF_STREAM_VISUAL)) + use_negccts=GF_TRUE; + else if ((ctx->ctmode==MP4MX_CT_AUTO) && (ctx->store>=MP4MX_MODE_FRAG)) { + if ( gf_isom_get_min_negative_cts_offset(ctx->file, tkw->track_num, GF_ISOM_MIN_NEGCTTS_CLSG) != 0) + use_negccts=GF_TRUE; + } + } + if (use_negccts) { gf_isom_set_composition_offset_mode(ctx->file, tkw->track_num, GF_TRUE); @@ -3997,12 +4217,22 @@ gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO2, GF_FALSE); gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO3, GF_FALSE); } - + //can be 0 if remuxing from ISOBMFF with negctts tkw->negctts_shift = (tkw->ts_delay<0) ? -tkw->ts_delay : 0; } else { + if (!has_pid_delay && (ctx->store>=MP4MX_MODE_FRAG)) { + //when PID delay is signaled, CTS is always >= DTS + //otherwise, and only when refragmenting ISOBMFF sources, we need to patch CTS offset (when remuxing negctts to non neg ctts) + //check from isobmf file if info was present (cslg is cloned in track template) + tkw->negctts_shift = gf_isom_get_min_negative_cts_offset(ctx->file, tkw->track_num, GF_ISOM_MIN_NEGCTTS_CLSG); + if (tkw->negctts_shift) + gf_isom_set_edit(ctx->file, tkw->track_num, 0, 0, -tkw->negctts_shift, GF_ISOM_EDIT_NORMAL); + } //this will remove any cslg in the track due to template gf_isom_set_composition_offset_mode(ctx->file, tkw->track_num, GF_FALSE); } + if (ctx->ctmode==MP4MX_CT_NOEDIT) + gf_isom_remove_edits(ctx->file, tkw->track_num); if (!ctx->noinit) { mp4_mux_set_tags(ctx, tkw); @@ -4029,6 +4259,7 @@ gf_list_del_item(ctx->tracks, tkw); if (ctx->ref_tkw == tkw) ctx->ref_tkw = gf_list_get(ctx->tracks, 0); gf_free(tkw); + ctx->track_removed = GF_TRUE; } //removing last pid if (ctx->opid && !gf_list_count(ctx->tracks)) { @@ -4130,7 +4361,7 @@ if (!tkw->dyn_pssh) return; pck = gf_filter_pid_get_packet(tkw->ipid); if (pck) { - pssh = gf_filter_pck_get_property(pck, GF_PROP_PID_CENC_PSSH); + pssh = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_PSSH); //change of dynamic pssh is pending, don't inject the old one if (pssh) return; } @@ -4146,7 +4377,7 @@ if (dyn_pssh_mode) { GF_FilterPacket *pck = gf_filter_pid_get_packet(tkw->ipid); if (pck) { - p = gf_filter_pck_get_property(pck, GF_PROP_PID_CENC_PSSH); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_PSSH); } } if (!p) @@ -4172,19 +4403,28 @@ if (kid_count>=max_keys) { max_keys = kid_count; + if ( (max_keys > GF_UINT_MAX / 16) || (max_keys > gf_bs_available(ctx->bs_r)/16)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux kid count invalid\n" )); + break; + } + keyIDs = gf_realloc(keyIDs, sizeof(bin128)*max_keys); } for (j=0; j<kid_count; j++) { gf_bs_read_data(ctx->bs_r, keyIDsj, 16); } len = gf_bs_read_u32(ctx->bs_r); + if (len>gf_bs_available(ctx->bs_r)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux pssh length invalid\n" )); + break; + } data = p->value.data.ptr + gf_bs_get_position(ctx->bs_r); if (tkw->is_item) mode = 2; else if (tkw->scheme_type==GF_ISOM_PIFF_SCHEME) mode = 1; else mode = 0; - gf_cenc_set_pssh(ctx->file, sysID, version, kid_count, keyIDs, data, len, mode); + gf_isom_cenc_set_pssh(ctx->file, sysID, version, kid_count, keyIDs, data, len, mode); gf_bs_skip_bytes(ctx->bs_r, len); if (gf_bs_is_overflow(ctx->bs_r)) break; @@ -4390,7 +4630,7 @@ return e; } - p = gf_filter_pck_get_property(pck, GF_PROP_PID_CENC_PSSH); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_PSSH); if (p && (p->type == GF_PROP_DATA) && p->value.data.ptr) { if (ctx->store>=MP4MX_MODE_FRAG) { mp4_mux_cenc_insert_pssh(ctx, tkw, p, 0); @@ -4448,7 +4688,6 @@ u32 offset = 0; u32 first_sub_clear, sub_count_size; u8 *sai_d; - u8 key_info_get_iv_size(const u8 *key_info, u32 nb_keys, u32 idx, u8 *const_iv_size, const u8 **const_iv); gf_assert(tkw->cenc_subsamples); @@ -4467,7 +4706,7 @@ idx<<=8; idx |= sai_p1; - mk_iv_size = key_info_get_iv_size(tkw->cenc_ki->value.data.ptr, tkw->cenc_ki->value.data.size, idx, NULL, NULL); + mk_iv_size = gf_cenc_key_info_get_iv_size(tkw->cenc_ki->value.data.ptr, tkw->cenc_ki->value.data.size, idx, NULL, NULL); mk_iv_size += 2; //idx if (mk_iv_size > remain) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Invalid multi-key CENC SAI, cannot modify first subsample !\n")); @@ -4484,7 +4723,7 @@ sub_count_size = 4; //32bit sub count } else { - offset = key_info_get_iv_size(tkw->cenc_ki->value.data.ptr, tkw->cenc_ki->value.data.size, 1, NULL, NULL); + offset = gf_cenc_key_info_get_iv_size(tkw->cenc_ki->value.data.ptr, tkw->cenc_ki->value.data.size, 1, NULL, NULL); sub_count_size = 2; //16bit sub count } if (sai_size < offset + sub_count_size + 6) { @@ -4593,6 +4832,15 @@ u32 sample_desc_index = tkw->stsd_idx; Bool sample_timing_ok = GF_TRUE; + if (!sample_desc_index) { +#ifndef GPAC_DISABLE_LOG + //we log as debug when initial timing config was performed + u32 logl = ((ctx->store>=MP4MX_MODE_FRAG) && !ctx->tsalign) ? GF_LOG_WARNING : GF_LOG_DEBUG; + GF_LOG(logl, GF_LOG_CONTAINER, ("MP4Mux No valid sample desc for sample from %s, discarding\n", gf_filter_pid_get_name(tkw->ipid) )); +#endif + return GF_OK; + } + timescale = gf_filter_pck_get_timescale(pck); prev_dts = tkw->nb_samples ? tkw->sample.DTS : GF_FILTER_NO_TS; @@ -4621,7 +4869,7 @@ u32 min_pck_dur = gf_filter_pid_get_min_pck_duration(tkw->ipid); if (min_pck_dur) { tkw->sample.DTS = prev_dts; - //transform back to inpput timescale + //transform back to input timescale if (timescale != tkw->tk_timescale) { tkw->sample.DTS = gf_timestamp_rescale(tkw->sample.DTS, tkw->tk_timescale, timescale); } @@ -4722,7 +4970,7 @@ "Partial Segment containing an independent frame SHOULD carry it to increase the efficiency with which clients can join and switch Renditions" -> if used for switching, this only allows SAP 1 and 2 - Spec should be fixed to allow for both cases (fast tune-in or in-segment switchingà) + Spec should be fixed to allow for both cases (fast tune-in or in-segment switching) */ if ((tkw->sample.IsRAP == SAP_TYPE_1) || (tkw->sample.IsRAP == SAP_TYPE_2)) ctx->frag_has_intra = GF_TRUE; @@ -4733,7 +4981,9 @@ if (!for_fragment && ctx->patch_dts) { gf_isom_patch_last_sample_duration(ctx->file, tkw->track_num, prev_dts ? prev_dts : 1); } - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux PID %s ID %d Sample %d with DTS "LLU" less than previous sample DTS "LLU", patching DTS%s\n", gf_filter_pid_get_name(tkw->ipid), tkw->track_id, tkw->nb_samples+1, tkw->sample.DTS, prev_dts, ctx->patch_dts ? "and adjusting prev sample duration" : "" )); + if ((tkw->stream_type!=GF_STREAM_TEXT) || (prev_dts >= tkw->sample.DTS+tkw->src_timescale/10)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux PID %s ID %d Sample %d with DTS "LLU" less than previous sample DTS "LLU", patching DTS%s\n", gf_filter_pid_get_name(tkw->ipid), tkw->track_id, tkw->nb_samples+1, tkw->sample.DTS, prev_dts, ctx->patch_dts ? "and adjusting prev sample duration" : "" )); + } sample_timing_ok = GF_FALSE; if (prev_dts) { @@ -4833,14 +5083,14 @@ inject_pps = GF_TRUE; } - if ((tkw->sample.IsRAP || tkw->force_inband_inject || inject_pps) && tkw->xps_inband) { + if ((tkw->sample.IsRAP || tkw->force_inband_inject || inject_pps || (sap_type==GF_FILTER_SAP_3)) && tkw->xps_inband) { u8 *inband_xps; u32 inband_xps_size; char *au_delim=NULL; u32 au_delim_size=0; char *pck_data = tkw->sample.data; u32 pck_data_len = tkw->sample.dataLength; - if (tkw->sample.IsRAP || tkw->force_inband_inject) { + if (tkw->sample.IsRAP || tkw->force_inband_inject || (sap_type==GF_FILTER_SAP_3)) { inband_xps = tkw->inband_hdr; inband_xps_size = tkw->inband_hdr_size; tkw->force_inband_inject = GF_FALSE; @@ -4955,6 +5205,21 @@ if (e) return e; + const GF_PropertyValue *p_id = gf_filter_pck_get_property(pck, GF_PROP_PCK_ID); + if (p_id) { + const GF_PropertyValue *p_refs = gf_filter_pck_get_property(pck, GF_PROP_PCK_REFS); + u32 nb_refs = p_refs ? p_refs->value.sint_list.nb_items : 0; + s32 *refs = p_refs ? p_refs->value.sint_list.vals : NULL; + if (for_fragment) + e = gf_isom_fragment_add_sample_references(ctx->file, tkw->track_id, p_id->value.sint, nb_refs, refs); + else + e = gf_isom_set_sample_references(ctx->file, tkw->track_num, tkw->nb_samples, p_id->value.sint, nb_refs, refs); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to add sample references: %s\n", gf_error_to_string(e) )); + } + } + + if (!for_fragment && sample_timing_ok) { u64 samp_cts; if (!tkw->clamp_ts_plus_one) { @@ -4977,15 +5242,15 @@ } //compat with old arch: write sample to group info for all samples - if ((sap_type==3) || tkw->has_open_gop) { + if ((sap_type==GF_FILTER_SAP_3) || tkw->has_open_gop) { if (!ctx->norap) { if (for_fragment) { #ifndef GPAC_DISABLE_ISOM_FRAGMENTS - e = gf_isom_fragment_set_sample_rap_group(ctx->file, tkw->track_id, tkw->samples_in_frag, (sap_type==3) ? GF_TRUE : GF_FALSE, 0); + e = gf_isom_fragment_set_sample_rap_group(ctx->file, tkw->track_id, tkw->samples_in_frag, (sap_type==GF_FILTER_SAP_3) ? GF_TRUE : GF_FALSE, 0); #else e = GF_NOT_SUPPORTED; #endif - } else if (sap_type==3) { + } else if (sap_type==GF_FILTER_SAP_3) { e = gf_isom_set_sample_rap_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE /*(sap_type==3) ? GF_TRUE : GF_FALSE*/, 0); } if (e) { @@ -4994,6 +5259,22 @@ } tkw->has_open_gop = GF_TRUE; } + + if (gf_filter_pck_get_switch_frame(pck)) { + if (for_fragment) { +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + e = gf_isom_fragment_set_sample_av1_switch_frame_group(ctx->file, tkw->track_id, tkw->samples_in_frag, GF_TRUE); +#else + e = GF_NOT_SUPPORTED; +#endif + } else { + e = gf_isom_set_sample_av1_switch_frame_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Failed to set sample DTS "LLU" SAP 3 in RAP group: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); + } + } + if (!ctx->noroll) { if ((sap_type==GF_FILTER_SAP_4) || (sap_type==GF_FILTER_SAP_4_PROL) || tkw->gdr_type) { GF_ISOSampleRollType roll_type = 0; @@ -5081,11 +5362,13 @@ if (ctx->deps) { u8 dep_flags = gf_filter_pck_get_dependency_flags(pck); - if (dep_flags) { + if (dep_flags || tkw->has_deps) { u32 is_leading = (dep_flags>>6) & 0x3; u32 depends_on = (dep_flags>>4) & 0x3; u32 depended_on = (dep_flags>>2) & 0x3; u32 redundant = (dep_flags) & 0x3; + //once we start signaling deps (usually on first iframe), always signal them + tkw->has_deps = GF_TRUE; if (for_fragment) { #ifndef GPAC_DISABLE_ISOM_FRAGMENTS gf_isom_fragment_set_sample_flags(ctx->file, tkw->track_id, is_leading, depends_on, depended_on, redundant); @@ -5359,7 +5642,6 @@ break; case GF_CODECID_AV1: if (!dsi) return GF_OK; - config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_AV1C); ((GF_AV1ConfigurationBox *)config_box)->config = gf_odf_av1_cfg_read(dsi->value.data.ptr, dsi->value.data.size); @@ -5380,6 +5662,15 @@ } media_brand = GF_ISOM_BRAND_AVIF; break; + case GF_CODECID_IAMF: + if (!dsi) return GF_OK; + config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_IAMF); + ((GF_IAConfigurationBox *)config_box)->cfg = gf_odf_iamf_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (! ((GF_IAConfigurationBox *)config_box)->cfg) return GF_NON_COMPLIANT_BITSTREAM; + + item_type = GF_ISOM_SUBTYPE_IAMF; + media_brand = GF_ISOM_BRAND_IAMF; + break; case GF_CODECID_JPEG: item_type = GF_ISOM_SUBTYPE_JPEG; media_brand = GF_ISOM_SUBTYPE_JPEG /* == GF_4CC('j', 'p', 'e', 'g') */; @@ -5387,6 +5678,12 @@ case GF_CODECID_J2K: item_type = GF_ISOM_SUBTYPE_JP2K; media_brand = GF_4CC('j', '2', 'k', 'i'); + if (dsi) { + GF_BitStream *dsi_bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ); + gf_isom_box_parse(&config_box, dsi_bs); + gf_bs_del(dsi_bs); + } + break; case GF_CODECID_PNG: item_type = GF_ISOM_SUBTYPE_PNG; @@ -5439,7 +5736,7 @@ p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ALPHA); if (p) image_props.alpha = p->value.boolean; p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_SAR); - if (p) { + if (p && (p->value.frac.num>0)) { image_props.hSpacing = p->value.frac.num; image_props.vSpacing = p->value.frac.den; } else { @@ -5599,7 +5896,7 @@ } } -static void mp4_mux_flush_frag_hls(GF_MP4MuxCtx *ctx) +static void mp4_mux_flush_frag_llhas(GF_MP4MuxCtx *ctx) { GF_FilterEvent evt; TrackWriter *tkw = NULL; @@ -5627,6 +5924,7 @@ static void mp4_mux_flush_seg(GF_MP4MuxCtx *ctx, Bool is_init, u64 idx_start_range, u64 idx_end_range, Bool signal_flush) { GF_FilterEvent evt; + u8 *base64_init = NULL; TrackWriter *tkw = NULL; if (ctx->dst_pck) { @@ -5648,7 +5946,7 @@ gf_filter_pck_set_carousel_version(ctx->dst_pck, 1); } //also inject m4cc after init seg - cf issue 2482 - //don't send as new packet as this wll break framing if init segment, just expand the packet + //don't send as new packet as this will break framing if init segment, just expand the packet //the init segment packet is always an allocated one, so expanding it is safe if (is_init && ctx->eos_marker) { u8 *data; @@ -5661,12 +5959,21 @@ data7 = ctx->m4cc3; mp4_mux_on_data(ctx, data, 8, NULL, 0); } + + if (!ctx->single_file && ctx->dash_mode && is_init && ctx->send_base64) { + u32 init_size, size_b64; + const u8 *init_data = gf_filter_pck_get_data(ctx->dst_pck, &init_size); + size_b64 = 2*init_size + 3; + base64_init = gf_malloc(sizeof(char) * size_b64); + size_b64 = gf_base64_encode((const char *)init_data, init_size, (char *)base64_init, size_b64); + base64_initsize_b64 = 0; + } mp4mux_send_output(ctx); if (signal_flush) gf_filter_pid_send_flush(ctx->opid); } - if (!is_init && ctx->llhls_mode && ctx->frag_size) { - mp4_mux_flush_frag_hls(ctx); + if (!is_init && (ctx->llhas_mode) && ctx->frag_size) { + mp4_mux_flush_frag_llhas(ctx); } if (ctx->dash_mode) { //send event on first track only @@ -5681,6 +5988,9 @@ if (idx_end_range && (ctx->vodcache==MP4MX_VODCACHE_INSERT)) evt.seg_size.is_shift = 1; + if (base64_init) { + evt.seg_size.base64_version = base64_init; + } evt.seg_size.idx_range_start = idx_start_range; evt.seg_size.idx_range_end = idx_end_range; gf_filter_pid_send_event(tkw->ipid, &evt); @@ -5691,6 +6001,8 @@ ctx->frag_size = 0; ctx->frag_num = 0; ctx->frag_has_intra = GF_FALSE; + if (base64_init) gf_free(base64_init); + //changing file if (ctx->seg_name) { ctx->first_pck_sent = GF_FALSE; @@ -5747,7 +6059,7 @@ pck = gf_filter_pid_get_packet(tkw->ipid); if (!pck) { - //eos (wether real or flush event), continue setup + //eos (whether real or flush event), continue setup if (gf_filter_pid_is_eos(tkw->ipid)) { if (tkw->dgl_copy) { gf_filter_pck_discard(tkw->dgl_copy); @@ -5783,7 +6095,6 @@ p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DURATION); if (p && p->value.lfrac.den) { tkw->pid_dur = p->value.lfrac; - if (tkw->pid_dur.num<0) tkw->pid_dur.num = -tkw->pid_dur.num; if (gf_timestamp_less(max_dur.num, max_dur.den, tkw->pid_dur.num, tkw->pid_dur.den)) { max_dur.num = tkw->pid_dur.num; max_dur.den = tkw->pid_dur.den; @@ -5893,7 +6204,7 @@ mp4_mux_set_hevc_groups(ctx, tkw); - //use 1 for the default sample description index. If no multi stsd, this is always the case + //use GF_TRUE for the default sample description index. If no multi stsd, this is always the case //otherwise we need to update the stsd idx in the traf headers e = gf_isom_setup_track_fragment(ctx->file, tkw->track_id, tkw->stsd_idx, def_pck_dur, def_samp_size, def_is_rap, 0, 0, ctx->nofragdef ? GF_TRUE : GF_FALSE); if (e) { @@ -6316,6 +6627,9 @@ tkw->sample.CTS_Offset = 0; tkw->samples_in_stsd = 0; tkw->samples_in_frag = 0; + if (tkw->cenc_state) + tkw->cenc_state = CENC_NEED_SETUP; + } gf_assert(ctx->next_file_idx); ctx->cur_file_idx_plus_one = ctx->next_file_idx; @@ -6329,6 +6643,69 @@ return GF_OK; } +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +static void mp4_process_id3(GF_MovieFragmentBox *moof, const GF_PropertyValue *emsg_prop, u32 id_sequence) +{ + GF_Err err = GF_OK; + GF_ID3_TAG id3_tag; + GF_BitStream *bs = gf_bs_new(emsg_prop->value.data.ptr, emsg_prop->value.data.size, GF_BITSTREAM_READ); + + // first, read the number of tags serialized in the bitstream + u32 tag_count = gf_bs_read_u32(bs); + for (u32 i = 0; i < tag_count; ++i) { + memset(&id3_tag, 0, sizeof(GF_ID3_TAG)); + err = gf_id3_from_bitstream(&id3_tag, bs); + if (err != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error deserializing ID3 tag: %s", gf_error_to_string(err))); + gf_id3_tag_free(&id3_tag); + gf_bs_del(bs); + return; + } + + GF_EventMessageBox *emsg = (GF_EventMessageBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EMSG); + + emsg->version = 1; + emsg->timescale = id3_tag.timescale; + emsg->presentation_time_delta = id3_tag.pts; + emsg->event_duration = 0xFFFFFFFF; + emsg->event_id = id_sequence; + emsg->scheme_id_uri = gf_strdup(id3_tag.scheme_uri); + emsg->value = gf_strdup(id3_tag.value_uri); + emsg->message_data_size = id3_tag.data_length; + emsg->message_data = (u8 *)gf_malloc(id3_tag.data_length); + memcpy(emsg->message_data, id3_tag.data, id3_tag.data_length); + + // insert only if its presentation time is not already present + u32 i, insert_emsg = GF_TRUE; + for (i = 0; i < gf_list_count(moof->emsgs); ++i) + { + GF_EventMessageBox *existing_emsg = gf_list_get(moof->emsgs, i); + if (existing_emsg->presentation_time_delta == emsg->presentation_time_delta) + { + if (!strcmp(existing_emsg->scheme_id_uri, id3_tag.scheme_uri) && !strcmp(existing_emsg->value, id3_tag.value_uri)) + { + if (existing_emsg->message_data_size == emsg->message_data_size && !memcmp(existing_emsg->message_data, emsg->message_data, emsg->message_data_size)) + insert_emsg = GF_FALSE; + break; + } + } + } + + if (insert_emsg == GF_TRUE) { + if (!moof->emsgs) + moof->emsgs = gf_list_new(); + gf_list_add(moof->emsgs, emsg); + } else { + gf_isom_box_del((GF_Box*)emsg); + } + + gf_id3_tag_free(&id3_tag); + } + + gf_bs_del(bs); +} +#endif + static GF_Err mp4_mux_process_fragmented(GF_MP4MuxCtx *ctx) { #ifndef GPAC_DISABLE_ISOM_FRAGMENTS @@ -6360,6 +6737,9 @@ u64 cts, dts, ncts; TrackWriter *tkw = gf_list_get(ctx->tracks, i); + if (!tkw) + continue; + if (ctx->fragment_started && tkw->fragment_done) { nb_done ++; continue; @@ -6373,9 +6753,14 @@ while (1) { const GF_PropertyValue *p; u32 orig_frag_bounds=0; + ctx->track_removed=GF_FALSE; GF_FilterPacket *pck = gf_filter_pid_get_packet(tkw->ipid); if (!pck) { + if (ctx->track_removed) { + nb_done ++; + break; + } if (gf_filter_pid_is_eos(tkw->ipid)) { tkw->fragment_done = GF_TRUE; if (ctx->dash_mode) ctx->flush_seg = GF_TRUE; @@ -6439,7 +6824,7 @@ if (orig_frag_bounds==2) { if (!ctx->segment_started) { - ctx->dash_mode = 1; + ctx->dash_mode = MP4MX_DASH_ON; ctx->insert_tfdt = GF_TRUE; gf_isom_start_segment(ctx->file, ctx->single_file ? NULL : "_gpac_isobmff_redirect", GF_FALSE); } else if (tkw->samples_in_frag) { @@ -6483,20 +6868,29 @@ e = mp4_mux_start_fragment(ctx, orig_frag_bounds ? pck : NULL); if (e) return e; - //push emsgonce the segment is started + //push emsg boxes once the segment is started + GF_Err gf_isom_set_emsg(GF_ISOFile *movie, u8 *data, u32 size); + const GF_PropertyValue *emsg = gf_filter_pck_get_property_str(pck, "grp_EMSG"); if (emsg && (emsg->type==GF_PROP_DATA) && emsg->value.data.ptr) { - GF_Err gf_isom_set_emsg(GF_ISOFile *movie, u8 *data, u32 size); - gf_isom_set_emsg(ctx->file, emsg->value.data.ptr, emsg->value.data.size); } ctx->nb_frags++; if (ctx->dash_mode) ctx->nb_frags_in_seg++; - } + //push ID3 packet properties as emsg + const GF_PropertyValue *emsg = gf_filter_pck_get_property_str(pck, "id3"); + if (emsg && (emsg->type == GF_PROP_DATA) && emsg->value.ptr) { + if (emsg != ctx->last_id3_processed) { + mp4_process_id3(ctx->file->moof, emsg, ctx->id3_id_sequence); + ctx->id3_id_sequence = ctx->id3_id_sequence + 1; + } + + ctx->last_id3_processed = emsg; + } if (ctx->dash_mode) { if (p) { @@ -6521,12 +6915,20 @@ if (ctx->seg_name) gf_free(ctx->seg_name); ctx->seg_name = gf_strdup(p->value.string); } + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE); + if (p && p->value.string) { + if (ctx->llhas_template) gf_free(ctx->llhas_template); + ctx->llhas_template = gf_strdup(p->value.string); + } //store PRFT only for reference track at segment start if (tkw==ctx->ref_tkw) { p = gf_filter_pck_get_property(pck, GF_PROP_PCK_SENDER_NTP); if (p) { - gf_isom_set_fragment_reference_time(ctx->file, tkw->track_id, p->value.longuint, cts); + gf_isom_set_fragment_reference_time(ctx->file, tkw->track_id, p->value.longuint, cts, ctx->prft == PRFT_BOTH); GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("MuxIsom Segment %s, storing NTP TS "LLU" for CTS "LLU" at "LLU" us, at UTC "LLU"\n", ctx->seg_name ? ctx->seg_name : "singlefile", p->value.longuint, cts, gf_sys_clock_high_res(), gf_net_get_utc())); + } else if (ctx->prft == PRFT_BOTH) { + gf_isom_set_fragment_reference_time(ctx->file, tkw->track_id, 0, cts, GF_TRUE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("MuxIsom Segment %s, will store mux time NTP TS for CTS "LLU" at "LLU" us, at UTC "LLU"\n", ctx->seg_name ? ctx->seg_name : "singlefile", cts, gf_sys_clock_high_res(), gf_net_get_utc())); } } } @@ -6536,6 +6938,18 @@ if (tkw->first_dts_in_seg_plus_one && (tkw->first_dts_in_seg_plus_one - 1 > dts)) tkw->first_dts_in_seg_plus_one = 1 + dts; } + + if (ctx->prft && !ctx->dash_mode) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_SENDER_NTP); + if (p) { + gf_isom_set_fragment_reference_time(ctx->file, tkw->track_id, p->value.longuint, cts, ctx->prft == PRFT_BOTH); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("MuxIsom Storing NTP TS "LLU" for CTS "LLU" at "LLU" us, at UTC "LLU"\n", p->value.longuint, cts, gf_sys_clock_high_res(), gf_net_get_utc())); + } else if (ctx->prft == PRFT_BOTH) { + gf_isom_set_fragment_reference_time(ctx->file, tkw->track_id, 0, cts, GF_TRUE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("MuxIsom Will store mux time NTP TS for CTS "LLU" at "LLU" us, at UTC "LLU"\n", cts, gf_sys_clock_high_res(), gf_net_get_utc())); + } + } + ncts = cts + gf_filter_pck_get_duration(pck); if (tkw->cts_next < ncts) tkw->cts_next = ncts; @@ -6576,7 +6990,7 @@ break; } tkw->dur_in_frag += dur; - if (ctx->llhls_mode && (ctx->frag_duration * tkw->src_timescale <= tkw->dur_in_frag * ctx->frag_timescale)) { + if ((ctx->llhas_mode) && (ctx->frag_duration * tkw->src_timescale <= tkw->dur_in_frag * ctx->frag_timescale)) { ctx->frag_duration = tkw->dur_in_frag; ctx->frag_timescale = tkw->src_timescale; } @@ -6600,7 +7014,7 @@ } if ((ctx->store>=MP4MX_MODE_FRAG) && tkw->samples_in_frag) { - p = gf_filter_pck_get_property(pck, GF_PROP_PID_CENC_PSSH); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_PSSH); if (p && (p->type == GF_PROP_DATA) && p->value.data.ptr && !ctx->flush_seg && !ctx->dash_mode) { tkw->fragment_done = GF_TRUE; tkw->samples_in_frag = 0; @@ -6661,6 +7075,11 @@ //process packet e = mp4_mux_process_sample(ctx, tkw, pck, GF_TRUE); + p = ctx->rsot ? gf_filter_pck_get_property(pck, GF_PROP_PCK_ORIG_DUR) : NULL; + if (p) { + gf_isom_set_fragment_original_duration(ctx->file, tkw->track_id, p->value.frac.den, (u32) p->value.frac.num); + } + //discard gf_filter_pid_drop_packet(tkw->ipid); @@ -6672,7 +7091,7 @@ } //done with this track - if single track per moof, request new fragment but don't touch the //fragmentation state of the track writers - if (ctx->straf && (i+1 < count)) { + if (ctx->straf && (i+1 < count) && ctx->fragment_started) { GF_ISOStartFragmentFlags flags = 0; if (ctx->moof_first) flags |= GF_ISOM_FRAG_MOOF_FIRST; #ifdef GF_ENABLE_CTRN @@ -6701,6 +7120,9 @@ if (!ctx->segment_started && !ctx->fragment_started) goto check_eos; + if (!ctx->ref_tkw) + goto check_eos; + Bool is_eos = (count == nb_eos) ? GF_TRUE : GF_FALSE; u32 ref_timescale; Bool flush_refs = ctx->dash_mode ? GF_FALSE : GF_TRUE; @@ -6777,7 +7199,9 @@ } //cannot flush in DASH mode if using sidx (vod single sidx or live 1 sidx/seg) else if (!ctx->dash_mode || ((ctx->subs_sidx<0) && (ctx->dash_mode<MP4MX_DASH_VOD) && !ctx->cloned_sidx) ) { - gf_isom_flush_fragments(ctx->file, GF_FALSE); + if (ctx->lmsg && is_eos && !ctx->dash_mode) + ctx->file->write_styp = GF_TRUE; + gf_isom_flush_fragments(ctx->file, is_eos); flush_refs = GF_TRUE; //if not in dash and EOS marker is set, inject marker after each fragment if (!ctx->dash_mode && ctx->eos_marker && ctx->fragment_started) { @@ -6794,12 +7218,12 @@ //we need to wait for packet to be written if (ctx->seg_flush_state) { - if (ctx->llhls_mode) ctx->flush_ll_hls = GF_TRUE; + if (ctx->llhas_mode) ctx->flush_llhas = GF_TRUE; return GF_OK; } - if (ctx->llhls_mode) { - mp4_mux_flush_frag_hls(ctx); + if (ctx->llhas_mode) { + mp4_mux_flush_frag_llhas(ctx); } if (!ctx->dash_mode || ctx->flush_seg) { @@ -6923,7 +7347,7 @@ static void mp4_mux_update_init_edit(GF_MP4MuxCtx *ctx, TrackWriter *tkw, u64 min_ts_service, Bool skip_adjust) { //compute offsets - s64 dts_diff = ctx->tsalign ? gf_timestamp_rescale(min_ts_service, 1000000, tkw->src_timescale) : 0; + s64 dts_diff = ctx->tsalign ? gf_timestamp_rescale(min_ts_service, (ctx->moovts ? ctx->moovts : 1000000), tkw->src_timescale) : 0; if (!skip_adjust) { dts_diff = (s64) tkw->ts_shift - dts_diff; @@ -6951,13 +7375,14 @@ } GF_List *services = gf_list_new(); u32 i, count; - Bool not_ready, blocking_refs, has_ready; + Bool not_ready, blocking_refs, has_ready, has_drop, force_ready=GF_FALSE; retry_all: count = gf_list_count(ctx->tracks); not_ready = GF_FALSE; blocking_refs = GF_FALSE; has_ready = GF_FALSE; + has_drop = GF_FALSE; for (i=0; i<gf_list_count(services);i++) { struct _service_info *si = gf_list_get(services, i); @@ -6967,7 +7392,7 @@ //compute min dts of first packet on each track - this assume all tracks are synchronized, might need adjustment for MPEG4 Systems for (i=0; i<count; i++) { - u64 ts, dts_min; + u64 ts, ts_min; GF_FilterPacket *pck; TrackWriter *tkw = gf_list_get(ctx->tracks, i); if (tkw->fake_track) continue; @@ -6976,19 +7401,19 @@ //already setup (happens when new PIDs are declared after a packet has already been written on other PIDs) if (tkw->nb_samples) { - dts_min = gf_timestamp_rescale(tkw->ts_shift, tkw->src_timescale, 1000000); + ts_min = gf_timestamp_rescale(tkw->ts_shift, tkw->src_timescale, (ctx->moovts ? ctx->moovts : 1000000) ); - if (si->first_ts_min > dts_min) { - si->first_ts_min = (u64) dts_min; + if (si->first_ts_min > ts_min) { + si->first_ts_min = (u64) ts_min; } continue; } retry_pck: pck = gf_filter_pid_get_packet(tkw->ipid); //check this after fetching a packet since it may reconfigure the track - if (!tkw->track_num) { + if (!tkw->track_num && !force_ready) { if (gf_filter_pid_is_eos(tkw->ipid)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux PID has no input packet and configuration not known after 10 retries, aborting initial timing sync\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux PID configuration not known after EOS, aborting initial timing sync\n")); continue; } not_ready = GF_TRUE; @@ -7004,6 +7429,7 @@ Bool seek = gf_filter_pck_get_seek_flag(pck); if (seek || !sap) { gf_filter_pid_drop_packet(tkw->ipid); + has_drop = GF_TRUE; goto retry_pck; } else { tkw->wait_sap = GF_FALSE; @@ -7039,7 +7465,7 @@ } if (!pck) { - //eos (wether real or flush event), setup cenc + //eos (whether real or flush event), setup cenc if (gf_filter_pid_is_eos(tkw->ipid)) { if (tkw->cenc_state==CENC_NEED_SETUP) mp4_mux_cenc_update(ctx, tkw, NULL, CENC_CONFIG, 0, 0); @@ -7075,16 +7501,16 @@ if (gf_list_find(ctx->tracks, tkw) != i) { goto retry_all; } - ts = gf_filter_pck_get_dts(pck); + //align timelines based on CTS otherwise we could add extra delay on other pids (audio, etc) when video with dts<cts appears first + ts = gf_filter_pck_get_cts(pck); if (ts==GF_FILTER_NO_TS) - ts = gf_filter_pck_get_cts(pck); + ts = gf_filter_pck_get_dts(pck); if (ts==GF_FILTER_NO_TS) ts=0; - dts_min = gf_timestamp_rescale(ts, tkw->src_timescale, 1000000); - - if (si->first_ts_min > dts_min) { - si->first_ts_min = (u64) dts_min; + ts_min = gf_timestamp_rescale(ts, tkw->src_timescale, (ctx->moovts ? ctx->moovts : 1000000) ); + if (si->first_ts_min > ts_min) { + si->first_ts_min = (u64) ts_min; has_ready = GF_TRUE; } @@ -7097,8 +7523,11 @@ si->nb_sparse_ready++; break; } + //ts_shift must be the first dts + tkw->ts_shift = gf_filter_pck_get_dts(pck); + if (tkw->ts_shift==GF_FILTER_NO_TS) + tkw->ts_shift = ts; - tkw->ts_shift = ts; tkw->si_min_ts_plus_one = 0; } @@ -7113,17 +7542,22 @@ if (!si->nb_sparse_ready) not_ready = GF_TRUE; } } - - if (not_ready) { + if (not_ready && !force_ready) { if (blocking_refs && has_ready) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux Blocking input packets present, aborting initial timing sync\n")); } //this may be quite long until we have a packet in case input pid is video encoding else if (ctx->config_retry_start && (gf_sys_clock() - ctx->config_retry_start > 10000)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux No input packets present on one or more inputs for more than 10s, aborting initial timing sync\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux PID(s) configuration unknown on one or more inputs after 10s, aborting initial timing sync\n")); + force_ready=GF_TRUE; + goto retry_all; } else { - ctx->config_retry_start = gf_sys_clock(); + if (!ctx->config_retry_start) + ctx->config_retry_start = gf_sys_clock(); del_service_info(services); + //not ready and we didn't drop any packet, postpone by 1ms + if (!has_drop) + gf_filter_ask_rt_reschedule(ctx->filter, 1000); return; } } @@ -7217,8 +7651,9 @@ if (tkw->nb_frames) { pc = (u32) ( (10000 * (u64) (tkw->nb_samples + tkw->frame_offset)) / tkw->nb_frames); } else { - if (tkw->pid_dur.num && tkw->pid_dur.den) { - pc = (u32) ((tkw->sample.DTS*10000 * tkw->pid_dur.den) / (tkw->pid_dur.num * tkw->tk_timescale)); + if (tkw->pid_dur.num && tkw->pid_dur.den && tkw->tk_timescale) { + if (tkw->pid_dur.num <= GF_INT64_MAX / tkw->tk_timescale ) + pc = (u32) ((tkw->sample.DTS*10000 * tkw->pid_dur.den) / (tkw->pid_dur.num * tkw->tk_timescale)); } else if (tkw->down_bytes && tkw->down_size) { pc = (u32) (((tkw->down_bytes*10000) / tkw->down_size)); } @@ -7288,11 +7723,17 @@ } //regular mode + Bool do_flush=GF_FALSE; +force_flush: nb_suspended = 0; for (i=0; i<count; i++) { GF_Err e; + ctx->track_removed = GF_FALSE; TrackWriter *tkw = gf_list_get(ctx->tracks, i); GF_FilterPacket *pck = gf_filter_pid_get_packet(tkw->ipid); + if (!pck && ctx->track_removed) { + return GF_OK; + } if (tkw->suspended) { nb_suspended++; @@ -7302,6 +7743,10 @@ if (!pck) { if (gf_filter_pid_is_eos(tkw->ipid) && !gf_filter_pid_is_flush_eos(tkw->ipid)) { tkw->suspended = GF_FALSE; + if (!do_flush) { + do_flush=GF_TRUE; + goto force_flush; + } nb_eos++; } if (tkw->aborted) { @@ -7346,7 +7791,7 @@ //basic regulation in case we do on-the-fly interleaving //we need to regulate because sources do not produce packets at the same rate - if (ctx->store==MP4MX_MODE_FASTSTART) { + if ((ctx->store==MP4MX_MODE_FASTSTART) && !do_flush) { u64 cts = gf_filter_pck_get_cts(pck); if (ctx->is_rewind) cts = tkw->ts_shift - cts; @@ -7427,8 +7872,8 @@ static void mp4_mux_flush_seg_events(GF_MP4MuxCtx *ctx) { - if (ctx->flush_ll_hls) { - mp4_mux_flush_frag_hls(ctx); + if (ctx->flush_llhas) { + mp4_mux_flush_frag_llhas(ctx); } if (!ctx->dash_mode || ctx->flush_seg) { @@ -7447,7 +7892,7 @@ ctx->seg_flush_state = 0; ctx->flush_idx_start_range = 0; ctx->flush_idx_end_range = 0; - ctx->flush_ll_hls = GF_FALSE; + ctx->flush_llhas = GF_FALSE; } static void mp4_mux_on_packet_destruct(GF_Filter *filter, GF_FilterPid *PID, GF_FilterPacket *pck) @@ -7567,6 +8012,9 @@ gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->dash_seg_num_plus_one-1) ); if (ctx->dash_seg_start.den) gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_MPD_SEGSTART, &PROP_FRAC64(ctx->dash_seg_start) ); + + if (ctx->llhas_template) + gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_LLHAS_TEMPLATE, &PROP_STRING(ctx->llhas_template) ); } if (ctx->min_cts_plus_one) { @@ -7583,9 +8031,13 @@ gf_filter_pck_set_duration(ctx->dst_pck, 1); } - if ((ctx->llhls_mode>1) && ctx->fragment_started && !ctx->frag_size && ctx->dst_pck) { + if ((ctx->llhas_mode>GF_LLHAS_BYTERANGES) && ctx->fragment_started && !ctx->frag_size && ctx->dst_pck) { + u32 fnum = ctx->frag_num; + //we'll need to redo all LLHLS tests + if (gf_sys_is_test_mode() && (ctx->llhas_mode == GF_LLHAS_PARTS)) + fnum++; + gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_LLHAS_FRAG_NUM, &PROP_UINT(fnum)); ctx->frag_num++; - gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_HLS_FRAG_NUM, &PROP_UINT(ctx->frag_num)); } ctx->frag_size += block_size; @@ -7611,6 +8063,8 @@ GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); gf_filter_set_max_extra_input_pids(filter, -1); ctx->filter = filter; + time_t current_time =time(NULL); + ctx->id3_id_sequence=(long)current_time; #ifdef GPAC_DISABLE_ISOM_FRAGMENTS if (ctx->store>=MP4MX_MODE_FRAG) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MP4Mux Cannot use fragmented mode, disabled in build\n")); @@ -7705,6 +8159,9 @@ gf_isom_enable_compression(ctx->file, ctx->compress, flags); } + if (ctx->store < MP4MX_MODE_FRAG) + ctx->prft = PRFT_OFF; + if ((ctx->store>=MP4MX_MODE_FRAG) && !ctx->tsalign) ctx->insert_tfdt = GF_TRUE; @@ -7810,6 +8267,10 @@ //set linf for (i=0; i < gf_isom_get_track_count(ctx->file); i++) { u32 subtype = gf_isom_get_media_subtype(ctx->file, i+1, 1); + if ( gf_isom_is_media_encrypted(ctx->file, i+1, 1)) + gf_isom_get_original_format_type(ctx->file, i+1, i+1, &subtype); + + switch (subtype) { case GF_ISOM_SUBTYPE_AVC_H264: case GF_ISOM_SUBTYPE_AVC2_H264: @@ -8117,7 +8578,8 @@ GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); if (ctx->owns_mov && (ctx->file || (ctx->store>=MP4MX_MODE_FRAG))) { - if (ctx->store < MP4MX_MODE_FRAG) { + //if PLAY was seen and not in fragmented mode, throw warning + if (ctx->force_play && (ctx->store < MP4MX_MODE_FRAG)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MP4Mux Session aborted before writing to file, use fragmented storage mode to record session\n")); } gf_isom_delete(ctx->file); @@ -8136,11 +8598,11 @@ gf_list_del(ctx->ref_pcks); if (ctx->bs_r) gf_bs_del(ctx->bs_r); if (ctx->seg_name) gf_free(ctx->seg_name); + if (ctx->llhas_template) gf_free(ctx->llhas_template); if (ctx->tmp_store) gf_fclose(ctx->tmp_store); if (ctx->seg_sizes) gf_free(ctx->seg_sizes); if (ctx->cur_file_suffix) gf_free(ctx->cur_file_suffix); - } static const GF_FilterCapability MP4MuxCaps = @@ -8171,9 +8633,10 @@ { OFFS(m4sys), "force MPEG-4 Systems signaling of tracks", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(dref), "only reference data from source file - not compatible with all media sources", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(ctmode), "set composition offset mode for video tracks\n" + "- auto: if fragmenting an ISOBMFF source, use source settings otherwise resolve to `edit`\n" "- edit: uses edit lists to shift first frame to presentation time 0\n" "- noedit: ignore edit lists and does not shift timeline\n" - "- negctts: uses ctts v1 with possibly negative offsets and no edit lists", GF_PROP_UINT, "edit", "edit|noedit|negctts", GF_FS_ARG_HINT_ADVANCED}, + "- negctts: uses ctts v1 with possibly negative offsets and no edit lists", GF_PROP_UINT, "auto", "auto|edit|noedit|negctts", GF_FS_ARG_HINT_ADVANCED}, { OFFS(dur), "only import the specified duration. If negative, specify the number of coded frames to import", GF_PROP_FRACTION, "0", NULL, 0}, { OFFS(pack3gp), "pack a given number of 3GPP audio frames in one sample", GF_PROP_UINT, "1", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(importer), "compatibility with old importer, displays import progress", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -8211,9 +8674,13 @@ { OFFS(msninc), "sequence number increase between `moof` boxes", GF_PROP_UINT, "1", NULL, 0}, { OFFS(tfdt), "set initial decode time (`tfdt`) of first traf", GF_PROP_FRACTION64, "0", NULL, 0}, { OFFS(tfdt_traf), "force `tfdt` box in each traf", GF_PROP_BOOL, "false", NULL, 0}, - { OFFS(nofragdef), "disable default flags in fragments", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(nofragdef), "disable default fragment flags in initial `moov`", GF_PROP_BOOL, "false", NULL, 0}, { OFFS(straf), "use a single traf per moof (smooth streaming and co)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(strun), "use a single trun per traf (smooth streaming and co)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(prft), "set `prft` box mode, disabled if not fragmented mode\n" + "- off: disable `prft` box\n" + "- sender: put ntp time before encoder\n" + "- both: put sender time (if available) and ntp time when writing the moof", GF_PROP_UINT, "sender", "off|sender|both", GF_FS_ARG_HINT_ADVANCED}, { OFFS(psshs), "set `pssh` boxes store mode\n" "- moof: in first moof of each segments\n" "- moov: in movie box\n" @@ -8224,7 +8691,7 @@ "- on: use temp storage of complete file for sidx and ssix injection\n" "- insert: insert sidx and ssix by shifting bytes in output file\n" "- replace: precompute pace requirements for sidx and ssix and rewrite file range at end", GF_PROP_UINT, "replace", "on|insert|replace", 0}, - { OFFS(noinit), "do not produce initial `moov, used for DASH bitstream switching mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(noinit), "do not produce initial `moov`, used for DASH bitstream switching mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(tktpl), "use track box from input if any as a template to create new track\n" "- no: disables template\n" "- yes: clones the track (except edits and decoder config)\n" @@ -8242,14 +8709,16 @@ { OFFS(fragdur), "fragment based on fragment duration rather than CTS. Mostly used for `MP4Box -frag` option", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(btrt), "set `btrt` box in sample description", GF_PROP_BOOL, "true", NULL, 0}, { OFFS(styp), "set segment `styp` major brand (and optionally version) to the given 4CC.version", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(lmsg), "set `lmsg` brand for the last segment or fragment", GF_PROP_BOOL, "false", NULL, 0}, { OFFS(mediats), "set media timescale. A value of 0 means inherit from PID, a value of -1 means derive from samplerate or frame rate", GF_PROP_SINT, "0", NULL, 0}, { OFFS(ase), "set audio sample entry mode for more than stereo layouts\n" - "- v0: use v0 signaling but channel count from stream, recommended for backward compatibility\n" + "- v0: use v0 signaling with channel count from stream (except for (e)AC3), recommended for backward compatibility\n" "- v0s: use v0 signaling and force channel count to 2 (stereo) if more than 2 channels\n" + "- v0bs: use v0 signaling from bitstream only\n" "- v1: use v1 signaling, ISOBMFF style (will mux raw PCM as ISOBMFF style)\n" "- v1qt: use v1 signaling, QTFF style\n" "- v2qt: use v2 signaling, QTFF style (lpcm entry type)" - , GF_PROP_UINT, "v0", "|v0|v0s|v1|v1qt|v2qt", 0}, + , GF_PROP_UINT, "v0", "|v0|v0s|v0bs|v1|v1qt|v2qt", 0}, { OFFS(ssix), "create `ssix` box when `sidx` box is present, level 1 mapping I-frames byte ranges, level 0xFF mapping the rest", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(ccst), "insert coding constraint box for video tracks", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(maxchunk), "set max chunk size in bytes for runs (only used in non-fragmented mode). 0 means no constraints", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -8299,7 +8768,7 @@ { OFFS(pad_sparse), "inject sample with no data (size 0) to keep durations in unknown sparse text and metadata tracks", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(force_dv), "force DV sample entry types even when AVC/HEVC compatibility is signaled", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(dvsingle), "ignore DolbyVision profile 8 in xps inband mode if profile 5 is already set", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, - { OFFS(tsalign), "enable timeline realignment to 0 for first sample - if false, this will keep original timing with empty edit (possibly long) at begin)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tsalign), "enable timeline realignment to 0 for first sample - if false, this will keep original timing with empty edit (possibly long) at begin", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(chapm), "chapter storage mode\n" "- off: disable chapters\n" "- tk: use chapter track (QT-style)\n" @@ -8313,6 +8782,7 @@ "- prof: enabled and write profile if known\n" "- tiny: enabled and write reduced version if profile known and compatible", GF_PROP_UINT, "prof", "off|gen|prof|tiny", GF_FS_ARG_HINT_EXPERT}, { OFFS(trunv1), "force using version 1 of trun regardless of media type or CMAF brand", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(rsot), "inject redundant sample timing information when present", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -8354,11 +8824,12 @@ "When tagging is enabled, the filter will watch the property `CoverArt` and all custom properties on incoming PID.\n" "The built-in tag names are indicated by `MP4Box -h tags`.\n" "QT tags can be specified using `qtt_NAME` property names, and will be added using formatting specified in `MP4Box -h tags`.\n" - "Other tag class may be specified using `tag_NAME` property names, and will be added if -tags() is set to `all` using:\n" + "Other tag class may be specified using `tag_NAME` property names, and will be added if -itags() is set to `all` using:\n" "- `NAME` as a box 4CC if `NAME` is four characters long\n" "- `NAME` as a box 4CC if `NAME` is 3 characters long, and will be prefixed by 0xA9\n" "- the CRC32 of the `NAME` as a box 4CC if `NAME` is not four characters long\n" " \n" + "Property names formatted as `cust_NAME@MEAN` are added as a custom tag with name `NAME` and mean `MEAN`. Both `NAME` and `MEAN` can be empty.\n" "# User data\n" "The filter will look for the following PID properties to create user data entries:\n" "- `udtab`: set the track user-data box to the property value which __must__ be a serialized box array blob\n" @@ -8378,7 +8849,7 @@ "- `sai_A4CC_param`: same as above and sets `aux_info_type_parameter`to `param`\n" " \n" "The property `grp_EMSG` consists in one or more `EventMessageBox` as defined in MPEG-DASH.\n" - "- in fragmented mode, presence of these boxes in a packet will start a new fragment, with the boxes written before the `moof`\n" + "- in fragmented mode, presence of this property in a packet will start a new fragment, with the boxes written before the `moof`\n" "- in regular mode, an internal sample group of type `EMSG` is currently used for `emsg` box storage\n" " \n" "# Notes\n" @@ -8394,7 +8865,7 @@ "- if `Sparse` is true, empty packet is inserted for all stream types\n" "- if `Sparse` is false, empty packet is never injected\n" " \n" - "The default media type used for a PID can be overriden using property `StreamSubtype`. \n" + "The default media type used for a PID can be overridden using property `StreamSubtype`. \n" "EX -i src.srt:#StreamSubtype=sbtl -i ... -o test.mp4 \n" "This will force the text stream to use `sbtl` handler type instead of default `text` one." "\n" @@ -8411,7 +8882,8 @@ SETCAPS(MP4MuxCaps), .configure_pid = mp4_mux_configure_pid, .process = mp4_mux_process, - .process_event = mp4_mux_process_event + .process_event = mp4_mux_process_event, + .hint_class_type = GF_FS_CLASS_MULTIPLEXER };
View file
gpac-2.4.0.tar.gz/src/filters/mux_ogg.c -> gpac-26.02.0.tar.gz/src/filters/mux_ogg.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022-2023 + * Copyright (c) Telecom ParisTech 2022-2024 * All rights reserved * * This file is part of GPAC / OGG muxer filter @@ -69,6 +69,7 @@ u32 seg_num, next_seg_num; Bool wait_dash, copy_props; u64 seg_start, seg_size; + Bool cdur_overwrite; Bool force_seg_sync; u32 packets_pending; @@ -241,6 +242,14 @@ if (!ctx->opid) { ctx->opid = gf_filter_pid_new(filter); + + if (!ctx->cdur_overwrite) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_FDUR); + if (p && p->value.frac.den) { + ctx->cdur = p->value.frac; + ctx->cdur_overwrite = GF_TRUE; + } + } } gf_filter_pid_copy_properties(ctx->opid, pid); gf_filter_pid_set_name(ctx->opid, "ogg_mux"); @@ -393,7 +402,7 @@ pctx->op.packetno ++; if (pctx->theora_kgs) { - Bool is_sap = (data0 & 0x40) ? 0 : 1; + Bool is_sap = (data0 & 0x40) ? 0 : 1; if (is_sap) { pctx->nb_i += pctx->nb_p; pctx->nb_p = 0; @@ -558,6 +567,7 @@ .configure_pid = oggmux_configure_pid, .process = oggmux_process, .process_event = oggmux_process_event, + .hint_class_type = GF_FS_CLASS_MULTIPLEXER }; #endif
View file
gpac-2.4.0.tar.gz/src/filters/mux_ts.c -> gpac-26.02.0.tar.gz/src/filters/mux_ts.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / MPEG-2 TS mux filter @@ -80,6 +80,11 @@ TEMI_TC64_ALWAYS, }; +GF_OPT_ENUM (GF_TSMuxInputDescriptorAction, + IN_TEMI_DROP=0, + IN_TEMI_FWD, + IN_TEMI_NTP, +); typedef struct { @@ -105,6 +110,7 @@ s32 subs_sidx; Bool keepts; GF_Fraction cdur; + GF_TSMuxInputDescriptorAction temi_fwd; //internal GF_FilterPid *opid; @@ -121,6 +127,12 @@ Bool init_buffering; u32 last_log_time; Bool pmt_update_pending; + Bool cdur_overwrite; + + //SCTE-35 data is conveyed by packet properties + GF_M2TS_Mux_Stream *scte35_stream; + u8 *scte35_payload; + u32 scte35_size; u32 dash_mode; Bool init_dash; @@ -133,6 +145,8 @@ char dash_file_nameGF_MAX_PATH; char idx_file_nameGF_MAX_PATH; + char llhas_templateGF_MAX_PATH; + //dash indexing u32 nb_sidx_entries, nb_sidx_alloc; @@ -149,10 +163,10 @@ Double start_range; struct __tsmx_pid *ref_pid; - Bool wait_llhls_flush; + Bool wait_llhas_flush; - u32 llhls; - Bool next_is_llhls_start; + u32 llhas_mode; + Bool next_is_llhas_start; u32 frag_num; u32 frag_offset, frag_size, frag_duration; @@ -170,9 +184,11 @@ u32 id, delay, timescale; u64 offset, init_val; char *url; - Bool ntp, use_init_val; + u32 ntp_mode; + Bool use_init_val; u32 mode_64bits; u64 cts_at_init_val_plus_one; + u64 ntp_init_ts, ntp_init_cts; } TEMIDesc; enum @@ -182,8 +198,8 @@ M2TS_EODS_FOUND, //forced end of DASH segment (found empty packet with EODS set) M2TS_EODS_FORCED, - //end of LL-HLS part - M2TS_EODS_LLHLS, + //end of LLHAS part + M2TS_EODS_LLHAS, }; typedef struct __tsmx_pid @@ -224,18 +240,18 @@ u8 *pck_data_buf; u32 suspended; - u64 llhls_dts_init; + u64 llhas_dts_init; Bool is_sparse; } M2Pid; -static GF_Err tsmux_format_af_descriptor(GF_BitStream *bs, u32 timeline_id, u64 timecode, u32 timescale, u32 mode_64bits, u64 ntp, const char *temi_url, u32 temi_delay, u32 *last_url_time) +static GF_Err tsmux_format_af_descriptor(GF_BitStream *bs, Bool is_realtime, u32 timeline_id, u64 timecode, u32 timescale, u32 mode_64bits, u64 ntp, const char *temi_url, u32 temi_delay, u32 *last_url_time, Bool is_reload, Bool is_splicing, GF_Fraction *announce, Bool is_paused, Bool is_discontinuity) { u32 len; - u32 last_time; - if (ntp) { + u32 last_time=0; + if (ntp && is_realtime) { last_time = 1000*(ntp>>32); last_time += 1000*(ntp&0xFFFFFFFF)/0xFFFFFFFF; - } else { + } else if (timescale) { last_time = (u32) (1000*timecode/timescale); } if (temi_url && (!*last_url_time || (last_time - *last_url_time + 1 >= temi_delay)) ) { @@ -246,12 +262,16 @@ gf_bs_write_int(bs, GF_M2TS_AFDESC_LOCATION_DESCRIPTOR, 8); gf_bs_write_int(bs, len, 8); - gf_bs_write_int(bs, 0, 1); //force_reload - gf_bs_write_int(bs, 0, 1); //is_announcement - gf_bs_write_int(bs, 0, 1); //splicing_flag - gf_bs_write_int(bs, 0, 1); //use_base_temi_url + gf_bs_write_int(bs, is_reload, 1); //force_reload + gf_bs_write_int(bs, announce ? 1 : 0, 1); //is_announcement + gf_bs_write_int(bs, is_splicing, 1); //splicing_flag + gf_bs_write_int(bs, temi_url0 ? 0 : 1, 1); //use_base_temi_url gf_bs_write_int(bs, 0xFF, 5); //reserved gf_bs_write_int(bs, timeline_id, 7); //timeline_id + if (announce) { + gf_bs_write_u32(bs, announce->den); + gf_bs_write_u32(bs, announce->num); + } if (strlen(temi_url)) { char *url = (char *)temi_url; @@ -266,8 +286,8 @@ } gf_bs_write_u8(bs, (u32) strlen(url)); //url_path_len gf_bs_write_data(bs, url, (u32) strlen(url) ); //url - gf_bs_write_u8(bs, 0); //nb_addons } + gf_bs_write_u8(bs, 0); //nb_addons //rewrite len end = gf_bs_get_position(bs); len = (u32) (end - start - 2); @@ -287,7 +307,6 @@ while (timecode > 0xFFFFFFFFUL) { timecode -= 0xFFFFFFFFUL; } - } len = 3; //3 bytes flags @@ -303,9 +322,9 @@ gf_bs_write_int(bs, ntp ? 1 : 0, 1); //has_ntp gf_bs_write_int(bs, 0, 1); //has_ptp gf_bs_write_int(bs, 0, 2); //has_timecode - gf_bs_write_int(bs, 0, 1); //force_reload - gf_bs_write_int(bs, 0, 1); //paused - gf_bs_write_int(bs, 0, 1); //discontinuity + gf_bs_write_int(bs, is_reload, 1); //force_reload + gf_bs_write_int(bs, is_paused, 1); //paused + gf_bs_write_int(bs, is_discontinuity, 1); //discontinuity gf_bs_write_int(bs, 0xFF, 7); //reserved gf_bs_write_int(bs, timeline_id, 8); //timeline_id if (timescale) { @@ -397,6 +416,27 @@ } } +static u32 tsmux_stream_process_scte35(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) +{ + if (stream->table_needs_update) { /* generate table payload */ + GF_TSMuxCtx *ctx = ((M2Pid*)stream->ifce->input_udta)->ctx; + gf_m2ts_mux_table_update(stream, GF_M2TS_TABLE_ID_SCTE35_SPLICE_INFO, stream->program->number, + ctx->scte35_payload+3, ctx->scte35_size-3, // remove redundancy since payload already contains an entire SCTE 35 splice_info_section + GF_FALSE, GF_FALSE); + ctx->scte35_size = 0; + gf_free(ctx->scte35_payload); + ctx->scte35_payload = NULL; + + stream->table_needs_update = GF_FALSE; + stream->table_needs_send = GF_TRUE; + } + if (stream->table_needs_send) + return 1; + if (stream->refresh_rate_ms) + return 1; + return 0; +} + static GF_Err tsmux_esi_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) { u32 cversion; @@ -453,6 +493,7 @@ tspid->ctx->wait_dash_flush = GF_TRUE; tspid->ctx->dash_seg_num = p->value.uint; tspid->ctx->dash_file_name0 = 0; + tspid->ctx->llhas_template0 = 0; tsmux_check_mpd_start_time(tspid->ctx, pck); } @@ -467,6 +508,10 @@ strcpy(tspid->ctx->dash_file_name, p->value.string); tspid->ctx->dash_file_switch = GF_TRUE; } + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE); + if (p) { + strcpy(tspid->ctx->llhas_template, p->value.string); + } return GF_OK; } else if (tspid->ctx->last_is_eods_flush) { @@ -474,6 +519,9 @@ if (p2) { strcpy(tspid->ctx->dash_file_name, p2->value.string); tspid->ctx->next_is_start = GF_TRUE; + p2 = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE); + if (p2) + strcpy(tspid->ctx->llhas_template, p2->value.string); } } } @@ -525,20 +573,20 @@ if (tspid->ctx->ref_pid) { dts = gf_filter_pck_get_dts(pck); - if (!tspid->llhls_dts_init) { - tspid->llhls_dts_init = dts+1; + if (!tspid->llhas_dts_init) { + tspid->llhas_dts_init = dts+1; if (tspid->ctx->ref_pid == tspid) { if (gf_filter_pck_get_sap(pck)) tspid->ctx->frag_has_intra = GF_TRUE; } } else { - u64 dur = dts - tspid->llhls_dts_init + 1; + u64 dur = dts - tspid->llhas_dts_init + 1; if (gf_timestamp_greater_or_equal(dur, ifce->timescale, tspid->ctx->cdur.num, tspid->ctx->cdur.den)) { if (tspid->ctx->ref_pid == tspid) { - tspid->ctx->wait_llhls_flush = GF_TRUE; + tspid->ctx->wait_llhas_flush = GF_TRUE; tspid->ctx->frag_duration = (u32) dur; } - tspid->has_seen_eods = M2TS_EODS_LLHLS; + tspid->has_seen_eods = M2TS_EODS_LLHAS; return GF_OK; } if (tspid->ctx->ref_pid == tspid) { @@ -578,12 +626,13 @@ es_pck.cts = 0; } + //prepare for adaptation field descriptors + if (!tspid->temi_af_bs) tspid->temi_af_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(tspid->temi_af_bs, tspid->af_data, tspid->af_data_alloc); + if (tspid->temi_descs) { u32 i, count=gf_list_count(tspid->temi_descs); - if (!tspid->temi_af_bs) tspid->temi_af_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - else gf_bs_reassign_buffer(tspid->temi_af_bs, tspid->af_data, tspid->af_data_alloc); - for (i=0; i<count; i++) { TEMIDesc *temi = gf_list_get(tspid->temi_descs, i); u64 ntp=0; @@ -620,20 +669,105 @@ tc += temi->offset; } - if (temi->ntp) { - u32 sec, frac; - gf_net_get_ntp(&sec, &frac); - ntp = sec; - ntp <<= 32; - ntp |= frac; + if (temi->ntp_mode==1) { + ntp = gf_net_get_ntp_ts(); } - tsmux_format_af_descriptor(tspid->temi_af_bs, temi->id, tc, timescale, temi->mode_64bits, ntp, temi->url, temi->delay, &tspid->last_temi_url); + else if (temi->ntp_mode==2) { + u64 pck_ts = 0; + if ((s64) cts + tspid->media_delay >= 0) + pck_ts = gf_timestamp_rescale(cts + tspid->media_delay, ifce->timescale, 1000000); + + if (!temi->ntp_init_ts) { + temi->ntp_init_ts = gf_net_get_ntp_ts(); + temi->ntp_init_cts = pck_ts; + } + + s64 diff_usec = pck_ts; + diff_usec -= temi->ntp_init_cts; + ntp = gf_net_ntp_add_usec(temi->ntp_init_ts, (s32) diff_usec); + } + tsmux_format_af_descriptor(tspid->temi_af_bs, tspid->ctx->realtime, temi->id, tc, timescale, temi->mode_64bits, ntp, temi->url, temi->delay, &tspid->last_temi_url, GF_FALSE, GF_FALSE, NULL, GF_FALSE, GF_FALSE); + } + } + + u32 pidx = 0; + //rewrite temi descriptors present in source + while (tspid->ctx->temi_fwd) { + u32 p4cc; + const char *pname; + const GF_PropertyValue *p = gf_filter_pck_enum_properties(pck, &pidx, &p4cc, &pname); + if (!p) break; + if (pname && !strncmp(pname, "temi_l", 6)) { + GF_BitStream *bs = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + while (1) { + u8 achar = gf_bs_read_u8(bs); + if (!achar) break; + } + GF_Fraction time; + u32 timeline_id = atoi(pname+7); + const char *temi_url = p->value.data.ptr; + u8 is_announce = gf_bs_read_int(bs, 1); + u8 is_splicing = gf_bs_read_int(bs, 1); + u8 is_reload = gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 5); + if (is_announce) { + time.den = gf_bs_read_u32(bs); + time.num = gf_bs_read_u32(bs); + } + Bool invalid = gf_bs_is_overflow(bs); + gf_bs_del(bs); + //rewrite + u32 last_t=0; + if (!invalid) + tsmux_format_af_descriptor(tspid->temi_af_bs, tspid->ctx->realtime, timeline_id, 0, 0, 0, 0, temi_url, 0, &last_t, is_splicing, is_reload, is_announce ? &time : NULL, GF_FALSE, GF_FALSE); + } + else if (pname && !strncmp(pname, "temi_t", 6)) { + u32 timeline_id = atoi(pname+7); + GF_BitStream *bs = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + + u32 timescale = gf_bs_read_u32(bs); + u64 timestamp = gf_bs_read_u64(bs); + /*u64 media_pts = */gf_bs_read_u64(bs); + u8 is_reload = gf_bs_read_int(bs, 1); + u8 is_paused = gf_bs_read_int(bs, 1); + u8 is_discontinuity = gf_bs_read_int(bs, 1); + u8 has_ntp = gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 4); + + u64 ntp = 0; + if (has_ntp) { + ntp = gf_bs_read_u64(bs); + if (tspid->ctx->temi_fwd==IN_TEMI_NTP) + ntp = gf_net_get_ntp_ts(); + } + Bool invalid = gf_bs_is_overflow(bs); + gf_bs_del(bs); + + u32 last_t=0; + if (!invalid) + tsmux_format_af_descriptor(tspid->temi_af_bs, tspid->ctx->realtime, timeline_id, timestamp, timescale, 0, ntp, NULL, 0, &last_t, GF_FALSE, is_reload, NULL, is_paused, is_discontinuity); } - gf_bs_get_content_no_truncate(tspid->temi_af_bs, &tspid->af_data, &es_pck.mpeg2_af_descriptors_size, &tspid->af_data_alloc); - es_pck.mpeg2_af_descriptors = tspid->af_data; } + + gf_bs_get_content_no_truncate(tspid->temi_af_bs, &tspid->af_data, &es_pck.mpeg2_af_descriptors_size, &tspid->af_data_alloc); + if (es_pck.mpeg2_af_descriptors_size) + es_pck.mpeg2_af_descriptors = tspid->af_data; + else + es_pck.mpeg2_af_descriptors = NULL; + es_pck.cts += tspid->max_media_skip + tspid->media_delay; + p = gf_filter_pck_get_property_str(pck, "scte35"); + if (p) { + gf_assert(tspid->ctx->scte35_stream); + gf_assert(!tspid->ctx->scte35_payload); + tspid->ctx->scte35_payload = gf_malloc(p->value.data.size); + memcpy(tspid->ctx->scte35_payload, p->value.data.ptr, p->value.data.size); + tspid->ctx->scte35_size = p->value.data.size; + tspid->ctx->scte35_stream->table_needs_update = GF_TRUE; + tspid->ctx->scte35_stream->table_needs_send = GF_TRUE; + } + if (tspid->nb_repeat_last) { es_pck.cts += tspid->nb_repeat_last * ifce->timescale * tspid->ctx->repeat_img / 1000; } @@ -656,11 +790,10 @@ //we don't have reliable dts - double the diff should make sure we don't try to adjust too often diff = cts_diff = 2*(es_pck.dts - es_pck.cts); diff = gf_timestamp_rescale(diff, tspid->esi.timescale, 1000000); - gf_assert(tspid->prog->cts_offset <= diff); - tspid->prog->cts_offset += (u32) diff; - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSMux Packet CTS "LLU" is less than packet DTS "LLU", adjusting all CTS by %d / %d!\n", es_pck.cts, es_pck.dts, cts_diff, tspid->esi.timescale)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("M2TSMux Packet CTS "LLU" is less than packet DTS "LLU", adjusting all CTS by %d / %d (prev offset "LLU" us)\n", es_pck.cts, es_pck.dts, cts_diff, tspid->esi.timescale, tspid->prog->cts_offset)); + tspid->prog->cts_offset += (u32) diff; es_pck.cts += cts_diff; } if (tspid->esi.stream_type!=GF_STREAM_VISUAL) { @@ -940,7 +1073,12 @@ p = gf_filter_pid_get_property_str(tspid->ipid, "M2TSRA"); if (p) { if ((p->type==GF_PROP_UINT) || (p->type==GF_PROP_4CC)) m2ts_ra = p->value.uint; - else if (p->type==GF_PROP_STRING) m2ts_ra = gf_4cc_parse(p->value.string); + else if (p->type==GF_PROP_STRING) { + if (!stricmp(p->value.string, "srt")) + m2ts_ra = GF_M2TS_RA_STREAM_SRT; + else + m2ts_ra = gf_4cc_parse(p->value.string); + } } if (m2ts_ra != tspid->esi.ra_code) { changed = GF_TRUE; @@ -977,7 +1115,7 @@ u32 temi_delay=1000; u64 temi_offset=0; u32 temi_timescale=0; - Bool temi_ntp=GF_FALSE; + u32 temi_ntp=0; u32 temi_64bits=TEMI_TC64_AUTO; Bool temi_use_init_val=GF_FALSE; u64 temi_init_val = 0; @@ -1001,7 +1139,10 @@ service_id = atoi(temi_cfg+2); break; case 'N': - temi_ntp = GF_TRUE; + temi_ntp = 1; + break; + case 'n': + temi_ntp = 2; break; case 'D': temi_delay = atoi(temi_cfg+2); @@ -1064,7 +1205,7 @@ temi->url = gf_strdup(temi_cfg); temi->id = temi_id+1; } - temi->ntp = temi_ntp; + temi->ntp_mode = temi_ntp; temi->offset = temi_offset; temi->delay = temi_delay; temi->timescale = temi_timescale; @@ -1133,16 +1274,19 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); service_id = p ? p->value.uint : ctx->sid; - sname = ctx->name; - pname = ctx->provider; - p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_NAME); - if (p) sname = p->value.string; - p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_PROVIDER); - if (p) pname = p->value.string; - if (!ctx->opid) { ctx->opid = gf_filter_pid_new(filter); gf_filter_pid_set_name(ctx->opid, "ts_mux"); + //we are muxing, prevent m2tsdmx from trying to probe the file + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILEPATH, NULL); + + if (!ctx->cdur_overwrite) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_FDUR); + if (p && p->value.frac.den) { + ctx->cdur = p->value.frac; + ctx->cdur_overwrite = GF_TRUE; + } + } } //set output properties at init or reconfig gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); @@ -1153,7 +1297,6 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000)); gf_filter_pid_set_property(ctx->opid, GF_PROP_NO_TS_LOOP, &PROP_BOOL(GF_TRUE)); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DASH_MODE, NULL); - mux_assign_mime_file_ext(pid, ctx->opid, M2TS_FILE_EXTS, M2TS_MIMES, "ts"); p = gf_filter_pid_get_info(pid, GF_PROP_PID_DASH_MODE, &pe); @@ -1178,8 +1321,8 @@ ctx->force_seg_sync = GF_TRUE; } } - p = gf_filter_pid_get_info(pid, GF_PROP_PID_LLHLS, &pe); - ctx->llhls = p ? p->value.uint : 0; + p = gf_filter_pid_get_info(pid, GF_PROP_PID_LLHAS_MODE, &pe); + ctx->llhas_mode = p ? p->value.uint : GF_LLHAS_NONE; gf_filter_release_property(pe); @@ -1209,7 +1352,7 @@ } } - if (ctx->llhls && (!ctx->ref_pid || (streamtype == GF_STREAM_VISUAL)) ) { + if (ctx->llhas_mode && (!ctx->ref_pid || (streamtype == GF_STREAM_VISUAL)) ) { ctx->ref_pid = tspid; } @@ -1255,7 +1398,15 @@ prog = gf_m2ts_mux_program_add(ctx->mux, service_id, pmt_id, ctx->pmt_rate, pcr_offset, ctx->mpeg4, ctx->pmt_version, ctx->disc, first_pts_val); + pe = NULL; + sname = ctx->name; + pname = ctx->provider; + p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_NAME, &pe); + if (p) sname = p->value.string; + p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_PROVIDER, &pe); + if (p) pname = p->value.string; if (sname) gf_m2ts_mux_program_set_name(prog, sname, pname); + gf_filter_release_property(pe); p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_DASH_SPARSE); if (p && p->value.boolean) ctx->keepts = GF_TRUE; @@ -1264,6 +1415,51 @@ prog->force_first_pts = GF_FILTER_NO_TS; } + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_SCTE35_PID); + if (p && !tspid->ctx->scte35_stream) { + M2Pid *m2pid; + GF_SAFEALLOC(m2pid, M2Pid); + if (!m2pid) return GF_OUT_OF_MEM; + + m2pid->sid = service_id; // belongs to the same service + m2pid->ipid = pid; // attach to parent pid + m2pid->ctx = ctx; + m2pid->codec_id = GF_CODECID_SCTE35; + m2pid->is_sparse = GF_TRUE; + m2pid->prog = prog; + + m2pid->esi.stream_type = GF_STREAM_METADATA; + m2pid->esi.codecid = GF_CODECID_SCTE35; + m2pid->esi.timescale = 90000; + m2pid->esi.caps = GF_ESI_STREAM_WITHOUT_MPEG4_SYSTEMS|GF_ESI_STREAM_SPARSE; + //m2pid->esi.input_ctrl = tsmux_esi_ctrl; + m2pid->esi.input_udta = m2pid; + + gf_list_add(tspid->ctx->pids, m2pid); + + int scte35_pid = gf_m2ts_mux_program_get_pmt_pid(m2pid->prog); + scte35_pid += 1 + gf_m2ts_mux_program_get_stream_count(m2pid->prog); + m2pid->ctx->scte35_stream = gf_m2ts_program_stream_add(m2pid->prog, &m2pid->esi, scte35_pid, GF_FALSE, GF_FALSE, GF_FALSE); + + GF_M2TSDescriptor *scte35_desc; + GF_SAFEALLOC(scte35_desc, GF_M2TSDescriptor); + if (!scte35_desc) return GF_OUT_OF_MEM; + scte35_desc->tag = GF_M2TS_REGISTRATION_DESCRIPTOR; + scte35_desc->data = gf_strdup("CUEI"); + if (!scte35_desc->data) return GF_OUT_OF_MEM; + scte35_desc->data_len = 4; + gf_list_add(m2pid->prog->loop_descriptors, scte35_desc); + + m2pid->ctx->scte35_stream->process = tsmux_stream_process_scte35; + + m2pid->prog->pmt->table_needs_update = GF_TRUE; + m2pid->ctx->pmt_update_pending = GF_TRUE; + m2pid->ctx->update_mux = GF_TRUE; + } + if (codec_id == GF_CODECID_SCTE35) { + streamtype = GF_STREAM_METADATA; // necessary when importing from NHML + } + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("M2TSMux Setting up program ID %d - send rates: PSI %d ms PCR every %d ms max - PCR offset %d\n", service_id, ctx->pmt_rate, ctx->max_pcr, ctx->pcr_offset)); } if (tspid->esi.stream_type != streamtype) @@ -1587,7 +1783,7 @@ tsidx->offset = (ctx->nb_sidx_entries>1) ? 0 : ctx->nb_pck_first_sidx; } -static void tsmux_flush_frag_hls(GF_TSMuxCtx *ctx, Bool is_last) +static void tsmux_flush_frag_llhas(GF_TSMuxCtx *ctx, Bool is_last) { GF_FilterEvent evt; @@ -1653,6 +1849,9 @@ p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); if (p) strcpy(tspid->ctx->dash_file_name, p->value.string); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE); + if (p) + strcpy(tspid->ctx->llhas_template, p->value.string); p = gf_filter_pck_get_property(pck, GF_PROP_PCK_IDXFILENAME); if (p) strcpy(tspid->ctx->idx_file_name, p->value.string); @@ -1667,12 +1866,12 @@ } - if (ctx->wait_dash_flush || ctx->wait_llhls_flush) { + if (ctx->wait_dash_flush || ctx->wait_llhas_flush) { //we are waiting for all packets to be flushed if (ctx->pending_packets) return GF_OK; - Bool is_llhls_flush = GF_FALSE; + Bool is_llhas_flush = GF_FALSE; Bool is_eods_flush = GF_FALSE; u32 i, done=0, count = gf_list_count(ctx->pids); for (i=0; i<count; i++) { @@ -1685,26 +1884,26 @@ M2Pid *tspid = gf_list_get(ctx->pids, i); if (tspid->has_seen_eods==M2TS_EODS_FORCED) is_eods_flush = GF_TRUE; - else if (tspid->has_seen_eods==M2TS_EODS_LLHLS) { - is_llhls_flush = GF_TRUE; + else if (tspid->has_seen_eods==M2TS_EODS_LLHAS) { + is_llhas_flush = GF_TRUE; } tspid->has_seen_eods = 0; - tspid->llhls_dts_init = 0; + tspid->llhas_dts_init = 0; } ctx->wait_dash_flush = GF_FALSE; ctx->mux->force_pat = GF_TRUE; - if (is_llhls_flush) { - ctx->wait_llhls_flush = GF_FALSE; - ctx->next_is_llhls_start = GF_TRUE; + if (is_llhas_flush) { + ctx->wait_llhas_flush = GF_FALSE; + ctx->next_is_llhas_start = GF_TRUE; - tsmux_flush_frag_hls(ctx, GF_FALSE); + tsmux_flush_frag_llhas(ctx, GF_FALSE); } else { ctx->next_is_start = ctx->dash_file_switch; ctx->dash_file_switch = GF_FALSE; - if (ctx->llhls) { - tsmux_flush_frag_hls(ctx, GF_TRUE); + if (ctx->llhas_mode) { + tsmux_flush_frag_llhas(ctx, GF_TRUE); ctx->frag_offset = 0; } @@ -1776,20 +1975,31 @@ gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENAME, &PROP_STRING(ctx->dash_file_name) ) ; if (ctx->dash_seg_start.den) gf_filter_pck_set_property(pck, GF_PROP_PCK_MPD_SEGSTART, &PROP_FRAC64(ctx->dash_seg_start) ) ; + if (ctx->llhas_template0) + gf_filter_pck_set_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE, &PROP_STRING(ctx->llhas_template) ) ; ctx->dash_file_name0 = 0; + ctx->llhas_template0 = 0; ctx->next_is_start = GF_FALSE; - if (ctx->llhls>1) { - ctx->frag_num=1; - gf_filter_pck_set_property(pck, GF_PROP_PCK_HLS_FRAG_NUM, &PROP_UINT(ctx->frag_num)); + if (ctx->llhas_mode>GF_LLHAS_BYTERANGES) { + ctx->frag_num=0; + u32 fnum = ctx->frag_num; + //we'll need to redo all LLHLS tests + if (gf_sys_is_test_mode() && (ctx->llhas_mode == GF_LLHAS_PARTS)) + fnum++; + gf_filter_pck_set_property(pck, GF_PROP_PCK_LLHAS_FRAG_NUM, &PROP_UINT(fnum)); } } - else if (ctx->next_is_llhls_start) { - if (ctx->llhls>1) { + else if (ctx->next_is_llhas_start) { + if (ctx->llhas_mode>GF_LLHAS_BYTERANGES) { + u32 fnum = ctx->frag_num; ctx->frag_num++; - gf_filter_pck_set_property(pck, GF_PROP_PCK_HLS_FRAG_NUM, &PROP_UINT(ctx->frag_num)); + //we'll need to redo all LLHLS tests + if (gf_sys_is_test_mode() && (ctx->llhas_mode == GF_LLHAS_PARTS)) + fnum++; + gf_filter_pck_set_property(pck, GF_PROP_PCK_LLHAS_FRAG_NUM, &PROP_UINT(fnum)); } - ctx->next_is_llhls_start = GF_FALSE; + ctx->next_is_llhas_start = GF_FALSE; } pck_ts = gf_m2ts_get_ts_clock_90k(ctx->mux); @@ -1813,7 +2023,7 @@ ctx->nb_pck_in_file += nb_pck_in_pack; nb_pck_in_call += nb_pck_in_pack; nb_pck_in_pack = 0; - if (ctx->llhls) + if (ctx->llhas_mode) ctx->frag_size += osize; if (is_pack_flush) @@ -1826,7 +2036,7 @@ break; } - if (ctx->wait_dash_flush || ctx->wait_llhls_flush) { + if (ctx->wait_dash_flush || ctx->wait_llhas_flush) { u32 i, done=0, count = gf_list_count(ctx->pids); for (i=0; i<count; i++) { M2Pid *tspid = gf_list_get(ctx->pids, i); @@ -1906,7 +2116,7 @@ gf_filter_ask_rt_reschedule(filter, 0); } //PMT update management is still under progress... - ctx->pmt_update_pending = 0; + ctx->pmt_update_pending = GF_FALSE; return GF_OK; } @@ -1919,11 +2129,18 @@ ctx->mux = gf_m2ts_mux_new(ctx->rate, ctx->pat_rate, ctx->realtime); ctx->mux->flush_pes_at_rap = ctx->flush_rap; - if (gf_sys_is_test_mode() && ctx->pcr_init<0) + if (gf_sys_is_test_mode() && (ctx->pcr_init==-1)) ctx->pcr_init = 1000000; gf_m2ts_mux_use_single_au_pes_mode(ctx->mux, ctx->pes_pack); - if (ctx->pcr_init>=0) gf_m2ts_mux_set_initial_pcr(ctx->mux, (u64) ctx->pcr_init); + if (ctx->pcr_init != -1) { + u64 pcr_init; + if (ctx->pcr_init>=0) + pcr_init = (u64) ctx->pcr_init; + else + pcr_init = GF_M2TS_MAX_PCR + ctx->pcr_init; + gf_m2ts_mux_set_initial_pcr(ctx->mux, pcr_init); + } gf_m2ts_mux_set_pcr_max_interval(ctx->mux, ctx->max_pcr); gf_m2ts_mux_enable_pcr_only_packets(ctx->mux, ctx->pcr_only); @@ -2021,10 +2238,14 @@ //for MPEG-H audio MHAS we need to insert sync packets CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MHAS), + //for AC4 we need bitstream framing + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AC4), + //static output cap file extension CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_STRING(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_FILE_EXT, M2TS_FILE_EXTS), CAP_STRING(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_MIME, M2TS_MIMES), + CAP_UINT(GF_CAPS_OUTPUT_STATIC|GF_CAPFLAG_PRESENT|GF_CAPFLAG_OPTIONAL, GF_PROP_PID_TIMESCALE, 0), {0}, //for now don't accept files as input, although we could store them as items, to refine @@ -2044,7 +2265,9 @@ CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_SMPTE_VC1), //we don't accept MPEG-H audio without MHAS CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_MPHA), - //no RAW support for now$ + //we don't accept AC4 audio without framing + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AC4), + //no RAW support for now CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), {0}, @@ -2088,7 +2311,7 @@ "- copy: uses BIFS PES but removes timestamps in BIFS SL and only carries PES timestamps", GF_PROP_UINT, "off", "off|on|copy", GF_FS_ARG_HINT_EXPERT}, { OFFS(flush_rap), "force flushing mux program when RAP is found on video, and injects PAT and PMT before the next video PES begin", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(pcr_only), "enable PCR-only TS packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, - { OFFS(pcr_init), "set initial PCR value for the programs. A negative value means random value is picked", GF_PROP_LSINT, "-1", NULL, 0}, + { OFFS(pcr_init), "set initial PCR value for the programs. -1 means random value is picked, other negative value means offset to maximum PCR", GF_PROP_LSINT, "-1", NULL, 0}, { OFFS(sid), "set service ID for the program", GF_PROP_UINT, "0", NULL, 0}, { OFFS(name), "set service name for the program", GF_PROP_STRING, NULL, NULL, 0}, { OFFS(provider), "set service provider name for the program", GF_PROP_STRING, NULL, NULL, 0}, @@ -2099,7 +2322,11 @@ { OFFS(subs_sidx), "number of subsegments per sidx (negative value disables sidx)", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(keepts), "keep cts/dts untouched and adjust PCR accordingly, used to keep TS unmodified when dashing", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(cdur), "chunk duration for fragmentation modes", GF_PROP_FRACTION, "-1/1", NULL, GF_FS_ARG_HINT_HIDE}, - + { OFFS(temi_fwd), "input TEMI properties when remuwing\n" + "- drop: remove input descriptors\n" + "- fwd: forward input descriptors\n" + "- ntp: forward input descriptors after NTP rewriting" + , GF_PROP_UINT, "fwd", "drop|fwd|ntp", GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -2139,6 +2366,7 @@ " - `Y`: use 64 bit signaling only.\n" " - `N`: use 32 bit signaling only and wrap around timecode value.\n" "- N: insert NTP timestamp in TEMI timeline descriptor\n" + "- n: insert NTP timestamp using NTP for first packet than incrementing based on media timestamp (for non real-time)\n" "- ID_OR_URL: If number, indicate the TEMI ID to use for external timeline. Otherwise, give the URL to insert\n" " \n" "EX temi=\"url\"\n" @@ -2153,6 +2381,8 @@ "Inserts an external TEMI with ID 4 and timescale 30000, NTP injection and carousel of 500 ms in the video stream of all programs.\n" "\n" "Warning: multipliers (k,m,g) are not supported in TEMI options.\n" + "\n" + "When input TEMI properties are found, they can be removed using -temi_fwd(). When rewritten, any NTP information present is rewritten to the current NTP.\n" "# Adaptive Streaming\n" "In DASH and HLS mode:\n" "- the PCR is always initialized at 0, and -flush_rap() is automatically set.\n" @@ -2160,11 +2390,20 @@ "- `pes_pack=none` is forced since some demultiplexers have issues with non-aligned ADTS PES.\n" "\n" "The filter watches the property `FileNumber` on incoming packets to create new files, or new segments in DASH mode.\n" + "# Custom streams\n" "The filter will look for property `M2TSRA` set on the input stream.\n" "The value can either be a 4CC or a string, indicating the MP2G-2 TS Registration tag for unknown media types.\n" + "The value `SRT ` (alias: `srt`, `SRT`) will inject an SRT header with frame number increasing at each packet and start time 0.\n" + "EX gpac -i source.srt:#M2TSRA='SRT ' -o mux.ts\n" + "This will inject the content of the source SRT as a PES data stream, removing any markup.\n" + "EX gpac -i source.srt:stxtmod=sbtt:#M2TSRA='SRT ' -o mux.ts\n" + "This will inject the content of the source SRT as a PES data stream, keeping the markup.\n" "\n" "# Notes\n" "In LATM mux mode, the decoder configuration is inserted at the given -repeat_rate() or `CarouselRate` PID property if defined.\n" + "\n" + "By default text streams are embeded using HLS ID3 schemes, use `M2TSRA` property to use raw private PES.\n" + "WebVTT header and TX3G formatting are removed, only the text data is injected.\n" ) .private_size = sizeof(GF_TSMuxCtx), .args = TSMuxArgs, @@ -2175,6 +2414,7 @@ .configure_pid = tsmux_configure_pid, .process = tsmux_process, .process_event = tsmux_process_event, + .hint_class_type = GF_FS_CLASS_MULTIPLEXER };
View file
gpac-2.4.0.tar.gz/src/filters/out_audio.c -> gpac-26.02.0.tar.gz/src/filters/out_audio.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / audio output filter @@ -102,10 +102,11 @@ e = ctx->audio_out->Configure(ctx->audio_out, &sr, &nb_ch, &afmt, ch_cfg); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("AudioOut Failed to configure audio output: %s\n", gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("AudioOut Failed to configure audio output %u channels %u Hz: %s, defaulting to stereo @ 441000 Hz\n", nb_ch, sr, gf_error_to_string(e) )); afmt = GF_AUDIO_FMT_S16; sr = 44100; nb_ch = 2; + e = ctx->audio_out->Configure(ctx->audio_out, &sr, &nb_ch, &afmt, ch_cfg); } if (ctx->speed == FIX_ONE) ctx->speed_set = 1; @@ -178,6 +179,8 @@ { GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) p; + gf_filter_log_tag(ctx->filter, GF_FALSE); + ctx->audio_th_state = 1; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("AudioOut Entering audio thread ID %d\n", gf_th_id() )); @@ -194,6 +197,7 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("AudioOut Exiting audio thread\n")); ctx->audio_out->Shutdown(ctx->audio_out); ctx->audio_th_state = 3; + gf_filter_log_tag(ctx->filter, GF_TRUE); return 0; } #endif @@ -214,6 +218,8 @@ if (!ctx->pid || ctx->aborted) return 0; if (!ctx->speed) return 0; + gf_filter_log_tag(ctx->filter, GF_FALSE); + if (ctx->do_seek) { GF_FilterEvent evt; @@ -230,7 +236,8 @@ ctx->do_seek = GF_FALSE; ctx->pck_offset = 0; ctx->last_cts = 0; - return 0; + + goto err_empty; } @@ -250,20 +257,22 @@ //pid may be set to NULL if removed if (!ctx->pid || gf_filter_pid_is_eos(ctx->pid)) ctx->is_eos = GF_TRUE; - return 0; + + goto err_empty; } if (! gf_filter_pck_is_blocking_ref(pck) && !gf_filter_pid_has_seen_eos(ctx->pid) ) { - if ((dur < ctx->buffer * 1000) && !gf_filter_pid_is_eos(ctx->pid)) - return 0; + if ((dur < ctx->buffer * 1000) && !gf_filter_pid_is_eos(ctx->pid)) { + goto err_empty; + } gf_filter_pck_get_data(pck, &size); if (!size) { - gf_filter_pid_drop_packet(ctx->pid); - return 0; + goto err_empty; } //check the decoder output is full (avoids initial underrun) - if (gf_filter_pid_query_buffer_duration(ctx->pid, GF_TRUE)==0) - return 0; + if (gf_filter_pid_query_buffer_duration(ctx->pid, GF_TRUE)==0) { + goto err_empty; + } } ctx->buffer_done = GF_TRUE; if (ctx->rebuffer) { @@ -278,7 +287,7 @@ GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("AudioOut buffer %u less than min threshold %u, rebuffering\n", (u32) (dur/1000), ctx->rbuffer)); ctx->rebuffer = gf_sys_clock_high_res(); ctx->buffer_done = GF_FALSE; - return GF_OK; + goto err_empty; } #ifndef GPAC_DISABLE_LOG } else if (gf_log_tool_level_on(GF_LOG_MMIO, GF_LOG_DEBUG)) { @@ -304,10 +313,12 @@ } else if (!is_first_pck) { GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("AudioOut buffer underflow\n")); } + gf_filter_log_tag(ctx->filter, GF_TRUE); return done; } ctx->is_eos = GF_FALSE; if (ctx->needs_recfg) { + gf_filter_log_tag(ctx->filter, GF_TRUE); return done; } @@ -341,7 +352,7 @@ diff = gf_timestamp_rescale(diff, ctx->timescale, 1000000); if (now < ctx->last_clock + diff) { GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("AudioOut Frame too early by "LLU" us\n", ctx->last_clock + diff - now)); - return 0; + goto err_empty; } } } @@ -359,6 +370,7 @@ ctx->aborted = GF_TRUE; } + gf_filter_log_tag(ctx->filter, GF_TRUE); return done; } } @@ -405,14 +417,20 @@ is_first_pck = GF_FALSE; if (nb_copy + ctx->pck_offset < size) { ctx->pck_offset += nb_copy; + gf_filter_log_tag(ctx->filter, GF_TRUE); return done; } - ctx->last_cts += (size / ctx->bytes_per_sample) * ctx->sr; + ctx->last_cts += gf_timestamp_rescale(size / ctx->bytes_per_sample, ctx->sr, ctx->timescale); ctx->pck_offset = 0; } gf_filter_pid_drop_packet(ctx->pid); } + gf_filter_log_tag(ctx->filter, GF_TRUE); return done; + +err_empty: + gf_filter_log_tag(ctx->filter, GF_TRUE); + return 0; } void aout_set_priority(GF_AudioOutCtx *ctx, u32 prio) @@ -788,7 +806,7 @@ GF_FilterRegister AudioOutRegister = { .name = "aout", GF_FS_SET_DESCRIPTION("Audio output") - GF_FS_SET_HELP("This filter writes a single uncompressed audio input PID to a sound card or other audio output device.\n" + GF_FS_SET_HELP("This filter writes a single PCM (uncompressed) audio input PID to a sound card or other audio output device.\n" "\n" "The longer the audio buffering -bdur() is, the longer the audio latency will be (pause/resume). The quality of fast forward audio playback will also be degraded when using large audio buffers.\n" "\n" @@ -806,7 +824,8 @@ .configure_pid = aout_configure_pid, .process = aout_process, .process_event = aout_process_event, - .update_arg = aout_update_arg + .update_arg = aout_update_arg, + .hint_class_type = GF_FS_CLASS_MM_IO }; const GF_FilterRegister *aout_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/out_file.c -> gpac-26.02.0.tar.gz/src/filters/out_file.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / generic FILE output filter @@ -28,30 +28,30 @@ #include <gpac/constants.h> #include <gpac/xml.h> #include <gpac/network.h> +#include <gpac/mpd.h> #ifndef GPAC_DISABLE_FOUT -enum -{ +GF_OPT_ENUM (GF_FileOutConcatMode, FOUT_CAT_NONE = 0, FOUT_CAT_AUTO, - FOUT_CAT_ALL -}; + FOUT_CAT_ALL, +); -enum -{ +GF_OPT_ENUM (GF_FileOutOverwriteMode, FOUT_OW_YES = 0, FOUT_OW_NO, - FOUT_OW_ASK -}; + FOUT_OW_ASK, +); typedef struct { //options Double start, speed; char *dst, *mime, *ext; - Bool append, dynext, redund, noinitraw, force_null; - u32 cat, ow; + Bool append, dynext, redund, noinitraw, force_null, atomic; + GF_FileOutConcatMode cat; + GF_FileOutOverwriteMode ow; u32 mvbk; s32 max_cache_segs; @@ -65,6 +65,7 @@ GF_FilterCapability in_caps2; char szExt10; char szFileNameGF_MAX_PATH; + char *llhas_template; Bool patch_blocks; Bool is_null; @@ -76,13 +77,15 @@ FILE *hls_chunk; - u32 max_segs; + u32 max_segs, llhas_mode; GF_List *past_files; Bool gfio_pending; u64 last_file_size; Bool use_rel; + Bool use_move; + char *llhls_file_name; #ifdef GPAC_HAS_FD Bool no_fd; @@ -93,25 +96,38 @@ #ifdef WIN32 #include <io.h> #include <fcntl.h> - -#else //WIN32 - -#ifdef GPAC_HAS_FD -#include <unistd.h> -#include <sys/stat.h> -#include <fcntl.h> #endif -#endif +#define ATOMIC_SUFFIX ".gftmp" static void fileout_close_hls_chunk(GF_FileOutCtx *ctx, Bool final_flush) { if (!ctx->hls_chunk) return; gf_fclose(ctx->hls_chunk); ctx->hls_chunk = NULL; + if (!ctx->llhls_file_name) return; + + char szNameGF_MAX_PATH; + strcpy(szName, ctx->llhls_file_name); + strcat(szName, ATOMIC_SUFFIX); + gf_file_delete(ctx->llhls_file_name); + gf_file_move(szName, ctx->llhls_file_name); + gf_free(ctx->llhls_file_name); + ctx->llhls_file_name = NULL; } -static GF_Err fileout_open_close(GF_FileOutCtx *ctx, const char *filename, const char *ext, u32 file_idx, Bool explicit_overwrite, char *file_suffix) +static void fileout_check_close(GF_FileOutCtx *ctx) +{ + if (!ctx->use_move) return; + char szNameGF_MAX_PATH; + strcpy(szName, ctx->szFileName); + strcat(szName, ATOMIC_SUFFIX); + gf_file_delete(ctx->szFileName); + gf_file_move(szName, ctx->szFileName); + ctx->use_move = GF_FALSE; +} + +static GF_Err fileout_open_close(GF_FileOutCtx *ctx, const char *filename, const char *ext, u32 file_idx, Bool explicit_overwrite, char *file_suffix, Bool check_no_open) { if (!ctx->is_std) { #ifdef GPAC_HAS_FD @@ -126,6 +142,7 @@ gf_fclose(ctx->file); fileout_close_hls_chunk(ctx, GF_FALSE); } + fileout_check_close(ctx); } ctx->file = NULL; #ifdef GPAC_HAS_FD @@ -139,91 +156,113 @@ else if (!strcmp(filename, "stdout")) ctx->is_std = GF_TRUE; else ctx->is_std = GF_FALSE; + if (!strcmp(filename, "null") || !strcmp(filename, "/dev/null")) + ext = NULL; + if (ctx->is_std) { ctx->file = stdout; + ctx->nb_write = 0; #ifdef WIN32 _setmode(_fileno(stdout), _O_BINARY); #endif + return GF_OK; + } - } else { - char szFinalNameGF_MAX_PATH; - Bool append = ctx->append; - Bool is_gfio=GF_FALSE; - const char *url = filename; - - if (!strncmp(filename, "gfio://", 7)) { - url = gf_fileio_translate_url(filename); - is_gfio=GF_TRUE; - } + char szNameGF_MAX_PATH; + char szFinalNameGF_MAX_PATH; + Bool append = ctx->append; + Bool is_gfio=GF_FALSE; + const char *url = filename; - if (ctx->dynext) { - const char *has_ext = gf_file_ext_start(url); + if (!strncmp(filename, "gfio://", 7)) { + url = gf_fileio_translate_url(filename); + is_gfio=GF_TRUE; + } - strcpy(szFinalName, url); - if (!has_ext && ext) { - strcat(szFinalName, "."); - strcat(szFinalName, ext); - } - } else { - strcpy(szFinalName, url); + if (ctx->dynext) { + const char *has_ext = gf_file_ext_start(url); + + strcpy(szFinalName, url); + if (!has_ext && ext) { + strcat(szFinalName, "."); + strcat(szFinalName, ext); } + } else { + strcpy(szFinalName, url); + } - if (ctx->use_templates) { - GF_Err e; - char szNameGF_MAX_PATH; - gf_assert(ctx->dst); - if (!strcmp(filename, ctx->dst)) { - strcpy(szName, szFinalName); - e = gf_filter_pid_resolve_file_template(ctx->pid, szName, szFinalName, file_idx, file_suffix); - } else { - char szFileNameGF_MAX_PATH; - strcpy(szFileName, szFinalName); - strcpy(szName, ctx->dst); - e = gf_filter_pid_resolve_file_template_ex(ctx->pid, szName, szFinalName, file_idx, file_suffix, szFileName); - } - if (e) { - return ctx->error = e; - } + if (ctx->use_templates) { + GF_Err e; + gf_assert(ctx->dst); + if (!strcmp(filename, ctx->dst)) { + strcpy(szName, szFinalName); + e = gf_filter_pid_resolve_file_template(ctx->pid, szName, szFinalName, file_idx, file_suffix); + } else { + char szFileNameGF_MAX_PATH; + strcpy(szFileName, szFinalName); + strcpy(szName, ctx->dst); + e = gf_filter_pid_resolve_file_template_ex(ctx->pid, szName, szFinalName, file_idx, file_suffix, szFileName); + } + if (e) { + return ctx->error = e; } + } - if (!gf_file_exists(szFinalName)) append = GF_FALSE; + if (!gf_file_exists(szFinalName)) append = GF_FALSE; - if (!strcmp(szFinalName, ctx->szFileName) && (ctx->cat==FOUT_CAT_AUTO)) - append = GF_TRUE; + if (!strcmp(szFinalName, ctx->szFileName) && (ctx->cat==FOUT_CAT_AUTO)) + append = GF_TRUE; - if (!append && (ctx->ow!=FOUT_OW_YES) && gf_file_exists(szFinalName)) { - char szRes21; - s32 res; + if (!append && (ctx->ow!=FOUT_OW_YES) && gf_file_exists(szFinalName)) { + char szRes21; + s32 res; - if (ctx->ow==FOUT_OW_ASK) { - fprintf(stderr, "File %s already exists - override (y/n/a) ?:", szFinalName); - res = scanf("%20s", szRes); - if (!res || (szRes0 == 'n') || (szRes0 == 'N')) { - return ctx->error = GF_IO_ERR; - } - if ((szRes0 == 'a') || (szRes0 == 'A')) ctx->ow = GF_TRUE; - } else { + if (ctx->ow==FOUT_OW_ASK) { + fprintf(stderr, "File %s already exists - override (y/n/a) ?:", szFinalName); + res = scanf("%20s", szRes); + if (!res || (szRes0 == 'n') || (szRes0 == 'N')) { return ctx->error = GF_IO_ERR; } + if ((szRes0 == 'a') || (szRes0 == 'A')) ctx->ow = FOUT_OW_YES; + } else { + return ctx->error = GF_IO_ERR; } + } + + if (check_no_open && (ctx->llhas_mode==GF_LLHAS_SUBSEG)) { + strcpy(ctx->szFileName, szFinalName); + ctx->nb_write = 0; + return GF_OK; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("FileOut opening output file %s\n", szFinalName)); + + ctx->use_move = GF_FALSE; + if (ctx->atomic && !append && !is_gfio && (!ctx->original_url || strncmp(ctx->original_url, "gfio://", 7))) { + ctx->use_move = GF_TRUE; + } + strcpy(szName, szFinalName); + if (ctx->use_move) { + strcat(szName, ATOMIC_SUFFIX); + } - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("FileOut opening output file %s\n", szFinalName)); #ifdef GPAC_HAS_FD - if (!ctx->no_fd && !is_gfio && !append && !gf_opts_get_bool("core", "no-fd") - && (!ctx->original_url || strncmp(ctx->original_url, "gfio://", 7)) - ) { - //make sure output dir exists - gf_fopen(szFinalName, "mkdir"); - ctx->fd = open(szFinalName, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); - } else + if (!ctx->no_fd && !is_gfio && !append && !gf_opts_get_bool("core", "no-fd") + && (!ctx->original_url || strncmp(ctx->original_url, "gfio://", 7)) + ) { + //make sure output dir exists + gf_fopen(szFinalName, "mkdir"); + + ctx->fd = gf_fd_open(szName, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); + } else #endif - ctx->file = gf_fopen_ex(szFinalName, ctx->original_url, append ? "a+b" : "w+b", GF_FALSE); + ctx->file = gf_fopen_ex(szName, ctx->original_url, append ? "a+b" : "w+b", GF_FALSE); - if (!strcmp(szFinalName, ctx->szFileName) && !append && ctx->nb_write && !explicit_overwrite) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("FileOut re-opening in write mode output file %s, content overwrite (use `cat` option to enable append)\n", szFinalName)); - } - strcpy(ctx->szFileName, szFinalName); + if (!strcmp(szFinalName, ctx->szFileName) && !append && ctx->nb_write && !explicit_overwrite) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("FileOut re-opening in write mode output file %s, content overwrite (use `cat` option to enable append)\n", szFinalName)); } + strcpy(ctx->szFileName, szFinalName); + ctx->nb_write = 0; if (!ctx->file #ifdef GPAC_HAS_FD @@ -246,7 +285,7 @@ ext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); if (p && p->value.string) { - fileout_open_close(ctx, p->value.string, (ext && ctx->dynext) ? ext->value.string : NULL, 0, explicit_overwrite, NULL); + fileout_open_close(ctx, p->value.string, (ext && ctx->dynext) ? ext->value.string : NULL, 0, explicit_overwrite, NULL, GF_FALSE); return; } if (!dst) { @@ -266,19 +305,16 @@ } } if (ctx->dynext) { - p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PCK_FILENUM); - if (!p) { - if (ext && ext->value.string) { - fileout_open_close(ctx, dst, ext->value.string, 0, explicit_overwrite, NULL); - } + if (ext && ext->value.string) { + fileout_open_close(ctx, dst, ext->value.string, 0, explicit_overwrite, NULL, GF_FALSE); } } else if (ctx->dst) { - fileout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite, NULL); + fileout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite, NULL, GF_FALSE); } else { p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILEPATH); if (!p) p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_URL); if (p && p->value.string) - fileout_open_close(ctx, p->value.string, NULL, 0, explicit_overwrite, NULL); + fileout_open_close(ctx, p->value.string, NULL, 0, explicit_overwrite, NULL, GF_FALSE); } } static GF_Err fileout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) @@ -287,7 +323,7 @@ GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter); if (is_remove) { ctx->pid = NULL; - fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL, GF_FALSE); return GF_OK; } gf_filter_pid_check_caps(pid); @@ -320,6 +356,8 @@ if (ctx->max_segs && !ctx->past_files) ctx->past_files = gf_list_new(); } + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_LLHAS_MODE); + ctx->llhas_mode = p ? p->value.uint : GF_LLHAS_NONE; #ifdef GPAC_HAS_FD //disable fd for mp2t since we only dispatch small blocks - todo check this for other streams ? @@ -424,7 +462,7 @@ fileout_close_hls_chunk(ctx, GF_TRUE); - fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL, GF_FALSE); if (ctx->gfio_ref) gf_fileio_open_url((GF_FileIO *)ctx->gfio_ref, NULL, "unref", &e); @@ -435,6 +473,8 @@ } gf_list_del(ctx->past_files); } + if (ctx->llhas_template) gf_free(ctx->llhas_template); + if (ctx->llhls_file_name) gf_free(ctx->llhls_file_name); } static GF_Err fileout_process(GF_Filter *filter) @@ -482,7 +522,7 @@ evt.seg_size.media_range_start = ctx->offset_at_seg_start; #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - evt.seg_size.media_range_end = lseek(ctx->fd, 0, SEEK_CUR); + evt.seg_size.media_range_end = lseek_64(ctx->fd, 0, SEEK_CUR); } else #endif if (ctx->file) { @@ -497,7 +537,7 @@ gf_filter_pid_send_event(ctx->pid, &evt); } } - fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL, GF_FALSE); return GF_EOS; } return GF_OK; @@ -568,7 +608,7 @@ evt.seg_size.media_range_start = ctx->offset_at_seg_start; #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - evt.seg_size.media_range_end = lseek(ctx->fd, 0, SEEK_CUR); + evt.seg_size.media_range_end = lseek_64(ctx->fd, 0, SEEK_CUR); } else #endif if (ctx->file) { @@ -608,8 +648,6 @@ } //filename change at packet start, open new file if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); - if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH); - if (!ext) ext = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_EXT); if (fname) name = fname->value.string; fsuf = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); @@ -621,13 +659,13 @@ Bool use_rel = GF_FALSE; if (ctx->dst) { use_rel = ctx->use_rel; - rel = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_REL); + rel = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILE_REL); if (rel && rel->value.boolean) use_rel = GF_TRUE; } if (use_rel) { name = gf_url_concatenate(ctx->dst, name); } - fileout_open_close(ctx, name, ext ? ext->value.string : NULL, fnum ? fnum->value.uint : 0, explicit_overwrite, fsuf ? fsuf->value.string : NULL); + fileout_open_close(ctx, name, ext ? ext->value.string : NULL, fnum ? fnum->value.uint : 0, explicit_overwrite, fsuf ? fsuf->value.string : NULL, GF_TRUE); if (use_rel) { gf_free((char*) name); @@ -647,6 +685,12 @@ ctx->gfio_pending = GF_TRUE; } + fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE); + if (fname) { + if (ctx->llhas_template) gf_free(ctx->llhas_template); + ctx->llhas_template = gf_strdup(fname->value.string); + } + if (ctx->max_segs) { while (gf_list_count(ctx->past_files)>ctx->max_segs) { char *url = gf_list_pop_front(ctx->past_files); @@ -659,15 +703,36 @@ } } } - - p = gf_filter_pck_get_property(pck, GF_PROP_PCK_HLS_FRAG_NUM); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_FRAG_NUM); if (p) { - char szHLSChunkGF_MAX_PATH+21; - snprintf(szHLSChunk, GF_MAX_PATH+20, "%s.%d", ctx->szFileName, p->value.uint); +#ifndef GPAC_DISABLE_MPD + char *llhas_chunkname = gf_mpd_resolve_subnumber(ctx->llhas_template, ctx->szFileName, p->value.uint); //for now we only use buffered IO for hls chunks, too small to really benefit from direct write - if (ctx->hls_chunk) gf_fclose(ctx->hls_chunk); - ctx->hls_chunk = gf_fopen_ex(szHLSChunk, ctx->original_url, "w+b", GF_FALSE); + fileout_close_hls_chunk(ctx, GF_FALSE); + + if (ctx->use_move) { + ctx->llhls_file_name = gf_strdup(llhas_chunkname); + gf_dynstrcat(&llhas_chunkname, ATOMIC_SUFFIX, NULL); + } + + //we need a gf_url_concatenate to register target gfio, so simulate one + //this works because file subparts are always in the same dir as segment file + if (ctx->original_url && !strncmp(ctx->original_url, "gfio://", 7)) { + const char *rad = strrchr(llhas_chunkname, '/'); + if (!rad) rad = strrchr(llhas_chunkname, '\\'); + if (!rad) rad = llhas_chunkname; + else rad += 1; + //no need to check return: if failure, following gf_fopen_ex will also fail + gf_fileio_open_url(gf_fileio_from_url(ctx->original_url), rad, "url", &e); + } + ctx->hls_chunk = gf_fopen_ex(llhas_chunkname, ctx->original_url, "w+b", GF_FALSE); + ctx->gfio_pending = GF_TRUE; + gf_free(llhas_chunkname); +#else + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; +#endif } check_gfio: @@ -688,15 +753,19 @@ } - pck_data = gf_filter_pck_get_data(pck, &pck_size); + Bool main_valid = 0; if (ctx->file #ifdef GPAC_HAS_FD || (ctx->fd>=0) #endif - ) { + ) + main_valid = GF_TRUE; + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (main_valid || ctx->hls_chunk) { GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck); if (pck_data) { - if (ctx->patch_blocks && gf_filter_pck_get_seek_flag(pck)) { + if (ctx->patch_blocks && gf_filter_pck_get_seek_flag(pck) && main_valid) { u64 bo = gf_filter_pck_get_byte_offset(pck); if (ctx->is_std) { GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("FileOut Cannot patch file, output is stdout\n")); @@ -713,8 +782,8 @@ #ifdef GPAC_HAS_FD if (ctx->fd>=0) { nb_write = (u32) write(ctx->fd, pck_data, pck_size); - cur_w = lseek(ctx->fd, 0, SEEK_CUR); - lseek(ctx->fd, pos, SEEK_SET); + cur_w = lseek_64(ctx->fd, 0, SEEK_CUR); + lseek_64(ctx->fd, pos, SEEK_SET); } else #endif { @@ -742,7 +811,7 @@ #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - lseek(ctx->fd, cur_r - move_bytes, SEEK_SET); + lseek_64(ctx->fd, cur_r - move_bytes, SEEK_SET); nb_write = (u32) read(ctx->fd, block, (size_t) move_bytes); } else #endif @@ -758,7 +827,7 @@ #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - lseek(ctx->fd, cur_w - move_bytes, SEEK_SET); + lseek_64(ctx->fd, cur_w - move_bytes, SEEK_SET); nb_write = (u32) write(ctx->fd, block, (size_t) move_bytes); } else #endif @@ -780,9 +849,9 @@ #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - lseek(ctx->fd, bo, SEEK_SET); + lseek_64(ctx->fd, bo, SEEK_SET); nb_write = (u32) write(ctx->fd, pck_data, pck_size); - lseek(ctx->fd, pos, SEEK_SET); + lseek_64(ctx->fd, pos, SEEK_SET); } else #endif { @@ -797,18 +866,21 @@ } } } else { + if (main_valid) { + #ifdef GPAC_HAS_FD - if (ctx->fd>=0) { - nb_write = (u32) write(ctx->fd, pck_data, pck_size); - } else + if (ctx->fd>=0) { + nb_write = (u32) write(ctx->fd, pck_data, pck_size); + } else #endif - nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file); + nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file); - if (nb_write!=pck_size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("FileOut Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); - e = GF_IO_ERR; + if (nb_write!=pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("FileOut Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); + e = GF_IO_ERR; + } + ctx->nb_write += nb_write; } - ctx->nb_write += nb_write; if (ctx->hls_chunk) { nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->hls_chunk); @@ -816,9 +888,11 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("FileOut Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); e = GF_IO_ERR; } + if (!main_valid) + ctx->nb_write += nb_write; } } - } else if (hwf) { + } else if (hwf && main_valid) { u32 w, h, stride, stride_uv, pf; u32 nb_planes, uv_height; p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH); @@ -864,6 +938,8 @@ } } } + } else if (!main_valid && pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("FileOut output file handle is not opened, discarding %d bytes\n", pck_size)); } else { GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("FileOut No data associated with packet, cannot write\n")); } @@ -875,12 +951,12 @@ if (ctx->dash_mode) { #ifdef GPAC_HAS_FD if (ctx->fd>=0) { - ctx->last_file_size = lseek(ctx->fd, 0, SEEK_CUR); + ctx->last_file_size = lseek_64(ctx->fd, 0, SEEK_CUR); } else #endif ctx->last_file_size = gf_ftell(ctx->file); } - fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL, GF_FALSE); } pck = gf_filter_pid_get_packet(ctx->pid); if (pck) @@ -894,6 +970,8 @@ return e; } +GF_Err gf_fileio_file_delete(const char *fileName, const char *parent_gfio); + static Bool fileout_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { if (evt->base.type==GF_FEVT_FILE_DELETE) { @@ -902,7 +980,15 @@ GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("FileOut null delete (file name was %s)\n", evt->file_del.url)); } else { GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("FileOut delete file %s\n", evt->file_del.url)); - if (ctx->use_rel) { + + char *gfio_sep = NULL; + if (!strncmp(evt->file_del.url, "gfio://", 7)) gfio_sep = strchr(evt->file_del.url, '@'); + if (gfio_sep) { + gfio_sep0 = 0; + gf_fileio_file_delete(gfio_sep+1, evt->file_del.url); + gfio_sep0 = '@'; + } + else if (ctx->use_rel) { char *fname = gf_url_concatenate(ctx->dst, evt->file_del.url); gf_file_delete(fname); gf_free(fname); @@ -955,6 +1041,7 @@ { OFFS(noinitraw), "do not produce initial segment", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_HIDE}, { OFFS(max_cache_segs), "maximum number of segments cached per HAS quality when recording live sessions (0 means no limit)", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(force_null), "force no output regardless of file name", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(atomic), "use atomic file write for non append modes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(use_rel), "packet filename use relative names (only set by dasher)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_HIDE}, {0} }; @@ -975,6 +1062,9 @@ "The output file name can use gpac templating mechanism, see `gpac -h doc`." "The filter watches the property `FileNumber` on incoming packets to create new files.\n" "\n" + "By default output files are created directly, which may lead to issues if concurrent programs attempt to access them.\n" + "By enabling -atomic(), files will be created in target destination folder with the `"ATOMIC_SUFFIX"` suffix and move to their final name upon close.\n" + "\n" "# Discard sink mode\n" "When the destination is `null`, the filter is a sink dropping all input packets.\n" "In this case it accepts ANY type of input PID, not just file ones.\n" @@ -998,7 +1088,8 @@ .finalize = fileout_finalize, .configure_pid = fileout_configure_pid, .process = fileout_process, - .process_event = fileout_process_event + .process_event = fileout_process_event, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/out_http.c -> gpac-26.02.0.tar.gz/src/filters/out_http.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2025 * All rights reserved * * This file is part of GPAC / http server and output filter @@ -35,58 +35,33 @@ #include <gpac/config_file.h> #include <gpac/base_coding.h> #include <gpac/network.h> +#include <gpac/mpd.h> #ifdef GPAC_HAS_QJS #include "../quickjs/quickjs.h" #include "../scenegraph/qjs_common.h" #endif +#include "../utils/downloader.h" -//socket and SSL context ownership is transfered to the download session object -GF_DownloadSession *gf_dm_sess_new_server(GF_DownloadManager *dm, GF_Socket *server, void *ssl_ctx, gf_dm_user_io user_io, void *usr_cbk, Bool async, GF_Err *e); -GF_DownloadSession *gf_dm_sess_new_subsession(GF_DownloadSession *sess, u32 stream_id, void *usr_cbk, GF_Err *e); -u32 gf_dm_sess_subsession_count(GF_DownloadSession *); - -void gf_dm_sess_set_timeout(GF_DownloadSession *sess, u32 timeout); - -GF_Socket *gf_dm_sess_get_socket(GF_DownloadSession *); -GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size); -void gf_dm_sess_clear_headers(GF_DownloadSession *sess); -void gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value); -void gf_dm_sess_set_header_ex(GF_DownloadSession *sess, const char *name, const char *value, Bool allow_overwrite); -GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select); -u32 gf_dm_sess_async_pending(GF_DownloadSession *sess); - -GF_Err gf_dm_sess_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body); -void gf_dm_sess_server_reset(GF_DownloadSession *sess); -Bool gf_dm_sess_is_h2(GF_DownloadSession *sess); -void gf_dm_sess_flush_h2(GF_DownloadSession *sess); - -void gf_dm_sess_set_sock_group(GF_DownloadSession *sess, GF_SockGroup *sg); - -#ifdef GPAC_HAS_SSL - -void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e); -void *gf_ssl_server_context_new(const char *cert, const char *key); -void gf_ssl_server_context_del(void *ssl_server_ctx); -Bool gf_ssl_init_lib(); - -#endif - -enum -{ +GF_OPT_ENUM (GF_HTTPOutFilterOperationMode, MODE_DEFAULT=0, MODE_PUSH, MODE_SOURCE, -}; +); -enum -{ +GF_OPT_ENUM (GF_HTTPOutCORSMode, CORS_AUTO=0, CORS_OFF, CORS_ON, -}; +); +enum +{ + SKIP_RES_NO=0, + SKIP_RES_FILE, + SKIP_RES_PUSH, +}; typedef struct { @@ -107,14 +82,26 @@ char *js; #endif GF_PropStringList rdirs; - Bool close, hold, quit, post, dlist, ice, reopen, blockio; - u32 port, block_size, maxc, maxp, timeout, hmode, sutc, cors, max_client_errors, max_async_buf, ka, zmax; + Bool close, hold, quit, post, dlist, ice, reopen, blockio, cte, norange; + u32 block_size, maxc, maxp, timeout, sutc, max_client_errors, max_async_buf, ka, zmax, maxs; + GF_HTTPOutFilterOperationMode hmode; + GF_HTTPOutCORSMode cors; s32 max_cache_segs; GF_PropStringList hdrs; + GF_PropUIntList port; //internal GF_Filter *filter; GF_Socket *server_sock; + GF_Socket *server_sock_alt; + GF_Socket *server_sock_h3; +#ifdef GPAC_HAS_NGTCP2 + u32 quic_port; + GF_QuicServer *quic_sock; + void *ssl_ctx_quic; +#endif + + GF_List *sessions, *active_sessions; GF_List *inputs; @@ -167,6 +154,8 @@ char *mime; u32 nb_dest; Bool hold, write_not_ready; + u32 file_size; + u32 llhas_mode; Bool is_open, done, is_delete; Bool patch_blocks; @@ -175,7 +164,8 @@ //for PUT mode, NULL in server mode GF_DownloadSession *upload; GF_Socket *upload_sock; - Bool is_h2; + GF_HTTPSessionType http_type; + Bool use_cte, blockio; u32 cur_header; u64 offset_at_seg_start; @@ -183,19 +173,26 @@ char range_hdr100; Bool seg_info_sent; + //because of LLHLS/DASH SSR with separate parts, we cannot use packet aggregation from fiter core + GF_FilterPacket *no_cte_cache, *no_cte_llhas_cache; + u32 no_cte_cache_size, no_cte_llhas_cache_size; + Bool no_cte_flush_pending; + //for server mode, recording char *local_path; FILE *resource; + u32 skip_resource; - FILE *hls_chunk; - char *hls_chunk_path, *hls_chunk_local_path; + FILE *llhas_part; + char *llhas_part_path, *llhas_part_local_path; + char *llhas_template; u8 *tunein_data; u32 tunein_data_size; - Bool force_dst_name; - Bool in_error; - u32 clock_first_error; + Bool force_dst_name; + Bool in_error; + u32 clock_first_error; //max number of files to keep per pid u32 max_segs; @@ -206,13 +203,13 @@ GF_List *mem_files; Bool is_manifest; - //for PUT mode for LL-HLS SF, NULL in server mode - GF_DownloadSession *llhls_upload; - u32 llhls_cur_header; - Bool llhls_is_open; - char *llhls_url; + //for PUT mode for LLHAS SF, NULL in server mode + GF_DownloadSession *llhas_upload; + u32 llhas_cur_header; + Bool llhas_is_open; + char *llhas_url; - Bool flush_close, flush_close_llhls, flush_open, flush_llhls_open; + Bool flush_close, flush_close_llhas, flush_open, flush_llhas_open; } GF_HTTPOutInput; typedef struct @@ -221,19 +218,27 @@ s64 end; } HTTByteRange; +typedef enum +{ + SESS_END_OK=0, + SESS_END_CANCEL, + SESS_END_CLOSE +} SessionEndType; + struct __httpout_session { GF_HTTPOutCtx *ctx; GF_Socket *socket; GF_DownloadSession *http_sess; char peer_addressGF_MAX_IP_NAME_LEN; + u32 peer_port; Bool headers_done; Double start_range; FILE *resource; - char *path, *mime; + char *path, *mime, *req_url; u64 file_size, file_pos, nb_bytes, bytes_in_req; u8 *buffer; Bool done; @@ -244,6 +249,7 @@ u64 last_active_time; Bool file_in_progress; Bool use_chunk_transfer; + Bool blockio; u32 put_in_progress; //for upload only: 0 not an upload, 1 creation, 2: update u32 upload_type; @@ -253,7 +259,7 @@ GF_HTTPOutInput *in_source; Bool send_init_data; - Bool in_source_is_ll_hls_chunk; + Bool in_source_is_llhas_part; u32 nb_ranges, alloc_ranges, range_idx; HTTByteRange *ranges; @@ -262,9 +268,9 @@ u64 req_id; u32 method_type, reply_code, nb_consecutive_errors; - Bool is_h2; + GF_HTTPSessionType http_type; Bool sub_sess_pending; - Bool canceled; + SessionEndType req_end_type; Bool force_destroy; @@ -288,6 +294,8 @@ u64 next_process_clock; }; +static void httpout_delete_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in); + /*GF FileIO for mem mode*/ typedef struct __gf_http_io { @@ -302,7 +310,7 @@ GF_HTTPOutInput *in; u32 nb_used; GF_FileIO *fio; - Bool hls_ll_chunk, do_remove, is_static; + Bool is_llhas_chunk, do_remove, is_static; } GF_HTTPFileIO; static void httpio_del(GF_HTTPFileIO *hio) @@ -399,10 +407,10 @@ GF_HTTPFileIO *old = gf_list_get(ioctx->in->mem_files, i); //static file (init seg, manifest), do not purge if (old->is_static) continue; - //stop at first used io, or first HLS low latency chunk, if any - if (old->nb_used || old->hls_ll_chunk) break; + //stop at first used io, or first LLHAS chunk, if any + if (old->nb_used || old->is_llhas_chunk) break; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOutIO remove %s in write mode, exceed max_cache_seg %d\n", gf_fileio_resource_url(old->fio), count)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOutIO remove %s in write mode, exceed max_cache_segs %d\n", gf_fileio_resource_url(old->fio), count)); gf_list_rem(ioctx->in->mem_files, i); httpio_del(old); i--; @@ -451,21 +459,22 @@ Bool last_connection = GF_TRUE; if (!sess->http_sess) return; - if (sess->is_h2) { + if (sess->http_type) { u32 nb_sub_sess = gf_dm_sess_subsession_count(sess->http_sess); if (nb_sub_sess > 1) { last_connection = GF_FALSE; GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut %d sub-sessions still active in connection to %s, keeping alive\n", nb_sub_sess-1, sess->peer_address )); } else { - gf_dm_sess_flush_h2(sess->http_sess); + gf_dm_sess_close_hmux(sess->http_sess); } } if (last_connection) { - gf_assert(sess->ctx->nb_connections); - sess->ctx->nb_connections--; + if (sess->ctx->nb_connections) + sess->ctx->nb_connections--; - gf_sk_group_unregister(sess->ctx->sg, sess->socket); + if (sess->socket) + gf_sk_group_unregister(sess->ctx->sg, sess->socket); } gf_dm_sess_del(sess->http_sess); @@ -476,12 +485,14 @@ sess->comp_data = NULL; } sess->done = 1; + sess->async_pending = 0; sess->flush_close = 0; if (sess->cbk_close) sess->cbk_close(sess->rt_udta, code); #ifdef GPAC_HAS_QJS - if (sess->ctx->jsc) { + if (sess->ctx->jsc && !JS_IsUndefined(sess->obj)) { gf_js_lock(sess->ctx->jsc, GF_TRUE); + JS_SetOpaque(sess->obj, NULL); JS_FreeValue(sess->ctx->jsc, sess->obj); sess->obj = JS_UNDEFINED; gf_js_lock(sess->ctx->jsc, GF_FALSE); @@ -503,37 +514,53 @@ static void log_request_done(GF_HTTPOutSession *sess); -static Bool httpout_sess_flush_close(GF_HTTPOutSession *sess, Bool close_session) +static void httpout_mark_session_done(GF_HTTPOutSession *sess) +{ + sess->done = GF_TRUE; + sess->async_pending = 0; +} + +static Bool httpout_sess_flush_close(GF_HTTPOutSession *sess, Bool close_session, Bool from_cbk) { - if (!sess->ctx->blockio) { + if (!sess->blockio) { //first attempt at closing, remember close_session flag if (!sess->flush_close) { sess->flush_close = close_session ? 2 : 1; sess->ctx->nb_sess_flush_pending++; } - if (gf_dm_sess_flush_async(sess->http_sess, GF_TRUE) == GF_IP_NETWORK_EMPTY) + GF_Err e = gf_dm_sess_flush_close(sess->http_sess); + if (e == GF_IP_NETWORK_EMPTY) { + sess->ctx->next_wake_us = 1; return GF_FALSE; + } //done closing, restore close_session flag if (sess->flush_close == 2) close_session = GF_TRUE; + if (!from_cbk && (e==GF_IP_CONNECTION_CLOSED)) + close_session = GF_TRUE; + sess->flush_close = 0; sess->ctx->nb_sess_flush_pending--; } log_request_done(sess); - sess->done = GF_TRUE; + httpout_mark_session_done(sess); if (close_session) { httpout_close_session(sess, GF_OK); - } - //might be NULL if quit was set - else if (sess->http_sess) { - sess->headers_done = GF_FALSE; - gf_dm_sess_server_reset(sess->http_sess); + } else { + if (sess->cbk_close) + sess->cbk_close(sess->rt_udta, GF_EOS); + + //might be NULL if quit was set + if (sess->http_sess) { + sess->headers_done = GF_FALSE; + gf_dm_sess_server_reset(sess->http_sess); + } } return GF_TRUE; } -static void httpout_format_date(u64 time, char szDate200, Bool for_listing) +void httpout_format_date(u64 time, char szDate200, Bool for_listing) { time_t gtime; struct tm *t; @@ -734,7 +761,10 @@ gf_dynstrcat(&listing, " at ", NULL); gf_dynstrcat(&listing, szHost, NULL); gf_dynstrcat(&listing, " Port ", NULL); - sprintf(szHost, "%d", ctx->port); + if (ctx->port.nb_items>1) + sprintf(szHost, "%d and %d ", ctx->port.vals0, ctx->port.vals1); + else + sprintf(szHost, "%d", ctx->port.vals0); gf_dynstrcat(&listing, szHost, NULL); gf_dynstrcat(&listing, "</address>\n</body></html>", NULL); return listing; @@ -758,25 +788,40 @@ if (!strchr("/\\", dirlen-1)) gf_dynstrcat(&in->local_path, "/", NULL); if (in->path0=='/') - gf_dynstrcat(&in->local_path, in->path+1, NULL); - else - gf_dynstrcat(&in->local_path, in->path, NULL); + gf_dynstrcat(&in->local_path, in->path+1, NULL); + else + gf_dynstrcat(&in->local_path, in->path, NULL); } -static Bool httpout_sess_parse_range(GF_HTTPOutSession *sess, char *range) +typedef enum +{ + RANGE_OK = 0, + RANGE_BAD_FORMAT, + RANGE_INVALID_FORMAT, + RANGE_NOT_ALLOWED, + +} RangeState; + +static Bool httpout_sess_parse_range(GF_HTTPOutSession *sess, char *range, char **response_body) { Bool request_ok = GF_TRUE; u32 i; Bool has_open_start=GF_FALSE; Bool has_file_end=GF_FALSE; u64 known_file_size; + RangeState rst = RANGE_OK; + const char *range_msg = NULL; + sess->nb_ranges = 0; sess->nb_bytes = 0; sess->range_idx = 0; if (!range) return GF_TRUE; - if (sess->in_source && !sess->ctx->has_read_dir) - return GF_FALSE; + if (sess->in_source && !sess->ctx->has_read_dir) { + rst = RANGE_NOT_ALLOWED; + range_msg = "no associated read directory"; + goto exit; + } while (range) { char *sep; @@ -789,7 +834,8 @@ //unsupported unit if (strncmp(range, "bytes=", 6)) { - return GF_FALSE; + rst = RANGE_INVALID_FORMAT; + goto exit; } range += 6; sep = strchr(range, '/'); @@ -840,12 +886,19 @@ range = next+1; if (!request_ok) break; } - if (!request_ok) return GF_FALSE; + if (!request_ok) { + rst = RANGE_BAD_FORMAT; + goto exit; + } known_file_size = 0; if (sess->in_source && !sess->resource) { //cannot fetch end of file it is not yet known ! - if (has_file_end) return GF_FALSE; + if (has_file_end) { + rst = RANGE_NOT_ALLOWED; + range_msg = "resource does not yet existing"; + goto exit; + } known_file_size = sess->in_source->nb_write; } else { //HTTP does not allow for "range: X-" to resolve in "content-range: X-/*" @@ -884,18 +937,27 @@ request_ok = GF_FALSE; break; } + if (sess->rangesi.start > (s64) sess->rangesi.end) { + request_ok = GF_FALSE; + break; + } } else { if (known_file_size==0) { request_ok = GF_FALSE; break; } //no start, end is a file size - if (sess->rangesi.end >= (s64) known_file_size) { + if (sess->rangesi.end > (s64) known_file_size) { request_ok = GF_FALSE; break; } sess->rangesi.start = known_file_size - sess->rangesi.end; sess->rangesi.end = known_file_size - 1; + + if (sess->rangesi.start > (s64) sess->rangesi.end) { + request_ok = GF_FALSE; + break; + } } sess->bytes_in_req += (sess->rangesi.end + 1 - sess->rangesi.start); } @@ -904,16 +966,42 @@ sess->nb_ranges = 0; if (!request_ok) { - if (!sess->in_source || (sess->nb_ranges>1)) - return GF_FALSE; + if (!sess->in_source || (sess->nb_ranges>1)) { + rst = RANGE_NOT_ALLOWED; + range_msg = sess->in_source ? "multiple byte ranges not allowed" : "no associated source"; + goto exit; + } //source in progress, we accept single range - note that this could be further refined by postponing the request until the source - //is done or has written the requested byte range, however this will delay sending chunk in LL-HLS byterange ... + //is done or has written the requested byte range, however this will delay sending chunk in LLHAS byterange ... //for now, since we use chunk transfer in this case, we will send less data than asked and close resource using last 0-size chunk } sess->file_pos = sess->ranges0.start; if (sess->resource) gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + return GF_TRUE; + +exit: + switch (rst) { + case RANGE_BAD_FORMAT: + gf_dynstrcat(response_body, "Range format not valid: ", NULL); + break; + case RANGE_INVALID_FORMAT: + gf_dynstrcat(response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL); + break; + case RANGE_NOT_ALLOWED: + gf_dynstrcat(response_body, "Range request not satisfiable: ", NULL); + break; + case RANGE_OK: + return GF_TRUE; + } + sess->reply_code = 416; + gf_dynstrcat(response_body, range, NULL); + if (range_msg) + gf_dynstrcat(response_body, range_msg, " : "); + + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut %s\n", *response_body)); + return GF_FALSE; } static u32 httpout_do_log(GF_HTTPOutSession *sess, u32 method) @@ -970,22 +1058,24 @@ } #endif //GPAC_DISABLE_LOG -GF_Err httpout_new_subsession(GF_HTTPOutSession *sess, u32 stream_id) +GF_Err httpout_new_subsession(GF_HTTPOutSession *sess, s64 stream_id) { GF_HTTPOutSession *sub_sess; GF_Err e; - //warning, sess->is_h2 might not be set yet - if (!sess || !sess->http_sess || !gf_dm_sess_is_h2(sess->http_sess)) + //warning, sess->http_type might not be set yet + if (!sess->http_type) { + sess->http_type = gf_dm_sess_is_hmux(sess->http_sess); + } + if (!sess || !sess->http_sess || !sess->http_type) return GF_BAD_PARAM; - sess->is_h2 = GF_TRUE; GF_SAFEALLOC(sub_sess, GF_HTTPOutSession); if (!sub_sess) return GF_OUT_OF_MEM; sub_sess->socket = sess->socket; sub_sess->ctx = sess->ctx; sub_sess->last_active_time = gf_sys_clock_high_res(); - //mark the subsession as being h2 right away so that we can process it even if no pending data on socket (cf httpout_process_session) - sub_sess->is_h2 = GF_TRUE; + //mark the subsession as being hmux right away so that we can process it even if no pending data on socket (cf httpout_process_session) + sub_sess->http_type = sess->http_type; strcpy(sub_sess->peer_address, sess->peer_address); sub_sess->http_sess = gf_dm_sess_new_subsession(sess->http_sess, stream_id, sub_sess, &e); if (!sub_sess->http_sess) { @@ -1048,7 +1138,7 @@ #ifdef GPAC_HAS_QJS u32 js_sess_throttle(void *udta, u64 done, u64 total) { - s32 next_time; + s32 next_time=0; JSValue args2; GF_HTTPOutSession *sess = (void *)udta; gf_js_lock(sess->ctx->jsc, GF_TRUE); @@ -1056,7 +1146,11 @@ args0 = JS_NewInt64(sess->ctx->jsc, done); args1 = JS_NewInt64(sess->ctx->jsc, total); JSValue ret = JS_Call(sess->ctx->jsc, fun, sess->obj, 2, args); - JS_ToInt32(sess->ctx->jsc, &next_time, ret); + if (JS_IsException(ret)) { + js_dump_error(sess->ctx->jsc); + } else { + JS_ToInt32(sess->ctx->jsc, &next_time, ret); + } JS_FreeValue(sess->ctx->jsc, fun); JS_FreeValue(sess->ctx->jsc, ret); JS_FreeValue(sess->ctx->jsc, args0); @@ -1069,6 +1163,11 @@ { GF_HTTPOutSession *sess = (void *)udta; gf_js_lock(sess->ctx->jsc, GF_TRUE); + //check we haven't been canceled + if (JS_IsUndefined(sess->obj)) { + gf_js_lock(sess->ctx->jsc, GF_FALSE); + return 0; + } JSValue fun = JS_GetPropertyStr(sess->ctx->jsc, sess->obj, "read"); JSValue arg = JS_NewArrayBuffer(sess->ctx->jsc, sess->buffer, sess->ctx->block_size, NULL, NULL, 0); JSValue ret = JS_Call(sess->ctx->jsc, fun, sess->obj, 1, &arg); @@ -1081,16 +1180,28 @@ JS_FreeValue(sess->ctx->jsc, ret); JS_FreeValue(sess->ctx->jsc, fun); JS_FreeValue(sess->ctx->jsc, arg); + //detach as soon as EOS + if (!nb_read) { + if (sess->cbk_close) sess->cbk_close(sess->rt_udta, GF_EOS); + JS_SetOpaque(sess->obj, NULL); + JS_FreeValue(sess->ctx->jsc, sess->obj); + sess->obj = JS_UNDEFINED; + } gf_js_lock(sess->ctx->jsc, GF_FALSE); return nb_read; } u32 js_sess_write(void *udta, const u8 *data, u32 size) { GF_HTTPOutSession *sess = (void *)udta; + if (!sess || JS_IsUndefined(sess->obj)) return 0; + gf_js_lock(sess->ctx->jsc, GF_TRUE); JSValue fun = JS_GetPropertyStr(sess->ctx->jsc, sess->obj, "write"); JSValue arg = data ? JS_NewArrayBuffer(sess->ctx->jsc, (u8*)data, size, NULL, NULL, 0) : JS_NULL; JSValue ret = JS_Call(sess->ctx->jsc, fun, sess->obj, 1, &arg); + if (JS_IsException(ret)) { + js_dump_error(sess->ctx->jsc); + } JS_FreeValue(sess->ctx->jsc, ret); JS_FreeValue(sess->ctx->jsc, fun); JS_FreeValue(sess->ctx->jsc, arg); @@ -1100,10 +1211,15 @@ void js_sess_close(void *udta, GF_Err code) { GF_HTTPOutSession *sess = (void *)udta; + if (!sess || JS_IsUndefined(sess->obj)) return; + gf_js_lock(sess->ctx->jsc, GF_TRUE); JSValue fun = JS_GetPropertyStr(sess->ctx->jsc, sess->obj, "close"); JSValue arg = JS_NewInt32(sess->ctx->jsc, code); JSValue ret = JS_Call(sess->ctx->jsc, fun, sess->obj, 1, &arg); + if (JS_IsException(ret)) { + js_dump_error(sess->ctx->jsc); + } JS_FreeValue(sess->ctx->jsc, ret); JS_FreeValue(sess->ctx->jsc, arg); JS_FreeValue(sess->ctx->jsc, fun); @@ -1112,7 +1228,8 @@ static JSValue httpout_js_send(JSContext *c, JSValueConst this_val, int argc, JSValueConst *argv) { - GF_HTTPOutSession *sess = JS_GetOpaque_Nocheck(this_val); + JSClassID _classID; + GF_HTTPOutSession *sess = JS_GetAnyOpaque(this_val, &_classID); if (!sess) return js_throw_err_msg(c, GF_BAD_PARAM, "send() called on invalid session\n"); @@ -1132,7 +1249,8 @@ sess->reply = 500; JS_FreeValue(c, ret); - + sess->content_length = 0; + sess->use_chunk_transfer = GF_FALSE; JSValue hdrs = JS_GetPropertyStr(c, sess->obj, "headers_out"); u32 i, nb_hdrs=0; ret = JS_GetPropertyStr(c, hdrs, "length"); @@ -1149,6 +1267,10 @@ if (!sess->headers) sess->headers = gf_list_new(); gf_list_add(sess->headers, gf_strdup(n)); gf_list_add(sess->headers, gf_strdup(v)); + if (!stricmp(n, "Content-Length")) + sess->content_length = atoi(v); + if (!stricmp(n, "Transfer-Encoding") && !stricmp(v, "chunked") && !sess->http_type) + sess->use_chunk_transfer=GF_TRUE; } if (n) JS_FreeCString(c, n); if (v) JS_FreeCString(c, v); @@ -1162,21 +1284,25 @@ sess->rt_udta = sess; sess->next_process_clock = 0; ret = JS_GetPropertyStr(c, sess->obj, "throttle"); + sess->cbk_throttle = NULL; if (JS_IsFunction(c, ret)) sess->cbk_throttle = js_sess_throttle; JS_FreeValue(c, ret); ret = JS_GetPropertyStr(c, sess->obj, "close"); + sess->cbk_close = NULL; if (JS_IsFunction(c, ret)) sess->cbk_close = js_sess_close; JS_FreeValue(c, ret); + sess->cbk_read = NULL; if (sess->method_type==GF_HTTP_GET) { ret = JS_GetPropertyStr(c, sess->obj, "read"); if (JS_IsFunction(c, ret)) sess->cbk_read = js_sess_read; JS_FreeValue(c, ret); } + sess->cbk_write = NULL; if (sess->reply && ((sess->method_type==GF_HTTP_PUT) || (sess->method_type==GF_HTTP_POST))) { ret = JS_GetPropertyStr(c, sess->obj, "write"); sess->upload_type = 0; @@ -1200,33 +1326,43 @@ return 0; } gf_js_lock(c, GF_TRUE); - JSValue obj = JS_NewObject(c); - JS_SetOpaque(obj, sess); - JS_SetPropertyStr(c, obj, "method", JS_NewString(c, method )); - JS_SetPropertyStr(c, obj, "url", JS_NewString(c, url)); - JS_SetPropertyStr(c, obj, "auth_code", JS_NewInt32(c, auth_code )); - JS_SetPropertyStr(c, obj, "send", JS_NewCFunction(c, httpout_js_send, "send", 0) ); - JS_SetPropertyStr(c, obj, "reply", JS_NewInt32(c, 0)); - JS_FreeValue(c, sess->obj); - sess->obj = obj; + if (!JS_IsUndefined(sess->obj)) { + JS_SetOpaque(sess->obj, NULL); + JS_FreeValue(c, sess->obj); + } + + sess->obj = JS_NewObject(c); + JS_SetOpaque(sess->obj, sess); + JS_SetPropertyStr(c, sess->obj, "method", JS_NewString(c, method )); + JS_SetPropertyStr(c, sess->obj, "url", JS_NewString(c, url)); + JS_SetPropertyStr(c, sess->obj, "auth_code", JS_NewInt32(c, auth_code )); + JS_SetPropertyStr(c, sess->obj, "send", JS_NewCFunction(c, httpout_js_send, "send", 0) ); + JS_SetPropertyStr(c, sess->obj, "reply", JS_NewInt32(c, 0)); + JS_SetPropertyStr(c, sess->obj, "tls", JS_NewBool(c, gf_dm_sess_use_tls(sess->http_sess) ? 1 : 0)); + JS_SetPropertyStr(c, sess->obj, "netid", JS_NewInt64(c, sess->socket ? (s64)(uintptr_t) sess->socket : 0)); + JS_SetPropertyStr(c, sess->obj, "IP", JS_NewString(c, sess->peer_address)); + JS_SetPropertyStr(c, sess->obj, "port", JS_NewInt32(c, sess->peer_port)); JSValue hdrs = JS_NewArray(c); - JS_SetPropertyStr(c, obj, "headers_out", hdrs); + JS_SetPropertyStr(c, sess->obj, "headers_out", hdrs); hdrs = JS_NewArray(c); - JS_SetPropertyStr(c, obj, "headers_in", hdrs); - u32 i; + JS_SetPropertyStr(c, sess->obj, "headers_in", hdrs); + u32 i, k=0; for (i=0; i<nb_hdrs; i+=2) { + if (!headersi || !headersi+1) continue; JSValue h = JS_NewObject(c); JS_SetPropertyStr(c, h, "name", JS_NewString(c, headersi )); JS_SetPropertyStr(c, h, "value", JS_NewString(c, headersi+1 )); - JS_SetPropertyUint32(c, hdrs, i, h); + JS_SetPropertyUint32(c, hdrs, k, h); + k++; } - JSValue ret = JS_Call(c, sess->ctx->request_fun, sess->ctx->js_obj, 1, &obj); + JSValue ret = JS_Call(c, sess->ctx->request_fun, sess->ctx->js_obj, 1, &sess->obj); if (JS_IsException(ret)) { js_dump_error(c); JS_FreeValue(c, ret); + JS_SetOpaque(sess->obj, NULL); JS_FreeValue(c, sess->obj); sess->obj = JS_UNDEFINED; gf_js_lock(c, GF_FALSE); @@ -1312,17 +1448,17 @@ Bool send_cors; u32 i, count; GF_HTTPOutInput *source_pid = NULL; - Bool source_pid_is_ll_hls_chunk = GF_FALSE; + Bool source_pid_is_llhas_part = GF_FALSE; GF_HTTPOutSession *source_sess = NULL; GF_HTTPOutSession *sess = usr_cbk; HTTP_DIRInfo *the_dir=NULL; if (parameter->msg_type == GF_NETIO_REQUEST_SESSION) { - parameter->error = httpout_new_subsession(sess, parameter->reply); + parameter->error = httpout_new_subsession(sess, parameter->stream_id); return; } if (parameter->msg_type == GF_NETIO_CANCEL_STREAM) { - sess->canceled = GF_TRUE; + sess->req_end_type = SESS_END_CANCEL; return; } @@ -1389,8 +1525,35 @@ hdrsnb_hdrs+1 = (char*)val; nb_hdrs+=2; } + sess->nb_bytes = sess->bytes_in_req = 0; + + if (sess->req_url) gf_free(sess->req_url); + sess->req_url = gf_strdup(url); - u32 auth_code = httpout_auth_check(NULL, gf_dm_sess_get_header(sess->http_sess, "Authorization"), GF_FALSE); + HTTP_DIRInfo *di=NULL; + u32 di_len=0; + Bool is_write = GF_FALSE; + if ((parameter->reply==GF_HTTP_PUT) || (parameter->reply==GF_HTTP_POST) || (parameter->reply==GF_HTTP_DELETE)) + is_write=GF_TRUE; + for (i=0; i<gf_list_count(sess->ctx->directories); i++) { + HTTP_DIRInfo *adi = gf_list_get(sess->ctx->directories, i); + if (is_write) { + if (!adi->wu && !adi->wg) continue; + } else { + if (!adi->ru && !adi->rg) continue; + } + if (adi->name && strncmp(adi->name, url+1, adi->name_len)) + continue; + if (!adi->name || (adi->name_len>di_len)) { + di = adi; + di_len = adi->name_len; + } + } + u32 auth_code = 200; + if (di) { + sess->dir_desc = di; + auth_code = httpout_auth_check(sess->dir_desc, gf_dm_sess_get_header(sess->http_sess, "Authorization"), GF_FALSE); + } s32 ret = sess->ctx->on_request(sess->ctx->rt_udta, sess, get_method_name(parameter->reply), url, auth_code, nb_hdrs, (const char**)hdrs); gf_free(hdrs); if (ret>0) { @@ -1402,6 +1565,22 @@ if (url) gf_free(url); return; } + + cors_origin = (char *) gf_dm_sess_get_header(sess->http_sess, "Origin"); + switch (sess->ctx->cors) { + case CORS_ON: + send_cors = GF_TRUE; + break; + case CORS_AUTO: + if (cors_origin != NULL) { + send_cors = GF_TRUE; + break; + } + default: + send_cors = GF_FALSE; + break; + } + if (sess->async_pending==2) { sess->async_pending = 3; if (sess->reply) { @@ -1490,8 +1669,9 @@ hdr = gf_dm_sess_get_header(sess->http_sess, "Transfer-Encoding"); if (hdr && !strcmp(hdr, "chunked")) { sess->use_chunk_transfer = GF_TRUE; - } else if (!sess->is_h2) { - sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess); + } else { + sess->http_type = gf_dm_sess_is_hmux(sess->http_sess); + if (sess->http_type==GF_SESS_TYPE_HTTP3) sess->blockio = GF_FALSE; } sess->file_in_progress = GF_FALSE; sess->nb_bytes = 0; @@ -1502,6 +1682,15 @@ if (sess->resource) gf_fclose(sess->resource); sess->resource = NULL; + if (sess->ctx->maxs && sess->content_length && (sess->content_length>sess->ctx->maxs)) { + char szTmp100; + sprintf(szTmp, "%u bytes", sess->ctx->maxs); + sess->reply_code = 413; + gf_dynstrcat(&response_body, "Maximum payload size allowed is ", NULL); + gf_dynstrcat(&response_body, szTmp, NULL); + goto exit; + } + if (sess->ctx->hmode==MODE_SOURCE) { if (range) { GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Cannot handle PUT/POST request as PID output with byte ranges (%s)\n", range)); @@ -1524,7 +1713,7 @@ gf_dynstrcat(&response_body, "File exists but cannot be open", NULL); goto exit; } - if (!sess->content_length && !sess->use_chunk_transfer && !sess->is_h2) { + if (!sess->content_length && !sess->use_chunk_transfer && !sess->http_type) { sess->reply_code = 411; gf_dynstrcat(&response_body, "No content length specified and chunked transfer not enabled", NULL); goto exit; @@ -1534,11 +1723,7 @@ sess->file_pos = 0; range = gf_dm_sess_get_header(sess->http_sess, "Range"); - if (! httpout_sess_parse_range(sess, (char *) range) ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Unsupported Range format: %s\n", range)); - sess->reply_code = 416; - gf_dynstrcat(&response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL); - gf_dynstrcat(&response_body, range, NULL); + if (!httpout_sess_parse_range(sess, (char *) range, &response_body)) { goto exit; } if (!sess->buffer) { @@ -1566,6 +1751,7 @@ httpout_push_headers(sess); //send reply once we are done receiving + assert(sess->nb_bytes==0); return; } @@ -1590,9 +1776,9 @@ break; } } - if (in->hls_chunk_path && !strcmp(in->hls_chunk_path, url) && !in->done) { + if (in->llhas_part_path && !strcmp(in->llhas_part_path, url) && !in->done) { source_pid = in; - source_pid_is_ll_hls_chunk = GF_TRUE; + source_pid_is_llhas_part = GF_TRUE; break; } if (in->mem_files) { @@ -1631,8 +1817,18 @@ if (!strchr("/\\", mdirlen-1)) gf_dynstrcat(&full_path, "/", NULL); + char *querystring = strchr(res_url, '?'); + + // ignore query string when looking for a file on disk + if (querystring) + querystring0 = 0; + gf_dynstrcat(&full_path, res_url, NULL); + // restore query string in original url + if (querystring) + querystring0 = '?'; + if (gf_file_exists(full_path) || gf_dir_exists(full_path) ) { the_dir = adi; break; @@ -1664,20 +1860,6 @@ } } - cors_origin = (char *) gf_dm_sess_get_header(sess->http_sess, "Origin"); - switch (sess->ctx->cors) { - case CORS_ON: - send_cors = GF_TRUE; - break; - case CORS_AUTO: - if (cors_origin != NULL) { - send_cors = GF_TRUE; - break; - } - default: - send_cors = GF_FALSE; - break; - } if (is_options && (!url || !strcmp(url, "*"))) { sess->reply_code = 204; goto exit; @@ -1740,7 +1922,7 @@ } } - range = gf_dm_sess_get_header(sess->http_sess, "Range"); + range = sess->ctx->norange ? NULL : gf_dm_sess_get_header(sess->http_sess, "Range"); if (sess->in_source) { sess->in_source->nb_dest--; @@ -1770,7 +1952,6 @@ if (e) { sess->reply_code = 500; GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Error deleting file %s (full path %s)\n", url, full_path)); - sess->reply_code = 500; gf_dynstrcat(&response_body, "Error while deleting ", NULL); gf_dynstrcat(&response_body, url, NULL); gf_dynstrcat(&response_body, ": ", NULL); @@ -1782,7 +1963,7 @@ if (sess->do_log) { sess->req_id = ++sess->ctx->req_id; GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s DELETE %s\n", sess->req_id, sess->peer_address, url+1)); - sess->do_log = GF_FALSE; + sess->do_log = 0; } } else { e = GF_URL_ERROR; @@ -1799,7 +1980,7 @@ source_pid->hold = GF_FALSE; sess->file_pos = sess->file_size = 0; - sess->use_chunk_transfer = GF_TRUE; + sess->use_chunk_transfer = source_pid->use_cte; } /*we have matching etag*/ else if (etag && !strcmp(etag, szETag) && !sess->ctx->no_etag) { @@ -1820,16 +2001,16 @@ source_pid->nb_dest++; source_pid->hold = GF_FALSE; sess->send_init_data = GF_TRUE; - sess->in_source_is_ll_hls_chunk = source_pid_is_ll_hls_chunk; + sess->in_source_is_llhas_part = source_pid_is_llhas_part; sess->file_in_progress = GF_TRUE; gf_assert(!full_path); gf_assert(source_pid->local_path); - if (source_pid_is_ll_hls_chunk) - full_path = gf_strdup(source_pid->hls_chunk_local_path); + if (source_pid_is_llhas_part) + full_path = gf_strdup(source_pid->llhas_part_local_path); else full_path = gf_strdup(source_pid->local_path); - sess->use_chunk_transfer = GF_TRUE; + sess->use_chunk_transfer = source_pid->use_cte; sess->file_size = 0; } sess->path = full_path; @@ -1849,6 +2030,8 @@ } } else { GF_FilterProbeScore probe_score=GF_FPROBE_NOT_SUPPORTED; + + if (sess->resource) gf_fclose(sess->resource); //no need to use gf_fopen_ex in mem mode, since the fullpath is the gfio:// URL of the mem resource sess->resource = gf_fopen(full_path, "rb"); //we may not have the file if it is currently being created @@ -1890,6 +2073,8 @@ if (source_sess) { //use uploaded size on source as max file size for this request sess->file_size = source_sess->file_pos; + //we cannot disable CTE in this case for now as the source is still uploading + //we would need to postpone the request until upload is done sess->use_chunk_transfer = GF_TRUE; sess->put_in_progress = 1; } else if (sess->resource) { @@ -1908,11 +2093,7 @@ } //parse byte range except if associated input in single mode where byte ranges are ignored - if ( (!sess->in_source || !sess->ctx->single_mode) && ! httpout_sess_parse_range(sess, (char *) range) ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Unsupported Range format: %s\n", range)); - sess->reply_code = 416; - gf_dynstrcat(&response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL); - gf_dynstrcat(&response_body, range, NULL); + if ((!sess->in_source || !sess->ctx->single_mode) && !httpout_sess_parse_range(sess, (char *) range, &response_body)) { goto exit; } @@ -1962,6 +2143,14 @@ } if (cors_origin) gf_free(cors_origin); +#ifdef GPAC_HAS_NGTCP2 + if ((sess->http_type!=GF_SESS_TYPE_HTTP3) && sess->ctx->quic_sock) { + char szTmp100; + sprintf(szTmp, "h3=\":%u\"", sess->ctx->quic_port); + gf_dm_sess_set_header(sess->http_sess, "Alt-svc", szTmp); + } +#endif + if (sess->ctx->sutc) { sprintf(szFmt, LLU, gf_net_get_utc() ); gf_dm_sess_set_header(sess->http_sess, "Server-UTC", szFmt); @@ -2023,7 +2212,7 @@ gf_fseek(sess->resource, 0, SEEK_SET); } //only put content length if not using chunk transfer - bytes_in_req may be > 0 if we have a byte range on a chunk-transfer session - if (sess->bytes_in_req && !sess->use_chunk_transfer) { + if ((sess->bytes_in_req || (sess->nb_ranges==1)) && !sess->use_chunk_transfer) { sprintf(szFmt, LLU, sess->bytes_in_req); gf_dm_sess_set_header(sess->http_sess, "Content-Length", szFmt); } @@ -2039,7 +2228,8 @@ if (!is_head && sess->nb_ranges) { char *ranges = NULL; - gf_dynstrcat(&ranges, "bytes=", NULL); + //BNF: UNIT SP range, no bytes= as in request Range header ... + gf_dynstrcat(&ranges, "bytes ", NULL); for (i=0; i<sess->nb_ranges; i++) { if (sess->rangesi.end==-1) { sprintf(szFmt, LLD"-/*", sess->rangesi.start); @@ -2096,10 +2286,13 @@ gf_dm_sess_set_header(sess->http_sess, "icy-br", szFmt); } gf_dm_sess_set_header(sess->http_sess, "icy-pub", "1"); - p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_SERVICE_NAME); + GF_PropertyEntry *pe=NULL; + p = gf_filter_pid_get_info(sess->in_source->ipid, GF_PROP_PID_SERVICE_NAME, &pe); if (p && p->value.string) { gf_dm_sess_set_header(sess->http_sess, "icy-name", p->value.string); } + gf_filter_release_property(pe); + p_idx = 0; while (1) { const char *pname; @@ -2119,27 +2312,31 @@ } httpout_push_headers(sess); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Sending response to %s\n", sess->peer_address)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Sending response to %s\n", sess->peer_address)); if (sess->do_log) { sess->req_id = ++sess->ctx->req_id; sess->method_type = parameter->reply; sess->req_start_time = gf_sys_clock_high_res(); +#ifndef GPAC_DISABLE_LOG + u32 log_level = (sess->reply_code>=400) ? GF_LOG_WARNING : GF_LOG_INFO; +#endif if (not_modified) { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s %s %s: reply %d\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->reply_code)); + GF_LOG(log_level, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s %s %s: reply %d\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->reply_code)); } else if (szRange0) { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s %s %s range: %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, szRange, sess->use_chunk_transfer ? " chunk-transfer" : "")); + GF_LOG(log_level, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s %s %s range: %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, szRange, sess->use_chunk_transfer ? " chunk-transfer" : "")); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s %s %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->use_chunk_transfer ? " chunk-transfer" : "")); + GF_LOG(log_level, GF_LOG_ALL, ("HTTPOut REQ#"LLU" %s %s %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->use_chunk_transfer ? " chunk-transfer" : "")); } } sess->nb_consecutive_errors = 0; - sess->canceled = GF_FALSE; + sess->req_end_type = SESS_END_OK; gf_assert(sess->reply_code); e = gf_dm_sess_send_reply(sess->http_sess, sess->reply_code, response_body, response_body ? (u32) strlen(response_body) : 0, no_body); sess->headers_done = GF_TRUE; - sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess); + sess->http_type = gf_dm_sess_is_hmux(sess->http_sess); + if (sess->http_type==GF_SESS_TYPE_HTTP3) sess->blockio = GF_FALSE; if (url) gf_free(url); if (!sess->buffer) { @@ -2147,9 +2344,9 @@ } if (response_body) { gf_free(response_body); - sess->done = GF_TRUE; + httpout_mark_session_done(sess); } else if (parameter->reply == GF_HTTP_DELETE) { - sess->done = GF_TRUE; + httpout_mark_session_done(sess); } else if (parameter->reply == GF_HTTP_HEAD) { sess->done = GF_FALSE; sess->file_pos = sess->file_size; @@ -2160,13 +2357,13 @@ gf_sk_group_register(sess->ctx->sg, sess->socket); } if (not_modified) { - sess->done = GF_TRUE; + httpout_mark_session_done(sess); } } if (e<0) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Error sending reply: %s\n", gf_error_to_string(e))); - sess->done = GF_TRUE; + httpout_mark_session_done(sess); } if (sess->done) @@ -2176,7 +2373,7 @@ if (sess->done) { sess->done = GF_FALSE; - httpout_sess_flush_close(sess, GF_FALSE); + httpout_sess_flush_close(sess, GF_FALSE, GF_TRUE); } sess->last_active_time = gf_sys_clock_high_res(); return; @@ -2222,10 +2419,21 @@ gf_dm_sess_set_header(sess->http_sess, "Access-Control-Allow-Headers", "*"); } } +#ifdef GPAC_HAS_NGTCP2 + if ((sess->http_type!=GF_SESS_TYPE_HTTP3) && sess->ctx->quic_sock) { + char szTmp100; + sprintf(szTmp, "h3=\":%u\"", sess->ctx->quic_port); + gf_dm_sess_set_header(sess->http_sess, "Alt-svc", szTmp); + } +#endif if (sess->reply_code == 401) { gf_dm_sess_set_header(sess->http_sess, "WWW-Authenticate", "Basic"); } + if (sess->reply_code == 416) { + sprintf(szFmt, "bytes */"LLU, sess->file_size); + gf_dm_sess_set_header(sess->http_sess, "Content-Range", szFmt); + } if (response_body || sess->body_or_file) { body_size = (u32) strlen(response_body ? response_body : sess->body_or_file); @@ -2240,16 +2448,24 @@ gf_dm_sess_set_header(sess->http_sess, "Content-Length", "0"); } - if (sess->cbk_read || sess->cbk_write) { - if (sess->cbk_read) { - gf_dm_sess_set_header(sess->http_sess, "Transfer-Encoding", "chunked"); - sess->use_chunk_transfer=GF_TRUE; - } - if (!sess->buffer) { - sess->buffer = gf_malloc(sizeof(u8)*sess->ctx->block_size); + if (sess->ctx->sutc) { + sprintf(szFmt, LLU, gf_net_get_utc() ); + gf_dm_sess_set_header(sess->http_sess, "Server-UTC", szFmt); + } + + if ((sess->reply_code>=200) && (sess->reply_code<300)) { + if (sess->cbk_read || sess->cbk_write) { + if (sess->cbk_read && !sess->content_length) { + gf_dm_sess_set_header(sess->http_sess, "Transfer-Encoding", "chunked"); + sess->use_chunk_transfer=GF_TRUE; + } + if (!sess->buffer) { + sess->buffer = gf_malloc(sizeof(u8)*sess->ctx->block_size); + } + sess->http_type = gf_dm_sess_is_hmux(sess->http_sess); } - sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess); } + httpout_push_headers(sess); //upload with custom IO, do not send reply @@ -2258,7 +2474,8 @@ char *body = (sess->body_or_file || sess->cbk_read) ? sess->body_or_file : response_body; gf_dm_sess_send_reply(sess->http_sess, sess->reply_code, body, body ? (u32) strlen(body) : 0, GF_FALSE); } - sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess); + sess->http_type = gf_dm_sess_is_hmux(sess->http_sess); + if (sess->http_type==GF_SESS_TYPE_HTTP3) sess->blockio = GF_FALSE; if (response_body) gf_free(response_body); if (sess->body_or_file) { @@ -2277,19 +2494,21 @@ } if (url) gf_free(url); - sess->canceled = GF_FALSE; + sess->req_end_type = SESS_END_OK; sess->headers_done = GF_FALSE; - if (sess->cbk_read || sess->cbk_write) { - if (sess->cbk_read) sess->headers_done = GF_TRUE; - sess->done = GF_FALSE; - return; + if ((sess->reply_code>=200) && (sess->reply_code<300)) { + if (sess->cbk_read || sess->cbk_write) { + if (sess->cbk_read) sess->headers_done = GF_TRUE; + sess->done = GF_FALSE; + return; + } } sess->upload_type = 0; - httpout_sess_flush_close(sess, GF_FALSE); + httpout_sess_flush_close(sess, GF_FALSE, GF_TRUE); - if (!sess->is_h2 && (sess->ctx->close || (sess->nb_consecutive_errors == sess->ctx->max_client_errors))) { + if (!sess->http_type && (sess->ctx->close || (sess->nb_consecutive_errors == sess->ctx->max_client_errors))) { sess->force_destroy = GF_TRUE; } else if (sess->http_sess) { gf_dm_sess_server_reset(sess->http_sess); @@ -2305,17 +2524,18 @@ HTTP_PUT_HEADER_DONE }; -static void httpout_in_io_ex(void *usr_cbk, GF_NETIO_Parameter *parameter, Bool is_llhls) +static void httpout_in_io_ex(void *usr_cbk, GF_NETIO_Parameter *parameter, Bool is_llhas) { GF_HTTPOutInput *in =usr_cbk; - u32 *cur_header = is_llhls ? &in->llhls_cur_header : &in->cur_header; + u32 *cur_header = is_llhas ? &in->llhas_cur_header : &in->cur_header; if (parameter->msg_type==GF_NETIO_GET_METHOD) { if (in->is_delete) parameter->name = "DELETE"; else parameter->name = in->ctx->post ? "POST" : "PUT"; + *cur_header = HTTP_PUT_HEADER_ENCODING; return; } @@ -2326,8 +2546,16 @@ switch (*cur_header) { case HTTP_PUT_HEADER_ENCODING: - parameter->name = "Transfer-Encoding"; - parameter->value = "chunked"; + + if (!in->use_cte) { + parameter->name = "Content-Length"; + sprintf(in->range_hdr, "%u", in->file_size); + parameter->value = in->range_hdr; + } else { + parameter->name = "Transfer-Encoding"; + parameter->value = "chunked"; + } + if (in->mime) *cur_header = HTTP_PUT_HEADER_MIME; else @@ -2337,8 +2565,8 @@ parameter->name = "Content-Type"; parameter->value = in->mime; *cur_header = HTTP_PUT_HEADER_DONE; - //range only for non LLHLS - if (in->write_start_range && !is_llhls) + //range only for non LLHAS + if (in->write_start_range && !is_llhas) in->cur_header = HTTP_PUT_HEADER_RANGE; break; case HTTP_PUT_HEADER_RANGE: @@ -2369,11 +2597,125 @@ { httpout_in_io_ex(usr_cbk, parameter, GF_FALSE); } -static void httpout_in_io_llhls(void *usr_cbk, GF_NETIO_Parameter *parameter) +static void httpout_in_io_llhas(void *usr_cbk, GF_NETIO_Parameter *parameter) { httpout_in_io_ex(usr_cbk, parameter, GF_TRUE); } +static void httpout_configure_directories(GF_HTTPOutCtx *ctx) +{ + u32 i; + Bool has_gmem = GF_FALSE; + ctx->has_read_dir = GF_FALSE; + ctx->has_write_dir = GF_FALSE; + ctx->directories = gf_list_new(); + for (i=0; i<ctx->rdirs.nb_items; i++) { + char *dpath = ctx->rdirs.valsi; + if (gf_file_exists(dpath)) { + GF_Config *rules = gf_cfg_new(NULL, dpath); + u32 j, count = gf_cfg_get_section_count(rules); + for (j=0; j<count; j++) { + const char *dname = gf_cfg_get_section_name(rules, j); + if (!strcmp(dpath, "gmem")) { + if (has_gmem) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Only a single gmem directory can be specified, ignoring rule\n")); + continue; + } + has_gmem = GF_TRUE; + } + //we allow for non-existing directory names for virtual services + + const char *fnames = gf_cfg_get_key(rules, dname, "filters"); + if (fnames && strcmp(fnames, "*") && strcmp(fnames, "all")) { + if (!strstr(fnames, "httpout")) continue; + } + HTTP_DIRInfo *di; + GF_SAFEALLOC(di, HTTP_DIRInfo); + di->path = gf_strdup(dname); + gf_list_add(ctx->directories, di); + di->name = (char*)gf_cfg_get_key(rules, dname, "name"); + if (di->name) { + di->name = gf_strdup(di->name); + di->name_len = (u32) strlen(di->name); + if (di->namedi->name_len-1 != '/') { + gf_dynstrcat(&di->name, "/", NULL); + di->name_len += 1; + } + } + di->ru = (char*)gf_cfg_get_key(rules, dname, "ru"); + if (di->ru) di->ru = gf_strdup(di->ru); + di->rg = (char*)gf_cfg_get_key(rules, dname, "rg"); + if (di->rg) di->rg = gf_strdup(di->rg); + di->wu = (char*)gf_cfg_get_key(rules, dname, "wu"); + if (di->wu) di->wu = gf_strdup(di->wu); + di->wg = (char*)gf_cfg_get_key(rules, dname, "wg"); + if (di->wg) di->wg = gf_strdup(di->wg); + + if (di->wu || di->wg) ctx->has_write_dir = GF_TRUE; + ctx->has_read_dir = GF_TRUE; + } + gf_cfg_del(rules); + } else { + HTTP_DIRInfo *di; + if (!strcmp(dpath, "gmem")) { + if (has_gmem) continue; + has_gmem = GF_TRUE; + } else if (!gf_dir_exists(dpath)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut No such directory \"%s\": creating\n", dpath)); + GF_Err err = gf_mkdir(dpath); + if (err) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Failed to create directory \"%s\", ignoring rule\n", dpath)); + continue; + } + } + GF_SAFEALLOC(di, HTTP_DIRInfo); + di->path = gf_strdup(dpath); + gf_list_add(ctx->directories, di); + ctx->has_read_dir = GF_TRUE; + } + } + if (ctx->wdir) { + HTTP_DIRInfo *di; + GF_SAFEALLOC(di, HTTP_DIRInfo); + di->path = gf_strdup(ctx->wdir); + di->wu = gf_strdup("$ALL"); + gf_list_add(ctx->directories, di); + ctx->has_write_dir = GF_TRUE; + } + + u32 count = gf_list_count(ctx->directories); + for (i=0; i<count; i++) { + HTTP_DIRInfo *di = gf_list_get(ctx->directories, i); + u32 j, len = (u32) strlen(di->path); + for (j=0; j<count; j++) { + if (i==j) continue; + HTTP_DIRInfo *adi = gf_list_get(ctx->directories, j); + if (adi->is_subpath) continue; + u32 alen = (u32) strlen(adi->path); + if (alen>len) { + if (!strncmp(di->path, adi->path, len)) adi->is_subpath = GF_TRUE; + } else if (alen<len) { + if (!strncmp(adi->path, di->path, alen)) di->is_subpath = GF_TRUE; + } + } + } + + if (has_gmem) { + ctx->mem_fileio = gf_fileio_new("gmem", NULL, httpio_open, httpio_seek, httpio_read, httpio_write, httpio_tell, httpio_eof, NULL); + ctx->mem_url = gf_fileio_url(ctx->mem_fileio); + if (ctx->max_cache_segs==0) ctx->max_cache_segs = 1; + else if (ctx->max_cache_segs<0) ctx->max_cache_segs = -ctx->max_cache_segs; + + ctx->has_read_dir = GF_TRUE; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + httpio_eof(ctx->mem_fileio); + } +#endif + } +} + static GF_Err httpout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { const GF_PropertyValue *p; @@ -2391,7 +2733,9 @@ GF_HTTPOutCtx *ctx_orig; Bool patch_blocks = GF_FALSE; GF_FilterEvent evt; - const char *res_path; + const char *res_path; + + if (is_remove) return GF_OK; p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE); if (p && p->value.uint) { @@ -2410,7 +2754,7 @@ /*if PID was connected to an alias, get the alias context to get the destination Otherwise PID was directly connected to the main filter, use main filter destination*/ ctx_orig = (GF_HTTPOutCtx *) gf_filter_pid_get_alias_udta(pid); - if (!ctx_orig) ctx_orig = ctx; + if (!ctx_orig) ctx_orig = ctx; if (!ctx_orig->dst && (ctx->hmode==MODE_PUSH)) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Push output but no destination set !\n")); @@ -2453,33 +2797,45 @@ pctx->is_manifest = GF_TRUE; if (!ctx->hmode && !ctx->has_read_dir) { if (!ctx->reopen) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Using DASH/HLS in server mode with no directory set is meaningless\n")); - return GF_FILTER_NOT_SUPPORTED; + char szFNameGF_MAX_PATH; + const char *cache_dir = gf_get_default_cache_directory(); + sprintf(szFName, "%s%cgpac_%u_" LLU "_%u", cache_dir, GF_PATH_SEPARATOR, gf_sys_get_process_id(), gf_sys_clock_high_res(), gf_rand()); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut DASH/HLS in server mode with no directory set: defaulting to %s\n", szFName)); + + // create a fake rdir and propagate the change + ctx->rdirs.nb_items = 1; + ctx->rdirs.vals = gf_malloc(sizeof(char*)); + ctx->rdirs.vals0 = gf_strdup(szFName); + gf_filter_make_sticky(filter); + httpout_configure_directories(ctx); + ctx->single_mode = GF_FALSE; } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Using DASH/HLS in server mode with no directory set will result in unconsistent file states, use at your own risks\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Using DASH/HLS in server mode with no directory set will result in inconsistent file states, use at your own risks\n")); } } } - res_path = NULL; + res_path = NULL; if (ctx_orig->dst) { - res_path = ctx_orig->dst; - char *path = strstr(res_path, "://"); - if (path) path = strchr(path+3, '/'); - if (path) pctx->path = gf_strdup(path); - } else if (!ctx->dst) { - p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); - if (p && p->value.string) { - res_path = p->value.string; - pctx->path = gf_strdup(res_path); - } - } - if (res_path) { - + res_path = ctx_orig->dst; + char *path = strstr(res_path, "://"); + if (path) path = strchr(path+3, '/'); + if (path) pctx->path = gf_strdup(path); + } else if (!ctx->dst) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (p && p->value.string) { + res_path = p->value.string; + pctx->path = gf_strdup(res_path); + } + } + if (res_path) { if (ctx->hmode==MODE_PUSH) { GF_Err e; - u32 flags = GF_NETIO_SESSION_NOT_THREADED|GF_NETIO_SESSION_NOT_CACHED|GF_NETIO_SESSION_PERSISTENT; - if (!ctx->blockio) + u32 flags = GF_NETIO_SESSION_NOT_THREADED|GF_NETIO_SESSION_NOT_CACHED|GF_NETIO_SESSION_PERSISTENT|GF_NETIO_SESSION_SHARE_SOCKET; + if (!ctx->blockio) { flags |= GF_NETIO_SESSION_NO_BLOCK; + } else { + pctx->blockio = GF_TRUE; + } //note that ctx_orig->dst might be wrong (eg indicating MPD url rather than segment), but this is fixed in httpout_open_input by resetting up the session //with the correct URL @@ -2512,14 +2868,23 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); if (p && p->value.string) pctx->mime = gf_strdup(p->value.string); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_LLHAS_MODE); + pctx->llhas_mode = p ? p->value.uint : GF_LLHAS_NONE; + gf_filter_pid_set_udta(pid, pctx); gf_list_add(ctx->inputs, pctx); + //disable chunked transfer-encoding + pctx->use_cte = ctx->cte; + gf_filter_pid_init_play_event(pid, &evt, 0.0, 1.0, "HTTPOut"); gf_filter_pid_send_event(pid, &evt); - } if (is_remove) { + if (pctx) { + gf_list_del_item(ctx->inputs, pctx); + httpout_delete_input(ctx, pctx); + } return GF_OK; } @@ -2533,8 +2898,8 @@ GF_Err e = gf_sk_probe(sess->socket); if (e==GF_IP_CONNECTION_CLOSED) { sess->last_active_time = gf_sys_clock_high_res(); - sess->done = GF_TRUE; - sess->canceled = GF_FALSE; + httpout_mark_session_done(sess); + sess->req_end_type = SESS_END_OK; sess->upload_type = 0; GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Client %s disconnected, destroying session\n", sess->peer_address)); httpout_close_session(sess, e); @@ -2542,9 +2907,75 @@ } } -static void httpout_check_new_session(GF_HTTPOutCtx *ctx) +static Bool httpout_accept_connection(GF_HTTPOutCtx *ctx, const char *peer_address, u32 peer_port) +{ + //check max connections + if (ctx->maxc && (ctx->nb_connections>=ctx->maxc)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Connection rejected due to too many connections\n")); + return GF_FALSE; + } + if (ctx->maxp) { + u32 i, nb_conn=0, count = gf_list_count(ctx->sessions); + for (i=0; i<count; i++) { + GF_HTTPOutSession *sess = gf_list_get(ctx->sessions, i); + if (strcmp(sess->peer_address, peer_address)) continue; + httpout_check_connection(sess); + if (sess->done) continue; + nb_conn++; + } + if (nb_conn>=ctx->maxp) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Connection rejected due to too many connections from peer %s (%d vs max %d)\n", peer_address, nb_conn, ctx->maxp)); + return GF_FALSE; + } + } + return GF_TRUE; +} + + +#ifdef GPAC_HAS_NGTCP2 +static Bool on_h3_accept(void *udta, GF_DownloadSession *http_sess, const char *address, u32 port) +{ + GF_HTTPOutSession *sess; + GF_HTTPOutCtx *ctx = udta; + if (!httpout_accept_connection(ctx, address, port)) + return GF_FALSE; + + GF_SAFEALLOC(sess, GF_HTTPOutSession); + if (!sess) return GF_FALSE; +#ifdef GPAC_HAS_QJS + sess->obj = JS_UNDEFINED; +#endif + //we keep track of the socket for sock group (un)register + sess->ctx = ctx; + sess->last_active_time = gf_sys_clock_high_res(); + sess->http_type = GF_SESS_TYPE_HTTP3; + sess->blockio = GF_FALSE; + + sess->http_sess = http_sess; + gf_dm_sess_set_callback(http_sess, httpout_sess_io, sess); + + gf_dm_sess_set_timeout(sess->http_sess, ctx->timeout); + ctx->nb_connections++; + if (ctx->quit) + ctx->had_connections = GF_TRUE; + + gf_list_add(ctx->sessions, sess); + gf_list_add(ctx->active_sessions, sess); + + strcpy(sess->peer_address, address); + sess->peer_port = port; + + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Accepting new QUIC connection from %s\n", sess->peer_address)); + //ask immediate reschedule + ctx->next_wake_us = 1; + return GF_TRUE; +} +#endif + +static void httpout_check_new_session(GF_HTTPOutCtx *ctx, GF_Socket *serversock) { char peer_addressGF_MAX_IP_NAME_LEN; + u32 peer_port; GF_HTTPOutSession *sess; GF_Err e; void *ssl_c; @@ -2554,7 +2985,11 @@ ssl_c = NULL; new_conn = NULL; - e = gf_sk_accept(ctx->server_sock, &new_conn); + if (serversock == ctx->server_sock_h3) { + return; + } else { + e = gf_sk_accept(serversock, &new_conn); + } if (e==GF_IP_NETWORK_EMPTY) return; @@ -2562,31 +2997,16 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Accept failure %s\n", gf_error_to_string(e) )); return; } - //check max connections - if (ctx->maxc && (ctx->nb_connections>=ctx->maxc)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Connection rejected due to too many connections\n")); + gf_sk_get_remote_address_port(new_conn, peer_address, &peer_port); + + if (!httpout_accept_connection(ctx, peer_address, peer_port)) { gf_sk_del(new_conn); return; } - gf_sk_get_remote_address(new_conn, peer_address); - if (ctx->maxp) { - u32 i, nb_conn=0, count = gf_list_count(ctx->sessions); - for (i=0; i<count; i++) { - sess = gf_list_get(ctx->sessions, i); - if (strcmp(sess->peer_address, peer_address)) continue; - httpout_check_connection(sess); - if (sess->done) continue; - nb_conn++; - } - if (nb_conn>=ctx->maxp) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Connection rejected due to too many connections from peer %s (%d vs max %d)\n", peer_address, nb_conn, ctx->maxp)); - gf_sk_del(new_conn); - return; - } - } + GF_SAFEALLOC(sess, GF_HTTPOutSession); if (!sess) { - gf_sk_del(new_conn); + if (new_conn) gf_sk_del(new_conn); return; } #ifdef GPAC_HAS_QJS @@ -2599,7 +3019,9 @@ sess->last_active_time = gf_sys_clock_high_res(); #ifdef GPAC_HAS_SSL - if (ctx->ssl_ctx) { + if (ctx->ssl_ctx && new_conn + && ((serversock == ctx->server_sock_alt) || !ctx->server_sock_alt) + ) { ssl_c = gf_ssl_new(ctx->ssl_ctx, new_conn, &e); if (e) { GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Failed to create TLS session from %s: %s\n", sess->peer_address, gf_error_to_string(e) )); @@ -2611,12 +3033,15 @@ #endif sess->http_sess = gf_dm_sess_new_server(gf_filter_get_download_manager(ctx->filter), new_conn, ssl_c, httpout_sess_io, sess, !ctx->blockio, &e); + if (!sess->http_sess) { - gf_sk_del(new_conn); + if (new_conn) gf_sk_del(new_conn); GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Failed to create HTTP server session from %s: %s\n", sess->peer_address, gf_error_to_string(e) )); gf_free(sess); return; } + sess->blockio = ctx->blockio; + gf_dm_sess_set_timeout(sess->http_sess, ctx->timeout); ctx->nb_connections++; if (ctx->quit) @@ -2629,8 +3054,9 @@ gf_sk_set_buffer_size(new_conn, GF_FALSE, ctx->block_size); gf_sk_set_buffer_size(new_conn, GF_TRUE, ctx->block_size); strcpy(sess->peer_address, peer_address); + sess->peer_port = peer_port; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Accepting new connection from %s\n", sess->peer_address)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Accepting new %sconnection from %s\n", ssl_c ? " TLS" : "", sess->peer_address)); //ask immediate reschedule ctx->next_wake_us = 1; @@ -2641,7 +3067,7 @@ { char szIP1024; GF_Err e; - u16 port; + u16 port, def_port; char *ip; const char *ext = NULL; char *sep, *url; @@ -2651,8 +3077,11 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Invalid custom headers, got odd number but expecting even number\n")); return GF_BAD_PARAM; } - - port = ctx->port; + if (!ctx->port.nb_items) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Invalid port specifier, at least one port shall be indicated\n")); + return GF_BAD_PARAM; + } + port = def_port = ctx->port.vals0; ip = ctx->ifce; url = ctx->dst; @@ -2674,7 +3103,7 @@ sep = strchr(szIP, ':'); if (sep) { port = atoi(sep+1); - if (!port) port = ctx->port; + if (!port) port = def_port; sep0 = 0; } if (strlen(szIP)) ip = szIP; @@ -2691,7 +3120,7 @@ } if (!ext && !ctx->mime) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut No extension provided nor mime type for output file %s, cannot infer format\nThis may result in invalid filter chain resolution", ctx->dst)); + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut No extension provided nor mime type for output %s, cannot infer format\nThis may result in invalid filter chain resolution", ctx->dst)); } else { //static cap, streamtype = file ctx->in_caps0.code = GF_PROP_PID_STREAM_TYPE; @@ -2718,117 +3147,9 @@ return GF_OK; } - //configure directories if (ctx->rdirs.nb_items || ctx->wdir) { gf_filter_make_sticky(filter); - - u32 i; - Bool has_gmem = GF_FALSE; - ctx->has_read_dir = GF_FALSE; - ctx->has_write_dir = GF_FALSE; - ctx->directories = gf_list_new(); - for (i=0; i<ctx->rdirs.nb_items; i++) { - char *dpath = ctx->rdirs.valsi; - if (gf_file_exists(dpath)) { - GF_Config *rules = gf_cfg_new(NULL, dpath); - u32 j, count = gf_cfg_get_section_count(rules); - for (j=0; j<count; j++) { - const char *dname = gf_cfg_get_section_name(rules, j); - if (!strcmp(dpath, "gmem")) { - if (has_gmem) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("HTTPOut Only a single gmem directory can be specified, ignoring rule\n")); - continue; - } - has_gmem = GF_TRUE; - } else if (!gf_dir_exists(dname)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("HTTPOut No such directory %s, ignoring rule\n", dname)); - continue; - } - const char *fnames = gf_cfg_get_key(rules, dname, "filters"); - if (fnames && strcmp(fnames, "*") && strcmp(fnames, "all")) { - if (!strstr(fnames, "httpout")) continue; - } - HTTP_DIRInfo *di; - GF_SAFEALLOC(di, HTTP_DIRInfo); - di->path = gf_strdup(dname); - gf_list_add(ctx->directories, di); - di->name = (char*)gf_cfg_get_key(rules, dname, "name"); - if (di->name) { - di->name = gf_strdup(di->name); - di->name_len = (u32) strlen(di->name); - if (di->namedi->name_len-1 != '/') { - gf_dynstrcat(&di->name, "/", NULL); - di->name_len += 1; - } - } - di->ru = (char*)gf_cfg_get_key(rules, dname, "ru"); - if (di->ru) di->ru = gf_strdup(di->ru); - di->rg = (char*)gf_cfg_get_key(rules, dname, "rg"); - if (di->rg) di->rg = gf_strdup(di->rg); - di->wu = (char*)gf_cfg_get_key(rules, dname, "wu"); - if (di->wu) di->wu = gf_strdup(di->wu); - di->wg = (char*)gf_cfg_get_key(rules, dname, "wg"); - if (di->wg) di->wg = gf_strdup(di->wg); - - if (di->wu || di->wg) ctx->has_write_dir = GF_TRUE; - ctx->has_read_dir = GF_TRUE; - } - gf_cfg_del(rules); - } else if (gf_dir_exists(dpath) || !strcmp(dpath, "gmem")) { - HTTP_DIRInfo *di; - if (!strcmp(dpath, "gmem")) { - if (has_gmem) continue; - has_gmem = GF_TRUE; - } - GF_SAFEALLOC(di, HTTP_DIRInfo); - di->path = gf_strdup(dpath); - gf_list_add(ctx->directories, di); - ctx->has_read_dir = GF_TRUE; - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("HTTPOut No such directory %s, ignoring rule\n", dpath)); - } - } - if (ctx->wdir) { - HTTP_DIRInfo *di; - GF_SAFEALLOC(di, HTTP_DIRInfo); - di->path = gf_strdup(ctx->wdir); - di->wu = gf_strdup("$ALL"); - gf_list_add(ctx->directories, di); - ctx->has_write_dir = GF_TRUE; - } - - u32 count = gf_list_count(ctx->directories); - for (i=0; i<count; i++) { - HTTP_DIRInfo *di = gf_list_get(ctx->directories, i); - u32 j, len = (u32) strlen(di->path); - for (j=0; j<count; j++) { - if (i==j) continue; - HTTP_DIRInfo *adi = gf_list_get(ctx->directories, j); - if (adi->is_subpath) continue; - u32 alen = (u32) strlen(adi->path); - if (alen>len) { - if (!strncmp(di->path, adi->path, len)) adi->is_subpath = GF_TRUE; - } else if (alen<len) { - if (!strncmp(adi->path, di->path, alen)) di->is_subpath = GF_TRUE; - } - } - } - - if (has_gmem) { - ctx->mem_fileio = gf_fileio_new("gmem", NULL, httpio_open, httpio_seek, httpio_read, httpio_write, httpio_tell, httpio_eof, NULL); - ctx->mem_url = gf_fileio_url(ctx->mem_fileio); - if (ctx->max_cache_segs==0) ctx->max_cache_segs = 1; - else if (ctx->max_cache_segs<0) ctx->max_cache_segs = -ctx->max_cache_segs; - - ctx->has_read_dir = GF_TRUE; - -#ifdef GPAC_ENABLE_COVERAGE - if (gf_sys_is_cov_mode()) { - httpio_eof(ctx->mem_fileio); - } -#endif - } - + httpout_configure_directories(ctx); } else if (ctx->hmode!=MODE_PUSH) { ctx->single_mode = GF_TRUE; } @@ -2859,7 +3180,7 @@ ctx->hold = GF_FALSE; return GF_OK; } - ctx->port = port; + ctx->port.vals0 = port; if (ctx->cert && !ctx->pkey) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut missing server private key file\n")); return GF_BAD_PARAM; @@ -2882,42 +3203,103 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to initialize OpenSSL library\n")); return GF_IO_ERR; } - ctx->ssl_ctx = gf_ssl_server_context_new(ctx->cert, ctx->pkey); + ctx->ssl_ctx = gf_ssl_server_context_new(ctx->cert, ctx->pkey, GF_FALSE); if (!ctx->ssl_ctx) return GF_IO_ERR; - if (!ctx->port) - ctx->port = 443; + if (!ctx->port.vals0) + ctx->port.vals0 = 443; #else GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut TLS key/certificate set but GPAC compiled without TLS support\n")); return GF_NOT_SUPPORTED; #endif + } else if (ctx->port.nb_items>1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Cannot use 2 ports if TLS is not enabled\n")); + return GF_BAD_PARAM; } - if (!ctx->port) - ctx->port = 80; + if (!ctx->port.vals0) + ctx->port.vals0 = 80; + + gf_filter_set_blocking(filter, ctx->blockio); - gf_filter_set_blocking(filter, GF_TRUE); //load DM if we are pushing (for rate limit) if (ctx->hmode==MODE_SOURCE) gf_filter_get_download_manager(ctx->filter); - ctx->server_sock = gf_sk_new_ex(GF_SOCK_TYPE_TCP, gf_filter_get_netcap_id(filter) ); - e = gf_sk_bind(ctx->server_sock, NULL, ctx->port, ip, 0, GF_SOCK_REUSE_PORT); - if (!e) e = gf_sk_listen(ctx->server_sock, ctx->maxc); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut failed to start server on port %d: %s\n", ctx->port, gf_error_to_string(e) )); - return e; + Bool use_tcp = GF_TRUE; +#ifdef GPAC_HAS_NGTCP2 + Bool use_h3 = GF_TRUE; + const char *opt = gf_opts_get_key("core", "h3"); + if (opt && !strcmp(opt, "no")) use_h3 = GF_FALSE; + else if (opt && !strcmp(opt, "only")) use_tcp = GF_FALSE; + if (!ctx->cert || !ctx->pkey) { + use_h3 = GF_FALSE; + if (!use_tcp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut HTTP3 only requested but key/certificate not specified\n")); + return GF_BAD_PARAM; + } } - gf_sk_group_register(ctx->sg, ctx->server_sock); - gf_sk_server_mode(ctx->server_sock, GF_TRUE); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Server running on port %d\n", ctx->port)); - if (ctx->reqlog) { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut Server running on port %d\n", ctx->port)); - if (strstr(ctx->reqlog, "REC")) - ctx->log_record = GF_TRUE; + if (use_h3) { + ctx->ssl_ctx_quic = gf_ssl_server_context_new(ctx->cert, ctx->pkey, GF_TRUE); + if (!ctx->ssl_ctx_quic) return GF_IO_ERR; + + u32 q_port = ctx->port.valsctx->port.nb_items-1; + if (q_port==80) port = 443; + e = gf_dm_quic_server_new(gf_filter_get_download_manager(filter), ctx->ssl_ctx_quic, &ctx->quic_sock, ip, q_port, gf_filter_get_netcap_id(filter), + on_h3_accept, + ctx); + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut failed to start QUIC server on port %u: %s\n", q_port, gf_error_to_string(e) )); + return e; + } + ctx->server_sock_h3 = gf_dm_quic_get_socket(ctx->quic_sock); + ctx->quic_port = q_port; + gf_sk_group_register(ctx->sg, ctx->server_sock_h3); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut QUIC Server running on port %u\n", q_port)); + if (ctx->reqlog) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut QUIC Server running on port %u\n", q_port)); + } } +#endif + + if (use_tcp) { + ctx->server_sock = gf_sk_new_ex(GF_SOCK_TYPE_TCP, gf_filter_get_netcap_id(filter) ); + e = gf_sk_bind(ctx->server_sock, NULL, ctx->port.vals0, ip, 0, GF_SOCK_REUSE_PORT); + if (!e) e = gf_sk_listen(ctx->server_sock, ctx->maxc); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut failed to start server on port %u: %s\n", ctx->port.vals0, gf_error_to_string(e) )); + return e; + } + gf_sk_group_register(ctx->sg, ctx->server_sock); + gf_sk_server_mode(ctx->server_sock, GF_TRUE); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Server running on port %u\n", ctx->port.vals0)); + if (ctx->reqlog) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut Server running on port %u\n", ctx->port.vals0)); + } + + if ((ctx->port.nb_items==2) && ctx->cert && ctx->pkey) { + ctx->server_sock_alt = gf_sk_new_ex(GF_SOCK_TYPE_TCP, gf_filter_get_netcap_id(filter) ); + e = gf_sk_bind(ctx->server_sock_alt, NULL, ctx->port.vals1, ip, 0, GF_SOCK_REUSE_PORT); + if (!e) e = gf_sk_listen(ctx->server_sock_alt, ctx->maxc); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut failed to start server on port %d: %s\n", ctx->port.vals1, gf_error_to_string(e) )); + return e; + } + gf_sk_group_register(ctx->sg, ctx->server_sock_alt); + gf_sk_server_mode(ctx->server_sock_alt, GF_TRUE); + + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Server running on secondary port %d\n", ctx->port.vals1)); + if (ctx->reqlog) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut Server running on secondary port %d\n", ctx->port.vals1)); + } + } + } + + if (ctx->reqlog && strstr(ctx->reqlog, "REC")) + ctx->log_record = GF_TRUE; #ifdef GPAC_HAS_QJS if (ctx->js) { @@ -2988,9 +3370,18 @@ if (s->buffer) gf_free(s->buffer); if (s->path) gf_free(s->path); if (s->mime) gf_free(s->mime); + if (s->req_url) gf_free(s->req_url); if (s->opid) gf_filter_pid_remove(s->opid); if (s->resource) gf_fclose(s->resource); if (s->ranges) gf_free(s->ranges); + +#ifdef GPAC_HAS_QJS + if (!JS_IsUndefined(s->obj)) { + JS_SetOpaque(s->obj, NULL); + s->obj = JS_UNDEFINED; + } +#endif + gf_free(s); } @@ -3013,14 +3404,15 @@ } } -static void httpout_close_hls_chunk(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, Bool final_flush) +static void httpout_close_llhas_part(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, Bool final_flush) { - if (!in->hls_chunk) return; + if (!in->llhas_part) return; - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("HTTPOut Closing LL-HLS %s output\n", in->hls_chunk_path)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("HTTPOut Closing output %s\n", in->llhas_part_path)); - gf_fclose(in->hls_chunk); - in->hls_chunk = NULL; + gf_fclose(in->llhas_part); + in->llhas_part = NULL; + in->llhas_is_open = GF_FALSE; if (!final_flush) { u32 i, count; @@ -3029,7 +3421,7 @@ for (i=0; i<count; i++) { GF_HTTPOutSession *sess = gf_list_get(ctx->sessions, i); if (sess->in_source != in) continue; - if (!sess->in_source_is_ll_hls_chunk) continue; + if (!sess->in_source_is_llhas_part) continue; if (strcmp(sess->path, in->local_path)) continue; gf_assert(sess->file_in_progress); @@ -3042,19 +3434,62 @@ sess->resource = gf_fopen(sess->path, "rb"); } } - sess->in_source_is_ll_hls_chunk = GF_FALSE; + sess->in_source_is_llhas_part = GF_FALSE; sess->file_size = gf_fsize(sess->resource); gf_fseek(sess->resource, sess->file_pos, SEEK_SET); sess->file_in_progress = GF_FALSE; } } - if (in->hls_chunk_path) gf_free(in->hls_chunk_path); - in->hls_chunk_path = NULL; - if (in->hls_chunk_local_path) gf_free(in->hls_chunk_local_path); - in->hls_chunk_local_path = NULL; + if (in->llhas_part_path) gf_free(in->llhas_part_path); + in->llhas_part_path = NULL; + if (in->llhas_part_local_path) gf_free(in->llhas_part_local_path); + in->llhas_part_local_path = NULL; } +static void httpout_delete_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) +{ + if (in->local_path) gf_free(in->local_path); + if (in->path) gf_free(in->path); + if (in->mime) gf_free(in->mime); + + httpout_close_llhas_part(ctx, in, GF_TRUE); + + if (in->resource) gf_fclose(in->resource); + if (in->llhas_upload) gf_dm_sess_del(in->llhas_upload); + if (in->llhas_url) gf_free(in->llhas_url); + if (in->upload) { + if (in->upload_sock) + gf_sk_group_unregister(ctx->sg, in->upload_sock); + gf_dm_sess_del(in->upload); + } + if (in->file_deletes) { + while (gf_list_count(in->file_deletes)) { + char *url = gf_list_pop_back(in->file_deletes); + gf_free(url); + } + gf_list_del(in->file_deletes); + } + if (in->mem_files) { + while (gf_list_count(in->mem_files)) { + GF_HTTPFileIO *hio = gf_list_pop_back(in->mem_files); + httpio_del(hio); + } + gf_list_del(in->mem_files); + } + if (in->past_files) { + while (gf_list_count(in->past_files)) { + char *url = gf_list_pop_back(in->past_files); + gf_free(url); + } + gf_list_del(in->past_files); + } + if (in->no_cte_llhas_cache) gf_filter_pck_discard(in->no_cte_llhas_cache); + if (in->no_cte_cache) gf_filter_pck_discard(in->no_cte_cache); + if (in->llhas_template) gf_free(in->llhas_template); + + gf_free(in); +} static void httpout_finalize(GF_Filter *filter) { @@ -3074,45 +3509,17 @@ while (gf_list_count(ctx->inputs)) { GF_HTTPOutInput *in = gf_list_pop_back(ctx->inputs); - if (in->local_path) gf_free(in->local_path); - if (in->path) gf_free(in->path); - if (in->mime) gf_free(in->mime); - - httpout_close_hls_chunk(ctx, in, GF_TRUE); - - if (in->resource) gf_fclose(in->resource); - if (in->llhls_upload) gf_dm_sess_del(in->llhls_upload); - if (in->llhls_url) gf_free(in->llhls_url); - if (in->upload) { - if (in->upload_sock) - gf_sk_group_unregister(ctx->sg, in->upload_sock); - gf_dm_sess_del(in->upload); - } - if (in->file_deletes) { - while (gf_list_count(in->file_deletes)) { - char *url = gf_list_pop_back(in->file_deletes); - gf_free(url); - } - gf_list_del(in->file_deletes); - } - if (in->mem_files) { - while (gf_list_count(in->mem_files)) { - GF_HTTPFileIO *hio = gf_list_pop_back(in->mem_files); - httpio_del(hio); - } - gf_list_del(in->mem_files); - } - if (in->past_files) { - while (gf_list_count(in->past_files)) { - char *url = gf_list_pop_back(in->past_files); - gf_free(url); - } - gf_list_del(in->past_files); - } - gf_free(in); + httpout_delete_input(ctx, in); } gf_list_del(ctx->inputs); if (ctx->server_sock) gf_sk_del(ctx->server_sock); + if (ctx->server_sock_alt) gf_sk_del(ctx->server_sock_alt); +#ifdef GPAC_HAS_NGTCP2 + if (ctx->quic_sock) { + gf_dm_quic_server_del(ctx->quic_sock); + } +#endif + if (ctx->sg) gf_sk_group_del(ctx->sg); if (ctx->ip) gf_free(ctx->ip); @@ -3121,6 +3528,11 @@ if (ctx->ssl_ctx) { gf_ssl_server_context_del(ctx->ssl_ctx); } +#ifdef GPAC_HAS_NGTCP2 + if (ctx->ssl_ctx_quic) { + gf_ssl_server_context_del(ctx->ssl_ctx_quic); + } +#endif #endif @@ -3169,11 +3581,14 @@ memcpy(buffer, data, size); gf_filter_pck_set_framing(pck, is_first, GF_FALSE); gf_filter_pck_send(pck); + sess->nb_bytes += size; + sess->file_pos += size; return GF_OK; } if (!sess->resource) { gf_fatal_assert(0); } + if (!sess->nb_ranges) { write = (u32) gf_fwrite(data, size, sess->resource); if (write != size) { @@ -3224,12 +3639,14 @@ static void log_request_done(GF_HTTPOutSession *sess) { if (sess->do_log!=1) return; - const char *sprefix = sess->is_h2 ? "H2 " : ""; + const char *sprefix = (sess->http_type==GF_SESS_TYPE_HTTP3) ? "H3" : (sess->http_type ? "H2 " : ""); - if (!sess->socket) { + if ((!sess->socket && (sess->http_type!=GF_SESS_TYPE_HTTP3)) + || (sess->req_end_type == SESS_END_CLOSE) + ) { GF_LOG(GF_LOG_WARNING, GF_LOG_ALL, ("HTTPOut %sREQ#"LLU" %s aborted!\n", sprefix, sess->req_id, get_method_name(sess->method_type))); - } else if (sess->canceled) { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut %sREQ#"LLU" %s canceled\n", sprefix, sess->req_id, get_method_name(sess->method_type))); + } else if (sess->req_end_type==SESS_END_CANCEL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ALL, ("HTTPOut %sREQ#"LLU" %s canceled\n", sprefix, sess->req_id, get_method_name(sess->method_type))); } else { char *unit = "bps"; u64 diff_us = (gf_sys_clock_high_res() - sess->req_start_time); @@ -3244,6 +3661,7 @@ } GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut %sREQ#"LLU" %s done: reply %d - "LLU" bytes in %d ms at %g %s\n", sprefix, sess->req_id, get_method_name(sess->method_type), sess->reply_code, sess->nb_bytes, (u32) (diff_us/1000), bps, unit)); } + sess->do_log = 2; } static void httpout_process_session(GF_Filter *filter, GF_HTTPOutCtx *ctx, GF_HTTPOutSession *sess) @@ -3288,7 +3706,7 @@ return; read = 0; - if (sess->canceled) { + if (sess->req_end_type) { e = GF_EOS; } else { e = gf_dm_sess_fetch_data(sess->http_sess, sess->buffer, ctx->block_size, &read); @@ -3304,6 +3722,22 @@ sess->last_active_time = gf_sys_clock_high_res(); //reschedule asap ctx->next_wake_us = 1; + + //if PUT/POST does not use chunk-transfer, monitor content length if set + if (!sess->use_chunk_transfer && sess->content_length) { + if (sess->nb_bytes==sess->content_length) + e = GF_EOS; + else if (sess->nb_bytes>sess->content_length) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Too many bytes for uploaded content %s: %u vs %u announced\n", sess->path ? sess->path : sess->req_url, sess->nb_bytes, sess->content_length)); + e = GF_REMOTE_SERVICE_ERROR; + } + } + if (ctx->maxs && (sess->nb_bytes > ctx->maxs)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Too many bytes for uploaded content %s: %u vs %u max\n", sess->path ? sess->path : sess->req_url, sess->nb_bytes, ctx->maxs)); + e = GF_REMOTE_SERVICE_ERROR; + } + + //we way be in end of stream if (e==GF_OK) return; @@ -3319,8 +3753,8 @@ return; } else if (e==GF_IP_CONNECTION_CLOSED) { sess->last_active_time = gf_sys_clock_high_res(); - sess->done = GF_TRUE; - sess->canceled = GF_FALSE; + httpout_mark_session_done(sess); + sess->req_end_type = SESS_END_OK; sess->upload_type = 0; httpout_close_session(sess, e); log_request_done(sess); @@ -3331,7 +3765,7 @@ GF_FilterPacket *pck = gf_filter_pck_new_alloc(sess->opid, 0, NULL); if (pck) { gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE); - if (sess->canceled) + if (sess->req_end_type) gf_filter_pck_set_corrupted(pck, GF_TRUE); gf_filter_pck_send(pck); @@ -3341,11 +3775,11 @@ if (sess->resource) gf_fclose(sess->resource); sess->resource = NULL; //for now we remove any canceled file - if (sess->canceled) + if (sess->req_end_type) gf_file_delete(sess->path); } - if (sess->canceled) { + if (sess->req_end_type) { log_request_done(sess); } else { char szDate200; @@ -3407,15 +3841,14 @@ } } - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Sending PUT response to %s - reply %d\n", sess->peer_address, sess->reply_code)); - - log_request_done(sess); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Sending PUT response to %s - reply %d\n", sess->peer_address, sess->reply_code)); gf_dm_sess_send_reply(sess->http_sess, sess->reply_code, NULL, 0, GF_TRUE); + //logging is done below } sess->last_active_time = gf_sys_clock_high_res(); - sess->canceled = GF_FALSE; + sess->req_end_type = SESS_END_OK; sess->upload_type = 0; sess->nb_consecutive_errors = 0; @@ -3432,7 +3865,7 @@ } } - httpout_sess_flush_close(sess, close_session); + httpout_sess_flush_close(sess, close_session, GF_FALSE); return; } @@ -3441,8 +3874,9 @@ if (!sess->headers_done) { //check we have something to read if not http2 //if http2, data might have been received on this session while processing another session - if (!sess->is_h2 && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_READ)) { - ctx->next_wake_us = 100; + if (!sess->http_type && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_READ)) { + //session is in progress, reschedule asap + if (!sess->done) ctx->next_wake_us = 1; return; } e = gf_dm_sess_process(sess->http_sess); @@ -3479,7 +3913,7 @@ if (!sess->http_sess) return; //H2 session, keep on processing inputs - if (sess->is_h2 + if (sess->http_type // && gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_READ) ) { gf_dm_sess_process(sess->http_sess); @@ -3489,12 +3923,12 @@ //associated input directly writes to session if (sess->in_source && !sess->in_source->resource) return; - if (sess->canceled) { + if (sess->req_end_type) { log_request_done(sess); goto session_done; } - if (!gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) { + if (sess->socket && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) { ctx->next_wake_us = 1; return; } @@ -3528,13 +3962,14 @@ resend: last_range=GF_FALSE; file_in_progress = sess->file_in_progress; + if (sess->put_in_progress==1) file_in_progress = GF_TRUE; to_read=0; //we have ranges if (sess->nb_ranges) { Bool range_done=GF_FALSE; //current range is done if ((sess->rangessess->range_idx.end>0) - && ((s64) sess->file_pos >= sess->rangessess->range_idx.end) + && ((s64) sess->file_pos > sess->rangessess->range_idx.end) ) { //load next range, seeking file if (sess->range_idx+1<sess->nb_ranges) { @@ -3566,8 +4001,8 @@ s32 nb_read = sess->cbk_read(sess->rt_udta, sess->buffer, sess->ctx->block_size); if (nb_read<0) { - ctx->next_wake_us = 1; - sess->last_active_time = gf_sys_clock_high_res(); + ctx->next_wake_us = 1000; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut sess %s no data\n", sess->path ? sess->path : sess->req_url)); return; } to_read = (u32) nb_read; @@ -3584,7 +4019,7 @@ to_read = (u64) sess->ctx->block_size; if (sess->comp_data) { - memcpy(sess->buffer, sess->comp_data+sess->file_pos, to_read); + memcpy(sess->buffer, sess->comp_data+(u32)sess->file_pos, (u32) to_read); read = (u32) to_read; } else if (sess->resource) { @@ -3598,7 +4033,7 @@ read = (u32) to_read; } //transfer of file being uploaded, use chunk transfer - if (!sess->is_h2 && sess->use_chunk_transfer) { + if (!sess->http_type && sess->use_chunk_transfer) { char szHdr100; u32 len; sprintf(szHdr, "%X\r\n", read); @@ -3617,29 +4052,36 @@ if (e) { if ((e==GF_IP_CONNECTION_CLOSED) || (e==GF_URL_REMOVED)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Connection to %s for %s closed\n", sess->peer_address, sess->path)); - sess->done = GF_TRUE; - sess->canceled = GF_FALSE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Connection to %s for %s closed\n", sess->peer_address, sess->path ? sess->path : sess->req_url)); + httpout_mark_session_done(sess); + sess->req_end_type = SESS_END_CLOSE; httpout_close_session(sess, e); log_request_done(sess); return; } - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Error sending data to %s for %s: %s\n", sess->peer_address, sess->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Error sending data to %s for %s: %s\n", sess->peer_address, sess->path ? sess->path : sess->req_url, gf_error_to_string(e) )); } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut sending data to %s for %s: "LLU"/"LLU" bytes\n", sess->peer_address, sess->path, sess->nb_bytes, sess->bytes_in_req)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut sending data to %s for %s: "LLU"/"LLU" bytes\n", sess->peer_address, sess->path ? sess->path : sess->req_url, sess->nb_bytes, sess->bytes_in_req)); //not in progress and we are done, notify (for chunk-transfer or h2) right away - if (!file_in_progress && last_range && (remain==read)) + if (!file_in_progress && last_range && (remain==read)) { + if (sess->http_type && (gf_dm_sess_flush_async(sess->http_sess, GF_FALSE)!=GF_OK)) { + sess->last_active_time = gf_sys_clock_high_res(); + ctx->next_wake_us = 1; + return; + } goto session_done; + } if (gf_dm_sess_flush_async(sess->http_sess, GF_FALSE)==GF_OK) { goto resend; } } + ctx->next_wake_us=1; return; } //file not done yet ... - if (file_in_progress || (sess->put_in_progress==1)) { + if (file_in_progress) { sess->last_active_time = gf_sys_clock_high_res(); return; } @@ -3657,10 +4099,15 @@ close_session = GF_TRUE; if (!sess->done) { - if (!sess->is_h2 && sess->use_chunk_transfer) { + if (!sess->http_type && sess->use_chunk_transfer) { gf_dm_sess_send(sess->http_sess, "0\r\n\r\n", 5); } else { gf_dm_sess_send(sess->http_sess, NULL, 0); + + if (gf_dm_sess_flush_async(sess->http_sess, GF_FALSE)==GF_IP_NETWORK_EMPTY) { + sess->last_active_time = gf_sys_clock_high_res(); + return; + } } if (sess->resource) gf_fclose(sess->resource); sess->resource = NULL; @@ -3669,44 +4116,64 @@ sess->comp_data = NULL; if (sess->nb_bytes) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Done sending %s to %s ("LLU"/"LLU" bytes)\n", sess->path, sess->peer_address, sess->nb_bytes, sess->bytes_in_req)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Done sending %s to %s ("LLU"/"LLU" bytes)\n", sess->path ? sess->path : sess->req_url, sess->peer_address, sess->nb_bytes, sess->bytes_in_req)); } //keep resource active - sess->canceled = GF_FALSE; + sess->req_end_type = SESS_END_OK; - httpout_sess_flush_close(sess, close_session); + httpout_sess_flush_close(sess, close_session, GF_FALSE); return; } if (close_session) { - httpout_close_session(sess, sess->canceled ? GF_URL_REMOVED : GF_OK); - } - //might be NULL if quit was set - else if (sess->http_sess) { - sess->headers_done = GF_FALSE; - gf_dm_sess_server_reset(sess->http_sess); + httpout_close_session(sess, sess->req_end_type ? GF_URL_REMOVED : GF_OK); + } else { + if (sess->cbk_close) + sess->cbk_close(sess->rt_udta, GF_EOS); + + //might be NULL if quit was set + if (sess->http_sess) { + sess->headers_done = GF_FALSE; + gf_dm_sess_server_reset(sess->http_sess); + } } } -static Bool httpout_close_upload(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, Bool for_llhls) +static Bool httpout_close_upload(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, Bool for_llhas) { Bool res = GF_TRUE; - GF_Err e = gf_dm_sess_process(for_llhls ? in->llhls_upload : in->upload); + if (!for_llhas && in->skip_resource) { + //res was not opened + in->skip_resource = SKIP_RES_NO; + in->flush_open = GF_FALSE; + in->done = GF_TRUE; + in->is_open = GF_FALSE; + in->is_delete = GF_FALSE; + in->write_not_ready = GF_FALSE; + return GF_TRUE; + } + + GF_DownloadSession *sess = for_llhas ? in->llhas_upload : in->upload; + //flush async/h2 in case we have pending data + GF_Err e = gf_dm_sess_flush_close(sess); + if (!e) + e = gf_dm_sess_process(sess); if (e) { - if (!ctx->blockio && (e==GF_IP_NETWORK_EMPTY)) { + if (!in->blockio && (e==GF_IP_NETWORK_EMPTY)) { res = GF_FALSE; } else { GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Failed to close output %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) )); } } - if (for_llhls) in->flush_close_llhls = !res; + + if (for_llhas) in->flush_close_llhas = !res; else in->flush_close = !res; if (res) { - if (for_llhls) in->flush_llhls_open = GF_FALSE; + if (for_llhas) in->flush_llhas_open = GF_FALSE; else in->flush_open = GF_FALSE; } - if (!for_llhls && in->is_delete && res) { + if (!for_llhas && in->is_delete && res) { in->done = GF_TRUE; in->is_open = GF_FALSE; in->is_delete = GF_FALSE; @@ -3716,73 +4183,74 @@ } -static Bool httpout_open_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const char *name, Bool is_delete, Bool is_static) +static Bool httpout_open_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const char *name, Bool is_delete, Bool is_static, Bool is_fake, Bool check_no_open) { // Bool reassign_clients = GF_TRUE; u32 len = 0; - char *o_url = NULL; - const char *dir = NULL; - const char *sep; - - if (in->is_open && !is_delete) return GF_FALSE; - if (!in->upload) { - //single session mode, not recording, nothing to do - if (ctx->single_mode) { + char *o_url = NULL; + const char *dir = NULL; + const char *sep; + + if (in->is_open && !is_delete) return GF_FALSE; + if (!in->upload) { + //single session mode, not recording, nothing to do + if (ctx->single_mode) { + if (is_fake) return GF_FALSE; in->done = GF_FALSE; in->is_open = GF_TRUE; return GF_FALSE; } - //server mode not recording, nothing to do + //server mode not recording, nothing to do if (!ctx->rdirs.nb_items) return GF_FALSE; - //otherwise pickup first dir - this should be refined - HTTP_DIRInfo *di = gf_list_get(ctx->directories, 0); + //otherwise pickup first dir - this should be refined + HTTP_DIRInfo *di = gf_list_get(ctx->directories, 0); if (!di || !di->path) return GF_FALSE; len = (u32) strlen(di->path); if (!len) return GF_FALSE; dir = di->path; - if (in->resource) return GF_FALSE; - } + if (in->resource) return GF_FALSE; + } - sep = name ? strstr(name, "://") : NULL; - if (sep) sep = strchr(sep+3, '/'); + sep = name ? strstr(name, "://") : NULL; + if (sep) sep = strchr(sep+3, '/'); if (!sep) { - if (in->force_dst_name) { - sep = in->path; - } else if (ctx->dst) { - u32 i, count = gf_list_count(ctx->inputs); - for (i=0; i<count; i++) { - char *path_sep; - GF_HTTPOutInput *an_in = gf_list_get(ctx->inputs, i); - if (an_in==in) continue; - if (!an_in->path) continue; - if (ctx->dst && !an_in->force_dst_name) continue; - if (!gf_filter_pid_share_origin(in->ipid, an_in->ipid)) - continue; - - o_url = gf_strdup(an_in->path); - path_sep = strrchr(o_url, '/'); - if (path_sep) { - path_sep1 = 0; - if (name0=='/') + if (in->force_dst_name) { + sep = in->path; + } else if (ctx->dst) { + u32 i, count = gf_list_count(ctx->inputs); + for (i=0; i<count; i++) { + char *path_sep; + GF_HTTPOutInput *an_in = gf_list_get(ctx->inputs, i); + if (an_in==in) continue; + if (!an_in->path) continue; + if (ctx->dst && !an_in->force_dst_name) continue; + if (!gf_filter_pid_share_origin(in->ipid, an_in->ipid)) + continue; + + o_url = gf_strdup(an_in->path); + path_sep = strrchr(o_url, '/'); + if (path_sep) { + path_sep1 = 0; + if (name0=='/') gf_dynstrcat(&o_url, name+1, NULL); - else + else gf_dynstrcat(&o_url, name, NULL); - sep = o_url; - } else { - sep = name; - } - break; - } + sep = o_url; + } else { + sep = name; + } + break; + } } else { sep = name; } - } - //default to name - if (!sep && ctx->dst && in->path) { + } + //default to name + if (!sep && ctx->dst && in->path) { sep = name; - } - if (!sep) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("HTTPOut %s output file %s but cannot guess path !\n", is_delete ? "Deleting" : "Opening", name)); + } + if (!sep) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("HTTPOut %s output %s but cannot guess path !\n", is_delete ? "Deleting" : "Opening", name)); return GF_FALSE; } @@ -3794,49 +4262,70 @@ sep = o_url = new_url; } + if (!is_fake) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("HTTPOut %s output %s\n", is_delete ? "Deleting" : "Opening", sep+1)); + } - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("HTTPOut %s output file %s\n", is_delete ? "Deleting" : "Opening", sep+1)); if (in->upload) { GF_Err e; + char *orig_path = NULL; in->done = GF_FALSE; - in->is_open = GF_TRUE; in->is_delete = is_delete; + if (is_delete && !is_fake) + orig_path = gf_strdup(in->path); + if (!in->force_dst_name) { char *old = in->path; in->path = gf_strdup(sep); if (old) gf_free(old); } if (o_url) gf_free(o_url); + if (is_fake) return GF_TRUE; + in->is_open = GF_TRUE; + //only for CTE disabled in HTTP1 + if (check_no_open && (in->llhas_mode==GF_LLHAS_SUBSEG)) { + in->flush_open = GF_FALSE; + in->http_type = GF_SESS_TYPE_HTTP; + in->skip_resource = SKIP_RES_PUSH; + return GF_TRUE; + } if (in->upload_sock) { gf_sk_group_unregister(ctx->sg, in->upload_sock); in->upload_sock = NULL; } - e = gf_dm_sess_setup_from_url(in->upload, in->path, GF_TRUE); + e = gf_dm_sess_setup_from_url(in->upload, in->path, GF_FALSE); if (!e) { in->cur_header = 0; e = gf_dm_sess_process(in->upload); } - if (!ctx->blockio && (e==GF_IP_NETWORK_EMPTY)) { + if (!in->blockio && (e==GF_IP_NETWORK_EMPTY)) { in->flush_open = GF_TRUE; e = GF_OK; } if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s\n", in->path, gf_error_to_string(e) )); in->is_open = GF_FALSE; + if (orig_path) gf_free(orig_path); return GF_FALSE; } - in->is_h2 = gf_dm_sess_is_h2(in->upload); + in->http_type = gf_dm_sess_is_hmux(in->upload); + if (in->http_type == GF_SESS_TYPE_HTTP3) in->blockio = GF_FALSE; if (is_delete) { httpout_close_upload(ctx, in, GF_FALSE); + //restore path before delete for LLHAS setup + if (orig_path) { + if (in->path) gf_free(in->path); + in->path = orig_path; + } } return GF_TRUE; } - if (ctx->log_record) { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut %s output file %s\n", is_delete ? "Deleting" : "Opening", name)); + if (ctx->log_record && !is_fake) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut %s output %s\n", is_delete ? "Deleting" : "Opening", name)); } //file delete is async (the resource associated with the input can still be active) @@ -3873,21 +4362,28 @@ return GF_TRUE; } - in->done = GF_FALSE; - in->is_open = GF_TRUE; - - if (in->path && !strcmp(in->path, sep)) { // reassign_clients = GF_FALSE; } else { if (in->path) gf_free(in->path); in->path = gf_strdup(sep); } - if (o_url) gf_free(o_url); + if (o_url) gf_free(o_url); httpout_set_local_path(ctx, in); + if (is_fake) return GF_FALSE; + + in->done = GF_FALSE; + in->is_open = GF_TRUE; + in->skip_resource = 0; + if (check_no_open && (in->llhas_mode==GF_LLHAS_SUBSEG)) { + in->skip_resource = GF_TRUE; + in->resource = NULL; + return GF_TRUE; + } //for mem mode, pass the parent gfio for fileIO construction + gf_assert(in->resource == NULL); in->resource = gf_fopen_ex(in->local_path, ctx->mem_url, "wb", GF_FALSE); if (!in->resource) in->is_open = GF_FALSE; @@ -3937,7 +4433,7 @@ in->clock_first_error = gf_sys_clock(); } else if (gf_sys_clock() - in->clock_first_error > in->ctx->timeout*1000) { force_close = GF_TRUE; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s, aborting\n", in->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s, aborting\n", in->path, gf_error_to_string(e) )); } } @@ -3950,31 +4446,31 @@ } } -//for upload of LLHLS in seperate file mode only -static void httpout_close_input_llhls(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) +//for upload of LLHAS in separate file mode only +static void httpout_close_input_llhas(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) { GF_Err e; - if (!in->llhls_is_open) return; + if (!in->llhas_is_open || !in->llhas_upload) return; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTPOut Closing LL-HLS %s upload\n", in->llhls_url)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Closing LLHAS %s upload\n", in->llhas_url)); - in->llhls_is_open = GF_FALSE; + in->llhas_is_open = GF_FALSE; //close prev session - if (!in->is_h2) { - e = gf_dm_sess_send(in->llhls_upload, "0\r\n\r\n", 5); + if ((in->http_type==GF_SESS_TYPE_HTTP) && in->use_cte) { + e = gf_dm_sess_send(in->llhas_upload, "0\r\n\r\n", 5); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Error sending last chunk of LLHLS part %s: %s\n", in->llhls_url, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Error sending EOF of LLHAS part %s: %s\n", in->llhas_url, gf_error_to_string(e) )); } } //signal we're done sending the body - gf_dm_sess_send(in->llhls_upload, NULL, 0); + gf_dm_sess_send(in->llhas_upload, NULL, 0); httpout_close_upload(ctx, in, GF_TRUE); } static void httpout_close_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) { - httpout_close_input_llhls(ctx, in); + httpout_close_input_llhas(ctx, in); if (!in->is_open) return; in->is_open = GF_FALSE; in->done = GF_TRUE; @@ -3984,7 +4480,7 @@ if (in->upload) { GF_Err e; - if (!in->is_h2) { + if ((in->http_type==GF_SESS_TYPE_HTTP) && in->use_cte && !in->skip_resource) { e = gf_dm_sess_send(in->upload, "0\r\n\r\n", 5); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Error sending last chunk to %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) )); @@ -3995,7 +4491,8 @@ } } //signal we're done sending the body - gf_dm_sess_send(in->upload, NULL, 0); + if (!in->skip_resource) + gf_dm_sess_send(in->upload, NULL, 0); httpout_close_upload(ctx, in, GF_FALSE); @@ -4003,13 +4500,13 @@ u32 i, count; if (ctx->log_record) { - GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut Closing output file %s\n", in->local_path ? in->local_path : in->path)); + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("HTTPOut Closing output %s\n", in->local_path ? in->local_path : in->path)); } - if (in->resource) { + if (in->resource || in->skip_resource) { gf_assert(in->local_path); - //close all LL-HLS chunks before closing session - httpout_close_hls_chunk(ctx, in, GF_FALSE); + //close all LLHAS chunks before closing session + httpout_close_llhas_part(ctx, in, GF_FALSE); //detach all clients from this input and reassign to a regular output count = gf_list_count(ctx->sessions); @@ -4022,17 +4519,24 @@ httpout_check_mem_path(sess, sess->in_source); sess->in_source->nb_dest--; sess->in_source = NULL; - if (!sess->resource && sess->path) { + if (!sess->resource && sess->path && in->resource) { sess->resource = gf_fopen(sess->path, "rb"); } + //reset last modif time to avoid rematching the session when doing byte-range access + sess->last_file_modif = 0; } //get final size by forcing a seek - sess->file_size = gf_fsize(sess->resource); - gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + if (sess->resource) { + sess->file_size = gf_fsize(sess->resource); + gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + } sess->file_in_progress = GF_FALSE; } - gf_fclose(in->resource); - in->resource = NULL; + if (in->resource) { + gf_fclose(in->resource); + in->resource = NULL; + } + in->skip_resource = SKIP_RES_NO; } else { count = gf_list_count(ctx->active_sessions); for (i=0; i<count; i++) { @@ -4041,14 +4545,14 @@ if (sess->in_source != in) continue; //if we sent bytes, flush - otherwise session has just started if (sess->nb_bytes) { - if (!sess->is_h2) + if (!sess->http_type && in->use_cte) gf_dm_sess_send(sess->http_sess, "0\r\n\r\n", 5); //signal we're done sending the body gf_dm_sess_send(sess->http_sess, NULL, 0); //flush session - httpout_sess_flush_close(sess, GF_FALSE); + httpout_sess_flush_close(sess, GF_FALSE, GF_FALSE); } } } @@ -4058,40 +4562,38 @@ } -//for upload of LLHLS in seperate file mode only -static Bool httpout_open_input_llhls(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, char *dst) +//for upload of LLHAS in separate file mode only +static Bool httpout_open_input_llhas(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, char *dst) { - GF_Err e = gf_dm_sess_setup_from_url(in->llhls_upload, dst, GF_TRUE); + GF_Err e = gf_dm_sess_setup_from_url(in->llhas_upload, dst, GF_FALSE); if (!e) { - in->llhls_cur_header = 0; - e = gf_dm_sess_process(in->llhls_upload); + in->llhas_cur_header = 0; + e = gf_dm_sess_process(in->llhas_upload); } - if (!ctx->blockio && (e==GF_IP_NETWORK_EMPTY)) { - in->flush_llhls_open = GF_TRUE; + if (!in->blockio && (e==GF_IP_NETWORK_EMPTY)) { + in->flush_llhas_open = GF_TRUE; e = GF_OK; } if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) )); - in->llhls_is_open = GF_FALSE; + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s\n", in->path, gf_error_to_string(e) )); + in->llhas_is_open = GF_FALSE; return GF_FALSE; } - in->llhls_is_open = GF_TRUE; - if (in->llhls_url != dst) { - if (in->llhls_url) gf_free(in->llhls_url); - in->llhls_url = gf_strdup(dst); + in->llhas_is_open = GF_TRUE; + if (in->llhas_url != dst) { + if (in->llhas_url) gf_free(in->llhas_url); + in->llhas_url = gf_strdup(dst); } return GF_TRUE; } -u32 httpout_write_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const u8 *pck_data, u32 pck_size, Bool file_start) +u32 httpout_write_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const u8 *pck_data, u32 pck_size, Bool file_start, Bool no_llhas) { u32 out=0; - if (!in->is_open) return 0; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("HTTPOut Writing %d bytes to output file %s\n", pck_size, in->local_path ? in->local_path : in->path)); + if (!in->is_open && !in->llhas_is_open) return 0; if (in->upload) { char szChunkHdr100; @@ -4101,17 +4603,25 @@ u32 nb_retry = 0; out = pck_size; - if (!in->is_h2) { + if ((in->http_type==GF_SESS_TYPE_HTTP) && in->use_cte) { sprintf(szChunkHdr, "%X\r\n", pck_size); chunk_hdr_len = (u32) strlen(szChunkHdr); } - if (in->llhls_upload && in->llhls_is_open) + if (in->llhas_upload && in->llhas_is_open && !no_llhas) max_out = 2; for (s_idx=0; s_idx<max_out; s_idx++) { - GF_DownloadSession *up_sess = s_idx ? in->llhls_upload : in->upload; + GF_DownloadSession *up_sess = s_idx ? in->llhas_upload : in->upload; + if (!s_idx && !in->is_open) continue; + if (!s_idx && in->skip_resource) continue; + if (s_idx && !in->llhas_is_open) continue; + + const char *loc_path = s_idx ? in->llhas_url : (in->local_path ? in->local_path : in->path); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("HTTPOut Writing %u bytes to output %s\n", pck_size, loc_path)); + retry: - if (!in->is_h2) { + if ((in->http_type==GF_SESS_TYPE_HTTP) && in->use_cte) { e = gf_dm_sess_send(up_sess, szChunkHdr, chunk_hdr_len); e |= gf_dm_sess_send(up_sess, (u8 *) pck_data, pck_size); e |= gf_dm_sess_send(up_sess, "\r\n", 2); @@ -4124,22 +4634,22 @@ nb_retry++; //reopen if (s_idx) { - if (httpout_open_input_llhls(ctx, in, in->llhls_url)) { + if (httpout_open_input_llhas(ctx, in, in->llhas_url)) { //force sync - while (in->flush_llhls_open) { - e = gf_dm_sess_process(in->llhls_upload); + while (in->flush_llhas_open) { + e = gf_dm_sess_process(in->llhas_upload); if (e==GF_IP_NETWORK_EMPTY) { gf_sleep(1); continue; } - if (!e) in->flush_llhls_open = GF_FALSE; + if (!e) in->flush_llhas_open = GF_FALSE; break; } - if (!in->flush_llhls_open) + if (!in->flush_llhas_open) goto retry; } } else { - if (httpout_open_input(ctx, in, in->path, GF_FALSE, GF_FALSE)) { + if (httpout_open_input(ctx, in, in->path, GF_FALSE, GF_FALSE, GF_FALSE, GF_TRUE)) { //force sync while (in->flush_open) { e = gf_dm_sess_process(in->upload); @@ -4155,17 +4665,17 @@ } } } - if (s_idx) in->flush_llhls_open = GF_FALSE; + if (s_idx) in->flush_llhas_open = GF_FALSE; else in->flush_open = GF_FALSE; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Connection lost, aborting source %s\n", in->local_path ? in->local_path : in->path)); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Connection lost, aborting source %s\n", loc_path)); httpout_input_in_error(in, GF_IP_CONNECTION_CLOSED); httpout_close_input(ctx, in); return 0; } if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("HTTPOut Error writing to output file %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("HTTPOut Error writing to output %s: %s\n", loc_path, gf_error_to_string(e) )); out = 0; } } @@ -4175,17 +4685,23 @@ u32 chunk_hdr_len=0; u32 i, count = gf_list_count(ctx->active_sessions); - if (in->resource) { - out = (u32) gf_fwrite(pck_data, pck_size, in->resource); - gf_fflush(in->resource); + if (in->resource || in->llhas_part) { - if (in->hls_chunk) { - u32 wb = (u32) gf_fwrite(pck_data, pck_size, in->hls_chunk); + if (in->resource) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("HTTPOut Writing %d bytes to output %s\n", pck_size, in->local_path ? in->local_path : in->path)); + out = (u32) gf_fwrite(pck_data, pck_size, in->resource); + gf_fflush(in->resource); + } + if (in->llhas_part && !no_llhas) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("HTTPOut Writing %d bytes to output %s\n", pck_size, in->llhas_part_path)); + u32 wb = (u32) gf_fwrite(pck_data, pck_size, in->llhas_part); if (wb != pck_size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Write error for HLS chunk, wrote %d bytes but had %d to write\n", wb, pck_size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Write error for LLHAS chunk, wrote %d bytes but had %d to write\n", wb, pck_size)); out = 0; //to trigger IO err in process + } else if (!in->resource) { + out = pck_size; } - gf_fflush(in->hls_chunk); + gf_fflush(in->llhas_part); } } else { out = pck_size; @@ -4197,7 +4713,7 @@ if (sess->done) continue; if (sess->send_init_data && in->tunein_data_size && !sess->file_in_progress) { - if (!sess->is_h2) { + if (!sess->http_type && in->use_cte) { char szHdrInit100; sprintf(szHdrInit, "%X\r\n", in->tunein_data_size); u32 len_hdr = (u32) strlen(szHdrInit); @@ -4219,7 +4735,7 @@ /*source is not read from disk, write data*/ else { GF_Err e; - if (!sess->is_h2) { + if (!sess->http_type && in->use_cte) { if (!chunk_hdr_len) { sprintf(szChunkHdr, "%X\r\n", pck_size); chunk_hdr_len = (u32) strlen(szChunkHdr); @@ -4279,10 +4795,10 @@ if (sess->file_in_progress) continue; //direct stream write, check if ready - if (!gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) + if (sess->socket && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) continue; nb_ready++; - //data still pending, flush. If flush not full, check how many btes are still pending + //data still pending, flush. If flush not full, check how many bytes are still pending if (gf_dm_sess_flush_async(sess->http_sess, GF_TRUE)==GF_IP_NETWORK_EMPTY) { //decide what to do if one source is not reading fast enough u32 bytes_pending = gf_dm_sess_async_pending(sess->http_sess); @@ -4329,7 +4845,7 @@ if (!in->is_open && in->file_deletes && !in->flush_close) { while (gf_list_count(in->file_deletes)) { char *url = gf_list_pop_front(in->file_deletes); - httpout_open_input(ctx, in, url, GF_TRUE, GF_FALSE); + httpout_open_input(ctx, in, url, GF_TRUE, GF_FALSE, GF_FALSE, GF_FALSE); //URL may be queued for later delete, remove it if (in->past_files) { u32 i, count = gf_list_count(in->past_files); @@ -4351,6 +4867,19 @@ } } +static void httpout_input_drop(GF_HTTPOutInput *in, GF_FilterPacket *pck) +{ + if (pck==in->no_cte_llhas_cache) { + in->no_cte_llhas_cache_size = 0; + in->no_cte_flush_pending = GF_FALSE; + } else if (pck==in->no_cte_cache) { + in->no_cte_cache_size = 0; + in->no_cte_flush_pending = GF_FALSE; + } else { + gf_filter_pid_drop_packet(in->ipid); + } +} + static void httpout_process_inputs(GF_HTTPOutCtx *ctx) { Bool keep_alive=GF_FALSE; @@ -4360,6 +4889,10 @@ const GF_PropertyValue *p; const u8 *pck_data; u32 pck_size, nb_write; + Bool no_cte_flush; + Bool no_cte_fake_open; + Bool no_cte_no_llhas; + Bool no_cte_frag_push; GF_FilterPacket *pck; GF_HTTPOutInput *in = gf_list_get(ctx->inputs, i); @@ -4368,12 +4901,17 @@ next_pck: skip_start = GF_FALSE; - //if waiting for reply (llhls or regular), flush if possible, otherwise postpone - if (in->flush_close_llhls && !httpout_close_upload(ctx, in, GF_TRUE)) { + no_cte_flush=GF_FALSE; + no_cte_fake_open = GF_FALSE; + no_cte_no_llhas = GF_FALSE; + //if waiting for reply (llhas or regular), flush if possible, otherwise postpone + if (in->flush_close_llhas && !httpout_close_upload(ctx, in, GF_TRUE)) { + ctx->next_wake_us = 10; keep_alive=GF_TRUE; continue; } if (in->flush_close && !httpout_close_upload(ctx, in, GF_FALSE)) { + ctx->next_wake_us = 10; keep_alive=GF_TRUE; continue; } @@ -4386,34 +4924,119 @@ ctx->next_wake_us = 1; continue; } - if (gf_dm_sess_flush_async(in->llhls_upload, GF_TRUE) == GF_IP_NETWORK_EMPTY) { + if (gf_dm_sess_flush_async(in->llhas_upload, GF_TRUE) == GF_IP_NETWORK_EMPTY) { ctx->next_wake_us = 1; continue; } pck = gf_filter_pid_get_packet(in->ipid); - if (!pck) { - nb_nopck++; + if (!pck && (in->use_cte || !in->no_cte_flush_pending)) { //check end of PID state if (gf_filter_pid_is_eos(in->ipid) && !gf_filter_pid_is_flush_eos(in->ipid)) { - nb_eos++; - if (in->dash_mode && !in->seg_info_sent) { - httpin_send_seg_info(in); - } - httpout_close_input(ctx, in); + if (in->no_cte_cache_size || in->no_cte_llhas_cache_size) { + no_cte_flush = GF_TRUE; + } else { + nb_eos++; + if (in->dash_mode && !in->seg_info_sent) { + httpin_send_seg_info(in); + } + httpout_close_input(ctx, in); - if (in->flush_close || in->flush_close_llhls) - keep_alive = GF_TRUE; + if (in->flush_close || in->flush_close_llhas) + keep_alive = GF_TRUE; + } + } + if (!no_cte_flush) { + nb_nopck++; + ctx->next_wake_us = 1000; + //test mode, don't destroy too early + if (ctx->hold && gf_sys_is_test_mode() && (gf_list_count(ctx->sessions)==1)) + ctx->next_wake_us = 50000; + continue; } - ctx->next_wake_us = 100; - continue; } if (in->in_error) { continue; } + if (pck) { + gf_filter_pck_get_framing(pck, &start, &end); + } else { + start = no_cte_flush; + end = GF_FALSE; + } + + //CTE disabled, we need to reaggregate full packet on one hand and LLHAS fragments on the other + //in CTE mode, the input is open on first packet of segment, and if needed opened for LLHAS + //in non-CTE mode, we must push fragments before the segment. Therefore + //- we fake an open() on the segment to setup paths without opening / writing (variable no_cte_fake_open) + //- when flushing the segment (all fragments are written), we don't write anything on the LLHAS (variable no_cte_no_llhas) + no_cte_frag_push = GF_FALSE; + if (!in->use_cte) { + p = pck ? gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_FRAG_NUM) : NULL; + //new LLHAS fragment or EOS, flush previous + if ((p || no_cte_flush) && in->no_cte_llhas_cache_size) { + pck = in->no_cte_llhas_cache; + in->file_size = in->no_cte_llhas_cache_size; + ctx->next_wake_us = 1; + start = end = GF_FALSE; + no_cte_frag_push = GF_TRUE; + //we always push LLHAS frag first, so we will need to fake an open on the regular fragment to properly setup segment name + if (!in->llhas_is_open) { + Bool seg_start, seg_end; + gf_filter_pck_get_framing(in->no_cte_llhas_cache, &seg_start, &seg_end); + if (seg_start) + no_cte_fake_open = GF_TRUE; + } + } + //new file, flush previous file + else if (start && in->no_cte_cache_size) { + pck = in->no_cte_cache; + start = end = GF_TRUE; + in->file_size = in->no_cte_cache_size; + ctx->next_wake_us = 1; + //disable writing LLHAS data + no_cte_no_llhas = GF_TRUE; + } + else if (start && end) { + gf_filter_pck_get_data(pck, &in->file_size); + } else { + if (!pck) { + in->no_cte_flush_pending = GF_FALSE; + goto next_pck; + } + //start new packet + if (start) { + gf_assert(in->no_cte_cache_size==0); + in->no_cte_cache = gf_filter_pck_dangling_clone(pck, in->no_cte_cache); + gf_filter_pck_get_data(pck, &in->no_cte_cache_size); + } else { + u32 data_size; + u8 *new_range; + const u8 *data = gf_filter_pck_get_data(pck, &data_size); + gf_filter_pck_expand(in->no_cte_cache, data_size, NULL, &new_range, &in->no_cte_cache_size); + memcpy(new_range, data, data_size); + } + + //also aggregate LLHAS + if (p) { + gf_assert(in->no_cte_llhas_cache_size==0); + in->no_cte_llhas_cache = gf_filter_pck_dangling_clone(pck, in->no_cte_llhas_cache); + gf_filter_pck_get_data(pck, &in->no_cte_llhas_cache_size); + } else if (in->no_cte_llhas_cache_size) { + u32 data_size; + u8 *new_range; + const u8 *data = gf_filter_pck_get_data(pck, &data_size); + gf_filter_pck_expand(in->no_cte_llhas_cache, data_size, NULL, &new_range, &in->no_cte_llhas_cache_size); + memcpy(new_range, data, data_size); + } - gf_filter_pck_get_framing(pck, &start, &end); + gf_filter_pid_drop_packet(in->ipid); + ctx->next_wake_us = 1; + if (end) in->no_cte_flush_pending = GF_TRUE; + continue; + } + } if (in->dash_mode) { p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); @@ -4440,7 +5063,7 @@ if (e==GF_IP_NETWORK_EMPTY) continue; if (e) { httpout_input_in_error(in, e); - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s, retrying\n", in->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s, retrying\n", in->path, gf_error_to_string(e) )); in->is_open = GF_FALSE; //if ignoring error, consider the flush open is done if (!ctx->ka) continue; @@ -4452,19 +5075,20 @@ //disable start since this setup for this output is already done done skip_start = GF_TRUE; //in case we had an upgrade - if (!in->is_h2) - in->is_h2 = gf_dm_sess_is_h2(in->upload); + in->http_type = gf_dm_sess_is_hmux(in->upload); + if (in->http_type == GF_SESS_TYPE_HTTP3) in->blockio = GF_FALSE; } - //we are waiting for llhls open ack - this means that we already processed open for this packet (always before llhls), disable start - if (in->flush_llhls_open) { + //we are waiting for LLHAS open ack - this means that we already processed open for this packet (always before LLHAS), disable start + if (in->flush_llhas_open) { skip_start = GF_TRUE; } //last retry, we couldn't write but we could open the upload, skip start if (in->is_open && in->write_not_ready) skip_start = GF_TRUE; - if (start && !skip_start) { + if ((start && !skip_start) || no_cte_fake_open) { Bool is_static = in->is_manifest; + Bool is_init = GF_FALSE; const GF_PropertyValue *fnum, *fname; const char *name = NULL; fname = NULL; @@ -4489,13 +5113,21 @@ if (fname) in->force_dst_name = GF_FALSE; } - if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH); if (fname) name = fname->value.string; p = gf_filter_pck_get_property(pck, GF_PROP_PCK_INIT); - if (p && p->value.boolean) is_static = GF_TRUE; + if (p && p->value.boolean) { + is_static = GF_TRUE; + is_init = GF_TRUE; + } - p = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_REL); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_TEMPLATE); + if (p) { + if (in->llhas_template) gf_free(in->llhas_template); + in->llhas_template = gf_strdup(p->value.string); + } + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILE_REL); Bool use_rel = (p && p->value.boolean) ? GF_TRUE : GF_FALSE; char *dyn_name=NULL; @@ -4506,7 +5138,7 @@ if (!orig_ctx) orig_ctx = ctx; if (orig_ctx->dst_in && (orig_ctx->dst_in != in) ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Mutliple input PIDs with no file name set, broken graph, discarding input %s\n\tYou may retry by adding a new http output filter\n", gf_filter_pid_get_name(in->ipid) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Multiple input PIDs with no file name set, broken graph, discarding input %s\n\tYou may retry by adding a new http output filter\n", gf_filter_pid_get_name(in->ipid) )); httpout_input_in_error(in, GF_SERVICE_ERROR); continue; } else { @@ -4519,86 +5151,92 @@ dyn_name = gf_url_concatenate(ctx->dst, name); } - httpout_open_input(ctx, in, dyn_name ? dyn_name : name, GF_FALSE, is_static); + httpout_open_input(ctx, in, dyn_name ? dyn_name : name, GF_FALSE, is_static, no_cte_fake_open, !is_init); if (dyn_name) gf_free(dyn_name); - if (!in->is_open) { - httpout_input_in_error(in, GF_SERVICE_ERROR); - continue; - } + if (!no_cte_fake_open) { + if (!in->is_open) { + httpout_input_in_error(in, GF_SERVICE_ERROR); + continue; + } - if (!ctx->hmode && !ctx->has_read_dir && !in->nb_dest) { - if ((gf_filter_pck_get_dependency_flags(pck)==0xFF) && (gf_filter_pck_get_carousel_version(pck)==1)) { - pck_data = gf_filter_pck_get_data(pck, &pck_size); - if (pck_data) { - in->tunein_data_size = pck_size; - in->tunein_data = gf_realloc(in->tunein_data, pck_size); - memcpy(in->tunein_data, pck_data, pck_size); + if (!ctx->hmode && !ctx->has_read_dir && !in->nb_dest) { + if ((gf_filter_pck_get_dependency_flags(pck)==0xFF) && (gf_filter_pck_get_carousel_version(pck)==1)) { + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (pck_data) { + in->tunein_data_size = pck_size; + in->tunein_data = gf_realloc(in->tunein_data, pck_size); + memcpy(in->tunein_data, pck_data, pck_size); + } } } - } - if (in->flush_open) { - continue; + if (in->flush_open) { + continue; + } + //reset seg_info_sent only once we have acknowledged opening of the file + in->seg_info_sent = GF_FALSE; } - //reset seg_info_sent only once we have acknowledged opening of the file - in->seg_info_sent = GF_FALSE; } - p = gf_filter_pck_get_property(pck, GF_PROP_PCK_HLS_FRAG_NUM); - if (p && in->resource) { - char szHLSChunkGF_MAX_PATH; - snprintf(szHLSChunk, GF_MAX_PATH-1, "%s.%d", in->local_path, p->value.uint); - httpout_close_hls_chunk(ctx, in, GF_FALSE); + p = no_cte_no_llhas ? NULL : gf_filter_pck_get_property(pck, GF_PROP_PCK_LLHAS_FRAG_NUM); + if (p && in->local_path && (in->resource || (in->skip_resource==SKIP_RES_FILE) || no_cte_frag_push) ) { + char *llhas_chunkname = gf_mpd_resolve_subnumber(in->llhas_template, in->local_path, p->value.uint); + + httpout_close_llhas_part(ctx, in, GF_FALSE); + gf_assert(in->llhas_part == NULL); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("HTTPOut Opening output %s\n", llhas_chunkname)); //for mem mode, pass the parent gfio for fileIO construction - in->hls_chunk = gf_fopen_ex(szHLSChunk, ctx->mem_url, "wb", GF_FALSE); - in->hls_chunk_local_path = gf_strdup(szHLSChunk); - snprintf(szHLSChunk, GF_MAX_PATH-1, "%s.%d", in->path, p->value.uint); - in->hls_chunk_path = gf_strdup(szHLSChunk); + in->llhas_part = gf_fopen_ex(llhas_chunkname, ctx->mem_url, "wb", GF_FALSE); + in->llhas_part_local_path = llhas_chunkname; - if (ctx->mem_url && in->hls_chunk) { - GF_HTTPFileIO *hio = gf_fileio_get_udta((GF_FileIO *) in->hls_chunk); + llhas_chunkname = gf_mpd_resolve_subnumber(in->llhas_template, in->path, p->value.uint); + in->llhas_part_path = llhas_chunkname; + in->llhas_is_open = GF_TRUE; + + if (ctx->mem_url && in->llhas_part) { + GF_HTTPFileIO *hio = gf_fileio_get_udta((GF_FileIO *) in->llhas_part); hio->in = in; - hio->hls_ll_chunk = GF_TRUE; + hio->is_llhas_chunk = GF_TRUE; gf_list_add(in->mem_files, hio); } } else if (p && in->upload) { GF_Err e; - char szHLSChunkGF_MAX_PATH; - snprintf(szHLSChunk, GF_MAX_PATH-1, "%s.%d", in->path, p->value.uint); - if (!in->llhls_upload) { + if (!in->llhas_upload) { u32 flags = GF_NETIO_SESSION_NOT_THREADED|GF_NETIO_SESSION_NOT_CACHED|GF_NETIO_SESSION_PERSISTENT; - if (!ctx->blockio) + if (!in->blockio) flags |= GF_NETIO_SESSION_NO_BLOCK; - in->llhls_upload = gf_dm_sess_new(gf_filter_get_download_manager(ctx->filter), ctx->dst, flags, httpout_in_io_llhls, in, &e); + in->llhas_upload = gf_dm_sess_new(gf_filter_get_download_manager(ctx->filter), ctx->dst, flags, httpout_in_io_llhas, in, &e); - if (in->llhls_upload) { - gf_dm_sess_set_sock_group(in->llhls_upload, ctx->sg); - gf_dm_sess_set_timeout(in->llhls_upload, ctx->timeout); + if (in->llhas_upload) { + gf_dm_sess_set_sock_group(in->llhas_upload, ctx->sg); + gf_dm_sess_set_timeout(in->llhas_upload, ctx->timeout); } } - if (in->llhls_upload) { - if (in->flush_llhls_open) { - e = gf_dm_sess_process(in->llhls_upload); + if (in->llhas_upload) { + if (in->flush_llhas_open) { + e = gf_dm_sess_process(in->llhas_upload); if (e==GF_IP_NETWORK_EMPTY) { continue; } else if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) )); - in->llhls_is_open = GF_FALSE; + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s\n", in->path, gf_error_to_string(e) )); + in->llhas_is_open = GF_FALSE; continue; } - in->flush_llhls_open = GF_FALSE; + in->flush_llhas_open = GF_FALSE; } else { - //close llhls file - if cannot be done sync, abort (we will resume here at next call) - httpout_close_input_llhls(ctx, in); - if (in->flush_close_llhls) + //close llhas file - if cannot be done sync, abort (we will resume here at next call) + httpout_close_input_llhas(ctx, in); + if (in->flush_close_llhas) continue; - httpout_open_input_llhls(ctx, in, szHLSChunk); - if (in->flush_llhls_open) continue; + char *llhas_chunkname = gf_mpd_resolve_subnumber(in->llhas_template, in->path, p->value.uint); + httpout_open_input_llhas(ctx, in, llhas_chunkname); + gf_free(llhas_chunkname); + if (in->flush_llhas_open) continue; } } } @@ -4608,7 +5246,7 @@ if (end) { httpout_close_input(ctx, in); } - gf_filter_pid_drop_packet(in->ipid); + httpout_input_drop(in, pck); if (in->nb_write && ctx->quit) { httpout_input_in_error(in, GF_OK); nb_eos++; @@ -4625,7 +5263,7 @@ } pck_data = gf_filter_pck_get_data(pck, &pck_size); - if (in->upload || ctx->single_mode || in->resource) { + if (in->upload || ctx->single_mode || (in->resource || in->skip_resource || no_cte_frag_push) ) { GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck); if (pck_data && pck_size) { @@ -4644,7 +5282,7 @@ if (e==GF_IP_NETWORK_EMPTY) continue; in->flush_open = GF_FALSE; if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s\n", in->path, gf_error_to_string(e) )); in->is_open = GF_FALSE; continue; } @@ -4658,11 +5296,11 @@ in->write_start_range = bo; in->write_end_range = bo + pck_size - 1; //we muse use sync open here - httpout_open_input(ctx, in, in->path, GF_FALSE, GF_FALSE); + httpout_open_input(ctx, in, in->path, GF_FALSE, GF_FALSE, GF_FALSE, GF_FALSE); if (in->flush_open) continue; } - nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start); + nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start, no_cte_no_llhas); if (nb_write!=pck_size) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); } @@ -4680,17 +5318,17 @@ if (e==GF_IP_NETWORK_EMPTY) continue; in->flush_open = GF_FALSE; if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Failed to open output %s: %s\n", in->path, gf_error_to_string(e) )); in->is_open = GF_FALSE; continue; } } else { - httpout_open_input(ctx, in, in->path, GF_FALSE, GF_FALSE); + httpout_open_input(ctx, in, in->path, GF_FALSE, GF_FALSE, GF_FALSE, GF_FALSE); if (in->flush_open) continue; } } - nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start); + nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start, no_cte_no_llhas); if (nb_write!=pck_size) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); } @@ -4727,7 +5365,7 @@ lsize = stride; } for (j=0; j<write_h; j++) { - nb_write = (u32) httpout_write_input(ctx, in, out_ptr, lsize, start); + nb_write = (u32) httpout_write_input(ctx, in, out_ptr, lsize, start, no_cte_no_llhas); if (nb_write!=lsize) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut Write error, wrote %d bytes but had %d to write\n", nb_write, lsize)); } @@ -4742,15 +5380,15 @@ } ctx->next_wake_us = 1; } else if (pck_size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut output file handle is not opened, discarding %d bytes\n", pck_size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPOut output handle is not opened, discarding %d bytes\n", pck_size)); } packet_done: - gf_filter_pid_drop_packet(in->ipid); + httpout_input_drop(in, pck); if (end) { httpout_close_input(ctx, in); } - if (!ctx->blockio && in->upload && gf_dm_sess_async_pending(in->upload)) + if (!in->blockio && in->upload && gf_dm_sess_async_pending(in->upload)) continue; goto next_pck; @@ -4795,16 +5433,24 @@ ctx->next_wake_us = 50000; e = gf_sk_group_select(ctx->sg, 10, GF_SK_SELECT_BOTH); - if ((e==GF_OK) && ctx->server_sock) { + if ((e==GF_OK) && (ctx->server_sock||ctx->server_sock_h3)) { //server mode, check pending connections - if (gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock, GF_SK_SELECT_READ)) { - httpout_check_new_session(ctx); + if (ctx->server_sock && gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock, GF_SK_SELECT_READ)) { + httpout_check_new_session(ctx, ctx->server_sock); + } + if (ctx->server_sock_alt && gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock_alt, GF_SK_SELECT_READ)) { + httpout_check_new_session(ctx, ctx->server_sock_alt); } +#ifdef GPAC_HAS_NGTCP2 + if (ctx->server_sock_h3 && gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock_h3, GF_SK_SELECT_READ)) { + gf_dm_quic_process(ctx->quic_sock); + } +#endif count = gf_list_count(ctx->active_sessions); for (i=0; i<count; i++) { GF_HTTPOutSession *sess = gf_list_get(ctx->active_sessions, i); - if ((sess->flush_close && !httpout_sess_flush_close(sess, GF_FALSE)) + if ((sess->flush_close && !httpout_sess_flush_close(sess, GF_FALSE, GF_FALSE)) #ifdef GPAC_HAS_QJS || (sess->async_pending==1) #endif @@ -4849,10 +5495,14 @@ } else if ((e==GF_IP_NETWORK_EMPTY) && gf_list_count(ctx->active_sessions)) { ctx->next_wake_us = 1; } +#ifdef GPAC_HAS_NGTCP2 + if (gf_dm_quic_verify(ctx->quic_sock)==GF_OK) + ctx->next_wake_us = 1; +#endif httpout_process_inputs(ctx); - if (ctx->timeout && ctx->server_sock) { + if (ctx->timeout && (ctx->server_sock||ctx->server_sock_h3)) { u32 nb_active=0; count = gf_list_count(ctx->active_sessions); for (i=0; i<count; i++) { @@ -4862,7 +5512,7 @@ diff_sec = (u32) (gf_sys_clock_high_res() - sess->last_active_time)/1000000; if (diff_sec>ctx->timeout) { - GF_LOG(sess->done ? GF_LOG_INFO : GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Timeout for peer %s after %d sec, closing connection (last request %s)\n", sess->peer_address, diff_sec, sess->in_source ? sess->in_source->path : sess->path )); + GF_LOG(sess->done ? GF_LOG_INFO : GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Timeout for peer %s after %d sec, closing connection (last request %s)\n", sess->peer_address, diff_sec, sess->in_source ? sess->in_source->path : (sess->path ? sess->path : sess->req_url) )); httpout_close_session(sess, GF_IP_UDP_TIMEOUT); @@ -4955,7 +5605,7 @@ return GF_FALSE; } len = (u32) (sep - url); - if (!strncmp(ctx->dst, url, len)) return GF_TRUE; + if (ctx->dst && !strncmp(ctx->dst, url, len)) return GF_TRUE; return GF_FALSE; } @@ -4971,7 +5621,7 @@ static const GF_FilterArgs HTTPOutArgs = { { OFFS(dst), "location of destination resource", GF_PROP_NAME, NULL, NULL, 0}, - { OFFS(port), "server port", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(port), "server port", GF_PROP_UINT_LIST, "0", NULL, 0}, { OFFS(ifce), "default network interface to use", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(rdirs), "list of directories to expose for read", GF_PROP_STRING_LIST, NULL, NULL, 0}, { OFFS(wdir), "directory to expose for write", GF_PROP_STRING, NULL, NULL, 0}, @@ -5012,13 +5662,16 @@ { OFFS(js), "javascript logic for server", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, #endif { OFFS(zmax), "maximum uncompressed size allowed for gzip or deflate compression for text files (only enabled if client indicates it), 0 will disable compression", GF_PROP_UINT, "50000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cte), "use chunked transfer-encoding mode when possible", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(maxs), "maximum upload size allowed in bytes", GF_PROP_UINT, "50M", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(norange), "disable byte range support in GET (reply 200 on partial requests)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; GF_FilterRegister HTTPOutRegister = { .name = "httpout", - GF_FS_SET_DESCRIPTION("HTTP Server") + GF_FS_SET_DESCRIPTION("HTTP server") GF_FS_SET_HELP("The HTTP output filter can act as:\n" "- a simple HTTP server\n" @@ -5038,6 +5691,8 @@ "When multiple read directories are specified, the server root `/` contains the list of the mount points with their directory names.\n" "When a write directory is specified, the upload resource name identifies a file in this directory (the write directory name is not present in the URL).\n" " \n" + "Warning: files uploaded / created in the write directory are always created in non-atomic modes.\n" + " \n" "A directory rule file (cf `gpac -h creds`) can be specified in -rdirs() but NOT in -wdir(). When rules are used:\n" "- if a directory has a `name` rule, it will be used in the URL\n" "- otherwise, the directory is directly available under server root `/`\n" @@ -5046,13 +5701,18 @@ "EX name=bar\n" "Content `RES` of this directory is exposed as `http://SERVER/bar/RES`.\n" " \n" + "To authenticate services handled by bindings, use a non-existing directory and a name describing the authentication.\n" + "EX NonExistingDir\n" + "EX name=service_root\n" + "Requests in the form `http://SERVER/service_root/*` will be authenticated by this rule.\n" + " \n" "Listing can be enabled on server using -dlist().\n" "When disabled, a GET on a directory will fail.\n" "When enabled, a GET on a directory will return a simple HTML listing of the content inspired from Apache.\n" " \n" "Custom headers can be specified using -hdrs(), they apply to all requests. For more advanced control on requests, use a javascript binding (see -js() and howtos).\n" " \n" - "Text files are compressed using gzip or deflate if the client accepts these encodings, unless -no_z() is set.\n" + "Text files are compressed using gzip or deflate if the client accepts these encodings, unless -zmax() is set to 0.\n" " \n" "# Simple HTTP server\n" "In this mode, the filter does not need any input connection and exposes all files in the directories given by -rdirs().\n" @@ -5146,7 +5806,8 @@ .process = httpout_process, .process_event = httpout_process_event, .use_alias = httpout_use_alias, - .flags = GF_FS_REG_TEMP_INIT|GF_FS_REG_USE_SYNC_READ + .flags = GF_FS_REG_TEMP_INIT|GF_FS_REG_USE_SYNC_READ, + .hint_class_type = GF_FS_CLASS_NETWORK_IO }; @@ -5237,6 +5898,8 @@ nb_headers--; GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTPOut Not enough values in header list, truncating to %u\n", nb_headers)); } + sess->nb_bytes = 0; + sess->bytes_in_req = 0; if (nb_headers) { if (!sess->headers) sess->headers = gf_list_new(); u32 i; @@ -5244,6 +5907,9 @@ if (headersi && headersi+1) { gf_list_add(sess->headers, gf_strdup(headersi)); gf_list_add(sess->headers, gf_strdup(headersi+1)); + if (!stricmp(headersi, "Content-Length")) { + sess->bytes_in_req = atoi(headersi+1); + } } } }
View file
gpac-2.4.0.tar.gz/src/filters/out_pipe.c -> gpac-26.02.0.tar.gz/src/filters/out_pipe.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / generic pipe output filter @@ -213,12 +213,9 @@ if (p && p->value.string) { return pipeout_open_close(ctx, p->value.string, NULL, 0, explicit_overwrite); } else if (ctx->dynext) { - p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PCK_FILENUM); - if (!p) { - p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); - if (p && p->value.string) { - return pipeout_open_close(ctx, ctx->dst, p->value.string, 0, explicit_overwrite); - } + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string) { + return pipeout_open_close(ctx, ctx->dst, p->value.string, 0, explicit_overwrite); } } else { return pipeout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite); @@ -383,8 +380,6 @@ } //filename change at packet start, open new file if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); - if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH); - if (!fext) fext = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_EXT); if (fname) name = fname->value.string; if (end && gf_filter_pck_get_seek_flag(pck)) @@ -545,7 +540,7 @@ GF_FilterRegister PipeOutRegister = { .name = "pout", - GF_FS_SET_DESCRIPTION("pipe output") + GF_FS_SET_DESCRIPTION("Pipe output") GF_FS_SET_HELP("This filter handles generic output pipes (mono-directional) in blocking mode only.\n" "Warning: Output pipes do not currently support non blocking mode.\n" "The associated protocol scheme is `pipe://` when loaded as a generic output (e.g. -o `pipe://URL` where URL is a relative or absolute pipe name).\n" @@ -575,7 +570,8 @@ .finalize = pipeout_finalize, .configure_pid = pipeout_configure_pid, .process = pipeout_process, - .flags = GF_FS_REG_TEMP_INIT + .flags = GF_FS_REG_TEMP_INIT, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/out_route.c -> gpac-26.02.0.tar.gz/src/filters/out_route.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2024 + * Copyright (c) Telecom ParisTech 2020-2025 * All rights reserved * * This file is part of GPAC / ROUTE output filter @@ -32,48 +32,98 @@ #if !defined(GPAC_DISABLE_ROUTE) -enum -{ +GF_OPT_ENUM (DVBFluteChecksumMode, + DVB_CSUM_NO=0, + DVB_CSUM_META, + DVB_CSUM_ALL, +); + +GF_OPT_ENUM (LCTChannelSplitMode, LCT_SPLIT_NONE=0, LCT_SPLIT_TYPE, LCT_SPLIT_ALL, -}; + LCT_SPLIT_MCAST, +); typedef struct { + //options char *dst, *ext, *mime, *ifce, *ip; - u32 carousel, first_port, bsid, mtu, splitlct, ttl, brinc, runfor; - Bool korean, llmode, noreg, nozip; + u32 carousel, first_port, bsid, mtu, ttl, brinc, runfor; + LCTChannelSplitMode splitlct; + Bool korean, llmode, noreg, nozip, flute, use_inband, ssm; + DVBFluteChecksumMode csum; + u32 recv_obj_timeout; + //caps, overloaded at init GF_FilterCapability in_caps2; char szExt10; GF_List *services; + //set to true if all services are done + Bool done; - //ATSC3 - GF_Socket *sock_atsc_lls; - - u64 clock_init, clock, clock_stats; - u64 last_lls_clock; - - u8 *lls_time_table; - u32 lls_time_table_len; - - u8 *lls_slt_table; - u32 lls_slt_table_len; + //clock is sampled at each process() begin or before each LCT packet to be send + u64 clock_init, clock; - u64 bytes_sent; + //preallocated buffer for LCT packet formatting u8 *lct_buffer; + GF_BitStream *lct_bs; u64 reschedule_us; + //TOI for raw files u32 next_raw_file_toi; + //stats reporting + u64 clock_stats; Bool reporting_on; u64 total_size, total_bytes; Bool total_size_unknown; u32 nb_resources; + u64 bytes_sent; - Bool done; + char *ifce_ip; + const char *log_name; + + //ATSC3 + //main socket + GF_Socket *sock_atsc_lls; + //last LLS sent time + u64 last_lls_clock; + //time table + u8 *lls_time_table; + u32 lls_time_table_len; + //service table + u8 *lls_slt_table; + u32 lls_slt_table_len; + + + //DVB-MABR + //set to true if using DVB-MABR + Bool dvb_mabr; + u32 flute_msize; + //global service announcement + GF_Socket *sock_dvb_mabr; + u16 dvb_mabr_port; + u32 dvb_mabr_tsi; + + u64 last_dvb_mabr_clock; + //multicast gateway config + u8 *dvb_mabr_config; + u32 dvb_mabr_config_len; + u32 dvb_mabr_config_toi; + //FDT for mcast config + manifests + init segments + u8 *dvb_mabr_fdt; + u32 dvb_mabr_fdt_len; + u32 dvb_mabr_fdt_instance_id; + + u32 next_toi_avail; + Bool check_pending; + u32 check_init_clock; + + //simulate errors based on a 2-state Markov chain + Bool state_is_error; + GF_PropVec2 errsim; //{error->ok, ok->error} } GF_ROUTEOutCtx; typedef struct @@ -90,29 +140,56 @@ { u32 service_id; GF_List *pids; + //for raw media mode in dash mode, u32 dash_mode; + //LCT channels GF_List *rlcts; + //pointer to the main LCT channel ROUTELCT *rlct_base; - + //next port to use when spliting components over LCT channels + u32 next_port; Bool is_done; + //service needs to check input data before being ready Bool wait_for_inputs; + //service needs a reconfigure (mainest/init changed) + //for route: a new STSID needs to be regenerated + //for DVB MABR: a new global config needs to be regenerated + Bool needs_reconfig; + //set to FALSE if waiting for manifest of init segments, TRUE when everything is in place + Bool service_ready; + //time of service creation, used for setup timeout + u32 creation_time; + char *log_name; + //0: no HAS, 1: DASH, 2: HLS + u32 manifest_type; //storage for main manifest - all manifests (including HLS sub-playlists) are sent in the same PID //HLS sub-playlists are stored on their related PID to be pushed at each new seg char *manifest, *manifest_name, *manifest_mime, *manifest_server, *manifest_url; + char *service_base_uri; u32 manifest_version, manifest_crc; - Bool stsid_changed; - u32 stsid_version; - u32 first_port; + //TOI for manifest in FLUTE mode + u32 manifest_toi; + u32 manifest_server_port; + + //same for dual HLS/DASH, storage for alt manifest + char *manifest_alt, *manifest_alt_name, *manifest_alt_mime, *manifest_alt_server, *manifest_alt_url; + u32 manifest_alt_version, manifest_alt_crc; + u32 manifest_alt_toi; + u32 manifest_alt_server_port; + //for route + u32 stsid_version; u8 *stsid_bundle; u32 stsid_bundle_size; u32 stsid_bundle_toi; - u64 last_stsid_clock; - u32 manifest_type; - u32 creation_time; + + //service name for DVB MABR + char *service_name; + + Bool use_flute; } ROUTEService; typedef struct @@ -124,35 +201,63 @@ u32 tsi, bandwidth, stream_type; GF_Fraction dash_dur; - //we cannot hold a ref to the init segment packet, as this may lock the input waiting for its realease to dispatch next packets - u8 *init_seg_data; - u32 init_seg_size; - u32 init_seg_crc; - Bool no_init; - char *init_seg_name; - //0: not manifest, 1: MPD, 2: HLS u32 manifest_type; + //DASH template if any + char *template; + //raw file + Bool raw_file; + //template uses no '/' + Bool use_basename; + //set to true if no init seg for this format + Bool no_init_seg; Bool init_seg_sent; + //we cannot hold a ref to the init segment packet, as this may lock the input waiting for its release to dispatch next packets + u8 *init_seg_data; + u32 init_seg_size; + u32 init_seg_crc; + char *init_seg_name; - char *template; - + //HLS variant playlist - we cannot hold a ref to the HLS packet, as this may lock the input waiting fot its release char *hld_child_pl, *hld_child_pl_name; u32 hld_child_pl_version, hld_child_pl_crc; u64 hls_ref_id; Bool update_hls_child_pl; - u32 fmtp, mode; + //ROUTE code point + u32 fmtp; + //reference to current packet GF_FilterPacket *current_pck; + //matches dash startNumber for ROUTE, otherwise in 1,0xFFFF for flute u32 current_toi; const u8 *pck_data; u32 pck_size, pck_offset; - u64 res_size, offset_at_seg_start; char *seg_name; + //cumulated segment size in RAW dash (for event signaling) + u64 res_size; + //byte offset at seg start, for RAW dash + u64 offset_at_seg_start; + + //size of segment/file + u32 full_frame_size; + //cumulated size of chunks in segment for low latency mode + u32 cumulated_frag_size; + //byte offset of chunk in segment for low latency mode + u32 frag_offset; + //chunk index in segment + u32 frag_idx; + //set to TRUE when init sement must be pushed + Bool push_init; + //set to TRUE if last packet of segment - may trigger sending 0-bytes just to signal TOL in route or end of seg in flute + Bool force_send_empty; + + //scheduling info + u64 clock_at_frame_start, cts_us_at_frame_start, cts_at_frame_start; + u32 pck_dur_at_frame_start; u32 timescale; u64 clock_at_first_pck; u64 cts_first_pck; @@ -160,17 +265,35 @@ u64 current_dur_us; u64 carousel_time_us; u64 clock_at_pck; - Bool raw_file, use_basename; - - u32 full_frame_size, cumulated_frag_size, frag_offset; - u32 frag_idx; - Bool push_init, force_tol_send; - u64 clock_at_frame_start, cts_us_at_frame_start, cts_at_frame_start; - u32 pck_dur_at_frame_start; - + //target send rate u32 bitrate; + u64 last_init_push; + Bool use_time_tpl; + //for flute + u32 init_toi, hls_child_toi; + //set to true to force sending fragment name (flute LL mode) + Bool push_frag_name; + Bool init_cfg_done; + u32 fdt_instance_id; + s32 diff_send_at_frame_start, diff_recv_at_frame_start; } ROUTEPid; +static GF_Err routemx_setup_socket(GF_ROUTEOutCtx *ctx, GF_Socket *sock, const char *dst_ip, u32 dst_port) +{ + GF_Err e; + if (gf_sk_is_multicast_address(dst_ip)) { + e = gf_sk_setup_multicast(sock, dst_ip, dst_port, ctx->ttl, GF_FALSE, ctx->ifce); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to setup multicast %s:%u on %s interface\n", ctx->log_name, dst_ip, dst_port, ctx->ifce ? ctx->ifce : "default")); + } + } else { + e = gf_sk_bind(sock, ctx->ifce, dst_port, dst_ip, dst_port, GF_SOCK_REUSE_PORT|GF_SOCK_IS_SENDER); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to bind socket %s:%u on %s interface\n", ctx->log_name, dst_ip, dst_port, ctx->ifce ? ctx->ifce : "default")); + } + } + return e; +} ROUTELCT *route_create_lct_channel(GF_Filter *filter, GF_ROUTEOutCtx *ctx, const char *ip, u32 port, GF_Err *e) { @@ -191,7 +314,7 @@ if (rlct->ip) { rlct->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, gf_filter_get_netcap_id(filter) ); if (rlct->sock) { - *e = gf_sk_setup_multicast(rlct->sock, rlct->ip, rlct->port, ctx->ttl, GF_FALSE, ctx->ifce); + *e = routemx_setup_socket(ctx, rlct->sock, rlct->ip, rlct->port); if (*e) { gf_sk_del(rlct->sock); rlct->sock = NULL; @@ -207,7 +330,7 @@ return NULL; } -ROUTEService *routeout_create_service(GF_Filter *filter, GF_ROUTEOutCtx *ctx, u32 service_id, const char *ip, u32 port, GF_Err *e) +ROUTEService *routeout_create_service(GF_Filter *filter, GF_ROUTEOutCtx *ctx, u32 service_id, const char *service_name, const char *ip, u32 port, GF_Err *e) { ROUTEService *rserv; ROUTELCT *rlct = NULL; @@ -221,8 +344,9 @@ return NULL; } if (port) { - rserv->first_port = port+1; + rserv->next_port = port+1; } + if (ctx->dvb_mabr) rserv->use_flute = ctx->flute; rserv->pids = gf_list_new(); if (!rserv->pids) goto fail; @@ -233,6 +357,14 @@ rserv->rlct_base = rlct; rserv->service_id = service_id; + if (service_name) { + rserv->service_name = gf_strdup(service_name); + } else { + char szName100; + sprintf(szName, "gpac.io:dvb-mabr:service:%u", service_id); + rserv->service_name = gf_strdup(szName); + } + gf_list_add(ctx->services, rserv); if (ctx->lls_slt_table) { @@ -240,6 +372,14 @@ ctx->lls_slt_table = NULL; ctx->last_lls_clock = 0; } + //reset MABR config + if (ctx->dvb_mabr_config) { + gf_free(ctx->dvb_mabr_config); + ctx->dvb_mabr_config = NULL; + } + char logname1024; + sprintf(logname, "%s S%u", rserv->use_flute ? "DVB-FLUTE" : "ROUTE", service_id); + rserv->log_name = gf_strdup(logname); *e = GF_OK; return rserv; @@ -257,7 +397,7 @@ { if (!is_rem) { gf_list_del_item(rpid->route->pids, rpid); - rpid->route->stsid_changed = GF_TRUE; + rpid->route->needs_reconfig = GF_TRUE; } if (rpid->rlct != rpid->route->rlct_base) { gf_list_del_item(rpid->route->rlcts, rpid->rlct); @@ -298,9 +438,18 @@ if (serv->manifest_name) gf_free(serv->manifest_name); if (serv->manifest_server) gf_free(serv->manifest_server); if (serv->manifest_url) gf_free(serv->manifest_url); - if (serv->manifest) gf_free(serv->manifest); + + if (serv->manifest_alt_mime) gf_free(serv->manifest_alt_mime); + if (serv->manifest_alt_name) gf_free(serv->manifest_alt_name); + if (serv->manifest_alt_server) gf_free(serv->manifest_alt_server); + if (serv->manifest_alt_url) gf_free(serv->manifest_alt_url); + if (serv->manifest_alt) gf_free(serv->manifest_alt); + if (serv->service_base_uri) gf_free(serv->service_base_uri); + if (serv->stsid_bundle) gf_free(serv->stsid_bundle); + if (serv->service_name) gf_free(serv->service_name); + if (serv->log_name) gf_free(serv->log_name); gf_free(serv); } @@ -319,6 +468,7 @@ rpid = gf_filter_pid_get_udta(pid); if (is_remove) { if (rpid) routeout_remove_pid(rpid, GF_FALSE); + ctx->check_pending = GF_TRUE; return GF_OK; } if (! gf_filter_pid_check_caps(pid)) @@ -328,7 +478,7 @@ if (rpid) { //any change to a raw file will require reconfiguring S-TSID if (rpid->raw_file) { - rpid->route->stsid_changed = GF_TRUE; + rpid->route->needs_reconfig = GF_TRUE; } else if (!rpid->manifest_type) { p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TEMPLATE); @@ -336,7 +486,7 @@ char *sep1 = strstr(p->value.string, "$Number"); char *sep2 = strstr(p->value.string, "$Time"); if (sep1 && sep2) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE DASH Template is %s but ROUTE cannot use both Time and Number !\n", p->value.string)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s DASH Template is %s but ROUTE cannot use both Time and Number !\n", rpid->route->log_name, p->value.string)); rpid->route->is_done = GF_TRUE; return GF_BAD_PARAM; } @@ -344,13 +494,13 @@ if (!rpid->template || strcmp(rpid->template, p->value.string)) { if (rpid->template) gf_free(rpid->template); rpid->template = gf_strdup(p->value.string); - rpid->route->stsid_changed = GF_TRUE; + rpid->route->needs_reconfig = GF_TRUE; rpid->use_basename = (strchr(rpid->template, '/')==NULL) ? GF_TRUE : GF_FALSE; } } else if (rpid->template) { gf_free(rpid->template); rpid->template = NULL; - rpid->route->stsid_changed = GF_TRUE; + rpid->route->needs_reconfig = GF_TRUE; } } @@ -359,7 +509,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE); if (p && p->value.uint) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Delivering files with progressive download disabled is not possible in ROUTE !\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Delivering files with progressive download disabled is not possible in ROUTE !\n", ctx->log_name)); return GF_FILTER_NOT_SUPPORTED; } @@ -369,29 +519,35 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); if (p) service_id = p->value.uint; + manifest_type = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_IS_MANIFEST); + if (p) manifest_type = p->value.uint; + rserv = NULL; for (i=0; i<gf_list_count(ctx->services); i++) { rserv = gf_list_get(ctx->services, i); - if (service_id == rserv->service_id) break; + if (service_id == rserv->service_id) { + //throw warning if same manifest type is detected + if (rserv->manifest_type && (manifest_type==rserv->manifest_type)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Multiple manifests on same service - if not desired set `ServiceID` on sources !\n", ctx->log_name)); + } + break; + } rserv = NULL; } - manifest_type = 0; - p = gf_filter_pid_get_property(pid, GF_PROP_PID_IS_MANIFEST); - if (p) manifest_type = p->value.uint; - if (manifest_type) { p = gf_filter_pid_get_property(pid, GF_PROP_PID_PREMUX_STREAM_TYPE); if (!p || (p->value.uint!=GF_STREAM_FILE)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Manifest file detected but no dashin filter, file will be uploaded as is !\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Manifest file detected but no dashin filter, file will be uploaded as is !\n", ctx->log_name)); manifest_type = 0; } } if (manifest_type) { - if (manifest_type & 0x80000000) { - manifest_type &= 0x7FFFFFFF; + if (manifest_type & (1<<8)) { + manifest_type &= ~(1<<8); } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Manifest file describes a static session, clients tune-in will likely fail !\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Manifest file describes a static session, clients tune-in will likely fail !\n", ctx->log_name)); } } @@ -401,34 +557,36 @@ const char *service_ip = ctx->ip; //cannot have 2 manifest pids connecting in route mode - if (!ctx->sock_atsc_lls && gf_list_count(ctx->services) && manifest_type) { + if (!ctx->sock_atsc_lls && !ctx->dvb_mabr && gf_list_count(ctx->services) && manifest_type) { if (strchr(ctx->dst, '$')) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Multiple services in route mode, creating a new output filter\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Multiple services in route mode, creating a new output filter\n", ctx->log_name)); return GF_REQUIRES_NEW_INSTANCE; } - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Multiple services in route mode and no URL templating, cannot create new output\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Multiple services in route mode and no URL templating, cannot create new output\n", ctx->log_name)); return GF_FILTER_NOT_SUPPORTED; } - if (ctx->sock_atsc_lls) { - p = gf_filter_pid_get_property(pid, GF_PROP_PID_ROUTE_IP); + if (ctx->sock_atsc_lls || ctx->sock_dvb_mabr) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MCAST_IP); if (p && p->value.string) service_ip = p->value.string; - p = gf_filter_pid_get_property(pid, GF_PROP_PID_ROUTE_PORT); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MCAST_PORT); if (p && p->value.uint) port = p->value.uint; else ctx->first_port++; } - rserv = routeout_create_service(filter, ctx, service_id, service_ip, port, &e); + GF_PropertyEntry *pe=NULL; + p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_NAME, &pe); + rserv = routeout_create_service(filter, ctx, service_id, p ? p->value.string : NULL, service_ip, port, &e); + gf_filter_release_property(pe); if (!rserv) return e; rserv->dash_mode = pid_dash_mode; } - if (!rserv->manifest_type) - rserv->manifest_type = manifest_type; + rserv->manifest_type |= manifest_type; if (rserv->dash_mode != pid_dash_mode){ - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Mix of raw and muxed files should never happen - please report bug !\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Mix of raw and muxed files should never happen - please report bug !\n", rserv->log_name)); return GF_SERVICE_ERROR; } @@ -437,7 +595,6 @@ rpid->route = rserv; rpid->pid = pid; rpid->fmtp = 128; - rpid->mode = 1; rpid->tsi = 0; rpid->manifest_type = manifest_type; if (manifest_type) { @@ -446,7 +603,7 @@ } else { p = gf_filter_pid_get_property(pid, GF_PROP_PID_NO_INIT); if (p && p->value.boolean) { - rpid->no_init = GF_TRUE; + rpid->no_init_seg = GF_TRUE; } } @@ -455,8 +612,13 @@ rpid->rlct = rserv->rlct_base; + rpid->bitrate = 0; + rpid->bandwidth = 0; p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_BITRATE); - if (p) rpid->bitrate = p->value.uint * (100+ctx->brinc) / 100; + if (p) { + rpid->bitrate = p->value.uint * (100+ctx->brinc) / 100; + rpid->bandwidth = p->value.uint; + } rpid->stream_type = 0; p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_PREMUX_STREAM_TYPE); @@ -468,10 +630,6 @@ rpid->stream_type = p->value.uint; } - rpid->bandwidth = 0; - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_BITRATE); - if (p) rpid->bandwidth = p->value.uint; - rpid->dash_dur.num = 1; rpid->dash_dur.den = 1; p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_DASH_DUR); @@ -483,13 +641,13 @@ char *sep1 = strstr(p->value.string, "$Number"); char *sep2 = strstr(p->value.string, "$Time"); if (sep1 && sep2) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE DASH Template is %s but ROUTE cannot use both Time and Number !\n", p->value.string)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s DASH Template is %s but ROUTE cannot use both Time and Number !\n", rserv->log_name, p->value.string)); gf_filter_abort(filter); return GF_BAD_PARAM; } rpid->template = gf_strdup(p->value.string); } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Segment file PID detected but no template assigned, assuming raw file upload!\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Segment file PID detected but no template assigned, assuming raw file upload!\n", rserv->log_name)); rpid->raw_file = GF_TRUE; } } @@ -506,13 +664,13 @@ p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_TIMESCALE); if (p) rpid->timescale = p->value.uint; - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PCK_HLS_REF); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_HLS_REF); if (p) rpid->hls_ref_id = p->value.longuint; rpid->tsi = gf_list_find(rserv->pids, rpid) * 10; //do we put this on the main LCT of the service or do we split ? - if (ctx->splitlct==LCT_SPLIT_ALL) { + if (ctx->splitlct>=LCT_SPLIT_ALL) { if (gf_list_count(rserv->pids)>1) do_split = GF_TRUE; } @@ -533,19 +691,62 @@ } if (do_split) { GF_Err e; - rserv->first_port++; - ctx->first_port++; - rpid->rlct = route_create_lct_channel(filter, ctx, NULL, rserv->first_port, &e); + char *alloc_ip = NULL; + u32 port = rserv->next_port; + const char *ip = NULL; + if (ctx->splitlct==LCT_SPLIT_MCAST) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MCAST_IP); + if (p && p->value.string) ip = p->value.string; + if (ip && !strcmp(ip, rserv->rlct_base->ip)) + ip = NULL; + if (!ip) { + alloc_ip = gf_net_bump_ip_address(rserv->rlct_base->ip, gf_list_count(rserv->pids)-1); + ip = alloc_ip; + } + if (!ip) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to assign new IP address to PID, using service one\n", rserv->log_name)); + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MCAST_PORT); + if (p) port = p->value.uint; + } + } + rpid->rlct = route_create_lct_channel(filter, ctx, ip, port, &e); + if (alloc_ip) { + gf_free(alloc_ip); + } else if (!e) { + rserv->next_port++; + ctx->first_port++; + } + if (e) return e; if (rpid->rlct) { gf_list_add(rserv->rlcts, rpid->rlct); } } } - + //a new pid has appeared, we need to reset MABR manifest + if (ctx->dvb_mabr_config) { + gf_free(ctx->dvb_mabr_config); + ctx->dvb_mabr_config = NULL; + } + ctx->check_pending = GF_TRUE; gf_filter_pid_init_play_event(pid, &evt, 0, 1.0, "ROUTEOut"); gf_filter_pid_send_event(pid, &evt); + if (rpid->manifest_type) { + GF_FEVT_INIT(evt, GF_FEVT_NETWORK_HINT, pid); + evt.net_hint.sink_type = GF_4CC('M','A','B','R'); + gf_filter_pid_send_event(pid, &evt); + } + + if (ctx->llmode && !rpid->raw_file) { + rpid->carousel_time_us = ctx->carousel; + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MCAST_CAROUSEL); + if (p) { + //can be 0 + rpid->carousel_time_us = gf_timestamp_rescale(p->value.frac.num, p->value.frac.den, 1000000); + } + } rserv->wait_for_inputs = GF_TRUE; //low-latency mode, consume media segment files as they are received (don't wait for full segment reconstruction) @@ -561,16 +762,44 @@ { char *base_name; Bool is_atsc = GF_TRUE; + u32 proto_offset = 0; char *ext=NULL; GF_ROUTEOutCtx *ctx = (GF_ROUTEOutCtx *) gf_filter_get_udta(filter); if (!ctx || !ctx->dst) return GF_BAD_PARAM; + ctx->log_name = "ROUTE"; if (!strnicmp(ctx->dst, "route://", 8)) { is_atsc = GF_FALSE; + proto_offset = 8; + } else if (!strnicmp(ctx->dst, "mabr://", 7)) { + proto_offset = 7; + is_atsc = GF_FALSE; + ctx->dvb_mabr = GF_TRUE; + ctx->log_name = "DVB-FLUTE"; } else if (strnicmp(ctx->dst, "atsc://", 7)) { return GF_NOT_SUPPORTED; } + if (ctx->ssm) { + if (!ctx->ifce) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Cannot use source-specific multicast if `-ifce` is not set\n", ctx->log_name)); + return GF_BAD_PARAM; + } + } + if (ctx->ifce) { + char *v4, *v6; + gf_net_get_adapter_ip(ctx->ifce, &v4, &v6); + if (v4) { + ctx->ifce_ip = v4; + if (v6) gf_free(v6); + } else { + ctx->ifce_ip = v6; + } + if (!ctx->ifce_ip && ctx->ssm) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Cannot get IP address for %s\n", ctx->log_name, ctx->ifce)); + return GF_BAD_PARAM; + } + } if (ctx->ext) { ext = ctx->ext; @@ -578,7 +807,7 @@ if (is_atsc) { base_name = gf_file_basename(ctx->dst); } else { - char *sep = strchr(ctx->dst + 8, '/'); + char *sep = strchr(ctx->dst + proto_offset, '/'); base_name = sep ? gf_file_basename(ctx->dst) : NULL; } ext = base_name ? gf_file_ext_start(base_name) : NULL; @@ -587,7 +816,7 @@ #if 0 if (!ext) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing destination manifest type, cannot infer format!\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing destination manifest type, cannot infer format!\n", ctx->log_name)); return GF_BAD_PARAM; } #endif @@ -596,12 +825,12 @@ if (!ctx->ip) return GF_BAD_PARAM; } else { char *sep, *root; - sep = strrchr(ctx->dst+8, ':'); + sep = strrchr(ctx->dst+proto_offset, ':'); if (sep) sep0 = 0; root = sep ? strchr(sep+1, '/') : NULL; if (root) root0 = 0; if (ctx->ip) gf_free(ctx->ip); - ctx->ip = gf_strdup(ctx->dst+8); + ctx->ip = gf_strdup(ctx->dst + proto_offset); if (sep) { ctx->first_port = atoi(sep+1); sep0 = ':'; @@ -610,8 +839,7 @@ } if (!gf_sk_is_multicast_address(ctx->ip)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE IP %s is not a multicast address\n", ctx->ip)); - return GF_BAD_PARAM; + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s IP %s is not a multicast address\n", ctx->log_name, ctx->ip)); } if (ext || ctx->mime) { @@ -645,17 +873,34 @@ if (is_atsc) { ctx->sock_atsc_lls = gf_sk_new_ex(GF_SOCK_TYPE_UDP, gf_filter_get_netcap_id(filter) ); - gf_sk_setup_multicast(ctx->sock_atsc_lls, GF_ATSC_MCAST_ADDR, GF_ATSC_MCAST_PORT, 0, GF_FALSE, ctx->ifce); + GF_Err e = routemx_setup_socket(ctx, ctx->sock_atsc_lls, GF_ATSC_MCAST_ADDR, GF_ATSC_MCAST_PORT); + if (e) return e; + } + if (ctx->dvb_mabr) { + ctx->sock_dvb_mabr = gf_sk_new_ex(GF_SOCK_TYPE_UDP, gf_filter_get_netcap_id(filter) ); + GF_Err e = routemx_setup_socket(ctx, ctx->sock_dvb_mabr, ctx->ip, ctx->first_port); + if (e) return e; + ctx->dvb_mabr_port = ctx->first_port; + ctx->first_port++; + ctx->dvb_mabr_tsi = 1; } ctx->lct_buffer = gf_malloc(sizeof(u8) * ctx->mtu); ctx->clock_init = gf_sys_clock_high_res(); ctx->clock_stats = ctx->clock_init; + ctx->lct_bs = gf_bs_new(ctx->lct_buffer, ctx->mtu, GF_BITSTREAM_WRITE); + ctx->flute_msize = ctx->mtu - 14*4; //max size of headers and extensins if (!ctx->carousel) ctx->carousel = 1000; //move to microseconds ctx->carousel *= 1000; - ctx->next_raw_file_toi = 1; + ctx->next_raw_file_toi = ctx->dvb_mabr ? 0x7FFF : 1; + ctx->next_toi_avail = 1; + if (ctx->llmode && (ctx->csum == DVB_CSUM_ALL)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Low-latency mode is activated but `csum=all`set, using `csum=meta`\n", ctx->log_name)); + ctx->csum = DVB_CSUM_META; + } + ctx->check_pending = GF_TRUE; return GF_OK; } @@ -674,10 +919,16 @@ gf_list_del(ctx->services); if (ctx->sock_atsc_lls) gf_sk_del(ctx->sock_atsc_lls); + if (ctx->sock_dvb_mabr) + gf_sk_del(ctx->sock_dvb_mabr); if (ctx->lct_buffer) gf_free(ctx->lct_buffer); if (ctx->lls_slt_table) gf_free(ctx->lls_slt_table); if (ctx->lls_time_table) gf_free(ctx->lls_time_table); + if (ctx->dvb_mabr_config) gf_free(ctx->dvb_mabr_config); + if (ctx->dvb_mabr_fdt) gf_free(ctx->dvb_mabr_fdt); + if (ctx->ifce_ip) gf_free(ctx->ifce_ip); + if (ctx->lct_bs) gf_bs_del(ctx->lct_bs); } char *routeout_strip_base(ROUTEService *serv, char *url) @@ -702,16 +953,16 @@ return gf_strdup(url); } -#define MULTIPART_BOUNDARY "_GPAC_BOUNDARY_ROUTE_.67706163_" -#define ROUTE_INIT_TOI 0xFFFFFFFF +static GF_Err routeout_update_stsid_bundle(GF_ROUTEOutCtx *ctx, ROUTEService *serv, Bool manifest_updated); +static GF_Err routeout_update_dvb_mabr_fdt(GF_ROUTEOutCtx *ctx, ROUTEService *serv, Bool manifest_updated); + static GF_Err routeout_check_service_updates(GF_ROUTEOutCtx *ctx, ROUTEService *serv) { - u32 i, j, count; - char temp1000; - char *payload_text = NULL; + u32 i, count; Bool manifest_updated = GF_FALSE; u32 nb_media=0, nb_media_init=0, nb_raw_files=0; + serv->service_ready = GF_FALSE; count = gf_list_count(serv->pids); //check no changes in init segment or in manifests for (i=0; i<count; i++) { @@ -726,7 +977,7 @@ //media file, check for init segment and hls child manifest if (!rpid->manifest_type) { nb_media++; - while (! rpid->no_init) { + while (! rpid->no_init_seg) { GF_FilterPacket *pck = gf_filter_pid_get_packet(rpid->pid); if (!pck) break; @@ -744,11 +995,13 @@ rpid->init_seg_size = len; rpid->init_seg_crc = crc; rpid->init_seg_sent = GF_FALSE; - serv->stsid_changed = GF_TRUE; + //we need a new TOI since init seg changed + rpid->init_toi = 0; + serv->needs_reconfig = GF_TRUE; rpid->current_toi = 0; p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); - if (!p) p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PCK_FILENAME); + if (!p) p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_INIT_NAME); if (rpid->init_seg_name) gf_free(rpid->init_seg_name); rpid->init_seg_name = p ? routeout_strip_base(rpid->route, p->value.string) : NULL; } @@ -758,9 +1011,9 @@ break; } - if (rpid->init_seg_data || rpid->no_init) { + if (rpid->init_seg_data || rpid->no_init_seg) { nb_media_init ++; - if (serv->manifest_type==2) { + if (serv->manifest_type & 2) { if (!rpid->hld_child_pl_name) nb_media_init --; } @@ -798,9 +1051,9 @@ } if (!file_name) { - snprintf(szLocManfest, 100, "manifest.%s", (serv->manifest_type==2) ? "m3u8" : "mpd"); + snprintf(szLocManfest, 100, "manifest.%s", (rpid->manifest_type&2) ? "m3u8" : "mpd"); file_name = szLocManfest; - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE No manifest name assigned, will use %s\n", file_name)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s No manifest name assigned, will use %s\n", serv->log_name, file_name)); } //child subplaylist @@ -828,57 +1081,109 @@ memcpy(media_pid->hld_child_pl, data, len); media_pid->hld_child_pllen = 0; media_pid->hld_child_pl_crc = crc; + //we need a new TOI since manifest changed + media_pid->hls_child_toi = 0; + //for route we alternate MAX_INT-1 and MAX_INT-2 and update S-TSID each time + //NOTE: ROUTE spec is not really designed for HLS with variant playlist updates + //in particular is silent about what happens when FDT-Instance changes during updates + //we assume than any file removed from the FDT-Instance by an update is no longer available + media_pid->hld_child_pl_version = media_pid->hld_child_pl_version ? 0 : 1; + if (!serv->use_flute) + serv->needs_reconfig = GF_TRUE; if (!media_pid->hld_child_pl_name || strcmp(media_pid->hld_child_pl_name, file_name)) { if (media_pid->hld_child_pl_name) gf_free(media_pid->init_seg_name); media_pid->hld_child_pl_name = routeout_strip_base(rpid->route, (char *)file_name); - serv->stsid_changed = GF_TRUE; + serv->needs_reconfig = GF_TRUE; } media_pid->update_hls_child_pl = GF_TRUE; + manifest_updated = GF_TRUE; } } else { const u8 *man_data = gf_filter_pck_get_data(pck, &man_size); man_crc = gf_crc_32(man_data, man_size); - if (man_crc != serv->manifest_crc) { - serv->manifest_crc = man_crc; - if (serv->manifest) gf_free(serv->manifest); - serv->manifest = gf_malloc(man_size+1); - memcpy(serv->manifest, man_data, man_size); - serv->manifestman_size = 0; - serv->manifest_version++; - if (serv->manifest_name) { - if (strcmp(serv->manifest_name, file_name)) serv->stsid_changed = GF_TRUE; - gf_free(serv->manifest_name); + + char **manifest = &serv->manifest; + char **manifest_name = &serv->manifest_name; + char **manifest_server = &serv->manifest_server; + char **manifest_url = &serv->manifest_url; + char **manifest_mime = &serv->manifest_mime; + u32 *manifest_toi = &serv->manifest_toi; + u32 *manifest_crc = &serv->manifest_crc; + u32 *manifest_version = &serv->manifest_version; + u32 *manifest_port = &serv->manifest_server_port; + + if ((serv->manifest_type & 1) && (serv->manifest_type & 2) && (rpid->manifest_type==2)) { + manifest = &serv->manifest_alt; + manifest_name = &serv->manifest_alt_name; + manifest_server = &serv->manifest_alt_server; + manifest_url = &serv->manifest_alt_url; + manifest_mime = &serv->manifest_alt_mime; + manifest_toi = &serv->manifest_alt_toi; + manifest_crc = &serv->manifest_alt_crc; + manifest_version = &serv->manifest_alt_version; + manifest_port = &serv->manifest_alt_server_port; + } + + if (man_crc != *manifest_crc) { + *manifest_crc = man_crc; + if (*manifest) gf_free(*manifest); + (*manifest) = gf_malloc(man_size+1); + memcpy(*manifest, man_data, man_size); + (*manifest) man_size = 0; + (*manifest_version) ++; + if (*manifest_name) { + if (strcmp(*manifest_name, file_name)) serv->needs_reconfig = GF_TRUE; + gf_free(*manifest_name); } - serv->manifest_name = gf_strdup(file_name); + if (file_name0) + *manifest_name = gf_strdup(file_name); + else + *manifest_name = gf_strdup((rpid->manifest_type==2) ? "live.m3u8" : "live.mpd"); manifest_updated = GF_TRUE; + //we need a new TOI since manifest changed + *manifest_toi = 0; - if (serv->manifest_server) gf_free(serv->manifest_server); - if (serv->manifest_url) gf_free(serv->manifest_url); - serv->manifest_server = serv->manifest_url = NULL; + if (*manifest_server) gf_free(*manifest_server); + if (*manifest_url) gf_free(*manifest_url); + *manifest_server = *manifest_url = NULL; p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_URL); - serv->manifest_url = p ? gf_strdup(p->value.string) : NULL; - if (serv->manifest_url) { - char *sep = strstr(serv->manifest_url, file_name); + *manifest_url = p ? gf_strdup(p->value.string) : NULL; + if (*manifest_url) { + char *sep = strstr(*manifest_url, file_name); if (sep) sep0 = 0; - sep = strstr(serv->manifest_url, "://"); + sep = strstr(*manifest_url, "://"); if (sep) sep = strchr(sep + 3, '/'); if (sep) { - serv->manifest_server = serv->manifest_url; - serv->manifest_url = gf_strdup(sep+1); + *manifest_server = *manifest_url; + *manifest_url = gf_strdup(sep+1); sep0 = 0; - sep = strchr(serv->manifest_server + 8, ':'); - if (sep) sep0 = 0; + sep = strchr(*manifest_server + 8, ':'); + if (sep) { + *manifest_port = atoi(sep+1); + sep0 = 0; + } else { + *manifest_port = 0; + } + } else { + gf_free(*manifest_url); + *manifest_url = NULL; } } - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MIME); if (p) { - if (serv->manifest_mime) gf_free(serv->manifest_mime); - serv->manifest_mime = gf_strdup(p->value.string); + if (*manifest_mime) gf_free(*manifest_mime); + *manifest_mime = gf_strdup(p->value.string); } + + if (serv->service_base_uri) gf_free(serv->service_base_uri); + serv->service_base_uri = gf_strdup("tag:mabr.gpac.io.2025.services."); + char szTmp100; + char *name = *manifest_url ? *manifest_url : *manifest_name; + sprintf(szTmp, "%u", gf_crc_32(name, (u32) strlen(name)) ); + gf_dynstrcat(&serv->service_base_uri, szTmp, NULL); } } gf_filter_pid_drop_packet(rpid->pid); @@ -888,8 +1193,9 @@ if ((nb_media+nb_raw_files==0) || (nb_media_init<nb_media) || (serv->manifest && !nb_media)) { u32 now = gf_sys_clock() - serv->creation_time; if (now > 5000) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE No media PIDs found for HAS service after %d ms, aborting !\n", now)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s No media PIDs found for HAS service after %d ms, aborting !\n", serv->log_name, now)); serv->is_done = GF_TRUE; + serv->service_ready = GF_TRUE; return GF_SERVICE_ERROR; } return GF_OK; @@ -900,22 +1206,42 @@ return GF_NOT_READY; } //already setup and no changes - else if (!serv->wait_for_inputs) { - if (!manifest_updated && !serv->stsid_changed) - return GF_OK; + else if (!serv->wait_for_inputs && !manifest_updated && !serv->needs_reconfig) { + serv->service_ready = GF_TRUE; + return GF_OK; } + serv->service_ready = GF_TRUE; if (serv->wait_for_inputs) { if (!serv->manifest) { gf_assert(nb_raw_files); - serv->stsid_changed = GF_TRUE; + serv->needs_reconfig = GF_TRUE; } serv->wait_for_inputs = GF_FALSE; } + if (serv->use_flute) { + if (manifest_updated) { + if (ctx->dvb_mabr_fdt) gf_free(ctx->dvb_mabr_fdt); + ctx->dvb_mabr_fdt = NULL; + ctx->last_dvb_mabr_clock = 0; + } + return GF_OK; + } + return routeout_update_stsid_bundle(ctx, serv, manifest_updated); +} + +#define MULTIPART_BOUNDARY "_GPAC_BOUNDARY_ROUTE_.67706163_" +#define ROUTE_INIT_TOI 0xFFFFFFFF +static GF_Err routeout_update_stsid_bundle(GF_ROUTEOutCtx *ctx, ROUTEService *serv, Bool manifest_updated) +{ + u32 i, j, nb_pids; + char temp1000; + char *payload_text = NULL; - if (serv->stsid_changed) { + nb_pids = gf_list_count(serv->pids); + if (serv->needs_reconfig) { serv->stsid_version++; - for (i=0; i<count; i++) { + for (i=0; i<nb_pids; i++) { ROUTEPid *rpid = gf_list_get(serv->pids, i); if (rpid->manifest_type) continue; rpid->clock_at_first_pck = 0; @@ -982,7 +1308,8 @@ gf_dynstrcat(&payload_text, "\r\nContent-Location: usbd.xml\r\n\r\n", NULL); rpid = gf_list_get(serv->pids, 0); - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_SERVICE_NAME); + GF_PropertyEntry *pe=NULL; + p = gf_filter_pid_get_info(rpid->pid, GF_PROP_PID_SERVICE_NAME, &pe); if (p && p->value.string) service_name = p->value.string; else @@ -997,8 +1324,9 @@ gf_dynstrcat(&payload_text, "</Name>\n" " <DeliveryMethod>\n" " <BroadcastAppService>\n", NULL); + gf_filter_release_property(pe); - for (i=0;i<count; i++) { + for (i=0;i<nb_pids; i++) { rpid = gf_list_get(serv->pids, i); if (rpid->manifest_type) continue; //set template @@ -1057,29 +1385,32 @@ for (j=0; j<gf_list_count(serv->rlcts); j++) { ROUTELCT *rlct = gf_list_get(serv->rlcts, j); + Bool has_rs_hdr=GF_FALSE; - const char *src_ip; + const char *src_ip = ctx->ifce_ip; char szIPGF_MAX_IP_NAME_LEN; - src_ip = ctx->ifce; if (!src_ip) { - if (gf_sk_get_local_ip(rlct->sock, szIP) != GF_OK) + if (gf_sk_get_local_ip(serv->rlct_base->sock, szIP)!=GF_OK) strcpy(szIP, "127.0.0.1"); src_ip = szIP; } - gf_dynstrcat(&payload_text, " <RS dIpAddr=\"", NULL); - gf_dynstrcat(&payload_text, rlct->ip, NULL); - snprintf(temp, 1000, "\" dPort=\"%d\" sIpAddr=\"", rlct->port); - gf_dynstrcat(&payload_text, temp, NULL); - gf_dynstrcat(&payload_text, src_ip, NULL); - gf_dynstrcat(&payload_text, "\">\n", NULL); - - for (i=0; i<count; i++) { + for (i=0; i<nb_pids; i++) { const GF_PropertyValue *p; ROUTEPid *rpid = gf_list_get(serv->pids, i); if (rpid->manifest_type) continue; if (rpid->rlct != rlct) continue; + if (!has_rs_hdr) { + gf_dynstrcat(&payload_text, " <RS dIpAddr=\"", NULL); + gf_dynstrcat(&payload_text, rlct->ip, NULL); + snprintf(temp, 1000, "\" dPort=\"%d\" sIpAddr=\"", rlct->port); + gf_dynstrcat(&payload_text, temp, NULL); + gf_dynstrcat(&payload_text, src_ip, NULL); + gf_dynstrcat(&payload_text, "\">\n", NULL); + has_rs_hdr = GF_TRUE; + } + if (rpid->bandwidth) { u32 kbps = rpid->bandwidth / 1000; kbps *= 110; @@ -1110,13 +1441,15 @@ sep2 = strstr(temp, "$Time"); if (sep && sep2) { gf_free(payload_text); - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE DASH Template is %s but ROUTE cannot use both Time and Number !\n", p->value.string)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s DASH Template is %s but ROUTE cannot use both Time and Number !\n", serv->log_name, p->value.string)); serv->is_done = GF_TRUE; return GF_SERVICE_ERROR; } + rpid->use_time_tpl = GF_FALSE; if (!sep) { sep = sep2; key = "$Time"; + rpid->use_time_tpl = GF_TRUE; } if (sep) { sep0 = 0; @@ -1156,7 +1489,7 @@ max_size *= 2; } - snprintf(temp, 1000, " Expires=\"4000000000\" afdt:maxTransportSize=\"%d\">\n", max_size); + snprintf(temp, 1000, " Expires=\"4294944000\" afdt:maxTransportSize=\"%d\">\n", max_size); gf_dynstrcat(&payload_text, temp, NULL); } @@ -1165,7 +1498,7 @@ gf_dynstrcat(&payload_text, temp, NULL); } if (rpid->hld_child_pl_name) { - snprintf(temp, 1000, " <fdt:File Content-Location=\"%s\" TOI=\"%u\"/>\n", rpid->hld_child_pl_name, ROUTE_INIT_TOI - 1); + snprintf(temp, 1000, " <fdt:File Content-Location=\"%s\" TOI=\"%u\"/>\n", rpid->hld_child_pl_name, ROUTE_INIT_TOI - 1 - rpid->hld_child_pl_version); gf_dynstrcat(&payload_text, temp, NULL); } if (rpid->raw_file) { @@ -1176,7 +1509,7 @@ else { mime = "application/octet-string"; } - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_NAME); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MCAST_NAME); if (p && p->value.string) url = p->value.string; else { @@ -1190,7 +1523,7 @@ if (ctx->korean) { gf_dynstrcat(&payload_text, " </FDTParameters>\n", NULL); } else { - gf_dynstrcat(&payload_text, " </FDT-Instance>\n", NULL); + gf_dynstrcat(&payload_text, " </FDT-Instance>\n", NULL); } gf_dynstrcat(&payload_text, " </EFDT>\n", NULL); @@ -1200,7 +1533,7 @@ if (p && p->value.string) { rep_id = p->value.string; } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing representation ID on PID (broken input filter), using \"1\"\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing representation ID on PID (broken input filter), using \"1\"\n", serv->log_name)); rep_id = "1"; } gf_dynstrcat(&payload_text, " <ContentInfo>\n", NULL); @@ -1223,9 +1556,10 @@ gf_dynstrcat(&payload_text, " </ContentInfo>\n", NULL); } //setup payload format - we remove srcFecPayloadId=\"0\" as there is no FEC support yet + //format is always 1 for the time being snprintf(temp, 1000, - " <Payload codePoint=\"%d\" formatId=\"%d\" frag=\"0\" order=\"true\"/>\n" - , rpid->fmtp, rpid->mode); + " <Payload codePoint=\"%d\" formatId=\"1\" frag=\"0\" order=\"true\"/>\n" + , rpid->fmtp); gf_dynstrcat(&payload_text, temp, NULL); @@ -1233,7 +1567,8 @@ " </SrcFlow>\n" " </LS>\n", NULL); } - gf_dynstrcat(&payload_text, " </RS>\n", NULL); + if (has_rs_hdr) + gf_dynstrcat(&payload_text, " </RS>\n", NULL); } gf_dynstrcat(&payload_text, "</S-TSID>\n\r\n", NULL); @@ -1241,7 +1576,7 @@ gf_dynstrcat(&payload_text, "--"MULTIPART_BOUNDARY"--\n", NULL); - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Updated Manifest+S-TSID bundle to:\n%s\n", payload_text)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Updated Manifest+S-TSID bundle to:\n%s\n", serv->log_name, payload_text)); if (serv->stsid_bundle) gf_free(serv->stsid_bundle); serv->stsid_bundle = (u8 *) payload_text; @@ -1253,46 +1588,212 @@ serv->stsid_bundle_toi = 0x80000000; //compressed } if (manifest_updated) serv->stsid_bundle_toi |= (1<<18); - if (serv->stsid_changed) { + if (serv->needs_reconfig) { serv->stsid_bundle_toi |= (1<<17); serv->stsid_bundle_toi |= (serv->stsid_version & 0xFF); } else if (manifest_updated) { serv->stsid_bundle_toi |= (serv->manifest_version & 0xFF); } - serv->stsid_changed = GF_FALSE; + serv->needs_reconfig = GF_FALSE; //reset last sent time serv->last_stsid_clock = 0; return GF_OK; } +#include <gpac/base_coding.h> -u32 routeout_lct_send(GF_ROUTEOutCtx *ctx, GF_Socket *sock, u32 tsi, u32 toi, u32 codepoint, u8 *payload, u32 len, u32 offset, u32 service_id, u32 total_size, u32 offset_in_frame) +static void inject_fdt_file_desc(GF_ROUTEOutCtx *ctx, char **payload, ROUTEService *serv, char *url, char *mime, const u8 *data, u32 size, u32 TOI) +{ + char tmp100; + gf_dynstrcat(payload, "<File FEC-OTI-FEC-Encoding-ID=\"0\" FEC-OTI-Maximum-Source-Block-Length=\"65535\" Content-Length=\"", NULL); + sprintf(tmp, "%u", size); + gf_dynstrcat(payload, tmp, NULL); + gf_dynstrcat(payload, "\" Transfer-Length=\"", NULL); + gf_dynstrcat(payload, tmp, NULL); + gf_dynstrcat(payload, "\" Content-Location=\"", NULL); + + Bool use_full_url = GF_TRUE; + if (!serv && (TOI==ctx->dvb_mabr_config_toi)) + use_full_url = GF_FALSE; + + if (use_full_url && serv) { + gf_dynstrcat(payload, serv->service_base_uri, NULL); + gf_dynstrcat(payload, url, "/"); + } else + gf_dynstrcat(payload, url, NULL); + + gf_dynstrcat(payload, "\" Content-Type=\"", NULL); + gf_dynstrcat(payload, mime, NULL); + gf_dynstrcat(payload, "\" FEC-OTI-Encoding-Symbol-Length=\"", NULL); + sprintf(tmp, "%u", ctx->flute_msize); + gf_dynstrcat(payload, tmp, NULL); + gf_dynstrcat(payload, "\" TOI=\"", NULL); + sprintf(tmp, "%u", TOI); + gf_dynstrcat(payload, tmp, NULL); + gf_dynstrcat(payload, "\"", NULL); + + if (ctx->csum && data) { + u8 digestGF_MD5_DIGEST_SIZE; + gf_md5_csum(data, size, digest); + u32 db64_len = gf_base64_encode(digest, GF_MD5_DIGEST_SIZE, tmp, 100); + tmpdb64_len=0; + gf_dynstrcat(payload, " Content-MD5=\"", NULL); + gf_dynstrcat(payload, tmp, NULL); + gf_dynstrcat(payload, "\"", NULL); + } + gf_dynstrcat(payload, "/>\n", NULL); +} + +static void routeout_send_mabr_manifest(GF_ROUTEOutCtx *ctx); + +static GF_Err routeout_update_dvb_mabr_fdt(GF_ROUTEOutCtx *ctx, ROUTEService *serv, Bool manifest_updated) +{ + u32 i, nb_serv; + if (serv && ctx->dvb_mabr_fdt && !serv->needs_reconfig) return GF_OK; + char *payload=NULL; + gf_dynstrcat(&payload, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n", NULL); + gf_dynstrcat(&payload, "<FDT-Instance Expires=\"4294944000\" xmlns=\"urn:IETF:metadata:2005:FLUTE:FDT\">\n", NULL); + + if (!ctx->dvb_mabr_config_toi) { + ctx->dvb_mabr_config_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + //TS 103 769 section 8.3.5 + inject_fdt_file_desc(ctx, &payload, NULL, "urn:dvb:metadata:cs:MulticastTransportObjectTypeCS:2021:gateway-configuration", "application/xml+dvb-mabr-session-configuration", ctx->dvb_mabr_config, ctx->dvb_mabr_config_len, ctx->dvb_mabr_config_toi); + + nb_serv = gf_list_count(ctx->services); + for (i=0; i<nb_serv; i++) { + ROUTEService *serv = gf_list_get(ctx->services, i); + if (!serv->use_flute || ctx->use_inband) continue; + //inject manifest + if (serv->manifest && serv->manifest_type) { + u32 len = (u32) strlen(serv->manifest); + if (!serv->manifest_toi) { + serv->manifest_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, &payload, serv, serv->manifest_name, serv->manifest_mime, serv->manifest, len, serv->manifest_toi); + + //inject alt manifest + if (serv->manifest_alt) { + len = (u32) strlen(serv->manifest_alt); + if (!serv->manifest_alt_toi) { + serv->manifest_alt_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, &payload, serv, serv->manifest_alt_name, serv->manifest_alt_mime, serv->manifest_alt, len, serv->manifest_alt_toi); + } + } + + //inject init segs and HLS variant or RAW info + u32 j, nb_pids = gf_list_count(serv->pids); + for (j=0; j<nb_pids; j++) { + ROUTEPid *pid = gf_list_get(serv->pids, j); + if (pid->raw_file) { + if (!pid->current_toi || !pid->full_frame_size) + continue; + + char *mime; + const GF_PropertyValue *p = gf_filter_pid_get_property(pid->pid, GF_PROP_PID_MIME); + if (p && p->value.string && strcmp(p->value.string, "*")) { + mime = p->value.string; + } else { + mime = "application/octet-string"; + } + inject_fdt_file_desc(ctx, &payload, serv, pid->seg_name, mime, pid->pck_data, pid->full_frame_size, pid->current_toi); + continue; + } + + if (pid->init_seg_data) { + if (!pid->init_toi) { + pid->init_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, &payload, serv, pid->init_seg_name, "video/mp4", pid->init_seg_data, pid->init_seg_size, pid->init_toi); + } + if (pid->hld_child_pl) { + if (!pid->hls_child_toi) { + pid->hls_child_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, &payload, serv, pid->hld_child_pl_name, "application/vnd.apple.mpegURL", pid->hld_child_pl, (u32) strlen(pid->hld_child_pl), pid->hls_child_toi); + } + } + } + + + gf_dynstrcat(&payload, "</FDT-Instance>", NULL); + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Updated Bootstrap FDT info to:\n%s\n", ctx->log_name, payload)); + if (ctx->dvb_mabr_fdt) gf_free(ctx->dvb_mabr_fdt); + ctx->dvb_mabr_fdt = payload; + ctx->dvb_mabr_fdt_len = (u32) strlen(payload); + ctx->last_dvb_mabr_clock = 0; + ctx->dvb_mabr_fdt_instance_id = (ctx->dvb_mabr_fdt_instance_id+1) % 0xFFFFF; + return GF_OK; +} + +static void update_error_simulation_state(GF_ROUTEOutCtx *ctx) { +#define ERRSIM_ACCURACY 100 + Double p = (gf_rand() % (100 * ERRSIM_ACCURACY)) / (Double)ERRSIM_ACCURACY; + Double t = ctx->state_is_error ? ctx->errsim.y : ctx->errsim.x; + if (p < t) + ctx->state_is_error = !ctx->state_is_error; +#undef ERRSIM_ACCURACY +} + +u32 routeout_lct_send(GF_ROUTEOutCtx *ctx, GF_Socket *sock, u32 tsi, u32 toi, u32 codepoint, u8 *payload, u32 len, u32 offset, ROUTEService *serv, u32 total_size, u32 offset_in_frame, u32 fdt_instance_id, Bool is_flute, Bool can_set_close) { u32 max_size = ctx->mtu; u32 send_payl_size; u32 hdr_len = 4; u32 hpos; + Bool short_h=GF_FALSE; GF_Err e; - if (total_size) { - //TOL extension - if (total_size<=0xFFFFFF) hdr_len += 1; - else hdr_len += 2; + if (is_flute) { + codepoint = 0; + hdr_len = 2; + if ((tsi<0xFFFF) && (toi<0xFFFF)) { + hdr_len+=1; + short_h = GF_TRUE; + } else { + hdr_len+=2; + } + //add extensions for FDT: EXT_FDT (1x 32bits), EXT_FTI (4x 32bits) and EXT_TIME (3x 32bits, we only send NTP) + if (!toi) { + hdr_len += 8; + } + } else { + hdr_len = 4; + if (total_size) { + //TOL extension + if (total_size<=0xFFFFFF) hdr_len += 1; + else hdr_len += 2; + } } - //start offset is not in header send_payl_size = 4 * (hdr_len+1) + len - offset; if (send_payl_size > max_size) { - send_payl_size = max_size - 4 * (hdr_len+1); + if (is_flute) + send_payl_size = ctx->flute_msize; + else + send_payl_size = max_size - 4 * (hdr_len+1); } else { send_payl_size = len - offset; } - ctx->lct_buffer0 = 0x12; //V=b0001, C=b00, PSI=b10 - ctx->lct_buffer1 = 0xA0; //S=b1, 0=b01, h=b0, res=b00, A=b0, B=X - //set close flag only if total_len is known - if (total_size && (offset + send_payl_size == len)) + ctx->lct_buffer0 = 0x10; //V=b0001, C=b00, PSI=b00 or b10 with ROUTE + if (!is_flute) ctx->lct_buffer0 |= 0x02; + //S=b1|b0, O=b01|b00, h=b0|b1, res=b00, A=b0, B=X + ctx->lct_buffer1 = short_h ? 0x10 : 0xA0; + + //Set the close flag (only when total_len is known) only if asked for + //Typically: + //- carrousel files (raw, manifests, init segments) will never set this as they are resent with the same TOI + //- segments will, as they will never be resent + if (can_set_close && total_size && (offset + send_payl_size == len)) ctx->lct_buffer1 |= 1; ctx->lct_buffer2 = hdr_len; @@ -1306,65 +1807,123 @@ ctx->lct_bufferhpos+3 = (_val & 0xFF); \ hpos+=4; +#define PUT_U16(_val)\ + ctx->lct_bufferhpos = (_val>>8 & 0xFF);\ + ctx->lct_bufferhpos+1 = (_val & 0xFF);\ + hpos+=2; + + //CCI=0 PUT_U32(0); - PUT_U32(tsi); - PUT_U32(toi); + if (short_h) { + PUT_U16(tsi); + PUT_U16(toi); + } else { + PUT_U32(tsi); + PUT_U32(toi); + } - //total length - if (total_size) { - if (total_size<=0xFFFFFF) { - ctx->lct_bufferhpos = GF_LCT_EXT_TOL24; - ctx->lct_bufferhpos+1 = total_size>>16 & 0xFF; - ctx->lct_bufferhpos+2 = total_size>>8 & 0xFF; - ctx->lct_bufferhpos+3 = total_size & 0xFF; - hpos+=4; - } else { - ctx->lct_bufferhpos = GF_LCT_EXT_TOL48; - ctx->lct_bufferhpos+1 = 2; //2 x 32 bits for header ext - ctx->lct_bufferhpos+2 = 0; - ctx->lct_bufferhpos+3 = 0; - hpos+=4; - PUT_U32(total_size); + if (is_flute) { + u32 ESI = offset_in_frame/ctx->flute_msize; + if (toi==0) { + gf_bs_reassign_buffer(ctx->lct_bs, ctx->lct_buffer+hpos, ctx->mtu); + //set FDT + gf_bs_write_u8(ctx->lct_bs, GF_LCT_EXT_FDT); + gf_bs_write_int(ctx->lct_bs, 1, 4); + gf_bs_write_int(ctx->lct_bs, fdt_instance_id, 20); + hpos+=4; //32 bits + + //set FTI + gf_bs_write_u8(ctx->lct_bs, GF_LCT_EXT_FTI); //TOH + gf_bs_write_u8(ctx->lct_bs, 4); //TOL + //48 bits of transfer length + gf_bs_write_long_int(ctx->lct_bs, total_size, 48); + //16bits of fec instanceID + gf_bs_write_u16(ctx->lct_bs, 0); + //16bits of symbol length + gf_bs_write_u16(ctx->lct_bs, ctx->flute_msize); + //32bits of max source block length + gf_bs_write_u32(ctx->lct_bs, ctx->mtu); + hpos+=16; //4 32bit words + + //set TIME + gf_bs_write_u8(ctx->lct_bs, GF_LCT_EXT_TIME); //TOH + gf_bs_write_u8(ctx->lct_bs, 3); //TOL + gf_bs_write_u16(ctx->lct_bs, 0xC000); //use bits, set SCT high and low + u32 ntp_s, ntp_f; + gf_net_get_ntp(&ntp_s, &ntp_f); + gf_bs_write_u32(ctx->lct_bs, ntp_s); + gf_bs_write_u32(ctx->lct_bs, ntp_f); + hpos+=12; //3 32bit words } - } + PUT_U16(0) + PUT_U16(ESI) + } else { - //start_offset - PUT_U32(offset_in_frame); + //total length + if (total_size) { + if (total_size<=0xFFFFFF) { + ctx->lct_bufferhpos = GF_LCT_EXT_TOL24; + ctx->lct_bufferhpos+1 = total_size>>16 & 0xFF; + ctx->lct_bufferhpos+2 = total_size>>8 & 0xFF; + ctx->lct_bufferhpos+3 = total_size & 0xFF; + hpos+=4; + } else { + ctx->lct_bufferhpos = GF_LCT_EXT_TOL48; + ctx->lct_bufferhpos+1 = 2; //2 x 32 bits for header ext + ctx->lct_bufferhpos+2 = 0; + ctx->lct_bufferhpos+3 = 0; + hpos+=4; + PUT_U32(total_size); + } + } + //start_offset + PUT_U32(offset_in_frame); + } gf_assert(send_payl_size+hpos <= ctx->mtu); - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE LCT SID %u TSI %u TOI %u size %u (frag %u total %u) offset %u (%u in obj)\n", service_id, tsi, toi, send_payl_size, len, total_size, offset, offset_in_frame)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s LCT TSI %u TOI %u size %u (frag %u total %u) offset %u (%u in obj)\n", serv ? serv->log_name : ctx->log_name, tsi, toi, send_payl_size, len, total_size, offset, offset_in_frame)); - memcpy(ctx->lct_buffer + hpos, payload + offset, send_payl_size); - e = gf_sk_send(sock, ctx->lct_buffer, send_payl_size + hpos); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to send LCT object TSI %u TOI %u fragment: %s\n", tsi, toi, gf_error_to_string(e) )); + update_error_simulation_state(ctx); + if (!ctx->state_is_error) { + memcpy(ctx->lct_buffer + hpos, payload + offset, send_payl_size); + e = gf_sk_send(sock, ctx->lct_buffer, send_payl_size + hpos); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to send LCT object TSI %u TOI %u fragment: %s\n", serv ? serv->log_name : ctx->log_name, tsi, toi, gf_error_to_string(e) )); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Simulated error loss for LCT object TSI %u TOI %u service_name %s size %u (frag %u total %u) offset %u (%u in obj)\n", + serv ? serv->log_name : ctx->log_name, tsi, toi, serv ? serv->service_name : "N/A", send_payl_size, len, total_size, offset, offset_in_frame)); } + //store what we actually sent including header for rate estimation ctx->bytes_sent += send_payl_size + hpos; //but return what we sent from the source return send_payl_size; } -static GF_Err routeout_service_send_bundle(GF_ROUTEOutCtx *ctx, ROUTEService *serv) +static void routeout_send_file(GF_ROUTEOutCtx *ctx, ROUTEService *serv, GF_Socket *sock, u32 tsi, u32 toi, u8 *payload, u32 size, u32 codepoint, u32 fdt_instance_id, Bool is_flute) { - u32 offset = 0; - - //we don't regulate - while (offset < serv->stsid_bundle_size) { - /*send lct with codepoint "NRT - Unsigned Package Mode" (multipart): - "In broadcast delivery of SLS for a DASH-formatted streaming Service delivered using ROUTE, since SLS fragments are NRT files in nature, - their carriage over the ROUTE session/LCT channel assigned by the SLT shall be in accordance to the Unsigned Packaged Mode or - the Signed Package Mode as described in Section A.3.3.4 and A.3.3.5, respectively" - */ - offset += routeout_lct_send(ctx, serv->rlct_base->sock, 0, serv->stsid_bundle_toi, 3, serv->stsid_bundle, serv->stsid_bundle_size, offset, serv->service_id, serv->stsid_bundle_size, offset); + u32 offset=0; + while (offset<size) { + offset += routeout_lct_send(ctx, sock, tsi, toi, codepoint, payload, size, offset, serv, size, offset, fdt_instance_id, is_flute, GF_FALSE); } - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Sent service %d bundle (%d bytes)\n", serv->service_id, serv->stsid_bundle_size)); +} + +static GF_Err routeout_service_send_stsid_bundle(GF_ROUTEOutCtx *ctx, ROUTEService *serv) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Sent bundle (%d bytes)\n", serv->log_name, serv->stsid_bundle_size)); + /*send lct with codepoint "NRT - Unsigned Package Mode" (multipart): + "In broadcast delivery of SLS for a DASH-formatted streaming Service delivered using ROUTE, since SLS fragments are NRT files in nature, + their carriage over the ROUTE session/LCT channel assigned by the SLT shall be in accordance to the Unsigned Packaged Mode or + the Signed Package Mode as described in Section A.3.3.4 and A.3.3.5, respectively" + */ + routeout_send_file(ctx, serv, serv->rlct_base->sock, 0, serv->stsid_bundle_toi, serv->stsid_bundle, serv->stsid_bundle_size, 3, 0, GF_FALSE); return GF_OK; } -static void routeout_fetch_packet(GF_ROUTEOutCtx *ctx, ROUTEPid *rpid) +static GF_Err routeout_fetch_packet(GF_ROUTEOutCtx *ctx, ROUTEPid *rpid) { const GF_PropertyValue *p; Bool start, end; @@ -1407,9 +1966,9 @@ } //done rpid->res_size = 0; - return; + return GF_OK; } - return; + return GF_OK; } //skip eods packets if (rpid->route->dash_mode && gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_EODS)) { @@ -1435,6 +1994,12 @@ } rpid->pck_data = gf_filter_pck_get_data(rpid->current_pck, &rpid->pck_size); + //this can happen with httpin and chunks + if (!rpid->pck_size || !rpid->pck_data) { + gf_filter_pck_unref(rpid->current_pck); + rpid->current_pck = NULL; + goto retry; + } rpid->pck_offset = 0; p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_INIT); @@ -1442,10 +2007,10 @@ u32 crc; gf_assert(start && end); crc = gf_crc_32(rpid->pck_data, rpid->pck_size); - //whenever init seg changes, bump stsid version + //whenever init seg changes, reconfig service if (crc != rpid->init_seg_crc) { rpid->init_seg_crc = crc; - rpid->route->stsid_changed = GF_TRUE; + rpid->route->needs_reconfig = GF_TRUE; rpid->current_toi = 0; if (rpid->init_seg_data) gf_free(rpid->init_seg_data); rpid->init_seg_data = gf_malloc(rpid->pck_size); @@ -1461,29 +2026,6 @@ goto retry; } - p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PID_HLS_PLAYLIST); - if (p && p->value.string) { - u32 crc; - gf_assert(start && end); - crc = gf_crc_32(rpid->pck_data, rpid->pck_size); - //whenever init seg changes, bump stsid version - if (crc != rpid->hld_child_pl_crc) { - rpid->hld_child_pl_crc = crc; - if (rpid->hld_child_pl) gf_free(rpid->hld_child_pl); - rpid->hld_child_pl = gf_malloc(rpid->pck_size+1); - memcpy(rpid->hld_child_pl, rpid->pck_data, rpid->pck_size); - rpid->hld_child_plrpid->pck_size = 0; - - if (!rpid->hld_child_pl_name || strcmp(rpid->hld_child_pl_name, p->value.string)) { - rpid->route->stsid_changed = GF_TRUE; - if (rpid->hld_child_pl_name) gf_free(rpid->hld_child_pl_name); - rpid->hld_child_pl_name = routeout_strip_base(rpid->route, p->value.string); - } - } - gf_filter_pck_unref(rpid->current_pck); - rpid->current_pck = NULL; - goto retry; - } if (rpid->route->dash_mode) { p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENUM); if (p) { @@ -1516,7 +2058,7 @@ if (rpid->seg_name) gf_free(rpid->seg_name); rpid->seg_name = "unknown"; - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_NAME); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MCAST_NAME); if (p) rpid->seg_name = p->value.string; else { @@ -1527,54 +2069,75 @@ //setup carousel period rpid->carousel_time_us = ctx->carousel; - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_CAROUSEL); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MCAST_CAROUSEL); if (p) { - rpid->carousel_time_us = p->value.frac.num; - rpid->carousel_time_us *= 1000000; - rpid->carousel_time_us /= p->value.frac.den; + rpid->carousel_time_us = gf_timestamp_rescale(p->value.frac.num, p->value.frac.den, 1000000); } //setup upload time rpid->current_dur_us = ctx->carousel; - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_ROUTE_SENDTIME); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MCAST_SENDTIME); if (p) { rpid->current_dur_us = p->value.frac.num; rpid->current_dur_us *= 1000000; rpid->current_dur_us /= p->value.frac.den; } if (rpid->carousel_time_us && (rpid->current_dur_us>rpid->carousel_time_us)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Requested upload time of file "LLU" is greater than its carousel time "LLU", adjusting carousel\n", rpid->current_dur_us, rpid->carousel_time_us)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Requested upload time of file "LLU" is greater than its carousel time "LLU", adjusting carousel\n", rpid->route->log_name, rpid->current_dur_us, rpid->carousel_time_us)); rpid->carousel_time_us = rpid->current_dur_us; } rpid->clock_at_pck = rpid->current_cts_us = rpid->cts_us_at_frame_start = ctx->clock; rpid->full_frame_size = rpid->pck_size; - return; + return GF_OK; } if (start) { p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENAME); if (rpid->seg_name) gf_free(rpid->seg_name); if (p) { - rpid->seg_name = rpid->use_basename ? gf_file_basename(p->value.string) : p->value.string; + if (rpid->use_basename) + rpid->seg_name = gf_strdup(gf_file_basename(p->value.string)); + else + rpid->seg_name = routeout_strip_base(rpid->route, p->value.string); } else { - rpid->seg_name = "unknown"; + rpid->seg_name = gf_strdup("unknown"); } - rpid->seg_name = gf_strdup(rpid->seg_name); //file num increased per packet, open new file - p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_MPD_SEGSTART); - if (p) { - if (p->value.lfrac.num >= ROUTE_INIT_TOI) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE MPD Time "LLU" greater than 32 bits, session no longer valid, aborting\n", p->value.lfrac.num)); + Bool in_error = GF_FALSE; + p = rpid->use_time_tpl ? gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_MPD_SEGSTART) : NULL; + if (rpid->use_time_tpl && !p) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing MPD Time on segment, cannot map to TOI - aborting\n", rpid->route->log_name)); + in_error = GF_TRUE; + } + if (ctx->dvb_mabr) { + //keep using only 16bits but no TOI =0 (reserved for FDT) + rpid->current_toi = (rpid->current_toi+1) % 0xFFFF; + if (!rpid->current_toi) rpid->current_toi = 1; + } else if (p || in_error) { + if (p && p->value.lfrac.num >= ROUTE_INIT_TOI) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s MPD Time "LLU" greater than 32 bits, cannot map on TOI - aborting\n", rpid->route->log_name, p->value.lfrac.num)); + in_error = GF_TRUE; + } + if (in_error) { + u32 i; + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, NULL); rpid->route->is_done = GF_TRUE; - gf_filter_pid_set_discard(rpid->pid, GF_TRUE); + for (i=0; i<gf_list_count(rpid->route->pids); i++) { + ROUTEPid *r_pid = gf_list_get(rpid->route->pids, i); + gf_filter_pid_set_discard(r_pid->pid, GF_TRUE); + evt.base.on_pid = r_pid->pid; + gf_filter_pid_send_event(r_pid->pid, &evt); + } + return GF_SERVICE_ERROR; } rpid->current_toi = (u32) p->value.lfrac.num; } else { p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_FILENUM); - if (p) + if (p) { rpid->current_toi = p->value.uint; - else { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing filenum on segment %s, something is wrong in source chain - assuming +1 increase\n", rpid->seg_name)); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing filenum on segment %s, something is wrong in source chain - assuming +1 increase\n", rpid->route->log_name, rpid->seg_name)); rpid->current_toi ++; } } @@ -1583,12 +2146,30 @@ rpid->cumulated_frag_size = rpid->pck_size; rpid->push_init = GF_TRUE; rpid->frag_offset = 0; + + p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_SENDER_NTP); + if (p) { + u64 ntp = gf_net_get_ntp_ts(); + rpid->diff_send_at_frame_start = gf_net_ntp_diff_ms(ntp, p->value.longuint); + p = gf_filter_pck_get_property(rpid->current_pck, GF_PROP_PCK_RECEIVER_NTP); + rpid->diff_recv_at_frame_start = p ? gf_net_ntp_diff_ms(ntp, p->value.longuint) : -1; + } else { + rpid->diff_send_at_frame_start = -1; + rpid->diff_recv_at_frame_start = -1; + } } else { rpid->frag_idx++; + if (ctx->dvb_mabr) { + //one object per fragment in flute + //keep using only 16bits but no TOI =0 (reserved for FDT) + rpid->current_toi = (rpid->current_toi+1) % 0xFFFF; + if (!rpid->current_toi) rpid->current_toi = 1; + } rpid->cumulated_frag_size += rpid->pck_size; + rpid->push_frag_name = (ctx->dvb_mabr && ctx->llmode) ? GF_TRUE : GF_FALSE; if (end) { rpid->full_frame_size = rpid->cumulated_frag_size; - rpid->force_tol_send = GF_TRUE; + rpid->force_send_empty = GF_TRUE; } } @@ -1635,7 +2216,7 @@ } else { frag_time = 0; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Missing timing for fragment %d of segment %s - timing estimated from bitrate: TS "LLU" ("LLU" in segment) dur "LLU"\n", rpid->frag_idx, rpid->seg_name, ts, frag_time, pck_dur)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Missing timing for fragment %d of segment %s - timing estimated from bitrate: TS "LLU" ("LLU" in segment) dur "LLU"\n", rpid->route->log_name, rpid->frag_idx, rpid->seg_name, ts, frag_time, pck_dur)); } if (ts!=GF_FILTER_NO_TS) { @@ -1654,47 +2235,148 @@ rpid->clock_at_frame_start = ctx->clock; rpid->cts_us_at_frame_start = rpid->current_cts_us; rpid->cts_at_frame_start = ts; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Segment %s init send time to "LLU" CTS "LLU" us source TS "LLU"/%u\n", rpid->route->log_name, rpid->seg_name, rpid->cts_us_at_frame_start, rpid->current_cts_us, ts, rpid->timescale)); } rpid->current_dur_us = pck_dur; if (!rpid->current_dur_us) { rpid->current_dur_us = rpid->timescale; - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing duration on segment %s, something is wrong in source chain, will not be able to regulate correctly\n", rpid->seg_name)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing duration on segment %s, something is wrong in source chain, will not be able to regulate correctly\n", rpid->route->log_name, rpid->seg_name)); } rpid->current_dur_us = gf_timestamp_rescale(rpid->current_dur_us, rpid->timescale, 1000000); } else if (start && end) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing timing on segment %s, using previous fragment timing CTS "LLU" duration "LLU" us\nSomething could be wrong in demux chain, will not be able to regulate correctly\n", rpid->seg_name, rpid->current_cts_us-rpid->clock_at_first_pck, rpid->current_dur_us)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing timing on segment %s, using previous fragment timing CTS "LLU" duration "LLU" us\nSomething could be wrong in demux chain, will not be able to regulate correctly\n", rpid->route->log_name, rpid->seg_name, rpid->current_cts_us-rpid->clock_at_first_pck, rpid->current_dur_us)); } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Missing timing on fragment %d of segment %s, using previous fragment timing CTS "LLU" duration "LLU" us\nSomething could be wrong in demux chain, will not be able to regulate correctly\n", rpid->frag_idx, rpid->seg_name, rpid->current_cts_us-rpid->clock_at_first_pck, rpid->current_dur_us)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing timing on fragment %d of segment %s, using previous fragment timing CTS "LLU" duration "LLU" us\nSomething could be wrong in demux chain, will not be able to regulate correctly\n", rpid->route->log_name, rpid->frag_idx, rpid->seg_name, rpid->current_cts_us-rpid->clock_at_first_pck, rpid->current_dur_us)); } - rpid->res_size += rpid->pck_size; + return GF_OK; +} + + +void inject_mani_init_hls_variant_fdt(GF_ROUTEOutCtx *ctx, ROUTEService *serv, ROUTEPid *rpid, char **payload) +{ + //inject manifest + if (serv->manifest && serv->manifest_type) { + u32 len = (u32) strlen(serv->manifest); + if (!serv->manifest_toi) { + serv->manifest_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, payload, serv, serv->manifest_name, serv->manifest_mime, + serv->manifest, len, serv->manifest_toi); + + if (serv->manifest_alt) { + len = (u32) strlen(serv->manifest_alt); + if (!serv->manifest_alt_toi) { + serv->manifest_alt_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, payload, serv, serv->manifest_alt_name, serv->manifest_alt_mime, + serv->manifest_alt, len, serv->manifest_alt_toi); + } + } + + //inject init segs and HLS variant or RAW info + u32 j, nb_pids = gf_list_count(serv->pids); + for (j=0; j<nb_pids; j++) { + ROUTEPid *pid = gf_list_get(serv->pids, j); + if (pid->tsi != rpid->tsi) continue; + + if (pid->raw_file) { + if (!pid->current_toi || !pid->full_frame_size) + continue; + + char *mime; + const GF_PropertyValue *p = gf_filter_pid_get_property(pid->pid, GF_PROP_PID_MIME); + if (p && p->value.string && strcmp(p->value.string, "*")) { + mime = p->value.string; + } else { + mime = "application/octet-string"; + } + inject_fdt_file_desc(ctx, payload, serv, pid->seg_name, mime, pid->pck_data, pid->full_frame_size, pid->current_toi); + continue; + } + + if (pid->init_seg_data) { + if (!pid->init_toi) { + pid->init_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, payload, serv, pid->init_seg_name, "video/mp4", pid->init_seg_data, pid->init_seg_size, pid->init_toi); + } + if (pid->hld_child_pl) { + if (!pid->hls_child_toi) { + pid->hls_child_toi = ctx->next_toi_avail; + ctx->next_toi_avail++; + } + inject_fdt_file_desc(ctx, payload, serv, pid->hld_child_pl_name, "application/vnd.apple.mpegURL", pid->hld_child_pl, (u32) strlen(pid->hld_child_pl), pid->hls_child_toi); + } + } } +void routeout_send_fdt(GF_ROUTEOutCtx *ctx, ROUTEService *serv, ROUTEPid *rpid) +{ + char *payload = NULL; + char szNameGF_MAX_PATH, *seg_name; + + gf_dynstrcat(&payload, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n", NULL); + gf_dynstrcat(&payload, "<FDT-Instance Expires=\"4294944000\" xmlns=\"urn:IETF:metadata:2005:FLUTE:FDT\">\n", NULL); + //if use_inband: inject manifest, init seg and child playlis + if (ctx->use_inband){ + inject_mani_init_hls_variant_fdt(ctx, serv, rpid, &payload); + //update current toi with value of next toi available since we sent mani and init + if (rpid->current_toi !=1 && rpid->current_toi < ctx->next_toi_avail) + rpid->current_toi = ctx->next_toi_avail; + } + + //cannot use TOI 0 for anything else than FDT + if (!rpid->current_toi) + rpid->current_toi = 1; + const u8 *pck_data; + if (!rpid->frag_idx && (ctx->csum==DVB_CSUM_ALL)) + pck_data = rpid->pck_data; + else + pck_data = NULL; + + if (ctx->llmode && (rpid->pck_size!=rpid->full_frame_size)) { + if (rpid->full_frame_size) + sprintf(szName, "%s?isLast#%u", rpid->seg_name, rpid->frag_offset); + else + sprintf(szName, "%s#%u", rpid->seg_name, rpid->frag_offset); + seg_name = szName; + } else { + seg_name = rpid->seg_name; + } + inject_fdt_file_desc(ctx, &payload, serv, seg_name, "video/mp4", pck_data, rpid->pck_size, rpid->current_toi); + gf_dynstrcat(&payload, "</FDT-Instance>", NULL); + + if (payload) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Sending FDT for segment %s:\n%s\n", serv->log_name, seg_name, payload)); + routeout_send_file(ctx, serv, rpid->rlct->sock, rpid->tsi, 0, payload, (u32) strlen(payload), 0, rpid->fdt_instance_id, GF_TRUE); + gf_free(payload); + rpid->fdt_instance_id = (rpid->fdt_instance_id+1) % 0xFFFFF; + } +} + static GF_Err routeout_process_service(GF_ROUTEOutCtx *ctx, ROUTEService *serv) { u32 i, count, nb_done; - GF_Err e; - - e = routeout_check_service_updates(ctx, serv); - if (e==GF_NOT_READY) - return GF_OK; + if (!serv->service_ready) return GF_OK; + //carousel STSID bundle if (serv->stsid_bundle) { u64 diff = ctx->clock - serv->last_stsid_clock; if (!serv->last_stsid_clock || (diff >= ctx->carousel)) { - routeout_service_send_bundle(ctx, serv); + routeout_service_send_stsid_bundle(ctx, serv); serv->last_stsid_clock = ctx->clock; } else { u64 next_sched = ctx->carousel - diff; if (next_sched < ctx->reschedule_us) ctx->reschedule_us = next_sched; } - } else { - //not ready - return e; } nb_done = 0; @@ -1721,8 +2403,10 @@ next_packet: if (!rpid->current_pck) { - u32 offset; - routeout_fetch_packet(ctx, rpid); + Bool push_init; + GF_Err e = routeout_fetch_packet(ctx, rpid); + if (e) return e; + if (!rpid->current_pck) { if (gf_filter_pid_is_eos(rpid->pid) && !gf_filter_pid_is_flush_eos(rpid->pid)) nb_done++; @@ -1739,41 +2423,84 @@ ctx->nb_resources++; } - if (rpid->push_init) { + //in low-latency mode, push manifest and child playlists + push_init = rpid->push_init; + if (ctx->llmode && !push_init + && rpid->carousel_time_us + && (gf_sys_clock_high_res() - rpid->last_init_push > rpid->carousel_time_us) + ) { + push_init = GF_TRUE; + if (rpid->hld_child_pl) + send_hls_child = GF_TRUE; + } + + if (push_init) { + GF_Socket *init_sock = rpid->rlct->sock; + u32 init_tsi = rpid->tsi; + u32 init_toi = ROUTE_INIT_TOI; + rpid->push_init = GF_FALSE; send_hls_child = GF_TRUE; - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Sending init segment %s\n", rpid->init_seg_name)); - - //send init asap (may be empty) - offset = 0; - while (offset < rpid->init_seg_size) { - //we use codepoint 5 (new IS) or 7 (repeated IS) - u32 codepoint; - if (rpid->init_seg_sent) { - codepoint = 7; + if (serv->use_flute) { + //update fdt + routeout_send_fdt(ctx, serv, rpid); + rpid->push_frag_name = GF_FALSE; + + if (ctx->use_inband) { + init_sock = rpid->rlct->sock; + init_tsi = rpid->tsi; } else { - rpid->init_seg_sent = GF_FALSE; - codepoint = 5; + init_sock = ctx->sock_dvb_mabr; + init_tsi = ctx->dvb_mabr_tsi; + } + + //always send manifest before init + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Sending Manifest %s (TOI %u PID type %u)\n", serv->log_name, serv->manifest_name, serv->manifest_toi, rpid->stream_type)); + routeout_send_file(ctx, serv, init_sock, init_tsi, serv->manifest_toi, serv->manifest, (u32) strlen(serv->manifest), 0, 0, GF_TRUE); + + if (serv->manifest_alt) { + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Sending Alternative Manifest %s\n", serv->log_name, serv->manifest_alt_name)); + routeout_send_file(ctx, serv, init_sock, init_tsi, serv->manifest_alt_toi, serv->manifest_alt, (u32) strlen(serv->manifest_alt), 0, 0, GF_TRUE); } - offset += routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, ROUTE_INIT_TOI, codepoint, (u8 *) rpid->init_seg_data, rpid->init_seg_size, offset, serv->service_id, rpid->init_seg_size, offset); + init_toi = rpid->init_toi; } + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Sending init segment %s\n", serv->log_name, rpid->init_seg_name)); + u32 codepoint; + if (rpid->init_seg_sent) { + codepoint = 7; + } else { + rpid->init_seg_sent = GF_FALSE; + codepoint = 5; + } + //send init asap + routeout_send_file(ctx, serv, init_sock, init_tsi, init_toi, (u8 *) rpid->init_seg_data, rpid->init_seg_size, codepoint, 0, serv->use_flute); + if (ctx->reporting_on) { ctx->total_size += rpid->init_seg_size; ctx->total_bytes = rpid->init_seg_size; ctx->nb_resources++; } + if (ctx->llmode) + rpid->last_init_push = gf_sys_clock_high_res(); } } //send child m3u8 asap if (send_hls_child && rpid->hld_child_pl) { u32 hls_len = (u32) strlen(rpid->hld_child_pl); - u32 offset = 0; - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Sending HLS sub playlist %s: \n%s\n", rpid->hld_child_pl_name, rpid->hld_child_pl)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Sending HLS sub playlist %s\n", rpid->route->log_name, rpid->hld_child_pl_name)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s HLS sub playlist content:\n%s\n", rpid->route->log_name, rpid->hld_child_pl)); - while (offset < hls_len) { + if (serv->use_flute) { + GF_Socket *hls_sock = ctx->use_inband ? rpid->rlct->sock : ctx->sock_dvb_mabr; + u32 hls_tsi = ctx->use_inband ? rpid->tsi : ctx->dvb_mabr_tsi; + + routeout_send_file(ctx, serv, hls_sock, hls_tsi, rpid->hls_child_toi, rpid->hld_child_pl, hls_len, 0, 0, GF_TRUE); + } else { //we use codepoint 1 (NRT - file mode) for subplaylists - offset += routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, ROUTE_INIT_TOI-1, 1, (u8 *) rpid->hld_child_pl, hls_len, offset, serv->service_id, hls_len, offset); + routeout_send_file(ctx, serv, rpid->rlct->sock, rpid->tsi, ROUTE_INIT_TOI-1-rpid->hld_child_pl_version, rpid->hld_child_pl, hls_len, 1, 0, GF_FALSE); } + if (ctx->reporting_on) { ctx->total_size += hls_len; ctx->total_bytes = hls_len; @@ -1781,8 +2508,13 @@ } rpid->update_hls_child_pl = GF_FALSE; } + //update fdt + if (rpid->push_frag_name) { + routeout_send_fdt(ctx, serv, rpid); + rpid->push_frag_name = GF_FALSE; + } - while ((rpid->pck_offset < rpid->pck_size) || rpid->force_tol_send) { + while ((rpid->pck_offset < rpid->pck_size) || rpid->force_send_empty) { u32 sent, codepoint; //we have timing info, let's regulate if (rpid->current_cts_us!=GF_FILTER_NO_TS) { @@ -1809,26 +2541,34 @@ } } } + if (ctx->dvb_mabr && !rpid->pck_offset && rpid->raw_file) { + routeout_send_fdt(ctx, serv, rpid); + } //we use codepoint 8 (media segment, file mode) for media segments, otherwise as listed in S-TSID codepoint = rpid->raw_file ? rpid->fmtp : 8; - sent = routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, rpid->current_toi, codepoint, (u8 *) rpid->pck_data, rpid->pck_size, rpid->pck_offset, serv->service_id, rpid->full_frame_size, rpid->pck_offset + rpid->frag_offset); + //ll mode in flute, each packet is sent as an object so use packet offset instead of file offset + if (ctx->dvb_mabr && ctx->llmode) { + sent = routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, rpid->current_toi, codepoint, (u8 *) rpid->pck_data, rpid->pck_size, rpid->pck_offset, serv, rpid->pck_size, rpid->pck_offset, 0, serv->use_flute, GF_TRUE); + } else { + sent = routeout_lct_send(ctx, rpid->rlct->sock, rpid->tsi, rpid->current_toi, codepoint, (u8 *) rpid->pck_data, rpid->pck_size, rpid->pck_offset, serv, rpid->full_frame_size, rpid->pck_offset + rpid->frag_offset, 0, serv->use_flute, GF_TRUE); + } rpid->pck_offset += sent; if (ctx->reporting_on) { ctx->total_bytes += sent; } - rpid->force_tol_send = GF_FALSE; + rpid->force_send_empty = GF_FALSE; } assert (rpid->pck_offset <= rpid->pck_size); if (rpid->pck_offset == rpid->pck_size) { //print fragment push info except if single fragment if (rpid->frag_idx || !rpid->full_frame_size) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE pushed fragment %s#%d (%d bytes) in "LLU" us - target push "LLU" us\n", rpid->seg_name, rpid->frag_idx+1, rpid->pck_size, ctx->clock - rpid->clock_at_pck, rpid->current_dur_us)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s pushed fragment %s#%d (%d bytes) in "LLU" us - target push "LLU" us\n", rpid->route->log_name, rpid->seg_name, rpid->frag_idx+1, rpid->pck_size, ctx->clock - rpid->clock_at_pck, rpid->current_dur_us)); } #ifndef GPAC_DISABLE_LOG //print full object push info if (rpid->full_frame_size && (gf_log_get_tool_level(GF_LOG_ROUTE)>=GF_LOG_INFO)) { - char szFInfo1000, szSID31; + char szFInfo1000, szSID31, szDelay100; u64 seg_clock, target_push_dur; if (rpid->pck_dur_at_frame_start) { @@ -1843,12 +2583,18 @@ else szSID0 = 0; + if (rpid->diff_send_at_frame_start>=0) { + sprintf(szDelay, " started %d ms after request (%d ms after reception)", rpid->diff_send_at_frame_start, rpid->diff_recv_at_frame_start); + } else { + szDelay0 = 0; + } + if (rpid->frag_idx) snprintf(szFInfo, 100, "%s%s (%d frags %d bytes)", szSID, rpid->seg_name, rpid->frag_idx+1, rpid->full_frame_size); else snprintf(szFInfo, 100, "%s%s (%d bytes)", szSID, rpid->seg_name, rpid->full_frame_size); - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Pushed %s in "LLU" us - target push "LLU" us\n", szFInfo, ctx->clock - rpid->clock_at_frame_start, target_push_dur)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Pushed %s in "LLU" us - target push "LLU" us%s\n", rpid->route->log_name, szFInfo, ctx->clock - rpid->clock_at_frame_start, target_push_dur, szDelay)); //real-time stream, check we are not out of sync if (!rpid->raw_file) { @@ -1857,18 +2603,18 @@ //if segment clock time is greater than clock, we've been pushing too fast if (seg_clock > ctx->clock) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Segment %s pushed early by "LLU" us\n", rpid->seg_name, seg_clock - ctx->clock)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Segment %s pushed early by "LLU" us\n", rpid->route->log_name, rpid->seg_name, seg_clock - ctx->clock)); } //otherwise we've been pushing too slowly else if (ctx->clock > 1000 + seg_clock) { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Segment %s pushed too late by "LLU" us\n", rpid->seg_name, ctx->clock - seg_clock)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Segment %s pushed too late by "LLU" us (target push duration %u us)\n", rpid->route->log_name, rpid->seg_name, ctx->clock - seg_clock, target_push_dur)); } if (rpid->pck_dur_at_frame_start && ctx->llmode) { u64 seg_rate = gf_timestamp_rescale(rpid->full_frame_size * 8, rpid->pck_dur_at_frame_start, rpid->timescale); if (seg_rate > rpid->bitrate) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Segment %s rate "LLU" but stream rate "LLU", updating bitrate\n", rpid->seg_name, seg_rate, rpid->bitrate)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Segment %s rate "LLU" but stream rate "LLU", updating bitrate\n", rpid->route->log_name, rpid->seg_name, seg_rate, rpid->bitrate)); rpid->bitrate = (u32) seg_rate; } } @@ -1907,7 +2653,7 @@ u32 i, count, len, comp_size; s32 timezone, h, m; u64 diff = ctx->clock - ctx->last_lls_clock; - if (diff < ctx->carousel) { + if (ctx->last_lls_clock && (diff < ctx->carousel)) { u64 next_sched = ctx->carousel - diff; if (next_sched < ctx->reschedule_us) ctx->reschedule_us = next_sched; @@ -1937,7 +2683,7 @@ gf_dynstrcat(&payload_text, gf_net_time_is_dst() ? "true" : "false", NULL); gf_dynstrcat(&payload_text, "\"/>\n", NULL); - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Updating ATSC3 LLS.SysTime:\n%s\n", payload_text)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Updating ATSC3 LLS.SysTime:\n%s\n", ctx->log_name, payload_text)); len = (u32) strlen(payload_text); comp_size = 2*len; payload = gf_malloc(sizeof(char)*(comp_size+4)); @@ -1957,7 +2703,7 @@ ctx->lls_time_table_len = comp_size; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Sending ATSC3 LLS.SysTime\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Sending ATSC3 LLS.SysTime\n", ctx->log_name)); gf_sk_send(ctx->sock_atsc_lls, ctx->lls_time_table, ctx->lls_time_table_len); ctx->bytes_sent += ctx->lls_time_table_len; @@ -1978,14 +2724,16 @@ rpid = gf_list_get(serv->pids, 0); + GF_PropertyEntry *pe=NULL; p = gf_filter_pid_get_property_str(rpid->pid, "ATSC3ShortServiceName"); if (!p) - p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_SERVICE_NAME); + p = gf_filter_pid_get_info(rpid->pid, GF_PROP_PID_SERVICE_NAME, &pe); service_name = (p && p->value.string) ? p->value.string : "GPAC"; len = (u32) strlen(service_name); if (len>7) len = 7; strncpy(szIP, service_name, len); szIPlen = 0; + gf_filter_release_property(pe); // ATSC 3.0 major channel number starts at 2. This really should be set rather than using the default. u32 major = 2; @@ -2014,22 +2762,23 @@ " <Service serviceId=\"%d\" globalServiceID=\"urn:atsc:gpac:%d:%d\" sltSvcSeqNum=\"0\" protected=\"false\" majorChannelNo=\"%d\" minorChannelNo=\"%d\" serviceCategory=\"%d\" shortServiceName=\"%s\" hidden=\"%s\" hideInGuide=\"%s\" broadbandAccessRequired=\"false\" configuration=\"%s\"> \n", sid, ctx->bsid, sid, major, minor, service_cat, szIP, hidden, hideInESG, configuration); gf_dynstrcat(&payload_text, tmp, NULL); - src_ip = ctx->ifce; + src_ip = ctx->ifce_ip; if (!src_ip) { if (gf_sk_get_local_ip(serv->rlct_base->sock, szIP)!=GF_OK) strcpy(szIP, "127.0.0.1"); src_ip = szIP; } + int res = snprintf(tmp, 1000, " <BroadcastSvcSignaling slsProtocol=\"1\" slsDestinationIpAddress=\"%s\" slsDestinationUdpPort=\"%d\" slsSourceIpAddress=\"%s\"/>\n" " </Service>\n", serv->rlct_base->ip, serv->rlct_base->port, src_ip); if (res<0) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE String truncated will trying to write: <BroadcastSvcSignaling slsProtocol=\"1\" slsDestinationIpAddress=\"%s\" slsDestinationUdpPort=\"%d\" slsSourceIpAddress=\"%s\"/>\n", serv->rlct_base->ip, serv->rlct_base->port, src_ip)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s String truncated will trying to write: <BroadcastSvcSignaling slsProtocol=\"1\" slsDestinationIpAddress=\"%s\" slsDestinationUdpPort=\"%d\" slsSourceIpAddress=\"%s\"/>\n", serv->log_name, serv->rlct_base->ip, serv->rlct_base->port, src_ip)); } gf_dynstrcat(&payload_text, tmp, NULL); } gf_dynstrcat(&payload_text, "</SLT>\n", NULL); - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Updating ATSC3 LLS.SLT:\n%s\n", payload_text)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Updating ATSC3 LLS.SLT:\n%s\n", ctx->log_name, payload_text)); len = (u32) strlen(payload_text); comp_size = 2*len; @@ -2050,11 +2799,471 @@ ctx->lls_slt_table_len = comp_size; } - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Sending ATSC3 LLS SysTime and SLT\n")); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Sending ATSC3 LLS SysTime and SLT\n", ctx->log_name)); gf_sk_send(ctx->sock_atsc_lls, ctx->lls_slt_table, ctx->lls_slt_table_len); ctx->bytes_sent += ctx->lls_slt_table_len; } +static char *mabr_get_carousel_info(GF_ROUTEOutCtx *ctx, ROUTEService *serv, Bool inject_names, u32 *out_tot_rate) +{ + char tmp2000; + u32 carousel_size=0, tot_rate=0; + char *carousel_text = NULL; + if (serv->manifest) carousel_size += (u32) strlen(serv->manifest); + if (serv->manifest_alt) carousel_size += (u32) strlen(serv->manifest_alt); + + u32 carousel_us = ctx->carousel; + u32 j, nb_pids=gf_list_count(serv->pids); + for (j=0;j<nb_pids; j++) { + ROUTEPid *rpid = gf_list_get(serv->pids, j); + carousel_size += rpid->init_seg_size; + if (rpid->hld_child_pl) + carousel_size += (u32) strlen(rpid->hld_child_pl); + if (rpid->raw_file) { + carousel_size += rpid->pck_size; + if (rpid->carousel_time_us) { + tot_rate += (u32) (rpid->pck_size * 8000000 / rpid->carousel_time_us); + } + } else if (!rpid->manifest_type) { + tot_rate += rpid->bitrate; + } + if (rpid->carousel_time_us && (rpid->carousel_time_us < carousel_us)) + carousel_us = (u32) rpid->carousel_time_us; + } + if (out_tot_rate) *out_tot_rate = tot_rate; + + gf_dynstrcat(&carousel_text, "<ObjectCarousel aggregateTransportSize=\"", NULL); + sprintf(tmp, "%u", carousel_size); + gf_dynstrcat(&carousel_text, tmp, NULL); + gf_dynstrcat(&carousel_text, "\">\n", NULL); + + if (inject_names) { + //announce manifest and init segs + gf_dynstrcat(&carousel_text, "<PresentationManifests serviceIdRef=\"", NULL); + gf_dynstrcat(&carousel_text, serv->service_name, NULL); + gf_dynstrcat(&carousel_text, "\" targetAcquisitionLatency=\"PT", NULL); + sprintf(tmp, "%u", (u32) (carousel_us/1000000)); + gf_dynstrcat(&carousel_text, tmp, NULL); + gf_dynstrcat(&carousel_text, "S\"/>\n", NULL); + + gf_dynstrcat(&carousel_text, "<InitSegments serviceIdRef=\"", NULL); + gf_dynstrcat(&carousel_text, serv->service_name, NULL); + gf_dynstrcat(&carousel_text, "\" targetAcquisitionLatency=\"PT", NULL); + gf_dynstrcat(&carousel_text, tmp, NULL); + gf_dynstrcat(&carousel_text, "S\"/>\n", NULL); + } + gf_dynstrcat(&carousel_text, "</ObjectCarousel>\n", NULL); + return carousel_text; +} + +static void routeout_update_mabr_manifest(GF_ROUTEOutCtx *ctx) +{ + char *payload_text = NULL; + char tmp2000; + u32 i, count; + if (ctx->dvb_mabr_config) return; + + count = gf_list_count(ctx->services); + snprintf(tmp, 1000, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MulticastGatewayConfiguration xmlns=\"urn:dvb:metadata:MulticastSessionConfiguration:2023\""); + gf_dynstrcat(&payload_text, tmp, NULL); + //todo, validity ? : snprintf(tmp, 1000, " validUntil=\"2030-01-01T00:00:00:000F1000\""; + + gf_dynstrcat(&payload_text, ">\n", NULL); + + for (i=0; i<count; i++) { + ROUTEService *serv = gf_list_get(ctx->services, i); + //not ready + if (!serv->manifest_mime && serv->dash_mode) { + gf_free(payload_text); + return; + } + + gf_dynstrcat(&payload_text, "<MulticastGatewayConfigurationTransportSession transportSecurity=\"", NULL); + gf_dynstrcat(&payload_text, ctx->csum ? "integrity\">\n" : "none\">\n", NULL); + gf_dynstrcat(&payload_text, "<TransportProtocol protocolIdentifier=\"urn:dvb:metadata:cs:MulticastTransportProtocolCS:2019:", NULL); + gf_dynstrcat(&payload_text, serv->use_flute ? "FLUTE" : "ROUTE", NULL); + gf_dynstrcat(&payload_text, "\" protocolVersion=\"1\"/>\n", NULL); + gf_dynstrcat(&payload_text, "<EndpointAddress>\n", NULL); + if (ctx->ifce_ip && ctx->ssm) { + gf_dynstrcat(&payload_text, "<NetworkSourceAddress>", NULL); + gf_dynstrcat(&payload_text, ctx->ifce_ip, NULL); + gf_dynstrcat(&payload_text, "</NetworkSourceAddress>\n", NULL); + } + gf_dynstrcat(&payload_text, "<NetworkDestinationGroupAddress>", NULL); + //- for FLUTE we use the main port to deliver FDT, manifests and init - we could split by service as done for ROUTE + //- for ROUTE we must send the STSID, on the session port + gf_dynstrcat(&payload_text, serv->use_flute ? ctx->ip : serv->rlct_base->ip, NULL); + gf_dynstrcat(&payload_text, "</NetworkDestinationGroupAddress>\n<TransportDestinationPort>", NULL); + sprintf(tmp, "%u", serv->use_flute ? ctx->dvb_mabr_port : serv->rlct_base->port); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "</TransportDestinationPort>\n<MediaTransportSessionIdentifier>", NULL); + sprintf(tmp, "%u", ctx->dvb_mabr_tsi); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "</MediaTransportSessionIdentifier>\n</EndpointAddress>\n", NULL); + + u32 tot_rate = 0; + char *carousel_text = mabr_get_carousel_info(ctx, serv, serv->dash_mode, &tot_rate); + + //add bitrate info + sprintf(tmp, "<BitRate average=\"%u\" maximum=\"%u\"/>\n", tot_rate, tot_rate); + gf_dynstrcat(&payload_text, tmp, NULL); + + //carousel info + if (carousel_text) { + gf_dynstrcat(&payload_text, carousel_text, NULL); + gf_free(carousel_text); + } + gf_dynstrcat(&payload_text, "</MulticastGatewayConfigurationTransportSession>\n", NULL); + } + + + for (i=0; i<count; i++) { + u32 k; + const GF_PropertyValue *p; + ROUTEPid *rpid; + ROUTEService *serv = gf_list_get(ctx->services, i); + if (!serv->manifest_mime && serv->dash_mode) continue; + + u32 sid = serv->service_id; + if (!sid) sid = 1; + + const GF_PropertyValue *alt_base_urls = NULL; + Bool skip_source_repair = GF_FALSE; + Bool use_repair = GF_FALSE; + + rpid = gf_list_get(serv->pids, 0); + gf_dynstrcat(&payload_text, "<MulticastSession serviceIdentifier=\"", NULL); + gf_dynstrcat(&payload_text, serv->service_name, NULL); + gf_dynstrcat(&payload_text, "\"", NULL); + + p = gf_filter_pid_get_property_str(rpid->pid, "DVBPlaybackOffset"); + sprintf(tmp, " contentPlaybackAvailabilityOffset=\"PT%dS\"", p ? p->value.uint : 4); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, ">\n", NULL); + + alt_base_urls = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_MABR_URLS); + if (alt_base_urls) { + for (k=0; k<alt_base_urls->value.string_list.nb_items; k++) { + char *src = strchr(alt_base_urls->value.string_list.valsk, ';'); + if (!src) src = alt_base_urls->value.string_list.valsk; + else src++; + if (stricmp(src, "src")) continue; + if (alt_base_urls->value.string_list.valsk0 != '-') continue; + skip_source_repair=GF_TRUE; + if (alt_base_urls->value.string_list.nb_items==1) { + alt_base_urls = NULL; + } + break; + } + } + + //inject source by default ? + if (serv->manifest_server && !strstr(serv->manifest_server, "mabr://")) { + if (!skip_source_repair) use_repair = GF_TRUE; + } else { + skip_source_repair = GF_TRUE; + + } + if (alt_base_urls) + use_repair = GF_TRUE; + + if (serv->manifest_type & 1) { + char *man_uri = NULL; + char *man_url = NULL; + //build URL, don't forget the port + if (serv->manifest_server) { + gf_dynstrcat(&man_url, serv->manifest_server, NULL); + if (serv->manifest_server_port) { + char szPort100; + sprintf(szPort, ":%u", serv->manifest_server_port); + gf_dynstrcat(&man_url, szPort, NULL); + } + gf_dynstrcat(&man_url, serv->manifest_url, "/"); + gf_dynstrcat(&man_url, serv->manifest_name, NULL); + } else { + gf_dynstrcat(&man_url, serv->manifest_name, NULL); + } + //build URI + gf_dynstrcat(&man_uri, serv->service_base_uri, NULL); + gf_dynstrcat(&man_uri, serv->manifest_name, "/"); + + gf_dynstrcat(&payload_text, "<PresentationManifestLocator manifestId=\"", NULL); + sprintf(tmp, "gpac_mani_serv_%u", serv->service_id); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "\" contentType=\"", NULL); + gf_dynstrcat(&payload_text, serv->manifest_mime, NULL); + //set transportObjectURI to the URI advertised in FDT - cf discussion in #3030 + gf_dynstrcat(&payload_text, "\" transportObjectURI=\"", NULL); + gf_dynstrcat(&payload_text, man_uri, NULL); + gf_dynstrcat(&payload_text, "\">", NULL); + //we set as content the original location + if (!skip_source_repair) + gf_dynstrcat(&payload_text, man_url, NULL); + + gf_dynstrcat(&payload_text, "</PresentationManifestLocator>\n", NULL); + gf_free(man_uri); + gf_free(man_url); + } + if (serv->manifest_type & 2) { + char *manifest_server = serv->manifest_server; + char *manifest_mime = serv->manifest_mime; + char *manifest_name = serv->manifest_name; + char *manifest_url = serv->manifest_url; + u32 manifest_port = serv->manifest_server_port; + + if (serv->manifest_type & 1) { + manifest_server = serv->manifest_alt_server; + manifest_mime = serv->manifest_alt_mime; + manifest_name = serv->manifest_alt_name; + manifest_url = serv->manifest_alt_url; + manifest_port = serv->manifest_alt_server_port; + } + + char *man_uri = NULL; + char *man_url = NULL; + if (manifest_server) { + gf_dynstrcat(&man_url, manifest_server, NULL); + if (manifest_port) { + char szPort100; + sprintf(szPort, ":%u", manifest_port); + gf_dynstrcat(&man_url, szPort, NULL); + } + gf_dynstrcat(&man_url, manifest_url, "/"); + gf_dynstrcat(&man_url, manifest_name, NULL); + } else { + gf_dynstrcat(&man_url, manifest_name, NULL); + } + + gf_dynstrcat(&man_uri, serv->service_base_uri, NULL); + gf_dynstrcat(&man_uri, manifest_name, "/"); + + gf_dynstrcat(&payload_text, "<PresentationManifestLocator manifestId=\"", NULL); + sprintf(tmp, "gpac_mani_serv_%u_hls", serv->service_id); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "\" contentType=\"", NULL); + gf_dynstrcat(&payload_text, manifest_mime, NULL); + //see notes above + gf_dynstrcat(&payload_text, "\" transportObjectURI=\"", NULL); + gf_dynstrcat(&payload_text, man_uri, NULL); + gf_dynstrcat(&payload_text, "\">", NULL); + if (!skip_source_repair) + gf_dynstrcat(&payload_text, man_url, NULL); + gf_dynstrcat(&payload_text, "</PresentationManifestLocator>\n", NULL); + gf_free(man_uri); + gf_free(man_url); + } + + u32 j, nb_pids = gf_list_count(serv->pids); + for (j=0; j<nb_pids; j++) { + ROUTEPid *rpid = gf_list_get(serv->pids, j); + if (rpid->manifest_type) continue; + + gf_dynstrcat(&payload_text, "<MulticastTransportSession sessionIdleTimeout=\"60000\" transportSecurity=\"", NULL); + gf_dynstrcat(&payload_text, (ctx->csum==DVB_CSUM_ALL) ? "integrity\"" : "none\"", NULL); + sprintf(tmp, " id=\"%u\"", rpid->tsi); + gf_dynstrcat(&payload_text, tmp, NULL); + //todo start, duration + + //if set to chunked for Low latency + gf_dynstrcat(&payload_text, " transmissionMode=\"", NULL); + gf_dynstrcat(&payload_text, ctx->llmode ? "chunked" : "resource", NULL); + gf_dynstrcat(&payload_text, "\"", NULL); + gf_dynstrcat(&payload_text, ">\n", NULL); + if (serv->use_flute) { + gf_dynstrcat(&payload_text, "<TransportProtocol protocolIdentifier=\"urn:dvb:metadata:cs:MulticastTransportProtocolCS:2019:FLUTE\" protocolVersion=\"1\"/>\n", NULL); + } else { + gf_dynstrcat(&payload_text, "<TransportProtocol protocolIdentifier=\"urn:dvb:metadata:cs:MulticastTransportProtocolCS:2019:ROUTE\" protocolVersion=\"1\"/>\n", NULL); + } + gf_dynstrcat(&payload_text, "<EndpointAddress>\n", NULL); + if (ctx->ssm && ctx->ifce_ip) { + gf_dynstrcat(&payload_text, "<NetworkSourceAddress>", NULL); + gf_dynstrcat(&payload_text, ctx->ifce_ip, NULL); + gf_dynstrcat(&payload_text, "</NetworkSourceAddress>\n", NULL); + } + gf_dynstrcat(&payload_text, "<NetworkDestinationGroupAddress>", NULL); + gf_dynstrcat(&payload_text, rpid->rlct->ip, NULL); + gf_dynstrcat(&payload_text, "</NetworkDestinationGroupAddress>\n<TransportDestinationPort>", NULL); + sprintf(tmp, "%u", rpid->rlct->port); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "</TransportDestinationPort>\n<MediaTransportSessionIdentifier>", NULL); + sprintf(tmp, "%u", rpid->tsi); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "</MediaTransportSessionIdentifier>\n</EndpointAddress>\n", NULL); + sprintf(tmp, "<BitRate average=\"%u\" maximum=\"%u\"/>\n", rpid->bitrate, rpid->bitrate); + gf_dynstrcat(&payload_text, tmp, NULL); + + + if (use_repair) { + sprintf(tmp, "<UnicastRepairParameters transportObjectReceptionTimeout=\"%u\" fixedBackOffPeriod=\"10\" randomBackOffPeriod=\"20\" transportObjectBaseURI=\"", ctx->recv_obj_timeout); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, serv->service_base_uri, NULL); + gf_dynstrcat(&payload_text, "\">\n", NULL); + + Bool self_found = GF_FALSE; + u32 nb_alts = alt_base_urls ? alt_base_urls->value.string_list.nb_items : 0; + for (k=0; k<nb_alts; k++) { + s32 weight=1; + Bool is_self=GF_FALSE; + char *src = alt_base_urls->value.string_list.valsk; + char *ssep = strchr(src, ';'); + if (ssep) { + ssep0=0; + weight=atoi(src); + ssep0=';'; + src = ssep+1; + } + if (!stricmp(src, "src")) { + self_found = GF_TRUE; + if (skip_source_repair) continue; + src = serv->manifest_server; + is_self=GF_TRUE; + } + if (weight<0) continue; + + gf_dynstrcat(&payload_text, "<BaseURL", NULL); + if (weight!=1) { + char szW100; + sprintf(szW, " relativeWeight=\"%u\"", weight); + gf_dynstrcat(&payload_text, szW, NULL); + } + if (is_self) { + gf_dynstrcat(&payload_text, serv->manifest_server, ">"); + gf_dynstrcat(&payload_text, serv->manifest_url, "/"); + } else { + gf_dynstrcat(&payload_text, src, ">"); + } + gf_dynstrcat(&payload_text, "</BaseURL>\n", NULL); + } + if (!skip_source_repair && !self_found && serv->manifest_server && !strstr(serv->manifest_server, "mabr://")) { + gf_dynstrcat(&payload_text, serv->manifest_server, "<BaseURL>"); + if (serv->manifest_server_port) { + char szPort100; + sprintf(szPort, ":%u", serv->manifest_server_port); + gf_dynstrcat(&payload_text, szPort, NULL); + } + gf_dynstrcat(&payload_text, serv->manifest_url, "/"); + gf_dynstrcat(&payload_text, "</BaseURL>\n", NULL); + } + gf_dynstrcat(&payload_text, "</UnicastRepairParameters>\n", NULL); + } + + //HLS + if (serv->manifest_type&2) { + gf_dynstrcat(&payload_text, "<ServiceComponentIdentifier xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" manifestIdRef=\"", NULL); + sprintf(tmp, "gpac_mani_serv_%u_hls", serv->service_id); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "\"", NULL); + gf_dynstrcat(&payload_text, " xsi:type=\"HLSComponentIdentifierType\" mediaPlaylistLocator=\"", NULL); + gf_dynstrcat(&payload_text, serv->service_base_uri, NULL); + gf_dynstrcat(&payload_text, rpid->hld_child_pl_name, "/"); + gf_dynstrcat(&payload_text, "\"", NULL); + gf_dynstrcat(&payload_text, "/>\n", NULL); + } + //DASH + if (serv->manifest_type & 1) { + const char *id; + gf_dynstrcat(&payload_text, "<ServiceComponentIdentifier xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" manifestIdRef=\"", NULL); + sprintf(tmp, "gpac_mani_serv_%u", serv->service_id); + gf_dynstrcat(&payload_text, tmp, NULL); + gf_dynstrcat(&payload_text, "\"", NULL); + + gf_dynstrcat(&payload_text, " xsi:type=\"DASHComponentIdentifierType\" periodIdentifier=\"", NULL); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_PERIOD_ID); + if (p && p->value.string) { + id = p->value.string; + } else { + if (!rpid->init_cfg_done) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing Period ID on PID using \"1\"\n", serv->log_name)); + } + id = "1"; + } + gf_dynstrcat(&payload_text, id, NULL); + gf_dynstrcat(&payload_text, "\" adaptationSetIdentifier=\"", NULL); + + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_AS_ID); + if (p) { + sprintf(tmp, "%u", p->value.uint); + id = tmp; + } else { + if (!rpid->init_cfg_done) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing AS ID on PID, using \"1\"\n", serv->log_name)); + } + id = "1"; + } + gf_dynstrcat(&payload_text, id, NULL); + gf_dynstrcat(&payload_text, "\" representationIdentifier=\"", NULL); + p = gf_filter_pid_get_property(rpid->pid, GF_PROP_PID_REP_ID); + if (p && p->value.string) { + id = p->value.string; + } else { + if (!rpid->init_cfg_done) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing representation ID on PID (broken input filter), using \"1\"\n", serv->log_name)); + } + id = "1"; + } + gf_dynstrcat(&payload_text, id, NULL); + gf_dynstrcat(&payload_text, "\"", NULL); + gf_dynstrcat(&payload_text, "/>\n", NULL); + } + if (ctx->use_inband) { + char *carousel_text = mabr_get_carousel_info(ctx, serv, GF_TRUE, NULL); + if (carousel_text) { + gf_dynstrcat(&payload_text, carousel_text, NULL); + gf_free(carousel_text); + } + } + gf_dynstrcat(&payload_text, "</MulticastTransportSession>\n", NULL); + rpid->init_cfg_done = GF_TRUE; + } + gf_dynstrcat(&payload_text, "</MulticastSession>\n", NULL); + } + gf_dynstrcat(&payload_text, "</MulticastGatewayConfiguration>\n", NULL); + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Updated MulticastGatewayConfiguration to:\n%s\n", ctx->log_name, payload_text)); + + ctx->dvb_mabr_config = payload_text; + ctx->dvb_mabr_config_len = (u32) strlen(payload_text); + //we need a new TOI since config changed + ctx->dvb_mabr_config_toi = 0; + + //reset FDT + if (ctx->dvb_mabr_fdt) { + gf_free(ctx->dvb_mabr_fdt); + ctx->dvb_mabr_fdt = NULL; + } + //force sending asap + ctx->last_dvb_mabr_clock = 0; +} + +static void routeout_send_mabr_manifest(GF_ROUTEOutCtx *ctx) +{ + //update multicast gateway config - this may trigger an FDT recompute + if (!ctx->dvb_mabr_config) routeout_update_mabr_manifest(ctx); + //update fdt if needed + if (!ctx->dvb_mabr_fdt) routeout_update_dvb_mabr_fdt(ctx, NULL, GF_FALSE); + //not ready + if (!ctx->dvb_mabr_config || !ctx->dvb_mabr_fdt) return; + if (ctx->dvb_mabr_config && ctx->last_dvb_mabr_clock) { + u64 diff = ctx->clock - ctx->last_dvb_mabr_clock; + if (diff < ctx->carousel) { + u64 next_sched = ctx->carousel - diff; + if (next_sched < ctx->reschedule_us) + ctx->reschedule_us = next_sched; + return; + } + } + + if (!ctx->bytes_sent) ctx->clock_stats = ctx->clock; + ctx->last_dvb_mabr_clock = ctx->clock; + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Sending Bootstrap info\n", ctx->log_name)); + + //send fdt + routeout_send_file(ctx, NULL, ctx->sock_dvb_mabr, ctx->dvb_mabr_tsi, 0, ctx->dvb_mabr_fdt, ctx->dvb_mabr_fdt_len, 0, ctx->dvb_mabr_fdt_instance_id, GF_TRUE); + //send mcast gateway config + routeout_send_file(ctx, NULL, ctx->sock_dvb_mabr, ctx->dvb_mabr_tsi, ctx->dvb_mabr_config_toi, ctx->dvb_mabr_config, ctx->dvb_mabr_config_len, 0, 0, GF_TRUE); +} + static GF_Err routeout_process(GF_Filter *filter) { GF_Err e = GF_OK; @@ -2089,12 +3298,39 @@ } } + if (ctx->check_pending) { + if (gf_filter_connections_pending(filter)) { + if (!ctx->check_init_clock) ctx->check_init_clock = gf_sys_clock(); + else if (gf_sys_clock() - ctx->check_init_clock > 2000) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s PIDs still pending after 2s, starting broadcast but will need to update signaling\n", ctx->log_name)); + ctx->check_pending = GF_FALSE; + } + if (ctx->check_pending) return GF_OK; + } + ctx->check_pending = GF_FALSE; + ctx->check_init_clock = 0; + } + if (ctx->sock_atsc_lls) routeout_send_lls(ctx); + //check all our services are ready count = gf_list_count(ctx->services); for (i=0; i<count; i++) { ROUTEService *serv = gf_list_get(ctx->services, i); + if (serv->is_done) continue; + e = routeout_check_service_updates(ctx, serv); + if (!serv->service_ready || (e==GF_NOT_READY)) { + gf_filter_ask_rt_reschedule(filter, 10000); + return GF_OK; + } + } + if (ctx->sock_dvb_mabr) { + routeout_send_mabr_manifest(ctx); + } + + for (i=0; i<count; i++) { + ROUTEService *serv = gf_list_get(ctx->services, i); if (!serv->is_done) { e |= routeout_process_service(ctx, serv); if (!serv->is_done) @@ -2108,7 +3344,7 @@ if (ctx->clock - ctx->clock_stats >= 1000000) { u64 rate = ctx->bytes_sent * 8 * 1000 / (ctx->clock - ctx->clock_stats); - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Mux rate "LLU" kbps\n", rate)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Mux rate "LLU" kbps\n", ctx->log_name, rate)); if (ctx->reporting_on) { u32 progress = 0; char szStatus200; @@ -2134,6 +3370,7 @@ { if (!strnicmp(url, "atsc://", 7)) return GF_FPROBE_SUPPORTED; if (!strnicmp(url, "route://", 8)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "mabr://", 7)) return GF_FPROBE_SUPPORTED; return GF_FPROBE_NOT_SUPPORTED; } static Bool routeout_use_alias(GF_Filter *filter, const char *url, const char *mime) @@ -2182,8 +3419,9 @@ { OFFS(splitlct), "split mode for LCT channels\n" "- off: all streams are in the same LCT channel\n" "- type: each new stream type results in a new LCT channel\n" - "- all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling" - , GF_PROP_UINT, "off", "off|type|all", 0}, + "- all: all streams are in dedicated LCT channel, the first stream being used for STSID signaling\n" + "- mcast: all streams are in dedicated multicast groups" + , GF_PROP_UINT, "off", "off|type|all|mcast", 0}, { OFFS(korean), "use Korean version of ATSC 3.0 spec instead of US", GF_PROP_BOOL, "false", NULL, 0}, { OFFS(llmode), "use low-latency mode", GF_PROP_BOOL, "false", NULL, GF_ARG_HINT_EXPERT}, { OFFS(brinc), "bitrate increase in percent when estimating timing in low latency mode", GF_PROP_UINT, "10", NULL, GF_ARG_HINT_EXPERT}, @@ -2191,6 +3429,15 @@ { OFFS(runfor), "run for the given time in ms", GF_PROP_UINT, "0", NULL, 0}, { OFFS(nozip), "do not zip signaling package (STSID+manifest)", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(flute), "use flute for DVB-MABR object delivery", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(csum), "send MD5 checksum for DVB flute\n" + "- no: do not send checksum\n" + "- meta: only send checksum for configuration files, manifests and init segments\n" + "- all: send checksum for everything", GF_PROP_UINT, "meta", "no|meta|all", 0}, + { OFFS(recv_obj_timeout), "set timeout period in ms before client resorts to unicast repair", GF_PROP_UINT, "50", NULL, 0}, + { OFFS(errsim), "simulate errors using a 2-state Markov chain. Value are percentages", GF_PROP_VEC2, "0.0x100.0", NULL, 0}, + { OFFS(use_inband), "send manifest and init segments in media transport sessions for MABR", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(ssm), "indicate source-specific multicast for DVB-MABR, requires `ifce` to be set", GF_PROP_BOOL, "false", NULL, 0}, {0} }; @@ -2203,13 +3450,14 @@ GF_FilterRegister ROUTEOutRegister = { .name = "routeout", - GF_FS_SET_DESCRIPTION("ROUTE output") - GF_FS_SET_HELP("The ROUTE output filter is used to distribute a live file-based session using ROUTE.\n" - "The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE signaling.\n" + GF_FS_SET_DESCRIPTION("MABR & ROUTE output") + GF_FS_SET_HELP("The ROUTE output filter is used to distribute a live file-based session using ROUTE or DVB-MABR.\n" + "The filter supports DASH and HLS inputs, ATSC3.0 signaling and generic ROUTE or DVB-MABR signaling.\n" "\n" "The filter is identified using the following URL schemes:\n" "- `atsc://`: session is a full ATSC 3.0 session\n" "- `route://IP:port`: session is a ROUTE session running on given multicast IP and port\n" + "- `mabr://IP:port`: session is a DVB-MABR session using FLUTE running on given multicast IP and port\n" "\n" "The filter only accepts input PIDs of type `FILE`.\n" "- HAS Manifests files are detected by file extension and/or MIME types, and sent as part of the signaling bundle or as LCT object files for HLS child playlists.\n" @@ -2217,11 +3465,11 @@ "- A PID without `OrigStreamType` property set is delivered as a regular LCT object file (called `raw` hereafter).\n" " \n" "For `raw` file PIDs, the filter will look for the following properties:\n" - "- `ROUTEName`: set resource name. If not found, uses basename of URL\n" - "- `ROUTECarousel`: set repeat period. If not found, uses -carousel(). If 0, the file is only sent once\n" - "- `ROUTEUpload`: set resource upload time. If not found, uses -carousel(). If 0, the file will be sent as fast as possible.\n" + "- `MCASTName`: set resource name. If not found, uses basename of URL\n" + "- `MCASTCarousel`: set repeat period. If not found, uses -carousel(). If 0, the file is only sent once\n" + "- `MCASTUpload`: set resource upload time. If not found, uses -carousel(). If 0, the file will be sent as fast as possible.\n" "\n" - "When DASHing for ROUTE or single service ATSC, a file extension, either in -dst() or in -ext(), may be used to identify the HAS session type (DASH or HLS).\n" + "When DASHing for ROUTE, DVB-MABR or single service ATSC, a file extension, either in -dst() or in -ext(), may be used to identify the HAS session type (DASH or HLS).\n" "EX \"route://IP:PORT/manifest.mpd\", \"route://IP:PORT/:ext=mpd\"\n" "\n" "When DASHing for multi-service ATSC, forcing an extension will force all service to use the same formats.\n" @@ -2232,17 +3480,25 @@ "\n" "Warning: When forwarding an existing DASH/HLS session, do NOT set any extension or manifest name.\n" "\n" - "By default, all streams in a service are assigned to a single route session, and differentiated by ROUTE TSI (see -splitlct()).\n" + "The filter will look for `MCASTIP` and `MCASTPort` properties on the incoming PID to setup multicast of each service. If not found, the default -ip() and port will be used, with port incremented by one for each new multicast stream.\n" + "\n" + "By default, all streams in a service are assigned to a single multicast session, and differentiated by TSI (see -splitlct()).\n" "TSI are assigned as follows:\n" - "- signaling TSI is always 0\n" + "- signaling TSI is always 0 for ROUTE, 1 for DVB+Flute\n" "- raw files are assigned TSI 1 and increasing number of TOI\n" "- otherwise, the first PID found is assigned TSI 10, the second TSI 20 etc ...\n" "\n" + "When -splitlct() is set to `mcast`, the IP multicast address is computed as follows:\n" + " - if `MCASTIP` is set on the PID and is different from the service multicast IP, it is used\n" + " - otherwise the service multicast IP plus one is used\n" + "The multicast port used is set as follows:\n" + "- if `MCASTPort` is set on the PID, it is used\n" + "- otherwise the same port as the service one is used." + "\n" "Init segments and HLS child playlists are sent before each new segment, independently of -carousel().\n" "# ATSC 3.0 mode\n" "In this mode, the filter allows multiple service multiplexing, identified through the `ServiceID` property.\n" - "By default, a single multicast IP is used for route sessions, each service will be assigned a different port.\n" - "The filter will look for `ROUTEIP` and `ROUTEPort` properties on the incoming PID. If not found, the default -ip() and -port() will be used.\n" + "By default (see above), a single multicast IP is used for route sessions, each service will be assigned a different port.\n" "\n" "ATSC 3.0 attributes set by using the following PID properties:\n" "- ATSC3ShortServiceName: set the short service name, maxiumu of 7 characters. If not found, `ServiceName` is checked, otherwise default to `GPAC`.\n" @@ -2258,13 +3514,31 @@ "Note: -ip() is ignored, and -first_port() is used if no port is specified in -dst().\n" "The ROUTE session will include a multi-part MIME unsigned package containing manifest and S-TSID, sent on TSI=0.\n" "\n" + "# DVB-MABR mode\n" + "In this mode, the filter allows multiple service multiplexing, identified through the `ServiceID` and `ServiceName` properties.\n" + "Note: -ip() and -first_port() are used to send the multicast gateway configuration. -first_port() is used only if no port is specified in -dst().\n" + "\n" + "The session will carry DVB-MABR gateway configuration, maifests and init segments on `TSI=1`. The -use_inband() option can be used to send manifests and init segments in media multicast sessions.\n" + "\n" + "The FLUTE session always uses a symbol length of -mtu() minus 44 bytes.\n" + "\n" + "The `MABRBaseURLs` property can be set on sources to declare a list of alternate repair servers to be injected.\n" + "Each base URL can be prefixed with `N;`, where `N` gives the relative weight of the server, a negative value skipping the server.\n" + "The special value `src` is used to indicate the source of the session.\n" + "EX gpac -i HTTP_MPD_URL:gpac::#MABRBaseURLs=-1;src,SOME_ALT_URL dashin:forward=file -o mabr://225.0.0.1:1234/\n" + "This will forward the source DASH session to multicast and:\n" + "- hide the source server as a repair URL\n" + "- add `SOME_ALT_URL` as a repair URL\n" + "\n" "# Low latency mode\n" - "When using low-latency mode, the input media segments are not re-assembled in a single packet but are instead sent as they are received.\n" + "When using low-latency mode -llmode(), the input media segments are not re-assembled in a single packet but are instead sent as they are received.\n" "In order for the real-time scheduling of data chunks to work, each fragment of the segment should have a CTS and timestamp describing its timing.\n" "If this is not the case (typically when used with an existing DASH session in file mode), the scheduler will estimate CTS and duration based on the stream bitrate and segment duration. The indicated bitrate is increased by -brinc() percent for safety.\n" "If this fails, the filter will trigger warnings and send as fast as possible.\n" "Note: The LCT objects are sent with no length (TOL header) assigned until the final segment size is known, potentially leading to a final 0-size LCT fragment signaling only the final size.\n" "\n" + "In this mode, init segments and manifests are sent at the frequency given by property `MCASTCarousel` of the source PID if set or by -carousel() option.\n" + "Indicating `MCASTCarousel=0` will disable mid-segment repeating of manifests and init segments.\n" "# Examples\n" "Since the ROUTE filter only consumes files, it is required to insert:\n" "- the dash demultiplexer in file forwarding mode when loading a DASH session\n" @@ -2280,7 +3554,7 @@ "EX gpac -i source.mp4 dasher -o route://225.1.1.0:6000/manifest.mpd:profile=live:cdur=0.2:llmode\n" "\n" "Sending a single file in ROUTE using half a second upload time, 2 seconds carousel:\n" - "EX gpac -i URL:#ROUTEUpload=0.5:#ROUTECarousel=2 -o route://225.1.1.0:6000/\n" + "EX gpac -i URL:#MCASTUpload=0.5:#MCASTCarousel=2 -o route://225.1.1.0:6000/\n" "\n" "Common mistakes:\n" "EX gpac -i source.mpd -o route://225.1.1.0:6000/\n" @@ -2290,6 +3564,11 @@ "EX gpac -i source.mpd dasher -o route://225.1.1.0:6000/\n" "EX gpac -i source.mpd dasher -o route://225.1.1.0:6000/manifest.mpd\n" "These will demultiplex the input, re-dash it and send the output of the dasher to ROUTE\n" + "\n" + "# Error simulation\n" + "It is possible to simulate errors with -errsim(). In this mode the LCT network sender implements a 2-state Markov chain:\n" + "EX gpac -i source.mpd dasher -o route://225.1.1.0:6000/:errsim=1.0x98.0\n" + "This will set a 1.0 percent chance to transition to error (not sending data over the network) and 98.0 percent chance to transition from error back to OK.\n" ) .private_size = sizeof(GF_ROUTEOutCtx), .max_extra_pids = -1, @@ -2301,13 +3580,14 @@ .configure_pid = routeout_configure_pid, .process = routeout_process, .use_alias = routeout_use_alias, - .flags = GF_FS_REG_TEMP_INIT + .flags = GF_FS_REG_TEMP_INIT, + .hint_class_type = GF_FS_CLASS_NETWORK_IO }; const GF_FilterRegister *routeout_register(GF_FilterSession *session) { if (gf_opts_get_bool("temp", "get_proto_schemes")) { - gf_opts_set_key("temp_out_proto", ROUTEOutRegister.name, "atsc,route"); + gf_opts_set_key("temp_out_proto", ROUTEOutRegister.name, "atsc,route,mabr"); } return &ROUTEOutRegister; }
View file
gpac-2.4.0.tar.gz/src/filters/out_rtp.c -> gpac-26.02.0.tar.gz/src/filters/out_rtp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / rtp output filter @@ -54,6 +54,7 @@ char *info, *url, *email; s32 runfor, tso; Bool latm; + Double start, speed; /*timeline origin of our session (all tracks) in microseconds*/ u64 sys_clock_at_init; @@ -74,9 +75,7 @@ Bool wait_for_loop; u64 microsec_ts_init; - //0: not single stream, 1: input is raw media, 2: input is TS - u32 single_stream; - GF_FilterCapability in_caps2; + GF_FilterCapability in_caps3; char szExt10; } GF_RTPOutCtx; @@ -153,7 +152,7 @@ if (dur>max_dur) max_dur = dur; } p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_PLAYBACK_MODE); - if (!p || (p->value.uint<GF_PLAYBACK_MODE_FASTFORWARD)) + if (!p || (p->value.uint<GF_PLAYBACK_MODE_SEEK)) disable_seek = GF_TRUE; } @@ -184,7 +183,7 @@ u32 dsi_len = 0; u32 dsi_enh_len = 0; u32 nb_chan = 0; - const GF_PropertyValue *p; + const GF_PropertyValue *p; GF_RTPOutStream *stream = gf_list_get(streams, i); if (!stream->rtp) continue; @@ -268,14 +267,31 @@ } if (is_rtsp) { - gf_fprintf(sdp_out, "a=control:%s=%d\n", stream->ctrl_name ? stream->ctrl_name : "trackID", stream->ctrl_id); + gf_fprintf(sdp_out, "a=control:%s_%d\n", stream->ctrl_name ? stream->ctrl_name : "trackID", stream->ctrl_id); } } gf_fprintf(sdp_out, "\n"); return GF_OK; } -GF_Err rtpout_init_streamer(GF_RTPOutStream *stream, const char *ipdest, Bool inject_xps, Bool use_mpeg4_signaling, Bool use_latm, u32 payt, u32 mtu, u32 ttl, const char *ifce, Bool is_rtsp, u32 *base_pid_id, u32 file_mode, const char *netcap_id) +#define M2TS_MIME_TYPES "video/mpeg-2|video/mp2t|video/mpeg" +#define M2TS_FILE_EXTS "ts|m2t|mts|dmb|trp" +static Bool check_mime_ext(const char *string, const char *pattern) +{ + char szLwr100; + if (!pattern) return GF_FALSE; + + strncpy(szLwr, pattern, 99); + szLwr99=0; + strlwr(szLwr); + u32 len = (u32) strlen(szLwr); + char *sep = strstr(string, szLwr); + if (!sep) return GF_FALSE; + if (!seplen || (seplen == '|')) return GF_TRUE; + return GF_FALSE; +} + +GF_Err rtpout_init_streamer(GF_RTPOutStream *stream, const char *ipdest, Bool inject_xps, Bool use_mpeg4_signaling, Bool use_latm, u32 payt, u32 mtu, u32 ttl, const char *ifce, Bool is_rtsp, u32 *base_pid_id, const char *netcap_id) { Bool disable_mpeg4 = GF_FALSE; u32 flags, average_size, max_size, max_tsdelta, codecid, const_dur, nb_ch, samplerate, max_cts_offset, bandwidth, IV_length, KI_length, dsi_len, max_ptime, au_sn_len; @@ -360,9 +376,17 @@ case GF_STREAM_VISUAL: break; case GF_STREAM_FILE: - if (file_mode==2) { + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_MIME); + if (p && check_mime_ext(M2TS_MIME_TYPES, p->value.string)) { stream->codecid = codecid = GF_CODECID_FAKE_MP2T; - stream->timescale = 90000; + } else { + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_FILE_EXT); + if (p && check_mime_ext(M2TS_FILE_EXTS, p->value.string) ) { + stream->codecid = codecid = GF_CODECID_FAKE_MP2T; + } else { + return GF_FILTER_NOT_SUPPORTED; + } } break; default: @@ -405,6 +429,8 @@ else gf_odf_avc_cfg_del(avcc); } + } else { + return GF_NOT_READY; } break; case GF_CODECID_HEVC: @@ -465,6 +491,8 @@ if (hvcc) gf_odf_hevc_cfg_del(hvcc); } } + } else { + return GF_NOT_READY; } break; case GF_CODECID_AAC_MPEG4: @@ -481,6 +509,8 @@ } if (use_latm) flags |= GP_RTP_PCK_USE_LATM_AAC; + if (!dsi) + return GF_NOT_READY; break; } @@ -501,7 +531,7 @@ p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_BITRATE); bandwidth = p ? p->value.uint : 0; - if (codecid==GF_CODECID_AAC_MPEG4) + if (codecid==GF_CODECID_AAC_MPEG4) {} if (is_crypted) { p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ISMA_SELECTIVE_ENC); @@ -517,6 +547,12 @@ p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DEPENDENCY_ID); if (p) stream->depends_on = p->value.uint; + if (!payt) + flags |= GP_RTP_PCK_FORCE_STATIC_ID; + //prefer pre-defined payload types + else + flags |= GP_RTP_PCK_USE_STATIC_ID; + GF_RTPStreamerConfig cfg; memset(&cfg, 0, sizeof(GF_RTPStreamerConfig)); cfg.streamType = stream->streamtype; @@ -546,7 +582,11 @@ cfg.au_sn_len = au_sn_len; cfg.netcap_id = netcap_id; - stream->rtp = gf_rtp_streamer_new_ex(&cfg, is_rtsp); + if (!stream->rtp) + stream->rtp = gf_rtp_streamer_new_ex(&cfg, is_rtsp); + //we cannot change codec on the fly in an RTP session + else if (gf_rtp_streamer_get_codecid(stream->rtp) != codecid) + return GF_FILTER_NOT_SUPPORTED; if (!stream->rtp) { GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTPOut Could not initialize RTP for stream %s: not supported\n", gf_filter_pid_get_name(stream->pid) )); @@ -573,6 +613,10 @@ *base_pid_id = p->value.uint; gf_rtp_streamer_disable_auto_rtcp(stream->rtp); } + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_NETWORK_HINT, stream->pid); + evt.net_hint.mtu_size = mtu; + gf_filter_pid_send_event(stream->pid, &evt); return GF_OK; } @@ -586,7 +630,7 @@ const char *ip = ctx->ip; if (!ip) ip = "127.0.0.1"; - if (ctx->single_stream) return GF_OK; + if (ctx->dst) return GF_OK; e = rtpout_create_sdp(ctx->streams, GF_FALSE, ip, ctx->info, "livesession", ctx->url, ctx->email, ctx->base_pid_id, &sdp_out, &sess_id); if (e) return e; @@ -646,14 +690,11 @@ GF_RTPOutCtx *ctx = (GF_RTPOutCtx *) gf_filter_get_udta(filter); GF_Err e = GF_OK; GF_RTPOutStream *stream; - u16 first_port; - u32 streamType, payt; + u32 streamType, dyn_payt; const GF_PropertyValue *p; - first_port = ctx->port; - if (is_remove) { - GF_RTPOutStream *t =gf_filter_pid_get_udta(pid); + GF_RTPOutStream *t = gf_filter_pid_get_udta(pid); if (t) { if (ctx->active_stream==t) ctx->active_stream = NULL; gf_list_del_item(ctx->streams, t); @@ -681,20 +722,8 @@ gf_list_del_item(ctx->streams, stream); rtpout_del_stream(stream); } - if (!ctx->dst) - return GF_FILTER_NOT_SUPPORTED; - p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); - if (p && p->value.string && !strcmp(p->value.string, "video/mpeg-2")) { - ctx->single_stream = 2; - } else { - p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); - if (p && p->value.string && !strcmp(p->value.string, "ts")) { - ctx->single_stream = 2; - } else { - return GF_FILTER_NOT_SUPPORTED; - } - } - + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (!p) return GF_FILTER_NOT_SUPPORTED; break; default: break; @@ -707,13 +736,15 @@ stream->streamtype = streamType; stream->min_dts = GF_FILTER_NO_TS; gf_filter_pid_set_udta(pid, stream); - if (ctx->single_stream) { + if (ctx->dst) { GF_FilterEvent evt; - gf_filter_pid_init_play_event(pid, &evt, 0, 1.0, "RTPOut"); + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "RTPOut"); gf_filter_pid_send_event(pid, &evt); } + + stream->port = rtpout_check_next_port(ctx, ctx->port); } - if (!ctx->single_stream) { + if (!ctx->dst) { if (!ctx->opid) { ctx->opid = gf_filter_pid_new(filter); } @@ -723,30 +754,25 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, NULL ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("sdp") ); gf_filter_pid_set_name(ctx->opid, "SDP"); - } else { - char *dst = ctx->dst+6; - char *sep = strchr(dst, ':'); - if (sep) { - first_port = ctx->port = atoi(sep+1); - sep0 = 0; - if (ctx->ip) gf_free(ctx->ip); - ctx->ip = gf_strdup(dst); - sep0 = ':'; - } } - stream->port = rtpout_check_next_port(ctx, first_port); + //if direct rtp stream, force no dynamic payload type + if (ctx->dst) + dyn_payt = 0; + else + dyn_payt = ctx->payt + gf_list_find(ctx->streams, stream); - payt = ctx->payt + gf_list_find(ctx->streams, stream); //init rtp - e = rtpout_init_streamer(stream, ctx->ip ? ctx->ip : "127.0.0.1", ctx->xps, ctx->mpeg4, ctx->latm, payt, ctx->mtu, ctx->ttl, ctx->ifce, GF_FALSE, &ctx->base_pid_id, ctx->single_stream, gf_filter_get_netcap_id(filter)); - if (e) return e; - + e = rtpout_init_streamer(stream, ctx->ip ? ctx->ip : "127.0.0.1", ctx->xps, ctx->mpeg4, ctx->latm, dyn_payt, ctx->mtu, ctx->ttl, ctx->ifce, GF_FALSE, &ctx->base_pid_id, gf_filter_get_netcap_id(filter)); + if (e) { + if (e==GF_NOT_READY) return GF_OK; + return e; + } stream->selected = GF_TRUE; if (ctx->loop) { p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); - if (!p || (p->value.uint<GF_PLAYBACK_MODE_FASTFORWARD)) { + if (!p || (p->value.uint<GF_PLAYBACK_MODE_SEEK)) { ctx->loop = GF_FALSE; GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTPOut PID %s cannot be seek, disabling loop\n", gf_filter_pid_get_name(pid) )); } @@ -764,6 +790,18 @@ if (ctx->payt>127) ctx->payt = 127; ctx->streams = gf_list_new(); + if (ctx->dst) { + char *dst = ctx->dst+6; + char *sep = strchr(dst, ':'); + if (sep) { + ctx->port = atoi(sep+1); + sep0 = 0; + if (ctx->ip) gf_free(ctx->ip); + ctx->ip = gf_strdup(dst); + sep0 = ':'; + } + } + if (ctx->dst && (ctx->ext || ctx->mime) ) { //static cap, streamtype = file ctx->in_caps0.code = GF_PROP_PID_STREAM_TYPE; @@ -782,9 +820,12 @@ ctx->in_caps1.val = PROP_NAME( ctx->szExt ); ctx->in_caps1.flags = GF_CAPS_INPUT; } - gf_filter_override_caps(filter, ctx->in_caps, 2); + ctx->in_caps2.code = GF_PROP_PID_TIMESCALE; + ctx->in_caps2.val = PROP_UINT( 0 ); + ctx->in_caps2.flags = GF_CAPS_INPUT|GF_CAPFLAG_PRESENT; + + gf_filter_override_caps(filter, ctx->in_caps, 3); gf_filter_set_max_extra_input_pids(filter, 0); - ctx->single_stream = GF_TRUE; } return GF_OK; } @@ -1088,7 +1129,7 @@ for (i=0; i<count; i++) { GF_RTPOutStream *astream = gf_list_get(streams, i); - if (!astream->has_pck) break; + if (!astream->selected || !astream->has_pck) break; u32 ts = (u32) (astream->current_cts + astream->ts_offset + astream->rtp_ts_offset); gf_rtp_streamer_send_rtcp(stream->rtp, GF_TRUE, ts, ntp_type, ntp_sec, ntp_frac); @@ -1201,9 +1242,11 @@ return GF_FPROBE_NOT_SUPPORTED; } +//regular caps when solving to sdp +//for direct rtp:// scheme invocation, caps are overridden in rtpin_initialize static const GF_FilterCapability RTPOutCaps = { - //anything else (not file and framed) result in manifest PID + //media stream (not file and framed) result in SDP CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), @@ -1212,13 +1255,18 @@ CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "sdp"), CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/sdp"), {0}, - //anything else (not file and framed) result in media pids not file + //restamped files result in SDP (only media type for now for this mode) + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"), + CAP_UINT(GF_CAPS_INPUT|GF_CAPFLAG_PRESENT, GF_PROP_PID_TIMESCALE, 0), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "sdp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/sdp"), + {0}, + //accept anything not file-based from loaded filters CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_UNFRAMED, GF_TRUE), - {0}, - CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), - CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "ts|m2t|mts|dmb|trp"), - CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/mpeg-2|video/mp2t|video/mpeg"), }; @@ -1227,7 +1275,7 @@ { { OFFS(ip), "destination IP address (NULL is 127.0.0.1)", GF_PROP_STRING, NULL, NULL, 0}, { OFFS(port), "port for first stream in session", GF_PROP_UINT, "7000", NULL, 0}, - { OFFS(loop), "loop all streams in session (not always possible depending on source type)", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(loop), "loop all streams in session (not always possible depending on source type)", GF_PROP_BOOL, "false", NULL, 0}, { OFFS(mpeg4), "send all streams using MPEG-4 generic payload format if possible", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(mtu), "size of RTP MTU in bytes", GF_PROP_UINT, "1460", NULL, 0}, { OFFS(ttl), "time-to-live for multicast packets", GF_PROP_UINT, "2", NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -1242,6 +1290,8 @@ { OFFS(dst), "URL for direct RTP mode", GF_PROP_NAME, NULL, NULL, 0}, { OFFS(ext), "file extension for direct RTP mode", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(mime), "set mime type for direct RTP mode", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(speed), "set streaming speed. If negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(start), "set streaming start offset. A negative value means percent of media duration with -1 equal to duration", GF_PROP_DOUBLE, "0.0", NULL, 0}, {0} }; @@ -1278,7 +1328,8 @@ SETCAPS(RTPOutCaps), .configure_pid = rtpout_configure_pid, .probe_url = rtpout_probe_url, - .process = rtpout_process + .process = rtpout_process, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/out_rtp.h -> gpac-26.02.0.tar.gz/src/filters/out_rtp.h
Changed
@@ -100,6 +100,7 @@ const char *ctrl_name; u32 rtp_id, rtcp_id; u32 mcast_port; + Bool do_probe; u32 rtp_timescale; @@ -109,7 +110,7 @@ GF_Err rtpout_create_sdp(GF_List *streams, Bool is_rtsp, const char *ip, const char *info, const char *sess_name, const char *url, const char *email, u32 base_pid_id, FILE **sdp_tmp, u64 *session_id); -GF_Err rtpout_init_streamer(GF_RTPOutStream *stream, const char *ipdest, Bool inject_xps, Bool use_mpeg4_signaling, Bool use_latm, u32 payt, u32 mtu, u32 ttl, const char *ifce, Bool is_rtsp, u32 *base_pid_id, u32 file_mode, const char *netcap_id); +GF_Err rtpout_init_streamer(GF_RTPOutStream *stream, const char *ipdest, Bool inject_xps, Bool use_mpeg4_signaling, Bool use_latm, u32 payt, u32 mtu, u32 ttl, const char *ifce, Bool is_rtsp, u32 *base_pid_id, const char *netcap_id); GF_Err rtpout_process_rtp(GF_List *streams, GF_RTPOutStream **active_stream, Bool loop, s32 delay, u32 *active_stream_idx, u64 sys_clock_at_init, u64 *active_min_ts_microsec, u64 microsec_ts_init, Bool *wait_for_loop, u32 *repost_delay_us, Bool *first_RTCP_sent, u32 base_pid_id);
View file
gpac-2.4.0.tar.gz/src/filters/out_rtsp.c -> gpac-26.02.0.tar.gz/src/filters/out_rtsp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2022 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / rtsp output filter @@ -39,11 +39,7 @@ #include "out_rtp.h" #ifdef GPAC_HAS_SSL - -void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e); -void *gf_ssl_server_context_new(const char *cert, const char *key); -void gf_ssl_server_context_del(void *ssl_server_ctx); -Bool gf_ssl_init_lib(); +#include "../utils/downloader.h" #endif @@ -54,21 +50,19 @@ SDP_LOADED }; -enum -{ +GF_OPT_ENUM(GF_RTSPOutMulticastMode, MCAST_OFF = 0, MCAST_ON, MCAST_MIRROR, //describe does not ened authenticate but multicast setup does MCAST_AUTHENTICATE_SETUP, -}; +); -enum -{ +GF_OPT_ENUM (GF_RTSPOutTransportMode, TRP_BOTH=0, TRP_UDP_ONLY, - TRP_TCP_ONLY -}; + TRP_TCP_ONLY, +); typedef struct { @@ -96,7 +90,8 @@ u32 maxc; u32 block_size; Bool close, loop, mpeg4, quit, htun, dynurl; - u32 mcast, trp; + GF_RTSPOutMulticastMode mcast; + GF_RTSPOutTransportMode trp; Bool latm; GF_Filter *filter; @@ -129,7 +124,7 @@ char *service_name; char *sessionID; char peer_addressGF_MAX_IP_NAME_LEN; - char ctrl_name10; + char ctrl_name20; u32 play_state; Double start_range; @@ -158,12 +153,13 @@ u32 sdp_state; u32 next_stream_id; + Bool needs_reconfig; u64 pause_sys_clock; Bool request_pending; char *multicast_ip; u64 sdp_id; - u32 mcast_mode; + GF_RTSPOutMulticastMode mcast_mode; char *mcast_sname; u32 last_active_time; @@ -210,11 +206,11 @@ //setup on resource not loaded, reassign control string based on setup query //this typically happen after a seek when we teardown the session right away if (!sess->sessionID || !sess->ctrl_name0) { - char *sep = strchr(sess->setup_ctrl, '='); + char *sep = strchr(sess->setup_ctrl, '_'); if (sep) sep0 = 0; strncpy(sess->ctrl_name, sess->setup_ctrl+1, 9); sess->ctrl_name9 = 0; - if (sep) sep0 = '='; + if (sep) sep0 = '_'; } e = rtspout_process_setup(sess->ctx, sess, sess->setup_ctrl); gf_free(sess->setup_ctrl); @@ -245,10 +241,12 @@ gf_rtsp_response_reset(sess->response); sess->response->ResponseCode = NC_RTSP_OK; sess->response->CSeq = sess->command->CSeq; + sess->response->Content_Type = "application/sdp"; sess->response->body = sdp_output; rtspout_send_response(sess->ctx, sess); sess->response->body = NULL; + sess->response->Content_Type = NULL; gf_free(sdp_output); return GF_OK; @@ -356,17 +354,32 @@ } return GF_OK; } - if (!sess) return GF_SERVICE_ERROR; stream = gf_filter_pid_get_udta(pid); + //session not found, fail silently if we had a stream - it is likely that TEARDOWWN was received and we posted a filter remove + //but still get a reconfigure on trailing packets + if (!sess) + return stream ? GF_OK : GF_SERVICE_ERROR; p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); streamType = p ? p->value.uint : 0; + Bool is_m2ts=0; switch (streamType) { case GF_STREAM_VISUAL: case GF_STREAM_AUDIO: break; case GF_STREAM_FILE: + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string && !strcmp(p->value.string, "video/mpeg-2")) { + is_m2ts = GF_TRUE; + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string && !strcmp(p->value.string, "ts")) { + is_m2ts = GF_TRUE; + } + } + if (is_m2ts) break; + //fallthrough case GF_STREAM_UNKNOWN: if (stream) { if (sess->active_stream==stream) sess->active_stream = NULL; @@ -392,19 +405,31 @@ rtspout_on_rtcp(stream->on_rtcp_udta); #endif gf_filter_pid_set_udta(pid, stream); + + stream->ctrl_id = sess->next_stream_id+1; + sess->next_stream_id++; } - stream->ctrl_id = sess->next_stream_id+1; - sess->next_stream_id++; stream->ctrl_name = sess->ctrl_name; payt = ctx->payt + gf_list_find(sess->streams, stream); - e = rtpout_init_streamer(stream, ctx->ifce ? ctx->ifce : "127.0.0.1", ctx->xps, ctx->mpeg4, ctx->latm, payt, ctx->mtu, ctx->ttl, ctx->ifce, GF_TRUE, &sess->base_pid_id, 0, gf_filter_get_netcap_id(filter)); - if (e) return e; - + e = rtpout_init_streamer(stream, ctx->ifce ? ctx->ifce : "127.0.0.1", ctx->xps, ctx->mpeg4, ctx->latm, payt, ctx->mtu, ctx->ttl, ctx->ifce, GF_TRUE, &sess->base_pid_id, gf_filter_get_netcap_id(filter)); + if (e) { + if (e==GF_NOT_READY) { + if (!stream->do_probe) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + sess->needs_reconfig++; + stream->do_probe = GF_TRUE; + } + return GF_OK; + } + return e; + } if (ctx->loop) { p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); - if (!p || (p->value.uint<GF_PLAYBACK_MODE_FASTFORWARD)) { + if (!p || (p->value.uint<GF_PLAYBACK_MODE_SEEK)) { GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTSPOut PID %s cannot be seek, disabling loop\n", gf_filter_pid_get_name(pid) )); sess->loop_disabled = GF_TRUE; @@ -435,16 +460,17 @@ sess->streams = gf_list_new(); sess->filter_srcs = gf_list_new(); if (gf_sys_is_test_mode()) { - strcpy(sess->ctrl_name, "trackID"); + strcpy(sess->ctrl_name, "/trackID"); } else { - u32 seed = gf_rand(); + u64 seed = gf_rand(); + seed <<= 32; #ifndef GPAC_64_BITS seed |= (u32) sess; #else - seed |= (u32) (u64) sess; + seed |= (u64) sess; #endif seed |= gf_sys_clock(); - sprintf(sess->ctrl_name, "s%08X", seed); + sprintf(sess->ctrl_name, "/s"LLX"", seed); } if (new_sess) { @@ -533,7 +559,7 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("RTSPOut Failed to initialize OpenSSL library\n")); return GF_IO_ERR; } - ctx->ssl_ctx = gf_ssl_server_context_new(ctx->cert, ctx->pkey); + ctx->ssl_ctx = gf_ssl_server_context_new(ctx->cert, ctx->pkey, GF_FALSE); if (!ctx->ssl_ctx) return GF_IO_ERR; if (!ctx->port || (ctx->port==554)) @@ -714,7 +740,7 @@ if (rtpi) { u32 timescale; rtpi->url = gf_malloc(sizeof(char) * (strlen(sess->service_name)+50)); - sprintf(rtpi->url, "%s/%s=%d", sess->service_name, sess->ctrl_name, stream->ctrl_id); + sprintf(rtpi->url, "%s_%d", sess->ctrl_name, stream->ctrl_id); rtpi->seq = gf_rtp_streamer_get_next_rtp_sn(stream->rtp); rtpi->rtp_time = (u32) (stream->current_cts + stream->ts_offset + stream->rtp_ts_offset); @@ -960,7 +986,7 @@ return NULL; } -static char *rtspout_get_local_res_path(GF_RTSPOutCtx *ctx, char *res_path, GF_RTSPCommand *com, u32 *err_code, u32 *mcast_mode) +static char *rtspout_get_local_res_path(GF_RTSPOutCtx *ctx, char *res_path, GF_RTSPCommand *com, u32 *err_code, GF_RTSPOutMulticastMode *mcast_mode) { u32 i, count, di_len; RTSP_DIRInfo *di = NULL; @@ -1073,8 +1099,8 @@ u32 stream_ctrl_id=0; if (ctrl && (ctrl0=='/')) { u32 len = (u32) strlen(sess->ctrl_name); - if (!strncmp(ctrl+1, sess->ctrl_name, len)) { - if (sscanf(ctrl+1+len, "=%d", &stream_ctrl_id)<1) { + if (!strncmp(ctrl, sess->ctrl_name, len)) { + if (sscanf(ctrl+len, "_%d", &stream_ctrl_id)<1) { stream_ctrl_id=0; } } @@ -1094,15 +1120,6 @@ u32 stream_ctrl_id = rtspout_get_ctrl_id(sess, ctrl); - if (ctrl && (ctrl0=='/')) { - u32 len = (u32) strlen(sess->ctrl_name); - if (!strncmp(ctrl+1, sess->ctrl_name, len)) { - if (sscanf(ctrl+1+len, "=%d", &stream_ctrl_id)<1) { - stream_ctrl_id=0; - } - } - } - if (!ctrl || !transport) { rsp_code = NC_RTSP_Bad_Request; } else if (sess->sessionID && sess->command->Session && strcmp(sess->sessionID, sess->command->Session)) { @@ -1180,7 +1197,7 @@ if (transport->destination && !gf_sk_is_multicast_address(transport->destination)) { rsp_code = NC_RTSP_Bad_Request; } else { - u32 mcast_mode = ctx->mcast; + GF_RTSPOutMulticastMode mcast_mode = ctx->mcast; if (sess->mcast_mode) mcast_mode = sess->mcast_mode; if (mcast_mode==MCAST_AUTHENTICATE_SETUP) { @@ -1393,7 +1410,7 @@ || (!sess->service_name && !sess->sessionID && !strcmp(sess->command->method, GF_RTSP_SETUP)) ) { u32 rsp_code = NC_RTSP_OK; - u32 mcast_mode=MCAST_OFF; + GF_RTSPOutMulticastMode mcast_mode=MCAST_OFF; Bool is_setup = !strcmp(sess->command->method, GF_RTSP_SETUP); char *res_path = NULL; @@ -1573,15 +1590,8 @@ } //extract control string if any - if (sess->service_name) { - char *sep = strstr(sess->service_name, "://"); - if (sep) sep = strchr(sep+3, '/'); - if (sep) sep = strstr(sess->command->service_name, sep); - - if (sep) { - ctrl = strrchr(sess->command->service_name, '/'); - } - } else { + ctrl = NULL; + if (sess->command->service_name) { ctrl = strrchr(sess->command->service_name, '/'); } @@ -1739,6 +1749,25 @@ GF_RTSPOutSession *sess = gf_list_get(ctx->sessions, i); if (!sess) break; + if (sess->needs_reconfig) { + u32 k, nb_st = gf_list_count(sess->streams); + for (k=0; k<nb_st; k++) { + GF_RTPOutStream *st = gf_list_get(sess->streams, k); + if (!st->do_probe) continue; + st->do_probe = GF_FALSE; + GF_FilterPacket *pck = gf_filter_pid_get_packet(st->pid); + if (pck && !st->do_probe) { + sess->needs_reconfig --; + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, st->pid); + gf_filter_pid_send_event(st->pid, &evt); + } else { + st->do_probe = GF_TRUE; + } + } + continue; + } + sess_err = rtspout_process_session_signaling(filter, ctx, &sess); if ((s32)sess_err == GF_RTSP_TUNNEL_POST) { gf_list_rem(ctx->sessions, i); @@ -1807,6 +1836,11 @@ {0}, CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "*"), + //but only file streals that have a timescale set + CAP_UINT(GF_CAPS_INPUT|GF_CAPFLAG_PRESENT, GF_PROP_PID_TIMESCALE, 0), }; @@ -1833,7 +1867,7 @@ { OFFS(timeout), "timeout in seconds for inactive sessions (0 disable timeout)", GF_PROP_UINT, "20", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(user_agent), "user agent string, by default solved from GPAC preferences", GF_PROP_STRING, "$GUA", NULL, 0}, { OFFS(close), "close RTSP connection after each request, except when RTP over RTSP is used", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, - { OFFS(loop), "loop all streams in session (not always possible depending on source type)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(loop), "loop all streams in session (not always possible depending on source type)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(dynurl), "allow dynamic service assembly", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(mcast), "control multicast setup of a session\n" "- off: clients are never allowed to create a multicast\n" @@ -1854,7 +1888,7 @@ GF_FilterRegister RTSPOutRegister = { .name = "rtspout", - GF_FS_SET_DESCRIPTION("RTSP Server") + GF_FS_SET_DESCRIPTION("RTSP server") GF_FS_SET_HELP("The RTSP server partially implements RTSP 1.0, with support for OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE and TEARDOWN.\n" "Multiple PLAY ranges are not supported, PLAY range end is not supported, PAUSE range is not supported.\n" "Only aggregated control is supported for PLAY and PAUSE, PAUSE/PLAY on single stream is not supported.\n" @@ -1922,7 +1956,8 @@ .finalize = rtspout_finalize, SETCAPS(RTSPOutCaps), .configure_pid = rtspout_configure_pid, - .process = rtspout_process + .process = rtspout_process, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/out_sock.c -> gpac-26.02.0.tar.gz/src/filters/out_sock.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / generic socket output filter @@ -69,6 +69,8 @@ GF_FilterPacket *rev_pck; u32 next_pckd_idx, next_pckr_idx; u32 nb_pckd_wnd, nb_pckr_wnd; + + GF_SockGroup *sg; } GF_SockOutCtx; @@ -78,6 +80,7 @@ GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter); if (is_remove) { ctx->pid = NULL; + gf_sk_group_unregister(ctx->sg, ctx->socket); gf_sk_del(ctx->socket); ctx->socket = NULL; return GF_OK; @@ -163,7 +166,7 @@ } gf_filter_override_caps(filter, ctx->in_caps, 2); - /*create our ourput socket*/ + /*create our output socket*/ if (!strnicmp(ctx->dst, "udp://", 6)) { sock_type = GF_SOCK_TYPE_UDP; @@ -212,7 +215,7 @@ || (sock_type == GF_SOCK_TYPE_UDP_UN) #endif ) { - e = gf_sk_bind(ctx->socket, ctx->ifce, port, url, port, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND); + e = gf_sk_bind(ctx->socket, ctx->ifce, port, url, port, GF_SOCK_REUSE_PORT|GF_SOCK_IS_SENDER); ctx->listen = GF_FALSE; } else if (ctx->listen) { e = gf_sk_bind(ctx->socket, NULL, port, url, 0, GF_SOCK_REUSE_PORT); @@ -232,8 +235,11 @@ ctx->socket = NULL; return e; } - + ctx->sg = gf_sk_group_new(); gf_sk_set_buffer_size(ctx->socket, 0, ctx->sockbuf); + if (!ctx->listen) { + gf_sk_group_register(ctx->sg, ctx->socket); + } return GF_OK; } @@ -251,6 +257,8 @@ } if (ctx->socket) gf_sk_del(ctx->socket); + if (ctx->sg) gf_sk_group_del(ctx->sg); + } static GF_Err sockout_send_packet(GF_SockOutCtx *ctx, GF_FilterPacket *pck, GF_Socket *dst_sock) @@ -266,13 +274,14 @@ pck_data = gf_filter_pck_get_data(pck, &pck_size); if (pck_data) { - e = gf_sk_send(dst_sock, pck_data, pck_size); + u32 nb_bytes_sent = 0; + e = gf_sk_send_ex(dst_sock, pck_data, pck_size, &nb_bytes_sent); if ((e==GF_IP_CONNECTION_CLOSED) || (e==GF_URL_REMOVED)) return GF_IP_CONNECTION_CLOSED; if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("SockOut Write error: %s\n", gf_error_to_string(e) )); } - ctx->nb_bytes_sent += pck_size; + ctx->nb_bytes_sent += nb_bytes_sent; return e; } hwf = gf_filter_pck_get_frame_interface(pck); @@ -351,6 +360,7 @@ } } + if (ctx->listen) { GF_Socket *new_conn=NULL; e = gf_sk_accept(ctx->socket, &new_conn); @@ -375,6 +385,9 @@ sc->pck_pending = ctx->pck_pending; if (!ctx->nb_pck_processed) sc->is_tuned = GF_TRUE; + + gf_sk_group_register(ctx->sg, sc->socket); + } if (!ctx->pid_started) { gf_filter_ask_rt_reschedule(filter, 50000); @@ -385,6 +398,14 @@ return GF_OK; } + e = gf_sk_group_select(ctx->sg, 10, GF_SK_SELECT_WRITE); + if (e == GF_IP_NETWORK_EMPTY) { + gf_filter_ask_rt_reschedule(filter, 5000); + return GF_OK; + } else if (e<0) { + return e; + } + pck = gf_filter_pid_get_packet(ctx->pid); if (!pck) { if (gf_filter_pid_is_eos(ctx->pid) && !gf_filter_pid_is_flush_eos(ctx->pid) ) { @@ -393,13 +414,14 @@ pck = ctx->rev_pck; } else { if (!ctx->listen) { + gf_sk_group_unregister(ctx->sg, ctx->socket); gf_sk_del(ctx->socket); ctx->socket = NULL; return GF_EOS; } if (!ctx->ka) return GF_EOS; - //keep alive, ask for real-time reschedule of 100 ms - we should use socket groups and selects ! + //keep alive, ask for real-time reschedule of 100 ms gf_filter_ask_rt_reschedule(filter, 100000); } } @@ -459,6 +481,9 @@ GF_SockOutClient *sc = gf_list_get(ctx->clients, i); if (!sc->socket) continue; + if (!gf_sk_group_sock_is_set(ctx->sg, sc->socket, GF_SK_SELECT_WRITE)) + continue; + if (!sc->is_tuned) { } @@ -469,6 +494,7 @@ e = sockout_send_packet(ctx, pck, sc->socket); if (e==GF_IP_CONNECTION_CLOSED) { + gf_sk_group_unregister(ctx->sg, sc->socket); gf_sk_del(sc->socket); sc->socket = NULL; gf_list_rem(ctx->clients, i); @@ -498,8 +524,8 @@ if (ctx->pck_pending) return GF_OK; } else { - if (gf_sk_select(ctx->socket, GF_SK_SELECT_WRITE)==GF_IP_NETWORK_EMPTY) { - gf_filter_ask_rt_reschedule(filter, 1000); + if (!gf_sk_group_sock_is_set(ctx->sg, ctx->socket, GF_SK_SELECT_WRITE)) { + gf_filter_ask_rt_reschedule(filter, 5000); return GF_OK; } e = sockout_send_packet(ctx, pck, ctx->socket); @@ -508,6 +534,7 @@ GF_FilterEvent evt; GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid); gf_filter_pid_send_event(ctx->pid, &evt); + gf_sk_group_unregister(ctx->sg, ctx->socket); gf_sk_del(ctx->socket); ctx->socket = NULL; } @@ -616,7 +643,8 @@ .finalize = sockout_finalize, .configure_pid = sockout_configure_pid, .process = sockout_process, - .flags = GF_FS_REG_TEMP_INIT + .flags = GF_FS_REG_TEMP_INIT, + .hint_class_type = GF_FS_CLASS_NETWORK_IO };
View file
gpac-2.4.0.tar.gz/src/filters/out_video.c -> gpac-26.02.0.tar.gz/src/filters/out_video.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / video output filter @@ -69,23 +69,21 @@ #endif -typedef enum -{ +GF_OPT_ENUM (GF_VideoOutMode, MODE_GL, MODE_GL_PBO, MODE_2D, MODE_2D_SOFT, -} GF_VideoOutMode; +); -enum -{ +GF_OPT_ENUM (GF_VideoFlipMode, FLIP_NO, FLIP_VERT, FLIP_HORIZ, FLIP_BOTH, FLIP_BOTH2, -}; +); static u32 nb_vout_inst=0; @@ -97,7 +95,8 @@ Bool vsync, linear, fullscreen, drop, hide, step, vjs, async; GF_Fraction64 dur; Double speed, hold; - u32 back, vflip, vrot; + u32 back, vrot; + GF_VideoFlipMode vflip; GF_PropVec2i wsize, owsize; GF_PropVec2i wpos; Double start; @@ -138,7 +137,7 @@ //hold the frame until its CTS is reached, triggering drops at capture time u32 raw_grab; GF_DisplayOrientationType screen_orientation; - + #ifdef VOUT_USE_OPENGL GLint glsl_program; GF_SHADERID vertex_shader; @@ -181,7 +180,8 @@ u64 rebuffer; Bool force_reconfig_pid; - u32 pid_vflip, pid_vrot; + GF_VideoFlipMode pid_vflip; + u32 pid_vrot; Bool too_slow; } GF_VideoOutCtx; @@ -236,6 +236,11 @@ } glDeleteTextures(1, &ctx->overlay_tx); ctx->overlay_tx = 0; + + if (!ctx->pid) { + glClear(GL_COLOR_BUFFER_BIT); + ctx->video_out->Flush(ctx->video_out, NULL); + } if (filter) gf_filter_lock(filter, GF_FALSE); } @@ -411,7 +416,7 @@ ctx->sar.num = ctx->sar.den = 1; p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p && p->value.frac.den && p->value.frac.num) { + if (p && p->value.frac.den && (p->value.frac.num>0)) { if (ctx->sar.num * p->value.frac.den != p->value.frac.num * ctx->sar.den) sar_changed = GF_TRUE; ctx->sar = p->value.frac; @@ -429,7 +434,7 @@ if (p && p->value.uint) { if (ctx->buffer < p->value.uint) ctx->buffer = p->value.uint; } - + p = gf_filter_pid_get_property(pid, GF_PROP_PID_RAWGRAB); ctx->raw_grab = p ? p->value.uint : 0; @@ -689,6 +694,7 @@ case GF_PIXEL_XRGB: case GF_PIXEL_XBGR: case GF_PIXEL_RGBX: + case GF_PIXEL_RGB_332: case GF_PIXEL_RGB_444: case GF_PIXEL_RGB_555: case GF_PIXEL_RGB_565: @@ -1375,6 +1381,11 @@ } } + if (!ctx->sar.num || ctx->sar.den) { + ctx->sar.num = 1; + ctx->sar.den = 1; + } + //if we fill width to display width and height is outside if (ctx->display_width * v_h / v_w > ctx->display_height) { ctx->dw = (Float) (ctx->display_height * v_w / v_h); @@ -1750,17 +1761,27 @@ } if (!ctx->pid) { + GF_Err ret = GF_OK; + u32 resched = 100000; //we don't lock here since we don't access the pointer if (ctx->oldata.ptr && ctx->update_oldata) return vout_draw_frame(ctx); - if (gf_filter_has_connect_errors(filter) || gf_filter_all_sinks_done(filter)) + if (gf_filter_has_connect_errors(filter)) { + //connection error, abort (no reschedule) return GF_EOS; - //when we use vout+aout on audio only, we want the filter to still be active to process events - gf_filter_post_process_task(filter); - gf_filter_ask_rt_reschedule(filter, 10000); - - return ctx->oldata.ptr ? GF_OK : GF_EOS; + } + if (gf_filter_all_sinks_done(filter) && !gf_filter_connections_pending(filter)) { + //all done, abort (no reschedule) + return GF_EOS; + } + else if (ctx->oldata.ptr) { + ret = GF_EOS; + resched = 40000; + } + //we always reschedule when no input to avoid having a non-responding window + gf_filter_ask_rt_reschedule(filter, resched); + return ret; } pck = gf_filter_pid_get_packet(ctx->pid); @@ -1806,7 +1827,7 @@ } return ctx->aborted ? GF_EOS : GF_OK; } - + if (!ctx->width || !ctx->height) { GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("VideoOut pid display wsize unknown, discarding packet\n")); gf_filter_pid_drop_packet(ctx->pid); @@ -2150,10 +2171,8 @@ if ((ctx->pfmt && ctx->last_pck) || !ctx->pid || ctx->update_oldata) { #ifdef VOUT_USE_OPENGL if (ctx->disp < MODE_2D) { - gf_rmt_begin_gl(vout_draw_gl); glGetError(); vout_draw_gl(ctx, ctx->last_pck); - gf_rmt_end_gl(); glGetError(); } else #endif @@ -2367,7 +2386,8 @@ .configure_pid = vout_configure_pid, .process = vout_process, .process_event = vout_process_event, - .update_arg = vout_update_arg + .update_arg = vout_update_arg, + .hint_class_type = GF_FS_CLASS_MM_IO }; const GF_FilterRegister *vout_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/reframe_ac3.c -> gpac-26.02.0.tar.gz/src/filters/reframe_ac3.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / AC3 reframer filter @@ -247,7 +247,10 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : ctx->sample_rate)); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) ); - + if (!gf_sys_is_test_mode()) { + u64 layout = gf_ac3_get_channel_layout(&ctx->hdr); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(layout) ); + } gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->is_eac3 ? GF_CODECID_EAC3 : GF_CODECID_AC3) ); ctx->hdr.is_ec3 = ctx->is_eac3; @@ -505,7 +508,9 @@ //truncated last frame if (bytes_to_drop>remain) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("ADTSDmx truncated AC3 frame!\n")); + if (!gf_filter_pid_has_seen_eos(ctx->ipid)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("ADTSDmx truncated AC3 frame!\n")); + } bytes_to_drop=remain; } @@ -538,6 +543,10 @@ if (remain && (remain < ctx->ac3_buffer_size)) { memmove(ctx->ac3_buffer, start, remain); } + if (!ctx->src_pck && pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } ctx->ac3_buffer_size = remain; gf_filter_pid_drop_packet(ctx->ipid); } @@ -550,6 +559,7 @@ if (ctx->bs) gf_bs_del(ctx->bs); if (ctx->ac3_buffer) gf_free(ctx->ac3_buffer); if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); } static const char *ac3dmx_probe_data(const u8 *_data, u32 _size, GF_FilterProbeScore *score) @@ -671,7 +681,8 @@ .configure_pid = ac3dmx_configure_pid, .process = ac3dmx_process, .probe_data = ac3dmx_probe_data, - .process_event = ac3dmx_process_event + .process_event = ac3dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-26.02.0.tar.gz/src/filters/reframe_ac4.c
Added
@@ -0,0 +1,630 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2024 + * All rights reserved + * + * This file is part of GPAC / AC4 reframer filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/avparse.h> +#include <gpac/constants.h> +#include <gpac/filters.h> + +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_RFAC4) + +typedef struct +{ + u64 pos; + Double duration; +} AC4Idx; + +typedef struct +{ + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 file_pos, cts; + u32 sample_rate, nb_ch; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 in_timescale, out_timescale; + + GF_AC4Config hdr; + u8 *ac4_buffer; + u32 ac4_buffer_size, ac4_buffer_alloc, resume_from; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + GF_FilterPacket *src_pck; + + AC4Idx *indexes; + u32 index_alloc_size, index_size; + u32 bitrate; + Bool copy_props; +} GF_AC4DmxCtx; + +GF_Err ac4dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_AC4DmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + if (ctx->opid) { + gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + } + return GF_OK; + } + + if (!gf_filter_pid_check_caps(pid)) { + return GF_NOT_SUPPORTED; + } + + ctx->ipid = pid; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) { + ctx->in_timescale = p->value.uint; + } + + if (ctx->in_timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + + //make sure we move to audio (may happen if source filter is writegen) + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_STREAM_TYPE); + if (!p || (p->value.uint==GF_STREAM_FILE)) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO)); + } + } + if (ctx->in_timescale) { + ctx->copy_props = GF_TRUE; + } + + return GF_OK; +} + +static void ac4dmx_check_dur(GF_Filter *filter, GF_AC4DmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + GF_AC4Config hdr = {0}; + u64 duration, rate; + u32 sample_rate = 0; + const GF_PropertyValue *p; + + // ac4dmx_check_dur() is only done once + if (!ctx->opid || ctx->in_timescale || ctx->file_loaded) { + return; + } + + // open the stream + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen_ex(p->value.string, NULL, "rb", GF_TRUE); + if (!stream) { + if (gf_fileio_is_main_thread(p->value.string)) + ctx->file_loaded = GF_TRUE; + return; + } + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + duration = 0; + + // duration + while (gf_ac4_parser_bs(bs, &hdr, GF_FALSE, GF_FALSE)) { + duration += ctx->hdr.sample_duration; + sample_rate = hdr.sample_rate; + + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + + ctx->indexes = gf_realloc(ctx->indexes, sizeof(AC4Idx)*ctx->index_alloc_size); + ctx->indexesctx->index_size.pos = gf_bs_get_position(bs) + hdr.header_size; + ctx->indexesctx->index_size.duration = (Double) duration; + ctx->indexesctx->index_size.duration /= hdr.sample_rate; + ctx->index_size ++; + + gf_bs_skip_bytes(bs, hdr.frame_size + hdr.header_size + hdr.crc_size); + } + + rate = gf_bs_get_position(bs); + gf_bs_del(bs); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * sample_rate != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = sample_rate; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + // bitrate + if (duration && !gf_sys_is_test_mode() ) { + rate *= 8 * ctx->duration.den; + rate /= ctx->duration.num; + ctx->bitrate = (u32) rate; + } + } + + // set ctx->file_loaded + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; +} + +static void ac4dmx_check_pid(GF_Filter *filter, GF_AC4DmxCtx *ctx) +{ + u8 *data; + u32 size; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + ac4dmx_check_dur(filter, ctx); + } + + // return if key parameters remain unchanged + if ((ctx->sample_rate == ctx->hdr.sample_rate) && ctx->nb_ch >= ctx->hdr.channel_count && !ctx->copy_props) { + return; + } + + ctx->copy_props = GF_FALSE; + + // copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + + const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_STREAM_TYPE); + if (!p || (p->value.uint==GF_STREAM_FILE)) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO)); + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->hdr.sample_duration)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, & PROP_BOOL(GF_FALSE) ); + + if (ctx->duration.num) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + if (!ctx->in_timescale && !gf_sys_is_test_mode()) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE)); + } + + if (!ctx->copy_props && ctx->hdr.channel_count > ctx->nb_ch) { + ctx->nb_ch = ctx->hdr.channel_count; + } + + if (!ctx->in_timescale) { + // we change sample rate, change cts + if (ctx->cts && (ctx->out_timescale != ctx->hdr.media_time_scale)) { + ctx->cts = gf_timestamp_rescale(ctx->cts, ctx->out_timescale, ctx->hdr.media_time_scale); + } + } + ctx->sample_rate = ctx->hdr.sample_rate; + ctx->out_timescale = ctx->hdr.media_time_scale; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->out_timescale)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) ); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(GF_CODECID_AC4) ); + gf_odf_ac4_cfg_write(&ctx->hdr, &data, &size); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(data, size) ); + + if (ctx->bitrate) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, & PROP_UINT(ctx->bitrate)); + } +} + +static Bool ac4dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_AC4DmxCtx *ctx = gf_filter_get_udta(filter); + + if (evt->base.type == GF_FEVT_PLAY) { + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + + if (!ctx->is_file) { + return GF_FALSE; + } + + // locate the start position and cts + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i = 1; i < ctx->index_size; i++) { + if (ctx->indexesi.duration > ctx->start_range) { + ctx->cts = (u64) (ctx->indexesi - 1.duration * ctx->out_timescale); + ctx->file_pos = ctx->indexesi-1.pos; + break; + } + } + } + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) { + return GF_TRUE; + } + } + + ctx->ac4_buffer_size = 0; + ctx->resume_from = 0; + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + return GF_TRUE; // cancel event + } else if (evt->base.type == GF_FEVT_STOP) { + ctx->is_playing = GF_FALSE; + ctx->cts = 0; + + return GF_FALSE; // don't cancel event + } else if (evt->base.type == GF_FEVT_SET_SPEED) { + return GF_TRUE; // cancel event + } + + // by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err ac4dmx_process(GF_Filter *filter) +{ + GF_AC4DmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *output; + u8 *start; + u32 pck_size, remain, prev_pck_size; + u64 cts; + u32 sync_framesize = 0; + Bool is_first = GF_TRUE; + +restart: + cts = GF_FILTER_NO_TS; + + //always reparse duration + if (!ctx->duration.num) { + ac4dmx_check_dur(filter, ctx); + } + + if (ctx->opid && !ctx->is_playing) { + return GF_OK; + } + + // get new pck from input + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->ac4_buffer_size) { + if (ctx->opid) { + gf_filter_pid_set_eos(ctx->opid); + } + if (ctx->src_pck) { + gf_filter_pck_unref(ctx->src_pck); + } + ctx->src_pck = NULL; + return GF_EOS; + } + } else { + return GF_OK; + } + } + + prev_pck_size = ctx->ac4_buffer_size; + if (pck && !ctx->resume_from) { + // get data from pck + const u8 *data = gf_filter_pck_get_data(pck, &pck_size); + if (!pck_size) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + + // copy to ctx->ac4_buffer + if (ctx->ac4_buffer_size + pck_size > ctx->ac4_buffer_alloc) { + ctx->ac4_buffer_alloc = ctx->ac4_buffer_size + pck_size; + ctx->ac4_buffer = gf_realloc(ctx->ac4_buffer, ctx->ac4_buffer_alloc); + } + memcpy(ctx->ac4_buffer + ctx->ac4_buffer_size, data, pck_size); + ctx->ac4_buffer_size += pck_size; + } + + remain = ctx->ac4_buffer_size; + start = ctx->ac4_buffer; + + if (ctx->resume_from) { + start += ctx->resume_from - 1; + remain -= ctx->resume_from - 1; + ctx->resume_from = 0; + } + + if (!ctx->bs) { + ctx->bs = gf_bs_new(start, remain, GF_BITSTREAM_READ); + } + else { + gf_bs_reassign_buffer(ctx->bs, start, remain); + } + + while (remain) { + u8 *sync; + Bool res; + u32 sync_pos, bytes_to_drop=0; + + res = gf_ac4_parser_bs(ctx->bs, &(ctx->hdr), is_first, GF_FALSE); + + sync_pos = (u32) gf_bs_get_position(ctx->bs); + sync_framesize = ctx->hdr.frame_size + ctx->hdr.header_size + ctx->hdr.crc_size; + + //if not end of stream or no valid frame + if (pck || !sync_framesize) { + //startcode not found or not enough bytes, gather more + if (!res || (remain < sync_pos + sync_framesize)) { + if (sync_pos && sync_framesize) { + start += sync_pos; + remain -= sync_pos; + } + break; + } + ac4dmx_check_pid(filter, ctx); + } + + if (is_first && ctx->in_timescale) { + // input pid sets some timescale - we flushed pending data , update cts + if (pck) { + cts = gf_filter_pck_get_cts(pck); + //move to output timescale + if (cts != GF_FILTER_NO_TS) + cts = gf_timestamp_rescale(cts, ctx->in_timescale, ctx->out_timescale); + + //init cts at first packet + if (!ctx->cts && (cts != GF_FILTER_NO_TS)) { + ctx->cts = cts; + } + } + // avoids updating cts + if (cts == GF_FILTER_NO_TS) { + prev_pck_size = 0; + } + } + + + is_first = GF_FALSE; + + if (!ctx->is_playing) { + ctx->resume_from = 1 + ctx->ac4_buffer_size - remain; + return GF_OK; + } + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->hdr.media_time_scale); + if (ctx->cts + ctx->hdr.sample_duration >= nb_samples_at_seek) { + ctx->in_seek = GF_FALSE; + } + } + + if (ctx->in_timescale && !prev_pck_size && (cts != GF_FILTER_NO_TS)) { + //trust input CTS if diff is more than one sec + if ((cts > ctx->cts + ctx->out_timescale) || (ctx->cts > cts + ctx->out_timescale)) { + ctx->cts = cts; + } + cts = GF_FILTER_NO_TS; + } + + // generate the dst_pck + if (!ctx->in_seek && remain >= sync_pos + sync_framesize) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->hdr.frame_size, &output); + if (!dst_pck) { + return GF_OUT_OF_MEM; + } + + if (ctx->src_pck) { + gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + } + + // sync is the position of raw_ac4_frame() + sync = start + sync_pos + ctx->hdr.header_size; + memcpy(output, sync, ctx->hdr.frame_size); + + gf_filter_pck_set_dts(dst_pck, ctx->cts); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + //ctx->hdr.sample_duration is always in out_timescale units + gf_filter_pck_set_duration(dst_pck, ctx->hdr.sample_duration); + + gf_filter_pck_set_sap(dst_pck, ctx->hdr.stream.b_iframe_global ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + // send dst_pck + gf_filter_pck_send(dst_pck); + } + + // update cts - sample_duration is always in output timescale + ctx->cts += ctx->hdr.sample_duration; + + // calculate memory offset + bytes_to_drop = sync_pos + sync_framesize; + if (bytes_to_drop > remain) { + bytes_to_drop = remain; // truncated last frame + } + if (bytes_to_drop == 0) { + bytes_to_drop = 1; + } + + start += bytes_to_drop; + remain -= bytes_to_drop; + gf_bs_reassign_buffer(ctx->bs, start, remain); + + if (prev_pck_size) { + if (prev_pck_size > bytes_to_drop) { + prev_pck_size -= bytes_to_drop; + } + else { + prev_pck_size = 0; + if (ctx->src_pck) { + gf_filter_pck_unref(ctx->src_pck); + } + ctx->src_pck = pck; + if (pck) { + gf_filter_pck_ref_props(&ctx->src_pck); + } + } + } + } + + if (!pck) { + ctx->ac4_buffer_size = 0; + //avoid recursive call + goto restart; + } else { + if (remain && (remain < ctx->ac4_buffer_size)) { + memmove(ctx->ac4_buffer, start, remain); + } + if (!ctx->src_pck && pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } + ctx->ac4_buffer_size = remain; + gf_filter_pid_drop_packet(ctx->ipid); + } + return GF_OK; +} + +static void ac4dmx_finalize(GF_Filter *filter) +{ + GF_AC4DmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->ac4_buffer) gf_free(ctx->ac4_buffer); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + + gf_odf_ac4_cfg_clean_list(&(ctx->hdr)); +} + +static const char *ac4dmx_probe_data(const u8 *_data, u32 _size, GF_FilterProbeScore *score) +{ + u32 nb_frames = 0, sync_framesize = 0, pos = 0; + u32 nb_broken_frames = GF_FALSE; + GF_AC4Config ahdr = {0}; + + u32 lt = gf_log_get_tool_level(GF_LOG_CODING); + gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_QUIET); + + GF_BitStream *bs = gf_bs_new(_data, _size, GF_BITSTREAM_READ); + while (gf_bs_available(bs) && nb_frames <= 4) { + Bool bytes_lost=GF_FALSE; + if (!gf_ac4_parser_bs(bs, &ahdr, GF_TRUE, GF_FALSE)) { + if (ahdr.sample_rate) nb_frames++; + break; + } + + if (pos != (u32) gf_bs_get_position(bs)) { + bytes_lost=GF_TRUE; + nb_broken_frames++; + } + + nb_frames += 1; + sync_framesize = ahdr.frame_size + ahdr.header_size + ahdr.crc_size; + gf_bs_skip_bytes(bs, sync_framesize); + if (!pos && !bytes_lost && (nb_frames==1) && !gf_bs_available(bs)) nb_frames++; + pos += sync_framesize; + } + + gf_log_set_tool_level(GF_LOG_CODING, lt); + gf_bs_del(bs); + + gf_odf_ac4_cfg_clean_list(&ahdr); + + if (nb_frames>=2) { + *score = nb_broken_frames ? GF_FPROBE_MAYBE_NOT_SUPPORTED : GF_FPROBE_SUPPORTED; + return "audio/ac4"; + } + + return NULL; +} + +static const GF_FilterCapability AC4DmxCaps = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "ac4"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/x-ac4|audio/ac4"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AC4), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_FALSE), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AC4), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +/* {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_HLS_SAMPLE_AES_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AC4), +*/ + +}; + +#define OFFS(_n) #_n, offsetof(GF_AC4DmxCtx, _n) +static const GF_FilterArgs AC4DmxArgs = +{ + {0} +}; + +GF_FilterRegister AC4DmxRegister = { + .name = "rfac4", + GF_FS_SET_DESCRIPTION("AC4 reframer") + GF_FS_SET_HELP("This filter parses AC4 files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_AC4DmxCtx), + .args = AC4DmxArgs, + .finalize = ac4dmx_finalize, + SETCAPS(AC4DmxCaps), + .configure_pid = ac4dmx_configure_pid, + .process = ac4dmx_process, + .probe_data = ac4dmx_probe_data, + .process_event = ac4dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING +}; + + +const GF_FilterRegister *rfac4_register(GF_FilterSession *session) +{ + return &AC4DmxRegister; +} +#else +const GF_FilterRegister *rfac4_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_RFAC4)
View file
gpac-2.4.0.tar.gz/src/filters/reframe_adts.c -> gpac-26.02.0.tar.gz/src/filters/reframe_adts.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AAC ADTS reframer filter @@ -29,12 +29,11 @@ #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_RFADTS) -enum -{ +GF_OPT_ENUM (GF_PS_SBRSignalingMode, AAC_SIGNAL_NONE=0, AAC_SIGNAL_IMPLICIT, - AAC_SIGNAL_EXPLICIT -}; + AAC_SIGNAL_EXPLICIT, +); typedef struct { @@ -53,8 +52,8 @@ //filter args u32 frame_size; Double index; - u32 sbr; - u32 ps; + GF_PS_SBRSignalingMode sbr; + GF_PS_SBRSignalingMode ps; // Bool mpeg4; Bool ovsbr; Bool expart; @@ -896,6 +895,10 @@ if (remain) { memmove(ctx->adts_buffer, start, remain); } + if (!ctx->src_pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } ctx->adts_buffer_size = remain; gf_filter_pid_drop_packet(ctx->ipid); } @@ -1040,7 +1043,9 @@ .configure_pid = adts_dmx_configure_pid, .process = adts_dmx_process, .probe_data = adts_dmx_probe_data, - .process_event = adts_dmx_process_event + .process_event = adts_dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING + };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_amr.c -> gpac-26.02.0.tar.gz/src/filters/reframe_amr.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AMR&EVRC&SMV reframer filter @@ -620,7 +620,8 @@ .configure_pid = amrdmx_configure_pid, .process = amrdmx_process, .probe_data = amrdmx_probe_data, - .process_event = amrdmx_process_event + .process_event = amrdmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_av1.c -> gpac-26.02.0.tar.gz/src/filters/reframe_av1.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Romain Bouqueau, Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / AV1 IVF/OBU/annexB reframer filter @@ -47,6 +47,8 @@ IVF, //input is raw VPX RAW_VPX, + //input is IAMF + IAMF, UNSUPPORTED } AV1BitstreamSyntax; @@ -86,6 +88,7 @@ Bool is_av1; Bool is_vp9; + Bool is_iamf; u32 codecid; u32 num_frames; GF_VPConfig *vp_cfg; @@ -100,6 +103,7 @@ u32 index_alloc_size, index_size; AV1State state; + IAMFState iamfstate; u32 dsi_crc; Bool pts_from_file; @@ -108,6 +112,8 @@ u32 clli_crc, mdcv_crc; Bool copy_props; + + GF_SEILoader *sei_loader; } GF_AV1DmxCtx; @@ -135,6 +141,8 @@ ctx->opid = gf_filter_pid_new(filter); gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + if (ctx->sei_loader) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); } //if source has no timescale, recompute time @@ -162,7 +170,16 @@ if (!ctx->state.config) ctx->state.config = gf_odf_av1_cfg_new(); - ctx->is_av1 = ctx->is_vp9 = GF_FALSE; + if (!ctx->iamfstate.config) { + ctx->iamfstate.config = gf_odf_iamf_cfg_new(); + if (!ctx->iamfstate.config) return GF_OUT_OF_MEM; + } + + ctx->is_av1 = ctx->is_vp9 = ctx->is_iamf = GF_FALSE; + if (ctx->sei_loader) { + gf_sei_loader_del(ctx->sei_loader); + ctx->sei_loader = NULL; + } ctx->codecid = 0; if (ctx->vp_cfg) gf_odf_vp_cfg_del(ctx->vp_cfg); ctx->vp_cfg = NULL; @@ -173,6 +190,14 @@ } ctx->pts_from_file = GF_FALSE; + if (gf_media_probe_iamf(bs)) { + ctx->bsmode = IAMF; + ctx->is_iamf = GF_TRUE; + ctx->codecid = GF_CODECID_IAMF; + if (last_obu_end) (*last_obu_end) = (u32) gf_bs_get_position(bs); + + return GF_OK; + } if (gf_media_probe_ivf(bs)) { u32 width = 0, height = 0; u32 codec_fourcc = 0, timebase_den = 0, timebase_num = 0, num_frames = 0; @@ -185,6 +210,8 @@ case GF_4CC('A', 'V', '0', '1'): ctx->is_av1 = GF_TRUE; ctx->codecid = GF_CODECID_AV1; + if (!ctx->sei_loader) ctx->sei_loader = gf_sei_loader_new(); + gf_sei_init_from_av1(ctx->sei_loader, &ctx->state); break; case GF_4CC('V', 'P', '9', '0'): ctx->is_vp9 = GF_TRUE; @@ -292,6 +319,8 @@ ctx->is_av1 = GF_TRUE; ctx->state.unframed = GF_TRUE; ctx->codecid = GF_CODECID_AV1; + if (!ctx->sei_loader) ctx->sei_loader = gf_sei_loader_new(); + gf_sei_init_from_av1(ctx->sei_loader, &ctx->state); return GF_OK; } @@ -322,7 +351,8 @@ GF_Err e; GF_BitStream *bs; u64 duration, cur_dur, last_cdur, file_size, max_pts, last_pts, probe_size=0; - AV1State av1state; + AV1State *av1state=NULL; + IAMFState *iamfstate=NULL; const char *filepath=NULL; const GF_PropertyValue *p; if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; @@ -345,7 +375,7 @@ } else { p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); if (!p || (p->value.longuint > 20000000)) { - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("AV1/VP9 Source file larger than 20M, skipping indexing\n")); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("AV1/VP9/IAMF Source file larger than 20M, skipping indexing\n")); if (!gf_sys_is_test_mode()) probe_size = 20000000; } else { @@ -364,6 +394,19 @@ } ctx->index_size = 0; + switch (ctx->bsmode) { + case IAMF: + GF_SAFEALLOC(iamfstate, IAMFState); + if (!iamfstate) { + return; + } + break; + default: + GF_SAFEALLOC(av1state, AV1State); + if (!av1state) { + return; + } + } bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); #ifndef GPAC_DISABLE_LOG @@ -375,9 +418,18 @@ gf_bs_seek(bs, ctx->file_hdr_size); } file_size = gf_bs_available(bs); - gf_av1_init_state(&av1state); - av1state.skip_frames = GF_TRUE; - av1state.config = gf_odf_av1_cfg_new(); + + switch (ctx->bsmode) { + case IAMF: + gf_iamf_init_state(iamfstate); + iamfstate->config = gf_odf_iamf_cfg_new(); + if (!iamfstate->config) return; + break; + default: + gf_av1_init_state(av1state); + av1state->skip_frames = GF_TRUE; + av1state->config = gf_odf_av1_cfg_new(); + } max_pts = last_pts = 0; duration = 0; @@ -390,19 +442,25 @@ if (probe_size && (frame_start>probe_size)) break; - gf_av1_reset_state(&av1state, GF_FALSE); + switch (ctx->bsmode) { + case IAMF: + gf_iamf_reset_state(iamfstate, GF_FALSE); + break; + default: + gf_av1_reset_state(av1state, GF_FALSE); + } /*we process each TU and extract only the necessary OBUs*/ switch (ctx->bsmode) { case OBUs: - e = aom_av1_parse_temporal_unit_from_section5(bs, &av1state); + e = aom_av1_parse_temporal_unit_from_section5(bs, av1state); break; case AnnexB: - e = aom_av1_parse_temporal_unit_from_annexb(bs, &av1state); + e = aom_av1_parse_temporal_unit_from_annexb(bs, av1state); break; case IVF: if (ctx->is_av1) { - e = aom_av1_parse_temporal_unit_from_ivf(bs, &av1state); + e = aom_av1_parse_temporal_unit_from_ivf(bs, av1state); } else { u64 frame_size; e = gf_media_parse_ivf_frame_header(bs, &frame_size, &pts); @@ -411,6 +469,10 @@ pts *= ctx->cur_fps.den; } break; + case IAMF: + e = aom_iamf_parse_temporal_unit(bs, iamfstate); + is_sap = GF_TRUE; + break; default: e = GF_NOT_SUPPORTED; } @@ -430,7 +492,7 @@ duration += ctx->cur_fps.den; cur_dur += ctx->cur_fps.den; } - if (av1state.frame_state.key_frame) + if (ctx->bsmode != IAMF && av1state->frame_state.key_frame) is_sap = GF_TRUE; //only index at I-frame start @@ -450,18 +512,28 @@ probe_size = gf_bs_get_position(bs); gf_bs_del(bs); gf_fclose(stream); - gf_odf_av1_cfg_del(av1state.config); - gf_av1_reset_state(&av1state, GF_TRUE); + switch (ctx->bsmode) { + case IAMF: + if (iamfstate->config) gf_odf_iamf_cfg_del(iamfstate->config); + gf_iamf_reset_state(iamfstate, GF_TRUE); + gf_free(iamfstate); + break; + default: + if (av1state->config) gf_odf_av1_cfg_del(av1state->config); + gf_av1_reset_state(av1state, GF_TRUE); + gf_free(av1state); + } if (!ctx->duration.num || (ctx->duration.num * ctx->cur_fps.num != duration * ctx->duration.den)) { if (probe_size) { duration *= file_size / probe_size; } ctx->duration.num = (s32) duration; - if (probe_size) ctx->duration.num = -ctx->duration.num; ctx->duration.den = ctx->cur_fps.num; gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + if (probe_size) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION_AVG, &PROP_BOOL(GF_TRUE) ); if (ctx->duration.num && (!gf_sys_is_test_mode() || gf_opts_get_bool("temp", "force_indexing"))) { file_size *= 8 * ctx->duration.den; @@ -559,39 +631,6 @@ } } -static void av1dmx_set_mdcv(GF_AV1DmxCtx *ctx, GF_FilterPid *pid, GF_FilterPacket *pck) -{ - u32 i; - u64 val; - u8 rw_mdcv24; - GF_BitStream *bs_r = gf_bs_new(ctx->state.mdcv_data, 24, GF_BITSTREAM_READ); - GF_BitStream *bs_w = gf_bs_new(rw_mdcv, 24, GF_BITSTREAM_WRITE); - - //3x{display_primaries_x, display_primaries_y} + whitePoint_x + whitePoint_y - //translate from AV1 representation 0.16 float to MPEG in increments of 0.00002 (1/50000) - for (i=0; i<8; i++) { - val = gf_bs_read_u16(bs_r); - val = (50000 * val) / 65536; - gf_bs_write_u16(bs_w, (u32) val); - } - //max_display_mastering_luminance: 24.8 fixed point in AV1 vs increments of 0.0001 (1/10000) candelas per square metre in MPEG - val = gf_bs_read_u32(bs_r); - val = (10000 * val) / 256; - gf_bs_write_u32(bs_w, (u32) val); - - //min_display_mastering_luminance: 18.14 fixed point in AV1 vs increments of 0.0001 (1/10000) candelas per square metre in MPEG - val = gf_bs_read_u32(bs_r); - val = (10000 * val) / 16384; - gf_bs_write_u32(bs_w, (u32) val); - gf_bs_del(bs_r); - gf_bs_del(bs_w); - - if (pid) { - gf_filter_pid_set_property(pid, GF_PROP_PID_MASTER_DISPLAY_COLOUR, &PROP_DATA(rw_mdcv, 24)); - } else if (pck) { - gf_filter_pck_set_property(pck, GF_PROP_PID_MASTER_DISPLAY_COLOUR, &PROP_DATA(rw_mdcv, 24)); - } -} static void av1dmx_check_pid(GF_Filter *filter, GF_AV1DmxCtx *ctx) { u8 *dsi; @@ -599,6 +638,12 @@ //no config or no config change if (ctx->is_av1 && !gf_list_count(ctx->state.frame_state.header_obus)) return; + if (ctx->is_iamf && (!ctx->iamfstate.frame_state.pre_skip_is_finalized || !gf_list_count(ctx->iamfstate.frame_state.descriptor_obus))) return; + + if (ctx->is_iamf) { + ctx->cur_fps.num = ctx->iamfstate.sample_rate; + ctx->cur_fps.den = ctx->iamfstate.num_samples_per_frame; + } if (!ctx->opid) { if (ctx->bsmode==UNSUPPORTED) return; @@ -639,6 +684,31 @@ gf_free(dsi); return; } + } else if (ctx->is_iamf) { + //IAMF Descriptors changed, compute dsi + + //Clear any old configOBUs - these will be repopulated from the descriptor OBUs below. + while (gf_list_count(ctx->iamfstate.config->configOBUs)) { + GF_IamfObu *a = (GF_IamfObu*) gf_list_pop_back(ctx->iamfstate.config->configOBUs); + if (a->raw_obu_bytes) gf_free(a->raw_obu_bytes); + gf_free(a); + } + ctx->iamfstate.config->configOBUs_size = 0; + + dsi = NULL; + dsi_size = 0; + + while (gf_list_count(ctx->iamfstate.frame_state.descriptor_obus)) { + GF_IamfObu *a = (GF_IamfObu*) gf_list_get(ctx->iamfstate.frame_state.descriptor_obus, 0); + gf_list_add(ctx->iamfstate.config->configOBUs, a); + ctx->iamfstate.config->configOBUs_size += (u32) a->obu_length; + gf_list_rem(ctx->iamfstate.frame_state.descriptor_obus, 0); + } + + gf_odf_iamf_cfg_write(ctx->iamfstate.config, &dsi, &dsi_size); + + // Compute the CRC of the entire iacb box. + crc = gf_crc_32(dsi, (u32) dsi_size); } if ((crc == ctx->dsi_crc) && !ctx->copy_props) { @@ -652,12 +722,23 @@ ctx->copy_props = GF_FALSE; gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + + if (ctx->is_iamf) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_AUDIO)); + if(ctx->iamfstate.pre_skip > 0) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DELAY, &PROP_LONGSINT(- (s64)ctx->iamfstate.pre_skip)); + } + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + } + if (ctx->sei_loader) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->codecid)); if (!ctx->timescale) { gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->cur_fps.num)); } + //if we have a FPS prop, use it if (!gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FPS)) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, & PROP_FRAC(ctx->cur_fps)); @@ -700,13 +781,17 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_RANGE, & PROP_BOOL(ctx->state.color_range) ); - if (ctx->state.clli_valid) { - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CONTENT_LIGHT_LEVEL, &PROP_DATA(ctx->state.clli_data, 4)); - ctx->clli_crc = gf_crc_32(ctx->state.clli_data, 4); + if (ctx->state.sei.clli_valid) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CONTENT_LIGHT_LEVEL, &PROP_DATA(ctx->state.sei.clli_data, 4)); + ctx->clli_crc = gf_crc_32(ctx->state.sei.clli_data, 4); + ctx->state.sei.clli_valid = 0; } - if (ctx->state.mdcv_valid) { - av1dmx_set_mdcv(ctx, ctx->opid, NULL); - ctx->mdcv_crc = gf_crc_32(ctx->state.mdcv_data, 24); + if (ctx->state.sei.mdcv_valid) { + u8 rw_mdcv24; + gf_av1_format_mdcv_to_mpeg(ctx->state.sei.mdcv_data, rw_mdcv); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MASTER_DISPLAY_COLOUR, &PROP_DATA(rw_mdcv, 24)); + ctx->mdcv_crc = gf_crc_32(ctx->state.sei.mdcv_data, 24); + ctx->state.sei.mdcv_valid = 0; } } @@ -729,7 +814,7 @@ u64 frame_size = 0, pts = GF_FILTER_NO_TS; GF_FilterPacket *pck; u64 pos=0, pos_ivf_hdr=0; - u8 *output; + u8 *output=NULL; if (ctx->bsmode==IVF) { pos_ivf_hdr = gf_bs_get_position(ctx->bs); @@ -787,9 +872,8 @@ } gf_bs_seek(ctx->bs, pos); - gf_bs_read_data(ctx->bs, output, pck_size); - if (output0 & 0x80) + if (gf_bs_read_data(ctx->bs, output, pck_size) && (output0 & 0x80)) gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); else gf_filter_pck_set_sap(pck, GF_FILTER_SAP_NONE); @@ -940,11 +1024,22 @@ if (!ctx->opid) return GF_NON_COMPLIANT_BITSTREAM; - if (gf_bs_get_size(ctx->state.bs)) + if (ctx->is_iamf) { + if (ctx->iamfstate.temporal_unit_obus) { + gf_free(ctx->iamfstate.temporal_unit_obus); + ctx->iamfstate.temporal_unit_obus = NULL; + } + gf_bs_get_content_no_truncate(ctx->iamfstate.bs, &ctx->iamfstate.temporal_unit_obus, &pck_size, &ctx->iamfstate.temporal_unit_obus_alloc); + } else if (ctx->state.bs && gf_bs_get_size(ctx->state.bs)) { gf_bs_get_content_no_truncate(ctx->state.bs, &ctx->state.frame_obus, &pck_size, &ctx->state.frame_obus_alloc); + } if (!pck_size) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("AV1Dmx no frame OBU, skipping OBU\n")); + if (ctx->is_iamf) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("AV1Dmx no IAMF OBUs making up a temporal unit, skipping OBUs\n")); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("AV1Dmx no frame OBU, skipping OBU\n")); + } return GF_OK; } @@ -956,13 +1051,26 @@ gf_filter_pck_set_cts(pck, ctx->cts); gf_filter_pck_set_sap(pck, ctx->state.frame_state.key_frame ? GF_FILTER_SAP_1 : 0); + gf_filter_pck_set_switch_frame(pck, ctx->state.frame_state.switch_frame); - memcpy(output, ctx->state.frame_obus, pck_size); + if (ctx->is_iamf) { + memcpy(output, ctx->iamfstate.temporal_unit_obus, pck_size); + if (ctx->iamfstate.audio_roll_distance != 0) { + gf_filter_pck_set_roll_info(pck, ctx->iamfstate.audio_roll_distance); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_4); + } + if (ctx->iamfstate.frame_state.num_samples_to_trim_at_end > 0) { + u64 trimmed_duration = ctx->iamfstate.num_samples_per_frame - ctx->iamfstate.frame_state.num_samples_to_trim_at_end; + gf_filter_pck_set_duration(pck, (u32) trimmed_duration); + } + } else { + memcpy(output, ctx->state.frame_obus, pck_size); + } if (ctx->deps) { u8 flags = 0; //dependsOn - flags = ( ctx->state.frame_state.key_frame) ? 2 : 1; + flags = (ctx->state.frame_state.key_frame || ctx->state.frame_state.switch_frame) ? 2 : 1; flags <<= 2; //dependedOn flags |= ctx->state.frame_state.refresh_frame_flags ? 1 : 2; @@ -972,23 +1080,14 @@ gf_filter_pck_set_dependency_flags(pck, flags); } - if (ctx->state.clli_valid) { - u32 crc = gf_crc_32(ctx->state.clli_data, 4); - if (crc != ctx->clli_crc) { - gf_filter_pck_set_property(pck, GF_PROP_PID_CONTENT_LIGHT_LEVEL, &PROP_DATA(ctx->state.clli_data, 4)); - } - } - if (ctx->state.mdcv_valid) { - u32 crc = gf_crc_32(ctx->state.mdcv_data, 24); - if (crc != ctx->mdcv_crc) { - av1dmx_set_mdcv(ctx, NULL, pck); - } - } + if (ctx->sei_loader) + gf_sei_load_from_state(ctx->sei_loader, pck); gf_filter_pck_send(pck); av1dmx_update_cts(ctx); gf_av1_reset_state(&ctx->state, GF_FALSE); + gf_iamf_reset_state(&ctx->iamfstate, GF_FALSE); return GF_OK; @@ -1066,8 +1165,47 @@ return GF_OK; } - return av1dmx_parse_flush_sample(filter, ctx); + e = av1dmx_parse_flush_sample(filter, ctx); + ctx->state.sei.clli_valid = ctx->state.sei.mdcv_valid = 0; + return e; +} + +GF_Err av1dmx_parse_iamf(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + GF_Err e = GF_OK; + + //first TU loaded ! + u64 start = gf_bs_get_position(ctx->bs); + if (ctx->iamfstate.frame_state.found_full_temporal_unit) { + e = GF_OK; + } else { + e = aom_iamf_parse_temporal_unit(ctx->bs, &ctx->iamfstate); + if (e==GF_BUFFER_TOO_SMALL) { + gf_iamf_reset_state(&ctx->iamfstate, GF_FALSE); + gf_bs_seek(ctx->bs, start); + } + } + //check pid state + av1dmx_check_pid(filter, ctx); + + if (e) { + if (e!=GF_EOS && e!=GF_BUFFER_TOO_SMALL) { + av1dmx_parse_flush_sample(filter, ctx); + } + return e; + } + + if (!ctx->opid) { + if (ctx->iamfstate.frame_state.pre_skip_is_finalized) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("AV1Dmx output pid not configured (no IAMF Descriptors yet?), skipping OBUs\n")); + } + gf_iamf_reset_state(&ctx->iamfstate, GF_FALSE); + return GF_OK; + } + + e = av1dmx_parse_flush_sample(filter, ctx); + return e; } GF_Err av1dmx_process_buffer(GF_Filter *filter, GF_AV1DmxCtx *ctx, const char *data, u32 data_size, Bool is_copy) @@ -1083,14 +1221,16 @@ gf_bs_set_logger(ctx->bs, av1dmx_bs_log, ctx); #endif - //check ivf vs obu vs annexB + //check ivf vs obu vs annexB vs iamf e = av1dmx_check_format(filter, ctx, ctx->bs, &last_obu_end); if (e==GF_BUFFER_TOO_SMALL) return GF_OK; else if (e) return e; while (gf_bs_available(ctx->bs)) { - if (ctx->is_vp9) { + if (ctx->is_iamf) { + e = av1dmx_parse_iamf(filter, ctx); + } else if (ctx->is_vp9) { e = av1dmx_parse_vp9(filter, ctx); } else if (ctx->is_av1) { e = av1dmx_parse_av1(filter, ctx); @@ -1245,6 +1385,7 @@ gf_av1_init_state(&ctx->state); if (ctx->temporal_delim) ctx->state.keep_temporal_delim = GF_TRUE; + gf_iamf_init_state(&ctx->iamfstate); return GF_OK; } @@ -1262,6 +1403,13 @@ if (ctx->buffer) gf_free(ctx->buffer); if (ctx->vp_cfg) gf_odf_vp_cfg_del(ctx->vp_cfg); + + gf_iamf_reset_state(&ctx->iamfstate, GF_TRUE); + if (ctx->iamfstate.config) gf_odf_iamf_cfg_del(ctx->iamfstate.config); + if (ctx->iamfstate.bs) gf_bs_del(ctx->iamfstate.bs); + if (ctx->iamfstate.temporal_unit_obus) gf_free(ctx->iamfstate.temporal_unit_obus); + if (ctx->sei_loader) + gf_sei_loader_del(ctx->sei_loader); } static const char * av1dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) @@ -1273,27 +1421,36 @@ lt = gf_log_get_tool_level(GF_LOG_CODING); gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_QUIET); - res = gf_media_probe_ivf(bs); - if (res) { + if (gf_media_probe_iamf(bs)) { + res = GF_TRUE; + *score = GF_FPROBE_SUPPORTED; + mime = "audio/iamf"; + } else if (gf_media_probe_ivf(bs)) { + res = GF_TRUE; *score = GF_FPROBE_SUPPORTED; mime = "video/x-ivf"; + } else if (gf_media_aom_probe_annexb(bs)) { + res = GF_TRUE; + *score = GF_FPROBE_SUPPORTED; } else { res = gf_media_aom_probe_annexb(bs); if (res) { *score = GF_FPROBE_SUPPORTED; } else { - AV1State state; + AV1State *av1_state; Bool has_seq_header = GF_FALSE; GF_Err e; u32 nb_units = 0; + GF_SAFEALLOC(av1_state, AV1State); + if (!av1_state) return NULL; - gf_av1_init_state(&state); - state.config = gf_odf_av1_cfg_new(); + gf_av1_init_state(av1_state); + av1_state->config = gf_odf_av1_cfg_new(); while (gf_bs_available(bs)) { - e = aom_av1_parse_temporal_unit_from_section5(bs, &state); + e = aom_av1_parse_temporal_unit_from_section5(bs, av1_state); if ((e==GF_OK) || (nb_units && (e==GF_BUFFER_TOO_SMALL) ) ) { - if (!nb_units || gf_list_count(state.frame_state.header_obus) || gf_list_count(state.frame_state.frame_obus)) { - if (gf_list_count(state.frame_state.header_obus)) { + if (!nb_units || gf_list_count(av1_state->frame_state.header_obus) || gf_list_count(av1_state->frame_state.frame_obus)) { + if (gf_list_count(av1_state->frame_state.header_obus)) { has_seq_header = GF_TRUE; } nb_units++; @@ -1310,7 +1467,7 @@ } //very large frame else if (!nb_units && (e==GF_BUFFER_TOO_SMALL)) { - if (gf_list_count(state.frame_state.header_obus) && state.width && state.height) { + if (gf_list_count(av1_state->frame_state.header_obus) && av1_state->width && av1_state->height) { res = GF_TRUE; *score = GF_FPROBE_MAYBE_SUPPORTED; } @@ -1318,15 +1475,16 @@ } else { break; } - gf_av1_reset_state(&state, GF_FALSE); + gf_av1_reset_state(av1_state, GF_FALSE); if (nb_units>2) { res = GF_TRUE; *score = GF_FPROBE_SUPPORTED; break; } } - gf_odf_av1_cfg_del(state.config); - gf_av1_reset_state(&state, GF_TRUE); + gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); } } @@ -1349,12 +1507,23 @@ CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_VP10), CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), {0}, - CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), - CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AV1), - CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP8), - CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP9), - CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP10), - CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AV1), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VP8), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VP9), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VP10), + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "obu|iamf"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/iamf"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_IAMF), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_IAMF), + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), }; #define OFFS(_n) #_n, offsetof(GF_AV1DmxCtx, _n) @@ -1379,8 +1548,9 @@ GF_FilterRegister AV1DmxRegister = { .name = "rfav1", - GF_FS_SET_DESCRIPTION("AV1/IVF/VP9 reframer") - GF_FS_SET_HELP("This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and outputs corresponding visual PID and frames.") + GF_FS_SET_DESCRIPTION("AV1/IVF/VP9/IAMF reframer") + GF_FS_SET_HELP("This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and outputs corresponding visual PID and frames. " + "It also parses IAMF OBU and outputs corresponding temporal units containing audio frames and parameter blocks.") .private_size = sizeof(GF_AV1DmxCtx), .args = AV1DmxArgs, .initialize = av1dmx_initialize, @@ -1389,7 +1559,8 @@ .configure_pid = av1dmx_configure_pid, .process = av1dmx_process, .probe_data = av1dmx_probe_data, - .process_event = av1dmx_process_event + .process_event = av1dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -1403,4 +1574,3 @@ return NULL; } #endif // #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_RFAV1) -
View file
gpac-2.4.0.tar.gz/src/filters/reframe_flac.c -> gpac-26.02.0.tar.gz/src/filters/reframe_flac.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / FLAC reframer filter @@ -387,7 +387,7 @@ { u32 crc = 0; const u8 *end = data+len; - while (data < end) { + while (data < end) { crc = flac_dmx_crc16_table((u8) crc) ^ *data++ ^ (crc >> 8); } return crc; @@ -426,16 +426,16 @@ return GF_FALSE; ch_lay = gf_bs_read_int(ctx->bs, 4); - if (ch_lay < FLAC_CHANNELS) { - } else if (ch_lay < FLAC_CHANNELS + FLAC_MID_SIDE) { - ch_lay = 1; - } else { + if (ch_lay < FLAC_CHANNELS) { + } else if (ch_lay < FLAC_CHANNELS + FLAC_MID_SIDE) { + ch_lay = 1; + } else { return GF_FALSE; - } + } u32 bps = gf_bs_read_int(ctx->bs, 3); - if (bps == 3) + if (bps == 3) return GF_FALSE; //reserved=0 if (gf_bs_read_int(ctx->bs, 1)) @@ -487,18 +487,18 @@ if (crc != crc_hdr) { return GF_FALSE; } - // subframe reserved zero bit - if (gf_bs_read_int(ctx->bs, 1) != 0) - return GF_FALSE; - // subframe type - crc = gf_bs_read_int(ctx->bs, 6); - if ((crc == 0) || (crc == 1) + // subframe reserved zero bit + if (gf_bs_read_int(ctx->bs, 1) != 0) + return GF_FALSE; + // subframe type + crc = gf_bs_read_int(ctx->bs, 6); + if ((crc == 0) || (crc == 1) || ((crc >= 8) && (crc <= 12)) || (crc >= 32) ) { } else { - return GF_FALSE; - } + return GF_FALSE; + } if (gf_bs_is_overflow(ctx->bs)) return GF_FALSE; @@ -520,6 +520,7 @@ u32 pck_size, remain, prev_pck_size; u64 cts; FLACHeader hdr; + memset(&hdr, 0, sizeof(FLACHeader)); restart: cts = GF_FILTER_NO_TS; @@ -743,7 +744,7 @@ cts = GF_FILTER_NO_TS; } - if (!ctx->in_seek) { + if (!ctx->in_seek && remain >= next_frame) { dst_pck = gf_filter_pck_new_alloc(ctx->opid, next_frame, &output); if (!dst_pck) return GF_OUT_OF_MEM; memcpy(output, start, next_frame); @@ -781,6 +782,10 @@ if (remain < ctx->flac_buffer_size) { memmove(ctx->flac_buffer, start, remain); } + if (!ctx->src_pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } ctx->flac_buffer_size = remain; gf_filter_pid_drop_packet(ctx->ipid); } @@ -854,7 +859,8 @@ .configure_pid = flac_dmx_configure_pid, .process = flac_dmx_process, .probe_data = flac_dmx_probe_data, - .process_event = flac_dmx_process_event + .process_event = flac_dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -878,4 +884,3 @@ return NULL; } #endif // GPAC_DISABLE_RFFLAC -
View file
gpac-2.4.0.tar.gz/src/filters/reframe_h263.c -> gpac-26.02.0.tar.gz/src/filters/reframe_h263.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / H263 reframer filter @@ -767,7 +767,8 @@ .configure_pid = h263dmx_configure_pid, .process = h263dmx_process, .probe_data = h263dmx_probe_data, - .process_event = h263dmx_process_event + .process_event = h263dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_img.c -> gpac-26.02.0.tar.gz/src/filters/reframe_img.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / image (jpg/png/bmp/j2k) reframer filter @@ -321,7 +321,13 @@ in_stride = out_stride; while (in_stride % 4) in_stride++; - u32 max_offset = fh.bfOffBits+(h-1)*in_stride + out_stride ; + u32 h_offset = (h-1)*in_stride + out_stride; + if ((in_stride && ((h-1) >= GF_UINT_MAX/in_stride)) || (fh.bfOffBits >= GF_UINT_MAX - h_offset)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Invalid parameters fh.bfOffBits=%u h=%u in_stride=%u out_stride=%u\n", fh.bfOffBits, h, in_stride, out_stride)); + return GF_NON_COMPLIANT_BITSTREAM; + } + + u32 max_offset = fh.bfOffBits + h_offset; if (data_size < max_offset) { GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Trying to output image of size %lu but provided only %lu input bytes\n", max_offset, data_size)); return GF_NON_COMPLIANT_BITSTREAM; @@ -442,7 +448,8 @@ .configure_pid = img_configure_pid, .probe_data = img_probe_data, .process = img_process, - .process_event = img_process_event + .process_event = img_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; const GF_FilterRegister *rfimg_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/reframe_latm.c -> gpac-26.02.0.tar.gz/src/filters/reframe_latm.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AAC ADTS reframer filter @@ -77,6 +77,8 @@ u32 bitrate; GF_Err in_error; Bool copy_props; + + u8 latm_dmx_bufferLATM_DMX_MAX_SIZE; } GF_LATMDmxCtx; @@ -130,7 +132,8 @@ if (!same_cfg) { if (amux_version==1) gf_latm_get_value(bs); - gf_m4a_parse_config(bs, acfg, GF_FALSE); + GF_Err e = gf_m4a_parse_config(bs, acfg, GF_FALSE); + if (e) return GF_FALSE; } frameLengthType = gf_bs_read_int(bs, 3); if (!frameLengthType) { @@ -527,9 +530,8 @@ while (1) { pos = (u32) gf_bs_get_position(ctx->bs); - u8 latm_bufferLATM_DMX_MAX_SIZE; u32 latm_frame_size = LATM_DMX_MAX_SIZE; - if (!latm_dmx_sync_frame_bs(ctx->bs,&ctx->acfg, &latm_frame_size, latm_buffer, NULL)) break; + if (!latm_dmx_sync_frame_bs(ctx->bs,&ctx->acfg, &latm_frame_size, ctx->latm_dmx_buffer, NULL)) break; if (ctx->in_seek) { u64 nb_samples_at_seek = (u64) (ctx->start_range * GF_M4ASampleRatesctx->sr_idx); @@ -557,7 +559,7 @@ if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); - memcpy(output, latm_buffer, latm_frame_size); + memcpy(output, ctx->latm_dmx_buffer, latm_frame_size); gf_filter_pck_set_cts(dst_pck, ctx->cts); if (ctx->timescale && (ctx->timescale!=ctx->sample_rate)) @@ -602,6 +604,10 @@ } else { ctx->latm_buffer_size = 0; } + if (!ctx->src_pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } gf_filter_pid_drop_packet(ctx->ipid); gf_assert(!ctx->resume_from); } else { @@ -618,6 +624,7 @@ if (ctx->bs) gf_bs_del(ctx->bs); if (ctx->indexes) gf_free(ctx->indexes); if (ctx->latm_buffer) gf_free(ctx->latm_buffer); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); } static const char *latm_dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) @@ -625,6 +632,7 @@ u32 nb_frames=0; u32 nb_skip=0; GF_M4ADecSpecInfo acfg; + acfg.base_sr_index = (u32)-1; GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); while (1) { u32 nb_skipped = 0; @@ -690,7 +698,8 @@ .configure_pid = latm_dmx_configure_pid, .process = latm_dmx_process, .probe_data = latm_dmx_probe_data, - .process_event = latm_dmx_process_event + .process_event = latm_dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_mhas.c -> gpac-26.02.0.tar.gz/src/filters/reframe_mhas.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2023 + * Copyright (c) Telecom ParisTech 2020-2024 * All rights reserved * * This file is part of GPAC / MHAS reframer filter @@ -323,6 +323,7 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY( data, (dsi_size+5) ) ); } else { gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_MHAS ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, & PROP_UINT( PL ) ); } gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : ctx->sample_rate)); @@ -406,15 +407,15 @@ return GF_FALSE; } -static GFINLINE void mhas_dmx_update_cts(GF_MHASDmxCtx *ctx) +static GFINLINE void mhas_dmx_update_cts(GF_MHASDmxCtx *ctx, u32 pck_dur) { if (ctx->timescale) { - u64 inc = ctx->frame_len; + u64 inc = pck_dur; inc *= ctx->timescale; inc /= ctx->sample_rate; ctx->cts += inc; } else { - ctx->cts += ctx->frame_len; + ctx->cts += pck_dur; } } @@ -706,8 +707,10 @@ offset /= ctx->sample_rate; } gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DELAY , &PROP_LONGSINT( -offset)); + } else if (pck_dur >= nb_trunc_samples) { + pck_dur -= nb_trunc_samples; } - } else { + } else if (pck_dur >= nb_trunc_samples) { pck_dur -= nb_trunc_samples; } nb_trunc_samples = 0; @@ -725,12 +728,12 @@ if (!has_cfg) mhas_sap = 0; - if (mhas_sap) { - gf_filter_pck_set_sap(dst, GF_FILTER_SAP_1); - } + gf_filter_pck_set_sap(dst, mhas_sap ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); + gf_filter_pck_set_dts(dst, ctx->cts); gf_filter_pck_set_cts(dst, ctx->cts); gf_filter_pck_set_duration(dst, (u32) pck_dur); + gf_filter_pck_set_framing(dst, GF_TRUE, GF_TRUE); if (ctx->byte_offset != GF_FILTER_NO_BO) { u64 offset = (u64) (start - ctx->mhas_buffer); offset += ctx->byte_offset + au_start; @@ -743,7 +746,7 @@ consumed = au_start; ctx->nb_frames ++; - mhas_dmx_update_cts(ctx); + mhas_dmx_update_cts(ctx,(u32) pck_dur); has_cfg = 0; if (prev_pck_size) { @@ -796,10 +799,15 @@ if (!ctx->mhas_buffer_size) { if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); ctx->src_pck = NULL; + } else if (in_pck && !ctx->src_pck) { + ctx->src_pck = in_pck; + gf_filter_pck_ref_props(&ctx->src_pck); } - if (in_pck) + + if (in_pck) { gf_filter_pid_drop_packet(ctx->ipid); + } return GF_OK; } @@ -915,7 +923,8 @@ .configure_pid = mhas_dmx_configure_pid, .process = mhas_dmx_process, .probe_data = mhas_dmx_probe_data, - .process_event = mhas_dmx_process_event + .process_event = mhas_dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_mp3.c -> gpac-26.02.0.tar.gz/src/filters/reframe_mp3.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / MP3 reframer filter @@ -750,6 +750,10 @@ if (remain) { memmove(ctx->mp3_buffer, start, remain); } + if (!ctx->src_pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } ctx->mp3_buffer_size = remain; gf_filter_pid_drop_packet(ctx->ipid); } @@ -917,7 +921,8 @@ .configure_pid = mp3_dmx_configure_pid, .process = mp3_dmx_process, .probe_data = mp3_dmx_probe_data, - .process_event = mp3_dmx_process_event + .process_event = mp3_dmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_mpgvid.c -> gpac-26.02.0.tar.gz/src/filters/reframe_mpgvid.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / MPEG-1/2/4(Part2) video reframer filter @@ -109,19 +109,24 @@ return GF_NOT_SUPPORTED; ctx->ipid = pid; - ctx->cur_fps = ctx->fps; - if (!ctx->fps.num || !ctx->fps.den) { - ctx->cur_fps.num = 25000; - ctx->cur_fps.den = 1000; + if (!ctx->cur_fps.num) { + ctx->cur_fps = ctx->fps; + if (!ctx->fps.num || !ctx->fps.den) { + ctx->cur_fps.num = 25000; + ctx->cur_fps.den = 1000; + } } p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); if (p) { - ctx->timescale = ctx->cur_fps.num = p->value.uint; - ctx->cur_fps.den = 0; + u32 old_timescale = ctx->timescale; + ctx->timescale = p->value.uint; p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); if (p) { ctx->cur_fps = p->value.frac; + } else if (!old_timescale || (old_timescale != ctx->timescale)) { + ctx->cur_fps.den = 0; + ctx->cur_fps.num = ctx->timescale; } p = gf_filter_pid_get_property_str(pid, "nocts"); if (p && p->value.boolean) ctx->recompute_cts = GF_TRUE; @@ -1405,7 +1410,8 @@ .configure_pid = mpgviddmx_configure_pid, .process = mpgviddmx_process, .probe_data = mpgvdmx_probe_data, - .process_event = mpgviddmx_process_event + .process_event = mpgviddmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_nalu.c -> gpac-26.02.0.tar.gz/src/filters/reframe_nalu.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / NALU (AVC, HEVC, VVC) reframer filter @@ -37,14 +37,13 @@ GF_Err gf_bs_set_logger(GF_BitStream *bs, void (*on_bs_log)(void *udta, const char *field_name, u32 nb_bits, u64 field_val, s32 idx1, s32 idx2, s32 idx3), void *udta); -enum -{ +GF_OPT_ENUM (GF_DolbyVisionSignalingMode, DVMODE_NONE=0, DVMODE_AUTO, DVMODE_FORCE, DVMODE_CLEAN, DVMODE_SINGLE, -}; +); typedef struct { @@ -60,23 +59,24 @@ u32 min_temporal_id, max_temporal_id; } LHVCLayerInfo; -enum { +GF_OPT_ENUM (GF_GOPBufferingMode, STRICT_POC_OFF = 0, STRICT_POC_ON, STRICT_POC_ERROR, -}; +); typedef struct { //filter args GF_Fraction fps; Double index; - Bool explicit, force_sync, nosei, importer, subsamples, nosvc, novpsext, deps, seirw, audelim, analyze, notime; + Bool explicit, force_sync, nosei, importer, subsamples, nosvc, novpsext, deps, seirw, audelim, keepfiller, analyze, notime, refs; u32 nal_length; - u32 strict_poc; + GF_GOPBufferingMode strict_poc; u32 bsdbg; GF_Fraction dur; - u32 dv_mode, dv_profile, dv_compatid; + GF_DolbyVisionSignalingMode dv_mode; + u32 dv_profile, dv_compatid; //only one input pid declared GF_FilterPid *ipid; @@ -155,9 +155,11 @@ u32 nal_store_size, nal_store_alloc; //list of param sets found - GF_List *sps, *pps, *vps, *sps_ext, *pps_svc, *vvc_aps_pre, *vvc_dci, *vvc_opi; - //set to true if one of the PS has been modified, will potentially trigger a PID reconfigure - Bool ps_modified; + GF_List *sps, *pps, *vps, *sps_ext, *pps_svc, *vvc_aps_pre, *vvc_dci, *vvc_opi, *sei_prefix; + //set if one of the PS has been modified, will potentially trigger a PID reconfigure + //if bit 1 is set, PS changed in bitstream + //if bit 2 is set, this is a source PID reconfiguration + u32 ps_modified; //set to true if one PS has been changed - if false and ps_modified is set, only new PS have been added Bool ps_changed; @@ -237,6 +239,7 @@ //layer and temporal ID of last VCL nal u8 last_layer_id, last_temporal_id; + //only used to set the first clli / mdcv u32 clli_crc, mdcv_crc; u32 nb_dv_rpu, nb_dv_el; @@ -248,6 +251,8 @@ Bool sap2_as_sap1; GF_FilterPacket *prev_sap; + + GF_SEILoader *sei_loader; } GF_NALUDmxCtx; static void naludmx_enqueue_or_dispatch(GF_NALUDmxCtx *ctx, GF_FilterPacket *n_pck, Bool flush_ref); @@ -276,13 +281,15 @@ ctx->ipid = pid; p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); if (p) { + u32 old_timescale = ctx->timescale; ctx->timescale = p->value.uint; - ctx->cur_fps.den = 0; - ctx->cur_fps.num = ctx->timescale; p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); if (p) { ctx->cur_fps = p->value.frac; + } else if (!old_timescale || (old_timescale != ctx->timescale)) { + ctx->cur_fps.den = 0; + ctx->cur_fps.num = ctx->timescale; } } @@ -354,17 +361,32 @@ if (ctx->vvc_state) { gf_free(ctx->vvc_state); ctx->vvc_state = NULL; } if (!ctx->hevc_state) GF_SAFEALLOC(ctx->hevc_state, HEVCState); ctx->min_layer_id = 0xFF; + if (ctx->refs) + ctx->hevc_state->full_slice_header_parse = GF_TRUE; + + gf_sei_init_from_hevc(ctx->sei_loader, ctx->hevc_state); } else if (ctx->codecid==GF_CODECID_VVC) { ctx->log_name = "VVC"; if (ctx->hevc_state) { gf_free(ctx->hevc_state); ctx->hevc_state = NULL; } if (ctx->avc_state) { gf_free(ctx->avc_state); ctx->avc_state = NULL; } if (!ctx->vvc_state) GF_SAFEALLOC(ctx->vvc_state, VVCState); + if (ctx->refs) { + //use parse mode 2 as we don't need the exact slice header length + ctx->vvc_state->parse_mode = 2; + } + gf_sei_init_from_vvc(ctx->sei_loader, ctx->vvc_state); } else { ctx->log_name = "AVC|H264"; if (ctx->hevc_state) { gf_free(ctx->hevc_state); ctx->hevc_state = NULL; } if (ctx->vvc_state) { gf_free(ctx->vvc_state); ctx->vvc_state = NULL; } if (!ctx->avc_state) GF_SAFEALLOC(ctx->avc_state, AVCState); + if (ctx->refs) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("%s reference picture list parsing not supported, patch welcome\n", ctx->log_name)); + ctx->refs = 0; + } + gf_sei_init_from_avc(ctx->sei_loader, ctx->avc_state); } + if (ctx->timescale && !ctx->opid) { ctx->opid = gf_filter_pid_new(filter); ctx->nb_slices_in_au = 0; @@ -395,10 +417,11 @@ naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); } - ctx->nal_store_size = 0; - - if (ctx->timescale != 0) - ctx->resume_from = 0; + if (old_codecid != ctx->codecid) { + ctx->nal_store_size = 0; + if (ctx->timescale != 0) + ctx->resume_from = 0; + } gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); //don't change codec type if reframing an ES (for HLS SAES) @@ -411,7 +434,10 @@ if (!gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_ID)) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(1)); - ctx->ps_modified = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); + + //force re-export of properties + ctx->ps_modified = 1<<1; ctx->crc_cfg = ctx->crc_cfg_enh = 0; } @@ -700,10 +726,11 @@ duration *= filesize/probe_size; } ctx->duration.num = (s32) duration; - if (probe_size) ctx->duration.num = -ctx->duration.num; ctx->duration.den = ctx->cur_fps.num; gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + if (probe_size) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION_AVG, &PROP_BOOL(GF_TRUE) ); if (duration && ctx->duration.num && (!gf_sys_is_test_mode() || gf_opts_get_bool("temp", "force_indexing"))) { filesize *= 8 * ctx->duration.den; @@ -802,7 +829,7 @@ dts += dts_inc; } //poc is stored as diff to adjusted POC (poc_shift) of last IDR - cts = ( ((s32) poc ) * ctx->cur_fps.den ) / ctx->poc_diff + ctx->dts_last_IDR; + cts = ( ((s64) poc ) * ctx->cur_fps.den ) / ctx->poc_diff + ctx->dts_last_IDR; gf_filter_pck_set_cts(q_pck, cts); GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("%s Frame timestamps computed dts "LLU" cts "LLU" (poc %d min poc %d poc_diff %d last IDR DTS "LLU")\n", ctx->log_name, dts, cts, poc, ctx->min_poc, ctx->poc_diff, ctx->dts_last_IDR)); @@ -849,12 +876,14 @@ static void naludmx_hevc_set_parall_type(GF_NALUDmxCtx *ctx, GF_HEVCConfig *hevc_cfg) { u32 use_tiles, use_wpp, nb_pps, i, count; - HEVCState hevc; + HEVCState *hvc_state; count = gf_list_count(ctx->pps); - memset(&hevc, 0, sizeof(HEVCState)); - hevc.sps_active_idx = -1; + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return; + + hvc_state->sps_active_idx = -1; use_tiles = 0; use_wpp = 0; @@ -862,12 +891,12 @@ for (i=0; i<count; i++) { GF_NALUFFParam *slc = (GF_NALUFFParam*)gf_list_get(ctx->pps, i); - s32 idx = gf_hevc_read_pps(slc->data, slc->size, &hevc); + s32 idx = gf_hevc_read_pps(slc->data, slc->size, hvc_state); if (idx>=0) { HEVC_PPS *pps; nb_pps++; - pps = &hevc.ppsidx; + pps = &hvc_state->ppsidx; if (!pps->entropy_coding_sync_enabled_flag && pps->tiles_enabled_flag) use_tiles++; else if (pps->entropy_coding_sync_enabled_flag && !pps->tiles_enabled_flag) @@ -878,6 +907,7 @@ else if (!use_wpp && (use_tiles==nb_pps) ) hevc_cfg->parallelismType = 2; else if (!use_tiles && (use_wpp==nb_pps) ) hevc_cfg->parallelismType = 3; else hevc_cfg->parallelismType = 0; + gf_free(hvc_state); } GF_Err naludmx_set_hevc_oinf(GF_NALUDmxCtx *ctx, u8 *max_temporal_id) @@ -1196,6 +1226,16 @@ naludmx_add_param_nalu(layer_id ? lvcc->param_array : cfg->param_array, sl, GF_HEVC_NALU_PIC_PARAM); } + cfg = ctx->explicit ? lvcc : hvcc; + count = gf_list_count(ctx->sei_prefix); + for (i=0; i<count; i++) { + GF_NALUFFParam *sl = gf_list_get(ctx->sei_prefix, i); + layer_id = ((sl->data0 & 0x1) << 5) | (sl->data1 >> 3); + if (!layer_id) *has_hevc_base = GF_TRUE; + if (!ctx->analyze) + naludmx_add_param_nalu(layer_id ? lvcc->param_array : cfg->param_array, sl, GF_HEVC_NALU_SEI_PREFIX); + } + *dsi = *dsi_enh = NULL; *dsi_size = *dsi_enh_size = 0; @@ -1493,7 +1533,6 @@ && (sps->vui.time_scale / 1000 <= sps->vui.num_units_in_tick) ) { /*ISO/IEC 14496-10 n11084 Table E-6*/ - /* not used : u8 DeltaTfiDivisorTable = {1,1,1,2,2,2,2,3,3,4,6}; */ u8 DeltaTfiDivisorIdx; if (!sps->vui.pic_struct_present_flag) { DeltaTfiDivisorIdx = 1 + (1 - ctx->avc_state->s_info.field_pic_flag); @@ -1608,24 +1647,23 @@ { if (!ctx->opid) return; + GF_SEIInfo *sei = NULL; + if (ctx->avc_state) sei = &ctx->avc_state->sei; + else if (ctx->hevc_state) sei = &ctx->hevc_state->sei; + else if (ctx->vvc_state) sei = &ctx->vvc_state->sei; + if (reset_crc) ctx->clli_crc = 0; - if ((ctx->hevc_state && ctx->hevc_state->clli_valid) - || (ctx->vvc_state && ctx->vvc_state->clli_valid) - ) { - u8 *clli = ctx->hevc_state ? ctx->hevc_state->clli_data : ctx->vvc_state->clli_data; - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CONTENT_LIGHT_LEVEL, &PROP_DATA(clli, 4)); - ctx->clli_crc = gf_crc_32(clli, 4); + if (sei && sei->clli_valid) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CONTENT_LIGHT_LEVEL, &PROP_DATA(sei->clli_data, 4)); + ctx->clli_crc = gf_crc_32(sei->clli_data, 4); } + if (reset_crc) ctx->mdcv_crc = 0; - - if ((ctx->hevc_state && ctx->hevc_state->mdcv_valid) - || (ctx->vvc_state && ctx->vvc_state->mdcv_valid) - ) { - u8 *mdcv = ctx->hevc_state ? ctx->hevc_state->mdcv_data : ctx->vvc_state->mdcv_data; - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MASTER_DISPLAY_COLOUR, &PROP_DATA(mdcv, 24)); - ctx->mdcv_crc = gf_crc_32(mdcv, 24); + if (sei && sei->mdcv_valid) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MASTER_DISPLAY_COLOUR, &PROP_DATA(sei->mdcv_data, 24)); + ctx->mdcv_crc = gf_crc_32(sei->mdcv_data, 24); } } @@ -1770,7 +1808,7 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOLBY_VISION, &PROP_DATA(dv_cfg, 24)); } -static void naludmx_check_pid(GF_Filter *filter, GF_NALUDmxCtx *ctx, Bool force_au_flush) +static void naludmx_check_pid(GF_Filter *filter, GF_NALUDmxCtx *ctx, Bool force_au_flush, Bool next_is_idr) { u32 w, h, ew, eh; u8 *dsi, *dsi_enh; @@ -1780,6 +1818,7 @@ Bool has_hevc_base = GF_TRUE; Bool has_colr_info = GF_FALSE; Bool res; + Bool is_reconfig_only; Bool dsi_is_superset = (!ctx->crc_cfg || ctx->ps_changed) ? GF_FALSE : GF_TRUE; if (ctx->analyze) { @@ -1789,7 +1828,8 @@ if (ctx->opid && (!gf_list_count(ctx->sps) || !gf_list_count(ctx->pps))) return; } - ctx->ps_modified = GF_FALSE; + is_reconfig_only = (ctx->ps_modified == (1<<1)) ? GF_TRUE : GF_FALSE; + ctx->ps_modified = 0; ctx->ps_changed = GF_FALSE; dsi = dsi_enh = NULL; @@ -1841,10 +1881,22 @@ if (force_au_flush) { naludmx_end_access_unit(ctx); } - naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); - if (!ctx->analyze && (gf_list_count(ctx->pck_queue)>1)) { + //special case for IDRs while in poc probe phase (typically if consecutive IDRs at start) + if (next_is_idr && !ctx->min_poc_probe_done) { + ctx->min_poc_probe_done = GF_TRUE; + u32 old_poc_diff = ctx->poc_diff; + if (!ctx->poc_diff) ctx->poc_diff = 1; + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + ctx->min_poc_probe_done = GF_FALSE; + ctx->poc_diff = old_poc_diff; + } else { + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + } + + if (!is_reconfig_only && !ctx->analyze && (gf_list_count(ctx->pck_queue)>1)) { GF_LOG(dsi_enh ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_MEDIA, ("%s xPS changed but could not flush frames before signaling state change %s\n", ctx->log_name, dsi_enh ? "- likely scalable xPS update" : "!")); } + //copy properties at init or reconfig gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); @@ -1855,6 +1907,8 @@ if (!gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_ID)) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(1)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); + ctx->width = w; ctx->height = h; ctx->sar = sar; @@ -1908,7 +1962,7 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); } //set interlaced or remove interlaced property - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, ctx->interlaced ? & PROP_UINT(GF_TRUE) : NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, ctx->interlaced ? & PROP_BOOL(GF_TRUE) : NULL); if (ctx->codecid==GF_CODECID_HEVC) { HEVC_SPS *sps = &ctx->hevc_state->spsctx->hevc_state->sps_active_idx; @@ -1917,6 +1971,9 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER, & PROP_UINT(sps->transfer_characteristic) ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, & PROP_UINT(sps->matrix_coeffs) ); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_RANGE, & PROP_BOOL(sps->video_full_range_flag) ); + if (ctx->hevc_state->sei.alternative_transfer_characteristics){ + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER_ALT, & PROP_UINT(ctx->hevc_state->sei.alternative_transfer_characteristics) ); + } has_colr_info = GF_TRUE; } } else if (ctx->codecid==GF_CODECID_VVC) { @@ -1944,7 +2001,6 @@ naludmx_update_clli_mdcv(ctx, GF_TRUE); naludmx_set_dolby_vision(ctx); - } static Bool naludmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) @@ -1959,6 +2015,8 @@ if (!ctx->is_playing) { ctx->is_playing = GF_TRUE; ctx->cts = ctx->dts = 0; + ctx->prev_cts = ctx->prev_dts = 0; + ctx->prev_sap = 0; } if (! ctx->is_file) { if (!ctx->initial_play_done) { @@ -2018,11 +2076,20 @@ return GF_TRUE; case GF_FEVT_STOP: - //don't cancel event + //reset parsing state ctx->is_playing = GF_FALSE; ctx->nal_store_size = 0; ctx->resume_from = 0; ctx->cts = 0; + + while (gf_list_count(ctx->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->pck_queue); + gf_filter_pck_discard(pck); + } + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + ctx->prev_sap = ctx->first_pck_in_au = NULL; + //don't cancel event return GF_FALSE; case GF_FEVT_SET_SPEED: @@ -2077,6 +2144,11 @@ list = ctx->pps; ctx->valid_ps_flags |= 1<<1; break; + case GF_HEVC_NALU_SEI_PREFIX: + if (!ctx->sei_prefix) + ctx->sei_prefix = gf_list_new(); + list = ctx->sei_prefix; + break; default: gf_assert(0); return; @@ -2170,7 +2242,7 @@ memcpy(sl->data, data, size); sl->size = size; sl->crc = crc; - ctx->ps_modified = GF_TRUE; + ctx->ps_modified |= 1; ctx->ps_changed = GF_TRUE; //flush AU if we have a slice if (ctx->opid && flush_au && ctx->first_pck_in_au && ctx->nb_slices_in_au) { @@ -2192,7 +2264,7 @@ sl->id = ps_id; sl->crc = crc; - ctx->ps_modified = GF_TRUE; + ctx->ps_modified |= 1; //flush AU if we have a slice if (ctx->opid && flush_au && ctx->first_pck_in_au && ctx->nb_slices_in_au) { naludmx_end_access_unit(ctx); @@ -2273,36 +2345,17 @@ ctx->has_ref_slices = GF_FALSE; ctx->has_redundant = GF_FALSE; - if ((ctx->hevc_state && ctx->hevc_state->clli_valid) - || (ctx->vvc_state && ctx->vvc_state->clli_valid) - ) { - u8 *clli = ctx->hevc_state ? ctx->hevc_state->clli_data : ctx->vvc_state->clli_data; - u32 crc = gf_crc_32(clli, 4); - if (!ctx->clli_crc) { - naludmx_update_clli_mdcv(ctx, GF_FALSE); - } - if (crc != ctx->clli_crc) { - gf_filter_pck_set_property(ctx->first_pck_in_au, GF_PROP_PID_CONTENT_LIGHT_LEVEL, &PROP_DATA(clli, 4)); - } + if (!ctx->clli_crc) { + naludmx_update_clli_mdcv(ctx, GF_FALSE); } - if ((ctx->hevc_state && ctx->hevc_state->mdcv_valid) - || (ctx->vvc_state && ctx->vvc_state->mdcv_valid) - ) { - u8 *mdcv = ctx->hevc_state ? ctx->hevc_state->mdcv_data : ctx->vvc_state->mdcv_data; - u32 crc = gf_crc_32(mdcv, 24); - if (!ctx->mdcv_crc) { - naludmx_update_clli_mdcv(ctx, GF_FALSE); - } - if (crc != ctx->mdcv_crc) { - gf_filter_pck_set_property(ctx->first_pck_in_au, GF_PROP_PID_MASTER_DISPLAY_COLOUR, &PROP_DATA(mdcv, 24)); - } + + if (!ctx->mdcv_crc) { + naludmx_update_clli_mdcv(ctx, GF_FALSE); } - if (ctx->hevc_state) - ctx->hevc_state->clli_valid = ctx->hevc_state->mdcv_valid = 0; - if (ctx->vvc_state) - ctx->vvc_state->clli_valid = ctx->vvc_state->mdcv_valid = 0; + //set all SEIs from state - this will reset the various valid flags in the SEI structure + gf_sei_load_from_state(ctx->sei_loader, ctx->first_pck_in_au); //if we reuse input packets timing, we can dispatch asap. //otherwise if poc probe is done (we know the min_poc_diff between images) and we are not in strict mode, dispatch asap @@ -2323,7 +2376,6 @@ } } - GF_FilterPacket *naludmx_start_nalu(GF_NALUDmxCtx *ctx, u32 nal_size, Bool skip_nal_field, Bool *au_start, u8 **pck_data) { GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->opid, nal_size + (skip_nal_field ? 0 : ctx->nal_length), pck_data); @@ -2366,6 +2418,35 @@ naludmx_update_time(ctx); *au_start = GF_FALSE; ctx->nb_frames++; + + if (ctx->refs) { + s32 POC = GF_INT_MAX; + u32 nb_refs=0; + s32 *refs = NULL; + if (ctx->hevc_state) { + if (!ctx->hevc_state->s_info.nb_lt_ref_pics) { + POC = ctx->hevc_state->s_info.poc; + nb_refs = ctx->hevc_state->s_info.nb_reference_pocs; + refs = ctx->hevc_state->s_info.reference_pocs; + } + } + if (ctx->vvc_state) { + if (!ctx->vvc_state->s_info.nb_lt_or_il_pics) { + POC = ctx->vvc_state->s_info.poc; + nb_refs = ctx->vvc_state->s_info.nb_reference_pocs; + refs = ctx->vvc_state->s_info.reference_pocs; + } + } + + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_ID, &PROP_SINT(POC)); + if (refs && nb_refs) { + GF_PropertyValue p; + p.type = GF_PROP_SINT_LIST; + p.value.sint_list.nb_items = nb_refs; + p.value.sint_list.vals = refs; + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_REFS, &p); + } + } } else { gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); } @@ -2406,7 +2487,7 @@ memcpy(ctx->sei_buffer + ctx->sei_buffer_size + ctx->nal_length, data, size); if (avc_sei_rewrite) { - u32 rw_sei_size = gf_avc_reformat_sei(ctx->sei_buffer + ctx->sei_buffer_size + ctx->nal_length, size, ctx->seirw, ctx->avc_state); + u32 rw_sei_size = gf_avc_reformat_sei(ctx->sei_buffer + ctx->sei_buffer_size + ctx->nal_length, size, ctx->seirw, ctx->avc_state, NULL); if (rw_sei_size < size) { gf_bs_seek(ctx->bs_w, 0); gf_bs_write_int(ctx->bs_w, rw_sei_size, 8*ctx->nal_length); @@ -2416,11 +2497,11 @@ ctx->sei_buffer_size += size + ctx->nal_length; } -static s32 naludmx_parse_nal_hevc(GF_NALUDmxCtx *ctx, char *data, u32 size, Bool *skip_nal, Bool *is_slice, Bool *is_islice) +static s32 naludmx_parse_nal_hevc(GF_NALUDmxCtx *ctx, char *data, u32 size, Bool *skip_nal, u32 *is_slice, Bool *is_islice) { s32 ps_idx = 0; s32 res; - u8 nal_unit_type, temporal_id, layer_id; + u8 nal_unit_type=0, temporal_id=0, layer_id=0; *skip_nal = GF_FALSE; if (size<2) return -1; @@ -2476,6 +2557,12 @@ break; case GF_HEVC_NALU_SEI_PREFIX: gf_hevc_parse_sei(data, size, ctx->hevc_state); + if (ctx->hevc_state->sei.has_3d_ref_disp_info) { + naludmx_queue_param_set(ctx, data, size, GF_HEVC_NALU_SEI_PREFIX, 0, temporal_id, layer_id); + } + if (ctx->hevc_state->sei.alternative_transfer_characteristics && ctx->opid) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER_ALT, & PROP_UINT(ctx->hevc_state->sei.alternative_transfer_characteristics) ); + } if (!ctx->nosei) { ctx->nb_sei++; naludmx_push_prefix(ctx, data, size, GF_FALSE); @@ -2512,7 +2599,10 @@ case GF_HEVC_NALU_SLICE_IDR_N_LP: case GF_HEVC_NALU_SLICE_CRA: if (! ctx->is_playing) return 0; - *is_slice = GF_TRUE; + *is_slice = 1; + if ((nal_unit_type==GF_HEVC_NALU_SLICE_IDR_W_DLP) || (nal_unit_type==GF_HEVC_NALU_SLICE_IDR_N_LP)) + *is_slice = 2; + ctx->last_layer_id = layer_id; ctx->last_temporal_id = temporal_id; if (! *skip_nal) { @@ -2543,8 +2633,10 @@ memcpy(ctx->init_aud, data, 3); } break; - /*remove*/ case GF_HEVC_NALU_FILLER_DATA: + *skip_nal = !ctx->keepfiller; + break; + /*remove*/ case GF_HEVC_NALU_END_OF_SEQ: case GF_HEVC_NALU_END_OF_STREAM: *skip_nal = GF_TRUE; @@ -2591,11 +2683,11 @@ } -static s32 naludmx_parse_nal_vvc(GF_NALUDmxCtx *ctx, char *data, u32 size, Bool *skip_nal, Bool *is_slice, Bool *is_islice) +static s32 naludmx_parse_nal_vvc(GF_NALUDmxCtx *ctx, char *data, u32 size, Bool *skip_nal, u32 *is_slice, Bool *is_islice) { s32 ps_idx = 0; s32 res; - u8 nal_unit_type, temporal_id, layer_id; + u8 nal_unit_type=0, temporal_id=0, layer_id=0; *skip_nal = GF_FALSE; if (size<2) return -1; @@ -2711,7 +2803,10 @@ case GF_VVC_NALU_SLICE_CRA: case GF_VVC_NALU_SLICE_GDR: if (! ctx->is_playing) return 0; - *is_slice = GF_TRUE; + *is_slice = 1; + if ((nal_unit_type==GF_VVC_NALU_SLICE_IDR_W_RADL) || (nal_unit_type==GF_VVC_NALU_SLICE_IDR_N_LP)) + *is_slice = 2; + ctx->last_layer_id = layer_id; ctx->last_temporal_id = temporal_id; if (! *skip_nal) { @@ -2745,8 +2840,10 @@ memcpy(ctx->init_aud, data, 3); } break; - /*remove*/ case GF_VVC_NALU_FILLER_DATA: + *skip_nal = !ctx->keepfiller; + break; + /*remove*/ case GF_VVC_NALU_END_OF_SEQ: case GF_VVC_NALU_END_OF_STREAM: *skip_nal = GF_TRUE; @@ -2772,7 +2869,7 @@ return res; } -static s32 naludmx_parse_nal_avc(GF_NALUDmxCtx *ctx, char *data, u32 size, u32 nal_type, Bool *skip_nal, Bool *is_slice, Bool *is_islice) +static s32 naludmx_parse_nal_avc(GF_NALUDmxCtx *ctx, char *data, u32 size, u32 nal_type, Bool *skip_nal, u32 *is_slice, Bool *is_islice) { s32 ps_idx = 0; s32 res = 0; @@ -2846,8 +2943,10 @@ memcpy(ctx->init_aud, data, 2); } return 1; - /*remove*/ case GF_AVC_NALU_FILLER_DATA: + *skip_nal = !ctx->keepfiller; + break; + /*remove*/ case GF_AVC_NALU_END_OF_SEQ: case GF_AVC_NALU_END_OF_STREAM: *skip_nal = GF_TRUE; @@ -2859,7 +2958,10 @@ case GF_AVC_NALU_DP_B_SLICE: case GF_AVC_NALU_DP_C_SLICE: case GF_AVC_NALU_IDR_SLICE: - *is_slice = GF_TRUE; + *is_slice = 1; + if (nal_type==GF_AVC_NALU_IDR_SLICE) + *is_slice = 2; + switch (ctx->avc_state->s_info.slice_type) { case GF_AVC_TYPE_P: case GF_AVC_TYPE2_P: @@ -2896,36 +2998,36 @@ i--; if (!ctx->pps_svc) ctx->pps_svc = gf_list_new(); gf_list_add(ctx->pps_svc, slc); - ctx->ps_modified = GF_TRUE; + ctx->ps_modified |= 1; ctx->ps_changed = GF_TRUE; } } } - *is_slice = GF_TRUE; + *is_slice = 1; //we disable temporal scalability when parsing mvc - never used and many encoders screw up POC in enhancement if (ctx->is_mvc && (res>=0)) { res=0; ctx->avc_state->s_info.poc = ctx->last_poc; } - if (ctx->avc_state->s_info.sps) { - switch (ctx->avc_state->s_info.slice_type) { - case GF_AVC_TYPE_P: - case GF_AVC_TYPE2_P: - ctx->avc_state->s_info.sps->nb_ep++; - break; - case GF_AVC_TYPE_I: - case GF_AVC_TYPE2_I: - ctx->avc_state->s_info.sps->nb_ei++; - break; - case GF_AVC_TYPE_B: - case GF_AVC_TYPE2_B: - ctx->avc_state->s_info.sps->nb_eb++; - break; - } - } - break; + if (ctx->avc_state->s_info.sps) { + switch (ctx->avc_state->s_info.slice_type) { + case GF_AVC_TYPE_P: + case GF_AVC_TYPE2_P: + ctx->avc_state->s_info.sps->nb_ep++; + break; + case GF_AVC_TYPE_I: + case GF_AVC_TYPE2_I: + ctx->avc_state->s_info.sps->nb_ei++; + break; + case GF_AVC_TYPE_B: + case GF_AVC_TYPE2_B: + ctx->avc_state->s_info.sps->nb_eb++; + break; + } + } + break; case GF_AVC_NALU_SLICE_AUX: - *is_slice = GF_TRUE; + *is_slice = 1; break; case GF_AVC_NALU_DV_RPU: @@ -3171,7 +3273,7 @@ gf_bs_reassign_buffer(ctx->bs_r, start, remain); } - gf_assert(remain>=0); + gf_assert(remain>=0); while (remain) { u8 *pck_data; @@ -3186,7 +3288,7 @@ u32 next_sc_size=0; s32 nal_parse_result; Bool slice_is_idr, slice_force_ref; - Bool is_slice = GF_FALSE; + u32 is_slice = 0; Bool is_islice = GF_FALSE; u32 field_type = 0; Bool au_start; @@ -3377,7 +3479,7 @@ if (!ctx->opid) { //check output pid cfg before checking NAL skip only if no output pid - naludmx_check_pid(filter, ctx, force_au_flush); + naludmx_check_pid(filter, ctx, force_au_flush, GF_FALSE); if (!ctx->opid) skip_nal = GF_TRUE; } @@ -3389,12 +3491,12 @@ naldmx_check_timestamp_switch(ctx, &nalu_store_before, nal_size, &drop_packet, pck); continue; } - //check output pid cfg after skiping nal, to make sure we can flush pending packets when config change - naludmx_check_pid(filter, ctx, force_au_flush); + //check output pid cfg after skipping nal, to make sure we can flush pending packets when config changes + naludmx_check_pid(filter, ctx, force_au_flush, (is_slice==2) ? GF_TRUE : GF_FALSE); if (!ctx->is_playing) { ctx->resume_from = (u32) (start - ctx->nal_store); - gf_assert(ctx->resume_from<=ctx->nal_store_size); + gf_assert(ctx->resume_from<=ctx->nal_store_size); GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("%s not yet playing\n", ctx->log_name)); if (drop_packet) @@ -3574,10 +3676,10 @@ avc_svc_subs_priority = (63 - (p1 & 0x3F)) << 2; } if (nal_type==GF_AVC_NALU_SVC_PREFIX_NALU) { - if (ctx->svc_prefix_buffer_size) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("%s broken bitstream, two consecutive SVC prefix NALU without SVC slice in-between\n", ctx->log_name)); - ctx->svc_prefix_buffer_size = 0; - } + if (ctx->svc_prefix_buffer_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("%s broken bitstream, two consecutive SVC prefix NALU without SVC slice in-between\n", ctx->log_name)); + ctx->svc_prefix_buffer_size = 0; + } /* remember reserved and priority value */ ctx->svc_nalu_prefix_reserved = avc_svc_subs_reserved; @@ -3721,13 +3823,14 @@ s64 pdiff = ctx->last_poc; pdiff -= slice_poc; if (pdiff<0) pdiff=-pdiff; - if (pdiff>GF_INT_MAX) { + if (pdiff>GF_INT_MAX || slice_poc <= GF_INT_MIN) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("%s POC diff overflow %d vs last %d, reseting poc counter - timing will likely be corrupted\n", ctx->log_name, slice_poc, ctx->last_poc)); pdiff = 0; slice_poc = ctx->last_poc; ctx->poc_shift = slice_poc; } + if ((slice_poc < 0) && !ctx->last_poc) ctx->poc_diff = 0; else if ((slice_poc < 0) && (-slice_poc < ctx->poc_diff)) { @@ -3765,13 +3868,20 @@ if (slice_is_idr) { if (first_in_au) { Bool temp_poc_diff = GF_FALSE; - //two consecutive IDRs, force poc_diff to 1 if 0 (when we have intra-only) to force frame dispatch - if (ctx->last_frame_is_idr && !ctx->poc_diff) { - temp_poc_diff = GF_TRUE; - ctx->poc_diff = 1; + Bool temp_min_poc_probe_done = ctx->min_poc_probe_done; + if (ctx->last_frame_is_idr) { + //two consecutive IDRs: force frame dispatch + ctx->min_poc_probe_done = GF_TRUE; + + if (!ctx->poc_diff) { + //force poc_diff to 1 if 0 (when we have intra-only) to force frame dispatch + temp_poc_diff = GF_TRUE; + ctx->poc_diff = 1; + } } //new ref frame, dispatch all pending packets naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + ctx->min_poc_probe_done = temp_min_poc_probe_done; //if IDR with DLP (sap2), only reset poc probing if the poc is below current max poc //otherwise assume no diff in poc @@ -3978,6 +4088,7 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("%s DV profile forced but compatID in auto mode, using no compatibility\n", ctx->log_name)); } } + ctx->sei_loader = gf_sei_loader_new(); return GF_OK; } @@ -4056,6 +4167,7 @@ naludmx_del_param_list(ctx->vvc_aps_pre, do_free); naludmx_del_param_list(ctx->vvc_dci, do_free); naludmx_del_param_list(ctx->vvc_opi, do_free); + naludmx_del_param_list(ctx->sei_prefix, do_free); } static void naludmx_finalize(GF_Filter *filter) @@ -4087,6 +4199,7 @@ if (ctx->avc_state) gf_free(ctx->avc_state); if (ctx->hevc_state) gf_free(ctx->hevc_state); if (ctx->vvc_state) gf_free(ctx->vvc_state); + gf_sei_loader_del(ctx->sei_loader); } @@ -4268,6 +4381,7 @@ { OFFS(fps), "import frame rate (0 default to FPS from bitstream or 25 Hz)", GF_PROP_FRACTION, "0/1000", NULL, 0}, { OFFS(index), "indexing window length. If 0, bitstream is not probed for duration. A negative value skips the indexing if the source file is larger than 20M (slows down importers) unless a play with start range > 0 is issued", GF_PROP_DOUBLE, "-1.0", NULL, 0}, { OFFS(explicit), "use explicit layered (SVC/LHVC) import", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(force_sync), "force sync points on non-IDR samples with I slices (not compliant)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(strict_poc), "delay frame output of an entire GOP to ensure CTS info is correct when POC suddenly changes\n" "- off: disable GOP buffering\n" "- on: enable GOP buffering, assuming no error in POC\n" @@ -4280,8 +4394,10 @@ { OFFS(nal_length), "set number of bytes used to code length field: 1, 2 or 4", GF_PROP_UINT, "4", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(subsamples), "import subsamples information", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(deps), "import sample dependency information", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(refs), "import sample reference picture list (currently only for HEVC and VVC)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(seirw), "rewrite AVC sei messages for ISOBMFF constraints", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(audelim), "keep Access Unit delimiter in payload", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(keepfiller), "keep filler NAL units in output", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(analyze), "skip reformat of decoder config and SEI and dispatch all NAL in input order - shall only be used with inspect filter analyze mode!", GF_PROP_UINT, "off", "off|on|bs|full", GF_FS_ARG_HINT_HIDE}, { OFFS(notime), "ignore input timestamps, rebuild from 0", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, @@ -4301,7 +4417,7 @@ "- hlg2100: HLG BT.2100 gamut in ITU-R BT.2020\n" "- bt2020: SDR BT.2020\n" "- brd: Ultra HD Blu-ray Disc HDR", GF_PROP_UINT, "auto", "auto|none|hdr10|bt709|hlg709|hlg2100|bt2020|brd", GF_FS_ARG_HINT_ADVANCED}, - { OFFS(bsdbg), "debug NAL parsing in `parser@debug` logs\n" + { OFFS(bsdbg), "debug NAL parsing in `media@debug` logs\n" "- off: not enabled\n" "- on: enabled\n" "- full: enable with number of bits dumped", GF_PROP_UINT, "off", "off|on|full", GF_FS_ARG_HINT_EXPERT}, @@ -4324,6 +4440,7 @@ .process = naludmx_process, .process_event = naludmx_process_event, .probe_data = naludmx_probe_data, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_prores.c -> gpac-26.02.0.tar.gz/src/filters/reframe_prores.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / ProRes reframer filter @@ -151,7 +151,7 @@ stream = gf_fopen_ex(filepath, NULL, "rb", GF_TRUE); if (!stream) { - if (gf_fileio_is_main_thread(p->value.string)) + if (p && gf_fileio_is_main_thread(p->value.string)) ctx->file_loaded = GF_TRUE; return; } @@ -431,7 +431,7 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, & PROP_UINT(finfo->matrix_coefficients) ); //set interlaced or remove interlaced property - gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, ctx->cur_cfg.interlaced_mode ? & PROP_UINT(GF_TRUE) : NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, ctx->cur_cfg.interlaced_mode ? & PROP_BOOL(GF_TRUE) : NULL); } @@ -446,7 +446,7 @@ while (gf_bs_available(ctx->bs)) { u8 *output; - GF_FilterPacket *pck; + GF_FilterPacket *pck=NULL; GF_ProResFrameInfo finfo; e = gf_media_prores_parse_bs(ctx->bs, &finfo); @@ -461,7 +461,8 @@ if (gf_bs_available(ctx->bs)<finfo.frame_size) break; - pck = gf_filter_pck_new_alloc(ctx->opid, finfo.frame_size, &output); + if (ctx->opid) + pck = gf_filter_pck_new_alloc(ctx->opid, finfo.frame_size, &output); if (!pck) break; gf_bs_read_data(ctx->bs, output, finfo.frame_size); @@ -689,7 +690,8 @@ .configure_pid = proresdmx_configure_pid, .process = proresdmx_process, .probe_data = proresdmx_probe_data, - .process_event = proresdmx_process_event + .process_event = proresdmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -703,4 +705,3 @@ return NULL; } #endif // GPAC_DISABLE_RFPRORES -
View file
gpac-2.4.0.tar.gz/src/filters/reframe_qcp.c -> gpac-26.02.0.tar.gz/src/filters/reframe_qcp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AMR&EVRC&SMV reframer filter @@ -192,7 +192,7 @@ gf_fseek(stream, size, SEEK_CUR); } data_chunk_size-= size; - + duration += ctx->block_size; cur_dur += ctx->block_size; if (cur_dur > ctx->index * ctx->sample_rate) { @@ -680,19 +680,26 @@ static const char *qcpdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) { + if (size < 4) + return NULL; + char magic5; Bool is_qcp = GF_TRUE; GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); magic4 = 0; - gf_bs_read_data(bs, magic, 4); - if (strnicmp(magic, "RIFF", 4)) { + if (gf_bs_read_data(bs, magic, 4) != 4) { is_qcp = GF_FALSE; - } else { - /*riff_size = */gf_bs_read_u32_le(bs); - gf_bs_read_data(bs, magic, 4); - if (strnicmp(magic, "QLCM", 4)) { + } + else { + if (strnicmp(magic, "RIFF", 4)) { is_qcp = GF_FALSE; + } else { + /*riff_size = */gf_bs_read_u32_le(bs); + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "QLCM", 4)) { + is_qcp = GF_FALSE; + } } } gf_bs_del(bs); @@ -734,7 +741,8 @@ .configure_pid = qcpdmx_configure_pid, .process = qcpdmx_process, .probe_data = qcpdmx_probe_data, - .process_event = qcpdmx_process_event + .process_event = qcpdmx_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -748,4 +756,3 @@ return NULL; } #endif //#ifndef GPAC_DISABLE_RFQCP -
View file
gpac-2.4.0.tar.gz/src/filters/reframe_rawpcm.c -> gpac-26.02.0.tar.gz/src/filters/reframe_rawpcm.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / RAW PCM reframer filter @@ -369,6 +369,10 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("PCMReframe Samplerate %d invalid in wave\n", ctx->sr)); wav_ok = GF_FALSE; } + if (!ctx->safmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("PCMReframe Audio format unrecognized in wave\n")); + wav_ok = GF_FALSE; + } ctx->wav_hdr_size = (u32) gf_bs_get_position(bs); @@ -528,7 +532,8 @@ .configure_pid = pcmreframe_configure_pid, .process = pcmreframe_process, .process_event = pcmreframe_process_event, - .probe_data = pcmreframe_probe_data + .probe_data = pcmreframe_probe_data, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/reframe_rawvid.c -> gpac-26.02.0.tar.gz/src/filters/reframe_rawvid.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / RAW video (YUV,RGB) reframer filter @@ -297,7 +297,7 @@ else if (data0 == 'H') ctx->size.y = atoi(data+1); else if (data0 == 'F') sscanf(data+1, "%d:%d", &ctx->fps.num, &ctx->fps.den); else if (data0 == 'A') { - GF_Fraction sar; + GF_Fraction sar = {0,1}; sscanf(data+1, "%d:%d", &sar.num, &sar.den); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(sar)); } @@ -401,7 +401,7 @@ data += remain; byte_offset += remain; offset_in_pck += remain; - + ctx->out_pck = NULL; ctx->nb_bytes_in_frame = 0; @@ -489,7 +489,8 @@ .configure_pid = rawvidreframe_configure_pid, .probe_data = rawvidreframe_probe_data, .process = rawvidreframe_process, - .process_event = rawvidreframe_process_event + .process_event = rawvidreframe_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -505,5 +506,3 @@ return NULL; } #endif //#ifndef GPAC_DISABLE_RFRAWVID - -
View file
gpac-2.4.0.tar.gz/src/filters/reframe_truehd.c -> gpac-26.02.0.tar.gz/src/filters/reframe_truehd.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2023 + * Copyright (c) Telecom ParisTech 2023-2024 * All rights reserved * * This file is part of GPAC / TrueHD reframer filter @@ -687,7 +687,7 @@ } ctx->is_sync = GF_TRUE; - if (!ctx->in_seek) { + if (!ctx->in_seek && remain >= hdr.frame_size) { dst_pck = gf_filter_pck_new_alloc(ctx->opid, hdr.frame_size, &output); if (!dst_pck) return GF_OUT_OF_MEM; gf_filter_pck_merge_properties(ctx->src_pck ? ctx->src_pck : ctx->src_current, dst_pck); @@ -743,6 +743,10 @@ if (remain && (remain<ctx->truehd_buffer_size)) { memmove(ctx->truehd_buffer, start, remain); } + if (!ctx->src_pck) { + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } ctx->truehd_buffer_size = remain; gf_filter_pid_drop_packet(ctx->ipid); } @@ -755,6 +759,7 @@ if (ctx->bs) gf_bs_del(ctx->bs); if (ctx->truehd_buffer) gf_free(ctx->truehd_buffer); if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); } static const char *truehd_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) @@ -814,7 +819,8 @@ .configure_pid = truehd_configure_pid, .process = truehd_process, .probe_data = truehd_probe_data, - .process_event = truehd_process_event + .process_event = truehd_process_event, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -828,4 +834,3 @@ return NULL; } #endif //#ifndef GPAC_DISABLE_RFTRUEHD -
View file
gpac-2.4.0.tar.gz/src/filters/reframer.c -> gpac-26.02.0.tar.gz/src/filters/reframer.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / force reframer filter @@ -29,20 +29,25 @@ #ifndef GPAC_DISABLE_REFRAMER -enum -{ +GF_OPT_ENUM (GF_RealTimeRegulationMode, REFRAME_RT_OFF = 0, REFRAME_RT_ON, REFRAME_RT_SYNC, -}; + REFRAME_RT_ALIGN, +); -enum -{ +GF_OPT_ENUM (GF_ExtractionStartAdjustment, REFRAME_ROUND_BEFORE=0, REFRAME_ROUND_SEEK, REFRAME_ROUND_AFTER, REFRAME_ROUND_CLOSEST, -}; +); + +GF_OPT_ENUM (GF_WaitSegmentState, + WAIT_SEG_BOUNDARY_NONE=0, + WAIT_SEG_BOUNDARY_ACTIVE, + WAIT_SEG_BOUNDARY_DONE, +); enum { @@ -52,13 +57,12 @@ RANGE_DONE }; -enum -{ +GF_OPT_ENUM (GF_UTCReferenceMode, UTCREF_LOCAL=0, UTCREF_ANY, UTCREF_MEDIA, -}; - + UTCREF_TC, +); enum { @@ -69,13 +73,12 @@ EXTRACT_DUR, }; -enum -{ +GF_OPT_ENUM (GF_ForceInputDecodingMode, RAW_AV=0, RAW_AUDIO, RAW_VIDEO, RAW_NONE, -}; +); #define RT_PRECISION_US 2000 @@ -104,6 +107,9 @@ u64 sap_ts_plus_one; Bool first_pck_sent; + GF_Fraction segdur; + GF_WaitSegmentState wait_seg_boundary; + //only positive delay here u64 tk_delay; //only media skip (video, audio priming) @@ -132,7 +138,6 @@ Bool fetch_done; u64 last_utc_ref, last_utc_ref_ts; - } RTStream; typedef struct @@ -142,16 +147,19 @@ GF_PropUIntList saps; GF_PropIntList frames; Bool refs; - u32 rt; + GF_RealTimeRegulationMode rt; Double speed; - u32 raw; + GF_ForceInputDecodingMode raw; GF_PropStringList xs, xe; - Bool nosap, splitrange, xadjust, tcmdrw, no_audio_seek, probe_ref, xots; - u32 xround, utc_ref, utc_probe; + Bool nosap, splitrange, xadjust, tcmdrw, no_audio_seek, probe_ref, xots, xdts, nodisc; + GF_ExtractionStartAdjustment xround; + GF_UTCReferenceMode utc_ref; + u32 utc_probe; Double seeksafe; GF_PropStringList props; Bool copy, rmseek; u32 cues; + u32 sapcue; //internal Bool filter_sap1; @@ -169,12 +177,20 @@ u32 range_type; u32 cur_range_idx; - //if cur_start.den is 0, cur_start.num is UTC start time - //if cur_end.den is 0, cur_start.num is UTC stop time, only if cur_start uses UTC + //if cur_start.den is 0, cur_start.num is UTC start time in milliseconds + //if cur_end.den. is 0, cur_start.num is UTC stop time, only if cur_start uses UTC GF_Fraction64 cur_start, cur_end; + GF_TimeCode *cur_start_tc, *cur_end_tc; + Bool cur_start_valid, cur_end_valid; u64 start_frame_idx_plus_one, end_frame_idx_plus_one; + GF_Fraction64 ts_tc_offset; + + //when we are waiting for segment boundary, we want other PIDs to + //short circuit and start sending when one of them can + GF_Fraction64 wait_seg_boundary_ts; Bool in_range; + Bool load_sei; Bool seekable; @@ -207,6 +223,9 @@ u64 last_utc_time_s; u32 last_clock_probe; + + u32 nb_align_pending; + u64 next_cts_align; } GF_ReframerCtx; static void reframer_reset_stream(GF_ReframerCtx *ctx, RTStream *st, Bool do_free) @@ -223,6 +242,9 @@ st->split_pck = NULL; if (st->reinsert_single_pck) gf_filter_pck_unref(st->reinsert_single_pck); st->reinsert_single_pck = NULL; + if (ctx->clock == st) + ctx->clock = NULL; + if (do_free) gf_free(st); } @@ -240,7 +262,7 @@ //seek mode, signal we have sample-accurate seek info for the pid if (st->seek_mode) - gf_filter_pid_set_property(st->opid, GF_PROP_PCK_SKIP_BEGIN, &PROP_UINT(1)); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_HAS_SKIP_BEGIN, &PROP_BOOL(GF_TRUE)); //for old arch compat, signal we must remove edits if (gf_sys_old_arch_compat()) { @@ -257,20 +279,24 @@ if (is_remove) { if (st) { - if (st->opid) + if (st->opid) { gf_filter_pid_remove(st->opid); + gf_filter_pid_set_udta(st->opid, NULL); + } gf_list_del_item(ctx->streams, st); + if (st->ipid) + gf_filter_pid_set_udta(st->ipid, NULL); reframer_reset_stream(ctx, st, GF_TRUE); } return GF_OK; } - if (! gf_filter_pid_check_caps(pid)) + if (!gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; if (!st) { GF_SAFEALLOC(st, RTStream); if (!st) return GF_OUT_OF_MEM; - + gf_list_add(ctx->streams, st); st->opid = gf_filter_pid_new(filter); gf_filter_pid_set_udta(pid, st); @@ -278,6 +304,8 @@ st->ipid = pid; st->pck_queue = gf_list_new(); st->all_saps = GF_TRUE; + } else if (ctx->nodisc) { + return GF_OK; } p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); @@ -341,6 +369,22 @@ if (!p || (p->value.uint < GF_PLAYBACK_MODE_FASTFORWARD)) ctx->seekable = GF_FALSE; + //trigger SEI loading if needed + if (ctx->load_sei) { + switch (st->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + case GF_CODECID_VVC: + case GF_CODECID_AV1: + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SEI_LOADED); + if (!p) + gf_filter_pid_negotiate_property(pid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); + } + } + ctx->filter_sap1 = ctx->filter_sap2 = ctx->filter_sap3 = ctx->filter_sap4 = ctx->filter_sap_none = GF_FALSE; for (i=0; i<ctx->saps.nb_items; i++) { @@ -387,9 +431,10 @@ return GF_OK; } -static Bool reframer_parse_date(char *date, GF_Fraction64 *value, u64 *frame_idx_plus_one, u32 *extract_mode, Bool *is_dur) +static Bool reframer_parse_date(GF_ReframerCtx *ctx, char *date, GF_Fraction64 *value, u64 *frame_idx_plus_one, u32 *extract_mode, Bool *is_dur, GF_TimeCode **as_timecode) { u64 v; + *as_timecode = NULL; value->num =0; value->den = 0; @@ -397,7 +442,33 @@ *extract_mode = EXTRACT_RANGE; if (is_dur) *is_dur = GF_FALSE; + if (*as_timecode) + gf_free(*as_timecode); + + if (strlen(date)>2 && date0=='T' && date1=='C') { + u32 h=0, m=0, s=0, n_frames=0; + if (sscanf(date, "TC%u:%u:%u:%u", &h, &m, &s, &n_frames) != 4) { + goto exit; + } + //we use timecodes, load SEI + ctx->load_sei = GF_TRUE; + + // Express timecode as timestamp + // cur_start/end will be overwritten later. This ensures checks after this parser can work + v = h*3600 + m*60 + s; + v *= 1000; + v += n_frames; + value->num = v; + value->den = 1000; + // Encode timecode as 4 bytes + GF_SAFEALLOC(*as_timecode, GF_TimeCode); + (*as_timecode)->hours = h; + (*as_timecode)->minutes = m; + (*as_timecode)->seconds = s; + (*as_timecode)->n_frames = n_frames; + return GF_TRUE; + } if (date0 == 'T') { u32 h=0, m=0, s=0, ms=0; if (strchr(date, '.')) { @@ -479,7 +550,7 @@ } exit: - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer Unrecognized date format %s, expecting THH:MM:SS.ms, TMM:SS.ms, TSS.ms, INT or FRAC\n", date)); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer Unrecognized date format %s, expecting TCHH:MM:SS:FFF, THH:MM:SS.ms, TMM:SS.ms, TSS.ms, INT or FRAC\n", date)); if (extract_mode) *extract_mode = EXTRACT_NONE; return GF_FALSE; @@ -494,6 +565,8 @@ GF_Fraction64 prev_end; char *start_date=NULL, *end_date=NULL; + ctx->cur_start_valid = GF_TRUE; + ctx->cur_end_valid = GF_TRUE; ctx->nb_video_frames_since_start_at_range_start = ctx->nb_video_frames_since_start; if (ctx->extract_mode==EXTRACT_DUR) { @@ -504,6 +577,7 @@ } if ((ctx->extract_mode==EXTRACT_SAP) || (ctx->extract_mode==EXTRACT_SIZE)) { ctx->cur_start = ctx->cur_end; + if (!ctx->cur_start.den) ctx->cur_start.den = 1; ctx->min_ts_computed = 0; ctx->min_ts_scale = 0; ctx->file_idx++; @@ -541,13 +615,17 @@ if (!end_date) ctx->range_type = RANGE_OPEN; else ctx->range_type = RANGE_CLOSED; - if (!reframer_parse_date(start_date, &ctx->cur_start, &ctx->start_frame_idx_plus_one, &ctx->extract_mode, NULL)) { + if (!reframer_parse_date(ctx, start_date, &ctx->cur_start, &ctx->start_frame_idx_plus_one, &ctx->extract_mode, NULL, &ctx->cur_start_tc)) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer cannot parse start date, assuming end of ranges\n")); //done ctx->range_type = RANGE_DONE; return; } + //start will be adjusted + if (ctx->cur_start_tc || ctx->utc_ref==UTCREF_TC) + ctx->cur_start_valid = GF_FALSE; + //range in frame if (ctx->start_frame_idx_plus_one) { //either range is before or prev range was not frame-based @@ -608,10 +686,13 @@ if (end_date) { Bool is_dur = GF_FALSE; ctx->end_frame_idx_plus_one = 0; - if (!reframer_parse_date(end_date, &ctx->cur_end, &ctx->end_frame_idx_plus_one, NULL, &is_dur)) { + if (!reframer_parse_date(ctx, end_date, &ctx->cur_end, &ctx->end_frame_idx_plus_one, NULL, &is_dur, &ctx->cur_end_tc)) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer cannot parse end date, assuming open range\n")); ctx->range_type = RANGE_OPEN; } else { + //end will be adjusted + if (ctx->cur_end_tc) + ctx->cur_end_valid = GF_FALSE; if (is_dur) { ctx->cur_end.num = gf_timestamp_rescale(ctx->cur_end.num, ctx->cur_end.den, ctx->cur_start.den); ctx->cur_end.den = ctx->cur_start.den; @@ -690,7 +771,6 @@ gf_filter_pid_send_event(st->ipid, &evt); gf_filter_pid_set_eos(st->opid); } - } void reframer_drop_packet(GF_ReframerCtx *ctx, RTStream *st, GF_FilterPacket *pck, Bool pck_is_ref) @@ -731,6 +811,51 @@ if (cts_us==GF_FILTER_NO_TS) { do_send = GF_TRUE; + } else if (ctx->rt==REFRAME_RT_ALIGN) { + Bool do_align = GF_FALSE; + RTStream *st_clock = st; + cts_us += st->tk_delay; + cts_us = gf_timestamp_rescale(cts_us, st->timescale, 1000000); + + if (!ctx->clock) ctx->clock = st; + st_clock = ctx->clock; + + //very first packet sent + if (!st_clock->sys_clock_at_init) { + st_clock->sys_clock_at_init = cts_us+1; + do_align = GF_TRUE; + } + //no more packets and the stream is the next to be scheduled + else if (!ctx->nb_align_pending && ((st->cts_us_at_init == ctx->next_cts_align) || !ctx->next_cts_align) ) { + st_clock->sys_clock_at_init = st->cts_us_at_init; + do_align = GF_TRUE; + } else if (cts_us <= st_clock->sys_clock_at_init - 1) { + do_send = GF_TRUE; + } + //ahead of clock, signal first time we see this + else if (ctx->nb_align_pending && st->cts_us_at_init != cts_us+1) { + //remember last cts + st->cts_us_at_init = cts_us+1; + ctx->nb_align_pending--; + //select as clock for next regulation period if less than next align time + if (!ctx->next_cts_align || (cts_us < ctx->next_cts_align-1)) + ctx->next_cts_align = cts_us+1; + } + + if (do_align) { + ctx->next_cts_align = 0; + ctx->nb_align_pending = 0; + u32 i; + for (i=0; i<gf_list_count(ctx->streams); i++) { + RTStream *ast = gf_list_get(ctx->streams, i); + //reset last cts, and increment nb_align_pending for all playing streams + ast->cts_us_at_init = 0; + if (ast->in_eos) continue; + if (!ast->is_playing) continue; + ctx->nb_align_pending++; + } + do_send = GF_TRUE; + } } else { RTStream *st_clock = st; u64 clock = ctx->clock_val; @@ -998,6 +1123,7 @@ ts += st->tk_delay; ts += st->ts_at_range_end; ts -= st->ts_at_range_start_plus_one - 1; + ts += gf_timestamp_rescale(ctx->ts_tc_offset.num, ctx->ts_tc_offset.den, st->timescale); if (ts<0) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer Negative TS while splitting, something went wrong during range estimation, forcing to 0\n")); @@ -1020,6 +1146,7 @@ ts += st->tk_delay; ts -= st->ts_at_range_start_plus_one - 1; ts += st->ts_at_range_end; + ts += gf_timestamp_rescale(ctx->ts_tc_offset.num, ctx->ts_tc_offset.den, st->timescale); gf_filter_pck_set_dts(new_pck, (u64) ts); } } @@ -1058,18 +1185,65 @@ } if (ctx->rmseek) gf_filter_pck_set_seek_flag(new_pck, GF_FALSE); - gf_filter_pck_send(new_pck); + + // forward SAPs as cue points + u32 sap = gf_filter_pck_get_sap(new_pck); + if (sap > 0 && sap <= ctx->sapcue) + gf_filter_pck_set_property(new_pck, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE)); + + //if all saps, using the the final timestamp, decide if we should send the packet + if (st->all_saps && !ctx->nosap && st->stream_type==GF_STREAM_VISUAL + && st->wait_seg_boundary==WAIT_SEG_BOUNDARY_ACTIVE && st->segdur.den>0) { + GF_Fraction fps = {0}; + const GF_PropertyValue *p = gf_filter_pid_get_property(st->ipid, GF_PROP_PID_FPS); + u64 cts = gf_filter_pck_get_cts(new_pck); + if (p) fps = p->value.frac; + if (fps.den==0 || fps.num==0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Reframer Invalid FPS %d/%u when looking for UTCREF_TC segment boundary\n", fps.num, fps.den)); + } + else if (cts != GF_FILTER_NO_TS) { + u32 seg_nframes = st->segdur.num * fps.num / (st->segdur.den * fps.den); + assert(ctx->ts_tc_offset.den == st->timescale); + u32 diff_nframes = (cts - ctx->ts_tc_offset.num - ctx->cur_start.num) * fps.num / (st->timescale * fps.den); + Bool is_seg_boundary = diff_nframes % seg_nframes == 0; + + //fallback, check if other tracks are currently sending + if (!is_seg_boundary && ctx->wait_seg_boundary_ts.den>0) { + is_seg_boundary = gf_timestamp_greater_or_equal(ts, st->timescale, ctx->wait_seg_boundary_ts.num, ctx->wait_seg_boundary_ts.den); + } + + if (is_seg_boundary) { + // first packet to be sent adjusts the start time + if (ctx->wait_seg_boundary_ts.den==0 || gf_timestamp_less(cts, st->timescale, ctx->wait_seg_boundary_ts.num, ctx->wait_seg_boundary_ts.den)) { + ctx->wait_seg_boundary_ts.num = cts; + ctx->wait_seg_boundary_ts.den = st->timescale; + } + st->wait_seg_boundary = WAIT_SEG_BOUNDARY_DONE; + } else { + // this frame wouldn't be considered as sap because it's within the segment duration + gf_filter_pck_discard(new_pck); + new_pck = NULL; + } + } + } + + if (new_pck) gf_filter_pck_send(new_pck); } else { GF_FilterPacket *dst = ctx->copy ? gf_filter_pck_new_copy(st->opid, pck, NULL) : gf_filter_pck_new_ref(st->opid, 0, 0, pck); if (dst) { gf_filter_pck_merge_properties(pck, dst); if (ctx->rmseek) gf_filter_pck_set_seek_flag(dst, GF_FALSE); + + // forward SAPs as cue points + u32 sap = gf_filter_pck_get_sap(dst); + if (sap > 0 && sap <= ctx->sapcue) + gf_filter_pck_set_property(dst, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE)); + gf_filter_pck_send(dst); } } - reframer_drop_packet(ctx, st, pck, pck_is_ref); st->nb_frames++; @@ -1640,7 +1814,6 @@ } - GF_Err reframer_process(GF_Filter *filter) { GF_ReframerCtx *ctx = gf_filter_get_udta(filter); @@ -1695,6 +1868,95 @@ GF_FilterPid *ipid = gf_filter_get_ipid(filter, i); RTStream *st = gf_filter_pid_get_udta(ipid); st->fetch_done = GF_FALSE; + if (ctx->cur_start_valid && ctx->cur_end_valid) + continue; + + //try to get the timecode + GF_FilterPacket *pck = gf_filter_pid_get_packet(ipid); + if (!pck) + return GF_OK; + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_TIMECODE); + if (!p || !p->value.data.ptr || !p->value.data.size) + continue; + GF_TimeCode *pck_tc = (GF_TimeCode*) p->value.data.ptr; + u64 pck_cts = gf_filter_pck_get_cts(pck); + u32 pck_ts = gf_filter_pck_get_timescale(pck); + + //get the pid fps + GF_Fraction fps = {0}; + p = gf_filter_pid_get_property(ipid, GF_PROP_PID_FPS); + if (p) fps = p->value.frac; + if (!fps.num || !fps.den) { + fps.num = 25; + fps.den = 1; + } + + //calculate the correct cts for packet + u64 ts = pck_cts + st->tk_delay; + if (ts > st->ts_sub) ts -= st->ts_sub; + else ts = 0; + + //process both start and end timestamps/timecodes + GF_TimeCode *tc_list2 = {ctx->cur_start_tc, ctx->cur_end_tc}; + GF_Fraction64 *ts_list2 = {&ctx->cur_start, &ctx->cur_end}; + Bool *valid_list2 = {&ctx->cur_start_valid, &ctx->cur_end_valid}; + + for (int tc_idx = 0; tc_idx < 2; tc_idx++) { + Bool *valid = valid_listtc_idx; + if (*valid) + continue; + + GF_TimeCode *tc = tc_listtc_idx; + GF_Fraction64 *frac = ts_listtc_idx; + u64 cur_ts, target_ts; + Bool use_tc_as_utc = (ctx->utc_ref == UTCREF_TC); + + if (use_tc_as_utc) { + //convert timecode to UTC: timecode don't contain the day so consider today + time_t utc_now = (time_t) (gf_net_get_utc() / 1000); + struct tm *tm = gf_gmtime(&utc_now); + //use a better 180k accuracy timescale as it allows dealing with high fps.num (up to 60000) + u64 nowIn180k = 180 * gf_net_get_utc_ts(tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, pck_tc->hours, pck_tc->minutes, pck_tc->seconds); + nowIn180k += gf_timestamp_rescale(pck_tc->n_frames * 180000, fps.num, fps.den); + + cur_ts = gf_timestamp_rescale(nowIn180k, 180000, pck_ts); + target_ts = gf_timestamp_rescale(ctx->cur_start.num, 1000, pck_ts); + st->last_utc_ref = gf_timestamp_rescale(nowIn180k, 180000, 1000); + st->last_utc_ref_ts = ts; + } else { + tc->max_fps = pck_tc->max_fps; + cur_ts = gf_timecode_to_timestamp(pck_tc, pck_ts); + target_ts = gf_timecode_to_timestamp(tc, pck_ts); + } + + // Common logic for both modes + Bool tc_out_of_bounds = use_tc_as_utc ? (target_ts < cur_ts) : gf_timecode_less_or_equal(tc, pck_tc); + + if (tc_out_of_bounds) { + // Start from the first frame since timecode is out-of-bounds + frac->num = ts; + frac->den = pck_ts; + if (tc == ctx->cur_start_tc) { + ctx->ts_tc_offset.num = cur_ts - target_ts; + ctx->ts_tc_offset.den = pck_ts; + } + } else { + frac->num = ts + (target_ts - cur_ts); + frac->den = pck_ts; + if (tc == ctx->cur_start_tc) { + ctx->ts_tc_offset.num = 0; + ctx->ts_tc_offset.den = pck_ts; + } + } + + *valid = GF_TRUE; + } + } + + if (!ctx->cur_start_valid || !ctx->cur_end_valid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Reframer No timecode for the first packet in the range, aborting\n")); + gf_filter_abort(filter); + return GF_BAD_PARAM; } refetch_streams: @@ -1755,6 +2017,11 @@ nb_start_range_reached++; } if (!ctx->is_range_extraction) { + if (!st->in_eos && ctx->nb_align_pending) { + ctx->nb_align_pending--; + if (st->cts_us_at_init == ctx->next_cts_align) + ctx->next_cts_align = 0; + } st->in_eos = GF_TRUE; } continue; @@ -1762,6 +2029,11 @@ if (!ctx->is_range_extraction) { check_split = GF_TRUE; + if (!st->in_eos && ctx->nb_align_pending) { + ctx->nb_align_pending--; + if (st->cts_us_at_init == ctx->next_cts_align) + ctx->next_cts_align = 0; + } st->in_eos = GF_TRUE; } else { st->range_start_computed = 2; @@ -1799,8 +2071,11 @@ } st->nb_frames_range++; + if (ctx->xdts) { + check_ts = gf_filter_pck_get_dts(pck); + } //in range extraction we target the presentation time, use CTS and apply delay - if (ctx->is_range_extraction) { + else if (ctx->is_range_extraction) { check_ts = gf_filter_pck_get_cts(pck) + st->tk_delay; if (check_ts > st->ts_sub) check_ts -= st->ts_sub; else check_ts = 0; @@ -1925,7 +2200,7 @@ st->sap_ts_plus_one = st->prev_sap_ts + 1; } } else if (ctx->xround<=REFRAME_ROUND_SEEK) { - st->sap_ts_plus_one = st->prev_sap_ts+1; + st->sap_ts_plus_one = (ctx->nosap ? ts : st->prev_sap_ts) + 1; if ((ctx->extract_mode==EXTRACT_RANGE) && !ctx->start_frame_idx_plus_one) { u64 start_range_ts = gf_timestamp_rescale(ctx->cur_start.num, ctx->cur_start.den, st->timescale); @@ -2005,7 +2280,7 @@ //time-based extraction or dur split, try to clone packet if (st->can_split && !ctx->start_frame_idx_plus_one) { if (gf_timestamp_less(ts, st->timescale, ctx->cur_end.num, ctx->cur_end.den)) { - //force enqueing this packet + //force enqueuing this packet enqueue = GF_TRUE; st->split_end = (u32) ( (ctx->cur_end.num * st->timescale) / ctx->cur_end.den - ts); st->range_end_reached_ts += st->split_end; @@ -2094,8 +2369,8 @@ RTStream *st = gf_filter_pid_get_udta(ipid); if (!st->is_playing) continue; gf_assert(st->range_start_computed || st->in_eos); - //eos - if (st->range_start_computed==2) { + //eos and no packets queued + if (!gf_list_count(st->pck_queue) && (st->range_start_computed==2)) { continue; } //packet will be reinserted at cut time, do not check its timestamp @@ -2136,7 +2411,12 @@ if (!min_ts) { purge_all = GF_TRUE; if (ctx->extract_mode==EXTRACT_RANGE) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer All streams in end of stream for desired start range "LLD"/"LLU"\n", ctx->cur_start.num, ctx->cur_start.den)); + if (ctx->cur_start_tc) { + char tcBuf100; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer All streams in end of stream for desired start range %s\n", gf_format_timecode(ctx->cur_start_tc, tcBuf))); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Reframer All streams in end of stream for desired start range "LLD"/"LLU"\n", ctx->cur_start.num, ctx->cur_start.den)); + } } ctx->eos_state = 1; } else { @@ -2411,6 +2691,12 @@ if (gf_filter_pid_is_eos(ipid)) { gf_filter_pid_set_eos(st->opid); + if (!st->in_eos && ctx->nb_align_pending) { + ctx->nb_align_pending--; + if (st->cts_us_at_init == ctx->next_cts_align) + ctx->next_cts_align = 0; + st->in_eos = GF_TRUE; + } nb_eos++; } } @@ -2421,7 +2707,6 @@ u32 size; u64 cts; - cts = gf_filter_pck_get_dts(pck); if (cts==GF_FILTER_NO_TS) cts = gf_filter_pck_get_cts(pck); @@ -2618,8 +2903,25 @@ if (ctx->speed != 0) ctx->rt_speed = ctx->speed; + if ((ctx->xs.nb_items==0) && (ctx->xe.nb_items)) { + if (ctx->xe.nb_items==1) { + ctx->xs.nb_items = 1; + ctx->xs.vals = gf_malloc(sizeof(char *)); + ctx->xs.vals0 = gf_strdup("0"); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Reframer Multiple `xe` set but no `xs`, cannot extract range\n")); + return GF_BAD_PARAM; + } + } + reframer_load_range(ctx); + if (ctx->utc_ref==UTCREF_TC) { + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("Reframer using timecodes: the considered day for comparisons will be today /!\\\n")); + //we use timecodes, load SEI + ctx->load_sei = GF_TRUE; + } + switch (ctx->raw) { case RAW_AV: e = gf_filter_override_caps(filter, ReframerCaps_RAW_AV, GF_ARRAY_LENGTH(ReframerCaps_RAW_AV)); @@ -2680,6 +2982,19 @@ st->sys_clock_at_init = 0; if (ctx->speed==0) ctx->rt_speed = evt->play.speed; + } else if (evt->base.type==GF_FEVT_TRANSPORT_HINTS) { + if (evt->transport_hints.wait_seg_boundary) { + //this applies to all streams + u32 ipid_count = gf_filter_get_ipid_count(filter); + for (u32 i=0; i<ipid_count; i++) { + GF_FilterPid *ipid = gf_filter_get_ipid(filter, i); + RTStream *ist = gf_filter_pid_get_udta(ipid); + if (ist && ist->wait_seg_boundary==WAIT_SEG_BOUNDARY_NONE) { + ist->wait_seg_boundary = WAIT_SEG_BOUNDARY_ACTIVE; + ist->segdur = evt->transport_hints.seg_duration; + } + } + } } gf_filter_pid_send_event(st->ipid, &fevt); @@ -2695,6 +3010,10 @@ reframer_reset_stream(ctx, st, GF_TRUE); } gf_list_del(ctx->streams); + if (ctx->cur_start_tc) + gf_free(ctx->cur_start_tc); + if (ctx->cur_end_tc) + gf_free(ctx->cur_end_tc); } static GF_Err reframer_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) @@ -2715,7 +3034,7 @@ static const GF_FilterCapability ReframerCaps = { CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), - //we do accept everything, including raw streams + //we do accept everything, including raw streams CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), //we don't accept files as input so don't output them @@ -2732,9 +3051,10 @@ { { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(rt), "real-time regulation mode of input\n" - "- off: disables real-time regulation\n" - "- on: enables real-time regulation, one clock per PID\n" - "- sync: enables real-time regulation one clock for all PIDs", GF_PROP_UINT, "off", "off|on|sync", GF_FS_ARG_HINT_NORMAL|GF_FS_ARG_UPDATE}, + "- off: disable real-time regulation\n" + "- on: enable real-time regulation, one clock per PID\n" + "- sync: enable real-time regulation, one clock for all PIDs\n" + "- align: send packets in DTS order following one clock for all PIDs (undo input packet bursts), no real-time regulation", GF_PROP_UINT, "off", "off|on|sync|align", GF_FS_ARG_HINT_NORMAL|GF_FS_ARG_UPDATE}, { OFFS(saps), "list of SAP types (0,1,2,3,4) to forward, other packets are dropped (forwarding only sap 0 will break the decoding)", GF_PROP_UINT_LIST, NULL, "0|1|2|3|4", GF_FS_ARG_HINT_NORMAL|GF_FS_ARG_UPDATE}, { OFFS(refs), "forward only frames used as reference frames, if indicated in the input stream", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_NORMAL|GF_FS_ARG_UPDATE}, { OFFS(speed), "speed for real-time regulation mode, a value of 0 uses speed from play commands", GF_PROP_DOUBLE, "0.0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, @@ -2744,7 +3064,7 @@ "- a: force decoding of audio inputs\n" "- v: force decoding of video inputs", GF_PROP_UINT, "no", "av|a|v|no", GF_FS_ARG_HINT_NORMAL}, { OFFS(frames), "drop all except listed frames (first being 1). A negative value `-V` keeps only first frame every `V` frames", GF_PROP_SINT_LIST, NULL, NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, - { OFFS(xs), "extraction start time(s)", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_NORMAL}, + { OFFS(xs), "extraction start time(s). If not set and an extraction end time is set, 0 is used", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_NORMAL}, { OFFS(xe), "extraction end time(s). If less values than start times, the last time interval extracted is an open range", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_NORMAL}, { OFFS(xround), "adjust start time of extraction range to I-frame\n" "- before: use first I-frame preceding or matching range start\n" @@ -2753,6 +3073,7 @@ "- closest: use I-frame closest to range start", GF_PROP_UINT, "before", "before|seek|after|closest", GF_FS_ARG_HINT_ADVANCED}, { OFFS(xadjust), "adjust end time of extraction range to be before next I-frame", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(xots), "keep original timestamps after extraction", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(xdts), "compute start times based on DTS and not CTS", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(nosap), "do not cut at SAP when extracting range (may result in broken streams)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(splitrange), "signal file boundary at each extraction first packet for template-base file generation", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(seeksafe), "rewind play requests by given seconds (to make sure the I-frame preceding start is catched)", GF_PROP_DOUBLE, "10.0", NULL, GF_FS_ARG_HINT_EXPERT}, @@ -2763,20 +3084,23 @@ { OFFS(utc_ref), "set reference mode for UTC range extraction\n" "- local: use UTC of local host\n" "- any: use UTC of media, or UTC of local host if not found in media after probing time\n" - "- media: use UTC of media (abort if none found)", GF_PROP_UINT, "any", "local|any|media", GF_FS_ARG_HINT_ADVANCED}, + "- media: use UTC of media (abort if none found)\n" + "- tc: use timecode of media (be careful: considered day will be today)", GF_PROP_UINT, "any", "local|any|media|tc", GF_FS_ARG_HINT_ADVANCED}, { OFFS(utc_probe), "timeout in milliseconds to try to acquire UTC reference from media", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(copy), "try copying frame interface into packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, { OFFS(cues), "cue filtering mode\n" "- no: do no filter frames based on cue info\n" "- segs: only forward frames marked as segment start\n" "- frags: only forward frames marked as fragment start", GF_PROP_UINT, "no", "no|segs|frags", GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, + { OFFS(sapcue), "treat SAPs smaller than or equal to this value as cue points", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT }, { OFFS(rmseek), "remove seek flag of all sent packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, + { OFFS(nodisc), "ignore all discontinuities from input - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, {0} }; GF_FilterRegister ReframerRegister = { .name = "reframer", - GF_FS_SET_DESCRIPTION("Media Reframer") + GF_FS_SET_DESCRIPTION("Media reframer") GF_FS_SET_HELP("This filter provides various tools on inputs:\n" "- ensure reframing (1 packet = 1 Access Unit)\n" "- optionally force decoding\n" @@ -2806,6 +3130,7 @@ "# Range extraction\n" "The filter can perform time range extraction of the source using -xs() and -xe() options.\n" "The formats allowed for times specifiers are:\n" + "- 'TC'HH:MM:SS:FF: specify time in timecode\n" "- 'T'H:M:S, 'T'M:S: specify time in hours, minutes, seconds\n" "- 'T'H:M:S.MS, 'T'M:S.MS, 'T'S.MS: specify time in hours, minutes, seconds and milliseconds\n" "- INT, FLOAT, NUM/DEN: specify time in seconds (number or fraction)\n" @@ -2877,6 +3202,14 @@ "\n" "Note: In these modes, -splitrange() and -xadjust() are implicitly set.\n" "\n" + "# Absorbing stream discontinuities\n" + "Discontinuities may happen quite often in streaming sessions due to resolution switching, codec change, etc ...\n" + "While GPAC handles these discontinuities internally, it may be desired to ignore them, for example when a source is known to have no discontinuity but GPAC detects some due to network errors or other changing properties that should be ignored.\n" + "The -nodisc() option allows removing all discontinuities once a stream is setup.\n" + "Warning: Make sure you know what you are doing as using this option could make the stream not playable (ignoring a codec config change).\n" + "EX gpac -i SOMEURL reframer:nodisc -o DASH_ORIGIN\n" + "In this example, the dasher filter will never trigger a period switch due to input stream discontinuity.\n" + "\n" ) .private_size = sizeof(GF_ReframerCtx), .max_extra_pids = (u32) -1, @@ -2889,7 +3222,8 @@ .configure_pid = reframer_configure_pid, .process = reframer_process, .process_event = reframer_process_event, - .update_arg = reframer_update_arg + .update_arg = reframer_update_arg, + .hint_class_type = GF_FS_CLASS_STREAM }; @@ -2903,4 +3237,3 @@ return NULL; } #endif //GPAC_DISABLE_REFRAMER -
View file
gpac-2.4.0.tar.gz/src/filters/resample_audio.c -> gpac-26.02.0.tar.gz/src/filters/resample_audio.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2025 * All rights reserved * * This file is part of GPAC / audio resample filter @@ -306,6 +306,8 @@ } } } + if (!ctx->speed) + return GF_OK; if (ctx->passthrough) { gf_filter_pck_forward(ctx->in_pck, ctx->opid); @@ -456,6 +458,12 @@ CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_SAMPLE_RATE, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_NUM_CHANNELS, 0), + CAP_UINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_AUDIO_FORMAT, 0), + CAP_LUINT(GF_CAPFLAG_RECONFIG, GF_PROP_PID_CHANNEL_LAYOUT, 0), + CAP_DOUBLE(GF_CAPFLAG_RECONFIG, GF_PROP_PID_AUDIO_SPEED, 0), }; #define OFFS(_n) #_n, offsetof(GF_ResampleCtx, _n) @@ -482,6 +490,7 @@ .process = resample_process, .reconfigure_output = resample_reconfigure_output, .process_event = resample_process_event, + .hint_class_type = GF_FS_CLASS_AV }; const GF_FilterRegister *resample_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/restamp.c -> gpac-26.02.0.tar.gz/src/filters/restamp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom Paris 2022-2023 + * Copyright (c) Telecom Paris 2022-2024 * All rights reserved * * This file is part of GPAC / restamper filter @@ -54,18 +54,17 @@ GF_List *packets; } RestampPid; -enum -{ +GF_OPT_ENUM (GF_VideoFrameCopyMode, RESTAMP_RAWV_NO=0, RESTAMP_RAWV_FORCE, RESTAMP_RAWV_DYN, -}; +); typedef struct { GF_Fraction fps, delay, delay_v, delay_a, delay_t, delay_o; GF_Fraction64 tsinit; - u32 rawv; + GF_VideoFrameCopyMode rawv; u32 align; Bool reorder; @@ -112,6 +111,7 @@ gf_filter_pid_set_udta(pid, pctx); pctx->opid = gf_filter_pid_new(filter); if (!pctx->opid) return GF_OUT_OF_MEM; + gf_filter_pid_set_udta(pctx->opid, pctx); ctx->config_timing = GF_TRUE; if (ctx->reorder) pctx->packets = gf_list_new(); @@ -208,7 +208,7 @@ } } prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); - if ((ctx->fps.num) && prop->value.lfrac.den) { + if (prop && (ctx->fps.num) && prop->value.lfrac.den) { GF_Fraction64 ndur = prop->value.lfrac; if (ctx->fps.num>0) { if (fps_scaler.num) { @@ -545,6 +545,18 @@ return GF_OK; } +static Bool restamp_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + if (!evt->base.on_pid) return GF_FALSE; + RestampPid *pctx = gf_filter_pid_get_udta(evt->base.on_pid); + if (pctx) { + GF_FilterEvent fwd_evt = *evt; + fwd_evt.base.on_pid = pctx->ipid; + gf_filter_pid_send_event(pctx->ipid, &fwd_evt); + } + return GF_TRUE; +} + static GF_Err restamp_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) { RestampCtx *ctx = (RestampCtx *) gf_filter_get_udta(filter); @@ -637,7 +649,7 @@ const GF_FilterRegister RestampRegister = { .name = "restamp", - GF_FS_SET_DESCRIPTION("Packet timestamp rewriter") + GF_FS_SET_DESCRIPTION("Timestamp rewriter") GF_FS_SET_HELP("This filter rewrites timing (offsets and rate) of packets.\n" "\n" "The delays (global or per stream class) can be either positive (stream presented later) or negative (stream presented sooner).\n" @@ -647,9 +659,9 @@ "- otherwise if negative, stream rate is multiplied by `-fps.num/fps.den`.\n" "- otherwise if positive and the stream is not video, stream rate is not modified.\n" "- otherwise (video PID), constant frame rate is assumed and:\n" - " - if -rawv=no(), video frame rate is changed to the specified rate (speed-up or slow-down).\n" - " - if -rawv=force(), input video stream is decoded and video frames are dropped/copied to match the new rate.\n" - " - if -rawv=dyn(), input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate.\n" + " - if -rawv() = `no`, video frame rate is changed to the specified rate (speed-up or slow-down).\n" + " - if -rawv() = `force`, input video stream is decoded and video frames are dropped/copied to match the new rate.\n" + " - if -rawv() = `dyn`, input video stream is decoded if not all-intra and video frames are dropped/copied to match the new rate.\n" "\n" "Note: frames are simply copied or dropped with no motion compensation.\n" "\n" @@ -665,7 +677,9 @@ .finalize = restamp_finalize, .configure_pid = restamp_configure_pid, .process = restamp_process, - .update_arg = restamp_update_arg + .process_event = restamp_process_event, + .update_arg = restamp_update_arg, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *restamp_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/rewind.c -> gpac-26.02.0.tar.gz/src/filters/rewind.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / media rewinder filter @@ -254,7 +254,8 @@ SETCAPS(RewinderCaps), .configure_pid = rewind_configure_pid, .process = rewind_process, - .process_event = rewind_process_event + .process_event = rewind_process_event, + .hint_class_type = GF_FS_CLASS_STREAM };
View file
gpac-26.02.0.tar.gz/src/filters/rewrite_ac4.c
Added
@@ -0,0 +1,225 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2020-2023 + * All rights reserved + * + * This file is part of GPAC / MHAS write filter + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/filters.h> +#include <gpac/constants.h> +#include <gpac/bitstream.h> + +#ifndef GPAC_DISABLE_UFAC4 + +#include <gpac/avparse.h> + + +typedef struct +{ + //opts + Bool rcfg; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs_w; + + u8 *dsi; + u32 dsi_size; + u32 dsi_crc; + Bool update_dsi; + GF_Fraction fdsi; + u64 last_cts; + u32 timescale; +} GF_AC4MxCtx; + +GF_Err ac4mx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 crc; + const GF_PropertyValue *p; + GF_AC4MxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + if (ctx->opid) { + gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (!p) return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!p) { + ctx->dsi_crc = 0; + ctx->dsi = NULL; + ctx->dsi_size = 0; + } else if (p->value.data.size<=5) { + return GF_NON_COMPLIANT_BITSTREAM; + } else { + crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (crc != ctx->dsi_crc) { + ctx->dsi_crc = crc; + ctx->update_dsi = GF_TRUE; + ctx->dsi = p->value.data.ptr + 5; + ctx->dsi_size = p->value.data.size - 5; + } + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (!p) return GF_NOT_SUPPORTED; + ctx->timescale = p->value.uint; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + ctx->ipid = pid; + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + return GF_OK; +} + + + +GF_Err ac4mx_process(GF_Filter *filter) +{ + GF_AC4MxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u32 pck_size, size; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + if (!pck_size) { + //if output and packet properties, forward - this is required for sinks using packets for state signaling + //such as TS muxer in dash mode looking for EODS property + if (ctx->opid && gf_filter_pck_has_properties(pck)) + gf_filter_pck_forward(pck, ctx->opid); + + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + + u32 hdr_size = 0; + + hdr_size += 7; // base header needs 2 bytes + + size = pck_size + hdr_size; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + if (!dst_pck) return GF_OUT_OF_MEM; + + gf_bs_reassign_buffer(ctx->bs_w, output, size); + + //write sync & framesize + gf_bs_write_u8(ctx->bs_w, 0xAC); + gf_bs_write_u8(ctx->bs_w, 0x40); + gf_bs_write_u8(ctx->bs_w, 0xFF); + gf_bs_write_u8(ctx->bs_w, 0xFF); + + gf_bs_write_int(ctx->bs_w, pck_size, 24); + + // copy payload + memcpy(output+hdr_size, data, pck_size); + + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static GF_Err ac4mx_initialize(GF_Filter *filter) +{ + GF_AC4MxCtx *ctx = gf_filter_get_udta(filter); + ctx->bs_w = gf_bs_new((u8*)ctx, 1, GF_BITSTREAM_WRITE); + return GF_OK; +} + +static void ac4mx_finalize(GF_Filter *filter) +{ + GF_AC4MxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); +} + +static const GF_FilterCapability AC4MxCaps = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AC4), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_AC4MxCtx, _n) +static const GF_FilterArgs AC4MxArgs = +{ + { OFFS(rcfg), "force repeating decoder config at each I-frame", GF_PROP_BOOL, "true", NULL, 0}, + {0} +}; + + +GF_FilterRegister AC4MxRegister = { + .name = "ufac4", + GF_FS_SET_DESCRIPTION("AC4 writer") + GF_FS_SET_HELP("This filter converts MPEG-H Audio streams into AC4 encapsulated data.") + .private_size = sizeof(GF_AC4MxCtx), + .args = AC4MxArgs, + .initialize = ac4mx_initialize, + .finalize = ac4mx_finalize, + SETCAPS(AC4MxCaps), + .configure_pid = ac4mx_configure_pid, + .process = ac4mx_process, + .hint_class_type = GF_FS_CLASS_FRAMING +}; + + +const GF_FilterRegister *ufac4_register(GF_FilterSession *session) +{ + return &AC4MxRegister; +} +#else +const GF_FilterRegister *ufac4_register(GF_FilterSession *session) +{ + return NULL; +} +#endif //#ifndef GPAC_DISABLE_UFAC4 +
View file
gpac-2.4.0.tar.gz/src/filters/rewrite_adts.c -> gpac-26.02.0.tar.gz/src/filters/rewrite_adts.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / AAC ADTS write filter @@ -31,11 +31,17 @@ #include <gpac/avparse.h> +GF_OPT_ENUM (GF_Mpeg2AACSignalMode, + AAC_MPEG2_NO=0, + AAC_MPEG2_YES, + AAC_MPEG2_AUTO, +); typedef struct { //opts - Bool exporter, mpeg2; + Bool exporter; + GF_Mpeg2AACSignalMode mpeg2; //only one input pid declared GF_FilterPid *ipid; @@ -61,6 +67,8 @@ GF_Fraction fdsi; u64 last_cts; u32 timescale; + + u32 signal_mpeg2; } GF_ADTSMxCtx; @@ -88,6 +96,21 @@ if (!p) return GF_NOT_SUPPORTED; ctx->codecid = p->value.uint; + if (ctx->mpeg2==AAC_MPEG2_AUTO) { + switch(ctx->codecid) { + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + ctx->signal_mpeg2 = GF_TRUE; + break; + default: + ctx->signal_mpeg2 = GF_FALSE; + break; + } + } else { + ctx->signal_mpeg2 = ctx->mpeg2 ? GF_TRUE : GF_FALSE; + } + if (!ctx->opid) { ctx->opid = gf_filter_pid_new(filter); } @@ -139,7 +162,7 @@ if (chan_cfg==8) chan_cfg = 7; - if (!ctx->mpeg2) { + if (!ctx->signal_mpeg2) { #ifndef GPAC_DISABLE_AV_PARSERS p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); if (p) { @@ -336,7 +359,7 @@ else gf_bs_reassign_buffer(ctx->bs_w, output, size); gf_bs_write_int(ctx->bs_w, 0xFFF, 12);/*sync*/ - gf_bs_write_int(ctx->bs_w, (ctx->mpeg2==1) ? 1 : 0, 1);/*mpeg2 aac*/ + gf_bs_write_int(ctx->bs_w, ctx->signal_mpeg2 ? 1 : 0, 1);/*mpeg2 aac*/ gf_bs_write_int(ctx->bs_w, 0, 2); /*layer*/ gf_bs_write_int(ctx->bs_w, 1, 1); /* protection_absent*/ gf_bs_write_int(ctx->bs_w, ctx->aac_type, 2); @@ -398,21 +421,22 @@ "- auto: selects based on AAC profile\n" "- no: always signals as MPEG-4 AAC\n" "- yes: always signals as MPEG-2 AAC" - "", GF_PROP_UINT, "auto", "auto|no|yes", GF_FS_ARG_HINT_ADVANCED}, + "", GF_PROP_UINT, "auto", "no|yes|auto", GF_FS_ARG_HINT_ADVANCED}, {0} }; GF_FilterRegister ADTSMxRegister = { .name = "ufadts", - GF_FS_SET_DESCRIPTION("ADTS writer") + GF_FS_SET_DESCRIPTION("ADTS rewriter") GF_FS_SET_HELP("This filter converts AAC streams into ADTS encapsulated data.") .private_size = sizeof(GF_ADTSMxCtx), .args = ADTSMxArgs, .finalize = adtsmx_finalize, SETCAPS(ADTSMxCaps), .configure_pid = adtsmx_configure_pid, - .process = adtsmx_process + .process = adtsmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -450,7 +474,7 @@ GF_FilterRegister LATMMxRegister = { .name = "uflatm", - GF_FS_SET_DESCRIPTION("Raw AAC to LATM writer") + GF_FS_SET_DESCRIPTION("LATM rewriter") GF_FS_SET_HELP("This filter converts AAC streams into LATM encapsulated data.") .private_size = sizeof(GF_ADTSMxCtx), .args = LATMMxArgs, @@ -458,7 +482,8 @@ .finalize = adtsmx_finalize, SETCAPS(LATMMxCaps), .configure_pid = adtsmx_configure_pid, - .process = adtsmx_process + .process = adtsmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/rewrite_mhas.c -> gpac-26.02.0.tar.gz/src/filters/rewrite_mhas.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2023 + * Copyright (c) Telecom ParisTech 2020-2024 * All rights reserved * * This file is part of GPAC / MHAS write filter @@ -255,14 +255,14 @@ #define OFFS(_n) #_n, offsetof(GF_MHASMxCtx, _n) static const GF_FilterArgs MHASMxArgs = { - { OFFS(syncp), "if set, insert sync packet at each frame, otherwise only at SAP", GF_PROP_BOOL, "yes", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(syncp), "if set, insert sync packet at each frame, otherwise only at SAP", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, {0} }; GF_FilterRegister MHASMxRegister = { .name = "ufmhas", - GF_FS_SET_DESCRIPTION("MHAS writer") + GF_FS_SET_DESCRIPTION("MHAS rewriter") GF_FS_SET_HELP("This filter converts MPEG-H Audio streams into MHAS encapsulated data.") .private_size = sizeof(GF_MHASMxCtx), .args = MHASMxArgs, @@ -270,7 +270,8 @@ .finalize = mhasmx_finalize, SETCAPS(MHASMxCaps), .configure_pid = mhasmx_configure_pid, - .process = mhasmx_process + .process = mhasmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/rewrite_mp4v.c -> gpac-26.02.0.tar.gz/src/filters/rewrite_mp4v.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / MPEG-4 part2 video rewrite filter @@ -203,13 +203,14 @@ GF_FilterRegister M4VMxRegister = { .name = "ufm4v", - GF_FS_SET_DESCRIPTION("M4V writer") + GF_FS_SET_DESCRIPTION("M4V rewriter") GF_FS_SET_HELP("This filter converts MPEG-4 part 2 visual streams into writable format (reinsert decoder config).") .private_size = sizeof(GF_M4VMxCtx), .args = M4VMxArgs, SETCAPS(M4VMxCaps), .configure_pid = m4vmx_configure_pid, - .process = m4vmx_process + .process = m4vmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -240,14 +241,15 @@ GF_FilterRegister VC1VMxRegister = { .name = "ufvc1", - GF_FS_SET_DESCRIPTION("VC1 writer") + GF_FS_SET_DESCRIPTION("VC1 rewriter") GF_FS_SET_HELP("This filter converts VC1 visual streams into writable format (reinsert decoder config and start codes if needed).") .private_size = sizeof(GF_M4VMxCtx), .args = M4VMxArgs, SETCAPS(VC1MxCaps), .initialize = vc1mx_initialize, .configure_pid = m4vmx_configure_pid, - .process = m4vmx_process + .process = m4vmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/rewrite_nalu.c -> gpac-26.02.0.tar.gz/src/filters/rewrite_nalu.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / NALU video AnnexB write filter @@ -310,8 +310,9 @@ } -static Bool nalumx_is_nal_skip(GF_NALUMxCtx *ctx, u8 *data, u32 pos, u32 nal_size, Bool *has_nal_delim, u32 *out_temporal_id, u32 *out_layer_id, u8 *avc_hdr, u32 *delim_flags) +static Bool nalumx_is_nal_skip(GF_NALUMxCtx *ctx, u8 *data, u32 pos, u32 nal_size, Bool *has_nal_delim, u32 *out_temporal_id, u32 *out_layer_id, u8 *avc_hdr, u32 *delim_flags, Bool *is_sap) { + *is_sap = GF_FALSE; Bool is_layer = GF_FALSE; if (ctx->vtype==UFNAL_HEVC) { u8 nal_type = (datapos & 0x7E) >> 1; @@ -331,9 +332,10 @@ default: if (layer_id) is_layer = GF_TRUE; #ifndef GPAC_DISABLE_AV_PARSERS - if (nal_size && (*delim_flags != 3) && (nal_type<=GF_HEVC_NALU_SLICE_CRA)) { + if (nal_size && (*delim_flags != 3) && (nal_type<=GF_HEVC_NALU_SLICE_CRA) && ctx->hevc_state) { u8 nut, tid, lid; - gf_hevc_parse_nalu(data+pos, nal_size, ctx->hevc_state, &nut, &tid, &lid); + s32 ret = gf_hevc_parse_nalu(data+pos, nal_size, ctx->hevc_state, &nut, &tid, &lid); + if (ret) break; u32 flags=0; switch (ctx->hevc_state->s_info.slice_type) { @@ -344,6 +346,9 @@ *delim_flags |= flags; } #endif + if ((nal_type>=GF_HEVC_NALU_SLICE_BLA_W_LP) && (nal_type<=GF_HEVC_NALU_SLICE_CRA)) + *is_sap = GF_TRUE; + break; } } else if (ctx->vtype==UFNAL_VVC) { @@ -375,6 +380,9 @@ *delim_flags |= flags; } #endif + if ((nal_type>=GF_VVC_NALU_SLICE_IDR_W_RADL) && (nal_type<GF_VVC_NALU_SLICE_GDR)) + *is_sap = GF_TRUE; + break; } } else { @@ -392,6 +400,9 @@ case GF_AVC_NALU_ACCESS_UNIT: *has_nal_delim = GF_TRUE; break; + case GF_AVC_NALU_IDR_SLICE: + *is_sap = GF_TRUE; + //fallthrough default: if (! (*avc_hdr)) (*avc_hdr) = datapos; @@ -467,9 +478,13 @@ if (ctx->vvc_state) ctx->vvc_state->s_info.slice_type = 0; #endif + u32 nb_nalsize_zero=0; + Bool has_sap = GF_FALSE; + while (gf_bs_available((ctx->bs_r))) { Bool skip_nal = GF_FALSE; Bool is_nalu_delim = GF_FALSE; + Bool is_sap = GF_FALSE; u32 pos; u32 nal_size = gf_bs_read_int(ctx->bs_r, 8*ctx->nal_hdr_size); if (nal_size > gf_bs_available(ctx->bs_r) ) { @@ -477,11 +492,19 @@ return GF_NON_COMPLIANT_BITSTREAM; } //we allow nal_size=0 for incomplete files, abort as soon as we see one to avoid parsing thousands of 0 bytes - if (!nal_size) break; + if (!nal_size) { + if (nb_nalsize_zero) break; + nb_nalsize_zero++; + } else { + nb_nalsize_zero=0; + } + + if (!gf_bs_available(ctx->bs_r)) + break; pos = (u32) gf_bs_get_position(ctx->bs_r); //even if not filtering, parse to check for AU delim - skip_nal = nalumx_is_nal_skip(ctx, data, pos, nal_size, &is_nalu_delim, &layer_id, &temporal_id, &avc_hdr, &delim_flags); + skip_nal = nalumx_is_nal_skip(ctx, data, pos, nal_size, &is_nalu_delim, &layer_id, &temporal_id, &avc_hdr, &delim_flags, &is_sap); if (!ctx->extract) { skip_nal = GF_FALSE; } @@ -494,6 +517,7 @@ size += nal_size + 4; } gf_bs_skip_bytes(ctx->bs_r, nal_size); + if (is_sap) has_sap = GF_TRUE; } gf_bs_seek(ctx->bs_r, 0); @@ -518,7 +542,8 @@ if (sap && (sap <= GF_FILTER_SAP_3) ) { insert_dsi = GF_TRUE; } - if (!insert_dsi) { + //have only limited trust in dependency flags , some streams always use dependsOn=2 which would inject SPS/PPS at each frame + if (!insert_dsi && has_sap) { u8 flags = gf_filter_pck_get_dependency_flags(pck); //get dependsOn if (flags) { @@ -596,7 +621,7 @@ pos = (u32) gf_bs_get_position(ctx->bs_r); if (!nal_size) continue; - skip_nal = nalumx_is_nal_skip(ctx, data, pos, 0, &is_nalu_delim, &layer_id, &temporal_id, &avc_hdr, NULL); + skip_nal = nalumx_is_nal_skip(ctx, data, pos, 0, &is_nalu_delim, &layer_id, &temporal_id, &avc_hdr, NULL, &has_sap); if (!ctx->extract) { skip_nal = GF_FALSE; } @@ -640,7 +665,7 @@ if (gf_filter_reporting_enabled(filter)) { char szStatus1024; - sprintf(szStatus, "%s Annex-B %dx%d % 10d NALU", (ctx->vtype==UFNAL_HEVC) ? "HEVC" : ((ctx->vtype==UFNAL_VVC) ? "VVC" : "AVC|H264"), ctx->width, ctx->height, ctx->nb_nalu); + snprintf(szStatus, sizeof(szStatus), "%s Annex-B %dx%d % 10d NALU", (ctx->vtype==UFNAL_HEVC) ? "HEVC" : ((ctx->vtype==UFNAL_VVC) ? "VVC" : "AVC|H264"), ctx->width, ctx->height, ctx->nb_nalu); gf_filter_update_status(filter, -1, szStatus); } @@ -732,7 +757,7 @@ GF_FilterRegister NALUMxRegister = { .name = "ufnalu", - GF_FS_SET_DESCRIPTION("AVC/HEVC to AnnexB writer") + GF_FS_SET_DESCRIPTION("AVC/HEVC to AnnexB rewriter") GF_FS_SET_HELP("This filter converts AVC|H264 and HEVC streams into AnnexB format, with inband parameter sets and start codes.") .private_size = sizeof(GF_NALUMxCtx), .args = NALUMxArgs, @@ -740,7 +765,8 @@ .initialize = nalumx_initialize, SETCAPS(NALUMxCaps), .configure_pid = nalumx_configure_pid, - .process = nalumx_process + .process = nalumx_process, + .hint_class_type = GF_FS_CLASS_FRAMING }; @@ -754,4 +780,3 @@ return NULL; } #endif //#ifndef GPAC_DISABLE_UFNALU -
View file
gpac-2.4.0.tar.gz/src/filters/rewrite_obu.c -> gpac-26.02.0.tar.gz/src/filters/rewrite_obu.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / AV1 OBU rewrite filter @@ -145,7 +145,7 @@ while ((obu = gf_list_enum(ctx->av1c->obu_array, &i))) { //we don't output sequence header since it shall be present in sync sample - //this avoids creating duplicate of the seqeunce header in the output stream + //this avoids creating duplicate of the sequence header in the output stream if (obu->obu_type==OBU_SEQUENCE_HEADER) { i--; gf_list_rem(ctx->av1c->obu_array, i); @@ -598,6 +598,7 @@ return GF_OK; } + static void obumx_finalize(GF_Filter *filter) { GF_OBUMxCtx *ctx = gf_filter_get_udta(filter); @@ -626,12 +627,12 @@ {0} }; - GF_FilterRegister OBUMxRegister = { .name = "ufobu", - GF_FS_SET_DESCRIPTION("IVF/OBU/annexB writer") + GF_FS_SET_DESCRIPTION("IVF/OBU/annexB rewriter") GF_FS_SET_HELP("This filter rewrites VPx or AV1 bitstreams into a IVF, annexB or OBU sequence.\n" - "The temporal delimiter OBU is re-inserted in annexB (`.av1` and `.av1b`files, with obu_size set) and OBU sequences (`.obu`files, without obu_size)\n" + "The temporal delimiter OBU is re-inserted in annexB (`.av1` and `.av1b` files, with obu_size set) and OBU sequences (`.obu` files, without obu_size)\n" + "Timecode metadata optionally inserted\n" "Note: VP8/9 codecs will only use IVF output (equivalent to file extension `.ivf` or `:ext=ivf` set on output).\n" ) .private_size = sizeof(GF_OBUMxCtx), @@ -639,7 +640,8 @@ SETCAPS(OBUMxCaps), .finalize = obumx_finalize, .configure_pid = obumx_configure_pid, - .process = obumx_process + .process = obumx_process, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-26.02.0.tar.gz/src/filters/sei_load.c
Added
@@ -0,0 +1,612 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2025 + * All rights reserved + * + * This file is part of GPAC / SEI loader + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/filters.h> +#include <gpac/constants.h> +#include <gpac/internal/media_dev.h> +#include <gpac/mpeg4_odf.h> + +#ifndef GPAC_DISABLE_AV_PARSERS + +enum +{ + COD_TYPE_AVC = 1, + COD_TYPE_HEVC, + COD_TYPE_VVC, + COD_TYPE_AV1, +}; + +struct _gf_sei_loader +{ + Bool is_identity, external; + u32 codec_type, nalu_size; + + AVCState *avc_state; + HEVCState *hevc_state; + VVCState *vvc_state; + AV1State *av1_state; + GF_BitStream *full_bs, *bs; +}; + + + +static GF_Err seiloader_set_type(GF_SEILoader *sei, u32 type) +{ + //reset shared pointers + if (sei->external) { + sei->external = GF_FALSE; + sei->avc_state = NULL; + sei->hevc_state = NULL; + sei->vvc_state = NULL; + } + if (type==sei->codec_type) + return GF_OK; + + sei->codec_type = 0; + if (type==COD_TYPE_AVC) { + if (sei->hevc_state) { gf_free(sei->hevc_state); sei->hevc_state = NULL; } + if (sei->vvc_state) { gf_free(sei->vvc_state); sei->vvc_state = NULL; } + if (sei->av1_state) { gf_av1_reset_state(sei->av1_state, GF_TRUE); gf_free(sei->av1_state); sei->av1_state = NULL; } + GF_SAFEALLOC(sei->avc_state, AVCState); + if (!sei->avc_state) return GF_OUT_OF_MEM; + } + if (type==COD_TYPE_HEVC) { + if (sei->avc_state) { gf_free(sei->avc_state); sei->avc_state = NULL; } + if (sei->vvc_state) { gf_free(sei->vvc_state); sei->vvc_state = NULL; } + if (sei->av1_state) { gf_av1_reset_state(sei->av1_state, GF_TRUE); gf_free(sei->av1_state); sei->av1_state = NULL; } + GF_SAFEALLOC(sei->hevc_state, HEVCState); + if (!sei->hevc_state) return GF_OUT_OF_MEM; + } + if (type==COD_TYPE_VVC) { + if (sei->hevc_state) { gf_free(sei->hevc_state); sei->hevc_state = NULL; } + if (sei->avc_state) { gf_free(sei->avc_state); sei->avc_state = NULL; } + if (sei->av1_state) { gf_av1_reset_state(sei->av1_state, GF_TRUE); gf_free(sei->av1_state); sei->av1_state = NULL; } + GF_SAFEALLOC(sei->vvc_state, VVCState); + if (!sei->vvc_state) return GF_OUT_OF_MEM; + } + if (type==COD_TYPE_AV1) { + if (sei->avc_state) { gf_free(sei->avc_state); sei->avc_state = NULL; } + if (sei->hevc_state) { gf_free(sei->hevc_state); sei->hevc_state = NULL; } + if (sei->vvc_state) { gf_free(sei->vvc_state); sei->vvc_state = NULL; } + GF_SAFEALLOC(sei->av1_state, AV1State); + if (!sei->av1_state) return GF_OUT_OF_MEM; + gf_av1_init_state(sei->av1_state); + } + if (type==0) { + if (sei->avc_state) { gf_free(sei->avc_state); sei->avc_state = NULL; } + if (sei->hevc_state) { gf_free(sei->hevc_state); sei->hevc_state = NULL; } + if (sei->vvc_state) { gf_free(sei->vvc_state); sei->vvc_state = NULL; } + if (sei->av1_state) { gf_av1_reset_state(sei->av1_state, GF_TRUE); gf_free(sei->av1_state); sei->av1_state = NULL; } + } + sei->codec_type = type; + return GF_OK; +} + +static void parse_param_list(GF_SEILoader *sei, GF_List *params) +{ + u32 i, count = gf_list_count(params); + for (i=0; i<count; i++) { + GF_NALUFFParam *p = gf_list_get(params, i); + gf_bs_reassign_buffer(sei->bs, p->data, p->size); + if (sei->avc_state) { + gf_avc_parse_nalu(sei->bs, sei->avc_state); + } else if (sei->hevc_state) { + u8 nal_unit_type, temporal_id, layer_id; + gf_hevc_parse_nalu_bs(sei->bs, sei->hevc_state, &nal_unit_type, &temporal_id, &layer_id); + } + else if (sei->vvc_state) { + u8 nal_unit_type, temporal_id, layer_id; + gf_vvc_parse_nalu_bs(sei->bs, sei->vvc_state, &nal_unit_type, &temporal_id, &layer_id); + } + } +} + +GF_Err gf_sei_init_from_pid(GF_SEILoader *sei, GF_FilterPid *pid) +{ + u32 i, count; + sei->is_identity = GF_TRUE; + const GF_PropertyValue *p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_OK; + u32 codec_id = p->value.uint; + Bool is_enh = GF_FALSE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!p) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + if (p) is_enh = GF_TRUE; + } + if (!p) return GF_OK; + + sei->nalu_size = 0; + + switch (codec_id) { + case GF_CODECID_AVC: + { + GF_AVCConfig *avcc = gf_odf_avc_cfg_read(p->value.data.ptr, p->value.data.size); + if (avcc) { + seiloader_set_type(sei, COD_TYPE_AVC); + sei->nalu_size = avcc->nal_unit_size; + parse_param_list(sei, avcc->sequenceParameterSets); + parse_param_list(sei, avcc->sequenceParameterSetExtensions); + parse_param_list(sei, avcc->pictureParameterSets); + gf_odf_avc_cfg_del(avcc); + } + sei->is_identity = GF_FALSE; + } + break; + case GF_CODECID_HEVC: + { + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, is_enh); + if (hvcc) { + seiloader_set_type(sei, COD_TYPE_HEVC); + sei->nalu_size = hvcc->nal_unit_size; + count = gf_list_count(hvcc->param_array); + for (i=0; i<count; i++) { + GF_NALUFFParamArray *pa = gf_list_get(hvcc->param_array, i); + parse_param_list(sei, pa->nalus); + } + gf_odf_hevc_cfg_del(hvcc); + } + sei->is_identity = GF_FALSE; + } + break; + case GF_CODECID_VVC: + { + GF_VVCConfig *vvcc = gf_odf_vvc_cfg_read(p->value.data.ptr, p->value.data.size); + if (vvcc) { + seiloader_set_type(sei, COD_TYPE_VVC); + sei->nalu_size = vvcc->nal_unit_size; + count = gf_list_count(vvcc->param_array); + for (i=0; i<count; i++) { + GF_NALUFFParamArray *pa = gf_list_get(vvcc->param_array, i); + parse_param_list(sei, pa->nalus); + } + gf_odf_vvc_cfg_del(vvcc); + } + } + sei->is_identity = GF_FALSE; + break; + case GF_CODECID_AV1: + { + GF_AV1Config *av1c = gf_odf_av1_cfg_read(p->value.data.ptr, p->value.data.size); + if (av1c) { + seiloader_set_type(sei, COD_TYPE_AV1); + sei->av1_state->config = av1c; + //for now no need to load the OBUs + } + gf_odf_av1_cfg_del(av1c); + } + sei->is_identity = GF_FALSE; + break; + + } + + return GF_OK; +} + +static GFINLINE GF_SEIInfo *get_sei_info(GF_SEILoader *sei) +{ + if (sei->avc_state) return &sei->avc_state->sei; + if (sei->hevc_state) return &sei->hevc_state->sei; + if (sei->vvc_state) return &sei->vvc_state->sei; + if (sei->av1_state) return &sei->av1_state->sei; + return NULL; +} + +static GF_Err gf_sei_load_from_state_internal(GF_SEILoader *ctx, GF_FilterPacket *pck, Bool skip_check) +{ + GF_SEIInfo *sei = get_sei_info(ctx); + if (!sei) return GF_OK; + if (!skip_check && gf_filter_pck_get_property(pck, GF_PROP_PCK_SEI_LOADED)) + return GF_OK; + + skip_check = GF_FALSE; + if (sei->clli_valid) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_CONTENT_LIGHT_LEVEL, &PROP_DATA(sei->clli_data, 4)); + sei->clli_valid = 0; + skip_check = GF_TRUE; + } + if (sei->mdcv_valid) { + if (ctx->av1_state) { + u8 rw_mdcv24; + gf_av1_format_mdcv_to_mpeg(sei->mdcv_data, rw_mdcv); + gf_filter_pck_set_property(pck, GF_PROP_PCK_MASTER_DISPLAY_COLOUR, &PROP_DATA(rw_mdcv, 24)); + } else { + gf_filter_pck_set_property(pck, GF_PROP_PCK_MASTER_DISPLAY_COLOUR, &PROP_DATA(sei->mdcv_data, 24)); + sei->mdcv_valid = 0; + } + skip_check = GF_TRUE; + } + if (sei->pic_timing.num_clock_ts && sei->pic_timing.timecodes0.clock_timestamp_flag) { + AVCSeiPicTimingTimecode *a_tc = &sei->pic_timing.timecodes0; + + GF_TimeCode tc_dst = {0}; + tc_dst.hours = a_tc->hours; + tc_dst.minutes = a_tc->minutes; + tc_dst.seconds = a_tc->seconds; + tc_dst.n_frames = a_tc->n_frames; + tc_dst.max_fps = a_tc->max_fps; + tc_dst.drop_frame = (a_tc->counting_type==4 && a_tc->cnt_dropped_flag) ? 1 : 0; + tc_dst.counting_type = a_tc->counting_type; + gf_filter_pck_set_property(pck, GF_PROP_PCK_TIMECODE, &PROP_DATA((u8*)&tc_dst, sizeof(GF_TimeCode))); + + sei->pic_timing.num_clock_ts = 0; + skip_check = GF_TRUE; + } + + if (skip_check) + gf_filter_pck_set_property(pck, GF_PROP_PCK_SEI_LOADED, &PROP_BOOL(GF_TRUE)); + + return GF_OK; +} +GF_Err gf_sei_load_from_state(GF_SEILoader *ctx, GF_FilterPacket *pck) +{ + return gf_sei_load_from_state_internal(ctx, pck, GF_FALSE); +} + +static GF_Err gf_sei_load_from_packet_nalu(GF_SEILoader *sei, GF_FilterPacket *pck, Bool *needs_load) +{ + u32 data_len; + u8 *data = (u8*) gf_filter_pck_get_data(pck, &data_len); + + u32 pos=0; + while (pos<data_len) { + if (pos + sei->nalu_size >= data_len) break; + u32 tmp=0, size = 0; + while (tmp<sei->nalu_size-1) { + size |= datapos+tmp; + tmp++; + size<<=8; + } + size |= datapos+tmp; + //we allow nal_size=0 for incomplete files, abort as soon as we see one to avoid parsing thousands of 0 bytes + if (!size || size >= GF_UINT_MAX-pos-sei->nalu_size) break; + if (pos+sei->nalu_size+size > data_len) break; + + gf_bs_reassign_buffer(sei->bs, data+pos+sei->nalu_size, size); + Bool is_sei = GF_FALSE; + + if (sei->avc_state) { + u32 nal_type = gf_bs_peek_bits(sei->bs, 8, 0) & 0x1F; + switch (nal_type) { + case GF_AVC_NALU_SEI: + is_sei = GF_TRUE; + gf_avc_reformat_sei(data+pos+sei->nalu_size, size, GF_FALSE, sei->avc_state, NULL); + break; + case GF_AVC_NALU_SEQ_PARAM: //mandatory for pic timing parsing + case GF_AVC_NALU_SEQ_PARAM_EXT: + gf_avc_parse_nalu(sei->bs, sei->avc_state); + break; + } + } else if (sei->hevc_state) { + u8 temporal_id, layer_id; + u8 nal_type = (gf_bs_peek_bits(sei->bs, 8, 0) & 0x7E) >> 1; + switch (nal_type) { + case GF_HEVC_NALU_SEI_PREFIX: + case GF_HEVC_NALU_SEI_SUFFIX: + is_sei = GF_TRUE; + gf_hevc_parse_sei(data+pos+sei->nalu_size, size, sei->hevc_state); + case GF_HEVC_NALU_SEQ_PARAM: //for pic timing full parse we need SPS... + gf_hevc_parse_nalu_bs(sei->bs, sei->hevc_state, &nal_type, &temporal_id, &layer_id); + break; + } + } + else if (sei->vvc_state) { + u8 temporal_id, layer_id; + u8 nal_unit_type = gf_bs_peek_bits(sei->bs, 8, 1) >> 3; + switch (nal_unit_type) { + case GF_VVC_NALU_SEI_PREFIX: + case GF_VVC_NALU_SEI_SUFFIX: + is_sei = GF_TRUE; + gf_vvc_parse_sei(data+pos+sei->nalu_size, size, sei->vvc_state); + gf_vvc_parse_nalu_bs(sei->bs, sei->vvc_state, &nal_unit_type, &temporal_id, &layer_id); + break; + } + } + + if (!is_sei) { + pos += sei->nalu_size + data_len; + continue; + } + *needs_load = GF_TRUE; +#if 0 + //if needed, load SEIs not handled by avparser ? + gf_bs_seek(sei->bs, 0); + //load SEIs + while (gf_bs_available(sei->bs) ) { + u32 sei_type = 0; + u32 sei_size = 0; + u32 sei_pos; + while (gf_bs_peek_bits(sei->bs, 8, 0) == 0xFF) { + sei_type += 255; + gf_bs_read_int(sei->bs, 8); + } + sei_type += gf_bs_read_int(sei->bs, 8); + while (gf_bs_peek_bits(sei->bs, 8, 0) == 0xFF) { + sei_size += 255; + gf_bs_read_int(sei->bs, 8); + } + sei_size += gf_bs_read_int(sei->bs, 8); + sei_pos = (u32) gf_bs_get_position(sei->bs); + + //take actions here + + gf_bs_seek(sei->bs, sei_pos); + while (sei_size && gf_bs_available(sei->bs)) { + gf_bs_read_u8(sei->bs); + sei_size--; + } + if (gf_bs_peek_bits(sei->bs, 8, 0) == 0x80) { + break; + } + } +#endif + + pos += sei->nalu_size + data_len; + } + return GF_OK; +} + +static GF_Err gf_sei_load_from_packet_av1(GF_SEILoader *sei, GF_FilterPacket *pck, Bool *needs_load) +{ + u32 data_len; + u8 *data = (u8*) gf_filter_pck_get_data(pck, &data_len); + + while (data_len) { + ObuType obu_type = 0; + u64 obu_size = 0; + u32 hdr_size = 0; + gf_bs_reassign_buffer(sei->bs, data, data_len); + + sei->av1_state->parse_metadata_filter = 1; + GF_Err e = gf_av1_parse_obu(sei->bs, &obu_type, &obu_size, &hdr_size, sei->av1_state); + if (e) break; + if (sei->av1_state->parse_metadata_filter == 2) + *needs_load = GF_TRUE; + + if (!obu_size || (obu_size > data_len)) { + break; + } + + data += obu_size; + data_len -= (u32)obu_size; + } + return GF_OK; +} + +GF_Err gf_sei_load_from_packet(GF_SEILoader *sei, GF_FilterPacket *pck) +{ + Bool needs_load = GF_FALSE; + GF_Err e = GF_OK; + if (sei->is_identity) + return GF_OK; + + if (sei->nalu_size) + e = gf_sei_load_from_packet_nalu(sei, pck, &needs_load); + else if (sei->av1_state) + e = gf_sei_load_from_packet_av1(sei, pck, &needs_load); + + if (e) return e; + if (needs_load) + return gf_sei_load_from_state_internal(sei, pck, GF_TRUE); + return GF_OK; +} + +GF_Err gf_sei_init_from_avc(GF_SEILoader *sei, AVCState *avc) +{ + if (!sei || !avc) return GF_BAD_PARAM; + seiloader_set_type(sei, 0); + sei->avc_state = avc; + sei->external = GF_TRUE; + return GF_OK; +} +GF_Err gf_sei_init_from_hevc(GF_SEILoader *sei, HEVCState *hevc) +{ + if (!sei || !hevc) return GF_BAD_PARAM; + seiloader_set_type(sei, 0); + sei->hevc_state = hevc; + sei->external = GF_TRUE; + return GF_OK; +} + +GF_Err gf_sei_init_from_vvc(GF_SEILoader *sei, VVCState *vvc) +{ + if (!sei || !vvc) return GF_BAD_PARAM; + seiloader_set_type(sei, 0); + sei->vvc_state = vvc; + sei->external = GF_TRUE; + return GF_OK; +} +GF_Err gf_sei_init_from_av1(GF_SEILoader *sei, AV1State *av1) +{ + if (!sei || !av1) return GF_BAD_PARAM; + seiloader_set_type(sei, 0); + sei->av1_state = av1; + sei->external = GF_TRUE; + return GF_OK; +} + +GF_SEILoader *gf_sei_loader_new() +{ + GF_SEILoader *sei; + + GF_SAFEALLOC(sei, GF_SEILoader); + if (!sei) return NULL; + sei->bs = gf_bs_new((u8*)sei, 1, GF_BITSTREAM_READ); + if (!sei->bs) { + gf_free(sei); + return NULL; + } + return sei; +} + +void gf_sei_loader_del(GF_SEILoader *sei) +{ + if (!sei) return; + seiloader_set_type(sei, 0); + gf_free(sei->bs); + gf_free(sei); +} + +#endif + + +#if !defined(GPAC_DISABLE_SEI_LOAD) && !defined(GPAC_DISABLE_AV_PARSERS) + +typedef struct +{ + GF_List *loaders; +} SEILoadCtx; + + +static GF_Err seiload_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + SEILoadCtx *ctx = (SEILoadCtx *) gf_filter_get_udta(filter); + GF_FilterPid *opid = gf_filter_pid_get_udta(pid); + GF_SEILoader *loader = opid ? gf_filter_pid_get_udta(opid) : NULL; + if (is_remove) { + if (loader) { + gf_filter_pid_set_udta(opid, NULL); + gf_list_del_item(ctx->loaders, loader); + gf_sei_loader_del(loader); + } + if (opid) gf_filter_pid_remove(opid); + return GF_OK; + } + if (!opid) { + opid = gf_filter_pid_new(filter); + loader = gf_sei_loader_new(); + gf_filter_pid_set_udta(pid, opid); + gf_filter_pid_set_udta(opid, loader); + gf_list_add(ctx->loaders, loader); + } + gf_filter_pid_copy_properties(opid, pid); + gf_filter_pid_set_property(opid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) ); + return gf_sei_init_from_pid(loader, opid); +} +static GF_Err seiload_process(GF_Filter *filter) +{ + u32 i, nb_eos=0, count = gf_filter_get_ipid_count(filter); + for (i=0; i<count; i++) { + GF_FilterPid *ipid = gf_filter_get_ipid(filter, i); + GF_FilterPid *opid = gf_filter_pid_get_udta(ipid); + GF_SEILoader *loader = opid ? gf_filter_pid_get_udta(opid) : NULL; + while (1) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ipid)) { + gf_filter_pid_set_eos(opid); + nb_eos++; + } + break; + } + if (loader->is_identity + || (gf_filter_pck_get_property(pck, GF_PROP_PCK_SEI_LOADED)!=NULL) + ) { + gf_filter_pck_forward(pck, opid); + } else { + GF_FilterPacket *opck = gf_filter_pck_new_ref(opid, 0, 0, pck); + gf_filter_pck_merge_properties(pck, opck); + gf_sei_load_from_packet(loader, opck); + gf_filter_pck_send(opck); + } + gf_filter_pid_drop_packet(ipid); + } + } + if (nb_eos==count) return GF_EOS; + return GF_OK; +} + + +static GF_Err seiload_reconfigure_output(GF_Filter *filter, GF_FilterPid *opid) +{ + const GF_PropertyValue *p = gf_filter_pid_caps_query(opid, GF_PROP_PID_SEI_LOADED); + if (p) return GF_OK; + return GF_NOT_SUPPORTED; +} + +static GF_Err seiload_initialize(GF_Filter *filter) +{ + SEILoadCtx *ctx = (SEILoadCtx *) gf_filter_get_udta(filter); + ctx->loaders = gf_list_new(); + return GF_OK; +} +static void seiload_finalize(GF_Filter *filter) +{ + SEILoadCtx *ctx = (SEILoadCtx *) gf_filter_get_udta(filter); + while (gf_list_count(ctx->loaders)) { + GF_SEILoader *pctx = gf_list_pop_back(ctx->loaders); + gf_sei_loader_del(pctx); + } + gf_list_del(ctx->loaders); +} + +#define OFFS(_n) #_n, offsetof(SEILoadCtx, _n) +static GF_FilterArgs SEILoadArgs = +{ + {0} +}; + +static const GF_FilterCapability SEILoadCaps = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_BOOL(GF_CAPFLAG_RECONFIG, GF_PROP_PID_SEI_LOADED, GF_TRUE), +}; + +GF_FilterRegister SEILoadRegister = { + .name = "seiload", + GF_FS_SET_DESCRIPTION("SEI message loader") + GF_FS_SET_HELP("This filter loads known inband metadata as packet properties\n" + ) + .private_size = sizeof(SEILoadCtx), + .max_extra_pids = 0xFFFFFFFF, + //only allow explicit loading or as PID adaptation filter: since the caps allow any codec and any stream type, the filter could + //otherwise get elected in place of a transcoding chain + .flags = GF_FS_REG_EXPLICIT_ONLY, + .args = SEILoadArgs, + SETCAPS(SEILoadCaps), + .initialize = seiload_initialize, + .finalize = seiload_finalize, + .configure_pid = seiload_configure_pid, + .process = seiload_process, + .reconfigure_output = seiload_reconfigure_output, + .hint_class_type = GF_FS_CLASS_STREAM +}; + +const GF_FilterRegister *seiload_register(GF_FilterSession *session) +{ + return (const GF_FilterRegister *) &SEILoadRegister; +} +#else +const GF_FilterRegister *seiload_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_SEILOAD
View file
gpac-2.4.0.tar.gz/src/filters/tileagg.c -> gpac-26.02.0.tar.gz/src/filters/tileagg.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / tile aggregrator filter @@ -574,7 +574,7 @@ GF_FilterRegister TileAggRegister = { .name = "tileagg", - GF_FS_SET_DESCRIPTION("HEVC tile aggregator") + GF_FS_SET_DESCRIPTION("HEVC Tile aggregator") GF_FS_SET_HELP("This filter aggregates a set of split tiled HEVC streams (`hvt1` or `hvt2` in ISOBMFF) into a single HEVC stream.") .private_size = sizeof(GF_TileAggCtx), .flags = GF_FS_REG_DYNAMIC_REUSE, @@ -586,6 +586,7 @@ .process = tileagg_process, .process_event = tileagg_process_event, .max_extra_pids = (u32) (-1), + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *tileagg_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/tilesplit.c -> gpac-26.02.0.tar.gz/src/filters/tilesplit.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2020-2023 + * Copyright (c) Telecom ParisTech 2020-2024 * All rights reserved * * This file is part of GPAC / tile splitting filter @@ -562,7 +562,7 @@ GF_FilterRegister TileSplitRegister = { .name = "tilesplit", .flags = GF_FS_REG_EXPLICIT_ONLY, - GF_FS_SET_DESCRIPTION("HEVC tile bitstream splitter") + GF_FS_SET_DESCRIPTION("HEVC Tile splitter") GF_FS_SET_HELP("This filter splits an HEVC tiled stream into tiled HEVC streams (`hvt1` or `hvt2` in ISOBMFF)." "\n" "The filter will move to passthrough mode if the bitstream is not tiled.\n" @@ -585,6 +585,7 @@ .configure_pid = tilesplit_configure_pid, #endif .process = tilesplit_process, + .hint_class_type = GF_FS_CLASS_STREAM };
View file
gpac-2.4.0.tar.gz/src/filters/tssplit.c -> gpac-26.02.0.tar.gz/src/filters/tssplit.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom Paris 2019-2023 + * Copyright (c) Telecom Paris 2019-2024 * All rights reserved * * This file is part of GPAC / MPEG Transport Stream splitter filter @@ -39,6 +39,12 @@ u32 pmt_pid; u8 pat_pck192; u32 pat_pck_size; + u64 last_pcr_plus_one, init_clock, init_pcr; + u32 pcr_id; + Bool start_sent; + + //when restamping, gather tune-in packets before first PCR + GF_FilterPacket *init_pck; u8 *pck_buffer; u32 nb_pck; @@ -52,6 +58,9 @@ s32 mux_id; Bool avonly; u32 nb_pack; + Bool gendts; + Bool kpad; + Bool rt; //internal GF_Filter *filter; @@ -63,15 +72,63 @@ u8 tsbuf192; GF_BitStream *bsw; - + GF_M2TSSplit_SPTS *out; + + u64 filesize; + u64 process_clock; + u32 resched_next; + u32 ts_pck_size; + GF_Fraction64 duration; + Bool initial_play_done; } GF_M2TSSplitCtx; +static void m2tssplit_on_event(struct tag_m2ts_demux *ts, u32 evt_type, void *par); - -void m2tssplit_send_packet(GF_M2TSSplitCtx *ctx, GF_M2TSSplit_SPTS *stream, u8 *data, u32 size) +void m2tssplit_send_packet(GF_M2TSSplitCtx *ctx, GF_M2TSSplit_SPTS *stream, u8 *data, u32 size, u64 pcr_plus_one) { u8 *buffer; GF_FilterPacket *pck; + + if (ctx->gendts) { + //aggregate everything until we have a PCR + if (!stream->last_pcr_plus_one && !pcr_plus_one && data) { + if (stream->init_pck) + gf_filter_pck_expand(stream->init_pck, size, NULL, &buffer, NULL); + else + stream->init_pck = gf_filter_pck_new_alloc(stream->opid, size, &buffer); + + if (buffer) + memcpy(buffer, data, size); + return; + } + if (pcr_plus_one) { + if (!stream->last_pcr_plus_one && ctx->rt) { + stream->init_clock = gf_sys_clock_high_res(); + stream->init_pcr = pcr_plus_one; + } + stream->last_pcr_plus_one = pcr_plus_one; + if (ctx->rt) { + u64 pck_time = gf_timestamp_rescale(pcr_plus_one - stream->init_pcr, 27000000, 1000000); + pck_time += stream->init_clock; + u32 next_time = 0; + if (pck_time > ctx->process_clock) + next_time = (u32)(pck_time - ctx->process_clock); + if (!ctx->resched_next || (ctx->resched_next > next_time)) + ctx->resched_next = next_time; + } + } + if (stream->init_pck) { + gf_filter_pck_set_framing(stream->init_pck, !stream->start_sent, GF_FALSE); + stream->start_sent = GF_TRUE; + gf_filter_pck_set_dts(stream->init_pck, stream->last_pcr_plus_one-1); + gf_filter_pck_set_cts(stream->init_pck, stream->last_pcr_plus_one-1); + gf_filter_pck_send(stream->init_pck); + stream->init_pck = NULL; + } + } else { + pcr_plus_one=0; + } + if (ctx->nb_pack) { assert (stream->nb_pck<ctx->nb_pack); if (data) { @@ -81,35 +138,134 @@ return; } } - u32 osize = size*stream->nb_pck; + if (size) + ctx->ts_pck_size = size; + else + size = ctx->ts_pck_size; + + u32 osize = size * stream->nb_pck; pck = gf_filter_pck_new_alloc(stream->opid, osize, &buffer); if (pck) { - gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_framing(pck, !stream->start_sent, GF_FALSE); + stream->start_sent = GF_TRUE; memcpy(buffer, stream->pck_buffer, osize); + + if (pcr_plus_one) stream->last_pcr_plus_one = pcr_plus_one; + if (stream->last_pcr_plus_one) { + gf_filter_pck_set_dts(pck, stream->last_pcr_plus_one-1); + gf_filter_pck_set_cts(pck, stream->last_pcr_plus_one-1); + } gf_filter_pck_send(pck); } stream->nb_pck = 0; - } else { - pck = gf_filter_pck_new_alloc(stream->opid, size, &buffer); - if (pck) { - gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); - memcpy(buffer, data, size); - gf_filter_pck_send(pck); + return; + } + + pck = gf_filter_pck_new_alloc(stream->opid, size, &buffer); + if (pck) { + gf_filter_pck_set_framing(pck, !stream->start_sent, GF_FALSE); + stream->start_sent = GF_TRUE; + memcpy(buffer, data, size); + if (pcr_plus_one) stream->last_pcr_plus_one = pcr_plus_one; + if (stream->last_pcr_plus_one) { + gf_filter_pck_set_dts(pck, stream->last_pcr_plus_one-1); + gf_filter_pck_set_cts(pck, stream->last_pcr_plus_one-1); } + gf_filter_pck_send(pck); } } void m2tssplit_flush(GF_M2TSSplitCtx *ctx) { u32 i; - if (!ctx->nb_pack) return; - for (i=0; i<gf_list_count(ctx->streams); i++ ) { GF_M2TSSplit_SPTS *stream = gf_list_get(ctx->streams, i); - if (stream->opid && stream->nb_pck) - m2tssplit_send_packet(ctx, stream, NULL, 0); + if (!stream->opid) continue; + if (stream->nb_pck) + m2tssplit_send_packet(ctx, stream, NULL, 0, 0); + gf_filter_pid_set_eos(stream->opid); + } +} + +typedef struct +{ + GF_M2TSSplitCtx *ctx; + u64 first_pcr; + u32 first_pcr_pid; + u32 first_pcr_pck_num; + u64 last_pcr; + u32 pck_num; + Bool abort; +} M2TSDurProber; + +static void m2tssplit_on_event_duration_probe(GF_M2TS_Demuxer *ts, u32 evt_type, void *param) +{ + M2TSDurProber *prober = (M2TSDurProber *) ts->user; + + if (evt_type != GF_M2TS_EVT_PCK) return; + GF_M2TS_TSPCK *tspck = param; + if (!tspck->pcr_plus_one || prober->abort) return; + if (prober->first_pcr_pid && (prober->first_pcr_pid != tspck->pid)) return; + + if (tspck->pcr_plus_one && (tspck->pcr_plus_one < prober->first_pcr)) { + prober->first_pcr_pid = 0; + } + if (!prober->first_pcr_pid) { + prober->first_pcr_pid = tspck->pid; + prober->first_pcr = tspck->pcr_plus_one; + prober->first_pcr_pck_num = ts->pck_number; + return; + } + prober->last_pcr = tspck->pcr_plus_one; + prober->pck_num = ts->pck_number; + if ((tspck->pcr_plus_one < prober->first_pcr) || (tspck->pcr_plus_one - prober->first_pcr > 5*27000000)) { + prober->abort = GF_TRUE; + } +} + +void m2ts_split_estimate_duration(GF_M2TSSplitCtx *ctx, GF_FilterPid *pid) +{ + if (ctx->duration.num) return; + + const GF_PropertyValue *p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + return; } + FILE *stream = gf_fopen(p->value.string, "rb"); + ctx->ipid = pid; + + if (!stream) return; + + M2TSDurProber prober; + memset(&prober, 0, sizeof(M2TSDurProber)); + prober.ctx = ctx; + ctx->dmx->seek_mode = GF_TRUE; + ctx->dmx->user = &prober; + ctx->dmx->on_event = m2tssplit_on_event_duration_probe; + while (!gf_feof(stream)) { + char buf1880; + u32 nb_read = (u32) gf_fread(buf, 1880, stream); + gf_m2ts_process_data(ctx->dmx, buf, nb_read); + if (prober.abort) break; + } + ctx->filesize = gf_fsize(stream); + if (prober.last_pcr > prober.first_pcr) { + u64 dur = prober.last_pcr - prober.first_pcr; + u32 size = (prober.pck_num - prober.first_pcr_pck_num) * (ctx->dmx->prefix_present ? 192 : 188); + ctx->duration.num = gf_timestamp_rescale(dur, size, ctx->filesize); + } else { + ctx->duration.num = -1; + } + ctx->duration.den = 27000000; + gf_fclose(stream); + + GF_M2TSRawMode mode = ctx->dmx->raw_mode; + gf_m2ts_demux_del(ctx->dmx); + ctx->dmx = gf_m2ts_demux_new(); + ctx->dmx->raw_mode = mode; + ctx->dmx->user = ctx; + ctx->dmx->on_event = m2tssplit_on_event; } GF_Err m2tssplit_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) @@ -130,29 +286,107 @@ if (! gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; + if (!ctx->ipid) + m2ts_split_estimate_duration(ctx, pid); + ctx->ipid = pid; + if (ctx->dmx->raw_mode==GF_M2TS_RAW_FORWARD) { + GF_M2TSSplit_SPTS *stream; + GF_SAFEALLOC(stream, GF_M2TSSplit_SPTS); + if (!stream) return GF_OUT_OF_MEM; + if (ctx->nb_pack) + stream->pck_buffer = gf_malloc(sizeof(char) * ctx->nb_pack * (ctx->dmx->prefix_present ? 192 : 188) ); + + gf_list_add(ctx->streams, stream); + + stream->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(stream->opid, pid); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(27000000) ); + if (ctx->duration.num) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK) ); + } + ctx->out = stream; + } return GF_OK; } static Bool m2tssplit_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { -// GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + u64 file_pos; + u32 i; + GF_FilterEvent fevt; + GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); switch (evt->base.type) { case GF_FEVT_PLAY: - //cancel event + //cancel event if not file-based, mux or duation unknown + if ((gf_list_count(ctx->streams)>1) || !ctx->filesize || !ctx->duration.num) + return GF_TRUE; + + if (evt->play.hint_start_offset || evt->play.hint_end_offset) { + file_pos = evt->play.hint_start_offset; + } else { + file_pos = (u64) (ctx->filesize * evt->play.start_range); + file_pos *= ctx->duration.den; + file_pos /= ctx->duration.num; + if (file_pos > ctx->filesize) return GF_TRUE; + } + //round down to packet boundary + file_pos /= ctx->dmx->prefix_present ? 192 : 188; + file_pos *= ctx->dmx->prefix_present ? 192 : 188; + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) return GF_TRUE; + } + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); return GF_TRUE; case GF_FEVT_STOP: + if (gf_list_count(ctx->streams)>1) + return GF_TRUE; + + //reset streams + for (i=0;i<gf_list_count(ctx->streams); i++) { + GF_M2TSSplit_SPTS *st = gf_list_get(ctx->streams, i); + st->nb_pck = 0; + //reset clock otherwise we would dispatch first packets before PCR with the previous PCR + st->last_pcr_plus_one = 0; + if (st->init_pck) { + gf_filter_pck_discard(st->init_pck); + st->init_pck = NULL; + } + } + gf_m2ts_reset_parsers(ctx->dmx); return GF_FALSE; case GF_FEVT_SET_SPEED: //cancel event return GF_TRUE; + case GF_FEVT_NETWORK_HINT: + if (evt->net_hint.mtu_size) { + u32 new_pack = evt->net_hint.mtu_size / (ctx->dmx->prefix_present ? 194 : 188); + if (!new_pack) new_pack = 1; + if (new_pack==ctx->nb_pack) return GF_TRUE; + + for (i=0;i<gf_list_count(ctx->streams); i++) { + GF_M2TSSplit_SPTS *st = gf_list_get(ctx->streams, i); + if (st->nb_pck>=new_pack) + m2tssplit_send_packet(ctx, st, NULL, 0, 0); + + st->pck_buffer = gf_realloc(st->pck_buffer, new_pack * (ctx->dmx->prefix_present ? 192 : 188) ); + } + ctx->nb_pack = new_pack; + } + return GF_TRUE; default: break; } - //by default don't cancel event - to rework once we have downloading in place return GF_FALSE; } @@ -170,14 +404,21 @@ } data = gf_filter_pck_get_data(pck, &data_size); if (data) { + if (ctx->rt) { + ctx->process_clock = gf_sys_clock_high_res(); + } gf_m2ts_process_data(ctx->dmx, (u8 *)data, data_size); } gf_filter_pid_drop_packet(ctx->ipid); + if (ctx->resched_next) { + gf_filter_ask_rt_reschedule(filter, ctx->resched_next); + ctx->resched_next = 0; + } return GF_OK; } -void m2tssplit_on_event(struct tag_m2ts_demux *ts, u32 evt_type, void *par) +static void m2tssplit_on_event(struct tag_m2ts_demux *ts, u32 evt_type, void *par) { u32 i; GF_M2TSSplitCtx *ctx = ts->user; @@ -338,6 +579,8 @@ case GF_M2TS_SYSTEMS_MPEG4_PES: case GF_M2TS_METADATA_PES: case GF_M2TS_METADATA_ID3_HLS: + case GF_M2TS_METADATA_ID3_KLVA: + case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: known_streams++; break; default: @@ -356,7 +599,9 @@ gf_filter_pid_set_property(stream->opid, GF_PROP_PID_MIME, &PROP_STRING("video/mpeg-2")); gf_filter_pid_set_property(stream->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("ts")); gf_filter_pid_set_property(stream->opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(prog->number)); - + if (ctx->duration.num) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); + } GF_FilterPacket *pck = gf_filter_pck_new_alloc(stream->opid, stream->pat_pck_size, &buffer); if (pck) { gf_filter_pck_set_framing(pck, GF_TRUE, GF_FALSE); @@ -370,17 +615,30 @@ if (evt_type==GF_M2TS_EVT_PCK) { GF_M2TS_TSPCK *tspck = par; Bool do_fwd; - GF_M2TSSplit_SPTS *stream = tspck->stream ? tspck->stream->program->user : NULL; + GF_M2TSSplit_SPTS *stream; + if (ctx->out) + stream = ctx->out; + else + stream = tspck->stream ? tspck->stream->program->user : NULL; + + if (ctx->gendts && tspck->pcr_plus_one) { + if (!stream->pcr_id) + stream->pcr_id = tspck->pid; + else if (tspck->pid!=stream->pcr_id) + tspck->pcr_plus_one = 0; + } if (stream) { if (!stream->opid) return; + if (!ctx->kpad && (tspck->pid==0x1FFF)) + return; if (ctx->dmx->prefix_present) { u8 *data = tspck->data; data -= 4; - m2tssplit_send_packet(ctx, stream, data, 192); + m2tssplit_send_packet(ctx, stream, data, 192, tspck->pcr_plus_one); } else { - m2tssplit_send_packet(ctx, stream, tspck->data, 188); + m2tssplit_send_packet(ctx, stream, tspck->data, 188, tspck->pcr_plus_one); } return; } @@ -413,9 +671,9 @@ if (ctx->dmx->prefix_present) { u8 *data = tspck->data; data -= 4; - m2tssplit_send_packet(ctx, stream, data, 192); + m2tssplit_send_packet(ctx, stream, data, 192, 0); } else { - m2tssplit_send_packet(ctx, stream, tspck->data, 188); + m2tssplit_send_packet(ctx, stream, tspck->data, 188, 0); } } } @@ -428,12 +686,15 @@ ctx->streams = gf_list_new(); ctx->dmx = gf_m2ts_demux_new(); ctx->dmx->on_event = m2tssplit_on_event; - ctx->dmx->split_mode = GF_TRUE; + ctx->dmx->raw_mode = GF_M2TS_RAW_SPLIT; ctx->dmx->user = ctx; ctx->filter = filter; ctx->bsw = gf_bs_new(ctx->tsbuf, 192, GF_BITSTREAM_WRITE); if (ctx->nb_pack<=1) ctx->nb_pack = 0; + + if (ctx->rt) + ctx->gendts = GF_TRUE; return GF_OK; } @@ -465,7 +726,9 @@ { OFFS(mux_id), "set initial ID of output mux; the first program will use mux_id, the second mux_id+1, etc. If not set, this value will be set to sourceMuxId*255", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, { OFFS(avonly), "do not forward programs with no AV component", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(nb_pack), "pack N packets before sending", GF_PROP_UINT, "10", NULL, GF_FS_ARG_HINT_ADVANCED}, - + { OFFS(gendts), "generate timestamps on output packets based on PCR", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(kpad), "keep padding (null) TS packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(rt), "enable real-time regulation", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, {0} }; @@ -473,7 +736,7 @@ GF_FilterRegister M2TSSplitRegister = { .name = "tssplit", - GF_FS_SET_DESCRIPTION("MPEG Transport Stream splitter") + GF_FS_SET_DESCRIPTION("MPEG-2 TS splitter") GF_FS_SET_HELP("This filter splits an MPEG-2 transport stream into several single program transport streams.\n" "Only the PAT table is rewritten, other tables (PAT, PMT) and streams (PES) are forwarded as is.\n" "If -dvb() is set, global DVB tables of the input multiplex are forwarded to each output mux; otherwise these tables are discarded.") @@ -486,6 +749,7 @@ .configure_pid = m2tssplit_configure_pid, .process = m2tssplit_process, .process_event = m2tssplit_process_event, + .hint_class_type = GF_FS_CLASS_STREAM }; const GF_FilterRegister *tssplit_register(GF_FilterSession *session) @@ -493,10 +757,64 @@ return &M2TSSplitRegister; } +static const GF_FilterCapability M2TSGenDTSCaps = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_FILE_EXT, "ts|m2t|mts|dmb|trp"), + CAP_STRING(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_MIME, "video/mpeg-2|video/mp2t|video/mpeg"), + CAP_UINT(GF_CAPS_OUTPUT|GF_CAPFLAG_PRESENT, GF_PROP_PID_TIMESCALE, 0) +}; + + +#define OFFS(_n) #_n, offsetof(GF_M2TSSplitCtx, _n) +static const GF_FilterArgs M2TSGenDTSArgs = +{ + { OFFS(nb_pack), "pack N packets before sending", GF_PROP_UINT, "10", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(kpad), "keep padding (null) TS packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(rt), "enable real-time regulation", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +GF_Err m2ts_gendts_initialize(GF_Filter *filter) +{ + GF_Err e = m2tssplit_initialize(filter); + if (e) return e; + GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + ctx->gendts = GF_TRUE; + ctx->dmx->raw_mode = GF_M2TS_RAW_FORWARD; + return GF_OK; +} + + +GF_FilterRegister M2TSRestampRegister = { + .name = "tsgendts", + GF_FS_SET_DESCRIPTION("MPEG-2 TS timestamper") + GF_FS_SET_HELP("This filter restamps input MPEG-2 transport stream based on PCR.\n") + .flags = GF_FS_REG_HIDE_WEIGHT, + .private_size = sizeof(GF_M2TSSplitCtx), + .initialize = m2ts_gendts_initialize, + .finalize = m2tssplit_finalize, + .args = M2TSGenDTSArgs, + SETCAPS(M2TSGenDTSCaps), + .configure_pid = m2tssplit_configure_pid, + .process = m2tssplit_process, + .process_event = m2tssplit_process_event, + .hint_class_type = GF_FS_CLASS_STREAM +}; + +const GF_FilterRegister *tsgendts_register(GF_FilterSession *session) +{ + return &M2TSRestampRegister; +} + #else const GF_FilterRegister *tssplit_register(GF_FilterSession *session) { return NULL; } +const GF_FilterRegister *tsgendts_register(GF_FilterSession *session) +{ + return NULL; +} #endif /*GPAC_DISABLE_MPEG2TS*/
View file
gpac-2.4.0.tar.gz/src/filters/ttml_conv.c -> gpac-26.02.0.tar.gz/src/filters/ttml_conv.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022-2023 + * Copyright (c) Telecom ParisTech 2022-2024 * All rights reserved * * This file is part of GPAC / TTML to SRT converter filter @@ -81,7 +81,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); ctx->delay = p ? p->value.longsint : 0; - gf_filter_pid_set_property(pid, GF_PROP_PID_DELAY, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DELAY, NULL); return GF_OK; } @@ -323,7 +323,8 @@ .configure_pid = ttmlconv_configure_pid, .process = ttmlconv_process, //lower priority so that we always favor ttml2srt when converting ttml to tx3g - .priority = 128 + .priority = 128, + .hint_class_type = GF_FS_CLASS_SUBTITLE }; @@ -363,7 +364,8 @@ .finalize = ttmlconv_finalize, SETCAPS(TTMLConv2Caps), .configure_pid = ttmlconv_configure_pid, - .process = ttmlconv_process + .process = ttmlconv_process, + .hint_class_type = GF_FS_CLASS_SUBTITLE };
View file
gpac-2.4.0.tar.gz/src/filters/unframer.c -> gpac-26.02.0.tar.gz/src/filters/unframer.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom Paris 2022-2023 + * Copyright (c) Telecom Paris 2022-2024 * All rights reserved * * This file is part of GPAC / unframer filter @@ -113,7 +113,7 @@ const GF_FilterRegister UnframerRegister = { .name = "unframer", - GF_FS_SET_DESCRIPTION("Stream unframer") + GF_FS_SET_DESCRIPTION("Stream rewriter") GF_FS_SET_HELP("This filter is used to force reframing of input sources using the same internal framing as GPAC (e.g. ISOBMFF) but with broken framing or signaling.\n" "EX gpac -i src.mp4 unframer -o dst.mp4\n" "This will:\n" @@ -126,6 +126,7 @@ SETCAPS(UnframerCaps), .configure_pid = unframer_configure_pid, .process = unframer_process, + .hint_class_type = GF_FS_CLASS_TOOL }; const GF_FilterRegister *unframer_register(GF_FilterSession *session)
View file
gpac-2.4.0.tar.gz/src/filters/unit_test_filter.c -> gpac-26.02.0.tar.gz/src/filters/unit_test_filter.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / unit test filters @@ -968,7 +968,8 @@ .initialize = utfilter_initialize, .finalize = ut_filter_finalize, .process = ut_filter_process_source, - .update_arg = ut_filter_update_arg + .update_arg = ut_filter_update_arg, + .hint_class_type = GF_FS_CLASS_TOOL };
View file
gpac-26.02.0.tar.gz/src/filters/unittests
Added
+(directory)
View file
gpac-26.02.0.tar.gz/src/filters/unittests/ut_dec_cc.c
Added
@@ -0,0 +1,166 @@ +#include "tests.h" +#include "../dec_cc.c" +#include "../../filter_core/filter_session.h" // GF_FilterPacket + + +#ifdef GPAC_HAS_LIBCAPTION +static GF_Err pck_truncate(GF_FilterPacket *pck, u32 size) +{ + pck->data_length = size; + return GF_OK; +} + +static GF_FilterPacket* pck_new_alloc(GF_FilterPid *pid, u32 data_size, u8 **data) +{ + GF_FilterPacket *pck; + GF_SAFEALLOC(pck, GF_FilterPacket); + pck->data = gf_malloc(data_size); + pck->pck = pck; + pck->data_length = data_size; + *data = pck->data; + pck->filter_owns_mem = 0; + return pck; +} +#endif + + +const char* txt = "GP\nA C \n1 2"; + +static void ccdec_test_template(int agg, Bool text_with_overlaps, GF_Err (*pck_send)(GF_FilterPacket *pck)) +{ +#ifdef GPAC_HAS_LIBCAPTION + CCDecCtx ctx = {0}; + ctx.agg = agg; + + ctx.pck_send = pck_send; + ctx.pck_truncate = pck_truncate; + ctx.pck_new_alloc = pck_new_alloc; + + for (u32 i=0; i<strlen(txt); ++i) { + //we write to ctx.txtdata+ctx.txtlen like dec_cc.c does + if (text_with_overlaps) { + strncpy(ctx.txtdata+ctx.txtlen, txt, i+1); + text_aggregate_and_post(&ctx, i+1, i); + } else { + assert_equal(ctx.txtlen, i, "%u"); + strncpy(ctx.txtdata+ctx.txtlen, txt+i, 1); + text_aggregate_and_post(&ctx, 1, i); + } + } + + //termination calls + ccdec_flush(&ctx); + pck_send(NULL); +#endif // GPAC_HAS_LIBCAPTION +} + + +static GF_Err pck_send_default(GF_FilterPacket *pck) +{ + static int calls = 0; + static const char* expected = { "G", "GP", "GP\n", "GP\nA", "GP\nA ", "GP\nA C", "GP\nA C ", "GP\nA C \n", "GP\nA C \n1", "GP\nA C \n1 ", "GP\nA C \n1 2" }; + const int num_expected = sizeof(expected)/sizeof(const char*); + + if (!pck) { + assert_equal(calls, num_expected, "%d"); + return GF_OK; + } + if (calls >= num_expected) + assert_true(0); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal_str(data, expectedcalls); + gf_free((u8*)data); + gf_free(pck); + calls++; + assert_equal(size, calls, "%u"); + return GF_OK; +} + +unittest(ccdec_default) +{ + ccdec_test_template(0, GF_TRUE, pck_send_default); +} + + +static GF_Err pck_send_aggregation(GF_FilterPacket *pck) +{ + static int calls = 0; + static const char* expected = { "GP\n", "GP\nA", "GP\nA C", "GP\nA C \n", "GP\nA C \n1", "GP\nA C \n1 2" }; + const int num_expected = sizeof(expected)/sizeof(const char*); + + if (!pck) { + assert_equal(calls, num_expected, "%d"); + calls = 0; + return GF_OK; + } + if (calls >= num_expected) + assert_true(0); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal_str(data, expectedcalls); + gf_free((u8*)data); + gf_free(pck); + calls++; + return GF_OK; +} + +unittest(ccdec_aggregation) +{ + ccdec_test_template(1, GF_FALSE, pck_send_aggregation); +} + +unittest(ccdec_aggregation_overlaps) +{ + ccdec_test_template(1, GF_TRUE, pck_send_aggregation); +} + + +#ifdef GPAC_HAS_LIBCAPTION +static GF_Err pck_send_several_entries(GF_FilterPacket *pck) +{ + static int calls = 0; + static const char* expected = { "GPAC", "ROCKS" }; + const int num_expected = sizeof(expected)/sizeof(const char*); + + if (!pck) { + assert_equal(calls, num_expected, "%d"); + return GF_OK; + } + if (calls >= num_expected) + assert_true(0); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal_str(data, expectedcalls); + gf_free((u8*)data); + gf_free(pck); + calls++; + return GF_OK; +} +#endif + +unittest(ccdec_several_entries) +{ +#ifdef GPAC_HAS_LIBCAPTION + u64 ts = 0; + CCDecCtx ctx = {0}; + ctx.agg = 1; + + ctx.pck_send = pck_send_several_entries; + ctx.pck_truncate = pck_truncate; + ctx.pck_new_alloc = pck_new_alloc; + + strcpy(ctx.txtdata+ctx.txtlen, "GPAC"); + text_aggregate_and_post(&ctx, strlen("GPAC"), ts++); + + strcpy(ctx.txtdata+ctx.txtlen, "ROCKS"); + text_aggregate_and_post(&ctx, strlen("ROCKS"), ts++); + + //termination calls + ccdec_flush(&ctx); + ctx.pck_send(NULL); +#endif // GPAC_HAS_LIBCAPTION +}
View file
gpac-26.02.0.tar.gz/src/filters/unittests/ut_dec_scte35.c
Added
@@ -0,0 +1,445 @@ +#include "tests.h" +#include "../dec_scte35.c" +#include "../../filter_core/filter_session.h" // GF_FilterPacket + +static GF_FilterPacket* pck_new_alloc(GF_FilterPid *pid, u32 data_size, u8 **data) +{ + GF_FilterPacket *pck; + GF_SAFEALLOC(pck, GF_FilterPacket); + pck->data = gf_malloc(data_size); + pck->pck = pck; + pck->data_length = data_size; + *data = pck->data; + pck->filter_owns_mem = 0; + return pck; +} + +static GF_FilterPacket* pck_new_shared(GF_FilterPid *pid, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct) +{ + GF_FilterPacket *pck; + GF_SAFEALLOC(pck, GF_FilterPacket); + pck->pck = pck; + pck->data = (char*)data; + pck->data_length = data_size; + pck->destructor = destruct; + pck->filter_owns_mem = 1; + return pck; +} + +// ISOBMFF box sizes (used for identifying the eponym boxes) +#define EMIB_BOX_SIZE 105 +#define EMEB_BOX_SIZE 8 + +static u8 scte35_payload = { + 0xfc, 0x00, 0x28, // m2ts section header + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xf0, 0x14, 0x05, 0x4f, 0xff, 0xff, 0xf2, 0x7f, + 0xef, 0xfe, 0x00, 0x00, 0xe8, 0xbf, 0xfe, 0x00, + 0x00, 0x8f, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x08, 0x43, 0x55, 0x45, 0x49, 0x00, +}; + +#define SCTE35_PTS 59583ULL +#define SCTE35_DUR 36637U +#define SCTE35_LAST_EVENT_ID 1342177266U + +static u8 emeb_boxEMEB_BOX_SIZE = { + 0x00, 0x00, 0x00, 0x08, 0x65, 0x6d, 0x65, 0x62 +}; + +#define TIMESCALE 90000 // usual value +#define FPS 3 // allows to put event at the beginning, middle, and end + +#define SEND_VIDEO(num_frames) { \ + for (int i=0; i<num_frames; ++i) { \ + scte35dec_process_timing(&ctx, pts, TIMESCALE, TIMESCALE/FPS); \ + scte35dec_process_dispatch(&ctx, pts, TIMESCALE/FPS); \ + pts += TIMESCALE/FPS; \ + } \ + } + +#define SEND_EVENT() { \ + GF_PropertyValue emsg = { .type=GF_PROP_CONST_DATA, .value.data.ptr=scte35_payload, .value.data.size=sizeof(scte35_payload)}; \ + scte35dec_process_timing(&ctx, pts, TIMESCALE, SCTE35_DUR); \ + scte35dec_process_emsg(&ctx, emsg.value.data.ptr, emsg.value.data.size, pts); \ + if (ctx.mode != 1) scte35dec_process_dispatch(&ctx, pts, SCTE35_DUR); \ + pts = SCTE35_PTS + SCTE35_DUR; \ + } + +#define UT_SCTE35_INIT(pck_send_fct) \ + SCTE35DecCtx ctx = {0}; \ + assert_equal(scte35dec_initialize_internal(&ctx), GF_OK, "%d"); \ + ctx.pck_new_shared = pck_new_shared;\ + ctx.pck_new_alloc = pck_new_alloc; \ + ctx.pck_send = pck_send_fct; + +#define UT_SCTE35_PCK_SEND_FINALIZE() \ + if (!pck->filter_owns_mem) gf_free(pck->data); \ + gf_free(pck); \ + calls++; \ + return GF_OK; + +/*************************************/ + +unittest(scte35dec_safety) +{ + const int remainder = TIMESCALE % FPS; + assert_equal(remainder, 0, "%d"); +} + +/*************************************/ + +static GF_Err pck_send_no_event(GF_FilterPacket *pck) +{ + #define expected_calls 1 + static int calls = 0; + static u64 expected_dts expected_calls = { 0 }; + static u32 expected_dur expected_calls = { TIMESCALE*2 }; + static u32 expected_sizeexpected_calls = { EMEB_BOX_SIZE }; + + if (pck == NULL) { + // checks at termination + assert_equal(calls, expected_calls, "%d"); + return GF_OK; + } + + // dynamic checks + assert_less(calls, expected_calls, "%d"); + assert_equal(gf_filter_pck_get_dts(pck), expected_dtscalls, LLU); + assert_equal(gf_filter_pck_get_duration(pck), expected_durcalls, "%u"); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal(size, expected_sizecalls, "%u"); + assert_equal(size, EMEB_BOX_SIZE, "%u"); + assert_equal_mem(data, emeb_box, EMEB_BOX_SIZE); + + UT_SCTE35_PCK_SEND_FINALIZE(); + #undef expected_calls +} + +unittest(scte35dec_no_event) +{ + UT_SCTE35_INIT(pck_send_no_event); + u64 pts = 0; + + SEND_VIDEO(FPS*2); // send 2 seconds of heartbeat + + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); + ctx.pck_send(NULL); // final checks +} + +/*************************************/ + +static GF_Err pck_send_segmentation_no_event(GF_FilterPacket *pck) +{ + #define expected_calls 3 + static int calls = 0; + static u64 expected_dts expected_calls = { 0, TIMESCALE, 2*TIMESCALE }; + static u32 expected_dur expected_calls = { TIMESCALE, TIMESCALE, TIMESCALE }; + static u32 expected_sizeexpected_calls = { EMEB_BOX_SIZE, EMEB_BOX_SIZE, EMEB_BOX_SIZE }; + + if (pck == NULL) { + // checks at termination + assert_equal(calls, expected_calls, "%d"); + return GF_OK; + } + + // dynamic checks + assert_less(calls, expected_calls, "%d"); + assert_equal(gf_filter_pck_get_dts(pck), expected_dtscalls, LLU); + assert_equal(gf_filter_pck_get_duration(pck), expected_durcalls, "%u"); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal(size, expected_sizecalls, "%u"); + assert_equal(size, EMEB_BOX_SIZE, "%u"); + assert_equal_mem(data, emeb_box, EMEB_BOX_SIZE); + + UT_SCTE35_PCK_SEND_FINALIZE(); + #undef expected_calls +} + +unittest(scte35dec_segmentation_no_event) +{ + UT_SCTE35_INIT(pck_send_segmentation_no_event); + ctx.sampdur = (GF_Fraction){1, 1}; + u64 pts = 0; + + SEND_VIDEO(FPS*3); // send 3 seconds of heartbeat + + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); + ctx.pck_send(NULL); // final checks +} + +/*************************************/ + +unittest(scte35dec_splice_point_with_idr) +{ + SCTE35DecCtx ctx = {0}; + assert_equal(scte35dec_initialize_internal(&ctx), GF_OK, "%d"); + ctx.mode = 1; // passthru + u64 pts = 0; + + SEND_EVENT(); + assert_true(scte35dec_is_splice_point(&ctx, SCTE35_PTS)); + + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); +} + +/*************************************/ + +static GF_Err pck_send_simple(GF_FilterPacket *pck) +{ + #define expected_calls 3 + static int calls = 0; + static u64 expected_dts expected_calls = { 0, SCTE35_PTS, SCTE35_PTS+SCTE35_DUR }; + static u32 expected_dur expected_calls = { SCTE35_PTS, SCTE35_DUR, TIMESCALE }; + static u32 expected_sizeexpected_calls = { EMEB_BOX_SIZE, EMIB_BOX_SIZE, EMEB_BOX_SIZE }; + static s64 expected_event_pts_deltaexpected_calls = { 0, 0, 0 }; + static u32 expected_event_duration expected_calls = { 0, SCTE35_DUR, 0 }; + static u32 expected_event_id expected_calls = { 0, SCTE35_LAST_EVENT_ID, 0 }; + + if (pck == NULL) { + // checks at termination + assert_equal(calls, expected_calls, "%d"); + return GF_OK; + } + + // dynamic checks + assert_less(calls, expected_calls, "%d"); + assert_equal(gf_filter_pck_get_dts(pck), expected_dtscalls, LLU); + assert_equal(gf_filter_pck_get_duration(pck), expected_durcalls, "%u"); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal(size, expected_sizecalls, "%u"); + + switch(size) { + case EMEB_BOX_SIZE: + assert_equal(EMEB_BOX_SIZE, size, "%u"); + assert_equal_mem(data, emeb_box, EMEB_BOX_SIZE); + break; + case EMIB_BOX_SIZE: { + assert_less_equal((u32)sizeof(scte35_payload), size, "%u"); + assert_equal_mem(data + size - sizeof(scte35_payload), scte35_payload, sizeof(scte35_payload)); + + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + gf_bs_seek(bs, 16); + assert_equal(gf_bs_read_u64(bs), expected_event_pts_deltacalls, LLU); //presentation_time_delta + assert_equal(gf_bs_read_u32(bs), expected_event_durationcalls, "%u"); //event_duration + assert_equal(gf_bs_read_u32(bs), expected_event_idcalls, "%u"); //event_id + gf_bs_del(bs); + break; + } + default: assert_true(0); + } + + UT_SCTE35_PCK_SEND_FINALIZE(); + #undef expected_calls +} + +unittest(scte35dec_simple) +{ + UT_SCTE35_INIT(pck_send_simple); + u64 pts = 0; + + SEND_VIDEO(1); + pts = SCTE35_PTS; + SEND_EVENT(); + SEND_VIDEO(FPS); + + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); + ctx.pck_send(NULL); // final checks +} + +/*************************************/ + +static GF_Err pck_send_initial_delay(GF_FilterPacket *pck) +{ + #define expected_calls 7 + static int calls = 0; + static u64 expected_dts expected_calls = { 0, TIMESCALE/FPS, TIMESCALE/FPS*3/2, SCTE35_PTS, + TIMESCALE/FPS*2, TIMESCALE/FPS*3, SCTE35_PTS+SCTE35_DUR }; + static u32 expected_dur expected_calls = { TIMESCALE/FPS, TIMESCALE/FPS/2, SCTE35_PTS-TIMESCALE/FPS*3/2, TIMESCALE/FPS*2-SCTE35_PTS, + TIMESCALE/FPS, SCTE35_PTS+SCTE35_DUR-TIMESCALE/FPS*3, TIMESCALE/FPS*4-(SCTE35_PTS+SCTE35_DUR) }; + static u32 expected_sizeexpected_calls = { EMEB_BOX_SIZE, EMEB_BOX_SIZE, EMIB_BOX_SIZE, EMIB_BOX_SIZE, + EMIB_BOX_SIZE, EMIB_BOX_SIZE, EMEB_BOX_SIZE }; + + if (pck == NULL) { + // checks at termination + assert_equal(calls, expected_calls, "%d"); + return GF_OK; + } + + // dynamic checks + assert_less(calls, expected_calls, "%d"); + assert_equal(gf_filter_pck_get_dts(pck), expected_dtscalls, LLU); + assert_equal(gf_filter_pck_get_duration(pck), expected_durcalls, "%u"); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal(size, expected_sizecalls, "%u"); + + UT_SCTE35_PCK_SEND_FINALIZE(); + #undef expected_calls +} + +unittest(scte35dec_initial_delay) +{ + UT_SCTE35_INIT(pck_send_initial_delay); + ctx.sampdur = (GF_Fraction){1, FPS}; + u64 pts = 0; + + SEND_VIDEO(1); // video (1 frame) + pts=TIMESCALE/FPS*3/2; // introduce a delay before the first scte35 event + SEND_EVENT(); // scte35 event scheduled at pts=59583 w/ dur=36637 + + scte35dec_flush(&ctx); + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); + ctx.pck_send(NULL); // trigger final checks +} + +/*************************************/ + +static GF_Err pck_send_segmentation_beginning(GF_FilterPacket *pck) +{ + #define expected_calls 4 + static int calls = 0; + static u64 expected_dts expected_calls = { 0, SCTE35_PTS, TIMESCALE, SCTE35_PTS+SCTE35_DUR}; + static u32 expected_dur expected_calls = { SCTE35_PTS, TIMESCALE-SCTE35_PTS, SCTE35_PTS+SCTE35_DUR-TIMESCALE, 2*TIMESCALE-(SCTE35_PTS+SCTE35_DUR) }; + static u32 expected_sizeexpected_calls = { EMIB_BOX_SIZE, EMIB_BOX_SIZE, EMIB_BOX_SIZE, EMEB_BOX_SIZE }; + static s64 expected_event_pts_deltaexpected_calls = { SCTE35_PTS, 0, 0, 0 }; + static u32 expected_event_duration expected_calls = { SCTE35_DUR, SCTE35_DUR, SCTE35_PTS+SCTE35_DUR-TIMESCALE, 0 }; + static u32 expected_event_id expected_calls = { SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID, 0 }; + + if (pck == NULL) { + // checks at termination + assert_equal(calls, expected_calls, "%d"); + return GF_OK; + } + + // dynamic checks + assert_less(calls, expected_calls, "%d"); + assert_equal(gf_filter_pck_get_dts(pck), expected_dtscalls, LLU); + assert_equal(gf_filter_pck_get_duration(pck), expected_durcalls, "%u"); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal(size, expected_sizecalls, "%u"); + + switch(size) { + case EMEB_BOX_SIZE: + assert_equal(EMEB_BOX_SIZE, size, "%u"); + assert_equal_mem(data, emeb_box, EMEB_BOX_SIZE); + break; + case EMIB_BOX_SIZE: { + assert_less_equal((u32)sizeof(scte35_payload), size, "%u"); + assert_equal_mem(data + size - sizeof(scte35_payload), scte35_payload, sizeof(scte35_payload)); + + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + gf_bs_seek(bs, 16); + assert_equal(gf_bs_read_u64(bs), expected_event_pts_deltacalls, LLU); //presentation_time_delta + assert_equal(gf_bs_read_u32(bs), expected_event_durationcalls, "%u"); //event_duration + assert_equal(gf_bs_read_u32(bs), expected_event_idcalls, "%u"); //event_id + gf_bs_del(bs); + break; + } + default: assert_true(0); + } + + UT_SCTE35_PCK_SEND_FINALIZE(); + #undef expected_calls +} + +unittest(scte35dec_segmentation_beginning) +{ + UT_SCTE35_INIT(pck_send_segmentation_beginning); + ctx.sampdur = (GF_Fraction){1, 1}; + u64 pts = 0; + + SEND_EVENT(); // scte35 event scheduled for pts=59583 with dur=36637 + SEND_VIDEO(1); // video (1 frame) + + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); + ctx.pck_send(NULL); // trigger final checks +} + +/*************************************/ + +static GF_Err pck_send_segmentation_end(GF_FilterPacket *pck) +{ + #define expected_calls 6 + static int calls = 0; + //scte35 event at pts=30000 scheduled for pts=59583 with dur=36637, segment_dur=30000 + //v0:30000 emeb30000:59583 emib59583:60000 emib60000:90000 emib90000:96220 emeb96220:120000 + static u64 expected_dts expected_calls = { 0, TIMESCALE/FPS, SCTE35_PTS, + 2*TIMESCALE/FPS, 3*TIMESCALE/FPS, SCTE35_PTS+SCTE35_DUR }; + static u32 expected_dur expected_calls = { TIMESCALE/FPS, SCTE35_PTS-TIMESCALE/FPS, 2*TIMESCALE/FPS-SCTE35_PTS, + TIMESCALE/FPS, SCTE35_PTS+SCTE35_DUR-3*TIMESCALE/FPS, 4*TIMESCALE/FPS-SCTE35_PTS-SCTE35_DUR }; + static u32 expected_sizeexpected_calls = { EMEB_BOX_SIZE, EMIB_BOX_SIZE, EMIB_BOX_SIZE, EMIB_BOX_SIZE, EMIB_BOX_SIZE, EMEB_BOX_SIZE }; + static s64 expected_event_pts_deltaexpected_calls = { 0, SCTE35_PTS-TIMESCALE/FPS, 0, 0, 0, 0 }; + static u32 expected_event_duration expected_calls = { 0, SCTE35_DUR, SCTE35_DUR, SCTE35_DUR-(2*TIMESCALE/FPS-SCTE35_PTS), SCTE35_DUR-(3*TIMESCALE/FPS-SCTE35_PTS), 0 }; + static u32 expected_event_id expected_calls = { SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID, SCTE35_LAST_EVENT_ID }; + + if (pck == NULL) { + // checks at termination + assert_equal(calls, expected_calls, "%d"); + return GF_OK; + } + + // dynamic checks + assert_less(calls, expected_calls, "%d"); + assert_equal(gf_filter_pck_get_dts(pck), expected_dtscalls, LLU); + assert_equal(gf_filter_pck_get_duration(pck), expected_durcalls, "%u"); + + u32 size = 0; + const u8 *data = gf_filter_pck_get_data(pck, &size); + assert_equal(size, expected_sizecalls, "%u"); + + switch(size) { + case EMEB_BOX_SIZE: + assert_equal(EMEB_BOX_SIZE, size, "%u"); + assert_equal_mem(data, emeb_box, EMEB_BOX_SIZE); + break; + case EMIB_BOX_SIZE: { + assert_less_equal((u32)sizeof(scte35_payload), size, "%u"); + assert_equal_mem(data + size - sizeof(scte35_payload), scte35_payload, sizeof(scte35_payload)); + + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + gf_bs_seek(bs, 16); + assert_equal(gf_bs_read_u64(bs), expected_event_pts_deltacalls, LLU); //presentation_time_delta + assert_equal(gf_bs_read_u32(bs), expected_event_durationcalls, "%u"); //event_duration + assert_equal(gf_bs_read_u32(bs), expected_event_idcalls, "%u"); //event_id + gf_bs_del(bs); + break; + } + default: assert_true(0); + } + + UT_SCTE35_PCK_SEND_FINALIZE(); + #undef expected_calls +} + +unittest(scte35dec_short_segmentation_end) +{ + UT_SCTE35_INIT(pck_send_segmentation_end); + ctx.sampdur = (GF_Fraction){1, FPS}; + u64 pts = 0; + + SEND_VIDEO(1); // video (1 frame) + SEND_EVENT(); // scte35 event at "pts=1 frame" scheduled for pts=59583 with dur=36637 + + scte35dec_flush(&ctx); + scte35dec_flush(&ctx); // this test requires an additional segment + scte35dec_finalize_internal(&ctx); + ctx.pck_send(NULL); // trigger final checks +} + +/*************************************/
View file
gpac-2.4.0.tar.gz/src/filters/vcrop.c -> gpac-26.02.0.tar.gz/src/filters/vcrop.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2023 + * Copyright (c) Telecom ParisTech 2018-2024 * All rights reserved * * This file is part of GPAC / video cropping filter @@ -483,7 +483,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); if (p) pfmt = p->value.uint; p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p) sar = p->value.frac; + if (p && (p->value.frac.num>0)) sar = p->value.frac; else sar.den = sar.num = 1; if (!w || !h || !pfmt) { @@ -611,7 +611,7 @@ GF_FilterRegister VCropRegister = { .name = "vcrop", - GF_FS_SET_DESCRIPTION("Video crop") + GF_FS_SET_DESCRIPTION("Video cropper") GF_FS_SET_HELP("This filter is used to crop raw video data.") .private_size = sizeof(GF_VCropCtx), .flags = GF_FS_REG_EXPLICIT_ONLY|GF_FS_REG_ALLOW_CYCLIC, @@ -620,6 +620,7 @@ SETCAPS(VCropCaps), .process = vcrop_process, .finalize = vcrop_finalize, + .hint_class_type = GF_FS_CLASS_AV };
View file
gpac-2.4.0.tar.gz/src/filters/vflip.c -> gpac-26.02.0.tar.gz/src/filters/vflip.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Samir Mustapha - Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2019-2023 + * Copyright (c) Telecom ParisTech 2019-2024 * All rights reserved * * This file is part of GPAC / video flip filter @@ -29,10 +29,18 @@ #include <gpac/network.h> #ifndef GPAC_DISABLE_VFLIP + +GF_OPT_ENUM (GF_FlipMode, + VFLIP_OFF = 0, + VFLIP_VERT, + VFLIP_HORIZ, + VFLIP_BOTH, +); + typedef struct { //options - u32 mode; + GF_FlipMode mode; //internal data Bool initialized; @@ -57,13 +65,6 @@ } GF_VFlipCtx; -enum -{ - VFLIP_OFF = 0, - VFLIP_VERT, - VFLIP_HORIZ, - VFLIP_BOTH, -}; @@ -379,7 +380,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); if (p) pfmt = p->value.uint; p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p) sar = p->value.frac; + if (p && (p->value.frac.num>0)) sar = p->value.frac; else sar.den = sar.num = 1; if (!w || !h || !pfmt) { @@ -490,7 +491,7 @@ GF_FilterRegister VFlipRegister = { .name = "vflip", - GF_FS_SET_DESCRIPTION("Video flip") + GF_FS_SET_DESCRIPTION("Video flipper") GF_FS_SET_HELP("This filter flips uncompressed video frames vertically, horizontally, in both directions or no flip") .private_size = sizeof(GF_VFlipCtx), .flags = GF_FS_REG_EXPLICIT_ONLY|GF_FS_REG_ALLOW_CYCLIC, @@ -499,6 +500,7 @@ SETCAPS(VFlipCaps), .process = vflip_process, .finalize = vflip_finalize, + .hint_class_type = GF_FS_CLASS_AV };
View file
gpac-2.4.0.tar.gz/src/filters/write_generic.c -> gpac-26.02.0.tar.gz/src/filters/write_generic.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / generic stream to file filter @@ -33,27 +33,27 @@ #include <gpac/internal/isomedia_dev.h> -enum -{ +GF_OPT_ENUM (GF_DecoderConfigInsertMode, DECINFO_NO=0, DECINFO_FIRST, DECINFO_SAP, - DECINFO_AUTO -}; + DECINFO_AUTO, +); -enum -{ +GF_OPT_ENUM (GF_VttHeaderInjectionMode, VTTH_SINGLE=0, VTTH_SEG, VTTH_ALL, -}; +); typedef struct { //opts - Bool exporter, frame, split, merge_region; - u32 sstart, send, vtth; - u32 pfmt, afmt, decinfo; + Bool exporter, frame, split, merge_region, add_nl, rawb; + u32 sstart, send; + GF_VttHeaderInjectionMode vtth; + u32 pfmt, afmt; + GF_DecoderConfigInsertMode decinfo; GF_Fraction dur; //only one input pid declared @@ -91,20 +91,24 @@ GF_FilterPacket *ttml_dash_pck; - Bool dump_srt; + Bool dump_srt, srt_dump_forced; Bool need_ttxt_footer; u8 *write_buf; u32 write_alloc; Bool unframe_only; Bool vc1_ilaced; + Bool ttml_merger; + u64 ttml_cts_offset; // used to rewrite ttml timestamps over reconfigure (i.e. DASH period change) + u64 ttml_first_cts; + GF_FilterPacket *ttml_first_pck; } GF_GenDumpCtx; GF_Err writegen_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { u32 cid, chan, sr, w, h, stype, pf, sfmt, av1mode, nb_bps; - const char *name, *mimetype; + const char *name, *mimetype, *out_ext=NULL; char szExtGF_4CC_MSIZE, szCodecExt30, *sep; const GF_PropertyValue *p; GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); @@ -134,6 +138,8 @@ ctx->codecid = cid; if (!ctx->opid) { ctx->opid = gf_filter_pid_new(filter); + if (!ctx->opid) + return GF_OUT_OF_MEM; ctx->first = GF_TRUE; } @@ -145,6 +151,7 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); stype = p ? p->value.uint : 0; + if (stype!=GF_STREAM_TEXT) ctx->add_nl = GF_FALSE; p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); sr = p ? p->value.uint : 0; @@ -206,7 +213,8 @@ ctx->dump_srt = GF_FALSE; p = gf_filter_pid_caps_query(ctx->opid, GF_PROP_PID_FILE_EXT); if (p && p->value.string) { - if (!strcmp(p->value.string, "srt")) { + out_ext = p->value.string; + if (!strcmp(out_ext, "srt")) { ctx->dump_srt = GF_TRUE; strcpy(szCodecExt, "srt"); } @@ -273,9 +281,11 @@ if (ctx->decinfo == DECINFO_AUTO) ctx->decinfo = DECINFO_FIRST; - p = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED); - if (p && p->value.boolean) - ctx->dump_srt = GF_TRUE; + if (!out_ext || stricmp(out_ext, "txt")) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED); + if (p && p->value.boolean) + ctx->dump_srt = GF_TRUE; + } break; case GF_CODECID_TX3G: @@ -326,6 +336,10 @@ ctx->decinfo = DECINFO_FIRST; break; + case GF_CODECID_SUBS_SSA: + ctx->add_nl = GF_TRUE; + break; + case GF_CODECID_AV1: av1mode = 0; p = gf_filter_pid_get_property_str(ctx->ipid, "obu:mode"); @@ -490,12 +504,8 @@ //avoid creating a file when dumping individual samples if (ctx->split) { p = gf_filter_pid_get_property(pid, GF_PROP_PID_NB_FRAMES); - if (!p || (p->value.uint>1)) - gf_filter_pid_set_property(ctx->opid, GF_PROP_PCK_FILENUM, &PROP_UINT(0) ); - else + if (p && (p->value.uint<=1)) ctx->split = GF_FALSE; - } else if (ctx->frame) { - gf_filter_pid_set_property(ctx->opid, GF_PROP_PCK_FILENUM, &PROP_UINT(0) ); } p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); @@ -506,6 +516,16 @@ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(stype)); gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(cid)); } + if (ctx->ttml_merger) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NB_FRAMES, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + ctx->ttml_cts_offset = GF_FILTER_NO_TS; + } + + if (ctx->dump_srt && gf_opts_get_bool("core", "srt-forced")) + ctx->srt_dump_forced = GF_TRUE; + else + ctx->srt_dump_forced = GF_FALSE; gf_filter_pid_set_framing_mode(pid, GF_TRUE); return GF_OK; @@ -757,9 +777,11 @@ u32 j=0; GF_XMLAttribute *att2; while ( (att2 = gf_list_enum(n2->attributes, &j))) { - if (!strcmp(att1->name, att2->name) && !strcmp(att1->value, att2->value)) { - found = GF_TRUE; - break; + if (!strcmp(att1->name, att2->name)) { + if (!strcmp(att1->value, att2->value)) { + found = GF_TRUE; + break; + } } } if (!found) return GF_FALSE; @@ -921,6 +943,58 @@ return GF_OK; } + +//from filter txtin +u64 ttml_get_timestamp_ex(char *value, u32 tick_rate, u32 *ttml_fps_num, u32 *ttml_fps_den, u32 *ttml_sfps); + +static u64 ttml_get_timestamp(char *value) +{ + u32 ttml_fps_num, ttml_fps_den, ttml_sfps; + return ttml_get_timestamp_ex(value, 25, &ttml_fps_num, &ttml_fps_den, &ttml_sfps); +} + +// modifications in this function should be mirrored in load_text.c-ttml_rewrite_timestamp() +static void writegen_rewrite_timestamp_ttml(s64 ts_offset, GF_XMLAttribute *att, s64 *value) +{ + u64 v; + char szTS21; + u32 h, m, s, ms; + *value = ttml_get_timestamp(att->value); + if (!ts_offset) + return; + + *value += ts_offset; + v = (u64) (*value / 1000); + h = (u32) (v / 3600); + m = (u32) (v - h*3600) / 60; + s = (u32) (v - h*3600 - m*60); + ms = (*value) % 1000; + + snprintf(szTS, 20, "%02u:%02u:%02u.%03u", h, m, s, ms); + szTS20 = 0; + gf_free(att->value); + att->value = gf_strdup(szTS); + return; +} + +static void ttml_rewrite_timestamp(u64 offset, GF_List *p_attributes) +{ + u32 p_idx = 0; + GF_XMLAttribute *p_att; + + while ( (p_att = (GF_XMLAttribute*)gf_list_enum(p_attributes, &p_idx))) { + if (!strcmp(p_att->name, "begin")) { + s64 unused = 0; + writegen_rewrite_timestamp_ttml(offset, p_att, &unused); //Romain: we don't need &being, &end, &drop + } + if (!strcmp(p_att->name, "end")) { + s64 unused = 0; + writegen_rewrite_timestamp_ttml(offset, p_att, &unused); + } + } +} + + static GF_Err writegen_push_ttml(GF_GenDumpCtx *ctx, char *data, u32 data_size, GF_FilterPacket *in_pck) { const GF_PropertyValue *subs = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_SUBS); @@ -977,6 +1051,26 @@ } if (!ctx->ttml_root) { ctx->ttml_root = gf_xml_dom_detach_root(dom); + + // walk through the document to find <p> to rewrite timestamps + body_pck = ttml_get_body(root_pck); + if (body_pck) { + div_idx = 0; + nb_children = body_pck ? gf_list_count(body_pck->content) : 0; + for (k=0; k<nb_children; k++) { + GF_XMLNode *div_pck; + div_pck = gf_list_get(body_pck->content, k); + if (div_pck) { + u32 j=0; + while ( (p_pck = gf_list_enum(div_pck->content, &j)) ) { + if (p_pck->type) continue; + if (strcmp(p_pck->name, "p")) continue; + + ttml_rewrite_timestamp(ctx->ttml_cts_offset, p_pck->attributes); + } + } + } + } goto exit; } root_global = ctx->ttml_root; @@ -1008,7 +1102,7 @@ GF_XMLAttribute *div_reg = NULL; GF_XMLNode *div_global, *div_pck; div_pck = gf_list_get(body_pck->content, k); - if (div_pck->type) continue; + if (!div_pck || div_pck->type) continue; if (strcmp(div_pck->name, "div")) continue; if (ctx->merge_region) @@ -1053,6 +1147,9 @@ idx = gf_list_count(div_global->content); } + // rewrite the appended timestamp + ttml_rewrite_timestamp(ctx->ttml_cts_offset, p_pck->attributes); + i--; gf_list_rem(div_pck->content, i); if (!div_global->content) div_global->content = gf_list_new(); @@ -1066,7 +1163,6 @@ } } gf_list_insert(div_global->content, p_pck, idx); - } } @@ -1121,9 +1217,23 @@ gf_filter_pck_unref(ctx->ttml_dash_pck); ctx->ttml_dash_pck = NULL; } - gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); gf_xml_dom_node_del(ctx->ttml_root); ctx->ttml_root = NULL; + + if (ctx->ttml_first_pck) { + gf_filter_pck_merge_properties(ctx->ttml_first_pck, pck); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_dts(pck, ctx->ttml_first_cts); + gf_filter_pck_set_cts(pck, ctx->ttml_first_cts); + u64 last_cts = gf_filter_pck_get_cts(ctx->ttml_first_pck); + last_cts += gf_filter_pck_get_duration(ctx->ttml_first_pck); + gf_filter_pck_set_duration(pck, (u32) (last_cts - ctx->ttml_first_cts)); + + gf_filter_pck_unref(ctx->ttml_first_pck); + ctx->ttml_first_pck = NULL; + ctx->ttml_first_cts = 0; + } + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); return gf_filter_pck_send(pck); } @@ -1143,7 +1253,7 @@ #include <gpac/webvtt.h> -static void webvtt_timestamps_dump(GF_BitStream *bs, u64 start_ts, u64 end_ts, u32 timescale, Bool write_srt) +static void webvtt_timestamps_dump(GF_BitStream *bs, u64 start_ts, u64 end_ts, u32 timescale, Bool write_srt, Bool forced) { #ifndef GPAC_DISABLE_VTT char szTS200; @@ -1173,6 +1283,10 @@ } sprintf(szTS, "%02u:%02u%c%03u", end.min, end.sec, write_srt ? ',' : '.', end.ms); gf_bs_write_data(bs, szTS, (u32) strlen(szTS) ); + + if (write_srt && forced) { + gf_bs_write_data(bs, " !!!", 4); + } #endif } @@ -1326,7 +1440,7 @@ gf_dynstrcat(&y4m_hdr, szInfo, NULL); } p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_SAR); - if (p) { + if (p && (p->value.frac.num>0)) { sprintf(szInfo, " A%d:%d", p->value.frac.num, p->value.frac.den); gf_dynstrcat(&y4m_hdr, szInfo, NULL); } @@ -1377,8 +1491,21 @@ ctx->nb_bytes += 44; return GF_OK; } else if (ctx->ttml_agg) { - GF_Err e = writegen_push_ttml(ctx, data, pck_size, pck); ctx->first = GF_FALSE; + if (ctx->ttml_merger) { + if (!ctx->ttml_first_pck) { + ctx->ttml_first_cts = gf_filter_pck_get_cts(pck); + ctx->ttml_first_pck = pck; + gf_filter_pck_ref_props(&ctx->ttml_first_pck); + + // initialized once per reconfigure + if (ctx->ttml_cts_offset == GF_FILTER_NO_TS) + ctx->ttml_cts_offset = ctx->ttml_first_cts; + } else { + gf_filter_pck_merge_properties(pck, ctx->ttml_first_pck); + } + } + GF_Err e = writegen_push_ttml(ctx, data, pck_size, pck); if (e) { gf_filter_pid_drop_packet(ctx->ipid); return e; @@ -1434,6 +1561,8 @@ goto no_output; } empty_seg=GF_TRUE; + } else if (ctx->webvtt) { + empty_seg=GF_TRUE; } else { ctx->sample_num--; goto no_output; @@ -1444,6 +1573,8 @@ u64 end = start + gf_filter_pck_get_duration(pck); u32 timescale = gf_filter_pck_get_timescale(pck); Bool first = ctx->first; + Bool forced = GF_FALSE; + //in dash mode always inject the webvtt header for HLS if (ctx->dash_mode && (ctx->vtth!=VTTH_SINGLE) && gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM)) first = GF_TRUE; @@ -1477,6 +1608,12 @@ if (len && (p->value.stringlen-1!='\n')) gf_bs_write_data(ctx->bs, "\n", 1); } + if (ctx->srt_dump_forced) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FORCED_SUB); + if (p && p->value.boolean) { + forced = GF_TRUE; + } + } if (!empty_seg) { if (ctx->dump_srt) { @@ -1484,7 +1621,7 @@ sprintf(szCID, "%d\n", ctx->sample_num); gf_bs_write_data(ctx->bs, szCID, (u32) strlen(szCID)); } - webvtt_timestamps_dump(ctx->bs, start, end, timescale, ctx->dump_srt); + webvtt_timestamps_dump(ctx->bs, start, end, timescale, ctx->dump_srt, forced); p = gf_filter_pck_get_property_str(pck, "vtt_settings"); if (!ctx->dump_srt && p && p->value.string) { @@ -1512,7 +1649,7 @@ dst_pck = gf_filter_pck_new_ref(ctx->opid, 0, 0, pck); } if (!dst_pck) return GF_OUT_OF_MEM; - + gf_filter_pck_merge_properties(pck, dst_pck); //don't keep byte offset gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); @@ -1531,6 +1668,14 @@ gf_filter_pck_set_seek_flag(dst_pck, 0); gf_filter_pck_send(dst_pck); + if (ctx->add_nl) { + u8 *output; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, 1, &output); + output0 = '\n'; + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_send(dst_pck); + } + if (split && ctx->need_ttxt_footer) writegen_flush_ttxt(ctx); ctx->nb_bytes += pck_size; @@ -1621,6 +1766,14 @@ CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/x-ivf|video/av1"), {0}, + //we accept unframed AC4 without sync word and size + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AC4), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "ac4"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/x-ac4|audio/ac4"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), @@ -1817,6 +1970,12 @@ CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "x-subtitle/ttxt|subtitle/ttxt|text/ttxt"), {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SUBS_SSA), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "ssa|ass"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "x-subtitle/ssa"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_QCELP), CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "qcelp"), @@ -1951,7 +2110,7 @@ "- first: inserted on first packet\n" "- sap: inserted at each SAP\n" "- auto: selects between no and first based on media type", GF_PROP_UINT, "auto", "no|first|sap|auto", GF_FS_ARG_HINT_ADVANCED}, - { OFFS(split), "force one file per decoded frame", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(split), "force one file per frame", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(frame), "force single frame dump with no rewrite. In this mode, all codec types are supported", GF_PROP_BOOL, "false", NULL, 0}, { OFFS(sstart), "start number of frame to forward. If 0, all samples are forwarded", GF_PROP_UINT, "0", NULL, 0}, { OFFS(send), "end number of frame to forward. If less than start frame, all samples after start are forwarded", GF_PROP_UINT, "0", NULL, 0}, @@ -1961,6 +2120,8 @@ "- single: inject only at first frame of the stream\n" "- seg: inject at each non-empty segment\n" "- all: inject at each segment even empty ones", GF_PROP_UINT, "seg", "single|seg|all", 0}, + { OFFS(add_nl), "add new line after each packet when dumping text streams", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(rawb), "force direct dump of input without framing rewrite. In this mode, all codec types are supported", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, {0} }; @@ -1982,7 +2143,7 @@ GF_FilterRegister GenDumpRegister = { .name = "writegen", - GF_FS_SET_DESCRIPTION("Stream to file") + GF_FS_SET_DESCRIPTION("Stream to File converter") GF_FS_SET_HELP("Generic single stream to file converter, used when extracting/converting PIDs.\n" "The writegen filter should usually not be explicitly loaded without a source ID specified, since the filter would likely match any PID connection.") .private_size = sizeof(GF_GenDumpCtx), @@ -1992,7 +2153,8 @@ SETCAPS(GenDumpCaps), .configure_pid = writegen_configure_pid, .process = writegen_process, - .flags = GF_FS_REG_TEMP_INIT + .flags = GF_FS_REG_TEMP_INIT, + .hint_class_type = GF_FS_CLASS_TOOL }; static const GF_FilterCapability FrameDumpCaps = @@ -2007,7 +2169,7 @@ static GF_Err writegen_initialize(GF_Filter *filter) { GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); - if (ctx->frame) { + if (ctx->frame || ctx->rawb) { return gf_filter_override_caps(filter, (const GF_FilterCapability *) FrameDumpCaps, sizeof(FrameDumpCaps) / sizeof(GF_FilterCapability)); } return GF_OK; @@ -2050,20 +2212,60 @@ const GF_FilterRegister WriteUFRegister = { .name = "writeuf", - GF_FS_SET_DESCRIPTION("Stream to unframed format") + GF_FS_SET_DESCRIPTION("Framed to Unframed converter") GF_FS_SET_HELP("Generic single stream to unframed format converter, used when converting PIDs. This filter should not be explicitly loaded.\n") .private_size = sizeof(GF_GenDumpCtx), .initialize = writeuf_initialize, .finalize = writegen_finalize, SETCAPS(GenDumpXCaps), .configure_pid = writegen_configure_pid, - .process = writegen_process + .process = writegen_process, + .hint_class_type = GF_FS_CLASS_FRAMING }; const GF_FilterRegister *writeuf_register(GF_FilterSession *session) { return &WriteUFRegister; } + + +static GF_Err ttmlmerge_initialize(GF_Filter *filter) +{ + GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); + ctx->unframe_only = GF_TRUE; + ctx->ttml_merger = GF_TRUE; + ctx->ttml_agg = GF_TRUE; + return GF_OK; +} + +/* ttml merger: we reuse writegen logic for TTML merging, but keeping sample timing*/ +static GF_FilterCapability TTMLMergeCaps = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SUBS_XML), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +const GF_FilterRegister TTMLMergeRegister = { + .name = "ttmlmerge", + GF_FS_SET_DESCRIPTION("TTML sample merger") + GF_FS_SET_HELP("Merge input samples into a single TTML sample. Merging restarts at the start of DASH segments.\n") +// .flags = GF_FS_REG_EXPLICIT_ONLY, + .private_size = sizeof(GF_GenDumpCtx), + .initialize = ttmlmerge_initialize, + .finalize = writegen_finalize, + SETCAPS(TTMLMergeCaps), + .configure_pid = writegen_configure_pid, + .process = writegen_process, + .hint_class_type = GF_FS_CLASS_SUBTITLE +}; +const GF_FilterRegister *ttmlmerge_register(GF_FilterSession *session) +{ + return &TTMLMergeRegister; +} + + #else const GF_FilterRegister *writegen_register(GF_FilterSession *session) { @@ -2073,5 +2275,8 @@ { return NULL; } +const GF_FilterRegister *ttmlmerge_register(GF_FilterSession *session) +{ + return NULL; +} #endif //#ifndef GPAC_DISABLE_WRITEGEN -
View file
gpac-2.4.0.tar.gz/src/filters/write_nhml.c -> gpac-26.02.0.tar.gz/src/filters/write_nhml.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / NHML stream to file filter @@ -36,11 +36,18 @@ #include <zlib.h> #endif +GF_OPT_ENUM (GF_NHMLChksum, + NO_CHKSUM, + CRC32_CHKSUM, + SHA1_CHKSUM, +); + typedef struct { //opts const char *name; - Bool exporter, dims, pckp, nhmlonly, chksum; + Bool exporter, dims, pckp, nhmlonly, payload; + GF_NHMLChksum chksum; FILE *filep; @@ -60,7 +67,8 @@ GF_Fraction64 duration; Bool first; - Bool uncompress, is_dims, is_stpp; + s64 delay; + Bool uncompress, is_dims, is_stpp, is_evte; GF_BitStream *bs_w, *bs_r; u8 *nhml_buffer; @@ -114,6 +122,9 @@ if (!ctx->opid_mdia && !ctx->nhmlonly) ctx->opid_mdia = gf_filter_pid_new(filter); + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DELAY); + ctx->delay = p ? p->value.longsint : 0; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DECODER_CONFIG); if (p) { ctx->dcfg = p->value.data.ptr; @@ -138,7 +149,8 @@ name = gf_file_ext_start(fileName); if (name) name0 = 0; - strcat(fileName, ".media"); + if (strcmp(fileName, "null") && strcmp(fileName, "/dev/null")) + strcat(fileName, ".media"); if (gfio) { res_name = (char *) gf_fileio_factory(gfio, nhmldump_get_resolved_basename(ctx->ipid, fileName, szTempName) ); } else { @@ -166,7 +178,8 @@ name = gf_file_ext_start(fileName); if (name) name0 = 0; - strcat(fileName, ".info"); + if (strcmp(fileName, "null") && strcmp(fileName, "/dev/null")) + strcat(fileName, ".info"); if (gfio) { res_name = (char *) gf_fileio_factory(gfio, nhmldump_get_resolved_basename(ctx->ipid, fileName, szTempName) ); } else { @@ -301,7 +314,8 @@ name0 = 0; gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_OUTPATH, &PROP_STRING(ctx->name) ); } else { - strcat(fileName, ".nhml"); + if (strcmp(fileName, "null") && strcmp(fileName, "/dev/null")) + strcat(fileName, ".nhml"); gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_OUTPATH, &PROP_STRING(fileName) ); } } @@ -314,6 +328,7 @@ ctx->first = GF_TRUE; ctx->is_stpp = (cid==GF_CODECID_SUBS_XML) ? GF_TRUE : GF_FALSE; + ctx->is_evte = (cid==GF_CODECID_EVTE) ? GF_TRUE : GF_FALSE; p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); if (p && (p->value.lfrac.num>0)) ctx->duration = p->value.lfrac; @@ -479,7 +494,7 @@ gf_filter_pck_send(dst_pck); } } - + NHML_PRINT_STRING(0, "meta:encoding", "encoding") NHML_PRINT_STRING(0, "meta:contentEncoding", "content_encoding") ctx->uncompress = GF_FALSE; @@ -522,7 +537,9 @@ u64 cts = gf_filter_pck_get_cts(pck); if (dts==GF_FILTER_NO_TS) dts = cts; + else dts += ctx->delay; if (cts==GF_FILTER_NO_TS) cts = dts; + else cts += ctx->delay; if (!ctx->bs_r) ctx->bs_r = gf_bs_new(data, data_size, GF_BITSTREAM_READ); else gf_bs_reassign_buffer(ctx->bs_r, data, data_size); @@ -682,11 +699,17 @@ u64 cts = gf_filter_pck_get_cts(pck); if (dts==GF_FILTER_NO_TS) dts = cts; + else dts += ctx->delay; if (cts==GF_FILTER_NO_TS) cts = dts; + else cts += ctx->delay; ctx->pck_num++; - sprintf(nhml, "<NHNTSample number=\"%d\" DTS=\""LLU"\" dataLength=\"%d\" ", ctx->pck_num, dts, data_size); + sprintf(nhml, "<NHNTSample number=\"%d\" DTS=\""LLU"\" ", ctx->pck_num, dts); gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + if (!ctx->nhmlonly) { + sprintf(nhml, "dataLength=\"%d\" ", data_size); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } if (ctx->pckp || (cts != dts) ) { sprintf(nhml, "CTSOffset=\"%d\" ", (s32) ((s64)cts - (s64)dts)); gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); @@ -710,13 +733,15 @@ if (ctx->pckp) { u64 bo; u32 duration, idx; - sprintf(nhml, "mediaOffset=\""LLU"\" ", ctx->mdia_pos); - gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); - - bo = gf_filter_pck_get_byte_offset(pck); - if (bo!=GF_FILTER_NO_BO) { - sprintf(nhml, "sourceByteOffset=\""LLU"\" ", bo); + if (!ctx->nhmlonly) { + sprintf(nhml, "mediaOffset=\""LLU"\" ", ctx->mdia_pos); gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + bo = gf_filter_pck_get_byte_offset(pck); + if (bo!=GF_FILTER_NO_BO) { + sprintf(nhml, "sourceByteOffset=\""LLU"\" ", bo); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } } duration = gf_filter_pck_get_duration(pck); if (duration) { @@ -760,8 +785,8 @@ } } - if (ctx->chksum) { - if (ctx->chksum==1) { + if (ctx->chksum!=NO_CHKSUM) { + if (ctx->chksum==CRC32_CHKSUM) { u32 crc = gf_crc_32(data, data_size); sprintf(nhml, "crc=\"%08X\" ", crc); gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); @@ -858,13 +883,54 @@ sprintf(nhml, "></NHNTSubSample>\n"); gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); } - sprintf(nhml, "</NHNTSample>\n"); - gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); - - gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); if (ctx->filep) { + // dump sample opening tag + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); if (gf_fwrite(ctx->nhml_buffer, size, ctx->filep) != size) return GF_IO_ERR; + } + + if (ctx->payload) { + if (ctx->is_evte) { + FILE *f = ctx->filep ? ctx->filep : gf_file_temp(NULL); + Bool owns = !ctx->filep; + GF_BitStream *bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); + GF_Err e = GF_OK; + while (gf_bs_available(bs) > 0) { + GF_Box *a = NULL; + e = gf_isom_box_parse(&a, bs); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("NHMLMx Event Track / SCTE-35: error while parsing data boxes\n")); + break; //don't parse any further + } + gf_isom_box_dump(a, f); + data += a->size; + gf_isom_box_del(a); + a=NULL; + } + gf_bs_del(bs); + + if (owns) { + s64 sz = (s64)gf_ftell(f); + gf_fseek(f, 0, SEEK_SET); + while (sz > 0) { + size_t read = gf_fread(nhml, sizeof(nhml), f); + gf_bs_write_data(ctx->bs_w, nhml, (u32) read); + sz -= read; + } + gf_fclose(f); + } + } + } + + // close sample tag + if (!ctx->filep) { + sprintf(nhml, "</NHNTSample>\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); + } else { + fprintf(ctx->filep, "</NHNTSample>\n"); return GF_OK; } @@ -1010,6 +1076,7 @@ { OFFS(name), "set output name of media and info files produced", GF_PROP_STRING, NULL, NULL, 0}, { OFFS(nhmlonly), "only dump NHML info, not media", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(pckp), "full NHML dump", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(payload), "dump payload (scte35 only at the moment), should be combined with ǹhmlonly`", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(chksum), "insert frame checksum\n" "- none: no checksum\n" "- crc: CRC32 checksum\n" @@ -1024,14 +1091,15 @@ .name = "nhmlw", GF_FS_SET_DESCRIPTION("NHML writer") GF_FS_SET_HELP("This filter converts a single stream to an NHML output file.\n" - "NHML documentation is available at https://wiki.gpac.io/NHML-Format\n") + "NHML documentation is available at https://wiki.gpac.io/xmlformats/NHML-Format\n") .private_size = sizeof(GF_NHMLDumpCtx), .args = NHMLDumpArgs, .initialize = nhmldump_initialize, .finalize = nhmldump_finalize, SETCAPS(NHMLDumpCaps), .configure_pid = nhmldump_configure_pid, - .process = nhmldump_process + .process = nhmldump_process, + .hint_class_type = GF_FS_CLASS_TOOL }; const GF_FilterRegister *nhmlw_register(GF_FilterSession *session) @@ -1044,5 +1112,3 @@ return NULL; } #endif //#ifndef GPAC_DISABLE_NHMLW - -
View file
gpac-2.4.0.tar.gz/src/filters/write_nhnt.c -> gpac-26.02.0.tar.gz/src/filters/write_nhnt.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2024 * All rights reserved * * This file is part of GPAC / NHNT stream to file filter @@ -344,7 +344,7 @@ //send the complete data packet dst_pck = gf_filter_pck_new_ref(ctx->opid_mdia, 0, pck_size, pck); if (!dst_pck) return GF_OUT_OF_MEM; - + gf_filter_pck_merge_properties(pck, dst_pck); //keep byte offset ? // gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); @@ -394,13 +394,14 @@ .name = "nhntw", GF_FS_SET_DESCRIPTION("NHNT writer") GF_FS_SET_HELP("This filter converts a single stream to an NHNT output file.\n" - "NHNT documentation is available at https://wiki.gpac.io/NHNT-Format\n") + "NHNT documentation is available at https://wiki.gpac.io/xmlformats/NHNT-Format\n") .private_size = sizeof(GF_NHNTDumpCtx), .args = NHNTDumpArgs, .finalize = nhntdump_finalize, SETCAPS(NHNTDumpCaps), .configure_pid = nhntdump_configure_pid, - .process = nhntdump_process + .process = nhntdump_process, + .hint_class_type = GF_FS_CLASS_TOOL }; const GF_FilterRegister *nhntw_register(GF_FilterSession *session) @@ -413,4 +414,3 @@ return NULL; } #endif //#ifndef GPAC_DISABLE_NHNTW -
View file
gpac-2.4.0.tar.gz/src/filters/write_qcp.c -> gpac-26.02.0.tar.gz/src/filters/write_qcp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / QCP stream to file filter @@ -383,7 +383,8 @@ .args = QCPMxArgs, SETCAPS(QCPMxCaps), .configure_pid = qcpmx_configure_pid, - .process = qcpmx_process + .process = qcpmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/filters/write_tx3g.c -> gpac-26.02.0.tar.gz/src/filters/write_tx3g.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2022-2023 + * Copyright (c) Telecom ParisTech 2022-2025 * All rights reserved * * This file is part of GPAC / TX3G to SRT/VTT/TTML convert filter @@ -257,7 +257,7 @@ if (txtd->font_table && txtd->font_table->entry_count && txtd->font_table->fonts0.fontName) gf_fprintf(dump, " tts:fontFamily=\"%s\"", txtd->font_table->fonts0.fontName); - gf_fprintf(dump, "/>\n </layout>\n </head>\n <body>\n <div>\n"); + gf_fprintf(dump, "/>\n </layout>\n </head>\n <body region=\"Default\">\n <div>\n"); tx3g_format_time(start_ts, 1000, szTime, GF_FALSE); gf_fprintf(dump, " <p begin=\"%s\"", szTime); @@ -281,7 +281,7 @@ len = txt->len; } else { u8 *str = (u8 *) (txt->text); - len = gf_utf8_mbstowcs(utf16Line, 10000, (const char **) &str); + len = gf_utf8_mbstowcs(utf16Line, txt->len+1, (const char **) &str); if (len == GF_UTF8_FAIL) return GF_NON_COMPLIANT_BITSTREAM; utf16Linelen = 0; } @@ -315,7 +315,15 @@ //end of <p> gf_fprintf(dump, ">"); + Bool needs_new_line = GF_FALSE; for (j=0; j<len; j++) { + if (needs_new_line) { + //safety check for weird files using CR but no LF + if (utf16Linej=='\r') continue; + if (utf16Linej!='\n') + gf_fprintf(dump, "<br/>"); + needs_new_line = GF_FALSE; + } if (!single_style) { if (txt->styles) { new_styles = txtd->default_style.style_flags; @@ -382,7 +390,7 @@ } if (utf16Linej=='\r') { - + needs_new_line = GF_TRUE; } else if (utf16Linej=='\n') { gf_fprintf(dump, "<br/>"); } else { @@ -419,6 +427,8 @@ u8 *data, *output; u64 start_ts, end_ts, o_start_ts; u32 pck_size, timescale; + Bool forced = GF_FALSE; + const GF_PropertyValue *p; FILE *dump=NULL; pck = gf_filter_pid_get_packet(ctx->ipid); if (!pck) { @@ -452,6 +462,13 @@ if ((s64) end_ts > -ctx->delay) end_ts += ctx->delay; else end_ts = 0; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FORCED_SUB); + if (p && p->value.boolean) forced = GF_TRUE; + if (!forced) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FORCED_SUB); + if (p && (p->value.uint==2)) forced = GF_TRUE; + } + start_ts = gf_timestamp_rescale(start_ts, timescale, 1000); end_ts = gf_timestamp_rescale(end_ts, timescale, 1000); @@ -496,6 +513,10 @@ GF_FontTableBox font_ent; tx3gmx_get_stsd(ent, &txtd, &font_ent); + + if (txt->is_forced || (txtd.displayFlags & GF_TXT_ALL_SAMPLES_FORCED)) + forced = GF_TRUE; + if (ctx->dump_type<3) { dump_ttxt_sample_srt(dump, txt, &txtd, (ctx->dump_type==2) ? GF_TRUE : GF_FALSE); } else { @@ -529,6 +550,9 @@ gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); gf_filter_pck_set_cts(dst_pck, o_start_ts); gf_filter_pck_set_dts(dst_pck, o_start_ts); + if (forced) { + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_FORCED_SUB, &PROP_BOOL(GF_TRUE)); + } gf_filter_pck_send(dst_pck); } @@ -581,7 +605,7 @@ GF_FilterRegister TTXTMxRegister = { .name = "ufttxt", - GF_FS_SET_DESCRIPTION("TX3G unframer") + GF_FS_SET_DESCRIPTION("TX3G rewriter") GF_FS_SET_HELP("This filter converts a single ISOBMFF TX3G stream to TTXT (xml format) unframed stream.") .private_size = sizeof(TX3GMxCtx), .args = TX3GMxArgs, @@ -589,7 +613,8 @@ .finalize = tx3gmx_finalize, SETCAPS(TX3GMxCaps), .configure_pid = tx3gmx_configure_pid, - .process = tx3gmx_process + .process = tx3gmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING }; const GF_FilterRegister *ufttxt_register(GF_FilterSession *session) @@ -627,7 +652,8 @@ .finalize = tx3gmx_finalize, SETCAPS(TX3G2SRTCaps), .configure_pid = tx3gmx_configure_pid, - .process = tx3gmx_process + .process = tx3gmx_process, + .hint_class_type = GF_FS_CLASS_SUBTITLE }; const GF_FilterRegister *tx3g2srt_register(GF_FilterSession *session) @@ -666,7 +692,8 @@ .finalize = tx3gmx_finalize, SETCAPS(TX3G2VTTCaps), .configure_pid = tx3gmx_configure_pid, - .process = tx3gmx_process + .process = tx3gmx_process, + .hint_class_type = GF_FS_CLASS_SUBTITLE }; const GF_FilterRegister *tx3g2vtt_register(GF_FilterSession *session) @@ -706,7 +733,8 @@ .finalize = tx3gmx_finalize, SETCAPS(TX3G2TTMLCaps), .configure_pid = tx3gmx_configure_pid, - .process = tx3gmx_process + .process = tx3gmx_process, + .hint_class_type = GF_FS_CLASS_SUBTITLE };
View file
gpac-2.4.0.tar.gz/src/filters/write_vtt.c -> gpac-26.02.0.tar.gz/src/filters/write_vtt.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / WebVTT stream unframer filter @@ -35,7 +35,7 @@ typedef struct { //opts - Bool exporter, merge_cues; + Bool exporter, merge_cues, noempty; //only one input pid declared GF_FilterPid *ipid; @@ -54,7 +54,7 @@ GF_WebVTTParser *parser; GF_FilterPacket *src_pck; - Bool dash_mode; + Bool dash_mode, first; u32 seg_pck_in, seg_pck_out; } GF_WebVTTMxCtx; @@ -80,6 +80,7 @@ if (!p) return GF_NOT_SUPPORTED; ctx->codecid = p->value.uint; ctx->ipid = pid; + ctx->first = GF_TRUE; if (!ctx->opid) { @@ -234,6 +235,14 @@ pck = gf_filter_pid_get_packet(ctx->ipid); if (!pck) { if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->noempty && ctx->first) { + // send empty packet to init the file + GF_FilterPacket *dst = gf_filter_pck_new_alloc(ctx->opid, 0, NULL); + gf_filter_pck_set_sap(dst, GF_FILTER_SAP_1); + gf_filter_pck_set_byte_offset(dst, GF_FILTER_NO_BO); + gf_filter_pck_send(dst); + ctx->first = GF_FALSE; + } gf_filter_pid_set_eos(ctx->opid); if (ctx->parser) { vttmx_parser_flush(ctx); @@ -277,6 +286,7 @@ } cues = gf_webvtt_parse_cues_from_data(data, pck_size, start_ts, end_ts); + if (cues && gf_list_count(cues)) ctx->first = GF_FALSE; if (ctx->parser) { gf_webvtt_merge_cues(ctx->parser, start_ts, cues); } else { @@ -333,20 +343,22 @@ { { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, { OFFS(merge_cues), "merge VTT cues (undo ISOBMFF cue split)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(noempty), "do not create an empty file if no VTT cues are present", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, {0} }; GF_FilterRegister WebVTTMxRegister = { .name = "ufvtt", - GF_FS_SET_DESCRIPTION("WebVTT unframer") + GF_FS_SET_DESCRIPTION("WebVTT rewriter") GF_FS_SET_HELP("This filter converts a single ISOBMFF WebVTT stream to its unframed format.") .private_size = sizeof(GF_WebVTTMxCtx), .args = WebVTTMxArgs, .finalize = vttmx_finalize, SETCAPS(WebVTTMxCaps), .configure_pid = vttmx_configure_pid, - .process = vttmx_process + .process = vttmx_process, + .hint_class_type = GF_FS_CLASS_FRAMING };
View file
gpac-2.4.0.tar.gz/src/ietf/rtp.c -> gpac-26.02.0.tar.gz/src/ietf/rtp.c
Changed
@@ -253,7 +253,7 @@ //else bind and set remote destination else { if (!ch->net_info.port_first) ch->net_info.port_first = ch->net_info.client_port_first; - e = gf_sk_bind(ch->rtp, local_ip,ch->net_info.port_first, ch->net_info.destination, ch->net_info.client_port_first, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND); + e = gf_sk_bind(ch->rtp, local_ip,ch->net_info.port_first, ch->net_info.destination, ch->net_info.client_port_first, GF_SOCK_REUSE_PORT|GF_SOCK_IS_SENDER); if (e) return e; } } else { @@ -290,7 +290,7 @@ e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.client_port_last, ch->net_info.source, port, GF_SOCK_REUSE_PORT); if (e) return e; } else { - e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.port_last, ch->net_info.destination, ch->net_info.client_port_last, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND); + e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.port_last, ch->net_info.destination, ch->net_info.client_port_last, GF_SOCK_REUSE_PORT|GF_SOCK_IS_SENDER); if (e) return e; } } else {
View file
gpac-2.4.0.tar.gz/src/ietf/rtp_depacketizer.c -> gpac-26.02.0.tar.gz/src/ietf/rtp_depacketizer.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / RTP input module @@ -1040,7 +1040,7 @@ #endif #if GPAC_ENABLE_3GPP_DIMS_RTP -static void gf_rtp_parse_3gpp_dims(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_3gpp_dims(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u32 du_size, offset, dsize, hdr_size; char *data, dhdr6; @@ -1090,7 +1090,7 @@ case 3: if (!rtp->inter_bs) return; gf_bs_write_data(rtp->inter_bs, payload+offset, size-offset); - gf_bs_get_content(rtp->inter_bs, &data, &dsize); + gf_bs_get_content(rtp->inter_bs, (u8**)&data, &dsize); gf_bs_del(rtp->inter_bs); /*send unit header - if dims size is >0xFFFF, use our internal hack for large units*/ @@ -1972,9 +1972,16 @@ nb_chan = 1; } else { payt = gf_rtp_get_payload_type(map, media); - if (!payt) return NULL; - clock_rate = map->ClockRate; - nb_chan = map->AudioChannels; + if (payt) { + clock_rate = map->ClockRate; + nb_chan = map->AudioChannels; + } else { + static_map = gf_rtp_is_valid_static_payt(map->PayloadType); + if (!static_map) return NULL; + clock_rate = static_map->clock_rate; + if (static_map->stream_type==GF_STREAM_AUDIO) + nb_chan = 1; + } } }
View file
gpac-2.4.0.tar.gz/src/ietf/rtp_packetizer.c -> gpac-26.02.0.tar.gz/src/ietf/rtp_packetizer.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -564,6 +564,11 @@ strcpy(szMediaName, "video"); strcpy(szPayloadName, "H266"); return GF_TRUE; + + case GF_RTP_PAYT_MP2T: + strcpy(szMediaName, "video"); + strcpy(szPayloadName, "MP2T"); + return GF_TRUE; default: strcpy(szMediaName, ""); strcpy(szPayloadName, ""); @@ -574,32 +579,35 @@ GF_EXPORT -GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, char *dsi, u32 dsi_size) +GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char **out_sdp_line, char *dsi, u32 dsi_size) { - char buffer20100, dsiString20000; - u32 i, k; + char buffer100; + u32 i; Bool is_first = GF_TRUE; - char *sdp = NULL; GF_Err e = GF_OK; + if (!out_sdp_line) return GF_BAD_PARAM; + if (*out_sdp_line) + (*out_sdp_line)0 = 0; if ((builder->rtp_payt!=GF_RTP_PAYT_MPEG4) && (builder->rtp_payt!=GF_RTP_PAYT_LATM) ) return GF_BAD_PARAM; #define SDP_ADD_INT(_name, _val) {\ - if (!is_first) gf_dynstrcat(&sdp, "; ", NULL); \ - sprintf(buffer, "%s=%d", _name, _val);\ - gf_dynstrcat(&sdp, buffer, NULL);\ + if (!is_first) gf_dynstrcat(out_sdp_line, "; ", NULL); \ + gf_dynstrcat(out_sdp_line, _name, NULL);\ + sprintf(buffer, "%d", _val);\ + gf_dynstrcat(out_sdp_line, buffer, "=");\ is_first = 0;\ } #define SDP_ADD_STR(_name, _val) {\ - if (!is_first) gf_dynstrcat(&sdp, "; ", NULL);\ - sprintf(buffer, "%s=%s", _name, _val);\ - gf_dynstrcat(&sdp, buffer, NULL);\ + if (!is_first) gf_dynstrcat(out_sdp_line, "; ", NULL);\ + gf_dynstrcat(out_sdp_line, _name, NULL);\ + gf_dynstrcat(out_sdp_line, _val, "=");\ is_first = 0;\ } sprintf(buffer, "a=fmtp:%d ", builder->PayloadType); - gf_dynstrcat(&sdp, buffer, NULL); + gf_dynstrcat(out_sdp_line, buffer, NULL); /*mandatory fields*/ if (builder->slMap.PL_ID) SDP_ADD_INT("profile-level-id", builder->slMap.PL_ID); @@ -607,17 +615,18 @@ if (builder->rtp_payt == GF_RTP_PAYT_LATM) SDP_ADD_INT("cpresent", 0); if (dsi && dsi_size) { - k = 0; if (dsi_size>10000) { e = GF_OUT_OF_MEM; goto exit; } + if (!is_first) gf_dynstrcat(out_sdp_line, "; ", NULL); + gf_dynstrcat(out_sdp_line, "config=", NULL); + buffer2=0; for (i=0; i<dsi_size; i++) { - sprintf(&dsiStringk, "%02x", (unsigned char) dsii); - k+=2; + sprintf(buffer, "%02x", (unsigned char) dsii); + gf_dynstrcat(out_sdp_line, buffer, NULL); } - dsiStringk = 0; - SDP_ADD_STR("config", dsiString); + is_first = 0; } if (!strcmp(payload_name, "MP4V-ES") || (builder->rtp_payt == GF_RTP_PAYT_LATM) ) { e = GF_OK; @@ -658,17 +667,7 @@ } exit: - sdpLine0 = 0; - if (sdp) { - k = (u32) strlen(sdp); - if (k<20000) - strcpy(sdpLine, sdp); - else - e = GF_OUT_OF_MEM; - gf_free(sdp); - } else { - e = GF_OUT_OF_MEM; - } + if (!out_sdp_line) return GF_OUT_OF_MEM; return e; }
View file
gpac-2.4.0.tar.gz/src/ietf/rtp_pck_3gpp.c -> gpac-26.02.0.tar.gz/src/ietf/rtp_pck_3gpp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -147,6 +147,7 @@ while (data_size>offset) { u8 frame_type = dataoffset; u8 size = qes_get_rate_size(frame_type, GF_QCELP_RATE_TO_SIZE, GF_QCELP_RATE_TO_SIZE_NB); + if (!size) return GF_NON_COMPLIANT_BITSTREAM; /*reserved, not sent)*/ if (frame_type>=5) { offset += size; @@ -585,7 +586,7 @@ #if GPAC_ENABLE_3GPP_DIMS_RTP -GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration) +GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration) { u32 frag_state; GF_BitStream *bs;
View file
gpac-2.4.0.tar.gz/src/ietf/rtp_streamer.c -> gpac-26.02.0.tar.gz/src/ietf/rtp_streamer.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -132,7 +132,7 @@ if (!rtp->channel) return GF_OUT_OF_MEM; rtp->channel->TimeScale = rtp->packetizer->sl_config.timestampResolution; - gf_rtp_set_ports(rtp->channel, 0); + //gf_rtp_set_ports(rtp->channel, 0); memset(&tr, 0, sizeof(GF_RTSPTransport)); tr.IsUnicast = gf_sk_is_multicast_address(dest) ? GF_FALSE : GF_TRUE; @@ -428,8 +428,16 @@ break; } + if (flags & GP_RTP_PCK_FORCE_STATIC_ID) { + if (!OfficialPayloadType) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTP Packetizer Codec type %s requires SDP output\n", gf_codecid_name(codecid) )); + gf_rtp_streamer_del(stream); + return NULL; + } + PayloadType = OfficialPayloadType; + } /*override hinter type if requested and possible*/ - if (has_mpeg4_mapping && (flags & GP_RTP_PCK_FORCE_MPEG4)) { + else if (has_mpeg4_mapping && (flags & GP_RTP_PCK_FORCE_MPEG4)) { rtp_type = GF_RTP_PAYT_MPEG4; } /*use static payload ID if enabled*/ @@ -567,20 +575,26 @@ #if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_STREAMING) -void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, u32 w, u32 h, s32 tx, s32 ty, s16 l, u32 max_w, u32 max_h, char *tx3g_base64) +void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char **out_sdp_line, u32 w, u32 h, s32 tx, s32 ty, s16 l, u32 max_w, u32 max_h, char *tx3g_base64) { - char buffer2000; - sprintf(sdpLine, "a=fmtp:%d sver=60; ", builder->PayloadType); + char tmp_buf101; + if (!out_sdp_line) return; + if (*out_sdp_line) + (*out_sdp_line)0 = 0; - sprintf(buffer, "width=%d; height=%d; tx=%d; ty=%d; layer=%d; ", w, h, tx, ty, l); - strcat(sdpLine, buffer); + tmp_buf100 = 0; + snprintf(tmp_buf, 100, "a=fmtp:%d sver=60; ", builder->PayloadType); + gf_dynstrcat(out_sdp_line, tmp_buf, NULL); - sprintf(buffer, "max-w=%d; max-h=%d", max_w, max_h); - strcat(sdpLine, buffer); + snprintf(tmp_buf, 100, "width=%d; height=%d; tx=%d; ty=%d; layer=%d; ", w, h, tx, ty, l); + gf_dynstrcat(out_sdp_line, tmp_buf, NULL); + + snprintf(tmp_buf, 100, "max-w=%d; max-h=%d", max_w, max_h); + gf_dynstrcat(out_sdp_line, tmp_buf, NULL); if (tx3g_base64) { - strcat(sdpLine, "; tx3g="); - strcat(sdpLine, tx3g_base64); + gf_dynstrcat(out_sdp_line, "; tx3g=", NULL); + gf_dynstrcat(out_sdp_line, tx3g_base64, NULL); } } @@ -590,11 +604,11 @@ GF_EXPORT GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, const u8 *dsi_enh, u32 dsi_enh_len, char *KMS_URI, u32 width, u32 height, u32 tw, u32 th, s32 tx, s32 ty, s16 tl, u32 nb_channels, Bool for_rtsp, char **out_sdp_buffer) { - u32 size; u16 port=0; char mediaName30, payloadName30; - char sdp20000, sdpLine10000; + char tmp_buf101; + tmp_buf100=0; if (!out_sdp_buffer) return GF_BAD_PARAM; gf_rtp_builder_get_payload_name(rtp->packetizer, payloadName, mediaName); @@ -604,12 +618,13 @@ gf_rtp_get_ports(rtp->channel, &port, NULL); } - sprintf(sdp, "m=%s %d RTP/%s %u\n", mediaName, for_rtsp ? 0 : port, rtp->packetizer->slMap.IV_length ? "SAVP" : "AVP", rtp->packetizer->PayloadType); + snprintf(tmp_buf, 100, "m=%s %d RTP/%s %u\n", mediaName, for_rtsp ? 0 : port, rtp->packetizer->slMap.IV_length ? "SAVP" : "AVP", rtp->packetizer->PayloadType); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); if (nb_channels > 1) - sprintf(sdpLine, "a=rtpmap:%u %s/%u/%u\n", rtp->packetizer->PayloadType, payloadName, rtp->packetizer->sl_config.timestampResolution, nb_channels); + snprintf(tmp_buf, 100, "a=rtpmap:%u %s/%u/%u\n", rtp->packetizer->PayloadType, payloadName, rtp->packetizer->sl_config.timestampResolution, nb_channels); else - sprintf(sdpLine, "a=rtpmap:%u %s/%u\n", rtp->packetizer->PayloadType, payloadName, rtp->packetizer->sl_config.timestampResolution); - strcat(sdp, sdpLine); + snprintf(tmp_buf, 100, "a=rtpmap:%u %s/%u\n", rtp->packetizer->PayloadType, payloadName, rtp->packetizer->sl_config.timestampResolution); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); if (ESID #if GPAC_ENABLE_3GPP_DIMS_RTP @@ -617,67 +632,73 @@ #endif && (rtp->packetizer->rtp_payt != GF_RTP_PAYT_OPUS) ) { - sprintf(sdpLine, "a=mpeg4-esid:%d\n", ESID); - strcat(sdp, sdpLine); + snprintf(tmp_buf, 100, "a=mpeg4-esid:%d\n", ESID); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); } if (width && height) { if (rtp->packetizer->rtp_payt == GF_RTP_PAYT_H263) { - sprintf(sdpLine, "a=cliprect:0,0,%d,%d\n", height, width); - strcat(sdp, sdpLine); + snprintf(tmp_buf, 100, "a=cliprect:0,0,%d,%d\n", height, width); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); } /*extensions for some mobile phones*/ - sprintf(sdpLine, "a=framesize:%d %d-%d\n", rtp->packetizer->PayloadType, width, height); - strcat(sdp, sdpLine); + snprintf(tmp_buf, 100, "a=framesize:%d %d-%d\n", rtp->packetizer->PayloadType, width, height); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); } - strcpy(sdpLine, ""); - /*AMR*/ if ((rtp->packetizer->rtp_payt == GF_RTP_PAYT_AMR) || (rtp->packetizer->rtp_payt == GF_RTP_PAYT_AMR_WB)) { - sprintf(sdpLine, "a=fmtp:%d octet-align=1\n", rtp->packetizer->PayloadType); + snprintf(tmp_buf, 100, "a=fmtp:%d octet-align=1\n", rtp->packetizer->PayloadType); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); } #if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_STREAMING) /*Text*/ else if (rtp->packetizer->rtp_payt == GF_RTP_PAYT_3GPP_TEXT) { - gf_media_format_ttxt_sdp(rtp->packetizer, payloadName, sdpLine, tw, th, tx, ty, tl, width, height, (u8 *)dsi_enh); - strcat(sdpLine, "\n"); + char *sdp = NULL; + gf_media_format_ttxt_sdp(rtp->packetizer, payloadName, &sdp, tw, th, tx, ty, tl, width, height, (u8 *)dsi_enh); + gf_dynstrcat(out_sdp_buffer, sdp, NULL); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); + if (sdp) gf_free(sdp); } #endif /*EVRC/SMV in non header-free mode*/ else if ((rtp->packetizer->rtp_payt == GF_RTP_PAYT_EVRC_SMV) && (rtp->packetizer->auh_size>1)) { - sprintf(sdpLine, "a=fmtp:%d maxptime=%d\n", rtp->packetizer->PayloadType, rtp->packetizer->auh_size*20); + snprintf(tmp_buf, 100, "a=fmtp:%d maxptime=%d\n", rtp->packetizer->PayloadType, rtp->packetizer->auh_size*20); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); } /*H264/AVC*/ else if ((rtp->packetizer->rtp_payt == GF_RTP_PAYT_H264_AVC) || (rtp->packetizer->rtp_payt == GF_RTP_PAYT_H264_SVC)) { GF_AVCConfig *avcc = dsi ? gf_odf_avc_cfg_read((u8*)dsi, dsi_len) : NULL; if (avcc) { - sprintf(sdpLine, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", rtp->packetizer->PayloadType, avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication); + snprintf(tmp_buf, 100, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", rtp->packetizer->PayloadType, avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); + if (gf_list_count(avcc->pictureParameterSets) || gf_list_count(avcc->sequenceParameterSets)) { u32 i, count, b64s; char b64200; - strcat(sdpLine, "; sprop-parameter-sets="); + gf_dynstrcat(out_sdp_buffer, "; sprop-parameter-sets=", NULL); + count = gf_list_count(avcc->sequenceParameterSets); for (i=0; i<count; i++) { GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(avcc->sequenceParameterSets, i); b64s = gf_base64_encode(sl->data, sl->size, b64, 200); b64b64s=0; - strcat(sdpLine, b64); - if (i+1<count) strcat(sdpLine, ","); + gf_dynstrcat(out_sdp_buffer, b64, NULL); + if (i+1<count) gf_dynstrcat(out_sdp_buffer, ",", NULL); } - if (i) strcat(sdpLine, ","); + if (i) gf_dynstrcat(out_sdp_buffer, ",", NULL); count = gf_list_count(avcc->pictureParameterSets); for (i=0; i<count; i++) { GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(avcc->pictureParameterSets, i); b64s = gf_base64_encode(sl->data, sl->size, b64, 200); b64b64s=0; - strcat(sdpLine, b64); - if (i+1<count) strcat(sdpLine, ","); + gf_dynstrcat(out_sdp_buffer, b64, NULL); + if (i+1<count) gf_dynstrcat(out_sdp_buffer, ",", NULL); } } gf_odf_avc_cfg_del(avcc); - strcat(sdpLine, "\n"); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); } } else if ((rtp->packetizer->rtp_payt == GF_RTP_PAYT_HEVC) @@ -709,53 +730,59 @@ if (param_array) { u32 count, i, j, b64s; char b64200; - sprintf(sdpLine, "a=fmtp:%d", rtp->packetizer->PayloadType); + snprintf(tmp_buf, 100, "a=fmtp:%d", rtp->packetizer->PayloadType); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); + count = gf_list_count(param_array); for (i = 0; i < count; i++) { GF_NALUFFParamArray *ar = (GF_NALUFFParamArray *)gf_list_get(param_array, i); if (ar->type==sps_nut) { - strcat(sdpLine, "; sprop-sps="); + gf_dynstrcat(out_sdp_buffer, "; sprop-sps=", NULL); } else if (ar->type==pps_nut) { - strcat(sdpLine, "; sprop-pps="); + gf_dynstrcat(out_sdp_buffer, "; sprop-pps=", NULL); } else if (ar->type==vps_nut) { - strcat(sdpLine, "; sprop-vps="); + gf_dynstrcat(out_sdp_buffer, "; sprop-vps=", NULL); } for (j = 0; j < gf_list_count(ar->nalus); j++) { GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(ar->nalus, j); b64s = gf_base64_encode(sl->data, sl->size, b64, 200); b64b64s=0; - if (j) strcat(sdpLine, ", "); - strcat(sdpLine, b64); + if (j) gf_dynstrcat(out_sdp_buffer, ", ", NULL); + gf_dynstrcat(out_sdp_buffer, b64, NULL); } } if (vvcc) gf_odf_vvc_cfg_del(vvcc); if (hvcc) gf_odf_hevc_cfg_del(hvcc); - strcat(sdpLine, "\n"); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); } } /*MPEG-4 decoder config*/ else if (rtp->packetizer->rtp_payt==GF_RTP_PAYT_MPEG4) { - gf_rtp_builder_format_sdp(rtp->packetizer, payloadName, sdpLine, (u8*)dsi, dsi_len); - strcat(sdpLine, "\n"); + char *sdp = NULL; + gf_rtp_builder_format_sdp(rtp->packetizer, payloadName, &sdp, (u8*)dsi, dsi_len); + gf_dynstrcat(out_sdp_buffer, sdp, NULL); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); + if (sdp) gf_free(sdp); if (rtp->packetizer->slMap.IV_length && KMS_URI) { if (!strnicmp(KMS_URI, "(key)", 5) || !strnicmp(KMS_URI, "(ipmp)", 6) || !strnicmp(KMS_URI, "(uri)", 5)) { - strcat(sdpLine, "; ISMACrypKey="); + gf_dynstrcat(out_sdp_buffer, "; ISMACrypKey=", NULL); } else { - strcat(sdpLine, "; ISMACrypKey=(uri)"); + gf_dynstrcat(out_sdp_buffer, "; ISMACrypKey=(uri)", NULL); } - strcat(sdpLine, KMS_URI); - strcat(sdpLine, "\n"); + gf_dynstrcat(out_sdp_buffer, KMS_URI, NULL); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); } } #if GPAC_ENABLE_3GPP_DIMS_RTP /*DIMS decoder config*/ else if (rtp->packetizer->rtp_payt==GF_RTP_PAYT_3GPP_DIMS) { - sprintf(sdpLine, "a=fmtp:%d Version-profile=%d", rtp->packetizer->PayloadType, 10); + snprintf(tmp_buf, 100, "a=fmtp:%d Version-profile=%d", rtp->packetizer->PayloadType, 10); + gf_dynstrcat(out_sdp_buffer, tmp_buf, NULL); if (rtp->packetizer->flags & GP_RTP_DIMS_COMPRESSED) { - strcat(sdpLine, ";content-coding=deflate"); + gf_dynstrcat(out_sdp_buffer, ";content-coding=deflate", NULL); } - strcat(sdpLine, "\n"); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); } #endif /*MPEG-4 Audio LATM*/ @@ -783,22 +810,12 @@ gf_bs_get_content(bs, &config_bytes, &config_size); gf_bs_del(bs); - gf_rtp_builder_format_sdp(rtp->packetizer, payloadName, sdpLine, config_bytes, config_size); + char *sdp = NULL; + gf_rtp_builder_format_sdp(rtp->packetizer, payloadName, &sdp, config_bytes, config_size); gf_free(config_bytes); - strcat(sdpLine, "\n"); - } - - strcat(sdp, sdpLine); - - size = (u32) strlen(sdp) + (*out_sdp_buffer ? (u32) strlen(*out_sdp_buffer) : 0) + 1; - if ( !*out_sdp_buffer) { - *out_sdp_buffer = (char*)gf_malloc(sizeof(char)*size); - if (! *out_sdp_buffer) return GF_OUT_OF_MEM; - strcpy(*out_sdp_buffer, sdp); - } else { - *out_sdp_buffer = (char*)gf_realloc(*out_sdp_buffer, sizeof(char)*size); - if (! *out_sdp_buffer) return GF_OUT_OF_MEM; - strcat(*out_sdp_buffer, sdp); + gf_dynstrcat(out_sdp_buffer, sdp, NULL); + gf_dynstrcat(out_sdp_buffer, "\n", NULL); + if (sdp) gf_free(sdp); } return GF_OK; } @@ -977,5 +994,11 @@ return (streamer && streamer->packetizer) ? streamer->packetizer->sl_config.timestampResolution : 0; } +GF_EXPORT +u32 gf_rtp_streamer_get_codecid(GF_RTPStreamer *streamer) +{ + return (streamer && streamer->packetizer) ? streamer->packetizer->slMap.CodecID : 0 ; +} + #endif /*GPAC_DISABLE_STREAMING && GPAC_DISABLE_ISOM*/
View file
gpac-2.4.0.tar.gz/src/ietf/rtsp_command.c -> gpac-26.02.0.tar.gz/src/ietf/rtsp_command.c
Changed
@@ -331,23 +331,11 @@ } else { rad = (sess->ConnectionType == GF_SOCK_TYPE_TCP) ? "rtsp" : "rtspu"; if (sCtrl) { - //if both server and service names are included in the control, just - //use the control - if (strstr(sCtrl, sess->Server) && strstr(sCtrl, sess->Service)) { - sprintf(buffer, "%s %s %s\r\n", com->method, sCtrl, GF_RTSP_VERSION); - } - //if service is specified in ctrl, do not rewrite it - else if (strstr(sCtrl, sess->Service)) { - sprintf(buffer, "%s %s://%s:%d/%s %s\r\n", com->method, rad, sess->Server, sess->Port, sCtrl, GF_RTSP_VERSION); - } - else if (!strnicmp(sCtrl, "rtsp", 4)) { - sprintf(buffer, "%s %s %s\r\n", com->method, sCtrl, GF_RTSP_VERSION); - } - //otherwise rewrite full URL - else { - sprintf(buffer, "%s %s://%s/%s/%s %s\r\n", com->method, rad, sess->Server, sess->Service, sCtrl, GF_RTSP_VERSION); -// sprintf(buffer, "%s %s://%s:%d/%s/%s %s\r\n", com->method, rad, sess->Server, sess->Port, sess->Service, sCtrl, GF_RTSP_VERSION); - } + char szServURL1024; + sprintf(szServURL, "%s://%s:%d/%s", rad, sess->Server, sess->Port, sess->Service); + char *ctrl_url = gf_url_concatenate(szServURL, sCtrl); + sprintf(buffer, "%s %s %s\r\n", com->method, ctrl_url, GF_RTSP_VERSION); + gf_free(ctrl_url); } else { sprintf(buffer, "%s %s://%s:%d/%s %s\r\n", com->method, rad, sess->Server, sess->Port, sess->Service, GF_RTSP_VERSION); }
View file
gpac-2.4.0.tar.gz/src/ietf/sdp.c -> gpac-26.02.0.tar.gz/src/ietf/sdp.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -30,10 +30,13 @@ #ifndef GPAC_DISABLE_STREAMING #include <gpac/token.h> +#include <gpac/utf.h> #define SDP_WRITE_STEPALLOC 2048 +#define SDP_MAX_LINE 1000 + GF_EXPORT GF_SDP_FMTP *gf_sdp_fmtp_new() @@ -75,28 +78,28 @@ { s32 pos; u32 PayT; - char comp3000; + char compSDP_MAX_LINE+1; GF_X_Attribute *att; - pos = gf_token_get(buffer, 0, " :\t\r\n", comp, 3000); + pos = gf_token_get(buffer, 0, " :\t\r\n", comp, SDP_MAX_LINE); if (!strcmp(comp, "cat")) { if (media) return; - /*pos = */gf_token_get(buffer, pos, ":\t\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\t\r\n", comp, SDP_MAX_LINE); if (sdp->a_cat) return; sdp->a_cat = gf_strdup(comp); return; } if (!strcmp(comp, "keywds")) { if (media) return; - /*pos = */gf_token_get(buffer, pos, ":\t\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\t\r\n", comp, SDP_MAX_LINE); if (sdp->a_keywds) return; sdp->a_keywds = gf_strdup(comp); return; } if (!strcmp(comp, "tool")) { if (media) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); if (sdp->a_tool) return; sdp->a_tool = gf_strdup(comp); return; @@ -104,7 +107,7 @@ if (!strcmp(comp, "ptime")) { if (!media) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); media->PacketTime = atoi(comp); return; } @@ -134,27 +137,27 @@ } if (!strcmp(comp, "orient")) { if (!media || media->Type) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); if (media->orientation) return; media->orientation = gf_strdup(comp); return; } if (!strcmp(comp, "type")) { if (media) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); if (sdp->a_type) return; sdp->a_type = gf_strdup(comp); return; } if (!strcmp(comp, "charset")) { if (media) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); if (sdp->a_charset) sdp->a_charset = gf_strdup(comp); return; } if (!strcmp(comp, "sdplang")) { - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); if (media) { if (media->sdplang) return; media->sdplang = gf_strdup(comp); @@ -165,7 +168,7 @@ return; } if (!strcmp(comp, "lang")) { - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); if (media) { if (media->lang) return; media->lang = gf_strdup(comp); @@ -178,13 +181,13 @@ if (!strcmp(comp, "framerate")) { //only for video if (!media || (media->Type != 1)) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); media->FrameRate = atof(comp); return; } if (!strcmp(comp, "quality")) { if (!media) return; - /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(buffer, pos, ":\r\n", comp, SDP_MAX_LINE); media->Quality = atoi(comp); return; } @@ -192,13 +195,13 @@ GF_RTPMap *map; if (!media) return; map = (GF_RTPMap*)gf_malloc(sizeof(GF_RTPMap)); - pos = gf_token_get(buffer, pos, ": \r\n", comp, 3000); + pos = gf_token_get(buffer, pos, ": \r\n", comp, SDP_MAX_LINE); map->PayloadType = atoi(comp); - pos = gf_token_get(buffer, pos, " /\r\n", comp, 3000); + pos = gf_token_get(buffer, pos, " /\r\n", comp, SDP_MAX_LINE); map->payload_name = gf_strdup(comp); - pos = gf_token_get(buffer, pos, " /\r\n", comp, 3000); + pos = gf_token_get(buffer, pos, " /\r\n", comp, SDP_MAX_LINE); map->ClockRate = atoi(comp); - pos = gf_token_get(buffer, pos, " /\r\n", comp, 3000); + pos = gf_token_get(buffer, pos, " /\r\n", comp, SDP_MAX_LINE); map->AudioChannels = (pos > 0) ? atoi(comp) : 0; gf_list_add(media->RTPMaps, map); return; @@ -207,7 +210,7 @@ if (!strcmp(comp, "fmtp")) { GF_SDP_FMTP *fmtp; if (!media) return; - pos = gf_token_get(buffer, pos, ": \r\n", comp, 3000); + pos = gf_token_get(buffer, pos, ": \r\n", comp, SDP_MAX_LINE); PayT = atoi(comp); fmtp = SDP_GetFMTPForPayload(media, PayT); if (!fmtp) { @@ -216,13 +219,13 @@ gf_list_add(media->FMTP, fmtp); } while (1) { - pos = gf_token_get(buffer, pos, "; =\r\n", comp, 3000); + pos = gf_token_get(buffer, pos, "; =\r\n", comp, SDP_MAX_LINE); if (pos <= 0) break; att = (GF_X_Attribute*)gf_malloc(sizeof(GF_X_Attribute)); att->Name = gf_strdup(comp); att->Value = NULL; pos ++; - pos = gf_token_get(buffer, pos, ";\r\n", comp, 3000); + pos = gf_token_get(buffer, pos, ";\r\n", comp, SDP_MAX_LINE); if (pos > 0) att->Value = gf_strdup(comp); gf_list_add(fmtp->Attributes, att); } @@ -232,7 +235,7 @@ //so keep it. //a= <attribute> || <attribute>:<value> //we add <attribute> <value> in case ... - pos = gf_token_get(buffer, 0, " :\r\n", comp, 3000); + pos = gf_token_get(buffer, 0, " :\r\n", comp, SDP_MAX_LINE); att = (GF_X_Attribute*)gf_malloc(sizeof(GF_X_Attribute)); att->Name = gf_strdup(comp); att->Value = NULL; @@ -242,7 +245,7 @@ if (bufferpos == ' ') pos += 1; } - pos = gf_token_get(buffer, pos, "\r\n", comp, 3000); + pos = gf_token_get(buffer, pos, "\r\n", comp, SDP_MAX_LINE); if (pos > 0) att->Value = gf_strdup(comp); if (media) { @@ -454,6 +457,7 @@ sign = -1; buf += 1; } + memset(num, 0, 30); test = strstr(buf, "d"); if (test) { @@ -461,7 +465,12 @@ memcpy(num, buf, MIN(sizeof(num)-1, strlen(buf)-strlen(test))); else gf_assert(0); - return (atoi(num)*sign*86400); + int inum = atoi(num); + if (inum >= GF_INT_MAX/86400) { + GF_LOG( GF_LOG_WARNING, GF_LOG_CORE, ("Error parsing duration: %s cannot be represented as integer\n", num)); + return 0; + } + return (inum*sign*86400); } test = strstr(buf, "h"); if (test) { @@ -469,7 +478,12 @@ memcpy(num, buf, MIN(sizeof(num)-1, strlen(buf)-strlen(test))); else gf_assert(0); - return (atoi(num)*sign*3600); + int inum = atoi(num); + if (inum >= GF_INT_MAX/3600) { + GF_LOG( GF_LOG_WARNING, GF_LOG_CORE, ("Error parsing duration: %s cannot be represented as integer\n", num)); + return 0; + } + return (inum*sign*3600); } test = strstr(buf, "m"); if (test) { @@ -477,7 +491,12 @@ memcpy(num, buf, MIN(sizeof(num)-1, strlen(buf)-strlen(test))); else gf_assert(0); - return (atoi(num)*sign*60); + int inum = atoi(num); + if (inum >= GF_INT_MAX/60) { + GF_LOG( GF_LOG_WARNING, GF_LOG_CORE, ("Error parsing duration: %s cannot be represented as integer\n", num)); + return 0; + } + return (inum*sign*60); } return (atoi(buf) * sign); } @@ -492,12 +511,15 @@ GF_SDPTiming *timing; u32 i; s32 pos, LinePos; - char LineBuf3000, comp3000; + char LineBufSDP_MAX_LINE+1, compSDP_MAX_LINE+1; media = NULL; timing = NULL; if (!sdp) return GF_BAD_PARAM; + if (!gf_utf8_is_legal(sdp_text, text_size)) + return GF_NON_COMPLIANT_BITSTREAM; + #ifdef GPAC_ENABLE_COVERAGE if (gf_sys_is_cov_mode()) { @@ -510,58 +532,58 @@ LinePos = 0; while (1) { - LinePos = gf_token_get_line(sdp_text, LinePos, text_size, LineBuf, 3000); + LinePos = gf_token_get_line(sdp_text, LinePos, text_size, LineBuf, SDP_MAX_LINE); if (LinePos <= 0) break; if (!strcmp(LineBuf, "\r\n") || !strcmp(LineBuf, "\n") || !strcmp(LineBuf, "\r")) continue; pos=0; switch (LineBuf0) { case 'v': - /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, SDP_MAX_LINE); sdp->Version = atoi(comp); break; case 'o': //only use first one if (sdp->o_username) break; - pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, SDP_MAX_LINE); sdp->o_username = gf_strdup(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); sdp->o_session_id = gf_strdup(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); sdp->o_version = gf_strdup(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); sdp->o_net_type = gf_strdup(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); sdp->o_add_type = gf_strdup(comp); - /*pos = */gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); sdp->o_address = gf_strdup(comp); break; case 's': if (sdp->s_session_name) break; - /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, SDP_MAX_LINE); sdp->s_session_name = gf_strdup(comp); break; case 'i': if (sdp->i_description) break; - /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, SDP_MAX_LINE); sdp->i_description = gf_strdup(comp); break; case 'u': if (sdp->u_uri) break; - /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, SDP_MAX_LINE); sdp->u_uri = gf_strdup(comp); break; case 'e': if (sdp->e_email) break; - /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, SDP_MAX_LINE); sdp->e_email = gf_strdup(comp); break; case 'p': if (sdp->p_phone) break; - /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, 2, "\t\r\n", comp, SDP_MAX_LINE); sdp->p_phone = gf_strdup(comp); break; case 'c': @@ -570,21 +592,21 @@ conn = gf_sdp_conn_new(); - pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, SDP_MAX_LINE); conn->net_type = gf_strdup(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); conn->add_type = gf_strdup(comp); - pos = gf_token_get(LineBuf, pos, " /\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " /\r\n", comp, SDP_MAX_LINE); conn->host = gf_strdup(comp); if (gf_sk_is_multicast_address(conn->host)) { //a valid SDP will have TTL if address is multicast - pos = gf_token_get(LineBuf, pos, "/\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, "/\r\n", comp, SDP_MAX_LINE); if (pos > 0) { conn->TTL = atoi(comp); //multiple address indication is only valid for media - pos = gf_token_get(LineBuf, pos, "/\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, "/\r\n", comp, SDP_MAX_LINE); } if (pos > 0) { if (!media) { @@ -601,13 +623,13 @@ break; case 'b': - pos = gf_token_get(LineBuf, 2, ":\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, ":\r\n", comp, SDP_MAX_LINE); if (strcmp(comp, "CT") && strcmp(comp, "AS") && (comp0 != 'X')) break; GF_SAFEALLOC(bw, GF_SDPBandwidth); if (!bw) return GF_OUT_OF_MEM; bw->name = gf_strdup(comp); - /*pos = */gf_token_get(LineBuf, pos, ":\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, pos, ":\r\n", comp, SDP_MAX_LINE); bw->value = atoi(comp); if (media) { gf_list_add(media->Bandwidths, bw); @@ -621,22 +643,22 @@ //create a new time structure for each entry GF_SAFEALLOC(timing, GF_SDPTiming); if (!timing) return GF_OUT_OF_MEM; - pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, SDP_MAX_LINE); timing->StartTime = atoi(comp); - /*pos = */gf_token_get(LineBuf, pos, "\r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, pos, "\r\n", comp, SDP_MAX_LINE); timing->StopTime = atoi(comp); gf_list_add(sdp->Timing, timing); break; case 'r': if (media) break; - pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, SDP_MAX_LINE); if (!timing) return GF_NON_COMPLIANT_BITSTREAM; timing->RepeatInterval = SDP_MakeSeconds(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); timing->ActiveDuration = SDP_MakeSeconds(comp); while (pos>=0) { if (timing->NbRepeatOffsets == GF_SDP_MAX_TIMEOFFSET) break; - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); if (pos <= 0) break; timing->OffsetFromStarttiming->NbRepeatOffsets = SDP_MakeSeconds(comp); timing->NbRepeatOffsets += 1; @@ -647,24 +669,24 @@ pos = 2; if (!timing) return GF_NON_COMPLIANT_BITSTREAM; while (1) { - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); if (pos <= 0) break; if (timing->NbZoneOffsets >= GF_SDP_MAX_TIMEOFFSET) break; timing->AdjustmentTimetiming->NbZoneOffsets = atoi(comp); - pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, SDP_MAX_LINE); timing->AdjustmentOffsettiming->NbZoneOffsets = SDP_MakeSeconds(comp); timing->NbZoneOffsets += 1; } break; case 'k': if (sdp->k_method) break; - pos = gf_token_get(LineBuf, 2, ":\t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, ":\t\r\n", comp, SDP_MAX_LINE); if (media) { media->k_method = gf_strdup(comp); } else { sdp->k_method = gf_strdup(comp); } - pos = gf_token_get(LineBuf, pos, ":\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, ":\r\n", comp, SDP_MAX_LINE); if (pos > 0) { if (media) { media->k_key = gf_strdup(comp); @@ -677,7 +699,7 @@ SDP_ParseAttribute(sdp, LineBuf+2, media); break; case 'm': - pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, 3000); + pos = gf_token_get(LineBuf, 2, " \t\r\n", comp, SDP_MAX_LINE); if (strcmp(comp, "audio") && strcmp(comp, "data") && strcmp(comp, "control") @@ -695,21 +717,21 @@ else if (!strcmp(comp, "control")) media->Type = 5; else media->Type = 0; //port numbers - gf_token_get(LineBuf, pos, " ", comp, 3000); + gf_token_get(LineBuf, pos, " ", comp, SDP_MAX_LINE); if (!strstr(comp, "/")) { - pos = gf_token_get(LineBuf, pos, " \r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \r\n", comp, SDP_MAX_LINE); media->PortNumber = atoi(comp); media->NumPorts = 0; } else { - pos = gf_token_get(LineBuf, pos, " /\r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " /\r\n", comp, SDP_MAX_LINE); media->PortNumber = atoi(comp); - pos = gf_token_get(LineBuf, pos, " \r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \r\n", comp, SDP_MAX_LINE); media->NumPorts = atoi(comp); } //transport Profile - pos = gf_token_get(LineBuf, pos, " \r\n", comp, 3000); + pos = gf_token_get(LineBuf, pos, " \r\n", comp, SDP_MAX_LINE); media->Profile = gf_strdup(comp); - /*pos = */gf_token_get(LineBuf, pos, " \r\n", comp, 3000); + /*pos = */gf_token_get(LineBuf, pos, " \r\n", comp, SDP_MAX_LINE); media->fmt_list = gf_strdup(comp); gf_list_add(sdp->media_desc, media); @@ -728,7 +750,7 @@ strcpy(LineBuf, ""); while (1) { if (!media->fmt_list) break; - pos = gf_token_get(media->fmt_list, pos, " ", comp, 3000); + pos = gf_token_get(media->fmt_list, pos, " ", comp, SDP_MAX_LINE); if (pos <= 0) break; if (!SDP_IsDynamicPayload(media, comp)) { if (!LinePos) {
View file
gpac-2.4.0.tar.gz/src/isomedia/avc_ext.c -> gpac-26.02.0.tar.gz/src/isomedia/avc_ext.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -163,7 +163,7 @@ ref_extract_mode = GF_ISOM_NALU_EXTRACT_INSPECT; gf_isom_set_nalu_extract_mode(file, ref_track_num, ref_extract_mode); - ref_trak = gf_isom_get_track_from_file(file, ref_track_num); + ref_trak = gf_isom_get_track_box(file, ref_track_num); if (!ref_trak) return GF_ISOM_INVALID_FILE; if (!mdia->extracted_samp) { @@ -330,8 +330,12 @@ if (!mdia->nalu_parser) mdia->nalu_parser = gf_bs_new(sample->data, sample->dataLength, GF_BITSTREAM_READ); - else - gf_bs_reassign_buffer(mdia->nalu_parser, sample->data, sample->dataLength); + else { + GF_Err e = gf_bs_reassign_buffer(mdia->nalu_parser, sample->data, sample->dataLength); + if (e) { + return RAP_NO; + } + } if (!mdia->nalu_parser) return RAP_NO; @@ -634,7 +638,8 @@ mdia->in_sample_buffer_alloc = sample->dataLength; mdia->in_sample_buffer = gf_realloc(mdia->in_sample_buffer, sample->dataLength); } - memcpy(mdia->in_sample_buffer, sample->data, sample->dataLength); + if (sample->data && sample->dataLength) + memcpy(mdia->in_sample_buffer, sample->data, sample->dataLength); if (!mdia->nalu_parser) { mdia->nalu_parser = gf_bs_new(mdia->in_sample_buffer, sample->dataLength, GF_BITSTREAM_READ); @@ -657,7 +662,16 @@ gf_bs_get_content(mdia->nalu_out_bs, &output, &outSize); } - gf_bs_reassign_buffer(mdia->nalu_out_bs, sample->data, sample->alloc_size ? sample->alloc_size : sample->dataLength); + if (sample->data && sample->dataLength) { + e = gf_bs_reassign_buffer(mdia->nalu_out_bs, sample->data, sample->alloc_size ? sample->alloc_size : sample->dataLength); + if (e) { + // if we couldn't reassign here we need to decouple the bs from its buffer to avoid double frees later + u8 *output; + u32 outSize, allocSize; + gf_bs_get_content_no_truncate(mdia->nalu_out_bs, &output, &outSize, &allocSize); + gf_bs_reassign_buffer(mdia->nalu_out_bs, sample->data, sample->alloc_size ? sample->alloc_size : sample->dataLength); + } + } /*rewrite start code with NALU delim*/ if (rewrite_start_codes) { @@ -761,7 +775,7 @@ nal_size = gf_bs_read_int(mdia->nalu_parser, 8*nal_unit_size_field); if (gf_bs_get_position(mdia->nalu_parser) + nal_size > sample->dataLength) { GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("Sample %u (size %u) rewrite: corrupted NAL Unit (size %u)\n", sampleNumber, sample->dataLength, nal_size)); - goto exit; + break; } if (nal_size > mdia->tmp_nal_copy_buffer_alloc) { mdia->tmp_nal_copy_buffer_alloc = nal_size; @@ -904,6 +918,13 @@ if (sei_suffix_bs) gf_bs_del(sei_suffix_bs); + if (e && mdia->in_nalu_rewrite) { + // avoid double free later + u8 *output; + u32 outSize, allocSize; + gf_bs_get_content_no_truncate(mdia->nalu_out_bs, &output, &outSize, &allocSize); + } + mdia->in_nalu_rewrite = GF_FALSE; return e; } @@ -1114,7 +1135,7 @@ void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *mdia) { - GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)avc, GF_FALSE); + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate_box((GF_SampleEntryBox *)avc, GF_FALSE); if (avc->emul_esd) gf_odf_desc_del((GF_Descriptor *)avc->emul_esd); avc->emul_esd = gf_odf_desc_esd_new(2); @@ -1186,7 +1207,7 @@ void HEVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *hevc, GF_MediaBox *mdia) { - GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)hevc, GF_FALSE); + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate_box((GF_SampleEntryBox *)hevc, GF_FALSE); if (hevc->emul_esd) gf_odf_desc_del((GF_Descriptor *)hevc->emul_esd); hevc->emul_esd = gf_odf_desc_esd_new(2); @@ -1238,7 +1259,7 @@ GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd) { - GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)avc, GF_TRUE); + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate_box((GF_SampleEntryBox *)avc, GF_TRUE); GF_MPEG4ExtensionDescriptorsBox *mdesc = (GF_MPEG4ExtensionDescriptorsBox *) gf_isom_box_find_child(avc->child_boxes, GF_ISOM_BOX_TYPE_M4DS); if (mdesc) { @@ -1417,9 +1438,34 @@ return out; } +static GF_IAConfig* IAMF_DuplicateConfig(GF_IAConfig const * const cfg) +{ + u32 i = 0; + GF_IAConfig *out = gf_odf_iamf_cfg_new(); + if (!out) return NULL; + + out->configurationVersion = cfg->configurationVersion; + out->configOBUs_size = cfg->configOBUs_size; + + for (i = 0; i<gf_list_count(cfg->configOBUs); ++i) { + GF_IamfObu *dst = gf_malloc(sizeof(GF_IamfObu)), *src = gf_list_get(cfg->configOBUs, i); + if (!dst) { + gf_odf_iamf_cfg_del(out); + return NULL; + } + + dst->obu_length = src->obu_length; + dst->obu_type = src->obu_type; + dst->raw_obu_bytes = gf_malloc((size_t)dst->obu_length); + memcpy(dst->raw_obu_bytes, src->raw_obu_bytes, (size_t)src->obu_length); + gf_list_add(out->configOBUs, dst); + } + return out; +} + void AV1_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *av1, GF_MediaBox *mdia) { - GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)av1, GF_FALSE); + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate_box((GF_SampleEntryBox *)av1, GF_FALSE); if (av1->emul_esd) gf_odf_desc_del((GF_Descriptor *)av1->emul_esd); av1->emul_esd = gf_odf_desc_esd_new(2); @@ -1464,9 +1510,23 @@ return out; } +static GF_AVS3VConfig* AVS3V_DuplicateConfig(GF_AVS3VConfig const * const cfg) +{ + GF_AVS3VConfig *out = gf_odf_avs3v_cfg_new(); + if (out) { + out->configurationVersion = cfg->configurationVersion; + out->sequence_header_length = cfg->sequence_header_length; + out->sequence_header = gf_malloc(cfg->sequence_header_length); + memcpy(out->sequence_header, cfg->sequence_header, cfg->sequence_header_length); + out->library_dependency_idc = cfg->library_dependency_idc; + } + + return out; +} + void VP9_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *vp9, GF_MediaBox *mdia) { - GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)vp9, GF_FALSE); + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate_box((GF_SampleEntryBox *)vp9, GF_FALSE); if (vp9->emul_esd) gf_odf_desc_del((GF_Descriptor *)vp9->emul_esd); vp9->emul_esd = gf_odf_desc_esd_new(2); @@ -1520,10 +1580,10 @@ u32 dataRefIndex; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -1559,9 +1619,9 @@ u32 i; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return GF_BAD_PARAM; @@ -1713,6 +1773,7 @@ return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_add ? 1 : 2, GF_FALSE); } +GF_EXPORT GF_Err gf_isom_mvc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, Bool is_add) { return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_add ? 4 : 5, GF_FALSE); @@ -1724,9 +1785,9 @@ GF_Err e; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return GF_BAD_PARAM; @@ -1772,10 +1833,10 @@ GF_SampleDescriptionBox *stsd; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -1833,10 +1894,10 @@ GF_SampleDescriptionBox *stsd; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -1873,10 +1934,10 @@ GF_SampleDescriptionBox *stsd; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -1911,10 +1972,10 @@ GF_SampleDescriptionBox *stsd; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -1951,10 +2012,10 @@ GF_MPEGVisualSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -1980,6 +2041,86 @@ return e; } +GF_EXPORT +GF_Err gf_isom_avs3v_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVS3VConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) +{ + GF_TrackBox *trak; + GF_Err e; + u32 dataRefIndex; + GF_MPEGVisualSampleEntryBox *entry; + GF_SampleDescriptionBox *stsd; + + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; + + //get or create the data ref + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + if (!dataRefIndex) { + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + } + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + stsd = trak->Media->information->sampleTable->SampleDescription; + //create a new entry + entry = (GF_MPEGVisualSampleEntryBox *)gf_isom_box_new_parent(&stsd->child_boxes, GF_ISOM_BOX_TYPE_AVS3); + if (!entry) return GF_OUT_OF_MEM; + entry->avs3v_config = (GF_AVS3VConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_AV3C); + if (!entry->avs3v_config) return GF_OUT_OF_MEM; + entry->avs3v_config->config = AVS3V_DuplicateConfig(cfg); + if (!entry->avs3v_config->config) return GF_OUT_OF_MEM; + entry->dataReferenceIndex = dataRefIndex; + *outDescriptionIndex = gf_list_count(stsd->child_boxes); + return e; +} + +GF_EXPORT +GF_Err gf_isom_iamf_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_IAConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) +{ + GF_TrackBox *trak; + GF_Err e; + u32 dataRefIndex; + GF_MPEGAudioSampleEntryBox *entry; + GF_SampleDescriptionBox *stsd; + + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; + + //get or create the data ref + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + if (!dataRefIndex) { + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + } + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + stsd = trak->Media->information->sampleTable->SampleDescription; + //create a new entry + entry = (GF_MPEGAudioSampleEntryBox *)gf_isom_box_new_parent(&stsd->child_boxes, GF_ISOM_BOX_TYPE_IAMF); + if (!entry) return GF_OUT_OF_MEM; + entry->cfg_iamf = (GF_IAConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_IACB); + if (!entry->cfg_iamf) return GF_OUT_OF_MEM; + entry->cfg_iamf->cfg = IAMF_DuplicateConfig(cfg); + if (!entry->cfg_iamf->cfg) return GF_OUT_OF_MEM; + entry->dataReferenceIndex = dataRefIndex; + entry->channel_count = 0; + entry->bitspersample = 0; + entry->samplerate_hi = 0; + entry->samplerate_lo = 0; + *outDescriptionIndex = gf_list_count(stsd->child_boxes); + return e; +} + typedef enum { @@ -2039,9 +2180,9 @@ GF_MPEGVisualSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(stsd->child_boxes, DescriptionIndex-1); @@ -2233,9 +2374,9 @@ GF_MPEGVisualSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(stsd->child_boxes, DescriptionIndex-1); @@ -2328,7 +2469,7 @@ { GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_avc_svc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_AVCTYPE_NONE) return NULL; @@ -2352,7 +2493,7 @@ trackNumber = ref_track; } } - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_hevc_lhvc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_HEVCTYPE_NONE) return NULL; @@ -2369,7 +2510,7 @@ u32 type; GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_ISOM_VVCTYPE_NONE; if (!gf_isom_is_video_handler_type(trak->Media->handler->handlerType)) return GF_ISOM_VVCTYPE_NONE; @@ -2407,7 +2548,7 @@ u32 type; GF_MPEGVisualSampleEntryBox *entry; /*todo, add support for subpic track and nvcl tracks*/ - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; type = gf_isom_get_vvc_type(the_file, trackNumber, DescriptionIndex); if (type==GF_ISOM_VVCTYPE_NONE) @@ -2432,7 +2573,7 @@ { GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_avc_svc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_AVCTYPE_NONE) return NULL; @@ -2449,7 +2590,7 @@ { GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_avc_svc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_AVCTYPE_NONE) return NULL; @@ -2472,7 +2613,7 @@ trackNumber = ref_track; } } - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); if (!entry) return NULL; @@ -2481,13 +2622,12 @@ return AV1_DuplicateConfig(entry->av1_config->config); } - GF_EXPORT GF_VPConfig *gf_isom_vp_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) { GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); if (!entry) return NULL; @@ -2497,11 +2637,25 @@ } GF_EXPORT +GF_AVS3VConfig *gf_isom_avs3v_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +{ + GF_TrackBox *trak; + GF_MPEGVisualSampleEntryBox *entry; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media || !DescriptionIndex) return NULL; + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); + if (!entry) return NULL; + if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO) return NULL; + if (!entry->avs3v_config || !entry->avs3v_config->config) return NULL; + return AVS3V_DuplicateConfig(entry->avs3v_config->config); +} + +GF_EXPORT GF_DOVIDecoderConfigurationRecord *gf_isom_dovi_config_get(GF_ISOFile* the_file, u32 trackNumber, u32 DescriptionIndex) { GF_TrackBox* trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); if (!entry) return NULL; @@ -2511,12 +2665,26 @@ } GF_EXPORT +GF_IAConfig *gf_isom_iamf_config_get(GF_ISOFile* the_file, u32 trackNumber, u32 DescriptionIndex) +{ + GF_TrackBox* trak; + GF_MPEGAudioSampleEntryBox *entry; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media || !DescriptionIndex) return NULL; + entry = (GF_MPEGAudioSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); + if (!entry) return NULL; + if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO) return NULL; + if (!entry->cfg_iamf) return NULL; + return IAMF_DuplicateConfig(entry->cfg_iamf->cfg); +} + +GF_EXPORT GF_ISOMAVCType gf_isom_get_avc_svc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) { u32 type; GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->handler || !DescriptionIndex) return GF_ISOM_AVCTYPE_NONE; if (!gf_isom_is_video_handler_type(trak->Media->handler->handlerType)) return GF_ISOM_AVCTYPE_NONE; @@ -2562,7 +2730,7 @@ u32 type; GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_ISOM_HEVCTYPE_NONE; if (!gf_isom_is_video_handler_type(trak->Media->handler->handlerType)) return GF_ISOM_HEVCTYPE_NONE; @@ -2606,7 +2774,7 @@ GF_OperatingPointsInformation *oinf=NULL; GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_hevc_lhvc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_HEVCTYPE_NONE) return NULL; @@ -2833,16 +3001,19 @@ #ifndef GPAC_DISABLE_AV_PARSERS GF_NALUFFParam *sl = (GF_NALUFFParam*)gf_list_get(ptr->config->sequenceParameterSets, 0); if (sl) { - AVCState avc; + AVCState *avc_state; s32 idx; - memset(&avc, 0, sizeof(AVCState)); - idx = gf_avc_read_sps(sl->data, sl->size, &avc, 0, NULL); - if (idx>=0) { - ptr->config->chroma_format = avc.spsidx.chroma_format; - ptr->config->luma_bit_depth = 8 + avc.spsidx.luma_bit_depth_m8; - ptr->config->chroma_bit_depth = 8 + avc.spsidx.chroma_bit_depth_m8; + GF_SAFEALLOC(avc_state, AVCState); + if (avc_state) { + idx = gf_avc_read_sps(sl->data, sl->size, avc_state, 0, NULL); + if (idx>=0) { + ptr->config->chroma_format = avc_state->spsidx.chroma_format; + ptr->config->luma_bit_depth = 8 + avc_state->spsidx.luma_bit_depth_m8; + ptr->config->chroma_bit_depth = 8 + avc_state->spsidx.chroma_bit_depth_m8; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("isom/avcc Missing REXT profile signaling, patching using SPS.\n")); + gf_free(avc_state); } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("isom/avcc Missing REXT profile signaling, patching using SPS.\n")); } else #endif { @@ -3235,7 +3406,10 @@ pos = gf_bs_get_position(bs); ptr->config = gf_odf_av1_cfg_read_bs_size(bs, (u32) ptr->size); - + if (!ptr->config) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("ISOBMFF AV1ConfigurationBox invalid config\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } read = gf_bs_get_position(bs) - pos; if (read < ptr->size) @@ -3280,6 +3454,67 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *av3c_box_new() { + GF_AVS3VConfigurationBox *tmp = (GF_AVS3VConfigurationBox *)gf_malloc(sizeof(GF_AVS3VConfigurationBox)); + if (tmp == NULL) return NULL; + memset(tmp, 0, sizeof(GF_AVS3VConfigurationBox)); + tmp->type = GF_ISOM_BOX_TYPE_AV3C; + return (GF_Box *)tmp; +} + +void av3c_box_del(GF_Box *s) { + GF_AVS3VConfigurationBox *ptr = (GF_AVS3VConfigurationBox*)s; + if (ptr->config) gf_odf_avs3v_cfg_del(ptr->config); + gf_free(ptr); +} + +GF_Err av3c_box_read(GF_Box *s, GF_BitStream *bs) +{ + u64 pos; + GF_AVS3VConfigurationBox *ptr = (GF_AVS3VConfigurationBox*)s; + + if (ptr->config) gf_odf_avs3v_cfg_del(ptr->config); + ptr->config = NULL; + + pos = gf_bs_get_position(bs); + ptr->config = gf_odf_avs3v_cfg_read_bs(bs); + pos = gf_bs_get_position(bs) - pos ; + + if (pos < ptr->size) + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("ISOBMFF AVS3VConfigurationBox: read only "LLU" bytes (expected "LLU").\n", pos, ptr->size)); + if (pos > ptr->size) + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("ISOBMFF AVS3VConfigurationBox overflow read "LLU" bytes, of box size "LLU".\n", pos, ptr->size)); + + return ptr->config ? GF_OK : GF_ISOM_INVALID_FILE; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Err av3c_box_write(GF_Box *s, GF_BitStream *bs) { + GF_Err e; + GF_AVS3VConfigurationBox *ptr = (GF_AVS3VConfigurationBox*)s; + if (!s) return GF_BAD_PARAM; + if (!ptr->config) return GF_BAD_PARAM; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + return gf_odf_avs3v_cfg_write_bs(ptr->config, bs); +} + +GF_Err av3c_box_size(GF_Box *s) { + GF_AVS3VConfigurationBox *ptr = (GF_AVS3VConfigurationBox *)s; + if (!ptr->config) { + ptr->size = 0; + return GF_BAD_PARAM; + } + + ptr->size += 4; + ptr->size += ptr->config->sequence_header_length; + + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + void vpcc_box_del(GF_Box *s)
View file
gpac-2.4.0.tar.gz/src/isomedia/box_code_3gpp.c -> gpac-26.02.0.tar.gz/src/isomedia/box_code_3gpp.c
Changed
@@ -428,7 +428,7 @@ u32 next_size = gf_bs_peek_bits(bs, 32, 0); if (next_size > ptr->size) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Broken text box detected, skiping parsing.\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Broken text box detected, skipping parsing.\n")); ptr->textJustification = 1; return GF_OK; } @@ -1115,20 +1115,20 @@ gf_bs_write_int(bs, p->fullRequestHost, 1); gf_bs_write_int(bs, p->streamType, 1); gf_bs_write_int(bs, p->containsRedundant, 2); - if (p->textEncoding) - gf_bs_write_data(bs, p->textEncoding, (u32) strlen(p->textEncoding)); - gf_bs_write_u8(bs, 0); - if (p->contentEncoding) - gf_bs_write_data(bs, p->contentEncoding, (u32) strlen(p->contentEncoding)); - gf_bs_write_u8(bs, 0); + if (p->textEncoding) + gf_bs_write_data(bs, p->textEncoding, (u32) strlen(p->textEncoding)); + gf_bs_write_u8(bs, 0); + if (p->contentEncoding) + gf_bs_write_data(bs, p->contentEncoding, (u32) strlen(p->contentEncoding)); + gf_bs_write_u8(bs, 0); return GF_OK; } GF_Err dimC_box_size(GF_Box *s) { GF_DIMSSceneConfigBox *p = (GF_DIMSSceneConfigBox *)s; - s->size += 3 + 2; - if (p->textEncoding) s->size += strlen(p->textEncoding); - if (p->contentEncoding) s->size += strlen(p->contentEncoding); + s->size += 3 + 2; + if (p->textEncoding) s->size += strlen(p->textEncoding); + if (p->contentEncoding) s->size += strlen(p->contentEncoding); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/
View file
gpac-2.4.0.tar.gz/src/isomedia/box_code_apple.c -> gpac-26.02.0.tar.gz/src/isomedia/box_code_apple.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2006-2021 + * Copyright (c) Telecom ParisTech 2006-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -38,7 +38,7 @@ { GF_Err e; u32 sub_type; - GF_Box *a; + GF_Box *a = NULL; GF_ItemListBox *ptr = (GF_ItemListBox *)s; while (ptr->size) { /*if no ilst type coded, break*/ @@ -47,7 +47,7 @@ e = gf_isom_box_parse_ex(&a, bs, s->type, GF_FALSE, s->size); /* the macro will return in this case before we can free */ - if (!e && ptr->size < a->size) { + if (!e && a && ptr->size < a->size) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("isom not enough bytes in box %s: %d left, reading %d (file %s, line %d)\n", gf_4cc_to_str(ptr->type), ptr->size, a->size, __FILE__, __LINE__ )); \ e = GF_ISOM_INVALID_FILE; } @@ -55,6 +55,7 @@ if (a) gf_isom_box_del(a); return e; } + if (!a) return GF_NON_COMPLIANT_BITSTREAM; ISOM_DECREASE_SIZE(ptr, a->size); gf_list_add(ptr->child_boxes, a); @@ -175,6 +176,25 @@ return (GF_Box *)tmp; } +GF_Err ilst_item_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem) +{ + GF_ListItemBox *ptr = (GF_ListItemBox*)s; + switch (a->type) { + case GF_QT_BOX_TYPE_NAME: + BOX_FIELD_ASSIGN(name, GF_NameBox) + break; + case GF_QT_BOX_TYPE_MEAN: + BOX_FIELD_ASSIGN(mean, GF_NameBox) + break; + case GF_ISOM_BOX_TYPE_DATA: + BOX_FIELD_ASSIGN(data, GF_DataBox) + break; + default: + return GF_OK; + } + return GF_OK; +} + #ifndef GPAC_DISABLE_ISOM_WRITE GF_Err ilst_item_box_write(GF_Box *s, GF_BitStream *bs) @@ -214,6 +234,8 @@ /*iTune way: data-box-encapsulated box list*/ else if (ptr->data && !ptr->data->qt_style) { u32 pos=0; + gf_isom_check_position(s, (GF_Box* ) ptr->mean, &pos); + gf_isom_check_position(s, (GF_Box* ) ptr->name, &pos); gf_isom_check_position(s, (GF_Box* ) ptr->data, &pos); } /*QT way: raw data*/ @@ -240,7 +262,7 @@ GF_DataBox *ptr = (GF_DataBox *)s; ISOM_DECREASE_SIZE(ptr, 4); - ptr->reserved = gf_bs_read_u32(bs); + ptr->locale = gf_bs_read_u32(bs); if (ptr->size) { ptr->dataSize = (u32) ptr->size; @@ -269,7 +291,7 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; - gf_bs_write_int(bs, ptr->reserved, 32); + gf_bs_write_int(bs, ptr->locale, 32); if(ptr->data != NULL && ptr->dataSize > 0) { gf_bs_write_data(bs, ptr->data, ptr->dataSize); } @@ -422,7 +444,7 @@ meta = (GF_MetaBox *)gf_isom_box_new(udta_subtype); if (meta) { - udta_on_child_box((GF_Box *)mov->moov->udta, (GF_Box *)meta, GF_FALSE); + udta_on_child_box_ex((GF_Box *)mov->moov->udta, (GF_Box *)meta, GF_FALSE, GF_TRUE); if (meta_type!=1) { meta->handler = (GF_HandlerBox *)gf_isom_box_new_parent(&meta->child_boxes, GF_ISOM_BOX_TYPE_HDLR); if(meta->handler == NULL) { @@ -857,7 +879,7 @@ ptr->audio_descs = gf_malloc(sizeof(GF_AudioChannelDescription) * ptr->num_audio_description); if (!ptr->audio_descs) return GF_OUT_OF_MEM; - + for (i=0; i<ptr->num_audio_description; i++) { GF_AudioChannelDescription *adesc = &ptr->audio_descsi; ISOM_DECREASE_SIZE(s, 20);
View file
gpac-2.4.0.tar.gz/src/isomedia/box_code_base.c -> gpac-26.02.0.tar.gz/src/isomedia/box_code_base.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -26,7 +26,6 @@ #include <gpac/internal/isomedia_dev.h> - #ifndef GPAC_DISABLE_ISOM void co64_box_del(GF_Box *s) @@ -362,12 +361,12 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; - if (ptr->schemeURI) - gf_bs_write_data(bs, ptr->schemeURI, (u32) (strlen(ptr->schemeURI) + 1 )); - else - gf_bs_write_u8(bs, 0); + if (ptr->schemeURI) + gf_bs_write_data(bs, ptr->schemeURI, (u32) (strlen(ptr->schemeURI) + 1 )); + else + gf_bs_write_u8(bs, 0); - if (ptr->value) { + if (ptr->value) { gf_bs_write_data(bs, ptr->value, (u32) (strlen(ptr->value) + 1) ); } return GF_OK; @@ -377,7 +376,7 @@ { GF_KindBox *ptr = (GF_KindBox *)s; - ptr->size += (ptr->schemeURI ? strlen(ptr->schemeURI) : 0) + 1; + ptr->size += (ptr->schemeURI ? strlen(ptr->schemeURI) : 0) + 1; if (ptr->value) { ptr->size += strlen(ptr->value) + 1; } @@ -425,6 +424,8 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Invalid decodingOffset (%d) in entry #%i - defaulting to 0.\n", ptr->entriesi.decodingOffset, i)); ptr->entriesi.decodingOffset = 0; } + if (ptr->entriesi.decodingOffset<ptr->min_neg_cts_offset) + ptr->min_neg_cts_offset = ptr->entriesi.decodingOffset; } if (ptr->entriesi.decodingOffset == INT_MIN) { @@ -458,6 +459,8 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; + if (ptr->nb_entries && !ptr->entries) + return GF_ISOM_INVALID_MEDIA; gf_bs_write_u32(bs, ptr->nb_entries); for (i=0; i<ptr->nb_entries; i++ ) { gf_bs_write_u32(bs, ptr->entriesi.sampleCount); @@ -492,13 +495,24 @@ { GF_CompositionToDecodeBox *ptr = (GF_CompositionToDecodeBox *)s; - ISOM_DECREASE_SIZE(ptr, 20); - ptr->compositionToDTSShift = gf_bs_read_int(bs, 32); - ptr->leastDecodeToDisplayDelta = gf_bs_read_int(bs, 32); - ptr->greatestDecodeToDisplayDelta = gf_bs_read_int(bs, 32); - ptr->compositionStartTime = gf_bs_read_int(bs, 32); - ptr->compositionEndTime = gf_bs_read_int(bs, 32); - return GF_OK; + if (s->version == 0) { + ISOM_DECREASE_SIZE(ptr, 20); + ptr->compositionToDTSShift = gf_bs_read_int(bs, 32); + ptr->leastDecodeToDisplayDelta = gf_bs_read_int(bs, 32); + ptr->greatestDecodeToDisplayDelta = gf_bs_read_int(bs, 32); + ptr->compositionStartTime = gf_bs_read_int(bs, 32); + ptr->compositionEndTime = gf_bs_read_int(bs, 32); + return GF_OK; + } else if (s->version == 1) { + ISOM_DECREASE_SIZE(ptr, 40); + ptr->compositionToDTSShift = gf_bs_read_int(bs, 64); + ptr->leastDecodeToDisplayDelta = gf_bs_read_int(bs, 64); + ptr->greatestDecodeToDisplayDelta = gf_bs_read_int(bs, 64); + ptr->compositionStartTime = gf_bs_read_int(bs, 64); + ptr->compositionEndTime = gf_bs_read_int(bs, 64); + return GF_OK; + } + return GF_NOT_SUPPORTED; } GF_Box *cslg_box_new() @@ -516,20 +530,36 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; - gf_bs_write_int(bs, ptr->compositionToDTSShift, 32); - gf_bs_write_int(bs, ptr->leastDecodeToDisplayDelta, 32); - gf_bs_write_int(bs, ptr->greatestDecodeToDisplayDelta, 32); - gf_bs_write_int(bs, ptr->compositionStartTime, 32); - gf_bs_write_int(bs, ptr->compositionEndTime, 32); - return GF_OK; + if (s->version == 0) { + gf_bs_write_int(bs, (s32) ptr->compositionToDTSShift, 32); + gf_bs_write_int(bs, (s32) ptr->leastDecodeToDisplayDelta, 32); + gf_bs_write_int(bs, (s32) ptr->greatestDecodeToDisplayDelta, 32); + gf_bs_write_int(bs, (s32) ptr->compositionStartTime, 32); + gf_bs_write_int(bs, (s32) ptr->compositionEndTime, 32); + return GF_OK; + } else if (s->version == 1) { + gf_bs_write_long_int(bs, ptr->compositionToDTSShift, 64); + gf_bs_write_long_int(bs, ptr->leastDecodeToDisplayDelta, 64); + gf_bs_write_long_int(bs, ptr->greatestDecodeToDisplayDelta, 64); + gf_bs_write_long_int(bs, ptr->compositionStartTime, 64); + gf_bs_write_long_int(bs, ptr->compositionEndTime, 64); + return GF_OK; + } + return GF_NOT_SUPPORTED; } GF_Err cslg_box_size(GF_Box *s) { GF_CompositionToDecodeBox *ptr = (GF_CompositionToDecodeBox *)s; + if (s->version == 0) { + ptr->size += 20; + return GF_OK; + } else if (s->version == 1) { + ptr->size += 40; + return GF_OK; + } + return GF_NOT_SUPPORTED; - ptr->size += 20; - return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -1370,10 +1400,10 @@ u32 descSize = 0; GF_ESDBox *ptr = (GF_ESDBox *)s; //make sure we write with no ESID and no OCRESID - if (ptr->desc) { - ptr->desc->ESID = 0; - ptr->desc->OCRESID = 0; - } + if (ptr->desc) { + ptr->desc->ESID = 0; + ptr->desc->OCRESID = 0; + } e = gf_isom_full_box_write(s, bs); if (e) return e; e = gf_odf_desc_write((GF_Descriptor *)ptr->desc, &enc_desc, &descSize); @@ -1389,10 +1419,10 @@ u32 descSize = 0; GF_ESDBox *ptr = (GF_ESDBox *)s; //make sure we write with no ESID and no OCRESID - if (ptr->desc) { - ptr->desc->ESID = 0; - ptr->desc->OCRESID = 0; - } + if (ptr->desc) { + ptr->desc->ESID = 0; + ptr->desc->OCRESID = 0; + } descSize = gf_odf_desc_size((GF_Descriptor *)ptr->desc); ptr->size += descSize; return GF_OK; @@ -1805,17 +1835,18 @@ return (GF_Box *)tmp; } -GF_Err hinf_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem) +GF_Err hinf_on_child_box(GF_Box *s, GF_Box *_a, Bool is_rem) { GF_HintInfoBox *hinf = (GF_HintInfoBox *)s; - switch (a->type) { + switch (_a->type) { case GF_ISOM_BOX_TYPE_MAXR: if (!is_rem) { u32 i=0; + GF_MAXRBox *a = (GF_MAXRBox *)_a; GF_MAXRBox *maxR; while ((maxR = (GF_MAXRBox *)gf_list_enum(hinf->child_boxes, &i))) { - if ((maxR->type==GF_ISOM_BOX_TYPE_MAXR) && (maxR->granularity == ((GF_MAXRBox *)a)->granularity)) - ERROR_ON_DUPLICATED_BOX(a, s) + if ((maxR != a) && (maxR->type==GF_ISOM_BOX_TYPE_MAXR) && (maxR->granularity == a->granularity)) + ERROR_ON_DUPLICATED_BOX(_a, s) } } break; @@ -2663,7 +2694,7 @@ e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_u32(bs, ptr->payloadCode); - len = ptr->payloadString ? (u32) strlen(ptr->payloadString) : 0; + len = ptr->payloadString ? (u32) strlen(ptr->payloadString) : 0; gf_bs_write_u8(bs, len); if (len) gf_bs_write_data(bs, ptr->payloadString, len); return GF_OK; @@ -2709,7 +2740,7 @@ } GF_Box *name_box_new() { - ISOM_DECL_BOX_ALLOC(GF_NameBox, GF_ISOM_BOX_TYPE_NAME); + ISOM_DECL_BOX_ALLOC(GF_NameBox, GF_QT_BOX_TYPE_NAME); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE @@ -3037,7 +3068,7 @@ // https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34320 // to 3-letter codes (per ISO/IEC 639-2/T, https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) // NOTE that Media Info maps them to 2-letter codes, possibly with region codes https://github.com/MediaArea/MediaInfoLib/blob/72213574cbf2ca01c0fbb97d2239b53891ad7b9d/Source/MediaInfo/Multiple/File_Mpeg4.cpp#L754 -// NOTE that FFMPEG mostly maps to ISO/IEC 639-2/B and sometimes 2-letter code +// NOTE that FFmpeg mostly maps to ISO/IEC 639-2/B and sometimes 2-letter code // (see https://ffmpeg.org/doxygen/trunk/isom_8c_source.html) static const char* qtLanguages = { "eng", // 0 English @@ -3257,7 +3288,11 @@ GF_Err mdhd_box_size(GF_Box *s) { GF_MediaHeaderBox *ptr = (GF_MediaHeaderBox *)s; - ptr->version = (ptr->duration>0xFFFFFFFF) ? 1 : 0; + ptr->version = 0; + + if (ptr->duration!=(u64)-1 && (ptr->duration>0xFFFFFFFF)) ptr->version = 1; + if (ptr->creationTime!=(u64)-1 && (ptr->creationTime>0xFFFFFFFF)) ptr->version = 1; + if (ptr->modificationTime!=(u64)-1 && (ptr->modificationTime>0xFFFFFFFF)) ptr->version = 1; ptr->size += 4; ptr->size += (ptr->version == 1) ? 28 : 16; @@ -3278,6 +3313,10 @@ if (ptr->extracted_samp) gf_isom_sample_del(&ptr->extracted_samp); if (ptr->in_sample_buffer) gf_free(ptr->in_sample_buffer); if (ptr->tmp_nal_copy_buffer) gf_free(ptr->tmp_nal_copy_buffer); + if (ptr->information && ptr->information->dataHandler) { + gf_isom_datamap_del(ptr->information->dataHandler); + ptr->information->dataHandler = NULL; + } gf_free(ptr); } @@ -3930,6 +3969,7 @@ return GF_OK; case GF_ISOM_BOX_TYPE_TRAK: + case GF_ISOM_BOX_TYPE_EXTK: if (is_rem) { gf_list_del_item(ptr->trackList, a); return GF_OK; @@ -3948,8 +3988,13 @@ } return gf_list_add(ptr->trackList, a); case GF_QT_BOX_TYPE_CMVD: - ptr->has_cmvd = GF_TRUE; + ptr->has_cmvd = 1; break; + case GF_ISOM_BOX_TYPE_UNKNOWN: + if (((GF_UnknownBox*)a)->original_4cc == GF_QT_BOX_TYPE_CMOV) { + ptr->has_cmvd = 2; + break; + } } return GF_OK; } @@ -4041,6 +4086,10 @@ BOX_FIELD_ASSIGN(cfg_opus, GF_OpusSpecificBox) ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; break; + case GF_ISOM_BOX_TYPE_IACB: + BOX_FIELD_ASSIGN(cfg_iamf, GF_IAConfigurationBox) + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + break; case GF_ISOM_BOX_TYPE_DAC3: BOX_FIELD_ASSIGN(cfg_ac3, GF_AC3ConfigBox) ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; @@ -4048,6 +4097,9 @@ case GF_ISOM_BOX_TYPE_DEC3: BOX_FIELD_ASSIGN(cfg_ac3, GF_AC3ConfigBox) break; + case GF_ISOM_BOX_TYPE_DAC4: + BOX_FIELD_ASSIGN(cfg_ac4, GF_AC4ConfigBox) + break; case GF_ISOM_BOX_TYPE_DMLP: BOX_FIELD_ASSIGN(cfg_mlp, GF_TrueHDConfigBox) break; @@ -4071,29 +4123,29 @@ //wave subboxes may have been properly parsed if ((wave->original_4cc == GF_QT_BOX_TYPE_WAVE) && gf_list_count(wave->child_boxes)) { u32 i; - for (i =0; i<gf_list_count(wave->child_boxes); i++) { - GF_Box *inner_box = (GF_Box *)gf_list_get(wave->child_boxes, i); - if (inner_box->type == GF_ISOM_BOX_TYPE_ESDS) { - ptr->esd = (GF_ESDBox *)inner_box; + for (i =0; i<gf_list_count(wave->child_boxes); i++) { + GF_Box *inner_box = (GF_Box *)gf_list_get(wave->child_boxes, i); + if (inner_box->type == GF_ISOM_BOX_TYPE_ESDS) { + ptr->esd = (GF_ESDBox *)inner_box; if (ptr->qtff_mode & GF_ISOM_AUDIO_QTFF_CONVERT_FLAG) { - gf_list_rem(a->child_boxes, i); - drop_wave=GF_TRUE; - ptr->compression_id = 0; - gf_list_add(ptr->child_boxes, inner_box); + gf_list_rem(a->child_boxes, i); + drop_wave=GF_TRUE; + ptr->compression_id = 0; + gf_list_add(ptr->child_boxes, inner_box); } - } - } + } + } if (drop_wave) { gf_isom_box_del_parent(&ptr->child_boxes, a); - ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; ptr->version = 0; return GF_OK; } - ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; - return GF_OK; - } - gf_isom_box_del_parent(&ptr->child_boxes, a); - return GF_ISOM_INVALID_MEDIA; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; + return GF_OK; + } + gf_isom_box_del_parent(&ptr->child_boxes, a); + return GF_ISOM_INVALID_MEDIA; } ptr->qtff_mode &= ~GF_ISOM_AUDIO_QTFF_CONVERT_FLAG; @@ -4118,6 +4170,10 @@ cfg_ptr = (GF_Box **) &ptr->cfg_ac3; subtype = GF_ISOM_BOX_TYPE_DEC3; } + else if (s->type == GF_ISOM_BOX_TYPE_AC4) { + cfg_ptr = (GF_Box **) &ptr->cfg_ac4; + subtype = GF_ISOM_BOX_TYPE_DAC4; + } else if (s->type == GF_ISOM_BOX_TYPE_OPUS) { cfg_ptr = (GF_Box **) &ptr->cfg_opus; subtype = GF_ISOM_BOX_TYPE_DOPS; @@ -4145,28 +4201,28 @@ //wave subboxes may have been properly parsed if (gf_list_count(a->child_boxes)) { u32 i; - for (i =0; i<gf_list_count(a->child_boxes); i++) { - GF_Box *inner_box = (GF_Box *)gf_list_get(a->child_boxes, i); - if (inner_box->type == subtype) { - *cfg_ptr = inner_box; + for (i =0; i<gf_list_count(a->child_boxes); i++) { + GF_Box *inner_box = (GF_Box *)gf_list_get(a->child_boxes, i); + if (inner_box->type == subtype) { + *cfg_ptr = inner_box; if (ptr->qtff_mode & GF_ISOM_AUDIO_QTFF_CONVERT_FLAG) { - gf_list_rem(a->child_boxes, i); - drop_wave=GF_TRUE; - gf_list_add(ptr->child_boxes, inner_box); + gf_list_rem(a->child_boxes, i); + drop_wave=GF_TRUE; + gf_list_add(ptr->child_boxes, inner_box); } break; - } - } + } + } if (drop_wave) { gf_isom_box_del_parent(&ptr->child_boxes, a); - ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; ptr->compression_id = 0; ptr->version = 0; return GF_OK; } - ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; - return GF_OK; - } + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; + return GF_OK; + } } } ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; @@ -4317,6 +4373,11 @@ if (ptr->qtff_mode) return GF_OK; + if (ptr->version) { + gf_isom_check_position(s, gf_isom_box_find_child(ptr->child_boxes, GF_ISOM_BOX_TYPE_SRAT), &pos); + gf_isom_check_position(s, gf_isom_box_find_child(ptr->child_boxes, GF_ISOM_BOX_TYPE_CHNL), &pos); + } + gf_isom_check_position(s, (GF_Box *)ptr->esd, &pos); gf_isom_check_position(s, (GF_Box *)ptr->cfg_mha, &pos); gf_isom_check_position(s, (GF_Box *)ptr->cfg_3gpp, &pos); @@ -4324,6 +4385,11 @@ gf_isom_check_position(s, (GF_Box *)ptr->cfg_ac3, &pos); gf_isom_check_position(s, (GF_Box *)ptr->cfg_flac, &pos); gf_isom_check_position(s, (GF_Box *)ptr->cfg_mlp, &pos); + + if (!ptr->version) { + gf_isom_check_position(s, gf_isom_box_find_child(ptr->child_boxes, GF_ISOM_BOX_TYPE_SRAT), &pos); + gf_isom_check_position(s, gf_isom_box_find_child(ptr->child_boxes, GF_ISOM_BOX_TYPE_CHNL), &pos); + } return GF_OK; } @@ -4349,7 +4415,7 @@ GF_Box *gen_sample_entry_box_new() { - ISOM_DECL_BOX_ALLOC(GF_SampleEntryBox, GF_QT_SUBTYPE_C608);//type will be overriten + ISOM_DECL_BOX_ALLOC(GF_SampleEntryBox, GF_QT_SUBTYPE_C608);//type will be overwritten gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp); return (GF_Box *)tmp; } @@ -4438,7 +4504,7 @@ if (e) return e; gf_bs_write_data(bs, ptr->reserved, 6); gf_bs_write_u16(bs, ptr->dataReferenceIndex); - return GF_OK; + return GF_OK; } GF_Err mp4s_box_size(GF_Box *s) @@ -4447,7 +4513,7 @@ GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; s->size += 8; gf_isom_check_position(s, (GF_Box *)ptr->esd, &pos); - return GF_OK; + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -4498,6 +4564,9 @@ case GF_ISOM_BOX_TYPE_VPCC: BOX_FIELD_ASSIGN(vp_config, GF_VPConfigurationBox) break; + case GF_ISOM_BOX_TYPE_AV3C: + BOX_FIELD_ASSIGN(avs3v_config, GF_AVS3VConfigurationBox) + break; case GF_ISOM_BOX_TYPE_DVCC: case GF_ISOM_BOX_TYPE_DVVC: BOX_FIELD_ASSIGN(dovi_config, GF_DOVIConfigurationBox) @@ -4885,8 +4954,12 @@ GF_Err mvhd_box_size(GF_Box *s) { GF_MovieHeaderBox *ptr = (GF_MovieHeaderBox *)s; - if (ptr->duration==(u64) -1) ptr->version = 0; - else ptr->version = (ptr->duration>0xFFFFFFFF) ? 1 : 0; + ptr->version = 0; + + if (ptr->duration!=(u64)-1 && (ptr->duration>0xFFFFFFFF)) ptr->version = 1; + if (ptr->creationTime!=(u64)-1 && (ptr->creationTime>0xFFFFFFFF)) ptr->version = 1; + if (ptr->modificationTime!=(u64)-1 && (ptr->modificationTime>0xFFFFFFFF)) ptr->version = 1; + ptr->size += (ptr->version == 1) ? 28 : 16; ptr->size += 80; @@ -5291,6 +5364,10 @@ case GF_ISOM_BOX_TYPE_SAIO: BOX_FIELD_LIST_ASSIGN(sai_offsets) break; + case GF_GPAC_BOX_TYPE_SREF: + case GF_ISOM_BOX_TYPE_CDRF: + BOX_FIELD_ASSIGN(SampleRefs, GF_SampleReferences) + break; } return GF_OK; } @@ -5332,6 +5409,13 @@ if (ptr->SampleSize->sampleCount) { if (!ptr->TimeToSample->nb_entries || !ptr->SampleToChunk->nb_entries) return GF_ISOM_INVALID_FILE; + //safety check : get info for last sample, if error consider file is invalid + u64 sample_offset; + u32 di, chunk; + e = stbl_GetSampleInfos(ptr, ptr->SampleSize->sampleCount, &sample_offset, &chunk, &di, NULL); + if (e) return e; + e = stbl_GetSampleDTS(ptr->TimeToSample, ptr->SampleSize->sampleCount, &sample_offset); + if (e) return e; } u32 i, max_chunks=0; if (ptr->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) { @@ -6119,7 +6203,7 @@ GF_Err stts_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; - Bool logged=GF_FALSE; + u32 nb_patches=0, needs_patch = 0; GF_TimeToSampleBox *ptr = (GF_TimeToSampleBox *)s; #ifndef GPAC_DISABLE_ISOM_WRITE @@ -6140,25 +6224,18 @@ for (i=0; i<ptr->nb_entries; i++) { ptr->entriesi.sampleCount = gf_bs_read_u32(bs); ptr->entriesi.sampleDelta = gf_bs_read_u32(bs); -#ifndef GPAC_DISABLE_ISOM_WRITE - ptr->w_currentSampleNum += ptr->entriesi.sampleCount; - ptr->w_LastDTS += (u64)ptr->entriesi.sampleCount * ptr->entriesi.sampleDelta; -#endif - if (ptr->max_ts_delta<ptr->entriesi.sampleDelta) - ptr->max_ts_delta = ptr->entriesi.sampleDelta; - if (!ptr->entriesi.sampleDelta) { - if ((i+1<ptr->nb_entries) ) { - if (!logged) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Found stts entry with sample_delta=0 - forbidden ! Fixing to 1\n" )); - logged=GF_TRUE; - } - ptr->entriesi.sampleDelta = 1; - } else if (ptr->entriesi.sampleCount>1) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file more than one stts entry at the end of the track with sample_delta=0 - forbidden ! Fixing to 1\n" )); - ptr->entriesi.sampleDelta = 1; - } + //check for 0-duration + if (!ptr->entriesi.sampleDelta + //forbiden if not last sample + && ((i+1 < ptr->nb_entries) || (ptr->entriesi.sampleCount>1)) + ) { + needs_patch += ptr->entriesi.sampleCount; + if (i + 1 == ptr->nb_entries) needs_patch --; + ptr->entriesi.sampleDelta = 1; + nb_patches++; } + //cf issue 1644: some media streams may have sample duration > 2^31 (ttml mostly), we cannot patch this //for now we disable the check, one opt could be to have the check only for some media types, or only for the first entry #if 0 @@ -6168,9 +6245,56 @@ } #endif + //we patch exitsing sample_delta=0 to sample_delta=1 but we want to avoid breaking the timing of following samples + //accumulate the ticks added due to patching and remove them from previous entries - cf #3319 + if (needs_patch) { + s64 k; + //walk down the previous samples and try to shorten the duration + for (k=i; k>=0; k--) { + if (ptr->entriesk.sampleDelta == 1) continue; + //we only try to locate entries with a delta larger than the accumulated number of patches + if (ptr->entriesk.sampleDelta <= needs_patch) continue; + + if (ptr->entriesk.sampleCount == 1) { + ptr->entriesk.sampleDelta -= needs_patch; + needs_patch = 0; + break; + } + //split entry in two + if (ptr->nb_entries>=ptr->alloc_size) { + ptr->alloc_size += 1; + ptr->entries = gf_realloc(ptr->entries, sizeof(GF_SttsEntry) * ptr->alloc_size); + } + memmove(&ptr->entriesk+2, &ptr->entriesk+1, sizeof(GF_SttsEntry) * (ptr->nb_entries - k - 1) ); + ptr->entriesk.sampleCount -= 1; + ptr->entriesk+1.sampleCount = 1; + ptr->entriesk+1.sampleDelta = ptr->entriesk.sampleDelta - needs_patch; + i++; + ptr->nb_entries++; + needs_patch = 0; + break; + } + } + //needs_patch may still be non-0 if we couldn't find an entry, we'll adjust ts of next entries + + +#ifndef GPAC_DISABLE_ISOM_WRITE + ptr->w_currentSampleNum += ptr->entriesi.sampleCount; + ptr->w_LastDTS += (u64)ptr->entriesi.sampleCount * ptr->entriesi.sampleDelta; +#endif + if (ptr->max_ts_delta<ptr->entriesi.sampleDelta) + ptr->max_ts_delta = ptr->entriesi.sampleDelta; + } ISOM_DECREASE_SIZE(ptr, ptr->nb_entries*8); + if (nb_patches) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Found %u stts entries with forbidden sample_delta=0 - patching to 1\n", nb_patches)); + if (needs_patch) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file %u stts patches failed\n", needs_patch)); + } + } + //remove the last sample delta. #ifndef GPAC_DISABLE_ISOM_WRITE if (ptr->nb_entries) ptr->w_LastDTS -= ptr->entriesptr->nb_entries-1.sampleDelta; @@ -6464,8 +6588,12 @@ { GF_TrackHeaderBox *ptr = (GF_TrackHeaderBox *)s; - if (ptr->duration==(u64) -1) ptr->version = 0; - else ptr->version = (ptr->duration>0xFFFFFFFF) ? 1 : 0; + ptr->version = 0; + + if (ptr->duration!=(u64)-1 && (ptr->duration>0xFFFFFFFF)) ptr->version = 1; + if (ptr->creationTime!=(u64)-1 && (ptr->creationTime>0xFFFFFFFF)) ptr->version = 1; + if (ptr->modificationTime!=(u64)-1 && (ptr->modificationTime>0xFFFFFFFF)) ptr->version = 1; + ptr->size += (ptr->version == 1) ? 32 : 20; ptr->size += 60; return GF_OK; @@ -6507,6 +6635,9 @@ case GF_ISOM_BOX_TYPE_TFDT: BOX_FIELD_ASSIGN(tfdt, GF_TFBaseMediaDecodeTimeBox) return GF_OK; + case GF_ISOM_BOX_TYPE_RSOT: + BOX_FIELD_ASSIGN(rsot, GF_TFOriginalDurationBox) + return GF_OK; case GF_ISOM_BOX_TYPE_SUBS: BOX_FIELD_LIST_ASSIGN(sub_samples) return GF_OK; @@ -6544,6 +6675,10 @@ if (!is_rem) ptr->sample_encryption->traf = ptr; return GF_OK; + case GF_GPAC_BOX_TYPE_SREF: + case GF_ISOM_BOX_TYPE_CDRF: + BOX_FIELD_ASSIGN(SampleRefs, GF_SampleReferences) + return GF_OK; } return GF_OK; } @@ -6590,6 +6725,7 @@ gf_isom_check_position_list(s, ptr->sub_samples, &pos); gf_isom_check_position(s, (GF_Box *)ptr->tfdt, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->rsot, &pos); //cmaf-like if (ptr->truns_first) { @@ -6885,6 +7021,7 @@ case GF_ISOM_BOX_TYPE_OPUS: case GF_ISOM_BOX_TYPE_AC3: case GF_ISOM_BOX_TYPE_EC3: + case GF_ISOM_BOX_TYPE_AC4: case GF_QT_SUBTYPE_RAW_AUD: case GF_QT_SUBTYPE_TWOS: case GF_QT_SUBTYPE_SOWT: @@ -6925,7 +7062,10 @@ case GF_ISOM_BOX_TYPE_AV01: case GF_ISOM_BOX_TYPE_VP08: case GF_ISOM_BOX_TYPE_VP09: + case GF_ISOM_BOX_TYPE_VP10: case GF_ISOM_BOX_TYPE_AV1C: + case GF_ISOM_BOX_TYPE_AV3C: + case GF_ISOM_BOX_TYPE_AVS3: case GF_ISOM_BOX_TYPE_JPEG: case GF_ISOM_BOX_TYPE_PNG: case GF_ISOM_BOX_TYPE_JP2K: @@ -7011,7 +7151,7 @@ /*only process visual or audio note: no need for new_box_parent here since we always store sample descriptions in child_boxes*/ switch (trak->Media->handler->handlerType) { - case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_VISUAL: case GF_ISOM_MEDIA_AUXV: case GF_ISOM_MEDIA_PICT: { @@ -7060,6 +7200,9 @@ case GF_ISOM_BOX_TYPE_TKHD: BOX_FIELD_ASSIGN(Header, GF_TrackHeaderBox) return GF_OK; + case GF_ISOM_BOX_TYPE_EXTL: + BOX_FIELD_ASSIGN(extl, GF_ExternalTrackLocationBox) + return GF_OK; case GF_ISOM_BOX_TYPE_EDTS: BOX_FIELD_ASSIGN(editBox, GF_EditBox) return GF_OK; @@ -7103,13 +7246,15 @@ GF_TrackBox *ptr = (GF_TrackBox *)s; e = gf_isom_box_array_read(s, bs); if (e) return e; - e = gf_isom_check_sample_desc(ptr); - if (e) return e; - if (!ptr->Header) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Missing TrackHeaderBox\n")); return GF_ISOM_INVALID_FILE; } + if (ptr->extl) return GF_OK; + + e = gf_isom_check_sample_desc(ptr); + if (e) return e; + if (!ptr->Media) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Missing MediaBox\n")); return GF_ISOM_INVALID_FILE; @@ -7164,6 +7309,7 @@ GF_Err e = senc_Parse(ptr->moov->mov->movieFileMap->bs, ptr, NULL, ptr->sample_encryption, 0); if (e) return e; } + gf_isom_check_position(s, (GF_Box *)ptr->extl, &pos); gf_isom_check_position(s, (GF_Box *)ptr->Header, &pos); gf_isom_check_position(s, (GF_Box *)ptr->Aperture, &pos); @@ -8303,7 +8449,7 @@ return NULL; } -GF_Err udta_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem) +GF_Err udta_on_child_box_ex(GF_Box *s, GF_Box *a, Bool is_rem, Bool rem_same_type) { GF_Err e; u32 box_type; @@ -8345,22 +8491,29 @@ gf_list_del_item(map->boxes, a); return GF_OK; } - u32 i, count = gf_list_count(map->boxes); - for (i=0; i<count; i++) { - GF_Box *b = gf_list_get(map->boxes, i); - u32 btype = b->type; - if (b->type==GF_ISOM_BOX_TYPE_UNKNOWN) btype = ((GF_UnknownBox*)b)->original_4cc; - if (btype != box_type) continue; - if (box_type == GF_ISOM_BOX_TYPE_UUID) { - if (memcmp( ((GF_UUIDBox *)a)->uuid, ((GF_UUIDBox *)b)->uuid, 16)) continue; + + if (rem_same_type) { + u32 i, count = gf_list_count(map->boxes); + for (i=0; i<count; i++) { + GF_Box *b = gf_list_get(map->boxes, i); + u32 btype = b->type; + if (b->type==GF_ISOM_BOX_TYPE_UNKNOWN) btype = ((GF_UnknownBox*)b)->original_4cc; + if (btype != box_type) continue; + if (box_type == GF_ISOM_BOX_TYPE_UUID) { + if (memcmp( ((GF_UUIDBox *)a)->uuid, ((GF_UUIDBox *)b)->uuid, 16)) continue; + } + gf_isom_box_del(b); + gf_list_rem(map->boxes, i); + break; } - gf_isom_box_del(b); - gf_list_rem(map->boxes, i); - break; } + return gf_list_add(map->boxes, a); } - +GF_Err udta_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem) +{ + return udta_on_child_box_ex(s, a, is_rem, GF_FALSE); +} GF_Err udta_box_read(GF_Box *s, GF_BitStream *bs) { @@ -9043,7 +9196,7 @@ GF_AC3ConfigBox *ptr = (GF_AC3ConfigBox *)s; if (ptr == NULL) return GF_BAD_PARAM; pos = gf_bs_get_position(bs); - e = gf_odf_ac3_config_parse_bs(bs, ptr->cfg.is_ec3, &ptr->cfg); + e = gf_odf_ac3_cfg_parse_bs(bs, ptr->cfg.is_ec3, &ptr->cfg); if (e) return e; pos = gf_bs_get_position(bs) - pos; ISOM_DECREASE_SIZE(ptr, pos); @@ -9108,6 +9261,67 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *dac4_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_AC4ConfigBox, GF_ISOM_BOX_TYPE_DAC3); + return (GF_Box *)tmp; +} + +void dac4_box_del(GF_Box *s) +{ + GF_AC4ConfigBox *ptr = (GF_AC4ConfigBox *)s; + gf_odf_ac4_cfg_clean_list(&ptr->cfg); + gf_free(ptr); +} + + +GF_Err dac4_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u64 pos; + GF_AC4ConfigBox *ptr = (GF_AC4ConfigBox *)s; + if (ptr == NULL) return GF_BAD_PARAM; + + pos = gf_bs_get_position(bs); + e = gf_odf_ac4_cfg_parse_bs(bs, &ptr->cfg); + if (e) return e; + + pos = gf_bs_get_position(bs) - pos; + ISOM_DECREASE_SIZE(ptr, pos); + + //the rest is reserved + gf_bs_skip_bytes(bs, ptr->size); + ptr->size = 0; + return GF_OK; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err dac4_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_AC4ConfigBox *ptr = (GF_AC4ConfigBox *)s; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + e = gf_odf_ac4_cfg_write_bs(&ptr->cfg, bs); + if (e) return e; + + return GF_OK; +} + +GF_Err dac4_box_size(GF_Box *s) +{ + GF_AC4ConfigBox *ptr = (GF_AC4ConfigBox *)s; + + s->size += gf_odf_ac4_cfg_size(&ptr->cfg); + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + void lsrc_box_del(GF_Box *s) { @@ -9358,7 +9572,7 @@ ptr->subsegment_alloc = ptr->subsegment_count; GF_SAFE_ALLOC_N(ptr->subsegments, ptr->subsegment_count, GF_SubsegmentInfo); if (!ptr->subsegments) - return GF_OUT_OF_MEM; + return GF_OUT_OF_MEM; for (i = 0; i < ptr->subsegment_count; i++) { GF_SubsegmentInfo *subseg = &ptr->subsegmentsi; ISOM_DECREASE_SIZE(ptr, 4) @@ -9799,6 +10013,68 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *rsot_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_TFOriginalDurationBox, GF_ISOM_BOX_TYPE_RSOT); + return (GF_Box *)tmp; +} + +void rsot_box_del(GF_Box *s) +{ + gf_free(s); +} + +/*this is using chpl format according to some NeroRecode samples*/ +GF_Err rsot_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_TFOriginalDurationBox *ptr = (GF_TFOriginalDurationBox *)s; + + if (ptr->flags & 1) { + ISOM_DECREASE_SIZE(ptr, 4); + ptr->original_duration = gf_bs_read_u32(bs); + } + if (ptr->flags & 2) { + ISOM_DECREASE_SIZE(ptr, 4); + ptr->elapsed_duration = (u32) gf_bs_read_u32(bs); + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err rsot_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_TFOriginalDurationBox *ptr = (GF_TFOriginalDurationBox *) s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + if (ptr->flags & 1) { + gf_bs_write_u32(bs, ptr->original_duration); + } + if (ptr->flags & 2) { + gf_bs_write_u32(bs, (u32) ptr->elapsed_duration); + } + return GF_OK; +} + +GF_Err rsot_box_size(GF_Box *s) +{ + GF_TFOriginalDurationBox *ptr = (GF_TFOriginalDurationBox *)s; + + if (ptr->original_duration) { + ptr->flags |= 1; + ptr->size+=4; + } + if (ptr->elapsed_duration) { + ptr->flags |= 2; + ptr->size+=4; + } + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ @@ -9881,7 +10157,7 @@ ptr->entry_count = gf_bs_read_u32(bs); if (ptr->size < sizeof(GF_SampleGroupEntry)*ptr->entry_count || (u64)ptr->entry_count > (u64)SIZE_MAX/sizeof(GF_SampleGroupEntry)) - return GF_ISOM_INVALID_FILE; + return GF_ISOM_INVALID_FILE; ptr->sample_entries = gf_malloc(sizeof(GF_SampleGroupEntry)*ptr->entry_count); if (!ptr->sample_entries) return GF_OUT_OF_MEM; @@ -10145,6 +10421,7 @@ } break; + case GF_ISOM_SAMPLE_GROUP_AV1S: case GF_ISOM_SAMPLE_GROUP_TSAS: case GF_ISOM_SAMPLE_GROUP_STSA: null_size_ok = GF_TRUE; @@ -10283,8 +10560,15 @@ return def_ptr; } -void sgpd_del_entry(u32 grouping_type, void *entry) +void sgpd_del_entry(u32 grouping_type, void *entry, Bool is_opaque) { + if (is_opaque) { + GF_DefaultSampleGroupDescriptionEntry *ptr = (GF_DefaultSampleGroupDescriptionEntry *)entry; + if (ptr && ptr->data) gf_free(ptr->data); + gf_free(entry); + return; + } + switch (grouping_type) { case GF_ISOM_SAMPLE_GROUP_SYNC: case GF_ISOM_SAMPLE_GROUP_ROLL: @@ -10292,6 +10576,7 @@ case GF_ISOM_SAMPLE_GROUP_RAP: case GF_ISOM_SAMPLE_GROUP_TELE: case GF_ISOM_SAMPLE_GROUP_SAP: + case GF_ISOM_SAMPLE_GROUP_AV1S: gf_free(entry); return; case GF_ISOM_SAMPLE_GROUP_SEIG: @@ -10481,6 +10766,7 @@ return 20; case GF_ISOM_SAMPLE_GROUP_LBLI: return 2; + case GF_ISOM_SAMPLE_GROUP_AV1S: case GF_ISOM_SAMPLE_GROUP_TSAS: case GF_ISOM_SAMPLE_GROUP_STSA: return 0; @@ -10544,7 +10830,7 @@ GF_SampleGroupDescriptionBox *p = (GF_SampleGroupDescriptionBox *)a; while (gf_list_count(p->group_descriptions)) { void *ptr = gf_list_last(p->group_descriptions); - sgpd_del_entry(p->grouping_type, ptr); + sgpd_del_entry(p->grouping_type, ptr, p->is_opaque); gf_list_rem_last(p->group_descriptions); } gf_list_del(p->group_descriptions); @@ -10681,12 +10967,12 @@ if (ptr->default_sample_info_size == 0) { if (ptr->size < ptr->sample_count) - return GF_ISOM_INVALID_FILE; + return GF_ISOM_INVALID_FILE; ptr->sample_info_size = gf_malloc(sizeof(u8)*ptr->sample_count); ptr->sample_alloc = ptr->sample_count; if (!ptr->sample_info_size) - return GF_OUT_OF_MEM; + return GF_OUT_OF_MEM; ISOM_DECREASE_SIZE(ptr, ptr->sample_count); gf_bs_read_data(bs, (char *) ptr->sample_info_size, ptr->sample_count); @@ -11663,7 +11949,7 @@ if (ptr->headersi.data_length) { ptr->headersi.data_length = 4*ptr->headersi.data_length - 2; if (ptr->size < sizeof(char) * ptr->headersi.data_length) - return GF_ISOM_INVALID_FILE; + return GF_ISOM_INVALID_FILE; ptr->headersi.data = gf_malloc(sizeof(char) * ptr->headersi.data_length); if (!ptr->headersi.data) return GF_OUT_OF_MEM; @@ -12006,16 +12292,16 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; gf_bs_write_u32(bs, ptr->profile_version); - if (ptr->APID) - gf_bs_write_data(bs, ptr->APID, (u32) strlen(ptr->APID) ); - gf_bs_write_u8(bs, 0); + if (ptr->APID) + gf_bs_write_data(bs, ptr->APID, (u32) strlen(ptr->APID) ); + gf_bs_write_u8(bs, 0); return GF_OK; } GF_Err ainf_box_size(GF_Box *s) { GF_AssetInformationBox *ptr = (GF_AssetInformationBox *) s; - s->size += 4 + (ptr->APID ? strlen(ptr->APID) : 0 ) + 1; + s->size += 4 + (ptr->APID ? strlen(ptr->APID) : 0 ) + 1; return GF_OK; } @@ -12142,6 +12428,43 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *jp_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_JP2SignatureBox, GF_ISOM_BOX_TYPE_JP); + return (GF_Box *)tmp; +} + +void jp_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err jp_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_JP2SignatureBox *ptr = (GF_JP2SignatureBox *) s; + ISOM_DECREASE_SIZE(s, 4) + ptr->signature = gf_bs_read_u32(bs); + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err jp_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_JP2SignatureBox *ptr = (GF_JP2SignatureBox *) s; + GF_Err e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u32(bs, ptr->signature); + return GF_OK; +} + +GF_Err jp_box_size(GF_Box *s) +{ + s->size += 4; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ void jp2h_box_del(GF_Box *s) { @@ -12186,6 +12509,158 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *jp2p_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_JP2ProfileBox, GF_ISOM_BOX_TYPE_JP2P); + tmp->compatible_brands = gf_list_new(); + return (GF_Box *)tmp; +} + +void jp2p_box_del(GF_Box *s) +{ + GF_JP2ProfileBox *ptr = (GF_JP2ProfileBox *) s; + u32 i, count = gf_list_count(ptr->compatible_brands); + for (i=0; i<count; i++) { + u32 *brand = (u32 *)gf_list_get(ptr->compatible_brands, i); + gf_free(brand); + } + gf_list_del(ptr->compatible_brands); + gf_free(s); +} + +GF_Err jp2p_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_JP2ProfileBox *ptr = (GF_JP2ProfileBox *) s; + while (ptr->size) { + ISOM_DECREASE_SIZE_NO_ERR(s, 4) + u32 *brand = (u32 *)gf_malloc(sizeof(u32)); + if (!brand) return GF_OUT_OF_MEM; + *brand = gf_bs_read_u32(bs); + if (gf_list_add(ptr->compatible_brands, brand) != GF_OK) + return GF_OUT_OF_MEM; + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err jp2p_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_JP2ProfileBox *ptr = (GF_JP2ProfileBox *) s; + u32 i, count = gf_list_count(ptr->compatible_brands); + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + for (i=0; i<count; i++) { + u32 *brand = (u32 *)gf_list_get(ptr->compatible_brands, i); + gf_bs_write_u32(bs, *brand); + } + return GF_OK; +} + +GF_Err jp2p_box_size(GF_Box *s) +{ + GF_JP2ProfileBox *ptr = (GF_JP2ProfileBox *) s; + u32 count = gf_list_count(ptr->compatible_brands); + s->size += 4 * count; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +GF_Box *jsub_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_JP2SubSamplingBox, GF_ISOM_BOX_TYPE_JSUB); + return (GF_Box *)tmp; +} + +void jsub_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err jsub_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_JP2SubSamplingBox *ptr = (GF_JP2SubSamplingBox *) s; + ISOM_DECREASE_SIZE(s, 4) + ptr->horizontal_sub = gf_bs_read_u8(bs); + ptr->vertical_sub = gf_bs_read_u8(bs); + ptr->horizontal_offset = gf_bs_read_u8(bs); + ptr->vertical_offset = gf_bs_read_u8(bs); + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err jsub_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_JP2SubSamplingBox *ptr = (GF_JP2SubSamplingBox *) s; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + gf_bs_write_u8(bs, ptr->horizontal_sub); + gf_bs_write_u8(bs, ptr->vertical_sub); + gf_bs_write_u8(bs, ptr->horizontal_offset); + gf_bs_write_u8(bs, ptr->vertical_offset); + + return GF_OK; +} + +GF_Err jsub_box_size(GF_Box *s) +{ + s->size += 4; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +GF_Box *orfo_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_JP2OriginalFormatBox, GF_ISOM_BOX_TYPE_JSUB); + return (GF_Box *)tmp; +} + +void orfo_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err orfo_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_JP2OriginalFormatBox *ptr = (GF_JP2OriginalFormatBox *) s; + ISOM_DECREASE_SIZE(s, 2) + ptr->original_fieldcount = gf_bs_read_u8(bs); + ptr->original_fieldorder = gf_bs_read_u8(bs); + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err orfo_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_JP2OriginalFormatBox *ptr = (GF_JP2OriginalFormatBox *) s; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + gf_bs_write_u8(bs, ptr->original_fieldcount); + gf_bs_write_u8(bs, ptr->original_fieldorder); + + return GF_OK; +} + +GF_Err orfo_box_size(GF_Box *s) +{ + s->size += 2; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ void ihdr_box_del(GF_Box *s) { @@ -12443,6 +12918,52 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *iacb_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_IAConfigurationBox, GF_ISOM_BOX_TYPE_IACB); + return (GF_Box *)tmp; +} + +void iacb_box_del(GF_Box *s) +{ + GF_IAConfigurationBox *ptr = (GF_IAConfigurationBox *)s; + if (ptr->cfg) gf_odf_iamf_cfg_del(ptr->cfg); + gf_free(ptr); +} + +GF_Err iacb_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_IAConfigurationBox *ptr = (GF_IAConfigurationBox *)s; + if (ptr->cfg) gf_odf_iamf_cfg_del(ptr->cfg); + ptr->cfg = gf_odf_iamf_cfg_read_bs_size(bs, (u32)ptr->size); + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Err iacb_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_IAConfigurationBox *ptr = (GF_IAConfigurationBox *)s; + if (!s) return GF_BAD_PARAM; + if (!ptr->cfg) return GF_BAD_PARAM; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + return gf_odf_iamf_cfg_write_bs(ptr->cfg, bs); +} + +GF_Err iacb_box_size(GF_Box *s) +{ + GF_IAConfigurationBox *ptr = (GF_IAConfigurationBox *)s; + if (!ptr->cfg) { + ptr->size = 0; + return GF_BAD_PARAM; + } + ptr->size += gf_odf_iamf_cfg_size(ptr->cfg); + return GF_OK; +} +#endif /*GPAC_DISABLE_ISOM_WRITE*/ void dfla_box_del(GF_Box *s) { @@ -13227,6 +13748,271 @@ #endif // GPAC_DISABLE_ISOM_WRITE +GF_Box *silb_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_SchemeIdListBox, GF_ISOM_BOX_TYPE_SILB); + return (GF_Box *)tmp; +} + +void silb_box_del(GF_Box *s) +{ + GF_SchemeIdListBox *ptr = (GF_SchemeIdListBox *) s; + if (ptr == NULL) return; + while (gf_list_count(ptr->schemes)) { + GF_SchemeIdListBoxEntry *ent = (GF_SchemeIdListBoxEntry *)gf_list_get(ptr->schemes, 0); + gf_free(ent->scheme_id_uri); + gf_free(ent->value); + gf_free(ent); + gf_list_rem(ptr->schemes, 0); + } + gf_list_del(ptr->schemes); + gf_free(ptr); +} + +GF_Err silb_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_SchemeIdListBox *ptr = (GF_SchemeIdListBox*) s; + + ptr->number_of_schemes = gf_bs_read_u32(bs); + ISOM_DECREASE_SIZE(ptr, 4+ptr->number_of_schemes*sizeof(GF_SchemeIdListBoxEntry)+1); + if (ptr->number_of_schemes) { + ptr->schemes = gf_list_new(); + if (!ptr->schemes) return GF_OUT_OF_MEM; + + for (u32 i=0; i<ptr->number_of_schemes; i++) { + GF_SchemeIdListBoxEntry *ent = gf_malloc(sizeof(GF_SchemeIdListBoxEntry)); + if (!ent) return GF_OUT_OF_MEM; + memset(ent, 0, sizeof(GF_SchemeIdListBoxEntry)); + + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ent->scheme_id_uri); + if (e) { + gf_free(ent); + return e; + } + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ent->value); + if (e) { + if (ent->scheme_id_uri) gf_free(ent->scheme_id_uri); + gf_free(ent); + return e; + } + + ent->reserved = gf_bs_read_int(bs, 7); + ent->atleast_once_flag = gf_bs_read_int(bs, 1); + + gf_list_add(ptr->schemes, ent); + } + } + + ptr->reserved = gf_bs_read_int(bs, 7); + ptr->other_schemes_flag = gf_bs_read_int(bs, 1); + + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err silb_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_SchemeIdListBox *ptr = (GF_SchemeIdListBox*) s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_u32(bs, ptr->number_of_schemes); + for (u32 i=0; i<ptr->number_of_schemes; i++) { + GF_SchemeIdListBoxEntry *ent = gf_list_get(ptr->schemes, i); + + u32 len = ent->scheme_id_uri ? (u32) strlen(ent->scheme_id_uri) : 0; + if (len) gf_bs_write_data(bs, ent->scheme_id_uri, len); + gf_bs_write_u8(bs, 0); + + len = ent->value ? (u32) strlen(ent->value) : 0; + if (len) gf_bs_write_data(bs, ent->value, len); + gf_bs_write_u8(bs, 0); + + gf_bs_write_int(bs, ent->reserved, 7); + gf_bs_write_int(bs, ent->atleast_once_flag, 1); + } + + gf_bs_write_int(bs, ptr->reserved, 7); + gf_bs_write_int(bs, ptr->other_schemes_flag, 1); + + return GF_OK; +} + +GF_Err silb_box_size(GF_Box *s) +{ + GF_SchemeIdListBox *ptr = (GF_SchemeIdListBox*) s; + + ptr->size += 4; //number_of_schemes + for (u32 i=0; i<ptr->number_of_schemes; i++) { + GF_SchemeIdListBoxEntry *ent = gf_list_get(ptr->schemes, i); + if (!ent) return GF_BAD_PARAM; + ptr->size += 2; //2 NULL-terminated strings + if (ent->scheme_id_uri) ptr->size += strlen(ent->scheme_id_uri); + if (ent->value) ptr->size += strlen(ent->value); + ptr->size += 1; //reserved + atleast_once_flag + } + ptr->size += 1; //reserved + other_schemes_flag + + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + + +GF_Box *emib_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_EventMessageBox, GF_ISOM_BOX_TYPE_EMIB); + return (GF_Box *)tmp; +} + +void emib_box_del(GF_Box *s) +{ + GF_EventMessageBox *ptr = (GF_EventMessageBox *) s; + if (ptr == NULL) return; + if (ptr->scheme_id_uri) gf_free(ptr->scheme_id_uri); + if (ptr->value) gf_free(ptr->value); + if (ptr->message_data) gf_free(ptr->message_data); + gf_free(ptr); +} + +GF_Err emib_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_Err e; + GF_EventMessageBox *ptr = (GF_EventMessageBox*) s; + + ISOM_DECREASE_SIZE(ptr, 20); + gf_bs_read_u32(bs); /*reserved*/ + ptr->presentation_time_delta = gf_bs_read_u64(bs); + ptr->event_duration = gf_bs_read_u32(bs); + ptr->event_id = gf_bs_read_u32(bs); + + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->scheme_id_uri); + if (e) return e; + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->value); + if (e) return e; + + if (ptr->size) { + if (ptr->size>0xFFFFFFFUL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia emib message data size too big ("LLU") to be loaded\n", ptr->size)); + return GF_OUT_OF_MEM; + } + ptr->message_data_size = (u32) ptr->size; + ptr->message_data = gf_malloc(ptr->message_data_size); + if (!ptr->message_data) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->message_data, ptr->message_data_size); + ptr->size = 0; + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err emib_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 len; + GF_EventMessageBox *ptr = (GF_EventMessageBox*) s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u32(bs, 0); /*reserved*/ + gf_bs_write_u64(bs, ptr->presentation_time_delta); + gf_bs_write_u32(bs, ptr->event_duration); + gf_bs_write_u32(bs, ptr->event_id); + + len = ptr->scheme_id_uri ? (u32) strlen(ptr->scheme_id_uri) : 0; + if (len) gf_bs_write_data(bs, ptr->scheme_id_uri, len); + gf_bs_write_u8(bs, 0); + + len = ptr->value ? (u32) strlen(ptr->value) : 0; + if (len) gf_bs_write_data(bs, ptr->value, len); + gf_bs_write_u8(bs, 0); + + if (ptr->message_data) + gf_bs_write_data(bs, ptr->message_data, ptr->message_data_size); + + return GF_OK; +} + +GF_Err emib_box_size(GF_Box *s) +{ + GF_EventMessageBox *ptr = (GF_EventMessageBox*) s; + ptr->size += 20; + ptr->size += 2; //1 NULL-terminated strings + if (ptr->scheme_id_uri) ptr->size += strlen(ptr->scheme_id_uri); + if (ptr->value) ptr->size += strlen(ptr->value); + if (ptr->message_data) + ptr->size += ptr->message_data_size; + + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + + +GF_Box *emeb_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_Box, GF_ISOM_BOX_TYPE_EMEB); + return (GF_Box *)tmp; +} + +void emeb_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err emeb_box_read(GF_Box *s,GF_BitStream *bs) +{ + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err emeb_box_write(GF_Box *s, GF_BitStream *bs) +{ + return gf_isom_box_write_header(s, bs); +} + +GF_Err emeb_box_size(GF_Box *s) +{ + (void)s; + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + + +GF_Box *evte_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_Box, GF_ISOM_BOX_TYPE_EVTE); + return (GF_Box *)tmp; +} + +void evte_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err evte_box_read(GF_Box *s,GF_BitStream *bs) +{ + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err evte_box_write(GF_Box *s, GF_BitStream *bs) +{ + return gf_isom_box_write_header(s, bs); +} + +GF_Err evte_box_size(GF_Box *s) +{ + (void)s; + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + GF_Box *csgp_box_new() @@ -13846,4 +14632,920 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ + +void extl_box_del(GF_Box *s) +{ + GF_ExternalTrackLocationBox *ptr = (GF_ExternalTrackLocationBox *)s; + if (ptr->location) gf_free(ptr->location); + gf_free(ptr); + return; +} + + +GF_Err extl_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_ExternalTrackLocationBox *ptr = (GF_ExternalTrackLocationBox *)s; + + ISOM_DECREASE_SIZE(ptr, 8); + ptr->referenced_track_ID = gf_bs_read_u32(bs); + ptr->referenced_handler_type = gf_bs_read_u32(bs); + ISOM_DECREASE_SIZE(ptr, 4); + ptr->media_timescale = gf_bs_read_u32(bs); + + if (ptr->size) { + u32 name_size = (u32) ptr->size; + if (name_size < 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Invalid size %llu in hdlr\n", ptr->size)); + return GF_ISOM_INVALID_FILE; + } + ptr->location = (char*)gf_malloc(name_size+1); + if (!ptr->location) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->location, name_size); + ptr->locationname_size = 0; + } + return GF_OK; +} + +GF_Box *extl_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_ExternalTrackLocationBox, GF_ISOM_BOX_TYPE_EXTL); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err extl_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_ExternalTrackLocationBox *ptr = (GF_ExternalTrackLocationBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u32(bs, (u32) ptr->referenced_track_ID); + gf_bs_write_u32(bs, (u32) ptr->referenced_handler_type); + gf_bs_write_u32(bs, ptr->media_timescale); + + gf_bs_write_utf8(bs, ptr->location); + return GF_OK; +} + +GF_Err extl_box_size(GF_Box *s) +{ + GF_ExternalTrackLocationBox *ptr = (GF_ExternalTrackLocationBox *)s; + + ptr->size += 12; + ptr->size += 1 + (ptr->location ? (u32) strlen(ptr->location) : 0); + + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +//#define DUMP_STATS + +#ifdef DUMP_STATS +static u32 global_boxes=0; +static u32 global_samples=0; +static u32 global_cost=0; +static u32 global_cost_full=0; +static s32 global_cost_diff=0; +static u32 global_patterns_diff=0; +static s32 global_cost_nodiff=0; +static u32 global_patterns_nodiff=0; +static s32 global_cost_refs_sbgp=0; +#endif + + +GF_Box *sref_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_SampleReferences, GF_ISOM_BOX_TYPE_CDRF); + tmp->entries = gf_list_new(); + return (GF_Box *) tmp; +} + +void sref_box_del(GF_Box *s) +{ + GF_SampleReferences *ptr = (GF_SampleReferences*)s; + while (gf_list_count(ptr->entries)) { + GF_SampleRefEntry *ent = gf_list_pop_back(ptr->entries); + if (ent->sample_refs) gf_free(ent->sample_refs); + gf_free(ent); + } + gf_list_del(ptr->entries); + gf_free(s); + +#ifdef DUMP_STATS + if (global_boxes) + fprintf(stdout, "\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", global_boxes, global_samples, global_cost, global_cost_full, global_cost_diff, global_cost_nodiff, global_cost_refs_sbgp, global_patterns_diff, global_patterns_nodiff); +#endif +} + + +typedef struct +{ + Bool is_abs; + u32 orig_sampleID; + s32 diff_sampleID; + u32 nb_refs; + s32 *sample_refs; + s32 *orig_sample_refs; + + u32 flags; +} GF_SampleRefDiffEntry; + +static s32 read_signed_int(GF_BitStream *bs, u32 nb_bits, s32 max_val) +{ + s32 val = gf_bs_read_int(bs, nb_bits); + if (val<max_val) return val; + if (max_val>=GF_INT_MAX/2) return max_val; + return val - (2*max_val); +} + +GF_Err cdrf_box_read(GF_SampleReferences *s, GF_BitStream *bs) +{ + GF_Err e = GF_OK; + ISOM_DECREASE_SIZE(s, 1) + s->flags = gf_bs_read_u8(bs); + Bool use_nodiff = (s->flags & 16) ? GF_TRUE : GF_FALSE; + + u32 i, j; + u32 bits = 8, entry_bits=8; + if (s->flags & 1) bits = 16; + else if (s->flags & 2) bits = 32; + + s32 max_val = 1; + i=0; + while (i<bits-1) { + i++; + max_val = i<31 ? max_val<<1 : GF_INT_MAX; + } + s32 max_val2 = max_val>>1; + + if (s->flags & 4) entry_bits = 16; + else if (s->flags & 8) entry_bits = 32; + s->cdrf_entries = gf_bs_read_int(bs, entry_bits); + + GF_List *def_refs = gf_list_new(); + GF_List *samples = gf_list_new(); + for (i=0; i<s->cdrf_entries; i++) { + ISOM_DECREASE_SIZE_GOTO_EXIT(s, bits/8) + Bool is_ref = gf_bs_read_int(bs, 1); + if (!is_ref) { + GF_SampleRefDiffEntry *ent; + u32 nb_refs = gf_bs_read_int(bs, bits-1);; + ISOM_DECREASE_SIZE_GOTO_EXIT(s, (1+nb_refs)*bits/8) + GF_SAFEALLOC(ent, GF_SampleRefDiffEntry); + if (!ent) { + e = GF_OUT_OF_MEM; + goto exit; + } + gf_list_add(def_refs, ent); + gf_list_add(samples, ent); + ent->sample_refs = gf_malloc(sizeof(s32)*nb_refs); + if (!ent->sample_refs) { + e = GF_OUT_OF_MEM; + goto exit; + } + ent->nb_refs = nb_refs; + if (use_nodiff) { + ent->diff_sampleID = (s32) read_signed_int(bs, bits, max_val); + } else { + ent->is_abs = gf_bs_read_int(bs, 1); + ent->diff_sampleID = (s32) read_signed_int(bs, bits-1, max_val2); + } + for (j=0; j<nb_refs; j++) { + ent->sample_refsj = (s32) (s8) read_signed_int(bs, bits, max_val); + } + + } else { + ISOM_DECREASE_SIZE_GOTO_EXIT(s, 2*bits/8) + u32 offset = gf_bs_read_int(bs, bits-1); + u32 pattern_len = gf_bs_read_int(bs, bits); + u32 nb_samples = gf_bs_read_int(bs, bits); + u32 pattern_idx=0; + for (j=0; j<nb_samples; j++) { + u32 s_offset = offset + pattern_idx; + GF_SampleRefDiffEntry *ent = gf_list_get(samples, s_offset); + if (!ent) { + e = GF_ISOM_INVALID_FILE; + goto exit; + } + if (use_nodiff) { + GF_SampleRefDiffEntry *rent; + ISOM_DECREASE_SIZE_GOTO_EXIT(s, bits/8) + GF_SAFEALLOC(rent, GF_SampleRefDiffEntry); + if (!rent) { + e = GF_OUT_OF_MEM; + goto exit; + } + rent->diff_sampleID = (s32) read_signed_int(bs, bits, max_val); + rent->nb_refs = ent->nb_refs; + rent->sample_refs = ent->sample_refs; + rent->flags = 1; + gf_list_add(samples, rent); + } else { + gf_list_add(samples, ent); + } + pattern_idx++; + if (pattern_idx==pattern_len) pattern_idx=0; + } + } + } + + u32 prev_sample_id = 0; + while (gf_list_count(samples)) { + GF_SampleRefDiffEntry *ent = gf_list_pop_front(samples); + GF_SampleRefEntry *final_ent; + GF_SAFEALLOC(final_ent, GF_SampleRefEntry); + if (final_ent) { + final_ent->sample_refs = gf_malloc(sizeof(u32)*ent->nb_refs); + gf_list_add(s->entries, final_ent); + } + if (!final_ent || !final_ent->sample_refs) { + e = GF_OUT_OF_MEM; + goto exit; + } + if (use_nodiff) + final_ent->sampleID = ent->diff_sampleID; + else + if (ent->is_abs) final_ent->sampleID = ent->diff_sampleID; + else final_ent->sampleID = prev_sample_id + ent->diff_sampleID; + + prev_sample_id = final_ent->sampleID; + final_ent->nb_refs = ent->nb_refs; + for (j=0; j<ent->nb_refs;j++) { + s32 ref; + if (use_nodiff) + ref = ent->sample_refsj; + else + ref = final_ent->sampleID - ent->sample_refsj; + + assert(ref>=0); + final_ent->sample_refsj = ref; + } + if (ent->flags) { + gf_free(ent); + } + } + +exit: + if (use_nodiff) { + while (gf_list_count(samples)) { + GF_SampleRefDiffEntry *ent = gf_list_pop_front(samples); + if (gf_list_find(def_refs, ent) < 0) { + gf_free(ent); + } + } + } + gf_list_del(samples); + + while (gf_list_count(def_refs)) { + GF_SampleRefDiffEntry *ent = gf_list_pop_back(def_refs); + if (ent->sample_refs) gf_free(ent->sample_refs); + gf_free(ent); + } + gf_list_del(def_refs); + + return e; +} +GF_Err sref_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_SampleReferences *ptr = (GF_SampleReferences*)s; + if (ptr->type==GF_ISOM_BOX_TYPE_CDRF) { + return cdrf_box_read(ptr, bs); + } + ISOM_DECREASE_SIZE(s, 4) + + u32 bits = 8; + if (ptr->flags & (1<<1)) bits = 32; + else if (ptr->flags & (1)) bits = 16; + u32 max_val = (u32)(((u64)1<<bits) - 1); + u32 i, nb_entries = gf_bs_read_u32(bs); + if (nb_entries * bits / 8 > s->size) return GF_ISOM_INVALID_FILE; + for (i=0; i<nb_entries; i++) { + GF_SampleRefEntry *ent; + GF_SAFEALLOC(ent, GF_SampleRefEntry); + if (!ent) return GF_OUT_OF_MEM; + gf_list_add(ptr->entries, ent); + ISOM_DECREASE_SIZE(s, bits/8) + ent->sampleID = gf_bs_read_int(bs, bits); + if (ent->sampleID == max_val) + continue; + ISOM_DECREASE_SIZE(s, bits/8) + ent->nb_refs = gf_bs_read_int(bs, bits); + if (ent->nb_refs > s->size * 8 / bits) return GF_ISOM_INVALID_FILE; + if (ent->nb_refs) { + u32 j; + ent->sample_refs = gf_malloc(sizeof(u32) * ent->nb_refs); + for (j=0; j<ent->nb_refs; j++) { + ent->sample_refsj = gf_bs_read_int(bs, bits); + ISOM_DECREASE_SIZE(s, bits/8) + } + } + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +#ifdef DUMP_STATS +static Bool cdrf_sample_is_leaf(GF_SampleReferences *ptr, u32 sample_id) +{ + u32 i, j, nb_entries = gf_list_count(ptr->entries); + for (i=0; i<nb_entries; i++) { + GF_SampleRefEntry *ent = gf_list_get(ptr->entries, i); + for (j=0; j<ent->nb_refs; j++) { + if (ent->sample_refsj==sample_id) return GF_TRUE; + } + } + return GF_FALSE; +} +#endif + +static GF_List *cdrf_get_refs(GF_SampleReferences *ptr, s32 *out_max_id, s32 *out_max_id_nodiff) +{ + Bool all_intra=GF_TRUE; + s32 max_id_diff=-1; + s32 max_id_nodiff=-1; + u32 i, j, nb_entries = gf_list_count(ptr->entries); + u32 prev_sample_id = 0; + Bool prev_is_irap=GF_FALSE; + GF_List *refs = gf_list_new(); + for (i=0; i<nb_entries; i++) { + GF_SampleRefEntry *ent = gf_list_get(ptr->entries, i); + GF_SampleRefDiffEntry *rent; + GF_SAFEALLOC(rent, GF_SampleRefDiffEntry); + rent->diff_sampleID = rent->orig_sampleID = ent->sampleID; + if (ent->sampleID==GF_INT_MAX) { + rent->is_abs = GF_TRUE; + prev_is_irap = GF_FALSE; + } else if (!ent->nb_refs) { + //if prev is irap code as diff + if (prev_is_irap) { + rent->is_abs = GF_FALSE; + rent->diff_sampleID -= prev_sample_id; + } else { + rent->is_abs = GF_TRUE; + } + prev_is_irap = GF_TRUE; + } else { + prev_is_irap = GF_FALSE; + all_intra = GF_FALSE; + rent->nb_refs = ent->nb_refs; + rent->orig_sample_refs = ent->sample_refs; + rent->sample_refs = gf_malloc(sizeof(s32) * ent->nb_refs); + for (j=0; j<rent->nb_refs; j++) { + rent->sample_refsj = ent->sampleID; + rent->sample_refsj -= ent->sample_refsj; + + if (out_max_id) { + if (max_id_diff < ABS(rent->sample_refsj)) + max_id_diff = ABS(rent->sample_refsj); + + if (max_id_nodiff < rent->orig_sample_refsj) + max_id_nodiff = rent->orig_sample_refsj; + } + } + if (!i) + rent->is_abs = GF_TRUE; + else + rent->diff_sampleID -= prev_sample_id; + } +#ifdef DUMP_STATS + if (cdrf_sample_is_leaf(ptr, ent->sampleID)) + rent->flags = 1; +#endif + prev_sample_id = ent->sampleID; + gf_list_add(refs, rent); + + if (out_max_id) { + if (max_id_diff < rent->diff_sampleID) + max_id_diff = rent->diff_sampleID; + if (max_id_nodiff < rent->orig_sampleID) + max_id_nodiff = rent->orig_sampleID; + } + } + if (all_intra) { + for (i=0; i<nb_entries; i++) { + GF_SampleRefDiffEntry *ent = gf_list_get(refs, i); + ent->flags = 1; + } + } + + if (out_max_id) *out_max_id = max_id_diff; + if (out_max_id_nodiff) *out_max_id_nodiff = max_id_nodiff; + return refs; +} +static void cdrf_clean_refs(GF_List *refs) +{ + while (gf_list_count(refs)) { + GF_SampleRefDiffEntry *rent = gf_list_pop_back(refs); + if (rent->sample_refs) gf_free(rent->sample_refs); + gf_free(rent); + } + gf_list_del(refs); +} + +static Bool cdrf_same_entry(GF_SampleRefDiffEntry *ref_ent, GF_SampleRefDiffEntry *ref, u32 mode) +{ + //diff cdrf + if (mode==0) { + if (ref->is_abs != ref_ent->is_abs) return GF_FALSE; + if (ref->diff_sampleID != ref_ent->diff_sampleID) return GF_FALSE; + } +#ifdef DUMP_STATS + //refs + if (mode==2) { + //both leaf samples, check ID + if (!ref->flags || !ref_ent->flags) { + if (ref->orig_sampleID != ref_ent->orig_sampleID) return GF_FALSE; + } + } +#endif + if (ref->nb_refs != ref_ent->nb_refs) return GF_FALSE; + u32 k, l, match = 0; + s32 *ref_ent_ids = mode ? ref_ent->orig_sample_refs : ref_ent->sample_refs; + s32 *ref_ids = mode ? ref->orig_sample_refs : ref->sample_refs; + + for (k=0; k<ref_ent->nb_refs; k++) { + for (l=0; l<ref->nb_refs; l++) { + if (ref_idsl == ref_ent_idsk) { + match ++; + break; + } + } + } + if (match==ref_ent->nb_refs) return GF_TRUE; + return GF_FALSE; +} + + +static s32 cdrf_locate_first_ref(GF_SampleRefDiffEntry *ref_ent, u32 max_idx, GF_List *refs, s32 pattern_offset, u32 nb_in_pattern, u32 mode) +{ + u32 j; + if (pattern_offset>=0) { + GF_SampleRefDiffEntry *ref = gf_list_get(refs, pattern_offset); + //detect loop + if (cdrf_same_entry(ref_ent, ref, mode)) + return pattern_offset; + //detect following + pattern_offset += nb_in_pattern; + ref = gf_list_get(refs, pattern_offset); + if (cdrf_same_entry(ref_ent, ref, mode)) + return pattern_offset; + } + //browse from start + for (j=0; j<max_idx; j++) { + GF_SampleRefDiffEntry *ref = gf_list_get(refs, j); + if (cdrf_same_entry(ref_ent, ref, mode)) + return j; + } + return -1; +} + +static void cdrf_box_flush_pattern(GF_BitStream *bs, u32 pattern_len, s32 prev_offset, u32 nb_samples, u32 *sample_ids, u32 bits) +{ + gf_assert(pattern_len); + gf_assert(nb_samples); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_int(bs, prev_offset, bits-1); + gf_bs_write_int(bs, pattern_len, bits); + gf_bs_write_int(bs, nb_samples, bits); + + if (sample_ids) { + u32 j; + for (j=0; j<nb_samples; j++) + gf_bs_write_int(bs, sample_idsj, bits); + } +} + +GF_Err cdrf_box_write(GF_SampleReferences *ptr, GF_BitStream *bs) +{ + GF_List *refs = cdrf_get_refs(ptr, NULL, NULL); + u32 i, j; + u32 bits = 8, entry_bits=8; + if (ptr->flags & 1) bits = 16; + else if (ptr->flags & 2) bits = 32; + + if (ptr->flags & 4) entry_bits = 16; + else if (ptr->flags & 8) entry_bits = 32; + + gf_isom_box_write_header((GF_Box*)ptr, bs); + gf_bs_write_u8(bs, ptr->flags); + gf_bs_write_int(bs, ptr->cdrf_entries, entry_bits); + + u32 mode = (ptr->flags & 16) ? 1 : 0; + + s32 prev_offset = -1; + u32 prev_len = 0; + u32 pattern_len = 0; + u32 nb_samples = 0; + u32 nb_entries = gf_list_count(ptr->entries); + s32 *sample_ids = NULL; + if (mode) { + sample_ids = gf_malloc(sizeof(s32)*nb_entries); + if (!sample_ids) return GF_OUT_OF_MEM; + } + + for (i=0; i<nb_entries; i++) { + GF_SampleRefDiffEntry *rent = gf_list_get(refs, i); + s32 found = cdrf_locate_first_ref(rent, i, refs, prev_offset, prev_len, mode); + + if (found<0) { + if (prev_len) { + cdrf_box_flush_pattern(bs, pattern_len ? pattern_len : prev_len, prev_offset, nb_samples, sample_ids, bits); + } + //write direct sample + gf_bs_write_int(bs, 0, 1); + gf_bs_write_int(bs, rent->nb_refs, bits-1); + if (mode) { + gf_bs_write_int(bs, rent->orig_sampleID, bits); + for (j=0; j<rent->nb_refs; j++) + gf_bs_write_int(bs, rent->orig_sample_refsj, bits); + } else { + gf_bs_write_int(bs, rent->is_abs, 1); + gf_bs_write_int(bs, rent->diff_sampleID, bits-1); + for (j=0; j<rent->nb_refs; j++) + gf_bs_write_int(bs, rent->sample_refsj, bits); + } + + //reset pattern to empty + prev_offset = -1; + prev_len = 0; + pattern_len = 0; + nb_samples=0; + } else { + if (pattern_len && (pattern_len == prev_len)) { + prev_len = 0; + } + if ((prev_offset>=0) && (found == prev_offset+prev_len)) { + prev_len++; + if (sample_ids) + sample_idsnb_samples = rent->orig_sampleID; + nb_samples++; + continue; + } + if (!pattern_len && (found == prev_offset)) { + pattern_len = prev_len; + prev_len = 1; + if (sample_ids) + sample_idsnb_samples = rent->orig_sampleID; + nb_samples++; + continue; + } + if (prev_len || pattern_len) { + cdrf_box_flush_pattern(bs, pattern_len ? pattern_len : prev_len, prev_offset, nb_samples, sample_ids, bits); + } + + //reset pattern to one sample found + prev_offset = found; + pattern_len = 0; + prev_len = 1; + nb_samples = 1; + if (sample_ids) + sample_ids0 = rent->orig_sampleID; + } + } + if (prev_len) { + cdrf_box_flush_pattern(bs, pattern_len ? pattern_len : prev_len, prev_offset, nb_samples, sample_ids, bits); + } + if (sample_ids) + gf_free(sample_ids); + cdrf_clean_refs(refs); + return GF_OK; +} + +GF_Err sref_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_SampleReferences *ptr = (GF_SampleReferences*)s; + if (ptr->type==GF_ISOM_BOX_TYPE_CDRF) { + return cdrf_box_write(ptr, bs); + } + GF_Err e = gf_isom_full_box_write(s, bs); + if (e) return e; + + u32 bits = 8; + if (ptr->flags & (1<<1)) bits = 32; + else if (ptr->flags & (1)) bits = 16; + + u32 i, nb_entries = gf_list_count(ptr->entries); + gf_bs_write_u32(bs, nb_entries); + for (i=0; i<nb_entries; i++) { + GF_SampleRefEntry *ent = gf_list_get(ptr->entries, i); + if (ent->sampleID == GF_INT_MAX) { + gf_bs_write_int(bs, 0xFFFFFFFF, bits); + continue; + } + gf_bs_write_int(bs, ent->sampleID, bits); + gf_bs_write_int(bs, ent->nb_refs, bits); + if (ent->nb_refs) { + u32 j; + for (j=0; j<ent->nb_refs; j++) { + gf_bs_write_int(bs, ent->sample_refsj, bits); + } + } + } + return GF_OK; +} + +//#define DUMP_REFS + + +static u32 cdrf_get_cost(GF_List *refs, u32 mode, s32 max_id, u32 *cost_full, u32 *total_entries, u8 *flags) +{ + s32 prev_offset = -1; + u32 prev_len = 0; + u32 nb_samples = 0; + u32 pattern_len = 0; + +#ifdef DUMP_STATS + u32 cost_refs=0; + u32 cost_sbgp=0; +#endif + u32 i, nb_entries = gf_list_count(refs); + u32 nb_direct_cost=0, nb_direct_entries=0, nb_pattern_entries=0, nb_samples_reused=0; + *cost_full=0; + + for (i=0; i<nb_entries; i++) { + GF_SampleRefDiffEntry *rent = gf_list_get(refs, i); + s32 found = cdrf_locate_first_ref(rent, i, refs, prev_offset, prev_len, mode); + + *cost_full += 2 + rent->nb_refs; +#ifdef DUMP_REFS + Bool pattern_start=GF_FALSE; + Bool pattern_end=GF_FALSE; +#endif + if (found<0) { +#ifdef DUMP_STATS + if (mode==2) { + //need refs sample entry + length + cost_refs += 4 + 5+4*rent->nb_refs; + //need sgpd entry + cost_sbgp += 8; + continue; + } +#endif + if (prev_len > max_id) { + max_id = prev_len; + } + if (prev_len) { + gf_assert(nb_samples); + nb_pattern_entries += 1; +#ifdef DUMP_REFS + pattern_end=GF_TRUE; +#endif + } + if (prev_offset > max_id) + max_id = prev_offset; + + prev_offset = -1; + prev_len = 0; + pattern_len = 0; + nb_samples = 0; + nb_direct_entries += 1; + nb_direct_cost += 2 + rent->nb_refs; + } else { +#ifdef DUMP_STATS + if (mode==2) { + //reuse existing refs sample group, no refs extra cost + GF_SampleRefDiffEntry *prev_ent = gf_list_get(refs, i-1); + //same as previous, RLE from previous sbgp entry can be used + if (prev_ent && cdrf_same_entry(rent, prev_ent, 2)) { + + } else { + //need sgpd entry + cost_sbgp += 8; + } + continue; + } +#endif + nb_samples_reused += 1; + if (pattern_len && (pattern_len == prev_len)) { + prev_len = 0; + } + + if ((prev_offset>=0) && (found == prev_offset+prev_len)) { + prev_len++; + nb_samples ++; + } + else if (!pattern_len && (found == prev_offset)) { + pattern_len = prev_len; + prev_len = 1; + nb_samples ++; + } else { + if (prev_len > max_id) { + max_id = prev_len; + } + if (prev_len || pattern_len) { + nb_pattern_entries += 1; + gf_assert(nb_samples); +#ifdef DUMP_REFS + pattern_end=GF_TRUE; +#endif + } + if (prev_offset > max_id) + max_id = prev_offset; + + pattern_len = 0; + prev_offset = found; + prev_len = 1; + nb_samples = 1; +#ifdef DUMP_REFS + pattern_start=GF_TRUE; +#endif + } + } + +#ifdef DUMP_REFS + fprintf(stderr, "Sample #%d %d %d refs", i+1, rent->orig_sampleID, rent->diff_sampleID); + for (u32 j=0; j<rent->nb_refs; j++) fprintf(stderr, " %d", mode ? rent->orig_sample_refsj : rent->sample_refsj); + fprintf(stderr, " Pattern End %d Start %d", pattern_end, pattern_start); + if (found>=0) + fprintf(stderr, " reuse entry #%u", found+1); + fprintf(stderr, "\n"); +#endif + + } + if (prev_len || pattern_len) { + nb_pattern_entries += 1; + } + +#ifdef DUMP_STATS + if (mode==2) { + *flags = 0; + cost_refs += 24; //Full box + grouping type + length + nb_entries + cost_sbgp += 20; //FullBox + grouping_type + nb_entries + return cost_refs+cost_sbgp; + } +#endif + + *flags = 0; + u32 byte_s=1; + if (max_id > 32767) { + byte_s=4; + *flags |= 2; + } + else if (max_id > 255) { + byte_s=2; + *flags |= 1; + } + *total_entries = nb_pattern_entries + nb_direct_entries; + + u32 byte_e=1; + if (nb_pattern_entries + nb_direct_entries > 32767) { + byte_e=4; + *flags |= 8; + } + else if (nb_pattern_entries + nb_direct_entries > 255) { + byte_e=2; + *flags |= 4; + } + + //move to bytes + *cost_full *= byte_s; + //size+type+flags + *cost_full += 9; + + if (nb_entries > 32767) *cost_full += 4; + else if (nb_entries > 255) *cost_full += 2; + else *cost_full += 1; + + u32 final_size = 8 + 1 + byte_e; + final_size += nb_direct_cost * byte_s + nb_pattern_entries * 3 * byte_s; + if (mode==1) { + final_size += nb_samples_reused * byte_s; + *flags |= 16; + } + +#ifdef DUMP_STATS + if (mode==1) global_patterns_nodiff += nb_pattern_entries; + else global_patterns_diff += nb_pattern_entries; +#endif + + return final_size; +} +GF_Err cdrf_box_size(GF_SampleReferences *ptr) +{ + if (ptr->cdrf_cache_size) { + ptr->size = ptr->cdrf_cache_size; + return GF_OK; + } + s32 max_id = 0; + s32 max_id_nodiff = 0; + GF_List *refs = cdrf_get_refs(ptr, &max_id, &max_id_nodiff); + + u32 cost_full, nb_entries; + u8 flags; +#ifdef DUMP_STATS + //get cost for refs+sbgp + u32 final_size_refs_sbgp = cdrf_get_cost(refs, 2, max_id_nodiff, &cost_full, &nb_entries, &flags); +#endif + //get cost in diff mode + u32 final_size = cdrf_get_cost(refs, 0, max_id, &cost_full, &nb_entries, &flags); + + //get cost in nodiff mode + u32 nb_entries_nodiff; + u8 flags_nodiff; + u32 final_size_nodiff = cdrf_get_cost(refs, 1, max_id_nodiff, &cost_full, &nb_entries_nodiff, &flags_nodiff); + + if (final_size_nodiff < final_size) { + ptr->size = final_size_nodiff; + ptr->flags = flags_nodiff; + ptr->cdrf_entries = nb_entries_nodiff; + } else { + ptr->size = final_size; + ptr->flags = flags; + ptr->cdrf_entries = nb_entries; + } + ptr->cdrf_cache_size = ptr->size; + cdrf_clean_refs(refs); + +#ifdef DUMP_STATS + global_boxes+=1; + global_samples+=gf_list_count(ptr->entries); + global_cost += ptr->size; + global_cost_full += cost_full; + global_cost_diff += final_size; + global_cost_nodiff += final_size_nodiff; + global_cost_refs_sbgp += final_size_refs_sbgp; +#endif + return GF_OK; +} + +GF_Err sref_box_size(GF_Box *s) +{ + GF_SampleReferences *ptr = (GF_SampleReferences*)s; + if ((ptr->type!=GF_GPAC_BOX_TYPE_SREF) && gf_opts_get_bool("core", "no-cdrf")) { + ptr->type = GF_GPAC_BOX_TYPE_SREF; + } + if (ptr->type == GF_ISOM_BOX_TYPE_CDRF) { + return cdrf_box_size(ptr); + } + + u32 i, j, tot_entries = 0, nb_entries = gf_list_count(ptr->entries), nb_refs=0; + ptr->size += 4; + u32 max_size = 1; + for (i=0; i<nb_entries; i++) { + GF_SampleRefEntry *ent = gf_list_get(ptr->entries, i); + if (ent->sampleID==GF_INT_MAX) { + continue; + } + nb_refs++; + tot_entries += ent->nb_refs; + if (ent->sampleID >= 0xFFFF) max_size = 4; + else if (ent->sampleID >= 0xFF) max_size = MAX(2, max_size); + else max_size = MAX(1, max_size); + for (j=0; j<ent->nb_refs; j++) { + if (ent->sample_refsj > 0xFFFF) max_size = 4; + else if (ent->sample_refsj > 0xFF) max_size = MAX(2, max_size); + else max_size = MAX(1, max_size); + } + } + ptr->size += max_size*nb_entries + max_size*nb_refs + max_size*tot_entries; + if (max_size==4) ptr->flags = 1<<1; + else if (max_size==2) ptr->flags = 1; + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + + +GF_Box *srat_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_SamplingRateBox, GF_ISOM_BOX_TYPE_SRAT); + return (GF_Box *)tmp; +} + +void srat_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err srat_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_SamplingRateBox *ptr = (GF_SamplingRateBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) + ptr->sampling_rate = gf_bs_read_u32(bs); + return GF_OK; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err srat_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_SamplingRateBox *ptr = (GF_SamplingRateBox *)s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u32(bs, ptr->sampling_rate); + return GF_OK; +} + +GF_Err srat_box_size(GF_Box *s) +{ + s->size += 4; + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + #endif /*GPAC_DISABLE_ISOM*/
View file
gpac-2.4.0.tar.gz/src/isomedia/box_code_drm.c -> gpac-26.02.0.tar.gz/src/isomedia/box_code_drm.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Cyril Concolato - * Copyright (c) Telecom ParisTech 2005-2024 + * Copyright (c) Telecom ParisTech 2005-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -74,7 +74,7 @@ gf_isom_check_position(s, (GF_Box *)ptr->original_format, &pos); gf_isom_check_position(s, (GF_Box *)ptr->scheme_type, &pos); gf_isom_check_position(s, (GF_Box *)ptr->info, &pos); - return GF_OK; + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -293,16 +293,16 @@ if (!s) return GF_BAD_PARAM; e = gf_isom_full_box_write(s, bs); if (e) return e; - if (ptr->URI) - gf_bs_write_data(bs, ptr->URI, (u32) strlen(ptr->URI)); - gf_bs_write_u8(bs, 0); + if (ptr->URI) + gf_bs_write_data(bs, ptr->URI, (u32) strlen(ptr->URI)); + gf_bs_write_u8(bs, 0); return GF_OK; } GF_Err iKMS_box_size(GF_Box *s) { GF_ISMAKMSBox *ptr = (GF_ISMAKMSBox *)s; - ptr->size += (ptr->URI ? strlen(ptr->URI) : 0) + 1; + ptr->size += (ptr->URI ? strlen(ptr->URI) : 0) + 1; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -1060,6 +1060,7 @@ //as for senc, we skip parsing of the box until we have all saiz/saio info gf_bs_skip_bytes(bs, ptr->size); ptr->size = 0; + ptr->load_needed = GF_TRUE; return GF_OK; } @@ -1203,10 +1204,10 @@ ptr->private_data_size = gf_bs_read_u32(bs); if (ptr->size < ptr->private_data_size) - return GF_ISOM_INVALID_FILE; + return GF_ISOM_INVALID_FILE; ptr->private_data = gf_malloc(sizeof(char)*ptr->private_data_size); if (!ptr->private_data) - return GF_OUT_OF_MEM; + return GF_OUT_OF_MEM; ISOM_DECREASE_SIZE(ptr, ptr->private_data_size); gf_bs_read_data(bs, (char *) ptr->private_data, ptr->private_data_size); @@ -1256,48 +1257,6 @@ gf_free(s); } -#endif //ISOM - -u8 key_info_get_iv_size(const u8 *key_info, u32 key_info_size, u32 idx, u8 *const_iv_size, const u8 **const_iv) -{ - u32 i=0, kpos=3; - if (const_iv_size) *const_iv_size = 0; - if (const_iv) *const_iv = NULL; - - if (!key_info || !key_info_size) - return 0; - - while (1) { - u8 civ_size=0; - const u8 *civ = NULL; - u8 iv_size = key_infokpos; - kpos += 17; - - if (!iv_size) { - if (kpos>key_info_size) - break; - civ_size = key_infokpos; - civ = key_info + kpos + 1; - kpos += 1 + civ_size; - } - - if (kpos>key_info_size) - break; - - if (i+1==idx) { - if (const_iv_size) *const_iv_size = civ_size; - if (const_iv) *const_iv = civ; - return iv_size; - } - i++; - if (kpos==key_info_size) - break; - } - return 0; -} - -#ifndef GPAC_DISABLE_ISOM - #ifndef GPAC_DISABLE_ISOM_FRAGMENTS GF_Err senc_Parse(GF_BitStream *bs, GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_SampleEncryptionBox *senc, u32 max_nb_samples) #else @@ -1449,7 +1408,7 @@ u32 nb_iv_init = gf_bs_read_u16(bs); for (j=0; j<nb_iv_init; j++) { u32 idx = gf_bs_read_u16(bs); - IV_size = key_info_get_iv_size(key_info, key_info_size, idx, NULL, NULL); + IV_size = gf_cenc_key_info_get_iv_size(key_info, key_info_size, idx, NULL, NULL); if (!IV_size) { gf_isom_cenc_samp_aux_info_del(sai); GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("isobmf Failed to parse SENC box, invalid SAI multikey with IV size 0\n" )); @@ -1654,7 +1613,7 @@ GF_AdobeDRMKeyManagementSystemBox *ptr = (GF_AdobeDRMKeyManagementSystemBox *)s; gf_isom_check_position(s, (GF_Box *)ptr->header, &pos); gf_isom_check_position(s, (GF_Box *)ptr->au_format, &pos); - return GF_OK; + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE @@ -1699,7 +1658,7 @@ u32 pos=0; GF_AdobeDRMHeaderBox *ptr = (GF_AdobeDRMHeaderBox *)s; gf_isom_check_position(s, (GF_Box *)ptr->std_enc_params, &pos); - return GF_OK; + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE @@ -1747,7 +1706,7 @@ GF_AdobeStdEncryptionParamsBox *ptr = (GF_AdobeStdEncryptionParamsBox *)s; gf_isom_check_position(s, (GF_Box *)ptr->enc_info, &pos); gf_isom_check_position(s, (GF_Box *)ptr->key_info, &pos); - return GF_OK; + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE @@ -1855,7 +1814,7 @@ u32 pos=0; GF_AdobeKeyInfoBox *ptr = (GF_AdobeKeyInfoBox *)s; gf_isom_check_position(s, (GF_Box *)ptr->params, &pos); - return GF_OK; + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE
View file
gpac-2.4.0.tar.gz/src/isomedia/box_dump.c -> gpac-26.02.0.tar.gz/src/isomedia/box_dump.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -94,14 +94,20 @@ } -GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace) +GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace, u16 parent_internal_flags) { u32 i; GF_Box *a; if (!list) return GF_OK; i=0; while ((a = (GF_Box *)gf_list_enum(list, &i))) { - gf_isom_box_dump(a, trace); + if (parent_internal_flags & GF_ISOM_DUMP_SKIP_SIZE) { + a->internal_flags |= GF_ISOM_DUMP_SKIP_SIZE; + gf_isom_box_dump_ex(a, trace, GF_FALSE); + a->internal_flags &= ~GF_ISOM_DUMP_SKIP_SIZE; + } else { + gf_isom_box_dump_ex(a, trace, GF_FALSE); + } } return GF_OK; } @@ -134,12 +140,21 @@ while ((box = (GF_Box *)gf_list_enum(mov->TopBoxes, &i))) { if (box->type==GF_ISOM_BOX_TYPE_UNKNOWN) { - gf_fprintf(trace, "<!--WARNING: Unknown Top-level Box Found -->\n"); + switch (((GF_UnknownBox*)box)->original_4cc) { + case GF_ISOM_BOX_TYPE_CMOV: + case GF_ISOM_BOX_TYPE_CMOF: + case GF_ISOM_BOX_TYPE_CSIX: + case GF_ISOM_BOX_TYPE_CSSX: + case GF_QT_BOX_TYPE_CMOV: + break; + default: + gf_fprintf(trace, "<!--WARNING: Unknown Top-level Box Found -->\n"); + } } else if (box->type==GF_ISOM_BOX_TYPE_UUID) { } else if (!gf_isom_box_is_file_level(box)) { gf_fprintf(trace, "<!--ERROR: Invalid Top-level Box Found (\"%s\")-->\n", gf_4cc_to_str(box->type)); } - gf_isom_box_dump(box, trace); + gf_isom_box_dump_ex(box, trace, GF_TRUE); } gf_fprintf(trace, "</IsoMediaFile>\n"); @@ -435,39 +450,6 @@ } -static char *format_duration(u64 dur, u32 timescale, char *szDur) -{ - u32 h, m, s, ms; - if (!timescale) return NULL; - dur = (u32) (( ((Double) (s64) dur)/timescale)*1000); - h = (u32) (dur / 3600000); - dur -= h*3600000; - m = (u32) (dur / 60000); - dur -= m*60000; - s = (u32) (dur/1000); - dur -= s*1000; - ms = (u32) (dur); - - if (h<=24) { - sprintf(szDur, "%02d:%02d:%02d.%03d", h, m, s, ms); - } else { - u32 d = (u32) (dur / 3600000 / 24); - h = (u32) (dur/3600000)-24*d; - if (d<=365) { - sprintf(szDur, "%d Days, %02d:%02d:%02d.%03d", d, h, m, s, ms); - } else { - u32 y=0; - while (d>365) { - y++; - d-=365; - if (y%4) d--; - } - sprintf(szDur, "%d Years %d Days, %02d:%02d:%02d.%03d", y, d, h, m, s, ms); - } - } - return szDur; -} - static void dump_escape_string(FILE * trace, char *name) { u32 i, len = name ? (u32) strlen(name) : 0; @@ -487,18 +469,17 @@ if (p->size) { count = gf_list_count(p->list); for (i=0; i<count; i++) { - char szDur20; + char szDur100; GF_ChapterEntry *ce = (GF_ChapterEntry *)gf_list_get(p->list, i); gf_fprintf(trace, "<Chapter name=\""); dump_escape_string(trace, ce->name); - gf_fprintf(trace, "\" startTime=\"%s\" />\n", format_duration(ce->start_time, 1000*10000, szDur)); + gf_fprintf(trace, "\" startTime=\"%s\" />\n", gf_format_duration(ce->start_time, 1000*10000, szDur)); } } else { gf_fprintf(trace, "<Chapter name=\"\" startTime=\"\"/>\n"); } #ifdef GPAC_ENABLE_COVERAGE if (gf_sys_is_cov_mode()) { - format_duration(0, 0, NULL); dump_escape_string(NULL, NULL); } #endif @@ -566,14 +547,14 @@ GF_Err trak_box_dump(GF_Box *a, FILE * trace) { GF_TrackBox *p; - p = (GF_TrackBox *)a; - gf_isom_box_dump_start(a, "TrackBox", trace); + const char *name = p->extl ? "ExternalTrackBox" : "TrackBox"; + gf_isom_box_dump_start(a, name, trace); gf_fprintf(trace, ">\n"); if (p->size && !p->Header) { gf_fprintf(trace, "<!--INVALID FILE: Missing Track Header-->\n"); } - gf_isom_box_dump_done("TrackBox", a, trace); + gf_isom_box_dump_done(name, a, trace); return GF_OK; } @@ -720,22 +701,23 @@ gf_fprintf(trace, " DataReferenceIndex=\"%d\" Width=\"%d\" Height=\"%d\"", p->dataReferenceIndex, p->Width, p->Height); if (full_dump) { - Float dpih, dpiv; gf_fprintf(trace, " Version=\"%d\" Revision=\"%d\" Vendor=\"%s\" TemporalQuality=\"%d\" SpatialQuality=\"%d\" FramesPerSample=\"%d\" ColorTableIndex=\"%d\"", p->version, p->revision, gf_4cc_to_str(p->vendor), p->temporal_quality, p->spatial_quality, p->frames_per_sample, p->color_table_index); + } - dpih = (Float) (p->horiz_res&0xFFFF); - dpih /= 0xFFFF; - dpih += (p->vert_res>>16); - dpiv = (Float) (p->vert_res&0xFFFF); - dpiv /= 0xFFFF; - dpiv += (p->vert_res>>16); - - gf_fprintf(trace, " XDPI=\"%g\" YDPI=\"%g\" BitDepth=\"%d\"", dpih, dpiv, p->bit_depth); - } else { - //dump reserved info + Float dpih, dpiv; + dpih = (Float) (p->horiz_res&0xFFFF); + dpih /= 0xFFFF; + dpih += (p->vert_res>>16); + dpiv = (Float) (p->vert_res&0xFFFF); + dpiv /= 0xFFFF; + dpiv += (p->vert_res>>16); + if (gf_sys_is_test_mode()) { gf_fprintf(trace, " XDPI=\"%d\" YDPI=\"%d\" BitDepth=\"%d\"", p->horiz_res, p->vert_res, p->bit_depth); + } else { + gf_fprintf(trace, " XDPI=\"%g\" YDPI=\"%g\" BitDepth=\"%d\"", dpih, dpiv, p->bit_depth); } + if (strlen((const char*)p->compressor_name) ) { if (isalnum(p->compressor_name0)) { gf_fprintf(trace, " CompressorName=\"%s\"\n", p->compressor_name); @@ -846,6 +828,11 @@ if (!p->cfg_ac3) error = "<!--INVALID EC3 Entry: AC3Config not present in Audio Sample Description -->"; break; + case GF_ISOM_BOX_TYPE_AC4: + szName = "AC4SampleEntryBox"; + if (!p->cfg_ac4) + error = "<!--INVALID AC4 Entry: AC4Config not present in Audio Sample Description -->"; + break; case GF_ISOM_BOX_TYPE_MHA1: case GF_ISOM_BOX_TYPE_MHA2: if (!p->cfg_mha) @@ -903,7 +890,7 @@ gf_fprintf(trace, ">\n"); return; } - + GF_BitStream *bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); gf_bs_set_cookie(bs, GF_ISOM_BS_COOKIE_NO_LOGS); while (gf_bs_available(bs)) { @@ -919,7 +906,7 @@ gf_fprintf(trace, ">\n"); while (gf_list_count(list)) { GF_Box *a = gf_list_pop_front(list); - gf_isom_box_dump(a, trace); + gf_isom_box_dump_ex(a, trace, GF_FALSE); gf_isom_box_del(a); } } else { @@ -1008,7 +995,7 @@ i=0; while ((map = (GF_UserDataMap *)gf_list_enum(p->recordList, &i))) { - gf_isom_box_array_dump(map->boxes, trace); + gf_isom_box_array_dump(map->boxes, trace, a->internal_flags); } gf_isom_box_dump_done("UserDataBox", a, trace); return GF_OK; @@ -1580,7 +1567,7 @@ get_and_print("num_tile_rows_minus_one", 32) gf_fprintf(trace, ">\n"); - gf_bs_seek(bs, 10); + gf_bs_seek(bs, 12); for (i=0; i<nb_comps; i++) { gf_fprintf(trace, "<ComponentInfo"); get_and_print("index", 16) @@ -1602,7 +1589,7 @@ { u32 nb_cnames = GF_ARRAY_LENGTH(ctyp_names); if (ctype<nb_cnames) return ctyp_namesctype; - return "unknwon"; + return "unknown"; } static GF_Err dump_cmpd(GF_UnknownBox *u, FILE * trace) @@ -1650,10 +1637,15 @@ gf_fprintf(trace, ">\n"); nb_comps = gf_bs_read_u16(bs); + if (16*nb_comps > gf_bs_available(bs)) { + gf_bs_del(bs); + gf_isom_box_dump_done("ComponentPaletteBox", (GF_Box *)u, trace); + return GF_ISOM_INVALID_MEDIA; + } types = gf_malloc(sizeof(CompInfo) * nb_comps); if (!types) { gf_bs_del(bs); - gf_isom_box_dump_done("ComponentDefinitionBox", (GF_Box *)u, trace); + gf_isom_box_dump_done("ComponentPaletteBox", (GF_Box *)u, trace); return GF_OUT_OF_MEM; } for (i=0; i<nb_comps; i++) { @@ -1664,6 +1656,12 @@ gf_fprintf(trace, " bit_depth=\"%u\" type=\"%u\" name=\"%s\"/>\n", typesi.bits, typesi.type, get_comp_type_name(typesi.type) ); } nb_vals = gf_bs_read_u32(bs); + if (nb_vals/8 > gf_bs_available(bs)) { + gf_free(types); + gf_bs_del(bs); + gf_isom_box_dump_done("ComponentPaletteBox", (GF_Box *)u, trace); + return GF_ISOM_INVALID_MEDIA; + } for (j=0; j<nb_vals; j++) { gf_fprintf(trace, "<ComponentValue"); for (i=0; i<nb_comps; i++) { @@ -1671,26 +1669,27 @@ szTmp0 = 0; switch (typesi.type) { case 0: - sprintf(szTmp, " C%d=\"%u\"", i+1, gf_bs_read_int(bs, typesi.bits)); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"%u\"", i+1, gf_bs_read_int(bs, typesi.bits)); break; case 1: if (typesi.bits==32) - sprintf(szTmp, " C%d=\"%f\"", i+1, gf_bs_read_float(bs)); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"%g\"", i+1, gf_bs_read_float(bs)); else if (typesi.bits==64) - sprintf(szTmp, " C%d=\"%f\"", i+1, gf_bs_read_double(bs)); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"%g\"", i+1, gf_bs_read_double(bs)); else - sprintf(szTmp, " C%d=\"0x%X\"", i+1, gf_bs_read_int(bs, typesi.bits)); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"0x%X\"", i+1, gf_bs_read_int(bs, typesi.bits)); break; case 2: if (typesi.bits==64) - sprintf(szTmp, " C%d=\"%f + %fi\"", i+1, gf_bs_read_float(bs), gf_bs_read_float(bs)); - else if (typesi.bits==128) - sprintf(szTmp, " C%d=\"%f + %fi\"", i+1, gf_bs_read_double(bs), gf_bs_read_double(bs)); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"%g + %gi\"", i+1, gf_bs_read_float(bs), gf_bs_read_float(bs)); + else if (typesi.bits==128) { + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"%g + %gi\"", i+1, gf_bs_read_double(bs), gf_bs_read_double(bs)); + } else - sprintf(szTmp, " C%d=\"0x%X + 0x%Xi\"", i+1, gf_bs_read_int(bs, typesi.bits/2), gf_bs_read_int(bs, typesi.bits/2) ); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"0x%X + 0x%Xi\"", i+1, gf_bs_read_int(bs, typesi.bits/2), gf_bs_read_int(bs, typesi.bits/2) ); break; default: - sprintf(szTmp, " C%d=\"invalid", i+1); + snprintf(szTmp, GF_ARRAY_LENGTH(szTmp), " C%d=\"invalid", i+1); break; } gf_fprintf(trace, "%s", szTmp); @@ -1700,7 +1699,7 @@ gf_bs_del(bs); gf_free(types); - gf_isom_box_dump_done("ComponentDefinitionBox", (GF_Box *)u, trace); + gf_isom_box_dump_done("ComponentPaletteBox", (GF_Box *)u, trace); return GF_OK; } @@ -1735,7 +1734,7 @@ { u32 val, i, nb_comp, nb_r, nb_c, nb_p; GF_BitStream *bs = gf_bs_new(u->data, u->dataSize, GF_BITSTREAM_READ); - gf_isom_box_dump_start((GF_Box *)u, "SensorBrokenPixelMap", trace); + gf_isom_box_dump_start((GF_Box *)u, "SensorBadPixelsMap", trace); //full box get_and_print("version", 8) @@ -1780,7 +1779,7 @@ } gf_fprintf(trace, ">\n"); gf_bs_del(bs); - gf_isom_box_dump_done("ComponentPatternBox", (GF_Box *)u, trace); + gf_isom_box_dump_done("SensorBadPixelsMap", (GF_Box *)u, trace); return GF_OK; } @@ -1800,6 +1799,64 @@ return GF_OK; } +static GF_Err dump_taic(GF_UnknownBox *u, FILE * trace) +{ + u32 val; + GF_BitStream *bs = gf_bs_new(u->data, u->dataSize, GF_BITSTREAM_READ); + gf_isom_box_dump_start((GF_Box *)u, "TAIClockInfoBox", trace); + + //full box + get_and_print("version", 8) + get_and_print("flags", 24) + get_and_print("time_uncertainty", 64) + get_and_print("clock_resolution", 32) + s32 clock_drift_rate = (s32)(gf_bs_read_int(bs, 32)); + gf_fprintf(trace, " \"clock_drift_rate\"%d\"", clock_drift_rate); + get_and_print("clock_type", 2) + gf_fprintf(trace, ">\n"); + gf_bs_del(bs); + gf_isom_box_dump_done("TAIClockInfoBox", (GF_Box *)u, trace); + return GF_OK; +} + +static GF_Err dump_itai(GF_UnknownBox *u, FILE * trace) +{ + u32 val; + GF_BitStream *bs = gf_bs_new(u->data, u->dataSize, GF_BITSTREAM_READ); + gf_isom_box_dump_start((GF_Box *)u, "TAITimestampBox", trace); + + //full box + get_and_print("version", 8) + get_and_print("flags", 24) + get_and_print("TAI_timestamp", 64) + get_and_print("synchronization_state", 1) + get_and_print("timestamp_generation_failure", 1) + get_and_print("timestamp_is_modified", 1) + gf_fprintf(trace, ">\n"); + gf_bs_del(bs); + gf_isom_box_dump_done("TAITimestampBox", (GF_Box *)u, trace); + return GF_OK; +} + +static GF_Err dump_cmpc(GF_UnknownBox *u, FILE * trace) +{ + u32 val; + GF_BitStream *bs = gf_bs_new(u->data, u->dataSize, GF_BITSTREAM_READ); + gf_isom_box_dump_start((GF_Box *)u, "CompressionConfigurationBox", trace); + + //full box + get_and_print("version", 8) + get_and_print("flags", 24) + get_4cc_and_print("compression_type", 32) + // Check if this is in the final must_decompress_individual_units + get_and_print("must_decompress_individual_units", 1); + get_and_print("compressed_unit_type", 7) + gf_fprintf(trace, ">\n"); + gf_bs_del(bs); + gf_isom_box_dump_done("CompressionConfigurationBox", (GF_Box *)u, trace); + return GF_OK; +} + static GF_Err dump_fpac(GF_UnknownBox *u, FILE * trace) { u32 val; @@ -1841,9 +1898,58 @@ return GF_OK; } +static GF_Err dump_icef(GF_UnknownBox *u, FILE * trace) +{ + u32 val, num_compressed_units, unit_offset_code, unit_size_code, i; + GF_BitStream *bs = gf_bs_new(u->data, u->dataSize, GF_BITSTREAM_READ); + gf_isom_box_dump_start((GF_Box *)u, "GenericallyCompressedUnitsItemInfoBox", trace); + + //full box + get_and_print("version", 8) + get_and_print("flags", 24) + unit_offset_code = gf_bs_read_int(bs, 3); + unit_size_code = gf_bs_read_int(bs, 3); + gf_bs_read_int(bs, 2); + num_compressed_units = gf_bs_read_u32(bs); + gf_fprintf(trace, ">\n"); + for (i=0; i<num_compressed_units; i++) { + u64 extent_offset = 0; + u64 extent_size = 0; + if (unit_offset_code == 1) { + extent_offset = gf_bs_read_u16(bs); + } else if (unit_offset_code == 2) { + extent_offset = gf_bs_read_u24(bs); + } else if (unit_offset_code == 3) { + extent_offset = gf_bs_read_u32(bs); + } else if (unit_offset_code == 4) { + extent_offset = gf_bs_read_u64(bs); + } + if (unit_size_code == 0) { + extent_size = gf_bs_read_u8(bs); + } else if (unit_size_code == 1) { + extent_size = gf_bs_read_u16(bs); + } else if (unit_size_code == 2) { + extent_size = gf_bs_read_u24(bs); + } else if (unit_size_code == 3) { + extent_size = gf_bs_read_u32(bs); + } else if (unit_size_code == 4) { + extent_size = gf_bs_read_u64(bs); + } + if (unit_offset_code == 0) { + gf_fprintf(trace, "<compressed_unit_info extent_size=\"%u\"/>\n", extent_size); + } else { + gf_fprintf(trace, "<compressed_unit_info extent_offset=\"%u\" extent_size=\"%u\"/>\n", extent_offset, extent_size); + } + } + gf_bs_del(bs); + gf_isom_box_dump_done("GenericallyCompressedUnitsItemInfoBox", (GF_Box *)u, trace); + return GF_OK; +} + static GF_Err dump_dvc1(GF_UnknownBox *u, FILE * trace) { u32 val, pos; + if (!u || !u->data || !u->dataSize) return GF_BAD_PARAM; GF_BitStream *bs = gf_bs_new(u->data, u->dataSize, GF_BITSTREAM_READ); gf_isom_box_dump_start((GF_Box *)u, "VC1ConfigurationBox", trace); @@ -1903,12 +2009,37 @@ return dump_cloc(u, trace); } else if (u->original_4cc==GF_4CC('s','b','p','m')) { return dump_sbpm(u, trace); + } else if (u->original_4cc==GF_4CC('t','a','i','c')) { + return dump_taic(u, trace); + } else if (u->original_4cc==GF_4CC('i','t','a','i')) { + return dump_itai(u, trace); } else if (u->original_4cc==GF_4CC('f','p','a','c')) { return dump_fpac(u, trace); } else if (u->original_4cc==GF_4CC('G','M','C','C')) { return dump_gmcc(u, trace); } else if (u->original_4cc==GF_4CC('d','v','c','1')) { return dump_dvc1(u, trace); + } else if (u->original_4cc==GF_4CC('c','m','p','C')) { + return dump_cmpc(u, trace); + } else if (u->original_4cc==GF_4CC('i','c','e','f')) { + return dump_icef(u, trace); + } else if ((u->original_4cc==GF_ISOM_BOX_TYPE_CMOV) + || (u->original_4cc==GF_ISOM_BOX_TYPE_CMOF) + || (u->original_4cc==GF_ISOM_BOX_TYPE_CSIX) + || (u->original_4cc==GF_ISOM_BOX_TYPE_CSSX) + || (u->original_4cc==GF_QT_BOX_TYPE_CMOV) + ) { + char *bname = "CompressedMovieBox"; + char *spec = "p12"; + if (u->original_4cc==GF_ISOM_BOX_TYPE_CMOF) bname = "CompressedMovieFragmentBox"; + else if (u->original_4cc==GF_ISOM_BOX_TYPE_CSIX) bname = "CompressedSegmentIndexBox"; + else if (u->original_4cc==GF_ISOM_BOX_TYPE_CSSX) bname = "CompressedSubSegmentIndexBox"; + else if (u->original_4cc==GF_QT_BOX_TYPE_CMOV) spec = "apple"; + + gf_isom_box_dump_start_ex(a, bname, trace, GF_FALSE, spec, "file"); + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done(bname, a, trace); + return GF_OK; } else { #ifdef GPAC_HAS_QJS const char *opt = gf_opts_get_key("core", "boxdir"); @@ -2341,6 +2472,20 @@ return GF_OK; } + +GF_Err av3c_box_dump(GF_Box *a, FILE *trace) { + GF_AVS3VConfigurationBox *ptr = (GF_AVS3VConfigurationBox*)a; + gf_fprintf(trace, "<AVS3ConfigurationBox>\n"); + if (ptr->config) { + gf_fprintf(trace, "<AVS3Config version=\"%u\" library_dependency_idc=\"%u\" sequence_header_length=\"%u\" sequence_header=\"", (u32)ptr->config->configurationVersion, (u32)ptr->config->library_dependency_idc, (u32)ptr->config->sequence_header_length); + dump_data(trace, (char *)ptr->config->sequence_header, (u32)ptr->config->sequence_header_length); + gf_fprintf(trace, "\"/>\n"); + } + gf_fprintf(trace, "</AVS3ConfigurationBox>\n"); + return GF_OK; +} + + GF_Err SmDm_box_dump(GF_Box *a, FILE *trace) { GF_SMPTE2086MasteringDisplayMetadataBox * ptr = (GF_SMPTE2086MasteringDisplayMetadataBox *)a; if (!a) return GF_BAD_PARAM; @@ -2571,7 +2716,7 @@ GF_Err meta_box_dump(GF_Box *a, FILE * trace) { GF_MetaBox *ptr = (GF_MetaBox *)a; - gf_isom_box_dump_start_ex(a, "MetaBox", trace, ptr->is_qt ? GF_FALSE : GF_TRUE); + gf_isom_box_dump_start_ex(a, "MetaBox", trace, ptr->is_qt ? GF_FALSE : GF_TRUE, NULL, NULL); gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MetaBox", a, trace); return GF_OK; @@ -3067,11 +3212,11 @@ gf_fprintf(trace, " IsLeading=\"%d\" DependsOn=\"%d\"", GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags)); } else if (field_idx==2) { gf_fprintf(trace, " IsLeading=\"%d\" DependsOn=\"%d\" IsDependedOn=\"%d\" HasRedundancy=\"%d\" SamplePadding=\"%d\" Sync=\"%d\"", - GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags), GF_ISOM_GET_FRAG_PAD(flags), GF_ISOM_GET_FRAG_SYNC(flags)); + GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags), GF_ISOM_GET_FRAG_PAD(flags), GF_ISOM_GET_FRAG_SYNC(flags)); } else { gf_fprintf(trace, " SamplePadding=\"%d\" Sync=\"%d\" DegradationPriority=\"%d\" IsLeading=\"%d\" DependsOn=\"%d\" IsDependedOn=\"%d\" HasRedundancy=\"%d\"", - GF_ISOM_GET_FRAG_PAD(flags), GF_ISOM_GET_FRAG_SYNC(flags), GF_ISOM_GET_FRAG_DEG(flags), - GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags)); + GF_ISOM_GET_FRAG_PAD(flags), GF_ISOM_GET_FRAG_SYNC(flags), GF_ISOM_GET_FRAG_DEG(flags), + GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags)); } } @@ -3199,7 +3344,7 @@ if (full_dump) { for (i=0; i<p->nb_samples; i++) { GF_TrunEntry *ent = &p->samplesi; - + gf_fprintf(trace, "<TrackRunEntry"); #ifdef GF_ENABLE_CTRN @@ -3324,7 +3469,7 @@ GF_RTPPacket *pck; char *szName; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; tmp = gf_isom_get_sample(the_file, trackNumber, SampleNum, &descIndex); @@ -3598,7 +3743,8 @@ gf_fprintf(dump, " xml:space=\"preserve\">"); if (s_txt->len) { - unsigned short utf16Line10000; + unsigned short *utf16Line = gf_malloc( sizeof(u16) * (s_txt->len/2)*2 + 4 ); + if (!utf16Line) return; /*UTF16*/ if ((s_txt->len>2) && ((unsigned char) s_txt->text0 == (unsigned char) 0xFE) && ((unsigned char) s_txt->text1 == (unsigned char) 0xFF)) { /*copy 2 more chars because the lib always add 2 '0' at the end for UTF16 end of string*/ @@ -3607,7 +3753,7 @@ } else { char *str; str = s_txt->text; - len = gf_utf8_mbstowcs((u16*)utf16Line, 10000, (const char **) &str); + len = gf_utf8_mbstowcs((u16*)utf16Line, s_txt->len+1, (const char **) &str); } if (len != GF_UTF8_FAIL) { utf16Linelen = 0; @@ -3648,6 +3794,7 @@ } } } + if (utf16Line) gf_free(utf16Line); } if (box_dump) { @@ -3730,7 +3877,7 @@ Bool box_dump = (dump_type==GF_TEXTDUMPTYPE_TTXT_BOXES) ? GF_TRUE : GF_FALSE; Bool skip_empty = (dump_type==GF_TEXTDUMPTYPE_TTXT_CHAP) ? GF_TRUE : GF_FALSE; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, track); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, track); if (!trak) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { case GF_ISOM_MEDIA_TEXT: @@ -3860,112 +4007,114 @@ u32 len, j, k; if (!txt || !txt->len) { gf_fprintf(dump, "\n"); - } else { - u32 styles, char_num, new_styles, color, new_color; - u16 utf16Line10000; + return GF_OK; + } - /*UTF16*/ - if ((txt->len>2) && ((unsigned char) txt->text0 == (unsigned char) 0xFE) && ((unsigned char) txt->text1 == (unsigned char) 0xFF)) { - memcpy(utf16Line, txt->text+2, sizeof(char)*txt->len); - ( ((char *)utf16Line)txt->len ) = 0; - len = txt->len; - } else { - u8 *str = (u8 *) (txt->text); - len = gf_utf8_mbstowcs(utf16Line, 10000, (const char **) &str); - if (len == GF_UTF8_FAIL) return GF_NON_COMPLIANT_BITSTREAM; - utf16Linelen = 0; - } - char_num = 0; - styles = 0; - new_styles = txtd->default_style.style_flags; - color = new_color = txtd->default_style.text_color; - - for (j=0; j<len; j++) { - Bool is_new_line; - - if (txt->styles) { - new_styles = txtd->default_style.style_flags; - new_color = txtd->default_style.text_color; - for (k=0; k<txt->styles->entry_count; k++) { - if (txt->styles->stylesk.startCharOffset>char_num) continue; - if (txt->styles->stylesk.endCharOffset<char_num+1) continue; - - if (txt->styles->stylesk.style_flags & (GF_TXT_STYLE_ITALIC | GF_TXT_STYLE_BOLD | GF_TXT_STYLE_UNDERLINED | GF_TXT_STYLE_STRIKETHROUGH)) - new_styles = txt->styles->stylesk.style_flags; - if (txt->styles->stylesk.text_color) - new_color = txt->styles->stylesk.text_color; + u32 styles, char_num, new_styles, color, new_color; + u16 *utf16_buf = gf_malloc(sizeof(u16)*((txt->len/2)*2 + 4)); + if (!utf16_buf) return GF_OUT_OF_MEM; + + /*UTF16*/ + if ((txt->len>2) && ((unsigned char) txt->text0 == (unsigned char) 0xFE) && ((unsigned char) txt->text1 == (unsigned char) 0xFF) + ) { + memcpy(utf16_buf, txt->text+2, txt->len); + ( ((char *)utf16_buf)txt->len ) = 0; + len = txt->len; + } else { + u8 *str = (u8 *) (txt->text); + len = gf_utf8_mbstowcs(utf16_buf, txt->len+1, (const char **) &str); + if (len == GF_UTF8_FAIL) return GF_NON_COMPLIANT_BITSTREAM; + utf16_buflen = 0; + } + char_num = 0; + styles = 0; + new_styles = txtd->default_style.style_flags; + color = new_color = txtd->default_style.text_color; + + for (j=0; j<len; j++) { + Bool is_new_line; + + if (txt->styles) { + new_styles = txtd->default_style.style_flags; + new_color = txtd->default_style.text_color; + for (k=0; k<txt->styles->entry_count; k++) { + if (txt->styles->stylesk.startCharOffset>char_num) continue; + if (txt->styles->stylesk.endCharOffset<char_num+1) continue; + + if (txt->styles->stylesk.style_flags & (GF_TXT_STYLE_ITALIC | GF_TXT_STYLE_BOLD | GF_TXT_STYLE_UNDERLINED | GF_TXT_STYLE_STRIKETHROUGH)) + new_styles = txt->styles->stylesk.style_flags; + if (txt->styles->stylesk.text_color) + new_color = txt->styles->stylesk.text_color; - break; - } + break; } - if (new_styles != styles) { - if ((new_styles & GF_TXT_STYLE_BOLD) && !(styles & GF_TXT_STYLE_BOLD)) gf_fprintf(dump, "<b>"); - if ((new_styles & GF_TXT_STYLE_ITALIC) && !(styles & GF_TXT_STYLE_ITALIC)) gf_fprintf(dump, "<i>"); - if ((new_styles & GF_TXT_STYLE_UNDERLINED) && !(styles & GF_TXT_STYLE_UNDERLINED)) gf_fprintf(dump, "<u>"); - if ((new_styles & GF_TXT_STYLE_STRIKETHROUGH) && !(styles & GF_TXT_STYLE_STRIKETHROUGH)) gf_fprintf(dump, "<strike>"); - - if ((styles & GF_TXT_STYLE_STRIKETHROUGH) && !(new_styles & GF_TXT_STYLE_STRIKETHROUGH)) gf_fprintf(dump, "</strike>"); - if ((styles & GF_TXT_STYLE_UNDERLINED) && !(new_styles & GF_TXT_STYLE_UNDERLINED)) gf_fprintf(dump, "</u>"); - if ((styles & GF_TXT_STYLE_ITALIC) && !(new_styles & GF_TXT_STYLE_ITALIC)) gf_fprintf(dump, "</i>"); - if ((styles & GF_TXT_STYLE_BOLD) && !(new_styles & GF_TXT_STYLE_BOLD)) gf_fprintf(dump, "</b>"); - - styles = new_styles; - } - if (!vtt_dump && (new_color != color)) { - if (new_color ==txtd->default_style.text_color) { - gf_fprintf(dump, "</font>"); + } + if (new_styles != styles) { + if ((new_styles & GF_TXT_STYLE_BOLD) && !(styles & GF_TXT_STYLE_BOLD)) gf_fprintf(dump, "<b>"); + if ((new_styles & GF_TXT_STYLE_ITALIC) && !(styles & GF_TXT_STYLE_ITALIC)) gf_fprintf(dump, "<i>"); + if ((new_styles & GF_TXT_STYLE_UNDERLINED) && !(styles & GF_TXT_STYLE_UNDERLINED)) gf_fprintf(dump, "<u>"); + if ((new_styles & GF_TXT_STYLE_STRIKETHROUGH) && !(styles & GF_TXT_STYLE_STRIKETHROUGH)) gf_fprintf(dump, "<strike>"); + + if ((styles & GF_TXT_STYLE_STRIKETHROUGH) && !(new_styles & GF_TXT_STYLE_STRIKETHROUGH)) gf_fprintf(dump, "</strike>"); + if ((styles & GF_TXT_STYLE_UNDERLINED) && !(new_styles & GF_TXT_STYLE_UNDERLINED)) gf_fprintf(dump, "</u>"); + if ((styles & GF_TXT_STYLE_ITALIC) && !(new_styles & GF_TXT_STYLE_ITALIC)) gf_fprintf(dump, "</i>"); + if ((styles & GF_TXT_STYLE_BOLD) && !(new_styles & GF_TXT_STYLE_BOLD)) gf_fprintf(dump, "</b>"); + + styles = new_styles; + } + if (!vtt_dump && (new_color != color)) { + if (new_color ==txtd->default_style.text_color) { + gf_fprintf(dump, "</font>"); + } else { + const char *cname = gf_color_get_name(new_color); + if (cname) { + gf_fprintf(dump, "<font color=\"%s\">", cname); } else { - const char *cname = gf_color_get_name(new_color); - if (cname) { - gf_fprintf(dump, "<font color=\"%s\">", cname); - } else { - if (new_color >> 24 < 0xFF) - gf_fprintf(dump, "<font color=\"#%X\">", new_color); - else - gf_fprintf(dump, "<font color=\"#%06X\">", new_color&0x00FFFFFF); - } + if (new_color >> 24 < 0xFF) + gf_fprintf(dump, "<font color=\"#%X\">", new_color); + else + gf_fprintf(dump, "<font color=\"#%06X\">", new_color&0x00FFFFFF); } - color = new_color; - } - - /*not sure if styles must be reseted at line breaks in srt...*/ - is_new_line = GF_FALSE; - if ((utf16Linej=='\n') || (utf16Linej=='\r') ) { - if ((utf16Linej=='\r') && (utf16Linej+1=='\n')) j++; - gf_fprintf(dump, "\n"); - is_new_line = GF_TRUE; } - - if (!is_new_line) { - u32 sl; - char szChar30; - s16 swT2, *swz; - swT0 = utf16Linej; - swT1 = 0; - swz= (s16 *)swT; - sl = gf_utf8_wcstombs(szChar, 30, (const unsigned short **) &swz); - if (sl == GF_UTF8_FAIL) sl=0; - szCharsl=0; - gf_fprintf(dump, "%s", szChar); - } - char_num++; + color = new_color; } - new_styles = 0; - if (new_styles != styles) { - if (styles & GF_TXT_STYLE_STRIKETHROUGH) gf_fprintf(dump, "</strike>"); - if (styles & GF_TXT_STYLE_UNDERLINED) gf_fprintf(dump, "</u>"); - if (styles & GF_TXT_STYLE_ITALIC) gf_fprintf(dump, "</i>"); - if (styles & GF_TXT_STYLE_BOLD) gf_fprintf(dump, "</b>"); -// styles = 0; + /*not sure if styles must be reseted at line breaks in srt...*/ + is_new_line = GF_FALSE; + if ((utf16_bufj=='\n') || (utf16_bufj=='\r') ) { + if ((utf16_bufj=='\r') && (utf16_bufj+1=='\n')) j++; + gf_fprintf(dump, "\n"); + is_new_line = GF_TRUE; } - if (color != txtd->default_style.text_color) { - gf_fprintf(dump, "</font>"); -// color = txtd->default_style.text_color; - } - gf_fprintf(dump, "\n"); + if (!is_new_line) { + u32 sl; + char szChar30; + s16 swT2, *swz; + swT0 = utf16_bufj; + swT1 = 0; + swz= (s16 *)swT; + sl = gf_utf8_wcstombs(szChar, 30, (const unsigned short **) &swz); + if (sl == GF_UTF8_FAIL) sl=0; + szCharsl=0; + gf_fprintf(dump, "%s", szChar); + } + char_num++; + } + new_styles = 0; + if (new_styles != styles) { + if (styles & GF_TXT_STYLE_STRIKETHROUGH) gf_fprintf(dump, "</strike>"); + if (styles & GF_TXT_STYLE_UNDERLINED) gf_fprintf(dump, "</u>"); + if (styles & GF_TXT_STYLE_ITALIC) gf_fprintf(dump, "</i>"); + if (styles & GF_TXT_STYLE_BOLD) gf_fprintf(dump, "</b>"); + } + + if (color != txtd->default_style.text_color) { + gf_fprintf(dump, "</font>"); } + gf_fprintf(dump, "\n"); + gf_free(utf16_buf); + return GF_OK; } #else @@ -4037,7 +4186,8 @@ GF_Tx3gSampleEntryBox *txtd; char szDur100; Bool is_wvtt = GF_FALSE; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, track); + Bool srt_forced_subs = gf_opts_get_bool("core", "srt-forced"); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, track); u32 subtype = gf_isom_get_media_subtype(the_file, track, 1); if (!trak) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -4157,8 +4307,10 @@ txtd = (GF_Tx3gSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, di-1); - if (txt->is_forced) gf_fprintf(dump, " !!!"); - else if (txtd->displayFlags & GF_TXT_ALL_SAMPLES_FORCED) gf_fprintf(dump, " !!!"); + if (srt_forced_subs) { + if (txt->is_forced) gf_fprintf(dump, " !!!"); + else if (txtd->displayFlags & GF_TXT_ALL_SAMPLES_FORCED) gf_fprintf(dump, " !!!"); + } gf_fprintf(dump, "\n"); @@ -4182,7 +4334,7 @@ u64 start, end; GF_BitStream *bs; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, track); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, track); if (!trak) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { case GF_ISOM_MEDIA_TEXT: @@ -4265,7 +4417,7 @@ u64 start; GF_BitStream *bs; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, track); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, track); if (!trak) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { case GF_ISOM_MEDIA_TEXT: @@ -4302,8 +4454,8 @@ if (!txt->len) continue; if (dump_type==GF_TEXTDUMPTYPE_OGG_CHAP) { - char szDur20; - fprintf(dump, "CHAPTER%02d=%s\n", i+1, format_duration(start, ts, szDur)); + char szDur100; + fprintf(dump, "CHAPTER%02d=%s\n", i+1, gf_format_duration(start, ts, szDur)); fprintf(dump, "CHAPTER%02dNAME=%s\n", i+1, txt->text); } else { fprintf(dump, "AddChapterBySecond("LLD",%s)\n", start / ts, txt->text); @@ -4415,7 +4567,7 @@ GF_Err e; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; @@ -4952,6 +5104,27 @@ return GF_OK; } +GF_Err iacb_box_dump(GF_Box *a, FILE * trace) +{ + GF_IAConfigurationBox *p = (GF_IAConfigurationBox *)a; + gf_isom_box_dump_start(a, "IAConfigurationBox", trace); + + if (p->cfg) { + u32 i; + u32 obu_count = gf_list_count(p->cfg->configOBUs); + gf_fprintf(trace, "configurationVersion=\"%u\" configOBUs_size=\"%u\">\n", + (u32)p->cfg->configurationVersion, (u32)p->cfg->configOBUs_size); + for (i = 0; i < obu_count; ++i) { + GF_IamfObu *config_obu = gf_list_get(p->cfg->configOBUs, i); + gf_fprintf(trace, "<IAConfig obu_type=\"%u\" name=\"%s\" size=\"%d\" />\n", + config_obu->obu_type, gf_iamf_get_obu_name(config_obu->obu_type), (u32)config_obu->obu_length); + } + } + + gf_isom_box_dump_done("IAConfigurationBox", a, trace); + return GF_OK; +} + GF_Err dac3_box_dump(GF_Box *a, FILE * trace) { GF_AC3ConfigBox *p = (GF_AC3ConfigBox *)a; @@ -4980,6 +5153,16 @@ return GF_OK; } +GF_Err dac4_box_dump(GF_Box *a, FILE * trace) +{ + GF_AC4ConfigBox *p = (GF_AC4ConfigBox *)a; + gf_isom_box_dump_start(a, "AC4SpecificBox", trace); + gf_fprintf(trace, "ac4_dsi_version=\"%d\" bitstream_version=\"%d\" fs_index=\"%d\" frame_rate_index=\"%d\" n_presentations=\"%d\">\n", + p->cfg.stream.ac4_dsi_version, p->cfg.stream.bitstream_version, p->cfg.stream.fs_index, p->cfg.stream.frame_rate_index, p->cfg.stream.n_presentations); + gf_isom_box_dump_done("AC4SpecificBox", a, trace); + return GF_OK; +} + GF_Err dmlp_box_dump(GF_Box *a, FILE * trace) { GF_TrueHDConfigBox *p = (GF_TrueHDConfigBox *)a; @@ -5204,6 +5387,17 @@ gf_isom_box_dump_done("TrackFragmentBaseMediaDecodeTimeBox", a, trace); return GF_OK; } +GF_Err rsot_box_dump(GF_Box *a, FILE * trace) +{ + GF_TFOriginalDurationBox *ptr = (GF_TFOriginalDurationBox*) a; + if (!a) return GF_BAD_PARAM; + gf_isom_box_dump_start(a, "RedundantSampleOriginalTimingBox", trace); + if (ptr->flags & 1) gf_fprintf(trace, " originalDuration=\"%u\"", ptr->original_duration); + if (ptr->flags & 2) gf_fprintf(trace, " elapsedDuration=\"%u\"", ptr->elapsed_duration); + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done("RedundantSampleOriginalTimingBox", a, trace); + return GF_OK; +} #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ GF_Err rvcc_box_dump(GF_Box *a, FILE * trace) @@ -5845,8 +6039,6 @@ return GF_OK; } -u8 key_info_get_iv_size(const u8 *key_info, u32 key_info_size, u32 idx, u8 *const_iv_size, const u8 **const_iv); - GF_Err senc_box_dump(GF_Box *a, FILE * trace) { u32 i, sample_count; @@ -5937,7 +6129,7 @@ for (k=0; k<nb_ivs; k++) { u32 pos; u32 idx = gf_bs_read_u16(bs); - u8 mk_iv_size = key_info_get_iv_size(sai->key_info, sai->key_info_size, idx, NULL, NULL); + u8 mk_iv_size = gf_cenc_key_info_get_iv_size(sai->key_info, sai->key_info_size, idx, NULL, NULL); pos = (u32) gf_bs_get_position(bs); if (mk_iv_size + pos <= sai->cenc_data_size) { gf_fprintf(trace, "%sidx:%d,iv_size:%d,IV:", k ? "," : "", idx, mk_iv_size); @@ -5959,16 +6151,16 @@ gf_fprintf(trace, ">\n"); for (j=0; j<nb_subs; j++) { - u32 clear, crypt; + u32 clear, nb_crypt; gf_fprintf(trace, "<SubSampleEncryptionEntry"); if (nb_keys>1) { u32 kidx = gf_bs_read_u16(bs); gf_fprintf(trace, " MultiKeyIndex=\"%u\"", kidx); } clear = gf_bs_read_u16(bs); - crypt = gf_bs_read_u32(bs); - gf_fprintf(trace, " NumClearBytes=\"%u\" NumEncryptedBytes=\"%u\"/>\n", clear, crypt); - total_bytes+=clear+crypt; + nb_crypt = gf_bs_read_u32(bs); + gf_fprintf(trace, " NumClearBytes=\"%u\" NumEncryptedBytes=\"%u\"/>\n", clear, nb_crypt); + total_bytes+=clear+nb_crypt; } if (!gf_sys_is_test_mode()) gf_fprintf(trace, "<!-- counted %u bytes for entry -->\n", total_bytes); @@ -5979,7 +6171,7 @@ } if (bs) gf_bs_del(bs); - + if (!ptr->size) { gf_fprintf(trace, "<SampleEncryptionEntry sampleCount=\"\" IV=\"\" SubsampleCount=\"\">\n"); gf_fprintf(trace, "<SubSampleEncryptionEntry NumClearBytes=\"\" NumEncryptedBytes=\"\"/>\n"); @@ -6098,7 +6290,7 @@ GF_Err a1lx_box_dump(GF_Box *a, FILE * trace) { - GF_AV1LayeredImageIndexingPropertyBox *ptr = (GF_AV1LayeredImageIndexingPropertyBox*)a; + GF_AV1LayeredImageIndexingPropertyBox *ptr = (GF_AV1LayeredImageIndexingPropertyBox*)a; if (!a) return GF_BAD_PARAM; gf_isom_box_dump_start(a, "AV1LayeredImageIndexingPropertyBox", trace); gf_fprintf(trace, "large_size=\"%d\" layer_size0=\"%d\" layer_size1=\"%d\" layer_size2=\"%d\">\n", ptr->large_size, ptr->layer_size0, ptr->layer_size1, ptr->layer_size2); @@ -6108,7 +6300,7 @@ GF_Err a1op_box_dump(GF_Box *a, FILE * trace) { - GF_AV1OperatingPointSelectorPropertyBox *ptr = (GF_AV1OperatingPointSelectorPropertyBox*)a; + GF_AV1OperatingPointSelectorPropertyBox *ptr = (GF_AV1OperatingPointSelectorPropertyBox*)a; if (!a) return GF_BAD_PARAM; gf_isom_box_dump_start(a, "AV1OperatingPointSelectorPropertyBox", trace); gf_fprintf(trace, "op_index=\"%d\">\n", ptr->op_index); @@ -6223,6 +6415,30 @@ return GF_OK; } +GF_Err txlo_box_dump(GF_Box *a, FILE *trace) +{ + GF_TextLayoutPropertyBox *ptr = (GF_TextLayoutPropertyBox *)a; + if (!a) + return GF_BAD_PARAM; + gf_isom_box_dump_start(a, "TextLayoutPropertyBox", trace); + gf_fprintf(trace, "reference_width=\"%d\" reference_height=\"%d\" x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" font_size=\"%d\" direction=\"%s\" writing_mode=\"%s\">\n", + ptr->reference_width, ptr->reference_height, ptr->x, ptr->y, ptr->width, ptr->height, ptr->font_size, ptr->direction, ptr->writing_mode); + gf_isom_box_dump_done("TextLayoutPropertyBox", a, trace); + return GF_OK; +} + +GF_Err fnch_box_dump(GF_Box *a, FILE *trace) +{ + GF_FontCharacteristicsPropertyBox *ptr = (GF_FontCharacteristicsPropertyBox *)a; + if (!a) + return GF_BAD_PARAM; + gf_isom_box_dump_start(a, "FontCharacteristicsPropertyBox", trace); + gf_fprintf(trace, "font_family=\"%s\" font_style=\"%s\" font_weight=\"%s\">\n", + ptr->font_family, ptr->font_style, ptr->font_weight); + gf_isom_box_dump_done("FontCharacteristicsPropertyBox", a, trace); + return GF_OK; +} + GF_Err clli_box_dump(GF_Box *a, FILE * trace) { GF_ContentLightLevelBox *ptr = (GF_ContentLightLevelBox *)a; @@ -6373,7 +6589,10 @@ a->type = GF_4CC('u','k','n','w'); gf_isom_box_dump_start(a, "EntityToGroupTypeBox", trace); a->type = GF_ISOM_BOX_TYPE_GRPT; - gf_fprintf(trace, "group_id=\"%d\">\n", ptr->group_id); + gf_fprintf(trace, "group_id=\"%d\"", ptr->group_id); + if (ptr->data) dump_data_attribute(trace, "data", ptr->data, ptr->data_len); + else if (!ptr->size) gf_fprintf(trace, " data=\"\""); + gf_fprintf(trace, ">\n"); for (i=0; i<ptr->entity_id_count ; i++) gf_fprintf(trace, "<EntityToGroupTypeBoxEntry EntityID=\"%d\"/>\n", ptr->entity_idsi); @@ -6617,7 +6836,7 @@ gf_isom_box_dump_start(a, "FDSampleBox", trace); gf_fprintf(trace, ">\n"); - e = gf_isom_box_array_dump(ptr->packetTable, trace); + e = gf_isom_box_array_dump(ptr->packetTable, trace, a->internal_flags); if (e) return e; gf_isom_box_dump_done("FDSampleBox", a, trace); return GF_OK; @@ -6774,6 +6993,14 @@ return GF_OK; } +GF_Err jp_box_dump(GF_Box *a, FILE * trace) +{ + GF_JP2SignatureBox *p = (GF_JP2SignatureBox *) a; + gf_isom_box_dump_start(a, "JP2SignatureBox", trace); + gf_fprintf(trace, "signature=\"0x%.8X\">\n", p->signature); + gf_isom_box_dump_done("JP2SignatureBox", a, trace); + return GF_OK; +} GF_Err jp2h_box_dump(GF_Box *a, FILE * trace) { @@ -6783,6 +7010,42 @@ return GF_OK; } +GF_Err jp2p_box_dump(GF_Box *a, FILE * trace) +{ + GF_JP2ProfileBox *p = (GF_JP2ProfileBox *) a; + u32 i, count = gf_list_count(p->compatible_brands); + + gf_isom_box_dump_start(a, "JP2ProfileBox", trace); + gf_fprintf(trace, ">\n"); + for (i=0; i<count; i++) { + u32 *brand = (u32 *)gf_list_get(p->compatible_brands, i); + gf_fprintf(trace, "<CompatibleBrand brand=\"%s\"/>\n", gf_4cc_to_str(*brand)); + } + gf_isom_box_dump_done("JP2ProfileBox", a, trace); + return GF_OK; +} + +GF_Err jsub_box_dump(GF_Box *a, FILE * trace) +{ + GF_JP2SubSamplingBox *p = (GF_JP2SubSamplingBox *) a; + + gf_isom_box_dump_start(a, "JP2SubSamplingBox", trace); + gf_fprintf(trace, "HorizontalSub=\"%d\" VerticalSub=\"%d\" ", p->horizontal_sub, p->vertical_sub); + gf_fprintf(trace, "HorizontalOffset=\"%d\" VerticalOffset=\"%d\">\n", p->horizontal_offset, p->vertical_offset); + gf_isom_box_dump_done("JP2SubSamplingBox", a, trace); + return GF_OK; +} + +GF_Err orfo_box_dump(GF_Box *a, FILE * trace) +{ + GF_JP2OriginalFormatBox *p = (GF_JP2OriginalFormatBox *) a; + + gf_isom_box_dump_start(a, "JP2OriginalFormatBox", trace); + gf_fprintf(trace, "FieldCount=\"%d\" FieldOrder=\"%d\">\n", p->original_fieldcount, p->original_fieldorder); + gf_isom_box_dump_done("JP2OriginalFormatBox", a, trace); + return GF_OK; +} + GF_Err ihdr_box_dump(GF_Box *a, FILE * trace) { GF_J2KImageHeaderBox *p = (GF_J2KImageHeaderBox *) a; @@ -6805,12 +7068,12 @@ { GF_DTSSpecificBox *ptr = (GF_DTSSpecificBox *)a; - gf_isom_box_dump_start(a, "DTSpecificBox", trace); + gf_isom_box_dump_start(a, "DTSSpecificBox", trace); gf_fprintf(trace, "SamplingFrequency=\"%d\" MaxBitrate=\"%d\" AvgBitrate=\"%d\" " "SampleDepth=\"%d\" FrameDuration=\"%d\" StreamConstruction=\"%d\" " "CoreLFEPresent=\"%d\" CoreLayout=\"%d\" CoreSize=\"%d\" StereoDownmix=\"%d\" " "RepresentationType=\"%d\" ChannelLayout=\"%d\" MultiAssetFlag=\"%d\" " - "LBRDurationMod=\"%d\"", + "LBRDurationMod=\"%d\">\n", ptr->cfg.SamplingFrequency, ptr->cfg.MaxBitrate, ptr->cfg.AvgBitrate, ptr->cfg.SampleDepth, ptr->cfg.FrameDuration, ptr->cfg.StreamConstruction, ptr->cfg.CoreLFEPresent, ptr->cfg.CoreLayout, ptr->cfg.CoreSize, @@ -6826,12 +7089,12 @@ u32 byte; u8 i; u8 *data; - gf_isom_box_dump_start(a, "UDTSpecificBox", trace); + gf_isom_box_dump_start(a, "UDTSSpecificBox", trace); gf_fprintf(trace, "DecoderProfileCode=\"%d\" FrameDurationCode=\"%d\" MaxPayloadCode=\"%d\" " "NumPresentationsCode=\"%d\" ChannelMask=\"%d\" BaseSamplingFrequencyCode=\"%d\" " "SampleRateMod=\"%d\" RepresentationType=\"%d\" StreamIndex=\"%d\" " - "ExpansionBoxPresent=\"%d\"", + "ExpansionBoxPresent=\"%d\">\n", ptr->cfg.DecoderProfileCode, ptr->cfg.FrameDurationCode, ptr->cfg.MaxPayloadCode, ptr->cfg.NumPresentationsCode, ptr->cfg.ChannelMask, ptr->cfg.BaseSamplingFrequencyCode, ptr->cfg.SampleRateMod, @@ -6945,6 +7208,16 @@ return GF_OK; } +GF_Err srat_box_dump(GF_Box *a, FILE * trace) +{ + GF_SamplingRateBox *p = (GF_SamplingRateBox *) a; + + gf_isom_box_dump_start(a, "SamplingRateBox", trace); + gf_fprintf(trace, " sampling_rate=\"%u\">\n", p->sampling_rate); + gf_isom_box_dump_done("SamplingRateBox", a, trace); + return GF_OK; +} + GF_Err load_box_dump(GF_Box *a, FILE * trace) { GF_TrackLoadBox *p = (GF_TrackLoadBox *) a; @@ -6976,6 +7249,75 @@ return GF_OK; } +void scte35_dump_xml(FILE *dump, GF_BitStream *bs); +GF_Err emib_box_dump(GF_Box *a, FILE * trace) +{ + GF_EventMessageBox *p = (GF_EventMessageBox *) a; + + gf_isom_box_dump_start(a, "EventMessageInstanceBox", trace); + fprintf(trace, "presentation_time_delta=\""LLD"\" event_duration=\"%u\" event_id=\"%u\"", + p->presentation_time_delta, p->event_duration, p->event_id); + + if (p->scheme_id_uri) + fprintf(trace, " scheme_id_uri=\"%s\"", p->scheme_id_uri); + if (p->value) + fprintf(trace, " value=\"%s\"", p->value); + + if (p->message_data) { + dump_data_attribute(trace, " message_data", p->message_data, p->message_data_size); + gf_fprintf(trace, ">\n"); + +#ifndef GPAC_DISABLE_INSPECT + if (p->scheme_id_uri && !strcmp(p->scheme_id_uri, "urn:scte:scte35:2013:bin")) { + GF_BitStream *bs = gf_bs_new(p->message_data, p->message_data_size, GF_BITSTREAM_READ); + scte35_dump_xml(trace, bs); + gf_bs_del(bs); + } +#endif + } else { + gf_fprintf(trace, ">\n"); + } + + gf_isom_box_dump_done("EventMessageInstanceBox", a, trace); + return GF_OK; +} + +GF_Err emeb_box_dump(GF_Box *a, FILE * trace) +{ + gf_isom_box_dump_start(a, "EventMessageEmptyBox", trace); + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done("EventMessageEmptyBox", a, trace); + return GF_OK; +} + +GF_Err evte_box_dump(GF_Box *a, FILE * trace) +{ + //GF_EventMessageSampleEntryBox *p = (GF_EventMessageSampleEntryBox *)a; + gf_isom_box_dump_start(a, "EventMessageSampleEntryBox", trace); + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done("EventMessageSampleEntryBox", a, trace); + return GF_OK; +} + +GF_Err silb_box_dump(GF_Box *a, FILE * trace) +{ + GF_SchemeIdListBox *p = (GF_SchemeIdListBox *) a; + gf_isom_box_dump_start(a, "SchemeIdListBox", trace); + + fprintf(trace, "number_of_schemes=\"%u\" other_schemes_flag=\"%u\"", + p->number_of_schemes, p->other_schemes_flag); + gf_fprintf(trace, ">\n"); + + for (u32 i=0; i<p->number_of_schemes; ++i) { + GF_SchemeIdListBoxEntry *ent = (GF_SchemeIdListBoxEntry*)gf_list_get(p->schemes, i); + fprintf(trace, "<SchemeIdListBoxEntry scheme_id_uri=\"%s\" value=\"%s\" value=\"%u\"/>\n", + ent->scheme_id_uri, ent->value, ent->atleast_once_flag); + } + + gf_isom_box_dump_done("SchemeIdListBox", a, trace); + return GF_OK; +} + GF_Err csgp_box_dump(GF_Box *a, FILE * trace) { u32 i; @@ -7084,14 +7426,15 @@ if (res_len != GF_UTF8_FAIL) { utf8strres_len = 0; - gf_fprintf(trace, " value=\"%s\">\n", utf8str); + gf_fprintf(trace, " value=\"%s\"", utf8str); } gf_free(utf8str); } else { gf_fprintf(trace, " value=\""); dump_data_hex(trace, tag->prop_value, tag->prop_size); - gf_fprintf(trace, "\">\n"); + gf_fprintf(trace, "\""); } + gf_fprintf(trace, " />\n"); } gf_isom_box_dump_done("XtraBox", a, trace); return GF_OK; @@ -7177,6 +7520,28 @@ gf_isom_box_dump_done("KeysBox", NULL, trace); return GF_OK; } +GF_Err sref_box_dump(GF_Box *a, FILE * trace) +{ + GF_SampleReferences *ptr = (GF_SampleReferences*)a; + u32 i, nb_entries = gf_list_count(ptr->entries); + + gf_isom_box_dump_start(a, "SampleReferences", trace); + gf_fprintf(trace, ">\n"); + + for (i=0; i<nb_entries; i++) { + u32 j; + GF_SampleRefEntry *ent = gf_list_get(ptr->entries, i); + gf_fprintf(trace, "<SampleReferenceEntry sampleID=\"%u\" refs=\"", ent->sampleID); + for (j=0; j<ent->nb_refs; j++) { + if (j) gf_fprintf(trace, " "); + gf_fprintf(trace, "%u", ent->sample_refsj); + } + gf_fprintf(trace, "\"/>\n"); + } + + gf_isom_box_dump_done("SampleReferences", NULL, trace); + return GF_OK; +} GF_Err empty_box_dump(GF_Box *a, FILE * trace) { @@ -7191,6 +7556,18 @@ return GF_OK; } +GF_Err extl_box_dump(GF_Box *a, FILE * trace) +{ + GF_ExternalTrackLocationBox *ptr = (GF_ExternalTrackLocationBox *)a; + if (!a) return GF_BAD_PARAM; + gf_isom_box_dump_start(a, "ExternalTrackLocationBox", trace); + gf_fprintf(trace, " referenced_trackID=\"%u\" referenced_type=\"%s\"", ptr->referenced_track_ID, gf_4cc_to_str(ptr->referenced_handler_type)); + gf_fprintf(trace, " media_timescale=\"%u\"", ptr->media_timescale); + gf_fprintf(trace, " location=\"%s\">\n", ptr->location ? ptr->location : "NONE"); + gf_isom_box_dump_done("ExternalTrackLocationBox", a, trace); + return GF_OK; +} + #ifdef GPAC_HAS_QJS #include "../quickjs/quickjs.h" @@ -7352,13 +7729,13 @@ gf_dynstrcat(&buf, js_suf, NULL); blen = (u32) strlen(buf); - JSValue ret = JS_Eval(ctx, (char *)buf, blen, szPath, JS_EVAL_TYPE_MODULE); + JSValue ret = JS_Eval(ctx, (char *)buf, blen, szPath, JS_EVAL_TYPE_MODULE); if (JS_IsException(ret)) { e = GF_BAD_PARAM; js_dump_error(ctx); } JS_FreeValue(ctx, ret); - gf_free(buf); + gf_free(buf); if (!e) { JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
View file
gpac-2.4.0.tar.gz/src/isomedia/box_funcs.c -> gpac-26.02.0.tar.gz/src/isomedia/box_funcs.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -30,6 +30,7 @@ //Add this funct to handle incomplete files... //bytesExpected is 0 most of the time. If the file is incomplete, bytesExpected //is the number of bytes missing to parse the box... +GF_EXPORT GF_Err gf_isom_parse_root_box(GF_Box **outBox, GF_BitStream *bs, u32 *box_type, u64 *bytesExpected, Bool progressive_mode) { GF_Err ret; @@ -92,9 +93,10 @@ #define GF_SKIP_BOX 10 +GF_EXPORT GF_Err gf_isom_box_parse_ex(GF_Box **outBox, GF_BitStream *bs, u32 parent_type, Bool is_root_box, u64 parent_size) { - u32 type, uuid_type, hdr_size, restore_type; + u32 type, otype, uuid_type, hdr_size, restore_type; u64 size, start, comp_start, end; char uuid16; GF_Err e; @@ -120,10 +122,20 @@ /*fix for some boxes found in some old hinted files*/ if ((size >= 2) && (size <= 4)) { size = 4; - type = GF_ISOM_BOX_TYPE_VOID; + type = otype = GF_ISOM_BOX_TYPE_VOID; } else { - type = gf_bs_read_u32(bs); + type = otype = gf_bs_read_u32(bs); hdr_size += 4; + + //check if free as top-level is not a moof + if (is_root_box && (type==GF_ISOM_BOX_TYPE_FREE) && !(gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_NO_MABR_PATCH)) { + u32 child_type = gf_bs_peek_bits(bs, 32, 4); + if (child_type==GF_ISOM_BOX_TYPE_MFHD) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Recovering free top-level into moof\n")); + type = otype = GF_ISOM_BOX_TYPE_MOOF; + } + } + /*no size means till end of file - EXCEPT FOR some old QuickTime boxes...*/ if (type == GF_ISOM_BOX_TYPE_TOTL) size = 12; @@ -141,27 +153,42 @@ return GF_ISOM_INVALID_FILE; } } - if ((is_root_box && (size>=8)) - || (type==GF_QT_BOX_TYPE_CMOV) - ) { + Bool do_uncompress = GF_FALSE; + if (is_root_box && (size>=8) && ! (gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_NO_DECOMP)) + do_uncompress = GF_TRUE; + else if ((type==GF_QT_BOX_TYPE_CMOV) && ! (gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_NO_DECOMP)) + do_uncompress = GF_TRUE; + + if (do_uncompress) { u32 do_uncompress = 0; u8 *compb = NULL; u32 extra_bytes = 0; u32 osize = 0; u32 otype = type; - if (type==GF_4CC('!', 'm', 'o', 'v')) { + if (type==GF_ISOM_BOX_TYPE_CMOV) { do_uncompress = 1; type = GF_ISOM_BOX_TYPE_MOOV; } else if (type==GF_QT_BOX_TYPE_CMOV) { do_uncompress = 2; u32 cbtype, cbsize, ctype; + + if (!skip_logs) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("iso file Read Box type %s size %d start "LLU"\n", gf_4cc_to_str(type), size, start)); + } + start+=8; //parse child boxes directly cbsize = gf_bs_read_u32(bs); if (cbsize != 12) return GF_ISOM_INVALID_FILE; cbtype = gf_bs_read_u32(bs); if (cbtype != GF_QT_BOX_TYPE_DCOM) return GF_ISOM_INVALID_FILE; + + if (!skip_logs) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("iso file Read Box type %s size %d start "LLU"\n", gf_4cc_to_str(cbtype), cbsize, start)); + } + skip_logs = 1; + ctype = gf_bs_read_u32(bs); if (ctype != GF_4CC('z', 'l', 'i', 'b')) return GF_NOT_SUPPORTED; cbsize = gf_bs_read_u32(bs); @@ -177,20 +204,24 @@ type = cbtype; } #ifndef GPAC_DISABLE_ISOM_FRAGMENTS - else if (type==GF_4CC('!', 'm', 'o', 'f')) { + else if (type==GF_ISOM_BOX_TYPE_CMOF) { do_uncompress = 1; type = GF_ISOM_BOX_TYPE_MOOF; } - else if (type==GF_4CC('!', 's', 'i', 'x')) { + else if (type==GF_ISOM_BOX_TYPE_CSIX) { do_uncompress = 1; type = GF_ISOM_BOX_TYPE_SIDX; } - else if (type==GF_4CC('!', 's', 's', 'x')) { + else if (type==GF_ISOM_BOX_TYPE_CSSX) { do_uncompress = 1; type = GF_ISOM_BOX_TYPE_SSIX; } #endif if (do_uncompress) { + if (size<=8 || size-8 <= extra_bytes) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Compressed payload size invalid (%u) with extra_bytes (%u)\n", size, extra_bytes)); + return GF_NOT_SUPPORTED; + } if (size>100000000) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Compressed payload too large (%u)\n", size)); return GF_NOT_SUPPORTED; @@ -199,8 +230,10 @@ if (!compb) return GF_OUT_OF_MEM; compressed_size = (u32) (size - 8 - extra_bytes); - gf_bs_read_data(bs, compb, compressed_size); - e = gf_gz_decompress_payload_ex(compb, compressed_size, &uncomp_data, &osize, GF_FALSE); + if (!compressed_size || gf_bs_read_data(bs, compb, compressed_size) != compressed_size) + e = GF_NON_COMPLIANT_BITSTREAM; + else + e = gf_gz_decompress_payload_ex(compb, compressed_size, &uncomp_data, &osize, GF_FALSE); if (e) { gf_free(compb); GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Failed to uncompress payload for box type %s (0x%08X)\n", gf_4cc_to_str(otype), otype)); @@ -236,6 +269,10 @@ hdr_size += 16; uuid_type = gf_isom_solve_uuid_box(uuid); } + else if ((parent_type==GF_QT_BOX_TYPE_CMVD) && (type == GF_ISOM_BOX_TYPE_MOOV)) { + parent_type = GF_QT_BOX_TYPE_CMOV; + otype = GF_QT_BOX_TYPE_CMVD; + } //handle large box if (size == 1) { @@ -246,15 +283,15 @@ hdr_size += 8; } if (!skip_logs) - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("iso file Read Box type %s size "LLD" start "LLD"\n", gf_4cc_to_str(type), size, start)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("iso file Read Box type %s size "LLD" start "LLD"\n", gf_4cc_to_str(otype), size, start)); if ( size < hdr_size ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Box %s size "LLD" less than box header size %d\n", gf_4cc_to_str(type), size, hdr_size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Box %s size "LLD" less than box header size %d\n", gf_4cc_to_str(otype), size, hdr_size)); ERR_EXIT(GF_ISOM_INVALID_FILE); } //if parent size is given, make sure box fits within parent if (parent_size && (parent_size<size)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Box %s size "LLU" is larger than remaining parent size "LLU"\n", gf_4cc_to_str(type), size, parent_size )); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Box %s size "LLU" is larger than remaining parent size "LLU"\n", gf_4cc_to_str(otype), size, parent_size )); ERR_EXIT(GF_ISOM_INVALID_FILE); } restore_type = 0; @@ -656,6 +693,8 @@ ISOM_BOX_IMPL_DECL(def_parent) ISOM_BOX_IMPL_DECL(def_parent_full) ISOM_BOX_IMPL_DECL(csgp) +ISOM_BOX_IMPL_DECL(extl) +ISOM_BOX_IMPL_DECL(srat) #ifndef GPAC_DISABLE_ISOM_HINTING @@ -807,6 +846,7 @@ ISOM_BOX_IMPL_DECL(ec3) ISOM_BOX_IMPL_DECL(dac3) ISOM_BOX_IMPL_DECL(dec3) +ISOM_BOX_IMPL_DECL(dac4) ISOM_BOX_IMPL_DECL(dmlp) ISOM_BOX_IMPL_DECL(lsrc) ISOM_BOX_IMPL_DECL_CHILD(lsr1) @@ -829,8 +869,14 @@ ISOM_BOX_IMPL_DECL(leva) ISOM_BOX_IMPL_DECL(pcrb) ISOM_BOX_IMPL_DECL(tfdt) +ISOM_BOX_IMPL_DECL(rsot) ISOM_BOX_IMPL_DECL(emsg) +ISOM_BOX_IMPL_DECL(emib) +ISOM_BOX_IMPL_DECL(emeb) +ISOM_BOX_IMPL_DECL(evte) +ISOM_BOX_IMPL_DECL(silb) + #endif ISOM_BOX_IMPL_DECL(rvcc) @@ -851,7 +897,10 @@ ISOM_BOX_IMPL_DECL(auxi) ISOM_BOX_IMPL_DECL(hvcc) ISOM_BOX_IMPL_DECL(av1c) +ISOM_BOX_IMPL_DECL(av3c) ISOM_BOX_IMPL_DECL(dOps) +ISOM_BOX_IMPL_DECL(iamf) +ISOM_BOX_IMPL_DECL(iacb) ISOM_BOX_IMPL_DECL(prft) ISOM_BOX_IMPL_DECL(vvcc) ISOM_BOX_IMPL_DECL(vvnc) @@ -900,6 +949,8 @@ ISOM_BOX_IMPL_DECL(trgt) ISOM_BOX_IMPL_DECL(ienc) ISOM_BOX_IMPL_DECL(iaux) +ISOM_BOX_IMPL_DECL(txlo) +ISOM_BOX_IMPL_DECL(fnch) /* MIAF declarations */ ISOM_BOX_IMPL_DECL(clli) @@ -928,7 +979,11 @@ ISOM_BOX_IMPL_DECL(grptype) +ISOM_BOX_IMPL_DECL(jp) ISOM_BOX_IMPL_DECL_CHILD(jp2h) +ISOM_BOX_IMPL_DECL(jp2p) +ISOM_BOX_IMPL_DECL(jsub) +ISOM_BOX_IMPL_DECL(orfo) ISOM_BOX_IMPL_DECL(ihdr) ISOM_BOX_IMPL_DECL(load) @@ -955,6 +1010,10 @@ ISOM_BOX_IMPL_DECL(keys) +ISOM_BOX_IMPL_DECL(sref) + + + #if defined(GPAC_DISABLE_ISOM_DUMP) && defined(GPAC_DISABLE_ISOM_WRITE) #define BOX_DECLARATION(_a, _b, _c, _d, _write, _size, _dump, _g, _h, _i, _j, _k, _l, _m) \ @@ -1132,8 +1191,8 @@ BOX_DEFINE( GF_ISOM_BOX_TYPE_MDAT, mdat, "file"), BOX_DEFINE( GF_ISOM_BOX_TYPE_IDAT, mdat, "meta"), BOX_DEFINE( GF_ISOM_BOX_TYPE_IMDA, mdat, "file"), - BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_MOOV, moov, "file cmvd"), - BOX_DEFINE_CHILD( GF_QT_BOX_TYPE_CMVD, moov, "cmov moov"), + BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_MOOV, moov, "file"), + BOX_DEFINE_CHILD( GF_QT_BOX_TYPE_CMVD, moov, "cmov"), FBOX_DEFINE( GF_ISOM_BOX_TYPE_MVHD, mvhd, "moov", 1), FBOX_DEFINE( GF_ISOM_BOX_TYPE_MDHD, mdhd, "mdia", 1), FBOX_DEFINE( GF_ISOM_BOX_TYPE_VMHD, vmhd, "minf", 0), @@ -1150,7 +1209,9 @@ FBOX_DEFINE( GF_ISOM_BOX_TYPE_KIND, kind, "udta", 0), FBOX_DEFINE( GF_ISOM_BOX_TYPE_HDLR, hdlr, "mdia meta minf", 0), //minf container is OK in QT ... BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_TRAK, trak, "moov"), - BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_EDTS, edts, "trak"), + BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_EXTK, trak, "moov"), + FBOX_DEFINE_FLAGS( GF_ISOM_BOX_TYPE_EXTL, extl, "extk", 0, 0x000001 | 0x000002 | 0x000004 | 0x000008), + BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_EDTS, edts, "trak extk"), BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_UDTA, udta, "moov trak moof traf"), FBOX_DEFINE( GF_ISOM_BOX_TYPE_DREF, dref, "dinf", 0), FBOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_STSD, stsd, "stbl", 0), @@ -1168,13 +1229,13 @@ FBOX_DEFINE( GF_ISOM_BOX_TYPE_SDTP, sdtp, "stbl traf", 0), FBOX_DEFINE( GF_ISOM_BOX_TYPE_CO64, co64, "stbl", 0), BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_MINF, minf, "mdia"), - FBOX_DEFINE_FLAGS(GF_ISOM_BOX_TYPE_TKHD, tkhd, "trak", 1, 0x000001 | 0x000002 | 0x000004 | 0x000008), - BOX_DEFINE( GF_ISOM_BOX_TYPE_TREF, tref, "trak"), + FBOX_DEFINE_FLAGS(GF_ISOM_BOX_TYPE_TKHD, tkhd, "trak extk", 1, 0x000001 | 0x000002 | 0x000004 | 0x000008), + BOX_DEFINE( GF_ISOM_BOX_TYPE_TREF, tref, "trak extk"), BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_MDIA, mdia, "trak"), BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_MFRA, mfra, "file"), FBOX_DEFINE( GF_ISOM_BOX_TYPE_MFRO, mfro, "mfra", 0), FBOX_DEFINE( GF_ISOM_BOX_TYPE_TFRA, tfra, "mfra", 1), - FBOX_DEFINE( GF_ISOM_BOX_TYPE_ELNG, elng, "mdia", 0), + FBOX_DEFINE( GF_ISOM_BOX_TYPE_ELNG, elng, "mdia extk ipco", 0), FBOX_DEFINE( GF_ISOM_BOX_TYPE_PDIN, pdin, "file", 0), FBOX_DEFINE( GF_ISOM_BOX_TYPE_SBGP, sbgp, "stbl traf", 1), FBOX_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", 2), @@ -1184,7 +1245,7 @@ FBOX_DEFINE_FLAGS(GF_ISOM_BOX_TYPE_SAIO, saio, "stbl traf", 1, 0), FBOX_DEFINE_FLAGS(GF_ISOM_BOX_TYPE_SAIO, saio, "stbl traf", 1, 1), FBOX_DEFINE_FLAGS( GF_ISOM_BOX_TYPE_SUBS, subs, "stbl traf", 0, 7), //warning flags are not used as a bit mask but as an enum!! - BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_TRGR, trgr, "trak"), + BOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_TRGR, trgr, "trak extk"), BOX_DEFINE( GF_ISOM_BOX_TYPE_FTYP, ftyp, "file otyp"), BOX_DEFINE( GF_ISOM_BOX_TYPE_OTYP, def_parent, "file"), FBOX_DEFINE( GF_ISOM_BOX_TYPE_PADB, padb, "stbl", 0), @@ -1286,7 +1347,8 @@ BOX_DEFINE( GF_ISOM_BOX_TYPE_TIMS, tims, "rtp srtp rrtp"), BOX_DEFINE( GF_ISOM_BOX_TYPE_TSRO, tsro, "rtp srtp rrtp"), BOX_DEFINE( GF_ISOM_BOX_TYPE_SNRO, snro, "rtp srtp"), - BOX_DEFINE( GF_ISOM_BOX_TYPE_NAME, name, "udta"), + BOX_DEFINE( GF_QT_BOX_TYPE_NAME, name, "udta ----"), + BOX_DEFINE( GF_QT_BOX_TYPE_MEAN, name, "----"), BOX_DEFINE( GF_ISOM_BOX_TYPE_TSSY, tssy, "rrtp"), BOX_DEFINE( GF_ISOM_BOX_TYPE_RSSR, rssr, "rrtp"), FBOX_DEFINE_CHILD( GF_ISOM_BOX_TYPE_SRPP, srpp, "srtp", 0), @@ -1308,12 +1370,18 @@ FBOX_DEFINE_FLAGS(GF_ISOM_BOX_TYPE_CTRN, trun, "traf", 0, 0), #endif FBOX_DEFINE( GF_ISOM_BOX_TYPE_TFDT, tfdt, "traf", 1), + FBOX_DEFINE_FLAGS(GF_ISOM_BOX_TYPE_RSOT, rsot, "traf", 0, 0x000001|0x000002), BOX_DEFINE( GF_ISOM_BOX_TYPE_STYP, ftyp, "file"), FBOX_DEFINE( GF_ISOM_BOX_TYPE_PRFT, prft, "file", 1), FBOX_DEFINE( GF_ISOM_BOX_TYPE_SIDX, sidx, "file", 1), FBOX_DEFINE( GF_ISOM_BOX_TYPE_SSIX, ssix, "file", 0), BOX_DEFINE_S( GF_ISOM_BOX_TYPE_PCRB, pcrb, "file", "dash"), FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_EMSG, emsg, "file", 1, "dash"), + + FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_EMIB, emib, "file", 1, "EventMessageTrack"), + BOX_DEFINE_S( GF_ISOM_BOX_TYPE_EMEB, emeb, "file", "EventMessageTrack"), + FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_SILB, silb, "file", 1, "EventMessageTrack"), + BOX_DEFINE_S( GF_ISOM_BOX_TYPE_EVTE, gen_sample_entry, "stsd", "EventMessageTrack"), #endif @@ -1365,16 +1433,26 @@ BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_FPCM, audio_sample_entry, "stsd", "23003_5"), FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_PCMC, pcmC, "ipcm fpcm", 0, "23003_5"), + + FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_SRAT, srat, "audio_sample_entry", 0, "p12"), + + //AV1 in ISOBMFF boxes BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_AV01, video_sample_entry, "stsd", "av1"), BOX_DEFINE_S(GF_ISOM_BOX_TYPE_AV1C, av1c, "av01 encv resv ipco dav1", "av1"), + SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_AV1S, "av1"), // VP8-9 boxes - FBOX_DEFINE_FLAGS_S( GF_ISOM_BOX_TYPE_VPCC, vpcc, "vp08 vp09 encv resv", 1, 0, "vp"), + FBOX_DEFINE_FLAGS_S( GF_ISOM_BOX_TYPE_VPCC, vpcc, "vp08 vp09 vp10 encv resv", 1, 0, "vp"), BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_VP08, video_sample_entry, "stsd", "vp"), BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_VP09, video_sample_entry, "stsd", "vp"), - FBOX_DEFINE_FLAGS_S(GF_ISOM_BOX_TYPE_SMDM, SmDm, "vp08 vp09 encv resv", 1, 0, "vp"), - FBOX_DEFINE_FLAGS_S(GF_ISOM_BOX_TYPE_COLL, CoLL, "vp08 vp09 encv resv", 1, 0, "vp"), + BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_VP10, video_sample_entry, "stsd", "vp"), + FBOX_DEFINE_FLAGS_S(GF_ISOM_BOX_TYPE_SMDM, SmDm, "vp08 vp09 vp10 encv resv", 1, 0, "vp"), + FBOX_DEFINE_FLAGS_S(GF_ISOM_BOX_TYPE_COLL, CoLL, "vp08 vp09 vp10 encv resv", 1, 0, "vp"), + + //AVS3 in ISOBMFF boxes + BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_AVS3, video_sample_entry, "stsd", "avs"), + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_AV3C, av3c, "avs3 encv resv ipco", "avs"), //Opus in ISOBMFF boxes #ifndef GPAC_DISABLE_OGG @@ -1382,6 +1460,10 @@ BOX_DEFINE_S(GF_ISOM_BOX_TYPE_DOPS, dOps, "Opus wave enca", "Opus"), #endif + // IAMF boxes + BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_IAMF, audio_sample_entry, "stsd", "iamf"), + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_IACB, iacb, "iamf", "iamf"), + //part20 boxes BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_LSR1, lsr1, "stsd", "p20"), BOX_DEFINE_S( GF_ISOM_BOX_TYPE_LSRC, lsrc, "lsr1", "p20"), @@ -1427,6 +1509,8 @@ FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_AUXC, auxc, "ipco", 0, "iff"), FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_OINF, oinf, "ipco", 0, "iff"), FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_TOLS, tols, "ipco", 0, "iff"), + FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_TXLO, txlo, "ipco", 0, "iff"), + FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_FNCH, fnch, "ipco", 0, "iff"), FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_IENC, ienc, "ipco", 0, "cenc"), FBOX_DEFINE_S( GF_ISOM_BOX_TYPE_IAUX, iaux, "ipco", 0, "cenc"), @@ -1564,10 +1648,10 @@ ITUNES_TAG(GF_ISOM_ITUNE_EXEC_PRODUCER), ITUNES_TAG(GF_ISOM_ITUNE_LOCATION), - BOX_DEFINE_S( GF_ISOM_BOX_TYPE_iTunesSpecificInfo, ilst_item, "ilst data", "apple"), + BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_iTunesSpecificInfo, ilst_item, "ilst data", "apple"), BOX_DEFINE_S(GF_ISOM_BOX_TYPE_GMHD, def_parent, "minf", "apple"), - BOX_DEFINE_S(GF_QT_BOX_TYPE_LOAD, load, "trak", "apple"), - BOX_DEFINE_S(GF_QT_BOX_TYPE_TAPT, def_parent, "trak", "apple"), + BOX_DEFINE_S(GF_QT_BOX_TYPE_LOAD, load, "trak extk", "apple"), + BOX_DEFINE_S(GF_QT_BOX_TYPE_TAPT, def_parent, "trak extk", "apple"), FBOX_DEFINE_S( GF_QT_BOX_TYPE_GMIN, gmin, "gmhd", 0, "apple"), FBOX_DEFINE_FLAGS_S( GF_QT_BOX_TYPE_ALIS, alis, "dref", 0, 1, "apple"), FBOX_DEFINE_FLAGS_S( GF_QT_BOX_TYPE_CIOS, alis, "dref", 0, 1, "apple"), @@ -1639,6 +1723,7 @@ //dolby boxes BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_AC3, audio_sample_entry, "stsd", "dolby"), BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_EC3, audio_sample_entry, "stsd", "dolby"), + BOX_DEFINE_S_CHILD( GF_ISOM_BOX_TYPE_AC4, audio_sample_entry, "stsd", "dolby"), BOX_DEFINE_S( GF_ISOM_BOX_TYPE_DAC3, dac3, "ac-3 wave enca", "dolby"), {GF_ISOM_BOX_TYPE_DEC3, dec3_box_new, dac3_box_del, dac3_box_read, #ifndef GPAC_DISABLE_ISOM_WRITE @@ -1648,6 +1733,7 @@ dac3_box_dump, #endif 0, 0, 0, "ec-3 wave enca", "dolby" }, + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_DAC4, dac4, "ac-4 wave enca", "dolby"), BOX_DEFINE_S(GF_ISOM_BOX_TYPE_DVCC, dvcC, "dvav dva1 dvhe dvh1 dav1 avc1 avc2 avc3 avc4 hev1 hvc1 av01 encv resv", "DolbyVision"), BOX_DEFINE_S(GF_ISOM_BOX_TYPE_DVVC, dvvC, "dvav dva1 dvhe dvh1 dav1 avc1 avc2 avc3 avc4 hev1 hvc1 av01 encv resv", "DolbyVision"), BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_DVHE, video_sample_entry, "stsd", "DolbyVision"), @@ -1697,9 +1783,15 @@ //J2K boxes + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_JP, jp, "file", "j2k"), BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_MJP2, video_sample_entry, "stsd", "j2k"), BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_JP2H, jp2h, "mjp2 encv", "j2k"), + BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_J2KH, jp2h, "ipco", "j2k"), BOX_DEFINE_S(GF_ISOM_BOX_TYPE_IHDR, ihdr, "jp2h", "j2k"), + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_CDEF, unkn, "j2kH", "j2k"), + FBOX_DEFINE_S(GF_ISOM_BOX_TYPE_JP2P, jp2p, "mjp2", 0, "j2k"), + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_JSUB, jsub, "mjp2", "j2k"), + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_ORFO, orfo, "mjp2", "j2k"), /* Image tracks */ BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_JPEG, video_sample_entry, "stsd", "apple"), @@ -1755,11 +1847,18 @@ BOX_DEFINE_S(GF_4CC('s','n','u','c'), unkn, "video_sample_entry ipco", "rawff"), BOX_DEFINE_S(GF_4CC('s','b','p','m'), unkn, "video_sample_entry ipco", "rawff"), BOX_DEFINE_S(GF_4CC('c','l','o','c'), unkn, "video_sample_entry ipco", "rawff"), + BOX_DEFINE_S(GF_4CC('i','t','a','i'), unkn, "ipco", "rawff"), + BOX_DEFINE_S(GF_4CC('t','a','i','c'), unkn, "sample_entry ipco", "rawff"), BOX_DEFINE_S(GF_4CC('f','p','a','c'), unkn, "video_sample_entry ipco", "rawff"), BOX_DEFINE_S(GF_4CC('d','i','s','i'), unkn, "video_sample_entry ipco", "rawff"), BOX_DEFINE_S(GF_4CC('d','e','p','i'), unkn, "video_sample_entry ipco", "rawff"), BOX_DEFINE_S(GF_4CC('i','l','c','p'), unkn, "ipco", "rawff"), + BOX_DEFINE_S(GF_4CC('c','m','p','C'), unkn, "schi ipco", "rawff"), + BOX_DEFINE_S(GF_4CC('i','c','e','f'), unkn, "ipco", "rawff"), + + FBOX_DEFINE_S(GF_GPAC_BOX_TYPE_SREF, sref, "stbl traf", 0, "GPAC"), + BOX_DEFINE_S(GF_ISOM_BOX_TYPE_CDRF, sref, "stbl traf", "p12"), /* GF_ISOM_BOX_TYPE_CBMP = GF_4CC( 'c', 'b', 'm', 'p' ), @@ -1806,6 +1905,9 @@ if (box_registryi.box_4cc != boxCode) continue; + if ((boxCode== GF_ISOM_BOX_TYPE_MOOV) && (parent_type==GF_QT_BOX_TYPE_CMOV)) + return i; + if (!parent_type) return i; if (strstr(box_registryi.parents_4cc, gf_4cc_to_str_safe(parent_type, p4cc) ) != NULL) @@ -1851,6 +1953,9 @@ case GF_ISOM_BOX_TYPE_iTunesSpecificInfo: case GF_QT_BOX_TYPE_WAVE: break; + case GF_QT_BOX_TYPE_CMOV: + if (boxType==GF_ISOM_BOX_TYPE_MOOV) break; + //some sample descritions are handled as generic ones but we know them, don't warn case GF_ISOM_BOX_TYPE_STSD: //fallthrough @@ -1866,9 +1971,20 @@ } if (is_root_box) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("iso file Unknown top-level box type %s\n", gf_4cc_to_str(boxType))); + switch (boxType) { + case GF_ISOM_BOX_TYPE_CMOV: + case GF_ISOM_BOX_TYPE_CMOF: + case GF_ISOM_BOX_TYPE_CSIX: + case GF_ISOM_BOX_TYPE_CSSX: + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Unknown top-level box type %s\n", gf_4cc_to_str(boxType))); + break; + } } else if (parentType) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("iso file Unknown box type %s in parent %s\n", gf_4cc_to_str(boxType), gf_4cc_to_str(parentType) )); + if (boxType != GF_QT_BOX_TYPE_CMOV) { + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("iso file Unknown box type %s in parent %s\n", gf_4cc_to_str(boxType), gf_4cc_to_str(parentType) )); + } } else { GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("iso file Unknown box type %s\n", gf_4cc_to_str(boxType))); } @@ -1876,17 +1992,17 @@ } } #endif - if (is_uuid || (boxType==GF_ISOM_BOX_TYPE_UUID)) { - a = uuid_box_new(); - if (a) a->registry = &box_registry1; - } else { - a = unkn_box_new(); - if (a) { - ((GF_UnknownBox *)a)->original_4cc = boxType; + if (is_uuid || (boxType==GF_ISOM_BOX_TYPE_UUID)) { + a = uuid_box_new(); + if (a) a->registry = &box_registry1; + } else { + a = unkn_box_new(); + if (a) { + ((GF_UnknownBox *)a)->original_4cc = boxType; ((GF_UnknownBox *)a)->parent_4cc = parentType; - a->registry = &box_registry0; + a->registry = &box_registry0; } - } + } return a; } a = box_registryidx.new_fn(); @@ -1961,6 +2077,12 @@ parent_OK = GF_TRUE; } else if (!strcmp(a->registry->parents_4cc, "*") || strstr(a->registry->parents_4cc, "* ") || strstr(a->registry->parents_4cc, " *")) { parent_OK = GF_TRUE; + } + //these next two are needed because we don't parse CMVD as a smvd box but as a moov box after decompressing the payload + else if ((a->type == GF_QT_BOX_TYPE_CMVD) && (parent->type==GF_ISOM_BOX_TYPE_MOOV)) { + parent_OK = GF_TRUE; + } else if ((parent->type == GF_QT_BOX_TYPE_CMVD) && (a->type==GF_ISOM_BOX_TYPE_MOOV)) { + parent_OK = GF_TRUE; } else { //parent must be a sample entry if (strstr(a->registry->parents_4cc, "sample_entry") != NULL) { @@ -2237,13 +2359,16 @@ #ifndef GPAC_DISABLE_ISOM_DUMP -GF_Err gf_isom_box_dump_start_ex(GF_Box *a, const char *name, FILE * trace, Bool force_version) +GF_Err gf_isom_box_dump_start_ex(GF_Box *a, const char *name, FILE * trace, Bool force_version, const char *spec, const char *container) { gf_fprintf(trace, "<%s ", name); - if (a->size > 0xFFFFFFFF) { - gf_fprintf(trace, "LargeSize=\""LLU"\" ", a->size); - } else { - gf_fprintf(trace, "Size=\"%u\" ", (u32) a->size); + + if (!(a->internal_flags & GF_ISOM_DUMP_SKIP_SIZE)) { + if (a->size > 0xFFFFFFFF) { + gf_fprintf(trace, "LargeSize=\""LLU"\" ", a->size); + } else { + gf_fprintf(trace, "Size=\"%u\" ", (u32) a->size); + } } if (a->type==GF_ISOM_BOX_TYPE_UNKNOWN) { gf_fprintf(trace, "Type=\"%s\" ", gf_4cc_to_str(((GF_UnknownBox*)a)->original_4cc)); @@ -2261,23 +2386,26 @@ gf_fprintf(trace, "}\" "); } - if (a->registry->max_version_plus_one || force_version) { + if ((a->registry && a->registry->max_version_plus_one) || force_version) { gf_fprintf(trace, "Version=\"%d\" Flags=\"%d\" ", ((GF_FullBox*)a)->version,((GF_FullBox*)a)->flags); } - gf_fprintf(trace, "Specification=\"%s\" ", a->registry->spec); + gf_fprintf(trace, "Specification=\"%s\" ", spec ? spec : a->registry->spec); //don't write containers in test mode, that would require rewriting hashes whenever spec changes if (!gf_sys_is_test_mode()) { - gf_fprintf(trace, "Container=\"%s\" ", a->registry->parents_4cc); + if (container) + gf_fprintf(trace, "Container=\"%s\" ", container); + else + gf_fprintf(trace, "Container=\"%s\" ", a->registry->parents_4cc); } return GF_OK; } GF_Err gf_isom_box_dump_start(GF_Box *a, const char *name, FILE * trace) { - return gf_isom_box_dump_start_ex(a, name, trace, GF_FALSE); + return gf_isom_box_dump_start_ex(a, name, trace, GF_FALSE, NULL, NULL); } -GF_Err gf_isom_box_dump(void *ptr, FILE * trace) +GF_Err gf_isom_box_dump_ex(void *ptr, FILE * trace, Bool subtree_root) { GF_Box *a = (GF_Box *) ptr; @@ -2289,14 +2417,24 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("isom trying to dump box %s not registered\n", gf_4cc_to_str(a->type) )); return GF_ISOM_INVALID_FILE; } - a->registry->dump_fn(a, trace); + if (subtree_root && gf_opts_get_bool("core", "diso-nosize")) { + a->internal_flags |= GF_ISOM_DUMP_SKIP_SIZE; + a->registry->dump_fn(a, trace); + a->internal_flags &= ~GF_ISOM_DUMP_SKIP_SIZE; + } else { + a->registry->dump_fn(a, trace); + } return GF_OK; } +GF_Err gf_isom_box_dump(void *ptr, FILE * trace) +{ + return gf_isom_box_dump_ex(ptr, trace, GF_TRUE); +} void gf_isom_box_dump_done(const char *name, GF_Box *ptr, FILE *trace) { if (ptr && ptr->child_boxes) { - gf_isom_box_array_dump(ptr->child_boxes, trace); + gf_isom_box_array_dump(ptr->child_boxes, trace, ptr->internal_flags); } if (name) gf_fprintf(trace, "</%s>\n", name); @@ -2311,7 +2449,7 @@ } #endif - +GF_EXPORT GF_Box *gf_isom_box_find_child(GF_List *children, u32 code) { u32 i, count; @@ -2346,6 +2484,7 @@ return GF_TRUE; } +GF_EXPORT void gf_isom_box_del_parent(GF_List **child_boxes, GF_Box*b) { if (child_boxes) {
View file
gpac-2.4.0.tar.gz/src/isomedia/data_map.c -> gpac-26.02.0.tar.gz/src/isomedia/data_map.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -33,13 +33,6 @@ #ifndef GPAC_DISABLE_ISOM -#ifdef GPAC_HAS_FD -#include <stdio.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> -#endif - GF_BitStream *gf_bs_from_fd(int fd, u32 mode); void gf_isom_datamap_del(GF_DataMap *ptr) @@ -78,7 +71,7 @@ } //if ent NULL, the data entry was not used (smooth) - if (ent == NULL) return; + if (ent==NULL) return; //self contained, do nothing switch (ent->type) { @@ -281,18 +274,18 @@ } //return the NB of bytes actually read (used for HTTP, ...) in case file is uncomplete -u32 gf_isom_datamap_get_data(GF_DataMap *map, u8 *buffer, u32 bufferLength, u64 Offset) +u32 gf_isom_datamap_get_data(GF_DataMap *map, u8 *buffer, u32 bufferLength, u64 Offset, GF_BlobRangeStatus *is_corrupted) { if (!map || !buffer) return 0; switch (map->type) { case GF_ISOM_DATA_FILE: case GF_ISOM_DATA_MEM: - return gf_isom_fdm_get_data((GF_FileDataMap *)map, buffer, bufferLength, Offset); + return gf_isom_fdm_get_data((GF_FileDataMap *)map, buffer, bufferLength, Offset, is_corrupted); #if 0 case GF_ISOM_DATA_FILE_MAPPING: - return gf_isom_fmo_get_data((GF_FileMappingDataMap *)map, buffer, bufferLength, Offset); + return gf_isom_fmo_get_data((GF_FileMappingDataMap *)map, buffer, bufferLength, Offset, is_corrupted); #endif default: @@ -300,6 +293,7 @@ } } + void gf_isom_datamap_flush(GF_DataMap *map) { if (!map) return; @@ -351,12 +345,15 @@ tmp->type = GF_ISOM_DATA_FILE; tmp->mode = GF_ISOM_DATA_MAP_WRITE; +#ifdef GPAC_HAS_FD + tmp->fd = -1; +#endif if (!sPath) { tmp->stream = gf_file_temp(&tmp->temp_file); } else { char szPathGF_MAX_PATH; - if ((sPathstrlen(sPath)-1 != '\\') && (sPathstrlen(sPath)-1 != '/')) { + if (strlen(sPath) && (sPathstrlen(sPath)-1 != '\\') && (sPathstrlen(sPath)-1 != '/')) { sprintf(szPath, "%s%c%p_isotmp", sPath, GF_PATH_SEPARATOR, (void*) tmp); } else { sprintf(szPath, "%s%p_isotmp", sPath, (void*) tmp); @@ -416,11 +413,14 @@ if (!strncmp(sPath, "gmem://", 7)) { if (sscanf(sPath, "gmem://%p", &tmp->blob) != 1) return NULL; + gf_mx_p(tmp->blob->mx); tmp->bs = gf_bs_new(tmp->blob->data, tmp->blob->size, GF_BITSTREAM_READ); + gf_mx_v(tmp->blob->mx); if (!tmp->bs) { gf_free(tmp); return NULL; } + tmp->use_blob = 1; return (GF_DataMap *)tmp; } @@ -428,7 +428,7 @@ case GF_ISOM_DATA_MAP_READ: #ifdef GPAC_HAS_FD if (strncmp(sPath, "gfio://", 7) && !gf_opts_get_bool("core", "no-fd")) { - tmp->fd = open(sPath, O_RDONLY); + tmp->fd = gf_fd_open(sPath, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR); if (tmp->fd<0) break; tmp->bs = gf_bs_from_fd(tmp->fd, GF_BITSTREAM_READ); } else @@ -451,7 +451,7 @@ if (strncmp(sPath, "gfio://", 7) && !gf_opts_get_bool("core", "no-fd")) { //make sure output dir exists gf_fopen(sPath, "mkdir"); - tmp->fd = open(sPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); + tmp->fd = gf_fd_open(sPath, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); if (tmp->fd<0) break; tmp->bs = gf_bs_from_fd(tmp->fd, GF_BITSTREAM_WRITE); } else @@ -519,7 +519,7 @@ gf_free(ptr); } -u32 gf_isom_fdm_get_data(GF_FileDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset) +u32 gf_isom_fdm_get_data(GF_FileDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset, GF_BlobRangeStatus *is_corrupted) { u32 bytesRead; @@ -527,8 +527,17 @@ if (fileOffset > gf_bs_get_size(ptr->bs)) return 0; + if (is_corrupted) + *is_corrupted = GF_BLOB_RANGE_VALID; + if (ptr->blob) { gf_mx_p(ptr->blob->mx); + GF_BlobRangeStatus rs = gf_blob_query_range(ptr->blob, GF_TRUE, fileOffset, bufferLength); + if (is_corrupted) *is_corrupted = rs; + if (rs==GF_BLOB_RANGE_IN_TRANSFER) { + gf_mx_v(ptr->blob->mx); + return 0; + } gf_bs_reassign_buffer(ptr->bs, ptr->blob->data, ptr->blob->size); if (gf_bs_seek(ptr->bs, fileOffset) != GF_OK) { gf_mx_v(ptr->blob->mx); @@ -545,7 +554,7 @@ //update our cache if (bytesRead == bufferLength) { ptr->curPos += bytesRead; - } else { + } else if (!ptr->blob) { gf_bs_get_refreshed_size(ptr->bs); gf_bs_seek(ptr->bs, fileOffset); bytesRead = gf_bs_read_data(ptr->bs, buffer, bufferLength); @@ -565,6 +574,57 @@ return bytesRead; } +static Bool gf_isom_fdm_check_top_level(GF_FileDataMap *ptr) +{ + if (!ptr->blob) { + if (!gf_bs_available(ptr->bs)) return GF_FALSE; + return GF_TRUE; + } + //if we use a blob, make sure the top-level box is complete + gf_mx_p(ptr->blob->mx); + u64 fileOffset = gf_bs_get_position(ptr->bs); + gf_bs_reassign_buffer(ptr->bs, ptr->blob->data, ptr->blob->size); + if (ptr->blob->size < fileOffset+8) { + gf_mx_v(ptr->blob->mx); + return GF_FALSE; + } + gf_bs_seek(ptr->bs, fileOffset); + u32 size = gf_bs_peek_bits(ptr->bs, 32, 0); + u32 type = gf_bs_peek_bits(ptr->bs, 32, 4); + //no size: either range is invalid or the box extends till end of blob + if (!size) + size = ptr->blob->size - (u32) fileOffset; + + if ((type==GF_ISOM_BOX_TYPE_MDAT) || (type==GF_ISOM_BOX_TYPE_IDAT)) { + size = 8; + } + + GF_BlobRangeStatus rs = gf_blob_query_range(ptr->blob, GF_TRUE, fileOffset, size); + gf_mx_v(ptr->blob->mx); + if (rs==GF_BLOB_RANGE_IN_TRANSFER) + return GF_FALSE; + return GF_TRUE; +} + +Bool gf_isom_datamap_top_level_box_avail(GF_DataMap *map) +{ + if (!map) return GF_FALSE; + + switch (map->type) { + case GF_ISOM_DATA_FILE: + case GF_ISOM_DATA_MEM: + return gf_isom_fdm_check_top_level((GF_FileDataMap *)map); + +#if 0 + case GF_ISOM_DATA_FILE_MAPPING: + if (gf_bs_available( ((GF_FileMappingDataMap*)map)->bs)) return GF_TRUE; + return GF_FALSE; +#endif + + default: + return 0; + } +} #ifndef GPAC_DISABLE_ISOM_WRITE @@ -703,7 +763,7 @@ } -u32 gf_isom_fmo_get_data(GF_FileMappingDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset) +u32 gf_isom_fmo_get_data(GF_FileMappingDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset, GF_BlobRangeStatus *is_corrupted) { //can we seek till that point ??? if (fileOffset > ptr->file_size) return 0; @@ -725,9 +785,9 @@ gf_isom_fdm_del((GF_FileDataMap *)ptr); } -u32 gf_isom_fmo_get_data(GF_FileMappingDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset) +u32 gf_isom_fmo_get_data(GF_FileMappingDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset, GF_BlobRangeStatus *is_corrupted) { - return gf_isom_fdm_get_data((GF_FileDataMap *)ptr, buffer, bufferLength, fileOffset); + return gf_isom_fdm_get_data((GF_FileDataMap *)ptr, buffer, bufferLength, fileOffset, is_corrupted); } #endif //win32
View file
gpac-2.4.0.tar.gz/src/isomedia/drm_sample.c -> gpac-26.02.0.tar.gz/src/isomedia/drm_sample.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato / Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2024 + * Copyright (c) Telecom ParisTech 2005-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -162,7 +162,7 @@ GF_ISMASampleFormatBox *fmt; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return NULL; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, 0, NULL); @@ -196,7 +196,7 @@ u32 i, count; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; count = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); for (i=0; i<count; i++) { @@ -220,7 +220,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, GF_ISOM_ISMACRYP_SCHEME, NULL); @@ -239,7 +239,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, GF_ISOM_OMADRM_SCHEME, NULL); @@ -259,7 +259,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, GF_ISOM_ISMACRYP_SCHEME, NULL); @@ -300,7 +300,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, GF_ISOM_OMADRM_SCHEME, NULL); @@ -351,10 +351,10 @@ GF_SampleEntryBox *sea; GF_ProtectionSchemeInfoBox *sinf; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !sampleDescriptionIndex) return GF_BAD_PARAM; sea = NULL; @@ -390,10 +390,10 @@ GF_SampleEntryBox *sea; GF_ProtectionSchemeInfoBox *sinf; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !sampleDescriptionIndex) return GF_BAD_PARAM; sea = NULL; @@ -421,7 +421,7 @@ u32 *overwrite_type=NULL; GF_SampleEntryBox *sea; GF_ProtectionSchemeInfoBox *sinf; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, desc_index, &sea, NULL); @@ -450,6 +450,7 @@ case GF_ISOM_BOX_TYPE_DSMV: case GF_ISOM_BOX_TYPE_AC3: case GF_ISOM_BOX_TYPE_EC3: + case GF_ISOM_BOX_TYPE_AC4: sea->type = GF_ISOM_BOX_TYPE_ENCA; break; case GF_ISOM_BOX_TYPE_MP4V: @@ -585,6 +586,7 @@ return GF_OK; } +GF_EXPORT GF_Err gf_isom_set_oma_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, char *contentID, char *kms_URI, u32 encryption_type, u64 plainTextLength, char *textual_headers, u32 textual_headers_len, Bool selective_encryption, u32 KI_length, u32 IV_length) @@ -620,6 +622,7 @@ return GF_OK; } +GF_EXPORT GF_Err gf_isom_set_generic_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, u32 scheme_type, u32 scheme_version, char *scheme_uri, char *kms_URI) { GF_Err e; @@ -650,7 +653,7 @@ GF_ProtectionSchemeInfoBox *sinf; u32 i, count; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; count = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); @@ -681,7 +684,7 @@ GF_ProtectionSchemeInfoBox *sinf; u32 i, count; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; count = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); @@ -725,7 +728,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; @@ -751,6 +754,7 @@ #ifndef GPAC_DISABLE_ISOM_WRITE +GF_EXPORT GF_Err gf_isom_set_cenc_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, u32 scheme_type, u32 scheme_version, u32 default_IsEncrypted, u32 default_crypt_byte_block, u32 default_skip_byte_block, u8 *key_info, u32 key_info_size) @@ -804,7 +808,7 @@ { u32 i; GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -845,7 +849,7 @@ { u32 i; GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -878,7 +882,8 @@ } #endif -GF_Err gf_cenc_set_pssh(GF_ISOFile *file, bin128 systemID, u32 version, u32 KID_count, bin128 *KIDs, u8 *data, u32 len, u32 pssh_mode) +GF_EXPORT +GF_Err gf_isom_cenc_set_pssh(GF_ISOFile *file, bin128 systemID, u32 version, u32 KID_count, bin128 *KIDs, u8 *data, u32 len, u32 pssh_mode) { GF_ProtectionSystemHeaderBox *pssh = NULL; GF_PIFFProtectionSystemHeaderBox *pssh_piff = NULL; @@ -987,12 +992,12 @@ } - -GF_Err gf_isom_remove_samp_enc_box(GF_ISOFile *the_file, u32 trackNumber) +GF_EXPORT +GF_Err gf_isom_remove_cenc_senc_box(GF_ISOFile *the_file, u32 trackNumber) { u32 i; GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (!stbl) @@ -1028,11 +1033,12 @@ return GF_OK; } -GF_Err gf_isom_remove_samp_group_box(GF_ISOFile *the_file, u32 trackNumber) +GF_EXPORT +GF_Err gf_isom_remove_cenc_seig_sample_group(GF_ISOFile *the_file, u32 trackNumber) { u32 i; GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (!stbl) @@ -1132,9 +1138,10 @@ return senc; } +GF_EXPORT GF_Err gf_isom_cenc_allocate_storage(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->sample_encryption) return GF_OK; @@ -1144,9 +1151,10 @@ return gf_list_add(trak->child_boxes, trak->sample_encryption); } +GF_EXPORT GF_Err gf_isom_piff_allocate_storage(GF_ISOFile *the_file, u32 trackNumber, u32 AlgorithmID, u8 IV_size, bin128 KID) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->sample_encryption) return GF_OK; @@ -1297,12 +1305,13 @@ } #endif /* GPAC_DISABLE_ISOM_FRAGMENTS */ +GF_EXPORT GF_Err gf_isom_track_cenc_add_sample_info(GF_ISOFile *the_file, u32 trackNumber, u32 container_type, u8 *buf, u32 len, Bool use_subsamples, Bool use_saio_32bit, Bool use_multikey) { GF_SampleEncryptionBox *senc; GF_CENCSampleAuxInfo *sai; GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (!stbl) return GF_BAD_PARAM; @@ -1559,7 +1568,7 @@ } if ((nb_saio==1) && !saio_cenc->total_size) { for (j = 0; j < saiz->sample_count; j++) { - saio_cenc->total_size += saiz->default_sample_info_size ? saiz->default_sample_info_size : saiz->sample_info_sizej; + saio_cenc->total_size += (saiz->default_sample_info_size || !saiz->sample_info_size) ? saiz->default_sample_info_size : saiz->sample_info_sizej; } } if (saiz->cached_sample_num+1== sampleNumber) { @@ -1569,7 +1578,7 @@ return GF_ISOM_INVALID_FILE; for (j = 0; j < sampleNumber-1; j++) - prev_sai_size += saiz->default_sample_info_size ? saiz->default_sample_info_size : saiz->sample_info_sizej; + prev_sai_size += (saiz->default_sample_info_size || !saiz->sample_info_size) ? saiz->default_sample_info_size : saiz->sample_info_sizej; } if (saiz->default_sample_info_size) { size = saiz->default_sample_info_size; @@ -1622,7 +1631,8 @@ (*out_buffer) = gf_realloc((*out_buffer), sizeof(char)*(size) ); if (! *out_buffer) return GF_OUT_OF_MEM; } - gf_bs_read_data(mdia->information->dataHandler->bs, (*out_buffer), size); + if ((*out_buffer) && size) + gf_bs_read_data(mdia->information->dataHandler->bs, (*out_buffer), size); } (*out_size) = size; @@ -1640,7 +1650,7 @@ u32 type, scheme_type = -1; GF_CENCSampleAuxInfo *a_sai; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (!stbl) @@ -1823,7 +1833,7 @@ GF_EXPORT GF_Err gf_isom_cenc_get_default_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *container_type, Bool *default_IsEncrypted, u32 *crypt_byte_block, u32 *skip_byte_block, const u8 **key_info, u32 *key_info_size) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; gf_isom_cenc_get_default_info_internal(trak, sampleDescriptionIndex, container_type, default_IsEncrypted, crypt_byte_block, skip_byte_block, key_info, key_info_size); return GF_OK; @@ -1832,6 +1842,7 @@ /* Adobe'protection scheme */ +GF_EXPORT GF_Err gf_isom_set_adobe_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, u32 scheme_type, u32 scheme_version, Bool is_selective_enc, char *metadata, u32 len) { GF_ProtectionSchemeInfoBox *sinf; @@ -1892,7 +1903,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, GF_ISOM_ADOBE_SCHEME, NULL); @@ -1912,7 +1923,7 @@ GF_TrackBox *trak; GF_ProtectionSchemeInfoBox *sinf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; sinf = isom_get_sinf_entry(trak, sampleDescriptionIndex, GF_ISOM_ADOBE_SCHEME, NULL); @@ -1954,7 +1965,7 @@ } #endif - +GF_EXPORT Bool gf_cenc_validate_key_info(const u8 *key_info, u32 key_info_size) { u32 i, n_keys, kpos, nb_missing = 19;
View file
gpac-2.4.0.tar.gz/src/isomedia/hint_track.c -> gpac-26.02.0.tar.gz/src/isomedia/hint_track.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -105,10 +105,10 @@ default: return GF_NOT_SUPPORTED; } - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return gf_isom_last_error(movie); //check we have a hint ... @@ -155,15 +155,12 @@ udta = trak->udta; //HNTI - e = udta_on_child_box((GF_Box *)udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HNTI), GF_FALSE); + e = udta_on_child_box_ex((GF_Box *)udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HNTI), GF_FALSE, GF_TRUE); if (e) return e; /* - //NAME - e = udta_on_child_box((GF_Box *)udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_NAME)); - if (e) return e; //HINF - return udta_on_child_box((GF_Box *)udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HINF)); + return udta_on_child_box_ex((GF_Box *)udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HINF), GF_FALSE, GF_TRUE); */ return GF_OK; } @@ -179,10 +176,10 @@ GF_SampleDescriptionBox *stsd; GF_RelyHintBox *relyA; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); *HintDescriptionIndex = 0; if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; @@ -233,7 +230,7 @@ u32 i, count; GF_TSHintEntryBox *ent; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return GF_BAD_PARAM; //OK, create a new HintSampleDesc @@ -266,7 +263,7 @@ u32 i, count; GF_TimeOffHintEntryBox *ent; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return GF_BAD_PARAM; //OK, create a new HintSampleDesc @@ -299,7 +296,7 @@ u32 i, count; GF_SeqOffHintEntryBox *ent; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return GF_BAD_PARAM; //OK, create a new HintSampleDesc @@ -332,7 +329,7 @@ GF_HintSampleEntryBox *entry; GF_Err e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; //assert we're increasing the timing... @@ -374,7 +371,7 @@ GF_BitStream *bs; GF_ISOSample *samp; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &dataRefIndex); @@ -423,7 +420,7 @@ GF_EmptyDTE *dte; GF_Err e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &count); @@ -452,7 +449,7 @@ u32 offset = 0; if (!dataLength) return GF_OK; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak) || (dataLength > 14)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &count); @@ -480,7 +477,7 @@ GF_Err e; GF_TrackReferenceTypeBox *hint; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; @@ -550,7 +547,7 @@ GF_Err e; GF_TrackReferenceTypeBox *hint; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !IsHintTrack(trak)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &count); @@ -592,7 +589,7 @@ u32 dataRefIndex, ind; GF_Err e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &dataRefIndex); @@ -628,7 +625,7 @@ u32 dataRefIndex; GF_Err e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &dataRefIndex); @@ -662,7 +659,7 @@ u32 dataRefIndex, i; GF_Err e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, trak->Media->information->sampleTable->currentEntryIndex, (GF_SampleEntryBox **) &entry, &dataRefIndex); @@ -749,7 +746,7 @@ GF_Err e; char *buf; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; //currently, only RTP hinting supports SDP @@ -796,7 +793,7 @@ GF_UserDataMap *map; GF_HintTrackInfoBox *hnti; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; //currently, only RTP hinting supports SDP @@ -838,14 +835,14 @@ //find a hnti in the udta map = udta_getEntry(movie->moov->udta, GF_ISOM_BOX_TYPE_HNTI, NULL); if (!map) { - e = udta_on_child_box((GF_Box *)movie->moov->udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HNTI), GF_FALSE); + e = udta_on_child_box_ex((GF_Box *)movie->moov->udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HNTI), GF_FALSE, GF_TRUE); if (e) return e; map = udta_getEntry(movie->moov->udta, GF_ISOM_BOX_TYPE_HNTI, NULL); } //there should be one and only one hnti if (!gf_list_count(map->boxes) ) { - e = udta_on_child_box((GF_Box *)movie->moov->udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HNTI), GF_FALSE); + e = udta_on_child_box_ex((GF_Box *)movie->moov->udta, gf_isom_box_new(GF_ISOM_BOX_TYPE_HNTI), GF_FALSE, GF_TRUE); if (e) return e; } else if (gf_list_count(map->boxes) < 1) return GF_ISOM_INVALID_FILE; @@ -947,7 +944,7 @@ *sdp = NULL; *length = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) return GF_OK; @@ -976,7 +973,7 @@ GF_HintInfoBox *hinf; GF_PAYTBox *payt; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; if (!CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return 0; @@ -1002,7 +999,7 @@ GF_HintInfoBox *hinf; GF_PAYTBox *payt; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !index) return NULL; if (!CheckHintFormat(trak, GF_ISOM_HINT_RTP)) return NULL;
View file
gpac-2.4.0.tar.gz/src/isomedia/hinting.c -> gpac-26.02.0.tar.gz/src/isomedia/hinting.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -921,7 +921,7 @@ GF_TrackBox *trak; GF_HintSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!sample_start) return GF_BAD_PARAM; @@ -1030,7 +1030,7 @@ if (repeated) *repeated = 0; if (sample_num) *sample_num = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, 1, (GF_SampleEntryBox **) &entry, NULL); if (e) return e;
View file
gpac-2.4.0.tar.gz/src/isomedia/iff.c -> gpac-26.02.0.tar.gz/src/isomedia/iff.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2026 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -749,6 +749,7 @@ GF_EntityToGroupTypeBox *ptr = (GF_EntityToGroupTypeBox *)s; if (!ptr) return; if (ptr->entity_ids) gf_free(ptr->entity_ids); + if (ptr->data) gf_free(ptr->data); gf_free(ptr); } @@ -769,6 +770,15 @@ for (i = 0; i < ptr->entity_id_count; i++) { ptr->entity_idsi = gf_bs_read_u32(bs); + ISOM_DECREASE_SIZE(ptr, 4) + } + + if (ptr->size) { + ptr->data = gf_malloc((u32) ptr->size); + if (!ptr->data) return GF_OUT_OF_MEM; + ptr->data_len = (u32) ptr->size; + gf_bs_read_data(bs, ptr->data, ptr->data_len); + ptr->size = 0; } return GF_OK; } @@ -798,6 +808,7 @@ for (i = 0; i < ptr->entity_id_count; i++) { gf_bs_write_u32(bs, ptr->entity_idsi); } + if (ptr->data) gf_bs_write_data(bs, ptr->data, ptr->data_len); return GF_OK; } @@ -805,7 +816,7 @@ GF_Err grptype_box_size(GF_Box *s) { GF_EntityToGroupTypeBox *ptr = (GF_EntityToGroupTypeBox *)s; - ptr->size += 8 + 4*ptr->entity_id_count; + ptr->size += 8 + 4*ptr->entity_id_count + ptr->data_len; return GF_OK; } @@ -848,9 +859,9 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; //with terminating 0 - if (p->aux_urn) - gf_bs_write_data(bs, p->aux_urn, (u32) strlen(p->aux_urn) ); - gf_bs_write_u8(bs, 0); + if (p->aux_urn) + gf_bs_write_data(bs, p->aux_urn, (u32) strlen(p->aux_urn) ); + gf_bs_write_u8(bs, 0); gf_bs_write_data(bs, p->data, p->data_size); return GF_OK; @@ -859,7 +870,7 @@ GF_Err auxc_box_size(GF_Box *s) { GF_AuxiliaryTypePropertyBox *p = (GF_AuxiliaryTypePropertyBox*)s; - p->size += (p->aux_urn ? strlen(p->aux_urn) : 0) + 1 + p->data_size; + p->size += (p->aux_urn ? strlen(p->aux_urn) : 0) + 1 + p->data_size; return GF_OK; } @@ -895,16 +906,16 @@ e = gf_isom_full_box_write(s, bs); if (e) return e; //with terminating 0 - if (ptr->aux_track_type) - gf_bs_write_data(bs, ptr->aux_track_type, (u32) strlen(ptr->aux_track_type) ); - gf_bs_write_u8(bs, 0); + if (ptr->aux_track_type) + gf_bs_write_data(bs, ptr->aux_track_type, (u32) strlen(ptr->aux_track_type) ); + gf_bs_write_u8(bs, 0); return GF_OK; } GF_Err auxi_box_size(GF_Box *s) { GF_AuxiliaryTypeInfoBox *ptr = (GF_AuxiliaryTypeInfoBox *)s; - ptr->size += (ptr->aux_track_type ? strlen(ptr->aux_track_type) : 0 )+ 1; + ptr->size += (ptr->aux_track_type ? strlen(ptr->aux_track_type) : 0 )+ 1; return GF_OK; } @@ -1225,6 +1236,134 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *fnch_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_FontCharacteristicsPropertyBox, GF_ISOM_BOX_TYPE_FNCH); + return (GF_Box *)tmp; +} + +void fnch_box_del(GF_Box *a) +{ + GF_FontCharacteristicsPropertyBox *p = (GF_FontCharacteristicsPropertyBox *)a; + if (p->font_family) + gf_free(p->font_family); + if (p->font_style) + gf_free(p->font_style); + if (p->font_weight) + gf_free(p->font_weight); + gf_free(p); +} + +GF_Err fnch_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_FontCharacteristicsPropertyBox *p = (GF_FontCharacteristicsPropertyBox *)s; + + p->font_family = gf_bs_read_utf8(bs); + p->font_style = gf_bs_read_utf8(bs); + p->font_weight = gf_bs_read_utf8(bs); + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Err fnch_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_FontCharacteristicsPropertyBox *p = (GF_FontCharacteristicsPropertyBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) + return e; + gf_bs_write_utf8(bs, p->font_family); + gf_bs_write_utf8(bs, p->font_style); + gf_bs_write_utf8(bs, p->font_weight); + return GF_OK; +} + +GF_Err fnch_box_size(GF_Box *s) +{ + GF_FontCharacteristicsPropertyBox *p = (GF_FontCharacteristicsPropertyBox *)s; + p->size += (p->font_family ? strlen(p->font_family) : 0) + 1; + p->size += (p->font_style ? strlen(p->font_style) : 0) + 1; + p->size += (p->font_weight ? strlen(p->font_weight) : 0) + 1; + return GF_OK; +} +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +GF_Box *txlo_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_TextLayoutPropertyBox, GF_ISOM_BOX_TYPE_TXLO); + return (GF_Box *)tmp; +} + +void txlo_box_del(GF_Box *a) +{ + GF_TextLayoutPropertyBox *p = (GF_TextLayoutPropertyBox *)a; + if (p->direction) + gf_free(p->direction); + if (p->writing_mode) + gf_free(p->writing_mode); + gf_free(p); +} + +GF_Err txlo_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_TextLayoutPropertyBox *p = (GF_TextLayoutPropertyBox *)s; + + if ((p->flags & 0x1) == 1) + { + p->reference_width = gf_bs_read_u32(bs); + p->reference_height = gf_bs_read_u32(bs); + p->x = (s32)gf_bs_read_u32(bs); + p->y = (s32)gf_bs_read_u32(bs); + p->width = gf_bs_read_u32(bs); + p->height = gf_bs_read_u32(bs); + } + else + { + p->reference_width = gf_bs_read_u16(bs); + p->reference_height = gf_bs_read_u16(bs); + p->x = (s16)gf_bs_read_u16(bs); + p->y = (s16)gf_bs_read_u16(bs); + p->width = gf_bs_read_u16(bs); + p->height = gf_bs_read_u16(bs); + } + p->font_size = gf_bs_read_u16(bs); + p->direction = gf_bs_read_utf8(bs); + p->writing_mode = gf_bs_read_utf8(bs); + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Err txlo_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_TextLayoutPropertyBox *p = (GF_TextLayoutPropertyBox *)s; + p->flags = 0x01; + + e = gf_isom_full_box_write(s, bs); + if (e) + return e; + gf_bs_write_u32(bs, p->reference_width); + gf_bs_write_u32(bs, p->reference_height); + gf_bs_write_u32(bs, (u32)p->x); + gf_bs_write_u32(bs, (u32)p->y); + gf_bs_write_u32(bs, p->width); + gf_bs_write_u32(bs, p->height); + gf_bs_write_u16(bs, p->font_size); + gf_bs_write_utf8(bs, p->direction); + gf_bs_write_utf8(bs, p->writing_mode); + return GF_OK; +} + +GF_Err txlo_box_size(GF_Box *s) +{ + GF_TextLayoutPropertyBox *p = (GF_TextLayoutPropertyBox *)s; + p->size += 26; + p->size += (p->direction ? strlen(p->direction) : 0) + 1; + p->size += (p->writing_mode ? strlen(p->writing_mode) : 0) + 1; + return GF_OK; +} +#endif /*GPAC_DISABLE_ISOM_WRITE*/ #ifndef GPAC_DISABLE_ISOM_WRITE @@ -1244,6 +1383,7 @@ Bool is_cenc = GF_FALSE; Bool is_first = GF_TRUE; Bool neg_time = (image_props && image_props->time<0) ? GF_TRUE : GF_FALSE; + char *tile_name = NULL; u8 *sai = NULL; u32 sai_size = 0, sai_alloc_size = 0; u32 sample_desc_index = 0; @@ -1264,7 +1404,6 @@ u32 i, count; u32 tile_track; GF_List *tile_item_ids; - char sz_item_name256; GF_TileItemMode orig_tile_mode; #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_MEDIA_IMPORT) @@ -1289,11 +1428,17 @@ gf_list_add(tile_item_ids, tile_item_id); e = gf_isom_get_reference(movie, imported_track, GF_ISOM_REF_SABT, 1, &tile_track); if (e) return e; - if (item_name) - sprintf(sz_item_name, "%s-Tile%d", item_name, i + 1); + tile_name = NULL; + if (item_name) { + char szTmp50; + sprintf(szTmp, "-Tile%d", i+1); + gf_dynstrcat(&tile_name, item_name, NULL); + gf_dynstrcat(&tile_name, szTmp, NULL); + } if (orig_tile_mode != TILE_ITEM_SINGLE || image_props->single_tile_number == i + 1) { - e = gf_isom_iff_create_image_item_from_track(movie, root_meta, meta_track_number, tile_track, item_name ? sz_item_name : NULL, *tile_item_id, NULL, NULL); + e = gf_isom_iff_create_image_item_from_track(movie, root_meta, meta_track_number, tile_track, tile_name, *tile_item_id, NULL, NULL); } + if (tile_name) gf_free(tile_name); if (e) return e; gf_isom_remove_track(movie, tile_track); if (orig_tile_mode == TILE_ITEM_ALL_BASE) { @@ -1301,14 +1446,19 @@ } if (e) return e; } - if (item_name) - sprintf(sz_item_name, "%s-TileBase", item_name); + tile_name = NULL; + if (item_name) { + gf_dynstrcat(&tile_name, item_name, NULL); + gf_dynstrcat(&tile_name, "-TileBase", NULL); + } if (orig_tile_mode == TILE_ITEM_ALL_BASE) { - gf_isom_iff_create_image_item_from_track(movie, root_meta, meta_track_number, imported_track, item_name ? sz_item_name : NULL, item_id, image_props, tile_item_ids); + gf_isom_iff_create_image_item_from_track(movie, root_meta, meta_track_number, imported_track, tile_name, item_id, image_props, tile_item_ids); } else if (orig_tile_mode == TILE_ITEM_ALL_GRID) { // TODO } + if (tile_name) gf_free(tile_name); + for (i = 0; i < count; i++) { u32 *tile_item_id = gf_list_get(tile_item_ids, i); gf_free(tile_item_id); @@ -1327,75 +1477,89 @@ if (!imported_track) { GF_ImageItemProperties src_props; - u32 item_idx, ref_id; + u32 item_idx, ref_id, nb_items, for_ref_idx; u32 scheme_type=0, scheme_version=0; + Bool found=GF_FALSE; const char *orig_item_name, *orig_item_mime_type, *orig_item_encoding; - if (!image_props->item_ref_id) return GF_BAD_PARAM; + nb_items = gf_isom_get_meta_item_count(fsrc, GF_TRUE, 0); - if (gf_isom_meta_get_item_ref_count(fsrc, GF_TRUE, 0, image_props->item_ref_id, GF_4CC('d','i','m','g')) > 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error: Cannnot import derived image, only native image import is supported\n")); - return GF_NOT_SUPPORTED; - } - - item_idx = gf_isom_get_meta_item_by_id(fsrc, GF_TRUE, 0, image_props->item_ref_id); - if (!item_idx) { + for_ref_idx = image_props->item_ref_id ? gf_isom_get_meta_item_by_id(fsrc, GF_TRUE, 0, image_props->item_ref_id) : 0; + if (image_props->item_ref_id && !for_ref_idx) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error: No item with ID %d, cannnot import\n", image_props->item_ref_id)); return GF_BAD_PARAM; } - orig_item_name = orig_item_mime_type = orig_item_encoding = NULL; - gf_isom_get_meta_item_info(fsrc, GF_TRUE, 0, item_idx, &ref_id, &item_type, &scheme_type, &scheme_version, NULL, NULL, NULL, &orig_item_name, &orig_item_mime_type, &orig_item_encoding); - - if (!ref_id) return GF_BAD_PARAM; - if (ref_id != image_props->item_ref_id) return GF_ISOM_INVALID_FILE; - - gf_isom_get_meta_image_props(fsrc, GF_TRUE, 0, ref_id, &src_props, NULL); - - image_props->config = src_props.config; - image_props->width = src_props.width; - image_props->height = src_props.height; - image_props->num_channels = src_props.num_channels; - memcpy(image_props->av1_layer_size, src_props.av1_layer_size, sizeof(u32)*3); - memcpy(image_props->bits_per_channel, src_props.bits_per_channel, sizeof(u32)*3); - if (!image_props->hSpacing && !image_props->vSpacing) { - image_props->hSpacing = src_props.hSpacing; - image_props->vSpacing = src_props.vSpacing; - } - if (image_props->copy_props) { - if (!image_props->hOffset && !image_props->vOffset) { - image_props->hOffset = src_props.hOffset; - image_props->vOffset = src_props.vOffset; + + for (item_idx=1; item_idx<=nb_items; item_idx++) { + if (for_ref_idx && (for_ref_idx != item_idx)) continue; + + + if (gf_isom_meta_get_item_ref_count(fsrc, GF_TRUE, 0, image_props->item_ref_id, GF_4CC('d','i','m','g')) > 0) { + if (!for_ref_idx) continue; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error: Cannnot import derived image, only native image import is supported\n")); + return GF_NOT_SUPPORTED; } - if (!image_props->clap_wden) { - image_props->clap_wnum = src_props.clap_wnum; - image_props->clap_wden = src_props.clap_wden; - image_props->clap_hnum = src_props.clap_hnum; - image_props->clap_hden = src_props.clap_hden; - image_props->clap_honum = src_props.clap_honum; - image_props->clap_hoden = src_props.clap_hoden; - image_props->clap_vonum = src_props.clap_vonum; - image_props->clap_voden = src_props.clap_voden; + + orig_item_name = orig_item_mime_type = orig_item_encoding = NULL; + gf_isom_get_meta_item_info(fsrc, GF_TRUE, 0, item_idx, &ref_id, &item_type, &scheme_type, &scheme_version, NULL, NULL, NULL, &orig_item_name, &orig_item_mime_type, &orig_item_encoding); + + if (!ref_id) return GF_BAD_PARAM; + if (image_props->item_ref_id && (ref_id != image_props->item_ref_id)) return GF_ISOM_INVALID_FILE; + + gf_isom_get_meta_image_props(fsrc, GF_TRUE, 0, ref_id, &src_props, NULL); + + image_props->config = src_props.config; + image_props->width = src_props.width; + image_props->height = src_props.height; + image_props->num_channels = src_props.num_channels; + memcpy(image_props->av1_layer_size, src_props.av1_layer_size, sizeof(u32)*3); + memcpy(image_props->bits_per_channel, src_props.bits_per_channel, sizeof(u32)*3); + if (!image_props->hSpacing && !image_props->vSpacing) { + image_props->hSpacing = src_props.hSpacing; + image_props->vSpacing = src_props.vSpacing; } - if (!image_props->alpha) image_props->alpha = src_props.alpha; - if (!image_props->depth) image_props->depth = src_props.depth; - if (!image_props->hidden) image_props->hidden = src_props.hidden; - if (!image_props->angle) image_props->angle = src_props.angle; - if (!image_props->mirror) image_props->mirror = src_props.mirror; - if (!image_props->av1_op_index) image_props->av1_op_index = src_props.av1_op_index; - } - if (!item_name) item_name = orig_item_name; + if (image_props->copy_props) { + if (!image_props->hOffset && !image_props->vOffset) { + image_props->hOffset = src_props.hOffset; + image_props->vOffset = src_props.vOffset; + } + if (!image_props->clap_wden) { + image_props->clap_wnum = src_props.clap_wnum; + image_props->clap_wden = src_props.clap_wden; + image_props->clap_hnum = src_props.clap_hnum; + image_props->clap_hden = src_props.clap_hden; + image_props->clap_honum = src_props.clap_honum; + image_props->clap_hoden = src_props.clap_hoden; + image_props->clap_vonum = src_props.clap_vonum; + image_props->clap_voden = src_props.clap_voden; + } + if (!image_props->alpha) image_props->alpha = src_props.alpha; + if (!image_props->depth) image_props->depth = src_props.depth; + if (!image_props->hidden) image_props->hidden = src_props.hidden; + if (!image_props->angle) image_props->angle = src_props.angle; + if (!image_props->mirror) image_props->mirror = src_props.mirror; + if (!image_props->av1_op_index) image_props->av1_op_index = src_props.av1_op_index; + } + if (!item_name) item_name = orig_item_name; - if (!image_props->use_reference || (fsrc == image_props->src_file)) { - u8 *data = NULL; - u32 size=0; - e = gf_isom_extract_meta_item_mem(fsrc, GF_TRUE, 0, ref_id, &data, &size, &size, NULL, GF_FALSE); - if (e) return GF_BAD_PARAM; + if (!image_props->use_reference || (fsrc == image_props->src_file)) { + u8 *data = NULL; + u32 size=0; + e = gf_isom_extract_meta_item_mem(fsrc, GF_TRUE, 0, ref_id, &data, &size, &size, NULL, GF_FALSE); + if (e) return GF_BAD_PARAM; - e = gf_isom_add_meta_item_memory(movie, root_meta, meta_track_number, item_name, &item_id, item_type, NULL, NULL, image_props, data, size, NULL); - if (data) gf_free(data); - } else { - e = gf_isom_add_meta_item_sample_ref(movie, root_meta, meta_track_number, item_name, &item_id, item_type, NULL, NULL, image_props, 0, ref_id); + e = gf_isom_add_meta_item_memory(movie, root_meta, meta_track_number, item_name, &item_id, item_type, NULL, NULL, image_props, data, size, NULL); + if (data) gf_free(data); + } else { + e = gf_isom_add_meta_item_sample_ref(movie, root_meta, meta_track_number, item_name, &item_id, item_type, NULL, NULL, image_props, 0, ref_id); + } + found = GF_TRUE; } - return e; + if (e) return e; + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error: Cannnot import any image\n")); + return GF_URL_ERROR; + } + return GF_OK; } import_next_sample: @@ -1559,10 +1723,10 @@ gf_list_del(((GF_AV1ConfigurationBox *)config_box)->config->obu_array); ((GF_AV1ConfigurationBox *)config_box)->config->obu_array = NULL; e = gf_media_av1_layer_size_get(fsrc, imported_track, sample_number, image_props->av1_op_index, image_props->av1_layer_size); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("AV1 operating point index out of range for stream\n")); - goto exit; - } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("AV1 operating point index out of range for stream\n")); + goto exit; + } //media_brand = GF_ISOM_BRAND_AVIF; } break; @@ -1648,7 +1812,7 @@ gf_isom_get_pssh_info(fsrc, i+1, SystemID, &version, &KID_count, &KIDs, &private_data, &private_data_size); - gf_cenc_set_pssh(movie, SystemID, version, KID_count, (bin128 *) KIDs, (u8 *) private_data, private_data_size, 2); + gf_isom_cenc_set_pssh(movie, SystemID, version, KID_count, (bin128 *) KIDs, (u8 *) private_data, private_data_size, 2); } } @@ -1795,7 +1959,7 @@ if ((w != props.width) || (h != props.height)) { if (imgs_ids) gf_free(imgs_ids); - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Auto grid can only be generated for images of the same size - try using `-add-image-grid`\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Auto grid can only be generated for images of the same size - try using `-add-derived-image type=grid`\n")); return GF_NOT_SUPPORTED; } imgs_ids = gf_realloc(imgs_ids, sizeof(u32) * (nb_imgs+1));
View file
gpac-2.4.0.tar.gz/src/isomedia/isom_intern.c -> gpac-26.02.0.tar.gz/src/isomedia/isom_intern.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -32,7 +32,6 @@ /************************************************************** Some Local functions for movie creation **************************************************************/ -GF_Err gf_isom_parse_root_box(GF_Box **outBox, GF_BitStream *bs, u32 *boxType, u64 *bytesExpected, Bool progressive_mode); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS GF_Err MergeFragment(GF_MovieFragmentBox *moof, GF_ISOFile *mov) @@ -249,7 +248,7 @@ } #endif //GPAC_DISABLE_ISOM_FRAGMENTS -void gf_isom_push_mdat_end(GF_ISOFile *mov, u64 mdat_end) +void gf_isom_push_mdat_end(GF_ISOFile *mov, u64 mdat_end, Bool is_pred) { u32 i, count; if (!mov || !mov->moov) return; @@ -263,8 +262,16 @@ traf_map = trak->Media->information->sampleTable->traf_map; for (j=traf_map->nb_entries; j>0; j--) { - if (!traf_map->frag_startsj-1.mdat_end) { - traf_map->frag_startsj-1.mdat_end = mdat_end; + GF_TrafMapEntry *finfo = &traf_map->frag_startsj-1; + if (finfo->is_predicted_offset) { + finfo->is_predicted_offset = is_pred ? 1 : 0; + if (mdat_end > finfo->mdat_end) + finfo->mdat_end = 0; + } + + if (!finfo->mdat_end) { + finfo->mdat_end = mdat_end; + finfo->is_predicted_offset = is_pred ? 1 : 0; break; } } @@ -298,7 +305,7 @@ //for now we only use regular sample to group internally (except when dumping), not the pattern version //we unrill the pattern and replace the compact version with a regular one -static void convert_compact_sample_groups(GF_List *child_boxes, GF_List *sampleGroups) +static void convert_compact_sample_groups(u32 all_samples, GF_List *child_boxes, GF_List *sampleGroups) { u32 i; for (i=0; i<gf_list_count(sampleGroups); i++) { @@ -307,6 +314,16 @@ GF_CompactSampleGroupBox *csgp = gf_list_get(sampleGroups, i); if (csgp->type != GF_ISOM_BOX_TYPE_CSGP) continue; + if (!all_samples) { + u32 j=0; + for (j=0; j<gf_list_count(child_boxes); j++) { + GF_TrackFragmentRunBox *trun = (GF_TrackFragmentRunBox *)gf_list_get(child_boxes, j); + if (trun->type != GF_ISOM_BOX_TYPE_TRUN) continue; + all_samples += trun->sample_count; + } + } + u32 max_samples = all_samples; + gf_list_rem(sampleGroups, i); gf_list_del_item(child_boxes, csgp); @@ -324,6 +341,12 @@ for (j=0; j<csgp->pattern_count; j++) { u32 k=0; u32 nb_samples = csgp->patternsj.sample_count; + if (nb_samples > max_samples) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Invalid compact sample to group box, %u samples in pattern but %u remaining\n", nb_samples, max_samples)); + break; + } + max_samples -= nb_samples; + //unroll the pattern while (nb_samples) { u32 nb_same_index=1; @@ -352,12 +375,12 @@ } } - static GF_Err gf_isom_parse_movie_boxes_internal(GF_ISOFile *mov, u32 *boxType, u64 *bytesMissing, Bool progressive_mode) { GF_Box *a; u64 top_start, mdat_end=0; GF_Err e = GF_OK; + u32 btype; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (mov->single_moof_mode && mov->single_moof_state == 2) { @@ -371,18 +394,23 @@ top_start -= mov->bytes_removed; } gf_bs_seek(mov->movieFileMap->bs, top_start); + + if (gf_opts_get_bool("core", "no-mabr-patch")) { + gf_bs_set_cookie(mov->movieFileMap->bs, gf_bs_get_cookie(mov->movieFileMap->bs) | GF_ISOM_BS_COOKIE_NO_MABR_PATCH); + } #endif /*while we have some data, parse our boxes*/ - while (gf_bs_available(mov->movieFileMap->bs)) { + while (gf_isom_datamap_top_level_box_avail(mov->movieFileMap)) { *bytesMissing = 0; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS mov->current_top_box_start = gf_bs_get_position(mov->movieFileMap->bs) + mov->bytes_removed; GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("iso file Parsing a top-level box at position %d\n", mov->current_top_box_start)); #endif - e = gf_isom_parse_root_box(&a, mov->movieFileMap->bs, boxType, bytesMissing, progressive_mode); + e = gf_isom_parse_root_box(&a, mov->movieFileMap->bs, &btype, bytesMissing, progressive_mode); + if (boxType) *boxType = btype; if (e >= 0) { //safety check, should never happen @@ -393,6 +421,18 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Incomplete MDAT while file is not read-only\n")); return GF_ISOM_INVALID_FILE; } + + if ((btype == GF_ISOM_BOX_TYPE_MDAT) + && mov->signal_frag_bounds + && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) + ) { + //signal mdat end - note that if multiple mdats are used per fragment and hidden data is present at the end of + //N(>1) mdats, the prediction may not be true and we will expose offsets that could be incomplete when + //processing non-local files (http, pipes...) + u64 mdat_end = gf_bs_get_size(mov->movieFileMap->bs) + *bytesMissing; + gf_isom_push_mdat_end(mov, mdat_end, GF_TRUE); + } + if ((mov->openMode == GF_ISOM_OPEN_READ) && !progressive_mode) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Incomplete file while reading for dump - aborting parsing\n")); break; @@ -411,7 +451,7 @@ return GF_ISOM_INVALID_FILE; } mov->moov = (GF_MovieBox *)a; - if (mov->moov->has_cmvd) { + if (mov->moov->has_cmvd==1) { GF_Box *cmvd = gf_isom_box_find_child(a->child_boxes, GF_QT_BOX_TYPE_CMVD); mov->moov = (GF_MovieBox *) (cmvd ? gf_isom_box_find_child(cmvd->child_boxes, GF_ISOM_BOX_TYPE_MOOV) : NULL); if (!mov->moov) { @@ -439,12 +479,14 @@ e = gf_list_add(mov->TopBoxes, a); if (e) return e; - if (!mov->moov->mvhd) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Missing MovieHeaderBox\n")); - return GF_ISOM_INVALID_FILE; - } + if (!mov->moov->mvhd) { + if (mov->moov->has_cmvd!=2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Missing MovieHeaderBox\n")); + return GF_ISOM_INVALID_FILE; + } + } - if (mov->meta) { + if (mov->meta) { gf_isom_meta_restore_items_ref(mov, mov->meta); } @@ -463,16 +505,17 @@ u32 k; for (k=0; k<gf_list_count(mov->moov->trackList); k++) { GF_TrackBox *trak = (GF_TrackBox *)gf_list_get(mov->moov->trackList, k); + if (trak->extl) continue; if (trak->Media->information->sampleTable->sampleGroups) { - convert_compact_sample_groups(trak->Media->information->sampleTable->child_boxes, trak->Media->information->sampleTable->sampleGroups); + convert_compact_sample_groups(trak->Media->information->sampleTable->SampleSize->sampleCount, trak->Media->information->sampleTable->child_boxes, trak->Media->information->sampleTable->sampleGroups); } } } - if (mdat_end && mov->signal_frag_bounds && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) { - gf_isom_push_mdat_end(mov, mdat_end); - mdat_end=0; - } + if (mdat_end && mov->signal_frag_bounds && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) { + gf_isom_push_mdat_end(mov, mdat_end, GF_FALSE); + mdat_end=0; + } break; /*META box*/ @@ -520,11 +563,11 @@ if (mov->signal_frag_bounds && !(mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) { - mdat_end = gf_bs_get_position(mov->movieFileMap->bs); - if (mov->moov) { - gf_isom_push_mdat_end(mov, mdat_end); - mdat_end=0; - } + mdat_end = gf_bs_get_position(mov->movieFileMap->bs); + if (mov->moov) { + gf_isom_push_mdat_end(mov, mdat_end, GF_FALSE); + mdat_end=0; + } } } //keep all imda boxes for later rewrite @@ -649,7 +692,7 @@ } else { gf_isom_box_del(a); } - gf_isom_push_mdat_end(mov, mov->current_top_box_start); + gf_isom_push_mdat_end(mov, mov->current_top_box_start, GF_FALSE); } else if (!mov->NextMoofNumber && (a->type==GF_ISOM_BOX_TYPE_SIDX)) { if (mov->main_sidx) gf_isom_box_del( (GF_Box *) mov->main_sidx); mov->main_sidx = (GF_SegmentIndexBox *) a; @@ -687,7 +730,7 @@ for (k=0; k<gf_list_count(mov->moof->TrackList); k++) { GF_TrackFragmentBox *traf = (GF_TrackFragmentBox *)gf_list_get(mov->moof->TrackList, k); if (traf->sampleGroups) { - convert_compact_sample_groups(traf->child_boxes, traf->sampleGroups); + convert_compact_sample_groups(0, traf->child_boxes, traf->sampleGroups); } } } @@ -732,7 +775,7 @@ } } - } else if (mov->openMode==GF_ISOM_OPEN_KEEP_FRAGMENTS) { + } else if (mov->openMode==GF_ISOM_OPEN_KEEP_FRAGMENTS && mov->moof->mfhd) { mov->NextMoofNumber = mov->moof->mfhd->sequence_number+1; mov->moof = NULL; gf_isom_box_del(a); @@ -831,6 +874,8 @@ mov->current_top_box_start = gf_bs_get_position(mov->movieFileMap->bs) + mov->bytes_removed; #endif } + if (!mov->first_data_toplevel_offset) + mov->first_data_toplevel_offset = mov->current_top_box_start; /*we need at least moov or meta*/ if (!mov->moov && !mov->meta @@ -842,7 +887,7 @@ } /*we MUST have movie header*/ if (!gf_opts_get_bool("core", "no-check")) { - if (mov->moov && !mov->moov->mvhd) { + if (mov->moov && !mov->moov->mvhd && (mov->moov->has_cmvd!=2)) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Missing MVHD in MOOV!\n")); return GF_ISOM_INVALID_FILE; } @@ -858,7 +903,7 @@ if (mov->moov) { /*set the default interleaving time*/ - mov->interleavingTime = mov->moov->mvhd->timeScale; + mov->interleavingTime = mov->moov->mvhd ? mov->moov->mvhd->timeScale : 0; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS /*in edit mode with successfully loaded fragments, delete all fragment signaling since @@ -948,7 +993,11 @@ mov->store_traf_map = GF_TRUE; #endif - if ( (OpenMode == GF_ISOM_OPEN_READ) || (OpenMode == GF_ISOM_OPEN_READ_DUMP) || (OpenMode == GF_ISOM_OPEN_READ_EDIT) ) { + if ( (OpenMode == GF_ISOM_OPEN_READ) + || (OpenMode == GF_ISOM_OPEN_READ_DUMP) + || (OpenMode == GF_ISOM_OPEN_READ_DUMP_NO_COMP) + || (OpenMode == GF_ISOM_OPEN_READ_EDIT) + ) { if (OpenMode == GF_ISOM_OPEN_READ_EDIT) { mov->openMode = GF_ISOM_OPEN_READ_EDIT; @@ -979,6 +1028,10 @@ gf_isom_delete_movie(mov); return NULL; } + if (OpenMode == GF_ISOM_OPEN_READ_DUMP_NO_COMP) { + OpenMode = GF_ISOM_OPEN_READ_DUMP; + gf_bs_set_cookie(mov->movieFileMap->bs, GF_ISOM_BS_COOKIE_NO_DECOMP); + } if (OpenMode == GF_ISOM_OPEN_READ_DUMP) { mov->FragmentsFlags |= GF_ISOM_FRAG_READ_DEBUG; @@ -1094,10 +1147,10 @@ GF_TrackBox *trak = (GF_TrackBox*)gf_list_get(mov->moov->trackList, i); if (trak && trak->Media && trak->Media->information) { - if (trak->Media->information->dataHandler == mov->movieFileMap) + if (trak->Media->information->dataHandler == mov->movieFileMap || trak->Media->information->dataHandler == mov->editFileMap) trak->Media->information->dataHandler = NULL; - if (trak->Media->information->scalableDataHandler == mov->movieFileMap) + if (trak->Media->information->scalableDataHandler == mov->movieFileMap || trak->Media->information->scalableDataHandler == mov->editFileMap) trak->Media->information->scalableDataHandler = NULL; } } @@ -1164,7 +1217,8 @@ return NULL; } -GF_TrackBox *gf_isom_get_track_from_file(GF_ISOFile *movie, u32 trackNumber) +GF_EXPORT +GF_TrackBox *gf_isom_get_track_box(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; if (!movie) return NULL; @@ -1575,7 +1629,7 @@ #if 0 //unused u32 gf_isom_sample_get_subsamples_count(GF_ISOFile *movie, u32 track) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track); + GF_TrackBox *trak = gf_isom_get_track_box(movie, track); if (!track) return 0; if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return 0; return gf_list_count(trak->Media->information->sampleTable->sub_samples); @@ -1585,7 +1639,7 @@ Bool gf_isom_get_subsample_types(GF_ISOFile *movie, u32 track, u32 subs_index, u32 *flags) { GF_SubSampleInformationBox *sub_samples=NULL; - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track); + GF_TrackBox *trak = gf_isom_get_track_box(movie, track); if (!track || !subs_index) return GF_FALSE; if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return GF_FALSE; @@ -1599,7 +1653,7 @@ { u32 i, count, last_sample; GF_SubSampleInformationBox *sub_samples=NULL; - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track); + GF_TrackBox *trak = gf_isom_get_track_box(movie, track); if (sub_sample) *sub_sample = NULL; if (!track) return 0; if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return 0;
View file
gpac-2.4.0.tar.gz/src/isomedia/isom_read.c -> gpac-26.02.0.tar.gz/src/isomedia/isom_read.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -126,9 +126,9 @@ case GF_ISOM_BOX_TYPE_SIDX: case GF_ISOM_BOX_TYPE_EMSG: case GF_ISOM_BOX_TYPE_PRFT: - //we map free as segment when it is first in the file - a regular file shall start with ftyp or a file sig, not free - //since our route stack may patch boxes to free for incomplete segments, we must map this to free - case GF_ISOM_BOX_TYPE_FREE: + //we map free as segment when it is first in the file - a regular file shall start with ftyp or a file sig, not free + //since our route stack may patch boxes to free for incomplete segments, we must map this to free + case GF_ISOM_BOX_TYPE_FREE: return 3; #ifndef GPAC_DISABLE_ISOM_ADOBE /*Adobe specific*/ @@ -160,10 +160,10 @@ if (gf_blob_get(fileName, &mem_address, &size, NULL) != GF_OK) { return 0; } - if (size && (size > start_range + 8)) { + if (size && (size > start_range + 8)) { type = GF_4CC(mem_addressstart_range + 4, mem_addressstart_range + 5, mem_addressstart_range + 6, mem_addressstart_range + 7); - } - gf_blob_release(fileName); + } + gf_blob_release(fileName); } else if (!strncmp(fileName, "isobmff://", 10)) { return 2; } else { @@ -267,6 +267,8 @@ } else if (!stricmp(sz4cc, "AACL")) { } + else if (!stricmp(sz4cc, "none")) { + } else { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file Cannot convert smooth media type %s to ISO init segment\n", sz4cc)); return GF_NOT_SUPPORTED; @@ -310,7 +312,7 @@ trak->Media->handler = (GF_HandlerBox *) gf_isom_box_new_parent(&trak->Media->child_boxes,GF_ISOM_BOX_TYPE_HDLR); if (!trak->Media->handler) return GF_OUT_OF_MEM; - //we assume by default vide for handler type (only used for smooth streaming) + //we assume by default vide for handler type (only used for smooth streaming) trak->Media->handler->handlerType = width ? GF_ISOM_MEDIA_VISUAL : GF_ISOM_MEDIA_AUDIO; trak->Media->information = (GF_MediaInformationBox *) gf_isom_box_new_parent(&trak->Media->child_boxes, GF_ISOM_BOX_TYPE_MINF); @@ -417,7 +419,9 @@ aac->samplerate_hi = sample_rate; aac->channel_count = nb_channels; } - + else if (!stricmp(sz4cc, "none")) { + gf_isom_box_new_parent(&stbl->SampleDescription->child_boxes, GF_ISOM_BOX_TYPE_FREE); + } return GF_OK; } #endif @@ -455,7 +459,7 @@ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS e = isom_create_init_from_mem(fileName, movie); #else - e = GF_NOT_SUPPORTED; + e = GF_NOT_SUPPORTED; #endif } else { //do NOT use FileMapping on incomplete files @@ -527,6 +531,7 @@ switch (OpenMode & 0xFF) { case GF_ISOM_OPEN_READ_DUMP: + case GF_ISOM_OPEN_READ_DUMP_NO_COMP: case GF_ISOM_OPEN_READ: movie = gf_isom_open_file(fileName, OpenMode, NULL); break; @@ -564,6 +569,7 @@ u32 i, count = gf_list_count(movie->moov->trackList); for (i=0; i<count; i++) { GF_TrackBox *trak = gf_list_get(movie->moov->trackList, i); + if (trak->extl) continue; e = SetTrackDuration(trak); if (e) return e; } @@ -791,7 +797,7 @@ { GF_TrackBox *trak; if (!movie) return 0; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Header) return 0; return trak->Header->trackID; } @@ -807,7 +813,7 @@ count = gf_isom_get_track_count(the_file); if (!count) return 0; for (i = 0; i < count; i++) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, i+1); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, i+1); if (!trak || !trak->Header) return 0; if (trak->Header->trackID == trackID) return i+1; } @@ -819,7 +825,7 @@ { GF_TrackBox *trak; if (!movie) return 0; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; return trak->originalID; } @@ -999,7 +1005,7 @@ { GF_TrackBox *trak; if (!movie || !movie->moov) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; if (creationTime) *creationTime = trak->Media->mediaHeader->creationTime; @@ -1048,7 +1054,7 @@ u8 gf_isom_is_track_enabled(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Header) return 2; return (trak->Header->flags & 1) ? 1 : 0; @@ -1058,7 +1064,7 @@ u32 gf_isom_get_track_flags(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->Header->flags; } @@ -1070,7 +1076,7 @@ u64 gf_isom_get_track_duration(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; #ifndef GPAC_DISABLE_ISOM_WRITE @@ -1086,7 +1092,7 @@ u64 gf_isom_get_track_duration_orig(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; return trak->Header->duration; } @@ -1100,8 +1106,15 @@ return GF_BAD_PARAM; } *lang = NULL; - trak = gf_isom_get_track_from_file(the_file, trackNumber); - if (!trak || !trak->Media) return GF_BAD_PARAM; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak) return GF_BAD_PARAM; + + if (trak->extl) { + GF_ExtendedLanguageBox *elng = (GF_ExtendedLanguageBox *) gf_isom_box_find_child(trak->child_boxes, GF_ISOM_BOX_TYPE_ELNG); + if (elng) *lang = gf_strdup(elng->extended_language); + return GF_OK; + } + if (!trak->Media) return GF_BAD_PARAM; count = gf_list_count(trak->Media->child_boxes); if (count>0) { u32 i; @@ -1125,7 +1138,7 @@ GF_UserDataBox *udta; GF_UserDataMap *map; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; if (!trak->udta) { return 0; @@ -1154,7 +1167,7 @@ *value = NULL; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE); @@ -1185,7 +1198,7 @@ { GF_TrackBox *trak; GF_TrackReferenceTypeBox *dpnd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return -1; if (!trak->References) return 0; if (movie->openMode == GF_ISOM_OPEN_WRITE) { @@ -1207,7 +1220,7 @@ { GF_TrackBox *trak; GF_TrackReferenceTypeBox *dpnd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return NULL; if (!trak->References) return NULL; dpnd = gf_list_get(trak->References->child_boxes, idx); @@ -1227,7 +1240,7 @@ GF_TrackBox *trak; GF_TrackReferenceTypeBox *dpnd; GF_ISOTrackID refTrackNum; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); *refTrack = 0; if (!trak || !trak->References) return GF_BAD_PARAM; @@ -1259,7 +1272,7 @@ GF_Err e; GF_TrackBox *trak; GF_TrackReferenceTypeBox *dpnd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); *refTrackID = 0; if (!trak || !trak->References || !referenceIndex) return GF_BAD_PARAM; @@ -1284,7 +1297,7 @@ u32 i; GF_TrackBox *trak; GF_TrackReferenceTypeBox *dpnd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; if (!trak->References) return 0; @@ -1305,7 +1318,7 @@ u32 i, j, count; GF_TrackBox *trak; GF_TrackReferenceTypeBox *dpnd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; count = gf_list_count(movie->moov->trackList); for (i=0; i<count; i++) { @@ -1331,7 +1344,7 @@ GF_TrackBox *trak; u8 useEdit; s64 SegmentStartTime, mediaOffset; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !MediaTime) return GF_BAD_PARAM; SegmentStartTime = 0; @@ -1346,7 +1359,7 @@ { u32 streamDescIndex; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; if ( (movie->LastError = Media_GetSampleDescIndex(trak->Media, for_time, &streamDescIndex)) ) { @@ -1360,8 +1373,8 @@ u32 gf_isom_get_sample_description_count(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); - if (!trak) return 0; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media) return 0; return gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); } @@ -1372,6 +1385,9 @@ GF_EXPORT GF_ESD *gf_isom_get_esd(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex) { + if (!gf_isom_has_movie(movie)) + return NULL; + GF_ESD *esd; GF_Err e; e = GetESD(movie->moov, gf_isom_get_track_id(movie, trackNumber), StreamDescriptionIndex, &esd); @@ -1392,7 +1408,7 @@ GF_TrackBox *trak; GF_ESD *esd; GF_Descriptor *decInfo; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return NULL; //get the ESD (possibly emulated) Media_GetESD(trak->Media, StreamDescriptionIndex, &esd, GF_FALSE); @@ -1410,7 +1426,7 @@ u64 gf_isom_get_media_duration(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; @@ -1432,7 +1448,7 @@ u64 gf_isom_get_media_original_duration(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !trak->Media->mediaHeader) return 0; return trak->Media->mediaHeader->original_duration; @@ -1443,8 +1459,12 @@ u32 gf_isom_get_media_timescale(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); - if (!trak || !trak->Media || !trak->Media->mediaHeader) return 0; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak) return 0; + if (trak->extl) { + return trak->extl->media_timescale; + } + if (!trak->Media || !trak->Media->mediaHeader) return 0; return trak->Media->mediaHeader->timeScale; } @@ -1513,7 +1533,7 @@ udta = NULL; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; udta = trak->udta; } else { @@ -1539,7 +1559,7 @@ udta = NULL; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; udta = trak->udta; } else { @@ -1566,7 +1586,7 @@ u32 gf_isom_get_media_type(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; return (trak->Media && trak->Media->handler) ? trak->Media->handler->handlerType : 0; } @@ -1605,7 +1625,7 @@ { GF_TrackBox *trak; u32 i=0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 2; while (1) { GF_Box *entry = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, i); @@ -1625,7 +1645,7 @@ { GF_TrackBox *trak; GF_Box *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !DescriptionIndex || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable) return 0; entry = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return 0; @@ -1652,7 +1672,7 @@ { GF_TrackBox *trak; GF_Box *entry=NULL; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !DescriptionIndex) return 0; if (trak->Media @@ -1674,7 +1694,7 @@ GF_Err gf_isom_get_handler_name(GF_ISOFile *the_file, u32 trackNumber, const char **outName) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !outName) return GF_BAD_PARAM; *outName = trak->Media->handler->nameUTF8; return GF_OK; @@ -1689,7 +1709,7 @@ GF_TrackBox *trak; if (!StreamDescriptionIndex || !trackNumber) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex , NULL, &drefIndex); @@ -1711,7 +1731,7 @@ *outURL = *outURN = NULL; if (!StreamDescriptionIndex || !trackNumber) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex , NULL, &drefIndex); @@ -1747,7 +1767,7 @@ u32 gf_isom_get_sample_count(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize) return 0; return trak->Media->information->sampleTable->SampleSize->sampleCount #ifndef GPAC_DISABLE_ISOM_FRAGMENTS @@ -1760,7 +1780,7 @@ u32 gf_isom_get_constant_sample_size(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize) return 0; return trak->Media->information->sampleTable->SampleSize->sampleSize; } @@ -1769,7 +1789,7 @@ u32 gf_isom_get_constant_sample_duration(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->TimeToSample) return 0; if (trak->Media->information->sampleTable->TimeToSample->nb_entries != 1) return 0; return trak->Media->information->sampleTable->TimeToSample->entries0.sampleDelta; @@ -1782,7 +1802,7 @@ Bool from_qt=GF_FALSE; GF_TrackBox *trak; GF_MPEGAudioSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; trak->pack_num_samples = 0; //we only activate sample packing for raw audio @@ -1829,7 +1849,8 @@ gf_4cc_to_str(entry->type), from_qt ? " (as indicated in QT sample description)" : "" )); - trak->Media->information->sampleTable->SampleSize->sampleSize = bps * nb_ch; + if (nb_ch) + trak->Media->information->sampleTable->SampleSize->sampleSize = bps * nb_ch; } } return GF_TRUE; @@ -1838,7 +1859,7 @@ Bool gf_isom_has_time_offset_table(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media->information->sampleTable->CompositionOffset) return GF_FALSE; return GF_TRUE; } @@ -1849,7 +1870,7 @@ u32 i; GF_CompositionOffsetBox *ctts; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media->information->sampleTable->CompositionOffset) return 0; //return true at the first offset found @@ -1864,7 +1885,7 @@ s64 gf_isom_get_cts_to_dts_shift(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media->information->sampleTable->CompositionToDecode) return 0; return trak->Media->information->sampleTable->CompositionToDecode->compositionToDTSShift; } @@ -1872,7 +1893,7 @@ GF_EXPORT Bool gf_isom_has_sync_shadows(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; if (!trak->Media->information->sampleTable->ShadowSync) return GF_FALSE; if (gf_list_count(trak->Media->information->sampleTable->ShadowSync->entries) ) return GF_TRUE; @@ -1882,7 +1903,7 @@ GF_EXPORT Bool gf_isom_has_sample_dependency(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; if (!trak->Media->information->sampleTable->SampleDep) return GF_FALSE; return GF_TRUE; @@ -1896,7 +1917,7 @@ *dependsOn = 0; *dependedOn = 0; *redundant = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->SampleDep) return GF_BAD_PARAM; @@ -1920,7 +1941,7 @@ GF_TrackBox *trak; GF_ISOSample *samp; Bool ext_realloc = GF_FALSE; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return NULL; if (!sampleNumber) return NULL; @@ -1970,7 +1991,7 @@ { u32 dur; u64 dts; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !sampleNumber) return 0; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (sampleNumber<=trak->sample_count_at_seg_start) return 0; @@ -1986,7 +2007,7 @@ u32 gf_isom_get_sample_size(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber) { u32 size = 0; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !sampleNumber) return 0; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (sampleNumber<=trak->sample_count_at_seg_start) return 0; @@ -1999,7 +2020,7 @@ GF_EXPORT u32 gf_isom_get_max_sample_size(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize) return 0; return trak->Media->information->sampleTable->SampleSize->max_size; @@ -2008,7 +2029,7 @@ GF_EXPORT u32 gf_isom_get_avg_sample_size(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize) return 0; if ( trak->Media->information->sampleTable->SampleSize->sampleSize) @@ -2021,7 +2042,7 @@ GF_EXPORT u32 gf_isom_get_max_sample_delta(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->TimeToSample) return 0; return trak->Media->information->sampleTable->TimeToSample->max_ts_delta; @@ -2030,7 +2051,7 @@ GF_EXPORT u32 gf_isom_get_avg_sample_delta(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->TimeToSample) return 0; GF_TimeToSampleBox *stts = trak->Media->information->sampleTable->TimeToSample; @@ -2048,7 +2069,7 @@ GF_EXPORT u32 gf_isom_get_max_sample_cts_offset(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->CompositionOffset) return 0; return trak->Media->information->sampleTable->CompositionOffset->max_cts_delta; @@ -2060,7 +2081,7 @@ { GF_ISOSAPType is_rap; GF_Err e; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !sampleNumber) return GF_FALSE; if (! trak->Media->information->sampleTable->SyncSample) return GF_TRUE; @@ -2080,7 +2101,7 @@ GF_Err e; GF_TrackBox *trak; GF_ISOSample *samp; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return NULL; if (!sampleNumber) return NULL; @@ -2121,7 +2142,7 @@ { u64 dts; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; if (!sampleNumber) return 0; @@ -2137,7 +2158,7 @@ Bool gf_isom_is_self_contained(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; return Media_IsSelfContained(trak->Media, sampleDescriptionIndex); } @@ -2151,7 +2172,7 @@ GF_TrackBox *trak; GF_SampleTableBox *stbl; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; stbl = trak->Media->information->sampleTable; @@ -2180,7 +2201,7 @@ Bool ext_realloc = GF_FALSE; if (SampleNum) *SampleNum = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -2351,7 +2372,7 @@ u32 sampNum; u8 useEdit; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; //only check duration if initially set - do not check duration as updated after fragment merge since that duration does not take @@ -2461,7 +2482,7 @@ u64 gf_isom_get_missing_bytes(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->Media->BytesMissing; @@ -2471,7 +2492,7 @@ GF_Err gf_isom_set_sample_padding(GF_ISOFile *the_file, u32 trackNumber, u32 padding_bytes) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; trak->padding_bytes = padding_bytes; return GF_OK; @@ -2485,7 +2506,7 @@ GF_EdtsEntry *ent; GF_TrackBox *trak; u32 count; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; *mediaOffset = 0; if (!trak->editBox || !trak->editBox->editList) return GF_FALSE; @@ -2526,7 +2547,7 @@ u32 gf_isom_get_edits_count(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; if (!trak->editBox || !trak->editBox->editList) return 0; @@ -2545,7 +2566,7 @@ GF_EdtsEntry *ent; ent = NULL; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->editBox || @@ -2583,7 +2604,7 @@ { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable) return 0; if (trak->Media->information->sampleTable->SyncSample) { if (!trak->Media->information->sampleTable->SyncSample->nb_entries) return 2; @@ -2597,7 +2618,7 @@ u32 gf_isom_get_sync_point_count(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; if (trak->Media->information->sampleTable->SyncSample) { return trak->Media->information->sampleTable->SyncSample->nb_entries; @@ -2644,7 +2665,7 @@ { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; @@ -2660,7 +2681,7 @@ { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; if (trak->Media->information->sampleTable->PaddingBits) return GF_TRUE; @@ -2675,7 +2696,7 @@ if (!movie || !movie->moov) return 0; if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; udta = trak->udta; } else { @@ -2695,7 +2716,7 @@ if (!movie || !movie->moov || !udta_idx) return GF_BAD_PARAM; if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_OK; udta = trak->udta; } else { @@ -2725,7 +2746,7 @@ memset(t, 1, 16); if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; udta = trak->udta; } else { @@ -2738,7 +2759,7 @@ while ((map = (GF_UserDataMap*)gf_list_enum(udta->recordList, &i))) { count = gf_list_count(map->boxes); - if ((map->boxType == GF_ISOM_BOX_TYPE_UUID) && !memcmp(map->uuid, UUID, 16)) return count; + if ((map->boxType == GF_ISOM_BOX_TYPE_UUID) && UUID && !memcmp(map->uuid, UUID, 16)) return count; else if (map->boxType == UserDataType) return count; } return 0; @@ -2758,7 +2779,7 @@ if (!movie || !movie->moov) return GF_BAD_PARAM; if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; udta = trak->udta; } else { @@ -2811,8 +2832,7 @@ } else { char *str = NULL; switch (ptr->type) { - case GF_ISOM_BOX_TYPE_NAME: - //case GF_QT_BOX_TYPE_NAME: same as above + case GF_QT_BOX_TYPE_NAME: str = ((GF_NameBox *)ptr)->string; break; case GF_ISOM_BOX_TYPE_KIND: @@ -2891,7 +2911,7 @@ GF_SampleToChunkBox *stsc; GF_TimeToSampleBox *stts; if (!movie || !trackNumber || !movie->moov) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsc = trak->Media->information->sampleTable->SampleToChunk; @@ -2962,7 +2982,7 @@ GF_TrackExtendsBox *trex; #endif GF_SampleTableBox *stbl; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; /*if trex is already set, restore flags*/ @@ -3105,9 +3125,11 @@ gf_isom_datamap_del(previous_movie_fileMap_address); } } - - prevsize = gf_bs_get_refreshed_size(movie->movieFileMap->bs); - if (prevsize==size) return GF_OK; + //if data map uses a blob, we cannot check the size, as it may be correctly advertized before but incomplete at last refresh (multicast source) + if (!movie->movieFileMap->use_blob) { + prevsize = gf_bs_get_refreshed_size(movie->movieFileMap->bs); + if (prevsize==size) return GF_OK; + } if (!movie->moov->mvex) return GF_OK; @@ -3175,7 +3197,7 @@ GF_TrackExtendsBox *trex; GF_SampleTableBox *stbl; #endif - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; /*if trex is already set, restore flags*/ @@ -3192,6 +3214,8 @@ stbl = trak->Media->information->sampleTable; if (!stbl->TimeToSample || !stbl->SampleSize || !stbl->SampleToChunk) return GF_ISOM_INVALID_FILE; + if (stbl->SampleSize->sampleCount < nb_samples) return GF_BAD_PARAM; + //remove at once nb_samples in stts, ctts, stsz, stco, stsc and stdp (n-times removal is way too slow) //do NOT change the order DTS, CTS, size chunk stbl_RemoveDTS(stbl, 1, nb_samples, 0); @@ -3225,6 +3249,24 @@ } } } + //also purge tmap + if (stbl->traf_map) { + u32 i; + for (i=0; i<stbl->traf_map->nb_entries; i++) { + GF_TrafMapEntry *tmap_ent = &stbl->traf_map->frag_startsi; + if (tmap_ent->sample_num <= nb_samples) { + if (tmap_ent->moof_template) gf_free(tmap_ent->moof_template); + memmove(&stbl->traf_map->frag_startsi, + &stbl->traf_map->frag_startsi+1, + sizeof(GF_TrafMapEntry) * (stbl->traf_map->nb_entries-1) + ); + i--; + stbl->traf_map->nb_entries--; + continue; + } + tmap_ent->sample_num -= nb_samples; + } + } //then remove sample per sample for the rest, which is either //- sparse data //- allocated structure rather than memmove-able array @@ -3238,6 +3280,11 @@ GF_CENCSampleAuxInfo *sai = gf_list_pop_front(trak->sample_encryption->samp_aux_info); gf_isom_cenc_samp_aux_info_del(sai); } + if (stbl->SampleRefs) { + GF_SampleRefEntry *ent = gf_list_pop_front(stbl->SampleRefs->entries); + if (ent->sample_refs) gf_free(ent->sample_refs); + gf_free(ent); + } nb_samples--; } return GF_OK; @@ -3300,8 +3347,8 @@ } if (stbl->ShadowSync) { - gf_isom_box_del_parent(&stbl->child_boxes, (GF_Box *) stbl->ShadowSync); - stbl->ShadowSync = (GF_ShadowSyncBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSH); + gf_isom_box_del_parent(&stbl->child_boxes, (GF_Box *) stbl->ShadowSync); + stbl->ShadowSync = (GF_ShadowSyncBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSH); } if (stbl->SyncSample) { @@ -3316,6 +3363,10 @@ stbl->TimeToSample->r_currentEntryIndex = 0; stbl->TimeToSample->r_CurrentDTS = 0; } + if (stbl->SampleRefs) { + gf_isom_box_del_parent(&stbl->child_boxes, (GF_Box *)stbl->SampleRefs); + stbl->SampleRefs = NULL; + } gf_isom_box_array_del_parent(&stbl->child_boxes, stbl->sai_offsets); stbl->sai_offsets = NULL; @@ -3444,7 +3495,7 @@ GF_TrackBox *base; gf_isom_get_reference(movie, i+1, GF_ISOM_REF_BASE, 1, &on_track); - base = gf_isom_get_track_from_file(movie, on_track); + base = gf_isom_get_track_box(movie, on_track); if (!base) { base_track_sample_count=0; } else { @@ -3453,9 +3504,13 @@ } } - trak->sample_count_at_seg_start += base_track_sample_count ? base_track_sample_count : stbl->SampleSize->sampleCount; + if (base_track_sample_count) { + trak->sample_count_at_seg_start += base_track_sample_count; + } else if (stbl->SampleSize) { + trak->sample_count_at_seg_start += stbl->SampleSize->sampleCount; + } - if (trak->sample_count_at_seg_start) { + if (trak->sample_count_at_seg_start && stbl->SampleSize) { GF_Err e; e = stbl_GetSampleDTS_and_Duration(stbl->TimeToSample, stbl->SampleSize->sampleCount, &dts, &dur); if (e == GF_OK) { @@ -3646,7 +3701,7 @@ if (a->type == GF_ISOM_BOX_TYPE_UNKNOWN) break; a = NULL; } - if (!a) return; + if (!a || !a->data || !a->dataSize) return; udesc->extension_buf = (char*)gf_malloc(sizeof(char) * a->dataSize); if (udesc->extension_buf) { udesc->extension_buf_size = a->dataSize; @@ -3663,7 +3718,7 @@ GF_GenericSampleEntryBox *genm; GF_TrackBox *trak; GF_GenericSampleDescription *udesc; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !StreamDescriptionIndex || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable) return 0; entry = (GF_GenericVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -3769,7 +3824,7 @@ GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -3800,7 +3855,7 @@ GF_SampleEntryBox* entry; GF_SampleDescriptionBox* stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -3828,7 +3883,7 @@ GF_AudioSampleEntryBox *entry; GF_SampleDescriptionBox *stsd = NULL; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->Media && trak->Media->information && trak->Media->information->sampleTable && trak->Media->information->sampleTable->SampleDescription) @@ -3850,6 +3905,9 @@ sr |= entry->samplerate_lo; (*SampleRate) = sr; } + + GF_SamplingRateBox *srat = (GF_SamplingRateBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SRAT); + if (srat) *SampleRate = srat->sampling_rate; } if (Channels) (*Channels) = entry->channel_count; if (bitsPerSample) (*bitsPerSample) = (u8) entry->bitspersample; @@ -3865,7 +3923,7 @@ GF_SampleDescriptionBox *stsd; GF_ChannelLayoutBox *chnl; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !layout) return GF_BAD_PARAM; memset(layout, 0, sizeof(GF_AudioChannelLayout)); @@ -3891,7 +3949,7 @@ GF_VisualSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !hSpacing || !vSpacing) return GF_BAD_PARAM; *hSpacing = 1; *vSpacing = 1; @@ -3924,7 +3982,7 @@ GF_VisualSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -3969,7 +4027,7 @@ *icc_size = 0; if (icc_restricted) *icc_restricted = GF_FALSE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -4048,7 +4106,7 @@ GF_EXPORT GF_Err gf_isom_get_track_matrix(GF_ISOFile *the_file, u32 trackNumber, u32 matrix9) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Header) return GF_BAD_PARAM; memcpy(matrix, trak->Header->matrix, sizeof(trak->Header->matrix)); return GF_OK; @@ -4057,7 +4115,7 @@ GF_EXPORT GF_Err gf_isom_get_track_layout_info(GF_ISOFile *movie, u32 trackNumber, u32 *width, u32 *height, s32 *translation_x, s32 *translation_y, s16 *layer) { - GF_TrackBox *tk = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *tk = gf_isom_get_track_box(movie, trackNumber); if (!tk) return GF_BAD_PARAM; if (width) *width = tk->Header->width>>16; if (height) *height = tk->Header->height>>16; @@ -4075,7 +4133,7 @@ u32 i; u64 size; GF_SampleSizeBox *stsz; - GF_TrackBox *tk = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *tk = gf_isom_get_track_box(movie, trackNumber); if (!tk) return 0; stsz = tk->Media->information->sampleTable->SampleSize; if (!stsz) return 0; @@ -4088,7 +4146,11 @@ } if (stsz->sampleSize) return stsz->sampleSize*stsz->sampleCount; size = 0; - for (i=0; i<stsz->sampleCount; i++) size += stsz->sizesi; + if (stsz->sizes) { + for (i=0; i<stsz->sampleCount; i++) { + size += stsz->sizesi; + } + } #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (movie->moov->mvex) return size; #endif @@ -4146,7 +4208,7 @@ GF_EXPORT void gf_isom_set_default_sync_track(GF_ISOFile *movie, u32 trackNumber) { - GF_TrackBox *tk = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *tk = gf_isom_get_track_box(movie, trackNumber); if (!tk) movie->es_id_default_sync = -1; else movie->es_id_default_sync = tk->Header->trackID; } @@ -4179,16 +4241,16 @@ case GF_ISOM_MEDIA_AUDIO: nb_a++; break; - case GF_ISOM_MEDIA_AUXV: - /*discard file with images*/ - if (gf_isom_get_sample_count(file, i+1)==1) nb_any++; - else nb_auxv++; - break; - case GF_ISOM_MEDIA_PICT: - /*discard file with images*/ - if (gf_isom_get_sample_count(file, i+1)==1) nb_any++; - else nb_pict++; - break; + case GF_ISOM_MEDIA_AUXV: + /*discard file with images*/ + if (gf_isom_get_sample_count(file, i+1)==1) nb_any++; + else nb_auxv++; + break; + case GF_ISOM_MEDIA_PICT: + /*discard file with images*/ + if (gf_isom_get_sample_count(file, i+1)==1) nb_any++; + else nb_pict++; + break; case GF_ISOM_MEDIA_VISUAL: /*discard file with images*/ if (gf_isom_get_sample_count(file, i+1)==1) nb_any++; @@ -4426,12 +4488,13 @@ } GF_EXPORT -GF_Err gf_isom_apple_enum_tag(GF_ISOFile *mov, u32 idx, GF_ISOiTunesTag *out_tag, const u8 **data, u32 *data_len, u64 *out_int_val, u32 *out_int_val2, u32 *out_flags) +GF_Err gf_isom_apple_enum_tag_ex(GF_ISOFile *mov, u32 idx, GF_ISOiTunesTag *out_tag, const u8 **data, u32 *data_len, u64 *out_int_val, u32 *out_int_val2, u32 *out_flags, const char **out_mean, const char **out_name, u32 *out_locale) { u32 i, child_index; GF_ListItemBox *info; GF_ItemListBox *ilst; GF_MetaBox *meta; + GF_NameBox *name_box=NULL, *mean_box=NULL; GF_DataBox *dbox = NULL; Bool found=GF_FALSE; u32 itype, tag_val; @@ -4441,6 +4504,9 @@ *out_int_val = 0; *out_int_val2 = 0; *out_flags = 0; + if (out_mean) *out_mean = NULL; + if (out_name) *out_name = NULL; + if (out_locale) *out_locale = 0; meta = (GF_MetaBox *) gf_isom_get_meta_extensions(mov, 0); if (!meta) return GF_URL_ERROR; @@ -4451,6 +4517,7 @@ child_index = i = 0; while ( (info=(GF_ListItemBox*)gf_list_enum(ilst->child_boxes, &i))) { GF_DataBox *data_box = NULL; + name_box = mean_box = NULL; if (gf_itags_find_by_itag(info->type)<0) { tag_val = info->type; if (info->type==GF_ISOM_BOX_TYPE_UNKNOWN) { @@ -4458,6 +4525,13 @@ if (!data_box) continue; tag_val = ((GF_UnknownBox *)info)->original_4cc; } + else if (info->type==GF_ISOM_BOX_TYPE_iTunesSpecificInfo) { + name_box = (GF_NameBox *) gf_isom_box_find_child(info->child_boxes, GF_QT_BOX_TYPE_NAME); + mean_box = (GF_NameBox *) gf_isom_box_find_child(info->child_boxes, GF_QT_BOX_TYPE_MEAN); + data_box = (GF_DataBox *) gf_isom_box_find_child(info->child_boxes, GF_ISOM_BOX_TYPE_DATA); + if (!data_box) continue; + tag_val = ((GF_UnknownBox *)info)->original_4cc; + } } else { data_box = info->data; tag_val = info->type; @@ -4479,6 +4553,11 @@ } return GF_URL_ERROR; } + + if (out_mean && mean_box) *out_mean = mean_box->string; + if (out_name && name_box) *out_name = name_box->string; + if (out_locale) *out_locale = dbox->locale; + *out_flags = dbox->flags; *out_tag = tag_val; if (!dbox->data) { @@ -4572,6 +4651,12 @@ } GF_EXPORT +GF_Err gf_isom_apple_enum_tag(GF_ISOFile *mov, u32 idx, GF_ISOiTunesTag *out_tag, const u8 **data, u32 *data_len, u64 *out_int_val, u32 *out_int_val2, u32 *out_flags) +{ + return gf_isom_apple_enum_tag_ex(mov, idx, out_tag, data, data_len, out_int_val, out_int_val2, out_flags, NULL, NULL, NULL); +} + +GF_EXPORT GF_Err gf_isom_enum_udta_keys(GF_ISOFile *mov, u32 idx, GF_QT_UDTAKey *okey) { u32 i, count; @@ -4730,7 +4815,7 @@ GF_UserDataMap *map; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Header) return GF_BAD_PARAM; if (alternateGroupID) *alternateGroupID = trak->Header->alternate_group; if (nb_groups) *nb_groups = 0; @@ -4749,7 +4834,7 @@ GF_UserDataMap *map; GF_TrackSelectionBox *tsel; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!group_index || !trak || !trak->udta) return NULL; map = udta_getEntry(trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL); @@ -4769,7 +4854,7 @@ u32 i=0; while (i< gf_isom_get_track_count(movie) ) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, i+1); + GF_TrackBox *trak = gf_isom_get_track_box(movie, i+1); if (trak->Header->alternate_group > id) id = trak->Header->alternate_group; i++; @@ -4784,7 +4869,7 @@ u32 size; u32 i, count; GF_BitStream *bs = NULL; - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track); + GF_TrackBox *trak = gf_isom_get_track_box(movie, track); if (!trak || !osize) return NULL; if (!trak->Media || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->sub_samples) return NULL; @@ -4823,7 +4908,7 @@ GF_EXPORT u32 gf_isom_sample_has_subsamples(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track); + GF_TrackBox *trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sub_samples) return 0; if (!sampleNumber) return 1; @@ -4856,7 +4941,7 @@ if (!rvc_predefined || !data || !size) return GF_BAD_PARAM; *rvc_predefined = 0; - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; @@ -4891,10 +4976,11 @@ void gf_isom_reset_fragment_info(GF_ISOFile *movie, Bool keep_sample_count) { u32 i; - if (!movie) return; + if (!movie || !movie->moov) return; for (i=0; i<gf_list_count(movie->moov->trackList); i++) { GF_TrackBox *trak = (GF_TrackBox*)gf_list_get(movie->moov->trackList, i); - trak->Media->information->sampleTable->SampleSize->sampleCount = 0; + if (trak->Media->information->sampleTable->SampleSize) + trak->Media->information->sampleTable->SampleSize->sampleCount = 0; #ifdef GPAC_DISABLE_ISOM_FRAGMENTS } #else @@ -4939,7 +5025,7 @@ if (has_selective) *has_selective = GF_FALSE; if (has_roll) *has_roll = GF_FALSE; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_FALSE; if (!trak->Media->information->sampleTable->sampleGroups) return GF_FALSE; @@ -4965,6 +5051,70 @@ return GF_TRUE; } +GF_Err isom_get_sample_switch_frame(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *switch_frame) +{ + GF_TrackBox *trak; + u32 i, count; + + if (switch_frame) *switch_frame = GF_FALSE; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak) return GF_BAD_PARAM; + if (!trak->Media->information->sampleTable->sampleGroups) return GF_OK; + + if (!sample_number) { + count = gf_list_count(trak->Media->information->sampleTable->sampleGroupsDescription); + for (i=0; i<count; i++) { + GF_SampleGroupDescriptionBox *sgdesc = (GF_SampleGroupDescriptionBox*)gf_list_get(trak->Media->information->sampleTable->sampleGroupsDescription, i); + switch (sgdesc->grouping_type) { + case GF_ISOM_SAMPLE_GROUP_AV1S: + if (switch_frame) *switch_frame = GF_TRUE; + break; + } + } + return GF_OK; + } + + count = gf_list_count(trak->Media->information->sampleTable->sampleGroups); + for (i=0; i<count; i++) { + GF_SampleGroupBox *sg; + u32 j, group_desc_index; + GF_SampleGroupDescriptionBox *sgdesc; + u32 first_sample_in_entry, last_sample_in_entry; + first_sample_in_entry = 1; + group_desc_index = 0; + sg = (GF_SampleGroupBox*)gf_list_get(trak->Media->information->sampleTable->sampleGroups, i); + for (j=0; j<sg->entry_count; j++) { + last_sample_in_entry = first_sample_in_entry + sg->sample_entriesj.sample_count - 1; + if ((sample_number<first_sample_in_entry) || (sample_number>last_sample_in_entry)) { + first_sample_in_entry = last_sample_in_entry+1; + continue; + } + /*we found our sample*/ + group_desc_index = sg->sample_entriesj.group_description_index; + break; + } + /*no sampleGroup info associated*/ + if (!group_desc_index) continue; + + sgdesc = NULL; + for (j=0; j<gf_list_count(trak->Media->information->sampleTable->sampleGroupsDescription); j++) { + sgdesc = (GF_SampleGroupDescriptionBox*)gf_list_get(trak->Media->information->sampleTable->sampleGroupsDescription, j); + if (sgdesc->grouping_type==sg->grouping_type) break; + sgdesc = NULL; + } + /*no sampleGroup description found for this group (invalid file)*/ + if (!sgdesc) continue; + + switch (sgdesc->grouping_type) { + case GF_ISOM_SAMPLE_GROUP_AV1S: + if (switch_frame) *switch_frame = GF_TRUE; + break; + } + } + return GF_OK; +} + GF_EXPORT GF_Err gf_isom_get_sample_rap_roll_info(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *is_rap, GF_ISOSampleRollType *roll_type, s32 *roll_distance) { @@ -4975,7 +5125,7 @@ if (roll_type) *roll_type = 0; if (roll_distance) *roll_distance = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sampleGroups) return GF_OK; @@ -5070,7 +5220,7 @@ *sampleGroupDescIndex = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sampleGroups) return GF_OK; @@ -5115,7 +5265,7 @@ if (sgrp_size) *sgrp_size = 0; *sgrp_type = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sampleGroupsDescription) return GF_OK; @@ -5225,7 +5375,7 @@ { GF_DefaultSampleGroupDescriptionEntry *sg_entry; GF_SampleGroupDescriptionBox *sgdp=NULL; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (default_index) *default_index = 0; if (size) *size = 0; @@ -5331,7 +5481,7 @@ GF_Err gf_isom_set_nalu_extract_mode(GF_ISOFile *the_file, u32 trackNumber, GF_ISONaluExtractMode nalu_extract_mode) { GF_TrackReferenceTypeBox *dpnd; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; trak->extractor_mode = nalu_extract_mode; @@ -5348,7 +5498,7 @@ GF_EXPORT GF_ISONaluExtractMode gf_isom_get_nalu_extract_mode(GF_ISOFile *the_file, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->extractor_mode; } @@ -5356,10 +5506,10 @@ GF_EXPORT s32 gf_isom_get_composition_offset_shift(GF_ISOFile *file, u32 track) { - GF_TrackBox *trak = gf_isom_get_track_from_file(file, track); + GF_TrackBox *trak = gf_isom_get_track_box(file, track); if (!trak) return 0; if (!trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->CompositionToDecode) return 0; - return trak->Media->information->sampleTable->CompositionToDecode->compositionToDTSShift; + return (s32) trak->Media->information->sampleTable->CompositionToDecode->compositionToDTSShift; } GF_EXPORT @@ -5639,7 +5789,7 @@ GF_EXPORT GF_Err gf_isom_get_sample_cenc_info(GF_ISOFile *movie, u32 track, u32 sample_number, Bool *IsEncrypted, u32 *crypt_byte_block, u32 *skip_byte_block, const u8 **key_info, u32 *key_info_size) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track); + GF_TrackBox *trak = gf_isom_get_track_box(movie, track); GF_SampleEncryptionBox *senc = trak->sample_encryption; return gf_isom_get_sample_cenc_info_internal(trak, NULL, senc, sample_number, IsEncrypted, crypt_byte_block, skip_byte_block, key_info, key_info_size); @@ -5675,7 +5825,7 @@ return 0; #else GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->dts_at_seg_start; #endif @@ -5688,7 +5838,7 @@ return 0; #else GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->dts_at_next_frag_start; #endif @@ -5740,14 +5890,14 @@ Bool gf_isom_get_oinf_info(GF_ISOFile *file, u32 trackNumber, GF_OperatingPointsInformation **ptr) { u32 oref_track, def_index=0; - GF_TrackBox *trak = gf_isom_get_track_from_file(file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(file, trackNumber); if (!ptr) return GF_FALSE; oref_track=0; gf_isom_get_reference(file, trackNumber, GF_ISOM_REF_OREF, 1, &oref_track); if (oref_track) { - trak = gf_isom_get_track_from_file(file, oref_track); + trak = gf_isom_get_track_box(file, oref_track); if (!trak) return GF_FALSE; } @@ -5772,7 +5922,7 @@ GF_MPEGVisualSampleEntryBox *ve; GF_SampleDescriptionBox *stsd; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) { file->LastError = GF_BAD_PARAM; return 0; @@ -5823,7 +5973,7 @@ GF_TrackBox *trak; GF_ESDBox *esd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; mrate = arate = dbsize = 0; @@ -5833,7 +5983,7 @@ ent = (GF_SampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, i); if (!ent) return GF_BAD_PARAM; - a = gf_isom_sample_entry_get_bitrate(ent, GF_FALSE); + a = gf_isom_sample_entry_get_bitrate_box(ent, GF_FALSE); if (a) { if (mrate<a->maxBitrate) mrate = a->maxBitrate; if (arate<a->avgBitrate) arate = a->avgBitrate; @@ -5889,7 +6039,7 @@ if (frag_info) memset(frag_info, 0, sizeof(GF_ISOFragmentBoundaryInfo)); - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_FALSE; if (!trak->Media->information->sampleTable->traf_map) return GF_FALSE; @@ -5949,10 +6099,10 @@ GF_MPEGVisualSampleEntryBox *entry; GF_BitStream *bs; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleDescription) return GF_ISOM_INVALID_FILE; entry = (GF_MPEGVisualSampleEntryBox *) gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDesc-1); - if (!entry || !entry->jp2h) return GF_BAD_PARAM; + if (!entry || entry->type == GF_ISOM_BOX_TYPE_GNRA || !entry->jp2h) return GF_BAD_PARAM; if (!entry->jp2h->ihdr) return GF_ISOM_INVALID_FILE; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -6009,7 +6159,7 @@ GF_EXPORT u64 gf_isom_get_track_magic(GF_ISOFile *movie, u32 trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return 0; return trak->magic; } @@ -6068,7 +6218,7 @@ GF_MHACompatibleProfilesBox *mhap; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !nb_compat_profiles) return NULL; *nb_compat_profiles = 0; ent = gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescIndex-1); @@ -6084,7 +6234,7 @@ #ifdef GPAC_DISABLE_ISOM_FRAGMENTS return NULL; #else - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return NULL; return trak->tfrf; @@ -6096,7 +6246,7 @@ GF_SampleEntryBox *ent; GF_TrackBox *trak; Bool found = GF_FALSE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !info) return GF_BAD_PARAM; ent = gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescriptionIndex-1); @@ -6160,7 +6310,7 @@ GF_TrackBox *trak; if (!movie || !movie->moov || !trackNumber) return 0; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->ChunkOffset ) return 0; stco = (GF_ChunkOffsetBox *) trak->Media->information->sampleTable->ChunkOffset; @@ -6181,7 +6331,7 @@ u32 i, nb_entries, nb_samples, sample_desc_index; if (!movie || !movie->moov || !trackNumber || !chunk_num) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->ChunkOffset ) return GF_BAD_PARAM; stsc = (GF_SampleToChunkBox *) trak->Media->information->sampleTable->SampleToChunk; @@ -6258,7 +6408,7 @@ GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -6290,7 +6440,7 @@ GF_TrackGroupTypeBox *trgt; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(file, track_number); + trak = gf_isom_get_track_box(file, track_number); if (!trak) return 0; if (!trak->groups) return 0; @@ -6309,7 +6459,7 @@ GF_TrackGroupTypeBox *trgt; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(file, track_number); + trak = gf_isom_get_track_box(file, track_number); if (!trak || !idx) return GF_FALSE; if (!trak->groups) return GF_FALSE; @@ -6328,7 +6478,7 @@ GF_SampleEntryBox* entry; GF_SampleDescriptionBox* stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return NULL; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -6353,7 +6503,7 @@ GF_SampleEntryBox* entry; GF_SampleDescriptionBox* stsd; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return NULL; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -6380,7 +6530,7 @@ if (sai_parameter) *sai_parameter = 0; *sai_type = 0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sai_sizes) return GF_OK; if (!trak->Media->information->sampleTable->sai_offsets) return GF_OK; @@ -6445,18 +6595,20 @@ e = GF_OK; if (saio->sai_data) { - if (offset + *sai_size <= saio->sai_data->dataSize) { + if (*sai_data && offset + *sai_size <= saio->sai_data->dataSize) { memcpy(*sai_data, saio->sai_data->data + offset, *sai_size); } else { e = GF_IO_ERR; } } else { - u64 cur_position = gf_bs_get_position(the_file->movieFileMap->bs); - gf_bs_seek(the_file->movieFileMap->bs, offset); - - u32 nb_read = gf_bs_read_data(the_file->movieFileMap->bs, *sai_data, *sai_size); - if (nb_read != *sai_size) e = GF_IO_ERR; - gf_bs_seek(the_file->movieFileMap->bs, cur_position); + if (*sai_data) { + u64 cur_position = gf_bs_get_position(the_file->movieFileMap->bs); + gf_bs_seek(the_file->movieFileMap->bs, offset); + + u32 nb_read = gf_bs_read_data(the_file->movieFileMap->bs, *sai_data, *sai_size); + if (nb_read != *sai_size) e = GF_IO_ERR; + gf_bs_seek(the_file->movieFileMap->bs, cur_position); + } } if (e) { @@ -6493,11 +6645,103 @@ GF_Err gf_isom_set_sample_alloc(GF_ISOFile *the_file, u32 trackNumber, u8 *(*sample_alloc)(u32 size, void *cbk), void *udta) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; trak->sample_alloc_cbk = sample_alloc; trak->sample_alloc_udta = udta; return GF_OK; } +GF_EXPORT +Bool gf_isom_is_external_track(GF_ISOFile *the_file, u32 trackNumber, GF_ISOTrackID *tkid, u32 *type, u32 *flags, const char **location) +{ + GF_TrackBox *trak; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->extl) return GF_FALSE; + + if (tkid) *tkid = trak->extl->referenced_track_ID; + if (type) *type = trak->extl->referenced_handler_type; + if (location) *location = trak->extl->location; + if (flags) *flags = trak->extl->flags; + return GF_TRUE; +} + +GF_Err gf_isom_merge_external_edit(GF_ISOFile *dst, u32 dst_track, GF_ISOFile *src, u32 src_track) +{ + GF_TrackBox *trak_dst = gf_isom_get_track_box(dst, dst_track); + if (!trak_dst || trak_dst->extl || !dst->moov->mvhd) return GF_BAD_PARAM; + GF_TrackBox *trak_src = gf_isom_get_track_box(src, src_track); + if (!trak_src || !trak_src->extl || !src->moov->mvhd) return GF_BAD_PARAM; + if (!trak_src->extl->media_timescale) return GF_ISOM_INVALID_FILE; + + if (trak_dst->editBox) { + gf_isom_box_del_parent(&trak_dst->child_boxes, (GF_Box*)trak_dst->editBox); + trak_dst->editBox = NULL; + } + if (!trak_src->editBox) return GF_OK; + gf_list_del_item(trak_src->child_boxes, trak_src->editBox); + if (!trak_src->editBox->editList || !gf_list_count(trak_src->editBox->editList->entryList)) { + gf_isom_box_del((GF_Box*)trak_src->editBox); + trak_src->editBox = NULL; + return GF_OK; + } + trak_dst->editBox = trak_src->editBox; + trak_src->editBox = NULL; + u32 i, nb_entries = gf_list_count(trak_dst->editBox->editList->entryList); + for (i=0; i<nb_entries; i++) { + GF_EdtsEntry *ent = gf_list_get(trak_dst->editBox->editList->entryList, i); + gf_timestamp_rescale(ent->segmentDuration, src->moov->mvhd->timeScale, dst->moov->mvhd->timeScale); + gf_timestamp_rescale(ent->mediaTime, trak_src->extl->media_timescale, trak_dst->Media->mediaHeader->timeScale); + } + return GF_OK; +} + +GF_EXPORT +s32 gf_isom_get_min_negative_cts_offset(GF_ISOFile *the_file, u32 trackNumber, GF_ISOMMinNegCtsQuery query_mode) +{ + GF_TrackBox *trak; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak) return 0; + if (!trak->Media->information->sampleTable) return 0; + if (query_mode!=GF_ISOM_MIN_NEGCTTS_SAMPLES) { + if (trak->Media->information->sampleTable->CompositionToDecode) { + return (s32) -trak->Media->information->sampleTable->CompositionToDecode->compositionToDTSShift; + } + if (query_mode==GF_ISOM_MIN_NEGCTTS_CLSG) return 0; + } + if (!trak->Media->information->sampleTable->CompositionOffset) return 0; + return trak->Media->information->sampleTable->CompositionOffset->min_neg_cts_offset; +} + +GF_EXPORT +GF_Err gf_isom_switch_source(GF_ISOFile *the_file, const char *new_file) +{ + if (!the_file) return GF_BAD_PARAM; + if (the_file->openMode>GF_ISOM_OPEN_READ) return GF_BAD_PARAM; + gf_isom_datamap_del(the_file->movieFileMap); + + return gf_isom_datamap_new(new_file, NULL, GF_ISOM_DATA_MAP_READ_ONLY, &the_file->movieFileMap); +} + +GF_EXPORT +GF_Err gf_isom_get_sample_references(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u32 *ID, u32 *nb_refs, const u32 **refs) +{ + GF_TrackBox *trak; + *ID = *nb_refs = 0; + *refs = NULL; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !sampleNumber) return GF_BAD_PARAM; + if (!trak->Media->information->sampleTable->SampleRefs) return GF_NOT_FOUND; + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + if (sampleNumber <= trak->sample_count_at_seg_start) + return GF_BAD_PARAM; + sampleNumber -= trak->sample_count_at_seg_start; +#endif + GF_SampleRefEntry *ent = gf_list_get(trak->Media->information->sampleTable->SampleRefs->entries, sampleNumber-1); + *ID = ent->sampleID; + *nb_refs = ent->nb_refs; + *refs = ent->sample_refs; + return GF_OK; +} #endif /*GPAC_DISABLE_ISOM*/
View file
gpac-2.4.0.tar.gz/src/isomedia/isom_store.c -> gpac-26.02.0.tar.gz/src/isomedia/isom_store.c
Changed
@@ -159,6 +159,7 @@ for (i = 0; i < trackCount; i++) { GF_SampleTableBox *stbl; trak = gf_isom_get_track(movie->moov, i+1); + if (trak->extl) continue; stbl = (trak->Media && trak->Media->information) ? trak->Media->information->sampleTable : NULL; if (!stbl || !stbl->SampleSize || !stbl->ChunkOffset || !stbl->SampleToChunk || !stbl->SampleSize) { @@ -631,7 +632,7 @@ map = mw->movie->movieFileMap; } //get the payload... - bytes = gf_isom_datamap_get_data(map, mw->buffer, size, offset); + bytes = gf_isom_datamap_get_data(map, mw->buffer, size, offset, NULL); if (bytes != size) return GF_IO_ERR; //write it to our stream... @@ -848,6 +849,7 @@ GF_Err DoWriteMeta(GF_ISOFile *file, GF_MetaBox *meta, GF_BitStream *bs, Bool Emulation, u64 baseOffset, u64 *mdatSize) { + char cache_data4096; GF_ItemExtentEntry *entry; u64 maxExtendOffset, maxExtendSize; u32 i, j, count; @@ -926,7 +928,6 @@ if (iinf->tk_id && iinf->sample_num) { } else if (src) { - char cache_data4096; u64 remain = entry->extent_length; while (remain) { u32 size_cache = (remain>4096) ? 4096 : (u32) remain; @@ -956,7 +957,6 @@ /*Reading from the input file*/ if (!Emulation) { - char cache_data4096; u64 remain = entry->extent_length; gf_bs_seek(file->movieFileMap->bs, entry->original_extent_offset + iloc->original_base_offset); while (remain) { @@ -1116,7 +1116,8 @@ } } //set the mdatSize... - movie->mdat->dataSize = mdatSize; + if (movie && movie->mdat) + movie->mdat->dataSize = mdatSize; return GF_OK; } @@ -1428,7 +1429,8 @@ gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_MDAT); if (totSize > 0xFFFFFFFF) gf_bs_write_u64(bs, totSize); e = gf_bs_seek(bs, offset); - movie->mdat->size = totSize; + if (movie->mdat) + movie->mdat->size = totSize; } //then the rest @@ -2144,7 +2146,7 @@ } if (movie->moov) { - if (movie->moov->meta) + if (movie->moov->meta) ShiftMetaOffset(movie->moov->meta, shift_offset); count = gf_list_count(movie->moov->trackList); @@ -2159,6 +2161,8 @@ if (trak->meta) ShiftMetaOffset(trak->meta, shift_offset); + if (trak->extl) continue; + stbl = trak->Media->information->sampleTable; e = shift_chunk_offsets(stbl->SampleToChunk, trak->Media, stbl->ChunkOffset, shift_offset, movie->force_co64, &new_stco); if (e) return e; @@ -2555,6 +2559,7 @@ if (gf_sys_is_test_mode()) { trak->Header->creationTime = 0; trak->Header->modificationTime = 0; + if (trak->extl) continue; if (trak->Media->handler && trak->Media->handler->nameUTF8 && strstr(trak->Media->handler->nameUTF8, "@GPAC")) { gf_free(trak->Media->handler->nameUTF8); trak->Media->handler->nameUTF8 = gf_strdup("MediaHandler"); @@ -2664,7 +2669,7 @@ gf_bs_del(moov_bs); if (!e) e = gf_bs_insert_data(movie->editFileMap->bs, moov_data, moov_size, movie->mdat->bsOffset); - + gf_free(moov_data); } }
View file
gpac-2.4.0.tar.gz/src/isomedia/isom_write.c -> gpac-26.02.0.tar.gz/src/isomedia/isom_write.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -30,7 +30,8 @@ #if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_ISOM_WRITE) -GF_Err CanAccessMovie(GF_ISOFile *movie, GF_ISOOpenMode Mode) +GF_EXPORT +GF_Err gf_isom_can_access_movie(GF_ISOFile *movie, GF_ISOOpenMode Mode) { if (!movie) return GF_BAD_PARAM; if (movie->openMode < Mode) return GF_ISOM_INVALID_MODE; @@ -137,7 +138,7 @@ GF_Err e; GF_ES_ID_Inc *inc; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; @@ -165,7 +166,7 @@ { GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!movie->moov || !movie->moov->iods) return GF_OK; gf_isom_box_del_parent(&movie->moov->child_boxes, (GF_Box *)movie->moov->iods); @@ -182,7 +183,7 @@ u32 i; GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!movie->moov) return GF_OK; @@ -229,7 +230,7 @@ GF_Err gf_isom_set_track_creation_time(GF_ISOFile *movie,u32 trackNumber, u64 ctime, u64 mtime) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; trak->Header->creationTime = ctime; @@ -241,7 +242,7 @@ GF_Err gf_isom_set_media_creation_time(GF_ISOFile *movie,u32 trackNumber, u64 ctime, u64 mtime) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media || !trak->Media->mediaHeader) return GF_ISOM_INVALID_FILE; @@ -257,10 +258,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (enableTrack) { @@ -278,10 +279,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (op==GF_ISOM_TKFLAGS_ADD) trak->Header->flags |= flags; @@ -298,11 +299,25 @@ GF_Err e; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !code) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; + if (trak->extl) { + GF_ExtendedLanguageBox *elng = (GF_ExtendedLanguageBox *) gf_isom_box_find_child(trak->child_boxes, GF_ISOM_BOX_TYPE_ELNG); + if (!elng) { + elng = (GF_ExtendedLanguageBox *)gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_ELNG); + if (!elng) return GF_OUT_OF_MEM; + } + if (elng->extended_language) gf_free(elng->extended_language); + elng->extended_language = gf_strdup(code); + + if (!movie->keep_utc) + trak->Header->modificationTime = gf_isom_get_mp4time(); + return GF_OK; + } + // Old language-storage processing // if the new code is on 3 chars, we use it // otherwise, we find the associated 3 chars code and use it @@ -400,7 +415,7 @@ GF_Err e; GF_Descriptor *desc, *dupDesc; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; @@ -439,7 +454,7 @@ u32 i; GF_Err e; if (!timeScale) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; @@ -488,7 +503,7 @@ GF_IsomInitialObjectDescriptor *iod; GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_set_root_iod(movie); @@ -525,7 +540,7 @@ GF_Err gf_isom_set_root_od_id(GF_ISOFile *movie, u32 OD_ID) { GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); @@ -552,7 +567,7 @@ GF_Err gf_isom_set_root_od_url(GF_ISOFile *movie, const char *url_string) { GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; @@ -589,7 +604,7 @@ { GF_BitStream *bs; - GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; @@ -621,8 +636,6 @@ return e; } -//creates a new Track. If trackID = 0, the trackID is chosen by the API -//returns the track number or 0 if error GF_EXPORT u32 gf_isom_new_track_from_template(GF_ISOFile *movie, GF_ISOTrackID trakID, u32 MediaType, u32 TimeScale, u8 *tk_box, u32 tk_box_size, Bool udta_only) { @@ -634,7 +647,7 @@ GF_MediaBox *mdia; GF_UserDataBox *udta = NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) { gf_isom_set_last_error(movie, e); return 0; @@ -818,6 +831,7 @@ return 0; } +GF_EXPORT GF_Err gf_isom_set_track_stsd_templates(GF_ISOFile *movie, u32 trackNumber, u8 *stsd_data, u32 stsd_data_size) { GF_TrackBox *trak; @@ -825,10 +839,10 @@ GF_SampleDescriptionBox *stsd=NULL; GF_List *tmp; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; if (gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes)) return GF_BAD_PARAM; @@ -852,16 +866,56 @@ } GF_EXPORT +u32 gf_isom_new_external_track(GF_ISOFile *movie, GF_ISOTrackID trakID, GF_ISOTrackID refTrakID, u32 MediaType, u32 TimeScale, const char *uri) +{ + GF_TrackBox *trak; + if (!uri) { + gf_isom_set_last_error(movie, GF_BAD_PARAM); + return 0; + } + u32 track_num = gf_isom_new_track_from_template(movie, trakID, MediaType, TimeScale, NULL, 0, GF_FALSE); + if (!track_num) return GF_FALSE; + trak = gf_isom_get_track_box(movie, track_num); + if (!trak || !trak->Media) return GF_BAD_PARAM; + gf_isom_box_del_parent(&trak->child_boxes, (GF_Box*)trak->Media); + trak->Media = NULL; + + trak->type = GF_ISOM_BOX_TYPE_EXTK; + trak->extl = (GF_ExternalTrackLocationBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_EXTL); + gf_list_add(trak->child_boxes, trak->extl); + trak->extl->referenced_track_ID = refTrakID; + trak->extl->referenced_handler_type = MediaType; + trak->extl->media_timescale = TimeScale; + trak->extl->location = gf_strdup(uri); + trak->Header->flags = GF_ISOM_TK_IN_MOVIE | GF_ISOM_TK_ENABLED; + trak->Header->duration = 0xFFFFFFFF; + return track_num; +} + +GF_EXPORT +GF_Err gf_isom_force_track_duration(GF_ISOFile *movie, u32 trackNumber, u64 dur) +{ + GF_TrackBox *trak; + GF_Err e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); + if (e) return e; + trak = gf_isom_get_track_box(movie, trackNumber); + if (!trak || !trak->Header) return GF_BAD_PARAM; + trak->Header->duration = dur; + return GF_OK; +} + + +GF_EXPORT GF_Err gf_isom_remove_stream_description(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex) { GF_TrackBox *trak; GF_Err e; GF_SampleEntryBox *entry; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; if (!movie->keep_utc) @@ -888,10 +942,10 @@ u32 dataRefIndex; GF_ESD *new_esd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !esd || !esd->decoderConfig || !esd->slConfig) return GF_BAD_PARAM; @@ -1014,10 +1068,10 @@ u32 descIndex; GF_DataEntryURLBox *Dentry; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = FlushCaptureMode(movie); @@ -1098,10 +1152,10 @@ GF_DataEntryURLBox *Dentry; Bool offset_times = GF_FALSE; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !sample) return GF_BAD_PARAM; e = FlushCaptureMode(movie); @@ -1171,10 +1225,10 @@ GF_DataEntryURLBox *Dentry; if (!data_size) return GF_OK; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) return GF_BAD_PARAM; @@ -1221,10 +1275,10 @@ GF_DataEntryURLBox *Dentry; GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = unpack_track(trak); @@ -1277,10 +1331,10 @@ GF_Err e; Bool is_patch = GF_FALSE; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (mode==0) { @@ -1386,10 +1440,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = unpack_track(trak); @@ -1404,14 +1458,16 @@ e = Media_ParseODFrame(trak->Media, sample, &od_sample); if (!e) e = Media_UpdateSample(trak->Media, sampleNumber, od_sample, data_only); if (od_sample) gf_isom_sample_del(&od_sample); + gf_isom_disable_inplace_rewrite(movie); } else { e = Media_UpdateSample(trak->Media, sampleNumber, sample, data_only); + if (data_only || sample->data) + gf_isom_disable_inplace_rewrite(movie); } if (e) return e; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); - gf_isom_disable_inplace_rewrite(movie); return GF_OK; } @@ -1423,10 +1479,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; //block for hint tracks @@ -1487,10 +1543,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !sampleNumber || (sampleNumber > trak->Media->information->sampleTable->SampleSize->sampleCount) ) return GF_BAD_PARAM; @@ -1554,7 +1610,7 @@ if (!movie ) return GF_BAD_PARAM; //if mode is not OPEN_EDIT file was created under the right name - e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; if (filename) { @@ -1584,10 +1640,10 @@ GF_TrackReferenceTypeBox *dpnd; GF_MPEGVisualSampleEntryBox *entry; u32 msubtype; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; /*GETS NATIVE DESCRIPTOR ONLY*/ @@ -1681,10 +1737,10 @@ GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -1716,10 +1772,10 @@ GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -1758,10 +1814,10 @@ GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -1794,10 +1850,10 @@ GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -1838,10 +1894,10 @@ GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; GF_ColourInformationBox *clr=NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -1902,10 +1958,10 @@ Bool is_avc = GF_FALSE; GF_SampleDescriptionBox* stsd; GF_DOVIConfigurationBox* dovi = NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2028,10 +2084,10 @@ GF_SampleEntryBox* entry; GF_SampleDescriptionBox* stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2079,10 +2135,10 @@ GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2131,10 +2187,10 @@ u32 j, hspacing, vspacing, clap_width_num, clap_width_den, clap_height_num, clap_height_den, high, low; Double width, height; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (remove) { @@ -2240,10 +2296,10 @@ GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2281,10 +2337,10 @@ GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2332,10 +2388,10 @@ GF_ChromaInfoBox *enda=NULL; GF_ESDBox *esds=NULL; GF_Box *terminator=NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2364,19 +2420,32 @@ aud_entry->bitspersample = bitsPerSample; switch (asemode) { - case GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2: + case GF_IMPORT_AUDIO_SAMPLE_ENTRY_NOT_SET: + case GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_DEFAULT: stsd->version = 0; aud_entry->version = 0; aud_entry->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; - aud_entry->channel_count = 2; + aud_entry->channel_count = nbChannels; // Get from bitstream + + // According to ETSI TS 102 366 V1.4.1 (2017-09), the channel_count of AC3, EC3 (based on AudioSampleEntry) + // should always be 2, and the sample size should always be 16 + if (aud_entry->type == GF_ISOM_BOX_TYPE_AC3 || aud_entry->type == GF_ISOM_BOX_TYPE_EC3) { + aud_entry->channel_count = 2; + aud_entry->bitspersample = 16; + } break; - case GF_IMPORT_AUDIO_SAMPLE_ENTRY_NOT_SET: case GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS: stsd->version = 0; aud_entry->version = 0; aud_entry->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; aud_entry->channel_count = nbChannels; break; + case GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2: + stsd->version = 0; + aud_entry->version = 0; + aud_entry->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + aud_entry->channel_count = 2; + break; case GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG: stsd->version = 1; aud_entry->version = 1; @@ -2398,10 +2467,12 @@ } aud_entry->compression_id = 0; - + GF_SamplingRateBox *srat = NULL; //check for wave+children and chan for QTFF or remove them for isobmff for (i=0; i<gf_list_count(aud_entry->child_boxes); i++) { GF_Box *b = gf_list_get(aud_entry->child_boxes, i); + if (b->type == GF_ISOM_BOX_TYPE_SRAT) srat = (GF_SamplingRateBox *)b; + if ((b->type != GF_QT_BOX_TYPE_WAVE) && (b->type != GF_QT_BOX_TYPE_CHAN) ) continue; if (asemode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF) { if (b->type == GF_QT_BOX_TYPE_WAVE) wave_box = b; @@ -2415,6 +2486,13 @@ } } + if (sampleRate>0xFFFF) { + if (!srat) { + srat = (GF_SamplingRateBox *) gf_isom_box_new_parent(&aud_entry->child_boxes, GF_ISOM_BOX_TYPE_SRAT); + } + if (srat) srat->sampling_rate = sampleRate; + } + //TODO: insert channelLayout for ISOBMFF if (asemode!=GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF) return GF_OK; @@ -2509,14 +2587,14 @@ GF_AudioSampleEntryBox*aud_entry; GF_SampleDescriptionBox *stsd; GF_ChannelLayoutBox *chnl; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!layout) return GF_BAD_PARAM; if ((layout->stream_structure & 1) && (layout->definedLayout==0) && (layout->channels_count>=64)) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -2558,7 +2636,7 @@ GF_Err gf_isom_set_storage_mode(GF_ISOFile *movie, GF_ISOStorageMode storageMode) { GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; switch (storageMode) { @@ -2609,10 +2687,10 @@ GF_Err e; u64 startTime; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; edts = trak->editBox; @@ -2628,6 +2706,10 @@ edts_on_child_box((GF_Box*)edts, (GF_Box *)elst, GF_FALSE); } + if (trak->extl) { + if (!trak->extl->media_timescale) trak->extl->media_timescale = movie->moov->mvhd->timeScale; + trak->extl->flags |= GF_ISOM_EXTK_EDTS_SKIP; + } startTime = 0; ent = NULL; //get the prev entry to this startTime if any @@ -2716,12 +2798,13 @@ { GF_Err e; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; + if (trak->extl) trak->extl->flags &= ~GF_ISOM_EXTK_EDTS_SKIP; if (!trak->editBox || !trak->editBox->editList) return GF_OK; while (gf_list_count(trak->editBox->editList->entryList)) { @@ -2744,10 +2827,10 @@ GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent, *next_ent; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !seg_index) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox || !trak->editBox->editList) return GF_OK; @@ -2768,9 +2851,9 @@ GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox) { @@ -2813,9 +2896,9 @@ GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !seg_index) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox || !trak->editBox->editList) return GF_OK; @@ -2865,12 +2948,20 @@ GF_ISOTrackID *newRefs; u8 found; GF_ISOSample *samp; - the_trak = gf_isom_get_track_from_file(movie, trackNumber); + the_trak = gf_isom_get_track_box(movie, trackNumber); if (!the_trak) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; + if (the_trak && the_trak->Media && the_trak->Media->information) { + if (the_trak->Media->information->dataHandler == movie->movieFileMap || the_trak->Media->information->dataHandler == movie->editFileMap) + the_trak->Media->information->dataHandler = NULL; + + if (the_trak->Media->information->scalableDataHandler == movie->movieFileMap || the_trak->Media->information->scalableDataHandler == movie->editFileMap) + the_trak->Media->information->scalableDataHandler = NULL; + } + if (movie->moov->iods && movie->moov->iods->descriptor) { GF_Descriptor *desc; GF_ES_ID_Inc *inc; @@ -2992,7 +3083,7 @@ GF_UserDataMap *map; u32 count, i; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!notice || !threeCharCode) return GF_BAD_PARAM; @@ -3028,7 +3119,7 @@ ptr->notice = (char*)gf_malloc(sizeof(char) * (strlen(notice)+1)); if (!ptr->notice) return GF_OUT_OF_MEM; strcpy(ptr->notice, notice); - return udta_on_child_box((GF_Box *)movie->moov->udta, (GF_Box *) ptr, GF_FALSE); + return udta_on_child_box_ex((GF_Box *)movie->moov->udta, (GF_Box *) ptr, GF_FALSE, GF_FALSE); } GF_EXPORT @@ -3040,14 +3131,14 @@ GF_UserDataMap *map; u32 i, count; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE); @@ -3058,6 +3149,9 @@ return GF_BAD_PARAM; } + //we may get null on schemeURI if not set in the source + if (!schemeURI) schemeURI = ""; + map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_KIND, NULL); if (map) { count = gf_list_count(map->boxes); @@ -3070,6 +3164,11 @@ // Already there return GF_OK; } + if (!strcmp(kb->schemeURI, schemeURI)) { + if (kb->value) gf_free(kb->value); + kb->value = value ? gf_strdup(value) : NULL; + return GF_OK; + } } } } @@ -3079,7 +3178,7 @@ ptr->schemeURI = gf_strdup(schemeURI); if (value) ptr->value = gf_strdup(value); - return udta_on_child_box((GF_Box *)udta, (GF_Box *) ptr, GF_FALSE); + return udta_on_child_box_ex((GF_Box *)udta, (GF_Box *) ptr, GF_FALSE, GF_FALSE); } GF_EXPORT @@ -3090,13 +3189,13 @@ GF_UserDataMap *map; u32 i; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE); @@ -3134,14 +3233,14 @@ GF_UserDataBox *udta; GF_UserDataMap *map; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE); @@ -3160,7 +3259,7 @@ map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_CHPL, NULL); if (!map) { ptr = (GF_ChapterListBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CHPL); - e = udta_on_child_box((GF_Box *)udta, (GF_Box *) ptr, GF_FALSE); + e = udta_on_child_box_ex((GF_Box *)udta, (GF_Box *) ptr, GF_FALSE, GF_FALSE); if (e) return e; map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_CHPL, NULL); } else { @@ -3206,13 +3305,13 @@ GF_UserDataBox *udta; GF_UserDataMap *map; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) return GF_OK; udta = trak->udta; @@ -3256,7 +3355,7 @@ GF_UserDataMap *map; u32 count; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); if (e) return e; @@ -3291,7 +3390,7 @@ GF_UnknownUUIDBox *ptr; GF_UserDataMap *map; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(movie); @@ -3322,7 +3421,7 @@ if (!ptr->data) return GF_OUT_OF_MEM; memcpy(ptr->data, data, length); ptr->dataSize = length; - return udta_on_child_box((GF_Box *)movie->moov->udta, (GF_Box *) ptr); + return udta_on_child_box_ex((GF_Box *)movie->moov->udta, (GF_Box *) ptr, GF_FALSE, GF_FALSE); } #endif @@ -3332,7 +3431,7 @@ GF_Err gf_isom_set_interleave_time(GF_ISOFile *movie, u32 InterleaveTime) { GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!InterleaveTime || !movie->moov) return GF_OK; @@ -3353,10 +3452,10 @@ GF_SampleSizeBox *stsz; GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media || !trak->Media->information @@ -3428,7 +3527,7 @@ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (! (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { - GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = CheckNoData(movie); @@ -3479,7 +3578,7 @@ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (! (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { - GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = CheckNoData(movie); @@ -3543,7 +3642,7 @@ return GF_OK; } - +GF_EXPORT GF_Err gf_isom_reset_alt_brands_ex(GF_ISOFile *movie, Bool leave_empty) { u32 *p; @@ -3553,7 +3652,7 @@ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (! (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { - GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = CheckNoData(movie); @@ -3591,10 +3690,10 @@ GF_TrackBox *trak; GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || NbBits > 7) return GF_BAD_PARAM; //set Padding info @@ -3613,14 +3712,14 @@ GF_TrackBox *trak; GF_UserDataBox *udta; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0; memset(t, 1, 16); if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; udta = trak->udta; } else { @@ -3665,14 +3764,14 @@ GF_TrackBox *trak; GF_UserDataBox *udta; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0; memset(t, 1, 16); if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_EOS; udta = trak->udta; } else { @@ -3707,13 +3806,13 @@ GF_TrackBox *trak; GF_UserDataBox *udta; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0; if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE); udta = trak->udta; @@ -3735,7 +3834,7 @@ memcpy(a->data, data, DataLength); a->dataSize = DataLength; } - return udta_on_child_box((GF_Box *)udta, (GF_Box *) a, GF_FALSE); + return udta_on_child_box_ex((GF_Box *)udta, (GF_Box *) a, GF_FALSE, GF_TRUE); } else if (UUID) { GF_UnknownUUIDBox *a = (GF_UnknownUUIDBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_UUID); if (!a) return GF_OUT_OF_MEM; @@ -3746,7 +3845,7 @@ memcpy(a->data, data, DataLength); a->dataSize = DataLength; } - return udta_on_child_box((GF_Box *)udta, (GF_Box *) a, GF_FALSE); + return udta_on_child_box_ex((GF_Box *)udta, (GF_Box *) a, GF_FALSE, GF_TRUE); } else { return GF_BAD_PARAM; } @@ -3761,11 +3860,11 @@ GF_UserDataBox *udta; GF_BitStream *bs; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (trackNumber) { - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE); udta = trak->udta; @@ -3781,7 +3880,7 @@ GF_Box *a; e = gf_isom_box_parse(&a, bs); if (e) break; - e = udta_on_child_box((GF_Box *)udta, a, GF_FALSE); + e = udta_on_child_box_ex((GF_Box *)udta, a, GF_FALSE, GF_FALSE); if (e) break; } gf_bs_del(bs); @@ -3857,7 +3956,7 @@ u32 i; GF_Box *box; - e = CanAccessMovie(dest_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(dest_file, GF_ISOM_OPEN_WRITE); if (e) return e; if (orig_file->brand) { @@ -4012,6 +4111,28 @@ return e; } +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +static GF_Err gf_isom_get_trex_props(GF_ISOFile *file, GF_TrackBox *trak, GF_TrackExtendsBox **trex, GF_TrackExtensionPropertiesBox **trexprop) +{ + u32 i; + if (!file->moov->mvex) return GF_NOT_FOUND; + *trex = NULL; + for (i=0; i<gf_list_count(file->moov->mvex->TrackExList); i++) { + *trex = gf_list_get(file->moov->mvex->TrackExList, i); + if ((*trex)->trackID == trak->Header->trackID) break; + *trex = NULL; + } + if (! *trex) return GF_NOT_FOUND; + + for (i=0; i<gf_list_count(file->moov->mvex->TrackExPropList); i++) { + *trexprop = gf_list_get(file->moov->mvex->TrackExPropList, i); + if ((*trexprop)->trackID== trak->Header->trackID) break; + *trexprop = NULL; + } + return GF_OK; +} +#endif + GF_EXPORT GF_Err gf_isom_get_track_template(GF_ISOFile *file, u32 track, u8 **output, u32 *output_size) { @@ -4026,7 +4147,7 @@ *output = NULL; *output_size = 0; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak || !trak->Media) return GF_BAD_PARAM; //don't serialize dref @@ -4066,6 +4187,23 @@ gf_list_add(stbl_temp->child_boxes, stbl->CompositionToDecode); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + //get CSLG from trex if not in stbl + GF_CompositionToDecodeBox *trex_cslg=NULL; + if (!stbl_temp->CompositionToDecode) { + GF_TrackExtendsBox *trex=NULL; + GF_TrackExtensionPropertiesBox *trexprop=NULL; + gf_isom_get_trex_props(file, trak, &trex, &trexprop); + if (trexprop) { + trex_cslg = (GF_CompositionToDecodeBox *) gf_isom_box_find_child(trexprop->child_boxes, GF_ISOM_BOX_TYPE_CSLG); + if (trex_cslg) { + stbl_temp->CompositionToDecode = trex_cslg; + gf_list_add(stbl_temp->child_boxes, trex_cslg); + } + } + } +#endif + count = gf_list_count(trak->child_boxes); for (i=0;i<count; i++) { GF_UnknownBox *b = gf_list_get(trak->child_boxes, i); @@ -4106,6 +4244,12 @@ trak->sample_encryption = senc; gf_list_add(trak->child_boxes, senc); } +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + if (trex_cslg) { + stbl_temp->CompositionToDecode = NULL; + gf_list_del_item(stbl_temp->child_boxes, trex_cslg); + } +#endif stbl_temp->sampleGroupsDescription = NULL; count = gf_list_count(stbl->sampleGroupsDescription); @@ -4132,28 +4276,18 @@ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS GF_TrackBox *trak; GF_BitStream *bs; - u32 i; GF_TrackExtendsBox *trex = NULL; GF_TrackExtensionPropertiesBox *trexprop = NULL; *output = NULL; *output_size = 0; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak || !trak->Media) return GF_BAD_PARAM; if (!file->moov->mvex) return GF_NOT_FOUND; - for (i=0; i<gf_list_count(file->moov->mvex->TrackExList); i++) { - trex = gf_list_get(file->moov->mvex->TrackExList, i); - if (trex->trackID == trak->Header->trackID) break; - trex = NULL; - } - if (!trex) return GF_NOT_FOUND; + GF_Err e = gf_isom_get_trex_props(file, trak, &trex, &trexprop); + if (e) return e; - for (i=0; i<gf_list_count(file->moov->mvex->TrackExPropList); i++) { - trexprop = gf_list_get(file->moov->mvex->TrackExPropList, i); - if (trexprop->trackID== trak->Header->trackID) break; - trexprop = NULL; - } bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size( (GF_Box *) trex); gf_isom_box_write((GF_Box *) trex, bs); @@ -4182,7 +4316,7 @@ *output = NULL; *output_size = 0; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleDescription) return GF_BAD_PARAM; if (stsd_idx) { @@ -4199,7 +4333,6 @@ gf_bs_get_content(bs, output, output_size); gf_bs_del(bs); return GF_OK; - } @@ -4213,49 +4346,52 @@ u32 data_size; u32 i, count; GF_Err e; - GF_SampleTableBox *stbl, *stbl_temp; - GF_SampleEncryptionBox *senc; + GF_SampleTableBox *stbl=NULL, *stbl_temp=NULL; + GF_SampleEncryptionBox *senc=NULL; - e = CanAccessMovie(dest_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(dest_file, GF_ISOM_OPEN_WRITE); if (e) return e; e = gf_isom_insert_moov(dest_file); if (e) return e; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(orig_file, orig_track); - if (!trak || !trak->Media) return GF_BAD_PARAM; - - stbl = trak->Media->information->sampleTable; - stbl_temp = (GF_SampleTableBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_STBL); - if (!stbl_temp->child_boxes) stbl_temp->child_boxes = gf_list_new(); - - trak->Media->information->sampleTable = stbl_temp; - gf_list_add(trak->Media->information->child_boxes, stbl_temp); - gf_list_del_item(trak->Media->information->child_boxes, stbl); - - if (!stbl_temp->child_boxes) stbl_temp->child_boxes = gf_list_new(); + trak = gf_isom_get_track_box(orig_file, orig_track); + if (!trak) return GF_BAD_PARAM; + if (!trak->Media && !trak->extl) return GF_BAD_PARAM; - /*clone sampleDescription table*/ - stbl_temp->SampleDescription = stbl->SampleDescription; - gf_list_add(stbl_temp->child_boxes, stbl->SampleDescription); - /*also clone sampleGroups description tables if any*/ - stbl_temp->sampleGroupsDescription = stbl->sampleGroupsDescription; - count = gf_list_count(stbl->sampleGroupsDescription); - for (i=0; i<count; i++) { - GF_Box *b = gf_list_get(stbl->sampleGroupsDescription, i); - gf_list_add(stbl_temp->child_boxes, b); - } - /*clone CompositionToDecode table, we may remove it later*/ - stbl_temp->CompositionToDecode = stbl->CompositionToDecode; - gf_list_add(stbl_temp->child_boxes, stbl->CompositionToDecode); + //for non-external tracks only + if (trak->Media) { + stbl = trak->Media->information->sampleTable; + stbl_temp = (GF_SampleTableBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_STBL); + if (!stbl_temp->child_boxes) stbl_temp->child_boxes = gf_list_new(); - senc = trak->sample_encryption; - if (senc) { - gf_assert(trak->child_boxes); - gf_list_del_item(trak->child_boxes, senc); - trak->sample_encryption = NULL; + trak->Media->information->sampleTable = stbl_temp; + gf_list_add(trak->Media->information->child_boxes, stbl_temp); + gf_list_del_item(trak->Media->information->child_boxes, stbl); + + if (!stbl_temp->child_boxes) stbl_temp->child_boxes = gf_list_new(); + + /*clone sampleDescription table*/ + stbl_temp->SampleDescription = stbl->SampleDescription; + gf_list_add(stbl_temp->child_boxes, stbl->SampleDescription); + /*also clone sampleGroups description tables if any*/ + stbl_temp->sampleGroupsDescription = stbl->sampleGroupsDescription; + count = gf_list_count(stbl->sampleGroupsDescription); + for (i=0; i<count; i++) { + GF_Box *b = gf_list_get(stbl->sampleGroupsDescription, i); + gf_list_add(stbl_temp->child_boxes, b); + } + /*clone CompositionToDecode table, we may remove it later*/ + stbl_temp->CompositionToDecode = stbl->CompositionToDecode; + gf_list_add(stbl_temp->child_boxes, stbl->CompositionToDecode); + + senc = trak->sample_encryption; + if (senc) { + gf_assert(trak->child_boxes); + gf_list_del_item(trak->child_boxes, senc); + trak->sample_encryption = NULL; + } } - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size( (GF_Box *) trak); @@ -4272,27 +4408,29 @@ gf_bs_del(bs); gf_free(data); - trak->Media->information->sampleTable = stbl; - gf_list_del_item(trak->Media->information->child_boxes, stbl_temp); - gf_list_add(trak->Media->information->child_boxes, stbl); + if (trak->Media) { + trak->Media->information->sampleTable = stbl; + gf_list_del_item(trak->Media->information->child_boxes, stbl_temp); + gf_list_add(trak->Media->information->child_boxes, stbl); + + if (senc) { + trak->sample_encryption = senc; + gf_list_add(trak->child_boxes, senc); + } + gf_list_del_item(stbl_temp->child_boxes, stbl_temp->SampleDescription); + stbl_temp->SampleDescription = NULL; - if (senc) { - trak->sample_encryption = senc; - gf_list_add(trak->child_boxes, senc); - } - gf_list_del_item(stbl_temp->child_boxes, stbl_temp->SampleDescription); - stbl_temp->SampleDescription = NULL; + count = gf_list_count(stbl->sampleGroupsDescription); + for (i=0; i<count; i++) { + GF_Box *b = gf_list_get(stbl->sampleGroupsDescription, i); + gf_list_del_item(stbl_temp->child_boxes, b); + } + stbl_temp->sampleGroupsDescription = NULL; - count = gf_list_count(stbl->sampleGroupsDescription); - for (i=0; i<count; i++) { - GF_Box *b = gf_list_get(stbl->sampleGroupsDescription, i); - gf_list_del_item(stbl_temp->child_boxes, b); + gf_list_del_item(stbl_temp->child_boxes, stbl_temp->CompositionToDecode); + stbl_temp->CompositionToDecode = NULL; + gf_isom_box_del((GF_Box *)stbl_temp); } - stbl_temp->sampleGroupsDescription = NULL; - - gf_list_del_item(stbl_temp->child_boxes, stbl_temp->CompositionToDecode); - stbl_temp->CompositionToDecode = NULL; - gf_isom_box_del((GF_Box *)stbl_temp); if (e) { if (new_tk) gf_isom_box_del((GF_Box *)new_tk); @@ -4301,16 +4439,18 @@ gf_isom_disable_inplace_rewrite(dest_file); - /*create default boxes*/ - stbl = new_tk->Media->information->sampleTable; - stbl->ChunkOffset = gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STCO); - if (!stbl->ChunkOffset) return GF_OUT_OF_MEM; - stbl->SampleSize = (GF_SampleSizeBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSZ); - if (!stbl->SampleSize) return GF_OUT_OF_MEM; - stbl->SampleToChunk = (GF_SampleToChunkBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSC); - if (!stbl->SampleToChunk) return GF_OUT_OF_MEM; - stbl->TimeToSample = (GF_TimeToSampleBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STTS); - if (!stbl->TimeToSample) return GF_OUT_OF_MEM; + if (trak->Media) { + /*create default boxes*/ + stbl = new_tk->Media->information->sampleTable; + stbl->ChunkOffset = gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STCO); + if (!stbl->ChunkOffset) return GF_OUT_OF_MEM; + stbl->SampleSize = (GF_SampleSizeBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSZ); + if (!stbl->SampleSize) return GF_OUT_OF_MEM; + stbl->SampleToChunk = (GF_SampleToChunkBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSC); + if (!stbl->SampleToChunk) return GF_OUT_OF_MEM; + stbl->TimeToSample = (GF_TimeToSampleBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STTS); + if (!stbl->TimeToSample) return GF_OUT_OF_MEM; + } if (flags & GF_ISOM_CLONE_TRACK_DROP_ID) new_tk->Header->trackID = 0; @@ -4349,36 +4489,38 @@ } } - if (flags & GF_ISOM_CLONE_RESET_DURATION) - new_tk->Media->mediaHeader->duration = 0; - - if (!new_tk->Media->information->dataInformation->dref) return GF_BAD_PARAM; - - /*reset data ref*/ - if (! (flags & GF_ISOM_CLONE_TRACK_KEEP_DREF) ) { - GF_SampleEntryBox *entry; - Bool use_alis = GF_FALSE; - if (! (flags & GF_ISOM_CLONE_TRACK_NO_QT)) { - GF_Box *b = gf_list_get(new_tk->Media->information->dataInformation->dref->child_boxes, 0); - if (b && (b->type==GF_QT_BOX_TYPE_ALIS)) - use_alis = GF_TRUE; - } - gf_isom_box_array_del(new_tk->Media->information->dataInformation->dref->child_boxes); - new_tk->Media->information->dataInformation->dref->child_boxes = gf_list_new(); - /*update data ref*/ - entry = (GF_SampleEntryBox*)gf_list_get(new_tk->Media->information->sampleTable->SampleDescription->child_boxes, 0); - if (entry) { - u32 dref; - Media_CreateDataRef(dest_file, new_tk->Media->information->dataInformation->dref, use_alis ? "alis" : NULL, NULL, &dref); - entry->dataReferenceIndex = dref; - } - } else { - for (i=0; i<gf_list_count(new_tk->Media->information->dataInformation->dref->child_boxes); i++) { - GF_DataEntryBox *dref_entry = (GF_DataEntryBox *)gf_list_get(new_tk->Media->information->dataInformation->dref->child_boxes, i); - if (dref_entry->flags & 1) { - dref_entry->flags &= ~1; - e = Media_SetDrefURL((GF_DataEntryURLBox *)dref_entry, orig_file->fileName, dest_file->finalName); - if (e) return e; + if (trak->Media) { + if (flags & GF_ISOM_CLONE_RESET_DURATION) + new_tk->Media->mediaHeader->duration = 0; + + if (!new_tk->Media->information->dataInformation->dref) return GF_BAD_PARAM; + + /*reset data ref*/ + if (! (flags & GF_ISOM_CLONE_TRACK_KEEP_DREF) ) { + GF_SampleEntryBox *entry; + Bool use_alis = GF_FALSE; + if (! (flags & GF_ISOM_CLONE_TRACK_NO_QT)) { + GF_Box *b = gf_list_get(new_tk->Media->information->dataInformation->dref->child_boxes, 0); + if (b && (b->type==GF_QT_BOX_TYPE_ALIS)) + use_alis = GF_TRUE; + } + gf_isom_box_array_del(new_tk->Media->information->dataInformation->dref->child_boxes); + new_tk->Media->information->dataInformation->dref->child_boxes = gf_list_new(); + /*update data ref*/ + entry = (GF_SampleEntryBox*)gf_list_get(new_tk->Media->information->sampleTable->SampleDescription->child_boxes, 0); + if (entry) { + u32 dref; + Media_CreateDataRef(dest_file, new_tk->Media->information->dataInformation->dref, use_alis ? "alis" : NULL, NULL, &dref); + entry->dataReferenceIndex = dref; + } + } else { + for (i=0; i<gf_list_count(new_tk->Media->information->dataInformation->dref->child_boxes); i++) { + GF_DataEntryBox *dref_entry = (GF_DataEntryBox *)gf_list_get(new_tk->Media->information->dataInformation->dref->child_boxes, i); + if (dref_entry->flags & 1) { + dref_entry->flags &= ~1; + e = Media_SetDrefURL((GF_DataEntryURLBox *)dref_entry, orig_file->fileName, dest_file->finalName); + if (e) return e; + } } } } @@ -4399,6 +4541,11 @@ if (dest_file->moov->mvhd->nextTrackID <= new_tk->Header->trackID) dest_file->moov->mvhd->nextTrackID = new_tk->Header->trackID+1; + /*if it contains IAMF, add the iamf brand to ftyp*/ + if (gf_isom_get_media_subtype(dest_file, new_tk->Header->trackID, 1) == GF_ISOM_SUBTYPE_IAMF) { + gf_isom_modify_alternate_brand(dest_file, GF_ISOM_BRAND_IAMF, GF_TRUE); + } + return GF_OK; } @@ -4408,12 +4555,12 @@ { u32 i; GF_TrackBox *dst_trak, *src_trak; - GF_Err e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - dst_trak = gf_isom_get_track_from_file(the_file, trackNumber); + dst_trak = gf_isom_get_track_box(the_file, trackNumber); if (!dst_trak || !dst_trak->Media) return GF_BAD_PARAM; - src_trak = gf_isom_get_track_from_file(orig_file, orig_track); + src_trak = gf_isom_get_track_box(orig_file, orig_track); if (!src_trak || !src_trak->Media) return GF_BAD_PARAM; if (reset_existing) { @@ -4444,17 +4591,31 @@ u32 mtype; u32 internal_type; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(orig_file, orig_track); + trak = gf_isom_get_track_box(orig_file, orig_track); if (!trak || !trak->Media) return GF_BAD_PARAM; entry = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, orig_desc_index-1); if (!entry) return GF_BAD_PARAM; internal_type = ((GF_SampleEntryBox *)entry)->internal_type; + + // for generic entries, force the entrytype to unknown to avoid allocating potentially disallowed box types + u32 generic_entry_type = 0; + if (entry->type == GF_ISOM_BOX_TYPE_GNRV) { + generic_entry_type = ((GF_GenericVisualSampleEntryBox *) entry)->EntryType; + ((GF_GenericVisualSampleEntryBox *) entry)->EntryType = GF_ISOM_BOX_TYPE_UNKNOWN; + } else if (entry->type == GF_ISOM_BOX_TYPE_GNRA) { + generic_entry_type = ((GF_GenericAudioSampleEntryBox *) entry)->EntryType; + ((GF_GenericAudioSampleEntryBox *) entry)->EntryType = GF_ISOM_BOX_TYPE_UNKNOWN; + } else if (entry->type == GF_ISOM_BOX_TYPE_GNRM) { + generic_entry_type = ((GF_GenericSampleEntryBox *) entry)->EntryType; + ((GF_GenericSampleEntryBox *) entry)->EntryType = GF_ISOM_BOX_TYPE_UNKNOWN; + } + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size(entry); @@ -4462,29 +4623,41 @@ gf_bs_get_content(bs, &data, &data_size); gf_bs_del(bs); bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); + + // restore the original entrytype before losing the entry pointer + if (entry->type == GF_ISOM_BOX_TYPE_GNRV) { + ((GF_GenericVisualSampleEntryBox *) entry)->EntryType = generic_entry_type; + } else if (entry->type == GF_ISOM_BOX_TYPE_GNRA) { + ((GF_GenericAudioSampleEntryBox *) entry)->EntryType = generic_entry_type; + } else if (entry->type == GF_ISOM_BOX_TYPE_GNRM) { + ((GF_GenericSampleEntryBox *) entry)->EntryType = generic_entry_type; + } + + e = gf_isom_box_parse(&entry, bs); gf_bs_del(bs); gf_free(data); if (e) return e; + if (entry->type==GF_ISOM_BOX_TYPE_UNKNOWN) { GF_UnknownBox *ubox = (GF_UnknownBox*)entry; if (internal_type == GF_ISOM_SAMPLE_ENTRY_VIDEO) { GF_GenericVisualSampleEntryBox *ve = (GF_GenericVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRV); - ve->EntryType = ubox->type; + ve->EntryType = generic_entry_type ? generic_entry_type : ubox->original_4cc; ve->data = ubox->data; ve->data_size = ubox->dataSize; entry = (GF_Box *) ve; } else if (internal_type == GF_ISOM_SAMPLE_ENTRY_AUDIO) { GF_GenericAudioSampleEntryBox *ae = (GF_GenericAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRA); - ae->EntryType = ubox->type; + ae->EntryType = generic_entry_type ? generic_entry_type : ubox->original_4cc; ae->data = ubox->data; ae->data_size = ubox->dataSize; entry = (GF_Box *) ae; } else { GF_GenericSampleEntryBox *ge = (GF_GenericSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRM); - ge->EntryType = ubox->type; + ge->EntryType = generic_entry_type ? generic_entry_type : ubox->original_4cc; ge->data = ubox->data; ge->data_size = ubox->dataSize; entry = (GF_Box *) ge; @@ -4495,7 +4668,7 @@ } /*get new track and insert clone*/ - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) goto exit; /*get or create the data ref*/ @@ -4533,10 +4706,10 @@ u32 *wrap_size; u32 dataRefIndex; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !udesc) return GF_BAD_PARAM; //get or create the data ref @@ -4690,10 +4863,10 @@ GF_Err e; GF_GenericVisualSampleEntryBox *entry; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !StreamDescriptionIndex) return GF_BAD_PARAM; entry = (GF_GenericVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -4777,9 +4950,9 @@ GF_Err e; GF_Box *entry; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !streamDescIndex) return GF_BAD_PARAM; entry = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, streamDescIndex-1); if (!entry) return GF_BAD_PARAM; @@ -4798,7 +4971,7 @@ GF_TrackReferenceBox *tref; GF_TrackReferenceTypeBox *dpnd; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; //no tref, create one @@ -4828,7 +5001,7 @@ GF_TrackBox *trak; GF_TrackReferenceTypeBox *ref; u32 i=0; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; //no tref, nothing to remove @@ -4864,7 +5037,7 @@ { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->References) { @@ -4880,7 +5053,7 @@ GF_TrackBox *trak; u32 i=0; GF_TrackReferenceTypeBox *ref; - trak = gf_isom_get_track_from_file(isom_file, trackNumber); + trak = gf_isom_get_track_box(isom_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->References) return GF_OK; @@ -4907,7 +5080,7 @@ u32 i, j, k; if (!movie) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (trak && (trak->Header->trackID==trackID)) return GF_OK; a_trak = gf_isom_get_track_from_id(movie->moov, trackID); if (!trak || a_trak) return GF_BAD_PARAM; @@ -4950,7 +5123,7 @@ GF_TrackBox *trak, *a_trak; u32 i, k; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->References) @@ -4985,7 +5158,7 @@ GF_EXPORT GF_Err gf_isom_change_sample_desc_index(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, u32 newSampleDescIndex) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !sample_number || !newSampleDescIndex) return GF_BAD_PARAM; if (!trak->is_unpacked) { unpack_track(trak); @@ -5000,7 +5173,7 @@ GF_EXPORT GF_Err gf_isom_modify_cts_offset(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, u32 offset) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset->unpack_mode) return GF_BAD_PARAM; @@ -5014,7 +5187,7 @@ GF_Err gf_isom_shift_cts_offset(GF_ISOFile *the_file, u32 trackNumber, s32 offset_shift) { u32 i; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset->unpack_mode) return GF_BAD_PARAM; @@ -5039,7 +5212,7 @@ GF_Err gf_isom_remove_cts_info(GF_ISOFile *the_file, u32 trackNumber) { GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -5059,7 +5232,7 @@ GF_Err stbl_unpackCTS(GF_SampleTableBox *stbl); GF_SampleTableBox *stbl; - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -5080,7 +5253,7 @@ GF_EXPORT GF_Err gf_isom_set_track_matrix(GF_ISOFile *the_file, u32 trackNumber, s32 matrix9) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Header) return GF_BAD_PARAM; memcpy(trak->Header->matrix, matrix, sizeof(trak->Header->matrix)); return GF_OK; @@ -5089,7 +5262,7 @@ GF_EXPORT GF_Err gf_isom_set_track_layout_info(GF_ISOFile *the_file, u32 trackNumber, u32 width, u32 height, s32 translation_x, s32 translation_y, s16 layer) { - GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Header) return GF_BAD_PARAM; trak->Header->width = width; trak->Header->height = height; @@ -5108,10 +5281,10 @@ GF_TrackBox *trak; GF_SampleTableBox *stbl; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !trak->Media->mediaHeader) return GF_BAD_PARAM; if ((trak->Media->mediaHeader->timeScale==newTS) && !new_tsinc) - return GF_EOS; + return GF_OK; //nothing to do if (!newTS) newTS = trak->Media->mediaHeader->timeScale; scale = newTS; @@ -5317,6 +5490,7 @@ if (a == b) return GF_TRUE; if (!a || !b) return GF_FALSE; + //do NOT check size, they could be set/not set at this point data1 = data2 = NULL; @@ -5361,9 +5535,9 @@ GF_Box *a, *b; /*get orig sample desc and clone it*/ - trak1 = gf_isom_get_track_from_file(f1, tk1); + trak1 = gf_isom_get_track_box(f1, tk1); if (!trak1 || !trak1->Media) return GF_FALSE; - trak2 = gf_isom_get_track_from_file(f2, tk2); + trak2 = gf_isom_get_track_box(f2, tk2); if (!trak2 || !trak2->Media) return GF_FALSE; if (trak1->Media->handler->handlerType != trak2->Media->handler->handlerType) return GF_FALSE; @@ -5398,7 +5572,7 @@ case GF_ISOM_BOX_TYPE_ENCS: Media_GetESD(trak1->Media, sdesc_index1 ? sdesc_index1 : i+1, &esd1, GF_TRUE); Media_GetESD(trak2->Media, sdesc_index2 ? sdesc_index2 : i+1, &esd2, GF_TRUE); - if (!esd1 || !esd2) continue; + if (!esd1 || !esd2 || !esd1->decoderConfig || !esd2->decoderConfig) continue; need_memcmp = GF_FALSE; if (esd1->decoderConfig->streamType != esd2->decoderConfig->streamType) return GF_FALSE; if (esd1->decoderConfig->objectTypeIndication != esd2->decoderConfig->objectTypeIndication) return GF_FALSE; @@ -5445,6 +5619,12 @@ a = (GF_Box *) avc1->mvc_config; else if (avc1->av1_config) a = (GF_Box *)avc1->av1_config; + else if (avc1->vvc_config) + a = (GF_Box *)avc1->vvc_config; + else if (avc1->vp_config) + a = (GF_Box *)avc1->vp_config; + else if (avc1->cfg_3gpp) + a = (GF_Box *)avc1->cfg_3gpp; else a = (GF_Box *) avc1->avc_config; @@ -5458,10 +5638,22 @@ b = (GF_Box *) avc2->mvc_config; else if (avc2->av1_config) b = (GF_Box *)avc2->av1_config; + else if (avc2->vvc_config) + b = (GF_Box *)avc2->vvc_config; + else if (avc2->vp_config) + b = (GF_Box *)avc2->vp_config; + else if (avc2->cfg_3gpp) + b = (GF_Box *)avc2->cfg_3gpp; else b = (GF_Box *) avc2->avc_config; - return gf_isom_box_equal(a,b); + Bool res = gf_isom_box_equal(a,b); + if (!res) return GF_FALSE; + + //check dovi config disabled for now + //res = gf_isom_box_equal((GF_Box*)avc1->dovi_config, (GF_Box*)avc2->dovi_config); + //if (!res) return GF_FALSE; + return GF_TRUE; } break; case GF_ISOM_BOX_TYPE_LSR1: @@ -5624,7 +5816,7 @@ GF_SampleTableBox *stbl; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -5646,7 +5838,7 @@ GF_Err e; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !sampleNumber || !syncSample) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; @@ -5678,7 +5870,7 @@ GF_TrackBox *trak; if (movie->openMode != GF_ISOM_OPEN_EDIT) return GF_ISOM_INVALID_MODE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !GroupID) return GF_BAD_PARAM; trak->Media->information->sampleTable->groupID = GroupID; @@ -5694,7 +5886,7 @@ GF_TrackBox *trak; if (movie->openMode != GF_ISOM_OPEN_EDIT) return GF_ISOM_INVALID_MODE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !Priority) return GF_BAD_PARAM; trak->Media->information->sampleTable->trackPriority = Priority > 255 ? 255 : Priority; @@ -5708,7 +5900,7 @@ GF_TrackBox *trak; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !maxChunkSize) return GF_BAD_PARAM; trak->Media->information->sampleTable->MaxChunkSize = maxChunkSize; @@ -5723,7 +5915,7 @@ GF_TrackBox *trak; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; trak->Media->information->sampleTable->MaxChunkDur = maxChunkDur; @@ -5740,7 +5932,7 @@ GF_SLConfig **slc; GF_ESDBox *esds; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex, &entry, NULL); @@ -5787,7 +5979,7 @@ GF_Err e; GF_SLConfig *slc; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex, &entry, NULL); @@ -5821,7 +6013,7 @@ u32 gf_isom_get_track_group(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->Media->information->sampleTable->groupID; } @@ -5829,7 +6021,7 @@ u32 gf_isom_get_track_priority_in_group(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return 0; return trak->Media->information->sampleTable->trackPriority; } @@ -5868,7 +6060,7 @@ GF_Err gf_isom_set_handler_name(GF_ISOFile *the_file, u32 trackNumber, const char *nameUTF8) { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->Media->handler->nameUTF8) gf_free(trak->Media->handler->nameUTF8); trak->Media->handler->nameUTF8 = NULL; @@ -5995,7 +6187,7 @@ GF_EXPORT GF_Err gf_isom_set_media_type(GF_ISOFile *movie, u32 trackNumber, u32 new_type) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !new_type) return GF_BAD_PARAM; trak->Media->handler->handlerType = new_type; return GF_OK; @@ -6005,7 +6197,7 @@ GF_Err gf_isom_set_media_subtype(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 new_type) { GF_SampleEntryBox*entry; - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !sampleDescriptionIndex || !new_type) return GF_BAD_PARAM; entry = (GF_SampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescriptionIndex - 1); @@ -6041,7 +6233,7 @@ if (!movie) return GF_BAD_PARAM; list = movie->TopBoxes; } else if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; list = trak->child_boxes; } else { @@ -6075,7 +6267,7 @@ if (!movie) return GF_BAD_PARAM; list = movie->TopBoxes; } else if (trackNumber) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->child_boxes) trak->child_boxes = gf_list_new(); list = trak->child_boxes; @@ -6103,20 +6295,23 @@ GF_EXPORT -GF_Err gf_isom_apple_set_tag(GF_ISOFile *mov, GF_ISOiTunesTag tag, const u8 *data, u32 data_len, u64 int_val, u32 int_val2) +GF_Err gf_isom_apple_set_tag_ex(GF_ISOFile *mov, GF_ISOiTunesTag tag, const u8 *data, u32 data_len, u64 int_val, u32 int_val2, const char *in_cust_name, const char *in_cust_mean, u32 locale) { GF_Err e; GF_ItemListBox *ilst; GF_MetaBox *meta; GF_ListItemBox *info; - u32 btype, i, itype; + u32 btype=0, i, itype; s32 tag_idx; u32 n=0, d=0; u8 loc_data10; u32 int_flags = 0x15; GF_DataBox *dbox; + char *cust_mean=NULL, *cust_name=NULL; + + if (in_cust_name || in_cust_mean) tag = GF_4CC('c','u','s','t'); - e = CanAccessMovie(mov, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(mov, GF_ISOM_OPEN_WRITE); if (e) return e; tag_idx = gf_itags_find_by_itag(tag); @@ -6127,6 +6322,8 @@ } meta = (GF_MetaBox *) gf_isom_create_meta_extensions(mov, GF_FALSE); if (!meta) return GF_BAD_PARAM; + if (mov->brand->majorBrand == GF_4CC_CSTR("qt ")) + meta->write_qt = 1; ilst = gf_isom_locate_box(meta->child_boxes, GF_ISOM_BOX_TYPE_ILST, NULL); if (!ilst) { @@ -6154,12 +6351,57 @@ } } btype = data ? GF_ISOM_ITUNE_GENRE_USER : GF_ISOM_ITUNE_GENRE; + } else if (tag==GF_4CC('c','u','s','t') ) { + if (in_cust_name || in_cust_mean) { + if (in_cust_mean && in_cust_mean0) + cust_mean = gf_strdup(in_cust_mean); + if (in_cust_name && in_cust_name0) + cust_name = gf_strdup(in_cust_name); + btype = GF_ISOM_BOX_TYPE_iTunesSpecificInfo; + } else { + char *sep = strchr(data, ','); + if (sep) { + sep0 = 0; + if (data0) + cust_mean = gf_strdup(data); + sep0 = ','; + sep++; + + char *sep2 = strchr(sep+1, ','); + if (sep2) { + sep20 = 0; + if (sep0) + cust_name = gf_strdup(sep); + sep20 = ','; + data_len -= (u32) (sep2-(char*)data) +1; + data = sep2+1; + } else { + data_len -= (u32) (sep-(char*)data)+1; + data = sep+1; + } + btype = GF_ISOM_BOX_TYPE_iTunesSpecificInfo; + } + } } else { btype = tag; } /*remove tag*/ i = 0; while ((info = (GF_ListItemBox*)gf_list_enum(ilst->child_boxes, &i))) { + if (info->type==GF_ISOM_BOX_TYPE_iTunesSpecificInfo) { + + if (info->name && cust_name && !strcmp(info->name->string, cust_name)) {} + else if (!info->name && !cust_name) {} + else continue; + + if (info->mean && cust_mean && !strcmp(info->mean->string, cust_mean)) {} + else if (!info->mean && !cust_mean) {} + else continue; + + gf_isom_box_del_parent(&ilst->child_boxes, (GF_Box *) info); + info = NULL; + break; + } if (info->type==btype) { gf_isom_box_del_parent(&ilst->child_boxes, (GF_Box *) info); info = NULL; @@ -6178,6 +6420,8 @@ if (!data && data_len) { if (!gf_list_count(ilst->child_boxes) ) gf_isom_box_del_parent(&meta->child_boxes, (GF_Box *) ilst); + if (cust_mean) gf_free(cust_mean); + if (cust_name) gf_free(cust_name); return GF_OK; } @@ -6188,15 +6432,32 @@ } else { info = (GF_ListItemBox *)gf_isom_box_new(btype); } - if (info == NULL) return GF_OUT_OF_MEM; + if (info == NULL) { + if (cust_mean) gf_free(cust_mean); + if (cust_name) gf_free(cust_name); + return GF_OUT_OF_MEM; + } dbox = (GF_DataBox *)gf_isom_box_new_parent(&info->child_boxes, GF_ISOM_BOX_TYPE_DATA); if (!dbox) { gf_isom_box_del((GF_Box *)info); + if (cust_mean) gf_free(cust_mean); + if (cust_name) gf_free(cust_name); return GF_OUT_OF_MEM; } + dbox->locale = locale; + if (info->type!=GF_ISOM_BOX_TYPE_UNKNOWN) { info->data = dbox; + + if (cust_name) { + info->name = (GF_NameBox *)gf_isom_box_new_parent(&info->child_boxes, GF_QT_BOX_TYPE_NAME); + info->name->string = cust_name; + } + if (cust_mean) { + info->mean = (GF_NameBox *)gf_isom_box_new_parent(&info->child_boxes, GF_QT_BOX_TYPE_MEAN); + info->mean->string = cust_mean; + } } switch (itype) { @@ -6318,6 +6579,12 @@ return gf_list_add(ilst->child_boxes, info); } +GF_EXPORT +GF_Err gf_isom_apple_set_tag(GF_ISOFile *mov, GF_ISOiTunesTag tag, const u8 *data, u32 data_len, u64 int_val, u32 int_val2) +{ + return gf_isom_apple_set_tag_ex(mov, tag, data, data_len, int_val, int_val2, NULL, NULL, 0); +} + #include <gpac/utf.h> GF_EXPORT @@ -6328,7 +6595,7 @@ u32 count, i; GF_XtraBox *xtra; - e = CanAccessMovie(mov, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(mov, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_create_meta_extensions(mov, GF_FALSE); @@ -6354,7 +6621,8 @@ return GF_OK; } gf_free(tag->prop_value); - tag->prop_value = 0; + tag->prop_value = NULL; + break; } if (!tag) { if (!name) return GF_OK; @@ -6367,10 +6635,10 @@ } u32 len = (u32) strlen(value); - tag->prop_value = gf_malloc(sizeof(u16) * (len+1) ); - memset(tag->prop_value, 0, sizeof(u16) * (len+1) ); + tag->prop_value = gf_malloc(sizeof(u16) * ((len/2)*2+2) ); + memset(tag->prop_value, 0, sizeof(u16) * ((len/2)*2+2) ); if (len) { - u32 _len = gf_utf8_mbstowcs((u16 *) tag->prop_value, len, (const char **) &value); + u32 _len = gf_utf8_mbstowcs((u16 *) tag->prop_value, len+1, (const char **) &value); if (_len == GF_UTF8_FAIL) _len = 0; tag->prop_value2 * _len = 0; tag->prop_value2 * _len + 1 = 0; @@ -6391,7 +6659,7 @@ u32 i, nb_keys; if (!movie) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) { gf_isom_set_last_error(movie, e); return 0; @@ -6593,7 +6861,7 @@ GF_EXPORT GF_Err gf_isom_set_alternate_group_id(GF_ISOFile *movie, u32 trackNumber, u32 groupId) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; trak->Header->alternate_group = groupId; return GF_OK; @@ -6610,12 +6878,12 @@ u32 alternateGroupID = 0; u32 next_switch_group_id = 0; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !switchGroupID) return GF_BAD_PARAM; if (trackRefGroup) { - GF_TrackBox *trak_ref = gf_isom_get_track_from_file(movie, trackRefGroup); + GF_TrackBox *trak_ref = gf_isom_get_track_box(movie, trackRefGroup); if (trak_ref != trak) { if (!trak_ref || !trak_ref->Header->alternate_group) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Track %d has not an alternate group - skipping\n", trak_ref ? trak_ref->Header->trackID : 0)); @@ -6639,7 +6907,7 @@ u32 i=0; while (i< gf_isom_get_track_count(movie) ) { //locate first available ID - GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i+1); + GF_TrackBox *a_trak = gf_isom_get_track_box(movie, i+1); if (a_trak->udta) { u32 j, count; @@ -6690,7 +6958,7 @@ if (!tsel) { tsel = (GF_TrackSelectionBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TSEL); if (!tsel) return GF_OUT_OF_MEM; - e = udta_on_child_box((GF_Box *)trak->udta, (GF_Box *) tsel, GF_FALSE); + e = udta_on_child_box_ex((GF_Box *)trak->udta, (GF_Box *) tsel, GF_FALSE, GF_TRUE); if (e) return e; } @@ -6723,7 +6991,7 @@ GF_TrackBox *trak; u32 alternateGroupID = 0; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Header->alternate_group) return GF_OK; @@ -6732,7 +7000,7 @@ u32 i=0; while (i< gf_isom_get_track_count(movie) ) { //locate first available ID - GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i+1); + GF_TrackBox *a_trak = gf_isom_get_track_box(movie, i+1); if (a_trak->Header->alternate_group == alternateGroupID) reset_tsel_box(a_trak); i++; } @@ -6749,7 +7017,7 @@ u32 i=0; while (i< gf_isom_get_track_count(movie) ) { //locate first available ID - GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i+1); + GF_TrackBox *a_trak = gf_isom_get_track_box(movie, i+1); reset_tsel_box(a_trak); i++; } @@ -6764,10 +7032,10 @@ GF_TrackBox *trak; GF_Err e; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak || !trak->Media || !trak->Media->information->sampleTable) return GF_BAD_PARAM; @@ -6800,10 +7068,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; @@ -7072,7 +7340,7 @@ GF_TrackFragmentBox *traf=NULL; #endif if (!trafID && (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; trafID = trak->Header->trackID; } @@ -7088,10 +7356,10 @@ #endif } else if (track) { - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; } @@ -7122,7 +7390,7 @@ if (sampleGroupDescriptionIndex) *sampleGroupDescriptionIndex = 0; if (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY) { - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; trafID = trak->Header->trackID; } @@ -7138,11 +7406,11 @@ #endif } else { if (check_access) { - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; } - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); } if (!trak) return GF_BAD_PARAM; @@ -7172,7 +7440,7 @@ void *sgde_dst = gf_list_get(sgdesc->group_descriptions, k); if (gf_isom_is_identical_sgpd(entry, sgde_dst, sgdesc->grouping_type)) { if (sampleGroupDescriptionIndex) *sampleGroupDescriptionIndex = k+1; - sgpd_del_entry(sgdesc->grouping_type, entry); + sgpd_del_entry(sgdesc->grouping_type, entry, sgdesc->is_opaque); if (use_default) { u32 idx = k+1; if (is_traf_sgpd && *is_traf_sgpd) idx |= 0x10000; @@ -7190,7 +7458,7 @@ e = gf_list_add(sgdesc->group_descriptions, entry); if (e) { - sgpd_del_entry(sgdesc->grouping_type, entry); + sgpd_del_entry(sgdesc->grouping_type, entry, sgdesc->is_opaque); return e; } @@ -7307,7 +7575,7 @@ GF_TrackFragmentBox *traf=NULL; #endif if (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY) { - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; trafID = trak->Header->trackID; } @@ -7324,11 +7592,11 @@ } else if (track) { if (check_access) { - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; } - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; } @@ -7367,10 +7635,10 @@ GF_TrackBox *trak; u32 count, i; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; if (trak->Media->information->sampleTable->sampleGroupsDescription) { @@ -7406,10 +7674,10 @@ GF_Err e; GF_TrackBox *trak; GF_List *groupList; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, track); + trak = gf_isom_get_track_box(movie, track); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sampleGroups) @@ -7423,6 +7691,7 @@ return gf_isom_add_sample_group_entry(groupList, sample_number, sgdesc, grouping_type_parameter, sampleGroupDescriptionIndex, trak->Media->information->sampleTable->child_boxes, trak->Media->information->sampleTable); } + void *sg_rap_create_entry(void *udta) { GF_VisualRandomAccessEntry *entry; @@ -7454,6 +7723,31 @@ } +void *sg_av1s_create_entry(void *udta) +{ + GF_AV1SwitchingEntry *entry; + GF_SAFEALLOC(entry, GF_AV1SwitchingEntry); + if (!entry) return NULL; + return entry; +} + +Bool sg_av1s_compare_entry(void *udta, void *entry) +{ + return GF_TRUE; +} + +GF_EXPORT +GF_Err gf_isom_set_sample_av1_switch_frame_group(GF_ISOFile *movie, u32 track, u32 sample_number, Bool is_switch_Frame) +{ + return gf_isom_set_sample_group_info_internal(movie, track, 0, sample_number, GF_ISOM_SAMPLE_GROUP_AV1S, 0, NULL, is_switch_Frame ? sg_av1s_create_entry : NULL, is_switch_Frame ? sg_av1s_compare_entry : NULL); +} + +GF_Err gf_isom_fragment_set_sample_av1_switch_frame_group(GF_ISOFile *movie, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_switch_Frame) +{ + return gf_isom_set_sample_group_info_internal(movie, 0, trackID, sample_number_in_frag, GF_ISOM_SAMPLE_GROUP_AV1S, 0, NULL, is_switch_Frame ? sg_av1s_create_entry : NULL, is_switch_Frame ? sg_av1s_compare_entry : NULL); +} + + void *sg_roll_create_entry(void *udta) { @@ -7554,9 +7848,9 @@ GF_Err gf_isom_force_ctts(GF_ISOFile *file, u32 track) { GF_TrackBox *trak; - GF_Err e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; if (trak->Media->information->sampleTable->CompositionOffset) return GF_OK; @@ -7569,6 +7863,7 @@ return GF_OK; } +GF_EXPORT GF_Err gf_isom_set_ctts_v1(GF_ISOFile *file, u32 track, u32 ctts_shift) { u32 i, shift; @@ -7577,10 +7872,10 @@ GF_CompositionToDecodeBox *cslg; s32 leastCTTS, greatestCTTS; GF_TrackBox *trak; - GF_Err e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; ctts = trak->Media->information->sampleTable->CompositionOffset; @@ -7670,7 +7965,7 @@ else { cslg = trak->Media->information->sampleTable->CompositionToDecode; - shift = cslg->compositionToDTSShift; + shift = (s32) cslg->compositionToDTSShift; for (i=0; i<ctts->nb_entries; i++) { s64 new_ts = ctts->entriesi.decodingOffset; new_ts += shift; @@ -7709,10 +8004,10 @@ GF_TrackBox *trak; GF_CompositionOffsetBox *ctts; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; ctts = trak->Media->information->sampleTable->CompositionOffset; @@ -7738,10 +8033,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->SyncSample) { @@ -7759,10 +8054,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; return stbl_SetDependencyType(trak->Media->information->sampleTable, sampleNumber, isLeading, dependsOn, dependedOn, redundant); } @@ -7772,7 +8067,7 @@ { GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; return stbl_AddDependencyType(trak->Media->information->sampleTable, sampleNumber, isLeading, dependsOn, dependedOn, redundant); @@ -7787,10 +8082,10 @@ GF_Err e; GF_TrackBox *src_trak, *dst_trak; - src_trak = gf_isom_get_track_from_file(src, src_track); + src_trak = gf_isom_get_track_box(src, src_track); if (!src_trak) return GF_BAD_PARAM; - dst_trak = gf_isom_get_track_from_file(dst, dst_track); + dst_trak = gf_isom_get_track_box(dst, dst_track); if (!dst_trak) return GF_BAD_PARAM; dst_sample_num = dst_trak->Media->information->sampleTable->SampleSize->sampleCount; @@ -8001,10 +8296,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; for (i=0; i < gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); i++) { @@ -8064,10 +8359,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; @@ -8145,9 +8440,13 @@ memmove(pssh->KIDs, ((GF_ProtectionSystemHeaderBox *)a)->KIDs, pssh->KID_count*sizeof(bin128)); } pssh->private_data_size = ((GF_ProtectionSystemHeaderBox *)a)->private_data_size; - pssh->private_data = (u8 *)gf_malloc(pssh->private_data_size*sizeof(char)); - if (!pssh->private_data) return GF_OUT_OF_MEM; - memmove(pssh->private_data, ((GF_ProtectionSystemHeaderBox *)a)->private_data, pssh->private_data_size); + if (!pssh->private_data_size) { + pssh->private_data = NULL; + } else { + pssh->private_data = (u8 *)gf_malloc(pssh->private_data_size*sizeof(char)); + if (!pssh->private_data) return GF_OUT_OF_MEM; + memmove(pssh->private_data, ((GF_ProtectionSystemHeaderBox *)a)->private_data, pssh->private_data_size); + } } } return GF_OK; @@ -8161,10 +8460,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track_number); + trak = gf_isom_get_track_box(file, track_number); if (!trak) return GF_BAD_PARAM; if (!trak->groups) trak->groups = (GF_TrackGroupBox*) gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_TRGR); if (!trak->groups) return GF_OUT_OF_MEM; @@ -8208,10 +8507,10 @@ GF_MPEGVisualSampleEntryBox *ve; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; @@ -8234,10 +8533,11 @@ return GF_OK; } +GF_EXPORT GF_Err gf_isom_set_sample_group_in_traf(GF_ISOFile *file) { GF_Err e; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; file->sample_groups_in_traf = GF_TRUE; @@ -8261,7 +8561,7 @@ GF_MPEGVisualSampleEntryBox *vid_ent; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak || !stsd_idx) return GF_BAD_PARAM; if (!trak->Media || !trak->Media->handler || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleDescription) @@ -8293,7 +8593,7 @@ return GF_OK; } - +GF_EXPORT GF_Err gf_isom_update_sample_description_from_template(GF_ISOFile *file, u32 track, u32 sampleDescriptionIndex, u8 *data, u32 size) { GF_BitStream *bs; @@ -8301,7 +8601,7 @@ GF_Box *ent, *tpl_ent; GF_Err e; /*get orig sample desc and clone it*/ - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak || !sampleDescriptionIndex) return GF_BAD_PARAM; if (!trak->Media || !trak->Media->handler || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleDescription) @@ -8466,7 +8766,7 @@ if (!box) { if (box_type==GF_ISOM_BOX_TYPE_TRAK) { if (trackID) { - box = (GF_Box *) gf_isom_get_track_from_file(file, gf_isom_get_track_by_id(file, trackID) ); + box = (GF_Box *) gf_isom_get_track_box(file, gf_isom_get_track_by_id(file, trackID) ); } if (!box && gf_list_count(file->moov->trackList)==1) { box = gf_list_get(file->moov->trackList, 0); @@ -8573,18 +8873,21 @@ if (!item_id) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("ISOBMFF Inserting box in ipco without itemID, no association added\n")); } else if (ipma) { - u32 nb_asso, k; + u32 nb_asso, k, insert_pos; GF_ItemPropertyAssociationEntry *entry = NULL; nb_asso = gf_list_count(ipma->entries); + insert_pos = 0; for (k=0; k<nb_asso;k++) { entry = gf_list_get(ipma->entries, k); if (entry->item_id==item_id) break; + // item ids must appear in increasing order + if (item_id>entry->item_id) ++insert_pos; entry = NULL; } if (!entry) { GF_SAFEALLOC(entry, GF_ItemPropertyAssociationEntry); if (!entry) return GF_OUT_OF_MEM; - gf_list_add(ipma->entries, entry); + gf_list_insert(ipma->entries, entry, insert_pos); entry->item_id = item_id; } entry->associations = gf_realloc(entry->associations, sizeof(GF_ItemPropertyAssociationSlot) * (entry->nb_associations+1)); @@ -8632,7 +8935,7 @@ GF_EXPORT GF_Err gf_isom_set_track_magic(GF_ISOFile *movie, u32 trackNumber, u64 magic) { - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak) return GF_BAD_PARAM; trak->magic = magic; return GF_OK; @@ -8643,7 +8946,7 @@ { u32 i, j, count; GF_List *tracks; - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !index) return GF_BAD_PARAM; trak->index = index; tracks = gf_list_new(); @@ -8695,9 +8998,9 @@ GF_Err e; GF_MPEGVisualSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, 0); if (!entry) return GF_OK; @@ -8737,10 +9040,10 @@ if (movie->editFileMap && gf_bs_get_size(movie->editFileMap->bs)) movie->no_inplace_rewrite = GF_TRUE; //block redirect (used by mp4mx), no inplace rewrite - else if (movie->on_block_out || !strcmp(movie->finalName, "_gpac_isobmff_redirect")) + else if (movie->on_block_out || (movie->finalName && !strcmp(movie->finalName, "_gpac_isobmff_redirect"))) movie->no_inplace_rewrite = GF_TRUE; //stdout redirect, no inplace rewrite - else if (!strcmp(movie->finalName, "std")) + else if (movie->finalName && !strcmp(movie->finalName, "std")) movie->no_inplace_rewrite = GF_TRUE; //new file, no inplace rewrite else if (!movie->fileName) @@ -8758,7 +9061,7 @@ movie->no_inplace_rewrite = GF_TRUE; } - +GF_EXPORT GF_Err gf_isom_set_y3d_info(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, GF_ISOM_Y3D_Info *info) { GF_Err e; @@ -8766,10 +9069,10 @@ GF_SampleEntryBox *ent; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !info) return GF_BAD_PARAM; ent = gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescriptionIndex-1); @@ -9000,10 +9303,10 @@ GF_Err e; GF_TrackBox *trak; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; return gf_isom_add_sample_aux_info_internal(trak, NULL, sampleNumber, aux_type, aux_info, data, size); @@ -9014,7 +9317,7 @@ { u32 i, count; if (!file) return GF_BAD_PARAM; - GF_Err e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; if (file->moov->meta) file->moov->meta->write_qt = 1; @@ -9037,7 +9340,7 @@ GF_MHACompatibleProfilesBox *mhap; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; ent = gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescIndex-1); if (!ent) return GF_BAD_PARAM; @@ -9078,7 +9381,7 @@ GF_SampleEntryBox *ent; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; ent = gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescIndex-1); if (!ent) return GF_BAD_PARAM; @@ -9170,5 +9473,62 @@ return GF_OK; } +GF_Err isom_sample_refs_push(GF_SampleReferences *sref, s32 refID, u32 nb_refs, s32 *refs) +{ + GF_SampleRefEntry *ent; + GF_SAFEALLOC(ent, GF_SampleRefEntry); + if (!ent) return GF_OUT_OF_MEM; + refID += sref->id_shift; + if (refID<0) { + u32 new_shift = -refID; + sref->id_shift += -refID; + refID = 0; + u32 i, j, count = gf_list_count(sref->entries); + for (i=0; i<count; i++) { + GF_SampleRefEntry *a = gf_list_get(sref->entries, i); + a->sampleID += new_shift; + for (j=0; j<a->nb_refs; j++) + a->sample_refsj += new_shift; + } + } + + ent->sampleID = refID; + if (nb_refs) { + u32 j; + ent->nb_refs = nb_refs; + ent->sample_refs = gf_malloc(sizeof(u32)*nb_refs); + memcpy(ent->sample_refs, refs, sizeof(s32)*nb_refs); + if (sref->id_shift) { + for (j=0; j<ent->nb_refs; j++) + ent->sample_refsj += sref->id_shift; + } + } + sref->cdrf_cache_size = 0; + return gf_list_add(sref->entries, ent); +} + +GF_Err gf_isom_set_sample_references(GF_ISOFile *file, u32 track, u32 sampleNumber, s32 refID, u32 nb_refs, s32 *refs) +{ + GF_Err e; + GF_TrackBox *trak; + GF_SampleTableBox *stbl; + + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_box(file, track); + if (!trak) return GF_BAD_PARAM; + stbl = trak->Media->information->sampleTable; + if (sampleNumber != stbl->SampleSize->sampleCount) + return GF_BAD_PARAM; + + if (!stbl->SampleRefs) { + stbl->SampleRefs = (GF_SampleReferences *)gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_CDRF); + if (!stbl->SampleRefs) return GF_OUT_OF_MEM; + } + return isom_sample_refs_push(stbl->SampleRefs, refID, nb_refs, refs); + +} + #endif /*!defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_ISOM_WRITE)*/
View file
gpac-2.4.0.tar.gz/src/isomedia/media.c -> gpac-26.02.0.tar.gz/src/isomedia/media.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -241,6 +241,7 @@ break; case GF_ISOM_BOX_TYPE_VP08: case GF_ISOM_BOX_TYPE_VP09: + case GF_ISOM_BOX_TYPE_VP10: if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO) return GF_ISOM_INVALID_MEDIA; VP9_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*)entry, mdia); @@ -249,12 +250,12 @@ case GF_ISOM_BOX_TYPE_MP4A: if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO) return GF_ISOM_INVALID_MEDIA; - { - GF_MPEGAudioSampleEntryBox *ase = (GF_MPEGAudioSampleEntryBox*)entry; - ESDa = ase->esd; - if (ESDa) { + { + GF_MPEGAudioSampleEntryBox *ase = (GF_MPEGAudioSampleEntryBox*)entry; + ESDa = ase->esd; + if (ESDa) { esd = (GF_ESD *) ESDa->desc; - } else if (!true_desc_only) { + } else if (!true_desc_only) { Bool make_mp4a = GF_FALSE; sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF); @@ -281,8 +282,8 @@ return GF_NOT_SUPPORTED; #endif } - } - } + } + } break; case GF_ISOM_BOX_TYPE_MP4S: if (entry->internal_type==GF_ISOM_SAMPLE_ENTRY_MP4S) { @@ -309,19 +310,21 @@ if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_GENERIC) return GF_ISOM_INVALID_MEDIA; - if (true_desc_only) return GF_ISOM_INVALID_MEDIA; + if (true_desc_only) + return GF_ISOM_INVALID_MEDIA; + { - GF_WebVTTSampleEntryBox*vtte = (GF_WebVTTSampleEntryBox*)entry; - esd = gf_odf_desc_esd_new(2); - *out_esd = esd; - esd->decoderConfig->streamType = GF_STREAM_TEXT; - esd->decoderConfig->objectTypeIndication = GF_CODECID_WEBVTT; - if (vtte->config) { - esd->decoderConfig->decoderSpecificInfo->dataLength = (u32) strlen(vtte->config->string); - esd->decoderConfig->decoderSpecificInfo->data = gf_malloc(sizeof(char)*esd->decoderConfig->decoderSpecificInfo->dataLength); - memcpy(esd->decoderConfig->decoderSpecificInfo->data, vtte->config->string, esd->decoderConfig->decoderSpecificInfo->dataLength); + GF_WebVTTSampleEntryBox*vtte = (GF_WebVTTSampleEntryBox*)entry; + esd = gf_odf_desc_esd_new(2); + *out_esd = esd; + esd->decoderConfig->streamType = GF_STREAM_TEXT; + esd->decoderConfig->objectTypeIndication = GF_CODECID_WEBVTT; + if (vtte->config) { + esd->decoderConfig->decoderSpecificInfo->dataLength = (u32) strlen(vtte->config->string); + esd->decoderConfig->decoderSpecificInfo->data = gf_malloc(sizeof(char)*esd->decoderConfig->decoderSpecificInfo->dataLength); + memcpy(esd->decoderConfig->decoderSpecificInfo->data, vtte->config->string, esd->decoderConfig->decoderSpecificInfo->dataLength); + } } - } break; case GF_ISOM_BOX_TYPE_STPP: case GF_ISOM_BOX_TYPE_SBTT: @@ -361,6 +364,7 @@ gf_odf_opus_cfg_write(&opus_c->opcfg, & (*out_esd)->decoderConfig->decoderSpecificInfo->data, & (*out_esd)->decoderConfig->decoderSpecificInfo->dataLength); break; } + case GF_ISOM_SUBTYPE_3GP_H263: if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO) return GF_ISOM_INVALID_MEDIA; @@ -495,6 +499,7 @@ if (out_offset) *out_offset = offset; if (!samp ) return GF_OK; + (*samp)->corrupted = 0; if (mdia->information->sampleTable->TimeToSample) { //get the DTS e = stbl_GetSampleDTS_and_Duration(mdia->information->sampleTable->TimeToSample, sampleNumber, &(*samp)->DTS, &(*samp)->duration); @@ -606,6 +611,7 @@ } if (data_size != 0) { + GF_BlobRangeStatus range_status; if (mdia->mediaTrack->pack_num_samples) { u32 idx_in_chunk = sampleNumber - mdia->information->sampleTable->SampleToChunk->firstSampleInCurrentChunk; u32 left_in_chunk = stsc_entry->samplesPerChunk - idx_in_chunk; @@ -618,21 +624,16 @@ (*samp)->alloc_size = 0; /*and finally get the data, include padding if needed*/ - if ((*samp)->alloc_size) { - if ((*samp)->alloc_size < data_size + mdia->mediaTrack->padding_bytes) { - (*samp)->data = (char *) gf_realloc((*samp)->data, sizeof(char) * ( data_size + mdia->mediaTrack->padding_bytes) ); - if (! (*samp)->data) return GF_OUT_OF_MEM; - - (*samp)->alloc_size = data_size + mdia->mediaTrack->padding_bytes; - } + if (ext_realloc) { + (*samp)->data = mdia->mediaTrack->sample_alloc_cbk(data_size + mdia->mediaTrack->padding_bytes, mdia->mediaTrack->sample_alloc_udta); + } else if ((*samp)->alloc_size) { + (*samp)->data = (char *) gf_realloc((*samp)->data, sizeof(char) * ( data_size + mdia->mediaTrack->padding_bytes) ); + if ((*samp)->data) (*samp)->alloc_size = data_size + mdia->mediaTrack->padding_bytes; } else { - if (ext_realloc) { - (*samp)->data = mdia->mediaTrack->sample_alloc_cbk(data_size + mdia->mediaTrack->padding_bytes, mdia->mediaTrack->sample_alloc_udta); - } else { - (*samp)->data = (u8 *) gf_malloc(data_size + mdia->mediaTrack->padding_bytes); - } - if (! (*samp)->data) return GF_OUT_OF_MEM; + (*samp)->data = (u8 *) gf_malloc(data_size + mdia->mediaTrack->padding_bytes); } + if (! (*samp)->data) return GF_OUT_OF_MEM; + (*samp)->dataLength = data_size; if (mdia->mediaTrack->padding_bytes) memset((*samp)->data + data_size, 0, sizeof(char) * mdia->mediaTrack->padding_bytes); @@ -647,11 +648,18 @@ return GF_ISOM_INCOMPLETE_FILE; } } - bytesRead = gf_isom_datamap_get_data(mdia->information->dataHandler, (*samp)->data, (*samp)->dataLength, offset); + bytesRead = gf_isom_datamap_get_data(mdia->information->dataHandler, (*samp)->data, (*samp)->dataLength, offset, &range_status); //if bytesRead != sampleSize, we have an IO err if (bytesRead < data_size) { + if (range_status == GF_BLOB_RANGE_IN_TRANSFER) { + mdia->BytesMissing = (*samp)->dataLength; + return GF_ISOM_INCOMPLETE_FILE; + } return GF_IO_ERR; } + if (range_status == GF_BLOB_RANGE_CORRUPTED) { + (*samp)->corrupted = 1; + } mdia->BytesMissing = 0; } else { (*samp)->dataLength = 0; @@ -1090,8 +1098,10 @@ GF_SampleTableBox *stbl = mdia->information->sampleTable; //set size, offset, RAP, CTS ... - stbl_SetSampleSize(stbl->SampleSize, sampleNumber, size); - stbl_SetChunkOffset(mdia, sampleNumber, offset); + if (size) { + stbl_SetSampleSize(stbl->SampleSize, sampleNumber, size); + stbl_SetChunkOffset(mdia, sampleNumber, offset); + } //do we have a CTS? if (stbl->CompositionOffset) { @@ -1138,6 +1148,30 @@ stbl = mdia->information->sampleTable; if (!data_only) { + if (!sample->data) { + u32 osample_num; + if (sampleNumber==1) { + gf_free(stbl->TimeToSample->entries); + stbl->TimeToSample->entries = NULL; + stbl->TimeToSample->nb_entries = 0; + stbl->TimeToSample->alloc_size = 0; + stbl->TimeToSample->w_LastDTS = 0; + stbl->TimeToSample->w_currentSampleNum = 0; + + if (stbl->CompositionOffset) { + gf_isom_box_del_parent(&stbl->child_boxes, (GF_Box *)stbl->CompositionOffset); + stbl->CompositionOffset = NULL; + } + } + stbl_unpackCTS(stbl); + stbl_AddDTS(stbl, sample->DTS, &osample_num, 0, 0); + if (osample_num != sampleNumber) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso file DTS patching must be done incrementally but input changes sample number for source sample %u to new number %u\n", sampleNumber, osample_num)); + return GF_BAD_PARAM; + } + return UpdateSample(mdia, sampleNumber, 0, sample->CTS_Offset, 0, sample->IsRAP); + + } //check we have the sampe dts e = stbl_GetSampleDTS(stbl->TimeToSample, sampleNumber, &DTS); if (e) return e;
View file
gpac-2.4.0.tar.gz/src/isomedia/media_odf.c -> gpac-26.02.0.tar.gz/src/isomedia/media_odf.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2019 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -512,7 +512,7 @@ { u32 i, j, di, the_od_id; GF_TrackBox *od_tk; - GF_TrackBox *tk = gf_isom_get_track_from_file(file, track); + GF_TrackBox *tk = gf_isom_get_track_box(file, track); if (!tk) return 0; i=0;
View file
gpac-2.4.0.tar.gz/src/isomedia/meta.c -> gpac-26.02.0.tar.gz/src/isomedia/meta.c
Changed
@@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Cyril Concolato - Jean le Feuvre - * Copyright (c) Telecom ParisTech 2005-2024 + * Copyright (c) Telecom ParisTech 2005-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -295,6 +295,10 @@ /*FIXME*/ else if (location_entry->data_reference_index) { char *item_url = NULL, *item_urn = NULL; + + if (!meta || !meta->file_locations || !meta->file_locations->dref || !meta->file_locations->dref->child_boxes) + return GF_ISOM_INVALID_FILE; + GF_FullBox *a = (GF_FullBox *)gf_list_get(meta->file_locations->dref->child_boxes, location_entry->data_reference_index-1); if (!a) return GF_ISOM_INVALID_FILE; if (a->type==GF_ISOM_BOX_TYPE_URL) { @@ -356,10 +360,11 @@ item_bs = gf_bs_from_file(resource, GF_BITSTREAM_WRITE); } - if ((item_type == GF_ISOM_SUBTYPE_HVC1) || (item_type == GF_ISOM_SUBTYPE_AVC_H264) ) { + if ((item_type == GF_ISOM_SUBTYPE_HVC1) || (item_type == GF_ISOM_SUBTYPE_AVC_H264) || (item_type == GF_ISOM_SUBTYPE_VVC1) ) { u32 j, nb_assoc; GF_HEVCConfigurationBox *hvcc = NULL; GF_AVCConfigurationBox *avcc = NULL; + GF_VVCConfigurationBox *vvcc = NULL; if (!meta->item_props || !meta->item_props->property_container || !meta->item_props->property_association) { if (item_bs) gf_bs_del(item_bs); return GF_NON_COMPLIANT_BITSTREAM; @@ -377,14 +382,21 @@ if (item_bs) gf_bs_del(item_bs); return GF_NON_COMPLIANT_BITSTREAM; } - if (hvcc->type == GF_ISOM_BOX_TYPE_HVCC) break; + if (hvcc->type == GF_ISOM_BOX_TYPE_HVCC) + break; if (hvcc->type == GF_ISOM_BOX_TYPE_AVCC) { avcc = (GF_AVCConfigurationBox *) hvcc; hvcc = NULL; break; } + if (hvcc->type == GF_ISOM_BOX_TYPE_VVCC) { + vvcc = (GF_VVCConfigurationBox *) hvcc; + hvcc = NULL; + break; + } + hvcc = NULL; } - if (avcc || hvcc) break; + if (avcc || hvcc || vvcc) break; } if (hvcc) { if (! hvcc->config) { @@ -408,6 +420,17 @@ } nalu_size_length = avcc->config->nal_unit_size; } + } else if (vvcc) { + if (! vvcc->config) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Missing VVC config in vvcC\n")); + } else { + if (use_annex_b) { + vvcc->config->write_annex_b = GF_TRUE; + gf_odf_vvc_cfg_write_bs(vvcc->config, item_bs); + vvcc->config->write_annex_b = GF_FALSE; + } + nalu_size_length = vvcc->config->nal_unit_size; + } } } @@ -451,6 +474,10 @@ gf_bs_write_data(item_bs, buf_cache, cache_size); remain -= cache_size; } + if (gf_bs_is_overflow(file->movieFileMap->bs)) { + e = GF_ISOM_INVALID_FILE; + break; + } } } if (out_data) { @@ -587,7 +614,7 @@ char szName40; GF_MetaBox *meta; - GF_Err e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + GF_Err e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; meta = gf_isom_get_meta(file, root_meta, track_num); @@ -677,7 +704,7 @@ if (!XMLFileName && !data) return GF_BAD_PARAM; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; meta = gf_isom_get_meta(file, root_meta, track_num); @@ -820,7 +847,9 @@ case GF_ISOM_BOX_TYPE_AVCC: case GF_ISOM_BOX_TYPE_AV1C: case GF_ISOM_BOX_TYPE_VVCC: - prop->config = b; + case GF_ISOM_BOX_TYPE_J2KH: + if (!prop->config) + prop->config = b; break; default: @@ -964,19 +993,22 @@ } static GF_Err meta_add_item_property_association(GF_ItemPropertyAssociationBox *ipma, u32 item_ID, u32 prop_index, Bool essential) { - u32 i, count; + u32 i, count, insert_pos; GF_ItemPropertyAssociationEntry *found_entry = NULL; count = gf_list_count(ipma->entries); + insert_pos = 0; for (i = 0; i < count; i++) { found_entry = (GF_ItemPropertyAssociationEntry *)gf_list_get(ipma->entries, i); if (found_entry->item_id == item_ID) break; + // item ids must appear in increasing order + if (item_ID > found_entry->item_id) ++insert_pos; found_entry = NULL; } if (!found_entry) { GF_SAFEALLOC(found_entry, GF_ItemPropertyAssociationEntry); if (!found_entry) return GF_OUT_OF_MEM; - gf_list_add(ipma->entries, found_entry); + gf_list_insert(ipma->entries, found_entry, insert_pos); found_entry->item_id = item_ID; } found_entry->associations = gf_realloc(found_entry->associations, sizeof(GF_ItemPropertyAssociationSlot) * (found_entry->nb_associations+1)); @@ -1414,11 +1446,15 @@ u32 lastItemID = 0; u32 item_id = io_item_id ? *io_item_id : 0; - e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(file, GF_ISOM_OPEN_WRITE); if (e) return e; meta = gf_isom_get_meta(file, root_meta, track_num); if (!meta) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Trying to add item, but missing meta box")); + if (track_num) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Trying to add item, but missing meta box in track %u\n", track_num)); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Trying to add item, but missing meta box in %s\n", root_meta ? "file" : "movie box")); + } return GF_BAD_PARAM; } @@ -1438,7 +1474,7 @@ GF_ItemInfoEntryBox *iinf_e= (GF_ItemInfoEntryBox *)gf_list_get(meta->item_infos->item_infos, i); if (iinf_e->item_ID > lastItemID) lastItemID = iinf_e->item_ID; if (item_id == iinf_e->item_ID) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("IsoMedia Item with id %d already exists, ignoring id\n", item_id)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("IsoMedia Item with ID %d already exists, ignoring ID\n", item_id)); item_id = 0; } } @@ -1753,6 +1789,14 @@ } GF_EXPORT +GF_Err gf_isom_add_meta_item2(GF_ISOFile *file, Bool root_meta, u32 track_num, Bool self_reference, char *resource_path, const char *item_name, u32 *io_item_id, u32 item_type, + const char *mime_type, const char *content_encoding, const char *URL, const char *URN, + GF_ImageItemProperties *image_props) +{ + return gf_isom_add_meta_item_extended(file, root_meta, track_num, self_reference, resource_path, item_name, io_item_id, item_type, mime_type, content_encoding, image_props, URL, URN, NULL, 0, NULL, 0, 0); +} + +GF_EXPORT GF_Err gf_isom_add_meta_item_memory(GF_ISOFile *file, Bool root_meta, u32 track_num, const char *item_name, u32 *item_id, u32 item_type, const char *mime_type, const char *content_encoding, GF_ImageItemProperties *image_props, char *data, u32 data_len, GF_List *item_extent_refs) { return gf_isom_add_meta_item_extended(file, root_meta, track_num, GF_FALSE, NULL, item_name, item_id, item_type, mime_type, content_encoding, image_props, NULL, NULL, data, data_len, item_extent_refs, 0, 0);
View file
gpac-2.4.0.tar.gz/src/isomedia/movie_fragments.c -> gpac-26.02.0.tar.gz/src/isomedia/movie_fragments.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -24,6 +24,7 @@ */ #include <gpac/internal/isomedia_dev.h> +#include <gpac/network.h> #ifndef GPAC_DISABLE_ISOM @@ -56,12 +57,15 @@ #ifndef GPAC_DISABLE_ISOM_WRITE +GF_EXPORT GF_Err gf_isom_set_movie_duration(GF_ISOFile *movie, u64 duration, Bool remove_mehd) { if (!movie || !movie->moov || !movie->moov->mvex) return GF_BAD_PARAM; if (remove_mehd) { - if (!movie->moov->mvex->mehd) { + if (duration) + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("iso fragment removing mehd box while duration is not zero. Contact the GPAC team!\n")); + if (movie->moov->mvex->mehd) { gf_isom_box_del_parent(&movie->moov->mvex->child_boxes, (GF_Box*)movie->moov->mvex->mehd); movie->moov->mvex->mehd = NULL; } @@ -1223,6 +1227,15 @@ gf_bs_write_u64(bs, movie->moof->ntp); gf_bs_write_u64(bs, movie->moof->timestamp); } + if (movie->moof->prft_at_mux) { + gf_bs_write_u32(bs, 8*4); + gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_PRFT ); + gf_bs_write_u8(bs, 1); + gf_bs_write_u24(bs, 4); + gf_bs_write_u32(bs, movie->moof->reference_track_ID); + gf_bs_write_u64(bs, !gf_sys_is_test_mode() ? gf_net_get_ntp_ts() : 0); + gf_bs_write_u64(bs, movie->moof->timestamp); + } if (movie->moof->emsgs) { while (1) { GF_Box *emsg = gf_list_pop_front(movie->moof->emsgs); @@ -1257,6 +1270,9 @@ } if (trun_ref_size) { + //this may happen when starting a new fragments without explicitly flushing previous one + if (!movie->moof->mdat_size) + movie->moof->mdat_size = 8 + (u32) trun_ref_size; gf_bs_write_u32(bs, movie->moof->mdat_size); gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_MDAT); } @@ -1410,13 +1426,15 @@ static GF_Err gf_isom_write_styp(GF_ISOFile *movie, Bool last_segment) { /*write STYP if we write to a different file or if we write the last segment*/ - if (movie->use_segments && !movie->append_segment && !movie->segment_start && movie->write_styp) { + if (!movie->append_segment && !movie->segment_start && movie->write_styp) { GF_Err e; /*modify brands STYP*/ if (movie->write_styp==1) { - /*"msix" brand: this is a DASH Initialization Segment*/ - gf_isom_modify_alternate_brand(movie, GF_ISOM_BRAND_MSIX, GF_TRUE); + if (movie->use_segments) { + /*"msix" brand: this is a DASH Initialization Segment*/ + gf_isom_modify_alternate_brand(movie, GF_ISOM_BRAND_MSIX, GF_TRUE); + } if (last_segment) { /*"lmsg" brand: this is the last DASH Segment*/ gf_isom_modify_alternate_brand(movie, GF_ISOM_BRAND_LMSG, GF_TRUE); @@ -2531,12 +2549,13 @@ } GF_EXPORT -GF_Err gf_isom_set_fragment_reference_time(GF_ISOFile *movie, GF_ISOTrackID reference_track_ID, u64 ntp, u64 timestamp) +GF_Err gf_isom_set_fragment_reference_time(GF_ISOFile *movie, GF_ISOTrackID reference_track_ID, u64 ntp, u64 timestamp, Bool at_mux) { if (!movie || !movie->moof) return GF_BAD_PARAM; movie->moof->reference_track_ID = reference_track_ID; movie->moof->ntp = ntp; movie->moof->timestamp = timestamp; + movie->moof->prft_at_mux = at_mux; return GF_OK; } @@ -3148,6 +3167,24 @@ return gf_isom_add_subsample_info(subs, last_sample, subSampleSize, priority, reserved, discardable); } + +GF_Err gf_isom_set_fragment_original_duration(GF_ISOFile *movie, GF_ISOTrackID TrackID, u32 orig_dur, u32 elapsed_dur) +{ + GF_TrackFragmentBox *traf; + if (!movie->moof || !(movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY) ) return GF_BAD_PARAM; + + traf = gf_isom_get_traf(movie, TrackID); + if (!traf) return GF_BAD_PARAM; + + if (!traf->rsot) { + traf->rsot = (GF_TFOriginalDurationBox *) gf_isom_box_new_parent(&traf->child_boxes, GF_ISOM_BOX_TYPE_RSOT); + if (!traf->rsot) return GF_OUT_OF_MEM; + } + if (orig_dur) traf->rsot->original_duration = orig_dur; + if (elapsed_dur) traf->rsot->elapsed_duration = elapsed_dur; + return GF_OK; +} + #if 0 //unused static GF_Err gf_isom_copy_sample_group_entry_to_traf(GF_TrackFragmentBox *traf, GF_SampleTableBox *stbl, u32 grouping_type, u32 grouping_type_parameter, u32 sampleGroupDescriptionIndex, Bool sgpd_in_traf) { @@ -3224,7 +3261,7 @@ traf = gf_isom_get_traf(dest, TrackID); if (!traf || !traf->tfhd->sample_desc_index) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(orig, track); + trak = gf_isom_get_track_box(orig, track); if (!trak) return GF_BAD_PARAM; /*modify depends flags*/ @@ -3421,6 +3458,7 @@ return GF_OK; } +GF_EXPORT GF_Err gf_isom_enable_mfra(GF_ISOFile *file) { if (!file) return GF_BAD_PARAM; @@ -3492,4 +3530,25 @@ return GF_FALSE; } + +GF_Err isom_sample_refs_push(GF_SampleReferences *sref, s32 refID, u32 nb_refs, s32 *refs); + +GF_EXPORT +GF_Err gf_isom_fragment_add_sample_references(GF_ISOFile *movie, GF_ISOTrackID TrackID, s32 refID, u32 nb_refs, s32 *refs) +{ + GF_TrackFragmentBox *traf; + if (!movie->moof || !(movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) + return GF_BAD_PARAM; + + traf = gf_isom_get_traf(movie, TrackID); + if (!traf) + return GF_BAD_PARAM; + + if (!traf->SampleRefs) { + traf->SampleRefs = (GF_SampleReferences *)gf_isom_box_new_parent(&traf->child_boxes, GF_ISOM_BOX_TYPE_CDRF); + if (!traf->SampleRefs) return GF_OUT_OF_MEM; + } + return isom_sample_refs_push(traf->SampleRefs, refID, nb_refs, refs); +} + #endif /*GPAC_DISABLE_ISOM*/
View file
gpac-2.4.0.tar.gz/src/isomedia/sample_descs.c -> gpac-26.02.0.tar.gz/src/isomedia/sample_descs.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -232,8 +232,12 @@ case GF_ISOM_BOX_TYPE_AC3: case GF_ISOM_BOX_TYPE_EC3: return (GF_Box *)mpga->cfg_ac3; + case GF_ISOM_BOX_TYPE_AC4: + return (GF_Box *)mpga->cfg_ac4; case GF_ISOM_BOX_TYPE_OPUS: return (GF_Box *)mpga->cfg_opus; + case GF_ISOM_BOX_TYPE_IAMF: + return (GF_Box *)mpga->cfg_iamf; case GF_ISOM_BOX_TYPE_MHA1: case GF_ISOM_BOX_TYPE_MHA2: return (GF_Box *)mpga->cfg_mha; @@ -302,7 +306,7 @@ GF_3GPConfig *config, *res; GF_TrackBox *trak; GF_SampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return NULL; config = NULL; @@ -340,7 +344,7 @@ GF_AC3Config *res; GF_TrackBox *trak; GF_MPEGAudioSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return NULL; entry = (GF_MPEGAudioSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -356,12 +360,35 @@ } GF_EXPORT +GF_AC4Config *gf_isom_ac4_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex) +{ + GF_AC4Config *res; + GF_TrackBox *trak; + GF_MPEGAudioSampleEntryBox *entry; + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !StreamDescriptionIndex) return NULL; + + entry = (GF_MPEGAudioSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); + if (!entry) return NULL; + if (!entry->cfg_ac4) return NULL; + if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO) return NULL; + if (entry->cfg_ac4->type != GF_ISOM_BOX_TYPE_DAC4) return NULL; + + res = (GF_AC4Config*)gf_malloc(sizeof(GF_AC4Config)); + if (res) { + gf_odf_ac4_cfg_deep_copy(res, &entry->cfg_ac4->cfg); + if (!res->channel_count) res->channel_count = entry->channel_count; + } + return res; +} + +GF_EXPORT GF_Err gf_isom_flac_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u8 **dsi, u32 *dsi_size) { u32 type; GF_TrackBox *trak; GF_MPEGAudioSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (dsi) *dsi = NULL; if (dsi_size) *dsi_size = 0; if (!trak || !StreamDescriptionIndex) return GF_BAD_PARAM; @@ -390,7 +417,7 @@ { GF_TrackBox *trak; GF_MPEGAudioSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return GF_BAD_PARAM; entry = (GF_MPEGAudioSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -405,6 +432,7 @@ } #ifndef GPAC_DISABLE_ISOM_WRITE +GF_EXPORT GF_Err gf_isom_truehd_config_new(GF_ISOFile *the_file, u32 trackNumber, char *URLname, char *URNname, u32 format, u32 peak_rate, u32 *outDescriptionIndex) { GF_TrackBox *trak; @@ -413,10 +441,10 @@ GF_MPEGAudioSampleEntryBox *entry; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; //get or create the data ref @@ -454,10 +482,10 @@ GF_SampleDescriptionBox *stsd; if (!cfg) return GF_BAD_PARAM; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; //get or create the data ref @@ -490,7 +518,7 @@ u32 type; GF_TrackBox *trak; GF_MPEGAudioSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (dsi) *dsi = NULL; if (dsi_size) *dsi_size = 0; if (ocfg) memset(ocfg, 0, sizeof(GF_OpusConfig)); @@ -544,10 +572,10 @@ u32 cfg_type; GF_SampleDescriptionBox *stsd; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; switch (cfg->type) { @@ -637,10 +665,10 @@ GF_MPEGAudioSampleEntryBox *a_entry; GF_MPEGVisualSampleEntryBox *v_entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !param || !DescriptionIndex) return GF_BAD_PARAM; cfg = NULL; @@ -677,10 +705,10 @@ u32 dataRefIndex; GF_MPEGAudioSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref @@ -721,10 +749,10 @@ GF_Err e; GF_MPEGAudioSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media || !cfg || !sampleDescriptionIndex) return GF_BAD_PARAM; if (!the_file->keep_utc) @@ -744,6 +772,70 @@ } GF_EXPORT +GF_Err gf_isom_ac4_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AC4Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) +{ + GF_TrackBox *trak; + GF_Err e; + u32 dataRefIndex; + GF_MPEGAudioSampleEntryBox *entry; + + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; + + //get or create the data ref + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + if (!dataRefIndex) { + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + } + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + entry = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AC4); + if (!entry) return GF_OUT_OF_MEM; + entry->cfg_ac4 = (GF_AC4ConfigBox *) gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_DAC4); + + if (!entry->cfg_ac4) { + gf_isom_box_del((GF_Box *) entry); + return GF_OUT_OF_MEM; + } + gf_odf_ac4_cfg_deep_copy(&entry->cfg_ac4->cfg, cfg); + entry->samplerate_hi = trak->Media->mediaHeader->timeScale; + entry->dataReferenceIndex = dataRefIndex; + e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, entry); + *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); + return e; +} + +GF_EXPORT +GF_Err gf_isom_ac4_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AC4Config *cfg) +{ + GF_TrackBox *trak; + GF_Err e; + GF_MPEGAudioSampleEntryBox *entry; + + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media || !cfg || !sampleDescriptionIndex) return GF_BAD_PARAM; + + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + entry = gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescriptionIndex-1); + if (!entry) return GF_BAD_PARAM; + if (!entry->cfg_ac4) return GF_BAD_PARAM; + + gf_odf_ac4_cfg_deep_copy(&entry->cfg_ac4->cfg, cfg); + return GF_OK; +} + +GF_EXPORT GF_Err gf_isom_flac_config_new(GF_ISOFile *the_file, u32 trackNumber, u8 *metadata, u32 metadata_size, const char *URLname, const char *URNname, u32 *outDescriptionIndex) { GF_TrackBox *trak; @@ -751,10 +843,10 @@ u32 dataRefIndex; GF_MPEGAudioSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; //get or create the data ref @@ -776,8 +868,11 @@ return GF_OUT_OF_MEM; } entry->cfg_flac->dataSize = metadata_size; - entry->cfg_flac->data = gf_malloc(sizeof(u8)*metadata_size); - memcpy(entry->cfg_flac->data, metadata, sizeof(u8)*metadata_size); + entry->cfg_flac->data = NULL; + if (metadata && metadata_size) { + entry->cfg_flac->data = gf_malloc(sizeof(u8)*metadata_size); + memcpy(entry->cfg_flac->data, metadata, sizeof(u8)*metadata_size); + } entry->samplerate_hi = trak->Media->mediaHeader->timeScale; entry->dataReferenceIndex = dataRefIndex; e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, entry); @@ -794,10 +889,10 @@ GF_Err e; u32 dataRefIndex=0; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; //get or create the data ref @@ -837,7 +932,7 @@ { GF_DIMSSampleEntryBox *dims; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !descriptionIndex || !desc) return GF_BAD_PARAM; dims = (GF_DIMSSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, descriptionIndex-1); @@ -871,10 +966,10 @@ u32 dataRefIndex; GF_DIMSSampleEntryBox *dims; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; if (trak->Media->handler->handlerType != GF_ISOM_MEDIA_SCENE) return GF_BAD_PARAM; @@ -923,10 +1018,10 @@ GF_Err e; GF_DIMSSampleEntryBox *dims; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !desc || !DescriptionIndex) return GF_BAD_PARAM; dims = (GF_DIMSSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); @@ -975,7 +1070,7 @@ GF_Err gf_isom_get_udts_config(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, GF_UDTSConfig *cfg) { GF_Box *sample_entry; - GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); + GF_TrackBox *trak = gf_isom_get_track_box(movie, trackNumber); GF_UDTSSpecificBox *udts = NULL; if (!trak || !descriptionIndex || !cfg) return GF_BAD_PARAM; @@ -1002,7 +1097,7 @@ GF_Err LSR_UpdateESD(GF_LASeRSampleEntryBox *lsr, GF_ESD *esd) { - GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)lsr, GF_TRUE); + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate_box((GF_SampleEntryBox *)lsr, GF_TRUE); if (lsr->descr) gf_isom_box_del_parent(&lsr->child_boxes, (GF_Box *) lsr->descr); lsr->descr = NULL; @@ -1076,7 +1171,7 @@ if (_namespace) *_namespace = NULL; if (content_encoding) *content_encoding = NULL; if (schema_loc) *schema_loc = NULL; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak || !sampleDescription) return GF_BAD_PARAM; ptr = (GF_MetaDataSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDescription-1); if (!ptr) return GF_BAD_PARAM; @@ -1089,6 +1184,7 @@ #ifndef GPAC_DISABLE_ISOM_WRITE +GF_EXPORT GF_Err gf_isom_new_xml_metadata_description(GF_ISOFile *movie, u32 trackNumber, const char *_namespace, const char *schema_loc, const char *content_encoding, u32 *outDescriptionIndex) @@ -1100,10 +1196,10 @@ char *URLname = NULL; char *URNname = NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !_namespace) return GF_BAD_PARAM; @@ -1152,12 +1248,12 @@ if (xmlnamespace) *xmlnamespace = NULL; if (xml_schema_loc) *xml_schema_loc = NULL; if (mimes) *mimes = NULL; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return GF_BAD_PARAM; entry = (GF_MetaDataSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); if (!entry) return GF_BAD_PARAM; - + if ((entry->type!=GF_ISOM_BOX_TYPE_STPP) && (entry->type!=GF_ISOM_BOX_TYPE_METX)) { return GF_BAD_PARAM; } @@ -1180,7 +1276,7 @@ GF_TrackBox *trak; GF_TextConfigBox *mime; GF_MetaDataSampleEntryBox *entry; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return NULL; entry = (GF_MetaDataSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -1202,10 +1298,10 @@ GF_TextConfigBox *mime; GF_MetaDataSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return GF_BAD_PARAM; entry = (GF_MetaDataSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -1238,10 +1334,10 @@ char *URLname = NULL; char *URNname = NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -1294,7 +1390,7 @@ if (mime) *mime = NULL; if (config) *config = NULL; if (encoding) *encoding = NULL; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !StreamDescriptionIndex) return GF_BAD_PARAM; entry = (GF_MetaDataSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex-1); @@ -1318,6 +1414,7 @@ } #ifndef GPAC_DISABLE_ISOM_WRITE +GF_EXPORT GF_Err gf_isom_new_stxt_description(GF_ISOFile *movie, u32 trackNumber, u32 type, const char *mime, const char *encoding, const char * config, u32 *outDescriptionIndex) @@ -1329,10 +1426,10 @@ char *URLname = NULL; char *URNname = NULL; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -1405,10 +1502,10 @@ GF_Err e; GF_MetaDataSampleEntryBox *sample_entry; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; sample_entry = (GF_MetaDataSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); @@ -1453,7 +1550,7 @@ if (!descriptionIndex) return NULL; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return NULL; switch (trak->Media->handler->handlerType) { @@ -1504,10 +1601,10 @@ GF_WebVTTSampleEntryBox *wvtt; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -1536,7 +1633,7 @@ } #endif - +GF_EXPORT GF_Err gf_isom_new_webvtt_description(GF_ISOFile *movie, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, const char *config) { GF_TrackBox *trak; @@ -1544,10 +1641,10 @@ u32 dataRefIndex; GF_WebVTTSampleEntryBox *wvtt; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -1584,7 +1681,7 @@ #endif /*GPAC_DISABLE_VTT*/ #endif //GPAC_DISABLE_ISOM_WRITE -GF_BitRateBox *gf_isom_sample_entry_get_bitrate(GF_SampleEntryBox *ent, Bool create) +GF_BitRateBox *gf_isom_sample_entry_get_bitrate_box(GF_SampleEntryBox *ent, Bool create) { u32 i=0; GF_BitRateBox *a; @@ -1599,7 +1696,7 @@ #ifndef GPAC_DISABLE_ISOM_WRITE GF_EXPORT -GF_Err gf_isom_update_bitrate(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size) +GF_Err gf_isom_update_bitrate_ex(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size, Bool forced_for_mpeg4) { GF_BitRateBox *a; GF_Err e; @@ -1607,10 +1704,10 @@ u32 i, count; GF_TrackBox *trak; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; @@ -1653,12 +1750,13 @@ if (decode_buffer_size) esds->desc->decoderConfig->bufferSizeDB = decode_buffer_size; } - continue; + if (!forced_for_mpeg4) + continue; } - + //using BTRT if (!max_bitrate && average_bitrate) max_bitrate = average_bitrate; - a = gf_isom_sample_entry_get_bitrate(ent, max_bitrate ? GF_TRUE : GF_FALSE); + a = gf_isom_sample_entry_get_bitrate_box(ent, max_bitrate ? GF_TRUE : GF_FALSE); if (!max_bitrate) { if (a) { @@ -1674,6 +1772,13 @@ return GF_OK; } +GF_EXPORT +GF_Err gf_isom_update_bitrate(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size) +{ + + return gf_isom_update_bitrate_ex(movie, trackNumber, sampleDescriptionIndex, average_bitrate, max_bitrate, decode_buffer_size, GF_FALSE); + +} GF_EXPORT GF_Err gf_isom_tmcd_config_new(GF_ISOFile *the_file, u32 trackNumber, u32 fps_num, u32 fps_den, s32 frames_per_counter_tick, Bool is_drop, Bool is_counter, u32 *outDescriptionIndex) @@ -1686,10 +1791,10 @@ GF_TimeCodeMediaInformationBox *tcmi; GF_TimeCodeSampleEntryBox *entry; - e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(the_file, trackNumber); + trak = gf_isom_get_track_box(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; //get or create the data ref @@ -1730,13 +1835,47 @@ entry->timescale = fps_num; entry->frame_duration = fps_den; entry->frames_per_counter_tick = (u8) frames_per_counter_tick; - + entry->dataReferenceIndex = dataRefIndex; e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, entry); *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); return e; } +GF_EXPORT +GF_Err gf_isom_evte_config_new(GF_ISOFile *the_file, u32 trackNumber, u32 *outDescriptionIndex) +{ + GF_TrackBox *trak; + GF_Err e; + u32 dataRefIndex; + GF_EventMessageSampleEntryBox *entry; + + e = gf_isom_can_access_movie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak || !trak->Media) return GF_BAD_PARAM; + + //get or create the data ref + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, NULL, NULL, &dataRefIndex); + if (e) return e; + if (!dataRefIndex) { + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, NULL, NULL, &dataRefIndex); + if (e) return e; + } + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + entry = (GF_EventMessageSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_EVTE); + if (!entry) return GF_OUT_OF_MEM; + entry->dataReferenceIndex = dataRefIndex; + gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, entry); + if (outDescriptionIndex) *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); + + return e; +} + +GF_EXPORT GF_Err gf_isom_new_mpha_description(GF_ISOFile *movie, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u8 *dsi, u32 dsi_size, u32 mha_subtype) { GF_TrackBox *trak; @@ -1744,10 +1883,10 @@ u32 dataRefIndex; GF_MPEGAudioSampleEntryBox *mpa; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -1786,6 +1925,9 @@ if (outDescriptionIndex) *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); if (dsi) { + if (dsi_size <=5) + return GF_BAD_PARAM; + mpa->cfg_mha = (GF_MHAConfigBox *) gf_isom_box_new_parent(&mpa->child_boxes, GF_ISOM_BOX_TYPE_MHAC); if (!mpa->cfg_mha) return GF_OUT_OF_MEM; mpa->cfg_mha->configuration_version = dsi0; @@ -1794,6 +1936,10 @@ mpa->cfg_mha->mha_config_size = dsi3; mpa->cfg_mha->mha_config_size <<= 8; mpa->cfg_mha->mha_config_size |= dsi4; + + if (mpa->cfg_mha->mha_config_size < dsi_size-5) + return GF_BAD_PARAM; + mpa->cfg_mha->mha_config = gf_malloc(sizeof(u8) * mpa->cfg_mha->mha_config_size); if (!mpa->cfg_mha->mha_config) return GF_OUT_OF_MEM; memcpy(mpa->cfg_mha->mha_config, dsi+5, dsi_size-5); @@ -1808,7 +1954,7 @@ { GF_TimeCodeSampleEntryBox *tmcd; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !descriptionIndex) return GF_BAD_PARAM; tmcd = (GF_TimeCodeSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, descriptionIndex-1); @@ -1828,7 +1974,7 @@ GF_AudioSampleEntryBox *aent; GF_PCMConfigBox *pcmC; GF_TrackBox *trak; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !descriptionIndex) return GF_BAD_PARAM; aent = (GF_AudioSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, descriptionIndex-1); @@ -1871,7 +2017,7 @@ GF_AudioSampleEntryBox *aent; GF_TrackBox *trak; GF_BitStream *bs; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !descriptionIndex) return GF_BAD_PARAM; aent = (GF_AudioSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, descriptionIndex-1);
View file
gpac-2.4.0.tar.gz/src/isomedia/stbl_read.c -> gpac-26.02.0.tar.gz/src/isomedia/stbl_read.c
Changed
@@ -221,6 +221,8 @@ //no ent, this is really weird. Let's assume the DTS is then what is written in the table if (!ent || (i == count)) { + if (SampleNumber>stts->r_FirstSampleInEntry) + return GF_ISOM_INVALID_FILE; (*DTS) = stts->r_CurrentDTS; if (duration) *duration = ent ? ent->sampleDelta : 0; } @@ -339,7 +341,7 @@ *prevRAP = first_rap_in_entry; } *nextRAP = last_rap_in_entry; - + /*sample lies in this (rap) group, it is rap*/ if (is_rap_group) { if ((first_rap_in_entry <= SampleNumber) && (SampleNumber <= last_rap_in_entry)) { @@ -409,7 +411,7 @@ (*chunkNumber) = (*descIndex) = 0; if (out_ent) (*out_ent) = NULL; if (!stbl || !sampleNumber) return GF_BAD_PARAM; - if (!stbl->ChunkOffset || !stbl->SampleToChunk || !stbl->SampleSize) return GF_ISOM_INVALID_FILE; + if (!stbl->ChunkOffset || !stbl->SampleToChunk || !stbl->SampleSize || !stbl->SampleToChunk->entries) return GF_ISOM_INVALID_FILE; if (stbl->SampleSize && stbl->SampleToChunk->nb_entries == stbl->SampleSize->sampleCount) { ent = &stbl->SampleToChunk->entriessampleNumber-1; @@ -466,7 +468,7 @@ //check if sample is in current chunk u32 max_chunks_in_entry = stbl->SampleToChunk->ghostNumber - k; u32 nb_chunks_for_sample = sampleNumber - stbl->SampleToChunk->firstSampleInCurrentChunk; - if (ent->samplesPerChunk) + if (ent->samplesPerChunk) nb_chunks_for_sample /= ent->samplesPerChunk; if ( @@ -503,7 +505,7 @@ if (out_ent) *out_ent = ent; if (! *chunkNumber) return GF_ISOM_INVALID_FILE; - + //ok, get the size of all the previous samples in the chunk offsetInChunk = 0; //constant size
View file
gpac-2.4.0.tar.gz/src/isomedia/stbl_write.c -> gpac-26.02.0.tar.gz/src/isomedia/stbl_write.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -246,7 +246,11 @@ ctts->entriesctts->nb_entries.sampleCount = 1; ctts->nb_entries++; } - if (offset<0) ctts->version=1; + if (offset<0) { + ctts->version=1; + if (offset<ctts->min_neg_cts_offset) + ctts->min_neg_cts_offset = offset; + } ctts->w_LastSampleNumber++; return GF_OK; } @@ -295,7 +299,7 @@ } //NOPE we are inserting a sample... - CTSs = (u32*)gf_malloc(sizeof(u32) * (stbl->SampleSize->sampleCount+1) ); + GF_SAFE_ALLOC_N(CTSs, (stbl->SampleSize->sampleCount+1), u32); if (!CTSs) return GF_OUT_OF_MEM; sampNum = 0; for (i=0; i<ctts->nb_entries; i++) { @@ -980,7 +984,7 @@ gf_assert(ctts->unpack_mode); //if we're setting the CTS of a sample we've skipped... - if (ctts->w_LastSampleNumber < sampleNumber) { + if ((sampleNumber > ctts->nb_entries) && (ctts->w_LastSampleNumber < sampleNumber)) { //add some 0 till we get to the sample while (ctts->w_LastSampleNumber + 1 != sampleNumber) { GF_Err e = AddCompositionOffset(ctts, 0); @@ -1161,8 +1165,10 @@ if (nb_samples==1) { tot_samples = stbl->SampleSize->sampleCount - 1; - } else { + } else if (stbl->SampleSize->sampleCount >= nb_samples) { tot_samples = stbl->SampleSize->sampleCount - nb_samples; + } else { + tot_samples = 0; } if (tot_samples) { sampNum = 1; @@ -1201,14 +1207,15 @@ j++; stts->entriesj.sampleCount = 1; stts->entriesj.sampleDelta = (u32) (DTSsi+1 - DTSsi); - gf_assert(stts->entriesj.sampleDelta); + gf_assert(stts->entriesj.sampleDelta || !DTSsi+1); sampNum ++; } } stts->w_LastDTS = tot_samples ? DTSstot_samples - 1 : 0; gf_free(DTSs); gf_assert(sampNum == tot_samples); - gf_assert(sampNum + nb_samples == stbl->SampleSize->sampleCount); + + gf_assert(!tot_samples || (sampNum + nb_samples == stbl->SampleSize->sampleCount)); } //reset write the cache to the end @@ -1292,7 +1299,7 @@ if ((nb_samples>1) && (sampleNumber>1)) return GF_BAD_PARAM; - + //raw audio or constant sample size and dur if (stsc->nb_entries < stbl->SampleSize->sampleCount) { if (sampleNumber==stbl->SampleSize->sampleCount+1) { @@ -1680,19 +1687,21 @@ stsz->sampleSize = data_size; } else { u32 single_size; + Bool use_same_size=GF_TRUE; stsz->sizesstsz->sampleCount-1 += data_size; single_size = stsz->sizes0; for (i=1; i<stsz->sampleCount; i++) { if (stsz->sizesi != single_size) { - single_size = 0; + use_same_size = GF_FALSE; break; } } - if (single_size) { + if (use_same_size) { stsz->sampleSize = single_size; gf_free(stsz->sizes); stsz->sizes = NULL; + stsz->alloc_size = 0; } } return GF_OK; @@ -1776,7 +1785,7 @@ GF_ChunkOffsetBox *stco; GF_ChunkLargeOffsetBox *co64; u32 i; - + //we may have to convert the table... if (stbl->ChunkOffset->type==GF_ISOM_BOX_TYPE_STCO) { stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset; @@ -1986,6 +1995,9 @@ ctts->max_cts_delta = ABS(offset); //ctts->sample_num_max_cts_delta = ctts->w_LastSampleNumber; } + if (offset<ctts->min_neg_cts_offset) { + ctts->min_neg_cts_offset = offset; + } return GF_OK; } @@ -2289,7 +2301,7 @@ u32 i, size; GF_TrackBox *trak; GF_SampleSizeBox *stsz; - trak = gf_isom_get_track_from_file(file, trackNumber); + trak = gf_isom_get_track_box(file, trackNumber); if (!trak) return GF_BAD_PARAM; stsz = trak->Media->information->sampleTable->SampleSize;
View file
gpac-2.4.0.tar.gz/src/isomedia/track.c -> gpac-26.02.0.tar.gz/src/isomedia/track.c
Changed
@@ -100,7 +100,7 @@ e = Track_FindRef(trak, ref , &dpnd); if (e) return e; - if (dpnd) { + if (dpnd && dpnd->trackIDCount) { //ONLY ONE STREAM DEPENDENCY IS ALLOWED if (!k && (dpnd->trackIDCount != 1)) return GF_ISOM_INVALID_MEDIA; //fix the spec: where is the index located ?? @@ -377,17 +377,19 @@ GF_Err SetTrackDurationEx(GF_TrackBox *trak, Bool keep_utc) { - u64 trackDuration; + u64 trackDuration=0xFFFFFFFF; u32 i; - GF_Err e; + GF_Err e = GF_OK; //the total duration is the media duration: adjust it in case... - e = Media_SetDuration(trak); - if (e) return e; + if (!trak->extl) { + e = Media_SetDuration(trak); + if (e) return e; - //assert the timeScales are non-NULL - if (!trak->moov->mvhd || !trak->moov->mvhd->timeScale || !trak->Media->mediaHeader->timeScale) return GF_ISOM_INVALID_FILE; - trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale; + //assert the timeScales are non-NULL + if (!trak->moov->mvhd || !trak->moov->mvhd->timeScale || !trak->Media->mediaHeader->timeScale) return GF_ISOM_INVALID_FILE; + trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale; + } //if we have an edit list, the duration is the sum of all the editList //entries' duration (always expressed in MovieTimeScale) @@ -400,10 +402,10 @@ trackDuration += ent->segmentDuration; } } - if (!trackDuration) { + if (!trackDuration && trak->Media) { trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale; } - if (!trak->Header) { + if (!trak->Header || (trak->extl && (trackDuration==0xFFFFFFFF))) { return GF_OK; } trak->Header->duration = trackDuration; @@ -587,6 +589,16 @@ gf_list_del_item(traf->child_boxes, traf->tfrf); gf_list_add(trak->child_boxes, trak->tfrf); } + if (traf->SampleRefs) { + if (!trak->Media->information->sampleTable->SampleRefs) { + trak->Media->information->sampleTable->SampleRefs = traf->SampleRefs; + gf_list_add(trak->Media->information->sampleTable->child_boxes, traf->SampleRefs); + gf_list_del_item(traf->child_boxes, traf->SampleRefs); + traf->SampleRefs = NULL; + } else { + gf_list_transfer(trak->Media->information->sampleTable->SampleRefs->entries, traf->SampleRefs->entries); + } + } if (trak->moov->mov->signal_frag_bounds) { store_traf_map = GF_TRUE; @@ -614,6 +626,10 @@ gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->sdtp); traf_clone->sdtp = NULL; } + if (traf_clone->SampleRefs) { + gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->SampleRefs); + traf_clone->SampleRefs = NULL; + } } gf_isom_box_size((GF_Box *)moof_clone); bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -655,11 +671,19 @@ store_traf_map = GF_TRUE; } + u64 max_end = 0; #ifdef GF_ENABLE_CTRN sample_index = 0; #endif i=0; while ((trun = (GF_TrackFragmentRunBox *)gf_list_enum(traf->TrackRuns, &i))) { + if (! (trun->flags & (GF_ISOM_TRUN_DURATION | GF_ISOM_TRUN_SIZE | GF_ISOM_TRUN_FLAGS | GF_ISOM_TRUN_CTS_OFFSET) ) ) { + if (!def_size || (trun->sample_count>0x10000)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("iso file Invalid track run for track %d - default size %d num samples %d\n", traf->trex->trackID, def_size, trun->sample_count)); + return GF_ISOM_INVALID_FILE; + } + } + //merge the run for (j=0; j<trun->sample_count; j++) { GF_Err e; @@ -675,7 +699,7 @@ flags = def_flags; //CTS - if flag not set (trun or ctrn) defaults to 0 which is the base value after alloc - //we just need to overrite its value if inherited + //we just need to overwrite its value if inherited cts_offset = ent->CTS_Offset; #ifdef GF_ENABLE_CTRN @@ -853,6 +877,10 @@ e = stbl_AppendDependencyType(trak->Media->information->sampleTable, GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags)); if (e) return e; } + + u64 data_offset_end = data_offset + chunk_size; + if (!max_end || (max_end<data_offset_end)) + max_end = data_offset_end; } //remember target next dts - last_dts is the duration in media timescale, dos not include tfdt @@ -930,7 +958,7 @@ count ++; new_entry = GF_FALSE; - sgpd_del_entry(new_sgdesc->grouping_type, sgpd_entry); + sgpd_del_entry(new_sgdesc->grouping_type, sgpd_entry, new_sgdesc->is_opaque); break; } } @@ -1159,6 +1187,9 @@ e = gf_isom_cenc_merge_saiz_saio(senc, trak->Media->information->sampleTable, samp_num, offset, size); if (e) return e; + if (offset + size > max_end) + max_end = offset + size; + //we no longer load sai, this will be loaded through saio/saiz when fecthing it //this avoids too high mem usage //we do keep it if edit mode to rewrite senc @@ -1244,12 +1275,16 @@ if (nb_saio != 1) { u32 saio_idx = saio_get_index(traf, i); if (saio_idx>=saio->entry_count) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("isobmf Number of offset less than number of fragments, cannot merge SAI %s aux info type %d\n", gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("isobmf Number of offset less than number of fragments, cannot merge SAI %s aux info type %d\n", gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter)); break; } offset = saio->offsetsj + moof_offset; } size = saiz->default_sample_info_size ? saiz->default_sample_info_size : saiz->sample_info_sizej; + if (!size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("isobmf SAI of size 0 cannot be merged\n")); + continue; + } u64 cur_position = gf_bs_get_position(trak->moov->mov->movieFileMap->bs); gf_bs_seek(trak->moov->mov->movieFileMap->bs, offset); @@ -1270,9 +1305,18 @@ //always increment size even for saio.nb_entries>1 offset += size; + + if (offset > max_end) + max_end = offset; + } if (sai) gf_free(sai); } + //signal max offset from what we could gather - this is just an estimation, as there could be hidden data at the end of + //the containing mdat + if (trak->moov->mov->signal_frag_bounds && !(trak->moov->mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) { + gf_isom_push_mdat_end(trak->moov->mov, max_end, GF_TRUE); + } return GF_OK; } @@ -1632,14 +1676,32 @@ GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF); if (sinf && sinf->original_format) entry_type = sinf->original_format->data_format; + GF_MPEGSampleEntryBox *mpgs_entry = NULL; + entry_v = NULL; + entry_a = NULL; + switch (entry->internal_type) { + case GF_ISOM_SAMPLE_ENTRY_GENERIC: + break; + case GF_ISOM_SAMPLE_ENTRY_VIDEO: + entry_v = (GF_MPEGVisualSampleEntryBox*) entry; + break; + case GF_ISOM_SAMPLE_ENTRY_AUDIO: + entry_a = (GF_MPEGAudioSampleEntryBox*) entry; + break; + case GF_ISOM_SAMPLE_ENTRY_MP4S: + mpgs_entry = (GF_MPEGSampleEntryBox *) entry; + break; + } + switch (entry_type) { case GF_ISOM_BOX_TYPE_MP4S: + if (!mpgs_entry) return GF_ISOM_INVALID_FILE; //OK, delete the previous ESD gf_odf_desc_del((GF_Descriptor *) entry->esd->desc); entry->esd->desc = esd; break; case GF_ISOM_BOX_TYPE_MP4V: - entry_v = (GF_MPEGVisualSampleEntryBox*) entry; + if (!entry_v) return GF_ISOM_INVALID_MEDIA; if (entry_v->esd) { gf_odf_desc_del((GF_Descriptor *) entry_v->esd->desc); entry_v->esd->desc = esd; @@ -1648,7 +1710,7 @@ } break; case GF_ISOM_BOX_TYPE_MP4A: - entry_a = (GF_MPEGAudioSampleEntryBox*) entry; + if (!entry_a) return GF_ISOM_INVALID_MEDIA; if (entry_a->esd) { // some non-conformant files may not have an ESD ... //OK, delete the previous ESD gf_odf_desc_del((GF_Descriptor *) entry_a->esd->desc); @@ -1674,7 +1736,8 @@ case GF_ISOM_BOX_TYPE_HVT1: case GF_ISOM_BOX_TYPE_VVC1: case GF_ISOM_BOX_TYPE_VVI1: - e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry, esd); + if (!entry_v) return GF_ISOM_INVALID_MEDIA; + e = AVC_HEVC_UpdateESD(entry_v, esd); if (e) return e; break; case GF_ISOM_BOX_TYPE_LSR1: @@ -1684,6 +1747,8 @@ case GF_ISOM_BOX_TYPE_AV01: case GF_ISOM_BOX_TYPE_DAV1: case GF_ISOM_BOX_TYPE_AV1C: + case GF_ISOM_BOX_TYPE_AVS3: + case GF_ISOM_BOX_TYPE_AV3C: case GF_ISOM_BOX_TYPE_OPUS: case GF_ISOM_BOX_TYPE_DOPS: case GF_ISOM_BOX_TYPE_STXT: @@ -1748,6 +1813,13 @@ if (!opus->cfg_opus) return GF_OUT_OF_MEM; entry = (GF_MPEGSampleEntryBox*)opus; gf_odf_desc_del((GF_Descriptor *) esd); + } else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_IAMF) { + GF_MPEGAudioSampleEntryBox *iamf = (GF_MPEGAudioSampleEntryBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_IAMF); + if (!iamf) return GF_OUT_OF_MEM; + iamf->cfg_iamf = (GF_IAConfigurationBox *)gf_isom_box_new_parent(&iamf->child_boxes, GF_ISOM_BOX_TYPE_IACB); + if (!iamf->cfg_iamf) return GF_OUT_OF_MEM; + entry = (GF_MPEGSampleEntryBox*)iamf; + gf_odf_desc_del((GF_Descriptor *) esd); } else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AC3) { GF_MPEGAudioSampleEntryBox *ac3 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AC3); if (!ac3) return GF_OUT_OF_MEM; @@ -1762,6 +1834,13 @@ if (!eac3->cfg_ac3) return GF_OUT_OF_MEM; entry = (GF_MPEGSampleEntryBox*) eac3; gf_odf_desc_del((GF_Descriptor *) esd); + } else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AC4) { + GF_MPEGAudioSampleEntryBox *ac4 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AC4); + if (!ac4) return GF_OUT_OF_MEM; + ac4->cfg_ac4 = (GF_AC4ConfigBox *) gf_isom_box_new_parent(&ac4->child_boxes, GF_ISOM_BOX_TYPE_DAC4); + if (!ac4->cfg_ac4) return GF_OUT_OF_MEM; + entry = (GF_MPEGSampleEntryBox*) ac4; + gf_odf_desc_del((GF_Descriptor *) esd); } else { entry_a = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4A); if (!entry_a) return GF_OUT_OF_MEM;
View file
gpac-2.4.0.tar.gz/src/isomedia/tx3g.c -> gpac-26.02.0.tar.gz/src/isomedia/tx3g.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -54,7 +54,7 @@ GF_TextSampleEntryBox *qt_txt = NULL; if (!descriptionIndex || !out_desc) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -135,10 +135,10 @@ GF_Tx3gSampleEntryBox *txt; if (!descriptionIndex || !desc) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !desc->font_count) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -201,10 +201,10 @@ u32 dataRefIndex, i; GF_Tx3gSampleEntryBox *txt; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !desc) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -522,6 +522,9 @@ gf_isom_box_size((GF_Box *)samp->wrap); size += (u32) samp->wrap->size; } + if (samp->is_forced) { + size += 8; + } i=0; while ((a = (GF_Box*)gf_list_enum(samp->others, &i))) { gf_isom_box_size((GF_Box *)a); @@ -550,10 +553,10 @@ *outDescIdx = 0; if (!desc) return GF_BAD_PARAM; - e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); + e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); if (e) return GF_BAD_PARAM; - trak = gf_isom_get_track_from_file(movie, trackNumber); + trak = gf_isom_get_track_box(movie, trackNumber); if (!trak || !trak->Media || !desc->font_count) return GF_BAD_PARAM; switch (trak->Media->handler->handlerType) { @@ -952,7 +955,7 @@ *tx3g = NULL; *tx3g_size = 0; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; a = (GF_Tx3gSampleEntryBox *) gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sidx-1); @@ -966,12 +969,13 @@ return GF_OK; } +GF_EXPORT GF_Err gf_isom_set_forced_text(GF_ISOFile *file, u32 track, u32 stsd_idx, u32 flags) { GF_TrackBox *trak; GF_Tx3gSampleEntryBox *a; - trak = gf_isom_get_track_from_file(file, track); + trak = gf_isom_get_track_box(file, track); if (!trak) return GF_BAD_PARAM; a = (GF_Tx3gSampleEntryBox *) gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, stsd_idx-1);
View file
gpac-2.4.0.tar.gz/src/jsmods/WebGLRenderingContextBase.c -> gpac-26.02.0.tar.gz/src/jsmods/WebGLRenderingContextBase.c
Changed
@@ -496,6 +496,8 @@ JS_FreeValue(ctx, glo->obj); glo->obj = JS_UNDEFINED; gf_list_del_item(glo->par_ctx->all_objects, glo); + gf_free(glo); + JS_SetOpaque(argv0, NULL); } } return ret_val_js; @@ -516,6 +518,8 @@ JS_FreeValue(ctx, glo->obj); glo->obj = JS_UNDEFINED; gf_list_del_item(glo->par_ctx->all_objects, glo); + gf_free(glo); + JS_SetOpaque(argv0, NULL); } } return ret_val_js; @@ -536,6 +540,8 @@ JS_FreeValue(ctx, glo->obj); glo->obj = JS_UNDEFINED; gf_list_del_item(glo->par_ctx->all_objects, glo); + gf_free(glo); + JS_SetOpaque(argv0, NULL); } } return ret_val_js; @@ -556,6 +562,8 @@ JS_FreeValue(ctx, glo->obj); glo->obj = JS_UNDEFINED; gf_list_del_item(glo->par_ctx->all_objects, glo); + gf_free(glo); + JS_SetOpaque(argv0, NULL); } } return ret_val_js; @@ -576,6 +584,8 @@ JS_FreeValue(ctx, glo->obj); glo->obj = JS_UNDEFINED; gf_list_del_item(glo->par_ctx->all_objects, glo); + gf_free(glo); + JS_SetOpaque(argv0, NULL); } } return ret_val_js; @@ -596,6 +606,8 @@ JS_FreeValue(ctx, glo->obj); glo->obj = JS_UNDEFINED; gf_list_del_item(glo->par_ctx->all_objects, glo); + gf_free(glo); + JS_SetOpaque(argv0, NULL); } } return ret_val_js;
View file
gpac-2.4.0.tar.gz/src/jsmods/core.c -> gpac-26.02.0.tar.gz/src/jsmods/core.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2007-2023 + * Copyright (c) Telecom ParisTech 2007-2026 * All rights reserved * * This file is part of GPAC / JavaScript libgpac Core bindings @@ -44,18 +44,42 @@ static void qjs_init_runtime_libc(JSRuntime *rt); static void qjs_uninit_runtime_libc(JSRuntime *rt); +extern void *user_log_cbk; +extern Bool gpac_use_logx; + typedef struct { JSRuntime *js_runtime; u32 nb_inst; - JSContext *ctx; GF_Mutex *mx; GF_List *allocated_contexts; + + //for logging + void *prev_user_log_cbk; + gf_log_cbk prev_log_cbk; + JSValue log_fun; + JSValue log_obj; + u32 js_log_buf_size; + //WARNING - libc mem, not gf_alloc + char *js_log_buf; + JSContext *log_ctx; + char *js_orig_logs; + } GF_JSRuntime; static GF_JSRuntime *js_rt = NULL; +typedef struct __js_sys_task { + JSValue fun; + JSValue _obj; + + u32 type; + JSContext *ctx; +} JS_Sys_Task; + +static void js_reset_logs(JSContext *ctx); + JSContext *gf_js_create_context() { JSContext *ctx; @@ -93,8 +117,31 @@ { if (!js_rt) return; + js_reset_logs(ctx); gf_js_call_gc(ctx); + RMT_WS* rmt = (RMT_WS*) gf_sys_get_rmtws(); + if (rmt) { + JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt); + if (task && task->type == RMT_CALLBACK_JS) { + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + gf_free(task); + } + } + + rmt = (RMT_WS*) gf_sys_get_userws(); + if (rmt) { + JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt); + if (task && task->type == RMT_CALLBACK_JS) { + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + gf_free(task); + } + } + gf_mx_p(js_rt->mx); gf_list_del_item(js_rt->allocated_contexts, ctx); JS_FreeContext(ctx); @@ -132,7 +179,7 @@ gf_js_lock(c, 0); } -Bool gs_js_context_is_valid(JSContext *ctx) +Bool gf_js_context_is_valid(JSContext *ctx) { if (gf_list_find(js_rt->allocated_contexts, ctx) < 0) return GF_FALSE; @@ -150,6 +197,11 @@ if (LockIt) { gf_mx_p(js_rt->mx); + //if this is the first time the thread grabs the mutex, update stack top + //do not do it if not first otherwise we would keep extending the current stack size + if (gf_mx_get_num_locks(js_rt->mx)==1) { + JS_UpdateStackTop(js_rt->js_runtime); + } } else { gf_mx_v(js_rt->mx); } @@ -172,18 +224,18 @@ } - +Bool is_js_log = GF_FALSE; static JSValue js_print_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 ltool, u32 error_type) { - int i=0; - Bool first=GF_TRUE; - s32 logl = GF_LOG_INFO; - Bool no_new_line = GF_FALSE; - JSValue v, g; - const char *c_logname=NULL; - const char *log_name = "JS"; + int i=0; + Bool first=GF_TRUE; + s32 logl = GF_LOG_INFO; + Bool no_new_line = GF_FALSE; + JSValue v, g; + const char *c_logname=NULL; + const char *log_name = "JS"; - if ((argc>1) && JS_IsNumber(argv0)) { + if ((argc>1) && JS_IsNumber(argv0)) { JS_ToInt32(ctx, &logl, argv0); i=1; } @@ -205,24 +257,26 @@ if (log_name) { #ifndef GPAC_DISABLE_LOG - GF_LOG(logl, ltool, ("%s ", log_name)); -#else - fprintf(stderr, "%s ", log_name); + if (!is_js_log) { + GF_LOG(logl, ltool, ("%s ", log_name)); + } else #endif + fprintf(stderr, "%s ", log_name); } if (error_type==2) { #ifndef GPAC_DISABLE_LOG - GF_LOG(logl, ltool, ("Throw ")); -#else - fprintf(stderr, "Throw "); + if (!is_js_log) { + GF_LOG(logl, ltool, ("Throw ")); + } else #endif + fprintf(stderr, "Throw "); } - for (; i < argc; i++) { + for (; i < argc; i++) { const char *str = JS_ToCString(ctx, argvi); - if (!str) return GF_JS_EXCEPTION(ctx); + if (!str) return GF_JS_EXCEPTION(ctx); - if (logl==-1) { + if (logl==-1) { gf_sys_format_help(stderr, GF_PRINTARG_HIGHLIGHT_FIRST, "%s\n", str); } else if (logl==-2) { gf_sys_format_help(stderr, 0, "%s\n", str); @@ -230,27 +284,30 @@ fprintf(stderr, "%s%s", (first) ? "" : " ", str); } else { #ifndef GPAC_DISABLE_LOG - GF_LOG(logl, ltool, ("%s%s", (first) ? "" : " ", str)); -#else - fprintf(stderr, "%s%s", (first) ? "" : " ", str); + if (!is_js_log) { + GF_LOG(logl, ltool, ("%s%s", (first) ? "" : " ", str)); + } else #endif + fprintf(stderr, "%s%s", (first) ? "" : " ", str); + if (JS_IsException(argvi)) { js_dump_error_exc(ctx, argvi); } } - if (i+1==argc) { + if (i+1==argc) { u32 len = (u32) strlen(str); if (len && (strlen-1=='\r')) no_new_line = GF_TRUE; } - JS_FreeCString(ctx, str); - first=GF_FALSE; - } - if (!no_new_line) { + JS_FreeCString(ctx, str); + first=GF_FALSE; + } + if (!no_new_line) { #ifndef GPAC_DISABLE_LOG - GF_LOG(logl, ltool, ("\n")); -#else - fprintf(stderr, "\n"); + if (!is_js_log) { + GF_LOG(logl, ltool, ("\n")); + } else #endif + fprintf(stderr, "\n"); } if (c_logname) JS_FreeCString(ctx, c_logname); return JS_UNDEFINED; @@ -262,33 +319,33 @@ void js_dump_error_exc(JSContext *ctx, const JSValue exception_val) { - Bool is_error; + Bool is_error; u32 err_type = 1; - is_error = JS_IsError(ctx, exception_val); - if (!is_error) err_type = 2; + is_error = JS_IsError(ctx, exception_val); + if (!is_error) err_type = 2; - js_print_ex(ctx, JS_NULL, 1, (JSValueConst *)&exception_val, GF_LOG_SCRIPT, err_type); + js_print_ex(ctx, JS_NULL, 1, (JSValueConst *)&exception_val, GF_LOG_SCRIPT, err_type); - if (is_error) { - JSValue val = JS_GetPropertyStr(ctx, exception_val, "stack"); - if (!JS_IsUndefined(val)) { + if (is_error) { + JSValue val = JS_GetPropertyStr(ctx, exception_val, "stack"); + if (!JS_IsUndefined(val)) { const char *stack = JS_ToCString(ctx, val); #ifndef GPAC_DISABLE_LOG GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("%s\n", stack) ); #else fprintf(stderr, "%s\n", stack); #endif - JS_FreeCString(ctx, stack); - } - JS_FreeValue(ctx, val); - } + JS_FreeCString(ctx, stack); + } + JS_FreeValue(ctx, val); + } } void js_dump_error(JSContext *ctx) { - JSValue exception_val = JS_GetException(ctx); + JSValue exception_val = JS_GetException(ctx); js_dump_error_exc(ctx, exception_val); - JS_FreeValue(ctx, exception_val); + JS_FreeValue(ctx, exception_val); } #ifdef GPAC_DISABLE_QJS_LIBC @@ -318,6 +375,7 @@ static JSClassID file_class_id = 0; static JSClassID fileio_class_id = 0; static JSClassID amix_class_id = 0; +static JSClassID core_class_id = 0; typedef struct { @@ -351,8 +409,8 @@ } JSClassDef bitstreamClass = { - "Bitstream", - .finalizer = js_bs_finalize, + "Bitstream", + .finalizer = js_bs_finalize, .gc_mark = js_bs_gc_mark }; @@ -807,14 +865,14 @@ } static const JSCFunctionListEntry bitstream_funcs = { - JS_CGETSET_MAGIC_DEF_ENUM("pos", js_bs_prop_get, js_bs_prop_set, JS_BS_POS), - JS_CGETSET_MAGIC_DEF_ENUM("size", js_bs_prop_get, NULL, JS_BS_SIZE), - JS_CGETSET_MAGIC_DEF_ENUM("bit_offset", js_bs_prop_get, NULL, JS_BS_BIT_OFFSET), - JS_CGETSET_MAGIC_DEF_ENUM("bit_pos", js_bs_prop_get, NULL, JS_BS_BIT_POS), - JS_CGETSET_MAGIC_DEF_ENUM("available", js_bs_prop_get, NULL, JS_BS_AVAILABLE), - JS_CGETSET_MAGIC_DEF_ENUM("bits_available", js_bs_prop_get, NULL, JS_BS_BITS_AVAILABLE), - JS_CGETSET_MAGIC_DEF_ENUM("refreshed_size", js_bs_prop_get, NULL, JS_BS_REFRESH_SIZE), - JS_CGETSET_MAGIC_DEF_ENUM("overflow", js_bs_prop_get, NULL, JS_BS_OVERFLOW), + JS_CGETSET_MAGIC_DEF_ENUM("pos", js_bs_prop_get, js_bs_prop_set, JS_BS_POS), + JS_CGETSET_MAGIC_DEF_ENUM("size", js_bs_prop_get, NULL, JS_BS_SIZE), + JS_CGETSET_MAGIC_DEF_ENUM("bit_offset", js_bs_prop_get, NULL, JS_BS_BIT_OFFSET), + JS_CGETSET_MAGIC_DEF_ENUM("bit_pos", js_bs_prop_get, NULL, JS_BS_BIT_POS), + JS_CGETSET_MAGIC_DEF_ENUM("available", js_bs_prop_get, NULL, JS_BS_AVAILABLE), + JS_CGETSET_MAGIC_DEF_ENUM("bits_available", js_bs_prop_get, NULL, JS_BS_BITS_AVAILABLE), + JS_CGETSET_MAGIC_DEF_ENUM("refreshed_size", js_bs_prop_get, NULL, JS_BS_REFRESH_SIZE), + JS_CGETSET_MAGIC_DEF_ENUM("overflow", js_bs_prop_get, NULL, JS_BS_OVERFLOW), JS_CFUNC_DEF("skip", 0, js_bs_skip_bytes), JS_CFUNC_DEF("is_align", 0, js_bs_is_align), @@ -916,6 +974,291 @@ return anobj; } +//// RMTClient js class //// + +static JSClassID js_sys_rmt_client_class_id; + + +static JSValue js_sys_enable_rmtws(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + Bool enable = GF_TRUE; + if (argc > 0 && JS_IsBool(argv0)) enable = JS_ToBool(ctx, argv0); + gf_sys_enable_rmtws(enable); + return JS_UNDEFINED; +} +static JSValue js_sys_enable_userws(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + Bool enable = GF_TRUE; + if (argc > 0 && JS_IsBool(argv0)) enable = JS_ToBool(ctx, argv0); + gf_sys_enable_userws(enable); + return JS_UNDEFINED; +} + +static void js_sys_rmt_client_finalizer(JSRuntime *rt, JSValue val) { + + RMT_ClientCtx* client = JS_GetOpaque(val, js_sys_rmt_client_class_id); + if (!client) return; + + JS_Sys_Task* task = gf_rmt_client_get_on_data_task(client); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d js_sys_rmt_client_finalizer client %p task %p\n", __FILE__, __LINE__, client, task)); + gf_rmt_client_set_on_data_cbk(client, NULL, NULL); + + JS_Sys_Task* deltask = gf_rmt_client_get_on_del_task(client); + gf_rmt_client_set_on_del_cbk(client, NULL, NULL); + + JS_SetOpaque(val, NULL); + + if (task && task->type == RMT_CALLBACK_JS) { + JS_FreeValue(task->ctx, task->fun); + JS_FreeValue(task->ctx, task->_obj); + gf_free(task); + } + + if (deltask && deltask->type == RMT_CALLBACK_JS) { + JS_FreeValue(deltask->ctx, deltask->fun); + JS_FreeValue(deltask->ctx, deltask->_obj); + gf_free(deltask); + } + +} + +static void js_sys_rmt_client_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { + + RMT_ClientCtx* client = JS_GetOpaque(val, js_sys_rmt_client_class_id); + if (!client) return; + + JS_Sys_Task* task = gf_rmt_client_get_on_data_task(client); + if (task && task->type == RMT_CALLBACK_JS) { + JS_MarkValue(rt, task->fun, mark_func); + JS_MarkValue(rt, task->_obj, mark_func); + } + + task = gf_rmt_client_get_on_del_task(client); + if (task && task->type == RMT_CALLBACK_JS) { + JS_MarkValue(rt, task->fun, mark_func); + JS_MarkValue(rt, task->_obj, mark_func); + } + +} + +static JSClassDef js_sys_rmt_client_class = { + "RMTClient", + .finalizer = js_sys_rmt_client_finalizer, + .gc_mark = js_sys_rmt_client_gc_mark +}; + +enum { + JS_SYS_RMT_CLIENT_ON_DATA, + JS_SYS_RMT_CLIENT_ON_CLOSE, + JS_SYS_RMT_CLIENT_PEER_ADDRESS +}; + + + +static void js_sys_rmt_run_task(JS_Sys_Task* task, JSValue arg) { + + if (!task || task->type != RMT_CALLBACK_JS ) + return; + + int argc = JS_IsUndefined(arg) ? 0 : 1; + + gf_js_lock(task->ctx, GF_TRUE); + + JSValue ret = JS_Call(task->ctx, task->fun, task->_obj, argc, &arg); + + if (JS_IsException(ret)) { + js_dump_error(task->ctx); + } + JS_FreeValue(task->ctx, ret); + JS_FreeValue(task->ctx, arg); + js_std_loop(task->ctx); + gf_js_lock(task->ctx, GF_FALSE); + +} + + +static void js_sys_rmt_on_del_client(void *udta) { + if (udta) { + JS_Sys_Task *task = udta; + if (task->type == RMT_CALLBACK_JS && task->ctx && !JS_IsUndefined(task->_obj)) { + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d deleting opaque obj from task %p\n", __FILE__, __LINE__,task)); + + if (JS_IsFunction(task->ctx, task->fun)) + js_sys_rmt_run_task(task, JS_UNDEFINED); + + js_sys_rmt_client_finalizer(NULL, task->_obj); + + } + } +} + + +static void js_sys_rmt_client_on_data(void *udta, const u8* payload, u64 size, Bool is_binary) { + JS_Sys_Task *task = udta; + if (!task || task->type != RMT_CALLBACK_JS) return; + + JSValue arg; + if (is_binary) { + arg = JS_NewArrayBufferCopy(task->ctx, payload, size); + } else { + arg = JS_NewStringLen(task->ctx, payload, size); + } + + js_sys_rmt_run_task(task, arg); + +} + +static JSValue js_sys_rmt_client_prop_get(JSContext *ctx, JSValueConst this_val, int magic) { + + RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id); + if (!client) + return JS_UNDEFINED; + + switch (magic) { + case JS_SYS_RMT_CLIENT_PEER_ADDRESS:; + const char* peer_address = gf_rmt_get_peer_address(client); + if (peer_address) { + return JS_NewString(ctx, peer_address); + } + break; + } + return JS_UNDEFINED; +} + +static JSValue js_sys_rmt_client_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) { + + RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id); + if (!client) + return GF_JS_EXCEPTION(ctx); + + switch (magic) { + case JS_SYS_RMT_CLIENT_ON_DATA:; + + JS_Sys_Task* oldtask = (JS_Sys_Task*) gf_rmt_client_get_on_data_task(client); + if (oldtask && oldtask->type == RMT_CALLBACK_JS) { + gf_rmt_client_set_on_data_cbk(client, NULL, NULL); + JS_FreeValue(ctx, oldtask->fun); + JS_FreeValue(ctx, oldtask->_obj); + gf_free(oldtask); + } + + if (JS_IsFunction(ctx, value)) { + + JS_Sys_Task *task; + GF_SAFEALLOC(task, JS_Sys_Task); + if (!task) return GF_JS_EXCEPTION(ctx); + + task->type = RMT_CALLBACK_JS; + task->ctx = ctx; + task->fun = JS_DupValue(ctx, value); + task->_obj = JS_DupValue(ctx, this_val); + + gf_rmt_client_set_on_data_cbk(client, task, js_sys_rmt_client_on_data); + } + + + + break; + + case JS_SYS_RMT_CLIENT_ON_CLOSE: + + if (JS_IsUndefined(value) || JS_IsNull(value)) { + + JS_Sys_Task *task = gf_rmt_client_get_on_del_task(client); + if (task && task->type == RMT_CALLBACK_JS) { + // reset the js function but keep the ref to the client for on_delete + JS_FreeValue(ctx, task->fun); + task->fun = JS_UNDEFINED; + } + + } + + if (JS_IsFunction(ctx, value)) { + + JS_Sys_Task *task = gf_rmt_client_get_on_del_task(client); + if (task && task->type == RMT_CALLBACK_JS) { + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + } + else + GF_SAFEALLOC(task, JS_Sys_Task); + + if (!task) return GF_JS_EXCEPTION(ctx); + + task->type = RMT_CALLBACK_JS; + task->ctx = ctx; + task->fun = JS_DupValue(ctx, value); + task->_obj = JS_DupValue(ctx, this_val); + + gf_rmt_client_set_on_del_cbk(client, task, js_sys_rmt_on_del_client); + } + + + + break; + } + return JS_UNDEFINED; +} + +static JSValue js_sys_rmt_client_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + + RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id); + if (!client) + return GF_JS_EXCEPTION(ctx); + + if (!argc) + return JS_UNDEFINED; + + if (JS_IsString(argv0)) { + const char *msg = JS_ToCString(ctx, argv0); + GF_Err e = gf_rmt_client_send_to_ws(client, msg, strlen(msg), GF_FALSE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d sent msg <%s> to client <%p> returned <%d>\n", __FILE__, __LINE__, msg, client, e)); + JS_FreeCString(ctx, msg); + } + else if (JS_IsArrayBuffer(ctx, argv0)) { + u32 bufsize=0; + u8* buf = JS_GetArrayBuffer(ctx, (size_t*)&bufsize, argv0); + if (buf && bufsize) { + GF_Err e = gf_rmt_client_send_to_ws(client, buf, bufsize, GF_TRUE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d sent binary msg <%.*s> to client <%p> returned <%d>\n", __FILE__, __LINE__, bufsize, buf, client, e)); + } + + } + + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry js_sys_rmt_client_funcs = { + JS_CGETSET_MAGIC_DEF("on_data", NULL, js_sys_rmt_client_prop_set, JS_SYS_RMT_CLIENT_ON_DATA), + JS_CGETSET_MAGIC_DEF("on_close", NULL, js_sys_rmt_client_prop_set, JS_SYS_RMT_CLIENT_ON_CLOSE), + + JS_CGETSET_MAGIC_DEF("peer_address", js_sys_rmt_client_prop_get, NULL, JS_SYS_RMT_CLIENT_PEER_ADDRESS), + + JS_CFUNC_DEF("send", 1, js_sys_rmt_client_send) +}; + +static void js_sys_rmt_on_new_client(void *udta, void* new_client) { + JS_Sys_Task *task = udta; + if (!task) return; + + JSValue obj = JS_NewObjectClass(task->ctx, js_sys_rmt_client_class_id); + if (JS_IsException(obj)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("%s:%d obj JS_IsException\n", __FILE__, __LINE__)); + } + JS_SetOpaque(obj, new_client); + + JS_Sys_Task *deltask; + GF_SAFEALLOC(deltask, JS_Sys_Task); + deltask->type = RMT_CALLBACK_JS; + deltask->ctx = task->ctx; + deltask->_obj = JS_DupValue(task->ctx, obj); + + gf_rmt_client_set_on_del_cbk((RMT_ClientCtx*)new_client, (void*)deltask, js_sys_rmt_on_del_client); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d js_sys_rmt_on_new_client calling task %p with opaque %p\n", __FILE__, __LINE__, task, new_client)); + + js_sys_rmt_run_task(task, obj); +} + enum { JS_SYS_NB_CORES = 1, @@ -956,8 +1299,14 @@ JS_SYS_V_MAJOR, JS_SYS_V_MINOR, JS_SYS_V_MICRO, + JS_SYS_LOGX, + JS_SYS_RMT_ON_NEW_CLIENT, + JS_SYS_USERWS_ON_NEW_CLIENT, + JS_SYS_ON_LOG, }; + + #define RTI_REFRESH_MS 200 static JSValue js_sys_prop_get(JSContext *ctx, JSValueConst this_val, int magic) { @@ -1105,11 +1454,108 @@ return JS_NewInt32(ctx, GPAC_VERSION_MINOR ); case JS_SYS_V_MICRO: return JS_NewInt32(ctx, GPAC_VERSION_MICRO ); + case JS_SYS_LOGX: + return gpac_use_logx ? JS_TRUE : JS_FALSE; } return JS_UNDEFINED; } +static void js_reset_logs(JSContext *ctx) +{ + if (ctx != js_rt->log_ctx) return; + + gf_js_lock(ctx, GF_TRUE); + js_rt->js_log_buf_size = 0; + //WARNING - libc mem, not gf_alloc + if (js_rt->js_log_buf) free(js_rt->js_log_buf); + js_rt->js_log_buf = NULL; + + if (js_rt->js_orig_logs) { + gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE); + gf_free(js_rt->js_orig_logs); + js_rt->js_orig_logs = NULL; + } + + if (!js_rt->log_ctx) { + gf_js_lock(ctx, GF_FALSE); + return; + } + JS_FreeValue(js_rt->log_ctx, js_rt->log_fun); + js_rt->log_fun = JS_UNDEFINED; + JS_FreeValue(js_rt->log_ctx, js_rt->log_obj); + js_rt->log_obj = JS_UNDEFINED; + gf_log_set_callback(NULL, js_rt->prev_log_cbk); + js_rt->log_ctx = NULL; + gf_js_lock(ctx, GF_FALSE); +} + +void *gf_logs_get_thread_tag(u32 *tag_type, u32 *o_th_id); +JSValue gf_fs_get_script_data(JSContext *ctx, void *udta, u32 tag_type); + +extern GF_Mutex *logs_mx; +static void js_log_cbk(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist) +{ + JSContext *ctx = js_rt->log_ctx; + JSValue args5; + u32 nb_args = 3; + //in case mutex@debug is set, unlock logs mutex before locking JS runtime + //not doing so could create a deadlock while unlocking logs_mx or the JS runtime + gf_mx_v(logs_mx); + + //if context is locked by another thread and we cannot grab it, drop the log + //this typically happens when audio thread releases a packet (lock tasks_mx) and the main thread + //is querying through JS the filter stats which requires tasks_mx access + //usually only happens if mutex logs are on but we do the check for all tools + if (!gf_js_try_lock(ctx)) { + gf_mx_p(logs_mx); + return; + } + + args0 = JS_NewString(ctx, gf_log_tool_name(log_tool) ); + args1 = JS_NewInt32(ctx, log_level); + + va_list vlist_tmp; + va_copy(vlist_tmp, vlist); + u32 len = vsnprintf(NULL, 0, fmt, vlist_tmp); + va_end(vlist_tmp); + if (js_rt->js_log_buf_size < len+2) { + js_rt->js_log_buf_size = len+2; + //WARNING - libc mem, not gf_alloc + js_rt->js_log_buf = realloc(js_rt->js_log_buf, js_rt->js_log_buf_size); + } + vsprintf(js_rt->js_log_buf, fmt, vlist); + args2 = JS_NewString(ctx, js_rt->js_log_buf); + + if (gpac_use_logx) { + u32 tag_type=0, th_id=0; + void *udta = gf_logs_get_thread_tag(&tag_type, &th_id); + args3 = JS_NewInt32(ctx, th_id); + nb_args = 4; + if (udta) { + args4 = gf_fs_get_script_data(ctx, udta, tag_type); + if (! JS_IsNull( args4 )) { + nb_args = 5; + } + } + } + + JSValue res = JS_Call(ctx, js_rt->log_fun, js_rt->log_obj, nb_args, args); + JS_FreeValue(ctx, args0); + JS_FreeValue(ctx, args1); + JS_FreeValue(ctx, args2); + if (nb_args>=4) { + JS_FreeValue(ctx, args3); + if (nb_args==5) + JS_FreeValue(ctx, args4); + } + JS_FreeValue(ctx, res); + + gf_js_lock(ctx, GF_FALSE); + //and relock + gf_mx_p(logs_mx); +} + static JSValue js_sys_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) { const char *prop_val; @@ -1120,6 +1566,90 @@ gf_opts_set_key("core", "last-dir", prop_val); JS_FreeCString(ctx, prop_val); break; + + case JS_SYS_LOGX: + gpac_use_logx = JS_ToBool(ctx, value) ? GF_TRUE : GF_FALSE; + break; + case JS_SYS_RMT_ON_NEW_CLIENT: + case JS_SYS_USERWS_ON_NEW_CLIENT:; + + RMT_WS* rmt = NULL; + if (magic == JS_SYS_RMT_ON_NEW_CLIENT) rmt = (RMT_WS*) gf_sys_get_rmtws(); + if (magic == JS_SYS_USERWS_ON_NEW_CLIENT) rmt = (RMT_WS*) gf_sys_get_userws(); + if (!rmt) + break; + + if (JS_IsUndefined(value) || JS_IsNull(value)) { + + JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt); + if (task && task->type == RMT_CALLBACK_JS) { + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + gf_free(task); + } + return JS_UNDEFINED; + } + + if (JS_IsFunction(ctx, value)) { + JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt); + + if (task && task->type == RMT_CALLBACK_JS) { + //not allowed + if (magic == JS_SYS_RMT_ON_NEW_CLIENT) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("Attempting to redefine rmt_on_new_client ignored. Set it to null first to reset.\n", __FILE__, __LINE__)); + return JS_UNDEFINED; + } else { + gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL); + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + gf_free(task); + } + } + + GF_SAFEALLOC(task, JS_Sys_Task); + if (!task) return GF_JS_EXCEPTION(ctx); + + task->type = RMT_CALLBACK_JS; + task->ctx = ctx; + task->fun = JS_DupValue(ctx, value); + task->_obj = JS_DupValue(ctx, this_val); + + gf_rmt_set_on_new_client_cbk(rmt, task, js_sys_rmt_on_new_client); + } + break; + + case JS_SYS_ON_LOG: + //do not allow override of log call, only assign if non-null + if (!JS_IsUndefined(js_rt->log_fun) && !JS_IsNull(js_rt->log_fun)) { + if (JS_IsUndefined(value) || JS_IsNull(value)) { + return JS_UNDEFINED; + } + } + + JS_FreeValue(ctx, js_rt->log_fun); + js_rt->log_fun = JS_UNDEFINED; + JS_FreeValue(ctx, js_rt->log_obj); + js_rt->log_obj = JS_UNDEFINED; + js_rt->log_ctx = NULL; + is_js_log = GF_FALSE; + + if (JS_IsUndefined(value) || JS_IsNull(value)) { + if (js_rt->js_orig_logs) { + gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE); + gf_free(js_rt->js_orig_logs); + js_rt->js_orig_logs = NULL; + } + gf_log_set_callback(NULL, js_rt->prev_log_cbk); + } else if (JS_IsFunction(ctx, value)) { + js_rt->log_fun = JS_DupValue(ctx, value); + js_rt->log_obj = JS_DupValue(ctx, this_val); + js_rt->prev_user_log_cbk = user_log_cbk; + js_rt->prev_log_cbk = gf_log_set_callback(ctx, js_log_cbk); + js_rt->log_ctx = ctx; + is_js_log = GF_TRUE; + } + break; } return JS_UNDEFINED; } @@ -1286,7 +1816,7 @@ { char in_char2; - if (!gf_prompt_has_input()) + if (!gf_prompt_has_input()) return JS_NULL; in_char0 = gf_prompt_get_char(); in_char1 = 0; @@ -1298,7 +1828,7 @@ char input4096, *read; u32 len; //#ifdef GPAC_ENABLE_COVERAGE - if (argc) { + if (argc) { return JS_NewString(ctx, "Coverage OK"); } //#endif // GPAC_ENABLE_COVERAGE @@ -1308,8 +1838,8 @@ input4095=0; len = (u32) strlen(input); if (len && (inputlen-1 == '\n')) { - inputlen-1 = 0; - len--; + inputlen-1 = 0; + len--; } if (!len) return JS_NULL; return JS_NewString(ctx, input); @@ -1378,8 +1908,8 @@ static JSValue js_sys_gc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JS_RunGC(JS_GetRuntime(ctx)); - return JS_UNDEFINED; + JS_RunGC(JS_GetRuntime(ctx)); + return JS_UNDEFINED; } static JSValue js_sys_clock(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -1607,16 +2137,16 @@ /* load and evaluate a file */ static JSValue js_sys_load_script(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - const char *filename; - u8 *data=NULL; - u32 data_size; - JSValue res; - GF_Err e; + const char *filename; + u8 *data=NULL; + u32 data_size; + JSValue res; + GF_Err e; char *full_url = NULL; if (!argc || !JS_IsString(argv0)) return GF_JS_EXCEPTION(ctx); - filename = JS_ToCString(ctx, argv0); - if (!filename) return GF_JS_EXCEPTION(ctx); + filename = JS_ToCString(ctx, argv0); + if (!filename) return GF_JS_EXCEPTION(ctx); if ((argc>1) && JS_ToBool(ctx, argv1) ) { const char *par_url = jsf_get_script_filename(ctx); @@ -1637,13 +2167,13 @@ } else { res = JS_UNDEFINED; } - if (data) gf_free(data); + if (data) gf_free(data); - if (full_url) + if (full_url) gf_free(full_url); - else + else JS_FreeCString(ctx, filename); - return res; + return res; } @@ -1752,6 +2282,324 @@ return js_sys_rect_union_ex(ctx, this_val, argc, argv, GF_TRUE); } +#include <gpac/xml.h> +#include <gpac/mpd.h> +static JSValue js_sys_mpd_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + if (argc<1) return GF_JS_EXCEPTION(ctx); + size_t ab_size; + const char *data = JS_GetArrayBuffer(ctx, &ab_size, argv0); + if (!data || !gf_utf8_is_legal(data, (u32) ab_size)) { + return JS_NULL; + } + char *str = gf_malloc(sizeof(char)*(ab_size+1)); + memcpy(str, data, ab_size); + strab_size=0; + //HLS playlist + if (strstr(str, "#EXTM3U")) { + JSValue mpdo = JS_NewObject(ctx); + Bool is_master = GF_FALSE; + Bool is_live = GF_TRUE; + JSValue reps = JS_NewArray(ctx); + if (strstr(str, "#EXT-X-STREAM-INF")) { + is_master = GF_TRUE; + JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx, 1)); + //unused for HLS + JS_SetPropertyStr(ctx, mpdo, "ast", JS_NewInt64(ctx, 0) ); + //unknown until first variant is parsed ... + JS_SetPropertyStr(ctx, mpdo, "tsb", JS_NewInt32(ctx, 0) ); + JS_SetPropertyStr(ctx, mpdo, "live", JS_FALSE); + + JSValue periods = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, mpdo, "periods", periods); + JSValue po = JS_NewObject(ctx); + JS_SetPropertyUint32(ctx, periods, 0, po); + + JS_SetPropertyStr(ctx, po, "start", JS_NewInt64(ctx, 0) ); + JS_SetPropertyStr(ctx, po, "duration", JS_NewInt64(ctx, 0) ); + JS_SetPropertyStr(ctx, po, "ID", JS_NULL ); + + JS_SetPropertyStr(ctx, po, "reps", reps); + } else { + JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx,2)); + JS_SetPropertyStr(ctx, mpdo, "segments", reps); + } + Double target_dur=0; + Double cur_dur=0; + u32 media_seq = 0; + u32 vidx=0; + u32 ASID = 1; + u32 cur_bandwidth=0; + char *cur = str; + while (cur0) { + while (cur0 && strchr(" \n\t\r", cur0)) + cur++; + + char *sep = strchr(cur, '\n'); + if (cur0 != '#') { + if (sep) sep0 = 0; + JSValue var = JS_NewObject(ctx); + JS_SetPropertyUint32(ctx, reps, vidx, var); + if (is_master) { + JS_SetPropertyStr(ctx, var, "xlink", JS_NewString(ctx, cur) ); + JS_SetPropertyStr(ctx, var, "ID", JS_NewString(ctx, gf_file_basename(cur) ) ); + JS_SetPropertyStr(ctx, var, "AS_ID", JS_NewInt32(ctx, ASID) ); + JS_SetPropertyStr(ctx, var, "as_idx", JS_NewInt32(ctx, ASID) ); + JS_SetPropertyStr(ctx, var, "URLs", JS_NewArray(ctx) ); + JS_SetPropertyStr(ctx, var, "segments", JS_NewArray(ctx) ); + JS_SetPropertyStr(ctx, var, "template", JS_NULL ); + JS_SetPropertyStr(ctx, var, "timescale", JS_NewInt32(ctx, 1) ); + JS_SetPropertyStr(ctx, var, "duration", JS_NewInt32(ctx, 0) ); //not known yet + JS_SetPropertyStr(ctx, var, "bandwidth", JS_NewInt32(ctx, cur_bandwidth) ); + JS_SetPropertyStr(ctx, var, "live_seg_num", JS_NewInt64(ctx, 0) ); + } else { + JS_SetPropertyStr(ctx, var, "url", JS_NewString(ctx, cur) ); + if (!cur_dur) cur_dur = target_dur; + JS_SetPropertyStr(ctx, var, "duration", JS_NewFloat64(ctx, cur_dur) ); + cur_dur = 0; + } + vidx++; + if (!sep) break; + cur = sep+1; + continue; + } + if (strstr(cur, "EXT-X-ENDLIST")) is_live = GF_FALSE; + + //for variant, extract target duration and segment duration + if (!is_master) { + if (sep) sep0 = 0; + char *tdur = strstr(cur, "EXT-X-TARGETDURATION:"); + if (tdur) target_dur = atof(tdur+21); + + char *mseq = strstr(cur, "EXT-X-MEDIA-SEQUENCE:"); + if (mseq) media_seq = atoi(mseq+21); + + char *extinf = strstr(cur, "EXT-INF:"); + if (extinf) { + char *last_c = strchr(extinf, ','); + if (last_c) last_c0 = 0; + cur_dur = atof(extinf+8); + if (last_c) last_c0 = ','; + } + + if (!sep) break; + if (sep) sep0 = '\n'; + cur = sep+1; + continue; + } + //for master, extract URI ad bandwidth + if (sep) sep0 = 0; + + char *bw = strstr(cur, "BANDWIDTH="); + if (bw) { + bw += 10; + char *end = strchr(bw, ','); + if (end) end0 = 0; + cur_bandwidth = atoi(bw); + if (end) end0 = ','; + } + + char *uri = strstr(cur, "URI=\""); + if (uri) { + ASID++; + uri += 5; + char *end = strchr(uri, '\"'); + if (end) { + end0 = 0; + JSValue var = JS_NewObject(ctx); + JS_SetPropertyUint32(ctx, reps, vidx, var); + vidx++; + JS_SetPropertyStr(ctx, var, "xlink", JS_NewString(ctx, uri) ); + JS_SetPropertyStr(ctx, var, "ID", JS_NewString(ctx, gf_file_basename(uri) ) ); + JS_SetPropertyStr(ctx, var, "AS_ID", JS_NewInt32(ctx, ASID) ); + JS_SetPropertyStr(ctx, var, "as_idx", JS_NewInt32(ctx, ASID) ); + JS_SetPropertyStr(ctx, var, "URLs", JS_NewArray(ctx) ); + JS_SetPropertyStr(ctx, var, "segments", JS_NewArray(ctx) ); + JS_SetPropertyStr(ctx, var, "template", JS_NULL ); + JS_SetPropertyStr(ctx, var, "timescale", JS_NewInt32(ctx, 1) ); + JS_SetPropertyStr(ctx, var, "duration", JS_NewInt32(ctx, 0) ); //not known yet + JS_SetPropertyStr(ctx, var, "bandwidth", JS_NewInt32(ctx, 0) ); + JS_SetPropertyStr(ctx, var, "live_seg_num", JS_NewInt64(ctx, 0) ); + end0 = '\n'; + } + } + //do we need to extract codec info ? + if (!sep) break; + sep0 = '\n'; + cur = sep+1; + } + JS_SetPropertyStr(ctx, mpdo, "live", is_live ? JS_TRUE : JS_FALSE); + if (media_seq) { + JS_SetPropertyStr(ctx, mpdo, "seq_start", JS_NewInt32(ctx, media_seq) ); + JS_SetPropertyStr(ctx, mpdo, "live_seg_num", JS_NewInt32(ctx, media_seq+vidx) ); + } + if (!is_master) { + JS_SetPropertyStr(ctx, mpdo, "min_update", JS_NewInt64(ctx, (s32) (cur_dur * 1000) ) ); + } else { + JS_SetPropertyStr(ctx, mpdo, "min_update", JS_NewInt64(ctx, -1 ) ); + } + gf_free(str); + return mpdo; + } else if (!strstr(str, "<MPD ")) { + gf_free(str); + return JS_NULL; + } + GF_DOMParser *parser = gf_xml_dom_new(); + GF_Err e = gf_xml_dom_parse_string(parser, str); + if (e) { + gf_xml_dom_del(parser); + gf_free(str); + return JS_NULL; + } + GF_MPD *mpd = gf_mpd_new(); + e = gf_mpd_init_from_dom(gf_xml_dom_get_root(parser), mpd, NULL); + gf_free(str); + gf_xml_dom_del(parser); + if (e) { + gf_mpd_del(mpd); + return JS_NULL; + } + + u32 i, count = gf_list_count(mpd->periods); + JSValue mpdo = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx, 0)); + JS_SetPropertyStr(ctx, mpdo, "ast", JS_NewInt64(ctx, mpd->availabilityStartTime) ); + JS_SetPropertyStr(ctx, mpdo, "tsb", JS_NewInt32(ctx, mpd->time_shift_buffer_depth) ); + JS_SetPropertyStr(ctx, mpdo, "live", JS_NewBool(ctx, mpd->type==GF_MPD_TYPE_DYNAMIC) ); + JS_SetPropertyStr(ctx, mpdo, "min_update", JS_NewInt64(ctx, mpd->minimum_update_period) ); + + u64 now = gf_net_get_utc(); + JS_SetPropertyStr(ctx, mpdo, "live_utc", JS_NewInt64(ctx, now) ); + JSValue periods = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, mpdo, "periods", periods); + u32 period_idx=0; + u64 mpd_time = mpd->availabilityStartTime; + for (i=0; i<count; i++) { + u32 j, nb_sets, cur_rep=0; + GF_MPD_Period *p = gf_list_get(mpd->periods, i); + //closed period, don't add if end less than now + if (mpd->type && p->duration) { + //add 10s margin + if (mpd_time + p->start + p->duration + 10000 < now) continue; + //we could also supress period announcements ? + } + + JSValue po = JS_NewObject(ctx); + JS_SetPropertyUint32(ctx, periods, period_idx, po); + period_idx++; + + JS_SetPropertyStr(ctx, po, "start", JS_NewInt64(ctx, p->start) ); + JS_SetPropertyStr(ctx, po, "duration", JS_NewInt64(ctx, p->duration) ); + JS_SetPropertyStr(ctx, po, "ID", p->ID ? JS_NewString(ctx, p->ID) : JS_NULL ); + JSValue reps = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, po, "reps", reps ); + + u32 mpd_timescale = 0; + u64 seg_duration = 0; + GF_MPD_SegmentTimeline *mpd_stl = NULL; + if (p->segment_template) { + if (p->segment_template->duration) seg_duration = p->segment_template->duration; + if (p->segment_template->timescale) mpd_timescale = p->segment_template->timescale; + if (p->segment_template->segment_timeline) mpd_stl = p->segment_template->segment_timeline; + } + + nb_sets = gf_list_count(p->adaptation_sets); + for (j=0; j<nb_sets; j++) { + u32 k, nb_reps; + GF_MPD_AdaptationSet *set = gf_list_get(p->adaptation_sets, j); + + if (set->segment_template) { + if (set->segment_template->duration) seg_duration = set->segment_template->duration; + if (set->segment_template->timescale) mpd_timescale = set->segment_template->timescale; + if (set->segment_template->segment_timeline) mpd_stl = set->segment_template->segment_timeline; + } + + nb_reps = gf_list_count(set->representations); + for (k=0; k<nb_reps; k++) { + u64 start_range, end_range, segdur_ms; + u32 bidx; + GF_MPD_Representation *rep = gf_list_get(set->representations, k); + char *seg_url = NULL; + + if (rep->segment_template) { + if (rep->segment_template->duration) seg_duration = rep->segment_template->duration; + if (rep->segment_template->timescale) mpd_timescale = rep->segment_template->timescale; + if (rep->segment_template->segment_timeline) mpd_stl = rep->segment_template->segment_timeline; + } + JSValue repo = JS_NewObject(ctx); + JS_SetPropertyUint32(ctx, reps, cur_rep, repo); + cur_rep++; + + JS_SetPropertyStr(ctx, repo, "ID", rep->id ? JS_NewString(ctx, rep->id) : JS_NULL); + JS_SetPropertyStr(ctx, repo, "AS_ID", JS_NewInt32(ctx, set->id) ); + JS_SetPropertyStr(ctx, repo, "as_idx", JS_NewInt32(ctx, j+1) ); + JS_SetPropertyStr(ctx, repo, "xlink", JS_NULL); + JSValue urls_o = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, repo, "URLs", urls_o ); + u32 el_idx, js_idx=0; + for (el_idx=0;el_idx<4; el_idx++) { + GF_List *urls = rep->base_URLs; + if (el_idx==0) urls = mpd->base_URLs; + else if (el_idx==1) urls = p->base_URLs; + else if (el_idx==2) urls = set->base_URLs; + + for (bidx=0; bidx < gf_list_count(urls); bidx++) { + GF_MPD_BaseURL *burl = gf_list_get(urls, bidx); + if (burl->URL) { + JS_SetPropertyUint32(ctx, urls_o, js_idx, JS_NewString(ctx, burl->URL) ); + js_idx++; + } + } + } + JS_SetPropertyStr(ctx, repo, "timescale", JS_NewInt32(ctx, mpd_timescale ? mpd_timescale : 1) ); + JS_SetPropertyStr(ctx, repo, "duration", JS_NewInt64(ctx, seg_duration) ); + JS_SetPropertyStr(ctx, repo, "bandwidth", JS_NewInt32(ctx, rep->bandwidth) ); + + gf_mpd_resolve_url(mpd, rep, set, p, "./", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &seg_url, &start_range, &end_range, &segdur_ms, NULL, NULL, NULL, NULL, 0); + + JS_SetPropertyStr(ctx, repo, "template", seg_url ? JS_NewString(ctx, seg_url) : JS_NULL ); + if (seg_url) gf_free(seg_url); + + u64 seg_idx = 0; + if (seg_duration) { + seg_idx = now - p->start - mpd->availabilityStartTime; + seg_idx /= 1000; + if (mpd_timescale) seg_idx *= mpd_timescale; + seg_idx /= seg_duration; + } + JS_SetPropertyStr(ctx, repo, "live_seg_num", JS_NewInt64(ctx, seg_idx) ); + + JSValue segs = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, repo, "segments", segs ); + if (mpd_stl) { + u32 sidx, re, cur_seg=0; + //u64 start = mpd->availabilityStartTime + p->start; + for (sidx=0; sidx<gf_list_count(mpd_stl->entries); sidx++) { + GF_MPD_SegmentTimelineEntry *e = gf_list_get(mpd_stl->entries, sidx); + //if (e->start_time) start = e->start_time; + for (re=0; re<e->repeat_count+1; re++) { + JSValue sego = JS_NewObject(ctx); + gf_mpd_resolve_url(mpd, rep, set, p, "./", 0, GF_MPD_RESOLVE_URL_MEDIA, cur_seg, 0, &seg_url, &start_range, &end_range, &segdur_ms, NULL, NULL, NULL, NULL, 0); + + //move back to timescale + segdur_ms = gf_timestamp_rescale(segdur_ms, 1000, mpd_timescale ? mpd_timescale : 1); + JS_SetPropertyStr(ctx, sego, "url", JS_NewString(ctx, seg_url) ); + JS_SetPropertyStr(ctx, sego, "duration", JS_NewInt32(ctx, (u32) segdur_ms) ); + + JS_SetPropertyUint32(ctx, segs, cur_seg, sego ); + cur_seg++; + gf_free(seg_url); + } + } + } + } + } + } + gf_mpd_del(mpd); + return mpdo; +} + + enum { OPT_RMDIR, @@ -2066,6 +2914,45 @@ return JS_NewInt32(ctx, gf_audio_fmt_bit_depth(prop.value.uint)/8 ); } +static JSValue js_sys_set_logs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *logs; + Bool reset = GF_FALSE; + if (!argc || !JS_IsString(argv0)) return GF_JS_EXCEPTION(ctx); + if (argc>1) + reset = JS_ToBool(ctx, argv1); + + if (!js_rt->js_orig_logs) + js_rt->js_orig_logs = gf_log_get_tools_levels(); + + logs = JS_ToCString(ctx, argv0); + if (!logs) { + if (js_rt->js_orig_logs) { + gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE); + gf_free(js_rt->js_orig_logs); + js_rt->js_orig_logs = NULL; + } else { + gf_log_set_tools_levels("all@warning", GF_TRUE); + } + } else { + gf_log_set_tools_levels(logs, reset); + JS_FreeCString(ctx, logs); + } + return JS_UNDEFINED; +} + + +static JSValue js_sys_get_logs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + if (argc && JS_ToBool(ctx, argv0) && js_rt->js_orig_logs) { + return JS_NewString(ctx, js_rt->js_orig_logs); + } + char *logs = gf_log_get_tools_levels(); + JSValue res = JS_NewString(ctx, logs); + gf_free(logs); + return res; +} + #include <gpac/color.h> static JSValue js_color_lerp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -2423,8 +3310,8 @@ } JSClassDef amixClass = { - "FILE", - .finalizer = js_amix_finalize, + "FILE", + .finalizer = js_amix_finalize, }; static const JSCFunctionListEntry amix_funcs = { @@ -2498,21 +3385,21 @@ } static const JSCFunctionListEntry sys_funcs = { - JS_CGETSET_MAGIC_DEF_ENUM("nb_cores", js_sys_prop_get, NULL, JS_SYS_NB_CORES), - JS_CGETSET_MAGIC_DEF_ENUM("sampling_period_duration", js_sys_prop_get, NULL, JS_SYS_SAMPLE_DUR), - JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU), - JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU), - JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_DIFF), - JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_DIFF), - JS_CGETSET_MAGIC_DEF_ENUM("cpu_idle_time", js_sys_prop_get, NULL, JS_SYS_CPU_IDLE), - JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_usage", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_USAGE), - JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_usage", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_USAGE), - JS_CGETSET_MAGIC_DEF_ENUM("pid", js_sys_prop_get, NULL, JS_SYS_PID), - JS_CGETSET_MAGIC_DEF_ENUM("thread_count", js_sys_prop_get, NULL, JS_SYS_THREADS), - JS_CGETSET_MAGIC_DEF_ENUM("process_memory", js_sys_prop_get, NULL, JS_SYS_PROCESS_MEM), - JS_CGETSET_MAGIC_DEF_ENUM("physical_memory", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM), - JS_CGETSET_MAGIC_DEF_ENUM("physical_memory_avail", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM_AVAIL), - JS_CGETSET_MAGIC_DEF_ENUM("gpac_memory", js_sys_prop_get, NULL, JS_SYS_GPAC_MEM), + JS_CGETSET_MAGIC_DEF_ENUM("nb_cores", js_sys_prop_get, NULL, JS_SYS_NB_CORES), + JS_CGETSET_MAGIC_DEF_ENUM("sampling_period_duration", js_sys_prop_get, NULL, JS_SYS_SAMPLE_DUR), + JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU), + JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU), + JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_DIFF), + JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_DIFF), + JS_CGETSET_MAGIC_DEF_ENUM("cpu_idle_time", js_sys_prop_get, NULL, JS_SYS_CPU_IDLE), + JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_usage", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_USAGE), + JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_usage", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_USAGE), + JS_CGETSET_MAGIC_DEF_ENUM("pid", js_sys_prop_get, NULL, JS_SYS_PID), + JS_CGETSET_MAGIC_DEF_ENUM("thread_count", js_sys_prop_get, NULL, JS_SYS_THREADS), + JS_CGETSET_MAGIC_DEF_ENUM("process_memory", js_sys_prop_get, NULL, JS_SYS_PROCESS_MEM), + JS_CGETSET_MAGIC_DEF_ENUM("physical_memory", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM), + JS_CGETSET_MAGIC_DEF_ENUM("physical_memory_avail", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM_AVAIL), + JS_CGETSET_MAGIC_DEF_ENUM("gpac_memory", js_sys_prop_get, NULL, JS_SYS_GPAC_MEM), JS_CGETSET_MAGIC_DEF_ENUM("last_wdir", js_sys_prop_get, js_sys_prop_set, JS_SYS_LAST_WORK_DIR), JS_CGETSET_MAGIC_DEF_ENUM("batteryOn", js_sys_prop_get, NULL, JS_SYS_BATTERY_ON), @@ -2538,18 +3425,23 @@ JS_CGETSET_MAGIC_DEF_ENUM("version_major", js_sys_prop_get, NULL, JS_SYS_V_MAJOR), JS_CGETSET_MAGIC_DEF_ENUM("version_minor", js_sys_prop_get, NULL, JS_SYS_V_MINOR), JS_CGETSET_MAGIC_DEF_ENUM("version_micro", js_sys_prop_get, NULL, JS_SYS_V_MICRO), + JS_CGETSET_MAGIC_DEF_ENUM("use_logx", js_sys_prop_get, js_sys_prop_set, JS_SYS_LOGX), + + JS_CGETSET_MAGIC_DEF_ENUM("rmt_on_new_client", NULL, js_sys_prop_set, JS_SYS_RMT_ON_NEW_CLIENT), + JS_CGETSET_MAGIC_DEF_ENUM("userws_on_new_client", NULL, js_sys_prop_set, JS_SYS_USERWS_ON_NEW_CLIENT), + JS_CGETSET_MAGIC_DEF_ENUM("on_log", NULL, js_sys_prop_set, JS_SYS_ON_LOG), JS_CFUNC_DEF("set_arg_used", 0, js_sys_set_arg_used), JS_CFUNC_DEF("error_string", 0, js_sys_error_string), - JS_CFUNC_DEF("prompt_input", 0, js_sys_prompt_input), - JS_CFUNC_DEF("prompt_string", 0, js_sys_prompt_string), - JS_CFUNC_DEF("prompt_echo_off", 0, js_sys_prompt_echo_off), - JS_CFUNC_DEF("prompt_code", 0, js_sys_prompt_code), - JS_CFUNC_DEF("prompt_size", 0, js_sys_prompt_size), - - JS_CFUNC_DEF("keyname", 0, js_sys_keyname), - JS_CFUNC_DEF("get_event_type", 0, js_sys_evt_by_name), - JS_CFUNC_DEF("gc", 0, js_sys_gc), + JS_CFUNC_DEF("prompt_input", 0, js_sys_prompt_input), + JS_CFUNC_DEF("prompt_string", 0, js_sys_prompt_string), + JS_CFUNC_DEF("prompt_echo_off", 0, js_sys_prompt_echo_off), + JS_CFUNC_DEF("prompt_code", 0, js_sys_prompt_code), + JS_CFUNC_DEF("prompt_size", 0, js_sys_prompt_size), + + JS_CFUNC_DEF("keyname", 0, js_sys_keyname), + JS_CFUNC_DEF("get_event_type", 0, js_sys_evt_by_name), + JS_CFUNC_DEF("gc", 0, js_sys_gc), JS_CFUNC_DEF("enum_directory", 0, js_sys_enum_directory), JS_CFUNC_DEF("clock_ms", 0, js_sys_clock), @@ -2605,8 +3497,14 @@ JS_CFUNC_DEF("url_cat", 0, js_sys_url_cat), JS_CFUNC_DEF("rect_union", 0, js_sys_rect_union), JS_CFUNC_DEF("rect_intersect", 0, js_sys_rect_intersect), + JS_CFUNC_DEF("mpd_parse", 0, js_sys_mpd_parse), JS_CFUNC_DEF("_avmix_audio", 0, js_audio_mix), + + JS_CFUNC_DEF("enable_rmtws", 0, js_sys_enable_rmtws), + JS_CFUNC_DEF("enable_userws", 0, js_sys_enable_userws), + JS_CFUNC_DEF("set_logs", 0, js_sys_set_logs), + JS_CFUNC_DEF("get_logs", 0, js_sys_get_logs), }; @@ -2618,8 +3516,8 @@ } JSClassDef sha1Class = { - "SHA1", - .finalizer = js_sha1_finalize, + "SHA1", + .finalizer = js_sha1_finalize, }; static JSValue js_sha1_push(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -2682,8 +3580,8 @@ } JSClassDef fileClass = { - "FILE", - .finalizer = js_file_finalize, + "FILE", + .finalizer = js_file_finalize, }; enum @@ -2871,11 +3769,11 @@ } static const JSCFunctionListEntry file_funcs = { - JS_CGETSET_MAGIC_DEF_ENUM("pos", js_file_prop_get, js_file_prop_set, JS_FILE_POS), - JS_CGETSET_MAGIC_DEF_ENUM("eof", js_file_prop_get, NULL, JS_FILE_EOF), - JS_CGETSET_MAGIC_DEF_ENUM("error", js_file_prop_get, NULL, JS_FILE_ERROR), - JS_CGETSET_MAGIC_DEF_ENUM("size", js_file_prop_get, NULL, JS_FILE_SIZE), - JS_CGETSET_MAGIC_DEF_ENUM("gfio", js_file_prop_get, NULL, JS_FILE_IS_GFIO), + JS_CGETSET_MAGIC_DEF_ENUM("pos", js_file_prop_get, js_file_prop_set, JS_FILE_POS), + JS_CGETSET_MAGIC_DEF_ENUM("eof", js_file_prop_get, NULL, JS_FILE_EOF), + JS_CGETSET_MAGIC_DEF_ENUM("error", js_file_prop_get, NULL, JS_FILE_ERROR), + JS_CGETSET_MAGIC_DEF_ENUM("size", js_file_prop_get, NULL, JS_FILE_SIZE), + JS_CGETSET_MAGIC_DEF_ENUM("gfio", js_file_prop_get, NULL, JS_FILE_IS_GFIO), JS_CFUNC_DEF("flush", 0, js_file_flush), JS_CFUNC_DEF("close", 0, js_file_close), @@ -2979,8 +3877,8 @@ } JSClassDef fileioClass = { - "FILEIO", - .finalizer = js_fileio_finalize, + "FILEIO", + .finalizer = js_fileio_finalize, .gc_mark = js_fileio_gc_mark }; @@ -3064,9 +3962,9 @@ } static const JSCFunctionListEntry fileio_funcs = { - JS_CGETSET_MAGIC_DEF_ENUM("url", js_fileio_prop_get, NULL, JS_FILEIO_URL), - JS_CGETSET_MAGIC_DEF_ENUM("resource_url", js_fileio_prop_get, NULL, JS_FILEIO_RES_URL), - JS_CGETSET_MAGIC_DEF_ENUM("parent", js_fileio_prop_get, NULL, JS_FILEIO_PARENT), + JS_CGETSET_MAGIC_DEF_ENUM("url", js_fileio_prop_get, NULL, JS_FILEIO_URL), + JS_CGETSET_MAGIC_DEF_ENUM("resource_url", js_fileio_prop_get, NULL, JS_FILEIO_RES_URL), + JS_CGETSET_MAGIC_DEF_ENUM("parent", js_fileio_prop_get, NULL, JS_FILEIO_PARENT), JS_CFUNC_DEF("protect", 0, js_fileio_protect), JS_CFUNC_DEF("destroy", 0, js_fileio_destroy), }; @@ -3143,7 +4041,7 @@ JSValue res; s32 ret; JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio); - if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM; + if (!ioctx || !ioctx->gfio) return GF_FALSE; JSContext *ctx = ioctx->factory->ctx; gf_js_lock(ctx, GF_TRUE); res = JS_Call(ctx, ioctx->factory->eof, ioctx->js_obj, 0, NULL); @@ -3426,6 +4324,18 @@ return anobj; } +static void js_core_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func) +{ + if (js_rt->log_ctx) { + JS_MarkValue(rt, js_rt->log_fun, mark_func); + JS_MarkValue(rt, js_rt->log_obj, mark_func); + } +} +JSClassDef coreClass = { + "Core", + .gc_mark = js_core_gc_mark +}; + static int js_gpaccore_init(JSContext *ctx, JSModuleDef *m) { @@ -3445,21 +4355,25 @@ JS_NewClassID(&fileio_class_id); JS_NewClass(JS_GetRuntime(ctx), fileio_class_id, &fileioClass); + + JS_NewClassID(&core_class_id); + JS_NewClass(JS_GetRuntime(ctx), core_class_id, &coreClass); } - JSValue core_o = JS_NewObject(ctx); + //JSValue core_o = JS_NewObject(ctx); + JSValue core_o = JS_NewObjectClass(ctx, core_class_id); JS_SetPropertyFunctionList(ctx, core_o, sys_funcs, countof(sys_funcs)); - JS_SetModuleExport(ctx, m, "Sys", core_o); + JS_SetModuleExport(ctx, m, "Sys", core_o); JSValue args = JS_NewArray(ctx); - u32 i, nb_args = gf_sys_get_argc(); - for (i=0; i<nb_args; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, gf_sys_get_arg(i))); - } - JS_SetPropertyStr(ctx, core_o, "args", args); + u32 i, nb_args = gf_sys_get_argc(); + for (i=0; i<nb_args; i++) { + JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, gf_sys_get_arg(i))); + } + JS_SetPropertyStr(ctx, core_o, "args", args); #define DEF_CONST( _val ) \ - JS_SetPropertyStr(ctx, core_o, #_val, JS_NewInt32(ctx, _val)); + JS_SetPropertyStr(ctx, core_o, #_val, JS_NewInt32(ctx, _val)); DEF_CONST(GF_CONSOLE_RESET) DEF_CONST(GF_CONSOLE_RED) @@ -3488,35 +4402,42 @@ JS_SetPropertyFunctionList(ctx, proto, bitstream_funcs, countof(bitstream_funcs)); JS_SetClassProto(ctx, bitstream_class_id, proto); ctor = JS_NewCFunction2(ctx, bitstream_constructor, "Bitstream", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(ctx, m, "Bitstream", ctor); + JS_SetModuleExport(ctx, m, "Bitstream", ctor); //sha1 constructor proto = JS_NewObjectClass(ctx, sha1_class_id); JS_SetPropertyFunctionList(ctx, proto, sha1_funcs, countof(sha1_funcs)); JS_SetClassProto(ctx, sha1_class_id, proto); ctor = JS_NewCFunction2(ctx, sha1_constructor, "SHA1", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(ctx, m, "SHA1", ctor); + JS_SetModuleExport(ctx, m, "SHA1", ctor); //FILE constructor proto = JS_NewObjectClass(ctx, file_class_id); JS_SetPropertyFunctionList(ctx, proto, file_funcs, countof(file_funcs)); JS_SetClassProto(ctx, file_class_id, proto); ctor = JS_NewCFunction2(ctx, file_constructor, "File", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(ctx, m, "File", ctor); + JS_SetModuleExport(ctx, m, "File", ctor); //amix constructor proto = JS_NewObjectClass(ctx, amix_class_id); JS_SetPropertyFunctionList(ctx, proto, amix_funcs, countof(amix_funcs)); JS_SetClassProto(ctx, amix_class_id, proto); ctor = JS_NewCFunction2(ctx, amix_constructor, "AudioMixer", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(ctx, m, "AudioMixer", ctor); + JS_SetModuleExport(ctx, m, "AudioMixer", ctor); //FILEIO constructor proto = JS_NewObjectClass(ctx, fileio_class_id); JS_SetPropertyFunctionList(ctx, proto, fileio_funcs, countof(fileio_funcs)); JS_SetClassProto(ctx, fileio_class_id, proto); ctor = JS_NewCFunction2(ctx, fileio_constructor, "FileIO", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(ctx, m, "FileIO", ctor); + JS_SetModuleExport(ctx, m, "FileIO", ctor); + + //RMTClient constructor + JS_NewClassID(&js_sys_rmt_client_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_sys_rmt_client_class_id, &js_sys_rmt_client_class); + proto = JS_NewObjectClass(ctx, js_sys_rmt_client_class_id); + JS_SetPropertyFunctionList(ctx, proto, js_sys_rmt_client_funcs, countof(js_sys_rmt_client_funcs)); + JS_SetClassProto(ctx, js_sys_rmt_client_class_id, proto); return 0; } @@ -3549,16 +4470,16 @@ void qjs_module_init_qjs_libc(JSContext *ctx) { #ifdef CONFIG_BIGNUM - if (bignum_ext) { - JS_AddIntrinsicBigFloat(ctx); - JS_AddIntrinsicBigDecimal(ctx); - JS_AddIntrinsicOperators(ctx); - JS_EnableBignumExt(ctx, TRUE); - } -#endif - /* system modules */ - js_init_module_std(ctx, "std"); - js_init_module_os(ctx, "os"); + if (bignum_ext) { + JS_AddIntrinsicBigFloat(ctx); + JS_AddIntrinsicBigDecimal(ctx); + JS_AddIntrinsicOperators(ctx); + JS_EnableBignumExt(ctx, TRUE); + } +#endif + /* system modules */ + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); } #endif // GPAC_DISABLE_QJS_LIBC @@ -3659,7 +4580,10 @@ #endif // GPAC_STATIC_BIN -JSModuleDef *qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque) +JSModuleDef *create_json_module(JSContext *ctx, const char *module_name, JSValue val); +int js_module_test_json(JSContext *ctx, JSValueConst attributes); + +JSModuleDef *qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque, JSValueConst attributes) { JSModuleDef *m; const char *fext = gf_file_ext_start(module_name); @@ -3680,8 +4604,11 @@ const char *par_url = jsf_get_script_filename(ctx); url = gf_url_concatenate(par_url, module_name); + //depending on caller context, module_name may already be resolved against parent if (gf_file_exists(url ? url : module_name)) { e = gf_file_load_data(url ? url : module_name, &buf, &buf_len); + } else if (url && gf_file_exists(module_name)) { + e = gf_file_load_data(module_name, &buf, &buf_len); } else { e = GF_URL_ERROR; } @@ -3691,16 +4618,36 @@ JS_ThrowReferenceError(ctx, "could not load module filename '%s': %s", module_name, gf_error_to_string(e) ); return NULL; } - /* compile the module */ - func_val = JS_Eval(ctx, buf ? (char *) buf : "", buf_len, module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - gf_free(buf); - if (JS_IsException(func_val)) - return NULL; - /* XXX: could propagate the exception */ - js_module_set_import_meta(ctx, func_val, GF_TRUE, GF_FALSE); - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); + + char *fext = gf_file_ext_start(module_name); + int res = js_module_test_json(ctx, attributes); + if ((fext && !stricmp(fext, ".json")) || res > 0) { + /* compile as JSON or JSON5 depending on "type" */ + JSValue val; + int flags; + if (res == 2) + flags = JS_PARSE_JSON_EXT; + else + flags = 0; + val = JS_ParseJSON2(ctx, (char *)buf, buf_len, module_name, flags); + js_free(ctx, buf); + if (JS_IsException(val)) + return NULL; + m = create_json_module(ctx, module_name, val); + if (!m) + return NULL; + } else { + /* compile the module */ + func_val = JS_Eval(ctx, buf ? (char *) buf : "", buf_len, module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + gf_free(buf); + if (JS_IsException(func_val)) + return NULL; + /* XXX: could propagate the exception */ + js_module_set_import_meta(ctx, func_val, GF_TRUE, GF_FALSE); + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } } return m; } @@ -3711,7 +4658,7 @@ { JSContext *ctx = JS_NewContext(rt); if (!ctx) - return NULL; + return NULL; JSValue global_obj = JS_GetGlobalObject(ctx); js_load_constants(ctx, global_obj); @@ -3724,10 +4671,10 @@ void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque) { - if (!is_handled) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONSOLE, ("Possibly unhandled promise rejection: ")); - js_dump_error_exc(ctx, reason); - } + if (!is_handled) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONSOLE, ("Possibly unhandled promise rejection: ")); + js_dump_error_exc(ctx, reason); + } } #endif @@ -3736,18 +4683,18 @@ if (gf_opts_get_bool("core", "no-js-mods")) return; - /* module loader */ - JS_SetModuleLoaderFunc(rt, NULL, qjs_module_loader, NULL); + /* module loader */ + JS_SetModuleLoaderFunc2(rt, NULL, qjs_module_loader, NULL, NULL); #ifndef GPAC_DISABLE_QJS_LIBC - js_std_set_worker_new_context_func(JS_NewWorkerContext); - js_std_init_handlers(rt); + js_std_set_worker_new_context_func(JS_NewWorkerContext); + js_std_init_handlers(rt); - if (gf_opts_get_bool("core", "unhandled-rejection")) { - JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, NULL); - } + if (gf_opts_get_bool("core", "unhandled-rejection")) { + JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, NULL); + } #ifdef GPAC_ENABLE_COVERAGE if (gf_sys_is_cov_mode()) { js_promise_rejection_tracker(NULL, JS_NULL, JS_NULL, 1, NULL); @@ -3770,4 +4717,3 @@ } #endif -
View file
gpac-2.4.0.tar.gz/src/jsmods/evg.c -> gpac-26.02.0.tar.gz/src/jsmods/evg.c
Changed
@@ -181,15 +181,15 @@ //we separate VAI/MX from base value in order to be able to assign a VAI value union { - struct { + struct { EVG_VAI *vai; JSValue ref; } vai; - struct { + struct { EVG_VA *va; JSValue ref; } va; - struct { + struct { GF_Matrix *mx; JSValue ref; } mx; @@ -1566,8 +1566,8 @@ static Float evg_float_clamp(Float val, Float minval, Float maxval) { - _mm_store_ss( &val, _mm_min_ss( _mm_max_ss(_mm_set_ss(val),_mm_set_ss(minval)), _mm_set_ss(maxval) ) ); - return val; + _mm_store_ss( &val, _mm_min_ss( _mm_max_ss(_mm_set_ss(val),_mm_set_ss(minval)), _mm_set_ss(maxval) ) ); + return val; } #else @@ -5587,37 +5587,37 @@ void rgb2hsv(const u8 src_r, const u8 src_g, const u8 src_b, u8 *dst_h, u8 *dst_s, u8 *dst_v) { - float h, s, v; // h:0-360.0, s:0.0-1.0, v:0.0-1.0 - float r = src_r / 255.0f; - float g = src_g / 255.0f; - float b = src_b / 255.0f; - - float max = max_f(r, g, b); - float min = min_f(r, g, b); - - v = max; - if (max == 0.0f) { - s = 0; - h = 0; - } else if (max - min == 0.0f) { - s = 0; - h = 0; - } else { - s = (max - min) / max; - - if (max == r) { - h = 60 * ((g - b) / (max - min)) + 0; - } else if (max == g) { - h = 60 * ((b - r) / (max - min)) + 120; - } else { - h = 60 * ((r - g) / (max - min)) + 240; - } - } - if (h < 0) h += 360.0f; - - *dst_h = (u8)(h / 2); // dst_h : 0-180 - *dst_s = (u8)(s * 255); // dst_s : 0-255 - *dst_v = (u8)(v * 255); // dst_v : 0-255 + float h, s, v; // h:0-360.0, s:0.0-1.0, v:0.0-1.0 + float r = src_r / 255.0f; + float g = src_g / 255.0f; + float b = src_b / 255.0f; + + float max = max_f(r, g, b); + float min = min_f(r, g, b); + + v = max; + if (max == 0.0f) { + s = 0; + h = 0; + } else if (max - min == 0.0f) { + s = 0; + h = 0; + } else { + s = (max - min) / max; + + if (max == r) { + h = 60 * ((g - b) / (max - min)) + 0; + } else if (max == g) { + h = 60 * ((b - r) / (max - min)) + 120; + } else { + h = 60 * ((r - g) / (max - min)) + 240; + } + } + if (h < 0) h += 360.0f; + + *dst_h = (u8)(h / 2); // dst_h : 0-180 + *dst_s = (u8)(s * 255); // dst_s : 0-255 + *dst_v = (u8)(v * 255); // dst_v : 0-255 } void hsv2rgb(u8 src_h, u8 src_s, u8 src_v, u8 *dst_r, u8 *dst_g, u8 *dst_b) @@ -7841,48 +7841,48 @@ } proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, canvas_funcs, countof(canvas_funcs)); - JS_SetClassProto(c, canvas_class_id, proto); + JS_SetPropertyFunctionList(c, proto, canvas_funcs, countof(canvas_funcs)); + JS_SetClassProto(c, canvas_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, path_funcs, countof(path_funcs)); - JS_SetClassProto(c, path_class_id, proto); + JS_SetPropertyFunctionList(c, proto, path_funcs, countof(path_funcs)); + JS_SetClassProto(c, path_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, mx2d_funcs, countof(mx2d_funcs)); - JS_SetClassProto(c, mx2d_class_id, proto); + JS_SetPropertyFunctionList(c, proto, mx2d_funcs, countof(mx2d_funcs)); + JS_SetClassProto(c, mx2d_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, colmx_funcs, countof(colmx_funcs)); - JS_SetClassProto(c, colmx_class_id, proto); + JS_SetPropertyFunctionList(c, proto, colmx_funcs, countof(colmx_funcs)); + JS_SetClassProto(c, colmx_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, stencil_funcs, countof(stencil_funcs)); - JS_SetClassProto(c, stencil_class_id, proto); + JS_SetPropertyFunctionList(c, proto, stencil_funcs, countof(stencil_funcs)); + JS_SetClassProto(c, stencil_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, texture_funcs, countof(texture_funcs)); - JS_SetClassProto(c, texture_class_id, proto); + JS_SetPropertyFunctionList(c, proto, texture_funcs, countof(texture_funcs)); + JS_SetClassProto(c, texture_class_id, proto); #ifndef GPAC_DISABLE_FONTS proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, text_funcs, countof(text_funcs)); - JS_SetClassProto(c, text_class_id, proto); + JS_SetPropertyFunctionList(c, proto, text_funcs, countof(text_funcs)); + JS_SetClassProto(c, text_class_id, proto); #endif proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, mx_funcs, countof(mx_funcs)); - JS_SetClassProto(c, matrix_class_id, proto); + JS_SetPropertyFunctionList(c, proto, mx_funcs, countof(mx_funcs)); + JS_SetClassProto(c, matrix_class_id, proto); #ifdef EVG_USE_JS_SHADER proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, fragment_funcs, countof(fragment_funcs)); - JS_SetClassProto(c, fragment_class_id, proto); + JS_SetPropertyFunctionList(c, proto, fragment_funcs, countof(fragment_funcs)); + JS_SetClassProto(c, fragment_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, vertex_funcs, countof(vertex_funcs)); - JS_SetClassProto(c, vertex_class_id, proto); + JS_SetPropertyFunctionList(c, proto, vertex_funcs, countof(vertex_funcs)); + JS_SetClassProto(c, vertex_class_id, proto); proto = JS_NewObject(c); JS_SetPropertyFunctionList(c, proto, vaires_funcs, countof(vaires_funcs)); @@ -7890,21 +7890,21 @@ #endif proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, shader_funcs, countof(shader_funcs)); - JS_SetClassProto(c, shader_class_id, proto); + JS_SetPropertyFunctionList(c, proto, shader_funcs, countof(shader_funcs)); + JS_SetClassProto(c, shader_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, vai_funcs, countof(vai_funcs)); - JS_SetClassProto(c, vai_class_id, proto); + JS_SetPropertyFunctionList(c, proto, vai_funcs, countof(vai_funcs)); + JS_SetClassProto(c, vai_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, va_funcs, countof(va_funcs)); - JS_SetClassProto(c, va_class_id, proto); + JS_SetPropertyFunctionList(c, proto, va_funcs, countof(va_funcs)); + JS_SetClassProto(c, va_class_id, proto); #ifndef GPAC_DISABLE_3D proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, mesh_funcs, countof(mesh_funcs)); - JS_SetClassProto(c, mesh_class_id, proto); + JS_SetPropertyFunctionList(c, proto, mesh_funcs, countof(mesh_funcs)); + JS_SetClassProto(c, mesh_class_id, proto); #endif global = JS_GetGlobalObject(c); @@ -8027,44 +8027,44 @@ /*export constructors*/ ctor = JS_NewCFunction2(c, canvas_constructor, "Canvas", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Canvas", ctor); + JS_SetModuleExport(c, m, "Canvas", ctor); ctor = JS_NewCFunction2(c, path_constructor, "Path", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Path", ctor); + JS_SetModuleExport(c, m, "Path", ctor); ctor = JS_NewCFunction2(c, mx2d_constructor, "Matrix2D", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Matrix2D", ctor); + JS_SetModuleExport(c, m, "Matrix2D", ctor); ctor = JS_NewCFunction2(c, colmx_constructor, "ColorMatrix", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "ColorMatrix", ctor); + JS_SetModuleExport(c, m, "ColorMatrix", ctor); ctor = JS_NewCFunction2(c, solid_brush_constructor, "SolidBrush", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "SolidBrush", ctor); + JS_SetModuleExport(c, m, "SolidBrush", ctor); ctor = JS_NewCFunction2(c, linear_gradient_constructor, "LinearGradient", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "LinearGradient", ctor); + JS_SetModuleExport(c, m, "LinearGradient", ctor); ctor = JS_NewCFunction2(c, radial_gradient_constructor, "RadialGradient", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "RadialGradient", ctor); + JS_SetModuleExport(c, m, "RadialGradient", ctor); ctor = JS_NewCFunction2(c, texture_constructor, "Texture", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Texture", ctor); + JS_SetModuleExport(c, m, "Texture", ctor); #ifndef GPAC_DISABLE_FONTS ctor = JS_NewCFunction2(c, text_constructor, "Text", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Text", ctor); + JS_SetModuleExport(c, m, "Text", ctor); #endif ctor = JS_NewCFunction2(c, mx_constructor, "Matrix", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Matrix", ctor); + JS_SetModuleExport(c, m, "Matrix", ctor); ctor = JS_NewCFunction2(c, vai_constructor, "VertexAttribInterpolator", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "VertexAttribInterpolator", ctor); + JS_SetModuleExport(c, m, "VertexAttribInterpolator", ctor); ctor = JS_NewCFunction2(c, va_constructor, "VertexAttrib", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "VertexAttrib", ctor); + JS_SetModuleExport(c, m, "VertexAttrib", ctor); #ifndef GPAC_DISABLE_3D ctor = JS_NewCFunction2(c, mesh_constructor, "Mesh", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "Mesh", ctor); + JS_SetModuleExport(c, m, "Mesh", ctor); #endif ctor = JS_NewCFunction2(c, evg_pixel_size, "PixelSize", 1, JS_CFUNC_generic, 0); - JS_SetModuleExport(c, m, "PixelSize", ctor); + JS_SetModuleExport(c, m, "PixelSize", ctor); #ifdef GPAC_HAS_FFMPEG - JS_SetModuleExport(c, m, "BlitEnabled", JS_TRUE); + JS_SetModuleExport(c, m, "BlitEnabled", JS_TRUE); #else - JS_SetModuleExport(c, m, "BlitEnabled", JS_FALSE); + JS_SetModuleExport(c, m, "BlitEnabled", JS_FALSE); #endif return 0; } @@ -8086,11 +8086,11 @@ JS_AddModuleExport(ctx, m, "Text"); JS_AddModuleExport(ctx, m, "Matrix"); JS_AddModuleExport(ctx, m, "Mesh"); - JS_AddModuleExport(ctx, m, "VertexAttribInterpolator"); - JS_AddModuleExport(ctx, m, "VertexAttrib"); - JS_AddModuleExport(ctx, m, "PixelSize"); - JS_AddModuleExport(ctx, m, "BlitEnabled"); - return; + JS_AddModuleExport(ctx, m, "VertexAttribInterpolator"); + JS_AddModuleExport(ctx, m, "VertexAttrib"); + JS_AddModuleExport(ctx, m, "PixelSize"); + JS_AddModuleExport(ctx, m, "BlitEnabled"); + return; } #else // defined(GPAC_HAS_QJS) && !defined(GPAC_DISABLE_EVG)
View file
gpac-2.4.0.tar.gz/src/jsmods/scene_js.c -> gpac-26.02.0.tar.gz/src/jsmods/scene_js.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2007-2023 + * Copyright (c) Telecom ParisTech 2007-2025 * All rights reserved * * This file is part of GPAC / JavaScript Compositor extensions @@ -64,6 +64,7 @@ GF_List *event_queue; GF_Mutex *event_mx; + Bool owns_fs_api; } GF_SCENEJSExt; enum { @@ -176,21 +177,21 @@ static void scenejs_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { GF_SCENEJSExt *ext = JS_GetOpaque(val, scene_class_id); - if (ext) { + if (ext) { JS_MarkValue(rt, ext->evt_fun, mark_func); - } + } } JSClassDef sceneClass = { - "JSSCENE", - .finalizer = scenejs_finalize, - .gc_mark = scenejs_gc_mark + "JSSCENE", + .finalizer = scenejs_finalize, + .gc_mark = scenejs_gc_mark }; JSClassDef gpacEvtClass = { - "GPACEVT" + "GPACEVT" }; JSClassDef odmClass = { - "MediaObject" + "MediaObject" }; JSClassDef anyClass = { "GPACOBJECT" @@ -767,7 +768,7 @@ return JS_NewInt32(ctx, odi.max_bitrate); case GJS_OM_PROP_SERVICE_HANDLER: gf_odm_get_object_info(odm, &odi); - return JS_NewString(ctx, odi.service_handler ? odi.service_handler : "unloaded"); + return JS_NewString(ctx, odi.service_handler ? odi.service_handler : "unloaded"); case GJS_OM_PROP_CODEC: gf_odm_get_object_info(odm, &odi); return JS_NewString(ctx, odi.codec_name ? odi.codec_name : "unloaded"); @@ -825,15 +826,15 @@ return JS_NewInt32(ctx, (!odm->addon && odm->subscene) ? odm->subscene->selected_service_id : odm->parentscene->selected_service_id); break; case GJS_OM_PROP_BANDWIDTH_DOWN: - if (odm->scene_ns->source_filter) { + if (odm->scene_ns->source_filter) { JSValue ret; - GF_PropertyEntry *pe=NULL; - const GF_PropertyValue *prop = gf_filter_get_info(odm->scene_ns->source_filter, GF_PROP_PID_DOWN_RATE, &pe); - ret = JS_NewInt32(ctx, prop ? prop->value.uint/1000 : 0); - gf_filter_release_property(pe); - return ret; - } - return JS_NewInt32(ctx, 0); + GF_PropertyEntry *pe=NULL; + const GF_PropertyValue *prop = gf_filter_get_info(odm->scene_ns->source_filter, GF_PROP_PID_DOWN_RATE, &pe); + ret = JS_NewInt32(ctx, prop ? prop->value.uint/1000 : 0); + gf_filter_release_property(pe); + return ret; + } + return JS_NewInt32(ctx, 0); case GJS_OM_PROP_NB_HTTP: if (odm->scene_ns->source_filter) { @@ -917,9 +918,13 @@ case GJS_OM_PROP_SERVICE_NAME: if (odm->pid) { - const GF_PropertyValue *p = gf_filter_pid_get_property(odm->pid, GF_PROP_PID_SERVICE_NAME); + GF_PropertyEntry *pe=NULL; + JSValue ret = JS_NULL; + const GF_PropertyValue *p = gf_filter_pid_get_info(odm->pid, GF_PROP_PID_SERVICE_NAME, &pe); if (p && p->value.string) - return JS_NewString(ctx, p->value.string); + ret = JS_NewString(ctx, p->value.string); + gf_filter_release_property(pe); + return ret; } return JS_NULL; @@ -1025,6 +1030,7 @@ u32 sr=0, ch=0, w=0, h=0, bw=0, par_n=1, par_d=1, tile_adaptation_mode=0,dependent_group_index=0; Bool ilced=GF_FALSE, disabled=GF_FALSE, selected=GF_FALSE, automatic=GF_FALSE; Double fps=30.0; + u32 ssr = 0; s32 idx; s32 dep_idx=0; @@ -1095,6 +1101,9 @@ else if (!strncmp(qdesc, "sar=", 4)) { sscanf(qdesc, "sar=%d/%d", &par_n, &par_d); } + else if (!strncmp(qdesc, "ssr=", 4)) { + sscanf(qdesc, "ssr=%u", &ssr); + } if (!sep) break; sep0=':'; qdesc = sep+2; @@ -1139,6 +1148,7 @@ JS_SetPropertyStr(ctx, a, "channels", JS_NewInt32(ctx, ch)); JS_SetPropertyStr(ctx, a, "par_num", JS_NewInt32(ctx, par_n)); JS_SetPropertyStr(ctx, a, "par_den", JS_NewInt32(ctx, par_d)); + JS_SetPropertyStr(ctx, a, "ssr", JS_NewInt32(ctx, ssr)); JS_SetPropertyStr(ctx, a, "disabled", JS_NewBool(ctx, disabled)); JS_SetPropertyStr(ctx, a, "is_selected", JS_NewBool(ctx, selected)); JS_SetPropertyStr(ctx, a, "automatic", JS_NewBool(ctx, automatic)); @@ -1579,7 +1589,7 @@ evt_clone = gf_malloc(sizeof(GF_Event)); memcpy(evt_clone, evt, sizeof(GF_Event)); gf_list_add(sjs->event_queue, evt_clone); - GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("SCENEJS Couldn't lock % mutex, queing event\n", (lock_fail==2) ? "JavaScript" : "Compositor")); + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("SCENEJS Couldn't lock % mutex, queuing event\n", (lock_fail==2) ? "JavaScript" : "Compositor")); gf_mx_v(sjs->event_mx); if (lock_fail==2){ @@ -1865,13 +1875,13 @@ gf_list_del(sjs->event_queue); gf_mx_del(sjs->event_mx); - if (sjs->compositor && sjs->compositor->filter) { + if (sjs->owns_fs_api && sjs->compositor && sjs->compositor->filter) { gf_fs_unload_script(sjs->compositor->filter->session, NULL); } /*if we destroy the script context holding the gpac event filter (only one for the time being), remove the filter*/ JS_FreeValueRT(rt, sjs->evt_fun); if (sjs->evt_filter.udta) { - if (sjs->compositor) + if (sjs->owns_fs_api && sjs->compositor) gf_filter_remove_event_listener(sjs->compositor->filter, &sjs->evt_filter); sjs->evt_filter.udta = NULL; } @@ -1938,6 +1948,7 @@ //don't check error code, this may fail if global JS has been set but the script may still run if (gf_fs_load_js_api(c, fs) == GF_OK) { scene->attached_session = fs; + sjs->owns_fs_api = GF_TRUE; } } @@ -1988,7 +1999,7 @@ JS_FreeValue(c, global); - JS_SetModuleExport(c, m, "scene", sjs->scene_obj); + JS_SetModuleExport(c, m, "scene", sjs->scene_obj); return 0; }
View file
gpac-2.4.0.tar.gz/src/jsmods/webgl.c -> gpac-26.02.0.tar.gz/src/jsmods/webgl.c
Changed
@@ -874,9 +874,9 @@ strcpy(sname, name); if ((size > 1) && (j >= 1)) { char szIdx100; - sprintf(szIdx, "%d", j); - strcat(sname, szIdx); - } + sprintf(szIdx, "%d", j); + strcat(sname, szIdx); + } GLint loc = glGetUniformLocation(program_shader, sname); if (loc == location) { found = GF_TRUE; @@ -2504,17 +2504,17 @@ } proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, webgl_funcs, countof(webgl_funcs)); - JS_SetPropertyFunctionList(c, proto, WebGLRenderingContextBase_funcs, countof(WebGLRenderingContextBase_funcs)); - JS_SetClassProto(c, WebGLRenderingContextBase_class_id, proto); + JS_SetPropertyFunctionList(c, proto, webgl_funcs, countof(webgl_funcs)); + JS_SetPropertyFunctionList(c, proto, WebGLRenderingContextBase_funcs, countof(WebGLRenderingContextBase_funcs)); + JS_SetClassProto(c, WebGLRenderingContextBase_class_id, proto); proto = JS_NewObject(c); - JS_SetPropertyFunctionList(c, proto, webgl_named_tx_funcs, countof(webgl_named_tx_funcs)); - JS_SetClassProto(c, NamedTexture_class_id, proto); + JS_SetPropertyFunctionList(c, proto, webgl_named_tx_funcs, countof(webgl_named_tx_funcs)); + JS_SetClassProto(c, NamedTexture_class_id, proto); /*export constructors*/ ctor = JS_NewCFunction2(c, webgl_constructor, "WebGLContext", 1, JS_CFUNC_constructor, 0); - JS_SetModuleExport(c, m, "WebGLContext", ctor); + JS_SetModuleExport(c, m, "WebGLContext", ctor); return 0; }
View file
gpac-2.4.0.tar.gz/src/jsmods/xhr.c -> gpac-26.02.0.tar.gz/src/jsmods/xhr.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2007-2023 + * Copyright (c) Telecom ParisTech 2007-2025 * All rights reserved * * This file is part of GPAC / JavaScript XmlHttpRequest bindings @@ -125,6 +125,7 @@ XHR_ReadyState readyState; Bool async; + Bool in_send; /* GPAC extension to control the caching of XHR-downloaded resources */ XHR_CacheType cache; @@ -132,7 +133,6 @@ /*header/header-val, terminated by NULL*/ char **headers; u32 cur_header; - char **recv_headers; char *method, *url; GF_DownloadSession *sess; @@ -173,32 +173,6 @@ } #endif -static void xml_http_reset_recv_hdr(XMLHTTPContext *ctx) -{ - u32 nb_hdr = 0; - if (ctx->recv_headers) { - while (ctx->recv_headersnb_hdr) { - gf_free(ctx->recv_headersnb_hdr); - gf_free(ctx->recv_headersnb_hdr+1); - nb_hdr+=2; - } - gf_free(ctx->recv_headers); - ctx->recv_headers = NULL; - } -} - -static void xml_http_append_recv_header(XMLHTTPContext *ctx, const char *hdr, const char *val) -{ - u32 nb_hdr = 0; - if (ctx->recv_headers) { - while (ctx->recv_headersnb_hdr) nb_hdr+=2; - } - ctx->recv_headers = (char **)gf_realloc(ctx->recv_headers, sizeof(char*)*(nb_hdr+3)); - ctx->recv_headersnb_hdr = gf_strdup(hdr); - ctx->recv_headersnb_hdr+1 = gf_strdup(val ? val : ""); - ctx->recv_headersnb_hdr+2 = NULL; -} - static void xml_http_append_send_header(XMLHTTPContext *ctx, char *hdr, char *val) { if (!hdr) return; @@ -263,7 +237,6 @@ } } } - xml_http_append_recv_header(ctx, hdr, val); } static void xml_http_del_data(XMLHTTPContext *ctx) @@ -282,7 +255,6 @@ static void xml_http_reset_partial(XMLHTTPContext *ctx) { - xml_http_reset_recv_hdr(ctx); xml_http_del_data(ctx); if (ctx->mime) { gf_free(ctx->mime); @@ -292,11 +264,20 @@ gf_free(ctx->statusText); ctx->statusText = NULL; } + if (ctx->headers) { + u32 i=0; + while (ctx->headersi) { + gf_free(ctx->headersi); + i++; + } + gf_free(ctx->headers); + ctx->headers = NULL; + } ctx->cur_header = 0; ctx->html_status = 0; } -static void xml_http_reset(XMLHTTPContext *ctx) +static void xml_http_reset(XMLHTTPContext *ctx, Bool delete_conn) { if (ctx->method) { gf_free(ctx->method); @@ -309,7 +290,7 @@ xml_http_reset_partial(ctx); - if (ctx->sess) { + if (ctx->sess && delete_conn) { GF_DownloadSession *tmp = ctx->sess; ctx->sess = NULL; gf_dm_sess_abort(tmp); @@ -349,6 +330,7 @@ ctx->async = GF_FALSE; ctx->readyState = XHR_READYSTATE_UNSENT; ctx->ret_code = GF_OK; + ctx->in_send = GF_FALSE; } static void xml_http_finalize(JSRuntime *rt, JSValue obj) @@ -363,7 +345,7 @@ JS_FreeValueRT(rt, ctx->onprogress); JS_FreeValueRT(rt, ctx->onreadystatechange); JS_FreeValueRT(rt, ctx->ontimeout); - xml_http_reset(ctx); + xml_http_reset(ctx, GF_TRUE); #if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_VRML) if (ctx->event_target) { @@ -389,8 +371,9 @@ static GFINLINE GF_SceneGraph *xml_get_scenegraph(JSContext *c) { GF_SceneGraph *scene; + JSClassID _classID; JSValue global = JS_GetGlobalObject(c); - scene = (GF_SceneGraph *) JS_GetOpaque_Nocheck(global); + scene = (GF_SceneGraph *) JS_GetAnyOpaque(global, &_classID); JS_FreeValue(c, global); return scene; } @@ -507,7 +490,7 @@ if (!ctx) return GF_JS_EXCEPTION(c); /*reset*/ - if (ctx->readyState) xml_http_reset(ctx); + if (ctx->readyState) xml_http_reset(ctx, GF_FALSE); if (argc<2) return GF_JS_EXCEPTION(c); /*method is a string*/ @@ -515,7 +498,7 @@ /*url is a string*/ if (!JS_CHECK_STRING(argv1)) return GF_JS_EXCEPTION(c); - xml_http_reset(ctx); + xml_http_reset(ctx, GF_FALSE); val = JS_ToCString(c, argv0); if (strcmp(val, "GET") && strcmp(val, "POST") && strcmp(val, "HEAD") && strcmp(val, "PUT") && strcmp(val, "DELETE") && strcmp(val, "OPTIONS") ) { @@ -676,7 +659,7 @@ static void xml_http_terminate(XMLHTTPContext *ctx, GF_Err error) { /*if we get here, destroy downloader*/ - if (ctx->sess) { + if (ctx->sess && error && (error!=GF_URL_ERROR) && (error!=GF_URL_REMOVED)) { gf_dm_sess_del(ctx->sess); ctx->sess = NULL; } @@ -696,6 +679,11 @@ if (JS_IsException(rval)) js_dump_error(ctx->c); JS_FreeValue(ctx->c, rval); } + if (error && JS_IsFunction(ctx->c, ctx->onerror) ) { + JSValue rval = JS_Call(ctx->c, ctx->onerror, ctx->_this, 0, NULL); + if (JS_IsException(rval)) js_dump_error(ctx->c); + JS_FreeValue(ctx->c, rval); + } } static void xml_http_on_data(void *usr_cbk, GF_NETIO_Parameter *parameter) @@ -733,8 +721,8 @@ case GF_NETIO_WAIT_FOR_REPLY: /*reset send() state (data, current header) and prepare recv headers*/ xml_http_reset_partial(ctx); - ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED; - xml_http_state_change(ctx); +// ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED; +// xml_http_state_change(ctx); xml_http_fire_event(ctx, GF_EVENT_MEDIA_PROGRESS); if (JS_IsFunction(ctx->c, ctx->onprogress) ) { JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 0, NULL); @@ -748,8 +736,26 @@ if (parameter->value) { ctx->statusText = gf_strdup(parameter->value); } - ctx->readyState = XHR_READYSTATE_LOADING; - xml_http_state_change(ctx); + +#ifndef GPAC_DISABLE_SVG + /*prepare SAX parser*/ + if (ctx->sess && (ctx->responseType == XHR_RESPONSETYPE_SAX)) { + const char *type = gf_dm_sess_get_header(ctx->sess, "Content-Type"); + if (!strncmp(type, "application/xml", 15) + || !strncmp(type, "text/xml", 8) + || strstr(type, "+xml") + || strstr(type, "/xml") + ) { + gf_assert(!ctx->sax); + ctx->sax = gf_xml_sax_new(xml_http_sax_start, xml_http_sax_end, xml_http_sax_text, ctx); + ctx->node_stack = gf_list_new(); + ctx->document = gf_sg_new(); + /*mark this doc as "nomade", and let it leave until all references to it are destroyed*/ + ctx->document->reference_count = 1; + } + } +#endif + ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED; xml_http_state_change(ctx); xml_http_fire_event(ctx, GF_EVENT_MEDIA_PROGRESS); @@ -776,30 +782,11 @@ parameter->size = ctx->size; } goto exit; - case GF_NETIO_PARSE_HEADER: - xml_http_append_recv_header(ctx, parameter->name, parameter->value); - /*prepare SAX parser*/ - if (ctx->responseType != XHR_RESPONSETYPE_SAX) goto exit; - if (strcmp(parameter->name, "Content-Type")) goto exit; - -#ifndef GPAC_DISABLE_SVG - if (!strncmp(parameter->value, "application/xml", 15) - || !strncmp(parameter->value, "text/xml", 8) - || strstr(parameter->value, "+xml") - || strstr(parameter->value, "/xml") -// || !strncmp(parameter->value, "text/plain", 10) - ) { - gf_assert(!ctx->sax); - ctx->sax = gf_xml_sax_new(xml_http_sax_start, xml_http_sax_end, xml_http_sax_text, ctx); - ctx->node_stack = gf_list_new(); - ctx->document = gf_sg_new(); - /*mark this doc as "nomade", and let it leave until all references to it are destroyed*/ - ctx->document->reference_count = 1; - } -#endif - - goto exit; case GF_NETIO_DATA_EXCHANGE: + if (ctx->readyState == XHR_READYSTATE_HEADERS_RECEIVED) { + ctx->readyState = XHR_READYSTATE_LOADING; + xml_http_state_change(ctx); + } if (parameter->data && parameter->size) { if (ctx->sax) { GF_Err e; @@ -841,7 +828,14 @@ } JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 1, &prog_evt); - if (JS_IsException(rval)) js_dump_error(ctx->c); + if (JS_IsException(rval)) { + js_dump_error(ctx->c); + } else if (JS_IsNumber(rval)) { + s32 throttle; + JS_ToInt32(ctx->c, &throttle, rval); + if (throttle>=0) + gf_dm_sess_set_max_rate(ctx->sess, (u32) throttle); + } JS_FreeValue(ctx->c, rval); if (ctx->responseType==XHR_RESPONSETYPE_PUSH) { JS_DetachArrayBuffer(ctx->c, buffer_ab); @@ -853,6 +847,8 @@ case GF_NETIO_DATA_TRANSFERED: /* No return, go till the end of the function */ break; + case GF_NETIO_ICY_META: + goto exit; case GF_NETIO_DISCONNECTED: goto exit; case GF_NETIO_STATE_ERROR: @@ -880,7 +876,6 @@ /* For XML Http Requests to files, we fake the processing by calling the HTTP callbacks */ GF_NETIO_Parameter par; u64 fsize; - char contentLengthHeader256; FILE *responseFile; /*opera-style local host*/ @@ -916,25 +911,18 @@ ctx->datafsize = 0; ctx->size = (u32)fsize; - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.msg_type = GF_NETIO_PARSE_HEADER; - par.name = "Content-Type"; - if (ctx->responseType == XHR_RESPONSETYPE_SAX) { - par.value = "application/xml"; - } else if (ctx->responseType == XHR_RESPONSETYPE_DOCUMENT) { - par.value = "application/xml"; - } else { - par.value = "application/octet-stream"; +#ifndef GPAC_DISABLE_SVG + if ((ctx->responseType == XHR_RESPONSETYPE_SAX) + || (ctx->responseType == XHR_RESPONSETYPE_DOCUMENT) + ) { + gf_assert(!ctx->sax); + ctx->sax = gf_xml_sax_new(xml_http_sax_start, xml_http_sax_end, xml_http_sax_text, ctx); + ctx->node_stack = gf_list_new(); + ctx->document = gf_sg_new(); + /*mark this doc as "nomade", and let it leave until all references to it are destroyed*/ + ctx->document->reference_count = 1; } - xml_http_on_data(ctx, &par); - - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.msg_type = GF_NETIO_PARSE_HEADER; - par.name = "Content-Length"; - sprintf(contentLengthHeader, "%d", ctx->size); - par.value = contentLengthHeader; - xml_http_on_data(ctx, &par); - +#endif if (ctx->sax) { memset(&par, 0, sizeof(GF_NETIO_Parameter)); @@ -971,7 +959,8 @@ if (!ctx) return GF_JS_EXCEPTION(c); if (ctx->readyState!=XHR_READYSTATE_OPENED) return GF_JS_EXCEPTION(c); - if (ctx->sess) return GF_JS_EXCEPTION(c); + if (ctx->in_send) return GF_JS_EXCEPTION(c); + ctx->in_send = GF_TRUE; scene = xml_get_scenegraph(c); if (scene) { @@ -982,7 +971,7 @@ par.dnld_man = jsf_get_download_manager(c); } #ifndef GPAC_DISABLE_NETWORKING - if (!par.dnld_man) return GF_JS_EXCEPTION(c); + if (!par.dnld_man && !ctx->sess) return GF_JS_EXCEPTION(c); #endif if (argc) { @@ -1008,7 +997,7 @@ JS_FreeCString(c, data); - if (!strncmp(ctx->url, "http://", 7)) { + if (!strncmp(ctx->url, "http://", 7) || !strncmp(ctx->url, "https://", 8)) { u32 flags = GF_NETIO_SESSION_NOTIFY_DATA; //disable sync XHR in emscripten #ifdef GPAC_CONFIG_EMSCRIPTEN @@ -1026,8 +1015,23 @@ flags |= GF_NETIO_SESSION_MEMORY_CACHE; } } - ctx->sess = gf_dm_sess_new(par.dnld_man, ctx->url, flags, xml_http_on_data, ctx, &e); - if (!ctx->sess) return GF_JS_EXCEPTION(c); + //allow connetion reuse + if (ctx->sess) { + e = gf_dm_sess_setup_from_url(ctx->sess, ctx->url, GF_FALSE); + if (e) { + gf_dm_sess_del(ctx->sess); + ctx->sess = NULL; + } else { + GF_NetIOStatus status; + gf_dm_sess_get_stats(ctx->sess, NULL, NULL, 0, 0, 0, &status); + assert(status<=GF_NETIO_CONNECTED); + } + } + + if (!ctx->sess) { + ctx->sess = gf_dm_sess_new(par.dnld_man, ctx->url, flags, xml_http_on_data, ctx, &e); + if (!ctx->sess) return GF_JS_EXCEPTION(c); + } /*start our download (whether the session is threaded or not)*/ e = gf_dm_sess_process(ctx->sess); @@ -1058,11 +1062,10 @@ if (sess) { //abort first, so that on HTTP/2 this results in RST_STREAM gf_dm_sess_abort(sess); - gf_dm_sess_del(sess); } xml_http_fire_event(ctx, GF_EVENT_ABORT); - xml_http_reset(ctx); + xml_http_reset(ctx, GF_TRUE); if (JS_IsFunction(c, ctx->onabort)) { return JS_Call(ctx->c, ctx->onabort, ctx->_this, 0, NULL); } @@ -1073,20 +1076,20 @@ { u32 nb_hdr; char *szVal = NULL; + const char *hdr_name, *hdr_val; JSValue res; XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id); if (!ctx) return GF_JS_EXCEPTION(c); /*must be received or loaded*/ - if (ctx->readyState<XHR_READYSTATE_LOADING) return GF_JS_EXCEPTION(c); + if (ctx->readyState<XHR_READYSTATE_HEADERS_RECEIVED) return GF_JS_EXCEPTION(c); nb_hdr = 0; - if (ctx->recv_headers) { - while (ctx->recv_headersnb_hdr) { - if (szVal) gf_dynstrcat(&szVal, "\r\n", NULL); - gf_dynstrcat(&szVal, ctx->recv_headersnb_hdr, NULL); - gf_dynstrcat(&szVal, ctx->recv_headersnb_hdr+1, ": "); - nb_hdr+=2; - } + while (1) { + GF_Err e = gf_dm_sess_enum_headers(ctx->sess, &nb_hdr, &hdr_name, &hdr_val); + if (e) break; + if (szVal) gf_dynstrcat(&szVal, "\r\n", NULL); + gf_dynstrcat(&szVal, hdr_name, NULL); + gf_dynstrcat(&szVal, hdr_val, ": "); } if (!szVal) { @@ -1099,32 +1102,19 @@ static JSValue xml_http_get_header(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv) { - u32 nb_hdr; const char *hdr; - char *szVal = NULL; XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id); if (!ctx) return GF_JS_EXCEPTION(c); if (!JS_CHECK_STRING(argv0)) return GF_JS_EXCEPTION(c); /*must be received or loaded*/ - if (ctx->readyState<XHR_READYSTATE_LOADING) return GF_JS_EXCEPTION(c); + if (ctx->readyState<XHR_READYSTATE_HEADERS_RECEIVED) return GF_JS_EXCEPTION(c); hdr = JS_ToCString(c, argv0); - - nb_hdr = 0; - if (ctx->recv_headers) { - while (ctx->recv_headersnb_hdr) { - if (!strcmp(ctx->recv_headersnb_hdr, hdr)) { - gf_dynstrcat(&szVal, ctx->recv_headersnb_hdr+1, ", "); - } - nb_hdr+=2; - } - } + const char *hdr_val = gf_dm_sess_get_header(ctx->sess, hdr); JS_FreeCString(c, hdr); - if (!szVal) { - return JS_NULL; - } - JSValue res = JS_NewString(c, szVal); - gf_free(szVal); + if (!hdr_val) return JS_NULL; + + JSValue res = JS_NewString(c, hdr_val); return res; }
View file
gpac-2.4.0.tar.gz/src/laser/lsr_dec.c -> gpac-26.02.0.tar.gz/src/laser/lsr_dec.c
Changed
@@ -554,6 +554,10 @@ { XMLRI *iri; char *text, *sep, *sep2, *cur; + if (!l) { + lsr->last_error = GF_BAD_PARAM; + return; + } while (gf_list_count(l)) { char *str = (char *)gf_list_last(l); gf_list_rem_last(l); @@ -646,12 +650,14 @@ len = lsr_read_vluimsbf5(lsr, "len"); if (len > gf_bs_available(lsr->bs)) { lsr->last_error = GF_NON_COMPLIANT_BITSTREAM; + if (s) { gf_free(s); } return; } len_rad = s ? (u32) strlen(s) : 0; iri->string = (char*)gf_malloc(sizeof(char)*(len_rad+1+len+1)); if (!iri->string) { lsr->last_error = GF_OUT_OF_MEM; + if (s) { gf_free(s); } return; } @@ -780,7 +786,7 @@ XMLRI *href = (XMLRI *)gf_list_get(lsr->deferred_hrefs, i); char *str_id = href ? href->string : NULL; if (!str_id) return; - + if (str_id0 == '#') str_id++; /*skip 'N'*/ str_id++; @@ -888,7 +894,7 @@ static Fixed lsr_translate_scale(GF_LASeRCodec *lsr, u32 val) { - if (val >> (lsr->coord_bits-1) ) { + if (lsr && lsr->coord_bits && val >> (lsr->coord_bits-1) ) { s32 neg; if (lsr->coord_bits >= 31) neg = (s32)val - 0x80000000;
View file
gpac-2.4.0.tar.gz/src/laser/lsr_enc.c -> gpac-26.02.0.tar.gz/src/laser/lsr_enc.c
Changed
@@ -2138,7 +2138,7 @@ } lsr_write_point_sequence(lsr, &pts, "seq"); gf_list_del(pts); - /*first moveTo is skiped*/ + /*first moveTo is skipped*/ lsr_write_vluimsbf5(lsr, nb_types-1, "nbOfTypes"); for (i=0; i<path->n_points; ) { switch (path->tagsi) {
View file
gpac-2.4.0.tar.gz/src/media_tools/av_parsers.c -> gpac-26.02.0.tar.gz/src/media_tools/av_parsers.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Romain Bouqueau, Cyril Concolato - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / Media Tools sub-project @@ -23,6 +23,7 @@ * */ +#include <gpac/internal/odf_dev.h> #include <gpac/internal/media_dev.h> #include <gpac/constants.h> #include <gpac/mpeg4_odf.h> @@ -1177,6 +1178,7 @@ } gf_bs_align(bs); cfg->comment_field_bytes = gf_bs_read_int_log(bs, 8, "comment_field_bytes"); + if (gf_bs_available(bs) < cfg->comment_field_bytes) return GF_NON_COMPLIANT_BITSTREAM; gf_bs_read_data(bs, (char *)cfg->comments, cfg->comment_field_bytes); cfg->nb_chan = cfg->num_front_channel_elements + cfg->num_back_channel_elements + cfg->num_side_channel_elements + cfg->num_lfe_channel_elements; @@ -1254,7 +1256,8 @@ ext_flag = gf_bs_read_int_log(bs, 1, "extension_flag"); if (!cfg->chan_cfg) { - gf_m4a_parse_program_config_element(bs, cfg); + GF_Err e = gf_m4a_parse_program_config_element(bs, cfg); + if(e) return e; } if ((cfg->base_object_type == 6) || (cfg->base_object_type == 20)) { @@ -2377,9 +2380,43 @@ return GF_OK; } + +void gf_av1_format_mdcv_to_mpeg(u8 mdcv_in24, u8 mdcv_out24) +{ + u32 i; + u64 val8 = {0}; + u8 shuffle8 = {4,5,0,1,2,3,6,7}; //RGB->GBR + GF_BitStream *bs_r = gf_bs_new(mdcv_in, 24, GF_BITSTREAM_READ); + GF_BitStream *bs_w = gf_bs_new(mdcv_out, 24, GF_BITSTREAM_WRITE); + + //3x{display_primaries_x, display_primaries_y} + whitePoint_x + whitePoint_y + //translate from AV1 representation 0.16 float to MPEG in increments of 0.00002 (1/50000) + for (i=0; i<8; i++) { + vali = gf_bs_read_u16(bs_r); + } + for (i=0; i<8; i++) { + gf_bs_write_u16(bs_w, (u32) ((50000 * valshufflei) / 65536)); + } + //max_display_mastering_luminance: 24.8 fixed point in AV1 vs increments of 0.0001 (1/10000) candelas per square metre in MPEG + *val = gf_bs_read_u32(bs_r); + *val = (10000 * *val) / 256; + gf_bs_write_u32(bs_w, (u32) *val); + + //min_display_mastering_luminance: 18.14 fixed point in AV1 vs increments of 0.0001 (1/10000) candelas per square metre in MPEG + *val = gf_bs_read_u32(bs_r); + *val = (10000 * *val) / 16384; + gf_bs_write_u32(bs_w, (u32) *val); + gf_bs_del(bs_r); + gf_bs_del(bs_w); +} + GF_EXPORT GF_Err gf_av1_parse_obu_header(GF_BitStream *bs, ObuType *obu_type, Bool *obu_extension_flag, Bool *obu_has_size_field, u8 *temporal_id, u8 *spatial_id) { + u64 pos = gf_bs_get_position(bs); + if (gf_bs_bits_available(bs) < 8) + return GF_BUFFER_TOO_SMALL; + Bool forbidden = gf_bs_read_int(bs, 1); if (forbidden) { return GF_NON_COMPLIANT_BITSTREAM; @@ -2392,6 +2429,10 @@ return GF_NON_COMPLIANT_BITSTREAM; } if (*obu_extension_flag) { + if (gf_bs_bits_available(bs) < 8) { + gf_bs_seek(bs, pos); + return GF_BUFFER_TOO_SMALL; + } *temporal_id = gf_bs_read_int(bs, 3); *spatial_id = gf_bs_read_int(bs, 2); /*extension_header_reserved_3bits = */gf_bs_read_int(bs, 3); @@ -2431,7 +2472,7 @@ switch (obu_type) { case OBU_SEQUENCE_HEADER: case OBU_METADATA: - // TODO add check based on the metadata type + // we're liberal in what we accept but we're conservative in what we send return GF_TRUE; default: return GF_FALSE; @@ -2453,6 +2494,33 @@ } } +Bool iamf_is_audio_frame_obu(IamfObuType obu_type) +{ + return OBU_IAMF_AUDIO_FRAME <= obu_type && obu_type <= OBU_IAMF_AUDIO_FRAME_ID17; +} + +Bool iamf_is_temporal_unit_obu(IamfObuType obu_type) +{ + if (iamf_is_audio_frame_obu(obu_type) || obu_type == OBU_IAMF_PARAMETER_BLOCK || obu_type == OBU_IAMF_TEMPORAL_DELIMITER) { + return GF_TRUE; + } + + return GF_FALSE; +} + +Bool iamf_is_descriptor_obu(IamfObuType obu_type) +{ + switch (obu_type) { + case OBU_IAMF_CODEC_CONFIG: + case OBU_IAMF_AUDIO_ELEMENT: + case OBU_IAMF_MIX_PRESENTATION: + case OBU_IAMF_SEQUENCE_HEADER: + return GF_TRUE; + default: + return GF_FALSE; + } +} + GF_EXPORT u64 gf_av1_leb128_read(GF_BitStream *bs, u8 *opt_Leb128Bytes) { u64 value = 0; @@ -2592,7 +2660,7 @@ } } if (!obu_list) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AV1 internal error, no OBU list cannot add\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AV1 internal error, no OBU list: cannot add\n")); gf_free(a->obu); gf_free(a); return; @@ -2606,6 +2674,23 @@ static void av1_populate_state_from_obu(GF_BitStream *bs, u64 pos, u64 obu_length, ObuType obu_type, AV1State *state) { if (av1_is_obu_header(obu_type)) { + if (obu_type == OBU_METADATA) { + u64 cur_pos = gf_bs_get_position(bs); + gf_bs_seek(bs, pos); + + Bool obu_extension_flag=GF_FALSE, obu_has_size_field=GF_FALSE; + u8 tid=0, sid=0; + gf_av1_parse_obu_header(bs, &obu_type, &obu_extension_flag, &obu_has_size_field, &tid, &sid); + if (obu_has_size_field) gf_av1_leb128_read(bs, NULL); + ObuMetadataType metadata_type = (ObuMetadataType)gf_av1_leb128_read(bs, NULL); + + gf_bs_seek(bs, cur_pos); + + // TODO: filter out any metadata which values change + if (metadata_type == OBU_METADATA_TYPE_TIMECODE) + return; + } + av1_add_obu_internal(bs, pos, obu_length, obu_type, &state->frame_state.header_obus, NULL); } if (!state->skip_frames && av1_is_obu_frame(state, obu_type)) { @@ -2635,6 +2720,8 @@ e = gf_av1_parse_obu(bs, &state->obu_type, &obu_size, NULL, state); if (e) return e; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 parsed AV1 OBU type=%u size="LLU" at position "LLU".\n", state->obu_type, obu_size, pos)); + if (obu_size != gf_bs_get_position(bs) - pos) { GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("AV1 OBU (Section 5) frame size "LLU" different from consumed bytes "LLU".\n", obu_size, gf_bs_get_position(bs) - pos)); @@ -2774,6 +2861,8 @@ e = gf_av1_parse_obu(bs, &state->obu_type, &obu_size, NULL, state); if (e) return e; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 AnnexB: parsed AV1 OBU type=%u size="LLU" at position "LLU".\n", state->obu_type, obu_size, pos)); + if (obu_size != gf_bs_get_position(bs) - pos) { GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("AV1 Annex B frame size "LLU" different from consumed bytes "LLU".\n", obu_size, gf_bs_get_position(bs) - pos)); @@ -2803,7 +2892,7 @@ if (gf_bs_available(bs)<12) return GF_EOS; e = gf_media_parse_ivf_frame_header(bs, &frame_size, &pts_ignored); if (e) return e; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 IVF frame detected (size "LLU")\n", frame_size)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 IVF: frame detected (size "LLU", PTS "LLU")\n", frame_size, pts_ignored)); if (gf_bs_available(bs) < frame_size) return GF_EOS; @@ -2813,6 +2902,7 @@ e = gf_av1_parse_obu(bs, &state->obu_type, &obu_size, NULL, state); if (e != GF_OK) return e; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 IVF: parsed AV1 OBU type=%u size="LLU" at position "LLU".\n", state->obu_type, obu_size, pos)); if (obu_size != gf_bs_get_position(bs) - pos) { GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("AV1 IVF frame size "LLU" different from consumed bytes "LLU".\n", obu_size, gf_bs_get_position(bs) - pos)); @@ -3239,7 +3329,6 @@ //gm_params ref j is set equal to SavedGmParams frame_to_show_map_idx ref j for ref = LAST_FRAME..ALTREF_FRAME, for j = 0..5. state->GmParams = state->SavedGmParamsstate->frame_state.frame_to_show_map_idx; - } } @@ -3426,6 +3515,9 @@ if (frame_state->is_first_frame) { frame_state->key_frame = frame_state->seen_seq_header && frame_state->show_frame && frame_state->frame_type == AV1_KEY_FRAME && frame_state->seen_frame_header; } + if (frame_state->frame_type == AV1_SWITCH_FRAME) { + frame_state->switch_frame = GF_TRUE; + } if (frame_state->show_frame && state->decoder_model_info_present_flag && !state->equal_picture_interval) { gf_bs_read_int_log(bs, state->frame_presentation_time_length, "frame_presentation_time"); } @@ -3626,7 +3718,7 @@ s32 DeltaQVDc = 0; s32 DeltaQVAc = 0; s32 DeltaQYDc = av1_delta_q(bs, "DeltaQYDc_coded", "DeltaQYDc"); - if (!state->config->monochrome) { + if (!state->config || !state->config->monochrome) { u8 diff_uv_delta = 0; if (state->separate_uv_delta_q) diff_uv_delta = gf_bs_read_int_log(bs, 1, "diff_uv_delta"); @@ -3682,7 +3774,6 @@ } //ignore all init steps } - } //delta_q_params(): @@ -3723,7 +3814,7 @@ if (!CodedLossless && !allow_intrabc) { u8 loop_filter_level_0 = gf_bs_read_int_log(bs, 6, "loop_filter_level_0"); u8 loop_filter_level_1 = gf_bs_read_int_log(bs, 6, "loop_filter_level_1"); - if (!state->config->monochrome) { + if (state && state->config && !state->config->monochrome) { if (loop_filter_level_0 || loop_filter_level_1) { gf_bs_read_int_log(bs, 6, "loop_filter_level_2"); gf_bs_read_int_log(bs, 6, "loop_filter_level_3"); @@ -4070,6 +4161,87 @@ } } +GF_EXPORT +void gf_iamf_init_state(IAMFState *state) +{ + if (!state) + return; + + memset(state, 0, sizeof(IAMFState)); + state->codec_id = 0; + state->num_samples_per_frame = 0; + state->sample_size = 0; + state->sample_rate = 0; + state->total_substreams = 0; + state->bitstream_has_temporal_delimiters = GF_FALSE; + state->pre_skip = 0; + + state->frame_state.seen_first_frame = GF_FALSE; + state->frame_state.seen_valid_iamf_seq_header = GF_FALSE; + state->frame_state.previous_obu_is_descriptor = GF_FALSE; + state->frame_state.pre_skip_is_finalized = GF_FALSE; + state->frame_state.previous_num_samples_to_trim_at_start = 0; + state->frame_state.num_samples_to_trim_at_end = 0; + + state->frame_state.found_full_temporal_unit = GF_FALSE; + state->frame_state.seen_first_obu_in_temporal_unit = GF_FALSE; + state->frame_state.num_audio_frames_in_temporal_unit = 0; + + state->frame_state.cache_descriptor_obus = GF_FALSE; +} + +GF_EXPORT +void gf_iamf_reset_state(IAMFState *state, Bool is_destroy) +{ + GF_List *l1, *l2; + + if (state->frame_state.descriptor_obus && (!state->frame_state.cache_descriptor_obus || is_destroy)) { + while (gf_list_count(state->frame_state.descriptor_obus)) { + GF_IamfObu *a = (GF_IamfObu *)gf_list_pop_back(state->frame_state.descriptor_obus); + if (a->raw_obu_bytes) + gf_free(a->raw_obu_bytes); + gf_free(a); + } + } + + if (state->frame_state.temporal_unit_obus) { + while (gf_list_count(state->frame_state.temporal_unit_obus)) { + GF_IamfObu *a = (GF_IamfObu *)gf_list_pop_back(state->frame_state.temporal_unit_obus); + if (a->raw_obu_bytes) + gf_free(a->raw_obu_bytes); + gf_free(a); + } + } + l1 = state->frame_state.temporal_unit_obus; + l2 = state->frame_state.descriptor_obus; + + // Reset temporal unit status, now that it has been flushed. + state->frame_state.num_samples_to_trim_at_end = 0; + state->frame_state.found_full_temporal_unit = GF_FALSE; + state->frame_state.seen_first_obu_in_temporal_unit = GF_FALSE; + state->frame_state.num_audio_frames_in_temporal_unit = 0; + + if (is_destroy) { + gf_list_del(l1); + gf_list_del(l2); + if (state->bs) { + if (state->temporal_unit_obus) { + gf_free(state->temporal_unit_obus); + state->temporal_unit_obus = NULL; + state->temporal_unit_obus_alloc = 0; + } + gf_bs_del(state->bs); + state->bs = NULL; + } + } else + { + state->frame_state.temporal_unit_obus = l1; + state->frame_state.descriptor_obus = l2; + if (state->bs) + gf_bs_seek(state->bs, 0); + } +} + static GF_Err av1_parse_tile_group(GF_BitStream *bs, AV1State *state, u64 obu_start, u64 obu_size) { u32 TileNum, tg_start = 0, tg_end = 0; @@ -4159,27 +4331,67 @@ { av1_parse_frame_header(bs, state); //byte alignment - { - u32 nbBits = gf_bs_align(bs); - gf_bs_log_idx(bs, nbBits, "alignment", 0, -1, -1, -1); - } + { + u32 nbBits = gf_bs_align(bs); + gf_bs_log_idx(bs, nbBits, "alignment", 0, -1, -1, -1); + } return av1_parse_tile_group(bs, state, obu_start, obu_size); } +static void av1_parse_timecode_obu(GF_SEIInfo *sei, GF_BitStream *bs) +{ + AVCSeiPicTiming *pt = &sei->pic_timing; + pt->num_clock_ts = 1; + AVCSeiPicTimingTimecode *tc = &pt->timecodes0; + tc->clock_timestamp_flag = 1; + + tc->counting_type = gf_bs_read_int_log(bs, 5, "counting_type"); + Bool full_timestamp_flag = gf_bs_read_int_log(bs, 1, "full_timestamp_flag"); + gf_bs_read_int_log(bs, 1, "discontinuity_flag"); + tc->cnt_dropped_flag = gf_bs_read_int_log(bs, 1, "cnt_dropped_flag"); + tc->n_frames = gf_bs_read_int_log(bs, 9, "n_frames"); + + if (full_timestamp_flag) { + tc->seconds = gf_bs_read_int_log(bs, 6, "seconds_value"); + tc->minutes = gf_bs_read_int_log(bs, 6, "minutes_value"); + tc->hours = gf_bs_read_int_log(bs, 5, "hours_value"); + } else { + Bool seconds_flag = gf_bs_read_int_log(bs, 1, "seconds_flag"); + if (seconds_flag) { + tc->seconds = gf_bs_read_int_log(bs, 6, "seconds_value"); + Bool minutes_flag = gf_bs_read_int_log(bs, 1, "minutes_flag"); + if (minutes_flag) { + tc->minutes = gf_bs_read_int_log(bs, 6, "minutes_value"); + Bool hours_flag = gf_bs_read_int_log(bs, 1, "hours_flag"); + if (hours_flag) { + tc->hours = gf_bs_read_int_log(bs, 5, "hours_value"); + } + } + } + } + u8 time_offset_length = gf_bs_read_int_log(bs, 5, "time_offset_length"); + if (time_offset_length) { + gf_bs_read_int_log(bs, time_offset_length, "time_offset_value"); + } +} + static void av1_parse_obu_metadata(AV1State *state, GF_BitStream *bs) { - u32 metadata_type = (u32)gf_av1_leb128_read(bs, NULL); + ObuMetadataType metadata_type = (ObuMetadataType)gf_av1_leb128_read(bs, NULL); switch (metadata_type) { case OBU_METADATA_TYPE_ITUT_T35: break; case OBU_METADATA_TYPE_HDR_CLL: - gf_bs_read_data(bs, state->clli_data, 4); - state->clli_valid = 1; + gf_bs_read_data(bs, state->sei.clli_data, 4); + state->sei.clli_valid = 1; break; case OBU_METADATA_TYPE_HDR_MDCV: - gf_bs_read_data(bs, state->mdcv_data, 24); - state->mdcv_valid = 1; + gf_bs_read_data(bs, state->sei.mdcv_data, 24); + state->sei.mdcv_valid = 1; + break; + case OBU_METADATA_TYPE_TIMECODE: + av1_parse_timecode_obu(&state->sei, bs); break; default: break; @@ -4248,6 +4460,14 @@ for (i = state->spatial_id; i < 4; i++) { state->layer_sizei = (u32) (pos + *obu_size); } + if (state->parse_metadata_filter) { + //for now no dependency on metadat to state + if (*obu_type != OBU_METADATA) { + gf_bs_seek(bs, pos + *obu_size); + return e; + } + state->parse_metadata_filter = 2; + } switch (*obu_type) { case OBU_SEQUENCE_HEADER: @@ -4298,7 +4518,7 @@ break; case OBU_TEMPORAL_DELIMITER: state->frame_state.seen_frame_header = GF_FALSE; - state->clli_valid = state->mdcv_valid = 0; + // fallthru case OBU_PADDING: gf_bs_seek(bs, pos + *obu_size); break; @@ -4384,6 +4604,466 @@ #endif /*GPAC_DISABLE_AV_PARSERS*/ GF_EXPORT +const char *gf_iamf_get_obu_name(IamfObuType obu_type) +{ + switch (obu_type) { + case OBU_IAMF_CODEC_CONFIG: return "codec_config"; + case OBU_IAMF_AUDIO_ELEMENT: return "audio_element"; + case OBU_IAMF_MIX_PRESENTATION: return "mix_presentation"; + case OBU_IAMF_PARAMETER_BLOCK: return "parameter_block"; + case OBU_IAMF_TEMPORAL_DELIMITER: return "temporal_delimiter"; + case OBU_IAMF_AUDIO_FRAME: return "audio_frame"; + case OBU_IAMF_AUDIO_FRAME_ID0: return "audio_frame_id0"; + case OBU_IAMF_AUDIO_FRAME_ID1: return "audio_frame_id1"; + case OBU_IAMF_AUDIO_FRAME_ID2: return "audio_frame_id2"; + case OBU_IAMF_AUDIO_FRAME_ID3: return "audio_frame_id3"; + case OBU_IAMF_AUDIO_FRAME_ID4: return "audio_frame_id4"; + case OBU_IAMF_AUDIO_FRAME_ID5: return "audio_frame_id5"; + case OBU_IAMF_AUDIO_FRAME_ID6: return "audio_frame_id6"; + case OBU_IAMF_AUDIO_FRAME_ID7: return "audio_frame_id7"; + case OBU_IAMF_AUDIO_FRAME_ID8: return "audio_frame_id8"; + case OBU_IAMF_AUDIO_FRAME_ID9: return "audio_frame_id9"; + case OBU_IAMF_AUDIO_FRAME_ID10: return "audio_frame_id10"; + case OBU_IAMF_AUDIO_FRAME_ID11: return "audio_frame_id11"; + case OBU_IAMF_AUDIO_FRAME_ID12: return "audio_frame_id12"; + case OBU_IAMF_AUDIO_FRAME_ID13: return "audio_frame_id13"; + case OBU_IAMF_AUDIO_FRAME_ID14: return "audio_frame_id14"; + case OBU_IAMF_AUDIO_FRAME_ID15: return "audio_frame_id15"; + case OBU_IAMF_AUDIO_FRAME_ID16: return "audio_frame_id16"; + case OBU_IAMF_AUDIO_FRAME_ID17: return "audio_frame_id17"; + case OBU_IAMF_RESERVED_24: + case OBU_IAMF_RESERVED_25: + case OBU_IAMF_RESERVED_26: + case OBU_IAMF_RESERVED_27: + case OBU_IAMF_RESERVED_28: + case OBU_IAMF_RESERVED_29: + case OBU_IAMF_RESERVED_30: + return "reserved"; + case OBU_IAMF_SEQUENCE_HEADER: return "ia_sequence_header"; + default: return "unknown"; + } +} + +#ifndef GPAC_DISABLE_AV_PARSERS + +static +GF_Err gf_iamf_parse_obu_header(GF_BitStream *bs, IamfObuType *obu_type, u64 *obu_size, u64 *num_samples_to_trim_at_start, u64 *num_samples_to_trim_at_end) +{ + Bool obu_redundant_copy; + Bool obu_trimming_status_flag; + Bool obu_extension_flag; + u64 extension_header_size; + u8 leb128_size; + int i; + + *obu_type = gf_bs_read_int(bs, 5); + + obu_redundant_copy = gf_bs_read_int(bs, 1); + if (obu_redundant_copy) { + if (iamf_is_temporal_unit_obu(*obu_type)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF An OBU with obu_type = %s must have obu_redundant_copy set to 0, but got 1.\n", gf_iamf_get_obu_name(*obu_type))); + return GF_NON_COMPLIANT_BITSTREAM; + } + } + + obu_trimming_status_flag = gf_bs_read_int(bs, 1); + if (obu_trimming_status_flag) { + if (!iamf_is_audio_frame_obu(*obu_type)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF An OBU with obu_type = %s must have obu_trimming_status_flag set to 0, but got 1.\n", gf_iamf_get_obu_name(*obu_type))); + return GF_NON_COMPLIANT_BITSTREAM; + } + } + obu_extension_flag = gf_bs_read_int(bs, 1); + + + /* gpac's `obu_size` includes the header and payload, which is different + * from the `obu_size` field in the IAMF bitstream. The latter includes the + * partial header size and payload size. + * The size of the header read so far is 1 byte. */ + *obu_size = 1; + + /* Add the remaining size of the OBU, carried in the `obu_size` field in + * the OBU header, plus the number of bytes used the encode that leb128 field. */ + *obu_size += (u32)gf_av1_leb128_read(bs, &leb128_size); + *obu_size += leb128_size; + + u64 read_num_samples_to_trim_at_start = 0; + u64 read_num_samples_to_trim_at_end = 0; + if (obu_trimming_status_flag) { + read_num_samples_to_trim_at_end = gf_av1_leb128_read(bs, NULL); + read_num_samples_to_trim_at_start = gf_av1_leb128_read(bs, NULL); + } + if (num_samples_to_trim_at_start) { + *num_samples_to_trim_at_start = read_num_samples_to_trim_at_start; + } + if (num_samples_to_trim_at_end) { + *num_samples_to_trim_at_end = read_num_samples_to_trim_at_end; + } + + if (obu_extension_flag) { + extension_header_size = gf_av1_leb128_read(bs, NULL); + if (gf_bs_available(bs) < extension_header_size) { + return GF_BUFFER_TOO_SMALL; + } + for (i = 0; i < extension_header_size; ++i) { + /*extension_header_bytes=*/gf_bs_read_u8(bs); + } + } + + return GF_OK; +} + +static Bool iamf_is_profile_supported(u8 profile) +{ + return profile == 0 || profile == 1 || profile == 2; +} + +static GF_Err iamf_parse_iamf_sequence_header(GF_BitStream *bs, IAMFState *state) +{ + u32 ia_code = gf_bs_read_int_log(bs, 32, "ia_code"); + if (ia_code != GF_4CC('i', 'a', 'm', 'f')) { + return GF_NON_COMPLIANT_BITSTREAM; + } + u8 primary_profile = gf_bs_read_int_log(bs, 8, "primary_profile"); + if (state) state->primary_profile = primary_profile; + u8 additional_profile = gf_bs_read_int_log(bs, 8, "additional_profile"); + if (state) state->additional_profile = additional_profile; + if (iamf_is_profile_supported(primary_profile) || iamf_is_profile_supported(additional_profile)) { + return GF_OK; + } + + // Maybe this is a future version of IAMF, which claims some backwards incompatibility. + return GF_NOT_SUPPORTED; +} + +static GF_Err iamf_parse_codec_config(GF_BitStream *bs, IAMFState *state) +{ + GF_Descriptor *desc = NULL; + GF_Err e = GF_OK; + gf_av1_leb128_read(bs, NULL); // `codec_config_id`. + state->codec_id = gf_bs_read_int_log(bs, 32, "codec_id"); + state->num_samples_per_frame = (int) gf_av1_leb128_read(bs, NULL); + state->audio_roll_distance = gf_bs_read_int_log(bs, 16, "roll_distance"); + switch (state->codec_id) { + case GF_4CC('O', 'p', 'u', 's'): + state->sample_rate = 48000; + state->sample_size = 16; + break; + case GF_4CC('f', 'L', 'a', 'C'): + gf_bs_read_int_log(bs, 1, "last_metadata_block_flag"); + gf_bs_read_int_log(bs, 7, "block_type"); + gf_bs_read_int_log(bs, 24, "block_length"); + gf_bs_read_int_log(bs, 16, "minimum_block_size"); + gf_bs_read_int_log(bs, 16, "maximum_block_size"); + gf_bs_read_int_log(bs, 24, "minimum_frame_size"); + gf_bs_read_int_log(bs, 24, "maximum_frame_size"); + state->sample_rate = gf_bs_read_int_log(bs, 20, "sample_rate"); + /*state->num_of_channels = 1 + */gf_bs_read_int_log(bs, 3, "num_of_channels"); + state->sample_size = gf_bs_read_int_log(bs, 5, "bits_per_sample") + 1; + break; + // LPCM. + case GF_4CC('i', 'p', 'c', 'm'): + gf_bs_read_int_log(bs, 8, "sample_format_flags"); + state->sample_size = gf_bs_read_int_log(bs, 8, "sample_size"); + state->sample_rate = gf_bs_read_int_log(bs, 32, "sample_rate"); + break; + // AAC-LC. + case GF_4CC('m', 'p', '4', 'a'): + { + // Read `DecoderConfig`. + u32 unused_size; + e = gf_odf_parse_descriptor(bs, &desc, &unused_size); + if (!desc || desc->tag != GF_ODF_DCD_TAG) + { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + // Check that the `DecoderSpecificInfo` is `AudioSpecificConfig`. + GF_DecoderConfig *decoder_config = (GF_DecoderConfig *)desc; + if (!decoder_config->decoderSpecificInfo || decoder_config->decoderSpecificInfo->tag != GF_ODF_DSI_TAG) + { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + // Read the `AacDecoderConfig`. + GF_BitStream *aac_decoder_config_bs = gf_bs_new(decoder_config->decoderSpecificInfo->data, decoder_config->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); + GF_M4ADecSpecInfo aac_config; + e = gf_m4a_parse_config(aac_decoder_config_bs, &aac_config, GF_FALSE); + // All we care about is the sample rate. + state->sample_rate = aac_config.base_sr; + state->sample_size = 16; + gf_bs_del(aac_decoder_config_bs); + break; + } + default: + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + + if (desc) + gf_odf_delete_descriptor(desc); + + return e; +} + +static void iamf_parse_audio_element(GF_BitStream *bs, IAMFState *state) +{ + gf_av1_leb128_read(bs, NULL); // `audio_element_id`. + gf_bs_read_int_log(bs, 3, "audio_element_type"); + gf_bs_read_int_log(bs, 5, "reserved_for_future_use"); + gf_av1_leb128_read(bs, NULL); // `codec_config_id`. + const u64 num_substreams = gf_av1_leb128_read(bs, NULL); + //not bound in iamf specs ? + if (num_substreams>10000) + return; + if (state->total_substreams>GF_INT_MAX-num_substreams) + return; + + state->total_substreams += (int) num_substreams; + // OK to skip over the rest. + return; +} + +Bool gf_media_probe_iamf(GF_BitStream *bs) +{ + u64 start; + GF_Err e; + IamfObuType obu_type; + u64 obu_size; + + obu_type = gf_bs_peek_bits(bs, 5, 0); + if (obu_type != OBU_IAMF_SEQUENCE_HEADER) { + return GF_FALSE; + } + + // Likely IAMF. Check the IAMF Sequence header is valid. + start = gf_bs_get_position(bs); + e = gf_iamf_parse_obu_header(bs, &obu_type, &obu_size, NULL, NULL); + if (e) { + gf_bs_seek(bs, start); + return GF_FALSE; + } + + e = iamf_parse_iamf_sequence_header(bs, NULL); + gf_bs_seek(bs, start); + return !e; +} + +GF_EXPORT +GF_Err gf_iamf_parse_obu(GF_BitStream *bs, IamfObuType *obu_type, u64 *obu_size, IAMFState *state) +{ + GF_Err e = GF_OK; + u64 pos = gf_bs_get_position(bs); + + if (!bs || !obu_type) { + return GF_BAD_PARAM; + } + + gf_bs_mark_overflow(bs, GF_TRUE); + + e = gf_iamf_parse_obu_header(bs, obu_type, obu_size, + &state->frame_state.previous_num_samples_to_trim_at_start, + &state->frame_state.num_samples_to_trim_at_end); + u64 header_size = gf_bs_get_position(bs) - pos; + if (gf_bs_is_overflow(bs) || (gf_bs_available(bs) < (*obu_size - header_size)) ) { + gf_bs_seek(bs, pos); + return GF_BUFFER_TOO_SMALL; + } + + if (gf_bs_is_overflow(bs)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF OBU parsing consumed too many bytes.\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + } + if (e) + return e; + + switch (*obu_type) { + case OBU_IAMF_SEQUENCE_HEADER: + e = iamf_parse_iamf_sequence_header(bs, state); + if (!e) + state->frame_state.seen_valid_iamf_seq_header = GF_TRUE; + state->total_substreams = 0; + break; + case OBU_IAMF_CODEC_CONFIG: + e = iamf_parse_codec_config(bs, state); + break; + case OBU_IAMF_AUDIO_ELEMENT: + iamf_parse_audio_element(bs, state); + break; + default: + break; + } + if (iamf_is_descriptor_obu(*obu_type)) { + state->frame_state.previous_obu_is_descriptor = GF_TRUE; + } else if (iamf_is_temporal_unit_obu(*obu_type)) { + state->frame_state.previous_obu_is_descriptor = GF_FALSE; + } + // Reserved OBUs get treated as neither descriptor nor temporal unit OBUs. + + if (gf_bs_is_overflow(bs) || (gf_bs_get_position(bs) > pos + *obu_size)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF Parsing OBU type %s parsing consumed too many bytes !\n", gf_iamf_get_obu_name(*obu_type))); + e = GF_NON_COMPLIANT_BITSTREAM; + } + // Skip over to the end of the OBU. + gf_bs_seek(bs, pos + *obu_size); + + return e; +} + +static void iamf_add_obu_internal(GF_BitStream *bs, u64 pos, u64 obu_size, IamfObuType obu_type, GF_List **obu_list, IAMFState *state) +{ + GF_IamfObu *a = NULL; + + GF_SAFEALLOC(a, GF_IamfObu); + if (!a) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF Failed to allocate OBU\n")); + return; + } + + if (!state->bs) { + state->bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!state->bs) { + gf_free(a); + return; + } + } else if (state->temporal_unit_obus) { + u8* content=NULL; + u32 bssize; + gf_bs_get_content(state->bs, &content, &bssize); + if (content) { + gf_free(content); + } + gf_bs_reassign_buffer(state->bs, state->temporal_unit_obus, state->temporal_unit_obus_alloc); + //make sure we don't attempt at freeing this buffer while assigned to the bitstream - cf gf_iamf_reset_state + state->temporal_unit_obus = NULL; + } + + gf_bs_seek(bs, pos); + gf_iamf_parse_obu_header(bs, &obu_type, &obu_size, NULL, NULL); + gf_bs_seek(bs, pos); + + a->raw_obu_bytes = gf_malloc((size_t)obu_size); + gf_bs_read_data(bs, a->raw_obu_bytes, (u32)obu_size); + a->obu_length = obu_size; + + if (iamf_is_temporal_unit_obu(obu_type)) { + gf_bs_write_data(state->bs, a->raw_obu_bytes, (u32)obu_size); + } + + if (!obu_list) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF internal error, no OBU list cannot add\n")); + gf_free(a->raw_obu_bytes); + gf_free(a); + return; + } + a->obu_type = obu_type; + if (!*obu_list) + *obu_list = gf_list_new(); + gf_list_add(*obu_list, a); +} + +static void iamf_populate_state_from_obu(GF_BitStream *bs, u64 pos, u64 obu_length, IamfObuType obu_type, IAMFState *state) +{ + GF_List **list; + if (iamf_is_descriptor_obu(obu_type)) { + list = &state->frame_state.descriptor_obus; + } else if (iamf_is_temporal_unit_obu(obu_type)) { + list = &state->frame_state.temporal_unit_obus; + } + // Treat reserved OBUs based on the most recent OBU seen. + else if (state->frame_state.previous_obu_is_descriptor) { + list = &state->frame_state.descriptor_obus; + } else { + list = &state->frame_state.temporal_unit_obus; + } + iamf_add_obu_internal(bs, pos, obu_length, obu_type, list, state); +} + +GF_Err aom_iamf_parse_temporal_unit(GF_BitStream *bs, IAMFState *state) +{ + if (!state) + return GF_BAD_PARAM; + + IamfObuType obu_type; + while (1) { + GF_Err e; + if (!gf_bs_available(bs)) + return GF_BUFFER_TOO_SMALL; + + u64 pos = gf_bs_get_position(bs), obu_size = 0; + + e = gf_iamf_parse_obu(bs, &obu_type, &obu_size, state); + if (e) + return e; + + if (obu_size != gf_bs_get_position(bs) - pos) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF OBU size " LLU " different from consumed bytes " LLU ".\n", obu_size, gf_bs_get_position(bs) - pos)); + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (obu_type == OBU_IAMF_TEMPORAL_DELIMITER) { + if (!state->frame_state.seen_first_frame) { + // IAMF requires all or no temporal units to have temporal delimiters. Determine it from the first frame. + state->bitstream_has_temporal_delimiters = GF_TRUE; + } + if (!state->bitstream_has_temporal_delimiters) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF Expected all or no frames to have temporal delimiters.\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (state->frame_state.num_audio_frames_in_temporal_unit) { + if (state->frame_state.num_audio_frames_in_temporal_unit != state->total_substreams) + { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF Failed to find enough audio frames in a temporal unit.\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + // Read into the next temporal unit! Seek back to the end. + gf_bs_seek(bs, pos); + break; + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("IAMF OBU detected (size " LLU ")\n", obu_size)); + iamf_populate_state_from_obu(bs, pos, obu_size, obu_type, state); + + if (iamf_is_temporal_unit_obu(obu_type)) { + // Made it passed the descriptors. We are now in the first frame. + state->frame_state.seen_first_frame = GF_TRUE; + state->frame_state.seen_first_obu_in_temporal_unit = GF_TRUE; + if (iamf_is_audio_frame_obu(obu_type)) { + state->frame_state.num_audio_frames_in_temporal_unit++; + } + + if (state->frame_state.num_audio_frames_in_temporal_unit == state->total_substreams) { + // Track the cumulative trimming information from the state. + if (state->frame_state.pre_skip_is_finalized && state->frame_state.previous_num_samples_to_trim_at_start) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF Cannot have frames trimmed from start after the first frame with samples.\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } else if (!state->frame_state.pre_skip_is_finalized) { + // IAMF requires all audio frames to have the same pre-skip; infer it from the final frame. + state->pre_skip += state->frame_state.previous_num_samples_to_trim_at_start; + if (state->frame_state.previous_num_samples_to_trim_at_start < state->num_samples_per_frame) { + state->frame_state.cache_descriptor_obus = GF_FALSE; + state->frame_state.pre_skip_is_finalized = GF_TRUE; + } else { + // Cache the descriptor OBUs until we determine the pre-skip. + state->frame_state.cache_descriptor_obus = GF_TRUE; + } + } + + // All audio frames seen for this temporal unit. Proceed to the next. + state->frame_state.found_full_temporal_unit = GF_TRUE; + state->frame_state.seen_first_obu_in_temporal_unit = GF_FALSE; + state->frame_state.num_audio_frames_in_temporal_unit = 0; + break; + } + } + } + + return GF_OK; +} + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +GF_EXPORT u8 gf_mp3_version(u32 hdr) { return ((hdr >> 19) & 0x3); @@ -4685,6 +5365,7 @@ #endif /*GPAC_DISABLE_AV_PARSERS*/ +GF_EXPORT Bool gf_avcc_use_extensions(u8 profile_idc) { switch (profile_idc) { @@ -5628,7 +6309,7 @@ return 1; } -static void ref_pic_list_modification(GF_BitStream *bs, u32 slice_type) { +static void avc_ref_pic_list_modification(GF_BitStream *bs, u32 slice_type, Bool is_mvc) { if (slice_type % 5 != 2 && slice_type % 5 != 4) { if (gf_bs_read_int_log(bs, 1, "ref_pic_list_modification_flag_l0")) { u32 idx=0, modification_of_pic_nums_idc; @@ -5640,6 +6321,9 @@ else if (modification_of_pic_nums_idc == 2) { gf_bs_read_ue_log_idx(bs, "long_term_pic_num", idx); } + else if (is_mvc && ((modification_of_pic_nums_idc == 4) || (modification_of_pic_nums_idc == 5))) { + gf_bs_read_ue_log_idx(bs, "abs_diff_view_idx_minus1", idx); + } idx++; } while ((modification_of_pic_nums_idc != 3) && gf_bs_available(bs)); } @@ -5655,6 +6339,9 @@ else if (modification_of_pic_nums_idc == 2) { gf_bs_read_ue_log_idx(bs, "long_term_pic_num", idx); } + else if (is_mvc && ((modification_of_pic_nums_idc == 4) || (modification_of_pic_nums_idc == 5))) { + gf_bs_read_ue_log_idx(bs, "abs_diff_view_idx_minus1", idx); + } idx++; } while ((modification_of_pic_nums_idc != 3) && gf_bs_available(bs)); } @@ -5788,13 +6475,9 @@ } if (si->nal_unit_type == 20 || si->nal_unit_type == 21) { - //ref_pic_list_mvc_modification(); /* specified in Annex H */ - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("avc-h264 unimplemented ref_pic_list_mvc_modification() in slide header\n")); - gf_assert(0); - return -1; - } - else { - ref_pic_list_modification(bs, si->slice_type); + avc_ref_pic_list_modification(bs, si->slice_type, GF_TRUE); + } else { + avc_ref_pic_list_modification(bs, si->slice_type, GF_FALSE); } if ((si->pps->weighted_pred_flag && (si->slice_type % 5 == GF_AVC_TYPE_P || si->slice_type % 5 == GF_AVC_TYPE_SP)) @@ -5924,34 +6607,42 @@ return 1; } + pt->num_clock_ts = NumClockTSpt->pic_struct; for (i = 0; i < NumClockTSpt->pic_struct; i++) { - if (gf_bs_read_int_log_idx(bs, 1, "clock_timestamp_flag", i)) { - Bool full_timestamp_flag; + Bool clock_timestamp_flag = gf_bs_read_int_log_idx(bs, 1, "clock_timestamp_flag", i); + if (clock_timestamp_flag) { + AVCSeiPicTimingTimecode *tc = &pt->timecodesi; + tc->clock_timestamp_flag = clock_timestamp_flag; gf_bs_read_int_log_idx(bs, 2, "ct_type", i); - gf_bs_read_int_log_idx(bs, 1, "nuit_field_based_flag", i); - gf_bs_read_int_log_idx(bs, 5, "counting_type", i); - full_timestamp_flag = gf_bs_read_int_log_idx(bs, 1, "full_timestamp_flag", i); + Bool unit_field_based_flag = gf_bs_read_int_log_idx(bs, 1, "unit_field_based_flag", i); + tc->counting_type = gf_bs_read_int_log_idx(bs, 5, "counting_type", i); + Bool full_timestamp_flag = gf_bs_read_int_log_idx(bs, 1, "full_timestamp_flag", i); gf_bs_read_int_log_idx(bs, 1, "discontinuity_flag", i); - gf_bs_read_int_log_idx(bs, 1, "cnt_dropped_flag", i); - gf_bs_read_int_log_idx(bs, 8, "n_frames", i); + tc->cnt_dropped_flag = gf_bs_read_int_log_idx(bs, 1, "cnt_dropped_flag", i); + tc->n_frames = gf_bs_read_int_log_idx(bs, 8, "n_frames", i); if (full_timestamp_flag) { - gf_bs_read_int_log_idx(bs, 6, "seconds_value", i); - gf_bs_read_int_log_idx(bs, 6, "minutes_value", i); - gf_bs_read_int_log_idx(bs, 5, "hours_value", i); + tc->seconds = gf_bs_read_int_log_idx(bs, 6, "seconds_value", i); + tc->minutes = gf_bs_read_int_log_idx(bs, 6, "minutes_value", i); + tc->hours = gf_bs_read_int_log_idx(bs, 5, "hours_value", i); } else { if (gf_bs_read_int_log_idx(bs, 1, "seconds_flag", i)) { - gf_bs_read_int_log_idx(bs, 6, "seconds_value", i); + tc->seconds = gf_bs_read_int_log_idx(bs, 6, "seconds_value", i); if (gf_bs_read_int_log_idx(bs, 1, "minutes_flag", i)) { - gf_bs_read_int_log_idx(bs, 6, "minutes_value", i); + tc->minutes = gf_bs_read_int_log_idx(bs, 6, "minutes_value", i); if (gf_bs_read_int_log_idx(bs, 1, "hours_flag", i)) { - gf_bs_read_int_log_idx(bs, 5, "hours_value", i); + tc->hours = gf_bs_read_int_log_idx(bs, 5, "hours_value", i); } } } if (avc->spssps_id.vui.hrd.time_offset_length > 0) gf_bs_read_int_log_idx(bs, avc->spssps_id.vui.hrd.time_offset_length, "time_offset", i); } + if ((1 + unit_field_based_flag) * avc->spssps_id.vui.num_units_in_tick) { + tc->max_fps = gf_ceil(avc->spssps_id.vui.time_scale / ((1 + unit_field_based_flag) * avc->spssps_id.vui.num_units_in_tick)); + } else { + tc->max_fps = 0; + } } } } @@ -5959,6 +6650,48 @@ return 0; } +static s32 hevc_parse_pic_timing_sei(GF_BitStream *bs, HEVCState *hevc) +{ + int sps_id = hevc->sps_active_idx; + AVCSeiPicTiming *pt = &hevc->sei.pic_timing; + + pt->num_clock_ts = gf_bs_read_int(bs, 2); + for (int i = 0; i < pt->num_clock_ts; i++) { + Bool clock_timestamp_flag = gf_bs_read_int(bs, 1); + if (clock_timestamp_flag) { + AVCSeiPicTimingTimecode *tc = &pt->timecodesi; + tc->clock_timestamp_flag = clock_timestamp_flag; + Bool unit_field_based_flag = gf_bs_read_int_log_idx(bs, 1, "units_field_based_flag", i); + tc->counting_type = gf_bs_read_int_log_idx(bs, 5, "counting_type", i); + Bool full_timestamp_flag = gf_bs_read_int(bs, 1); + gf_bs_read_int_log_idx(bs, 1, "discontinuity_flag", i); + tc->cnt_dropped_flag = gf_bs_read_int_log_idx(bs, 1, "cnt_dropped_flag", i); + + tc->n_frames = gf_bs_read_int(bs, 9); + if (full_timestamp_flag) { + tc->seconds = gf_bs_read_int_log_idx(bs, 6, "seconds_value", i); + tc->minutes = gf_bs_read_int_log_idx(bs, 6, "minutes_value", i); + tc->hours = gf_bs_read_int_log_idx(bs, 5, "hours_value", i); + } else { + if (gf_bs_read_int_log_idx(bs, 1, "seconds_flag", i)) { + tc->seconds = gf_bs_read_int_log_idx(bs, 6, "seconds_value", i); + if (gf_bs_read_int_log_idx(bs, 1, "minutes_flag", i)) { + tc->minutes = gf_bs_read_int_log_idx(bs, 6, "minutes_value", i); + if (gf_bs_read_int_log_idx(bs, 1, "hours_flag", i)) { + tc->hours = gf_bs_read_int_log_idx(bs, 5, "hours_value", i); + } + } + } + } + if ((1 + unit_field_based_flag) * hevc->spssps_id.num_units_in_tick) { + tc->max_fps = gf_ceil(hevc->spssps_id.time_scale / ((1 + unit_field_based_flag) * hevc->spssps_id.num_units_in_tick)); + } else { + tc->max_fps = 0; + } + } + } + return 0; +} static void avc_parse_itu_t_t35_sei(GF_BitStream* bs, AVCSeiItuTT35DolbyVision *dovi) { @@ -5978,7 +6711,8 @@ AVC_PIC_FIELD_TOP, AVC_PIC_FIELD_BOTTOM, } pic_type; - s32 field_poc2 = { 0,0 }; + //A bit range greater than 32 bits should be allocated for the variables TopFieldOrderCnt and BottomFieldOrderCnt + s64 field_poc2 = { 0,0 }; s32 max_frame_num; if (!si->sps) return; @@ -6085,11 +6819,11 @@ /*ISO 14496-10 N.11084 eq (8-1)*/ if (pic_type == AVC_PIC_FRAME) - si->poc = MIN(field_poc0, field_poc1); + si->poc = (s32) MIN(field_poc0, field_poc1); else if (pic_type == AVC_PIC_FIELD_TOP) - si->poc = field_poc0; + si->poc = (s32) field_poc0; else - si->poc = field_poc1; + si->poc = (s32) field_poc1; } GF_EXPORT @@ -6267,8 +7001,7 @@ return ret; } - -u32 gf_avc_reformat_sei(u8 *buffer, u32 nal_size, Bool isobmf_rewrite, AVCState *avc) +u32 gf_avc_reformat_sei(u8 *buffer, u32 nal_size, Bool isobmf_rewrite, AVCState *avc, SEI_Filter *sei_filter) { u32 ptype, psize, hdr, var; u32 start; @@ -6276,6 +7009,7 @@ GF_BitStream *bs_dest = NULL; u8 nhdr; Bool sei_removed = GF_FALSE; + Bool all_sei_removed = GF_TRUE; char store; hdr = buffer0; @@ -6306,10 +7040,22 @@ if (v != 0xFF) break; } - start = (u32)gf_bs_get_position(bs); - - do_copy = 1; + //check if we need to copy this SEI + do_copy = GF_TRUE; + if (sei_filter) { + do_copy = !sei_filter->is_whitelist; + do_copy &= (sei_filter->seis.nb_items > 0); // if no SEI type specified, drop all + for (u32 i = 0; i < sei_filter->seis.nb_items; i++) { + if (sei_filter->seis.valsi == ptype) { + do_copy = !do_copy; + break; + } + } + if (sei_filter->extra_filter == - (s32)ptype) + do_copy = GF_FALSE; + } + start = (u32)gf_bs_get_position(bs); if (start + psize >= nal_size) { GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("avc-h264 SEI user message type %d size error (%d but %d remain), keeping full SEI untouched\n", ptype, psize, nal_size - start)); if (bs_dest) gf_bs_del(bs_dest); @@ -6322,8 +7068,7 @@ case 10: /*sub_seq info*/ case 11: /*sub_seq_layer char*/ case 12: /*sub_seq char*/ - do_copy = 0; - sei_removed = GF_TRUE; + do_copy = GF_FALSE; break; case 5: /*user unregistered */ store = bufferstart + psize; @@ -6333,11 +7078,13 @@ break; case 6: /*recovery point*/ - avc_parse_recovery_point_sei(bs, avc); + if (avc) avc_parse_recovery_point_sei(bs, avc); break; case 1: /*pic_timing*/ - avc_parse_pic_timing_sei(bs, avc); + if (avc) { + avc_parse_pic_timing_sei(bs, avc); + } break; case 0: /*buffering period*/ @@ -6357,6 +7104,7 @@ } if (do_copy && bs_dest) { + all_sei_removed = GF_FALSE; var = ptype; while (var >= 255) { gf_bs_write_int(bs_dest, 0xFF, 8); @@ -6380,10 +7128,11 @@ } } else { + if (!do_copy) sei_removed = GF_TRUE; gf_bs_seek(bs, start); //bs_skip_bytes does not skip EPB, skip byte per byte - while (psize) { + while (psize && gf_bs_available(bs)) { gf_bs_read_u8(bs); psize--; } @@ -6402,6 +7151,12 @@ } } gf_bs_del(bs); + + if (all_sei_removed) { + if (bs_dest) gf_bs_del(bs_dest); + return 0; + } + //we cannot compare final size and original size since original may have EPB and final does not yet have them if (bs_dest && sei_removed) { u8 *dst_no_epb = NULL; @@ -6639,8 +7394,6 @@ time_scale = gf_bs_read_int(orig, 32); if (codec == GF_CODECID_AVC) { fixed_frame_rate_flag = gf_bs_read_int(orig, 1); - - //LAST bit read for AVC } else if (codec == GF_CODECID_HEVC) { poc_proportional_to_timing_flag = gf_bs_read_int(orig, 1); if (poc_proportional_to_timing_flag) @@ -6880,13 +7633,70 @@ } } + //transfer hrd parameters (if any) + if (codec == GF_CODECID_AVC) { + if (vui_present_flag) { + AVC_HRD hrd; + Bool nal_hrd_parameters_present_flag = gf_bs_read_int(orig, 1); + gf_bs_write_int(mod, nal_hrd_parameters_present_flag, 1); + if (nal_hrd_parameters_present_flag) { + u32 pos_bp = gf_bs_get_bit_position(orig); + u32 pos = (u32) gf_bs_get_position(orig); + u32 start = gf_bs_get_bit_offset(orig); + avc_parse_hrd_parameters(orig, &hrd); + u32 size = gf_bs_get_bit_offset(orig) - start; + + //seek back + gf_bs_seek(orig, pos); + while (pos_bp--) gf_bs_read_int(orig, 1); + + //copy over nal hrd parameters + while (size--) { + u8 val = gf_bs_read_int(orig, 1); + gf_bs_write_int(mod, val, 1); + } + } + + Bool vcl_hrd_parameters_present_flag = gf_bs_read_int(orig, 1); + gf_bs_write_int(mod, vcl_hrd_parameters_present_flag, 1); + if (vcl_hrd_parameters_present_flag) { + u32 pos_bp = gf_bs_get_bit_position(orig); + u32 pos = (u32) gf_bs_get_position(orig); + u32 start = gf_bs_get_bit_offset(orig); + avc_parse_hrd_parameters(orig, &hrd); + u32 size = gf_bs_get_bit_offset(orig) - start; + + //seek back + gf_bs_seek(orig, pos); + while (pos_bp--) gf_bs_read_int(orig, 1); + + //copy over nal hrd parameters + while (size--) { + u8 val = gf_bs_read_int(orig, 1); + gf_bs_write_int(mod, val, 1); + } + } + + if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) { + u8 low_delay_hrd_flag = gf_bs_read_int(orig, 1); + gf_bs_write_int(mod, low_delay_hrd_flag, 1); + } + + gf_bs_read_int(orig, 1); /*pic_struct_present*/ + + //LAST bit read for AVC + } else { + gf_bs_write_int(mod, 0, 1); /*nal_hrd_parameters_present*/ + gf_bs_write_int(mod, 0, 1); /*vcl_hrd_parameters_present*/ + } + + gf_bs_write_int(mod, vui_info->enable_pic_struct, 1); + } + /*no VUI in input bitstream but we just inserted one, set all remaining vui flags to 0*/ if (!vui_present_flag) { if (codec == GF_CODECID_AVC) { - gf_bs_write_int(mod, 0, 1); /*nal_hrd_parameters_present*/ - gf_bs_write_int(mod, 0, 1); /*vcl_hrd_parameters_present*/ - gf_bs_write_int(mod, 0, 1); /*pic_struct_present*/ - gf_bs_write_int(mod, 0, 1); /*bitstream_restriction*/ + gf_bs_write_int(mod, 0, 1); /*bitstream_restriction*/ } else if (codec == GF_CODECID_HEVC) { if (timing_info_present_flag) { gf_bs_write_int(mod, 0, 1); /*vui_hrd_parameters_present_flag*/ @@ -6899,7 +7709,7 @@ GF_Err gf_avc_change_vui(GF_AVCConfig *avcc, GF_VUIInfo *vui_info) { - AVCState avc; + AVCState *avc_state; u32 i, bit_offset, flag; s32 idx; GF_AVCConfigSlot *slc; @@ -6907,15 +7717,16 @@ if (!avcc) return GF_NON_COMPLIANT_BITSTREAM; - memset(&avc, 0, sizeof(AVCState)); - avc.sps_active_idx = -1; + GF_SAFEALLOC(avc_state, AVCState); + if (!avc_state) return GF_OUT_OF_MEM; + avc_state->sps_active_idx = -1; i=0; while ((slc = (GF_AVCConfigSlot *)gf_list_enum(avcc->sequenceParameterSets, &i))) { GF_BitStream *orig, *mod; u8 *no_emulation_buf = NULL; u32 no_emulation_buf_size = 0, emulation_bytes = 0; - idx = gf_avc_read_sps(slc->data, slc->size, &avc, 0, &bit_offset); + idx = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, &bit_offset); if (idx<0) { continue; } @@ -6957,6 +7768,7 @@ gf_bs_del(mod); gf_free(no_emulation_buf); } + gf_free(avc_state); return GF_OK; } @@ -6994,22 +7806,25 @@ GF_EXPORT GF_Err gf_avc_get_sps_info(u8 *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d) { - AVCState avc; + AVCState *avc_state; s32 idx; - memset(&avc, 0, sizeof(AVCState)); - avc.sps_active_idx = -1; + GF_SAFEALLOC(avc_state, AVCState); + if (!avc_state) return GF_OUT_OF_MEM; + avc_state->sps_active_idx = -1; - idx = gf_avc_read_sps(sps_data, sps_size, &avc, 0, NULL); + idx = gf_avc_read_sps(sps_data, sps_size, avc_state, 0, NULL); if (idx < 0) { + gf_free(avc_state); return GF_NON_COMPLIANT_BITSTREAM; } if (sps_id) *sps_id = idx; - if (width) *width = avc.spsidx.width; - if (height) *height = avc.spsidx.height; - if (par_n) *par_n = avc.spsidx.vui.par_num ? avc.spsidx.vui.par_num : (u32)-1; - if (par_d) *par_d = avc.spsidx.vui.par_den ? avc.spsidx.vui.par_den : (u32)-1; + if (width) *width = avc_state->spsidx.width; + if (height) *height = avc_state->spsidx.height; + if (par_n) *par_n = avc_state->spsidx.vui.par_num ? avc_state->spsidx.vui.par_num : (u32)-1; + if (par_d) *par_d = avc_state->spsidx.vui.par_den ? avc_state->spsidx.vui.par_den : (u32)-1; + gf_free(avc_state); return GF_OK; } @@ -7104,7 +7919,7 @@ s32 used_by_curr_pic_flag = gf_bs_read_int_log_idx2(bs, 1, "used_by_curr_pic_flag", idx_rps, i); ref_idc = used_by_curr_pic_flag ? 1 : 0; if (!used_by_curr_pic_flag) { - used_by_curr_pic_flag = gf_bs_read_int_log_idx2(bs, 1, "used_by_curr_pic_flag", idx_rps, i); + used_by_curr_pic_flag = gf_bs_read_int_log_idx2(bs, 1, "use_delta_flag", idx_rps, i); ref_idc = used_by_curr_pic_flag << 1; } if ((ref_idc == 1) || (ref_idc == 2)) { @@ -7117,8 +7932,10 @@ deltaPOC += ref_ps->delta_poci; } - if (k<16) + if (k<32) { rps->delta_pock = deltaPOC; + rps->used_by_curr_pick = used_by_curr_pic_flag; + } if (deltaPOC < 0) k0++; else k1++; @@ -7130,26 +7947,36 @@ rps->num_positive_pics = k1; } else { - s32 prev = 0, poc; + s32 prev = 0, poc, offset; + Bool nb_pics_valid = GF_TRUE; sps->rpsidx_rps.num_negative_pics = gf_bs_read_ue_log_idx(bs, "num_negative_pics", idx_rps); sps->rpsidx_rps.num_positive_pics = gf_bs_read_ue_log_idx(bs, "num_positive_pics", idx_rps); - if (sps->rpsidx_rps.num_negative_pics > 16) - return GF_FALSE; - if (sps->rpsidx_rps.num_positive_pics > 16) + if (sps->rpsidx_rps.num_negative_pics > 16) { + sps->rpsidx_rps.num_negative_pics = 0; + nb_pics_valid = GF_FALSE; + } + if (sps->rpsidx_rps.num_positive_pics > 16) { + sps->rpsidx_rps.num_positive_pics = 0; + nb_pics_valid = GF_FALSE; + } + if (!nb_pics_valid) return GF_FALSE; + for (i = 0; i < sps->rpsidx_rps.num_negative_pics; i++) { u32 delta_poc_s0_minus1 = gf_bs_read_ue_log_idx2(bs, "delta_poc_s0_minus1", idx_rps, i); poc = prev - delta_poc_s0_minus1 - 1; prev = poc; sps->rpsidx_rps.delta_poci = poc; - gf_bs_read_int_log_idx2(bs, 1, "used_by_curr_pic_s0_flag", idx_rps, i); + sps->rpsidx_rps.used_by_curr_pici = gf_bs_read_int_log_idx2(bs, 1, "used_by_curr_pic_s0_flag", idx_rps, i); } + prev = 0; + offset = sps->rpsidx_rps.num_negative_pics; for (i = 0; i < sps->rpsidx_rps.num_positive_pics; i++) { u32 delta_poc_s1_minus1 = gf_bs_read_ue_log_idx2(bs, "delta_poc_s1_minus1" , idx_rps, i); poc = prev + delta_poc_s1_minus1 + 1; prev = poc; - sps->rpsidx_rps.delta_poci = poc; - gf_bs_read_int_log_idx2(bs, 1, "used_by_curr_pic_s1_flag", idx_rps, i); + sps->rpsidx_rps.delta_pocoffset + i = poc; + sps->rpsidx_rps.used_by_curr_picoffset + i = gf_bs_read_int_log_idx2(bs, 1, "used_by_curr_pic_s1_flag", idx_rps, i); } } return GF_TRUE; @@ -7201,41 +8028,38 @@ } } -static -Bool ref_pic_lists_modification(GF_BitStream *bs, u32 slice_type, u32 num_ref_idx_l0_active, u32 num_ref_idx_l1_active) +static void hevc_ref_pic_lists_modification(GF_BitStream *bs, HEVC_ReferencePictureSets *rps, u32 slice_type, u32 num_ref_idx_l0_active, u32 num_ref_idx_l1_active, u32 NumPicTotalCurr) { - //u32 i; - Bool ref_pic_list_modification_flag_l0 = gf_bs_read_int_log(bs, 1, "ref_pic_list_modification_flag_l0"); - if (ref_pic_list_modification_flag_l0) { - /*for (i=0; i<num_ref_idx_l0_active; i++) { - list_entry_l0i = *//*gf_bs_read_int(bs, (u32)ceil(log(getNumPicTotalCurr())/log(2))); - }*/ - return GF_FALSE; + u32 i; + rps->modif_flag_l0 = gf_bs_read_int_log(bs, 1, "ref_pic_list_modification_flag_l0"); + u32 nb_bits = 1, val=NumPicTotalCurr-1; + while ( val >>= 1) nb_bits++; + + if (rps->modif_flag_l0) { + for (i=0; i<MIN(num_ref_idx_l0_active, GF_ARRAY_LENGTH(rps->modif_idx_l0)); i++) { + rps->modif_idx_l0i = gf_bs_read_int(bs, nb_bits); + } } if (slice_type == GF_HEVC_SLICE_TYPE_B) { - Bool ref_pic_list_modification_flag_l1 = gf_bs_read_int_log(bs, 1, "ref_pic_list_modification_flag_l1"); - if (ref_pic_list_modification_flag_l1) { - /*for (i=0; i<num_ref_idx_l1_active; i++) { - list_entry_l1i = *//*gf_bs_read_int(bs, (u32)ceil(log(getNumPicTotalCurr()) / log(2))); - }*/ - return GF_FALSE; + rps->modif_flag_l1 = gf_bs_read_int_log(bs, 1, "ref_pic_list_modification_flag_l1"); + if (rps->modif_flag_l1) { + for (i=0; i<MIN(num_ref_idx_l1_active, GF_ARRAY_LENGTH(rps->modif_idx_l1)); i++) { + rps->modif_idx_l1i = gf_bs_read_int(bs, nb_bits); + } } } - - return GF_TRUE; } static s32 hevc_parse_slice_segment(GF_BitStream *bs, HEVCState *hevc, HEVCSliceInfo *si) { u32 i, j; - u32 num_ref_idx_l0_active = 0, num_ref_idx_l1_active = 0; HEVC_PPS *pps; HEVC_SPS *sps; s32 pps_id; Bool RapPicFlag = GF_FALSE; Bool IDRPicFlag = GF_FALSE; - + u64 bs_start_offset = gf_bs_get_position(bs)-2; si->first_slice_segment_in_pic_flag = gf_bs_read_int_log(bs, 1, "first_slice_segment_in_pic_flag"); switch (si->nal_unit_type) { @@ -7264,6 +8088,7 @@ sps = &hevc->spspps->sps_id; si->sps = sps; si->pps = pps; + si->num_ref_idx_l0_active = si->num_ref_idx_l1_active = 0; if (!si->first_slice_segment_in_pic_flag && pps->dependent_slice_segments_enabled_flag) { si->dependent_slice_segment_flag = gf_bs_read_int_log(bs, 1, "dependent_slice_segment_flag"); @@ -7285,6 +8110,9 @@ Bool slice_sao_luma_flag = 0; Bool slice_sao_chroma_flag = 0; Bool slice_deblocking_filter_disabled_flag = 0; + si->st_rps = NULL; + si->nb_reference_pocs = 0; + si->nb_lt_ref_pics = 0; //"slice_reserved_undetermined_flag" gf_bs_read_int_log(bs, pps->num_extra_slice_header_bits, "slice_reserved_undetermined_flag"); @@ -7310,10 +8138,12 @@ //if not asked to parse full header, abort once we have the poc if (!hevc->full_slice_header_parse) return 0; + u32 st_ref_idx = 0; if (gf_bs_read_int_log(bs, 1, "short_term_ref_pic_set_sps_flag") == 0) { Bool ret = hevc_parse_short_term_ref_pic_set(bs, sps, sps->num_short_term_ref_pic_sets); if (!ret) return -1; + st_ref_idx = sps->num_short_term_ref_pic_sets; } else if (sps->num_short_term_ref_pic_sets > 1) { u32 numbits = 0; @@ -7321,10 +8151,11 @@ while ((u32)(1 << numbits) < sps->num_short_term_ref_pic_sets) numbits++; if (numbits > 0) - gf_bs_read_int_log(bs, numbits, "short_term_ref_pic_set_idx"); - /*else - short_term_ref_pic_set_idx = 0;*/ + st_ref_idx = gf_bs_read_int_log(bs, numbits, "short_term_ref_pic_set_idx"); } + si->st_rps = &sps->rpsst_ref_idx; + si->st_rps->modif_flag_l0 = si->st_rps->modif_flag_l1 = 0; + if (sps->long_term_ref_pics_present_flag) { u8 DeltaPocMsbCycleLt32; u32 num_long_term_sps = 0; @@ -7338,7 +8169,9 @@ num_long_term_pics = gf_bs_read_ue_log(bs, "num_long_term_pics"); if (num_long_term_sps+num_long_term_pics>32) return -1; - for (i = 0; i < num_long_term_sps + num_long_term_pics; i++) { + si->nb_lt_ref_pics = num_long_term_sps + num_long_term_pics; + + for (i = 0; i < si->nb_lt_ref_pics; i++) { if (i < num_long_term_sps) { if (sps->num_long_term_ref_pic_sps > 1) gf_bs_read_int_log_idx(bs, gf_get_bit_size(sps->num_long_term_ref_pic_sps), "lt_idx_sps", i); @@ -7358,6 +8191,55 @@ if (sps->temporal_mvp_enable_flag) slice_temporal_mvp_enabled_flag = gf_bs_read_int_log(bs, 1, "slice_temporal_mvp_enabled_flag"); } + + u32 NumActiveRefLayerPics = 0; + HEVC_VPS *vps = &hevc->vpssps->vps_id; + if (si->layer_id) { + u32 numRefLayerPics = 0; + + for (i=0, j=0; i<vps->num_direct_ref_layerssi->layer_id; i++) { + u32 refLayerIdx = vps->layer_idx_in_vpsvps->id_direct_ref_layerssi->layer_idi; + if ((vps->sub_layers_vps_max_minus1refLayerIdx >= si->temporal_id) + && ((si->temporal_id == 0) || (vps->max_tid_il_ref_pics_plus1 refLayerIdx vps->layer_idx_in_vps si->layer_id > si->temporal_id) ) + ) { + //refLayerPicIdc + j++; + // = i; + } + } + numRefLayerPics = j; + + if (numRefLayerPics == 0) + NumActiveRefLayerPics = 0; + else if (vps->default_ref_layers_active_flag) + NumActiveRefLayerPics = numRefLayerPics; + } + + if ((si->layer_id>0) + && !vps->default_ref_layers_active_flag + && (vps->num_direct_ref_layerssi->layer_id>0) + ) { + Bool inter_layer_pred_enabled_flag = gf_bs_read_int_log(bs, 1, "inter_layer_pred_enabled_flag"); + if (inter_layer_pred_enabled_flag && (vps->num_direct_ref_layerssi->layer_id > 1) ) { + if (!vps->max_one_active_ref_layer_flag) { + u32 nb_bits = 1, val = vps->num_direct_ref_layerssi->layer_id; //+1 - 1 for ceil(log2) + while ( val >>= 1) nb_bits++; + u32 num_inter_layer_ref_pics_minus1 = gf_bs_read_int_log(bs, nb_bits, "num_inter_layer_ref_pics_minus1"); + NumActiveRefLayerPics = num_inter_layer_ref_pics_minus1 + 1; + } + if (NumActiveRefLayerPics != vps->num_direct_ref_layerssi->layer_id) { + for (i=0; i<NumActiveRefLayerPics; i++) { + u32 nb_bits = 1, val = vps->num_direct_ref_layerssi->layer_id; //+1 - 1 for ceil(log2) + while ( val >>= 1) nb_bits++; + /*u32 inter_layer_pred_layer_idc = */gf_bs_read_int_log(bs, nb_bits, "inter_layer_pred_layer_idc"); + } + } + } else if (!inter_layer_pred_enabled_flag) { + NumActiveRefLayerPics = 0; + } else if (vps->max_one_active_ref_layer_flag || vps->num_ref_list_layerssi->layer_id == 1) + NumActiveRefLayerPics = 1; + } + if (sps->sample_adaptive_offset_enabled_flag) { u32 ChromaArrayType = sps->separate_colour_plane_flag ? 0 : sps->chroma_format_idc; slice_sao_luma_flag = gf_bs_read_int_log(bs, 1, "slice_sao_luma_flag"); @@ -7367,21 +8249,36 @@ if (si->slice_type == GF_HEVC_SLICE_TYPE_P || si->slice_type == GF_HEVC_SLICE_TYPE_B) { //u32 NumPocTotalCurr; - num_ref_idx_l0_active = pps->num_ref_idx_l0_default_active; - num_ref_idx_l1_active = 0; + si->num_ref_idx_l0_active = pps->num_ref_idx_l0_default_active; + si->num_ref_idx_l1_active = 0; if (si->slice_type == GF_HEVC_SLICE_TYPE_B) - num_ref_idx_l1_active = pps->num_ref_idx_l1_default_active; + si->num_ref_idx_l1_active = pps->num_ref_idx_l1_default_active; if (gf_bs_read_int_log(bs, 1, "num_ref_idx_active_override_flag")) { - num_ref_idx_l0_active = 1 + gf_bs_read_ue_log(bs, "num_ref_idx_l0_active"); + si->num_ref_idx_l0_active = 1 + gf_bs_read_ue_log(bs, "num_ref_idx_l0_active"); if (si->slice_type == GF_HEVC_SLICE_TYPE_B) - num_ref_idx_l1_active = 1 + gf_bs_read_ue_log(bs, "num_ref_idx_l1_active"); + si->num_ref_idx_l1_active = 1 + gf_bs_read_ue_log(bs, "num_ref_idx_l1_active"); } - if (pps->lists_modification_present_flag /*TODO: && NumPicTotalCurr > 1*/) { - if (!ref_pic_lists_modification(bs, si->slice_type, num_ref_idx_l0_active, num_ref_idx_l1_active)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("hevc ref_pic_lists_modification( ) not implemented\n")); - return -1; + if (pps->lists_modification_present_flag && si->st_rps) { + u32 NumPicTotalCurr = 0; + HEVC_ReferencePictureSets *rps = si->st_rps; + for (i=0; i < rps->num_negative_pics; i++) { + if (rps->used_by_curr_pici) NumPicTotalCurr++; + } + for (; i < rps->num_negative_pics+rps->num_positive_pics; i++) { + if (rps->used_by_curr_pici) NumPicTotalCurr++; + } + //TODO long term pics !! + if (pps->curr_pic_ref_enabled_flag) + NumPicTotalCurr++; + + if (si->layer_id) { + NumPicTotalCurr += NumActiveRefLayerPics; + } + + if (NumPicTotalCurr>1) { + hevc_ref_pic_lists_modification(bs, rps, si->slice_type, si->num_ref_idx_l0_active, si->num_ref_idx_l1_active, NumPicTotalCurr); } } @@ -7396,8 +8293,8 @@ if (si->slice_type == GF_HEVC_SLICE_TYPE_B) collocated_from_l0_flag = gf_bs_read_int_log(bs, 1, "collocated_from_l0_flag"); - if ((collocated_from_l0_flag && (num_ref_idx_l0_active > 1)) - || (!collocated_from_l0_flag && (num_ref_idx_l1_active > 1)) + if ((collocated_from_l0_flag && (si->num_ref_idx_l0_active > 1)) + || (!collocated_from_l0_flag && (si->num_ref_idx_l1_active > 1)) ) { gf_bs_read_ue_log(bs, "collocated_ref_idx"); } @@ -7406,7 +8303,7 @@ if ((pps->weighted_pred_flag && si->slice_type == GF_HEVC_SLICE_TYPE_P) || (pps->weighted_bipred_flag && si->slice_type == GF_HEVC_SLICE_TYPE_B) ) { - hevc_pred_weight_table(bs, hevc, si, pps, sps, num_ref_idx_l0_active, num_ref_idx_l1_active); + hevc_pred_weight_table(bs, hevc, si, pps, sps, si->num_ref_idx_l0_active, si->num_ref_idx_l1_active); } gf_bs_read_ue_log(bs, "five_minus_max_num_merge_cand"); } @@ -7472,10 +8369,10 @@ } } - si->header_size_bits = (gf_bs_get_position(bs) - 1) * 8 + gf_bs_get_bit_position(bs); // av_parser.c modified on 16 jan. 2019 + si->header_size_bits = (gf_bs_get_position(bs) - 1 - bs_start_offset) * 8 + gf_bs_get_bit_position(bs); // av_parser.c modified on 16 jan. 2019 if (gf_bs_read_int_log(bs, 1, "byte_align") == 0) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("Error parsing slice header: byte_align not found at end of header !\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("Error parsing slice header: byte_align not found at end of header NALU type %u!\n")); } gf_bs_align(bs); @@ -7483,12 +8380,286 @@ return 0; } +static void gf_hevc_push_ref_poc(HEVCSliceInfo *si, s32 poc) +{ + u32 i; + for (i=0;i<si->nb_reference_pocs; i++) { + if (si->reference_pocsi==poc) return; + } + if (si->nb_reference_pocs==GF_ARRAY_LENGTH(si->reference_pocs)) return; + si->reference_pocssi->nb_reference_pocs = poc; + si->nb_reference_pocs++; +} + +static void gf_hevc_compute_ref_list(HEVCState *hevc, HEVCSliceInfo *si) +{ + u32 i; + HEVC_ReferencePictureSets *rps = si->st_rps; + + if (si->slice_type == GF_HEVC_SLICE_TYPE_I) + return; + + if (!rps) return; + if (si->nb_lt_ref_pics) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("HEVC Long-term picture not yet supported for refPicList parsing\n")); + si->nb_reference_pocs = 0; + return; + } + + u32 nb_poc_st_curr0=0; + s32 poc_st_curr016; + u32 nb_poc_st_curr1=0; + s32 poc_st_curr116; + u32 nb_poc_lt_curr=0; + s32 poc_lt_curr16; + for (i=0; i < rps->num_negative_pics; i++) { + if (i>=GF_ARRAY_LENGTH(rps->used_by_curr_pic) || i>=GF_ARRAY_LENGTH(rps->delta_poc) || nb_poc_st_curr0>=GF_ARRAY_LENGTH(poc_st_curr0)) + break; + if (!rps->used_by_curr_pici) continue; + poc_st_curr0nb_poc_st_curr0 = si->poc + rps->delta_poci; + nb_poc_st_curr0++; + } + + for (; i < rps->num_negative_pics+rps->num_positive_pics; i++) { + if (i>=GF_ARRAY_LENGTH(rps->used_by_curr_pic) || i>=GF_ARRAY_LENGTH(rps->delta_poc) || nb_poc_st_curr1>=GF_ARRAY_LENGTH(poc_st_curr1)) + break; + if (!rps->used_by_curr_pici) continue; + poc_st_curr1nb_poc_st_curr1 = si->poc + rps->delta_poci; + nb_poc_st_curr1++; + } + //todo long term and multi layer + u32 num_long_term_pictures = 0; + u32 num_interlayer_ref_idx = 0; + for (i = rps->num_negative_pics + rps->num_positive_pics + num_long_term_pictures - 1; i >rps->num_negative_pics + rps->num_positive_pics-1 ; i--) { + if (i>=GF_ARRAY_LENGTH(rps->used_by_curr_pic) || nb_poc_lt_curr>=GF_ARRAY_LENGTH(poc_lt_curr)) + break; + if (!rps->used_by_curr_pici) continue; + poc_lt_currnb_poc_lt_curr = 0; //todo, get LT from SH + nb_poc_lt_curr++; + } + //compute deps + u32 num_poc_total = nb_poc_st_curr0 + nb_poc_st_curr1 + nb_poc_lt_curr + num_interlayer_ref_idx; + //build L0 + s32 ref_pocs_l032; + u32 nb_poc_l0 = 0; + for (i=0; i<MIN(nb_poc_st_curr0, GF_ARRAY_LENGTH(ref_pocs_l0)); i++, nb_poc_l0++) { + ref_pocs_l0nb_poc_l0 = poc_st_curr0i; + } + for ( i=0; i<MIN(nb_poc_st_curr1, GF_ARRAY_LENGTH(ref_pocs_l0)); i++, nb_poc_l0++) { + ref_pocs_l0nb_poc_l0 = poc_st_curr1i; + } + for (i=0; i<MIN(nb_poc_lt_curr, GF_ARRAY_LENGTH(ref_pocs_l0)); i++, nb_poc_l0++) { + ref_pocs_l0nb_poc_l0 = poc_lt_curri; + } + assert(nb_poc_l0 == num_poc_total); + + //no need to compute L1, same IDs as in L0 + s32 ref_pocs_l132; + u32 nb_poc_l1 = 0; + if (si->slice_type == GF_HEVC_SLICE_TYPE_B) { + for ( i=0; i<nb_poc_st_curr1; i++, nb_poc_l1++) { + if (i>=GF_ARRAY_LENGTH(poc_st_curr1) || nb_poc_l1>=GF_ARRAY_LENGTH(ref_pocs_l1)) + break; + ref_pocs_l1nb_poc_l1 = poc_st_curr1i; + } + for ( i=0; i<nb_poc_st_curr0; i++, nb_poc_l1++) { + if (i>=GF_ARRAY_LENGTH(poc_st_curr0) || nb_poc_l1>=GF_ARRAY_LENGTH(ref_pocs_l1)) + break; + ref_pocs_l1nb_poc_l1 = poc_st_curr0i; + } + for ( i=0; i<nb_poc_lt_curr; i++, nb_poc_l1++) { + if (i>=GF_ARRAY_LENGTH(poc_lt_curr) || nb_poc_l1>=GF_ARRAY_LENGTH(ref_pocs_l1)) + break; + ref_pocs_l1nb_poc_l1 = poc_lt_curri; + } + assert(nb_poc_l1 == num_poc_total); + } + if (rps->modif_flag_l0 || num_poc_total) { + for (i=0; i<si->num_ref_idx_l0_active; i++) { + u32 idx = (u32)-1; + if (rps->modif_flag_l0 && i<GF_ARRAY_LENGTH(rps->modif_idx_l0)) + idx = rps->modif_idx_l0i; + else if (num_poc_total) + idx = i%num_poc_total; + if (idx < GF_ARRAY_LENGTH(ref_pocs_l0)) + gf_hevc_push_ref_poc(si, ref_pocs_l0idx); + } + } + if (rps->modif_flag_l1 || num_poc_total) { + for (i=0; i<si->num_ref_idx_l1_active; i++) { + u32 idx = (u32)-1; + if (rps->modif_flag_l1 && i<GF_ARRAY_LENGTH(rps->modif_idx_l1)) + idx = rps->modif_idx_l1i; + else if (num_poc_total) + idx = i%num_poc_total; + if (idx < GF_ARRAY_LENGTH(ref_pocs_l1)) + gf_hevc_push_ref_poc(si, ref_pocs_l1idx); + } + } +} + +u32 gf_hevc_vvc_reformat_sei(u8 *buffer, u32 nal_size, Bool isobmf_rewrite, Bool is_hevc, SEI_Filter *sei_filter) +{ + u32 ptype, psize, hdr, var; + u64 start; + GF_BitStream *bs; + GF_BitStream *bs_dest = NULL; + u16 nhdr; + Bool sei_removed = GF_FALSE; + Bool all_sei_removed = GF_TRUE; + + hdr = buffer!is_hevc; + switch (is_hevc ? (hdr & 0x7e) >> 1 : hdr >> 3) + { + case GF_HEVC_NALU_SEI_PREFIX: + case GF_HEVC_NALU_SEI_SUFFIX: + case GF_VVC_NALU_SEI_PREFIX: + case GF_VVC_NALU_SEI_SUFFIX: + break; + default: + return nal_size; + } + + if (isobmf_rewrite) bs_dest = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + bs = gf_bs_new(buffer, nal_size, GF_BITSTREAM_READ); + gf_bs_enable_emulation_byte_removal(bs, GF_TRUE); + + nhdr = gf_bs_read_int(bs, 16); + if (bs_dest) gf_bs_write_int(bs_dest, nhdr, 16); + + /*parse SEI*/ + while (gf_bs_available(bs)) { + u32 consumed, nb_zeros; + Bool do_copy; + ptype = 0; + while (gf_bs_peek_bits(bs, 8, 0)==0xFF) { + gf_bs_read_int(bs, 8); + ptype += 255; + } + ptype += gf_bs_read_int(bs, 8); + psize = 0; + while (gf_bs_peek_bits(bs, 8, 0)==0xFF) { + gf_bs_read_int(bs, 8); + psize += 255; + } + psize += gf_bs_read_int(bs, 8); + + //check if we need to copy this SEI + do_copy = GF_TRUE; + if (sei_filter) { + do_copy = !sei_filter->is_whitelist; + do_copy &= (sei_filter->seis.nb_items > 0); // if no SEI type specified, drop all + for (u32 i = 0; i < sei_filter->seis.nb_items; i++) { + if (sei_filter->seis.valsi == ptype) { + do_copy = !do_copy; + break; + } + } + if (sei_filter->extra_filter == -(s32)ptype) + do_copy = GF_FALSE; + } + + start = gf_bs_get_position(bs); + if (start + psize >= nal_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("%s SEI user message type %d size error (%d but %d remain), keeping full SEI untouched\n", is_hevc ? "HEVC" : "VVC", ptype, psize, nal_size-start)); + if (bs_dest) gf_bs_del(bs_dest); + gf_bs_del(bs); + return nal_size; + } + + nb_zeros = gf_bs_get_emulation_byte_removed(bs); + if (do_copy && bs_dest) { + all_sei_removed = GF_FALSE; + var = ptype; + while (var >= 255) { + gf_bs_write_int(bs_dest, 0xFF, 8); + var -= 255; + } + gf_bs_write_int(bs_dest, var, 8); + + var = psize; + while (var >= 255) { + gf_bs_write_int(bs_dest, 0xFF, 8); + var -= 255; + } + gf_bs_write_int(bs_dest, var, 8); + gf_bs_seek(bs, start); + + //bs_read_data does not skip EPB, read byte per byte + var = psize; + while (var) { + gf_bs_write_u8(bs_dest, gf_bs_read_u8(bs)); + var--; + } + } else if (!do_copy) { + sei_removed = GF_TRUE; + gf_bs_seek(bs, start); + + //bs_skip_bytes does not skip EPB, skip byte per byte + var = psize; + while (var) { + gf_bs_read_u8(bs); + var--; + } + } + + nb_zeros = gf_bs_get_emulation_byte_removed(bs) - nb_zeros; + gf_bs_align(bs); + if (bs_dest) gf_bs_align(bs_dest); + consumed = (u32) (gf_bs_get_position(bs) - start); + consumed -= nb_zeros; + psize -= consumed; + //do not use skip bytes due to possible EPB + while (psize) { + gf_bs_read_u8(bs); + psize--; + } + + if (gf_bs_available(bs) <= 2) { + var = gf_bs_read_int(bs, 8); + if (var != 0x80) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("%s SEI user message has less than 2 bytes remaining but no end of sei found, keeping full SEI untouched\n", is_hevc ? "HEVC" : "VVC")); + if (bs_dest) gf_bs_del(bs_dest); + gf_bs_del(bs); + return nal_size; + } + if (bs_dest) gf_bs_write_int(bs_dest, 0x80, 8); + break; + } + } + gf_bs_del(bs); + + if (all_sei_removed) { + if (bs_dest) gf_bs_del(bs_dest); + return 0; + } + + //we cannot compare final size and original size since original may have EPB and final does not yet have them + if (bs_dest && sei_removed) { + u8 *dst_no_epb = NULL; + u32 dst_no_epb_size = 0; + gf_bs_get_content(bs_dest, &dst_no_epb, &dst_no_epb_size); + if (dst_no_epb) { + u32 nb_bytes_add = gf_media_nalu_emulation_bytes_add_count(dst_no_epb, dst_no_epb_size); + //if result fits into source buffer, reformat + //otherwise ignore and return source (happens in some fuzzing cases, cf issue 1903) + if (dst_no_epb_size + nb_bytes_add <= nal_size) + nal_size = gf_media_nalu_add_emulation_bytes(dst_no_epb, buffer, dst_no_epb_size); + + gf_free(dst_no_epb); + } + } + if (bs_dest) gf_bs_del(bs_dest); + return nal_size; +} + static void gf_hevc_vvc_parse_sei(char *buffer, u32 nal_size, HEVCState *hevc, VVCState *vvc) { u32 ptype, psize, hdr, i; - u8 *dst_ptr; u64 start; GF_BitStream *bs; + GF_SEIInfo *sei = hevc ? &hevc->sei : &vvc->sei; hdr = buffer0; if (((hdr & 0x7e) >> 1) != GF_HEVC_NALU_SEI_PREFIX) return; @@ -7521,39 +8692,43 @@ } nb_zeros = gf_bs_get_emulation_byte_removed(bs); - + sei->has_3d_ref_disp_info = 0; switch (ptype) { case 4: /*user registered ITU-T T35*/ - if (hevc) { - avc_parse_itu_t_t35_sei(bs, &hevc->sei.dovi); - } + avc_parse_itu_t_t35_sei(bs, &sei->dovi); break; //clli case 144: - dst_ptr = hevc ? hevc->clli_data : vvc->clli_data; //do not use read data due to possible EPB for (i=0; i<4; i++) - dst_ptri = gf_bs_read_u8(bs); + sei->clli_datai = gf_bs_read_u8(bs); - if (hevc) { - hevc->clli_valid = 1; - } else { - vvc->clli_valid = 1; - } + sei->clli_valid = 1; break; //mdcv case 137: - dst_ptr = hevc ? hevc->mdcv_data : vvc->mdcv_data; //do not use read data due to possible EPB for (i=0; i<24; i++) - dst_ptri = gf_bs_read_u8(bs); + sei->mdcv_datai = gf_bs_read_u8(bs); - if (hevc) { - hevc->mdcv_valid = 1; + sei->mdcv_valid = 1; + break; + // atc + case 147: + if (hevc){ + hevc->sei.alternative_transfer_characteristics = gf_bs_read_u8(bs); } else { - vvc->mdcv_valid = 1; + vvc->sei.alternative_transfer_characteristics = gf_bs_read_u8(bs); } break; + // three_dimensional_reference_displays_info + case 176: + sei->has_3d_ref_disp_info = 1; + break; + // time_code + case 136: + hevc_parse_pic_timing_sei(bs, hevc); + break; default: break; } @@ -7562,7 +8737,7 @@ gf_bs_align(bs); consumed = (u32) (gf_bs_get_position(bs) - start); consumed -= nb_zeros; - psize-=consumed; + psize = (consumed < psize) ? psize-consumed : 0; //do not use skip bytes due to possible EPB while (psize) { gf_bs_read_u8(bs); @@ -7579,6 +8754,11 @@ gf_hevc_vvc_parse_sei(buffer, nal_size, hevc, NULL); } +u32 gf_hevc_reformat_sei(char *buffer, u32 nal_size, Bool isobmf_rewrite, SEI_Filter *sei_filter) +{ + return gf_hevc_vvc_reformat_sei(buffer, nal_size, isobmf_rewrite, GF_TRUE, sei_filter); +} + static void hevc_compute_poc(HEVCSliceInfo *si) { u32 max_poc_lsb = 1 << (si->sps->log2_max_pic_order_cnt_lsb); @@ -7746,14 +8926,13 @@ u8 ols_highest_output_layer_idMAX_LHVC_LAYERS + 1; u32 k, d, r, p, iNuhLId, jNuhLId; - u8 num_direct_ref_layers64, num_pred_layers64, num_layers_in_tree_partitionMAX_LHVC_LAYERS; + u8 num_pred_layers64, num_layers_in_tree_partitionMAX_LHVC_LAYERS; u8 dependency_flagMAX_LHVC_LAYERSMAX_LHVC_LAYERS, id_pred_layers64MAX_LHVC_LAYERS; // u8 num_ref_layers64; // u8 tree_partition_layer_idMAX_LHVC_LAYERSMAX_LHVC_LAYERS; // u8 id_ref_layers64MAX_LHVC_LAYERS; - // u8 id_direct_ref_layers64MAX_LHVC_LAYERS; u8 layer_id_in_list_flag64; - Bool OutputLayerFlagMAX_LHVC_LAYERSMAX_LHVC_LAYERS; + Bool OutputLayerFlagMAX_LHVC_LAYERSMAX_LHVC_LAYERS = {0}; vps->vps_extension_found = 1; if ((vps->max_layers > 1) && vps->base_layer_internal_flag) @@ -7861,7 +9040,7 @@ for (j = 0; j < vps->max_layers; j++) { jNuhLId = vps->layer_id_in_nuhj; if (vps->direct_dependency_flagij) { - // id_direct_ref_layersiNuhLIdd = jNuhLId; + vps->id_direct_ref_layersiNuhLIdd = jNuhLId; d++; } if (dependency_flagij) { @@ -7872,7 +9051,7 @@ if (dependency_flagji) id_pred_layersiNuhLIdp++ = jNuhLId; } - num_direct_ref_layersiNuhLId = d; + vps->num_direct_ref_layersiNuhLId = d; // num_ref_layersiNuhLId = r; num_pred_layersiNuhLId = p; } @@ -7881,7 +9060,7 @@ k = 0; //num_indepentdent_layers for (i = 0; i < vps->max_layers; i++) { iNuhLId = vps->layer_id_in_nuhi; - if (!num_direct_ref_layersiNuhLId) { + if (!vps->num_direct_ref_layersiNuhLId) { u32 h = 1; //tree_partition_layer_idk0 = iNuhLId; for (j = 0; j < num_pred_layersiNuhLId; j++) { @@ -7894,6 +9073,36 @@ num_layers_in_tree_partitionk++ = h; } } + + u32 DepthLayerFlag64; + for (i = 0; i < vps->max_layers; i++) { + u32 j, smIdx, lId = vps->layer_id_in_nuhi; + u32 scalID16; + for (smIdx=0, j=0; smIdx < 16; smIdx++) { + if (vps->scalability_masksmIdx) + scalIDsmIdx = vps->dimension_idij++; + else + scalIDsmIdx = 0; + } + DepthLayerFlaglId = scalID0; + } + + for (i = 0; i < vps->max_layers; i++) { + u32 j, iNuhLId = vps->layer_id_in_nuhi; + vps->num_ref_list_layersiNuhLId = 0; + for (j = 0; j < vps->num_direct_ref_layersiNuhLId; j++) { + jNuhLId = vps->id_direct_ref_layersiNuhLIdj; + if( DepthLayerFlagiNuhLId == DepthLayerFlagjNuhLId ) { + //IdRefListLayer iNuhLId + vps->num_ref_list_layersiNuhLId ++; + // = jNuhLId; + } + } + } + for (i = 0; i < vps->max_layers; i++) { + vps->layer_idx_in_vpsvps->layer_id_in_nuhi = i; + } + num_indepentdent_layers = k; num_add_layer_set = 0; @@ -7911,7 +9120,11 @@ if (gf_bs_read_int_log(bs, 1, "vps_sub_layers_max_minus1_present_flag")) { for (i = 0; i < vps->max_layers; i++) { - gf_bs_read_int_log_idx(bs, 3, "sub_layers_vps_max_minus1", i); + vps->sub_layers_vps_max_minus1i = gf_bs_read_int_log_idx(bs, 3, "sub_layers_vps_max_minus1", i); + } + } else { + for (i = 0; i < vps->max_layers; i++) { + vps->sub_layers_vps_max_minus1i = vps->max_layers-1; } } @@ -7919,11 +9132,11 @@ for (i = 0; i < (vps->max_layers - 1); i++) { for (j = i + 1; j < vps->max_layers; j++) { if (vps->direct_dependency_flagji) - gf_bs_read_int_log_idx2(bs, 3, "max_tid_il_ref_pics_plus1", i, j); + vps->max_tid_il_ref_pics_plus1ij = gf_bs_read_int_log_idx2(bs, 3, "max_tid_il_ref_pics_plus1", i, j); } } } - gf_bs_read_int_log(bs, 1, "default_ref_layers_active_flag"); + vps->default_ref_layers_active_flag = gf_bs_read_int_log(bs, 1, "default_ref_layers_active_flag"); vps->num_profile_tier_level = 1 + gf_bs_read_ue_log(bs, "num_profile_tier_level"); if (vps->num_profile_tier_level > MAX_LHVC_LAYERS) { @@ -8056,6 +9269,7 @@ vps->rep_format_idxi = i < vps->num_rep_formats - 1 ? i : vps->num_rep_formats - 1; } } + vps->max_one_active_ref_layer_flag = gf_bs_read_int_log(bs, 1, "max_one_active_ref_layer_flag"); //TODO - we don't use the rest ... return GF_TRUE; @@ -8319,8 +9533,10 @@ sps_ext_or_max_sub_layers_minus1 = 0; if (layer_id == 0) max_sub_layers_minus1 = gf_bs_read_int_log(bs, 3, "max_sub_layers_minus1"); - else + else if (hevc && hevc->vpsvps_id.state) { sps_ext_or_max_sub_layers_minus1 = gf_bs_read_int_log(bs, 3, "sps_ext_or_max_sub_layers_minus1"); + max_sub_layers_minus1 = sps_ext_or_max_sub_layers_minus1 == 7 ? hevc->vpsvps_id.max_sub_layers - 1 : sps_ext_or_max_sub_layers_minus1; + } multiLayerExtSpsFlag = (layer_id != 0) && (sps_ext_or_max_sub_layers_minus1 == 7); if (!multiLayerExtSpsFlag) { gf_bs_read_int_log(bs, 1, "temporal_id_nesting_flag"); @@ -8635,6 +9851,38 @@ return -1;\ } +static void hevc_color_map_octants(GF_BitStream *bs, u32 cm_octant_depth, u32 OctantNumY, u32 PartNumY, u32 CMResLSBits, u32 inpDepth, u32 idxY, u32 idxCb, u32 idxCr, u32 inpLength) +{ + u32 i, j, c, k, n, m; + Bool split_octant_flag=GF_FALSE; + if ( inpDepth < cm_octant_depth ) + split_octant_flag = gf_bs_read_int_log(bs, 1, "split_octant_flag"); + if (split_octant_flag) { + for (k=0; k<2; k++) { + for (m=0; m<2; m++) { + for (n=0; n<2; n++) { + hevc_color_map_octants(bs, cm_octant_depth, OctantNumY, PartNumY, CMResLSBits, inpDepth + 1, idxY + PartNumY * k * inpLength / 2, idxCb + m * inpLength / 2, idxCr + n * inpLength / 2, inpLength / 2 ); + } + } + } + return; + } + for (i=0; i<PartNumY; i++) { + // idxShiftY = idxY + (i << (cm_octant_depth-inpDepth)); + for (j=0; j<4; j++) { + if (gf_bs_read_int_log_idx2(bs, 1, "coded_res_flag", PartNumY, j)) { + for (c=0; c<3; c++) { + u32 v = gf_bs_read_ue_log_idx3(bs, "res_coeff_q", PartNumY, j, c); + v += gf_bs_read_int_log_idx3(bs, CMResLSBits, "res_coeff_r", PartNumY, j, c); + if (v) { + gf_bs_read_int_log_idx3(bs, 1, "res_coeff_s", PartNumY, j, c); + } + } + } + } + } +} + static s32 gf_hevc_read_pps_bs_internal(GF_BitStream *bs, HEVCState *hevc) { u32 i; @@ -8718,13 +9966,157 @@ pps->lists_modification_present_flag = gf_bs_read_int_log(bs, 1, "lists_modification_present_flag"); pps->log2_parallel_merge_level_minus2 = gf_bs_read_ue_log(bs, "log2_parallel_merge_level_minus2"); pps->slice_segment_header_extension_present_flag = gf_bs_read_int_log(bs, 1, "slice_segment_header_extension_present_flag"); + + u8 pps_range_extension_flag=0; + u8 pps_multilayer_extension_flag=0; + u8 pps_3d_extension_flag=0; + u8 pps_scc_extension_flag=0; if (gf_bs_read_int_log(bs, 1, "pps_extension_flag")) { -#if 0 - while (gf_bs_available(bs)) { - /*pps_extension_data_flag */ gf_bs_read_int(bs, 1); + pps_range_extension_flag = gf_bs_read_int_log(bs, 1, "pps_range_extension_flag"); + pps_multilayer_extension_flag = gf_bs_read_int_log(bs, 1, "pps_multilayer_extension_flag"); + pps_3d_extension_flag = gf_bs_read_int_log(bs, 1, "pps_3d_extension_flag"); + pps_scc_extension_flag = gf_bs_read_int_log(bs, 1, "pps_scc_extension_flag"); + gf_bs_read_int_log(bs, 4, "pps_extension_4bits"); + } + if (pps_range_extension_flag) { + if (pps->transform_skip_enabled_flag) + gf_bs_read_ue_log(bs, "log2_max_transform_skip_block_size_minus2"); + gf_bs_read_int_log(bs, 1, "cross_component_prediction_enabled_flag"); + u8 flag = gf_bs_read_int_log(bs, 1, "chroma_qp_offset_list_enabled_flag"); + if (flag) { + gf_bs_read_ue_log(bs, "diff_cu_chroma_qp_offset_depth"); + u32 nb_chroma = 1 + gf_bs_read_ue_log(bs, "chroma_qp_offset_list_len_minus1"); + for (i=0; i<nb_chroma; i++) { + gf_bs_read_se_log_idx(bs, "cb_qp_offset_list", i); + gf_bs_read_se_log_idx(bs, "cr_qp_offset_list", i); + } + } + gf_bs_read_ue_log(bs, "log2_sao_offset_scale_luma"); + gf_bs_read_ue_log(bs, "log2_sao_offset_scale_chroma"); + } + if (pps_multilayer_extension_flag ) { + gf_bs_read_int_log(bs, 1, "poc_reset_info_present_flag"); + if (gf_bs_read_int_log(bs, 1, "pps_infer_scaling_list_flag")) { + gf_bs_read_int_log(bs, 6, "pps_scaling_list_ref_layer_id"); + } + u32 nb_refs = gf_bs_read_ue_log(bs, "num_ref_loc_offsets"); + for (i=0; i<nb_refs; i++) { + gf_bs_read_int_log_idx(bs, 6, "ref_loc_offset_layer_id", i); + if (gf_bs_read_int_log_idx(bs, 1, "scaled_ref_layer_offset_present_flag", i)) { + gf_bs_read_se_log_idx(bs, "scaled_ref_layer_left_offset", i); + gf_bs_read_se_log_idx(bs, "scaled_ref_layer_top_offset", i); + gf_bs_read_se_log_idx(bs, "scaled_ref_layer_right_offset", i); + gf_bs_read_se_log_idx(bs, "scaled_ref_layer_bottom_offset", i); + } + if (gf_bs_read_int_log_idx(bs, 1, "ref_region_offset_present_flag", i)) { + gf_bs_read_se_log_idx(bs, "ref_region_left_offset", i); + gf_bs_read_se_log_idx(bs, "ref_region_top_offset", i); + gf_bs_read_se_log_idx(bs, "ref_region_right_offset", i); + gf_bs_read_se_log_idx(bs, "ref_region_bottom_offset", i); + } + if (gf_bs_read_int_log_idx(bs, 1, "resample_phase_set_present_flag", i)) { + gf_bs_read_ue_log_idx(bs, "phase_hor_luma", i); + gf_bs_read_ue_log_idx(bs, "phase_ver_luma", i); + gf_bs_read_ue_log_idx(bs, "phase_hor_chroma_plus8", i); + gf_bs_read_ue_log_idx(bs, "phase_ver_chroma_plus8", i); + } } -#endif + if (gf_bs_read_int_log(bs, 1, "colour_mapping_enabled_flag")) { + u32 nb_vals = gf_bs_read_ue_log(bs, "num_cm_ref_layers_minus1"); + for (i=0; i<nb_vals; i++) { + gf_bs_read_int_log_idx(bs, 6, "cm_ref_layer_id", i); + } + u32 cm_octant_depth = gf_bs_read_int_log(bs, 2, "cm_octant_depth"); + u32 cm_y_part_num_log2 = gf_bs_read_int_log(bs, 2, "cm_y_part_num_log2"); + u32 BitDepthCmInputY = 8 + gf_bs_read_ue_log(bs, "luma_bit_depth_cm_input_minus8"); + gf_bs_read_ue_log(bs, "chroma_bit_depth_cm_input_minus8"); + u32 BitDepthCmOutputY = 8 + gf_bs_read_ue_log(bs, "luma_bit_depth_cm_output_minus8"); + gf_bs_read_ue_log(bs, "chroma_bit_depth_cm_output_minus8"); + u32 cm_res_quant_bits = gf_bs_read_int_log(bs, 2, "cm_res_quant_bits"); + u32 cm_delta_flc_bits_minus1 = gf_bs_read_int_log(bs, 2, "cm_delta_flc_bits_minus1"); + if (cm_octant_depth == 1) { + gf_bs_read_se_log(bs, "cm_adapt_threshold_u_delta"); + gf_bs_read_se_log(bs, "cm_adapt_threshold_v_delta"); + } + u32 OctantNumY = 1 << ( cm_octant_depth + cm_y_part_num_log2 ); + u32 PartNumY = 1 << cm_y_part_num_log2; + u32 CMResLSBits = MAX( 0, (10 + BitDepthCmInputY - BitDepthCmOutputY - cm_res_quant_bits - (cm_delta_flc_bits_minus1 + 1 ) ) ); + if ((s32)CMResLSBits<0) CMResLSBits=0; + + hevc_color_map_octants(bs, cm_octant_depth, OctantNumY, PartNumY, CMResLSBits, 0, 0, 0, 0, 1 << cm_octant_depth); + } + } + if (pps_3d_extension_flag) { + if (gf_bs_read_int_log(bs, 1, "dlts_present_flag")) { + u32 pps_depth_layers = 1 + gf_bs_read_int_log(bs, 6, "pps_depth_layers_minus1"); + u32 pps_bit_depth_for_depth_layers = 8 + gf_bs_read_int_log(bs, 4, "pps_bit_depth_for_depth_layers_minus8"); + for (i=0; i<pps_depth_layers; i++) { + if (gf_bs_read_int_log_idx(bs, 1, "dlt_flag", i)) { + Bool flag_resent = GF_FALSE; + if (!gf_bs_read_int_log_idx(bs, 1, "dlt_pred_flag", i)) { + flag_resent = gf_bs_read_int_log_idx(bs, 1, "dlt_val_flags_present_flag", i); + } + if (flag_resent) { + u32 j, depthMaxValue = (1 << (pps_bit_depth_for_depth_layers) ) - 1; + for (j=0; j<depthMaxValue; j++) { + gf_bs_read_int_log_idx2(bs, 1, "dlt_value_flag", i, j); + } + } else { + u32 num_val_delta_dlt = gf_bs_read_int_log_idx(bs, pps_bit_depth_for_depth_layers, "num_val_delta_dlt", i); + if (num_val_delta_dlt>0) { + u32 max_diff = 0; + u32 min_diff=0; + if (num_val_delta_dlt>1) + max_diff = gf_bs_read_int_log_idx(bs, pps_bit_depth_for_depth_layers, "max_diff", i); + + if ((num_val_delta_dlt>2) && (max_diff>0)) { + u32 nb_bits = 1, val = max_diff; //+1 - 1 for ceil(log2) + while ( val >>= 1) nb_bits++; + min_diff = 1+gf_bs_read_int_log_idx(bs, nb_bits, "min_diff_minus1", i); + } else { + min_diff = max_diff; + } + //delta_dlt_val0 = + gf_bs_read_int_log_idx(bs, pps_bit_depth_for_depth_layers, "delta_dlt_val0", i); + if (max_diff > min_diff) { + u32 k; + u32 nb_bits = 1, val = max_diff - min_diff; + while ( val >>= 1) nb_bits++; + for (k=1; k<num_val_delta_dlt; k++) { + gf_bs_read_int_log_idx2(bs, nb_bits, "delta_val_diff_minus_min", i, k); + } + } + } + } + } + } + } + } + if (pps_scc_extension_flag) { + pps->curr_pic_ref_enabled_flag = gf_bs_read_int_log(bs, 1, "pps_curr_pic_ref_enabled_flag"); + if (gf_bs_read_int_log(bs, 1, "residual_adaptive_colour_transform_enabled_flag")) { + gf_bs_read_int_log(bs, 1, "pps_slice_act_qp_offsets_present_flag"); + gf_bs_read_se_log(bs, "pps_act_y_qp_offset_plus5"); + gf_bs_read_se_log(bs, "pps_act_cb_qp_offset_plus5"); + gf_bs_read_se_log(bs, "pps_act_cr_qp_offset_plus3"); + } + if (gf_bs_read_int_log(bs, 1, "pps_palette_predictor_initializers_present_flag")) { + u32 pps_num_palette_predictor_initializers = gf_bs_read_ue_log(bs, "pps_num_palette_predictor_initializers"); + if (pps_num_palette_predictor_initializers>0) { + Bool mono_flag = gf_bs_read_int_log(bs, 1, "monochrome_palette_flag"); + u32 luma_bpp = 8 + gf_bs_read_ue_log(bs, "luma_bit_depth_entry"); + u32 chroma_bpp = 0; + if (!mono_flag) + chroma_bpp = gf_bs_read_ue_log(bs, "chroma_bit_depth_entry_minus8"); + u32 j, numComps = mono_flag ? 1 : 3; + for (i=0; i<numComps; i++) { + for (j=0; j<pps_num_palette_predictor_initializers; j++) { + gf_bs_read_int_log_idx2(bs, i ? chroma_bpp : luma_bpp, "pps_palette_predictor_initializer", i, j); + } + } + } + } } if (gf_bs_is_overflow(bs)) @@ -8777,6 +10169,8 @@ if (!hevc_parse_nal_header(bs, nal_unit_type, temporal_id, layer_id)) return -1; n_state.nal_unit_type = *nal_unit_type; + n_state.layer_id = *layer_id; + n_state.temporal_id = *temporal_id; switch (n_state.nal_unit_type) { case GF_HEVC_NALU_ACCESS_UNIT: @@ -8808,6 +10202,8 @@ if (ret < 0) return ret; hevc_compute_poc(&n_state); + if (hevc->full_slice_header_parse) + gf_hevc_compute_ref_list(hevc, &n_state); ret = 0; @@ -8892,15 +10288,16 @@ GF_Err gf_hevc_change_vui(GF_HEVCConfig *hvcc, GF_VUIInfo *vui_info) { GF_BitStream *orig, *mod; - HEVCState hevc; + HEVCState *hvc_state; u32 i, bit_offset, flag; s32 idx; GF_NALUFFParamArray *spss; GF_NALUFFParam *slc; orig = NULL; - memset(&hevc, 0, sizeof(HEVCState)); - hevc.sps_active_idx = -1; + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return GF_OUT_OF_MEM; + hvc_state->sps_active_idx = -1; i = 0; spss = NULL; @@ -8909,7 +10306,10 @@ break; spss = NULL; } - if (!spss) return GF_NON_COMPLIANT_BITSTREAM; + if (!spss) { + gf_free(hvc_state); + return GF_NON_COMPLIANT_BITSTREAM; + } i = 0; while ((slc = (GF_NALUFFParam *)gf_list_enum(spss->nalus, &i))) { @@ -8920,7 +10320,7 @@ no_emulation_buf = gf_malloc((slc->size) * sizeof(char)); no_emulation_buf_size = gf_media_nalu_remove_emulation_bytes(slc->data, no_emulation_buf, slc->size); - idx = gf_hevc_read_sps_ex(no_emulation_buf, no_emulation_buf_size, &hevc, &bit_offset); + idx = gf_hevc_read_sps_ex(no_emulation_buf, no_emulation_buf_size, hvc_state, &bit_offset); if (idx < 0) { if (orig) gf_bs_del(orig); @@ -8960,6 +10360,7 @@ gf_bs_del(mod); gf_free(no_emulation_buf); } + gf_free(hvc_state); return GF_OK; } @@ -9015,10 +10416,13 @@ GF_EXPORT GF_Err gf_hevc_get_sps_info(u8 *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d) { - HEVCState hevc; - memset(&hevc, 0, sizeof(HEVCState)); - hevc.sps_active_idx = -1; - return gf_hevc_get_sps_info_with_state(&hevc, sps_data, sps_size, sps_id, width, height, par_n, par_d); + HEVCState *hvc_state; + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return GF_OUT_OF_MEM; + hvc_state->sps_active_idx = -1; + GF_Err res = gf_hevc_get_sps_info_with_state(hvc_state, sps_data, sps_size, sps_id, width, height, par_n, par_d); + gf_free(hvc_state); + return res; } static u32 AC3_FindSyncCode(u8 *buf, u32 buflen) @@ -9106,6 +10510,7 @@ return ac3_sizecod_to_bitratebrcode; } +GF_EXPORT Bool gf_ac3_parser(u8 *buf, u32 buflen, u32 *pos, GF_AC3Config *hdr, Bool full_parse) { GF_BitStream *bs; @@ -9195,6 +10600,52 @@ } GF_EXPORT +u64 gf_ac3_get_channel_layout(GF_AC3Config *ac3) +{ + u64 layout = 0; + switch (ac3->streams0.acmod) { + case 0: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + break; + case 1: + layout |= GF_AUDIO_CH_FRONT_CENTER; + break; + case 2: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + break; + case 3: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER; + break; + case 4: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_REAR_CENTER; + break; + case 5: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_CENTER; + break; + case 6: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT; + break; + case 7: + layout |= GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_RIGHT; + break; + } + if (ac3->streams0.lfon) layout |= GF_AUDIO_CH_LFE; + if (ac3->streams0.nb_dep_sub) { + u32 chan_loc = ac3->streams0.chan_loc; + if (chan_loc & 1) layout |= GF_AUDIO_CH_FRONT_CENTER_LEFT | GF_AUDIO_CH_FRONT_CENTER_RIGHT; //Lc/Rc pair + if (chan_loc & (1<<1)) layout |= GF_AUDIO_CH_REAR_SURROUND_LEFT|GF_AUDIO_CH_REAR_SURROUND_RIGHT; //Lrs/Rrs pair + if (chan_loc & (1<<2)) layout |= GF_AUDIO_CH_REAR_CENTER; //Cs + if (chan_loc & (1<<3)) layout |= GF_AUDIO_CH_REAR_CENTER_TOP; //Ts + if (chan_loc & (1<<4)) layout |= GF_AUDIO_CH_SIDE_SURROUND_LEFT | GF_AUDIO_CH_SIDE_SURROUND_RIGHT; //Lsd/Rsd pair + if (chan_loc & (1<<5)) layout |= GF_AUDIO_CH_WIDE_FRONT_LEFT | GF_AUDIO_CH_WIDE_FRONT_RIGHT ; //Lw/Rw pair + if (chan_loc & (1<<6)) layout |= GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT; //Lvh/Rvh pair + if (chan_loc & (1<<7)) layout |= GF_AUDIO_CH_FRONT_TOP_CENTER; //Cvh + if (chan_loc & (1<<8)) layout |= GF_AUDIO_CH_LFE2; //LFE2 + } + return layout; +} + +GF_EXPORT u32 gf_eac3_get_chan_loc_count(u32 chan_loc) { u32 nb_ch=0; @@ -9630,7 +11081,7 @@ Bool gf_vorbis_parse_header(GF_VorbisParser *vp, u8 *data, u32 data_len) { u32 pack_type, i, j, k, times, nb_part, nb_books, nb_modes; - u32 l; + u32 l, ofchk; char szNAME8; oggpack_buffer opb; @@ -9692,19 +11143,29 @@ if (oggpack_read(&opb, 1)) { for (j = 0; j < entries; j++) { if (oggpack_read(&opb, 1)) { - oggpack_read(&opb, 5); + ofchk = oggpack_read(&opb, 5); + if (ofchk==(u32) -1) { + return GF_FALSE; + } } } } else { - for (j = 0; j < entries; j++) - oggpack_read(&opb, 5); + for (j = 0; j < entries; j++) { + ofchk = oggpack_read(&opb, 5); + if (ofchk==(u32) -1) { + return GF_FALSE; + } + } } } else { oggpack_read(&opb, 5); for (j = 0; j < entries;) { u32 num = oggpack_read(&opb, ilog(entries - j, GF_FALSE)); + if (num==(u32) -1) { + return GF_FALSE; + } for (k = 0; k < num && j < entries; k++, j++) { } } @@ -9721,7 +11182,12 @@ if (map_type == 1) qb = vorbis_book_maptype1_quantvals(entries, dim); else if (map_type == 2) qb = entries * dim; else qb = 0; - for (j = 0; j < qb; j++) oggpack_read(&opb, qq); + for (j = 0; j < qb; j++) { + ofchk = oggpack_read(&opb, qq); + if (ofchk==(u32) -1) { + return GF_FALSE; + } + } break; } } @@ -9730,6 +11196,9 @@ times = oggpack_read(&opb, 6) + 1; for (i = 0; i < times; i++) { u32 type = oggpack_read(&opb, 16); + if (type==(u32) -1) { + return GF_FALSE; + } if (type) { u32 *parts, *class_dims, count, rangebits; u32 max_class = 0; @@ -9737,6 +11206,11 @@ parts = (u32*)gf_malloc(sizeof(u32) * nb_part); for (j = 0; j < nb_part; j++) { partsj = oggpack_read(&opb, 4); + if (partsj==(u32) -1) { + gf_free(parts); + return GF_FALSE; + } + if (max_class < partsj) max_class = partsj; } class_dims = (u32*)gf_malloc(sizeof(u32) * (max_class + 1)); @@ -9745,14 +11219,28 @@ class_dimsj = oggpack_read(&opb, 3) + 1; class_sub = oggpack_read(&opb, 2); if (class_sub) oggpack_read(&opb, 8); - for (k = 0; k < (u32)(1 << class_sub); k++) oggpack_read(&opb, 8); + for (k = 0; k < (u32)(1 << class_sub); k++) { + ofchk = oggpack_read(&opb, 8); + if (ofchk==(u32)-1) { + gf_free(parts); + gf_free(class_dims); + return GF_FALSE; + } + } } oggpack_read(&opb, 2); rangebits = oggpack_read(&opb, 4); count = 0; for (j = 0, k = 0; j < nb_part; j++) { count += class_dimspartsj; - for (; k < count; k++) oggpack_read(&opb, rangebits); + for (; k < count; k++) { + ofchk = oggpack_read(&opb, rangebits); + if (ofchk==(u32)-1) { + gf_free(parts); + gf_free(class_dims); + return GF_FALSE; + } + } } gf_free(parts); gf_free(class_dims); @@ -9764,11 +11252,19 @@ oggpack_read(&opb, 6); oggpack_read(&opb, 8); nb_books = oggpack_read(&opb, 4) + 1; - for (j = 0; j < nb_books; j++) - oggpack_read(&opb, 8); + for (j = 0; j < nb_books; j++) { + ofchk = oggpack_read(&opb, 8); + if (ofchk==(u32)-1) { + return GF_FALSE; + } + } } } - times = oggpack_read(&opb, 6) + 1; + times = oggpack_read(&opb, 6); + if (times==(u32)-1) { + return GF_FALSE; + } + times++; for (i = 0; i < times; i++) { u32 acc = 0; oggpack_read(&opb, 16);/*type*/ @@ -9784,13 +11280,21 @@ } for (j = 0; j < acc; j++) oggpack_read(&opb, 8); } - times = oggpack_read(&opb, 6) + 1; + times = oggpack_read(&opb, 6); + if (times==(u32)-1) { + return GF_FALSE; + } + times++; for (i = 0; i < times; i++) { u32 sub_maps = 1; oggpack_read(&opb, 16); if (oggpack_read(&opb, 1)) sub_maps = oggpack_read(&opb, 4) + 1; if (oggpack_read(&opb, 1)) { - u32 nb_steps = oggpack_read(&opb, 8) + 1; + u32 nb_steps = oggpack_read(&opb, 8); + if (nb_steps==(u32)-1) { + return GF_FALSE; + } + nb_steps++; for (j = 0; j < nb_steps; j++) { oggpack_read(&opb, ilog(vp->channels, GF_TRUE)); oggpack_read(&opb, ilog(vp->channels, GF_TRUE)); @@ -9807,7 +11311,11 @@ oggpack_read(&opb, 8); } } - nb_modes = oggpack_read(&opb, 6) + 1; + nb_modes = oggpack_read(&opb, 6); + if (nb_modes==(u32)-1) { + return GF_FALSE; + } + nb_modes += 1; for (i = 0; i < nb_modes; i++) { vp->mode_flagi = oggpack_read(&opb, 1); oggpack_read(&opb, 16); @@ -9845,8 +11353,6 @@ #if !defined(GPAC_DISABLE_AV_PARSERS) -/*call with vorbis header packets - initializes the parser on success, leave it to NULL otherwise -returns 1 if success, 0 if error.*/ Bool gf_opus_parse_header(GF_OpusConfig *ocfg, u8 *data, u32 data_len) { char tag9; @@ -9911,63 +11417,63 @@ /* return nb bytes read */ static u8 gf_opus_read_length(u8 *data, u32 data_length, u32 offset, u16 *read_length) { - if (!data || !data_length || !read_length) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Cannot read Opus length value\n")); - return 0; - } - if (offset >= data_length) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough bytes to read Opus length\n")); - return 0; - } - if (dataoffset < 252) { - *read_length = dataoffset; - return 1; - } else { - if (offset+1 >= data_length) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough bytes to read 2-byte Opus length\n")); - return 0; - } - *read_length = dataoffset+1*4+dataoffset; - return 2; - } + if (!data || !data_length || !read_length) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Cannot read Opus length value\n")); + return 0; + } + if (offset >= data_length) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough bytes to read Opus length\n")); + return 0; + } + if (dataoffset < 252) { + *read_length = dataoffset; + return 1; + } else { + if (offset+1 >= data_length) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough bytes to read 2-byte Opus length\n")); + return 0; + } + *read_length = dataoffset+1*4+dataoffset; + return 2; + } } GF_EXPORT u8 gf_opus_parse_packet_header(u8 *data, u32 data_length, Bool self_delimited, GF_OpusPacketHeader *header) { - u32 i; - u32 nb_read_bytes; - if (!data || !data_length) - return 0; - if (!header) - return 0; - if (data_length>=8 && !memcmp(data, "OpusHead", sizeof(char)*8)) - return 0; - if (data_length>=8 && !memcmp(data, "OpusTags", sizeof(char)*8)) - return 0; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("Processing Opus packet, self: %d, size %d\n", self_delimited, data_length)); - - if (data_length < 1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Opus packet size must be at least one to parse TOC byte\n")); - return 0; - } - memset(header, 0, sizeof(GF_OpusPacketHeader)); - header->self_delimited = self_delimited; - header->TOC_config = (data0 & 0xf8) >> 3; - header->TOC_stereo = (data0 & 0x4) >> 2; - header->TOC_code = data0 & 0x03; - header->size = 1; - if (header->TOC_code == 0) { - header->nb_frames = 1; - if (self_delimited) { - nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); - if (nb_read_bytes) { - header->size += nb_read_bytes; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 0\n")); - return 0; - } + u32 i; + u32 nb_read_bytes; + if (!data || !data_length) + return 0; + if (!header) + return 0; + if (data_length>=8 && !memcmp(data, "OpusHead", sizeof(char)*8)) + return 0; + if (data_length>=8 && !memcmp(data, "OpusTags", sizeof(char)*8)) + return 0; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("Processing Opus packet, self: %d, size %d\n", self_delimited, data_length)); + + if (data_length < 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Opus packet size must be at least one to parse TOC byte\n")); + return 0; + } + memset(header, 0, sizeof(GF_OpusPacketHeader)); + header->self_delimited = self_delimited; + header->TOC_config = (data0 & 0xf8) >> 3; + header->TOC_stereo = (data0 & 0x4) >> 2; + header->TOC_code = data0 & 0x03; + header->size = 1; + if (header->TOC_code == 0) { + header->nb_frames = 1; + if (self_delimited) { + nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); + if (nb_read_bytes) { + header->size += nb_read_bytes; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 0\n")); + return 0; + } // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -9977,8 +11483,8 @@ // : | // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - header->frame_lengths0 = header->self_delimited_length; - } else { + header->frame_lengths0 = header->self_delimited_length; + } else { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -9988,19 +11494,19 @@ // : | // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - header->frame_lengths0 = data_length - header->size; - } - header->packet_size = header->size + header->frame_lengths0; - } else if (header->TOC_code == 1) { - header->nb_frames = 2; - if (self_delimited) { - nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); - if (nb_read_bytes) { - header->size += nb_read_bytes; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 1\n")); - return 0; - } + header->frame_lengths0 = data_length - header->size; + } + header->packet_size = header->size + header->frame_lengths0; + } else if (header->TOC_code == 1) { + header->nb_frames = 2; + if (self_delimited) { + nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); + if (nb_read_bytes) { + header->size += nb_read_bytes; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 1\n")); + return 0; + } // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10014,9 +11520,9 @@ // : +-+-+-+-+-+-+-+-+ // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - header->frame_lengths0 = header->self_delimited_length; - header->frame_lengths1 = header->self_delimited_length; - } else { + header->frame_lengths0 = header->self_delimited_length; + header->frame_lengths1 = header->self_delimited_length; + } else { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10030,33 +11536,33 @@ // : +-+-+-+-+-+-+-+-+ // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - if ((data_length-header->size) % 2) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Size of non-self-delimited Opus packet with code 2 must be even but is %d\n",data_length-header->size)); - return 0; - } - header->frame_lengths0 = (data_length-header->size)/2; - header->frame_lengths1 = (data_length-header->size)/2; - } - header->packet_size = header->size + header->frame_lengths0 + header->frame_lengths1; - } else if (header->TOC_code == 2) { - header->nb_frames = 2; - if (self_delimited) { - nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); - if (nb_read_bytes) { - header->size += nb_read_bytes; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 2\n")); - return 0; - } - } - nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->code2_frame_length); - if (nb_read_bytes) { - header->size += nb_read_bytes; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read frame length in Opus packet code 2\n")); - return 0; - } - if (self_delimited) { + if ((data_length-header->size) % 2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Size of non-self-delimited Opus packet with code 2 must be even but is %d\n",data_length-header->size)); + return 0; + } + header->frame_lengths0 = (data_length-header->size)/2; + header->frame_lengths1 = (data_length-header->size)/2; + } + header->packet_size = header->size + header->frame_lengths0 + header->frame_lengths1; + } else if (header->TOC_code == 2) { + header->nb_frames = 2; + if (self_delimited) { + nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); + if (nb_read_bytes) { + header->size += nb_read_bytes; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 2\n")); + return 0; + } + } + nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->code2_frame_length); + if (nb_read_bytes) { + header->size += nb_read_bytes; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read frame length in Opus packet code 2\n")); + return 0; + } + if (self_delimited) { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10070,9 +11576,9 @@ // : | // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - header->frame_lengths0 = header->self_delimited_length; - header->frame_lengths1 = header->code2_frame_length; - } else { + header->frame_lengths0 = header->self_delimited_length; + header->frame_lengths1 = header->code2_frame_length; + } else { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10086,48 +11592,52 @@ // : | // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - header->frame_lengths0 = header->code2_frame_length; - header->frame_lengths1 = data_length - header->size - header->code2_frame_length; - } - header->packet_size = header->size + header->frame_lengths0 + header->frame_lengths1; - } else if (header->TOC_code == 3) { - u32 sum = 0; - if (data_length <= header->size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 data\n")); - return 0; - } - header->code3_vbr = (dataheader->size & 0x80) >> 7; - header->code3_padding = (dataheader->size & 0x40) >> 6; - header->nb_frames = dataheader->size & 0x3f; - header->size++; - if (header->code3_padding) { - if (data_length <= header->size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 padding length\n")); - return 0; - } - if (dataheader->size == 255) { - header->code3_padding_length = 254 + dataheader->size+1; - header->size += 2; - } else { - header->code3_padding_length = dataheader->size; - header->size++; - } - } else { - header->code3_padding_length = 0; - } - if (self_delimited) { - nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); - if (nb_read_bytes) { - header->size += nb_read_bytes; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 3\n")); - return 0; - } - } - if (header->code3_vbr) { - u32 max; - u32 min; - if (self_delimited) { + header->frame_lengths0 = header->code2_frame_length; + header->frame_lengths1 = data_length - header->size - header->code2_frame_length; + } + header->packet_size = header->size + header->frame_lengths0 + header->frame_lengths1; + } else if (header->TOC_code == 3) { + u32 sum = 0; + if (data_length <= header->size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 data\n")); + return 0; + } + header->code3_vbr = (dataheader->size & 0x80) >> 7; + header->code3_padding = (dataheader->size & 0x40) >> 6; + header->nb_frames = dataheader->size & 0x3f; + header->size++; + if (header->code3_padding) { + if (data_length <= header->size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 padding length\n")); + return 0; + } + if (dataheader->size == 255) { + if (data_length <= (u32) header->size+1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 data\n")); + return 0; + } + header->code3_padding_length = 254 + dataheader->size+1; + header->size += 2; + } else { + header->code3_padding_length = dataheader->size; + header->size++; + } + } else { + header->code3_padding_length = 0; + } + if (self_delimited) { + nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &header->self_delimited_length); + if (nb_read_bytes) { + header->size += nb_read_bytes; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read self delimited length in Opus packet code 3\n")); + return 0; + } + } + if (header->code3_vbr) { + u32 max; + u32 min; + if (self_delimited) { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10153,11 +11663,11 @@ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // : Opus Padding (Optional)... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - header->frame_lengths0 = header->self_delimited_length; - min = 1; - max = header->nb_frames; - sum += header->frame_lengths0; - } else { + header->frame_lengths0 = header->self_delimited_length; + min = 1; + max = header->nb_frames; + sum += header->frame_lengths0; + } else { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10183,30 +11693,30 @@ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // : Opus Padding (Optional)... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - min = 0; - max = header->nb_frames-1; - } - for (i = min; i < max; i++) { - if (data_length <= header->size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 length\n")); - return 0; - } - nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &(header->frame_lengthsi)); - if (nb_read_bytes) { - header->size += nb_read_bytes; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read frame length in Opus packet code 3\n")); - return 0; - } - sum += header->frame_lengthsi; - } - if (!self_delimited) { - header->frame_lengthsheader->nb_frames-1 = data_length - header->size - header->code3_padding_length - sum; - sum += header->frame_lengthsheader->nb_frames-1; - } - } else { - u32 cbr_length; - if (self_delimited) { + min = 0; + max = header->nb_frames ? header->nb_frames-1 : 0; + } + for (i = min; i < max; i++) { + if (data_length <= header->size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Not enough data to parse TOC code 3 length\n")); + return 0; + } + nb_read_bytes = gf_opus_read_length(data, data_length, header->size, &(header->frame_lengthsi)); + if (nb_read_bytes) { + header->size += nb_read_bytes; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Could not read frame length in Opus packet code 3\n")); + return 0; + } + sum += header->frame_lengthsi; + } + if (!self_delimited && header->nb_frames) { + header->frame_lengthsheader->nb_frames-1 = data_length - header->size - header->code3_padding_length - sum; + sum += header->frame_lengthsheader->nb_frames-1; + } + } else { + u32 cbr_length=0; + if (self_delimited) { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10230,8 +11740,8 @@ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // : Opus Padding (Optional)... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - cbr_length = header->self_delimited_length; - } else { + cbr_length = header->self_delimited_length; + } else if (header->nb_frames) { // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -10255,20 +11765,20 @@ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // : Opus Padding (Optional)... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - if ((data_length - header->size - header->code3_padding_length) % header->nb_frames) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Sum of frame lengths is not a multiple of the number of frames\n")); - return 0; - } - cbr_length = (data_length - header->size - header->code3_padding_length)/header->nb_frames; - } - for (i = 0; i < header->nb_frames; i++) { - header->frame_lengthsi = cbr_length; - sum += header->frame_lengthsi; - } - } - header->packet_size = header->size + header->code3_padding_length + sum; - } - return 1; + if ((data_length - header->size - header->code3_padding_length) % header->nb_frames) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("Sum of frame lengths is not a multiple of the number of frames\n")); + return 0; + } + cbr_length = (data_length - header->size - header->code3_padding_length)/header->nb_frames; + } + for (i = 0; i < header->nb_frames; i++) { + header->frame_lengthsi = cbr_length; + sum += header->frame_lengthsi; + } + } + header->packet_size = header->size + header->code3_padding_length + sum; + } + return 1; } u64 gf_mpegh_escaped_value(GF_BitStream *bs, u32 nBits1, u32 nBits2, u32 nBits3) @@ -10293,7 +11803,7 @@ u32 i; s32 sync_pos=-1; - if (!ptr || !size) return 0; + if (!ptr || !size || size<=3) return 0; for (i=0; i<size-3; i++) { if ((ptri==0xC0) && (ptri+1== 0x01) && (ptri+2==0xA5)) { @@ -10343,6 +11853,12 @@ gf_hevc_vvc_parse_sei(buffer, nal_size, NULL, vvc); } +GF_EXPORT +u32 gf_vvc_reformat_sei(char *buffer, u32 nal_size, Bool isobmf_rewrite, SEI_Filter *sei_filter) +{ + return gf_hevc_vvc_reformat_sei(buffer, nal_size, isobmf_rewrite, GF_FALSE, sei_filter); +} + static Bool vvc_parse_nal_header(GF_BitStream *bs, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id) { u32 val; @@ -10509,6 +12025,9 @@ static s32 vvc_parse_ref_pic_list_struct(GF_BitStream *bs, VVC_SPS *sps, u32 listIdx, u32 rplsIdx, VVC_RefPicList *rpl) { u32 i; + Bool is_first = GF_TRUE; + s32 prev_delta = 0; + memset(rpl, 0, sizeof(VVC_RefPicList)); rpl->num_ref_entries = gf_bs_read_ue_log_idx2(bs, "num_ref_entries", listIdx, rplsIdx); if (rpl->num_ref_entries>=VVC_MAX_REF_PICS) { @@ -10529,22 +12048,37 @@ inter_layer_ref_pic_flag = gf_bs_read_int_log_idx3(bs, 1, "inter_layer_ref_pic_flag", listIdx, rplsIdx, i); } if (!inter_layer_ref_pic_flag) { - u32 AbsDeltaPocSt; + s32 AbsDeltaPocSt; Bool st_ref_pic_flag = 1; if (sps->long_term_ref_pics_flag) { st_ref_pic_flag = gf_bs_read_int_log_idx3(bs, 1, "st_ref_pic_flag", listIdx, rplsIdx, i); } if (st_ref_pic_flag) { u32 abs_delta_poc_st = gf_bs_read_ue_log_idx3(bs, "abs_delta_poc_st", listIdx, rplsIdx, i); + if (abs_delta_poc_st >= 0x8000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("VVC abs_delta_poc_st %U exceeds maximum allowed value %d\n", abs_delta_poc_st, 0x8000-1)); + return -1; + } if ((sps->weighted_pred_flag || sps->weighted_bipred_flag) && (i!=0)) { AbsDeltaPocSt = abs_delta_poc_st; } else { AbsDeltaPocSt = abs_delta_poc_st + 1; } + if (AbsDeltaPocSt>0) { - gf_bs_read_int_log_idx3(bs, 1, "strp_entry_sign_flag", listIdx, rplsIdx, i); + if (gf_bs_read_int_log_idx3(bs, 1, "strp_entry_sign_flag", listIdx, rplsIdx, i)) + AbsDeltaPocSt = -AbsDeltaPocSt; + } + if (is_first) { + is_first = GF_FALSE; + prev_delta = AbsDeltaPocSt; + } else { + AbsDeltaPocSt = prev_delta + AbsDeltaPocSt; + prev_delta = AbsDeltaPocSt; } + rpl->poc_deltai = AbsDeltaPocSt; + rpl->nb_short_term_pictures++; rpl->ref_pic_typei = VVC_RPL_ST; } else if( !rpl->ltrp_in_header_flag) { @@ -10747,15 +12281,16 @@ gf_bs_read_int_log(bs, 1, "loop_filter_across_subpic_enabled_flag"); } } - sps->subpicid_len = gf_bs_read_ue_log(bs, "subpic_id_len_minus1") + 1; - sps->subpicid_mapping_explicit = gf_bs_read_int_log(bs, 1, "subpic_id_mapping_explicitly_signalled_flag"); - if (sps->subpicid_mapping_explicit) { - sps->subpicid_mapping_present = gf_bs_read_int_log(bs, 1, "subpic_id_mapping_present_flag"); - if (sps->subpicid_mapping_present) { - for (i=0; i<sps->nb_subpics; i++) { - VVC_SubpicInfo *sp = &sps->subpicsi; - sp->id = gf_bs_read_int_log_idx(bs, sps->subpicid_len, "subpic_id", i); - } + } + //coded even if nb_subpics<=1 + sps->subpicid_len = gf_bs_read_ue_log(bs, "subpic_id_len_minus1") + 1; + sps->subpicid_mapping_explicit = gf_bs_read_int_log(bs, 1, "subpic_id_mapping_explicitly_signalled_flag"); + if (sps->subpicid_mapping_explicit) { + sps->subpicid_mapping_present = gf_bs_read_int_log(bs, 1, "subpic_id_mapping_present_flag"); + if (sps->subpicid_mapping_present) { + for (i=0; i<sps->nb_subpics; i++) { + VVC_SubpicInfo *sp = &sps->subpicsi; + sp->id = gf_bs_read_int_log_idx(bs, sps->subpicid_len, "subpic_id", i); } } } @@ -10841,6 +12376,10 @@ for (i=0; i<numQpTables; i++) { gf_bs_read_se_log_idx(bs, "sps_qp_table_start_minus26", i); u32 j, sps_num_points_in_qp_table = 1 + gf_bs_read_ue_log_idx(bs, "sps_num_points_in_qp_table_minus1", i); + if (sps_num_points_in_qp_table>36) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("VVC sps_num_points_in_qp_table too high %u\n", sps_num_points_in_qp_table)); + VVC_SPS_BROKEN + } for (j=0; j<sps_num_points_in_qp_table; j++) { gf_bs_read_ue_log_idx2(bs, "sps_delta_qp_in_val_minus1", i, j); gf_bs_read_ue_log_idx2(bs, "sps_delta_qp_diff_val", i, j); @@ -11502,6 +13041,8 @@ { s32 pps_id; + si->nb_lt_or_il_pics = si->nb_reference_pocs = 0; + si->irap_or_gdr_pic = gf_bs_read_int_log(bs, 1, "irap_or_gdr_pic"); si->non_ref_pic = gf_bs_read_int_log(bs, 1, "non_ref_pic"); if (si->irap_or_gdr_pic) @@ -11710,23 +13251,49 @@ static s32 vvc_get_ctb_info_in_slice(VVCSliceInfo *si, u32 sh_slice_address, u32 sh_num_tiles_in_slice, s32 ctu_index) { + u32 ctb_y, ctb_x; if (si->pps->rect_slice_flag) { -/* - TODO - u32 picLevelSliceIdx = sh_slice_address; - for (j=0; j<CurrSubpicIdx; j++) { - picLevelSliceIdx += NumSlicesInSubpicj; - - u32 NumCtusInCurrSlice = NumCtusInSlicepicLevelSliceIdx; - for (i=0; i<NumCtusInCurrSlice; i++) - CtbAddrInCurrSlicei = CtbAddrInSlice picLevelSliceIdx i -*/ + if (!si->sps->subpic_info_present) { + u32 i, j, NumCtusInCurrSlice=0; + u32 tcolw_bdVVC_MAX_TILE_COLS+1; + tcolw_bd0 = 0; + for (i=0; i<si->pps->num_tile_cols; i++) { + tcolw_bdi+1 = tcolw_bdi + si->pps->tile_cols_width_ctbi; + } + u32 trowh_bdVVC_MAX_TILE_ROWS+1; + trowh_bd0 = 0; + for (i=0; i<si->pps->num_tile_rows; i++) { + trowh_bdi+1 = trowh_bdi + si->pps->tile_rows_height_ctbi; + } + + for (j=0; j<si->pps->num_tile_rows; j++) { + for (i=0; i<si->pps->num_tile_cols; i++) { + u32 min_ctbx = tcolw_bdi; + u32 max_ctbx = tcolw_bdi+1; + u32 min_ctby = trowh_bdj; + u32 max_ctby = trowh_bdj+1; + for (ctb_y=min_ctby; ctb_y < max_ctby; ctb_y++) { + for (ctb_x=min_ctbx; ctb_x < max_ctbx; ctb_x++) { + if (ctu_index>=0) { + if (ctu_index == NumCtusInCurrSlice) + return ctb_y * si->pps->pic_width_in_ctbsY + ctb_x; + } + NumCtusInCurrSlice++; + } + } + } + } + return NumCtusInCurrSlice; + } + /* + TODO for tiles in subpic ! + */ + return -1; } else { u32 i, tidx, NumCtusInCurrSlice = 0; for (tidx=sh_slice_address; tidx < sh_slice_address + sh_num_tiles_in_slice; tidx++) { - u32 ctb_y, ctb_x; u32 tileX = tidx % si->pps->num_tile_cols; u32 tileY = tidx / si->pps->num_tile_cols; u32 min_ctbx=0; @@ -11789,16 +13356,18 @@ return 0; } -static u32 vvc_get_num_entry_points(VVCSliceInfo *si, u32 sh_slice_address, u32 sh_num_tiles_in_slice) +static s32 vvc_get_num_entry_points(VVCSliceInfo *si, u32 sh_slice_address, u32 sh_num_tiles_in_slice) { if (!si->sps->entry_point_offsets_present_flag) return 0; - u32 nb_entry_points = 0; + s32 nb_entry_points = 0; u32 prev_ctb_addr_y=0; u32 prev_ctb_to_tile_row_bd, prev_ctb_to_tile_col_bd; s32 i; + if (!si->pps->num_tile_rows || !si->pps->num_tile_cols) return 0; + s32 nb_ctus_in_slice = vvc_get_ctb_info_in_slice(si, sh_slice_address, sh_num_tiles_in_slice, -1); - if (nb_ctus_in_slice<0) return 0; + if (nb_ctus_in_slice<0) return -1; for (i=0; i < nb_ctus_in_slice; i++ ) { s32 addr; @@ -11806,24 +13375,22 @@ u32 ctb_to_tile_row_bd, ctb_to_tile_col_bd; addr = vvc_get_ctb_info_in_slice(si, sh_slice_address, sh_num_tiles_in_slice, i); - if (addr<0) return 0; + if (addr<0) return -1; ctb_addr_x = (u32) ( addr % si->pps->pic_width_in_ctbsY ); ctb_addr_y = (u32) ( addr / si->pps->pic_width_in_ctbsY ); ctb_to_tile_row_bd = vvc_ctb_to_tile_row_bd(si, ctb_addr_y); ctb_to_tile_col_bd = vvc_ctb_to_tile_col_bd(si, ctb_addr_x); + //ignore first CTU, not an entry point if (i) { - if ( ctb_to_tile_row_bd != prev_ctb_to_tile_row_bd || ctb_to_tile_col_bd != prev_ctb_to_tile_col_bd || ((ctb_addr_y != prev_ctb_addr_y) && si->sps->entropy_coding_sync_enabled_flag) ) { nb_entry_points++; } - } - prev_ctb_addr_y = ctb_addr_y; prev_ctb_to_tile_row_bd = ctb_to_tile_row_bd; prev_ctb_to_tile_col_bd = ctb_to_tile_col_bd; @@ -11876,7 +13443,8 @@ } } - gf_bs_read_int_log(bs, si->sps->sh_num_extra_bits, "num_extra_bits"); + if (si->sps->sh_num_extra_bits) + gf_bs_read_int_log(bs, si->sps->sh_num_extra_bits, "num_extra_bits"); if (!si->pps->rect_slice_flag && (si->pps->num_tiles_in_pic - slice_address > 1)) { num_tiles_in_slice = 1 + gf_bs_read_ue_log(bs, "sh_num_tiles_in_slice_minus1"); @@ -11946,7 +13514,7 @@ if (res<0) return (vvc->parse_mode==1) ? res : 0; } - u32 num_ref_idx_active2 = {0, 0}; + si->num_ref_idx_active0 = si->num_ref_idx_active1 = 0; if ( ((si->slice_type != GF_VVC_SLICE_TYPE_I) && (si->rpl0.num_ref_entries > 1) ) @@ -11958,36 +13526,36 @@ if (si->rpl0.num_ref_entries>1) { nb_active = 1 + gf_bs_read_ue_log_idx(bs, "sh_num_ref_idx_active_minus1", 0); } - num_ref_idx_active0 = nb_active; + si->num_ref_idx_active0 = nb_active; //L1 if (si->slice_type == GF_VVC_SLICE_TYPE_B) { nb_active = 0; if (si->rpl1.num_ref_entries>1) { nb_active = 1 + gf_bs_read_ue_log_idx(bs, "sh_num_ref_idx_active_minus1", 1); } - num_ref_idx_active1 = nb_active; + si->num_ref_idx_active1 = nb_active; } else { - num_ref_idx_active1 = 0; + si->num_ref_idx_active1 = 0; } } else { if (si->rpl0.num_ref_entries >= si->pps->num_ref_idx_default_active0) { - num_ref_idx_active0 = si->pps->num_ref_idx_default_active0; + si->num_ref_idx_active0 = si->pps->num_ref_idx_default_active0; } else { - num_ref_idx_active0 = si->rpl0.num_ref_entries; + si->num_ref_idx_active0 = si->rpl0.num_ref_entries; } if (si->slice_type == GF_VVC_SLICE_TYPE_B) { if (si->rpl1.num_ref_entries >= si->pps->num_ref_idx_default_active1) { - num_ref_idx_active1 = si->pps->num_ref_idx_default_active1; + si->num_ref_idx_active1 = si->pps->num_ref_idx_default_active1; } else { - num_ref_idx_active1 = si->rpl1.num_ref_entries; + si->num_ref_idx_active1 = si->rpl1.num_ref_entries; } } else { - num_ref_idx_active1 = 0; + si->num_ref_idx_active1 = 0; } } } else { - num_ref_idx_active0 = (si->slice_type == GF_VVC_SLICE_TYPE_I) ? 0 : 1; - num_ref_idx_active1 = (si->slice_type == GF_VVC_SLICE_TYPE_B) ? 1 : 0; + si->num_ref_idx_active0 = (si->slice_type == GF_VVC_SLICE_TYPE_I) ? 0 : 1; + si->num_ref_idx_active1 = (si->slice_type == GF_VVC_SLICE_TYPE_B) ? 1 : 0; } if (si->slice_type != GF_VVC_SLICE_TYPE_I) { @@ -11998,9 +13566,12 @@ u8 collocated_from_l0_flag = 0; if (si->slice_type == GF_VVC_SLICE_TYPE_B) { collocated_from_l0_flag = gf_bs_read_int_log(bs, 1, "sh_collocated_from_l0_flag"); + } else { + //(sh_slice_type is equal to P), the value of sh_collocated_from_l0_flag is inferred to be equal to 1 + collocated_from_l0_flag = 1; } - if ( (collocated_from_l0_flag && (num_ref_idx_active0 > 1)) - || (!collocated_from_l0_flag && (num_ref_idx_active1 > 1)) + if ( (collocated_from_l0_flag && (si->num_ref_idx_active0 > 1)) + || (!collocated_from_l0_flag && (si->num_ref_idx_active1 > 1)) ) { gf_bs_read_ue_log(bs, "sh_collocated_ref_idx"); } @@ -12011,7 +13582,7 @@ || (si->pps->weighted_bipred_flag && (si->slice_type == GF_VVC_SLICE_TYPE_B)) ) ) { - s32 res = vvc_pred_weight_table(bs, vvc, si, si->pps, si->sps, num_ref_idx_active); + s32 res = vvc_pred_weight_table(bs, vvc, si, si->pps, si->sps, si->num_ref_idx_active); if (res<0) return (vvc->parse_mode==1) ? res : 0; } } @@ -12034,11 +13605,12 @@ if (si->pps->deblocking_filter_override_enabled_flag && !si->pps->dbf_info_in_ph_flag) { if (gf_bs_read_int_log(bs, 1, "sh_deblocking_params_present_flag")) { - u8 deblocking_params_present_flag=0; + u8 deblocking_params_disabled_flag=0; if (!si->pps->deblocking_filter_disabled_flag) { - deblocking_params_present_flag = gf_bs_read_int_log(bs, 1, "sh_deblocking_filter_disabled_flag"); + deblocking_params_disabled_flag = gf_bs_read_int_log(bs, 1, "sh_deblocking_filter_disabled_flag"); } - if (deblocking_params_present_flag) { + + if (!deblocking_params_disabled_flag) { gf_bs_read_se_log(bs, "sh_luma_beta_offset_div2"); gf_bs_read_se_log(bs, "sh_luma_tc_offset_div2"); if (si->pps->chroma_tool_offsets_present_flag) { @@ -12077,24 +13649,22 @@ } } - if (!si->pps->rect_slice_flag && !si->pps->num_tile_cols && !si->pps->num_tile_rows) { - } else { - if (si->sps->entry_point_offsets_present_flag && si->pps->rect_slice_flag) { + if (si->sps->entry_point_offsets_present_flag) { + s32 nb_entry_points = vvc_get_num_entry_points(si, slice_address, num_tiles_in_slice); + if (nb_entry_points<0) { if (vvc->parse_mode==1) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("VVC Entry point offsets parsing for sub-picture not yet implemented, wrong slice header size estimation (result might be non-compliant) - patch welcome\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("VVC Entry point offsets parsing for tiled sub-picture not yet implemented, wrong slice header size estimation (result might be non-compliant) - patch welcome\n")); gf_bs_align(bs); si->payload_start_offset = (u32) gf_bs_get_position(bs); return -2; } - GF_LOG(GF_LOG_INFO, GF_LOG_CODING, ("VVC Entry point offsets parsing for sub-picture not yet implemented, aborting slice header parsing - patch welcome\n")); + GF_LOG(GF_LOG_INFO, GF_LOG_CODING, ("VVC Entry point offsets parsing for tiled sub-picture not yet implemented, aborting slice header parsing - patch welcome\n")); return 0; } - - u32 nb_entry_points = vvc_get_num_entry_points(si, slice_address, num_tiles_in_slice); if (nb_entry_points) { u32 nb_bits = 1 + gf_bs_read_ue_log(bs, "sh_entry_offset_len_minus1"); - for (i=0; i<nb_entry_points; i++) { + for (i=0; i<(u32) nb_entry_points; i++) { gf_bs_read_int_log_idx(bs, nb_bits, "sh_entry_point_offset_minus1", i); } } @@ -12134,6 +13704,55 @@ si->poc = si->poc_msb + si->poc_lsb; } +static void vvc_push_ref_poc(VVCSliceInfo *si, s32 poc) +{ + u32 i; + for (i=0;i<si->nb_reference_pocs; i++) { + if (si->reference_pocsi==poc) return; + } + if (si->nb_reference_pocs==GF_ARRAY_LENGTH(si->reference_pocs)) return; + si->reference_pocssi->nb_reference_pocs = poc; + si->nb_reference_pocs++; +} + +static void vvc_compute_refs(VVCState *vvc, VVCSliceInfo *si) +{ + u32 lidx, ridx; + + if (si->slice_type==GF_VVC_SLICE_TYPE_I) { + si->nb_reference_pocs = 0; + return; + } + + for (lidx=0; lidx<2; lidx++) { + VVC_RefPicList *rpl = &si->rpllidx; + u32 num_active_refs = si->num_ref_idx_activelidx; + + for (ridx=0; ridx < MIN(rpl->num_ref_entries, VVC_MAX_REF_PICS); ridx++) { + Bool is_active_ref = ridx < num_active_refs ? GF_TRUE : GF_FALSE; + s32 refPOC=0; + //bool isLongTerm = false; + if (rpl->ref_pic_typeridx == VVC_RPL_IL) { + //TODO interlayer + if (!si->nb_lt_or_il_pics) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("VVC Inter-Layer pictures in RefPicList not supported, patch welcome\n")); + } + si->nb_lt_or_il_pics ++; + } else if (rpl->ref_pic_typeridx == VVC_RPL_ST) { + refPOC = si->poc + rpl->poc_deltaridx; + } else { + //TODO LongTerm + if (!si->nb_lt_or_il_pics) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("VVC Long-Term pictures in RefPicList not supported, patch welcome\n")); + } + si->nb_lt_or_il_pics ++; + } + + if (!is_active_ref) continue; + vvc_push_ref_poc(si, refPOC); + } + } +} GF_EXPORT s32 gf_vvc_parse_nalu_bs(GF_BitStream *bs, VVCState *vvc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id) @@ -12182,6 +13801,9 @@ n_state.compute_poc_defer = 0; vvc_compute_poc(&n_state, poc_reset); + if (vvc->parse_mode) + vvc_compute_refs(vvc, &n_state); + if (vvc->s_info.poc != n_state.poc) { ret = 1; break; @@ -12479,23 +14101,25 @@ u8 profile=12; u8 *sqhdr = memchr(seq_hdr+1, 0x0F, seq_hdr_len); if (sqhdr) { - u32 skip = (u32) (sqhdr - seq_hdr - 3); + u32 skip = (u32) (sqhdr - seq_hdr); seq_hdr+=skip; seq_hdr_len-=skip; - bs = gf_bs_new(seq_hdr+4, seq_hdr_len-4, GF_BITSTREAM_READ); - profile = gf_bs_read_int(bs, 2); - if (profile==3) { - level = gf_bs_read_int(bs, 3); - /*cfmt*/gf_bs_read_int(bs, 2); - /*fps*/gf_bs_read_int(bs, 3); - /*btrt*/gf_bs_read_int(bs, 5); - gf_bs_read_int(bs, 1); - /*mw*/gf_bs_read_int(bs, 12); - /*mh*/gf_bs_read_int(bs, 12); - /*bcast*/gf_bs_read_int(bs, 1); - interlace = gf_bs_read_int(bs, 1); + if (seq_hdr_len > 1) { + bs = gf_bs_new(seq_hdr+1, seq_hdr_len-1, GF_BITSTREAM_READ); + profile = gf_bs_read_int(bs, 2); + if (profile==3) { + level = gf_bs_read_int(bs, 3); + /*cfmt*/gf_bs_read_int(bs, 2); + /*fps*/gf_bs_read_int(bs, 3); + /*btrt*/gf_bs_read_int(bs, 5); + gf_bs_read_int(bs, 1); + /*mw*/gf_bs_read_int(bs, 12); + /*mh*/gf_bs_read_int(bs, 12); + /*bcast*/gf_bs_read_int(bs, 1); + interlace = gf_bs_read_int(bs, 1); + } + gf_bs_del(bs); } - gf_bs_del(bs); } *dsi_size = seq_hdr_len+7; *dsi = gf_malloc(seq_hdr_len+7); @@ -12552,4 +14176,2097 @@ } } +// ch_mode - TS 103 190-2 table 78 +#define AC4_CH_MODE_MONO 0 +#define AC4_CH_MODE_STEREO 1 +#define AC4_CH_MODE_3_0 2 +#define AC4_CH_MODE_5_0 3 +#define AC4_CH_MODE_5_1 4 +#define AC4_CH_MODE_70_34 5 +#define AC4_CH_MODE_71_34 6 +#define AC4_CH_MODE_70_52 7 +#define AC4_CH_MODE_71_52 8 +#define AC4_CH_MODE_70_322 9 +#define AC4_CH_MODE_71_322 10 +#define AC4_CH_MODE_7_0_4 11 +#define AC4_CH_MODE_7_1_4 12 +#define AC4_CH_MODE_9_0_4 13 +#define AC4_CH_MODE_9_1_4 14 +#define AC4_CH_MODE_22_2 15 +#define AC4_CH_MODE_RESERVED 16 + +#define GF_AP4_CH_MODE_LENGTH 16 /* AC-4 ch_mode length */ + +// speaker group index mask, indexed by ch_mode - TS 103 190-2 A.27 +const s32 AC4_SPEAKER_GROUP_INDEX_MASK_BY_CH_MODE = +{ + 2, // 0b10 - 1.0 + 1, // 0b01 - 2.0 + 3, // 0b11 - 3.0 + 7, // 0b0000111 - 5.0 + 71, // 0b1000111 - 5.1 + 15, // 0b0001111 - 7.0: 3/4/0 + 79, // 0b1001111 - 7.1: 3/4/0.1 + 131079, // 0b100000000000000111 - 7.0: 5/2/0 + 131143, // 0b100000000001000111 - 7.1: 5/2/0.1 + 262151, // 0b1000000000000000111 - 7.0: 3/2/2 + 262215, // 0b1000000000001000111 - 7.1: 3/2/2.1 + 63, // 0b0111111 - 7.0.4 + 127, // 0b1111111 - 7.1.4 + 65599, // 0b10000000000111111 - 9.0.4 + 65663, // 0b10000000001111111 - 9.1.4 + 196479, // 0b101111111101111111 - 22.2 + 0 // reserved +}; + +const unsigned char AC4_SUPER_SET_CH_MODEGF_AP4_CH_MODE_LENGTHGF_AP4_CH_MODE_LENGTH = +{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13,14,15}, + {1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13,14,15}, + {2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13,14,15}, + {3, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13,14,15}, + {4, 4, 4, 4, 4, 6, 6, 8, 8, 10,10,12,12,14,14,15}, + {5, 5, 5, 5, 6, 5, 6, 7, 8, 9, 10,11,12,13,14,15}, + {6, 6, 6, 6, 6, 6, 6, 6, 8, 6, 10,12,12,14,14,15}, + {7, 7, 7, 7, 8, 7, 6, 7, 8, 9, 10,12,12,13,14,15}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10,11,12,14,14,15}, + {9, 9, 9, 9, 10,9, 10,9, 9, 9, 10,11,12,13,14,15}, + {10,10,10,10,10,10,10,10,10,10,10,10,12,13,14,15}, + {11,11,11,11,12,11,12,11,12,11,12,11,13,13,14,15}, + {12,12,12,12,12,12,12,12,12,12,12,12,12,13,14,15}, + {13,13,13,13,14,13,14,13,14,13,14,13,14,13,14,15}, + {14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,15}, + {15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15} +}; + +const u32 AC4_SAMPLING_FREQ_TABLE = { + 44100, // 44.1 kHz + 48000 // 48 kHz +}; + +/* ETSI TS 103 190-1 V1.3.1 (2018-02) Table E.1*/ +const u32 AC4_SAMPLE_DELTA_TABLE_48 = { + 2002, + 2000, + 1920, + 8008, // 29.97 fps, using 240 000 media time scale + 1600, + 1001, + 1000, + 960, + 4004, // 59.97 fps + 800, + 480, + 2002, // 119.88 fps + 400, + 2048 // 23.44 fps, AC-4 native frame rate +}; + +const u32 AC4_SAMPLE_DELTA_TABLE_441 = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // reserved 0~12 + 2048 // 21.53 fps +}; + +const u32 AC4_MEDIA_TIMESCALE_48 = { + 48000, + 48000, + 48000, + 240000, + 48000, + 48000, + 48000, + 48000, + 240000, + 48000, + 48000, + 240000, + 48000, + 48000 +}; + +const u32 AC4_MEDIA_TIMESCALE_441 = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // reserved 0~12 + 44100 +}; + +static u32 gf_ac4_variable_bits(GF_BitStream *bs, int bits) +{ + u32 value = 0; + if (!gf_bs_available(bs)) return value; + + u32 b_moreBits = 0; + do{ + value += gf_bs_read_int(bs, bits); + b_moreBits = gf_bs_read_int(bs, 1); + if (b_moreBits == 1) { + value <<= bits; + value += (1<<bits); + } + } while (b_moreBits == 1 && gf_bs_available(bs)); + return value; +} + +static Bool gf_ac4_frame_rate_multiply_info(GF_BitStream *bs, GF_AC4PresentationV1* pinfo, u32 frame_rate_idx) +{ + u8 multiplier_bit; + switch (frame_rate_idx) { + case 2: + case 3: + case 4: + // The value of dsi_frame_rate_multiply_info is defined by ETSI TS 103 190-1 V1.3.1 (2018-02) E.4 + if (gf_bs_read_int_log(bs, 1, "b_multiplier")) { + multiplier_bit = gf_bs_read_int_log(bs, 1, "multiplier_bit"); + pinfo->dsi_frame_rate_multiply_info = (multiplier_bit == 0)? 1: 2; + } else { + pinfo->dsi_frame_rate_multiply_info = 0; + } + break; + case 0: + case 1: + case 7: + case 8: + case 9: + if (gf_bs_read_int_log(bs, 1, "b_multiplier")) { + pinfo->dsi_frame_rate_multiply_info = 1; + } else { + pinfo->dsi_frame_rate_multiply_info = 0; + } + break; + default: + pinfo->dsi_frame_rate_multiply_info = 0; + break; + } + return GF_TRUE; +} + +static Bool gf_ac4_presentation_version(GF_BitStream *bs, GF_AC4PresentationV1* pinfo, u8 bitstream_version) +{ + pinfo->presentation_version = 0; + while(gf_bs_read_int(bs, 1) == 1){ + pinfo->presentation_version ++; + } + return GF_TRUE; +} + +static Bool gf_ac4_emdf_payloads_substream_info(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 2, "substream_index") == 3) { + gf_ac4_variable_bits(bs, 2); + } + return GF_TRUE; +} + +static Bool gf_ac4_emdf_protection(GF_BitStream *bs) +{ + u8 protection_length_primary, protection_length_secondary; + protection_length_primary = gf_bs_read_int_log(bs, 2, "protection_length_primary"); + protection_length_secondary = gf_bs_read_int_log(bs, 2, "protection_length_secondary"); + + switch (protection_length_primary) { + case 1: + gf_bs_read_int_log(bs, 8, "protection_bits_primary"); + break; + case 2: + for (unsigned idx = 0; idx < 4; idx ++) { gf_bs_read_int_log(bs, 8, "protection_bits_primary"); } + break; + case 3: + for (unsigned idx = 0; idx < 16; idx ++) { gf_bs_read_int_log(bs, 8, "protection_bits_primary"); } + break; + default: + break; + } + switch (protection_length_secondary) { + case 0: + break; + case 1: + gf_bs_read_int_log(bs, 8, "protection_bits_secondary"); + break; + case 2: + for (unsigned idx = 0; idx < 4; idx ++) { gf_bs_read_int_log(bs, 8, "protection_bits_secondary"); } + break; + case 3: + for (unsigned idx = 0; idx < 16; idx ++) { gf_bs_read_int_log(bs, 8, "protection_bits_secondary"); } + break; + default: + break; + } + return GF_TRUE; +} + +static Bool gf_ac4_emdf_info(GF_BitStream *bs, u32 *emdf_version, u32 *key_id) +{ + *emdf_version = gf_bs_read_int_log(bs, 2, "emdf_version"); + if (*emdf_version == 3) { + *emdf_version += gf_ac4_variable_bits(bs, 2); + } + *key_id = gf_bs_read_int_log(bs, 3, "key_id"); + if (*key_id == 7) { + *key_id += gf_ac4_variable_bits(bs, 3); + } + + if (gf_bs_read_int_log(bs, 1, "b_emdf_payloads_substream_info")) { + gf_ac4_emdf_payloads_substream_info(bs); + } + gf_ac4_emdf_protection(bs); + return GF_TRUE; +} + +static Bool gf_ac4_frame_rate_fractions_info(GF_BitStream *bs, GF_AC4PresentationV1* pinfo, u32 frame_rate_index) +{ + // The dsi_frame_rate_fraction_info is defined by ETSI TS 103 190-2 V1.2.1 (2018-02) E.10.7 + pinfo->dsi_frame_rate_fraction_info = 0; + switch (frame_rate_index) { + case 5: + case 6: + case 7: + case 8: + case 9: + if (gf_bs_read_int_log(bs, 1, "b_frame_rate_fraction")) { + // frame_rate_fraction = 2; + pinfo->dsi_frame_rate_fraction_info = 1; + } + break; + case 10: + case 11: + case 12: + if (gf_bs_read_int_log(bs, 1, "b_frame_rate_fraction")) { + if (gf_bs_read_int_log(bs, 1, "b_frame_rate_fraction_is_4") == 1) { + // frame_rate_fraction = 4; + pinfo->dsi_frame_rate_fraction_info = 2; + } else { + // frame_rate_fraction = 2; + pinfo->dsi_frame_rate_fraction_info = 1; + } + } + break; + default: + break; + } + return GF_TRUE; +} + +static Bool gf_ac4_get_channel_mode(GF_BitStream *bs, + u32 presentation_version, + u8 *dolby_atmos_indicator) +{ + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.3.2.7.2 Table 78 + u32 channel_mode_code = 0; + channel_mode_code = gf_bs_read_int(bs, 1); + if (channel_mode_code == 0) { // Mono 0b0 + return AC4_CH_MODE_MONO; + } + channel_mode_code = (channel_mode_code << 1) | gf_bs_read_int(bs, 1); + if (channel_mode_code == 2) { // Stereo 0b10 + return AC4_CH_MODE_STEREO; + } + channel_mode_code = (channel_mode_code << 2) | gf_bs_read_int(bs, 2); + switch (channel_mode_code) { + case 12: // 3.0 0b1100 + return AC4_CH_MODE_3_0; + case 13: // 5.0 0b1101 + return AC4_CH_MODE_5_0; + case 14: // 5.1 0b1110 + return AC4_CH_MODE_5_1; + } + channel_mode_code = (channel_mode_code << 3) | gf_bs_read_int(bs, 3); + switch (channel_mode_code) { + case 120: // 7.0: 3/4/0 0b1111000 + // Dolby AC-4 in MPEG-DASH for Broadcast Services Specification 2.5.3 + if (presentation_version == 2) { + return AC4_CH_MODE_STEREO; // signaling Dolby content in IMS + } else { + return AC4_CH_MODE_70_34; + } + case 121: // 7.1: 3/4/0.1 0b1111001 + if (presentation_version == 2) { + *dolby_atmos_indicator |= 1; + return AC4_CH_MODE_STEREO; // signaling Dolby Atmos content in IMS + } else { + return AC4_CH_MODE_71_34; + } + case 122: // 7.0: 5/2/0 0b1111010 + return AC4_CH_MODE_70_52; + case 123: // 7.1: 5/2/0.1 0b1111011 + return AC4_CH_MODE_71_52; + case 124: // 7.0: 3/2/2 0b1111100 + return AC4_CH_MODE_70_322; + case 125: // 7.1: 3/2/2.1 0b1111101 + return AC4_CH_MODE_71_322; + } + channel_mode_code = (channel_mode_code << 1) | gf_bs_read_int(bs, 1); + switch (channel_mode_code) { + case 252: // 7.0.4 0b11111100 + return AC4_CH_MODE_7_0_4; + case 253: // 7.1.4 0b11111101 + return AC4_CH_MODE_7_1_4; + } + channel_mode_code = (channel_mode_code << 1) | gf_bs_read_int(bs, 1); + switch (channel_mode_code) { + case 508: // 9.0.4 0b111111100 + return AC4_CH_MODE_9_0_4; + case 509: // 9.1.4 0b111111101 + return AC4_CH_MODE_9_1_4; + case 510: // 22.2 0b111111110 + return AC4_CH_MODE_22_2; + case 511: // Reserved, escape value 0b111111111 + default: + gf_ac4_variable_bits(bs, 2); + return AC4_CH_MODE_RESERVED; + } +} + +static Bool gf_ac4_dsi_sf_mutiplier(GF_BitStream *bs, GF_AC4SubStream* substream, u32 fs_index) +{ + if (fs_index == 1) { + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.9.3 + if (gf_bs_read_int_log(bs, 1, "b_sf_multiplier")) { + // 96 kHz or 192 kHz + substream->dsi_sf_multiplier = gf_bs_read_int_log(bs, 1, "sf_multiplier") + 1; + } else { + // 48 kHz + substream->dsi_sf_multiplier = 0; + } + } + return GF_TRUE; +} + +static Bool gf_ac4_bitreate_indicator(GF_BitStream *bs, GF_AC4SubStream* substream) +{ + // ETSI TS 103 190-1 V1.3.1 (2018-02) 4.3.3.7.5 + substream->substream_bitrate_indicator = gf_bs_read_int_log(bs, 3, "bitrate_indicator"); + if ((substream->substream_bitrate_indicator & 0x1) == 1) { + substream->substream_bitrate_indicator = (substream->substream_bitrate_indicator << 2) +gf_bs_read_int(bs, 2); + } + return GF_TRUE; +} + +static Bool gf_ac4_substream_index_info(GF_BitStream *bs, u32 b_substreams_present) +{ + if (b_substreams_present == 1) { + if (gf_bs_read_int_log(bs, 2, "substream_index") == 3) { + gf_ac4_variable_bits(bs, 2); + } + } + return GF_TRUE; +} + +static Bool gf_compare_ch_mode(const u32 ch_mode, const u32 *list, const u32 n) +{ + u32 i; + for(i = 0; i < n; i++) { + if (ch_mode == listi) { + return GF_TRUE; + } + } + return GF_FALSE; +} + +static Bool gf_ac4_substream_info_chan(GF_BitStream *bs, + GF_AC4SubStream *substream, + u32 presentation_version, + u8 default_presentation_flag, + u32 fs_index, + u32 *speaker_index_mask, + u32 frame_rate_factor, + u8 b_substreams_present, + u8 *dolby_atmos_indicator) +{ + u32 i, mask; + const u32 ch1 = {AC4_CH_MODE_7_0_4, AC4_CH_MODE_7_1_4, AC4_CH_MODE_9_0_4, AC4_CH_MODE_9_1_4}; + const u32 ch1_n = 4; + const u32 ch2 = {AC4_CH_MODE_70_52, AC4_CH_MODE_71_52, AC4_CH_MODE_70_322, AC4_CH_MODE_71_322}; + const u32 ch2_n = 4; + + substream->ch_mode = gf_ac4_get_channel_mode(bs, presentation_version, dolby_atmos_indicator); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.7 + // This bit mask shall indicate the presence of channels in the audio presentation. + mask = AC4_SPEAKER_GROUP_INDEX_MASK_BY_CH_MODEsubstream->ch_mode; + + if (gf_compare_ch_mode(substream->ch_mode, ch1, ch1_n)) { + substream->b_4_back_channels_present = gf_bs_read_int_log(bs, 1, "b_4_back_channels_present"); + substream->b_centre_present = gf_bs_read_int_log(bs, 1, "b_centre_present"); + substream->top_channels_present = gf_bs_read_int_log(bs, 2, "top_channels_present"); + + if (!substream->b_4_back_channels_present) { + mask &= ~0x8; // Remove back channels (Lb,Rb) from mask + } + if (!substream->b_centre_present) { + mask &= ~0x2; // Remove centre channel (C) from mask + } + switch (substream->top_channels_present) { + case 0: + mask &= ~0x30; // Remove top channels (Tfl,Tfr,Tbl,Tbr) from mask + break; + case 1: + case 2: + mask &= ~0x30; // Remove top channels (Tfl,Tfr,Tbl,Tbr) from mask + mask |= 0x80; // Add top channels (Tl, Tr) from mask; + break; + } + } + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.7 + substream->dsi_substream_channel_mask = mask; + + // Only combine channel masks of substream groups that are part of the first/default presentation + if (default_presentation_flag) { + *speaker_index_mask |= mask; + } + + gf_ac4_dsi_sf_mutiplier(bs, substream, fs_index); + + substream->b_substream_bitrate_indicator = gf_bs_read_int_log(bs, 1, "b_bitrate_info"); + if (substream->b_substream_bitrate_indicator) { + gf_ac4_bitreate_indicator(bs, substream); + } + + if (gf_compare_ch_mode(substream->ch_mode, ch2, ch2_n)) { + gf_bs_read_int_log(bs, 1, "add_ch_base"); + } + for (i = 0; i < frame_rate_factor; i++) { + gf_bs_read_int_log(bs, 1, "b_audio_ndot"); + } + + gf_ac4_substream_index_info(bs, b_substreams_present); + + return GF_TRUE; +} + +static Bool gf_ac4_hsf_ext_substream_info(GF_BitStream *bs, u8 b_substreams_present) +{ + if (b_substreams_present == 1) { + if (gf_bs_read_int_log(bs, 2, "substream_index") == 3) { + gf_ac4_variable_bits(bs, 2); + } + } + return GF_TRUE; +} + +static Bool gf_ac4_oamd_substream_info(GF_BitStream *bs, u8 b_substreams_present) +{ + gf_bs_read_int_log(bs, 1, "b_oamd_ndot"); + if (b_substreams_present == 1) { + if (gf_bs_read_int_log(bs, 2, "substream_index") == 3) { + gf_ac4_variable_bits(bs, 2); + } + } + return GF_TRUE; +} + +static u32 gf_ac4_obj_num_from_is_config(u32 isf_config) +{ + u32 obj_num = 0; + switch (isf_config){ + case 0: obj_num = 4 ; break; + case 1: obj_num = 8 ; break; + case 2: obj_num = 10; break; + case 3: obj_num = 14; break; + case 4: obj_num = 15; break; + case 5: obj_num = 30; break; + default: obj_num = 0; + } + return obj_num; +} + +static u32 gf_ac4_bed_num_from_assign_code(u32 assign_code) +{ + u32 bed_num = 0; + switch (assign_code){ + case 0: bed_num = 2 ; break; + case 1: bed_num = 3 ; break; + case 2: bed_num = 6 ; break; + case 3: bed_num = 8 ; break; + case 4: bed_num = 10; break; + case 5: bed_num = 8 ; break; + case 6: bed_num = 10; break; + case 7: bed_num = 12; break; + default: bed_num = 0; + } + return bed_num; +} + +static u32 gf_ac4_bed_num_from_non_std_mask(u32 non_std_mask) +{ + u32 bed_num = 0, idx; + // Table 85: nonstd_bed_channel_assignment AC-4 part-2 v1.2.1 + for (idx = 0; idx < 17; idx ++) { + if ((non_std_mask >> idx) & 0x1){ + bed_num ++; + } + } + return bed_num; +} + +static u32 gf_ac4_bed_num_from_std_mask(u32 std_mask) +{ + u32 bed_num = 0, idx; + // Table 86 std_bed_channel_assignment_flag AC-4 part-2 v1.2.1 + for (idx = 0; idx < 10; idx ++) { + if ((std_mask >> idx) & 0x1){ + if ((idx == 1) || (idx == 2) || (idx == 9)) { bed_num ++;} + else { bed_num += 2; } + } + } + return bed_num; +} + +static Bool gf_ac4_bed_dyn_obj_assignment(GF_BitStream *bs, + GF_AC4SubStream* substream, + u32 n_signals, // n_fullband_dmx_signals + Bool is_upmix) +{ + u32 bed_ch_bits, n_bed_signals, b, isf_config, bed_chan_assign_code; + u32 nonstd_bed_channel_assignment_mask, bed_num, std_bed_channel_assignment_mask; + + if (gf_bs_read_int_log(bs, 1, "b_dyn_objects_only") == 0) { + if (gf_bs_read_int_log(bs, 1, "b_isf")) { + isf_config = gf_bs_read_int_log(bs, 3, "isf_config"); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 and Table E.8 + if (is_upmix) { + substream->b_substream_contains_ISF_objects |= 1; // obj_typen_objs = ISF; + if(n_signals > gf_ac4_obj_num_from_is_config(isf_config)) { + substream->b_substream_contains_dynamic_objects |= 1; // b_ajoc_codedn_objs = 1; + } + } + } else { + if (gf_bs_read_int_log(bs, 1, "b_ch_assign_code")) { + bed_chan_assign_code = gf_bs_read_int_log(bs, 3, "bed_chan_assign_code"); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 and Table E.8 + if (is_upmix) { + substream->b_substream_contains_bed_objects |= 1; // obj_typen_objs = BED; + if (n_signals > gf_ac4_bed_num_from_assign_code(bed_chan_assign_code)) { + substream->b_substream_contains_dynamic_objects |= 1; // b_ajoc_codedn_objs = 1; + } + } + } else { + if (gf_bs_read_int_log(bs, 1, "b_chan_assign_mask")) { + if (gf_bs_read_int_log(bs, 1, "b_nonstd_bed_channel_assignment")) { + nonstd_bed_channel_assignment_mask = gf_bs_read_int_log(bs, 17, "nonstd_bed_channel_assignment_mask"); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 and Table E.8 + if (is_upmix) { + bed_num = gf_ac4_bed_num_from_non_std_mask(nonstd_bed_channel_assignment_mask); + if (bed_num > 0) { + substream->b_substream_contains_bed_objects |= 1; // obj_typen_objs = BED; + } + if (n_signals > bed_num) { + substream->b_substream_contains_dynamic_objects |= 1; // b_ajoc_codedn_objs = 1; + } + } + } else { + std_bed_channel_assignment_mask = gf_bs_read_int_log(bs, 10, "std_bed_channel_assignment_mask"); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 and Table E.8 + if (is_upmix) { + bed_num = gf_ac4_bed_num_from_std_mask(std_bed_channel_assignment_mask); + if (bed_num > 0) { + substream->b_substream_contains_bed_objects |= 1; // obj_typen_objs = BED; + } + if (n_signals > bed_num) { + substream->b_substream_contains_dynamic_objects |= 1; // b_ajoc_codedn_objs = 1; + } + } + } + } else { + if (n_signals > 1) { + bed_ch_bits = (u32) gf_ceil(log2(n_signals)); + n_bed_signals = gf_bs_read_int_log(bs, bed_ch_bits, "n_bed_signals_minus1") + 1; + } else { + n_bed_signals = 1; + } + for (b = 0; b < n_bed_signals; b++) { + gf_bs_read_int_log(bs, 4, "nonstd_bed_channel_assignment"); + } + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 and Table E.8 + if (is_upmix) { + substream->b_substream_contains_bed_objects |= 1; // obj_typen_objs = BED; + if (n_signals > n_bed_signals){ + substream->b_substream_contains_dynamic_objects |= 1; // b_ajoc_codedn_objs = 1; + } + } + } + } + } + } else { + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.3.2.10.3 the substream contains only dynamic objects + if (is_upmix) { + substream->b_substream_contains_dynamic_objects |= 1; + substream->b_substream_contains_bed_objects |= 0; + substream->b_substream_contains_ISF_objects |= 0; + } + } + return GF_TRUE; +} + +static Bool gf_ac4_trim(GF_BitStream *bs) +{ + const u32 NUM_TRIM_CONFIGS = 9; + u32 trim_balance_presence9 = {0}; + u32 i; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.2.8.9 + if (gf_bs_read_int_log(bs, 1, "b_trim_present")) { + gf_bs_read_int_log(bs, 2, "warp_mode"); + gf_bs_read_int_log(bs, 2, "reserved"); + if (gf_bs_read_int_log(bs, 2, "global_trim_mode") == 0b10) { + for (i = 0; i < NUM_TRIM_CONFIGS; i++) { + if (gf_bs_read_int_log(bs, 1, "b_default_trim") == 0) { + if (gf_bs_read_int_log(bs, 1, "b_disable_trim") == 0) { + trim_balance_presence0 = gf_bs_read_int_log(bs, 1, "trim_balance_presence0"); + trim_balance_presence1 = gf_bs_read_int_log(bs, 1, "trim_balance_presence1"); + trim_balance_presence2 = gf_bs_read_int_log(bs, 1, "trim_balance_presence2"); + trim_balance_presence3 = gf_bs_read_int_log(bs, 1, "trim_balance_presence3"); + trim_balance_presence4 = gf_bs_read_int_log(bs, 1, "trim_balance_presence4"); + + if (trim_balance_presence4) { + gf_bs_read_int_log(bs, 4, "trim_centre"); + } + if (trim_balance_presence3) { + gf_bs_read_int_log(bs, 4, "trim_surround"); + } + if (trim_balance_presence2) { + gf_bs_read_int_log(bs, 4, "trim_height"); + } + if (trim_balance_presence1) { + gf_bs_read_int_log(bs, 1, "bal3D_Y_sign_tb_code"); + gf_bs_read_int_log(bs, 4, "bal3D_Y_amount_tb"); + } + if (trim_balance_presence0) { + gf_bs_read_int_log(bs, 1, "bal3D_Y_sign_lis_code"); + gf_bs_read_int_log(bs, 4, "bal3D_Y_amount_lis"); + } + } + } + } + } + } + return GF_TRUE; +} + +static Bool gf_ac4_tool_t2_to_f_s_b(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 1, "b_top_to_front") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2a_code"); + // gain_t2b_code = 7; + } else { + if (gf_bs_read_int_log(bs, 1, "b_top_to_side") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2b_code"); + } else { + gf_bs_read_int_log(bs, 3, "gain_t2c_code"); + // gain_t2b_code = 7; + } + } + return GF_TRUE; +} + +static Bool gf_ac4_tool_t2_to_f_s(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 1, "b_top_to_front") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2a_code"); + // gain_t2b_code = 7; + } else { + gf_bs_read_int_log(bs, 3, "gain_t2b_code"); + } + return GF_TRUE; +} + +static Bool gf_ac4_tool_tb_to_f_s_b(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 1, "b_top_back_to_front") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2d_code"); + // gain_t2e_code = 7; + } else { + if (gf_bs_read_int_log(bs, 1, "b_top_back_to_side") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2e_code"); + } else { + gf_bs_read_int_log(bs, 3, "gain_t2f_code"); + // gain_t2e_code = 7; + } + } + return GF_TRUE; +} + +static Bool gf_ac4_tool_tb_to_f_s(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 1, "b_top_back_to_front") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2d_code"); + // gain_t2e_code = 7; + } else { + gf_bs_read_int_log(bs, 3, "gain_t2e_code"); + } + return GF_TRUE; +} + +static Bool gf_ac4_tool_tf_to_f_s_b(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 1, "b_top_front_to_front") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2a_code"); + // gain_t2b_code = 7; + } else { + if (gf_bs_read_int_log(bs, 1, "b_top_front_to_side") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2b_code"); + } else { + gf_bs_read_int_log(bs, 3, "gain_t2c_code"); + // gain_t2b_code = 7; + } + } + return GF_TRUE; +} + +static Bool gf_ac4_tool_tf_to_f_s(GF_BitStream *bs) +{ + if (gf_bs_read_int_log(bs, 1, "b_top_front_to_front") == 1) { + gf_bs_read_int_log(bs, 3, "gain_t2a_code"); + // gain_t2b_code = 7; + } else { + gf_bs_read_int_log(bs, 3, "gain_t2b_code"); + } + return GF_TRUE; +} + +static Bool gf_ac4_stereo_dmx_coeff(GF_BitStream *bs) +{ + gf_bs_read_int_log(bs, 3, "loro_centre_mixgain"); + gf_bs_read_int_log(bs, 3, "loro_surround_mixgain"); + if (gf_bs_read_int_log(bs, 1, "b_ltrt_mixinfo")) { + gf_bs_read_int_log(bs, 3, "ltrt_centre_mixgain"); + gf_bs_read_int_log(bs, 3, "ltrt_surround_mixgain"); + } + if (gf_bs_read_int_log(bs, 1, "b_lfe_mixinfo")) { + gf_bs_read_int_log(bs, 5, "lfe_mixgain"); + } + gf_bs_read_int_log(bs, 2, "preferred_dmx_method"); + return GF_TRUE; +} + +static Bool gf_ac4_bed_render_info(GF_BitStream *bs) +{ + u8 b_tb_ch_present, b_tf_ch_present; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.2.8.8 + if (gf_bs_read_int_log(bs, 1, "b_bed_render_info")) { + if (gf_bs_read_int_log(bs, 1, "b_stereo_dmx_coeff")) { + gf_ac4_stereo_dmx_coeff(bs); + } + if (gf_bs_read_int_log(bs, 1, "b_cdmx_data_present")) { + if (gf_bs_read_int_log(bs, 1, "b_cdmx_w_to_f")) { + gf_bs_read_int_log(bs, 3, "gain_w_to_f_code"); + } + if (gf_bs_read_int_log(bs, 1, "b_cdmx_b4_to_b2")) { + gf_bs_read_int_log(bs, 3, "gain_b4_to_b2_code"); + } + if (gf_bs_read_int_log(bs, 1, "b_tm_ch_present")) { + if (gf_bs_read_int_log(bs, 1, "b_cdmx_t2_to_f_s_b")) { + gf_ac4_tool_t2_to_f_s_b(bs); + } + if (gf_bs_read_int_log(bs, 1, "b_cdmx_t2_to_f_s")) { + gf_ac4_tool_t2_to_f_s(bs); + } + } + b_tb_ch_present = gf_bs_read_int_log(bs, 1, "b_tb_ch_present"); + if (b_tb_ch_present) { + if (gf_bs_read_int_log(bs, 1, "b_cdmx_tb_to_f_s_b")) { + gf_ac4_tool_tb_to_f_s_b(bs); + } + if (gf_bs_read_int_log(bs, 1, "b_cdmx_tb_to_f_s")) { + gf_ac4_tool_tb_to_f_s(bs); + } + } + b_tf_ch_present = gf_bs_read_int_log(bs, 1, "b_tf_ch_present"); + if (b_tf_ch_present) { + if (gf_bs_read_int_log(bs, 1, "b_cdmx_tf_to_f_s_b")) { + gf_ac4_tool_tf_to_f_s_b(bs); + } + if (gf_bs_read_int_log(bs, 1, "b_cdmx_tf_to_f_s")) { + gf_ac4_tool_tf_to_f_s(bs); + } + } + if (b_tb_ch_present || b_tf_ch_present) { + if (gf_bs_read_int_log(bs, 1, "b_cdmx_tfb_to_tm")) { + gf_bs_read_int_log(bs, 3, "gain_tfb_to_tm_code"); + } + } + } + } + return GF_TRUE; +} + +static Bool gf_ac4_oamd_common_data(GF_BitStream *bs) +{ + u32 add_data_bytes, bits_used = 0; + u64 pos; + + if (gf_bs_read_int_log(bs, 1, "b_default_screen_size_ratio") == 0) { + gf_bs_read_int_log(bs, 5, "master_screen_size_ratio_code"); + } + gf_bs_read_int_log(bs, 1, "b_bed_object_chan_distribute"); + if (gf_bs_read_int_log(bs, 1, "b_additional_data")) { + add_data_bytes = gf_bs_read_int_log(bs, 1, "add_data_bytes_minus1") + 1; + if (add_data_bytes == 2) { + add_data_bytes += gf_ac4_variable_bits(bs, 2); + } + + pos = gf_bs_get_bit_offset(bs); + + gf_ac4_trim(bs); + gf_ac4_bed_render_info(bs); + + bits_used = (u32) (gf_bs_get_bit_offset(bs) - pos); + u32 bits_to_read = MIN( (u32)(8*gf_bs_available(bs)), (u32)(add_data_bytes * 8 - bits_used)); + gf_bs_read_int(bs, MIN(32, bits_to_read)); + } + return GF_TRUE; +} + +static Bool gf_ac4_substream_info_ajoc(GF_BitStream *bs, + GF_AC4SubStream* substream, + u32 *channel_count, + u8 default_presentation_flag, + u32 fs_index, + u32 frame_rate_factor, + u8 b_substreams_present) +{ + u32 i, n_fullband_dmx_signals, n_fullband_upmix_signals; + + substream->b_lfe = gf_bs_read_int_log(bs, 1, "b_lfe"); + substream->b_static_dmx = gf_bs_read_int_log(bs, 1, "b_static_dmx"); + if (substream->b_static_dmx) { + n_fullband_dmx_signals = 5; + + if (default_presentation_flag) { + *channel_count += 5; + } + } else { + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.10 n_dmx_objects_minus1 = n_fullband_dmx_signals_minus1 + substream->n_dmx_objects_minus1 = gf_bs_read_int_log(bs, 4, "n_fullband_dmx_signals_minus1"); + n_fullband_dmx_signals = substream->n_dmx_objects_minus1 + 1; + + gf_ac4_bed_dyn_obj_assignment(bs, substream, n_fullband_dmx_signals, GF_FALSE); + + // n_dmx_objects_minus1 shall contain the number of downmix objects of an A-JOC coded substream + if (default_presentation_flag) { + *channel_count += n_fullband_dmx_signals; + } + } + + if (gf_bs_read_int_log(bs, 1, "b_oamd_common_data_present")) { + gf_ac4_oamd_common_data(bs); + } + + n_fullband_upmix_signals = gf_bs_read_int_log(bs, 4, "n_fullband_upmix_signals_minus1") + 1; + if (n_fullband_upmix_signals == 16) { + n_fullband_upmix_signals += gf_ac4_variable_bits(bs, 3); + } + substream->n_umx_objects_minus1 = n_fullband_upmix_signals - 1; + + // substream is A-JOC coded + gf_ac4_bed_dyn_obj_assignment(bs, substream, n_fullband_upmix_signals, GF_TRUE); + + if (fs_index == 1) { + if (gf_bs_read_int_log(bs, 1, "b_sf_multiplier")) { + gf_bs_read_int_log(bs, 1, "sf_multiplier"); + } + } + + substream->b_substream_bitrate_indicator = gf_bs_read_int_log(bs, 1, "b_bitrate_info"); + if (substream->b_substream_bitrate_indicator) { + gf_ac4_bitreate_indicator(bs, substream); + } + + for (i = 0; i < frame_rate_factor; i++) { + gf_bs_read_int_log(bs, 1, "b_audio_ndot"); + } + + gf_ac4_substream_index_info(bs, b_substreams_present); + + // sus_ver = 1; + return GF_TRUE; +} + +static Bool gf_ac4_substream_info_obj(GF_BitStream *bs, + GF_AC4SubStream* substream, + u32 *channel_count, + u8 default_presentation_flag, + u32 fs_index, + u32 frame_rate_factor, + u8 b_substreams_present) +{ + u32 i, n_objects_code, res_bytes; + + n_objects_code = gf_bs_read_int_log(bs, 3, "n_objects_code"); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) Table 82 + if (default_presentation_flag) { + switch(n_objects_code) { + case 0: + case 1: + case 2: + case 3: + *channel_count += n_objects_code; + break; + case 4: + *channel_count += 5; + break; + default: + break; + } + } + + if (gf_bs_read_int_log(bs, 1, "b_dynamic_objects")) { + substream->b_lfe = gf_bs_read_int_log(bs, 1, "b_lfe"); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 + substream->b_substream_contains_dynamic_objects = 1; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.3.2.10.4 + if (default_presentation_flag && substream->b_lfe) { + *channel_count += 1; + } + } else { + if (gf_bs_read_int_log(bs, 1, "b_bed_objects")) { + if (gf_bs_read_int_log(bs, 1, "b_bed_start")) { + if (gf_bs_read_int_log(bs, 1, "b_ch_assign_code")) { + gf_bs_read_int_log(bs, 3, "bed_chan_assign_code"); + } else { + if (gf_bs_read_int_log(bs, 1, "b_nonstd_bed_channel_assignment")) { + gf_bs_read_int_log(bs, 17, "nonstd_bed_channel_assignment_mask"); + } else { + gf_bs_read_int_log(bs, 10, "std_bed_channel_assignment_mask"); + } + } + } + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 + substream->b_substream_contains_bed_objects = 1; + } else { + if (gf_bs_read_int_log(bs, 1, "b_isf")) { + if (gf_bs_read_int_log(bs, 1, "b_isf_start")) { + gf_bs_read_int_log(bs, 3, "isf_config"); + } + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.11.12 + substream->b_substream_contains_ISF_objects = 1; + } else { + res_bytes = gf_bs_read_int_log(bs, 4, "res_bytes"); + for (i = 0; i < res_bytes; i++) { + gf_bs_read_int_log(bs, 8, "reserved_data"); + } + } + } + } + + gf_ac4_dsi_sf_mutiplier(bs, substream, fs_index); + + substream->b_substream_bitrate_indicator = gf_bs_read_int_log(bs, 1, "b_bitrate_info"); + if (substream->b_substream_bitrate_indicator) { + gf_ac4_bitreate_indicator(bs, substream); + } + + for (i = 0; i < frame_rate_factor; i++) { + gf_bs_read_int_log(bs, 1, "b_audio_ndot"); + } + + gf_ac4_substream_index_info(bs, b_substreams_present); + + return GF_TRUE; +} + +static Bool gf_ac4_content_type(GF_BitStream *bs, GF_AC4SubStreamGroupV1* ginfo) +{ + u32 i; + + ginfo->content_classifier = gf_bs_read_int_log(bs, 3, "content_classifier"); + ginfo->b_language_indicator = gf_bs_read_int_log(bs, 1, "b_language_indicator"); + if (ginfo->b_language_indicator == 1) { + if (gf_bs_read_int_log(bs, 1, "b_serialized_language_tag")) { + gf_bs_read_int_log(bs, 1, "b_start_tag"); + gf_bs_read_int_log(bs, 16, "language_tag_chunk"); + } else { + ginfo->n_language_tag_bytes = gf_bs_read_int_log(bs, 6, "language_tag_chunk"); + for (i = 0; i < ginfo->n_language_tag_bytes; i++) { + ginfo->language_tag_bytesi = gf_bs_read_int_log(bs, 8, "language_tag_bytes"); + } + } + } + return GF_TRUE; +} + +static Bool gf_ac4_substream_group_info(GF_BitStream *bs, + GF_AC4SubStreamGroupV1* ginfo, + u8 bitstream_version, + u8 presentation_version, + u8 default_presentation_flag, + u32 frame_rate_factor, + u32 fs_index, + u32 *channel_count, + u32 *speaker_index_mask, + u32 *b_obj_or_ajoc) +{ + u32 i, local_channel_count; + GF_AC4SubStream* substream; + + ginfo->b_substreams_present = gf_bs_read_int_log(bs, 1, "b_substreams_present"); + ginfo->b_hsf_ext = gf_bs_read_int_log(bs, 1, "b_hsf_ext"); + if (gf_bs_read_int_log(bs, 1, "b_single_substream")) { + ginfo->n_lf_substreams = 1; + } else { + ginfo->n_lf_substreams = gf_bs_read_int_log(bs, 2, "n_lf_substreams_minus2") + 2; + if (ginfo->n_lf_substreams == 5) { + ginfo->n_lf_substreams += gf_ac4_variable_bits(bs, 2); + } + } + + // calloc space for substream + ginfo->substreams = gf_list_new(); + + ginfo->b_channel_coded = gf_bs_read_int_log(bs, 1, "b_channel_coded"); + if (ginfo->b_channel_coded) { + for (i = 0; i < ginfo->n_lf_substreams; i++) { + if (bitstream_version == 1) { + // bitstream_version 1 is not supported. + } else { + // sus_ver = 1; + } + + GF_SAFEALLOC(substream, GF_AC4SubStream); + gf_ac4_substream_info_chan(bs, + substream, + presentation_version, + default_presentation_flag, + fs_index, + speaker_index_mask, + frame_rate_factor, + ginfo->b_substreams_present, + &ginfo->dolby_atmos_indicator); + gf_list_add(ginfo->substreams, substream); + + if (ginfo->b_hsf_ext) { + gf_ac4_hsf_ext_substream_info(bs, ginfo->b_substreams_present); + } + + ginfo->dolby_atmos_indicator |= substream->b_ajoc; + } + } + else { + // indicate whether there is a non-channel based substream + *b_obj_or_ajoc = 1; + + if (gf_bs_read_int_log(bs, 1, "b_oamd_substream")) { + gf_ac4_oamd_substream_info(bs, ginfo->b_substreams_present); + } + + if (ginfo->substreams) { + gf_list_del(ginfo->substreams); + } + ginfo->substreams = gf_list_new(); + + for (i = 0; i < ginfo->n_lf_substreams; i++) { + GF_SAFEALLOC(substream, GF_AC4SubStream); + local_channel_count = 0; + + substream->b_ajoc = gf_bs_read_int_log(bs, 1, "b_ajoc"); + if (substream->b_ajoc) { + gf_ac4_substream_info_ajoc(bs, + substream, + &local_channel_count, + default_presentation_flag, + fs_index, + frame_rate_factor, + ginfo->b_substreams_present); + if (ginfo->b_hsf_ext) { + gf_ac4_hsf_ext_substream_info(bs, ginfo->b_substreams_present); + } + } else { + gf_ac4_substream_info_obj(bs, + substream, + &local_channel_count, + default_presentation_flag, + fs_index, + frame_rate_factor, + ginfo->b_substreams_present); + if (ginfo->b_hsf_ext) { + gf_ac4_hsf_ext_substream_info(bs, ginfo->b_substreams_present); + } + } + gf_list_add(ginfo->substreams, substream); + + if (*channel_count < local_channel_count) { + *channel_count = local_channel_count; + } + + ginfo->dolby_atmos_indicator |= substream->b_ajoc; + } + } + + ginfo->b_content_type = gf_bs_read_int_log(bs, 1, "b_content_type"); + if (ginfo->b_content_type) { + gf_ac4_content_type(bs, ginfo); + } + return GF_TRUE; +} + +static u32 gf_ac4_sgi_specifier(GF_BitStream *bs, u8 bitstream_version) +{ + u32 group_index = 0; + if (bitstream_version == 1) { + // bitstream_version 1 is not supported. + } else { + group_index = gf_bs_read_int_log(bs, 3, "group_index"); + if (group_index == 7) { + group_index += gf_ac4_variable_bits(bs, 2); + } + } + return group_index; +} + +static Bool gf_ac4_presentation_config_ext_info(GF_BitStream *bs, + GF_AC4PresentationV1* pinfo, + u8 bitstream_version) +{ + u32 i; + + pinfo->n_skip_bytes = gf_bs_read_int_log(bs, 5, "n_skip_bytes"); + if (gf_bs_read_int_log(bs, 1, "b_more_skip_bytes")) { + pinfo->n_skip_bytes += gf_ac4_variable_bits(bs, 2) << 5; + } + if (bitstream_version == 1 && pinfo->presentation_config == 7) { + // bitstream_version 1 is not supported. + } + for (i = 0; i < pinfo->n_skip_bytes; i++) { + gf_bs_read_int_log(bs, 8, "reserved"); + } + return GF_TRUE; +} + +static Bool gf_ac4_presentation_substream_info(GF_BitStream *bs) +{ + u32 substream_index; + gf_bs_read_int_log(bs, 1, "b_alternative"); + gf_bs_read_int_log(bs, 1, "b_pres_ndot"); + substream_index = gf_bs_read_int_log(bs, 2, "substream_index"); + if (substream_index == 3) { + substream_index += gf_ac4_variable_bits(bs, 2); + } + return GF_TRUE; +} + +static Bool gf_ac4_sgi_specifier_add(GF_BitStream *bs, + GF_List *idx_list, + u8 bitstream_version, + u32 *group_index) { + u32 *idx = NULL; + + GF_SAFEALLOC(idx, u32); + *idx = gf_ac4_sgi_specifier(bs, bitstream_version); + gf_list_add(idx_list, idx); + + // Mark the max group index + *group_index = MAX(*group_index, *idx); + return GF_TRUE; +} + +static Bool gf_ac4_presentation_v1_info(GF_BitStream *bs, + GF_AC4PresentationV1* pinfo, + u8 bitstream_version, + u32 frame_rate_index, + u32 *max_group_index) +{ + u32 group_index = 0, b_single_substream_group, i; + GF_List *substream_group_indexes = gf_list_new(); + u32 emdf_version, key_id; + + b_single_substream_group = gf_bs_read_int_log(bs, 1, "b_single_substream_group"); + if (b_single_substream_group != 1) { + pinfo->presentation_config = gf_bs_read_int_log(bs, 3, "presentation_config"); + if (pinfo->presentation_config == 7) { + pinfo->presentation_config += gf_ac4_variable_bits(bs, 2); + } + } + else { + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.3.2.2.1 + // b_single_substream_group = TRUE indicates that a single substream group is present + // the value of presentation_config should not be used + // set to 0x1f for gf_odf_ac4_cfg_presentation_v1_dsi() + pinfo->presentation_config = 0x1f; + } + + if (bitstream_version != 1) { + gf_ac4_presentation_version(bs, pinfo, bitstream_version); + } + + if (b_single_substream_group != 1 && pinfo->presentation_config == 6){ + pinfo->b_add_emdf_substreams = 1; + } + else { + if (bitstream_version != 1) { + pinfo->mdcompat = gf_bs_read_int_log(bs, 3, "mdcompat"); + } + pinfo->b_presentation_id = gf_bs_read_int_log(bs, 1, "b_presentation_id"); + if (pinfo->b_presentation_id) { + pinfo->presentation_id = gf_ac4_variable_bits(bs, 2); + } + + gf_ac4_frame_rate_multiply_info(bs, pinfo, frame_rate_index); + gf_ac4_frame_rate_fractions_info(bs, pinfo, frame_rate_index); + gf_ac4_emdf_info(bs, &emdf_version, &key_id); + pinfo->presentation_emdf_version = emdf_version; + pinfo->presentation_key_id = key_id; + + pinfo->b_presentation_filter = gf_bs_read_int_log(bs, 1, "b_presentation_filter"); + if (pinfo->b_presentation_filter) { + pinfo->b_enable_presentation = gf_bs_read_int_log(bs, 1, "b_enable_presentation"); + } + + if (b_single_substream_group == 1) { + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + pinfo->n_substream_groups = 1; + } + else { + pinfo->b_multi_pid = gf_bs_read_int_log(bs, 1, "b_multi_pid"); + switch (pinfo->presentation_config) { + case 0: + /* Music and Effects + Dialogue */ + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + pinfo->n_substream_groups = 2; + break; + case 1: + /* Main + DE */ + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + // In ETSI TS 103 190-2 V1.2.1 (2018-02), this should be 1 + // Main + DE are considered as one substream group in logic, but there are two substream group configurations spearately in bitstream + pinfo->n_substream_groups = 2; + break; + case 2: + /* Main + Associated Audio */ + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + pinfo->n_substream_groups = 2; + break; + case 3: + /* Music and Effects + Dialogue + Associated Audio */ + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + pinfo->n_substream_groups = 3; + break; + case 4: + /* Main + DE + Associated Audio */ + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + // In ETSI TS 103 190-2 V1.2.1 (2018-02), this should be 2 + pinfo->n_substream_groups = 3; + break; + case 5: + /* Arbitrary number of roles and substream groups */ + pinfo->n_substream_groups = gf_bs_read_int_log(bs, 1, "n_substream_groups_minus2") + 2; + if (pinfo->n_substream_groups == 5) { + pinfo->n_substream_groups += gf_ac4_variable_bits(bs, 2); + } + + for (i = 0; i < pinfo->n_substream_groups; i++) { + gf_ac4_sgi_specifier_add(bs, substream_group_indexes, bitstream_version, &group_index); + } + break; + default: + /* EMDF and other data */ + gf_ac4_presentation_config_ext_info(bs, pinfo, + bitstream_version); + break; + } + } + pinfo->b_pre_virtualized = gf_bs_read_int_log(bs, 1, "b_pre_virtualized"); + // IMS shall set b_pre_virtualized = 1 based on Dolby AC-4 in MPEG-DASH for Broadcast Services Specification + if (pinfo->presentation_version == 2) { + pinfo->b_pre_virtualized = 1; + } + pinfo->b_add_emdf_substreams = gf_bs_read_int_log(bs, 1, "b_add_emdf_substreams"); + gf_ac4_presentation_substream_info(bs); + } + if (pinfo->b_add_emdf_substreams) { + pinfo->n_add_emdf_substreams = gf_bs_read_int_log(bs, 2, "n_add_emdf_substreams"); + if (pinfo->n_add_emdf_substreams == 0) { + pinfo->n_add_emdf_substreams = gf_ac4_variable_bits(bs, 2) + 4; + } + for (i = 0; i < pinfo->n_add_emdf_substreams && i < MIN(GF_ARRAY_LENGTH(pinfo->substream_emdf_version), GF_ARRAY_LENGTH(pinfo->substream_key_id)); i++) { + gf_ac4_emdf_info(bs, &emdf_version, &key_id); + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.8.16 & E.8.17 + pinfo->substream_emdf_versioni = emdf_version; + pinfo->substream_key_idi = key_id; + } + } + + *max_group_index = MAX(*max_group_index, group_index); + pinfo->substream_group_indexs = substream_group_indexes; + + return GF_TRUE; +} + +static GF_AC4PresentationV1* gf_ac4_get_presentation_by_substreamgroup(GF_AC4StreamInfo* stream, u32 idx) +{ + u32 i, j, *x; + GF_AC4PresentationV1 *p; + + for(i = 0; i < stream->n_presentations; i++) { + p = gf_list_get(stream->presentations, i); + if (!p) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AC4 presentation %u/%u is NULL: ignoring\n", i, stream->n_presentations)); + continue; + } + for (j = 0; j < p->n_substream_groups; j++) { + x = gf_list_get(p->substream_group_indexs, j); + if(x && idx == *x) { + return p; + } + } + } + return NULL; +} + +static Bool gf_ac4_is_substream_group_part_of_default_presentation(GF_List *presentation_v1, u32 idx) +{ + u32 *j; + GF_AC4PresentationV1 *p = (GF_AC4PresentationV1*)gf_list_get(presentation_v1, 0); + + for (u32 i = 0; p != NULL && i < p->n_substream_groups; i++) { + j = (u32*)gf_list_get(p->substream_group_indexs, i); + if (*j == idx) { + return GF_TRUE; + } + } + return GF_FALSE; +} + +static u32 gf_ac4_get_channel_count_from_speaker_group_index_mask(u32 mask) +{ + u32 ch= 0; + if ((mask & 1) != 0) { // 0: L,R 0b1 + ch += 2; + } + if ((mask & 2) != 0) { // 1: C 0b10 + ch += 1; + } + if ((mask & 4) != 0) { // 2: Ls,Rs 0b100 + ch += 2; + } + if ((mask & 8) != 0) { // 3: Lb,Rb 0b1000 + ch += 2; + } + if ((mask & 16) != 0) { // 4: Tfl,Tfr 0b10000 + ch += 2; + } + if ((mask & 32) != 0) { // 5: Tbl,Tbr 0b100000 + ch += 2; + } + if ((mask & 64) != 0) { // 6: LFE 0b1000000 + ch += 1; + } + if ((mask & 128) != 0) { // 7: TL,TR 0b10000000 + ch += 2; + } + if ((mask & 256) != 0) { // 8: Tsl,Tsr 0b100000000 + ch += 2; + } + if ((mask & 512) != 0) { // 9: Tfc + ch += 1; + } + if ((mask & 1024) != 0) { // 10: Tbc + ch += 1; + } + if ((mask & 2048) != 0) { // 11: Tc + ch += 1; + } + if ((mask & 4096) != 0) { // 12: LFE2 + ch += 1; + } + if ((mask & 8192) != 0) { // 13: Bfl,Bfr + ch += 2; + } + if ((mask & 16384) != 0) { // 14: Bfc + ch += 1; + } + if ((mask & 32768) != 0) { // 15: Cb + ch += 1; + } + if ((mask & 65536) != 0) { // 16: Lscr,Rscr + ch += 2; + } + if ((mask & 131072) != 0) { // 17: Lw,Rw + ch += 2; + } + if ((mask & 262144) != 0) { // 18: Vhl,Vhr + ch += 2; + } + if ((mask & 1) != 0 && (mask & 2) != 0 && ch == 3) { // mono_stereo + ch = 2; + } + return ch; +} + +s32 gf_ac4_cfg_super_set(s32 lvalue, s32 rvalue) +{ + // This function takes two ch_mode values and returns one ch_mode value. The returned ch_mode value indicates the lowest possible ch_mode which includes all channels present in the two provided ch_mode values. + if ((lvalue == -1) || (lvalue > 15)) return rvalue; + if ((rvalue == -1) || (rvalue > 15)) return lvalue; + return AC4_SUPER_SET_CH_MODElvaluervalue; +} + +static s32 gf_ac4_presentation_ch_mode(GF_AC4PresentationV1 *p) +{ + s32 pres_ch_mode = -1, b_obj_or_ajoc = 0; + u32 i, j; + GF_AC4SubStreamGroupV1 *group; + GF_AC4SubStream *substream; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.3.3.1.27 Table 91 + for (i = 0; i < p->n_substream_groups; i++){ + group = gf_list_get(p->substream_groups, i); + if (!group) continue; + for (j = 0; j < group->n_lf_substreams; j++){ + substream = gf_list_get(group->substreams, j); + if (!substream) continue; + if (group->b_channel_coded){ + pres_ch_mode = gf_ac4_cfg_super_set(pres_ch_mode, substream->ch_mode); + }else { + b_obj_or_ajoc = 1; + } + } + } + if (b_obj_or_ajoc == 1) { + pres_ch_mode = -1; + } + return pres_ch_mode; +} + +static u32 gf_ac4_presentation_channel_mask_v1(GF_AC4PresentationV1 *p) +{ + u32 channel_mask = 0, i, j; + u8 b_obj_or_ajoc = 0; + GF_AC4SubStreamGroupV1 *group; + GF_AC4SubStream *substream; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.10.14 + for (i = 0; i < p->n_substream_groups; i++){ + group = gf_list_get(p->substream_groups, i); + if (!group) continue; + for (j = 0; j < group->n_lf_substreams; j++){ + substream = gf_list_get(group->substreams, j); + if (group->b_channel_coded){ + channel_mask |= substream->dsi_substream_channel_mask; + }else { + b_obj_or_ajoc = 1; + } + } + } + + // TODO: temporary solution according to Dolby's internal discussion + if (channel_mask == 0x03) { channel_mask = 0x01;} + + // If one substream contains Tfl, Tfr, Tbl, Tbr, Tl and Tr shall be removed. + if ((channel_mask & 0x30) && (channel_mask & 0x80)) { channel_mask &= ~0x80;} + + // objective channel mask + if (b_obj_or_ajoc == 1) { channel_mask = 0x800000; } + return channel_mask; +} + +static u8 gf_ac4_pres_b_4_back_channels_present(GF_AC4PresentationV1 *p) +{ + u32 i, j; + u8 mask = 0; + GF_AC4SubStreamGroupV1 *group; + GF_AC4SubStream *substream; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.10.12 + for (i = 0; i < p->n_substream_groups; i ++){ + group = gf_list_get(p->substream_groups, i); + if (!group) continue; + for (j = 0; j < group->n_lf_substreams; j++){ + substream = gf_list_get(group->substreams, j); + mask |= substream->b_4_back_channels_present; + } + } + return mask; +} + +static u8 gf_ac4_pres_top_channel_pairs(GF_AC4PresentationV1 *p) +{ + u8 tmp_pres_top_channel_pairs = 0; + u32 i, j; + GF_AC4SubStreamGroupV1 *group; + GF_AC4SubStream *substream; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) 6.3.3.1.30 Table 94 + for (i = 0; i < p->n_substream_groups; i ++){ + group = gf_list_get(p->substream_groups, i); + if (!group) continue; + for (j = 0; j < group->n_lf_substreams; j++){ + substream = gf_list_get(group->substreams, j); + if (tmp_pres_top_channel_pairs < substream->top_channels_present) { + tmp_pres_top_channel_pairs = substream->top_channels_present; + } + } + } + switch (tmp_pres_top_channel_pairs){ + case 0: + return 0; + case 1: + case 2: + return 1; + case 3: + return 2; + default: + return 0; + } + return 0; +} + +static s32 gf_ac4_get_ch_mode_core(u8 b_channel_coded, + u8 b_ajoc, + u8 b_static_dmx, + u8 b_lfe, + u32 ch_mode) +{ + // ETSI TS 103 190-2 V1.2.1 (2018-02) Table 92 + if (b_channel_coded == 0 && b_ajoc == 1 && b_static_dmx == 1 && b_lfe == 0) { + return 3; + } + if (b_channel_coded == 0 && b_ajoc == 1 && b_static_dmx == 1 && b_lfe == 1) { + return 4; + } + if (b_channel_coded == 1 && (ch_mode == 11 || ch_mode == 13)) { + return 5; + } + if (b_channel_coded == 1 && (ch_mode == 12 || ch_mode == 14)) { + return 6; + } + return -1; +} + +static s32 gf_ac4_get_b_presentation_core_differs(GF_AC4PresentationV1 *p, s32 pres_ch_mode) +{ + s32 pres_ch_mode_core = -1, ch_mode_core = -1; + u8 b_obj_or_ajoc_adaptive = 0; + u32 i, j; + GF_AC4SubStreamGroupV1 *group; + GF_AC4SubStream *substream; + + // ETSI TS 103 190-2 V1.2.1 (2018-02) Table 93 + for (i = 0; i < p->n_substream_groups; i ++){ + group = gf_list_get(p->substream_groups, i); + if (!group) continue; + for (j = 0; j < group->n_lf_substreams; j++){ + substream = gf_list_get(group->substreams, j); + if (!substream) continue; + if (group->b_channel_coded){ + ch_mode_core = gf_ac4_get_ch_mode_core(group->b_channel_coded, + substream->b_ajoc, + substream->b_static_dmx, + substream->b_lfe, + substream->ch_mode); + pres_ch_mode_core = gf_ac4_cfg_super_set(pres_ch_mode_core, ch_mode_core); + } else { + if (substream->b_ajoc){ + if (substream->b_static_dmx){ + ch_mode_core = gf_ac4_get_ch_mode_core(group->b_channel_coded, + substream->b_ajoc, + substream->b_static_dmx, + substream->b_lfe, + substream->ch_mode); + pres_ch_mode_core = gf_ac4_cfg_super_set(pres_ch_mode_core, ch_mode_core); + } else { + b_obj_or_ajoc_adaptive = 1; + } + } else { + b_obj_or_ajoc_adaptive = 1; + } + } + } + } + if (b_obj_or_ajoc_adaptive) { + pres_ch_mode_core = -1; + } + if (pres_ch_mode_core == pres_ch_mode) { + pres_ch_mode_core = -1; + } + return pres_ch_mode_core; +} + +static Bool gf_ac4_substream_index_table(GF_BitStream *bs, GF_AC4Config* hdr) +{ + u32 n_substreams, s; //, substream_size; + u8 b_size_present, b_more_bits; + + n_substreams = gf_bs_read_int_log(bs, 2, "n_substreams"); + if (n_substreams == 0) { + n_substreams = gf_ac4_variable_bits(bs, 2) + 4; + } + if (n_substreams == 1) { + b_size_present = gf_bs_read_int_log(bs, 1, "b_size_present"); + } else { + b_size_present = 1; + } + if (b_size_present) { + for (s = 0; s < n_substreams && gf_bs_available(bs); s++) { + b_more_bits = gf_bs_read_int_log(bs, 1, "b_more_bits"); + /*substream_size = */gf_bs_read_int_log(bs, 10, "substream_size"); + if (b_more_bits) { + //substream_size += (gf_ac4_variable_bits(bs, 2) << 10); + gf_ac4_variable_bits(bs, 2); + } + } + } + return GF_TRUE; +} + +static Bool gf_ac4_raw_frame(GF_BitStream *bs, GF_AC4Config* hdr, Bool full_parse) +{ + u8 bitstream_version, fs_index, b_program_id, b_program_uuid_present = 0, b_iframe_global = 0; + u32 n_presentations, payload_base, frame_rate_index, i, short_program_id = 0, j, *idx; + u32 max_group_index = 0, frame_rate_factor = 0, b_obj_or_ajoc = 0; + u32 channel_count = 0, speaker_group_index_mask = 0, local_channel_count = 0; + u8 program_uuid16; + Bool default_presentation_flag; + s32 wait_frames, pres_ch_mode_core, pres_ch_mode; + GF_AC4PresentationV1 *pinfo; + GF_AC4SubStreamGroupV1 *group; + GF_List *temp_groups, *hdr_p_list; + GF_AC4StreamInfo* stream = &(hdr->stream); + u64 toc_pos; + GF_Err e = GF_OK; + + // ac4_toc + toc_pos = gf_bs_get_position(bs); + + bitstream_version = gf_bs_read_int_log(bs, 2, "bitstream_version"); + if (bitstream_version == 3) { + bitstream_version += gf_ac4_variable_bits(bs, 2); + } + + gf_bs_read_int_log(bs, 10, "sequence_counter"); + if (gf_bs_read_int_log(bs, 1, "b_wait_frames")) { + wait_frames = gf_bs_read_int_log(bs, 3, "wait_frames"); + if (wait_frames > 0) { + gf_bs_read_int_log(bs, 2, "reserved"); + } + } else { + wait_frames = -1; + } + fs_index = gf_bs_read_int_log(bs, 1, "fs_index"); + frame_rate_index = gf_bs_read_int_log(bs, 4, "frame_rate_index"); + b_iframe_global = gf_bs_read_int_log(bs, 1, "b_iframe_global"); + if (gf_bs_read_int_log(bs, 1, "b_single_presentation") == 1){ + n_presentations = 1; + } else { + if (gf_bs_read_int_log(bs, 1, "b_more_presentations") == 1){ + n_presentations = gf_ac4_variable_bits(bs, 2) + 2; + } else { + n_presentations = 0; + } + } + + payload_base = 0; + if (gf_bs_read_int_log(bs, 1, "b_payload_base") == 1){ + payload_base = gf_bs_read_int_log(bs, 5, "payload_base_minus1") + 1; + if (payload_base == 0x20){ + payload_base += gf_ac4_variable_bits(bs, 3); + } + } + + // write into GF_AC4StreamInfo + stream->bitstream_version = bitstream_version; + stream->fs_index = fs_index; + stream->b_iframe_global = b_iframe_global; + stream->frame_rate_index = frame_rate_index; + stream->n_presentations = n_presentations; + + if (bitstream_version <= 1) { + /* ac4_presentation_info() is described in ETSI TS 103 190-1 1 and it is deprecated. + for (i = 0; i < n_presentations; i++) { + gf_ac4_ac4_presentation_info(); + } + */ + return GF_TRUE; + } else { + b_program_id = gf_bs_read_int_log(bs, 1, "b_program_id"); + if (b_program_id == 1) { + short_program_id = gf_bs_read_int_log(bs, 16, "short_program_id"); + b_program_uuid_present = gf_bs_read_int_log(bs, 1, "b_program_uuid_present"); + if (b_program_uuid_present == 1){ + for (i = 0; i < 16; i++){ + program_uuidi = gf_bs_read_int_log(bs, 8, "program_uuid"); + } + } + } + + // write into GF_AC4StreamInfo + stream->b_program_id = b_program_id; + stream->short_program_id = short_program_id; + stream->b_uuid = b_program_uuid_present; + for (i = 0; i < 16; i++) { + stream->program_uuidi = program_uuidi; + } + + // Calcuate the bit rate mode according to ETSI TS 103 190-2 V1.2.1 (2018-02) Annex B + if (wait_frames == 0) { + stream->ac4_bitrate_dsi.bit_rate_mode = 1; + } else if (wait_frames >= 1 && wait_frames <= 6) { + stream->ac4_bitrate_dsi.bit_rate_mode = 2; + } else if (wait_frames > 6) { + stream->ac4_bitrate_dsi.bit_rate_mode = 3; + } + stream->ac4_bitrate_dsi.bit_rate = 0; + stream->ac4_bitrate_dsi.bit_rate_precision = 0xffffffff; + + // skip the rest if full_parse is GF_FALSE + if (full_parse == GF_FALSE) { + return GF_TRUE; + } + + // calloc the space for GF_LIST<GF_AC4PresentationV1> + if (n_presentations > 0){ + stream->presentations = gf_list_new(); + } else { + stream->presentations = NULL; + return GF_TRUE; + } + + hdr_p_list = stream->presentations; + + for (i = 0; i < n_presentations; i++) { + GF_SAFEALLOC(pinfo, GF_AC4PresentationV1); + gf_ac4_presentation_v1_info(bs, + pinfo, + bitstream_version, + frame_rate_index, + &max_group_index); + gf_list_add(hdr_p_list, pinfo); + + if (gf_bs_is_overflow(bs)) { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + } + + // calloc the space for GF_LIST<GF_AC4SubStreamGroupV1> + temp_groups = gf_list_new(); + + for (i = 0; (i < max_group_index + 1) && (n_presentations > 0); i++) { + pinfo = gf_ac4_get_presentation_by_substreamgroup(&hdr->stream, i); + if (pinfo == NULL) { + break; + } + + local_channel_count = 0; + frame_rate_factor = pinfo->dsi_frame_rate_multiply_info == 0? 1: (pinfo->dsi_frame_rate_multiply_info * 2); + default_presentation_flag = gf_ac4_is_substream_group_part_of_default_presentation(hdr_p_list, i); + + GF_SAFEALLOC(group, GF_AC4SubStreamGroupV1); + gf_ac4_substream_group_info(bs, + group, + bitstream_version, + pinfo->presentation_version, + default_presentation_flag, + frame_rate_factor, + fs_index, + &local_channel_count, + &speaker_group_index_mask, + &b_obj_or_ajoc); + gf_list_add(temp_groups, group); + + if (channel_count < local_channel_count) { + channel_count = local_channel_count; + } + if (gf_bs_is_overflow(bs)) { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + } + + // write into header + for (i = 0; i < n_presentations; i++) { + GF_AC4PresentationV1 *p = (GF_AC4PresentationV1*)gf_list_get(hdr_p_list, i); + if (p == NULL) { + break; + } + + // calloc the space for GF_LIST<GF_AC4SubStreamGroupV1> + p->substream_groups = gf_list_new(); + + for (j = 0; j < p->n_substream_groups; j++) { + idx = gf_list_get(p->substream_group_indexs, j); + if (!idx) continue; + group = (GF_AC4SubStreamGroupV1*)gf_list_get(temp_groups, *idx); + if (group) { + gf_list_add(p->substream_groups, group); + p->dolby_atmos_indicator |= group->dolby_atmos_indicator; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AC4 Cannot find substream group %d for presentation %d\n", *idx, i)); + e = GF_NOT_SUPPORTED; + break; + } + } + + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.10 + // other elements in GF_AC4PresentationV1 for Sample Description Box + pres_ch_mode = gf_ac4_presentation_ch_mode(p); + p->b_presentation_channel_coded = (pres_ch_mode == -1) ? 0: 1; + if (p->b_presentation_channel_coded == 1) { + p->dsi_presentation_ch_mode = pres_ch_mode; + if (pres_ch_mode >= 11 && pres_ch_mode <= 14){ + p->pres_b_4_back_channels_present = gf_ac4_pres_b_4_back_channels_present(p); + p->pres_top_channel_pairs = gf_ac4_pres_top_channel_pairs(p); + } + p->presentation_channel_mask_v1 = gf_ac4_presentation_channel_mask_v1(p); + } + + // channel based immersive + if (p->pres_top_channel_pairs) { + p->dolby_atmos_indicator = 1; + } + + pres_ch_mode_core = gf_ac4_get_b_presentation_core_differs(p, pres_ch_mode); + p->b_presentation_core_differs = (pres_ch_mode_core == -1) ? 0: 1; + if (p->b_presentation_core_differs == 1) { + p->b_presentation_core_channel_coded = (pres_ch_mode_core == -1) ? 0: 1; + if (p->b_presentation_core_channel_coded == 1) { + // ETSI TS 103 190-2 V1.2.1 (2018-02) E.10.17 Table E.7 + p->dsi_presentation_channel_mode_core = pres_ch_mode_core - 3; + } + } + + // free auxiliary information substream_group_indexs + for (j = 0; j < gf_list_count(p->substream_group_indexs); j++) { + idx = gf_list_get(p->substream_group_indexs, j); + gf_free(idx); + } + gf_list_del(p->substream_group_indexs); + } + + // remove from temp groups that have been added elsewhere to avoid double frees + for (i = 0; i < n_presentations; i++) { + GF_AC4PresentationV1 *p = (GF_AC4PresentationV1*)gf_list_get(hdr_p_list, i); + if (p == NULL) { + break; + } + for (j=0; j < gf_list_count(p->substream_groups); j++) { + group = (GF_AC4SubStreamGroupV1*)gf_list_get(p->substream_groups, j); + if (group) { + gf_list_del_item(temp_groups, group); + } + } + } + + // free auxiliary information temp_groups that have not been copied elsewhere + while ((group = (GF_AC4SubStreamGroupV1*)gf_list_pop_back(temp_groups))) { + if (group->substreams) { + for (int s = 0; s < gf_list_count(group->substreams); s++) { + GF_AC4SubStream* subs = gf_list_get(group->substreams, s); + gf_free(subs); + } + gf_list_del(group->substreams); + } + gf_free(group); + } + gf_list_del(temp_groups); + + // If the substreams are channel-based, calculate channel_count with speaker_group_index_mask of the first/default presentation. If the substreams are non-channel-based, set channel_count to max(channel_count) + if (b_obj_or_ajoc == 0) { + hdr->channel_count = gf_ac4_get_channel_count_from_speaker_group_index_mask(speaker_group_index_mask); + } + else { + hdr->channel_count = channel_count; + } + } + + gf_ac4_substream_index_table(bs, hdr); + gf_bs_align(bs); + + hdr->toc_size = gf_bs_get_position(bs) - toc_pos; + + return e == GF_OK ? GF_TRUE : GF_FALSE; +} + +static Bool AC4_FindSyncCodeBS(GF_BitStream *bs) +{ + u8 b1; + if (gf_bs_available(bs)<6) return GF_FALSE; + u64 pos = gf_bs_get_position(bs); + u64 end = gf_bs_get_size(bs); + + pos += 1; + b1 = gf_bs_read_u8(bs); + while (pos + 1 <= end) { + u8 b2 = gf_bs_read_u8(bs); + if ((b1 == 0xac && b2 == 0x40) || (b1 == 0xac && b2 == 0x41)) { + gf_bs_seek(bs, pos - 1); + return GF_TRUE; + } + pos++; + b1 = b2; + } + return GF_FALSE; +} + +static u32 AC4_FindSyncCode(u8 *buf, u32 buflen) +{ + if (buflen<6) return buflen; + + u32 end = buflen - 6; + u32 offset = 0; + while (offset <= end) { + if ((bufoffset == 0xac && bufoffset + 1 == 0x40) || (bufoffset == 0xac && bufoffset + 1 == 0x41)) { + return offset; + } + offset++; + } + return buflen; +} + +Bool gf_ac4_parser(u8 *buf, u32 buflen, u32 *pos, GF_AC4Config *hdr, Bool full_parse, Bool start_from_toc) +{ + GF_BitStream *bs; + Bool ret; + + if (buflen < 6) return GF_FALSE; + (*pos) = AC4_FindSyncCode(buf, buflen); + if (*pos >= buflen) return GF_FALSE; + + bs = gf_bs_new((const char*)(buf + *pos), buflen, GF_BITSTREAM_READ); + ret = gf_ac4_parser_bs(bs, hdr, full_parse, start_from_toc); + gf_bs_del(bs); + + return ret; +} + +Bool gf_ac4_frame_size(GF_BitStream *bs, GF_AC4Config *hdr) +{ + hdr->frame_size = gf_bs_read_int_log(bs, 16, "frame_size"); + hdr->header_size += 2; + if (hdr->frame_size == 0xFFFF) { + hdr->frame_size = gf_bs_read_int_log(bs, 24, "frame_size"); + hdr->header_size += 3; + } + return GF_TRUE; +} + +GF_EXPORT +Bool gf_ac4_parser_bs(GF_BitStream *bs, GF_AC4Config *hdr, Bool full_parse, Bool start_from_toc) +{ + u32 sync_word = 0; + u64 pos; + GF_AC4StreamInfo* stream; + if (!hdr || !bs) return GF_FALSE; + + pos = gf_bs_get_position(bs); + stream = &(hdr->stream); + + // clean the GF_List data if exits + if (full_parse) { + gf_odf_ac4_cfg_clean_list(hdr); + } + + if (start_from_toc) { + goto skip_header; + } + + if (!AC4_FindSyncCodeBS(bs)) { + return GF_FALSE; + } + pos = gf_bs_get_position(bs); + + sync_word = gf_bs_read_u16(bs); + if (sync_word != 0xAC40 && sync_word != 0xAC41) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AC4 Wrong sync word detected (0x%X - expecting 0xAC40 or 0xAC41).\n", sync_word)); + return GF_FALSE; + } + hdr->header_size = 2; + + gf_ac4_frame_size(bs, hdr); + +skip_header: + if (!gf_ac4_raw_frame(bs, hdr, full_parse)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AC4 Fail to parse raw ac4 frame\n")); + + gf_bs_seek(bs, pos); + return GF_FALSE; + } + + if (stream->bitstream_version <= 1) { + // bitstream_version <= 1 is not supported. + + gf_bs_seek(bs, pos); + return GF_FALSE; + } + + if (sync_word == 0xAC41) { + hdr->crc_size = 2; + } else { + hdr->crc_size = 0; + } + + /* fill some AC4 DSI info */ + stream->ac4_dsi_version = 1; + if (stream->fs_index >= GF_ARRAY_LENGTH(AC4_SAMPLING_FREQ_TABLE)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AC4 stream fs_index %d >= length AC4_SAMPLING_FREQ_TABLE\n", stream->fs_index)); + gf_bs_seek(bs, pos); + return GF_FALSE; + } + hdr->sample_rate = AC4_SAMPLING_FREQ_TABLEstream->fs_index; + if (stream->fs_index == 0) { + + if (stream->frame_rate_index >= MIN(GF_ARRAY_LENGTH(AC4_SAMPLE_DELTA_TABLE_441), GF_ARRAY_LENGTH(AC4_MEDIA_TIMESCALE_441))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AC4 stream frame_rate_index %d >= length AC4_SAMPLE_DELTA_TABLE_441 or AC4_MEDIA_TIMESCALE_441\n", stream->frame_rate_index)); + gf_bs_seek(bs, pos); + return GF_FALSE; + } + hdr->sample_duration = AC4_SAMPLE_DELTA_TABLE_441stream->frame_rate_index; + hdr->media_time_scale = AC4_MEDIA_TIMESCALE_441stream->frame_rate_index; + + } else { + + if (stream->frame_rate_index >= MIN(GF_ARRAY_LENGTH(AC4_SAMPLE_DELTA_TABLE_48), GF_ARRAY_LENGTH(AC4_MEDIA_TIMESCALE_48))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AC4 stream frame_rate_index %d >= length AC4_SAMPLE_DELTA_TABLE_48 or AC4_MEDIA_TIMESCALE_48\n", stream->frame_rate_index)); + gf_bs_seek(bs, pos); + return GF_FALSE; + } + + hdr->sample_duration = AC4_SAMPLE_DELTA_TABLE_48stream->frame_rate_index; + hdr->media_time_scale = AC4_MEDIA_TIMESCALE_48stream->frame_rate_index; + } + + gf_bs_seek(bs, pos); + return GF_TRUE; +} + #endif /*GPAC_DISABLE_AV_PARSERS*/
View file
gpac-2.4.0.tar.gz/src/media_tools/avilib.c -> gpac-26.02.0.tar.gz/src/media_tools/avilib.c
Changed
@@ -1769,15 +1769,14 @@ } gf_free(AVI->video_superindex); } - for (j=0; j<AVI->anum; j++) { if(AVI->trackj.audio_index) gf_free(AVI->trackj.audio_index); if(AVI->trackj.audio_superindex) { avisuperindex_chunk *asi = AVI->trackj.audio_superindex; - if (asi->aIndex) gf_free(asi->aIndex); + if (asi && asi->aIndex) gf_free(asi->aIndex); - if (asi->stdindex) { + if (asi && asi->stdindex) { for (j=0; j < NR_IXNN_CHUNKS; j++) { if (asi->stdindexj->aIndex) gf_free(asi->stdindexj->aIndex); @@ -1942,6 +1941,8 @@ { if (n>0xFFFFFFFF) ERR_EXIT(AVI_ERR_READ) hdrl_len = (u32) n; + if (hdrl_data) + gf_free(hdrl_data); hdrl_data = (unsigned char *) gf_malloc((u32)n); if(hdrl_data==0) ERR_EXIT(AVI_ERR_NO_MEM); @@ -1988,8 +1989,9 @@ for(i=0; i<hdrl_len;) { - /* List tags are completly ignored */ + if (i+4>hdrl_len) ERR_EXIT(AVI_ERR_READ) + /* List tags are completly ignored */ #ifdef DEBUG_ODML GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("avilib TAG %c%c%c%c\n", (hdrl_data+i)0, (hdrl_data+i)1, (hdrl_data+i)2, (hdrl_data+i)3)); #endif @@ -1998,7 +2000,8 @@ i+= 12; continue; } - if (i+4>=hdrl_len) ERR_EXIT(AVI_ERR_READ) + + if (i+8>hdrl_len) ERR_EXIT(AVI_ERR_READ) n = str2ulong(hdrl_data+i+4); n = PAD_EVEN(n); @@ -2010,6 +2013,7 @@ if(strnicmp((char *)hdrl_data+i,"strh",4)==0) { i += 8; + if (i+4>hdrl_len) ERR_EXIT(AVI_ERR_READ) #ifdef DEBUG_ODML GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("avilib TAG %c%c%c%c\n", (hdrl_data+i)0, (hdrl_data+i)1, (hdrl_data+i)2, (hdrl_data+i)3)); #endif @@ -2051,8 +2055,11 @@ AVI->trackAVI->aptr.a_vbr = !str2ulong(hdrl_data+i+44); AVI->trackAVI->aptr.padrate = str2ulong(hdrl_data+i+24); - memcpy(&AVI->stream_headersAVI->aptr, hdrl_data + i, - sizeof(alAVISTREAMHEADER)); + if (i + sizeof(alAVISTREAMHEADER) > hdrl_len) { + ERR_EXIT(AVI_ERR_READ); + } + + memcpy(&AVI->stream_headersAVI->aptr, hdrl_data + i, sizeof(alAVISTREAMHEADER)); // auds_strh_seen = 1; lasttag = 2; /* auds */ @@ -2070,6 +2077,9 @@ num_stream++; } else if(strnicmp((char*)hdrl_data+i,"dmlh",4) == 0) { + + if (i+8+4>hdrl_len) ERR_EXIT(AVI_ERR_READ) + AVI->total_frames = str2ulong(hdrl_data+i+8); #ifdef DEBUG_ODML GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("avilib real number of frames %d\n", AVI->total_frames)); @@ -2081,9 +2091,12 @@ i += 8; if(lasttag == 1) { + if (i+sizeof(alBITMAPINFOHEADER)>hdrl_len) ERR_EXIT(AVI_ERR_READ) + alBITMAPINFOHEADER bih; memcpy(&bih, hdrl_data + i, sizeof(alBITMAPINFOHEADER)); + if (bih.bi_size < 4) ERR_EXIT(AVI_ERR_READ) bih.bi_size = str2ulong((unsigned char *)&bih.bi_size); if (i + bih.bi_size > hdrl_len) ERR_EXIT(AVI_ERR_READ) @@ -2092,6 +2105,8 @@ if (AVI->bitmap_info_header != NULL) memcpy(AVI->bitmap_info_header, hdrl_data + i, bih.bi_size); + if (i+16+4>hdrl_len) ERR_EXIT(AVI_ERR_READ) + AVI->width = str2ulong(hdrl_data+i+4); AVI->height = str2ulong(hdrl_data+i+8); vids_strf_seen = 1; @@ -2101,9 +2116,9 @@ memcpy(AVI->compressor2, hdrl_data+i+16, 4); AVI->compressor24 = 0; - if (n>40) { + if ((n>40) && (hdrl_len > (i+40))) { if (n>0xFFFFFFFF) ERR_EXIT(AVI_ERR_READ) - AVI->extradata_size = (u32) (n - 40); + AVI->extradata_size = (u32) MIN(n - 40, hdrl_len-i-40); AVI->extradata = gf_malloc(sizeof(u8)* AVI->extradata_size); if (!AVI->extradata) ERR_EXIT(AVI_ERR_NO_MEM) memcpy(AVI->extradata, hdrl_data + i + 40, AVI->extradata_size); @@ -2112,6 +2127,8 @@ } else if(lasttag == 2) { + if (i>=hdrl_len) ERR_EXIT(AVI_ERR_READ) + alWAVEFORMATEX *wfe; char *nwfe; int wfes; @@ -2141,6 +2158,7 @@ } AVI->wave_format_exAVI->aptr = wfe; } + if (i+14+4>hdrl_len) ERR_EXIT(AVI_ERR_READ) AVI->trackAVI->aptr.a_fmt = str2ushort(hdrl_data+i ); @@ -2192,7 +2210,11 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("avilib Invalid Header, bIndexSubType != 0\n")); } avail -= 32; - if (avail < (int) AVI->video_superindex->nEntriesInUse*16) ERR_EXIT(AVI_ERR_READ) + if (AVI->video_superindex->nEntriesInUse >= GF_INT_MAX/16 || + avail < (int) AVI->video_superindex->nEntriesInUse*16) { + + ERR_EXIT(AVI_ERR_READ) + } AVI->video_superindex->aIndex = (avisuperindex_entry*) gf_malloc (AVI->video_superindex->wLongsPerEntry * AVI->video_superindex->nEntriesInUse * sizeof (u32)); @@ -2237,7 +2259,7 @@ int avail = (int) (hdrl_len-i); if (avail<32) ERR_EXIT(AVI_ERR_READ) - AVI->trackAVI->aptr.audio_superindex = (avisuperindex_chunk *) gf_malloc (sizeof (avisuperindex_chunk)); + GF_SAFEALLOC(AVI->trackAVI->aptr.audio_superindex, avisuperindex_chunk); memcpy (AVI->trackAVI->aptr.audio_superindex->fcc, a, 4); a += 4; AVI->trackAVI->aptr.audio_superindex->dwSize = str2ulong((unsigned char*)a); @@ -2263,11 +2285,26 @@ } avail -= 32; - if (avail < (int) AVI->trackAVI->aptr.audio_superindex->nEntriesInUse*16) ERR_EXIT(AVI_ERR_READ) + if (AVI->trackAVI->aptr.audio_superindex->nEntriesInUse >= GF_INT_MAX/16 + || avail < (int) AVI->trackAVI->aptr.audio_superindex->nEntriesInUse*16) { + + ERR_EXIT(AVI_ERR_READ) + } - AVI->trackAVI->aptr.audio_superindex->aIndex = (avisuperindex_entry*) + if (AVI->trackAVI->aptr.audio_superindex->nEntriesInUse) { + + // must be 4 so that wLongsPerEntry*sizeof(u32) == sizeof(avisuperindex_entry) + if (AVI->trackAVI->aptr.audio_superindex->wLongsPerEntry != 4) { + ERR_EXIT(AVI_ERR_READ) + } + + AVI->trackAVI->aptr.audio_superindex->aIndex = (avisuperindex_entry*) gf_malloc (AVI->trackAVI->aptr.audio_superindex->wLongsPerEntry * AVI->trackAVI->aptr.audio_superindex->nEntriesInUse * sizeof (u32)); + } + else { + AVI->trackAVI->aptr.audio_superindex->aIndex = NULL; + } // position of ix## chunks for (j=0; j<AVI->trackAVI->aptr.audio_superindex->nEntriesInUse; ++j) { @@ -2448,7 +2485,10 @@ for (j=0; j<AVI->video_superindex->nEntriesInUse; j++) { // read from file - chunk_start = en = (char*) gf_malloc ((u32) (AVI->video_superindex->aIndexj.dwSize+hdrl_len) ); + u32 chunk_size = (u32) (AVI->video_superindex->aIndexj.dwSize+hdrl_len); + if (!chunk_size) + continue; + chunk_start = en = (char*) gf_malloc(chunk_size); if (gf_fseek(AVI->fdes, AVI->video_superindex->aIndexj.qwOffset, SEEK_SET) == (u64)-1) { gf_free(chunk_start); @@ -2469,14 +2509,26 @@ // skip header en += hdrl_len; nvi += nrEntries; + + if (nvi <= 0 || nvi >= GF_INT_MAX/sizeof(video_index_entry)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("avilib invalid nvi value %d\n", nvi)); + gf_free(chunk_start); + ERR_EXIT(AVI_ERR_READ); + } + AVI->video_index = (video_index_entry *) gf_realloc (AVI->video_index, nvi * sizeof (video_index_entry)); + if (!AVI->video_index) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("avilib out of mem (size = %ld)\n", nvi * sizeof (video_index_entry))); - exit(1); + gf_free(chunk_start); + ERR_EXIT(AVI_ERR_NO_MEM); } while (k < nvi) { + if (en-chunk_start+8 > chunk_size) + break; + AVI->video_indexk.pos = offset + str2ulong((unsigned char*)en); en += 4; AVI->video_indexk.len = str2ulong_len((unsigned char*)en); @@ -2604,6 +2656,7 @@ for(j=0; j<AVI->anum; ++j) { if(AVI->trackj.audio_chunks) { + if (AVI->trackj.audio_index) gf_free(AVI->trackj.audio_index); AVI->trackj.audio_index = (audio_index_entry *) gf_malloc((naij+1)*sizeof(audio_index_entry)); memset(AVI->trackj.audio_index, 0, (naij+1)*(sizeof(audio_index_entry))); if(AVI->trackj.audio_index==0) ERR_EXIT(AVI_ERR_NO_MEM); @@ -2632,11 +2685,15 @@ aud_chunks += AVI->total_frames; AVI->trackj.audio_index = (audio_index_entry *) gf_realloc( AVI->trackj.audio_index, (aud_chunks+1)*sizeof(audio_index_entry)); + if (!AVI->trackj.audio_index) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("avilib Internal error in avilib -- no mem\n")); AVI_errno = AVI_ERR_NO_MEM; return -1; } + + if (AVI->anum <= j) + AVI->anum = j+1; } /* Check if we got a tag ##db, ##dc or ##wb */ @@ -2722,6 +2779,7 @@ for(j=0; j<AVI->anum; ++j) { if(AVI->trackj.audio_chunks) { + if (AVI->trackj.audio_index) gf_free(AVI->trackj.audio_index); AVI->trackj.audio_index = (audio_index_entry *) gf_malloc((naij+1)*sizeof(audio_index_entry)); memset(AVI->trackj.audio_index, 0, (naij+1)*(sizeof(audio_index_entry))); if(AVI->trackj.audio_index==0) ERR_EXIT(AVI_ERR_NO_MEM);
View file
gpac-2.4.0.tar.gz/src/media_tools/crypt_tools.c -> gpac-26.02.0.tar.gz/src/media_tools/crypt_tools.c
Changed
@@ -126,11 +126,11 @@ GF_Err e; has_key = GF_TRUE; e = gf_bin128_parse(att->value, tkc->keys0.key ); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Cannnot parse key value in CrypTrack\n")); + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Cannnot parse key value in CrypTrack\n")); info->last_parse_error = GF_BAD_PARAM; - return; - } + return; + } } else if (!stricmp(att->name, "salt")) { u32 len, j; @@ -307,6 +307,7 @@ tkc->keys0.constant_IV_size = atoi(att->value); if ((tkc->keys0.constant_IV_size != 8) && (tkc->keys0.constant_IV_size != 16)) { GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Constant IV size %d is not 8 or 16\n", att->value)); + info->last_parse_error = GF_BAD_PARAM; } } else if (!stricmp(att->name, "constant_IV") @@ -329,6 +330,14 @@ else if (!stricmp(att->name, "encryptSliceHeader")) { tkc->allow_encrypted_slice_header = !strcmp(att->value, "yes") ? GF_TRUE : GF_FALSE; } + else if (!stricmp(att->name, "encryptNonVCLs")) { + if (!strcmp(att->value, "yes")) + tkc->allow_encrypted_nonVCLs = GF_CRYPT_NONVCL_CLEAR_NONE; + else if (!strcmp(att->value, "no")) + tkc->allow_encrypted_nonVCLs = GF_CRYPT_NONVCL_CLEAR_ALL; + else + tkc->allow_encrypted_nonVCLs = GF_CRYPT_NONVCL_CLEAR_SEI_AUD; + } else if (!stricmp(att->name, "blockAlign")) { if (!strcmp(att->value, "disable")) tkc->block_align = 1; else if (!strcmp(att->value, "always")) tkc->block_align = 2; @@ -440,22 +449,25 @@ if (!stricmp(att->name, "KID")) { GF_Err e = gf_bin128_parse(att->value, tkc->keystkc->nb_keys.KID); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Cannnot parse KID\n")); - return; - } + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Cannnot parse KID\n")); + info->last_parse_error = GF_BAD_PARAM; + return; + } } else if (!stricmp(att->name, "value")) { GF_Err e = gf_bin128_parse(att->value, tkc->keystkc->nb_keys.key); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Cannnot parse key value\n")); - return; - } + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Cannnot parse key value\n")); + info->last_parse_error = GF_BAD_PARAM; + return; + } } else if (!stricmp(att->name, "hlsInfo")) { if (!strstr(att->value, "URI=\"")) { - GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Missing URI in HLS info %s\n", att->value)); - return; + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("CENC Missing URI in HLS info %s\n", att->value)); + info->last_parse_error = GF_BAD_PARAM; + return; } if (tkc->keystkc->nb_keys.hls_info) gf_free(tkc->keystkc->nb_keys.hls_info); tkc->keystkc->nb_keys.hls_info = gf_strdup(att->value); @@ -623,6 +635,8 @@ return GF_OUT_OF_MEM; } + //we use implicit mode, don't set any filter ID + sprintf(an_arg, "mp4dmx:mov=%p", mp4); gf_dynstrcat(&szArgs, an_arg, NULL); if (fragment_name) { @@ -639,7 +653,7 @@ return e; } - gf_dynstrcat(&szArgs, "cdcrypt:FID=1", NULL); + gf_dynstrcat(&szArgs, "cdcrypt", NULL); if (drm_file) { gf_dynstrcat(&szArgs, ":cfile=", NULL); gf_dynstrcat(&szArgs, drm_file, NULL); @@ -653,7 +667,7 @@ return e; } - gf_dynstrcat(&szArgs, "SID=1", NULL); + gf_dynstrcat(&szArgs, "xps_inband=auto", NULL); if (fragment_name) { gf_dynstrcat(&szArgs, ":sseg:noinit:store=frag:refrag:cdur=1000000000", NULL); } else { @@ -664,8 +678,7 @@ gf_dynstrcat(&szArgs, ":store=flat", NULL); } } - gf_dynstrcat(&szArgs, ":xps_inband=auto", NULL); - + if (gf_isom_has_keep_utc_times(mp4)) gf_dynstrcat(&szArgs, ":keep_utc", NULL); @@ -745,7 +758,7 @@ char an_arg100; char *arg_dst=NULL; u32 progress = (u32) -1; - GF_Filter *src, *dst, *crypt; + GF_Filter *src, *dst, *cryptf; GF_FilterSession *fsess; GF_Err e = GF_OK; @@ -774,12 +787,12 @@ gf_dynstrcat(&szArgs, "cecrypt:FID=1:cfile=", NULL); gf_dynstrcat(&szArgs, drm_file, NULL); - crypt = gf_fs_load_filter(fsess, szArgs, &e); + cryptf = gf_fs_load_filter(fsess, szArgs, &e); gf_free(szArgs); szArgs = NULL; - if (!crypt) { + if (!cryptf) { gf_fs_del(fsess); GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("Encrypter Cannot load encryptor: %s\n", gf_error_to_string(e) )); return e;
View file
gpac-2.4.0.tar.gz/src/media_tools/dash_client.c -> gpac-26.02.0.tar.gz/src/media_tools/dash_client.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Cyril Concolato - * Copyright (c) Telecom ParisTech 2010-2024 + * Copyright (c) Telecom ParisTech 2010-2025 * All rights reserved * * This file is part of GPAC / Adaptive HTTP Streaming @@ -45,6 +45,11 @@ /*set to 1 if you want MPD to use SegmentTimeline*/ #define M3U8_TO_MPD_USE_SEGTIMELINE 0 +//number of segments to wait before timout when refreshing manifest +//we keep this high for MABR cases where the manifest is most - note that we don't check if session is MABR +//as the session could be HTTP on a MABR->HTTP gateway +#define SEGLIST_TIMEOUT_SEG 4 + typedef enum { GF_DASH_STATE_STOPPED = 0, @@ -66,6 +71,18 @@ GF_DASH_CHAIN_POP }; +enum +{ + //not coming from broadcast + GF_DASH_MCAST_NONE=0, + //coming from broadcast, wait clock + GF_DASH_MCAST_INIT, + //coming from broadcast, clock synchronized to filenames + GF_DASH_MCAST_SYNC_SOURCE, + //coming from broadcast, clock is synchronized using the default mechanism: UTC for DASH, last playlist entry for HLS + GF_DASH_MCAST_SYNC_DEFAULT, +}; + //shifts AST in the past(>0) or future (<0) so that client starts request in the future or in the past //#define FORCE_DESYNC 4000 @@ -84,6 +101,7 @@ GF_FileDownload getter; char *base_url; + char *query_part; u32 max_cache_duration, max_width, max_height; u8 max_bit_per_pixel; @@ -156,13 +174,12 @@ s32 suggested_presentation_delay; - //0: not ROUTE - 1: ROUTE but clock not init - 2: ROUTE clock init - u32 route_clock_state; - //ROUTE AST shift in ms - s32 route_ast_shift; - u32 route_skip_segments_ms; - Bool route_low_latency; - u32 route_last_retune; + u32 mcast_clock_state; + //multicast AST shift in ms + s32 mcast_ast_shift; + u32 mcast_skip_segments_ms; + Bool mcast_low_latency; + u32 mcast_last_retune; Bool initial_period_tunein; u32 preroll_state; @@ -226,6 +243,10 @@ GF_DASHFileIOSession pending_utc_session; u32 pending_utc_idx; + + GF_DASHCrossASMode cross_as_mode; + //max segment start time in period, reset after seek/chaining/period switch + Double max_last_seg_start; }; static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic); @@ -300,8 +321,10 @@ u32 min_representation_bitrate; u32 nb_segments_in_rep; - //cached index of this group in dash->groups to avoid using gf_list_find - u32 groups_idx; + //index of this group as seen by the API + //* if no cross-AS switching, this is the 0-based index in dash->groups + //* if cross-AS switching is used, this is the index of the master group of the sets + u32 index; /* Segment duration as advertised in the MPD for the real duration of the segment being downloaded see current_downloaded_segment_duration */ @@ -325,6 +348,7 @@ u32 ast_offset; Bool init_segment_is_media; + u32 init_segment_start_number; u32 max_cached_segments, nb_cached_segments; segment_cache_entry *cached; @@ -410,6 +434,9 @@ //for dash custom, allows temporary disabling a group Bool disabled; + //0: no SSR, 1: all SSR, 2: some SSRs + u32 has_ssr; + u32 nb_parts, part_idx; /* current segment index in BBA and BOLA algorithm */ u32 current_index; @@ -439,6 +466,25 @@ u32 sidx_start; GF_MPD_Representation *pending_sidx_rep; u32 last_error_time; + + u32 hls_start_num; + + //cross-AdaptationSet switching: + //list of other groups switchable with this group + GF_List *xas_groups; + //for groups switchable to a group, indicate base group (hodling the xas_groups) + GF_DASH_Group *xas_base; + //only on base: if not 0, indicates the 1-based index of the selected group in xas_group + u32 xas_selected; + //when force-switching a quality, indicate to which group the forcing shall be done + //this is set on the currently active group + u32 xas_force_switch; + + //indicate an SSR->regular switch should be done after next segment + Bool ssr_switch; + //when switching from SSR to regular representation in the same AS at initial tunein, try to use this index + //which corresponds to the rep that would have been setup if no SSR + u32 ssr_shadow_idx_plus_one; }; //wait time before requesting again a M3U8 child playlist update when something goes wrong during the update: either same file or the expected next segment is not there @@ -457,6 +503,37 @@ +static GF_Err gf_dash_set_base_url(GF_DashClient *dash, const char *manifest_url) +{ + if (!dash || !manifest_url) return GF_BAD_PARAM; + GF_Err e = GF_OK; + if (dash->base_url) gf_free(dash->base_url); + char *sep_query = (char *) strrchr(manifest_url, '?'); + if (sep_query) sep_query0 = 0; + dash->base_url = gf_strdup(manifest_url); + if (!dash->base_url) e = GF_OUT_OF_MEM; + if (sep_query) { + sep_query0 = '?'; + dash->query_part = gf_strdup(sep_query); + if (!dash->query_part) e = GF_OUT_OF_MEM; + } else if (dash->query_part) { + gf_free(dash->query_part); + dash->query_part = NULL; + } + return e; +} + +static GF_DASH_Group *gf_dash_get_active_group(GF_DashClient *dash, u32 idx) +{ + GF_DASH_Group *group = dash ? gf_list_get(dash->groups, idx) : NULL; + if (!group) return NULL; + if (group->xas_selected) { + group = gf_list_get(group->xas_groups, group->xas_selected-1); + if (!group) return NULL; + } + return group; +} + static const char *gf_dash_get_mime_type(GF_MPD_SubRepresentation *subrep, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set) { if (subrep && subrep->mime_type) return subrep->mime_type; @@ -594,7 +671,7 @@ } } } - gf_blob_release(cache_name); + gf_blob_release(cache_name); dash->dash_io->del(dash->dash_io, dash->pending_utc_session); dash->pending_utc_session = NULL; @@ -603,6 +680,23 @@ GF_Err gf_dash_download_resource(GF_DashClient *dash, GF_DASHFileIOSession *sess, const char *url, u64 start_range, u64 end_range, u32 persistent_mode, GF_DASH_Group *group); +static void dash_ssr_adjust_group_start(GF_DASH_Group *group, Double seg_dur, u32 ssr_parts) +{ + if (ssr_parts<=1) return; + + Double seg_start = seg_dur * group->download_segment_index; + //TODO use SegmentSequenceProperties.SAP to identify switch points + //for now we assume each part is a switch point + Double time_in_seg = group->start_playback_range - seg_start; + u32 nb_part = 0; + Double pdur = seg_dur / ssr_parts; + while (time_in_seg > pdur) { + nb_part++; + time_in_seg -= pdur; + } + group->part_idx = nb_part; +} + static void gf_dash_group_timeline_setup_single(GF_MPD *mpd, GF_DASH_Group *group, u64 fetch_time) { GF_MPD_SegmentTimeline *timeline = NULL; @@ -614,20 +708,50 @@ u32 ast_diff, start_number; Double ast_offset = 0; - if (mpd->type==GF_MPD_TYPE_STATIC) { - if (group->dash->route_clock_state) - goto setup_route; - return; - } - - //always init clock even if active period is a remote one -#if 0 - if (group->period->origin_base_url && (group->period->type != GF_MPD_TYPE_DYNAMIC)) - return; -#endif - - /*M3U8 does not use NTP sync, we solve edge while loading subplaylist */ + /*M3U8 does not use NTP sync, we solve edge while loading subplaylist*/ if (group->dash->is_m3u8) { + // For broadcast, locate the current segment being receibed and tune on this entry + if (group->dash->mcast_clock_state) { + //force refreshing the root manifest, because the download session might be tuned on a child playlist + //which will not have the x-mcast-first-seg set + gf_dash_download_resource(group->dash, &(group->dash->mpd_dnload), group->dash->base_url, 0, 0, 1, NULL); + + const char *val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-mcast-first-seg"); + if (!val) + return; + u32 g_idx; + s32 pl_idx = -1; + for (g_idx = 0; g_idx<gf_list_count(group->dash->groups); g_idx++) { + GF_DASH_Group *agr = gf_list_get(group->dash->groups, g_idx); + GF_MPD_Representation *rep = gf_list_get(agr->adaptation_set->representations, agr->active_rep_index); + if (!rep->segment_list) continue; + + u32 idx, nb_segs=gf_list_count(rep->segment_list->segment_URLs); + for (idx=0; idx<nb_segs; idx++) { + GF_MPD_SegmentURL *segurl = gf_list_get(rep->segment_list->segment_URLs, idx); + if (strstr(val, segurl->media)) { + pl_idx = idx; + break; + } + } + if (pl_idx>=0) break; + } + if (pl_idx>=0) { + for (g_idx = 0; g_idx<gf_list_count(group->dash->groups); g_idx++) { + GF_DASH_Group *agr = gf_list_get(group->dash->groups, g_idx); + agr->download_segment_index = pl_idx; + agr->timeline_setup = GF_TRUE; + GF_MPD_Representation *rep = gf_list_get(agr->adaptation_set->representations, agr->active_rep_index); + agr->nb_segments_in_rep = rep->segment_list ? gf_list_count(rep->segment_list->segment_URLs) : 0; + } + group->dash->mcast_clock_state = GF_DASH_MCAST_SYNC_SOURCE; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Broadcast HLS tuned on playlist entry %d (segment %s)\n", pl_idx, val)); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Broadcast HLS failed to locate segment %s in any playlist, will use last entry\n", val)); + group->dash->mcast_clock_state = GF_DASH_MCAST_SYNC_DEFAULT; + } + return; + } //check if we talk to GPAC, in which case allow the tune-in request to use an open-range //otherwise, only allow merging of open-range on the first part of the segment //this is because http does not allow a range request response to have an undefined size (only the resource size can be unknown) @@ -641,6 +765,14 @@ return; } + //static MPD, check broadcast bootstraping + if (mpd->type==GF_MPD_TYPE_STATIC) { + if (group->dash->mcast_clock_state) + goto setup_multicast_clock; + return; + } + + if (group->dash->is_smooth) { u32 seg_idx = 0; u64 timeshift = 0; @@ -717,18 +849,13 @@ return; } - //if ROUTE and clock not setup, do it -setup_route: - val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route"); - if (val && !group->dash->utc_drift_estimate) { + //if multicast and clock not setup, do it +setup_multicast_clock: + if (group->dash->mcast_clock_state && !group->dash->utc_drift_estimate) { u32 i; GF_MPD_Period *dyn_period=NULL; u32 found = 0; u64 timeline_offset_ms=0; - if (!group->dash->route_clock_state) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Detected ROUTE DASH service ID %s\n", val)); - group->dash->route_clock_state = 1; - } for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) { dyn_period = gf_list_get(group->dash->mpd->periods, i); @@ -737,20 +864,23 @@ dyn_period = NULL; } if (!dyn_period) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH ROUTE with no dynamic period, cannot init clock yet\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Multicast with no dynamic period, cannot init clock yet\n")); return; } - //for m3u8 we force refreshing the root manifest, because the download session might be tuned on a child playlist - //which will not have the x-route-first-seg set - if (group->dash->is_m3u8) { - gf_dash_download_resource(group->dash, &(group->dash->mpd_dnload), group->dash->base_url, 0, 0, 1, NULL); - } - val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-first-seg"); + val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-mcast-first-seg"); if (!val) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Waiting for ROUTE clock ...\n")); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Waiting for Multicast clock ...\n")); return; } + const char *root_url = strstr(group->dash->base_url+7, "gmcast/"); + if (root_url) { + root_url = strchr(root_url+7, '/'); + if (root_url) root_url++; + } + else root_url = group->dash->base_url; + //if no parent path use local + if (!strstr(root_url, "/")) root_url = "./"; for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) { u64 sr, seg_dur_ms; @@ -781,11 +911,19 @@ continue; } u32 tpl_use_time=0; - gf_mpd_resolve_url(group->dash->mpd, rep, set, dyn_period, "./", 0, GF_MPD_RESOLVE_URL_MEDIA_NOSTART, 9876, 0, &seg_url, &sr, &sr, &seg_dur_ms, NULL, NULL, NULL, &tpl_use_time); + gf_mpd_resolve_url(group->dash->mpd, rep, set, dyn_period, root_url, 0, GF_MPD_RESOLVE_URL_MEDIA_NOSTART, 9876, 0, &seg_url, &sr, &sr, &seg_dur_ms, NULL, NULL, NULL, &tpl_use_time, -1); dyn_period->duration = dur; - sep = seg_url ? strstr(seg_url, "987") : NULL; + size_t seg_url_len = seg_url ? strlen(seg_url) : 0; + sep = seg_url ? strstr(seg_url, "9876") : NULL; + //check last occurence + while (sep && seg_url) { + char *sep2 = strstr(sep+4, "9876"); + if (sep2) sep = sep2; + else break; + } + if (!sep) { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Failed to resolve template for segment #9876 on rep #%d\n", j+1)); if (seg_url) gf_free(seg_url); @@ -798,9 +936,11 @@ len = (u32) strlen(seg_url); if (!strncmp(val, seg_url, len)) { u64 number=0; - char szTemplate100; + u32 template_len = (u32) (seg_url_len + 20); // Allocate extra space for "%" + char *szTemplate; + GF_SAFE_ALLOC_N(szTemplate, template_len, char); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Resolve ROUTE clock on bootstrap segment URL %s template %s\n", val, seg_url+2)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Resolve Multicast clock on bootstrap segment URL %s template %s\n", val, seg_url)); strcpy(szTemplate, seg_url); strcat(szTemplate, "%"); @@ -814,7 +954,7 @@ if (sscanf(val, szTemplate, &number) == 1) { u32 startNum = 1; u64 pto=0; - u32 segdur=0, timescale=0; + u32 segdur=0, timescale=1; //MPD timescale is 1 if not present Bool is_valid = GF_TRUE; GF_MPD_SegmentTimeline *seg_timeline=NULL; if (dyn_period->segment_template) { @@ -822,7 +962,7 @@ segdur = (u32) dyn_period->segment_template->duration; seg_timeline = dyn_period->segment_template->segment_timeline; pto = dyn_period->segment_template->presentation_time_offset; - timescale = dyn_period->segment_template->timescale; + if (dyn_period->segment_template->timescale) timescale = dyn_period->segment_template->timescale; } if (set->segment_template) { startNum = set->segment_template->start_number; @@ -875,13 +1015,13 @@ } if (is_valid && (number>=startNum)) { //clock is init which means the segment is available, so the timeline offset must match the AST of the segment (includes seg dur) - const char *ll_val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-ll"); + const char *ll_val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-mcast-ll"); //low latency case, we are currently receiving the segment //only do this if not using segment-timeline if (!seg_timeline) { if (ll_val && !strcmp(ll_val, "yes")) { //low latency case, we are currently receiving the segment - group->dash->route_low_latency = GF_TRUE; + group->dash->mcast_low_latency = GF_TRUE; number--; } timeline_offset_ms = segdur * ( 1 + number - startNum); @@ -890,8 +1030,9 @@ timeline_offset_ms = gf_timestamp_rescale(timeline_offset_ms, timescale, 1000); } } + gf_free(szTemplate); } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH ROUTE bootstrap segment URL %s does not match template %s for rep #%d\n", val, seg_url+2, j+1)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Multicast bootstrap segment URL %s does not match template %s for rep #%d\n", val, seg_url+2, j+1)); } gf_free(seg_url); if (found) break; @@ -899,53 +1040,34 @@ if (found) break; } if (found) { - if (group->dash->is_m3u8) { - //purge segments (we assume we roughly are in the same state on all child playlists, we could keep one for safety) - for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) { - u32 j; - GF_MPD_AdaptationSet *set = gf_list_get(dyn_period->adaptation_sets, i); - for (j=0; j<gf_list_count(set->representations); j++) { - u32 to_rem; - rep = gf_list_get(set->representations, j); - if (!rep->segment_list) continue; - to_rem = found-1; - while (to_rem) { - GF_MPD_SegmentURL *surl = gf_list_pop_front(rep->segment_list->segment_URLs); - gf_mpd_segment_url_free(surl); - to_rem--; - } - } - } - } else { - //adjust so that nb_seg = current_time/segdur = (fetch-ast)/seg_dur; - // = (fetch- ( mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate) / segdur; - //hence nb_seg*seg_dur = fetch - mpd->availabilityStartTime - group->dash->utc_shift - group->dash->utc_drift_estimate - //so group->dash->utc_drift_estimate = fetch - (mpd->availabilityStartTime + nb_seg*seg_dur) + //adjust so that nb_seg = current_time/segdur = (fetch-ast)/seg_dur; + // = (fetch- ( mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate) / segdur; + //hence nb_seg*seg_dur = fetch - mpd->availabilityStartTime - group->dash->utc_shift - group->dash->utc_drift_estimate + //so group->dash->utc_drift_estimate = fetch - (mpd->availabilityStartTime + nb_seg*seg_dur) - u64 utc = mpd->availabilityStartTime + dyn_period->start + timeline_offset_ms; - group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc); - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Estimated UTC diff of ROUTE broadcast "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU" - bootstraping on segment %s\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime, val)); - } - group->dash->route_clock_state = 2; + u64 utc = mpd->availabilityStartTime + dyn_period->start + timeline_offset_ms; + group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Estimated UTC diff of Multicast broadcast "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU" - bootstraping on segment %s\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime, val)); + + if (!group->dash->utc_drift_estimate) group->dash->utc_drift_estimate=1; + group->dash->mcast_clock_state = GF_DASH_MCAST_SYNC_SOURCE; } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Failed to setup ROUTE clock from segment template with bootstrap URL %s, using NTP\n", val)); - group->dash->route_clock_state = 3; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Failed to setup Multicast clock from segment template with bootstrap URL %s, using NTP\n", val)); + group->dash->mcast_clock_state = GF_DASH_MCAST_SYNC_DEFAULT; } if (mpd->type==GF_MPD_TYPE_STATIC) { if (found) - group->dash->route_skip_segments_ms = (u32) timeline_offset_ms; + group->dash->mcast_skip_segments_ms = (u32) timeline_offset_ms; group->timeline_setup = GF_TRUE; return; } } - else if (val) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH ROUTE clock already setup - UTC diff of ROUTE broadcast "LLD" ms\n", group->dash->utc_drift_estimate)); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH No ROUTE entity on HTTP request\n")); + else if (group->dash->mcast_clock_state==GF_DASH_MCAST_SYNC_SOURCE) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Multicast clock already setup - UTC diff "LLD" ms\n", group->dash->utc_drift_estimate)); } - if (!group->dash->route_clock_state || (group->dash->route_clock_state>2)) { + if (!group->dash->mcast_clock_state || (group->dash->mcast_clock_state==GF_DASH_MCAST_SYNC_DEFAULT)) { GF_MPD_ProducerReferenceTime *pref = gf_list_get(group->adaptation_set->producer_reference_time, 0); if (pref) utc_timing = pref->utc_timing; @@ -1004,7 +1126,7 @@ } } - if ((!group->dash->route_clock_state || (group->dash->route_clock_state>2)) + if ((!group->dash->mcast_clock_state || (group->dash->mcast_clock_state>GF_DASH_MCAST_SYNC_SOURCE)) && !group->dash->ntp_forced && group->dash->estimate_utc_drift && !group->dash->utc_drift_estimate @@ -1099,8 +1221,9 @@ availabilityStartTime = current_time; current_time = 0; } + } else { + current_time -= availabilityStartTime; } - else current_time -= availabilityStartTime; if (gf_list_count(group->dash->mpd->periods)) { u64 seg_start_ms = current_time; @@ -1165,7 +1288,8 @@ group->dash->time_in_tsb = group->dash->prev_time_in_tsb = 0; timeline = NULL; - timescale=1; + timescale=1; //MPD timescale is 1 if not present + u32 ssr_parts = 0; u64 rep_pto = 0; start_number=0; rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); @@ -1198,6 +1322,7 @@ if (group->period->segment_template->start_number) start_number = group->period->segment_template->start_number; if (group->period->segment_template->availability_time_offset) ast_offset = group->period->segment_template->availability_time_offset; if (group->period->segment_template->presentation_time_offset) rep_pto = group->period->segment_template->presentation_time_offset; + ssr_parts = group->period->segment_template->nb_parts; } if (group->adaptation_set->segment_template) { if (group->adaptation_set->segment_template->segment_timeline) timeline = group->adaptation_set->segment_template->segment_timeline; @@ -1205,6 +1330,8 @@ if (group->adaptation_set->segment_template->start_number) start_number = group->adaptation_set->segment_template->start_number; if (group->adaptation_set->segment_template->availability_time_offset) ast_offset = group->adaptation_set->segment_template->availability_time_offset; if (group->adaptation_set->segment_template->presentation_time_offset) rep_pto = group->adaptation_set->segment_template->presentation_time_offset; + if (group->adaptation_set->segment_template->nb_parts) + ssr_parts = group->adaptation_set->segment_template->nb_parts; } if (rep->segment_template) { if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline; @@ -1212,6 +1339,8 @@ if (rep->segment_template->start_number) start_number = rep->segment_template->start_number; if (rep->segment_template->availability_time_offset) ast_offset = rep->segment_template->availability_time_offset; if (rep->segment_template->presentation_time_offset) rep_pto = rep->segment_template->presentation_time_offset; + if (rep->segment_template->nb_parts) + ssr_parts = rep->segment_template->nb_parts; } if (start_number==(u32) -1) start_number = 1; @@ -1234,7 +1363,7 @@ current_time_rescale = current_time; current_time_rescale *= timescale; current_time_rescale /= 1000; - //warning, MPD time in SegmentTimeline is (t-pto), so add pto to current time rescaled before comaring to t@s + //warning, MPD time in SegmentTimeline is (t-pto), so add pto to current time rescaled before comparing to t@s current_time_rescale += rep_pto; count = gf_list_count(timeline->entries); @@ -1281,7 +1410,10 @@ repeat = 1+ent->repeat_count; while (repeat) { - if ((current_time_rescale >= segtime) && (current_time_rescale < segtime + ent->duration)) { + Bool is_last = GF_FALSE; + if ((repeat==1) && (i+1==count)) is_last = GF_TRUE; + + if ((current_time_rescale >= segtime) && (is_last || (current_time_rescale < segtime + ent->duration))) { GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Found segment %d for current time "LLU" is in SegmentTimeline "LLU"-"LLU" (timecale %d - current index %d - startNumber %d)\n", seg_idx, current_time_rescale, start_segtime, segtime + ent->duration, timescale, group->download_segment_index, start_number)); group->download_segment_index = seg_idx; @@ -1294,6 +1426,18 @@ if (group->dash->utc_drift_estimate<0) { group->ast_at_init -= (timeline_duration - (segtime-start_segtime)) *1000/timescale; } + + if ((current_time_rescale > segtime + ent->duration) /* <=> is_last*/) { + group->start_playback_range = 0; + u32 diff = (u32) (current_time_rescale - segtime - ent->duration); + if (diff>ent->duration) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Last segment end time is %u sec less than current time, using last entry in timeline\n", diff/timescale)); + } + } + + if (ent->nb_parts) { + dash_ssr_adjust_group_start(group, ((Double)ent->duration) / timescale, ent->nb_parts); + } return; } segtime += ent->duration; @@ -1303,7 +1447,7 @@ segdur_in_timeline += ent->duration; } } - //check if we're ahead of time but "reasonnably" ahead (max 1 min) - otherwise consider the timing is broken + //check if we're ahead of time but "reasonably" ahead (max 1 seg) - otherwise consider the timing is broken if ((current_time_rescale + last_s_dur >= segtime) && (current_time_rescale <= segtime + 60*timescale)) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH current time "LLU" is greater than last SegmentTimeline end "LLU" by %g sec - defaulting to last entry in SegmentTimeline\n", current_time_rescale, segtime, (Double) (current_time_rescale - segtime)/timescale )); group->download_segment_index = (seg_idx > 2) ? (seg_idx-2) : 0; @@ -1333,12 +1477,12 @@ nb_seg /= group->segment_duration; shift = (u32) nb_seg; - if ((group->dash->route_clock_state == 2) && shift) { + if ((group->dash->mcast_clock_state == GF_DASH_MCAST_SYNC_SOURCE) && shift) { //shift currently points to the next segment after the one used for clock bootstrap - if (!group->dash->route_low_latency) - shift--; - //avoid querying too early the cache since segments do not usually arrive exactly on time ... - availabilityStartTime += group->dash->route_ast_shift; + if (!group->dash->mcast_low_latency) + shift--; + //avoid querying too early the cache since segments do not usually arrive exactly on time ... + availabilityStartTime += group->dash->mcast_ast_shift; } if (group->dash->initial_period_tunein || group->force_timeline_reeval) { @@ -1382,8 +1526,11 @@ time_in_seg = (Double) current_time/1000.0; time_in_seg -= group->start_playback_range; + if (ssr_parts) { + group->start_playback_range += time_in_seg; + } //if low latency, try to adjust - if (ast_offset) { + else if (ast_offset) { Double ast_diff_d; if (ast_offset>group->segment_duration) ast_offset = group->segment_duration; ast_diff_d = group->segment_duration - ast_offset; @@ -1439,6 +1586,10 @@ } } group->prev_segment_ok = GF_TRUE; + + if (ssr_parts) + dash_ssr_adjust_group_start(group, group->segment_duration, ssr_parts); + } else { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Segment duration unknown - cannot estimate current startNumber\n")); } @@ -1486,7 +1637,7 @@ GF_Err gf_dash_group_check_bandwidth(GF_DashClient *dash, u32 group_idx, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start) { s32 res; - GF_DASH_Group *group = gf_list_get(dash->groups, group_idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, group_idx); if (!group) return GF_BAD_PARAM; if (! dash->rate_adaptation_download_monitor) return GF_OK; @@ -1501,13 +1652,13 @@ } //force a call go query buffer - dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group_idx, GF_OK); + dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group->index, GF_OK); res = dash->rate_adaptation_download_monitor(dash, group, bits_per_sec, total_bytes, bytes_done, us_since_start, group->buffer_occupancy_ms, (u32) group->current_downloaded_segment_duration); if (res==-1) return GF_OK; - dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_ABORT_DOWNLOAD, group->groups_idx, GF_OK); + dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_ABORT_DOWNLOAD, group->index, GF_OK); //internal return value, switching has already been setup if (res<0) return GF_OK; @@ -1539,7 +1690,7 @@ goto update_download; } else if (group) { - group_idx = group->groups_idx; + group_idx = group->index; if (group->download_sidx_pending) { assert ( *sess); goto update_download; @@ -1549,7 +1700,7 @@ if (! *sess) { *sess = dash_io->create(dash_io, persistent_mode ? 1 : 0, url, group_idx); if (!(*sess)) { - if (dash->route_clock_state) + if (dash->mcast_clock_state) return GF_IP_NETWORK_EMPTY; GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Cannot try to download %s... out of memory ?\n", url)); return GF_OUT_OF_MEM; @@ -1559,8 +1710,8 @@ if (persistent_mode!=2) { e = dash_io->setup_from_url(dash_io, *sess, url, group_idx); if (e) { - //with ROUTE we may have 404 right away if nothing in cache yet, not an error - GF_LOG(dash->route_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("DASH Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) )); + //with multicast we may have 404 right away if nothing in cache yet, not an error + GF_LOG(dash->mcast_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("DASH Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) )); return e; } } @@ -1712,7 +1863,8 @@ u32 timescale; u64 duration; GF_MPD_SegmentTimeline *timeline = NULL; - *nb_segments = timescale = 0; + *nb_segments = 0; + timescale = 1; //MPD timescale is 1 if not present duration = 0; if (rep->segment_list || set->segment_list || period->segment_list) { @@ -1833,6 +1985,11 @@ period, set, rep, &start_time, segment_duration, scale); + //adjust start time for HLS, assuming roughly constant duration + if (group->hls_start_num && segment_duration) { + start_time += group->hls_start_num * (*segment_duration); + } + if (current_pto) { u32 pto_ts=1; if (period->segment_list && period->segment_list->presentation_time_offset) { @@ -1937,11 +2094,33 @@ return idx; } +static GF_MPD_SegmentTimelineEntry *gf_dash_get_timeline_entry(GF_MPD_SegmentTimeline *timeline, u32 segment_index) +{ +// u64 start_time = 0; + u32 seg_idx = 0; + u32 i, count, repeat; + count = gf_list_count(timeline->entries); + for (i=0; i<count; i++) { + GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i); +// if (!i || ent->start_time) start_time = ent->start_time; + + repeat = ent->repeat_count+1; + while (repeat) { + if (seg_idx == segment_index) return ent; + seg_idx++; +// start_time+=ent->duration; + repeat--; + } + } + return NULL; +} + static GF_Err gf_dash_merge_segment_timeline(GF_DASH_Group *group, GF_DashClient *dash, GF_MPD_SegmentList *old_list, GF_MPD_SegmentTemplate *old_template, GF_MPD_SegmentList *new_list, GF_MPD_SegmentTemplate *new_template, Double min_start_time) { GF_MPD_SegmentTimeline *old_timeline, *new_timeline; - u32 i, idx, timescale, nb_new_segs; + u32 i, idx, nb_new_segs; + u32 timescale = 1; //MPD timescale is 1 if not present GF_MPD_SegmentTimelineEntry *ent; old_timeline = new_timeline = NULL; @@ -1952,7 +2131,7 @@ } old_timeline = old_list->segment_timeline; new_timeline = new_list->segment_timeline; - timescale = new_list->timescale; + if (new_list->timescale) timescale = new_list->timescale; } else if (old_template && old_template->segment_timeline) { if (!new_template || !new_template->segment_timeline) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Error - cannot update playlist: segment timeline not present in new MPD segmentTemplate\n")); @@ -1960,7 +2139,7 @@ } old_timeline = old_template->segment_timeline; new_timeline = new_template->segment_timeline; - timescale = new_template->timescale; + if (new_template->timescale) timescale = new_template->timescale; } if (!old_timeline && !new_timeline) return GF_OK; @@ -2212,8 +2391,16 @@ static void gf_dash_mark_group_done(GF_DASH_Group *group) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d group is done\n", 1+group->groups_idx )); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d group is done\n", 1+group->index )); group->done = GF_TRUE; + if (group->xas_groups || group->xas_base) { + if (group->xas_base) group = group->xas_base; + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + agr->done = GF_TRUE; + } + } } @@ -2255,7 +2442,7 @@ local_url = dash->getter.get_cache_name(&dash->getter); } name = dash_strip_base_url(xlink_copy, dash->base_url); - dash->dash_io->manifest_updated(dash->dash_io, name, local_url, group->groups_idx); + dash->dash_io->manifest_updated(dash->dash_io, name, local_url, group->index); gf_free(xlink_copy); return e; } @@ -2364,7 +2551,7 @@ GF_DOMParser *mpd_parser; u8 signatureGF_SHA1_DIGEST_SIZE; GF_MPD_Period *period=NULL, *new_period=NULL; - const char *local_url; + const char *local_url = NULL; char mime128; char * purl; Double timeline_start_time=0; @@ -2409,16 +2596,17 @@ /*if no absolute URL, use <Location> to get MPD in case baseURL is relative...*/ if (!strstr(dash->base_url, "://")) { - gf_free(dash->base_url); - dash->base_url = gf_strdup(purl); + gf_dash_set_base_url(dash, purl); } fetch_only = 1; } } else { +#if 0 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload); - if (local_url) { + if (local_url && !dash->manifest_pending) { gf_file_delete(local_url); } +#endif //use the redirected url stored in base URL - DO NOT USE the redirected URL of the session since //the session may have been reused for period XLINK dowload. purl = gf_strdup( dash->base_url ); @@ -2434,6 +2622,9 @@ } } + if (dash->query_part && purl && !strrchr(purl, '?')) { + gf_dynstrcat(&purl, dash->query_part, NULL); + } GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Updating Playlist %s...\n", purl ? purl : local_url)); if (purl) { const char *mime_type; @@ -2448,7 +2639,7 @@ dash->manifest_pending = 0; if (!dash->in_error) { - if (e==GF_URL_ERROR) { + if ((e==GF_URL_ERROR) || (e==GF_REMOTE_SERVICE_ERROR)) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Error - cannot update manifest: %s, aborting\n", gf_error_to_string(e))); dash->in_error = GF_TRUE; } else { @@ -2487,9 +2678,7 @@ /*if relocated, reassign MPD base URL*/ if (strcmp(purl, dash->base_url)) { - gf_free(dash->base_url); - dash->base_url = gf_strdup(purl); - + gf_dash_set_base_url(dash, purl); } purl = NULL; } @@ -3119,6 +3308,7 @@ //it is recomputed below for low-latency cases if (group->download_segment_index) { group->download_segment_index--; + group->hls_start_num++; } } } else { @@ -3192,7 +3382,7 @@ if (!found) { //use group last modification time u32 timer = gf_sys_clock() - group->last_mpd_change_time; - if (!group->segment_duration || (timer < group->segment_duration * 2000) ) { + if (!group->segment_duration || (timer < group->segment_duration * 1000 * SEGLIST_TIMEOUT_SEG) ) { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Cannot find segment for given HLS SN %d - forcing manifest update\n", group->hls_next_seq_num)); HLS_MIN_RELOAD_TIME(dash) } else { @@ -3455,9 +3645,14 @@ if (!group->timeline_setup) { gf_dash_group_timeline_setup(mpd, group, 0); - //we must wait for ROUTE clock to initialize, even if first period is static remote (we need to know when to tune) - if (group->dash->route_clock_state==1) + //we must wait for multicast clock to initialize, even if first period is static remote (we need to know when to tune) + if (group->dash->mcast_clock_state == GF_DASH_MCAST_INIT) { + const char *hdr = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-mcast-over"); + if (hdr && !strcmp(hdr, "yes")) { + gf_dash_mark_group_done(group); + } return GF_IP_NETWORK_EMPTY; + } if (group->dash->reinit_period_index || group->dash->pending_utc_session) return GF_IP_NETWORK_EMPTY; @@ -3466,32 +3661,52 @@ item_index = group->download_segment_index; } + s32 subseg_index = -1; + if (group->nb_parts) { + subseg_index = group->part_idx; + } gf_mpd_resolve_segment_duration(rep, set, period, segment_duration, ×cale, NULL, NULL); *segment_duration = (resolve_type==GF_MPD_RESOLVE_URL_MEDIA) ? (u32) ((Double) ((*segment_duration) * 1000.0) / timescale) : 0; - e = gf_mpd_resolve_url(mpd, rep, set, period, mpd_url, group->current_base_url_idx, resolve_type, item_index, group->nb_segments_purged, out_url, out_range_start, out_range_end, segment_duration, is_in_base_url, out_key_url, out_key_iv, out_start_number); + e = gf_mpd_resolve_url(mpd, rep, set, period, mpd_url, group->current_base_url_idx, resolve_type, item_index, group->nb_segments_purged, out_url, out_range_start, out_range_end, segment_duration, is_in_base_url, out_key_url, out_key_iv, out_start_number, subseg_index); - if (e == GF_NON_COMPLIANT_BITSTREAM) { -// group->selection = GF_DASH_GROUP_NOT_SELECTABLE; + if (!group->dash->is_m3u8 && (e == GF_NON_COMPLIANT_BITSTREAM)) { + group->selection = GF_DASH_GROUP_NOT_SELECTABLE; + gf_dash_mark_group_done(group); } if (!*out_url) { return e; } + //hacky, HLS does not carry startNumber info which can be needed when forwarding files - assume 1 + if (group->dash->is_m3u8 && out_start_number && ! *out_start_number) { + if (!group->hls_start_num) { + char *template = gf_dash_group_get_template(group->dash, gf_list_find(group->dash->groups, group), NULL, NULL, NULL); + if (template) gf_free(template); + } + *out_start_number = group->hls_start_num ? group->hls_start_num : 1; + } - if (*out_url && data_url_process && !strncmp(*out_url, "data:", 5)) { + if (*out_url && data_url_process && !strncmp(*out_url, "data:", 5) && (resolve_type==GF_MPD_RESOLVE_URL_INIT)) { char *sep; sep = strstr(*out_url, ";base64,"); if (sep) { - GF_Blob *blob; - u32 len; - sep+=8; - len = (u32)strlen(sep) + 1; - GF_SAFEALLOC(blob, GF_Blob); - if (!blob) return GF_OUT_OF_MEM; - - blob->data = (char *)gf_malloc(len); - blob->size = gf_base64_decode(sep, len, blob->data, len); - sprintf(*out_url, "gmem://%p", blob); + const char *scheme = "gpac://"; + if (mpd_url && !strnicmp(mpd_url, "http://", 7)) scheme="http://"; + else if (mpd_url && !strnicmp(mpd_url, "https://", 8)) scheme="https://"; + + //first resolution of init, create and register blob + if (!rep->playback.init_segment.data) { + u32 len; + sep+=8; + len = (u32)strlen(sep) + 1; + rep->playback.init_segment.data = (char *)gf_malloc(len); + rep->playback.init_segment.size = gf_base64_decode(sep, len, rep->playback.init_segment.data, len); + char *b_url = gf_blob_register(&rep->playback.init_segment); + sprintf(*out_url, "%s%s", scheme, b_url); + gf_free(b_url); + } else { + sprintf(*out_url, "%sgmem://%p", scheme, &rep->playback.init_segment); + } *data_url_process = GF_TRUE; } else { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("data scheme with encoding different from base64 not supported\n")); @@ -3620,6 +3835,11 @@ if (e) { if (group->dash->dash_state != GF_DASH_STATE_RUNNING) { + if (e == GF_URL_REMOVED) { + rep->playback.disabled = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH XLINK %s does not exist, disabling representation\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : "")); + return; + } group->dash->force_period_reload = 1; GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Could not reslove XLINK %s of initial rep, will retry\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : "", gf_error_to_string(e) )); } else { @@ -3698,7 +3918,7 @@ group->hls_next_seq_num = next_media_seq; } else { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH next media segment %d not found in new variant stream (min %d - max %d), aborting\n", next_media_seq, rep->m3u8_media_seq_min, rep->m3u8_media_seq_max)); - group->done = GF_TRUE; + gf_dash_mark_group_done(group); } } else { if (is_dynamic) { @@ -3706,7 +3926,6 @@ } } } - //switching to a rep for which we didn't solve the init segment yet if (!rep->playback.cached_init_segment_url) { GF_Err e; @@ -3716,17 +3935,31 @@ e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &r_base_init_url, &r_start, &r_end, &r_dur, NULL, &rep->playback.key_url, &rep->playback.key_IV, &rep->playback.owned_gmem, NULL); - group->timeline_setup = timeline_setup; + //for broadcast profiles, do not reset + if (group->dash->mcast_clock_state!=GF_DASH_MCAST_SYNC_SOURCE) + group->timeline_setup = timeline_setup; + if (!e && r_base_init_url) { rep->playback.cached_init_segment_url = r_base_init_url; rep->playback.init_start_range = r_start; rep->playback.init_end_range = r_end; rep->playback.init_seg_name_start = dash_strip_base_url(r_base_init_url, group->dash->base_url); - } else if (e) { + } else if (e && (e!=GF_IP_NETWORK_EMPTY)) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) )); rep->playback.disabled = 1; } } + //broadcast clock not yet setup, do it + if ((group->dash->mcast_clock_state == GF_DASH_MCAST_INIT) && !group->timeline_setup) { + gf_dash_group_timeline_setup(group->dash->mpd, group, 0); + if (!group->timeline_setup) { + group->pending_rep = rep; + group->pending_prev_active_rep_index = prev_active_rep_index; + group->dash->has_pending_groups = GF_TRUE; + return; + } + } + group->pending_rep = NULL; group->m3u8_start_media_seq = rep->m3u8_media_seq_min; if (rep->m3u8_low_latency) @@ -3777,13 +4010,13 @@ if (framerate->den) den = framerate->den; } - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d changed quality to bitrate %d kbps - Width %d Height %d FPS %d/%d (playback speed %g)\n", 1+group->groups_idx, rep->bandwidth/1000, width, height, num, den, group->dash->speed)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d changed quality to bitrate %d kbps - Width %d Height %d FPS %d/%d (playback speed %g)\n", 1+group->index, rep->bandwidth/1000, width, height, num, den, group->dash->speed)); } else if (samplerate) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d changed quality to bitrate %d kbps - sample rate %u (playback speed %g)\n", 1+group->groups_idx, rep->bandwidth/1000, samplerate, group->dash->speed)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d changed quality to bitrate %d kbps - sample rate %u (playback speed %g)\n", 1+group->index, rep->bandwidth/1000, samplerate, group->dash->speed)); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d changed quality to bitrate %d kbps (playback speed %g)\n", 1+group->groups_idx, rep->bandwidth/1000, group->dash->speed)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d changed quality to bitrate %d kbps (playback speed %g)\n", 1+group->index, rep->bandwidth/1000, group->dash->speed)); } #endif @@ -3799,8 +4032,28 @@ if (timeshift == -1) timeshift = (s32) group->dash->mpd->time_shift_buffer_depth; group->time_shift_buffer_depth = (u32) timeshift; + group->nb_parts = 0; + group->part_idx = 0; + if (rep->playback.use_ssr) { + if (set->segment_template) { + if (set->segment_template->segment_timeline) { + GF_MPD_SegmentTimelineEntry *e = gf_dash_get_timeline_entry(set->segment_template->segment_timeline, group->download_segment_index); + if (e) group->nb_parts = e->nb_parts; + } else { + group->nb_parts = set->segment_template->nb_parts; + } + } + if (rep->playback.use_ssr && rep->segment_template) { + if (rep->segment_template->segment_timeline) { + GF_MPD_SegmentTimelineEntry *e = gf_dash_get_timeline_entry(set->segment_template->segment_timeline, group->download_segment_index); + if (e) group->nb_parts = e->nb_parts; + } else { + group->nb_parts = rep->segment_template->nb_parts; + } + } + } - group->dash->dash_io->on_dash_event(group->dash->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, group->groups_idx, GF_OK); + group->dash->dash_io->on_dash_event(group->dash->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, group->index, GF_OK); } static void gf_dash_switch_group_representation(GF_DashClient *mpd, GF_DASH_Group *group) @@ -3809,12 +4062,36 @@ GF_MPD_Representation *rep_sel = NULL; GF_MPD_Representation *min_rep_sel = NULL; Bool min_bandwidth_selected = GF_FALSE; + Bool force_switch = GF_FALSE; bandwidth = 0; min_bandwidth = (u32) -1; GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Checking representations between %d and %d kbps\n", group->min_bitrate/1000, group->max_bitrate/1000)); if (group->force_representation_idx_plus_one) { + if (group->xas_base || group->xas_groups) { + GF_DASH_Group *target, *base = group->xas_base ? group->xas_base : group; + if (group->xas_force_switch==0) target = base; + else target = gf_list_get(base->xas_groups, group->xas_force_switch-1); + + base->xas_selected = group->xas_force_switch; + group->xas_force_switch = 0; + group->force_switch_bandwidth = 0; + if (group != target) { + group->selection = GF_DASH_GROUP_NOT_SELECTED; + target->selection = GF_DASH_GROUP_SELECTED; + target->force_representation_idx_plus_one = group->force_representation_idx_plus_one; + group->force_representation_idx_plus_one = 0; + + target->bytes_per_sec = group->bytes_per_sec; + target->download_segment_index = group->download_segment_index; + target->nb_segments_purged = group->nb_segments_purged; + target->prev_active_rep_index = (u32) -1; + + group = target; + force_switch = GF_TRUE; + } + } rep_sel = gf_list_get(group->adaptation_set->representations, group->force_representation_idx_plus_one - 1); group->force_representation_idx_plus_one = 0; } @@ -3850,7 +4127,7 @@ group->max_bitrate = 0; group->min_bitrate = (u32) -1; - if (i != group->active_rep_index) { + if (force_switch || (i != group->active_rep_index)) { if (min_bandwidth_selected) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH No representation found with bandwidth below %d kbps - using representation @ %d kbps\n", group->max_bitrate/1000, rep_sel->bandwidth/1000)); } @@ -3944,7 +4221,7 @@ group->nb_segments_since_switch ++; group->prev_segment_ok = GF_TRUE; - dash->route_last_retune = 0; + dash->mcast_last_retune = 0; if (group->time_at_first_failure) { #ifndef GPAC_DISABLE_LOG @@ -4002,7 +4279,7 @@ u32 i, buffer_ms = 0; Double bitrate, time_sec; //force a call go query buffer - dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group->groups_idx, GF_OK); + dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group->index, GF_OK); buffer_ms = group->buffer_occupancy_ms; for (i=0; i < group->nb_cached_segments; i++) { buffer_ms += group->cachedi.duration; @@ -4025,7 +4302,7 @@ time_sec /= 1000000; } - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d got %s stats: %d bytes in %.03g sec at %d kbps - dur %g sec - bitrate: %d (avg %d) kbps - buffer %d ms\n", 1+group->groups_idx, url, group->total_size, time_sec, 8*bytes_per_sec/1000, group->current_downloaded_segment_duration/1000.0, (u32) bitrate, rep->bandwidth/1000, buffer_ms)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d got %s stats: %d bytes in %.03g sec at %d kbps - dur %g sec - bitrate: %d (avg %d) kbps - buffer %d ms\n", 1+group->index, url, group->total_size, time_sec, 8*bytes_per_sec/1000, group->current_downloaded_segment_duration/1000.0, (u32) bitrate, rep->bandwidth/1000, buffer_ms)); } #endif } @@ -4042,13 +4319,13 @@ if (group->min_bandwidth_selected) { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Downloading from set #%d at rate %d kbps but media bitrate is" - " %d kbps - no lower bitrate available ...\n", 1+group->groups_idx, bits_per_sec/1000, group->active_bitrate/1000 )); + " %d kbps - no lower bitrate available ...\n", 1+group->index, bits_per_sec/1000, group->active_bitrate/1000 )); return -1; } //we start checking after 100ms if (us_since_start < 100000) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Downloading from set #%d at rate %d kbps (media bitrate %d kbps) but 100ms only elapsed, waiting\n", 1+group->groups_idx, bits_per_sec/1000, group->active_bitrate/1000 )); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Downloading from set #%d at rate %d kbps (media bitrate %d kbps) but 100ms only elapsed, waiting\n", 1+group->index, bits_per_sec/1000, group->active_bitrate/1000 )); return -1; } @@ -4061,11 +4338,11 @@ //we have enough cache data to go until end of this download, perform rate switching at next segment if (time_until_end < buffer_dur_ms) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Downloading from set #%ds at rate %d kbps (media bitrate %d kbps) - %d ms until end of download and %d ms in buffer, not aborting\n", 1+group->groups_idx, bits_per_sec/1000, group->active_bitrate/1000, time_until_end, buffer_dur_ms)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Downloading from set #%ds at rate %d kbps (media bitrate %d kbps) - %d ms until end of download and %d ms in buffer, not aborting\n", 1+group->index, bits_per_sec/1000, group->active_bitrate/1000, time_until_end, buffer_dur_ms)); return -1; } - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Downloading from set #%d at rate %d kbps but media bitrate is %d kbps - %d ms until end of download but %d ms in buffer - aborting segment download\n", 1+group->groups_idx, bits_per_sec/1000, group->active_bitrate/1000, buffer_dur_ms)); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Downloading from set #%d at rate %d kbps but media bitrate is %d kbps - %d ms until end of download but %d ms in buffer - aborting segment download\n", 1+group->index, bits_per_sec/1000, group->active_bitrate/1000, buffer_dur_ms)); //in live we just abort current download and go to next. In onDemand, we may want to rebuffer default_switch_mode = (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_FALSE : GF_TRUE; @@ -4127,6 +4404,9 @@ if (arep->playback.disabled) { continue; } + if ((group->has_ssr==2) && arep->playback.use_ssr) { + continue; + } if (arep->playback.prev_max_available_speed && (speed > arep->playback.prev_max_available_speed)) { continue; } @@ -4203,7 +4483,7 @@ } if (!new_rep || (new_rep == rep)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d no better match for requested bandwidth %d - not switching (AS bitrate %d)!\n", 1 + group->groups_idx, dl_rate, rep->bandwidth)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d no better match for requested bandwidth %d - not switching (AS bitrate %d)!\n", 1 + group->index, dl_rate, rep->bandwidth)); do_switch = GF_FALSE; } @@ -4246,6 +4526,12 @@ dl_rate = group->min_representation_bitrate; } + if (group->ssr_shadow_idx_plus_one) { + new_index = group->ssr_shadow_idx_plus_one-1; + group->ssr_shadow_idx_plus_one = 0; + return new_index; + } + //we have buffered output if (group->buffer_max_ms) { u32 buf_high_threshold, buf_low_threshold; @@ -4275,17 +4561,17 @@ dl_rate = (rep->bandwidth > 10) ? rep->bandwidth - 10 : 1; } go_up_bitrate = GF_FALSE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running low, switching down, target rate %d\n", 1 + group->groups_idx, rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running low, switching down, target rate %d\n", 1 + group->index, rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate)); } //switch up if above max threshold and buffer refill is fast enough else if ((occ>0) && (group->buffer_occupancy_ms > buf_high_threshold)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running high, will try to switch up, target rate %d\n", 1 + group->groups_idx, rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running high, will try to switch up, target rate %d\n", 1 + group->index, rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate)); go_up_bitrate = GF_TRUE; } //don't do anything in the middle range of the buffer or if refill not fast enough else { do_switch = GF_FALSE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d bitrate %d bps buffer max %d current %d refill since last %d - steady\n", 1 + group->groups_idx, rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d bitrate %d bps buffer max %d current %d refill since last %d - steady\n", 1 + group->index, rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ)); } } @@ -4384,24 +4670,24 @@ return -1; } - if (rate_prev == rate_max) { - rate_plus = rate_max; - } else { + if (rate_prev == rate_max) { + rate_plus = rate_max; + } else { rate_plus = get_min_rate_above(group->adaptation_set->representations, rate_prev, NULL); - } + } - if (rate_prev == rate_min) { - rate_minus = rate_min; - } else { + if (rate_prev == rate_min) { + rate_minus = rate_min; + } else { rate_minus = get_max_rate_below(group->adaptation_set->representations, rate_prev, NULL); - } + } - /* - * the size of the reservoir is 37.5% of the buffer size, but at least = 1 chunk duration) + /* + * the size of the reservoir is 37.5% of the buffer size, but at least = 1 chunk duration) * the size of the upper reservoir is 10% of the buffer size - * the size of cushion is between 37.5% and 90% of the buffer size + * the size of cushion is between 37.5% and 90% of the buffer size * the rate map is piece-wise - */ + */ if (buf_max <= segment_duration_ms) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH BBA-0: cannot initialize BBA-0 given the buffer size (%d) and segment duration (%d)\n", group->buffer_max_ms, group->segment_duration*1000)); return -1; @@ -4600,11 +4886,41 @@ Double max_available_speed; u32 dl_rate; u32 k; + Bool force_switch_set=GF_FALSE; s32 new_index, old_index; GF_DASH_Group *base_group; GF_MPD_Representation *rep; Bool force_lower_complexity; + //in middle of a sequence segment, cannot adapt + if (group->nb_parts) { + if (group->part_idx) return; + if ((group->ssr_switch && group->xas_groups) || (group->xas_base && group->xas_base->ssr_switch)) { + GF_DASH_Group *switch_to = NULL; + if (!group->xas_base) { + group->ssr_switch = GF_FALSE; + group->xas_selected = 1; + switch_to = gf_list_get(group->xas_groups, 0); + } else { + group->xas_base->xas_selected = 0; + group->xas_base->ssr_switch = GF_FALSE; + switch_to = group->xas_base; + } + switch_to->bytes_per_sec = group->bytes_per_sec; + switch_to->download_segment_index = group->download_segment_index; + switch_to->nb_segments_purged = group->nb_segments_purged; + //force BS switch + switch_to->prev_active_rep_index = (u32) -1; + group->selection = GF_DASH_GROUP_NOT_SELECTED; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d leaving SSR tune-in, resuming on AS%d\n", 1+gf_list_find(dash->groups, group), 1+gf_list_find(dash->groups, switch_to) )); + group = switch_to; + force_switch_set = GF_TRUE; + } else if (group->ssr_switch) { + force_switch_set = GF_TRUE; + group->ssr_switch = GF_FALSE; + } + } + /* Don't do adaptation if configured switching to happen systematically (debug) */ if (dash->auto_switch_count) { return; @@ -4662,7 +4978,7 @@ group->codec_reset = 0; /* the DASH Client asks the player for its buffer level (uses a function pointer to avoid depenencies on the player code, to reuse the DASH client in different situations)*/ - dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group->groups_idx, GF_OK); + dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group->index, GF_OK); /* If the playback for the current representation was waiting for a codec reset and it happened, indicate that this representation does not need a reset anymore */ @@ -4675,11 +4991,11 @@ if (group->base_rep_index_plus_one) { group->active_rep_index = group->max_complementary_rep_index; } - if (group->dash->route_clock_state) { + if (group->dash->mcast_clock_state) { rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); if (rep->playback.broadcast_flag && (dl_rate < rep->bandwidth)) { dl_rate = rep->bandwidth+1; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d representation %d segment sent over broadcast, forcing bandwidth to %d\n", 1 + group->groups_idx, group->active_rep_index, dl_rate)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d representation %d segment sent over broadcast, forcing bandwidth to %d\n", 1 + group->index, group->active_rep_index, dl_rate)); } } @@ -4692,7 +5008,7 @@ - the information of the current representation (rep) - the download_rate dl_rate (computed on the previously downloaded segment, and adjusted to the playback speed), - the buffer levels: - - current: group->buffer_occupancy_ms, + - current: group->buffer_occupancy_ms, - previous: group->buffer_occupancy_at_last_seg - max: group->buffer_max_ms, - the playback speed, @@ -4731,15 +5047,18 @@ return; } group->disabled = GF_FALSE; - if (new_index != group->active_rep_index) { + if (force_switch_set || (new_index != group->active_rep_index)) { GF_MPD_Representation *new_rep = gf_list_get(group->adaptation_set->representations, (u32)new_index); if (!new_rep) { group->active_rep_index = old_index; GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Cannot find new representation index %d, using previous one\n", new_index)); return; } + if (force_switch_set && (new_index != group->active_rep_index)) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH AS#%d leaving SSR tune-in, resuming on representation index %d\n", 1+group->index, new_index)); + } - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d switching after playing %d segments, from rep %d to rep %d\n", 1 + group->groups_idx, + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH AS#%d switching after playing %d segments, from rep %d to rep %d\n", 1 + group->index, group->nb_segments_since_switch, group->active_rep_index, new_index)); group->nb_segments_since_switch = 0; @@ -4847,15 +5166,25 @@ } else if (dash->is_m3u8) { char *tmp_url=NULL; u64 dur, sr, er; - u32 startnum; - e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &tmp_url, &sr, &er, &dur, NULL, &key_url, &key_iv, NULL, &startnum); + e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &tmp_url, &sr, &er, &dur, NULL, &key_url, &key_iv, NULL, &start_number); if (tmp_url) gf_free(tmp_url); } base_url = base_url_orig; base_init_url = gf_dash_get_fileio_url(base_url, base_init_url); + if (!base_init_url) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Failed to resolve init segment URL\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (!base_init_url) { + return GF_IO_ERR; + } - if (nb_segment_read) group->init_segment_is_media = GF_TRUE; + if (nb_segment_read) { + group->init_segment_is_media = GF_TRUE; + group->init_segment_start_number = start_number; + } if (!strstr(base_init_url, "://") || !strnicmp(base_init_url, "file://", 7) || !strnicmp(base_init_url, "gmem://", 7) || !strnicmp(base_init_url, "views://", 8) || !strnicmp(base_init_url, "mosaic://", 9) @@ -4944,14 +5273,14 @@ group->min_bitrate = (u32)-1; - if (dash->route_clock_state && !group->period->origin_base_url) { + if (dash->mcast_clock_state && !group->period->origin_base_url) { GF_DASHFileIOSession sess = NULL; /*check the init segment has been received (eg is in the cache)*/ e = gf_dash_download_resource(dash, &sess, base_init_url, start_range, end_range, 1, NULL); dash->dash_io->del(dash->dash_io, sess); if (e!=GF_OK) { - //ROUTE + segment format not using init segment, we must wait for the segment to be available + //multicast + segment format not using init segment, we must wait for the segment to be available //if not available after segment duration, check next segment if (group->no_init_seg) { u32 ck = gf_sys_clock(); @@ -4963,16 +5292,21 @@ group->download_segment_index++; } } - //if init seg failed at tune-in, re-estimate route clock - this ensures we are always bootstraping on the last correct state + //if init seg failed at tune-in, re-estimate clock - this ensures we are always bootstraping on the last correct state //otherwise we could init clock at segN in low latency but tune-in at segN+1, hence having one extra segment delay else if (dash->initial_period_tunein) { group->timeline_setup = GF_FALSE; group->force_timeline_reeval = GF_TRUE; dash->utc_drift_estimate = 0; - dash->route_clock_state = 1; - dash->route_low_latency = 0; + dash->mcast_low_latency = 0; } gf_free(base_init_url); + if (e==GF_IP_NETWORK_EMPTY) { + const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-mcast-over"); + if (hdr && !strcmp(hdr, "yes")) { + gf_dash_mark_group_done(group); + } + } return e; } } @@ -5117,6 +5451,7 @@ gf_dash_group_reset(dash, group); gf_list_del(group->groups_depending_on); + gf_list_del(group->xas_groups); gf_free(group->cached); if (group->service_mime) gf_free(group->service_mime); @@ -5192,7 +5527,7 @@ rep = gf_dash_find_rep(dash, rep->dependency_id, &group); if (sep) sep0 = ' '; } - return group ? group->groups_idx : idx; + return group ? group->index : idx; } GF_EXPORT @@ -5200,7 +5535,7 @@ { GF_DASH_Group *group = gf_list_get(dash->groups, idx); if (!group) return -1; - return group->depend_on_group ? group->depend_on_group->groups_idx : -1; + return group->depend_on_group ? group->depend_on_group->index : -1; } GF_EXPORT @@ -5219,7 +5554,7 @@ if (!group || !group->groups_depending_on) return -1; group_depending_on = gf_list_get(group->groups_depending_on, group_depending_on_dep_idx); if (!group_depending_on) return -1; - return group_depending_on->groups_idx; + return group_depending_on->index; } GF_EXPORT @@ -5227,8 +5562,8 @@ { GF_DASH_Group *group = gf_list_get(dash->groups, idx); if (!group) return -1; - if (group->groups_depending_on) return group->groups_idx; - if (group->depend_on_group) return group->depend_on_group->groups_idx; + if (group->groups_depending_on) return group->index; + if (group->depend_on_group) return group->depend_on_group->index; return -1; } @@ -5279,6 +5614,15 @@ group->bitstream_switching = (set->bitstream_switching || period->bitstream_switching) ? GF_TRUE : GF_FALSE; group->last_mpd_change_time = gf_sys_clock(); + + for (j=0; j<gf_list_count(group->adaptation_set->essential_properties); j++) { + GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->essential_properties, j); + if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:ssr:2023") ) { + group->has_ssr = 1; + break; + } + } + seg_dur = 0; nb_dependent_rep = 0; u32 first_w=0, first_h=0; @@ -5361,10 +5705,32 @@ } } } + if (group->has_ssr==1) { + rep->playback.use_ssr = 1; + if (group->adaptation_set->segment_template) { + rep->playback.use_ssr = group->adaptation_set->segment_template->nb_parts; + if (group->adaptation_set->segment_template->segment_timeline) { + GF_MPD_SegmentTimelineEntry *e = gf_list_get(group->adaptation_set->segment_template->segment_timeline->entries, 0); + rep->playback.use_ssr = e->nb_parts; + } + } + } for (k=0; k<gf_list_count(rep->essential_properties); k++) { GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->essential_properties, k); + if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:ssr:2023")) { + rep->playback.use_ssr = 1; + if (rep->segment_template) { + rep->playback.use_ssr = rep->segment_template->nb_parts; + if (rep->segment_template->segment_timeline) { + GF_MPD_SegmentTimelineEntry *e = gf_list_get(rep->segment_template->segment_timeline->entries, 0); + rep->playback.use_ssr = e->nb_parts; + } + } + continue; + } + //we don't know any defined scheme for now if (! strstr(mpd_desc->scheme_id_uri, "gpac") ) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Representation with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri)); @@ -5423,6 +5789,12 @@ if (set->bitstream_switching && (set->starts_with_sap==3) && strstr(rep->codecs, "vvi")) rep->playback.vvc_rpr_switch = GF_TRUE; + + if (!rep->playback.use_ssr) { + group->has_ssr = 0; + } else if (!group->has_ssr) { + group->has_ssr = 2; + } } if (!seg_dur && !dash->is_m3u8) { @@ -5448,7 +5820,7 @@ gf_free(group); return GF_OUT_OF_MEM; } - group->groups_idx = gf_list_count(dash->groups); + group->index = gf_list_count(dash->groups); e = gf_list_add(dash->groups, group); if (e) { gf_free(group->cached); @@ -5457,7 +5829,6 @@ } } - count = gf_list_count(dash->groups); for (i=0; i<count; i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); @@ -5494,6 +5865,53 @@ } } } + //setup cross-AS switching + if ((dash->cross_as_mode==GF_DASH_XAS_NONE) || dash->split_adaptation_set) + return GF_OK; + + GF_List *groups_merged = gf_list_new(); + for (i=0; i<count; i++) { + GF_DASH_Group *group = gf_list_get(dash->groups, i); + if (group->depend_on_group) continue; + if (group->groups_depending_on) continue; + + u32 idx; + GF_MPD_Descriptor *switch_desc = gf_mpd_get_descriptor(group->adaptation_set->supplemental_properties, "urn:mpeg:dash:adaptation-set-switching:2016"); + if (!switch_desc) switch_desc = gf_mpd_get_descriptor(group->adaptation_set->essential_properties, "urn:mpeg:dash:adaptation-set-switching:2016"); + if (!switch_desc) continue; + char *att = switch_desc->value; + while (att) { + char *sep = strchr(att, ','); + if (sep) sep0 = 0; + u32 asid = atoi(att); + GF_DASH_Group *target = NULL; + for (idx=0; idx<count; idx++) { + target = gf_list_get(dash->groups, idx); + if ((target != group) + && (gf_list_find(groups_merged, target)<0) + && (target->adaptation_set->id == asid) + && (target->adaptation_set->segment_alignment == group->adaptation_set->segment_alignment) + && (target->adaptation_set->subsegment_alignment == group->adaptation_set->subsegment_alignment) + ) break; + + target = NULL; + } + + if (target) { + gf_list_add(groups_merged, group); + if (!group->xas_groups) group->xas_groups = gf_list_new(); + gf_list_add(group->xas_groups, target); + target->xas_base = group; + //override index + target->index = group->index; + } + + if (!sep) break; + sep0 = ','; + att = sep+1; + } + } + gf_list_del(groups_merged); return GF_OK; } @@ -5573,7 +5991,7 @@ if (e) return e; bs = gf_bs_new(mem_address, size, GF_BITSTREAM_READ); - gf_blob_release(cache_name); + gf_blob_release(cache_name); } else { f = gf_fopen(cache_name, "rb"); if (!f) return GF_IO_ERR; @@ -5620,7 +6038,7 @@ mem_address += offset; *box_size = GF_4CC(mem_address0, mem_address1, mem_address2, mem_address3); *box_type = GF_4CC(mem_address4, mem_address5, mem_address6, mem_address7); - gf_blob_release(cache_name); + gf_blob_release(cache_name); } else { unsigned char data4; FILE *f = gf_fopen(cache_name, "rb"); @@ -5863,7 +6281,7 @@ rep->segment_list->initialization_segment->sourceURL = gf_blob_register(&rep->playback.init_segment); rep->segment_list->initialization_segment->is_resolved = GF_TRUE; - gf_blob_release(cache_name); + gf_blob_release(cache_name); } else { FILE *t = gf_fopen(cache_name, "rb"); if (t) { @@ -5945,7 +6363,7 @@ u32 nb_inserted = 0; period = gf_list_get(period_list, period_idx); - if (!period->xlink_href || (dash->route_clock_state==1)) { + if (!period->xlink_href || (dash->mcast_clock_state == GF_DASH_MCAST_INIT)) { return; } start = period->start; @@ -6275,7 +6693,7 @@ retry --; } period = gf_list_get(dash->mpd->periods, dash->active_period_index); - if (period->xlink_href && (dash->route_clock_state!=1) ) { + if (period->xlink_href && (dash->mcast_clock_state!=GF_DASH_MCAST_INIT) ) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Too many xlink indirections on the same period - not supported\n")); return GF_NOT_SUPPORTED; } @@ -6306,6 +6724,7 @@ Bool cp_supported = GF_FALSE; GF_DASH_Group *group = gf_list_get(dash->groups, group_i); Bool active_rep_found; + s32 ssr_rep_idx = -1; active_rep = 0; nb_rep = gf_list_count(group->adaptation_set->representations); @@ -6389,11 +6808,15 @@ } else if (!strcmp(mpd_desc->scheme_id_uri, "http://dashif.org/guidelines/trickmode") ) { continue; + } else if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:ssr:2023") ) { + group->has_ssr = 1; + } else if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:adaptation-set-switching:2016") ) { + } else if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:mpegB:cicp:ColourPrimaries") ) { + } else if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:mpegB:cicp:TransferCharacteristics") ) { + } else if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:mpegB:cicp:MatrixCoefficients") ) { } else { - //we don't know any defined scheme for now - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH AdaptationSet with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri)); - disabled = 1; - break; + //we still load this as we could be use for anything but playback - we let the client decide + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH AdaptationSet with unrecognized EssentialProperty %s\n", mpd_desc->scheme_id_uri)); } } if (disabled) { @@ -6437,6 +6860,7 @@ if (h>group->srd_desc->srd_fh) group->srd_desc->srd_fh = h; } + } else if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:adaptation-set-switching:2016") ) { } } @@ -6479,6 +6903,7 @@ GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i); if (rep->playback.disabled) continue; + if (!active_rep_found) { active_rep = rep_i; active_rep_found = GF_TRUE; @@ -6531,8 +6956,15 @@ } } } - //move to highest rate if ROUTE session and rep is not a remote one (baseURL set) - if (dash->route_clock_state && (first_select_mode==GF_DASH_SELECT_BANDWIDTH_LOWEST) && !gf_list_count(rep->base_URLs)) + + if (rep->playback.use_ssr) { + ssr_rep_idx = rep_i; + continue; + } + + //move to highest rate if multicast session and rep is not a remote one (baseURL set) + //this might not be true in DVB-MABR gateway + if (dash->mcast_clock_state && (first_select_mode==GF_DASH_SELECT_BANDWIDTH_LOWEST) && !gf_list_count(rep->base_URLs)) first_select_mode = GF_DASH_SELECT_BANDWIDTH_HIGHEST; switch (first_select_mode) { @@ -6563,6 +6995,7 @@ break; } } + nb_rep_ok=0; for (rep_i = 0; rep_i < nb_rep; rep_i++) { GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i); if (!rep->playback.disabled) @@ -6574,6 +7007,13 @@ group->selection = GF_DASH_GROUP_NOT_SELECTABLE; continue; } + if (ssr_rep_idx>=0) { + if (!group->xas_groups && !group->xas_base) { + group->ssr_switch = GF_TRUE; + group->ssr_shadow_idx_plus_one = active_rep+1; + } + active_rep = ssr_rep_idx; + } rep_sel = gf_list_get(group->adaptation_set->representations, active_rep); @@ -6640,10 +7080,16 @@ if (dg1_weight > dg2_weight) { gf_list_rem(dash->groups, j); gf_list_insert(dash->groups, dg2, j-1); - dg2->groups_idx = j-1; + dg2->index = j-1; j=0; } } + //we have rewritten indices, rewrite all cross-AS set + for (j=0; j < gf_list_count(dash->groups); j++) { + GF_DASH_Group *dg = gf_list_get(dash->groups, j); + if (dg->xas_base) dg->index = dg->xas_base->index; + } + if (nb_srd_it && (dash->auto_switch_count<0)) { nb_srd_it++; dash->auto_switch_count = nb_srd_it; @@ -6792,19 +7238,27 @@ u32 clock_time; Bool will_retry = GF_FALSE; Bool is_live = GF_FALSE; - u32 min_wait; + u32 min_wait; if (!dash || !group) return GF_DASH_DownloadCancel; clock_time = gf_sys_clock(); - min_wait = dash->min_timeout_between_404; - if (dash->route_clock_state) { - if (!group->period->origin_base_url) - min_wait = 50; //50 ms between retries if route and not a remote period - } + min_wait = dash->min_timeout_between_404; + if (dash->mcast_clock_state) { + if (!group->period->origin_base_url) + min_wait = 50; //50 ms between retries if multicast and not a remote period + + const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-mcast-over"); + if (hdr && !strcmp(hdr, "yes")) { + gf_dash_mark_group_done(group); + if (new_base_seg_url) gf_free(new_base_seg_url); + if (key_url) gf_free(key_url); + return GF_DASH_DownloadCancel; + } + } - dash_set_min_wait(dash, min_wait); + dash_set_min_wait(dash, min_wait); group->retry_after_utc = min_wait + gf_net_get_utc(); if (!group->period->origin_base_url && (dash->mpd->type==GF_MPD_TYPE_DYNAMIC)) @@ -6834,15 +7288,15 @@ } group->current_dep_idx=0; group->segment_in_valid_range=0; - } - //ROUTE case, the file was removed from cache by the route demuxer - else if (e==GF_URL_REMOVED) { + } + //Multicast case, the file was removed from cache by the file receiver + else if (e==GF_URL_REMOVED) { group->current_dep_idx=0; } else if (group->prev_segment_ok && !group->time_at_first_failure && !group->loop_detected) { - group->time_at_first_failure = clock_time; - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Error in downloading new segment: %s %s - starting countdown for %d ms (delay between retry %d ms)\n", new_base_seg_url, gf_error_to_string(e), group->current_downloaded_segment_duration, min_wait)); + group->time_at_first_failure = clock_time; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Error in downloading new segment: %s %s - starting countdown for %d ms (delay between retry %d ms)\n", new_base_seg_url, gf_error_to_string(e), group->current_downloaded_segment_duration, min_wait)); - will_retry = GF_TRUE; + will_retry = GF_TRUE; } //if multiple baseURL, try switching the base else if ((e==GF_URL_ERROR) && (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) )) { @@ -6862,11 +7316,11 @@ else if (group->prev_segment_ok && (clock_time - group->time_at_first_failure <= group->current_downloaded_segment_duration + dash->segment_lost_after_ms )) { will_retry = GF_TRUE; } else { - if ((group->dash->route_clock_state==2) && (e==GF_URL_ERROR)) { - const char *val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-loop"); + if ((group->dash->mcast_clock_state == GF_DASH_MCAST_SYNC_SOURCE) && (e==GF_URL_ERROR)) { + const char *val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-mcast-loop"); Bool is_loop = (val && !strcmp(val, "yes")) ? GF_TRUE : GF_FALSE; - if (!is_loop && dash->route_last_retune && (gf_sys_clock() - dash->route_last_retune > 10000)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH ROUTE lost signal for 10s, aborting\n")); + if (!is_loop && dash->mcast_last_retune && (gf_sys_clock() - dash->mcast_last_retune > 10000)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Multicast lost signal for 10s, aborting\n")); gf_dash_mark_group_done(group); if (new_base_seg_url) gf_free(new_base_seg_url); if (key_url) gf_free(key_url); @@ -6876,14 +7330,14 @@ else if ((group->nb_consecutive_segments_lost >= 5) || is_loop) { u32 i=0; if (is_loop) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH ROUTE loop detected, resetting timeline\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Multicast loop detected, resetting timeline\n")); } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH ROUTE lost %d consecutive segments, resetup tune-in\n", group->nb_consecutive_segments_lost)); + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH Multicast lost %d consecutive segments, resetup tune-in\n", group->nb_consecutive_segments_lost)); } dash->utc_drift_estimate = 0; + dash->mcast_clock_state = GF_DASH_MCAST_INIT; dash->initial_period_tunein = GF_TRUE; - dash->route_clock_state = 1; - dash->route_last_retune = gf_sys_clock(); + dash->mcast_last_retune = gf_sys_clock(); while ((group = gf_list_enum(dash->groups, &i))) { group->start_number_at_last_ast = 0; @@ -6904,8 +7358,8 @@ if (group->prev_segment_ok) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Error in downloading new segment %s: %s - waited %d ms but segment still not available, checking next one ...\n", new_base_seg_url, gf_error_to_string(e), clock_time - group->time_at_first_failure)); group->time_at_first_failure = 0; - //for route we still consider the previous segment valid and don't attempt to resync the timeline - if (!group->dash->route_clock_state) { + //for multicast we still consider the previous segment valid and don't attempt to resync the timeline + if (!group->dash->mcast_clock_state) { group->prev_segment_ok = GF_FALSE; if ((dash->mpd->type==GF_MPD_TYPE_STATIC) && (dash->chaining_mode==2) && dash->chain_fallback) { @@ -7083,10 +7537,17 @@ dash->force_mpd_update = GF_TRUE; group->time_at_first_reload_required = now; } + if (dash->mcast_clock_state) { + const char *hdr = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-mcast-over"); + if (hdr && !strcmp(hdr, "yes")) { + gf_dash_mark_group_done(group); + } + return GF_DASH_DownloadCancel; + } //use group last modification time timer = now - group->last_mpd_change_time; - if (timer < group->segment_duration * 4000) { + if (timer < group->segment_duration * 1000 * SEGLIST_TIMEOUT_SEG) { //no more segment, force a manifest update now dash->force_mpd_update = GF_TRUE; } else { @@ -7112,7 +7573,7 @@ //dyn mode, check group last modification time, if time elapsed less than 2 seg dur, wait if (dyn_type==GF_MPD_TYPE_DYNAMIC) { timer = now - group->last_mpd_change_time; - if (timer < 2 * group->segment_duration * 1000) + if (timer < group->segment_duration * 1000 * SEGLIST_TIMEOUT_SEG) return GF_DASH_DownloadCancel; } @@ -7125,7 +7586,7 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH End of period reached for group\n")); gf_dash_mark_group_done(group); if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following) { - dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, base_group->groups_idx, GF_OK); + dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, base_group->index, GF_OK); } return GF_DASH_DownloadCancel; } @@ -7163,7 +7624,7 @@ if (group->force_early_fetch) { if (to_wait>1) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Set #%d demux empty but wait time for segment %d is still %d ms, forcing scheduling\n", 1+group->groups_idx, group->download_segment_index + start_number, to_wait)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Set #%d demux empty but wait time for segment %d is still %d ms, forcing scheduling\n", 1+group->index, group->download_segment_index + start_number, to_wait)); to_wait = 0; } else { //we officially reached segment AST @@ -7173,7 +7634,7 @@ /*if segment AST is greater than now, it is not yet available - we would need an estimate on how long the request takes to be sent to the server in order to be more reactive ...*/ if (to_wait > 1) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) is not yet available on server - requesting later in %d ms\n", 1+group->groups_idx, gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, to_wait)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) is not yet available on server - requesting later in %d ms\n", 1+group->index, gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, to_wait)); if (group->last_segment_time) { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time)); } @@ -7182,7 +7643,7 @@ return GF_DASH_DownloadCancel; } else { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) should now be available on server since %d ms - requesting it\n", 1+group->groups_idx, gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, -to_wait)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) should now be available on server since %d ms - requesting it\n", 1+group->index, gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, -to_wait)); if (group->last_segment_time) { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time)); @@ -7190,10 +7651,11 @@ seg_utc = segment_ast; } - group->time_at_last_request = gf_sys_clock(); - } + group->time_at_last_request = gf_sys_clock(); + } base_url = dash->base_url; + start_number=0; if (group->period->origin_base_url) base_url = group->period->origin_base_url; /* At this stage, there are some segments left to be downloaded */ e = gf_dash_resolve_url(dash->mpd, rep, group, base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &new_base_seg_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, NULL, &start_number); @@ -7267,11 +7729,11 @@ #ifndef GPAC_DISABLE_LOG if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) { if (llhls_live_edge_type==2) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Queing next segment: %s (live edge merged range: "LLU" -> END)\n", gf_file_basename(new_base_seg_url), start_range)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Queuing next segment: %s (live edge merged range: "LLU" -> END)\n", gf_file_basename(new_base_seg_url), start_range)); } else if (use_byterange) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Queing next %s: %s (range: "LLU" -> "LLU")\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", gf_file_basename(new_base_seg_url), start_range, end_range)); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Queuing next %s (%u): %s (range: "LLU" -> "LLU")\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", group->download_segment_index, gf_file_basename(new_base_seg_url), start_range, end_range)); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Queing next %s: %s\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", gf_file_basename(new_base_seg_url))); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Queuing next %s (%u): %s\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", group->download_segment_index, gf_file_basename(new_base_seg_url))); } } #endif @@ -7353,6 +7815,11 @@ cache_entry->seg_number = group->download_segment_index + start_number; cache_entry->seg_name_start = dash_strip_base_url(cache_entry->url, base_url); group->loop_detected = GF_FALSE; + if (!base_group->nb_cached_segments) { + Double mtime = (Double) cache_entry->time.num; + mtime /= cache_entry->time.den; + if (dash->max_last_seg_start < mtime) dash->max_last_seg_start = mtime; + } if (group->local_files && use_byterange) { cache_entry->start_range = start_range; @@ -7371,9 +7838,22 @@ else { if (group->base_rep_index_plus_one) group->active_rep_index = group->base_rep_index_plus_one - 1; if (dash->speed >= 0) { - group->download_segment_index++; + if (group->nb_parts && (group->part_idx + 1 < group->nb_parts)) { + group->part_idx++; + group->nb_segments_since_switch --; + } else { + group->part_idx = 0; + //switching from SSR is done in rate adaptation + group->download_segment_index++; + } } else if (group->download_segment_index) { - group->download_segment_index--; + if (group->nb_parts && group->part_idx) { + group->part_idx --; + group->nb_segments_since_switch --; + } else { + group->part_idx = 0; + group->download_segment_index--; + } } else { GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Playing in backward - start of playlist reached - assuming end of stream\n")); gf_dash_mark_group_done(group); @@ -7396,7 +7876,7 @@ //do not notify segments if there is a pending period switch - since these are decided by the user, we don't //want to notify old segments if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following) { - dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, base_group->groups_idx, GF_OK); + dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, base_group->index, GF_OK); } //do NOT free new_base_seg_url, it is now in cache_entry->url @@ -7471,7 +7951,7 @@ //estimate bitrate for (i=0; i<count; i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); - if (group->selection != GF_DASH_GROUP_SELECTED) continue; + if ((group->selection != GF_DASH_GROUP_SELECTED) || group->xas_selected) continue; if (group->local_files) local_files ++; if (!group->bytes_per_sec) { if (!for_postponed_only && !group->disabled) @@ -7492,7 +7972,7 @@ group->backup_Bps = group->bytes_per_sec; //only count broadband ones - if (dash->route_clock_state && !gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs) && !group->period->origin_base_url) { + if (dash->mcast_clock_state && !gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs) && !group->period->origin_base_url) { u32 j; //get all active rep, count bandwidth for broadband ones for (j=0; j<=group->max_complementary_rep_index; j++) { @@ -7537,7 +8017,7 @@ for (i=0; i<count; i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); - if (group->selection != GF_DASH_GROUP_SELECTED) continue; + if ((group->selection != GF_DASH_GROUP_SELECTED) || group->xas_selected) continue; if (group->done) continue; quality_rank = gf_dash_get_tiles_quality_rank(dash, group); @@ -7589,7 +8069,7 @@ for (i=0; i<count; i++) { u32 diff; GF_DASH_Group *group = gf_list_get(dash->groups, i); - if (group->selection != GF_DASH_GROUP_SELECTED) continue; + if ((group->selection != GF_DASH_GROUP_SELECTED) || group->xas_selected) continue; if (group->done) continue; quality_rank = gf_dash_get_tiles_quality_rank(dash, group); @@ -7604,7 +8084,7 @@ rep_new = gf_list_get(group->adaptation_set->representations, group->target_new_rep+1); diff = rep_new->bandwidth - diff; - if (dash->route_clock_state) { + if (dash->mcast_clock_state) { //if baseURL in period or adaptation set, we assume we are in broadband mode, otherwise we re in broadcast, don't count bitrate if (!gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs)) { //new rep is in broadcast, force diff to 0 to select the rep @@ -7679,7 +8159,7 @@ //bandwitdh sharing done, perform rate adaptation with theses new numbers for (i=0; i<count; i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); - if (group->selection != GF_DASH_GROUP_SELECTED) continue; + if ((group->selection != GF_DASH_GROUP_SELECTED) || group->xas_selected) continue; if (group->done) continue; //in custom algo case, we don't change the bitrate of the group @@ -7711,7 +8191,7 @@ if (!for_postponed_only) { for (i=0; i<count; i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); - if (group->selection != GF_DASH_GROUP_SELECTED) continue; + if ((group->selection != GF_DASH_GROUP_SELECTED) || group->xas_selected) continue; if (group->done) continue; if (!group->rate_adaptation_postponed) @@ -7758,12 +8238,12 @@ if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue; - if (group->group_setup) continue; + if (group->group_setup || group->done) continue; e = gf_dash_download_init_segment(dash, group); //might happen with broadcast DASH (eg ATSC3) - if (e == GF_IP_NETWORK_EMPTY) { + if ((e == GF_IP_NETWORK_EMPTY) || (e == GF_NOT_READY)) { if (dash->reinit_period_index) { gf_dash_reset_groups(dash); dash->active_period_index = dash->reinit_period_index-1; @@ -7775,7 +8255,7 @@ return e; } group->group_setup = GF_TRUE; - if (dash->initial_period_tunein && !dash->route_clock_state) { + if (dash->initial_period_tunein && !dash->mcast_clock_state) { group->timeline_setup = GF_FALSE; group->force_timeline_reeval = GF_TRUE; } @@ -7791,6 +8271,37 @@ return e; } + //select SSR in cross-AS groups + for (i=0; i<group_count; i++) { + GF_DASH_Group *group = gf_list_get(dash->groups, i); + if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) + continue; + //get group + if (!group->xas_groups) continue; + u32 k, nb_in_set = gf_list_count(group->xas_groups); + GF_DASH_Group *forced_ssr = group; + for (k=0; k<nb_in_set; k++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, k); + if (agr->selection == GF_DASH_GROUP_NOT_SELECTABLE) continue; + if (agr->has_ssr) forced_ssr = agr; + } + group->xas_selected = 0; + if (forced_ssr != group) + group->selection = GF_DASH_GROUP_NOT_SELECTED; + else if (group->has_ssr) + group->ssr_switch = GF_TRUE; + + for (k=0; k<nb_in_set; k++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, k); + if (forced_ssr && (agr == forced_ssr)) { + agr->selection = GF_DASH_GROUP_SELECTED; + group->xas_selected = k+1; + group->ssr_switch = GF_TRUE; + } else { + agr->selection = GF_DASH_GROUP_NOT_SELECTED; + } + } + } return GF_OK; } @@ -7803,7 +8314,9 @@ /*for each selected groups*/ for (i=0; i<group_count; i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); - if (group->selection != GF_DASH_GROUP_SELECTED) { + if ((group->selection != GF_DASH_GROUP_SELECTED) + || group->xas_selected + ) { if (group->nb_cached_segments) { gf_dash_group_reset(dash, group); } @@ -7888,6 +8401,7 @@ if ((group->selection != GF_DASH_GROUP_SELECTED) || group->depend_on_group + || group->xas_selected || !group->adaptation_set || (group->done && !group->nb_cached_segments) ) { @@ -7954,6 +8468,7 @@ (*cache_is_full) = GF_FALSE; dash->request_period_switch = 0; + dash->max_last_seg_start = 0; dash->period_groups_setup = GF_FALSE; GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Switching to period #%d\n", dash->active_period_index+1)); dash->dash_state = GF_DASH_STATE_SETUP; @@ -8274,23 +8789,78 @@ return dash->request_period_switch ? 1 : 0; } +static void gather_ssr_rep(GF_DASH_Group *group, GF_List *select) +{ + u32 i, count = gf_list_count(group->adaptation_set->representations); + for (i=0; i<count; i++) { + GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i); + if (rep->playback.disabled) continue; + if (!rep->playback.use_ssr) continue; + u32 j, nb_sel = gf_list_count(select); + for (j=0; j<nb_sel; j+=2) { + GF_MPD_Representation *arep = gf_list_get(select, j+1); + if (arep->bandwidth>rep->bandwidth) { + gf_list_insert(select, rep, j); + gf_list_insert(select, group, j); + rep = NULL; + break; + } + } + if (rep) { + gf_list_add(select, group); + gf_list_add(select, rep); + } + } +} static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic) { GF_Err e; - u32 first_downloaded, last_downloaded, segment_idx, orig_idx; - + u32 orig_idx; if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) return; + GF_MPD_AdaptationSet *set = group->adaptation_set; + GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); + orig_idx = group->download_segment_index; group->force_segment_switch = 0; + + //switch to SSR rep + if (group->has_ssr || group->xas_groups || group->xas_base) { + GF_List *select = gf_list_new(); + GF_DASH_Group *gr = group->xas_base ? group->xas_base : group; + gather_ssr_rep(gr, select); + u32 j, count = gf_list_count(gr->xas_groups); + for (j=0; j<count; j++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, j); + gather_ssr_rep(agr, select); + } + if (gf_list_count(select)) { + GF_DASH_Group *new_group = gf_list_get(select, 0); + rep = gf_list_get(select, 1); + set = new_group->adaptation_set; + new_group->active_rep_index = gf_list_find(set->representations, rep); + if (new_group->xas_base) { + new_group->xas_base->xas_selected = 1+gf_list_find(new_group->xas_base->xas_groups, new_group); + } else { + new_group->xas_selected = 0; + } + group->selection = GF_DASH_GROUP_NOT_SELECTED; + new_group->selection = GF_DASH_GROUP_SELECTED; + group = new_group; + if (group->xas_base) group->xas_base->ssr_switch = GF_TRUE; + else group->ssr_switch = GF_TRUE; + } + gf_list_del(select); + } + if (!is_dynamic) { + u32 first_downloaded, last_downloaded, segment_idx; + Double seg_start, seg_dur; + /*figure out where to seek*/ - orig_idx = group->download_segment_index; - e = gf_mpd_seek_in_period(seek_to, MPD_SEEK_PREV, group->period, group->adaptation_set, gf_list_get(group->adaptation_set->representations, group->active_rep_index), &segment_idx, NULL); + e = gf_mpd_seek_in_period(seek_to, MPD_SEEK_PREV, group->period, set, rep, &segment_idx, &seg_start, &seg_dur); if (e<0) GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("DASH An error occured while seeking to time %lf: %s\n", seek_to, gf_error_to_string(e))); - group->download_segment_index = orig_idx; - /*remember to seek to given duration*/ group->start_playback_range = seek_to; @@ -8299,12 +8869,16 @@ first_downloaded = group->download_segment_index + 1 - group->nb_cached_segments; } /*we are seeking in our download range, just go on*/ - if ((segment_idx>=first_downloaded) && (segment_idx<=last_downloaded)) { + if ((segment_idx>=first_downloaded) && (segment_idx<=last_downloaded) && !rep->playback.use_ssr) { + group->download_segment_index = orig_idx; return; } group->force_segment_switch = 1; group->download_segment_index = segment_idx; + if (rep->playback.use_ssr) { + dash_ssr_adjust_group_start(group, seg_dur, rep->playback.use_ssr); + } } else { group->start_number_at_last_ast = 0; /*remember to adjust time in timeline setup*/ @@ -8312,11 +8886,24 @@ group->timeline_setup = GF_FALSE; } + //reset group cache + if (group->xas_base) group = group->xas_base; while (group->nb_cached_segments) { group->nb_cached_segments--; gf_dash_group_reset_cache_entry(&group->cachedgroup->nb_cached_segments); } - group->done = 0; + group->done = GF_FALSE; + if (group->xas_groups) { + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + while (agr->nb_cached_segments) { + agr->nb_cached_segments--; + gf_dash_group_reset_cache_entry(&agr->cachedagr->nb_cached_segments); + } + agr->done = GF_FALSE; + } + } } GF_EXPORT @@ -8341,6 +8928,8 @@ } for (i=0; i<gf_list_count(dash->groups); i++) { GF_DASH_Group *group = gf_list_get(dash->groups, i); + if (group->xas_base) continue; + gf_dash_seek_group(dash, group, seek_time, is_dynamic); } } @@ -8374,8 +8963,8 @@ } e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, group_idx); if (e) { - //with ROUTE we may have 404 right away if nothing in cache yet, not an error - GF_LOG(dash->route_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("DASH Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) )); + //with multicast we may have 404 right away if nothing in cache yet, not an error + GF_LOG(dash->mcast_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("DASH Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) )); return e; } sess = (GF_DASHFileIOSession *)getter->session; @@ -8420,7 +9009,7 @@ { char local_pathGF_MAX_PATH; const char *local_url; - char *sep_cgi = NULL; + char *sep_query = NULL; char *sep_frag = NULL; GF_Err e; GF_DOMParser *mpd_parser=NULL; @@ -8432,18 +9021,16 @@ dash->reload_count = 0; dash->initial_period_tunein = GF_TRUE; - dash->route_clock_state = dash->reload_count = dash->last_update_time = 0; + dash->mcast_clock_state = GF_DASH_MCAST_NONE; + dash->reload_count = dash->last_update_time = 0; memset(dash->lastMPDSignature, 0, sizeof(char)*GF_SHA1_DIGEST_SIZE); dash->utc_drift_estimate = 0; dash->time_in_tsb = dash->prev_time_in_tsb = 0; dash->reinit_period_index = 0; dash->seek_pending = -1; + dash->max_last_seg_start = 0; - if (dash->base_url) gf_free(dash->base_url); - sep_cgi = strrchr(manifest_url, '?'); - if (sep_cgi) sep_cgi0 = 0; - dash->base_url = gf_strdup(manifest_url); - if (sep_cgi) sep_cgi0 = '?'; + gf_dash_set_base_url(dash, manifest_url); dash->getter.udta = dash; dash->getter.new_session = http_ifce_get; @@ -8483,8 +9070,7 @@ /*if relocated use new URL as base URL for all requests*/ if (strcmp(reloc_url, manifest_url)) { - gf_free(dash->base_url); - dash->base_url = gf_strdup(reloc_url); + gf_dash_set_base_url(dash, reloc_url); } local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload); @@ -8502,14 +9088,14 @@ if (is_local && strncmp(manifest_url, "gfio://", 7)) { FILE *f = gf_fopen(local_url, "rt"); if (!f) { - sep_cgi = strrchr(local_url, '?'); - if (sep_cgi) sep_cgi0 = 0; + sep_query = strrchr(local_url, '?'); + if (sep_query) sep_query0 = 0; sep_frag = strrchr(local_url, '#'); if (sep_frag) sep_frag0 = 0; f = gf_fopen(local_url, "rt"); if (!f) { - if (sep_cgi) sep_cgi0 = '?'; + if (sep_query) sep_query0 = '?'; if (sep_frag) sep_frag0 = '#'; return GF_URL_ERROR; } @@ -8529,18 +9115,18 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("DASH Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e))); goto exit; } - if (dash->dash_io->manifest_updated) { - const char *szName = gf_file_basename(manifest_url); - dash->dash_io->manifest_updated(dash->dash_io, szName, local_url, -1); - } //peek payload, check if m3u8 - MPD and SmoothStreaming are checked after char szLine100; + szLine0 = 0; FILE *f = gf_fopen(local_url, "r"); if (f) { - gf_fread(szLine, 100, f); + u32 read_count = (u32) gf_fread(szLine, 100, f); + if (read_count < 99) + szLineread_count = 0; gf_fclose(f); } + szLine99 = 0; if (strstr(szLine, "#EXTM3U")) dash->is_m3u8 = 1; @@ -8561,6 +9147,17 @@ e = gf_m3u8_to_mpd(local_url, redirected_url, NULL, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, dash->mpd, GF_FALSE, dash->keep_files); } +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("Translated M3U8 to MPD:\n")); + if (!dash->mpd->x_attributes) dash->mpd->x_attributes = gf_list_new(); + dash->mpd->write_context=GF_TRUE; + gf_list_add(dash->mpd->x_attributes, gf_xml_dom_create_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink")); + gf_mpd_write(dash->mpd, stderr, GF_FALSE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("\n")); + } +#endif //GPAC_DISABLE_LOG + if (!e && dash->split_adaptation_set) gf_mpd_split_adaptation_sets(dash->mpd); @@ -8582,7 +9179,7 @@ mpd_parser = gf_xml_dom_new(); e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL); - if (sep_cgi) sep_cgi0 = '?'; + if (sep_query) sep_query0 = '?'; if (sep_frag) sep_frag0 = '#'; if (e != GF_OK) { @@ -8607,14 +9204,19 @@ dash_purge_xlink(dash->mpd); } - //for both DASH and HLS, we support ROUTE + //notify manifest update after parsing it as the callback could ask for some properties of the MPD + if (!e && dash->dash_io->manifest_updated) { + const char *szName = gf_file_basename(manifest_url); + dash->dash_io->manifest_updated(dash->dash_io, szName, local_url, -1); + } + + + //for both DASH and HLS, we support multicast if (!is_local) { - const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-route"); - if (hdr) { - if (!dash->route_clock_state) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Detected ROUTE DASH service ID %s\n", hdr)); - dash->route_clock_state = 1; - } + const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-mcast"); + if (hdr && !strcmp(hdr, "yes") && !dash->mcast_clock_state) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Detected Multicast DASH service ID %s\n", hdr)); + dash->mcast_clock_state = GF_DASH_MCAST_INIT; } } @@ -8736,8 +9338,8 @@ GF_MPD_Representation *rep, Bool go_up_bitrate) { GF_DASHCustomAlgoInfo stats; - u32 g_idx = group->groups_idx; - u32 b_idx = base_group->groups_idx; + u32 g_idx = group->index; + u32 b_idx = base_group->index; stats.download_rate = dl_rate; stats.file_size = group->total_size; @@ -8758,7 +9360,7 @@ static s32 dash_do_rate_monitor_custom(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur) { - u32 g_idx = group->groups_idx; + u32 g_idx = group->index; return dash->rate_adaptation_download_monitor_custom(dash->udta_custom_algo, g_idx, bits_per_sec, total_bytes, bytes_done, us_since_start, buffer_dur_ms, current_seg_dur); } @@ -8810,7 +9412,7 @@ dash->segment_lost_after_ms = 100; dash->dbg_grps_index = NULL; dash->tile_rate_decrease = 100; - dash->route_ast_shift = 1000; + dash->mcast_ast_shift = 1000; dash->initial_period_tunein = GF_TRUE; GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DASH Client created\n")); @@ -8839,6 +9441,7 @@ if (dash->mimeTypeForM3U8Segments) gf_free(dash->mimeTypeForM3U8Segments); if (dash->base_url) gf_free(dash->base_url); + if (dash->query_part) gf_free(dash->query_part); gf_free(dash); } @@ -8881,6 +9484,11 @@ dash->auto_switch_loop = auto_switch_loop; } +GF_EXPORT +void gf_dash_enable_cross_as_switch(GF_DashClient *dash, GF_DASHCrossASMode cross_as_mode) +{ + if (dash) dash->cross_as_mode = cross_as_mode; +} GF_EXPORT u32 gf_dash_get_group_count(GF_DashClient *dash) @@ -8910,16 +9518,21 @@ Bool gf_dash_is_group_selected(GF_DashClient *dash, u32 idx) { GF_DASH_Group *group = gf_list_get(dash->groups, idx); - if (!group) return 0; - return (group->selection == GF_DASH_GROUP_SELECTED) ? 1 : 0; + if (!group) return GF_FALSE; + if (group->selection == GF_DASH_GROUP_SELECTED) return GF_TRUE; + if (group->xas_selected) return GF_TRUE; + return GF_FALSE; } GF_EXPORT Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 idx) { GF_DASH_Group *group = gf_list_get(dash->groups, idx); - if (!group) return 0; - return (group->selection == GF_DASH_GROUP_NOT_SELECTABLE) ? 0 : 1; + if (!group) return GF_FALSE; + if (group->selection == GF_DASH_GROUP_NOT_SELECTABLE) return GF_FALSE; + //all gorups in cross-AS switching sets are non selectable + if (group->xas_base) return GF_FALSE; + return GF_TRUE; } GF_EXPORT @@ -9103,24 +9716,12 @@ return dash->is_smooth; } -GF_EXPORT -const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 idx) -{ - GF_MPD_Representation *rep; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); - if (!group) return NULL; - - rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); - return gf_dash_get_mime_type(NULL, rep, group->adaptation_set); -} - - GF_EXPORT -const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 idx, u64 *start_range, u64 *end_range) +const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 idx, u64 *start_range, u64 *end_range, const char **mime_type) { GF_MPD_Representation *rep; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return NULL; /*solve dependencies if any - we first test highest: if this is a complementary rep, keep the highest for init @@ -9131,6 +9732,8 @@ if (!rep->dependency_id) rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); + if (mime_type) *mime_type = gf_dash_get_mime_type(NULL, rep, group->adaptation_set); + if (group->bs_switching_init_segment_url) { if (start_range) *start_range = group->bs_switching_init_segment_url_start_range; if (end_range) *end_range = group->bs_switching_init_segment_url_end_range; @@ -9158,7 +9761,7 @@ const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 idx, u32 *crypt_type, bin128 *key_IV) { GF_MPD_Representation *rep; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return NULL; rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); @@ -9176,7 +9779,7 @@ GF_MPD_Descriptor *desc_kid=NULL; GF_MPD_Descriptor *desc_ck=NULL; GF_MPD_Representation *rep; - GF_DASH_Group *group = gf_list_get(dash->groups, group_idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, group_idx); if (!group) return NULL; u32 i, count = gf_list_count(group->adaptation_set->content_protection); @@ -9240,7 +9843,7 @@ Bool gf_dash_group_init_segment_is_media(GF_DashClient *dash, u32 idx) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return GF_FALSE; return group->init_segment_is_media; } @@ -9265,15 +9868,18 @@ GF_EXPORT void gf_dash_group_select(GF_DashClient *dash, u32 idx, Bool select) { - Bool needs_resetup = GF_FALSE; + //Bool needs_resetup = GF_FALSE; GF_DASH_Group *group = gf_list_get(dash->groups, idx); if (!group) return; if (group->selection == GF_DASH_GROUP_NOT_SELECTABLE) return; - if ((group->selection==GF_DASH_GROUP_NOT_SELECTED) && select) needs_resetup = 1; + //if ((group->selection==GF_DASH_GROUP_NOT_SELECTED) && select) needs_resetup = 1; group->selection = select ? GF_DASH_GROUP_SELECTED : GF_DASH_GROUP_NOT_SELECTED; + if (!select) { + group->dash->dash_io->on_dash_event(group->dash->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, group->index, GF_OK); + } /*this set is part of a group, make sure no all other sets from the indicated group are unselected*/ if (dash->enable_group_selection && select && (group->adaptation_set->group>=0)) { @@ -9291,11 +9897,6 @@ } } } - - //TODO: recompute group download index based on current playback ... - if (needs_resetup) { - - } } GF_EXPORT @@ -9479,28 +10080,38 @@ GF_Err gf_dash_group_get_segment_duration(GF_DashClient *dash, u32 idx, u32 *dur, u32 *timescale) { GF_MPD_Representation *rep; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return GF_BAD_PARAM; if (!group->adaptation_set) return GF_BAD_PARAM; rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); if (!rep) return GF_BAD_PARAM; *dur = 0; - *timescale = 0; - if (rep->segment_template) { *dur = (u32) rep->segment_template->duration; (*timescale) = rep->segment_template->timescale; } - else if (rep->segment_list) { *dur = (u32) rep->segment_list->duration; *timescale = rep->segment_list->timescale; } + *timescale = 1; //MPD timescale is 1 if not present + if (rep->segment_template) { + *dur = (u32) rep->segment_template->duration; + if (rep->segment_template->timescale) (*timescale) = rep->segment_template->timescale; + } + else if (rep->segment_list) { + *dur = (u32) rep->segment_list->duration; + if (rep->segment_list->timescale) *timescale = rep->segment_list->timescale; + } if (group->adaptation_set->segment_template && ! *dur) *dur = (u32) group->adaptation_set->segment_template->duration; else if (group->adaptation_set->segment_list && ! *dur) *dur = (u32) group->adaptation_set->segment_list->duration; - if (group->adaptation_set->segment_template && ! *timescale) *timescale = group->adaptation_set->segment_template->timescale; - else if (group->adaptation_set->segment_list && ! *timescale) *timescale = group->adaptation_set->segment_list->timescale; + if (group->adaptation_set->segment_template && group->adaptation_set->segment_template->timescale) + *timescale = group->adaptation_set->segment_template->timescale; + else if (group->adaptation_set->segment_list && group->adaptation_set->segment_list->timescale) + *timescale = group->adaptation_set->segment_list->timescale; if (group->period->segment_template && ! *dur) *dur = (u32) group->period->segment_template->timescale; else if (group->period->segment_list && ! *dur) *dur = (u32) group->period->segment_list->timescale; - if (group->period->segment_template && ! *timescale) *timescale = group->period->segment_template->timescale; - else if (group->period->segment_list && ! *timescale) *timescale = group->period->segment_list->timescale; + if (group->period->segment_template && group->period->segment_template->timescale) + *timescale = group->period->segment_template->timescale; + else if (group->period->segment_list && group->period->segment_list->timescale) + *timescale = group->period->segment_list->timescale; return GF_OK; } @@ -9508,7 +10119,7 @@ GF_Err gf_dash_group_next_seg_info(GF_DashClient *dash, u32 group_idx, u32 dependent_representation_index, const char **seg_name, u32 *seg_number, GF_Fraction64 *seg_time, u32 *seg_dur_ms, const char **init_segment) { GF_Err res = GF_OK; - GF_DASH_Group *group = gf_list_get(dash->groups, group_idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, group_idx); if (!group) return GF_BAD_PARAM; if (init_segment) { @@ -9519,7 +10130,7 @@ *init_segment = rep ? rep->playback.init_seg_name_start : NULL; } if (group->init_segment_is_media) { - if (seg_number) *seg_number = 0; + if (seg_number) *seg_number = group->init_segment_start_number; if (seg_time || seg_dur_ms) { u64 segment_dur, res; u32 seg_scale; @@ -9554,7 +10165,7 @@ const char *gf_dash_group_get_representation_id(GF_DashClient *dash, u32 idx) { GF_MPD_Representation *rep; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return NULL; if (group->nb_cached_segments) rep = gf_list_get(group->adaptation_set->representations, group->cached0.representation_index); @@ -9563,19 +10174,13 @@ return rep->id; } - -GF_EXPORT -void gf_dash_group_discard_segment(GF_DashClient *dash, u32 idx) +static void gf_dash_group_discard_segment_internal(GF_DashClient *dash, GF_DASH_Group *group) { - GF_DASH_Group *group; Bool delete_next; - group = gf_list_get(dash->groups, idx); - discard_segment: - if (!group->nb_cached_segments) { - return; - } + if (!group->nb_cached_segments) return; + delete_next = (group->cached0.flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE; gf_assert(group->cached0.url); @@ -9600,12 +10205,36 @@ goto discard_segment; } } +GF_EXPORT +void gf_dash_group_discard_segment(GF_DashClient *dash, u32 idx) +{ + GF_DASH_Group *group; + //we check both base group and selected one in AS group for initial discard - could be further optimize + group = gf_list_get(dash->groups, idx); + gf_dash_group_discard_segment_internal(dash, group); + if (group->xas_groups) { + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + gf_dash_group_discard_segment_internal(dash, agr); + } + } +} GF_EXPORT void gf_dash_set_group_done(GF_DashClient *dash, u32 idx, Bool done) { GF_DASH_Group *group = gf_list_get(dash->groups, idx); - if (group) group->done = done; + if (!group) return; + + group->done = done; + if (group->xas_groups) { + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + agr->done = done; + } + } } GF_EXPORT @@ -9619,7 +10248,7 @@ GF_EXPORT GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 idx, u64 *presentation_time_offset, u32 *timescale) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (group) { u64 duration; GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index); @@ -9646,11 +10275,8 @@ if (has_next_segment) *has_next_segment = GF_FALSE; if (utc) *utc = 0; - group = gf_list_get(dash->groups, idx); - - if (!group) { - return GF_BAD_PARAM; - } + group = gf_dash_get_active_group(dash, idx); + if (!group) return GF_BAD_PARAM; if (!group->nb_cached_segments) { if (group->done) return GF_EOS; @@ -9741,6 +10367,7 @@ GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH Seek request - playing from %g\n", start_range)); + dash->max_last_seg_start = 0; //are we live ? if so adjust start range if (dash->preroll_state == 1) { dash->initial_period_tunein = GF_TRUE; @@ -9776,7 +10403,7 @@ GF_EXPORT Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 idx) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); return group->force_segment_switch; } @@ -9794,6 +10421,17 @@ *max_width = group->adaptation_set->max_width; *max_height = group->adaptation_set->max_height; + + if (group->xas_groups) { + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + if (*max_width < agr->adaptation_set->max_width) + *max_width = agr->adaptation_set->max_width; + if (*max_height < agr->adaptation_set->max_height) + *max_height = agr->adaptation_set->max_height; + } + } return GF_OK; } @@ -9805,6 +10443,17 @@ *max_width = group->srd_desc->width; *max_height = group->srd_desc->height; + if (group->xas_groups) { + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + if (!agr->srd_desc) continue; + if (*max_width < agr->srd_desc->width) + *max_width = agr->srd_desc->width; + if (*max_height < agr->srd_desc->height) + *max_height = agr->srd_desc->height; + } + } return GF_TRUE; } @@ -9827,7 +10476,7 @@ GF_EXPORT Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 idx) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group || !group->nb_cached_segments) return GF_FALSE; return (group->cached0.flags & SEG_FLAG_LOOP_DETECTED) ? GF_TRUE : GF_FALSE; @@ -9836,7 +10485,7 @@ GF_EXPORT Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 idx) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return 0.0; return group->start_playback_range; } @@ -9856,7 +10505,7 @@ Bool gf_dash_is_low_latency(GF_DashClient *dash, u32 idx) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return GF_FALSE; return group->is_low_latency; } @@ -10052,9 +10701,10 @@ } GF_EXPORT -void gf_dash_debug_groups(GF_DashClient *dash, const u32 *groups_idx, u32 nb_groups) +void gf_dash_debug_groups(GF_DashClient *dash, const u32 *dbg_groups_idx, u32 nb_groups) { - dash->dbg_grps_index = groups_idx; + dash->dbg_grps_index = dbg_groups_idx; + dash->dbg_grps_index = dbg_groups_idx; dash->nb_dbg_grps = nb_groups; } @@ -10148,7 +10798,7 @@ { GF_MPD_Descriptor *mpd_desc; u32 i=0; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return 0; GF_List *l = group->adaptation_set->audio_channels; if (!gf_list_count(l)) { @@ -10157,6 +10807,10 @@ } while ((mpd_desc=gf_list_enum(l, &i))) { + + if (!mpd_desc->scheme_id_uri || !mpd_desc->value) + continue; + if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:23003:3:audio_channel_configuration:2011")) { return atoi(mpd_desc->value); } @@ -10172,7 +10826,15 @@ { GF_DASH_Group *group = gf_list_get(dash->groups, idx); if (!group) return 0; - return gf_list_count(group->adaptation_set->representations); + u32 nb_qualities = gf_list_count(group->adaptation_set->representations); + if (group->xas_groups) { + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + nb_qualities += gf_list_count(agr->adaptation_set->representations); + } + } + return nb_qualities; } GF_EXPORT @@ -10243,7 +10905,7 @@ if (tpl) { u64 range_start, range_end, segment_duration_in_ms; - gf_mpd_resolve_url(dash->mpd, rep, group->adaptation_set, group->period, "", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &solved_template, &range_start, &range_end, &segment_duration_in_ms, NULL, NULL, NULL, NULL); + gf_mpd_resolve_url(dash->mpd, rep, group->adaptation_set, group->period, "", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &solved_template, &range_start, &range_end, &segment_duration_in_ms, NULL, NULL, NULL, NULL, -1); return solved_template; } if (dash->is_m3u8 && rep) { @@ -10275,10 +10937,20 @@ if (!last_non_num || (last_num>=last_non_num+1)) { u32 num; char szVal100; - solved_templatelast_num = 0; - num = atoi(solved_template+last_non_num+1); + if (len > last_num + 1) + solved_templatelast_num+1 = 0; + num = atoi(solved_template + (last_non_num ? (last_non_num+1) : 0)); snprintf(szVal, 100, "%u", num); len = (u32) strlen(szVal); + + if (!group->hls_start_num) + group->hls_start_num = num; + + if (!last_non_num) + solved_template0 = 0; + else + solved_templatelast_non_num+1 = 0; + if (len < last_num - last_non_num) { u32 pad = last_num - last_non_num - len; snprintf(szVal, 100, "$Number%%0%dd$", pad); @@ -10295,15 +10967,37 @@ return NULL; } +static GF_DASH_Group *dash_get_group_for_quality(GF_DashClient *dash, u32 idx, u32 *quality_idx) +{ + GF_DASH_Group *group = gf_list_get(dash->groups, idx); + u32 nb_qual = gf_list_count(group->adaptation_set->representations); + if (*quality_idx >= nb_qual) { + if (!group->xas_groups) return NULL; + *quality_idx -= nb_qual; + u32 i, count = gf_list_count(group->xas_groups); + GF_DASH_Group *agr=NULL; + for (i=0; i<count; i++) { + agr = gf_list_get(group->xas_groups, i); + u32 nb_q = gf_list_count(agr->adaptation_set->representations); + if (*quality_idx <= nb_q) break; + *quality_idx -= nb_q; + agr=NULL; + } + if (!agr) return NULL; + group = agr; + } + return group; +} GF_EXPORT GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 idx, u32 quality_idx, GF_DASHQualityInfo *quality) { GF_MPD_Fractional *sar; - u32 timescale = 0; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + u32 timescale = 1; //MPD timescale is 1 if not present + GF_DASH_Group *group = dash_get_group_for_quality(dash, idx, &quality_idx); GF_MPD_Representation *rep; if (!group || !quality) return GF_BAD_PARAM; + rep = gf_list_get(group->adaptation_set->representations, quality_idx); if (!rep) return GF_BAD_PARAM; @@ -10328,12 +11022,16 @@ quality->bandwidth = rep->bandwidth; quality->ID = rep->id; quality->interlaced = (rep->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : ( (group->adaptation_set->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : 0); + if (dash->is_m3u8 && rep->segment_list) + quality->hls_variant_url = rep->segment_list->previous_xlink_href; if (group->was_segment_base && rep->segment_list) quality->seg_urls = rep->segment_list->segment_URLs; //scalable rep, selected quality is max_complementary_rep_index - if (group->base_rep_index_plus_one) { + if ((group->selection == GF_DASH_GROUP_NOT_SELECTED) || (group->selection == GF_DASH_GROUP_NOT_SELECTABLE)) { + quality->is_selected = GF_FALSE; + } else if (group->base_rep_index_plus_one) { quality->is_selected = (quality_idx==group->max_complementary_rep_index) ? 1 : 0; } else { quality->is_selected = (quality_idx==group->active_rep_index) ? 1 : 0; @@ -10341,17 +11039,17 @@ if (rep->segment_template) { if (!quality->ast_offset) quality->ast_offset = rep->segment_template->availability_time_offset; - if (!timescale) timescale = rep->segment_template->timescale; + if (rep->segment_template->timescale) timescale = rep->segment_template->timescale; if (!quality->average_duration) quality->average_duration = (Double) rep->segment_template->duration; } if (group->adaptation_set->segment_template) { if (!quality->ast_offset) quality->ast_offset = group->adaptation_set->segment_template->availability_time_offset; - if (!timescale) timescale = group->adaptation_set->segment_template->timescale; + if (group->adaptation_set->segment_template->timescale) timescale = group->adaptation_set->segment_template->timescale; if (!quality->average_duration) quality->average_duration = (Double) group->adaptation_set->segment_template->duration; } if (group->period->segment_template) { if (!quality->ast_offset) quality->ast_offset = group->period->segment_template->availability_time_offset; - if (!timescale) timescale = group->period->segment_template->timescale; + if (group->period->segment_template->timescale) timescale = group->period->segment_template->timescale; if (!quality->average_duration) quality->average_duration = (Double) group->period->segment_template->duration; } @@ -10360,6 +11058,7 @@ } else { quality->average_duration = 0; } + quality->ssr = rep->playback.use_ssr; return GF_OK; } @@ -10437,8 +11136,14 @@ if (!group) return GF_BAD_PARAM; if (!ID) { - GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, q_idx); - if (!rep) return GF_BAD_PARAM; + GF_DASH_Group *agroup = dash_get_group_for_quality(dash, idx, &q_idx); + if (!agroup) return GF_BAD_PARAM; + if (agroup != group) { + u32 force_idx = 1+gf_list_find(group->xas_groups, agroup); + if (group->xas_selected) + group = gf_list_get(group->xas_groups, group->xas_selected-1); + group->xas_force_switch = force_idx; + } group->force_representation_idx_plus_one = q_idx+1; group->force_switch_bandwidth = 1; return GF_OK; @@ -10450,9 +11155,29 @@ if (rep->id && !strcmp(rep->id, ID)) { group->force_representation_idx_plus_one = i+1; group->force_switch_bandwidth = 1; + group->xas_force_switch = 0; return GF_OK; } } + q_idx -= count; + if (group->xas_groups) { + u32 j; + for (j=0;j<gf_list_count(group->xas_groups); j++) { + GF_DASH_Group *gr = gf_list_get(group->xas_groups, j); + count = gf_list_count(gr->adaptation_set->representations); + for (i=0; i<count; i++) { + GF_MPD_Representation *rep = gf_list_get(gr->adaptation_set->representations, i); + if (rep->id && !strcmp(rep->id, ID)) { + if (group->xas_selected) + group = gf_list_get(group->xas_groups, group->xas_selected-1); + group->xas_force_switch = j+1; + group->force_representation_idx_plus_one = i+1; + group->force_switch_bandwidth = 1; + return GF_OK; + } + } + } + } return GF_BAD_PARAM; } @@ -10461,7 +11186,16 @@ { GF_DASH_Group *group = gf_list_get(dash->groups, idx); if (!group) return -1; - return group->active_rep_index; + + if (!group->xas_selected) return group->active_rep_index; + u32 qidx = gf_list_count(group->adaptation_set->representations); + u32 i, count = gf_list_count(group->xas_groups); + for (i=0; i<count; i++) { + GF_DASH_Group *agr = gf_list_get(group->xas_groups, i); + if (i+1 == group->xas_selected) return qidx + group->active_rep_index; + qidx += gf_list_count(agr->adaptation_set->representations); + } + return qidx; } GF_EXPORT @@ -10481,7 +11215,7 @@ GF_EXPORT void gf_dash_group_set_codec_stat(GF_DashClient *dash, u32 idx, u32 avg_dec_time, u32 max_dec_time, u32 irap_avg_dec_time, u32 irap_max_dec_time, Bool codec_reset, Bool decode_only_rap) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return; group->avg_dec_time = avg_dec_time; group->max_dec_time = max_dec_time; @@ -10494,7 +11228,7 @@ GF_EXPORT void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return; group->buffer_min_ms = buffer_min_ms; group->buffer_max_ms = buffer_max_ms; @@ -10595,15 +11329,15 @@ } GF_EXPORT -void gf_dash_set_route_ast_shift(GF_DashClient *dash, s32 ast_shift) +void gf_dash_set_mcast_ast_shift(GF_DashClient *dash, s32 ast_shift) { - dash->route_ast_shift = ast_shift; + dash->mcast_ast_shift = ast_shift; } GF_EXPORT GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 idx, u32 max_buffer_playout_ms) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return GF_BAD_PARAM; group->max_buffer_playout_ms = max_buffer_playout_ms; return GF_OK; @@ -10612,7 +11346,7 @@ GF_EXPORT GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 idx, u32 quality_degradation_hint) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return GF_BAD_PARAM; group->quality_degradation_hint = quality_degradation_hint; @@ -10672,7 +11406,7 @@ GF_Err gf_dash_group_set_visible_rect(GF_DashClient *dash, u32 idx, u32 min_x, u32 max_x, u32 min_y, u32 max_y, Bool is_gaze) { u32 i, count; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return GF_BAD_PARAM; if (!min_x && !max_x && !min_y && !max_y) { @@ -10705,7 +11439,7 @@ u32 resume_from_dep_group, i; char *key_url, *url; GF_DASH_Group *base_group; - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return; //we forced early fetch because demux was empty, consider all errors as 404 @@ -10780,7 +11514,7 @@ void gf_dash_group_store_stats(GF_DashClient *dash, u32 idx, u32 dep_rep_idx, u32 bytes_per_sec, u64 file_size, Bool is_broadcast, u64 us_since_start) { - GF_DASH_Group *group = gf_list_get(dash->groups, idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, idx); if (!group) return; if (!group->nb_cached_segments) return; @@ -10835,9 +11569,7 @@ GF_EXPORT s32 gf_dash_group_get_as_id(GF_DashClient *dash, u32 group_idx) { - GF_DASH_Group *group; - if (!dash) return 0; - group = gf_list_get(dash->groups, group_idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, group_idx); if (!group) return 0; return group->adaptation_set->id; } @@ -10854,7 +11586,7 @@ if (!dash->is_smooth) return GF_OK; if (dash->mpd->type != GF_MPD_TYPE_DYNAMIC) return GF_OK; - group = gf_list_get(dash->groups, group_idx); + group = gf_dash_get_active_group(dash, group_idx); if (!group) return GF_BAD_PARAM; if (!group->adaptation_set || !group->adaptation_set->segment_template || !group->adaptation_set->segment_template->segment_timeline) return GF_BAD_PARAM; @@ -10908,9 +11640,7 @@ Bool gf_dash_group_has_init_segment(GF_DashClient *dash, u32 group_idx) { - GF_DASH_Group *group; - if (!dash) return GF_FALSE; - group = gf_list_get(dash->groups, group_idx); + GF_DASH_Group *group = gf_dash_get_active_group(dash, group_idx); if (!group) return GF_FALSE; if (group->no_init_seg) return GF_FALSE; return GF_TRUE;
View file
gpac-2.4.0.tar.gz/src/media_tools/dash_segmenter.c -> gpac-26.02.0.tar.gz/src/media_tools/dash_segmenter.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre , Cyril Concolato - * Copyright (c) Telecom ParisTech 2000-2022 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / Media Tools sub-project @@ -603,7 +603,8 @@ if (dasher->dash_mode) { e |= gf_dynstrcat(&args, (dasher->dash_mode == GF_DASH_DYNAMIC_LAST) ? "dynlast" : "dynamic", ":"); //make dasher reschedule by default for MP4Box - e |= gf_dynstrcat(&args, "reschedule", ":"); + if (dasher->dash_state || dasher->sub_duration) + e |= gf_dynstrcat(&args, "reschedule", ":"); } if (dasher->disable_segment_alignment) e |= gf_dynstrcat(&args, "!align", ":"); if (dasher->enable_mix_codecs) e |= gf_dynstrcat(&args, "mix_codecs", ":"); @@ -795,6 +796,16 @@ if (dasher->samplegroups_in_traf) e |= gf_dynstrcat(&args, "sgpd_traf", ":"); + //if llhls or asto is specified in manifest name or globally, disable subsegs_per_sidx + if (sep_ext && !dasher->subsegs_per_sidx && ( + strstr(sep_ext+1, "llhls") + || strstr(sep_ext+1, "asto") + || gf_sys_find_global_arg("llhls") + || gf_sys_find_global_arg("asto") + )) { + dasher->enable_sidx = 0; + } + if (dasher->enable_sidx) { sprintf(szArg, "subs_sidx=%d", dasher->subsegs_per_sidx ); e |= gf_dynstrcat(&args, szArg, ":"); @@ -922,7 +933,7 @@ for (i=0; i<count; i++) { u32 j; - char szSourceID100; + char szSourceID100, *source_id=NULL; GF_Filter *src = NULL; GF_Filter *rt = NULL; const char *url = NULL; @@ -961,15 +972,18 @@ else { sprintf(szSourceID, "%s", frag_val); } - //we need tkid for demuxers able to fetch specific tracks (eg isobmf) - sprintf(szArg, "tkid=%s", frag_val); - e |= gf_dynstrcat(&args, szArg, ":"); + if (fID || !strcmp(frag_val, "audio") || !strcmp(frag_val, "video") || (strlen(frag_val)==4)) { + //we set tkid for demuxers able to fetch specific tracks (eg isobmf) + sprintf(szArg, "tkid=%s", frag_val); + e |= gf_dynstrcat(&args, szArg, ":"); + } } else if (di->track_id) { sprintf(szSourceID, "PID=%d", di->track_id); - //we need tkid for isobmf + //we set tkid for isobmf sprintf(szArg, "tkid=%d", di->track_id); e |= gf_dynstrcat(&args, szArg, ":"); } + if (szSourceID0) source_id = szSourceID; if (di->source_opts) { e |= gf_dynstrcat(&args, di->source_opts, ":"); @@ -1111,14 +1125,17 @@ return e; } + //source_id fragment only applies to first filter in chain, reset it after each set_source + if (rt) { - gf_filter_set_source(rt, src, NULL); + gf_filter_set_source(rt, src, source_id); src = rt; + source_id = NULL; } if (!di->filter_chain) { //assign this source - gf_filter_set_source(dasher->output, src, szSourceID0 ? szSourceID : NULL); + gf_filter_set_source(dasher->output, src, source_id); continue; } //create the filter chain between source (or rt if it was set) and dasher @@ -1151,7 +1168,8 @@ return e; } if (prev_filter) { - gf_filter_set_source(f, prev_filter, NULL); + gf_filter_set_source(f, prev_filter, source_id); + source_id=NULL; } prev_filter = f; if (!sep) break; @@ -1159,15 +1177,16 @@ if (old_syntax || end_of_sub_chain) { fargs = sep+2; if (end_of_sub_chain && prev_filter) { - gf_filter_set_source(dasher->output, prev_filter, NULL); + gf_filter_set_source(dasher->output, prev_filter, source_id); prev_filter = src; + source_id = NULL; } } else { fargs = sep+1; } } if (prev_filter) { - gf_filter_set_source(dasher->output, prev_filter, szSourceID0 ? szSourceID : NULL); + gf_filter_set_source(dasher->output, prev_filter, source_id); } }
View file
gpac-2.4.0.tar.gz/src/media_tools/dvb_mpe.c -> gpac-26.02.0.tar.gz/src/media_tools/dvb_mpe.c
Changed
@@ -657,7 +657,7 @@ switch (id) { - case GF_M2TS_DVB_TARGET_IP_SLASH_DESCRIPTOR: + case GF_M2TS_PRIVATE_DATA_INDICATOR_DESCRIPTOR: { gf_m2ts_target_ip(ip_str,data);
View file
gpac-2.4.0.tar.gz/src/media_tools/gpac_ogg.c -> gpac-26.02.0.tar.gz/src/media_tools/gpac_ogg.c
Changed
@@ -373,8 +373,8 @@ /* not the main path */ ret=(u32) (-1); - gf_fatal_assert(b->endbyte <= GF_INT_MAX/8 - bits); - if(b->endbyte*8+bits>b->storage*8)goto overflow; + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + else if (!bits) return 0; } ret=b->ptr0>>b->endbit; @@ -410,7 +410,8 @@ if(b->endbyte+4>=b->storage) { /* not the main path */ ret=(u32) (-1); - if(b->endbyte*8+bits>b->storage*8)goto overflow; + if (b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + else if (!bits) return 0; } ret = ((u32)b->ptr0) << (24+b->endbit);
View file
gpac-26.02.0.tar.gz/src/media_tools/id3.c
Added
@@ -0,0 +1,157 @@ +#include <gpac/id3.h> + +// First 36 bytes of a Nielsen ID3 tag: "ID3\x04\x00 \x00\x00\x02\x05PRIV\x00\x00\x01{\x00\x00www.nielsen.com/" +static const u32 NIELSEN_ID3_TAG_PREFIX_LEN = 36; +static const u8 NIELSEN_ID3_TAG_PREFIX = {0x49, 0x44, 0x33, 0x04, 0x00, 0x20, + 0x00, 0x00, 0x02, 0x05, 0x50, 0x52, + 0x49, 0x56, 0x00, 0x00, 0x01, 0x7B, + 0x00, 0x00, 0x77, 0x77, 0x77, 0x2E, + 0x6E, 0x69, 0x65, 0x6C, 0x73, 0x65, + 0x6E, 0x2E, 0x63, 0x6F, 0x6D, 0x2F}; + +static const char *ID3_PROP_SCHEME_URI = "https://aomedia.org/emsg/ID3"; +static const char *ID3_PROP_VALUE_URI_NIELSEN = "www.nielsen.com:id3:v1"; +static const char *ID3_PROP_VALUE_URI_DEFAULT = "https://aomedia.org/emsg/ID3"; + +GF_EXPORT +GF_Err gf_id3_tag_new(GF_ID3_TAG *tag, u32 timescale, u64 pts, u8 *data, u32 data_length) +{ + if (!tag) { + return GF_BAD_PARAM; + } + + if (data == NULL || data_length == 0) { + return GF_BAD_PARAM; + } + + tag->timescale = timescale; + tag->pts = pts; + tag->scheme_uri = gf_strdup(ID3_PROP_SCHEME_URI); + tag->scheme_uri_length = (u32) strlen(ID3_PROP_SCHEME_URI) + 1; // plus null character + + // test if the data is a Nielsen ID3 tag + if ((data_length >= NIELSEN_ID3_TAG_PREFIX_LEN) && !memcmp(data, NIELSEN_ID3_TAG_PREFIX, NIELSEN_ID3_TAG_PREFIX_LEN)) + { + tag->value_uri = gf_strdup(ID3_PROP_VALUE_URI_NIELSEN); + } + else + { + tag->value_uri = gf_strdup(ID3_PROP_VALUE_URI_DEFAULT); + } + + tag->value_uri_length = (u32) strlen(tag->value_uri) + 1; // plus null character + + tag->data_length = data_length; + tag->data = gf_malloc(data_length); + memcpy(tag->data, data, data_length); + + return GF_OK; +} + +GF_EXPORT +void gf_id3_tag_free(GF_ID3_TAG *tag) +{ + if (!tag) { + return; + } + + if (tag->scheme_uri) { + gf_free(tag->scheme_uri); + tag->scheme_uri = NULL; + } + + if (tag->value_uri) { + gf_free(tag->value_uri); + tag->value_uri = NULL; + } + + if (tag->data) { + gf_free(tag->data); + tag->data = NULL; + } +} + +GF_EXPORT +GF_Err gf_id3_to_bitstream(GF_ID3_TAG *tag, GF_BitStream *bs) +{ + if (!tag || !bs) { + return GF_BAD_PARAM; + } + + gf_bs_write_u32(bs, tag->timescale); + gf_bs_write_u64(bs, tag->pts); + + gf_bs_write_u32(bs, tag->scheme_uri_length); + u32 bytes_read = gf_bs_write_data(bs, (const u8 *)tag->scheme_uri, tag->scheme_uri_length); + if (bytes_read != tag->scheme_uri_length) { + return GF_IO_ERR; + } + + gf_bs_write_u32(bs, tag->value_uri_length); + bytes_read = gf_bs_write_data(bs, (const u8 *)tag->value_uri, tag->value_uri_length); + if (bytes_read != tag->value_uri_length) { + return GF_IO_ERR; + } + + gf_bs_write_u32(bs, tag->data_length); + bytes_read = gf_bs_write_data(bs, tag->data, tag->data_length); + if (bytes_read != tag->data_length) { + return GF_IO_ERR; + } + + return GF_OK; +} + +GF_EXPORT +GF_Err gf_id3_list_to_bitstream(GF_List *tag_list, GF_BitStream *bs) { + + GF_Err err = GF_OK; + + // first, write the number of tags + u32 id3_count = gf_list_count(tag_list); + gf_bs_write_u32(bs, id3_count); + + for (u32 i = 0; i < id3_count; ++i) { + GF_ID3_TAG *tag = gf_list_get(tag_list, i); + err = gf_id3_to_bitstream(tag, bs); + if (err != GF_OK) { + return err; + } + } + + return err; +} + +GF_EXPORT +GF_Err gf_id3_from_bitstream(GF_ID3_TAG *tag, GF_BitStream *bs) +{ + if (!tag || !bs) { + return GF_BAD_PARAM; + } + + tag->timescale = gf_bs_read_u32(bs); + tag->pts = gf_bs_read_u64(bs); + + tag->scheme_uri_length = gf_bs_read_u32(bs); + tag->scheme_uri = gf_malloc(tag->scheme_uri_length); + u32 bytes_read = gf_bs_read_data(bs, tag->scheme_uri, tag->scheme_uri_length); + if (bytes_read != tag->scheme_uri_length) { + return GF_IO_ERR; + } + + tag->value_uri_length = gf_bs_read_u32(bs); + tag->value_uri = gf_malloc(tag->value_uri_length); + bytes_read = gf_bs_read_data(bs, tag->value_uri, tag->value_uri_length); + if (bytes_read != tag->value_uri_length) { + return GF_IO_ERR; + } + + tag->data_length = gf_bs_read_u32(bs); + tag->data = gf_malloc(tag->data_length); + bytes_read = gf_bs_read_data(bs, tag->data, tag->data_length); + if (bytes_read != tag->data_length) { + return GF_IO_ERR; + } + + return GF_OK; +}
View file
gpac-2.4.0.tar.gz/src/media_tools/img.c -> gpac-26.02.0.tar.gz/src/media_tools/img.c
Changed
@@ -101,12 +101,12 @@ case 0xCD: case 0xCE: case 0xCF: - length = gf_bs_read_u16(bs); - /*prec = */gf_bs_read_u8(bs); + length = gf_bs_read_u16(bs); + /*prec = */gf_bs_read_u8(bs); h = gf_bs_read_int(bs, 16); w = gf_bs_read_int(bs, 16); - nb_comp = gf_bs_read_int(bs, 8); - if (length != 8 + 3*nb_comp) continue; //This is not the right marker + nb_comp = gf_bs_read_int(bs, 8); + if (length != 8 + 3*nb_comp) continue; //This is not the right marker if ((w > *width) || (h > *height)) { *width = w; *height = h;
View file
gpac-2.4.0.tar.gz/src/media_tools/isom_hinter.c -> gpac-26.02.0.tar.gz/src/media_tools/isom_hinter.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / Media Tools sub-project @@ -53,7 +53,7 @@ for (i=0; i<count; i++) { samp = gf_isom_get_sample_info(file, Track, i+1, NULL, NULL); if (!samp) break; - + //get the size *avgSize += samp->dataLength; if (*MaxSize < samp->dataLength) *MaxSize = samp->dataLength; @@ -459,6 +459,7 @@ PL_ID = 0x0F; gf_odf_avc_cfg_del(avcc); gf_odf_avc_cfg_del(svcc); + gf_odf_avc_cfg_del(mvcc); } break; case GF_ISOM_SUBTYPE_HVC1: @@ -857,7 +858,7 @@ return GF_OK; } -static u32 write_nalu_config_array(char *sdpLine, GF_List *nalus) +static u32 write_nalu_config_array(char **sdpLine, GF_List *nalus) { u32 i, count, b64s; char b64200; @@ -867,13 +868,13 @@ GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(nalus, i); b64s = gf_base64_encode(sl->data, sl->size, b64, 200); b64b64s=0; - strcat(sdpLine, b64); - if (i+1<count) strcat(sdpLine, ","); + gf_dynstrcat(sdpLine, b64, NULL); + if (i+1<count) gf_dynstrcat(sdpLine, ",", NULL); } return count; } -static void write_avc_config(char *sdpLine, GF_AVCConfig *avcc, GF_AVCConfig *svcc) +static void write_avc_config(char **sdpLine, GF_AVCConfig *avcc, GF_AVCConfig *svcc) { u32 count = 0; @@ -881,26 +882,26 @@ if (svcc) count += gf_list_count(svcc->sequenceParameterSets) + gf_list_count(svcc->pictureParameterSets); if (!count) return; - strcat(sdpLine, "; sprop-parameter-sets="); + gf_dynstrcat(sdpLine, "; sprop-parameter-sets=", NULL); if (avcc) { count = write_nalu_config_array(sdpLine, avcc->sequenceParameterSets); - if (count) strcat(sdpLine, ","); + if (count) gf_dynstrcat(sdpLine, ",", NULL); count = write_nalu_config_array(sdpLine, avcc->sequenceParameterSetExtensions); - if (count) strcat(sdpLine, ","); + if (count) gf_dynstrcat(sdpLine, ",", NULL); count = write_nalu_config_array(sdpLine, avcc->pictureParameterSets); - if (count) strcat(sdpLine, ","); + if (count) gf_dynstrcat(sdpLine, ",", NULL); } if (svcc) { count = write_nalu_config_array(sdpLine, svcc->sequenceParameterSets); - if (count) strcat(sdpLine, ","); + if (count) gf_dynstrcat(sdpLine, ",", NULL); count = write_nalu_config_array(sdpLine, svcc->pictureParameterSets); - if (count) strcat(sdpLine, ","); + if (count) gf_dynstrcat(sdpLine, ",", NULL); } - count = (u32) strlen(sdpLine); - if (sdpLinecount-1 == ',') - sdpLinecount-1 = 0; + count = (u32) strlen(*sdpLine); + if (count && (*sdpLine)count-1 == ',') + (*sdpLine)count-1 = 0; } GF_EXPORT @@ -908,10 +909,11 @@ { u32 Width, Height; GF_ESD *esd; - char sdpLine20000; + char tmp_buf101; char mediaName30, payloadName30; u32 mtype; + tmp_buf100=0; Width = Height = 0; gf_isom_sdp_clean_track(tkHint->file, tkHint->TrackNum); mtype = gf_isom_get_media_type(tkHint->file, tkHint->TrackNum); @@ -921,36 +923,36 @@ gf_rtp_builder_get_payload_name(tkHint->rtp_p, payloadName, mediaName); /*TODO- extract out of rtp_p for future live tools*/ - sprintf(sdpLine, "m=%s 0 RTP/%s %d", mediaName, tkHint->rtp_p->slMap.IV_length ? "SAVP" : "AVP", tkHint->rtp_p->PayloadType); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "m=%s 0 RTP/%s %d", mediaName, tkHint->rtp_p->slMap.IV_length ? "SAVP" : "AVP", tkHint->rtp_p->PayloadType); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); if (tkHint->bandwidth) { - sprintf(sdpLine, "b=AS:%d", tkHint->bandwidth); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "b=AS:%d", tkHint->bandwidth); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } if (tkHint->nb_chan) { - sprintf(sdpLine, "a=rtpmap:%d %s/%d/%d", tkHint->rtp_p->PayloadType, payloadName, tkHint->rtp_p->sl_config.timestampResolution, tkHint->nb_chan); + snprintf(tmp_buf, 100, "a=rtpmap:%d %s/%d/%d", tkHint->rtp_p->PayloadType, payloadName, tkHint->rtp_p->sl_config.timestampResolution, tkHint->nb_chan); } else { - sprintf(sdpLine, "a=rtpmap:%d %s/%d", tkHint->rtp_p->PayloadType, payloadName, tkHint->rtp_p->sl_config.timestampResolution); + snprintf(tmp_buf, 100, "a=rtpmap:%d %s/%d", tkHint->rtp_p->PayloadType, payloadName, tkHint->rtp_p->sl_config.timestampResolution); } - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); /*control for MPEG-4*/ if (AddSystemInfo) { - sprintf(sdpLine, "a=mpeg4-esid:%d", gf_isom_get_track_id(tkHint->file, tkHint->TrackNum)); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "a=mpeg4-esid:%d", gf_isom_get_track_id(tkHint->file, tkHint->TrackNum)); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } /*control for QTSS/DSS*/ - sprintf(sdpLine, "a=control:trackID=%d", gf_isom_get_track_id(tkHint->file, tkHint->HintTrack)); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "a=control:trackID=%d", gf_isom_get_track_id(tkHint->file, tkHint->HintTrack)); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); /*H263 extensions*/ if (tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_H263) { - sprintf(sdpLine, "a=cliprect:0,0,%d,%d", Height, Width); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "a=cliprect:0,0,%d,%d", Height, Width); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } /*AMR*/ else if ((tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_AMR) || (tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_AMR_WB)) { - sprintf(sdpLine, "a=fmtp:%d octet-align=1", tkHint->rtp_p->PayloadType); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "a=fmtp:%d octet-align=1", tkHint->rtp_p->PayloadType); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } /*Text*/ else if (tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_3GPP_TEXT) { @@ -976,9 +978,9 @@ } } - gf_media_format_ttxt_sdp(tkHint->rtp_p, payloadName, sdpLine, w, h, tx, ty, l, m_w, m_h, NULL); - - strcat(sdpLine, "; tx3g="); + char *sdp_line = NULL; + gf_media_format_ttxt_sdp(tkHint->rtp_p, payloadName, &sdp_line, w, h, tx, ty, l, m_w, m_h, NULL); + gf_dynstrcat(&sdp_line, "; tx3g=", NULL); for (i=0; i<gf_isom_get_sample_description_count(tkHint->file, tkHint->TrackNum); i++) { u8 *tx3g; GF_Err e; @@ -987,20 +989,22 @@ e = gf_isom_text_get_encoded_tx3g(tkHint->file, tkHint->TrackNum, i+1, GF_RTP_TX3G_SIDX_OFFSET, &tx3g, &tx3g_len); if (e) { if (i) continue; + if (sdp_line) gf_free(sdp_line); return GF_ISOM_INVALID_FILE; } len = gf_base64_encode(tx3g, tx3g_len, buffer, 2000); gf_free(tx3g); bufferlen = 0; - if (i) strcat(sdpLine, ", "); - strcat(sdpLine, buffer); + if (i) gf_dynstrcat(&sdp_line, ", ", NULL); + gf_dynstrcat(&sdp_line, buffer, NULL); } - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdp_line); + gf_free(sdp_line); } /*EVRC/SMV in non header-free mode*/ else if ((tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_EVRC_SMV) && (tkHint->rtp_p->auh_size>1)) { - sprintf(sdpLine, "a=fmtp:%d maxptime=%d", tkHint->rtp_p->PayloadType, tkHint->rtp_p->auh_size*20); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "a=fmtp:%d maxptime=%d", tkHint->rtp_p->PayloadType, tkHint->rtp_p->auh_size*20); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } /*H264/AVC*/ else if ((tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_H264_AVC) || (tkHint->rtp_p->rtp_payt == GF_RTP_PAYT_H264_SVC)) { @@ -1009,16 +1013,17 @@ /*TODO - check syntax for SVC (might be some extra signaling)*/ if (avcc) { - sprintf(sdpLine, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", tkHint->rtp_p->PayloadType, avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication); + snprintf(tmp_buf, 100, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", tkHint->rtp_p->PayloadType, avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication); } else { if (!svcc) return GF_ISOM_INVALID_FILE; - sprintf(sdpLine, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", tkHint->rtp_p->PayloadType, svcc->AVCProfileIndication, svcc->profile_compatibility, svcc->AVCLevelIndication); + snprintf(tmp_buf, 100, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", tkHint->rtp_p->PayloadType, svcc->AVCProfileIndication, svcc->profile_compatibility, svcc->AVCLevelIndication); } - write_avc_config(sdpLine, avcc, svcc); - - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + char *sdp_line = gf_strdup(tmp_buf); + write_avc_config(&sdp_line, avcc, svcc); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdp_line); + gf_free(sdp_line); gf_odf_avc_cfg_del(avcc); gf_odf_avc_cfg_del(svcc); } @@ -1026,27 +1031,29 @@ else if (tkHint->rtp_p->rtp_payt==GF_RTP_PAYT_MPEG4) { GF_Err e; esd = gf_isom_get_esd(tkHint->file, tkHint->TrackNum, 1); - + char *sdp = NULL; if (esd && esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { - e = gf_rtp_builder_format_sdp(tkHint->rtp_p, payloadName, sdpLine, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); + e = gf_rtp_builder_format_sdp(tkHint->rtp_p, payloadName, &sdp, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); } else { - e = gf_rtp_builder_format_sdp(tkHint->rtp_p, payloadName, sdpLine, NULL, 0); + e = gf_rtp_builder_format_sdp(tkHint->rtp_p, payloadName, &sdp, NULL, 0); } if (esd) gf_odf_desc_del((GF_Descriptor *)esd); - if (e) return e; - + if (e) { + if (sdp) gf_free(sdp); + return e; + } if (tkHint->rtp_p->slMap.IV_length) { const char *kms; gf_isom_get_ismacryp_info(tkHint->file, tkHint->TrackNum, 1, NULL, NULL, NULL, NULL, &kms, NULL, NULL, NULL); if (!strnicmp(kms, "(key)", 5) || !strnicmp(kms, "(ipmp)", 6) || !strnicmp(kms, "(uri)", 5)) { - strcat(sdpLine, "; ISMACrypKey="); + gf_dynstrcat(&sdp, "; ISMACrypKey=", NULL); } else { - strcat(sdpLine, "; ISMACrypKey=(uri)"); + gf_dynstrcat(&sdp, "; ISMACrypKey=(uri)", NULL); } - strcat(sdpLine, kms); + gf_dynstrcat(&sdp, kms, NULL); } - - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdp); + if (sdp) gf_free(sdp); } /*MPEG-4 Audio LATM*/ else if (tkHint->rtp_p->rtp_payt==GF_RTP_PAYT_LATM) { @@ -1078,9 +1085,11 @@ gf_bs_get_content(bs, &config_bytes, &config_size); gf_bs_del(bs); - gf_rtp_builder_format_sdp(tkHint->rtp_p, payloadName, sdpLine, config_bytes, config_size); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + char *sdp = NULL; + gf_rtp_builder_format_sdp(tkHint->rtp_p, payloadName, &sdp, config_bytes, config_size); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdp); gf_free(config_bytes); + if (sdp) gf_free(sdp); } #if GPAC_ENABLE_3GPP_DIMS_RTP /*3GPP DIMS*/ @@ -1089,51 +1098,53 @@ gf_isom_get_visual_info(tkHint->file, tkHint->TrackNum, 1, &Width, &Height); gf_isom_get_dims_description(tkHint->file, tkHint->TrackNum, 1, &dims); - sprintf(sdpLine, "a=fmtp:%d Version-profile=%d", tkHint->rtp_p->PayloadType, dims.profile); + char *sdp = NULL; + sprintf(tmp_buf, "a=fmtp:%d Version-profile=%d", tkHint->rtp_p->PayloadType, dims.profile); + gf_dynstrcat(&sdp, tmp_buf, NULL); if (! dims.fullRequestHost) { - char fmt200; - strcat(sdpLine, ";useFullRequestHost=0"); - sprintf(fmt, ";pathComponents=%d", dims.pathComponents); - strcat(sdpLine, fmt); + gf_dynstrcat(&sdp, ";useFullRequestHost=0", NULL); + sprintf(tmp_buf, ";pathComponents=%d", dims.pathComponents); + gf_dynstrcat(&sdp, tmp_buf, NULL); } - if (!dims.streamType) strcat(sdpLine, ";stream-type=secondary"); - if (dims.containsRedundant == 1) strcat(sdpLine, ";contains-redundant=main"); - else if (dims.containsRedundant == 2) strcat(sdpLine, ";contains-redundant=redundant"); + if (!dims.streamType) gf_dynstrcat(&sdp, ";stream-type=secondary", NULL); + if (dims.containsRedundant == 1) gf_dynstrcat(&sdp, ";contains-redundant=main", NULL); + else if (dims.containsRedundant == 2) gf_dynstrcat(&sdp, ";contains-redundant=redundant", NULL); if (dims.textEncoding && strlen(dims.textEncoding)) { - strcat(sdpLine, ";text-encoding="); - strcat(sdpLine, dims.textEncoding); + gf_dynstrcat(&sdp, ";text-encoding=", NULL); + gf_dynstrcat(&sdp, dims.textEncoding, NULL); } if (dims.contentEncoding && strlen(dims.contentEncoding)) { - strcat(sdpLine, ";content-coding="); - strcat(sdpLine, dims.contentEncoding); + gf_dynstrcat(&sdp, ";content-coding=", NULL); + gf_dynstrcat(&sdp, dims.contentEncoding, NULL); } if (dims.contentEncoding && dims.content_script_types && strlen(dims.content_script_types) ) { - strcat(sdpLine, ";content-script-types="); - strcat(sdpLine, dims.contentEncoding); + gf_dynstrcat(&sdp, ";content-script-types=", NULL); + gf_dynstrcat(&sdp, dims.contentEncoding, NULL); } - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdp); + if (sdp) gf_free(sdp); } #endif /*extensions for some mobile phones*/ if (Width && Height) { - sprintf(sdpLine, "a=framesize:%d %d-%d", tkHint->rtp_p->PayloadType, Width, Height); - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + snprintf(tmp_buf, 100, "a=framesize:%d %d-%d", tkHint->rtp_p->PayloadType, Width, Height); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } esd = gf_isom_get_esd(tkHint->file, tkHint->TrackNum, 1); if (esd && esd->decoderConfig && (esd->decoderConfig->rvc_config || esd->decoderConfig->predefined_rvc_config)) { if (esd->decoderConfig->predefined_rvc_config) { - sprintf(sdpLine, "a=rvc-config-predef:%d", esd->decoderConfig->predefined_rvc_config); + snprintf(tmp_buf, 100, "a=rvc-config-predef:%d", esd->decoderConfig->predefined_rvc_config); } else { /*temporary ...*/ if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_AVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_SVC)) { - sprintf(sdpLine, "a=rvc-config:%s", "http://download.tsi.telecom-paristech.fr/gpac/RVC/rvc_config_avc.xml"); + snprintf(tmp_buf, 100, "a=rvc-config:%s", "http://download.tsi.telecom-paristech.fr/gpac/RVC/rvc_config_avc.xml"); } else { - sprintf(sdpLine, "a=rvc-config:%s", "http://download.tsi.telecom-paristech.fr/gpac/RVC/rvc_config_sp.xml"); + snprintf(tmp_buf, 100, "a=rvc-config:%s", "http://download.tsi.telecom-paristech.fr/gpac/RVC/rvc_config_sp.xml"); } } - gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, sdpLine); + gf_isom_sdp_add_track_line(tkHint->file, tkHint->HintTrack, tmp_buf); } if (esd) gf_odf_desc_del((GF_Descriptor *)esd); @@ -1175,22 +1186,24 @@ GF_ISOSample *samp; Bool remove_ocr; u8 *buffer; - char buf645000, sdpLine5100; + char tmp_buf201; +// char buf645000, sdpLine5100; gf_isom_sdp_clean(file); + tmp_buf200 = 0; if (bandwidth) { - sprintf(buf64, "b=AS:%d", bandwidth); - gf_isom_sdp_add_line(file, buf64); + snprintf(tmp_buf, 200, "b=AS:%d", bandwidth); + gf_isom_sdp_add_line(file, tmp_buf); } //xtended attribute for copyright if (gf_sys_is_test_mode()) { - sprintf(buf64, "a=x-copyright: %s", "MP4/3GP File hinted with GPAC - (c) Telecom ParisTech (http://gpac.io)"); + snprintf(tmp_buf, 200, "a=x-copyright: %s", "MP4/3GP File hinted with GPAC - (c) Telecom ParisTech (http://gpac.io)"); } else { - sprintf(buf64, "a=x-copyright: MP4/3GP File hinted with GPAC %s - %s", gf_gpac_version(), gf_gpac_copyright() ); + snprintf(tmp_buf, 200, "a=x-copyright: MP4/3GP File hinted with GPAC %s - %s", gf_gpac_version(), gf_gpac_copyright() ); } - gf_isom_sdp_add_line(file, buf64); + gf_isom_sdp_add_line(file, tmp_buf); if (IOD_Profile == GF_SDP_IOD_NONE) return GF_OK; @@ -1233,7 +1246,7 @@ /*get OD esd, and embbed stream data if possible*/ if (odT) { esd = gf_isom_get_esd(file, odT, 1); - if (gf_isom_get_sample_count(file, odT)==1) { + if (esd && gf_isom_get_sample_count(file, odT)==1) { samp = gf_isom_get_sample(file, odT, 1, &descIndex); if (samp && gf_hinter_can_embbed_data(samp->data, samp->dataLength, GF_STREAM_OD)) { InitSL_NULL(&slc); @@ -1246,33 +1259,35 @@ //set the SL for future extraction gf_isom_set_extraction_slc(file, odT, 1, &slc); - size64 = gf_base64_encode(samp->data, samp->dataLength, buf64, 2000); - buf64size64 = 0; - sprintf(sdpLine, "data:application/mpeg4-od-au;base64,%s", buf64); - + u32 len_prfx = (u32) strlen("data:application/mpeg4-od-au;base64,"); + esd->URLString = gf_malloc(1 + len_prfx + samp->dataLength*3); + if (esd->URLString) { + strcpy(esd->URLString, "data:application/mpeg4-od-au;base64,"); + size64 = gf_base64_encode(samp->data, samp->dataLength, esd->URLString+len_prfx, samp->dataLength*3); + esd->URLStringlen_prfx + size64 = 0; + } if (esd->decoderConfig) { esd->decoderConfig->avgBitrate = 0; esd->decoderConfig->bufferSizeDB = samp->dataLength; esd->decoderConfig->maxBitrate = 0; } - size64 = (u32) strlen(sdpLine)+1; - esd->URLString = (char*)gf_malloc(sizeof(char) * size64); - strcpy(esd->URLString, sdpLine); } else { GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("rtp hinter OD sample too large to be embedded in IOD - ISMA disabled\n")); is_ok = 0; } gf_isom_sample_del(&samp); } - if (remove_ocr) esd->OCRESID = 0; - else if (esd->OCRESID == esd->ESID) esd->OCRESID = 0; + if (esd) { + if (remove_ocr) esd->OCRESID = 0; + else if (esd->OCRESID == esd->ESID) esd->OCRESID = 0; - //OK, add this to our IOD - gf_list_add(iod->ESDescriptors, esd); + //OK, add this to our IOD + gf_list_add(iod->ESDescriptors, esd); + } } esd = gf_isom_get_esd(file, sceneT, 1); - if (gf_isom_get_sample_count(file, sceneT)==1) { + if (esd && gf_isom_get_sample_count(file, sceneT)==1) { samp = gf_isom_get_sample(file, sceneT, 1, &descIndex); if (samp && gf_hinter_can_embbed_data(samp->data, samp->dataLength, GF_STREAM_SCENE)) { InitSL_NULL(&slc); @@ -1283,17 +1298,18 @@ //set the SL for future extraction gf_isom_set_extraction_slc(file, sceneT, 1, &slc); //encode in Base64 the sample - size64 = gf_base64_encode(samp->data, samp->dataLength, buf64, 2000); - buf64size64 = 0; - sprintf(sdpLine, "data:application/mpeg4-bifs-au;base64,%s", buf64); - + u32 len_prfx = (u32) strlen("data:application/mpeg4-bifs-au;base64,"); + esd->URLString = gf_malloc(1 + len_prfx + samp->dataLength*3); + if (esd->URLString) { + strcpy(esd->URLString, "data:application/mpeg4-bifs-au;base64,"); + size64 = gf_base64_encode(samp->data, samp->dataLength, esd->URLString+len_prfx, samp->dataLength*3); + esd->URLStringlen_prfx + size64 = 0; + } if (esd->decoderConfig) { esd->decoderConfig->avgBitrate = 0; esd->decoderConfig->bufferSizeDB = samp->dataLength; esd->decoderConfig->maxBitrate = 0; } - esd->URLString = (char*)gf_malloc(sizeof(char) * (strlen(sdpLine)+1)); - strcpy(esd->URLString, sdpLine); } else { GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("rtp hinter Scene description sample too large to be embedded in IOD - ISMA disabled\n")); is_ok = 0; @@ -1327,8 +1343,8 @@ } /*only 1 MPEG-4 visual max and 1 MPEG-4 audio max for ISMA compliancy*/ if (!has_v && !has_a && (has_i_v<=1) && (has_i_a<=1)) { - sprintf(sdpLine, "a=isma-compliance:1,1.0,1"); - gf_isom_sdp_add_line(file, sdpLine); + snprintf(tmp_buf, 200, "a=isma-compliance:1,1.0,1"); + gf_isom_sdp_add_line(file, tmp_buf); } } } @@ -1340,13 +1356,18 @@ gf_odf_desc_del((GF_Descriptor *)iod); //encode in Base64 the iod - size64 = gf_base64_encode(buffer, size, buf64, 2000); - buf64size64 = 0; + u32 len_prfx = (u32) strlen("a=mpeg4-iod:\"data:application/mpeg4-iod;base64,"); + u8 *buf64 = gf_malloc(size*3+4+len_prfx); + if (buf64) { + strcpy(buf64, "a=mpeg4-iod:\"data:application/mpeg4-iod;base64,"); + size64 = gf_base64_encode(buffer, size, buf64+len_prfx, size*3); + buf64len_prfx + size64 = '"'; + buf64len_prfx + size64+1 = 0; + gf_isom_sdp_add_line(file, buf64); + gf_free(buf64); + } gf_free(buffer); - sprintf(sdpLine, "a=mpeg4-iod:\"data:application/mpeg4-iod;base64,%s\"", buf64); - gf_isom_sdp_add_line(file, sdpLine); - return GF_OK; }
View file
gpac-2.4.0.tar.gz/src/media_tools/isom_tools.c -> gpac-26.02.0.tar.gz/src/media_tools/isom_tools.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / Media Tools sub-project @@ -292,7 +292,7 @@ #endif in = gf_fopen(file, "rb"); - if (!in) return GF_URL_ERROR; + if (!in) return GF_URL_ERROR; size = gf_fsize(in); ctx = gf_sha1_starts(); @@ -431,8 +431,8 @@ mType = gf_isom_get_media_type(mp4file, i+1); switch (mType) { case GF_ISOM_MEDIA_VISUAL: - case GF_ISOM_MEDIA_AUXV: - case GF_ISOM_MEDIA_PICT: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: image_track = 0; if (esd && esd->decoderConfig && ((esd->decoderConfig->objectTypeIndication==GF_CODECID_JPEG) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_PNG)) ) image_track = 1; @@ -739,8 +739,8 @@ stype = gf_isom_get_media_subtype(mp4file, i+1, 1); switch (mType) { case GF_ISOM_MEDIA_VISUAL: - case GF_ISOM_MEDIA_AUXV: - case GF_ISOM_MEDIA_PICT: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: /*remove image tracks if wanted*/ if (gf_isom_get_sample_count(mp4file, i+1)<=1) { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("3GPP convert Visual track ID %d: only one sample found\n", gf_isom_get_track_id(mp4file, i+1) )); @@ -965,32 +965,36 @@ || (stype==GF_ISOM_SUBTYPE_AVC3_H264) || (stype==GF_ISOM_SUBTYPE_AVC4_H264) ) { - AVCState avc; + AVCState *avc_state; GF_AVCConfig *avcc = gf_isom_avc_config_get(file, track, sampleDescriptionIndex); + if (!avcc) return GF_NOT_FOUND; u32 i; s32 idx; GF_NALUFFParam *slc; - memset(&avc, 0, sizeof(AVCState)); - avc.sps_active_idx = -1; + GF_SAFEALLOC(avc_state, AVCState); + if (!avc_state) return GF_OUT_OF_MEM; + avc_state->sps_active_idx = -1; i=0; while ((slc = (GF_NALUFFParam *)gf_list_enum(avcc->sequenceParameterSets, &i))) { - idx = gf_avc_read_sps(slc->data, slc->size, &avc, 0, NULL); + idx = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, NULL); if (idx<0) continue; - if (! avc.spsidx.vui_parameters_present_flag ) + if (! avc_state->spsidx.vui_parameters_present_flag ) continue; - *colour_type = avc.spsidx.vui.video_format; - *colour_primaries = avc.spsidx.vui.colour_primaries; - *transfer_characteristics = avc.spsidx.vui.transfer_characteristics; - *matrix_coefficients = avc.spsidx.vui.matrix_coefficients; - *full_range_flag = avc.spsidx.vui.video_full_range_flag; + *colour_type = avc_state->spsidx.vui.video_format; + *colour_primaries = avc_state->spsidx.vui.colour_primaries; + *transfer_characteristics = avc_state->spsidx.vui.transfer_characteristics; + *matrix_coefficients = avc_state->spsidx.vui.matrix_coefficients; + *full_range_flag = avc_state->spsidx.vui.video_full_range_flag; gf_odf_avc_cfg_del(avcc); + gf_free(avc_state); return GF_OK; } gf_odf_avc_cfg_del(avcc); + gf_free(avc_state); return GF_NOT_FOUND; } if ((stype==GF_ISOM_SUBTYPE_HEV1) @@ -1000,13 +1004,15 @@ || (stype==GF_ISOM_SUBTYPE_LHV1) || (stype==GF_ISOM_SUBTYPE_LHE1) ) { - HEVCState hvc; + HEVCState *hvc_state; GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(file, track, sampleDescriptionIndex); + if (!hvcc) return GF_NOT_FOUND; u32 i; GF_NALUFFParamArray *pa; - memset(&hvc, 0, sizeof(HEVCState)); - hvc.sps_active_idx = -1; + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return GF_OUT_OF_MEM; + hvc_state->sps_active_idx = -1; i=0; while ((pa = (GF_NALUFFParamArray *)gf_list_enum(hvcc->param_array, &i))) { @@ -1017,55 +1023,61 @@ j=0; while ((slc = (GF_NALUFFParam *)gf_list_enum(pa->nalus, &j))) { - idx = gf_hevc_read_sps(slc->data, slc->size, &hvc); + idx = gf_hevc_read_sps(slc->data, slc->size, hvc_state); if (idx<0) continue; - if (! hvc.spsidx.vui_parameters_present_flag) + if (! hvc_state->spsidx.vui_parameters_present_flag) continue; - *colour_type = hvc.spsidx.video_format; - *colour_primaries = hvc.spsidx.colour_primaries; - *transfer_characteristics = hvc.spsidx.transfer_characteristic; - *matrix_coefficients = hvc.spsidx.matrix_coeffs; - *full_range_flag = hvc.spsidx.video_full_range_flag; + *colour_type = hvc_state->spsidx.video_format; + *colour_primaries = hvc_state->spsidx.colour_primaries; + *transfer_characteristics = hvc_state->spsidx.transfer_characteristic; + *matrix_coefficients = hvc_state->spsidx.matrix_coeffs; + *full_range_flag = hvc_state->spsidx.video_full_range_flag; gf_odf_hevc_cfg_del(hvcc); + gf_free(hvc_state); return GF_OK; } } gf_odf_hevc_cfg_del(hvcc); + gf_free(hvc_state); return GF_NOT_FOUND; } if (stype==GF_ISOM_SUBTYPE_AV01) { - AV1State av1; + AV1State *av1_state; - gf_av1_init_state(&av1); - av1.config = gf_isom_av1_config_get(file, track, sampleDescriptionIndex); - if (av1.config) { + GF_SAFEALLOC(av1_state, AV1State); + if (!av1_state) return GF_OUT_OF_MEM; + gf_av1_init_state(av1_state); + av1_state->config = gf_isom_av1_config_get(file, track, sampleDescriptionIndex); + if (av1_state->config) { u32 i; - for (i=0; i<gf_list_count(av1.config->obu_array); i++) { + for (i=0; i<gf_list_count(av1_state->config->obu_array); i++) { GF_BitStream *bs; ObuType obu_type = 0; u32 hdr_size = 0; u64 obu_size = 0; - GF_AV1_OBUArrayEntry *obu = gf_list_get(av1.config->obu_array, i); + GF_AV1_OBUArrayEntry *obu = gf_list_get(av1_state->config->obu_array, i); bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); - gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state); gf_bs_del(bs); - if (av1.color_description_present_flag) { + if (av1_state->color_description_present_flag) { *colour_type = 0; - *colour_primaries = av1.color_primaries; - *transfer_characteristics = av1.transfer_characteristics; - *matrix_coefficients = av1.matrix_coefficients; - *full_range_flag = av1.color_range; - if (av1.config) gf_odf_av1_cfg_del(av1.config); - gf_av1_reset_state(&av1, GF_TRUE); + *colour_primaries = av1_state->color_primaries; + *transfer_characteristics = av1_state->transfer_characteristics; + *matrix_coefficients = av1_state->matrix_coefficients; + *full_range_flag = av1_state->color_range; + if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); return GF_OK; } } } - if (av1.config) gf_odf_av1_cfg_del(av1.config); - gf_av1_reset_state(&av1, GF_TRUE); + if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); return GF_NOT_FOUND; } @@ -1245,6 +1257,7 @@ if (ifps>= 2996 && ifps<=2998) target_ts = 30000; //29.97 else if (ifps>= 2999 && ifps<=3001) target_ts = 3000; //30 else if (ifps>= 2495 && ifps<=2505) target_ts = 2500; //25 + else if (!(def_dur%125) && !(timescale % 2997)) target_ts = 2997; //23.97 else if (ifps >= 2396 && ifps<=2398) target_ts = 24000; //23.97 else if ((ifps>=2399) && (ifps<=2401)) target_ts = 2400; //24 else if (ifps>= 4990 && ifps<=5010) target_ts = 5000; //50 @@ -1329,6 +1342,8 @@ if (subtype == GF_ISOM_SUBTYPE_3GP_DIMS) { GF_BitStream *bs; GF_DIMSDescription dims; + GF_Err e = gf_isom_get_dims_description(mp4, track, 1, &dims); + if (e!=GF_OK) return NULL; esd = gf_odf_desc_esd_new(0); if (!esd) return NULL; esd->slConfig->timestampResolution = gf_isom_get_media_timescale(mp4, track); @@ -1337,7 +1352,6 @@ esd->decoderConfig->streamType = GF_STREAM_SCENE; /*use private DSI*/ esd->decoderConfig->objectTypeIndication = GF_CODECID_DIMS; - gf_isom_get_dims_description(mp4, track, 1, &dims); bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); /*format ext*/ gf_bs_write_u8(bs, dims.profile); @@ -1346,8 +1360,17 @@ gf_bs_write_int(bs, dims.fullRequestHost, 1); gf_bs_write_int(bs, dims.streamType, 1); gf_bs_write_int(bs, dims.containsRedundant, 2); - gf_bs_write_data(bs, (char*)dims.textEncoding, (u32) strlen(dims.textEncoding)+1); - gf_bs_write_data(bs, (char*)dims.contentEncoding, (u32) strlen(dims.contentEncoding)+1); + + if (dims.textEncoding) + gf_bs_write_data(bs, (char*)dims.textEncoding, (u32) strlen(dims.textEncoding)+1); + else + gf_bs_write_data(bs, "", 1); + + if (dims.contentEncoding) + gf_bs_write_data(bs, (char*)dims.contentEncoding, (u32) strlen(dims.contentEncoding)+1); + else + gf_bs_write_data(bs, "", 1); + gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); gf_bs_del(bs); return esd; @@ -1379,11 +1402,16 @@ e = gf_isom_get_meta_item_info(mp4, GF_TRUE, 0, item_idx, &item_id, &item_type, &prot_scheme, &prot_scheme_version, &is_self_ref, &name, &mime, &encoding, &url, &urn); if (e != GF_OK) return NULL; - if (item_type == GF_ISOM_SUBTYPE_HVC1) { - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; + GF_ImageItemProperties *props; + GF_SAFEALLOC(props, GF_ImageItemProperties); + if (!props) return NULL; + esd = gf_odf_desc_esd_new(0); + if (!esd) { + gf_free(props); + return NULL; + } + if (item_type == GF_ISOM_SUBTYPE_HVC1) { if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1391,21 +1419,25 @@ esd->OCRESID = esd->ESID; esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_HEVC; - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, NULL); - if (e == GF_OK && props.config) { - gf_odf_hevc_cfg_write(((GF_HEVCConfigurationBox *)props.config)->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); + if (e == GF_OK && props->config) { + GF_HEVCConfigurationBox *hvcc = props->config; + if (hvcc->type==GF_ISOM_BOX_TYPE_HVCC) { + gf_odf_hevc_cfg_write(hvcc->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + } else { + gf_odf_desc_del((GF_Descriptor*)esd); + gf_free(props); + return NULL; + } } esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } if (item_type == GF_ISOM_SUBTYPE_AVC_H264) { - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; - if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1413,20 +1445,25 @@ esd->OCRESID = esd->ESID; esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_AVC; - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, NULL); - if (e == GF_OK && props.config) { - gf_odf_avc_cfg_write(((GF_AVCConfigurationBox *)props.config)->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); + if (e == GF_OK && props->config) { + GF_AVCConfigurationBox *avcc = props->config; + if (avcc->type==GF_ISOM_BOX_TYPE_AVCC) { + gf_odf_avc_cfg_write(avcc->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + } else { + gf_odf_desc_del((GF_Descriptor*)esd); + gf_free(props); + return NULL; + } } esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } if (item_type == GF_ISOM_SUBTYPE_AV01) { - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1434,21 +1471,25 @@ esd->OCRESID = esd->ESID; esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_AV1; - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, NULL); - if (e == GF_OK && props.config) { - gf_odf_av1_cfg_write( ((GF_AV1ConfigurationBox *)props.config)->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); + if (e == GF_OK && props->config) { + GF_AV1ConfigurationBox *av1c = props->config; + if (av1c->type==GF_ISOM_BOX_TYPE_AV1C) { + gf_odf_av1_cfg_write(av1c->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + } else { + gf_odf_desc_del((GF_Descriptor*)esd); + gf_free(props); + return NULL; + } } esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } if ((item_type == GF_ISOM_SUBTYPE_JPEG) || (mime && !strcmp(mime, "image/jpeg")) ){ - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; - if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1456,21 +1497,18 @@ esd->OCRESID = esd->ESID; esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_JPEG; - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, NULL); - if (e == GF_OK && props.config) { + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); + if (e == GF_OK && props->config) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("JPEG image item decoder config not supported, patch welcome\n")); } esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } if ((item_type == GF_ISOM_SUBTYPE_PNG) || (mime && !strcmp(mime, "image/png")) ){ - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; - if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1478,21 +1516,18 @@ esd->OCRESID = esd->ESID; esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_PNG; - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, NULL); + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); if (e) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Error fetching item properties %s\n", gf_error_to_string(e) )); } esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } if (item_type == GF_ISOM_SUBTYPE_VVC1) { - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; - if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1500,20 +1535,46 @@ esd->OCRESID = esd->ESID; esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_VVC; - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, NULL); - if (e == GF_OK && props.config) { - gf_odf_vvc_cfg_write(((GF_VVCConfigurationBox *)props.config)->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); + if (e == GF_OK && props->config) { + GF_VVCConfigurationBox *vvcc = props->config; + if (vvcc->type == GF_ISOM_BOX_TYPE_VVCC) { + gf_odf_vvc_cfg_write(vvcc->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + } else { + gf_odf_desc_del((GF_Descriptor*)esd); + gf_free(props); + return NULL; + } } esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); + return esd; + } + if (item_type == GF_4CC('j','2','k','1')) { + if (item_id > (1 << 16)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); + } + esd->ESID = (u16)item_id; + esd->OCRESID = esd->ESID; + esd->decoderConfig->streamType = GF_STREAM_VISUAL; + esd->decoderConfig->objectTypeIndication = GF_CODECID_J2K; + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL); + if (e == GF_OK && props->config) { + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_isom_box_write((GF_Box*)props->config, bs); + gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); + gf_bs_del(bs); + } + esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; + esd->slConfig->useTimestampsFlag = 1; + esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } - if ((item_type == GF_ISOM_SUBTYPE_UNCV) || (item_type == GF_ISOM_ITEM_TYPE_UNCI)) { - GF_ImageItemProperties props; - esd = gf_odf_desc_esd_new(0); - if (!esd) return NULL; + if ((item_type == GF_ISOM_SUBTYPE_UNCV) || (item_type == GF_ISOM_ITEM_TYPE_UNCI)) { if (item_id > (1 << 16)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n")); } @@ -1522,7 +1583,7 @@ esd->decoderConfig->streamType = GF_STREAM_VISUAL; esd->decoderConfig->objectTypeIndication = GF_CODECID_RAW; GF_List *other_props = gf_list_new(); - e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, &props, other_props); + e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, other_props); #ifndef GPAC_DISABLE_ISOM_WRITE if ((e == GF_OK) && gf_list_count(other_props)) { GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -1535,10 +1596,12 @@ esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; esd->slConfig->useTimestampsFlag = 1; esd->slConfig->timestampResolution = 1000; + gf_free(props); return esd; } - + gf_odf_desc_del((GF_Descriptor*)esd); + gf_free(props); return NULL; } @@ -1655,7 +1718,7 @@ u32 num_svc_track, num_sample, svc_track, dst_track, ref_trackID, ref_trackNum, max_id, di, width, height, size, nalu_size_length, i, j, t, max_size, num_pps, num_sps, num_subseq, NALUnitHeader, data_offset, data_length, count, timescale, cur_extract_mode; GF_Err e; GF_NALUFFParam *slc, *sl; - AVCState avc; + AVCState *avc_state=NULL; s32 sps_id, pps_id; GF_ISOSample *samp, *dst_samp; GF_BitStream *bs, *dst_bs; @@ -1727,8 +1790,13 @@ max_id = gf_isom_get_track_id_max(file); di = 0; - memset(&avc, 0, sizeof(AVCState)); - avc.sps_active_idx = -1; + GF_SAFEALLOC(avc_state, AVCState); + if (!avc_state) { + e = GF_OUT_OF_MEM; + goto exit; + } + + avc_state->sps_active_idx = -1; nalu_size_length = 8 * svccfg->nal_unit_size; /*read all sps, but we need only the subset sequence parameter sets*/ sps = (s32 *) gf_malloc(num_subseq * sizeof(s32)); @@ -1738,7 +1806,7 @@ { slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, i); nal_type = slc->data0 & 0x1F; - sps_id = gf_avc_read_sps(slc->data, slc->size, &avc, 0, NULL); + sps_id = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, NULL); if (sps_id < 0) { e = GF_NON_COMPLIANT_BITSTREAM; goto exit; @@ -1757,7 +1825,7 @@ for (j = 0; j < num_pps; j++) { slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j); - pps_id = gf_avc_read_pps(slc->data, slc->size, &avc); + pps_id = gf_avc_read_pps(slc->data, slc->size, avc_state); if (pps_id < 0) { e = GF_NON_COMPLIANT_BITSTREAM; goto exit; @@ -1795,8 +1863,8 @@ max_size = size; } - gf_avc_parse_nalu(bs, &avc); - nal_type = avc.last_nal_type_parsed; + gf_avc_parse_nalu(bs, avc_state); + nal_type = avc_state->last_nal_type_parsed; e = gf_bs_seek(bs, offset+nalu_size_length/8); if (e) @@ -1807,24 +1875,24 @@ { for (i = 0; i < num_pps; i++) { - if (avc.s_info.pps->id == ppsi) + if (avc_state->s_info.pps->id == ppsi) { is_subseq_ppsi = 1; break; } } - if ((count > 0) && (avc.s_info.pps->sps_id == spscount-1)) + if ((count > 0) && (avc_state->s_info.pps->sps_id == spscount-1)) continue; /*verify the order of SPS, reorder if necessary*/ - if (avc.s_info.pps->sps_id != spscount) + if (avc_state->s_info.pps->sps_id != spscount) { for (i = count+1; i < num_subseq; i++) { /*swap two SPS*/ - if (avc.s_info.pps->sps_id == spsi) + if (avc_state->s_info.pps->sps_id == spsi) { spsi = spscount; - spscount = avc.s_info.pps->sps_id; + spscount = avc_state->s_info.pps->sps_id; sps_trackcount = i; break; } @@ -1869,16 +1937,16 @@ if (splitAll) { sps_id = spst; - width = avc.spssps_id.width; - height = avc.spssps_id.height; + width = avc_state->spssps_id.width; + height = avc_state->spssps_id.height; gf_isom_set_visual_info(file, svc_track, di, width, height); cfg->configurationVersion = 1; - cfg->chroma_bit_depth = 8 + avc.spssps_id.chroma_bit_depth_m8; - cfg->chroma_format = avc.spssps_id.chroma_format; - cfg->luma_bit_depth = 8 + avc.spssps_id.luma_bit_depth_m8; - cfg->profile_compatibility = avc.spssps_id.prof_compat; - cfg->AVCLevelIndication = avc.spssps_id.level_idc; - cfg->AVCProfileIndication = avc.spssps_id.profile_idc; + cfg->chroma_bit_depth = 8 + avc_state->spssps_id.chroma_bit_depth_m8; + cfg->chroma_format = avc_state->spssps_id.chroma_format; + cfg->luma_bit_depth = 8 + avc_state->spssps_id.luma_bit_depth_m8; + cfg->profile_compatibility = avc_state->spssps_id.prof_compat; + cfg->AVCLevelIndication = avc_state->spssps_id.level_idc; + cfg->AVCProfileIndication = avc_state->spssps_id.profile_idc; cfg->nal_unit_size = svccfg->nal_unit_size; slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, sps_trackt); sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam)); @@ -1890,7 +1958,7 @@ for (j = 0; j < num_pps; j++) { pps_id = ppsj; - if (is_subseq_ppsj && (avc.ppspps_id.sps_id == sps_id)) + if (is_subseq_ppsj && (avc_state->ppspps_id.sps_id == sps_id)) { slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j); sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam)); @@ -1907,16 +1975,16 @@ for (i = 0; i < num_subseq; i++) { sps_id = spsi; - width = avc.spssps_id.width; - height = avc.spssps_id.height; + width = avc_state->spssps_id.width; + height = avc_state->spssps_id.height; gf_isom_set_visual_info(file, svc_track, di, width, height); cfg->configurationVersion = 1; - cfg->chroma_bit_depth = 8 + avc.spssps_id.chroma_bit_depth_m8; - cfg->chroma_format = avc.spssps_id.chroma_format; - cfg->luma_bit_depth = 8 + avc.spssps_id.luma_bit_depth_m8; - cfg->profile_compatibility = avc.spssps_id.prof_compat; - cfg->AVCLevelIndication = avc.spssps_id.level_idc; - cfg->AVCProfileIndication = avc.spssps_id.profile_idc; + cfg->chroma_bit_depth = 8 + avc_state->spssps_id.chroma_bit_depth_m8; + cfg->chroma_format = avc_state->spssps_id.chroma_format; + cfg->luma_bit_depth = 8 + avc_state->spssps_id.luma_bit_depth_m8; + cfg->profile_compatibility = avc_state->spssps_id.prof_compat; + cfg->AVCLevelIndication = avc_state->spssps_id.level_idc; + cfg->AVCProfileIndication = avc_state->spssps_id.profile_idc; cfg->nal_unit_size = svccfg->nal_unit_size; slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, sps_tracki); sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam)); @@ -1928,7 +1996,7 @@ for (j = 0; j < num_pps; j++) { pps_id = ppsj; - if (avc.ppspps_id.sps_id == sps_id) + if (avc_state->ppspps_id.sps_id == sps_id) { slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j); sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam)); @@ -2027,8 +2095,8 @@ max_size = size; } - gf_avc_parse_nalu(bs, &avc); - nal_type = avc.last_nal_type_parsed; + gf_avc_parse_nalu(bs, avc_state); + nal_type = avc_state->last_nal_type_parsed; e = gf_bs_seek(bs, offset+nalu_size_length/8); if (e) goto exit; @@ -2037,7 +2105,7 @@ switch (nal_type) { case GF_AVC_NALU_PIC_PARAM: - pps_id = avc.last_ps_idx; + pps_id = avc_state->last_ps_idx; j = 0; dst_track = 0; while (j < num_pps) @@ -2052,7 +2120,7 @@ { for (t = 0; t < num_svc_track; t++) { - if (spst == avc.ppspps_id.sps_id) + if (spst == avc_state->ppspps_id.sps_id) { dst_track = t + 1; break; @@ -2065,7 +2133,7 @@ dst_bs = sample_bsdst_track; break; case GF_AVC_NALU_SVC_SUBSEQ_PARAM: - sps_id = avc.last_ps_idx; + sps_id = avc_state->last_ps_idx; dst_track = 0; if (splitAll) { @@ -2088,7 +2156,7 @@ { for (t = 0; t < num_svc_track; t++) { - if (spst == (avc.s_info.pps)->sps_id) + if (spst == (avc_state->s_info.pps)->sps_id) { dst_track = t + 1; break; @@ -2172,7 +2240,7 @@ for (i = 0; i < gf_list_count(svccfg->sequenceParameterSets); i++) { slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, i); - sps_id = gf_avc_read_sps(slc->data, slc->size, &avc, 0, NULL); + sps_id = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, NULL); if (sps_id < 0) { e = GF_NON_COMPLIANT_BITSTREAM; goto exit; @@ -2190,7 +2258,7 @@ for (j = 0; j < gf_list_count(svccfg->pictureParameterSets); j++) { slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j); - pps_id = gf_avc_read_pps(slc->data, slc->size, &avc); + pps_id = gf_avc_read_pps(slc->data, slc->size, avc_state); if (pps_id < 0) { e = GF_NON_COMPLIANT_BITSTREAM; goto exit; @@ -2233,6 +2301,7 @@ if (first_DTS_track) gf_free(first_DTS_track); if (buffer) gf_free(buffer); if (is_subseq_pps) gf_free(is_subseq_pps); + if (avc_state) gf_free(avc_state); gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode); return e; #else @@ -2730,7 +2799,6 @@ GF_Err gf_media_split_lhvc(GF_ISOFile *file, u32 track, Bool for_temporal_sublayers, Bool splitAll, GF_LHVCExtractoreMode extractor_mode) { #if !defined(GPAC_DISABLE_AV_PARSERS) - LHVCTrackInfo sti64; GF_HEVCConfig *hevccfg, *lhvccfg; u32 sample_num, count, cur_extract_mode, j, k, max_layer_id; char *nal_data=NULL; @@ -2738,26 +2806,32 @@ u32 nal_unit_size=0; Bool single_layer_per_track=GF_TRUE; GF_Err e = GF_OK; - HEVCState hevc_state; + HEVCState *hvc_state; - memset(&hevc_state, 0, sizeof(HEVCState)); + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return GF_OUT_OF_MEM; hevccfg = gf_isom_hevc_config_get(file, track, 1); lhvccfg = gf_isom_lhvc_config_get(file, track, 1); if (!lhvccfg && !for_temporal_sublayers) { if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); + gf_free(hvc_state); return GF_OK; } else if (for_temporal_sublayers) { if (lhvccfg) { if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); gf_odf_hevc_cfg_del(lhvccfg); + gf_free(hvc_state); + return GF_NOT_SUPPORTED; + } + if (!hevccfg) { + gf_free(hvc_state); return GF_NOT_SUPPORTED; } - if (!hevccfg) return GF_NOT_SUPPORTED; - if (hevccfg->numTemporalLayers<=1) { gf_odf_hevc_cfg_del(hevccfg); + gf_free(hvc_state); return GF_OK; } } @@ -2765,7 +2839,11 @@ cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track); gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT); - memset(sti, 0, sizeof(sti)); + LHVCTrackInfo *sti; + sti = gf_malloc(sizeof(LHVCTrackInfo)*64); + if (!sti) return GF_OUT_OF_MEM; + + memset(sti, 0, sizeof(LHVCTrackInfo)*64); sti0.track_num = track; sti0.has_samples=GF_TRUE; max_layer_id = 0; @@ -2796,15 +2874,15 @@ if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { u32 lw, lh; - s32 idx = gf_hevc_get_sps_info_with_state(&hevc_state, sl->data, sl->size, NULL, &lw, &lh, NULL, NULL); + s32 idx = gf_hevc_get_sps_info_with_state(hvc_state, sl->data, sl->size, NULL, &lw, &lh, NULL, NULL); if (idx>=0) { if (lw > stilayer_id.width) stilayer_id.width = lw; if (lh > stilayer_id.height) stilayer_id.height = lh; } } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { - gf_hevc_read_pps(sl->data, sl->size, &hevc_state); + gf_hevc_read_pps(sl->data, sl->size, hvc_state); } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { - gf_hevc_read_vps(sl->data, sl->size, &hevc_state); + gf_hevc_read_vps(sl->data, sl->size, hvc_state); } //don't touch base layer @@ -2978,15 +3056,15 @@ if (nal_type==GF_HEVC_NALU_SEQ_PARAM) { u32 lw, lh; - s32 idx = gf_hevc_get_sps_info_with_state(&hevc_state, sample->data + offset, nal_size, NULL, &lw, &lh, NULL, NULL); + s32 idx = gf_hevc_get_sps_info_with_state(hvc_state, sample->data + offset, nal_size, NULL, &lw, &lh, NULL, NULL); if (idx>=0) { if (lw > stilayer_id.width) stilayer_id.width = lw; if (lh > stilayer_id.height) stilayer_id.height = lh; } } else if (nal_type==GF_HEVC_NALU_PIC_PARAM) { - gf_hevc_read_pps(sample->data + offset, nal_size, &hevc_state); + gf_hevc_read_pps(sample->data + offset, nal_size, hvc_state); } else if (nal_type==GF_HEVC_NALU_VID_PARAM) { - gf_hevc_read_vps(sample->data + offset, nal_size, &hevc_state); + gf_hevc_read_vps(sample->data + offset, nal_size, hvc_state); } } @@ -3262,6 +3340,9 @@ if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg); if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); if (nal_data) gf_free(nal_data); + gf_free(hvc_state); + + gf_free(sti); return e; #else return GF_NOT_SUPPORTED; @@ -3424,7 +3505,7 @@ u32 i, j, cur_tile, count, stype, track, nb_tiles, di, nalu_size_length, tx, ty, tw, th; s32 pps_idx=-1, sps_idx=-1, ret; GF_Err e = GF_OK; - HEVCState hevc; + HEVCState *hvc_state; HEVCTileImport *tiles; GF_HEVCConfig *hvcc; Bool filter_disabled=GF_TRUE; @@ -3450,7 +3531,8 @@ hvcc = gf_isom_hevc_config_get(file, track, 1); nalu_size_length = hvcc->nal_unit_size; - memset(&hevc, 0, sizeof(HEVCState)); + GF_SAFEALLOC(hvc_state, HEVCState); + if (!hvc_state) return GF_OUT_OF_MEM; count = gf_list_count(hvcc->param_array); for (i=0; i<count; i++) { @@ -3460,13 +3542,13 @@ if (!sl) continue; switch (ar->type) { case GF_HEVC_NALU_PIC_PARAM: - pps_idx = gf_hevc_read_pps(sl->data, sl->size, &hevc); + pps_idx = gf_hevc_read_pps(sl->data, sl->size, hvc_state); break; case GF_HEVC_NALU_SEQ_PARAM: - sps_idx = gf_hevc_read_sps(sl->data, sl->size, &hevc); + sps_idx = gf_hevc_read_sps(sl->data, sl->size, hvc_state); break; case GF_HEVC_NALU_VID_PARAM: - gf_hevc_read_vps(sl->data, sl->size, &hevc); + gf_hevc_read_vps(sl->data, sl->size, hvc_state); break; } } @@ -3489,17 +3571,17 @@ for (j=0; j<nalu_size_length; j++) { nalu_size = (nalu_size<<8) + dataj; } - gf_hevc_parse_nalu(data + nalu_size_length, nalu_size, &hevc, &nal_type, &temporal_id, &layer_id); + gf_hevc_parse_nalu(data + nalu_size_length, nalu_size, hvc_state, &nal_type, &temporal_id, &layer_id); switch (nal_type) { case GF_HEVC_NALU_PIC_PARAM: - pps_idx = gf_hevc_read_pps((char *) data+nalu_size_length, nalu_size, &hevc); + pps_idx = gf_hevc_read_pps((char *) data+nalu_size_length, nalu_size, hvc_state); break; case GF_HEVC_NALU_SEQ_PARAM: - sps_idx = gf_hevc_read_sps((char *) data+nalu_size_length, nalu_size, &hevc); + sps_idx = gf_hevc_read_sps((char *) data+nalu_size_length, nalu_size, hvc_state); break; case GF_HEVC_NALU_VID_PARAM: - gf_hevc_read_vps((char *) data+nalu_size_length, nalu_size, &hevc); + gf_hevc_read_vps((char *) data+nalu_size_length, nalu_size, hvc_state); break; } data += nalu_size + nalu_size_length; @@ -3508,21 +3590,27 @@ gf_isom_sample_del(&sample); } - if (pps_idx==-1) return GF_BAD_PARAM; - if (sps_idx==-1) return GF_BAD_PARAM; + if ((pps_idx==-1) || (sps_idx==-1)) { + gf_free(hvc_state); + return GF_BAD_PARAM; + } - if (hevc.ppspps_idx.loop_filter_across_tiles_enabled_flag) + if (hvc_state->ppspps_idx.loop_filter_across_tiles_enabled_flag) filter_disabled=GF_FALSE; - if (! hevc.ppspps_idx.tiles_enabled_flag) { - hevc_add_trif(file, track, gf_isom_get_track_id(file, track), GF_TRUE, 1, filter_disabled, 0, 0, hevc.spspps_idx.width, hevc.spspps_idx.height, GF_TRUE); + if (! hvc_state->ppspps_idx.tiles_enabled_flag) { + hevc_add_trif(file, track, gf_isom_get_track_id(file, track), GF_TRUE, 1, filter_disabled, 0, 0, hvc_state->spspps_idx.width, hvc_state->spspps_idx.height, GF_TRUE); GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("HEVC Tiles Tiles not enabled, signal only single tile full picture\n")); + gf_free(hvc_state); return GF_OK; } - nb_tiles = hevc.ppspps_idx.num_tile_columns * hevc.ppspps_idx.num_tile_rows; + nb_tiles = hvc_state->ppspps_idx.num_tile_columns * hvc_state->ppspps_idx.num_tile_rows; tiles = gf_malloc(sizeof(HEVCTileImport) * nb_tiles); - if (!tiles) return GF_OUT_OF_MEM; + if (!tiles) { + gf_free(hvc_state); + return GF_OUT_OF_MEM; + } memset(tiles, 0, sizeof(HEVCTileImport) * nb_tiles); for (i=0; i<nb_tiles; i++) { @@ -3585,7 +3673,7 @@ for (j=0; j<nalu_size_length; j++) { nalu_size = (nalu_size<<8) + dataj; } - ret = gf_hevc_parse_nalu(data + nalu_size_length, nalu_size, &hevc, &nal_type, &temporal_id, &layer_id); + ret = gf_hevc_parse_nalu(data + nalu_size_length, nalu_size, hvc_state, &nal_type, &temporal_id, &layer_id); //error parsing NAL, set nal to fallback to regular import if (ret<0) nal_type = GF_HEVC_NALU_VID_PARAM; @@ -3608,7 +3696,7 @@ case GF_HEVC_NALU_SLICE_RASL_R: case GF_HEVC_NALU_SLICE_RASL_N: tx = ty = tw = th = 0; - cur_tile = hevc_get_tile_id(&hevc, &tx, &ty, &tw, &th); + cur_tile = hevc_get_tile_id(hvc_state, &tx, &ty, &tw, &th); if (cur_tile>=nb_tiles) { GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("HEVC Tiles Tile index %d is greater than number of tiles %d in PPS\n", cur_tile, nb_tiles)); e = GF_NON_COMPLIANT_BITSTREAM; @@ -3620,7 +3708,7 @@ tilescur_tile.ty = ty; tilescur_tile.tw = tw; tilescur_tile.th = th; - if (hevc.s_info.slice_type != GF_HEVC_SLICE_TYPE_I) { + if (hvc_state->s_info.slice_type != GF_HEVC_SLICE_TYPE_I) { tilescur_tile.all_intra = 0; } @@ -3680,8 +3768,12 @@ for (j=0; j<nb_tiles; j++) { sample->dataLength = 0; gf_bs_get_content(tilesj.sample_data, &sample->data, &sample->dataLength); - if (!sample->data) + if (!sample->data) { + gf_bs_del(tilesj.sample_data); + tilesj.sample_data = NULL; continue; + } + e = gf_isom_add_sample(file, tilesj.track, 1, sample); if (e) goto err_exit; @@ -3746,6 +3838,7 @@ err_exit: + gf_free(hvc_state); gf_free(tiles); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("ISOBMF Could not split HEVC tiles into tracks: %s\n", gf_error_to_string(e) )); @@ -3823,18 +3916,18 @@ sprintf(szArgs, "mp4dmx:mov=%p", input); f = gf_fs_load_filter(fsess, szArgs, &e); - if (!f) return e; + if (!f) { gf_fs_del(fsess); return e; } strcpy(szArgs, "reframer:FID=1"); f = gf_fs_load_filter(fsess, szArgs, &e); - if (!f) return e; + if (!f) { gf_fs_del(fsess); return e; } sprintf(szArgs, "%s:SID=1:frag:cdur=%g:abs_offset:fragdur", output_file, max_duration_sec); if (use_mfra) strcat(szArgs, ":mfra"); f = gf_fs_load_destination(fsess, szArgs, NULL, NULL, &e); - if (!f) return e; + if (!f) { gf_fs_del(fsess); return e; } if (!gf_sys_is_test_mode() #ifndef GPAC_DISABLE_LOG @@ -3909,6 +4002,34 @@ return GF_OK; } +GF_Err dolby_get_codec_ac4(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size) +{ + GF_Err e = GF_NON_COMPLIANT_BITSTREAM; + if (!dsi || !dsi_size) return e; + + GF_AC4Config cfg = {0}; + GF_BitStream *bs = gf_bs_new((const char *)dsi, dsi_size, GF_BITSTREAM_READ); + if (!bs) return GF_OUT_OF_MEM; + + e = gf_odf_ac4_cfg_parse_bs(bs, &cfg); + gf_bs_del(bs); + if (e == GF_OK) { + e = GF_NON_COMPLIANT_BITSTREAM; + if (cfg.stream.n_presentations > 0) { + GF_AC4PresentationV1 *presentations = (GF_AC4PresentationV1*)gf_list_get(cfg.stream.presentations, 0); + if (presentations) { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "ac-4.%02d.%02d.%02d", cfg.stream.bitstream_version, presentations->presentation_version, presentations->mdcompat); + e = GF_OK; + } + } + } + gf_odf_ac4_cfg_clean_list(&cfg); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("RFC6381 invalid AC4 DSI %s\n", gf_error_to_string(e) )); + } + return e; +} + GF_Err rfc_6381_get_codec_m4v(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size) { #ifndef GPAC_DISABLE_AV_PARSERS @@ -4001,11 +4122,13 @@ #ifndef GPAC_DISABLE_AV_PARSERS GF_Err e; u32 i = 0; - AV1State av1_state; + AV1State *av1_state; gf_assert(av1c); - gf_av1_init_state(&av1_state); - av1_state.config = av1c; + GF_SAFEALLOC(av1_state, AV1State); + if (!av1_state) return GF_OUT_OF_MEM; + gf_av1_init_state(av1_state); + av1_state->config = av1c; for (i = 0; i < gf_list_count(av1c->obu_array); ++i) { GF_BitStream *bs; @@ -4014,7 +4137,7 @@ if (!av1_is_obu_header(a->obu_type)) GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("RFC6381 AV1: unexpected obu_type %d - Parsing anyway.\n", a->obu_type, gf_4cc_to_str(subtype))); - e = aom_av1_parse_temporal_unit_from_section5(bs, &av1_state); + e = aom_av1_parse_temporal_unit_from_section5(bs, av1_state); gf_bs_del(bs); bs = NULL; if (e) { @@ -4023,27 +4146,28 @@ } snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%01u.%02u%c.%02u", gf_4cc_to_str(subtype), - av1_state.config->seq_profile, av1_state.config->seq_level_idx_0, av1_state.config->seq_tier_0 ? 'H' : 'M', av1_state.bit_depth); + av1_state->config->seq_profile, av1_state->config->seq_level_idx_0, av1_state->config->seq_tier_0 ? 'H' : 'M', av1_state->bit_depth); - if (av1_state.color_description_present_flag) { + if (av1_state->color_description_present_flag) { char tmpRFC6381_CODEC_NAME_SIZE_MAX; snprintf(tmp, RFC6381_CODEC_NAME_SIZE_MAX, ".%01u.%01u%01u%01u.%02u.%02u.%02u.%01u", - av1_state.config->monochrome, av1_state.config->chroma_subsampling_x, av1_state.config->chroma_subsampling_y, - av1_state.config->chroma_subsampling_x && av1_state.config->chroma_subsampling_y ? av1_state.config->chroma_sample_position : 0, - colr.override == GF_TRUE ? colr.colour_primaries : av1_state.color_primaries, - colr.override == GF_TRUE ? colr.transfer_characteristics : av1_state.transfer_characteristics, - colr.override == GF_TRUE ? colr.matrix_coefficients : av1_state.matrix_coefficients, - colr.override == GF_TRUE ? colr.full_range : av1_state.color_range); + av1_state->config->monochrome, av1_state->config->chroma_subsampling_x, av1_state->config->chroma_subsampling_y, + av1_state->config->chroma_subsampling_x && av1_state->config->chroma_subsampling_y ? av1_state->config->chroma_sample_position : 0, + colr.override == GF_TRUE ? colr.colour_primaries : av1_state->color_primaries, + colr.override == GF_TRUE ? colr.transfer_characteristics : av1_state->transfer_characteristics, + colr.override == GF_TRUE ? colr.matrix_coefficients : av1_state->matrix_coefficients, + colr.override == GF_TRUE ? colr.full_range : av1_state->color_range); strcat(szCodec, tmp); } else { - if ((av1_state.color_primaries == 2) && (av1_state.transfer_characteristics == 2) && (av1_state.matrix_coefficients == 2) && av1_state.color_range == GF_FALSE) { + if ((av1_state->color_primaries == 2) && (av1_state->transfer_characteristics == 2) && (av1_state->matrix_coefficients == 2) && av1_state->color_range == GF_FALSE) { } else { GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("RFC6381 incoherent color characteristics primaries %d transfer %d matrix %d color range %d\n", - av1_state.color_primaries, av1_state.transfer_characteristics, av1_state.matrix_coefficients, av1_state.color_range)); + av1_state->color_primaries, av1_state->transfer_characteristics, av1_state->matrix_coefficients, av1_state->color_range)); } } - gf_av1_reset_state(&av1_state, GF_TRUE); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); return GF_OK; #else return GF_NOT_SUPPORTED; @@ -4065,6 +4189,20 @@ return GF_OK; } +GF_Err rfc_6381_get_codec_avs3v(char *szCodec, u32 subtype, GF_AVS3VConfig *av3c, COLR colr) +{ +#ifndef GPAC_DISABLE_AV_PARSERS + if (!av3c || !av3c->sequence_header || av3c->sequence_header_length < 6) + return GF_BAD_PARAM; + u8 profile = av3c->sequence_header4; + u8 level = av3c->sequence_header5; + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%2x.%2x", gf_4cc_to_str(subtype), profile, level); + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif +} + GF_Err rfc_6381_get_codec_dolby_vision(char *szCodec, u32 subtype, GF_DOVIDecoderConfigurationRecord *dovi) { snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02u.%02u", gf_4cc_to_str(subtype), dovi->dv_profile, dovi->dv_level); @@ -4114,19 +4252,24 @@ nb_bits++; } char b32_char = base32_charsc; - //should not happen, we use 100 bytes by default and max general_constraint_info is 63 bytes - if (len >= RFC6381_CODEC_NAME_SIZE_MAX) { + + if (len<=RFC6381_CODEC_NAME_SIZE_MAX-2) { + szCodeclen = b32_char; + len++; + szCodeclen = 0; + } + else { + szCodecRFC6381_CODEC_NAME_SIZE_MAX-1 = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("RFC6381 truncated codec string - current value: %s - trying to write: %c\n", szCodec, b32_char)); gf_bs_del(bs); - return GF_OUT_OF_MEM; + return GF_ISOM_INVALID_FILE; } - szCodeclen = b32_char; - len++; - szCodeclen = 0; } gf_bs_del(bs); return GF_OK; } + GF_Err rfc_6381_get_codec_mpegha(char *szCodec, u32 subtype, u8 *dsi, u32 dsi_size, s32 pl) { if (dsi && (dsi_size>=2) ) { @@ -4139,6 +4282,48 @@ return GF_OK; } +GF_Err rfc_6381_get_codec_iamf(char *szCodec, GF_IAConfig *cfg) +{ + IAMFState state; + gf_iamf_init_state(&state); + + u32 obu_count = gf_list_count(cfg->configOBUs); + for (u32 i=0; i<obu_count; ++i) { + GF_IamfObu *obu = gf_list_get(cfg->configOBUs, i); + GF_BitStream *bs = gf_bs_new(obu->raw_obu_bytes, obu->obu_length, GF_BITSTREAM_READ); + + u64 obu_size = 0; + IamfObuType obu_type; + GF_Err e = gf_iamf_parse_obu(bs, &obu_type, &obu_size, &state); + gf_bs_del(bs); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("IAMF could not parse configOBU #%u. Leaving parsing.\n", i)); + return GF_ISOM_INVALID_FILE; + } + } + + const char *codec = NULL; + switch (state.codec_id) { + case GF_4CC('O', 'p', 'u', 's'): + codec = "opus"; + break; + case GF_4CC('f', 'L', 'a', 'C'): + codec = "fLaC"; + break; + case GF_4CC('i', 'p', 'c', 'm'): + codec = "ipcm"; + break; + case GF_4CC('m', 'p', '4', 'a'): + codec = "mp4a.40.2"; + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("IAMF Unsupported codec_id=%u. Leaving parsing.\n", state.codec_id)); + return GF_ISOM_INVALID_FILE; + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "iamf.%03u.%03u.%s", state.primary_profile, state.additional_profile, codec); + return GF_OK; +} + GF_Err rfc_6381_get_codec_uncv(char *szCodec, u32 subtype, u8 *dsi, u32 dsi_size); // Selected (namespace,identifier) pairs from https://www.w3.org/TR/ttml-profile-registry/ @@ -4158,25 +4343,25 @@ const char *xml_schema_loc, const char *mimes) { - // we ignore schema location and auxiliary mime types - // we focus on the provided list of namespaces - if (xmlnamespace != NULL) { - int i; - for (i = 0; i < TTML_NAMESPACES_COUNT; i+=2) { - if(strstr(xmlnamespace, ttml_namespacesi+1) != NULL) { - snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%s.%s", gf_4cc_to_str(subtype), "ttml", ttml_namespacesi); - return GF_OK; - } - } - // if none of the namespaces above have been found, search the default TTML namespace - if(strstr(xmlnamespace, "http://www.w3.org/ns/ttml")) { - snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%s", gf_4cc_to_str(subtype), "ttml"); - return GF_OK; - } - } - // None of the known namespaces are found, default - snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); - return GF_OK; + // we ignore schema location and auxiliary mime types + // we focus on the provided list of namespaces + if (xmlnamespace != NULL) { + int i; + for (i = 0; i < TTML_NAMESPACES_COUNT; i+=2) { + if(strstr(xmlnamespace, ttml_namespacesi+1) != NULL) { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%s.%s", gf_4cc_to_str(subtype), "ttml", ttml_namespacesi); + return GF_OK; + } + } + // if none of the namespaces above have been found, search the default TTML namespace + if(strstr(xmlnamespace, "http://www.w3.org/ns/ttml")) { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%s", gf_4cc_to_str(subtype), "ttml"); + return GF_OK; + } + } + // None of the known namespaces are found, default + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + return GF_OK; } GF_Err rfc6381_codec_name_default(char *szCodec, u32 subtype, u32 codec_id) @@ -4342,6 +4527,24 @@ return GF_BAD_PARAM; } + case GF_ISOM_SUBTYPE_AVS3: + { + GF_AVS3VConfig *av3c = gf_isom_avs3v_config_get(movie, track, stsd_idx); + if (av3c) { + u32 colour_type; + COLR colr; + memset(&colr, 0, sizeof(colr)); + if (GF_OK == gf_isom_get_color_info(movie, track, stsd_idx, &colour_type, &colr.colour_primaries, &colr.transfer_characteristics, &colr.matrix_coefficients, &colr.full_range)) { + colr.override = GF_TRUE; + } + e = rfc_6381_get_codec_avs3v(szCodec, subtype, av3c, colr); + gf_odf_avs3v_cfg_del(av3c); + return e; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("RFC6381 No config found for AVS3 Video file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype))); + return GF_BAD_PARAM; + } + case GF_ISOM_SUBTYPE_VP08: case GF_ISOM_SUBTYPE_VP09: { @@ -4410,6 +4613,18 @@ return e; } + case GF_ISOM_SUBTYPE_IAMF: + { + GF_IAConfig *iamf = gf_isom_iamf_config_get(movie, track, stsd_idx); + if (!iamf) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("RFC6381 No config found for IAMF file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype))); + return GF_NON_COMPLIANT_BITSTREAM; + } + e = rfc_6381_get_codec_iamf(szCodec, iamf); + gf_odf_iamf_cfg_del(iamf); + return e; + } + case GF_ISOM_SUBTYPE_UNCV: { GF_GenericSampleDescription *udesc = gf_isom_get_generic_sample_description(movie, track, stsd_idx); @@ -4421,21 +4636,20 @@ } return e; } - case GF_ISOM_SUBTYPE_STPP: - { - const char *xmlnamespace; - const char *xml_schema_loc; - const char *mimes; - e = gf_isom_xml_subtitle_get_description(movie, track, stsd_idx, - &xmlnamespace, &xml_schema_loc, &mimes); - if (e == GF_OK) { - rfc_6381_get_codec_stpp(szCodec, subtype, xmlnamespace, xml_schema_loc, mimes); - } - return e; - } + case GF_ISOM_SUBTYPE_STPP: + { + const char *xmlnamespace; + const char *xml_schema_loc; + const char *mimes; + e = gf_isom_xml_subtitle_get_description(movie, track, stsd_idx, + &xmlnamespace, &xml_schema_loc, &mimes); + if (e == GF_OK) { + rfc_6381_get_codec_stpp(szCodec, subtype, xmlnamespace, xml_schema_loc, mimes); + } + return e; + } default: return rfc6381_codec_name_default(szCodec, subtype, gf_codec_id_from_isobmf(subtype)); - } return GF_OK; } @@ -4445,7 +4659,7 @@ { #ifndef GPAC_DISABLE_AV_PARSERS u32 i; - AV1State av1; + AV1State *av1_state; ObuType obu_type; u64 obu_size = 0; u32 hdr_size; @@ -4466,17 +4680,23 @@ return GF_BAD_PARAM; } - gf_av1_init_state(&av1); - av1.config = gf_isom_av1_config_get(file, trackNumber, sdidx); - if (!av1.config) { + GF_SAFEALLOC(av1_state, AV1State); + if (!av1_state) { + gf_isom_sample_del(&samp); + return GF_OUT_OF_MEM; + } + gf_av1_init_state(av1_state); + av1_state->config = gf_isom_av1_config_get(file, trackNumber, sdidx); + if (!av1_state->config) { gf_isom_sample_del(&samp); + gf_free(av1_state); return GF_ISOM_INVALID_FILE; } - for (i=0; i<gf_list_count(av1.config->obu_array); i++) { - GF_AV1_OBUArrayEntry *obu = gf_list_get(av1.config->obu_array, i); + for (i=0; i<gf_list_count(av1_state->config->obu_array); i++) { + GF_AV1_OBUArrayEntry *obu = gf_list_get(av1_state->config->obu_array, i); bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); - e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state); gf_bs_del(bs); if (e) break; } @@ -4484,29 +4704,31 @@ if (!e) { bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ); while (gf_bs_available(bs)) { - e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state); if (e) break; } gf_bs_del(bs); } gf_isom_sample_del(&samp); - if (op_index > av1.operating_points_count) { - if (av1.config) gf_odf_av1_cfg_del(av1.config); - gf_av1_reset_state(&av1, GF_TRUE); - return GF_BAD_PARAM; + if (op_index > av1_state->operating_points_count) { + if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); + return GF_BAD_PARAM; } for (i=0; i<3; i++) { - if (av1.layer_sizei+1==av1.layer_sizei) { + if (av1_state->layer_sizei+1==av1_state->layer_sizei) { layer_sizei = 0; } else { - layer_sizei = av1.layer_sizei; + layer_sizei = av1_state->layer_sizei; } } - if (av1.config) gf_odf_av1_cfg_del(av1.config); - gf_av1_reset_state(&av1, GF_TRUE); + if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config); + gf_av1_reset_state(av1_state, GF_TRUE); + gf_free(av1_state); return e; #else
View file
gpac-2.4.0.tar.gz/src/media_tools/m2ts_mux.c -> gpac-26.02.0.tar.gz/src/media_tools/m2ts_mux.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre , Cyril Concolato, Romain Bouqueau - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2026 * All rights reserved * * This file is part of GPAC / MPEG2-TS multiplexer sub-project @@ -51,7 +51,7 @@ GF_SEG_BOUNDARY_NONE=0, /*! segment start*/ GF_SEG_BOUNDARY_START, - /*! segment start with forced PMT - triggered by setting \ref struct __m2ts_mux.force_pat, triggers a forced PCR*/ + /*! segment start with forced PMT - triggered by setting \ref __m2ts_mux.force_pat, triggers a forced PCR*/ GF_SEG_BOUNDARY_FORCE_PMT, /*! segment start with forced PCR*/ GF_SEG_BOUNDARY_FORCE_PCR, @@ -171,6 +171,9 @@ case GF_M2TS_TABLE_ID_MPEG4_OD: maxSectionLength = 4096; break; + case GF_M2TS_TABLE_ID_SCTE35_SPLICE_INFO: + maxSectionLength = 1024; + break; default: GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS Muxer PID %d: Cannot create sections for table id %d\n", stream->pid, table_id)); return; @@ -509,7 +512,7 @@ //#define USE_AF_STUFFING -void gf_m2ts_mux_table_get_next_packet(GF_M2TS_Mux *mux, GF_M2TS_Mux_Stream *stream, char *packet) +static void gf_m2ts_mux_table_get_next_packet(GF_M2TS_Mux *mux, GF_M2TS_Mux_Stream *stream, char *packet) { GF_BitStream *bs; GF_M2TS_Mux_Table *table; @@ -626,7 +629,7 @@ } -u32 gf_m2ts_stream_process_sdt(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) +static u32 gf_m2ts_stream_process_sdt(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) { if (stream->table_needs_update) { /* generate table payload */ GF_M2TS_Mux_Program *prog; @@ -684,7 +687,7 @@ return 0; } -u32 gf_m2ts_stream_process_pat(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) +static u32 gf_m2ts_stream_process_pat(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) { if (stream->table_needs_update) { /* generate table payload */ GF_M2TS_Mux_Program *prog; @@ -717,7 +720,7 @@ static void gf_m2ts_program_stream_format_updated(GF_M2TS_Mux_Stream *stream); static s32 gf_m2ts_find_stream(GF_M2TS_Mux_Program *program, u32 pid, u32 stream_id, GF_M2TS_Mux_Stream **out_stream); -u32 gf_m2ts_stream_process_pmt(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) +static u32 gf_m2ts_stream_process_pmt(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) { if (stream->table_needs_update) { /* generate table payload */ GF_M2TS_Mux_Stream *es; @@ -824,6 +827,11 @@ es_info_length += 2; type = GF_M2TS_PRIVATE_DATA; break; + case GF_M2TS_AUDIO_AC4: + //reg desc + es_info_length += 2 + 4; + type = GF_M2TS_PRIVATE_DATA; + break; case GF_M2TS_VIDEO_VC1: case GF_M2TS_AUDIO_DTS: case GF_M2TS_AUDIO_OPUS: @@ -852,6 +860,19 @@ es_info_length += 2 + dv_len; } break; + case GF_M2TS_HLS_AVC_CRYPT: + es_info_length += 6; + break; + case GF_M2TS_HLS_AC3_CRYPT: + es_info_length += 6; + es_info_length += 10; + break; + case GF_M2TS_HLS_EC3_CRYPT: + case GF_M2TS_HLS_AAC_CRYPT: + es_info_length += 6; + es_info_length += 10 + es->ifce->decoder_config_size; + break; + default: if (es->ifce->codecid==GF_CODECID_DVB_SUBS) { es_info_length += 2 + 5+3; @@ -982,6 +1003,12 @@ gf_bs_write_int(bs, 4, 8); gf_bs_write_u32(bs, GF_M2TS_RA_STREAM_EAC3); break; + case GF_M2TS_AUDIO_AC4: + //write reg desc + gf_bs_write_int(bs, GF_M2TS_REGISTRATION_DESCRIPTOR, 8); + gf_bs_write_int(bs, 4, 8); + gf_bs_write_u32(bs, GF_M2TS_RA_STREAM_AC4); + break; case GF_M2TS_AUDIO_DTS: gf_bs_write_int(bs, GF_M2TS_REGISTRATION_DESCRIPTOR, 8); gf_bs_write_int(bs, 4, 8); @@ -1001,6 +1028,53 @@ gf_bs_write_u32(bs, GF_4CC('A', 'O', 'M', 'S')); break; + case GF_M2TS_HLS_AC3_CRYPT: + gf_bs_write_u8(bs, GF_M2TS_PRIVATE_DATA_INDICATOR_DESCRIPTOR); + gf_bs_write_u8(bs, 4); + gf_bs_write_u32(bs, GF_4CC('a', 'c', '3', 'd')); + + //add reg descriptor for SAES + gf_bs_write_u8(bs, GF_M2TS_REGISTRATION_DESCRIPTOR); + gf_bs_write_u8(bs, 8); + gf_bs_write_u32(bs, GF_4CC('z', 'a', 'c', '3')); + gf_bs_write_u16(bs, 0); //priming + gf_bs_write_u8(bs, 1); //version + gf_bs_write_u8(bs, 0); + break; + case GF_M2TS_HLS_EC3_CRYPT: + gf_bs_write_u8(bs, GF_M2TS_PRIVATE_DATA_INDICATOR_DESCRIPTOR); + gf_bs_write_u8(bs, 4); + gf_bs_write_u32(bs, GF_4CC('e', 'c', '3', 'd')); + + //add reg descriptor for SAES + gf_bs_write_u8(bs, GF_M2TS_REGISTRATION_DESCRIPTOR); + gf_bs_write_u8(bs, 8); + gf_bs_write_u32(bs, GF_4CC('z', 'e', 'c', '3')); + gf_bs_write_u16(bs, 0); //priming + gf_bs_write_u8(bs, 1); //version + gf_bs_write_u8(bs, es->ifce->decoder_config_size); //config size + gf_bs_write_data(bs, es->ifce->decoder_config, es->ifce->decoder_config_size); //config size + break; + case GF_M2TS_HLS_AAC_CRYPT: + gf_bs_write_u8(bs, GF_M2TS_PRIVATE_DATA_INDICATOR_DESCRIPTOR); + gf_bs_write_u8(bs, 4); + gf_bs_write_u32(bs, GF_4CC('a', 'a', 'c', 'd')); + + //add reg descriptor for SAES + gf_bs_write_u8(bs, GF_M2TS_REGISTRATION_DESCRIPTOR); + gf_bs_write_u8(bs, 8+es->ifce->decoder_config_size); + gf_bs_write_u32(bs, GF_4CC('z', 'a', 'a', 'c')); + gf_bs_write_u16(bs, 0); //priming + gf_bs_write_u8(bs, 1); //version + gf_bs_write_u8(bs, es->ifce->decoder_config_size); //config size + gf_bs_write_data(bs, es->ifce->decoder_config, es->ifce->decoder_config_size); //config size + break; + case GF_M2TS_HLS_AVC_CRYPT: + gf_bs_write_u8(bs, GF_M2TS_PRIVATE_DATA_INDICATOR_DESCRIPTOR); + gf_bs_write_u8(bs, 4); + gf_bs_write_u32(bs, GF_4CC('z', 'a', 'v', 'c')); + break; + default: if (es->force_reg_desc && es->ifce && es->ifce->codecid) { gf_bs_write_u8(bs, GF_M2TS_REGISTRATION_DESCRIPTOR); @@ -1186,7 +1260,43 @@ *dts = *dts - stream->program->initial_ts + pcr_offset; } -void id3_write_size(GF_BitStream *bs, u32 len) +static Bool gf_m2ts_adjust_next_stream_time_for_pcr(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) +{ + u32 pck_diff; + s32 us_diff; + GF_M2TS_Time next_pcr_time, stream_time; + + if (!muxer->enable_forced_pcr) return 1; + + if (!muxer->bit_rate) return 1; + + next_pcr_time = stream->program->ts_time_at_pcr_init; + pck_diff = (u32) (stream->program->nb_pck_last_pcr - stream->program->num_pck_at_pcr_init); + gf_m2ts_time_inc(&next_pcr_time, pck_diff*1504, stream->program->mux->bit_rate); + gf_m2ts_time_inc(&next_pcr_time, stream->program->mux->pcr_update_ms, 1000); + + stream_time = stream->pcr_only_mode ? stream->next_time : stream->time; + //If next_pcr_time < stream->time, we need to inject pure pcr data + us_diff = gf_m2ts_time_diff_us(&next_pcr_time, &stream_time); + if (us_diff > 0) { + if (!stream->pcr_only_mode) { + stream->pcr_only_mode = GF_TRUE; + stream->next_time = stream->time; + } + stream->time = next_pcr_time; + /*if too ahead of mux time, don't insert PCR*/ + us_diff = gf_m2ts_time_diff_us(&stream->program->mux->time, &stream->time); + if (us_diff>1000) + return 0; + } else if (stream->pcr_only_mode) { + stream->pcr_only_mode = GF_FALSE; + stream->time = stream->next_time; + } + return 1; +} + + +static void id3_write_size(GF_BitStream *bs, u32 len) { u32 size; @@ -1207,7 +1317,7 @@ gf_bs_write_int(bs, size, 7); } -static void id3_tag_create(u8 **input, u32 *len) +static void m2ts_id3_tag_create(u8 **input, u32 *len) { GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_bs_write_u8(bs, 'I'); @@ -1229,42 +1339,40 @@ gf_bs_del(bs); } -static Bool gf_m2ts_adjust_next_stream_time_for_pcr(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) +//get the number of bytes that WILL be added by framing in gf_m2ts_stream_process_pes +static u32 gf_m2ts_stream_get_reframe_overhead(GF_M2TS_Mux_Stream *stream, u32 dts, u32 pay_size) { - u32 pck_diff; - s32 us_diff; - GF_M2TS_Time next_pcr_time, stream_time; - - if (!muxer->enable_forced_pcr) return 1; - - if (!muxer->bit_rate) return 1; - - next_pcr_time = stream->program->ts_time_at_pcr_init; - pck_diff = (u32) (stream->program->nb_pck_last_pcr - stream->program->num_pck_at_pcr_init); - gf_m2ts_time_inc(&next_pcr_time, pck_diff*1504, stream->program->mux->bit_rate); - gf_m2ts_time_inc(&next_pcr_time, stream->program->mux->pcr_update_ms, 1000); + //other types are (currently) either 0 or constant + if (stream->mpeg2_stream_type != GF_M2TS_AUDIO_LATM_AAC) + return stream->reframe_overhead; + + //LATM overhead size varries based on + //- the frequency at which we inject the DSI (based on dts) + //- the length of the payload because of non-aligned syntax... + + u32 nb_bits=24; //LATM header + //use same condition as in gf_m2ts_stream_process_pes + u32 stream_time_ms = dts/90; + if (stream->ifce->decoder_config && (!stream_time_ms || (stream->latm_last_aac_time + stream->refresh_rate_ms < stream_time_ms-1))) { + + nb_bits += 16; + nb_bits += stream->ifce->decoder_config_size*8; + nb_bits += 13; + } else { + nb_bits += 1; + } - stream_time = stream->pcr_only_mode ? stream->next_time : stream->time; - //If next_pcr_time < stream->time, we need to inject pure pcr data - us_diff = gf_m2ts_time_diff_us(&next_pcr_time, &stream_time); - if (us_diff > 0) { - if (!stream->pcr_only_mode) { - stream->pcr_only_mode = GF_TRUE; - stream->next_time = stream->time; - } - stream->time = next_pcr_time; - /*if too ahead of mux time, don't insert PCR*/ - us_diff = gf_m2ts_time_diff_us(&stream->program->mux->time, &stream->time); - if (us_diff>1000) - return 0; - } else if (stream->pcr_only_mode) { - stream->pcr_only_mode = GF_FALSE; - stream->time = stream->next_time; + while (pay_size>=255) { + nb_bits += 8; + pay_size -= 255; } - return 1; -} + nb_bits += 8; + while (nb_bits % 8) + nb_bits++; + return nb_bits / 8; +} static u32 gf_m2ts_stream_process_pes(GF_M2TS_Mux *muxer, GF_M2TS_Mux_Stream *stream) { @@ -1503,8 +1611,10 @@ gf_bs_write_int(bs, 0x2B7, 11); gf_bs_write_int(bs, 0, 13); - /*same mux config = 0 (refresh aac config)*/ - stream_time_ms = (u32) (stream->time.sec*1000 + stream->time.nanosec/1000000); + //directly use dts - using stream->time will not work when estimating the overhead as this time may change + //between the overhead estimation call and the actual packet reframing + stream_time_ms = stream->curr_pck.dts/90; + if (stream->ifce->decoder_config && (!stream_time_ms || (stream->latm_last_aac_time + stream->refresh_rate_ms < stream_time_ms-1))) { #ifndef GPAC_DISABLE_AV_PARSERS GF_M4ADecSpecInfo cfg; @@ -1620,12 +1730,37 @@ /*since we reallocated the packet data buffer, force a discard in pull mode*/ stream->discard_data = GF_TRUE; break; - case GF_M2TS_METADATA_PES: case GF_M2TS_METADATA_ID3_HLS: - { - id3_tag_create(&stream->curr_pck.data, &stream->curr_pck.data_len); + m2ts_id3_tag_create(&stream->curr_pck.data, &stream->curr_pck.data_len); stream->discard_data = GF_TRUE; - } + break; + case GF_M2TS_METADATA_PES: + case GF_M2TS_METADATA_ID3_KLVA: + case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: + // nothing to do + if ((stream->ifce->stream_type==GF_STREAM_TEXT) && (stream->ifce->ra_code==GF_M2TS_RA_STREAM_SRT)) { + if (!stream->curr_pck.data_len || !gf_utf8_is_legal(stream->curr_pck.data, stream->curr_pck.data_len)) + return stream->scheduling_priority; + + char szHeader100, szDur100; + stream->num_frame++; + sprintf(szHeader, "%u\n00:00:00,000 --> ", stream->num_frame); + char *tx3g_format_time(u64 ts, u32 timescale, char *szDur, Bool is_srt); + tx3g_format_time(stream->curr_pck.duration, stream->ifce->timescale, szDur, GF_TRUE); + strcat(szHeader, szDur); + strcat(szHeader, "\n"); + u32 hlen = (u32) strlen(szHeader); + u8 *data = gf_malloc(stream->curr_pck.data_len+hlen+1); + memcpy(data, szHeader, hlen); + if (stream->curr_pck.data_len) + memcpy(data+hlen, stream->curr_pck.data, stream->curr_pck.data_len); + datastream->curr_pck.data_len + hlen = 0; + + stream->curr_pck.data_len = stream->curr_pck.data_len+hlen+1; + gf_free(stream->curr_pck.data); + stream->curr_pck.data = data; + stream->discard_data = GF_TRUE; + } break; default: if (stream->ifce->codecid==GF_CODECID_DVB_SUBS) { @@ -1636,6 +1771,7 @@ gf_free(stream->curr_pck.data); gf_bs_get_content(bs, &stream->curr_pck.data, &stream->curr_pck.data_len); gf_bs_del(bs); + stream->discard_data = GF_TRUE; } break; } @@ -1786,11 +1922,12 @@ } if (stream->next_payload_size) { - stream->next_payload_size += stream->reframe_overhead; + stream->next_payload_size += gf_m2ts_stream_get_reframe_overhead(stream, stream->pck_first->dts, stream->next_payload_size); + if (stream->next_next_payload_size) { - stream->next_next_payload_size += stream->reframe_overhead; + stream->next_next_payload_size += gf_m2ts_stream_get_reframe_overhead(stream, stream->pck_first->next->dts, stream->next_next_payload_size); if (stream->next_next_next_payload_size) { - stream->next_next_next_payload_size += stream->reframe_overhead; + stream->next_next_next_payload_size += gf_m2ts_stream_get_reframe_overhead(stream, stream->pck_first->next->next->dts, stream->next_next_payload_size); } } @@ -1893,20 +2030,7 @@ return GF_FALSE; } - if (stream->ifce->caps & GF_ESI_STREAM_IS_OVER) { -#if 0 - while (stream->copy_from_next_packets > stream->next_payload_size) { - if (stream->copy_from_next_packets < 184) { - stream->copy_from_next_packets = 0; - break; - } - stream->copy_from_next_packets -= 184; - } -#endif - stream->pes_data_len += stream->next_payload_size; - } else { - stream->pes_data_len += stream->copy_from_next_packets; - } + stream->pes_data_len += stream->copy_from_next_packets; } stream->pes_data_remain = stream->pes_data_len; return GF_TRUE; @@ -1956,7 +2080,7 @@ } /*PES packet length: number of bytes in the PES packet following the last byte of the field "pes packet length"*/ - gf_assert(stream->pes_data_len); + //we allow 0-length PES packet as some media streams may have a signification for this pes_len = stream->pes_data_len + 3; // 3 = header size if (use_pts) pes_len += 5; if (use_dts) pes_len += 5; @@ -2314,7 +2438,7 @@ u32 remain = 0; Bool res = stream->process(stream->program->mux, stream); if (!res) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG2-TS Muxer Not enough data to fill current PES (PID %d) - filling with 0xFF\n", stream->pid) ); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG2-TS Muxer Not enough data to fill current PES (PID %d) and %u bytes remaining - filling with 0xFF\n", stream->pes_data_remain, stream->pid) ); memset(packet+pos, 0xFF, copy_next); if (stream->copy_from_next_packets > copy_next) { @@ -2724,6 +2848,11 @@ stream->refresh_rate_ms = ifce->repeat_rate ? ifce->repeat_rate : 500; break; + case GF_CODECID_USAC: + stream->mpeg2_stream_type = GF_M2TS_AUDIO_LATM_AAC; + stream->refresh_rate_ms = ifce->repeat_rate ? ifce->repeat_rate : 500; + //stream->force_single_au = GF_TRUE; + break; case GF_CODECID_AC3: if (ifce->caps & GF_ESI_STREAM_HLS_SAES) stream->mpeg2_stream_type = GF_M2TS_HLS_AC3_CRYPT; @@ -2737,6 +2866,9 @@ stream->mpeg2_stream_type = GF_M2TS_AUDIO_EC3; break; + case GF_CODECID_AC4: + stream->mpeg2_stream_type = GF_M2TS_AUDIO_AC4; + break; case GF_CODECID_DTS_CA: case GF_CODECID_DTS_HD_HR_MASTER: case GF_CODECID_DTS_HD_LOSSLESS: @@ -2798,11 +2930,21 @@ break; default: stream->mpeg2_stream_type = GF_M2TS_METADATA_PES; - gf_m2ts_stream_add_metadata_pointer_descriptor(stream->program); - gf_m2ts_stream_add_metadata_descriptor(stream); + if (stream->ifce->ra_code) { + stream->force_reg_desc = GF_TRUE; + } else { + gf_m2ts_stream_add_metadata_pointer_descriptor(stream->program); + gf_m2ts_stream_add_metadata_descriptor(stream); + } break; } break; + case GF_STREAM_METADATA: + if (ifce->codecid == GF_CODECID_SCTE35) { + stream->mpeg2_stream_type = GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS; + break; + } + //fallthrough default: GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS Muxer Unsupported codec %s, signaling as raw data\n", gf_codecid_name(ifce->codecid) )); stream->mpeg2_stream_id = 0xBD;
View file
gpac-2.4.0.tar.gz/src/media_tools/m3u8.c -> gpac-26.02.0.tar.gz/src/media_tools/m3u8.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Pierre Souchay, Jean Le Feuvre, Romain Bouqueau - * Copyright (c) Telecom ParisTech 2010-2023 + * Copyright (c) Telecom ParisTech 2010-2025 * All rights reserved * * This file is part of GPAC @@ -43,17 +43,17 @@ char *name; u32 channels; MediaType type; - union { - char *audio; - char *video; - char *subtitle; - char *closed_captions; - } group; + char *group_audio; + char *group_video; + char *group_subtitle; + char *group_closed_captions; + Bool forced; + int target_duration_in_seconds; int min_media_sequence; int current_media_seq; - int version; - int compatibility_version; /*compute version required by the M3U8 content*/ + u32 version; + u32 compatibility_version; /*compute version required by the M3U8 content*/ Bool is_master_playlist; Bool is_media_segment; Bool is_playlist_ended; @@ -122,6 +122,12 @@ if (e->init_segment_url) { gf_free(e->init_segment_url); } + if (e->alt_bandwidths) { + gf_free(e->alt_bandwidths); + } + if (e->main_codecs) { + gf_free(e->main_codecs); + } memset(e->key_iv, 0, sizeof(bin128) ); if (e->url) gf_free(e->url); @@ -198,34 +204,7 @@ e->element.playlist.media_seq_max = 0; e->element.playlist.elements = gf_list_new(); if (NULL == (e->element.playlist.elements)) { - if (e->title) - gf_free(e->title); - if (e->codecs) - gf_free(e->codecs); - if (e->language) - gf_free(e->language); - if (e->name) - gf_free(e->name); - if (e->audio_group) - gf_free(e->audio_group); - if (e->video_group) - gf_free(e->video_group); - if (e->url) - gf_free(e->url); - if (e->init_segment_url) - gf_free(e->init_segment_url); - if (e->key_uri) - gf_free(e->key_uri); - e->url = NULL; - e->title = NULL; - e->codecs = NULL; - e->language = NULL; - e->name = NULL; - e->audio_group = NULL; - e->video_group = NULL; - e->key_uri = NULL; - memset(e->key_iv, 0, sizeof(bin128)); - gf_free(e); + playlist_element_del(e); return NULL; } } else { @@ -478,7 +457,9 @@ } else if (!strncmp(ret0+method_len, "AES-128", 7)) { attributes->key_method = DRM_AES_128; } else if (!strncmp(ret0+method_len, "SAMPLE-AES", 10)) { - attributes->key_method = DRM_CENC; + attributes->key_method = DRM_CENC_CBCS; + } else if (!strncmp(ret0+method_len, "SAMPLE-AES-CTR", 14)) { + attributes->key_method = DRM_CENC_CTR; } else { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("M3U8 EXT-X-KEY method not recognized.\n")); } @@ -489,7 +470,7 @@ attributes->key_url = gf_strdup(&(ret15)); if (attributes->key_url) { u32 klen = (u32) strlen(attributes->key_url); - attributes->key_urlklen-1 = 0; + attributes->key_urlklen ? klen-1 : 0 = 0; } } } @@ -593,7 +574,8 @@ int_value = (u32) strlen(reti); if (retiint_value-1 == '"') { if (attributes->codecs) gf_free(attributes->codecs); - attributes->codecs = gf_strdup(&(reti7)); + attributes->codecs = gf_strdup(&(reti8)); + if (attributes->codecs0) attributes->codecsstrlen(attributes->codecs)-1 = 0; } } else if (safe_start_equals("RESOLUTION=", reti)) { u32 w, h; @@ -606,14 +588,14 @@ } else if (safe_start_equals("AUDIO=", reti)) { gf_assert(attributes->type == MEDIA_TYPE_UNKNOWN); attributes->type = MEDIA_TYPE_AUDIO; - if (attributes->group.audio) gf_free(attributes->group.audio); - attributes->group.audio = gf_strdup(reti + 6); + if (attributes->group_audio) gf_free(attributes->group_audio); + attributes->group_audio = gf_strdup(reti + 6); M3U8_COMPATIBILITY_VERSION(4); } else if (safe_start_equals("VIDEO=", reti)) { gf_assert(attributes->type == MEDIA_TYPE_UNKNOWN); attributes->type = MEDIA_TYPE_VIDEO; - if (attributes->group.video) gf_free(attributes->group.video); - attributes->group.video = gf_strdup(reti + 6); + if (attributes->group_video) gf_free(attributes->group_video); + attributes->group_video = gf_strdup(reti + 6); M3U8_COMPATIBILITY_VERSION(4); } i++; @@ -664,6 +646,11 @@ /* #EXT-X-MEDIA:TYPE={AUDIO,VIDEO},URI,GROUP-ID,LANGUAGE,NAME,DEFAULT={YES,NO},AUTOSELECT={YES,NO} */ M3U8_COMPATIBILITY_VERSION(4); attributes->is_master_playlist = GF_TRUE; + attributes->bandwidth = 0; + attributes->width = 0; + attributes->height = 0; + attributes->channels = 0; + attributes->forced = GF_FALSE; i = 0; while (reti != NULL) { if (safe_start_equals("TYPE=", reti)) { @@ -690,21 +677,21 @@ } } else if (safe_start_equals("GROUP-ID=", reti)) { if (attributes->type == MEDIA_TYPE_AUDIO) { - if (attributes->group.audio) gf_free(attributes->group.audio); - attributes->group.audio = gf_strdup(reti+9); - attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(AUDIO, attributes->group.audio); + if (attributes->group_audio) gf_free(attributes->group_audio); + attributes->group_audio = gf_strdup(reti+9); + attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(AUDIO, attributes->group_audio); } else if (attributes->type == MEDIA_TYPE_VIDEO) { - if (attributes->group.video) gf_free(attributes->group.video); - attributes->group.video = gf_strdup(reti+9); - attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(VIDEO, attributes->group.video); + if (attributes->group_video) gf_free(attributes->group_video); + attributes->group_video = gf_strdup(reti+9); + attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(VIDEO, attributes->group_video); } else if (attributes->type == MEDIA_TYPE_SUBTITLES) { - if (attributes->group.subtitle) gf_free(attributes->group.subtitle); - attributes->group.subtitle = gf_strdup(reti+9); - attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(SUBTITLES, attributes->group.subtitle); + if (attributes->group_subtitle) gf_free(attributes->group_subtitle); + attributes->group_subtitle = gf_strdup(reti+9); + attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(SUBTITLES, attributes->group_subtitle); } else if (attributes->type == MEDIA_TYPE_CLOSED_CAPTIONS) { - if (attributes->group.closed_captions) gf_free(attributes->group.closed_captions); - attributes->group.closed_captions = gf_strdup(reti+9); - attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(CLOSED_CAPTIONS, attributes->group.closed_captions); + if (attributes->group_closed_captions) gf_free(attributes->group_closed_captions); + attributes->group_closed_captions = gf_strdup(reti+9); + attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(CLOSED_CAPTIONS, attributes->group_closed_captions); } else if (attributes->type == MEDIA_TYPE_UNKNOWN) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("M3U8 Invalid #EXT-X-MEDIA:GROUP-ID=%s. Ignoring the line.\n", reti+9)); free_attrs(ret); @@ -712,8 +699,10 @@ } } else if (safe_start_equals("LANGUAGE=\"", reti)) { size_t len; + u32 offset=9; + if (reti9 == '"') offset++; if (attributes->language) gf_free(attributes->language); - attributes->language = gf_strdup(reti+9); + attributes->language = gf_strdup(reti+offset); len = strlen(attributes->language); if (len && (attributes->languagelen-1 == '"')) { attributes->languagelen-1 = '\0'; @@ -743,11 +732,13 @@ } } else if (safe_start_equals("CHANNELS=", reti)) { sscanf(reti + 9, "\"%u\"", &attributes->channels); - + } else if (safe_start_equals("INSTREAM-ID=", reti)) { + //we don't signal CC for now + } else if (safe_start_equals("FORCED=", reti)) { + attributes->forced = !stricmp(reti + 7, "yes") ? GF_TRUE : GF_FALSE; } else { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("M3U8 Attribute %s not supported\n", reti)); } - i++; } @@ -761,8 +752,8 @@ gf_free(attributes->mediaURL); attributes->mediaURL = NULL; } - if ((attributes->type == MEDIA_TYPE_AUDIO && !attributes->group.audio) - || (attributes->type == MEDIA_TYPE_VIDEO && !attributes->group.video)) { + if ((attributes->type == MEDIA_TYPE_AUDIO && !attributes->group_audio) + || (attributes->type == MEDIA_TYPE_VIDEO && !attributes->group_video)) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("M3U8 Invalid #EXT-X-MEDIA: missing GROUP-ID attribute. Ignoring the line.\n")); free_attrs(ret); return NULL; @@ -804,6 +795,10 @@ if (!strncmp(line, "#EXT-X-RENDITION-REPORT", strlen("#EXT-X-RENDITION-REPORT") )) { return NULL; } + //TODO for now we don't support interstitials + if (!strncmp(line, "#EXT-X-DATERANGE", strlen("#EXT-X-DATERANGE") )) { + return NULL; + } GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("M3U8 Unsupported directive %s\n", line)); return NULL; } @@ -951,6 +946,32 @@ if (attribs->is_master_playlist) { if (curr_playlist != NULL) { //playlist has already been defined - this happens when the same video playlist is defined several times with different audio codecs ... + if (!attribs->codecs || !curr_playlist->audio_group || !attribs->group_audio || strstr(curr_playlist->audio_group, attribs->group_audio)) + return GF_OK; + //gather codecs and bandwidth so that we can recompute them when generating the MPD + if (!curr_playlist->alt_bandwidths) { + curr_playlist->nb_alt_bandwidths = 1; + curr_playlist->alt_bandwidths = gf_malloc(sizeof(u32)); + curr_playlist->alt_bandwidths0 = curr_playlist->bandwidth; + } + char *codec = attribs->codecs; + while (codec) { + char *sep = strchr(codec, ','); + if (sep) sep0 = 0; + if (!curr_playlist->codecs) + gf_dynstrcat(&curr_playlist->codecs, codec, NULL); + else if (!strstr(curr_playlist->codecs, codec)) + gf_dynstrcat(&curr_playlist->codecs, codec, ","); + else if (!curr_playlist->main_codecs || !strstr(curr_playlist->main_codecs, codec)) + gf_dynstrcat(&curr_playlist->main_codecs, codec, ","); + if (!sep) break; + sep0 = ','; + codec = sep+1; + } + gf_dynstrcat(&curr_playlist->audio_group, attribs->group_audio, ","); + curr_playlist->alt_bandwidths = gf_realloc(curr_playlist->alt_bandwidths, sizeof(u32)*(curr_playlist->nb_alt_bandwidths+1) ); + curr_playlist->alt_bandwidthscurr_playlist->nb_alt_bandwidths = attribs->bandwidth; + curr_playlist->nb_alt_bandwidths++; return GF_OK; } curr_playlist = playlist_element_new(TYPE_PLAYLIST, fullURL, attribs); @@ -974,6 +995,7 @@ if (curr_playlist->video_group) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("M3U8 Warning: found an VIDEO group in the master playlist.")); } + curr_playlist->audio_group = attribs->group_audio ? gf_strdup(attribs->group_audio) : NULL; gf_list_add(stream->variants, curr_playlist); curr_playlist->width = attribs->width; curr_playlist->height = attribs->height; @@ -1074,13 +1096,21 @@ gf_free(attribs->name); attribs->name = NULL; } - if (attribs->group.audio != NULL) { - gf_free(attribs->group.audio); - attribs->group.audio = NULL; + if (attribs->group_audio != NULL) { + gf_free(attribs->group_audio); + attribs->group_audio = NULL; } - if (attribs->group.video != NULL) { - gf_free(attribs->group.video); - attribs->group.video = NULL; + if (attribs->group_video != NULL) { + gf_free(attribs->group_video); + attribs->group_video = NULL; + } + if (attribs->group_subtitle != NULL) { + gf_free(attribs->group_subtitle); + attribs->group_subtitle = NULL; + } + if (attribs->group_closed_captions != NULL) { + gf_free(attribs->group_closed_captions); + attribs->group_closed_captions = NULL; } return GF_OK; } @@ -1099,7 +1129,10 @@ #define RST_ATTR(_name) if (attribs->_name) { gf_free(attribs->_name); attribs->_name = NULL; } RST_ATTR(codecs) - RST_ATTR(group.audio) + RST_ATTR(group_audio) + RST_ATTR(group_video) + RST_ATTR(group_subtitle) + RST_ATTR(group_closed_captions) RST_ATTR(language) RST_ATTR(title) if (is_cleanup) { @@ -1280,6 +1313,9 @@ (*playlist)->low_latency = GF_TRUE; attribs.low_latency = GF_FALSE; } + if (attribs.version > (*playlist)->version) { + (*playlist)->version = attribs.version; + } if (attribs.mediaURL) { GF_Err e = declare_sub_playlist(attribs.mediaURL, baseURL, &attribs, sub_playlist, playlist, in_stream); gf_free(attribs.mediaURL);
View file
gpac-2.4.0.tar.gz/src/media_tools/media_export.c -> gpac-26.02.0.tar.gz/src/media_tools/media_export.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / Media Tools sub-project @@ -390,7 +390,6 @@ GF_ISOFile *outfile; GF_Err e; Bool add_to_iod, is_stdout; - char szName1000; u32 track; GF_ISOOpenMode mode; @@ -406,25 +405,27 @@ dumper->flags |= GF_EXPORT_MERGE; return GF_OK; } + char *in_name = NULL; if (dumper->out_name && gf_file_ext_start(dumper->out_name)) { - strcpy(szName, dumper->out_name); + gf_dynstrcat(&in_name, dumper->out_name, NULL); } else { char *ext = (char *) gf_isom_get_filename(dumper->file); if (ext) ext = gf_file_ext_start(ext); - sprintf(szName, "%s%s", dumper->out_name, ext ? ext : ".mp4"); + gf_dynstrcat(&in_name, dumper->out_name, NULL); + gf_dynstrcat(&in_name, ext ? ext : ".mp4", NULL); } is_stdout = (dumper->out_name && !strcmp(dumper->out_name, "std")) ? 1 : 0; add_to_iod = 1; mode = GF_ISOM_WRITE_EDIT; if (!is_stdout && (dumper->flags & GF_EXPORT_MERGE)) { - FILE *t = gf_fopen(szName, "rb"); + FILE *t = gf_fopen(in_name, "rb"); if (t) { add_to_iod = 0; mode = GF_ISOM_OPEN_EDIT; gf_fclose(t); } } - outfile = gf_isom_open(is_stdout ? "std" : szName, mode, NULL); + outfile = gf_isom_open(is_stdout ? "std" : in_name, mode, NULL); if (mode == GF_ISOM_WRITE_EDIT) { gf_isom_set_pl_indication(outfile, GF_ISOM_PL_AUDIO, 0xFF); @@ -452,8 +453,9 @@ gf_isom_keep_utc_times(outfile, GF_TRUE); if (e) gf_isom_delete(outfile); - else gf_isom_close(outfile); + else e = gf_isom_close(outfile); + gf_free(in_name); return e; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -522,17 +524,19 @@ gf_fprintf(vtt, "WEBVTT Metadata track generated by GPAC MP4Box %s\n", gf_sys_is_test_mode() ? "" : gf_gpac_version()); gf_fprintf(vtt, "kind:metadata\n"); - { - char *lang; - gf_isom_get_media_language(dumper->file, track, &lang); + + char *lang; + gf_isom_get_media_language(dumper->file, track, &lang); + if (lang) { gf_fprintf(vtt, "language:%s\n", lang); gf_free(lang); } - { - const char *handler; - gf_isom_get_handler_name(dumper->file, track, &handler); + + const char *handler; + gf_isom_get_handler_name(dumper->file, track, &handler); + if (handler) gf_fprintf(vtt, "label: %s\n", handler); - } + if (gf_isom_is_track_in_root_od(dumper->file, track)) gf_fprintf(vtt, "inRootOD: yes\n"); gf_fprintf(vtt, "trackID: %d\n", dumper->trackID); if (med) { @@ -830,14 +834,18 @@ u32 size; Bool is_stdout = 0; FILE *saf_f; - SAFInfo safs1024; GF_Err e=GF_OK; if (dumper->flags & GF_EXPORT_PROBE_ONLY) return GF_OK; s_count = tot_samp = 0; - mux = gf_saf_mux_new(); count = gf_isom_get_track_count(dumper->file); + + SAFInfo *safs; + safs = gf_malloc(sizeof(SAFInfo) * count); + if (!safs) return GF_OUT_OF_MEM; + memset(safs, 0, sizeof(SAFInfo) * count); + for (i=0; i<count; i++) { u32 time_scale, mtype, stream_id = 0; GF_ESD *esd; @@ -901,6 +909,7 @@ if (!s_count) { gf_export_message(dumper, GF_OK, "No tracks available for SAF muxing"); gf_saf_mux_del(mux); + gf_free(safs); return GF_OK; } gf_export_message(dumper, GF_OK, "SAF: Multiplexing %d tracks", s_count); @@ -910,6 +919,11 @@ strcpy(out_file, dumper->out_name ? dumper->out_name : ""); strcat(out_file, ".saf"); saf_f = is_stdout ? stdout : gf_fopen(out_file, "wb"); + if (!saf_f) { + gf_saf_mux_del(mux); + gf_free(safs); + return GF_BAD_PARAM; + } samp_done = 0; while (samp_done<tot_samp) { @@ -919,6 +933,7 @@ samp = gf_isom_get_sample(dumper->file, safsi.track_num, safsi.last_sample + 1, &di); if (!samp) { gf_saf_mux_del(mux); + gf_free(safs); return gf_isom_last_error(dumper->file); } @@ -946,6 +961,7 @@ gf_fclose(saf_f); gf_saf_mux_del(mux); + gf_free(safs); return e; #else return GF_NOT_SUPPORTED; @@ -1092,16 +1108,20 @@ #ifdef GPAC_DISABLE_MEDIA_IMPORT return GF_NOT_SUPPORTED; #else - GF_MediaImporter import; - memset(&import, 0, sizeof(GF_MediaImporter)); - import.flags = GF_IMPORT_PROBE_ONLY; - import.in_name = dumper->in_name; - e = gf_media_import(&import); - if (e) return e; + GF_MediaImporter *import; + GF_SAFEALLOC(import, GF_MediaImporter); + if (!import) return GF_OUT_OF_MEM; + import->flags = GF_IMPORT_PROBE_ONLY; + import->in_name = dumper->in_name; + e = gf_media_import(import); + if (e) { + gf_free(import); + return e; + } Bool found = GF_FALSE; u32 i; - for (i=0; i<import.nb_tracks; i++) { - struct __track_import_info *tki = &import.tk_infoi; + for (i=0; i<import->nb_tracks; i++) { + struct __track_import_info *tki = &import->tk_infoi; if (!tki->codecid) continue; if (dumper->trackID) { if (dumper->trackID != tki->track_num) continue; @@ -1129,6 +1149,7 @@ dumper->track_type = 0; break; } + gf_free(import); if (!found) return GF_NOT_FOUND; #endif } @@ -1215,7 +1236,7 @@ } if (load_dest) { - //skip fout:dst= whenever we have an extension specified, to allow using meta filters (ffmx) + //skip fout:dst= whenever we have an extension specified, to allow using meta filters (ffmx) file_out = gf_fs_load_destination(fsess, args+9, NULL, NULL, &e); } else { file_out = gf_fs_load_filter(fsess, args, &e); @@ -1259,7 +1280,7 @@ if (dumper->flags & GF_EXPORT_NHML_FULL) e |= gf_dynstrcat(&args, ":pckp", NULL); if (dumper->dump_file) { - sprintf(szSubArgs, ":nhmlonly:filep=%p", dumper->dump_file); + sprintf(szSubArgs, ":nhmlonly:payload:filep=%p", dumper->dump_file); e |= gf_dynstrcat(&args, szSubArgs, NULL); } remux = e ? NULL : gf_fs_load_filter(fsess, args, &e); @@ -1318,7 +1339,7 @@ sprintf(szSubArgs, ":mov=%p", dumper->file); e = gf_dynstrcat(&args, szSubArgs, NULL); } - + //we want to expose every track src_filter = gf_fs_load_filter(fsess, args, &e);
View file
gpac-2.4.0.tar.gz/src/media_tools/media_import.c -> gpac-26.02.0.tar.gz/src/media_tools/media_import.c
Changed
@@ -165,12 +165,12 @@ else if (level_check <= 3840*2160*30) dv_level = 7; else if (level_check <= 3840*2160*48) dv_level = 8; else if (level_check <= 3840*2160*60) dv_level = 9; - else if (level_check <= 3840*2160*120) { - if (width == 7680) dv_level = 11; - else dv_level = 10; - } - else if (level_check <= 7680*4320*60) dv_level = 12; - else dv_level = 13; + else if (level_check <= 3840*2160*120) { + if (width == 7680) dv_level = 11; + else dv_level = 10; + } + else if (level_check <= 7680*4320*60) dv_level = 12; + else dv_level = 13; } return dv_level; } @@ -300,6 +300,7 @@ GF_Err e; u64 offset, sampDTS, duration, dts_offset; Bool is_nalu_video=GF_FALSE; + u32 inject_rap_sample_group = 0; u32 track, di, trackID, track_in, i, num_samples, mtype, w, h, sr, sbr_sr, ch, mstype, cur_extract_mode, cdur, bps; u64 mtimescale; s32 trans_x, trans_y; @@ -383,51 +384,82 @@ } mtype = gf_isom_get_media_type(import->orig, track_in); if (mtype==GF_ISOM_MEDIA_VISUAL) { + Bool skip_profile=GF_FALSE; u8 PL = iod ? iod->visual_profileAndLevel : 0xFE; gf_isom_get_visual_info(import->orig, track_in, 1, &w, &h); -#ifndef GPAC_DISABLE_AV_PARSERS /*for MPEG-4 visual, always check size (don't trust input file)*/ - if (origin_esd - && origin_esd->decoderConfig - && (origin_esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) - ) { - if (origin_esd->decoderConfig->decoderSpecificInfo) { - GF_M4VDecSpecInfo dsi; - gf_m4v_get_config(origin_esd->decoderConfig->decoderSpecificInfo->data, origin_esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); - w = dsi.width; - h = dsi.height; - PL = dsi.VideoPL; - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Missing DecoderSpecificInfo in MPEG-4 Visual (Part2) stream\n")); + if (origin_esd && origin_esd->decoderConfig) { + switch (origin_esd->decoderConfig->objectTypeIndication) { +#ifndef GPAC_DISABLE_AV_PARSERS + case GF_CODECID_MPEG4_PART2: + if (origin_esd->decoderConfig->decoderSpecificInfo) { + GF_M4VDecSpecInfo dsi; + gf_m4v_get_config(origin_esd->decoderConfig->decoderSpecificInfo->data, origin_esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); + w = dsi.width; + h = dsi.height; + PL = dsi.VideoPL; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Missing DecoderSpecificInfo in MPEG-4 Visual (Part2) stream\n")); + } + break; +#endif + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_SPATIAL: + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + break; + default: + skip_profile=GF_TRUE; + break; } } -#endif - gf_isom_set_pl_indication(import->dest, GF_ISOM_PL_VISUAL, PL); + if (!skip_profile) + gf_isom_set_pl_indication(import->dest, GF_ISOM_PL_VISUAL, PL); } else if (mtype==GF_ISOM_MEDIA_AUDIO) { + Bool skip_profile = GF_FALSE; u8 PL = iod ? iod->audio_profileAndLevel : 0xFE; bps = 16; sr = ch = sbr_sr = 0; sbr = GF_FALSE; ps = GF_FALSE; gf_isom_get_audio_info(import->orig, track_in, 1, &sr, &ch, &bps); + if (origin_esd && origin_esd->decoderConfig) { + switch (origin_esd->decoderConfig->objectTypeIndication) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: #ifndef GPAC_DISABLE_AV_PARSERS - if (origin_esd && origin_esd->decoderConfig && (origin_esd->decoderConfig->objectTypeIndication==GF_CODECID_AAC_MPEG4)) { - if (origin_esd->decoderConfig->decoderSpecificInfo) { - GF_M4ADecSpecInfo dsi; - gf_m4a_get_config(origin_esd->decoderConfig->decoderSpecificInfo->data, origin_esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); - sr = dsi.base_sr; - if (dsi.has_sbr) sbr_sr = dsi.sbr_sr; - ch = dsi.nb_chan; - PL = dsi.audioPL; - sbr = dsi.has_sbr ? ((dsi.base_object_type==GF_M4A_AAC_SBR || dsi.base_object_type==GF_M4A_AAC_PS) ? 2 : 1) : GF_FALSE; - ps = dsi.has_ps; - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Missing DecoderSpecificInfo in MPEG-4 AAC stream\n")); + if (origin_esd->decoderConfig->decoderSpecificInfo) { + GF_M4ADecSpecInfo dsi; + gf_m4a_get_config(origin_esd->decoderConfig->decoderSpecificInfo->data, origin_esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); + sr = dsi.base_sr; + if (dsi.has_sbr) sbr_sr = dsi.sbr_sr; + ch = dsi.nb_chan; + PL = dsi.audioPL; + sbr = dsi.has_sbr ? ((dsi.base_object_type==GF_M4A_AAC_SBR || dsi.base_object_type==GF_M4A_AAC_PS) ? 2 : 1) : GF_FALSE; + ps = dsi.has_ps; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Missing DecoderSpecificInfo in MPEG-4 AAC stream\n")); + } + break; +#endif + case GF_CODECID_MPEG_AUDIO: + break; + default: + skip_profile=GF_TRUE; + break; } } -#endif - gf_isom_set_pl_indication(import->dest, GF_ISOM_PL_AUDIO, PL); + if (!skip_profile) + gf_isom_set_pl_indication(import->dest, GF_ISOM_PL_AUDIO, PL); } else if (mtype==GF_ISOM_MEDIA_SUBPIC) { w = h = 0; @@ -500,12 +532,12 @@ case GF_ISOM_MEDIA_VISUAL: gf_import_message(import, GF_OK, "IsoMedia import %s - track ID %d - Video (size %d x %d)", orig_name, trackID, w, h); break; - case GF_ISOM_MEDIA_AUXV: - gf_import_message(import, GF_OK, "IsoMedia import %s - track ID %d - Auxiliary Video (size %d x %d)", orig_name, trackID, w, h); - break; - case GF_ISOM_MEDIA_PICT: - gf_import_message(import, GF_OK, "IsoMedia import %s - track ID %d - Picture sequence (size %d x %d)", orig_name, trackID, w, h); - break; + case GF_ISOM_MEDIA_AUXV: + gf_import_message(import, GF_OK, "IsoMedia import %s - track ID %d - Auxiliary Video (size %d x %d)", orig_name, trackID, w, h); + break; + case GF_ISOM_MEDIA_PICT: + gf_import_message(import, GF_OK, "IsoMedia import %s - track ID %d - Picture sequence (size %d x %d)", orig_name, trackID, w, h); + break; case GF_ISOM_MEDIA_AUDIO: { if (ps) { @@ -575,7 +607,7 @@ break; case GF_ISOM_SUBTYPE_VVC1: gf_isom_set_nalu_extract_mode(import->orig, track_in, GF_ISOM_NALU_EXTRACT_INSPECT | GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG); - //gf_isom_vvc_set_inband_config(import->dest, track, 1, (import->xps_inband==2) ? GF_TRUE : GF_FALSE); + gf_isom_vvc_set_inband_config(import->dest, track, 1, (import->xps_inband==2) ? GF_TRUE : GF_FALSE); break; } } @@ -599,6 +631,7 @@ } num_samples = gf_isom_get_sample_count(import->orig, track_in); + if (is_cenc) { u32 container_type; e = gf_isom_cenc_get_sample_aux_info(import->orig, track_in, 0, 1, &container_type, NULL, NULL); @@ -626,11 +659,21 @@ gf_isom_sample_del(&samp); break; } + + if ((import->flags & GF_IMPORT_FORCE_SYNC) && ((samp->IsRAP==SAP_TYPE_3) || (samp->IsRAP==SAP_TYPE_4))) { + samp->IsRAP = SAP_TYPE_1; + } + e = gf_isom_add_sample_reference(import->dest, track, di, samp, offset); } else { samp = gf_isom_get_sample(import->orig, track_in, i+1, &di); if (!samp) { /*couldn't get the sample, but still move on*/ + e = gf_isom_last_error(import->orig); + if (e==GF_ISOM_INCOMPLETE_FILE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("ISOM import Incomplete file detected, aborting track #%d import after %d / %d samples\n", track_in, i+1, num_samples)); + e = GF_OK; + } goto exit; } samp->DTS -= dts_offset; @@ -661,11 +704,28 @@ break; } + if ((import->flags & GF_IMPORT_FORCE_SYNC) && ((samp->IsRAP==SAP_TYPE_3) || (samp->IsRAP==SAP_TYPE_4))) { + samp->IsRAP = SAP_TYPE_1; + } + e = gf_isom_add_sample(import->dest, track, di, samp); } sampDTS = samp->DTS; if (samp->nb_pack) i+= samp->nb_pack-1; + + //when importing files indicating SAP3 as sync points, we will drop all SAP3 points - check if we need to add 'rap ' + //sample to group info, ie if it was not present in source track - cf #2992 + if (!(import->flags & GF_IMPORT_FORCE_SYNC) && (samp->IsRAP == SAP_TYPE_3)) { + if (!inject_rap_sample_group) { + u32 sgdi=0; + gf_isom_get_sample_to_group_info(import->orig, track_in, i+1, GF_ISOM_SAMPLE_GROUP_RAP, 0, &sgdi); + inject_rap_sample_group = sgdi ? 1 : 2; + } + if (inject_rap_sample_group == 2) { + e = gf_isom_set_sample_rap_group(import->dest, track, i+1, GF_TRUE, 0); + } + } gf_isom_sample_del(&samp); //this will also copy all sample to group mapping, including seig for CENC @@ -835,6 +895,7 @@ Bool found_chap = GF_FALSE; u32 i, h, m, s, ms, fr, fps; char line1024; + char szTemp1025; char szTitle1024; FILE *f = gf_fopen(import->in_name, "rt"); if (!f) return GF_URL_ERROR; @@ -904,7 +965,7 @@ for (i=0; i<gf_isom_get_track_count(import->dest); i++) { GF_ISOSample *samp; u32 timescale, inc; - u32 mtype = gf_isom_get_media_type(import->dest, i+1); + u32 mtype = gf_isom_get_media_type(import->dest, i+1); if (!gf_isom_is_video_handler_type(mtype)) continue; if (gf_isom_get_sample_count(import->dest, i+1) < 20) continue; samp = gf_isom_get_sample_info(import->dest, 1, 2, NULL, NULL); @@ -953,7 +1014,7 @@ if (!strnicmp(sL, "AddChapter(", 11)) { u32 nb_fr; sscanf(sL, "AddChapter(%u,%1023s)", &nb_fr, szTitle); - ts = gf_timestamp_rescale(nb_fr, 1000 * import->video_fps.den, import->video_fps.num); + ts = gf_timestamp_rescale(nb_fr, import->video_fps.num, 1000 * import->video_fps.den); sL = strchr(sL, ','); strcpy(szTitle, sL+1); sL = strrchr(szTitle, ')'); @@ -986,7 +1047,7 @@ ts = (h*3600 + m*60+s)*1000; } else { - char szTS1025, *tok; + char *tok, *szTS = szTemp; strncpy(szTS, sL, 1024); szTS1024=0; tok = strrchr(szTS, ' '); @@ -1024,7 +1085,7 @@ /*CHAPTERX= and CHAPTERXNAME=*/ else if (!strnicmp(sL, "CHAPTER", 7)) { u32 idx; - char szTemp1025, *str; + char *str; strncpy(szTemp, sL, 1024); szTemp1024 = 0; str = strrchr(szTemp, '='); @@ -1094,7 +1155,7 @@ { GF_Err e; u32 i; - GF_MediaImporter import; + GF_MediaImporter *import = NULL; //remove all chapter info gf_isom_remove_chapter(file, 0, 0); @@ -1111,17 +1172,18 @@ } } - memset(&import, 0, sizeof(GF_MediaImporter)); - import.dest = file; - import.in_name = chap_file; - import.video_fps = import_fps; - import.streamFormat = "CHAP"; - e = gf_media_import(&import); - if (e) return e; - - if (!import.final_trackID) return GF_OK; + GF_SAFEALLOC(import, GF_MediaImporter); + import->dest = file; + import->in_name = chap_file; + import->video_fps = import_fps; + import->streamFormat = "CHAP"; + e = gf_media_import(import); + if (e || !import->final_trackID) { + gf_free(import); + return e; + } if (use_qt) { - u32 chap_track = gf_isom_get_track_by_id(file, import.final_trackID); + u32 chap_track = gf_isom_get_track_by_id(file, import->final_trackID); u32 nb_sdesc = gf_isom_get_sample_description_count(file, chap_track); for (i=0; i<nb_sdesc; i++) { gf_isom_set_media_subtype(file, chap_track, i+1, GF_ISOM_SUBTYPE_TEXT); @@ -1135,10 +1197,11 @@ case GF_ISOM_MEDIA_AUXV: case GF_ISOM_MEDIA_PICT: case GF_ISOM_MEDIA_AUDIO: - gf_isom_set_track_reference(file, i+1, GF_ISOM_REF_CHAP, import.final_trackID); + gf_isom_set_track_reference(file, i+1, GF_ISOM_REF_CHAP, import->final_trackID); break; } } + gf_free(import); return GF_OK; } @@ -1255,7 +1318,8 @@ p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); tki->codecid = p ? p->value.uint : GF_CODECID_NONE; p = gf_filter_pid_get_property(pid, GF_PROP_PID_LANGUAGE); - if (p && p->value.string) tki->lang = GF_4CC(p->value.string0, p->value.string1, p->value.string2, ' '); + if (p && p->value.string && (strlen(p->value.string)>=3)) + tki->lang = GF_4CC(p->value.string0, p->value.string1, p->value.string2, ' '); p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); tki->track_num = p ? p->value.uint : 1; p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); @@ -1283,7 +1347,7 @@ if (p->value.frac.den) tki->video_info.FPS /= p->value.frac.den; } p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); - if (p) tki->video_info.par = (p->value.frac.num << 16) | p->value.frac.den; + if (p && (p->value.frac.num>0)) tki->video_info.par = (p->value.frac.num << 16) | p->value.frac.den; } p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); if (p) { @@ -1372,6 +1436,7 @@ e |= gf_dynstrcat(&args, szSubArg, ":"); } if (importer->asemode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2) { e |= gf_dynstrcat(&args, "ase=v0s", ":"); } + else if (importer->asemode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS) { e |= gf_dynstrcat(&args, "ase=v0bs", ":"); } else if (importer->asemode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG) { e |= gf_dynstrcat(&args, "ase=v1", ":"); } else if (importer->asemode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF) { e |= gf_dynstrcat(&args, "ase=v1qt", ":"); }
View file
gpac-2.4.0.tar.gz/src/media_tools/mpd.c -> gpac-26.02.0.tar.gz/src/media_tools/mpd.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Cyril Concolato - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2026 * All rights reserved * * This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module @@ -396,6 +396,8 @@ if (seg_tl_ent->repeat_count == (u32)-1) seg_tl_ent->repeat_count--; } + else if (!strcmp(att->name, "k")) + seg_tl_ent->nb_parts = gf_mpd_parse_int(att->value); } if (seg_tl_ent->start_time) curr_start_time = seg_tl_ent->start_time; @@ -477,11 +479,11 @@ else if (!strcmp(att->name, "hls:keyURL")) seg->key_url = gf_mpd_parse_string(att->value); else if (!strcmp(att->name, "hls:keyIV")) { GF_Err e = gf_bin128_parse(att->value, seg->key_iv); - if (e != GF_OK) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("MPD Cannot parse hls:keyIV\n")); - return; - } - } + if (e != GF_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("MPD Cannot parse hls:keyIV\n")); + return; + } + } else if (!strcmp(att->name, "duration")) seg->duration=gf_mpd_parse_int(att->value); else if (!strcmp(att->name, "t")) seg->first_tfdt = gf_mpd_parse_long_int(att->value); else if (!strcmp(att->name, "n")) seg->first_pck_seq = gf_mpd_parse_int(att->value); @@ -551,6 +553,8 @@ seg->initialization = gf_mpd_parse_string(att->value); } else if (!strcmp(att->name, "bitstreamSwitching")) seg->bitstream_switching = gf_mpd_parse_string(att->value); + else if (!strcmp(att->name, "k")) + seg->nb_parts = gf_mpd_parse_int(att->value); } gf_mpd_parse_multiple_segment_base(mpd, (GF_MPD_MultipleSegmentBase *)seg, root); return seg; @@ -573,6 +577,20 @@ return GF_OK; } +static GF_Err gf_mpd_parse_inband_event(GF_List *comps, GF_XMLNode *root) { + u32 i; + GF_XMLAttribute *att; + GF_MPD_Inband_Event *ibe; + GF_SAFEALLOC(ibe, GF_MPD_Inband_Event); + if (!ibe) return GF_OUT_OF_MEM; + i = 0; + while ((att = gf_list_enum(root->attributes, &i))) { + if (!strcmp(att->name, "schemeIdUri")) ibe->scheme_id_uri = gf_strdup(att->value); + else if (!strcmp(att->name, "value")) ibe->value = gf_strdup(att->value); + } + gf_list_add(comps, ibe); + return GF_OK; +} static GF_Err gf_mpd_parse_descriptor_ex(GF_List *container, GF_MPD_Descriptor **out_ptr, GF_XMLNode *root) { @@ -765,7 +783,7 @@ else if (!strcmp(att->name, "dashDuration")) dasher->dash_dur = gf_mpd_parse_fraction(att->value); else if (!strcmp(att->name, "nextSegmentStart")) dasher->next_seg_start = gf_mpd_parse_long_int(att->value); else if (!strcmp(att->name, "firstCTS")) dasher->first_cts = gf_mpd_parse_long_int(att->value); - else if (!strcmp(att->name, "firstDTS")) dasher->first_cts = gf_mpd_parse_long_int(att->value); + else if (!strcmp(att->name, "firstDTS")) dasher->first_dts = gf_mpd_parse_long_int(att->value); else if (!strcmp(att->name, "estimatedNextDTS")) dasher->est_next_dts = gf_mpd_parse_long_int(att->value); else if (!strcmp(att->name, "nbRepeat")) dasher->nb_repeat = gf_mpd_parse_int(att->value); else if (!strcmp(att->name, "tsOffset")) dasher->ts_offset = gf_mpd_parse_long_int(att->value); @@ -895,6 +913,7 @@ set->rating = gf_list_new(); set->viewpoint = gf_list_new(); set->content_component = gf_list_new(); + set->inband_event = gf_list_new(); set->base_URLs = gf_list_new(); set->representations = gf_list_new(); GF_SAFEALLOC(set->par, GF_MPD_Fractional); @@ -975,8 +994,10 @@ else if (!strcmp(child->name, "ContentComponent")) { e = gf_mpd_parse_content_component(set->content_component, child); if (e) return e; - } - else if (!strcmp(child->name, "SegmentBase")) { + } else if(!strcmp(child->name, "InbandEventStream")) { + e = gf_mpd_parse_inband_event(set->inband_event, child); + if (e) return e; + } else if (!strcmp(child->name, "SegmentBase")) { set->segment_base = gf_mpd_parse_segment_base(mpd, child); } else if (!strcmp(child->name, "SegmentList")) { @@ -1228,6 +1249,13 @@ gf_free(item); } +void gf_mpd_inband_event_free(void *item) { + GF_MPD_Inband_Event *inband_event_desc = (GF_MPD_Inband_Event *) item; + if (inband_event_desc->scheme_id_uri) gf_free(inband_event_desc->scheme_id_uri); + if (inband_event_desc->value) gf_free(inband_event_desc->value); + gf_free(item); +} + void gf_mpd_producer_reftime_free(void *item) { GF_MPD_ProducerReferenceTime *pref=(GF_MPD_ProducerReferenceTime*) item; @@ -1301,12 +1329,14 @@ GF_DASH_SegmentContext *s = gf_list_pop_back(ptr->state_seg_list); if (s->filename) gf_free(s->filename); if (s->filepath) gf_free(s->filepath); + if (s->llhas_template) gf_free(s->llhas_template); if (s->frags) gf_free(s->frags); if (s->hls_key_uri) gf_free(s->hls_key_uri); gf_free(s); } gf_list_del(ptr->state_seg_list); } + if (ptr->m3u8_name) gf_free(ptr->m3u8_name); if (ptr->m3u8_var_name) gf_free(ptr->m3u8_var_name); if (ptr->m3u8_var_file) gf_fclose(ptr->m3u8_var_file); if (ptr->res_url) gf_free(ptr->res_url); @@ -1326,6 +1356,7 @@ gf_mpd_del_list(ptr->rating, gf_mpd_descriptor_free, 0); gf_mpd_del_list(ptr->viewpoint, gf_mpd_descriptor_free, 0); gf_mpd_del_list(ptr->content_component, gf_mpd_content_component_free, 0); + if (ptr->inband_event) gf_mpd_del_list(ptr->inband_event, gf_mpd_inband_event_free, 0); if (ptr->segment_base) gf_mpd_segment_base_free(ptr->segment_base); if (ptr->segment_list) gf_mpd_segment_list_free(ptr->segment_list); if (ptr->segment_template) gf_mpd_segment_template_free(ptr->segment_template); @@ -1533,6 +1564,64 @@ return gf_mpd_complete_from_dom(root, mpd, default_base_url); } +//locate codec in renditions and try to extract bandwidth (we can't really) +static char *group_to_codecs(MasterPlaylist *pl, PlaylistElement *pe, u32 *bandwidth) +{ + PlaylistElement *par_pe=NULL; + u32 stidx, k, nb_streams = gf_list_count(pl->streams);; + for (stidx=0; stidx<nb_streams; stidx++) { + Stream *astream = gf_list_get(pl->streams, stidx); + for (k=0; k<gf_list_count(astream->variants); k++) { + par_pe = gf_list_get(astream->variants, k); + if (par_pe->main_codecs && par_pe->audio_group && strstr(par_pe->audio_group, pe->audio_group)) + break; + par_pe = NULL; + } + if (par_pe) break; + } + if (!par_pe) return NULL; + + u32 grp_idx=0, target_idx=0; + char *group = par_pe->audio_group; + while (group) { + char *sep = strchr(group, ','); + if (sep) sep0 = 0; + grp_idx++; + if (!strcmp(group, pe->audio_group)) target_idx = grp_idx; + if (sep) sep0 = ','; + if (!sep || target_idx) break; + group = sep+1; + } + if (!target_idx) return NULL; + + if (par_pe->alt_bandwidths && bandwidth && (target_idx<=par_pe->nb_alt_bandwidths)) { + u32 min_bw = par_pe->alt_bandwidths0; + for (k=1; k<par_pe->nb_alt_bandwidths; k++) { + if (min_bw > par_pe->alt_bandwidthsk) + min_bw = par_pe->alt_bandwidthsk; + } + //assume default audio bandwidth is 64k + *bandwidth = 64000 + par_pe->alt_bandwidthstarget_idx-1-min_bw; + } + grp_idx = 0; + group = par_pe->codecs; + while (group) { + char *sep = strchr(group, ','); + if (sep) sep0 = 0; + if (!strstr(par_pe->main_codecs, group)) { + grp_idx++; + if (grp_idx == target_idx) { + char *res = gf_strdup(group); + if (sep) sep0 = ','; + return res; + } + } + if (sep) sep0 = ','; + if (!sep) break; + group = sep+1; + } + return NULL; +} static GF_Err gf_m3u8_fill_mpd_struct(MasterPlaylist *pl, const char *m3u8_file, const char *src_base_url, const char *mpd_file, char *title, Double update_interval, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, Bool use_segment_timeline, Bool is_end, u32 max_dur, GF_MPD *mpd, Bool parse_sub_playlist) { char *sep, *template_base=NULL, *template_ext; @@ -1620,6 +1709,7 @@ set->rating = gf_list_new(); set->viewpoint = gf_list_new(); set->content_component = gf_list_new(); + set->inband_event = gf_list_new(); set->base_URLs = gf_list_new(); set->representations = gf_list_new(); /*assign default ID and group*/ @@ -1747,8 +1837,12 @@ if (pe->codecs && (pe->codecs0 == '\"')) { u32 len = (u32) strlen(pe->codecs); - memmove(pe->codecs, pe->codecs+1, len-1); - pe->codecslen-2 = 0; + if (len>1) { + memmove(pe->codecs, pe->codecs+1, len-1); + pe->codecslen-2 = 0; + } else { + pe->codecs0 = 0; + } } #ifndef GPAC_DISABLE_MEDIA_IMPORT if (pe->bandwidth && pe->codecs && pe->width && pe->height) { @@ -1805,9 +1899,10 @@ #ifndef GPAC_DISABLE_MEDIA_IMPORT if (elt && import_file) { - GF_MediaImporter import; char *elt_url = elt->init_segment_url ? elt->init_segment_url : elt->url; char *tmp_file = NULL; + GF_MediaImporter *import = gf_malloc(sizeof(GF_MediaImporter)); + if (!import) return GF_OUT_OF_MEM; #ifndef GPAC_DISABLE_NETWORK u64 br_start = elt->init_segment_url ? elt->init_byte_range_start : elt->byte_range_start; @@ -1817,9 +1912,9 @@ elt_url = gf_url_concatenate(par_url, elt_url); gf_free(par_url); - memset(&import, 0, sizeof(GF_MediaImporter)); - import.trackID = 0; - import.flags = GF_IMPORT_PROBE_ONLY; + memset(import, 0, sizeof(GF_MediaImporter)); + import->trackID = 0; + import->flags = GF_IMPORT_PROBE_ONLY; if (strstr(elt_url, "://") && !strstr(elt_url, "file://")) { tmp_file = strrchr(elt_url, '/'); @@ -1830,12 +1925,12 @@ #ifndef GPAC_DISABLE_NETWORK e = gf_dm_wget(elt_url, tmp_file, br_start, br_end, NULL); if (e == GF_OK) { - import.in_name = tmp_file; + import->in_name = tmp_file; } #endif } } else { - import.in_name = elt_url; + import->in_name = elt_url; } if (!strstr(elt_url, "://") && !gf_file_exists(elt_url)) { @@ -1843,19 +1938,20 @@ if (elt_url) gf_free(elt_url); goto retry_import; } - e = gf_media_import(&import); + e = gf_media_import(import); if (e != GF_OK) { k++; if (elt_url) gf_free(elt_url); + gf_free(import); goto try_next_segment; } - if (import.in_name && !pe->bandwidth && !elt->init_segment_url && pe->duration_info) { + if (import->in_name && !pe->bandwidth && !elt->init_segment_url && pe->duration_info) { u64 pos = 0; Double bw; - FILE *t = gf_fopen(import.in_name, "rb"); + FILE *t = gf_fopen(import->in_name, "rb"); if (t) { pos = gf_fsize(t); gf_fclose(t); @@ -1874,26 +1970,27 @@ if (!pe->codecs) { char *codecs = NULL; - for (k=0; k<import.nb_tracks; k++) { - if (strlen(import.tk_infok.szCodecProfile)) { - gf_dynstrcat(&codecs, import.tk_infok.szCodecProfile, ","); + for (k=0; k<import->nb_tracks; k++) { + if (strlen(import->tk_infok.szCodecProfile)) { + gf_dynstrcat(&codecs, import->tk_infok.szCodecProfile, ","); } } pe->codecs = codecs; } - for (k=0; k<import.nb_tracks; k++) { - switch (import.tk_infok.stream_type) { + for (k=0; k<import->nb_tracks; k++) { + switch (import->tk_infok.stream_type) { case GF_STREAM_VISUAL: - width = import.tk_infok.video_info.width; - height = import.tk_infok.video_info.height; + width = import->tk_infok.video_info.width; + height = import->tk_infok.video_info.height; break; case GF_STREAM_AUDIO: - samplerate = import.tk_infok.audio_info.sample_rate; - num_channels = import.tk_infok.audio_info.nb_channels; + samplerate = import->tk_infok.audio_info.sample_rate; + num_channels = import->tk_infok.audio_info.nb_channels; break; } } if (elt_url) gf_free(elt_url); + gf_free(import); } #endif GF_SAFEALLOC(rep, GF_MPD_Representation); @@ -1920,14 +2017,23 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("M3U8 Unknown mime-type when converting from M3U8 HLS playlist, setting %s\n", mimeTypeForM3U8Segments)); } char *fext = (elt && elt->init_segment_url) ? gf_file_ext_start(elt->init_segment_url) : NULL; - if (fext && (!stricmp(fext, ".mp4") || !stricmp(fext, ".m4s")) ) { + if ((fext && (!stricmp(fext, ".mp4") || !stricmp(fext, ".m4s")) ) + //default to MP4 for v6 or above + || (pl->version>=6) + ) { rep->mime_type = gf_strdup(samplerate ? "audio/mp4" : "video/mp4"); } else { rep->mime_type = gf_strdup(mimeTypeForM3U8Segments); } - if (pe->codecs) { + if (pe->main_codecs ) { + rep->codecs = gf_strdup(pe->main_codecs); + } else if (pe->codecs) { rep->codecs = gf_strdup(pe->codecs); } + else if (pe->audio_group) { + rep->codecs = group_to_codecs(pl, pe, &rep->bandwidth); + } + if (pe->language && !set->lang) { set->lang = pe->language; pe->language = NULL; @@ -1939,8 +2045,10 @@ if (elt) { if (elt->drm_method==DRM_AES_128) rep->crypto_type = 1; - else if (elt->drm_method==DRM_CENC) + else if (elt->drm_method==DRM_CENC_CBCS) rep->crypto_type = 2; + else if (elt->drm_method==DRM_CENC_CTR) + rep->crypto_type = 3; } if (samplerate) { rep->samplerate = samplerate; @@ -1956,6 +2064,13 @@ if (!rep->audio_channels) rep->audio_channels = gf_list_new(); gf_list_add(rep->audio_channels, desc); } + //audio bandwidth not announced ... + if (!pe->bandwidth && !rep->bandwidth) + rep->bandwidth = 64000 * num_channels/2; + } + if (pe->media_type==MEDIA_TYPE_SUBTITLES) { + //subs bandwidth not announced ... + if (!pe->bandwidth) rep->bandwidth = 10000; } //if parsing subplaylist with a MPD, we translate HLS to MPD directly @@ -2406,6 +2521,10 @@ gf_sha1_csum(m3u8_payload, m3u8_size, signature); } else if (gf_url_is_local(full_url)) { + if (!gf_file_exists(full_url)) { + gf_free(full_url); + return GF_URL_REMOVED; + } loc_file = full_url; gf_sha1_file(loc_file, signature); } else { @@ -2467,7 +2586,12 @@ } stream = (Stream *)gf_list_get(pl->streams, 0); - gf_assert(gf_list_count(stream->variants) == 1); + if (gf_list_count(stream->variants) != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("M3U8 Invalid playlist %s resolves to multiple variants %d, disabling it\n", rep->segment_list->xlink_href, gf_list_count(stream->variants))); + gf_m3u8_master_playlist_del(&pl); + rep->playback.disabled = GF_TRUE; + return GF_NON_COMPLIANT_BITSTREAM; + } pe = (PlaylistElement *)gf_list_get(stream->variants, 0); if (duration) { @@ -2532,12 +2656,15 @@ //NOTE: for GPAC now, we disable stream AAC to avoid the problem when switching quality. It should be improved later ! if (strstr(elt->url, ".aac")) { rep->playback.disabled = GF_TRUE; + gf_m3u8_master_playlist_del(&pl); return GF_OK; } if (elt->drm_method==DRM_AES_128) rep->crypto_type = 1; - else if (elt->drm_method==DRM_CENC) + else if (elt->drm_method==DRM_CENC_CBCS) rep->crypto_type = 2; + else if (elt->drm_method==DRM_CENC_CTR) + rep->crypto_type = 3; if (elt->low_lat_chunk && !has_full_seg_following) { u32 j; @@ -2756,7 +2883,10 @@ if (s->index_range_exact) gf_fprintf(out, " indexRangeExact=\"true\""); if (s->index_range) gf_fprintf(out, " indexRange=\""LLD"-"LLD"\"", s->index_range->start_range, s->index_range->end_range); } - if (s->availability_time_offset) gf_fprintf(out, " availabilityTimeOffset=\"%g\"", s->availability_time_offset); + if (s->availability_time_offset) { + gf_fprintf(out, " availabilityTimeOffset=\"%g\"", s->availability_time_offset); + gf_fprintf(out, " availabilityTimeComplete=\"%s\"", "false"); + } if ((s32) s->time_shift_buffer_depth > 0) gf_mpd_print_duration(out, "timeShiftBufferDepth", s->time_shift_buffer_depth, GF_TRUE); } @@ -2803,6 +2933,7 @@ //close entry if ((se->start_time != start_time) || (prev->duration!=se->duration)) { if (rcount) gf_fprintf(out, " r=\"%d\"", rcount); + if (prev->nb_parts) gf_fprintf(out, " k=\"%d\"", prev->nb_parts); gf_fprintf(out, "/>"); gf_mpd_lf(out, indent); //start new one @@ -2823,6 +2954,7 @@ } //close last entry if (rcount) gf_fprintf(out, " r=\"%d\"", rcount); + if (prev->nb_parts) gf_fprintf(out, " k=\"%d\"", prev->nb_parts); gf_fprintf(out, "/>"); gf_mpd_lf(out, indent); @@ -2941,6 +3073,7 @@ if (s->index) gf_fprintf(out, " index=\"%s\"", s->index); if (s->initialization) gf_xml_dump_string(out, " initialization=\"", s->initialization, "\""); if (s->bitstream_switching) gf_fprintf(out, " bitstreamSwitching=\"%s\"", s->bitstream_switching); + if (s->nb_parts) gf_fprintf(out, " k=\"%d\"", s->nb_parts); if (gf_mpd_print_multiple_segment_base(out, (GF_MPD_MultipleSegmentBase *)s, indent, GF_TRUE)) return; @@ -3057,6 +3190,16 @@ } } +static void gf_mpd_print_inband_event(FILE *out, GF_List *inband_event, s32 indent) { + u32 i = 0; + GF_MPD_Inband_Event *ibe; + while ((ibe = gf_list_enum(inband_event, &i))) { + gf_mpd_nl(out, indent); + gf_fprintf(out, "<InbandEventStream schemeIdUri=\"%s\" value=\"%s\"/>", ibe->scheme_id_uri, ibe->value); + gf_mpd_lf(out, indent); + } +} + static void gf_mpd_print_common_attributes(FILE *out, GF_MPD_CommonAttributes *ca) { if (ca->profiles) { @@ -3246,7 +3389,7 @@ gf_mpd_lf(out, indent); } -static void gf_mpd_print_representation(GF_MPD_Representation *rep, FILE *out, Bool write_context, s32 indent, u32 alt_mha_profile) +static void gf_mpd_print_representation(GF_MPD_Representation *rep, FILE *out, Bool write_context, s32 indent, u32 alt_mha_profile, Bool skip_mime) { u32 child_idx = 0; char *bck_codecs = NULL; @@ -3267,7 +3410,10 @@ sep = strstr(rep->codecs, ".0x"); if (sep) strcpy(sep+1, szTmp); } + char *mime_type = rep->mime_type; + if (skip_mime) rep->mime_type = NULL; gf_mpd_print_common_attributes(out, (GF_MPD_CommonAttributes*)rep); + rep->mime_type = mime_type; if (rep->bandwidth) gf_fprintf(out, " bandwidth=\"%d\"", rep->bandwidth); if (rep->quality_ranking) gf_fprintf(out, " qualityRanking=\"%d\"", rep->quality_ranking); @@ -3341,6 +3487,20 @@ return; } + //check if all reps have the same mime, if so only write it at AS level + char *mime_type = NULL; + if (!as->mime_type && !gf_sys_is_test_mode()) { + for (i=0; i<gf_list_count(as->representations); i++) { + GF_MPD_Representation *rep = gf_list_get(as->representations, i); + if (!i) + mime_type = rep->mime_type; + else if (mime_type && (!rep->mime_type || strcmp(rep->mime_type, mime_type))) + mime_type = NULL; + } + as->mime_type = mime_type; + } + + gf_mpd_nl(out, indent); gf_fprintf(out, "<AdaptationSet"); @@ -3392,6 +3552,7 @@ gf_mpd_print_descriptors(out, as->rating, "Rating", indent+1, as->x_children, &child_idx); gf_mpd_print_descriptors(out, as->viewpoint, "Viewpoint", indent+1, as->x_children, &child_idx); gf_mpd_print_content_component(out, as->content_component, indent+1); + gf_mpd_print_inband_event(out, as->inband_event, indent + 1); if (as->segment_base) { gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_FALSE); @@ -3409,13 +3570,15 @@ i=0; while ((rep = (GF_MPD_Representation *)gf_list_enum(as->representations, &i))) { gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_FALSE); - gf_mpd_print_representation(rep, out, write_context, indent+1, alt_mha_profile); + gf_mpd_print_representation(rep, out, write_context, indent+1, alt_mha_profile, mime_type ? GF_TRUE : GF_FALSE); } gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_TRUE); gf_mpd_nl(out, indent); gf_fprintf(out, "</AdaptationSet>"); gf_mpd_lf(out, indent); + if (mime_type) as->mime_type = NULL; + if (!alt_mha_profile) { for (i=0; i<as->nb_alt_mha_profiles; i++) { gf_mpd_print_adaptation_set(as, out, write_context, indent, as->alt_mha_profilesi + 1); @@ -3495,7 +3658,7 @@ return GF_OK; } -static void gf_mpd_write_m3u8_playlist_tags_entry(FILE *out, const GF_MPD_Representation *rep, char *m3u8_name, const char *codec_ext, const char *g_type, const char *g_id_pref, const char *g2_type, const char *g2_id_pref, const GF_MPD_AdaptationSet *set, u32 max_alt_bandwidth, u32 max_alt_width, u32 max_alt_height, Double max_alt_fps) +static void gf_mpd_write_m3u8_playlist_tags_entry(FILE *out, const GF_MPD_Representation *rep, char *m3u8_name, const char *codec_ext, const char *g_type, const char *g_id_pref, const char *g2_type, const char *g2_id_pref, const GF_MPD_AdaptationSet *set, u32 max_alt_bandwidth, u32 max_alt_width, u32 max_alt_height, Double max_alt_fps, u32 hls_version, const char *prim_group_id) { u32 i; @@ -3531,8 +3694,17 @@ gf_fprintf(out,",%s=\"%s", g2_type, g2_id_pref); gf_fprintf(out,"\""); } + Bool has_cc = GF_FALSE; for (i=0; i<rep->nb_hls_master_tags; i++) { gf_fprintf(out,",%s", rep->hls_master_tagsi); + if (strstr(rep->hls_master_tagsi, "CLOSED-CAPTIONS")) + has_cc = GF_TRUE; + } + if (!has_cc && !gf_sys_is_test_mode() && (hls_version>=6)) { + gf_fprintf(out, ",CLOSED-CAPTIONS=NONE"); + } + if (prim_group_id) { + gf_fprintf(out, ",%s=\"%s\"", (rep->streamtype==GF_STREAM_VISUAL) ? "VIDEO" : "AUDIO", prim_group_id); } gf_fprintf(out,"\n"); @@ -3540,7 +3712,7 @@ } -static void gf_mpd_write_m3u8_playlist_tags(const GF_MPD_AdaptationSet *as, u32 as_idx, const GF_MPD_Representation *rep, FILE *out, char *m3u8_name, GF_MPD_Period *period, u32 nb_alt_media, u32 nb_subs, u32 nb_cc, const char *forced_inf_ids) +static void gf_mpd_write_m3u8_playlist_tags(const GF_MPD_AdaptationSet *as, u32 as_idx, const GF_MPD_Representation *rep, FILE *out, char *m3u8_name, GF_MPD_Period *period, u32 nb_alt_media, u32 nb_subs, u32 nb_cc, const char *forced_inf_ids, u32 hls_version, const char *prim_group_id) { u32 i; GF_MPD_AdaptationSet *r_as; @@ -3612,31 +3784,39 @@ gf_fprintf(out, ",URI=\"%s\"", m3u8_name); - if (!has_chan && rep->nb_chan) - gf_fprintf(out, ",CHANNELS=\"%d\"", rep->nb_chan); - + if (!has_chan) { + if (rep->nb_chan) gf_fprintf(out, ",CHANNELS=\"%d\"", rep->nb_chan); + else if (rep->str_chan0 != '\0') gf_fprintf(out, ",CHANNELS=\"%s\"", rep->str_chan); + } return; } //no other streams, directly write the entry if (!nb_alt_media && !nb_subs && !nb_cc && (!forced_inf_ids || !forced_inf_ids0)) { - gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, NULL, NULL, NULL, NULL, NULL, as, 0, 0, 0, 0); + gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, NULL, NULL, NULL, NULL, NULL, as, 0, 0, 0, 0, hls_version, prim_group_id); return; } + char *groups_done = NULL; + char *groups_subs_done = NULL; + + char *grp_codecs; + const char *g_type, *g_id, *g_type_subs, *g_id_subs; + u32 g_m_bandwidth, g_m_bandwidth_subs, g_m_width, g_m_height; + Double g_m_fps; + +re_dump: + //gather all codecs and max values for alternate stream type and subs - char *grp_codecs = NULL; - const char *g_type = NULL; - const char *g_id = NULL; - const char *g_type_subs = NULL; - const char *g_id_subs = NULL; - u32 g_m_bandwidth=0, g_m_bandwidth_subs=0, g_m_width=0, g_m_height=0; - Double g_m_fps=0; + grp_codecs = NULL; + g_type = g_id = g_type_subs = g_id_subs = NULL; + g_m_bandwidth = g_m_bandwidth_subs = g_m_width = g_m_height = 0; + g_m_fps = 0; i=0; while ( (r_as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) { u32 j=0; while ( (r_rep = (GF_MPD_Representation *) gf_list_enum(r_as->representations, &j))) { - const char *hls_group = rep->groupID; + const char *hls_group = NULL; if (forced_inf_ids) { if (r_rep == rep) continue; if ((r_rep->streamtype!=rep->streamtype) && (r_rep->streamtype!=GF_STREAM_TEXT)) continue; @@ -3653,41 +3833,95 @@ } if (!res) continue; } - //if rep we generate EXT-X-STREAM-INF for has a group set, do not match (even if no group set on tested rep) - else if (hls_group) continue; - //always match if no groups are specified - //some reps do not have codecs (raw formats, onlly mime is used) - if (r_rep->codecs && (!grp_codecs || !strstr(grp_codecs, r_rep->codecs))) - gf_dynstrcat(&grp_codecs, r_rep->codecs, ","); + const char *local_gid = r_rep->groupID; + if (r_rep->streamtype==GF_STREAM_AUDIO) { + if (!local_gid) local_gid = "audio"; + } + else if (r_rep->streamtype==GF_STREAM_VISUAL) { + if (!local_gid) local_gid = "video"; + } + else if (r_rep->streamtype==GF_STREAM_TEXT) { + if (!local_gid) local_gid = "subs"; + } else { + if (!local_gid) local_gid = "other"; + } + u32 k; + Bool exclude = GF_FALSE; + for (k=0;k<rep->nb_group_ids_rend; k++) { + if (!strcmp(rep->group_ids_rendk, local_gid)) { + exclude = GF_TRUE; + break; + } + } + if (exclude) + continue; + + if (groups_done && strstr(groups_done, local_gid)) continue; + if (groups_subs_done && strstr(groups_subs_done, local_gid)) continue; if (r_rep->streamtype==GF_STREAM_AUDIO) { + if (!g_id) g_id = local_gid; if (!g_type) g_type = "AUDIO"; - if (!g_id) g_id = r_rep->groupID ? r_rep->groupID : "audio"; g_m_bandwidth = MAX(g_m_bandwidth, r_rep->bandwidth); - } - else if (r_rep->streamtype==GF_STREAM_VISUAL) { + } else if (r_rep->streamtype==GF_STREAM_VISUAL) { + if (!g_id) g_id = local_gid; if (!g_type) g_type = "VIDEO"; - if (!g_id) g_id = r_rep->groupID ? r_rep->groupID : "video"; g_m_bandwidth = MAX(g_m_bandwidth, r_rep->bandwidth); g_m_width = MAX(g_m_width, r_rep->width); g_m_height = MAX(g_m_height, r_rep->height); g_m_fps = MAX(g_m_fps, r_rep->fps); } else if (r_rep->streamtype==GF_STREAM_TEXT) { + if (!g_id_subs) g_id_subs = local_gid; if (!g_type_subs) g_type_subs = "SUBTITLES"; - if (!g_id_subs) g_id_subs = r_rep->groupID ? r_rep->groupID : "subs"; g_m_bandwidth_subs = MAX(g_m_bandwidth_subs, r_rep->bandwidth); } + + //some reps do not have codecs (raw formats, onlly mime is used) + if (r_rep->codecs && (!grp_codecs || !strstr(grp_codecs, r_rep->codecs))) + gf_dynstrcat(&grp_codecs, r_rep->codecs, ","); + } } + //done dumping all streams + if (!g_id && groups_done) { + if (grp_codecs) gf_free(grp_codecs); + if (groups_done) gf_free(groups_done); + if (groups_subs_done) gf_free(groups_subs_done); + return; + } + + //done dumping all subs streams for the secondary media + if (groups_subs_done && !g_id_subs) { + if (grp_codecs) gf_free(grp_codecs); + if (groups_subs_done) gf_free(groups_subs_done); + groups_subs_done = NULL; + if (g_id) gf_dynstrcat(&groups_done, g_id, ","); + goto re_dump; + } + g_m_bandwidth += g_m_bandwidth_subs; if (gf_sys_is_test_mode() && !g_m_width) g_m_bandwidth = 0; - gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, grp_codecs, g_type, g_id, g_type_subs, g_id_subs, as, g_m_bandwidth, g_m_width, g_m_height, g_m_fps); + gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, grp_codecs, g_type, g_id, g_type_subs, g_id_subs, as, g_m_bandwidth, g_m_width, g_m_height, g_m_fps, hls_version, prim_group_id); if (grp_codecs) gf_free(grp_codecs); - grp_codecs=NULL; + + //remember this subs id and redumping for the secondary media + if (g_id_subs) { + gf_dynstrcat(&groups_subs_done, g_id_subs, ","); + goto re_dump; + } + //no more subs, we are done dumping the secondary media + if (g_id) { + if (groups_subs_done) gf_free(groups_subs_done); + groups_subs_done = NULL; + gf_dynstrcat(&groups_done, g_id, ","); + goto re_dump; + } + if (groups_done) gf_free(groups_done); + if (groups_subs_done) gf_free(groups_subs_done); } static const char *gf_mpd_m3u8_get_init_seg(const GF_MPD_Period *period, const GF_MPD_AdaptationSet *as, const GF_MPD_Representation *rep) @@ -3707,6 +3941,47 @@ return url; } +static void hls_insert_crypt_info(FILE *out, GF_MPD_Representation *rep, GF_DASH_SegmentContext *sctx, const char **last_kms) +{ + if (!rep->crypto_type) return; + const char *kms; + if (!sctx->encrypted) + kms = "NONE"; + else if (sctx->hls_key_uri) kms = sctx->hls_key_uri; + else { + kms = "URI=\"gpac:hls:key:locator:null\""; + if (!rep->def_kms_used) { + rep->def_kms_used = 1; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("HLS Missing key URI in one or more keys - will use dummy one %s\n", kms)); + } + } + + if (! *last_kms || strcmp(kms, *last_kms)) { + if (!strcmp(kms, "NONE")) { + gf_fprintf(out,"#EXT-X-KEY:METHOD=NONE\n"); + } else { + char *subkms = (char *) kms; + while (1) { + char *next = strstr(subkms, ",URI"); + if (next) next0 = 0; + if (rep->crypto_type==1) { + u32 k; + gf_fprintf(out,"#EXT-X-KEY:METHOD=AES-128,%s,IV=0x", subkms); + for (k=0; k<16; k++) + gf_fprintf(out, "%02X", sctx->hls_ivk); + gf_fprintf(out, "\n"); + } else { + gf_fprintf(out,"#EXT-X-KEY:METHOD=SAMPLE-AES%s,%s\n", (rep->crypto_type==3) ? "-CTR" : "", subkms); + } + if (!next) break; + next0 = ','; + subkms = next+1; + } + } + *last_kms = (rep->crypto_type>=2) ? kms : NULL; + } +} + static GF_Err gf_mpd_write_m3u8_playlist(const GF_MPD *mpd, const GF_MPD_Period *period, const GF_MPD_AdaptationSet *as, GF_MPD_Representation *rep, char *m3u8_name, u32 hls_version, Double max_part_dur_session, const char *force_base_url) { u32 i, count; @@ -3759,16 +4034,24 @@ gf_fprintf(out, "\n"); } + //things common to onDemand and live profiles + if (as->intra_only) { + gf_fprintf(out,"#EXT-X-I-FRAMES-ONLY\n"); + } + //one file per segment if (sctx && sctx->filename) { - if (as->intra_only) { - gf_fprintf(out,"#EXT-X-I-FRAMES-ONLY\n"); - } if (rep->hls_single_file_name) { if (force_base_url) force_url = gf_url_concatenate(force_base_url, rep->hls_single_file_name); - gf_fprintf(out,"#EXT-X-MAP:URI=\"%s\"\n", force_url ? force_url : rep->hls_single_file_name); + if (rep->init_base64) { + const char *mime = rep->mime_type; + if (!mime) mime = "video/mp4"; + gf_fprintf(out,"#EXT-X-MAP:URI=\"data:%s;base64,%s\"\n", mime, rep->init_base64); + } else { + gf_fprintf(out,"#EXT-X-MAP:URI=\"%s\"\n", force_url ? force_url : rep->hls_single_file_name); + } if (force_url) { gf_free(force_url); @@ -3781,46 +4064,12 @@ sctx = gf_list_get(rep->state_seg_list, i); gf_assert(sctx->filename); - if (rep->crypto_type) { - const char *kms; - if (!sctx->encrypted) kms = "NONE"; - else if (sctx->hls_key_uri) kms = sctx->hls_key_uri; - else { - kms = "URI=\"gpac:hls:key:locator:null\""; - if (!rep->def_kms_used) { - rep->def_kms_used = 1; - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("HLS Missing key URI in one or more keys - will use dummy one %s\n", kms)); - } - } - - if (!last_kms || strcmp(kms, last_kms)) { - if (!strcmp(kms, "NONE")) { - gf_fprintf(out,"#EXT-X-KEY:METHOD=NONE\n"); - } else { - char *subkms = (char *) kms; - while (1) { - char *next = strstr(subkms, ",URI"); - if (next) next0 = 0; - if (rep->crypto_type==1) { - u32 k; - gf_fprintf(out,"#EXT-X-KEY:METHOD=AES-128,%s,IV=0x", subkms); - for (k=0; k<16; k++) - gf_fprintf(out, "%02X", sctx->hls_ivk); - gf_fprintf(out, "\n"); - } else { - gf_fprintf(out,"#EXT-X-KEY:METHOD=SAMPLE-AES,%s\n", subkms); - } - if (!next) break; - next0 = ','; - subkms = next+1; - } - } - last_kms = (rep->crypto_type==2) ? kms : NULL; - } - } + hls_insert_crypt_info(out, rep, sctx, &last_kms); u64 next_br_start_plus_one=0; u32 next_seg_idx=0; + + //LL-HLS related if ((mpd->type == GF_MPD_TYPE_DYNAMIC) && sctx->llhls_mode) { u32 k, nb_parts=sctx->nb_frags; //EXT-X-PART tags SHOULD be removed from the Playlist after they are greater than three Target Durations from the end of the Playlist. @@ -3830,25 +4079,35 @@ dur = sctx->fragsk.duration; dur /= rep->timescale; + if (mpd->force_llhls_mode==1) write_br = GF_TRUE; + else if (mpd->force_llhls_mode==2) write_br = GF_FALSE; + else if (sctx->llhls_mode==GF_DASH_LL_HLS_BR) write_br = GF_TRUE; + if (force_base_url) force_url = gf_url_concatenate(force_base_url, sctx->filename); + if (!write_br) { + u32 frag_idx = k; + //we'll need to redo all LLHLS tests + if (gf_sys_is_test_mode()) frag_idx++; + + char *res = gf_mpd_resolve_subnumber(sctx->llhas_template, force_url ? force_url : sctx->filename, frag_idx); + if (force_url ) gf_free(force_url); + force_url = res; + next_seg_idx = k+1; + } + gf_fprintf(out, "#EXT-X-PART:DURATION=%g,URI=\"%s", dur, force_url ? force_url : sctx->filename); if (force_url ) { gf_free(force_url); force_url = NULL; } - if (mpd->force_llhls_mode==1) write_br = GF_TRUE; - else if (mpd->force_llhls_mode==2) write_br = GF_FALSE; - else if (sctx->llhls_mode==1) write_br = GF_TRUE; - if (write_br) { gf_fprintf(out, "\",BYTERANGE=\""LLU"@"LLU"\"", sctx->fragsk.size, sctx->fragsk.offset ); next_br_start_plus_one = 1 + sctx->fragsk.offset + sctx->fragsk.size; } else { - next_seg_idx = k+2; - gf_fprintf(out, ".%d\"", k+1); + gf_fprintf(out, "\""); } if (sctx->fragsk.independent) @@ -3870,16 +4129,19 @@ //last seg has no parts yet, we just started it (live edge), advertise first part if (!sctx->nb_frags) { gf_assert(sctx->filename); - if (sctx->llhls_mode==2) next_seg_idx = 1; + if (sctx->llhls_mode==GF_DASH_LL_HLS_SF) next_seg_idx = 1; else next_br_start_plus_one = 1; } if (force_base_url) force_url = gf_url_concatenate(force_base_url, sctx->filename); - if (next_seg_idx) - gf_fprintf(out, "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s.%d\"\n", force_url ? force_url : sctx->filename, next_seg_idx); - else if (next_br_start_plus_one) + if (next_seg_idx) { + char *res = gf_mpd_resolve_subnumber(sctx->llhas_template, force_url ? force_url : sctx->filename, next_seg_idx); + if (force_url ) gf_free(force_url); + force_url = res;; + gf_fprintf(out, "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s\"\n", force_url); + } else if (next_br_start_plus_one) gf_fprintf(out, "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s\",BYTERANGE-START="LLU"\n", force_url ? force_url : sctx->filename, next_br_start_plus_one-1); if (force_url) { @@ -3909,7 +4171,7 @@ if (!o_sctx || !o_sctx->nb_frags) continue; } - char *o_name = (char *) o_rep->m3u8_name; + char *o_name = o_rep->m3u8_name; if (!o_name) { o_name = gf_file_basename(o_rep->m3u8_var_name); } @@ -3944,7 +4206,9 @@ force_url = NULL; } } - } else { + } + //byte-range in single file + else { GF_MPD_BaseURL *base_url=NULL; const char *b_url=NULL; GF_MPD_URL *init=NULL; @@ -3987,6 +4251,8 @@ gf_assert(!sctx->filename); gf_assert(sctx->file_size); + hls_insert_crypt_info(out, rep, sctx, &last_kms); + dur = (Double) sctx->dur; dur /= rep->timescale; gf_fprintf(out,"#EXTINF:%g\n", dur); @@ -4012,6 +4278,30 @@ return GF_OK; } +static char *get_rep_variant_filename(GF_MPD const * const mpd, GF_MPD_Representation *rep, const char * force_base_url) +{ + char szSuffixNameGF_MAX_PATH+1; + char *name = rep->m3u8_name; + if (rep->m3u8_var_name) { + name = gf_file_basename(rep->m3u8_var_name); + } + + if (mpd->force_llhls_mode==2) { + strcpy(szSuffixName, name); + char *sep = gf_file_ext_start(szSuffixName); + if (sep) sep0 = 0; + strcat(szSuffixName, "_IF"); + sep = gf_file_ext_start(name); + if (sep) + strcat(szSuffixName, sep); + name = szSuffixName; + } + + if (force_base_url && ((mpd->hls_abs_url==GF_DASH_ABS_URL_MASTER) || (mpd->hls_abs_url==GF_DASH_ABS_URL_BOTH))) + return gf_url_concatenate(force_base_url, name); + + return gf_strdup(name); +} GF_Err gf_mpd_write_m3u8_master_playlist(GF_MPD const * const mpd, FILE *out, const char* m3u8_name, GF_MPD_Period *period, GF_M3U8WriteMode mode) { @@ -4024,6 +4314,7 @@ Bool use_intra_only = GF_FALSE; Bool use_init = GF_FALSE; Bool use_ind_segments = GF_TRUE; + Bool use_saes_crypto = GF_FALSE; Bool is_fmp4 = GF_FALSE; char *szVariantName; char *m3u8_name_rad, *sep, *force_base_url=NULL; @@ -4034,6 +4325,7 @@ Bool has_muxed_comp = GF_FALSE; Bool has_video = GF_FALSE; Bool has_audio = GF_FALSE; + Bool has_cc = GF_FALSE; if (!m3u8_name || !period) return GF_BAD_PARAM; @@ -4065,13 +4357,25 @@ if (rep->streamtype==GF_STREAM_AUDIO) nb_audio++; else if (rep->streamtype==GF_STREAM_TEXT) nb_subs++; else if (rep->streamtype==GF_STREAM_VISUAL) nb_video++; + + if (rep->crypto_type>=2) use_saes_crypto = GF_TRUE; + + if (!has_cc) { + u32 k; + for (k=0; k<rep->nb_hls_master_tags; k++) { + if (strstr(rep->hls_master_tagsk, "CLOSED-CAPTIONS")) { + has_cc = GF_TRUE; + break; + } + } + } } } //we by default use floating point durations hls_version = 3; if (use_range) hls_version = 4; - if (use_intra_only) hls_version = 5; - if (is_fmp4 || use_init) hls_version = 6; + if (use_intra_only || use_saes_crypto) hls_version = 5; + if (is_fmp4 || use_init || has_cc) hls_version = 6; if (mode!=GF_M3U8_WRITE_CHILD) { @@ -4191,13 +4495,13 @@ } if (mode==GF_M3U8_WRITE_MASTER) break; - char *name = (char *) rep->m3u8_name; + char *name = rep->m3u8_name; if (!name) { name = gf_file_basename(rep->m3u8_var_name); } e = gf_mpd_write_m3u8_playlist(mpd, period, as, rep, name, hls_version, max_part_dur_session, - ((mpd->hls_abs_url==1) || (mpd->hls_abs_url==3)) ? force_base_url : NULL); + ((mpd->hls_abs_url==GF_DASH_ABS_URL_VARIANT) || (mpd->hls_abs_url==GF_DASH_ABS_URL_BOTH)) ? force_base_url : NULL); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("M3U8 IO error while opening m3u8 files\n")); return GF_IO_ERR; @@ -4209,6 +4513,8 @@ if (!has_video && !has_muxed_comp) nb_audio = 0; + GF_List *all_primary_reps_done = NULL; + //third pass, generate master playlists with the right groups i=0; while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) { @@ -4233,6 +4539,8 @@ //check if we have audio or video j=0; while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) { + if (all_primary_reps_done && (gf_list_find(all_primary_reps_done, rep)>=0) ) + continue; if (rep->mime_type) { if (!strncmp(rep->mime_type, "video/", 6)) is_video = GF_TRUE; else if (!strncmp(rep->mime_type, "audio/", 6)) is_audio = GF_TRUE; @@ -4256,42 +4564,61 @@ j=0; while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) { - char szSuffixNameGF_MAX_PATH+1; - char *name = (char *) rep->m3u8_name; if (!mpd->allow_empty_reps && (!rep->state_seg_list || !gf_list_count(rep->state_seg_list)) ) { continue; } - if (rep->m3u8_var_name) { - name = gf_file_basename(rep->m3u8_var_name); - } + if (all_primary_reps_done && (gf_list_find(all_primary_reps_done, rep)>=0) ) + continue; - if (mpd->force_llhls_mode==2) { - strcpy(szSuffixName, name); - sep = gf_file_ext_start(szSuffixName); - if (sep) sep0 = 0; - strcat(szSuffixName, "_IF"); - sep = gf_file_ext_start(name); - if (sep) - strcat(szSuffixName, sep); - name = szSuffixName; - } + char *name = get_rep_variant_filename(mpd, rep, force_base_url); + if (!name) continue; + //if this is the primary media type and we have a groupID, check all reps in ALL sets with the same ID + //and generate #EXT-X-MEDIA:TYPE + const char *prim_group_id = NULL; + GF_List *all_reps_in_group = NULL; + if (is_primary && rep->groupID) { + u32 l, k=0; + GF_MPD_Representation *a_rep; + all_reps_in_group = gf_list_new(); + GF_MPD_AdaptationSet *an_as; + while ( (an_as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &k))) { + l=0; + while ( (a_rep = (GF_MPD_Representation *) gf_list_enum(an_as->representations, &l))) { + if (a_rep->groupID && !strcmp(a_rep->groupID, rep->groupID)) { + gf_list_add(all_reps_in_group, a_rep); + } + } + } + while (gf_list_count(all_reps_in_group)) { + a_rep = gf_list_pop_front(all_reps_in_group); + char *a_name = get_rep_variant_filename(mpd, a_rep, force_base_url); + if (!a_name) continue; + gf_mpd_write_m3u8_playlist_tags(as, i, a_rep, out, a_name, NULL, nb_alt_streams, nb_subs, nb_cc, 0, hls_version, NULL); + gf_fprintf(out, "\n"); + gf_free(a_name); - char *force_url = NULL; - if (force_base_url && ((mpd->hls_abs_url==2) || (mpd->hls_abs_url==3))) - force_url = gf_url_concatenate(force_base_url, name); + if (!all_primary_reps_done) all_primary_reps_done = gf_list_new(); + gf_list_add(all_primary_reps_done, a_rep); + //remember to add primary type in #EXT-X-STREAM-INF + prim_group_id = a_rep->groupID; + } + gf_list_del(all_reps_in_group); + all_reps_in_group = NULL; + } - gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, force_url ? force_url : name, is_primary ? period : NULL, nb_alt_streams, nb_subs, nb_cc, 0); + gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, name, is_primary ? period : NULL, nb_alt_streams, nb_subs, nb_cc, 0, hls_version, prim_group_id); gf_fprintf(out, "\n"); if (!is_primary && rep->hls_forced) { - gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, force_url ? force_url : name, period, 0, 0, 0, rep->hls_forced); + gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, name, period, 0, 0, 0, rep->hls_forced, hls_version, NULL); gf_fprintf(out, "\n"); } - if (force_url) gf_free(force_url); + gf_free(name); } } + if (all_primary_reps_done) gf_list_del(all_primary_reps_done); gf_free(m3u8_name_rad); gf_free(szVariantName); return GF_OK; @@ -4557,8 +4884,25 @@ return parent_url; } +Bool gf_mpd_check_print_format(const char *print_fmt) +{ + if (!print_fmt) return GF_FALSE; + if (!print_fmt0) return GF_FALSE; + + char *fmt = strchr(print_fmt, '%'); + if (!fmt) return GF_FALSE; + fmt++; + while (fmt0 && (fmt0 >= '0') && (fmt0 <= '9')) + fmt++; + if (strchr("duioxX", fmt0) == NULL) + return GF_FALSE; + if (fmt1) + return GF_FALSE; + return GF_TRUE; +} + GF_EXPORT -GF_Err gf_mpd_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, const char *mpd_url, u32 base_url_index, GF_MPD_URLResolveType resolve_type, u32 item_index, u32 nb_segments_removed, char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration_in_ms, Bool *is_in_base_url, char **out_key_url, bin128 *out_key_iv, u32 *out_start_number) +GF_Err gf_mpd_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, const char *mpd_url, u32 base_url_index, GF_MPD_URLResolveType resolve_type, u32 item_index, u32 nb_segments_removed, char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration_in_ms, Bool *is_in_base_url, char **out_key_url, bin128 *out_key_iv, u32 *out_start_number, s32 subseg_index) { GF_MPD_SegmentTimeline *timeline = NULL; u32 start_number = 1; @@ -4592,8 +4936,10 @@ if (!rep->segment_list && !set->segment_list && !period->segment_list && !rep->segment_template && !set->segment_template && !period->segment_template) { GF_MPD_URL *res_url; GF_MPD_SegmentBase *base_seg = NULL; - if (item_index > 0) + if (item_index > 0) { + gf_free(url); return GF_EOS; + } switch (resolve_type) { case GF_MPD_RESOLVE_URL_MEDIA: case GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE: @@ -4853,10 +5199,15 @@ format_tag = strchr(first_sep+1, '%'); if (format_tag) { + if (!gf_mpd_check_print_format(format_tag)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("MPD Invalid format %s on representation - cannot solve template\n\n", format_tag)); + gf_free(url); + gf_free(solved_template); + second_sep0 = '$'; + return GF_NON_COMPLIANT_BITSTREAM; + } strcpy(szPrintFormat, format_tag); format_tag0 = 0; - if (!strchr(szPrintFormat, 'd') && !strchr(szPrintFormat, 'i') && !strchr(szPrintFormat, 'u')) - strcat(szPrintFormat, "d"); } else { strcpy(szPrintFormat, "%d"); } @@ -4891,8 +5242,10 @@ strcat(solved_template, szFormat); } - /*check start time is in period (start time is ~seg_duration * item_index, since startNumber seg has start time = 0 in the period*/ - if (period->duration + /*check start time is in period (start time is ~seg_duration * item_index, since startNumber seg has start time = 0 in the period + DO NOT do this if timeline is used, as the segment duration can likely vary a lot + */ + if (period->duration && !timeline && (item_index * (*segment_duration_in_ms) > period->duration)) { gf_free(url); gf_free(solved_template); @@ -4900,6 +5253,27 @@ return GF_EOS; } } + else if (!strcmp(first_sep+1, "SubNumber")) { + if ((resolve_type!=GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE) && (subseg_index<0)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("MPD Invalid SubNumber template identifier with no SSR subsegment index\n")); + gf_free(url); + gf_free(solved_template); + second_sep0 = '$'; + return GF_NON_COMPLIANT_BITSTREAM; + } + if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE) { + strcat(solved_template, "$SubNumber"); + if (format_tag) + strcat(solved_template, szPrintFormat); + strcat(solved_template, "$"); + } else if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_NOSTART) { + sprintf(szFormat, szPrintFormat, subseg_index); + strcat(solved_template, szFormat); + } else { + sprintf(szFormat, szPrintFormat, subseg_index); + strcat(solved_template, szFormat); + } + } else if (!strcmp(first_sep+1, "Index")) { GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("MPD Wrong template identifier Index detected - using Number instead\n\n")); sprintf(szFormat, szPrintFormat, start_number + item_index); @@ -5140,9 +5514,6 @@ start_time = gf_mpd_segment_timeline_start(timeline, in_segment_index, &duration); start_time -= pto; } - else if (duration) { - start_time = in_segment_index * duration; - } else if (seglist && (in_segment_index >= 0)) { u32 i; start_time = 0; @@ -5154,6 +5525,9 @@ start_time += url->duration; } } + else if (duration) { + start_time = in_segment_index * duration; + } if (out_opt_segment_duration) *out_opt_segment_duration = duration; if (out_opt_scale) *out_opt_scale = timescale; @@ -5241,7 +5615,7 @@ GF_EXPORT GF_Err gf_mpd_seek_in_period(Double seek_time, MPDSeekMode seek_mode, GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, - u32 *out_segment_index, Double *out_opt_seek_time) + u32 *out_segment_index, Double *out_opt_seek_time, Double *out_seg_dur) { Double seg_start = 0.0; u32 segment_idx = 0; @@ -5261,6 +5635,7 @@ return e; segment_duration = segment_duration_in_scale / (Double)timescale; + if (out_seg_dur) *out_seg_dur = segment_duration; if (seek_mode == MPD_SEEK_PREV) { if ((seek_time >= seg_start) && (seek_time < seg_start + segment_duration)) { if (out_opt_seek_time) *out_opt_seek_time = seg_start; @@ -5279,7 +5654,6 @@ break; } } else { - gf_assert(0); return GF_NOT_SUPPORTED; } @@ -5291,29 +5665,6 @@ return GF_OK; } -#if 0 //unused -GF_Err gf_mpd_seek_to_time(Double seek_time, MPDSeekMode seek_mode, - GF_MPD const * const in_mpd, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, - GF_MPD_Period **out_period, u32 *out_segment_index, Double *out_opt_seek_time) -{ - GF_Err e = GF_OK; - - if (!out_period || !out_segment_index) { - return GF_BAD_PARAM; - } - - e = mpd_seek_periods(seek_time, in_mpd, out_period); - if (e) - return e; - - e = gf_mpd_seek_in_period(seek_time, seek_mode, *out_period, in_set, in_rep, out_segment_index, out_opt_seek_time); - if (e) - return e; - - return GF_OK; -} -#endif - /* smooth streaming 2.1 support @@ -5332,71 +5683,71 @@ static GF_Err smooth_parse_chunk(GF_MPD *mpd, GF_List *container, GF_XMLNode *root) { - u32 i; - GF_MPD_SegmentTimelineEntry *chunk; - GF_XMLAttribute *att; - - GF_SAFEALLOC(chunk, GF_MPD_SegmentTimelineEntry); - if (!chunk) return GF_OUT_OF_MEM; - gf_list_add(container, chunk); - i = 0; - while ( (att = gf_list_enum(root->attributes, &i)) ) { - if (!strcmp(att->name, "r")) { + u32 i; + GF_MPD_SegmentTimelineEntry *chunk; + GF_XMLAttribute *att; + + GF_SAFEALLOC(chunk, GF_MPD_SegmentTimelineEntry); + if (!chunk) return GF_OUT_OF_MEM; + gf_list_add(container, chunk); + i = 0; + while ( (att = gf_list_enum(root->attributes, &i)) ) { + if (!strcmp(att->name, "r")) { chunk->repeat_count = atoi(att->value); //repeat count is one-based in smooth (number of repeats), 0-based in dash (number of additional repeats) if (chunk->repeat_count) chunk->repeat_count -= 1; } - else if (!strcmp(att->name, "d")) + else if (!strcmp(att->name, "d")) chunk->duration = atoi(att->value); - else if (!strcmp(att->name, "t")) { + else if (!strcmp(att->name, "t")) { sscanf(att->value, LLU, &chunk->start_time); } - } - return GF_OK; + } + return GF_OK; } static GF_Err smooth_replace_string(char *src_str, char *str_match, char *str_replace, char **output) { - u32 len; - char c, *res, *sep = strstr(src_str, str_match); - if (!sep) { - res = gf_strdup(src_str); - if (*output) gf_free(*output); - *output = res; - return GF_OK; - } - - c = sep0; - sep0 = 0; - len = (u32) ( strlen(src_str) + strlen(str_replace) + strlen(sep+strlen(str_match)) + 1 ); - res = gf_malloc(sizeof(char) * len); - strcpy(res, src_str); - strcat(res, str_replace); - strcat(res, sep+strlen(str_match)); - sep0 = c; - - if (*output) gf_free(*output); - *output = res; - return GF_OK; + u32 len; + char c, *res, *sep = strstr(src_str, str_match); + if (!sep) { + res = gf_strdup(src_str); + if (*output) gf_free(*output); + *output = res; + return GF_OK; + } + + c = sep0; + sep0 = 0; + len = (u32) ( strlen(src_str) + strlen(str_replace) + strlen(sep+strlen(str_match)) + 1 ); + res = gf_malloc(sizeof(char) * len); + strcpy(res, src_str); + strcat(res, str_replace); + strcat(res, sep+strlen(str_match)); + sep0 = c; + + if (*output) gf_free(*output); + *output = res; + return GF_OK; } static GF_Err smooth_parse_quality_level(GF_MPD *mpd, GF_List *container, GF_XMLNode *root, u32 timescale) { - u32 i; - Bool is_audio = GF_FALSE; - GF_MPD_Representation *rep; - GF_XMLAttribute *att; - GF_Err e; - char *szISOBMFFInit = NULL; - - GF_SAFEALLOC(rep, GF_MPD_Representation); - if (!rep) return GF_OUT_OF_MEM; - gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)rep); - rep->base_URLs = gf_list_new(); - rep->sub_representations = gf_list_new(); - e = gf_list_add(container, rep); - if (e) return e; + u32 i; + Bool is_audio = GF_FALSE; + GF_MPD_Representation *rep; + GF_XMLAttribute *att; + GF_Err e; + char *szISOBMFFInit = NULL; + + GF_SAFEALLOC(rep, GF_MPD_Representation); + if (!rep) return GF_OUT_OF_MEM; + gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)rep); + rep->base_URLs = gf_list_new(); + rep->sub_representations = gf_list_new(); + e = gf_list_add(container, rep); + if (e) return e; gf_dynstrcat(&szISOBMFFInit, "isobmff://", NULL); @@ -5416,180 +5767,181 @@ gf_dynstrcat(&szISOBMFFInit, " ", NULL);\ - i = 0; - while ( (att = gf_list_enum(root->attributes, &i)) ) { - if (!strcmp(att->name, "Index")) rep->id = gf_strdup(att->value); - else if (!strcmp(att->name, "Bitrate")) rep->bandwidth = atoi(att->value); - else if (!strcmp(att->name, "MaxWidth")) { - rep->width = atoi(att->value); - ISBMFFI_ADD_KEYWORD("w", att->value) - } - else if (!strcmp(att->name, "MaxHeight")) { - rep->height = atoi(att->value); - ISBMFFI_ADD_KEYWORD("h", att->value) - } - else if (!strcmp(att->name, "FourCC")) { - ISBMFFI_ADD_KEYWORD("4cc", att->value) - } - else if (!strcmp(att->name, "CodecPrivateData")) { - ISBMFFI_ADD_KEYWORD("init", att->value) - } - else if (!strcmp(att->name, "NALUnitLengthField")) { - ISBMFFI_ADD_KEYWORD("nal", att->value) - } - else if (!strcmp(att->name, "BitsPerSample")) { - ISBMFFI_ADD_KEYWORD("bps", att->value) - is_audio = GF_TRUE; - } - else if (!strcmp(att->name, "AudioTag")) { - ISBMFFI_ADD_KEYWORD("atag", att->value) - is_audio = GF_TRUE; - } - else if (!strcmp(att->name, "Channels")) { - ISBMFFI_ADD_KEYWORD("ch", att->value) - is_audio = GF_TRUE; - } - else if (!strcmp(att->name, "SamplingRate")) { - ISBMFFI_ADD_KEYWORD("srate", att->value) - is_audio = GF_TRUE; - } - } - if (timescale != 10000000) { - char szTS20, *v; - sprintf(szTS, "%d", timescale); + i = 0; + while ( (att = gf_list_enum(root->attributes, &i)) ) { + if (!strcmp(att->name, "Index")) rep->id = gf_strdup(att->value); + else if (!strcmp(att->name, "Bitrate")) rep->bandwidth = atoi(att->value); + else if (!strcmp(att->name, "MaxWidth")) { + rep->width = atoi(att->value); + ISBMFFI_ADD_KEYWORD("w", att->value) + } + else if (!strcmp(att->name, "MaxHeight")) { + rep->height = atoi(att->value); + ISBMFFI_ADD_KEYWORD("h", att->value) + } + else if (!strcmp(att->name, "FourCC")) { + ISBMFFI_ADD_KEYWORD("4cc", att->value) + } + else if (!strcmp(att->name, "CodecPrivateData")) { + ISBMFFI_ADD_KEYWORD("init", att->value) + } + else if (!strcmp(att->name, "NALUnitLengthField")) { + ISBMFFI_ADD_KEYWORD("nal", att->value) + } + else if (!strcmp(att->name, "BitsPerSample")) { + ISBMFFI_ADD_KEYWORD("bps", att->value) + is_audio = GF_TRUE; + } + else if (!strcmp(att->name, "AudioTag")) { + ISBMFFI_ADD_KEYWORD("atag", att->value) + is_audio = GF_TRUE; + } + else if (!strcmp(att->name, "Channels")) { + ISBMFFI_ADD_KEYWORD("ch", att->value) + is_audio = GF_TRUE; + } + else if (!strcmp(att->name, "SamplingRate")) { + ISBMFFI_ADD_KEYWORD("srate", att->value) + is_audio = GF_TRUE; + } + } + if (timescale != 10000000) { + char szTS20, *v; + sprintf(szTS, "%d", timescale); //prevent gcc warning v = (char *)szTS; - ISBMFFI_ADD_KEYWORD("scale", v) - } - //reserve space for max u64 as hex, without 0x prefix - ISBMFFI_ADD_KEYWORD_CONST("tfdt", "0000000000000000") - //create a url for the IS to be reconstructed - rep->mime_type = gf_strdup(is_audio ? "audio/mp4" : "video/mp4"); - GF_SAFEALLOC(rep->segment_template, GF_MPD_SegmentTemplate); - if (!rep->segment_template) return GF_OUT_OF_MEM; - rep->segment_template->initialization = szISOBMFFInit; - return GF_OK; + ISBMFFI_ADD_KEYWORD("scale", v) + } + //reserve space for max u64 as hex, without 0x prefix + ISBMFFI_ADD_KEYWORD_CONST("tfdt", "0000000000000000") + //create a url for the IS to be reconstructed + rep->mime_type = gf_strdup(is_audio ? "audio/mp4" : "video/mp4"); + GF_SAFEALLOC(rep->segment_template, GF_MPD_SegmentTemplate); + if (!rep->segment_template) return GF_OUT_OF_MEM; + rep->segment_template->initialization = szISOBMFFInit; + return GF_OK; } static GF_Err smooth_parse_stream_index(GF_MPD *mpd, GF_List *container, GF_XMLNode *root, u32 timescale) { - u32 i; - GF_MPD_AdaptationSet *set; - GF_XMLAttribute *att; - GF_XMLNode *child; - - GF_SAFEALLOC(set, GF_MPD_AdaptationSet); - if (!set) return GF_OUT_OF_MEM; - set->id = -1; - gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)set); - - gf_list_add(container, set); - - set->accessibility = gf_list_new(); - set->role = gf_list_new(); - set->rating = gf_list_new(); - set->viewpoint = gf_list_new(); - set->content_component = gf_list_new(); - set->base_URLs = gf_list_new(); - set->representations = gf_list_new(); - set->segment_alignment = GF_TRUE; - /*assign default ID and group*/ - set->group = -1; - - i=0; - while ((att = gf_list_enum(root->attributes, &i))) { - if (!strcmp(att->name, "Type")) {} - else if (!strcmp(att->name, "Name")) {} - else if (!strcmp(att->name, "Chunks")) { + u32 i; + GF_MPD_AdaptationSet *set; + GF_XMLAttribute *att; + GF_XMLNode *child; + + GF_SAFEALLOC(set, GF_MPD_AdaptationSet); + if (!set) return GF_OUT_OF_MEM; + set->id = -1; + gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)set); + + gf_list_add(container, set); + + set->accessibility = gf_list_new(); + set->role = gf_list_new(); + set->rating = gf_list_new(); + set->viewpoint = gf_list_new(); + set->content_component = gf_list_new(); + set->inband_event = gf_list_new(); + set->base_URLs = gf_list_new(); + set->representations = gf_list_new(); + set->segment_alignment = GF_TRUE; + /*assign default ID and group*/ + set->group = -1; + + i=0; + while ((att = gf_list_enum(root->attributes, &i))) { + if (!strcmp(att->name, "Type")) {} + else if (!strcmp(att->name, "Name")) {} + else if (!strcmp(att->name, "Chunks")) { set->smooth_max_chunks = atoi(att->value); } - else if (!strcmp(att->name, "MaxWidth")) set->max_width = atoi(att->value); - else if (!strcmp(att->name, "MaxHeight")) set->max_height = atoi(att->value); - else if (!strcmp(att->name, "Url")) { - char *template_url=NULL; - smooth_replace_string(att->value, "{bitrate}", "$Bandwidth$", &template_url); - smooth_replace_string(template_url, "{Bitrate}", "$Bandwidth$", &template_url); - smooth_replace_string(template_url, "{start time}", "$Time$", &template_url); - smooth_replace_string(template_url, "{start_time}", "$Time$", &template_url); - //TODO handle track substitution and custom attrib - - GF_SAFEALLOC(set->segment_template, GF_MPD_SegmentTemplate); - if (!set->segment_template) return GF_OUT_OF_MEM; - set->segment_template->media = template_url; - set->segment_template->timescale = timescale; - GF_SAFEALLOC(set->segment_template->segment_timeline, GF_MPD_SegmentTimeline); - if (!set->segment_template->segment_timeline) return GF_OUT_OF_MEM; - - set->segment_template->segment_timeline->entries = gf_list_new(); - } - } - - i = 0; - while ( ( child = gf_list_enum(root->content, &i )) ) { - if (!strcmp(child->name, "QualityLevel")) { - smooth_parse_quality_level(mpd, set->representations, child, timescale); - } - if (!strcmp(child->name, "c")) { - smooth_parse_chunk(mpd, set->segment_template->segment_timeline->entries, child); - } - } + else if (!strcmp(att->name, "MaxWidth")) set->max_width = atoi(att->value); + else if (!strcmp(att->name, "MaxHeight")) set->max_height = atoi(att->value); + else if (!strcmp(att->name, "Url")) { + char *template_url=NULL; + smooth_replace_string(att->value, "{bitrate}", "$Bandwidth$", &template_url); + smooth_replace_string(template_url, "{Bitrate}", "$Bandwidth$", &template_url); + smooth_replace_string(template_url, "{start time}", "$Time$", &template_url); + smooth_replace_string(template_url, "{start_time}", "$Time$", &template_url); + //TODO handle track substitution and custom attrib - return GF_OK; + GF_SAFEALLOC(set->segment_template, GF_MPD_SegmentTemplate); + if (!set->segment_template) return GF_OUT_OF_MEM; + set->segment_template->media = template_url; + set->segment_template->timescale = timescale; + GF_SAFEALLOC(set->segment_template->segment_timeline, GF_MPD_SegmentTimeline); + if (!set->segment_template->segment_timeline) return GF_OUT_OF_MEM; + + set->segment_template->segment_timeline->entries = gf_list_new(); + } + } + + i = 0; + while ( ( child = gf_list_enum(root->content, &i )) ) { + if (!strcmp(child->name, "QualityLevel")) { + smooth_parse_quality_level(mpd, set->representations, child, timescale); + } + if (!strcmp(child->name, "c")) { + smooth_parse_chunk(mpd, set->segment_template->segment_timeline->entries, child); + } + } + + return GF_OK; } GF_EXPORT GF_Err gf_mpd_init_smooth_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url) { - GF_Err e; - u32 i, timescale; - GF_XMLAttribute *att; - GF_XMLNode *child; - GF_MPD_Period *period; - u64 tsb = 0; + GF_Err e; + u32 i, timescale; + GF_XMLAttribute *att; + GF_XMLNode *child; + GF_MPD_Period *period; + u64 tsb = 0; - if (!root || !mpd) return GF_BAD_PARAM; + if (!root || !mpd) return GF_BAD_PARAM; gf_mpd_init_struct(mpd); - /*setup some defaults*/ - mpd->type = GF_MPD_TYPE_STATIC; - mpd->time_shift_buffer_depth = (u32) -1; /*infinite by default*/ - mpd->xml_namespace = NULL; + /*setup some defaults*/ + mpd->type = GF_MPD_TYPE_STATIC; + mpd->time_shift_buffer_depth = (u32) -1; /*infinite by default*/ + mpd->xml_namespace = NULL; - timescale = 10000000; - i=0; - while ((att = gf_list_enum(root->attributes, &i))) { - if (!strcmp(att->name, "TimeScale")) + timescale = 10000000; + i=0; + while ((att = gf_list_enum(root->attributes, &i))) { + if (!strcmp(att->name, "TimeScale")) timescale = atoi(att->value); - else if (!strcmp(att->name, "Duration")) + else if (!strcmp(att->name, "Duration")) mpd->media_presentation_duration = atoi(att->value); - else if (!strcmp(att->name, "IsLive") && !stricmp(att->value, "true") ) + else if (!strcmp(att->name, "IsLive") && !stricmp(att->value, "true") ) mpd->type = GF_MPD_TYPE_DYNAMIC; - else if (!strcmp(att->name, "DVRWindowLength")) + else if (!strcmp(att->name, "DVRWindowLength")) sscanf(att->value, LLU, &tsb); -// else if (!strcmp(att->name, "LookaheadCount")) { } - } - mpd->media_presentation_duration = mpd->media_presentation_duration * 1000 / timescale; - tsb *= 1000; - tsb /= timescale; - mpd->time_shift_buffer_depth = (u32) tsb; - - GF_SAFEALLOC(period, GF_MPD_Period); - if (!period) return GF_OUT_OF_MEM; - gf_list_add(mpd->periods, period); - period->adaptation_sets = gf_list_new(); - if (!period->adaptation_sets) return GF_OUT_OF_MEM; - - i = 0; - while ( ( child = gf_list_enum(root->content, &i )) ) { - if (!strcmp(child->name, "StreamIndex")) { - e = smooth_parse_stream_index(mpd, period->adaptation_sets, child, timescale); - if (e) return e; - } - } +// else if (!strcmp(att->name, "LookaheadCount")) { } + } + mpd->media_presentation_duration = mpd->media_presentation_duration * 1000 / timescale; + tsb *= 1000; + tsb /= timescale; + mpd->time_shift_buffer_depth = (u32) tsb; + + GF_SAFEALLOC(period, GF_MPD_Period); + if (!period) return GF_OUT_OF_MEM; + gf_list_add(mpd->periods, period); + period->adaptation_sets = gf_list_new(); + if (!period->adaptation_sets) return GF_OUT_OF_MEM; - return GF_OK; + i = 0; + while ( ( child = gf_list_enum(root->content, &i )) ) { + if (!strcmp(child->name, "StreamIndex")) { + e = smooth_parse_stream_index(mpd, period->adaptation_sets, child, timescale); + if (e) return e; + } + } + + return GF_OK; } GF_EXPORT @@ -5615,7 +5967,9 @@ char *sep = strchr(seg_rad_name+char_template, '$'); \ if (sep) { \ sep0 = 0; \ - strcpy(szFmt, seg_rad_name+char_template); \ + if (gf_mpd_check_print_format(seg_rad_name+char_template)) {\ + strcpy(szFmt, seg_rad_name+char_template); \ + }\ char_template += (u32) strlen(seg_rad_name+char_template); \ sep0 = '$'; \ } \ @@ -5693,7 +6047,11 @@ if (!is_template && !is_init_template && !strnicmp(& seg_rad_namechar_template, "$RepresentationID$", 18) ) { char_template += 18; - strcat(segment_name, rep_id); + if (rep_id) + strcat(segment_name, rep_id); + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("MPD representation id is null when trying to format segment name\n")); + } needs_init = GF_FALSE; } else if (!is_template && !is_init_template && !strnicmp(& seg_rad_namechar_template, "$Bandwidth", 10)) { @@ -5719,6 +6077,24 @@ strcat(segment_name, tmp); has_number = GF_TRUE; } + else if (!strnicmp(& seg_rad_namechar_template, "$SubNumber", 10)) { + char *sep = strchr(seg_rad_name + char_template+10, '$'); + if (!sep) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("MPD Missing final `$` separator\n")); + return GF_BAD_PARAM; + } + if ( (seg_type==GF_DASH_TEMPLATE_TEMPLATE) + || (seg_type==GF_DASH_TEMPLATE_SEGMENT_SUBNUMBER) + ) { + sep0 = 0; + strcat(segment_name, seg_rad_name + char_template); + strcat(segment_name, "$"); + sep0 = '$'; + char_template += 1 +(u32) (sep - (seg_rad_name + char_template)); + } else { + char_template += 1 +(u32) (sep - (seg_rad_name + char_template)); + } + } else if (!is_template && !strnicmp(& seg_rad_namechar_template, "$Number", 7)) { EXTRACT_FORMAT(7); @@ -6056,5 +6432,35 @@ return NULL; } +GF_EXPORT +char *gf_mpd_resolve_subnumber(char *llhas_template, char *segment_filename, u32 part_idx) +{ + char *res = NULL; + char szTmp20; + sprintf(szTmp, "%d", part_idx); + res = gf_strdup(segment_filename); + if (!llhas_template) { + gf_dynstrcat(&res, szTmp, "."); + return res; + } + char *sep = strchr(llhas_template, '$'); + u32 subnum_len = (u32) (sep - llhas_template); + sep0 = 0; + char *name = strstr(res, llhas_template); + namesubnum_len = 0; + sep0 = '$'; + char *sep_fmt = strchr(sep, '%'); + if (sep_fmt) { + char *final = strchr(sep_fmt, '$'); + if (final) final0 = 0; + if (gf_mpd_check_print_format(sep_fmt)) + sprintf(szTmp, sep_fmt, part_idx); + if (final) final0 = '$'; + } + gf_dynstrcat(&res, szTmp, NULL); + sep = strchr(sep+1, '$'); + gf_dynstrcat(&res, sep+1, NULL); + return res; +} #endif /*GPAC_DISABLE_MPD*/
View file
gpac-2.4.0.tar.gz/src/media_tools/mpeg2_ps.c -> gpac-26.02.0.tar.gz/src/media_tools/mpeg2_ps.c
Changed
@@ -368,8 +368,8 @@ static u64 read_pts (u8 *pak) { - u64 pts; - u16 temp; + u64 pts=0; + u16 temp=0; pts = ((pak0 >> 1) & 0x7); pts <<= 15; @@ -421,8 +421,8 @@ u8 *pak, u32 read_from_start) { - u8 stuffed; - u8 readbyte; + u8 stuffed=0; + u8 readbyte=0; u8 val; if (read_from_start < 5) { file_skip_bytes(fd, 5 - read_from_start); @@ -441,7 +441,8 @@ file_skip_bytes(fd, 13 - read_from_start); file_read_bytes(fd, &readbyte, 1); stuffed = readbyte & 0x7; - file_skip_bytes(fd, stuffed); + if (stuffed) + file_skip_bytes(fd, stuffed); } /* @@ -523,8 +524,8 @@ sptr->pes_buffer_size_max = to_move + pes_len + 2048; } } - file_read_bytes(sptr->m_fd, sptr->pes_buffer + sptr->pes_buffer_size, pes_len); - sptr->pes_buffer_size += pes_len; + u32 size_read = (u32) gf_fread(sptr->pes_buffer + sptr->pes_buffer_size, pes_len, sptr->m_fd); + sptr->pes_buffer_size += size_read; } /* @@ -587,7 +588,7 @@ mpeg2ps_ts_t *ts) { u16 pes_len = orig_pes_len; - u8 local10; + u8 local10 = {0}; u32 hdr_len; ts->have_pts = 0; @@ -1074,7 +1075,7 @@ /* * find_stream_from_id - given the stream, get the sptr. - * only used in inital set up, really. APIs use index into + * only used in initial set up, really. APIs use index into * video_streams and audio_streams arrays. */ static mpeg2ps_stream_t *find_stream_from_id (mpeg2ps_t *ps, @@ -1250,6 +1251,7 @@ u8 stream_id, stream_ix, substream, av_ix, max_cnt; u16 pes_len, pes_left; mpeg2ps_ts_t ts; + memset(&ts, 0, sizeof(mpeg2ps_ts_t)); s64 loc, first_video_loc = 0, first_audio_loc = 0; s64 check, orig_check; mpeg2ps_stream_t *sptr;
View file
gpac-2.4.0.tar.gz/src/media_tools/mpegts.c -> gpac-26.02.0.tar.gz/src/media_tools/mpegts.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2025 * * This file is part of GPAC / MPEG2-TS sub-project * @@ -111,6 +111,10 @@ return "Metadata (PES)"; case GF_M2TS_METADATA_ID3_HLS: return "ID3/HLS Metadata (PES)"; + case GF_M2TS_METADATA_ID3_KLVA: + return "ID3/KLV Metadata (PES)"; + case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: + return "SCTE35 splice_info_section (Section)"; default: return "Unknown"; @@ -153,86 +157,17 @@ return 0; } - -static void add_text(char **buffer, u32 *size, u32 *pos, char *msg, u32 msg_len) +static u32 gf_m2ts_reframe_add_prop(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, Bool same_pts, unsigned char *data, u32 data_len, GF_M2TS_PESHeader *pes_hdr) { - if (!msg || !buffer) return; - - if (*pos+msg_len>*size) { - *size = *pos+msg_len-*size+256; - *buffer = (char *)gf_realloc(*buffer, *size); - } - if (! *buffer) - return; - - memcpy((*buffer)+(*pos), msg, msg_len); - (*buffer)*pos+msg_len = 0; - *pos += msg_len; -} - -static GF_Err id3_parse_tag(char *data, u32 length, char **output, u32 *output_size, u32 *output_pos) -{ - GF_BitStream *bs; - u32 pos, size; - - if ((data0 != 'I') || (data1 != 'D') || (data2 != '3')) - return GF_NOT_SUPPORTED; - - bs = gf_bs_new(data, length, GF_BITSTREAM_READ); - - gf_bs_skip_bytes(bs, 3); - /*u8 major = */gf_bs_read_u8(bs); - /*u8 minor = */gf_bs_read_u8(bs); - /*u8 unsync = */gf_bs_read_int(bs, 1); - /*u8 ext_hdr = */ gf_bs_read_int(bs, 1); - gf_bs_read_int(bs, 6); - /*size = */gf_id3_read_size(bs); - - pos = (u32) gf_bs_get_position(bs); - size = length-pos; - - while (size && (gf_bs_available(bs)>=10) ) { - u32 ftag = gf_bs_read_u32(bs); - u32 fsize = gf_id3_read_size(bs); - /*u16 fflags = */gf_bs_read_u16(bs); - size -= 10; - - //TODO, handle more ID3 tags ? - if (ftag==GF_ID3V2_FRAME_TXXX) { - u32 tpos = (u32) gf_bs_get_position(bs); - char *text = data+tpos; - add_text(output, output_size, output_pos, text, fsize); - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS ID3 tag not handled, patch welcome\n", gf_4cc_to_str(ftag) ) ); - } - gf_bs_skip_bytes(bs, fsize); - } - gf_bs_del(bs); - return GF_OK; -} - -static u32 gf_m2ts_reframe_id3_pes(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, Bool same_pts, unsigned char *data, u32 data_len, GF_M2TS_PESHeader *pes_hdr) -{ - char frame_header256; - char *output_text = NULL; - u32 output_len = 0; - u32 pos = 0; GF_M2TS_PES_PCK pck; pck.flags = 0; - if (pes->rap) pck.flags |= GF_M2TS_PES_PCK_RAP; - if (!same_pts) pck.flags |= GF_M2TS_PES_PCK_AU_START; pck.DTS = pes->DTS; pck.PTS = pes->PTS; - sprintf(frame_header, LLU" --> NEXT\n", pes->PTS); - add_text(&output_text, &output_len, &pos, frame_header, (u32)strlen(frame_header)); - id3_parse_tag((char *)data, data_len, &output_text, &output_len, &pos); - add_text(&output_text, &output_len, &pos, "\n\n", 2); - pck.data = (char *)output_text; - pck.data_len = pos; + pck.data = (char *)data; + pck.data_len = data_len; pck.stream = pes; - ts->on_event(ts, GF_M2TS_EVT_PES_PCK, &pck); - gf_free(output_text); - /*we consumed all data*/ + pck.stream->stream_type = pes->stream_type; + ts->on_event(ts, GF_M2TS_EVT_ID3/*should depend on pes->streamtype*/, &pck); return 0; } @@ -362,9 +297,15 @@ if (pes->temi_tc_desc) gf_free(pes->temi_tc_desc); if (pes->metadata_descriptor) gf_m2ts_metadata_descriptor_del(pes->metadata_descriptor); + if (pes->gpac_meta_dsi) gf_free(pes->gpac_meta_dsi); } if (es->slcfg) gf_free(es->slcfg); + for (u32 i=0; i<GF_M2TS_MAX_STREAMS; i++) { + if (ts->essi==es) { + ts->essi = NULL; + } + } gf_free(es); } @@ -432,6 +373,13 @@ ts->on_mpe_event(ts, GF_M2TS_EVT_DVB_MPE, &pck); } #endif + else if ((ts->on_event && (sec->section0==GF_M2TS_TABLE_ID_SCTE35_SPLICE_INFO)) ) { + GF_M2TS_SL_PCK pck; + pck.data_len = sec->length; + pck.data = sec->section; + pck.stream = (GF_M2TS_ES *)ses; + ts->on_event(ts, GF_M2TS_EVT_SCTE35_SPLICE_INFO, &pck); + } else if (ts->on_event) { GF_M2TS_SL_PCK pck; pck.data_len = sec->length; @@ -554,7 +502,7 @@ section_start = 3; } /*process section*/ - if (section_valid) { + if (section_valid && sec->length > section_start) { GF_M2TS_Section *section; GF_SAFEALLOC(section, GF_M2TS_Section); @@ -709,7 +657,7 @@ sec->section = (char*)gf_realloc(sec->section, sizeof(char)*sec->length); } - if (sec->length && sec->received + ptr_field >= sec->length) { + if (sec->length && (sec->received < sec->length) && (data_size >= (u32) (1 + sec->length - sec->received))) { u32 len = sec->length - sec->received; memcpy(sec->section + sec->received, data+1, sizeof(char)*len); sec->received += len; @@ -807,7 +755,7 @@ //orig_net_id = (data0 << 8) | data1; pos = 3; - while (pos < data_size) { + while (pos+4 < data_size) { GF_M2TS_SDT *sdt; u32 descs_size, d_pos, ulen; @@ -825,14 +773,13 @@ sdt->free_CA_mode = (datapos+3>>4) & 0x1; descs_size = ((datapos+3&0xf)<<8) | datapos+4; pos += 5; - if (pos+descs_size > data_size) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid descriptors size read from data (%u)\n", descs_size)); return; } d_pos = 0; - while (d_pos < descs_size) { + while (d_pos+1 < descs_size) { u8 d_tag = datapos+d_pos; u8 d_len = datapos+d_pos+1; @@ -844,16 +791,39 @@ sdt->service = NULL; d_pos+=2; + if (pos+d_pos+1 >= data_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid descriptors size read from data (%u)\n", descs_size)); + return; + } sdt->service_type = datapos+d_pos; ulen = datapos+d_pos+1; + d_pos += 2; + if (pos+d_pos+ulen > data_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid descriptors size read from data (%u)\n", descs_size)); + return; + } sdt->provider = (char*)gf_malloc(sizeof(char)*(ulen+1)); memcpy(sdt->provider, data+pos+d_pos, sizeof(char)*ulen); sdt->providerulen = 0; + d_pos += ulen; + if (pos+d_pos >= data_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid descriptors size read from data (%u)\n", descs_size)); + gf_free(sdt->provider); + sdt->provider = NULL; + return; + } ulen = datapos+d_pos; d_pos += 1; + if (pos+d_pos+ulen > data_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid descriptors size read from data (%u)\n", descs_size)); + gf_free(sdt->provider); + sdt->provider = NULL; + return; + } + sdt->service = (char*)gf_malloc(sizeof(char)*(ulen+1)); memcpy(sdt->service, data+pos+d_pos, sizeof(char)*ulen); sdt->serviceulen = 0; @@ -901,6 +871,37 @@ } } +static void gf_m2ts_process_generic_section(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) +{ + GF_M2TS_GenericSectionInfo sinfo; + u32 i, nb_sec, evt_type; + if (!ts->on_event) return; + + /*skip if already received*/ + if (status & GF_M2TS_TABLE_REPEAT) + if (!(es->flags & GF_M2TS_ES_SEND_REPEATED_SECTIONS)) + return; + + evt_type = (status & GF_M2TS_TABLE_UPDATE) ? GF_M2TS_EVT_SECTION_UPDATE : GF_M2TS_EVT_SECTION; + + memset(&sinfo, 0, sizeof(GF_M2TS_GenericSectionInfo)); + sinfo.stream = (GF_M2TS_ES *) es; + sinfo.table_id = table_id; + sinfo.version_number = version_number; + sinfo.ex_table_id = ex_table_id; + sinfo.pts = es->program->last_pcr_value/300; + sinfo.num_sections = nb_sec = gf_list_count(sections); + for (i=0; i<nb_sec; i++) { + GF_M2TS_Section *section = (GF_M2TS_Section *)gf_list_get(sections, i); + sinfo.section_idx = i; + sinfo.section_data = section->data; + sinfo.section_data_len = section->data_size; + + ts->on_event(ts, evt_type, &sinfo); + } +} + + static void gf_m2ts_process_nit(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *nit_es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) { GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS NIT table processing (not yet implemented)")); @@ -1085,7 +1086,7 @@ d->ts_id = gf_bs_read_u16(bs); size += 4; } - if (length-size > 0) { + if (length > size) { d->data_size = length-size; d->data = (char *)gf_malloc(d->data_size); gf_bs_read_data(bs, d->data, d->data_size); @@ -1190,13 +1191,14 @@ data = section->data; data_size = section->data_size; - if (data_size < 6) { + if (data_size < 4) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid PMT header data size %d\n", data_size ) ); return; } pmt->program->pcr_pid = ((data0 & 0x1f) << 8) | data1; + Bool is_scrambled = GF_FALSE; info_length = ((data2&0xf)<<8) | data3; if (info_length + 4 > data_size) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken PMT first loop, %d bytes avail but first loop size %d\n", data_size, info_length)); @@ -1205,11 +1207,16 @@ /* ...Read Descriptors ... */ u32 tag, len; u32 first_loop_len = 0; + if (data_size < 6) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS Invalid PMT header data size %d\n", data_size ) ); + return; + } + tag = (u32) data4; len = (u32) data5; while (info_length > first_loop_len) { if (tag == GF_M2TS_MPEG4_IOD_DESCRIPTOR) { - if ((len>2) && (len - 2 <= info_length)) { + if ((len>2) && (len - 2 <= info_length) && (data_size>8) && (data_size-8 > (u32)len-2)) { u32 size; GF_BitStream *iod_bs; iod_bs = gf_bs_new((char *)data+8, len-2, GF_BITSTREAM_READ); @@ -1257,6 +1264,11 @@ /* don't know what to do with it for now, delete */ gf_m2ts_metadata_pointer_descriptor_del(metapd); } + } else if(tag == GF_M2TS_REGISTRATION_DESCRIPTOR && len >= 4 && data_size>9) { + u32 reg_desc_format = GF_4CC(data6, data7, data8, data9); + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("MPEG-2 TS Registration descriptor with format_identifier \"%s\"\n", gf_4cc_to_str(reg_desc_format))); + } else if (tag==GF_M2TS_CA_DESCRIPTOR) { + is_scrambled = GF_TRUE; } else { GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS Skipping descriptor (0x%x) and others not supported\n", tag)); } @@ -1268,13 +1280,6 @@ data_size -= 4 + info_length; pos = 0; - /* count de number of program related PMT received */ - for(i=0; i<gf_list_count(ts->programs); i++) { - GF_M2TS_Program *prog = (GF_M2TS_Program *)gf_list_get(ts->programs,i); - if(prog->pmt_pid == pmt->pid) { - break; - } - } nb_hevc_temp = nb_shvc = nb_shvc_temp = nb_mhvc = nb_mhvc_temp = 0; while (pos<data_size) { @@ -1395,6 +1400,10 @@ case GF_M2TS_HLS_AAC_CRYPT: case GF_M2TS_HLS_AC3_CRYPT: case GF_M2TS_HLS_EC3_CRYPT: + case GF_M2TS_VIDEO_AVS2: + case GF_M2TS_AUDIO_AVS2: + case GF_M2TS_VIDEO_AVS3: + case GF_M2TS_AUDIO_AVS3: case 0xA1: GF_SAFEALLOC(pes, GF_M2TS_PES); if (!pes) { @@ -1446,6 +1455,7 @@ case GF_M2TS_PRIVATE_SECTION: case GF_M2TS_QUALITY_SEC: case GF_M2TS_MORE_SEC: + case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: GF_SAFEALLOC(ses, GF_M2TS_SECTION_ES); if (!ses) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG2TS Failed to allocate ES for pid %d\n", pid)); @@ -1461,6 +1471,8 @@ GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("Quality metadata sections on pid %d\n", pid)); } else if (stream_type == GF_M2TS_MORE_SEC) { GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("MORE sections on pid %d\n", pid)); + } else if (stream_type == GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS) { + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("SCTE35 Splice Info sections on pid %d\n", pid)); } else { GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("stream type DSM CC user private sections on pid %d \n", pid)); } @@ -1528,15 +1540,18 @@ /* cf https://smpte-ra.org/registered-mpeg-ts-ids */ switch (reg_desc_format) { case GF_M2TS_RA_STREAM_AC3: - //don't overwrite if alread EAC3 or TrueHD + //don't overwrite if already EAC3 or TrueHD if ((es->stream_type != GF_M2TS_AUDIO_EC3) && (es->stream_type != GF_M2TS_AUDIO_TRUEHD)) es->stream_type = GF_M2TS_AUDIO_AC3; break; case GF_M2TS_RA_STREAM_EAC3: - //don't overwrite if alread AC3 or TrueHD + //don't overwrite if already AC3 or TrueHD if ((es->stream_type != GF_M2TS_AUDIO_AC3) && (es->stream_type != GF_M2TS_AUDIO_TRUEHD)) es->stream_type = GF_M2TS_AUDIO_EC3; break; + case GF_M2TS_RA_STREAM_AC4: + es->stream_type = GF_M2TS_AUDIO_AC4; + break; case GF_M2TS_RA_STREAM_VC1: es->stream_type = GF_M2TS_VIDEO_VC1; break; @@ -1548,6 +1563,15 @@ case GF_M2TS_RA_STREAM_DTS3: es->stream_type = GF_M2TS_AUDIO_DTS; break; + case GF_M2TS_RA_STREAM_AVSA: + es->stream_type = GF_M2TS_AUDIO_AVS3; + break; + case GF_M2TS_RA_STREAM_AVSV: + es->stream_type = GF_M2TS_VIDEO_AVS3; + // AVS3-P6-TAI 109.6 Table-9 + if (len<10) break; + memcpy(pes->avs3_video_descriptor, data+6, 10); + break; case GF_M2TS_RA_STREAM_OPUS: es->stream_type = GF_M2TS_AUDIO_OPUS; break; @@ -1556,6 +1580,15 @@ case GF_M2TS_RA_STREAM_AV1: es->stream_type = GF_M2TS_VIDEO_AV1; break; + case GF_M2TS_RA_STREAM_SCTE35: + es->stream_type = GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS; + break; + case GF_M2TS_RA_STREAM_SRT: + es->stream_type = GF_M2TS_METADATA_SRT; + break; + case GF_M2TS_RA_STREAM_TXT: + es->stream_type = GF_M2TS_METADATA_TEXT; + break; case GF_M2TS_RA_STREAM_GPAC: if (len<8) break; @@ -1587,20 +1620,24 @@ } break; case GF_M2TS_DVB_SUBTITLING_DESCRIPTOR: - if (pes && (len>=8)) { - pes->sub.language0 = data2; - pes->sub.language1 = data3; - pes->sub.language2 = data4; - pes->sub.type = data5; - pes->sub.composition_page_id = (data6<<8) | data7; - pes->sub.ancillary_page_id = (data8<<8) | data9; + if (pes) { + if (len>=8) { + pes->sub.language0 = data2; + pes->sub.language1 = data3; + pes->sub.language2 = data4; + pes->sub.type = data5; + pes->sub.composition_page_id = (data6<<8) | data7; + pes->sub.ancillary_page_id = (data8<<8) | data9; + } else { + memset(& (pes->sub), 0, sizeof(pes->sub)); + } + es->stream_type = GF_M2TS_DVB_SUBTITLE; } - es->stream_type = GF_M2TS_DVB_SUBTITLE; break; case GF_M2TS_DVB_STREAM_IDENTIFIER_DESCRIPTOR: if (len>=1) { es->component_tag = data2; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Component Tag: %d on Program %d\n", es->component_tag, es->program->number)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Component Tag: %d on Program %d PID %d\n", es->component_tag, es->program->number, es->pid)); } break; case GF_M2TS_DVB_TELETEXT_DESCRIPTOR: @@ -1640,19 +1677,33 @@ gf_bs_del(metadatad_bs); if (metad->application_format_identifier == GF_M2TS_META_ID3 && metad->format_identifier == GF_M2TS_META_ID3) { - /*HLS ID3 Metadata */ + /*HLS ID3 Metadata*/ if (pes) { if (pes->metadata_descriptor) gf_m2ts_metadata_descriptor_del(pes->metadata_descriptor); pes->metadata_descriptor = metad; pes->stream_type = GF_M2TS_METADATA_ID3_HLS; } + else { + gf_m2ts_metadata_descriptor_del(metad); + } + } else if (metad->format_identifier == GF_M2TS_META_KLVA) { + /*ID3 with KLVA generic encoding (https://en.wikipedia.org/wiki/KLV)*/ + if (pes) { + if (pes->metadata_descriptor) + gf_m2ts_metadata_descriptor_del(pes->metadata_descriptor); + pes->metadata_descriptor = metad; + pes->stream_type = GF_M2TS_METADATA_ID3_KLVA; + } + else { + gf_m2ts_metadata_descriptor_del(metad); + } } else { /* don't know what to do with it for now, delete */ gf_m2ts_metadata_descriptor_del(metad); } } - break; + break; case GF_M2TS_HEVC_VIDEO_DESCRIPTOR: if (es) es->stream_type = GF_M2TS_VIDEO_HEVC; break; @@ -1676,6 +1727,39 @@ gf_bs_del(hbs); } break; + case GF_M2TS_MAX_BITRATE_DESCRIPTOR: + break; + + case GF_M2TS_DVB_EXT_DESCRIPTOR: + if ((len>4) && (data2 == 0x06) && pes) { + u32 flags = data3; + u32 aflags = (flags >> 2) & 0x1F; + if ((flags & 0x80) == 0) + pes->audio_flags |= GF_M2TS_AUDIO_SUBSTREAM_COMP; + + switch (aflags) { + case 0x01: + pes->audio_flags |= GF_M2TS_AUDIO_DESCRIPTION; + break; + case 0x02: + pes->audio_flags |= GF_M2TS_AUDIO_HEARING_IMPAIRED; + break; + case 0x03: + pes->audio_flags |= GF_M2TS_AUDIO_SUB_DESCRIPTION; + break; + } + + if ((flags & 0x01) && (len>=7)) { + pes->lang = ' '; + pes->lang <<= 8; + pes->lang = data4; + pes->lang <<= 8; + pes->lang = data5; + pes->lang <<= 8; + pes->lang = data6; + } + } + break; default: GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS skipping descriptor (0x%x) not supported\n", tag)); @@ -1692,9 +1776,14 @@ desc_len-=len+2; } if (es && !es->stream_type) { +#if 0 gf_free(es); es = NULL; GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS Private Stream type (0x%x) for PID %d not supported\n", stream_type, pid ) ); +#else + es->stream_type = stream_type; +#endif + } if (!es) continue; @@ -1702,6 +1791,7 @@ //non-compatible base layer dolby vision pes->dv_info24 = 1; } + if (pes) pes->is_protected = is_scrambled; if (ts->esspid) { //this is component reuse across programs, overwrite the previously declared stream ... @@ -1726,7 +1816,7 @@ && (o_es->mpeg4_es_id == es->mpeg4_es_id) && ((o_es->flags & GF_M2TS_ES_IS_SECTION) || ((GF_M2TS_PES *)o_es)->lang == ((GF_M2TS_PES *)es)->lang) ) { - gf_free(es); + gf_m2ts_es_del(es, ts); es = NULL; } else { gf_m2ts_es_del(o_es, ts); @@ -2016,6 +2106,7 @@ pesh->PTS = gf_m2ts_get_pts(data); data+=5; len_check += 5; + pesh->has_pts = 1; } if (has_dts) { pesh->DTS = gf_m2ts_get_pts(data); @@ -2030,7 +2121,7 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d Wrong pes_header_data_length field %d bytes - read %d\n", pes->pid, pesh->hdr_data_len, len_check)); } - if ((pesh->PTS<90000) && ((s32)pesh->DTS<0)) { + if ((pesh->PTS<90000) && ((s32)pesh->DTS<0) && (GF_M2TS_MAX_PCR_90K - pesh->DTS >= 18000)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d Wrong DTS %d negative for PTS %d - forcing to 0\n", pes->pid, pesh->DTS, pesh->PTS)); pesh->DTS=0; } @@ -2062,7 +2153,7 @@ } gf_bs_del(bs); pes->temi_tc_desc_len = 0; - pes->temi_pending = 1; + pes->temi_pending = GF_TRUE; } void gf_m2ts_flush_pes(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, u32 force_flush_type) @@ -2098,7 +2189,7 @@ gf_m2ts_pes_header(pes, pes->pck_data + 3, pes->pck_data_len - 3, &pesh); /*send PES timing*/ - if (ts->notify_pes_timing) { + if (ts->notify_pes_timing && pesh.has_pts) { GF_M2TS_PES_PCK pck; memset(&pck, 0, sizeof(GF_M2TS_PES_PCK)); pck.PTS = pesh.PTS; @@ -2108,10 +2199,11 @@ pes->pes_end_packet_number = ts->pck_number; if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_PES_TIMING, &pck); } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d Got PES header DTS %d PTS %d\n", pes->pid, pesh.DTS, pesh.PTS)); - if (pesh.PTS) { - if (pesh.PTS == pes->PTS) { + if (pesh.has_pts) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d Got PES header DTS %u PTS %u\n", pes->pid, pesh.DTS, pesh.PTS)); + + if (pes->before_last_pes_start_pn && (pesh.PTS == pes->PTS)) { same_pts = GF_TRUE; if ((pes->stream_type==GF_M2TS_AUDIO_TRUEHD) || (pes->stream_type==GF_M2TS_AUDIO_EC3)) { same_pts = GF_FALSE; @@ -2129,14 +2221,14 @@ pes->PTS = pesh.PTS; #ifndef GPAC_DISABLE_LOG - { - if (!pes->is_resume && pes->DTS && (pesh.DTS == pes->DTS)) { - if ((pes->stream_type==GF_M2TS_AUDIO_TRUEHD) || (pes->stream_type==GF_M2TS_AUDIO_EC3)) { - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d - same DTS "LLU" for two consecutive PES packets \n", pes->pid, pes->DTS)); - } + if (!pes->is_resume && pes->before_last_pes_start_pn && (pesh.DTS == pes->DTS)) { + if ((pes->stream_type==GF_M2TS_AUDIO_TRUEHD) || (pes->stream_type==GF_M2TS_AUDIO_EC3)) { + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d - same DTS "LLU" for two consecutive PES packets \n", pes->pid, pes->DTS)); } - if (pesh.DTS < pes->DTS) { + } + if (pesh.DTS < pes->DTS) { + if (GF_M2TS_MAX_PCR_90K + pesh.DTS < pes->DTS) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d - DTS "LLU" less than previous DTS "LLU"\n", pes->pid, pesh.DTS, pes->DTS)); } } @@ -2144,8 +2236,11 @@ pes->DTS = pesh.DTS; } /*no PTSs were coded, same time*/ - else if (!pesh.hdr_data_len) { - same_pts = GF_TRUE; + else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d Got PES header no timestamps\n", pes->pid)); + + if (!pesh.hdr_data_len) + same_pts = GF_TRUE; } pes->is_resume = GF_FALSE; @@ -2200,20 +2295,21 @@ } } - if (!pes->temi_pending && pes->temi_tc_desc_len) { + if (pes->temi_tc_desc_len && !pes->temi_pending) { gf_m2ts_store_temi(ts, pes); } if (pes->temi_pending) { - pes->temi_pending = 0; + pes->temi_pending = GF_FALSE; pes->temi_tc.pes_pts = pes->PTS; pes->temi_tc.pid = pes->pid; if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_TEMI_TIMECODE, &pes->temi_tc); } - if (! ts->seek_mode) + if (!ts->seek_mode && pes->pck_data_len > offset) { remain = pes->reframe(ts, pes, same_pts, pes->pck_data+offset, pes->pck_data_len-offset, &pesh); + } //CLEANUP alloc stuff if (pes->prev_data) gf_free(pes->prev_data); @@ -2226,8 +2322,12 @@ pes->prev_data_len = remain; } } - } else if (pes->pck_data_len) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PES %d: Bad PES Header, discarding packet (maybe stream is encrypted ?)\n", pes->pid)); + } else if (pes->pck_data_len < 4) { + if (pes->pck_data_len) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d invalid PES header size %u\n", pes->pid, pes->pck_data_len)); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d invalid PES startcode %02x%02x%02x\n", pes->pid, pes->pck_data0, pes->pck_data1, pes->pck_data2)); } exit: @@ -2461,6 +2561,10 @@ char *_url = URL; u8 scheme = gf_bs_read_int(bs, 8); u8 url_len = gf_bs_read_int(bs, 8); + if (url_len + 4 > desc_len) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d: Invalid AF Location descriptor (size=%u) found (scheme=%u, url_len=%u))\n", pid, desc_len, scheme, url_len)); + break; + } u8 scheme_len = 0; switch (scheme) { case 1: @@ -2512,6 +2616,8 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d: Adaptation Field found: Discontinuity %d - RAP %d - PCR: "LLD"\n", pid, paf->discontinuity_indicator, paf->random_access_indicator, paf->PCR_flag ? paf->PCR_base * 300 + paf->PCR_ext : 0)); } +static void gf_m2ts_reset_parsers_for_program_ex(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog, Bool keep_pcr); + static GF_Err gf_m2ts_process_packet(GF_M2TS_Demuxer *ts, unsigned char *data) { GF_M2TS_ES *es; @@ -2526,6 +2632,10 @@ hdr.sync = data0; if (hdr.sync != 0x47) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d does not start with sync marker\n", ts->pck_number)); + if (ts->raw_mode == GF_M2TS_RAW_PROBE) + ts->pck_errors++; + else + ts->pck_number--; return GF_CORRUPTED_DATA; } hdr.error = (data1 & 0x80) ? 1 : 0; @@ -2537,7 +2647,8 @@ hdr.continuity_counter = data3 & 0xf; if (hdr.error) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d has error (PID could be %d)\n", ts->pck_number, hdr.pid)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d has error (PID could be %d)\n", ts->pck_number, hdr.pid)); + ts->pck_errors++; return GF_CORRUPTED_DATA; } //#if DEBUG_TS_PACKET @@ -2545,8 +2656,15 @@ //#endif if (hdr.scrambling_ctrl) { - //TODO add decyphering - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d is scrambled - not supported\n", ts->pck_number, hdr.pid)); + if (ts->raw_mode==GF_M2TS_RAW_FORWARD) { + GF_M2TS_TSPCK tspck; + memset(&tspck, 0, sizeof(GF_M2TS_TSPCK)); + tspck.data = data - pos; + ts->on_event(ts, GF_M2TS_EVT_PCK, &tspck); + return GF_OK; + } + //TODO add decyphering API ? + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d is scrambled - not supported\n", ts->pck_number, hdr.pid)); return GF_NOT_SUPPORTED; } @@ -2560,8 +2678,10 @@ if (af_size>183) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d AF field larger than 183 for AF type 3!\n", ts->pck_number)); //error + ts->pck_errors++; return GF_CORRUPTED_DATA; } + if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; paf = ⁡ memset(paf, 0, sizeof(GF_M2TS_AdaptationField)); if (af_size) gf_m2ts_get_adaptation_field(ts, paf, data+5, af_size, hdr.pid); @@ -2573,8 +2693,10 @@ af_size = data4; if (af_size != 183) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS TS Packet %d AF size is %d when it must be 183 for AF type 2\n", ts->pck_number, af_size)); + ts->pck_errors++; return GF_CORRUPTED_DATA; } + if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; paf = ⁡ memset(paf, 0, sizeof(GF_M2TS_AdaptationField)); gf_m2ts_get_adaptation_field(ts, paf, data+5, af_size, hdr.pid); @@ -2587,19 +2709,29 @@ case 0: return GF_OK; default: + if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; break; } data += pos; /*PAT*/ if (hdr.pid == GF_M2TS_PID_PAT) { + if (ts->raw_mode==GF_M2TS_RAW_FORWARD) { + GF_M2TS_TSPCK tspck; + memset(&tspck, 0, sizeof(GF_M2TS_TSPCK)); + tspck.data = data - pos; + ts->on_event(ts, GF_M2TS_EVT_PCK, &tspck); + return GF_OK; + } gf_m2ts_gather_section(ts, ts->pat, NULL, &hdr, data, payload_size); return GF_OK; } es = ts->esshdr.pid; //we work in split mode - if (ts->split_mode) { + if (ts->raw_mode) { + if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; + GF_M2TS_TSPCK tspck; //process PMT table if (es && (es->flags & GF_M2TS_ES_IS_PMT)) { @@ -2607,9 +2739,13 @@ if (ses->sec) gf_m2ts_gather_section(ts, ses->sec, ses, &hdr, data, payload_size); } //and forward every packet other than PAT + memset(&tspck, 0, sizeof(GF_M2TS_TSPCK)); tspck.stream = es; tspck.pid = hdr.pid; tspck.data = data - pos; + if (paf && paf->PCR_flag) { + tspck.pcr_plus_one = paf->PCR_base * 300 + paf->PCR_ext; + } ts->on_event(ts, GF_M2TS_EVT_PCK, &tspck); return GF_OK; } @@ -2672,20 +2808,29 @@ } } + s64 pcr_diff=0; memset(&pck, 0, sizeof(GF_M2TS_PES_PCK)); - prev_diff_in_us = (s64) (es->program->last_pcr_value /27- es->program->before_last_pcr_value/27); + prev_diff_in_us = (s64) (es->program->last_pcr_value - es->program->before_last_pcr_value)/27; es->program->before_last_pcr_value = es->program->last_pcr_value; es->program->before_last_pcr_value_pck_number = es->program->last_pcr_value_pck_number; es->program->last_pcr_value_pck_number = ts->pck_number; es->program->last_pcr_value = paf->PCR_base * 300 + paf->PCR_ext; if (!es->program->last_pcr_value) es->program->last_pcr_value = 1; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d PCR found "LLU" ("LLU" at 90kHz) - PCR diff is %d us\n", hdr.pid, es->program->last_pcr_value, es->program->last_pcr_value/300, (s32) (es->program->last_pcr_value - es->program->before_last_pcr_value)/27 )); + if (es->program->before_last_pcr_value) { + pcr_diff = es->program->last_pcr_value; + pcr_diff -= es->program->before_last_pcr_value; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d PCR found "LLU" ("LLU" at 90kHz) - PCR diff is %d us\n", hdr.pid, es->program->last_pcr_value, es->program->last_pcr_value/300, (s32) (pcr_diff)/27 )); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d PCR found "LLU" ("LLU" at 90kHz)\n", hdr.pid, es->program->last_pcr_value, es->program->last_pcr_value/300)); + } pck.PTS = es->program->last_pcr_value; pck.stream = (GF_M2TS_PES *)es; //try to ignore all discontinuities that are less than 200 ms (seen in some HLS setup ...) + Bool adjust_pcr=GF_FALSE; if (discontinuity) { s64 diff_in_us = (s64) (es->program->last_pcr_value - es->program->before_last_pcr_value) / 27; u64 diff = ABS(diff_in_us - prev_diff_in_us); @@ -2704,23 +2849,36 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d PCR discontinuity not signaled (diff %d us - PCR diff %d vs prev PCR diff %d)\n", hdr.pid, diff, diff_in_us, prev_diff_in_us)); pck.flags = GF_M2TS_PES_PCK_DISCONTINUITY; } + adjust_pcr = GF_TRUE; } - else if ((es->flags & GF_M2TS_CHECK_DISC) && (es->program->last_pcr_value < es->program->before_last_pcr_value) ) { - s64 diff_in_us = (s64) (es->program->last_pcr_value - es->program->before_last_pcr_value) / 27; + else if ((es->flags & GF_M2TS_CHECK_DISC) && ABS(pcr_diff)>270000000 ) { + s64 diff_in_us = (s64) (pcr_diff) / 27; //if less than 200 ms before PCR loop at the last PCR, this is a PCR loop if (GF_M2TS_MAX_PCR - es->program->before_last_pcr_value < 5400000 /*2*2700000*/) { GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d PCR loop found from "LLU" to "LLU" \n", hdr.pid, es->program->before_last_pcr_value, es->program->last_pcr_value)); + + es->program->pcr_base_offset += GF_M2TS_MAX_PCR; + //do not adjust PCR } else if ((diff_in_us<0) && (diff_in_us >= -200000)) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d new PCR, without discontinuity signaled, is less than previously received PCR (diff %d us) but not too large, trying to ignore discontinuity\n", hdr.pid, diff_in_us)); + adjust_pcr = GF_TRUE; } else { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("MPEG-2 TS PID %d PCR found "LLU" is less than previously received PCR "LLU" (PCR diff %g sec) but no discontinuity signaled\n", hdr.pid, es->program->last_pcr_value, es->program->before_last_pcr_value, (GF_M2TS_MAX_PCR - es->program->before_last_pcr_value + es->program->last_pcr_value) / 27000000.0)); pck.flags = GF_M2TS_PES_PCK_DISCONTINUITY; + adjust_pcr = GF_TRUE; } } + //adjust pcr_base_offset to absorb the discontinuity + if (adjust_pcr) { + s64 pcr_base_offset_delta = es->program->before_last_pcr_value + prev_diff_in_us*27; + pcr_base_offset_delta -= es->program->last_pcr_value; + es->program->pcr_base_offset += pcr_base_offset_delta; + } + if (pck.flags & GF_M2TS_PES_PCK_DISCONTINUITY) { - gf_m2ts_reset_parsers_for_program(ts, es->program); + gf_m2ts_reset_parsers_for_program_ex(ts, es->program, GF_TRUE); } if (ts->on_event) { @@ -2899,8 +3057,7 @@ #endif -GF_EXPORT -void gf_m2ts_reset_parsers_for_program(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog) +static void gf_m2ts_reset_parsers_for_program_ex(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog, Bool keep_pcr) { u32 i; @@ -2928,17 +3085,26 @@ pes->temi_tc_desc = NULL; pes->temi_tc_desc_len = pes->temi_tc_desc_alloc_size = 0; + if (keep_pcr) continue; + pes->before_last_pcr_value = pes->before_last_pcr_value_pck_number = 0; pes->last_pcr_value = pes->last_pcr_value_pck_number = 0; if (pes->program->pcr_pid==pes->pid) { pes->program->last_pcr_value = pes->program->last_pcr_value_pck_number = 0; pes->program->before_last_pcr_value = pes->program->before_last_pcr_value_pck_number = 0; + pes->program->pcr_base_offset = 0; } } } } GF_EXPORT +void gf_m2ts_reset_parsers_for_program(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog) +{ + gf_m2ts_reset_parsers_for_program_ex(ts, prog, GF_FALSE); + +} +GF_EXPORT void gf_m2ts_reset_parsers(GF_M2TS_Demuxer *ts) { gf_m2ts_reset_parsers_for_program(ts, NULL); @@ -3008,6 +3174,11 @@ ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = NULL; } } + else if (mode==GF_M2TS_PES_FRAMING_DEFAULT) { + ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = gf_m2ts_process_generic_section; + } else { + ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = NULL; + } return GF_OK; } @@ -3063,10 +3234,12 @@ case GF_M2TS_PRIVATE_DATA: /* TODO: handle DVB subtitle streams */ break; + case GF_M2TS_METADATA_ID3_HLS: - //TODO - pes->reframe = gf_m2ts_reframe_id3_pes; + case GF_M2TS_METADATA_ID3_KLVA: + pes->reframe = gf_m2ts_reframe_add_prop; break; + default: pes->reframe = gf_m2ts_reframe_default; break; @@ -3214,7 +3387,8 @@ } #endif -#define M2TS_PROBE_SIZE 188000 +//20 packets max +#define M2TS_PROBE_SIZE 188*20 static Bool gf_m2ts_probe_buffer(char *buf, u32 size) { GF_Err e; @@ -3225,6 +3399,7 @@ gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_QUIET); ts = gf_m2ts_demux_new(); + ts->raw_mode = GF_M2TS_RAW_PROBE; e = gf_m2ts_process_data(ts, buf, size); if (!ts->pck_number) { @@ -3241,6 +3416,9 @@ //probe success if after align we have nb_pck - 2 and at least 2 packets if ((nb_pck<2) || (ts->pck_number + 2 < nb_pck)) e = GF_BAD_PARAM; + //accept if we have not too few errors (triggered on error bit and corrupted AF fields) + else if ((nb_pck>3) && ts->pck_errors*3<ts->pck_number) + e = GF_OK; } gf_m2ts_demux_del(ts);
View file
gpac-2.4.0.tar.gz/src/media_tools/route_dmx.c -> gpac-26.02.0.tar.gz/src/media_tools/route_dmx.c
Changed
@@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2018-2024 + * Copyright (c) Telecom ParisTech 2018-2026 * All rights reserved * - * This file is part of GPAC / Media Tools ROUTE (ATSC3, DVB-I) demux sub-project + * This file is part of GPAC / Media Tools ROUTE (ATSC3, DVB-MABR) and DVB-MABR Flute demux sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -34,6 +34,18 @@ #define GF_ROUTE_SOCK_SIZE 0x80000 +Bool gf_sk_has_nrt_netcap(GF_Socket *sk); + +typedef struct __route_service GF_ROUTEService; + +typedef enum +{ + GF_SERVICE_UNDEFINED=0, + GF_SERVICE_ROUTE=1,//as defined in ATSC SLS + GF_SERVICE_MMTP=2,//as defined in ATSC SLS + GF_SERVICE_DVB_FLUTE, +} GF_ServiceProtocolType; + typedef struct { u8 codepoint; @@ -48,18 +60,38 @@ char *filename; u32 toi; u32 crc; + //set if flute, in which case this object is not tracked in the static_files of the LCT channel object + u32 fdt_tsi; + //for flute only, indicate the object is no longer advertized in FDT and can be removed + Bool can_remove; + u32 channel_hint; } GF_ROUTELCTFile; typedef struct { u32 tsi; char *toi_template; + //for route services only, list of static files announced in STSID GF_List *static_files; + u32 num_components; GF_ROUTELCTReg CPs8; u32 nb_cps; u32 last_dispatched_tsi, last_dispatched_toi; Bool tsi_init; + u32 flute_fdt_crc; + + GF_ROUTEService *flute_parent_service; + + Bool tsi_probe; + Bool is_active; + Bool first_seg_received; + + char *dash_period_id; + s32 dash_as_id; + char *dash_rep_id; + + u32 channel_hint; } GF_ROUTELCTChannel; typedef enum @@ -68,10 +100,30 @@ GF_LCT_OBJ_RECEPTION, GF_LCT_OBJ_DONE_ERR, GF_LCT_OBJ_DONE, - GF_LCT_OBJ_DISPATCHED, } GF_LCTObjectStatus; -typedef struct +typedef enum +{ + GF_FLUTE_NONE = 0, + GF_FLUTE_FDT, + GF_FLUTE_DVB_MABR_CFG, + GF_FLUTE_DASH_MANIFEST, + GF_FLUTE_HLS_MANIFEST, + GF_FLUTE_HLS_VARIANT, + GF_FLUTE_OBJ, + GF_FLUTE_PROBE_TYPE, +} GF_FLUTEType; + +typedef struct { + u32 toi; + u32 offset; + u32 length; + //no guarantee that flute symbol size will be the same for all chunks... + u16 flute_symbol_size, flute_nb_symbols; +} GF_FLUTELLMapEntry; + + +typedef struct __gf_lct_object { u32 toi, tsi; u32 total_length; @@ -81,29 +133,40 @@ u32 nb_frags, nb_alloc_frags, nb_recv_frags; GF_LCTFragInfo *frags; GF_LCTObjectStatus status; - u32 download_time_ms; - u32 last_gather_time; + u32 start_time_ms, download_time_ms; + u64 last_active_time; u8 closed_flag; u8 force_keep; + //flag set when the last chunk has been declared in ll_map + u8 ll_map_last; + u8 flute_type; + u8 dispatched; GF_ROUTELCTChannel *rlct; GF_ROUTELCTFile *rlct_file; u32 prev_start_offset; - char solved_pathGF_MAX_PATH; + u32 flute_symbol_size, flute_nb_symbols; - GF_Blob blob; - void *udta; -} GF_LCTObject; + char solved_pathGF_MAX_PATH; + //for flute ll, we rebuild the complete segment so we need a map of chunk TOIs + u32 ll_maps_count, ll_maps_alloc; + GF_FLUTELLMapEntry *ll_map; + GF_Blob blob; + void *udta; +} GF_LCTObject; typedef struct { GF_Socket *sock; + u32 nb_active; GF_List *channels; + char *mcast_addr; + u32 mcast_port; } GF_ROUTESession; typedef enum @@ -113,25 +176,50 @@ GF_ROUTE_TUNE_SLS_ONLY, } GF_ROUTETuneMode; -typedef struct +typedef GF_Err (*gf_service_process)(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTESession *route_sess); +struct __route_service { u32 service_id; - u32 protocol; + GF_ServiceProtocolType protocol; + u32 mpd_version, stsid_version; GF_Socket *sock; u32 secondary_sockets; GF_List *objects; GF_LCTObject *last_active_obj; + u32 nb_media_streams; + //number of active session running on main socket + u32 nb_active; + + gf_service_process process_service; u32 port; char *dst_ip; u32 last_dispatched_toi_on_tsi_zero; u32 stsid_crc; + u32 flute_fdt_crc; + u32 dvb_mabr_cfg_crc; GF_List *route_sessions; GF_ROUTETuneMode tune_mode; void *udta; -} GF_ROUTEService; + + //for flute + u32 manifest_crc; + + char *service_identifier; + char *log_name; + char *repair_uri_base; + char *repair_server; + Bool in_reset; +}; + +//maximum segs we keep in cache when playing from pcap in no realtime: this accounts for +//- init segment +//- oldest segment (used by player) +//- next segment being downloaded +//- following segment for packet reorder tests +#define MAX_SEG_IN_NRT 4 struct __gf_routedmx { const char *ip_ifce; @@ -141,10 +229,12 @@ u32 buffer_size; u8 *unz_buffer; u32 unz_buffer_size; - - u32 reorder_timeout; - Bool force_reorder; - Bool progressive_dispatch; + u32 max_obj_size; + //reordering time in us + u64 reorder_timeout_us; + Bool force_in_order; + GF_RouteProgressiveDispatch dispatch_mode; + u32 nrt_max_seg; u32 slt_version, rrt_version, systime_version, aeat_version; GF_List *services; @@ -158,7 +248,6 @@ GF_SockGroup *active_sockets; - void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *info); void *udta; @@ -168,11 +257,18 @@ u64 total_bytes_recv; u64 first_pck_time, last_pck_time; - //for now use a single mutex for all blob access - GF_Mutex *blob_mx; + //for now use a single mutex for all blob access + GF_Mutex *blob_mx; + Bool dvb_mabr; + Bool start_inactive; + u32 nb_active; }; +static GF_Err dmx_process_service_route(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTESession *route_sess); +static GF_Err dmx_process_service_dvb_flute(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTESession *route_sess); + + static void gf_route_static_files_del(GF_List *files) { while (gf_list_count(files)) { @@ -183,15 +279,21 @@ gf_list_del(files); } -static void gf_route_route_session_del(GF_ROUTESession *rs) +static void gf_route_route_session_del(GF_ROUTEDmx *routedmx, GF_ROUTESession *rs) { - if (rs->sock) gf_sk_del(rs->sock); + if (rs->sock) { + gf_sk_group_unregister(routedmx->active_sockets, rs->sock); + gf_sk_del(rs->sock); + } while (gf_list_count(rs->channels)) { GF_ROUTELCTChannel *lc = gf_list_pop_back(rs->channels); gf_route_static_files_del(lc->static_files); - gf_free(lc->toi_template); + if (lc->toi_template) gf_free(lc->toi_template); + if (lc->dash_period_id) gf_free(lc->dash_period_id); + if (lc->dash_rep_id) gf_free(lc->dash_rep_id); gf_free(lc); } + if (rs->mcast_addr) gf_free(rs->mcast_addr); gf_list_del(rs->channels); gf_free(rs); } @@ -200,6 +302,12 @@ { if (o->frags) gf_free(o->frags); if (o->payload) gf_free(o->payload); + if (o->rlct_file && (o->rlct_file->fdt_tsi || o->rlct_file->can_remove)) { + if (o->rlct_file->filename) gf_free(o->rlct_file->filename); + gf_free(o->rlct_file); + } + + if (o->ll_map) gf_free(o->ll_map); gf_free(o); } @@ -209,18 +317,24 @@ gf_sk_group_unregister(routedmx->active_sockets, s->sock); gf_sk_del(s->sock); } - while (gf_list_count(s->route_sessions)) { - GF_ROUTESession *rsess = gf_list_pop_back(s->route_sessions); - gf_route_route_session_del(rsess); - } - gf_list_del(s->route_sessions); - while (gf_list_count(s->objects)) { GF_LCTObject *o = gf_list_pop_back(s->objects); gf_route_lct_obj_del(o); } gf_list_del(s->objects); + + while (gf_list_count(s->route_sessions)) { + GF_ROUTESession *rsess = gf_list_pop_back(s->route_sessions); + gf_route_route_session_del(routedmx, rsess); + } + gf_list_del(s->route_sessions); + if (s->dst_ip) gf_free(s->dst_ip); + if (s->log_name) gf_free(s->log_name); + if (s->service_identifier) gf_free(s->service_identifier); + if (s->repair_uri_base) gf_free(s->repair_uri_base); + if (s->repair_server) gf_free(s->repair_server); + gf_list_del_item(routedmx->services, s); gf_free(s); } @@ -232,8 +346,8 @@ if (routedmx->buffer) gf_free(routedmx->buffer); if (routedmx->unz_buffer) gf_free(routedmx->unz_buffer); if (routedmx->atsc_sock) gf_sk_del(routedmx->atsc_sock); - if (routedmx->dom) gf_xml_dom_del(routedmx->dom); - if (routedmx->blob_mx) gf_mx_del(routedmx->blob_mx); + if (routedmx->dom) gf_xml_dom_del(routedmx->dom); + if (routedmx->blob_mx) gf_mx_del(routedmx->blob_mx); if (routedmx->services) { while (gf_list_count(routedmx->services)) { GF_ROUTEService *s = gf_list_pop_back(routedmx->services); @@ -253,43 +367,65 @@ gf_free(routedmx); } +static GF_Err routedmx_setup_socket(GF_ROUTEDmx *routedmx, const char *log_name, GF_Socket *sock, const char *dst_ip, u32 dst_port) +{ + if (!dst_ip || !dst_port) return GF_BAD_PARAM; + GF_Err e; + if (gf_sk_is_multicast_address(dst_ip)) { + e = gf_sk_setup_multicast(sock, dst_ip, dst_port, 0, GF_FALSE, (char*) routedmx->ip_ifce); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to bind to multicast address %s:%u on %s interface\n", log_name, dst_ip, dst_port, routedmx->ip_ifce ? routedmx->ip_ifce : "default")); + } + } else { + e = gf_sk_bind(sock, (char*) routedmx->ip_ifce, dst_port, dst_ip, dst_port, GF_SOCK_REUSE_PORT); + if (e) return e; + if (!e) + e = gf_sk_connect(sock, dst_ip, dst_port, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to bind socket %s:%u on %s interface\n", log_name, dst_ip, dst_port, routedmx->ip_ifce ? routedmx->ip_ifce : "default")); + } + } + return e; +} + static GF_ROUTEDmx *gf_route_dmx_new_internal(const char *ifce, u32 sock_buffer_size, const char *netcap_id, Bool is_atsc, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *info), - void *udta) + void *udta, const char *log_name) { GF_ROUTEDmx *routedmx; GF_Err e; GF_SAFEALLOC(routedmx, GF_ROUTEDmx); if (!routedmx) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate ROUTE demuxer\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate ROUTE demuxer\n", log_name)); return NULL; } routedmx->ip_ifce = ifce; routedmx->netcap_id = netcap_id; + routedmx->nrt_max_seg = 0; routedmx->dom = gf_xml_dom_new(); if (!routedmx->dom) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate DOM parser\n" )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate DOM parser\n", log_name)); gf_route_dmx_del(routedmx); return NULL; } routedmx->services = gf_list_new(); if (!routedmx->services) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate ROUTE service list\n" )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate ROUTE service list\n", log_name)); gf_route_dmx_del(routedmx); return NULL; } routedmx->object_reservoir = gf_list_new(); if (!routedmx->object_reservoir) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate ROUTE object reservoir\n" )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate ROUTE object reservoir\n", log_name)); + gf_route_dmx_del(routedmx); + return NULL; + } + routedmx->blob_mx = gf_mx_new("ROUTEBlob"); + if (!routedmx->blob_mx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate ROUTE blob mutex\n", log_name)); gf_route_dmx_del(routedmx); return NULL; } - routedmx->blob_mx = gf_mx_new("ROUTEBlob"); - if (!routedmx->blob_mx) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate ROUTE blob mutex\n" )); - gf_route_dmx_del(routedmx); - return NULL; - } if (!sock_buffer_size) sock_buffer_size = GF_ROUTE_SOCK_SIZE; routedmx->unz_buffer_size = sock_buffer_size; @@ -297,13 +433,13 @@ routedmx->buffer_size = 10000; routedmx->buffer = gf_malloc(routedmx->buffer_size); if (!routedmx->buffer) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate socket buffer\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate socket buffer\n", log_name)); gf_route_dmx_del(routedmx); return NULL; } routedmx->unz_buffer = gf_malloc(routedmx->unz_buffer_size); if (!routedmx->unz_buffer) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate socket buffer\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate socket buffer\n", log_name)); gf_route_dmx_del(routedmx); return NULL; } @@ -311,13 +447,15 @@ routedmx->active_sockets = gf_sk_group_new(); if (!routedmx->active_sockets) { gf_route_dmx_del(routedmx); - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to create socket group\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to create socket group\n", log_name)); return NULL; } //create static bs routedmx->bs = gf_bs_new((char*)&e, 1, GF_BITSTREAM_READ); - routedmx->reorder_timeout = 5000; + routedmx->reorder_timeout_us = 100000; + //50MB max per object - for 10s fragments, this gives 40 mbps which should be enough + routedmx->max_obj_size = 50000000; routedmx->on_event = on_event; routedmx->udta = udta; @@ -328,15 +466,17 @@ routedmx->atsc_sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); if (!routedmx->atsc_sock) { gf_route_dmx_del(routedmx); - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to create UDP socket\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to create UDP socket\n", log_name)); return NULL; } + if (gf_sk_has_nrt_netcap(routedmx->atsc_sock)) + routedmx->nrt_max_seg = MAX_SEG_IN_NRT; gf_sk_set_usec_wait(routedmx->atsc_sock, 1); - e = gf_sk_setup_multicast(routedmx->atsc_sock, GF_ATSC_MCAST_ADDR, GF_ATSC_MCAST_PORT, 1, GF_FALSE, (char *) ifce); + + e = routedmx_setup_socket(routedmx, log_name, routedmx->atsc_sock, GF_ATSC_MCAST_ADDR, GF_ATSC_MCAST_PORT); if (e) { gf_route_dmx_del(routedmx); - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to bind to multicast address on interface %s\n", ifce ? ifce : "default")); return NULL; } gf_sk_set_buffer_size(routedmx->atsc_sock, GF_FALSE, sock_buffer_size); @@ -348,55 +488,88 @@ static void gf_route_register_service_sockets(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, Bool do_register) { - u32 i; - GF_ROUTESession *rsess; - if (do_register) gf_sk_group_register(routedmx->active_sockets, s->sock); - else gf_sk_group_unregister(routedmx->active_sockets, s->sock); - - if (!s->secondary_sockets) return; - - i=0; - while ((rsess = gf_list_enum(s->route_sessions, &i))) { - if (! rsess->sock) continue; - if (do_register) gf_sk_group_register(routedmx->active_sockets, rsess->sock); - else gf_sk_group_unregister(routedmx->active_sockets, rsess->sock); - } + u32 i; + GF_ROUTESession *rsess; + if (do_register) gf_sk_group_register(routedmx->active_sockets, s->sock); + else gf_sk_group_unregister(routedmx->active_sockets, s->sock); + + if (!s->secondary_sockets) return; + + i=0; + while ((rsess = gf_list_enum(s->route_sessions, &i))) { + if (! rsess->sock) continue; + if (do_register) gf_sk_group_register(routedmx->active_sockets, rsess->sock); + else gf_sk_group_unregister(routedmx->active_sockets, rsess->sock); + } } -static void gf_route_create_service(GF_ROUTEDmx *routedmx, const char *dst_ip, u32 dst_port, u32 service_id, u32 protocol) +static GF_ROUTEService *gf_route_create_service(GF_ROUTEDmx *routedmx, const char *dst_ip, u32 dst_port, u32 service_id, GF_ServiceProtocolType protocol_type) { GF_ROUTEService *service; GF_Err e; + char log_name1024; + if ((protocol_type == GF_SERVICE_DVB_FLUTE) && !service_id) { + sprintf(log_name, "DVB-FLUTE RFDT"); + } else { + sprintf(log_name, "%s S%u", (protocol_type==GF_SERVICE_ROUTE) ? "ROUTE" : "DVB-FLUTE", service_id); + } + + switch (protocol_type) { + case GF_SERVICE_ROUTE: + case GF_SERVICE_DVB_FLUTE: + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Unsupported protocol type %d\n", log_name, protocol_type)); + return NULL; + } - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Setting up service %d destination IP %s port %d\n", service_id, dst_ip, dst_port)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Setting up service %d destination IP %s port %d\n", log_name, service_id, dst_ip, dst_port)); GF_SAFEALLOC(service, GF_ROUTEService); if (!service) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to allocate service %d\n", service_id)); - return; + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate service %d\n", log_name, service_id)); + return NULL; } service->service_id = service_id; - service->protocol = protocol; - - service->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); - gf_sk_set_usec_wait(service->sock, 1); - e = gf_sk_setup_multicast(service->sock, dst_ip, dst_port, 0, GF_FALSE, (char*) routedmx->ip_ifce); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to setup multicast on %s:%d for service %d\n", dst_ip, dst_port, service_id)); - gf_route_service_del(routedmx, service); - return; + service->protocol = protocol_type; + if (protocol_type==GF_SERVICE_ROUTE) { + service->process_service = dmx_process_service_route; + } else { + service->process_service = dmx_process_service_dvb_flute; } - gf_sk_set_buffer_size(service->sock, GF_FALSE, routedmx->unz_buffer_size); - //gf_sk_set_block_mode(service->sock, GF_TRUE); - + service->log_name = gf_strdup(log_name); service->dst_ip = gf_strdup(dst_ip); service->port = dst_port; service->objects = gf_list_new(); service->route_sessions = gf_list_new(); - gf_list_add(routedmx->services, service); - if (routedmx->atsc_sock) { + Bool is_mabr_root = (protocol_type == GF_SERVICE_DVB_FLUTE) && !service_id; + if (!routedmx->start_inactive || is_mabr_root) { + + service->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); + if (!routedmx->nrt_max_seg && gf_sk_has_nrt_netcap(service->sock)) + routedmx->nrt_max_seg = MAX_SEG_IN_NRT; + + gf_sk_set_usec_wait(service->sock, 1); + + e = routedmx_setup_socket(routedmx, service->log_name, service->sock, dst_ip, dst_port); + if (e) { + gf_route_service_del(routedmx, service); + return NULL; + } + gf_sk_set_buffer_size(service->sock, GF_FALSE, routedmx->unz_buffer_size); + } + + //flute default service for root FDT carrying DVB MABR manifest + other static (depending on muxers) + //always on + if ((protocol_type == GF_SERVICE_DVB_FLUTE) && !service_id) { + service->tune_mode = GF_ROUTE_TUNE_ON; + gf_sk_group_register(routedmx->active_sockets, service->sock); + return service; + } + + if (routedmx->atsc_sock || routedmx->dvb_mabr) { if (routedmx->service_autotune==0xFFFFFFFF) service->tune_mode = GF_ROUTE_TUNE_ON; else if (routedmx->service_autotune==0xFFFFFFFE) { service->tune_mode = GF_ROUTE_TUNE_ON; @@ -406,17 +579,20 @@ else if (routedmx->tune_all_sls) service->tune_mode = GF_ROUTE_TUNE_SLS_ONLY; //we are tuning, register socket - if (service->tune_mode != GF_ROUTE_TUNE_OFF) { + if (!routedmx->start_inactive && (service->tune_mode != GF_ROUTE_TUNE_OFF)) { //call gf_route_register_service_sockets rather than gf_sk_group_register for coverage purpose only - gf_route_register_service_sockets(routedmx, service, GF_TRUE); - } + gf_route_register_service_sockets(routedmx, service, GF_TRUE); + } } else { service->tune_mode = GF_ROUTE_TUNE_ON; routedmx->service_autotune = service_id; - gf_sk_group_register(routedmx->active_sockets, service->sock); + if (!routedmx->start_inactive) + gf_sk_group_register(routedmx->active_sockets, service->sock); } + if (routedmx->on_event) + routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_SERVICE_FOUND, service_id, NULL); - if (routedmx->on_event) routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_SERVICE_FOUND, service_id, NULL); + return service; } GF_EXPORT @@ -424,16 +600,16 @@ void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *info), void *udta) { - return gf_route_dmx_new_internal(ifce, sock_buffer_size, NULL, GF_TRUE, on_event, udta); + return gf_route_dmx_new_internal(ifce, sock_buffer_size, NULL, GF_TRUE, on_event, udta, "ROUTE"); } GF_EXPORT GF_ROUTEDmx *gf_route_dmx_new(const char *ip, u32 port, const char *ifce, u32 sock_buffer_size, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *info), void *udta) { - GF_ROUTEDmx *routedmx = gf_route_dmx_new_internal(ifce, sock_buffer_size, NULL, GF_FALSE, on_event, udta); + GF_ROUTEDmx *routedmx = gf_route_dmx_new_internal(ifce, sock_buffer_size, NULL, GF_FALSE, on_event, udta, "ROUTE"); if (!routedmx) return NULL; - gf_route_create_service(routedmx, ip, port, 1, 1); + gf_route_create_service(routedmx, ip, port, 1, GF_SERVICE_ROUTE); return routedmx; } @@ -443,16 +619,25 @@ void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *info), void *udta) { - return gf_route_dmx_new_internal(ifce, sock_buffer_size, netcap_id, GF_TRUE, on_event, udta); + return gf_route_dmx_new_internal(ifce, sock_buffer_size, netcap_id, GF_TRUE, on_event, udta, "ROUTE"); } GF_EXPORT GF_ROUTEDmx *gf_route_dmx_new_ex(const char *ip, u32 port, const char *ifce, u32 sock_buffer_size, const char *netcap_id, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *info), void *udta) { - GF_ROUTEDmx *routedmx = gf_route_dmx_new_internal(ifce, sock_buffer_size, netcap_id, GF_FALSE, on_event, udta); + GF_ROUTEDmx *routedmx = gf_route_dmx_new_internal(ifce, sock_buffer_size, netcap_id, GF_FALSE, on_event, udta, "ROUTE"); if (!routedmx) return NULL; - gf_route_create_service(routedmx, ip, port, 1, 1); + gf_route_create_service(routedmx, ip, port, 1, GF_SERVICE_ROUTE); + return routedmx; +} + +GF_ROUTEDmx *gf_dvb_mabr_dmx_new(const char *ip, u32 port, const char *ifce, u32 sock_buffer_size, const char *netcap_id, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo), void *udta) +{ + GF_ROUTEDmx *routedmx = gf_route_dmx_new_internal(ifce, sock_buffer_size, netcap_id, GF_FALSE, on_event, udta, "DVB-FLUTE"); + if (!routedmx) return NULL; + routedmx->dvb_mabr = GF_TRUE; + gf_route_create_service(routedmx, ip, port, 0, GF_SERVICE_DVB_FLUTE); return routedmx; } @@ -462,10 +647,19 @@ u32 i; GF_ROUTEService *s; if (!routedmx) return GF_BAD_PARAM; + if ((s32)serviceID==-3) { + serviceID = 0xFFFFFFFF; + routedmx->start_inactive = GF_TRUE; + } else { + routedmx->start_inactive = GF_FALSE; + } routedmx->service_autotune = serviceID; + if (routedmx->dvb_mabr) tune_all_sls = GF_FALSE; routedmx->tune_all_sls = tune_all_sls; i=0; while ((s = gf_list_enum(routedmx->services, &i))) { + if (routedmx->dvb_mabr && !s->service_id) continue; + GF_ROUTETuneMode prev_mode = s->tune_mode; if (s->service_id==serviceID) s->tune_mode = GF_ROUTE_TUNE_ON; else if (serviceID==0xFFFFFFFF) s->tune_mode = GF_ROUTE_TUNE_ON; @@ -494,75 +688,98 @@ } GF_EXPORT -GF_Err gf_route_set_reorder(GF_ROUTEDmx *routedmx, Bool force_reorder, u32 timeout_ms) +GF_Err gf_route_dmx_set_reorder(GF_ROUTEDmx *routedmx, Bool force_reorder, u32 timeout_us) { if (!routedmx) return GF_BAD_PARAM; - routedmx->reorder_timeout = timeout_ms; - routedmx->force_reorder = force_reorder; + routedmx->reorder_timeout_us = timeout_us; + routedmx->force_in_order = !force_reorder; return GF_OK; } GF_EXPORT -GF_Err gf_route_set_allow_progressive_dispatch(GF_ROUTEDmx *routedmx, Bool allow_progressive) +GF_Err gf_route_set_dispatch_mode(GF_ROUTEDmx *routedmx, GF_RouteProgressiveDispatch dispatch_mode) { - if (!routedmx) return GF_BAD_PARAM; - routedmx->progressive_dispatch = allow_progressive; - return GF_OK; + if (!routedmx) return GF_BAD_PARAM; + routedmx->dispatch_mode = dispatch_mode; + return GF_OK; } + static GF_Err gf_route_dmx_process_slt(GF_ROUTEDmx *routedmx, GF_XMLNode *root) { GF_XMLNode *n; u32 i=0; + GF_List *old_services = gf_list_clone(routedmx->services); while ( ( n = gf_list_enum(root->content, &i)) ) { if (n->type != GF_XML_NODE_TYPE) continue; //setup service - if (!strcmp(n->name, "Service")) { - GF_XMLAttribute *att; - GF_XMLNode *m; - u32 j=0; - const char *dst_ip=NULL; - u32 dst_port = 0; - u32 protocol = 0; - u32 service_id=0; - while ( ( att = gf_list_enum(n->attributes, &j)) ) { - if (!strcmp(att->name, "serviceId")) sscanf(att->value, "%u", &service_id); - } - - j=0; - while ( ( m = gf_list_enum(n->content, &j)) ) { - if (m->type != GF_XML_NODE_TYPE) continue; - if (!strcmp(m->name, "BroadcastSvcSignaling")) { - u32 k=0; - while ( ( att = gf_list_enum(m->attributes, &k)) ) { - if (!strcmp(att->name, "slsProtocol")) protocol = atoi(att->value); - if (!strcmp(att->name, "slsDestinationIpAddress")) dst_ip = att->value; - else if (!strcmp(att->name, "slsDestinationUdpPort")) dst_port = atoi(att->value); - //don't care about the rest - } + if (strcmp(n->name, "Service")) continue; + + GF_XMLAttribute *att; + GF_XMLNode *m; + u32 j=0; + const char *dst_ip=NULL; + u32 dst_port = 0; + u32 protocol = 0; + u32 service_id=0; + while ( ( att = gf_list_enum(n->attributes, &j)) ) { + if (!strcmp(att->name, "serviceId")) sscanf(att->value, "%u", &service_id); + } + + j=0; + while ( ( m = gf_list_enum(n->content, &j)) ) { + if (m->type != GF_XML_NODE_TYPE) continue; + if (!strcmp(m->name, "BroadcastSvcSignaling")) { + u32 k=0; + while ( ( att = gf_list_enum(m->attributes, &k)) ) { + if (!strcmp(att->name, "slsProtocol")) protocol = atoi(att->value); + if (!strcmp(att->name, "slsDestinationIpAddress")) dst_ip = att->value; + else if (!strcmp(att->name, "slsDestinationUdpPort")) dst_port = atoi(att->value); + //don't care about the rest } } + } - if (!dst_ip || !dst_port) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE No service destination IP or port found for service %d - ignoring service\n", service_id)); - continue; - } - if (protocol==2) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE ATSC service %d using MMTP protocol is not supported - ignoring\n", service_id)); - continue; - } - if (protocol!=1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Unknown ATSC signaling protocol %d for service %d - ignoring\n", protocol, service_id)); + if (!dst_ip || !dst_port) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ATSC No service destination IP or port found for service %d - ignoring service\n", service_id)); + continue; + } + if (protocol==2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ATSC ATSC service %d using MMTP protocol is not supported - ignoring\n", service_id)); + continue; + } + if (protocol!=1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ATSC Unknown ATSC signaling protocol %d for service %d - ignoring\n", protocol, service_id)); + continue; + } + protocol = GF_SERVICE_ROUTE; + + GF_ROUTEService *orig_serv=NULL; + for (j=0; j<gf_list_count(routedmx->services); j++) { + orig_serv = gf_list_get(routedmx->services, j); + if (orig_serv->service_id==service_id) break; + orig_serv=NULL; + } + if (orig_serv) { + gf_list_del_item(old_services, orig_serv); + if (!strcmp(dst_ip, orig_serv->dst_ip) && (orig_serv->port==dst_port) && (orig_serv->protocol==protocol)) { continue; } - - //todo - remove existing service ? - gf_route_create_service(routedmx, dst_ip, dst_port, service_id, protocol); - + //remove service + gf_list_del_item(routedmx->services, orig_serv); + gf_route_service_del(routedmx, orig_serv); } + gf_route_create_service(routedmx, dst_ip, dst_port, service_id, protocol); + } + //remove all non redeclared services + while (gf_list_count(old_services)) { + GF_ROUTEService *serv = gf_list_pop_back(old_services); + gf_route_service_del(routedmx, serv); } - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Done scaning all services\n")); + gf_list_del(old_services); + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ATSC Done scaning all services\n")); if (routedmx->on_event) routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_SERVICE_SCAN, 0, NULL); return GF_OK; } @@ -575,7 +792,6 @@ case GF_LCT_OBJ_RECEPTION: return "reception"; case GF_LCT_OBJ_DONE_ERR: return "done_error"; case GF_LCT_OBJ_DONE: return "done"; - case GF_LCT_OBJ_DISPATCHED: return "dispatched"; } return "unknown"; } @@ -583,33 +799,24 @@ static void gf_route_obj_to_reservoir(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_LCTObject *obj) { + gf_assert (obj->status != GF_LCT_OBJ_RECEPTION); + //we should never drop manifests as they carry channel association info (rlct) + if (obj->rlct) { + gf_assert (obj->flute_type != GF_FLUTE_DASH_MANIFEST); + gf_assert (obj->flute_type != GF_FLUTE_HLS_VARIANT); + gf_assert (obj->flute_type != GF_FLUTE_HLS_MANIFEST); + } - assert (obj->status != GF_LCT_OBJ_RECEPTION); - - if (routedmx->on_event && obj->solved_path0) { - GF_ROUTEEventFileInfo finfo; - memset(&finfo, 0, sizeof(GF_ROUTEEventFileInfo)); - finfo.filename = obj->solved_path; + if (routedmx->on_event && (obj->solved_path0 || (obj->rlct_file && obj->rlct_file->filename))) { + GF_ROUTEEventFileInfo finfo; + memset(&finfo, 0, sizeof(GF_ROUTEEventFileInfo)); + finfo.filename = obj->solved_path0 ? obj->solved_path : obj->rlct_file->filename; finfo.udta = obj->udta; - routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_FILE_DELETE, s->service_id, &finfo); - } + routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_FILE_DELETE, s->service_id, &finfo); + } //remove other objects - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : moving object tsi %u toi %u to reservoir (status %s)\n", s->service_id, obj->tsi, obj->toi, get_lct_obj_status_name(obj->status) )); - -#ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_ROUTE, GF_LOG_DEBUG)){ - u32 i, count = gf_list_count(s->objects); - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : active objects TOIs for tsi %u: ", s->service_id, obj->tsi)); - for (i=0;i<count;i++) { - GF_LCTObject *o = gf_list_get(s->objects, i); - if (o==obj) continue; - if (o->tsi != obj->tsi) continue; - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, (" %u", o->toi)); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("\n")); - } -#endif + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Moving object tsi %u toi %u to reservoir (status %s) %s\n", s->log_name, obj->tsi, obj->toi, get_lct_obj_status_name(obj->status), obj->rlct_file ? obj->rlct_file->filename : "" )); if (s->last_active_obj==obj) s->last_active_obj = NULL; obj->closed_flag = 0; @@ -617,188 +824,1162 @@ obj->nb_bytes = 0; obj->nb_frags = GF_FALSE; obj->nb_recv_frags = 0; + obj->ll_maps_count = 0; + obj->ll_map_last = 0; + obj->flute_type = 0; + obj->rlct = NULL; + //flute rlct file, delete + if (obj->rlct_file && (obj->rlct_file->fdt_tsi || obj->rlct_file->can_remove)) { + if (obj->rlct_file->filename) gf_free(obj->rlct_file->filename); + gf_free(obj->rlct_file); + } obj->rlct_file = NULL; obj->toi = 0; - obj->tsi = 0; + obj->tsi = 0; obj->udta = NULL; - obj->solved_path0 = 0; + obj->solved_path0 = 0; obj->total_length = 0; obj->prev_start_offset = 0; - obj->download_time_ms = 0; - obj->last_gather_time = 0; + obj->download_time_ms = obj->start_time_ms = 0; + obj->last_active_time = 0; + obj->dispatched = 0; + gf_mx_p(obj->blob.mx); + obj->blob.data = NULL; + obj->blob.size = 0; + obj->blob.flags = GF_BLOB_CORRUPTED; + gf_mx_v(obj->blob.mx); obj->status = GF_LCT_OBJ_INIT; gf_list_del_item(s->objects, obj); + gf_assert(gf_list_find(routedmx->object_reservoir, obj)<0); gf_list_add(routedmx->object_reservoir, obj); +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_ROUTE, GF_LOG_DEBUG)){ + u32 i, count = gf_list_count(s->objects); + if (!count) return; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Active objects (tsi/toi) for service: ", s->log_name)); + for (i=0;i<count;i++) { + GF_LCTObject *o = gf_list_get(s->objects, i); + if (o==obj) continue; + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, (" %u/%u", o->tsi, o->toi)); + if (o->rlct_file) + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("(%s)", o->rlct_file->filename)); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("\n")); + } +#endif + } -static GF_Err gf_route_dmx_push_object(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_LCTObject *obj, Bool final_push, Bool partial, Bool updated, u64 bytes_done) +static void gf_route_lct_removed(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTELCTChannel *lc) { - char *filepath; - Bool is_init = GF_FALSE; + u32 i, count = gf_list_count(s->objects); + for (i=0; i<count;) { + GF_LCTObject *o = gf_list_get(s->objects, i); + if (o->rlct == lc) { + o->rlct = NULL; + gf_route_obj_to_reservoir(routedmx, s, o); + count--; + } else { + i++; + } + } + count = gf_list_count(s->route_sessions); + for (i=0; i<count; i++) { + GF_ROUTESession *rsess = gf_list_get(s->route_sessions, i); + if (gf_list_del_item(rsess->channels, lc)>=0) { + if (rsess->mcast_addr) { + rsess->nb_active--; + if (!rsess->nb_active) { + gf_sk_group_unregister(routedmx->active_sockets, rsess->sock); + gf_sk_del(rsess->sock); + rsess->sock = NULL; + s->secondary_sockets--; + } + } else { + s->nb_active--; + //keep service socket active + } + routedmx->nb_active--; + } + } + gf_route_static_files_del(lc->static_files); + if (lc->toi_template) gf_free(lc->toi_template); + if (lc->dash_period_id) gf_free(lc->dash_period_id); + if (lc->dash_rep_id) gf_free(lc->dash_rep_id); + gf_free(lc); +} + +static GF_Err gf_route_dmx_push_object(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_LCTObject *obj, Bool final_push) +{ + char *filepath; + GF_LCTObjectPartial partial; + Bool updated = GF_FALSE; + Bool is_init = GF_FALSE; + u64 bytes_done=0; + + if (final_push && (obj->status==GF_LCT_OBJ_DONE)) { + partial = GF_LCTO_PARTIAL_NONE; + } else if (obj->nb_frags) { + //push=begin, notified size is the start range starting at 0 + partial = GF_LCTO_PARTIAL_BEGIN; + bytes_done = obj->frags0.size; + + GF_LCTFragInfo *f = &obj->fragsobj->nb_frags-1; + //push=any, notified size is the max received size + if ((obj->nb_frags>1) || obj->frags0.offset) { + partial = GF_LCTO_PARTIAL_ANY; + bytes_done = f->offset+f->size; + } + } else { + partial = GF_LCTO_PARTIAL_BEGIN; + } - if (obj->rlct_file) { - filepath = obj->rlct_file->filename ? obj->rlct_file->filename : "ghost-init.mp4"; - is_init = GF_TRUE; - gf_assert(final_push); - } else { - if (!obj->solved_path0) { + if (obj->rlct_file) { + filepath = obj->rlct_file->filename ? obj->rlct_file->filename : "ghost-init.mp4"; + is_init = GF_TRUE; + if ((s->protocol==GF_SERVICE_DVB_FLUTE) && obj->rlct) { + is_init = GF_FALSE; + //crude hack: in flute with init segment injected inband, there is no signaling present for the int segment + //and mime type is usually the same for init and segments, so we must probe for ftyp box... + if ((obj->total_length>8) && (obj->payload4=='f') && (obj->payload5=='t') && (obj->payload6=='y') && (obj->payload7=='p')) + is_init = GF_TRUE; + } else { + gf_assert(final_push); + } + if (final_push) { + u32 crc = gf_crc_32(obj->payload, obj->total_length); + if (crc != obj->rlct_file->crc) { + obj->rlct_file->crc = crc; + updated = GF_TRUE; + } else { + updated = GF_FALSE; + } + } + } else { + if (!obj->solved_path0) { if (!obj->rlct->toi_template) { if (obj->status != GF_LCT_OBJ_RECEPTION) gf_route_obj_to_reservoir(routedmx, s, obj); return GF_OK; } - sprintf(obj->solved_path, obj->rlct->toi_template, obj->toi); + sprintf(obj->solved_path, obj->rlct->toi_template, obj->toi); } - filepath = obj->solved_path; - } + filepath = obj->solved_path; + } #ifndef GPAC_DISABLE_LOG - if (partial) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d got file %s (TSI %u TOI %u) size %d in %d ms (%d bytes in %d fragments)\n", s->service_id, filepath, obj->tsi, obj->toi, obj->total_length, obj->download_time_ms, obj->nb_bytes, obj->nb_recv_frags)); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d got file %s (TSI %u TOI %u) size %d in %d ms\n", s->service_id, filepath, obj->tsi, obj->toi, obj->total_length, obj->download_time_ms)); - } + if (final_push) { + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Got file %s (TSI %u TOI %u) size %d in %d ms%s\n", s->log_name, filepath, obj->tsi, obj->toi, obj->total_length, obj->download_time_ms, obj->status==GF_LCT_OBJ_DONE ? "" : " with errors")); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s File %s (TSI %u TOI %u) in progress - size %d in %d ms (%d bytes in %d fragments)\n", s->log_name, filepath, obj->tsi, obj->toi, obj->total_length, obj->download_time_ms, obj->nb_bytes, obj->nb_recv_frags)); + } #endif if (routedmx->on_event) { GF_ROUTEEventType evt_type; GF_ROUTEEventFileInfo finfo; - memset(&finfo, 0, sizeof(GF_ROUTEEventFileInfo)); + memset(&finfo, 0,sizeof(GF_ROUTEEventFileInfo)); + finfo.filename = filepath; + if (obj->rlct) + finfo.channel_hint = obj->rlct->channel_hint; + else if (obj->rlct_file) + finfo.channel_hint = obj->rlct_file->channel_hint; + + finfo.start_time = obj->start_time_ms; + gf_mx_p(obj->blob.mx); obj->blob.data = obj->payload; - obj->blob.flags = 0; if (final_push) { if (!obj->total_length) obj->total_length = obj->alloc_size; - if (partial) - obj->blob.flags |= GF_BLOB_CORRUPTED; obj->blob.size = (u32) obj->total_length; } else { - obj->blob.flags = GF_BLOB_IN_TRANSFER; obj->blob.size = (u32) bytes_done; } + gf_mx_v(obj->blob.mx); finfo.blob = &obj->blob; - finfo.total_size = obj->total_length; - finfo.tsi = obj->tsi; - finfo.toi = obj->toi; + if (final_push && obj->ll_map && !obj->ll_map_last) { + finfo.total_size = 0; + partial = ((obj->nb_frags==1) && !obj->frags0.offset) ? GF_LCTO_PARTIAL_BEGIN : GF_LCTO_PARTIAL_ANY; + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u missed last fragment, unknown size (%u bytes %u fragments)\n", s->log_name, obj->tsi, obj->toi, obj->total_length, obj->nb_frags)); + } else { + finfo.total_size = obj->total_length; + } + + finfo.tsi = obj->tsi; + finfo.toi = obj->toi; finfo.updated = updated; + finfo.partial = partial; finfo.udta = obj->udta; - finfo.download_ms = obj->download_time_ms; - if (is_init) - evt_type = GF_ROUTE_EVT_FILE; - else if (final_push) { - evt_type = GF_ROUTE_EVT_DYN_SEG; - finfo.nb_frags = obj->nb_frags; - finfo.frags = obj->frags; + finfo.download_ms = obj->download_time_ms; + finfo.nb_frags = obj->nb_frags; + finfo.frags = obj->frags; + if (obj->rlct) { + if (!is_init) { + finfo.first_toi_received = obj->rlct->first_seg_received; + } + finfo.dash_period_id = obj->rlct->dash_period_id; + finfo.dash_as_id = obj->rlct->dash_as_id; + finfo.dash_rep_id = obj->rlct->dash_rep_id; + } else { + finfo.dash_as_id = -1; + } + + if (final_push) { + evt_type = is_init ? GF_ROUTE_EVT_FILE : GF_ROUTE_EVT_DYN_SEG; gf_assert(obj->total_length <= obj->alloc_size); - } - else - evt_type = GF_ROUTE_EVT_DYN_SEG_FRAG; + if (obj->rlct && !is_init) { + obj->rlct->first_seg_received = GF_TRUE; + } + } + else + evt_type = GF_ROUTE_EVT_DYN_SEG_FRAG; routedmx->on_event(routedmx->udta, evt_type, s->service_id, &finfo); //store udta cookie obj->udta = finfo.udta; } else if (final_push) { //keep static files active, move other to reservoir - if (!obj->rlct_file) + if (!obj->rlct_file || obj->rlct_file->can_remove) gf_route_obj_to_reservoir(routedmx, s, obj); } - return GF_OK; + return GF_OK; +} + +#define HAS_MIMES "application/dash+xml|video/vnd.3gpp.mpd|audio/vnd.3gpp.mpd|video/vnd.mpeg.dash.mpd|audio/vnd.mpeg.dash.mpd|audio/mpegurl|video/mpegurl|application/vnd.ms-sstr+xml|application/x-mpegURL|application/vnd.apple.mpegURL" + +static GF_Err gf_route_dmx_process_dvb_flute_signaling(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_LCTObject *fdt_obj) +{ + if (fdt_obj->status==GF_LCT_OBJ_DONE_ERR) return GF_OK; + + u32 crc = gf_crc_32(fdt_obj->payload, fdt_obj->total_length); + if (fdt_obj->rlct) { + if (crc == fdt_obj->rlct->flute_fdt_crc) + return GF_OK; + fdt_obj->rlct->flute_fdt_crc = crc; + } else { + if (crc == s->flute_fdt_crc) return GF_OK; + s->flute_fdt_crc = crc; + } + + // Verifying that the payload is not erroneously treated as plaintext + if (!isprint(fdt_obj->payload0)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Package appears to be compressed but is being treated as plaintext:\n%s\n", s->log_name, fdt_obj->payload)); + } + fdt_obj->payloadfdt_obj->total_length=0; + + GF_Err e = gf_xml_dom_parse_string(routedmx->dom, fdt_obj->payload); + GF_XMLNode *root = gf_xml_dom_get_root(routedmx->dom); + if (!root || strcmp(root->name, "FDT-Instance") || e) { + // Error: Couldn't find start or end tags + if (!e) e = GF_NON_COMPLIANT_BITSTREAM; + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Unable to extract XML content from package: %s - %s\n%s\n", s->log_name, gf_error_to_string(e), gf_xml_dom_get_error(routedmx->dom), fdt_obj->payload)); + return e; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s TSI %u got FDT config package:\n%s\n", s->log_name, fdt_obj->tsi, fdt_obj->payload)); + + //mark all objects previously declared by this fdt as removable + u32 i, count=gf_list_count(s->objects); + for (i=0; i<count; i++) { + GF_LCTObject *old_o = gf_list_get(s->objects, i); + if (old_o->rlct_file && (old_o->rlct_file->fdt_tsi==fdt_obj->tsi)) { + old_o->rlct_file->can_remove = GF_TRUE; + } + } + + GF_XMLNode *fdt; + u32 fdt_idx=0; + while ( (fdt = gf_list_enum(root->content, &fdt_idx)) ) { + GF_XMLAttribute *att; + if (!fdt || strcmp(fdt->name, "File")) + continue; + + u32 toi=0; + u32 tsi = fdt_obj->tsi; + const char *content_location=NULL; + u32 transfer_length=0; + u32 content_length=0; + char *content_type=NULL; + u32 flute_symbol_size = 0; + u32 a_idx=0; + + while ( (att = gf_list_enum(fdt->attributes, &a_idx)) ) { + if (!att->name || !att->value) continue; + if (!strcmp(att->name, "Content-Location")) content_location = att->value; + else if (!strcmp(att->name, "Content-Type")) content_type = att->value; + else if (!strcmp(att->name, "Transfer-Length")) transfer_length = atoi(att->value); + else if (!strcmp(att->name, "Content-Length")) content_length = atoi(att->value); + else if (!strcmp(att->name, "FEC-OTI-Encoding-Symbol-Length")) flute_symbol_size = atoi(att->value); + else if (!strcmp(att->name, "TOI")) sscanf(att->value, "%u", &toi); + } + if (!toi) continue; + //some tools only signal content_length + if (transfer_length) content_length = transfer_length; + + GF_LCTObject *obj=NULL; + GF_ROUTELCTChannel *prev_rlct=NULL; + u32 prev_flute_type = 0; + u32 prev_flute_crc = 0; + Bool is_obj_update = GF_FALSE; + u32 i; + Bool is_manifest = GF_FALSE; + for (i=0; i<gf_list_count(s->objects); i++) { + obj = gf_list_get(s->objects, i); + if ((obj->tsi==tsi) && obj->rlct_file && !strcmp(obj->rlct_file->filename, content_location)) { + obj->toi = toi; + if (strstr(obj->rlct_file->filename, ".mpd") || strstr(obj->rlct_file->filename, ".m3u8")) + is_manifest = GF_TRUE; + break; + } + if ((obj->toi==toi) && (obj->tsi==tsi)) break; + obj=NULL; + } + + u32 flute_nb_symbols = content_length / flute_symbol_size; + if (flute_nb_symbols * flute_symbol_size < content_length) + flute_nb_symbols++; + + //found, we assume the same content if same size except for manifests + if (obj && (obj->total_length==content_length) && !is_manifest) { + if (obj->rlct_file) obj->rlct_file->can_remove = GF_FALSE; + continue; + } + if (obj && !obj->ll_maps_count) { + prev_rlct = obj->rlct; + prev_flute_type = obj->flute_type; + if (obj->rlct_file) + prev_flute_crc = obj->rlct_file->crc; + //keep manifests in list of active objects, otherwise we could loose the link to the LCT channel established in mabr config + if (!is_manifest || !obj->rlct) { + gf_list_del_item(s->objects, obj); + gf_route_obj_to_reservoir(routedmx, s, obj); + obj=NULL; + } else { + //we reuse manifest object, reset total_length to reset status below + //this will force receiving agin the manifest, and we'll check for changes using CRC + obj->total_length = 0; + } + } + if (obj && is_manifest && !obj->rlct) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Reused manifest but no associated service, something went wrong! Reloading MABR configuration\n", s->log_name)); + //force reload, this will resetup link between manifest object and LCT channel + s->dvb_mabr_cfg_crc = 0; + } + + char *frag_sep = strrchr(content_location, '#'); + char *query_sep = NULL; + u32 ll_offset = 0; + Bool ll_is_last = 0; + if (frag_sep) { + ll_offset = atoi(frag_sep+1); + query_sep = strrchr(content_location, '?'); + if (query_sep && strstr(query_sep, "isLast")) + ll_is_last = GF_TRUE; + + if (query_sep) query_sep0 = 0; + else frag_sep0 = 0; + //look for obj + for (i=0; i<gf_list_count(s->objects); i++) { + obj = gf_list_get(s->objects, i); + if ((obj->tsi==tsi) && obj->rlct_file && !strcmp(obj->rlct_file->filename, content_location)) + break; + obj = NULL; + } + + if (obj) { + //0-size content, we are done so don't add a llmap entry + if (!content_length) { + obj->ll_map_last = 1; + if (query_sep) query_sep0 = 0; + else if (frag_sep) frag_sep0 = 0; + continue; + } + + if (obj->ll_maps_alloc<=obj->ll_maps_count) { + obj->ll_maps_alloc ++; + obj->ll_map = gf_realloc(obj->ll_map, sizeof(GF_FLUTELLMapEntry)*obj->ll_maps_alloc); + } + GF_FLUTELLMapEntry *ll_map = &obj->ll_mapobj->ll_maps_count; + obj->ll_maps_count++; + if (obj->rlct_file) obj->rlct_file->can_remove = GF_FALSE; + ll_map->toi = toi; + ll_map->offset = ll_offset; + ll_map->length = content_length; + ll_map->flute_symbol_size = flute_symbol_size; + ll_map->flute_nb_symbols = flute_nb_symbols; + if (ll_is_last) obj->ll_map_last = 1; + + if (obj->total_length < ll_offset+content_length) { + gf_mx_p(routedmx->blob_mx); + obj->total_length = ll_offset+content_length; + obj->blob.size = obj->total_length; + if (obj->total_length > obj->alloc_size) { + obj->payload = gf_realloc(obj->payload, obj->total_length+1); + obj->alloc_size = obj->total_length; + obj->blob.data = obj->payload; + } + gf_mx_v(routedmx->blob_mx); + } + if (query_sep) query_sep0 = 0; + else if (frag_sep) frag_sep0 = 0; + continue; + } + } + + if (obj) + is_obj_update = GF_TRUE; + + //gathering for flute LL, try to find a preallocated obj with an allocated ll map + if (!obj && frag_sep) { + for (i=0; i<gf_list_count(routedmx->object_reservoir); i++) { + obj = gf_list_get(routedmx->object_reservoir, i); + if (obj->ll_maps_alloc) { + gf_list_rem(routedmx->object_reservoir, i); + break; + } + obj = NULL; + } + } + + if (!obj) + obj = gf_list_pop_back(routedmx->object_reservoir); + + if (!obj) { + GF_SAFEALLOC(obj, GF_LCTObject); + if (!obj) { + if (query_sep) query_sep0 = 0; + else if (frag_sep) frag_sep0 = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate LCT object TSI %u TOI %u\n", s->log_name, toi, tsi )); + return GF_OUT_OF_MEM; + } + obj->nb_alloc_frags = 10; + obj->frags = gf_malloc(sizeof(GF_LCTFragInfo)*obj->nb_alloc_frags); + obj->blob.mx = routedmx->blob_mx; + } + obj->toi = toi; + obj->tsi = tsi; + obj->blob.flags = 0; + obj->blob.range_valid = NULL; + obj->blob.range_udta = obj; + obj->last_active_time = gf_sys_clock_high_res(); + + content_length += ll_offset; + + obj->flute_symbol_size = flute_symbol_size; + obj->flute_nb_symbols = flute_nb_symbols; + + obj->rlct = fdt_obj->rlct; + obj->flute_type = GF_FLUTE_OBJ; + if (content_type) { + if (!strcmp(content_type, "application/xml+dvb-mabr-session-configuration")) { + obj->flute_type = GF_FLUTE_DVB_MABR_CFG; + } else if (strstr(HAS_MIMES, content_type)) { + if (prev_flute_type) + obj->flute_type = prev_flute_type; + else if (strstr(content_type, "mpd") || strstr(content_type, "dash")) + obj->flute_type = GF_FLUTE_DASH_MANIFEST; + else + obj->flute_type = GF_FLUTE_HLS_MANIFEST; + + if (prev_rlct) + obj->rlct = prev_rlct; + + //always keep manifests + if (obj->rlct_file) { + obj->rlct_file->can_remove = GF_FALSE; + } + } + else if (!strcmp(content_type, "application/octet-stream")) { + obj->flute_type = GF_FLUTE_PROBE_TYPE; + } + } + + if (obj->total_length!=content_length) { + obj->status = GF_LCT_OBJ_INIT; + obj->total_length = content_length; + obj->nb_frags = obj->nb_recv_frags = 0; + obj->nb_bytes = obj->nb_recv_bytes = 0; + + if (obj->alloc_size < content_length) { + gf_mx_p(routedmx->blob_mx); + obj->payload = gf_realloc(obj->payload, obj->total_length+1); + obj->alloc_size = obj->total_length; + obj->blob.size = obj->total_length; + obj->blob.data = obj->payload; + gf_mx_v(routedmx->blob_mx); + } + obj->payloadobj->total_length = 0; + } + if (!obj->rlct_file) { + GF_SAFEALLOC(obj->rlct_file, GF_ROUTELCTFile); + obj->rlct_file->filename = gf_strdup(content_location); + obj->rlct_file->toi = toi; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Added object %s TSI %u TOI %u size %u (is update %d)\n", s->log_name, content_location, tsi, toi, obj->total_length, is_obj_update)); + obj->rlct_file->fdt_tsi = fdt_obj->tsi; + obj->rlct_file->crc = prev_flute_crc; + + //if last frag and no offset, obj is a single chunk, otherwise build map + if (frag_sep && (!ll_is_last || ll_offset)) { + if (!obj->ll_maps_alloc) { + obj->ll_maps_alloc = 10; + obj->ll_map = gf_malloc(sizeof(GF_FLUTELLMapEntry)*obj->ll_maps_alloc); + } + obj->ll_maps_count = 1; + GF_FLUTELLMapEntry *ll_map = &obj->ll_map0; + memset(ll_map, 0, sizeof(GF_FLUTELLMapEntry)); + ll_map->toi = toi; + ll_map->offset = ll_offset; + ll_map->length = content_length; + ll_map->flute_symbol_size = flute_symbol_size; + ll_map->flute_nb_symbols = flute_nb_symbols; + obj->flute_symbol_size = 0; + obj->flute_nb_symbols = 0; + } + } + if (query_sep) query_sep0 = 0; + else if (frag_sep) frag_sep0 = 0; + + if (!is_obj_update) { + gf_assert(gf_list_find(s->objects, obj)<0); + obj->start_time_ms = gf_sys_clock(); + gf_list_add(s->objects, obj); + } + } + return GF_OK; +} + +static GF_Err gf_route_service_setup_dash(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, char *content, char *content_location, u32 file_type); + +static const char *_xml_get_attr(const GF_XMLNode *n, const char *att_name) +{ + u32 i=0; + GF_XMLAttribute *att; + if (!n) return NULL; + while ((att = gf_list_enum(n->attributes, &i))) { + if (att->name && !strcmp(att->name, att_name)) return att->value; + } + return NULL; +} + +static const GF_XMLNode *_xml_get_child(const GF_XMLNode *n, const char *child_name) +{ + u32 i=0; + GF_XMLNode *c; + if (!n) return NULL; + while ((c = gf_list_enum(n->content, &i))) { + if (!c->type && !strcmp(c->name, child_name)) return c; + } + return NULL; +} +static const char *_xml_get_child_text(const GF_XMLNode *n, const char *child_name) +{ + u32 i=0; + GF_XMLNode *c; + if (!n) return NULL; + while ((c = gf_list_enum(n->content, &i))) { + if (c->type==GF_XML_TEXT_TYPE && !child_name) return c->name; + if (!c->type && child_name && !strcmp(c->name, child_name)) { + c = gf_list_get(c->content, 0); + return c->name; + } + } + return NULL; +} +static u32 _xml_get_child_count(const GF_XMLNode *n, const char *child_name) +{ + u32 i=0, nb_children=0; + GF_XMLNode *c; + if (!n) return 0; + while ((c = gf_list_enum(n->content, &i))) { + if (!c->type && !strcmp(c->name, child_name)) nb_children++; + } + return nb_children; +} + + +static GF_Err gf_route_dmx_process_dvb_mcast_signaling(GF_ROUTEDmx *routedmx, GF_ROUTEService *parent_s, GF_LCTObject *object) +{ + if (object->status==GF_LCT_OBJ_DONE_ERR) return GF_OK; + u32 crc = gf_crc_32(object->payload, object->total_length); + if (crc == parent_s->dvb_mabr_cfg_crc) return GF_OK; + parent_s->dvb_mabr_cfg_crc = crc; + + + // Verifying that the payload is not erroneously treated as plaintext + if (!isprint(object->payload0)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Package appears to be compressed but is being treated as plaintext:\n%s\n", + parent_s->log_name, object->payload)); + } + object->payloadobject->total_length=0; + + GF_Err e = gf_xml_dom_parse_string(routedmx->dom, object->payload); + GF_XMLNode *root = gf_xml_dom_get_root(routedmx->dom); + if (!root || strcmp(root->name, "MulticastGatewayConfiguration") || e) { + // Error: Couldn't find start or end tags + if (!e) e = GF_NON_COMPLIANT_BITSTREAM; + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Unable to extract XML content from package: %s - %s\n%s\n", parent_s->log_name, gf_error_to_string(e), gf_xml_dom_get_error(routedmx->dom), object->payload)); + return e; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Got TSI %d MulticastGateway configuration:\n%s\n", parent_s->log_name, object->tsi, object->payload)); + GF_List *old_services = gf_list_clone(routedmx->services); + gf_list_del_item(old_services, parent_s); + + GF_XMLNode *mcast_sess; + u32 s_idx=0; + Bool has_stsid_session=GF_FALSE; + while ( (mcast_sess = gf_list_enum(root->content, &s_idx)) ) { + Bool is_cfg_session=GF_FALSE; + if (!mcast_sess || !mcast_sess->name) continue; + if (!strcmp(mcast_sess->name, "MulticastGatewayConfigurationTransportSession")) { + is_cfg_session = GF_TRUE; + } else if (strcmp(mcast_sess->name, "MulticastSession")) + continue; + + GF_ROUTEService *new_service = NULL; + GF_List *old_sessions = NULL; + GF_List *old_channels = NULL; + GF_LCTObject *mani_obj = NULL; + GF_XMLNode *tr_sess; + GF_ServiceProtocolType proto_id = 0; + const char *service_id_uri = _xml_get_attr(mcast_sess, "serviceIdentifier"); + if (!service_id_uri) service_id_uri = "unknown"; + u32 trs_idx=0; + while ( (tr_sess = gf_list_enum(mcast_sess->content, &trs_idx)) ) { + u32 j; + if (!tr_sess || !tr_sess->name) continue; + if (!strcmp(tr_sess->name, "PresentationManifestLocator") && !is_cfg_session) { + const char *trp_obj_uri = _xml_get_attr(tr_sess, "transportObjectURI"); + tr_sess = gf_list_get(tr_sess->content, 0); + const char *mani_url = tr_sess ? tr_sess->name : NULL; + if (!mani_url && !trp_obj_uri) continue; + + u32 i, count=gf_list_count(parent_s->objects); + for (i=0;i<count; i++) { + GF_LCTObject *obj = gf_list_get(parent_s->objects, i); + if (!obj->rlct_file) continue; + if ( + //use URI indicated in transportObjectURI + (trp_obj_uri && !strcmp(obj->rlct_file->filename, trp_obj_uri)) + //otherwise try to match using content type (repair url) - cf #3030 and DVB MABR A176 section 10.2.2.2. + || (tr_sess && !strcmp(obj->rlct_file->filename, tr_sess->name)) + ) { + mani_obj=obj; + break; + } + } + continue; + } + else if (is_cfg_session && !strcmp(tr_sess->name, "TransportProtocol")) { + tr_sess = mcast_sess; + } else if (strcmp(tr_sess->name, "MulticastTransportSession")) { + continue; + } + + const GF_XMLNode *trp = _xml_get_child(tr_sess, "TransportProtocol"); + const char *mode = _xml_get_attr(trp, "protocolIdentifier"); + if (mode && !strcmp(mode, "urn:dvb:metadata:cs:MulticastTransportProtocolCS:2019:FLUTE")) { + proto_id = GF_SERVICE_DVB_FLUTE; + } else if (mode && !strcmp(mode, "urn:dvb:metadata:cs:MulticastTransportProtocolCS:2019:ROUTE")) { + proto_id = GF_SERVICE_ROUTE; + //spec is really bad here, there is no way to match a ROUTE config session with the declared channels in the MulticastConfig + //we assume that if the config session is present, setup will be done using stsid + if (!is_cfg_session && has_stsid_session) continue; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Unrecognized protocol type %s, ignoring\n", parent_s->log_name, mode ? mode : "UNSPECIFIED")); + continue; + } + trp = _xml_get_child(tr_sess, "EndpointAddress"); + if (!trp) continue; + + const char *dst_add = _xml_get_child_text(trp, "NetworkDestinationGroupAddress"); + const char *dst_port_str = _xml_get_child_text(trp, "TransportDestinationPort"); + const char *_dst_tsi = _xml_get_child_text(trp, "MediaTransportSessionIdentifier"); + //dst_tsi can be NULL for some FLUTE config, in which case we must probe incoming TSIs + if (!dst_add || !dst_port_str) continue; + u16 dst_port = atoi(dst_port_str); + u32 dst_tsi = 0; + if (_dst_tsi) sscanf(_dst_tsi, "%u", &dst_tsi); + + if (!new_service) { + //config session same as our bootstrap address, do not process + if (!strcmp(dst_add, parent_s->dst_ip) && (parent_s->port == dst_port)) { + gf_list_del_item(old_services, parent_s); + if (is_cfg_session) continue; + new_service = parent_s; + old_sessions = gf_list_clone(new_service->route_sessions); + } else { + GF_ROUTEService *existing = NULL; + for (j=0; j<gf_list_count(routedmx->services); j++) { + existing = gf_list_get(routedmx->services, j); + if (existing->service_identifier + && !strcmp(existing->service_identifier, service_id_uri) + && (existing->port==dst_port) + && !strcmp(existing->dst_ip, dst_add) + ) + break; + existing=NULL; + } + if (!existing) { + u32 service_id = gf_list_count(routedmx->services); + new_service = gf_route_create_service(routedmx, dst_add, dst_port, service_id, proto_id); + if (!new_service) continue; + new_service->service_identifier = gf_strdup(service_id_uri); + } else { + gf_list_del_item(old_services, existing); + new_service = existing; + old_sessions = gf_list_clone(new_service->route_sessions); + old_channels = gf_list_new(); + for (j=0; j<gf_list_count(new_service->route_sessions); j++) { + u32 k; + GF_ROUTESession *s = gf_list_get(new_service->route_sessions, j); + for (k=0; k<gf_list_count(s->channels); k++) { + GF_ROUTELCTChannel *ch = gf_list_get(s->channels, k); + gf_list_add(old_channels, ch); + } + } + } + } + } + if (proto_id==GF_SERVICE_ROUTE) { + //setup done through stsid + has_stsid_session = GF_TRUE; + break; + } + trp = _xml_get_child(tr_sess, "UnicastRepairParameters"); + if (trp) { + const char *uriBase = _xml_get_attr(trp, "transportObjectBaseURI"); + if (new_service->repair_uri_base) gf_free(new_service->repair_uri_base); + new_service->repair_uri_base = uriBase ? gf_strdup(uriBase) : NULL; + u32 b_idx=0; + const char *b_url=NULL; + u32 b_weight=0; + GF_XMLNode *burl; + while ( (burl = gf_list_enum(trp->content, &b_idx)) ) { + if (burl->type != GF_XML_NODE_TYPE) continue; + if (strcmp(burl->name, "BaseURL")) continue; + const char *weight_s = _xml_get_attr(burl, "relativeWeight"); + if (!weight_s) weight_s = "1"; + u32 weight = atoi(weight_s); + if (!weight) continue; + if (!b_weight || (weight>b_weight)) { + b_weight = weight; + b_url = _xml_get_child_text(burl, NULL); + } + } + if (new_service->repair_server) gf_free(new_service->repair_server); + new_service->repair_server = b_url ? gf_strdup(b_url) : NULL; + } + + GF_ROUTESession *rsess=NULL; + for (j=0; j<gf_list_count(new_service->route_sessions); j++) { + rsess = gf_list_get(new_service->route_sessions, j); + if (rsess->mcast_addr && !strcmp(rsess->mcast_addr, dst_add) && (rsess->mcast_port==dst_port)) + break; + if (!rsess->mcast_addr && !strcmp(new_service->dst_ip, dst_add) && (new_service->port==dst_port)) + break; + rsess = NULL; + } + GF_ROUTELCTChannel *rlct=NULL; + if (rsess) { + gf_list_del_item(old_sessions, rsess); + for (j=0; j<gf_list_count(rsess->channels); j++) { + rlct = gf_list_get(rsess->channels, j); + if (dst_tsi && (rlct->tsi == dst_tsi)) break; + if (!dst_tsi && rlct->tsi_probe) break; + rlct = NULL; + } + if (rlct) gf_list_del_item(old_channels, rlct); + } else { + GF_SAFEALLOC(rsess, GF_ROUTESession); + if (!rsess) continue; + rsess->channels = gf_list_new(); + + //need a new socket for the session + if ((strcmp(new_service->dst_ip, dst_add)) || (new_service->port != dst_port) ) { + + rsess->mcast_addr = gf_strdup(dst_add); + rsess->mcast_port = dst_port; + + if (!routedmx->start_inactive) { + rsess->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); + if (gf_sk_has_nrt_netcap(rsess->sock)) + routedmx->nrt_max_seg = MAX_SEG_IN_NRT; + + gf_sk_set_usec_wait(rsess->sock, 1); + + e = routedmx_setup_socket(routedmx, new_service->log_name, rsess->sock, dst_add, dst_port); + if (e) { + gf_list_del(rsess->channels); + if (rsess->mcast_addr) gf_free(rsess->mcast_addr); + gf_free(rsess); + gf_list_del(old_sessions); + goto exit; + } + gf_sk_set_buffer_size(rsess->sock, GF_FALSE, routedmx->unz_buffer_size); + new_service->secondary_sockets++; + if (new_service->tune_mode == GF_ROUTE_TUNE_ON) + gf_sk_group_register(routedmx->active_sockets, rsess->sock); + } + } + gf_list_add(new_service->route_sessions, rsess); + } + + if (!rlct) { + //OK setup LCT channel for route + GF_SAFEALLOC(rlct, GF_ROUTELCTChannel); + if (!rlct) { + continue; + } + gf_list_add(rsess->channels, rlct); + rlct->is_active = routedmx->start_inactive ? GF_FALSE : GF_TRUE; + if (rlct->is_active) { + if (rsess->mcast_addr) + rsess->nb_active ++; + else + new_service->nb_active ++; + routedmx->nb_active++; + } + + if (new_service->protocol==GF_SERVICE_ROUTE) + rlct->static_files = gf_list_new(); + } + + new_service->nb_media_streams -= rlct->num_components; + rlct->num_components = _xml_get_child_count(tr_sess, "ServiceComponentIdentifier"); + new_service->nb_media_streams += rlct->num_components; + + rlct->flute_parent_service = new_service; + if (dst_tsi) { + rlct->tsi = dst_tsi; + rlct->tsi_probe = GF_FALSE; + } else { + rlct->tsi = 0; + rlct->tsi_probe = GF_TRUE; + } + rlct->toi_template = NULL; + GF_ROUTELCTReg *lreg = &rlct->CPs0; + rlct->nb_cps=1; + lreg->order = 1; //default + lreg->codepoint = 0; + lreg->format_id = 1; + + //associate manifest object with first channel we use + if (mani_obj && !mani_obj->rlct) { + mani_obj->rlct = rlct; + if ((mani_obj->status>=GF_LCT_OBJ_RECEPTION) && (new_service->tune_mode==GF_ROUTE_TUNE_ON)) + gf_route_service_setup_dash(routedmx, new_service, mani_obj->payload, mani_obj->rlct_file->filename, mani_obj->flute_type); + } + + trp = _xml_get_child(tr_sess, "ServiceComponentIdentifier"); + const char *trp_attr = _xml_get_attr(trp, "mediaPlaylistLocator"); + if (trp_attr) { + if (rlct->dash_rep_id) gf_free(rlct->dash_rep_id); + rlct->dash_rep_id = gf_strdup(trp_attr); + u32 i, count=gf_list_count(parent_s->objects); + for (i=0;i<count; i++) { + GF_LCTObject *obj = gf_list_get(parent_s->objects, i); + Bool pl_match = GF_FALSE; + if (obj->rlct_file && !strcmp(obj->rlct_file->filename, trp_attr)) { + pl_match = GF_TRUE; + } else { + char *pl = obj->rlct_file ? strstr(obj->rlct_file->filename, trp_attr) : NULL; + if (pl && ((pl==obj->rlct_file->filename) || (pl-1=='/')) ) + pl_match = GF_TRUE; + } + if (pl_match) { + obj->rlct = rlct; + obj->flute_type = GF_FLUTE_HLS_VARIANT; + break; + } + } + } else { + if (rlct->dash_period_id) gf_free(rlct->dash_period_id); + trp_attr = _xml_get_attr(trp, "periodIdentifier"); + rlct->dash_period_id = trp_attr ? gf_strdup(trp_attr) : NULL; + + trp_attr = _xml_get_attr(trp, "adaptationSetIdentifier"); + rlct->dash_as_id = trp_attr ? atoi(trp_attr) : 1; + + if (rlct->dash_rep_id) gf_free(rlct->dash_rep_id); + trp_attr = _xml_get_attr(trp, "representationIdentifier"); + rlct->dash_rep_id = trp_attr ? gf_strdup(trp_attr) : NULL; + } + } + //purge old LCT channels + while (gf_list_count(old_channels)) { + GF_ROUTELCTChannel *lc = gf_list_pop_back(old_channels); + gf_route_lct_removed(routedmx, new_service, lc); + } + gf_list_del(old_channels); + + //purge old LCT sessions + while (gf_list_count(old_sessions)) { + GF_ROUTESession *rsess = gf_list_pop_back(old_sessions); + gf_route_route_session_del(routedmx, rsess); + } + gf_list_del(old_sessions); + + if (new_service && (new_service->tune_mode != GF_ROUTE_TUNE_ON)) + gf_route_register_service_sockets(routedmx, new_service, GF_FALSE); + } + +exit: + //purge old services sessions + while (gf_list_count(old_services)) { + GF_ROUTEService *serv = gf_list_pop_back(old_services); + gf_route_service_del(routedmx, serv); + } + gf_list_del(old_services); + return e; + } static GF_Err gf_route_dmx_process_object(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_LCTObject *obj) { - Bool partial = GF_FALSE; - Bool updated = GF_TRUE; + u32 crc; + Bool check_main = GF_FALSE; + if (obj->flute_type==GF_FLUTE_PROBE_TYPE) { + obj->payloadobj->total_length = 0; + if (strstr(obj->payload, "MulticastGatewayConfiguration")) + obj->flute_type = GF_FLUTE_DVB_MABR_CFG; + else if (strstr(obj->payload, "FDT-Instance")) + obj->flute_type = GF_FLUTE_FDT; + else if (strstr(obj->payload, "<MPD")) + obj->flute_type = GF_FLUTE_DASH_MANIFEST; + else if (strstr(obj->payload, "#EXT-X-STREAM-IN")) + obj->flute_type = GF_FLUTE_HLS_MANIFEST; + else if (strstr(obj->payload, "#EXT-X-MEDIA-SEQUENCE")) + obj->flute_type = GF_FLUTE_HLS_VARIANT; + } + switch (obj->flute_type) { + // if this flute and its an FDT packet parse the fdt and associate the object with the TOI + case GF_FLUTE_FDT: + if (obj->status<GF_LCT_OBJ_RECEPTION) + return GF_SERVICE_ERROR; + GF_Err e = gf_route_dmx_process_dvb_flute_signaling(routedmx, s, obj); + //FDTs are always pushed to reservoir since they may have different content with same TOI + if (obj->rlct) obj->rlct->last_dispatched_toi = obj->rlct->last_dispatched_tsi = 0; + gf_route_obj_to_reservoir(routedmx, s, obj); + return e; + case GF_FLUTE_DVB_MABR_CFG: + if (obj->status<GF_LCT_OBJ_RECEPTION) + return GF_SERVICE_ERROR; + return gf_route_dmx_process_dvb_mcast_signaling(routedmx, s, obj); + case GF_FLUTE_DASH_MANIFEST: + case GF_FLUTE_HLS_MANIFEST: + check_main = GF_TRUE; + case GF_FLUTE_HLS_VARIANT: + if (!obj->rlct || !obj->rlct->flute_parent_service) + return GF_OK; + if (obj->rlct->flute_parent_service->tune_mode != GF_ROUTE_TUNE_ON) + return GF_OK; + crc = gf_crc_32(obj->payload, obj->total_length); + if (check_main) { + //for flute injecting inband manifest in each rep, only forward once + if (crc == s->manifest_crc) { + //do NOT move to reservoir, we could loose the link to rlct established in MABR configuration + //we just keep the object as done and active + return GF_OK; + } + s->manifest_crc = crc; + } + if (crc != obj->rlct_file->crc) { + obj->rlct_file->crc = crc; + return gf_route_service_setup_dash(routedmx, obj->rlct->flute_parent_service, obj->payload, obj->rlct_file->filename, obj->flute_type); + } + return GF_OK; + default: + break; + } - if (!obj->rlct) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : internal error, no LCT ROUTE channel defined for object TSI %u TOI %u\n", s->service_id, obj->tsi, obj->toi)); + if (!obj->flute_type && !obj->rlct) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Internal error, no LCT ROUTE channel defined for object TSI %u TOI %u\n", s->log_name, obj->tsi, obj->toi)); return GF_SERVICE_ERROR; } if (obj->status<GF_LCT_OBJ_RECEPTION) return GF_SERVICE_ERROR; if (obj->status==GF_LCT_OBJ_DONE_ERR) { - if (obj->rlct->tsi_init) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d : object TSI %u TOI %u partial received only\n", s->service_id, obj->tsi, obj->toi)); + if (obj->rlct && obj->rlct->tsi_init) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u partial received only\n", s->log_name, obj->tsi, obj->toi)); } - partial = GF_TRUE; } - obj->rlct->tsi_init = GF_TRUE; - if (obj->status == GF_LCT_OBJ_DISPATCHED) return GF_OK; - obj->status = GF_LCT_OBJ_DISPATCHED; + if (obj->rlct) + obj->rlct->tsi_init = GF_TRUE; - if (obj->rlct_file) { - u32 crc = gf_crc_32(obj->payload, obj->total_length); - if (crc != obj->rlct_file->crc) { - obj->rlct_file->crc = crc; - updated = GF_TRUE; - } else { - updated = GF_FALSE; - } - } - return gf_route_dmx_push_object(routedmx, s, obj, GF_TRUE, partial, updated, 0); + if (obj->dispatched) return GF_OK; + obj->dispatched = 1; + + return gf_route_dmx_push_object(routedmx, s, obj, GF_TRUE); } static GF_Err gf_route_service_flush_object(GF_ROUTEService *s, GF_LCTObject *obj) { u32 i; u64 start_offset = 0; - obj->status = GF_LCT_OBJ_DONE; + obj->download_time_ms = gf_sys_clock() - obj->start_time_ms; for (i=0; i<obj->nb_frags; i++) { if (start_offset != obj->fragsi.offset) { obj->status = GF_LCT_OBJ_DONE_ERR; - break; + return GF_EOS; } start_offset += obj->fragsi.size; } if (start_offset != obj->total_length) { obj->status = GF_LCT_OBJ_DONE_ERR; + return GF_EOS; } - obj->download_time_ms = gf_sys_clock() - obj->download_time_ms; + obj->status = GF_LCT_OBJ_DONE; return GF_EOS; } -static GF_Err gf_route_service_gather_object(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, u32 tsi, u32 toi, u32 start_offset, char *data, u32 size, u32 total_len, Bool close_flag, Bool in_order, GF_ROUTELCTChannel *rlct, GF_LCTObject **gather_obj) +static void gf_route_service_purge_old_objects(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, u32 tsi, u32 toi, Bool in_order, GF_LCTObject *in_obj) +{ + if (!tsi && in_order) return; + + u32 i, count = gf_list_count(s->objects); + for (i=0; i<count; i++) { + u32 new_count; + GF_LCTObject *o = gf_list_get(s->objects, i); + //we can only detect losses if a new TOI on the same TSI is found + if (tsi && (o->tsi != tsi)) continue; + if (in_obj && (in_obj==o)) break; + + if (o->status>=GF_LCT_OBJ_DONE_ERR) continue; + + //FDT repeat in middle of object, keep alive + if (tsi && !toi && (s->protocol==GF_SERVICE_DVB_FLUTE) ) { + continue; + } + //commented out since we could have no bytes on object received (heavy losses) - we need to cleanup +#if 0 + //object pushed by flute FDT with no bytes received or not last frag, keep alive + else if (o->rlct_file && !o->rlct_file->can_remove + && (!o->nb_bytes || (o->ll_maps_count && !o->ll_map_last)) + ) { + continue; + } +#endif + //packets not in order and timeout used + else if (!in_order && routedmx->reorder_timeout_us) { + if (o->last_active_time) { + u64 elapsed = gf_sys_clock_high_res() - o->last_active_time; + if (elapsed < routedmx->reorder_timeout_us) + continue; + + //only warn for non-config and non manifest files + if (!o->flute_type || (o->flute_type>=GF_FLUTE_OBJ)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u timeout after %d us - forcing dispatch\n", s->log_name, o->tsi, o->toi, elapsed )); + } + } + } else if (tsi && o->rlct && !o->rlct->tsi_init) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u incomplete (tune-in) - forcing dispatch\n", s->log_name, o->tsi, o->toi)); + } + //do not warn if we received a last frag in seg - todo try to flush earlier + else if (!o->ll_map_last && toi) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s object TSI %u TOI %u not completely received but in-order delivery signaled and new TOI %u - forcing dispatch\n", s->log_name, o->tsi, o->toi, toi )); + } + + gf_route_service_flush_object(s, o); + if (o->tsi && o->nb_frags) { + gf_route_dmx_process_object(routedmx, s, o); + } else if (!o->rlct_file || o->rlct_file->can_remove) { + gf_route_obj_to_reservoir(routedmx, s, o); + } + new_count = gf_list_count(s->objects); + //objects purged + if (new_count<count) { + i=-1; + count = new_count; + } + } +} + +static GF_Err gf_route_service_gather_object(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, u32 tsi, u32 toi, u32 start_offset, char *data, u32 size, u32 total_len, Bool close_flag, Bool in_order, GF_ROUTELCTChannel *rlct, GF_LCTObject **gather_obj, s32 flute_esi, u32 fdt_symbol_length) { Bool done; - u32 i, count; - Bool do_push = GF_FALSE; + u32 i, j, count; + Bool do_push = GF_FALSE; GF_LCTObject *obj = s->last_active_obj; + GF_FLUTELLMapEntry *ll_map = NULL; - if (routedmx->force_reorder) + //not on a broadcast channel, ignore in_order flag + if (!routedmx->force_in_order) in_order = GF_FALSE; - //in case last packet(s) are duplicated after we sent the object, skip them - if (rlct) { - if ((tsi==rlct->last_dispatched_tsi) && (toi==rlct->last_dispatched_toi)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d TSI %u TOI %u LCT fragment on already dispatched object, skipping\n", s->service_id, tsi, toi)); - return GF_OK; + if (fdt_symbol_length) { + s32 fdt_symbols = total_len / fdt_symbol_length; + if (fdt_symbols * fdt_symbol_length < total_len) + fdt_symbols++; + + if (fdt_symbols <= flute_esi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s TSI %u TOI %u invalid ESI %u for block size %u and content size %u\n", s->log_name, tsi, toi, fdt_symbols, fdt_symbol_length, total_len)); + return GF_NOT_SUPPORTED; } - } else { - if (!tsi && (toi==s->last_dispatched_toi_on_tsi_zero)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d TSI %u TOI %u LCT fragment on already dispatched object, skipping\n", s->service_id, tsi, toi)); - return GF_OK; + start_offset = flute_esi * fdt_symbol_length; + } + + if (rlct && !rlct->is_active) { + return GF_OK; + } + + if((rlct && (tsi==rlct->last_dispatched_tsi) && (toi==rlct->last_dispatched_toi)) + || (!fdt_symbol_length && !rlct && !tsi && (toi==s->last_dispatched_toi_on_tsi_zero)) + ) { + if (routedmx->on_event) { + // Sending event about the delayed data received. + GF_ROUTEEventFileInfo finfo; + GF_Blob blob; + memset(&finfo, 0, sizeof(GF_ROUTEEventFileInfo)); + memset(&blob, 0, sizeof(GF_Blob)); + blob.data = data; + blob.size = size; + finfo.blob = &blob; + finfo.total_size = size; + finfo.tsi = tsi; + finfo.toi = toi; + finfo.late_fragment_offset = start_offset; + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u received delayed data %u, %u - event sent\n", s->log_name, tsi, toi, start_offset, start_offset+size-1)); + routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_LATE_DATA, s->service_id, &finfo); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u received delayed data %u, %u - ignoring\n", s->log_name, tsi, toi, start_offset, start_offset+size-1)); } + return GF_OK; } if((u64)start_offset + size > GF_UINT_MAX) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d TSI %u TOI %u Not supported: Offset (%u) + Size (%u) exceeds the maximum supported value (%u), skipping\n", s->service_id, tsi, toi, start_offset, size, GF_UINT_MAX)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s TSI %u TOI %u Not supported: Offset (%u) + Size (%u) exceeds the maximum supported value (%u), skipping\n", s->log_name, tsi, toi, start_offset, size, GF_UINT_MAX)); + + gf_route_service_purge_old_objects(routedmx, s, tsi, toi, in_order, NULL); return GF_NOT_SUPPORTED; } - if(total_len && (start_offset + size > total_len)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d TSI %u TOI %u Corrupted data: Offset (%u) + Size (%u) exceeds Total Size of the object (%u), skipping\n", s->service_id, tsi, toi, start_offset, size, total_len)); + if (total_len && (start_offset + size > total_len)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s TSI %u TOI %u Corrupted data: Offset (%u) + Size (%u) exceeds Total Size of the object (%u), skipping\n", s->log_name, tsi, toi, start_offset, size, total_len)); + + gf_route_service_purge_old_objects(routedmx, s, tsi, toi, in_order, NULL); return GF_NOT_SUPPORTED; } - if (!obj || (obj->tsi!=tsi) || (obj->toi!=toi)) { + if (!obj || (obj->tsi!=tsi) || (obj->toi!=toi) || obj->ll_maps_count) { count = gf_list_count(s->objects); for (i=0; i<count; i++) { obj = gf_list_get(s->objects, i); - if ((obj->toi == toi) && (obj->tsi==tsi)) break; - if (!tsi && !obj->tsi && ((obj->toi&0xFFFFFF00) == (toi&0xFFFFFF00)) ) { + if (obj->ll_maps_count && (obj->tsi==tsi)) { + for (j=0;j<obj->ll_maps_count;j++) { + if (obj->ll_mapj.toi == toi) { + ll_map = &obj->ll_mapj; + break; + } + } + if (ll_map) break; + } + if ((obj->toi == toi) && (obj->tsi==tsi)) break; + //for route STSID only + if (!tsi && !obj->flute_type && !obj->tsi && ((obj->toi&0xFFFFFF00) == (toi&0xFFFFFF00)) ) { //change in version of bundle but same other flags: reuse this one obj->nb_frags = obj->nb_recv_frags = 0; obj->nb_bytes = obj->nb_recv_bytes = 0; @@ -812,36 +1993,75 @@ gf_mx_v(routedmx->blob_mx); } obj->toi = toi; + obj->rlct = rlct; obj->status = GF_LCT_OBJ_INIT; break; } obj = NULL; } } + if ((s->protocol==GF_SERVICE_DVB_FLUTE) && !fdt_symbol_length) { + if (!obj) { + if (size) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s TSI %u TOI %u unknown, skipping\n", s->log_name, tsi, toi)); + } + return GF_NOT_FOUND; + } + s32 flute_nb_symbols = ll_map ? ll_map->flute_nb_symbols : obj->flute_nb_symbols; + u32 flute_symbol_size = ll_map ? ll_map->flute_symbol_size : obj->flute_symbol_size; + if (flute_nb_symbols) { + if (flute_nb_symbols <= flute_esi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s TSI %u TOI %u invalid ESI %u for number of symbols %u (content size %u)\n", s->log_name, tsi, toi, flute_esi, flute_nb_symbols, obj->total_length)); + return GF_NOT_SUPPORTED; + } + } + //consider 0-length objects as closed + else if (ll_map && obj->ll_map_last) { + close_flag = GF_TRUE; + } + //recompute start offset + start_offset = flute_esi * flute_symbol_size; + if (ll_map) start_offset += ll_map->offset; + + total_len = obj->total_length; + } + + if ((total_len>routedmx->max_obj_size) || (start_offset>routedmx->max_obj_size)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u too big - size %u but max allowed size %u\n", s->log_name, toi, tsi, total_len>routedmx->max_obj_size ? total_len : start_offset, routedmx->max_obj_size)); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (!obj) { obj = gf_list_pop_back(routedmx->object_reservoir); if (!obj) { GF_SAFEALLOC(obj, GF_LCTObject); if (!obj) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to allocate LCT object TSI %u TOI %u\n", s->service_id, toi, tsi )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to allocate LCT object TSI %u TOI %u\n", s->log_name, toi, tsi )); return GF_OUT_OF_MEM; } obj->nb_alloc_frags = 10; obj->frags = gf_malloc(sizeof(GF_LCTFragInfo)*obj->nb_alloc_frags); - obj->blob.mx = routedmx->blob_mx; + obj->blob.mx = routedmx->blob_mx; } obj->toi = toi; obj->tsi = tsi; + obj->blob.range_valid = NULL; + obj->blob.range_udta = obj; obj->status = GF_LCT_OBJ_INIT; obj->total_length = total_len; + obj->last_active_time = gf_sys_clock_high_res(); + if (fdt_symbol_length) obj->flute_type = GF_FLUTE_FDT; + if (obj->alloc_size < total_len) { - gf_mx_p(routedmx->blob_mx); - obj->payload = gf_realloc(obj->payload, total_len+1); - obj->alloc_size = total_len; - obj->blob.size = total_len; - obj->blob.data = obj->payload; - gf_mx_v(routedmx->blob_mx); - } + gf_mx_p(routedmx->blob_mx); + obj->payload = gf_realloc(obj->payload, total_len+1); + obj->alloc_size = total_len; + obj->blob.size = total_len; + obj->blob.data = obj->payload; + gf_mx_v(routedmx->blob_mx); + } + if (obj->payload) + obj->payloadtotal_len = 0; if (tsi && rlct) { count = gf_list_count(rlct->static_files); obj->rlct = rlct; @@ -856,34 +2076,37 @@ } if (!total_len) { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u started without total-length assigned !\n", s->service_id, tsi, toi )); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u started without total-length assigned !\n", s->log_name, tsi, toi )); } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d starting object TSI %u TOI %u total-length %d\n", s->service_id, tsi, toi, total_len)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Starting object TSI %u TOI %u total-length %d\n", s->log_name, tsi, toi, total_len)); } - obj->download_time_ms = gf_sys_clock(); + obj->start_time_ms = gf_sys_clock(); + gf_assert(gf_list_find(s->objects, obj)<0); gf_list_add(s->objects, obj); } else if (!obj->total_length && total_len) { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u was started without total-length assigned, assigning to %u\n", s->service_id, tsi, toi, total_len)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u was started without total-length assigned, assigning to %u\n", s->log_name, tsi, toi, total_len)); // Check if there are no fragments in the object that extend beyond the total length for (i=0; i < obj->nb_frags; i++) { if((u64) obj->fragsi.offset + obj->fragsi.size > total_len) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u: TOL (%u) doesn't cover previously received fragment %u, %u, purging object \n", s->service_id, tsi, toi, total_len, obj->fragsi.offset, obj->fragsi.offset+obj->fragsi.size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u: TOL (%u) doesn't cover previously received fragment %u, %u, purging object \n", s->log_name, tsi, toi, total_len, obj->fragsi.offset, obj->fragsi.offset+obj->fragsi.size)); obj->nb_frags = obj->nb_recv_frags = 0; obj->nb_bytes = obj->nb_recv_bytes = 0; } } - if (obj->alloc_size < total_len) { - gf_mx_p(routedmx->blob_mx); - obj->payload = gf_realloc(obj->payload, total_len+1); - obj->alloc_size = total_len; - obj->blob.size = total_len; - obj->blob.data = obj->payload; - gf_mx_v(routedmx->blob_mx); - } + if (obj->alloc_size < total_len) { + gf_mx_p(routedmx->blob_mx); + obj->payload = gf_realloc(obj->payload, total_len+1); + obj->alloc_size = total_len; + obj->blob.size = total_len; + obj->blob.data = obj->payload; + gf_mx_v(routedmx->blob_mx); + } obj->total_length = total_len; + if (obj->payload) + obj->payloadtotal_len = 0; } else if (total_len && (obj->total_length != total_len) && (obj->status < GF_LCT_OBJ_DONE)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u mismatch in total-length %u assigned, %u redeclared, purging objet\n", s->service_id, tsi, toi, obj->total_length, total_len)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u mismatch in total-length %u assigned, %u redeclared, purging objet\n", s->log_name, tsi, toi, obj->total_length, total_len)); obj->nb_frags = obj->nb_recv_frags = 0; obj->nb_bytes = obj->nb_recv_bytes = 0; obj->total_length = total_len; @@ -897,6 +2120,8 @@ gf_mx_v(routedmx->blob_mx); } + if (obj->payload) + obj->payloadtotal_len = 0; obj->status = GF_LCT_OBJ_INIT; } if (s->last_active_obj != obj) { @@ -907,62 +2132,62 @@ gf_route_service_flush_object(s, o); gf_route_dmx_process_object(routedmx, s, o); } else { + o->status = GF_LCT_OBJ_DONE_ERR; gf_route_obj_to_reservoir(routedmx, s, o); } - } - //note that if not in order and no timeout, we wait forever ! - else if (in_order || routedmx->reorder_timeout) { - count = gf_list_count(s->objects); - for (i=0; i<count; i++) { - u32 new_count; - GF_LCTObject *o = gf_list_get(s->objects, i); - if (o==obj) break; - //we can only detect losses if a new TOI on the same TSI is found - if (o->tsi != obj->tsi) continue; - if (o->status>=GF_LCT_OBJ_DONE_ERR) continue; - - if (!in_order) { - u32 elapsed = gf_sys_clock() - o->last_gather_time; - if (elapsed < routedmx->reorder_timeout) - continue; - - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u timeout after %d ms - forcing dispatch\n", s->service_id, o->tsi, o->toi, elapsed )); - } else if (o->rlct && !o->rlct->tsi_init) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u incomplete (tune-in) - forcing dispatch\n", s->service_id, o->tsi, o->toi, toi )); - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u not completely received but in-order delivery signaled and new TOI %u - forcing dispatch\n", s->service_id, o->tsi, o->toi, toi )); - } - - if (o->tsi && o->nb_frags) { - gf_route_service_flush_object(s, o); - gf_route_dmx_process_object(routedmx, s, o); - } else { - gf_route_obj_to_reservoir(routedmx, s, o); - } - new_count = gf_list_count(s->objects); - //objects purged - if (new_count<count) { - i=-1; - count = new_count; - } - } + } else { + gf_route_service_purge_old_objects(routedmx, s, obj->tsi, toi, in_order, obj); } s->last_active_obj = obj; } + //do not purge old objects when gathering the same object, we consider the server is busy sending one obj + //this cleanup should be done only through gf_route_dmx_check_timeouts when we have no input on first wake + *gather_obj = obj; - gf_assert(obj->toi == toi); + gf_assert((ll_map ? ll_map->toi : obj->toi) == toi); gf_assert(obj->tsi == tsi); - //keep receiving if we are done with errors - if (obj->status >= GF_LCT_OBJ_DONE) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u already received - skipping\n", s->service_id, tsi, toi )); + //ignore if we are done without errors + if (obj->status == GF_LCT_OBJ_DONE) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u received on object done, ignoring\n", s->log_name, tsi, toi)); return GF_EOS; } - obj->last_gather_time = gf_sys_clock(); + //keep receiving if we are done with errors + if (obj->status == GF_LCT_OBJ_DONE_ERR) { + //if we had error on this object and we start with first byte-range, restart + if (!start_offset) { + obj->nb_frags = obj->nb_recv_frags = 0; + obj->nb_bytes = obj->nb_recv_bytes = 0; + obj->status = GF_LCT_OBJ_RECEPTION; + } + else if (routedmx->on_event) { + // Sending event about the delayed data received. + GF_ROUTEEventFileInfo finfo; + GF_Blob blob; + memset(&finfo, 0, sizeof(GF_ROUTEEventFileInfo)); + memset(&blob, 0, sizeof(GF_Blob)); + blob.data = data; + blob.size = size; + finfo.blob = &blob; + finfo.total_size = size; + finfo.tsi = tsi; + finfo.toi = toi; + finfo.late_fragment_offset = start_offset; + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u received data after dispatch %u, %u - event sent\n", s->log_name, tsi, toi, start_offset, start_offset+size-1)); + routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_LATE_DATA, s->service_id, &finfo); + return GF_EOS; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u received data after dispatch %u, %u - ignoring\n", s->log_name, tsi, toi, start_offset, start_offset+size-1)); + return GF_EOS; + } + } + obj->last_active_time = gf_sys_clock_high_res(); + obj->blob.last_modification_time = obj->last_active_time; - if (!size) { - goto check_done; - } + if (!size) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Empty LCT packet TSI %u TOI %u\n", s->log_name, tsi, toi)); + goto check_done; + } obj->nb_recv_bytes += size; int start_frag = -1; @@ -983,7 +2208,17 @@ end_frag = obj->nb_frags; } - if(start_frag == end_frag) { + + //only push on first packet of object + if (!start_frag) { + if (routedmx->dispatch_mode==GF_ROUTE_DISPATCH_OUT_OF_ORDER) { + do_push = GF_TRUE; + } else if (!start_offset && (routedmx->dispatch_mode==GF_ROUTE_DISPATCH_PROGRESSIVE)) { + do_push = GF_TRUE; + } + } + + if (start_frag == end_frag) { // insert new fragment between two already received fragments or at the end if (obj->nb_frags==obj->nb_alloc_frags) { obj->nb_alloc_frags *= 2; @@ -1004,13 +2239,15 @@ if(end_frag == start_frag + 1) { // received data extends fragment of index start_frag if(obj->fragsstart_frag.size < old_size + size) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d Overlapping or already received LCT fragment %u, %u\n", s->service_id, start_offset, start_offset+size-1)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Overlapping or already received LCT fragment %u, %u\n", s->log_name, start_offset, start_offset+size-1)); } obj->nb_bytes += obj->fragsstart_frag.size - old_size; + //adding bytes in first frag, we can push + if (!start_frag && !obj->frags0.offset) + do_push = GF_TRUE; } else if(end_frag > start_frag + 1) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d Overlapping LCT fragment\n", s->service_id)); - + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Merging LCT fragment\n", s->log_name)); memmove(&obj->fragsstart_frag+1, &obj->fragsend_frag, sizeof(GF_LCTFragInfo) * (obj->nb_frags - end_frag)); obj->nb_frags += start_frag - end_frag + 1; @@ -1022,14 +2259,10 @@ } } - if (!start_frag) { - do_push = start_offset ? GF_FALSE : routedmx->progressive_dispatch; - } - obj->nb_recv_frags++; obj->status = GF_LCT_OBJ_RECEPTION; - gf_assert(obj->toi == toi); + gf_assert((ll_map ? ll_map->toi : obj->toi) == toi); gf_assert(obj->tsi == tsi); if (start_offset + size > obj->alloc_size) { obj->alloc_size = start_offset + size; @@ -1039,40 +2272,44 @@ //for signaling objects, we set byte after last to 0 to use string functions if (!tsi) obj->alloc_size++; - gf_mx_p(routedmx->blob_mx); + gf_mx_p(routedmx->blob_mx); obj->payload = gf_realloc(obj->payload, obj->alloc_size+1); obj->payloadobj->alloc_size = 0; - obj->blob.data = obj->payload; - gf_mx_v(routedmx->blob_mx); - } + obj->blob.data = obj->payload; + obj->blob.size = obj->alloc_size; + gf_mx_v(routedmx->blob_mx); + } gf_assert(obj->alloc_size >= start_offset + size); + gf_assert(!obj->total_length || (start_offset + size <= obj->total_length)); memcpy(obj->payload + start_offset, data, size); - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d TSI %u TOI %u append LCT fragment, offset %u total size %u recv bytes %u - offset diff since last %ld\n", s->service_id, obj->tsi, obj->toi, start_offset, obj->total_length, obj->nb_bytes, (s32) start_offset - (s32) obj->prev_start_offset)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s TSI %u TOI %u append LCT fragment (%d/%d), offset %u total size %u recv bytes %u - offset diff since last %d\n", s->log_name, obj->tsi, toi, start_frag, obj->nb_frags, start_offset, obj->total_length, obj->nb_bytes, (s32) start_offset - (s32) obj->prev_start_offset)); obj->prev_start_offset = start_offset; - gf_assert(obj->toi == toi); + gf_assert((ll_map ? ll_map->toi : obj->toi) == toi); gf_assert(obj->tsi == tsi); - //not a file (uses templates->segment) and can push - if (do_push && !obj->rlct_file && obj->rlct) { - gf_route_dmx_push_object(routedmx, s, obj, GF_FALSE, GF_TRUE, GF_FALSE, obj->frags0.size); - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d TSI %u TOI %u: %u bytes inserted on non-first fragment (%u totals), cannot push\n", s->service_id, obj->tsi, obj->toi, size, obj->nb_frags)); - } + //media file (uses templates->segment or is FLUTE obj), push if we can + if (do_push && obj->rlct && ((!obj->rlct_file && !obj->flute_type) || (obj->flute_type==GF_FLUTE_OBJ))) { + gf_route_dmx_push_object(routedmx, s, obj, GF_FALSE); + } + //if no TOL specified, update blob size - no need to lock the mutex as we only increase the size but do not change the data pointer + if (!obj->total_length && (start_offset+size > obj->blob.size)) + obj->blob.size = start_offset+size; check_done: //check if we are done done = GF_FALSE; - if (obj->total_length) { + if (obj->total_length && (!ll_map || obj->ll_map_last)) { if (obj->nb_bytes >= obj->total_length) { done = GF_TRUE; } else if (close_flag) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d object TSI %u TOI %u closed flag found (object not yet completed)\n", s->service_id, tsi, toi )); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Object TSI %u TOI %u closed flag found (object not yet completed)\n", s->log_name, tsi, toi )); + done = GF_TRUE; } } else { - if (close_flag) obj->closed_flag = 1; + if (close_flag && (!ll_map || obj->ll_map_last)) obj->closed_flag = 1; } if (!done) return GF_OK; @@ -1086,30 +2323,49 @@ return gf_route_service_flush_object(s, obj); } -static GF_Err gf_route_service_setup_dash(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, char *content, char *content_location) +static GF_Err gf_route_service_setup_dash(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, char *content, char *content_location, u32 file_type) { u32 len = (u32) strlen(content); - if (s->tune_mode==GF_ROUTE_TUNE_SLS_ONLY) { - s->tune_mode = GF_ROUTE_TUNE_OFF; - //unregister sockets - gf_route_register_service_sockets(routedmx, s, GF_FALSE); + if (file_type!=GF_FLUTE_HLS_VARIANT) { + if (s->tune_mode==GF_ROUTE_TUNE_SLS_ONLY) { + s->tune_mode = GF_ROUTE_TUNE_OFF; + //unregister sockets + gf_route_register_service_sockets(routedmx, s, GF_FALSE); + } } if (routedmx->on_event) { + u32 evt_type = GF_ROUTE_EVT_MPD; GF_ROUTEEventFileInfo finfo; - GF_Blob blob; + GF_Blob blob; memset(&finfo, 0, sizeof(GF_ROUTEEventFileInfo)); - memset(&blob, 0, sizeof(GF_Blob)); - blob.data = content; - blob.size = len; - finfo.blob = &blob; - finfo.total_size = len; + memset(&blob, 0, sizeof(GF_Blob)); + blob.data = content; + blob.size = len; + finfo.blob = &blob; + finfo.total_size = len; finfo.filename = content_location; - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d received MPD file %s\n", s->service_id, content_location )); - routedmx->on_event(routedmx->udta, GF_ROUTE_EVT_MPD, s->service_id, &finfo); + switch (file_type) { + case GF_FLUTE_HLS_VARIANT: + finfo.mime = "application/vnd.apple.mpegURL"; + evt_type = GF_ROUTE_EVT_HLS_VARIANT; + break; + case GF_FLUTE_HLS_MANIFEST: + finfo.mime = "application/vnd.apple.mpegURL"; + evt_type = GF_ROUTE_EVT_MPD; + break; + case GF_FLUTE_DASH_MANIFEST: + evt_type = GF_ROUTE_EVT_MPD; + finfo.mime = "application/dash+xml"; + default: + break; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Received Manifest file %s\n", s->log_name, content_location)); + routedmx->on_event(routedmx->udta, evt_type, s->service_id, &finfo); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d received MPD file %s content:\n%s\n", s->service_id, content_location, content )); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Received Manifest file %s content:\n%s\n", s->log_name, content_location, content )); } return GF_OK; } @@ -1124,7 +2380,7 @@ e = gf_xml_dom_parse_string(routedmx->dom, content); root = gf_xml_dom_get_root(routedmx->dom); if (e || !root) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to parse S-TSID: %s\n", s->service_id, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to parse S-TSID: %s - %s\n", s->log_name, gf_error_to_string(e), gf_xml_dom_get_error(routedmx->dom) )); return e; } @@ -1143,27 +2399,41 @@ else if (!stricmp(att->name, "version")) version = atoi(att->value); } if (!content_type) continue; - if (!strcmp(content_type, "application/s-tsid") || !strcmp(content_type, "application/route-s-tsid+xml")) - *stsid_version = version; + if (!strcmp(content_type, "application/s-tsid") || !strcmp(content_type, "application/route-s-tsid+xml")) + *stsid_version = version; else if (!strcmp(content_type, "application/dash+xml")) - *mpd_version = version; + *mpd_version = version; } return GF_OK; } +Bool gf_mpd_check_print_format(const char *print_fmt); + static GF_Err gf_route_service_setup_stsid(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, char *content, char *content_location) { GF_Err e; GF_XMLAttribute *att; GF_XMLNode *rs, *ls, *srcf, *efdt, *node, *root; u32 i, j, k, crc, nb_lct_channels=0; + GF_List *remove_sessions = NULL; + GF_List *remove_channels = NULL; crc = gf_crc_32(content, (u32) strlen(content) ); if (!s->stsid_crc) { s->stsid_crc = crc; } else if (s->stsid_crc != crc) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d update of S-TSID not yet supported, skipping\n", s->service_id)); - return GF_NOT_SUPPORTED; + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Update of S-TSID\n", s->log_name)); + //collect old network sessions + remove_sessions = gf_list_clone(s->route_sessions); + //collect old LCT channels + remove_channels = gf_list_new(); + for (i=0; i<gf_list_count(s->route_sessions); i++) { + GF_ROUTESession *rsess = gf_list_get(s->route_sessions, i); + for (j=0; j<gf_list_count(rsess->channels); j++) { + gf_list_add(remove_channels, gf_list_get(rsess->channels, j)); + } + } + s->stsid_crc = crc; } else { return GF_OK; } @@ -1171,58 +2441,102 @@ e = gf_xml_dom_parse_string(routedmx->dom, content); root = gf_xml_dom_get_root(routedmx->dom); if (e || !root) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to parse S-TSID: %s\n", s->service_id, (e)?gf_error_to_string(e) : "Unknown error")); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to parse S-TSID: %s - %s\n", s->log_name, gf_error_to_string(e), gf_xml_dom_get_error(routedmx->dom))); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_CORRUPTED_DATA; } i=0; while ((rs = gf_list_enum(root->content, &i))) { char *dst_ip = s->dst_ip; u32 dst_port = s->port; - char *file_template = NULL; GF_ROUTESession *rsess; GF_ROUTELCTChannel *rlct; u32 tsi = 0; if (rs->type != GF_XML_NODE_TYPE) continue; if (strcmp(rs->name, "RS")) continue; + if (!_xml_get_child_count(rs, "LS")) continue; j=0; while ((att = gf_list_enum(rs->attributes, &j))) { if (!stricmp(att->name, "dIpAddr")) dst_ip = att->value; else if (!stricmp(att->name, "dPort")) { if(! gf_strict_atoui(att->value, &dst_port)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d wrong dPort value (%s), it should be numeric \n", s->service_id, att->value)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong dPort value (%s), it should be numeric \n", s->log_name, att->value)); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_CORRUPTED_DATA; } else if(dst_port >= 65536) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d wrong dPort value (%s), it should belong to the interval 0, 65535 \n", s->service_id, att->value)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong dPort value (%s), it should belong to the interval 0, 65535 \n", s->log_name, att->value)); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_CORRUPTED_DATA; } } } - GF_SAFEALLOC(rsess, GF_ROUTESession); - if (!rsess) return GF_OUT_OF_MEM; + //locate existing session + rsess = NULL; + for (j=0; j< gf_list_count(s->route_sessions); j++) { + rsess = gf_list_get(s->route_sessions, j); + if (rsess->mcast_addr) { + if (!strcmp(rsess->mcast_addr, dst_ip) && rsess->mcast_port==dst_port) { + gf_list_del_item(remove_sessions, rsess); + break; + } + } else { + if (!strcmp(s->dst_ip, dst_ip) && s->port==dst_port) { + gf_list_del_item(remove_sessions, rsess); + break; + } + } + rsess=NULL; + } - rsess->channels = gf_list_new(); + if (!rsess) { + GF_SAFEALLOC(rsess, GF_ROUTESession); + if (rsess) rsess->channels = gf_list_new(); + if (!rsess || !rsess->channels) { + if (rsess) gf_free(rsess); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); + return GF_OUT_OF_MEM; + } + gf_list_add(s->route_sessions, rsess); - //need a new socket for the session - if ((strcmp(s->dst_ip, dst_ip)) || (s->port != dst_port) ) { - rsess->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); - gf_sk_set_usec_wait(rsess->sock, 1); - e = gf_sk_setup_multicast(rsess->sock, dst_ip, dst_port, 0, GF_FALSE, (char *) routedmx->ip_ifce); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to setup mcast for route session on %s:%d\n", s->service_id, dst_ip, dst_port)); - return e; + //need a new socket for the session + if ((strcmp(s->dst_ip, dst_ip)) || (s->port != dst_port) ) { + rsess->mcast_addr = gf_strdup(dst_ip); + rsess->mcast_port = dst_port; + if (!routedmx->start_inactive) { + rsess->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); + if (gf_sk_has_nrt_netcap(rsess->sock)) + routedmx->nrt_max_seg = MAX_SEG_IN_NRT; + + gf_sk_set_usec_wait(rsess->sock, 1); + e = routedmx_setup_socket(routedmx, s->log_name, rsess->sock, dst_ip, dst_port); + if (e) { + gf_sk_del(rsess->sock); + gf_list_del(rsess->channels); + gf_list_del_item(s->route_sessions, rsess); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); + if (rsess->mcast_addr) gf_free(rsess->mcast_addr); + gf_free(rsess); + return e; + } + gf_sk_set_buffer_size(rsess->sock, GF_FALSE, routedmx->unz_buffer_size); + //gf_sk_set_block_mode(rsess->sock, GF_TRUE); + s->secondary_sockets++; + if (s->tune_mode == GF_ROUTE_TUNE_ON) gf_sk_group_register(routedmx->active_sockets, rsess->sock); + } } - gf_sk_set_buffer_size(rsess->sock, GF_FALSE, routedmx->unz_buffer_size); - //gf_sk_set_block_mode(rsess->sock, GF_TRUE); - s->secondary_sockets++; - if (s->tune_mode == GF_ROUTE_TUNE_ON) gf_sk_group_register(routedmx->active_sockets, rsess->sock); } - gf_list_add(s->route_sessions, rsess); + u32 nb_media_streams=0; j=0; while ((ls = gf_list_enum(rs->content, &j))) { - GF_List *static_files; + char *file_template = NULL; char *sep; if (ls->type != GF_XML_NODE_TYPE) continue; if (strcmp(ls->name, "LS")) continue; @@ -1232,13 +2546,17 @@ while ((att = gf_list_enum(ls->attributes, &k))) { if (!strcmp(att->name, "tsi")) { if(! gf_strict_atoui(att->value, &tsi)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d wrong TSI value (%s), it should be numeric \n", s->service_id, att->value)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong TSI value (%s), it should be numeric \n", s->log_name, att->value)); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_CORRUPTED_DATA; } } } if (!tsi) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d missing TSI in LS/ROUTE session\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing TSI in LS/ROUTE session\n", s->log_name)); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_NON_COMPLIANT_BITSTREAM; } k=0; @@ -1248,7 +2566,9 @@ srcf = NULL; } if (!srcf) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d missing srcFlow in LS/ROUTE session\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing srcFlow in LS/ROUTE session\n", s->log_name)); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_NON_COMPLIANT_BITSTREAM; } //enum srcf for efdt @@ -1259,48 +2579,46 @@ if (!strcmp(node->name, "EFDT")) efdt = node; } if (!efdt) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d missing EFDT element in LS/ROUTE session, not supported\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Missing EFDT element in LS/ROUTE session, not supported\n", s->log_name)); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); return GF_NOT_SUPPORTED; } - static_files = gf_list_new(); + //collect TOI template and all FDT files + GF_List *fdt_files = gf_list_new(); k=0; while ((node = gf_list_enum(efdt->content, &k))) { if (node->type != GF_XML_NODE_TYPE) continue; + //Korean version if (!strcmp(node->name, "FileTemplate")) { GF_XMLNode *cnode = gf_list_get(node->content, 0); if (cnode->type==GF_XML_TEXT_TYPE) file_template = cnode->name; + nb_media_streams++; } else if (!strcmp(node->name, "FDTParameters")) { u32 l=0; GF_XMLNode *fdt = NULL; while ((fdt = gf_list_enum(node->content, &l))) { - GF_ROUTELCTFile *rf; if (fdt->type != GF_XML_NODE_TYPE) continue; if (strstr(fdt->name, "File")==NULL) continue; - GF_SAFEALLOC(rf, GF_ROUTELCTFile) - if (rf) { - u32 n=0; - while ((att = gf_list_enum(fdt->attributes, &n))) { - if (!strcmp(att->name, "Content-Location")) rf->filename = gf_strdup(att->value); - else if (!strcmp(att->name, "TOI")) { - if(! gf_strict_atoui(att->value, &rf->toi)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d wrong TOI value (%s), it should be numeric \n", s->service_id, att->value)); - gf_free(rf->filename); - gf_free(rf); - return GF_CORRUPTED_DATA; - } - } - } - if (!rf->filename) { - gf_free(rf); - } else { - gf_list_add(static_files, rf); + char *fdt_location = NULL; + u32 fdt_toi = 0; + u32 n=0; + while ((att = gf_list_enum(fdt->attributes, &n))) { + if (!strcmp(att->name, "Content-Location")) fdt_location = gf_strdup(att->value); + else if (!strcmp(att->name, "TOI")) { + if (! gf_strict_atoui(att->value, &fdt_toi)) fdt_toi=0; } } + if (!fdt_toi || !fdt_location) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Corrupted FDT instance : location/TOI missing\n", s->log_name)); + continue; + } + gf_list_add(fdt_files, fdt); } } //US version @@ -1308,90 +2626,171 @@ u32 l=0; GF_XMLNode *fdt = NULL; while ((att = gf_list_enum(node->attributes, &l))) { - if (strstr(att->name, "fileTemplate")) file_template = att->value; + if (strstr(att->name, "fileTemplate")) { + file_template = att->value; + nb_media_streams++; + } } l=0; while ((fdt = gf_list_enum(node->content, &l))) { - GF_ROUTELCTFile *rf; if (fdt->type != GF_XML_NODE_TYPE) continue; if (strstr(fdt->name, "File")==NULL) continue; - GF_SAFEALLOC(rf, GF_ROUTELCTFile) - if (rf) { - u32 n=0; - while ((att = gf_list_enum(fdt->attributes, &n))) { - if (!strcmp(att->name, "Content-Location")) rf->filename = gf_strdup(att->value); - else if (!strcmp(att->name, "TOI")) { - if(! gf_strict_atoui(att->value, &rf->toi)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d wrong TOI value (%s), it should be numeric \n", s->service_id, att->value)); - gf_free(rf->filename); - gf_free(rf); - return GF_CORRUPTED_DATA; - } - } - } - if (!rf->filename) { - gf_free(rf); - } else { - gf_list_add(static_files, rf); + u32 n=0; + char *fdt_location = NULL; + u32 fdt_toi = 0; + while ((att = gf_list_enum(fdt->attributes, &n))) { + if (!strcmp(att->name, "Content-Location")) fdt_location = att->value; + else if (!strcmp(att->name, "TOI")) { + if(! gf_strict_atoui(att->value, &fdt_toi)) fdt_toi = 0; } } + if (!fdt_toi || !fdt_location) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Corrupted FDT instance : location/TOI missing\n", s->log_name)); + continue; + } + gf_list_add(fdt_files, fdt); } } } - if (!gf_list_count(static_files)) { - GF_ROUTELCTFile *rf; - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d missing init file name in LS/ROUTE session, could be problematic - will consider any TOI %u (-1) present as a ghost init segment\n", s->service_id, (u32)-1)); - //force an init at -1, some streams still have the init not declared but send on TOI -1 - // interpreting it as a regular segment would break clock setup - GF_SAFEALLOC(rf, GF_ROUTELCTFile) - rf->toi = (u32) -1; - gf_list_add(static_files, rf); - } + if (!file_template) { - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d missing file TOI template in LS/ROUTE session, static content only\n", s->service_id)); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Missing file TOI template in LS/ROUTE session, static content only\n", s->log_name)); } else { sep = strstr(file_template, "$TOI"); if (sep) sep = strchr(sep+3, '$'); if (!sep) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d wrong TOI template %s in LS/ROUTE session\n", s->service_id, file_template)); - gf_route_static_files_del(static_files); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong TOI template %s in LS/ROUTE session\n", s->log_name, file_template)); + gf_list_del(fdt_files); return GF_NOT_SUPPORTED; } } nb_lct_channels++; - //OK setup LCT channel for route - GF_SAFEALLOC(rlct, GF_ROUTELCTChannel); - if (!rlct) { - gf_route_static_files_del(static_files); - return GF_OUT_OF_MEM; + rlct = NULL; + for (k=0; k<gf_list_count(rsess->channels); k++) { + rlct = gf_list_get(rsess->channels, k); + if (rlct->tsi == tsi) break; + rlct = NULL; } - rlct->static_files = static_files; - rlct->tsi = tsi; + if (rlct) { + gf_list_del_item(remove_channels, rlct); + } else { + //OK setup LCT channel for route + GF_SAFEALLOC(rlct, GF_ROUTELCTChannel); + if (!rlct) { + gf_list_del(fdt_files); + gf_list_del(remove_sessions); + gf_list_del(remove_channels); + return GF_OUT_OF_MEM; + } + rlct->static_files = gf_list_new(); + rlct->tsi = tsi; + rlct->is_active = routedmx->start_inactive ? GF_FALSE : GF_TRUE; + if (rlct->is_active) { + if (rsess->mcast_addr) + rsess->nb_active ++; + else + s->nb_active ++; + routedmx->nb_active++; + } + + gf_list_add(rsess->channels, rlct); + } + GF_List *purge_rlct = gf_list_clone(rlct->static_files); + for (k=0; k<gf_list_count(fdt_files); k++) { + u32 l; + GF_XMLNode *fdt = gf_list_get(fdt_files, k); + u32 toi; + sscanf(_xml_get_attr(fdt, "TOI"), "%u", &toi); + const char *location = _xml_get_attr(fdt, "Content-Location"); + GF_ROUTELCTFile *fdt_file = NULL; + for (l=0; l<gf_list_count(purge_rlct); l++) { + fdt_file = gf_list_get(purge_rlct, l); + if ((fdt_file->toi==toi) && fdt_file->filename && !strcmp(fdt_file->filename, location)) { + gf_list_rem(purge_rlct, l); + break; + } + fdt_file = NULL; + } + if (fdt_file) continue; + GF_SAFEALLOC(fdt_file,GF_ROUTELCTFile); + if (!fdt_file) continue; + fdt_file->filename = gf_strdup(location); + fdt_file->toi = toi; + if (strstr(location, ".m3u8")) { + if (rlct->dash_rep_id) gf_free(rlct->dash_rep_id); + rlct->dash_rep_id = gf_strdup(location); + } + gf_list_add(rlct->static_files, fdt_file); + } + gf_list_del(fdt_files); + //trash all objects pending on files removed + while (gf_list_count(purge_rlct)) { + GF_ROUTELCTFile *old_fdt = gf_list_pop_back(purge_rlct); + //remove from static file list + gf_list_del_item(rlct->static_files, old_fdt); + //remove all active objects on this file + for (k=0; k<gf_list_count(s->objects);k++) { + GF_LCTObject *o = gf_list_get(s->objects, k); + if (o->rlct_file==old_fdt) { + gf_route_obj_to_reservoir(routedmx, s, o); + k--; + } + } + //delete file + if (old_fdt->filename) gf_free(old_fdt->filename); + gf_free(old_fdt); + } + gf_list_del(purge_rlct); + + if (!gf_list_count(rlct->static_files)) { + GF_ROUTELCTFile *rf; + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Missing init file name in LS/ROUTE session, could be problematic - will consider any TOI %u (-1) present as a ghost init segment\n", s->log_name, (u32)-1)); + //force an init at -1, some streams still have the init not declared but send on TOI -1 + // interpreting it as a regular segment would break clock setup + GF_SAFEALLOC(rf, GF_ROUTELCTFile) + rf->toi = (u32) -1; + gf_list_add(rlct->static_files, rf); + } + + + if (rlct->toi_template) gf_free(rlct->toi_template); rlct->toi_template = NULL; if (file_template) { + if (rlct->toi_template) gf_free(rlct->toi_template); + rlct->toi_template = NULL; sep = strstr(file_template, "$TOI"); sep0 = 0; gf_dynstrcat(&rlct->toi_template, file_template, NULL); sep0 = '$'; if (sep4=='$') { - gf_dynstrcat(&rlct->toi_template, "%d", NULL); + gf_dynstrcat(&rlct->toi_template, "%u", NULL); sep += 5; } else { char *sep_end = strchr(sep+3, '$'); sep_end0 = 0; - gf_dynstrcat(&rlct->toi_template, sep+4, NULL); + if (gf_mpd_check_print_format(sep+4)) { + gf_dynstrcat(&rlct->toi_template, sep+4, NULL); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Corrupted TOI template %s, patching\n", s->log_name, file_template)); + gf_dynstrcat(&rlct->toi_template, "%u", NULL); + } sep_end0 = '$'; sep = sep_end + 1; } gf_dynstrcat(&rlct->toi_template, sep, NULL); } + s->nb_media_streams -= rlct->num_components; + rlct->num_components = nb_media_streams; + s->nb_media_streams += rlct->num_components; + //fill in payloads + rlct->nb_cps = 0; k=0; efdt = NULL; while ((node = gf_list_enum(srcf->content, &k))) { @@ -1412,30 +2811,51 @@ else if (!strcmp(att->name, "srcFecPayloadId")) lreg->src_fec_payload_id = (u8) atoi(att->value); } if (lreg->src_fec_payload_id) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d payload format indicates srcFecPayloadId %d (reserved), assuming 0\n", s->service_id, lreg->src_fec_payload_id)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Payload format indicates srcFecPayloadId %d (reserved), assuming 0\n", s->log_name, lreg->src_fec_payload_id)); } if (lreg->format_id != 1) { if (lreg->format_id && (lreg->format_id<5)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d payload formatId %d not supported\n", s->service_id, lreg->format_id)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Payload formatId %d not supported\n", s->log_name, lreg->format_id)); } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d payload formatId %d reserved, assuming 1\n", s->service_id, lreg->format_id)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s payload formatId %d reserved, assuming 1\n", s->log_name, lreg->format_id)); } } rlct->nb_cps++; if (rlct->nb_cps==8) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d more payload formats than supported (8 max)\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s More payload formats than supported (8 max)\n", s->log_name)); break; } } + else if (!strcmp(node->name, "ContentInfo") && !rlct->dash_rep_id) { + const GF_XMLNode *n = _xml_get_child(node, "MediaInfo"); + if (n) { + const char *rep = _xml_get_attr(n, "repId"); + if (rep) { + if (rlct->dash_rep_id) gf_free(rlct->dash_rep_id); + rlct->dash_rep_id = gf_strdup(rep); + } + } + } } - - gf_list_add(rsess->channels, rlct); } } + while (gf_list_count(remove_channels)) { + GF_ROUTELCTChannel *lc = gf_list_pop_back(remove_channels); + gf_route_lct_removed(routedmx, s, lc); + } + gf_list_del(remove_channels); + + while (gf_list_count(remove_sessions)) { + GF_ROUTESession *rsess = gf_list_pop_back(remove_sessions); + gf_list_del_item(s->route_sessions, rsess); + gf_route_route_session_del(routedmx, rsess); + } + gf_list_del(remove_sessions); + if (!nb_lct_channels) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d does not have any supported LCT channels\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s No supported LCT channels\n", s->log_name)); return GF_NOT_SUPPORTED; } return GF_OK; @@ -1460,7 +2880,7 @@ raw_size = routedmx->unz_buffer_size; e = gf_gz_decompress_payload_ex(routedmx->buffer, object->total_length, &routedmx->unz_buffer, &raw_size, GF_TRUE); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d failed to decompress signaling bundle: %s\n", s->service_id, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to decompress signaling bundle: %s\n", s->log_name, gf_error_to_string(e) )); return e; } if (raw_size > routedmx->unz_buffer_size) routedmx->unz_buffer_size = raw_size; @@ -1473,23 +2893,23 @@ payloadpayload_size = 0; //object->payload is allocated with one extra byte // Verifying that the payload is not erroneously treated as plaintext if(!isprint(payload0)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d package appears to be compressed but is being treated as plaintext:\n%s\n", s->service_id, payload)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Package appears to be compressed but is being treated as plaintext:\n%s\n", s->log_name, payload)); } } - GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("ROUTE Service %d got TSI 0 config package:\n%s\n", s->service_id, payload )); + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Got TSI 0 config package:\n%s\n", s->log_name, payload )); //check for multipart if (!strncmp(payload, "Content-Type: multipart/", 24)) { sep = strstr(payload, "boundary=\""); if (!sep) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d cannot find multipart boundary in package:\n%s\n", s->service_id, payload )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Cannot find multipart boundary in package:\n%s\n", s->log_name, payload )); return GF_NON_COMPLIANT_BITSTREAM; } payload = sep + 10; sep = strstr(payload, "\""); if (!sep) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d multipart boundary not properly formatted in package:\n%s\n", s->service_id, payload )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Multipart boundary not properly formatted in package:\n%s\n", s->log_name, payload )); return GF_NON_COMPLIANT_BITSTREAM; } sep0 = 0; @@ -1531,22 +2951,23 @@ } else { char tmp = payloadi; payloadi = 0; - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d unrecognized header entity in package:\n%s\n", s->service_id, payload)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Unrecognized header entity in package:\n%s\n", s->log_name, payload)); payloadi = tmp; } payload += i; } if(!payload0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d end of package has been prematurely reached\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s End of package has been prematurely reached\n", s->log_name)); + gf_free(boundary); return GF_NON_COMPLIANT_BITSTREAM; } payload += 4; content = boundary ? strstr(payload, "\r\n--") : strstr(payload, "\r\n\r\n"); if (content) { content0 = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d package type %s location %s content:\n%s\n", s->service_id, szContentType, szContentLocation, payload )); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Package type %s location %s content:\n%s\n", s->log_name, szContentType, szContentLocation, payload )); } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d %s not properly formatted in package:\n%s\n", s->service_id, boundary ? "multipart boundary" : "entity", payload )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s %s not properly formatted in package:\n%s\n", s->log_name, boundary ? "multipart boundary" : "entity", payload )); if (sep && boundary) sep0 = boundary0; gf_free(boundary); return GF_NON_COMPLIANT_BITSTREAM; @@ -1556,21 +2977,25 @@ e = gf_route_service_parse_mbms_enveloppe(routedmx, s, payload, szContentLocation, &stsid_version, &mpd_version); if (e || !stsid_version || !mpd_version) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d MBMS envelope error %s, S-TSID version %d MPD version %d\n",s->service_id, gf_error_to_string(e), stsid_version, mpd_version )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s MBMS envelope error %s, S-TSID version %d MPD version %d\n", s->log_name, gf_error_to_string(e), stsid_version, mpd_version )); gf_free(boundary); return e ? e : GF_SERVICE_ERROR; } } else if (!strcmp(szContentType, "application/mbms-user-service-description+xml")) { + } else if (!strcmp(szContentType, "application/route-usd+xml")) { } else if (!strcmp(szContentType, "application/dash+xml") || !strcmp(szContentType, "video/vnd.3gpp.mpd") || !strcmp(szContentType, "audio/mpegurl") || !strcmp(szContentType, "video/mpegurl") + || !strcmp(szContentType, "application/vnd.apple.mpegURL") ) { if (!s->mpd_version || (mpd_version && (mpd_version+1 != s->mpd_version))) { + u32 ftype = GF_FLUTE_HLS_MANIFEST; + if (strstr(szContentType, "dash") || strstr(szContentType, "mpd")) ftype = GF_FLUTE_DASH_MANIFEST; s->mpd_version = mpd_version+1; - gf_route_service_setup_dash(routedmx, s, payload, szContentLocation); + gf_route_service_setup_dash(routedmx, s, payload, szContentLocation, ftype); } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d same MPD version, ignoring\n",s->service_id)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Same MPD version, ignoring\n", s->log_name)); } } //Korean and US version have different mime types @@ -1583,10 +3008,10 @@ return e; } } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d same S-TSID version, ignoring\n",s->service_id)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Same S-TSID version, ignoring\n", s->log_name)); } } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d unsupported content type (%s), parsing payload is skipped\n", s->service_id, szContentType)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Unsupported content type (%s), parsing payload is skipped\n", s->log_name, szContentType)); } if (!sep) break; sep0 = boundary0; @@ -1596,7 +3021,7 @@ gf_free(boundary); payload_size = (u32) strlen(payload); if(payload_size > 1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d Unable to process %d remaining characters in the payload due to data corruption\n",s->service_id, payload_size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Unable to process %d remaining characters in the payload due to data corruption\n", s->log_name, payload_size)); return GF_CORRUPTED_DATA; } else { GF_ROUTESession *rsess; @@ -1606,16 +3031,17 @@ nb_channels += gf_list_count(rsess->channels); } if(nb_channels == 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d No session found, dropping manifest\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s No session found, dropping manifest\n", s->log_name)); return GF_INVALID_CONFIGURATION; } return GF_OK; } } + #define GF_ROUTE_MAX_SIZE 0x40000000 -static GF_Err gf_route_dmx_process_service(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTESession *route_sess) +static GF_Err dmx_process_service_route(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTESession *route_sess) { GF_Err e; u32 nb_read, v, C, psi, S, O, H, /*Res, A,*/ B, hdr_len, cp, cc, tsi, toi, pos; @@ -1657,37 +3083,37 @@ cp = gf_bs_read_int(routedmx->bs, 8); if (v!=1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong LCT header version %d, expecting 1\n", s->service_id, v)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header version %d, expecting 1\n", s->log_name, v)); return GF_NON_COMPLIANT_BITSTREAM; } else if (C!=0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE LCT header C %d, expecting 0\n", s->service_id, C)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header C %d, expecting 0\n", s->log_name, C)); return GF_NON_COMPLIANT_BITSTREAM; } else if ((psi!=0) && (psi!=2) ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE LCT header PSI %d, expecting b00 or b10\n", s->service_id, psi)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header PSI %d, expecting b00 or b10\n", s->log_name, psi)); return GF_NON_COMPLIANT_BITSTREAM; } else if (S!=1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE LCT header S, should be 1\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header S, should be 1\n", s->log_name)); return GF_NON_COMPLIANT_BITSTREAM; } else if (O!=1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE LCT header O, should be b01\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header O, should be b01\n", s->log_name)); return GF_NON_COMPLIANT_BITSTREAM; } else if (H!=0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE LCT header H, should be 0\n", s->service_id)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header H, should be 0\n", s->log_name)); return GF_NON_COMPLIANT_BITSTREAM; } if (hdr_len<4) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE LCT header len %d, should be at least 4\n", s->service_id, hdr_len)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header len %d, should be at least 4\n", s->log_name, hdr_len)); return GF_NON_COMPLIANT_BITSTREAM; } if (psi==0) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d : FEC ROUTE not implemented\n", s->service_id)); - return GF_NOT_SUPPORTED; + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s FEC not implemented\n", s->log_name)); + return GF_OK; } cc = gf_bs_read_u32(routedmx->bs); @@ -1724,7 +3150,7 @@ } } if (!in_session) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : no session with TSI %u defined, skipping packet (TOI %u)\n", s->service_id, tsi, toi)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s No session with TSI %u defined, skipping packet (TOI %u)\n", s->log_name, tsi, toi)); return GF_OK; } for (i=0; rlct && i<rlct->nb_cps; i++) { @@ -1736,7 +3162,7 @@ } if (!cp_found) { if ((cp==0) || (cp==2) || (cp>=9) ) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : unsupported code point %d, skipping packet (TOI %u)\n", s->service_id, cp, toi)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Unsupported code point %d, skipping packet (TOI %u)\n", s->log_name, cp, toi)); return GF_OK; } } @@ -1757,15 +3183,19 @@ //for now we only care about S and M if (!a_S && !a_M) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : SLT bundle without MPD or S-TSID, skipping packet\n", s->service_id)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s SLT bundle without MPD or S-TSID, skipping packet\n", s->log_name)); return GF_OK; } } //parse extensions while (hdr_len) { - u8 hel=0, het = gf_bs_read_u8(routedmx->bs); - if (het<128) hel = gf_bs_read_u8(routedmx->bs); + u32 h_pos = (u32) gf_bs_get_position(routedmx->bs); + u8 het = gf_bs_read_u8(routedmx->bs); + u8 hel =0 ; + + if (het<=127) hel = gf_bs_read_u8(routedmx->bs); + else hel=1; switch (het) { case GF_LCT_EXT_FDT: @@ -1781,50 +3211,57 @@ case GF_LCT_EXT_TOL24: tol_size = gf_bs_read_int(routedmx->bs, 24); if(! tol_size) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d : wrong TOL=%u value \n", s->service_id, tol_size)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Wrong TOL=%u value \n", s->log_name, tol_size)); } break; case GF_LCT_EXT_TOL48: if (hel!=2) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d : wrong HEL %d for TOL48 LCT extension, expecting 2\n", s->service_id, hel)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Wrong HEL %d for TOL48 LCT extension, expecting 2\n", s->log_name, hel)); continue; } tol_size = gf_bs_read_long_int(routedmx->bs, 48); if(! tol_size) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d : wrong TOL=%u value \n", s->service_id, tol_size)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Wrong TOL=%u value \n", s->log_name, tol_size)); } break; default: - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : unsupported header extension HEL %d HET %d, ignoring\n", s->service_id, hel, het)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Unsupported header extension HEL %d HET %d, ignoring\n", s->log_name, hel, het)); break; } if (hdr_len<hel) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Service %d : wrong HEL %d for LCT extension %d, remaining header size %d\n", s->service_id, hel, het, hdr_len)); + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Wrong HEL %d for LCT extension %d, remaining header size %d\n", s->log_name, hel, het, hdr_len)); continue; } + h_pos = (u32) (gf_bs_get_position(routedmx->bs) - h_pos); + while ((u32) hel*4 > h_pos) { + h_pos++; + gf_bs_read_u8(routedmx->bs); + } if (hel) hdr_len -= hel; else hdr_len -= 1; } start_offset = gf_bs_read_u32(routedmx->bs); if (start_offset>=GF_ROUTE_MAX_SIZE) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE start offset %u\n", s->service_id, start_offset)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Invalid start offset %u\n", s->log_name, start_offset)); return GF_NON_COMPLIANT_BITSTREAM; } if (tol_size>=GF_ROUTE_MAX_SIZE) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Service %d : wrong ROUTE object size %u\n", s->service_id, tol_size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Invalid object size %u\n", s->log_name, tol_size)); return GF_NON_COMPLIANT_BITSTREAM; } pos = (u32) gf_bs_get_position(routedmx->bs); - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE Service %d : LCT packet TSI %u TOI %u size %d startOffset %u TOL "LLU" (PckNum %d)\n", s->service_id, tsi, toi, nb_read-pos, start_offset, tol_size, routedmx->nb_packets)); - - e = gf_route_service_gather_object(routedmx, s, tsi, toi, start_offset, routedmx->buffer + pos, nb_read-pos, (u32) tol_size, B, in_order, rlct, &gather_object); + e = gf_route_service_gather_object(routedmx, s, tsi, toi, start_offset, routedmx->buffer + pos, nb_read-pos, (u32) tol_size, B, in_order, rlct, &gather_object, -1, 0); if (e==GF_EOS) { - if (!tsi) { + //in case we were pushed a NULL object + if (!gather_object->nb_frags) { + gf_route_obj_to_reservoir(routedmx, s, gather_object); + } + else if (!tsi) { if (gather_object->status==GF_LCT_OBJ_DONE_ERR) { s->last_dispatched_toi_on_tsi_zero=0; gf_route_obj_to_reservoir(routedmx, s, gather_object); @@ -1851,6 +3288,176 @@ return GF_OK; } +static GF_Err dmx_process_service_dvb_flute(GF_ROUTEDmx *routedmx, GF_ROUTEService *s, GF_ROUTESession *route_sess) +{ + GF_Err e; + u32 fdt_symbol_length=0; + Bool has_ext_fdt = GF_FALSE; + u32 nb_read, cp , v, C, psi, S, O, H, /*Res, A,*/ B, hdr_len, tsi, toi, pos; + u64 transfert_length=0; + u32 start_offset=0; + GF_ROUTELCTChannel *rlct=NULL; + GF_LCTObject *gather_object=NULL; + u32 /*SBN,*/ESI; //Source Block Length | Encoding Symbol + + if (route_sess) { + e = gf_sk_receive_no_select(route_sess->sock, routedmx->buffer, routedmx->buffer_size, &nb_read); + } else { + e = gf_sk_receive_no_select(s->sock, routedmx->buffer, routedmx->buffer_size, &nb_read); + } + + if (e != GF_OK) return e; + gf_assert(nb_read); + + routedmx->nb_packets++; + routedmx->total_bytes_recv += nb_read; + routedmx->last_pck_time = gf_sys_clock_high_res(); + if (!routedmx->first_pck_time) routedmx->first_pck_time = routedmx->last_pck_time; + + e = gf_bs_reassign_buffer(routedmx->bs, routedmx->buffer, nb_read); + if (e != GF_OK) return e; + + //parse LCT header + v = gf_bs_read_int(routedmx->bs, 4); + C = gf_bs_read_int(routedmx->bs, 2); + psi = gf_bs_read_int(routedmx->bs, 2); + S = gf_bs_read_int(routedmx->bs, 1); + O = gf_bs_read_int(routedmx->bs, 2); + H = gf_bs_read_int(routedmx->bs, 1); + /*Res = */gf_bs_read_int(routedmx->bs, 2); + /*A = */gf_bs_read_int(routedmx->bs, 1); + B = gf_bs_read_int(routedmx->bs, 1); + hdr_len = gf_bs_read_int(routedmx->bs, 8); + cp = gf_bs_read_int(routedmx->bs, 8); + + if (v!=1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header version %d, expecting 1\n", s->log_name, v)); + return GF_NON_COMPLIANT_BITSTREAM; + } + else if (C!=0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header C %d, expecting 0\n", s->log_name, C)); + return GF_NON_COMPLIANT_BITSTREAM; + } + else if ((psi!=0) && (psi!=2) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header PSI %d, expecting b00 or b10\n", s->log_name, psi)); + return GF_NON_COMPLIANT_BITSTREAM; + } + else if (cp>1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header CP %d but only Compact No-Code FEC and raptor FEC are allowed\n", s->log_name, cp)); + return GF_NON_COMPLIANT_BITSTREAM; + } + else if (S && H) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header S, only 16 and 32 bits supported\n", s->log_name)); + return GF_NOT_SUPPORTED; + } + else if ((O>1) || (O && H)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header O, only 16 and 32 bits supported\n", s->log_name)); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (hdr_len < (u32) (H ? 3 : 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Wrong LCT header len %d, should be at least %u\n", s->log_name, hdr_len, (H ? 3 : 4))); + return GF_NON_COMPLIANT_BITSTREAM; + } + + /*cc = */gf_bs_read_u32(routedmx->bs); + if (H) { + tsi = gf_bs_read_u16(routedmx->bs); + toi = gf_bs_read_u16(routedmx->bs); + hdr_len -= 3; + } else { + tsi = gf_bs_read_u32(routedmx->bs); + toi = gf_bs_read_u32(routedmx->bs); + hdr_len -= 4; + } + + //parse extensions + while (hdr_len) { + u32 h_pos = (u32) gf_bs_get_position(routedmx->bs); + u8 het = gf_bs_read_u8(routedmx->bs); + u8 hel =0 ; + + if (het<=127) hel = gf_bs_read_u8(routedmx->bs); + else hel=1; + + switch (het) { + case GF_LCT_EXT_FDT: + /*u8 flute_version = */gf_bs_read_int(routedmx->bs, 4); // TODO: add version verification, if different than 1 + /*u16 fdt_instance_id = */gf_bs_read_int(routedmx->bs, 20); + has_ext_fdt = GF_TRUE; + break; + + case GF_LCT_EXT_FTI: + { + transfert_length = gf_bs_read_int(routedmx->bs, 48); + /*u16 Fec_instance_ID = */gf_bs_read_int(routedmx->bs, 16); + fdt_symbol_length = gf_bs_read_int(routedmx->bs, 16); + /*u32 Maximum_source_block_length = */gf_bs_read_int(routedmx->bs, 32); + } + break; + + default: + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("%s Unsupported header extension HEL %d HET %d, ignoring\n", s->log_name, hel, het)); + break; + } + if (hdr_len<hel) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Wrong HEL %d for LCT extension %d, remaining header size %d\n", s->log_name, hel, het, hdr_len)); + continue; + } + h_pos = (u32) (gf_bs_get_position(routedmx->bs) - h_pos); + while ((u32) hel*4 > h_pos) { + h_pos++; + gf_bs_read_u8(routedmx->bs); + } + if (hel) hdr_len -= hel; + else hdr_len -= 1; + } + + //both no-code and raptor use 16 bits for each SBN and ESI + /*SBN =(u32) */gf_bs_read_u16(routedmx->bs); + ESI = (u32) gf_bs_read_u16(routedmx->bs); + pos = (u32) gf_bs_get_position(routedmx->bs); + + if (s->last_active_obj + && (s->last_active_obj->tsi==tsi) + //watchout for manifest and init segment objects which set RLCT to point to the segment delivery service + && s->last_active_obj->rlct + && (s->last_active_obj->rlct->tsi==tsi) + ) { + rlct = s->last_active_obj->rlct; + } else { + Bool in_session=GF_FALSE; + GF_ROUTESession *rsess; + u32 i=0; + while ((rsess = gf_list_enum(s->route_sessions, &i))) { + u32 j=0; + while ((rlct = gf_list_enum(rsess->channels, &j))) { + if (rlct->tsi_probe && has_ext_fdt) { + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s Assigning TSI %u to session %s port %u\n", s->log_name, tsi, rsess->mcast_addr ? rsess->mcast_addr : s->dst_ip, rsess->mcast_addr ? rsess->mcast_port : s->port)); + + rlct->tsi_probe = GF_FALSE; + rlct->tsi = tsi; + } + if (rlct->tsi == tsi) { + in_session = GF_TRUE; + break; + } + rlct = NULL; + } + if (in_session) break; + } + } + + e = gf_route_service_gather_object(routedmx, s, tsi, toi, start_offset, routedmx->buffer + pos, nb_read-pos, (u32) transfert_length, B, GF_FALSE, rlct, &gather_object, ESI, fdt_symbol_length); + + start_offset += (nb_read ) * ESI; + + if (e==GF_EOS) { + gf_route_dmx_process_object(routedmx, s, gather_object); + } + + return GF_OK; +} + static GF_Err gf_route_dmx_process_lls(GF_ROUTEDmx *routedmx) { u32 read; @@ -1874,7 +3481,7 @@ lls_group_count = 1 + routedmx->buffer2; lls_table_version = routedmx->buffer3; - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE LLSTable size %d version %d: ID %d (%s) - group ID %d - group count %d\n", read, lls_table_id, lls_table_version, (lls_table_id==1) ? "SLT" : (lls_table_id==2) ? "RRT" : (lls_table_id==3) ? "SystemTime" : (lls_table_id==4) ? "AEAT" : "Reserved", lls_group_id, lls_group_count)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ATSC LLSTable size %d version %d: ID %d (%s) - group ID %d - group count %d\n", read, lls_table_id, lls_table_version, (lls_table_id==1) ? "SLT" : (lls_table_id==2) ? "RRT" : (lls_table_id==3) ? "SystemTime" : (lls_table_id==4) ? "AEAT" : "Reserved", lls_group_id, lls_group_count)); switch (lls_table_id) { case 1: if (routedmx->slt_version== 1+lls_table_version) return GF_OK; @@ -1885,41 +3492,41 @@ if (routedmx->rrt_version== 1+lls_table_version) return GF_OK; routedmx->rrt_version = 1+lls_table_version; name="RRT"; - break; + break; case 3: if (routedmx->systime_version== 1+lls_table_version) return GF_OK; routedmx->systime_version = 1+lls_table_version; name="SysTime"; - break; + break; case 4: if (routedmx->aeat_version== 1+lls_table_version) return GF_OK; routedmx->aeat_version = 1+lls_table_version; name="AEAT"; - break; + break; default: return GF_OK; } e = gf_gz_decompress_payload_ex(&routedmx->buffer4, read-4, &routedmx->unz_buffer, &raw_size, GF_TRUE); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to decompress %s table: %s\n", name, gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ATSC Failed to decompress %s table: %s\n", name, gf_error_to_string(e) )); return e; } //realloc happened if (routedmx->unz_buffer_size<raw_size) routedmx->unz_buffer_size = raw_size; routedmx->unz_bufferraw_size=0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ROUTE %s table - payload:\n%s\n", name, routedmx->unz_buffer)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_ROUTE, ("ATSC %s table - payload:\n%s\n", name, routedmx->unz_buffer)); e = gf_xml_dom_parse_string(routedmx->dom, routedmx->unz_buffer); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to parse SLT XML: %s\n", gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ATSC Failed to parse SLT XML: %s - %s\n", gf_error_to_string(e), gf_xml_dom_get_error(routedmx->dom) )); return e; } root = gf_xml_dom_get_root(routedmx->dom); if (!root) { - GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ROUTE Failed to get XML root for %s table\n", name )); + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("ATSC Failed to get XML root for %s table\n", name )); return e; } @@ -1938,44 +3545,103 @@ GF_EXPORT GF_Err gf_route_dmx_process(GF_ROUTEDmx *routedmx) { - u32 i, count; + u32 i, j, count, nb_obj=0; GF_Err e; //check all active sockets e = gf_sk_group_select(routedmx->active_sockets, 10, GF_SK_SELECT_READ); - if (e) return e; + if (e) { + //this only happens when netcap is used, flush all pending objects + if (e==GF_EOS) { + count = gf_list_count(routedmx->services); + for (i=0; i<count; i++) { + GF_ROUTEService *s = (GF_ROUTEService *)gf_list_get(routedmx->services, i); + if (s->tune_mode==GF_ROUTE_TUNE_OFF) continue; + j=0; + GF_LCTObject *obj; + while ((obj=gf_list_enum(s->objects, &j))) { + if (obj->status==GF_LCT_OBJ_RECEPTION) { + obj->status = GF_LCT_OBJ_DONE_ERR; + gf_route_dmx_process_object(routedmx, s, obj); + } + } + } + } + return e; + } + //do NOT return if error on one of the sockets, make sure we flush all sockets first + //otherwise we could get NETWORK_EMPTY on first socket while there is data incoming on other ones + GF_Err out_err = GF_OK; if (routedmx->atsc_sock) { if (gf_sk_group_sock_is_set(routedmx->active_sockets, routedmx->atsc_sock, GF_SK_SELECT_READ)) { - e = gf_route_dmx_process_lls(routedmx); - if (e) return e; + out_err = gf_route_dmx_process_lls(routedmx); } } + Bool has_network_empty = GF_FALSE; count = gf_list_count(routedmx->services); for (i=0; i<count; i++) { - u32 j; + u32 nb_obj_service; GF_ROUTESession *rsess; GF_ROUTEService *s = (GF_ROUTEService *)gf_list_get(routedmx->services, i); if (s->tune_mode==GF_ROUTE_TUNE_OFF) continue; + nb_obj_service = gf_list_count(s->objects); + if (s->nb_media_streams) nb_obj_service /= s->nb_media_streams; + //except for flute + if (s->service_id) { + if (nb_obj<nb_obj_service) nb_obj = nb_obj_service; + if (routedmx->nrt_max_seg && (nb_obj_service > routedmx->nrt_max_seg)) + continue; + } if (gf_sk_group_sock_is_set(routedmx->active_sockets, s->sock, GF_SK_SELECT_READ)) { - e = gf_route_dmx_process_service(routedmx, s, NULL); - if (e) return e; + e = s->process_service(routedmx, s, NULL); + + if (e==GF_IP_NETWORK_EMPTY) { + has_network_empty = GF_TRUE; + } else if (e && !out_err) { + out_err = e; + } } if (s->tune_mode!=GF_ROUTE_TUNE_ON) continue; + if (!s->secondary_sockets) continue; j=0; while ((rsess = (GF_ROUTESession *)gf_list_enum(s->route_sessions, &j) )) { if (gf_sk_group_sock_is_set(routedmx->active_sockets, rsess->sock, GF_SK_SELECT_READ)) { - e = gf_route_dmx_process_service(routedmx, s, rsess); - if (e) return e; + e = s->process_service(routedmx, s, rsess); + if (e==GF_IP_NETWORK_EMPTY) { + has_network_empty = GF_TRUE; + } else if (e && !out_err) { + out_err = e; + } } } + } + if (has_network_empty + || (routedmx->nrt_max_seg && (nb_obj>routedmx->nrt_max_seg)) + ) { + return GF_IP_NETWORK_EMPTY; + } + return out_err; +} +void gf_route_dmx_check_timeouts(GF_ROUTEDmx *routedmx) +{ + u32 i, count = gf_list_count(routedmx->services); + for (i=0; i<count; i++) { + GF_ROUTEService *s = (GF_ROUTEService *)gf_list_get(routedmx->services, i); + if (s->tune_mode==GF_ROUTE_TUNE_OFF) continue; + + gf_route_service_purge_old_objects(routedmx, s, 0, 0, GF_FALSE, NULL); } - return GF_OK; +} + +Bool gf_route_dmx_has_active_multicast(GF_ROUTEDmx *routedmx) +{ + return routedmx->nb_active ? GF_TRUE : GF_FALSE; } GF_EXPORT @@ -1998,7 +3664,9 @@ GF_ROUTEService *s; while ((s = gf_list_enum(routedmx->services, &i))) { if (s->service_id != service_id) continue; - return gf_list_count(s->objects); + u32 nb_obj = gf_list_count(s->objects); + if (s->nb_media_streams) nb_obj /= s->nb_media_streams; + return nb_obj; } return 0; } @@ -2022,7 +3690,7 @@ #endif -static GF_Err gf_route_dmx_keep_or_remove_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName, Bool purge_previous, Bool is_remove) +static GF_Err gf_route_dmx_keep_or_remove_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName, Bool purge_previous, Bool is_remove, Bool locate_only) { u32 i=0; GF_ROUTEService *s=NULL; @@ -2032,14 +3700,20 @@ s = NULL; } if (!s) return GF_BAD_PARAM; + if (locate_only) s->in_reset = GF_FALSE; + i=0; while ((obj = gf_list_enum(s->objects, &i))) { u32 toi; if (obj->rlct && obj->rlct->toi_template && (sscanf(fileName, obj->rlct->toi_template, &toi) == 1)) { u32 tsi; if (toi == obj->toi) { - GF_ROUTELCTChannel *rlct = obj->rlct; + u32 obj_start_time; + //GF_ROUTELCTChannel *rlct = obj->rlct; + if (locate_only) { + return GF_OK; + } if (!is_remove) { obj->force_keep = 1; return GF_OK; @@ -2051,15 +3725,27 @@ //obj being received do not destroy if (obj->status == GF_LCT_OBJ_RECEPTION) break; + obj_start_time = obj->start_time_ms; tsi = obj->tsi; gf_route_obj_to_reservoir(routedmx, s, obj); if (purge_previous) { i=0; while ((obj = gf_list_enum(s->objects, &i))) { - if (obj->rlct != rlct) continue; - if (obj->rlct_file) continue; - if (obj->tsi != tsi) continue; - if (obj->toi < toi) { + //static file (ROUTE) or file still advertized in FDT (FLUTE) + if (obj->rlct_file && !obj->rlct_file->can_remove) continue; + if (obj->status <= GF_LCT_OBJ_RECEPTION) continue; + + //crude hack as we currently don't know which media is playing so we need to purge all other ones... + //- don't check LCT channel + //- if not same same tsi, prune if received a few segments ago + //if (obj->rlct != rlct) continue; + if (obj->tsi != tsi) { + if (obj->start_time_ms + 2*obj->download_time_ms >= obj_start_time) { + continue; + } + } + //do NOT purge based on TOI, won't work for flute + if (obj->start_time_ms+obj->download_time_ms < obj_start_time) { i--; //we likely have a loop here if (obj == s->last_active_obj) return GF_OK; @@ -2070,17 +3756,34 @@ return GF_OK; } } - else if (obj->rlct && obj->rlct_file && obj->rlct_file->filename && !strcmp(fileName, obj->rlct_file->filename)) { + else if (obj->rlct_file && obj->rlct_file->filename && !strcmp(fileName, obj->rlct_file->filename)) { + if (locate_only) { + return GF_OK; + } if (!is_remove) { obj->force_keep = 1; - } else { + } else if (!obj->rlct_file->fdt_tsi || obj->rlct_file->can_remove) { gf_route_obj_to_reservoir(routedmx, s, obj); } return GF_OK; } + else if (locate_only && obj->rlct_file && obj->rlct_file->filename && + (strstr(fileName, obj->rlct_file->filename) || strstr(obj->rlct_file->filename, fileName)) + ) { + return GF_OK; + } + } + //we are flute, check root service + if (routedmx->dvb_mabr && service_id) { + return gf_route_dmx_keep_or_remove_object_by_name(routedmx, 0, fileName, purge_previous, is_remove, locate_only); + } + if (locate_only) { + return GF_NOT_FOUND; } if (is_remove) { - GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("ROUTE Failed to remove object %s from service, object not found\n", fileName)); + if (!s->in_reset) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ROUTE, ("%s Failed to remove object %s from service, object not found\n", s->log_name, fileName)); + } return GF_NOT_FOUND; } return GF_OK; @@ -2089,16 +3792,41 @@ GF_EXPORT GF_Err gf_route_dmx_force_keep_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName) { - return gf_route_dmx_keep_or_remove_object_by_name(routedmx, service_id, fileName, GF_FALSE, GF_FALSE); + return gf_route_dmx_keep_or_remove_object_by_name(routedmx, service_id, fileName, GF_FALSE, GF_FALSE, GF_FALSE); } GF_EXPORT GF_Err gf_route_dmx_remove_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName, Bool purge_previous) { - return gf_route_dmx_keep_or_remove_object_by_name(routedmx, service_id, fileName, purge_previous, GF_TRUE); + return gf_route_dmx_keep_or_remove_object_by_name(routedmx, service_id, fileName, purge_previous, GF_TRUE, GF_FALSE); } GF_EXPORT +GF_Err gf_route_dmx_force_keep_object(GF_ROUTEDmx *routedmx, u32 service_id, u32 tsi, u32 toi, Bool force_keep) +{ + u32 i=0; + Bool found = GF_FALSE; + GF_ROUTEService *s=NULL; + GF_LCTObject *obj = NULL; + while ((s = gf_list_enum(routedmx->services, &i))) { + if (s->service_id == service_id) break; + s = NULL; + } + if (!s) return GF_NOT_FOUND; + i=0; + while ((obj = gf_list_enum(s->objects, &i))) { + if (obj->tsi != tsi) continue; + if (obj->toi != toi) continue; + obj->force_keep = force_keep; + found = GF_TRUE; + break; + } + if (!found) return GF_NOT_FOUND; + return GF_OK; +} + + +GF_EXPORT Bool gf_route_dmx_remove_first_object(GF_ROUTEDmx *routedmx, u32 service_id) { u32 i=0; @@ -2120,7 +3848,7 @@ return GF_FALSE; //keep static files active - if (obj->rlct_file) + if (obj->rlct_file && !obj->rlct_file->can_remove) continue; gf_route_obj_to_reservoir(routedmx, s, obj); @@ -2148,7 +3876,7 @@ //if object is being received keep it if (s->last_active_obj == obj) continue; //if object is static file keep it - this may need refinement in case we had init segment updates - if (obj->rlct_file) continue; + if (obj->rlct_file && !obj->rlct_file->can_remove) continue; //obj being received do not destroy if (obj->status <= GF_LCT_OBJ_RECEPTION) continue; //trash @@ -2212,4 +3940,398 @@ if (routedmx) routedmx->debug_tsi = tsi; } +GF_EXPORT +GF_Err gf_route_dmx_patch_frag_info(GF_ROUTEDmx *routedmx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 br_start, u32 br_end) +{ + u32 i=0; + Bool is_patched=GF_FALSE; + if (!routedmx) return GF_BAD_PARAM; + GF_ROUTEService *s=NULL; + GF_LCTObject *obj = NULL; + while ((s = gf_list_enum(routedmx->services, &i))) { + if (s->service_id == service_id) break; + s = NULL; + } + if (!s) + return GF_BAD_PARAM; + i=0; + while ((obj = gf_list_enum(s->objects, &i))) { + if ((obj->tsi == finfo->tsi) && (obj->toi == finfo->toi)) + break; + } + if (!obj) + return GF_BAD_PARAM; + gf_mx_p(obj->blob.mx); + if (!br_start && (br_end==obj->total_length)) { + obj->nb_frags = 1; + obj->frags0.offset = 0; + obj->frags0.size = obj->total_length; + finfo->nb_frags = obj->nb_frags; + finfo->frags = obj->frags; + gf_mx_v(obj->blob.mx); + return GF_OK; + } + + for (i=0; i<obj->nb_frags; i++) { + if (br_start < obj->fragsi.offset) { + //we patched until beginning of this fragment, merge + if (br_end >= obj->fragsi.offset) { + u32 frag_end = obj->fragsi.offset + obj->fragsi.size; + u32 last_end = i ? (obj->fragsi-1.offset+obj->fragsi-1.size) : 0; + obj->fragsi.offset = (last_end > br_start) ? last_end : br_start; + obj->fragsi.size = frag_end - obj->fragsi.offset; + is_patched = GF_TRUE; + br_start = obj->fragsi.offset + obj->fragsi.size; + //patched range was over several holes, continue + if (br_start<br_end) + continue; + break; + } + if (is_patched) + break; + + //we need a new fragment + if (obj->nb_frags+1>obj->nb_alloc_frags) { + obj->nb_alloc_frags = obj->nb_frags+1; + obj->frags = gf_realloc(obj->frags, sizeof(GF_LCTFragInfo)*obj->nb_alloc_frags); + if (!obj->frags) { + finfo->nb_frags = obj->nb_frags = 0; + finfo->frags = obj->frags; + gf_mx_v(obj->blob.mx); + return GF_OUT_OF_MEM; + } + } + memmove(&obj->fragsi+1, &obj->fragsi, sizeof(GF_LCTFragInfo) * (obj->nb_frags - i)); + obj->fragsi.offset = br_start; + obj->fragsi.size = br_end - br_start; + obj->nb_frags++; + is_patched = GF_TRUE; + break; + } + } + if (!is_patched) { + if (obj->nb_frags+1>obj->nb_alloc_frags) { + obj->nb_alloc_frags = obj->nb_frags+1; + obj->frags = gf_realloc(obj->frags, sizeof(GF_LCTFragInfo)*obj->nb_alloc_frags); + if (!obj->frags) { + finfo->nb_frags = obj->nb_frags = 0; + finfo->frags = obj->frags; + gf_mx_v(obj->blob.mx); + return GF_OUT_OF_MEM; + } + } + obj->fragsobj->nb_frags.offset = br_start; + obj->fragsobj->nb_frags.size = br_end - br_start; + obj->nb_frags++; + } + for (i=0; i<obj->nb_frags; i++) { + if ((obj->fragsi.offset==0) && (obj->fragsi.size==obj->total_length)) { + if (i) obj->frags0 = obj->fragsi; + obj->nb_frags=1; + break; + } + if (!i) continue; + + if (obj->fragsi-1.offset+obj->fragsi-1.size == obj->fragsi.offset) { + obj->fragsi-1.size += obj->fragsi.size; + if (i+1<obj->nb_frags) { + memmove(&obj->fragsi, &obj->fragsi+1, sizeof(GF_LCTFragInfo) * (obj->nb_frags - i - 1)); + } + obj->nb_frags--; + i--; + } + } + //patch last range size in case the file size was not known + if (br_end > obj->fragsobj->nb_frags-1.offset + obj->fragsobj->nb_frags-1.size) { + obj->fragsobj->nb_frags-1.size = br_end - obj->fragsobj->nb_frags-1.offset; + } + finfo->nb_frags = obj->nb_frags; + finfo->frags = obj->frags; + + gf_mx_v(obj->blob.mx); + return GF_OK; +} + +GF_Err gf_route_dmx_add_frag_hole(GF_ROUTEDmx *routedmx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 br_start, u32 br_size) +{ + u32 i=0; + if (!routedmx) return GF_BAD_PARAM; + GF_ROUTEService *s=NULL; + GF_LCTObject *obj = NULL; + while ((s = gf_list_enum(routedmx->services, &i))) { + if (s->service_id == service_id) break; + s = NULL; + } + if (!s) + return GF_BAD_PARAM; + i=0; + while ((obj = gf_list_enum(s->objects, &i))) { + if ((obj->tsi == finfo->tsi) && (obj->toi == finfo->toi)) + break; + } + if (!obj) + return GF_BAD_PARAM; + gf_mx_p(obj->blob.mx); + + for (i=0; i<obj->nb_frags; i++) { + if (br_start<obj->fragsi.offset) { + gf_assert(br_start+br_size<=obj->fragsi.offset); + continue; + } + if (obj->fragsi.offset+obj->fragsi.size<=br_start) { + continue; + } + gf_assert(br_start+br_size<=obj->fragsi.offset+obj->fragsi.size); + + //hole is at start of fragment + if (br_start == obj->fragsi.offset) { + //hole covers the whole fragment, drop it + if (obj->fragsi.size <= br_size) { + memmove(&obj->fragsi+1, &obj->fragsi, sizeof(GF_LCTFragInfo)*(obj->nb_frags-i-1)); + obj->nb_frags--; + break; + } + obj->fragsi.offset += br_size; + obj->fragsi.size -= br_size; + break; + } + //hole is at end of fragment + u32 orig_end = obj->fragsi.size + obj->fragsi.offset; + if (orig_end == br_start+br_size) { + gf_assert(br_start > obj->fragsi.offset); + obj->fragsi.size = br_start - obj->fragsi.offset; + break; + } + gf_assert(orig_end>br_start+br_size); + + //need a new fragment + if (obj->nb_frags+1 >= obj->nb_alloc_frags) { + obj->nb_alloc_frags+=1; + obj->frags = gf_realloc(obj->frags, sizeof(GF_LCTFragInfo)*obj->nb_alloc_frags); + } + memmove(&obj->fragsi+2, &obj->fragsi+1, sizeof(GF_LCTFragInfo)*(obj->nb_frags-i-1)); + + gf_assert(br_start > obj->fragsi.offset); + obj->fragsi.size = br_start - obj->fragsi.offset; + obj->fragsi+1.offset = br_start+br_size; + obj->fragsi+1.size = orig_end - (br_start+br_size); + obj->nb_frags++; + break; + } + finfo->nb_frags = obj->nb_frags; + finfo->frags = obj->frags; + + gf_mx_v(obj->blob.mx); + return GF_OK; +} +GF_EXPORT +GF_Err gf_route_dmx_patch_blob_size(GF_ROUTEDmx *routedmx, u32 service_id, GF_ROUTEEventFileInfo *finfo, u32 new_size) +{ + u32 i=0; + if (!routedmx) return GF_BAD_PARAM; + GF_ROUTEService *s=NULL; + GF_LCTObject *obj = NULL; + while ((s = gf_list_enum(routedmx->services, &i))) { + if (s->service_id == service_id) break; + s = NULL; + } + if (!s) return GF_BAD_PARAM; + i=0; + while ((obj = gf_list_enum(s->objects, &i))) { + if ((obj->tsi == finfo->tsi) && (obj->toi == finfo->toi)) + break; + } + if (!obj) return GF_BAD_PARAM; + //we allow patching size down due to fast repair when we lost end of object + if (obj->total_length >= new_size) { + gf_mx_p(obj->blob.mx); + obj->blob.size = new_size; + gf_mx_v(obj->blob.mx); + obj->total_length = new_size; + if (!finfo->total_size) + finfo->total_size = new_size; + return GF_OK; + } + + gf_mx_p(obj->blob.mx); + if (obj->alloc_size<new_size) { + obj->alloc_size = new_size; + obj->payload = gf_realloc(obj->payload, new_size); + obj->blob.data = obj->payload; + } + //if blob size set to total length, adjust otherwise this was set to bytes done, do NOT adjust + if (obj->total_length == obj->blob.size) + obj->blob.size = new_size; + if (!finfo->total_size) + finfo->total_size = new_size; + + obj->total_length = new_size; + gf_mx_v(obj->blob.mx); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_route_dmx_set_object_hint(GF_ROUTEDmx *routedmx, u32 service_id, u32 tsi, u32 toi, u32 hint) +{ + u32 i=0; + if (!routedmx) return GF_BAD_PARAM; + GF_ROUTEService *s=NULL; + GF_LCTObject *obj = NULL; + while ((s = gf_list_enum(routedmx->services, &i))) { + if (s->service_id == service_id) break; + s = NULL; + } + if (!s) return GF_BAD_PARAM; + i=0; + while ((obj = gf_list_enum(s->objects, &i))) { + if ((obj->tsi == tsi) && (obj->toi == toi)) + break; + } + if (!obj) return GF_BAD_PARAM; + if (obj->rlct) obj->rlct->channel_hint = hint; + else if (obj->rlct_file) obj->rlct_file->channel_hint = hint; + return GF_OK; +} + +GF_Err gf_route_dmx_mark_active_quality(GF_ROUTEDmx *routedmx, u32 service_id, const char *period_id, s32 as_id, const char *rep_id, Bool is_selected) +{ + u32 count, i=0, rlct_tsi=0; + if (!routedmx || !rep_id) return GF_BAD_PARAM; + GF_ROUTEService *s=NULL; + while ((s = gf_list_enum(routedmx->services, &i))) { + if (s->service_id == service_id) break; + s = NULL; + } + if (!s) return GF_BAD_PARAM; + + GF_ROUTESession *mcast_sess=NULL; + GF_ROUTELCTChannel *rlct=NULL; + count = gf_list_count(s->route_sessions); + for (i=0; i<count; i++) { + GF_ROUTESession *rsess = gf_list_get(s->route_sessions, i); + u32 j, nb_chan = gf_list_count(rsess->channels); + for (j=0; j<nb_chan; j++) { + rlct = gf_list_get(rsess->channels, j); + //if periodID is set, make sure they match + if (period_id && rlct->dash_period_id && strcmp(period_id, rlct->dash_period_id)) + continue; + if ((rlct->dash_as_id>=0) && (as_id>=0) && (rlct->dash_as_id!=as_id)) + continue; + + if (rep_id && rlct->dash_rep_id && !strcmp(rep_id, rlct->dash_rep_id)) { + mcast_sess = rsess; + rlct_tsi = rlct->tsi; + break; + } + } + if (mcast_sess) break; + } + if (!mcast_sess) + return GF_OK; + + if (!gf_sk_is_multicast_address(mcast_sess->mcast_addr ? mcast_sess->mcast_addr : s->dst_ip)) + return GF_OK; + + GF_LOG(GF_LOG_INFO, GF_LOG_ROUTE, ("%s %s rep %s MCAST %s:%d TSI %u\n", s->log_name, + is_selected ? "Activating" : "Deactivating", + rep_id, + mcast_sess->mcast_addr ? mcast_sess->mcast_addr : s->dst_ip, + mcast_sess->mcast_addr ? mcast_sess->mcast_port : s->port, + rlct_tsi + )); + + GF_Socket **sock = mcast_sess->mcast_addr ? &mcast_sess->sock : &s->sock; + u32 *nb_active = mcast_sess->mcast_addr ? &mcast_sess->nb_active : &s->nb_active; + + if (is_selected) { + if (rlct->is_active) return GF_OK; + + rlct->is_active = GF_TRUE; + if (!mcast_sess->nb_active && !(*sock)) { + *sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, routedmx->netcap_id); + + gf_sk_set_usec_wait(*sock, 1); + const char *dst_add = mcast_sess->mcast_addr ? mcast_sess->mcast_addr : s->dst_ip; + u32 dst_port = mcast_sess->mcast_addr ? mcast_sess->mcast_port : s->port; + GF_Err e = gf_sk_setup_multicast(*sock, dst_add, dst_port, 0, GF_FALSE, (char *) routedmx->ip_ifce); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ROUTE, ("%s Failed to resetup multicast for route session on %s:%d\n", s->log_name, dst_add, dst_port)); + return e; + } + gf_sk_set_buffer_size(*sock, GF_FALSE, routedmx->unz_buffer_size); + gf_sk_group_register(routedmx->active_sockets, *sock); + if (mcast_sess->mcast_addr) + s->secondary_sockets++; + } + + (*nb_active) ++; + routedmx->nb_active++; + } else { + if (!rlct->is_active) return GF_OK; + rlct->is_active = GF_FALSE; + if (s->last_active_obj && (s->last_active_obj->rlct==rlct)) + s->last_active_obj = NULL; + + (*nb_active) --; + routedmx->nb_active--; + //we cannot deactivate service socket in ROUTE, we need to get MPD and STSID updates + //for mabr (flute or route) we can + if (! (*nb_active) && (mcast_sess->mcast_addr || routedmx->dvb_mabr) ) { + gf_sk_group_unregister(routedmx->active_sockets, *sock); + gf_sk_del(*sock); + *sock = NULL; + if (mcast_sess->mcast_addr) + s->secondary_sockets--; + } + } + return GF_OK; +} + +void gf_route_dmx_reset_all(GF_ROUTEDmx *routedmx) +{ + if (!routedmx) return; + u32 i, j, count = gf_list_count(routedmx->services); + for (i=0; i<count; i++) { + GF_ROUTEService *s = (GF_ROUTEService *)gf_list_get(routedmx->services, i); + j=0; + s->in_reset = GF_TRUE; + GF_LCTObject *obj; + while ((obj=gf_list_enum(s->objects, &j))) { + obj->status = GF_LCT_OBJ_DONE_ERR; + gf_route_obj_to_reservoir(routedmx, s, obj); + } + } +} + +void gf_route_dmx_get_repair_info(GF_ROUTEDmx *routedmx, u32 service_id, const char **base_uri, const char **repair_server) +{ + if (base_uri) *base_uri = NULL; + if (repair_server) *repair_server = NULL; + if (!routedmx) return; + u32 i, count = gf_list_count(routedmx->services); + for (i=0; i<count; i++) { + GF_ROUTEService *s = (GF_ROUTEService *)gf_list_get(routedmx->services, i); + if (s->service_id != service_id) continue; + + if (base_uri) *base_uri = s->repair_uri_base; + if (repair_server) *repair_server = s->repair_server; + return; + } + return; +} + +Bool gf_route_dmx_get_object_info(struct __gf_lct_object *lct_obj, GF_ROUTEEventFileInfo *finfo) +{ + if (!lct_obj || !finfo) return GF_FALSE; + if (lct_obj->status==GF_LCT_OBJ_INIT) return GF_FALSE; + + + memset(finfo, 0, sizeof(GF_ROUTEEventFileInfo)); + finfo->blob = &lct_obj->blob; + finfo->frags = lct_obj->frags; + finfo->nb_frags = lct_obj->nb_frags; + finfo->channel_hint = lct_obj->rlct ? lct_obj->rlct->channel_hint : (lct_obj->rlct_file ? lct_obj->rlct_file->channel_hint : 0); + return GF_TRUE; +} + #endif /* !GPAC_DISABLE_ROUTE */
View file
gpac-2.4.0.tar.gz/src/media_tools/webvtt.c -> gpac-26.02.0.tar.gz/src/media_tools/webvtt.c
Changed
@@ -424,13 +424,13 @@ struct _webvtt_parser { GF_WebVTTParserState state; - Bool is_srt, suspend, is_eof, prev_line_empty, in_comment; + Bool is_init, is_srt, suspend, is_eof, prev_line_empty, in_comment; char *comment_text; /* List of non-overlapping GF_WebVTTSample */ GF_List *samples; - FILE *vtt_in; + FILE **vtt_in; s32 unicode_type; u64 last_duration; @@ -578,7 +578,7 @@ extern s32 gf_text_get_utf_type(FILE *in_src); -GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE *vtt_file, s32 unicode_type, Bool is_srt, +GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE **vtt_file, s32 unicode_type, Bool is_srt, void *user, GF_Err (*report_message)(void *, GF_Err, char *, const char *), void (*on_sample_parsed)(void *, GF_WebVTTSample *), void (*on_header_parsed)(void *, const char *)) @@ -613,9 +613,9 @@ void gf_webvtt_parser_restart(GF_WebVTTParser *parser) { - if (!parser->vtt_in) return; + if (!parser || !parser->vtt_in || !*(parser->vtt_in)) return; - gf_fseek(parser->vtt_in, 0, SEEK_SET); + gf_fseek(*parser->vtt_in, 0, SEEK_SET); parser->last_duration = 0; while (gf_list_count(parser->samples)) { gf_webvtt_sample_del((GF_WebVTTSample *)gf_list_get(parser->samples, 0)); @@ -669,9 +669,18 @@ u64 cue_end; u64 sample_end; - sample_end = 0; + if (!cue) + return GF_BAD_PARAM; + cue_start = gf_webvtt_timestamp_get(&cue->start); cue_end = gf_webvtt_timestamp_get(&cue->end); + sample_end = cue_start; + + if (!parser->is_init) { + sample_end = 0; // samples start at ts zero + parser->is_init = GF_TRUE; + } + /* samples in the samples list are contiguous: sample(n)->start == sample(n-1)->end */ for (i = 0; i < (s32)gf_list_count(samples); i++) { GF_WebVTTSample *sample; @@ -778,7 +787,7 @@ } else break; \ } -extern char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type); +extern char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type, Bool *in_progress); GF_Err gf_webvtt_parse_timestamp(GF_WebVTTParser *parser, GF_WebVTTTimestamp *ts, const char *line) { @@ -874,32 +883,32 @@ while (pos < len && linepos != ' ' && linepos != '\t') pos++; if (pos == len) { e = GF_CORRUPTED_DATA; - parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line); + parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in", line); return e; } linepos = 0; e = gf_webvtt_parse_timestamp(parser, &cue->start, timestamp_string); if (e) { - parser->report_message(parser->user, e, "Bad VTT timestamp formatting %s", timestamp_string); + parser->report_message(parser->user, e, "Bad VTT timestamp formatting", timestamp_string); return e; } linepos = ' '; SKIP_WHITESPACE if (pos == len) { e = GF_CORRUPTED_DATA; - parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line); + parser->report_message(parser->user, e, "Error scanning WebVTT cue timing", line); return e; } if ( (pos+2)>= len || linepos != '-' || linepos+1 != '-' || linepos+2 != '>') { e = GF_CORRUPTED_DATA; - parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line); + parser->report_message(parser->user, e, "Error scanning WebVTT cue timing", line); return e; } else { pos += 3; SKIP_WHITESPACE if (pos == len) { e = GF_CORRUPTED_DATA; - parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line); + parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in", line); return e; } timestamp_string = line + pos; @@ -909,7 +918,7 @@ } e = gf_webvtt_parse_timestamp(parser, &cue->end, timestamp_string); if (e) { - parser->report_message(parser->user, e, "Bad VTT timestamp formatting %s", timestamp_string); + parser->report_message(parser->user, e, "Bad VTT timestamp formatting", timestamp_string); return e; } if (pos < len) { @@ -932,7 +941,7 @@ return e; } -GF_Err gf_webvtt_parser_parse_internal(GF_WebVTTParser *parser, GF_WebVTTCue *cue) +GF_Err gf_webvtt_parser_parse_internal(GF_WebVTTParser *parser, GF_WebVTTCue *cue, FILE *ext_file, Bool is_eof) { char szLine2049; char *sOK; @@ -942,6 +951,7 @@ char *header = NULL; u32 header_len = 0; Bool had_marks = GF_FALSE; + u32 is_resume = ext_file ? 1 : 9; if (!parser) return GF_BAD_PARAM; parser->suspend = GF_FALSE; @@ -952,9 +962,18 @@ szLine2048=0; while (!parser->is_eof) { + Bool in_progress = is_eof; if (!cue && parser->suspend) break; - sOK = gf_text_get_utf8_line(szLine, 2048, parser->vtt_in, parser->unicode_type); + sOK = gf_text_get_utf8_line(szLine, 2048, ext_file ? ext_file : *parser->vtt_in, parser->unicode_type, &in_progress); + if (in_progress) { + parser->suspend = GF_TRUE; + if (ext_file && cue && !cue->text) { + gf_webvtt_add_cue_to_samples(parser, parser->samples, cue); + } + break; + } + had_marks = GF_FALSE; REM_TRAIL_MARKS(szLine, "\r\n") len = (u32) strlen(szLine); if (parser->is_srt && sOK && !strncmp(sOK, "WEBVTT", 6)) { @@ -965,7 +984,7 @@ case WEBVTT_PARSER_STATE_WAITING_SIGNATURE: if (!sOK || len < 6 || strnicmp(szLine, "WEBVTT", 6) || (len > 6 && szLine6 != ' ' && szLine6 != '\t')) { e = GF_CORRUPTED_DATA; - parser->report_message(parser->user, e, "Bad WEBVTT file signature %s", szLine); + parser->report_message(parser->user, e, "Bad WEBVTT file signature", szLine); goto exit; } else { if (had_marks) { @@ -1069,7 +1088,7 @@ case WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP: if (sOK && len) { if (cue == NULL) { - cue = gf_webvtt_cue_new(); + cue = gf_webvtt_cue_new(); } if (prevLine) { gf_webvtt_cue_add_property(cue, WEBVTT_ID, prevLine, (u32) strlen(prevLine)); @@ -1099,6 +1118,11 @@ } break; case WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD: + if ((is_resume==1) && !cue) { + GF_WebVTTSample *sample = (GF_WebVTTSample *)gf_list_last(parser->samples); + cue = sample ? gf_list_last(sample->cues) : NULL; + is_resume = 2; + } if (sOK && len) { if (!strncmp(szLine, "NOTE ", 5)) { if (had_marks) { @@ -1112,17 +1136,23 @@ parser->in_comment = GF_FALSE; } } - if (sOK && len) { + //special case when injecting multiple times the header, ignore it + if (ext_file && !strcmp(szLine, "WEBVTT")) { + } + else if (sOK && len) { if (had_marks) { szLinelen = '\n'; len++; } + assert(cue); gf_webvtt_cue_add_property(cue, parser->in_comment ? WEBVTT_POSTCUE_TEXT : WEBVTT_PAYLOAD, szLine, len); /* remain in the same state as a cue payload can have multiple lines */ break; } else { /* end of the current cue */ - gf_webvtt_add_cue_to_samples(parser, parser->samples, cue); + if (is_resume!=2) { + gf_webvtt_add_cue_to_samples(parser, parser->samples, cue); + } cue = NULL; parser->in_comment = GF_FALSE; @@ -1165,8 +1195,14 @@ GF_Err gf_webvtt_parser_parse(GF_WebVTTParser *parser) { - return gf_webvtt_parser_parse_internal(parser, NULL); + return gf_webvtt_parser_parse_internal(parser, NULL, NULL, GF_TRUE); } +GF_Err gf_webvtt_parser_parse_ext(GF_WebVTTParser *parser, FILE *ext_file, Bool in_eos) +{ + return gf_webvtt_parser_parse_internal(parser, NULL, ext_file, in_eos); +} + + void gf_webvtt_parser_not_done(GF_WebVTTParser *parser) { if (parser) parser->is_eof = GF_FALSE; @@ -1189,12 +1225,13 @@ parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD; gf_webvtt_timestamp_set(&cue->start, start); gf_webvtt_timestamp_set(&cue->end, end); + parser->is_init = GF_TRUE; if (vtt_cueid) cue->id = gf_strdup(vtt_cueid); if (vtt_settings) cue->settings = gf_strdup(vtt_settings); if (vtt_pre) cue->pre_text = gf_strdup(vtt_pre); - GF_Err e = gf_webvtt_parser_parse_internal(parser, cue); + GF_Err e = gf_webvtt_parser_parse_internal(parser, cue, NULL, GF_TRUE); parser->is_eof = GF_FALSE; return e; } @@ -1383,18 +1420,18 @@ if (!ts) return; tmp = value; ts->hour = (u32)(tmp/(3600*1000)); - tmp -= ts->hour*3600*1000; + tmp -= (u64) ts->hour*3600*1000; ts->min = (u32)(tmp/(60*1000)); - tmp -= ts->min*60*1000; + tmp -= (u64) ts->min*60*1000; ts->sec = (u32)(tmp/1000); - tmp -= ts->sec*1000; + tmp -= (u64) ts->sec*1000; ts->ms = (u32)tmp; } u64 gf_webvtt_timestamp_get(GF_WebVTTTimestamp *ts) { if (!ts) return 0; - return (3600*ts->hour + 60*ts->min + ts->sec)*1000 + ts->ms; + return (u64)(3600*ts->hour + 60*ts->min + ts->sec)*1000 + ts->ms; } void gf_webvtt_timestamp_dump(GF_WebVTTTimestamp *ts, FILE *dump, Bool dump_hour)
View file
gpac-2.4.0.tar.gz/src/odf/descriptors.c -> gpac-26.02.0.tar.gz/src/odf/descriptors.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / MPEG-4 ObjectDescriptor sub-project @@ -27,10 +27,7 @@ #include <gpac/constants.h> #include <gpac/avparse.h> - -#ifndef GPAC_DISABLE_AV_PARSERS #include <gpac/internal/media_dev.h> -#endif s32 gf_odf_size_field_size(u32 size_desc) { @@ -108,7 +105,7 @@ newDesc->tag = tag; err = gf_odf_read_descriptor(bs, newDesc, *desc_size); - /*FFMPEG fix*/ + /*FFmpeg fix*/ if ((tag==GF_ODF_SLC_TAG) && (((GF_SLConfig*)newDesc)->predefined==2)) { if (*desc_size==3) { *desc_size = 1; @@ -1266,6 +1263,8 @@ for (j=0; j<nalucount; j++) { GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(ar->nalus, j); + if (!sl) + return GF_ISOM_INVALID_MEDIA; if (!cfg->write_annex_b) { gf_bs_write_int(bs, sl->size, 16); } else { @@ -1406,7 +1405,7 @@ nalucount = gf_bs_read_int(bs, 16); else nalucount = 1; - + for (j=0; j<nalucount; j++) { GF_NALUFFParam *sl; u32 size = gf_bs_read_int(bs, 16); @@ -1624,16 +1623,18 @@ GF_AV1Config *gf_odf_av1_cfg_read_bs_size(GF_BitStream *bs, u32 size) { #ifndef GPAC_DISABLE_AV_PARSERS - AV1State state; + AV1State *av1_state; u8 reserved; GF_AV1Config *cfg; if (!size) size = (u32) gf_bs_available(bs); if (!size) return NULL; + GF_SAFEALLOC(av1_state, AV1State); + if (!av1_state) return NULL; cfg = gf_odf_av1_cfg_new(); - gf_av1_init_state(&state); - state.config = cfg; + gf_av1_init_state(av1_state); + av1_state->config = cfg; cfg->marker = gf_bs_read_int(bs, 1); cfg->version = gf_bs_read_int(bs, 7); @@ -1658,12 +1659,12 @@ size -= 4; if (reserved != 0 || cfg->marker != 1 || cfg->version != 1) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 wrong av1C reserved %d / marker %d / version %d expecting 0 1 1\n", reserved, cfg->marker, cfg->version)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 av1C: wrong reserved %d / marker %d / version %d expecting 0 1 1\n", reserved, cfg->marker, cfg->version)); gf_odf_av1_cfg_del(cfg); + gf_free(av1_state); return NULL; } - while (size) { u64 pos, obu_size; ObuType obu_type; @@ -1671,15 +1672,15 @@ pos = gf_bs_get_position(bs); obu_size = 0; - if (gf_av1_parse_obu(bs, &obu_type, &obu_size, NULL, &state) != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AV1 could not parse AV1 OBU at position "LLU". Leaving parsing.\n", pos)); + if (gf_av1_parse_obu(bs, &obu_type, &obu_size, NULL, av1_state) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AV1 av1C: could not parse AV1 OBU at position "LLU". Leaving parsing.\n", pos)); break; } gf_assert(obu_size == gf_bs_get_position(bs) - pos); - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 parsed AV1 OBU type=%u size="LLU" at position "LLU".\n", obu_type, obu_size, pos)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 av1C: parsed AV1 OBU type=%u size="LLU" at position "LLU".\n", obu_type, obu_size, pos)); if (!av1_is_obu_header(obu_type)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 AV1 unexpected OBU type=%u size="LLU" found at position "LLU". Forwarding.\n", pos)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("AV1 av1C: AV1 unexpected OBU type=%u size="LLU" found at position "LLU". Forwarding.\n", pos)); } GF_SAFEALLOC(a, GF_AV1_OBUArrayEntry); if (!a) break; @@ -1700,8 +1701,9 @@ } size -= (u32) obu_size; } - gf_av1_reset_state(& state, GF_TRUE); + gf_av1_reset_state(av1_state, GF_TRUE); gf_bs_align(bs); + gf_free(av1_state); return cfg; #else return NULL; @@ -1723,6 +1725,83 @@ return cfg; } + + +GF_EXPORT +GF_AVS3VConfig *gf_odf_avs3v_cfg_new() +{ + GF_AVS3VConfig *cfg; + GF_SAFEALLOC(cfg, GF_AVS3VConfig); + if (!cfg) return NULL; + return cfg; +} + +GF_EXPORT +void gf_odf_avs3v_cfg_del(GF_AVS3VConfig *cfg) +{ + if (!cfg) return; + if (cfg->sequence_header) gf_free(cfg->sequence_header); + gf_free(cfg); +} + +GF_EXPORT +GF_Err gf_odf_avs3v_cfg_write_bs(GF_AVS3VConfig *cfg, GF_BitStream *bs) +{ + gf_bs_write_int(bs, cfg->configurationVersion, 8); + gf_bs_write_int(bs, cfg->sequence_header_length, 16); + gf_bs_write_data(bs, cfg->sequence_header, cfg->sequence_header_length); + gf_bs_write_int(bs, 0xFF, 6); //reserved + gf_bs_write_int(bs, cfg->library_dependency_idc, 2); + + return GF_OK; +} + +GF_EXPORT +GF_Err gf_odf_avs3v_cfg_write(GF_AVS3VConfig *cfg, u8 **outData, u32 *outSize) +{ + GF_Err e; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + *outSize = 0; + *outData = NULL; + e = gf_odf_avs3v_cfg_write_bs(cfg, bs); + if (e==GF_OK) + gf_bs_get_content(bs, outData, outSize); + + gf_bs_del(bs); + return e; +} + +GF_EXPORT +GF_AVS3VConfig *gf_odf_avs3v_cfg_read_bs(GF_BitStream *bs) +{ + GF_AVS3VConfig *cfg = gf_odf_avs3v_cfg_new(); + + cfg->configurationVersion = gf_bs_read_int(bs, 8); + cfg->sequence_header_length = gf_bs_read_int(bs, 16); + cfg->sequence_header = gf_malloc(cfg->sequence_header_length); + if (!cfg->sequence_header) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AVS3 Video Sequence header allocation failed\n")); + gf_odf_avs3v_cfg_del(cfg); + return NULL; + } + gf_bs_read_data(bs, (char *)cfg->sequence_header, (u32)cfg->sequence_header_length); + gf_bs_read_int(bs, 6); //reserved + cfg->library_dependency_idc = gf_bs_read_int(bs, 2); // 6 bits reserved at '1' + 2 bits + + return cfg; +} + +GF_EXPORT +GF_AVS3VConfig *gf_odf_avs3v_cfg_read(u8 *dsi, u32 dsi_size) +{ + GF_BitStream *bs = gf_bs_new(dsi, dsi_size, GF_BITSTREAM_READ); + GF_AVS3VConfig *cfg = gf_odf_avs3v_cfg_read_bs(bs); + gf_bs_del(bs); + return cfg; +} + + +GF_EXPORT GF_DOVIDecoderConfigurationRecord *gf_odf_dovi_cfg_read_bs(GF_BitStream *bs) { GF_DOVIDecoderConfigurationRecord *cfg; @@ -1753,6 +1832,7 @@ gf_free(cfg); } +GF_EXPORT GF_Err gf_odf_dovi_cfg_write_bs(GF_DOVIDecoderConfigurationRecord *cfg, GF_BitStream *bs) { gf_bs_write_u8(bs, cfg->dv_version_major); @@ -1763,15 +1843,16 @@ gf_bs_write_int(bs, cfg->el_present_flag, 1); gf_bs_write_int(bs, cfg->bl_present_flag, 1); gf_bs_write_int(bs, cfg->dv_bl_signal_compatibility_id, 4); - gf_bs_write_int(bs, 0, 28); - gf_bs_write_u32(bs, 0); - gf_bs_write_u32(bs, 0); - gf_bs_write_u32(bs, 0); - gf_bs_write_u32(bs, 0); + gf_bs_write_int(bs, 0, 28); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); return GF_OK; } +GF_EXPORT GF_Err gf_odf_ac3_cfg_write_bs(GF_AC3Config *cfg, GF_BitStream *bs) { if (!cfg || !bs) return GF_BAD_PARAM; @@ -1806,6 +1887,7 @@ return GF_OK; } +GF_EXPORT GF_Err gf_odf_ac3_cfg_write(GF_AC3Config *cfg, u8 **data, u32 *size) { GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -1822,7 +1904,8 @@ return e; } -GF_Err gf_odf_ac3_config_parse_bs(GF_BitStream *bs, Bool is_ec3, GF_AC3Config *cfg) +GF_EXPORT +GF_Err gf_odf_ac3_cfg_parse_bs(GF_BitStream *bs, Bool is_ec3, GF_AC3Config *cfg) { if (!cfg || !bs) return GF_BAD_PARAM; memset(cfg, 0, sizeof(GF_AC3Config)); @@ -1861,13 +1944,14 @@ return GF_OK; } -GF_Err gf_odf_ac3_config_parse(u8 *dsi, u32 dsi_len, Bool is_ec3, GF_AC3Config *cfg) +GF_EXPORT +GF_Err gf_odf_ac3_cfg_parse(u8 *dsi, u32 dsi_len, Bool is_ec3, GF_AC3Config *cfg) { GF_BitStream *bs; GF_Err e; if (!cfg || !dsi) return GF_BAD_PARAM; bs = gf_bs_new(dsi, dsi_len, GF_BITSTREAM_READ); - e = gf_odf_ac3_config_parse_bs(bs, is_ec3, cfg); + e = gf_odf_ac3_cfg_parse_bs(bs, is_ec3, cfg); if (is_ec3 && gf_bs_available(bs)>=2) { gf_bs_read_int(bs, 7); cfg->atmos_ec3_ext = gf_bs_read_int(bs, 1); @@ -1877,7 +1961,614 @@ return e; } +#define GF_AC4_SSS(bs, value, nbits, size, mode) { \ + if (mode == GF_AC4_DESCMODE_PARSE) value = gf_bs_read_int(bs, nbits); \ + else if (mode == GF_AC4_DESCMODE_WRITE) gf_bs_write_int(bs, value, nbits); \ + else if (mode == GF_AC4_DESCMODE_GETSIZE) *size += nbits;} + +#define GF_AC4_ALLIGN(bs, size, mode) { \ + if (mode == GF_AC4_DESCMODE_PARSE) gf_bs_align(bs); \ + else if (mode == GF_AC4_DESCMODE_WRITE) gf_bs_align(bs); \ + else if (mode == GF_AC4_DESCMODE_GETSIZE && (*size % 8 != 0)) *size = ((*size / 8) + 1) * 8;} + +GF_Err gf_odf_ac4_cfg_alternative_info(GF_AC4AlternativeInfo *info, GF_BitStream *bs, u64 *size, u8 desc_mode) +{ + u32 i; + + GF_AC4_SSS(bs, info->name_len, 16, size, desc_mode); + + if (info->name_len >= GF_ARRAY_LENGTH(info->presentation_name)) + return GF_ISOM_INVALID_MEDIA; + + for (i = 0; i < info->name_len; i++) { + GF_AC4_SSS(bs, info->presentation_namei, 8, size, desc_mode); + } + + GF_AC4_SSS(bs, info->n_targets, 5, size, desc_mode); + + if (info->n_targets >= MIN(GF_ARRAY_LENGTH(info->target_md_compat), GF_ARRAY_LENGTH(info->target_device_category))) + return GF_ISOM_INVALID_MEDIA; + + for (i = 0; i < info->n_targets; i++) { + GF_AC4_SSS(bs, info->target_md_compati, 3, size, desc_mode); + GF_AC4_SSS(bs, info->target_device_categoryi, 8, size, desc_mode); + } + return GF_OK; +} + +GF_Err gf_odf_ac4_cfg_substream_dsi(GF_AC4SubStream *s, GF_BitStream *bs, u8 b_channel_coded, u64 *size, u8 desc_mode) +{ + u32 zero_val = 0; + + GF_AC4_SSS(bs, s->dsi_sf_multiplier, 2, size, desc_mode); + GF_AC4_SSS(bs, s->b_substream_bitrate_indicator, 1, size, desc_mode); + if (s->b_substream_bitrate_indicator == 1) { + GF_AC4_SSS(bs, s->substream_bitrate_indicator, 5, size, desc_mode); + } + if (b_channel_coded == 1) { + GF_AC4_SSS(bs, s->dsi_substream_channel_mask, 24, size, desc_mode); + } else { + GF_AC4_SSS(bs, s->b_ajoc, 1, size, desc_mode); + if (s->b_ajoc == 1) { + GF_AC4_SSS(bs, s->b_static_dmx, 1, size, desc_mode); + if (s->b_static_dmx == 0) { + GF_AC4_SSS(bs, s->n_dmx_objects_minus1, 4, size, desc_mode); + } + GF_AC4_SSS(bs, s->n_umx_objects_minus1, 6, size, desc_mode); + } + GF_AC4_SSS(bs, s->b_substream_contains_bed_objects, 1, size, desc_mode); + GF_AC4_SSS(bs, s->b_substream_contains_dynamic_objects, 1, size, desc_mode); + GF_AC4_SSS(bs, s->b_substream_contains_ISF_objects, 1, size, desc_mode); + GF_AC4_SSS(bs, zero_val, 1, size, desc_mode); //reserved bit + } + return GF_OK; +} + +GF_Err gf_odf_ac4_cfg_content_type(GF_AC4SubStreamGroupV1 *g, GF_BitStream *bs, u64 *size, u8 desc_mode) +{ + u32 i; + + GF_AC4_SSS(bs, g->b_content_type, 1, size, desc_mode); + if (g->b_content_type == 1){ + GF_AC4_SSS(bs, g->content_classifier, 3, size, desc_mode); + GF_AC4_SSS(bs, g->b_language_indicator, 1, size, desc_mode); + if (g->b_language_indicator == 1){ + GF_AC4_SSS(bs, g->n_language_tag_bytes, 6, size, desc_mode); + for (i = 0; i < g->n_language_tag_bytes; i++ ){ + GF_AC4_SSS(bs, g->language_tag_bytesi, 8, size, desc_mode); + } + } + } + return GF_OK; +} + +GF_Err gf_odf_ac4_cfg_substream_group_dsi(GF_AC4SubStreamGroupV1 *g, GF_BitStream *bs, u64 *size, u8 desc_mode) +{ + u32 i; + GF_AC4SubStream *s; + if (!g) + return GF_BAD_PARAM; + + GF_AC4_SSS(bs, g->b_substreams_present, 1, size, desc_mode); + GF_AC4_SSS(bs, g->b_hsf_ext, 1, size, desc_mode); + GF_AC4_SSS(bs, g->b_channel_coded, 1, size, desc_mode); + GF_AC4_SSS(bs, g->n_lf_substreams, 8, size, desc_mode); + + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + g->substreams = gf_list_new(); + } + for (i = 0; i < g->n_lf_substreams; i++ ){ + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + GF_SAFEALLOC(s, GF_AC4SubStream); + } else { // write or get_size + s = (GF_AC4SubStream*)gf_list_get(g->substreams, i); + } + gf_odf_ac4_cfg_substream_dsi(s, bs, g->b_channel_coded, size, desc_mode); + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + gf_list_add(g->substreams, s); + } + } + gf_odf_ac4_cfg_content_type(g, bs, size, desc_mode); + return GF_OK; +} + +GF_Err gf_odf_ac4_cfg_bitrate_dsi(GF_AC4BitrateDsi *bitr, GF_BitStream *bs, u64 *size, u8 desc_mode) +{ + GF_AC4_SSS(bs, bitr->bit_rate_mode, 2, size, desc_mode); + GF_AC4_SSS(bs, bitr->bit_rate, 32, size, desc_mode); + GF_AC4_SSS(bs, bitr->bit_rate_precision, 32, size, desc_mode); + return GF_OK; +} + +GF_Err gf_odf_ac4_cfg_presentation_v1_dsi(GF_AC4PresentationV1 *p, GF_BitStream *bs, u64 *size, u8 desc_mode) +{ + GF_AC4SubStreamGroupV1 *g; + u32 i, zero_val = 0; + + GF_AC4_SSS(bs, p->presentation_config, 5, size, desc_mode); + if (p->presentation_config == 0x06) { + p->b_add_emdf_substreams = 1; + } else { + GF_AC4_SSS(bs, p->mdcompat, 3, size, desc_mode); + GF_AC4_SSS(bs, p->b_presentation_id, 1, size, desc_mode); + if (p->b_presentation_id) { + GF_AC4_SSS(bs, p->presentation_id, 5, size, desc_mode); + } + GF_AC4_SSS(bs, p->dsi_frame_rate_multiply_info, 2, size, desc_mode); + GF_AC4_SSS(bs, p->dsi_frame_rate_fraction_info, 2, size, desc_mode); + GF_AC4_SSS(bs, p->presentation_emdf_version, 5, size, desc_mode); + GF_AC4_SSS(bs, p->presentation_key_id, 10, size, desc_mode); + + GF_AC4_SSS(bs, p->b_presentation_channel_coded, 1, size, desc_mode); + if (p->b_presentation_channel_coded) { + GF_AC4_SSS(bs, p->dsi_presentation_ch_mode, 5, size, desc_mode); + if (p->dsi_presentation_ch_mode >= 11 && p->dsi_presentation_ch_mode <= 14) { + GF_AC4_SSS(bs, p->pres_b_4_back_channels_present, 1, size, desc_mode); + GF_AC4_SSS(bs, p->pres_top_channel_pairs, 2, size, desc_mode); + } + GF_AC4_SSS(bs, p->presentation_channel_mask_v1, 24, size, desc_mode); + } + + GF_AC4_SSS(bs, p->b_presentation_core_differs, 1, size, desc_mode); + if (p->b_presentation_core_differs) { + GF_AC4_SSS(bs, p->b_presentation_core_channel_coded, 1, size, desc_mode); + if (p->b_presentation_core_channel_coded) { + GF_AC4_SSS(bs, p->dsi_presentation_channel_mode_core, 2, size, desc_mode); + } + } + GF_AC4_SSS(bs, p->b_presentation_filter, 1, size, desc_mode); + if (p->b_presentation_filter) { + GF_AC4_SSS(bs, p->b_enable_presentation, 1, size, desc_mode); + GF_AC4_SSS(bs, p->n_filter_bytes, 8, size, desc_mode); + for (i = 0; i < p->n_filter_bytes; i++) { + GF_AC4_SSS(bs, zero_val, 8, size, desc_mode); // filter_data + } + } + // calloc memory for substream_groups + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + p->substream_groups = gf_list_new(); + } + + if (p->presentation_config == 0x1f) { + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + GF_SAFEALLOC(g, GF_AC4SubStreamGroupV1); + gf_list_add(p->substream_groups, g); + p->n_substream_groups = 1; + } else { // write or get_size + g = (GF_AC4SubStreamGroupV1*)gf_list_get(p->substream_groups, 0); + } + if (g) + gf_odf_ac4_cfg_substream_group_dsi(g, bs, size, desc_mode); + } + else { + GF_AC4_SSS(bs, p->b_multi_pid, 1, size, desc_mode); + if (p->presentation_config >= 0 && p->presentation_config <= 2) { + p->n_substream_groups = 2; + } + if (p->presentation_config == 3 || p->presentation_config == 4) { + p->n_substream_groups = 3; + } + + if (p->presentation_config == 5) { + // n_substream_groups_minus2 + if (desc_mode == GF_AC4_DESCMODE_PARSE) + p->n_substream_groups = gf_bs_read_int(bs, 3) + 2; + else if (desc_mode == GF_AC4_DESCMODE_WRITE) + gf_bs_write_int(bs, p->n_substream_groups - 2, 3); + else if(desc_mode == GF_AC4_DESCMODE_GETSIZE) + *size += 3; + } + + for (i = 0; i < p->n_substream_groups; i++) { + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + GF_SAFEALLOC(g, GF_AC4SubStreamGroupV1); + gf_list_add(p->substream_groups, g); + } else { // write or get_size + g = (GF_AC4SubStreamGroupV1*)gf_list_get(p->substream_groups, i); + } + gf_odf_ac4_cfg_substream_group_dsi(g, bs, size, desc_mode); + } + + if (p->presentation_config > 5) { + GF_AC4_SSS(bs, p->n_skip_bytes, 7, size, desc_mode); + for (i = 0; i < p->n_skip_bytes; i++) { + GF_AC4_SSS(bs, zero_val, 8, size, desc_mode); // skip_data + } + } + } + GF_AC4_SSS(bs, p->b_pre_virtualized, 1, size, desc_mode); + GF_AC4_SSS(bs, p->b_add_emdf_substreams, 1, size, desc_mode); + } + if (p->b_add_emdf_substreams) { + GF_AC4_SSS(bs, p->n_add_emdf_substreams, 7, size, desc_mode); + for (i = 0; i < p->n_add_emdf_substreams && i < GF_ARRAY_LENGTH(p->substream_emdf_version) && i < GF_ARRAY_LENGTH(p->substream_key_id); i++) { + GF_AC4_SSS(bs, p->substream_emdf_versioni, 5, size, desc_mode); + GF_AC4_SSS(bs, p->substream_key_idi, 10, size, desc_mode); + } + } + GF_AC4_SSS(bs, p->b_presentation_bitrate_info, 1, size, desc_mode); + if (p->b_presentation_bitrate_info) { + gf_odf_ac4_cfg_bitrate_dsi(&(p->ac4_bitrate_dsi), bs, size, desc_mode); + } + GF_AC4_SSS(bs, p->b_alternative, 1, size, desc_mode); + if (p->b_alternative) { + GF_AC4_ALLIGN(bs, size, desc_mode); + gf_odf_ac4_cfg_alternative_info(&(p->alternative_info), bs, size, desc_mode); + } + GF_AC4_ALLIGN(bs, size, desc_mode); + /* + * TODO: Not implement, need the information from ac4_substream. + * Currently just set the value to 1 according to Dolby's internal discussion. + */ + p->de_indicator = 1; + GF_AC4_SSS(bs, p->de_indicator, 1, size, desc_mode); + GF_AC4_SSS(bs, p->dolby_atmos_indicator, 1, size, desc_mode); + GF_AC4_SSS(bs, zero_val, 4, size, desc_mode); + + if (p->presentation_id > 31) { + p->b_extended_presentation_id = 1; + p->extended_presentation_id = p->presentation_id; + } + GF_AC4_SSS(bs, p->b_extended_presentation_id, 1, size, desc_mode); + if (p->b_extended_presentation_id) { + GF_AC4_SSS(bs, p->extended_presentation_id, 9, size, desc_mode); + } + else { + GF_AC4_SSS(bs, zero_val, 1, size, desc_mode); + } + + return GF_OK; +} + +static void gf_odf_ac4_presentation_deep_copy(GF_AC4PresentationV1 *pres_dst, GF_AC4PresentationV1 *pres_src); + +GF_Err gf_odf_ac4_cfg_dsi_v1(GF_AC4StreamInfo *dsi, GF_BitStream *bs, u64 *size, u8 desc_mode) +{ + u32 i, j, add_pres_bytes, presentation_bytes, skip_bytes, ims_pres_num = 0, legacy_pres_num = 0; + u32 pres_bytes = 0, t_size_bytes = 0; + GF_AC4PresentationV1* p = NULL, *imsp = NULL; + u64 pos, t_size_bits = 0; + u8 *t_data = NULL; + GF_BitStream *t_bs; + + if (!dsi) + return GF_BAD_PARAM; + + GF_AC4_SSS(bs, dsi->ac4_dsi_version, 3, size, desc_mode); + GF_AC4_SSS(bs, dsi->bitstream_version, 7, size, desc_mode); + GF_AC4_SSS(bs, dsi->fs_index, 1, size, desc_mode); + GF_AC4_SSS(bs, dsi->frame_rate_index, 4, size, desc_mode); + + if (desc_mode == GF_AC4_DESCMODE_WRITE) { + // check whether legacy presentations are added in the presentations + for (i = 0; i < dsi->n_presentations; i++) { + p = gf_list_get(dsi->presentations, i); + if (!p) continue; + if (p->presentation_version == 1) { + legacy_pres_num += 1; + } else if (p->presentation_version == 2) { + ims_pres_num += 1; + } + } + + // In WRITE mode, modify n_presentations for IMS content and add legacy presentations + // For more information, please read Dolby AC-4 Online Delivery Kit - Signaling immersive stereo content + if (legacy_pres_num == 0 && ims_pres_num > 0) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("AC4 This is a Dolby AC-4 bitstreams signal immersive stereo content.\n")); + + for (i = 0; i < dsi->n_presentations; i++) { + p = gf_list_get(dsi->presentations, i); + if (!p) continue; + if (p->presentation_version == 2) { + GF_SAFEALLOC(imsp, GF_AC4PresentationV1); + gf_odf_ac4_presentation_deep_copy(imsp, p); + + imsp->presentation_version = 1; + imsp->b_pre_virtualized = 0; + imsp->dolby_atmos_indicator = 0; + gf_list_add(dsi->presentations, imsp); + } + } + dsi->n_presentations += ims_pres_num; + } + } + + GF_AC4_SSS(bs, dsi->n_presentations, 9, size, desc_mode); + if (dsi->bitstream_version > 1) { + GF_AC4_SSS(bs, dsi->b_program_id, 1, size, desc_mode); + if (dsi->b_program_id) { + GF_AC4_SSS(bs, dsi->short_program_id, 16, size, desc_mode); + GF_AC4_SSS(bs, dsi->b_uuid, 1, size, desc_mode); + if (dsi->b_uuid) { + for (i = 0; i < 16; i++) + GF_AC4_SSS(bs, dsi->program_uuidi, 8, size, desc_mode); + } + } + } + + // ac4_bitrate_dsi + gf_odf_ac4_cfg_bitrate_dsi(&dsi->ac4_bitrate_dsi, bs, size, desc_mode); + GF_AC4_ALLIGN(bs, size, desc_mode); + + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + dsi->presentations = gf_list_new(); + } + + for (i = 0; i < dsi->n_presentations; i++) { + if (desc_mode == GF_AC4_DESCMODE_PARSE) { + GF_SAFEALLOC(p, GF_AC4PresentationV1); + gf_list_add(dsi->presentations, p); + + p->presentation_version = gf_bs_read_int(bs, 8); + pres_bytes = gf_bs_read_int(bs, 8); + if (pres_bytes == 255) { + add_pres_bytes = gf_bs_read_int(bs, 16); + pres_bytes += add_pres_bytes; + } + + pos = gf_bs_get_position(bs); + + if (p->presentation_version == 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("AC4 Don't support presentation_version 0.\n")); + } else if (p->presentation_version == 1) { + gf_odf_ac4_cfg_presentation_v1_dsi(p, bs, size, desc_mode); + } else if (p->presentation_version == 2) { + gf_odf_ac4_cfg_presentation_v1_dsi(p, bs, size, desc_mode); + } + + presentation_bytes = (u32) (gf_bs_get_position(bs) - pos); + skip_bytes = pres_bytes - presentation_bytes; + + for (j = 0; j < skip_bytes && gf_bs_available(bs); j++) { + gf_bs_read_int(bs, 8); + } + } + else if (desc_mode == GF_AC4_DESCMODE_WRITE) { + p = gf_list_get(dsi->presentations, i); + if (!p) continue; + + t_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (p->presentation_version == 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("AC4 Don't support presentation_version 0.\n")); + } else if (p->presentation_version == 1 || p->presentation_version == 2) { + gf_odf_ac4_cfg_presentation_v1_dsi(p, t_bs, size, desc_mode); + } + + // write into output bitstream + gf_bs_write_int(bs, p->presentation_version, 8); + presentation_bytes = (u32) gf_bs_get_position(t_bs); + if (presentation_bytes < 255) { + gf_bs_write_int(bs, presentation_bytes, 8); + } else { + gf_bs_write_int(bs, 255, 8); + gf_bs_write_int(bs, presentation_bytes - 255, 16); + } + gf_bs_get_content(t_bs, &t_data, &t_size_bytes); + gf_bs_write_data(bs, t_data, t_size_bytes); + + gf_bs_del(t_bs); + gf_free(t_data); + } + else if (desc_mode == GF_AC4_DESCMODE_GETSIZE) { + p = gf_list_get(dsi->presentations, i); + + t_size_bits = 0; // t_size in bits + if (p->presentation_version == 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("AC4 Don't support presentation_version 0.\n")); + return GF_OK; + } else if (p->presentation_version == 1 || p->presentation_version == 2) { + gf_odf_ac4_cfg_presentation_v1_dsi(p, bs, &t_size_bits, desc_mode); + } + + if (t_size_bits < 255 * 8) { + *size += (8 + 8 + t_size_bits); + } else { + *size += (8 + 8 + 16 + t_size_bits); + } + } + } + + return GF_OK; +} + +GF_EXPORT +GF_Err gf_odf_ac4_cfg_write_bs(GF_AC4Config *cfg, GF_BitStream *bs) +{ + if (!cfg || !bs) return GF_BAD_PARAM; + GF_AC4StreamInfo* dsi = &cfg->stream; + if (dsi->ac4_dsi_version == 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("AC4 Don't support ac4_dsi_version 0.\n")); + return GF_OK; + } + GF_Err e = gf_odf_ac4_cfg_dsi_v1(dsi, bs, NULL, GF_AC4_DESCMODE_WRITE); + return e; +} + +GF_EXPORT +GF_Err gf_odf_ac4_cfg_write(GF_AC4Config *cfg, u8 **data, u32 *size) +{ + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + GF_Err e = gf_odf_ac4_cfg_write_bs(cfg, bs); + gf_bs_get_content(bs, data, size); + gf_bs_del(bs); + return e; +} + +GF_EXPORT +GF_Err gf_odf_ac4_cfg_parse_bs(GF_BitStream *bs, GF_AC4Config *cfg) +{ + GF_AC4StreamInfo* dsi = &cfg->stream; + u64 pos = gf_bs_get_position(bs); + dsi->ac4_dsi_version = gf_bs_read_int(bs, 3); + if (dsi->ac4_dsi_version == 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("AC4 Don't support ac4_dsi_version 0.\n")); + return GF_OK; + } + gf_bs_seek(bs, pos); + GF_Err e = gf_odf_ac4_cfg_dsi_v1(dsi, bs, NULL, GF_AC4_DESCMODE_PARSE); + if (e) return e; + cfg->sample_rate = dsi->fs_index ? 48000 : 44100; + return e; +} + +GF_EXPORT +GF_Err gf_odf_ac4_cfg_parse(u8 *dsi, u32 dsi_len, GF_AC4Config *cfg) +{ + GF_BitStream *bs; + GF_Err e; + if (!cfg || !dsi) return GF_BAD_PARAM; + bs = gf_bs_new(dsi, dsi_len, GF_BITSTREAM_READ); + e = gf_odf_ac4_cfg_parse_bs(bs, cfg); + gf_bs_del(bs); + return e; +} + +GF_EXPORT +u64 gf_odf_ac4_cfg_size(GF_AC4Config *cfg) +{ + GF_AC4StreamInfo* dsi = &cfg->stream; + u64 size = 0; + if (dsi->ac4_dsi_version == 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("AC4 Don't support ac4_dsi_version 0.\n")); + return 0; + } else { + gf_odf_ac4_cfg_dsi_v1(dsi, NULL, &size, GF_AC4_DESCMODE_GETSIZE); + } + return size / 8; +} + +GF_EXPORT +void gf_odf_ac4_cfg_deep_copy(GF_AC4Config *dst, GF_AC4Config *src) +{ + u32 i; + GF_List *presentations_src = src->stream.presentations; + GF_AC4PresentationV1 *pres_dst, *pres_src; + + if (!dst || !src) { + return; + } + + memcpy(dst, src, sizeof(GF_AC4Config)); + + if (!src->stream.presentations) { + return; + } + + dst->stream.presentations = gf_list_new(); + for (i = 0; i < gf_list_count(presentations_src); i++) { + pres_src = gf_list_get(presentations_src, i); + + GF_SAFEALLOC(pres_dst, GF_AC4PresentationV1); + gf_odf_ac4_presentation_deep_copy(pres_dst, pres_src); + gf_list_add(dst->stream.presentations, pres_dst); + } +} + +static void gf_odf_ac4_presentation_deep_copy(GF_AC4PresentationV1 *pres_dst, GF_AC4PresentationV1 *pres_src) +{ + u32 j, s; + GF_AC4SubStreamGroupV1 *group_dst, *group_src; + GF_AC4SubStream *subs_dst, *subs_src; + + if (!pres_dst || !pres_src) { + return; + } + + memcpy(pres_dst, pres_src, sizeof(GF_AC4PresentationV1)); + + if (!pres_src->substream_groups) { + return; + } + + pres_dst->substream_groups = gf_list_new(); + for (j = 0; j < gf_list_count(pres_src->substream_groups); j++) { + group_src = gf_list_get(pres_src->substream_groups, j); + + GF_SAFEALLOC(group_dst, GF_AC4SubStreamGroupV1); + memcpy(group_dst, group_src, sizeof(GF_AC4SubStreamGroupV1)); + gf_list_add(pres_dst->substream_groups, group_dst); + + if (!group_src->substreams) { + continue; + } + + group_dst->substreams = gf_list_new(); + for (s = 0; s < gf_list_count(group_src->substreams); s++) { + subs_src = gf_list_get(group_src->substreams, s); + + GF_SAFEALLOC(subs_dst, GF_AC4SubStream); + memcpy(subs_dst, subs_src, sizeof(GF_AC4SubStream)); + gf_list_add(group_dst->substreams, subs_dst); + } + } +} + +GF_EXPORT +void gf_odf_ac4_cfg_clean_list(GF_AC4Config *cfg) +{ + u32 s; + GF_AC4PresentationV1 *pres; + GF_AC4SubStreamGroupV1 *group; + GF_AC4SubStream *subs; + + if (!cfg) + return; + + + if (cfg->stream.presentations) { + + GF_List* groups_to_del = gf_list_new(); + + while ( (pres = gf_list_pop_back(cfg->stream.presentations)) ) { + + if (pres->substream_groups) { + + while ( (group = gf_list_pop_back(pres->substream_groups)) ) { + + if (group && gf_list_find(groups_to_del, group) < 0) + gf_list_add(groups_to_del, group); + + } + gf_list_del(pres->substream_groups); + + } + gf_free(pres); + } + + while ( (group = gf_list_pop_back(groups_to_del)) ) { + if (group->substreams) { + + for (s = 0; s < gf_list_count(group->substreams); s++) { + subs = gf_list_get(group->substreams, s); + if (!subs) { + continue; + } + + gf_free(subs); + } + gf_list_del(group->substreams); + + } + gf_free(group); + + } + gf_list_del(groups_to_del); + + gf_list_del(cfg->stream.presentations); + cfg->stream.presentations = NULL; + } +} + +GF_EXPORT +void gf_odf_ac4_cfg_del(GF_AC4Config *cfg) +{ + if (!cfg) return; + gf_odf_ac4_cfg_clean_list(cfg); + gf_free(cfg); +} + +GF_EXPORT GF_Err gf_odf_opus_cfg_parse_bs(GF_BitStream *bs, GF_OpusConfig *cfg) { memset(cfg, 0, sizeof(GF_OpusConfig)); @@ -1894,6 +2585,8 @@ } return GF_OK; } + +GF_EXPORT GF_Err gf_odf_opus_cfg_parse(u8 *dsi, u32 dsi_len, GF_OpusConfig *cfg) { GF_BitStream *bs; @@ -1905,6 +2598,7 @@ return e; } +GF_EXPORT GF_Err gf_odf_opus_cfg_write_bs(GF_OpusConfig *cfg, GF_BitStream *bs) { if (!cfg || !bs) return GF_BAD_PARAM; @@ -1922,6 +2616,7 @@ return GF_OK; } +GF_EXPORT GF_Err gf_odf_opus_cfg_write(GF_OpusConfig *cfg, u8 **data, u32 *size) { GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -1931,3 +2626,167 @@ gf_bs_del(bs); return e; } + +GF_EXPORT +GF_IAConfig *gf_odf_iamf_cfg_new() +{ + GF_IAConfig *cfg = NULL; + GF_SAFEALLOC(cfg, GF_IAConfig); + if (!cfg) return NULL; + cfg->configurationVersion = 1; + cfg->configOBUs_size = 0; + cfg->configOBUs = gf_list_new(); + if (!cfg->configOBUs) { + gf_free(cfg); + return NULL; + } + return cfg; +} + +GF_EXPORT +GF_IAConfig *gf_odf_iamf_cfg_read_bs_size(GF_BitStream *bs, u32 size) { +#ifndef GPAC_DISABLE_AV_PARSERS + IAMFState *state = NULL; + GF_IAConfig *cfg = NULL; + u8 leb128_size; + + if (!size) size = (u32) gf_bs_available(bs); + if (!size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF Unknown IAConfigurationBox size to read\n")); + return NULL; + } + + GF_SAFEALLOC(state, IAMFState); + if (!state) return NULL; + cfg = gf_odf_iamf_cfg_new(); + gf_iamf_init_state(state); + + cfg->configurationVersion = gf_bs_read_u8(bs); + if (cfg->configurationVersion != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF Unknown configurationVersion %d\n", cfg->configurationVersion)); + gf_odf_iamf_cfg_del(cfg); + gf_free(state); + return NULL; + } + size--; + + cfg->configOBUs_size = (u32)gf_av1_leb128_read(bs, &leb128_size); + size -= leb128_size; + + while(size) { + u64 pos, obu_size; + IamfObuType obu_type; + GF_IamfObu *config_obu; + + pos = gf_bs_get_position(bs); + obu_size = 0; + if (gf_iamf_parse_obu(bs, &obu_type, &obu_size, state) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("IAMF could not parse configOBUs at position "LLU". Leaving parsing.\n", pos)); + break; + } + gf_assert(obu_size == gf_bs_get_position(bs) - pos); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("IAMF parsed OBU type=%u size="LLU" at position "LLU".\n", obu_type, obu_size, pos)); + + GF_SAFEALLOC(config_obu, GF_IamfObu); + if (!config_obu) break; + config_obu->raw_obu_bytes = gf_malloc((size_t)obu_size); + if (!config_obu->raw_obu_bytes) { + gf_free(config_obu); + break; + } + gf_bs_seek(bs, pos); + gf_bs_read_data(bs, (char *)config_obu->raw_obu_bytes, (u32)obu_size); + config_obu->obu_length = obu_size; + config_obu->obu_type = obu_type; + gf_list_add(cfg->configOBUs, config_obu); + + if (size < obu_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("IAMF IAMF config misses %d bytes to fit the entire OBU\n", obu_size - size)); + break; + } + size -= (u32) obu_size; + } + + gf_iamf_reset_state(state, GF_TRUE); + gf_bs_align(bs); + gf_free(state); + return cfg; +#else + return NULL; +#endif +} + +GF_EXPORT +GF_IAConfig *gf_odf_iamf_cfg_read_bs(GF_BitStream *bs) { + return gf_odf_iamf_cfg_read_bs_size(bs, 0); +} + +GF_EXPORT +GF_IAConfig *gf_odf_iamf_cfg_read(u8 *dsi, u32 dsi_size) +{ + GF_BitStream *bs = gf_bs_new(dsi, dsi_size, GF_BITSTREAM_READ); + GF_IAConfig *cfg = gf_odf_iamf_cfg_read_bs(bs); + gf_bs_del(bs); + return cfg; +} + +GF_EXPORT +void gf_odf_iamf_cfg_del(GF_IAConfig *cfg) +{ + if (!cfg) return; + while (gf_list_count(cfg->configOBUs)) { + GF_IamfObu *configOBU = (GF_IamfObu*)gf_list_get(cfg->configOBUs, 0); + if (configOBU->raw_obu_bytes) gf_free(configOBU->raw_obu_bytes); + gf_list_rem(cfg->configOBUs, 0); + gf_free(configOBU); + } + gf_list_del(cfg->configOBUs); + gf_free(cfg); +} + +GF_EXPORT +GF_Err gf_odf_iamf_cfg_write_bs(GF_IAConfig *cfg, GF_BitStream *bs) +{ + u32 i; + if (!cfg || !bs) return GF_BAD_PARAM; + + #ifndef GPAC_DISABLE_AV_PARSERS + gf_bs_write_u8(bs, cfg->configurationVersion); + gf_av1_leb128_write(bs, cfg->configOBUs_size); + for (i = 0; i < gf_list_count(cfg->configOBUs); ++i) { + GF_IamfObu *configOBU = gf_list_get(cfg->configOBUs, i); + gf_bs_write_data(bs, configOBU->raw_obu_bytes, (u32)configOBU->obu_length); + } + #endif + + return GF_OK; +} + +GF_EXPORT +GF_Err gf_odf_iamf_cfg_write(GF_IAConfig *cfg, u8 **outData, u32 *outSize) { + GF_Err e; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + *outSize = 0; + *outData = NULL; + e = gf_odf_iamf_cfg_write_bs(cfg, bs); + if (e == GF_OK) + gf_bs_get_content(bs, outData, outSize); + + gf_bs_del(bs); + return e; +} + +GF_EXPORT +u32 gf_odf_iamf_cfg_size(GF_IAConfig *cfg) +{ + if (!cfg) return 0; + + #ifndef GPAC_DISABLE_AV_PARSERS + u32 cfg_size = 1; // configurationVersion + cfg_size += gf_av1_leb128_size(cfg->configOBUs_size); + cfg_size += cfg->configOBUs_size; + return cfg_size; + #else + return 0; + #endif +}
View file
gpac-2.4.0.tar.gz/src/odf/odf_code.c -> gpac-26.02.0.tar.gz/src/odf/odf_code.c
Changed
@@ -232,8 +232,7 @@ (desc->tag <= GF_ODF_EXT_END_TAG) ) { return gf_list_add(esd->extensionDescriptors, desc); } - gf_odf_delete_descriptor(desc); - return GF_OK; + return GF_BAD_PARAM; } return GF_OK; @@ -1222,7 +1221,7 @@ GF_Err e; u32 size, oti; if (! dcd) return GF_BAD_PARAM; - + oti = dcd->objectTypeIndication; if (oti > 0xFF) { oti = gf_codecid_oti(oti);
View file
gpac-2.4.0.tar.gz/src/odf/slc.c -> gpac-26.02.0.tar.gz/src/odf/slc.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / MPEG-4 ObjectDescriptor sub-project @@ -166,6 +166,8 @@ e = gf_odf_slc_set_pref(sl); if (e) return e; } else { + if (gf_bs_available(bs) < 15) return GF_ODF_INVALID_DESCRIPTOR; + sl->useAccessUnitStartFlag = gf_bs_read_int(bs, 1); sl->useAccessUnitEndFlag = gf_bs_read_int(bs, 1); sl->useRandomAccessPointFlag = gf_bs_read_int(bs, 1); @@ -179,13 +181,10 @@ sl->timestampLength = gf_bs_read_int(bs, 8); if (sl->timestampLength > 64) return GF_ODF_INVALID_DESCRIPTOR; - sl->OCRLength = gf_bs_read_int(bs, 8); if (sl->OCRLength > 64) return GF_ODF_INVALID_DESCRIPTOR; - sl->AULength = gf_bs_read_int(bs, 8); if (sl->AULength > 32) return GF_ODF_INVALID_DESCRIPTOR; - sl->instantBitrateLength = gf_bs_read_int(bs, 8); sl->degradationPriorityLength = gf_bs_read_int(bs, 4); sl->AUSeqNumLength = gf_bs_read_int(bs, 5); @@ -374,7 +373,7 @@ GF_EXPORT -void gf_sl_depacketize (GF_SLConfig *slConfig, GF_SLHeader *Header, const u8 *PDU, u32 PDULength, u32 *HeaderLen) +void gf_odf_sl_depacketize (GF_SLConfig *slConfig, GF_SLHeader *Header, const u8 *PDU, u32 PDULength, u32 *HeaderLen) { GF_BitStream *bs; *HeaderLen = 0;
View file
gpac-2.4.0.tar.gz/src/quickjs/GPAC_README.md -> gpac-26.02.0.tar.gz/src/quickjs/GPAC_README.md
Changed
@@ -1,13 +1,13 @@ # QuickJS in GPAC -Base version is quickjs-2021-03-27 : - https://github.com/bellard/quickjs/commit/b5e62895c619d4ffc75c9d822c8d85f1ece77e5b +Base version is quickjs-2025-10-15 : + https://github.com/bellard/quickjs/commit/eb2c89087def1829ed99630cb14b549d7a98408c ## Modifications to QuickJS - define CONFIG_VERSION in QuickJS.c - patched for MSVC compil, based on https://github.com/horhof/quickjs/compare/master...samhocevar:task/msvc-support - do not define CONFIG_STACK_CHECK (stack checking breaks multithread support) -- patched for JS_GetOpaque_Nocheck (get opaque data without class_id check, needed in some places in gpac due to original SpiderMonkey design) - patched for exporting JS_AtomIsArrayIndex and JS_IsArrayBuffer +- patched for switch classID on object (for webGL named textures in GPAC) ## Modifications to QuickJS-libc ('os' and 'std' modules) - moved from pthread to GF_Thread in 'os' module @@ -16,5 +16,3 @@ - patch js_std_loop to not run forever for main thread - added MSVC support for most 'os' functions (most importantly exec, waitpid and kill) - use gpac module loader -- pacthes for sighandler_t and environ -
View file
gpac-2.4.0.tar.gz/src/quickjs/cutils.c -> gpac-26.02.0.tar.gz/src/quickjs/cutils.c
Changed
@@ -1,6 +1,6 @@ /* * C utilities - * + * * Copyright (c) 2017 Fabrice Bellard * Copyright (c) 2018 Charlie Gordon * @@ -100,15 +100,20 @@ dbuf_init2(s, NULL, NULL); } -/* return < 0 if error */ -int dbuf_realloc(DynBuf *s, size_t new_size) +/* Try to allocate 'len' more bytes. return < 0 if error */ +int dbuf_claim(DynBuf *s, size_t len) { - size_t size; + size_t new_size, size; uint8_t *new_buf; + new_size = s->size + len; + if (new_size < len) + return -1; /* overflow case */ if (new_size > s->allocated_size) { if (s->error) return -1; - size = s->allocated_size * 3 / 2; + size = s->allocated_size + (s->allocated_size / 2); + if (size < s->allocated_size) + return -1; /* overflow case */ if (size > new_size) new_size = size; new_buf = s->realloc_func(s->opaque, s->buf, new_size); @@ -122,33 +127,21 @@ return 0; } -int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len) -{ - size_t end; - end = offset + len; - if (dbuf_realloc(s, end)) - return -1; - memcpy(s->buf + offset, data, len); - if (end > s->size) - s->size = end; - return 0; -} - int dbuf_put(DynBuf *s, const uint8_t *data, size_t len) { - if (unlikely((s->size + len) > s->allocated_size)) { - if (dbuf_realloc(s, s->size + len)) + if (unlikely((s->allocated_size - s->size) < len)) { + if (dbuf_claim(s, len)) return -1; } - memcpy(s->buf + s->size, data, len); + memcpy_no_ub(s->buf + s->size, data, len); s->size += len; return 0; } int dbuf_put_self(DynBuf *s, size_t offset, size_t len) { - if (unlikely((s->size + len) > s->allocated_size)) { - if (dbuf_realloc(s, s->size + len)) + if (unlikely((s->allocated_size - s->size) < len)) { + if (dbuf_claim(s, len)) return -1; } memcpy(s->buf + s->size, s->buf + offset, len); @@ -156,11 +149,26 @@ return 0; } -int dbuf_putc(DynBuf *s, uint8_t c) +int __dbuf_putc(DynBuf *s, uint8_t c) { return dbuf_put(s, &c, 1); } +int __dbuf_put_u16(DynBuf *s, uint16_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 2); +} + +int __dbuf_put_u32(DynBuf *s, uint32_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 4); +} + +int __dbuf_put_u64(DynBuf *s, uint64_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 8); +} + int dbuf_putstr(DynBuf *s, const char *str) { return dbuf_put(s, (const uint8_t *)str, strlen(str)); @@ -176,15 +184,17 @@ va_list ap; char buf128; int len; - + va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + if (len < 0) + return -1; if (len < sizeof(buf)) { /* fast case */ return dbuf_put(s, (uint8_t *)buf, len); } else { - if (dbuf_realloc(s, s->size + len + 1)) + if (dbuf_claim(s, len + 1)) return -1; va_start(ap, fmt); vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size,
View file
gpac-2.4.0.tar.gz/src/quickjs/cutils.h -> gpac-26.02.0.tar.gz/src/quickjs/cutils.h
Changed
@@ -1,6 +1,6 @@ /* * C utilities - * + * * Copyright (c) 2017 Fabrice Bellard * Copyright (c) 2018 Charlie Gordon * @@ -26,14 +26,13 @@ #define CUTILS_H #include <stdlib.h> +#include <string.h> #include <inttypes.h> #if defined(_MSC_VER) #include <intrin.h> #pragma intrinsic(_BitScanReverse) #endif -/* set if CPU is big endian */ -#undef WORDS_BIGENDIAN #if defined(_MSC_VER) #define likely(x) (x) @@ -60,6 +59,16 @@ #ifndef countof #define countof(x) (sizeof(x) / sizeof((x)0)) #endif +#ifndef container_of +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) +#endif + +#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define minimum_length(n) static n +#else +#define minimum_length(n) n +#endif typedef int BOOL; @@ -75,6 +84,12 @@ int strstart(const char *str, const char *val, const char **ptr); int has_suffix(const char *str, const char *suffix); +/* Prevent UB when n == 0 and (src == NULL or dest == NULL) */ +static inline void memcpy_no_ub(void *dest, const void *src, size_t n) { + if (n) + memcpy(dest, src, n); +} + static inline int max_int(int a, int b) { if (a > b) @@ -129,7 +144,7 @@ #if defined(_MSC_VER) unsigned long ret = 0; _BitScanReverse(&ret, a); - return (int)ret; + return 31 - ret; #else return __builtin_clz(a); #endif @@ -266,28 +281,34 @@ *tab = val; } +#ifndef bswap16 static inline uint16_t bswap16(uint16_t x) { return (x >> 8) | (x << 8); } +#endif +#ifndef bswap32 static inline uint32_t bswap32(uint32_t v) { return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); } +#endif +#ifndef bswap64 static inline uint64_t bswap64(uint64_t v) { - return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | - ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | - ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | - ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | - ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | - ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | - ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); } +#endif /* XXX: should take an extra argument to pass slack information to the caller */ typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); @@ -303,24 +324,58 @@ void dbuf_init(DynBuf *s); void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); -int dbuf_realloc(DynBuf *s, size_t new_size); -int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len); +int dbuf_claim(DynBuf *s, size_t len); int dbuf_put(DynBuf *s, const uint8_t *data, size_t len); int dbuf_put_self(DynBuf *s, size_t offset, size_t len); -int dbuf_putc(DynBuf *s, uint8_t c); int dbuf_putstr(DynBuf *s, const char *str); +int __dbuf_putc(DynBuf *s, uint8_t c); +int __dbuf_put_u16(DynBuf *s, uint16_t val); +int __dbuf_put_u32(DynBuf *s, uint32_t val); +int __dbuf_put_u64(DynBuf *s, uint64_t val); + +static inline int dbuf_putc(DynBuf *s, uint8_t val) +{ + if (unlikely((s->allocated_size - s->size) < 1)) { + return __dbuf_putc(s, val); + } else { + s->bufs->size++ = val; + return 0; + } +} + static inline int dbuf_put_u16(DynBuf *s, uint16_t val) { - return dbuf_put(s, (uint8_t *)&val, 2); + if (unlikely((s->allocated_size - s->size) < 2)) { + return __dbuf_put_u16(s, val); + } else { + put_u16(s->buf + s->size, val); + s->size += 2; + return 0; + } } + static inline int dbuf_put_u32(DynBuf *s, uint32_t val) { - return dbuf_put(s, (uint8_t *)&val, 4); + if (unlikely((s->allocated_size - s->size) < 4)) { + return __dbuf_put_u32(s, val); + } else { + put_u32(s->buf + s->size, val); + s->size += 4; + return 0; + } } + static inline int dbuf_put_u64(DynBuf *s, uint64_t val) { - return dbuf_put(s, (uint8_t *)&val, 8); + if (unlikely((s->allocated_size - s->size) < 8)) { + return __dbuf_put_u64(s, val); + } else { + put_u64(s->buf + s->size, val); + s->size += 8; + return 0; + } } + #if defined(_MSC_VER) int dbuf_printf(DynBuf *s, const char *fmt, ...); #else @@ -341,6 +396,36 @@ int unicode_to_utf8(uint8_t *buf, unsigned int c); int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp); +static inline BOOL is_surrogate(uint32_t c) +{ + return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF +} + +static inline BOOL is_hi_surrogate(uint32_t c) +{ + return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF +} + +static inline BOOL is_lo_surrogate(uint32_t c) +{ + return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF +} + +static inline uint32_t get_hi_surrogate(uint32_t c) +{ + return (c >> 10) - (0x10000 >> 10) + 0xD800; +} + +static inline uint32_t get_lo_surrogate(uint32_t c) +{ + return (c & 0x3FF) | 0xDC00; +} + +static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) +{ + return 0x10000 + 0x400 * (hi - 0xD800) + (lo - 0xDC00); +} + static inline int from_hex(int c) { if (c >= '0' && c <= '9') @@ -357,4 +442,113 @@ int (*cmp)(const void *, const void *, void *), void *arg); +static inline uint64_t float64_as_uint64(double d) +{ + union { + double d; + uint64_t u64; + } u; + u.d = d; + return u.u64; +} + +static inline double uint64_as_float64(uint64_t u64) +{ + union { + double d; + uint64_t u64; + } u; + u.u64 = u64; + return u.d; +} + + +//taken from QuickJS-NG, original function is not protable due to 0x1p1008 +#if defined(_MSC_VER) && !defined(__clang__) +#include <math.h> +#define INF INFINITY +#define NEG_INF -INFINITY + +static inline double fromfp16(uint16_t v) { + double d, s; + int e; + if ((v & 0x7C00) == 0x7C00) { + d = (v & 0x3FF) ? NAN : INFINITY; + } + else { + d = (v & 0x3FF) / 1024.; + e = (v & 0x7C00) >> 10; + if (e == 0) { + e = -14; + } + else { + d += 1; + e -= 15; + } + d = scalbn(d, e); + } + s = (v & 0x8000) ? -1.0 : 1.0; + return d * s; +} +#else + +static inline double fromfp16(uint16_t v) +{ + double d; + uint32_t v1; + v1 = v & 0x7fff; + if (unlikely(v1 >= 0x7c00)) + v1 += 0x1f8000; /* NaN or infinity */ + d = uint64_as_float64(((uint64_t)(v >> 15) << 63) | ((uint64_t)v1 << (52 - 10))); + return d * 0x1p1008; +} + +#endif + + +static inline uint16_t tofp16(double d) +{ + uint64_t a, addend; + uint32_t v, sgn; + int shift; + + a = float64_as_uint64(d); + sgn = a >> 63; + a = a & 0x7fffffffffffffff; + if (unlikely(a > 0x7ff0000000000000)) { + /* nan */ + v = 0x7c01; + } else if (a < 0x3f10000000000000) { /* 0x1p-14 */ + /* subnormal f16 number or zero */ + if (a <= 0x3e60000000000000) { /* 0x1p-25 */ + v = 0x0000; /* zero */ + } else { + shift = 1051 - (a >> 52); + a = ((uint64_t)1 << 52) | (a & (((uint64_t)1 << 52) - 1)); + addend = ((a >> shift) & 1) + (((uint64_t)1 << (shift - 1)) - 1); + v = (a + addend) >> shift; + } + } else { + /* normal number or infinity */ + a -= 0x3f00000000000000; /* adjust the exponent */ + /* round */ + addend = ((a >> (52 - 10)) & 1) + (((uint64_t)1 << (52 - 11)) - 1); + v = (a + addend) >> (52 - 10); + /* overflow ? */ + if (unlikely(v > 0x7c00)) + v = 0x7c00; + } + return v | (sgn << 15); +} + +static inline int isfp16nan(uint16_t v) +{ + return (v & 0x7FFF) > 0x7C00; +} + +static inline int isfp16zero(uint16_t v) +{ + return (v & 0x7FFF) == 0; +} + #endif /* CUTILS_H */
View file
gpac-26.02.0.tar.gz/src/quickjs/dtoa.c
Added
@@ -0,0 +1,1622 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <inttypes.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#if !defined(_MSC_VER) +#include <sys/time.h> +#endif +#include <math.h> +#include <setjmp.h> + +#include "cutils.h" +#include "dtoa.h" + +/* + TODO: + - test n_digits=101 instead of 100 + - simplify subnormal handling + - reduce max memory usage + - free format: could add shortcut if exact result + - use 64 bit limb_t when possible + - use another algorithm for free format dtoa in base 10 (ryu ?) +*/ + +#define USE_POW5_TABLE +/* use fast path to print small integers in free format */ +#define USE_FAST_INT + +#define LIMB_LOG2_BITS 5 + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; + +#define LIMB_DIGITS 9 + +#define JS_RADIX_MAX 36 + +#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ +#define MANT_LEN_MAX 18 /* < 36^100 */ + +typedef intptr_t mp_size_t; + +/* the represented number is sum(i, tabi*2^(LIMB_BITS * i)) */ +typedef struct { + int len; /* >= 1 */ + limb_t tab; +} mpb_t; + +static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) +{ + size_t i; + limb_t k, a; + + k=b; + for(i=0;i<n;i++) { + if (k == 0) + break; + a = tabi + k; + k = (a < k); + tabi = a; + } + return k; +} + +/* tabr = taba * b + l. Return the high carry */ +static limb_t mp_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t l) +{ + limb_t i; + dlimb_t t; + + for(i = 0; i < n; i++) { + t = (dlimb_t)tabai * (dlimb_t)b + l; + tabri = t; + l = t >> LIMB_BITS; + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is between + 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | tabai; + tabri = a1 / b; + r = a1 % b; + } + return r; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tabi; + tab_ri = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < + 2^shift. */ +static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t low) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = low; + for(i = 0; i < n; i++) { + a = tabi; + tab_ri = (a << shift) | l; + l = (a >> (LIMB_BITS - shift)); + } + return l; +} + +static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r, limb_t b_inv, int shift) +{ + slimb_t i; + + if (shift != 0) { + r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); + } + for(i = n - 1; i >= 0; i--) { + tabri = udiv1norm(&r, r, tabai, b, b_inv); + } + r >>= shift; + return r; +} + +static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) +{ + int i; + + printf("%s= 0x", str); + for(i = a->len - 1; i >= 0; i--) { + printf("%08x", a->tabi); + if (i != 0) + printf("_"); + } + printf("\n"); +} + +static void mpb_renorm(mpb_t *r) +{ + while (r->len > 1 && r->tabr->len - 1 == 0) + r->len--; +} + +#ifdef USE_POW5_TABLE +static const uint32_t pow5_table17 = { + 0x00000005, 0x00000019, 0x0000007d, 0x00000271, + 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, + 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, + 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, + 0xa2bc2ec5, +}; + +static const uint8_t pow5h_table4 = { + 0x00000001, 0x00000007, 0x00000023, 0x000000b1, +}; + +static const uint32_t pow5_inv_table13 = { + 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, + 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, + 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, + 0xc25c2684, +}; +#endif + +/* return a^b */ +static uint64_t pow_ui(uint32_t a, uint32_t b) +{ + int i, n_bits; + uint64_t r; + if (b == 0) + return 1; + if (b == 1) + return a; +#ifdef USE_POW5_TABLE + if ((a == 5 || a == 10) && b <= 17) { + r = pow5_tableb - 1; + if (b >= 14) { + r |= (uint64_t)pow5h_tableb - 14 << 32; + } + if (a == 10) + r <<= b; + return r; + } +#endif + r = a; + n_bits = 32 - clz32(b); + for(i = n_bits - 2; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + return r; +} + +static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) +{ + uint32_t r_inv, r; + int shift; +#ifdef USE_POW5_TABLE + if (a == 5 && b >= 1 && b <= 13) { + r = pow5_tableb - 1; + shift = clz32(r); + r <<= shift; + r_inv = pow5_inv_tableb - 1; + } else +#endif + { + r = pow_ui(a, b); + shift = clz32(r); + r <<= shift; + r_inv = udiv1norm_init(r); + } + *pshift = shift; + *pr_inv = r_inv; + return r; +} + +enum { + JS_RNDN, /* round to nearest, ties to even */ + JS_RNDNA, /* round to nearest, ties away from zero */ + JS_RNDZ, +}; + +static int mpb_get_bit(const mpb_t *r, int k) +{ + int l; + + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + if (l >= r->len) + return 0; + else + return (r->tabl >> k) & 1; +} + +/* compute round(r / 2^shift). 'shift' can be negative */ +static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) +{ + int l, i; + + if (shift == 0) + return; + if (shift < 0) { + shift = -shift; + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (shift != 0) { + r->tabr->len = mp_shl(r->tab, r->tab, r->len, shift, 0); + r->len++; + mpb_renorm(r); + } + if (l > 0) { + for(i = r->len - 1; i >= 0; i--) + r->tabi + l = r->tabi; + for(i = 0; i < l; i++) + r->tabi = 0; + r->len += l; + } + } else { + limb_t bit1, bit2; + int k, add_one; + + switch(rnd_mode) { + default: + case JS_RNDZ: + add_one = 0; + break; + case JS_RNDN: + case JS_RNDNA: + bit1 = mpb_get_bit(r, shift - 1); + if (bit1) { + if (rnd_mode == JS_RNDNA) { + bit2 = 1; + } else { + /* bit2 = oring of all the bits after bit1 */ + bit2 = 0; + if (shift >= 2) { + k = shift - 1; + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + for(i = 0; i < min_int(l, r->len); i++) + bit2 |= r->tabi; + if (l < r->len) + bit2 |= r->tabl & (((limb_t)1 << k) - 1); + } + } + if (bit2) { + add_one = 1; + } else { + /* round to even */ + add_one = mpb_get_bit(r, shift); + } + } else { + add_one = 0; + } + break; + } + + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (l >= r->len) { + r->len = 1; + r->tab0 = add_one; + } else { + if (l > 0) { + r->len -= l; + for(i = 0; i < r->len; i++) + r->tabi = r->tabi + l; + } + if (shift != 0) { + mp_shr(r->tab, r->tab, r->len, shift, 0); + mpb_renorm(r); + } + if (add_one) { + limb_t a; + a = mp_add_ui(r->tab, 1, r->len); + if (a) + r->tabr->len++ = a; + } + } + } +} + +/* return -1, 0 or 1 */ +static int mpb_cmp(const mpb_t *a, const mpb_t *b) +{ + mp_size_t i; + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + for(i = a->len - 1; i >= 0; i--) { + if (a->tabi != b->tabi) { + if (a->tabi < b->tabi) + return -1; + else + return 1; + } + } + return 0; +} + +static void mpb_set_u64(mpb_t *r, uint64_t m) +{ +#if LIMB_BITS == 64 + r->tab0 = m; + r->len = 1; +#else + r->tab0 = m; + r->tab1 = m >> LIMB_BITS; + if (r->tab1 == 0) + r->len = 1; + else + r->len = 2; +#endif +} + +static uint64_t mpb_get_u64(mpb_t *r) +{ +#if LIMB_BITS == 64 + return r->tab0; +#else + if (r->len == 1) { + return r->tab0; + } else { + return r->tab0 | ((uint64_t)r->tab1 << LIMB_BITS); + } +#endif +} + +/* floor_log2() = position of the first non zero bit or -1 if zero. */ +static int mpb_floor_log2(mpb_t *a) +{ + limb_t v; + v = a->taba->len - 1; + if (v == 0) + return -1; + else + return a->len * LIMB_BITS - 1 - clz32(v); +} + +#define MUL_LOG2_RADIX_BASE_LOG2 24 + +/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ +static const uint32_t mul_log2_radix_tableJS_RADIX_MAX - 1 = { + 0x000000, 0xa1849d, 0x000000, 0x6e40d2, + 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, + 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, + 0x433d00, 0x418677, 0x000000, 0x3ea16b, + 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, + 0x39680b, 0x3897b3, 0x37d5af, 0x372069, + 0x367686, 0x35d6df, 0x354072, 0x34b261, + 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, + 0x3251dd, 0x31e8d6, 0x318465, +}; + +/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ +static int mul_log2_radix(int a, int radix) +{ + int radix_bits, mult; + + if ((radix & (radix - 1)) == 0) { + /* if the radix is a power of two better to do it exactly */ + radix_bits = 31 - clz32(radix); + if (a < 0) + a -= radix_bits - 1; + return a / radix_bits; + } else { + mult = mul_log2_radix_tableradix - 2; + return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; + } +} + +#if 0 +static void build_mul_log2_radix_table(void) +{ + int base, radix, mult, col, base_log2; + + base_log2 = 24; + base = 1 << base_log2; + col = 0; + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) + mult = 0; + else + mult = lrint((double)base / log2(radix)); + printf("0x%06x, ", mult); + if (++col == 4) { + printf("\n"); + col = 0; + } + } + printf("\n"); +} + +static void mul_log2_radix_test(void) +{ + int radix, i, ref, r; + + for(radix = 2; radix <= 36; radix++) { + for(i = -2048; i <= 2047; i++) { + ref = (int)floor((double)i / log2(radix)); + r = mul_log2_radix(i, radix); + if (ref != r) { + printf("ERROR: radix=%d i=%d r=%d ref=%d\n", + radix, i, r, ref); + exit(1); + } + } + } + if (0) + build_mul_log2_radix_table(); +} +#endif + +static void u32toa_len(char *buf, uint32_t n, size_t len) +{ + int digit, i; + for(i = len - 1; i >= 0; i--) { + digit = n % 10; + n = n / 10; + bufi = digit + '0'; + } +} + +/* for power of 2 radixes. len >= 1 */ +static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + bufi = digit; + } +} + +/* len >= 1. 2 <= radix <= 36 */ +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ +#if LIMB_BITS == 32 + u32toa_len(buf, n, len); +#else + /* XXX: optimize */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + bufi = digit + '0'; + } +#endif + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + bufi = digit; + } + } +} + +size_t u32toa(char *buf, uint32_t n) +{ + char buf110, *q; + size_t len; + + q = buf1 + sizeof(buf1); + do { + *--q = n % 10 + '0'; + n /= 10; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; +} + +size_t i32toa(char *buf, int32_t n) +{ + if (n >= 0) { + return u32toa(buf, n); + } else { + buf0 = '-'; + return u32toa(buf + 1, -(uint32_t)n) + 1; + } +} + +#ifdef USE_FAST_INT +size_t u64toa(char *buf, uint64_t n) +{ + if (n < 0x100000000) { + return u32toa(buf, n); + } else { + uint64_t n1; + char *q = buf; + uint32_t n2; + + n1 = n / 1000000000; + n %= 1000000000; + if (n1 >= 0x100000000) { + n2 = n1 / 1000000000; + n1 = n1 % 1000000000; + /* at most two digits */ + if (n2 >= 10) { + *q++ = n2 / 10 + '0'; + n2 %= 10; + } + *q++ = n2 + '0'; + u32toa_len(q, n1, 9); + q += 9; + } else { + q += u32toa(q, n1); + } + u32toa_len(q, n, 9); + q += 9; + return q - buf; + } +} + +size_t i64toa(char *buf, int64_t n) +{ + if (n >= 0) { + return u64toa(buf, n); + } else { + buf0 = '-'; + return u64toa(buf + 1, -(uint64_t)n) + 1; + } +} + +/* XXX: only tested for 1 <= n < 2^53 */ +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) +{ + int radix_bits, l; + if (likely(radix == 10)) + return u64toa(buf, n); + if ((radix & (radix - 1)) == 0) { + radix_bits = 31 - clz32(radix); + if (n == 0) + l = 1; + else + l = (64 - clz64(n) + radix_bits - 1) / radix_bits; + u64toa_bin_len(buf, n, radix_bits, l); + return l; + } else { + char buf141, *q; /* maximum length for radix = 3 */ + size_t len; + int digit; + q = buf1 + sizeof(buf1); + do { + digit = n % radix; + n /= radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + *--q = digit; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; + } +} + +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) +{ + if (n >= 0) { + return u64toa_radix(buf, n, radix); + } else { + buf0 = '-'; + return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; + } +} +#endif /* USE_FAST_INT */ + +static const uint8_t digits_per_limb_tableJS_RADIX_MAX - 1 = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const uint32_t radix_base_tableJS_RADIX_MAX - 1 = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +/* XXX: remove the table ? */ +static uint8_t dtoa_max_digits_tableJS_RADIX_MAX - 1 = { + 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* we limit the maximum number of significant digits for atod to about + 128 bits of precision for non power of two bases. The only + requirement for Javascript is at least 20 digits in base 10. For + power of two bases, we do an exact rounding in all the cases. */ +static uint8_t atod_max_digits_tableJS_RADIX_MAX - 1 = { + 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, +}; + +/* if abs(d) >= B^max_exponent, it is an overflow */ +static const int16_t max_exponentJS_RADIX_MAX - 1 = { + 1024, 647, 512, 442, 397, 365, 342, 324, + 309, 297, 286, 277, 269, 263, 256, 251, + 246, 242, 237, 234, 230, 227, 224, 221, + 218, 216, 214, 211, 209, 207, 205, 203, + 202, 200, 199, +}; + +/* if abs(d) <= B^min_exponent, it is an underflow */ +static const int16_t min_exponentJS_RADIX_MAX - 1 = { +-1075, -679, -538, -463, -416, -383, -359, -340, + -324, -311, -300, -291, -283, -276, -269, -263, + -258, -254, -249, -245, -242, -238, -235, -232, + -229, -227, -224, -222, -220, -217, -215, -214, + -212, -210, -208, +}; + +#if 0 +void build_tables(void) +{ + int r, j, radix, n, col, i; + + /* radix_base_table */ + for(radix = 2; radix <= 36; radix++) { + r = 1; + for(j = 0; j < digits_per_limb_tableradix - 2; j++) { + r *= radix; + } + printf(" 0x%08x,", r); + if ((radix % 4) == 1) + printf("\n"); + } + printf("\n"); + + /* dtoa_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + /* Note: over estimated when the radix is a power of two */ + printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); + } + printf("\n"); + + /* atod_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) { + /* 64 bits is more than enough */ + n = (int)floor(64.0 / log2(radix)); + } else { + n = (int)floor(128.0 / log2(radix)); + } + printf(" %d,", n); + } + printf("\n"); + + printf("static const int16_t max_exponentJS_RADIX_MAX - 1 = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)ceil(1024 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const int16_t min_exponentJS_RADIX_MAX - 1 = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)floor(-1075 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const uint32_t pow5_table16 = {\n"); + col = 0; + for(i = 2; i <= 17; i++) { + r = 1; + for(j = 0; j < i; j++) { + r *= 5; + } + printf("0x%08x, ", r); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + /* high part */ + printf("static const uint8_t pow5h_table4 = {\n"); + col = 0; + for(i = 14; i <= 17; i++) { + uint64_t r1; + r1 = 1; + for(j = 0; j < i; j++) { + r1 *= 5; + } + printf("0x%08x, ", (uint32_t)(r1 >> 32)); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); +} +#endif + +/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, + the dot is not displayed. 'a' is modified. */ +static int output_digits(char *buf, + mpb_t *a, int radix, int n_digits1, + int dot_pos) +{ + int n_digits, digits_per_limb, radix_bits, n, len; + + n_digits = n_digits1; + if ((radix & (radix - 1)) == 0) { + /* radix = 2^radix_bits */ + radix_bits = 31 - clz32(radix); + } else { + radix_bits = 0; + } + digits_per_limb = digits_per_limb_tableradix - 2; + if (radix_bits != 0) { + for(;;) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + u64toa_bin_len(buf + n_digits, a->tab0, radix_bits, n); + if (n_digits == 0) + break; + mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); + } + } else { + limb_t r; + while (n_digits != 0) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + r = mp_div1(a->tab, a->tab, a->len, radix_base_tableradix - 2, 0); + mpb_renorm(a); + limb_to_a(buf + n_digits, r, radix, n); + } + } + + /* add the dot */ + len = n_digits1; + if (dot_pos != n_digits1) { + memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); + bufdot_pos = '.'; + len++; + } + return len; +} + +/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * + 2^-e_offset. 'f' can be negative. */ +static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, BOOL is_int, int e) +{ + int e_offset, d, n, n0; + + e_offset = -f * radix_shift; + if (radix1 != 1) { + d = digits_per_limb_tableradix1 - 2; + if (f >= 0) { + limb_t h, b; + + b = 0; + n0 = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui(radix1, n); + n0 = n; + } + h = mp_mul1(a->tab, a->tab, a->len, b, 0); + if (h != 0) { + a->taba->len++ = h; + } + f -= n; + } + } else { + int extra_bits, l, shift; + limb_t r, rem, b, b_inv; + + f = -f; + l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ + e_offset += l * LIMB_BITS; + if (!is_int) { + /* at least 'e' bits are needed in the final result for rounding */ + extra_bits = max_int(e - mpb_floor_log2(a), 0); + } else { + /* at least two extra bits are needed in the final result + for rounding */ + extra_bits = max_int(2 + e - e_offset, 0); + } + e_offset += extra_bits; + mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); + + b = 0; + b_inv = 0; + shift = 0; + n0 = 0; + rem = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui_inv(&b_inv, &shift, radix1, n); + n0 = n; + } + r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); + rem |= r; + mpb_renorm(a); + f -= n; + } + /* if the remainder is non zero, use it for rounding */ + a->tab0 |= (rem != 0); + } + } + return e_offset; +} + +/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ +static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, + int rnd_mode) +{ + int e_offset; + + mpb_set_u64(tmp1, m); + e_offset = mul_pow(tmp1, radix1, radix_shift, f, TRUE, e); + mpb_shr_round(tmp1, -e + e_offset, rnd_mode); +} + +/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ +static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) +{ + int e; + uint64_t m; + + if (a->tab0 == 0 && a->len == 1) { + /* zero result */ + m = 0; + e = 0; /* don't care */ + } else { + int prec, prec1, e_min; + e = mpb_floor_log2(a) + 1 - e_offset; + prec1 = 53; + e_min = -1021; + if (e < e_min) { + /* subnormal result or zero */ + prec = prec1 - (e_min - e); + } else { + prec = prec1; + } + mpb_shr_round(a, e + e_offset - prec, rnd_mode); + m = mpb_get_u64(a); + m <<= (53 - prec); + /* mantissa overflow due to rounding */ + if (m >= (uint64_t)1 << 53) { + m >>= 1; + e++; + } + } + *pe = e; + return m; +} + +/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 + <= m < 2^53 or m = 0. + 'a' is modified. */ +static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, + int radix1, int radix_shift, int f, int rnd_mode) +{ + int e_offset; + + e_offset = mul_pow(a, radix1, radix_shift, f, FALSE, 55); + return round_to_d(pe, a, e_offset, rnd_mode); +} + +#ifdef JS_DTOA_DUMP_STATS +static int out_len_count17; + +void js_dtoa_dump_stats(void) +{ + int i, sum; + sum = 0; + for(i = 0; i < 17; i++) + sum += out_len_counti; + for(i = 0; i < 17; i++) { + printf("%2d %8d %5.2f%%\n", + i + 1, out_len_counti, (double)out_len_counti / sum * 100); + } +} +#endif + +/* return a maximum bound of the string length. The bound depends on + 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED + is enabled. */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags) +{ + int fmt = flags & JS_DTOA_FORMAT_MASK; + int n, e; + uint64_t a; + + if (fmt != JS_DTOA_FORMAT_FRAC) { + if (fmt == JS_DTOA_FORMAT_FREE) { + n = dtoa_max_digits_tableradix - 2; + } else { + n = n_digits; + } + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { + /* no exponential */ + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + e -= 1023; + /* XXX: adjust */ + n += 10 + abs(mul_log2_radix(e - 1, radix)); + } + } else { + /* extra: sign, 1 dot and exponent "e-1000" */ + n += 1 + 1 + 6; + } + } else { + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + /* high bound for the integer part */ + e -= 1023; + /* x < 2^(e + 1) */ + if (e < 0) { + n = 1; + } else { + n = 2 + mul_log2_radix(e - 1, radix); + } + /* sign, extra digit, 1 dot */ + n += 1 + 1 + 1 + n_digits; + } + } + return max_int(n, 9); /* also include NaN and -Infinity */ +} + +#if defined(__SANITIZE_ADDRESS__) && 0 +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + return malloc(size); +} +static void dtoa_free(void *ptr) +{ + free(ptr); +} +#else +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + void *ret; + ret = *pptr; + *pptr += (size + 7) / 8; + return ret; +} + +static void dtoa_free(void *ptr) +{ +} +#endif + +/* return the length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem) +{ + uint64_t a, m, *mptr = tmp_mem->mem; + int e, sgn, l, E, P, i, E_max, radix1, radix_shift; + char *q; + mpb_t *tmp1, *mant_max; + int fmt = flags & JS_DTOA_FORMAT_MASK; + + tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr0)); + + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & 0x7ff; + m = a & (((uint64_t)1 << 52) - 1); + q = buf; + if (e == 0x7ff) { + if (m == 0) { + if (sgn) + *q++ = '-'; + memcpy(q, "Infinity", 8); + q += 8; + } else { + memcpy(q, "NaN", 3); + q += 3; + } + goto done; + } else if (e == 0) { + if (m == 0) { + tmp1->len = 1; + tmp1->tab0 = 0; + E = 1; + if (fmt == JS_DTOA_FORMAT_FREE) + P = 1; + else if (fmt == JS_DTOA_FORMAT_FRAC) + P = n_digits + 1; + else + P = n_digits; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* remove the bias */ + e -= 1022; + /* d = 2^(e-53)*m */ + // printf("m=0x%016" PRIx64 " e=%d\n", m, e); +#ifdef USE_FAST_INT + if (fmt == JS_DTOA_FORMAT_FREE && + e >= 1 && e <= 53 && + (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && + (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { + m >>= 53 - e; + /* 'm' is never zero */ + q += u64toa_radix(q, m, radix); + goto done; + } +#endif + + /* this choice of E implies F=round(x*B^(P-E) is such as: + B^(P-1) <= F < 2.B^P. */ + E = 1 + mul_log2_radix(e - 1, radix); + + if (fmt == JS_DTOA_FORMAT_FREE) { + int P_max, E0, e1, E_found, P_found; + uint64_t m1, mant_found, mant, mant_max1; + /* P_max is guarranteed to work by construction */ + P_max = dtoa_max_digits_tableradix - 2; + E0 = E; + E_found = 0; + P_found = 0; + mant_found = 0; + /* find the minimum number of digits by successive tries */ + P = P_max; /* P_max is guarateed to work */ + for(;;) { + /* mant_max always fits on 64 bits */ + mant_max1 = pow_ui(radix, P); + /* compute the mantissa in base B */ + E = E0; + for(;;) { + /* XXX: add inexact flag */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); + mant = mpb_get_u64(tmp1); + if (mant < mant_max1) + break; + E++; /* at most one iteration is possible */ + } + /* remove useless trailing zero digits */ + while ((mant % radix) == 0) { + mant /= radix; + P--; + } + /* garanteed to work for P = P_max */ + if (P_found == 0) + goto prec_found; + /* convert back to base 2 */ + mpb_set_u64(tmp1, mant); + m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); + // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); + /* Note: (m, e) is never zero here, so the exponent for m1 + = 0 does not matter */ + if (m1 == m && e1 == e) { + prec_found: + P_found = P; + E_found = E; + mant_found = mant; + if (P == 1) + break; + P--; /* try lower exponent */ + } else { + break; + } + } + P = P_found; + E = E_found; + mpb_set_u64(tmp1, mant_found); +#ifdef JS_DTOA_DUMP_STATS + if (radix == 10) { + out_len_countP - 1++; + } +#endif + } else if (fmt == JS_DTOA_FORMAT_FRAC) { + int len; + + assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); + /* P = max_int(E, 1) + n_digits; */ + /* frac is rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); + + /* we add one extra digit on the left and remove it if needed + to avoid testing if the result is < radix^P */ + len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, + max_int(E + 1, 1)); + if (q0 == '0' && len >= 2 && q1 != '.') { + len--; + memmove(q, q + 1, len); + } + q += len; + goto done; + } else { + int pow_shift; + assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); + P = n_digits; + /* mant_max = radix^P */ + mant_max->len = 1; + mant_max->tab0 = 1; + pow_shift = mul_pow(mant_max, radix1, radix_shift, P, FALSE, 0); + mpb_shr_round(mant_max, pow_shift, JS_RNDZ); + + for(;;) { + /* fixed and frac are rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); + if (mpb_cmp(tmp1, mant_max) < 0) + break; + E++; /* at most one iteration is possible */ + } + } + output: + if (fmt == JS_DTOA_FORMAT_FIXED) + E_max = n_digits; + else + E_max = dtoa_max_digits_tableradix - 2 + 4; + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || + ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { + q += output_digits(q, tmp1, radix, P, 1); + E--; + if (radix == 10) { + *q++ = 'e'; + } else if (radix1 == 1 && radix_shift <= 4) { + E *= radix_shift; + *q++ = 'p'; + } else { + *q++ = '@'; + } + if (E < 0) { + *q++ = '-'; + E = -E; + } else { + *q++ = '+'; + } + q += u32toa(q, E); + } else if (E <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -E; i++) + *q++ = '0'; + q += output_digits(q, tmp1, radix, P, P); + } else { + q += output_digits(q, tmp1, radix, P, min_int(P, E)); + for(i = 0; i < E - P; i++) + *q++ = '0'; + } + done: + *q = '\0'; + dtoa_free(mant_max); + dtoa_free(tmp1); + return q - buf; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ +static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) +{ + int i; + if (r->tab0 == 0 && r->len == 1) { + r->tab0 = a; + } else { + if (radix_base == 0) { + for(i = r->len; i >= 0; i--) { + r->tabi + 1 = r->tabi; + } + r->tab0 = a; + } else { + r->tabr->len = mp_mul1(r->tab, r->tab, r->len, + radix_base, a); + } + r->len++; + mpb_renorm(r); + } +} + +/* XXX: add fast path for small integers */ +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem) +{ + uint64_t *mptr = tmp_mem->mem; + const char *p, *p_start; + limb_t cur_limb, radix_base, extra_digits; + int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; + int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; + mpb_t *tmp0; + double dval; + BOOL is_bin_exp, is_zero, expn_overflow; + uint64_t m, a; + + tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr0)); + /* optional separator between digits */ + sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + is_neg = 0; + if (p0 == '+') { + p++; + p_start = p; + } else if (p0 == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + + if (p0 == '0') { + if ((p1 == 'x' || p1 == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p1 == 'o' || p1 == 'O') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p1 == 'b' || p1 == 'B') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p1 >= '0' && p1 <= '9') && + radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (pi >= '0' && pi <= '7'); i++) + continue; + if (pi == '8' || pi == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + if (!(flags & JS_ATOD_INT_ONLY) && strstart(p, "Infinity", &p)) + goto overflow; + } + if (radix == 0) + radix = 10; + + cur_limb = 0; + expn_offset = 0; + digit_count = 0; + limb_digit_count = 0; + max_digits = atod_max_digits_tableradix - 2; + digits_per_limb = digits_per_limb_tableradix - 2; + radix_base = radix_base_tableradix - 2; + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + if (radix1 == 1) { + /* radix = 2^radix_bits */ + radix_bits = radix_shift; + } else { + radix_bits = 0; + } + tmp0->len = 1; + tmp0->tab0 = 0; + extra_digits = 0; + pos = 0; + dot_pos = -1; + /* skip leading zeros */ + for(;;) { + if (*p == '.' && (p > p_start || to_digit(p1) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && p1 == '0') + p++; + if (*p != '0') + break; + p++; + pos++; + } + + sig_pos = pos; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p1) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && to_digit(p1) < radix) + p++; + c = to_digit(*p); + if (c >= radix) + break; + p++; + pos++; + if (digit_count < max_digits) { + /* XXX: could be faster when radix_bits != 0 */ + cur_limb = cur_limb * radix + c; + limb_digit_count++; + if (limb_digit_count == digits_per_limb) { + mpb_mul1_base(tmp0, radix_base, cur_limb); + cur_limb = 0; + limb_digit_count = 0; + } + digit_count++; + } else { + extra_digits |= c; + } + } + if (limb_digit_count != 0) { + mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); + } + if (digit_count == 0) { + is_zero = TRUE; + expn_offset = 0; + } else { + is_zero = FALSE; + if (dot_pos < 0) + dot_pos = pos; + expn_offset = sig_pos + digit_count - dot_pos; + } + + /* Use the extra digits for rounding if the base is a power of + two. Otherwise they are just truncated. */ + if (radix_bits != 0 && extra_digits != 0) { + tmp0->tab0 |= 1; + } + + /* parse the exponent, if any */ + expn = 0; + expn_overflow = FALSE; + is_bin_exp = FALSE; + if (!(flags & JS_ATOD_INT_ONLY) && + ((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + BOOL exp_is_neg; + int c; + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = 0; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = 1; + p++; + } + c = to_digit(*p); + if (c >= 10) + goto fail; /* XXX: could stop before the exponent part */ + expn = c; + p++; + for(;;) { + if (*p == sep && to_digit(p1) < 10) + p++; + c = to_digit(*p); + if (c >= 10) + break; + if (!expn_overflow) { + if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { + expn_overflow = TRUE; + } else { + expn = expn * 10 + c; + } + } + p++; + } + if (exp_is_neg) + expn = -expn; + /* if zero result, the exponent can be arbitrarily large */ + if (!is_zero && expn_overflow) { + if (exp_is_neg) + a = 0; + else + a = (uint64_t)0x7ff << 52; /* infinity */ + goto done; + } + } + + if (p == p_start) + goto fail; + + if (is_zero) { + a = 0; + } else { + int expn1; + if (radix_bits != 0) { + if (!is_bin_exp) + expn *= radix_bits; + expn -= expn_offset * radix_bits; + expn1 = expn + digit_count * radix_bits; + if (expn1 >= 1024 + radix_bits) + goto overflow; + else if (expn1 <= -1075) + goto underflow; + m = round_to_d(&e, tmp0, -expn, JS_RNDN); + } else { + expn -= expn_offset; + expn1 = expn + digit_count; + if (expn1 >= max_exponentradix - 2 + 1) + goto overflow; + else if (expn1 <= min_exponentradix - 2) + goto underflow; + m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); + } + if (m == 0) { + underflow: + a = 0; + } else if (e > 1024) { + overflow: + /* overflow */ + a = (uint64_t)0x7ff << 52; + } else if (e < -1073) { + /* underflow */ + /* XXX: check rounding */ + a = 0; + } else if (e < -1021) { + /* subnormal */ + a = m >> (-e - 1021); + } else { + a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); + } + } + done: + a |= (uint64_t)is_neg << 63; + dval = uint64_as_float64(a); + done1: + if (pnext) + *pnext = p; + dtoa_free(tmp0); + return dval; + fail: + dval = NAN; + goto done1; +}
View file
gpac-26.02.0.tar.gz/src/quickjs/dtoa.h
Added
@@ -0,0 +1,83 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +//#define JS_DTOA_DUMP_STATS + +/* maximum number of digits for fixed and frac formats */ +#define JS_DTOA_MAX_DIGITS 101 + +/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ +/* use as many digits as necessary */ +#define JS_DTOA_FORMAT_FREE (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ +#define JS_DTOA_FORMAT_FIXED (1 << 0) +/* force fractional format: -dd.dd with n_digits fractional digits. + 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ +#define JS_DTOA_FORMAT_FRAC (2 << 0) +#define JS_DTOA_FORMAT_MASK (3 << 0) + +/* select exponential notation either in fixed or free format */ +#define JS_DTOA_EXP_AUTO (0 << 2) +#define JS_DTOA_EXP_ENABLED (1 << 2) +#define JS_DTOA_EXP_DISABLED (2 << 2) +#define JS_DTOA_EXP_MASK (3 << 2) + +#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ + +/* only accepts integers (no dot, no exponent) */ +#define JS_ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) +/* accept _ between digits as a digit separator */ +#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) + +typedef struct { + uint64_t mem37; +} JSDTOATempMem; + +typedef struct { + uint64_t mem27; +} JSATODTempMem; + +/* return a maximum bound of the string length */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags); +/* return the string length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem); +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem); + +#ifdef JS_DTOA_DUMP_STATS +void js_dtoa_dump_stats(void); +#endif + +/* additional exported functions */ +size_t u32toa(char *buf, uint32_t n); +size_t i32toa(char *buf, int32_t n); +size_t u64toa(char *buf, uint64_t n); +size_t i64toa(char *buf, int64_t n); +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix);
View file
gpac-2.4.0.tar.gz/src/quickjs/libregexp-opcode.h -> gpac-26.02.0.tar.gz/src/quickjs/libregexp-opcode.h
Changed
@@ -1,6 +1,6 @@ /* * Regular Expression Engine - * + * * Copyright (c) 2017-2018 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,11 +26,15 @@ DEF(invalid, 1) /* never used */ DEF(char, 3) +DEF(char_i, 3) DEF(char32, 5) +DEF(char32_i, 5) DEF(dot, 1) DEF(any, 1) /* same as dot but match any character including line terminator */ DEF(line_start, 1) +DEF(line_start_m, 1) DEF(line_end, 1) +DEF(line_end_m, 1) DEF(goto, 5) DEF(split_goto_first, 5) DEF(split_next_first, 5) @@ -42,16 +46,21 @@ DEF(push_i32, 5) /* push integer on the stack */ DEF(drop, 1) DEF(word_boundary, 1) +DEF(word_boundary_i, 1) DEF(not_word_boundary, 1) +DEF(not_word_boundary_i, 1) DEF(back_reference, 2) -DEF(backward_back_reference, 2) /* must come after back_reference */ +DEF(back_reference_i, 2) /* must come after */ +DEF(backward_back_reference, 2) /* must come after */ +DEF(backward_back_reference_i, 2) /* must come after */ DEF(range, 3) /* variable length */ +DEF(range_i, 3) /* variable length */ DEF(range32, 3) /* variable length */ +DEF(range32_i, 3) /* variable length */ DEF(lookahead, 5) DEF(negative_lookahead, 5) DEF(push_char_pos, 1) /* push the character position on the stack */ -DEF(bne_char_pos, 5) /* pop one stack element and jump if equal to the character - position */ +DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ DEF(prev, 1) /* go to the previous char */ DEF(simple_greedy_quant, 17)
View file
gpac-2.4.0.tar.gz/src/quickjs/libregexp.c -> gpac-26.02.0.tar.gz/src/quickjs/libregexp.c
Changed
@@ -1,6 +1,6 @@ /* * Regular Expression Engine - * + * * Copyright (c) 2017-2018 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -30,13 +30,11 @@ #include "cutils.h" #include "libregexp.h" +#include "libunicode.h" /* TODO: - - Add full unicode canonicalize rules for character ranges (not - really useful but needed for exact "ignorecase" compatibility). - - Add a lock step execution mode (=linear time execution guaranteed) when the regular expression is "simple" i.e. no backreference nor complicated lookahead. The opcodes are designed for this execution @@ -56,6 +54,9 @@ #define CAPTURE_COUNT_MAX 255 #define STACK_SIZE_MAX 255 +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define INTERRUPT_COUNTER_INIT 10000 /* unicode code points */ #define CP_LS 0x2028 @@ -69,8 +70,10 @@ const uint8_t *buf_end; const uint8_t *buf_start; int re_flags; - BOOL is_utf16; + BOOL is_unicode; + BOOL unicode_sets; /* if set, is_unicode is also set */ BOOL ignore_case; + BOOL multi_line; BOOL dotall; int capture_count; int total_capture_count; /* -1 = not computed yet */ @@ -101,10 +104,11 @@ }; #define RE_HEADER_FLAGS 0 -#define RE_HEADER_CAPTURE_COUNT 1 -#define RE_HEADER_STACK_SIZE 2 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_STACK_SIZE 3 +#define RE_HEADER_BYTECODE_LEN 4 -#define RE_HEADER_LEN 7 +#define RE_HEADER_LEN 8 static inline int is_digit(int c) { return c >= '0' && c <= '9'; @@ -113,38 +117,269 @@ /* insert 'len' bytes at position 'pos'. Return < 0 if error. */ static int dbuf_insert(DynBuf *s, int pos, int len) { - if (dbuf_realloc(s, s->size + len)) + if (dbuf_claim(s, len)) return -1; memmove(s->buf + pos + len, s->buf + pos, s->size - pos); s->size += len; return 0; } -/* canonicalize with the specific JS regexp rules */ -static uint32_t lre_canonicalize(uint32_t c, BOOL is_utf16) +typedef struct REString { + struct REString *next; + uint32_t hash; + uint32_t len; + uint32_t buf; +} REString; + +typedef struct { + /* the string list is the union of 'char_range' and of the strings + in hash_table. The strings in hash_table have a length != + 1. */ + CharRange cr; + uint32_t n_strings; + uint32_t hash_size; + int hash_bits; + REString **hash_table; +} REStringList; + +static uint32_t re_string_hash(int len, const uint32_t *buf) { - uint32_t resLRE_CC_RES_LEN_MAX; - int len; - if (is_utf16) { - if (likely(c < 128)) { - if (c >= 'A' && c <= 'Z') - c = c - 'A' + 'a'; - } else { - lre_case_conv(res, c, 2); - c = res0; + int i; + uint32_t h; + h = 1; + for(i = 0; i < len; i++) + h = h * 263 + bufi; + return h * 0x61C88647; +} + +static void re_string_list_init(REParseState *s1, REStringList *s) +{ + cr_init(&s->cr, s1->opaque, lre_realloc); + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; +} + +static void re_string_list_free(REStringList *s) +{ + REString *p, *p_next; + int i; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_tablei; p != NULL; p = p_next) { + p_next = p->next; + lre_realloc(s->cr.mem_opaque, p, 0); } + } + lre_realloc(s->cr.mem_opaque, s->hash_table, 0); + + cr_free(&s->cr); +} + +static void lre_print_char(int c, BOOL is_range) +{ + if (c == '\'' || c == '\\' || + (is_range && (c == '-' || c == ''))) { + printf("\\%c", c); + } else if (c >= ' ' && c <= 126) { + printf("%c", c); } else { - if (likely(c < 128)) { - if (c >= 'a' && c <= 'z') - c = c - 'a' + 'A'; - } else { - /* legacy regexp: to upper case if single char >= 128 */ - len = lre_case_conv(res, c, FALSE); - if (len == 1 && res0 >= 128) - c = res0; + printf("\\u{%04x}", c); + } +} + +static __maybe_unused void re_string_list_dump(const char *str, const REStringList *s) +{ + REString *p; + const CharRange *cr; + int i, j, k; + + printf("%s:\n", str); + printf(" ranges: "); + cr = &s->cr; + for(i = 0; i < cr->len; i += 2) { + lre_print_char(cr->pointsi, TRUE); + if (cr->pointsi != cr->pointsi + 1 - 1) { + printf("-"); + lre_print_char(cr->pointsi + 1 - 1, TRUE); + } + } + printf("\n"); + + j = 0; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_tablei; p != NULL; p = p->next) { + printf(" %d/%d: '", j, s->n_strings); + for(k = 0; k < p->len; k++) { + lre_print_char(p->bufk, FALSE); + } + printf("'\n"); + j++; } } - return c; +} + +static int re_string_find2(REStringList *s, int len, const uint32_t *buf, + uint32_t h0, BOOL add_flag) +{ + uint32_t h = 0; /* avoid warning */ + REString *p; + if (s->n_strings != 0) { + h = h0 >> (32 - s->hash_bits); + for(p = s->hash_tableh; p != NULL; p = p->next) { + if (p->hash == h0 && p->len == len && + !memcmp(p->buf, buf, len * sizeof(buf0))) { + return 1; + } + } + } + /* not found */ + if (!add_flag) + return 0; + /* increase the size of the hash table if needed */ + if (unlikely((s->n_strings + 1) > s->hash_size)) { + REString **new_hash_table, *p_next; + int new_hash_bits, i; + uint32_t new_hash_size; + new_hash_bits = max_int(s->hash_bits + 1, 4); + new_hash_size = 1 << new_hash_bits; + new_hash_table = lre_realloc(s->cr.mem_opaque, NULL, + sizeof(new_hash_table0) * new_hash_size); + if (!new_hash_table) + return -1; + memset(new_hash_table, 0, sizeof(new_hash_table0) * new_hash_size); + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_tablei; p != NULL; p = p_next) { + p_next = p->next; + h = p->hash >> (32 - new_hash_bits); + p->next = new_hash_tableh; + new_hash_tableh = p; + } + } + lre_realloc(s->cr.mem_opaque, s->hash_table, 0); + s->hash_bits = new_hash_bits; + s->hash_size = new_hash_size; + s->hash_table = new_hash_table; + h = h0 >> (32 - s->hash_bits); + } + + p = lre_realloc(s->cr.mem_opaque, NULL, sizeof(REString) + len * sizeof(buf0)); + if (!p) + return -1; + p->next = s->hash_tableh; + s->hash_tableh = p; + s->n_strings++; + p->hash = h0; + p->len = len; + memcpy(p->buf, buf, sizeof(buf0) * len); + return 1; +} + +static int re_string_find(REStringList *s, int len, const uint32_t *buf, + BOOL add_flag) +{ + uint32_t h0; + h0 = re_string_hash(len, buf); + return re_string_find2(s, len, buf, h0, add_flag); +} + +/* return -1 if memory error, 0 if OK */ +static int re_string_add(REStringList *s, int len, const uint32_t *buf) +{ + if (len == 1) { + return cr_union_interval(&s->cr, buf0, buf0); + } + if (re_string_find(s, len, buf, TRUE) < 0) + return -1; + return 0; +} + +/* a = a op b */ +static int re_string_list_op(REStringList *a, REStringList *b, int op) +{ + int i, ret; + REString *p, **pp; + + if (cr_op1(&a->cr, b->cr.points, b->cr.len, op)) + return -1; + + switch(op) { + case CR_OP_UNION: + if (b->n_strings != 0) { + for(i = 0; i < b->hash_size; i++) { + for(p = b->hash_tablei; p != NULL; p = p->next) { + if (re_string_find2(a, p->len, p->buf, p->hash, TRUE) < 0) + return -1; + } + } + } + break; + case CR_OP_INTER: + case CR_OP_SUB: + for(i = 0; i < a->hash_size; i++) { + pp = &a->hash_tablei; + for(;;) { + p = *pp; + if (p == NULL) + break; + ret = re_string_find2(b, p->len, p->buf, p->hash, FALSE); + if (op == CR_OP_SUB) + ret = !ret; + if (!ret) { + /* remove it */ + *pp = p->next; + a->n_strings--; + lre_realloc(a->cr.mem_opaque, p, 0); + } else { + /* keep it */ + pp = &p->next; + } + } + } + break; + default: + abort(); + } + return 0; +} + +static int re_string_list_canonicalize(REParseState *s1, + REStringList *s, BOOL is_unicode) +{ + if (cr_regexp_canonicalize(&s->cr, is_unicode)) + return -1; + if (s->n_strings != 0) { + REStringList a_s, *a = &a_s; + int i, j; + REString *p; + + /* XXX: simplify */ + re_string_list_init(s1, a); + + a->n_strings = s->n_strings; + a->hash_size = s->hash_size; + a->hash_bits = s->hash_bits; + a->hash_table = s->hash_table; + + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; + + for(i = 0; i < a->hash_size; i++) { + for(p = a->hash_tablei; p != NULL; p = p->next) { + for(j = 0; j < p->len; j++) { + p->bufj = lre_canonicalize(p->bufj, is_unicode); + } + if (re_string_add(s, p->len, p->buf)) { + re_string_list_free(a); + return -1; + } + } + } + re_string_list_free(a); + } + return 0; } static const uint16_t char_range_d = { @@ -170,32 +405,6 @@ 0xFEFF, 0xFEFF + 1, }; -BOOL lre_is_space(int c) -{ - int i, n, low, high; - n = (countof(char_range_s) - 1) / 2; - for(i = 0; i < n; i++) { - low = char_range_s2 * i + 1; - if (c < low) - return FALSE; - high = char_range_s2 * i + 2; - if (c < high) - return TRUE; - } - return FALSE; -} - -uint32_t const lre_id_start_table_ascii4 = { - /* $ A-Z _ a-z */ - 0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE -}; - -uint32_t const lre_id_continue_table_ascii4 = { - /* $ 0-9 A-Z _ a-z */ - 0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE -}; - - static const uint16_t char_range_w = { 4, 0x0030, 0x0039 + 1, @@ -215,80 +424,55 @@ CHAR_RANGE_W, } CharRangeEnum; -static const uint16_t *char_range_table = { +static const uint16_t * const char_range_table = { char_range_d, char_range_s, char_range_w, }; -static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c) +static int cr_init_char_range(REParseState *s, REStringList *cr, uint32_t c) { BOOL invert; const uint16_t *c_pt; int len, i; - + invert = c & 1; c_pt = char_range_tablec >> 1; len = *c_pt++; - cr_init(cr, s->opaque, lre_realloc); + re_string_list_init(s, cr); for(i = 0; i < len * 2; i++) { - if (cr_add_point(cr, c_pti)) + if (cr_add_point(&cr->cr, c_pti)) goto fail; } if (invert) { - if (cr_invert(cr)) + if (cr_invert(&cr->cr)) goto fail; } return 0; fail: - cr_free(cr); + re_string_list_free(cr); return -1; } -static int cr_canonicalize(CharRange *cr) -{ - CharRange a; - uint32_t pt2; - int i, ret; - - cr_init(&a, cr->mem_opaque, lre_realloc); - pt0 = 'a'; - pt1 = 'z' + 1; - ret = cr_op(&a, cr->points, cr->len, pt, 2, CR_OP_INTER); - if (ret) - goto fail; - /* convert to upper case */ - /* XXX: the generic unicode case would be much more complicated - and not really useful */ - for(i = 0; i < a.len; i++) { - a.pointsi += 'A' - 'a'; - } - /* Note: for simplicity we keep the lower case ranges */ - ret = cr_union1(cr, a.points, a.len); - fail: - cr_free(&a); - return ret; -} - #ifdef DUMP_REOP static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, int buf_len) { int pos, len, opcode, bc_len, re_flags, i; uint32_t val; - + assert(buf_len >= RE_HEADER_LEN); - re_flags= buf0; - bc_len = get_u32(buf + 3); + re_flags = lre_get_flags(buf); + bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); assert(bc_len + RE_HEADER_LEN <= buf_len); printf("flags: 0x%x capture_count=%d stack_size=%d\n", - re_flags, buf1, buf2); + re_flags, bufRE_HEADER_CAPTURE_COUNT, bufRE_HEADER_STACK_SIZE); if (re_flags & LRE_FLAG_NAMED_GROUPS) { const char *p; p = (char *)buf + RE_HEADER_LEN + bc_len; printf("named groups: "); - for(i = 1; i < buf1; i++) { + for(i = 1; i < bufRE_HEADER_CAPTURE_COUNT; i++) { if (i != 1) printf(","); printf("<%s>", p); @@ -316,6 +500,7 @@ printf("%s", reopcode_infoopcode.name); switch(opcode) { case REOP_char: + case REOP_char_i: val = get_u16(buf + pos + 1); if (val >= ' ' && val <= 126) printf(" '%c'", val); @@ -323,6 +508,7 @@ printf(" 0x%04x", val); break; case REOP_char32: + case REOP_char32_i: val = get_u32(buf + pos + 1); if (val >= ' ' && val <= 126) printf(" '%c'", val); @@ -335,7 +521,6 @@ case REOP_loop: case REOP_lookahead: case REOP_negative_lookahead: - case REOP_bne_char_pos: val = get_u32(buf + pos + 1); val += (pos + 5); printf(" %u", val); @@ -350,7 +535,9 @@ case REOP_save_start: case REOP_save_end: case REOP_back_reference: + case REOP_back_reference_i: case REOP_backward_back_reference: + case REOP_backward_back_reference_i: printf(" %u", bufpos + 1); break; case REOP_save_reset: @@ -361,6 +548,7 @@ printf(" %d", val); break; case REOP_range: + case REOP_range_i: { int n, i; n = get_u16(buf + pos + 1); @@ -372,6 +560,7 @@ } break; case REOP_range32: + case REOP_range32_i: { int n, i; n = get_u16(buf + pos + 1); @@ -452,7 +641,7 @@ const uint8_t *p; uint64_t v; int c; - + p = *pp; v = 0; for(;;) { @@ -524,7 +713,7 @@ { int h, n, i; uint32_t c1; - + if (*p == '{' && allow_utf16) { p++; c = 0; @@ -554,7 +743,7 @@ } c = (c << 4) | h; } - if (c >= 0xd800 && c < 0xdc00 && + if (is_hi_surrogate(c) && allow_utf16 == 2 && p0 == '\\' && p1 == 'u') { /* convert an escaped surrogate pair into a unicode char */ @@ -565,9 +754,9 @@ break; c1 = (c1 << 4) | h; } - if (i == 4 && c1 >= 0xdc00 && c1 < 0xe000) { + if (i == 4 && is_lo_surrogate(c1)) { p += 6; - c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; + c = from_surrogate(c, c1); } } } @@ -614,8 +803,16 @@ (c == '_')); } -static int parse_unicode_property(REParseState *s, CharRange *cr, - const uint8_t **pp, BOOL is_inv) +/* XXX: memory error test */ +static void seq_prop_cb(void *opaque, const uint32_t *seq, int seq_len) +{ + REStringList *sl = opaque; + re_string_add(sl, seq_len, seq); +} + +static int parse_unicode_property(REParseState *s, REStringList *cr, + const uint8_t **pp, BOOL is_inv, + BOOL allow_sequence_prop) { const uint8_t *p; char name64, value64; @@ -655,51 +852,76 @@ } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { script_ext = TRUE; do_script: - cr_init(cr, s->opaque, lre_realloc); - ret = unicode_script(cr, value, script_ext); + re_string_list_init(s, cr); + ret = unicode_script(&cr->cr, value, script_ext); if (ret) { - cr_free(cr); + re_string_list_free(cr); if (ret == -2) return re_parse_error(s, "unknown unicode script"); else goto out_of_memory; } } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { - cr_init(cr, s->opaque, lre_realloc); - ret = unicode_general_category(cr, value); + re_string_list_init(s, cr); + ret = unicode_general_category(&cr->cr, value); if (ret) { - cr_free(cr); + re_string_list_free(cr); if (ret == -2) return re_parse_error(s, "unknown unicode general category"); else goto out_of_memory; } } else if (value0 == '\0') { - cr_init(cr, s->opaque, lre_realloc); - ret = unicode_general_category(cr, name); + re_string_list_init(s, cr); + ret = unicode_general_category(&cr->cr, name); if (ret == -1) { - cr_free(cr); + re_string_list_free(cr); goto out_of_memory; } if (ret < 0) { - ret = unicode_prop(cr, name); - if (ret) { - cr_free(cr); - if (ret == -2) - goto unknown_property_name; - else - goto out_of_memory; + ret = unicode_prop(&cr->cr, name); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (ret < 0 && !is_inv && allow_sequence_prop) { + CharRange cr_tmp; + cr_init(&cr_tmp, s->opaque, lre_realloc); + ret = unicode_sequence_prop(name, seq_prop_cb, cr, &cr_tmp); + cr_free(&cr_tmp); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; } } + if (ret < 0) + goto unknown_property_name; } else { unknown_property_name: return re_parse_error(s, "unknown unicode property name"); } + /* the ordering of case folding and inversion differs with + unicode_sets. 'unicode_sets' ordering is more consistent */ + /* XXX: the spec seems incorrect, we do it as the other engines + seem to do it. */ + if (s->ignore_case && s->unicode_sets) { + if (re_string_list_canonicalize(s, cr, s->is_unicode)) { + re_string_list_free(cr); + goto out_of_memory; + } + } if (is_inv) { - if (cr_invert(cr)) { - cr_free(cr); - return -1; + if (cr_invert(&cr->cr)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (s->ignore_case && !s->unicode_sets) { + if (re_string_list_canonicalize(s, cr, s->is_unicode)) { + re_string_list_free(cr); + goto out_of_memory; } } *pp = p; @@ -709,16 +931,67 @@ } #endif /* CONFIG_ALL_UNICODE */ +static int get_class_atom(REParseState *s, REStringList *cr, + const uint8_t **pp, BOOL inclass); + +static int parse_class_string_disjunction(REParseState *s, REStringList *cr, + const uint8_t **pp) +{ + const uint8_t *p; + DynBuf str; + int c; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\q"); + + dbuf_init2(&str, s->opaque, lre_realloc); + re_string_list_init(s, cr); + + p++; + for(;;) { + str.size = 0; + while (*p != '}' && *p != '|') { + c = get_class_atom(s, NULL, &p, FALSE); + if (c < 0) + goto fail; + if (dbuf_put_u32(&str, c)) { + re_parse_out_of_memory(s); + goto fail; + } + } + if (re_string_add(cr, str.size / 4, (uint32_t *)str.buf)) { + re_parse_out_of_memory(s); + goto fail; + } + if (*p == '}') + break; + p++; + } + if (s->ignore_case) { + if (re_string_list_canonicalize(s, cr, TRUE)) + goto fail; + } + p++; /* skip the '}' */ + dbuf_free(&str); + *pp = p; + return 0; + fail: + dbuf_free(&str); + re_string_list_free(cr); + return -1; +} + /* return -1 if error otherwise the character or a class range - (CLASS_RANGE_BASE). In case of class range, 'cr' is + (CLASS_RANGE_BASE) if cr != NULL. In case of class range, 'cr' is initialized. Otherwise, it is ignored. */ -static int get_class_atom(REParseState *s, CharRange *cr, +static int get_class_atom(REParseState *s, REStringList *cr, const uint8_t **pp, BOOL inclass) { const uint8_t *p; uint32_t c; int ret; - + p = *pp; c = *p; @@ -747,6 +1020,8 @@ case 'W': c = CHAR_RANGE_W; class_range: + if (!cr) + goto default_escape; if (cr_init_char_range(s, cr, c)) return -1; c = CLASS_RANGE_BASE; @@ -756,10 +1031,10 @@ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (((c >= '0' && c <= '9') || c == '_') && - inclass && !s->is_utf16)) { /* Annex B.1.4 */ + inclass && !s->is_unicode)) { /* Annex B.1.4 */ c &= 0x1f; p++; - } else if (s->is_utf16) { + } else if (s->is_unicode) { goto invalid_escape; } else { /* otherwise return '\' and 'c' */ @@ -767,27 +1042,54 @@ c = '\\'; } break; + case '-': + if (!inclass && s->is_unicode) + goto invalid_escape; + break; + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '': + case '': + case '{': + case '}': + case '|': + case '/': + /* always valid to escape these characters */ + break; #ifdef CONFIG_ALL_UNICODE case 'p': case 'P': - if (s->is_utf16) { - if (parse_unicode_property(s, cr, &p, (c == 'P'))) + if (s->is_unicode && cr) { + if (parse_unicode_property(s, cr, &p, (c == 'P'), s->unicode_sets)) return -1; c = CLASS_RANGE_BASE; break; } - /* fall thru */ + goto default_escape; #endif + case 'q': + if (s->unicode_sets && cr && inclass) { + if (parse_class_string_disjunction(s, cr, &p)) + return -1; + c = CLASS_RANGE_BASE; + break; + } + goto default_escape; default: + default_escape: p--; - ret = lre_parse_escape(&p, s->is_utf16 * 2); + ret = lre_parse_escape(&p, s->is_unicode * 2); if (ret >= 0) { c = ret; } else { - if (ret == -2 && *p != '\0' && strchr("^$\\.*+?(){}|/", *p)) { - /* always valid to escape these characters */ - goto normal_char; - } else if (s->is_utf16) { + if (s->is_unicode) { invalid_escape: return re_parse_error(s, "invalid escape sequence in regular expression"); } else { @@ -804,12 +1106,54 @@ return re_parse_error(s, "unexpected end"); } /* fall thru */ + goto normal_char; + + case '&': + case '!': + case '#': + case '$': + case '%': + case '*': + case '+': + case ',': + case '.': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '^': + case '`': + case '~': + if (s->unicode_sets && p1 == c) { + /* forbidden double characters */ + return re_parse_error(s, "invalid class set operation in regular expression"); + } + goto normal_char; + + case '(': + case ')': + case '': + case '': + case '{': + case '}': + case '/': + case '-': + case '|': + if (s->unicode_sets) { + /* invalid characters in unicode sets */ + return re_parse_error(s, "invalid character in class in regular expression"); + } + goto normal_char; + default: normal_char: /* normal char */ if (c >= 128) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); - if ((unsigned)c > 0xffff && !s->is_utf16) { + if ((unsigned)c > 0xffff && !s->is_unicode) { /* XXX: should handle non BMP-1 code points */ return re_parse_error(s, "malformed unicode char"); } @@ -826,13 +1170,11 @@ { int len, i; uint32_t high; - + len = (unsigned)cr->len / 2; if (len >= 65535) return re_parse_error(s, "too many ranges"); if (len == 0) { - /* not sure it can really happen. Emit a match that is always - false */ re_emit_op_u32(s, REOP_char32, -1); } else { high = cr->pointscr->len - 1; @@ -841,7 +1183,7 @@ if (high <= 0xffff) { /* can use 16 bit ranges with the conversion that 0xffff = infinity */ - re_emit_op_u16(s, REOP_range, len); + re_emit_op_u16(s, s->ignore_case ? REOP_range_i : REOP_range, len); for(i = 0; i < cr->len; i += 2) { dbuf_put_u16(&s->byte_code, cr->pointsi); high = cr->pointsi + 1 - 1; @@ -850,7 +1192,7 @@ dbuf_put_u16(&s->byte_code, high); } } else { - re_emit_op_u16(s, REOP_range32, len); + re_emit_op_u16(s, s->ignore_case ? REOP_range32_i : REOP_range32, len); for(i = 0; i < cr->len; i += 2) { dbuf_put_u32(&s->byte_code, cr->pointsi); dbuf_put_u32(&s->byte_code, cr->pointsi + 1 - 1); @@ -860,176 +1202,362 @@ return 0; } -static int re_parse_char_class(REParseState *s, const uint8_t **pp) +static int re_string_cmp_len(const void *a, const void *b, void *arg) +{ + REString *p1 = *(REString **)a; + REString *p2 = *(REString **)b; + return (p1->len < p2->len) - (p1->len > p2->len); +} + +static void re_emit_char(REParseState *s, int c) +{ + if (c <= 0xffff) + re_emit_op_u16(s, s->ignore_case ? REOP_char_i : REOP_char, c); + else + re_emit_op_u32(s, s->ignore_case ? REOP_char32_i : REOP_char32, c); +} + +static int re_emit_string_list(REParseState *s, const REStringList *sl) +{ + REString **tab, *p; + int i, j, split_pos, last_match_pos, n; + BOOL has_empty_string, is_last; + + // re_string_list_dump("sl", sl); + if (sl->n_strings == 0) { + /* simple case: only characters */ + if (re_emit_range(s, &sl->cr)) + return -1; + } else { + /* at least one string list is present : match the longest ones first */ + /* XXX: add a new op_switch opcode to compile as a trie */ + tab = lre_realloc(s->opaque, NULL, sizeof(tab0) * sl->n_strings); + if (!tab) { + re_parse_out_of_memory(s); + return -1; + } + has_empty_string = FALSE; + n = 0; + for(i = 0; i < sl->hash_size; i++) { + for(p = sl->hash_tablei; p != NULL; p = p->next) { + if (p->len == 0) { + has_empty_string = TRUE; + } else { + tabn++ = p; + } + } + } + assert(n <= sl->n_strings); + + rqsort(tab, n, sizeof(tab0), re_string_cmp_len, NULL); + + last_match_pos = -1; + for(i = 0; i < n; i++) { + p = tabi; + is_last = !has_empty_string && sl->cr.len == 0 && i == (n - 1); + if (!is_last) + split_pos = re_emit_op_u32(s, REOP_split_next_first, 0); + else + split_pos = 0; + for(j = 0; j < p->len; j++) { + re_emit_char(s, p->bufj); + } + if (!is_last) { + last_match_pos = re_emit_op_u32(s, REOP_goto, last_match_pos); + put_u32(s->byte_code.buf + split_pos, s->byte_code.size - (split_pos + 4)); + } + } + + if (sl->cr.len != 0) { + /* char range */ + is_last = !has_empty_string; + if (!is_last) + split_pos = re_emit_op_u32(s, REOP_split_next_first, 0); + else + split_pos = 0; /* not used */ + if (re_emit_range(s, &sl->cr)) { + lre_realloc(s->opaque, tab, 0); + return -1; + } + if (!is_last) + put_u32(s->byte_code.buf + split_pos, s->byte_code.size - (split_pos + 4)); + } + + /* patch the 'goto match' */ + while (last_match_pos != -1) { + int next_pos = get_u32(s->byte_code.buf + last_match_pos); + put_u32(s->byte_code.buf + last_match_pos, s->byte_code.size - (last_match_pos + 4)); + last_match_pos = next_pos; + } + + lre_realloc(s->opaque, tab, 0); + } + return 0; +} + +static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_t **pp); + +static int re_parse_class_set_operand(REParseState *s, REStringList *cr, const uint8_t **pp) +{ + int c1; + const uint8_t *p = *pp; + + if (*p == '') { + if (re_parse_nested_class(s, cr, pp)) + return -1; + } else { + c1 = get_class_atom(s, cr, pp, TRUE); + if (c1 < 0) + return -1; + if (c1 < CLASS_RANGE_BASE) { + /* create a range with a single character */ + re_string_list_init(s, cr); + if (s->ignore_case) + c1 = lre_canonicalize(c1, s->is_unicode); + if (cr_union_interval(&cr->cr, c1, c1)) { + re_string_list_free(cr); + return -1; + } + } + } + return 0; +} + +static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_t **pp) { const uint8_t *p; uint32_t c1, c2; - CharRange cr_s, *cr = &cr_s; - CharRange cr1_s, *cr1 = &cr1_s; - BOOL invert; - - cr_init(cr, s->opaque, lre_realloc); + int ret; + REStringList cr1_s, *cr1 = &cr1_s; + BOOL invert, is_first; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + re_string_list_init(s, cr); p = *pp; p++; /* skip '' */ + invert = FALSE; if (*p == '^') { p++; invert = TRUE; } + + /* handle unions */ + is_first = TRUE; for(;;) { if (*p == '') break; - c1 = get_class_atom(s, cr1, &p, TRUE); - if ((int)c1 < 0) - goto fail; - if (*p == '-' && p1 != '') { - const uint8_t *p0 = p + 1; - if (c1 >= CLASS_RANGE_BASE) { - if (s->is_utf16) { - cr_free(cr1); - goto invalid_class_range; - } - /* Annex B: match '-' character */ - goto class_atom; - } - c2 = get_class_atom(s, cr1, &p0, TRUE); - if ((int)c2 < 0) + if (*p == '' && s->unicode_sets) { + if (re_parse_nested_class(s, cr1, &p)) goto fail; - if (c2 >= CLASS_RANGE_BASE) { - cr_free(cr1); - if (s->is_utf16) { - goto invalid_class_range; - } - /* Annex B: match '-' character */ - goto class_atom; - } - p = p0; - if (c2 < c1) { - invalid_class_range: - re_parse_error(s, "invalid class range"); - goto fail; - } - if (cr_union_interval(cr, c1, c2)) - goto memory_error; + goto class_union; } else { - class_atom: - if (c1 >= CLASS_RANGE_BASE) { - int ret; - ret = cr_union1(cr, cr1->points, cr1->len); - cr_free(cr1); - if (ret) - goto memory_error; + c1 = get_class_atom(s, cr1, &p, TRUE); + if ((int)c1 < 0) + goto fail; + if (*p == '-' && p1 != '') { + const uint8_t *p0 = p + 1; + if (p1 == '-' && s->unicode_sets && is_first) + goto class_atom; /* first character class followed by '--' */ + if (c1 >= CLASS_RANGE_BASE) { + if (s->is_unicode) { + re_string_list_free(cr1); + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + c2 = get_class_atom(s, cr1, &p0, TRUE); + if ((int)c2 < 0) + goto fail; + if (c2 >= CLASS_RANGE_BASE) { + re_string_list_free(cr1); + if (s->is_unicode) { + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + p = p0; + if (c2 < c1) { + invalid_class_range: + re_parse_error(s, "invalid class range"); + goto fail; + } + if (s->ignore_case) { + CharRange cr2_s, *cr2 = &cr2_s; + cr_init(cr2, s->opaque, lre_realloc); + if (cr_add_interval(cr2, c1, c2 + 1) || + cr_regexp_canonicalize(cr2, s->is_unicode) || + cr_op1(&cr->cr, cr2->points, cr2->len, CR_OP_UNION)) { + cr_free(cr2); + goto memory_error; + } + cr_free(cr2); + } else { + if (cr_union_interval(&cr->cr, c1, c2)) + goto memory_error; + } + is_first = FALSE; /* union operation */ } else { - if (cr_union_interval(cr, c1, c1)) - goto memory_error; + class_atom: + if (c1 >= CLASS_RANGE_BASE) { + class_union: + ret = re_string_list_op(cr, cr1, CR_OP_UNION); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } else { + if (s->ignore_case) + c1 = lre_canonicalize(c1, s->is_unicode); + if (cr_union_interval(&cr->cr, c1, c1)) + goto memory_error; + } } } + if (s->unicode_sets && is_first) { + if (*p == '&' && p1 == '&' && p2 != '&') { + /* handle '&&' */ + for(;;) { + if (*p == '') { + break; + } else if (*p == '&' && p1 == '&' && p2 != '&') { + p += 2; + } else { + goto invalid_operation; + } + if (re_parse_class_set_operand(s, cr1, &p)) + goto fail; + ret = re_string_list_op(cr, cr1, CR_OP_INTER); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } + } else if (*p == '-' && p1 == '-') { + /* handle '--' */ + for(;;) { + if (*p == '') { + break; + } else if (*p == '-' && p1 == '-') { + p += 2; + } else { + invalid_operation: + re_parse_error(s, "invalid operation in regular expression"); + goto fail; + } + if (re_parse_class_set_operand(s, cr1, &p)) + goto fail; + ret = re_string_list_op(cr, cr1, CR_OP_SUB); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } + } + } + is_first = FALSE; } - if (s->ignore_case) { - if (cr_canonicalize(cr)) - goto memory_error; - } + + p++; /* skip '' */ + *pp = p; if (invert) { - if (cr_invert(cr)) + /* XXX: add may_contain_string syntax check to be fully + compliant. The test here accepts more input than the + spec. */ + if (cr->n_strings != 0) { + re_parse_error(s, "negated character class with strings in regular expression debugger eval code"); + goto fail; + } + if (cr_invert(&cr->cr)) goto memory_error; } - if (re_emit_range(s, cr)) - goto fail; - cr_free(cr); - p++; /* skip '' */ - *pp = p; return 0; memory_error: re_parse_out_of_memory(s); fail: - cr_free(cr); + re_string_list_free(cr); + return -1; +} + +static int re_parse_char_class(REParseState *s, const uint8_t **pp) +{ + REStringList cr_s, *cr = &cr_s; + + if (re_parse_nested_class(s, cr, pp)) + return -1; + if (re_emit_string_list(s, cr)) + goto fail; + re_string_list_free(cr); + return 0; + fail: + re_string_list_free(cr); return -1; } /* Return: - 1 if the opcodes in bc_buf always advance the character pointer. - 0 if the character pointer may not be advanced. - -1 if the code may depend on side effects of its previous execution (backreference) + - true if the opcodes may not advance the char pointer + - false if the opcodes always advance the char pointer */ -static int re_check_advance(const uint8_t *bc_buf, int bc_buf_len) +static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) { - int pos, opcode, ret, len, i; - uint32_t val, last; - BOOL has_back_reference; - uint8_t capture_bitmapCAPTURE_COUNT_MAX; - - ret = -2; /* not known yet */ + int pos, opcode, len; + uint32_t val; + BOOL ret; + + ret = TRUE; pos = 0; - has_back_reference = FALSE; - memset(capture_bitmap, 0, sizeof(capture_bitmap)); - while (pos < bc_buf_len) { opcode = bc_bufpos; len = reopcode_infoopcode.size; switch(opcode) { case REOP_range: + case REOP_range_i: val = get_u16(bc_buf + pos + 1); len += val * 4; goto simple_char; case REOP_range32: + case REOP_range32_i: val = get_u16(bc_buf + pos + 1); len += val * 8; goto simple_char; case REOP_char: + case REOP_char_i: case REOP_char32: + case REOP_char32_i: case REOP_dot: case REOP_any: simple_char: - if (ret == -2) - ret = 1; + ret = FALSE; break; case REOP_line_start: + case REOP_line_start_m: case REOP_line_end: + case REOP_line_end_m: case REOP_push_i32: case REOP_push_char_pos: case REOP_drop: case REOP_word_boundary: + case REOP_word_boundary_i: case REOP_not_word_boundary: + case REOP_not_word_boundary_i: case REOP_prev: /* no effect */ break; case REOP_save_start: case REOP_save_end: - val = bc_bufpos + 1; - capture_bitmapval |= 1; - break; case REOP_save_reset: - { - val = bc_bufpos + 1; - last = bc_bufpos + 2; - while (val < last) - capture_bitmapval++ |= 1; - } - break; case REOP_back_reference: + case REOP_back_reference_i: case REOP_backward_back_reference: - val = bc_bufpos + 1; - capture_bitmapval |= 2; - has_back_reference = TRUE; + case REOP_backward_back_reference_i: break; default: - /* safe behvior: we cannot predict the outcome */ - if (ret == -2) - ret = 0; - break; + /* safe behavior: we cannot predict the outcome */ + return TRUE; } pos += len; } - if (has_back_reference) { - /* check if there is back reference which references a capture - made in the some code */ - for(i = 0; i < CAPTURE_COUNT_MAX; i++) { - if (capture_bitmapi == 3) - return -1; - } - } - if (ret == -2) - ret = 0; return ret; } @@ -1039,7 +1567,7 @@ { int pos, opcode, len, count; uint32_t val; - + count = 0; pos = 0; while (pos < bc_buf_len) { @@ -1047,24 +1575,32 @@ len = reopcode_infoopcode.size; switch(opcode) { case REOP_range: + case REOP_range_i: val = get_u16(bc_buf + pos + 1); len += val * 4; goto simple_char; case REOP_range32: + case REOP_range32_i: val = get_u16(bc_buf + pos + 1); len += val * 8; goto simple_char; case REOP_char: + case REOP_char_i: case REOP_char32: + case REOP_char32_i: case REOP_dot: case REOP_any: simple_char: count++; break; case REOP_line_start: + case REOP_line_start_m: case REOP_line_end: + case REOP_line_end_m: case REOP_word_boundary: + case REOP_word_boundary_i: case REOP_not_word_boundary: + case REOP_not_word_boundary_i: break; default: return -1; @@ -1075,11 +1611,10 @@ } /* '*pp' is the first char after '<' */ -static int re_parse_group_name(char *buf, int buf_size, - const uint8_t **pp, BOOL is_utf16) +static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) { - const uint8_t *p; - uint32_t c; + const uint8_t *p, *p1; + uint32_t c, d; char *q; p = *pp; @@ -1090,11 +1625,18 @@ p++; if (*p != 'u') return -1; - c = lre_parse_escape(&p, is_utf16 * 2); + c = lre_parse_escape(&p, 2); // accept surrogate pairs } else if (c == '>') { break; } else if (c >= 128) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (is_hi_surrogate(c)) { + d = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + if (is_lo_surrogate(d)) { + c = from_surrogate(c, d); + p = p1; + } + } } else { p++; } @@ -1144,8 +1686,7 @@ /* potential named capture */ if (capture_name) { p += 3; - if (re_parse_group_name(name, sizeof(name), &p, - s->is_utf16) == 0) { + if (re_parse_group_name(name, sizeof(name), &p) == 0) { if (!strcmp(name, capture_name)) return capture_index; } @@ -1199,10 +1740,11 @@ const char *p, *buf_end; size_t len, name_len; int capture_index; - - name_len = strlen(name); + p = (char *)s->group_names.buf; + if (!p) return -1; buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); capture_index = 1; while (p < buf_end) { len = strlen(p); @@ -1216,13 +1758,48 @@ static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir); +static int re_parse_modifiers(REParseState *s, const uint8_t **pp) +{ + const uint8_t *p = *pp; + int mask = 0; + int val; + + for(;;) { + if (*p == 'i') { + val = LRE_FLAG_IGNORECASE; + } else if (*p == 'm') { + val = LRE_FLAG_MULTILINE; + } else if (*p == 's') { + val = LRE_FLAG_DOTALL; + } else { + break; + } + if (mask & val) + return re_parse_error(s, "duplicate modifier: '%c'", *p); + mask |= val; + p++; + } + *pp = p; + return mask; +} + +static BOOL update_modifier(BOOL val, int add_mask, int remove_mask, + int mask) +{ + if (add_mask & mask) + val = TRUE; + if (remove_mask & mask) + val = FALSE; + return val; +} + static int re_parse_term(REParseState *s, BOOL is_backward_dir) { const uint8_t *p; int c, last_atom_start, quant_min, quant_max, last_capture_count; BOOL greedy, add_zero_advance_check, is_neg, is_backward_lookahead; - CharRange cr_s, *cr = &cr_s; - + REStringList cr_s, *cr = &cr_s; + last_atom_start = -1; last_capture_count = 0; p = s->buf_ptr; @@ -1230,11 +1807,11 @@ switch(c) { case '^': p++; - re_emit_op(s, REOP_line_start); + re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start); break; case '$': p++; - re_emit_op(s, REOP_line_end); + re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end); break; case '.': p++; @@ -1247,7 +1824,7 @@ re_emit_op(s, REOP_prev); break; case '{': - if (s->is_utf16) { + if (s->is_unicode) { return re_parse_error(s, "syntax error"); } else if (!is_digit(p1)) { /* Annex B: we accept '{' not followed by digits as a @@ -1284,6 +1861,44 @@ p = s->buf_ptr; if (re_parse_expect(s, &p, ')')) return -1; + } else if (p2 == 'i' || p2 == 'm' || p2 == 's' || p2 == '-') { + BOOL saved_ignore_case, saved_multi_line, saved_dotall; + int add_mask, remove_mask; + p += 2; + remove_mask = 0; + add_mask = re_parse_modifiers(s, &p); + if (add_mask < 0) + return -1; + if (*p == '-') { + p++; + remove_mask = re_parse_modifiers(s, &p); + if (remove_mask < 0) + return -1; + } + if ((add_mask == 0 && remove_mask == 0) || + (add_mask & remove_mask) != 0) { + return re_parse_error(s, "invalid modifiers"); + } + if (re_parse_expect(s, &p, ':')) + return -1; + saved_ignore_case = s->ignore_case; + saved_multi_line = s->multi_line; + saved_dotall = s->dotall; + s->ignore_case = update_modifier(s->ignore_case, add_mask, remove_mask, LRE_FLAG_IGNORECASE); + s->multi_line = update_modifier(s->multi_line, add_mask, remove_mask, LRE_FLAG_MULTILINE); + s->dotall = update_modifier(s->dotall, add_mask, remove_mask, LRE_FLAG_DOTALL); + + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + s->ignore_case = saved_ignore_case; + s->multi_line = saved_multi_line; + s->dotall = saved_dotall; } else if ((p2 == '=' || p2 == '!')) { is_neg = (p2 == '!'); is_backward_lookahead = FALSE; @@ -1299,7 +1914,7 @@ lookahead: /* Annex B allows lookahead to be used as an atom for the quantifiers */ - if (!s->is_utf16 && !is_backward_lookahead) { + if (!s->is_unicode && !is_backward_lookahead) { last_atom_start = s->byte_code.size; last_capture_count = s->capture_count; } @@ -1318,7 +1933,7 @@ } else if (p2 == '<') { p += 3; if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), - &p, s->is_utf16)) { + &p)) { return re_parse_error(s, "invalid group name"); } if (find_group_name(s, s->u.tmp_buf) > 0) { @@ -1345,15 +1960,15 @@ capture_index = s->capture_count++; re_emit_op_u8(s, REOP_save_start + is_backward_dir, capture_index); - + s->buf_ptr = p; if (re_parse_disjunction(s, is_backward_dir)) return -1; p = s->buf_ptr; - + re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, capture_index); - + if (re_parse_expect(s, &p, ')')) return -1; } @@ -1362,28 +1977,32 @@ switch(p1) { case 'b': case 'B': - re_emit_op(s, REOP_word_boundary + (p1 != 'b')); + if (p1 != 'b') { + re_emit_op(s, s->ignore_case ? REOP_not_word_boundary_i : REOP_not_word_boundary); + } else { + re_emit_op(s, s->ignore_case ? REOP_word_boundary_i : REOP_word_boundary); + } p += 2; break; case 'k': { const uint8_t *p1; int dummy_res; - + p1 = p; if (p12 != '<') { /* annex B: we tolerate invalid group names in non unicode mode if there is no named capture definition */ - if (s->is_utf16 || re_has_named_captures(s)) + if (s->is_unicode || re_has_named_captures(s)) return re_parse_error(s, "expecting group name"); else goto parse_class_atom; } p1 += 3; if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), - &p1, s->is_utf16)) { - if (s->is_utf16 || re_has_named_captures(s)) + &p1)) { + if (s->is_unicode || re_has_named_captures(s)) return re_parse_error(s, "invalid group name"); else goto parse_class_atom; @@ -1394,7 +2013,7 @@ after (inefficient, but hopefully not common */ c = re_parse_captures(s, &dummy_res, s->u.tmp_buf); if (c < 0) { - if (s->is_utf16 || re_has_named_captures(s)) + if (s->is_unicode || re_has_named_captures(s)) return re_parse_error(s, "group name not defined"); else goto parse_class_atom; @@ -1406,7 +2025,7 @@ case '0': p += 2; c = 0; - if (s->is_utf16) { + if (s->is_unicode) { if (is_digit(*p)) { return re_parse_error(s, "invalid decimal escape in regular expression"); } @@ -1422,13 +2041,13 @@ goto normal_char; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': - case '9': + case '9': { const uint8_t *q = ++p; - + c = parse_digits(&p, FALSE); if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { - if (!s->is_utf16) { + if (!s->is_unicode) { /* Annex B.1.4: accept legacy octal */ p = q; if (*p <= '7') { @@ -1451,7 +2070,8 @@ emit_back_reference: last_atom_start = s->byte_code.size; last_capture_count = s->capture_count; - re_emit_op_u8(s, REOP_back_reference + is_backward_dir, c); + + re_emit_op_u8(s, REOP_back_reference + 2 * is_backward_dir + s->ignore_case, c); } break; default: @@ -1470,7 +2090,7 @@ break; case '': case '}': - if (s->is_utf16) + if (s->is_unicode) return re_parse_error(s, "syntax error"); goto parse_class_atom; default: @@ -1485,18 +2105,14 @@ re_emit_op(s, REOP_prev); if (c >= CLASS_RANGE_BASE) { int ret; - /* Note: canonicalization is not needed */ - ret = re_emit_range(s, cr); - cr_free(cr); + ret = re_emit_string_list(s, cr); + re_string_list_free(cr); if (ret) return -1; } else { if (s->ignore_case) - c = lre_canonicalize(c, s->is_utf16); - if (c <= 0xffff) - re_emit_op_u16(s, REOP_char, c); - else - re_emit_op_u32(s, REOP_char32, c); + c = lre_canonicalize(c, s->is_unicode); + re_emit_char(s, c); } if (is_backward_dir) re_emit_op(s, REOP_prev); @@ -1528,7 +2144,7 @@ /* As an extension (see ES6 annex B), we accept '{' not followed by digits as a normal atom */ if (!is_digit(p1)) { - if (s->is_utf16) + if (s->is_unicode) goto invalid_quant_count; break; } @@ -1547,7 +2163,7 @@ quant_max = INT32_MAX; /* infinity */ } } - if (*p != '}' && !s->is_utf16) { + if (*p != '}' && !s->is_unicode) { /* Annex B: normal atom if invalid '{' syntax */ p = p1; break; @@ -1566,7 +2182,7 @@ } if (greedy) { int len, pos; - + if (quant_max > 0) { /* specific optimization for simple quantifiers */ if (dbuf_error(&s->byte_code)) @@ -1575,7 +2191,7 @@ s->byte_code.size - last_atom_start); if (len > 0) { re_emit_op(s, REOP_match); - + if (dbuf_insert(&s->byte_code, last_atom_start, 17)) goto out_of_memory; pos = last_atom_start; @@ -1592,15 +2208,17 @@ goto done; } } - + if (dbuf_error(&s->byte_code)) goto out_of_memory; - add_zero_advance_check = (re_check_advance(s->byte_code.buf + last_atom_start, - s->byte_code.size - last_atom_start) == 0); - } else { - add_zero_advance_check = FALSE; } - + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + { int len, pos; len = s->byte_code.size - last_atom_start; @@ -1616,38 +2234,34 @@ } if (quant_max == 0) { s->byte_code.size = last_atom_start; - } else if (quant_max == 1) { - if (dbuf_insert(&s->byte_code, last_atom_start, 5)) - goto out_of_memory; - s->byte_code.buflast_atom_start = REOP_split_goto_first + - greedy; - put_u32(s->byte_code.buf + last_atom_start + 1, len); - } else if (quant_max == INT32_MAX) { + } else if (quant_max == 1 || quant_max == INT32_MAX) { + BOOL has_goto = (quant_max == INT32_MAX); if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) goto out_of_memory; s->byte_code.buflast_atom_start = REOP_split_goto_first + greedy; put_u32(s->byte_code.buf + last_atom_start + 1, - len + 5 + add_zero_advance_check); + len + 5 * has_goto + add_zero_advance_check * 2); if (add_zero_advance_check) { - /* avoid infinite loop by stoping the - recursion if no advance was made in the - atom (only works if the atom has no - side effect) */ s->byte_code.buflast_atom_start + 1 + 4 = REOP_push_char_pos; - re_emit_goto(s, REOP_bne_char_pos, last_atom_start); - } else { - re_emit_goto(s, REOP_goto, last_atom_start); + re_emit_op(s, REOP_check_advance); } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); } else { - if (dbuf_insert(&s->byte_code, last_atom_start, 10)) + if (dbuf_insert(&s->byte_code, last_atom_start, 10 + add_zero_advance_check)) goto out_of_memory; pos = last_atom_start; s->byte_code.bufpos++ = REOP_push_i32; put_u32(s->byte_code.buf + pos, quant_max); pos += 4; s->byte_code.bufpos++ = REOP_split_goto_first + greedy; - put_u32(s->byte_code.buf + pos, len + 5); + put_u32(s->byte_code.buf + pos, len + 5 + add_zero_advance_check * 2); + pos += 4; + if (add_zero_advance_check) { + s->byte_code.bufpos++ = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } re_emit_goto(s, REOP_loop, last_atom_start + 5); re_emit_op(s, REOP_drop); } @@ -1671,22 +2285,25 @@ if (quant_max == INT32_MAX) { pos = s->byte_code.size; re_emit_op_u32(s, REOP_split_goto_first + greedy, - len + 5 + add_zero_advance_check); + len + 5 + add_zero_advance_check * 2); if (add_zero_advance_check) re_emit_op(s, REOP_push_char_pos); /* copy the atom */ dbuf_put_self(&s->byte_code, last_atom_start, len); if (add_zero_advance_check) - re_emit_goto(s, REOP_bne_char_pos, pos); - else - re_emit_goto(s, REOP_goto, pos); + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_goto, pos); } else if (quant_max > quant_min) { re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min); pos = s->byte_code.size; - re_emit_op_u32(s, REOP_split_goto_first + greedy, len + 5); + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); /* copy the atom */ dbuf_put_self(&s->byte_code, last_atom_start, len); - + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); re_emit_goto(s, REOP_loop, pos); re_emit_op(s, REOP_drop); } @@ -1727,7 +2344,7 @@ speed is not really critical here) */ end = s->byte_code.size; term_size = end - term_start; - if (dbuf_realloc(&s->byte_code, end + term_size)) + if (dbuf_claim(&s->byte_code, term_size)) return -1; memmove(s->byte_code.buf + start + term_size, s->byte_code.buf + start, @@ -1738,14 +2355,14 @@ } return 0; } - + static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir) { int start, len, pos; if (lre_check_stack_overflow(s->opaque, 0)) return re_parse_error(s, "stack overflow"); - + start = s->byte_code.size; if (re_parse_alternative(s, is_backward_dir)) return -1; @@ -1765,7 +2382,7 @@ if (re_parse_alternative(s, is_backward_dir)) return -1; - + /* patch the goto */ len = s->byte_code.size - (pos + 4); put_u32(s->byte_code.buf + pos, len); @@ -1778,7 +2395,7 @@ { int stack_size, stack_size_max, pos, opcode, len; uint32_t val; - + stack_size = 0; stack_size_max = 0; bc_buf += RE_HEADER_LEN; @@ -1800,15 +2417,17 @@ } break; case REOP_drop: - case REOP_bne_char_pos: + case REOP_check_advance: assert(stack_size > 0); stack_size--; break; case REOP_range: + case REOP_range_i: val = get_u16(bc_buf + pos + 1); len += val * 4; break; case REOP_range32: + case REOP_range32_i: val = get_u16(bc_buf + pos + 1); len += val * 8; break; @@ -1818,6 +2437,17 @@ return stack_size_max; } +static void *lre_bytecode_realloc(void *opaque, void *ptr, size_t size) +{ + if (size > (INT32_MAX / 2)) { + /* the bytecode cannot be larger than 2G. Leave some slack to + avoid some overflows. */ + return NULL; + } else { + return lre_realloc(opaque, ptr, size); + } +} + /* 'buf' must be a zero terminated UTF-8 string of length buf_len. Return NULL if error and allocate an error message in *perror_msg, otherwise the compiled bytecode and its length in plen. @@ -1829,29 +2459,31 @@ REParseState s_s, *s = &s_s; int stack_size; BOOL is_sticky; - + memset(s, 0, sizeof(*s)); s->opaque = opaque; s->buf_ptr = (const uint8_t *)buf; s->buf_end = s->buf_ptr + buf_len; s->buf_start = s->buf_ptr; s->re_flags = re_flags; - s->is_utf16 = ((re_flags & LRE_FLAG_UTF16) != 0); + s->is_unicode = ((re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0); is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0); s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->unicode_sets = ((re_flags & LRE_FLAG_UNICODE_SETS) != 0); s->capture_count = 1; s->total_capture_count = -1; s->has_named_captures = -1; - - dbuf_init2(&s->byte_code, opaque, lre_realloc); + + dbuf_init2(&s->byte_code, opaque, lre_bytecode_realloc); dbuf_init2(&s->group_names, opaque, lre_realloc); - dbuf_putc(&s->byte_code, re_flags); /* first element is the flags */ + dbuf_put_u16(&s->byte_code, re_flags); /* first element is the flags */ dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ dbuf_putc(&s->byte_code, 0); /* stack size */ dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ - + if (!is_sticky) { /* iterate thru all positions (about the same as .*?( ... ) ) . We do it without an explicit loop so that lock step @@ -1873,7 +2505,7 @@ } re_emit_op_u8(s, REOP_save_end, 0); - + re_emit_op(s, REOP_match); if (*s->buf_ptr != '\0') { @@ -1885,28 +2517,30 @@ re_parse_out_of_memory(s); goto error; } - + stack_size = compute_stack_size(s->byte_code.buf, s->byte_code.size); if (stack_size < 0) { re_parse_error(s, "too many imbricated quantifiers"); goto error; } - + s->byte_code.bufRE_HEADER_CAPTURE_COUNT = s->capture_count; s->byte_code.bufRE_HEADER_STACK_SIZE = stack_size; - put_u32(s->byte_code.buf + 3, s->byte_code.size - RE_HEADER_LEN); + put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, + s->byte_code.size - RE_HEADER_LEN); /* add the named groups if needed */ if (s->group_names.size > (s->capture_count - 1)) { dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size); - s->byte_code.bufRE_HEADER_FLAGS |= LRE_FLAG_NAMED_GROUPS; + put_u16(s->byte_code.buf + RE_HEADER_FLAGS, + lre_get_flags(s->byte_code.buf) | LRE_FLAG_NAMED_GROUPS); } dbuf_free(&s->group_names); - + #ifdef DUMP_REOP lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); #endif - + error_msg0 = '\0'; *plen = s->byte_code.size; return s->byte_code.buf; @@ -1925,93 +2559,86 @@ (c == '_')); } -#define GET_CHAR(c, cptr, cbuf_end) \ +#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ do { \ if (cbuf_type == 0) { \ c = *cptr++; \ } else { \ - uint32_t __c1; \ - c = *(uint16_t *)cptr; \ - cptr += 2; \ - if (c >= 0xd800 && c < 0xdc00 && \ - cbuf_type == 2 && cptr < cbuf_end) { \ - __c1 = *(uint16_t *)cptr; \ - if (__c1 >= 0xdc00 && __c1 < 0xe000) { \ - c = (((c & 0x3ff) << 10) | (__c1 & 0x3ff)) + 0x10000; \ - cptr += 2; \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p++); \ } \ } \ + cptr = (const void *)_p; \ } \ } while (0) -#define PEEK_CHAR(c, cptr, cbuf_end) \ - do { \ - if (cbuf_type == 0) { \ - c = cptr0; \ - } else { \ - uint32_t __c1; \ - c = ((uint16_t *)cptr)0; \ - if (c >= 0xd800 && c < 0xdc00 && \ - cbuf_type == 2 && (cptr + 2) < cbuf_end) { \ - __c1 = ((uint16_t *)cptr)1; \ - if (__c1 >= 0xdc00 && __c1 < 0xe000) { \ - c = (((c & 0x3ff) << 10) | (__c1 & 0x3ff)) + 0x10000; \ +#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr0; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p); \ } \ } \ - } \ + } \ } while (0) -#define PEEK_PREV_CHAR(c, cptr, cbuf_start) \ - do { \ - if (cbuf_type == 0) { \ - c = cptr-1; \ - } else { \ - uint32_t __c1; \ - c = ((uint16_t *)cptr)-1; \ - if (c >= 0xdc00 && c < 0xe000 && \ - cbuf_type == 2 && (cptr - 4) >= cbuf_start) { \ - __c1 = ((uint16_t *)cptr)-2; \ - if (__c1 >= 0xd800 && __c1 < 0xdc00 ) { \ - c = (((__c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000; \ +#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr-1; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p-1)) { \ + c = from_surrogate(*--_p, c); \ } \ } \ } \ } while (0) -#define GET_PREV_CHAR(c, cptr, cbuf_start) \ - do { \ - if (cbuf_type == 0) { \ - cptr--; \ - c = cptr0; \ - } else { \ - uint32_t __c1; \ - cptr -= 2; \ - c = ((uint16_t *)cptr)0; \ - if (c >= 0xdc00 && c < 0xe000 && \ - cbuf_type == 2 && cptr > cbuf_start) { \ - __c1 = ((uint16_t *)cptr)-1; \ - if (__c1 >= 0xd800 && __c1 < 0xdc00 ) { \ - cptr -= 2; \ - c = (((__c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000; \ +#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + c = cptr0; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p-1)) { \ + c = from_surrogate(*--_p, c); \ } \ } \ + cptr = (const void *)_p; \ } \ } while (0) -#define PREV_CHAR(cptr, cbuf_start) \ - do { \ - if (cbuf_type == 0) { \ - cptr--; \ - } else { \ - cptr -= 2; \ - if (cbuf_type == 2) { \ - c = ((uint16_t *)cptr)0; \ - if (c >= 0xdc00 && c < 0xe000 && cptr > cbuf_start) { \ - c = ((uint16_t *)cptr)-1; \ - if (c >= 0xd800 && c < 0xdc00) \ - cptr -= 2; \ +#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + if (is_lo_surrogate(*_p) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p-1)) { \ + --_p; \ } \ } \ + cptr = (const void *)_p; \ } \ } while (0) @@ -2037,12 +2664,11 @@ const uint8_t *cbuf; const uint8_t *cbuf_end; /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ - int cbuf_type; + int cbuf_type; int capture_count; int stack_size_max; - BOOL multi_line; - BOOL ignore_case; - BOOL is_utf16; + BOOL is_unicode; + int interrupt_counter; void *opaque; /* used for stack overflow check */ size_t state_size; @@ -2089,7 +2715,17 @@ return 0; } -/* return 1 if match, 0 if not match or -1 if error. */ +static int lre_poll_timeout(REExecContext *s) +{ + if (unlikely(--s->interrupt_counter <= 0)) { + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + if (lre_check_timeout(s->opaque)) + return LRE_RET_TIMEOUT; + } + return 0; +} + +/* return 1 if match, 0 if not match or < 0 if error. */ static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, StackInt *stack, int stack_len, const uint8_t *pc, const uint8_t *cptr, @@ -2099,7 +2735,7 @@ int cbuf_type; uint32_t val, c; const uint8_t *cbuf_end; - + cbuf_type = s->cbuf_type; cbuf_end = s->cbuf_end; @@ -2120,6 +2756,8 @@ ret = 0; recurse: for(;;) { + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; if (s->state_stack_len == 0) return ret; rs = (REExecState *)(s->state_stack + @@ -2151,7 +2789,7 @@ /* go backward */ char_count = get_u32(pc + 12); for(i = 0; i < char_count; i++) { - PREV_CHAR(cptr, s->cbuf); + PREV_CHAR(cptr, s->cbuf, cbuf_type); } pc = (pc + 16) + (int)get_u32(pc); rs->cptr = cptr; @@ -2177,18 +2815,20 @@ } break; case REOP_char32: + case REOP_char32_i: val = get_u32(pc); pc += 4; goto test_char; case REOP_char: + case REOP_char_i: val = get_u16(pc); pc += 2; test_char: if (cptr >= cbuf_end) goto no_match; - GET_CHAR(c, cptr, cbuf_end); - if (s->ignore_case) { - c = lre_canonicalize(c, s->is_utf16); + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_char_i || opcode == REOP_char32_i) { + c = lre_canonicalize(c, s->is_unicode); } if (val != c) goto no_match; @@ -2197,7 +2837,7 @@ case REOP_split_next_first: { const uint8_t *pc1; - + val = get_u32(pc); pc += 4; if (opcode == REOP_split_next_first) { @@ -2209,7 +2849,7 @@ ret = push_state(s, capture, stack, stack_len, pc1, cptr, RE_EXEC_STATE_SPLIT, 0); if (ret < 0) - return -1; + return LRE_RET_MEMORY_ERROR; break; } case REOP_lookahead: @@ -2221,42 +2861,46 @@ RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead, 0); if (ret < 0) - return -1; + return LRE_RET_MEMORY_ERROR; break; - + case REOP_goto: val = get_u32(pc); pc += 4 + (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; break; case REOP_line_start: + case REOP_line_start_m: if (cptr == s->cbuf) break; - if (!s->multi_line) + if (opcode == REOP_line_start) goto no_match; - PEEK_PREV_CHAR(c, cptr, s->cbuf); + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); if (!is_line_terminator(c)) goto no_match; break; case REOP_line_end: + case REOP_line_end_m: if (cptr == cbuf_end) break; - if (!s->multi_line) + if (opcode == REOP_line_end) goto no_match; - PEEK_CHAR(c, cptr, cbuf_end); + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); if (!is_line_terminator(c)) goto no_match; break; case REOP_dot: if (cptr == cbuf_end) goto no_match; - GET_CHAR(c, cptr, cbuf_end); + GET_CHAR(c, cptr, cbuf_end, cbuf_type); if (is_line_terminator(c)) goto no_match; break; case REOP_any: if (cptr == cbuf_end) goto no_match; - GET_CHAR(c, cptr, cbuf_end); + GET_CHAR(c, cptr, cbuf_end, cbuf_type); break; case REOP_save_start: case REOP_save_end: @@ -2291,45 +2935,55 @@ pc += 4; if (--stackstack_len - 1 != 0) { pc += (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; } break; case REOP_push_char_pos: stackstack_len++ = (uintptr_t)cptr; break; - case REOP_bne_char_pos: - val = get_u32(pc); - pc += 4; - if (stack--stack_len != (uintptr_t)cptr) - pc += (int)val; + case REOP_check_advance: + if (stack--stack_len == (uintptr_t)cptr) + goto no_match; break; case REOP_word_boundary: + case REOP_word_boundary_i: case REOP_not_word_boundary: + case REOP_not_word_boundary_i: { BOOL v1, v2; + int ignore_case = (opcode == REOP_word_boundary_i || opcode == REOP_not_word_boundary_i); + BOOL is_boundary = (opcode == REOP_word_boundary || opcode == REOP_word_boundary_i); /* char before */ if (cptr == s->cbuf) { v1 = FALSE; } else { - PEEK_PREV_CHAR(c, cptr, s->cbuf); + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (ignore_case) + c = lre_canonicalize(c, s->is_unicode); v1 = is_word_char(c); } /* current char */ if (cptr >= cbuf_end) { v2 = FALSE; } else { - PEEK_CHAR(c, cptr, cbuf_end); + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (ignore_case) + c = lre_canonicalize(c, s->is_unicode); v2 = is_word_char(c); } - if (v1 ^ v2 ^ (REOP_not_word_boundary - opcode)) + if (v1 ^ v2 ^ is_boundary) goto no_match; } break; case REOP_back_reference: + case REOP_back_reference_i: case REOP_backward_back_reference: + case REOP_backward_back_reference_i: { const uint8_t *cptr1, *cptr1_end, *cptr1_start; uint32_t c1, c2; - + val = *pc++; if (val >= s->capture_count) goto no_match; @@ -2337,16 +2991,17 @@ cptr1_end = capture2 * val + 1; if (!cptr1_start || !cptr1_end) break; - if (opcode == REOP_back_reference) { + if (opcode == REOP_back_reference || + opcode == REOP_back_reference_i) { cptr1 = cptr1_start; while (cptr1 < cptr1_end) { if (cptr >= cbuf_end) goto no_match; - GET_CHAR(c1, cptr1, cptr1_end); - GET_CHAR(c2, cptr, cbuf_end); - if (s->ignore_case) { - c1 = lre_canonicalize(c1, s->is_utf16); - c2 = lre_canonicalize(c2, s->is_utf16); + GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); + GET_CHAR(c2, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_back_reference_i) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); } if (c1 != c2) goto no_match; @@ -2356,11 +3011,11 @@ while (cptr1 > cptr1_start) { if (cptr == s->cbuf) goto no_match; - GET_PREV_CHAR(c1, cptr1, cptr1_start); - GET_PREV_CHAR(c2, cptr, s->cbuf); - if (s->ignore_case) { - c1 = lre_canonicalize(c1, s->is_utf16); - c2 = lre_canonicalize(c2, s->is_utf16); + GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); + GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); + if (opcode == REOP_backward_back_reference_i) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); } if (c1 != c2) goto no_match; @@ -2369,17 +3024,18 @@ } break; case REOP_range: + case REOP_range_i: { int n; uint32_t low, high, idx_min, idx_max, idx; - + n = get_u16(pc); /* n must be >= 1 */ pc += 2; if (cptr >= cbuf_end) goto no_match; - GET_CHAR(c, cptr, cbuf_end); - if (s->ignore_case) { - c = lre_canonicalize(c, s->is_utf16); + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_range_i) { + c = lre_canonicalize(c, s->is_unicode); } idx_min = 0; low = get_u16(pc + 0 * 4); @@ -2409,17 +3065,18 @@ } break; case REOP_range32: + case REOP_range32_i: { int n; uint32_t low, high, idx_min, idx_max, idx; - + n = get_u16(pc); /* n must be >= 1 */ pc += 2; if (cptr >= cbuf_end) goto no_match; - GET_CHAR(c, cptr, cbuf_end); - if (s->ignore_case) { - c = lre_canonicalize(c, s->is_utf16); + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_range32_i) { + c = lre_canonicalize(c, s->is_unicode); } idx_min = 0; low = get_u32(pc + 0 * 8); @@ -2449,7 +3106,7 @@ /* go to the previous char */ if (cptr == s->cbuf) goto no_match; - PREV_CHAR(cptr, s->cbuf); + PREV_CHAR(cptr, s->cbuf, cbuf_type); break; case REOP_simple_greedy_quant: { @@ -2457,19 +3114,22 @@ size_t q; intptr_t res; const uint8_t *pc1; - + next_pos = get_u32(pc); quant_min = get_u32(pc + 4); quant_max = get_u32(pc + 8); pc += 16; pc1 = pc; pc += (int)next_pos; - + q = 0; for(;;) { + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; res = lre_exec_backtrack(s, capture, stack, stack_len, pc1, cptr, TRUE); - if (res == -1) + if (res == LRE_RET_MEMORY_ERROR || + res == LRE_RET_TIMEOUT) return res; if (!res) break; @@ -2487,7 +3147,7 @@ RE_EXEC_STATE_GREEDY_QUANT, q - quant_min); if (ret < 0) - return -1; + return LRE_RET_MEMORY_ERROR; } } break; @@ -2497,7 +3157,7 @@ } } -/* Return 1 if match, 0 if not match or -1 if error. cindex is the +/* Return 1 if match, 0 if not match or < 0 if error (see LRE_RET_x). cindex is the starting position of the match and must be such as 0 <= cindex <= clen. */ int lre_exec(uint8_t **capture, @@ -2507,18 +3167,18 @@ REExecContext s_s, *s = &s_s; int re_flags, i, alloca_size, ret; StackInt *stack_buf; - - re_flags = bc_bufRE_HEADER_FLAGS; - s->multi_line = (re_flags & LRE_FLAG_MULTILINE) != 0; - s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0; - s->is_utf16 = (re_flags & LRE_FLAG_UTF16) != 0; + const uint8_t *cptr; + + re_flags = lre_get_flags(bc_buf); + s->is_unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; s->capture_count = bc_bufRE_HEADER_CAPTURE_COUNT; s->stack_size_max = bc_bufRE_HEADER_STACK_SIZE; s->cbuf = cbuf; s->cbuf_end = cbuf + (clen << cbuf_type); s->cbuf_type = cbuf_type; - if (s->cbuf_type == 1 && s->is_utf16) + if (s->cbuf_type == 1 && s->is_unicode) s->cbuf_type = 2; + s->interrupt_counter = INTERRUPT_COUNTER_INIT; s->opaque = opaque; s->state_size = sizeof(REExecState) + @@ -2527,13 +3187,22 @@ s->state_stack = NULL; s->state_stack_len = 0; s->state_stack_size = 0; - + for(i = 0; i < s->capture_count * 2; i++) capturei = NULL; alloca_size = s->stack_size_max * sizeof(stack_buf0); stack_buf = alloca(alloca_size); + + cptr = cbuf + (cindex << cbuf_type); + if (0 < cindex && cindex < clen && s->cbuf_type == 2) { + const uint16_t *p = (const uint16_t *)cptr; + if (is_lo_surrogate(*p) && is_hi_surrogate(p-1)) { + cptr = (const uint8_t *)(p - 1); + } + } + ret = lre_exec_backtrack(s, capture, stack_buf, 0, bc_buf + RE_HEADER_LEN, - cbuf + (cindex << cbuf_type), FALSE); + cptr, FALSE); lre_realloc(s->opaque, s->state_stack, 0); return ret; } @@ -2545,7 +3214,7 @@ int lre_get_flags(const uint8_t *bc_buf) { - return bc_bufRE_HEADER_FLAGS; + return get_u16(bc_buf + RE_HEADER_FLAGS); } /* Return NULL if no group names. Otherwise, return a pointer to @@ -2555,8 +3224,8 @@ uint32_t re_bytecode_len; if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) return NULL; - re_bytecode_len = get_u32(bc_buf + 3); - return (const char *)(bc_buf + 7 + re_bytecode_len); + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); } #ifdef TEST @@ -2573,27 +3242,28 @@ int main(int argc, char **argv) { - int len, ret, i; + int len, flags, ret, i; uint8_t *bc; char error_msg64; uint8_t *captureCAPTURE_COUNT_MAX * 2; const char *input; int input_len, capture_count; - - if (argc < 3) { - printf("usage: %s regexp input\n", argv0); - exit(1); + + if (argc < 4) { + printf("usage: %s regexp flags input\n", argv0); + return 1; } + flags = atoi(argv2); bc = lre_compile(&len, error_msg, sizeof(error_msg), argv1, - strlen(argv1), 0, NULL); + strlen(argv1), flags, NULL); if (!bc) { fprintf(stderr, "error: %s\n", error_msg); exit(1); } - input = argv2; + input = argv3; input_len = strlen(input); - + ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); printf("ret=%d\n", ret); if (ret == 1) {
View file
gpac-2.4.0.tar.gz/src/quickjs/libregexp.h -> gpac-26.02.0.tar.gz/src/quickjs/libregexp.h
Changed
@@ -1,6 +1,6 @@ /* * Regular Expression Engine - * + * * Copyright (c) 2017-2018 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,19 +25,20 @@ #define LIBREGEXP_H #include <stddef.h> - -#include "libunicode.h" - -#define LRE_BOOL int /* for documentation purposes */ +#include <stdint.h> #define LRE_FLAG_GLOBAL (1 << 0) #define LRE_FLAG_IGNORECASE (1 << 1) #define LRE_FLAG_MULTILINE (1 << 2) #define LRE_FLAG_DOTALL (1 << 3) -#define LRE_FLAG_UTF16 (1 << 4) +#define LRE_FLAG_UNICODE (1 << 4) #define LRE_FLAG_STICKY (1 << 5) - +#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ #define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ +#define LRE_FLAG_UNICODE_SETS (1 << 8) + +#define LRE_RET_MEMORY_ERROR (-1) +#define LRE_RET_TIMEOUT (-2) uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, const char *buf, size_t buf_len, int re_flags, @@ -50,43 +51,11 @@ int cbuf_type, void *opaque); int lre_parse_escape(const uint8_t **pp, int allow_utf16); -LRE_BOOL lre_is_space(int c); -/* must be provided by the user */ -LRE_BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size); +/* must be provided by the user, return non zero if overflow */ +int lre_check_stack_overflow(void *opaque, size_t alloca_size); +/* must be provided by the user, return non zero if time out */ +int lre_check_timeout(void *opaque); void *lre_realloc(void *opaque, void *ptr, size_t size); -/* JS identifier test */ -extern uint32_t const lre_id_start_table_ascii4; -extern uint32_t const lre_id_continue_table_ascii4; - -static inline int lre_js_is_ident_first(int c) -{ - if ((uint32_t)c < 128) { - return (lre_id_start_table_asciic >> 5 >> (c & 31)) & 1; - } else { -#ifdef CONFIG_ALL_UNICODE - return lre_is_id_start(c); -#else - return !lre_is_space(c); -#endif - } -} - -static inline int lre_js_is_ident_next(int c) -{ - if ((uint32_t)c < 128) { - return (lre_id_continue_table_asciic >> 5 >> (c & 31)) & 1; - } else { - /* ZWNJ and ZWJ are accepted in identifiers */ -#ifdef CONFIG_ALL_UNICODE - return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; -#else - return !lre_is_space(c) || c == 0x200C || c == 0x200D; -#endif - } -} - -#undef LRE_BOOL - #endif /* LIBREGEXP_H */
View file
gpac-2.4.0.tar.gz/src/quickjs/libunicode-table.h -> gpac-26.02.0.tar.gz/src/quickjs/libunicode-table.h
Changed
@@ -3,7 +3,7 @@ #include <stdint.h> -static const uint32_t case_conv_table1361 = { +static const uint32_t case_conv_table1378 = { 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, @@ -13,137 +13,143 @@ 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, - 0x00cd0100, 0x00ce0130, 0x00ce8130, 0x00cf0100, - 0x00cf8130, 0x00d00640, 0x00d30130, 0x00d38240, - 0x00d48130, 0x00d60240, 0x00d70130, 0x00d78240, - 0x00d88230, 0x00d98440, 0x00db8130, 0x00dc0240, - 0x00de0240, 0x00df8100, 0x00e20350, 0x00e38350, - 0x00e50350, 0x00e69040, 0x00ee8100, 0x00ef1240, - 0x00f801b4, 0x00f88350, 0x00fa0240, 0x00fb0130, - 0x00fb8130, 0x00fc2840, 0x01100130, 0x01111240, - 0x011d0131, 0x011d8240, 0x011e8130, 0x011f0131, - 0x011f8201, 0x01208240, 0x01218130, 0x01220130, - 0x01228130, 0x01230a40, 0x01280101, 0x01288101, - 0x01290101, 0x01298100, 0x012a0100, 0x012b0200, - 0x012c8100, 0x012d8100, 0x012e0101, 0x01300100, - 0x01308101, 0x01318100, 0x01328101, 0x01330101, - 0x01340100, 0x01348100, 0x01350101, 0x01358101, - 0x01360101, 0x01378100, 0x01388101, 0x01390100, - 0x013a8100, 0x013e8101, 0x01400100, 0x01410101, - 0x01418100, 0x01438101, 0x01440100, 0x01448100, - 0x01450200, 0x01460100, 0x01490100, 0x014e8101, - 0x014f0101, 0x01a28173, 0x01b80440, 0x01bb0240, - 0x01bd8300, 0x01bf8130, 0x01c30130, 0x01c40330, - 0x01c60130, 0x01c70230, 0x01c801d0, 0x01c89130, - 0x01d18930, 0x01d60100, 0x01d68300, 0x01d801d3, - 0x01d89100, 0x01e10173, 0x01e18900, 0x01e60100, - 0x01e68200, 0x01e78130, 0x01e80173, 0x01e88173, - 0x01ea8173, 0x01eb0173, 0x01eb8100, 0x01ec1840, - 0x01f80173, 0x01f88173, 0x01f90100, 0x01f98100, - 0x01fa01a0, 0x01fa8173, 0x01fb8240, 0x01fc8130, - 0x01fd0240, 0x01fe8330, 0x02001030, 0x02082030, - 0x02182000, 0x02281000, 0x02302240, 0x02453640, - 0x02600130, 0x02608e40, 0x02678100, 0x02686040, - 0x0298a630, 0x02b0a600, 0x02c381b5, 0x08502631, - 0x08638131, 0x08668131, 0x08682b00, 0x087e8300, - 0x09d05011, 0x09f80610, 0x09fc0620, 0x0e400174, - 0x0e408174, 0x0e410174, 0x0e418174, 0x0e420174, - 0x0e428174, 0x0e430174, 0x0e438180, 0x0e440180, - 0x0e482b30, 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, - 0x0ec70101, 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, - 0x0f4b81b6, 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, - 0x0f4d8180, 0x0f4f0130, 0x0f506040, 0x0f800800, - 0x0f840830, 0x0f880600, 0x0f8c0630, 0x0f900800, - 0x0f940830, 0x0f980800, 0x0f9c0830, 0x0fa00600, - 0x0fa40630, 0x0fa801b0, 0x0fa88100, 0x0fa901d3, - 0x0fa98100, 0x0faa01d3, 0x0faa8100, 0x0fab01d3, - 0x0fab8100, 0x0fac8130, 0x0fad8130, 0x0fae8130, - 0x0faf8130, 0x0fb00800, 0x0fb40830, 0x0fb80200, - 0x0fb90400, 0x0fbb0200, 0x0fbc0201, 0x0fbd0201, - 0x0fbe0201, 0x0fc008b7, 0x0fc40867, 0x0fc808b8, - 0x0fcc0868, 0x0fd008b8, 0x0fd40868, 0x0fd80200, - 0x0fd901b9, 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, - 0x0fdb81d7, 0x0fdc0230, 0x0fdd0230, 0x0fde0161, - 0x0fdf0173, 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, - 0x0fe301b2, 0x0fe381d8, 0x0fe40430, 0x0fe60162, - 0x0fe80200, 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, - 0x0feb81d0, 0x0fec0230, 0x0fed0230, 0x0ff00201, - 0x0ff101d3, 0x0ff181d3, 0x0ff201ba, 0x0ff28101, - 0x0ff301b0, 0x0ff381d3, 0x0ff40230, 0x0ff50230, - 0x0ff60131, 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, - 0x0ffb01b2, 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, - 0x0ffe0162, 0x109301a0, 0x109501a0, 0x109581a0, - 0x10990131, 0x10a70101, 0x10b01031, 0x10b81001, - 0x10c18240, 0x125b1a31, 0x12681a01, 0x16002f31, - 0x16182f01, 0x16300240, 0x16310130, 0x16318130, - 0x16320130, 0x16328100, 0x16330100, 0x16338640, - 0x16368130, 0x16370130, 0x16378130, 0x16380130, - 0x16390240, 0x163a8240, 0x163f0230, 0x16406440, - 0x16758440, 0x16790240, 0x16802600, 0x16938100, - 0x16968100, 0x53202e40, 0x53401c40, 0x53910e40, - 0x53993e40, 0x53bc8440, 0x53be8130, 0x53bf0a40, - 0x53c58240, 0x53c68130, 0x53c80440, 0x53ca0101, - 0x53cb1440, 0x53d50130, 0x53d58130, 0x53d60130, - 0x53d68130, 0x53d70130, 0x53d80130, 0x53d88130, - 0x53d90130, 0x53d98131, 0x53da0c40, 0x53e10240, - 0x53e20131, 0x53e28130, 0x53e30130, 0x53e38440, - 0x53fa8240, 0x55a98101, 0x55b85020, 0x7d8001b2, - 0x7d8081b2, 0x7d8101b2, 0x7d8181da, 0x7d8201da, - 0x7d8281b3, 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, - 0x7d8a81bb, 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, - 0x7fa09a01, 0x82002831, 0x82142801, 0x82582431, - 0x826c2401, 0x86403331, 0x86603301, 0x8c502031, - 0x8c602001, 0xb7202031, 0xb7302001, 0xf4802231, - 0xf4912201, + 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, + 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, + 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, + 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, + 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, + 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, + 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, + 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, + 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, + 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, + 0x01220130, 0x01228130, 0x01230a40, 0x01280101, + 0x01288101, 0x01290101, 0x01298100, 0x012a0100, + 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, + 0x01300100, 0x01308101, 0x01318100, 0x01320101, + 0x01328101, 0x01330101, 0x01340100, 0x01348100, + 0x01350101, 0x01358101, 0x01360101, 0x01378100, + 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, + 0x01400100, 0x01410101, 0x01418100, 0x01438101, + 0x01440100, 0x01448100, 0x01450200, 0x01460100, + 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, + 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, + 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, + 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, + 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, + 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, + 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, + 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, + 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, + 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, + 0x02001030, 0x02082030, 0x02182000, 0x02281000, + 0x02302240, 0x02453640, 0x02600130, 0x02608e40, + 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, + 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, + 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, + 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, + 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, + 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, + 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, + 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, + 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, + 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, + 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, + 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, + 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, + 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, + 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, + 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, + 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, + 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, + 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, + 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, + 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, + 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, + 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, + 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, + 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, + 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, + 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, + 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, + 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, + 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, + 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, + 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, + 0x16300240, 0x16310130, 0x16318130, 0x16320130, + 0x16328100, 0x16330100, 0x16338640, 0x16368130, + 0x16370130, 0x16378130, 0x16380130, 0x16390240, + 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, + 0x16790240, 0x16802600, 0x16938100, 0x16968100, + 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, + 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, + 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, + 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, + 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, + 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, + 0x53e30130, 0x53e38440, 0x53e58130, 0x53e60240, + 0x53e80240, 0x53eb0640, 0x53ee0130, 0x53fa8240, + 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, + 0x7d8101b2, 0x7d8181da, 0x7d8201da, 0x7d8281b3, + 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, + 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, + 0x82002831, 0x82142801, 0x82582431, 0x826c2401, + 0x82b80b31, 0x82be0f31, 0x82c60731, 0x82ca0231, + 0x82cb8b01, 0x82d18f01, 0x82d98701, 0x82dd8201, + 0x86403331, 0x86603301, 0x86a81631, 0x86b81601, + 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, + 0xf4802231, 0xf4912201, }; -static const uint8_t case_conv_table2361 = { +static const uint8_t case_conv_table2378 = { 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, - 0x08, 0x00, 0x53, 0x4a, 0x51, 0x00, 0x52, 0x00, - 0x53, 0x00, 0x3a, 0x54, 0x55, 0x00, 0x57, 0x59, - 0x3f, 0x5d, 0x5c, 0x00, 0x46, 0x61, 0x63, 0x42, - 0x64, 0x00, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, - 0x6c, 0x00, 0x6e, 0x00, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, 0x20, - 0x35, 0x00, 0x27, 0x00, 0x21, 0x00, 0x24, 0x22, - 0x2a, 0x00, 0x13, 0x6b, 0x6d, 0x00, 0x26, 0x24, - 0x27, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x3e, 0x1e, - 0x3f, 0x1f, 0x39, 0x3d, 0x22, 0x21, 0x41, 0x1e, - 0x40, 0x25, 0x25, 0x26, 0x28, 0x20, 0x2a, 0x49, - 0x2c, 0x43, 0x2e, 0x4b, 0x30, 0x4c, 0x32, 0x44, - 0x42, 0x99, 0x00, 0x00, 0x95, 0x8f, 0x7d, 0x7e, - 0x83, 0x84, 0x12, 0x80, 0x82, 0x76, 0x77, 0x12, - 0x7b, 0xa3, 0x7c, 0x78, 0x79, 0x8a, 0x92, 0x98, - 0xa6, 0xa0, 0x85, 0x00, 0x9a, 0xa1, 0x93, 0x75, - 0x33, 0x95, 0x00, 0x8e, 0x00, 0x74, 0x99, 0x98, - 0x97, 0x96, 0x00, 0x00, 0x9e, 0x00, 0x9c, 0x00, - 0xa1, 0xa0, 0x15, 0x2e, 0x2f, 0x30, 0xb4, 0xb5, - 0x4e, 0xaa, 0xa9, 0x12, 0x14, 0x1e, 0x21, 0x22, - 0x22, 0x2a, 0x34, 0x35, 0xa6, 0xa7, 0x36, 0x1f, - 0x4a, 0x00, 0x00, 0x97, 0x01, 0x5a, 0xda, 0x1d, - 0x36, 0x05, 0x00, 0xc4, 0xc3, 0xc6, 0xc5, 0xc8, - 0xc7, 0xca, 0xc9, 0xcc, 0xcb, 0xc4, 0xd5, 0x45, - 0xd6, 0x42, 0xd7, 0x46, 0xd8, 0xce, 0xd0, 0xd2, - 0xd4, 0xda, 0xd9, 0xee, 0xf6, 0xfe, 0x0e, 0x07, - 0x0f, 0x80, 0x9f, 0x00, 0x21, 0x80, 0xa3, 0xed, - 0x00, 0xc0, 0x40, 0xc6, 0x60, 0xe7, 0xdb, 0xe6, - 0x99, 0xc0, 0x00, 0x00, 0x06, 0x60, 0xdc, 0x29, - 0xfd, 0x15, 0x12, 0x06, 0x16, 0xf8, 0xdd, 0x06, - 0x15, 0x12, 0x84, 0x08, 0xc6, 0x16, 0xff, 0xdf, - 0x03, 0xc0, 0x40, 0x00, 0x46, 0x60, 0xde, 0xe0, - 0x6d, 0x37, 0x38, 0x39, 0x15, 0x14, 0x17, 0x16, - 0x00, 0x1a, 0x19, 0x1c, 0x1b, 0x00, 0x5f, 0xb7, - 0x65, 0x44, 0x47, 0x00, 0x4f, 0x62, 0x4e, 0x50, - 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0xa3, 0xa4, - 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x00, - 0x00, 0x5a, 0x00, 0x48, 0x00, 0x5b, 0x56, 0x58, - 0x60, 0x5e, 0x70, 0x69, 0x6f, 0x4d, 0x00, 0x00, - 0x3b, 0x67, 0xb8, 0x00, 0x00, 0x45, 0xa8, 0x8a, - 0x8b, 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, - 0xb0, 0x6f, 0xb2, 0x5c, 0x5b, 0x5e, 0x5d, 0x60, - 0x5f, 0x62, 0x61, 0x64, 0x63, 0x66, 0x65, 0x68, - 0x67, + 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, + 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, + 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x52, 0x63, 0x65, + 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, + 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, + 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, + 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, + 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, + 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, + 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, + 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, + 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, + 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, + 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, + 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, + 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, + 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, + 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, + 0xb7, 0xb8, 0x55, 0xac, 0xab, 0x12, 0x14, 0x1e, + 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, + 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, + 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, + 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, + 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, + 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, + 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, + 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, + 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, + 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, + 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, + 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, + 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, + 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, + 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, + 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, + 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, + 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, + 0x54, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, + 0x00, 0x00, 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, + 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, + 0x6f, 0xb2, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66, + 0x6c, 0x6d, 0x6e, 0x6f, 0x68, 0x69, 0x6a, 0x6b, + 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, + 0x79, 0x78, }; static const uint16_t case_conv_ext58 = { @@ -157,38 +163,44 @@ 0x006b, 0x00e5, }; -static const uint8_t unicode_prop_Cased1_table172 = { +static const uint8_t unicode_prop_Cased1_table193 = { 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, - 0x80, 0x8c, 0x80, 0x8d, 0x81, 0x8d, 0x02, 0x80, - 0xe1, 0x80, 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, - 0x11, 0x00, 0x01, 0x04, 0x08, 0x01, 0x08, 0x30, - 0x08, 0x01, 0x15, 0x20, 0x00, 0x39, 0x99, 0x31, - 0x9d, 0x84, 0x40, 0x94, 0x80, 0xd6, 0x82, 0xa6, - 0x80, 0x41, 0x62, 0x80, 0xa6, 0x80, 0x57, 0x76, + 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, + 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, + 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, + 0x20, 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, + 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, + 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, - 0x05, 0x80, 0x98, 0x80, 0xc7, 0x82, 0x43, 0x34, - 0xa2, 0x06, 0x80, 0x8c, 0x61, 0x28, 0x96, 0xd4, - 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, - 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, - 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, 0x53, - 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x05, 0x80, 0x98, 0x80, 0xa2, 0x00, 0x80, 0x9b, + 0x12, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8d, + 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, + 0x60, 0xcc, 0x44, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x07, 0x59, 0x63, 0x99, - 0x85, 0x99, 0x85, 0x99, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x89, 0x80, 0x93, 0x2d, 0x41, + 0x04, 0xbd, 0x50, 0xc1, 0x99, 0x85, 0x99, 0x85, + 0x99, }; static const uint8_t unicode_prop_Cased1_index18 = { - 0xb9, 0x02, 0xe0, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, - 0x40, 0xba, 0xd4, 0x01, 0x89, 0xd7, 0x01, 0x8a, - 0xf1, 0x01, + 0xb9, 0x02, 0x80, // 002B9 at 36 + 0xa0, 0x1e, 0x40, // 01EA0 at 66 + 0x9e, 0xa6, 0x40, // 0A69E at 98 + 0xbb, 0x07, 0x01, // 107BB at 128 + 0xdb, 0xd6, 0x01, // 1D6DB at 160 + 0x8a, 0xf1, 0x01, // 1F18A at 192 (upper bound) }; -static const uint8_t unicode_prop_Case_Ignorable_table692 = { +static const uint8_t unicode_prop_Case_Ignorable_table764 = { 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, @@ -197,100 +209,124 @@ 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, - 0x97, 0x97, 0xaa, 0x82, 0xf6, 0xaf, 0xb6, 0x00, - 0x03, 0x3b, 0x02, 0x86, 0x89, 0x81, 0x8c, 0x80, - 0x8e, 0x80, 0xb9, 0x03, 0x1f, 0x80, 0x93, 0x81, - 0x99, 0x01, 0x81, 0xb8, 0x03, 0x0b, 0x09, 0x12, - 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x81, 0xb8, 0x03, - 0x20, 0x0b, 0x80, 0x93, 0x81, 0x95, 0x28, 0x80, - 0xb9, 0x01, 0x00, 0x1f, 0x06, 0x81, 0x8a, 0x81, - 0x9d, 0x80, 0xbc, 0x80, 0x8b, 0x80, 0xb1, 0x02, - 0x80, 0xb8, 0x14, 0x10, 0x1e, 0x81, 0x8a, 0x81, - 0x9c, 0x80, 0xb9, 0x01, 0x05, 0x04, 0x81, 0x93, - 0x81, 0x9b, 0x81, 0xb8, 0x0b, 0x1f, 0x80, 0x93, - 0x81, 0x9c, 0x80, 0xc7, 0x06, 0x10, 0x80, 0xd9, - 0x01, 0x86, 0x8a, 0x88, 0xe1, 0x01, 0x88, 0x88, - 0x00, 0x85, 0xc9, 0x81, 0x9a, 0x00, 0x00, 0x80, - 0xb6, 0x8d, 0x04, 0x01, 0x84, 0x8a, 0x80, 0xa3, - 0x88, 0x80, 0xe5, 0x18, 0x28, 0x09, 0x81, 0x98, - 0x0b, 0x82, 0x8f, 0x83, 0x8c, 0x01, 0x0d, 0x80, - 0x8e, 0x80, 0xdd, 0x80, 0x42, 0x5f, 0x82, 0x43, - 0xb1, 0x82, 0x9c, 0x82, 0x9c, 0x81, 0x9d, 0x81, - 0xbf, 0x08, 0x37, 0x01, 0x8a, 0x10, 0x20, 0xac, - 0x83, 0xb3, 0x80, 0xc0, 0x81, 0xa1, 0x80, 0xf5, - 0x13, 0x81, 0x88, 0x05, 0x82, 0x40, 0xda, 0x09, - 0x80, 0xb9, 0x00, 0x30, 0x00, 0x01, 0x3d, 0x89, - 0x08, 0xa6, 0x07, 0x90, 0xbe, 0x83, 0xaf, 0x00, - 0x20, 0x04, 0x80, 0xa7, 0x88, 0x8b, 0x81, 0x9f, - 0x19, 0x08, 0x82, 0xb7, 0x00, 0x0a, 0x00, 0x82, - 0xb9, 0x39, 0x81, 0xbf, 0x85, 0xd1, 0x10, 0x8c, - 0x06, 0x18, 0x28, 0x11, 0xb1, 0xbe, 0x8c, 0x80, - 0xa1, 0xde, 0x04, 0x41, 0xbc, 0x00, 0x82, 0x8a, - 0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, 0x81, 0x8b, - 0x27, 0x81, 0x89, 0x01, 0x01, 0x84, 0xb0, 0x20, - 0x89, 0x00, 0x8c, 0x80, 0x8f, 0x8c, 0xb2, 0xa0, - 0x4b, 0x8a, 0x81, 0xf0, 0x82, 0xfc, 0x80, 0x8e, - 0x80, 0xdf, 0x9f, 0xae, 0x80, 0x41, 0xd4, 0x80, - 0xa3, 0x1a, 0x24, 0x80, 0xdc, 0x85, 0xdc, 0x82, - 0x60, 0x6f, 0x15, 0x80, 0x44, 0xe1, 0x85, 0x41, - 0x0d, 0x80, 0xe1, 0x18, 0x89, 0x00, 0x9b, 0x83, - 0xcf, 0x81, 0x8d, 0xa1, 0xcd, 0x80, 0x96, 0x82, - 0xec, 0x0f, 0x02, 0x03, 0x80, 0x98, 0x0c, 0x80, - 0x40, 0x96, 0x81, 0x99, 0x91, 0x8c, 0x80, 0xa5, - 0x87, 0x98, 0x8a, 0xad, 0x82, 0xaf, 0x01, 0x19, - 0x81, 0x90, 0x80, 0x94, 0x81, 0xc1, 0x29, 0x09, - 0x81, 0x8b, 0x07, 0x80, 0xa2, 0x80, 0x8a, 0x80, - 0xb2, 0x00, 0x11, 0x0c, 0x08, 0x80, 0x9a, 0x80, - 0x8d, 0x0c, 0x08, 0x80, 0xe3, 0x84, 0x88, 0x82, - 0xf8, 0x01, 0x03, 0x80, 0x60, 0x4f, 0x2f, 0x80, - 0x40, 0x92, 0x8f, 0x42, 0x3d, 0x8f, 0x10, 0x8b, - 0x8f, 0xa1, 0x01, 0x80, 0x40, 0xa8, 0x06, 0x05, - 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, 0xae, 0x80, - 0xac, 0x81, 0xc2, 0x80, 0x94, 0x82, 0x42, 0x00, - 0x80, 0x40, 0xe1, 0x80, 0x40, 0x94, 0x84, 0x46, - 0x85, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, 0x40, - 0xa4, 0x81, 0x42, 0x3c, 0x83, 0x41, 0x82, 0x81, - 0x40, 0x98, 0x8a, 0x40, 0xaf, 0x80, 0xb5, 0x8e, - 0xb7, 0x82, 0xb0, 0x19, 0x09, 0x80, 0x8e, 0x80, - 0xb1, 0x82, 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, - 0x81, 0xb3, 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, - 0x00, 0x0d, 0x80, 0x40, 0x9f, 0x02, 0x87, 0x94, - 0x81, 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0x40, - 0xc2, 0x39, 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, - 0x03, 0x08, 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, - 0x9a, 0x81, 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, - 0x01, 0x28, 0x80, 0xe4, 0x11, 0x18, 0x84, 0x41, - 0x02, 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, - 0x40, 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, - 0x29, 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, - 0x01, 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, - 0x0e, 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, - 0x80, 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, - 0x55, 0x3a, 0x88, 0x60, 0x36, 0xb6, 0x84, 0xba, - 0x86, 0x88, 0x83, 0x44, 0x0a, 0x80, 0xbe, 0x90, - 0xbf, 0x08, 0x81, 0x60, 0x4c, 0xb7, 0x08, 0x83, - 0x54, 0xc2, 0x82, 0x88, 0x8f, 0x0e, 0x9d, 0x83, - 0x40, 0x93, 0x82, 0x47, 0xba, 0xb6, 0x83, 0xb1, - 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, 0x4f, - 0x30, 0x90, 0x0e, 0x01, 0x04, 0x41, 0x04, 0x8d, - 0x41, 0xad, 0x83, 0x45, 0xdf, 0x86, 0xec, 0x87, + 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, + 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, + 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, + 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, + 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, + 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, + 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, + 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, + 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, + 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, + 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, + 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, + 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, + 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, + 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, + 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, + 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, + 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, + 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, + 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, + 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, + 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, + 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, + 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, + 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, + 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, + 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, + 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, + 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, + 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, + 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, + 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, + 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, + 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, + 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, + 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, + 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, + 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, + 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, + 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, + 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, + 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, + 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, + 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, + 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, + 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, + 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, 0x3a, 0x81, + 0xce, 0x83, 0xc5, 0x8a, 0xb0, 0x83, 0xfa, 0x80, + 0xb5, 0x8e, 0xa8, 0x01, 0x81, 0x89, 0x82, 0xb0, + 0x19, 0x09, 0x03, 0x80, 0x89, 0x80, 0xb1, 0x82, + 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, 0x81, 0xb3, + 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, 0x00, 0x0d, + 0x01, 0x80, 0x40, 0x9c, 0x02, 0x87, 0x94, 0x81, + 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0xc5, 0x85, + 0x8c, 0x00, 0x00, 0x80, 0x8d, 0x81, 0xd4, 0x39, + 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, + 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a, 0x81, + 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, 0x28, + 0x80, 0xe4, 0x00, 0x01, 0x18, 0x84, 0x41, 0x02, + 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, 0x40, + 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, + 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, 0x01, + 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, + 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, + 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, 0x8a, + 0x81, 0xb3, 0x24, 0x00, 0x80, 0x96, 0x80, 0x54, + 0xd4, 0x90, 0x85, 0x8e, 0x60, 0x2c, 0xc7, 0x8b, + 0x12, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x88, 0x83, + 0x41, 0xfb, 0x82, 0xa7, 0x81, 0x41, 0xe1, 0x80, + 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, + 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, + 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, + 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, + 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, + 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, + 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, + 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, + 0x40, 0xfd, 0x81, 0x42, 0xdf, 0x86, 0xec, 0x87, 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, 0x80, 0x9d, 0xdf, 0xff, 0x40, 0xef, }; -static const uint8_t unicode_prop_Case_Ignorable_index66 = { - 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, - 0x20, 0x05, 0x0c, 0x20, 0x3b, 0x0e, 0x40, 0x61, - 0x10, 0x40, 0x0f, 0x18, 0x20, 0x43, 0x1b, 0x60, - 0x79, 0x1d, 0x00, 0xf1, 0x20, 0x00, 0x0d, 0xa6, - 0x40, 0x2e, 0xa9, 0x20, 0xde, 0xaa, 0x00, 0x0f, - 0xff, 0x20, 0xe7, 0x0a, 0x41, 0x82, 0x11, 0x21, - 0xc4, 0x14, 0x61, 0x44, 0x19, 0x01, 0x48, 0x1d, - 0x21, 0xa4, 0xbc, 0x01, 0x3e, 0xe1, 0x01, 0xf0, - 0x01, 0x0e, +static const uint8_t unicode_prop_Case_Ignorable_index72 = { + 0xbe, 0x05, 0x00, // 005BE at 32 + 0xfe, 0x07, 0x00, // 007FE at 64 + 0x52, 0x0a, 0xa0, // 00A52 at 101 + 0xc1, 0x0b, 0x00, // 00BC1 at 128 + 0x82, 0x0d, 0x00, // 00D82 at 160 + 0x3f, 0x10, 0x80, // 0103F at 196 + 0xd4, 0x17, 0x40, // 017D4 at 226 + 0xcf, 0x1a, 0x20, // 01ACF at 257 + 0xf5, 0x1c, 0x00, // 01CF5 at 288 + 0x80, 0x20, 0x00, // 02080 at 320 + 0x16, 0xa0, 0x00, // 0A016 at 352 + 0xc6, 0xa8, 0x00, // 0A8C6 at 384 + 0xc2, 0xaa, 0x60, // 0AAC2 at 419 + 0x56, 0xfe, 0x20, // 0FE56 at 449 + 0xb1, 0x07, 0x01, // 107B1 at 480 + 0x02, 0x10, 0x01, // 11002 at 512 + 0x42, 0x12, 0x41, // 11242 at 546 + 0xc4, 0x14, 0x21, // 114C4 at 577 + 0xe1, 0x19, 0x81, // 119E1 at 612 + 0x48, 0x1d, 0x01, // 11D48 at 640 + 0x44, 0x6b, 0x01, // 16B44 at 672 + 0x83, 0xd1, 0x21, // 1D183 at 705 + 0x3e, 0xe1, 0x01, // 1E13E at 736 + 0xf0, 0x01, 0x0e, // E01F0 at 768 (upper bound) }; -static const uint8_t unicode_prop_ID_Start_table1045 = { +static const uint8_t unicode_prop_ID_Start_table1133 = { 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, @@ -301,185 +337,220 @@ 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, - 0xb4, 0x94, 0x80, 0x91, 0xbb, 0xb5, 0x10, 0x91, - 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, 0x95, - 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, 0x08, - 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, 0x95, - 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, 0x92, - 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, 0x01, - 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, 0x80, - 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, 0x10, - 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, 0x2a, - 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, 0x8b, - 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, 0x8f, - 0x10, 0x99, 0x14, 0x81, 0x9d, 0x03, 0x38, 0x10, - 0x96, 0x80, 0x89, 0x04, 0x10, 0x9f, 0x00, 0x81, - 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, 0xa8, 0x08, - 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, 0x91, 0x82, - 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, 0xaf, 0x01, - 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, 0x97, 0x00, - 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, 0x80, 0x94, - 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, 0x9a, 0x84, - 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, 0x1a, 0x02, - 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, 0xa5, 0x00, - 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, 0x03, 0x0e, - 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, 0xa0, 0x03, - 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, 0xb8, 0x03, - 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, 0x0d, 0x82, - 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, 0x84, 0xca, - 0x82, 0x8a, 0x86, 0x8c, 0x03, 0x8d, 0x91, 0x8d, - 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, 0x03, - 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, 0xc5, - 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, 0x83, - 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, 0xdc, - 0xae, 0x90, 0x86, 0xb6, 0x9d, 0x8c, 0x81, 0x89, - 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, 0x81, - 0x88, 0x86, 0xaa, 0x0a, 0xa8, 0x18, 0x28, 0x0a, - 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, 0x81, - 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, 0x9e, - 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, 0x0d, - 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, 0x80, - 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x0d, - 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, 0x18, - 0x90, 0xa8, 0x4a, 0x76, 0xae, 0x80, 0xae, 0x80, - 0x40, 0x84, 0x2b, 0x11, 0x8b, 0xa5, 0x00, 0x20, - 0x81, 0xb7, 0x30, 0x8f, 0x96, 0x88, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x86, 0x42, 0x25, - 0x82, 0x98, 0x88, 0x34, 0x0c, 0x83, 0xd5, 0x1c, - 0x80, 0xd9, 0x03, 0x84, 0xaa, 0x80, 0xdd, 0x90, - 0x9f, 0xaf, 0x8f, 0x41, 0xff, 0x59, 0xbf, 0xbf, - 0x60, 0x51, 0xfc, 0x82, 0x44, 0x8c, 0xc2, 0xad, - 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, 0x81, 0x93, - 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, 0x88, 0x81, - 0xe6, 0x81, 0xb4, 0x81, 0x88, 0xa9, 0x8c, 0x02, - 0x03, 0x80, 0x96, 0x9c, 0xb3, 0x8d, 0xb1, 0xbd, - 0x2a, 0x00, 0x81, 0x8a, 0x9b, 0x89, 0x96, 0x98, - 0x9c, 0x86, 0xae, 0x9b, 0x80, 0x8f, 0x20, 0x89, - 0x89, 0x20, 0xa8, 0x96, 0x10, 0x87, 0x93, 0x96, - 0x10, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, 0x00, - 0x97, 0x11, 0x8a, 0x32, 0x8b, 0x29, 0x29, 0x85, - 0x88, 0x30, 0x30, 0xaa, 0x80, 0x8d, 0x85, 0xf2, - 0x9c, 0x60, 0x2b, 0xa3, 0x8b, 0x96, 0x83, 0xb0, - 0x60, 0x21, 0x03, 0x41, 0x6d, 0x81, 0xe9, 0xa5, - 0x86, 0x8b, 0x24, 0x00, 0x89, 0x80, 0x8c, 0x04, - 0x00, 0x01, 0x01, 0x80, 0xeb, 0xa0, 0x41, 0x6a, - 0x91, 0xbf, 0x81, 0xb5, 0xa7, 0x8b, 0xf3, 0x20, - 0x40, 0x86, 0xa3, 0x99, 0x85, 0x99, 0x8a, 0xd8, - 0x15, 0x0d, 0x0d, 0x0a, 0xa2, 0x8b, 0x80, 0x99, - 0x80, 0x92, 0x01, 0x80, 0x8e, 0x81, 0x8d, 0xa1, - 0xfa, 0xc4, 0xb4, 0x41, 0x0a, 0x9c, 0x82, 0xb0, - 0xae, 0x9f, 0x8c, 0x9d, 0x84, 0xa5, 0x89, 0x9d, - 0x81, 0xa3, 0x1f, 0x04, 0xa9, 0x40, 0x9d, 0x91, - 0xa3, 0x83, 0xa3, 0x83, 0xa7, 0x87, 0xb3, 0x40, - 0x9b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x40, - 0x97, 0x29, 0x00, 0xab, 0x01, 0x10, 0x81, 0x96, - 0x89, 0x96, 0x88, 0x9e, 0xc0, 0x92, 0x01, 0x89, - 0x95, 0x89, 0x99, 0xc5, 0xb7, 0x29, 0xbf, 0x80, - 0x8e, 0x18, 0x10, 0x9c, 0xa9, 0x9c, 0x82, 0x9c, - 0xa2, 0x38, 0x9b, 0x9a, 0xb5, 0x89, 0x95, 0x89, - 0x92, 0x8c, 0x91, 0xed, 0xc8, 0xb6, 0xb2, 0x8c, - 0xb2, 0x8c, 0xa3, 0x41, 0x5b, 0xa9, 0x29, 0xcd, - 0x9c, 0x89, 0x07, 0x95, 0xe9, 0x94, 0x9a, 0x96, - 0x8b, 0xb4, 0xca, 0xac, 0x9f, 0x98, 0x99, 0xa3, - 0x9c, 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, - 0x83, 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, - 0xd3, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, 0x86, - 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, - 0x10, 0x91, 0x80, 0x8b, 0x84, 0x40, 0x9d, 0xb4, - 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, 0x93, 0x08, - 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, 0xa3, 0xaf, - 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, 0xc6, 0x9a, - 0x40, 0xe4, 0xab, 0xf3, 0xbf, 0x9e, 0x39, 0x01, - 0x38, 0x08, 0x97, 0x8e, 0x00, 0x80, 0xdd, 0x39, - 0xa6, 0x8f, 0x00, 0x80, 0x9b, 0x80, 0x89, 0xa7, - 0x30, 0x94, 0x80, 0x8a, 0xad, 0x92, 0x80, 0xa1, - 0xb8, 0x41, 0x06, 0x88, 0x80, 0xa4, 0x90, 0x80, - 0xb0, 0x9d, 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, - 0x98, 0x28, 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, - 0x92, 0x40, 0xbc, 0x80, 0xce, 0x43, 0x99, 0xe5, - 0xee, 0x90, 0x40, 0xc3, 0x4a, 0xbb, 0x44, 0x2e, - 0x4f, 0xd0, 0x42, 0x46, 0x60, 0x21, 0xb8, 0x42, - 0x38, 0x86, 0x9e, 0xf0, 0x9d, 0x91, 0xaf, 0x8f, - 0x83, 0x9e, 0x94, 0x84, 0x92, 0x42, 0xaf, 0xbf, - 0xff, 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, - 0x9b, 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa9, 0x88, - 0x60, 0x22, 0xf6, 0x41, 0x1e, 0xb0, 0x82, 0x90, - 0x1f, 0x41, 0x8b, 0x49, 0x03, 0xea, 0x84, 0x8c, - 0x82, 0x88, 0x86, 0x89, 0x57, 0x65, 0xd4, 0x80, - 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, 0x00, - 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, - 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, 0x53, 0x81, - 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, - 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, - 0x9e, 0x80, 0x98, 0x07, 0x49, 0x33, 0xac, 0x89, - 0x86, 0x8f, 0x80, 0x41, 0x70, 0xab, 0x45, 0x13, - 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, 0xb3, 0x18, - 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, - 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, - 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, - 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, 0x60, 0xa6, - 0xdd, 0xa1, 0x50, 0x34, 0x8a, 0x40, 0xdd, 0x81, - 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x4c, 0x1e, 0x42, - 0x1d, 0x45, 0xe1, 0x53, 0x4a, -}; - -static const uint8_t unicode_prop_ID_Start_index99 = { - 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, - 0x00, 0xb4, 0x0a, 0x00, 0xba, 0x0b, 0x00, 0x3e, - 0x0d, 0x00, 0xe0, 0x0e, 0x20, 0x57, 0x12, 0x00, - 0xeb, 0x16, 0x00, 0xca, 0x19, 0x20, 0xc0, 0x1d, - 0x60, 0x80, 0x20, 0x00, 0x2e, 0x2d, 0x00, 0xc0, - 0x31, 0x20, 0x89, 0xa7, 0x20, 0xf0, 0xa9, 0x00, - 0xe3, 0xab, 0x00, 0x3e, 0xfd, 0x00, 0xfb, 0x00, - 0x21, 0x37, 0x07, 0x61, 0x01, 0x0a, 0x01, 0x1d, - 0x0f, 0x21, 0x2c, 0x12, 0x01, 0xc8, 0x14, 0x21, - 0xd1, 0x19, 0x21, 0x47, 0x1d, 0x01, 0x39, 0x6a, - 0x21, 0x09, 0x8d, 0x01, 0xbc, 0xd4, 0x01, 0xa9, - 0xd7, 0x21, 0x3a, 0xee, 0x01, 0xde, 0xa6, 0x22, - 0x4b, 0x13, 0x03, + 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, + 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, + 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, + 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, + 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, + 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, + 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, + 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, + 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, + 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, + 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, + 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, + 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, + 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, + 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, + 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, + 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, + 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, + 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, + 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, + 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, + 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, + 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, + 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, + 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, + 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, + 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, + 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, + 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, + 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, + 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, + 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, + 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, + 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, + 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, + 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, + 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, + 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, + 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, + 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, + 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, + 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, + 0x88, 0x81, 0xe6, 0x81, 0xc2, 0x09, 0x00, 0x07, + 0x94, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, + 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, + 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, + 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, + 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, + 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, + 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, + 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, + 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, + 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, + 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, + 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, + 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, + 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, + 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, + 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, + 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, + 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x82, 0xb3, + 0x8b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, + 0x28, 0xa9, 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, + 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, + 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, + 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, + 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, + 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, + 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, + 0x9b, 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, + 0x82, 0xba, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, + 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, + 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, + 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, + 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, + 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, + 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, + 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, + 0x00, 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, + 0xab, 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, + 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, + 0xa3, 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, + 0xc6, 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, + 0xbf, 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, + 0x00, 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, + 0x9b, 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, + 0xad, 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, + 0x9e, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, + 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, + 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, 0x8e, + 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, 0x43, + 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0x4b, + 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, 0x4f, + 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, 0x46, + 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, 0x90, + 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, + 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, 0xff, + 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, + 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa8, 0x89, 0x60, + 0x22, 0xe6, 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, + 0x80, 0x9c, 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, + 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, + 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, + 0x40, 0x91, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, + 0x40, 0x9d, 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, + 0xe3, 0x9d, 0x08, 0x41, 0xee, 0x30, 0x18, 0x08, + 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, + 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, + 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, + 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, + 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, + 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, + 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, 0xe1, + 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_ID_Start_index108 = { + 0xf6, 0x03, 0x20, // 003F6 at 33 + 0xa6, 0x07, 0x00, // 007A6 at 64 + 0xa9, 0x09, 0x20, // 009A9 at 97 + 0xb1, 0x0a, 0x00, // 00AB1 at 128 + 0xba, 0x0b, 0x20, // 00BBA at 161 + 0x3b, 0x0d, 0x20, // 00D3B at 193 + 0xc7, 0x0e, 0x20, // 00EC7 at 225 + 0x49, 0x12, 0x00, // 01249 at 256 + 0x9b, 0x16, 0x00, // 0169B at 288 + 0xac, 0x19, 0x00, // 019AC at 320 + 0xc0, 0x1d, 0x80, // 01DC0 at 356 + 0x80, 0x20, 0x20, // 02080 at 385 + 0x70, 0x2d, 0x00, // 02D70 at 416 + 0x00, 0x32, 0x00, // 03200 at 448 + 0xdd, 0xa7, 0x00, // 0A7DD at 480 + 0x4c, 0xaa, 0x20, // 0AA4C at 513 + 0xc7, 0xd7, 0x20, // 0D7C7 at 545 + 0xfc, 0xfd, 0x20, // 0FDFC at 577 + 0x9d, 0x02, 0x21, // 1029D at 609 + 0x96, 0x05, 0x01, // 10596 at 640 + 0x9f, 0x08, 0x01, // 1089F at 672 + 0x49, 0x0c, 0x21, // 10C49 at 705 + 0x76, 0x10, 0x21, // 11076 at 737 + 0xa9, 0x12, 0x01, // 112A9 at 768 + 0xb0, 0x14, 0x01, // 114B0 at 800 + 0x42, 0x19, 0x41, // 11942 at 834 + 0x90, 0x1c, 0x01, // 11C90 at 864 + 0xf1, 0x2f, 0x21, // 12FF1 at 897 + 0x90, 0x6b, 0x21, // 16B90 at 929 + 0x33, 0xb1, 0x21, // 1B133 at 961 + 0x06, 0xd5, 0x01, // 1D506 at 992 + 0xc3, 0xd7, 0x01, // 1D7C3 at 1024 + 0xff, 0xe7, 0x21, // 1E7FF at 1057 + 0x63, 0xee, 0x01, // 1EE63 at 1088 + 0x5e, 0xee, 0x42, // 2EE5E at 1122 + 0xb0, 0x23, 0x03, // 323B0 at 1152 (upper bound) }; -static const uint8_t unicode_prop_ID_Continue1_table626 = { +static const uint8_t unicode_prop_ID_Continue1_table695 = { 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, - 0x04, 0xaa, 0x82, 0xf6, 0x8e, 0x80, 0xa0, 0xb5, - 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, 0x90, 0x82, - 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, 0x80, 0x89, - 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, 0x00, 0x23, - 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, 0x8a, 0x82, - 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, 0x09, 0x89, - 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x16, - 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, 0xba, 0x22, - 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, 0x8f, 0x84, - 0xb8, 0x30, 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, - 0x90, 0x82, 0xb7, 0x00, 0x30, 0x10, 0x1e, 0x81, - 0x8a, 0x09, 0x89, 0x8f, 0x83, 0xb6, 0x08, 0x30, - 0x10, 0x83, 0x88, 0x80, 0x89, 0x09, 0x89, 0x90, - 0x82, 0xc5, 0x03, 0x28, 0x00, 0x3d, 0x89, 0x09, - 0xbc, 0x01, 0x86, 0x8b, 0x38, 0x89, 0xd6, 0x01, - 0x88, 0x8a, 0x29, 0x89, 0xbd, 0x0d, 0x89, 0x8a, - 0x00, 0x00, 0x03, 0x81, 0xb0, 0x93, 0x01, 0x84, - 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe3, 0x93, 0x80, - 0x89, 0x8b, 0x1b, 0x10, 0x11, 0x32, 0x83, 0x8c, - 0x8b, 0x80, 0x8e, 0x42, 0xbe, 0x82, 0x88, 0x88, - 0x43, 0x9f, 0x82, 0x9c, 0x82, 0x9c, 0x81, 0x9d, - 0x81, 0xbf, 0x9f, 0x88, 0x01, 0x89, 0xa0, 0x11, - 0x89, 0x40, 0x8e, 0x80, 0xf5, 0x8b, 0x83, 0x8b, - 0x89, 0x89, 0xff, 0x8a, 0xbb, 0x84, 0xb8, 0x89, - 0x80, 0x9c, 0x81, 0x8a, 0x85, 0x89, 0x95, 0x8d, - 0x01, 0xbe, 0x84, 0xae, 0x90, 0x8a, 0x89, 0x90, - 0x88, 0x8b, 0x82, 0x9d, 0x8c, 0x81, 0x89, 0xab, - 0x8d, 0xaf, 0x93, 0x87, 0x89, 0x85, 0x89, 0xf5, - 0x10, 0x94, 0x18, 0x28, 0x0a, 0x40, 0xc5, 0xb9, - 0x04, 0x42, 0x3e, 0x81, 0x92, 0x80, 0xfa, 0x8c, - 0x18, 0x82, 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, - 0x80, 0xdf, 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, - 0x60, 0x75, 0x84, 0x89, 0xc4, 0x03, 0x89, 0x9f, + 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, + 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, + 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, + 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, + 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, + 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, + 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, + 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, + 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, + 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, + 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, + 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, + 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, + 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, + 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, + 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, + 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, + 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, + 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, + 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, + 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, + 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, + 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, + 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, + 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, + 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x0b, 0x81, + 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, + 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, + 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0xdf, 0x80, + 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, 0x89, 0x9f, 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, 0x91, 0x89, 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, @@ -489,53 +560,74 @@ 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, - 0xa4, 0x80, 0x42, 0xbc, 0x80, 0x40, 0xe1, 0x80, - 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, 0x45, 0x56, - 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, 0x40, 0xa4, - 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x41, 0x70, 0x81, - 0x40, 0x98, 0x8a, 0x40, 0xae, 0x82, 0xb4, 0x8e, - 0x9e, 0x89, 0x8e, 0x83, 0xac, 0x8a, 0xb4, 0x89, - 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, 0xab, 0x80, - 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, 0x8b, 0xd1, - 0x8b, 0x28, 0x40, 0x9f, 0x8b, 0x84, 0x89, 0x2b, - 0xb6, 0x08, 0x31, 0x09, 0x82, 0x88, 0x80, 0x89, - 0x09, 0x32, 0x84, 0x40, 0xbf, 0x91, 0x88, 0x89, - 0x18, 0xd0, 0x93, 0x8b, 0x89, 0x40, 0xd4, 0x31, - 0x88, 0x9a, 0x81, 0xd1, 0x90, 0x8e, 0x89, 0xd0, - 0x8c, 0x87, 0x89, 0xd2, 0x8e, 0x83, 0x89, 0x40, - 0xf1, 0x8e, 0x40, 0xa4, 0x89, 0xc5, 0x28, 0x09, - 0x18, 0x00, 0x81, 0x8b, 0x89, 0xf6, 0x31, 0x32, - 0x80, 0x9b, 0x89, 0xa7, 0x30, 0x1f, 0x80, 0x88, - 0x8a, 0xad, 0x8f, 0x41, 0x94, 0x38, 0x87, 0x8f, - 0x89, 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, - 0x08, 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, - 0x89, 0x41, 0x48, 0x83, 0x60, 0x4b, 0x68, 0x89, - 0x40, 0x85, 0x84, 0xba, 0x86, 0x98, 0x89, 0x43, - 0xf4, 0x00, 0xb6, 0x33, 0xd0, 0x80, 0x8a, 0x81, - 0x60, 0x4c, 0xaa, 0x81, 0x54, 0xc5, 0x22, 0x2f, - 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x45, - 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, 0xb1, 0x38, - 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, 0x4f, 0x30, - 0x90, 0x0e, 0x01, 0x04, 0x41, 0x04, 0x86, 0x88, - 0x89, 0x41, 0xa1, 0x8d, 0x45, 0xd5, 0x86, 0xec, - 0x34, 0x89, 0x52, 0x95, 0x89, 0x6c, 0x05, 0x05, - 0x40, 0xef, -}; - -static const uint8_t unicode_prop_ID_Continue1_index60 = { - 0xfa, 0x06, 0x00, 0x84, 0x09, 0x00, 0xf0, 0x0a, - 0x00, 0x70, 0x0c, 0x00, 0xf4, 0x0d, 0x00, 0x4a, - 0x10, 0x20, 0x1a, 0x18, 0x20, 0x74, 0x1b, 0x20, - 0xdd, 0x20, 0x00, 0x0c, 0xa8, 0x00, 0x5a, 0xaa, - 0x20, 0x1a, 0xff, 0x00, 0xad, 0x0e, 0x01, 0x38, - 0x12, 0x21, 0xc1, 0x15, 0x21, 0xe5, 0x19, 0x21, - 0xaa, 0x1d, 0x21, 0x8c, 0xd1, 0x41, 0x4a, 0xe1, - 0x21, 0xf0, 0x01, 0x0e, + 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, 0x80, 0x40, + 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, + 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, + 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x85, + 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, 0xce, 0x83, + 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, + 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, + 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, + 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, + 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, + 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, + 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0xc2, 0x88, + 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, 0x81, 0xd1, + 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, + 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, + 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0x85, 0x93, + 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, + 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, 0x00, 0x81, + 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, 0x9b, 0x89, + 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, + 0x41, 0x55, 0x89, 0xb4, 0x38, 0x87, 0x8f, 0x89, + 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, 0x89, + 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, + 0x84, 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, + 0x2c, 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, + 0xa5, 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, + 0x89, 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, + 0x8a, 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, + 0x89, 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, + 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, + 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, + 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, + 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, + 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, + 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, + 0x89, 0x42, 0xd4, 0x86, 0xec, 0x34, 0x89, 0x52, + 0x95, 0x89, 0x6c, 0x05, 0x05, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index66 = { + 0xfa, 0x06, 0x00, // 006FA at 32 + 0x70, 0x09, 0x00, // 00970 at 64 + 0xf0, 0x0a, 0x40, // 00AF0 at 98 + 0x57, 0x0c, 0x00, // 00C57 at 128 + 0xf0, 0x0d, 0x60, // 00DF0 at 163 + 0xc7, 0x0f, 0x20, // 00FC7 at 193 + 0xea, 0x17, 0x40, // 017EA at 226 + 0x05, 0x1b, 0x00, // 01B05 at 256 + 0x0e, 0x20, 0x00, // 0200E at 288 + 0xa0, 0xa6, 0x20, // 0A6A0 at 321 + 0xe6, 0xa9, 0x20, // 0A9E6 at 353 + 0x10, 0xfe, 0x00, // 0FE10 at 384 + 0x40, 0x0a, 0x01, // 10A40 at 416 + 0xc3, 0x10, 0x01, // 110C3 at 448 + 0x4e, 0x13, 0x01, // 1134E at 480 + 0x41, 0x16, 0x01, // 11641 at 512 + 0x0b, 0x1a, 0x01, // 11A0B at 544 + 0xaa, 0x1d, 0x01, // 11DAA at 576 + 0x7a, 0x6d, 0x21, // 16D7A at 609 + 0x45, 0xd2, 0x21, // 1D245 at 641 + 0xaf, 0xe2, 0x01, // 1E2AF at 672 + 0xf0, 0x01, 0x0e, // E01F0 at 704 (upper bound) }; #ifdef CONFIG_ALL_UNICODE -static const uint8_t unicode_cc_table851 = { +static const uint8_t unicode_cc_table916 = { 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, @@ -559,34 +651,36 @@ 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, - 0xaa, 0x02, 0xdc, 0xb0, 0x46, 0x00, 0xdc, 0xcd, - 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, - 0xdc, 0xc2, 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, - 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, - 0x07, 0x8f, 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, - 0xc1, 0xb0, 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, - 0xaf, 0xc0, 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, + 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, + 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, + 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, + 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, + 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, + 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, + 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, - 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, - 0x4e, 0x00, 0x09, 0xb0, 0x4e, 0x00, 0x09, 0x86, - 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, 0x07, - 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, 0x8f, - 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, 0x3c, - 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, 0xb0, - 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, 0x7a, - 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, 0x80, - 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, 0x41, - 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, 0x81, - 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, 0xc1, - 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, 0x07, - 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, 0xb2, - 0x9e, 0xc2, 0xb3, 0x83, 0x00, 0x09, 0x9e, 0x00, - 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, 0xb0, - 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, 0xc0, - 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, 0xb0, - 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, 0xdc, - 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, 0x80, - 0x01, 0xdc, 0xb0, 0x42, 0x00, 0x07, 0x8e, 0x00, + 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, + 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, + 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, + 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, + 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, + 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, + 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, + 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, + 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, + 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, + 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, + 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, + 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, + 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, + 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, + 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, @@ -595,71 +689,95 @@ 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, - 0xe4, 0x00, 0xdc, 0x80, 0xc0, 0x00, 0xe9, 0x00, - 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, 0x01, - 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, 0x82, - 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, 0x01, - 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, 0xb0, - 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, 0xf9, - 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, 0xde, - 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, 0x6d, - 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, 0x1f, - 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, 0x09, - 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, 0x08, - 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, 0x00, - 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, 0x80, - 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, 0x80, - 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, 0x00, - 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, 0xd0, - 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, 0xdc, - 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, 0xb6, - 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, 0x00, - 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, 0x74, - 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb1, 0x52, - 0xc1, 0xb0, 0x68, 0x01, 0xdc, 0xc2, 0x00, 0xdc, - 0xc0, 0x03, 0xdc, 0xb0, 0xc4, 0x00, 0x09, 0xb0, - 0x07, 0x00, 0x09, 0xb0, 0x08, 0x00, 0x09, 0x00, - 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, 0x09, 0xb0, - 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, 0x09, 0x88, - 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, 0x00, 0x07, - 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, 0xb0, 0x1f, - 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, 0xc6, 0x82, - 0xc4, 0xb0, 0x9c, 0x00, 0x09, 0x82, 0x00, 0x07, - 0x96, 0xc0, 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, - 0xb0, 0xca, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, - 0x00, 0x09, 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, - 0xb0, 0x42, 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, - 0x00, 0x07, 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, - 0x07, 0xb0, 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, - 0x09, 0x91, 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, - 0xb1, 0x74, 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, - 0x80, 0x01, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb8, - 0x45, 0x27, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, - 0x88, 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, - 0xb8, 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, - 0x00, 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, - 0xc4, 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, - 0xb8, 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, - 0x80, 0xc1, 0x80, 0xc4, 0xb0, 0xd4, 0xc6, 0xb1, - 0x84, 0xc3, 0xb5, 0xaf, 0x06, 0xdc, 0xb0, 0x3c, - 0xc5, 0x00, 0x07, -}; - -static const uint8_t unicode_cc_index81 = { - 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, - 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0xe2, - 0x08, 0x00, 0x53, 0x09, 0x00, 0xcd, 0x0b, 0x20, - 0x38, 0x0e, 0x00, 0x73, 0x0f, 0x20, 0x5d, 0x13, - 0x20, 0x60, 0x1a, 0x20, 0xaa, 0x1b, 0x00, 0xf4, - 0x1c, 0x00, 0xfe, 0x1d, 0x20, 0x7f, 0x2d, 0x20, - 0xf0, 0xa6, 0x00, 0xb2, 0xaa, 0x00, 0xfe, 0x01, - 0x01, 0xab, 0x0e, 0x01, 0x73, 0x11, 0x21, 0x70, - 0x13, 0x01, 0xb8, 0x16, 0x01, 0x9a, 0x1a, 0x01, - 0x9f, 0xbc, 0x01, 0x22, 0xe0, 0x01, 0x4b, 0xe9, - 0x01, + 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, + 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, + 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, + 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, + 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, + 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, + 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, + 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, + 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, + 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, + 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, + 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, + 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, + 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, + 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, + 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, + 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, + 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, + 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, + 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, + 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb0, + 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1f, 0x02, + 0xdc, 0xb0, 0x15, 0x01, 0xdc, 0xc2, 0x00, 0xdc, + 0xc0, 0x03, 0xdc, 0xb0, 0x00, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xb0, 0x8f, 0x00, 0x09, 0xa8, + 0x00, 0x09, 0x8d, 0x00, 0x09, 0xb0, 0x08, 0x00, + 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, + 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, + 0x09, 0x88, 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, + 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, + 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, + 0xc6, 0x82, 0xc4, 0xb0, 0x28, 0x02, 0x09, 0xb0, + 0x40, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, + 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, 0xca, + 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, 0x09, + 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, + 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, 0x07, + 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, 0xb0, + 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, 0x91, + 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, + 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, + 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, 0x01, + 0x09, 0xb8, 0x39, 0xbb, 0x00, 0x09, 0xb8, 0x01, + 0x8f, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, 0x88, + 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, 0xb8, + 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, + 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, 0xc4, + 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, 0xb8, + 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, + 0xc1, 0x80, 0xc4, 0xb0, 0x33, 0xc0, 0xb0, 0x6f, + 0xc6, 0xb1, 0x46, 0xc0, 0xb0, 0x0c, 0xc3, 0xb1, + 0xcb, 0x01, 0xe8, 0x00, 0xdc, 0xc0, 0xb0, 0xcd, + 0xc0, 0x00, 0xdc, 0xb2, 0xaf, 0x06, 0xdc, 0xb0, + 0x3c, 0xc5, 0x00, 0x07, +}; + +static const uint8_t unicode_cc_index87 = { + 0x4d, 0x03, 0x00, // 0034D at 32 + 0x97, 0x05, 0x20, // 00597 at 65 + 0xc6, 0x05, 0x00, // 005C6 at 96 + 0xe7, 0x06, 0x00, // 006E7 at 128 + 0x45, 0x07, 0x00, // 00745 at 160 + 0x9c, 0x08, 0x00, // 0089C at 192 + 0x4d, 0x09, 0x00, // 0094D at 224 + 0x3c, 0x0b, 0x00, // 00B3C at 256 + 0x3d, 0x0d, 0x00, // 00D3D at 288 + 0x36, 0x0f, 0x00, // 00F36 at 320 + 0x38, 0x10, 0x20, // 01038 at 353 + 0x3a, 0x19, 0x00, // 0193A at 384 + 0xcb, 0x1a, 0x20, // 01ACB at 417 + 0xd3, 0x1c, 0x00, // 01CD3 at 448 + 0xcf, 0x1d, 0x00, // 01DCF at 480 + 0xe2, 0x20, 0x00, // 020E2 at 512 + 0x2e, 0x30, 0x20, // 0302E at 545 + 0x2b, 0xa9, 0x20, // 0A92B at 577 + 0xed, 0xab, 0x00, // 0ABED at 608 + 0x39, 0x0a, 0x01, // 10A39 at 640 + 0x4c, 0x0f, 0x01, // 10F4C at 672 + 0x35, 0x11, 0x21, // 11135 at 705 + 0x66, 0x13, 0x01, // 11366 at 736 + 0x40, 0x16, 0x01, // 11640 at 768 + 0x47, 0x1a, 0x01, // 11A47 at 800 + 0xf0, 0x6a, 0x21, // 16AF0 at 833 + 0x8a, 0xd1, 0x01, // 1D18A at 864 + 0xec, 0xe4, 0x21, // 1E4EC at 897 + 0x4b, 0xe9, 0x01, // 1E94B at 928 (upper bound) }; -static const uint32_t unicode_decomp_table1690 = { +static const uint32_t unicode_decomp_table1709 = { 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, @@ -786,56 +904,61 @@ 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, - 0x29dc0081, 0x29fe0103, 0x2ad70203, 0x2ada4081, - 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, 0x3e8aa102, - 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, 0x3ec00197, - 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, 0x3ec74184, - 0x3ec804ad, 0x3eca4081, 0x3eca8304, 0x3ecc03a0, - 0x3ece02a0, 0x3ecf8084, 0x3ed00120, 0x3ed0c120, - 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, 0x3ef4cbad, - 0x3efa892f, 0x3eff022d, 0x3f002f2f, 0x3f1782a5, - 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, 0x3f3c81a5, - 0x3f3d64af, 0x3f542031, 0x3f649b31, 0x3f7c0131, - 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, 0x3f7ec0bb, - 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, 0x3f8cc315, - 0x3f8e462d, 0x3f91cc03, 0x3f97c695, 0x3f9c01af, - 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, 0x3fbd442f, - 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, 0x3fe80081, - 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, 0x3ff4831f, - 0x3ff6819f, 0x3ff80783, 0x44268192, 0x442ac092, - 0x444b8112, 0x44d2c112, 0x452ec212, 0x456e8112, - 0x464e0092, 0x74578392, 0x746ec312, 0x75000d1f, - 0x75068d1f, 0x750d0d1f, 0x7513839f, 0x7515891f, - 0x751a0d1f, 0x75208d1f, 0x75271015, 0x752f439f, - 0x7531459f, 0x75340d1f, 0x753a8d1f, 0x75410395, - 0x7543441f, 0x7545839f, 0x75478d1f, 0x754e0795, - 0x7552839f, 0x75548d1f, 0x755b0d1f, 0x75618d1f, - 0x75680d1f, 0x756e8d1f, 0x75750d1f, 0x757b8d1f, - 0x75820d1f, 0x75888d1f, 0x758f0d1f, 0x75958d1f, - 0x759c0d1f, 0x75a28d1f, 0x75a90103, 0x75aa089f, - 0x75ae4081, 0x75ae839f, 0x75b04081, 0x75b08c9f, - 0x75b6c081, 0x75b7032d, 0x75b8889f, 0x75bcc081, - 0x75bd039f, 0x75bec081, 0x75bf0c9f, 0x75c54081, - 0x75c5832d, 0x75c7089f, 0x75cb4081, 0x75cb839f, - 0x75cd4081, 0x75cd8c9f, 0x75d3c081, 0x75d4032d, - 0x75d5889f, 0x75d9c081, 0x75da039f, 0x75dbc081, - 0x75dc0c9f, 0x75e24081, 0x75e2832d, 0x75e4089f, - 0x75e84081, 0x75e8839f, 0x75ea4081, 0x75ea8c9f, - 0x75f0c081, 0x75f1042d, 0x75f3851f, 0x75f6051f, - 0x75f8851f, 0x75fb051f, 0x75fd851f, 0x7b80022d, - 0x7b814dad, 0x7b884203, 0x7b89c081, 0x7b8a452d, - 0x7b8d0403, 0x7b908081, 0x7b91dc03, 0x7ba0052d, - 0x7ba2c8ad, 0x7ba84483, 0x7baac8ad, 0x7c400097, - 0x7c404521, 0x7c440d25, 0x7c4a8087, 0x7c4ac115, - 0x7c4b4117, 0x7c4c0d1f, 0x7c528217, 0x7c538099, - 0x7c53c097, 0x7c5a8197, 0x7c640097, 0x7c80012f, - 0x7c808081, 0x7c841603, 0x7c9004c1, 0x7c940103, - 0x7efc051f, 0xbe0001ac, 0xbe00d110, 0xbe0947ac, - 0xbe0d3910, 0xbe29872c, 0xbe2d022c, 0xbe2e3790, - 0xbe49ff90, 0xbe69bc10, + 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, + 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, + 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, + 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, + 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, + 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, + 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, + 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, + 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, + 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, + 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, + 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, + 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, + 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, + 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, + 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, + 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, + 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, + 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, + 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, + 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, + 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, + 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, + 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, + 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, + 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, + 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, + 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, + 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, + 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, + 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, + 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, + 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, + 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, + 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, + 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, + 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, + 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, + 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, + 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, + 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, + 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, + 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, + 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, + 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, + 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, + 0xbe69bc10, }; -static const uint16_t unicode_decomp_table2690 = { +static const uint16_t unicode_decomp_table2709 = { 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, @@ -899,33 +1022,35 @@ 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, - 0xa76f, 0x11af, 0x11b3, 0x028d, 0x11bb, 0x120d, 0x130b, 0x1409, - 0x148d, 0x1492, 0x1550, 0x1569, 0x156f, 0x1575, 0x157b, 0x1587, - 0x1593, 0x002b, 0x159e, 0x15b6, 0x15ba, 0x15be, 0x15c2, 0x15c6, - 0x15ca, 0x15de, 0x15e2, 0x1646, 0x165f, 0x1685, 0x168b, 0x1749, - 0x174f, 0x1754, 0x1774, 0x1874, 0x187a, 0x190e, 0x19d0, 0x1a74, - 0x1a7c, 0x1a9a, 0x1a9f, 0x1ab3, 0x1abd, 0x1ac3, 0x1ad7, 0x1adc, - 0x1ae2, 0x1af0, 0x1b20, 0x1b2d, 0x1b35, 0x1b39, 0x1b4f, 0x1bc6, - 0x1bd8, 0x1bda, 0x1bdc, 0x3164, 0x1c1d, 0x1c1f, 0x1c21, 0x1c23, - 0x1c25, 0x1c27, 0x1c45, 0x1c53, 0x1c58, 0x1c61, 0x1c6a, 0x1c7c, - 0x1c85, 0x1c8a, 0x1caa, 0x1cc5, 0x1cc7, 0x1cc9, 0x1ccb, 0x1ccd, - 0x1ccf, 0x1cd1, 0x1cd3, 0x1cf3, 0x1cf5, 0x1cf7, 0x1cf9, 0x1cfb, - 0x1d02, 0x1d04, 0x1d06, 0x1d08, 0x1d17, 0x1d19, 0x1d1b, 0x1d1d, - 0x1d1f, 0x1d21, 0x1d23, 0x1d25, 0x1d27, 0x1d29, 0x1d2b, 0x1d2d, - 0x1d2f, 0x1d31, 0x1d33, 0x1d37, 0x03f4, 0x1d39, 0x2207, 0x1d3b, - 0x2202, 0x1d3d, 0x1d45, 0x03f4, 0x1d47, 0x2207, 0x1d49, 0x2202, - 0x1d4b, 0x1d53, 0x03f4, 0x1d55, 0x2207, 0x1d57, 0x2202, 0x1d59, - 0x1d61, 0x03f4, 0x1d63, 0x2207, 0x1d65, 0x2202, 0x1d67, 0x1d6f, - 0x03f4, 0x1d71, 0x2207, 0x1d73, 0x2202, 0x1d75, 0x1d7f, 0x1d81, - 0x1d83, 0x1d85, 0x1d87, 0x1d89, 0x1d8f, 0x1dac, 0x062d, 0x1db4, - 0x1dc0, 0x062c, 0x1dd0, 0x1e40, 0x1e4c, 0x1e5f, 0x1e71, 0x1e84, - 0x1e86, 0x1e8a, 0x1e90, 0x1e96, 0x1e98, 0x1e9c, 0x1e9e, 0x1ea6, - 0x1ea9, 0x1eab, 0x1eb1, 0x1eb3, 0x30b5, 0x1eb9, 0x1f11, 0x1f27, - 0x1f2b, 0x1f2d, 0x1f32, 0x1f7f, 0x1f90, 0x2091, 0x20a1, 0x20a7, - 0x21a1, 0x22bf, + 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, + 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, + 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, + 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, + 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, + 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, + 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, + 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, + 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c4d, 0x1c52, 0x1c88, 0x1cce, + 0x1cdc, 0x1ce1, 0x1cea, 0x1cf3, 0x1d01, 0x1d06, 0x1d0b, 0x1d1d, + 0x1d2f, 0x1d38, 0x1d3d, 0x1d61, 0x1d6f, 0x1d71, 0x1d73, 0x1d93, + 0x1dae, 0x1db0, 0x1db2, 0x1db4, 0x1db6, 0x1db8, 0x1dba, 0x1dbc, + 0x1ddc, 0x1dde, 0x1de0, 0x1de2, 0x1de4, 0x1deb, 0x1ded, 0x1def, + 0x1df1, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, + 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, + 0x1e20, 0x03f4, 0x1e22, 0x2207, 0x1e24, 0x2202, 0x1e26, 0x1e2e, + 0x03f4, 0x1e30, 0x2207, 0x1e32, 0x2202, 0x1e34, 0x1e3c, 0x03f4, + 0x1e3e, 0x2207, 0x1e40, 0x2202, 0x1e42, 0x1e4a, 0x03f4, 0x1e4c, + 0x2207, 0x1e4e, 0x2202, 0x1e50, 0x1e58, 0x03f4, 0x1e5a, 0x2207, + 0x1e5c, 0x2202, 0x1e5e, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, + 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e80, 0x1ea3, 0x1ea7, 0x1ead, + 0x1eca, 0x062d, 0x1ed2, 0x1ede, 0x062c, 0x1eee, 0x1f5e, 0x1f6a, + 0x1f7d, 0x1f8f, 0x1fa2, 0x1fa4, 0x1fa8, 0x1fae, 0x1fb4, 0x1fb6, + 0x1fba, 0x1fbc, 0x1fc4, 0x1fc7, 0x1fc9, 0x1fcf, 0x1fd1, 0x30b5, + 0x1fd7, 0x202f, 0x2045, 0x2049, 0x204b, 0x2050, 0x209d, 0x20ae, + 0x21af, 0x21bf, 0x21c5, 0x22bf, 0x23dd, }; -static const uint8_t unicode_decomp_data9165 = { +static const uint8_t unicode_decomp_data9451 = { 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, @@ -1491,590 +1616,626 @@ 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, - 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x26, - 0x01, 0x53, 0x01, 0x27, 0xa7, 0x37, 0xab, 0x6b, - 0x02, 0x52, 0xab, 0x48, 0x8c, 0xf4, 0x66, 0xca, - 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, 0x32, 0x4e, 0xe5, - 0x53, 0x9c, 0x9f, 0x9c, 0x9f, 0x51, 0x59, 0xd1, - 0x91, 0x87, 0x55, 0x48, 0x59, 0xf6, 0x61, 0x69, - 0x76, 0x85, 0x7f, 0x3f, 0x86, 0xba, 0x87, 0xf8, - 0x88, 0x8f, 0x90, 0x02, 0x6a, 0x1b, 0x6d, 0xd9, - 0x70, 0xde, 0x73, 0x3d, 0x84, 0x6a, 0x91, 0xf1, - 0x99, 0x82, 0x4e, 0x75, 0x53, 0x04, 0x6b, 0x1b, - 0x72, 0x2d, 0x86, 0x1e, 0x9e, 0x50, 0x5d, 0xeb, - 0x6f, 0xcd, 0x85, 0x64, 0x89, 0xc9, 0x62, 0xd8, - 0x81, 0x1f, 0x88, 0xca, 0x5e, 0x17, 0x67, 0x6a, - 0x6d, 0xfc, 0x72, 0xce, 0x90, 0x86, 0x4f, 0xb7, - 0x51, 0xde, 0x52, 0xc4, 0x64, 0xd3, 0x6a, 0x10, - 0x72, 0xe7, 0x76, 0x01, 0x80, 0x06, 0x86, 0x5c, - 0x86, 0xef, 0x8d, 0x32, 0x97, 0x6f, 0x9b, 0xfa, - 0x9d, 0x8c, 0x78, 0x7f, 0x79, 0xa0, 0x7d, 0xc9, - 0x83, 0x04, 0x93, 0x7f, 0x9e, 0xd6, 0x8a, 0xdf, - 0x58, 0x04, 0x5f, 0x60, 0x7c, 0x7e, 0x80, 0x62, - 0x72, 0xca, 0x78, 0xc2, 0x8c, 0xf7, 0x96, 0xd8, - 0x58, 0x62, 0x5c, 0x13, 0x6a, 0xda, 0x6d, 0x0f, - 0x6f, 0x2f, 0x7d, 0x37, 0x7e, 0x4b, 0x96, 0xd2, - 0x52, 0x8b, 0x80, 0xdc, 0x51, 0xcc, 0x51, 0x1c, - 0x7a, 0xbe, 0x7d, 0xf1, 0x83, 0x75, 0x96, 0x80, - 0x8b, 0xcf, 0x62, 0x02, 0x6a, 0xfe, 0x8a, 0x39, - 0x4e, 0xe7, 0x5b, 0x12, 0x60, 0x87, 0x73, 0x70, - 0x75, 0x17, 0x53, 0xfb, 0x78, 0xbf, 0x4f, 0xa9, - 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, 0x78, 0x65, 0x22, - 0x7d, 0xc3, 0x53, 0x5e, 0x58, 0x01, 0x77, 0x49, - 0x84, 0xaa, 0x8a, 0xba, 0x6b, 0xb0, 0x8f, 0x88, - 0x6c, 0xfe, 0x62, 0xe5, 0x82, 0xa0, 0x63, 0x65, - 0x75, 0xae, 0x4e, 0x69, 0x51, 0xc9, 0x51, 0x81, - 0x68, 0xe7, 0x7c, 0x6f, 0x82, 0xd2, 0x8a, 0xcf, - 0x91, 0xf5, 0x52, 0x42, 0x54, 0x73, 0x59, 0xec, - 0x5e, 0xc5, 0x65, 0xfe, 0x6f, 0x2a, 0x79, 0xad, - 0x95, 0x6a, 0x9a, 0x97, 0x9e, 0xce, 0x9e, 0x9b, - 0x52, 0xc6, 0x66, 0x77, 0x6b, 0x62, 0x8f, 0x74, - 0x5e, 0x90, 0x61, 0x00, 0x62, 0x9a, 0x64, 0x23, - 0x6f, 0x49, 0x71, 0x89, 0x74, 0xca, 0x79, 0xf4, - 0x7d, 0x6f, 0x80, 0x26, 0x8f, 0xee, 0x84, 0x23, - 0x90, 0x4a, 0x93, 0x17, 0x52, 0xa3, 0x52, 0xbd, - 0x54, 0xc8, 0x70, 0xc2, 0x88, 0xaa, 0x8a, 0xc9, - 0x5e, 0xf5, 0x5f, 0x7b, 0x63, 0xae, 0x6b, 0x3e, - 0x7c, 0x75, 0x73, 0xe4, 0x4e, 0xf9, 0x56, 0xe7, - 0x5b, 0xba, 0x5d, 0x1c, 0x60, 0xb2, 0x73, 0x69, - 0x74, 0x9a, 0x7f, 0x46, 0x80, 0x34, 0x92, 0xf6, - 0x96, 0x48, 0x97, 0x18, 0x98, 0x8b, 0x4f, 0xae, - 0x79, 0xb4, 0x91, 0xb8, 0x96, 0xe1, 0x60, 0x86, - 0x4e, 0xda, 0x50, 0xee, 0x5b, 0x3f, 0x5c, 0x99, - 0x65, 0x02, 0x6a, 0xce, 0x71, 0x42, 0x76, 0xfc, - 0x84, 0x7c, 0x90, 0x8d, 0x9f, 0x88, 0x66, 0x2e, - 0x96, 0x89, 0x52, 0x7b, 0x67, 0xf3, 0x67, 0x41, - 0x6d, 0x9c, 0x6e, 0x09, 0x74, 0x59, 0x75, 0x6b, - 0x78, 0x10, 0x7d, 0x5e, 0x98, 0x6d, 0x51, 0x2e, - 0x62, 0x78, 0x96, 0x2b, 0x50, 0x19, 0x5d, 0xea, - 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, 0x44, 0x61, 0x17, - 0x68, 0x87, 0x73, 0x86, 0x96, 0x29, 0x52, 0x0f, - 0x54, 0x65, 0x5c, 0x13, 0x66, 0x4e, 0x67, 0xa8, - 0x68, 0xe5, 0x6c, 0x06, 0x74, 0xe2, 0x75, 0x79, - 0x7f, 0xcf, 0x88, 0xe1, 0x88, 0xcc, 0x91, 0xe2, - 0x96, 0x3f, 0x53, 0xba, 0x6e, 0x1d, 0x54, 0xd0, - 0x71, 0x98, 0x74, 0xfa, 0x85, 0xa3, 0x96, 0x57, - 0x9c, 0x9f, 0x9e, 0x97, 0x67, 0xcb, 0x6d, 0xe8, - 0x81, 0xcb, 0x7a, 0x20, 0x7b, 0x92, 0x7c, 0xc0, - 0x72, 0x99, 0x70, 0x58, 0x8b, 0xc0, 0x4e, 0x36, - 0x83, 0x3a, 0x52, 0x07, 0x52, 0xa6, 0x5e, 0xd3, - 0x62, 0xd6, 0x7c, 0x85, 0x5b, 0x1e, 0x6d, 0xb4, - 0x66, 0x3b, 0x8f, 0x4c, 0x88, 0x4d, 0x96, 0x8b, - 0x89, 0xd3, 0x5e, 0x40, 0x51, 0xc0, 0x55, 0x00, - 0x00, 0x00, 0x00, 0x5a, 0x58, 0x00, 0x00, 0x74, - 0x66, 0x00, 0x00, 0x00, 0x00, 0xde, 0x51, 0x2a, - 0x73, 0xca, 0x76, 0x3c, 0x79, 0x5e, 0x79, 0x65, - 0x79, 0x8f, 0x79, 0x56, 0x97, 0xbe, 0x7c, 0xbd, - 0x7f, 0x00, 0x00, 0x12, 0x86, 0x00, 0x00, 0xf8, - 0x8a, 0x00, 0x00, 0x00, 0x00, 0x38, 0x90, 0xfd, - 0x90, 0xef, 0x98, 0xfc, 0x98, 0x28, 0x99, 0xb4, - 0x9d, 0xde, 0x90, 0xb7, 0x96, 0xae, 0x4f, 0xe7, - 0x50, 0x4d, 0x51, 0xc9, 0x52, 0xe4, 0x52, 0x51, - 0x53, 0x9d, 0x55, 0x06, 0x56, 0x68, 0x56, 0x40, - 0x58, 0xa8, 0x58, 0x64, 0x5c, 0x6e, 0x5c, 0x94, - 0x60, 0x68, 0x61, 0x8e, 0x61, 0xf2, 0x61, 0x4f, - 0x65, 0xe2, 0x65, 0x91, 0x66, 0x85, 0x68, 0x77, - 0x6d, 0x1a, 0x6e, 0x22, 0x6f, 0x6e, 0x71, 0x2b, - 0x72, 0x22, 0x74, 0x91, 0x78, 0x3e, 0x79, 0x49, - 0x79, 0x48, 0x79, 0x50, 0x79, 0x56, 0x79, 0x5d, - 0x79, 0x8d, 0x79, 0x8e, 0x79, 0x40, 0x7a, 0x81, - 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, 0x09, 0x7e, 0x41, - 0x7e, 0x72, 0x7f, 0x05, 0x80, 0xed, 0x81, 0x79, - 0x82, 0x79, 0x82, 0x57, 0x84, 0x10, 0x89, 0x96, - 0x89, 0x01, 0x8b, 0x39, 0x8b, 0xd3, 0x8c, 0x08, - 0x8d, 0xb6, 0x8f, 0x38, 0x90, 0xe3, 0x96, 0xff, - 0x97, 0x3b, 0x98, 0x75, 0x60, 0xee, 0x42, 0x18, - 0x82, 0x02, 0x26, 0x4e, 0xb5, 0x51, 0x68, 0x51, - 0x80, 0x4f, 0x45, 0x51, 0x80, 0x51, 0xc7, 0x52, - 0xfa, 0x52, 0x9d, 0x55, 0x55, 0x55, 0x99, 0x55, - 0xe2, 0x55, 0x5a, 0x58, 0xb3, 0x58, 0x44, 0x59, - 0x54, 0x59, 0x62, 0x5a, 0x28, 0x5b, 0xd2, 0x5e, - 0xd9, 0x5e, 0x69, 0x5f, 0xad, 0x5f, 0xd8, 0x60, - 0x4e, 0x61, 0x08, 0x61, 0x8e, 0x61, 0x60, 0x61, - 0xf2, 0x61, 0x34, 0x62, 0xc4, 0x63, 0x1c, 0x64, - 0x52, 0x64, 0x56, 0x65, 0x74, 0x66, 0x17, 0x67, - 0x1b, 0x67, 0x56, 0x67, 0x79, 0x6b, 0xba, 0x6b, - 0x41, 0x6d, 0xdb, 0x6e, 0xcb, 0x6e, 0x22, 0x6f, - 0x1e, 0x70, 0x6e, 0x71, 0xa7, 0x77, 0x35, 0x72, - 0xaf, 0x72, 0x2a, 0x73, 0x71, 0x74, 0x06, 0x75, - 0x3b, 0x75, 0x1d, 0x76, 0x1f, 0x76, 0xca, 0x76, - 0xdb, 0x76, 0xf4, 0x76, 0x4a, 0x77, 0x40, 0x77, - 0xcc, 0x78, 0xb1, 0x7a, 0xc0, 0x7b, 0x7b, 0x7c, - 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, 0x7f, 0x05, 0x80, - 0x52, 0x83, 0xef, 0x83, 0x79, 0x87, 0x41, 0x89, - 0x86, 0x89, 0x96, 0x89, 0xbf, 0x8a, 0xf8, 0x8a, - 0xcb, 0x8a, 0x01, 0x8b, 0xfe, 0x8a, 0xed, 0x8a, - 0x39, 0x8b, 0x8a, 0x8b, 0x08, 0x8d, 0x38, 0x8f, - 0x72, 0x90, 0x99, 0x91, 0x76, 0x92, 0x7c, 0x96, - 0xe3, 0x96, 0x56, 0x97, 0xdb, 0x97, 0xff, 0x97, - 0x0b, 0x98, 0x3b, 0x98, 0x12, 0x9b, 0x9c, 0x9f, - 0x4a, 0x28, 0x44, 0x28, 0xd5, 0x33, 0x9d, 0x3b, - 0x18, 0x40, 0x39, 0x40, 0x49, 0x52, 0xd0, 0x5c, - 0xd3, 0x7e, 0x43, 0x9f, 0x8e, 0x9f, 0x2a, 0xa0, - 0x02, 0x66, 0x66, 0x66, 0x69, 0x66, 0x6c, 0x66, - 0x66, 0x69, 0x66, 0x66, 0x6c, 0x7f, 0x01, 0x74, - 0x73, 0x00, 0x74, 0x65, 0x05, 0x0f, 0x11, 0x0f, - 0x00, 0x0f, 0x06, 0x19, 0x11, 0x0f, 0x08, 0xd9, - 0x05, 0xb4, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf2, - 0x05, 0xb7, 0x05, 0xd0, 0x05, 0x12, 0x00, 0x03, - 0x04, 0x0b, 0x0c, 0x0d, 0x18, 0x1a, 0xe9, 0x05, - 0xc1, 0x05, 0xe9, 0x05, 0xc2, 0x05, 0x49, 0xfb, - 0xc1, 0x05, 0x49, 0xfb, 0xc2, 0x05, 0xd0, 0x05, - 0xb7, 0x05, 0xd0, 0x05, 0xb8, 0x05, 0xd0, 0x05, - 0xbc, 0x05, 0xd8, 0x05, 0xbc, 0x05, 0xde, 0x05, - 0xbc, 0x05, 0xe0, 0x05, 0xbc, 0x05, 0xe3, 0x05, - 0xbc, 0x05, 0xb9, 0x05, 0x2d, 0x03, 0x2e, 0x03, - 0x2f, 0x03, 0x30, 0x03, 0x31, 0x03, 0x1c, 0x00, - 0x18, 0x06, 0x22, 0x06, 0x2b, 0x06, 0xd0, 0x05, - 0xdc, 0x05, 0x71, 0x06, 0x00, 0x00, 0x0a, 0x0a, - 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, 0x0d, 0x0f, 0x0f, - 0x0f, 0x0f, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, - 0x0e, 0x0e, 0x08, 0x08, 0x08, 0x08, 0x33, 0x33, - 0x33, 0x33, 0x35, 0x35, 0x35, 0x35, 0x13, 0x13, - 0x13, 0x13, 0x12, 0x12, 0x12, 0x12, 0x15, 0x15, - 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x1c, 0x1c, - 0x1b, 0x1b, 0x1d, 0x1d, 0x17, 0x17, 0x27, 0x27, - 0x20, 0x20, 0x38, 0x38, 0x38, 0x38, 0x3e, 0x3e, - 0x3e, 0x3e, 0x42, 0x42, 0x42, 0x42, 0x40, 0x40, - 0x40, 0x40, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, - 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x4d, 0x4d, - 0x4d, 0x4d, 0x61, 0x61, 0x62, 0x62, 0x49, 0x06, - 0x64, 0x64, 0x64, 0x64, 0x7e, 0x7e, 0x7d, 0x7d, - 0x7f, 0x7f, 0x2e, 0x82, 0x82, 0x7c, 0x7c, 0x80, - 0x80, 0x87, 0x87, 0x87, 0x87, 0x00, 0x00, 0x26, - 0x06, 0x00, 0x01, 0x00, 0x01, 0x00, 0xaf, 0x00, - 0xaf, 0x00, 0x22, 0x00, 0x22, 0x00, 0xa1, 0x00, - 0xa1, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa2, 0x00, - 0xa2, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, - 0x23, 0x00, 0x23, 0x00, 0x23, 0xcc, 0x06, 0x00, - 0x00, 0x00, 0x00, 0x26, 0x06, 0x00, 0x06, 0x00, - 0x07, 0x00, 0x1f, 0x00, 0x23, 0x00, 0x24, 0x02, - 0x06, 0x02, 0x07, 0x02, 0x08, 0x02, 0x1f, 0x02, - 0x23, 0x02, 0x24, 0x04, 0x06, 0x04, 0x07, 0x04, - 0x08, 0x04, 0x1f, 0x04, 0x23, 0x04, 0x24, 0x05, - 0x06, 0x05, 0x1f, 0x05, 0x23, 0x05, 0x24, 0x06, - 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, 0x1f, 0x08, - 0x06, 0x08, 0x07, 0x08, 0x1f, 0x0d, 0x06, 0x0d, + 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, + 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, + 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, + 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, + 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, + 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, + 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, + 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, + 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, + 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, + 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, + 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, + 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, + 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, + 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, + 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, + 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, + 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, + 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, + 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, + 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, + 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, + 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, + 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, + 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, + 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, + 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, + 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, + 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, + 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, + 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, + 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, + 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, + 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, + 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, + 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, + 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, + 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, + 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, + 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, + 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, + 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, + 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, + 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, + 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, + 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, + 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, + 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, + 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, + 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, + 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, + 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, + 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, + 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, + 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, + 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, + 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, + 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, + 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, + 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, + 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, + 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, + 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, + 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, + 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, + 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, + 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, + 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, + 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, + 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, + 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, + 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, + 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, + 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, + 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, + 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, + 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, + 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, + 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, + 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, + 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, + 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, + 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, + 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, + 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, + 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, + 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, + 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, + 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, + 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, + 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, + 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, + 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, + 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, + 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, + 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, + 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, + 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, + 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, + 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, + 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, + 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, + 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, + 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, + 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, + 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, + 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, + 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, + 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, + 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, + 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, + 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, + 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, + 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, + 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, + 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, + 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, + 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, + 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, + 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, + 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, + 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, + 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, + 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, + 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, + 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, + 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, + 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, + 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, + 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, + 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, + 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, + 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, + 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, + 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, + 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, + 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, + 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, + 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, + 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, + 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, + 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, + 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, + 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, + 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, + 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, + 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, + 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, + 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, + 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, + 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, + 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, + 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, + 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, + 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, + 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, + 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, + 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, + 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, + 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, + 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, + 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, + 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, + 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, + 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, + 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, + 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, + 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, + 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, + 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, + 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, + 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, + 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, + 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, + 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, + 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, + 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, + 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, + 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, + 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, + 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, + 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, + 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, + 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, + 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, + 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, + 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, + 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, - 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, 0x08, 0x10, - 0x1f, 0x11, 0x07, 0x11, 0x1f, 0x12, 0x1f, 0x13, + 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, + 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, - 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1b, - 0x23, 0x1b, 0x24, 0x1c, 0x07, 0x1c, 0x1f, 0x1c, - 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x06, 0x1d, - 0x07, 0x1d, 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1d, - 0x23, 0x1d, 0x24, 0x1e, 0x06, 0x1e, 0x07, 0x1e, - 0x08, 0x1e, 0x1f, 0x1e, 0x23, 0x1e, 0x24, 0x1f, - 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x1f, - 0x23, 0x1f, 0x24, 0x20, 0x06, 0x20, 0x07, 0x20, - 0x08, 0x20, 0x1f, 0x20, 0x23, 0x20, 0x24, 0x21, - 0x06, 0x21, 0x1f, 0x21, 0x23, 0x21, 0x24, 0x24, + 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, + 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, + 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, + 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, + 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, + 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, + 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, - 0x23, 0x24, 0x24, 0x0a, 0x4a, 0x0b, 0x4a, 0x23, - 0x4a, 0x20, 0x00, 0x4c, 0x06, 0x51, 0x06, 0x51, - 0x06, 0xff, 0x00, 0x1f, 0x26, 0x06, 0x00, 0x0b, - 0x00, 0x0c, 0x00, 0x1f, 0x00, 0x20, 0x00, 0x23, - 0x00, 0x24, 0x02, 0x0b, 0x02, 0x0c, 0x02, 0x1f, - 0x02, 0x20, 0x02, 0x23, 0x02, 0x24, 0x04, 0x0b, - 0x04, 0x0c, 0x04, 0x1f, 0x26, 0x06, 0x04, 0x20, - 0x04, 0x23, 0x04, 0x24, 0x05, 0x0b, 0x05, 0x0c, - 0x05, 0x1f, 0x05, 0x20, 0x05, 0x23, 0x05, 0x24, - 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x23, 0x1c, 0x24, - 0x1d, 0x01, 0x1d, 0x1e, 0x1d, 0x1f, 0x1d, 0x23, - 0x1d, 0x24, 0x1e, 0x1f, 0x1e, 0x23, 0x1e, 0x24, - 0x1f, 0x01, 0x1f, 0x1f, 0x20, 0x0b, 0x20, 0x0c, - 0x20, 0x1f, 0x20, 0x20, 0x20, 0x23, 0x20, 0x24, - 0x23, 0x4a, 0x24, 0x0b, 0x24, 0x0c, 0x24, 0x1f, - 0x24, 0x20, 0x24, 0x23, 0x24, 0x24, 0x00, 0x06, - 0x00, 0x07, 0x00, 0x08, 0x00, 0x1f, 0x00, 0x21, - 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, 0x02, 0x1f, - 0x02, 0x21, 0x04, 0x06, 0x04, 0x07, 0x04, 0x08, - 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x06, 0x07, - 0x06, 0x1f, 0x07, 0x06, 0x07, 0x1f, 0x08, 0x06, - 0x08, 0x1f, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, - 0x0d, 0x1f, 0x0f, 0x07, 0x0f, 0x08, 0x0f, 0x1f, - 0x10, 0x06, 0x10, 0x07, 0x10, 0x08, 0x10, 0x1f, - 0x11, 0x07, 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, - 0x14, 0x06, 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, - 0x1b, 0x08, 0x1b, 0x1f, 0x1c, 0x07, 0x1c, 0x1f, - 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, - 0x1d, 0x1f, 0x1e, 0x06, 0x1e, 0x07, 0x1e, 0x08, - 0x1e, 0x1f, 0x1e, 0x21, 0x1f, 0x06, 0x1f, 0x07, - 0x1f, 0x08, 0x1f, 0x1f, 0x20, 0x06, 0x20, 0x07, - 0x20, 0x08, 0x20, 0x1f, 0x20, 0x21, 0x21, 0x06, - 0x21, 0x1f, 0x21, 0x4a, 0x24, 0x06, 0x24, 0x07, - 0x24, 0x08, 0x24, 0x1f, 0x24, 0x21, 0x00, 0x1f, - 0x00, 0x21, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x1f, - 0x04, 0x21, 0x05, 0x1f, 0x05, 0x21, 0x0d, 0x1f, - 0x0d, 0x21, 0x0e, 0x1f, 0x0e, 0x21, 0x1d, 0x1e, - 0x1d, 0x1f, 0x1e, 0x1f, 0x20, 0x1f, 0x20, 0x21, - 0x24, 0x1f, 0x24, 0x21, 0x40, 0x06, 0x4e, 0x06, - 0x51, 0x06, 0x27, 0x06, 0x10, 0x22, 0x10, 0x23, - 0x12, 0x22, 0x12, 0x23, 0x13, 0x22, 0x13, 0x23, - 0x0c, 0x22, 0x0c, 0x23, 0x0d, 0x22, 0x0d, 0x23, - 0x06, 0x22, 0x06, 0x23, 0x05, 0x22, 0x05, 0x23, - 0x07, 0x22, 0x07, 0x23, 0x0e, 0x22, 0x0e, 0x23, - 0x0f, 0x22, 0x0f, 0x23, 0x0d, 0x05, 0x0d, 0x06, - 0x0d, 0x07, 0x0d, 0x1e, 0x0d, 0x0a, 0x0c, 0x0a, - 0x0e, 0x0a, 0x0f, 0x0a, 0x10, 0x22, 0x10, 0x23, - 0x12, 0x22, 0x12, 0x23, 0x13, 0x22, 0x13, 0x23, - 0x0c, 0x22, 0x0c, 0x23, 0x0d, 0x22, 0x0d, 0x23, - 0x06, 0x22, 0x06, 0x23, 0x05, 0x22, 0x05, 0x23, - 0x07, 0x22, 0x07, 0x23, 0x0e, 0x22, 0x0e, 0x23, - 0x0f, 0x22, 0x0f, 0x23, 0x0d, 0x05, 0x0d, 0x06, - 0x0d, 0x07, 0x0d, 0x1e, 0x0d, 0x0a, 0x0c, 0x0a, - 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, 0x05, 0x0d, 0x06, - 0x0d, 0x07, 0x0d, 0x1e, 0x0c, 0x20, 0x0d, 0x20, - 0x10, 0x1e, 0x0c, 0x05, 0x0c, 0x06, 0x0c, 0x07, - 0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x10, 0x1e, - 0x11, 0x1e, 0x00, 0x24, 0x00, 0x24, 0x2a, 0x06, - 0x00, 0x02, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x03, - 0x02, 0x00, 0x03, 0x1b, 0x00, 0x04, 0x1b, 0x00, - 0x1b, 0x02, 0x00, 0x1b, 0x03, 0x00, 0x1b, 0x04, - 0x02, 0x1b, 0x03, 0x02, 0x1b, 0x03, 0x03, 0x1b, - 0x20, 0x03, 0x1b, 0x1f, 0x09, 0x03, 0x02, 0x09, - 0x02, 0x03, 0x09, 0x02, 0x1f, 0x09, 0x1b, 0x03, - 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x02, 0x09, 0x1b, - 0x1b, 0x09, 0x1b, 0x1b, 0x0b, 0x03, 0x03, 0x0b, - 0x03, 0x03, 0x0b, 0x1b, 0x1b, 0x0a, 0x03, 0x1b, - 0x0a, 0x03, 0x1b, 0x0a, 0x02, 0x20, 0x0a, 0x1b, - 0x04, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x1b, 0x0a, - 0x1b, 0x1b, 0x0c, 0x03, 0x1f, 0x0c, 0x04, 0x1b, - 0x0c, 0x04, 0x1b, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, - 0x03, 0x0d, 0x1b, 0x1b, 0x0d, 0x1b, 0x20, 0x0f, - 0x02, 0x1b, 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1b, - 0x0f, 0x1b, 0x1f, 0x10, 0x1b, 0x1b, 0x10, 0x1b, - 0x20, 0x10, 0x1b, 0x1f, 0x17, 0x04, 0x1b, 0x17, - 0x04, 0x1b, 0x18, 0x1b, 0x03, 0x18, 0x1b, 0x1b, - 0x1a, 0x03, 0x1b, 0x1a, 0x03, 0x20, 0x1a, 0x03, - 0x1f, 0x1a, 0x02, 0x02, 0x1a, 0x02, 0x02, 0x1a, - 0x04, 0x1b, 0x1a, 0x04, 0x1b, 0x1a, 0x1b, 0x03, - 0x1a, 0x1b, 0x03, 0x1b, 0x03, 0x02, 0x1b, 0x03, - 0x1b, 0x1b, 0x03, 0x20, 0x1b, 0x02, 0x03, 0x1b, - 0x02, 0x1b, 0x1b, 0x04, 0x02, 0x1b, 0x04, 0x1b, - 0x28, 0x06, 0x1d, 0x04, 0x06, 0x1f, 0x1d, 0x04, - 0x1f, 0x1d, 0x1d, 0x1e, 0x05, 0x1d, 0x1e, 0x05, - 0x21, 0x1e, 0x04, 0x1d, 0x1e, 0x04, 0x1d, 0x1e, - 0x04, 0x21, 0x1e, 0x1d, 0x22, 0x1e, 0x1d, 0x21, - 0x22, 0x1d, 0x1d, 0x22, 0x1d, 0x1d, 0x00, 0x06, - 0x22, 0x02, 0x04, 0x22, 0x02, 0x04, 0x21, 0x02, - 0x06, 0x22, 0x02, 0x06, 0x21, 0x02, 0x1d, 0x22, - 0x02, 0x1d, 0x21, 0x04, 0x1d, 0x22, 0x04, 0x05, - 0x21, 0x04, 0x1d, 0x21, 0x0b, 0x06, 0x21, 0x0d, - 0x05, 0x22, 0x0c, 0x05, 0x22, 0x0e, 0x05, 0x22, - 0x1c, 0x04, 0x22, 0x1c, 0x1d, 0x22, 0x22, 0x05, - 0x22, 0x22, 0x04, 0x22, 0x22, 0x1d, 0x22, 0x1d, - 0x1d, 0x22, 0x1a, 0x1d, 0x22, 0x1e, 0x05, 0x22, - 0x1a, 0x1d, 0x05, 0x1c, 0x05, 0x1d, 0x11, 0x1d, - 0x22, 0x1b, 0x1d, 0x22, 0x1e, 0x04, 0x05, 0x1d, - 0x06, 0x22, 0x1c, 0x04, 0x1d, 0x1b, 0x1d, 0x1d, - 0x1c, 0x04, 0x1d, 0x1e, 0x04, 0x05, 0x04, 0x05, - 0x22, 0x05, 0x04, 0x22, 0x1d, 0x04, 0x22, 0x19, - 0x1d, 0x22, 0x00, 0x05, 0x22, 0x1b, 0x1d, 0x1d, - 0x11, 0x04, 0x1d, 0x0d, 0x1d, 0x1d, 0x0b, 0x06, - 0x22, 0x1e, 0x04, 0x22, 0x35, 0x06, 0x00, 0x0f, - 0x9d, 0x0d, 0x0f, 0x9d, 0x27, 0x06, 0x00, 0x1d, - 0x1d, 0x20, 0x00, 0x1c, 0x01, 0x0a, 0x1e, 0x06, - 0x1e, 0x08, 0x0e, 0x1d, 0x12, 0x1e, 0x0a, 0x0c, - 0x21, 0x1d, 0x12, 0x1d, 0x23, 0x20, 0x21, 0x0c, - 0x1d, 0x1e, 0x35, 0x06, 0x00, 0x0f, 0x14, 0x27, - 0x06, 0x0e, 0x1d, 0x22, 0xff, 0x00, 0x1d, 0x1d, - 0x20, 0xff, 0x12, 0x1d, 0x23, 0x20, 0xff, 0x21, - 0x0c, 0x1d, 0x1e, 0x27, 0x06, 0x05, 0x1d, 0xff, - 0x05, 0x1d, 0x00, 0x1d, 0x20, 0x27, 0x06, 0x0a, - 0xa5, 0x00, 0x1d, 0x2c, 0x00, 0x01, 0x30, 0x02, - 0x30, 0x3a, 0x00, 0x3b, 0x00, 0x21, 0x00, 0x3f, - 0x00, 0x16, 0x30, 0x17, 0x30, 0x26, 0x20, 0x13, - 0x20, 0x12, 0x01, 0x00, 0x5f, 0x5f, 0x28, 0x29, - 0x7b, 0x7d, 0x08, 0x30, 0x0c, 0x0d, 0x08, 0x09, - 0x02, 0x03, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07, - 0x5b, 0x00, 0x5d, 0x00, 0x3e, 0x20, 0x3e, 0x20, - 0x3e, 0x20, 0x3e, 0x20, 0x5f, 0x00, 0x5f, 0x00, - 0x5f, 0x00, 0x2c, 0x00, 0x01, 0x30, 0x2e, 0x00, - 0x00, 0x00, 0x3b, 0x00, 0x3a, 0x00, 0x3f, 0x00, - 0x21, 0x00, 0x14, 0x20, 0x28, 0x00, 0x29, 0x00, - 0x7b, 0x00, 0x7d, 0x00, 0x14, 0x30, 0x15, 0x30, - 0x23, 0x26, 0x2a, 0x2b, 0x2d, 0x3c, 0x3e, 0x3d, - 0x00, 0x5c, 0x24, 0x25, 0x40, 0x40, 0x06, 0xff, - 0x0b, 0x00, 0x0b, 0xff, 0x0c, 0x20, 0x00, 0x4d, - 0x06, 0x40, 0x06, 0xff, 0x0e, 0x00, 0x0e, 0xff, - 0x0f, 0x00, 0x0f, 0xff, 0x10, 0x00, 0x10, 0xff, - 0x11, 0x00, 0x11, 0xff, 0x12, 0x00, 0x12, 0x21, - 0x06, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, - 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, - 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09, - 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, - 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10, - 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, - 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, - 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, - 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, - 0x19, 0x19, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, - 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, - 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, - 0x25, 0x25, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, - 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x22, 0x06, - 0x22, 0x00, 0x22, 0x00, 0x22, 0x01, 0x22, 0x01, - 0x22, 0x03, 0x22, 0x03, 0x22, 0x05, 0x22, 0x05, - 0x21, 0x00, 0x85, 0x29, 0x01, 0x30, 0x01, 0x0b, - 0x0c, 0x00, 0xfa, 0xf1, 0xa0, 0xa2, 0xa4, 0xa6, - 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, 0xfb, 0xa1, 0xa3, - 0xa5, 0xa7, 0xa9, 0xaa, 0xac, 0xae, 0xb0, 0xb2, - 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc3, - 0xc5, 0xc7, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, - 0xd1, 0xd4, 0xd7, 0xda, 0xdd, 0xde, 0xdf, 0xe0, - 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, - 0xec, 0xee, 0xf2, 0x98, 0x99, 0x31, 0x31, 0x4f, - 0x31, 0x55, 0x31, 0x5b, 0x31, 0x61, 0x31, 0xa2, - 0x00, 0xa3, 0x00, 0xac, 0x00, 0xaf, 0x00, 0xa6, - 0x00, 0xa5, 0x00, 0xa9, 0x20, 0x00, 0x00, 0x02, - 0x25, 0x90, 0x21, 0x91, 0x21, 0x92, 0x21, 0x93, - 0x21, 0xa0, 0x25, 0xcb, 0x25, 0x99, 0x10, 0xba, - 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, 0xba, - 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, 0x05, - 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, 0x11, - 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, 0x57, - 0x13, 0x55, 0xb9, 0x14, 0xba, 0x14, 0xb9, 0x14, - 0xb0, 0x14, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x14, - 0xbd, 0x14, 0x55, 0x50, 0xb8, 0x15, 0xaf, 0x15, - 0xb9, 0x15, 0xaf, 0x15, 0x55, 0x35, 0x19, 0x30, - 0x19, 0x05, 0x57, 0xd1, 0x65, 0xd1, 0x58, 0xd1, - 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, 0xd1, - 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, 0xd1, - 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, 0x55, - 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, 0xd1, - 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, 0xd1, - 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, 0xd1, - 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, 0x61, - 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, 0x00, - 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, 0x00, - 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, - 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, - 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, 0x00, - 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, 0x00, - 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, 0x53, - 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, 0x91, - 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, - 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, - 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, - 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, - 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, - 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, - 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, - 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, - 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, 0x30, - 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, - 0x00, 0x27, 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, - 0x06, 0x1e, 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, - 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, - 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, - 0x44, 0x90, 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, - 0x00, 0x00, 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, - 0x11, 0x12, 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, - 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, + 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, + 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, + 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, + 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, + 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, + 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, + 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, + 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, + 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, + 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, + 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, + 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, + 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, + 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, + 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, + 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, + 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, + 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, + 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, + 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, + 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, + 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, + 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, + 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, + 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, + 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, + 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, + 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, + 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, + 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, + 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, + 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, + 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, + 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, + 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, + 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, + 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, + 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, + 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, + 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, + 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, + 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, + 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, + 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, + 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, + 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, + 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, + 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, + 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, + 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, + 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, + 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, + 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, + 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, + 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, + 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, + 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, + 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, + 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, + 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, + 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, + 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, + 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, + 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, + 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, + 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, + 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, + 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, + 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, + 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, + 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, + 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, + 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, + 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, + 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, + 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, + 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, + 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, + 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, + 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, + 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, + 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, + 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, + 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, + 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, + 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, + 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, + 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, + 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, + 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, + 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, + 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, + 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, + 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, + 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, + 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, + 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, + 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, + 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, + 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, + 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, + 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, + 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, + 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, + 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, 0x07, + 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, + 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, + 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, + 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, + 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, + 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, + 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, + 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, + 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, + 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, + 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, + 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, + 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, + 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, + 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, + 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, + 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, + 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, + 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, + 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, 0x05, + 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, 0x13, + 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, 0x00, + 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, 0xc2, + 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, 0xba, + 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, + 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, 0x55, + 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, 0x1e, + 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, 0x1f, + 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, 0x20, + 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, 0x1f, + 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, 0x55, + 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, 0x67, + 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, 0x41, + 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, 0x58, + 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, + 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, + 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, + 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, + 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, + 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, + 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, + 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, + 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, + 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, + 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, + 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, + 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, + 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, + 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, + 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, + 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, + 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, + 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, + 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, + 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, + 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, + 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, + 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, + 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, + 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, + 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, + 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, + 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, + 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, - 0x2d, 0x06, 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, - 0x44, 0x06, 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, - 0x39, 0x06, 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, - 0x00, 0x00, 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x2e, 0x06, 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, - 0x3a, 0x06, 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, - 0x6f, 0x06, 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, - 0x00, 0x00, 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, - 0x00, 0x00, 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, - 0x39, 0x06, 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, - 0x00, 0x00, 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, - 0x2e, 0x06, 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, - 0x3a, 0x06, 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, - 0x27, 0x06, 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, - 0x0b, 0x06, 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, - 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, - 0x00, 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, - 0x06, 0x2c, 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, - 0x06, 0x32, 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, - 0x06, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, - 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, - 0x06, 0x0c, 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, - 0x2c, 0x00, 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, - 0x14, 0x30, 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, - 0x43, 0x44, 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, - 0x4d, 0x56, 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, - 0x56, 0x57, 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, - 0x52, 0x44, 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, - 0x68, 0x4b, 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, - 0x30, 0x8c, 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, - 0x59, 0xa4, 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, - 0x65, 0x4d, 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, - 0x65, 0x1d, 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, - 0x8c, 0xf0, 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, - 0x62, 0x55, 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, - 0x90, 0xe6, 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, - 0x63, 0x70, 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, - 0x7a, 0x08, 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, - 0x67, 0x33, 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, - 0x91, 0x14, 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, - 0x4e, 0x8c, 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, - 0x62, 0xd7, 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, - 0x5f, 0xef, 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, - 0x00, 0x09, 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, - 0xbb, 0x4f, 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, - 0xe7, 0x50, 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, - 0x4d, 0x51, 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, - 0x1c, 0x05, 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, - 0x4b, 0x05, 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, - 0xac, 0x51, 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, - 0x03, 0x52, 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, - 0x72, 0x52, 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, - 0x20, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, - 0x52, 0x00, 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, - 0x82, 0x8a, 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, - 0x2c, 0x0a, 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, - 0x63, 0x0b, 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, - 0x9e, 0x54, 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, - 0xa2, 0x54, 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, - 0x63, 0x55, 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, - 0xab, 0x55, 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, - 0x06, 0x56, 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, - 0x07, 0x52, 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, - 0x0d, 0x58, 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, - 0xac, 0x58, 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, - 0x06, 0x59, 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, - 0xa8, 0x16, 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, - 0x27, 0x5a, 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, - 0xfc, 0x36, 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, - 0xc8, 0x19, 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, - 0xf3, 0x5b, 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, - 0x53, 0x5f, 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, - 0x6e, 0x5c, 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, - 0x43, 0x5d, 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, - 0x7c, 0x5d, 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, - 0xfd, 0x5d, 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, - 0x62, 0x38, 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, - 0xb3, 0x5e, 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, - 0xfe, 0x5e, 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, - 0x22, 0x5f, 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, - 0xda, 0x61, 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, - 0x9a, 0x5f, 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, - 0x81, 0x60, 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, - 0xd4, 0x26, 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, - 0x00, 0x00, 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, - 0x00, 0x08, 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, - 0x02, 0x48, 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, - 0x46, 0x6a, 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, - 0xd3, 0x5d, 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, - 0x2b, 0x3d, 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, - 0x63, 0xe4, 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, - 0x63, 0xa9, 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, - 0x64, 0x9d, 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, - 0x65, 0x6c, 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, - 0x66, 0x49, 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, - 0x3b, 0xe4, 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, - 0x67, 0x9c, 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, - 0x67, 0x1b, 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, - 0x67, 0xc3, 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, - 0x67, 0x52, 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, - 0x68, 0x1f, 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, - 0x69, 0xa3, 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, - 0x36, 0xdb, 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, - 0x38, 0x54, 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, - 0x6b, 0xba, 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, - 0x1d, 0xfa, 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, - 0x6c, 0xcd, 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, - 0x6d, 0x77, 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, - 0x6d, 0x85, 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, - 0x6e, 0x6e, 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, - 0x6e, 0xd1, 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, - 0x3f, 0x8e, 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, - 0x70, 0x1b, 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, - 0x70, 0x77, 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, - 0x71, 0x63, 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, - 0x72, 0x35, 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, - 0x72, 0x95, 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, - 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, - 0x00, 0x00, 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, - 0x20, 0x00, 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, - 0x20, 0x14, 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, - 0x3e, 0xa5, 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, - 0x74, 0x5c, 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, - 0x74, 0x1b, 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, - 0x75, 0x92, 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, - 0x76, 0xa1, 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, - 0x3f, 0x08, 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, - 0x50, 0x19, 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, - 0x77, 0x1f, 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, - 0x77, 0x46, 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, - 0x78, 0x8c, 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, - 0x56, 0x56, 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, - 0x79, 0xeb, 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, - 0x7a, 0x4f, 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, - 0x5a, 0xee, 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, - 0x7b, 0xc9, 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, - 0x7c, 0xa0, 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, - 0x7d, 0x86, 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, - 0x7d, 0x02, 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, - 0x62, 0x47, 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, - 0x7f, 0x3e, 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, - 0x80, 0xda, 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, - 0x65, 0x70, 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, - 0x80, 0x03, 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, - 0x5a, 0xa7, 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, - 0x33, 0x01, 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, - 0x44, 0x91, 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, - 0x52, 0xb1, 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, - 0x82, 0x3c, 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, - 0x83, 0xad, 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, - 0x83, 0x57, 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, - 0x83, 0xdc, 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, - 0x00, 0x00, 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, - 0x20, 0x80, 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, - 0x02, 0x80, 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, - 0x6c, 0x2b, 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, - 0x85, 0xca, 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, - 0x45, 0x61, 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, - 0x45, 0x50, 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, - 0x86, 0xa9, 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, - 0x86, 0x79, 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, - 0x87, 0xd7, 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, - 0x45, 0x60, 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, - 0x88, 0xde, 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, - 0x34, 0xae, 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, - 0x46, 0xa0, 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, - 0x8c, 0xa8, 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, - 0x8d, 0x77, 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, - 0x8d, 0xbc, 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, - 0x8e, 0x38, 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, - 0x90, 0xf1, 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, - 0x91, 0x38, 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, - 0x92, 0xf9, 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, - 0x95, 0x95, 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, - 0x49, 0xc3, 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, - 0x91, 0x1a, 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, - 0x97, 0x0a, 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, - 0x98, 0x0b, 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, - 0x98, 0x33, 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, - 0x99, 0xfe, 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, - 0x9b, 0x40, 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, - 0x4c, 0x67, 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, - 0xa1, 0x0e, 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, - 0x4d, 0xf9, 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, - 0x9f, 0x16, 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, - 0x88, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, - 0x28, 0x00, 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, - 0x80, 0x80, 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, - 0x00, 0x20, 0x2a, 0x00, 0x80, + 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, + 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, + 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, + 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, + 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, + 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, + 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, + 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, + 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, + 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, + 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, + 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, + 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, + 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, + 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, + 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, + 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, + 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, + 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, + 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, + 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, + 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, + 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, + 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, + 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, + 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, + 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, + 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, + 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, + 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, + 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, + 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, + 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, + 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, + 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, + 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, + 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, + 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, + 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, + 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, + 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, + 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, + 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, + 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, + 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, + 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, + 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, + 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, + 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, + 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, + 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, + 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, + 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, + 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, + 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, + 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, + 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, + 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, + 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, + 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, + 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, + 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, + 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, + 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, + 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, + 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, + 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, + 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, + 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, + 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, + 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, + 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, + 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, + 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, + 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, + 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, + 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, + 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, + 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, + 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, + 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, + 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, + 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, + 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, + 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, + 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, + 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, + 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, + 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, + 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, + 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, + 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, + 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, + 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, + 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, + 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, + 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, + 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, + 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, + 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, + 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, + 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, + 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, + 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, + 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, + 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, + 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, + 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, + 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, + 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, + 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, + 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, + 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, + 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, + 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, + 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, + 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, + 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, + 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, + 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, + 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, + 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, + 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, + 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, + 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, + 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, + 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, + 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, + 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, + 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, + 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, + 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, + 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, + 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, + 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, + 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, + 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, + 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, + 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, + 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, + 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, + 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, + 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, + 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, + 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, + 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, + 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, + 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, + 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, + 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, + 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, + 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, + 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, + 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, + 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, + 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, + 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, + 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, + 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, + 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, + 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, + 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, + 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, + 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, + 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, + 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, + 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, + 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, + 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, + 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, + 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, + 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, + 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, + 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, + 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, + 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, + 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, + 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, + 0x2a, 0x00, 0x80, }; -static const uint16_t unicode_comp_table945 = { +static const uint16_t unicode_comp_table965 = { 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, @@ -2191,9 +2352,11 @@ 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, - 0x5901, 0x5902, 0x5903, 0x5940, 0x8e80, 0x8e82, 0x8ec0, 0x8f00, - 0x8f01, 0x8f40, 0x8f41, 0x8f81, 0x8f80, 0x8f83, 0x8fc0, 0x8fc1, - 0x9000, + 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, + 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, + 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, + 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, + 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, }; typedef enum { @@ -2279,7 +2442,7 @@ "C,Other" "\0" ; -static const uint8_t unicode_gc_table3790 = { +static const uint8_t unicode_gc_table4070 = { 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, @@ -2312,348 +2475,365 @@ 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, - 0x03, 0x16, 0x1b, 0x00, 0x36, 0xe5, 0x18, 0x04, - 0xe5, 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, - 0x06, 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, - 0xa6, 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, - 0x45, 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, - 0x06, 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, - 0xe6, 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, - 0x19, 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, - 0x06, 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, - 0x04, 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, - 0xe5, 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, - 0xe0, 0x2d, 0xe5, 0x0d, 0x00, 0xe5, 0x0a, 0xe0, - 0x03, 0xe6, 0x07, 0x1b, 0xe6, 0x18, 0x07, 0xe5, - 0x2e, 0x06, 0x07, 0x06, 0x05, 0x47, 0xe6, 0x00, - 0x67, 0x06, 0x27, 0x05, 0xc6, 0xe5, 0x02, 0x26, - 0x36, 0xe9, 0x02, 0x16, 0x04, 0xe5, 0x07, 0x06, - 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, 0x20, 0xe5, - 0x0e, 0x00, 0xc5, 0x00, 0x05, 0x40, 0x65, 0x20, - 0x06, 0x05, 0x47, 0x66, 0x20, 0x27, 0x20, 0x27, - 0x06, 0x05, 0xe0, 0x00, 0x07, 0x60, 0x25, 0x00, - 0x45, 0x26, 0x20, 0xe9, 0x02, 0x25, 0x2d, 0xab, - 0x0f, 0x0d, 0x05, 0x16, 0x06, 0x20, 0x26, 0x07, - 0x00, 0xa5, 0x60, 0x25, 0x20, 0xe5, 0x0e, 0x00, - 0xc5, 0x00, 0x25, 0x00, 0x25, 0x00, 0x25, 0x20, - 0x06, 0x00, 0x47, 0x26, 0x60, 0x26, 0x20, 0x46, - 0x40, 0x06, 0xc0, 0x65, 0x00, 0x05, 0xc0, 0xe9, - 0x02, 0x26, 0x45, 0x06, 0x16, 0xe0, 0x02, 0x26, - 0x07, 0x00, 0xe5, 0x01, 0x00, 0x45, 0x00, 0xe5, - 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x85, 0x20, - 0x06, 0x05, 0x47, 0x86, 0x00, 0x26, 0x07, 0x00, - 0x27, 0x06, 0x20, 0x05, 0xe0, 0x07, 0x25, 0x26, - 0x20, 0xe9, 0x02, 0x16, 0x0d, 0xc0, 0x05, 0xa6, - 0x00, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, - 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, - 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x07, 0x66, - 0x20, 0x27, 0x20, 0x27, 0x06, 0xc0, 0x26, 0x07, + 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, + 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, + 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, + 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, + 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, + 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, + 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, + 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, + 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, + 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, + 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, + 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0x80, 0xe6, + 0x01, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, + 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, + 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, + 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, + 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, + 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, + 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, + 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, - 0x0f, 0x05, 0xab, 0xe0, 0x02, 0x06, 0x05, 0x00, - 0xa5, 0x40, 0x45, 0x00, 0x65, 0x40, 0x25, 0x00, - 0x05, 0x00, 0x25, 0x40, 0x25, 0x40, 0x45, 0x40, - 0xe5, 0x04, 0x60, 0x27, 0x06, 0x27, 0x40, 0x47, - 0x00, 0x47, 0x06, 0x20, 0x05, 0xa0, 0x07, 0xe0, - 0x06, 0xe9, 0x02, 0x4b, 0xaf, 0x0d, 0x0f, 0x80, - 0x06, 0x47, 0x06, 0xe5, 0x00, 0x00, 0x45, 0x00, - 0xe5, 0x0f, 0x00, 0xe5, 0x08, 0x40, 0x05, 0x46, - 0x67, 0x00, 0x46, 0x00, 0x66, 0xc0, 0x26, 0x00, - 0x45, 0x80, 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, - 0x16, 0xcb, 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, - 0x00, 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, - 0x02, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, - 0x87, 0x00, 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, - 0x27, 0xc0, 0x05, 0x00, 0x25, 0x26, 0x20, 0xe9, - 0x02, 0x00, 0x25, 0xe0, 0x05, 0x26, 0x27, 0xe5, - 0x01, 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, - 0x47, 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, - 0x0f, 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, - 0xe9, 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, - 0x27, 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, - 0xe5, 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, - 0x60, 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, - 0xa0, 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, - 0xe5, 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, - 0x04, 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, - 0x1d, 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, - 0x10, 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, - 0xe6, 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, - 0xa6, 0x20, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, - 0x05, 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, - 0xaf, 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, - 0x06, 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, - 0xe5, 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, - 0x07, 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, - 0xe6, 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, - 0x2f, 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, - 0x27, 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, - 0x05, 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, - 0x46, 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, - 0x05, 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, - 0xe9, 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, - 0x01, 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, - 0x42, 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, - 0x00, 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, - 0x65, 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, - 0x00, 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, - 0xe5, 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, - 0x46, 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, - 0xef, 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, - 0x11, 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, - 0x17, 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, - 0x56, 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x05, 0x00, - 0x65, 0x46, 0xe0, 0x03, 0xe5, 0x0a, 0x46, 0x36, - 0xe0, 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, - 0x05, 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, - 0x2c, 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, - 0xe6, 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, - 0x20, 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, - 0x11, 0x76, 0x46, 0x1b, 0x00, 0xe9, 0x02, 0xa0, - 0xe5, 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, - 0xe5, 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, - 0x02, 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, - 0x60, 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, - 0x36, 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, - 0x03, 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, - 0x02, 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, - 0x27, 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, - 0x07, 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, - 0x00, 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, - 0xa0, 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, - 0xe6, 0x06, 0x08, 0x26, 0xe0, 0x37, 0x66, 0x07, + 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, + 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, + 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, + 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, + 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, + 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, + 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, + 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, + 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, + 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, + 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, + 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, + 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, + 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, + 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, + 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, + 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, + 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, + 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, + 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, + 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, + 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, + 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, + 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, + 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, + 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, + 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, + 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, + 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, + 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, + 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, + 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, + 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, + 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, + 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, + 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, + 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, + 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, + 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, + 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, + 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, + 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, + 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, + 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, + 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, + 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, + 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, + 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, + 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, + 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, + 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, + 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, + 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, + 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, + 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, + 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, + 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, + 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, + 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, + 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, + 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, + 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, + 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, + 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, + 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, + 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, + 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, + 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, + 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, + 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, + 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, + 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, + 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, + 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, + 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, + 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, + 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, + 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, + 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, + 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, + 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, + 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, + 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, - 0x06, 0x27, 0xc5, 0x60, 0xe9, 0x02, 0xd6, 0xef, - 0x02, 0xe6, 0x01, 0xef, 0x01, 0x40, 0x26, 0x07, - 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, 0x07, 0x46, - 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, 0x07, 0x26, - 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, 0x00, 0x76, - 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, 0x27, 0x26, - 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, 0xe9, 0x02, - 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, 0xc0, 0xe1, - 0x23, 0x20, 0x41, 0xf6, 0x00, 0xe0, 0x00, 0x46, - 0x16, 0xe6, 0x05, 0x07, 0xc6, 0x65, 0x06, 0xa5, - 0x06, 0x25, 0x07, 0x26, 0x05, 0x80, 0xe2, 0x24, - 0xe4, 0x37, 0xe2, 0x05, 0x04, 0xe2, 0x1a, 0xe4, - 0x1d, 0xe6, 0x32, 0x00, 0x86, 0xff, 0x80, 0x0e, - 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, 0x00, - 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, 0x00, - 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, 0x20, - 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, 0x20, - 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, 0x00, - 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, 0x61, - 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, 0x61, - 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, 0x4e, - 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, 0x22, - 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, 0xb1, - 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, 0x14, - 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, 0x01, - 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, 0x13, - 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, 0x17, - 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, 0xab, - 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, 0x12, - 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x18, 0xe0, - 0x08, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, 0x04, - 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, 0x02, - 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, 0x0c, - 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, - 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, 0x2f, - 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, 0x2f, - 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, 0x6a, - 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, 0x0c, - 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, 0x17, - 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, 0xec, - 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, 0x13, - 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, 0x49, - 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, 0xac, - 0xef, 0x3d, 0xe0, 0x11, 0xef, 0x03, 0xe0, 0x0d, - 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, 0x80, - 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, 0xec, - 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, 0xef, - 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, 0x13, + 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, 0xe9, 0x02, + 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x56, + 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, + 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, + 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, + 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, + 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, + 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, + 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, + 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, + 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, + 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, + 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, + 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, + 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, + 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, + 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, + 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, + 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, + 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, + 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, + 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, + 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, + 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, + 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, + 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, + 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, + 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, + 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, + 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, + 0xe0, 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, + 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, + 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, + 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, + 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, + 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, + 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, + 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, + 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, + 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, + 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, + 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, + 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, 0x03, 0xe0, + 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, + 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, + 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, 0x12, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, + 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, 0x12, - 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, 0xec, - 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, 0xac, - 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, 0x61, - 0xe1, 0x27, 0x00, 0xe2, 0x27, 0x00, 0x5f, 0x21, - 0x22, 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, - 0x24, 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, - 0x46, 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, - 0x00, 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, - 0x04, 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, - 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, - 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, - 0x00, 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, - 0x56, 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, - 0x11, 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, - 0x15, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, - 0x16, 0x12, 0xf6, 0x05, 0x2f, 0x16, 0xe0, 0x25, - 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, - 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x04, 0x60, 0x17, - 0x56, 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, + 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x11, 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, - 0x11, 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, - 0x00, 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, - 0x11, 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, - 0x23, 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, - 0x02, 0xe5, 0x18, 0xef, 0x1c, 0xe0, 0x04, 0xe5, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, + 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, + 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, + 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, + 0x61, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, + 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, + 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, + 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, + 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, + 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, + 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, + 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, + 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, + 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, + 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, + 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, + 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, + 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, + 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, + 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, + 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, + 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, - 0x75, 0x40, 0xe5, 0x0d, 0x04, 0xe5, 0x83, 0xef, - 0x40, 0xef, 0x2f, 0xe0, 0x01, 0xe5, 0x20, 0xa4, - 0x36, 0xe5, 0x80, 0x84, 0x04, 0x56, 0xe5, 0x08, - 0xe9, 0x02, 0x25, 0xe0, 0x0c, 0xff, 0x26, 0x05, - 0x06, 0x48, 0x16, 0xe6, 0x02, 0x16, 0x04, 0xff, - 0x14, 0x24, 0x26, 0xe5, 0x3e, 0xea, 0x02, 0x26, - 0xb6, 0xe0, 0x00, 0xee, 0x0f, 0xe4, 0x01, 0x2e, - 0xff, 0x06, 0x22, 0xff, 0x36, 0x04, 0xe2, 0x00, - 0x9f, 0xff, 0x02, 0x04, 0x2e, 0x7f, 0x05, 0x7f, - 0x22, 0xff, 0x0d, 0x61, 0x02, 0x81, 0x02, 0xff, - 0x02, 0x20, 0x5f, 0x41, 0x02, 0x3f, 0xe0, 0x22, - 0x3f, 0x05, 0x24, 0x02, 0xc5, 0x06, 0x45, 0x06, - 0x65, 0x06, 0xe5, 0x0f, 0x27, 0x26, 0x07, 0x6f, - 0x06, 0x40, 0xab, 0x2f, 0x0d, 0x0f, 0xa0, 0xe5, - 0x2c, 0x76, 0xe0, 0x00, 0x27, 0xe5, 0x2a, 0xe7, - 0x08, 0x26, 0xe0, 0x00, 0x36, 0xe9, 0x02, 0xa0, - 0xe6, 0x0a, 0xa5, 0x56, 0x05, 0x16, 0x25, 0x06, - 0xe9, 0x02, 0xe5, 0x14, 0xe6, 0x00, 0x36, 0xe5, - 0x0f, 0xe6, 0x03, 0x27, 0xe0, 0x03, 0x16, 0xe5, - 0x15, 0x40, 0x46, 0x07, 0xe5, 0x27, 0x06, 0x27, - 0x66, 0x27, 0x26, 0x47, 0xf6, 0x05, 0x00, 0x04, - 0xe9, 0x02, 0x60, 0x36, 0x85, 0x06, 0x04, 0xe5, - 0x01, 0xe9, 0x02, 0x85, 0x00, 0xe5, 0x21, 0xa6, - 0x27, 0x26, 0x27, 0x26, 0xe0, 0x01, 0x45, 0x06, - 0xe5, 0x00, 0x06, 0x07, 0x20, 0xe9, 0x02, 0x20, - 0x76, 0xe5, 0x08, 0x04, 0xa5, 0x4f, 0x05, 0x07, - 0x06, 0x07, 0xe5, 0x2a, 0x06, 0x05, 0x46, 0x25, - 0x26, 0x85, 0x26, 0x05, 0x06, 0x05, 0xe0, 0x10, - 0x25, 0x04, 0x36, 0xe5, 0x03, 0x07, 0x26, 0x27, - 0x36, 0x05, 0x24, 0x07, 0x06, 0xe0, 0x02, 0xa5, - 0x20, 0xa5, 0x20, 0xa5, 0xe0, 0x01, 0xc5, 0x00, - 0xc5, 0x00, 0xe2, 0x23, 0x0e, 0x64, 0xe2, 0x01, - 0x04, 0x2e, 0x60, 0xe2, 0x48, 0xe5, 0x1b, 0x27, - 0x06, 0x27, 0x06, 0x27, 0x16, 0x07, 0x06, 0x20, - 0xe9, 0x02, 0xa0, 0xe5, 0xab, 0x1c, 0xe0, 0x04, - 0xe5, 0x0f, 0x60, 0xe5, 0x29, 0x60, 0xfc, 0x87, - 0x78, 0xfd, 0x98, 0x78, 0xe5, 0x80, 0xe6, 0x20, - 0xe5, 0x62, 0xe0, 0x1e, 0xc2, 0xe0, 0x04, 0x82, - 0x80, 0x05, 0x06, 0xe5, 0x02, 0x0c, 0xe5, 0x05, - 0x00, 0x85, 0x00, 0x05, 0x00, 0x25, 0x00, 0x25, - 0x00, 0xe5, 0x64, 0xee, 0x08, 0xe0, 0x09, 0xe5, - 0x80, 0xe3, 0x13, 0x12, 0xe0, 0x08, 0xe5, 0x38, - 0x20, 0xe5, 0x2e, 0xe0, 0x20, 0xe5, 0x04, 0x0d, - 0x0f, 0x20, 0xe6, 0x08, 0xd6, 0x12, 0x13, 0x16, - 0xa0, 0xe6, 0x08, 0x16, 0x31, 0x30, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x36, 0x12, - 0x13, 0x76, 0x50, 0x56, 0x00, 0x76, 0x11, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x56, 0x0c, 0x11, - 0x4c, 0x00, 0x16, 0x0d, 0x36, 0x60, 0x85, 0x00, - 0xe5, 0x7f, 0x20, 0x1b, 0x00, 0x56, 0x0d, 0x56, - 0x12, 0x13, 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, - 0x02, 0x36, 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, - 0x13, 0x0e, 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, - 0x13, 0x0c, 0x12, 0x13, 0x16, 0x12, 0x13, 0x36, - 0xe5, 0x02, 0x04, 0xe5, 0x25, 0x24, 0xe5, 0x17, - 0x40, 0xa5, 0x20, 0xa5, 0x20, 0xa5, 0x20, 0x45, - 0x40, 0x2d, 0x0c, 0x0e, 0x0f, 0x2d, 0x00, 0x0f, - 0x6c, 0x2f, 0xe0, 0x02, 0x5b, 0x2f, 0x20, 0xe5, - 0x04, 0x00, 0xe5, 0x12, 0x00, 0xe5, 0x0b, 0x00, - 0x25, 0x00, 0xe5, 0x07, 0x20, 0xe5, 0x06, 0xe0, - 0x1a, 0xe5, 0x73, 0x80, 0x56, 0x60, 0xeb, 0x25, - 0x40, 0xef, 0x01, 0xea, 0x2d, 0x6b, 0xef, 0x09, - 0x2b, 0x4f, 0x00, 0xef, 0x05, 0x40, 0x0f, 0xe0, - 0x27, 0xef, 0x25, 0x06, 0xe0, 0x7a, 0xe5, 0x15, - 0x40, 0xe5, 0x29, 0xe0, 0x07, 0x06, 0xeb, 0x13, - 0x60, 0xe5, 0x18, 0x6b, 0xe0, 0x01, 0xe5, 0x0c, - 0x0a, 0xe5, 0x00, 0x0a, 0x80, 0xe5, 0x1e, 0x86, - 0x80, 0xe5, 0x16, 0x00, 0x16, 0xe5, 0x1c, 0x60, - 0xe5, 0x00, 0x16, 0x8a, 0xe0, 0x22, 0xe1, 0x20, - 0xe2, 0x20, 0xe5, 0x46, 0x20, 0xe9, 0x02, 0xa0, - 0xe1, 0x1c, 0x60, 0xe2, 0x1c, 0x60, 0xe5, 0x20, - 0xe0, 0x00, 0xe5, 0x2c, 0xe0, 0x03, 0x16, 0xe0, - 0x80, 0x08, 0xe5, 0x80, 0xaf, 0xe0, 0x01, 0xe5, - 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, 0x80, 0x10, - 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, 0x00, 0x25, - 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, 0x16, 0xeb, - 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, 0x17, 0xe0, - 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, 0x0b, 0x00, - 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, 0x40, 0x16, - 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, 0xe5, 0x30, - 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, 0xeb, 0x26, - 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, 0x65, 0x00, - 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, 0x60, 0x06, - 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, 0xe5, 0x15, - 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, 0x18, 0xe5, - 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, 0x8b, 0xd6, - 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, 0xe5, 0x0e, - 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, 0xeb, 0x00, - 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, 0xcb, 0xe0, - 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, 0x2b, 0xe0, - 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, 0x1c, 0x66, - 0xe0, 0x00, 0xe9, 0x02, 0xe0, 0x80, 0x9e, 0xeb, - 0x17, 0x00, 0xe5, 0x22, 0x00, 0x26, 0x11, 0x20, - 0x25, 0xe0, 0x46, 0xe5, 0x15, 0xeb, 0x02, 0x05, - 0xe0, 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, - 0xe0, 0x4e, 0xe5, 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, - 0x0f, 0xe0, 0x01, 0x07, 0x06, 0x07, 0xe5, 0x2d, - 0xe6, 0x07, 0xd6, 0x60, 0xeb, 0x0c, 0xe9, 0x02, - 0xe0, 0x07, 0x46, 0x07, 0xe5, 0x25, 0x47, 0x66, - 0x27, 0x26, 0x36, 0x1b, 0x76, 0xe0, 0x03, 0x1b, - 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, 0xa0, 0x46, - 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, 0x00, 0xe9, - 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, 0x00, 0xe5, - 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, 0x26, 0x07, - 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, 0x65, 0x76, - 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, 0x05, 0x16, - 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, 0x03, 0xe5, - 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, 0x27, 0x06, - 0x07, 0x26, 0xb6, 0x06, 0xe0, 0x39, 0xc5, 0x00, - 0x05, 0x00, 0x65, 0x00, 0xe5, 0x07, 0x00, 0xe5, - 0x02, 0x16, 0xa0, 0xe5, 0x27, 0x06, 0x47, 0xe6, - 0x00, 0x80, 0xe9, 0x02, 0xa0, 0x26, 0x27, 0x00, - 0xe5, 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, - 0xc5, 0x00, 0x25, 0x00, 0x85, 0x00, 0x26, 0x05, - 0x27, 0x06, 0x67, 0x20, 0x27, 0x20, 0x47, 0x20, - 0x05, 0xa0, 0x07, 0x80, 0x85, 0x27, 0x20, 0xc6, - 0x40, 0x86, 0xe0, 0x80, 0x03, 0xe5, 0x2d, 0x47, - 0xe6, 0x00, 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, - 0xe9, 0x02, 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, - 0x16, 0xe5, 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, - 0x26, 0x07, 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, - 0xe9, 0x02, 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, - 0x66, 0x20, 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, - 0x65, 0x26, 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, - 0x00, 0x27, 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, - 0x03, 0xe9, 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, - 0xe5, 0x23, 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, - 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, 0x2e, 0xe5, - 0x13, 0x20, 0x46, 0x27, 0x66, 0x07, 0x86, 0x60, - 0xe9, 0x02, 0x2b, 0x56, 0x0f, 0xe0, 0x80, 0x38, - 0xe5, 0x24, 0x47, 0xe6, 0x01, 0x07, 0x26, 0x16, - 0xe0, 0x5c, 0xe1, 0x18, 0xe2, 0x18, 0xe9, 0x02, - 0xeb, 0x01, 0xe0, 0x04, 0xe5, 0x00, 0x20, 0x05, - 0x20, 0xe5, 0x00, 0x00, 0x25, 0x00, 0xe5, 0x10, - 0xa7, 0x00, 0x27, 0x20, 0x26, 0x07, 0x06, 0x05, - 0x07, 0x05, 0x07, 0x06, 0x56, 0xe0, 0x01, 0xe9, - 0x02, 0xe0, 0x3e, 0xe5, 0x00, 0x20, 0xe5, 0x1f, - 0x47, 0x66, 0x20, 0x26, 0x67, 0x06, 0x05, 0x16, - 0x05, 0x07, 0xe0, 0x13, 0x05, 0xe6, 0x02, 0xe5, - 0x20, 0xa6, 0x07, 0x05, 0x66, 0xf6, 0x00, 0x06, - 0xe0, 0x00, 0x05, 0xa6, 0x27, 0x46, 0xe5, 0x26, - 0xe6, 0x05, 0x07, 0x26, 0x56, 0x05, 0x96, 0xe0, - 0x15, 0xe5, 0x31, 0xe0, 0x80, 0x7f, 0xe5, 0x01, + 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, + 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, + 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, + 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, + 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, + 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, + 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, + 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, + 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, + 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, + 0x5f, 0x3f, 0x20, 0x3f, 0x00, 0x02, 0x00, 0x02, + 0xdf, 0xe0, 0x0d, 0x44, 0x3f, 0x05, 0x24, 0x02, + 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, + 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, + 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, + 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, + 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, + 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, + 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, + 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, + 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, + 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, + 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, + 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, + 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, + 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, + 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, + 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, + 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, + 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, + 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, + 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, + 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, + 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, + 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, + 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, + 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, + 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, + 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, + 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, + 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, + 0x09, 0xe0, 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, + 0xef, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, + 0x0f, 0xe0, 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, + 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, + 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, + 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, + 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, + 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, + 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, + 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, + 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, + 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, + 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, + 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, + 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, + 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, + 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, + 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, + 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, + 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, + 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, + 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, + 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, + 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, + 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, + 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, + 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, + 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, + 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, + 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, + 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, 0x22, 0x40, + 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, 0xaf, 0xe0, + 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, + 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, 0xe4, 0x01, + 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, + 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, + 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, + 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, + 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, + 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, + 0xe5, 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, + 0xeb, 0x26, 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, + 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, + 0x60, 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, + 0xe5, 0x15, 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, + 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, + 0x8b, 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, + 0xe5, 0x0e, 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, + 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, + 0xcb, 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, + 0x2b, 0xe0, 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, + 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xe9, + 0x02, 0x65, 0x04, 0x05, 0xe1, 0x0e, 0x40, 0x86, + 0x11, 0x04, 0xe2, 0x0e, 0xe0, 0x00, 0x2c, 0xe0, + 0x80, 0x48, 0xeb, 0x17, 0x00, 0xe5, 0x22, 0x00, + 0x26, 0x11, 0x20, 0x25, 0xe0, 0x08, 0x45, 0xe0, + 0x2f, 0x66, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, + 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, + 0x0e, 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, + 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, + 0x07, 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, + 0x60, 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, + 0x05, 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, + 0x66, 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, + 0x02, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, + 0xa0, 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, + 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, + 0x00, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, + 0x26, 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, + 0x65, 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, + 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, + 0x03, 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, + 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, + 0xe0, 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, + 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, + 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, + 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, + 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, + 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, + 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x03, + 0xe5, 0x02, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, + 0x1e, 0x00, 0x05, 0x47, 0xa6, 0x00, 0x07, 0x20, + 0x07, 0x00, 0x67, 0x00, 0x27, 0x06, 0x07, 0x06, + 0x05, 0x06, 0x05, 0x36, 0x00, 0x36, 0xe0, 0x00, + 0x26, 0xe0, 0x15, 0xe5, 0x2d, 0x47, 0xe6, 0x00, + 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02, + 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, 0x16, 0xe5, + 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, + 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9, 0x02, + 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, 0x20, + 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, + 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00, 0x27, + 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, 0xe9, + 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, + 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06, 0x05, + 0x16, 0xa0, 0xe9, 0x02, 0xa0, 0xe9, 0x0c, 0xe0, + 0x14, 0xe5, 0x13, 0x20, 0x06, 0x07, 0x06, 0x27, + 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, + 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, + 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, + 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, + 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, + 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, + 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, + 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, + 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, + 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, + 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, + 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, + 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, + 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, + 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x2e, 0xe5, 0x19, + 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x01, 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, @@ -2663,94 +2843,112 @@ 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, - 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xe0, - 0x80, 0x2f, 0x05, 0xe0, 0x07, 0xeb, 0x0d, 0xef, - 0x00, 0x6d, 0xef, 0x09, 0xe0, 0x05, 0x16, 0xe5, - 0x83, 0x12, 0xe0, 0x5e, 0xea, 0x67, 0x00, 0x96, - 0xe0, 0x03, 0xe5, 0x80, 0x3c, 0xe0, 0x8a, 0x34, - 0xe5, 0x83, 0xa7, 0x00, 0xfb, 0x01, 0xe0, 0x8f, - 0x3f, 0xe5, 0x81, 0xbf, 0xe0, 0xa1, 0x31, 0xe5, - 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, 0xe9, 0x02, - 0x60, 0x36, 0xe0, 0x58, 0xe5, 0x16, 0x20, 0x86, - 0x16, 0xe0, 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, - 0x64, 0x16, 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, - 0xcb, 0x00, 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, - 0x82, 0x28, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, + 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, + 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, + 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, + 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, 0x05, 0xe0, + 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, + 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, + 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, + 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, + 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, + 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, 0x13, 0x80, + 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, 0xe5, 0x16, + 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, 0xe0, 0x86, + 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, + 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, 0xe9, + 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, + 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, 0x16, + 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, + 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x81, 0x28, + 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, 0x02, 0xe0, + 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, 0x06, 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, - 0x4e, 0xe0, 0x22, 0xe5, 0x01, 0xe0, 0xa2, 0x6f, - 0xe5, 0x80, 0x97, 0xe0, 0x29, 0x45, 0xe0, 0x09, - 0x65, 0xe0, 0x00, 0xe5, 0x81, 0x04, 0xe0, 0x88, - 0x7c, 0xe5, 0x63, 0x80, 0xe5, 0x05, 0x40, 0xe5, - 0x01, 0xc0, 0xe5, 0x02, 0x20, 0x0f, 0x26, 0x16, - 0x7b, 0xe0, 0x92, 0xd4, 0xef, 0x80, 0x6e, 0xe0, - 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, 0x46, - 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, 0xc6, - 0xef, 0x16, 0x66, 0xef, 0x33, 0xe0, 0x0f, 0xef, - 0x3a, 0x46, 0x0f, 0xe0, 0x80, 0x12, 0xeb, 0x0c, - 0xe0, 0x04, 0xef, 0x4f, 0xe0, 0x01, 0xeb, 0x11, - 0xe0, 0x7f, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, - 0xc2, 0x00, 0xe2, 0x0a, 0xe1, 0x12, 0xe2, 0x12, - 0x01, 0x00, 0x21, 0x20, 0x01, 0x20, 0x21, 0x20, - 0x61, 0x00, 0xe1, 0x00, 0x62, 0x00, 0x02, 0x00, - 0xc2, 0x00, 0xe2, 0x03, 0xe1, 0x12, 0xe2, 0x12, - 0x21, 0x00, 0x61, 0x20, 0xe1, 0x00, 0x00, 0xc1, - 0x00, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x00, 0x81, - 0x00, 0x01, 0x40, 0xc1, 0x00, 0xe2, 0x12, 0xe1, + 0x4e, 0xe0, 0x21, 0xe5, 0x02, 0xe0, 0xa2, 0x5f, + 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, + 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, + 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, + 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, + 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, + 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, 0xef, + 0x80, 0x68, 0xe9, 0x02, 0xa0, 0xef, 0x81, 0x2c, + 0xe0, 0x44, 0xe6, 0x26, 0x20, 0xe6, 0x0f, 0xe0, + 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, 0x80, 0x6e, + 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, + 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, + 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, 0xe0, 0x0d, + 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, 0xeb, 0x0c, + 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, 0xef, 0x4f, + 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, + 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, 0xe2, 0x0a, + 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, 0x20, + 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, + 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, 0xe2, 0x03, + 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x20, + 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, + 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, 0x40, 0xc1, + 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x14, 0x20, - 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, - 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, - 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, - 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, - 0x11, 0x0c, 0xa2, 0x3f, 0x20, 0xe9, 0x2a, 0xef, - 0x81, 0x78, 0xe6, 0x2f, 0x6f, 0xe6, 0x2a, 0xef, - 0x00, 0x06, 0xef, 0x06, 0x06, 0x2f, 0x96, 0xe0, - 0x07, 0x86, 0x00, 0xe6, 0x07, 0xe0, 0x84, 0xc8, - 0xc6, 0x00, 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, - 0x00, 0x86, 0xe0, 0x80, 0x4d, 0xe5, 0x25, 0x40, - 0xc6, 0xc4, 0x20, 0xe9, 0x02, 0x60, 0x05, 0x0f, - 0xe0, 0x80, 0xe8, 0xe5, 0x24, 0x66, 0xe9, 0x02, - 0x80, 0x0d, 0xe0, 0x84, 0x78, 0xe5, 0x80, 0x3d, - 0x20, 0xeb, 0x01, 0xc6, 0xe0, 0x21, 0xe1, 0x1a, - 0xe2, 0x1a, 0xc6, 0x04, 0x60, 0xe9, 0x02, 0x60, - 0x36, 0xe0, 0x82, 0x89, 0xeb, 0x33, 0x0f, 0x4b, - 0x0d, 0x6b, 0xe0, 0x44, 0xeb, 0x25, 0x0f, 0xeb, - 0x07, 0xe0, 0x80, 0x3a, 0x65, 0x00, 0xe5, 0x13, - 0x00, 0x25, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, - 0x02, 0x00, 0x65, 0x00, 0x05, 0x00, 0x05, 0xa0, - 0x05, 0x60, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, - 0x45, 0x00, 0x25, 0x00, 0x05, 0x20, 0x05, 0x00, - 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, - 0x25, 0x00, 0x05, 0x20, 0x65, 0x00, 0xc5, 0x00, - 0x65, 0x00, 0x65, 0x00, 0x05, 0x00, 0xe5, 0x02, - 0x00, 0xe5, 0x09, 0x80, 0x45, 0x00, 0x85, 0x00, - 0xe5, 0x09, 0xe0, 0x2c, 0x2c, 0xe0, 0x80, 0x86, - 0xef, 0x24, 0x60, 0xef, 0x5c, 0xe0, 0x04, 0xef, - 0x07, 0x20, 0xef, 0x07, 0x00, 0xef, 0x07, 0x00, - 0xef, 0x1d, 0xe0, 0x02, 0xeb, 0x05, 0xef, 0x80, - 0x19, 0xe0, 0x30, 0xef, 0x15, 0xe0, 0x05, 0xef, - 0x24, 0x60, 0xef, 0x01, 0xc0, 0x2f, 0xe0, 0x06, - 0xaf, 0xe0, 0x80, 0x12, 0xef, 0x80, 0x73, 0x8e, - 0xef, 0x82, 0x50, 0xe0, 0x00, 0xef, 0x05, 0x40, - 0xef, 0x05, 0x40, 0xef, 0x6c, 0xe0, 0x04, 0xef, - 0x51, 0xc0, 0xef, 0x04, 0xe0, 0x0c, 0xef, 0x04, - 0x60, 0xef, 0x30, 0xe0, 0x00, 0xef, 0x02, 0xa0, - 0xef, 0x20, 0xe0, 0x00, 0xef, 0x16, 0x20, 0x2f, - 0xe0, 0x46, 0xef, 0x71, 0x00, 0xef, 0x4a, 0x00, - 0xef, 0x7f, 0xe0, 0x04, 0xef, 0x06, 0x20, 0x8f, - 0x40, 0x4f, 0x80, 0xcf, 0xe0, 0x01, 0xef, 0x11, - 0xc0, 0xcf, 0xe0, 0x01, 0x4f, 0xe0, 0x05, 0xcf, - 0xe0, 0x21, 0xef, 0x80, 0x0b, 0x00, 0xef, 0x2f, - 0xe0, 0x1d, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, - 0xc0, 0x66, 0x56, 0xe0, 0x1a, 0xe5, 0x8f, 0xad, - 0xe0, 0x03, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, - 0xfa, 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x8b, - 0x97, 0xe5, 0x81, 0x96, 0xe0, 0x85, 0x5a, 0xe5, - 0x92, 0xc3, 0xe0, 0xca, 0xac, 0x2e, 0x1b, 0xe0, + 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, 0xe2, + 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, + 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, + 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, + 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, + 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, 0xe6, 0x2f, + 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, 0x06, + 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, + 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, 0x05, 0xe2, + 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, 0xc6, 0x00, + 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, + 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, 0xe0, 0x68, + 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, + 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, 0xe5, 0x16, + 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, 0xe9, 0x02, + 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, 0x13, 0x04, + 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, 0xe5, 0x16, + 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, 0xe0, 0x81, + 0x58, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, + 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, 0xc6, + 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, + 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, + 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, 0x80, + 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, + 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, + 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, + 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, 0x05, + 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, + 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, 0xe0, + 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, + 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, + 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, 0xe0, + 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, 0x30, + 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, + 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, + 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x50, + 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, 0xef, + 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, 0x60, + 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, 0x30, + 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, + 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, 0x60, 0x2f, + 0xe0, 0x36, 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, + 0x06, 0x20, 0xef, 0x05, 0x40, 0xef, 0x02, 0x80, + 0xef, 0x30, 0xc0, 0xef, 0x07, 0x20, 0xef, 0x03, + 0xa0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, 0x00, + 0xef, 0x54, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, + 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x8f, 0xb2, + 0xa0, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, + 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x07, 0xe5, + 0x81, 0xe6, 0xe0, 0x89, 0x1a, 0xe5, 0x81, 0x96, + 0xe0, 0x85, 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, + 0x8f, 0xd8, 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, @@ -2787,6 +2985,7 @@ UNICODE_SCRIPT_Cuneiform, UNICODE_SCRIPT_Cypriot, UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Cypro_Minoan, UNICODE_SCRIPT_Deseret, UNICODE_SCRIPT_Devanagari, UNICODE_SCRIPT_Dives_Akuru, @@ -2796,6 +2995,7 @@ UNICODE_SCRIPT_Elbasan, UNICODE_SCRIPT_Elymaic, UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Garay, UNICODE_SCRIPT_Georgian, UNICODE_SCRIPT_Glagolitic, UNICODE_SCRIPT_Gothic, @@ -2804,6 +3004,7 @@ UNICODE_SCRIPT_Gujarati, UNICODE_SCRIPT_Gunjala_Gondi, UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Gurung_Khema, UNICODE_SCRIPT_Han, UNICODE_SCRIPT_Hangul, UNICODE_SCRIPT_Hanifi_Rohingya, @@ -2819,12 +3020,14 @@ UNICODE_SCRIPT_Kaithi, UNICODE_SCRIPT_Kannada, UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Kawi, UNICODE_SCRIPT_Kayah_Li, UNICODE_SCRIPT_Kharoshthi, UNICODE_SCRIPT_Khmer, UNICODE_SCRIPT_Khojki, UNICODE_SCRIPT_Khitan_Small_Script, UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Kirat_Rai, UNICODE_SCRIPT_Lao, UNICODE_SCRIPT_Latin, UNICODE_SCRIPT_Lepcha, @@ -2853,6 +3056,7 @@ UNICODE_SCRIPT_Multani, UNICODE_SCRIPT_Myanmar, UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nag_Mundari, UNICODE_SCRIPT_Nandinagari, UNICODE_SCRIPT_New_Tai_Lue, UNICODE_SCRIPT_Newa, @@ -2861,6 +3065,7 @@ UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, UNICODE_SCRIPT_Ogham, UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Ol_Onal, UNICODE_SCRIPT_Old_Hungarian, UNICODE_SCRIPT_Old_Italic, UNICODE_SCRIPT_Old_North_Arabian, @@ -2869,6 +3074,7 @@ UNICODE_SCRIPT_Old_Sogdian, UNICODE_SCRIPT_Old_South_Arabian, UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Old_Uyghur, UNICODE_SCRIPT_Oriya, UNICODE_SCRIPT_Osage, UNICODE_SCRIPT_Osmanya, @@ -2891,6 +3097,7 @@ UNICODE_SCRIPT_Sora_Sompeng, UNICODE_SCRIPT_Soyombo, UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Sunuwar, UNICODE_SCRIPT_Syloti_Nagri, UNICODE_SCRIPT_Syriac, UNICODE_SCRIPT_Tagalog, @@ -2907,8 +3114,13 @@ UNICODE_SCRIPT_Tibetan, UNICODE_SCRIPT_Tifinagh, UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Tangsa, + UNICODE_SCRIPT_Todhri, + UNICODE_SCRIPT_Toto, + UNICODE_SCRIPT_Tulu_Tigalari, UNICODE_SCRIPT_Ugaritic, UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Vithkuqi, UNICODE_SCRIPT_Wancho, UNICODE_SCRIPT_Warang_Citi, UNICODE_SCRIPT_Yezidi, @@ -2918,6 +3130,7 @@ } UnicodeScriptEnum; static const char unicode_script_name_table = + "Unknown,Zzzz" "\0" "Adlam,Adlm" "\0" "Ahom,Ahom" "\0" "Anatolian_Hieroglyphs,Hluw" "\0" @@ -2947,6 +3160,7 @@ "Cuneiform,Xsux" "\0" "Cypriot,Cprt" "\0" "Cyrillic,Cyrl" "\0" + "Cypro_Minoan,Cpmn" "\0" "Deseret,Dsrt" "\0" "Devanagari,Deva" "\0" "Dives_Akuru,Diak" "\0" @@ -2956,6 +3170,7 @@ "Elbasan,Elba" "\0" "Elymaic,Elym" "\0" "Ethiopic,Ethi" "\0" + "Garay,Gara" "\0" "Georgian,Geor" "\0" "Glagolitic,Glag" "\0" "Gothic,Goth" "\0" @@ -2964,6 +3179,7 @@ "Gujarati,Gujr" "\0" "Gunjala_Gondi,Gong" "\0" "Gurmukhi,Guru" "\0" + "Gurung_Khema,Gukh" "\0" "Han,Hani" "\0" "Hangul,Hang" "\0" "Hanifi_Rohingya,Rohg" "\0" @@ -2979,12 +3195,14 @@ "Kaithi,Kthi" "\0" "Kannada,Knda" "\0" "Katakana,Kana" "\0" + "Kawi,Kawi" "\0" "Kayah_Li,Kali" "\0" "Kharoshthi,Khar" "\0" "Khmer,Khmr" "\0" "Khojki,Khoj" "\0" "Khitan_Small_Script,Kits" "\0" "Khudawadi,Sind" "\0" + "Kirat_Rai,Krai" "\0" "Lao,Laoo" "\0" "Latin,Latn" "\0" "Lepcha,Lepc" "\0" @@ -3013,6 +3231,7 @@ "Multani,Mult" "\0" "Myanmar,Mymr" "\0" "Nabataean,Nbat" "\0" + "Nag_Mundari,Nagm" "\0" "Nandinagari,Nand" "\0" "New_Tai_Lue,Talu" "\0" "Newa,Newa" "\0" @@ -3021,6 +3240,7 @@ "Nyiakeng_Puachue_Hmong,Hmnp" "\0" "Ogham,Ogam" "\0" "Ol_Chiki,Olck" "\0" + "Ol_Onal,Onao" "\0" "Old_Hungarian,Hung" "\0" "Old_Italic,Ital" "\0" "Old_North_Arabian,Narb" "\0" @@ -3029,6 +3249,7 @@ "Old_Sogdian,Sogo" "\0" "Old_South_Arabian,Sarb" "\0" "Old_Turkic,Orkh" "\0" + "Old_Uyghur,Ougr" "\0" "Oriya,Orya" "\0" "Osage,Osge" "\0" "Osmanya,Osma" "\0" @@ -3051,6 +3272,7 @@ "Sora_Sompeng,Sora" "\0" "Soyombo,Soyo" "\0" "Sundanese,Sund" "\0" + "Sunuwar,Sunu" "\0" "Syloti_Nagri,Sylo" "\0" "Syriac,Syrc" "\0" "Tagalog,Tglg" "\0" @@ -3067,8 +3289,13 @@ "Tibetan,Tibt" "\0" "Tifinagh,Tfng" "\0" "Tirhuta,Tirh" "\0" + "Tangsa,Tnsa" "\0" + "Todhri,Todr" "\0" + "Toto,Toto" "\0" + "Tulu_Tigalari,Tutg" "\0" "Ugaritic,Ugar" "\0" "Vai,Vaii" "\0" + "Vithkuqi,Vith" "\0" "Wancho,Wcho" "\0" "Warang_Citi,Wara" "\0" "Yezidi,Yezi" "\0" @@ -3076,297 +3303,321 @@ "Zanabazar_Square,Zanb" "\0" ; -static const uint8_t unicode_script_table2609 = { - 0xc0, 0x19, 0x99, 0x45, 0x85, 0x19, 0x99, 0x45, - 0xae, 0x19, 0x80, 0x45, 0x8e, 0x19, 0x80, 0x45, - 0x84, 0x19, 0x96, 0x45, 0x80, 0x19, 0x9e, 0x45, - 0x80, 0x19, 0xe1, 0x60, 0x45, 0xa6, 0x19, 0x84, - 0x45, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, - 0x0f, 0x37, 0x83, 0x2b, 0x80, 0x19, 0x82, 0x2b, - 0x01, 0x83, 0x2b, 0x80, 0x19, 0x80, 0x2b, 0x03, - 0x80, 0x2b, 0x80, 0x19, 0x80, 0x2b, 0x80, 0x19, - 0x82, 0x2b, 0x00, 0x80, 0x2b, 0x00, 0x93, 0x2b, - 0x00, 0xbe, 0x2b, 0x8d, 0x1a, 0x8f, 0x2b, 0xe0, - 0x24, 0x1d, 0x81, 0x37, 0xe0, 0x48, 0x1d, 0x00, +static const uint8_t unicode_script_table2803 = { + 0xc0, 0x19, 0x99, 0x4a, 0x85, 0x19, 0x99, 0x4a, + 0xae, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x80, 0x4a, + 0x84, 0x19, 0x96, 0x4a, 0x80, 0x19, 0x9e, 0x4a, + 0x80, 0x19, 0xe1, 0x60, 0x4a, 0xa6, 0x19, 0x84, + 0x4a, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, + 0x0f, 0x3a, 0x83, 0x2d, 0x80, 0x19, 0x82, 0x2d, + 0x01, 0x83, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x03, + 0x80, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x80, 0x19, + 0x82, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x93, 0x2d, + 0x00, 0xbe, 0x2d, 0x8d, 0x1a, 0x8f, 0x2d, 0xe0, + 0x24, 0x1d, 0x81, 0x3a, 0xe0, 0x48, 0x1d, 0x00, 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, - 0x00, 0xb6, 0x34, 0x07, 0x9a, 0x34, 0x03, 0x85, - 0x34, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, - 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x80, 0x04, - 0x00, 0x80, 0x04, 0x80, 0x19, 0x9f, 0x04, 0x80, - 0x19, 0x89, 0x04, 0x8a, 0x37, 0x99, 0x04, 0x80, - 0x37, 0xe0, 0x0b, 0x04, 0x80, 0x19, 0xa1, 0x04, - 0x8d, 0x87, 0x00, 0xbb, 0x87, 0x01, 0x82, 0x87, - 0xaf, 0x04, 0xb1, 0x91, 0x0d, 0xba, 0x63, 0x01, - 0x82, 0x63, 0xad, 0x7b, 0x01, 0x8e, 0x7b, 0x00, - 0x9b, 0x50, 0x01, 0x80, 0x50, 0x00, 0x8a, 0x87, - 0x34, 0x94, 0x04, 0x00, 0x91, 0x04, 0x0a, 0x8e, - 0x04, 0x80, 0x19, 0x9c, 0x04, 0xd0, 0x1f, 0x83, - 0x37, 0x8e, 0x1f, 0x81, 0x19, 0x99, 0x1f, 0x83, - 0x0b, 0x00, 0x87, 0x0b, 0x01, 0x81, 0x0b, 0x01, - 0x95, 0x0b, 0x00, 0x86, 0x0b, 0x00, 0x80, 0x0b, - 0x02, 0x83, 0x0b, 0x01, 0x88, 0x0b, 0x01, 0x81, - 0x0b, 0x01, 0x83, 0x0b, 0x07, 0x80, 0x0b, 0x03, - 0x81, 0x0b, 0x00, 0x84, 0x0b, 0x01, 0x98, 0x0b, - 0x01, 0x82, 0x2e, 0x00, 0x85, 0x2e, 0x03, 0x81, - 0x2e, 0x01, 0x95, 0x2e, 0x00, 0x86, 0x2e, 0x00, - 0x81, 0x2e, 0x00, 0x81, 0x2e, 0x00, 0x81, 0x2e, - 0x01, 0x80, 0x2e, 0x00, 0x84, 0x2e, 0x03, 0x81, - 0x2e, 0x01, 0x82, 0x2e, 0x02, 0x80, 0x2e, 0x06, - 0x83, 0x2e, 0x00, 0x80, 0x2e, 0x06, 0x90, 0x2e, - 0x09, 0x82, 0x2c, 0x00, 0x88, 0x2c, 0x00, 0x82, - 0x2c, 0x00, 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, - 0x81, 0x2c, 0x00, 0x84, 0x2c, 0x01, 0x89, 0x2c, - 0x00, 0x82, 0x2c, 0x00, 0x82, 0x2c, 0x01, 0x80, - 0x2c, 0x0e, 0x83, 0x2c, 0x01, 0x8b, 0x2c, 0x06, - 0x86, 0x2c, 0x00, 0x82, 0x70, 0x00, 0x87, 0x70, - 0x01, 0x81, 0x70, 0x01, 0x95, 0x70, 0x00, 0x86, - 0x70, 0x00, 0x81, 0x70, 0x00, 0x84, 0x70, 0x01, - 0x88, 0x70, 0x01, 0x81, 0x70, 0x01, 0x82, 0x70, - 0x06, 0x82, 0x70, 0x03, 0x81, 0x70, 0x00, 0x84, - 0x70, 0x01, 0x91, 0x70, 0x09, 0x81, 0x8e, 0x00, - 0x85, 0x8e, 0x02, 0x82, 0x8e, 0x00, 0x83, 0x8e, - 0x02, 0x81, 0x8e, 0x00, 0x80, 0x8e, 0x00, 0x81, - 0x8e, 0x02, 0x81, 0x8e, 0x02, 0x82, 0x8e, 0x02, - 0x8b, 0x8e, 0x03, 0x84, 0x8e, 0x02, 0x82, 0x8e, - 0x00, 0x83, 0x8e, 0x01, 0x80, 0x8e, 0x05, 0x80, - 0x8e, 0x0d, 0x94, 0x8e, 0x04, 0x8c, 0x90, 0x00, - 0x82, 0x90, 0x00, 0x96, 0x90, 0x00, 0x8f, 0x90, - 0x02, 0x87, 0x90, 0x00, 0x82, 0x90, 0x00, 0x83, - 0x90, 0x06, 0x81, 0x90, 0x00, 0x82, 0x90, 0x04, - 0x83, 0x90, 0x01, 0x89, 0x90, 0x06, 0x88, 0x90, - 0x8c, 0x3c, 0x00, 0x82, 0x3c, 0x00, 0x96, 0x3c, - 0x00, 0x89, 0x3c, 0x00, 0x84, 0x3c, 0x01, 0x88, - 0x3c, 0x00, 0x82, 0x3c, 0x00, 0x83, 0x3c, 0x06, - 0x81, 0x3c, 0x06, 0x80, 0x3c, 0x00, 0x83, 0x3c, - 0x01, 0x89, 0x3c, 0x00, 0x81, 0x3c, 0x0c, 0x8c, - 0x4f, 0x00, 0x82, 0x4f, 0x00, 0xb2, 0x4f, 0x00, - 0x82, 0x4f, 0x00, 0x85, 0x4f, 0x03, 0x8f, 0x4f, - 0x01, 0x99, 0x4f, 0x00, 0x82, 0x81, 0x00, 0x91, - 0x81, 0x02, 0x97, 0x81, 0x00, 0x88, 0x81, 0x00, - 0x80, 0x81, 0x01, 0x86, 0x81, 0x02, 0x80, 0x81, - 0x03, 0x85, 0x81, 0x00, 0x80, 0x81, 0x00, 0x87, - 0x81, 0x05, 0x89, 0x81, 0x01, 0x82, 0x81, 0x0b, - 0xb9, 0x92, 0x03, 0x80, 0x19, 0x9b, 0x92, 0x24, - 0x81, 0x44, 0x00, 0x80, 0x44, 0x00, 0x84, 0x44, - 0x00, 0x97, 0x44, 0x00, 0x80, 0x44, 0x00, 0x96, - 0x44, 0x01, 0x84, 0x44, 0x00, 0x80, 0x44, 0x00, - 0x85, 0x44, 0x01, 0x89, 0x44, 0x01, 0x83, 0x44, - 0x1f, 0xc7, 0x93, 0x00, 0xa3, 0x93, 0x03, 0xa6, - 0x93, 0x00, 0xa3, 0x93, 0x00, 0x8e, 0x93, 0x00, - 0x86, 0x93, 0x83, 0x19, 0x81, 0x93, 0x24, 0xe0, - 0x3f, 0x5e, 0xa5, 0x27, 0x00, 0x80, 0x27, 0x04, - 0x80, 0x27, 0x01, 0xaa, 0x27, 0x80, 0x19, 0x83, - 0x27, 0xe0, 0x9f, 0x30, 0xc8, 0x26, 0x00, 0x83, - 0x26, 0x01, 0x86, 0x26, 0x00, 0x80, 0x26, 0x00, - 0x83, 0x26, 0x01, 0xa8, 0x26, 0x00, 0x83, 0x26, - 0x01, 0xa0, 0x26, 0x00, 0x83, 0x26, 0x01, 0x86, - 0x26, 0x00, 0x80, 0x26, 0x00, 0x83, 0x26, 0x01, - 0x8e, 0x26, 0x00, 0xb8, 0x26, 0x00, 0x83, 0x26, - 0x01, 0xc2, 0x26, 0x01, 0x9f, 0x26, 0x02, 0x99, - 0x26, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, - 0xe2, 0x1f, 0x12, 0x9c, 0x66, 0x02, 0xca, 0x7a, - 0x82, 0x19, 0x8a, 0x7a, 0x06, 0x8c, 0x88, 0x00, - 0x86, 0x88, 0x0a, 0x94, 0x32, 0x81, 0x19, 0x08, - 0x93, 0x11, 0x0b, 0x8c, 0x89, 0x00, 0x82, 0x89, - 0x00, 0x81, 0x89, 0x0b, 0xdd, 0x40, 0x01, 0x89, - 0x40, 0x05, 0x89, 0x40, 0x05, 0x81, 0x5b, 0x81, - 0x19, 0x80, 0x5b, 0x80, 0x19, 0x88, 0x5b, 0x00, - 0x89, 0x5b, 0x05, 0xd8, 0x5b, 0x06, 0xaa, 0x5b, - 0x04, 0xc5, 0x12, 0x09, 0x9e, 0x47, 0x00, 0x8b, - 0x47, 0x03, 0x8b, 0x47, 0x03, 0x80, 0x47, 0x02, - 0x8b, 0x47, 0x9d, 0x8a, 0x01, 0x84, 0x8a, 0x0a, - 0xab, 0x61, 0x03, 0x99, 0x61, 0x05, 0x8a, 0x61, - 0x02, 0x81, 0x61, 0x9f, 0x40, 0x9b, 0x10, 0x01, - 0x81, 0x10, 0xbe, 0x8b, 0x00, 0x9c, 0x8b, 0x01, - 0x8a, 0x8b, 0x05, 0x89, 0x8b, 0x05, 0x8d, 0x8b, - 0x01, 0x90, 0x37, 0x3e, 0xcb, 0x07, 0x03, 0xac, - 0x07, 0x02, 0xbf, 0x85, 0xb3, 0x0a, 0x07, 0x83, - 0x0a, 0xb7, 0x46, 0x02, 0x8e, 0x46, 0x02, 0x82, - 0x46, 0xaf, 0x67, 0x88, 0x1d, 0x06, 0xaa, 0x27, - 0x01, 0x82, 0x27, 0x87, 0x85, 0x07, 0x82, 0x37, - 0x80, 0x19, 0x8c, 0x37, 0x80, 0x19, 0x86, 0x37, - 0x83, 0x19, 0x80, 0x37, 0x85, 0x19, 0x80, 0x37, - 0x82, 0x19, 0x81, 0x37, 0x80, 0x19, 0x04, 0xa5, - 0x45, 0x84, 0x2b, 0x80, 0x1d, 0xb0, 0x45, 0x84, - 0x2b, 0x83, 0x45, 0x84, 0x2b, 0x8c, 0x45, 0x80, - 0x1d, 0xc5, 0x45, 0x80, 0x2b, 0xb9, 0x37, 0x00, - 0x84, 0x37, 0xe0, 0x9f, 0x45, 0x95, 0x2b, 0x01, - 0x85, 0x2b, 0x01, 0xa5, 0x2b, 0x01, 0x85, 0x2b, - 0x01, 0x87, 0x2b, 0x00, 0x80, 0x2b, 0x00, 0x80, - 0x2b, 0x00, 0x80, 0x2b, 0x00, 0x9e, 0x2b, 0x01, - 0xb4, 0x2b, 0x00, 0x8e, 0x2b, 0x00, 0x8d, 0x2b, - 0x01, 0x85, 0x2b, 0x00, 0x92, 0x2b, 0x01, 0x82, - 0x2b, 0x00, 0x88, 0x2b, 0x00, 0x8b, 0x19, 0x81, - 0x37, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x45, - 0x01, 0x8a, 0x19, 0x80, 0x45, 0x8e, 0x19, 0x00, - 0x8c, 0x45, 0x02, 0x9f, 0x19, 0x0f, 0xa0, 0x37, - 0x0e, 0xa5, 0x19, 0x80, 0x2b, 0x82, 0x19, 0x81, - 0x45, 0x85, 0x19, 0x80, 0x45, 0x9a, 0x19, 0x80, - 0x45, 0x90, 0x19, 0xa8, 0x45, 0x82, 0x19, 0x03, - 0xe2, 0x36, 0x19, 0x18, 0x8a, 0x19, 0x14, 0xe3, + 0x00, 0xb6, 0x37, 0x07, 0x9a, 0x37, 0x03, 0x85, + 0x37, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, + 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, + 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, + 0x8a, 0x3a, 0x99, 0x04, 0x80, 0x3a, 0xe0, 0x0b, + 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x90, 0x00, + 0xbb, 0x90, 0x01, 0x82, 0x90, 0xaf, 0x04, 0xb1, + 0x9a, 0x0d, 0xba, 0x69, 0x01, 0x82, 0x69, 0xad, + 0x83, 0x01, 0x8e, 0x83, 0x00, 0x9b, 0x55, 0x01, + 0x80, 0x55, 0x00, 0x8a, 0x90, 0x04, 0x9e, 0x04, + 0x00, 0x81, 0x04, 0x04, 0xca, 0x04, 0x80, 0x19, + 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x3a, 0x8e, 0x20, + 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, + 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, + 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, + 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, + 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, + 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x30, + 0x00, 0x85, 0x30, 0x03, 0x81, 0x30, 0x01, 0x95, + 0x30, 0x00, 0x86, 0x30, 0x00, 0x81, 0x30, 0x00, + 0x81, 0x30, 0x00, 0x81, 0x30, 0x01, 0x80, 0x30, + 0x00, 0x84, 0x30, 0x03, 0x81, 0x30, 0x01, 0x82, + 0x30, 0x02, 0x80, 0x30, 0x06, 0x83, 0x30, 0x00, + 0x80, 0x30, 0x06, 0x90, 0x30, 0x09, 0x82, 0x2e, + 0x00, 0x88, 0x2e, 0x00, 0x82, 0x2e, 0x00, 0x95, + 0x2e, 0x00, 0x86, 0x2e, 0x00, 0x81, 0x2e, 0x00, + 0x84, 0x2e, 0x01, 0x89, 0x2e, 0x00, 0x82, 0x2e, + 0x00, 0x82, 0x2e, 0x01, 0x80, 0x2e, 0x0e, 0x83, + 0x2e, 0x01, 0x8b, 0x2e, 0x06, 0x86, 0x2e, 0x00, + 0x82, 0x78, 0x00, 0x87, 0x78, 0x01, 0x81, 0x78, + 0x01, 0x95, 0x78, 0x00, 0x86, 0x78, 0x00, 0x81, + 0x78, 0x00, 0x84, 0x78, 0x01, 0x88, 0x78, 0x01, + 0x81, 0x78, 0x01, 0x82, 0x78, 0x06, 0x82, 0x78, + 0x03, 0x81, 0x78, 0x00, 0x84, 0x78, 0x01, 0x91, + 0x78, 0x09, 0x81, 0x97, 0x00, 0x85, 0x97, 0x02, + 0x82, 0x97, 0x00, 0x83, 0x97, 0x02, 0x81, 0x97, + 0x00, 0x80, 0x97, 0x00, 0x81, 0x97, 0x02, 0x81, + 0x97, 0x02, 0x82, 0x97, 0x02, 0x8b, 0x97, 0x03, + 0x84, 0x97, 0x02, 0x82, 0x97, 0x00, 0x83, 0x97, + 0x01, 0x80, 0x97, 0x05, 0x80, 0x97, 0x0d, 0x94, + 0x97, 0x04, 0x8c, 0x99, 0x00, 0x82, 0x99, 0x00, + 0x96, 0x99, 0x00, 0x8f, 0x99, 0x01, 0x88, 0x99, + 0x00, 0x82, 0x99, 0x00, 0x83, 0x99, 0x06, 0x81, + 0x99, 0x00, 0x82, 0x99, 0x01, 0x80, 0x99, 0x01, + 0x83, 0x99, 0x01, 0x89, 0x99, 0x06, 0x88, 0x99, + 0x8c, 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x96, 0x3f, + 0x00, 0x89, 0x3f, 0x00, 0x84, 0x3f, 0x01, 0x88, + 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x83, 0x3f, 0x06, + 0x81, 0x3f, 0x05, 0x81, 0x3f, 0x00, 0x83, 0x3f, + 0x01, 0x89, 0x3f, 0x00, 0x82, 0x3f, 0x0b, 0x8c, + 0x54, 0x00, 0x82, 0x54, 0x00, 0xb2, 0x54, 0x00, + 0x82, 0x54, 0x00, 0x85, 0x54, 0x03, 0x8f, 0x54, + 0x01, 0x99, 0x54, 0x00, 0x82, 0x89, 0x00, 0x91, + 0x89, 0x02, 0x97, 0x89, 0x00, 0x88, 0x89, 0x00, + 0x80, 0x89, 0x01, 0x86, 0x89, 0x02, 0x80, 0x89, + 0x03, 0x85, 0x89, 0x00, 0x80, 0x89, 0x00, 0x87, + 0x89, 0x05, 0x89, 0x89, 0x01, 0x82, 0x89, 0x0b, + 0xb9, 0x9b, 0x03, 0x80, 0x19, 0x9b, 0x9b, 0x24, + 0x81, 0x49, 0x00, 0x80, 0x49, 0x00, 0x84, 0x49, + 0x00, 0x97, 0x49, 0x00, 0x80, 0x49, 0x00, 0x96, + 0x49, 0x01, 0x84, 0x49, 0x00, 0x80, 0x49, 0x00, + 0x86, 0x49, 0x00, 0x89, 0x49, 0x01, 0x83, 0x49, + 0x1f, 0xc7, 0x9c, 0x00, 0xa3, 0x9c, 0x03, 0xa6, + 0x9c, 0x00, 0xa3, 0x9c, 0x00, 0x8e, 0x9c, 0x00, + 0x86, 0x9c, 0x83, 0x19, 0x81, 0x9c, 0x24, 0xe0, + 0x3f, 0x63, 0xa5, 0x29, 0x00, 0x80, 0x29, 0x04, + 0x80, 0x29, 0x01, 0xaa, 0x29, 0x80, 0x19, 0x83, + 0x29, 0xe0, 0x9f, 0x33, 0xc8, 0x27, 0x00, 0x83, + 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, + 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, + 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, + 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, + 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, + 0xe2, 0x1f, 0x12, 0x9c, 0x6c, 0x02, 0xca, 0x82, + 0x82, 0x19, 0x8a, 0x82, 0x06, 0x95, 0x91, 0x08, + 0x80, 0x91, 0x94, 0x35, 0x81, 0x19, 0x08, 0x93, + 0x11, 0x0b, 0x8c, 0x92, 0x00, 0x82, 0x92, 0x00, + 0x81, 0x92, 0x0b, 0xdd, 0x44, 0x01, 0x89, 0x44, + 0x05, 0x89, 0x44, 0x05, 0x81, 0x60, 0x81, 0x19, + 0x80, 0x60, 0x80, 0x19, 0x93, 0x60, 0x05, 0xd8, + 0x60, 0x06, 0xaa, 0x60, 0x04, 0xc5, 0x12, 0x09, + 0x9e, 0x4c, 0x00, 0x8b, 0x4c, 0x03, 0x8b, 0x4c, + 0x03, 0x80, 0x4c, 0x02, 0x8b, 0x4c, 0x9d, 0x93, + 0x01, 0x84, 0x93, 0x0a, 0xab, 0x67, 0x03, 0x99, + 0x67, 0x05, 0x8a, 0x67, 0x02, 0x81, 0x67, 0x9f, + 0x44, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x94, + 0x00, 0x9c, 0x94, 0x01, 0x8a, 0x94, 0x05, 0x89, + 0x94, 0x05, 0x8d, 0x94, 0x01, 0x9e, 0x3a, 0x30, + 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x8d, 0xb3, + 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4b, 0x02, 0x8e, + 0x4b, 0x02, 0x82, 0x4b, 0xaf, 0x6d, 0x8a, 0x1d, + 0x04, 0xaa, 0x29, 0x01, 0x82, 0x29, 0x87, 0x8d, + 0x07, 0x82, 0x3a, 0x80, 0x19, 0x8c, 0x3a, 0x80, + 0x19, 0x86, 0x3a, 0x83, 0x19, 0x80, 0x3a, 0x85, + 0x19, 0x80, 0x3a, 0x82, 0x19, 0x81, 0x3a, 0x80, + 0x19, 0x04, 0xa5, 0x4a, 0x84, 0x2d, 0x80, 0x1d, + 0xb0, 0x4a, 0x84, 0x2d, 0x83, 0x4a, 0x84, 0x2d, + 0x8c, 0x4a, 0x80, 0x1d, 0xc5, 0x4a, 0x80, 0x2d, + 0xbf, 0x3a, 0xe0, 0x9f, 0x4a, 0x95, 0x2d, 0x01, + 0x85, 0x2d, 0x01, 0xa5, 0x2d, 0x01, 0x85, 0x2d, + 0x01, 0x87, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x80, + 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x9e, 0x2d, 0x01, + 0xb4, 0x2d, 0x00, 0x8e, 0x2d, 0x00, 0x8d, 0x2d, + 0x01, 0x85, 0x2d, 0x00, 0x92, 0x2d, 0x01, 0x82, + 0x2d, 0x00, 0x88, 0x2d, 0x00, 0x8b, 0x19, 0x81, + 0x3a, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x4a, + 0x01, 0x8a, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x00, + 0x8c, 0x4a, 0x02, 0xa0, 0x19, 0x0e, 0xa0, 0x3a, + 0x0e, 0xa5, 0x19, 0x80, 0x2d, 0x82, 0x19, 0x81, + 0x4a, 0x85, 0x19, 0x80, 0x4a, 0x9a, 0x19, 0x80, + 0x4a, 0x90, 0x19, 0xa8, 0x4a, 0x82, 0x19, 0x03, + 0xe2, 0x39, 0x19, 0x15, 0x8a, 0x19, 0x14, 0xe3, 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x19, - 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xae, - 0x28, 0x00, 0xae, 0x28, 0x00, 0x9f, 0x45, 0xe0, - 0x13, 0x1a, 0x04, 0x86, 0x1a, 0xa5, 0x27, 0x00, - 0x80, 0x27, 0x04, 0x80, 0x27, 0x01, 0xb7, 0x94, - 0x06, 0x81, 0x94, 0x0d, 0x80, 0x94, 0x96, 0x26, - 0x08, 0x86, 0x26, 0x00, 0x86, 0x26, 0x00, 0x86, - 0x26, 0x00, 0x86, 0x26, 0x00, 0x86, 0x26, 0x00, - 0x86, 0x26, 0x00, 0x86, 0x26, 0x00, 0x86, 0x26, - 0x00, 0x9f, 0x1d, 0xd2, 0x19, 0x2c, 0x99, 0x2f, - 0x00, 0xd8, 0x2f, 0x0b, 0xe0, 0x75, 0x2f, 0x19, - 0x8b, 0x19, 0x03, 0x84, 0x19, 0x80, 0x2f, 0x80, - 0x19, 0x80, 0x2f, 0x98, 0x19, 0x88, 0x2f, 0x83, - 0x37, 0x81, 0x30, 0x87, 0x19, 0x83, 0x2f, 0x83, - 0x19, 0x00, 0xd5, 0x35, 0x01, 0x81, 0x37, 0x81, - 0x19, 0x82, 0x35, 0x80, 0x19, 0xd9, 0x3d, 0x81, - 0x19, 0x82, 0x3d, 0x04, 0xaa, 0x0d, 0x00, 0xdd, - 0x30, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa3, 0x19, - 0x0b, 0x8f, 0x3d, 0x9e, 0x30, 0x00, 0xbf, 0x19, - 0x9e, 0x30, 0xd0, 0x19, 0xae, 0x3d, 0x80, 0x19, - 0xd7, 0x3d, 0xe0, 0x47, 0x19, 0xf0, 0x09, 0x5f, - 0x2f, 0xbf, 0x19, 0xf0, 0x41, 0x9c, 0x2f, 0x02, - 0xe4, 0x2c, 0x9b, 0x02, 0xb6, 0x9b, 0x08, 0xaf, - 0x4a, 0xe0, 0xcb, 0x97, 0x13, 0xdf, 0x1d, 0xd7, - 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, 0x45, 0x82, - 0x19, 0xb4, 0x45, 0x01, 0x88, 0x45, 0x29, 0x8a, - 0x45, 0xac, 0x86, 0x02, 0x89, 0x19, 0x05, 0xb7, - 0x76, 0x07, 0xc5, 0x7c, 0x07, 0x8b, 0x7c, 0x05, - 0x9f, 0x1f, 0xad, 0x3e, 0x80, 0x19, 0x80, 0x3e, - 0xa3, 0x79, 0x0a, 0x80, 0x79, 0x9c, 0x30, 0x02, - 0xcd, 0x3a, 0x00, 0x80, 0x19, 0x89, 0x3a, 0x03, - 0x81, 0x3a, 0x9e, 0x5e, 0x00, 0xb6, 0x16, 0x08, + 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xdf, + 0x2a, 0x9f, 0x4a, 0xe0, 0x13, 0x1a, 0x04, 0x86, + 0x1a, 0xa5, 0x29, 0x00, 0x80, 0x29, 0x04, 0x80, + 0x29, 0x01, 0xb7, 0x9d, 0x06, 0x81, 0x9d, 0x0d, + 0x80, 0x9d, 0x96, 0x27, 0x08, 0x86, 0x27, 0x00, + 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, + 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, 0xdd, + 0x19, 0x21, 0x99, 0x32, 0x00, 0xd8, 0x32, 0x0b, + 0xe0, 0x75, 0x32, 0x19, 0x94, 0x19, 0x80, 0x32, + 0x80, 0x19, 0x80, 0x32, 0x98, 0x19, 0x88, 0x32, + 0x83, 0x3a, 0x81, 0x33, 0x87, 0x19, 0x83, 0x32, + 0x83, 0x19, 0x00, 0xd5, 0x38, 0x01, 0x81, 0x3a, + 0x81, 0x19, 0x82, 0x38, 0x80, 0x19, 0xd9, 0x40, + 0x81, 0x19, 0x82, 0x40, 0x04, 0xaa, 0x0d, 0x00, + 0xdd, 0x33, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa5, + 0x19, 0x08, 0x80, 0x19, 0x8f, 0x40, 0x9e, 0x33, + 0x00, 0xbf, 0x19, 0x9e, 0x33, 0xd0, 0x19, 0xae, + 0x40, 0x80, 0x19, 0xd7, 0x40, 0xe0, 0x47, 0x19, + 0xf0, 0x09, 0x5f, 0x32, 0xbf, 0x19, 0xf0, 0x41, + 0x9f, 0x32, 0xe4, 0x2c, 0xa9, 0x02, 0xb6, 0xa9, + 0x08, 0xaf, 0x4f, 0xe0, 0xcb, 0xa4, 0x13, 0xdf, + 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, + 0x4a, 0x82, 0x19, 0xc2, 0x4a, 0x01, 0x81, 0x4a, + 0x00, 0x80, 0x4a, 0x00, 0x87, 0x4a, 0x14, 0x8d, + 0x4a, 0xac, 0x8f, 0x02, 0x89, 0x19, 0x05, 0xb7, + 0x7e, 0x07, 0xc5, 0x84, 0x07, 0x8b, 0x84, 0x05, + 0x9f, 0x20, 0xad, 0x42, 0x80, 0x19, 0x80, 0x42, + 0xa3, 0x81, 0x0a, 0x80, 0x81, 0x9c, 0x33, 0x02, + 0xcd, 0x3d, 0x00, 0x80, 0x19, 0x89, 0x3d, 0x03, + 0x81, 0x3d, 0x9e, 0x63, 0x00, 0xb6, 0x16, 0x08, 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83, 0x16, - 0x9f, 0x5e, 0xc2, 0x8c, 0x17, 0x84, 0x8c, 0x96, - 0x55, 0x09, 0x85, 0x26, 0x01, 0x85, 0x26, 0x01, - 0x85, 0x26, 0x08, 0x86, 0x26, 0x00, 0x86, 0x26, - 0x00, 0xaa, 0x45, 0x80, 0x19, 0x88, 0x45, 0x80, - 0x2b, 0x83, 0x45, 0x81, 0x19, 0x03, 0xcf, 0x17, - 0xad, 0x55, 0x01, 0x89, 0x55, 0x05, 0xf0, 0x1b, - 0x43, 0x30, 0x0b, 0x96, 0x30, 0x03, 0xb0, 0x30, - 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x2f, 0x01, 0xe0, - 0x09, 0x2f, 0x25, 0x86, 0x45, 0x0b, 0x84, 0x05, - 0x04, 0x99, 0x34, 0x00, 0x84, 0x34, 0x00, 0x80, - 0x34, 0x00, 0x81, 0x34, 0x00, 0x81, 0x34, 0x00, - 0x89, 0x34, 0xe0, 0x11, 0x04, 0x10, 0xe1, 0x0a, - 0x04, 0x81, 0x19, 0x0f, 0xbf, 0x04, 0x01, 0xb5, - 0x04, 0x27, 0x8d, 0x04, 0x01, 0x8f, 0x37, 0x89, - 0x19, 0x05, 0x8d, 0x37, 0x81, 0x1d, 0xa2, 0x19, - 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, 0x84, - 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, 0x19, - 0x00, 0x9f, 0x19, 0x99, 0x45, 0x85, 0x19, 0x99, - 0x45, 0x8a, 0x19, 0x89, 0x3d, 0x80, 0x19, 0xac, - 0x3d, 0x81, 0x19, 0x9e, 0x30, 0x02, 0x85, 0x30, - 0x01, 0x85, 0x30, 0x01, 0x85, 0x30, 0x01, 0x82, - 0x30, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, 0x09, - 0x84, 0x19, 0x01, 0x8b, 0x49, 0x00, 0x99, 0x49, - 0x00, 0x92, 0x49, 0x00, 0x81, 0x49, 0x00, 0x8e, - 0x49, 0x01, 0x8d, 0x49, 0x21, 0xe0, 0x1a, 0x49, - 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, 0x88, - 0x19, 0xce, 0x2b, 0x00, 0x8c, 0x19, 0x02, 0x80, - 0x2b, 0x2e, 0xac, 0x19, 0x80, 0x37, 0x60, 0x21, - 0x9c, 0x4b, 0x02, 0xb0, 0x13, 0x0e, 0x80, 0x37, - 0x9a, 0x19, 0x03, 0xa3, 0x69, 0x08, 0x82, 0x69, - 0x9a, 0x29, 0x04, 0xaa, 0x6b, 0x04, 0x9d, 0x96, - 0x00, 0x80, 0x96, 0xa3, 0x6c, 0x03, 0x8d, 0x6c, - 0x29, 0xcf, 0x1e, 0xaf, 0x7e, 0x9d, 0x72, 0x01, - 0x89, 0x72, 0x05, 0xa3, 0x71, 0x03, 0xa3, 0x71, - 0x03, 0xa7, 0x24, 0x07, 0xb3, 0x14, 0x0a, 0x80, - 0x14, 0x60, 0x2f, 0xe0, 0xd6, 0x48, 0x08, 0x95, - 0x48, 0x09, 0x87, 0x48, 0x60, 0x37, 0x85, 0x1c, - 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, 0x00, 0x81, - 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, 0x1c, 0x95, - 0x36, 0x00, 0x88, 0x36, 0x9f, 0x74, 0x9e, 0x5f, - 0x07, 0x88, 0x5f, 0x2f, 0x92, 0x33, 0x00, 0x81, - 0x33, 0x04, 0x84, 0x33, 0x9b, 0x77, 0x02, 0x80, - 0x77, 0x99, 0x4c, 0x04, 0x80, 0x4c, 0x3f, 0x9f, - 0x58, 0x97, 0x57, 0x03, 0x93, 0x57, 0x01, 0xad, - 0x57, 0x83, 0x3f, 0x00, 0x81, 0x3f, 0x04, 0x87, - 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x9c, 0x3f, 0x01, - 0x82, 0x3f, 0x03, 0x89, 0x3f, 0x06, 0x88, 0x3f, - 0x06, 0x9f, 0x6e, 0x9f, 0x6a, 0x1f, 0xa6, 0x51, - 0x03, 0x8b, 0x51, 0x08, 0xb5, 0x06, 0x02, 0x86, - 0x06, 0x95, 0x39, 0x01, 0x87, 0x39, 0x92, 0x38, - 0x04, 0x87, 0x38, 0x91, 0x78, 0x06, 0x83, 0x78, - 0x0b, 0x86, 0x78, 0x4f, 0xc8, 0x6f, 0x36, 0xb2, - 0x68, 0x0c, 0xb2, 0x68, 0x06, 0x85, 0x68, 0xa7, - 0x31, 0x07, 0x89, 0x31, 0x60, 0xc5, 0x9e, 0x04, - 0x00, 0xa9, 0x9a, 0x00, 0x82, 0x9a, 0x01, 0x81, - 0x9a, 0x4d, 0xa7, 0x6d, 0x07, 0xa9, 0x82, 0x55, - 0x9b, 0x18, 0x13, 0x96, 0x25, 0x08, 0xcd, 0x0e, - 0x03, 0x9d, 0x0e, 0x0e, 0x80, 0x0e, 0xc1, 0x3b, - 0x0a, 0x80, 0x3b, 0x01, 0x98, 0x83, 0x06, 0x89, - 0x83, 0x05, 0xb4, 0x15, 0x00, 0x91, 0x15, 0x07, - 0xa6, 0x4e, 0x08, 0xdf, 0x7d, 0x00, 0x93, 0x81, - 0x0a, 0x91, 0x41, 0x00, 0xab, 0x41, 0x40, 0x86, - 0x5d, 0x00, 0x80, 0x5d, 0x00, 0x83, 0x5d, 0x00, - 0x8e, 0x5d, 0x00, 0x8a, 0x5d, 0x05, 0xba, 0x43, - 0x04, 0x89, 0x43, 0x05, 0x83, 0x2a, 0x00, 0x87, - 0x2a, 0x01, 0x81, 0x2a, 0x01, 0x95, 0x2a, 0x00, - 0x86, 0x2a, 0x00, 0x81, 0x2a, 0x00, 0x84, 0x2a, - 0x00, 0x80, 0x37, 0x88, 0x2a, 0x01, 0x81, 0x2a, - 0x01, 0x82, 0x2a, 0x01, 0x80, 0x2a, 0x05, 0x80, - 0x2a, 0x04, 0x86, 0x2a, 0x01, 0x86, 0x2a, 0x02, - 0x84, 0x2a, 0x60, 0x2a, 0xdb, 0x62, 0x00, 0x84, - 0x62, 0x1d, 0xc7, 0x95, 0x07, 0x89, 0x95, 0x60, - 0x45, 0xb5, 0x7f, 0x01, 0xa5, 0x7f, 0x21, 0xc4, - 0x5a, 0x0a, 0x89, 0x5a, 0x05, 0x8c, 0x5b, 0x12, - 0xb8, 0x8d, 0x06, 0x89, 0x8d, 0x35, 0x9a, 0x02, - 0x01, 0x8e, 0x02, 0x03, 0x8f, 0x02, 0x60, 0x5f, - 0xbb, 0x21, 0x60, 0x03, 0xd2, 0x99, 0x0b, 0x80, - 0x99, 0x86, 0x20, 0x01, 0x80, 0x20, 0x01, 0x87, - 0x20, 0x00, 0x81, 0x20, 0x00, 0x9d, 0x20, 0x00, - 0x81, 0x20, 0x01, 0x8b, 0x20, 0x08, 0x89, 0x20, - 0x45, 0x87, 0x60, 0x01, 0xad, 0x60, 0x01, 0x8a, - 0x60, 0x1a, 0xc7, 0x9c, 0x07, 0xd2, 0x84, 0x1c, - 0xb8, 0x75, 0x60, 0xa6, 0x88, 0x0c, 0x00, 0xac, - 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, 0x02, - 0x9f, 0x52, 0x01, 0x95, 0x52, 0x00, 0x8d, 0x52, - 0x48, 0x86, 0x53, 0x00, 0x81, 0x53, 0x00, 0xab, - 0x53, 0x02, 0x80, 0x53, 0x00, 0x81, 0x53, 0x00, - 0x88, 0x53, 0x07, 0x89, 0x53, 0x05, 0x85, 0x2d, - 0x00, 0x81, 0x2d, 0x00, 0xa4, 0x2d, 0x00, 0x81, - 0x2d, 0x00, 0x85, 0x2d, 0x06, 0x89, 0x2d, 0x60, - 0xd5, 0x98, 0x4d, 0x60, 0x56, 0x80, 0x4a, 0x0e, - 0xb1, 0x8e, 0x0c, 0x80, 0x8e, 0xe3, 0x39, 0x1b, - 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, 0x1b, - 0x0a, 0xe0, 0x63, 0x1b, 0x6a, 0x5b, 0xe3, 0xce, - 0x23, 0x00, 0x88, 0x23, 0x6f, 0x66, 0xe1, 0xe6, - 0x03, 0x70, 0x11, 0x58, 0xe1, 0xd8, 0x08, 0x06, - 0x9e, 0x5c, 0x00, 0x89, 0x5c, 0x03, 0x81, 0x5c, - 0x5f, 0x9d, 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, - 0x73, 0x09, 0x89, 0x73, 0x00, 0x86, 0x73, 0x00, - 0x94, 0x73, 0x04, 0x92, 0x73, 0x62, 0x4f, 0xda, - 0x54, 0x60, 0x04, 0xca, 0x59, 0x03, 0xb8, 0x59, - 0x06, 0x90, 0x59, 0x3f, 0x80, 0x8f, 0x80, 0x64, - 0x81, 0x19, 0x80, 0x42, 0x0a, 0x81, 0x2f, 0x0d, - 0xf0, 0x07, 0x97, 0x8f, 0x07, 0xe2, 0x9f, 0x8f, - 0xe1, 0x75, 0x42, 0x29, 0x88, 0x8f, 0x70, 0x12, - 0x96, 0x80, 0x3d, 0xe0, 0xbd, 0x35, 0x30, 0x82, - 0x35, 0x10, 0x83, 0x3d, 0x07, 0xe1, 0x2b, 0x64, - 0x68, 0xa3, 0xe0, 0x0a, 0x22, 0x04, 0x8c, 0x22, - 0x02, 0x88, 0x22, 0x06, 0x89, 0x22, 0x01, 0x83, - 0x22, 0x83, 0x19, 0x70, 0x02, 0xfb, 0xe0, 0x95, - 0x19, 0x09, 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, - 0x37, 0x90, 0x19, 0x87, 0x37, 0x81, 0x19, 0x86, - 0x37, 0x9d, 0x19, 0x83, 0x37, 0xba, 0x19, 0x16, - 0xc5, 0x2b, 0x60, 0x39, 0x93, 0x19, 0x0b, 0xd6, - 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, 0x19, - 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, 0x80, - 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, 0x00, - 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, 0x19, - 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, 0x87, - 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, 0x00, - 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, 0x19, - 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, 0x01, - 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, 0x2b, - 0x80, 0x0e, 0x84, 0x80, 0x00, 0x8e, 0x80, 0x64, - 0xef, 0x86, 0x28, 0x00, 0x90, 0x28, 0x01, 0x86, - 0x28, 0x00, 0x81, 0x28, 0x00, 0x84, 0x28, 0x60, - 0x74, 0xac, 0x65, 0x02, 0x8d, 0x65, 0x01, 0x89, - 0x65, 0x03, 0x81, 0x65, 0x61, 0x0f, 0xb9, 0x98, - 0x04, 0x80, 0x98, 0x64, 0x9f, 0xe0, 0x64, 0x56, - 0x01, 0x8f, 0x56, 0x28, 0xcb, 0x01, 0x03, 0x89, + 0x9f, 0x63, 0xc2, 0x95, 0x17, 0x84, 0x95, 0x96, + 0x5a, 0x09, 0x85, 0x27, 0x01, 0x85, 0x27, 0x01, + 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0xaa, 0x4a, 0x80, 0x19, 0x88, 0x4a, 0x80, + 0x2d, 0x83, 0x4a, 0x81, 0x19, 0x03, 0xcf, 0x17, + 0xad, 0x5a, 0x01, 0x89, 0x5a, 0x05, 0xf0, 0x1b, + 0x43, 0x33, 0x0b, 0x96, 0x33, 0x03, 0xb0, 0x33, + 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x32, 0x01, 0xe0, + 0x09, 0x32, 0x25, 0x86, 0x4a, 0x0b, 0x84, 0x05, + 0x04, 0x99, 0x37, 0x00, 0x84, 0x37, 0x00, 0x80, + 0x37, 0x00, 0x81, 0x37, 0x00, 0x81, 0x37, 0x00, + 0x89, 0x37, 0xe0, 0x12, 0x04, 0x0f, 0xe1, 0x0a, + 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, 0xb5, 0x04, + 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, 0x8f, 0x3a, + 0x89, 0x19, 0x05, 0x8d, 0x3a, 0x81, 0x1d, 0xa2, + 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, + 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, + 0x19, 0x00, 0x9f, 0x19, 0x99, 0x4a, 0x85, 0x19, + 0x99, 0x4a, 0x8a, 0x19, 0x89, 0x40, 0x80, 0x19, + 0xac, 0x40, 0x81, 0x19, 0x9e, 0x33, 0x02, 0x85, + 0x33, 0x01, 0x85, 0x33, 0x01, 0x85, 0x33, 0x01, + 0x82, 0x33, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, + 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4e, 0x00, 0x99, + 0x4e, 0x00, 0x92, 0x4e, 0x00, 0x81, 0x4e, 0x00, + 0x8e, 0x4e, 0x01, 0x8d, 0x4e, 0x21, 0xe0, 0x1a, + 0x4e, 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, + 0x88, 0x19, 0xce, 0x2d, 0x00, 0x8c, 0x19, 0x02, + 0x80, 0x2d, 0x2e, 0xac, 0x19, 0x80, 0x3a, 0x60, + 0x21, 0x9c, 0x50, 0x02, 0xb0, 0x13, 0x0e, 0x80, + 0x3a, 0x9a, 0x19, 0x03, 0xa3, 0x70, 0x08, 0x82, + 0x70, 0x9a, 0x2b, 0x04, 0xaa, 0x72, 0x04, 0x9d, + 0xa3, 0x00, 0x80, 0xa3, 0xa3, 0x73, 0x03, 0x8d, + 0x73, 0x29, 0xcf, 0x1f, 0xaf, 0x86, 0x9d, 0x7a, + 0x01, 0x89, 0x7a, 0x05, 0xa3, 0x79, 0x03, 0xa3, + 0x79, 0x03, 0xa7, 0x25, 0x07, 0xb3, 0x14, 0x0a, + 0x80, 0x14, 0x8a, 0xa5, 0x00, 0x8e, 0xa5, 0x00, + 0x86, 0xa5, 0x00, 0x81, 0xa5, 0x00, 0x8a, 0xa5, + 0x00, 0x8e, 0xa5, 0x00, 0x86, 0xa5, 0x00, 0x81, + 0xa5, 0x02, 0xb3, 0xa0, 0x0b, 0xe0, 0xd6, 0x4d, + 0x08, 0x95, 0x4d, 0x09, 0x87, 0x4d, 0x17, 0x85, + 0x4a, 0x00, 0xa9, 0x4a, 0x00, 0x88, 0x4a, 0x44, + 0x85, 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, + 0x00, 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, + 0x1c, 0x95, 0x39, 0x00, 0x88, 0x39, 0x9f, 0x7c, + 0x9e, 0x64, 0x07, 0x88, 0x64, 0x2f, 0x92, 0x36, + 0x00, 0x81, 0x36, 0x04, 0x84, 0x36, 0x9b, 0x7f, + 0x02, 0x80, 0x7f, 0x99, 0x51, 0x04, 0x80, 0x51, + 0x3f, 0x9f, 0x5d, 0x97, 0x5c, 0x03, 0x93, 0x5c, + 0x01, 0xad, 0x5c, 0x83, 0x43, 0x00, 0x81, 0x43, + 0x04, 0x87, 0x43, 0x00, 0x82, 0x43, 0x00, 0x9c, + 0x43, 0x01, 0x82, 0x43, 0x03, 0x89, 0x43, 0x06, + 0x88, 0x43, 0x06, 0x9f, 0x75, 0x9f, 0x71, 0x1f, + 0xa6, 0x56, 0x03, 0x8b, 0x56, 0x08, 0xb5, 0x06, + 0x02, 0x86, 0x06, 0x95, 0x3c, 0x01, 0x87, 0x3c, + 0x92, 0x3b, 0x04, 0x87, 0x3b, 0x91, 0x80, 0x06, + 0x83, 0x80, 0x0b, 0x86, 0x80, 0x4f, 0xc8, 0x76, + 0x36, 0xb2, 0x6f, 0x0c, 0xb2, 0x6f, 0x06, 0x85, + 0x6f, 0xa7, 0x34, 0x07, 0x89, 0x34, 0x05, 0xa5, + 0x28, 0x02, 0x9c, 0x28, 0x07, 0x81, 0x28, 0x60, + 0x6f, 0x9e, 0x04, 0x00, 0xa9, 0xa8, 0x00, 0x82, + 0xa8, 0x01, 0x81, 0xa8, 0x0f, 0x82, 0x04, 0x36, + 0x83, 0x04, 0xa7, 0x74, 0x07, 0xa9, 0x8a, 0x15, + 0x99, 0x77, 0x25, 0x9b, 0x18, 0x13, 0x96, 0x26, + 0x08, 0xcd, 0x0e, 0x03, 0xa3, 0x0e, 0x08, 0x80, + 0x0e, 0xc2, 0x3e, 0x09, 0x80, 0x3e, 0x01, 0x98, + 0x8b, 0x06, 0x89, 0x8b, 0x05, 0xb4, 0x15, 0x00, + 0x91, 0x15, 0x07, 0xa6, 0x53, 0x08, 0xdf, 0x85, + 0x00, 0x93, 0x89, 0x0a, 0x91, 0x45, 0x00, 0xae, + 0x45, 0x3d, 0x86, 0x62, 0x00, 0x80, 0x62, 0x00, + 0x83, 0x62, 0x00, 0x8e, 0x62, 0x00, 0x8a, 0x62, + 0x05, 0xba, 0x47, 0x04, 0x89, 0x47, 0x05, 0x83, + 0x2c, 0x00, 0x87, 0x2c, 0x01, 0x81, 0x2c, 0x01, + 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, + 0x00, 0x84, 0x2c, 0x00, 0x80, 0x3a, 0x88, 0x2c, + 0x01, 0x81, 0x2c, 0x01, 0x82, 0x2c, 0x01, 0x80, + 0x2c, 0x05, 0x80, 0x2c, 0x04, 0x86, 0x2c, 0x01, + 0x86, 0x2c, 0x02, 0x84, 0x2c, 0x0a, 0x89, 0xa2, + 0x00, 0x80, 0xa2, 0x01, 0x80, 0xa2, 0x00, 0xa5, + 0xa2, 0x00, 0x89, 0xa2, 0x00, 0x80, 0xa2, 0x01, + 0x80, 0xa2, 0x00, 0x83, 0xa2, 0x00, 0x89, 0xa2, + 0x00, 0x81, 0xa2, 0x07, 0x81, 0xa2, 0x1c, 0xdb, + 0x68, 0x00, 0x84, 0x68, 0x1d, 0xc7, 0x9e, 0x07, + 0x89, 0x9e, 0x60, 0x45, 0xb5, 0x87, 0x01, 0xa5, + 0x87, 0x21, 0xc4, 0x5f, 0x0a, 0x89, 0x5f, 0x05, + 0x8c, 0x60, 0x12, 0xb9, 0x96, 0x05, 0x89, 0x96, + 0x05, 0x93, 0x63, 0x1b, 0x9a, 0x02, 0x01, 0x8e, + 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, + 0x60, 0x03, 0xd2, 0xa7, 0x0b, 0x80, 0xa7, 0x86, + 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, + 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, + 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, + 0x66, 0x01, 0xad, 0x66, 0x01, 0x8a, 0x66, 0x1a, + 0xc7, 0xaa, 0x07, 0xd2, 0x8c, 0x0c, 0x8f, 0x12, + 0xb8, 0x7d, 0x06, 0x89, 0x20, 0x60, 0x55, 0xa1, + 0x8e, 0x0d, 0x89, 0x8e, 0x05, 0x88, 0x0c, 0x00, + 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, + 0x02, 0x9f, 0x57, 0x01, 0x95, 0x57, 0x00, 0x8d, + 0x57, 0x48, 0x86, 0x58, 0x00, 0x81, 0x58, 0x00, + 0xab, 0x58, 0x02, 0x80, 0x58, 0x00, 0x81, 0x58, + 0x00, 0x88, 0x58, 0x07, 0x89, 0x58, 0x05, 0x85, + 0x2f, 0x00, 0x81, 0x2f, 0x00, 0xa4, 0x2f, 0x00, + 0x81, 0x2f, 0x00, 0x85, 0x2f, 0x06, 0x89, 0x2f, + 0x60, 0xd5, 0x98, 0x52, 0x06, 0x90, 0x41, 0x00, + 0xa8, 0x41, 0x02, 0x9c, 0x41, 0x54, 0x80, 0x4f, + 0x0e, 0xb1, 0x97, 0x0c, 0x80, 0x97, 0xe3, 0x39, + 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, + 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, 0xeb, 0xe0, + 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, 0x09, 0xef, + 0x3a, 0x24, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, + 0x58, 0xb9, 0x31, 0x66, 0x65, 0xe1, 0xd8, 0x08, + 0x06, 0x9e, 0x61, 0x00, 0x89, 0x61, 0x03, 0x81, + 0x61, 0xce, 0x9f, 0x00, 0x89, 0x9f, 0x05, 0x9d, + 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7b, 0x09, + 0x89, 0x7b, 0x00, 0x86, 0x7b, 0x00, 0x94, 0x7b, + 0x04, 0x92, 0x7b, 0x61, 0x4f, 0xb9, 0x48, 0x60, + 0x65, 0xda, 0x59, 0x60, 0x04, 0xca, 0x5e, 0x03, + 0xb8, 0x5e, 0x06, 0x90, 0x5e, 0x3f, 0x80, 0x98, + 0x80, 0x6a, 0x81, 0x32, 0x80, 0x46, 0x0a, 0x81, + 0x32, 0x0d, 0xf0, 0x07, 0x97, 0x98, 0x07, 0xe2, + 0x9f, 0x98, 0xe1, 0x75, 0x46, 0x28, 0x80, 0x46, + 0x88, 0x98, 0x70, 0x12, 0x86, 0x83, 0x40, 0x00, + 0x86, 0x40, 0x00, 0x81, 0x40, 0x00, 0x80, 0x40, + 0xe0, 0xbe, 0x38, 0x82, 0x40, 0x0e, 0x80, 0x38, + 0x1c, 0x82, 0x38, 0x01, 0x80, 0x40, 0x0d, 0x83, + 0x40, 0x07, 0xe1, 0x2b, 0x6a, 0x68, 0xa3, 0xe0, + 0x0a, 0x23, 0x04, 0x8c, 0x23, 0x02, 0x88, 0x23, + 0x06, 0x89, 0x23, 0x01, 0x83, 0x23, 0x83, 0x19, + 0x6e, 0xfb, 0xe0, 0x99, 0x19, 0x05, 0xe1, 0x53, + 0x19, 0x4b, 0xad, 0x3a, 0x01, 0x96, 0x3a, 0x08, + 0xe0, 0x13, 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, + 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, 0x3a, 0x90, + 0x19, 0x87, 0x3a, 0x81, 0x19, 0x86, 0x3a, 0x9d, + 0x19, 0x83, 0x3a, 0xbc, 0x19, 0x14, 0xc5, 0x2d, + 0x60, 0x19, 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, + 0xd6, 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, + 0x19, 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, + 0x80, 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, + 0x00, 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, + 0x19, 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, + 0x87, 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, + 0x00, 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, + 0x19, 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, + 0x01, 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, + 0x2b, 0x88, 0x0e, 0x84, 0x88, 0x00, 0x8e, 0x88, + 0x63, 0xef, 0x9e, 0x4a, 0x05, 0x85, 0x4a, 0x60, + 0x74, 0x86, 0x2a, 0x00, 0x90, 0x2a, 0x01, 0x86, + 0x2a, 0x00, 0x81, 0x2a, 0x00, 0x84, 0x2a, 0x04, + 0xbd, 0x1d, 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, + 0x6b, 0x02, 0x8d, 0x6b, 0x01, 0x89, 0x6b, 0x03, + 0x81, 0x6b, 0x60, 0xdf, 0x9e, 0xa1, 0x10, 0xb9, + 0xa6, 0x04, 0x80, 0xa6, 0x61, 0x6f, 0xa9, 0x65, + 0x60, 0x75, 0xaa, 0x6e, 0x03, 0x80, 0x6e, 0x61, + 0x7f, 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, + 0x27, 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x5b, + 0x01, 0x8f, 0x5b, 0x28, 0xcb, 0x01, 0x03, 0x89, 0x01, 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, 0x4b, 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, 0x9a, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, @@ -3384,129 +3635,186 @@ 0x81, 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, 0x03, 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, 0x00, 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, - 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x35, 0x81, + 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x38, 0x81, 0x19, 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, 0x81, 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, - 0x77, 0x19, 0x07, 0x8c, 0x19, 0x02, 0x8c, 0x19, - 0x02, 0xe0, 0x13, 0x19, 0x0b, 0xd8, 0x19, 0x06, - 0x8b, 0x19, 0x13, 0x8b, 0x19, 0x03, 0xb7, 0x19, - 0x07, 0x89, 0x19, 0x05, 0xa7, 0x19, 0x07, 0x9d, - 0x19, 0x01, 0x81, 0x19, 0x4d, 0xe0, 0x18, 0x19, - 0x00, 0xd1, 0x19, 0x00, 0xe0, 0x26, 0x19, 0x0b, - 0x8d, 0x19, 0x01, 0x84, 0x19, 0x02, 0x82, 0x19, - 0x04, 0x86, 0x19, 0x08, 0x98, 0x19, 0x06, 0x86, - 0x19, 0x08, 0x82, 0x19, 0x0c, 0x86, 0x19, 0x28, - 0xe0, 0x32, 0x19, 0x00, 0xb6, 0x19, 0x24, 0x89, - 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7d, 0x2f, 0x21, - 0xef, 0xd4, 0x2f, 0x0a, 0xe0, 0x7d, 0x2f, 0x01, - 0xf0, 0x06, 0x21, 0x2f, 0x0d, 0xf0, 0x0c, 0xd0, - 0x2f, 0x6b, 0xbe, 0xe1, 0xbd, 0x2f, 0x65, 0x81, - 0xf0, 0x02, 0xea, 0x2f, 0x7a, 0xdc, 0x55, 0x80, - 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, 0xe0, 0x8f, - 0x37, -}; - -static const uint8_t unicode_script_ext_table799 = { - 0x82, 0xc1, 0x00, 0x00, 0x01, 0x2b, 0x01, 0x00, - 0x00, 0x01, 0x2b, 0x1c, 0x00, 0x0c, 0x01, 0x45, - 0x80, 0x92, 0x00, 0x00, 0x02, 0x1d, 0x6b, 0x00, - 0x02, 0x1d, 0x28, 0x01, 0x02, 0x1d, 0x45, 0x00, - 0x02, 0x1d, 0x28, 0x81, 0x03, 0x00, 0x00, 0x05, - 0x04, 0x31, 0x87, 0x91, 0x9a, 0x0d, 0x00, 0x00, - 0x05, 0x04, 0x31, 0x87, 0x91, 0x9a, 0x00, 0x03, - 0x04, 0x87, 0x91, 0x01, 0x00, 0x00, 0x05, 0x04, - 0x31, 0x87, 0x91, 0x9a, 0x1f, 0x00, 0x00, 0x08, - 0x01, 0x04, 0x50, 0x51, 0x78, 0x31, 0x82, 0x87, - 0x09, 0x00, 0x0a, 0x02, 0x04, 0x87, 0x09, 0x00, - 0x09, 0x03, 0x04, 0x91, 0x9a, 0x05, 0x00, 0x00, - 0x02, 0x04, 0x87, 0x62, 0x00, 0x00, 0x02, 0x04, - 0x31, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x1f, - 0x2a, 0x2c, 0x2e, 0x3c, 0x45, 0x4f, 0x70, 0x7d, - 0x8e, 0x90, 0x95, 0x00, 0x0c, 0x0b, 0x1f, 0x2a, - 0x2c, 0x2e, 0x3c, 0x45, 0x4f, 0x70, 0x8e, 0x90, - 0x95, 0x10, 0x00, 0x00, 0x14, 0x0b, 0x1f, 0x21, - 0x2d, 0x53, 0x2a, 0x2c, 0x2e, 0x3c, 0x4e, 0x4f, - 0x60, 0x70, 0x43, 0x81, 0x86, 0x8d, 0x8e, 0x90, - 0x95, 0x00, 0x15, 0x0b, 0x1f, 0x21, 0x2d, 0x53, - 0x2a, 0x2c, 0x2e, 0x3c, 0x47, 0x4e, 0x4f, 0x60, - 0x70, 0x43, 0x81, 0x86, 0x8d, 0x8e, 0x90, 0x95, - 0x09, 0x04, 0x1f, 0x21, 0x3b, 0x4e, 0x75, 0x00, - 0x09, 0x03, 0x0b, 0x15, 0x86, 0x75, 0x00, 0x09, - 0x02, 0x2e, 0x5d, 0x75, 0x00, 0x09, 0x02, 0x2c, - 0x41, 0x80, 0x75, 0x00, 0x0d, 0x02, 0x2a, 0x8e, - 0x80, 0x71, 0x00, 0x09, 0x02, 0x3c, 0x60, 0x82, - 0xcf, 0x00, 0x09, 0x03, 0x15, 0x5e, 0x8a, 0x80, - 0x30, 0x00, 0x00, 0x02, 0x27, 0x45, 0x85, 0xb8, - 0x00, 0x01, 0x04, 0x11, 0x32, 0x89, 0x88, 0x80, - 0x4a, 0x00, 0x01, 0x02, 0x5b, 0x76, 0x00, 0x00, - 0x00, 0x02, 0x5b, 0x76, 0x84, 0x49, 0x00, 0x00, - 0x04, 0x0b, 0x1f, 0x2a, 0x3c, 0x00, 0x01, 0x1f, - 0x00, 0x04, 0x0b, 0x1f, 0x2a, 0x3c, 0x00, 0x02, - 0x1f, 0x2a, 0x00, 0x01, 0x1f, 0x01, 0x02, 0x0b, - 0x1f, 0x00, 0x02, 0x1f, 0x7d, 0x00, 0x02, 0x0b, - 0x1f, 0x00, 0x02, 0x1f, 0x7d, 0x00, 0x06, 0x1f, - 0x3c, 0x4f, 0x70, 0x8e, 0x90, 0x00, 0x01, 0x1f, - 0x01, 0x02, 0x1f, 0x7d, 0x01, 0x01, 0x1f, 0x00, - 0x02, 0x1f, 0x7d, 0x00, 0x02, 0x0b, 0x1f, 0x06, - 0x01, 0x1f, 0x00, 0x02, 0x1f, 0x60, 0x00, 0x02, - 0x0b, 0x1f, 0x01, 0x01, 0x1f, 0x00, 0x02, 0x0b, - 0x1f, 0x03, 0x01, 0x1f, 0x00, 0x08, 0x0b, 0x1f, - 0x2a, 0x3c, 0x60, 0x70, 0x90, 0x95, 0x00, 0x02, - 0x1f, 0x2a, 0x00, 0x03, 0x1f, 0x2a, 0x3c, 0x01, - 0x02, 0x0b, 0x1f, 0x00, 0x01, 0x0b, 0x01, 0x02, - 0x1f, 0x2a, 0x00, 0x01, 0x60, 0x80, 0x44, 0x00, - 0x01, 0x01, 0x2b, 0x35, 0x00, 0x00, 0x02, 0x1d, - 0x87, 0x81, 0xb5, 0x00, 0x00, 0x02, 0x45, 0x5b, - 0x80, 0x3f, 0x00, 0x00, 0x03, 0x1f, 0x2a, 0x45, - 0x8c, 0xd1, 0x00, 0x00, 0x02, 0x1d, 0x28, 0x81, - 0x3c, 0x00, 0x01, 0x06, 0x0d, 0x30, 0x2f, 0x35, - 0x3d, 0x9b, 0x00, 0x05, 0x0d, 0x30, 0x2f, 0x35, - 0x3d, 0x01, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, - 0x09, 0x06, 0x0d, 0x30, 0x2f, 0x35, 0x3d, 0x9b, - 0x00, 0x00, 0x00, 0x05, 0x0d, 0x30, 0x2f, 0x35, - 0x3d, 0x07, 0x06, 0x0d, 0x30, 0x2f, 0x35, 0x3d, - 0x9b, 0x03, 0x05, 0x0d, 0x30, 0x2f, 0x35, 0x3d, - 0x09, 0x00, 0x03, 0x02, 0x0d, 0x2f, 0x01, 0x00, - 0x00, 0x05, 0x0d, 0x30, 0x2f, 0x35, 0x3d, 0x04, - 0x02, 0x35, 0x3d, 0x00, 0x00, 0x00, 0x05, 0x0d, - 0x30, 0x2f, 0x35, 0x3d, 0x03, 0x00, 0x01, 0x03, - 0x2f, 0x35, 0x3d, 0x01, 0x01, 0x2f, 0x58, 0x00, - 0x03, 0x02, 0x35, 0x3d, 0x02, 0x00, 0x00, 0x02, - 0x35, 0x3d, 0x59, 0x00, 0x00, 0x06, 0x0d, 0x30, - 0x2f, 0x35, 0x3d, 0x9b, 0x00, 0x02, 0x35, 0x3d, - 0x80, 0x12, 0x00, 0x0f, 0x01, 0x2f, 0x1f, 0x00, - 0x23, 0x01, 0x2f, 0x3b, 0x00, 0x27, 0x01, 0x2f, - 0x37, 0x00, 0x30, 0x01, 0x2f, 0x0e, 0x00, 0x0b, - 0x01, 0x2f, 0x32, 0x00, 0x00, 0x01, 0x2f, 0x57, - 0x00, 0x18, 0x01, 0x2f, 0x09, 0x00, 0x04, 0x01, - 0x2f, 0x5f, 0x00, 0x1e, 0x01, 0x2f, 0xc0, 0x31, - 0xef, 0x00, 0x00, 0x02, 0x1d, 0x28, 0x80, 0x0f, - 0x00, 0x07, 0x02, 0x2f, 0x45, 0x80, 0xa7, 0x00, - 0x02, 0x0e, 0x1f, 0x21, 0x2c, 0x2e, 0x41, 0x3c, - 0x3b, 0x4e, 0x4f, 0x5a, 0x60, 0x43, 0x8d, 0x95, - 0x02, 0x0d, 0x1f, 0x21, 0x2c, 0x2e, 0x41, 0x3c, - 0x3b, 0x4e, 0x5a, 0x60, 0x43, 0x8d, 0x95, 0x03, - 0x0b, 0x1f, 0x21, 0x2c, 0x2e, 0x41, 0x3b, 0x4e, - 0x5a, 0x43, 0x8d, 0x95, 0x80, 0x36, 0x00, 0x00, - 0x02, 0x0b, 0x1f, 0x00, 0x00, 0x00, 0x02, 0x1f, - 0x8e, 0x39, 0x00, 0x00, 0x03, 0x3e, 0x45, 0x5e, - 0x80, 0x1f, 0x00, 0x00, 0x02, 0x10, 0x3a, 0xc0, - 0x13, 0xa1, 0x00, 0x00, 0x02, 0x04, 0x91, 0x09, - 0x00, 0x00, 0x02, 0x04, 0x91, 0x46, 0x00, 0x01, - 0x05, 0x0d, 0x30, 0x2f, 0x35, 0x3d, 0x80, 0x99, - 0x00, 0x04, 0x06, 0x0d, 0x30, 0x2f, 0x35, 0x3d, - 0x9b, 0x09, 0x00, 0x00, 0x02, 0x35, 0x3d, 0x2c, - 0x00, 0x01, 0x02, 0x35, 0x3d, 0x80, 0xdf, 0x00, - 0x02, 0x02, 0x1c, 0x49, 0x03, 0x00, 0x2c, 0x03, - 0x1c, 0x48, 0x49, 0x02, 0x00, 0x08, 0x02, 0x1c, - 0x49, 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, - 0x8f, 0x84, 0x00, 0x00, 0x02, 0x2a, 0x8e, 0x00, - 0x00, 0x00, 0x02, 0x2a, 0x8e, 0x36, 0x00, 0x01, - 0x02, 0x2a, 0x8e, 0x8c, 0x12, 0x00, 0x01, 0x02, - 0x2a, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x2a, 0x8e, - 0xc0, 0x5c, 0x4b, 0x00, 0x03, 0x01, 0x22, 0x96, - 0x3b, 0x00, 0x11, 0x01, 0x2f, 0x9e, 0x5d, 0x00, - 0x01, 0x01, 0x2f, 0xce, 0xcd, 0x2d, 0x00, + 0x77, 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, + 0x02, 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, + 0x8b, 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, + 0x03, 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, + 0x19, 0x07, 0x9d, 0x19, 0x01, 0x8b, 0x19, 0x03, + 0x81, 0x19, 0x3d, 0xe0, 0xf3, 0x19, 0x0b, 0x8d, + 0x19, 0x01, 0x8c, 0x19, 0x02, 0x89, 0x19, 0x04, + 0xb7, 0x19, 0x06, 0x8e, 0x19, 0x01, 0x8a, 0x19, + 0x05, 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, + 0xe0, 0x05, 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7f, + 0x32, 0x1f, 0xef, 0xd9, 0x32, 0x05, 0xe0, 0x7d, + 0x32, 0x01, 0xf0, 0x06, 0x21, 0x32, 0x0d, 0xf0, + 0x0c, 0xd0, 0x32, 0x0e, 0xe2, 0x0d, 0x32, 0x69, + 0x41, 0xe1, 0xbd, 0x32, 0x65, 0x81, 0xf0, 0x02, + 0xea, 0x32, 0x04, 0xef, 0xff, 0x32, 0x7a, 0xcb, + 0xf0, 0x80, 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, + 0xe0, 0x8f, 0x3a, +}; + +static const uint8_t unicode_script_ext_table1253 = { + 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x13, 0x1a, + 0x23, 0x25, 0x29, 0x2a, 0x2f, 0x2b, 0x2d, 0x32, + 0x4a, 0x51, 0x53, 0x72, 0x86, 0x81, 0x83, 0x00, + 0x00, 0x07, 0x0b, 0x1d, 0x20, 0x4a, 0x4f, 0x9b, + 0xa1, 0x09, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x00, + 0x00, 0x02, 0x02, 0x0d, 0x4a, 0x00, 0x00, 0x00, + 0x02, 0x4a, 0x4f, 0x08, 0x00, 0x00, 0x02, 0x4a, + 0x9b, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x25, + 0x00, 0x00, 0x08, 0x17, 0x1a, 0x1d, 0x2d, 0x4a, + 0x72, 0x8e, 0x93, 0x00, 0x08, 0x17, 0x1d, 0x2d, + 0x4a, 0x79, 0x8e, 0x93, 0xa0, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x9d, 0x00, 0x05, 0x2a, 0x4a, 0x8e, + 0x90, 0x9b, 0x00, 0x0b, 0x14, 0x17, 0x1a, 0x1d, + 0x2b, 0x2d, 0x4a, 0x79, 0x90, 0x9d, 0xa0, 0x00, + 0x06, 0x1a, 0x25, 0x2a, 0x2b, 0x40, 0x4a, 0x00, + 0x04, 0x1d, 0x2d, 0x4a, 0x72, 0x00, 0x09, 0x1a, + 0x23, 0x37, 0x4a, 0x72, 0x90, 0x93, 0x9d, 0xa0, + 0x00, 0x0a, 0x05, 0x1d, 0x23, 0x2b, 0x2d, 0x37, + 0x4a, 0x72, 0x90, 0x93, 0x00, 0x02, 0x4a, 0x9d, + 0x00, 0x03, 0x23, 0x4a, 0x90, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x79, 0x00, 0x03, 0x17, 0x4a, 0x93, + 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x02, 0x27, 0x4a, + 0x00, 0x00, 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x03, + 0x1d, 0x4a, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x2d, + 0x4a, 0x72, 0xa0, 0x0b, 0x00, 0x00, 0x02, 0x4a, + 0x90, 0x01, 0x00, 0x00, 0x05, 0x17, 0x23, 0x40, + 0x4a, 0x90, 0x00, 0x04, 0x17, 0x23, 0x4a, 0x90, + 0x00, 0x02, 0x4a, 0x90, 0x06, 0x00, 0x00, 0x03, + 0x4a, 0x8e, 0x90, 0x00, 0x02, 0x4a, 0x90, 0x00, + 0x00, 0x00, 0x03, 0x17, 0x4a, 0x90, 0x00, 0x06, + 0x14, 0x17, 0x2b, 0x4a, 0x8e, 0x9b, 0x0f, 0x00, + 0x00, 0x01, 0x2d, 0x01, 0x00, 0x00, 0x01, 0x2d, + 0x11, 0x00, 0x00, 0x02, 0x4a, 0x79, 0x04, 0x00, + 0x00, 0x03, 0x14, 0x4a, 0xa0, 0x03, 0x00, 0x0c, + 0x01, 0x4a, 0x03, 0x00, 0x01, 0x02, 0x1a, 0x2d, + 0x80, 0x8c, 0x00, 0x00, 0x02, 0x1d, 0x72, 0x00, + 0x02, 0x1d, 0x2a, 0x01, 0x02, 0x1d, 0x4a, 0x00, + 0x02, 0x1d, 0x2a, 0x80, 0x80, 0x00, 0x00, 0x03, + 0x05, 0x29, 0x2a, 0x80, 0x01, 0x00, 0x00, 0x07, + 0x04, 0x28, 0x69, 0x34, 0x90, 0x9a, 0xa8, 0x0d, + 0x00, 0x00, 0x07, 0x04, 0x28, 0x69, 0x34, 0x90, + 0x9a, 0xa8, 0x00, 0x03, 0x04, 0x90, 0x9a, 0x01, + 0x00, 0x00, 0x08, 0x01, 0x04, 0x28, 0x69, 0x34, + 0x90, 0x9a, 0xa8, 0x1f, 0x00, 0x00, 0x09, 0x01, + 0x04, 0x55, 0x56, 0x77, 0x80, 0x34, 0x8a, 0x90, + 0x09, 0x00, 0x0a, 0x02, 0x04, 0x90, 0x09, 0x00, + 0x09, 0x03, 0x04, 0x9a, 0xa8, 0x05, 0x00, 0x00, + 0x02, 0x04, 0x90, 0x62, 0x00, 0x00, 0x02, 0x04, + 0x34, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x20, + 0x2c, 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x85, + 0x97, 0x99, 0x9e, 0x00, 0x0c, 0x0b, 0x20, 0x2c, + 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x97, 0x99, + 0x9e, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x20, 0x22, + 0x2f, 0x58, 0x2c, 0x2e, 0x30, 0x3f, 0x53, 0x54, + 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, 0x97, + 0x99, 0x9e, 0x00, 0x17, 0x0b, 0x20, 0x22, 0x2f, + 0x58, 0x2c, 0x2e, 0x31, 0x30, 0x3f, 0x4c, 0x53, + 0x54, 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, + 0x97, 0x99, 0x9e, 0x09, 0x04, 0x20, 0x22, 0x3e, + 0x53, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x8f, + 0x75, 0x00, 0x09, 0x02, 0x30, 0x62, 0x75, 0x00, + 0x09, 0x02, 0x2e, 0x45, 0x80, 0x75, 0x00, 0x0d, + 0x02, 0x2c, 0x97, 0x80, 0x71, 0x00, 0x09, 0x03, + 0x3f, 0x66, 0xa2, 0x82, 0xcf, 0x00, 0x09, 0x03, + 0x15, 0x63, 0x93, 0x80, 0x30, 0x00, 0x00, 0x03, + 0x29, 0x2a, 0x4a, 0x85, 0x6e, 0x00, 0x02, 0x01, + 0x82, 0x46, 0x00, 0x01, 0x04, 0x11, 0x35, 0x92, + 0x91, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x60, 0x7e, + 0x00, 0x00, 0x00, 0x02, 0x60, 0x7e, 0x84, 0x49, + 0x00, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, 0x00, + 0x01, 0x20, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, + 0x00, 0x03, 0x20, 0x2c, 0x3f, 0x00, 0x01, 0x20, + 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x06, 0x20, 0x3f, 0x54, 0x78, 0x97, 0x99, + 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, 0x85, 0x01, + 0x01, 0x20, 0x00, 0x02, 0x20, 0x85, 0x00, 0x02, + 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, 0x02, 0x20, + 0x66, 0x00, 0x02, 0x0b, 0x20, 0x01, 0x01, 0x20, + 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, 0x20, 0x00, + 0x0b, 0x0b, 0x20, 0x2c, 0x3f, 0x54, 0x66, 0x78, + 0x89, 0x99, 0x9e, 0xa2, 0x00, 0x02, 0x20, 0x2c, + 0x00, 0x04, 0x20, 0x2c, 0x3f, 0xa2, 0x01, 0x02, + 0x0b, 0x20, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, + 0x2c, 0x00, 0x01, 0x66, 0x80, 0x44, 0x00, 0x01, + 0x01, 0x2d, 0x35, 0x00, 0x00, 0x03, 0x1d, 0x4a, + 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x81, 0xb3, + 0x00, 0x00, 0x03, 0x4a, 0x60, 0x7e, 0x1e, 0x00, + 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, 0x06, + 0x13, 0x29, 0x2a, 0x6f, 0x50, 0x76, 0x01, 0x00, + 0x00, 0x04, 0x13, 0x2d, 0x6f, 0x5d, 0x80, 0x11, + 0x00, 0x00, 0x03, 0x20, 0x2c, 0x4a, 0x8c, 0xa5, + 0x00, 0x00, 0x02, 0x1a, 0x4a, 0x17, 0x00, 0x00, + 0x02, 0x06, 0x76, 0x00, 0x07, 0x06, 0x13, 0x29, + 0x6f, 0x3e, 0x51, 0x83, 0x09, 0x00, 0x00, 0x01, + 0x23, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, 0x6f, + 0x00, 0x00, 0x00, 0x02, 0x1d, 0x2a, 0x81, 0x2b, + 0x00, 0x0f, 0x02, 0x32, 0x98, 0x00, 0x00, 0x00, + 0x07, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, 0xa9, + 0x00, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x7e, 0xa9, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x01, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, + 0x01, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x9c, 0xa9, 0x01, 0x09, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x4f, 0x60, 0x9c, 0xa9, 0x05, 0x06, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0xa9, 0x00, 0x00, 0x00, + 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x07, 0x06, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0xa9, 0x03, 0x05, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0x09, 0x00, 0x03, + 0x02, 0x0d, 0x32, 0x01, 0x00, 0x00, 0x05, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0x04, 0x02, 0x38, 0x40, + 0x00, 0x00, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x03, 0x00, 0x01, 0x03, 0x32, 0x38, 0x40, + 0x01, 0x01, 0x32, 0x58, 0x00, 0x03, 0x02, 0x38, + 0x40, 0x02, 0x00, 0x00, 0x02, 0x38, 0x40, 0x59, + 0x00, 0x00, 0x06, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0xa9, 0x00, 0x02, 0x38, 0x40, 0x80, 0x12, 0x00, + 0x0f, 0x01, 0x32, 0x1f, 0x00, 0x25, 0x01, 0x32, + 0x08, 0x00, 0x00, 0x02, 0x32, 0x98, 0x2f, 0x00, + 0x27, 0x01, 0x32, 0x37, 0x00, 0x30, 0x01, 0x32, + 0x0e, 0x00, 0x0b, 0x01, 0x32, 0x32, 0x00, 0x00, + 0x01, 0x32, 0x57, 0x00, 0x18, 0x01, 0x32, 0x09, + 0x00, 0x04, 0x01, 0x32, 0x5f, 0x00, 0x1e, 0x01, + 0x32, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, + 0x2a, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x32, 0x4a, + 0x80, 0xa7, 0x00, 0x02, 0x10, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x54, 0x5f, 0x66, + 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x02, 0x0f, 0x20, + 0x22, 0x2e, 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x5f, + 0x66, 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x01, 0x0b, + 0x20, 0x22, 0x2e, 0x30, 0x45, 0x3e, 0x53, 0x5f, + 0x47, 0x96, 0x9e, 0x00, 0x0c, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3e, 0x53, 0x5f, 0x85, 0x47, 0x96, + 0x9e, 0x00, 0x0b, 0x20, 0x22, 0x2e, 0x30, 0x45, + 0x3e, 0x53, 0x5f, 0x47, 0x96, 0x9e, 0x80, 0x36, + 0x00, 0x00, 0x03, 0x0b, 0x20, 0xa2, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x97, 0x39, 0x00, 0x00, 0x03, + 0x42, 0x4a, 0x63, 0x80, 0x1f, 0x00, 0x00, 0x02, + 0x10, 0x3d, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, + 0x04, 0x69, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, + 0x9a, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9a, 0x46, + 0x00, 0x01, 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x33, 0x32, + 0x38, 0x40, 0xa9, 0x09, 0x00, 0x00, 0x02, 0x38, + 0x40, 0x2c, 0x00, 0x01, 0x02, 0x38, 0x40, 0x80, + 0xdf, 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4e, 0x00, + 0x02, 0x1c, 0x4e, 0x03, 0x00, 0x2c, 0x03, 0x1c, + 0x4d, 0x4e, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4e, + 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, + 0x75, 0x00, 0x00, 0x02, 0x56, 0x77, 0x87, 0x8d, + 0x00, 0x00, 0x02, 0x2c, 0x97, 0x00, 0x00, 0x00, + 0x02, 0x2c, 0x97, 0x36, 0x00, 0x01, 0x02, 0x2c, + 0x97, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2c, 0x97, + 0x00, 0x00, 0x00, 0x02, 0x2c, 0x97, 0xc0, 0x5c, + 0x4b, 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, + 0x11, 0x01, 0x32, 0x9e, 0x5d, 0x00, 0x01, 0x01, + 0x32, 0xce, 0xcd, 0x2d, 0x00, }; static const uint8_t unicode_prop_Hyphen_table28 = { @@ -3544,69 +3852,75 @@ 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, }; -static const uint8_t unicode_prop_Other_Alphabetic_table411 = { - 0x43, 0x44, 0x80, 0x42, 0x69, 0x8d, 0x00, 0x01, - 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, 0x06, 0x8f, - 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, 0xa2, 0x80, - 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, 0x88, 0x02, - 0x03, 0x40, 0xa6, 0x8b, 0x16, 0x85, 0x93, 0xb5, - 0x09, 0x8e, 0x01, 0x22, 0x89, 0x81, 0x9c, 0x82, - 0xb9, 0x31, 0x09, 0x81, 0x89, 0x80, 0x89, 0x81, - 0x9c, 0x82, 0xb9, 0x23, 0x09, 0x0b, 0x80, 0x9d, - 0x0a, 0x80, 0x8a, 0x82, 0xb9, 0x38, 0x10, 0x81, - 0x94, 0x81, 0x95, 0x13, 0x82, 0xb9, 0x31, 0x09, - 0x81, 0x88, 0x81, 0x89, 0x81, 0x9d, 0x80, 0xba, - 0x22, 0x10, 0x82, 0x89, 0x80, 0xa7, 0x83, 0xb9, - 0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x9c, 0x82, - 0xb9, 0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x9b, - 0x83, 0xb9, 0x30, 0x10, 0x82, 0x89, 0x80, 0x89, - 0x81, 0x9c, 0x82, 0xca, 0x28, 0x00, 0x87, 0x91, - 0x81, 0xbc, 0x01, 0x86, 0x91, 0x80, 0xe2, 0x01, - 0x28, 0x81, 0x8f, 0x80, 0x40, 0xa2, 0x90, 0x8a, - 0x8a, 0x80, 0xa3, 0xed, 0x8b, 0x00, 0x0b, 0x96, - 0x1b, 0x10, 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x00, - 0x89, 0x83, 0x46, 0x73, 0x81, 0x9d, 0x81, 0x9d, - 0x81, 0x9d, 0x81, 0xc1, 0x92, 0x40, 0xbb, 0x81, - 0xa1, 0x80, 0xf5, 0x8b, 0x83, 0x88, 0x40, 0xdd, - 0x84, 0xb8, 0x89, 0x81, 0x93, 0xc9, 0x81, 0xbe, - 0x84, 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, - 0xb8, 0x8a, 0xb1, 0x92, 0x41, 0xaf, 0x8d, 0x46, - 0xc0, 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, - 0x87, 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, - 0x84, 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, - 0xa5, 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, - 0xa4, 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, - 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, - 0x80, 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, - 0x48, 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, - 0x43, 0x13, 0x83, 0x41, 0x82, 0x81, 0x41, 0x52, - 0x82, 0xb4, 0x8d, 0xbb, 0x80, 0xac, 0x88, 0xc6, - 0x82, 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, - 0x8c, 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x40, - 0x9f, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, - 0x89, 0x80, 0x89, 0x81, 0x40, 0xd0, 0x8c, 0x02, - 0xe9, 0x91, 0x40, 0xec, 0x31, 0x86, 0x9c, 0x81, - 0xd1, 0x8e, 0x00, 0xe9, 0x8a, 0xe6, 0x8d, 0x41, - 0x00, 0x8c, 0x40, 0xf6, 0x28, 0x09, 0x0a, 0x00, - 0x80, 0x40, 0x8d, 0x31, 0x2b, 0x80, 0x9b, 0x89, - 0xa9, 0x20, 0x83, 0x91, 0x8a, 0xad, 0x8d, 0x41, - 0x96, 0x38, 0x86, 0xd2, 0x95, 0x80, 0x8d, 0xf9, - 0x2a, 0x00, 0x08, 0x10, 0x02, 0x80, 0xc1, 0x20, - 0x08, 0x83, 0x41, 0x5b, 0x83, 0x60, 0x50, 0x57, - 0x00, 0xb6, 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, - 0x80, 0x60, 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, - 0x04, 0x49, 0x1b, 0x80, 0x47, 0xe7, 0x99, 0x85, +static const uint8_t unicode_prop_Other_Alphabetic_table443 = { + 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, + 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, + 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, + 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, + 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, + 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, + 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, + 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, + 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, + 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, + 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, + 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, + 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, + 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, + 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, + 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, + 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, + 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, + 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, + 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, + 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, + 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, + 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, + 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, + 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, + 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, + 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, + 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, + 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, + 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, + 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, + 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, + 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, + 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, + 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xce, + 0x80, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, + 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, + 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, + 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, + 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, + 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, + 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, + 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, + 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, + 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, + 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, + 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, + 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x10, 0x02, 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, + 0x5b, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x82, + 0x60, 0x41, 0xdc, 0x90, 0x4e, 0x1f, 0x00, 0xb6, + 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, 0x60, + 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, + 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, 0x85, 0x99, 0x85, 0x99, }; -static const uint8_t unicode_prop_Other_Lowercase_table51 = { +static const uint8_t unicode_prop_Other_Lowercase_table69 = { 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, - 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x59, - 0xb0, 0xbe, 0x8c, 0x80, 0xa1, 0xa4, 0x42, 0xb0, - 0x80, 0x8c, 0x80, 0x8f, 0x8c, 0x40, 0xd2, 0x8f, - 0x43, 0x4f, 0x99, 0x47, 0x91, 0x81, 0x60, 0x7a, - 0x1d, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x86, 0x81, - 0x43, 0x61, 0x83, + 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, + 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, + 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, + 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, + 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, + 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, + 0x88, 0x60, 0xd8, 0x74, 0xbd, }; static const uint8_t unicode_prop_Other_Uppercase_table15 = { @@ -3614,16 +3928,21 @@ 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, }; -static const uint8_t unicode_prop_Other_Grapheme_Extend_table65 = { +static const uint8_t unicode_prop_Other_Grapheme_Extend_table112 = { 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, - 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe9, - 0x80, 0x91, 0x81, 0xe6, 0x80, 0x97, 0x80, 0xf6, - 0x80, 0x8e, 0x80, 0x4d, 0x54, 0x80, 0x44, 0xd5, - 0x80, 0x50, 0x20, 0x81, 0x60, 0xcf, 0x6d, 0x81, - 0x53, 0x9d, 0x80, 0x97, 0x80, 0x41, 0x57, 0x80, - 0x8b, 0x80, 0x40, 0xf0, 0x80, 0x43, 0x7f, 0x80, - 0x60, 0xb8, 0x33, 0x07, 0x84, 0x6c, 0x2e, 0xac, - 0xdf, + 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, + 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, + 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, + 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, + 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, + 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, + 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, + 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, + 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, + 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, + 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, + 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, + 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, }; static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table32 = { @@ -3638,15 +3957,16 @@ 0x4f, 0x6b, 0x81, }; -static const uint8_t unicode_prop_Other_ID_Continue_table12 = { +static const uint8_t unicode_prop_Other_ID_Continue_table22 = { 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, - 0x88, 0x46, 0x67, 0x80, + 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, + 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, }; -static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table17 = { +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table19 = { 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, - 0x41, 0xd1, 0x80, 0x61, 0x07, 0xd9, 0x80, 0x8e, - 0x80, + 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, + 0x80, 0x8e, 0x80, }; static const uint8_t unicode_prop_XID_Start1_table31 = { @@ -3668,71 +3988,154 @@ 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, }; -static const uint8_t unicode_prop_Changes_When_Casefolded1_table33 = { - 0x40, 0xde, 0x80, 0xcf, 0x80, 0x97, 0x80, 0x44, - 0x3c, 0x80, 0x59, 0x11, 0x80, 0x40, 0xe4, 0x3f, - 0x3f, 0x87, 0x89, 0x11, 0x05, 0x02, 0x11, 0x80, - 0xa9, 0x11, 0x80, 0x60, 0xdb, 0x07, 0x86, 0x8b, - 0x84, +static const uint8_t unicode_prop_Changes_When_Casefolded1_table29 = { + 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, + 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, + 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, + 0x89, 0x10, 0x81, 0x8d, 0x80, }; -static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table441 = { +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table450 = { 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, - 0x10, 0x82, 0x9f, 0x80, 0xcf, 0x01, 0x80, 0x8b, - 0x07, 0x80, 0xfb, 0x01, 0x01, 0x80, 0xa5, 0x80, - 0x40, 0xbb, 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, - 0x81, 0x89, 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, - 0x80, 0xc9, 0x82, 0x9c, 0x80, 0x41, 0x93, 0x80, - 0x40, 0x93, 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, - 0xfb, 0x08, 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, - 0x80, 0x40, 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, - 0x80, 0xa7, 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, - 0x03, 0x03, 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, - 0x26, 0x80, 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, - 0x80, 0x8b, 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, - 0x46, 0x52, 0x81, 0xd4, 0x83, 0x45, 0x1c, 0x10, - 0x8a, 0x80, 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, - 0xa4, 0x40, 0xd9, 0x80, 0x40, 0xd5, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x3f, 0x87, - 0x89, 0x11, 0x04, 0x00, 0x29, 0x04, 0x12, 0x80, - 0x88, 0x12, 0x80, 0x88, 0x11, 0x11, 0x04, 0x08, - 0x8f, 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, - 0x00, 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, - 0x80, 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, - 0x01, 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, - 0x05, 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, - 0x40, 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, - 0x34, 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, - 0x82, 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, - 0x80, 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, - 0xd5, 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, - 0x80, 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, - 0x9e, 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, - 0x60, 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, - 0x86, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, + 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, + 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, + 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, + 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, + 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, + 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, + 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, + 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, + 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, + 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, + 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, + 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, + 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, + 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, + 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, + 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, + 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, + 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, + 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, + 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, + 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, + 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, + 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, + 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, + 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, + 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, + 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, + 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, + 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, + 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, - 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xa5, - 0x86, 0x8b, 0x24, 0x00, 0x97, 0x04, 0x00, 0x01, - 0x01, 0x80, 0xeb, 0xa0, 0x41, 0x6a, 0x91, 0xbf, - 0x81, 0xb5, 0xa7, 0x8c, 0x82, 0x99, 0x95, 0x94, - 0x81, 0x8b, 0x80, 0x92, 0x03, 0x1a, 0x00, 0x80, - 0x40, 0x86, 0x08, 0x80, 0x9f, 0x99, 0x40, 0x83, - 0x15, 0x0d, 0x0d, 0x0a, 0x16, 0x06, 0x80, 0x88, - 0x60, 0xbc, 0xa6, 0x83, 0x54, 0xb9, 0x86, 0x8d, - 0x87, 0xbf, 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, - 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, - 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, - 0x04, 0x00, 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, - 0x23, 0x81, 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, - 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, - 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, - 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, - 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, 0x80, - 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, 0x8c, - 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, 0x60, - 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, 0x4f, - 0xff, + 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, + 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, + 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, + 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, + 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, + 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, + 0x31, 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, + 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, + 0xb1, 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, + 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, + 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, + 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, + 0x04, 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, + 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, + 0x8c, 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, + 0x60, 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, + 0x4f, 0xff, +}; + +static const uint8_t unicode_prop_Basic_Emoji1_table143 = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xe5, 0x80, 0x97, 0x80, 0x93, 0x01, + 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, 0x8b, + 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, 0x1c, + 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, 0x40, + 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, 0x80, + 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, 0xc5, + 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, 0x40, + 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, + 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, + 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Basic_Emoji2_table183 = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x7c, 0x80, 0x40, 0xa5, 0x80, + 0x9c, 0x10, 0x0c, 0x82, 0x40, 0xc6, 0x80, 0x40, + 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, 0xb9, 0x0a, + 0x84, 0x88, 0x01, 0x05, 0x03, 0x01, 0x00, 0x09, + 0x02, 0x02, 0x0f, 0x14, 0x00, 0x80, 0x9b, 0x09, + 0x00, 0x08, 0x80, 0x91, 0x01, 0x80, 0x92, 0x00, + 0x18, 0x00, 0x0a, 0x05, 0x07, 0x81, 0x95, 0x05, + 0x00, 0x00, 0x80, 0x94, 0x05, 0x09, 0x01, 0x17, + 0x04, 0x09, 0x08, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x80, 0x90, 0x81, 0x8e, 0x01, 0x80, 0x9a, 0x81, + 0xbb, 0x80, 0x41, 0x91, 0x81, 0x41, 0xce, 0x82, + 0x45, 0x27, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbe, 0xd5, 0x81, 0x8b, 0x81, 0x40, + 0x81, 0x80, 0xb3, 0x80, 0x40, 0xe8, 0x01, 0x88, + 0x88, 0x80, 0xc5, 0x80, 0x97, 0x08, 0x11, 0x81, + 0xaa, 0x1c, 0x8b, 0x92, 0x00, 0x00, 0x80, 0xc6, + 0x00, 0x80, 0x40, 0xba, 0x80, 0xca, 0x81, 0xa3, + 0x09, 0x86, 0x8c, 0x01, 0x19, 0x80, 0x93, 0x01, + 0x07, 0x81, 0x88, 0x04, 0x82, 0x8b, 0x17, 0x11, + 0x00, 0x03, 0x05, 0x02, 0x05, 0x80, 0x40, 0xcf, + 0x00, 0x82, 0x8f, 0x2a, 0x05, 0x01, 0x80, +}; + +static const uint8_t unicode_prop_RGI_Emoji_Modifier_Sequence_table73 = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x18, 0x18, 0x88, + 0x10, 0x1a, 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, + 0xc8, 0x0b, 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, + 0x12, 0x84, 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, + 0x8a, 0x80, 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, + 0x88, 0x89, 0x11, 0xb7, 0x80, 0xbc, 0x08, 0x08, + 0x80, 0x90, 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, + 0x88, +}; + +static const uint8_t unicode_prop_RGI_Emoji_Flag_Sequence_table128 = { + 0x0c, 0x00, 0x09, 0x00, 0x04, 0x01, 0x02, 0x06, + 0x03, 0x03, 0x01, 0x02, 0x01, 0x03, 0x07, 0x0d, + 0x18, 0x00, 0x09, 0x00, 0x00, 0x89, 0x08, 0x00, + 0x00, 0x81, 0x88, 0x83, 0x8c, 0x10, 0x00, 0x01, + 0x07, 0x08, 0x29, 0x10, 0x28, 0x00, 0x80, 0x8a, + 0x00, 0x0a, 0x00, 0x0e, 0x15, 0x18, 0x83, 0x89, + 0x06, 0x00, 0x81, 0x8d, 0x00, 0x12, 0x08, 0x00, + 0x03, 0x00, 0x24, 0x00, 0x05, 0x21, 0x00, 0x00, + 0x29, 0x90, 0x00, 0x02, 0x00, 0x08, 0x09, 0x00, + 0x08, 0x18, 0x8b, 0x80, 0x8c, 0x02, 0x19, 0x1a, + 0x11, 0x00, 0x00, 0x80, 0x9c, 0x80, 0x88, 0x02, + 0x00, 0x00, 0x02, 0x20, 0x88, 0x0a, 0x00, 0x03, + 0x01, 0x02, 0x05, 0x08, 0x00, 0x01, 0x09, 0x20, + 0x21, 0x18, 0x22, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x28, 0x89, 0x80, 0x8b, 0x80, 0x90, 0x80, 0x92, + 0x80, 0x8d, 0x05, 0x80, 0x8a, 0x80, 0x88, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_Keycap_Sequence_table4 = { + 0xa2, 0x05, 0x04, 0x89, }; static const uint8_t unicode_prop_ASCII_Hex_Digit_table5 = { @@ -3744,14 +4147,15 @@ 0xb6, 0x83, }; -static const uint8_t unicode_prop_Dash_table53 = { +static const uint8_t unicode_prop_Dash_table58 = { 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, - 0x41, 0xda, 0x80, 0x92, 0x80, 0xee, 0x80, 0x60, - 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, 0x80, 0x40, - 0xa8, 0x80, 0x4f, 0x9e, 0x80, + 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, + 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, + 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, + 0x3d, 0x80, }; static const uint8_t unicode_prop_Deprecated_table23 = { @@ -3760,67 +4164,79 @@ 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, }; -static const uint8_t unicode_prop_Diacritic_table358 = { +static const uint8_t unicode_prop_Diacritic_table438 = { 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, - 0x81, 0x40, 0xc8, 0x9b, 0xbc, 0x80, 0x8f, 0x02, - 0x83, 0x9b, 0x80, 0xc9, 0x80, 0x8f, 0x80, 0xed, - 0x80, 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xae, - 0x82, 0xbb, 0x80, 0x8f, 0x06, 0x80, 0xf6, 0x80, - 0xfe, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xec, 0x81, - 0x8f, 0x80, 0xfb, 0x80, 0xfb, 0x28, 0x80, 0xea, - 0x80, 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, - 0x03, 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, - 0x00, 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, - 0x89, 0x81, 0x42, 0xc0, 0x82, 0x44, 0x68, 0x8a, - 0x88, 0x80, 0x41, 0x5a, 0x82, 0x41, 0x38, 0x39, - 0x80, 0xaf, 0x8d, 0xf5, 0x80, 0x8e, 0x80, 0xa5, - 0x88, 0xb5, 0x81, 0x40, 0x89, 0x81, 0xbf, 0x85, - 0xd1, 0x98, 0x18, 0x28, 0x0a, 0xb1, 0xbe, 0xd8, - 0x8b, 0xa4, 0x22, 0x82, 0x41, 0xbc, 0x00, 0x82, - 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, 0x81, - 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, 0x41, 0xf9, - 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, 0x75, 0x71, - 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, 0xd1, 0x81, - 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, 0x40, 0xc9, - 0x80, 0x9a, 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, - 0x80, 0x8b, 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, - 0xc0, 0x83, 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, - 0xff, 0x81, 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, - 0x8f, 0x41, 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, - 0x81, 0xc2, 0x80, 0x42, 0xfb, 0x80, 0x48, 0x03, - 0x81, 0x42, 0x3a, 0x85, 0x42, 0x1d, 0x8a, 0x41, - 0x67, 0x81, 0xf7, 0x81, 0xbd, 0x80, 0xcb, 0x80, - 0x88, 0x82, 0xe7, 0x81, 0x40, 0xb1, 0x81, 0xd0, - 0x80, 0x8f, 0x80, 0x97, 0x32, 0x84, 0x40, 0xcc, + 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, + 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, + 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, + 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, + 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, + 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, + 0x80, 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, + 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, + 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, + 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, + 0x81, 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, + 0x80, 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, + 0x82, 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, + 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, 0x80, 0xa5, + 0x88, 0xb5, 0x81, 0xb9, 0x80, 0x8a, 0x81, 0xc1, + 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, + 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, + 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, + 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, + 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, + 0x75, 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, + 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, + 0x8b, 0x80, 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, + 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, + 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, + 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, + 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, + 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x42, 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, + 0x81, 0x42, 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, + 0x41, 0x8e, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, + 0xbf, 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, + 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, + 0x40, 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, + 0x32, 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, 0x02, 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, 0x41, 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, - 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x60, 0x4d, - 0x57, 0x84, 0xba, 0x86, 0x44, 0x57, 0x90, 0xcf, - 0x81, 0x60, 0x61, 0x74, 0x12, 0x2f, 0x39, 0x86, - 0x9d, 0x83, 0x4f, 0x81, 0x86, 0x41, 0xb4, 0x83, - 0x45, 0xdf, 0x86, 0xec, 0x10, 0x82, + 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x41, 0xa8, + 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, 0x2c, + 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x42, + 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, 0x60, + 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, 0xad, + 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, 0x86, + 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, 0x86, + 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, 0x81, + 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, }; -static const uint8_t unicode_prop_Extender_table89 = { +static const uint8_t unicode_prop_Extender_table111 = { 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, - 0x80, 0x41, 0xb8, 0x80, 0x43, 0x59, 0x80, 0x42, - 0xef, 0x80, 0xfe, 0x80, 0x49, 0x42, 0x80, 0xb7, - 0x80, 0x42, 0x62, 0x80, 0x41, 0x8d, 0x80, 0xc3, - 0x80, 0x53, 0x88, 0x80, 0xaa, 0x84, 0xe6, 0x81, - 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, 0x45, 0xf5, - 0x80, 0x43, 0xc1, 0x80, 0x95, 0x80, 0x40, 0x88, - 0x80, 0xeb, 0x80, 0x94, 0x81, 0x60, 0x54, 0x7a, - 0x80, 0x53, 0xeb, 0x80, 0x42, 0x67, 0x82, 0x44, - 0xce, 0x80, 0x60, 0x50, 0xa8, 0x81, 0x44, 0x9b, - 0x08, 0x80, 0x60, 0x71, 0x57, 0x81, 0x48, 0x05, - 0x82, + 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, + 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, + 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, + 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, + 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, + 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, + 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, + 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, + 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, + 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, + 0xf1, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, + 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, + 0x81, 0x44, 0xb0, 0x80, 0x43, 0x53, 0x82, }; static const uint8_t unicode_prop_Hex_Digit_table12 = { @@ -3828,24 +4244,28 @@ 0x89, 0x35, 0x99, 0x85, }; -static const uint8_t unicode_prop_IDS_Binary_Operator_table5 = { - 0x60, 0x2f, 0xef, 0x09, 0x87, +static const uint8_t unicode_prop_IDS_Unary_Operator_table4 = { + 0x60, 0x2f, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table8 = { + 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, }; static const uint8_t unicode_prop_IDS_Trinary_Operator_table4 = { 0x60, 0x2f, 0xf1, 0x81, }; -static const uint8_t unicode_prop_Ideographic_table66 = { +static const uint8_t unicode_prop_Ideographic_table72 = { 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, - 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xfc, - 0x60, 0x59, 0x02, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, + 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, - 0xd5, 0xa9, 0x88, 0x60, 0x24, 0x66, 0x41, 0x8b, - 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdd, 0xa1, 0x50, - 0x34, 0x8a, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, - 0x5d, 0x30, 0x4c, 0x1e, 0x42, 0x1d, 0x45, 0xe1, - 0x53, 0x4a, + 0xd5, 0xa8, 0x89, 0x60, 0x24, 0x66, 0x41, 0x8b, + 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, + 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, + 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, + 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x50, 0x5f, }; static const uint8_t unicode_prop_Join_Control_table4 = { @@ -3857,6 +4277,11 @@ 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, }; +static const uint8_t unicode_prop_Modifier_Combining_Mark_table16 = { + 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, + 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, +}; + static const uint8_t unicode_prop_Noncharacter_Code_Point_table71 = { 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, @@ -3901,34 +4326,37 @@ 0x61, 0xf1, 0xe5, 0x99, }; -static const uint8_t unicode_prop_Sentence_Terminal_table188 = { +static const uint8_t unicode_prop_Sentence_Terminal_table213 = { 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, - 0x80, 0x40, 0x93, 0x81, 0x40, 0xb3, 0x80, 0xaa, + 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, - 0x40, 0xcb, 0x04, 0x80, 0x41, 0x39, 0x81, 0x41, - 0x61, 0x83, 0x40, 0xad, 0x09, 0x81, 0x40, 0xda, - 0x81, 0xc0, 0x81, 0x43, 0xbb, 0x81, 0x88, 0x82, - 0x4d, 0xe3, 0x80, 0x8c, 0x80, 0x41, 0xc4, 0x80, - 0x60, 0x74, 0xfb, 0x80, 0x41, 0x0d, 0x81, 0x40, - 0xe2, 0x02, 0x80, 0x41, 0x7d, 0x81, 0xd5, 0x81, - 0xde, 0x80, 0x40, 0x97, 0x81, 0x40, 0x92, 0x82, - 0x40, 0x8f, 0x81, 0x40, 0xf8, 0x80, 0x60, 0x52, - 0x65, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, + 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, + 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, + 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, + 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, + 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, + 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, + 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, + 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, + 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, + 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, + 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, - 0xfc, 0x84, 0x40, 0xec, 0x81, 0xf4, 0x83, 0xfe, - 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, - 0x08, 0x81, 0xeb, 0x80, 0x41, 0xa0, 0x81, 0x41, - 0x74, 0x0c, 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82, - 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, 0xd6, - 0x81, 0x41, 0xa3, 0x81, 0x42, 0xb3, 0x81, 0x60, - 0x4b, 0x74, 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, - 0x8a, 0x80, 0x43, 0x52, 0x80, 0x60, 0x4e, 0x05, - 0x80, 0x5d, 0xe7, 0x80, + 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, + 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, + 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, + 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, + 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, + 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, + 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, + 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, + 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, + 0x05, 0x80, 0x5d, 0xe7, 0x80, }; -static const uint8_t unicode_prop_Soft_Dotted_table71 = { +static const uint8_t unicode_prop_Soft_Dotted_table79 = { 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, @@ -3937,55 +4365,58 @@ 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, + 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, }; -static const uint8_t unicode_prop_Terminal_Punctuation_table241 = { +static const uint8_t unicode_prop_Terminal_Punctuation_table264 = { 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, - 0x80, 0xc7, 0x80, 0x8d, 0x01, 0x81, 0x40, 0xb3, + 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, - 0x8e, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, 0xf3, - 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, 0x81, - 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, 0x82, - 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, 0x19, - 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, 0x40, - 0xad, 0x08, 0x82, 0x40, 0xda, 0x84, 0xbd, 0x81, - 0x43, 0xbb, 0x81, 0x88, 0x82, 0x4d, 0xe3, 0x80, - 0x8c, 0x03, 0x80, 0x89, 0x00, 0x81, 0x41, 0xb0, - 0x81, 0x60, 0x74, 0xfa, 0x81, 0x41, 0x0c, 0x82, - 0x40, 0xe2, 0x84, 0x41, 0x7d, 0x81, 0xd5, 0x81, - 0xde, 0x80, 0x40, 0x96, 0x82, 0x40, 0x92, 0x82, - 0xfe, 0x80, 0x8f, 0x81, 0x40, 0xf8, 0x80, 0x60, - 0x52, 0x63, 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, - 0x00, 0x80, 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, - 0x44, 0x39, 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, - 0x40, 0xc6, 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, - 0x85, 0xc3, 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, - 0x40, 0xec, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, + 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, + 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, + 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, + 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, + 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, + 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, + 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, + 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, + 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, + 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, + 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, + 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, + 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, + 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, + 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, + 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, + 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, + 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, + 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, - 0x80, 0x41, 0xa0, 0x82, 0x8b, 0x81, 0x41, 0x65, - 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82, 0x42, - 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, 0xd6, 0x0b, - 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, 0x42, 0x84, - 0x81, 0x45, 0x76, 0x84, 0x60, 0x45, 0xf8, 0x81, - 0x40, 0x84, 0x80, 0xc0, 0x82, 0x89, 0x80, 0x43, - 0x51, 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, - 0x83, + 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, + 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, + 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, + 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, + 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, + 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, + 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, + 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, }; -static const uint8_t unicode_prop_Unified_Ideograph_table42 = { +static const uint8_t unicode_prop_Unified_Ideograph_table48 = { 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, - 0xfc, 0x60, 0x5a, 0x10, 0x08, 0x00, 0x81, 0x89, + 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, - 0xa6, 0xdd, 0xa1, 0x50, 0x34, 0x8a, 0x40, 0xdd, - 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x54, 0x1e, - 0x53, 0x4a, + 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, + 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, 0x42, + 0x6d, 0x51, 0xa1, 0x53, 0x4a, 0x84, 0x50, 0x5f, }; -static const uint8_t unicode_prop_Variation_Selector_table12 = { - 0x58, 0x0a, 0x82, 0x60, 0xe5, 0xf1, 0x8f, 0x6d, - 0x02, 0xef, 0x40, 0xef, +static const uint8_t unicode_prop_Variation_Selector_table13 = { + 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, + 0x6d, 0x02, 0xef, 0x40, 0xef, }; static const uint8_t unicode_prop_White_Space_table22 = { @@ -3994,14 +4425,14 @@ 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, }; -static const uint8_t unicode_prop_Bidi_Mirrored_table171 = { +static const uint8_t unicode_prop_Bidi_Mirrored_table173 = { 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, - 0x88, 0x08, 0x00, 0x39, 0x9e, 0x0b, 0x20, 0x88, + 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, @@ -4012,11 +4443,11 @@ 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, - 0x41, 0xdd, 0x89, 0x0f, 0x60, 0xce, 0x3c, 0x2c, - 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, 0x80, 0x9b, - 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, 0x81, 0x60, - 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, 0x80, 0xb8, - 0x80, 0xb8, 0x80, + 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, + 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, + 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, + 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, 0xb8, 0x80, }; static const uint8_t unicode_prop_Emoji_table238 = { @@ -4045,11 +4476,11 @@ 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, - 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x3d, - 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, 0x8b, - 0x41, 0x1f, 0xae, 0x80, 0x89, 0x80, 0xb1, 0x80, - 0xd1, 0x80, 0xb2, 0xef, 0x22, 0x14, 0x86, 0x88, - 0x98, 0x36, 0x88, 0x82, 0x8c, 0x86, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, + 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, + 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, + 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, 0xb7, + 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, }; static const uint8_t unicode_prop_Emoji_Component_table28 = { @@ -4063,7 +4494,7 @@ 0x61, 0xf3, 0xfa, 0x84, }; -static const uint8_t unicode_prop_Emoji_Modifier_Base_table66 = { +static const uint8_t unicode_prop_Emoji_Modifier_Base_table71 = { 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, @@ -4072,7 +4503,7 @@ 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, - 0x10, 0x8c, + 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, }; static const uint8_t unicode_prop_Emoji_Presentation_table144 = { @@ -4090,10 +4521,10 @@ 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, - 0xc5, 0x28, 0x12, 0x0a, 0x92, 0x0e, 0x88, 0x40, - 0xe2, 0x8b, 0x41, 0x1f, 0xae, 0x80, 0x89, 0x80, - 0xb1, 0x80, 0xd1, 0x80, 0xb2, 0xef, 0x22, 0x14, - 0x86, 0x88, 0x98, 0x36, 0x88, 0x82, 0x8c, 0x86, + 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, + 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, + 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, + 0x84, 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, }; static const uint8_t unicode_prop_Extended_Pictographic_table156 = { @@ -4122,7 +4553,7 @@ static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table51 = { 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, - 0x83, 0x47, 0xfb, 0x84, 0x99, 0x84, 0xb0, 0x8f, + 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, @@ -4146,6 +4577,11 @@ UNICODE_PROP_Changes_When_Titlecased1, UNICODE_PROP_Changes_When_Casefolded1, UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_Basic_Emoji1, + UNICODE_PROP_Basic_Emoji2, + UNICODE_PROP_RGI_Emoji_Modifier_Sequence, + UNICODE_PROP_RGI_Emoji_Flag_Sequence, + UNICODE_PROP_Emoji_Keycap_Sequence, UNICODE_PROP_ASCII_Hex_Digit, UNICODE_PROP_Bidi_Control, UNICODE_PROP_Dash, @@ -4153,11 +4589,13 @@ UNICODE_PROP_Diacritic, UNICODE_PROP_Extender, UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Unary_Operator, UNICODE_PROP_IDS_Binary_Operator, UNICODE_PROP_IDS_Trinary_Operator, UNICODE_PROP_Ideographic, UNICODE_PROP_Join_Control, UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Modifier_Combining_Mark, UNICODE_PROP_Noncharacter_Code_Point, UNICODE_PROP_Pattern_Syntax, UNICODE_PROP_Pattern_White_Space, @@ -4194,6 +4632,9 @@ UNICODE_PROP_Grapheme_Base, UNICODE_PROP_Grapheme_Extend, UNICODE_PROP_ID_Continue, + UNICODE_PROP_ID_Compat_Math_Start, + UNICODE_PROP_ID_Compat_Math_Continue, + UNICODE_PROP_InCB, UNICODE_PROP_Lowercase, UNICODE_PROP_Math, UNICODE_PROP_Uppercase, @@ -4211,11 +4652,13 @@ "Diacritic,Dia" "\0" "Extender,Ext" "\0" "Hex_Digit,Hex" "\0" + "IDS_Unary_Operator,IDSU" "\0" "IDS_Binary_Operator,IDSB" "\0" "IDS_Trinary_Operator,IDST" "\0" "Ideographic,Ideo" "\0" "Join_Control,Join_C" "\0" "Logical_Order_Exception,LOE" "\0" + "Modifier_Combining_Mark,MCM" "\0" "Noncharacter_Code_Point,NChar" "\0" "Pattern_Syntax,Pat_Syn" "\0" "Pattern_White_Space,Pat_WS" "\0" @@ -4252,6 +4695,9 @@ "Grapheme_Base,Gr_Base" "\0" "Grapheme_Extend,Gr_Ext" "\0" "ID_Continue,IDC" "\0" + "ID_Compat_Math_Start" "\0" + "ID_Compat_Math_Continue" "\0" + "InCB" "\0" "Lowercase,Lower" "\0" "Math" "\0" "Uppercase,Upper" "\0" @@ -4276,6 +4722,11 @@ unicode_prop_Changes_When_Titlecased1_table, unicode_prop_Changes_When_Casefolded1_table, unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_Basic_Emoji1_table, + unicode_prop_Basic_Emoji2_table, + unicode_prop_RGI_Emoji_Modifier_Sequence_table, + unicode_prop_RGI_Emoji_Flag_Sequence_table, + unicode_prop_Emoji_Keycap_Sequence_table, unicode_prop_ASCII_Hex_Digit_table, unicode_prop_Bidi_Control_table, unicode_prop_Dash_table, @@ -4283,11 +4734,13 @@ unicode_prop_Diacritic_table, unicode_prop_Extender_table, unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Unary_Operator_table, unicode_prop_IDS_Binary_Operator_table, unicode_prop_IDS_Trinary_Operator_table, unicode_prop_Ideographic_table, unicode_prop_Join_Control_table, unicode_prop_Logical_Order_Exception_table, + unicode_prop_Modifier_Combining_Mark_table, unicode_prop_Noncharacter_Code_Point_table, unicode_prop_Pattern_Syntax_table, unicode_prop_Pattern_White_Space_table, @@ -4329,6 +4782,11 @@ countof(unicode_prop_Changes_When_Titlecased1_table), countof(unicode_prop_Changes_When_Casefolded1_table), countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_Basic_Emoji1_table), + countof(unicode_prop_Basic_Emoji2_table), + countof(unicode_prop_RGI_Emoji_Modifier_Sequence_table), + countof(unicode_prop_RGI_Emoji_Flag_Sequence_table), + countof(unicode_prop_Emoji_Keycap_Sequence_table), countof(unicode_prop_ASCII_Hex_Digit_table), countof(unicode_prop_Bidi_Control_table), countof(unicode_prop_Dash_table), @@ -4336,11 +4794,13 @@ countof(unicode_prop_Diacritic_table), countof(unicode_prop_Extender_table), countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Unary_Operator_table), countof(unicode_prop_IDS_Binary_Operator_table), countof(unicode_prop_IDS_Trinary_Operator_table), countof(unicode_prop_Ideographic_table), countof(unicode_prop_Join_Control_table), countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Modifier_Combining_Mark_table), countof(unicode_prop_Noncharacter_Code_Point_table), countof(unicode_prop_Pattern_Syntax_table), countof(unicode_prop_Pattern_White_Space_table), @@ -4365,4 +4825,325 @@ countof(unicode_prop_Case_Ignorable_table), }; +typedef enum { + UNICODE_SEQUENCE_PROP_Basic_Emoji, + UNICODE_SEQUENCE_PROP_Emoji_Keycap_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Flag_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Tag_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji, + UNICODE_SEQUENCE_PROP_COUNT, +} UnicodeSequencePropertyEnum; + +static const char unicode_sequence_prop_name_table = + "Basic_Emoji" "\0" + "Emoji_Keycap_Sequence" "\0" + "RGI_Emoji_Modifier_Sequence" "\0" + "RGI_Emoji_Flag_Sequence" "\0" + "RGI_Emoji_Tag_Sequence" "\0" + "RGI_Emoji_ZWJ_Sequence" "\0" + "RGI_Emoji" "\0" +; + +static const uint8_t unicode_rgi_emoji_tag_sequence18 = { + 0x67, 0x62, 0x65, 0x6e, 0x67, 0x00, 0x67, 0x62, + 0x73, 0x63, 0x74, 0x00, 0x67, 0x62, 0x77, 0x6c, + 0x73, 0x00, +}; + +static const uint8_t unicode_rgi_emoji_zwj_sequence2320 = { + 0x02, 0xb8, 0x19, 0x40, 0x86, 0x02, 0xd1, 0x39, + 0xb0, 0x19, 0x02, 0x26, 0x39, 0x42, 0x86, 0x02, + 0xb4, 0x36, 0x42, 0x86, 0x03, 0x68, 0x54, 0x64, + 0x87, 0x68, 0x54, 0x02, 0xdc, 0x39, 0x42, 0x86, + 0x02, 0xd1, 0x39, 0x73, 0x13, 0x02, 0x39, 0x39, + 0x40, 0x86, 0x02, 0x69, 0x34, 0xbd, 0x19, 0x03, + 0xb6, 0x36, 0x40, 0x86, 0xa1, 0x87, 0x03, 0x68, + 0x74, 0x1d, 0x19, 0x68, 0x74, 0x03, 0x68, 0x34, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xf1, 0x7a, 0xf2, + 0x7a, 0x02, 0xca, 0x33, 0x42, 0x86, 0x02, 0x69, + 0x34, 0xb0, 0x19, 0x04, 0x68, 0x14, 0x68, 0x14, + 0x67, 0x14, 0x66, 0x14, 0x02, 0xf9, 0x26, 0x42, + 0x86, 0x03, 0x69, 0x74, 0x1d, 0x19, 0x69, 0x74, + 0x03, 0xd1, 0x19, 0xbc, 0x19, 0xa1, 0x87, 0x02, + 0x3c, 0x19, 0x40, 0x86, 0x02, 0x68, 0x34, 0xeb, + 0x13, 0x02, 0xc3, 0x33, 0xa1, 0x87, 0x02, 0x70, + 0x34, 0x40, 0x86, 0x02, 0xd4, 0x39, 0x42, 0x86, + 0x02, 0xcf, 0x39, 0x42, 0x86, 0x02, 0x47, 0x36, + 0x40, 0x86, 0x02, 0x39, 0x39, 0x42, 0x86, 0x04, + 0xd1, 0x79, 0x64, 0x87, 0x8b, 0x14, 0xd1, 0x79, + 0x02, 0xd1, 0x39, 0x95, 0x86, 0x02, 0x68, 0x34, + 0x93, 0x13, 0x02, 0x69, 0x34, 0xed, 0x13, 0x02, + 0xda, 0x39, 0x40, 0x86, 0x03, 0x69, 0x34, 0xaf, + 0x19, 0xa1, 0x87, 0x02, 0xd1, 0x39, 0x93, 0x13, + 0x03, 0xce, 0x39, 0x42, 0x86, 0xa1, 0x87, 0x03, + 0xd1, 0x79, 0x64, 0x87, 0xd1, 0x79, 0x03, 0xc3, + 0x33, 0x42, 0x86, 0xa1, 0x87, 0x03, 0x69, 0x74, + 0x1d, 0x19, 0x68, 0x74, 0x02, 0x69, 0x34, 0x92, + 0x16, 0x02, 0xd1, 0x39, 0x96, 0x86, 0x04, 0x69, + 0x14, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x14, 0x02, + 0x68, 0x34, 0x7c, 0x13, 0x02, 0x47, 0x36, 0x42, + 0x86, 0x02, 0x86, 0x34, 0x42, 0x86, 0x02, 0xd1, + 0x39, 0x7c, 0x13, 0x02, 0x69, 0x14, 0xa4, 0x13, + 0x02, 0xda, 0x39, 0x42, 0x86, 0x02, 0x37, 0x39, + 0x40, 0x86, 0x02, 0xd1, 0x39, 0x08, 0x87, 0x04, + 0x68, 0x54, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x54, + 0x02, 0x4d, 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, + 0x2c, 0x15, 0x02, 0x69, 0x34, 0xaf, 0x19, 0x02, + 0x6e, 0x34, 0x40, 0x86, 0x02, 0xcd, 0x39, 0x42, + 0x86, 0x02, 0xd1, 0x39, 0x2c, 0x15, 0x02, 0x6f, + 0x14, 0x40, 0x86, 0x03, 0xd1, 0x39, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0x68, 0x34, 0xa8, 0x13, 0x02, + 0x69, 0x34, 0x73, 0x13, 0x04, 0x69, 0x54, 0x64, + 0x87, 0x8b, 0x14, 0x68, 0x54, 0x02, 0x71, 0x34, + 0x42, 0x86, 0x02, 0xd1, 0x39, 0xa8, 0x13, 0x02, + 0x45, 0x36, 0x40, 0x86, 0x03, 0x69, 0x54, 0x64, + 0x87, 0x68, 0x54, 0x03, 0x69, 0x54, 0x64, 0x87, + 0x69, 0x54, 0x03, 0xce, 0x39, 0x40, 0x86, 0xa1, + 0x87, 0x02, 0xd8, 0x39, 0x40, 0x86, 0x03, 0xc3, + 0x33, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x4d, 0x36, + 0x42, 0x86, 0x02, 0xd1, 0x19, 0x92, 0x16, 0x02, + 0xd1, 0x39, 0xeb, 0x13, 0x02, 0x68, 0x34, 0xbc, + 0x14, 0x02, 0xd1, 0x39, 0xbc, 0x14, 0x02, 0x3d, + 0x39, 0x40, 0x86, 0x02, 0xb8, 0x39, 0x42, 0x86, + 0x02, 0xa3, 0x36, 0x40, 0x86, 0x02, 0x75, 0x35, + 0x40, 0x86, 0x02, 0xd8, 0x39, 0x42, 0x86, 0x02, + 0x69, 0x34, 0x93, 0x13, 0x02, 0x35, 0x39, 0x40, + 0x86, 0x02, 0x4b, 0x36, 0x40, 0x86, 0x02, 0x3d, + 0x39, 0x42, 0x86, 0x02, 0x38, 0x39, 0x42, 0x86, + 0x02, 0xa3, 0x36, 0x42, 0x86, 0x03, 0x69, 0x14, + 0x67, 0x14, 0x67, 0x14, 0x02, 0xb6, 0x36, 0x40, + 0x86, 0x02, 0x69, 0x34, 0x7c, 0x13, 0x02, 0x75, + 0x35, 0x42, 0x86, 0x02, 0xcc, 0x93, 0x40, 0x86, + 0x02, 0xcc, 0x33, 0x40, 0x86, 0x03, 0xd1, 0x39, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0x82, 0x34, 0x40, + 0x86, 0x02, 0x87, 0x34, 0x40, 0x86, 0x02, 0x69, + 0x14, 0x3e, 0x13, 0x02, 0xd6, 0x39, 0x40, 0x86, + 0x02, 0x68, 0x14, 0xbd, 0x19, 0x02, 0x46, 0x36, + 0x42, 0x86, 0x02, 0x4b, 0x36, 0x42, 0x86, 0x02, + 0x69, 0x34, 0x2c, 0x15, 0x03, 0xb6, 0x36, 0x42, + 0x86, 0xa1, 0x87, 0x02, 0xc4, 0x33, 0x40, 0x86, + 0x02, 0x26, 0x19, 0x40, 0x86, 0x02, 0x69, 0x14, + 0xb0, 0x19, 0x02, 0xde, 0x19, 0x42, 0x86, 0x02, + 0x69, 0x34, 0xa8, 0x13, 0x02, 0xcc, 0x33, 0x42, + 0x86, 0x02, 0x82, 0x34, 0x42, 0x86, 0x02, 0xd1, + 0x19, 0x93, 0x13, 0x02, 0x81, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x34, 0x95, 0x86, 0x02, 0x68, 0x34, + 0xbb, 0x14, 0x02, 0xd1, 0x39, 0xbb, 0x14, 0x02, + 0x69, 0x34, 0xeb, 0x13, 0x02, 0xd1, 0x39, 0x84, + 0x13, 0x02, 0x69, 0x34, 0xbc, 0x14, 0x04, 0x69, + 0x54, 0x64, 0x87, 0x8b, 0x14, 0x69, 0x54, 0x02, + 0x26, 0x39, 0x40, 0x86, 0x02, 0xb4, 0x36, 0x40, + 0x86, 0x02, 0x47, 0x16, 0x42, 0x86, 0x02, 0xdc, + 0x39, 0x40, 0x86, 0x02, 0xca, 0x33, 0x40, 0x86, + 0x02, 0xf9, 0x26, 0x40, 0x86, 0x02, 0x69, 0x34, + 0x08, 0x87, 0x03, 0x69, 0x14, 0x69, 0x14, 0x66, + 0x14, 0x03, 0xd1, 0x59, 0x1d, 0x19, 0xd1, 0x59, + 0x02, 0xd4, 0x39, 0x40, 0x86, 0x02, 0xcf, 0x39, + 0x40, 0x86, 0x02, 0x68, 0x34, 0xa4, 0x13, 0x02, + 0xd1, 0x39, 0xa4, 0x13, 0x02, 0xd1, 0x19, 0xa8, + 0x13, 0x02, 0xd7, 0x39, 0x42, 0x86, 0x03, 0x69, + 0x34, 0xbc, 0x19, 0xa1, 0x87, 0x02, 0x68, 0x14, + 0xb0, 0x19, 0x02, 0x68, 0x14, 0x73, 0x13, 0x04, + 0x69, 0x14, 0x69, 0x14, 0x66, 0x14, 0x66, 0x14, + 0x03, 0x68, 0x34, 0xaf, 0x19, 0xa1, 0x87, 0x02, + 0x68, 0x34, 0x80, 0x16, 0x02, 0x73, 0x34, 0x42, + 0x86, 0x02, 0xd1, 0x39, 0x80, 0x16, 0x02, 0x68, + 0x34, 0xb0, 0x19, 0x02, 0x86, 0x34, 0x40, 0x86, + 0x02, 0x38, 0x19, 0x42, 0x86, 0x02, 0x69, 0x34, + 0xbb, 0x14, 0x02, 0xb5, 0x36, 0x42, 0x86, 0x02, + 0xcd, 0x39, 0x40, 0x86, 0x02, 0x68, 0x34, 0x95, + 0x86, 0x02, 0x68, 0x34, 0x27, 0x15, 0x03, 0x68, + 0x14, 0x68, 0x14, 0x66, 0x14, 0x02, 0x71, 0x34, + 0x40, 0x86, 0x02, 0xd1, 0x39, 0x27, 0x15, 0x02, + 0x2e, 0x16, 0xa8, 0x14, 0x02, 0xc3, 0x33, 0x42, + 0x86, 0x02, 0x69, 0x14, 0x66, 0x14, 0x02, 0x68, + 0x34, 0x96, 0x86, 0x02, 0x69, 0x34, 0xa4, 0x13, + 0x03, 0x69, 0x14, 0x64, 0x87, 0x68, 0x14, 0x02, + 0xb8, 0x39, 0x40, 0x86, 0x02, 0x68, 0x34, 0x3e, + 0x13, 0x03, 0xd1, 0x19, 0xaf, 0x19, 0xa1, 0x87, + 0x02, 0xd1, 0x39, 0x3e, 0x13, 0x02, 0x68, 0x34, + 0xbd, 0x19, 0x02, 0xd1, 0x19, 0xbb, 0x14, 0x02, + 0xd1, 0x19, 0x95, 0x86, 0x02, 0xdb, 0x39, 0x42, + 0x86, 0x02, 0x38, 0x39, 0x40, 0x86, 0x02, 0x69, + 0x34, 0x80, 0x16, 0x02, 0x69, 0x14, 0xeb, 0x13, + 0x04, 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, 0x67, + 0x14, 0x02, 0x77, 0x34, 0x42, 0x86, 0x02, 0x46, + 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, 0x92, 0x16, + 0x02, 0x4e, 0x36, 0x42, 0x86, 0x03, 0x69, 0x14, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xde, 0x19, 0x40, + 0x86, 0x02, 0x69, 0x34, 0x27, 0x15, 0x03, 0xc3, + 0x13, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x81, 0x14, + 0x40, 0x86, 0x03, 0xd1, 0x39, 0xaf, 0x19, 0xa1, + 0x87, 0x02, 0x68, 0x34, 0xbc, 0x19, 0x02, 0xd1, + 0x19, 0x80, 0x16, 0x02, 0xd9, 0x39, 0x42, 0x86, + 0x02, 0xd1, 0x39, 0xbc, 0x19, 0x02, 0xdc, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x34, 0x73, 0x13, 0x02, + 0x69, 0x34, 0x3e, 0x13, 0x02, 0x47, 0x16, 0x40, + 0x86, 0x02, 0xd1, 0x39, 0xbd, 0x19, 0x02, 0x3e, + 0x39, 0x42, 0x86, 0x02, 0x69, 0x14, 0x95, 0x86, + 0x02, 0x68, 0x14, 0x96, 0x86, 0x03, 0x69, 0x34, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xd7, 0x39, 0x40, + 0x86, 0x02, 0x45, 0x16, 0x42, 0x86, 0x02, 0x68, + 0x34, 0xed, 0x13, 0x03, 0x68, 0x34, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0xd1, 0x39, 0xed, 0x13, 0x02, + 0xd1, 0x39, 0x92, 0x16, 0x02, 0x73, 0x34, 0x40, + 0x86, 0x02, 0x38, 0x19, 0x40, 0x86, 0x02, 0xb5, + 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, 0xaf, 0x19, + 0x02, 0xd1, 0x39, 0xaf, 0x19, 0x02, 0x69, 0x34, + 0xbc, 0x19, 0x02, 0xb6, 0x16, 0x42, 0x86, 0x02, + 0x26, 0x14, 0x25, 0x15, 0x02, 0xc3, 0x33, 0x40, + 0x86, 0x02, 0xdd, 0x39, 0x42, 0x86, 0x02, 0xcb, + 0x93, 0x42, 0x86, 0x02, 0xcb, 0x33, 0x42, 0x86, + 0x02, 0x81, 0x34, 0x42, 0x86, 0x02, 0xce, 0x39, + 0xa1, 0x87, 0x02, 0xdb, 0x39, 0x40, 0x86, 0x02, + 0x68, 0x34, 0x08, 0x87, 0x02, 0xd1, 0x19, 0xb0, + 0x19, 0x02, 0x77, 0x34, 0x40, 0x86, 0x02, 0x4e, + 0x36, 0x40, 0x86, 0x02, 0xce, 0x39, 0x42, 0x86, + 0x02, 0x4e, 0x16, 0x42, 0x86, 0x02, 0xd9, 0x39, + 0x40, 0x86, 0x02, 0xdc, 0x19, 0x40, 0x86, 0x02, + 0x3e, 0x39, 0x40, 0x86, 0x02, 0xb9, 0x39, 0x42, + 0x86, 0x02, 0xda, 0x19, 0x42, 0x86, 0x02, 0x42, + 0x16, 0x94, 0x81, 0x02, 0x45, 0x16, 0x40, 0x86, + 0x02, 0x69, 0x14, 0xbd, 0x19, 0x02, 0x70, 0x34, + 0x42, 0x86, 0x02, 0xce, 0x19, 0xa1, 0x87, 0x02, + 0xc3, 0x13, 0x42, 0x86, 0x02, 0x68, 0x14, 0x08, + 0x87, 0x02, 0xd1, 0x19, 0x7c, 0x13, 0x02, 0x68, + 0x14, 0x92, 0x16, 0x02, 0xb6, 0x16, 0x40, 0x86, + 0x02, 0x37, 0x39, 0x42, 0x86, 0x03, 0xce, 0x19, + 0x42, 0x86, 0xa1, 0x87, 0x03, 0x68, 0x14, 0x67, + 0x14, 0x67, 0x14, 0x02, 0xdd, 0x39, 0x40, 0x86, + 0x02, 0xcf, 0x19, 0x42, 0x86, 0x02, 0xd1, 0x19, + 0x2c, 0x15, 0x02, 0x4b, 0x13, 0xe9, 0x17, 0x02, + 0x68, 0x14, 0x67, 0x14, 0x02, 0xcb, 0x93, 0x40, + 0x86, 0x02, 0x6e, 0x34, 0x42, 0x86, 0x02, 0xcb, + 0x33, 0x40, 0x86, 0x02, 0x81, 0x34, 0x40, 0x86, + 0x02, 0xb6, 0x36, 0xa1, 0x87, 0x02, 0x45, 0x36, + 0x42, 0x86, 0x02, 0xb4, 0x16, 0x42, 0x86, 0x02, + 0x69, 0x14, 0x73, 0x13, 0x04, 0x69, 0x14, 0x69, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x02, 0x35, 0x39, + 0x42, 0x86, 0x02, 0x68, 0x14, 0x93, 0x13, 0x02, + 0xb6, 0x36, 0x42, 0x86, 0x03, 0x68, 0x14, 0x69, + 0x14, 0x66, 0x14, 0x02, 0xce, 0x39, 0x40, 0x86, + 0x02, 0x4e, 0x16, 0x40, 0x86, 0x02, 0x87, 0x34, + 0x42, 0x86, 0x02, 0x86, 0x14, 0x42, 0x86, 0x02, + 0xd6, 0x39, 0x42, 0x86, 0x02, 0xc4, 0x33, 0x42, + 0x86, 0x02, 0x69, 0x34, 0x96, 0x86, 0x02, 0xb9, + 0x39, 0x40, 0x86, 0x02, 0x68, 0x14, 0xa8, 0x13, + 0x02, 0xd1, 0x19, 0x84, 0x13, 0x02, 0xda, 0x19, + 0x40, 0x86, 0x02, 0xd8, 0x19, 0x42, 0x86, 0x02, + 0xc3, 0x13, 0x40, 0x86, 0x02, 0xb9, 0x19, 0x42, + 0x86, 0x02, 0x3d, 0x19, 0x42, 0x86, 0x02, 0xcf, + 0x19, 0x40, 0x86, 0x04, 0x68, 0x14, 0x68, 0x14, + 0x67, 0x14, 0x67, 0x14, 0x03, 0xd1, 0x19, 0xd1, + 0x19, 0xd2, 0x19, 0x02, 0x68, 0x14, 0xbb, 0x14, + 0x02, 0x3b, 0x14, 0x44, 0x87, 0x02, 0xd1, 0x19, + 0x27, 0x15, 0x02, 0xb4, 0x16, 0x40, 0x86, 0x02, + 0xcd, 0x19, 0x42, 0x86, 0x02, 0xd3, 0x86, 0xa5, + 0x14, 0x02, 0x70, 0x14, 0x42, 0x86, 0x03, 0xb6, + 0x16, 0x42, 0x86, 0xa1, 0x87, 0x04, 0x69, 0x14, + 0x64, 0x87, 0x8b, 0x14, 0x69, 0x14, 0x02, 0x36, + 0x16, 0x2b, 0x93, 0x02, 0x68, 0x14, 0x80, 0x16, + 0x02, 0x86, 0x14, 0x40, 0x86, 0x02, 0x08, 0x14, + 0x1b, 0x0b, 0x02, 0xd1, 0x19, 0xbc, 0x19, 0x02, + 0xca, 0x13, 0x42, 0x86, 0x02, 0x41, 0x94, 0xe8, + 0x95, 0x02, 0xd8, 0x19, 0x40, 0x86, 0x02, 0xb9, + 0x19, 0x40, 0x86, 0x02, 0xd1, 0x19, 0xed, 0x13, + 0x02, 0xf9, 0x86, 0x42, 0x86, 0x03, 0xd1, 0x19, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0x3d, 0x19, 0x40, + 0x86, 0x02, 0xd6, 0x19, 0x42, 0x86, 0x03, 0x69, + 0x14, 0x66, 0x14, 0x66, 0x14, 0x02, 0xd1, 0x19, + 0xaf, 0x19, 0x03, 0x69, 0x14, 0x69, 0x14, 0x67, + 0x14, 0x02, 0xcd, 0x19, 0x40, 0x86, 0x02, 0x70, + 0x14, 0x40, 0x86, 0x03, 0x68, 0x14, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0x6e, 0x14, 0x42, 0x86, 0x02, + 0x69, 0x14, 0x92, 0x16, 0x03, 0x68, 0x14, 0x68, + 0x14, 0x67, 0x14, 0x02, 0x69, 0x14, 0x67, 0x14, + 0x02, 0x75, 0x95, 0x42, 0x86, 0x03, 0x69, 0x14, + 0x64, 0x87, 0x69, 0x14, 0x02, 0xd1, 0x19, 0xbc, + 0x14, 0x02, 0xdf, 0x19, 0x42, 0x86, 0x02, 0xca, + 0x13, 0x40, 0x86, 0x02, 0x82, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x93, 0x13, 0x02, 0x68, 0x14, + 0x7c, 0x13, 0x02, 0xf9, 0x86, 0x40, 0x86, 0x02, + 0xd6, 0x19, 0x40, 0x86, 0x02, 0x68, 0x14, 0x2c, + 0x15, 0x02, 0x69, 0x14, 0xa8, 0x13, 0x02, 0xd4, + 0x19, 0x42, 0x86, 0x04, 0x68, 0x14, 0x69, 0x14, + 0x66, 0x14, 0x66, 0x14, 0x02, 0x77, 0x14, 0x42, + 0x86, 0x02, 0x39, 0x19, 0x42, 0x86, 0x02, 0xd1, + 0x19, 0xa4, 0x13, 0x02, 0x6e, 0x14, 0x40, 0x86, + 0x03, 0xd1, 0x19, 0xd2, 0x19, 0xd2, 0x19, 0x02, + 0x69, 0x14, 0xbb, 0x14, 0x02, 0xd1, 0x19, 0x96, + 0x86, 0x02, 0x75, 0x95, 0x40, 0x86, 0x04, 0x68, + 0x14, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x14, 0x02, + 0xd1, 0x19, 0x3e, 0x13, 0x02, 0xdf, 0x19, 0x40, + 0x86, 0x02, 0x82, 0x14, 0x40, 0x86, 0x02, 0x44, + 0x13, 0xeb, 0x17, 0x02, 0xdd, 0x19, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x80, 0x16, 0x03, 0x68, 0x14, + 0xaf, 0x19, 0xa1, 0x87, 0x02, 0xa3, 0x16, 0x42, + 0x86, 0x02, 0x69, 0x14, 0x96, 0x86, 0x02, 0x46, + 0x16, 0x42, 0x86, 0x02, 0xb6, 0x16, 0xa1, 0x87, + 0x02, 0x68, 0x14, 0x27, 0x15, 0x02, 0x26, 0x14, + 0x1b, 0x0b, 0x02, 0xd4, 0x19, 0x40, 0x86, 0x02, + 0x77, 0x14, 0x40, 0x86, 0x02, 0x39, 0x19, 0x40, + 0x86, 0x02, 0x37, 0x19, 0x42, 0x86, 0x03, 0x69, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x03, 0xc3, 0x13, + 0x42, 0x86, 0xa1, 0x87, 0x02, 0x68, 0x14, 0xbc, + 0x19, 0x02, 0xd1, 0x19, 0xeb, 0x13, 0x04, 0x69, + 0x14, 0x69, 0x14, 0x67, 0x14, 0x67, 0x14, 0x02, + 0xd1, 0x19, 0x08, 0x87, 0x02, 0x68, 0x14, 0xed, + 0x13, 0x03, 0x69, 0x14, 0xbc, 0x19, 0xa1, 0x87, + 0x02, 0xdd, 0x19, 0x40, 0x86, 0x02, 0xc3, 0x13, + 0xa1, 0x87, 0x03, 0x68, 0x14, 0x66, 0x14, 0x66, + 0x14, 0x03, 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, + 0x02, 0xa3, 0x16, 0x40, 0x86, 0x02, 0xdb, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x14, 0xaf, 0x19, 0x02, + 0x46, 0x16, 0x40, 0x86, 0x02, 0x35, 0x16, 0xab, + 0x14, 0x02, 0x68, 0x14, 0x95, 0x86, 0x02, 0x42, + 0x16, 0x95, 0x81, 0x02, 0xc4, 0x13, 0x42, 0x86, + 0x02, 0x15, 0x14, 0xba, 0x19, 0x02, 0x69, 0x14, + 0x08, 0x87, 0x03, 0xd1, 0x19, 0x1d, 0x19, 0xd1, + 0x19, 0x02, 0x69, 0x14, 0x7c, 0x13, 0x02, 0x37, + 0x19, 0x40, 0x86, 0x02, 0x73, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x2c, 0x15, 0x02, 0xb5, 0x16, + 0x42, 0x86, 0x02, 0x35, 0x19, 0x42, 0x86, 0x04, + 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, 0x66, 0x14, + 0x02, 0x64, 0x87, 0x25, 0x15, 0x02, 0x64, 0x87, + 0x79, 0x1a, 0x02, 0x68, 0x14, 0xbc, 0x14, 0x03, + 0xce, 0x19, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x87, + 0x14, 0x42, 0x86, 0x02, 0x4d, 0x16, 0x42, 0x86, + 0x04, 0x68, 0x14, 0x68, 0x14, 0x66, 0x14, 0x66, + 0x14, 0x02, 0xdb, 0x19, 0x40, 0x86, 0x02, 0xd9, + 0x19, 0x42, 0x86, 0x02, 0xc4, 0x13, 0x40, 0x86, + 0x02, 0xd1, 0x19, 0xbd, 0x19, 0x02, 0x68, 0x14, + 0xa4, 0x13, 0x02, 0x3e, 0x19, 0x42, 0x86, 0x02, + 0xf3, 0x93, 0xa7, 0x86, 0x03, 0x69, 0x14, 0xaf, + 0x19, 0xa1, 0x87, 0x02, 0xf3, 0x93, 0x08, 0x13, + 0x02, 0xd1, 0x19, 0xd2, 0x19, 0x02, 0x73, 0x14, + 0x40, 0x86, 0x02, 0xb5, 0x16, 0x40, 0x86, 0x02, + 0x35, 0x19, 0x40, 0x86, 0x02, 0x69, 0x14, 0x27, + 0x15, 0x02, 0xce, 0x19, 0x42, 0x86, 0x02, 0x71, + 0x14, 0x42, 0x86, 0x02, 0xd1, 0x19, 0x73, 0x13, + 0x02, 0x68, 0x14, 0x3e, 0x13, 0x02, 0xf4, 0x13, + 0x20, 0x86, 0x02, 0x87, 0x14, 0x40, 0x86, 0x03, + 0xb6, 0x16, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x4d, + 0x16, 0x40, 0x86, 0x02, 0x69, 0x14, 0xbc, 0x19, + 0x02, 0x4b, 0x16, 0x42, 0x86, 0x02, 0xd9, 0x19, + 0x40, 0x86, 0x02, 0x3e, 0x19, 0x40, 0x86, 0x02, + 0x69, 0x14, 0xed, 0x13, 0x02, 0xd7, 0x19, 0x42, + 0x86, 0x02, 0xb8, 0x19, 0x42, 0x86, 0x03, 0x68, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x02, 0x3c, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x14, 0x66, 0x14, 0x03, + 0x68, 0x14, 0x64, 0x87, 0x68, 0x14, 0x02, 0x69, + 0x14, 0xaf, 0x19, 0x02, 0xce, 0x19, 0x40, 0x86, + 0x02, 0x71, 0x14, 0x40, 0x86, 0x02, 0x68, 0x14, + 0xeb, 0x13, 0x03, 0x68, 0x14, 0xbd, 0x19, 0xa1, + 0x87, 0x02, 0x6f, 0x14, 0x42, 0x86, 0x04, 0xd1, + 0x19, 0xd1, 0x19, 0xd2, 0x19, 0xd2, 0x19, 0x02, + 0x69, 0x14, 0xbc, 0x14, 0x02, 0xcc, 0x93, 0x42, + 0x86, 0x02, 0x4b, 0x16, 0x40, 0x86, 0x02, 0x26, + 0x19, 0x42, 0x86, 0x02, 0xd7, 0x19, 0x40, 0x86, +}; + #endif /* CONFIG_ALL_UNICODE */ +/* 71 tables / 36311 bytes, 5 index / 351 bytes */
View file
gpac-2.4.0.tar.gz/src/quickjs/libunicode.c -> gpac-26.02.0.tar.gz/src/quickjs/libunicode.c
Changed
@@ -1,6 +1,6 @@ /* * Unicode utilities - * + * * Copyright (c) 2017-2018 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -43,15 +43,115 @@ RUN_TYPE_UF_D1_EXT, RUN_TYPE_U_EXT, RUN_TYPE_LF_EXT, - RUN_TYPE_U_EXT2, - RUN_TYPE_L_EXT2, - RUN_TYPE_U_EXT3, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, }; +static int lre_case_conv1(uint32_t c, int conv_type) +{ + uint32_t resLRE_CC_RES_LEN_MAX; + lre_case_conv(res, c, conv_type); + return res0; +} + +/* case conversion using the table entry 'idx' with value 'v' */ +static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) +{ + uint32_t code, data, type, a, is_lower; + is_lower = (conv_type != 0); + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + data = ((v & 0xf) << 8) | case_conv_table2idx; + code = v >> (32 - 17); + switch(type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + if (conv_type == (type & 1) || + (type >= RUN_TYPE_UF && conv_type == 2)) { + c = c - code + (case_conv_table1data >> (32 - 17)); + } + break; + case RUN_TYPE_UL: + a = c - code; + if ((a & 1) != (1 - is_lower)) + break; + c = (a ^ 1) + code; + break; + case RUN_TYPE_LSU: + a = c - code; + if (a == 1) { + c += 2 * is_lower - 1; + } else if (a == (1 - is_lower) * 2) { + c += (2 * is_lower - 1) * 2; + } + break; + case RUN_TYPE_U2L_399_EXT2: + if (!is_lower) { + res0 = c - code + case_conv_extdata >> 6; + res1 = 0x399; + return 2; + } else { + c = c - code + case_conv_extdata & 0x3f; + } + break; + case RUN_TYPE_UF_D20: + if (conv_type == 1) + break; + c = data + (conv_type == 2) * 0x20; + break; + case RUN_TYPE_UF_D1_EXT: + if (conv_type == 1) + break; + c = case_conv_extdata + (conv_type == 2); + break; + case RUN_TYPE_U_EXT: + case RUN_TYPE_LF_EXT: + if (is_lower != (type - RUN_TYPE_U_EXT)) + break; + c = case_conv_extdata; + break; + case RUN_TYPE_LF_EXT2: + if (!is_lower) + break; + res0 = c - code + case_conv_extdata >> 6; + res1 = case_conv_extdata & 0x3f; + return 2; + case RUN_TYPE_UF_EXT2: + if (conv_type == 1) + break; + res0 = c - code + case_conv_extdata >> 6; + res1 = case_conv_extdata & 0x3f; + if (conv_type == 2) { + /* convert to lower */ + res0 = lre_case_conv1(res0, 1); + res1 = lre_case_conv1(res1, 1); + } + return 2; + default: + case RUN_TYPE_UF_EXT3: + if (conv_type == 1) + break; + res0 = case_conv_extdata >> 8; + res1 = case_conv_ext(data >> 4) & 0xf; + res2 = case_conv_extdata & 0xf; + if (conv_type == 2) { + /* convert to lower */ + res0 = lre_case_conv1(res0, 1); + res1 = lre_case_conv1(res1, 1); + res2 = lre_case_conv1(res2, 1); + } + return 3; + } + res0 = c; + return 1; +} + /* conv_type: - 0 = to upper + 0 = to upper 1 = to lower - 2 = case folding (= to lower with modifications) + 2 = case folding (= to lower with modifications) */ int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) { @@ -66,10 +166,9 @@ } } } else { - uint32_t v, code, data, type, len, a, is_lower; + uint32_t v, code, len; int idx, idx_min, idx_max; - - is_lower = (conv_type != 0); + idx_min = 0; idx_max = countof(case_conv_table1) - 1; while (idx_min <= idx_max) { @@ -82,74 +181,7 @@ } else if (c >= code + len) { idx_min = idx + 1; } else { - type = (v >> (32 - 17 - 7 - 4)) & 0xf; - data = ((v & 0xf) << 8) | case_conv_table2idx; - switch(type) { - case RUN_TYPE_U: - case RUN_TYPE_L: - case RUN_TYPE_UF: - case RUN_TYPE_LF: - if (conv_type == (type & 1) || - (type >= RUN_TYPE_UF && conv_type == 2)) { - c = c - code + (case_conv_table1data >> (32 - 17)); - } - break; - case RUN_TYPE_UL: - a = c - code; - if ((a & 1) != (1 - is_lower)) - break; - c = (a ^ 1) + code; - break; - case RUN_TYPE_LSU: - a = c - code; - if (a == 1) { - c += 2 * is_lower - 1; - } else if (a == (1 - is_lower) * 2) { - c += (2 * is_lower - 1) * 2; - } - break; - case RUN_TYPE_U2L_399_EXT2: - if (!is_lower) { - res0 = c - code + case_conv_extdata >> 6; - res1 = 0x399; - return 2; - } else { - c = c - code + case_conv_extdata & 0x3f; - } - break; - case RUN_TYPE_UF_D20: - if (conv_type == 1) - break; - c = data + (conv_type == 2) * 0x20; - break; - case RUN_TYPE_UF_D1_EXT: - if (conv_type == 1) - break; - c = case_conv_extdata + (conv_type == 2); - break; - case RUN_TYPE_U_EXT: - case RUN_TYPE_LF_EXT: - if (is_lower != (type - RUN_TYPE_U_EXT)) - break; - c = case_conv_extdata; - break; - case RUN_TYPE_U_EXT2: - case RUN_TYPE_L_EXT2: - if (conv_type != (type - RUN_TYPE_U_EXT2)) - break; - res0 = c - code + case_conv_extdata >> 6; - res1 = case_conv_extdata & 0x3f; - return 2; - default: - case RUN_TYPE_U_EXT3: - if (conv_type != 0) - break; - res0 = case_conv_extdata >> 8; - res1 = case_conv_ext(data >> 4) & 0xf; - res2 = case_conv_extdata & 0xf; - return 3; - } - break; + return lre_case_conv_entry(res, c, conv_type, idx, v); } } } @@ -157,13 +189,80 @@ return 1; } +static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, BOOL is_unicode) +{ + uint32_t resLRE_CC_RES_LEN_MAX; + int len; + + if (is_unicode) { + len = lre_case_conv_entry(res, c, 2, idx, v); + if (len == 1) { + c = res0; + } else { + /* handle the few specific multi-character cases (see + unicode_gen.c:dump_case_folding_special_cases()) */ + if (c == 0xfb06) { + c = 0xfb05; + } else if (c == 0x01fd3) { + c = 0x390; + } else if (c == 0x01fe3) { + c = 0x3b0; + } + } + } else { + if (likely(c < 128)) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + } else { + /* legacy regexp: to upper case if single char >= 128 */ + len = lre_case_conv_entry(res, c, FALSE, idx, v); + if (len == 1 && res0 >= 128) + c = res0; + } + } + return c; +} + +/* JS regexp specific rules for case folding */ +int lre_canonicalize(uint32_t c, BOOL is_unicode) +{ + if (c < 128) { + /* fast case */ + if (is_unicode) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1idx; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_folding_entry(c, idx, v, is_unicode); + } + } + } + return c; +} + static uint32_t get_le24(const uint8_t *ptr) { -#if defined(__x86__) || defined(__x86_64__) - return *(uint16_t *)ptr | (ptr2 << 16); -#else return ptr0 | (ptr1 << 8) | (ptr2 << 16); -#endif } #define UNICODE_INDEX_BLOCK_LEN 32 @@ -208,12 +307,20 @@ uint32_t code, b, bit; int pos; const uint8_t *p; - + pos = get_index_pos(&code, c, index_table, index_table_len); if (pos < 0) return FALSE; /* outside the table */ p = table + pos; bit = 0; + /* Compressed run length encoding: + 00..3F: 2 packed lengths: 3-bit + 3-bit + 40..5F: 5-bits plus extra byte for length + 60..7F: 5-bits plus 2 extra bytes for length + 80..FF: 7-bit length + lengths must be incremented to get character count + Ranges alternate between false and true return value. + */ for(;;) { b = *p++; if (b < 64) { @@ -241,7 +348,7 @@ { uint32_t v, code, len; int idx, idx_min, idx_max; - + idx_min = 0; idx_max = countof(case_conv_table1) - 1; while (idx_min <= idx_max) { @@ -300,7 +407,7 @@ { int new_size; uint32_t *new_buf; - + if (size > cr->size) { new_size = max_int(size, cr->size * 3 / 2); new_buf = cr->realloc_func(cr->mem_opaque, cr->points, @@ -327,7 +434,7 @@ { int i, j, k, len; uint32_t *pt; - + pt = cr->points; len = cr->len; i = 0; @@ -357,7 +464,7 @@ { int a_idx, b_idx, is_in; uint32_t v; - + a_idx = 0; b_idx = 0; for(;;) { @@ -392,6 +499,9 @@ case CR_OP_XOR: is_in = (a_idx & 1) ^ (b_idx & 1); break; + case CR_OP_SUB: + is_in = (a_idx & 1) & ((b_idx & 1) ^ 1); + break; default: abort(); } @@ -404,14 +514,14 @@ return 0; } -int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len) +int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op) { CharRange a = *cr; int ret; cr->len = 0; cr->size = 0; cr->points = NULL; - ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION); + ret = cr_op(cr, a.points, a.len, b_pt, b_len, op); cr_free(&a); return ret; } @@ -430,6 +540,207 @@ return 0; } +#define CASE_U (1 << 0) +#define CASE_L (1 << 1) +#define CASE_F (1 << 2) + +/* use the case conversion table to generate range of characters. + CASE_U: set char if modified by uppercasing, + CASE_L: set char if modified by lowercasing, + CASE_F: set char if modified by case folding, + */ +static int unicode_case1(CharRange *cr, int case_mask) +{ +#define MR(x) (1 << RUN_TYPE_ ## x) + const uint32_t tab_run_mask3 = { + MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | + MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + + MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), + + MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + }; +#undef MR + uint32_t mask, v, code, type, len, i, idx; + + if (case_mask == 0) + return 0; + mask = 0; + for(i = 0; i < 3; i++) { + if ((case_mask >> i) & 1) + mask |= tab_run_maski; + } + for(idx = 0; idx < countof(case_conv_table1); idx++) { + v = case_conv_table1idx; + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if ((mask >> type) & 1) { + // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); + switch(type) { + case RUN_TYPE_UL: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + code += ((case_mask & CASE_U) != 0); + for(i = 0; i < len; i += 2) { + if (cr_add_interval(cr, code + i, code + i + 1)) + return -1; + } + break; + case RUN_TYPE_LSU: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + if (!(case_mask & CASE_U)) { + if (cr_add_interval(cr, code, code + 1)) + return -1; + } + if (cr_add_interval(cr, code + 1, code + 2)) + return -1; + if (case_mask & CASE_U) { + if (cr_add_interval(cr, code + 2, code + 3)) + return -1; + } + break; + default: + def_case: + if (cr_add_interval(cr, code, code + len)) + return -1; + break; + } + } + } + return 0; +} + +static int point_cmp(const void *p1, const void *p2, void *arg) +{ + uint32_t v1 = *(uint32_t *)p1; + uint32_t v2 = *(uint32_t *)p2; + return (v1 > v2) - (v1 < v2); +} + +static void cr_sort_and_remove_overlap(CharRange *cr) +{ + uint32_t start, end, start1, end1, i, j; + + /* the resulting ranges are not necessarily sorted and may overlap */ + rqsort(cr->points, cr->len / 2, sizeof(cr->points0) * 2, point_cmp, NULL); + j = 0; + for(i = 0; i < cr->len; ) { + start = cr->pointsi; + end = cr->pointsi + 1; + i += 2; + while (i < cr->len) { + start1 = cr->pointsi; + end1 = cr->pointsi + 1; + if (start1 > end) { + /* |------| + * |-------| */ + break; + } else if (end1 <= end) { + /* |------| + * |--| */ + i += 2; + } else { + /* |------| + * |-------| */ + end = end1; + i += 2; + } + } + cr->pointsj = start; + cr->pointsj + 1 = end; + j += 2; + } + cr->len = j; +} + +/* canonicalize a character set using the JS regex case folding rules + (see lre_canonicalize()) */ +int cr_regexp_canonicalize(CharRange *cr, BOOL is_unicode) +{ + CharRange cr_inter, cr_mask, cr_result, cr_sub; + uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; + + cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); + + if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) + goto fail; + if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + if (cr_invert(&cr_mask)) + goto fail; + if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + /* cr_inter = cr & cr_mask */ + /* cr_sub = cr & ~cr_mask */ + + /* use the case conversion table to compute the result */ + d_start = -1; + d_end = -1; + idx = 0; + v = case_conv_table1idx; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + for(i = 0; i < cr_inter.len; i += 2) { + start = cr_inter.pointsi; + end = cr_inter.pointsi + 1; + + for(c = start; c < end; c++) { + for(;;) { + if (c >= code && c < code + len) + break; + idx++; + assert(idx < countof(case_conv_table1)); + v = case_conv_table1idx; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + } + d = lre_case_folding_entry(c, idx, v, is_unicode); + /* try to merge with the current interval */ + if (d_start == -1) { + d_start = d; + d_end = d + 1; + } else if (d_end == d) { + d_end++; + } else { + cr_add_interval(&cr_result, d_start, d_end); + d_start = d; + d_end = d + 1; + } + } + } + if (d_start != -1) { + if (cr_add_interval(&cr_result, d_start, d_end)) + goto fail; + } + + /* the resulting ranges are not necessarily sorted and may overlap */ + cr_sort_and_remove_overlap(&cr_result); + + /* or with the character not affected by the case folding */ + cr->len = 0; + if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) + goto fail; + + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return 0; + fail: + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return -1; +} + #ifdef CONFIG_ALL_UNICODE BOOL lre_is_id_start(uint32_t c) @@ -658,7 +969,7 @@ { uint32_t v, type, is_compat, code, len; int idx_min, idx_max, idx; - + idx_min = 0; idx_max = countof(unicode_decomp_table1) - 1; while (idx_min <= idx_max) { @@ -688,7 +999,7 @@ uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; int idx_min, idx_max, idx, d; uint32_t pair2; - + idx_min = 0; idx_max = countof(unicode_comp_table) - 1; while (idx_min <= idx_max) { @@ -724,12 +1035,19 @@ uint32_t code, n, type, cc, c1, b; int pos; const uint8_t *p; - + pos = get_index_pos(&code, c, unicode_cc_index, sizeof(unicode_cc_index) / 3); if (pos < 0) return 0; p = unicode_cc_table + pos; + /* Compressed run length encoding: + - 2 high order bits are combining class type + - 0:0, 1:230, 2:extra byte linear progression, 3:extra byte + - 00..2F: range length (add 1) + - 30..37: 3-bit range-length + 1 extra byte + - 38..3F: 3-bit range-length + 2 extra byte + */ for(;;) { b = *p++; type = b >> 6; @@ -773,7 +1091,7 @@ static void sort_cc(int *buf, int len) { int i, j, k, cc, cc1, start, ch1; - + for(i = 0; i < len; i++) { cc = unicode_get_cc(bufi); if (cc != 0) { @@ -812,7 +1130,7 @@ uint32_t c, v; int i, l; uint32_t resUNICODE_DECOMP_LEN_MAX; - + for(i = 0; i < src_len; i++) { c = srci; if (c >= 0xac00 && c < 0xd7a4) { @@ -857,11 +1175,11 @@ int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; BOOL is_compat; DynBuf dbuf_s, *dbuf = &dbuf_s; - + is_compat = n_type >> 1; dbuf_init2(dbuf, opaque, realloc_func); - if (dbuf_realloc(dbuf, sizeof(int) * src_len)) + if (dbuf_claim(dbuf, sizeof(int) * src_len)) goto fail; /* common case: latin1 is unaffected by NFC */ @@ -887,13 +1205,13 @@ buf_len = (int) ( dbuf->size / sizeof(int) ); sort_cc(buf, buf_len); - + if (buf_len <= 1 || (n_type & 1) != 0) { /* NFD / NFKD */ *pdst = (uint32_t *)buf; return buf_len; } - + i = 1; out_len = 1; while (i < buf_len) { @@ -930,7 +1248,7 @@ const char *p, *r; int pos; size_t name_len, len; - + p = name_table; pos = 0; name_len = strlen(name); @@ -963,13 +1281,11 @@ CharRange cr1_s, *cr1; CharRange cr2_s={0}, *cr2 = &cr2_s; BOOL is_common; - + script_idx = unicode_find_name(unicode_script_name_table, script_name); if (script_idx < 0) return -2; - /* Note: we remove the "Unknown" Script */ - script_idx += UNICODE_SCRIPT_Unknown + 1; - + is_common = (script_idx == UNICODE_SCRIPT_Common || script_idx == UNICODE_SCRIPT_Inherited); if (is_ext) { @@ -998,17 +1314,21 @@ n |= *p++; n += 96 + (1 << 12); } - if (type == 0) - v = 0; - else - v = *p++; c1 = c + n + 1; - if (v == script_idx) { - if (cr_add_interval(cr1, c, c1)) - goto fail; + if (type != 0) { + v = *p++; + if (v == script_idx || script_idx == UNICODE_SCRIPT_Unknown) { + if (cr_add_interval(cr1, c, c1)) + goto fail; + } } c = c1; } + if (script_idx == UNICODE_SCRIPT_Unknown) { + /* Unknown is all the characters outside scripts */ + if (cr_invert(cr1)) + goto fail; + } if (is_ext) { /* add the script extensions */ @@ -1082,6 +1402,15 @@ p = unicode_gc_table; p_end = unicode_gc_table + countof(unicode_gc_table); c = 0; + /* Compressed range encoding: + initial byte: + bits 0..4: category number (special case 31) + bits 5..7: range length (add 1) + special case bits 5..7 == 7: read an extra byte + - 00..7F: range length (add 7 + 1) + - 80..BF: 6-bits plus extra byte for range length (add 7 + 128) + - C0..FF: 6-bits plus 2 extra bytes for range length (add 7 + 128 + 16384) + */ while (p < p_end) { b = *p++; n = b >> 5; @@ -1135,6 +1464,14 @@ p_end = p + unicode_prop_len_tableprop_idx; c = 0; bit = 0; + /* Compressed range encoding: + 00..3F: 2 packed lengths: 3-bit + 3-bit + 40..5F: 5-bits plus extra byte for length + 60..7F: 5-bits plus 2 extra bytes for length + 80..FF: 7-bit length + lengths must be incremented to get character count + Ranges alternate between false and true return value. + */ while (p < p_end) { c0 = c; b = *p++; @@ -1165,78 +1502,6 @@ return 0; } -#define CASE_U (1 << 0) -#define CASE_L (1 << 1) -#define CASE_F (1 << 2) - -/* use the case conversion table to generate range of characters. - CASE_U: set char if modified by uppercasing, - CASE_L: set char if modified by lowercasing, - CASE_F: set char if modified by case folding, - */ -static int unicode_case1(CharRange *cr, int case_mask) -{ -#define MR(x) (1 << RUN_TYPE_ ## x) - const uint32_t tab_run_mask3 = { - MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | - MR(UF_D1_EXT) | MR(U_EXT) | MR(U_EXT2) | MR(U_EXT3), - - MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(L_EXT2), - - MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT), - }; -#undef MR - uint32_t mask, v, code, type, len, i, idx; - - if (case_mask == 0) - return 0; - mask = 0; - for(i = 0; i < 3; i++) { - if ((case_mask >> i) & 1) - mask |= tab_run_maski; - } - for(idx = 0; idx < countof(case_conv_table1); idx++) { - v = case_conv_table1idx; - type = (v >> (32 - 17 - 7 - 4)) & 0xf; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if ((mask >> type) & 1) { - // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); - switch(type) { - case RUN_TYPE_UL: - if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) - goto def_case; - code += ((case_mask & CASE_U) != 0); - for(i = 0; i < len; i += 2) { - if (cr_add_interval(cr, code + i, code + i + 1)) - return -1; - } - break; - case RUN_TYPE_LSU: - if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) - goto def_case; - if (!(case_mask & CASE_U)) { - if (cr_add_interval(cr, code, code + 1)) - return -1; - } - if (cr_add_interval(cr, code + 1, code + 2)) - return -1; - if (case_mask & CASE_U) { - if (cr_add_interval(cr, code + 2, code + 3)) - return -1; - } - break; - default: - def_case: - if (cr_add_interval(cr, code, code + len)) - return -1; - break; - } - } - } - return 0; -} - typedef enum { POP_GC, POP_PROP, @@ -1256,7 +1521,7 @@ CharRange stackPOP_STACK_LEN_MAX; int stack_len, op, ret, i; uint32_t a; - + va_start(ap, cr); stack_len = 0; for(;;) { @@ -1294,6 +1559,7 @@ cr2 = &stackstack_len - 1; cr3 = &stackstack_len++; cr_init(cr3, cr->mem_opaque, cr->realloc_func); + /* CR_OP_XOR may be used here */ if (cr_op(cr3, cr1->points, cr1->len, cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) goto fail; @@ -1342,7 +1608,7 @@ { int gc_idx; uint32_t gc_mask; - + gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); if (gc_idx < 0) return -2; @@ -1360,7 +1626,7 @@ int unicode_prop(CharRange *cr, const char *prop_name) { int prop_idx, ret; - + prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); if (prop_idx < 0) return -2; @@ -1554,3 +1820,304 @@ } #endif /* CONFIG_ALL_UNICODE */ + +/*---- lre codepoint categorizing functions ----*/ + +#define S UNICODE_C_SPACE +#define D UNICODE_C_DIGIT +#define X UNICODE_C_XDIGIT +#define U UNICODE_C_UPPER +#define L UNICODE_C_LOWER +#define _ UNICODE_C_UNDER +#define d UNICODE_C_DOLLAR + +uint8_t const lre_ctype_bits256 = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, S, S, S, S, S, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S, 0, 0, 0, d, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + X|D, X|D, X|D, X|D, X|D, X|D, X|D, X|D, + X|D, X|D, 0, 0, 0, 0, 0, 0, + + 0, X|U, X|U, X|U, X|U, X|U, X|U, U, + U, U, U, U, U, U, U, U, + U, U, U, U, U, U, U, U, + U, U, U, 0, 0, 0, 0, _, + + 0, X|L, X|L, X|L, X|L, X|L, X|L, L, + L, L, L, L, L, L, L, L, + L, L, L, L, L, L, L, L, + L, L, L, 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, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S, 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, 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, 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, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#undef S +#undef D +#undef X +#undef U +#undef L +#undef _ +#undef d + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +BOOL lre_is_space_non_ascii(uint32_t c) +{ + size_t i, n; + + n = countof(char_range_s); + for(i = 5; i < n; i += 2) { + uint32_t low = char_range_si; + uint32_t high = char_range_si + 1; + if (c < low) + return FALSE; + if (c < high) + return TRUE; + } + return FALSE; +} + +#define SEQ_MAX_LEN 16 + +static int unicode_sequence_prop1(int seq_prop_idx, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr) +{ + int i, c, j; + uint32_t seqSEQ_MAX_LEN; + + switch(seq_prop_idx) { + case UNICODE_SEQUENCE_PROP_Basic_Emoji: + if (unicode_prop1(cr, UNICODE_PROP_Basic_Emoji1) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->pointsi; c < cr->pointsi + 1; c++) { + seq0 = c; + cb(opaque, seq, 1); + } + } + + cr->len = 0; + + if (unicode_prop1(cr, UNICODE_PROP_Basic_Emoji2) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->pointsi; c < cr->pointsi + 1; c++) { + seq0 = c; + seq1 = 0xfe0f; + cb(opaque, seq, 2); + } + } + + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_Emoji_Modifier_Base) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->pointsi; c < cr->pointsi + 1; c++) { + for(j = 0; j < 5; j++) { + seq0 = c; + seq1 = 0x1f3fb + j; + cb(opaque, seq, 2); + } + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Flag_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_RGI_Emoji_Flag_Sequence) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->pointsi; c < cr->pointsi + 1; c++) { + int c0, c1; + c0 = c / 26; + c1 = c % 26; + seq0 = 0x1F1E6 + c0; + seq1 = 0x1F1E6 + c1; + cb(opaque, seq, 2); + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence: + { + int len, code, pres, k, mod, mod_count, mod_pos2, hc_pos, n_mod, n_hc, mod1; + int mod_idx, hc_idx, i0, i1; + const uint8_t *tab = unicode_rgi_emoji_zwj_sequence; + + for(i = 0; i < countof(unicode_rgi_emoji_zwj_sequence);) { + len = tabi++; + k = 0; + mod = 0; + mod_count = 0; + hc_pos = -1; + for(j = 0; j < len; j++) { + code = tabi++; + code |= tabi++ << 8; + pres = code >> 15; + mod1 = (code >> 13) & 3; + code &= 0x1fff; + if (code < 0x1000) { + c = code + 0x2000; + } else { + c = 0x1f000 + (code - 0x1000); + } + if (c == 0x1f9b0) + hc_pos = k; + seqk++ = c; + if (mod1 != 0) { + assert(mod_count < 2); + mod = mod1; + mod_posmod_count++ = k; + seqk++ = 0; /* will be filled later */ + } + if (pres) { + seqk++ = 0xfe0f; + } + if (j < len - 1) { + seqk++ = 0x200d; + } + } + + /* genrate all the variants */ + switch(mod) { + case 1: + n_mod = 5; + break; + case 2: + n_mod = 25; + break; + case 3: + n_mod = 20; + break; + default: + n_mod = 1; + break; + } + if (hc_pos >= 0) + n_hc = 4; + else + n_hc = 1; + for(hc_idx = 0; hc_idx < n_hc; hc_idx++) { + for(mod_idx = 0; mod_idx < n_mod; mod_idx++) { + if (hc_pos >= 0) + seqhc_pos = 0x1f9b0 + hc_idx; + + switch(mod) { + case 1: + seqmod_pos0 = 0x1f3fb + mod_idx; + break; + case 2: + case 3: + i0 = mod_idx / 5; + i1 = mod_idx % 5; + /* avoid identical values */ + if (mod == 3 && i0 >= i1) + i0++; + seqmod_pos0 = 0x1f3fb + i0; + seqmod_pos1 = 0x1f3fb + i1; + break; + default: + break; + } +#if 0 + for(j = 0; j < k; j++) + printf(" %04x", seqj); + printf("\n"); +#endif + cb(opaque, seq, k); + } + } + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Tag_Sequence: + { + for(i = 0; i < countof(unicode_rgi_emoji_tag_sequence);) { + j = 0; + seqj++ = 0x1F3F4; + for(;;) { + c = unicode_rgi_emoji_tag_sequencei++; + if (c == 0x00) + break; + seqj++ = 0xe0000 + c; + } + seqj++ = 0xe007f; + cb(opaque, seq, j); + } + } + break; + case UNICODE_SEQUENCE_PROP_Emoji_Keycap_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_Emoji_Keycap_Sequence) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->pointsi; c < cr->pointsi + 1; c++) { + seq0 = c; + seq1 = 0xfe0f; + seq2 = 0x20e3; + cb(opaque, seq, 3); + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji: + /* all prevous sequences */ + for(i = UNICODE_SEQUENCE_PROP_Basic_Emoji; i <= UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence; i++) { + int ret; + ret = unicode_sequence_prop1(i, cb, opaque, cr); + if (ret < 0) + return ret; + cr->len = 0; + } + break; + default: + return -2; + } + return 0; +} + +/* build a unicode sequence property */ +/* return -2 if not found, -1 if other error. 'cr' is used as temporary memory. */ +int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr) +{ + int seq_prop_idx; + seq_prop_idx = unicode_find_name(unicode_sequence_prop_name_table, prop_name); + if (seq_prop_idx < 0) + return -2; + return unicode_sequence_prop1(seq_prop_idx, cb, opaque, cr); +}
View file
gpac-2.4.0.tar.gz/src/quickjs/libunicode.h -> gpac-26.02.0.tar.gz/src/quickjs/libunicode.h
Changed
@@ -1,6 +1,6 @@ /* * Unicode utilities - * + * * Copyright (c) 2017-2018 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,26 +24,13 @@ #ifndef LIBUNICODE_H #define LIBUNICODE_H -#include <inttypes.h> - -#define LRE_BOOL int /* for documentation purposes */ +#include <stdint.h> /* define it to include all the unicode tables (40KB larger) */ #define CONFIG_ALL_UNICODE #define LRE_CC_RES_LEN_MAX 3 -typedef enum { - UNICODE_NFC, - UNICODE_NFD, - UNICODE_NFKC, - UNICODE_NFKD, -} UnicodeNormalizationEnum; - -int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); -LRE_BOOL lre_is_cased(uint32_t c); -LRE_BOOL lre_is_case_ignorable(uint32_t c); - /* char ranges */ typedef struct { @@ -58,6 +45,7 @@ CR_OP_UNION, CR_OP_INTER, CR_OP_XOR, + CR_OP_SUB, } CharRangeOpEnum; void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); @@ -86,25 +74,28 @@ return 0; } -int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); +int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op); static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) { uint32_t b_pt2; b_pt0 = c1; b_pt1 = c2 + 1; - return cr_union1(cr, b_pt, 2); + return cr_op1(cr, b_pt, 2, CR_OP_UNION); } -int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, - const uint32_t *b_pt, int b_len, int op); - int cr_invert(CharRange *cr); -#ifdef CONFIG_ALL_UNICODE +int cr_regexp_canonicalize(CharRange *cr, int is_unicode); -LRE_BOOL lre_is_id_start(uint32_t c); -LRE_BOOL lre_is_id_continue(uint32_t c); +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, UnicodeNormalizationEnum n_type, @@ -112,13 +103,84 @@ /* Unicode character range functions */ -int unicode_script(CharRange *cr, - const char *script_name, LRE_BOOL is_ext); +int unicode_script(CharRange *cr, const char *script_name, int is_ext); int unicode_general_category(CharRange *cr, const char *gc_name); int unicode_prop(CharRange *cr, const char *prop_name); -#endif /* CONFIG_ALL_UNICODE */ +typedef void UnicodeSequencePropCB(void *opaque, const uint32_t *buf, int len); +int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr); + +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +int lre_canonicalize(uint32_t c, int is_unicode); + +/* Code point type categories */ +enum { + UNICODE_C_SPACE = (1 << 0), + UNICODE_C_DIGIT = (1 << 1), + UNICODE_C_UPPER = (1 << 2), + UNICODE_C_LOWER = (1 << 3), + UNICODE_C_UNDER = (1 << 4), + UNICODE_C_DOLLAR = (1 << 5), + UNICODE_C_XDIGIT = (1 << 6), +}; +extern uint8_t const lre_ctype_bits256; + +/* zero or non-zero return value */ +int lre_is_cased(uint32_t c); +int lre_is_case_ignorable(uint32_t c); +int lre_is_id_start(uint32_t c); +int lre_is_id_continue(uint32_t c); + +static inline int lre_is_space_byte(uint8_t c) { + return lre_ctype_bitsc & UNICODE_C_SPACE; +} + +static inline int lre_is_id_start_byte(uint8_t c) { + return lre_ctype_bitsc & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DOLLAR); +} + +static inline int lre_is_id_continue_byte(uint8_t c) { + return lre_ctype_bitsc & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DOLLAR | + UNICODE_C_DIGIT); +} + +int lre_is_space_non_ascii(uint32_t c); + +static inline int lre_is_space(uint32_t c) { + if (c < 256) + return lre_is_space_byte(c); + else + return lre_is_space_non_ascii(c); +} + +static inline int lre_js_is_ident_first(uint32_t c) { + if (c < 128) { + return lre_is_id_start_byte(c); + } else { +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_start(c); +#else + return !lre_is_space_non_ascii(c); +#endif + } +} -#undef LRE_BOOL +static inline int lre_js_is_ident_next(uint32_t c) { + if (c < 128) { + return lre_is_id_continue_byte(c); + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ + if (c >= 0x200C && c <= 0x200D) + return TRUE; +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_continue(c); +#else + return !lre_is_space_non_ascii(c); +#endif + } +} #endif /* LIBUNICODE_H */
View file
gpac-2.4.0.tar.gz/src/quickjs/list.h -> gpac-26.02.0.tar.gz/src/quickjs/list.h
Changed
@@ -1,6 +1,6 @@ /* * Linux klist like system - * + * * Copyright (c) 2016-2017 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -36,8 +36,7 @@ #define LIST_HEAD_INIT(el) { &(el), &(el) } /* return the pointer of type 'type *' containing 'el' as field 'member' */ -#define list_entry(el, type, member) \ - ((type *)((uint8_t *)(el) - offsetof(type, member))) +#define list_entry(el, type, member) container_of(el, type, member) static inline void init_list_head(struct list_head *head) { @@ -46,7 +45,7 @@ } /* insert 'el' between 'prev' and 'next' */ -static inline void __list_add(struct list_head *el, +static inline void __list_add(struct list_head *el, struct list_head *prev, struct list_head *next) { prev->next = el;
View file
gpac-2.4.0.tar.gz/src/quickjs/quickjs-atom.h -> gpac-26.02.0.tar.gz/src/quickjs/quickjs-atom.h
Changed
@@ -1,6 +1,6 @@ /* * QuickJS atom definitions - * + * * Copyright (c) 2017-2018 Fabrice Bellard * Copyright (c) 2017-2018 Charlie Gordon * @@ -78,10 +78,14 @@ /* empty string */ DEF(empty_string, "") /* identifiers */ +DEF(keys, "keys") +DEF(size, "size") DEF(length, "length") DEF(fileName, "fileName") DEF(lineNumber, "lineNumber") +DEF(columnNumber, "columnNumber") DEF(message, "message") +DEF(cause, "cause") DEF(errors, "errors") DEF(stack, "stack") DEF(name, "name") @@ -166,23 +170,28 @@ DEF(async, "async") DEF(exec, "exec") DEF(groups, "groups") +DEF(indices, "indices") DEF(status, "status") DEF(reason, "reason") DEF(globalThis, "globalThis") -#ifdef CONFIG_BIGNUM DEF(bigint, "bigint") -DEF(bigfloat, "bigfloat") -DEF(bigdecimal, "bigdecimal") -DEF(roundingMode, "roundingMode") -DEF(maximumSignificantDigits, "maximumSignificantDigits") -DEF(maximumFractionDigits, "maximumFractionDigits") -#endif -#ifdef CONFIG_ATOMICS +DEF(minus_zero, "-0") +DEF(Infinity, "Infinity") +DEF(minus_Infinity, "-Infinity") +DEF(NaN, "NaN") +DEF(hasIndices, "hasIndices") +DEF(ignoreCase, "ignoreCase") +DEF(multiline, "multiline") +DEF(dotAll, "dotAll") +DEF(sticky, "sticky") +DEF(unicodeSets, "unicodeSets") +/* the following 3 atoms are only used with CONFIG_ATOMICS */ DEF(not_equal, "not-equal") DEF(timed_out, "timed-out") DEF(ok, "ok") -#endif +/* */ DEF(toJSON, "toJSON") +DEF(maxByteLength, "maxByteLength") /* class names */ DEF(Object, "Object") DEF(Array, "Array") @@ -202,32 +211,29 @@ DEF(ArrayBuffer, "ArrayBuffer") DEF(SharedArrayBuffer, "SharedArrayBuffer") /* must keep same order as class IDs for typed arrays */ -DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Uint8ClampedArray, "Uint8ClampedArray") DEF(Int8Array, "Int8Array") DEF(Uint8Array, "Uint8Array") DEF(Int16Array, "Int16Array") DEF(Uint16Array, "Uint16Array") DEF(Int32Array, "Int32Array") DEF(Uint32Array, "Uint32Array") -#ifdef CONFIG_BIGNUM DEF(BigInt64Array, "BigInt64Array") DEF(BigUint64Array, "BigUint64Array") -#endif +DEF(Float16Array, "Float16Array") DEF(Float32Array, "Float32Array") DEF(Float64Array, "Float64Array") DEF(DataView, "DataView") -#ifdef CONFIG_BIGNUM DEF(BigInt, "BigInt") -DEF(BigFloat, "BigFloat") -DEF(BigFloatEnv, "BigFloatEnv") -DEF(BigDecimal, "BigDecimal") -DEF(OperatorSet, "OperatorSet") -DEF(Operators, "Operators") -#endif +DEF(WeakRef, "WeakRef") +DEF(FinalizationRegistry, "FinalizationRegistry") DEF(Map, "Map") DEF(Set, "Set") /* Map + 1 */ DEF(WeakMap, "WeakMap") /* Map + 2 */ DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Iterator, "Iterator") +DEF(IteratorHelper, "Iterator Helper") +DEF(IteratorWrap, "Iterator Wrap") DEF(Map_Iterator, "Map Iterator") DEF(Set_Iterator, "Set Iterator") DEF(Array_Iterator, "Array Iterator") @@ -250,6 +256,7 @@ DEF(TypeError, "TypeError") DEF(URIError, "URIError") DEF(InternalError, "InternalError") +DEF(AggregateError, "AggregateError") /* private symbols */ DEF(Private_brand, "<brand>") /* symbols */ @@ -266,8 +273,5 @@ DEF(Symbol_species, "Symbol.species") DEF(Symbol_unscopables, "Symbol.unscopables") DEF(Symbol_asyncIterator, "Symbol.asyncIterator") -#ifdef CONFIG_BIGNUM -DEF(Symbol_operatorSet, "Symbol.operatorSet") -#endif - + #endif /* DEF */
View file
gpac-2.4.0.tar.gz/src/quickjs/quickjs-libc.c -> gpac-26.02.0.tar.gz/src/quickjs/quickjs-libc.c
Changed
@@ -50,7 +50,6 @@ #else #include <sys/utime.h> #include <sys/timeb.h> -#include <gpac/tools.h> #include <io.h> #include <direct.h> #endif @@ -60,8 +59,15 @@ #include <sys/ioctl.h> #include <sys/wait.h> +#if defined(__FreeBSD__) +extern char **environ; +#endif + +#if defined(__APPLE__) || defined(__FreeBSD__) +typedef sig_t sighandler_t; +#endif + #if defined(__APPLE__) -typedef sig_t mysighandler_t; #if !defined(environ) #ifdef GPAC_CONFIG_IOS char *environ1 = {NULL}; @@ -73,7 +79,7 @@ #else /* __APPLE__ */ extern char **environ; -typedef void (*mysighandler_t)(int sig_num); +typedef void (*sighandler_t)(int sig_num); #endif #endif @@ -113,9 +119,11 @@ /* enable the os.Worker API. We rely on GF_Thread */ #ifndef GPAC_DISABLE_THREADS + +/* enable the os.Worker API. It relies on POSIX threads */ #define USE_WORKER -#endif +#endif #ifdef USE_WORKER #include <gpac/thread.h> @@ -138,11 +146,15 @@ #define js_std_dump_error1 js_dump_error_exc //use our own loader -JSModuleDef *qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque); +JSModuleDef *qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque, JSValueConst attributes); #define js_module_loader qjs_module_loader #endif //GPAC_HAS_QJS +#if !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif + /* TODO: - add socket calls */ @@ -161,7 +173,7 @@ typedef struct { struct list_head link; - BOOL has_object; + int timer_id; int64_t timeout; JSValue func; } JSOSTimer; @@ -175,14 +187,22 @@ size_t sab_tab_len; } JSWorkerMessage; +typedef struct JSWaker { +#ifdef _WIN32 + HANDLE handle; +#else + int read_fd; + int write_fd; +#endif +} JSWaker; + typedef struct { int ref_count; #ifdef USE_WORKER GF_Mutex *mutex; #endif struct list_head msg_queue; /* list of JSWorkerMessage.link */ - int read_fd; - int write_fd; + JSWaker waker; } JSWorkerMessagePipe; typedef struct { @@ -191,12 +211,20 @@ JSValue on_message_func; } JSWorkerMessageHandler; +typedef struct { + struct list_head link; + JSValue promise; + JSValue reason; +} JSRejectedPromiseEntry; + typedef struct JSThreadState { struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ struct list_head os_timers; /* list of JSOSTimer.link */ struct list_head port_list; /* list of JSWorkerMessageHandler.link */ + struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */ int eval_script_recurse; /* only used in the main thread */ + int next_timer_id; /* for setTimeout() */ /* not used in the main thread */ JSWorkerMessagePipe *recv_pipe, *send_pipe; int terminated; @@ -215,6 +243,7 @@ return (c >= '0' && c <= '9'); } +/* XXX: use 'o' and 'O' for object using JS_PrintValue() ? */ static JSValue js_printf_internal(JSContext *ctx, int argc, JSValueConst *argv, FILE *fp) { @@ -222,7 +251,7 @@ uint8_t cbufUTF8_CHAR_LEN_MAX+1; JSValue res; DynBuf dbuf; - const char *fmt_str; + const char *fmt_str = NULL; const uint8_t *fmt, *fmt_end; const uint8_t *p; char *q; @@ -323,7 +352,7 @@ string_arg = JS_ToCString(ctx, argvi++); if (!string_arg) goto fail; - int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); + int32_arg = unicode_from_utf8((const uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); JS_FreeCString(ctx, string_arg); } else { if (JS_ToInt32(ctx, &int32_arg, argvi++)) @@ -427,6 +456,7 @@ return res; fail: + JS_FreeCString(ctx, fmt_str); dbuf_free(&dbuf); return JS_EXCEPTION; } @@ -648,20 +678,109 @@ } +static int json_module_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue val; + val = JS_GetModulePrivateValue(ctx, m); + JS_SetModuleExport(ctx, m, "default", val); + return 0; +} + +JSModuleDef *create_json_module(JSContext *ctx, const char *module_name, JSValue val) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, json_module_init); + if (!m) { + JS_FreeValue(ctx, val); + return NULL; + } + /* only export the "default" symbol which will contain the JSON object */ + JS_AddModuleExport(ctx, m, "default"); + JS_SetModulePrivateValue(ctx, m, val); + return m; +} + +#ifndef GPAC_DISABLE_QJS_LIBC + + +/* in order to conform with the specification, only the keys should be + tested and not the associated values. */ +int js_module_check_attributes(JSContext *ctx, void *opaque, + JSValueConst attributes) +{ + JSPropertyEnum *tab; + uint32_t i, len; + int ret; + const char *cstr; + size_t cstr_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, attributes, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) + return -1; + ret = 0; + for(i = 0; i < len; i++) { + cstr = JS_AtomToCStringLen(ctx, &cstr_len, tabi.atom); + if (!cstr) { + ret = -1; + break; + } + if (!(cstr_len == 4 && !memcmp(cstr, "type", cstr_len))) { + JS_ThrowTypeError(ctx, "import attribute '%s' is not supported", cstr); + ret = -1; + } + JS_FreeCString(ctx, cstr); + if (ret) + break; + } + JS_FreePropertyEnum(ctx, tab, len); + return ret; +} +#endif + +/* return > 0 if the attributes indicate a JSON module */ +int js_module_test_json(JSContext *ctx, JSValueConst attributes) +{ + JSValue str; + const char *cstr; + size_t len; + BOOL res; + + if (JS_IsUndefined(attributes)) + return FALSE; + str = JS_GetPropertyStr(ctx, attributes, "type"); + if (!JS_IsString(str)) + return FALSE; + cstr = JS_ToCStringLen(ctx, &len, str); + JS_FreeValue(ctx, str); + if (!cstr) + return FALSE; + /* XXX: raise an error if unknown type ? */ + if (len == 4 && !memcmp(cstr, "json", len)) { + res = 1; + } else if (len == 5 && !memcmp(cstr, "json5", len)) { + res = 2; + } else { + res = 0; + } + JS_FreeCString(ctx, cstr); + return res; +} + #ifndef GPAC_DISABLE_QJS_LIBC #ifndef GPAC_HAS_QJS + JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque) + const char *module_name, void *opaque, + JSValueConst attributes) { JSModuleDef *m; - + int res; + if (has_suffix(module_name, ".so")) { m = js_module_loader_so(ctx, module_name); } else { size_t buf_len; uint8_t *buf; - JSValue func_val; buf = js_load_file(ctx, &buf_len, module_name); if (!buf) { @@ -670,17 +789,36 @@ return NULL; } - /* compile the module */ - func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - js_free(ctx, buf); - if (JS_IsException(func_val)) - return NULL; - /* XXX: could propagate the exception */ - js_module_set_import_meta(ctx, func_val, TRUE, FALSE); - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); + res = js_module_test_json(ctx, attributes); + if (has_suffix(module_name, ".json") || res > 0) { + /* compile as JSON or JSON5 depending on "type" */ + JSValue val; + int flags; + if (res == 2) + flags = JS_PARSE_JSON_EXT; + else + flags = 0; + val = JS_ParseJSON2(ctx, (char *)buf, buf_len, module_name, flags); + js_free(ctx, buf); + if (JS_IsException(val)) + return NULL; + m = create_json_module(ctx, module_name, val); + if (!m) + return NULL; + } else { + JSValue func_val; + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + /* XXX: could propagate the exception */ + js_module_set_import_meta(ctx, func_val, TRUE, FALSE); + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } } return m; } @@ -839,6 +977,7 @@ JSValue ret; JSValueConst options_obj; BOOL backtrace_barrier = FALSE; + BOOL is_async = FALSE; int flags; if (argc >= 2) { @@ -846,6 +985,9 @@ if (get_bool_option(ctx, &backtrace_barrier, options_obj, "backtrace_barrier")) return JS_EXCEPTION; + if (get_bool_option(ctx, &is_async, options_obj, + "async")) + return JS_EXCEPTION; } str = JS_ToCStringLen(ctx, &len, argv0); @@ -858,6 +1000,8 @@ flags = JS_EVAL_TYPE_GLOBAL; if (backtrace_barrier) flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; + if (is_async) + flags |= JS_EVAL_FLAG_ASYNC; ret = JS_Eval(ctx, str, len, "<evalScript>", flags); JS_FreeCString(ctx, str); if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { @@ -867,7 +1011,7 @@ /* convert the uncatchable "interrupted" error into a normal error so that it can be caught by the REPL */ if (JS_IsException(ret)) - JS_ResetUncatchableError(ctx); + JS_SetUncatchableException(ctx, FALSE); } return ret; } @@ -1146,6 +1290,19 @@ return js_printf_internal(ctx, argc, argv, f); } +static void js_print_value_write(void *opaque, const char *buf, size_t len) +{ + FILE *fo = opaque; + fwrite(buf, 1, len, fo); +} + +static JSValue js_std_file_printObject(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_PrintValue(ctx, js_print_value_write, stdout, argv0, NULL); + return JS_UNDEFINED; +} + static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1354,7 +1511,7 @@ /* urlGet */ -#define URL_GET_PROGRAM "curl -s -i" +#define URL_GET_PROGRAM "curl -s -i --" #define URL_GET_BUF_SIZE 4096 static int http_get_header_line(FILE *f, char *buf, size_t buf_size, @@ -1400,7 +1557,7 @@ DynBuf header_buf_s, *header_buf = &header_buf_s; char *buf; size_t i, len; - int c, status; + int status; JSValue response = JS_UNDEFINED, ret_obj; JSValueConst options_obj; FILE *f; @@ -1427,16 +1584,25 @@ } js_std_dbuf_init(ctx, &cmd_buf); - dbuf_printf(&cmd_buf, "%s ''", URL_GET_PROGRAM); - len = strlen(url); - for(i = 0; i < len; i++) { - c = urli; - if (c == '\'' || c == '\\') + dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); + for(i = 0; urli != '\0'; i++) { + unsigned char c = urli; + switch (c) { + case '\'': + /* shell single quoted string does not support \' */ + dbuf_putstr(&cmd_buf, "'\\''"); + break; + case '': case '': case '{': case '}': case '\\': + /* prevent interpretation by curl as range or set specification */ dbuf_putc(&cmd_buf, '\\'); - dbuf_putc(&cmd_buf, c); + /* FALLTHROUGH */ + default: + dbuf_putc(&cmd_buf, c); + break; + } } JS_FreeCString(ctx, url); - dbuf_putstr(&cmd_buf, "''"); + dbuf_putstr(&cmd_buf, "'"); dbuf_putc(&cmd_buf, '\0'); if (dbuf_error(&cmd_buf)) { dbuf_free(&cmd_buf); @@ -1586,6 +1752,7 @@ JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), + JS_CFUNC_DEF("__printObject", 1, js_std_file_printObject ), }; static const JSCFunctionListEntry js_std_file_proto_funcs = { @@ -1715,7 +1882,7 @@ } static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) + int argc, JSValueConst *argv, int magic) { int fd; uint64_t pos, len; @@ -1755,7 +1922,7 @@ int fd; if (JS_ToInt32(ctx, &fd, argv0)) return JS_EXCEPTION; - return JS_NewBool(ctx, (isatty(fd) != 0)); + return JS_NewBool(ctx, isatty(fd)); } #if defined(_WIN32) @@ -1864,7 +2031,7 @@ #endif /* !_WIN32 */ static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv) { const char *filename; int ret; @@ -2005,7 +2172,7 @@ } #if defined(_WIN32) -typedef void (*mysighandler_t)(int sig_num); +typedef void (*sighandler_t)(int sig_num); #endif static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, @@ -2016,7 +2183,7 @@ JSOSSignalHandler *sh; uint32_t sig_num; JSValueConst func; - mysighandler_t handler; + sighandler_t handler; if (!is_main_thread(rt)) return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); @@ -2062,15 +2229,25 @@ clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); } +static int64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; +} + #elif defined(_WIN32) && defined(_MSC_VER) -/* more portable, but does not work if the date is updated */ +//more portable, but does not work if the date is updated static int64_t get_time_ms(void) { struct _timeb tb; _ftime(&tb); return (int64_t)tb.time * 1000 + (tb.millitm); - +} +static int64_t get_time_ns(void) +{ + return gf_sys_clock_high_res() * 1000; } #else @@ -2081,43 +2258,28 @@ gettimeofday(&tv, NULL); return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); } + +static int64_t get_time_ns(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000000000 + (tv.tv_usec * 1000); +} #endif -static void unlink_timer(JSRuntime *rt, JSOSTimer *th) +static JSValue js_os_now(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - if (th->link.prev) { - list_del(&th->link); - th->link.prev = th->link.next = NULL; - } + return JS_NewFloat64(ctx, (double)get_time_ns() / 1e6); } static void free_timer(JSRuntime *rt, JSOSTimer *th) { + list_del(&th->link); JS_FreeValueRT(rt, th->func); js_free_rt(rt, th); } -static JSClassID js_os_timer_class_id; - -static void js_os_timer_finalizer(JSRuntime *rt, JSValue val) -{ - JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id); - if (th) { - th->has_object = FALSE; - if (!th->link.prev) - free_timer(rt, th); - } -} - -static void js_os_timer_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id); - if (th) { - JS_MarkValue(rt, th->func, mark_func); - } -} - static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -2126,44 +2288,87 @@ int64_t delay; JSValueConst func; JSOSTimer *th; - JSValue obj; func = argv0; if (!JS_IsFunction(ctx, func)) return JS_ThrowTypeError(ctx, "not a function"); if (JS_ToInt64(ctx, &delay, argv1)) return JS_EXCEPTION; - obj = JS_NewObjectClass(ctx, js_os_timer_class_id); - if (JS_IsException(obj)) - return obj; th = js_mallocz(ctx, sizeof(*th)); - if (!th) { - JS_FreeValue(ctx, obj); + if (!th) return JS_EXCEPTION; - } - th->has_object = TRUE; + th->timer_id = ts->next_timer_id; + if (ts->next_timer_id == INT32_MAX) + ts->next_timer_id = 1; + else + ts->next_timer_id++; th->timeout = get_time_ms() + delay; th->func = JS_DupValue(ctx, func); list_add_tail(&th->link, &ts->os_timers); - JS_SetOpaque(obj, th); - return obj; + return JS_NewInt32(ctx, th->timer_id); +} + +static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) +{ + struct list_head *el; + if (timer_id <= 0) + return NULL; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + if (th->timer_id == timer_id) + return th; + } + return NULL; } static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSOSTimer *th = JS_GetOpaque2(ctx, argv0, js_os_timer_class_id); - if (!th) + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSTimer *th; + int timer_id; + + if (JS_ToInt32(ctx, &timer_id, argv0)) return JS_EXCEPTION; - unlink_timer(JS_GetRuntime(ctx), th); + th = find_timer_by_id(ts, timer_id); + if (!th) + return JS_UNDEFINED; + free_timer(rt, th); return JS_UNDEFINED; } -static JSClassDef js_os_timer_class = { - "OSTimer", - .finalizer = js_os_timer_finalizer, - .gc_mark = js_os_timer_mark, -}; +/* return a promise */ +static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int64_t delay; + JSOSTimer *th; + JSValue promise, resolving_funcs2; + + if (JS_ToInt64(ctx, &delay, argv0)) + return JS_EXCEPTION; + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + + th = js_mallocz(ctx, sizeof(*th)); + if (!th) { + JS_FreeValue(ctx, promise); + JS_FreeValue(ctx, resolving_funcs0); + JS_FreeValue(ctx, resolving_funcs1); + return JS_EXCEPTION; + } + th->timer_id = -1; + th->timeout = get_time_ms() + delay; + th->func = JS_DupValue(ctx, resolving_funcs0); + list_add_tail(&th->link, &ts->os_timers); + JS_FreeValue(ctx, resolving_funcs0); + JS_FreeValue(ctx, resolving_funcs1); + return promise; +} static void call_handler(JSContext *ctx, JSValueConst func) { @@ -2181,82 +2386,145 @@ #ifdef USE_WORKER +#ifdef _WIN32 + +static int js_waker_init(JSWaker *w) +{ + w->handle = CreateEvent(NULL, TRUE, FALSE, NULL); + return w->handle ? 0 : -1; +} + +static void js_waker_signal(JSWaker *w) +{ + SetEvent(w->handle); +} + +static void js_waker_clear(JSWaker *w) +{ + ResetEvent(w->handle); +} + +static void js_waker_close(JSWaker *w) +{ + CloseHandle(w->handle); + w->handle = INVALID_HANDLE_VALUE; +} + +#else // !_WIN32 + +static int js_waker_init(JSWaker *w) +{ + int fds2; + + if (pipe(fds) < 0) + return -1; + w->read_fd = fds0; + w->write_fd = fds1; + return 0; +} + +static void js_waker_signal(JSWaker *w) +{ + int ret; + + for(;;) { + ret = write(w->write_fd, "", 1); + if (ret == 1) + break; + if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + break; + } +} + +static void js_waker_clear(JSWaker *w) +{ + uint8_t buf16; + int ret; + + for(;;) { + ret = read(w->read_fd, buf, sizeof(buf)); + if (ret >= 0) + break; + if (errno != EAGAIN && errno != EINTR) + break; + } +} + +static void js_waker_close(JSWaker *w) +{ + close(w->read_fd); + close(w->write_fd); + w->read_fd = -1; + w->write_fd = -1; +} + +#endif // _WIN32 + static void js_free_message(JSWorkerMessage *msg); /* return 1 if a message was handled, 0 if no message */ static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) + JSWorkerMessageHandler *port) { - JSWorkerMessagePipe *ps = port->recv_pipe; - int ret; - struct list_head *el; - JSWorkerMessage *msg; - JSValue obj, data_obj, func, retval; - - gf_mx_p(ps->mutex); - if (!list_empty(&ps->msg_queue)) { - el = ps->msg_queue.next; - msg = list_entry(el, JSWorkerMessage, link); - - /* remove the message from the queue */ - list_del(&msg->link); - - if (list_empty(&ps->msg_queue)) { - uint8_t buf16; - int ret; - for (;;) { - ret = read(ps->read_fd, buf, sizeof(buf)); - if (ret >= 0) - break; - if (errno != EAGAIN && errno != EINTR) - break; - } - } + JSWorkerMessagePipe *ps = port->recv_pipe; + int ret; + struct list_head *el; + JSWorkerMessage *msg; + JSValue obj, data_obj, func, retval; - gf_mx_v(ps->mutex); + gf_mx_p(ps->mutex); + if (!list_empty(&ps->msg_queue)) { + el = ps->msg_queue.next; + msg = list_entry(el, JSWorkerMessage, link); - data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, - JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); + /* remove the message from the queue */ + list_del(&msg->link); - js_free_message(msg); + if (list_empty(&ps->msg_queue)) + js_waker_clear(&ps->waker); - if (JS_IsException(data_obj)) - goto fail; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, data_obj); - goto fail; - } - JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); + gf_mx_v(ps->mutex); - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func = JS_DupValue(ctx, port->on_message_func); - retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, func); - if (JS_IsException(retval)) { - fail: - js_std_dump_error(ctx); - } - else { - JS_FreeValue(ctx, retval); - } - ret = 1; - } - else { - gf_mx_v(ps->mutex); - ret = 0; - } - return ret; + data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, + JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); + + js_free_message(msg); + + if (JS_IsException(data_obj)) + goto fail; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, data_obj); + goto fail; + } + JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); + + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func = JS_DupValue(ctx, port->on_message_func); + retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, func); + if (JS_IsException(retval)) { + fail: + js_std_dump_error(ctx); + } else { + JS_FreeValue(ctx, retval); + } + ret = 1; + } else { + gf_mx_v(ps->mutex); + ret = 0; + } + return ret; } #else static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) + JSWorkerMessageHandler *port) { - return 0; + return 0; } -#endif +#endif /* !USE_WORKER */ #if defined(_WIN32) @@ -2264,17 +2532,19 @@ { JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int min_delay, console_fd; + int min_delay, count; int64_t cur_time, delay; JSOSRWHandler *rh; struct list_head *el; + HANDLE handlesMAXIMUM_WAIT_OBJECTS; // 64 /* XXX: handle signals if useful */ - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && list_empty(&ts->port_list)) - return -1; /* no more events */ - - /* XXX: only timers and basic console input are supported */ + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && + list_empty(&ts->port_list)) { + return -1; /* no more events */ + } + if (!list_empty(&ts->os_timers)) { cur_time = get_time_ms(); min_delay = 10000; @@ -2286,83 +2556,80 @@ /* the timer expired */ func = th->func; th->func = JS_UNDEFINED; - unlink_timer(rt, th); - if (!th->has_object) - free_timer(rt, th); + free_timer(rt, th); call_handler(ctx, func); JS_FreeValue(ctx, func); return 0; } else if (delay < min_delay) { - min_delay = (int) delay; + min_delay = delay; } } } else { - min_delay = -1; - } + //min_delay = -1; + //we need to exit - max 1sec wait + min_delay = 1000; + } - console_fd = -1; + count = 0; list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); if (rh->fd == 0 && !JS_IsNull(rh->rw_func0)) { - console_fd = rh->fd; - break; + handlescount++ = (HANDLE)_get_osfhandle(rh->fd); // stdin + if (count == (int)countof(handles)) + break; } } - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - gf_mx_p(ps->mutex); - if (!list_empty(&ps->msg_queue)) { - console_fd = ps->read_fd; - gf_mx_v(ps->mutex); - break; - } - gf_mx_v(ps->mutex); - } - } + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (JS_IsNull(port->on_message_func)) + continue; + handlescount++ = port->recv_pipe->waker.handle; + if (count == (int)countof(handles)) + break; + } - if (console_fd >= 0) { - DWORD ti, ret; - HANDLE handle; - if (min_delay == -1) - ti = INFINITE; - else - ti = min_delay; - handle = (HANDLE)_get_osfhandle(console_fd); - ret = WaitForSingleObject(handle, ti); - if (ret == WAIT_OBJECT_0) { + if (count > 0) { + DWORD ret, timeout = INFINITE; + if (min_delay != -1) + timeout = min_delay; + ret = WaitForMultipleObjects(count, handles, FALSE, timeout); + + if (ret < count) { list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == console_fd && !JS_IsNull(rh->rw_func0)) { + if (rh->fd == 0 && !JS_IsNull(rh->rw_func0)) { call_handler(ctx, rh->rw_func0); /* must stop because the list may have been modified */ - break; + goto done; } } - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (ps->read_fd == console_fd) { - if (handle_posted_message(rt, ctx, port)) - break; - } - } - } - } - } + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (ps->waker.handle == handlesret) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } + } + } + } + } + //do not block if main thread ! - else if (!ts->recv_pipe || ts->terminated) { + if (!ts->recv_pipe || ts->terminated) { return -1; - } + } + +done: return 0; } -#else +#else + static int js_os_poll(JSContext *ctx) { JSRuntime *rt = JS_GetRuntime(ctx); @@ -2406,9 +2673,7 @@ /* the timer expired */ func = th->func; th->func = JS_UNDEFINED; - unlink_timer(rt, th); - if (!th->has_object) - free_timer(rt, th); + free_timer(rt, th); call_handler(ctx, func); JS_FreeValue(ctx, func); return 0; @@ -2427,7 +2692,7 @@ #else tvp = NULL; #endif - } + } FD_ZERO(&rfds); FD_ZERO(&wfds); @@ -2445,8 +2710,8 @@ JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (!JS_IsNull(port->on_message_func)) { JSWorkerMessagePipe *ps = port->recv_pipe; - fd_max = max_int(fd_max, ps->read_fd); - FD_SET(ps->read_fd, &rfds); + fd_max = max_int(fd_max, ps->waker.read_fd); + FD_SET(ps->waker.read_fd, &rfds); } } @@ -2472,18 +2737,19 @@ JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (!JS_IsNull(port->on_message_func)) { JSWorkerMessagePipe *ps = port->recv_pipe; - if (FD_ISSET(ps->read_fd, &rfds)) { + if (FD_ISSET(ps->waker.read_fd, &rfds)) { if (handle_posted_message(rt, ctx, port)) goto done; } } } } - //do not block if main thread ! + + //do not block if main thread ! else if (!ts->recv_pipe || ts->terminated) { return -1; } -done: + done: return 0; } #endif /* !_WIN32 */ @@ -2642,12 +2908,14 @@ else res = stat(path, &st); #endif + if (res < 0) + err = errno; + else + err = 0; JS_FreeCString(ctx, path); if (res < 0) { - err = errno; obj = JS_NULL; } else { - err = 0; obj = JS_NewObject(ctx); if (JS_IsException(obj)) return JS_EXCEPTION; @@ -2758,7 +3026,7 @@ /* sleep(delay_ms) */ static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv) { int64_t delay; int ret; @@ -2822,7 +3090,7 @@ #if !defined(_WIN32) static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv) { const char *target, *linkpath; int err; @@ -2910,9 +3178,7 @@ JS_FreeCString(ctx, str); } done: - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tabi.atom); - js_free(ctx, tab); + JS_FreePropertyEnum(ctx, tab, len); return envp; fail: if (envp) { @@ -3106,7 +3372,6 @@ } if (pid == 0) { /* child */ - int fd_max = sysconf(_SC_OPEN_MAX); /* remap the stdin/stdout/stderr handles if necessary */ for(i = 0; i < 3; i++) { @@ -3115,9 +3380,28 @@ _exit(127); } } +#if defined(HAVE_CLOSEFROM) + /* closefrom() is available on many recent unix systems: + Linux with glibc 2.34+, Solaris 9+, FreeBSD 7.3+, + NetBSD 3.0+, OpenBSD 3.5+. + Linux with the musl libc and macOS don't have it. + */ - for(i = 3; i < fd_max; i++) - close(i); + closefrom(3); +#else + { + /* Close the file handles manually, limit to 1024 to avoid + costly loop on linux Alpine where sysconf(_SC_OPEN_MAX) + returns a huge value 1048576. + Patch inspired by nicolas-duteil-nova. See also: + https://stackoverflow.com/questions/73229353/ + https://stackoverflow.com/questions/899038/#918469 + */ + int fd_max = min_int(sysconf(_SC_OPEN_MAX), 1024); + for(i = 3; i < fd_max; i++) + close(i); + } +#endif if (cwd) { if (chdir(cwd) < 0) _exit(127); @@ -3178,6 +3462,13 @@ goto done; } +/* getpid() -> pid */ +static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewInt32(ctx, getpid()); +} + /* waitpid(pid, block) -> pid, status */ static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3291,7 +3582,7 @@ char *exec_argv = NULL, *file = NULL, *cwd = NULL; char *envp = NULL; uint32_t exec_argc, i; - int ret; + DWORD ret; BOOL block_flag = TRUE, use_path = TRUE; val = JS_GetPropertyStr(ctx, args, "length"); @@ -3403,9 +3694,9 @@ { HANDLE hProcess, hThread; int64_t lval; - int status, options, ret, is_over=0; + int options, ret, is_over=0; JSValue obj, val; - DWORD rval; + DWORD rval, status; if (argc < 2) return JS_EXCEPTION; val = JS_GetPropertyUint32(ctx, argv0, 0); @@ -3456,6 +3747,14 @@ return obj; } +/* getpid() -> pid */ +static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewInt32(ctx, GetCurrentProcessId()); +} + + /* kill(pid, sig) */ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3520,26 +3819,26 @@ static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - int fd, ret; + int fd, ret; - if (JS_ToInt32(ctx, &fd, argv0)) - return JS_EXCEPTION; - ret = (int) js_get_errno(dup(fd)); - return JS_NewInt32(ctx, ret); + if (JS_ToInt32(ctx, &fd, argv0)) + return JS_EXCEPTION; + ret = js_get_errno(dup(fd)); + return JS_NewInt32(ctx, ret); } /* dup2(fd) */ static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - int fd, fd2, ret; + int fd, fd2, ret; - if (JS_ToInt32(ctx, &fd, argv0)) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &fd2, argv1)) - return JS_EXCEPTION; - ret = (int) js_get_errno(dup2(fd, fd2)); - return JS_NewInt32(ctx, ret); + if (JS_ToInt32(ctx, &fd, argv0)) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &fd2, argv1)) + return JS_EXCEPTION; + ret = js_get_errno(dup2(fd, fd2)); + return JS_NewInt32(ctx, ret); } @@ -3559,7 +3858,8 @@ char *filename; /* module filename */ char *basename; /* module base name */ JSWorkerMessagePipe *recv_pipe, *send_pipe; - JSWorkerData *worker; + int strip_flags; + JSWorkerData *worker; } WorkerFuncArgs; typedef struct { @@ -3612,24 +3912,20 @@ static JSWorkerMessagePipe *js_new_message_pipe(void) { JSWorkerMessagePipe *ps; - int pipe_fds2; - char mxName200; - - if (pipe(pipe_fds) < 0) - return NULL; ps = malloc(sizeof(*ps)); - if (!ps) { - close(pipe_fds0); - close(pipe_fds1); + if (!ps) + return NULL; + if (js_waker_init(&ps->waker)) { + free(ps); return NULL; } ps->ref_count = 1; init_list_head(&ps->msg_queue); + + char mxName100; sprintf(mxName, "Worker%pMx", ps); ps->mutex = gf_mx_new(mxName); - ps->read_fd = pipe_fds0; - ps->write_fd = pipe_fds1; return ps; } @@ -3667,9 +3963,9 @@ msg = list_entry(el, JSWorkerMessage, link); js_free_message(msg); } + + js_waker_close(&ps->waker); gf_mx_del(ps->mutex); - close(ps->read_fd); - close(ps->write_fd); free(ps); } } @@ -3715,20 +4011,23 @@ JSWorkerData *worker; JSThreadState *ts; JSContext *ctx; + JSValue val; rt = JS_NewRuntime(); if (rt == NULL) { fprintf(stderr, "JS_NewRuntime failure"); exit(1); } + JS_SetStripInfo(rt, args->strip_flags); js_std_init_handlers(rt); - JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL); /* set the pipe to communicate with the parent */ ts = JS_GetRuntimeOpaque(rt); ts->recv_pipe = args->recv_pipe; ts->send_pipe = args->send_pipe; + worker = args->worker; worker->ts = ts; @@ -3749,13 +4048,17 @@ js_std_add_helpers(ctx, -1, NULL); #endif - if (!JS_RunModule(ctx, args->basename, args->filename)) - js_std_dump_error(ctx); + val = JS_LoadModule(ctx, args->basename, args->filename); free(args->filename); free(args->basename); free(args); + val = js_std_await(ctx, val); + if (JS_IsException(val)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, val); js_std_loop(ctx); + worker->ts = NULL; worker->msg_handler = NULL; @@ -3846,6 +4149,8 @@ if (!args->send_pipe) goto oom_fail; + args->strip_flags = JS_GetStripInfo(rt); + obj = js_worker_ctor_internal(ctx, new_target, args->send_pipe, args->recv_pipe); if (JS_IsException(obj)) @@ -3913,10 +4218,12 @@ memcpy(msg->data, data, data_len); msg->data_len = data_len; - msg->sab_tab = malloc(sizeof(msg->sab_tab0) * sab_tab_len); - if (!msg->sab_tab) - goto fail; - memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab0) * sab_tab_len); + if (sab_tab_len > 0) { + msg->sab_tab = malloc(sizeof(msg->sab_tab0) * sab_tab_len); + if (!msg->sab_tab) + goto fail; + memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab0) * sab_tab_len); + } msg->sab_tab_len = sab_tab_len; js_free(ctx, data); @@ -3930,17 +4237,8 @@ ps = worker->send_pipe; gf_mx_p(ps->mutex); /* indicate that data is present */ - if (list_empty(&ps->msg_queue)) { - uint8_t ch = '\0'; - int ret; - for(;;) { - ret = write(ps->write_fd, &ch, 1); - if (ret == 1) - break; - if (ret < 0 && (errno != EAGAIN || errno != EINTR)) - break; - } - } + if (list_empty(&ps->msg_queue)) + js_waker_signal(&ps->waker); list_add_tail(&msg->link, &ps->msg_queue); gf_mx_v(ps->mutex); return JS_UNDEFINED; @@ -3970,7 +4268,7 @@ port = worker->msg_handler; if (JS_IsNull(func)) { if (port) { - js_free_port(rt, port); + js_free_port(rt, port); worker->msg_handler = NULL; } } else { @@ -4075,8 +4373,10 @@ OS_FLAG(SIGTTIN), OS_FLAG(SIGTTOU), #endif + JS_CFUNC_DEF("now", 0, js_os_now ), JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), + JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), JS_CFUNC_DEF("chdir", 0, js_os_chdir ), @@ -4107,7 +4407,8 @@ #else JS_PROP_INT32_DEF("WNOHANG", 1, JS_PROP_CONFIGURABLE), #endif - JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("getpid", 0, js_os_getpid ), JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), JS_CFUNC_DEF("pipe", 0, js_os_pipe ), JS_CFUNC_DEF("kill", 2, js_os_kill ), @@ -4119,10 +4420,6 @@ { os_poll_func = js_os_poll; - /* OSTimer class */ - JS_NewClassID(&js_os_timer_class_id); - JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class); - #ifdef USE_WORKER { JSRuntime *rt = JS_GetRuntime(ctx); @@ -4171,28 +4468,43 @@ /**********************************************************/ #ifndef GPAC_HAS_QJS static JSValue js_print(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv) { int i; - const char *str; - size_t len; - + JSValueConst v; + for(i = 0; i < argc; i++) { if (i != 0) putchar(' '); - str = JS_ToCStringLen(ctx, &len, argvi); - if (!str) - return JS_EXCEPTION; - fwrite(str, 1, len, stdout); - JS_FreeCString(ctx, str); + v = argvi; + if (JS_IsString(v)) { + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, stdout); + JS_FreeCString(ctx, str); + } else { + JS_PrintValue(ctx, js_print_value_write, stdout, v, NULL); + } } putchar('\n'); return JS_UNDEFINED; } +static JSValue js_console_log(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret; + ret = js_print(ctx, this_val, argc, argv); + fflush(stdout); + return ret; +} + void js_std_add_helpers(JSContext *ctx, int argc, char **argv) { - JSValue global_obj, console, args; + JSValue global_obj, console, args, performance; int i; /* XXX: should these global definitions be enumerable? */ @@ -4200,9 +4512,14 @@ console = JS_NewObject(ctx); JS_SetPropertyStr(ctx, console, "log", - JS_NewCFunction(ctx, js_print, "log", 1)); + JS_NewCFunction(ctx, js_console_log, "log", 1)); JS_SetPropertyStr(ctx, global_obj, "console", console); + performance = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, performance, "now", + JS_NewCFunction(ctx, js_os_now, "now", 0)); + JS_SetPropertyStr(ctx, global_obj, "performance", performance); + /* same methods as the mozilla JS shell */ if (argc >= 0) { args = JS_NewArray(ctx); @@ -4235,6 +4552,8 @@ init_list_head(&ts->os_signal_handlers); init_list_head(&ts->os_timers); init_list_head(&ts->port_list); + init_list_head(&ts->rejected_promise_list); + ts->next_timer_id = 1; JS_SetRuntimeOpaque(rt, ts); @@ -4268,9 +4587,14 @@ list_for_each_safe(el, el1, &ts->os_timers) { JSOSTimer *th = list_entry(el, JSOSTimer, link); - unlink_timer(rt, th); - if (!th->has_object) - free_timer(rt, th); + free_timer(rt, th); + } + + list_for_each_safe(el, el1, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + JS_FreeValueRT(rt, rp->promise); + JS_FreeValueRT(rt, rp->reason); + free(rp); } #ifdef USE_WORKER @@ -4295,37 +4619,14 @@ void js_std_free_handlers(JSRuntime *rt) { js_std_free_handlers_ex(rt, 0); - } -//unused -#ifndef GPAC_HAS_QJS -static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) -{ - const char *str; - str = JS_ToCString(ctx, val); - if (str) { - fprintf(f, "%s\n", str); - JS_FreeCString(ctx, str); - } else { - fprintf(f, "exception\n"); - } -} +#ifndef GPAC_HAS_QJS static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) { - JSValue val; - BOOL is_error; - - is_error = JS_IsError(ctx, exception_val); - js_dump_obj(ctx, stderr, exception_val); - if (is_error) { - val = JS_GetPropertyStr(ctx, exception_val, "stack"); - if (!JS_IsUndefined(val)) { - js_dump_obj(ctx, stderr, val); - } - JS_FreeValue(ctx, val); - } + JS_PrintValue(ctx, js_print_value_write, stderr, exception_val, NULL); + fputc('\n', stderr); } void js_std_dump_error(JSContext *ctx) @@ -4336,44 +4637,136 @@ js_std_dump_error1(ctx, exception_val); JS_FreeValue(ctx, exception_val); } +#endif + +static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts, + JSValueConst promise) +{ + struct list_head *el; + + list_for_each(el, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + if (JS_SameValue(ctx, rp->promise, promise)) + return rp; + } + return NULL; +} void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, BOOL is_handled, void *opaque) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSRejectedPromiseEntry *rp; + if (!is_handled) { - fprintf(stderr, "Possibly unhandled promise rejection: "); - js_std_dump_error1(ctx, reason); + /* add a new entry if needed */ + rp = find_rejected_promise(ctx, ts, promise); + if (!rp) { + rp = malloc(sizeof(*rp)); + if (rp) { + rp->promise = JS_DupValue(ctx, promise); + rp->reason = JS_DupValue(ctx, reason); + list_add_tail(&rp->link, &ts->rejected_promise_list); + } + } + } else { + /* the rejection is handled, so the entry can be removed if present */ + rp = find_rejected_promise(ctx, ts, promise); + if (rp) { + JS_FreeValue(ctx, rp->promise); + JS_FreeValue(ctx, rp->reason); + list_del(&rp->link); + free(rp); + } } } -#endif +/* check if there are pending promise rejections. It must be done + asynchrously in case a rejected promise is handled later. Currently + we do it once the application is about to sleep. It could be done + more often if needed. */ +static void js_std_promise_rejection_check(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + struct list_head *el; + + if (unlikely(!list_empty(&ts->rejected_promise_list))) { + list_for_each(el, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + fprintf(stderr, "Possibly unhandled promise rejection: "); + js_std_dump_error1(ctx, rp->reason); + } + exit(1); + } +} /* main loop which calls the user JS callbacks */ void js_std_loop(JSContext *ctx) { - JSContext *ctx1; int err; for(;;) { /* execute the pending jobs */ for(;;) { - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), NULL); if (err <= 0) { - if (err < 0) { - js_std_dump_error(ctx1); - } + if (err < 0) + js_std_dump_error(ctx); break; } } + js_std_promise_rejection_check(ctx); + if (!os_poll_func || os_poll_func(ctx)) break; } } //not exposed for now -#ifndef GPAC_HAS_QJS +//#ifndef GPAC_HAS_QJS + +/* Wait for a promise and execute pending jobs while waiting for + it. Return the promise result or JS_EXCEPTION in case of promise + rejection. */ +JSValue js_std_await(JSContext *ctx, JSValue obj) +{ + JSValue ret; + int state; + + for(;;) { + state = JS_PromiseState(ctx, obj); + if (state == JS_PROMISE_FULFILLED) { + ret = JS_PromiseResult(ctx, obj); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_REJECTED) { + ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_PENDING) { + int err; + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), NULL); + if (err < 0) { + js_std_dump_error(ctx); + } + if (err == 0) { + js_std_promise_rejection_check(ctx); + + if (os_poll_func) + os_poll_func(ctx); + } + } else { + /* not a promise */ + ret = obj; + break; + } + } + return ret; +} void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, int load_only) @@ -4386,6 +4779,7 @@ if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { js_module_set_import_meta(ctx, obj, FALSE, FALSE); } + JS_FreeValue(ctx, obj); } else { if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { if (JS_ResolveModule(ctx, obj) < 0) { @@ -4393,8 +4787,11 @@ goto exception; } js_module_set_import_meta(ctx, obj, FALSE, TRUE); + val = JS_EvalFunction(ctx, obj); + val = js_std_await(ctx, val); + } else { + val = JS_EvalFunction(ctx, obj); } - val = JS_EvalFunction(ctx, obj); if (JS_IsException(val)) { exception: js_std_dump_error(ctx); @@ -4403,6 +4800,23 @@ JS_FreeValue(ctx, val); } } -#endif + +void js_std_eval_binary_json_module(JSContext *ctx, + const uint8_t *buf, size_t buf_len, + const char *module_name) +{ + JSValue obj; + JSModuleDef *m; + + obj = JS_ReadObject(ctx, buf, buf_len, 0); + if (JS_IsException(obj)) + goto exception; + m = create_json_module(ctx, module_name, obj); + if (!m) { + exception: + js_std_dump_error(ctx); + exit(1); + } +} #endif //GPAC_DISABLE_QJS_LIBC
View file
gpac-2.4.0.tar.gz/src/quickjs/quickjs-libc.h -> gpac-26.02.0.tar.gz/src/quickjs/quickjs-libc.h
Changed
@@ -38,16 +38,23 @@ JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); void js_std_add_helpers(JSContext *ctx, int argc, char **argv); void js_std_loop(JSContext *ctx); +JSValue js_std_await(JSContext *ctx, JSValue obj); void js_std_init_handlers(JSRuntime *rt); void js_std_free_handlers(JSRuntime *rt); void js_std_dump_error(JSContext *ctx); uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, JS_BOOL use_realpath, JS_BOOL is_main); +int js_module_test_json(JSContext *ctx, JSValueConst attributes); +int js_module_check_attributes(JSContext *ctx, void *opaque, JSValueConst attributes); JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque); + const char *module_name, void *opaque, + JSValueConst attributes); void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags); +void js_std_eval_binary_json_module(JSContext *ctx, + const uint8_t *buf, size_t buf_len, + const char *module_name); void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque);
View file
gpac-2.4.0.tar.gz/src/quickjs/quickjs-opcode.h -> gpac-26.02.0.tar.gz/src/quickjs/quickjs-opcode.h
Changed
@@ -1,6 +1,6 @@ /* * QuickJS opcode definitions - * + * * Copyright (c) 2017-2018 Fabrice Bellard * Copyright (c) 2017-2018 Charlie Gordon * @@ -110,6 +110,7 @@ DEF( return_undef, 1, 0, 0, none) DEF(check_ctor_return, 1, 1, 2, none) DEF( check_ctor, 1, 0, 0, none) +DEF( init_ctor, 1, 0, 1, none) DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ DEF( return_async, 1, 1, 0, none) @@ -120,14 +121,12 @@ DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a bytecode string */ DEF( get_super, 1, 1, 1, none) -DEF( import, 1, 1, 1, none) /* dynamic module import */ +DEF( import, 1, 2, 1, none) /* dynamic module import */ -DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */ DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ -DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ DEF( get_ref_value, 1, 2, 3, none) DEF( put_ref_value, 1, 3, 0, none) @@ -143,6 +142,7 @@ DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ DEF( get_array_el, 1, 2, 1, none) DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( get_array_el3, 1, 2, 3, none) /* obj prop -> obj prop1 value */ DEF( put_array_el, 1, 3, 0, none) DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ @@ -165,14 +165,15 @@ DEF( get_arg, 3, 0, 1, arg) DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ -DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( get_var_ref, 3, 0, 1, var_ref) DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ DEF(set_loc_uninitialized, 3, 0, 0, loc) DEF( get_loc_check, 3, 0, 1, loc) DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ DEF( put_loc_check_init, 3, 1, 0, loc) -DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(get_loc_checkthis, 3, 0, 1, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ DEF(put_var_ref_check_init, 3, 1, 0, var_ref) DEF( close_loc, 3, 0, 0, loc) @@ -182,18 +183,17 @@ DEF( catch, 5, 0, 1, label) DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ +DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ DEF( to_object, 1, 1, 1, none) //DEF( to_string, 1, 1, 1, none) DEF( to_propkey, 1, 1, 1, none) -DEF( to_propkey2, 1, 2, 2, none) DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) DEF( make_loc_ref, 7, 0, 2, atom_u16) DEF( make_arg_ref, 7, 0, 2, atom_u16) @@ -205,10 +205,10 @@ DEF(for_await_of_start, 1, 1, 3, none) DEF( for_in_next, 1, 1, 3, none) DEF( for_of_next, 2, 3, 5, u8) +DEF(for_await_of_next, 1, 3, 4, none) /* iter next catch_offset -> iter next catch_offset obj */ DEF(iterator_check_object, 1, 1, 1, none) -DEF(iterator_get_value_done, 1, 1, 2, none) +DEF(iterator_get_value_done, 1, 2, 3, none) /* catch_offset obj -> catch_offset value done */ DEF( iterator_close, 1, 3, 0, none) -DEF(iterator_close_return, 1, 4, 4, none) DEF( iterator_next, 1, 4, 4, none) DEF( iterator_call, 2, 4, 5, u8) DEF( initial_yield, 1, 0, 0, none) @@ -256,12 +256,10 @@ DEF( xor, 1, 2, 1, none) DEF( or, 1, 2, 1, none) DEF(is_undefined_or_null, 1, 1, 1, none) -#ifdef CONFIG_BIGNUM -DEF( mul_pow10, 1, 2, 1, none) -DEF( math_mod, 1, 2, 1, none) -#endif +DEF( private_in, 1, 2, 1, none) +DEF(push_bigint_i32, 5, 0, 1, i32) /* must be the last non short and non temporary opcode */ -DEF( nop, 1, 0, 0, none) +DEF( nop, 1, 0, 0, none) /* temporary opcodes: never emitted in the final bytecode */ @@ -270,6 +268,8 @@ def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ +/* the following opcodes must be in the same order as the 'with_x' and + get_var_undef, get_var and put_var opcodes */ def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ @@ -277,12 +277,15 @@ def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */ def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ -def(scope_put_private_field, 7, 1, 1, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ - +def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ +def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ +def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ +def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ - + def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ #if SHORT_OPCODES
View file
gpac-2.4.0.tar.gz/src/quickjs/quickjs.c -> gpac-26.02.0.tar.gz/src/quickjs/quickjs.c
Changed
@@ -1,8 +1,8 @@ /* * QuickJS Javascript Engine * - * Copyright (c) 2017-2021 Fabrice Bellard - * Copyright (c) 2017-2021 Charlie Gordon + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,7 +36,7 @@ #include <math.h> #if defined(__APPLE__) #include <malloc/malloc.h> -#elif defined(__linux__) || defined(__NX__) +#elif defined(__linux__) || defined(__GLIBC__) #include <malloc.h> #elif defined(__FreeBSD__) #include <malloc_np.h> @@ -46,9 +46,8 @@ #include "list.h" #include "quickjs.h" #include "libregexp.h" -#ifdef CONFIG_BIGNUM -#include "libbf.h" -#endif +#include "libunicode.h" +#include "dtoa.h" #define OPTIMIZE 1 #define SHORT_OPCODES 1 @@ -71,17 +70,16 @@ #define CONFIG_PRINTF_RNDN #endif +#include <gpac/tools.h> + /* define to include Atomics.* operations which depend on the OS threads */ #if !defined(EMSCRIPTEN) && !defined(_MSC_VER) && !defined(GPAC_CONFIG_ANDROID) && !defined(__MINGW32__) && !defined(__CYGWIN__) #define CONFIG_ATOMICS #endif -//no stack limit in GPAC doesn't work with multithreaded context switch -#if defined(GPAC_QJS_STACK_CHECK) /* enable stack limitation */ #define CONFIG_STACK_CHECK -#endif /* dump object free */ @@ -94,6 +92,7 @@ 8: dump stdlib functions 16: dump bytecode in hex 32: dump line number table + 64: dump compute_stack_size */ //#define DUMP_BYTECODE (1) /* dump the occurence of the automatic GC */ @@ -108,8 +107,10 @@ //#define DUMP_ATOMS /* dump atoms in JS_FreeContext */ //#define DUMP_SHAPES /* dump shapes in JS_FreeContext */ //#define DUMP_MODULE_RESOLVE +//#define DUMP_MODULE_EXEC //#define DUMP_PROMISE //#define DUMP_READ_OBJECT +//#define DUMP_ROPE_REBALANCE /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC @@ -122,7 +123,7 @@ #include <errno.h> #endif -#define CONFIG_VERSION "2020-11-08-rc2" +#define CONFIG_VERSION "2025-09-13" enum { /* classid tag */ /* union usage | properties */ @@ -153,24 +154,20 @@ JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ -#ifdef CONFIG_BIGNUM JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ -#endif + JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ JS_CLASS_DATAVIEW, /* u.typed_array */ -#ifdef CONFIG_BIGNUM JS_CLASS_BIG_INT, /* u.object_data */ - JS_CLASS_BIG_FLOAT, /* u.object_data */ - JS_CLASS_FLOAT_ENV, /* u.float_env */ - JS_CLASS_BIG_DECIMAL, /* u.object_data */ - JS_CLASS_OPERATOR_SET, /* u.operator_set */ -#endif JS_CLASS_MAP, /* u.map_state */ JS_CLASS_SET, /* u.map_state */ JS_CLASS_WEAKMAP, /* u.map_state */ JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ + JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ @@ -187,6 +184,8 @@ JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_WEAK_REF, + JS_CLASS_FINALIZATION_REGISTRY, JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ }; @@ -209,20 +208,36 @@ JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ } JSErrorEnum; -#define JS_MAX_LOCAL_VARS 65536 +/* the variable and scope indexes must fit on 16 bits. The (-1) and + ARG_SCOPE_END values are reserved. */ +#define JS_MAX_LOCAL_VARS 65534 #define JS_STACK_SIZE_MAX 65534 #define JS_STRING_LEN_MAX ((1 << 30) - 1) +/* strings <= this length are not concatenated using ropes. if too + small, the rope memory overhead becomes high. */ +#define JS_STRING_ROPE_SHORT_LEN 512 +/* specific threshold for initial rope use */ +#define JS_STRING_ROPE_SHORT2_LEN 8192 +/* rope depth at which we rebalance */ +#define JS_STRING_ROPE_MAX_DEPTH 60 + #if defined(_WIN32) && !defined(__GNUC__) -#define __exception /* */ +#define __exception typedef int64_t ssize_t; #else + #define __exception __attribute__((warn_unused_result)) #endif typedef struct JSShape JSShape; typedef struct JSString JSString; typedef struct JSString JSAtomStruct; +typedef struct JSObject JSObject; + +#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_GET_STRING_ROPE(v) ((JSStringRope *)JS_VALUE_GET_PTR(v)) typedef enum { JS_GC_PHASE_NONE, @@ -232,26 +247,6 @@ typedef enum OPCodeEnum OPCodeEnum; -#ifdef CONFIG_BIGNUM -/* function pointers are used for numeric operations so that it is - possible to remove some numeric types */ -typedef struct { - JSValue (*to_string)(JSContext *ctx, JSValueConst val); - JSValue (*from_string)(JSContext *ctx, const char *buf, - int radix, int flags, slimb_t *pexponent); - int (*unary_arith)(JSContext *ctx, - JSValue *pres, OPCodeEnum op, JSValue op1); - int (*binary_arith)(JSContext *ctx, OPCodeEnum op, - JSValue *pres, JSValue op1, JSValue op2); - int (*compare)(JSContext *ctx, OPCodeEnum op, - JSValue op1, JSValue op2); - /* only for bigfloat: */ - JSValue (*mul_pow10_to_float64)(JSContext *ctx, const bf_t *a, - int64_t exponent); - int (*mul_pow10)(JSContext *ctx, JSValue *sp); -} JSNumericOperations; -#endif - struct JSRuntime { JSMallocFunctions mf; JSMallocState malloc_state; @@ -277,6 +272,7 @@ struct list_head tmp_obj_list; /* used during GC */ JSGCPhaseEnum gc_phase : 8; size_t malloc_gc_threshold; + struct list_head weakref_list; /* list of JSWeakRefHeader.link */ #ifdef DUMP_LEAKS struct list_head string_list; /* list of JSString.link */ #endif @@ -286,6 +282,8 @@ uintptr_t stack_limit; /* lower stack limit */ JSValue current_exception; + /* true if the current exception cannot be catched */ + BOOL current_exception_is_uncatchable : 8; /* true if inside an out of memory error, to avoid recursing */ BOOL in_out_of_memory : 8; @@ -300,25 +298,27 @@ struct list_head job_list; /* list of JSJobEntry.link */ JSModuleNormalizeFunc *module_normalize_func; - JSModuleLoaderFunc *module_loader_func; + BOOL module_loader_has_attr; + union { + JSModuleLoaderFunc *module_loader_func; + JSModuleLoaderFunc2 *module_loader_func2; + } u; + JSModuleCheckSupportedImportAttributes *module_check_attrs; void *module_loader_opaque; + /* timestamp for internal use in module evaluation */ + int64_t module_async_evaluation_next_timestamp; BOOL can_block : 8; /* TRUE if Atomics.wait can block */ /* used to allocate, free and clone SharedArrayBuffers */ JSSharedArrayBufferFunctions sab_funcs; + /* see JS_SetStripInfo() */ + uint8_t strip_flags; /* Shape hash table */ int shape_hash_bits; int shape_hash_size; int shape_hash_count; /* number of hashed shapes */ JSShape **shape_hash; -#ifdef CONFIG_BIGNUM - bf_context_t bf_ctx; - JSNumericOperations bigint_ops; - JSNumericOperations bigfloat_ops; - JSNumericOperations bigdecimal_ops; - uint32_t operator_count; -#endif void *user_opaque; }; @@ -333,19 +333,19 @@ }; #define JS_MODE_STRICT (1 << 0) -#define JS_MODE_STRIP (1 << 1) -#define JS_MODE_MATH (1 << 2) +#define JS_MODE_ASYNC (1 << 2) /* async function */ +#define JS_MODE_BACKTRACE_BARRIER (1 << 3) /* stop backtrace before this frame */ typedef struct JSStackFrame { struct JSStackFrame *prev_frame; /* NULL if first stack frame */ JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ JSValue *arg_buf; /* arguments */ JSValue *var_buf; /* variables */ - struct list_head var_ref_list; /* list of JSVarRef.link */ + struct list_head var_ref_list; /* list of JSVarRef.var_ref_link */ const uint8_t *cur_pc; /* only used in bytecode functions : PC of the instruction after the call */ int arg_count; - int js_mode; /* 0 or JS_MODE_MATH for C functions */ + int js_mode; /* not supported for C functions */ /* only used in generators. Current stack pointer value. NULL if the function is running. */ JSValue *cur_sp; @@ -358,6 +358,7 @@ JS_GC_OBJ_TYPE_VAR_REF, JS_GC_OBJ_TYPE_ASYNC_FUNCTION, JS_GC_OBJ_TYPE_JS_CONTEXT, + JS_GC_OBJ_TYPE_MODULE, } JSGCObjectTypeEnum; /* header for GC objects. GC objects are C data structures with a @@ -366,54 +367,83 @@ struct JSGCObjectHeader { int ref_count; /* must come first, 32-bit */ JSGCObjectTypeEnum gc_obj_type : 4; - uint8_t mark : 4; /* used by the GC */ + uint8_t mark : 1; /* used by the GC */ + uint8_t dummy0: 3; uint8_t dummy1; /* not used by the GC */ uint16_t dummy2; /* not used by the GC */ struct list_head link; }; +typedef enum { + JS_WEAKREF_TYPE_MAP, + JS_WEAKREF_TYPE_WEAKREF, + JS_WEAKREF_TYPE_FINREC, +} JSWeakRefHeaderTypeEnum; + +typedef struct { + struct list_head link; + JSWeakRefHeaderTypeEnum weakref_type; +} JSWeakRefHeader; + typedef struct JSVarRef { union { JSGCObjectHeader header; /* must come first */ struct { int __gc_ref_count; /* corresponds to header.ref_count */ uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - - /* 0 : the JSVarRef is on the stack. header.link is an element - of JSStackFrame.var_ref_list. - 1 : the JSVarRef is detached. header.link has the normal meanning - */ - uint8_t is_detached : 1; - uint8_t is_arg : 1; - uint16_t var_idx; /* index of the corresponding function variable on - the stack */ + uint8_t is_detached; }; }; JSValue *pvalue; /* pointer to the value, either on the stack or to 'value' */ - JSValue value; /* used when the variable is no longer on the stack */ + union { + JSValue value; /* used when is_detached = TRUE */ + struct { + struct list_head var_ref_link; /* JSStackFrame.var_ref_list list */ + struct JSAsyncFunctionState *async_func; /* != NULL if async stack frame */ + }; /* used when is_detached = FALSE */ + }; } JSVarRef; -#ifdef CONFIG_BIGNUM -typedef struct JSFloatEnv { - limb_t prec; - bf_flags_t flags; - unsigned int status; -} JSFloatEnv; - -/* the same structure is used for big integers and big floats. Big - integers are never infinite or NaNs */ -typedef struct JSBigFloat { - JSRefCountHeader header; /* must come first, 32-bit */ - bf_t num; -} JSBigFloat; +/* bigint */ + +#if JS_LIMB_BITS == 32 + +typedef int32_t js_slimb_t; +typedef uint32_t js_limb_t; +typedef int64_t js_sdlimb_t; +typedef uint64_t js_dlimb_t; + +#define JS_LIMB_DIGITS 9 + +#else + +typedef __int128 int128_t; +typedef unsigned __int128 uint128_t; +typedef int64_t js_slimb_t; +typedef uint64_t js_limb_t; +typedef int128_t js_sdlimb_t; +typedef uint128_t js_dlimb_t; + +#define JS_LIMB_DIGITS 19 -typedef struct JSBigDecimal { - JSRefCountHeader header; /* must come first, 32-bit */ - bfdec_t num; -} JSBigDecimal; #endif +typedef struct JSBigInt { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len; /* number of limbs, >= 1 */ + js_limb_t tab; /* two's complement representation, always + normalized so that 'len' is the minimum + possible length >= 1 */ +} JSBigInt; + +/* this bigint structure can hold a 64 bit integer */ +typedef struct { + js_limb_t big_int_bufsizeof(JSBigInt) / sizeof(js_limb_t); /* for JSBigInt */ + /* must come just after */ + js_limb_t tab(64 + JS_LIMB_BITS - 1) / JS_LIMB_BITS; +} JSBigIntBuf; + typedef enum { JS_AUTOINIT_ID_PROTOTYPE, JS_AUTOINIT_ID_MODULE_NS, @@ -431,6 +461,13 @@ uint16_t binary_object_count; int binary_object_size; + /* TRUE if the array prototype is "normal": + - no small index properties which are get/set or non writable + - its prototype is Object.prototype + - Object.prototype has no small index properties which are get/set or non writable + - the prototype of Object.prototype is null (always true as it is immutable) + */ + uint8_t std_array_prototype; JSShape *array_shape; /* initial shape for Array objects */ @@ -441,7 +478,7 @@ JSValue regexp_ctor; JSValue promise_ctor; JSValue native_error_protoJS_NATIVE_ERROR_COUNT; - JSValue iterator_proto; + JSValue iterator_ctor; JSValue async_iterator_proto; JSValue array_proto_values; JSValue throw_type_error; @@ -451,15 +488,9 @@ JSValue global_var_obj; /* contains the global let/const definitions */ uint64_t random_state; -#ifdef CONFIG_BIGNUM - bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */ - JSFloatEnv fp_env; /* global FP environment */ - BOOL bignum_ext : 8; /* enable math mode */ - BOOL allow_operator_overloading : 8; -#endif + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ int interrupt_counter; - BOOL is_error_property_enabled; struct list_head loaded_modules; /* list of JSModuleDef.link */ @@ -486,11 +517,6 @@ JS_ATOM_TYPE_PRIVATE, }; -enum { - JS_ATOM_HASH_SYMBOL, - JS_ATOM_HASH_PRIVATE, -}; - typedef enum { JS_ATOM_KIND_STRING, JS_ATOM_KIND_SYMBOL, @@ -498,13 +524,14 @@ } JSAtomKindEnum; #define JS_ATOM_HASH_MASK ((1 << 30) - 1) +#define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK struct JSString { JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len : 31; uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ - /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, - for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + /* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = JS_ATOM_HASH_PRIVATE, atom_type = 3 XXX: could change encoding to have one more bit in hash */ uint32_t hash : 30; uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ @@ -518,6 +545,17 @@ } u; }; +typedef struct JSStringRope { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len; + uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */ + uint8_t depth; /* max depth of the rope tree */ + /* XXX: could reduce memory usage by using a direct pointer with + bit 0 to select rope or string */ + JSValue left; + JSValue right; /* might be the empty string */ +} JSStringRope; + typedef struct JSClosureVar { uint8_t is_local : 1; uint8_t is_arg : 1; @@ -572,6 +610,7 @@ uint8_t is_const : 1; uint8_t is_lexical : 1; uint8_t is_captured : 1; + uint8_t is_static_private : 1; /* only used during private class field parsing */ uint8_t var_kind : 4; /* see JSVarKindEnum */ /* only used during compilation: function pool index for lexical variables with var_kind = @@ -610,9 +649,9 @@ uint8_t super_allowed : 1; uint8_t arguments_allowed : 1; uint8_t has_debug : 1; - uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ uint8_t read_only_bytecode : 1; - /* XXX: 4 bits available */ + uint8_t is_direct_or_indirect_eval : 1; /* used by JS_GetScriptOrModuleName() */ + /* XXX: 10 bits available */ uint8_t *byte_code_buf; /* (self pointer) */ int byte_code_len; JSAtom func_name; @@ -629,7 +668,6 @@ struct { /* debug info, move to separate structure to save memory? */ JSAtom filename; - int line_num; int source_len; int pc2line_len; uint8_t *pc2line_buf; @@ -652,9 +690,11 @@ typedef struct JSForInIterator { JSValue obj; - BOOL is_array; - uint32_t array_length; uint32_t idx; + uint32_t atom_count; + uint8_t in_prototype_chain; + uint8_t is_array; + JSPropertyEnum *tab_atom; /* is_array = FALSE */ } JSForInIterator; typedef struct JSRegExp { @@ -671,6 +711,7 @@ typedef struct JSArrayBuffer { int byte_length; /* 0 if detached */ + int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ uint8_t detached; uint8_t shared; /* if shared, the array buffer cannot be detached */ uint8_t *data; /* NULL if detached */ @@ -683,26 +724,22 @@ struct list_head link; /* link to arraybuffer */ JSObject *obj; /* back pointer to the TypedArray/DataView object */ JSObject *buffer; /* based array buffer */ - uint32_t offset; /* offset in the array buffer */ - uint32_t length; /* length in the array buffer */ + uint32_t offset; /* byte offset in the array buffer */ + uint32_t length; /* byte length in the array buffer */ + BOOL track_rab; /* auto-track length of backing array buffer */ } JSTypedArray; typedef struct JSAsyncFunctionState { - JSValue this_val; /* 'this' generator argument */ + JSGCObjectHeader header; + JSValue this_val; /* 'this' argument */ int argc; /* number of function arguments */ BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */ + BOOL is_completed; /* TRUE if the function has returned. The stack + frame is no longer valid */ + JSValue resolving_funcs2; /* only used in JS async functions */ JSStackFrame frame; } JSAsyncFunctionState; -/* XXX: could use an object instead to avoid the - JS_TAG_ASYNC_FUNCTION tag for the GC */ -typedef struct JSAsyncFunctionData { - JSGCObjectHeader header; /* must come first */ - JSValue resolving_funcs2; - BOOL is_active; /* true if the async function state is valid */ - JSAsyncFunctionState func_state; -} JSAsyncFunctionData; - typedef enum { /* binary operators */ JS_OVOP_ADD, @@ -753,6 +790,7 @@ typedef struct JSReqModuleEntry { JSAtom module_name; JSModuleDef *module; /* used using resolution */ + JSValue attributes; /* JS_UNDEFINED or an object contains the attributes as key/value */ } JSReqModuleEntry; typedef enum JSExportTypeEnum { @@ -780,12 +818,22 @@ typedef struct JSImportEntry { int var_idx; /* closure variable index */ + BOOL is_star; /* import_name = '*' is a valid import name, so need a flag */ JSAtom import_name; int req_module_idx; /* in req_module_entries */ } JSImportEntry; +typedef enum { + JS_MODULE_STATUS_UNLINKED, + JS_MODULE_STATUS_LINKING, + JS_MODULE_STATUS_LINKED, + JS_MODULE_STATUS_EVALUATING, + JS_MODULE_STATUS_EVALUATING_ASYNC, + JS_MODULE_STATUS_EVALUATED, +} JSModuleStatus; + struct JSModuleDef { - JSRefCountHeader header; /* must come first, 32-bit */ + JSGCObjectHeader header; /* must come first */ JSAtom module_name; struct list_head link; @@ -808,21 +856,36 @@ JSValue module_ns; JSValue func_obj; /* only used for JS modules */ JSModuleInitFunc *init_func; /* only used for C modules */ + BOOL has_tla : 8; /* true if func_obj contains await */ BOOL resolved : 8; BOOL func_created : 8; - BOOL instantiated : 8; - BOOL evaluated : 8; - BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */ + JSModuleStatus status : 8; + /* temp use during js_module_link() & js_module_evaluate() */ + int dfs_index, dfs_ancestor_index; + JSModuleDef *stack_prev; + /* temp use during js_module_evaluate() */ + JSModuleDef **async_parent_modules; + int async_parent_modules_count; + int async_parent_modules_size; + int pending_async_dependencies; + BOOL async_evaluation; /* true: async_evaluation_timestamp corresponds to AsyncEvaluationOrder + false: AsyncEvaluationOrder is UNSET or DONE */ + int64_t async_evaluation_timestamp; + JSModuleDef *cycle_root; + JSValue promise; /* corresponds to spec field: capability */ + JSValue resolving_funcs2; /* corresponds to spec field: capability */ + /* true if evaluation yielded an exception. It is saved in eval_exception */ BOOL eval_has_exception : 8; JSValue eval_exception; JSValue meta_obj; /* for import.meta */ + JSValue private_value; /* private value for C modules */ }; typedef struct JSJobEntry { struct list_head link; - JSContext *ctx; + JSContext *realm; JSJobFunc *job_func; int argc; JSValue argv0; @@ -848,7 +911,6 @@ #define JS_PROP_INITIAL_SIZE 2 #define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ -#define JS_ARRAY_INITIAL_SIZE 2 typedef struct JSShapeProperty { uint32_t hash_next : 26; /* 0 if last in list */ @@ -863,10 +925,6 @@ /* true if the shape is inserted in the shape hash table. If not, JSShape.hash is not valid */ uint8_t is_hashed; - /* If true, the shape may have small array index properties 'n' with 0 - <= n <= 2^31-1. If false, the shape is guaranteed not to have - small array index properties */ - uint8_t has_small_array_index; uint32_t hash; /* current hash value */ uint32_t prop_hash_mask; int prop_size; /* allocated properties */ @@ -882,25 +940,26 @@ JSGCObjectHeader header; struct { int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + uint8_t __gc_mark : 7; /* corresponds to header.mark/gc_obj_type */ + uint8_t is_prototype : 1; /* object may be used as prototype */ uint8_t extensible : 1; uint8_t free_mark : 1; /* only used when freeing objects with cycles */ uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ - uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ + uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ uint16_t class_id; /* see JS_CLASS_x */ }; }; - /* byte offsets: 16/24 */ + /* count the number of weak references to this object. The object + structure is freed only if header.ref_count = 0 and + weakref_count = 0 */ + uint32_t weakref_count; JSShape *shape; /* prototype and property names + flag */ JSProperty *prop; /* array of properties */ - /* byte offsets: 24/40 */ - struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */ - /* byte offsets: 28/48 */ union { void *opaque; struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ @@ -908,19 +967,17 @@ struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ -#ifdef CONFIG_BIGNUM - struct JSFloatEnv *float_env; /* JS_CLASS_FLOAT_ENV */ - struct JSOperatorSetData *operator_set; /* JS_CLASS_OPERATOR_SET */ -#endif struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ + struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ - struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ @@ -953,6 +1010,7 @@ uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ } u; @@ -961,8 +1019,29 @@ JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ } u; - /* byte sizes: 40/48/72 */ }; + +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + BOOL empty : 8; /* TRUE if the record is deleted */ + struct list_head link; + struct JSMapRecord *hash_next; + JSValue key; + JSValue value; +} JSMapRecord; + +typedef struct JSMapState { + BOOL is_weak; /* TRUE if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + JSMapRecord **hash_table; + int hash_bits; + uint32_t hash_size; /* = 2 ^ hash_bits */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ + JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */ +} JSMapState; + enum { __JS_ATOM_NULL = JS_ATOM_NULL, #define DEF(name, str) JS_ATOM_ ## name, @@ -1041,29 +1120,22 @@ JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); #endif static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); -static __maybe_unused void JS_DumpString(JSRuntime *rt, - const JSString *p); +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); -static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, - JSValueConst val); -static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val); -static __maybe_unused void JS_PrintValue(JSContext *ctx, - const char *str, - JSValueConst val); +static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val); +static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val); static __maybe_unused void JS_DumpShapes(JSRuntime *rt); +static void js_dump_value_write(void *opaque, const char *buf, size_t len); static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); static void js_array_finalizer(JSRuntime *rt, JSValue val); -static void js_array_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); +static void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_object_data_finalizer(JSRuntime *rt, JSValue val); -static void js_object_data_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_c_function_finalizer(JSRuntime *rt, JSValue val); -static void js_c_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val); static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); @@ -1090,6 +1162,12 @@ static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val); static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_wrap_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val); static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); @@ -1102,20 +1180,22 @@ static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); -#ifdef CONFIG_BIGNUM -static void js_operator_set_finalizer(JSRuntime *rt, JSValue val); -static void js_operator_set_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -#endif + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); static int JS_ToBoolFree(JSContext *ctx, JSValue val); static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, JSValueConst flags); -static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, - JSValue pattern, JSValue bc); +static JSValue js_regexp_set_internal(JSContext *ctx, JSValue obj, + JSValue pattern, JSValue bc); static void gc_decref(JSRuntime *rt); static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def, JSAtom name); @@ -1128,74 +1208,50 @@ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, JSStrictEqModeEnum eq_mode); -static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2); +static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2); static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); static JSValue JS_ToObject(JSContext *ctx, JSValueConst val); static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); static JSProperty *add_property(JSContext *ctx, JSObject *p, JSAtom prop, int prop_flags); -#ifdef CONFIG_BIGNUM -static void js_float_env_finalizer(JSRuntime *rt, JSValue val); -static JSValue JS_NewBigFloat(JSContext *ctx); -static inline bf_t *JS_GetBigFloat(JSValueConst val) -{ - JSBigFloat *p = JS_VALUE_GET_PTR(val); - return &p->num; -} -static JSValue JS_NewBigDecimal(JSContext *ctx); -static inline bfdec_t *JS_GetBigDecimal(JSValueConst val) -{ - JSBigDecimal *p = JS_VALUE_GET_PTR(val); - return &p->num; -} -static JSValue JS_NewBigInt(JSContext *ctx); -static inline bf_t *JS_GetBigInt(JSValueConst val) -{ - JSBigFloat *p = JS_VALUE_GET_PTR(val); - return &p->num; -} -static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val, - BOOL convert_to_safe_integer); -static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val); static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); -static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val); -static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf); -static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val); -static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val, - BOOL allow_null_or_undefined); -static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val); -#endif JSValue JS_ThrowOutOfMemory(JSContext *ctx); static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); -static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); -static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, - JSValueConst proto_val, BOOL throw_flag); -static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); -static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj); -static int js_proxy_isArray(JSContext *ctx, JSValueConst obj); + +static int js_resolve_proxy(JSContext *ctx, JSValueConst *pval, int throw_exception); static int JS_CreateProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags); -static int js_string_memcmp(const JSString *p1, const JSString *p2, int len); -static void reset_weak_ref(JSRuntime *rt, JSObject *p); +static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, + int pos2, int len); static JSValue js_array_buffer_constructor3(JSContext *ctx, JSValueConst new_target, - uint64_t len, JSClassID class_id, + uint64_t len, uint64_t *max_len, + JSClassID class_id, uint8_t *buf, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag); +static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf); static JSValue js_typed_array_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int classid); -static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p); -static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValueConst new_target, + JSValueConst src_obj, + int classid, uint32_t len); +static BOOL typed_array_is_oob(JSObject *p); +static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValueConst obj); static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg); +static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, @@ -1206,11 +1262,11 @@ static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); -static void js_free_module_def(JSContext *ctx, JSModuleDef *m); +static void js_free_module_def(JSRuntime *rt, JSModuleDef *m); static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, JS_MarkFunc *mark_func); static JSValue js_import_meta(JSContext *ctx); -static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier); +static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, JSValueConst options); static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); static JSValue js_new_promise_capability(JSContext *ctx, JSValue *resolving_funcs, @@ -1221,6 +1277,10 @@ JSValueConst *cap_resolving_funcs); static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); +static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static BOOL js_string_eq(JSContext *ctx, + const JSString *p1, const JSString *p2); static int js_string_compare(JSContext *ctx, const JSString *p1, const JSString *p2); static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val); @@ -1232,9 +1292,7 @@ static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, JSObject *p, JSAtom prop); static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); -static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, - JS_MarkFunc *mark_func); -static void JS_AddIntrinsicBasicObjects(JSContext *ctx); +static int JS_AddIntrinsicBasicObjects(JSContext *ctx); static void js_free_shape(JSRuntime *rt, JSShape *sh); static void js_free_shape_null(JSRuntime *rt, JSShape *sh); static int js_shape_prepare_update(JSContext *ctx, JSObject *p, @@ -1261,13 +1319,26 @@ static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type); static void remove_gc_object(JSGCObjectHeader *h); -static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); -void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag); +static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_map); +static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh); +static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh); +static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh); +static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects); +static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen, + JSValueConst obj, JSValueConst method); +static int js_string_find_invalid_codepoint(JSString *p); +static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue get_date_string(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -1329,15 +1400,6 @@ return memset(ptr, 0, size); } -#ifdef CONFIG_BIGNUM -/* called by libbf */ -static void *js_bf_realloc(void *opaque, void *ptr, size_t size) -{ - JSRuntime *rt = opaque; - return js_realloc_rt(rt, ptr, size); -} -#endif /* CONFIG_BIGNUM */ - /* Throw out of memory in case of error */ void *js_malloc(JSContext *ctx, size_t size) { @@ -1449,10 +1511,31 @@ dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt); } +static void *js_realloc_bytecode_rt(void *opaque, void *ptr, size_t size) +{ + JSRuntime *rt = opaque; + if (size > (INT32_MAX / 2)) { + /* the bytecode cannot be larger than 2G. Leave some slack to + avoid some overflows. */ + return NULL; + } else { + return rt->mf.js_realloc(&rt->malloc_state, ptr, size); + } +} + +static inline void js_dbuf_bytecode_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx->rt, js_realloc_bytecode_rt); +} + static inline int is_digit(int c) { return c >= '0' && c <= '9'; } +static inline int string_get(const JSString *p, int idx) { + return p->is_wide_char ? p->u.str16idx : p->u.str8idx; +} + typedef struct JSClassShortDef { JSAtom class_name; JSClassFinalizer *finalizer; @@ -1487,24 +1570,20 @@ { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ -#ifdef CONFIG_BIGNUM { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ -#endif + { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ -#ifdef CONFIG_BIGNUM { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ - { JS_ATOM_BigFloat, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_FLOAT */ - { JS_ATOM_BigFloatEnv, js_float_env_finalizer, NULL }, /* JS_CLASS_FLOAT_ENV */ - { JS_ATOM_BigDecimal, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_DECIMAL */ - { JS_ATOM_OperatorSet, js_operator_set_finalizer, js_operator_set_mark }, /* JS_CLASS_OPERATOR_SET */ -#endif { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */ { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */ + { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ + { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ + { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ @@ -1530,64 +1609,6 @@ return 0; } -#ifdef CONFIG_BIGNUM -static JSValue JS_ThrowUnsupportedOperation(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "unsupported operation"); -} - -static JSValue invalid_to_string(JSContext *ctx, JSValueConst val) -{ - return JS_ThrowUnsupportedOperation(ctx); -} - -static JSValue invalid_from_string(JSContext *ctx, const char *buf, - int radix, int flags, slimb_t *pexponent) -{ - return JS_NAN; -} - -static int invalid_unary_arith(JSContext *ctx, - JSValue *pres, OPCodeEnum op, JSValue op1) -{ - JS_FreeValue(ctx, op1); - JS_ThrowUnsupportedOperation(ctx); - return -1; -} - -static int invalid_binary_arith(JSContext *ctx, OPCodeEnum op, - JSValue *pres, JSValue op1, JSValue op2) -{ - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - JS_ThrowUnsupportedOperation(ctx); - return -1; -} - -static JSValue invalid_mul_pow10_to_float64(JSContext *ctx, const bf_t *a, - int64_t exponent) -{ - return JS_ThrowUnsupportedOperation(ctx); -} - -static int invalid_mul_pow10(JSContext *ctx, JSValue *sp) -{ - JS_ThrowUnsupportedOperation(ctx); - return -1; -} - -static void set_dummy_numeric_ops(JSNumericOperations *ops) -{ - ops->to_string = invalid_to_string; - ops->from_string = invalid_from_string; - ops->unary_arith = invalid_unary_arith; - ops->binary_arith = invalid_binary_arith; - ops->mul_pow10_to_float64 = invalid_mul_pow10_to_float64; - ops->mul_pow10 = invalid_mul_pow10; -} - -#endif /* CONFIG_BIGNUM */ - #if !defined(CONFIG_STACK_CHECK) /* no stack limitation */ static inline uintptr_t js_get_stack_pointer(void) @@ -1639,17 +1660,11 @@ rt->malloc_state = ms; rt->malloc_gc_threshold = 256 * 1024; -#ifdef CONFIG_BIGNUM - bf_context_init(&rt->bf_ctx, js_bf_realloc, rt); - set_dummy_numeric_ops(&rt->bigint_ops); - set_dummy_numeric_ops(&rt->bigfloat_ops); - set_dummy_numeric_ops(&rt->bigdecimal_ops); -#endif - init_list_head(&rt->context_list); init_list_head(&rt->gc_obj_list); init_list_head(&rt->gc_zero_ref_count_list); rt->gc_phase = JS_GC_PHASE_NONE; + init_list_head(&rt->weakref_list); #ifdef DUMP_LEAKS init_list_head(&rt->string_list); @@ -1677,7 +1692,7 @@ rt->stack_size = JS_DEFAULT_STACK_SIZE; JS_UpdateStackTop(rt); - rt->current_exception = JS_NULL; + rt->current_exception = JS_UNINITIALIZED; return rt; fail: @@ -1696,19 +1711,19 @@ } /* default memory allocation functions with memory limitation */ -static inline size_t js_def_malloc_usable_size(void *ptr) +static size_t js_def_malloc_usable_size(const void *ptr) { #if defined(__APPLE__) return malloc_size(ptr); #elif defined(_WIN32) - return _msize(ptr); + return _msize((void *)ptr); #elif defined(EMSCRIPTEN) return 0; -#elif defined(__linux__) || defined(__NX__) - return malloc_usable_size(ptr); +#elif defined(__linux__) || defined(__GLIBC__) + return malloc_usable_size((void *)ptr); #else /* change this to `return 0;` if compilation fails */ - return malloc_usable_size(ptr); + return malloc_usable_size((void *)ptr); #endif } @@ -1772,18 +1787,7 @@ js_def_malloc, js_def_free, js_def_realloc, -#if defined(__APPLE__) - malloc_size, -#elif defined(_WIN32) - (size_t (*)(const void *))_msize, -#elif defined(EMSCRIPTEN) - NULL, -#elif defined(__linux__) || defined(__NX__) - (size_t (*)(const void *))malloc_usable_size, -#else - /* change this to `NULL,` if compilation fails */ - malloc_usable_size, -#endif + js_def_malloc_usable_size, }; JSRuntime *JS_NewRuntime(void) @@ -1823,6 +1827,16 @@ rt->sab_funcs = *sf; } +void JS_SetStripInfo(JSRuntime *rt, int flags) +{ + rt->strip_flags = flags; +} + +int JS_GetStripInfo(JSRuntime *rt) +{ + return rt->strip_flags; +} + /* return 0 if OK, < 0 if exception */ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, int argc, JSValueConst *argv) @@ -1834,7 +1848,7 @@ e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); if (!e) return -1; - e->ctx = ctx; + e->realm = JS_DupContext(ctx); e->job_func = job_func; e->argc = argc; for(i = 0; i < argc; i++) { @@ -1850,7 +1864,10 @@ } /* return < 0 if exception, 0 if no job pending, 1 if a job was - executed successfully. the context of the job is stored in '*pctx' */ + executed successfully. The context of the job is stored in '*pctx' + if pctx != NULL. It may be NULL if the context was already + destroyed or if no job was pending. The 'pctx' parameter is now + absolete. */ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) { JSContext *ctx; @@ -1859,15 +1876,16 @@ int i, ret; if (list_empty(&rt->job_list)) { - *pctx = NULL; + if (pctx) + *pctx = NULL; return 0; } /* get the first pending job and execute it */ e = list_entry(rt->job_list.next, JSJobEntry, link); list_del(&e->link); - ctx = e->ctx; - res = e->job_func(e->ctx, e->argc, (JSValueConst *)e->argv); + ctx = e->realm; + res = e->job_func(ctx, e->argc, (JSValueConst *)e->argv); for(i = 0; i < e->argc; i++) JS_FreeValue(ctx, e->argvi); if (JS_IsException(res)) @@ -1876,7 +1894,13 @@ ret = 1; JS_FreeValue(ctx, res); js_free(ctx, e); - *pctx = ctx; + if (pctx) { + if (ctx->header.ref_count > 1) + *pctx = ctx; + else + *pctx = NULL; + } + JS_FreeContext(ctx); return ret; } @@ -1957,11 +1981,14 @@ JSJobEntry *e = list_entry(el, JSJobEntry, link); for(i = 0; i < e->argc; i++) JS_FreeValueRT(rt, e->argvi); + JS_FreeContext(e->realm); js_free_rt(rt, e); } init_list_head(&rt->job_list); - JS_RunGC(rt); + /* don't remove the weak objects to avoid create new jobs with + FinalizationRegistry */ + JS_RunGCInternal(rt, FALSE); #ifdef DUMP_LEAKS /* leaking objects */ @@ -2003,6 +2030,7 @@ } #endif assert(list_empty(&rt->gc_obj_list)); + assert(list_empty(&rt->weakref_list)); /* free the classes */ for(i = 0; i < rt->class_count; i++) { @@ -2013,10 +2041,6 @@ } js_free_rt(rt, rt->class_array); -#ifdef CONFIG_BIGNUM - bf_context_end(&rt->bf_ctx); -#endif - #ifdef DUMP_LEAKS /* only the atoms defined in JS_InitAtoms() should be left */ { @@ -2051,7 +2075,7 @@ printf(")"); break; case JS_ATOM_TYPE_SYMBOL: - if (p->hash == JS_ATOM_HASH_SYMBOL) { + if (p->hash != JS_ATOM_HASH_PRIVATE) { printf("Symbol("); JS_DumpString(rt, p); printf(")"); @@ -2153,19 +2177,18 @@ } ctx->rt = rt; list_add_tail(&ctx->link, &rt->context_list); -#ifdef CONFIG_BIGNUM - ctx->bf_ctx = &rt->bf_ctx; - ctx->fp_env.prec = 113; - ctx->fp_env.flags = bf_set_exp_bits(15) | BF_RNDN | BF_FLAG_SUBNORMAL; -#endif for(i = 0; i < rt->class_count; i++) ctx->class_protoi = JS_NULL; ctx->array_ctor = JS_NULL; + ctx->iterator_ctor = JS_NULL; ctx->regexp_ctor = JS_NULL; ctx->promise_ctor = JS_NULL; init_list_head(&ctx->loaded_modules); - JS_AddIntrinsicBasicObjects(ctx); + if (JS_AddIntrinsicBasicObjects(ctx)) { + JS_FreeContext(ctx); + return NULL; + } return ctx; } @@ -2177,19 +2200,20 @@ if (!ctx) return NULL; - JS_AddIntrinsicBaseObjects(ctx); - JS_AddIntrinsicDate(ctx); - JS_AddIntrinsicEval(ctx); - JS_AddIntrinsicStringNormalize(ctx); - JS_AddIntrinsicRegExp(ctx); - JS_AddIntrinsicJSON(ctx); - JS_AddIntrinsicProxy(ctx); - JS_AddIntrinsicMapSet(ctx); - JS_AddIntrinsicTypedArrays(ctx); - JS_AddIntrinsicPromise(ctx); -#ifdef CONFIG_BIGNUM - JS_AddIntrinsicBigInt(ctx); -#endif + if (JS_AddIntrinsicBaseObjects(ctx) || + JS_AddIntrinsicDate(ctx) || + JS_AddIntrinsicEval(ctx) || + JS_AddIntrinsicStringNormalize(ctx) || + JS_AddIntrinsicRegExp(ctx) || + JS_AddIntrinsicJSON(ctx) || + JS_AddIntrinsicProxy(ctx) || + JS_AddIntrinsicMapSet(ctx) || + JS_AddIntrinsicTypedArrays(ctx) || + JS_AddIntrinsicPromise(ctx) || + JS_AddIntrinsicWeakRef(ctx)) { + JS_FreeContext(ctx); + return NULL; + } return ctx; } @@ -2234,7 +2258,6 @@ typedef enum JSFreeModuleEnum { JS_FREE_MODULE_ALL, JS_FREE_MODULE_NOT_RESOLVED, - JS_FREE_MODULE_NOT_EVALUATED, } JSFreeModuleEnum; /* XXX: would be more efficient with separate module lists */ @@ -2244,9 +2267,14 @@ list_for_each_safe(el, el1, &ctx->loaded_modules) { JSModuleDef *m = list_entry(el, JSModuleDef, link); if (flag == JS_FREE_MODULE_ALL || - (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved) || - (flag == JS_FREE_MODULE_NOT_EVALUATED && !m->evaluated)) { - js_free_module_def(ctx, m); + (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { + /* warning: the module may be referenced elsewhere. It + could be simpler to use an array instead of a list for + 'ctx->loaded_modules' */ + list_del(&m->link); + m->link.prev = NULL; + m->link.next = NULL; + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); } } } @@ -2264,11 +2292,9 @@ int i; struct list_head *el; - /* modules are not seen by the GC, so we directly mark the objects - referenced by each module */ list_for_each(el, &ctx->loaded_modules) { JSModuleDef *m = list_entry(el, JSModuleDef, link); - js_mark_module_def(rt, m, mark_func); + JS_MarkValue(rt, JS_MKPTR(JS_TAG_MODULE, m), mark_func); } JS_MarkValue(rt, ctx->global_obj, mark_func); @@ -2284,7 +2310,7 @@ for(i = 0; i < rt->class_count; i++) { JS_MarkValue(rt, ctx->class_protoi, mark_func); } - JS_MarkValue(rt, ctx->iterator_proto, mark_func); + JS_MarkValue(rt, ctx->iterator_ctor, mark_func); JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); JS_MarkValue(rt, ctx->promise_ctor, mark_func); JS_MarkValue(rt, ctx->array_ctor, mark_func); @@ -2348,7 +2374,7 @@ JS_FreeValue(ctx, ctx->class_protoi); } js_free_rt(rt, ctx->class_proto); - JS_FreeValue(ctx, ctx->iterator_proto); + JS_FreeValue(ctx, ctx->iterator_ctor); JS_FreeValue(ctx, ctx->async_iterator_proto); JS_FreeValue(ctx, ctx->promise_ctor); JS_FreeValue(ctx, ctx->array_ctor); @@ -2395,14 +2421,6 @@ return (sf && (sf->js_mode & JS_MODE_STRICT)); } -#ifdef CONFIG_BIGNUM -static inline BOOL is_math_mode(JSContext *ctx) -{ - JSStackFrame *sf = ctx->rt->current_stack_frame; - return (sf && (sf->js_mode & JS_MODE_MATH)); -} -#endif - /* JSAtom support */ #define JS_ATOM_TAG_INT (1U << 31) @@ -2451,10 +2469,7 @@ len = p->len; if (len == 0 || len > 10) return FALSE; - if (p->is_wide_char) - c = p->u.str160; - else - c = p->u.str80; + c = string_get(p, 0); if (is_num(c)) { if (c == '0') { if (len != 1) @@ -2463,10 +2478,7 @@ } else { n = c - '0'; for(i = 1; i < len; i++) { - if (p->is_wide_char) - c = p->u.str16i; - else - c = p->u.str8i; + c = string_get(p, i); if (!is_num(c)) return FALSE; n64 = (uint64_t)n * 10 + (c - '0'); @@ -2511,10 +2523,35 @@ return h; } -static __maybe_unused void JS_DumpString(JSRuntime *rt, - const JSString *p) +static uint32_t hash_string_rope(JSValueConst val, uint32_t h) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + return hash_string(JS_VALUE_GET_STRING(val), h); + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + h = hash_string_rope(r->left, h); + return hash_string_rope(r->right, h); + } +} + +static __maybe_unused void JS_DumpChar(FILE *fo, int c, int sep) +{ + if (c == sep || c == '\\') { + fputc('\\', fo); + fputc(c, fo); + } else if (c >= ' ' && c <= 126) { + fputc(c, fo); + } else if (c == '\n') { + fputc('\\', fo); + fputc('n', fo); + } else { + fprintf(fo, "\\u%04x", c); + } +} + +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) { - int i, c, sep; + int i, sep; if (p == NULL) { printf("<null>"); @@ -2524,21 +2561,7 @@ sep = (p->header.ref_count == 1) ? '\"' : '\''; putchar(sep); for(i = 0; i < p->len; i++) { - if (p->is_wide_char) - c = p->u.str16i; - else - c = p->u.str8i; - if (c == sep || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } + JS_DumpChar(stdout, string_get(p, i), sep); } putchar(sep); } @@ -2618,7 +2641,7 @@ rt->atom_count = 0; rt->atom_size = 0; rt->atom_free_index = 0; - if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ + if (JS_ResizeAtomHash(rt, 512)) /* there are at least 504 predefined atoms */ return -1; p = js_atom_init; @@ -2676,14 +2699,10 @@ case JS_ATOM_TYPE_GLOBAL_SYMBOL: return JS_ATOM_KIND_SYMBOL; case JS_ATOM_TYPE_SYMBOL: - switch(p->hash) { - case JS_ATOM_HASH_SYMBOL: - return JS_ATOM_KIND_SYMBOL; - case JS_ATOM_HASH_PRIVATE: + if (p->hash == JS_ATOM_HASH_PRIVATE) return JS_ATOM_KIND_PRIVATE; - default: - abort(); - } + else + return JS_ATOM_KIND_SYMBOL; default: abort(); } @@ -2743,7 +2762,7 @@ if (p->hash == h && p->atom_type == atom_type && p->len == len && - js_string_memcmp(p, str, len) == 0) { + js_string_memcmp(p, 0, str, 0, len) == 0) { if (!__JS_AtomIsConst(i)) p->header.ref_count++; goto done; @@ -2753,7 +2772,7 @@ } else { h1 = 0; /* avoid warning */ if (atom_type == JS_ATOM_TYPE_SYMBOL) { - h = JS_ATOM_HASH_SYMBOL; + h = 0; } else { h = JS_ATOM_HASH_PRIVATE; atom_type = JS_ATOM_TYPE_SYMBOL; @@ -2767,9 +2786,9 @@ /* alloc new with size progression 3/2: 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 - preallocating space for predefined atoms (at least 195). + preallocating space for predefined atoms (at least 504). */ - new_size = max_int(211, rt->atom_size * 3 / 2); + new_size = max_int(711, rt->atom_size * 3 / 2); if (new_size > JS_ATOM_MAX) goto fail; /* XXX: should use realloc2 to use slack space */ @@ -2881,6 +2900,7 @@ return __JS_NewAtom(rt, p, atom_type); } +/* Warning: str must be ASCII only */ static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, int atom_type) { @@ -2945,7 +2965,13 @@ #ifdef DUMP_LEAKS list_del(&p->link); #endif - js_free_rt(rt, p); + if (p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash != JS_ATOM_HASH_PRIVATE && p->hash != 0) { + /* live weak references are still present on this object: keep + it */ + } else { + js_free_rt(rt, p); + } rt->atom_count--; assert(rt->atom_count >= 0); } @@ -2975,11 +3001,25 @@ return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); } +/* XXX: optimize */ +static size_t count_ascii(const uint8_t *buf, size_t len) +{ + const uint8_t *p, *p_end; + p = buf; + p_end = buf + len; + while (p < p_end && *p < 128) + p++; + return p - buf; +} + +/* str is UTF-8 encoded */ JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) { JSValue val; - if (len == 0 || !is_digit(*str)) { + if (len == 0 || + (!is_digit(*str) && + count_ascii((const uint8_t *)str, len) == len)) { JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); if (atom) return atom; @@ -3002,8 +3042,9 @@ } else { char buf11; JSValue val; - snprintf(buf, sizeof(buf), "%u", n); - val = JS_NewString(ctx, buf); + size_t len; + len = u32toa(buf, n); + val = js_new_string8_len(ctx, buf, len); if (JS_IsException(val)) return JS_ATOM_NULL; return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), @@ -3018,8 +3059,9 @@ } else { char buf24; JSValue val; - snprintf(buf, sizeof(buf), "%" PRId64 , n); - val = JS_NewString(ctx, buf); + size_t len; + len = i64toa(buf, n); + val = js_new_string8_len(ctx, buf, len); if (JS_IsException(val)) return JS_ATOM_NULL; return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), @@ -3085,10 +3127,7 @@ return (const char *)str->u.str8; } for(i = 0; i < str->len; i++) { - if (str->is_wide_char) - c = str->u.str16i; - else - c = str->u.str8i; + c = string_get(str, i); if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX) break; if (c < 128) { @@ -3114,8 +3153,8 @@ char bufATOM_GET_STR_BUF_SIZE; if (__JS_AtomIsTaggedInt(atom)) { - snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom)); - return JS_NewString(ctx, buf); + size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); + return js_new_string8_len(ctx, buf, len); } else { JSRuntime *rt = ctx->rt; JSAtomStruct *p; @@ -3179,7 +3218,7 @@ JSRuntime *rt = ctx->rt; JSAtomStruct *p1; JSString *p; - int c, len, ret; + int c, ret; JSValue num, str; if (__JS_AtomIsTaggedInt(atom)) @@ -3188,54 +3227,24 @@ p1 = rt->atom_arrayatom; if (p1->atom_type != JS_ATOM_TYPE_STRING) return JS_UNDEFINED; - p = p1; - len = p->len; - if (p->is_wide_char) { - const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len; - if (r >= r_end) - return JS_UNDEFINED; - c = *r; - if (c == '-') { - if (r >= r_end) - return JS_UNDEFINED; - r++; - c = *r; - /* -0 case is specific */ - if (c == '0' && len == 2) - goto minus_zero; - } - /* XXX: should test NaN, but the tests do not check it */ - if (!is_num(c)) { - /* XXX: String should be normalized, therefore 8-bit only */ - const uint16_t nfinity167 = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; - if (!(c =='I' && (r_end - r) == 8 && - !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) - return JS_UNDEFINED; - } - } else { - const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len; - if (r >= r_end) - return JS_UNDEFINED; - c = *r; - if (c == '-') { - if (r >= r_end) - return JS_UNDEFINED; - r++; - c = *r; - /* -0 case is specific */ - if (c == '0' && len == 2) { - minus_zero: - return __JS_NewFloat64(ctx, -0.0); - } - } - if (!is_num(c)) { - if (!(c =='I' && (r_end - r) == 8 && - !memcmp(r + 1, "nfinity", 7))) - return JS_UNDEFINED; - } + switch(atom) { + case JS_ATOM_minus_zero: + return __JS_NewFloat64(ctx, -0.0); + case JS_ATOM_Infinity: + return __JS_NewFloat64(ctx, INFINITY); + case JS_ATOM_minus_Infinity: + return __JS_NewFloat64(ctx, -INFINITY); + case JS_ATOM_NaN: + return __JS_NewFloat64(ctx, NAN); + default: + break; } - /* XXX: bignum: would be better to only accept integer to avoid - relying on current floating point precision */ + p = p1; + if (p->len == 0) + return JS_UNDEFINED; + c = string_get(p, 0); + if (!is_num(c) && c != '-') + return JS_UNDEFINED; /* this is ECMA CanonicalNumericIndexString primitive */ num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); if (JS_IsException(num)) @@ -3245,9 +3254,9 @@ JS_FreeValue(ctx, num); return str; } - ret = js_string_compare(ctx, p, JS_VALUE_GET_STRING(str)); + ret = js_string_eq(ctx, p, JS_VALUE_GET_STRING(str)); JS_FreeValue(ctx, str); - if (ret == 0) { + if (ret) { return num; } else { JS_FreeValue(ctx, num); @@ -3291,59 +3300,24 @@ return FALSE; p = rt->atom_arrayv; return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && - p->hash == JS_ATOM_HASH_SYMBOL) || + p->hash != JS_ATOM_HASH_PRIVATE) || p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && !(p->len == 0 && p->is_wide_char != 0)); } -static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) -{ - char bufATOM_GET_STR_BUF_SIZE; - const char *p; - int i; - - /* XXX: should handle embedded null characters */ - /* XXX: should move encoding code to JS_AtomGetStr */ - p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - for (i = 0; pi; i++) { - int c = (unsigned char)pi; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) - break; - } - if (i > 0 && pi == '\0') { - printf("%s", p); - } else { - putchar('"'); - printf("%.*s", i, p); - for (; pi; i++) { - int c = (unsigned char)pi; - if (c == '\"' || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar('\"'); - } -} - /* free with JS_FreeCString() */ -const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) +const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom) { JSValue str; const char *cstr; str = JS_AtomToString(ctx, atom); - if (JS_IsException(str)) + if (JS_IsException(str)) { + if (plen) + *plen = 0; return NULL; - cstr = JS_ToCString(ctx, str); + } + cstr = JS_ToCStringLen(ctx, plen, str); JS_FreeValue(ctx, str); return cstr; } @@ -3384,7 +3358,9 @@ static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) { char buf16; - snprintf(buf, sizeof(buf), "%u", n); + size_t len; + len = u32toa(buf, n); + buflen = '\0'; return js_atom_concat_str(ctx, name, buf); } @@ -3395,19 +3371,37 @@ /* JSClass support */ +#ifdef CONFIG_ATOMICS +static pthread_mutex_t js_class_id_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + /* a new class ID is allocated if *pclass_id != 0 */ JSClassID JS_NewClassID(JSClassID *pclass_id) { JSClassID class_id; - /* XXX: make it thread safe */ +#ifdef CONFIG_ATOMICS + pthread_mutex_lock(&js_class_id_mutex); +#endif class_id = *pclass_id; if (class_id == 0) { class_id = js_class_id_alloc++; *pclass_id = class_id; } +#ifdef CONFIG_ATOMICS + pthread_mutex_unlock(&js_class_id_mutex); +#endif return class_id; } +JSClassID JS_GetClassID(JSValue v) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_INVALID_CLASS_ID; + p = JS_VALUE_GET_OBJ(v); + return p->class_id; +} + BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) { return (class_id < rt->class_count && @@ -3482,7 +3476,7 @@ return ret; } -static JSValue js_new_string8(JSContext *ctx, const uint8_t *buf, int len) +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) { JSString *str; @@ -3497,7 +3491,12 @@ return JS_MKPTR(JS_TAG_STRING, str); } -static JSValue js_new_string16(JSContext *ctx, const uint16_t *buf, int len) +static JSValue js_new_string8(JSContext *ctx, const char *buf) +{ + return js_new_string8_len(ctx, buf, strlen(buf)); +} + +static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) { JSString *str; str = js_alloc_string(ctx, len, 1); @@ -3511,10 +3510,10 @@ { if (c < 0x100) { uint8_t ch8 = c; - return js_new_string8(ctx, &ch8, 1); + return js_new_string8_len(ctx, (const char *)&ch8, 1); } else { uint16_t ch16 = c; - return js_new_string16(ctx, &ch16, 1); + return js_new_string16_len(ctx, &ch16, 1); } } @@ -3532,7 +3531,7 @@ c |= p->u.str16i; } if (c > 0xFF) - return js_new_string16(ctx, p->u.str16 + start, len); + return js_new_string16_len(ctx, p->u.str16 + start, len); str = js_alloc_string(ctx, len, 0); if (!str) @@ -3543,7 +3542,7 @@ str->u.str8len = '\0'; return JS_MKPTR(JS_TAG_STRING, str); } else { - return js_new_string8(ctx, p->u.str8 + start, len); + return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len); } } @@ -3649,7 +3648,7 @@ return 0; } -static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +static no_inline int string_buffer_putc16_slow(StringBuffer *s, uint32_t c) { if (unlikely(s->len >= s->size)) { if (string_buffer_realloc(s, s->len + 1, c)) @@ -3694,24 +3693,39 @@ return 0; } } - return string_buffer_putc_slow(s, c); + return string_buffer_putc16_slow(s, c); } -/* 0 <= c <= 0x10ffff */ -static int string_buffer_putc(StringBuffer *s, uint32_t c) +static int string_buffer_putc_slow(StringBuffer *s, uint32_t c) { if (unlikely(c >= 0x10000)) { /* surrogate pair */ - c -= 0x10000; - if (string_buffer_putc16(s, (c >> 10) + 0xd800)) + if (string_buffer_putc16(s, get_hi_surrogate(c))) return -1; - c = (c & 0x3ff) + 0xdc00; + c = get_lo_surrogate(c); } return string_buffer_putc16(s, c); } -static int string_get(const JSString *p, int idx) { - return p->is_wide_char ? p->u.str16idx : p->u.str8idx; +/* 0 <= c <= 0x10ffff */ +static inline int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + if (c < 0x10000) { + s->str->u.str16s->len++ = c; + return 0; + } else if (likely((s->len + 1) < s->size)) { + s->str->u.str16s->len++ = get_hi_surrogate(c); + s->str->u.str16s->len++ = get_lo_surrogate(c); + return 0; + } + } else if (c < 0x100) { + s->str->u.str8s->len++ = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); } static int string_getc(const JSString *p, int *pidx) @@ -3720,10 +3734,10 @@ idx = *pidx; if (p->is_wide_char) { c = p->u.str16idx++; - if (c >= 0xd800 && c < 0xdc00 && idx < p->len) { + if (is_hi_surrogate(c) && idx < p->len) { c1 = p->u.str16idx; - if (c1 >= 0xdc00 && c1 < 0xe000) { - c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; + if (is_lo_surrogate(c1)) { + c = from_surrogate(c, c1); idx++; } } @@ -3808,13 +3822,21 @@ return -1; } if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { - v1 = JS_ToString(s->ctx, v); - if (JS_IsException(v1)) - return string_buffer_set_error(s); - p = JS_VALUE_GET_STRING(v1); - res = string_buffer_concat(s, p, 0, p->len); - JS_FreeValue(s->ctx, v1); - return res; + if (JS_VALUE_GET_TAG(v) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(v); + /* recursion is acceptable because the rope depth is bounded */ + if (string_buffer_concat_value(s, r->left)) + return -1; + return string_buffer_concat_value(s, r->right); + } else { + v1 = JS_ToString(s->ctx, v); + if (JS_IsException(v1)) + return string_buffer_set_error(s); + p = JS_VALUE_GET_STRING(v1); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v1); + return res; + } } p = JS_VALUE_GET_STRING(v); return string_buffer_concat(s, p, 0, p->len); @@ -3897,15 +3919,13 @@ p_start = (const uint8_t *)buf; p_end = p_start + buf_len; - p = p_start; - while (p < p_end && *p < 128) - p++; - len1 = p - p_start; + len1 = count_ascii(p_start, buf_len); + p = p_start + len1; if (len1 > JS_STRING_LEN_MAX) return JS_ThrowInternalError(ctx, "string too long"); if (p == p_end) { /* ASCII string */ - return js_new_string8(ctx, (const uint8_t *)buf, buf_len); + return js_new_string8_len(ctx, buf, buf_len); } else { if (string_buffer_init(ctx, b, buf_len)) goto fail; @@ -3921,9 +3941,8 @@ } else if (c <= 0x10FFFF) { p = p_next; /* surrogate pair */ - c -= 0x10000; - string_buffer_putc16(b, (c >> 10) + 0xd800); - c = (c & 0x3ff) + 0xdc00; + string_buffer_putc16(b, get_hi_surrogate(c)); + c = get_lo_surrogate(c); } else { /* invalid char */ c = 0xfffd; @@ -3979,11 +3998,6 @@ return JS_EXCEPTION; } -JSValue JS_NewString(JSContext *ctx, const char *str) -{ - return JS_NewStringLen(ctx, str, strlen(str)); -} - JSValue JS_NewAtomString(JSContext *ctx, const char *str) { JSAtom atom = JS_NewAtom(ctx, str); @@ -4061,13 +4075,12 @@ if (c < 0x80) { *q++ = c; } else { - if (c >= 0xd800 && c < 0xdc00) { + if (is_hi_surrogate(c)) { if (pos < len && !cesu8) { c1 = srcpos; - if (c1 >= 0xdc00 && c1 < 0xe000) { + if (is_lo_surrogate(c1)) { pos++; - /* surrogate pair */ - c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; + c = from_surrogate(c, c1); } else { /* Keep unmatched surrogate code points */ /* c = 0xfffd; */ /* error */ @@ -4100,7 +4113,7 @@ if (!ptr) return; /* purposely removing constness */ - p = (JSString *)(void *)(ptr - offsetof(JSString, u)); + p = container_of(ptr, JSString, u); JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); } @@ -4126,31 +4139,42 @@ return 0; } -static int js_string_memcmp(const JSString *p1, const JSString *p2, int len) +static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, + int pos2, int len) { int res; if (likely(!p1->is_wide_char)) { if (likely(!p2->is_wide_char)) - res = memcmp(p1->u.str8, p2->u.str8, len); + res = memcmp(p1->u.str8 + pos1, p2->u.str8 + pos2, len); else - res = -memcmp16_8(p2->u.str16, p1->u.str8, len); + res = -memcmp16_8(p2->u.str16 + pos2, p1->u.str8 + pos1, len); } else { if (!p2->is_wide_char) - res = memcmp16_8(p1->u.str16, p2->u.str8, len); + res = memcmp16_8(p1->u.str16 + pos1, p2->u.str8 + pos2, len); else - res = memcmp16(p1->u.str16, p2->u.str16, len); + res = memcmp16(p1->u.str16 + pos1, p2->u.str16 + pos2, len); } return res; } +static BOOL js_string_eq(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + if (p1->len != p2->len) + return FALSE; + if (p1 == p2) + return TRUE; + return js_string_memcmp(p1, 0, p2, 0, p1->len) == 0; +} + /* return < 0, 0 or > 0 */ static int js_string_compare(JSContext *ctx, const JSString *p1, const JSString *p2) { int res, len; len = min_int(p1->len, p2->len); - res = js_string_memcmp(p1, p2, len); + res = js_string_memcmp(p1, 0, p2, 0, len); if (res == 0) { if (p1->len == p2->len) res = 0; @@ -4200,53 +4224,452 @@ return JS_MKPTR(JS_TAG_STRING, p); } -/* op1 and op2 are converted to strings. For convience, op1 or op2 = +static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op2) { + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + JSString *p2 = JS_VALUE_GET_STRING(op2); + size_t size1; + + if (p2->len == 0) + return TRUE; + if (p1->header.ref_count != 1) + return FALSE; + size1 = js_malloc_usable_size(ctx, p1); + if (p1->is_wide_char) { + if (size1 >= sizeof(*p1) + ((p1->len + p2->len) << 1)) { + if (p2->is_wide_char) { + memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); + p1->len += p2->len; + return TRUE; + } else { + size_t i; + for (i = 0; i < p2->len; i++) { + p1->u.str16p1->len++ = p2->u.str8i; + } + return TRUE; + } + } + } else if (!p2->is_wide_char) { + if (size1 >= sizeof(*p1) + p1->len + p2->len + 1) { + memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); + p1->len += p2->len; + p1->u.str8p1->len = '\0'; + return TRUE; + } + } + } + return FALSE; +} + +static JSValue JS_ConcatString2(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue ret; + JSString *p1, *p2; + p1 = JS_VALUE_GET_STRING(op1); + if (JS_ConcatStringInPlace(ctx, p1, op2)) { + JS_FreeValue(ctx, op2); + return op1; + } + p2 = JS_VALUE_GET_STRING(op2); + ret = JS_ConcatString1(ctx, p1, p2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return ret; +} + +/* Return the character at position 'idx'. 'val' must be a string or rope */ +static int string_rope_get(JSValueConst val, uint32_t idx) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + return string_get(JS_VALUE_GET_STRING(val), idx); + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + uint32_t len; + if (JS_VALUE_GET_TAG(r->left) == JS_TAG_STRING) + len = JS_VALUE_GET_STRING(r->left)->len; + else + len = JS_VALUE_GET_STRING_ROPE(r->left)->len; + if (idx < len) + return string_rope_get(r->left, idx); + else + return string_rope_get(r->right, idx - len); + } +} + +typedef struct { + JSValueConst stackJS_STRING_ROPE_MAX_DEPTH; + int stack_len; +} JSStringRopeIter; + +static void string_rope_iter_init(JSStringRopeIter *s, JSValueConst val) +{ + s->stack_len = 0; + s->stacks->stack_len++ = val; +} + +/* iterate thru a rope and return the strings in order */ +static JSString *string_rope_iter_next(JSStringRopeIter *s) +{ + JSValueConst val; + JSStringRope *r; + + if (s->stack_len == 0) + return NULL; + val = s->stack--s->stack_len; + for(;;) { + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return JS_VALUE_GET_STRING(val); + r = JS_VALUE_GET_STRING_ROPE(val); + assert(s->stack_len < JS_STRING_ROPE_MAX_DEPTH); + s->stacks->stack_len++ = r->right; + val = r->left; + } +} + +static uint32_t string_rope_get_len(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return JS_VALUE_GET_STRING(val)->len; + else + return JS_VALUE_GET_STRING_ROPE(val)->len; +} + +static int js_string_rope_compare(JSContext *ctx, JSValueConst op1, + JSValueConst op2, BOOL eq_only) +{ + uint32_t len1, len2, len, pos1, pos2, l; + int res; + JSStringRopeIter it1, it2; + JSString *p1, *p2; + + len1 = string_rope_get_len(op1); + len2 = string_rope_get_len(op2); + /* no need to go further for equality test if + different length */ + if (eq_only && len1 != len2) + return 1; + len = min_uint32(len1, len2); + string_rope_iter_init(&it1, op1); + string_rope_iter_init(&it2, op2); + p1 = string_rope_iter_next(&it1); + p2 = string_rope_iter_next(&it2); + pos1 = 0; + pos2 = 0; + while (len != 0) { + l = min_uint32(p1->len - pos1, p2->len - pos2); + l = min_uint32(l, len); + res = js_string_memcmp(p1, pos1, p2, pos2, l); + if (res != 0) + return res; + len -= l; + pos1 += l; + if (pos1 >= p1->len) { + p1 = string_rope_iter_next(&it1); + pos1 = 0; + } + pos2 += l; + if (pos2 >= p2->len) { + p2 = string_rope_iter_next(&it2); + pos2 = 0; + } + } + + if (len1 == len2) + res = 0; + else if (len1 < len2) + res = -1; + else + res = 1; + return res; +} + +/* 'rope' must be a rope. return a string and modify the rope so that + it won't need to be linearized again. */ +static JSValue js_linearize_string_rope(JSContext *ctx, JSValue rope) +{ + StringBuffer b_s, *b = &b_s; + JSStringRope *r; + JSValue ret; + + r = JS_VALUE_GET_STRING_ROPE(rope); + + /* check whether it is already linearized */ + if (JS_VALUE_GET_TAG(r->right) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r->right)->len == 0) { + ret = JS_DupValue(ctx, r->left); + JS_FreeValue(ctx, rope); + return ret; + } + if (string_buffer_init2(ctx, b, r->len, r->is_wide_char)) + goto fail; + if (string_buffer_concat_value(b, rope)) + goto fail; + ret = string_buffer_end(b); + if (r->header.ref_count > 1) { + /* update the rope so that it won't need to be linearized again */ + JS_FreeValue(ctx, r->left); + JS_FreeValue(ctx, r->right); + r->left = JS_DupValue(ctx, ret); + r->right = JS_AtomToString(ctx, JS_ATOM_empty_string); + } + JS_FreeValue(ctx, rope); + return ret; + fail: + JS_FreeValue(ctx, rope); + return JS_EXCEPTION; +} + +static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope); + +/* op1 and op2 must be strings or string ropes */ +static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2) +{ + uint32_t len; + int is_wide_char, depth; + JSStringRope *r; + JSValue res; + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + JSString *p1 = JS_VALUE_GET_STRING(op1); + len = p1->len; + is_wide_char = p1->is_wide_char; + depth = 0; + } else { + JSStringRope *r1 = JS_VALUE_GET_STRING_ROPE(op1); + len = r1->len; + is_wide_char = r1->is_wide_char; + depth = r1->depth; + } + + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + JSString *p2 = JS_VALUE_GET_STRING(op2); + len += p2->len; + is_wide_char |= p2->is_wide_char; + } else { + JSStringRope *r2 = JS_VALUE_GET_STRING_ROPE(op2); + len += r2->len; + is_wide_char |= r2->is_wide_char; + depth = max_int(depth, r2->depth); + } + if (len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(ctx, "string too long"); + goto fail; + } + r = js_malloc(ctx, sizeof(*r)); + if (!r) + goto fail; + r->header.ref_count = 1; + r->len = len; + r->is_wide_char = is_wide_char; + r->depth = depth + 1; + r->left = op1; + r->right = op2; + res = JS_MKPTR(JS_TAG_STRING_ROPE, r); + if (r->depth > JS_STRING_ROPE_MAX_DEPTH) { + JSValue res2; +#ifdef DUMP_ROPE_REBALANCE + printf("rebalance: initial depth=%d\n", r->depth); +#endif + res2 = js_rebalancee_string_rope(ctx, res); +#ifdef DUMP_ROPE_REBALANCE + if (JS_VALUE_GET_TAG(res2) == JS_TAG_STRING_ROPE) + printf("rebalance: final depth=%d\n", JS_VALUE_GET_STRING_ROPE(res2)->depth); +#endif + JS_FreeValue(ctx, res); + return res2; + } else { + return res; + } + fail: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; +} + +#define ROPE_N_BUCKETS 44 + +/* Fibonacii numbers starting from F_2 */ +static const uint32_t rope_bucket_lenROPE_N_BUCKETS = { + 1, 2, 3, 5, + 8, 13, 21, 34, + 55, 89, 144, 233, + 377, 610, 987, 1597, + 2584, 4181, 6765, 10946, + 17711, 28657, 46368, 75025, + 121393, 196418, 317811, 514229, + 832040, 1346269, 2178309, 3524578, + 5702887, 9227465, 14930352, 24157817, + 39088169, 63245986, 102334155, 165580141, + 267914296, 433494437, 701408733, 1134903170, /* > JS_STRING_LEN_MAX */ +}; + +static int js_rebalancee_string_rope_rec(JSContext *ctx, JSValue *buckets, + JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + uint32_t len, i; + JSValue a, b; + + len = p->len; + if (len == 0) + return 0; /* nothing to do */ + /* find the bucket i so that rope_bucket_leni <= len < + rope_bucket_leni + 1 and concatenate the ropes in the + buckets before */ + a = JS_NULL; + i = 0; + while (len >= rope_bucket_leni + 1) { + b = bucketsi; + if (!JS_IsNull(b)) { + bucketsi = JS_NULL; + if (JS_IsNull(a)) { + a = b; + } else { + a = js_new_string_rope(ctx, b, a); + if (JS_IsException(a)) + return -1; + } + } + i++; + } + if (!JS_IsNull(a)) { + a = js_new_string_rope(ctx, a, JS_DupValue(ctx, val)); + if (JS_IsException(a)) + return -1; + } else { + a = JS_DupValue(ctx, val); + } + while (!JS_IsNull(bucketsi)) { + a = js_new_string_rope(ctx, bucketsi, a); + bucketsi = JS_NULL; + if (JS_IsException(a)) + return -1; + i++; + } + bucketsi = a; + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + js_rebalancee_string_rope_rec(ctx, buckets, r->left); + js_rebalancee_string_rope_rec(ctx, buckets, r->right); + } + return 0; +} + +/* Return a new rope which is balanced. Algorithm from "Ropes: an + Alternative to Strings", Hans-J. Boehm, Russ Atkinson and Michael + Plass. */ +static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope) +{ + JSValue bucketsROPE_N_BUCKETS, a, b; + int i; + + for(i = 0; i < ROPE_N_BUCKETS; i++) + bucketsi = JS_NULL; + if (js_rebalancee_string_rope_rec(ctx, buckets, rope)) + goto fail; + a = JS_NULL; + for(i = 0; i < ROPE_N_BUCKETS; i++) { + b = bucketsi; + if (!JS_IsNull(b)) { + bucketsi = JS_NULL; + if (JS_IsNull(a)) { + a = b; + } else { + a = js_new_string_rope(ctx, b, a); + if (JS_IsException(a)) + goto fail; + } + } + } + /* fail safe */ + if (JS_IsNull(a)) + return JS_AtomToString(ctx, JS_ATOM_empty_string); + else + return a; + fail: + for(i = 0; i < ROPE_N_BUCKETS; i++) { + JS_FreeValue(ctx, bucketsi); + } + return JS_EXCEPTION; +} + +/* op1 and op2 are converted to strings. For convenience, op1 or op2 = JS_EXCEPTION are accepted and return JS_EXCEPTION. */ static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) { - JSValue ret; JSString *p1, *p2; - if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING && + JS_VALUE_GET_TAG(op1) != JS_TAG_STRING_ROPE)) { op1 = JS_ToStringFree(ctx, op1); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); return JS_EXCEPTION; } } - if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) { + if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING && + JS_VALUE_GET_TAG(op2) != JS_TAG_STRING_ROPE)) { op2 = JS_ToStringFree(ctx, op2); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); return JS_EXCEPTION; } } - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - /* XXX: could also check if p1 is empty */ - if (p2->len == 0) { - goto ret_op1; - } - if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char - && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { - /* Concatenate in place in available space at the end of p1 */ - if (p1->is_wide_char) { - memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); - p1->len += p2->len; - } else { - memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); - p1->len += p2->len; - p1->u.str8p1->len = '\0'; + /* normal concatenation for short strings */ + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + p2 = JS_VALUE_GET_STRING(op2); + if (p2->len == 0) { + JS_FreeValue(ctx, op2); + return op1; + } + if (p2->len <= JS_STRING_ROPE_SHORT_LEN) { + if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(op1); + if (p1->len <= JS_STRING_ROPE_SHORT2_LEN) { + return JS_ConcatString2(ctx, op1, op2); + } else { + return js_new_string_rope(ctx, op1, op2); + } + } else { + JSStringRope *r1; + r1 = JS_VALUE_GET_STRING_ROPE(op1); + if (JS_VALUE_GET_TAG(r1->right) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r1->right)->len <= JS_STRING_ROPE_SHORT_LEN) { + JSValue val, ret; + val = JS_ConcatString2(ctx, JS_DupValue(ctx, r1->right), op2); + if (JS_IsException(val)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + ret = js_new_string_rope(ctx, JS_DupValue(ctx, r1->left), val); + JS_FreeValue(ctx, op1); + return ret; + } + } + } + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + JSStringRope *r2; + p1 = JS_VALUE_GET_STRING(op1); + if (p1->len == 0) { + JS_FreeValue(ctx, op1); + return op2; + } + r2 = JS_VALUE_GET_STRING_ROPE(op2); + if (JS_VALUE_GET_TAG(r2->left) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r2->left)->len <= JS_STRING_ROPE_SHORT_LEN) { + JSValue val, ret; + val = JS_ConcatString2(ctx, op1, JS_DupValue(ctx, r2->left)); + if (JS_IsException(val)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + ret = js_new_string_rope(ctx, val, JS_DupValue(ctx, r2->right)); + JS_FreeValue(ctx, op2); + return ret; } - ret_op1: - JS_FreeValue(ctx, op2); - return op1; } - ret = JS_ConcatString1(ctx, p1, p2); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return ret; + return js_new_string_rope(ctx, op1, op2); } /* Shape support */ @@ -4358,19 +4781,14 @@ rt->shape_hash_count--; } -/* create a new empty shape with prototype 'proto' */ -static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, - int hash_size, int prop_size) +/* create a new empty shape with prototype 'proto'. It is not hashed */ +static inline JSShape *js_new_shape_nohash(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) { JSRuntime *rt = ctx->rt; void *sh_alloc; JSShape *sh; - /* resize the shape hash table if necessary */ - if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { - resize_shape_hash(rt, rt->shape_hash_bits + 1); - } - sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); if (!sh_alloc) return NULL; @@ -4386,11 +4804,29 @@ sh->prop_size = prop_size; sh->prop_count = 0; sh->deleted_prop_count = 0; + sh->is_hashed = FALSE; + return sh; +} + +/* create a new empty shape with prototype 'proto' */ +static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh; + + /* resize the shape hash table if necessary */ + if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { + resize_shape_hash(rt, rt->shape_hash_bits + 1); + } + + sh = js_new_shape_nohash(ctx, proto, hash_size, prop_size); + if (!sh) + return NULL; /* insert in the hash table */ sh->hash = shape_initial_hash(proto); sh->is_hashed = TRUE; - sh->has_small_array_index = FALSE; js_shape_hash_link(ctx->rt, sh); return sh; } @@ -4401,6 +4837,7 @@ JS_PROP_INITIAL_SIZE); } + /* The shape is cloned. The new shape is not inserted in the shape hash table */ static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) @@ -4479,6 +4916,7 @@ JSShapeProperty *pr; void *sh_alloc; intptr_t h; + JSShape *old_sh; sh = *psh; new_size = max_int(count, sh->prop_size * 3 / 2); @@ -4494,19 +4932,21 @@ new_hash_size = sh->prop_hash_mask + 1; while (new_hash_size < new_size) new_hash_size = 2 * new_hash_size; + /* resize the property shapes. Using js_realloc() is not possible in + case the GC runs during the allocation */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + /* copy all the shape properties */ + memcpy(sh, old_sh, + sizeof(JSShape) + sizeof(sh->prop0) * old_sh->prop_count); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + if (new_hash_size != (sh->prop_hash_mask + 1)) { - JSShape *old_sh; /* resize the hash table and the properties */ - old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) - return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - /* copy all the fields and the properties */ - memcpy(sh, old_sh, - sizeof(JSShape) + sizeof(sh->prop0) * old_sh->prop_count); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); new_hash_mask = new_hash_size - 1; sh->prop_hash_mask = new_hash_mask; memset(prop_hash_end(sh) - new_hash_size, 0, @@ -4518,20 +4958,12 @@ prop_hash_end(sh)-h - 1 = i + 1; } } - js_free(ctx, get_alloc_from_shape(old_sh)); } else { - /* only resize the properties */ - list_del(&sh->header.link); - sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), - get_shape_size(new_hash_size, new_size)); - if (unlikely(!sh_alloc)) { - /* insert again in the GC list */ - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - return -1; - } - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + /* just copy the previous hash table */ + memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size, + sizeof(prop_hash_end(sh)0) * new_hash_size); } + js_free(ctx, get_alloc_from_shape(old_sh)); *psh = sh; sh->prop_size = new_size; return 0; @@ -4640,7 +5072,6 @@ pr = &propsh->prop_count++; pr->atom = JS_DupAtom(ctx, atom); pr->flags = prop_flags; - sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); /* add in hash table */ hash_mask = sh->prop_hash_mask; h = atom & hash_mask; @@ -4755,15 +5186,16 @@ if (unlikely(!p)) goto fail; p->class_id = class_id; + p->is_prototype = 0; p->extensible = TRUE; p->free_mark = 0; p->is_exotic = 0; p->fast_array = 0; p->is_constructor = 0; - p->is_uncatchable_error = 0; + p->has_immutable_prototype = 0; p->tmp_mark = 0; p->is_HTMLDDA = 0; - p->first_weak_ref = NULL; + p->weakref_count = 0; p->u.opaque = NULL; p->shape = sh; p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); @@ -4808,10 +5240,9 @@ case JS_CLASS_UINT16_ARRAY: case JS_CLASS_INT32_ARRAY: case JS_CLASS_UINT32_ARRAY: -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: case JS_CLASS_BIG_UINT64_ARRAY: -#endif + case JS_CLASS_FLOAT16_ARRAY: case JS_CLASS_FLOAT32_ARRAY: case JS_CLASS_FLOAT64_ARRAY: p->is_exotic = 1; @@ -4828,17 +5259,13 @@ case JS_CLASS_BOOLEAN: case JS_CLASS_SYMBOL: case JS_CLASS_DATE: -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: - case JS_CLASS_BIG_FLOAT: - case JS_CLASS_BIG_DECIMAL: -#endif p->u.object_data = JS_UNDEFINED; goto set_exotic; case JS_CLASS_REGEXP: p->u.regexp.pattern = NULL; p->u.regexp.bytecode = NULL; - goto set_exotic; + break; default: set_exotic: if (ctx->rt->class_arrayclass_id.exotic) { @@ -4878,6 +5305,29 @@ return JS_NewObjectFromShape(ctx, sh, class_id); } +/* WARNING: the shape is not hashed. It is used for objects where + factorizing the shape is not relevant (prototypes, constructors) */ +static JSValue JS_NewObjectProtoClassAlloc(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id, int n_alloc_props) +{ + JSShape *sh; + JSObject *proto; + int hash_size, hash_bits; + + if (n_alloc_props <= JS_PROP_INITIAL_SIZE) { + n_alloc_props = JS_PROP_INITIAL_SIZE; + hash_size = JS_PROP_INITIAL_HASH_SIZE; + } else { + hash_bits = 32 - clz32(n_alloc_props - 1); /* ceil(log2(radix)) */ + hash_size = 1 << hash_bits; + } + proto = get_proto_obj(proto_val); + sh = js_new_shape_nohash(ctx, proto, hash_size, n_alloc_props); + if (!sh) + return JS_EXCEPTION; + return JS_NewObjectFromShape(ctx, sh, class_id); +} + #if 0 static JSValue JS_GetObjectData(JSContext *ctx, JSValueConst obj) { @@ -4891,11 +5341,7 @@ case JS_CLASS_BOOLEAN: case JS_CLASS_SYMBOL: case JS_CLASS_DATE: -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: - case JS_CLASS_BIG_FLOAT: - case JS_CLASS_BIG_DECIMAL: -#endif return JS_DupValue(ctx, p->u.object_data); } } @@ -4915,13 +5361,11 @@ case JS_CLASS_BOOLEAN: case JS_CLASS_SYMBOL: case JS_CLASS_DATE: -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: - case JS_CLASS_BIG_FLOAT: - case JS_CLASS_BIG_DECIMAL: -#endif JS_FreeValue(ctx, p->u.object_data); - p->u.object_data = val; + p->u.object_data = val; /* for JS_CLASS_STRING, 'val' must + be JS_TAG_STRING (and not a + rope) */ return 0; } } @@ -5047,13 +5491,17 @@ static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic, - JSValueConst proto_val) + JSValueConst proto_val, int n_fields) { JSValue func_obj; JSObject *p; JSAtom name_atom; - func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + if (n_fields > 0) { + func_obj = JS_NewObjectProtoClassAlloc(ctx, proto_val, JS_CLASS_C_FUNCTION, n_fields); + } else { + func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + } if (JS_IsException(func_obj)) return func_obj; p = JS_VALUE_GET_OBJ(func_obj); @@ -5069,6 +5517,10 @@ if (!name) name = ""; name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } js_function_set_properties(ctx, func_obj, name_atom, length); JS_FreeAtom(ctx, name_atom); return func_obj; @@ -5080,7 +5532,7 @@ int length, JSCFunctionEnum cproto, int magic) { return JS_NewCFunction3(ctx, func, name, length, cproto, magic, - ctx->function_proto); + ctx->function_proto, 0); } typedef struct JSCFunctionDataRecord { @@ -5263,10 +5715,12 @@ if (--var_ref->header.ref_count == 0) { if (var_ref->is_detached) { JS_FreeValueRT(rt, var_ref->value); - remove_gc_object(&var_ref->header); } else { - list_del(&var_ref->header.link); /* still on the stack */ + list_del(&var_ref->var_ref_link); /* still on the stack */ + if (var_ref->async_func) + async_func_free(rt, var_ref->async_func); } + remove_gc_object(&var_ref->header); js_free_rt(rt, var_ref); } } @@ -5364,7 +5818,7 @@ if (var_refs) { for(i = 0; i < b->closure_var_count; i++) { JSVarRef *var_ref = var_refsi; - if (var_ref && var_ref->is_detached) { + if (var_ref) { mark_func(rt, &var_ref->header); } } @@ -5406,7 +5860,15 @@ { JSObject *p = JS_VALUE_GET_OBJ(val); JSForInIterator *it = p->u.for_in_iterator; + int i; + JS_FreeValueRT(rt, it->obj); + if (!it->is_array) { + for(i = 0; i < it->atom_count; i++) { + JS_FreeAtomRT(rt, it->tab_atomi.atom); + } + js_free_rt(rt, it->tab_atom); + } js_free_rt(rt, it); } @@ -5443,10 +5905,6 @@ p->shape = NULL; p->prop = NULL; - if (unlikely(p->first_weak_ref)) { - reset_weak_ref(rt, p); - } - finalizer = rt->class_arrayp->class_id.finalizer; if (finalizer) (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); @@ -5458,10 +5916,21 @@ p->u.func.home_object = NULL; remove_gc_object(&p->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { - list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES) { + if (p->header.ref_count == 0 && p->weakref_count == 0) { + js_free_rt(rt, p); + } else { + /* keep the object structure because there are may be + references to it */ + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } } else { - js_free_rt(rt, p); + /* keep the object structure in case there are weak references to it */ + if (p->weakref_count == 0) { + js_free_rt(rt, p); + } else { + p->header.mark = 0; /* reset the mark so that the weakref can be freed */ + } } } @@ -5474,6 +5943,12 @@ case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: free_function_bytecode(rt, (JSFunctionBytecode *)gp); break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + __async_func_free(rt, (JSAsyncFunctionState *)gp); + break; + case JS_GC_OBJ_TYPE_MODULE: + js_free_module_def(rt, (JSModuleDef *)gp); + break; default: abort(); } @@ -5527,39 +6002,36 @@ } } break; + case JS_TAG_STRING_ROPE: + /* Note: recursion is acceptable because the rope depth is bounded */ + { + JSStringRope *p = JS_VALUE_GET_STRING_ROPE(v); + JS_FreeValueRT(rt, p->left); + JS_FreeValueRT(rt, p->right); + js_free_rt(rt, p); + } + break; case JS_TAG_OBJECT: case JS_TAG_FUNCTION_BYTECODE: + case JS_TAG_MODULE: { JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&p->link); list_add(&p->link, &rt->gc_zero_ref_count_list); + p->mark = 1; /* indicate that the object is about to be freed */ if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount(rt); } } } break; - case JS_TAG_MODULE: - abort(); /* never freed here */ - break; -#ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *bf = JS_VALUE_GET_PTR(v); - bf_delete(&bf->num); - js_free_rt(rt, bf); - } - break; - case JS_TAG_BIG_DECIMAL: { - JSBigDecimal *bf = JS_VALUE_GET_PTR(v); - bfdec_delete(&bf->num); - js_free_rt(rt, bf); + JSBigInt *p = JS_VALUE_GET_PTR(v); + js_free_rt(rt, p); } break; -#endif case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(v); @@ -5567,7 +6039,6 @@ } break; default: - printf("__JS_FreeValue: unknown tag=%d\n", tag); abort(); } } @@ -5579,6 +6050,36 @@ /* garbage collection */ +static void gc_remove_weak_objects(JSRuntime *rt) +{ + struct list_head *el; + + /* add the freed objects to rt->gc_zero_ref_count_list so that + rt->weakref_list is not modified while we traverse it */ + rt->gc_phase = JS_GC_PHASE_DECREF; + + list_for_each(el, &rt->weakref_list) { + JSWeakRefHeader *wh = list_entry(el, JSWeakRefHeader, link); + switch(wh->weakref_type) { + case JS_WEAKREF_TYPE_MAP: + map_delete_weakrefs(rt, wh); + break; + case JS_WEAKREF_TYPE_WEAKREF: + weakref_delete_weakref(rt, wh); + break; + case JS_WEAKREF_TYPE_FINREC: + finrec_delete_weakref(rt, wh); + break; + default: + abort(); + } + } + + rt->gc_phase = JS_GC_PHASE_NONE; + /* free the freed objects here. */ + free_zero_refcount(rt); +} + static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type) { @@ -5598,6 +6099,7 @@ switch(JS_VALUE_GET_TAG(val)) { case JS_TAG_OBJECT: case JS_TAG_FUNCTION_BYTECODE: + case JS_TAG_MODULE: mark_func(rt, JS_VALUE_GET_PTR(val)); break; default: @@ -5630,11 +6132,9 @@ if (pr->u.getset.setter) mark_func(rt, &pr->u.getset.setter->header); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - if (pr->u.var_ref->is_detached) { - /* Note: the tag does not matter - provided it is a GC object */ - mark_func(rt, &pr->u.var_ref->header); - } + /* Note: the tag does not matter + provided it is a GC object */ + mark_func(rt, &pr->u.var_ref->header); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { js_autoinit_mark(rt, pr, mark_func); } @@ -5668,16 +6168,32 @@ case JS_GC_OBJ_TYPE_VAR_REF: { JSVarRef *var_ref = (JSVarRef *)gp; - /* only detached variable referenced are taken into account */ - assert(var_ref->is_detached); - JS_MarkValue(rt, *var_ref->pvalue, mark_func); + if (var_ref->is_detached) { + JS_MarkValue(rt, *var_ref->pvalue, mark_func); + } else if (var_ref->async_func) { + mark_func(rt, &var_ref->async_func->header); + } } break; case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: { - JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; - if (s->is_active) - async_func_mark(rt, &s->func_state, mark_func); + JSAsyncFunctionState *s = (JSAsyncFunctionState *)gp; + JSStackFrame *sf = &s->frame; + JSValue *sp; + + if (!s->is_completed) { + JS_MarkValue(rt, sf->cur_func, mark_func); + JS_MarkValue(rt, s->this_val, mark_func); + /* sf->cur_sp = NULL if the function is running */ + if (sf->cur_sp) { + /* if the function is running, cur_sp is not known so we + cannot mark the stack. Marking the variables is not needed + because a running function cannot be part of a removable + cycle */ + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) + JS_MarkValue(rt, *sp, mark_func); + } + } JS_MarkValue(rt, s->resolving_funcs0, mark_func); JS_MarkValue(rt, s->resolving_funcs1, mark_func); } @@ -5696,6 +6212,12 @@ JS_MarkContext(rt, ctx, mark_func); } break; + case JS_GC_OBJ_TYPE_MODULE: + { + JSModuleDef *m = (JSModuleDef *)gp; + js_mark_module_def(rt, m, mark_func); + } + break; default: abort(); } @@ -5785,12 +6307,14 @@ if (el == &rt->tmp_obj_list) break; p = list_entry(el, JSGCObjectHeader, link); - /* Only need to free the GC object associated with JS - values. The rest will be automatically removed because they - must be referenced by them. */ + /* Only need to free the GC object associated with JS values + or async functions. The rest will be automatically removed + because they must be referenced by them. */ switch(p->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + case JS_GC_OBJ_TYPE_MODULE: #ifdef DUMP_GC_FREE if (!header_done) { printf("Freeing cycles:\n"); @@ -5812,15 +6336,30 @@ list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { p = list_entry(el, JSGCObjectHeader, link); assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || - p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); - js_free_rt(rt, p); + p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || + p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION || + p->gc_obj_type == JS_GC_OBJ_TYPE_MODULE); + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && + ((JSObject *)p)->weakref_count != 0) { + /* keep the object because there are weak references to it */ + p->mark = 0; + } else { + js_free_rt(rt, p); + } } init_list_head(&rt->gc_zero_ref_count_list); } -void JS_RunGC(JSRuntime *rt) +static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects) { + if (remove_weak_objects) { + /* free the weakly referenced object or symbol structures, delete + the associated Map/Set entries and queue the finalization + registry callbacks. */ + gc_remove_weak_objects(rt); + } + /* decrement the reference of the children of each object. mark = 1 after this pass. */ gc_decref(rt); @@ -5832,6 +6371,11 @@ gc_free_cycles(rt); } +void JS_RunGC(JSRuntime *rt) +{ + JS_RunGCInternal(rt, TRUE); +} + /* Return false if not an object or if the object has already been freed (zombie objects are visible in finalizers when freeing cycles). */ @@ -5914,13 +6458,9 @@ case JS_TAG_STRING: compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); break; -#ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - case JS_TAG_BIG_DECIMAL: - /* should track JSBigFloat usage */ + /* should track JSBigInt usage */ break; -#endif } } @@ -6044,11 +6584,7 @@ case JS_CLASS_BOOLEAN: /* u.object_data */ case JS_CLASS_SYMBOL: /* u.object_data */ case JS_CLASS_DATE: /* u.object_data */ -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: /* u.object_data */ - case JS_CLASS_BIG_FLOAT: /* u.object_data */ - case JS_CLASS_BIG_DECIMAL: /* u.object_data */ -#endif compute_value_size(p->u.object_data, hp); break; case JS_CLASS_C_FUNCTION: /* u.cfunc */ @@ -6137,16 +6673,12 @@ case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ -#endif + case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_DATAVIEW: /* u.typed_array */ -#ifdef CONFIG_BIGNUM - case JS_CLASS_FLOAT_ENV: /* u.float_env */ -#endif case JS_CLASS_MAP: /* u.map_state */ case JS_CLASS_SET: /* u.map_state */ case JS_CLASS_WEAKMAP: /* u.map_state */ @@ -6216,12 +6748,8 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) { - fprintf(fp, "QuickJS memory usage -- " -#ifdef CONFIG_BIGNUM - "BigNum " -#endif - CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n", - (int)sizeof(void *) * 8, (int64_t)(ssize_t)s->malloc_limit); + fprintf(fp, "QuickJS memory usage -- " CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n", + (int)sizeof(void *) * 8, s->malloc_limit); #if 1 if (rt) { static const struct { @@ -6267,10 +6795,10 @@ if (obj_classes0) fprintf(fp, " %5d %2.0d %s\n", obj_classes0, 0, "none"); for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { - if (obj_classesclass_id) { + if (obj_classesclass_id && class_id < rt->class_count) { char bufATOM_GET_STR_BUF_SIZE; fprintf(fp, " %5d %2.0d %s\n", obj_classesclass_id, class_id, - JS_AtomGetStrRT(rt, buf, sizeof(buf), js_std_class_defclass_id - 1.class_name)); + JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_arrayclass_id.class_name)); } } if (obj_classesJS_CLASS_INIT_COUNT) @@ -6354,6 +6882,7 @@ JSRuntime *rt = ctx->rt; JS_FreeValue(ctx, rt->current_exception); rt->current_exception = obj; + rt->current_exception_is_uncatchable = FALSE; return JS_EXCEPTION; } @@ -6363,10 +6892,15 @@ JSValue val; JSRuntime *rt = ctx->rt; val = rt->current_exception; - rt->current_exception = JS_NULL; + rt->current_exception = JS_UNINITIALIZED; return val; } +JS_BOOL JS_HasException(JSContext *ctx) +{ + return !JS_IsUninitialized(ctx->rt->current_exception); +} + static void dbuf_put_leb128(DynBuf *s, uint32_t v) { uint32_t a; @@ -6422,65 +6956,98 @@ return ret; } +/* use pc_value = -1 to get the position of the function definition */ static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, - uint32_t pc_value) + uint32_t pc_value, int *pcol_num) { const uint8_t *p_end, *p; - int new_line_num, line_num, pc, v, ret; + int new_line_num, line_num, pc, v, ret, new_col_num, col_num; + uint32_t val; unsigned int op; - if (!b->has_debug || !b->debug.pc2line_buf) { - /* function was stripped */ - return -1; - } + if (!b->has_debug || !b->debug.pc2line_buf) + goto fail; /* function was stripped */ p = b->debug.pc2line_buf; p_end = p + b->debug.pc2line_len; - pc = 0; - line_num = b->debug.line_num; - while (p < p_end) { - op = *p++; - if (op == 0) { - uint32_t val; - ret = get_leb128(&val, p, p_end); + + /* get the function line and column numbers */ + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num = val + 1; + + if (pc_value != -1) { + pc = 0; + while (p < p_end) { + op = *p++; + if (op == 0) { + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128(&v, p, p_end); if (ret < 0) goto fail; - pc += val; - p += ret; - ret = get_sleb128(&v, p, p_end); - if (ret < 0) { - fail: - /* should never happen */ - return b->debug.line_num; - } p += ret; - new_line_num = line_num + v; - } else { - op -= PC2LINE_OP_FIRST; - pc += (op / PC2LINE_RANGE); - new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + new_col_num = col_num + v; + + if (pc_value < pc) + goto done; + line_num = new_line_num; + col_num = new_col_num; } - if (pc_value < pc) - return line_num; - line_num = new_line_num; } + done: + *pcol_num = col_num; return line_num; + fail: + *pcol_num = 0; + return 0; } -/* in order to avoid executing arbitrary code during the stack trace - generation, we only look at simple 'name' properties containing a - string. */ -static const char *get_func_name(JSContext *ctx, JSValueConst func) +/* return a string property without executing arbitrary JS code (used + when dumping the stack trace or in debug print). */ +static const char *get_prop_string(JSContext *ctx, JSValueConst obj, JSAtom prop) { + JSObject *p; JSProperty *pr; JSShapeProperty *prs; JSValueConst val; - if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) - return NULL; - prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); - if (!prs) + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return NULL; + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + /* we look at one level in the prototype to handle the 'name' + field of the Error objects */ + p = p->shape->proto; + if (!p) + return NULL; + prs = find_own_property(&pr, p, prop); + if (!prs) + return NULL; + } + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) return NULL; val = pr->u.value; @@ -6490,13 +7057,11 @@ } #define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) -/* only taken into account if filename is provided */ -#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) /* if filename != NULL, an additional level is added with the filename and line number information (used for parse error). */ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, - const char *filename, int line_num, + const char *filename, int line_num, int col_num, int backtrace_flags) { JSStackFrame *sf; @@ -6505,28 +7070,37 @@ const char *func_name_str; const char *str1; JSObject *p; - BOOL backtrace_barrier; + + if (!JS_IsObject(error_obj)) + return; /* protection in the out of memory case */ js_dbuf_init(ctx, &dbuf); if (filename) { dbuf_printf(&dbuf, " at %s", filename); if (line_num != -1) - dbuf_printf(&dbuf, ":%d", line_num); + dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); dbuf_putc(&dbuf, '\n'); str = JS_NewString(ctx, filename); - JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL) - goto done; + if (JS_IsException(str)) + return; + /* Note: SpiderMonkey does that, could update once there is a standard */ + if (JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0 || + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0 || + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_columnNumber, JS_NewInt32(ctx, col_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0) { + return; + } } for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) + break; if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; continue; } - func_name_str = get_func_name(ctx, sf->cur_func); + func_name_str = get_prop_string(ctx, sf->cur_func, JS_ATOM_name); if (!func_name_str || func_name_str0 == '\0') str1 = "<anonymous>"; else @@ -6535,34 +7109,28 @@ JS_FreeCString(ctx, func_name_str); p = JS_VALUE_GET_OBJ(sf->cur_func); - backtrace_barrier = FALSE; if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b; const char *atom_str; - int line_num1; + int line_num1, col_num1; b = p->u.func.function_bytecode; - backtrace_barrier = b->backtrace_barrier; if (b->has_debug) { line_num1 = find_line_num(ctx, b, - sf->cur_pc - b->byte_code_buf - 1); + sf->cur_pc - b->byte_code_buf - 1, &col_num1); atom_str = JS_AtomToCString(ctx, b->debug.filename); dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : "<null>"); JS_FreeCString(ctx, atom_str); - if (line_num1 != -1) - dbuf_printf(&dbuf, ":%d", line_num1); + if (line_num1 != 0) + dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); dbuf_putc(&dbuf, ')'); } } else { dbuf_printf(&dbuf, " (native)"); } dbuf_putc(&dbuf, '\n'); - /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ - if (backtrace_barrier) - break; } - done: dbuf_putc(&dbuf, '\0'); if (dbuf_error(&dbuf)) str = JS_NULL; @@ -6608,9 +7176,9 @@ JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, JS_NewString(ctx, buf), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - } - if (add_backtrace) { - build_backtrace(ctx, obj, NULL, 0, 0); + if (add_backtrace) { + build_backtrace(ctx, obj, NULL, 0, 0, 0); + } } ret = JS_Throw(ctx, obj); return ret; @@ -6828,15 +7396,19 @@ return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); } +static void JS_ThrowInterrupted(JSContext *ctx) +{ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableException(ctx, TRUE); +} + static no_inline __exception int __js_poll_interrupts(JSContext *ctx) { JSRuntime *rt = ctx->rt; ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; if (rt->interrupt_handler) { if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { - /* XXX: should set a specific flag to avoid catching */ - JS_ThrowInternalError(ctx, "interrupted"); - JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE); + JS_ThrowInterrupted(ctx); return -1; } } @@ -6852,7 +7424,17 @@ } } -/* return -1 (exception) or TRUE/FALSE */ +static void JS_SetImmutablePrototype(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->has_immutable_prototype = TRUE; +} + +/* Return -1 (exception) or TRUE/FALSE. 'throw_flag' = FALSE indicates + that it is called from Reflect.setPrototypeOf(). */ static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag) @@ -6883,12 +7465,32 @@ if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return TRUE; - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); + if (unlikely(p->is_exotic)) { + const JSClassExoticMethods *em = ctx->rt->class_arrayp->class_id.exotic; + int ret; + if (em && em->set_prototype) { + ret = em->set_prototype(ctx, obj, proto_val); + if (ret == 0 && throw_flag) { + JS_ThrowTypeError(ctx, "proxy: bad prototype"); + return -1; + } else { + return ret; + } + } + } + sh = p->shape; if (sh->proto == proto) return TRUE; - if (!p->extensible) { + if (unlikely(p->has_immutable_prototype)) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "prototype is immutable"); + return -1; + } else { + return FALSE; + } + } + if (unlikely(!p->extensible)) { if (throw_flag) { JS_ThrowTypeError(ctx, "object is not extensible"); return -1; @@ -6920,6 +7522,14 @@ if (sh->proto) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); sh->proto = proto; + if (proto) + proto->is_prototype = TRUE; + if (p->is_prototype) { + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_ARRAY))) { + ctx->std_array_prototype = FALSE; + } + } return TRUE; } @@ -6933,17 +7543,10 @@ static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) { switch(JS_VALUE_GET_NORM_TAG(val)) { -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: val = ctx->class_protoJS_CLASS_BIG_INT; break; - case JS_TAG_BIG_FLOAT: - val = ctx->class_protoJS_CLASS_BIG_FLOAT; - break; - case JS_TAG_BIG_DECIMAL: - val = ctx->class_protoJS_CLASS_BIG_DECIMAL; - break; -#endif case JS_TAG_INT: case JS_TAG_FLOAT64: val = ctx->class_protoJS_CLASS_NUMBER; @@ -6952,6 +7555,7 @@ val = ctx->class_protoJS_CLASS_BOOLEAN; break; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: val = ctx->class_protoJS_CLASS_STRING; break; case JS_TAG_SYMBOL: @@ -6967,22 +7571,24 @@ return val; } -/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ +/* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */ JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) { JSValue val; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *p; p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) { - val = js_proxy_getPrototypeOf(ctx, obj); - } else { - p = p->shape->proto; - if (!p) - val = JS_NULL; - else - val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (unlikely(p->is_exotic)) { + const JSClassExoticMethods *em = ctx->rt->class_arrayp->class_id.exotic; + if (em && em->get_prototype) { + return em->get_prototype(ctx, obj); + } } + p = p->shape->proto; + if (!p) + val = JS_NULL; + else + val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); } else { val = JS_DupValue(ctx, JS_GetPrototypePrimitive(ctx, obj)); } @@ -7029,8 +7635,8 @@ for(;;) { proto1 = p->shape->proto; if (!proto1) { - /* slow case if proxy in the prototype chain */ - if (unlikely(p->class_id == JS_CLASS_PROXY)) { + /* slow case if exotic object in the prototype chain */ + if (unlikely(p->is_exotic && !p->fast_array)) { JSValue obj1; obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); for(;;) { @@ -7112,12 +7718,14 @@ JSValue val; JSContext *realm; JSAutoInitFunc *func; + JSAutoInitIDEnum id; if (js_shape_prepare_update(ctx, p, &prs)) return -1; realm = js_autoinit_get_realm(pr); - func = js_autoinit_func_tablejs_autoinit_get_id(pr); + id = js_autoinit_get_id(pr); + func = js_autoinit_func_tableid; /* 'func' shall not modify the object properties 'pr' */ val = func(realm, p, prop, pr->u.init.opaque); js_autoinit_free(ctx->rt, pr); @@ -7125,7 +7733,15 @@ pr->u.value = JS_UNDEFINED; if (JS_IsException(val)) return -1; - pr->u.value = val; + if (id == JS_AUTOINIT_ID_MODULE_NS && + JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + /* WARNING: a varref is returned as a string ! */ + prs->flags |= JS_PROP_VARREF; + pr->u.var_ref = JS_VALUE_GET_PTR(val); + pr->u.var_ref->header.ref_count++; + } else { + pr->u.value = val; + } return 0; } @@ -7151,14 +7767,24 @@ { JSString *p1 = JS_VALUE_GET_STRING(obj); if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx, ch; + uint32_t idx; idx = __JS_AtomToUInt32(prop); if (idx < p1->len) { - if (p1->is_wide_char) - ch = p1->u.str16idx; - else - ch = p1->u.str8idx; - return js_new_string_char(ctx, ch); + return js_new_string_char(ctx, string_get(p1, idx)); + } + } else if (prop == JS_ATOM_length) { + return JS_NewInt32(ctx, p1->len); + } + } + break; + case JS_TAG_STRING_ROPE: + { + JSStringRope *p1 = JS_VALUE_GET_STRING_ROPE(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + return js_new_string_char(ctx, string_rope_get(obj, idx)); } } else if (prop == JS_ATOM_length) { return JS_NewInt32(ctx, p1->len); @@ -7383,6 +8009,8 @@ return 0; } +/* add a private brand field to 'home_obj' if not already present and + if obj is != null add a private brand to it */ static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) { JSObject *p, *p1; @@ -7398,10 +8026,10 @@ p = JS_VALUE_GET_OBJ(home_obj); prs = find_own_property(&pr, p, JS_ATOM_Private_brand); if (!prs) { + /* if the brand is not present, add it */ brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); if (JS_IsException(brand)) return -1; - /* if the brand is not present, add it */ pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); if (!pr) { JS_FreeValue(ctx, brand); @@ -7413,20 +8041,27 @@ } brand_atom = js_symbol_to_atom(ctx, brand); - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); + if (JS_IsObject(obj)) { + p1 = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p1, brand_atom); + if (unlikely(prs)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "private method is already present"); + return -1; + } + pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); + JS_FreeAtom(ctx, brand_atom); + if (!pr) + return -1; + pr->u.value = JS_UNDEFINED; + } else { JS_FreeAtom(ctx, brand_atom); - return -1; } - p1 = JS_VALUE_GET_OBJ(obj); - pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); - JS_FreeAtom(ctx, brand_atom); - if (!pr) - return -1; - pr->u.value = JS_UNDEFINED; return 0; } +/* return a boolean telling if the brand of the home object of 'func' + is present on 'obj' or -1 in case of exception */ static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func) { JSObject *p, *p1, *home_obj; @@ -7435,11 +8070,8 @@ JSValueConst brand; /* get the home object of 'func' */ - if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) { - not_obj: - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } + if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) + goto not_obj; p1 = JS_VALUE_GET_OBJ(func); if (!js_class_has_bytecode(p1->class_id)) goto not_obj; @@ -7457,32 +8089,30 @@ goto not_obj; /* get the brand array of 'obj' */ - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - goto not_obj; + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } p = JS_VALUE_GET_OBJ(obj); -#if defined(JS_VALUE_CANNOT_BE_CAST) - prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); -#else +#if !defined(_MSC_VER) prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand)); +#else + prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); #endif - if (!prs) { - JS_ThrowTypeError(ctx, "invalid brand on object"); - return -1; - } - return 0; + return (prs != NULL); } static uint32_t js_string_obj_get_length(JSContext *ctx, JSValueConst obj) { JSObject *p; - JSString *p1; uint32_t len = 0; /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ p = JS_VALUE_GET_OBJ(obj); if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { - p1 = JS_VALUE_GET_STRING(p->u.object_data); + JSString *p1 = JS_VALUE_GET_STRING(p->u.object_data); len = p1->len; } return len; @@ -7512,7 +8142,7 @@ return 1; } -static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) { uint32_t i; if (tab) { @@ -7606,7 +8236,7 @@ /* set the "is_enumerable" field if necessary */ res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); if (res < 0) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); + JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); return -1; } if (res) { @@ -7627,11 +8257,25 @@ /* fill them */ - atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; + atom_count = num_keys_count + str_keys_count; + if (atom_count < str_keys_count) + goto add_overflow; + atom_count += sym_keys_count; + if (atom_count < sym_keys_count) + goto add_overflow; + atom_count += exotic_keys_count; + if (atom_count < exotic_keys_count || atom_count > INT32_MAX) { + add_overflow: + JS_ThrowOutOfMemory(ctx); + JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); + return -1; + } + /* XXX: need generic way to test for js_malloc(ctx, a * b) overflow */ + /* avoid allocating 0 bytes */ tab_atom = js_malloc(ctx, sizeof(tab_atom0) * max_int(atom_count, 1)); if (!tab_atom) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); + JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); return -1; } @@ -7676,7 +8320,7 @@ for(i = 0; i < len; i++) { tab_atomnum_index.atom = __JS_AtomFromUInt32(i); if (tab_atomnum_index.atom == JS_ATOM_NULL) { - js_free_prop_enum(ctx, tab_atom, num_index); + JS_FreePropertyEnum(ctx, tab_atom, num_index); return -1; } tab_atomnum_index.is_enumerable = TRUE; @@ -7817,7 +8461,7 @@ return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); } -/* return -1 if exception (Proxy object only) or TRUE/FALSE */ +/* return -1 if exception (exotic object only) or TRUE/FALSE */ int JS_IsExtensible(JSContext *ctx, JSValueConst obj) { JSObject *p; @@ -7825,13 +8469,16 @@ if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return FALSE; p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_isExtensible(ctx, obj); - else - return p->extensible; + if (unlikely(p->is_exotic)) { + const JSClassExoticMethods *em = ctx->rt->class_arrayp->class_id.exotic; + if (em && em->is_extensible) { + return em->is_extensible(ctx, obj); + } + } + return p->extensible; } -/* return -1 if exception (Proxy object only) or TRUE/FALSE */ +/* return -1 if exception (exotic object only) or TRUE/FALSE */ int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) { JSObject *p; @@ -7839,8 +8486,24 @@ if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return FALSE; p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_preventExtensions(ctx, obj); + if (unlikely(p->is_exotic)) { + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSTypedArray *ta; + JSArrayBuffer *abuf; + /* resizable type arrays return FALSE */ + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (ta->track_rab || + (array_buffer_is_resizable(abuf) && !abuf->shared)) + return FALSE; + } else { + const JSClassExoticMethods *em = ctx->rt->class_arrayp->class_id.exotic; + if (em && em->prevent_extensions) { + return em->prevent_extensions(ctx, obj); + } + } + } p->extensible = FALSE; return TRUE; } @@ -7931,45 +8594,59 @@ if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { JSObject *p; - uint32_t idx, len; + uint32_t idx; /* fast path for array access */ p = JS_VALUE_GET_OBJ(this_obj); idx = JS_VALUE_GET_INT(prop); - len = (uint32_t)p->u.array.count; - if (unlikely(idx >= len)) - goto slow_path; switch(p->class_id) { case JS_CLASS_ARRAY: case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_DupValue(ctx, p->u.array.u.valuesidx); case JS_CLASS_INT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewInt32(ctx, p->u.array.u.int8_ptridx); case JS_CLASS_UINT8C_ARRAY: case JS_CLASS_UINT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewInt32(ctx, p->u.array.u.uint8_ptridx); case JS_CLASS_INT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewInt32(ctx, p->u.array.u.int16_ptridx); case JS_CLASS_UINT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewInt32(ctx, p->u.array.u.uint16_ptridx); case JS_CLASS_INT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewInt32(ctx, p->u.array.u.int32_ptridx); case JS_CLASS_UINT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewUint32(ctx, p->u.array.u.uint32_ptridx); -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewBigInt64(ctx, p->u.array.u.int64_ptridx); case JS_CLASS_BIG_UINT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptridx); -#endif + case JS_CLASS_FLOAT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return __JS_NewFloat64(ctx, fromfp16(p->u.array.u.fp16_ptridx)); case JS_CLASS_FLOAT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return __JS_NewFloat64(ctx, p->u.array.u.float_ptridx); case JS_CLASS_FLOAT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; return __JS_NewFloat64(ctx, p->u.array.u.double_ptridx); default: goto slow_path; } } else { slow_path: + /* ToObject() must be done before ToPropertyKey() */ + if (JS_IsNull(this_obj) || JS_IsUndefined(this_obj)) { + JS_FreeValue(ctx, prop); + return JS_ThrowTypeError(ctx, "cannot read property of %s", JS_IsNull(this_obj) ? "null" : "undefined"); + } atom = JS_ValueToAtom(ctx, prop); JS_FreeValue(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) @@ -8046,6 +8723,8 @@ JSAtom atom; JSValue ret; atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; ret = JS_GetProperty(ctx, this_obj, atom); JS_FreeAtom(ctx, atom); return ret; @@ -8058,6 +8737,14 @@ { JSShape *sh, *new_sh; + if (unlikely(p->is_prototype)) { + /* track addition of small integer properties to Array.prototype and Object.prototype */ + if (unlikely((p == JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_ARRAY) || + p == JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_OBJECT)) && + __JS_AtomIsTaggedInt(prop))) { + ctx->std_array_prototype = FALSE; + } + } sh = p->shape; if (sh->is_hashed) { /* try to find an existing shape */ @@ -8127,6 +8814,11 @@ p->u.array.u.values = NULL; /* fail safe */ p->u.array.u1.size = 0; p->fast_array = 0; + + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_ARRAY))) { + ctx->std_array_prototype = FALSE; + } return 0; } @@ -8351,8 +9043,8 @@ /* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = TRUE and p->extensible = TRUE */ -static int add_fast_array_element(JSContext *ctx, JSObject *p, - JSValue val, int flags) +static inline int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) { uint32_t new_len, array_len; /* extend the array by one */ @@ -8381,126 +9073,95 @@ return TRUE; } -static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +/* Allocate a new fast array. Its 'length' property is set to zero. It + maximum size is 2^31-1 elements. For convenience, 'len' is a 64 bit + integer. WARNING: the content of the array is not initialized. */ +static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) { - JS_FreeValue(ctx, desc->getter); - JS_FreeValue(ctx, desc->setter); - JS_FreeValue(ctx, desc->value); + JSValue arr; + JSObject *p; + + if (len > INT32_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return arr; + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; + } + p->u.array.count = len; + } + return arr; } -/* generic (and slower) version of JS_SetProperty() for - * Reflect.set(). 'obj' must be an object. */ -static int JS_SetPropertyGeneric(JSContext *ctx, - JSValueConst obj, JSAtom prop, - JSValue val, JSValueConst this_obj, - int flags) +static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab) { - int ret; - JSPropertyDescriptor desc; - JSValue obj1; + JSValue obj; JSObject *p; + int i; - obj1 = JS_DupValue(ctx, obj); - for(;;) { - p = JS_VALUE_GET_OBJ(obj1); - if (p->is_exotic) { - const JSClassExoticMethods *em = ctx->rt->class_arrayp->class_id.exotic; - if (em && em->set_property) { - ret = em->set_property(ctx, obj1, prop, - val, this_obj, flags); - JS_FreeValue(ctx, obj1); - JS_FreeValue(ctx, val); - return ret; - } - } - - ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); - if (ret < 0) { - JS_FreeValue(ctx, obj1); - JS_FreeValue(ctx, val); - return ret; - } - if (ret) { - if (desc.flags & JS_PROP_GETSET) { - JSObject *setter; - if (JS_IsUndefined(desc.setter)) - setter = NULL; - else - setter = JS_VALUE_GET_OBJ(desc.setter); - ret = call_setter(ctx, setter, this_obj, val, flags); - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - JS_FreeValue(ctx, obj1); - return ret; - } else { - JS_FreeValue(ctx, desc.value); - if (!(desc.flags & JS_PROP_WRITABLE)) { - JS_FreeValue(ctx, obj1); - goto read_only_error; - } - } - break; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (len > 0) { + p = JS_VALUE_GET_OBJ(obj); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; } - /* Note: at this point 'obj1' cannot be a proxy. XXX: may have - to check recursion */ - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - } - JS_FreeValue(ctx, obj1); - - if (!JS_IsObject(this_obj)) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorOrFalse(ctx, flags, "receiver is not an object"); + p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.valuesi = JS_DupValue(ctx, tabi); + /* update the 'length' field */ + set_value(ctx, &p->prop0.u.value, JS_NewInt32(ctx, len)); } + return obj; +} - p = JS_VALUE_GET_OBJ(this_obj); +static JSValue js_create_array_free(JSContext *ctx, int len, JSValue *tab) +{ + JSValue obj; + JSObject *p; + int i; - /* modify the property in this_obj if it already exists */ - ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); - if (ret < 0) { - JS_FreeValue(ctx, val); - return ret; - } - if (ret) { - if (desc.flags & JS_PROP_GETSET) { - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); - } else { - JS_FreeValue(ctx, desc.value); - if (!(desc.flags & JS_PROP_WRITABLE) || - p->class_id == JS_CLASS_MODULE_NS) { - read_only_error: - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); - } + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + goto fail; + if (len > 0) { + p = JS_VALUE_GET_OBJ(obj); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, obj); + fail: + for(i = 0; i < len; i++) + JS_FreeValue(ctx, tabi); + return JS_EXCEPTION; } - ret = JS_DefineProperty(ctx, this_obj, prop, val, - JS_UNDEFINED, JS_UNDEFINED, - JS_PROP_HAS_VALUE); - JS_FreeValue(ctx, val); - return ret; + p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.valuesi = tabi; + /* update the 'length' field */ + set_value(ctx, &p->prop0.u.value, JS_NewInt32(ctx, len)); } + return obj; +} - ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, - flags | - JS_PROP_HAS_VALUE | - JS_PROP_HAS_ENUMERABLE | - JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_CONFIGURABLE | - JS_PROP_C_W_E); - JS_FreeValue(ctx, val); - return ret; +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +{ + JS_FreeValue(ctx, desc->getter); + JS_FreeValue(ctx, desc->setter); + JS_FreeValue(ctx, desc->value); } + /* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is - freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, - JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, - the new property is not added and an error is raised. */ -int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj, - JSAtom prop, JSValue val, int flags) + freed by the function. 'flags' is a bitmask of JS_PROP_THROW and + JS_PROP_THROW_STRICT. 'this_obj' is the receiver. If obj != + this_obj, then obj must be an object (Reflect.set case). */ +int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValue val, JSValueConst this_obj, int flags) { JSObject *p, *p1; JSShapeProperty *prs; @@ -8513,25 +9174,37 @@ #endif tag = JS_VALUE_GET_TAG(this_obj); if (unlikely(tag != JS_TAG_OBJECT)) { - switch(tag) { - case JS_TAG_NULL: - JS_FreeValue(ctx, val); - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); - return -1; - case JS_TAG_UNDEFINED: - JS_FreeValue(ctx, val); - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); - return -1; - default: - /* even on a primitive type we can have setters on the prototype */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { p = NULL; - p1 = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, this_obj)); + p1 = JS_VALUE_GET_OBJ(obj); goto prototype_lookup; + } else { + switch(tag) { + case JS_TAG_NULL: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + return -1; + case JS_TAG_UNDEFINED: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + return -1; + default: + /* even on a primitive type we can have setters on the prototype */ + p = NULL; + p1 = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + goto prototype_lookup; + } } + } else { + p = JS_VALUE_GET_OBJ(this_obj); + p1 = JS_VALUE_GET_OBJ(obj); + if (unlikely(p != p1)) + goto retry2; } - p = JS_VALUE_GET_OBJ(this_obj); -retry: - prs = find_own_property(&pr, p, prop); + + /* fast path if obj == this_obj */ + retry: + prs = find_own_property(&pr, p1, prop); if (prs) { if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { @@ -8564,7 +9237,6 @@ } } - p1 = p; for(;;) { if (p1->is_exotic) { if (p1->fast_array) { @@ -8588,11 +9260,23 @@ return -1; } typed_array_oob: - val = JS_ToNumberFree(ctx, val); - JS_FreeValue(ctx, val); - if (JS_IsException(val)) - return -1; - return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); + if (p == p1) { + /* must convert the argument even if out of bound access */ + if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || + p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + } else { + val = JS_ToNumberFree(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(val)) + return -1; + } + } else { + JS_FreeValue(ctx, val); + } + return TRUE; } } } else { @@ -8664,19 +9348,13 @@ return -1; goto retry2; } else if (!(prs->flags & JS_PROP_WRITABLE)) { - read_only_prop: - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + goto read_only_prop; + } else { + break; } } } - if (unlikely(flags & JS_PROP_NO_ADD)) { - JS_FreeValue(ctx, val); - JS_ThrowReferenceErrorNotDefined(ctx, prop); - return -1; - } - if (unlikely(!p)) { JS_FreeValue(ctx, val); return JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); @@ -8687,17 +9365,57 @@ return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); } - if (p->is_exotic) { - if (p->class_id == JS_CLASS_ARRAY && p->fast_array && - __JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx == p->u.array.count) { - /* fast case */ - return add_fast_array_element(ctx, p, val, flags); + if (likely(p == JS_VALUE_GET_OBJ(obj))) { + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY && p->fast_array && + __JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + /* fast case */ + return add_fast_array_element(ctx, p, val, flags); + } else { + goto generic_create_prop; + } } else { goto generic_create_prop; } } else { + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return TRUE; + } + } else { + /* generic case: modify the property in this_obj if it already exists */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (ret < 0) { + JS_FreeValue(ctx, val); + return ret; + } + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE) || + p->class_id == JS_CLASS_MODULE_NS) { + read_only_prop: + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + } + } + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { generic_create_prop: ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, flags | @@ -8710,14 +9428,6 @@ return ret; } } - - pr = add_property(ctx, p, prop, JS_PROP_C_W_E); - if (unlikely(!pr)) { - JS_FreeValue(ctx, val); - return -1; - } - pr->u.value = val; - return TRUE; } /* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ @@ -8737,27 +9447,13 @@ switch(p->class_id) { case JS_CLASS_ARRAY: if (unlikely(idx >= (uint32_t)p->u.array.count)) { - JSObject *p1; - JSShape *sh1; - /* fast path to add an element to the array */ - if (idx != (uint32_t)p->u.array.count || - !p->fast_array || !p->extensible) + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_ARRAY) || + !ctx->std_array_prototype)) { goto slow_path; - /* check if prototype chain has a numeric property */ - p1 = p->shape->proto; - while (p1 != NULL) { - sh1 = p1->shape; - if (p1->class_id == JS_CLASS_ARRAY) { - if (unlikely(!p1->fast_array)) - goto slow_path; - } else if (p1->class_id == JS_CLASS_OBJECT) { - if (unlikely(sh1->has_small_array_index)) - goto slow_path; - } else { - goto slow_path; - } - p1 = sh1->proto; } /* add element */ return add_fast_array_element(ctx, p, val, flags); @@ -8802,7 +9498,6 @@ goto ta_out_of_bound; p->u.array.u.uint32_ptridx = v; break; -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: case JS_CLASS_BIG_UINT64_ARRAY: /* XXX: need specific conversion function */ @@ -8815,7 +9510,13 @@ p->u.array.u.uint64_ptridx = v; } break; -#endif + case JS_CLASS_FLOAT16_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.fp16_ptridx = tofp16(d); + break; case JS_CLASS_FLOAT32_ARRAY: if (JS_ToFloat64Free(ctx, &d, val)) return -1; @@ -8828,7 +9529,7 @@ return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) { ta_out_of_bound: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); + return TRUE; } p->u.array.u.double_ptridx = d; break; @@ -8846,7 +9547,7 @@ JS_FreeValue(ctx, val); return -1; } - ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags); + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, flags); JS_FreeAtom(ctx, atom); return ret; } @@ -8886,7 +9587,11 @@ JSAtom atom; int ret; atom = JS_NewAtom(ctx, prop); - ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, JS_PROP_THROW); JS_FreeAtom(ctx, atom); return ret; } @@ -9027,15 +9732,13 @@ if ((flags & JS_PROP_HAS_ENUMERABLE) && (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) return FALSE; - } - if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); if (has_accessor != is_getset) return FALSE; - if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { + if (!is_getset && !(prop_flags & JS_PROP_WRITABLE)) { /* not writable: cannot set the writable bit */ if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) @@ -9230,15 +9933,19 @@ spaces. */ if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) goto not_configurable; + } else { + /* update the reference */ + set_value(ctx, pr->u.var_ref->pvalue, + JS_DupValue(ctx, val)); } - /* update the reference */ - set_value(ctx, pr->u.var_ref->pvalue, - JS_DupValue(ctx, val)); } /* if writable is set to false, no longer a reference (for mapped arguments) */ if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { JSValue val1; + if (p->class_id == JS_CLASS_MODULE_NS) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "module namespace properties have writable = false"); + } if (js_shape_prepare_update(ctx, p, &prs)) return -1; val1 = JS_DupValue(ctx, *pr->u.var_ref->pvalue); @@ -9347,7 +10054,7 @@ } idx = __JS_AtomToUInt32(prop); /* if the typed array is detached, p->u.array.count = 0 */ - if (idx >= typed_array_get_length(ctx, p)) { + if (idx >= p->u.array.count) { typed_array_oob: return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); } @@ -9444,6 +10151,10 @@ JSAtom atom; int ret; atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); JS_FreeAtom(ctx, atom); return ret; @@ -9630,8 +10341,8 @@ return 0; } -static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, - BOOL throw_ref_error) +static inline JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, + BOOL throw_ref_error) { JSObject *p; JSShapeProperty *prs; @@ -9646,6 +10357,14 @@ return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return JS_DupValue(ctx, pr->u.value); } + + /* fast path */ + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + if (likely((prs->flags & JS_PROP_TMASK) == 0)) + return JS_DupValue(ctx, pr->u.value); + } return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, ctx->global_obj, throw_ref_error); } @@ -9687,37 +10406,16 @@ return 0; } -/* use for strict variable access: test if the variable exists */ -static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) -{ - JSObject *p; - JSShapeProperty *prs; - int ret; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property1(p, prop); - if (prs) { - ret = TRUE; - } else { - ret = JS_HasProperty(ctx, ctx->global_obj, prop); - if (ret < 0) - return -1; - } - return ret; -} - /* flag = 0: normal variable write flag = 1: initialize lexical variable - flag = 2: normal variable write, strict check was done before */ -static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, - int flag) +static inline int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, + int flag) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; - int flags; + int ret; /* no exotic behavior is possible in global_var_obj */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); @@ -9738,10 +10436,53 @@ set_value(ctx, &pr->u.value, val); return 0; } - flags = JS_PROP_THROW_STRICT; - if (is_strict_mode(ctx)) - flags |= JS_PROP_NO_ADD; - return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags); + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast path */ + set_value(ctx, &pr->u.value, val); + return 0; + } + } + /* slow path */ + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) { + JS_FreeValue(ctx, val); + return -1; + } + if (ret == 0 && is_strict_mode(ctx)) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorNotDefined(ctx, prop); + return -1; + } + return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, ctx->global_obj, + JS_PROP_THROW_STRICT); +} + +/* return -1, FALSE or TRUE */ +static int JS_DeleteGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int ret; + + /* 9.1.1.4.7 DeleteBinding ( N ) */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) + return FALSE; /* lexical variables cannot be deleted */ + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + return JS_DeleteProperty(ctx, ctx->global_obj, prop, 0); + } else { + return TRUE; + } } /* return -1, FALSE or TRUE. return FALSE if not configurable or @@ -9842,29 +10583,10 @@ return (p->class_id == JS_CLASS_ERROR); } -/* used to avoid catching interrupt exceptions */ -BOOL JS_IsUncatchableError(JSContext *ctx, JSValueConst val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return FALSE; - p = JS_VALUE_GET_OBJ(val); - return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; -} - -void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag) +/* must be called after JS_Throw() */ +void JS_SetUncatchableException(JSContext *ctx, BOOL flag) { - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(val); - if (p->class_id == JS_CLASS_ERROR) - p->is_uncatchable_error = flag; -} - -void JS_ResetUncatchableError(JSContext *ctx) -{ - JS_SetUncatchableError(ctx, ctx->rt->current_exception, FALSE); + ctx->rt->current_exception_is_uncatchable = flag; } void JS_SetOpaque(JSValue obj, void *opaque) @@ -9897,21 +10619,18 @@ return p; } -void *JS_GetOpaque_Nocheck(JSValueConst obj) +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) { JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + *class_id = 0; return NULL; + } p = JS_VALUE_GET_OBJ(obj); + *class_id = p->class_id; return p->u.opaque; } -#define HINT_STRING 0 -#define HINT_NUMBER 1 -#define HINT_NONE 2 -/* don't try Symbol.toPrimitive */ -#define HINT_FORCE_ORDINARY (1 << 4) - static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) { int i; @@ -10027,25 +10746,33 @@ JS_FreeValue(ctx, val); return ret; } -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: + case JS_TAG_STRING_ROPE: { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - BOOL ret; - ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; + BOOL ret = JS_VALUE_GET_STRING_ROPE(val)->len != 0; JS_FreeValue(ctx, val); return ret; } - case JS_TAG_BIG_DECIMAL: + case JS_TAG_SHORT_BIG_INT: + return JS_VALUE_GET_SHORT_BIG_INT(val) != 0; + case JS_TAG_BIG_INT: { - JSBigDecimal *p = JS_VALUE_GET_PTR(val); + JSBigInt *p = JS_VALUE_GET_PTR(val); BOOL ret; - ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; + int i; + + /* fail safe: we assume it is not necessarily + normalized. Beginning from the MSB ensures that the + test is fast. */ + ret = FALSE; + for(i = p->len - 1; i >= 0; i--) { + if (p->tabi != 0) { + ret = TRUE; + break; + } + } JS_FreeValue(ctx, val); return ret; } -#endif case JS_TAG_OBJECT: { JSObject *p = JS_VALUE_GET_OBJ(val); @@ -10105,53 +10832,1482 @@ return 36; } -/* XXX: remove */ -static double js_strtod(const char *p, int radix, BOOL is_float) + + +#define JS_BIGINT_MAX_SIZE ((1024 * 1024) / JS_LIMB_BITS) /* in limbs */ + +/* it is currently assumed that JS_SHORT_BIG_INT_BITS = JS_LIMB_BITS */ +#if JS_SHORT_BIG_INT_BITS == 32 +#define JS_SHORT_BIG_INT_MIN INT32_MIN +#define JS_SHORT_BIG_INT_MAX INT32_MAX +#elif JS_SHORT_BIG_INT_BITS == 64 +#define JS_SHORT_BIG_INT_MIN INT64_MIN +#define JS_SHORT_BIG_INT_MAX INT64_MAX +#else +#error unsupported +#endif + +#define ADDC(res, carry_out, op1, op2, carry_in) \ +do { \ + js_limb_t __v, __a, __k, __k1; \ + __v = (op1); \ + __a = __v + (op2); \ + __k1 = __a < __v; \ + __k = (carry_in); \ + __a = __a + __k; \ + carry_out = (__a < __k) | __k1; \ + res = __a; \ +} while (0) + +#if JS_LIMB_BITS == 32 +/* a != 0 */ +static inline js_limb_t js_limb_clz(js_limb_t a) { - double d; - int c; + return clz32(a); +} +#else +static inline js_limb_t js_limb_clz(js_limb_t a) +{ + return clz64(a); +} +#endif - if (!is_float || radix != 10) { - uint64_t n_max, n; - int int_exp, is_neg; - - is_neg = 0; - if (*p == '-') { - is_neg = 1; - p++; +/* handle a = 0 too */ +static inline js_limb_t js_limb_safe_clz(js_limb_t a) +{ + if (a == 0) + return JS_LIMB_BITS; + else + return js_limb_clz(a); +} + +static js_limb_t mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + js_limb_t n, js_limb_t carry) +{ + int i; + for(i = 0;i < n; i++) { + ADDC(resi, carry, op1i, op2i, carry); + } + return carry; +} + +static js_limb_t mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + int n, js_limb_t carry) +{ + int i; + js_limb_t k, a, v, k1; + + k = carry; + for(i=0;i<n;i++) { + v = op1i; + a = v - op2i; + k1 = a > v; + v = a - k; + k = (v > a) | k1; + resi = v; + } + return k; +} + +/* compute 0 - op2. carry = 0 or 1. */ +static js_limb_t mp_neg(js_limb_t *res, const js_limb_t *op2, int n) +{ + int i; + js_limb_t v, carry; + + carry = 1; + for(i=0;i<n;i++) { + v = ~op2i + carry; + carry = v < carry; + resi = v; + } + return carry; +} + +/* tabr = taba * b + l. Return the high carry */ +static js_limb_t mp_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t l) +{ + js_limb_t i; + js_dlimb_t t; + + for(i = 0; i < n; i++) { + t = (js_dlimb_t)tabai * (js_dlimb_t)b + l; + tabri = t; + l = t >> JS_LIMB_BITS; + } + return l; +} + +static js_limb_t mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | tabai; + tabri = a1 / b; + r = a1 % b; + } + return r; +} + +/* tabr += taba * b, return the high word. */ +static js_limb_t mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b) +{ + js_limb_t i, l; + js_dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = (js_dlimb_t)tabai * (js_dlimb_t)b + l + tabri; + tabri = t; + l = t >> JS_LIMB_BITS; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +static void mp_mul_basecase(js_limb_t *result, + const js_limb_t *op1, js_limb_t op1_size, + const js_limb_t *op2, js_limb_t op2_size) +{ + int i; + js_limb_t r; + + resultop1_size = mp_mul1(result, op1, op1_size, op20, 0); + for(i=1;i<op2_size;i++) { + r = mp_add_mul1(result + i, op1, op1_size, op2i); + resulti + op1_size = r; + } +} + +/* tabr -= taba * b. Return the value to substract to the high + word. */ +static js_limb_t mp_sub_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b) +{ + js_limb_t i, l; + js_dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = tabri - (js_dlimb_t)tabai * (js_dlimb_t)b - l; + tabri = t; + l = -(t >> JS_LIMB_BITS); + } + return l; +} + +/* WARNING: d must be >= 2^(JS_LIMB_BITS-1) */ +static inline js_limb_t udiv1norm_init(js_limb_t d) +{ + js_limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((js_dlimb_t)a1 << JS_LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^JS_LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline js_limb_t udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, + js_limb_t d, js_limb_t d_inv) +{ + js_limb_t n1m, n_adj, q, r, ah; + js_dlimb_t a; + n1m = ((js_slimb_t)a0 >> (JS_LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (js_dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> JS_LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is\ + between 0 and d - 1 */ + a = ((js_dlimb_t)a1 << JS_LIMB_BITS) | a0; + a = a - (js_dlimb_t)q * d - d; + ah = a >> JS_LIMB_BITS; + q += 1 + ah; + r = (js_limb_t)a + (ah & d); + *pr = r; + return q; +} + +#define UDIV1NORM_THRESHOLD 3 + +/* b must be >= 1 << (JS_LIMB_BITS - 1) */ +static js_limb_t mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + + if (n >= UDIV1NORM_THRESHOLD) { + js_limb_t b_inv; + b_inv = udiv1norm_init(b); + for(i = n - 1; i >= 0; i--) { + tabri = udiv1norm(&r, r, tabai, b, b_inv); + } + } else { + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | tabai; + tabri = a1 / b; + r = a1 % b; + } + } + return r; +} + +/* base case division: divides taba0..na-1 by tabb0..nb-1. tabbnb + - 1 must be >= 1 << (JS_LIMB_BITS - 1). na - nb must be >= 0. 'taba' + is modified and contains the remainder (nb limbs). tabq0..na-nb + contains the quotient with tabqna - nb <= 1. */ +static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, + const js_limb_t *tabb, js_limb_t nb) +{ + js_limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; + int i, j; + + b1 = tabbnb - 1; + if (nb == 1) { + taba0 = mp_div1norm(tabq, taba, na, b1, 0); + return; + } + n = na - nb; + + if (n >= UDIV1NORM_THRESHOLD) + b1_inv = udiv1norm_init(b1); + else + b1_inv = 0; + + /* first iteration: the quotient is only 0 or 1 */ + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taban + j != tabbj) { + if (taban + j < tabbj) + q = 0; + break; } + } + tabqn = q; + if (q) { + mp_sub(taba + n, taba + n, tabb, nb, 0); + } - /* skip leading zeros */ - while (*p == '0') - p++; - n = 0; - if (radix == 10) - n_max = ((uint64_t)-1 - 9) / 10; /* most common case */ + for(i = n - 1; i >= 0; i--) { + if (unlikely(tabai + nb >= b1)) { + q = -1; + } else if (b1_inv) { + q = udiv1norm(&dummy_r, tabai + nb, tabai + nb - 1, b1, b1_inv); + } else { + js_dlimb_t al; + al = ((js_dlimb_t)tabai + nb << JS_LIMB_BITS) | tabai + nb - 1; + q = al / b1; + r = al % b1; + } + r = mp_sub_mul1(taba + i, tabb, nb, q); + + v = tabai + nb; + a = v - r; + c = (a > v); + tabai + nb = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = mp_add(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++tabai + nb == 0) { + break; + } + } + } + } + tabqi = q; + } +} + +/* 1 <= shift <= JS_LIMB_BITS - 1 */ +static js_limb_t mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, + int shift) +{ + int i; + js_limb_t l, v; + l = 0; + for(i = 0; i < n; i++) { + v = tabai; + tabri = (v << shift) | l; + l = v >> (JS_LIMB_BITS - shift); + } + return l; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static js_limb_t mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, + int shift, js_limb_t high) +{ + int i; + js_limb_t l, a; + + l = high; + for(i = n - 1; i >= 0; i--) { + a = tabi; + tab_ri = (a >> shift) | (l << (JS_LIMB_BITS - shift)); + l = a; + } + return l & (((js_limb_t)1 << shift) - 1); +} + +static JSBigInt *js_bigint_new(JSContext *ctx, int len) +{ + JSBigInt *r; + if (len > JS_BIGINT_MAX_SIZE) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); + if (!r) + return NULL; + r->header.ref_count = 1; + r->len = len; + return r; +} + +static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) +{ + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + r->header.ref_count = 0; /* fail safe */ + r->len = 1; + r->tab0 = a; + return r; +} + +static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) +{ +#if JS_LIMB_BITS == 64 + return js_bigint_set_si(buf, a); +#else + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + r->header.ref_count = 0; /* fail safe */ + if (a >= INT32_MIN && a <= INT32_MAX) { + r->len = 1; + r->tab0 = a; + } else { + r->len = 2; + r->tab0 = a; + r->tab1 = a >> JS_LIMB_BITS; + } + return r; +#endif +} + +/* val must be a short big int */ +static JSBigInt *js_bigint_set_short(JSBigIntBuf *buf, JSValueConst val) +{ + return js_bigint_set_si(buf, JS_VALUE_GET_SHORT_BIG_INT(val)); +} + +static __maybe_unused void js_bigint_dump1(JSContext *ctx, const char *str, + const js_limb_t *tab, int len) +{ + int i; + printf("%s: ", str); + for(i = len - 1; i >= 0; i--) { +#if JS_LIMB_BITS == 32 + printf(" %08x", tabi); +#else + printf(" %016" PRIx64, tabi); +#endif + } + printf("\n"); +} + +static __maybe_unused void js_bigint_dump(JSContext *ctx, const char *str, + const JSBigInt *p) +{ + js_bigint_dump1(ctx, str, p->tab, p->len); +} + +static JSBigInt *js_bigint_new_si(JSContext *ctx, js_slimb_t a) +{ + JSBigInt *r; + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab0 = a; + return r; +} + +static JSBigInt *js_bigint_new_si64(JSContext *ctx, int64_t a) +{ +#if JS_LIMB_BITS == 64 + return js_bigint_new_si(ctx, a); +#else + if (a >= INT32_MIN && a <= INT32_MAX) { + return js_bigint_new_si(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab0 = a; + r->tab1 = a >> 32; + return r; + } +#endif +} + +static JSBigInt *js_bigint_new_ui64(JSContext *ctx, uint64_t a) +{ + if (a <= INT64_MAX) { + return js_bigint_new_si64(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, (65 + JS_LIMB_BITS - 1) / JS_LIMB_BITS); + if (!r) + return NULL; +#if JS_LIMB_BITS == 64 + r->tab0 = a; + r->tab1 = 0; +#else + r->tab0 = a; + r->tab1 = a >> 32; + r->tab2 = 0; +#endif + return r; + } +} + +static JSBigInt *js_bigint_new_di(JSContext *ctx, js_sdlimb_t a) +{ + JSBigInt *r; + if (a == (js_slimb_t)a) { + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab0 = a; + } else { + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab0 = a; + r->tab1 = a >> JS_LIMB_BITS; + } + return r; +} + +/* Remove redundant high order limbs. Warning: 'a' may be + reallocated. Can never fail. +*/ +static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) +{ + js_limb_t v; + + assert(a->header.ref_count == 1); + while (l > 1) { + v = a->tabl - 1; + if ((v != 0 && v != -1) || + (v & 1) != (a->tabl - 2 >> (JS_LIMB_BITS - 1))) { + break; + } + l--; + } + if (l != a->len) { + JSBigInt *a1; + /* realloc to reduce the size */ + a->len = l; + a1 = js_realloc(ctx, a, sizeof(JSBigInt) + l * sizeof(js_limb_t)); + if (a1) + a = a1; + } + return a; +} + +static JSBigInt *js_bigint_normalize(JSContext *ctx, JSBigInt *a) +{ + return js_bigint_normalize1(ctx, a, a->len); +} + +/* return 0 or 1 depending on the sign */ +static inline int js_bigint_sign(const JSBigInt *a) +{ + return a->taba->len - 1 >> (JS_LIMB_BITS - 1); +} + +static js_slimb_t js_bigint_get_si_sat(const JSBigInt *a) +{ + if (a->len == 1) { + return a->tab0; + } else { +#if JS_LIMB_BITS == 32 + if (js_bigint_sign(a)) + return INT32_MIN; + else + return INT32_MAX; +#else + if (js_bigint_sign(a)) + return INT64_MIN; + else + return INT64_MAX; +#endif + } +} + +/* add the op1 limb */ +static JSBigInt *js_bigint_extend(JSContext *ctx, JSBigInt *r, + js_limb_t op1) +{ + int n2 = r->len; + if ((op1 != 0 && op1 != -1) || + (op1 & 1) != r->tabn2 - 1 >> (JS_LIMB_BITS - 1)) { + JSBigInt *r1; + r1 = js_realloc(ctx, r, + sizeof(JSBigInt) + (n2 + 1) * sizeof(js_limb_t)); + if (!r1) { + js_free(ctx, r); + return NULL; + } + r = r1; + r->len = n2 + 1; + r->tabn2 = op1; + } else { + /* otherwise still need to normalize the result */ + r = js_bigint_normalize(ctx, r); + } + return r; +} + +/* return NULL in case of error. Compute a + b (b_neg = 0) or a - b + (b_neg = 1) */ +/* XXX: optimize */ +static JSBigInt *js_bigint_add(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, int b_neg) +{ + JSBigInt *r; + int n1, n2, i; + js_limb_t carry, op1, op2, a_sign, b_sign; + + n2 = max_int(a->len, b->len); + n1 = min_int(a->len, b->len); + r = js_bigint_new(ctx, n2); + if (!r) + return NULL; + /* XXX: optimize */ + /* common part */ + carry = b_neg; + for(i = 0; i < n1; i++) { + op1 = a->tabi; + op2 = b->tabi ^ (-b_neg); + ADDC(r->tabi, carry, op1, op2, carry); + } + a_sign = -js_bigint_sign(a); + b_sign = (-js_bigint_sign(b)) ^ (-b_neg); + /* part with sign extension of one operand */ + if (a->len > b->len) { + for(i = n1; i < n2; i++) { + op1 = a->tabi; + ADDC(r->tabi, carry, op1, b_sign, carry); + } + } else if (a->len < b->len) { + for(i = n1; i < n2; i++) { + op2 = b->tabi ^ (-b_neg); + ADDC(r->tabi, carry, a_sign, op2, carry); + } + } + + /* part with sign extension for both operands. Extend the result + if necessary */ + return js_bigint_extend(ctx, r, a_sign + b_sign + carry); +} + +/* XXX: optimize */ +static JSBigInt *js_bigint_neg(JSContext *ctx, const JSBigInt *a) +{ + JSBigIntBuf buf; + JSBigInt *b; + b = js_bigint_set_si(&buf, 0); + return js_bigint_add(ctx, b, a, 1); +} + +static JSBigInt *js_bigint_mul(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + JSBigInt *r; + + r = js_bigint_new(ctx, a->len + b->len); + if (!r) + return NULL; + mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); + /* correct the result if negative operands (no overflow is + possible) */ + if (js_bigint_sign(a)) + mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); + if (js_bigint_sign(b)) + mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); + return js_bigint_normalize(ctx, r); +} + +/* return the division or the remainder. 'b' must be != 0. return NULL + in case of exception (division by zero or memory error) */ +static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, BOOL is_rem) +{ + JSBigInt *r, *q; + js_limb_t *tabb, h; + int na, nb, a_sign, b_sign, shift; + + if (b->len == 1 && b->tab0 == 0) { + JS_ThrowRangeError(ctx, "BigInt division by zero"); + return NULL; + } + + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + na = a->len; + nb = b->len; + + r = js_bigint_new(ctx, na + 2); + if (!r) + return NULL; + if (a_sign) { + mp_neg(r->tab, a->tab, na); + } else { + memcpy(r->tab, a->tab, na * sizeof(a->tab0)); + } + /* normalize */ + while (na > 1 && r->tabna - 1 == 0) + na--; + + tabb = js_malloc(ctx, nb * sizeof(tabb0)); + if (!tabb) { + js_free(ctx, r); + return NULL; + } + if (b_sign) { + mp_neg(tabb, b->tab, nb); + } else { + memcpy(tabb, b->tab, nb * sizeof(tabb0)); + } + /* normalize */ + while (nb > 1 && tabbnb - 1 == 0) + nb--; + + /* trivial case if 'a' is small */ + if (na < nb) { + js_free(ctx, r); + js_free(ctx, tabb); + if (is_rem) { + /* r = a */ + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab0)); + return r; + } else { + /* q = 0 */ + return js_bigint_new_si(ctx, 0); + } + } + + /* normalize 'b' */ + shift = js_limb_clz(tabbnb - 1); + if (shift != 0) { + mp_shl(tabb, tabb, nb, shift); + h = mp_shl(r->tab, r->tab, na, shift); + if (h != 0) + r->tabna++ = h; + } + + q = js_bigint_new(ctx, na - nb + 2); /* one more limb for the sign */ + if (!q) { + js_free(ctx, r); + js_free(ctx, tabb); + return NULL; + } + + // js_bigint_dump1(ctx, "a", r->tab, na); + // js_bigint_dump1(ctx, "b", tabb, nb); + mp_divnorm(q->tab, r->tab, na, tabb, nb); + js_free(ctx, tabb); + + if (is_rem) { + js_free(ctx, q); + if (shift != 0) + mp_shr(r->tab, r->tab, nb, shift, 0); + r->tabnb++ = 0; + if (a_sign) + mp_neg(r->tab, r->tab, nb); + r = js_bigint_normalize1(ctx, r, nb); + return r; + } else { + js_free(ctx, r); + q->tabna - nb + 1 = 0; + if (a_sign ^ b_sign) { + mp_neg(q->tab, q->tab, q->len); + } + q = js_bigint_normalize(ctx, q); + return q; + } +} + +/* and, or, xor */ +static JSBigInt *js_bigint_logic(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, OPCodeEnum op) +{ + JSBigInt *r; + js_limb_t b_sign; + int a_len, b_len, i; + + if (a->len < b->len) { + const JSBigInt *tmp; + tmp = a; + a = b; + b = tmp; + } + /* a_len >= b_len */ + a_len = a->len; + b_len = b->len; + b_sign = -js_bigint_sign(b); + + r = js_bigint_new(ctx, a_len); + if (!r) + return NULL; + switch(op) { + case OP_or: + for(i = 0; i < b_len; i++) { + r->tabi = a->tabi | b->tabi; + } + for(i = b_len; i < a_len; i++) { + r->tabi = a->tabi | b_sign; + } + break; + case OP_and: + for(i = 0; i < b_len; i++) { + r->tabi = a->tabi & b->tabi; + } + for(i = b_len; i < a_len; i++) { + r->tabi = a->tabi & b_sign; + } + break; + case OP_xor: + for(i = 0; i < b_len; i++) { + r->tabi = a->tabi ^ b->tabi; + } + for(i = b_len; i < a_len; i++) { + r->tabi = a->tabi ^ b_sign; + } + break; + default: + abort(); + } + return js_bigint_normalize(ctx, r); +} + +static JSBigInt *js_bigint_not(JSContext *ctx, const JSBigInt *a) +{ + JSBigInt *r; + int i; + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + for(i = 0; i < a->len; i++) { + r->tabi = ~a->tabi; + } + /* no normalization is needed */ + return r; +} + +static JSBigInt *js_bigint_shl(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift; + JSBigInt *r; + js_limb_t l; + + if (a->len == 1 && a->tab0 == 0) + return js_bigint_new_si(ctx, 0); /* zero case */ + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + r = js_bigint_new(ctx, a->len + d); + if (!r) + return NULL; + for(i = 0; i < d; i++) + r->tabi = 0; + if (shift == 0) { + for(i = 0; i < a->len; i++) { + r->tabi + d = a->tabi; + } + } else { + l = mp_shl(r->tab + d, a->tab, a->len, shift); + if (js_bigint_sign(a)) + l |= (js_limb_t)(-1) << shift; + r = js_bigint_extend(ctx, r, l); + } + return r; +} + +static JSBigInt *js_bigint_shr(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift, a_sign, n1; + JSBigInt *r; + + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + a_sign = js_bigint_sign(a); + if (d >= a->len) + return js_bigint_new_si(ctx, -a_sign); + n1 = a->len - d; + r = js_bigint_new(ctx, n1); + if (!r) + return NULL; + if (shift == 0) { + for(i = 0; i < n1; i++) { + r->tabi = a->tabi + d; + } + /* no normalization is needed */ + } else { + mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); + r = js_bigint_normalize(ctx, r); + } + return r; +} + +static JSBigInt *js_bigint_pow(JSContext *ctx, const JSBigInt *a, JSBigInt *b) +{ + uint32_t e; + int n_bits, i; + JSBigInt *r, *r1; + + /* b must be >= 0 */ + if (js_bigint_sign(b)) { + JS_ThrowRangeError(ctx, "BigInt negative exponent"); + return NULL; + } + if (b->len == 1 && b->tab0 == 0) { + /* a^0 = 1 */ + return js_bigint_new_si(ctx, 1); + } else if (a->len == 1) { + js_limb_t v; + BOOL is_neg; + + v = a->tab0; + if (v <= 1) + return js_bigint_new_si(ctx, v); + else if (v == -1) + return js_bigint_new_si(ctx, 1 - 2 * (b->tab0 & 1)); + is_neg = (js_slimb_t)v < 0; + if (is_neg) + v = -v; + if ((v & (v - 1)) == 0) { + uint64_t e1; + int n; + /* v = 2^n */ + n = JS_LIMB_BITS - 1 - js_limb_clz(v); + if (b->len > 1) + goto overflow; + if (b->tab0 > INT32_MAX) + goto overflow; + e = b->tab0; + e1 = (uint64_t)e * n; + if (e1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) + goto overflow; + e = e1; + if (is_neg) + is_neg = b->tab0 & 1; + r = js_bigint_new(ctx, + (e + JS_LIMB_BITS + 1 - is_neg) / JS_LIMB_BITS); + if (!r) + return NULL; + memset(r->tab, 0, sizeof(r->tab0) * r->len); + r->tabe / JS_LIMB_BITS = + (js_limb_t)(1 - 2 * is_neg) << (e % JS_LIMB_BITS); + return r; + } + } + if (b->len > 1) + goto overflow; + if (b->tab0 > INT32_MAX) + goto overflow; + e = b->tab0; + n_bits = 32 - clz32(e); + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab0)); + for(i = n_bits - 2; i >= 0; i--) { + r1 = js_bigint_mul(ctx, r, r); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + if ((e >> i) & 1) { + r1 = js_bigint_mul(ctx, r, a); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + } + } + return r; + overflow: + JS_ThrowRangeError(ctx, "BigInt is too large"); + return NULL; +} + +/* return (mant, exp) so that abs(a) ~ mant*2^(exp - (limb_bits - + 1). a must be != 0. */ +static uint64_t js_bigint_get_mant_exp(JSContext *ctx, + int *pexp, const JSBigInt *a) +{ + js_limb_t t4 - JS_LIMB_BITS / 32, carry, v, low_bits; + int n1, n2, sgn, shift, i, j, e; + uint64_t a1, a0; + + n2 = 4 - JS_LIMB_BITS / 32; + n1 = a->len - n2; + sgn = js_bigint_sign(a); + + /* low_bits != 0 if there are a non zero low bit in abs(a) */ + low_bits = 0; + carry = sgn; + for(i = 0; i < n1; i++) { + v = (a->tabi ^ (-sgn)) + carry; + carry = v < carry; + low_bits |= v; + } + /* get the n2 high limbs of abs(a) */ + for(j = 0; j < n2; j++) { + i = j + n1; + if (i < 0) { + v = 0; + } else { + v = (a->tabi ^ (-sgn)) + carry; + carry = v < carry; + } + tj = v; + } + +#if JS_LIMB_BITS == 32 + a1 = ((uint64_t)t2 << 32) | t1; + a0 = (uint64_t)t0 << 32; +#else + a1 = t1; + a0 = t0; +#endif + a0 |= (low_bits != 0); + /* normalize */ + if (a1 == 0) { + /* JS_LIMB_BITS = 64 bit only */ + shift = 64; + a1 = a0; + a0 = 0; + } else { + shift = clz64(a1); + if (shift != 0) { + a1 = (a1 << shift) | (a0 >> (64 - shift)); + a0 <<= shift; + } + } + a1 |= (a0 != 0); /* keep the bits for the final rounding */ + /* compute the exponent */ + e = a->len * JS_LIMB_BITS - shift - 1; + *pexp = e; + return a1; +} + +/* shift left with round to nearest, ties to even. n >= 1 */ +static uint64_t shr_rndn(uint64_t a, int n) +{ + uint64_t addend = ((a >> n) & 1) + ((1 << (n - 1)) - 1); + return (a + addend) >> n; +} + +/* convert to float64 with round to nearest, ties to even. Return + +/-infinity if too large. */ +static double js_bigint_to_float64(JSContext *ctx, const JSBigInt *a) +{ + int sgn, e; + uint64_t mant; + + if (a->len == 1) { + /* fast case, including zero */ + return (double)(js_slimb_t)a->tab0; + } + + sgn = js_bigint_sign(a); + mant = js_bigint_get_mant_exp(ctx, &e, a); + if (e > 1023) { + /* overflow: return infinity */ + mant = 0; + e = 1024; + } else { + mant = (mant >> 1) | (mant & 1); /* avoid overflow in rounding */ + mant = shr_rndn(mant, 10); + /* rounding can cause an overflow */ + if (mant >= ((uint64_t)1 << 53)) { + mant >>= 1; + e++; + } + mant &= (((uint64_t)1 << 52) - 1); + } + return uint64_as_float64(((uint64_t)sgn << 63) | + ((uint64_t)(e + 1023) << 52) | + mant); +} + +/* return (1, NULL) if not an integer, (2, NULL) if NaN or Infinity, + (0, n) if an integer, (0, NULL) in case of memory error */ +static JSBigInt *js_bigint_from_float64(JSContext *ctx, int *pres, double a1) +{ + uint64_t a = float64_as_uint64(a1); + int sgn, e, shift; + uint64_t mant; + JSBigIntBuf buf; + JSBigInt *r; + + sgn = a >> 63; + e = (a >> 52) & ((1 << 11) - 1); + mant = a & (((uint64_t)1 << 52) - 1); + if (e == 2047) { + /* NaN, Infinity */ + *pres = 2; + return NULL; + } + if (e == 0 && mant == 0) { + /* zero */ + *pres = 0; + return js_bigint_new_si(ctx, 0); + } + e -= 1023; + /* 0 < a < 1 : not an integer */ + if (e < 0) + goto not_an_integer; + mant |= (uint64_t)1 << 52; + if (e < 52) { + shift = 52 - e; + /* check that there is no fractional part */ + if (mant & (((uint64_t)1 << shift) - 1)) { + not_an_integer: + *pres = 1; + return NULL; + } + mant >>= shift; + e = 0; + } else { + e -= 52; + } + if (sgn) + mant = -mant; + /* the integer is mant*2^e */ + r = js_bigint_set_si64(&buf, (int64_t)mant); + *pres = 0; + return js_bigint_shl(ctx, r, e); +} + +/* return -1, 0, 1 or (2) (unordered) */ +static int js_bigint_float64_cmp(JSContext *ctx, const JSBigInt *a, + double b) +{ + int b_sign, a_sign, e, f; + uint64_t mant, b1, a_mant; + + b1 = float64_as_uint64(b); + b_sign = b1 >> 63; + e = (b1 >> 52) & ((1 << 11) - 1); + mant = b1 & (((uint64_t)1 << 52) - 1); + a_sign = js_bigint_sign(a); + if (e == 2047) { + if (mant != 0) { + /* NaN */ + return 2; + } else { + /* +/- infinity */ + return 2 * b_sign - 1; + } + } else if (e == 0 && mant == 0) { + /* b = +/-0 */ + if (a->len == 1 && a->tab0 == 0) + return 0; else - n_max = ((uint64_t)-1 - (radix - 1)) / radix; - /* XXX: could be more precise */ - int_exp = 0; - while (*p != '\0') { - c = to_digit((uint8_t)*p); - if (c >= radix) + return 1 - 2 * a_sign; + } else if (a->len == 1 && a->tab0 == 0) { + /* a = 0, b != 0 */ + return 2 * b_sign - 1; + } else if (a_sign != b_sign) { + return 1 - 2 * a_sign; + } else { + e -= 1023; + /* Note: handling denormals is not necessary because we + compare to integers hence f >= 0 */ + /* compute f so that 2^f <= abs(a) < 2^(f+1) */ + a_mant = js_bigint_get_mant_exp(ctx, &f, a); + if (f != e) { + if (f < e) + return -1; + else + return 1; + } else { + mant = (mant | ((uint64_t)1 << 52)) << 11; /* align to a_mant */ + if (a_mant < mant) + return 2 * a_sign - 1; + else if (a_mant > mant) + return 1 - 2 * a_sign; + else + return 0; + } + } +} + +/* return -1, 0 or 1 */ +static int js_bigint_cmp(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + int a_sign, b_sign, res, i; + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + if (a_sign != b_sign) { + res = 1 - 2 * a_sign; + } else { + /* we assume the numbers are normalized */ + if (a->len != b->len) { + if (a->len < b->len) + res = 2 * a_sign - 1; + else + res = 1 - 2 * a_sign; + } else { + res = 0; + for(i = a->len -1; i >= 0; i--) { + if (a->tabi != b->tabi) { + if (a->tabi < b->tabi) + res = -1; + else + res = 1; + break; + } + } + } + } + return res; +} + +/* contains 10^i */ +static const js_limb_t js_pow_decJS_LIMB_DIGITS + 1 = { + 1U, + 10U, + 100U, + 1000U, + 10000U, + 100000U, + 1000000U, + 10000000U, + 100000000U, + 1000000000U, +#if JS_LIMB_BITS == 64 + 10000000000U, + 100000000000U, + 1000000000000U, + 10000000000000U, + 100000000000000U, + 1000000000000000U, + 10000000000000000U, + 100000000000000000U, + 1000000000000000000U, + 10000000000000000000U, +#endif +}; + +/* syntax: -digits in base radix. Return NULL if memory error. radix + = 10, 2, 8 or 16. */ +static JSBigInt *js_bigint_from_string(JSContext *ctx, + const char *str, int radix) +{ + const char *p = str; + size_t n_digits1; + int is_neg, n_digits, n_limbs, len, log2_radix, n_bits, i; + JSBigInt *r; + js_limb_t v, c, h; + + is_neg = 0; + if (*p == '-') { + is_neg = 1; + p++; + } + while (*p == '0') + p++; + n_digits1 = strlen(p); + /* the real check for overflox is done js_bigint_new(). Here + we just avoid integer overflow */ + if (n_digits1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + n_digits = n_digits1; + log2_radix = 32 - clz32(radix - 1); /* ceil(log2(radix)) */ + /* compute the maximum number of limbs */ + if (radix == 10) { + n_bits = (n_digits * 27 + 7) / 8; /* >= ceil(n_digits * log2(10)) */ + } else { + n_bits = n_digits * log2_radix; + } + /* we add one extra bit for the sign */ + n_limbs = max_int(1, n_bits / JS_LIMB_BITS + 1); + r = js_bigint_new(ctx, n_limbs); + if (!r) + return NULL; + if (radix == 10) { + int digits_per_limb = JS_LIMB_DIGITS; + len = 1; + r->tab0 = 0; + for(;;) { + /* XXX: slow */ + v = 0; + for(i = 0; i < digits_per_limb; i++) { + c = to_digit(*p); + if (c >= radix) + break; + p++; + v = v * 10 + c; + } + if (i == 0) break; - if (n <= n_max) { - n = n * radix + c; + if (len == 1 && r->tab0 == 0) { + r->tab0 = v; } else { - int_exp++; + h = mp_mul1(r->tab, r->tab, len, js_pow_deci, v); + if (h != 0) { + r->tablen++ = h; + } } - p++; } - d = n; - if (int_exp != 0) { - d *= pow(radix, int_exp); + /* add one extra limb to have the correct sign*/ + if ((r->tablen - 1 >> (JS_LIMB_BITS - 1)) != 0) + r->tablen++ = 0; + r->len = len; + } else { + unsigned int bit_pos, shift, pos; + + /* power of two base: no multiplication is needed */ + r->len = n_limbs; + memset(r->tab, 0, sizeof(r->tab0) * n_limbs); + for(i = 0; i < n_digits; i++) { + c = to_digit(pn_digits - 1 - i); + assert(c < radix); + bit_pos = i * log2_radix; + shift = bit_pos & (JS_LIMB_BITS - 1); + pos = bit_pos / JS_LIMB_BITS; + r->tabpos |= c << shift; + /* if log2_radix does not divide JS_LIMB_BITS, needed an + additional op */ + if (shift + log2_radix > JS_LIMB_BITS) { + r->tabpos + 1 |= c >> (JS_LIMB_BITS - shift); + } + } + } + r = js_bigint_normalize(ctx, r); + /* XXX: could do it in place */ + if (is_neg) { + JSBigInt *r1; + r1 = js_bigint_neg(ctx, r); + js_free(ctx, r); + r = r1; + } + return r; +} + +/* 2 <= base <= 36 */ +static char const digits36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + +/* special version going backwards */ +/* XXX: use dtoa.c */ +static char *js_u64toa(char *q, int64_t n, unsigned int base) +{ + int digit; + if (base == 10) { + /* division by known base uses multiplication */ + do { + digit = (uint64_t)n % 10; + n = (uint64_t)n / 10; + *--q = '0' + digit; + } while (n != 0); + } else { + do { + digit = (uint64_t)n % base; + n = (uint64_t)n / base; + *--q = digitsdigit; + } while (n != 0); + } + return q; +} + +/* len >= 1. 2 <= radix <= 36 */ +static char *limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ + /* XXX: optimize */ + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % 10; + n = (js_limb_t)n / 10; + *--q = digit + '0'; + } + } else { + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % radix; + n = (js_limb_t)n / radix; + *--q = digitsdigit; + } + } + return q; +} + +#define JS_RADIX_MAX 36 + +static const uint8_t digits_per_limb_tableJS_RADIX_MAX - 1 = { +#if JS_LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const js_limb_t radix_base_tableJS_RADIX_MAX - 1 = { +#if JS_LIMB_BITS == 32 + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +#else + 0x0000000000000000, 0xa8b8b452291fe821, 0x0000000000000000, 0x6765c793fa10079d, + 0x41c21cb8e1000000, 0x3642798750226111, 0x8000000000000000, 0xa8b8b452291fe821, + 0x8ac7230489e80000, 0x4d28cb56c33fa539, 0x1eca170c00000000, 0x780c7372621bd74d, + 0x1e39a5057d810000, 0x5b27ac993df97701, 0x0000000000000000, 0x27b95e997e21d9f1, + 0x5da0e1e53c5c8000, 0xd2ae3299c1c4aedb, 0x16bcc41e90000000, 0x2d04b7fdd9c0ef49, + 0x5658597bcaa24000, 0xa0e2073737609371, 0x0c29e98000000000, 0x14adf4b7320334b9, + 0x226ed36478bfa000, 0x383d9170b85ff80b, 0x5a3c23e39c000000, 0x8e65137388122bcd, + 0xdd41bb36d259e000, 0x0aee5720ee830681, 0x1000000000000000, 0x172588ad4f5f0981, + 0x211e44f7d02c1000, 0x2ee56725f06e5c71, 0x41c21cb8e1000000, +#endif +}; + +static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + char buf66; + int len; + len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix); + return js_new_string8_len(ctx, buf, len); + } else { + JSBigInt *r, *tmp = NULL; + char *buf, *q, *buf_end; + int is_neg, n_bits, log2_radix, n_digits; + BOOL is_binary_radix; + JSValue res; + + assert(JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT); + r = JS_VALUE_GET_PTR(val); + if (r->len == 1 && r->tab0 == 0) { + /* '0' case */ + return js_new_string8_len(ctx, "0", 1); + } + is_binary_radix = ((radix & (radix - 1)) == 0); + is_neg = js_bigint_sign(r); + if (is_neg) { + tmp = js_bigint_neg(ctx, r); + if (!tmp) + return JS_EXCEPTION; + r = tmp; + } else if (!is_binary_radix) { + /* need to modify 'r' */ + tmp = js_bigint_new(ctx, r->len); + if (!tmp) + return JS_EXCEPTION; + memcpy(tmp->tab, r->tab, r->len * sizeof(r->tab0)); + r = tmp; + } + log2_radix = 31 - clz32(radix); /* floor(log2(radix)) */ + n_bits = r->len * JS_LIMB_BITS - js_limb_safe_clz(r->tabr->len - 1); + /* n_digits is exact only if radix is a power of + two. Otherwise it is >= the exact number of digits */ + n_digits = (n_bits + log2_radix - 1) / log2_radix; + /* XXX: could directly build the JSString */ + buf = js_malloc(ctx, n_digits + is_neg + 1); + if (!buf) { + js_free(ctx, tmp); + return JS_EXCEPTION; + } + q = buf + n_digits + is_neg + 1; + *--q = '\0'; + buf_end = q; + if (!is_binary_radix) { + int len; + js_limb_t radix_base, v; + radix_base = radix_base_tableradix - 2; + len = r->len; + for(;;) { + /* remove leading zero limbs */ + while (len > 1 && r->tablen - 1 == 0) + len--; + if (len == 1 && r->tab0 < radix_base) { + v = r->tab0; + if (v != 0) { + q = js_u64toa(q, v, radix); + } + break; + } else { + v = mp_div1(r->tab, r->tab, len, radix_base, 0); + q = limb_to_a(q, v, radix, digits_per_limb_tableradix - 2); + } + } + } else { + int i, shift; + unsigned int bit_pos, pos, c; + + /* radix is a power of two */ + for(i = 0; i < n_digits; i++) { + bit_pos = i * log2_radix; + pos = bit_pos / JS_LIMB_BITS; + shift = bit_pos % JS_LIMB_BITS; + c = r->tabpos >> shift; + if ((shift + log2_radix) > JS_LIMB_BITS && + (pos + 1) < r->len) { + c |= r->tabpos + 1 << (JS_LIMB_BITS - shift); + } + c &= (radix - 1); + *--q = digitsc; + } } if (is_neg) - d = -d; + *--q = '-'; + js_free(ctx, tmp); + res = js_new_string8_len(ctx, q, buf_end - q); + js_free(ctx, buf); + return res; + } +} + +/* if possible transform a BigInt to short big and free it, otherwise + return a normal bigint */ +static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p) +{ + JSValue res; + if (p->len == 1) { + res = __JS_NewShortBigInt(ctx, (js_slimb_t)p->tab0); + js_free(ctx, p); + return res; } else { - d = strtod(p, NULL); + return JS_MKPTR(JS_TAG_BIG_INT, p); } - return d; } #define ATOD_INT_ONLY (1 << 0) @@ -10167,91 +12323,15 @@ #define ATOD_TYPE_MASK (3 << 7) #define ATOD_TYPE_FLOAT64 (0 << 7) #define ATOD_TYPE_BIG_INT (1 << 7) -#define ATOD_TYPE_BIG_FLOAT (2 << 7) -#define ATOD_TYPE_BIG_DECIMAL (3 << 7) -/* assume bigint mode: floats are parsed as integers if no decimal - point nor exponent */ -#define ATOD_MODE_BIGINT (1 << 9) + /* accept -0x1 */ #define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) -#ifdef CONFIG_BIGNUM -static JSValue js_string_to_bigint(JSContext *ctx, const char *buf, - int radix, int flags, slimb_t *pexponent) -{ - bf_t a_s, *a = &a_s; - int ret; - JSValue val; - val = JS_NewBigInt(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigInt(val); - ret = bf_atof(a, buf, NULL, radix, BF_PREC_INF, BF_RNDZ); - if (ret & BF_ST_MEM_ERROR) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - val = JS_CompactBigInt1(ctx, val, (flags & ATOD_MODE_BIGINT) != 0); - return val; -} - -static JSValue js_string_to_bigfloat(JSContext *ctx, const char *buf, - int radix, int flags, slimb_t *pexponent) -{ - bf_t *a; - int ret; - JSValue val; - - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigFloat(val); - if (flags & ATOD_ACCEPT_SUFFIX) { - /* return the exponent to get infinite precision */ - ret = bf_atof2(a, pexponent, buf, NULL, radix, BF_PREC_INF, - BF_RNDZ | BF_ATOF_EXPONENT); - } else { - ret = bf_atof(a, buf, NULL, radix, ctx->fp_env.prec, - ctx->fp_env.flags); - } - if (ret & BF_ST_MEM_ERROR) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - return val; -} - -static JSValue js_string_to_bigdecimal(JSContext *ctx, const char *buf, - int radix, int flags, slimb_t *pexponent) -{ - bfdec_t *a; - int ret; - JSValue val; - - val = JS_NewBigDecimal(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigDecimal(val); - ret = bfdec_atof(a, buf, NULL, BF_PREC_INF, - BF_RNDZ | BF_ATOF_NO_NAN_INF); - if (ret & BF_ST_MEM_ERROR) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - return val; -} - -#endif - /* return an exception in case of memory error. Return JS_NAN if invalid syntax */ -#ifdef CONFIG_BIGNUM -static JSValue js_atof2(JSContext *ctx, const char *str, const char **pp, - int radix, int flags, slimb_t *pexponent) -#else +/* XXX: directly use js_atod() */ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, int radix, int flags) -#endif { const char *p, *p_start; int sep, is_neg; @@ -10261,6 +12341,7 @@ int i, j, len; BOOL buf_allocated = FALSE; JSValue val; + JSATODTempMem atod_mem; /* optional separator between digits */ sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; @@ -10315,30 +12396,17 @@ } else { no_radix_prefix: if (!(flags & ATOD_INT_ONLY) && - (atod_type == ATOD_TYPE_FLOAT64 || - atod_type == ATOD_TYPE_BIG_FLOAT) && + (atod_type == ATOD_TYPE_FLOAT64) && strstart(p, "Infinity", &p)) { -#ifdef CONFIG_BIGNUM - if (atod_type == ATOD_TYPE_BIG_FLOAT) { - bf_t *a; - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - goto done; - a = JS_GetBigFloat(val); - bf_set_inf(a, is_neg); - } else -#endif - { -#if defined(_MSC_VER) - double const zero = 0.0; - double d = 1.0 / zero; + +#if defined(_MSC_VER) && !defined(__clang__) + double d = INFINITY; #else - double d = 1.0 / 0.0; + double d = 1.0 / 0.0; #endif - if (is_neg) - d = -d; - val = JS_NewFloat64(ctx, d); - } + if (is_neg) + d = -d; + val = JS_NewFloat64(ctx, d); goto done; } } @@ -10401,39 +12469,18 @@ } bufj = '\0'; -#ifdef CONFIG_BIGNUM if (flags & ATOD_ACCEPT_SUFFIX) { if (*p == 'n') { p++; atod_type = ATOD_TYPE_BIG_INT; - } else if (*p == 'l') { - p++; - atod_type = ATOD_TYPE_BIG_FLOAT; - } else if (*p == 'm') { - p++; - atod_type = ATOD_TYPE_BIG_DECIMAL; } else { - if (flags & ATOD_MODE_BIGINT) { - if (!is_float) - atod_type = ATOD_TYPE_BIG_INT; - if (has_legacy_octal) - goto fail; - } else { - if (is_float && radix != 10) - goto fail; - } + if (is_float && radix != 10) + goto fail; } } else { if (atod_type == ATOD_TYPE_FLOAT64) { - if (flags & ATOD_MODE_BIGINT) { - if (!is_float) - atod_type = ATOD_TYPE_BIG_INT; - if (has_legacy_octal) - goto fail; - } else { - if (is_float && radix != 10) - goto fail; - } + if (is_float && radix != 10) + goto fail; } } @@ -10441,40 +12488,28 @@ case ATOD_TYPE_FLOAT64: { double d; - d = js_strtod(buf, radix, is_float); + d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, + &atod_mem); /* return int or float64 */ val = JS_NewFloat64(ctx, d); } break; case ATOD_TYPE_BIG_INT: - if (has_legacy_octal || is_float) - goto fail; - val = ctx->rt->bigint_ops.from_string(ctx, buf, radix, flags, NULL); - break; - case ATOD_TYPE_BIG_FLOAT: - if (has_legacy_octal) - goto fail; - val = ctx->rt->bigfloat_ops.from_string(ctx, buf, radix, flags, - pexponent); - break; - case ATOD_TYPE_BIG_DECIMAL: - if (radix != 10) - goto fail; - val = ctx->rt->bigdecimal_ops.from_string(ctx, buf, radix, flags, NULL); + { + JSBigInt *r; + if (has_legacy_octal || is_float) + goto fail; + r = js_bigint_from_string(ctx, buf, radix); + if (!r) { + val = JS_EXCEPTION; + goto done; + } + val = JS_CompactBigInt(ctx, r); + } break; default: abort(); } -#else - { - double d; - (void)has_legacy_octal; - if (is_float && radix != 10) - goto fail; - d = js_strtod(buf, radix, is_float); - val = JS_NewFloat64(ctx, d); - } -#endif done: if (buf_allocated) @@ -10490,14 +12525,6 @@ goto done; } -#ifdef CONFIG_BIGNUM -static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, - int radix, int flags) -{ - return js_atof2(ctx, str, pp, radix, flags, NULL); -} -#endif - typedef enum JSToNumberHintEnum { TON_FLAG_NUMBER, TON_FLAG_NUMERIC, @@ -10512,29 +12539,14 @@ redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_DECIMAL: - if (flag != TON_FLAG_NUMERIC) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert bigdecimal to number"); - } - ret = val; - break; case JS_TAG_BIG_INT: + case JS_TAG_SHORT_BIG_INT: if (flag != TON_FLAG_NUMERIC) { JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert bigint to number"); } ret = val; break; - case JS_TAG_BIG_FLOAT: - if (flag != TON_FLAG_NUMERIC) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert bigfloat to number"); - } - ret = val; - break; -#endif case JS_TAG_FLOAT64: case JS_TAG_INT: case JS_TAG_EXCEPTION: @@ -10553,6 +12565,7 @@ return JS_EXCEPTION; goto redo; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: { const char *str; const char *p; @@ -10613,10 +12626,8 @@ uint32_t tag; val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = JS_FLOAT64_NAN; - return -1; - } + if (JS_IsException(val)) + goto fail; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: @@ -10625,24 +12636,14 @@ case JS_TAG_FLOAT64: d = JS_VALUE_GET_FLOAT64(val); break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - /* XXX: there can be a double rounding issue with some - primitives (such as JS_ToUint8ClampFree()), but it is - not critical to fix it. */ - bf_get_float64(&p->num, &d, BF_RNDN); - JS_FreeValue(ctx, val); - } - break; -#endif default: abort(); } *pres = d; return 0; + fail: + *pres = JS_FLOAT64_NAN; + return -1; } static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) @@ -10698,34 +12699,6 @@ } } break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: - { - bf_t a_s, *a, r_s, *r = &r_s; - BOOL is_nan; - - a = JS_ToBigFloat(ctx, &a_s, val); - if (!bf_is_finite(a)) { - is_nan = bf_is_nan(a); - if (is_nan) - ret = JS_NewInt32(ctx, 0); - else - ret = JS_DupValue(ctx, val); - } else { - ret = JS_NewBigInt(ctx); - if (!JS_IsException(ret)) { - r = JS_GetBigInt(ret); - bf_set(r, a); - bf_rint(r, BF_RNDZ); - ret = JS_CompactBigInt(ctx, ret); - } - } - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, val); - } - break; -#endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) @@ -10768,15 +12741,6 @@ } } break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - bf_get_int32(&ret, &p->num, 0); - JS_FreeValue(ctx, val); - } - break; -#endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { @@ -10835,22 +12799,13 @@ } else { if (d < INT64_MIN) *pres = INT64_MIN; - else if (d > INT64_MAX) + else if (d >= 0x8000000000000000ULL /*0x1p63*/) /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ *pres = INT64_MAX; else *pres = (int64_t)d; } } return 0; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - bf_get_int64(pres, &p->num, 0); - JS_FreeValue(ctx, val); - } - return 0; -#endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { @@ -10922,15 +12877,6 @@ } } break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - bf_get_int64(&ret, &p->num, BF_GET_INT_MOD); - JS_FreeValue(ctx, val); - } - break; -#endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { @@ -10997,15 +12943,6 @@ } } break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - bf_get_int32(&ret, &p->num, BF_GET_INT_MOD); - JS_FreeValue(ctx, val); - } - break; -#endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { @@ -11041,9 +12978,6 @@ case JS_TAG_NULL: case JS_TAG_UNDEFINED: res = JS_VALUE_GET_INT(val); -#ifdef CONFIG_BIGNUM - int_clamp: -#endif res = max_int(0, min_int(255, res)); break; case JS_TAG_FLOAT64: @@ -11061,20 +12995,6 @@ } } break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - bf_t r_s, *r = &r_s; - bf_init(ctx->bf_ctx, r); - bf_set(r, &p->num); - bf_rint(r, BF_RNDN); - bf_get_int32(&res, r, 0); - bf_delete(r); - JS_FreeValue(ctx, val); - } - goto int_clamp; -#endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { @@ -11105,28 +13025,12 @@ len = v; } break; -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - bf_t a; - BOOL res; - bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD); - bf_init(ctx->bf_ctx, &a); - bf_set_ui(&a, len); - res = bf_cmp_eq(&a, &p->num); - bf_delete(&a); - JS_FreeValue(ctx, val); - if (!res) - goto fail; - } - break; -#endif default: if (JS_TAG_IS_FLOAT64(tag)) { double d; d = JS_VALUE_GET_FLOAT64(val); + if (!(d >= 0 && d <= UINT32_MAX)) + goto fail; len = (uint32_t)d; if (len != d) goto fail; @@ -11226,430 +13130,66 @@ u.d = JS_VALUE_GET_FLOAT64(val); return (u.u64 >> 63); } -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: + return (JS_VALUE_GET_SHORT_BIG_INT(val) < 0); case JS_TAG_BIG_INT: { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - /* Note: integer zeros are not necessarily positive */ - return p->num.sign && !bf_is_zero(&p->num); - } - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - return p->num.sign; - } - break; - case JS_TAG_BIG_DECIMAL: - { - JSBigDecimal *p = JS_VALUE_GET_PTR(val); - return p->num.sign; + JSBigInt *p = JS_VALUE_GET_PTR(val); + return js_bigint_sign(p); } - break; -#endif default: return FALSE; } } -#ifdef CONFIG_BIGNUM - -static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) -{ - JSValue ret; - bf_t a_s, *a; - char *str; - int saved_sign; - - a = JS_ToBigInt(ctx, &a_s, val); - if (!a) - return JS_EXCEPTION; - saved_sign = a->sign; - if (a->expn == BF_EXP_ZERO) - a->sign = 0; - str = bf_ftoa(NULL, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC | - BF_FTOA_JS_QUIRKS); - a->sign = saved_sign; - JS_FreeBigInt(ctx, a, &a_s); - if (!str) - return JS_ThrowOutOfMemory(ctx); - ret = JS_NewString(ctx, str); - bf_free(ctx->bf_ctx, str); - return ret; -} - static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) { return js_bigint_to_string1(ctx, val, 10); } -static JSValue js_ftoa(JSContext *ctx, JSValueConst val1, int radix, - limb_t prec, bf_flags_t flags) -{ - JSValue val, ret; - bf_t a_s, *a; - char *str; - int saved_sign; - - val = JS_ToNumeric(ctx, val1); - if (JS_IsException(val)) - return val; - a = JS_ToBigFloat(ctx, &a_s, val); - saved_sign = a->sign; - if (a->expn == BF_EXP_ZERO) - a->sign = 0; - flags |= BF_FTOA_JS_QUIRKS; - if ((flags & BF_FTOA_FORMAT_MASK) == BF_FTOA_FORMAT_FREE_MIN) { - /* Note: for floating point numbers with a radix which is not - a power of two, the current precision is used to compute - the number of digits. */ - if ((radix & (radix - 1)) != 0) { - bf_t r_s, *r = &r_s; - int prec, flags1; - /* must round first */ - if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { - prec = ctx->fp_env.prec; - flags1 = ctx->fp_env.flags & - (BF_FLAG_SUBNORMAL | (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT)); - } else { - prec = 53; - flags1 = bf_set_exp_bits(11) | BF_FLAG_SUBNORMAL; - } - bf_init(ctx->bf_ctx, r); - bf_set(r, a); - bf_round(r, prec, flags1 | BF_RNDN); - str = bf_ftoa(NULL, r, radix, prec, flags1 | flags); - bf_delete(r); - } else { - str = bf_ftoa(NULL, a, radix, BF_PREC_INF, flags); - } - } else { - str = bf_ftoa(NULL, a, radix, prec, flags); - } - a->sign = saved_sign; - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, val); - if (!str) - return JS_ThrowOutOfMemory(ctx); - ret = JS_NewString(ctx, str); - bf_free(ctx->bf_ctx, str); - return ret; -} - -static JSValue js_bigfloat_to_string(JSContext *ctx, JSValueConst val) -{ - return js_ftoa(ctx, val, 10, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN); -} - -static JSValue js_bigdecimal_to_string1(JSContext *ctx, JSValueConst val, - limb_t prec, int flags) -{ - JSValue ret; - bfdec_t *a; - char *str; - int saved_sign; - - a = JS_ToBigDecimal(ctx, val); - saved_sign = a->sign; - if (a->expn == BF_EXP_ZERO) - a->sign = 0; - str = bfdec_ftoa(NULL, a, prec, flags | BF_FTOA_JS_QUIRKS); - a->sign = saved_sign; - if (!str) - return JS_ThrowOutOfMemory(ctx); - ret = JS_NewString(ctx, str); - bf_free(ctx->bf_ctx, str); - return ret; -} - -static JSValue js_bigdecimal_to_string(JSContext *ctx, JSValueConst val) -{ - return js_bigdecimal_to_string1(ctx, val, 0, - BF_RNDZ | BF_FTOA_FORMAT_FREE); -} - -#endif /* CONFIG_BIGNUM */ - -/* 2 <= base <= 36 */ -static char *i64toa(char *buf_end, int64_t n, unsigned int base) -{ - char *q = buf_end; - int digit, is_neg; - - is_neg = 0; - if (n < 0) { - is_neg = 1; - n = -n; - } - *--q = '\0'; - do { - digit = (uint64_t)n % base; - n = (uint64_t)n / base; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - *--q = digit; - } while (n != 0); - if (is_neg) - *--q = '-'; - return q; -} - -/* buf1 contains the printf result */ -static void js_ecvt1(double d, int n_digits, int *decpt, int *sign, char *buf, - int rounding_mode, char *buf1, int buf1_size) -{ - if (rounding_mode != FE_TONEAREST) - fesetround(rounding_mode); - snprintf(buf1, buf1_size, "%+.*e", n_digits - 1, d); - if (rounding_mode != FE_TONEAREST) - fesetround(FE_TONEAREST); - *sign = (buf10 == '-'); - /* mantissa */ - buf0 = buf11; - if (n_digits > 1) - memcpy(buf + 1, buf1 + 3, n_digits - 1); - bufn_digits = '\0'; - /* exponent */ - *decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1; -} - -/* maximum buffer size for js_dtoa */ -#define JS_DTOA_BUF_SIZE 128 - -/* needed because ecvt usually limits the number of digits to - 17. Return the number of digits. */ -static int js_ecvt(double d, int n_digits, int *decpt, int *sign, char *buf, - BOOL is_fixed) -{ - int rounding_mode; - char buf_tmpJS_DTOA_BUF_SIZE; - - if (!is_fixed) { - unsigned int n_digits_min, n_digits_max; - /* find the minimum amount of digits (XXX: inefficient but simple) */ - n_digits_min = 1; - n_digits_max = 17; - while (n_digits_min < n_digits_max) { - n_digits = (n_digits_min + n_digits_max) / 2; - js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST, - buf_tmp, sizeof(buf_tmp)); - if (strtod(buf_tmp, NULL) == d) { - /* no need to keep the trailing zeros */ - while (n_digits >= 2 && bufn_digits - 1 == '0') - n_digits--; - n_digits_max = n_digits; - } else { - n_digits_min = n_digits + 1; - } - } - n_digits = n_digits_max; - rounding_mode = FE_TONEAREST; - } else { - rounding_mode = FE_TONEAREST; -#ifdef CONFIG_PRINTF_RNDN - { - char buf1JS_DTOA_BUF_SIZE, buf2JS_DTOA_BUF_SIZE; - int decpt1, sign1, decpt2, sign2; - /* The JS rounding is specified as round to nearest ties away - from zero (RNDNA), but in printf the "ties" case is not - specified (for example it is RNDN for glibc, RNDNA for - Windows), so we must round manually. */ - js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_TONEAREST, - buf_tmp, sizeof(buf_tmp)); - /* XXX: could use 2 digits to reduce the average running time */ - if (buf1n_digits == '5') { - js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_DOWNWARD, - buf_tmp, sizeof(buf_tmp)); - js_ecvt1(d, n_digits + 1, &decpt2, &sign2, buf2, FE_UPWARD, - buf_tmp, sizeof(buf_tmp)); - if (memcmp(buf1, buf2, n_digits + 1) == 0 && decpt1 == decpt2) { - /* exact result: round away from zero */ - if (sign1) - rounding_mode = FE_DOWNWARD; - else - rounding_mode = FE_UPWARD; - } - } - } -#endif /* CONFIG_PRINTF_RNDN */ - } - js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode, - buf_tmp, sizeof(buf_tmp)); - return n_digits; -} - -static int js_fcvt1(char *buf, int buf_size, double d, int n_digits, - int rounding_mode) -{ - int n; - if (rounding_mode != FE_TONEAREST) - fesetround(rounding_mode); - n = snprintf(buf, buf_size, "%.*f", n_digits, d); - if (rounding_mode != FE_TONEAREST) - fesetround(FE_TONEAREST); - assert(n < buf_size); - return n; -} - -static void js_fcvt(char *buf, int buf_size, double d, int n_digits) -{ - int rounding_mode; - rounding_mode = FE_TONEAREST; -#ifdef CONFIG_PRINTF_RNDN - { - int n1, n2; - char buf1JS_DTOA_BUF_SIZE; - char buf2JS_DTOA_BUF_SIZE; - - /* The JS rounding is specified as round to nearest ties away from - zero (RNDNA), but in printf the "ties" case is not specified - (for example it is RNDN for glibc, RNDNA for Windows), so we - must round manually. */ - n1 = js_fcvt1(buf1, sizeof(buf1), d, n_digits + 1, FE_TONEAREST); - rounding_mode = FE_TONEAREST; - /* XXX: could use 2 digits to reduce the average running time */ - if (buf1n1 - 1 == '5') { - n1 = js_fcvt1(buf1, sizeof(buf1), d, n_digits + 1, FE_DOWNWARD); - n2 = js_fcvt1(buf2, sizeof(buf2), d, n_digits + 1, FE_UPWARD); - if (n1 == n2 && memcmp(buf1, buf2, n1) == 0) { - /* exact result: round away from zero */ - if (buf10 == '-') - rounding_mode = FE_DOWNWARD; - else - rounding_mode = FE_UPWARD; - } - } - } -#endif /* CONFIG_PRINTF_RNDN */ - js_fcvt1(buf, buf_size, d, n_digits, rounding_mode); -} - -/* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */ -/* use as many digits as necessary */ -#define JS_DTOA_VAR_FORMAT (0 << 0) -/* use n_digits significant digits (1 <= n_digits <= 101) */ -#define JS_DTOA_FIXED_FORMAT (1 << 0) -/* force fractional format: -dd.dd with n_digits fractional digits */ -#define JS_DTOA_FRAC_FORMAT (2 << 0) -/* force exponential notation either in fixed or variable format */ -#define JS_DTOA_FORCE_EXP (1 << 2) - -/* XXX: slow and maybe not fully correct. Use libbf when it is fast enough. - XXX: radix != 10 is only supported for small integers -*/ -static void js_dtoa1(char *buf, double d, int radix, int n_digits, int flags) +static JSValue js_dtoa2(JSContext *ctx, + double d, int radix, int n_digits, int flags) { - char *q; + char static_buf128, *buf, *tmp_buf; + int len, len_max; + JSValue res; + JSDTOATempMem dtoa_mem; + len_max = js_dtoa_max_len(d, radix, n_digits, flags); - if (!isfinite(d)) { - if (isnan(d)) { - strcpy(buf, "NaN"); - } else { - q = buf; - if (d < 0) - *q++ = '-'; - strcpy(q, "Infinity"); - } - } else if (flags == JS_DTOA_VAR_FORMAT) { - int64_t i64; - char buf170, *ptr; - i64 = (int64_t)d; - if (d != i64 || i64 > MAX_SAFE_INTEGER || i64 < -MAX_SAFE_INTEGER) - goto generic_conv; - /* fast path for integers */ - ptr = i64toa(buf1 + sizeof(buf1), i64, radix); - strcpy(buf, ptr); - } else { - if (d == 0.0) - d = 0.0; /* convert -0 to 0 */ - if (flags == JS_DTOA_FRAC_FORMAT) { - js_fcvt(buf, JS_DTOA_BUF_SIZE, d, n_digits); - } else { - char buf1JS_DTOA_BUF_SIZE; - int sign, decpt, k, n, i, p, n_max; - BOOL is_fixed; - generic_conv: - is_fixed = ((flags & 3) == JS_DTOA_FIXED_FORMAT); - if (is_fixed) { - n_max = n_digits; - } else { - n_max = 21; - } - /* the number has k digits (k >= 1) */ - k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed); - n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */ - q = buf; - if (sign) - *q++ = '-'; - if (flags & JS_DTOA_FORCE_EXP) - goto force_exp; - if (n >= 1 && n <= n_max) { - if (k <= n) { - memcpy(q, buf1, k); - q += k; - for(i = 0; i < (n - k); i++) - *q++ = '0'; - *q = '\0'; - } else { - /* k > n */ - memcpy(q, buf1, n); - q += n; - *q++ = '.'; - for(i = 0; i < (k - n); i++) - *q++ = buf1n + i; - *q = '\0'; - } - } else if (n >= -5 && n <= 0) { - *q++ = '0'; - *q++ = '.'; - for(i = 0; i < -n; i++) - *q++ = '0'; - memcpy(q, buf1, k); - q += k; - *q = '\0'; - } else { - force_exp: - /* exponential notation */ - *q++ = buf10; - if (k > 1) { - *q++ = '.'; - for(i = 1; i < k; i++) - *q++ = buf1i; - } - *q++ = 'e'; - p = n - 1; - if (p >= 0) - *q++ = '+'; - sprintf(q, "%d", p); - } - } + /* longer buffer may be used if radix != 10 */ + if (len_max > sizeof(static_buf) - 1) { + tmp_buf = js_malloc(ctx, len_max + 1); + if (!tmp_buf) + return JS_EXCEPTION; + buf = tmp_buf; + } else { + tmp_buf = NULL; + buf = static_buf; } + len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); + res = js_new_string8_len(ctx, buf, len); + js_free(ctx, tmp_buf); + return res; } -static JSValue js_dtoa(JSContext *ctx, - double d, int radix, int n_digits, int flags) -{ - char bufJS_DTOA_BUF_SIZE; - js_dtoa1(buf, d, radix, n_digits, flags); - return JS_NewString(ctx, buf); -} - -JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey) +static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey) { uint32_t tag; - const char *str; char buf32; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_STRING: return JS_DupValue(ctx, val); + case JS_TAG_STRING_ROPE: + return js_linearize_string_rope(ctx, JS_DupValue(ctx, val)); case JS_TAG_INT: - snprintf(buf, sizeof(buf), "%d", JS_VALUE_GET_INT(val)); - str = buf; - goto new_string; + { + size_t len; + len = i32toa(buf, JS_VALUE_GET_INT(val)); + return js_new_string8_len(ctx, buf, len); + } + break; case JS_TAG_BOOL: return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? JS_ATOM_true : JS_ATOM_false); @@ -11671,8 +13211,7 @@ } break; case JS_TAG_FUNCTION_BYTECODE: - str = "function bytecode"; - goto new_string; + return js_new_string8(ctx, "function bytecode"); case JS_TAG_SYMBOL: if (is_ToPropertyKey) { return JS_DupValue(ctx, val); @@ -11680,20 +13219,13 @@ return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); } case JS_TAG_FLOAT64: - return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, - JS_DTOA_VAR_FORMAT); -#ifdef CONFIG_BIGNUM + return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, + JS_DTOA_FORMAT_FREE); + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: - return ctx->rt->bigint_ops.to_string(ctx, val); - case JS_TAG_BIG_FLOAT: - return ctx->rt->bigfloat_ops.to_string(ctx, val); - case JS_TAG_BIG_DECIMAL: - return ctx->rt->bigdecimal_ops.to_string(ctx, val); -#endif + return js_bigint_to_string(ctx, val); default: - str = "unsupported type"; - new_string: - return JS_NewString(ctx, str); + return js_new_string8(ctx, "unsupported type"); } } @@ -11730,27 +13262,75 @@ return JS_ToString(ctx, val); } -static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) +#define JS_PRINT_MAX_DEPTH 8 + +typedef struct { + JSRuntime *rt; + JSContext *ctx; /* may be NULL */ + JSPrintValueOptions options; + JSPrintValueWrite *write_func; + void *write_opaque; + int level; + JSObject *print_stackJS_PRINT_MAX_DEPTH; /* level values */ +} JSPrintValueState; + +static void js_print_value(JSPrintValueState *s, JSValueConst val); + +static void js_putc(JSPrintValueState *s, char c) { - JSValue val; - JSString *p; - int i; - uint32_t c; - StringBuffer b_s, *b = &b_s; - char buf16; + s->write_func(s->write_opaque, &c, 1); +} - val = JS_ToStringCheckObject(ctx, val1); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); +static void js_puts(JSPrintValueState *s, const char *str) +{ + s->write_func(s->write_opaque, str, strlen(str)); +} - if (string_buffer_init(ctx, b, p->len + 2)) - goto fail; +static +#if !defined(_MSC_VER) +__attribute__((format(printf, 2, 3))) +#endif +void js_printf(JSPrintValueState *s, const char *fmt, ...) +{ + va_list ap; + char buf256; - if (string_buffer_putc8(b, '\"')) - goto fail; - for(i = 0; i < p->len; ) { - c = string_getc(p, &i); + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + s->write_func(s->write_opaque, buf, strlen(buf)); +} + +static void js_print_float64(JSPrintValueState *s, double d) +{ + JSDTOATempMem dtoa_mem; + char buf32; + int len; + len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); + s->write_func(s->write_opaque, buf, len); +} + +static uint32_t js_string_get_length(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + return p->len; + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_PTR(val); + return r->len; + } else { + return 0; + } +} + +/* pretty print the first 'len' characters of 'p' */ +static void js_print_string1(JSPrintValueState *s, JSString *p, int len, int sep) +{ + uint8_t bufUTF8_CHAR_LEN_MAX; + int l, i, c, c1; + + for(i = 0; i < len; i++) { + c = string_get(p, i); switch(c) { case '\t': c = 't'; @@ -11767,190 +13347,533 @@ case '\f': c = 'f'; goto quote; - case '\"': case '\\': quote: - if (string_buffer_putc8(b, '\\')) - goto fail; - if (string_buffer_putc8(b, c)) - goto fail; + js_putc(s, '\\'); + js_putc(s, c); break; default: - if (c < 32 || (c >= 0xd800 && c < 0xe000)) { - snprintf(buf, sizeof(buf), "\\u%04x", c); - if (string_buffer_puts8(b, buf)) - goto fail; + if (c == sep) + goto quote; + if (c >= 32 && c <= 126) { + js_putc(s, c); + } else if (c < 32 || + (c >= 0x7f && c <= 0x9f)) { + escape: + js_printf(s, "\\u%04x", c); } else { - if (string_buffer_putc(b, c)) - goto fail; + if (is_hi_surrogate(c)) { + if ((i + 1) >= len) + goto escape; + c1 = string_get(p, i + 1); + if (!is_lo_surrogate(c1)) + goto escape; + i++; + c = from_surrogate(c, c1); + } else if (is_lo_surrogate(c)) { + goto escape; + } + l = unicode_to_utf8(buf, c); + s->write_func(s->write_opaque, (char *)buf, l); } break; } } - if (string_buffer_putc8(b, '\"')) - goto fail; - JS_FreeValue(ctx, val); - return string_buffer_end(b); - fail: - JS_FreeValue(ctx, val); - string_buffer_free(b); - return JS_EXCEPTION; } -static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +static void js_print_string_rec(JSPrintValueState *s, JSValueConst val, + int sep, uint32_t pos) { - printf("%14s %4s %4s %14s %10s %s\n", - "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + uint32_t len; + if (pos < s->options.max_string_length) { + len = min_uint32(p->len, s->options.max_string_length - pos); + js_print_string1(s, p, len, sep); + } + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_PTR(val); + js_print_string_rec(s, r->left, sep, pos); + js_print_string_rec(s, r->right, sep, pos + js_string_get_length(r->left)); + } else { + js_printf(s, "<invalid string tag %d>", (int)JS_VALUE_GET_TAG(val)); + } } -/* for debug only: dump an object without side effect */ -static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +static void js_print_string(JSPrintValueState *s, JSValueConst val) { - uint32_t i; - char atom_bufATOM_GET_STR_BUF_SIZE; - JSShape *sh; - JSShapeProperty *prs; - JSProperty *pr; - BOOL is_first = TRUE; + int sep; + if (s->options.raw_dump && JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + js_printf(s, "%d", p->header.ref_count); + sep = (p->header.ref_count == 1) ? '\"' : '\''; + } else { + sep = '\"'; + } + js_putc(s, sep); + js_print_string_rec(s, val, sep, 0); + js_putc(s, sep); + if (js_string_get_length(val) > s->options.max_string_length) { + uint32_t n = js_string_get_length(val) - s->options.max_string_length; + js_printf(s, "... %u more character%s", n, n > 1 ? "s" : ""); + } +} - /* XXX: should encode atoms with special characters */ - sh = p->shape; /* the shape can be NULL while freeing an object */ - printf("%14p %4d ", - (void *)p, - p->header.ref_count); - if (sh) { - printf("%3d%c %14p ", - sh->header.ref_count, - " *"sh->is_hashed, - (void *)sh->proto); +static void js_print_raw_string(JSPrintValueState *s, JSValueConst val) +{ + const char *cstr; + size_t len; + cstr = JS_ToCStringLen(s->ctx, &len, val); + if (cstr) { + s->write_func(s->write_opaque, cstr, len); + JS_FreeCString(s->ctx, cstr); + } +} + +static BOOL is_ascii_ident(const JSString *p) +{ + int i, c; + + if (p->len == 0) + return FALSE; + for(i = 0; i < p->len; i++) { + c = string_get(p, i); + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + return FALSE; + } + return TRUE; +} + +static void js_print_atom(JSPrintValueState *s, JSAtom atom) +{ + int i; + if (__JS_AtomIsTaggedInt(atom)) { + js_printf(s, "%u", __JS_AtomToUInt32(atom)); + } else if (atom == JS_ATOM_NULL) { + js_puts(s, "<null>"); } else { - printf("%3s %14s ", "-", "-"); + assert(atom < s->rt->atom_size); + JSString *p; + p = s->rt->atom_arrayatom; + if (is_ascii_ident(p)) { + for(i = 0; i < p->len; i++) { + js_putc(s, string_get(p, i)); + } + } else { + js_putc(s, '"'); + js_print_string1(s, p, p->len, '\"'); + js_putc(s, '"'); + } } - printf("%10s ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_arrayp->class_id.class_name)); - if (p->is_exotic && p->fast_array) { - printf(" "); - for(i = 0; i < p->u.array.count; i++) { - if (i != 0) - printf(", "); - switch (p->class_id) { - case JS_CLASS_ARRAY: - case JS_CLASS_ARGUMENTS: - JS_DumpValueShort(rt, p->u.array.u.valuesi); +} + +/* return 0 if invalid length */ +static uint32_t js_print_array_get_length(JSObject *p) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + + prs = find_own_property(&pr, p, JS_ATOM_length); + if (!prs) + return 0; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return 0; + val = pr->u.value; + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val); + case JS_TAG_FLOAT64: + return (uint32_t)JS_VALUE_GET_FLOAT64(val); + default: + return 0; + } +} + +static void js_print_comma(JSPrintValueState *s, int *pcomma_state) +{ + switch(*pcomma_state) { + case 0: + break; + case 1: + js_printf(s, ", "); + break; + case 2: + js_printf(s, " { "); + break; + } + *pcomma_state = 1; +} + +static void js_print_more_items(JSPrintValueState *s, int *pcomma_state, + uint32_t n) +{ + js_print_comma(s, pcomma_state); + js_printf(s, "... %u more item%s", n, n > 1 ? "s" : ""); +} + +/* similar to js_regexp_toString() but without side effect */ +static void js_print_regexp(JSPrintValueState *s, JSObject *p1) +{ + JSRegExp *re = &p1->u.regexp; + JSString *p; + int i, n, c, c2, bra, flags; + static const char regexp_flags = { 'g', 'i', 'm', 's', 'u', 'y', 'd', 'v' }; + + if (!re->pattern || !re->bytecode) { + /* the regexp fields are zeroed at init */ + js_puts(s, "uninitialized_regexp"); + return; + } + p = re->pattern; + js_putc(s, '/'); + if (p->len == 0) { + js_puts(s, "(?:)"); + } else { + bra = 0; + for (i = 0, n = p->len; i < n;) { + c2 = -1; + switch (c = string_get(p, i++)) { + case '\\': + if (i < n) + c2 = string_get(p, i++); + break; + case '': + bra = 0; + break; + case '': + if (!bra) { + if (i < n && string_get(p, i) == '') + c2 = string_get(p, i++); + bra = 1; + } + break; + case '\n': + c = '\\'; + c2 = 'n'; + break; + case '\r': + c = '\\'; + c2 = 'r'; + break; + case '/': + if (!bra) { + c = '\\'; + c2 = '/'; + } break; + } + js_putc(s, c); + if (c2 >= 0) + js_putc(s, c2); + } + } + js_putc(s, '/'); + + flags = lre_get_flags(re->bytecode->u.str8); + for(i = 0; i < countof(regexp_flags); i++) { + if ((flags >> i) & 1) { + js_putc(s, regexp_flagsi); + } + } +} + +/* similar to js_error_toString() but without side effect */ +static void js_print_error(JSPrintValueState *s, JSObject *p) +{ + const char *str; + size_t len; + + str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_name); + if (!str) { + js_puts(s, "Error"); + } else { + js_puts(s, str); + JS_FreeCString(s->ctx, str); + } + + str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_message); + if (str && str0 != '\0') { + js_puts(s, ": "); + js_puts(s, str); + } + JS_FreeCString(s->ctx, str); + + /* dump the stack if present */ + str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_stack); + if (str) { + js_putc(s, '\n'); + + /* XXX: should remove the last '\n' in stack as + v8. SpiderMonkey does not do it */ + len = strlen(str); + if (len > 0 && strlen - 1 == '\n') + len--; + s->write_func(s->write_opaque, str, len); + + JS_FreeCString(s->ctx, str); + } +} + +static void js_print_object(JSPrintValueState *s, JSObject *p) +{ + JSRuntime *rt = s->rt; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + int comma_state; + BOOL is_array; + uint32_t i; + + comma_state = 0; + is_array = FALSE; + if (p->class_id == JS_CLASS_ARRAY) { + is_array = TRUE; + js_printf(s, " "); + /* XXX: print array like properties even if not fast array */ + if (p->fast_array) { + uint32_t len, n, len1; + len = js_print_array_get_length(p); + + len1 = min_uint32(p->u.array.count, s->options.max_item_count); + for(i = 0; i < len1; i++) { + js_print_comma(s, &comma_state); + js_print_value(s, p->u.array.u.valuesi); + } + if (len1 < p->u.array.count) + js_print_more_items(s, &comma_state, p->u.array.count - len1); + if (p->u.array.count < len) { + n = len - p->u.array.count; + js_print_comma(s, &comma_state); + js_printf(s, "<%u empty item%s>", n, n > 1 ? "s" : ""); + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + uint32_t size = 1 << typed_array_size_log2(p->class_id); + uint32_t len1; + int64_t v; + + js_print_atom(s, rt->class_arrayp->class_id.class_name); + js_printf(s, "(%u) ", p->u.array.count); + + is_array = TRUE; + len1 = min_uint32(p->u.array.count, s->options.max_item_count); + for(i = 0; i < len1; i++) { + const uint8_t *ptr = p->u.array.u.uint8_ptr + i * size; + js_print_comma(s, &comma_state); + switch(p->class_id) { case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_INT8_ARRAY: case JS_CLASS_UINT8_ARRAY: + v = *ptr; + goto ta_int64; + case JS_CLASS_INT8_ARRAY: + v = *(int8_t *)ptr; + goto ta_int64; case JS_CLASS_INT16_ARRAY: + v = *(int16_t *)ptr; + goto ta_int64; case JS_CLASS_UINT16_ARRAY: + v = *(uint16_t *)ptr; + goto ta_int64; case JS_CLASS_INT32_ARRAY: + v = *(int32_t *)ptr; + goto ta_int64; case JS_CLASS_UINT32_ARRAY: -#ifdef CONFIG_BIGNUM + v = *(uint32_t *)ptr; + goto ta_int64; case JS_CLASS_BIG_INT64_ARRAY: + v = *(int64_t *)ptr; + ta_int64: + js_printf(s, "%" PRId64, v); + break; case JS_CLASS_BIG_UINT64_ARRAY: -#endif + js_printf(s, "%" PRIu64, *(uint64_t *)ptr); + break; + case JS_CLASS_FLOAT16_ARRAY: + js_print_float64(s, fromfp16(*(uint16_t *)ptr)); + break; case JS_CLASS_FLOAT32_ARRAY: + js_print_float64(s, *(float *)ptr); + break; case JS_CLASS_FLOAT64_ARRAY: - { - int size = 1 << typed_array_size_log2(p->class_id); - const uint8_t *b = p->u.array.u.uint8_ptr + i * size; - while (size-- > 0) - printf("%02X", *b++); - } + js_print_float64(s, *(double *)ptr); break; } } - printf(" "); + if (len1 < p->u.array.count) + js_print_more_items(s, &comma_state, p->u.array.count - len1); + } else if (p->class_id == JS_CLASS_BYTECODE_FUNCTION || + (rt->class_arrayp->class_id.call != NULL && + p->class_id != JS_CLASS_PROXY)) { + js_printf(s, "Function"); + /* XXX: allow dump without ctx */ + if (!s->options.raw_dump && s->ctx) { + const char *func_name_str; + js_putc(s, ' '); + func_name_str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_name); + if (!func_name_str || func_name_str0 == '\0') + js_puts(s, "(anonymous)"); + else + js_puts(s, func_name_str); + JS_FreeCString(s->ctx, func_name_str); + } + js_printf(s, ""); + comma_state = 2; + } else if (p->class_id == JS_CLASS_MAP || p->class_id == JS_CLASS_SET) { + JSMapState *ms = p->u.opaque; + struct list_head *el; + + if (!ms) + goto default_obj; + js_print_atom(s, rt->class_arrayp->class_id.class_name); + js_printf(s, "(%u) { ", ms->record_count); + i = 0; + list_for_each(el, &ms->records) { + JSMapRecord *mr = list_entry(el, JSMapRecord, link); + js_print_comma(s, &comma_state); + if (mr->empty) + continue; + js_print_value(s, mr->key); + if (p->class_id == JS_CLASS_MAP) { + js_printf(s, " => "); + js_print_value(s, mr->value); + } + i++; + if (i >= s->options.max_item_count) + break; + } + if (i < ms->record_count) + js_print_more_items(s, &comma_state, ms->record_count - i); + } else if (p->class_id == JS_CLASS_REGEXP && s->ctx) { + js_print_regexp(s, p); + comma_state = 2; + } else if (p->class_id == JS_CLASS_DATE && s->ctx) { + /* get_date_string() has no side effect */ + JSValue str = get_date_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL, 0x23); /* toISOString() */ + if (JS_IsException(str)) + goto default_obj; + js_print_raw_string(s, str); + JS_FreeValueRT(s->rt, str); + comma_state = 2; + } else if (p->class_id == JS_CLASS_ERROR && s->ctx) { + js_print_error(s, p); + comma_state = 2; + } else { + default_obj: + if (p->class_id != JS_CLASS_OBJECT) { + js_print_atom(s, rt->class_arrayp->class_id.class_name); + js_printf(s, " "); + } + js_printf(s, "{ "); } + sh = p->shape; /* the shape can be NULL while freeing an object */ if (sh) { - printf("{ "); + uint32_t j; + + j = 0; for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { if (prs->atom != JS_ATOM_NULL) { - pr = &p->propi; - if (!is_first) - printf(", "); - printf("%s: ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - printf("getset %p %p", (void *)pr->u.getset.getter, - (void *)pr->u.getset.setter); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - printf("varref %p", (void *)pr->u.var_ref); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - printf("autoinit %p %d %p", - (void *)js_autoinit_get_realm(pr), - js_autoinit_get_id(pr), - (void *)pr->u.init.opaque); - } else { - JS_DumpValueShort(rt, pr->u.value); + if (!(prs->flags & JS_PROP_ENUMERABLE) && + !s->options.show_hidden) { + continue; + } + if (j < s->options.max_item_count) { + pr = &p->propi; + js_print_comma(s, &comma_state); + js_print_atom(s, prs->atom); + js_printf(s, ": "); + + /* XXX: autoinit property */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (s->options.raw_dump) { + js_printf(s, "Getter %p Setter %p", + pr->u.getset.getter, pr->u.getset.setter); + } else { + if (pr->u.getset.getter && pr->u.getset.setter) { + js_printf(s, "Getter/Setter"); + } else if (pr->u.getset.setter) { + js_printf(s, "Setter"); + } else { + js_printf(s, "Getter"); + } + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (s->options.raw_dump) { + js_printf(s, "varref %p", (void *)pr->u.var_ref); + } else { + js_print_value(s, *pr->u.var_ref->pvalue); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + if (s->options.raw_dump) { + js_printf(s, "autoinit %p %d %p", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + /* XXX: could autoinit but need to restart + the iteration */ + js_printf(s, "autoinit"); + } + } else { + js_print_value(s, pr->u.value); + } } - is_first = FALSE; + j++; } } - printf(" }"); + if (j > s->options.max_item_count) + js_print_more_items(s, &comma_state, j - s->options.max_item_count); } - - if (js_class_has_bytecode(p->class_id)) { + if (s->options.raw_dump && js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b = p->u.func.function_bytecode; - JSVarRef **var_refs; if (b->closure_var_count) { + JSVarRef **var_refs; var_refs = p->u.func.var_refs; - printf(" Closure:"); + + js_print_comma(s, &comma_state); + js_printf(s, "Closure: "); for(i = 0; i < b->closure_var_count; i++) { - printf(" "); - JS_DumpValueShort(rt, var_refsi->value); - } - if (p->u.func.home_object) { - printf(" HomeObject: "); - JS_DumpValueShort(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + if (i != 0) + js_printf(s, ", "); + js_print_value(s, var_refsi->value); } + js_printf(s, " "); + } + if (p->u.func.home_object) { + js_print_comma(s, &comma_state); + js_printf(s, "HomeObject: "); + js_print_value(s, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); } } - printf("\n"); -} -static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) -{ - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - JS_DumpObject(rt, (JSObject *)p); - } else { - printf("%14p %4d ", - (void *)p, - p->ref_count); - switch(p->gc_obj_type) { - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - printf("function bytecode"); - break; - case JS_GC_OBJ_TYPE_SHAPE: - printf("shape"); - break; - case JS_GC_OBJ_TYPE_VAR_REF: - printf("var_ref"); - break; - case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: - printf("async_function"); - break; - case JS_GC_OBJ_TYPE_JS_CONTEXT: - printf("js_context"); - break; - default: - printf("unknown %d", p->gc_obj_type); - break; + if (!is_array) { + if (comma_state != 2) { + js_printf(s, " }"); } - printf("\n"); + } else { + js_printf(s, " "); } } -static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, - JSValueConst val) +static int js_print_stack_index(JSPrintValueState *s, JSObject *p) +{ + int i; + for(i = 0; i < s->level; i++) + if (s->print_stacki == p) + return i; + return -1; +} + +static void js_print_value(JSPrintValueState *s, JSValueConst val) { uint32_t tag = JS_VALUE_GET_NORM_TAG(val); const char *str; switch(tag) { case JS_TAG_INT: - printf("%d", JS_VALUE_GET_INT(val)); + js_printf(s, "%d", JS_VALUE_GET_INT(val)); break; case JS_TAG_BOOL: if (JS_VALUE_GET_BOOL(val)) @@ -11970,108 +13893,257 @@ case JS_TAG_UNDEFINED: str = "undefined"; print_str: - printf("%s", str); + js_puts(s, str); break; case JS_TAG_FLOAT64: - printf("%.14g", JS_VALUE_GET_FLOAT64(val)); + js_print_float64(s, JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_SHORT_BIG_INT: + js_printf(s, "%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); break; -#ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - char *str; - str = bf_ftoa(NULL, &p->num, 10, 0, - BF_RNDZ | BF_FTOA_FORMAT_FRAC); - printf("%sn", str); - bf_realloc(&rt->bf_ctx, str, 0); - } - break; - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - char *str; - str = bf_ftoa(NULL, &p->num, 16, BF_PREC_INF, - BF_RNDZ | BF_FTOA_FORMAT_FREE | BF_FTOA_ADD_PREFIX); - printf("%sl", str); - bf_free(&rt->bf_ctx, str); - } - break; - case JS_TAG_BIG_DECIMAL: - { - JSBigDecimal *p = JS_VALUE_GET_PTR(val); - char *str; - str = bfdec_ftoa(NULL, &p->num, BF_PREC_INF, - BF_RNDZ | BF_FTOA_FORMAT_FREE); - printf("%sm", str); - bf_free(&rt->bf_ctx, str); + if (!s->options.raw_dump && s->ctx) { + JSValue str = js_bigint_to_string(s->ctx, val); + if (JS_IsException(str)) + goto raw_bigint; + js_print_raw_string(s, str); + js_putc(s, 'n'); + JS_FreeValueRT(s->rt, str); + } else { + JSBigInt *p; + int sgn, i; + raw_bigint: + p = JS_VALUE_GET_PTR(val); + /* In order to avoid allocations we just dump the limbs */ + sgn = js_bigint_sign(p); + if (sgn) + js_printf(s, "BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); + js_printf(s, "0x"); + for(i = p->len - 1; i >= 0; i--) { + if (i != p->len - 1) + js_putc(s, '_'); +#if JS_LIMB_BITS == 32 + js_printf(s, "%08x", p->tabi); +#else + js_printf(s, "%016" PRIx64, p->tabi); +#endif + } + js_putc(s, 'n'); + if (sgn) + js_putc(s, ')'); } break; -#endif case JS_TAG_STRING: - { - JSString *p; - p = JS_VALUE_GET_STRING(val); - JS_DumpString(rt, p); + case JS_TAG_STRING_ROPE: + if (s->options.raw_dump && tag == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + js_printf(s, "rope len=%d depth=%d", r->len, r->depth); + } else { + js_print_string(s, val); } break; case JS_TAG_FUNCTION_BYTECODE: { JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); - char bufATOM_GET_STR_BUF_SIZE; - printf("bytecode %s", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); + js_puts(s, "bytecode "); + js_print_atom(s, b->func_name); + js_putc(s, ''); } break; case JS_TAG_OBJECT: { JSObject *p = JS_VALUE_GET_OBJ(val); - JSAtom atom = rt->class_arrayp->class_id.class_name; - char atom_bufATOM_GET_STR_BUF_SIZE; - printf("%s %p", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); + int idx; + idx = js_print_stack_index(s, p); + if (idx >= 0) { + js_printf(s, "circular %d", idx); + } else if (s->level < s->options.max_depth) { + s->print_stacks->level++ = p; + js_print_object(s, JS_VALUE_GET_OBJ(val)); + s->level--; + } else { + JSAtom atom = s->rt->class_arrayp->class_id.class_name; + js_putc(s, ''); + js_print_atom(s, atom); + if (s->options.raw_dump) { + js_printf(s, " %p", (void *)p); + } + js_putc(s, ''); + } } break; case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(val); - char atom_bufATOM_GET_STR_BUF_SIZE; - printf("Symbol(%s)", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); + js_puts(s, "Symbol("); + js_print_atom(s, js_get_atom_index(s->rt, p)); + js_putc(s, ')'); } break; case JS_TAG_MODULE: - printf("module"); + js_puts(s, "module"); break; default: - printf("unknown tag %d", tag); + js_printf(s, "unknown tag %d", tag); break; } } -static __maybe_unused void JS_DumpValue(JSContext *ctx, - JSValueConst val) +void JS_PrintValueSetDefaultOptions(JSPrintValueOptions *options) +{ + memset(options, 0, sizeof(*options)); + options->max_depth = 2; + options->max_string_length = 1000; + options->max_item_count = 100; +} + +static void JS_PrintValueInternal(JSRuntime *rt, JSContext *ctx, + JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options) { - JS_DumpValueShort(ctx->rt, val); + JSPrintValueState ss, *s = &ss; + if (options) + s->options = *options; + else + JS_PrintValueSetDefaultOptions(&s->options); + if (s->options.max_depth <= 0) + s->options.max_depth = JS_PRINT_MAX_DEPTH; + else + s->options.max_depth = min_int(s->options.max_depth, JS_PRINT_MAX_DEPTH); + if (s->options.max_string_length == 0) + s->options.max_string_length = UINT32_MAX; + if (s->options.max_item_count == 0) + s->options.max_item_count = UINT32_MAX; + s->rt = rt; + s->ctx = ctx; + s->write_func = write_func; + s->write_opaque = write_opaque; + s->level = 0; + js_print_value(s, val); +} + +void JS_PrintValueRT(JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options) +{ + JS_PrintValueInternal(rt, NULL, write_func, write_opaque, val, options); +} + +void JS_PrintValue(JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options) +{ + JS_PrintValueInternal(ctx->rt, ctx, write_func, write_opaque, val, options); +} + +static void js_dump_value_write(void *opaque, const char *buf, size_t len) +{ + FILE *fo = opaque; + fwrite(buf, 1, len, fo); +} + +static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) +{ + JSPrintValueState ss, *s = &ss; + memset(s, 0, sizeof(*s)); + s->rt = ctx->rt; + s->ctx = ctx; + s->write_func = js_dump_value_write; + s->write_opaque = stdout; + js_print_atom(s, atom); +} + +static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val) +{ + printf("%s=", str); + JS_PrintValue(ctx, js_dump_value_write, stdout, val, NULL); + printf("\n"); } -static __maybe_unused void JS_PrintValue(JSContext *ctx, - const char *str, - JSValueConst val) +static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val) { printf("%s=", str); - JS_DumpValueShort(ctx->rt, val); + JS_PrintValueRT(rt, js_dump_value_write, stdout, val, NULL); + printf("\n"); +} + +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +{ + printf("%14s %4s %4s %14s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); +} + +/* for debug only: dump an object without side effect */ +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +{ + JSShape *sh; + JSPrintValueOptions options; + + /* XXX: should encode atoms with special characters */ + sh = p->shape; /* the shape can be NULL while freeing an object */ + printf("%14p %4d ", + (void *)p, + p->header.ref_count); + if (sh) { + printf("%3d%c %14p ", + sh->header.ref_count, + " *"sh->is_hashed, + (void *)sh->proto); + } else { + printf("%3s %14s ", "-", "-"); + } + + JS_PrintValueSetDefaultOptions(&options); + options.max_depth = 1; + options.show_hidden = TRUE; + options.raw_dump = TRUE; + JS_PrintValueRT(rt, js_dump_value_write, stdout, JS_MKPTR(JS_TAG_OBJECT, p), &options); + printf("\n"); } +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) +{ + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + JS_DumpObject(rt, (JSObject *)p); + } else { + printf("%14p %4d ", + (void *)p, + p->ref_count); + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + printf("function bytecode"); + break; + case JS_GC_OBJ_TYPE_SHAPE: + printf("shape"); + break; + case JS_GC_OBJ_TYPE_VAR_REF: + printf("var_ref"); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + printf("async_function"); + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + printf("js_context"); + break; + case JS_GC_OBJ_TYPE_MODULE: + printf("module"); + break; + default: + printf("unknown %d", p->gc_obj_type); + break; + } + printf("\n"); + } +} + /* return -1 if exception (proxy case) or TRUE/FALSE */ +// TODO: should take flags to make proxy resolution and exceptions optional int JS_IsArray(JSContext *ctx, JSValueConst val) { - JSObject *p; + if (js_resolve_proxy(ctx, &val, TRUE)) + return -1; if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(val); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_isArray(ctx, val); - else - return p->class_id == JS_CLASS_ARRAY; + JSObject *p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ARRAY; } else { return FALSE; } @@ -12087,114 +14159,34 @@ } } -#ifdef CONFIG_BIGNUM - -JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) -{ - JSValue val; - bf_t *a; - val = JS_NewBigInt(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigInt(val); - if (bf_set_si(a, v)) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - return val; -} - JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) { - if (is_math_mode(ctx) && - v >= -MAX_SAFE_INTEGER && v <= MAX_SAFE_INTEGER) { - return JS_NewInt64(ctx, v); +#if JS_SHORT_BIG_INT_BITS == 64 + return __JS_NewShortBigInt(ctx, v); +#else + if (v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); } else { - return JS_NewBigInt64_1(ctx, v); + JSBigInt *p; + p = js_bigint_new_si64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); } +#endif } JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) { - JSValue val; - if (is_math_mode(ctx) && v <= MAX_SAFE_INTEGER) { - val = JS_NewInt64(ctx, v); + if (v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); } else { - bf_t *a; - val = JS_NewBigInt(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigInt(val); - if (bf_set_ui(a, v)) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - } - return val; -} - -/* if the returned bigfloat is allocated it is equal to - 'buf'. Otherwise it is a pointer to the bigfloat in 'val'. Return - NULL in case of error. */ -static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val) -{ - uint32_t tag; - bf_t *r; - JSBigFloat *p; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - r = buf; - bf_init(ctx->bf_ctx, r); - if (bf_set_si(r, JS_VALUE_GET_INT(val))) - goto fail; - break; - case JS_TAG_FLOAT64: - r = buf; - bf_init(ctx->bf_ctx, r); - if (bf_set_float64(r, JS_VALUE_GET_FLOAT64(val))) { - fail: - bf_delete(r); - return NULL; - } - break; - case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - p = JS_VALUE_GET_PTR(val); - r = &p->num; - break; - case JS_TAG_UNDEFINED: - default: - r = buf; - bf_init(ctx->bf_ctx, r); - bf_set_nan(r); - break; - } - return r; -} - -/* return NULL if invalid type */ -static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val) -{ - uint32_t tag; - JSBigDecimal *p; - bfdec_t *r; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_BIG_DECIMAL: - p = JS_VALUE_GET_PTR(val); - r = &p->num; - break; - default: - JS_ThrowTypeError(ctx, "bigdecimal expected"); - r = NULL; - break; + JSBigInt *p; + p = js_bigint_new_ui64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); } - return r; } /* return NaN if bad bigint literal */ @@ -12214,8 +14206,6 @@ val = JS_NewBigInt64(ctx, 0); } else { flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; - if (is_math_mode(ctx)) - flags |= ATOD_MODE_BIGINT; val = js_atof(ctx, p, &p, 0, flags); p += skip_spaces(p); if (!JS_IsException(val)) { @@ -12237,724 +14227,95 @@ return val; } -/* if the returned bigfloat is allocated it is equal to - 'buf'. Otherwise it is a pointer to the bigfloat in 'val'. */ -static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val) +/* JS Numbers are not allowed */ +static JSValue JS_ToBigIntFree(JSContext *ctx, JSValue val) { uint32_t tag; - bf_t *r; - JSBigFloat *p; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + break; case JS_TAG_INT: case JS_TAG_NULL: case JS_TAG_UNDEFINED: - if (!is_math_mode(ctx)) - goto fail; - /* fall tru */ - case JS_TAG_BOOL: - r = buf; - bf_init(ctx->bf_ctx, r); - bf_set_si(r, JS_VALUE_GET_INT(val)); - break; case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (!is_math_mode(ctx)) - goto fail; - if (!isfinite(d)) - goto fail; - r = buf; - bf_init(ctx->bf_ctx, r); - d = trunc(d); - bf_set_float64(r, d); - } - break; - case JS_TAG_BIG_INT: - p = JS_VALUE_GET_PTR(val); - r = &p->num; - break; - case JS_TAG_BIG_FLOAT: - if (!is_math_mode(ctx)) - goto fail; - p = JS_VALUE_GET_PTR(val); - if (!bf_is_finite(&p->num)) - goto fail; - r = buf; - bf_init(ctx->bf_ctx, r); - bf_set(r, &p->num); - bf_rint(r, BF_RNDZ); - JS_FreeValue(ctx, val); + goto fail; + case JS_TAG_BOOL: + val = __JS_NewShortBigInt(ctx, JS_VALUE_GET_INT(val)); break; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: val = JS_StringToBigIntErr(ctx, val); if (JS_IsException(val)) - return NULL; + return val; goto redo; case JS_TAG_OBJECT: val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); if (JS_IsException(val)) - return NULL; + return val; goto redo; default: fail: JS_FreeValue(ctx, val); - JS_ThrowTypeError(ctx, "cannot convert to bigint"); - return NULL; + return JS_ThrowTypeError(ctx, "cannot convert to bigint"); } - return r; -} - -static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val) -{ - return JS_ToBigIntFree(ctx, buf, JS_DupValue(ctx, val)); + return val; } -static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val) +static JSValue JS_ToBigInt(JSContext *ctx, JSValueConst val) { - if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) { - return val; - } else { - bf_t a_s, *a, *r; - int ret; - JSValue res; - - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) - return JS_EXCEPTION; - a = JS_ToBigIntFree(ctx, &a_s, val); - if (!a) { - JS_FreeValue(ctx, res); - return JS_EXCEPTION; - } - r = JS_GetBigInt(res); - ret = bf_set(r, a); - JS_FreeBigInt(ctx, a, &a_s); - if (ret) { - JS_FreeValue(ctx, res); - return JS_ThrowOutOfMemory(ctx); - } - return JS_CompactBigInt(ctx, res); - } + return JS_ToBigIntFree(ctx, JS_DupValue(ctx, val)); } -/* free the bf_t allocated by JS_ToBigInt */ -static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf) -{ - if (a == buf) { - bf_delete(a); - } else { - JSBigFloat *p = (JSBigFloat *)((uint8_t *)a - - offsetof(JSBigFloat, num)); - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_FLOAT, p)); - } -} -/* XXX: merge with JS_ToInt64Free with a specific flag */ static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) { - bf_t a_s, *a; + uint64_t res; - a = JS_ToBigIntFree(ctx, &a_s, val); - if (!a) { + val = JS_ToBigIntFree(ctx, val); + if (JS_IsException(val)) { *pres = 0; return -1; } - bf_get_int64(pres, a, BF_GET_INT_MOD); - JS_FreeBigInt(ctx, a, &a_s); - return 0; -} - -int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) -{ - return JS_ToBigInt64Free(ctx, pres, JS_DupValue(ctx, val)); -} - -static JSBigFloat *js_new_bf(JSContext *ctx) -{ - JSBigFloat *p; - p = js_malloc(ctx, sizeof(*p)); - if (!p) - return NULL; - p->header.ref_count = 1; - bf_init(ctx->bf_ctx, &p->num); - return p; -} - -static JSValue JS_NewBigFloat(JSContext *ctx) -{ - JSBigFloat *p; - p = js_malloc(ctx, sizeof(*p)); - if (!p) - return JS_EXCEPTION; - p->header.ref_count = 1; - bf_init(ctx->bf_ctx, &p->num); - return JS_MKPTR(JS_TAG_BIG_FLOAT, p); -} - -static JSValue JS_NewBigDecimal(JSContext *ctx) -{ - JSBigDecimal *p; - p = js_malloc(ctx, sizeof(*p)); - if (!p) - return JS_EXCEPTION; - p->header.ref_count = 1; - bfdec_init(ctx->bf_ctx, &p->num); - return JS_MKPTR(JS_TAG_BIG_DECIMAL, p); -} - -static JSValue JS_NewBigInt(JSContext *ctx) -{ - JSBigFloat *p; - p = js_malloc(ctx, sizeof(*p)); - if (!p) - return JS_EXCEPTION; - p->header.ref_count = 1; - bf_init(ctx->bf_ctx, &p->num); - return JS_MKPTR(JS_TAG_BIG_INT, p); -} - -static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val, - BOOL convert_to_safe_integer) -{ - int64_t v; - bf_t *a; - - if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT) - return val; /* fail safe */ - a = JS_GetBigInt(val); - if (convert_to_safe_integer && bf_get_int64(&v, a, 0) == 0 && - v >= -MAX_SAFE_INTEGER && v <= MAX_SAFE_INTEGER) { - JS_FreeValue(ctx, val); - return JS_NewInt64(ctx, v); - } else if (a->expn == BF_EXP_ZERO && a->sign) { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - assert(p->header.ref_count == 1); - a->sign = 0; - } - return val; -} - -/* Convert the big int to a safe integer if in math mode. normalize - the zero representation. Could also be used to convert the bigint - to a short bigint value. The reference count of the value must be - 1. Cannot fail */ -static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val) -{ - return JS_CompactBigInt1(ctx, val, is_math_mode(ctx)); -} - -/* must be kept in sync with JSOverloadableOperatorEnum */ -/* XXX: use atoms ? */ -static const char js_overloadable_operator_namesJS_OVOP_COUNT4 = { - "+", - "-", - "*", - "/", - "%", - "**", - "|", - "&", - "^", - "<<", - ">>", - ">>>", - "==", - "<", - "pos", - "neg", - "++", - "--", - "~", -}; - -static int get_ovop_from_opcode(OPCodeEnum op) -{ - switch(op) { - case OP_add: - return JS_OVOP_ADD; - case OP_sub: - return JS_OVOP_SUB; - case OP_mul: - return JS_OVOP_MUL; - case OP_div: - return JS_OVOP_DIV; - case OP_mod: - case OP_math_mod: - return JS_OVOP_MOD; - case OP_pow: - return JS_OVOP_POW; - case OP_or: - return JS_OVOP_OR; - case OP_and: - return JS_OVOP_AND; - case OP_xor: - return JS_OVOP_XOR; - case OP_shl: - return JS_OVOP_SHL; - case OP_sar: - return JS_OVOP_SAR; - case OP_shr: - return JS_OVOP_SHR; - case OP_eq: - case OP_neq: - return JS_OVOP_EQ; - case OP_lt: - case OP_lte: - case OP_gt: - case OP_gte: - return JS_OVOP_LESS; - case OP_plus: - return JS_OVOP_POS; - case OP_neg: - return JS_OVOP_NEG; - case OP_inc: - return JS_OVOP_INC; - case OP_dec: - return JS_OVOP_DEC; - default: - abort(); - } -} - -/* return NULL if not present */ -static JSObject *find_binary_op(JSBinaryOperatorDef *def, - uint32_t operator_index, - JSOverloadableOperatorEnum op) -{ - JSBinaryOperatorDefEntry *ent; - int i; - for(i = 0; i < def->count; i++) { - ent = &def->tabi; - if (ent->operator_index == operator_index) - return ent->opsop; - } - return NULL; -} - -/* return -1 if exception, 0 if no operator overloading, 1 if - overloaded operator called */ -static __exception int js_call_binary_op_fallback(JSContext *ctx, - JSValue *pret, - JSValueConst op1, - JSValueConst op2, - OPCodeEnum op, - BOOL is_numeric, - int hint) -{ - JSValue opset1_obj, opset2_obj, method, ret, new_op1, new_op2; - JSOperatorSetData *opset1, *opset2; - JSOverloadableOperatorEnum ovop; - JSObject *p; - JSValueConst args2; - - if (!ctx->allow_operator_overloading) - return 0; - - opset2_obj = JS_UNDEFINED; - opset1_obj = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorSet); - if (JS_IsException(opset1_obj)) - goto exception; - if (JS_IsUndefined(opset1_obj)) - return 0; - opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); - if (!opset1) - goto exception; - - opset2_obj = JS_GetProperty(ctx, op2, JS_ATOM_Symbol_operatorSet); - if (JS_IsException(opset2_obj)) - goto exception; - if (JS_IsUndefined(opset2_obj)) { - JS_FreeValue(ctx, opset1_obj); - return 0; - } - opset2 = JS_GetOpaque2(ctx, opset2_obj, JS_CLASS_OPERATOR_SET); - if (!opset2) - goto exception; - - if (opset1->is_primitive && opset2->is_primitive) { - JS_FreeValue(ctx, opset1_obj); - JS_FreeValue(ctx, opset2_obj); - return 0; - } - - ovop = get_ovop_from_opcode(op); - - if (opset1->operator_counter == opset2->operator_counter) { - p = opset1->self_opsovop; - } else if (opset1->operator_counter > opset2->operator_counter) { - p = find_binary_op(&opset1->left, opset2->operator_counter, ovop); - } else { - p = find_binary_op(&opset2->right, opset1->operator_counter, ovop); - } - if (!p) { - JS_ThrowTypeError(ctx, "operator %s: no function defined", - js_overloadable_operator_namesovop); - goto exception; - } - - if (opset1->is_primitive) { - if (is_numeric) { - new_op1 = JS_ToNumeric(ctx, op1); - } else { - new_op1 = JS_ToPrimitive(ctx, op1, hint); - } - if (JS_IsException(new_op1)) - goto exception; - } else { - new_op1 = JS_DupValue(ctx, op1); - } - - if (opset2->is_primitive) { - if (is_numeric) { - new_op2 = JS_ToNumeric(ctx, op2); - } else { - new_op2 = JS_ToPrimitive(ctx, op2, hint); - } - if (JS_IsException(new_op2)) { - JS_FreeValue(ctx, new_op1); - goto exception; - } - } else { - new_op2 = JS_DupValue(ctx, op2); - } - - /* XXX: could apply JS_ToPrimitive() if primitive type so that the - operator function does not get a value object */ - - method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - if (ovop == JS_OVOP_LESS && (op == OP_lte || op == OP_gt)) { - args0 = new_op2; - args1 = new_op1; - } else { - args0 = new_op1; - args1 = new_op2; - } - ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args); - JS_FreeValue(ctx, new_op1); - JS_FreeValue(ctx, new_op2); - if (JS_IsException(ret)) - goto exception; - if (ovop == JS_OVOP_EQ) { - BOOL res = JS_ToBoolFree(ctx, ret); - if (op == OP_neq) - res ^= 1; - ret = JS_NewBool(ctx, res); - } else if (ovop == JS_OVOP_LESS) { - if (JS_IsUndefined(ret)) { - ret = JS_FALSE; - } else { - BOOL res = JS_ToBoolFree(ctx, ret); - if (op == OP_lte || op == OP_gte) - res ^= 1; - ret = JS_NewBool(ctx, res); - } - } - JS_FreeValue(ctx, opset1_obj); - JS_FreeValue(ctx, opset2_obj); - *pret = ret; - return 1; - exception: - JS_FreeValue(ctx, opset1_obj); - JS_FreeValue(ctx, opset2_obj); - *pret = JS_UNDEFINED; - return -1; -} - -/* try to call the operation on the operatorSet field of 'obj'. Only - used for "/" and "**" on the BigInt prototype in math mode */ -static __exception int js_call_binary_op_simple(JSContext *ctx, - JSValue *pret, - JSValueConst obj, - JSValueConst op1, - JSValueConst op2, - OPCodeEnum op) -{ - JSValue opset1_obj, method, ret, new_op1, new_op2; - JSOperatorSetData *opset1; - JSOverloadableOperatorEnum ovop; - JSObject *p; - JSValueConst args2; - - opset1_obj = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_operatorSet); - if (JS_IsException(opset1_obj)) - goto exception; - if (JS_IsUndefined(opset1_obj)) - return 0; - opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); - if (!opset1) - goto exception; - ovop = get_ovop_from_opcode(op); - - p = opset1->self_opsovop; - if (!p) { - JS_FreeValue(ctx, opset1_obj); - return 0; - } - - new_op1 = JS_ToNumeric(ctx, op1); - if (JS_IsException(new_op1)) - goto exception; - new_op2 = JS_ToNumeric(ctx, op2); - if (JS_IsException(new_op2)) { - JS_FreeValue(ctx, new_op1); - goto exception; - } - - method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - args0 = new_op1; - args1 = new_op2; - ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args); - JS_FreeValue(ctx, new_op1); - JS_FreeValue(ctx, new_op2); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, opset1_obj); - *pret = ret; - return 1; - exception: - JS_FreeValue(ctx, opset1_obj); - *pret = JS_UNDEFINED; - return -1; -} - -/* return -1 if exception, 0 if no operator overloading, 1 if - overloaded operator called */ -static __exception int js_call_unary_op_fallback(JSContext *ctx, - JSValue *pret, - JSValueConst op1, - OPCodeEnum op) -{ - JSValue opset1_obj, method, ret; - JSOperatorSetData *opset1; - JSOverloadableOperatorEnum ovop; - JSObject *p; - - if (!ctx->allow_operator_overloading) - return 0; - - opset1_obj = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorSet); - if (JS_IsException(opset1_obj)) - goto exception; - if (JS_IsUndefined(opset1_obj)) - return 0; - opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); - if (!opset1) - goto exception; - if (opset1->is_primitive) { - JS_FreeValue(ctx, opset1_obj); - return 0; - } - - ovop = get_ovop_from_opcode(op); - - p = opset1->self_opsovop; - if (!p) { - JS_ThrowTypeError(ctx, "no overloaded operator %s", - js_overloadable_operator_namesovop); - goto exception; - } - method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - ret = JS_CallFree(ctx, method, JS_UNDEFINED, 1, &op1); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, opset1_obj); - *pret = ret; - return 1; - exception: - JS_FreeValue(ctx, opset1_obj); - *pret = JS_UNDEFINED; - return -1; -} - -static JSValue throw_bf_exception(JSContext *ctx, int status) -{ - const char *str; - if (status & BF_ST_MEM_ERROR) - return JS_ThrowOutOfMemory(ctx); - if (status & BF_ST_DIVIDE_ZERO) { - str = "division by zero"; - } else if (status & BF_ST_INVALID_OP) { - str = "invalid operation"; + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + res = JS_VALUE_GET_SHORT_BIG_INT(val); } else { - str = "integer overflow"; - } - return JS_ThrowRangeError(ctx, "%s", str); -} - -static int js_unary_arith_bigint(JSContext *ctx, - JSValue *pres, OPCodeEnum op, JSValue op1) -{ - bf_t a_s, *r, *a; - int ret, v; - JSValue res; - - if (op == OP_plus && !is_math_mode(ctx)) { - JS_ThrowTypeError(ctx, "bigint argument with unary +"); - JS_FreeValue(ctx, op1); - return -1; - } - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) { - JS_FreeValue(ctx, op1); - return -1; - } - r = JS_GetBigInt(res); - a = JS_ToBigInt(ctx, &a_s, op1); - ret = 0; - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); - break; - case OP_plus: - ret = bf_set(r, a); - break; - case OP_neg: - ret = bf_set(r, a); - bf_neg(r); - break; - case OP_not: - ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ); - bf_neg(r); - break; - default: - abort(); - } - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeValue(ctx, op1); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; + JSBigInt *p = JS_VALUE_GET_PTR(val); + /* return the value mod 2^64 */ + res = p->tab0; +#if JS_LIMB_BITS == 32 + if (p->len >= 2) + res |= (uint64_t)p->tab1 << 32; +#endif + JS_FreeValue(ctx, val); } - res = JS_CompactBigInt(ctx, res); *pres = res; return 0; } -static int js_unary_arith_bigfloat(JSContext *ctx, - JSValue *pres, OPCodeEnum op, JSValue op1) +int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) { - bf_t a_s, *r, *a; - int ret, v; - JSValue res; - - if (op == OP_plus && !is_math_mode(ctx)) { - JS_ThrowTypeError(ctx, "bigfloat argument with unary +"); - JS_FreeValue(ctx, op1); - return -1; - } - - res = JS_NewBigFloat(ctx); - if (JS_IsException(res)) { - JS_FreeValue(ctx, op1); - return -1; - } - r = JS_GetBigFloat(res); - a = JS_ToBigFloat(ctx, &a_s, op1); - ret = 0; - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - ret = bf_add_si(r, a, v, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case OP_plus: - ret = bf_set(r, a); - break; - case OP_neg: - ret = bf_set(r, a); - bf_neg(r); - break; - default: - abort(); - } - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, op1); - if (unlikely(ret & BF_ST_MEM_ERROR)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = res; - return 0; + return JS_ToBigInt64Free(ctx, pres, JS_DupValue(ctx, val)); } -static int js_unary_arith_bigdecimal(JSContext *ctx, - JSValue *pres, OPCodeEnum op, JSValue op1) -{ - bfdec_t *r, *a; - int ret, v; - JSValue res; - - if (op == OP_plus && !is_math_mode(ctx)) { - JS_ThrowTypeError(ctx, "bigdecimal argument with unary +"); - JS_FreeValue(ctx, op1); - return -1; - } - - res = JS_NewBigDecimal(ctx); - if (JS_IsException(res)) { - JS_FreeValue(ctx, op1); - return -1; - } - r = JS_GetBigDecimal(res); - a = JS_ToBigDecimal(ctx, op1); - ret = 0; - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - ret = bfdec_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); - break; - case OP_plus: - ret = bfdec_set(r, a); - break; - case OP_neg: - ret = bfdec_set(r, a); - bfdec_neg(r); - break; - default: - abort(); - } - JS_FreeValue(ctx, op1); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = res; - return 0; -} static no_inline __exception int js_unary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { - JSValue op1, val; - int v, ret; + JSValue op1; + int v; uint32_t tag; + JSBigIntBuf buf1; + JSBigInt *p1; op1 = sp-1; /* fast path for float64 */ if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) goto handle_float64; - if (JS_IsObject(op1)) { - ret = js_call_unary_op_fallback(ctx, &val, op1, op); - if (ret < 0) - return -1; - if (ret) { - JS_FreeValue(ctx, op1); - sp-1 = val; - return 0; - } - } - op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) goto exception; @@ -12986,25 +14347,76 @@ sp-1 = JS_NewInt64(ctx, v64); } break; - case JS_TAG_BIG_INT: - handle_bigint: - if (ctx->rt->bigint_ops.unary_arith(ctx, sp - 1, op, op1)) - goto exception; - break; - case JS_TAG_BIG_FLOAT: - if (ctx->rt->bigfloat_ops.unary_arith(ctx, sp - 1, op, op1)) - goto exception; + case JS_TAG_SHORT_BIG_INT: + { + int64_t v; + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + goto exception; + case OP_inc: + if (v == JS_SHORT_BIG_INT_MAX) + goto bigint_slow_case; + sp-1 = __JS_NewShortBigInt(ctx, v + 1); + break; + case OP_dec: + if (v == JS_SHORT_BIG_INT_MIN) + goto bigint_slow_case; + sp-1 = __JS_NewShortBigInt(ctx, v - 1); + break; + case OP_neg: + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (v == JS_SHORT_BIG_INT_MIN) { + bigint_slow_case: + p1 = js_bigint_set_short(&buf1, op1); + goto bigint_slow_case1; + } + sp-1 = __JS_NewShortBigInt(ctx, -v); + break; + default: + abort(); + } + } break; - case JS_TAG_BIG_DECIMAL: - if (ctx->rt->bigdecimal_ops.unary_arith(ctx, sp - 1, op, op1)) - goto exception; + case JS_TAG_BIG_INT: + { + JSBigInt *r; + p1 = JS_VALUE_GET_PTR(op1); + bigint_slow_case1: + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + JS_FreeValue(ctx, op1); + goto exception; + case OP_inc: + case OP_dec: + { + JSBigIntBuf buf2; + JSBigInt *p2; + p2 = js_bigint_set_si(&buf2, 2 * (op - OP_dec) - 1); + r = js_bigint_add(ctx, p1, p2, 0); + } + break; + case OP_neg: + r = js_bigint_neg(ctx, p1); + break; + case OP_not: + r = js_bigint_not(ctx, p1); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp-1 = JS_CompactBigInt(ctx, r); + } break; default: handle_float64: { double d; - if (is_math_mode(ctx)) - goto handle_bigint; d = JS_VALUE_GET_FLOAT64(op1); switch(op) { case OP_inc: @@ -13049,27 +14461,21 @@ static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) { - JSValue op1, val; - int ret; + JSValue op1; op1 = sp-1; - if (JS_IsObject(op1)) { - ret = js_call_unary_op_fallback(ctx, &val, op1, OP_not); - if (ret < 0) - return -1; - if (ret) { - JS_FreeValue(ctx, op1); - sp-1 = val; - return 0; - } - } - op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) goto exception; - if (is_math_mode(ctx) || JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { - if (ctx->rt->bigint_ops.unary_arith(ctx, sp - 1, OP_not, op1)) + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) { + sp-1 = __JS_NewShortBigInt(ctx, ~JS_VALUE_GET_SHORT_BIG_INT(op1)); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { + JSBigInt *r; + r = js_bigint_not(ctx, JS_VALUE_GET_PTR(op1)); + JS_FreeValue(ctx, op1); + if (!r) goto exception; + sp-1 = JS_CompactBigInt(ctx, r); } else { int32_t v1; if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) @@ -13082,315 +14488,11 @@ return -1; } -static int js_binary_arith_bigfloat(JSContext *ctx, OPCodeEnum op, - JSValue *pres, JSValue op1, JSValue op2) -{ - bf_t a_s, b_s, *r, *a, *b; - int ret; - JSValue res; - - res = JS_NewBigFloat(ctx); - if (JS_IsException(res)) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return -1; - } - r = JS_GetBigFloat(res); - a = JS_ToBigFloat(ctx, &a_s, op1); - b = JS_ToBigFloat(ctx, &b_s, op2); - bf_init(ctx->bf_ctx, r); - switch(op) { - case OP_add: - ret = bf_add(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case OP_sub: - ret = bf_sub(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case OP_mul: - ret = bf_mul(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case OP_div: - ret = bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case OP_math_mod: - /* Euclidian remainder */ - ret = bf_rem(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags, - BF_DIVREM_EUCLIDIAN); - break; - case OP_mod: - ret = bf_rem(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags, - BF_RNDZ); - break; - case OP_pow: - ret = bf_pow(r, a, b, ctx->fp_env.prec, - ctx->fp_env.flags | BF_POW_JS_QUIRKS); - break; - default: - abort(); - } - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (unlikely(ret & BF_ST_MEM_ERROR)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = res; - return 0; -} - -static int js_binary_arith_bigint(JSContext *ctx, OPCodeEnum op, - JSValue *pres, JSValue op1, JSValue op2) -{ - bf_t a_s, b_s, *r, *a, *b; - int ret; - JSValue res; - - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) - goto fail; - a = JS_ToBigInt(ctx, &a_s, op1); - if (!a) - goto fail; - b = JS_ToBigInt(ctx, &b_s, op2); - if (!b) { - JS_FreeBigInt(ctx, a, &a_s); - goto fail; - } - r = JS_GetBigInt(res); - ret = 0; - switch(op) { - case OP_add: - ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_sub: - ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_mul: - ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_div: - if (!is_math_mode(ctx)) { - bf_t rem_s, *rem = &rem_s; - bf_init(ctx->bf_ctx, rem); - ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ, - BF_RNDZ); - bf_delete(rem); - } else { - goto math_mode_div_pow; - } - break; - case OP_math_mod: - /* Euclidian remainder */ - ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, - BF_DIVREM_EUCLIDIAN) & BF_ST_INVALID_OP; - break; - case OP_mod: - ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, - BF_RNDZ) & BF_ST_INVALID_OP; - break; - case OP_pow: - if (b->sign) { - if (!is_math_mode(ctx)) { - ret = BF_ST_INVALID_OP; - } else { - math_mode_div_pow: - JS_FreeValue(ctx, res); - ret = js_call_binary_op_simple(ctx, &res, ctx->class_protoJS_CLASS_BIG_INT, op1, op2, op); - if (ret != 0) { - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (ret < 0) { - return -1; - } else { - *pres = res; - return 0; - } - } - /* if no BigInt power operator defined, return a - bigfloat */ - res = JS_NewBigFloat(ctx); - if (JS_IsException(res)) { - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - goto fail; - } - r = JS_GetBigFloat(res); - if (op == OP_div) { - ret = bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags) & BF_ST_MEM_ERROR; - } else { - ret = bf_pow(r, a, b, ctx->fp_env.prec, - ctx->fp_env.flags | BF_POW_JS_QUIRKS) & BF_ST_MEM_ERROR; - } - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = res; - return 0; - } - } else { - ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUIRKS); - } - break; - - /* logical operations */ - case OP_shl: - case OP_sar: - { - slimb_t v2; -#if LIMB_BITS == 32 - bf_get_int32(&v2, b, 0); - if (v2 == INT32_MIN) - v2 = INT32_MIN + 1; -#else - bf_get_int64(&v2, b, 0); - if (v2 == INT64_MIN) - v2 = INT64_MIN + 1; -#endif - if (op == OP_sar) - v2 = -v2; - ret = bf_set(r, a); - ret |= bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ); - if (v2 < 0) { - ret |= bf_rint(r, BF_RNDD) & (BF_ST_OVERFLOW | BF_ST_MEM_ERROR); - } - } - break; - case OP_and: - ret = bf_logic_and(r, a, b); - break; - case OP_or: - ret = bf_logic_or(r, a, b); - break; - case OP_xor: - ret = bf_logic_xor(r, a, b); - break; - default: - abort(); - } - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = JS_CompactBigInt(ctx, res); - return 0; - fail: - JS_FreeValue(ctx, res); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return -1; -} - -/* b must be a positive integer */ -static int js_bfdec_pow(bfdec_t *r, const bfdec_t *a, const bfdec_t *b) -{ - bfdec_t b1; - int32_t b2; - int ret; - - bfdec_init(b->ctx, &b1); - ret = bfdec_set(&b1, b); - if (ret) { - bfdec_delete(&b1); - return ret; - } - ret = bfdec_rint(&b1, BF_RNDZ); - if (ret) { - bfdec_delete(&b1); - return BF_ST_INVALID_OP; /* must be an integer */ - } - ret = bfdec_get_int32(&b2, &b1); - bfdec_delete(&b1); - if (ret) - return ret; /* overflow */ - if (b2 < 0) - return BF_ST_INVALID_OP; /* must be positive */ - return bfdec_pow_ui(r, a, b2); -} - -static int js_binary_arith_bigdecimal(JSContext *ctx, OPCodeEnum op, - JSValue *pres, JSValue op1, JSValue op2) -{ - bfdec_t *r, *a, *b; - int ret; - JSValue res; - - res = JS_NewBigDecimal(ctx); - if (JS_IsException(res)) - goto fail; - r = JS_GetBigDecimal(res); - - a = JS_ToBigDecimal(ctx, op1); - if (!a) - goto fail; - b = JS_ToBigDecimal(ctx, op2); - if (!b) - goto fail; - switch(op) { - case OP_add: - ret = bfdec_add(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_sub: - ret = bfdec_sub(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_mul: - ret = bfdec_mul(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_div: - ret = bfdec_div(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_math_mod: - /* Euclidian remainder */ - ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_DIVREM_EUCLIDIAN); - break; - case OP_mod: - ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); - break; - case OP_pow: - ret = js_bfdec_pow(r, a, b); - break; - default: - abort(); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = res; - return 0; - fail: - JS_FreeValue(ctx, res); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return -1; -} - static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { - JSValue op1, op2, res; + JSValue op1, op2; uint32_t tag1, tag2; - int ret; double d1, d2; op1 = sp-2; @@ -13403,25 +14505,50 @@ d2 = JS_VALUE_GET_FLOAT64(op2); goto handle_float64; } - - /* try to call an overloaded operator */ - if ((tag1 == JS_TAG_OBJECT && - (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || - (tag2 == JS_TAG_OBJECT && - (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { - ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op, TRUE, 0); - if (ret != 0) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (ret < 0) { - goto exception; - } else { - sp-2 = res; - return 0; + /* fast path for short big int operations */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + switch(op) { + case OP_sub: + v = (js_sdlimb_t)v1 - (js_sdlimb_t)v2; + break; + case OP_mul: + v = (js_sdlimb_t)v1 * (js_sdlimb_t)v2; + break; + case OP_div: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp-2 = __JS_NewShortBigInt(ctx, v1 / v2); + return 0; + case OP_mod: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; } + sp-2 = __JS_NewShortBigInt(ctx, v1 % v2); + return 0; + case OP_pow: + goto slow_big_int; + default: + abort(); + } + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp-2 = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp-2 = JS_MKPTR(JS_TAG_BIG_INT, r); } + return 0; } - op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); @@ -13446,32 +14573,14 @@ break; case OP_mul: v = (int64_t)v1 * (int64_t)v2; - if (is_math_mode(ctx) && - (v < -MAX_SAFE_INTEGER || v > MAX_SAFE_INTEGER)) - goto handle_bigint; if (v == 0 && (v1 | v2) < 0) { sp-2 = __JS_NewFloat64(ctx, -0.0); return 0; } break; case OP_div: - if (is_math_mode(ctx)) - goto handle_bigint; - sp-2 = __JS_NewFloat64(ctx, (double)v1 / (double)v2); + sp-2 = JS_NewFloat64(ctx, (double)v1 / (double)v2); return 0; - case OP_math_mod: - if (unlikely(v2 == 0)) { - throw_bf_exception(ctx, BF_ST_DIVIDE_ZERO); - goto exception; - } - v = (int64_t)v1 % (int64_t)v2; - if (v < 0) { - if (v2 < 0) - v -= v2; - else - v += v2; - } - break; case OP_mod: if (v1 < 0 || v2 <= 0) { sp-2 = JS_NewFloat64(ctx, fmod(v1, v2)); @@ -13481,27 +14590,53 @@ } break; case OP_pow: - if (!is_math_mode(ctx)) { - sp-2 = JS_NewFloat64(ctx, js_pow(v1, v2)); - return 0; - } else { - goto handle_bigint; - } - break; + sp-2 = JS_NewFloat64(ctx, js_pow(v1, v2)); + return 0; default: abort(); } sp-2 = JS_NewInt64(ctx, v); - } else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { - if (ctx->rt->bigdecimal_ops.binary_arith(ctx, op, sp - 2, op1, op2)) - goto exception; - } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { - if (ctx->rt->bigfloat_ops.binary_arith(ctx, op, sp - 2, op1, op2)) - goto exception; - } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - handle_bigint: - if (ctx->rt->bigint_ops.binary_arith(ctx, op, sp - 2, op1, op2)) + } else if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_BIG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_add: + r = js_bigint_add(ctx, p1, p2, 0); + break; + case OP_sub: + r = js_bigint_add(ctx, p1, p2, 1); + break; + case OP_mul: + r = js_bigint_mul(ctx, p1, p2); + break; + case OP_div: + r = js_bigint_divrem(ctx, p1, p2, FALSE); + break; + case OP_mod: + r = js_bigint_divrem(ctx, p1, p2, TRUE); + break; + case OP_pow: + r = js_bigint_pow(ctx, p1, p2); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) goto exception; + sp-2 = JS_CompactBigInt(ctx, r); } else { double dr; /* float64 result */ @@ -13512,8 +14647,6 @@ if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; handle_float64: - if (is_math_mode(ctx) && is_safe_integer(d1) && is_safe_integer(d2)) - goto handle_bigint; switch(op) { case OP_sub: dr = d1 - d2; @@ -13527,13 +14660,6 @@ case OP_mod: dr = fmod(d1, d2); break; - case OP_math_mod: - d2 = fabs(d2); - dr = fmod(d1, d2); - /* XXX: loss of accuracy if dr < 0 */ - if (dr < 0) - dr += d2; - break; case OP_pow: dr = js_pow(d1, d2); break; @@ -13549,11 +14675,15 @@ return -1; } +static inline BOOL tag_is_string(uint32_t tag) +{ + return tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE; +} + static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) { - JSValue op1, op2, res; + JSValue op1, op2; uint32_t tag1, tag2; - int ret; op1 = sp-2; op2 = sp-1; @@ -13568,29 +14698,25 @@ sp-2 = __JS_NewFloat64(ctx, d1 + d2); return 0; } - - if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { - /* try to call an overloaded operator */ - if ((tag1 == JS_TAG_OBJECT && - (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED && - tag2 != JS_TAG_STRING)) || - (tag2 == JS_TAG_OBJECT && - (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED && - tag1 != JS_TAG_STRING))) { - ret = js_call_binary_op_fallback(ctx, &res, op1, op2, OP_add, - FALSE, HINT_NONE); - if (ret != 0) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (ret < 0) { - goto exception; - } else { - sp-2 = res; - return 0; - } - } + /* fast path for short bigint */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + v = (js_sdlimb_t)v1 + (js_sdlimb_t)v2; + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp-2 = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp-2 = JS_MKPTR(JS_TAG_BIG_INT, r); } + return 0; + } + if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); @@ -13606,7 +14732,7 @@ tag2 = JS_VALUE_GET_NORM_TAG(op2); } - if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { + if (tag_is_string(tag1) || tag_is_string(tag2)) { sp-2 = JS_ConcatString(ctx, op1, op2); if (JS_IsException(sp-2)) goto exception; @@ -13633,16 +14759,25 @@ v2 = JS_VALUE_GET_INT(op2); v = (int64_t)v1 + (int64_t)v2; sp-2 = JS_NewInt64(ctx, v); - } else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { - if (ctx->rt->bigdecimal_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) - goto exception; - } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { - if (ctx->rt->bigfloat_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) - goto exception; - } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - handle_bigint: - if (ctx->rt->bigint_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) + } else if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + r = js_bigint_add(ctx, p1, p2, 0); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) goto exception; + sp-2 = JS_CompactBigInt(ctx, r); } else { double d1, d2; /* float64 result */ @@ -13652,8 +14787,6 @@ } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; - if (is_math_mode(ctx) && is_safe_integer(d1) && is_safe_integer(d2)) - goto handle_bigint; sp-2 = __JS_NewFloat64(ctx, d1 + d2); } return 0; @@ -13667,8 +14800,7 @@ JSValue *sp, OPCodeEnum op) { - JSValue op1, op2, res; - int ret; + JSValue op1, op2; uint32_t tag1, tag2; uint32_t v1, v2, r; @@ -13677,24 +14809,62 @@ tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); - /* try to call an overloaded operator */ - if ((tag1 == JS_TAG_OBJECT && - (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || - (tag2 == JS_TAG_OBJECT && - (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { - ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op, TRUE, 0); - if (ret != 0) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (ret < 0) { - goto exception; + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2, v; + js_sdlimb_t vd; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + /* bigint fast path */ + switch(op) { + case OP_and: + v = v1 & v2; + break; + case OP_or: + v = v1 | v2; + break; + case OP_xor: + v = v1 ^ v2; + break; + case OP_sar: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_shl; + } + bigint_sar: + v = v1 >> v2; + break; + case OP_shl: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_sar; + } + bigint_shl: + vd = (js_dlimb_t)v1 << v2; + if (likely(vd >= JS_SHORT_BIG_INT_MIN && + vd <= JS_SHORT_BIG_INT_MAX)) { + v = vd; } else { - sp-2 = res; + JSBigInt *r = js_bigint_new_di(ctx, vd); + if (!r) + goto exception; + sp-2 = JS_MKPTR(JS_TAG_BIG_INT, r); return 0; } + break; + default: + abort(); } + sp-2 = __JS_NewShortBigInt(ctx, v); + return 0; } - op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); @@ -13706,22 +14876,52 @@ goto exception; } - if (is_math_mode(ctx)) - goto bigint_op; - tag1 = JS_VALUE_GET_TAG(op1); tag2 = JS_VALUE_GET_TAG(op2); - if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - if (tag1 != tag2) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - JS_ThrowTypeError(ctx, "both operands must be bigint"); - goto exception; - } else { - bigint_op: - if (ctx->rt->bigint_ops.binary_arith(ctx, op, sp - 2, op1, op2)) - goto exception; + if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_and: + case OP_or: + case OP_xor: + r = js_bigint_logic(ctx, p1, p2, op); + break; + case OP_shl: + case OP_sar: + { + js_slimb_t shift; + shift = js_bigint_get_si_sat(p2); + if (shift > INT32_MAX) + shift = INT32_MAX; + else if (shift < -INT32_MAX) + shift = -INT32_MAX; + if (op == OP_sar) + shift = -shift; + if (shift >= 0) + r = js_bigint_shl(ctx, p1, shift); + else + r = js_bigint_shr(ctx, p1, -shift); + } + break; + default: + abort(); } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp-2 = JS_CompactBigInt(ctx, r); } else { if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { JS_FreeValue(ctx, op2); @@ -13757,103 +14957,103 @@ return -1; } -/* Note: also used for bigint */ -static int js_compare_bigfloat(JSContext *ctx, OPCodeEnum op, - JSValue op1, JSValue op2) +/* op1 must be a bigint or int. */ +static JSBigInt *JS_ToBigIntBuf(JSContext *ctx, JSBigIntBuf *buf1, + JSValue op1) { - bf_t a_s, b_s, *a, *b; - int res; + JSBigInt *p1; - a = JS_ToBigFloat(ctx, &a_s, op1); - if (!a) { - JS_FreeValue(ctx, op2); - return -1; - } - b = JS_ToBigFloat(ctx, &b_s, op2); - if (!b) { - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, op1); - return -1; - } - switch(op) { - case OP_lt: - res = bf_cmp_lt(a, b); /* if NaN return false */ - break; - case OP_lte: - res = bf_cmp_le(a, b); /* if NaN return false */ - break; - case OP_gt: - res = bf_cmp_lt(b, a); /* if NaN return false */ + switch(JS_VALUE_GET_TAG(op1)) { + case JS_TAG_INT: + p1 = js_bigint_set_si(buf1, JS_VALUE_GET_INT(op1)); break; - case OP_gte: - res = bf_cmp_le(b, a); /* if NaN return false */ + case JS_TAG_SHORT_BIG_INT: + p1 = js_bigint_set_short(buf1, op1); break; - case OP_eq: - res = bf_cmp_eq(a, b); /* if NaN return false */ + case JS_TAG_BIG_INT: + p1 = JS_VALUE_GET_PTR(op1); break; default: abort(); } - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return res; + return p1; } -static int js_compare_bigdecimal(JSContext *ctx, OPCodeEnum op, - JSValue op1, JSValue op2) +/* op1 and op2 must be numeric types and at least one must be a + bigint. No exception is generated. */ +static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) { - bfdec_t *a, *b; - int res; + int res, val, tag1, tag2; + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; - /* Note: binary floats are converted to bigdecimal with - toString(). It is not mathematically correct but is consistent - with the BigDecimal() constructor behavior */ - op1 = JS_ToBigDecimalFree(ctx, op1, TRUE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - return -1; - } - op2 = JS_ToBigDecimalFree(ctx, op2, TRUE); - if (JS_IsException(op2)) { + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_INT)) { + /* fast path */ + js_slimb_t v1, v2; + if (tag1 == JS_TAG_INT) + v1 = JS_VALUE_GET_INT(op1); + else + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (tag2 == JS_TAG_INT) + v2 = JS_VALUE_GET_INT(op2); + else + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + val = (v1 > v2) - (v1 < v2); + } else { + if (tag1 == JS_TAG_FLOAT64) { + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_float64_cmp(ctx, p2, JS_VALUE_GET_FLOAT64(op1)); + if (val == 2) + goto unordered; + val = -val; + } else if (tag2 == JS_TAG_FLOAT64) { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + val = js_bigint_float64_cmp(ctx, p1, JS_VALUE_GET_FLOAT64(op2)); + if (val == 2) { + unordered: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return FALSE; + } + } else { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_cmp(ctx, p1, p2); + } JS_FreeValue(ctx, op1); - return -1; + JS_FreeValue(ctx, op2); } - a = JS_ToBigDecimal(ctx, op1); - b = JS_ToBigDecimal(ctx, op2); switch(op) { case OP_lt: - res = bfdec_cmp_lt(a, b); /* if NaN return false */ + res = val < 0; break; case OP_lte: - res = bfdec_cmp_le(a, b); /* if NaN return false */ + res = val <= 0; break; case OP_gt: - res = bfdec_cmp_lt(b, a); /* if NaN return false */ + res = val > 0; break; case OP_gte: - res = bfdec_cmp_le(b, a); /* if NaN return false */ + res = val >= 0; break; case OP_eq: - res = bfdec_cmp_eq(a, b); /* if NaN return false */ + res = val == 0; break; default: abort(); } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); return res; } static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { - JSValue op1, op2, ret; + JSValue op1, op2; int res; uint32_t tag1, tag2; @@ -13861,24 +15061,6 @@ op2 = sp-1; tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); - /* try to call an overloaded operator */ - if ((tag1 == JS_TAG_OBJECT && - (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || - (tag2 == JS_TAG_OBJECT && - (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { - res = js_call_binary_op_fallback(ctx, &ret, op1, op2, op, - FALSE, HINT_NUMBER); - if (res != 0) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (res < 0) { - goto exception; - } else { - sp-2 = ret; - return 0; - } - } - } op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); @@ -13892,11 +15074,13 @@ tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); - if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { - JSString *p1, *p2; - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = js_string_compare(ctx, p1, p2); + if (tag_is_string(tag1) && tag_is_string(tag2)) { + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + res = js_string_compare(ctx, JS_VALUE_GET_STRING(op1), + JS_VALUE_GET_STRING(op2)); + } else { + res = js_string_rope_compare(ctx, op1, op2, FALSE); + } switch(op) { case OP_lt: res = (res < 0); @@ -13919,17 +15103,20 @@ /* fast path for float64/int */ goto float64_compare; } else { - if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) || - (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING)) && - !is_math_mode(ctx)) { - if (tag1 == JS_TAG_STRING) { + if ((((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + tag_is_string(tag2)) || + ((tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) && + tag_is_string(tag1)))) { + if (tag_is_string(tag1)) { op1 = JS_StringToBigInt(ctx, op1); - if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) goto invalid_bigint_string; } - if (tag2 == JS_TAG_STRING) { + if (tag_is_string(tag2)) { op2 = JS_StringToBigInt(ctx, op2); - if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT) { invalid_bigint_string: JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); @@ -13953,18 +15140,9 @@ tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); - if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { - res = ctx->rt->bigdecimal_ops.compare(ctx, op, op1, op2); - if (res < 0) - goto exception; - } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { - res = ctx->rt->bigfloat_ops.compare(ctx, op, op1, op2); - if (res < 0) - goto exception; - } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - res = ctx->rt->bigint_ops.compare(ctx, op, op1, op2); - if (res < 0) - goto exception; + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + res = js_compare_bigint(ctx, op, op1, op2); } else { double d1, d2; @@ -14008,15 +15186,15 @@ static BOOL tag_is_number(uint32_t tag) { - return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT || - tag == JS_TAG_FLOAT64 || tag == JS_TAG_BIG_FLOAT || - tag == JS_TAG_BIG_DECIMAL); + return (tag == JS_TAG_INT || + tag == JS_TAG_FLOAT64 || + tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT); } static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { - JSValue op1, op2, ret; + JSValue op1, op2; int res; uint32_t tag1, tag2; @@ -14044,53 +15222,32 @@ d2 = JS_VALUE_GET_INT(op2); } res = (d1 == d2); - } else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { - res = ctx->rt->bigdecimal_ops.compare(ctx, OP_eq, op1, op2); - if (res < 0) - goto exception; - } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { - res = ctx->rt->bigfloat_ops.compare(ctx, OP_eq, op1, op2); - if (res < 0) - goto exception; } else { - res = ctx->rt->bigint_ops.compare(ctx, OP_eq, op1, op2); - if (res < 0) - goto exception; + res = js_compare_bigint(ctx, OP_eq, op1, op2); } } else if (tag1 == tag2) { - if (tag1 == JS_TAG_OBJECT) { - /* try the fallback operator */ - res = js_call_binary_op_fallback(ctx, &ret, op1, op2, - is_neq ? OP_neq : OP_eq, - FALSE, HINT_NONE); - if (res != 0) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (res < 0) { - goto exception; - } else { - sp-2 = ret; - return 0; - } - } - } res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { res = TRUE; - } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || - (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { + } else if (tag_is_string(tag1) && tag_is_string(tag2)) { + /* needed when comparing strings and ropes */ + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if ((tag_is_string(tag1) && tag_is_number(tag2)) || + (tag_is_string(tag2) && tag_is_number(tag1))) { - if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) && - !is_math_mode(ctx)) { - if (tag1 == JS_TAG_STRING) { + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + if (tag_is_string(tag1)) { op1 = JS_StringToBigInt(ctx, op1); - if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) goto invalid_bigint_string; } - if (tag2 == JS_TAG_STRING) { + if (tag_is_string(tag2)) { op2 = JS_StringToBigInt(ctx, op2); - if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT ) { invalid_bigint_string: JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); @@ -14110,7 +15267,7 @@ goto exception; } } - res = js_strict_eq(ctx, op1, op2); + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } else if (tag1 == JS_TAG_BOOL) { op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); goto redo; @@ -14118,25 +15275,9 @@ op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2)); goto redo; } else if ((tag1 == JS_TAG_OBJECT && - (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || + (tag_is_number(tag2) || tag_is_string(tag2) || tag2 == JS_TAG_SYMBOL)) || (tag2 == JS_TAG_OBJECT && - (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { - - /* try the fallback operator */ - res = js_call_binary_op_fallback(ctx, &ret, op1, op2, - is_neq ? OP_neq : OP_eq, - FALSE, HINT_NONE); - if (res != 0) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (res < 0) { - goto exception; - } else { - sp-2 = ret; - return 0; - } - } - + (tag_is_number(tag1) || tag_is_string(tag1) || tag1 == JS_TAG_SYMBOL))) { op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); @@ -14187,10 +15328,10 @@ JS_FreeValue(ctx, op1); goto exception; } - /* XXX: could forbid >>> in bignum mode */ - if (!is_math_mode(ctx) && - (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || - JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) { + if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) { JS_ThrowTypeError(ctx, "bigint operands are forbidden for >>>"); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); @@ -14208,451 +15349,6 @@ return -1; } -static JSValue js_mul_pow10_to_float64(JSContext *ctx, const bf_t *a, - int64_t exponent) -{ - bf_t r_s, *r = &r_s; - double d; - int ret; - - /* always convert to Float64 */ - bf_init(ctx->bf_ctx, r); - ret = bf_mul_pow_radix(r, a, 10, exponent, - 53, bf_set_exp_bits(11) | BF_RNDN | - BF_FLAG_SUBNORMAL); - bf_get_float64(r, &d, BF_RNDN); - bf_delete(r); - if (ret & BF_ST_MEM_ERROR) - return JS_ThrowOutOfMemory(ctx); - else - return __JS_NewFloat64(ctx, d); -} - -static no_inline int js_mul_pow10(JSContext *ctx, JSValue *sp) -{ - bf_t a_s, *a, *r; - JSValue op1, op2, res; - int64_t e; - int ret; - - res = JS_NewBigFloat(ctx); - if (JS_IsException(res)) - return -1; - r = JS_GetBigFloat(res); - op1 = sp-2; - op2 = sp-1; - a = JS_ToBigFloat(ctx, &a_s, op1); - if (!a) - return -1; - if (JS_IsBigInt(ctx, op2)) { - ret = JS_ToBigInt64(ctx, &e, op2); - } else { - ret = JS_ToInt64(ctx, &e, op2); - } - if (ret) { - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, res); - return -1; - } - - bf_mul_pow_radix(r, a, 10, e, ctx->fp_env.prec, ctx->fp_env.flags); - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp-2 = res; - return 0; -} - -#else /* !CONFIG_BIGNUM */ - -static JSValue JS_ThrowUnsupportedBigint(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "bigint is not supported"); -} - -JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) -{ - return JS_ThrowUnsupportedBigint(ctx); -} - -JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) -{ - return JS_ThrowUnsupportedBigint(ctx); -} - -int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) -{ - JS_ThrowUnsupportedBigint(ctx); - *pres = 0; - return -1; -} - -static no_inline __exception int js_unary_arith_slow(JSContext *ctx, - JSValue *sp, - OPCodeEnum op) -{ - JSValue op1; - double d; - - op1 = sp-1; - if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) { - sp-1 = JS_UNDEFINED; - return -1; - } - switch(op) { - case OP_inc: - d++; - break; - case OP_dec: - d--; - break; - case OP_plus: - break; - case OP_neg: - d = -d; - break; - default: - abort(); - } - sp-1 = JS_NewFloat64(ctx, d); - return 0; -} - -/* specific case necessary for correct return value semantics */ -static __exception int js_post_inc_slow(JSContext *ctx, - JSValue *sp, OPCodeEnum op) -{ - JSValue op1; - double d, r; - - op1 = sp-1; - if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) { - sp-1 = JS_UNDEFINED; - return -1; - } - r = d + 2 * (op - OP_post_dec) - 1; - sp0 = JS_NewFloat64(ctx, r); - sp-1 = JS_NewFloat64(ctx, d); - return 0; -} - -static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - double d1, d2, r; - - op1 = sp-2; - op2 = sp-1; - if (unlikely(JS_ToFloat64Free(ctx, &d1, op1))) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (unlikely(JS_ToFloat64Free(ctx, &d2, op2))) { - goto exception; - } - switch(op) { - case OP_sub: - r = d1 - d2; - break; - case OP_mul: - r = d1 * d2; - break; - case OP_div: - r = d1 / d2; - break; - case OP_mod: - r = fmod(d1, d2); - break; - case OP_pow: - r = js_pow(d1, d2); - break; - default: - abort(); - } - sp-2 = JS_NewFloat64(ctx, r); - return 0; - exception: - sp-2 = JS_UNDEFINED; - sp-1 = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - - op1 = sp-2; - op2 = sp-1; - tag1 = JS_VALUE_GET_TAG(op1); - tag2 = JS_VALUE_GET_TAG(op2); - if ((tag1 == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag1)) && - (tag2 == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag2))) { - goto add_numbers; - } else { - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_TAG(op1); - tag2 = JS_VALUE_GET_TAG(op2); - if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { - sp-2 = JS_ConcatString(ctx, op1, op2); - if (JS_IsException(sp-2)) - goto exception; - } else { - double d1, d2; - add_numbers: - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - sp-2 = JS_NewFloat64(ctx, d1 + d2); - } - } - return 0; - exception: - sp-2 = JS_UNDEFINED; - sp-1 = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_binary_logic_slow(JSContext *ctx, - JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - uint32_t v1, v2, r; - - op1 = sp-2; - op2 = sp-1; - if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) - goto exception; - switch(op) { - case OP_shl: - r = v1 << (v2 & 0x1f); - break; - case OP_sar: - r = (int)v1 >> (v2 & 0x1f); - break; - case OP_and: - r = v1 & v2; - break; - case OP_or: - r = v1 | v2; - break; - case OP_xor: - r = v1 ^ v2; - break; - default: - abort(); - } - sp-2 = JS_NewInt32(ctx, r); - return 0; - exception: - sp-2 = JS_UNDEFINED; - sp-1 = JS_UNDEFINED; - return -1; -} - -static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) -{ - int32_t v1; - - if (unlikely(JS_ToInt32Free(ctx, &v1, sp-1))) { - sp-1 = JS_UNDEFINED; - return -1; - } - sp-1 = JS_NewInt32(ctx, ~v1); - return 0; -} - -static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - int res; - - op1 = sp-2; - op2 = sp-1; - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING && - JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { - JSString *p1, *p2; - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = js_string_compare(ctx, p1, p2); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - switch(op) { - case OP_lt: - res = (res < 0); - break; - case OP_lte: - res = (res <= 0); - break; - case OP_gt: - res = (res > 0); - break; - default: - case OP_gte: - res = (res >= 0); - break; - } - } else { - double d1, d2; - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - switch(op) { - case OP_lt: - res = (d1 < d2); /* if NaN return false */ - break; - case OP_lte: - res = (d1 <= d2); /* if NaN return false */ - break; - case OP_gt: - res = (d1 > d2); /* if NaN return false */ - break; - default: - case OP_gte: - res = (d1 >= d2); /* if NaN return false */ - break; - } - } - sp-2 = JS_NewBool(ctx, res); - return 0; - exception: - sp-2 = JS_UNDEFINED; - sp-1 = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, - BOOL is_neq) -{ - JSValue op1, op2; - int tag1, tag2; - BOOL res; - - op1 = sp-2; - op2 = sp-1; - redo: - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - if (tag1 == tag2 || - (tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || - (tag2 == JS_TAG_INT && tag1 == JS_TAG_FLOAT64)) { - res = js_strict_eq(ctx, op1, op2); - } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || - (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { - res = TRUE; - } else if ((tag1 == JS_TAG_STRING && (tag2 == JS_TAG_INT || - tag2 == JS_TAG_FLOAT64)) || - (tag2 == JS_TAG_STRING && (tag1 == JS_TAG_INT || - tag1 == JS_TAG_FLOAT64))) { - double d1; - double d2; - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - res = (d1 == d2); - } else if (tag1 == JS_TAG_BOOL) { - op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); - goto redo; - } else if (tag2 == JS_TAG_BOOL) { - op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2)); - goto redo; - } else if (tag1 == JS_TAG_OBJECT && - (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64 || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) { - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - goto redo; - } else if (tag2 == JS_TAG_OBJECT && - (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64 || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL)) { - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - goto redo; - } else { - /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ - if ((JS_IsHTMLDDA(ctx, op1) && - (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || - (JS_IsHTMLDDA(ctx, op2) && - (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { - res = TRUE; - } else { - res = FALSE; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - } - sp-2 = JS_NewBool(ctx, res ^ is_neq); - return 0; - exception: - sp-2 = JS_UNDEFINED; - sp-1 = JS_UNDEFINED; - return -1; -} - -static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - uint32_t v1, v2, r; - - op1 = sp-2; - op2 = sp-1; - if (unlikely(JS_ToUint32Free(ctx, &v1, op1))) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (unlikely(JS_ToUint32Free(ctx, &v2, op2))) - goto exception; - r = v1 >> (v2 & 0x1f); - sp-2 = JS_NewUint32(ctx, r); - return 0; - exception: - sp-2 = JS_UNDEFINED; - sp-1 = JS_UNDEFINED; - return -1; -} - -#endif /* !CONFIG_BIGNUM */ /* XXX: Should take JSValueConst arguments */ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, @@ -14678,14 +15374,15 @@ res = (tag1 == tag2); break; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: { - JSString *p1, *p2; - if (tag1 != tag2) { + if (!tag_is_string(tag2)) { res = FALSE; + } else if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + res = js_string_eq(ctx, JS_VALUE_GET_STRING(op1), + JS_VALUE_GET_STRING(op2)); } else { - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = (js_string_compare(ctx, p1, p2) == 0); + res = (js_string_rope_compare(ctx, op1, op2, TRUE) == 0); } } break; @@ -14746,63 +15443,29 @@ res = (d1 == d2); /* if NaN return false and +0 == -0 */ } goto done_no_free; -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: { - bf_t a_s, *a, b_s, *b; - if (tag1 != tag2) { - res = FALSE; - break; - } - a = JS_ToBigFloat(ctx, &a_s, op1); - b = JS_ToBigFloat(ctx, &b_s, op2); - res = bf_cmp_eq(a, b); - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - } - break; - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p1, *p2; - const bf_t *a, *b; - if (tag1 != tag2) { - res = FALSE; - break; - } - p1 = JS_VALUE_GET_PTR(op1); - p2 = JS_VALUE_GET_PTR(op2); - a = &p1->num; - b = &p2->num; - if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { - if (eq_mode == JS_EQ_SAME_VALUE_ZERO && - a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) { - res = TRUE; - } else { - res = (bf_cmp_full(a, b) == 0); - } - } else { - res = bf_cmp_eq(a, b); - } - } - break; - case JS_TAG_BIG_DECIMAL: - { - JSBigDecimal *p1, *p2; - const bfdec_t *a, *b; - if (tag1 != tag2) { + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + if (tag2 != JS_TAG_SHORT_BIG_INT && + tag2 != JS_TAG_BIG_INT) { res = FALSE; break; } - p1 = JS_VALUE_GET_PTR(op1); - p2 = JS_VALUE_GET_PTR(op2); - a = &p1->num; - b = &p2->num; - res = bfdec_cmp_eq(a, b); + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + res = (js_bigint_cmp(ctx, p1, p2) == 0); } break; -#endif default: res = FALSE; break; @@ -14813,9 +15476,16 @@ return res; } -static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) +static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, + JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), + JS_EQ_STRICT); +} + +BOOL JS_StrictEq(JSContext *ctx, JSValueConst op1, JSValueConst op2) { - return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + return js_strict_eq(ctx, op1, op2); } static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) @@ -14825,6 +15495,11 @@ JS_EQ_SAME_VALUE); } +BOOL JS_SameValue(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_same_value(ctx, op1, op2); +} + static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_strict_eq2(ctx, @@ -14832,11 +15507,16 @@ JS_EQ_SAME_VALUE_ZERO); } +BOOL JS_SameValueZero(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_same_value_zero(ctx, op1, op2); +} + static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { BOOL res; - res = js_strict_eq(ctx, sp-2, sp-1); + res = js_strict_eq2(ctx, sp-2, sp-1, JS_EQ_STRICT); sp-2 = JS_NewBool(ctx, res ^ is_neq); return 0; } @@ -14867,6 +15547,43 @@ return 0; } +static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + + op1 = sp-2; /* object */ + op2 = sp-1; /* field name or method function */ + + if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + if (JS_IsObject(op2)) { + /* method: use the brand */ + ret = JS_CheckBrand(ctx, op1, op2); + if (ret < 0) + return -1; + } else { + JSAtom atom; + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + /* field */ + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + p = JS_VALUE_GET_OBJ(op1); + prs = find_own_property(&pr, p, atom); + JS_FreeAtom(ctx, atom); + ret = (prs != NULL); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp-2 = JS_NewBool(ctx, ret); + return 0; +} + static __exception int js_has_unscopable(JSContext *ctx, JSValueConst obj, JSAtom atom) { @@ -14908,17 +15625,10 @@ tag = JS_VALUE_GET_NORM_TAG(op1); switch(tag) { -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: atom = JS_ATOM_bigint; break; - case JS_TAG_BIG_FLOAT: - atom = JS_ATOM_bigfloat; - break; - case JS_TAG_BIG_DECIMAL: - atom = JS_ATOM_bigdecimal; - break; -#endif case JS_TAG_INT: case JS_TAG_FLOAT64: atom = JS_ATOM_number; @@ -14930,6 +15640,7 @@ atom = JS_ATOM_boolean; break; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: atom = JS_ATOM_string; break; case JS_TAG_OBJECT: @@ -14979,21 +15690,15 @@ return 0; } -static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_ThrowTypeError(ctx, "invalid property access"); -} - /* XXX: not 100% compatible, but mozilla seems to use a similar implementation to ensure that caller in non strict mode does not throw (ES5 compatibility) */ -static JSValue js_function_proto_caller(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) +static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype) { - return js_throw_type_error(ctx, this_val, 0, NULL); + if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype || argc >= 1) { + return JS_ThrowTypeError(ctx, "invalid property access"); } return JS_UNDEFINED; } @@ -15009,11 +15714,16 @@ } static JSValue js_function_proto_lineNumber(JSContext *ctx, - JSValueConst this_val) + JSValueConst this_val, int is_col) { JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); if (b && b->has_debug) { - return JS_NewInt32(ctx, b->debug.line_num); + int line_num, col_num; + line_num = find_line_num(ctx, b, -1, &col_num); + if (is_col) + return JS_NewInt32(ctx, col_num); + else + return JS_NewInt32(ctx, line_num); } return JS_UNDEFINED; } @@ -15057,16 +15767,16 @@ /* add the length field (cannot fail) */ pr = add_property(ctx, p, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (unlikely(!pr)) + goto fail; pr->u.value = JS_NewInt32(ctx, argc); /* initialize the fast array part */ tab = NULL; if (argc > 0) { tab = js_malloc(ctx, sizeof(tab0) * argc); - if (!tab) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } + if (!tab) + goto fail; for(i = 0; i < argc; i++) { tabi = JS_DupValue(ctx, argvi); } @@ -15082,6 +15792,9 @@ ctx->throw_type_error, ctx->throw_type_error, JS_PROP_HAS_GET | JS_PROP_HAS_SET); return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; } #define GLOBAL_VAR_OFFSET 0x40000000 @@ -15106,6 +15819,8 @@ /* add the length field (cannot fail) */ pr = add_property(ctx, p, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (unlikely(!pr)) + goto fail; pr->u.value = JS_NewInt32(ctx, argc); for(i = 0; i < arg_count; i++) { @@ -15143,32 +15858,12 @@ return JS_EXCEPTION; } -static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValueConst *argv) -{ - JSValue val; - int i, ret; - - val = JS_NewArray(ctx); - if (JS_IsException(val)) - return val; - for (i = first; i < argc; i++) { - ret = JS_DefinePropertyValueUint32(ctx, val, i - first, - JS_DupValue(ctx, argvi), - JS_PROP_C_W_E); - if (ret < 0) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - } - return val; -} - static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) { - JSObject *p; + JSObject *p, *p1; JSPropertyEnum *tab_atom; int i; - JSValue enum_obj, obj1; + JSValue enum_obj; JSForInIterator *it; uint32_t tag, tab_atom_count; @@ -15191,40 +15886,16 @@ it->is_array = FALSE; it->obj = obj; it->idx = 0; - p = JS_VALUE_GET_OBJ(enum_obj); - p->u.for_in_iterator = it; + it->tab_atom = NULL; + it->atom_count = 0; + it->in_prototype_chain = FALSE; + p1 = JS_VALUE_GET_OBJ(enum_obj); + p1->u.for_in_iterator = it; if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) return enum_obj; - /* fast path: assume no enumerable properties in the prototype chain */ - obj1 = JS_DupValue(ctx, obj); - for(;;) { - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - if (JS_IsException(obj1)) - goto fail; - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, - JS_VALUE_GET_OBJ(obj1), - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - if (tab_atom_count != 0) { - JS_FreeValue(ctx, obj1); - goto slow_path; - } - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - } - p = JS_VALUE_GET_OBJ(obj); - if (p->fast_array) { JSShape *sh; JSShapeProperty *prs; @@ -15236,61 +15907,90 @@ } /* for fast arrays, we only store the number of elements */ it->is_array = TRUE; - it->array_length = p->u.array.count; + it->atom_count = p->u.array.count; } else { normal_case: if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) - goto fail; - for(i = 0; i < tab_atom_count; i++) { - JS_SetPropertyInternal(ctx, enum_obj, tab_atomi.atom, JS_NULL, 0); + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_FreeValue(ctx, enum_obj); + return JS_EXCEPTION; } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; } return enum_obj; +} - slow_path: - /* non enumerable properties hide the enumerables ones in the - prototype chain */ - obj1 = JS_DupValue(ctx, obj); +/* obj -> enum_obj */ +static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) +{ + sp-1 = build_for_in_iterator(ctx, sp-1); + if (JS_IsException(sp-1)) + return -1; + return 0; +} + +/* return -1 if exception, 0 if slow case, 1 if the enumeration is finished */ +static __exception int js_for_in_prepare_prototype_chain_enum(JSContext *ctx, + JSValueConst enum_obj) +{ + JSObject *p; + JSForInIterator *it; + JSPropertyEnum *tab_atom; + uint32_t tab_atom_count, i; + JSValue obj1; + + p = JS_VALUE_GET_OBJ(enum_obj); + it = p->u.for_in_iterator; + + /* check if there are enumerable properties in the prototype chain (fast path) */ + obj1 = JS_DupValue(ctx, it->obj); for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, JS_VALUE_GET_OBJ(obj1), - JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { JS_FreeValue(ctx, obj1); goto fail; } - for(i = 0; i < tab_atom_count; i++) { - JS_DefinePropertyValue(ctx, enum_obj, tab_atomi.atom, JS_NULL, - (tab_atomi.is_enumerable ? - JS_PROP_ENUMERABLE : 0)); + JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); + if (tab_atom_count != 0) { + JS_FreeValue(ctx, obj1); + goto slow_path; } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - if (JS_IsException(obj1)) - goto fail; /* must check for timeout to avoid infinite loop */ if (js_poll_interrupts(ctx)) { JS_FreeValue(ctx, obj1); goto fail; } } - return enum_obj; + JS_FreeValue(ctx, obj1); + return 1; - fail: - JS_FreeValue(ctx, enum_obj); - return JS_EXCEPTION; -} + slow_path: + /* add the visited properties, even if they are not enumerable */ + if (it->is_array) { + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(it->obj), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + goto fail; + } + it->is_array = FALSE; + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + } -/* obj -> enum_obj */ -static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) -{ - sp-1 = build_for_in_iterator(ctx, sp-1); - if (JS_IsException(sp-1)) - return -1; + for(i = 0; i < it->atom_count; i++) { + if (JS_DefinePropertyValue(ctx, enum_obj, it->tab_atomi.atom, JS_NULL, JS_PROP_ENUMERABLE) < 0) + goto fail; + } return 0; + fail: + return -1; } /* enum_obj -> enum_obj value done */ @@ -15300,6 +16000,8 @@ JSObject *p; JSAtom prop; JSForInIterator *it; + JSPropertyEnum *tab_atom; + uint32_t tab_atom_count; int ret; enum_obj = sp-1; @@ -15312,28 +16014,68 @@ it = p->u.for_in_iterator; for(;;) { - if (it->is_array) { - if (it->idx >= it->array_length) - goto done; - prop = __JS_AtomFromUInt32(it->idx); - it->idx++; + if (it->idx >= it->atom_count) { + if (JS_IsNull(it->obj) || JS_IsUndefined(it->obj)) + goto done; /* not an object */ + /* no more property in the current object: look in the prototype */ + if (!it->in_prototype_chain) { + ret = js_for_in_prepare_prototype_chain_enum(ctx, enum_obj); + if (ret < 0) + return -1; + if (ret) + goto done; + it->in_prototype_chain = TRUE; + } + it->obj = JS_GetPrototypeFree(ctx, it->obj); + if (JS_IsException(it->obj)) + return -1; + if (JS_IsNull(it->obj)) + goto done; /* no more prototype */ + + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) + return -1; + + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(it->obj), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + return -1; + } + JS_FreePropertyEnum(ctx, it->tab_atom, it->atom_count); + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + it->idx = 0; } else { - JSShape *sh = p->shape; - JSShapeProperty *prs; - if (it->idx >= sh->prop_count) - goto done; - prs = get_shape_prop(sh) + it->idx; - prop = prs->atom; - it->idx++; - if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) - continue; + if (it->is_array) { + prop = __JS_AtomFromUInt32(it->idx); + it->idx++; + } else { + BOOL is_enumerable; + prop = it->tab_atomit->idx.atom; + is_enumerable = it->tab_atomit->idx.is_enumerable; + it->idx++; + if (it->in_prototype_chain) { + /* slow case: we are in the prototype chain */ + ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(enum_obj), prop); + if (ret < 0) + return ret; + if (ret) + continue; /* already visited */ + /* add to the visited property list */ + if (JS_DefinePropertyValue(ctx, enum_obj, prop, JS_NULL, + JS_PROP_ENUMERABLE) < 0) + return -1; + } + if (!is_enumerable) + continue; + } + /* check if the property was deleted */ + ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(it->obj), prop); + if (ret < 0) + return ret; + if (ret) + break; } - /* check if the property was deleted */ - ret = JS_HasProperty(ctx, it->obj, prop); - if (ret < 0) - return ret; - if (ret) - break; } /* return the property */ sp0 = JS_AtomToValue(ctx, prop); @@ -15436,6 +16178,7 @@ return JS_EXCEPTION; } +/* Note: always return JS_UNDEFINED when *pdone = TRUE. */ static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, JSValueConst method, int argc, JSValueConst *argv, BOOL *pdone) @@ -15446,9 +16189,13 @@ obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); if (JS_IsException(obj)) goto fail; - if (done != 2) { - *pdone = done; + if (likely(done == 0)) { + *pdone = FALSE; return obj; + } else if (done != 2) { + JS_FreeValue(ctx, obj); + *pdone = TRUE; + return JS_UNDEFINED; } else { done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); if (JS_IsException(done_val)) @@ -15476,7 +16223,7 @@ if (is_exception_pending) { ex_obj = ctx->rt->current_exception; - ctx->rt->current_exception = JS_NULL; + ctx->rt->current_exception = JS_UNINITIALIZED; res = -1; } else { ex_obj = JS_UNDEFINED; @@ -15556,6 +16303,21 @@ return 0; } +static __exception int js_for_await_of_next(JSContext *ctx, JSValue *sp) +{ + JSValue obj, iter, next; + + sp-1 = JS_UNDEFINED; /* disable the catch offset so that + exceptions do not close the iterator */ + iter = sp-3; + next = sp-2; + obj = JS_Call(ctx, next, iter, 0, NULL); + if (JS_IsException(obj)) + return -1; + sp0 = obj; + return 0; +} + static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValueConst obj, BOOL *pdone) { @@ -15588,6 +16350,9 @@ if (JS_IsException(value)) return -1; JS_FreeValue(ctx, obj); + /* put again the catch offset so that exceptions close the + iterator */ + sp-2 = JS_NewCatchOffset(ctx, 0); sp-1 = value; sp0 = JS_NewBool(ctx, done); return 0; @@ -15657,6 +16422,7 @@ int is_array_iterator; JSValue *arrp; uint32_t i, count32, pos; + JSCFunctionType ft; if (JS_VALUE_GET_TAG(sp-2) != JS_TAG_INT) { JS_ThrowInternalError(ctx, "invalid index for append"); @@ -15674,8 +16440,8 @@ iterator = JS_GetProperty(ctx, sp-1, JS_ATOM_Symbol_iterator); if (JS_IsException(iterator)) return -1; - is_array_iterator = JS_IsCFunction(ctx, iterator, - (JSCFunction *)js_create_array_iterator, + ft.generic_magic = js_create_array_iterator; + is_array_iterator = JS_IsCFunction(ctx, iterator, ft.generic, JS_ITERATOR_KIND_VALUE); JS_FreeValue(ctx, iterator); @@ -15687,8 +16453,10 @@ JS_FreeValue(ctx, enumobj); return -1; } + + ft.iterator_next = js_array_iterator_next; if (is_array_iterator - && JS_IsCFunction(ctx, method, (JSCFunction *)js_array_iterator_next, 0) + && JS_IsCFunction(ctx, method, ft.generic, 0) && js_get_fast_array(ctx, sp-1, &arrp, &count32)) { uint32_t len; if (js_get_length32(ctx, &len, sp-1)) @@ -15799,10 +16567,10 @@ if (ret < 0) goto exception; } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); + JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); return 0; exception: - js_free_prop_enum(ctx, tab_atom, tab_atom_count); + JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); return -1; } @@ -15817,10 +16585,16 @@ { JSVarRef *var_ref; struct list_head *el; + JSValue *pvalue; + + if (is_arg) + pvalue = &sf->arg_bufvar_idx; + else + pvalue = &sf->var_bufvar_idx; list_for_each(el, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) { + var_ref = list_entry(el, JSVarRef, var_ref_link); + if (var_ref->pvalue == pvalue) { var_ref->header.ref_count++; return var_ref; } @@ -15830,15 +16604,24 @@ if (!var_ref) return NULL; var_ref->header.ref_count = 1; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); var_ref->is_detached = FALSE; - var_ref->is_arg = is_arg; - var_ref->var_idx = var_idx; - list_add_tail(&var_ref->header.link, &sf->var_ref_list); - if (is_arg) - var_ref->pvalue = &sf->arg_bufvar_idx; - else - var_ref->pvalue = &sf->var_bufvar_idx; - var_ref->value = JS_UNDEFINED; + list_add_tail(&var_ref->var_ref_link, &sf->var_ref_list); + if (sf->js_mode & JS_MODE_ASYNC) { + /* The stack frame is detached and may be destroyed at any + time so its reference count must be increased. Calling + close_var_refs() when destroying the stack frame is not + possible because it would change the graph between the GC + objects. Another solution could be to temporarily detach + the JSVarRef of async functions during the GC. It would + have the advantage of allowing the release of unused stack + frames in a cycle. */ + var_ref->async_func = container_of(sf, JSAsyncFunctionState, frame); + var_ref->async_func->header.ref_count++; + } else { + var_ref->async_func = NULL; + } + var_ref->pvalue = pvalue; return var_ref; } @@ -16031,7 +16814,7 @@ goto fail; } - /* the constructor property must be first. It can be overriden by + /* the constructor property must be first. It can be overridden by computed property names */ if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, JS_DupValue(ctx, ctor), @@ -16066,37 +16849,36 @@ { struct list_head *el, *el1; JSVarRef *var_ref; - int var_idx; list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - var_idx = var_ref->var_idx; - if (var_ref->is_arg) - var_ref->value = JS_DupValueRT(rt, sf->arg_bufvar_idx); - else - var_ref->value = JS_DupValueRT(rt, sf->var_bufvar_idx); + var_ref = list_entry(el, JSVarRef, var_ref_link); + /* no need to unlink var_ref->var_ref_link as the list is never used afterwards */ + if (var_ref->async_func) + async_func_free(rt, var_ref->async_func); + var_ref->value = JS_DupValueRT(rt, *var_ref->pvalue); var_ref->pvalue = &var_ref->value; /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; - add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); } } -static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int idx, int is_arg) +static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int var_idx) { + JSValue *pvalue; struct list_head *el, *el1; JSVarRef *var_ref; - int var_idx = idx; + pvalue = &sf->var_bufvar_idx; list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - if (var_idx == var_ref->var_idx && var_ref->is_arg == is_arg) { - var_ref->value = JS_DupValue(ctx, sf->var_bufvar_idx); + var_ref = list_entry(el, JSVarRef, var_ref_link); + if (var_ref->pvalue == pvalue) { + list_del(&var_ref->var_ref_link); + if (var_ref->async_func) + async_func_free(ctx->rt, var_ref->async_func); + var_ref->value = JS_DupValue(ctx, *var_ref->pvalue); var_ref->pvalue = &var_ref->value; - list_del(&var_ref->header.link); /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; - add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); } } } @@ -16129,24 +16911,13 @@ sf->prev_frame = prev_sf; rt->current_stack_frame = sf; ctx = p->u.cfunc.realm; /* change the current realm */ - -#ifdef CONFIG_BIGNUM - /* we only propagate the bignum mode as some runtime functions - test it */ - if (prev_sf) - sf->js_mode = prev_sf->js_mode & JS_MODE_MATH; - else - sf->js_mode = 0; -#else sf->js_mode = 0; -#endif - -#if defined(JS_VALUE_CANNOT_BE_CAST) - sf->cur_func = func_obj; +#if defined(_MSC_VER) + sf->cur_func = func_obj; #else - sf->cur_func = (JSValue)func_obj; + sf->cur_func = (JSValue)func_obj; #endif - sf->arg_count = argc; + sf->arg_count = argc; arg_buf = argv; if (unlikely(argc < arg_count)) { @@ -16292,14 +17063,15 @@ OP_SPECIAL_OBJECT_IMPORT_META, } OPSpecialObjectEnum; -#define FUNC_RET_AWAIT 0 -#define FUNC_RET_YIELD 1 -#define FUNC_RET_YIELD_STAR 2 +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 +#define FUNC_RET_INITIAL_YIELD 3 /* argv is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, - int argc, JSValue *argv, int flags) + int argc, JSValue *argv, int in_flags) { JSRuntime *rt = caller_ctx->rt; JSContext *ctx; @@ -16311,6 +17083,11 @@ JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; JSVarRef **var_refs; size_t alloca_size; + JSValue val; + JSAtom atom; + JSValue op1, op2; + int ival; + int iflags; #if !DIRECT_DISPATCH #define SWITCH(pc) switch (opcode = *pc++) @@ -16337,7 +17114,7 @@ if (js_poll_interrupts(caller_ctx)) return JS_EXCEPTION; if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { - if (flags & JS_CALL_FLAG_GENERATOR) { + if (in_flags & JS_CALL_FLAG_GENERATOR) { JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); /* func_obj get contains a pointer to JSFuncAsyncState */ /* the stack frame is already allocated */ @@ -16371,11 +17148,11 @@ return JS_ThrowTypeError(caller_ctx, "not a function"); } return call_func(caller_ctx, func_obj, this_obj, argc, - (JSValueConst *)argv, flags); + (JSValueConst *)argv, in_flags); } b = p->u.func.function_bytecode; - if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + if (unlikely(argc < b->arg_count || (in_flags & JS_CALL_FLAG_COPY_ARGV))) { arg_allocated_size = b->arg_count; } else { arg_allocated_size = 0; @@ -16431,6 +17208,10 @@ *sp++ = JS_NewInt32(ctx, get_u32(pc)); pc += 4; BREAK; + CASE(OP_push_bigint_i32): + *sp++ = __JS_NewShortBigInt(ctx, (int)get_u32(pc)); + pc += 4; + BREAK; CASE(OP_push_const): *sp++ = JS_DupValue(ctx, b->cpoolget_u32(pc)); pc += 4; @@ -16466,17 +17247,6 @@ CASE(OP_push_empty_string): *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); BREAK; - CASE(OP_get_length): - { - JSValue val; - - val = JS_GetProperty(ctx, sp-1, JS_ATOM_length); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp-1); - sp-1 = val; - } - BREAK; #endif CASE(OP_push_atom_value): *sp++ = JS_AtomToValue(ctx, get_u32(pc)); @@ -16491,7 +17261,6 @@ CASE(OP_push_this): /* OP_push_this is only called at the start of a function */ { - JSValue val; if (!(b->js_mode & JS_MODE_STRICT)) { uint32_t tag = JS_VALUE_GET_TAG(this_obj); if (likely(tag == JS_TAG_OBJECT)) @@ -16571,7 +17340,8 @@ { int first = get_u16(pc); pc += 2; - *sp++ = js_build_rest(ctx, first, argc, (JSValueConst *)argv); + first = min_int(first, argc); + *sp++ = js_create_array(ctx, argc - first, (JSValueConst *)(argv + first)); if (unlikely(JS_IsException(sp-1))) goto exception; } @@ -16635,87 +17405,78 @@ BREAK; CASE(OP_perm3): /* obj a b -> a obj b (213) */ { - JSValue tmp; - tmp = sp-2; + val = sp-2; sp-2 = sp-3; - sp-3 = tmp; + sp-3 = val; } BREAK; CASE(OP_rot3l): /* x a b -> a b x (231) */ { - JSValue tmp; - tmp = sp-3; + val = sp-3; sp-3 = sp-2; sp-2 = sp-1; - sp-1 = tmp; + sp-1 = val; } BREAK; CASE(OP_rot4l): /* x a b c -> a b c x */ { - JSValue tmp; - tmp = sp-4; + val = sp-4; sp-4 = sp-3; sp-3 = sp-2; sp-2 = sp-1; - sp-1 = tmp; + sp-1 = val; } BREAK; CASE(OP_rot5l): /* x a b c d -> a b c d x */ { - JSValue tmp; - tmp = sp-5; + val = sp-5; sp-5 = sp-4; sp-4 = sp-3; sp-3 = sp-2; sp-2 = sp-1; - sp-1 = tmp; + sp-1 = val; } BREAK; CASE(OP_rot3r): /* a b x -> x a b (312) */ { - JSValue tmp; - tmp = sp-1; + val = sp-1; sp-1 = sp-2; sp-2 = sp-3; - sp-3 = tmp; + sp-3 = val; } BREAK; CASE(OP_perm4): /* obj prop a b -> a obj prop b */ { - JSValue tmp; - tmp = sp-2; + val = sp-2; sp-2 = sp-3; sp-3 = sp-4; - sp-4 = tmp; + sp-4 = val; } BREAK; CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ { - JSValue tmp; - tmp = sp-2; + val = sp-2; sp-2 = sp-3; sp-3 = sp-4; sp-4 = sp-5; - sp-5 = tmp; + sp-5 = val; } BREAK; CASE(OP_swap): /* a b -> b a */ { - JSValue tmp; - tmp = sp-2; + val = sp-2; sp-2 = sp-1; - sp-1 = tmp; + sp-1 = val; } BREAK; CASE(OP_swap2): /* a b c d -> c d a b */ { - JSValue tmp1, tmp2; - tmp1 = sp-4; - tmp2 = sp-3; + op1 = sp-4; + op2 = sp-3; sp-4 = sp-2; sp-3 = sp-1; - sp-2 = tmp1; - sp-1 = tmp2; + sp-2 = op1; + sp-1 = op2; } BREAK; @@ -16794,27 +17555,13 @@ } BREAK; CASE(OP_array_from): - { - int i, ret; - - call_argc = get_u16(pc); - pc += 2; - ret_val = JS_NewArray(ctx); - if (unlikely(JS_IsException(ret_val))) - goto exception; - call_argv = sp - call_argc; - for(i = 0; i < call_argc; i++) { - ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argvi, - JS_PROP_C_W_E | JS_PROP_THROW); - call_argvi = JS_UNDEFINED; - if (ret < 0) { - JS_FreeValue(ctx, ret_val); - goto exception; - } - } - sp -= call_argc; - *sp++ = ret_val; - } + call_argc = get_u16(pc); + pc += 2; + ret_val = js_create_array_free(ctx, call_argc, sp - call_argc); + sp -= call_argc; + if (unlikely(JS_IsException(ret_val))) + goto exception; + *sp++ = ret_val; BREAK; CASE(OP_apply): @@ -16822,6 +17569,7 @@ int magic; magic = get_u16(pc); pc += 2; + sf->cur_pc = pc; ret_val = js_function_apply(ctx, sp-3, 2, (JSValueConst *)&sp-2, magic); if (unlikely(JS_IsException(ret_val))) @@ -16855,13 +17603,37 @@ BREAK; CASE(OP_check_ctor): if (JS_IsUndefined(new_target)) { + non_ctor_call: JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); goto exception; } BREAK; + CASE(OP_init_ctor): + { + JSValue super, ret; + sf->cur_pc = pc; + if (JS_IsUndefined(new_target)) + goto non_ctor_call; + super = JS_GetPrototype(ctx, func_obj); + if (JS_IsException(super)) + goto exception; + ret = JS_CallConstructor2(ctx, super, new_target, argc, (JSValueConst *)argv); + JS_FreeValue(ctx, super); + if (JS_IsException(ret)) + goto exception; + *sp++ = ret; + } + BREAK; CASE(OP_check_brand): - if (JS_CheckBrand(ctx, sp-2, sp-1) < 0) - goto exception; + { + int ret = JS_CheckBrand(ctx, sp-2, sp-1); + if (ret < 0) + goto exception; + if (!ret) { + JS_ThrowTypeError(ctx, "invalid brand on object"); + goto exception; + } + } BREAK; CASE(OP_add_brand): if (JS_AddBrand(ctx, sp-2, sp-1) < 0) @@ -16882,7 +17654,6 @@ #define JS_THROW_ERROR_DELETE_SUPER 3 #define JS_THROW_ERROR_ITERATOR_THROW 4 { - JSAtom atom; int type; atom = get_u32(pc); type = pc4; @@ -16911,7 +17682,7 @@ JSValueConst obj; int scope_idx; call_argc = get_u16(pc); - scope_idx = get_u16(pc + 2) - 1; + scope_idx = get_u16(pc + 2) + ARG_SCOPE_END; pc += 4; call_argv = sp - call_argc; sf->cur_pc = pc; @@ -16942,8 +17713,9 @@ JSValue *tab; JSValueConst obj; - scope_idx = get_u16(pc) - 1; + scope_idx = get_u16(pc) + ARG_SCOPE_END; pc += 2; + sf->cur_pc = pc; tab = build_arg_list(ctx, &len, sp-1); if (!tab) goto exception; @@ -16970,8 +17742,13 @@ CASE(OP_regexp): { - sp-2 = js_regexp_constructor_internal(ctx, JS_UNDEFINED, - sp-2, sp-1); + JSValue obj; + obj = JS_NewObjectClass(ctx, JS_CLASS_REGEXP); + if (JS_IsException(obj)) + goto exception; + sp-2 = js_regexp_set_internal(ctx, obj, sp-2, sp-1); + if (JS_IsException(sp-2)) + goto exception; sp--; } BREAK; @@ -16979,6 +17756,7 @@ CASE(OP_get_super): { JSValue proto; + sf->cur_pc = pc; proto = JS_GetPrototype(ctx, sp-1); if (JS_IsException(proto)) goto exception; @@ -16989,36 +17767,23 @@ CASE(OP_import): { - JSValue val; - val = js_dynamic_import(ctx, sp-1); + sf->cur_pc = pc; + val = js_dynamic_import(ctx, sp-2, sp-1); if (JS_IsException(val)) goto exception; + JS_FreeValue(ctx, sp-2); JS_FreeValue(ctx, sp-1); + sp--; sp-1 = val; } BREAK; - CASE(OP_check_var): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_CheckGlobalVar(ctx, atom); - if (ret < 0) - goto exception; - *sp++ = JS_NewBool(ctx, ret); - } - BREAK; - CASE(OP_get_var_undef): CASE(OP_get_var): { - JSValue val; - JSAtom atom; atom = get_u32(pc); pc += 4; + sf->cur_pc = pc; val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); if (unlikely(JS_IsException(val))) @@ -17031,9 +17796,9 @@ CASE(OP_put_var_init): { int ret; - JSAtom atom; atom = get_u32(pc); pc += 4; + sf->cur_pc = pc; ret = JS_SetGlobalVar(ctx, atom, sp-1, opcode - OP_put_var); sp--; @@ -17042,55 +17807,33 @@ } BREAK; - CASE(OP_put_var_strict): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - /* sp-2 is JS_TRUE or JS_FALSE */ - if (unlikely(!JS_VALUE_GET_INT(sp-2))) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - goto exception; - } - ret = JS_SetGlobalVar(ctx, atom, sp-1, 2); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - CASE(OP_check_define_var): { - JSAtom atom; - int flags; atom = get_u32(pc); - flags = pc4; + iflags = pc4; pc += 5; - if (JS_CheckDefineGlobalVar(ctx, atom, flags)) + sf->cur_pc = pc; + if (JS_CheckDefineGlobalVar(ctx, atom, iflags)) goto exception; } BREAK; CASE(OP_define_var): { - JSAtom atom; - int flags; atom = get_u32(pc); - flags = pc4; + iflags = pc4; pc += 5; - if (JS_DefineGlobalVar(ctx, atom, flags)) + sf->cur_pc = pc; + if (JS_DefineGlobalVar(ctx, atom, iflags)) goto exception; } BREAK; CASE(OP_define_func): { - JSAtom atom; - int flags; atom = get_u32(pc); - flags = pc4; + iflags = pc4; pc += 5; - if (JS_DefineGlobalFunction(ctx, atom, sp-1, flags)) + sf->cur_pc = pc; + if (JS_DefineGlobalFunction(ctx, atom, sp-1, iflags)) goto exception; JS_FreeValue(ctx, sp-1); sp--; @@ -17196,7 +17939,6 @@ CASE(OP_get_var_ref): { int idx; - JSValue val; idx = get_u16(pc); pc += 2; val = *var_refsidx->pvalue; @@ -17224,7 +17966,6 @@ CASE(OP_get_var_ref_check): { int idx; - JSValue val; idx = get_u16(pc); pc += 2; val = *var_refsidx->pvalue; @@ -17283,6 +18024,19 @@ sp++; } BREAK; + CASE(OP_get_loc_checkthis): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_bufidx))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, FALSE); + goto exception; + } + sp0 = JS_DupValue(ctx, var_bufidx); + sp++; + } + BREAK; CASE(OP_put_loc_check): { int idx; @@ -17314,7 +18068,7 @@ int idx; idx = get_u16(pc); pc += 2; - close_lexical_var(ctx, sf, idx, FALSE); + close_lexical_var(ctx, sf, idx); } BREAK; @@ -17324,7 +18078,6 @@ { JSVarRef *var_ref; JSProperty *pr; - JSAtom atom; int idx; atom = get_u32(pc); idx = get_u16(pc + 4); @@ -17352,9 +18105,9 @@ BREAK; CASE(OP_make_var_ref): { - JSAtom atom; atom = get_u32(pc); pc += 4; + sf->cur_pc = pc; if (JS_GetGlobalVarRef(ctx, atom, sp)) goto exception; @@ -17382,7 +18135,6 @@ CASE(OP_if_true): { int res; - JSValue op1; op1 = sp-1; pc += 4; @@ -17402,10 +18154,10 @@ CASE(OP_if_false): { int res; - JSValue op1; op1 = sp-1; pc += 4; + /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and JS_TAG_UNDEFINED */ if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { res = JS_VALUE_GET_INT(op1); } else { @@ -17423,7 +18175,6 @@ CASE(OP_if_true8): { int res; - JSValue op1; op1 = sp-1; pc += 1; @@ -17443,7 +18194,6 @@ CASE(OP_if_false8): { int res; - JSValue op1; op1 = sp-1; pc += 1; @@ -17482,7 +18232,6 @@ BREAK; CASE(OP_ret): { - JSValue op1; uint32_t pos; op1 = sp-1; if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) @@ -17499,15 +18248,18 @@ BREAK; CASE(OP_for_in_start): + sf->cur_pc = pc; if (js_for_in_start(ctx, sp)) goto exception; BREAK; CASE(OP_for_in_next): + sf->cur_pc = pc; if (js_for_in_next(ctx, sp)) goto exception; sp += 2; BREAK; CASE(OP_for_of_start): + sf->cur_pc = pc; if (js_for_of_start(ctx, sp, FALSE)) goto exception; sp += 1; @@ -17517,18 +18269,27 @@ { int offset = -3 - pc0; pc += 1; + sf->cur_pc = pc; if (js_for_of_next(ctx, sp, offset)) goto exception; sp += 2; } BREAK; + CASE(OP_for_await_of_next): + sf->cur_pc = pc; + if (js_for_await_of_next(ctx, sp)) + goto exception; + sp++; + BREAK; CASE(OP_for_await_of_start): + sf->cur_pc = pc; if (js_for_of_start(ctx, sp, TRUE)) goto exception; sp += 1; *sp++ = JS_NewCatchOffset(ctx, 0); BREAK; CASE(OP_iterator_get_value_done): + sf->cur_pc = pc; if (js_iterator_get_value_done(ctx, sp)) goto exception; sp += 1; @@ -17546,32 +18307,28 @@ JS_FreeValue(ctx, sp-1); /* drop the next method */ sp--; if (!JS_IsUndefined(sp-1)) { + sf->cur_pc = pc; if (JS_IteratorClose(ctx, sp-1, FALSE)) goto exception; JS_FreeValue(ctx, sp-1); } sp--; BREAK; - CASE(OP_iterator_close_return): + CASE(OP_nip_catch): { JSValue ret_val; - /* iter_obj next catch_offset ... ret_val -> - ret_eval iter_obj next catch_offset */ + /* catch_offset ... ret_val -> ret_eval */ ret_val = *--sp; while (sp > stack_buf && JS_VALUE_GET_TAG(sp-1) != JS_TAG_CATCH_OFFSET) { JS_FreeValue(ctx, *--sp); } - if (unlikely(sp < stack_buf + 3)) { - JS_ThrowInternalError(ctx, "iterator_close_return"); + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); JS_FreeValue(ctx, ret_val); goto exception; } - sp0 = sp-1; - sp-1 = sp-2; - sp-2 = sp-3; - sp-3 = ret_val; - sp++; + sp-1 = ret_val; } BREAK; @@ -17579,6 +18336,7 @@ /* stack: iter_obj next catch_offset val */ { JSValue ret; + sf->cur_pc = pc; ret = JS_Call(ctx, sp-3, sp-4, 1, (JSValueConst *)(sp - 1)); if (JS_IsException(ret)) @@ -17593,16 +18351,16 @@ { JSValue method, ret; BOOL ret_flag; - int flags; - flags = *pc++; - method = JS_GetProperty(ctx, sp-4, (flags & 1) ? + iflags = *pc++; + sf->cur_pc = pc; + method = JS_GetProperty(ctx, sp-4, (iflags & 1) ? JS_ATOM_throw : JS_ATOM_return); if (JS_IsException(method)) goto exception; if (JS_IsUndefined(method) || JS_IsNull(method)) { ret_flag = TRUE; } else { - if (flags & 2) { + if (iflags & 2) { /* no argument */ ret = JS_CallFree(ctx, method, sp-4, 0, NULL); @@ -17624,7 +18382,6 @@ CASE(OP_lnot): { int res; - JSValue op1; op1 = sp-1; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { @@ -17636,48 +18393,114 @@ } BREAK; - CASE(OP_get_field): - { - JSValue val; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - val = JS_GetProperty(ctx, sp-1, atom); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp-1); - sp-1 = val; +#define GET_FIELD_INLINE(name, keep, is_length) \ + { \ + JSValue val, obj; \ + JSAtom atom; \ + JSObject *p; \ + JSProperty *pr; \ + JSShapeProperty *prs; \ + \ + if (is_length) { \ + atom = JS_ATOM_length; \ + } else { \ + atom = get_u32(pc); \ + pc += 4; \ + } \ + \ + obj = sp-1; \ + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { \ + p = JS_VALUE_GET_OBJ(obj); \ + for(;;) { \ + prs = find_own_property(&pr, p, atom); \ + if (prs) { \ + /* found */ \ + if (unlikely(prs->flags & JS_PROP_TMASK)) \ + goto name ## _slow_path; \ + val = JS_DupValue(ctx, pr->u.value); \ + break; \ + } \ + if (unlikely(p->is_exotic)) { \ + /* XXX: should avoid the slow path for arrays \ + and typed arrays by ensuring that 'prop' is \ + not numeric */ \ + obj = JS_MKPTR(JS_TAG_OBJECT, p); \ + goto name ## _slow_path; \ + } \ + p = p->shape->proto; \ + if (!p) { \ + val = JS_UNDEFINED; \ + break; \ + } \ + } \ + } else { \ + name ## _slow_path: \ + sf->cur_pc = pc; \ + val = JS_GetPropertyInternal(ctx, obj, atom, sp-1, 0); \ + if (unlikely(JS_IsException(val))) \ + goto exception; \ + } \ + if (keep) { \ + *sp++ = val; \ + } else { \ + JS_FreeValue(ctx, sp-1); \ + sp-1 = val; \ + } \ } + + + CASE(OP_get_field): + GET_FIELD_INLINE(get_field, 0, 0); BREAK; CASE(OP_get_field2): - { - JSValue val; - JSAtom atom; - atom = get_u32(pc); - pc += 4; + GET_FIELD_INLINE(get_field2, 1, 0); + BREAK; - val = JS_GetProperty(ctx, sp-1, atom); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - } +#if SHORT_OPCODES + CASE(OP_get_length): + GET_FIELD_INLINE(get_length, 0, 1); BREAK; +#endif CASE(OP_put_field): { int ret; + JSValue obj; JSAtom atom; + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + atom = get_u32(pc); pc += 4; - ret = JS_SetPropertyInternal(ctx, sp-2, atom, sp-1, - JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp-2); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; + obj = sp-2; + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, atom); + if (!prs) + goto put_field_slow_path; + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast path */ + set_value(ctx, &pr->u.value, sp-1); + } else { + goto put_field_slow_path; + } + JS_FreeValue(ctx, obj); + sp -= 2; + } else { + put_field_slow_path: + sf->cur_pc = pc; + ret = JS_SetPropertyInternal(ctx, obj, atom, sp-1, obj, + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, obj); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + } BREAK; @@ -17697,7 +18520,6 @@ CASE(OP_get_private_field): { - JSValue val; val = JS_GetPrivateField(ctx, sp-2, sp-1); JS_FreeValue(ctx, sp-1); @@ -17735,7 +18557,6 @@ CASE(OP_define_field): { int ret; - JSAtom atom; atom = get_u32(pc); pc += 4; @@ -17750,7 +18571,6 @@ CASE(OP_set_name): { int ret; - JSAtom atom; atom = get_u32(pc); pc += 4; @@ -17770,6 +18590,7 @@ CASE(OP_set_proto): { JSValue proto; + sf->cur_pc = pc; proto = sp-1; if (JS_IsObject(proto) || JS_IsNull(proto)) { if (JS_SetPrototypeInternal(ctx, sp-2, proto, TRUE) < 0) @@ -17787,8 +18608,7 @@ { JSValue getter, setter, value; JSValueConst obj; - JSAtom atom; - int flags, ret, op_flags; + int ret, op_flags; BOOL is_computed; #define OP_DEFINE_METHOD_METHOD 0 #define OP_DEFINE_METHOD_GETTER 1 @@ -17808,28 +18628,28 @@ op_flags = *pc++; obj = sp-2 - is_computed; - flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + iflags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) - flags |= JS_PROP_ENUMERABLE; + iflags |= JS_PROP_ENUMERABLE; op_flags &= 3; value = JS_UNDEFINED; getter = JS_UNDEFINED; setter = JS_UNDEFINED; if (op_flags == OP_DEFINE_METHOD_METHOD) { value = sp-1; - flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + iflags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; } else if (op_flags == OP_DEFINE_METHOD_GETTER) { getter = sp-1; - flags |= JS_PROP_HAS_GET; + iflags |= JS_PROP_HAS_GET; } else { setter = sp-1; - flags |= JS_PROP_HAS_SET; + iflags |= JS_PROP_HAS_SET; } - ret = js_method_set_properties(ctx, sp-1, atom, flags, obj); + ret = js_method_set_properties(ctx, sp-1, atom, iflags, obj); if (ret >= 0) { ret = JS_DefineProperty(ctx, obj, atom, value, - getter, setter, flags); + getter, setter, iflags); } JS_FreeValue(ctx, sp-1); if (is_computed) { @@ -17845,56 +18665,140 @@ CASE(OP_define_class): CASE(OP_define_class_computed): { - int class_flags; JSAtom atom; atom = get_u32(pc); - class_flags = pc4; + iflags = pc4; pc += 5; - if (js_op_define_class(ctx, sp, atom, class_flags, + if (js_op_define_class(ctx, sp, atom, iflags, var_refs, sf, (opcode == OP_define_class_computed)) < 0) goto exception; } BREAK; - CASE(OP_get_array_el): - { - JSValue val; - - val = JS_GetPropertyValue(ctx, sp-2, sp-1); - JS_FreeValue(ctx, sp-2); - sp-2 = val; - sp--; - if (unlikely(JS_IsException(val))) - goto exception; +#define GET_ARRAY_EL_INLINE(name, keep) \ + { \ + JSValue val, obj, prop; \ + JSObject *p; \ + uint32_t idx; \ + \ + obj = sp-2; \ + prop = sp-1; \ + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && \ + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { \ + p = JS_VALUE_GET_OBJ(obj); \ + idx = JS_VALUE_GET_INT(prop); \ + if (unlikely(p->class_id != JS_CLASS_ARRAY)) \ + goto name ## _slow_path; \ + if (unlikely(idx >= p->u.array.count)) \ + goto name ## _slow_path; \ + val = JS_DupValue(ctx, p->u.array.u.valuesidx); \ + } else { \ + name ## _slow_path: \ + sf->cur_pc = pc; \ + val = JS_GetPropertyValue(ctx, obj, prop); \ + if (unlikely(JS_IsException(val))) { \ + if (keep) \ + sp-1 = JS_UNDEFINED; \ + else \ + sp--; \ + goto exception; \ + } \ + } \ + if (keep) { \ + sp-1 = val; \ + } else { \ + JS_FreeValue(ctx, obj); \ + sp-2 = val; \ + sp--; \ + } \ } + + CASE(OP_get_array_el): + GET_ARRAY_EL_INLINE(get_array_el, 0); BREAK; CASE(OP_get_array_el2): + GET_ARRAY_EL_INLINE(get_array_el2, 1); + BREAK; + + CASE(OP_get_array_el3): { JSValue val; + JSObject *p; + uint32_t idx; - val = JS_GetPropertyValue(ctx, sp-2, sp-1); - sp-1 = val; - if (unlikely(JS_IsException(val))) - goto exception; + if (likely(JS_VALUE_GET_TAG(sp-2) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(sp-1) == JS_TAG_INT)) { + p = JS_VALUE_GET_OBJ(sp-2); + idx = JS_VALUE_GET_INT(sp-1); + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto get_array_el3_slow_path; + if (unlikely(idx >= p->u.array.count)) + goto get_array_el3_slow_path; + val = JS_DupValue(ctx, p->u.array.u.valuesidx); + } else { + get_array_el3_slow_path: + switch (JS_VALUE_GET_TAG(sp-1)) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + /* undefined and null are tested in JS_GetPropertyValue() */ + break; + default: + /* must be tested before JS_ToPropertyKey */ + if (unlikely(JS_IsUndefined(sp-2) || JS_IsNull(sp-2))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp-1); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp-1); + sp-1 = ret_val; + break; + } + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp-2, JS_DupValue(ctx, sp-1)); + if (unlikely(JS_IsException(val))) + goto exception; + } + *sp++ = val; } BREAK; CASE(OP_get_ref_value): { - JSValue val; + JSAtom atom; + int ret; + + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp-1); + if (atom == JS_ATOM_NULL) + goto exception; if (unlikely(JS_IsUndefined(sp-2))) { - JSAtom atom = JS_ValueToAtom(ctx, sp-1); - if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + ret = JS_HasProperty(ctx, sp-2, atom); + if (unlikely(ret <= 0)) { + if (ret < 0) { + JS_FreeAtom(ctx, atom); + goto exception; + } + if (is_strict_mode(ctx)) { JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); + goto exception; } - goto exception; + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, sp-2, atom); } - val = JS_GetPropertyValue(ctx, sp-2, - JS_DupValue(ctx, sp-1)); + JS_FreeAtom(ctx, atom); if (unlikely(JS_IsException(val))) goto exception; sp0 = val; @@ -17904,8 +18808,9 @@ CASE(OP_get_super_value): { - JSValue val; + //JSValue val; JSAtom atom; + sf->cur_pc = pc; atom = JS_ValueToAtom(ctx, sp-1); if (unlikely(atom == JS_ATOM_NULL)) goto exception; @@ -17924,35 +18829,87 @@ CASE(OP_put_array_el): { int ret; + JSObject *p; + uint32_t idx; - ret = JS_SetPropertyValue(ctx, sp-3, sp-2, sp-1, JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp-3); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; + if (likely(JS_VALUE_GET_TAG(sp-3) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(sp-2) == JS_TAG_INT)) { + p = JS_VALUE_GET_OBJ(sp-3); + idx = JS_VALUE_GET_INT(sp-2); + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto put_array_el_slow_path; + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + uint32_t new_len, array_len; + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_ARRAY) || + !ctx->std_array_prototype)) { + goto put_array_el_slow_path; + } + if (likely(JS_VALUE_GET_TAG(p->prop0.u.value) != JS_TAG_INT)) + goto put_array_el_slow_path; + /* cannot overflow otherwise the length would not be an integer */ + new_len = idx + 1; + if (unlikely(new_len > p->u.array.u1.size)) + goto put_array_el_slow_path; + array_len = JS_VALUE_GET_INT(p->prop0.u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) + goto put_array_el_slow_path; + p->prop0.u.value = JS_NewInt32(ctx, new_len); + } + p->u.array.count = new_len; + p->u.array.u.valuesidx = sp-1; + } else { + set_value(ctx, &p->u.array.u.valuesidx, sp-1); + } + JS_FreeValue(ctx, sp-3); + sp -= 3; + } else { + put_array_el_slow_path: + sf->cur_pc = pc; + ret = JS_SetPropertyValue(ctx, sp-3, sp-2, sp-1, JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp-3); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } } BREAK; CASE(OP_put_ref_value): { - int ret, flags; - flags = JS_PROP_THROW_STRICT; + int ret; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp-2); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; if (unlikely(JS_IsUndefined(sp-3))) { if (is_strict_mode(ctx)) { - JSAtom atom = JS_ValueToAtom(ctx, sp-2); - if (atom != JS_ATOM_NULL) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - JS_FreeAtom(ctx, atom); - } + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); goto exception; } else { sp-3 = JS_DupValue(ctx, ctx->global_obj); } - } else { - if (is_strict_mode(ctx)) - flags |= JS_PROP_NO_ADD; } - ret = JS_SetPropertyValue(ctx, sp-3, sp-2, sp-1, flags); + ret = JS_HasProperty(ctx, sp-3, atom); + if (unlikely(ret <= 0)) { + if (unlikely(ret < 0)) { + JS_FreeAtom(ctx, atom); + goto exception; + } + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + } + ret = JS_SetPropertyInternal(ctx, sp-3, atom, sp-1, sp-3, JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp-2); JS_FreeValue(ctx, sp-3); sp -= 3; if (unlikely(ret < 0)) @@ -17964,6 +18921,7 @@ { int ret; JSAtom atom; + sf->cur_pc = pc; if (JS_VALUE_GET_TAG(sp-3) != JS_TAG_OBJECT) { JS_ThrowTypeErrorNotAnObject(ctx); goto exception; @@ -17971,8 +18929,8 @@ atom = JS_ValueToAtom(ctx, sp-2); if (unlikely(atom == JS_ATOM_NULL)) goto exception; - ret = JS_SetPropertyGeneric(ctx, sp-3, atom, sp-1, sp-4, - JS_PROP_THROW_STRICT); + ret = JS_SetPropertyInternal(ctx, sp-3, atom, sp-1, sp-4, + JS_PROP_THROW_STRICT); JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, sp-4); JS_FreeValue(ctx, sp-3); @@ -17996,6 +18954,7 @@ CASE(OP_append): /* array pos enumobj -- array pos */ { + sf->cur_pc = pc; if (js_append_enumerate(ctx, sp)) goto exception; JS_FreeValue(ctx, *--sp); @@ -18011,6 +18970,7 @@ int mask; mask = *pc++; + sf->cur_pc = pc; if (JS_CopyDataProperties(ctx, sp-1 - (mask & 3), sp-1 - ((mask >> 2) & 7), sp-1 - ((mask >> 5) & 7), 0)) @@ -18020,7 +18980,6 @@ CASE(OP_add): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { @@ -18034,8 +18993,14 @@ sp-2 = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + JS_VALUE_GET_FLOAT64(op2)); sp--; + } else if (JS_IsString(op1) && JS_IsString(op2)) { + sp-2 = JS_ConcatString(ctx, op1, op2); + sp--; + if (JS_IsException(sp-1)) + goto exception; } else { add_slow: + sf->cur_pc = pc; if (js_add_slow(ctx, sp)) goto exception; sp--; @@ -18044,38 +19009,45 @@ BREAK; CASE(OP_add_loc): { + JSValue op2; JSValue *pv; int idx; idx = *pc; pc += 1; + op2 = sp-1; pv = &var_bufidx; - if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp-1))) { + if (likely(JS_VALUE_IS_BOTH_INT(*pv, op2))) { int64_t r; - r = (int64_t)JS_VALUE_GET_INT(*pv) + - JS_VALUE_GET_INT(sp-1); + r = (int64_t)JS_VALUE_GET_INT(*pv) + JS_VALUE_GET_INT(op2); if (unlikely((int)r != r)) goto add_loc_slow; *pv = JS_NewInt32(ctx, r); sp--; - } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { - JSValue op1; - op1 = sp-1; + } else if (JS_VALUE_IS_BOTH_FLOAT(*pv, op2)) { + *pv = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(*pv) + + JS_VALUE_GET_FLOAT64(op2)); sp--; - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) - goto exception; - op1 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op1); - if (JS_IsException(op1)) - goto exception; - set_value(ctx, pv, op1); + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING && + JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + sp--; + sf->cur_pc = pc; + if (JS_ConcatStringInPlace(ctx, JS_VALUE_GET_STRING(*pv), op2)) { + JS_FreeValue(ctx, op2); + } else { + op2 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op2); + if (JS_IsException(op2)) + goto exception; + set_value(ctx, pv, op2); + } } else { JSValue ops2; add_loc_slow: /* In case of exception, js_add_slow frees ops0 and ops1, so we must duplicate *pv */ + sf->cur_pc = pc; ops0 = JS_DupValue(ctx, *pv); - ops1 = sp-1; + ops1 = op2; sp--; if (js_add_slow(ctx, ops + 2)) goto exception; @@ -18085,7 +19057,6 @@ BREAK; CASE(OP_sub): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { @@ -18106,7 +19077,6 @@ BREAK; CASE(OP_mul): { - JSValue op1, op2; double d; op1 = sp-2; op2 = sp-1; @@ -18117,11 +19087,6 @@ v2 = JS_VALUE_GET_INT(op2); r = (int64_t)v1 * v2; if (unlikely((int)r != r)) { -#ifdef CONFIG_BIGNUM - if (unlikely(sf->js_mode & JS_MODE_MATH) && - (r < -MAX_SAFE_INTEGER || r > MAX_SAFE_INTEGER)) - goto binary_arith_slow; -#endif d = (double)r; goto mul_fp_res; } @@ -18133,10 +19098,6 @@ sp-2 = JS_NewInt32(ctx, r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { -#ifdef CONFIG_BIGNUM - if (unlikely(sf->js_mode & JS_MODE_MATH)) - goto binary_arith_slow; -#endif d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); mul_fp_res: sp-2 = __JS_NewFloat64(ctx, d); @@ -18148,13 +19109,10 @@ BREAK; CASE(OP_div): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int v1, v2; - if (unlikely(sf->js_mode & JS_MODE_MATH)) - goto binary_arith_slow; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); sp-2 = JS_NewFloat64(ctx, (double)v1 / (double)v2); @@ -18165,11 +19123,7 @@ } BREAK; CASE(OP_mod): -#ifdef CONFIG_BIGNUM - CASE(OP_math_mod): -#endif { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { @@ -18190,6 +19144,7 @@ BREAK; CASE(OP_pow): binary_arith_slow: + sf->cur_pc = pc; if (js_binary_arith_slow(ctx, sp, opcode)) goto exception; sp--; @@ -18197,12 +19152,12 @@ CASE(OP_plus): { - JSValue op1; uint32_t tag; op1 = sp-1; tag = JS_VALUE_GET_TAG(op1); if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { } else { + sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } @@ -18210,29 +19165,28 @@ BREAK; CASE(OP_neg): { - JSValue op1; uint32_t tag; - int val; double d; op1 = sp-1; tag = JS_VALUE_GET_TAG(op1); if (tag == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); + ival = JS_VALUE_GET_INT(op1); /* Note: -0 cannot be expressed as integer */ - if (unlikely(val == 0)) { + if (unlikely(ival == 0)) { d = -0.0; goto neg_fp_res; } - if (unlikely(val == INT32_MIN)) { - d = -(double)val; + if (unlikely(ival == INT32_MIN)) { + d = -(double)ival; goto neg_fp_res; } - sp-1 = JS_NewInt32(ctx, -val); + sp-1 = JS_NewInt32(ctx, -ival); } else if (JS_TAG_IS_FLOAT64(tag)) { d = -JS_VALUE_GET_FLOAT64(op1); neg_fp_res: sp-1 = __JS_NewFloat64(ctx, d); } else { + sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } @@ -18240,16 +19194,15 @@ BREAK; CASE(OP_inc): { - JSValue op1; - int val; op1 = sp-1; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MAX)) + ival = JS_VALUE_GET_INT(op1); + if (unlikely(ival == INT32_MAX)) goto inc_slow; - sp-1 = JS_NewInt32(ctx, val + 1); + sp-1 = JS_NewInt32(ctx, ival + 1); } else { inc_slow: + sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } @@ -18257,43 +19210,73 @@ BREAK; CASE(OP_dec): { - JSValue op1; - int val; op1 = sp-1; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MIN)) + ival = JS_VALUE_GET_INT(op1); + if (unlikely(ival == INT32_MIN)) goto dec_slow; - sp-1 = JS_NewInt32(ctx, val - 1); + sp-1 = JS_NewInt32(ctx, ival - 1); } else { dec_slow: + sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } } BREAK; CASE(OP_post_inc): - CASE(OP_post_dec): - if (js_post_inc_slow(ctx, sp, opcode)) - goto exception; - sp++; + { + JSValue op1; + int val; + op1 = sp-1; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto post_inc_slow; + sp0 = JS_NewInt32(ctx, val + 1); + } else { + post_inc_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } BREAK; - CASE(OP_inc_loc): + CASE(OP_post_dec): { JSValue op1; int val; + op1 = sp-1; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto post_dec_slow; + sp0 = JS_NewInt32(ctx, val - 1); + } else { + post_dec_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } + BREAK; + CASE(OP_inc_loc): + { int idx; idx = *pc; pc += 1; op1 = var_bufidx; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MAX)) + ival = JS_VALUE_GET_INT(op1); + if (unlikely(ival == INT32_MAX)) goto inc_loc_slow; - var_bufidx = JS_NewInt32(ctx, val + 1); + var_bufidx = JS_NewInt32(ctx, ival + 1); } else { inc_loc_slow: + sf->cur_pc = pc; /* must duplicate otherwise the variable value may be destroyed before JS code accesses it */ op1 = JS_DupValue(ctx, op1); @@ -18305,20 +19288,19 @@ BREAK; CASE(OP_dec_loc): { - JSValue op1; - int val; int idx; idx = *pc; pc += 1; op1 = var_bufidx; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MIN)) + ival = JS_VALUE_GET_INT(op1); + if (unlikely(ival == INT32_MIN)) goto dec_loc_slow; - var_bufidx = JS_NewInt32(ctx, val - 1); + var_bufidx = JS_NewInt32(ctx, ival - 1); } else { dec_loc_slow: + sf->cur_pc = pc; /* must duplicate otherwise the variable value may be destroyed before JS code accesses it */ op1 = JS_DupValue(ctx, op1); @@ -18330,11 +19312,11 @@ BREAK; CASE(OP_not): { - JSValue op1; op1 = sp-1; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { sp-1 = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1)); } else { + sf->cur_pc = pc; if (js_not_slow(ctx, sp)) goto exception; } @@ -18343,35 +19325,17 @@ CASE(OP_shl): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { uint32_t v1, v2; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); -#ifdef CONFIG_BIGNUM - { - int64_t r; - if (unlikely(sf->js_mode & JS_MODE_MATH)) { - if (v2 > 0x1f) - goto shl_slow; - r = (int64_t)v1 << v2; - if ((int)r != r) - goto shl_slow; - } else { - v2 &= 0x1f; - } - } -#else v2 &= 0x1f; -#endif sp-2 = JS_NewInt32(ctx, v1 << v2); sp--; } else { -#ifdef CONFIG_BIGNUM - shl_slow: -#endif + sf->cur_pc = pc; if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; sp--; @@ -18380,19 +19344,18 @@ BREAK; CASE(OP_shr): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { uint32_t v2; v2 = JS_VALUE_GET_INT(op2); - /* v1 >>> v2 retains its JS semantics if CONFIG_BIGNUM */ v2 &= 0x1f; sp-2 = JS_NewUint32(ctx, (uint32_t)JS_VALUE_GET_INT(op1) >> v2); sp--; } else { + sf->cur_pc = pc; if (js_shr_slow(ctx, sp)) goto exception; sp--; @@ -18401,29 +19364,17 @@ BREAK; CASE(OP_sar): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { uint32_t v2; v2 = JS_VALUE_GET_INT(op2); -#ifdef CONFIG_BIGNUM - if (unlikely(v2 > 0x1f)) { - if (unlikely(sf->js_mode & JS_MODE_MATH)) - goto sar_slow; - else - v2 &= 0x1f; - } -#else v2 &= 0x1f; -#endif sp-2 = JS_NewInt32(ctx, (int)JS_VALUE_GET_INT(op1) >> v2); sp--; } else { -#ifdef CONFIG_BIGNUM - sar_slow: -#endif + sf->cur_pc = pc; if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; sp--; @@ -18432,7 +19383,6 @@ BREAK; CASE(OP_and): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { @@ -18441,6 +19391,7 @@ JS_VALUE_GET_INT(op2)); sp--; } else { + sf->cur_pc = pc; if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; sp--; @@ -18449,7 +19400,6 @@ BREAK; CASE(OP_or): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { @@ -18458,6 +19408,7 @@ JS_VALUE_GET_INT(op2)); sp--; } else { + sf->cur_pc = pc; if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; sp--; @@ -18466,7 +19417,6 @@ BREAK; CASE(OP_xor): { - JSValue op1, op2; op1 = sp-2; op2 = sp-1; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { @@ -18475,6 +19425,7 @@ JS_VALUE_GET_INT(op2)); sp--; } else { + sf->cur_pc = pc; if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; sp--; @@ -18486,13 +19437,13 @@ #define OP_CMP(opcode, binary_op, slow_call) \ CASE(opcode): \ { \ - JSValue op1, op2; \ op1 = sp-2; \ op2 = sp-1; \ if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ sp-2 = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ sp--; \ } else { \ + sf->cur_pc = pc; \ if (slow_call) \ goto exception; \ sp--; \ @@ -18509,27 +19460,26 @@ OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); -#ifdef CONFIG_BIGNUM - CASE(OP_mul_pow10): - if (rt->bigfloat_ops.mul_pow10(ctx, sp)) + CASE(OP_in): + sf->cur_pc = pc; + if (js_operator_in(ctx, sp)) goto exception; sp--; BREAK; -#endif - CASE(OP_in): - if (js_operator_in(ctx, sp)) + CASE(OP_private_in): + sf->cur_pc = pc; + if (js_operator_private_in(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_instanceof): + sf->cur_pc = pc; if (js_operator_instanceof(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_typeof): { - JSValue op1; - JSAtom atom; op1 = sp-1; atom = js_operator_typeof(ctx, op1); @@ -18538,19 +19488,20 @@ } BREAK; CASE(OP_delete): + sf->cur_pc = pc; if (js_operator_delete(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_delete_var): { - JSAtom atom; int ret; atom = get_u32(pc); pc += 4; + sf->cur_pc = pc; - ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0); + ret = JS_DeleteGlobalVar(ctx, atom); if (unlikely(ret < 0)) goto exception; *sp++ = JS_NewBool(ctx, ret); @@ -18559,6 +19510,7 @@ CASE(OP_to_object): if (JS_VALUE_GET_TAG(sp-1) != JS_TAG_OBJECT) { + sf->cur_pc = pc; ret_val = JS_ToObject(ctx, sp-1); if (JS_IsException(ret_val)) goto exception; @@ -18574,6 +19526,7 @@ case JS_TAG_SYMBOL: break; default: + sf->cur_pc = pc; ret_val = JS_ToPropertyKey(ctx, sp-1); if (JS_IsException(ret_val)) goto exception; @@ -18583,26 +19536,6 @@ } BREAK; - CASE(OP_to_propkey2): - /* must be tested first */ - if (unlikely(JS_IsUndefined(sp-2) || JS_IsNull(sp-2))) { - JS_ThrowTypeError(ctx, "value has no property"); - goto exception; - } - switch (JS_VALUE_GET_TAG(sp-1)) { - case JS_TAG_INT: - case JS_TAG_STRING: - case JS_TAG_SYMBOL: - break; - default: - ret_val = JS_ToPropertyKey(ctx, sp-1); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp-1); - sp-1 = ret_val; - break; - } - BREAK; #if 0 CASE(OP_to_string): if (JS_VALUE_GET_TAG(sp-1) != JS_TAG_STRING) { @@ -18619,16 +19552,15 @@ CASE(OP_with_delete_var): CASE(OP_with_make_ref): CASE(OP_with_get_ref): - CASE(OP_with_get_ref_undef): { - JSAtom atom; int32_t diff; - JSValue obj, val; + JSValue obj; int ret, is_with; atom = get_u32(pc); diff = get_u32(pc + 4); is_with = pc8; pc += 9; + sf->cur_pc = pc; obj = sp-1; ret = JS_HasProperty(ctx, obj, atom); @@ -18644,14 +19576,35 @@ } switch (opcode) { case OP_with_get_var: - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; + /* in Object Environment Records, GetBindingValue() calls HasProperty() */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret <= 0)) { + if (ret < 0) + goto exception; + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + } set_value(ctx, &sp-1, val); break; - case OP_with_put_var: - /* XXX: check if strict mode */ - ret = JS_SetPropertyInternal(ctx, obj, atom, sp-2, + case OP_with_put_var: /* used e.g. in for in/of */ + /* in Object Environment Records, SetMutableBinding() calls HasProperty() */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret <= 0)) { + if (ret < 0) + goto exception; + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + } + ret = JS_SetPropertyInternal(ctx, obj, atom, sp-2, obj, JS_PROP_THROW_STRICT); JS_FreeValue(ctx, sp-1); sp -= 2; @@ -18671,18 +19624,17 @@ break; case OP_with_get_ref: /* produce a pair object/method on the stack */ - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - break; - case OP_with_get_ref_undef: - /* produce a pair undefined/function on the stack */ - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) + /* in Object Environment Records, GetBindingValue() calls HasProperty() */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) goto exception; - JS_FreeValue(ctx, sp-1); - sp-1 = JS_UNDEFINED; + if (!ret) { + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + } *sp++ = val; break; } @@ -18707,9 +19659,11 @@ ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD_STAR); goto done_generator; CASE(OP_return_async): - CASE(OP_initial_yield): ret_val = JS_UNDEFINED; goto done_generator; + CASE(OP_initial_yield): + ret_val = JS_NewInt32(ctx, FUNC_RET_INITIAL_YIELD); + goto done_generator; CASE(OP_nop): BREAK; @@ -18770,11 +19724,11 @@ before if the exception happens in a bytecode operation */ sf->cur_pc = pc; - build_backtrace(ctx, rt->current_exception, NULL, 0, 0); + build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0); } - if (!JS_IsUncatchableError(ctx, rt->current_exception)) { + if (!rt->current_exception_is_uncatchable) { while (sp > stack_buf) { - JSValue val = *--sp; + val = *--sp; JS_FreeValue(ctx, val); if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { int pos = JS_VALUE_GET_INT(val); @@ -18785,7 +19739,7 @@ JS_IteratorClose(ctx, sp-1, TRUE); } else { *sp++ = rt->current_exception; - rt->current_exception = JS_NULL; + rt->current_exception = JS_UNINITIALIZED; pc = b->byte_code_buf + pos; goto restart; } @@ -18991,26 +19945,35 @@ } /* JSAsyncFunctionState (used by generator and async functions) */ -static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, - JSValueConst func_obj, JSValueConst this_obj, - int argc, JSValueConst *argv) +static JSAsyncFunctionState *async_func_init(JSContext *ctx, + JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) { + JSAsyncFunctionState *s; JSObject *p; JSFunctionBytecode *b; JSStackFrame *sf; int local_count, i, arg_buf_len, n; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return NULL; + s->header.ref_count = 1; + add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + sf = &s->frame; init_list_head(&sf->var_ref_list); p = JS_VALUE_GET_OBJ(func_obj); b = p->u.func.function_bytecode; - sf->js_mode = b->js_mode; + sf->js_mode = b->js_mode | JS_MODE_ASYNC; sf->cur_pc = b->byte_code_buf; arg_buf_len = max_int(b->arg_count, argc); local_count = arg_buf_len + b->var_count + b->stack_size; sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); - if (!sf->arg_buf) - return -1; + if (!sf->arg_buf) { + js_free(ctx, s); + return NULL; + } sf->cur_func = JS_DupValue(ctx, func_obj); s->this_val = JS_DupValue(ctx, this_obj); s->argc = argc; @@ -19022,38 +19985,17 @@ n = arg_buf_len + b->var_count; for(i = argc; i < n; i++) sf->arg_bufi = JS_UNDEFINED; - return 0; -} - -static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, - JS_MarkFunc *mark_func) -{ - JSStackFrame *sf; - JSValue *sp; - - sf = &s->frame; - JS_MarkValue(rt, sf->cur_func, mark_func); - JS_MarkValue(rt, s->this_val, mark_func); - if (sf->cur_sp) { - /* if the function is running, cur_sp is not known so we - cannot mark the stack. Marking the variables is not needed - because a running function cannot be part of a removable - cycle */ - for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) - JS_MarkValue(rt, *sp, mark_func); - } + s->resolving_funcs0 = JS_UNDEFINED; + s->resolving_funcs1 = JS_UNDEFINED; + s->is_completed = FALSE; + return s; } -static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +static void async_func_free_frame(JSRuntime *rt, JSAsyncFunctionState *s) { - JSStackFrame *sf; + JSStackFrame *sf = &s->frame; JSValue *sp; - sf = &s->frame; - - /* close the closure variables. */ - close_var_refs(rt, sf); - if (sf->arg_buf) { /* cannot free the function if it is running */ assert(sf->cur_sp != NULL); @@ -19061,6 +20003,7 @@ JS_FreeValueRT(rt, *sp); } js_free_rt(rt, sf->arg_buf); + sf->arg_buf = NULL; } JS_FreeValueRT(rt, sf->cur_func); JS_FreeValueRT(rt, s->this_val); @@ -19068,17 +20011,66 @@ static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) { - JSValue func_obj; + JSRuntime *rt = ctx->rt; + JSStackFrame *sf = &s->frame; + JSValue func_obj, ret; - if (js_check_stack_overflow(ctx->rt, 0)) - return JS_ThrowStackOverflow(ctx); + assert(!s->is_completed); + if (js_check_stack_overflow(ctx->rt, 0)) { + ret = JS_ThrowStackOverflow(ctx); + } else { + /* the tag does not matter provided it is not an object */ + func_obj = JS_MKPTR(JS_TAG_INT, s); + ret = JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, + s->argc, sf->arg_buf, JS_CALL_FLAG_GENERATOR); + } + if (JS_IsException(ret) || JS_IsUndefined(ret)) { + if (JS_IsUndefined(ret)) { + ret = sf->cur_sp-1; + sf->cur_sp-1 = JS_UNDEFINED; + } + /* end of execution */ + s->is_completed = TRUE; + + /* close the closure variables. */ + close_var_refs(rt, sf); + + async_func_free_frame(rt, s); + } + return ret; +} + +static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + /* cannot close the closure variables here because it would + potentially modify the object graph */ + if (!s->is_completed) { + async_func_free_frame(rt, s); + } + + JS_FreeValueRT(rt, s->resolving_funcs0); + JS_FreeValueRT(rt, s->resolving_funcs1); - /* the tag does not matter provided it is not an object */ - func_obj = JS_MKPTR(JS_TAG_INT, s); - return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, - s->argc, s->frame.arg_buf, JS_CALL_FLAG_GENERATOR); + remove_gc_object(&s->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && s->header.ref_count != 0) { + list_add_tail(&s->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, s); + } } +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + if (--s->header.ref_count == 0) { + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&s->header.link); + list_add(&s->header.link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } +} /* Generators */ @@ -19092,14 +20084,17 @@ typedef struct JSGeneratorData { JSGeneratorStateEnum state; - JSAsyncFunctionState func_state; + JSAsyncFunctionState *func_state; } JSGeneratorData; static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) { if (s->state == JS_GENERATOR_STATE_COMPLETED) return; - async_func_free(rt, &s->func_state); + if (s->func_state) { + async_func_free(rt, s->func_state); + s->func_state = NULL; + } s->state = JS_GENERATOR_STATE_COMPLETED; } @@ -19124,9 +20119,9 @@ JSObject *p = JS_VALUE_GET_OBJ(val); JSGeneratorData *s = p->u.generator_data; - if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) + if (!s || !s->func_state) return; - async_func_mark(rt, &s->func_state, mark_func); + mark_func(rt, &s->func_state->header); } /* XXX: use enum */ @@ -19145,10 +20140,10 @@ *pdone = TRUE; if (!s) return JS_ThrowTypeError(ctx, "not a generator"); - sf = &s->func_state.frame; switch(s->state) { default: case JS_GENERATOR_STATE_SUSPENDED_START: + sf = &s->func_state->frame; if (magic == GEN_MAGIC_NEXT) { goto exec_no_arg; } else { @@ -19158,28 +20153,29 @@ break; case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: case JS_GENERATOR_STATE_SUSPENDED_YIELD: + sf = &s->func_state->frame; /* cur_sp-1 was set to JS_UNDEFINED in the previous call */ ret = JS_DupValue(ctx, argv0); if (magic == GEN_MAGIC_THROW && s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { JS_Throw(ctx, ret); - s->func_state.throw_flag = TRUE; + s->func_state->throw_flag = TRUE; } else { sf->cur_sp-1 = ret; sf->cur_sp0 = JS_NewInt32(ctx, magic); sf->cur_sp++; exec_no_arg: - s->func_state.throw_flag = FALSE; + s->func_state->throw_flag = FALSE; } s->state = JS_GENERATOR_STATE_EXECUTING; - func_ret = async_func_resume(ctx, &s->func_state); + func_ret = async_func_resume(ctx, s->func_state); s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; - if (JS_IsException(func_ret)) { - /* finalize the execution in case of exception */ + if (s->func_state->is_completed) { + /* finalize the execution in case of exception or normal return */ free_generator_stack(ctx, s); return func_ret; - } - if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + } else { + assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT); /* get the returned yield value at the top of the stack */ ret = sf->cur_sp-1; sf->cur_sp-1 = JS_UNDEFINED; @@ -19190,12 +20186,6 @@ } else { *pdone = FALSE; } - } else { - /* end of iterator */ - ret = sf->cur_sp-1; - sf->cur_sp-1 = JS_UNDEFINED; - JS_FreeValue(ctx, func_ret); - free_generator_stack(ctx, s); } break; case JS_GENERATOR_STATE_COMPLETED: @@ -19233,13 +20223,14 @@ if (!s) return JS_EXCEPTION; s->state = JS_GENERATOR_STATE_SUSPENDED_START; - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s->func_state) { s->state = JS_GENERATOR_STATE_COMPLETED; goto fail; } /* execute the function up to 'OP_initial_yield' */ - func_ret = async_func_resume(ctx, &s->func_state); + func_ret = async_func_resume(ctx, s->func_state); if (JS_IsException(func_ret)) goto fail; JS_FreeValue(ctx, func_ret); @@ -19257,36 +20248,12 @@ /* AsyncFunction */ -static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) -{ - if (s->is_active) { - async_func_free(rt, &s->func_state); - s->is_active = FALSE; - } -} - -static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) -{ - js_async_function_terminate(rt, s); - JS_FreeValueRT(rt, s->resolving_funcs0); - JS_FreeValueRT(rt, s->resolving_funcs1); - remove_gc_object(&s->header); - js_free_rt(rt, s); -} - -static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) -{ - if (--s->header.ref_count == 0) { - js_async_function_free0(rt, s); - } -} - static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); - JSAsyncFunctionData *s = p->u.async_function_data; + JSAsyncFunctionState *s = p->u.async_function_data; if (s) { - js_async_function_free(rt, s); + async_func_free(rt, s); } } @@ -19294,14 +20261,14 @@ JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); - JSAsyncFunctionData *s = p->u.async_function_data; + JSAsyncFunctionState *s = p->u.async_function_data; if (s) { mark_func(rt, &s->header); } } static int js_async_function_resolve_create(JSContext *ctx, - JSAsyncFunctionData *s, + JSAsyncFunctionState *s, JSValue *resolving_funcs) { int i; @@ -19323,60 +20290,58 @@ return 0; } -static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) +static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionState *s) { JSValue func_ret, ret2; - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) { - JSValue error; - fail: - error = JS_GetException(ctx); - ret2 = JS_Call(ctx, s->resolving_funcs1, JS_UNDEFINED, - 1, (JSValueConst *)&error); - JS_FreeValue(ctx, error); - js_async_function_terminate(ctx->rt, s); - JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ - } else { - JSValue value; - value = s->func_state.frame.cur_sp-1; - s->func_state.frame.cur_sp-1 = JS_UNDEFINED; - if (JS_IsUndefined(func_ret)) { - /* function returned */ - ret2 = JS_Call(ctx, s->resolving_funcs0, JS_UNDEFINED, - 1, (JSValueConst *)&value); + func_ret = async_func_resume(ctx, s); + if (s->is_completed) { + if (JS_IsException(func_ret)) { + JSValue error; + fail: + error = JS_GetException(ctx); + ret2 = JS_Call(ctx, s->resolving_funcs1, JS_UNDEFINED, + 1, (JSValueConst *)&error); + JS_FreeValue(ctx, error); JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ - JS_FreeValue(ctx, value); - js_async_function_terminate(ctx->rt, s); } else { - JSValue promise, resolving_funcs2, resolving_funcs12; - int i, res; + /* normal return */ + ret2 = JS_Call(ctx, s->resolving_funcs0, JS_UNDEFINED, + 1, (JSValueConst *)&func_ret); + JS_FreeValue(ctx, func_ret); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + } + } else { + JSValue value, promise, resolving_funcs2, resolving_funcs12; + int i, res; - /* await */ - JS_FreeValue(ctx, func_ret); /* not used */ - promise = js_promise_resolve(ctx, ctx->promise_ctor, - 1, (JSValueConst *)&value, 0); - JS_FreeValue(ctx, value); - if (JS_IsException(promise)) - goto fail; - if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { - JS_FreeValue(ctx, promise); - goto fail; - } + value = s->frame.cur_sp-1; + s->frame.cur_sp-1 = JS_UNDEFINED; - /* Note: no need to create 'thrownawayCapability' as in - the spec */ - for(i = 0; i < 2; i++) - resolving_funcs1i = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - (JSValueConst *)resolving_funcs, - (JSValueConst *)resolving_funcs1); + /* await */ + JS_FreeValue(ctx, func_ret); /* not used */ + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, (JSValueConst *)&value, 0); + JS_FreeValue(ctx, value); + if (JS_IsException(promise)) + goto fail; + if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { JS_FreeValue(ctx, promise); - for(i = 0; i < 2; i++) - JS_FreeValue(ctx, resolving_funcsi); - if (res) - goto fail; + goto fail; } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1i = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs, + (JSValueConst *)resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcsi); + if (res) + goto fail; } } @@ -19387,7 +20352,7 @@ int flags) { JSObject *p = JS_VALUE_GET_OBJ(func_obj); - JSAsyncFunctionData *s = p->u.async_function_data; + JSAsyncFunctionState *s = p->u.async_function_data; BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; JSValueConst arg; @@ -19395,12 +20360,12 @@ arg = argv0; else arg = JS_UNDEFINED; - s->func_state.throw_flag = is_reject; + s->throw_flag = is_reject; if (is_reject) { JS_Throw(ctx, JS_DupValue(ctx, arg)); } else { /* return value of await */ - s->func_state.frame.cur_sp-1 = JS_DupValue(ctx, arg); + s->frame.cur_sp-1 = JS_DupValue(ctx, arg); } js_async_function_resume(ctx, s); return JS_UNDEFINED; @@ -19411,32 +20376,21 @@ int argc, JSValueConst *argv, int flags) { JSValue promise; - JSAsyncFunctionData *s; + JSAsyncFunctionState *s; - s = js_mallocz(ctx, sizeof(*s)); + s = async_func_init(ctx, func_obj, this_obj, argc, argv); if (!s) return JS_EXCEPTION; - s->header.ref_count = 1; - add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); - s->is_active = FALSE; - s->resolving_funcs0 = JS_UNDEFINED; - s->resolving_funcs1 = JS_UNDEFINED; promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); - if (JS_IsException(promise)) - goto fail; - - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - fail: - JS_FreeValue(ctx, promise); - js_async_function_free(ctx->rt, s); + if (JS_IsException(promise)) { + async_func_free(ctx->rt, s); return JS_EXCEPTION; } - s->is_active = TRUE; js_async_function_resume(ctx, s); - js_async_function_free(ctx->rt, s); + async_func_free(ctx->rt, s); return promise; } @@ -19465,7 +20419,8 @@ typedef struct JSAsyncGeneratorData { JSObject *generator; /* back pointer to the object (const) */ JSAsyncGeneratorStateEnum state; - JSAsyncFunctionState func_state; + /* func_state is NULL is state AWAITING_RETURN and COMPLETED */ + JSAsyncFunctionState *func_state; struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ } JSAsyncGeneratorData; @@ -19483,10 +20438,8 @@ JS_FreeValueRT(rt, req->resolving_funcs1); js_free_rt(rt, req); } - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && - s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { - async_func_free(rt, &s->func_state); - } + if (s->func_state) + async_func_free(rt, s->func_state); js_free_rt(rt, s); } @@ -19513,9 +20466,8 @@ JS_MarkValue(rt, req->resolving_funcs0, mark_func); JS_MarkValue(rt, req->resolving_funcs1, mark_func); } - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && - s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { - async_func_mark(rt, &s->func_state, mark_func); + if (s->func_state) { + mark_func(rt, &s->func_state->header); } } } @@ -19625,7 +20577,8 @@ { if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - async_func_free(ctx->rt, &s->func_state); + async_func_free(ctx->rt, s->func_state); + s->func_state = NULL; } } @@ -19636,10 +20589,19 @@ JSValue promise, resolving_funcs2, resolving_funcs12; int res; - promise = js_promise_resolve(ctx, ctx->promise_ctor, - 1, (JSValueConst *)&value, 0); - if (JS_IsException(promise)) - return -1; + // Can fail looking up JS_ATOM_constructor when is_reject==0. + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, + /*is_reject*/0); + // A poisoned .constructor property is observable and the resulting + // exception should be delivered to the catch handler. + if (JS_IsException(promise)) { + JSValue err = JS_GetException(ctx); + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&err, + /*is_reject*/1); + JS_FreeValue(ctx, err); + if (JS_IsException(promise)) + return -1; + } if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), resolving_funcs1, @@ -19687,7 +20649,6 @@ } else if (next->completion_type == GEN_MAGIC_RETURN) { s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; js_async_generator_completed_return(ctx, s, next->result); - goto done; } else { js_async_generator_reject(ctx, s, next->result); } @@ -19698,30 +20659,38 @@ if (next->completion_type == GEN_MAGIC_THROW && s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { JS_Throw(ctx, value); - s->func_state.throw_flag = TRUE; + s->func_state->throw_flag = TRUE; } else { /* 'yield' returns a value. 'yield *' also returns a value in case the 'throw' method is called */ - s->func_state.frame.cur_sp-1 = value; - s->func_state.frame.cur_sp0 = + s->func_state->frame.cur_sp-1 = value; + s->func_state->frame.cur_sp0 = JS_NewInt32(ctx, next->completion_type); - s->func_state.frame.cur_sp++; + s->func_state->frame.cur_sp++; exec_no_arg: - s->func_state.throw_flag = FALSE; + s->func_state->throw_flag = FALSE; } s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; resume_exec: - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) { - value = JS_GetException(ctx); - js_async_generator_complete(ctx, s); - js_async_generator_reject(ctx, s, value); - JS_FreeValue(ctx, value); - } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { - int func_ret_code; - value = s->func_state.frame.cur_sp-1; - s->func_state.frame.cur_sp-1 = JS_UNDEFINED; + func_ret = async_func_resume(ctx, s->func_state); + if (s->func_state->is_completed) { + if (JS_IsException(func_ret)) { + value = JS_GetException(ctx); + js_async_generator_complete(ctx, s); + js_async_generator_reject(ctx, s, value); + JS_FreeValue(ctx, value); + } else { + /* end of function */ + js_async_generator_complete(ctx, s); + js_async_generator_resolve(ctx, s, func_ret, TRUE); + JS_FreeValue(ctx, func_ret); + } + } else { + int func_ret_code, ret; + assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT); func_ret_code = JS_VALUE_GET_INT(func_ret); + value = s->func_state->frame.cur_sp-1; + s->func_state->frame.cur_sp-1 = JS_UNDEFINED; switch(func_ret_code) { case FUNC_RET_YIELD: case FUNC_RET_YIELD_STAR: @@ -19733,20 +20702,17 @@ JS_FreeValue(ctx, value); break; case FUNC_RET_AWAIT: - js_async_generator_await(ctx, s, value); + ret = js_async_generator_await(ctx, s, value); JS_FreeValue(ctx, value); + if (ret < 0) { + /* exception: throw it */ + s->func_state->throw_flag = TRUE; + goto resume_exec; + } goto done; default: abort(); } - } else { - assert(JS_IsUndefined(func_ret)); - /* end of function */ - value = s->func_state.frame.cur_sp-1; - s->func_state.frame.cur_sp-1 = JS_UNDEFINED; - js_async_generator_complete(ctx, s); - js_async_generator_resolve(ctx, s, value, TRUE); - JS_FreeValue(ctx, value); } break; default: @@ -19780,12 +20746,12 @@ } else { /* restart function execution after await() */ assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); - s->func_state.throw_flag = is_reject; + s->func_state->throw_flag = is_reject; if (is_reject) { JS_Throw(ctx, JS_DupValue(ctx, arg)); } else { /* return value of await */ - s->func_state.frame.cur_sp-1 = JS_DupValue(ctx, arg); + s->func_state->frame.cur_sp-1 = JS_DupValue(ctx, arg); } js_async_generator_resume_next(ctx, s); } @@ -19849,14 +20815,12 @@ return JS_EXCEPTION; s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; init_list_head(&s->queue); - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s->func_state) goto fail; - } - /* execute the function up to 'OP_initial_yield' (no yield nor await are possible) */ - func_ret = async_func_resume(ctx, &s->func_state); + func_ret = async_func_resume(ctx, s->func_state); if (JS_IsException(func_ret)) goto fail; JS_FreeValue(ctx, func_ret); @@ -19892,9 +20856,6 @@ TOK_AND_ASSIGN, TOK_XOR_ASSIGN, TOK_OR_ASSIGN, -#ifdef CONFIG_BIGNUM - TOK_MATH_POW_ASSIGN, -#endif TOK_POW_ASSIGN, TOK_LAND_ASSIGN, TOK_LOR_ASSIGN, @@ -19914,9 +20875,6 @@ TOK_STRICT_NEQ, TOK_LAND, TOK_LOR, -#ifdef CONFIG_BIGNUM - TOK_MATH_POW, -#endif TOK_POW, TOK_ARROW, TOK_ELLIPSIS, @@ -19995,7 +20953,8 @@ int drop_count; /* number of stack elements to drop */ int label_finally; /* -1 if none */ int scope_level; - int has_iterator; + uint8_t has_iterator : 1; + uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ } BlockEnv; typedef struct JSGlobalVar { @@ -20031,9 +20990,17 @@ typedef struct LineNumberSlot { uint32_t pc; - int line_num; + uint32_t source_pos; } LineNumberSlot; +typedef struct { + /* last source position */ + const uint8_t *ptr; + int line_num; + int col_num; + const uint8_t *buf_start; +} GetLineColCache; + typedef enum JSParseFunctionEnum { JS_PARSE_FUNC_STATEMENT, JS_PARSE_FUNC_VAR, @@ -20042,6 +21009,7 @@ JS_PARSE_FUNC_GETTER, JS_PARSE_FUNC_SETTER, JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, JS_PARSE_FUNC_CLASS_CONSTRUCTOR, JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, } JSParseFunctionEnum; @@ -20083,7 +21051,6 @@ BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */ BOOL is_derived_class_constructor; BOOL in_function_body; - BOOL backtrace_barrier; JSFunctionKindEnum func_kind : 8; JSParseFunctionEnum func_type : 8; uint8_t js_mode; /* bitmap of JS_MODE_x */ @@ -20124,7 +21091,7 @@ DynBuf byte_code; int last_opcode_pos; /* -1 if no last opcode */ - int last_opcode_line_num; + const uint8_t *last_opcode_source_ptr; BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ LabelSlot *label_slots; @@ -20153,20 +21120,23 @@ int line_number_last_pc; /* pc2line table */ + BOOL strip_debug : 1; /* strip all debug info (implies strip_source = TRUE) */ + BOOL strip_source : 1; /* strip only source code */ JSAtom filename; - int line_num; + uint32_t source_pos; /* pointer in the eval() source */ + GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */ DynBuf pc2line; char *source; /* raw source, utf-8 encoded */ int source_len; JSModuleDef *module; /* != NULL when parsing a module */ + BOOL has_await; /* TRUE if await is used (used in module eval) */ } JSFunctionDef; typedef struct JSToken { int val; - int line_num; /* line number of token start */ - const uint8_t *ptr; + const uint8_t *ptr; /* position in the source */ union { struct { JSValue str; @@ -20174,9 +21144,6 @@ } str; struct { JSValue val; -#ifdef CONFIG_BIGNUM - slimb_t exponent; /* may be != 0 only if val is a float */ -#endif } num; struct { JSAtom atom; @@ -20192,12 +21159,11 @@ typedef struct JSParseState { JSContext *ctx; - int last_line_num; /* line number of last token */ - int line_num; /* line number of current offset */ const char *filename; JSToken token; BOOL got_lf; /* true if got line feed before the current token */ const uint8_t *last_ptr; + const uint8_t *buf_start; const uint8_t *buf_ptr; const uint8_t *buf_end; @@ -20206,6 +21172,7 @@ BOOL is_module; /* parsing a module */ BOOL allow_html_comments; BOOL ext_json; /* true if accepting JSON superset */ + GetLineColCache get_line_col_cache; } JSParseState; typedef struct JSOpCode { @@ -20249,11 +21216,9 @@ static void free_token(JSParseState *s, JSToken *token) { switch(token->val) { -#ifdef CONFIG_BIGNUM case TOK_NUMBER: JS_FreeValue(s->ctx, token->u.num.val); break; -#endif case TOK_STRING: case TOK_TEMPLATE: JS_FreeValue(s->ctx, token->u.str.str); @@ -20336,25 +21301,111 @@ } } +/* #if defined(_WIN32) int js_parse_error(JSParseState *s, const char *fmt, ...) #else int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...) #endif +*/ + +/* return the zero based line and column number in the source. */ +/* Note: we no longer support '\r' as line terminator */ +static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len) +{ + int line_num, col_num, c; + size_t i; + + line_num = 0; + col_num = 0; + for(i = 0; i < len; i++) { + c = bufi; + if (c == '\n') { + line_num++; + col_num = 0; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + *pcol_num = col_num; + return line_num; +} + +static int get_line_col_cached(GetLineColCache *s, int *pcol_num, const uint8_t *ptr) +{ + int line_num, col_num; + if (ptr >= s->ptr) { + line_num = get_line_col(&col_num, s->ptr, ptr - s->ptr); + if (line_num == 0) { + s->col_num += col_num; + } else { + s->line_num += line_num; + s->col_num = col_num; + } + } else { + line_num = get_line_col(&col_num, ptr, s->ptr - ptr); + if (line_num == 0) { + s->col_num -= col_num; + } else { + const uint8_t *p; + s->line_num -= line_num; + /* find the absolute column position */ + col_num = 0; + for(p = ptr - 1; p >= s->buf_start; p--) { + if (*p == '\n') { + break; + } else if (*p < 0x80 || *p >= 0xc0) { + col_num++; + } + } + s->col_num = col_num; + } + } + s->ptr = ptr; + *pcol_num = s->col_num; + return s->line_num; +} + +/* 'ptr' is the position of the error in the source */ +static int js_parse_error_v(JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) +{ + JSContext *ctx = s->ctx; + int line_num, col_num; + line_num = get_line_col(&col_num, s->buf_start, ptr - s->buf_start); + JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); + build_backtrace(ctx, ctx->rt->current_exception, s->filename, + line_num + 1, col_num + 1, 0); + return -1; +} + +static +#if !defined(_MSC_VER) +__attribute__((format(printf, 3, 4))) +#endif +int js_parse_error_pos(JSParseState *s, const uint8_t *ptr, const char *fmt, ...) { - JSContext *ctx = s->ctx; va_list ap; - int backtrace_flags; + int ret; va_start(ap, fmt); - JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); + ret = js_parse_error_v(s, ptr, fmt, ap); va_end(ap); - backtrace_flags = 0; - if (s->cur_func && s->cur_func->backtrace_barrier) - backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; - build_backtrace(ctx, ctx->rt->current_exception, s->filename, s->line_num, - backtrace_flags); - return -1; + return ret; +} + +static +#if !defined(_MSC_VER) +__attribute__((format(printf, 2, 3))) +#endif +int js_parse_error(JSParseState *s, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = js_parse_error_v(s, s->token.ptr, fmt, ap); + va_end(ap); + return ret; } static int js_parse_expect(JSParseState *s, int tok) @@ -20390,6 +21441,7 @@ { uint32_t c; StringBuffer b_s, *b = &b_s; + JSValue str; /* p points to the first byte of the template part */ if (string_buffer_init(s->ctx, b, 32)) @@ -20420,13 +21472,11 @@ p++; c = '\n'; } - if (c == '\n') { - s->line_num++; - } else if (c >= 0x80) { + if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { - js_parse_error(s, "invalid UTF-8 sequence"); + js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); goto fail; } p = p_next; @@ -20434,9 +21484,12 @@ if (string_buffer_putc(b, c)) goto fail; } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; s->token.val = TOK_TEMPLATE; s->token.u.str.sep = c; - s->token.u.str.str = string_buffer_end(b); + s->token.u.str.str = str; s->buf_ptr = p; return 0; @@ -20454,6 +21507,8 @@ int ret; uint32_t c; StringBuffer b_s, *b = &b_s; + const uint8_t *p_escape; + JSValue str; /* string */ if (string_buffer_init(s->ctx, b, 32)) @@ -20463,11 +21518,6 @@ goto invalid_char; c = *p; if (c < 0x20) { - if (!s->cur_func) { - if (do_throw) - js_parse_error(s, "invalid character in a JSON string"); - goto fail; - } if (sep == '`') { if (c == '\r') { if (p1 == '\n') @@ -20487,6 +21537,7 @@ break; } if (c == '\\') { + p_escape = p - 1; c = *p; /* XXX: need a specific JSON case to avoid accepting invalid escapes */ @@ -20509,13 +21560,9 @@ case '\n': /* ignore escaped newline sequence */ p++; - if (sep != '`') - s->line_num++; continue; default: if (c >= '0' && c <= '9') { - if (!s->cur_func) - goto invalid_escape; /* JSON case */ if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`') goto parse_escape; if (c == '0' && !(p1 >= '0' && p1 <= '9')) { @@ -20528,7 +21575,7 @@ goto invalid_escape; } else { if (do_throw) - js_parse_error(s, "octal escape sequences are not allowed in strict mode"); + js_parse_error_pos(s, p_escape, "octal escape sequences are not allowed in strict mode"); } goto fail; } @@ -20548,7 +21595,7 @@ if (ret == -1) { invalid_escape: if (do_throw) - js_parse_error(s, "malformed escape sequence in string literal"); + js_parse_error_pos(s, p_escape, "malformed escape sequence in string literal"); goto fail; } else if (ret < 0) { /* ignore the '\' (could output a warning) */ @@ -20569,9 +21616,12 @@ if (string_buffer_putc(b, c)) goto fail; } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; token->val = TOK_STRING; token->u.str.sep = c; - token->u.str.str = string_buffer_end(b); + token->u.str.str = str; *pp = p; return 0; @@ -20599,6 +21649,7 @@ StringBuffer b_s, *b = &b_s; StringBuffer b2_s, *b2 = &b2_s; uint32_t c; + JSValue body_str, flags_str; p = s->buf_ptr; p++; @@ -20647,16 +21698,16 @@ c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { invalid_utf8: - js_parse_error(s, "invalid UTF-8 sequence"); + js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); goto fail; } - p = p_next; /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { eol_error: - js_parse_error(s, "unexpected line terminator in regexp"); + js_parse_error_pos(s, p - 1, "unexpected line terminator in regexp"); goto fail; } + p = p_next; } if (string_buffer_putc(b, c)) goto fail; @@ -20669,6 +21720,7 @@ if (c >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { + p++; goto invalid_utf8; } } @@ -20679,9 +21731,17 @@ p = p_next; } + body_str = string_buffer_end(b); + flags_str = string_buffer_end(b2); + if (JS_IsException(body_str) || + JS_IsException(flags_str)) { + JS_FreeValue(s->ctx, body_str); + JS_FreeValue(s->ctx, flags_str); + return -1; + } s->token.val = TOK_REGEXP; - s->token.u.regexp.body = string_buffer_end(b); - s->token.u.regexp.flags = string_buffer_end(b2); + s->token.u.regexp.body = body_str; + s->token.u.regexp.flags = flags_str; s->buf_ptr = p; return 0; fail: @@ -20717,6 +21777,48 @@ return 0; } +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident(JSParseState *s) +{ + if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || + (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && + (s->cur_func->js_mode & JS_MODE_STRICT)) || + (s->token.u.ident.atom == JS_ATOM_yield && + ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || + (s->token.u.ident.atom == JS_ATOM_await && + (s->is_module || + (s->cur_func->func_kind & JS_FUNC_ASYNC) || + s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || + s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { + if (s->token.u.ident.has_escape) { + s->token.u.ident.is_reserved = TRUE; + s->token.val = TOK_IDENT; + } else { + /* The keywords atoms are pre allocated */ + s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token(JSParseState *s) +{ + if (s->token.val == TOK_IDENT || + (s->token.val >= TOK_FIRST_KEYWORD && + s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = FALSE; + update_token_ident(s); + } +} + /* 'c' is the first character. Return JS_ATOM_NULL in case of error */ static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, int c, BOOL is_private) @@ -20781,9 +21883,7 @@ p = s->last_ptr = s->buf_ptr; s->got_lf = FALSE; - s->last_line_num = s->token.line_num; redo: - s->token.line_num = s->line_num; s->token.ptr = p; c = *p; switch(c) { @@ -20813,7 +21913,6 @@ p++; line_terminator: s->got_lf = TRUE; - s->line_num++; goto redo; case '\f': case '\v': @@ -20834,11 +21933,7 @@ p += 2; break; } - if (*p == '\n') { - s->line_num++; - s->got_lf = TRUE; /* considered as LF for ASI */ - p++; - } else if (*p == '\r') { + if (*p == '\n' || *p == '\r') { s->got_lf = TRUE; /* considered as LF for ASI */ p++; } else if (*p >= 0x80) { @@ -20923,30 +22018,8 @@ s->token.u.ident.atom = atom; s->token.u.ident.has_escape = ident_has_escape; s->token.u.ident.is_reserved = FALSE; - if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || - (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && - (s->cur_func->js_mode & JS_MODE_STRICT)) || - (s->token.u.ident.atom == JS_ATOM_yield && - ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || - (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && - !s->cur_func->in_function_body && s->cur_func->parent && - (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || - (s->token.u.ident.atom == JS_ATOM_await && - (s->is_module || - (((s->cur_func->func_kind & JS_FUNC_ASYNC) || - (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && - !s->cur_func->in_function_body && s->cur_func->parent && - (s->cur_func->parent->func_kind & JS_FUNC_ASYNC))))))) { - if (ident_has_escape) { - s->token.u.ident.is_reserved = TRUE; - s->token.val = TOK_IDENT; - } else { - /* The keywords atoms are pre allocated */ - s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; - } - } else { - s->token.val = TOK_IDENT; - } + s->token.val = TOK_IDENT; + update_token_ident(s); break; case '#': /* private name */ @@ -21000,26 +22073,11 @@ { JSValue ret; const uint8_t *p1; - int flags, radix; + int flags; flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | - ATOD_ACCEPT_UNDERSCORES; -#ifdef CONFIG_BIGNUM - flags |= ATOD_ACCEPT_SUFFIX; - if (s->cur_func->js_mode & JS_MODE_MATH) { - flags |= ATOD_MODE_BIGINT; - if (s->cur_func->js_mode & JS_MODE_MATH) - flags |= ATOD_TYPE_BIG_FLOAT; - } -#endif - radix = 0; -#ifdef CONFIG_BIGNUM - s->token.u.num.exponent = 0; - ret = js_atof2(s->ctx, (const char *)p, (const char **)&p, radix, - flags, &s->token.u.num.exponent); -#else - ret = js_atof(s->ctx, (const char *)p, (const char **)&p, radix, + ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, flags); -#endif if (JS_IsException(ret)) goto fail; /* reject `10instanceof Number` */ @@ -21073,8 +22131,8 @@ p += 2; s->token.val = TOK_MINUS_ASSIGN; } else if (p1 == '-') { - if (s->allow_html_comments && - p2 == '>' && s->last_line_num != s->line_num) { + if (s->allow_html_comments && p2 == '>' && + (s->got_lf || s->last_ptr == s->buf_start)) { /* Annex B: `-->` at beginning of line is an html comment end. It extends to the end of the line. */ @@ -21175,33 +22233,6 @@ goto def_token; } break; -#ifdef CONFIG_BIGNUM - /* in math mode, '^' is the power operator. '^^' is always the - xor operator and '**' is always the power operator */ - case '^': - if (p1 == '=') { - p += 2; - if (s->cur_func->js_mode & JS_MODE_MATH) - s->token.val = TOK_MATH_POW_ASSIGN; - else - s->token.val = TOK_XOR_ASSIGN; - } else if (p1 == '^') { - if (p2 == '=') { - p += 3; - s->token.val = TOK_XOR_ASSIGN; - } else { - p += 2; - s->token.val = '^'; - } - } else { - p++; - if (s->cur_func->js_mode & JS_MODE_MATH) - s->token.val = TOK_MATH_POW; - else - s->token.val = '^'; - } - break; -#else case '^': if (p1 == '=') { p += 2; @@ -21210,7 +22241,6 @@ goto def_token; } break; -#endif case '|': if (p1 == '=') { p += 2; @@ -21281,6 +22311,7 @@ } /* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +/* XXX: accept unicode identifiers as JSON5 ? */ static JSAtom json_parse_ident(JSParseState *s, const uint8_t **pp, int c) { const uint8_t *p; @@ -21295,8 +22326,7 @@ for(;;) { bufident_pos++ = c; c = *p; - if (c >= 128 || - !((lre_id_continue_table_asciic >> 5 >> (c & 31)) & 1)) + if (c >= 128 || !lre_is_id_continue_byte(c)) break; p++; if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { @@ -21314,6 +22344,182 @@ return atom; } +static int json_parse_string(JSParseState *s, const uint8_t **pp, int sep) +{ + const uint8_t *p, *p_next; + int i; + uint32_t c; + StringBuffer b_s, *b = &b_s; + + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + + p = *pp; + for(;;) { + if (p >= s->buf_end) { + goto end_of_input; + } + c = *p++; + if (c == sep) + break; + if (c < 0x20) { + js_parse_error_pos(s, p - 1, "Bad control character in string literal"); + goto fail; + } + if (c == '\\') { + c = *p++; + switch(c) { + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case '\\': break; + case '/': break; + case 'u': + c = 0; + for(i = 0; i < 4; i++) { + int h = from_hex(*p++); + if (h < 0) { + js_parse_error_pos(s, p - 1, "Bad Unicode escape"); + goto fail; + } + c = (c << 4) | h; + } + break; + case '\n': + if (s->ext_json) + continue; + goto bad_escape; + case 'v': + if (s->ext_json) { + c = '\v'; + break; + } + goto bad_escape; + default: + if (c == sep) + break; + if (p > s->buf_end) + goto end_of_input; + bad_escape: + js_parse_error_pos(s, p - 1, "Bad escaped character"); + goto fail; + } + } else + if (c >= 0x80) { + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + js_parse_error_pos(s, p - 1, "Bad UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + s->token.val = TOK_STRING; + s->token.u.str.sep = sep; + s->token.u.str.str = string_buffer_end(b); + *pp = p; + return 0; + + end_of_input: + js_parse_error(s, "Unexpected end of JSON input"); + fail: + string_buffer_free(b); + return -1; +} + +static int json_parse_number(JSParseState *s, const uint8_t **pp) +{ + const uint8_t *p = *pp; + const uint8_t *p_start = p; + int radix; + double d; + JSATODTempMem atod_mem; + + if (*p == '+' || *p == '-') + p++; + + if (!is_digit(*p)) { + if (s->ext_json) { + if (strstart((const char *)p, "Infinity", (const char **)&p)) { +#if defined(_MSC_VER) && !defined(__clang__) + d = INFINITY; +#else + d = 1.0 / 0.0; +#endif + if (*p_start == '-') + d = -d; + goto done; + } else if (strstart((const char *)p, "NaN", (const char **)&p)) { + d = NAN; + goto done; + } else if (*p != '.') { + goto unexpected_token; + } + } else { + goto unexpected_token; + } + } + + if (p0 == '0') { + if (s->ext_json) { + /* also accepts base 16, 8 and 2 prefix for integers */ + radix = 10; + if (p1 == 'x' || p1 == 'X') { + p += 2; + radix = 16; + } else if ((p1 == 'o' || p1 == 'O')) { + p += 2; + radix = 8; + } else if ((p1 == 'b' || p1 == 'B')) { + p += 2; + radix = 2; + } + if (radix != 10) { + /* prefix is present */ + if (to_digit(*p) >= radix) { + unexpected_token: + return js_parse_error_pos(s, p, "Unexpected token '%c'", *p); + } + d = js_atod((const char *)p_start, (const char **)&p, 0, + JS_ATOD_INT_ONLY | JS_ATOD_ACCEPT_BIN_OCT, &atod_mem); + goto done; + } + } + if (is_digit(p1)) + return js_parse_error_pos(s, p, "Unexpected number"); + } + + while (is_digit(*p)) + p++; + + if (*p == '.') { + p++; + if (!is_digit(*p)) + return js_parse_error_pos(s, p, "Unterminated fractional number"); + while (is_digit(*p)) + p++; + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') + p++; + if (!is_digit(*p)) + return js_parse_error_pos(s, p, "Exponent part is missing a number"); + while (is_digit(*p)) + p++; + } + d = js_atod((const char *)p_start, NULL, 10, 0, &atod_mem); + done: + s->token.val = TOK_NUMBER; + s->token.u.num.val = JS_NewFloat64(s->ctx, d); + *pp = p; + return 0; +} + static __exception int json_next_token(JSParseState *s) { const uint8_t *p; @@ -21327,9 +22533,7 @@ free_token(s, &s->token); p = s->last_ptr = s->buf_ptr; - s->last_line_num = s->token.line_num; redo: - s->token.line_num = s->line_num; s->token.ptr = p; c = *p; switch(c) { @@ -21347,7 +22551,8 @@ } /* fall through */ case '\"': - if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) + p++; + if (json_parse_string(s, &p, c)) goto fail; break; case '\r': /* accept DOS and MAC newline sequences */ @@ -21357,7 +22562,6 @@ /* fall thru */ case '\n': p++; - s->line_num++; goto redo; case '\f': case '\v': @@ -21387,12 +22591,7 @@ p += 2; break; } - if (*p == '\n') { - s->line_num++; - p++; - } else if (*p == '\r') { - p++; - } else if (*p >= 0x80) { + if (*p >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); if (c == -1) { p++; /* skip invalid UTF-8 */ @@ -21443,7 +22642,6 @@ case 'Y': case 'Z': case '_': case '$': - /* identifier : only pure ascii characters are accepted */ p++; atom = json_parse_ident(s, &p, c); if (atom == JS_ATOM_NULL) @@ -21454,39 +22652,23 @@ s->token.val = TOK_IDENT; break; case '+': - if (!s->ext_json || !is_digit(p1)) + if (!s->ext_json) goto def_token; goto parse_number; - case '0': - if (is_digit(p1)) + case '.': + if (s->ext_json && is_digit(p1)) + goto parse_number; + else goto def_token; - goto parse_number; case '-': - if (!is_digit(p1)) - goto def_token; - goto parse_number; + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* number */ parse_number: - { - JSValue ret; - int flags, radix; - if (!s->ext_json) { - flags = 0; - radix = 10; - } else { - flags = ATOD_ACCEPT_BIN_OCT; - radix = 0; - } - ret = js_atof(s->ctx, (const char *)p, (const char **)&p, radix, - flags); - if (JS_IsException(ret)) - goto fail; - s->token.val = TOK_NUMBER; - s->token.u.num.val = ret; - } + if (json_parse_number(s, &p)) + goto fail; break; default: if (c >= 128) { @@ -21508,9 +22690,29 @@ return -1; } -/* only used for ':' and '=>', 'let' or 'function' look-ahead. *pp is - only set if TOK_IMPORT is returned */ -/* XXX: handle all unicode cases */ +static int match_identifier(const uint8_t *p, const char *s) { + uint32_t c; + while (*s) { + if ((uint8_t)*s++ != *p++) + return 0; + } + c = *p; + if (c >= 128) + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + return !lre_js_is_ident_next(c); +} + +/* simple_next_token() is used to check for the next token in simple cases. + It is only used for ':' and '=>', 'let' or 'function' look-ahead. + (*pp) is only set if TOK_IMPORT is returned for JS_DetectModule() + Whitespace and comments are skipped correctly. + Then the next token is analyzed, only for specific words. + Return values: + - '\n' if !no_line_terminator + - TOK_ARROW, TOK_IN, TOK_IMPORT, TOK_OF, TOK_EXPORT, TOK_FUNCTION + - TOK_IDENT is returned for other identifiers and keywords + - otherwise the next character or unicode codepoint is returned. + */ static int simple_next_token(const uint8_t **pp, BOOL no_line_terminator) { const uint8_t *p; @@ -21554,33 +22756,42 @@ if (*p == '>') return TOK_ARROW; break; + case 'i': + if (match_identifier(p, "n")) + return TOK_IN; + if (match_identifier(p, "mport")) { + *pp = p + 5; + return TOK_IMPORT; + } + return TOK_IDENT; + case 'o': + if (match_identifier(p, "f")) + return TOK_OF; + return TOK_IDENT; + case 'e': + if (match_identifier(p, "xport")) + return TOK_EXPORT; + return TOK_IDENT; + case 'f': + if (match_identifier(p, "unction")) + return TOK_FUNCTION; + return TOK_IDENT; + case '\\': + if (*p == 'u') { + if (lre_js_is_ident_first(lre_parse_escape(&p, TRUE))) + return TOK_IDENT; + } + break; default: - if (lre_js_is_ident_first(c)) { - if (c == 'i') { - if (p0 == 'n' && !lre_js_is_ident_next(p1)) { - return TOK_IN; - } - if (p0 == 'm' && p1 == 'p' && p2 == 'o' && - p3 == 'r' && p4 == 't' && - !lre_js_is_ident_next(p5)) { - *pp = p + 5; - return TOK_IMPORT; - } - } else if (c == 'o' && *p == 'f' && !lre_js_is_ident_next(p1)) { - return TOK_OF; - } else if (c == 'e' && - p0 == 'x' && p1 == 'p' && p2 == 'o' && - p3 == 'r' && p4 == 't' && - !lre_js_is_ident_next(p5)) { - *pp = p + 5; - return TOK_EXPORT; - } else if (c == 'f' && p0 == 'u' && p1 == 'n' && - p2 == 'c' && p3 == 't' && p4 == 'i' && - p5 == 'o' && p6 == 'n' && !lre_js_is_ident_next(p7)) { - return TOK_FUNCTION; - } - return TOK_IDENT; + if (c >= 128) { + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p); + if (no_line_terminator && (c == CP_PS || c == CP_LS)) + return '\n'; } + if (lre_is_space(c)) + continue; + if (lre_js_is_ident_first(c)) + return TOK_IDENT; break; } return c; @@ -21593,6 +22804,31 @@ return simple_next_token(&p, no_line_terminator); } +static void skip_shebang(const uint8_t **pp, const uint8_t *buf_end) +{ + const uint8_t *p = *pp; + int c; + + if (p0 == '#' && p1 == '!') { + p += 2; + while (p < buf_end) { + if (*p == '\n' || *p == '\r') { + break; + } else if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (c == CP_LS || c == CP_PS) { + break; + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + *pp = p; + } +} + /* return true if 'input' contains the source of a module (heuristic). 'input' must be a zero terminated. @@ -21603,6 +22839,8 @@ { const uint8_t *p = (const uint8_t *)input; int tok; + + skip_shebang(&p, p + input_len); switch(simple_next_token(&p, FALSE)) { case TOK_IMPORT: tok = simple_next_token(&p, FALSE); @@ -21615,7 +22853,7 @@ } static inline int get_prev_opcode(JSFunctionDef *fd) { - if (fd->last_opcode_pos < 0) + if (fd->last_opcode_pos < 0 || dbuf_error(&fd->byte_code)) return OP_invalid; else return fd->byte_code.buffd->last_opcode_pos; @@ -21657,26 +22895,34 @@ dbuf_put_u32(&s->cur_func->byte_code, val); } -static void emit_op(JSParseState *s, uint8_t val) +static void emit_source_pos(JSParseState *s, const uint8_t *source_ptr) { JSFunctionDef *fd = s->cur_func; DynBuf *bc = &fd->byte_code; - /* Use the line number of the last token used, not the next token, - nor the current offset in the source file. - */ - if (unlikely(fd->last_opcode_line_num != s->last_line_num)) { + if (unlikely(fd->last_opcode_source_ptr != source_ptr)) { dbuf_putc(bc, OP_line_num); - dbuf_put_u32(bc, s->last_line_num); - fd->last_opcode_line_num = s->last_line_num; + dbuf_put_u32(bc, source_ptr - s->buf_start); + fd->last_opcode_source_ptr = source_ptr; } +} + +static void emit_op(JSParseState *s, uint8_t val) +{ + JSFunctionDef *fd = s->cur_func; + DynBuf *bc = &fd->byte_code; + fd->last_opcode_pos = bc->size; dbuf_putc(bc, val); } static void emit_atom(JSParseState *s, JSAtom name) { - emit_u32(s, JS_DupAtom(s->ctx, name)); + DynBuf *bc = &s->cur_func->byte_code; + if (dbuf_claim(bc, 4)) + return; /* not enough memory : don't duplicate the atom */ + put_u32(bc->buf + bc->size, JS_DupAtom(s->ctx, name)); + bc->size += 4; } static int update_label(JSFunctionDef *s, int label, int delta) @@ -21690,29 +22936,41 @@ return ls->ref_count; } -static int new_label_fd(JSFunctionDef *fd, int label) +static int new_label_fd(JSFunctionDef *fd) { + int label; LabelSlot *ls; - if (label < 0) { - if (js_resize_array(fd->ctx, (void *)&fd->label_slots, - sizeof(fd->label_slots0), - &fd->label_size, fd->label_count + 1)) - return -1; - label = fd->label_count++; - ls = &fd->label_slotslabel; - ls->ref_count = 0; - ls->pos = -1; - ls->pos2 = -1; - ls->addr = -1; - ls->first_reloc = NULL; - } + if (js_resize_array(fd->ctx, (void *)&fd->label_slots, + sizeof(fd->label_slots0), + &fd->label_size, fd->label_count + 1)) + return -1; + label = fd->label_count++; + ls = &fd->label_slotslabel; + ls->ref_count = 0; + ls->pos = -1; + ls->pos2 = -1; + ls->addr = -1; + ls->first_reloc = NULL; return label; } static int new_label(JSParseState *s) { - return new_label_fd(s->cur_func, -1); + int label; + label = new_label_fd(s->cur_func); + if (unlikely(label < 0)) { + dbuf_set_error(&s->cur_func->byte_code); + } + return label; +} + +/* don't update the last opcode and don't emit line number info */ +static void emit_label_raw(JSParseState *s, int label) +{ + emit_u8(s, OP_label); + emit_u32(s, label); + s->cur_func->label_slotslabel.pos = s->cur_func->byte_code.size; } /* return the label ID offset */ @@ -21732,8 +22990,11 @@ static int emit_goto(JSParseState *s, int opcode, int label) { if (js_is_live_code(s)) { - if (label < 0) + if (label < 0) { label = new_label(s); + if (label < 0) + return -1; + } emit_op(s, opcode); emit_u32(s, label); s->cur_func->label_slotslabel.ref_count++; @@ -22213,7 +23474,7 @@ /* add a private field variable in the current scope */ static int add_private_class_field(JSParseState *s, JSFunctionDef *fd, - JSAtom name, JSVarKindEnum var_kind) + JSAtom name, JSVarKindEnum var_kind, BOOL is_static) { JSContext *ctx = s->ctx; JSVarDef *vd; @@ -22225,6 +23486,7 @@ vd = &fd->varsidx; vd->is_lexical = 1; vd->is_const = 1; + vd->is_static_private = is_static; return idx; } @@ -22232,15 +23494,13 @@ static __exception int js_parse_function_decl(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, - JSAtom func_name, const uint8_t *ptr, - int start_line); + JSAtom func_name, const uint8_t *ptr); static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s); static __exception int js_parse_function_decl2(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, - int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd); static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags); @@ -22363,7 +23623,6 @@ /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; - s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) return -1; } @@ -22415,14 +23674,20 @@ prop_type = PROP_TYPE_IDENT; if (allow_method) { - if (token_is_pseudo_keyword(s, JS_ATOM_get) - || token_is_pseudo_keyword(s, JS_ATOM_set)) { + /* if allow_private is true (for class field parsing) and + get/set is following by ';' (or LF with ASI), then it + is a field name */ + if ((token_is_pseudo_keyword(s, JS_ATOM_get) || + token_is_pseudo_keyword(s, JS_ATOM_set)) && + (!allow_private || peek_token(s, TRUE) != '\n')) { /* get x(), set x() */ name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; if (s->token.val == ':' || s->token.val == ',' || - s->token.val == '}' || s->token.val == '(') { + s->token.val == '}' || s->token.val == '(' || + s->token.val == '=' || + (s->token.val == ';' && allow_private)) { is_non_reserved_ident = TRUE; goto ident_found; } @@ -22438,7 +23703,8 @@ if (next_token(s)) goto fail1; if (s->token.val == ':' || s->token.val == ',' || - s->token.val == '}' || s->token.val == '(') { + s->token.val == '}' || s->token.val == '(' || + s->token.val == '=') { is_non_reserved_ident = TRUE; goto ident_found; } @@ -22478,21 +23744,7 @@ } else if (s->token.val == TOK_NUMBER) { JSValue val; val = s->token.u.num.val; -#ifdef CONFIG_BIGNUM - if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - val = s->ctx->rt->bigfloat_ops. - mul_pow10_to_float64(s->ctx, &p->num, - s->token.u.num.exponent); - if (JS_IsException(val)) - goto fail; - name = JS_ValueToAtom(s->ctx, val); - JS_FreeValue(s->ctx, val); - } else -#endif - { - name = JS_ValueToAtom(s->ctx, val); - } + name = JS_ValueToAtom(s->ctx, val); if (name == JS_ATOM_NULL) goto fail; if (next_token(s)) @@ -22500,7 +23752,7 @@ } else if (s->token.val == '') { if (next_token(s)) goto fail; - if (js_parse_expr(s)) + if (js_parse_assign_expr(s)) goto fail; if (js_parse_expect(s, '')) goto fail; @@ -22530,16 +23782,12 @@ } typedef struct JSParsePos { - int last_line_num; - int line_num; BOOL got_lf; const uint8_t *ptr; } JSParsePos; static int js_parse_get_pos(JSParseState *s, JSParsePos *sp) { - sp->last_line_num = s->last_line_num; - sp->line_num = s->token.line_num; sp->ptr = s->token.ptr; sp->got_lf = s->got_lf; return 0; @@ -22547,8 +23795,6 @@ static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp) { - s->token.line_num = sp->last_line_num; - s->line_num = sp->line_num; s->buf_ptr = sp->ptr; s->got_lf = sp->got_lf; return next_token(s); @@ -22581,6 +23827,17 @@ #define SKIP_HAS_ELLIPSIS (1 << 1) #define SKIP_HAS_ASSIGNMENT (1 << 2) +static BOOL has_lf_in_range(const uint8_t *p1, const uint8_t *p2) +{ + const uint8_t *tmp; + if (p1 > p2) { + tmp = p1; + p1 = p2; + p2 = tmp; + } + return (memchr(p1, '\n', p2 - p1) != NULL); +} + /* XXX: improve speed with early bailout */ /* XXX: no longer works if regexps are present. Could use previous regexp parsing heuristics to handle most cases */ @@ -22591,6 +23848,7 @@ JSParsePos pos; int last_tok, tok = TOK_EOF; int c, tok_len, bits = 0; + const uint8_t *last_token_ptr; /* protect from underflow */ statelevel++ = 0; @@ -22622,7 +23880,6 @@ /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; - s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) goto done; goto handle_template; @@ -22679,6 +23936,7 @@ } else { last_tok = s->token.val; } + last_token_ptr = s->token.ptr; if (next_token(s)) { /* XXX: should clear the exception generated by next_token() */ break; @@ -22687,7 +23945,7 @@ tok = s->token.val; if (token_is_pseudo_keyword(s, JS_ATOM_of)) tok = TOK_OF; - if (no_line_terminator && s->last_line_num != s->token.line_num) + if (no_line_terminator && has_lf_in_range(last_token_ptr, s->token.ptr)) tok = '\n'; break; } @@ -22754,7 +24012,7 @@ { JSAtom name = JS_ATOM_NULL; const uint8_t *start_ptr; - int start_line, prop_type; + int prop_type; BOOL has_proto; if (next_token(s)) @@ -22765,7 +24023,6 @@ while (s->token.val != '}') { /* specific case for getter/setter */ start_ptr = s->token.ptr; - start_line = s->token.line_num; if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) @@ -22811,7 +24068,7 @@ func_kind = JS_FUNC_ASYNC_GENERATOR; } if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL, - start_ptr, start_line)) + start_ptr)) goto fail; if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); @@ -22827,6 +24084,10 @@ } emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE); } else { + if (name == JS_ATOM_NULL) { + /* must be done before evaluating expr */ + emit_op(s, OP_to_propkey); + } if (js_parse_expect(s, ':')) goto fail; if (js_parse_assign_expr(s)) @@ -22868,53 +24129,81 @@ #define PF_IN_ACCEPTED (1 << 0) /* allow function calls parsing in js_parse_postfix_expr() */ #define PF_POSTFIX_CALL (1 << 1) -/* allow arrow functions parsing in js_parse_postfix_expr() */ -#define PF_ARROW_FUNC (1 << 2) /* allow the exponentiation operator in js_parse_unary() */ -#define PF_POW_ALLOWED (1 << 3) +#define PF_POW_ALLOWED (1 << 2) /* forbid the exponentiation operator in js_parse_unary() */ -#define PF_POW_FORBIDDEN (1 << 4) +#define PF_POW_FORBIDDEN (1 << 3) static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags); +static void emit_class_field_init(JSParseState *s); +static JSFunctionDef *js_new_function_def(JSContext *ctx, + JSFunctionDef *parent, + BOOL is_eval, + BOOL is_func_expr, + const char *filename, + const uint8_t *source_ptr, + GetLineColCache *get_line_col_cache); +static void emit_return(JSParseState *s, BOOL hasval); static __exception int js_parse_left_hand_side_expr(JSParseState *s) { return js_parse_postfix_expr(s, PF_POSTFIX_CALL); } -/* XXX: could generate specific bytecode */ static __exception int js_parse_class_default_ctor(JSParseState *s, BOOL has_super, JSFunctionDef **pfd) { - JSParsePos pos; - const char *str; - int ret, line_num; JSParseFunctionEnum func_type; - const uint8_t *saved_buf_end; + JSFunctionDef *fd = s->cur_func; + int idx; - js_parse_get_pos(s, &pos); + fd = js_new_function_def(s->ctx, fd, FALSE, FALSE, s->filename, + s->token.ptr, &s->get_line_col_cache); + if (!fd) + return -1; + + s->cur_func = fd; + fd->has_home_object = TRUE; + fd->super_allowed = TRUE; + fd->has_prototype = FALSE; + fd->has_this_binding = TRUE; + fd->new_target_allowed = TRUE; + + push_scope(s); /* enter body scope */ + fd->body_scope = fd->scope_level; if (has_super) { - /* spec change: no argument evaluation */ - str = "(){super(...arguments);}"; + fd->is_derived_class_constructor = TRUE; + fd->super_call_allowed = TRUE; + fd->arguments_allowed = TRUE; + fd->has_arguments_binding = TRUE; func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR; + emit_op(s, OP_init_ctor); + // TODO(bnoordhuis) roll into OP_init_ctor + emit_op(s, OP_scope_put_var_init); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + emit_class_field_init(s); } else { - str = "(){}"; func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; + /* error if not invoked as a constructor */ + emit_op(s, OP_check_ctor); + emit_class_field_init(s); } - line_num = s->token.line_num; - saved_buf_end = s->buf_end; - s->buf_ptr = (uint8_t *)str; - s->buf_end = (uint8_t *)(str + strlen(str)); - ret = next_token(s); - if (!ret) { - ret = js_parse_function_decl2(s, func_type, JS_FUNC_NORMAL, - JS_ATOM_NULL, (uint8_t *)str, - line_num, JS_PARSE_EXPORT_NONE, pfd); - } - s->buf_end = saved_buf_end; - ret |= js_parse_seek_token(s, &pos); - return ret; + + fd->func_kind = JS_FUNC_NORMAL; + fd->func_type = func_type; + emit_return(s, FALSE); + + s->cur_func = fd->parent; + if (pfd) + *pfd = fd; + + /* the real object will be set at the end of the compilation */ + idx = cpool_add(s, JS_NULL); + fd->parent_cpool_idx = idx; + + return 0; } /* find field in the current scope */ @@ -22970,8 +24259,9 @@ typedef struct { JSFunctionDef *fields_init_fd; int computed_fields_count; - BOOL has_brand; + BOOL need_brand; int brand_push_pos; + BOOL is_static; } ClassFieldsDef; static __exception int emit_class_init_start(JSParseState *s, @@ -22985,41 +24275,27 @@ s->cur_func = cf->fields_init_fd; - /* XXX: would be better to add the code only if needed, maybe in a - later pass */ - emit_op(s, OP_push_false); /* will be patched later */ - cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos; - label_add_brand = emit_goto(s, OP_if_false, -1); - - emit_op(s, OP_scope_get_var); - emit_atom(s, JS_ATOM_this); - emit_u16(s, 0); - - emit_op(s, OP_scope_get_var); - emit_atom(s, JS_ATOM_home_object); - emit_u16(s, 0); - - emit_op(s, OP_add_brand); + if (!cf->is_static) { + /* add the brand to the newly created instance */ + /* XXX: would be better to add the code only if needed, maybe in a + later pass */ + emit_op(s, OP_push_false); /* will be patched later */ + cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos; + label_add_brand = emit_goto(s, OP_if_false, -1); - emit_label(s, label_add_brand); + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); - s->cur_func = s->cur_func->parent; - return 0; -} + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_home_object); + emit_u16(s, 0); -static __exception int add_brand(JSParseState *s, ClassFieldsDef *cf) -{ - if (!cf->has_brand) { - /* define the brand field in 'this' of the initializer */ - if (!cf->fields_init_fd) { - if (emit_class_init_start(s, cf)) - return -1; - } - /* patch the start of the function to enable the OP_add_brand code */ - cf->fields_init_fd->byte_code.bufcf->brand_push_pos = OP_push_true; + emit_op(s, OP_add_brand); - cf->has_brand = TRUE; + emit_label(s, label_add_brand); } + s->cur_func = s->cur_func->parent; return 0; } @@ -23126,7 +24402,8 @@ ClassFieldsDef *cf = &class_fieldsi; cf->fields_init_fd = NULL; cf->computed_fields_count = 0; - cf->has_brand = FALSE; + cf->need_brand = FALSE; + cf->is_static = i; } ctor_fd = NULL; @@ -23136,11 +24413,51 @@ goto fail; continue; } - is_static = (s->token.val == TOK_STATIC); + is_static = FALSE; + if (s->token.val == TOK_STATIC) { + int next = peek_token(s, TRUE); + if (!(next == ';' || next == '}' || next == '(' || next == '=')) + is_static = TRUE; + } prop_type = -1; if (is_static) { if (next_token(s)) goto fail; + if (s->token.val == '{') { + ClassFieldsDef *cf = &class_fieldsis_static; + JSFunctionDef *init; + if (!cf->fields_init_fd) { + if (emit_class_init_start(s, cf)) + goto fail; + } + s->cur_func = cf->fields_init_fd; + /* XXX: could try to avoid creating a new function and + reuse 'fields_init_fd' with a specific 'var' + scope */ + // stack is now: <empty> + if (js_parse_function_decl2(s, JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + JS_PARSE_EXPORT_NONE, &init) < 0) { + goto fail; + } + // stack is now: fclosure + push_scope(s); + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + // stack is now: fclosure this + emit_op(s, OP_swap); + // stack is now: this fclosure + emit_op(s, OP_call_method); + emit_u16(s, 0); + // stack is now: returnvalue + emit_op(s, OP_drop); + // stack is now: <empty> + pop_scope(s); + s->cur_func = s->cur_func->parent; + continue; + } /* allow "static" field name */ if (s->token.val == ';' || s->token.val == '=') { is_static = FALSE; @@ -23171,29 +24488,31 @@ JSFunctionDef *method_fd; if (is_private) { - int idx, var_kind; + int idx, var_kind, is_static1; idx = find_private_class_field(ctx, fd, name, fd->scope_level); if (idx >= 0) { var_kind = fd->varsidx.var_kind; + is_static1 = fd->varsidx.is_static_private; if (var_kind == JS_VAR_PRIVATE_FIELD || var_kind == JS_VAR_PRIVATE_METHOD || var_kind == JS_VAR_PRIVATE_GETTER_SETTER || - var_kind == (JS_VAR_PRIVATE_GETTER + is_set)) { + var_kind == (JS_VAR_PRIVATE_GETTER + is_set) || + (var_kind == (JS_VAR_PRIVATE_GETTER + 1 - is_set) && + is_static != is_static1)) { goto private_field_already_defined; } fd->varsidx.var_kind = JS_VAR_PRIVATE_GETTER_SETTER; } else { if (add_private_class_field(s, fd, name, - JS_VAR_PRIVATE_GETTER + is_set) < 0) + JS_VAR_PRIVATE_GETTER + is_set, is_static) < 0) goto fail; } - if (add_brand(s, &class_fieldsis_static) < 0) - goto fail; + class_fieldsis_static.need_brand = TRUE; } if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set, JS_FUNC_NORMAL, JS_ATOM_NULL, - start_ptr, s->token.line_num, + start_ptr, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (is_private) { @@ -23210,7 +24529,7 @@ goto fail; emit_atom(s, setter_name); ret = add_private_class_field(s, fd, setter_name, - JS_VAR_PRIVATE_SETTER); + JS_VAR_PRIVATE_SETTER, is_static); JS_FreeAtom(ctx, setter_name); if (ret < 0) goto fail; @@ -23245,7 +24564,7 @@ goto private_field_already_defined; } if (add_private_class_field(s, fd, name, - JS_VAR_PRIVATE_FIELD) < 0) + JS_VAR_PRIVATE_FIELD, is_static) < 0) goto fail; emit_op(s, OP_private_symbol); emit_atom(s, name); @@ -23335,10 +24654,9 @@ func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; } if (is_private) { - if (add_brand(s, &class_fieldsis_static) < 0) - goto fail; + class_fieldsis_static.need_brand = TRUE; } - if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd)) + if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { @@ -23352,7 +24670,7 @@ goto fail; } if (add_private_class_field(s, fd, name, - JS_VAR_PRIVATE_METHOD) < 0) + JS_VAR_PRIVATE_METHOD, is_static) < 0) goto fail; emit_op(s, OP_set_home_object); emit_op(s, OP_set_name); @@ -23389,7 +24707,7 @@ put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx); /* store the class source code in the constructor. */ - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_source) { js_free(ctx, ctor_fd->source); ctor_fd->source_len = s->buf_ptr - class_start_ptr; ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr, @@ -23402,12 +24720,29 @@ if (next_token(s)) goto fail; - /* store the function to initialize the fields to that it can be - referenced by the constructor */ { ClassFieldsDef *cf = &class_fields0; int var_idx; + if (cf->need_brand) { + /* add a private brand to the prototype */ + emit_op(s, OP_dup); + emit_op(s, OP_null); + emit_op(s, OP_swap); + emit_op(s, OP_add_brand); + + /* define the brand field in 'this' of the initializer */ + if (!cf->fields_init_fd) { + if (emit_class_init_start(s, cf)) + goto fail; + } + /* patch the start of the function to enable the + OP_add_brand_instance code */ + cf->fields_init_fd->byte_code.bufcf->brand_push_pos = OP_push_true; + } + + /* store the function to initialize the fields to that it can be + referenced by the constructor */ var_idx = define_var(s, fd, JS_ATOM_class_fields_init, JS_VAR_DEF_CONST); if (var_idx < 0) @@ -23425,14 +24760,11 @@ /* drop the prototype */ emit_op(s, OP_drop); - /* initialize the static fields */ - if (class_fields1.fields_init_fd != NULL) { - ClassFieldsDef *cf = &class_fields1; + if (class_fields1.need_brand) { + /* add a private brand to the class */ emit_op(s, OP_dup); - emit_class_init_end(s, cf); - emit_op(s, OP_call_method); - emit_u16(s, 0); - emit_op(s, OP_drop); + emit_op(s, OP_dup); + emit_op(s, OP_add_brand); } if (class_name != JS_ATOM_NULL) { @@ -23444,6 +24776,17 @@ emit_atom(s, class_name); emit_u16(s, fd->scope_level); } + + /* initialize the static fields */ + if (class_fields1.fields_init_fd != NULL) { + ClassFieldsDef *cf = &class_fields1; + emit_op(s, OP_dup); + emit_class_init_end(s, cf); + emit_op(s, OP_call_method); + emit_u16(s, 0); + emit_op(s, OP_drop); + } + pop_scope(s); pop_scope(s); @@ -23692,6 +25035,8 @@ switch(opcode) { case OP_scope_get_var: label = new_label(s); + if (label < 0) + return -1; emit_op(s, OP_scope_make_ref); emit_atom(s, name); emit_u32(s, label); @@ -23710,10 +25055,7 @@ emit_u16(s, scope); break; case OP_get_array_el: - /* XXX: replace by a single opcode ? */ - emit_op(s, OP_to_propkey2); - emit_op(s, OP_dup2); - emit_op(s, OP_get_array_el); + emit_op(s, OP_get_array_el3); break; case OP_get_super_value: emit_op(s, OP_to_propkey); @@ -23727,6 +25069,8 @@ switch(opcode) { case OP_scope_get_var: label = new_label(s); + if (label < 0) + return -1; emit_op(s, OP_scope_make_ref); emit_atom(s, name); emit_u32(s, label); @@ -23734,11 +25078,7 @@ update_label(fd, label, 1); opcode = OP_get_ref_value; break; - case OP_get_array_el: - emit_op(s, OP_to_propkey2); - break; - case OP_get_super_value: - emit_op(s, OP_to_propkey); + default: break; } } @@ -23900,7 +25240,7 @@ && (fd->js_mode & JS_MODE_STRICT)) { return js_parse_error(s, "invalid variable name in strict mode"); } - if ((name == JS_ATOM_let || name == JS_ATOM_undefined) + if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) { return js_parse_error(s, "invalid lexical variable name"); } @@ -23970,6 +25310,25 @@ return js_parse_error(s, "duplicate parameter names not allowed in this context"); } +/* tok = TOK_VAR, TOK_LET or TOK_CONST. Return whether a reference + must be taken to the variable for proper 'with' or global variable + evaluation */ +/* Note: this function is needed only because variable references are + not yet optimized in destructuring */ +static BOOL need_var_reference(JSParseState *s, int tok) +{ + JSFunctionDef *fd = s->cur_func; + if (tok != TOK_VAR) + return FALSE; /* no reference for let/const */ + if (fd->js_mode & JS_MODE_STRICT) { + if (!fd->is_global_var) + return FALSE; /* local definitions in strict mode in function or direct eval */ + if (s->is_module) + return FALSE; /* in a module global variables are like closure variables */ + } + return TRUE; +} + static JSAtom js_parse_destructuring_var(JSParseState *s, int tok, int is_arg) { JSAtom name; @@ -23996,7 +25355,7 @@ present at the top level. */ static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, int hasval, int has_ellipsis, - BOOL allow_initializer) + BOOL allow_initializer, BOOL export_flag) { int label_parse, label_assign, label_done, label_lvalue, depth_lvalue; int start_addr, assign_addr; @@ -24051,14 +25410,23 @@ var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) return -1; - opcode = OP_scope_get_var; - scope = s->cur_func->scope_level; - label_lvalue = -1; - depth_lvalue = 0; + if (need_var_reference(s, tok)) { + /* Must make a reference for proper `with` semantics */ + emit_op(s, OP_scope_get_var); + emit_atom(s, var_name); + emit_u16(s, s->cur_func->scope_level); + JS_FreeAtom(s->ctx, var_name); + goto lvalue0; + } else { + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + } } else { if (js_parse_left_hand_side_expr(s)) return -1; - + lvalue0: if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) return -1; @@ -24076,10 +25444,6 @@ if (prop_type < 0) return -1; var_name = JS_ATOM_NULL; - opcode = OP_scope_get_var; - scope = s->cur_func->scope_level; - label_lvalue = -1; - depth_lvalue = 0; if (prop_type == PROP_TYPE_IDENT) { if (next_token(s)) goto prop_error; @@ -24112,7 +25476,7 @@ emit_op(s, OP_get_field2); emit_u32(s, prop_name); } - if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE) < 0) + if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE, export_flag) < 0) return -1; if (s->token.val == '}') break; @@ -24122,7 +25486,7 @@ continue; } if (prop_name == JS_ATOM_NULL) { - emit_op(s, OP_to_propkey2); + emit_op(s, OP_to_propkey); if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_perm3); @@ -24148,10 +25512,24 @@ var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) goto prop_error; + if (need_var_reference(s, tok)) { + /* Must make a reference for proper `with` semantics */ + emit_op(s, OP_scope_get_var); + emit_atom(s, var_name); + emit_u16(s, s->cur_func->scope_level); + JS_FreeAtom(s->ctx, var_name); + goto lvalue1; + } else { + /* no need to make a reference for let/const */ + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + } } else { if (js_parse_left_hand_side_expr(s)) goto prop_error; - lvalue: + lvalue1: if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) goto prop_error; @@ -24218,25 +25596,37 @@ emit_atom(s, prop_name); emit_op(s, OP_swap); } - if (!tok || tok == TOK_VAR) { + if (!tok || need_var_reference(s, tok)) { /* generate reference */ /* source -- source source */ emit_op(s, OP_dup); emit_op(s, OP_scope_get_var); emit_atom(s, prop_name); emit_u16(s, s->cur_func->scope_level); - goto lvalue; + goto lvalue1; + } else { + /* no need to make a reference for let/const */ + var_name = JS_DupAtom(s->ctx, prop_name); + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + + /* source -- source val */ + emit_op(s, OP_get_field2); + emit_u32(s, prop_name); } - var_name = JS_DupAtom(s->ctx, prop_name); - /* source -- source val */ - emit_op(s, OP_get_field2); - emit_u32(s, prop_name); } set_val: if (tok) { if (js_define_var(s, var_name, tok)) goto var_error; - scope = s->cur_func->scope_level; + if (export_flag) { + if (!add_export_entry(s, s->cur_func->module, var_name, var_name, + JS_EXPORT_TYPE_LOCAL)) + goto var_error; + } + scope = s->cur_func->scope_level; /* XXX: check */ } if (s->token.val == '=') { /* handle optional default value */ int label_hasval; @@ -24311,22 +25701,34 @@ emit_u8(s, 0); emit_op(s, OP_drop); } - if (js_parse_destructuring_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + if (js_parse_destructuring_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE, export_flag) < 0) return -1; } else { var_name = JS_ATOM_NULL; - enum_depth = 0; if (tok) { var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) goto var_error; if (js_define_var(s, var_name, tok)) goto var_error; - opcode = OP_scope_get_var; - scope = s->cur_func->scope_level; + if (need_var_reference(s, tok)) { + /* Must make a reference for proper `with` semantics */ + emit_op(s, OP_scope_get_var); + emit_atom(s, var_name); + emit_u16(s, s->cur_func->scope_level); + JS_FreeAtom(s->ctx, var_name); + goto lvalue2; + } else { + /* no need to make a reference for let/const */ + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + enum_depth = 0; + } } else { if (js_parse_left_hand_side_expr(s)) return -1; + lvalue2: if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &enum_depth, FALSE, '')) { return -1; @@ -24436,12 +25838,13 @@ emit_label(s, label_next); } -/* allowed parse_flags: PF_POSTFIX_CALL, PF_ARROW_FUNC */ +/* allowed parse_flags: PF_POSTFIX_CALL */ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) { FuncCallType call_type; int optional_chaining_label; BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; + const uint8_t *op_token_ptr; call_type = FUNC_CALL_NORMAL; switch(s->token.val) { @@ -24453,34 +25856,17 @@ if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { emit_op(s, OP_push_i32); emit_u32(s, JS_VALUE_GET_INT(val)); - } else -#ifdef CONFIG_BIGNUM - if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { - slimb_t e; - int ret; - - /* need a runtime conversion */ - /* XXX: could add a cache and/or do it once at - the start of the function */ - if (emit_push_const(s, val, 0) < 0) - return -1; - e = s->token.u.num.exponent; - if (e == (int32_t)e) { - emit_op(s, OP_push_i32); - emit_u32(s, e); + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + int64_t v; + v = JS_VALUE_GET_SHORT_BIG_INT(val); + if (v >= INT32_MIN && v <= INT32_MAX) { + emit_op(s, OP_push_bigint_i32); + emit_u32(s, v); } else { - val = JS_NewBigInt64_1(s->ctx, e); - if (JS_IsException(val)) - return -1; - ret = emit_push_const(s, val, 0); - JS_FreeValue(s->ctx, val); - if (ret < 0) - return -1; + goto large_number; } - emit_op(s, OP_mul_pow10); - } else -#endif - { + } else { + large_number: if (emit_push_const(s, val, 0) < 0) return -1; } @@ -24507,7 +25893,7 @@ parse_regexp: { JSValue str; - int ret, backtrace_flags; + int ret; if (!s->ctx->compile_regexp) return js_parse_error(s, "RegExp are not supported"); /* the previous token is '/' or '/=', so no need to free */ @@ -24518,12 +25904,10 @@ s->token.u.regexp.flags); if (JS_IsException(str)) { /* add the line number info */ - backtrace_flags = 0; - if (s->cur_func && s->cur_func->backtrace_barrier) - backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + int line_num, col_num; + line_num = get_line_col(&col_num, s->buf_start, s->token.ptr - s->buf_start); build_backtrace(s->ctx, s->ctx->rt->current_exception, - s->filename, s->token.line_num, - backtrace_flags); + s->filename, line_num + 1, col_num + 1, 0); return -1; } ret = emit_push_const(s, str, 0); @@ -24539,21 +25923,13 @@ } break; case '(': - if ((parse_flags & PF_ARROW_FUNC) && - js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) { - if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, - JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) - return -1; - } else { - if (js_parse_expr_paren(s)) - return -1; - } + if (js_parse_expr_paren(s)) + return -1; break; case TOK_FUNCTION: if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) + s->token.ptr)) return -1; break; case TOK_CLASS: @@ -24585,37 +25961,19 @@ case TOK_IDENT: { JSAtom name; + const uint8_t *source_ptr; if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier(s); } - if ((parse_flags & PF_ARROW_FUNC) && - peek_token(s, TRUE) == TOK_ARROW) { - if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, - JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) - return -1; - } else if (token_is_pseudo_keyword(s, JS_ATOM_async) && - peek_token(s, TRUE) != '\n') { - const uint8_t *source_ptr; - int source_line_num; - - source_ptr = s->token.ptr; - source_line_num = s->token.line_num; + source_ptr = s->token.ptr; + if (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) != '\n') { if (next_token(s)) return -1; if (s->token.val == TOK_FUNCTION) { if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_ASYNC, JS_ATOM_NULL, - source_ptr, source_line_num)) - return -1; - } else if ((parse_flags & PF_ARROW_FUNC) && - ((s->token.val == '(' && - js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) || - (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && - peek_token(s, TRUE) == TOK_ARROW))) { - if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, - JS_FUNC_ASYNC, JS_ATOM_NULL, - source_ptr, source_line_num)) + source_ptr)) return -1; } else { name = JS_DupAtom(s->ctx, JS_ATOM_async); @@ -24628,9 +25986,12 @@ return -1; } name = JS_DupAtom(s->ctx, s->token.u.ident.atom); - if (next_token(s)) /* update line number before emitting code */ + if (next_token(s)) { + JS_FreeAtom(s->ctx, name); return -1; + } do_get_var: + emit_source_pos(s, source_ptr); emit_op(s, OP_scope_get_var); emit_u32(s, name); emit_u16(s, s->cur_func->scope_level); @@ -24639,20 +26000,12 @@ break; case '{': case '': - { - int skip_bits; - if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { - if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) - return -1; - } else { - if (s->token.val == '{') { - if (js_parse_object_literal(s)) - return -1; - } else { - if (js_parse_array_literal(s)) - return -1; - } - } + if (s->token.val == '{') { + if (js_parse_object_literal(s)) + return -1; + } else { + if (js_parse_array_literal(s)) + return -1; } break; case TOK_NEW: @@ -24676,6 +26029,7 @@ accept_lparen = TRUE; if (s->token.val != '(') { /* new operator on an object */ + emit_source_pos(s, s->token.ptr); emit_op(s, OP_dup); emit_op(s, OP_call_constructor); emit_u16(s, 0); @@ -24726,6 +26080,23 @@ return js_parse_error(s, "invalid use of 'import()'"); if (js_parse_assign_expr(s)) return -1; + if (s->token.val == ',') { + if (next_token(s)) + return -1; + if (s->token.val != ')') { + if (js_parse_assign_expr(s)) + return -1; + /* accept a trailing comma */ + if (s->token.val == ',') { + if (next_token(s)) + return -1; + } + } else { + emit_op(s, OP_undefined); + } + } else { + emit_op(s, OP_undefined); + } if (js_parse_expect(s, ')')) return -1; emit_op(s, OP_import); @@ -24742,6 +26113,9 @@ BOOL has_optional_chain = FALSE; if (s->token.val == TOK_QUESTION_MARK_DOT) { + if ((parse_flags & PF_POSTFIX_CALL) == 0) + return js_parse_error(s, "new keyword cannot be used with an optional chain"); + op_token_ptr = s->token.ptr; /* optional chaining */ if (next_token(s)) return -1; @@ -24759,12 +26133,14 @@ return js_parse_error(s, "template literal cannot appear in an optional chain"); } call_type = FUNC_CALL_TEMPLATE; + op_token_ptr = s->token.ptr; /* XXX: check if right position */ goto parse_func_call2; } else if (s->token.val == '(' && accept_lparen) { int opcode, arg_count, drop_count; /* function call */ parse_func_call: + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; @@ -24776,6 +26152,25 @@ fd->byte_code.buffd->last_opcode_pos = OP_get_field2; drop_count = 2; break; + case OP_get_field_opt_chain: + { + int opt_chain_label, next_label; + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 4 + 1); + /* keep the object on the stack */ + fd->byte_code.buffd->last_opcode_pos = OP_get_field2; + fd->byte_code.size = fd->last_opcode_pos + 1 + 4; + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op(s, OP_undefined); + emit_label(s, next_label); + drop_count = 2; + opcode = OP_get_field; + } + break; case OP_scope_get_private_field: /* keep the object on the stack */ fd->byte_code.buffd->last_opcode_pos = OP_scope_get_private_field2; @@ -24786,6 +26181,25 @@ fd->byte_code.buffd->last_opcode_pos = OP_get_array_el2; drop_count = 2; break; + case OP_get_array_el_opt_chain: + { + int opt_chain_label, next_label; + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 1); + /* keep the object on the stack */ + fd->byte_code.buffd->last_opcode_pos = OP_get_array_el2; + fd->byte_code.size = fd->last_opcode_pos + 1; + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op(s, OP_undefined); + emit_label(s, next_label); + drop_count = 2; + opcode = OP_get_array_el; + } + break; case OP_scope_get_var: { JSAtom name; @@ -24925,6 +26339,7 @@ /* drop the index */ emit_op(s, OP_drop); + emit_source_pos(s, op_token_ptr); /* apply function call */ switch(opcode) { case OP_get_field: @@ -24970,6 +26385,7 @@ if (next_token(s)) return -1; emit_func_call: + emit_source_pos(s, op_token_ptr); switch(opcode) { case OP_get_field: case OP_scope_get_private_field: @@ -25008,9 +26424,11 @@ } call_type = FUNC_CALL_NORMAL; } else if (s->token.val == '.') { + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; parse_property: + emit_source_pos(s, op_token_ptr); if (s->token.val == TOK_PRIVATE_NAME) { /* private class field */ if (get_prev_opcode(fd) == OP_get_super) { @@ -25047,7 +26465,7 @@ return -1; } else if (s->token.val == '') { int prev_op; - + op_token_ptr = s->token.ptr; parse_array_access: prev_op = get_prev_opcode(fd); if (has_optional_chain) { @@ -25059,6 +26477,7 @@ return -1; if (js_parse_expect(s, '')) return -1; + emit_source_pos(s, op_token_ptr); if (prev_op == OP_get_super) { emit_op(s, OP_get_super_value); } else { @@ -25068,8 +26487,23 @@ break; } } - if (optional_chaining_label >= 0) - emit_label(s, optional_chaining_label); + if (optional_chaining_label >= 0) { + JSFunctionDef *fd = s->cur_func; + int opcode; + emit_label_raw(s, optional_chaining_label); + /* modify the last opcode so that it is an indicator of an + optional chain */ + opcode = get_prev_opcode(fd); + if (opcode == OP_get_field || opcode == OP_get_array_el) { + if (opcode == OP_get_field) + opcode = OP_get_field_opt_chain; + else + opcode = OP_get_array_el_opt_chain; + fd->byte_code.buffd->last_opcode_pos = opcode; + } else { + fd->last_opcode_pos = -1; + } + } return 0; } @@ -25085,27 +26519,57 @@ return -1; switch(opcode = get_prev_opcode(fd)) { case OP_get_field: + case OP_get_field_opt_chain: { JSValue val; - int ret; - + int ret, opt_chain_label, next_label; + if (opcode == OP_get_field_opt_chain) { + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 4 + 1); + } else { + opt_chain_label = -1; + } name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); fd->byte_code.size = fd->last_opcode_pos; - fd->last_opcode_pos = -1; val = JS_AtomToValue(s->ctx, name); ret = emit_push_const(s, val, 1); JS_FreeValue(s->ctx, val); JS_FreeAtom(s->ctx, name); if (ret) return ret; + emit_op(s, OP_delete); + if (opt_chain_label >= 0) { + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* if the optional chain is not taken, return 'true' */ + emit_op(s, OP_drop); + emit_op(s, OP_push_true); + emit_label(s, next_label); + } + fd->last_opcode_pos = -1; } - goto do_delete; + break; case OP_get_array_el: fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; - do_delete: emit_op(s, OP_delete); break; + case OP_get_array_el_opt_chain: + { + int opt_chain_label, next_label; + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 1); + fd->byte_code.size = fd->last_opcode_pos; + emit_op(s, OP_delete); + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* if the optional chain is not taken, return 'true' */ + emit_op(s, OP_drop); + emit_op(s, OP_push_true); + emit_label(s, next_label); + fd->last_opcode_pos = -1; + } + break; case OP_scope_get_var: /* 'delete this': this is not a reference */ name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); @@ -25120,6 +26584,8 @@ case OP_scope_get_private_field: return js_parse_error(s, "cannot delete a private class field"); case OP_get_super_value: + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; emit_op(s, OP_throw_error); emit_atom(s, JS_ATOM_NULL); emit_u8(s, JS_THROW_ERROR_DELETE_SUPER); @@ -25133,10 +26599,11 @@ return 0; } -/* allowed parse_flags: PF_ARROW_FUNC, PF_POW_ALLOWED, PF_POW_FORBIDDEN */ +/* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */ static __exception int js_parse_unary(JSParseState *s, int parse_flags) { int op; + const uint8_t *op_token_ptr; switch(s->token.val) { case '+': @@ -25144,6 +26611,7 @@ case '!': case '~': case TOK_VOID: + op_token_ptr = s->token.ptr; op = s->token.val; if (next_token(s)) return -1; @@ -25151,15 +26619,18 @@ return -1; switch(op) { case '-': + emit_source_pos(s, op_token_ptr); emit_op(s, OP_neg); break; case '+': + emit_source_pos(s, op_token_ptr); emit_op(s, OP_plus); break; case '!': emit_op(s, OP_lnot); break; case '~': + emit_source_pos(s, op_token_ptr); emit_op(s, OP_not); break; case TOK_VOID: @@ -25177,12 +26648,14 @@ int opcode, op, scope, label; JSAtom name; op = s->token.val; + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (js_parse_unary(s, 0)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; + emit_source_pos(s, op_token_ptr); emit_op(s, OP_dec + op - TOK_DEC); put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); @@ -25219,20 +26692,22 @@ return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; + s->cur_func->has_await = TRUE; emit_op(s, OP_await); parse_flags = 0; break; default: - if (js_parse_postfix_expr(s, (parse_flags & PF_ARROW_FUNC) | - PF_POSTFIX_CALL)) + if (js_parse_postfix_expr(s, PF_POSTFIX_CALL)) return -1; if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { int opcode, op, scope, label; JSAtom name; op = s->token.val; + op_token_ptr = s->token.ptr; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; + emit_source_pos(s, op_token_ptr); emit_op(s, OP_post_dec + op - TOK_DEC); put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, FALSE); @@ -25242,24 +26717,6 @@ break; } if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) { -#ifdef CONFIG_BIGNUM - if (s->token.val == TOK_POW || s->token.val == TOK_MATH_POW) { - /* Extended exponentiation syntax rules: we extend the ES7 - grammar in order to have more intuitive semantics: - -2**2 evaluates to -4. */ - if (!(s->cur_func->js_mode & JS_MODE_MATH)) { - if (parse_flags & PF_POW_FORBIDDEN) { - JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'"); - return -1; - } - } - if (next_token(s)) - return -1; - if (js_parse_unary(s, PF_POW_ALLOWED)) - return -1; - emit_op(s, OP_pow); - } -#else if (s->token.val == TOK_POW) { /* Strict ES7 exponentiation syntax rules: To solve conficting semantics between different implementations @@ -25270,31 +26727,56 @@ JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'"); return -1; } + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_ALLOWED)) return -1; + emit_source_pos(s, op_token_ptr); emit_op(s, OP_pow); } -#endif } return 0; } -/* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */ +/* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_expr_binary(JSParseState *s, int level, int parse_flags) { int op, opcode; + const uint8_t *op_token_ptr; if (level == 0) { - return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) | - PF_POW_ALLOWED); + return js_parse_unary(s, PF_POW_ALLOWED); + } else if (s->token.val == TOK_PRIVATE_NAME && + (parse_flags & PF_IN_ACCEPTED) && level == 4 && + peek_token(s, FALSE) == TOK_IN) { + JSAtom atom; + + atom = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail_private_in; + if (s->token.val != TOK_IN) + goto fail_private_in; + if (next_token(s)) + goto fail_private_in; + if (js_parse_expr_binary(s, level - 1, parse_flags)) { + fail_private_in: + JS_FreeAtom(s->ctx, atom); + return -1; + } + emit_op(s, OP_scope_in_private_field); + emit_atom(s, atom); + emit_u16(s, s->cur_func->scope_level); + JS_FreeAtom(s->ctx, atom); + return 0; + } else { + if (js_parse_expr_binary(s, level - 1, parse_flags)) + return -1; } - if (js_parse_expr_binary(s, level - 1, parse_flags)) - return -1; for(;;) { op = s->token.val; + op_token_ptr = s->token.ptr; switch(level) { case 1: switch(op) { @@ -25305,12 +26787,7 @@ opcode = OP_div; break; case '%': -#ifdef CONFIG_BIGNUM - if (s->cur_func->js_mode & JS_MODE_MATH) - opcode = OP_math_mod; - else -#endif - opcode = OP_mod; + opcode = OP_mod; break; default: return 0; @@ -25421,14 +26898,15 @@ } if (next_token(s)) return -1; - if (js_parse_expr_binary(s, level - 1, parse_flags & ~PF_ARROW_FUNC)) + if (js_parse_expr_binary(s, level - 1, parse_flags)) return -1; + emit_source_pos(s, op_token_ptr); emit_op(s, opcode); } return 0; } -/* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */ +/* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_logical_and_or(JSParseState *s, int op, int parse_flags) { @@ -25452,11 +26930,11 @@ emit_op(s, OP_drop); if (op == TOK_LAND) { - if (js_parse_expr_binary(s, 8, parse_flags & ~PF_ARROW_FUNC)) + if (js_parse_expr_binary(s, 8, parse_flags)) return -1; } else { if (js_parse_logical_and_or(s, TOK_LAND, - parse_flags & ~PF_ARROW_FUNC)) + parse_flags)) return -1; } if (s->token.val != op) { @@ -25488,7 +26966,7 @@ emit_goto(s, OP_if_false, label1); emit_op(s, OP_drop); - if (js_parse_expr_binary(s, 8, parse_flags & ~PF_ARROW_FUNC)) + if (js_parse_expr_binary(s, 8, parse_flags)) return -1; if (s->token.val != TOK_DOUBLE_QUESTION_MARK) break; @@ -25498,7 +26976,7 @@ return 0; } -/* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */ +/* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_cond_expr(JSParseState *s, int parse_flags) { int label1, label2; @@ -25527,12 +27005,10 @@ return 0; } -static void emit_return(JSParseState *s, BOOL hasval); - /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) { - int opcode, op, scope; + int opcode, op, scope, skip_bits; JSAtom name0 = JS_ATOM_NULL; JSAtom name; @@ -25592,7 +27068,6 @@ /* OP_async_yield_star takes the value as parameter */ emit_op(s, OP_get_field); emit_atom(s, JS_ATOM_value); - emit_op(s, OP_await); emit_op(s, OP_async_yield_star); } else { /* OP_yield_star takes (value, done) as parameter */ @@ -25674,17 +27149,61 @@ emit_label(s, label_next); } return 0; + } else if (s->token.val == '(' && + js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) { + return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr); + } else if (token_is_pseudo_keyword(s, JS_ATOM_async)) { + const uint8_t *source_ptr; + int tok; + JSParsePos pos; + + /* fast test */ + tok = peek_token(s, TRUE); + if (tok == TOK_FUNCTION || tok == '\n') + goto next; + + source_ptr = s->token.ptr; + js_parse_get_pos(s, &pos); + if (next_token(s)) + return -1; + if ((s->token.val == '(' && + js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) || + (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && + peek_token(s, TRUE) == TOK_ARROW)) { + return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, + JS_FUNC_ASYNC, JS_ATOM_NULL, + source_ptr); + } else { + /* undo the token parsing */ + if (js_parse_seek_token(s, &pos)) + return -1; + } + } else if (s->token.val == TOK_IDENT && + peek_token(s, TRUE) == TOK_ARROW) { + return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr); + } else if ((s->token.val == '{' || s->token.val == '') && + js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { + if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE, FALSE) < 0) + return -1; + return 0; } + next: if (s->token.val == TOK_IDENT) { /* name0 is used to check for OP_set_name pattern, not duplicated */ name0 = s->token.u.ident.atom; } - if (js_parse_cond_expr(s, parse_flags | PF_ARROW_FUNC)) + if (js_parse_cond_expr(s, parse_flags)) return -1; op = s->token.val; if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) { int label; + const uint8_t *op_token_ptr; + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0) @@ -25703,18 +27222,10 @@ static const uint8_t assign_opcodes = { OP_mul, OP_div, OP_mod, OP_add, OP_sub, OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or, -#ifdef CONFIG_BIGNUM - OP_pow, -#endif OP_pow, }; op = assign_opcodesop - TOK_MUL_ASSIGN; -#ifdef CONFIG_BIGNUM - if (s->cur_func->js_mode & JS_MODE_MATH) { - if (op == OP_mod) - op = OP_math_mod; - } -#endif + emit_source_pos(s, op_token_ptr); emit_op(s, op); } put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); @@ -25825,6 +27336,7 @@ be->label_finally = -1; be->scope_level = fd->scope_level; be->has_iterator = FALSE; + be->is_regular_stmt = FALSE; } static void pop_break_entry(JSFunctionDef *fd) @@ -25853,7 +27365,8 @@ } if (!is_cont && top->label_break != -1 && - (name == JS_ATOM_NULL || top->label_name == name)) { + ((name == JS_ATOM_NULL && !top->is_regular_stmt) || + top->label_name == name)) { emit_goto(s, OP_goto, top->label_break); return 0; } @@ -25886,61 +27399,61 @@ static void emit_return(JSParseState *s, BOOL hasval) { BlockEnv *top; - int drop_count; - drop_count = 0; + if (s->cur_func->func_kind != JS_FUNC_NORMAL) { + if (!hasval) { + /* no value: direct return in case of async generator */ + emit_op(s, OP_undefined); + hasval = TRUE; + } else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { + /* the await must be done before handling the "finally" in + case it raises an exception */ + emit_op(s, OP_await); + } + } + top = s->cur_func->top_break; while (top != NULL) { - /* XXX: emit the appropriate OP_leave_scope opcodes? Probably not - required as all local variables will be closed upon returning - from JS_CallInternal, but not in the same order. */ - if (top->has_iterator) { - /* with 'yield', the exact number of OP_drop to emit is - unknown, so we use a specific operation to look for - the catch offset */ + if (top->has_iterator || top->label_finally != -1) { if (!hasval) { emit_op(s, OP_undefined); hasval = TRUE; } - emit_op(s, OP_iterator_close_return); - if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { - int label_next, label_next2; - - emit_op(s, OP_drop); /* catch offset */ - emit_op(s, OP_drop); /* next */ - emit_op(s, OP_get_field2); - emit_atom(s, JS_ATOM_return); - /* stack: iter_obj return_func */ - emit_op(s, OP_dup); - emit_op(s, OP_is_undefined_or_null); - label_next = emit_goto(s, OP_if_true, -1); - emit_op(s, OP_call_method); - emit_u16(s, 0); - emit_op(s, OP_iterator_check_object); - emit_op(s, OP_await); - label_next2 = emit_goto(s, OP_goto, -1); - emit_label(s, label_next); - emit_op(s, OP_drop); - emit_label(s, label_next2); - emit_op(s, OP_drop); + /* Remove the stack elements up to and including the catch + offset. When 'yield' is used in an expression we have + no easy way to count them, so we use this specific + instruction instead. */ + emit_op(s, OP_nip_catch); + /* stack: iter_obj next ret_val */ + if (top->has_iterator) { + if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { + int label_next, label_next2; + emit_op(s, OP_nip); /* next */ + emit_op(s, OP_swap); + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_return); + /* stack: iter_obj return_func */ + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined_or_null); + label_next = emit_goto(s, OP_if_true, -1); + emit_op(s, OP_call_method); + emit_u16(s, 0); + emit_op(s, OP_iterator_check_object); + emit_op(s, OP_await); + label_next2 = emit_goto(s, OP_goto, -1); + emit_label(s, label_next); + emit_op(s, OP_drop); + emit_label(s, label_next2); + emit_op(s, OP_drop); + } else { + emit_op(s, OP_rot3r); + emit_op(s, OP_undefined); /* dummy catch offset */ + emit_op(s, OP_iterator_close); + } } else { - emit_op(s, OP_iterator_close); + /* execute the "finally" block */ + emit_goto(s, OP_gosub, top->label_finally); } - drop_count = -3; - } - drop_count += top->drop_count; - if (top->label_finally != -1) { - while(drop_count) { - /* must keep the stack top if hasval */ - emit_op(s, hasval ? OP_nip : OP_drop); - drop_count--; - } - if (!hasval) { - /* must push return value to keep same stack size */ - emit_op(s, OP_undefined); - hasval = TRUE; - } - emit_goto(s, OP_gosub, top->label_finally); } top = top->prev; } @@ -25957,20 +27470,15 @@ label_return = -1; } - /* XXX: if this is not initialized, should throw the - ReferenceError in the caller realm */ - emit_op(s, OP_scope_get_var); + /* The error should be raised in the caller context, so we use + a specific opcode */ + emit_op(s, OP_scope_get_var_checkthis); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_label(s, label_return); emit_op(s, OP_return); } else if (s->cur_func->func_kind != JS_FUNC_NORMAL) { - if (!hasval) { - emit_op(s, OP_undefined); - } else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { - emit_op(s, OP_await); - } emit_op(s, OP_return_async); } else { emit_op(s, hasval ? OP_return : OP_return_undef); @@ -26041,7 +27549,7 @@ if (s->token.val == '=') { if (next_token(s)) goto var_error; - if (tok == TOK_VAR) { + if (need_var_reference(s, tok)) { /* Must make a reference for proper `with` semantics */ int opcode, scope, label; JSAtom name1; @@ -26086,7 +27594,7 @@ if ((s->token.val == '' || s->token.val == '{') && js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { emit_op(s, OP_undefined); - if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE, export_flag) < 0) return -1; } else { return js_parse_error(s, "variable name expected"); @@ -26115,12 +27623,13 @@ static int is_let(JSParseState *s, int decl_mask) { int res = FALSE; + const uint8_t *last_token_ptr; if (token_is_pseudo_keyword(s, JS_ATOM_let)) { -#if 1 JSParsePos pos; js_parse_get_pos(s, &pos); for (;;) { + last_token_ptr = s->token.ptr; if (next_token(s)) { res = -1; break; @@ -26139,7 +27648,8 @@ /* Check for possible ASI if not scanning for Declaration */ /* XXX: should also check that `{` introduces a BindingPattern, but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */ - if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) { + if (!has_lf_in_range(last_token_ptr, s->token.ptr) || + (decl_mask & DECL_MASK_OTHER)) { res = TRUE; break; } @@ -26150,12 +27660,6 @@ if (js_parse_seek_token(s, &pos)) { res = -1; } -#else - int tok = peek_token(s, TRUE); - if (tok == '{' || tok == TOK_IDENT || peek_token(s, FALSE) == '') { - res = TRUE; - } -#endif } return res; } @@ -26215,7 +27719,7 @@ if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { if (s->token.val == '' || s->token.val == '{') { - if (js_parse_destructuring_element(s, tok, 0, TRUE, -1, FALSE) < 0) + if (js_parse_destructuring_element(s, tok, 0, TRUE, -1, FALSE, FALSE) < 0) return -1; has_destructuring = TRUE; } else { @@ -26237,11 +27741,14 @@ emit_atom(s, var_name); emit_u16(s, fd->scope_level); } + } else if (!is_async && token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, FALSE) == TOK_OF) { + return js_parse_error(s, "'for of' expression cannot start with 'async'"); } else { int skip_bits; if ((s->token.val == '' || s->token.val == '{') && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN || tok1 == TOK_OF)) { - if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE, FALSE) < 0) return -1; } else { int lvalue_label; @@ -26328,7 +27835,7 @@ int chunk_size = pos_expr - pos_next; int offset = bc->size - pos_next; int i; - dbuf_realloc(bc, bc->size + chunk_size); + dbuf_claim(bc, chunk_size); dbuf_put(bc, bc->buf + pos_next, chunk_size); memset(bc->buf + pos_next, OP_nop, chunk_size); /* `next` part ends with a goto */ @@ -26350,12 +27857,9 @@ emit_label(s, label_cont); if (is_for_of) { if (is_async) { - /* call the next method */ /* stack: iter_obj next catch_offset */ - emit_op(s, OP_dup3); - emit_op(s, OP_drop); - emit_op(s, OP_call_method); - emit_u16(s, 0); + /* call the next method */ + emit_op(s, OP_for_await_of_next); /* get the result of the promise */ emit_op(s, OP_await); /* unwrap the value and done values */ @@ -26429,6 +27933,7 @@ label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1, 0); + break_entry.is_regular_stmt = TRUE; if (!(s->cur_func->js_mode & JS_MODE_STRICT) && (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) { mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL; @@ -26449,34 +27954,49 @@ goto fail; break; case TOK_RETURN: - if (s->cur_func->is_eval) { - js_parse_error(s, "return not in a function"); - goto fail; - } - if (next_token(s)) - goto fail; - if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { - if (js_parse_expr(s)) + { + const uint8_t *op_token_ptr; + if (s->cur_func->is_eval) { + js_parse_error(s, "return not in a function"); + goto fail; + } + if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { + js_parse_error(s, "return in a static initializer block"); + goto fail; + } + op_token_ptr = s->token.ptr; + if (next_token(s)) + goto fail; + if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { + if (js_parse_expr(s)) + goto fail; + emit_source_pos(s, op_token_ptr); + emit_return(s, TRUE); + } else { + emit_source_pos(s, op_token_ptr); + emit_return(s, FALSE); + } + if (js_parse_expect_semi(s)) goto fail; - emit_return(s, TRUE); - } else { - emit_return(s, FALSE); } - if (js_parse_expect_semi(s)) - goto fail; break; case TOK_THROW: - if (next_token(s)) - goto fail; - if (s->got_lf) { - js_parse_error(s, "line terminator not allowed after throw"); - goto fail; + { + const uint8_t *op_token_ptr; + op_token_ptr = s->token.ptr; + if (next_token(s)) + goto fail; + if (s->got_lf) { + js_parse_error(s, "line terminator not allowed after throw"); + goto fail; + } + if (js_parse_expr(s)) + goto fail; + emit_source_pos(s, op_token_ptr); + emit_op(s, OP_throw); + if (js_parse_expect_semi(s)) + goto fail; } - if (js_parse_expr(s)) - goto fail; - emit_op(s, OP_throw); - if (js_parse_expect_semi(s)) - goto fail; break; case TOK_LET: case TOK_CONST: @@ -26621,6 +28141,7 @@ is_async = TRUE; if (next_token(s)) goto fail; + s->cur_func->has_await = TRUE; } if (js_parse_expect(s, '(')) goto fail; @@ -26720,7 +28241,7 @@ int chunk_size = pos_body - pos_cont; int offset = bc->size - pos_cont; int i; - dbuf_realloc(bc, bc->size + chunk_size); + dbuf_claim(bc, chunk_size); dbuf_put(bc, bc->buf + pos_cont, chunk_size); memset(bc->buf + pos_cont, OP_nop, chunk_size); /* increment part ends with a goto */ @@ -26913,7 +28434,7 @@ if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { if (s->token.val == '' || s->token.val == '{') { /* XXX: TOK_LET is not completely correct */ - if (js_parse_destructuring_element(s, TOK_LET, 0, TRUE, -1, TRUE) < 0) + if (js_parse_destructuring_element(s, TOK_LET, 0, TRUE, -1, TRUE, FALSE) < 0) goto fail; } else { js_parse_error(s, "identifier expected"); @@ -27083,7 +28604,7 @@ parse_func_var: if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) + s->token.ptr)) goto fail; break; } @@ -27114,6 +28635,7 @@ default: hasexpr: + emit_source_pos(s, s->token.ptr); if (js_parse_expr(s)) goto fail; if (s->cur_func->eval_ret_idx >= 0) { @@ -27136,7 +28658,7 @@ return -1; } -/* 'name' is freed */ +/* 'name' is freed. The module is referenced by 'ctx->loaded_modules' */ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name) { JSModuleDef *m; @@ -27146,11 +28668,16 @@ return NULL; } m->header.ref_count = 1; + add_gc_object(ctx->rt, &m->header, JS_GC_OBJ_TYPE_MODULE); m->module_name = name; m->module_ns = JS_UNDEFINED; m->func_obj = JS_UNDEFINED; m->eval_exception = JS_UNDEFINED; m->meta_obj = JS_UNDEFINED; + m->promise = JS_UNDEFINED; + m->resolving_funcs0 = JS_UNDEFINED; + m->resolving_funcs1 = JS_UNDEFINED; + m->private_value = JS_UNDEFINED; list_add_tail(&m->link, &ctx->loaded_modules); return m; } @@ -27160,6 +28687,11 @@ { int i; + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entriesi; + JS_MarkValue(rt, rme->attributes, mark_func); + } + for(i = 0; i < m->export_entries_count; i++) { JSExportEntry *me = &m->export_entriesi; if (me->export_type == JS_EXPORT_TYPE_LOCAL && @@ -27172,57 +28704,68 @@ JS_MarkValue(rt, m->func_obj, mark_func); JS_MarkValue(rt, m->eval_exception, mark_func); JS_MarkValue(rt, m->meta_obj, mark_func); + JS_MarkValue(rt, m->promise, mark_func); + JS_MarkValue(rt, m->resolving_funcs0, mark_func); + JS_MarkValue(rt, m->resolving_funcs1, mark_func); + JS_MarkValue(rt, m->private_value, mark_func); } -static void js_free_module_def(JSContext *ctx, JSModuleDef *m) +static void js_free_module_def(JSRuntime *rt, JSModuleDef *m) { int i; - JS_FreeAtom(ctx, m->module_name); + JS_FreeAtomRT(rt, m->module_name); for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entriesi; - JS_FreeAtom(ctx, rme->module_name); + JS_FreeAtomRT(rt, rme->module_name); + JS_FreeValueRT(rt, rme->attributes); } - js_free(ctx, m->req_module_entries); + js_free_rt(rt, m->req_module_entries); for(i = 0; i < m->export_entries_count; i++) { JSExportEntry *me = &m->export_entriesi; if (me->export_type == JS_EXPORT_TYPE_LOCAL) - free_var_ref(ctx->rt, me->u.local.var_ref); - JS_FreeAtom(ctx, me->export_name); - JS_FreeAtom(ctx, me->local_name); + free_var_ref(rt, me->u.local.var_ref); + JS_FreeAtomRT(rt, me->export_name); + JS_FreeAtomRT(rt, me->local_name); } - js_free(ctx, m->export_entries); + js_free_rt(rt, m->export_entries); - js_free(ctx, m->star_export_entries); + js_free_rt(rt, m->star_export_entries); for(i = 0; i < m->import_entries_count; i++) { JSImportEntry *mi = &m->import_entriesi; - JS_FreeAtom(ctx, mi->import_name); + JS_FreeAtomRT(rt, mi->import_name); } - js_free(ctx, m->import_entries); + js_free_rt(rt, m->import_entries); + js_free_rt(rt, m->async_parent_modules); - JS_FreeValue(ctx, m->module_ns); - JS_FreeValue(ctx, m->func_obj); - JS_FreeValue(ctx, m->eval_exception); - JS_FreeValue(ctx, m->meta_obj); - list_del(&m->link); - js_free(ctx, m); + JS_FreeValueRT(rt, m->module_ns); + JS_FreeValueRT(rt, m->func_obj); + JS_FreeValueRT(rt, m->eval_exception); + JS_FreeValueRT(rt, m->meta_obj); + JS_FreeValueRT(rt, m->promise); + JS_FreeValueRT(rt, m->resolving_funcs0); + JS_FreeValueRT(rt, m->resolving_funcs1); + JS_FreeValueRT(rt, m->private_value); + /* during the GC the finalizers are called in an arbitrary + order so the module may no longer be referenced by the JSContext list */ + if (m->link.next) { + list_del(&m->link); + } + remove_gc_object(&m->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && m->header.ref_count != 0) { + list_add_tail(&m->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, m); + } } static int add_req_module_entry(JSContext *ctx, JSModuleDef *m, JSAtom module_name) { JSReqModuleEntry *rme; - int i; - - /* no need to add the module request if it is already present */ - for(i = 0; i < m->req_module_entries_count; i++) { - rme = &m->req_module_entriesi; - if (rme->module_name == module_name) - return i; - } if (js_resize_array(ctx, (void **)&m->req_module_entries, sizeof(JSReqModuleEntry), @@ -27232,7 +28775,8 @@ rme = &m->req_module_entriesm->req_module_entries_count++; rme->module_name = JS_DupAtom(ctx, module_name); rme->module = NULL; - return i; + rme->attributes = JS_UNDEFINED; + return m->req_module_entries_count - 1; } static JSExportEntry *find_export_entry(JSContext *ctx, JSModuleDef *m, @@ -27312,6 +28856,8 @@ if (name == JS_ATOM_NULL) return NULL; m = js_new_module_def(ctx, name); + if (!m) + return NULL; m->init_func = func; return m; } @@ -27351,12 +28897,38 @@ return -1; } +int JS_SetModulePrivateValue(JSContext *ctx, JSModuleDef *m, JSValue val) +{ + set_value(ctx, &m->private_value, val); + return 0; +} + +JSValue JS_GetModulePrivateValue(JSContext *ctx, JSModuleDef *m) +{ + return JS_DupValue(ctx, m->private_value); +} + void JS_SetModuleLoaderFunc(JSRuntime *rt, JSModuleNormalizeFunc *module_normalize, JSModuleLoaderFunc *module_loader, void *opaque) { rt->module_normalize_func = module_normalize; - rt->module_loader_func = module_loader; + rt->module_loader_has_attr = FALSE; + rt->u.module_loader_func = module_loader; + rt->module_check_attrs = NULL; + rt->module_loader_opaque = opaque; +} + +void JS_SetModuleLoaderFunc2(JSRuntime *rt, + JSModuleNormalizeFunc *module_normalize, + JSModuleLoaderFunc2 *module_loader, + JSModuleCheckSupportedImportAttributes *module_check_attrs, + void *opaque) +{ + rt->module_normalize_func = module_normalize; + rt->module_loader_has_attr = TRUE; + rt->u.module_loader_func2 = module_loader; + rt->module_check_attrs = module_check_attrs; rt->module_loader_opaque = opaque; } @@ -27367,6 +28939,7 @@ { char *filename, *p; const char *r; + int cap; int len; if (name0 != '.') { @@ -27380,7 +28953,8 @@ else len = 0; - filename = js_malloc(ctx, len + strlen(name) + 1 + 1); + cap = len + strlen(name) + 1 + 1; + filename = js_malloc(ctx, cap); if (!filename) return NULL; memcpy(filename, base_name, len); @@ -27412,8 +28986,8 @@ } } if (filename0 != '\0') - strcat(filename, "/"); - strcat(filename, r); + pstrcat(filename, cap, "/"); + pstrcat(filename, cap, r); // printf("normalize: %s %s -> %s\n", base_name, name, filename); return filename; } @@ -27435,7 +29009,8 @@ /* return NULL in case of exception (e.g. module could not be loaded) */ static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx, const char *base_cname, - const char *cname1) + const char *cname1, + JSValueConst attributes) { JSRuntime *rt = ctx->rt; JSModuleDef *m; @@ -27468,22 +29043,26 @@ JS_FreeAtom(ctx, module_name); /* load the module */ - if (!rt->module_loader_func) { + if (!rt->u.module_loader_func) { /* XXX: use a syntax error ? */ JS_ThrowReferenceError(ctx, "could not load module '%s'", cname); js_free(ctx, cname); return NULL; } - - m = rt->module_loader_func(ctx, cname, rt->module_loader_opaque); + if (rt->module_loader_has_attr) { + m = rt->u.module_loader_func2(ctx, cname, rt->module_loader_opaque, attributes); + } else { + m = rt->u.module_loader_func(ctx, cname, rt->module_loader_opaque); + } js_free(ctx, cname); return m; } static JSModuleDef *js_host_resolve_imported_module_atom(JSContext *ctx, - JSAtom base_module_name, - JSAtom module_name1) + JSAtom base_module_name, + JSAtom module_name1, + JSValueConst attributes) { const char *base_cname, *cname; JSModuleDef *m; @@ -27496,7 +29075,7 @@ JS_FreeCString(ctx, base_cname); return NULL; } - m = js_host_resolve_imported_module(ctx, base_cname, cname); + m = js_host_resolve_imported_module(ctx, base_cname, cname, attributes); JS_FreeCString(ctx, base_cname); JS_FreeCString(ctx, cname); return m; @@ -27680,7 +29259,7 @@ typedef enum { EXPORTED_NAME_AMBIGUOUS, EXPORTED_NAME_NORMAL, - EXPORTED_NAME_NS, + EXPORTED_NAME_DELAYED, } ExportedNameEntryEnum; typedef struct ExportedNameEntry { @@ -27689,7 +29268,6 @@ union { JSExportEntry *me; /* using when the list is built */ JSVarRef *var_ref; /* EXPORTED_NAME_NORMAL */ - JSModuleDef *module; /* for EXPORTED_NAME_NS */ } u; } ExportedNameEntry; @@ -27795,13 +29373,33 @@ return ret; } -static JSValue js_get_module_ns(JSContext *ctx, JSModuleDef *m); - static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) { JSModuleDef *m = opaque; - return js_get_module_ns(ctx, m); + JSResolveResultEnum res; + JSExportEntry *res_me; + JSModuleDef *res_m; + JSVarRef *var_ref; + + res = js_resolve_export(ctx, &res_m, &res_me, m, atom); + if (res != JS_RESOLVE_RES_FOUND) { + /* fail safe: normally no error should happen here except for memory */ + js_resolve_export_throw_error(ctx, res, m, atom); + return JS_EXCEPTION; + } + if (res_me->local_name == JS_ATOM__star_) { + return JS_GetModuleNamespace(ctx, res_m->req_module_entriesres_me->u.req_module_idx.module); + } else { + if (res_me->u.local.var_ref) { + var_ref = res_me->u.local.var_ref; + } else { + JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj); + var_ref = p1->u.func.var_refsres_me->u.local.var_idx; + } + /* WARNING: a varref is returned as a string ! */ + return JS_MKPTR(JS_TAG_STRING, var_ref); + } } static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) @@ -27846,17 +29444,18 @@ en->export_type = EXPORTED_NAME_AMBIGUOUS; } else { if (res_me->local_name == JS_ATOM__star_) { - en->export_type = EXPORTED_NAME_NS; - en->u.module = res_m->req_module_entriesres_me->u.req_module_idx.module; + en->export_type = EXPORTED_NAME_DELAYED; } else { - en->export_type = EXPORTED_NAME_NORMAL; if (res_me->u.local.var_ref) { en->u.var_ref = res_me->u.local.var_ref; } else { JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj); - p1 = JS_VALUE_GET_OBJ(res_m->func_obj); en->u.var_ref = p1->u.func.var_refsres_me->u.local.var_idx; } + if (en->u.var_ref == NULL) + en->export_type = EXPORTED_NAME_DELAYED; + else + en->export_type = EXPORTED_NAME_NORMAL; } } } @@ -27880,13 +29479,13 @@ pr->u.var_ref = var_ref; } break; - case EXPORTED_NAME_NS: - /* the exported namespace must be created on demand */ + case EXPORTED_NAME_DELAYED: + /* the exported namespace or reference may depend on + circular references, so we resolve it lazily */ if (JS_DefineAutoInitProperty(ctx, obj, en->export_name, JS_AUTOINIT_ID_MODULE_NS, - en->u.module, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) - goto fail; + m, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) break; default: break; @@ -27907,7 +29506,7 @@ return JS_EXCEPTION; } -static JSValue js_get_module_ns(JSContext *ctx, JSModuleDef *m) +JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m) { if (JS_IsUndefined(m->module_ns)) { JSValue val; @@ -27938,7 +29537,8 @@ for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entriesi; m1 = js_host_resolve_imported_module_atom(ctx, m->module_name, - rme->module_name); + rme->module_name, + rme->attributes); if (!m1) return -1; rme->module = m1; @@ -28062,7 +29662,8 @@ /* Prepare a module to be executed by resolving all the imported variables. */ -static int js_link_module(JSContext *ctx, JSModuleDef *m) +static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m, + JSModuleDef **pstack_top, int index) { int i; JSImportEntry *mi; @@ -28072,21 +29673,47 @@ BOOL is_c_module; JSValue ret_val; - if (m->instantiated) - return 0; - m->instantiated = TRUE; + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return -1; + } #ifdef DUMP_MODULE_RESOLVE { char buf1ATOM_GET_STR_BUF_SIZE; - printf("start instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + printf("js_inner_module_linking '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); } #endif + if (m->status == JS_MODULE_STATUS_LINKING || + m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED) + return index; + + assert(m->status == JS_MODULE_STATUS_UNLINKED); + m->status = JS_MODULE_STATUS_LINKING; + m->dfs_index = index; + m->dfs_ancestor_index = index; + index++; + /* push 'm' on stack */ + m->stack_prev = *pstack_top; + *pstack_top = m; + for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entriesi; - if (js_link_module(ctx, rme->module) < 0) + m1 = rme->module; + index = js_inner_module_linking(ctx, m1, pstack_top, index); + if (index < 0) goto fail; + assert(m1->status == JS_MODULE_STATUS_LINKING || + m1->status == JS_MODULE_STATUS_LINKED || + m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m1->status == JS_MODULE_STATUS_EVALUATED); + if (m1->status == JS_MODULE_STATUS_LINKING) { + m->dfs_ancestor_index = min_int(m->dfs_ancestor_index, + m1->dfs_ancestor_index); + } } #ifdef DUMP_MODULE_RESOLVE @@ -28138,10 +29765,10 @@ printf(": "); #endif m1 = m->req_module_entriesmi->req_module_idx.module; - if (mi->import_name == JS_ATOM__star_) { + if (mi->is_star) { JSValue val; /* name space import */ - val = js_get_module_ns(ctx, m1); + val = JS_GetModuleNamespace(ctx, m1); if (JS_IsException(val)) goto fail; set_value(ctx, &var_refsmi->var_idx->value, val); @@ -28165,7 +29792,7 @@ JSModuleDef *m2; /* name space import from */ m2 = res_m->req_module_entriesres_me->u.req_module_idx.module; - val = js_get_module_ns(ctx, m2); + val = JS_GetModuleNamespace(ctx, m2); if (JS_IsException(val)) goto fail; var_ref = js_create_module_var(ctx, TRUE); @@ -28212,14 +29839,59 @@ JS_FreeValue(ctx, ret_val); } + assert(m->dfs_ancestor_index <= m->dfs_index); + if (m->dfs_index == m->dfs_ancestor_index) { + for(;;) { + /* pop m1 from stack */ + m1 = *pstack_top; + *pstack_top = m1->stack_prev; + m1->status = JS_MODULE_STATUS_LINKED; + if (m1 == m) + break; + } + } + #ifdef DUMP_MODULE_RESOLVE - printf("done instantiate\n"); + printf("js_inner_module_linking done\n"); #endif - return 0; + return index; fail: return -1; } +/* Prepare a module to be executed by resolving all the imported + variables. */ +static int js_link_module(JSContext *ctx, JSModuleDef *m) +{ + JSModuleDef *stack_top, *m1; + +#ifdef DUMP_MODULE_RESOLVE + { + char buf1ATOM_GET_STR_BUF_SIZE; + printf("js_link_module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + } +#endif + assert(m->status == JS_MODULE_STATUS_UNLINKED || + m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + stack_top = NULL; + if (js_inner_module_linking(ctx, m, &stack_top, 0) < 0) { + while (stack_top != NULL) { + m1 = stack_top; + assert(m1->status == JS_MODULE_STATUS_LINKING); + m1->status = JS_MODULE_STATUS_UNLINKED; + stack_top = m1->stack_prev; + } + return -1; + } + assert(stack_top == NULL); + assert(m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + return 0; +} + /* return JS_ATOM_NULL if the name cannot be found. Only works with not striped bytecode functions. */ JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels) @@ -28228,8 +29900,8 @@ JSFunctionBytecode *b; JSObject *p; /* XXX: currently we just use the filename of the englobing - function. It does not work for eval(). Need to add a - ScriptOrModule info in JSFunctionBytecode */ + function from the debug info. May need to add a ScriptOrModule + info in JSFunctionBytecode. */ sf = ctx->rt->current_stack_frame; if (!sf) return JS_ATOM_NULL; @@ -28238,15 +29910,23 @@ if (!sf) return JS_ATOM_NULL; } - if (JS_VALUE_GET_TAG(sf->cur_func) != JS_TAG_OBJECT) - return JS_ATOM_NULL; - p = JS_VALUE_GET_OBJ(sf->cur_func); - if (!js_class_has_bytecode(p->class_id)) - return JS_ATOM_NULL; - b = p->u.func.function_bytecode; - if (!b->has_debug) - return JS_ATOM_NULL; - return JS_DupAtom(ctx, b->debug.filename); + for(;;) { + if (JS_VALUE_GET_TAG(sf->cur_func) != JS_TAG_OBJECT) + return JS_ATOM_NULL; + p = JS_VALUE_GET_OBJ(sf->cur_func); + if (!js_class_has_bytecode(p->class_id)) + return JS_ATOM_NULL; + b = p->u.func.function_bytecode; + if (!b->is_direct_or_indirect_eval) { + if (!b->has_debug) + return JS_ATOM_NULL; + return JS_DupAtom(ctx, b->debug.filename); + } else { + sf = sf->prev_frame; + if (!sf) + return JS_ATOM_NULL; + } + } } JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m) @@ -28289,29 +29969,111 @@ return JS_GetImportMeta(ctx, m); } -/* used by os.Worker() and import() */ -JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename, - const char *filename) +static JSValue JS_NewModuleValue(JSContext *ctx, JSModuleDef *m) { + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); +} + +static JSValue js_load_module_rejected(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSValueConst *resolving_funcs = (JSValueConst *)func_data; + JSValueConst error; + JSValue ret; + + /* XXX: check if the test is necessary */ + if (argc >= 1) + error = argv0; + else + error = JS_UNDEFINED; + ret = JS_Call(ctx, resolving_funcs1, JS_UNDEFINED, + 1, &error); + JS_FreeValue(ctx, ret); + return JS_UNDEFINED; +} + +static JSValue js_load_module_fulfilled(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSValueConst *resolving_funcs = (JSValueConst *)func_data; + JSModuleDef *m = JS_VALUE_GET_PTR(func_data2); + JSValue ret, ns; + + /* return the module namespace */ + ns = JS_GetModuleNamespace(ctx, m); + if (JS_IsException(ns)) { + JSValue err = JS_GetException(ctx); + js_load_module_rejected(ctx, JS_UNDEFINED, 1, (JSValueConst *)&err, 0, func_data); + return JS_UNDEFINED; + } + ret = JS_Call(ctx, resolving_funcs0, JS_UNDEFINED, + 1, (JSValueConst *)&ns); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, ns); + return JS_UNDEFINED; +} + +static void JS_LoadModuleInternal(JSContext *ctx, const char *basename, + const char *filename, + JSValueConst *resolving_funcs, + JSValueConst attributes) +{ + JSValue evaluate_promise; JSModuleDef *m; - JSValue ret, func_obj; + JSValue ret, err, func_obj, evaluate_resolving_funcs2; + JSValueConst func_data3; - m = js_host_resolve_imported_module(ctx, basename, filename); + m = js_host_resolve_imported_module(ctx, basename, filename, attributes); if (!m) - return NULL; + goto fail; if (js_resolve_module(ctx, m) < 0) { js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED); - return NULL; + goto fail; } /* Evaluate the module code */ - func_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); - ret = JS_EvalFunction(ctx, func_obj); - if (JS_IsException(ret)) - return NULL; + func_obj = JS_NewModuleValue(ctx, m); + evaluate_promise = JS_EvalFunction(ctx, func_obj); + if (JS_IsException(evaluate_promise)) { + fail: + err = JS_GetException(ctx); + ret = JS_Call(ctx, resolving_funcs1, JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ + JS_FreeValue(ctx, err); + return; + } + + func_obj = JS_NewModuleValue(ctx, m); + func_data0 = resolving_funcs0; + func_data1 = resolving_funcs1; + func_data2 = func_obj; + evaluate_resolving_funcs0 = JS_NewCFunctionData(ctx, js_load_module_fulfilled, 0, 0, 3, func_data); + evaluate_resolving_funcs1 = JS_NewCFunctionData(ctx, js_load_module_rejected, 0, 0, 3, func_data); + JS_FreeValue(ctx, func_obj); + ret = js_promise_then(ctx, evaluate_promise, 2, (JSValueConst *)evaluate_resolving_funcs); JS_FreeValue(ctx, ret); - return m; + JS_FreeValue(ctx, evaluate_resolving_funcs0); + JS_FreeValue(ctx, evaluate_resolving_funcs1); + JS_FreeValue(ctx, evaluate_promise); +} + +/* Return a promise or an exception in case of memory error. Used by + os.Worker() */ +JSValue JS_LoadModule(JSContext *ctx, const char *basename, + const char *filename) +{ + JSValue promise, resolving_funcs2; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + JS_LoadModuleInternal(ctx, basename, filename, + (JSValueConst *)resolving_funcs, JS_UNDEFINED); + JS_FreeValue(ctx, resolving_funcs0); + JS_FreeValue(ctx, resolving_funcs1); + return promise; } static JSValue js_dynamic_import_job(JSContext *ctx, @@ -28320,9 +30082,9 @@ JSValueConst *resolving_funcs = argv; JSValueConst basename_val = argv2; JSValueConst specifier = argv3; - JSModuleDef *m; + JSValueConst attributes = argv4; const char *basename = NULL, *filename; - JSValue ret, err, ns; + JSValue ret, err; if (!JS_IsString(basename_val)) { JS_ThrowTypeError(ctx, "no function filename for import()"); @@ -28336,24 +30098,12 @@ if (!filename) goto exception; - m = JS_RunModule(ctx, basename, filename); + JS_LoadModuleInternal(ctx, basename, filename, + resolving_funcs, attributes); JS_FreeCString(ctx, filename); - if (!m) - goto exception; - - /* return the module namespace */ - ns = js_get_module_ns(ctx, m); - if (JS_IsException(ns)) - goto exception; - - ret = JS_Call(ctx, resolving_funcs0, JS_UNDEFINED, - 1, (JSValueConst *)&ns); - JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ - JS_FreeValue(ctx, ns); JS_FreeCString(ctx, basename); return JS_UNDEFINED; exception: - err = JS_GetException(ctx); ret = JS_Call(ctx, resolving_funcs1, JS_UNDEFINED, 1, (JSValueConst *)&err); @@ -28363,11 +30113,12 @@ return JS_UNDEFINED; } -static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier) +static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, JSValueConst options) { JSAtom basename; - JSValue promise, resolving_funcs2, basename_val; - JSValueConst args4; + JSValue promise, resolving_funcs2, basename_val, err, ret; + JSValue specifier_str = JS_UNDEFINED, attributes = JS_UNDEFINED, attributes_obj = JS_UNDEFINED; + JSValueConst args5; basename = JS_GetScriptOrModuleName(ctx, 0); if (basename == JS_ATOM_NULL) @@ -28384,96 +30135,608 @@ return promise; } + /* the string conversion must occur here */ + specifier_str = JS_ToString(ctx, specifier); + if (JS_IsException(specifier_str)) + goto exception; + + if (!JS_IsUndefined(options)) { + if (!JS_IsObject(options)) { + JS_ThrowTypeError(ctx, "options must be an object"); + goto exception; + } + attributes_obj = JS_GetProperty(ctx, options, JS_ATOM_with); + if (JS_IsException(attributes_obj)) + goto exception; + if (!JS_IsUndefined(attributes_obj)) { + JSPropertyEnum *atoms; + uint32_t atoms_len, i; + JSValue val; + + if (!JS_IsObject(attributes_obj)) { + JS_ThrowTypeError(ctx, "options.with must be an object"); + goto exception; + } + attributes = JS_NewObjectProto(ctx, JS_NULL); + if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &atoms_len, JS_VALUE_GET_OBJ(attributes_obj), + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { + goto exception; + } + for(i = 0; i < atoms_len; i++) { + val = JS_GetProperty(ctx, attributes_obj, atomsi.atom); + if (JS_IsException(val)) + goto exception1; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "module attribute values must be strings"); + goto exception1; + } + if (JS_DefinePropertyValue(ctx, attributes, atomsi.atom, val, + JS_PROP_C_W_E) < 0) { + exception1: + JS_FreePropertyEnum(ctx, atoms, atoms_len); + goto exception; + } + } + JS_FreePropertyEnum(ctx, atoms, atoms_len); + if (ctx->rt->module_check_attrs && + ctx->rt->module_check_attrs(ctx, ctx->rt->module_loader_opaque, attributes) < 0) { + goto exception; + } + JS_FreeValue(ctx, attributes_obj); + } + } + args0 = resolving_funcs0; args1 = resolving_funcs1; args2 = basename_val; - args3 = specifier; - - JS_EnqueueJob(ctx, js_dynamic_import_job, 4, args); + args3 = specifier_str; + args4 = attributes; + /* cannot run JS_LoadModuleInternal synchronously because it would + cause an unexpected recursion in js_evaluate_module() */ + JS_EnqueueJob(ctx, js_dynamic_import_job, 5, args); + done: JS_FreeValue(ctx, basename_val); JS_FreeValue(ctx, resolving_funcs0); JS_FreeValue(ctx, resolving_funcs1); + JS_FreeValue(ctx, specifier_str); + JS_FreeValue(ctx, attributes); return promise; + exception: + JS_FreeValue(ctx, attributes_obj); + err = JS_GetException(ctx); + ret = JS_Call(ctx, resolving_funcs1, JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, err); + goto done; } -/* Run the <eval> function of the module and of all its requested - modules. */ -static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m) +static void js_set_module_evaluated(JSContext *ctx, JSModuleDef *m) +{ + m->status = JS_MODULE_STATUS_EVALUATED; + if (!JS_IsUndefined(m->promise)) { + JSValue value, ret_val; + assert(m->cycle_root == m); + value = JS_UNDEFINED; + ret_val = JS_Call(ctx, m->resolving_funcs0, JS_UNDEFINED, + 1, (JSValueConst *)&value); + JS_FreeValue(ctx, ret_val); + } +} + +typedef struct { + JSModuleDef **tab; + int count; + int size; +} ExecModuleList; + +/* XXX: slow. Could use a linked list instead of ExecModuleList */ +static BOOL find_in_exec_module_list(ExecModuleList *exec_list, JSModuleDef *m) +{ + int i; + for(i = 0; i < exec_list->count; i++) { + if (exec_list->tabi == m) + return TRUE; + } + return FALSE; +} + +static int gather_available_ancestors(JSContext *ctx, JSModuleDef *module, + ExecModuleList *exec_list) +{ + int i; + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return -1; + } + for(i = 0; i < module->async_parent_modules_count; i++) { + JSModuleDef *m = module->async_parent_modulesi; + if (!find_in_exec_module_list(exec_list, m) && + !m->cycle_root->eval_has_exception) { + assert(m->status == JS_MODULE_STATUS_EVALUATING_ASYNC); + assert(!m->eval_has_exception); + assert(m->async_evaluation); + assert(m->pending_async_dependencies > 0); + m->pending_async_dependencies--; + if (m->pending_async_dependencies == 0) { + if (js_resize_array(ctx, (void **)&exec_list->tab, sizeof(exec_list->tab0), &exec_list->size, exec_list->count + 1)) { + return -1; + } + exec_list->tabexec_list->count++ = m; + if (!m->has_tla) { + if (gather_available_ancestors(ctx, m, exec_list)) + return -1; + } + } + } + } + return 0; +} + +static int exec_module_list_cmp(const void *p1, const void *p2, void *opaque) +{ + JSModuleDef *m1 = *(JSModuleDef **)p1; + JSModuleDef *m2 = *(JSModuleDef **)p2; + return (m1->async_evaluation_timestamp > m2->async_evaluation_timestamp) - + (m1->async_evaluation_timestamp < m2->async_evaluation_timestamp); +} + +static int js_execute_async_module(JSContext *ctx, JSModuleDef *m); +static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m, + JSValue *pvalue); +#ifdef DUMP_MODULE_EXEC +static void js_dump_module(JSContext *ctx, const char *str, JSModuleDef *m) +{ + char buf1ATOM_GET_STR_BUF_SIZE; + static const char *module_status_str = { "unlinked", "linking", "linked", "evaluating", "evaluating_async", "evaluated" }; + printf("%s: %s status=%s\n", str, JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name), module_status_strm->status); +} +#endif + +static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSModuleDef *module = JS_VALUE_GET_PTR(func_data0); + JSValueConst error = argv0; + int i; + +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, __func__, module); +#endif + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + + if (module->status == JS_MODULE_STATUS_EVALUATED) { + assert(module->eval_has_exception); + return JS_UNDEFINED; + } + + assert(module->status == JS_MODULE_STATUS_EVALUATING_ASYNC); + assert(!module->eval_has_exception); + assert(module->async_evaluation); + + module->eval_has_exception = TRUE; + module->eval_exception = JS_DupValue(ctx, error); + module->status = JS_MODULE_STATUS_EVALUATED; + module->async_evaluation = FALSE; + + if (!JS_IsUndefined(module->promise)) { + JSValue ret_val; + assert(module->cycle_root == module); + ret_val = JS_Call(ctx, module->resolving_funcs1, JS_UNDEFINED, + 1, &error); + JS_FreeValue(ctx, ret_val); + } + + for(i = 0; i < module->async_parent_modules_count; i++) { + JSModuleDef *m = module->async_parent_modulesi; + JSValue m_obj = JS_NewModuleValue(ctx, m); + js_async_module_execution_rejected(ctx, JS_UNDEFINED, 1, &error, 0, + &m_obj); + JS_FreeValue(ctx, m_obj); + } + return JS_UNDEFINED; +} + +static JSValue js_async_module_execution_fulfilled(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSModuleDef *module = JS_VALUE_GET_PTR(func_data0); + ExecModuleList exec_list_s, *exec_list = &exec_list_s; + int i; + +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, __func__, module); +#endif + if (module->status == JS_MODULE_STATUS_EVALUATED) { + assert(module->eval_has_exception); + return JS_UNDEFINED; + } + assert(module->status == JS_MODULE_STATUS_EVALUATING_ASYNC); + assert(!module->eval_has_exception); + assert(module->async_evaluation); + module->async_evaluation = FALSE; + js_set_module_evaluated(ctx, module); + + exec_list->tab = NULL; + exec_list->count = 0; + exec_list->size = 0; + + if (gather_available_ancestors(ctx, module, exec_list) < 0) { + js_free(ctx, exec_list->tab); + return JS_EXCEPTION; + } + + /* sort by increasing async_evaluation timestamp */ + rqsort(exec_list->tab, exec_list->count, sizeof(exec_list->tab0), + exec_module_list_cmp, NULL); + + for(i = 0; i < exec_list->count; i++) { + JSModuleDef *m = exec_list->tabi; +#ifdef DUMP_MODULE_EXEC + printf(" %d/%d", i, exec_list->count); js_dump_module(ctx, "", m); +#endif + if (m->status == JS_MODULE_STATUS_EVALUATED) { + assert(m->eval_has_exception); + } else if (m->has_tla) { + js_execute_async_module(ctx, m); + } else { + JSValue error; + if (js_execute_sync_module(ctx, m, &error) < 0) { + JSValue m_obj = JS_NewModuleValue(ctx, m); + js_async_module_execution_rejected(ctx, JS_UNDEFINED, + 1, (JSValueConst *)&error, 0, + &m_obj); + JS_FreeValue(ctx, m_obj); + JS_FreeValue(ctx, error); + } else { + m->async_evaluation = FALSE; + js_set_module_evaluated(ctx, m); + } + } + } + js_free(ctx, exec_list->tab); + return JS_UNDEFINED; +} + +static int js_execute_async_module(JSContext *ctx, JSModuleDef *m) +{ + JSValue promise, m_obj; + JSValue resolve_funcs2, ret_val; +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, __func__, m); +#endif + promise = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0); + if (JS_IsException(promise)) + return -1; + m_obj = JS_NewModuleValue(ctx, m); + resolve_funcs0 = JS_NewCFunctionData(ctx, js_async_module_execution_fulfilled, 0, 0, 1, (JSValueConst *)&m_obj); + resolve_funcs1 = JS_NewCFunctionData(ctx, js_async_module_execution_rejected, 0, 0, 1, (JSValueConst *)&m_obj); + ret_val = js_promise_then(ctx, promise, 2, (JSValueConst *)resolve_funcs); + JS_FreeValue(ctx, ret_val); + JS_FreeValue(ctx, m_obj); + JS_FreeValue(ctx, resolve_funcs0); + JS_FreeValue(ctx, resolve_funcs1); + JS_FreeValue(ctx, promise); + return 0; +} + +/* return < 0 in case of exception. *pvalue contains the exception. */ +static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m, + JSValue *pvalue) +{ +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, __func__, m); +#endif + if (m->init_func) { + /* C module init : no asynchronous execution */ + if (m->init_func(ctx, m) < 0) + goto fail; + } else { + JSValue promise; + JSPromiseStateEnum state; + + promise = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0); + if (JS_IsException(promise)) + goto fail; + state = JS_PromiseState(ctx, promise); + if (state == JS_PROMISE_FULFILLED) { + JS_FreeValue(ctx, promise); + } else if (state == JS_PROMISE_REJECTED) { + *pvalue = JS_PromiseResult(ctx, promise); + JS_FreeValue(ctx, promise); + return -1; + } else { + JS_FreeValue(ctx, promise); + JS_ThrowTypeError(ctx, "promise is pending"); + fail: + *pvalue = JS_GetException(ctx); + return -1; + } + } + *pvalue = JS_UNDEFINED; + return 0; +} + +/* spec: InnerModuleEvaluation. Return (index, JS_UNDEFINED) or (-1, + exception) */ +static int js_inner_module_evaluation(JSContext *ctx, JSModuleDef *m, + int index, JSModuleDef **pstack_top, + JSValue *pvalue) { JSModuleDef *m1; int i; - JSValue ret_val; - if (m->eval_mark) - return JS_UNDEFINED; /* avoid cycles */ +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, __func__, m); +#endif + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + *pvalue = JS_GetException(ctx); + return -1; + } - if (m->evaluated) { - /* if the module was already evaluated, rethrow the exception - it raised */ + if (m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED) { if (m->eval_has_exception) { - return JS_Throw(ctx, JS_DupValue(ctx, m->eval_exception)); + *pvalue = JS_DupValue(ctx, m->eval_exception); + return -1; } else { - return JS_UNDEFINED; + *pvalue = JS_UNDEFINED; + return index; } } + if (m->status == JS_MODULE_STATUS_EVALUATING) { + *pvalue = JS_UNDEFINED; + return index; + } + assert(m->status == JS_MODULE_STATUS_LINKED); - m->eval_mark = TRUE; + m->status = JS_MODULE_STATUS_EVALUATING; + m->dfs_index = index; + m->dfs_ancestor_index = index; + m->pending_async_dependencies = 0; + index++; + /* push 'm' on stack */ + m->stack_prev = *pstack_top; + *pstack_top = m; for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entriesi; m1 = rme->module; - if (!m1->eval_mark) { - ret_val = js_evaluate_module(ctx, m1); - if (JS_IsException(ret_val)) { - m->eval_mark = FALSE; - return ret_val; + index = js_inner_module_evaluation(ctx, m1, index, pstack_top, pvalue); + if (index < 0) + return -1; + assert(m1->status == JS_MODULE_STATUS_EVALUATING || + m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m1->status == JS_MODULE_STATUS_EVALUATED); + if (m1->status == JS_MODULE_STATUS_EVALUATING) { + m->dfs_ancestor_index = min_int(m->dfs_ancestor_index, + m1->dfs_ancestor_index); + } else { + m1 = m1->cycle_root; + assert(m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m1->status == JS_MODULE_STATUS_EVALUATED); + if (m1->eval_has_exception) { + *pvalue = JS_DupValue(ctx, m1->eval_exception); + return -1; } - JS_FreeValue(ctx, ret_val); + } + if (m1->async_evaluation) { + m->pending_async_dependencies++; + if (js_resize_array(ctx, (void **)&m1->async_parent_modules, sizeof(m1->async_parent_modules0), &m1->async_parent_modules_size, m1->async_parent_modules_count + 1)) { + *pvalue = JS_GetException(ctx); + return -1; + } + m1->async_parent_modulesm1->async_parent_modules_count++ = m; } } - if (m->init_func) { - /* C module init */ - if (m->init_func(ctx, m) < 0) - ret_val = JS_EXCEPTION; - else - ret_val = JS_UNDEFINED; + if (m->pending_async_dependencies > 0) { + assert(!m->async_evaluation); + m->async_evaluation = TRUE; + m->async_evaluation_timestamp = + ctx->rt->module_async_evaluation_next_timestamp++; + } else if (m->has_tla) { + assert(!m->async_evaluation); + m->async_evaluation = TRUE; + m->async_evaluation_timestamp = + ctx->rt->module_async_evaluation_next_timestamp++; + js_execute_async_module(ctx, m); } else { - ret_val = JS_CallFree(ctx, m->func_obj, JS_UNDEFINED, 0, NULL); - m->func_obj = JS_UNDEFINED; + if (js_execute_sync_module(ctx, m, pvalue) < 0) + return -1; } - if (JS_IsException(ret_val)) { - /* save the thrown exception value */ - m->eval_has_exception = TRUE; - m->eval_exception = JS_DupValue(ctx, ctx->rt->current_exception); + + assert(m->dfs_ancestor_index <= m->dfs_index); + if (m->dfs_index == m->dfs_ancestor_index) { + for(;;) { + /* pop m1 from stack */ + m1 = *pstack_top; + *pstack_top = m1->stack_prev; + if (!m1->async_evaluation) { + m1->status = JS_MODULE_STATUS_EVALUATED; + } else { + m1->status = JS_MODULE_STATUS_EVALUATING_ASYNC; + } + /* spec bug: cycle_root must be assigned before the test */ + m1->cycle_root = m; + if (m1 == m) + break; + } } - m->eval_mark = FALSE; - m->evaluated = TRUE; - return ret_val; + *pvalue = JS_UNDEFINED; + return index; +} + +/* Run the <eval> function of the module and of all its requested + modules. Return a promise or an exception. */ +static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m) +{ + JSModuleDef *m1, *stack_top; + JSValue ret_val, result; + +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, __func__, m); +#endif + assert(m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + if (m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED) { + m = m->cycle_root; + } + /* a promise may be created only on the cycle_root of a cycle */ + if (!JS_IsUndefined(m->promise)) + return JS_DupValue(ctx, m->promise); + m->promise = JS_NewPromiseCapability(ctx, m->resolving_funcs); + if (JS_IsException(m->promise)) + return JS_EXCEPTION; + + stack_top = NULL; + if (js_inner_module_evaluation(ctx, m, 0, &stack_top, &result) < 0) { + while (stack_top != NULL) { + m1 = stack_top; + assert(m1->status == JS_MODULE_STATUS_EVALUATING); + m1->status = JS_MODULE_STATUS_EVALUATED; + m1->eval_has_exception = TRUE; + m1->eval_exception = JS_DupValue(ctx, result); + m1->cycle_root = m; /* spec bug: should be present */ + stack_top = m1->stack_prev; + } + JS_FreeValue(ctx, result); + assert(m->status == JS_MODULE_STATUS_EVALUATED); + assert(m->eval_has_exception); + ret_val = JS_Call(ctx, m->resolving_funcs1, JS_UNDEFINED, + 1, (JSValueConst *)&m->eval_exception); + JS_FreeValue(ctx, ret_val); + } else { +#ifdef DUMP_MODULE_EXEC + js_dump_module(ctx, " done", m); +#endif + assert(m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + assert(!m->eval_has_exception); + if (!m->async_evaluation) { + JSValue value; + assert(m->status == JS_MODULE_STATUS_EVALUATED); + value = JS_UNDEFINED; + ret_val = JS_Call(ctx, m->resolving_funcs0, JS_UNDEFINED, + 1, (JSValueConst *)&value); + JS_FreeValue(ctx, ret_val); + } + assert(stack_top == NULL); + } + return JS_DupValue(ctx, m->promise); +} + +static __exception int js_parse_with_clause(JSParseState *s, JSReqModuleEntry *rme) +{ + JSContext *ctx = s->ctx; + JSAtom key; + int ret; + const uint8_t *key_token_ptr; + + if (next_token(s)) + return -1; + if (js_parse_expect(s, '{')) + return -1; + while (s->token.val != '}') { + key_token_ptr = s->token.ptr; + if (s->token.val == TOK_STRING) { + key = JS_ValueToAtom(ctx, s->token.u.str.str); + if (key == JS_ATOM_NULL) + return -1; + } else { + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + return -1; + } + key = JS_DupAtom(ctx, s->token.u.ident.atom); + } + if (next_token(s)) + return -1; + if (js_parse_expect(s, ':')) { + JS_FreeAtom(ctx, key); + return -1; + } + if (s->token.val != TOK_STRING) { + js_parse_error_pos(s, key_token_ptr, "string expected"); + return -1; + } + if (JS_IsUndefined(rme->attributes)) { + JSValue attributes = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(attributes)) { + JS_FreeAtom(ctx, key); + return -1; + } + rme->attributes = attributes; + } + ret = JS_HasProperty(ctx, rme->attributes, key); + if (ret != 0) { + JS_FreeAtom(ctx, key); + if (ret < 0) + return -1; + else + return js_parse_error(s, "duplicate with key"); + } + ret = JS_DefinePropertyValue(ctx, rme->attributes, key, + JS_DupValue(ctx, s->token.u.str.str), JS_PROP_C_W_E); + JS_FreeAtom(ctx, key); + if (ret < 0) + return -1; + if (next_token(s)) + return -1; + if (s->token.val != ',') + break; + if (next_token(s)) + return -1; + } + if (!JS_IsUndefined(rme->attributes) && + ctx->rt->module_check_attrs && + ctx->rt->module_check_attrs(ctx, ctx->rt->module_loader_opaque, rme->attributes) < 0) { + return -1; + } + return js_parse_expect(s, '}'); } -static __exception JSAtom js_parse_from_clause(JSParseState *s) +/* return the module index in m->req_module_entries or < 0 if error */ +static __exception int js_parse_from_clause(JSParseState *s, JSModuleDef *m) { JSAtom module_name; + int idx; + if (!token_is_pseudo_keyword(s, JS_ATOM_from)) { js_parse_error(s, "from clause expected"); - return JS_ATOM_NULL; + return -1; } if (next_token(s)) - return JS_ATOM_NULL; + return -1; if (s->token.val != TOK_STRING) { js_parse_error(s, "string expected"); - return JS_ATOM_NULL; + return -1; } module_name = JS_ValueToAtom(s->ctx, s->token.u.str.str); if (module_name == JS_ATOM_NULL) - return JS_ATOM_NULL; + return -1; if (next_token(s)) { JS_FreeAtom(s->ctx, module_name); - return JS_ATOM_NULL; + return -1; + } + + idx = add_req_module_entry(s->ctx, m, module_name); + JS_FreeAtom(s->ctx, module_name); + if (idx < 0) + return -1; + if (s->token.val == TOK_WITH) { + if (js_parse_with_clause(s, &m->req_module_entriesidx)) + return -1; } - return module_name; + return idx; } static __exception int js_parse_export(JSParseState *s) @@ -28482,7 +30745,6 @@ JSModuleDef *m = s->cur_func->module; JSAtom local_name, export_name; int first_export, idx, i, tok; - JSAtom module_name; JSExportEntry *me; if (next_token(s)) @@ -28496,7 +30758,7 @@ peek_token(s, TRUE) == TOK_FUNCTION)) { return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num, + s->token.ptr, JS_PARSE_EXPORT_NAMED, NULL); } @@ -28518,11 +30780,21 @@ if (token_is_pseudo_keyword(s, JS_ATOM_as)) { if (next_token(s)) goto fail; - if (!token_is_ident(s->token.val)) { - js_parse_error(s, "identifier expected"); - goto fail; + if (s->token.val == TOK_STRING) { + if (js_string_find_invalid_codepoint(JS_VALUE_GET_STRING(s->token.u.str.str)) >= 0) { + js_parse_error(s, "contains unpaired surrogate"); + goto fail; + } + export_name = JS_ValueToAtom(s->ctx, s->token.u.str.str); + if (export_name == JS_ATOM_NULL) + goto fail; + } else { + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + goto fail; + } + export_name = JS_DupAtom(ctx, s->token.u.ident.atom); } - export_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { fail: JS_FreeAtom(ctx, local_name); @@ -28547,11 +30819,7 @@ if (js_parse_expect(s, '}')) return -1; if (token_is_pseudo_keyword(s, JS_ATOM_from)) { - module_name = js_parse_from_clause(s); - if (module_name == JS_ATOM_NULL) - return -1; - idx = add_req_module_entry(ctx, m, module_name); - JS_FreeAtom(ctx, module_name); + idx = js_parse_from_clause(s, m); if (idx < 0) return -1; for(i = first_export; i < m->export_entries_count; i++) { @@ -28573,11 +30841,7 @@ export_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; - module_name = js_parse_from_clause(s); - if (module_name == JS_ATOM_NULL) - goto fail1; - idx = add_req_module_entry(ctx, m, module_name); - JS_FreeAtom(ctx, module_name); + idx = js_parse_from_clause(s, m); if (idx < 0) goto fail1; me = add_export_entry(s, m, JS_ATOM__star_, export_name, @@ -28587,11 +30851,7 @@ return -1; me->u.req_module_idx = idx; } else { - module_name = js_parse_from_clause(s); - if (module_name == JS_ATOM_NULL) - return -1; - idx = add_req_module_entry(ctx, m, module_name); - JS_FreeAtom(ctx, module_name); + idx = js_parse_from_clause(s, m); if (idx < 0) return -1; if (add_star_export_entry(ctx, m, idx) < 0) @@ -28606,7 +30866,7 @@ peek_token(s, TRUE) == TOK_FUNCTION)) { return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num, + s->token.ptr, JS_PARSE_EXPORT_DEFAULT, NULL); } else { if (js_parse_assign_expr(s)) @@ -28645,12 +30905,11 @@ JSVarKindEnum var_kind); static int add_import(JSParseState *s, JSModuleDef *m, - JSAtom local_name, JSAtom import_name) + JSAtom local_name, JSAtom import_name, BOOL is_star) { JSContext *ctx = s->ctx; int i, var_idx; JSImportEntry *mi; - BOOL is_local; if (local_name == JS_ATOM_arguments || local_name == JS_ATOM_eval) return js_parse_error(s, "invalid import binding"); @@ -28662,8 +30921,7 @@ } } - is_local = (import_name == JS_ATOM__star_); - var_idx = add_closure_var(ctx, s->cur_func, is_local, FALSE, + var_idx = add_closure_var(ctx, s->cur_func, is_star, FALSE, m->import_entries_count, local_name, TRUE, TRUE, FALSE); if (var_idx < 0) @@ -28676,6 +30934,7 @@ mi = &m->import_entriesm->import_entries_count++; mi->import_name = JS_DupAtom(ctx, import_name); mi->var_idx = var_idx; + mi->is_star = is_star; return 0; } @@ -28698,6 +30957,14 @@ JS_FreeAtom(ctx, module_name); return -1; } + idx = add_req_module_entry(ctx, m, module_name); + JS_FreeAtom(ctx, module_name); + if (idx < 0) + return -1; + if (s->token.val == TOK_WITH) { + if (js_parse_with_clause(s, &m->req_module_entriesidx)) + return -1; + } } else { if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { @@ -28708,7 +30975,7 @@ import_name = JS_ATOM_default; if (next_token(s)) goto fail; - if (add_import(s, m, local_name, import_name)) + if (add_import(s, m, local_name, import_name, FALSE)) goto fail; JS_FreeAtom(ctx, local_name); @@ -28734,7 +31001,7 @@ import_name = JS_ATOM__star_; if (next_token(s)) goto fail; - if (add_import(s, m, local_name, import_name)) + if (add_import(s, m, local_name, import_name, TRUE)) goto fail; JS_FreeAtom(ctx, local_name); } else if (s->token.val == '{') { @@ -28742,11 +31009,24 @@ return -1; while (s->token.val != '}') { - if (!token_is_ident(s->token.val)) { - js_parse_error(s, "identifier expected"); - return -1; + BOOL is_string; + if (s->token.val == TOK_STRING) { + is_string = TRUE; + if (js_string_find_invalid_codepoint(JS_VALUE_GET_STRING(s->token.u.str.str)) >= 0) { + js_parse_error(s, "contains unpaired surrogate"); + return -1; + } + import_name = JS_ValueToAtom(s->ctx, s->token.u.str.str); + if (import_name == JS_ATOM_NULL) + return -1; + } else { + is_string = FALSE; + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + return -1; + } + import_name = JS_DupAtom(ctx, s->token.u.ident.atom); } - import_name = JS_DupAtom(ctx, s->token.u.ident.atom); local_name = JS_ATOM_NULL; if (next_token(s)) goto fail; @@ -28758,16 +31038,19 @@ goto fail; } local_name = JS_DupAtom(ctx, s->token.u.ident.atom); - if (next_token(s)) { + if (next_token(s)) + goto fail; + } else { + if (is_string) { + js_parse_error(s, "expecting 'as'"); fail: JS_FreeAtom(ctx, local_name); JS_FreeAtom(ctx, import_name); return -1; } - } else { local_name = JS_DupAtom(ctx, import_name); } - if (add_import(s, m, local_name, import_name)) + if (add_import(s, m, local_name, import_name, FALSE)) goto fail; JS_FreeAtom(ctx, local_name); JS_FreeAtom(ctx, import_name); @@ -28780,14 +31063,10 @@ return -1; } end_import_clause: - module_name = js_parse_from_clause(s); - if (module_name == JS_ATOM_NULL) + idx = js_parse_from_clause(s, m); + if (idx < 0) return -1; } - idx = add_req_module_entry(ctx, m, module_name); - JS_FreeAtom(ctx, module_name); - if (idx < 0) - return -1; for(i = first_import; i < m->import_entries_count; i++) m->import_entriesi.req_module_idx = idx; @@ -28804,7 +31083,7 @@ peek_token(s, TRUE) == TOK_FUNCTION)) { if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) + s->token.ptr)) return -1; } else if (s->token.val == TOK_EXPORT && fd->module) { if (js_parse_export(s)) @@ -28826,7 +31105,9 @@ JSFunctionDef *parent, BOOL is_eval, BOOL is_func_expr, - const char *filename, int line_num) + const char *filename, + const uint8_t *source_ptr, + GetLineColCache *get_line_col_cache) { JSFunctionDef *fd; @@ -28845,10 +31126,12 @@ fd->js_mode = parent->js_mode; fd->parent_scope_level = parent->scope_level; } + fd->strip_debug = ((ctx->rt->strip_flags & JS_STRIP_DEBUG) != 0); + fd->strip_source = ((ctx->rt->strip_flags & (JS_STRIP_DEBUG | JS_STRIP_SOURCE)) != 0); fd->is_eval = is_eval; fd->is_func_expr = is_func_expr; - js_dbuf_init(ctx, &fd->byte_code); + js_dbuf_bytecode_init(ctx, &fd->byte_code); fd->last_opcode_pos = -1; fd->func_name = JS_ATOM_NULL; fd->var_object_idx = -1; @@ -28873,13 +31156,13 @@ fd->body_scope = -1; fd->filename = JS_NewAtom(ctx, filename); - fd->line_num = line_num; + fd->source_pos = source_ptr - get_line_col_cache->buf_start; + fd->get_line_col_cache = get_line_col_cache; js_dbuf_init(ctx, &fd->pc2line); //fd->pc2line_last_line_num = line_num; //fd->pc2line_last_pc = 0; - fd->last_opcode_line_num = line_num; - + fd->last_opcode_source_ptr = source_ptr; return fd; } @@ -28906,6 +31189,8 @@ case OP_FMT_atom_u16: case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: + if ((pos + 1 + 4) > bc_len) + break; /* may happen if there is not enough memory when emiting bytecode */ atom = get_u32(bc_buf + pos + 1); JS_FreeAtomRT(rt, atom); break; @@ -29008,14 +31293,19 @@ const JSVarDef *vars, int var_count, const JSClosureVar *closure_var, int closure_var_count, const JSValue *cpool, uint32_t cpool_count, - const char *source, int line_num, + const char *source, const LabelSlot *label_slots, JSFunctionBytecode *b) { const JSOpCode *oi; - int pos, pos_next, op, size, idx, addr, line, line1, in_source; + int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num; uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits)); BOOL use_short_opcodes = (b != NULL); + if (b) { + int col_num; + line_num = find_line_num(ctx, b, -1, &col_num); + } + /* scan for jump targets */ for (pos = 0; pos < len; pos = pos_next) { op = tabpos; @@ -29068,10 +31358,12 @@ pos = 0; while (pos < len) { op = tabpos; - if (source) { + if (source && b) { + int col_num; if (b) { - line1 = find_line_num(ctx, b, pos) - line_num + 1; + line1 = find_line_num(ctx, b, pos, &col_num) - line_num + 1; } else if (op == OP_line_num) { + /* XXX: no longer works */ line1 = get_u32(tab + pos + 1) - line_num + 1; } if (line1 > line) { @@ -29189,7 +31481,7 @@ has_pool_idx: printf(" %u: ", idx); if (idx < cpool_count) { - JS_DumpValue(ctx, cpoolidx); + JS_PrintValue(ctx, js_dump_value_write, stdout, cpoolidx, NULL); } break; case OP_FMT_atom: @@ -29272,49 +31564,64 @@ js_free(ctx, bits); } -static __maybe_unused void dump_pc2line(JSContext *ctx, const uint8_t *buf, int len, - int line_num) +static __maybe_unused void dump_pc2line(JSContext *ctx, const uint8_t *buf, int len) { - const uint8_t *p_end, *p_next, *p; - int pc, v; + const uint8_t *p_end, *p; + int pc, v, line_num, col_num, ret; unsigned int op; + uint32_t val; if (len <= 0) return; - printf("%5s %5s\n", "PC", "LINE"); + printf("%5s %5s %5s\n", "PC", "LINE", "COL"); p = buf; p_end = buf + len; + + /* get the function line and column numbers */ + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num = val + 1; + + printf("%5s %5d %5d\n", "-", line_num, col_num); + pc = 0; while (p < p_end) { op = *p++; if (op == 0) { - v = unicode_from_utf8(p, p_end - p, &p_next); - if (v < 0) + ret = get_leb128(&val, p, p_end); + if (ret < 0) goto fail; - pc += v; - p = p_next; - v = unicode_from_utf8(p, p_end - p, &p_next); - if (v < 0) { - fail: - printf("invalid pc2line encode pos=%d\n", (int)(p - buf)); - return; - } - if (!(v & 1)) { - v = v >> 1; - } else { - v = -(v >> 1) - 1; - } + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; line_num += v; - p = p_next; } else { op -= PC2LINE_OP_FIRST; pc += (op / PC2LINE_RANGE); line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE; } - printf("%5d %5d\n", pc, line_num); + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num += v; + + printf("%5d %5d %5d\n", pc, line_num, col_num); } + fail: ; } static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b) @@ -29324,8 +31631,10 @@ const char *str; if (b->has_debug && b->debug.filename != JS_ATOM_NULL) { + int line_num, col_num; str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename); - printf("%s:%d: ", str, b->debug.line_num); + line_num = find_line_num(ctx, b, -1, &col_num); + printf("%s:%d:%d: ", str, line_num, col_num); } str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name); @@ -29334,10 +31643,6 @@ printf(" mode:"); if (b->js_mode & JS_MODE_STRICT) printf(" strict"); -#ifdef CONFIG_BIGNUM - if (b->js_mode & JS_MODE_MATH) - printf(" math"); -#endif printf("\n"); } if (b->arg_count && b->vardefs) { @@ -29384,10 +31689,10 @@ b->closure_var, b->closure_var_count, b->cpool, b->cpool_count, b->has_debug ? b->debug.source : NULL, - b->has_debug ? b->debug.line_num : -1, NULL, b); + NULL, b); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32) if (b->has_debug) - dump_pc2line(ctx, b->debug.pc2line_buf, b->debug.pc2line_len, b->debug.line_num); + dump_pc2line(ctx, b->debug.pc2line_buf, b->debug.pc2line_len); #endif printf("\n"); } @@ -29552,20 +31857,10 @@ JSAtom var_name) { int label_pos, end_pos, pos, op; - BOOL is_strict; - is_strict = ((s->js_mode & JS_MODE_STRICT) != 0); /* replace the reference get/put with normal variable accesses */ - if (is_strict) { - /* need to check if the variable exists before evaluating the right - expression */ - /* XXX: need an extra OP_true if destructuring an array */ - dbuf_putc(bc, OP_check_var); - dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); - } else { - /* XXX: need 2 extra OP_true if destructuring an array */ - } + /* XXX: need 2 extra OP_true if destructuring an array */ if (bc_bufpos_next == OP_get_ref_value) { dbuf_putc(bc, OP_get_var); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); @@ -29579,34 +31874,10 @@ assert(bc_bufpos == OP_label); end_pos = label_pos + 2; op = bc_buflabel_pos; - if (is_strict) { - if (op != OP_nop) { - switch(op) { - case OP_insert3: - op = OP_insert2; - break; - case OP_perm4: - op = OP_perm3; - break; - case OP_rot3l: - op = OP_swap; - break; - default: - abort(); - } - bc_bufpos++ = op; - } - } else { - if (op == OP_insert3) - bc_bufpos++ = OP_dup; - } - if (is_strict) { - bc_bufpos = OP_put_var_strict; - /* XXX: need 1 extra OP_drop if destructuring an array */ - } else { - bc_bufpos = OP_put_var; - /* XXX: need 2 extra OP_drop if destructuring an array */ - } + if (op == OP_insert3) + bc_bufpos++ = OP_dup; + bc_bufpos = OP_put_var; + /* XXX: need 2 extra OP_drop if destructuring an array */ put_u32(bc_buf + pos + 1, JS_DupAtom(ctx, var_name)); pos += 5; /* pad with OP_nop */ @@ -29675,7 +31946,13 @@ { dbuf_putc(bc, get_with_scope_opcode(op)); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); - *plabel_done = new_label_fd(s, *plabel_done); + if (*plabel_done < 0) { + *plabel_done = new_label_fd(s); + if (*plabel_done < 0) { + dbuf_set_error(bc); + return; + } + } dbuf_put_u32(bc, *plabel_done); dbuf_putc(bc, is_with); update_label(s, *plabel_done, 1); @@ -29804,6 +32081,7 @@ case OP_scope_get_ref: dbuf_putc(bc, OP_undefined); /* fall thru */ + case OP_scope_get_var_checkthis: case OP_scope_get_var_undef: case OP_scope_get_var: case OP_scope_put_var: @@ -29829,7 +32107,12 @@ } } else { if (s->varsvar_idx.is_lexical) { - dbuf_putc(bc, OP_get_loc_check); + if (op == OP_scope_get_var_checkthis) { + /* only used for 'this' return in derived class constructors */ + dbuf_putc(bc, OP_get_loc_checkthis); + } else { + dbuf_putc(bc, OP_get_loc_check); + } } else { dbuf_putc(bc, OP_get_loc); } @@ -30286,12 +32569,17 @@ /* obj func value */ dbuf_putc(bc, OP_call_method); dbuf_put_u16(bc, 1); + dbuf_putc(bc, OP_drop); } break; default: abort(); } break; + case OP_scope_in_private_field: + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_private_in); + break; default: abort(); } @@ -30410,12 +32698,13 @@ is_arg_scope = (scope_idx == ARG_SCOPE_END); if (!is_arg_scope) { /* add unscoped variables */ + /* XXX: propagate is_const and var_kind too ? */ for(i = 0; i < fd->arg_count; i++) { vd = &fd->argsi; if (vd->var_name != JS_ATOM_NULL) { get_closure_var(ctx, s, fd, - TRUE, i, vd->var_name, FALSE, FALSE, - JS_VAR_NORMAL); + TRUE, i, vd->var_name, FALSE, + vd->is_lexical, JS_VAR_NORMAL); } } for(i = 0; i < fd->var_count; i++) { @@ -30425,8 +32714,8 @@ vd->var_name != JS_ATOM__ret_ && vd->var_name != JS_ATOM_NULL) { get_closure_var(ctx, s, fd, - FALSE, i, vd->var_name, FALSE, FALSE, - JS_VAR_NORMAL); + FALSE, i, vd->var_name, FALSE, + vd->is_lexical, JS_VAR_NORMAL); } } } else { @@ -30435,8 +32724,8 @@ /* do not close top level last result */ if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) { get_closure_var(ctx, s, fd, - FALSE, i, vd->var_name, FALSE, FALSE, - JS_VAR_NORMAL); + FALSE, i, vd->var_name, FALSE, + vd->is_lexical, JS_VAR_NORMAL); } } } @@ -30708,8 +32997,11 @@ evaluating the module so that the exported functions are visible if there are cyclic module references */ if (s->module) { - label_next = new_label_fd(s, -1); - + label_next = new_label_fd(s); + if (label_next < 0) { + dbuf_set_error(bc); + return; + } /* if 'this' is true, initialize the global variables and return */ dbuf_putc(bc, OP_push_this); dbuf_putc(bc, OP_if_false); @@ -30900,7 +33192,7 @@ cc.bc_buf = bc_buf = s->byte_code.buf; cc.bc_len = bc_len = s->byte_code.size; - js_dbuf_init(ctx, &bc_out); + js_dbuf_bytecode_init(ctx, &bc_out); /* first pass for runtime checks (must be done before the variables are created) */ @@ -30959,15 +33251,16 @@ mark_eval_captured_variables(ctx, s, scope); dbuf_putc(&bc_out, op); dbuf_put_u16(&bc_out, call_argc); - dbuf_put_u16(&bc_out, s->scopesscope.first + 1); + dbuf_put_u16(&bc_out, s->scopesscope.first - ARG_SCOPE_END); } break; case OP_apply_eval: /* convert scope index to adjusted variable index */ scope = get_u16(bc_buf + pos + 1); mark_eval_captured_variables(ctx, s, scope); dbuf_putc(&bc_out, op); - dbuf_put_u16(&bc_out, s->scopesscope.first + 1); + dbuf_put_u16(&bc_out, s->scopesscope.first - ARG_SCOPE_END); break; + case OP_scope_get_var_checkthis: case OP_scope_get_var_undef: case OP_scope_get_var: case OP_scope_put_var: @@ -30997,6 +33290,7 @@ case OP_scope_get_private_field: case OP_scope_get_private_field2: case OP_scope_put_private_field: + case OP_scope_in_private_field: { int ret; var_name = get_u32(bc_buf + pos + 1); @@ -31211,6 +33505,17 @@ /* only used during parsing */ break; + case OP_get_field_opt_chain: /* equivalent to OP_get_field */ + { + JSAtom name = get_u32(bc_buf + pos + 1); + dbuf_putc(&bc_out, OP_get_field); + dbuf_put_u32(&bc_out, name); + } + break; + case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */ + dbuf_putc(&bc_out, OP_get_array_el); + break; + default: no_change: dbuf_put(&bc_out, bc_buf + pos, len); @@ -31240,40 +33545,58 @@ return -1; } -/* the pc2line table gives a line number for each PC value */ -static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, int line_num) +/* the pc2line table gives a source position for each PC value */ +static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, uint32_t source_pos) { if (s->line_number_slots != NULL && s->line_number_count < s->line_number_size && pc >= s->line_number_last_pc - && line_num != s->line_number_last) { + && source_pos != s->line_number_last) { s->line_number_slotss->line_number_count.pc = pc; - s->line_number_slotss->line_number_count.line_num = line_num; + s->line_number_slotss->line_number_count.source_pos = source_pos; s->line_number_count++; s->line_number_last_pc = pc; - s->line_number_last = line_num; + s->line_number_last = source_pos; } } +/* XXX: could use a more compact storage */ +/* XXX: get_line_col_cached() is slow. For more predictable + performance, line/cols could be stored every N source + bytes. Alternatively, get_line_col_cached() could be issued in + emit_source_pos() so that the deltas are more likely to be + small. */ static void compute_pc2line_info(JSFunctionDef *s) { - if (!(s->js_mode & JS_MODE_STRIP) && s->line_number_slots) { - int last_line_num = s->line_num; + if (!s->strip_debug) { + int last_line_num, last_col_num; uint32_t last_pc = 0; - int i; - + int i, line_num, col_num; + const uint8_t *buf_start = s->get_line_col_cache->buf_start; js_dbuf_init(s->ctx, &s->pc2line); + + last_line_num = get_line_col_cached(s->get_line_col_cache, + &last_col_num, + buf_start + s->source_pos); + dbuf_put_leb128(&s->pc2line, last_line_num); /* line number minus 1 */ + dbuf_put_leb128(&s->pc2line, last_col_num); /* column number minus 1 */ + for (i = 0; i < s->line_number_count; i++) { uint32_t pc = s->line_number_slotsi.pc; - int line_num = s->line_number_slotsi.line_num; - int diff_pc, diff_line; + uint32_t source_pos = s->line_number_slotsi.source_pos; + int diff_pc, diff_line, diff_col; - if (line_num < 0) + if (source_pos == -1) continue; - diff_pc = pc - last_pc; + if (diff_pc < 0) + continue; + + line_num = get_line_col_cached(s->get_line_col_cache, &col_num, + buf_start + source_pos); diff_line = line_num - last_line_num; - if (diff_line == 0 || diff_pc < 0) + diff_col = col_num - last_col_num; + if (diff_line == 0 && diff_col == 0) continue; if (diff_line >= PC2LINE_BASE && @@ -31287,8 +33610,11 @@ dbuf_put_leb128(&s->pc2line, diff_pc); dbuf_put_sleb128(&s->pc2line, diff_line); } + dbuf_put_sleb128(&s->pc2line, diff_col); + last_pc = pc; last_line_num = line_num; + last_col_num = col_num; } } } @@ -31334,10 +33660,11 @@ /* return the target label, following the OP_goto jumps the first opcode at destination is stored in *pop */ -static int find_jump_target(JSFunctionDef *s, int label, int *pop, int *pline) +static int find_jump_target(JSFunctionDef *s, int label0, int *pop, int *pline) { - int i, pos, op; + int i, pos, op, label; + label = label0; update_label(s, label, -1); for (i = 0; i < 10; i++) { assert(label >= 0 && label < s->label_count); @@ -31368,6 +33695,19 @@ } } /* cycle detected, could issue a warning */ + /* XXX: the combination of find_jump_target() and skip_dead_code() + seems incorrect with cyclic labels. See for exemple: + + for (;;) { + l:break l; + l:break l; + l:break l; + l:break l; + } + + Avoiding changing the target is just a workaround and might not + suffice to completely fix the problem. */ + label = label0; done: *pop = op; update_label(s, label, +1); @@ -31470,11 +33810,11 @@ label_slots = s->label_slots; - line_num = s->line_num; + line_num = s->source_pos; cc.bc_buf = bc_buf = s->byte_code.buf; cc.bc_len = bc_len = s->byte_code.size; - js_dbuf_init(ctx, &bc_out); + js_dbuf_bytecode_init(ctx, &bc_out); #if SHORT_OPCODES if (s->jump_size) { @@ -31484,11 +33824,11 @@ } #endif /* XXX: Should skip this phase if not generating SHORT_OPCODES */ - if (s->line_number_size && !(s->js_mode & JS_MODE_STRIP)) { + if (s->line_number_size && !s->strip_debug) { s->line_number_slots = js_mallocz(s->ctx, sizeof(*s->line_number_slots) * s->line_number_size); if (s->line_number_slots == NULL) return -1; - s->line_number_last = s->line_num; + s->line_number_last = s->source_pos; s->line_number_last_pc = 0; } @@ -31639,7 +33979,7 @@ if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) { /* jump to return/throw: remove jump, append return/throw */ /* updating the line number obfuscates assembly listing */ - //if (line1 >= 0) line_num = line1; + //if (line1 != -1) line_num = line1; update_label(s, label, -1); add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, op1); @@ -31687,7 +34027,7 @@ int pos1 = cc.pos; int line1 = cc.line_num; if (code_has_label(&cc, pos1, label)) { - if (line1 >= 0) line_num = line1; + if (line1 != -1) line_num = line1; pos_next = pos1; update_label(s, label, -1); label = cc.label; @@ -31760,7 +34100,6 @@ case OP_with_delete_var: case OP_with_make_ref: case OP_with_get_ref: - case OP_with_get_ref_undef: { JSAtom atom; int is_with; @@ -31887,6 +34226,26 @@ } goto no_change; + case OP_push_bigint_i32: + if (OPTIMIZE) { + /* transform i32(val) neg -> i32(-val) */ + val = get_i32(bc_buf + pos + 1); + if (val != INT32_MIN + && code_match(&cc, pos_next, OP_neg, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (code_match(&cc, cc.pos, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + } else { + add_pc2line_info(s, bc_out.size, line_num); + dbuf_putc(&bc_out, OP_push_bigint_i32); + dbuf_put_u32(&bc_out, -val); + } + pos_next = cc.pos; + break; + } + } + goto no_change; + #if SHORT_OPCODES case OP_push_const: case OP_fclosure: @@ -31935,9 +34294,8 @@ goto no_change; case OP_to_propkey: - case OP_to_propkey2: if (OPTIMIZE) { - /* remove redundant to_propkey/to_propkey2 opcodes when storing simple data */ + /* remove redundant to_propkey opcodes when storing simple data */ if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_put_array_el, -1) || code_match(&cc, pos_next, M3(OP_push_i32, OP_push_const, OP_push_atom_value), OP_put_array_el, -1) || code_match(&cc, pos_next, M4(OP_undefined, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) { @@ -31994,12 +34352,11 @@ if (OPTIMIZE) { /* Transformation: insert2 put_field(a) drop -> put_field(a) - insert2 put_var_strict(a) drop -> put_var_strict(a) */ - if (code_match(&cc, pos_next, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) { + if (code_match(&cc, pos_next, OP_put_field, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); - dbuf_putc(&bc_out, cc.op); + dbuf_putc(&bc_out, OP_put_field); dbuf_put_u32(&bc_out, cc.atom); pos_next = cc.pos; break; @@ -32145,7 +34502,6 @@ /* transformation: post_inc put_x drop -> inc put_x post_inc perm3 put_field drop -> inc put_field - post_inc perm3 put_var_strict drop -> inc put_var_strict post_inc perm4 put_array_el drop -> inc put_array_el */ int op1, idx; @@ -32164,11 +34520,11 @@ put_short_code(&bc_out, op1, idx); break; } - if (code_match(&cc, pos_next, OP_perm3, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) { + if (code_match(&cc, pos_next, OP_perm3, OP_put_field, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); - dbuf_putc(&bc_out, cc.op); + dbuf_putc(&bc_out, OP_put_field); dbuf_put_u32(&bc_out, cc.atom); pos_next = cc.pos; break; @@ -32344,6 +34700,7 @@ int bc_len; int stack_len_max; uint16_t *stack_level_tab; + int32_t *catch_pos_tab; int *pc_stack; int pc_stack_len; int pc_stack_size; @@ -32351,7 +34708,7 @@ /* 'op' is only used for error indication */ static __exception int ss_check(JSContext *ctx, StackSizeState *s, - int pos, int op, int stack_len) + int pos, int op, int stack_len, int catch_pos) { if ((unsigned)pos >= s->bc_len) { JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); @@ -32367,9 +34724,13 @@ if (s->stack_level_tabpos != 0xffff) { /* already explored: check that the stack size is consistent */ if (s->stack_level_tabpos != stack_len) { - JS_ThrowInternalError(ctx, "unconsistent stack size: %d %d (pc=%d)", + JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)", s->stack_level_tabpos, stack_len, pos); return -1; + } else if (s->catch_pos_tabpos != catch_pos) { + JS_ThrowInternalError(ctx, "inconsistent catch position: %d %d (pc=%d)", + s->catch_pos_tabpos, catch_pos, pos); + return -1; } else { return 0; } @@ -32377,6 +34738,7 @@ /* mark as explored and store the stack size */ s->stack_level_tabpos = stack_len; + s->catch_pos_tabpos = catch_pos; /* queue the new PC to explore */ if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack0), @@ -32391,7 +34753,7 @@ int *pstack_size) { StackSizeState s_s, *s = &s_s; - int i, diff, n_pop, pos_next, stack_len, pos, op; + int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level; const JSOpCode *oi; const uint8_t *bc_buf; @@ -32404,24 +34766,33 @@ return -1; for(i = 0; i < s->bc_len; i++) s->stack_level_tabi = 0xffff; - s->stack_len_max = 0; s->pc_stack = NULL; + s->catch_pos_tab = js_malloc(ctx, sizeof(s->catch_pos_tab0) * + s->bc_len); + if (!s->catch_pos_tab) + goto fail; + + s->stack_len_max = 0; s->pc_stack_len = 0; s->pc_stack_size = 0; /* breadth-first graph exploration */ - if (ss_check(ctx, s, 0, OP_invalid, 0)) + if (ss_check(ctx, s, 0, OP_invalid, 0, -1)) goto fail; while (s->pc_stack_len > 0) { pos = s->pc_stack--s->pc_stack_len; stack_len = s->stack_level_tabpos; + catch_pos = s->catch_pos_tabpos; op = bc_bufpos; if (op == 0 || op >= OP_COUNT) { JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos); goto fail; } oi = &short_opcode_info(op); +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64) + printf("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos); +#endif pos_next = pos + oi->size; if (pos_next > s->bc_len) { JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); @@ -32477,55 +34848,103 @@ case OP_if_true8: case OP_if_false8: diff = (int8_t)bc_bufpos + 1; - if (ss_check(ctx, s, pos + 1 + diff, op, stack_len)) + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) goto fail; break; #endif case OP_if_true: case OP_if_false: - case OP_catch: diff = get_u32(bc_buf + pos + 1); - if (ss_check(ctx, s, pos + 1 + diff, op, stack_len)) + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) goto fail; break; case OP_gosub: diff = get_u32(bc_buf + pos + 1); - if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1)) + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos)) goto fail; break; case OP_with_get_var: case OP_with_delete_var: diff = get_u32(bc_buf + pos + 5); - if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1)) + if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1, catch_pos)) goto fail; break; case OP_with_make_ref: case OP_with_get_ref: - case OP_with_get_ref_undef: diff = get_u32(bc_buf + pos + 5); - if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2)) + if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2, catch_pos)) goto fail; break; case OP_with_put_var: diff = get_u32(bc_buf + pos + 5); - if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1)) + if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1, catch_pos)) goto fail; break; - + case OP_catch: + diff = get_u32(bc_buf + pos + 1); + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + catch_pos = pos; + break; + case OP_for_of_start: + case OP_for_await_of_start: + catch_pos = pos; + break; + /* we assume the catch offset entry is only removed with + some op codes */ + case OP_drop: + catch_level = stack_len; + goto check_catch; + case OP_nip: + catch_level = stack_len - 1; + goto check_catch; + case OP_nip1: + catch_level = stack_len - 1; + goto check_catch; + case OP_iterator_close: + catch_level = stack_len + 2; + check_catch: + /* Note: for for_of_start/for_await_of_start we consider + the catch offset is on the first stack entry instead of + the thirst */ + if (catch_pos >= 0) { + int level; + level = s->stack_level_tabcatch_pos; + if (bc_bufcatch_pos != OP_catch) + level++; /* for_of_start, for_wait_of_start */ + /* catch_level = stack_level before op_catch is executed ? */ + if (catch_level == level) { + catch_pos = s->catch_pos_tabcatch_pos; + } + } + break; + case OP_nip_catch: + if (catch_pos < 0) { + JS_ThrowInternalError(ctx, "nip_catch: no catch op (pc=%d)", pos); + goto fail; + } + stack_len = s->stack_level_tabcatch_pos; + if (bc_bufcatch_pos != OP_catch) + stack_len++; /* for_of_start, for_wait_of_start */ + stack_len++; /* no stack overflow is possible by construction */ + catch_pos = s->catch_pos_tabcatch_pos; + break; default: break; } - if (ss_check(ctx, s, pos_next, op, stack_len)) + if (ss_check(ctx, s, pos_next, op, stack_len, catch_pos)) goto fail; done_insn: ; } - js_free(ctx, s->stack_level_tab); js_free(ctx, s->pc_stack); + js_free(ctx, s->catch_pos_tab); + js_free(ctx, s->stack_level_tab); *pstack_size = s->stack_len_max; return 0; fail: - js_free(ctx, s->stack_level_tab); js_free(ctx, s->pc_stack); + js_free(ctx, s->catch_pos_tab); + js_free(ctx, s->stack_level_tab); *pstack_size = 0; return -1; } @@ -32631,12 +35050,12 @@ } #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4) - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_debug) { printf("pass 1\n"); dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, - fd->cpool, fd->cpool_count, fd->source, fd->line_num, + fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); printf("\n"); } @@ -32646,12 +35065,12 @@ goto fail; #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2) - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_debug) { printf("pass 2\n"); dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, - fd->cpool, fd->cpool_count, fd->source, fd->line_num, + fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); printf("\n"); } @@ -32663,7 +35082,7 @@ if (compute_stack_size(ctx, fd, &stack_size) < 0) goto fail; - if (fd->js_mode & JS_MODE_STRIP) { + if (fd->strip_debug) { function_size = offsetof(JSFunctionBytecode, debug); } else { function_size = sizeof(*b); @@ -32671,7 +35090,7 @@ cpool_offset = function_size; function_size += fd->cpool_count * sizeof(*fd->cpool); vardefs_offset = function_size; - if (!(fd->js_mode & JS_MODE_STRIP) || fd->has_eval_call) { + if (!fd->strip_debug || fd->has_eval_call) { function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs); } closure_var_offset = function_size; @@ -32692,7 +35111,7 @@ b->func_name = fd->func_name; if (fd->arg_count + fd->var_count > 0) { - if ((fd->js_mode & JS_MODE_STRIP) && !fd->has_eval_call) { + if (fd->strip_debug && !fd->has_eval_call) { /* Strip variable definitions not needed at runtime */ int i; for(i = 0; i < fd->var_count; i++) { @@ -32707,10 +35126,8 @@ } } else { b->vardefs = (void *)((uint8_t*)b + vardefs_offset); - if (fd->arg_count) - memcpy(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args0)); - if (fd->var_count) - memcpy(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars0)); + memcpy_no_ub(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args0)); + memcpy_no_ub(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars0)); } b->var_count = fd->var_count; b->arg_count = fd->arg_count; @@ -32728,7 +35145,7 @@ b->stack_size = stack_size; - if (fd->js_mode & JS_MODE_STRIP) { + if (fd->strip_debug) { JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); // probably useless } else { @@ -32737,7 +35154,6 @@ */ b->has_debug = 1; b->debug.filename = fd->filename; - b->debug.line_num = fd->line_num; //DynBuf pc2line; //compute_pc2line_info(fd, &pc2line); @@ -32771,13 +35187,14 @@ b->super_call_allowed = fd->super_call_allowed; b->super_allowed = fd->super_allowed; b->arguments_allowed = fd->arguments_allowed; - b->backtrace_barrier = fd->backtrace_barrier; + b->is_direct_or_indirect_eval = (fd->eval_type == JS_EVAL_TYPE_DIRECT || + fd->eval_type == JS_EVAL_TYPE_INDIRECT); b->realm = JS_DupContext(ctx); add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1) - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_debug) { js_dump_function_bytecode(ctx, b); } #endif @@ -32805,7 +35222,8 @@ JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); } #endif - free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE); + if (b->byte_code_buf) + free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE); if (b->vardefs) { for(i = 0; i < b->arg_count + b->var_count; i++) { @@ -32919,20 +35337,16 @@ s->cur_func->has_use_strict = TRUE; s->cur_func->js_mode |= JS_MODE_STRICT; } -#if !defined(DUMP_BYTECODE) || !(DUMP_BYTECODE & 8) - else if (!strcmp(str, "use strip")) { - s->cur_func->js_mode |= JS_MODE_STRIP; - } -#endif -#ifdef CONFIG_BIGNUM - else if (s->ctx->bignum_ext && !strcmp(str, "use math")) { - s->cur_func->js_mode |= JS_MODE_MATH; - } -#endif } return js_parse_seek_token(s, &pos); } +/* return TRUE if the keyword is forbidden only in strict mode */ +static BOOL is_strict_future_keyword(JSAtom atom) +{ + return (atom >= JS_ATOM_LAST_KEYWORD + 1 && atom <= JS_ATOM_LAST_STRICT_KEYWORD); +} + static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd, JSAtom func_name) { @@ -32943,13 +35357,15 @@ if (!fd->has_simple_parameter_list && fd->has_use_strict) { return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter"); } - if (func_name == JS_ATOM_eval || func_name == JS_ATOM_arguments) { + if (func_name == JS_ATOM_eval || func_name == JS_ATOM_arguments || + is_strict_future_keyword(func_name)) { return js_parse_error(s, "invalid function name in strict code"); } for (idx = 0; idx < fd->arg_count; idx++) { name = fd->argsidx.var_name; - if (name == JS_ATOM_eval || name == JS_ATOM_arguments) { + if (name == JS_ATOM_eval || name == JS_ATOM_arguments || + is_strict_future_keyword(name)) { return js_parse_error(s, "invalid argument name in strict code"); } } @@ -32989,7 +35405,8 @@ JSFunctionDef *fd; fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE, - s->filename, 0); + s->filename, s->buf_start, + &s->get_line_col_cache); if (!fd) return NULL; fd->func_name = JS_ATOM_NULL; @@ -33016,7 +35433,6 @@ JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, - int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd) { @@ -33054,8 +35470,9 @@ func_type == JS_PARSE_FUNC_EXPR && (func_kind & JS_FUNC_GENERATOR)) || (s->token.u.ident.atom == JS_ATOM_await && - func_type == JS_PARSE_FUNC_EXPR && - (func_kind & JS_FUNC_ASYNC))) { + ((func_type == JS_PARSE_FUNC_EXPR && + (func_kind & JS_FUNC_ASYNC)) || + func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))) { return js_parse_error_reserved_identifier(s); } } @@ -33130,7 +35547,8 @@ } fd = js_new_function_def(ctx, fd, FALSE, is_expr, - s->filename, function_line_num); + s->filename, ptr, + &s->get_line_col_cache); if (!fd) { JS_FreeAtom(ctx, func_name); return -1; @@ -33149,7 +35567,8 @@ func_type == JS_PARSE_FUNC_SETTER || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); - fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW); + fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW && + func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT); fd->has_this_binding = fd->has_arguments_binding; fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); if (func_type == JS_PARSE_FUNC_ARROW) { @@ -33157,6 +35576,11 @@ fd->super_call_allowed = fd->parent->super_call_allowed; fd->super_allowed = fd->parent->super_allowed; fd->arguments_allowed = fd->parent->arguments_allowed; + } else if (func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { + fd->new_target_allowed = TRUE; // although new.target === undefined + fd->super_call_allowed = FALSE; + fd->super_allowed = TRUE; + fd->arguments_allowed = FALSE; } else { fd->new_target_allowed = TRUE; fd->super_call_allowed = fd->is_derived_class_constructor; @@ -33194,7 +35618,7 @@ if (add_arg(ctx, fd, name) < 0) goto fail; fd->defined_arg_count = 1; - } else { + } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { if (s->token.val == '(') { int skip_bits; /* if there is an '=' inside the parameter list, we @@ -33221,6 +35645,8 @@ int idx, has_initializer; if (s->token.val == TOK_ELLIPSIS) { + if (func_type == JS_PARSE_FUNC_SETTER) + goto fail_accessor; fd->has_simple_parameter_list = FALSE; rest = TRUE; if (next_token(s)) @@ -33237,7 +35663,7 @@ emit_op(s, OP_get_arg); emit_u16(s, idx); } - has_initializer = js_parse_destructuring_element(s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1, TRUE); + has_initializer = js_parse_destructuring_element(s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1, TRUE, FALSE); if (has_initializer < 0) goto fail; if (has_initializer) @@ -33255,6 +35681,8 @@ goto fail; } if (fd->has_parameter_expressions) { + if (js_parse_check_duplicate_parameter(s, name)) + goto fail; if (define_var(s, fd, name, JS_VAR_DEF_LET) < 0) goto fail; } @@ -33332,6 +35760,7 @@ } if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) || (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) { + fail_accessor: js_parse_error(s, "invalid number of arguments for getter or setter"); goto fail; } @@ -33386,7 +35815,7 @@ push_scope(s); /* enter body scope */ fd->body_scope = fd->scope_level; - if (s->token.val == TOK_ARROW) { + if (s->token.val == TOK_ARROW && func_type == JS_PARSE_FUNC_ARROW) { if (next_token(s)) goto fail; @@ -33402,7 +35831,7 @@ else emit_op(s, OP_return); - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_source) { /* save the function source code */ /* the end of the function source code is after the last token of the function source stored into s->last_ptr */ @@ -33415,8 +35844,10 @@ } } - if (js_parse_expect(s, '{')) - goto fail; + if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { + if (js_parse_expect(s, '{')) + goto fail; + } if (js_parse_directives(s)) goto fail; @@ -33429,7 +35860,7 @@ if (js_parse_source_element(s)) goto fail; } - if (!(fd->js_mode & JS_MODE_STRIP)) { + if (!fd->strip_source) { /* save the function source code */ fd->source_len = s->buf_ptr - ptr; fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); @@ -33446,9 +35877,15 @@ if (js_is_live_code(s)) { emit_return(s, FALSE); } -done: + done: s->cur_func = fd->parent; + /* Reparse identifiers after the function is terminated so that + the token is parsed in the englobing function. It could be done + by just using next_token() here for normal functions, but it is + necessary for arrow functions with an expression body. */ + reparse_ident_token(s); + /* create the function object */ { int idx; @@ -33563,12 +36000,10 @@ JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, - const uint8_t *ptr, - int function_line_num) + const uint8_t *ptr) { return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr, - function_line_num, JS_PARSE_EXPORT_NONE, - NULL); + JS_PARSE_EXPORT_NONE, NULL); } static __exception int js_parse_program(JSParseState *s) @@ -33600,12 +36035,24 @@ if (!s->is_module) { /* return the value of the hidden variable eval_ret_idx */ - emit_op(s, OP_get_loc); - emit_u16(s, fd->eval_ret_idx); + if (fd->func_kind == JS_FUNC_ASYNC) { + /* wrap the return value in an object so that promises can + be safely returned */ + emit_op(s, OP_object); + emit_op(s, OP_dup); - emit_op(s, OP_return); + emit_op(s, OP_get_loc); + emit_u16(s, fd->eval_ret_idx); + + emit_op(s, OP_put_field); + emit_atom(s, JS_ATOM_value); + } else { + emit_op(s, OP_get_loc); + emit_u16(s, fd->eval_ret_idx); + } + emit_return(s, TRUE); } else { - emit_op(s, OP_return_undef); + emit_return(s, FALSE); } return 0; @@ -33618,11 +36065,15 @@ memset(s, 0, sizeof(*s)); s->ctx = ctx; s->filename = filename; - s->line_num = 1; - s->buf_ptr = (const uint8_t *)input; + s->buf_start = s->buf_ptr = (const uint8_t *)input; s->buf_end = s->buf_ptr + input_len; s->token.val = ' '; - s->token.line_num = 1; + s->token.ptr = s->buf_ptr; + + s->get_line_col_cache.ptr = s->buf_start; + s->get_line_col_cache.buf_start = s->buf_start; + s->get_line_col_cache.line_num = 0; + s->get_line_col_cache.col_num = 0; } static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, @@ -33648,7 +36099,6 @@ ret_val = js_evaluate_module(ctx, m); if (JS_IsException(ret_val)) { fail: - js_free_modules(ctx, JS_FREE_MODULE_NOT_EVALUATED); return JS_EXCEPTION; } } else { @@ -33663,31 +36113,6 @@ return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL); } -static void skip_shebang(JSParseState *s) -{ - const uint8_t *p = s->buf_ptr; - int c; - - if (p0 == '#' && p1 == '!') { - p += 2; - while (p < s->buf_end) { - if (*p == '\n' || *p == '\r') { - break; - } else if (*p >= 0x80) { - c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); - if (c == CP_LS || c == CP_PS) { - break; - } else if (c == -1) { - p++; /* skip invalid UTF-8 */ - } - } else { - p++; - } - } - s->buf_ptr = p; - } -} - /* 'input' must be zero terminated i.e. inputinput_len = '\0'. */ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, @@ -33703,7 +36128,7 @@ JSModuleDef *m; js_parse_init(ctx, s, input, input_len, filename); - skip_shebang(s); + skip_shebang(&s->buf_ptr, s->buf_end); eval_type = flags & JS_EVAL_TYPE_MASK; m = NULL; @@ -33724,8 +36149,6 @@ js_mode = 0; if (flags & JS_EVAL_FLAG_STRICT) js_mode |= JS_MODE_STRICT; - if (flags & JS_EVAL_FLAG_STRIP) - js_mode |= JS_MODE_STRIP; if (eval_type == JS_EVAL_TYPE_MODULE) { JSAtom module_name = JS_NewAtom(ctx, filename); if (module_name == JS_ATOM_NULL) @@ -33736,13 +36159,13 @@ js_mode |= JS_MODE_STRICT; } } - fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1); + fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, + s->buf_start, &s->get_line_col_cache); if (!fd) goto fail1; s->cur_func = fd; fd->eval_type = eval_type; fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); - fd->backtrace_barrier = ((flags & JS_EVAL_FLAG_BACKTRACE_BARRIER) != 0); if (eval_type == JS_EVAL_TYPE_DIRECT) { fd->new_target_allowed = b->new_target_allowed; fd->super_call_allowed = b->super_call_allowed; @@ -33761,6 +36184,10 @@ goto fail; } fd->module = m; + if (m != NULL || (flags & JS_EVAL_FLAG_ASYNC)) { + fd->in_function_body = TRUE; + fd->func_kind = JS_FUNC_ASYNC; + } s->is_module = (m != NULL); s->allow_html_comments = !s->is_module; @@ -33775,6 +36202,9 @@ goto fail1; } + if (m != NULL) + m->has_tla = fd->has_await; + /* create the function object and all the enclosed functions */ fun_obj = js_create_function(ctx, fd); if (JS_IsException(fun_obj)) @@ -33784,7 +36214,7 @@ m->func_obj = fun_obj; if (js_resolve_module(ctx, m) < 0) goto fail1; - fun_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); + fun_obj = JS_NewModuleValue(ctx, m); } if (flags & JS_EVAL_FLAG_COMPILE_ONLY) { ret_val = fun_obj; @@ -33795,7 +36225,7 @@ fail1: /* XXX: should free all the unresolved dependencies */ if (m) - js_free_module_def(ctx, m); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); return JS_EXCEPTION; } @@ -33804,11 +36234,22 @@ const char *input, size_t input_len, const char *filename, int flags, int scope_idx) { + BOOL backtrace_barrier = ((flags & JS_EVAL_FLAG_BACKTRACE_BARRIER) != 0); + int saved_js_mode = 0; + JSValue ret; + if (unlikely(!ctx->eval_internal)) { return JS_ThrowTypeError(ctx, "eval is not supported"); } - return ctx->eval_internal(ctx, this_obj, input, input_len, filename, - flags, scope_idx); + if (backtrace_barrier && ctx->rt->current_stack_frame) { + saved_js_mode = ctx->rt->current_stack_frame->js_mode; + ctx->rt->current_stack_frame->js_mode |= JS_MODE_BACKTRACE_BARRIER; + } + ret = ctx->eval_internal(ctx, this_obj, input, input_len, filename, + flags, scope_idx); + if (backtrace_barrier && ctx->rt->current_stack_frame) + ctx->rt->current_stack_frame->js_mode = saved_js_mode; + return ret; } static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, @@ -33982,8 +36423,6 @@ BC_TAG_OBJECT, BC_TAG_ARRAY, BC_TAG_BIG_INT, - BC_TAG_BIG_FLOAT, - BC_TAG_BIG_DECIMAL, BC_TAG_TEMPLATE_OBJECT, BC_TAG_FUNCTION_BYTECODE, BC_TAG_MODULE, @@ -33995,22 +36434,11 @@ BC_TAG_OBJECT_REFERENCE, } BCTagEnum; -#ifdef CONFIG_BIGNUM -#define BC_BASE_VERSION 2 -#else -#define BC_BASE_VERSION 1 -#endif -#define BC_BE_VERSION 0x40 -#ifdef WORDS_BIGENDIAN -#define BC_VERSION (BC_BASE_VERSION | BC_BE_VERSION) -#else -#define BC_VERSION BC_BASE_VERSION -#endif +#define BC_VERSION 5 typedef struct BCWriterState { JSContext *ctx; DynBuf dbuf; - BOOL byte_swap : 8; BOOL allow_bytecode : 8; BOOL allow_sab : 8; BOOL allow_reference : 8; @@ -34040,8 +36468,6 @@ "object", "array", "bigint", - "bigfloat", - "bigdecimal", "template", "function", "module", @@ -34054,6 +36480,15 @@ }; #endif +static inline BOOL is_be(void) +{ + union { + uint16_t a; + uint8_t b; + } u = {0x100}; + return u.b; +} + static void bc_put_u8(BCWriterState *s, uint8_t v) { dbuf_putc(&s->dbuf, v); @@ -34061,21 +36496,21 @@ static void bc_put_u16(BCWriterState *s, uint16_t v) { - if (s->byte_swap) + if (is_be()) v = bswap16(v); dbuf_put_u16(&s->dbuf, v); } static __maybe_unused void bc_put_u32(BCWriterState *s, uint32_t v) { - if (s->byte_swap) + if (is_be()) v = bswap32(v); dbuf_put_u32(&s->dbuf, v); } static void bc_put_u64(BCWriterState *s, uint64_t v) { - if (s->byte_swap) + if (is_be()) v = bswap64(v); dbuf_put(&s->dbuf, (uint8_t *)&v, sizeof(v)); } @@ -34245,7 +36680,7 @@ pos += len; } - if (s->byte_swap) + if (is_be()) bc_byte_swap(bc_buf, bc_len); dbuf_put(&s->dbuf, bc_buf, bc_len); @@ -34269,142 +36704,54 @@ } } -#ifdef CONFIG_BIGNUM -static int JS_WriteBigNum(BCWriterState *s, JSValueConst obj) +static int JS_WriteBigInt(BCWriterState *s, JSValueConst obj) { - uint32_t tag, tag1; - int64_t e; - JSBigFloat *bf = JS_VALUE_GET_PTR(obj); - bf_t *a = &bf->num; - size_t len, i, n1, j; - limb_t v; + JSBigIntBuf buf; + JSBigInt *p; + uint32_t len, i; + js_limb_t v, b; + int shift; - tag = JS_VALUE_GET_TAG(obj); - switch(tag) { - case JS_TAG_BIG_INT: - tag1 = BC_TAG_BIG_INT; - break; - case JS_TAG_BIG_FLOAT: - tag1 = BC_TAG_BIG_FLOAT; - break; - case JS_TAG_BIG_DECIMAL: - tag1 = BC_TAG_BIG_DECIMAL; - break; - default: - abort(); - } - bc_put_u8(s, tag1); + bc_put_u8(s, BC_TAG_BIG_INT); - /* sign + exponent */ - if (a->expn == BF_EXP_ZERO) - e = 0; - else if (a->expn == BF_EXP_INF) - e = 1; - else if (a->expn == BF_EXP_NAN) - e = 2; - else if (a->expn >= 0) - e = a->expn + 3; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_SHORT_BIG_INT) + p = js_bigint_set_short(&buf, obj); else - e = a->expn; - e = (e << 1) | a->sign; - if (e < INT32_MIN || e > INT32_MAX) { - JS_ThrowInternalError(s->ctx, "bignum exponent is too large"); - return -1; + p = JS_VALUE_GET_PTR(obj); + if (p->len == 1 && p->tab0 == 0) { + /* zero case */ + len = 0; + } else { + /* compute the length of the two's complement representation + in bytes */ + len = p->len * (JS_LIMB_BITS / 8); + v = p->tabp->len - 1; + shift = JS_LIMB_BITS - 8; + while (shift > 0) { + b = (v >> shift) & 0xff; + if (b != 0x00 && b != 0xff) + break; + if ((b & 1) != ((v >> (shift - 1)) & 1)) + break; + shift -= 8; + len--; + } } - bc_put_sleb128(s, e); - - /* mantissa */ - if (a->len != 0) { - if (tag != JS_TAG_BIG_DECIMAL) { - i = 0; - while (i < a->len && a->tabi == 0) - i++; - assert(i < a->len); - v = a->tabi; - n1 = sizeof(limb_t); - while ((v & 0xff) == 0) { - n1--; - v >>= 8; - } - i++; - len = (a->len - i) * sizeof(limb_t) + n1; - if (len > INT32_MAX) { - JS_ThrowInternalError(s->ctx, "bignum is too large"); - return -1; - } - bc_put_leb128(s, len); - /* always saved in byte based little endian representation */ - for(j = 0; j < n1; j++) { - dbuf_putc(&s->dbuf, v >> (j * 8)); - } - for(; i < a->len; i++) { - limb_t v = a->tabi; -#if LIMB_BITS == 32 -#ifdef WORDS_BIGENDIAN - v = bswap32(v); -#endif - dbuf_put_u32(&s->dbuf, v); + bc_put_leb128(s, len); + if (len > 0) { + for(i = 0; i < (len / (JS_LIMB_BITS / 8)); i++) { +#if JS_LIMB_BITS == 32 + bc_put_u32(s, p->tabi); #else -#ifdef WORDS_BIGENDIAN - v = bswap64(v); -#endif - dbuf_put_u64(&s->dbuf, v); + bc_put_u64(s, p->tabi); #endif - } - } else { - int bpos, d; - uint8_t v8; - size_t i0; - - /* little endian BCD */ - i = 0; - while (i < a->len && a->tabi == 0) - i++; - assert(i < a->len); - len = a->len * LIMB_DIGITS; - v = a->tabi; - j = 0; - while ((v % 10) == 0) { - j++; - v /= 10; - } - len -= j; - assert(len > 0); - if (len > INT32_MAX) { - JS_ThrowInternalError(s->ctx, "bignum is too large"); - return -1; - } - bc_put_leb128(s, len); - - bpos = 0; - v8 = 0; - i0 = i; - for(; i < a->len; i++) { - if (i != i0) { - v = a->tabi; - j = 0; - } - for(; j < LIMB_DIGITS; j++) { - d = v % 10; - v /= 10; - if (bpos == 0) { - v8 = d; - bpos = 1; - } else { - dbuf_putc(&s->dbuf, v8 | (d << 4)); - bpos = 0; - } - } - } - /* flush the last digit */ - if (bpos) { - dbuf_putc(&s->dbuf, v8); - } + } + for(i = 0; i < len % (JS_LIMB_BITS / 8); i++) { + bc_put_u8(s, (p->tabp->len - 1 >> (i * 8)) & 0xff); } } return 0; } -#endif /* CONFIG_BIGNUM */ static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj); @@ -34426,7 +36773,7 @@ bc_set_flags(&flags, &idx, b->super_allowed, 1); bc_set_flags(&flags, &idx, b->arguments_allowed, 1); bc_set_flags(&flags, &idx, b->has_debug, 1); - bc_set_flags(&flags, &idx, b->backtrace_barrier, 1); + bc_set_flags(&flags, &idx, b->is_direct_or_indirect_eval, 1); assert(idx <= 16); bc_put_u16(s, flags); bc_put_u8(s, b->js_mode); @@ -34478,9 +36825,14 @@ if (b->has_debug) { bc_put_atom(s, b->debug.filename); - bc_put_leb128(s, b->debug.line_num); bc_put_leb128(s, b->debug.pc2line_len); dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); + if (b->debug.source) { + bc_put_leb128(s, b->debug.source_len); + dbuf_put(&s->dbuf, (uint8_t *)b->debug.source, b->debug.source_len); + } else { + bc_put_leb128(s, 0); + } } for(i = 0; i < b->cpool_count; i++) { @@ -34504,6 +36856,8 @@ for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entriesi; bc_put_atom(s, rme->module_name); + if (JS_WriteObjectRec(s, rme->attributes)) + goto fail; } bc_put_leb128(s, m->export_entries_count); @@ -34529,10 +36883,13 @@ for(i = 0; i < m->import_entries_count; i++) { JSImportEntry *mi = &m->import_entriesi; bc_put_leb128(s, mi->var_idx); + bc_put_u8(s, mi->is_star); bc_put_atom(s, mi->import_name); bc_put_leb128(s, mi->req_module_idx); } + bc_put_u8(s, m->has_tla); + if (JS_WriteObjectRec(s, m->func_obj)) goto fail; return 0; @@ -34646,6 +37003,7 @@ } bc_put_u8(s, BC_TAG_ARRAY_BUFFER); bc_put_leb128(s, abuf->byte_length); + bc_put_leb128(s, abuf->max_byte_length); dbuf_put(&s->dbuf, abuf->data, abuf->byte_length); return 0; } @@ -34657,6 +37015,7 @@ assert(!abuf->detached); /* SharedArrayBuffer are never detached */ bc_put_u8(s, BC_TAG_SHARED_ARRAY_BUFFER); bc_put_leb128(s, abuf->byte_length); + bc_put_leb128(s, abuf->max_byte_length); bc_put_u64(s, (uintptr_t)abuf->data); if (js_resize_array(s->ctx, (void **)&s->sab_tab, sizeof(s->sab_tab0), &s->sab_tab_size, s->sab_tab_len + 1)) @@ -34705,6 +37064,16 @@ JS_WriteString(s, p); } break; + case JS_TAG_STRING_ROPE: + { + JSValue str; + str = JS_ToString(s->ctx, obj); + if (JS_IsException(str)) + goto fail; + JS_WriteObjectRec(s, str); + JS_FreeValue(s->ctx, str); + } + break; case JS_TAG_FUNCTION_BYTECODE: if (!s->allow_bytecode) goto invalid_tag; @@ -34761,11 +37130,7 @@ case JS_CLASS_NUMBER: case JS_CLASS_STRING: case JS_CLASS_BOOLEAN: -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: - case JS_CLASS_BIG_FLOAT: - case JS_CLASS_BIG_DECIMAL: -#endif bc_put_u8(s, BC_TAG_OBJECT_VALUE); ret = JS_WriteObjectRec(s, p->u.object_data); break; @@ -34784,14 +37149,11 @@ goto fail; } break; -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - case JS_TAG_BIG_DECIMAL: - if (JS_WriteBigNum(s, obj)) + if (JS_WriteBigInt(s, obj)) goto fail; break; -#endif default: invalid_tag: JS_ThrowInternalError(s->ctx, "unsupported tag (%d)", tag); @@ -34809,15 +37171,10 @@ JSRuntime *rt = s->ctx->rt; DynBuf dbuf1; int i, atoms_size; - uint8_t version; dbuf1 = s->dbuf; js_dbuf_init(s->ctx, &s->dbuf); - - version = BC_VERSION; - if (s->byte_swap) - version ^= BC_BE_VERSION; - bc_put_u8(s, version); + bc_put_u8(s, BC_VERSION); bc_put_leb128(s, s->idx_to_atom_count); for(i = 0; i < s->idx_to_atom_count; i++) { @@ -34830,7 +37187,7 @@ /* XXX: could just append dbuf1 data, but it uses more memory if dbuf1 is larger than dbuf */ atoms_size = s->dbuf.size; - if (dbuf_realloc(&dbuf1, dbuf1.size + atoms_size)) + if (dbuf_claim(&dbuf1, atoms_size)) goto fail; memmove(dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size); memcpy(dbuf1.buf, s->dbuf.buf, atoms_size); @@ -34850,8 +37207,6 @@ memset(s, 0, sizeof(*s)); s->ctx = ctx; - /* XXX: byte swapped output is untested */ - s->byte_swap = ((flags & JS_WRITE_OBJ_BSWAP) != 0); s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0); s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0); s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0); @@ -34972,33 +37327,45 @@ static int bc_get_u16(BCReaderState *s, uint16_t *pval) { + uint16_t v; if (unlikely(s->buf_end - s->ptr < 2)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } - *pval = get_u16(s->ptr); + v = get_u16(s->ptr); + if (is_be()) + v = bswap16(v); + *pval = v; s->ptr += 2; return 0; } static __maybe_unused int bc_get_u32(BCReaderState *s, uint32_t *pval) { + uint32_t v; if (unlikely(s->buf_end - s->ptr < 4)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } - *pval = get_u32(s->ptr); + v = get_u32(s->ptr); + if (is_be()) + v = bswap32(v); + *pval = v; s->ptr += 4; return 0; } static int bc_get_u64(BCReaderState *s, uint64_t *pval) { + uint64_t v; if (unlikely(s->buf_end - s->ptr < 8)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } - *pval = get_u64(s->ptr); + v = get_u64(s->ptr); + if (is_be()) + v = bswap64(v); + *pval = v; s->ptr += 8; return 0; } @@ -35097,6 +37464,10 @@ return NULL; is_wide_char = len & 1; len >>= 1; + if (len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(s->ctx, "string too long"); + return NULL; + } p = js_alloc_string(s->ctx, len, is_wide_char); if (!p) { s->error_state = -1; @@ -35110,7 +37481,13 @@ } memcpy(p->u.str8, s->ptr, size); s->ptr += size; - if (!is_wide_char) { + if (is_wide_char) { + if (is_be()) { + uint32_t i; + for (i = 0; i < len; i++) + p->u.str16i = bswap16(p->u.str16i); + } + } else { p->u.str8size = '\0'; /* add the trailing zero for 8 bit strings */ } #ifdef DUMP_READ_OBJECT @@ -35149,6 +37526,9 @@ } b->byte_code_buf = bc_buf; + if (is_be()) + bc_byte_swap(bc_buf, bc_len); + pos = 0; while (pos < bc_len) { op = bc_bufpos; @@ -35183,139 +37563,57 @@ return 0; } -#ifdef CONFIG_BIGNUM -static JSValue JS_ReadBigNum(BCReaderState *s, int tag) +static JSValue JS_ReadBigInt(BCReaderState *s) { JSValue obj = JS_UNDEFINED; + uint32_t len, i, n; + JSBigInt *p; + js_limb_t v; uint8_t v8; - int32_t e; - uint32_t len; - limb_t l, i, n, j; - JSBigFloat *p; - limb_t v; - bf_t *a; - int bpos, d; - p = js_new_bf(s->ctx); - if (!p) + if (bc_get_leb128(s, &len)) goto fail; - switch(tag) { - case BC_TAG_BIG_INT: - obj = JS_MKPTR(JS_TAG_BIG_INT, p); - break; - case BC_TAG_BIG_FLOAT: - obj = JS_MKPTR(JS_TAG_BIG_FLOAT, p); - break; - case BC_TAG_BIG_DECIMAL: - obj = JS_MKPTR(JS_TAG_BIG_DECIMAL, p); - break; - default: - abort(); + bc_read_trace(s, "len=%" PRId64 "\n", (int64_t)len); + if (len == 0) { + /* zero case */ + bc_read_trace(s, "}\n"); + return __JS_NewShortBigInt(s->ctx, 0); } - - /* sign + exponent */ - if (bc_get_sleb128(s, &e)) + p = js_bigint_new(s->ctx, (len - 1) / (JS_LIMB_BITS / 8) + 1); + if (!p) goto fail; - - a = &p->num; - a->sign = e & 1; - e >>= 1; - if (e == 0) - a->expn = BF_EXP_ZERO; - else if (e == 1) - a->expn = BF_EXP_INF; - else if (e == 2) - a->expn = BF_EXP_NAN; - else if (e >= 3) - a->expn = e - 3; - else - a->expn = e; - - /* mantissa */ - if (a->expn != BF_EXP_ZERO && - a->expn != BF_EXP_INF && - a->expn != BF_EXP_NAN) { - if (bc_get_leb128(s, &len)) - goto fail; - bc_read_trace(s, "len=%" PRId64 "\n", (int64_t)len); - if (len == 0) { - JS_ThrowInternalError(s->ctx, "invalid bignum length"); + for(i = 0; i < len / (JS_LIMB_BITS / 8); i++) { +#if JS_LIMB_BITS == 32 + if (bc_get_u32(s, &v)) goto fail; - } - if (tag != BC_TAG_BIG_DECIMAL) - l = (len + sizeof(limb_t) - 1) / sizeof(limb_t); - else - l = (len + LIMB_DIGITS - 1) / LIMB_DIGITS; - if (bf_resize(a, l)) { - JS_ThrowOutOfMemory(s->ctx); - goto fail; - } - if (tag != BC_TAG_BIG_DECIMAL) { - n = len & (sizeof(limb_t) - 1); - if (n != 0) { - v = 0; - for(i = 0; i < n; i++) { - if (bc_get_u8(s, &v8)) - goto fail; - v |= (limb_t)v8 << ((sizeof(limb_t) - n + i) * 8); - } - a->tab0 = v; - i = 1; - } else { - i = 0; - } - for(; i < l; i++) { -#if LIMB_BITS == 32 - if (bc_get_u32(s, &v)) - goto fail; -#ifdef WORDS_BIGENDIAN - v = bswap32(v); -#endif #else - if (bc_get_u64(s, &v)) - goto fail; -#ifdef WORDS_BIGENDIAN - v = bswap64(v); -#endif + if (bc_get_u64(s, &v)) + goto fail; #endif - a->tabi = v; - } - } else { - bpos = 0; - for(i = 0; i < l; i++) { - if (i == 0 && (n = len % LIMB_DIGITS) != 0) { - j = LIMB_DIGITS - n; - } else { - j = 0; - } - v = 0; - for(; j < LIMB_DIGITS; j++) { - if (bpos == 0) { - if (bc_get_u8(s, &v8)) - goto fail; - d = v8 & 0xf; - bpos = 1; - } else { - d = v8 >> 4; - bpos = 0; - } - if (d >= 10) { - JS_ThrowInternalError(s->ctx, "invalid digit"); - goto fail; - } - v += mp_pow_decj * d; - } - a->tabi = v; - } + p->tabi = v; + } + n = len % (JS_LIMB_BITS / 8); + if (n != 0) { + int shift; + v = 0; + for(i = 0; i < n; i++) { + if (bc_get_u8(s, &v8)) + goto fail; + v |= (js_limb_t)v8 << (i * 8); + } + shift = JS_LIMB_BITS - n * 8; + /* extend the sign */ + if (shift != 0) { + v = (js_slimb_t)(v << shift) >> shift; } + p->tabp->len - 1 = v; } bc_read_trace(s, "}\n"); - return obj; + return JS_CompactBigInt(s->ctx, p); fail: JS_FreeValue(s->ctx, obj); return JS_EXCEPTION; } -#endif /* CONFIG_BIGNUM */ static JSValue JS_ReadObjectRec(BCReaderState *s); @@ -35364,7 +37662,7 @@ bc.super_allowed = bc_get_flags(v16, &idx, 1); bc.arguments_allowed = bc_get_flags(v16, &idx, 1); bc.has_debug = bc_get_flags(v16, &idx, 1); - bc.backtrace_barrier = bc_get_flags(v16, &idx, 1); + bc.is_direct_or_indirect_eval = bc_get_flags(v16, &idx, 1); bc.read_only_bytecode = s->is_rom_data; if (bc_get_u8(s, &v8)) goto fail; @@ -35492,8 +37790,9 @@ bc_read_trace(s, "debug {\n"); if (bc_get_atom(s, &b->debug.filename)) goto fail; - if (bc_get_leb128_int(s, &b->debug.line_num)) - goto fail; +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); +#endif if (bc_get_leb128_int(s, &b->debug.pc2line_len)) goto fail; if (b->debug.pc2line_len) { @@ -35503,9 +37802,16 @@ if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len)) goto fail; } -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); -#endif + if (bc_get_leb128_int(s, &b->debug.source_len)) + goto fail; + if (b->debug.source_len) { + bc_read_trace(s, "source: %d bytes\n", b->source_len); + b->debug.source = js_mallocz(ctx, b->debug.source_len); + if (!b->debug.source) + goto fail; + if (bc_get_buf(s, (uint8_t *)b->debug.source, b->debug.source_len)) + goto fail; + } bc_read_trace(s, "}\n"); } if (b->cpool_count != 0) { @@ -35543,7 +37849,7 @@ m = js_new_module_def(ctx, module_name); if (!m) goto fail; - obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); + obj = JS_NewModuleValue(ctx, m); if (bc_get_leb128_int(s, &m->req_module_entries_count)) goto fail; if (m->req_module_entries_count != 0) { @@ -35553,8 +37859,13 @@ goto fail; for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entriesi; + JSValue val; if (bc_get_atom(s, &rme->module_name)) goto fail; + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + rme->attributes = val; } } @@ -35607,8 +37918,12 @@ goto fail; for(i = 0; i < m->import_entries_count; i++) { JSImportEntry *mi = &m->import_entriesi; + uint8_t v8; if (bc_get_leb128_int(s, &mi->var_idx)) goto fail; + if (bc_get_u8(s, &v8)) + goto fail; + mi->is_star = (v8 != 0); if (bc_get_atom(s, &mi->import_name)) goto fail; if (bc_get_leb128_int(s, &mi->req_module_idx)) @@ -35616,13 +37931,17 @@ } } + if (bc_get_u8(s, &v8)) + goto fail; + m->has_tla = (v8 != 0); + m->func_obj = JS_ReadObjectRec(s); if (JS_IsException(m->func_obj)) goto fail; return obj; fail: if (m) { - js_free_module_def(ctx, m); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); } return JS_EXCEPTION; } @@ -35758,16 +38077,31 @@ static JSValue JS_ReadArrayBuffer(BCReaderState *s) { JSContext *ctx = s->ctx; - uint32_t byte_length; + uint32_t byte_length, max_byte_length; + uint64_t max_byte_length_u64, *pmax_byte_length = NULL; JSValue obj; if (bc_get_leb128(s, &byte_length)) return JS_EXCEPTION; + if (bc_get_leb128(s, &max_byte_length)) + return JS_EXCEPTION; + if (max_byte_length < byte_length) + return JS_ThrowTypeError(ctx, "invalid array buffer"); + if (max_byte_length != UINT32_MAX) { + max_byte_length_u64 = max_byte_length; + pmax_byte_length = &max_byte_length_u64; + } if (unlikely(s->buf_end - s->ptr < byte_length)) { bc_read_error_end(s); return JS_EXCEPTION; } - obj = JS_NewArrayBufferCopy(ctx, s->ptr, byte_length); + // makes a copy of the input + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, + byte_length, pmax_byte_length, + JS_CLASS_ARRAY_BUFFER, + (uint8_t*)s->ptr, + js_array_buffer_free, NULL, + /*alloc_flag*/TRUE); if (JS_IsException(obj)) goto fail; if (BC_add_object_ref(s, obj)) @@ -35782,18 +38116,28 @@ static JSValue JS_ReadSharedArrayBuffer(BCReaderState *s) { JSContext *ctx = s->ctx; - uint32_t byte_length; + uint32_t byte_length, max_byte_length; + uint64_t max_byte_length_u64, *pmax_byte_length = NULL; uint8_t *data_ptr; JSValue obj; uint64_t u64; if (bc_get_leb128(s, &byte_length)) return JS_EXCEPTION; + if (bc_get_leb128(s, &max_byte_length)) + return JS_EXCEPTION; + if (max_byte_length < byte_length) + return JS_ThrowTypeError(ctx, "invalid array buffer"); + if (max_byte_length != UINT32_MAX) { + max_byte_length_u64 = max_byte_length; + pmax_byte_length = &max_byte_length_u64; + } if (bc_get_u64(s, &u64)) return JS_EXCEPTION; data_ptr = (uint8_t *)(uintptr_t)u64; /* the SharedArrayBuffer is cloned */ - obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, byte_length, + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, + byte_length, pmax_byte_length, JS_CLASS_SHARED_ARRAY_BUFFER, data_ptr, NULL, NULL, FALSE); @@ -35940,13 +38284,9 @@ case BC_TAG_OBJECT_VALUE: obj = JS_ReadObjectValue(s); break; -#ifdef CONFIG_BIGNUM case BC_TAG_BIG_INT: - case BC_TAG_BIG_FLOAT: - case BC_TAG_BIG_DECIMAL: - obj = JS_ReadBigNum(s, tag); + obj = JS_ReadBigInt(s); break; -#endif case BC_TAG_OBJECT_REFERENCE: { uint32_t val; @@ -35980,7 +38320,6 @@ if (bc_get_u8(s, &v8)) return -1; - /* XXX: could support byte swapped input */ if (v8 != BC_VERSION) { JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)", v8, BC_VERSION); @@ -36102,11 +38441,25 @@ return atom; } +static JSValue JS_NewObjectProtoList(JSContext *ctx, JSValueConst proto, + const JSCFunctionListEntry *fields, int n_fields) +{ + JSValue obj; + obj = JS_NewObjectProtoClassAlloc(ctx, proto, JS_CLASS_OBJECT, n_fields); + if (JS_IsException(obj)) + return obj; + if (JS_SetPropertyFunctionList(ctx, obj, fields, n_fields)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) { const JSCFunctionListEntry *e = opaque; - JSValue val; + JSValue val, proto; switch(e->def_type) { case JS_DEF_CFUNC: @@ -36117,8 +38470,13 @@ val = JS_NewAtomString(ctx, e->u.str); break; case JS_DEF_OBJECT: - val = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len); + /* XXX: could add a flag */ + if (atom == JS_ATOM_Symbol_unscopables) + proto = JS_NULL; + else + proto = ctx->class_protoJS_CLASS_OBJECT; + val = JS_NewObjectProtoList(ctx, proto, + e->u.prop_list.tab, e->u.prop_list.len); break; default: abort(); @@ -36207,6 +38565,12 @@ case JS_DEF_PROP_UNDEFINED: val = JS_UNDEFINED; break; + case JS_DEF_PROP_ATOM: + val = JS_AtomToValue(ctx, e->u.i32); + break; + case JS_DEF_PROP_BOOL: + val = JS_NewBool(ctx, e->u.i32); + break; case JS_DEF_PROP_STRING: case JS_DEF_OBJECT: JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP, @@ -36219,17 +38583,22 @@ return 0; } -void JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj, - const JSCFunctionListEntry *tab, int len) +int JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj, + const JSCFunctionListEntry *tab, int len) { - int i; + int i, ret; for (i = 0; i < len; i++) { const JSCFunctionListEntry *e = &tabi; JSAtom atom = find_atom(ctx, e->name); - JS_InstantiateFunctionListItem(ctx, obj, atom, e); + if (atom == JS_ATOM_NULL) + return -1; + ret = JS_InstantiateFunctionListItem(ctx, obj, atom, e); JS_FreeAtom(ctx, atom); + if (ret) + return -1; } + return 0; } int JS_AddModuleExportList(JSContext *ctx, JSModuleDef *m, @@ -36269,8 +38638,8 @@ val = __JS_NewFloat64(ctx, e->u.f64); break; case JS_DEF_OBJECT: - val = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len); + val = JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_OBJECT, + e->u.prop_list.tab, e->u.prop_list.len); break; default: abort(); @@ -36282,57 +38651,107 @@ } /* Note: 'func_obj' is not necessarily a constructor */ -static void JS_SetConstructor2(JSContext *ctx, - JSValueConst func_obj, - JSValueConst proto, - int proto_flags, int ctor_flags) -{ - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, - JS_DupValue(ctx, proto), proto_flags); - JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, - JS_DupValue(ctx, func_obj), - ctor_flags); +static int JS_SetConstructor2(JSContext *ctx, + JSValueConst func_obj, + JSValueConst proto, + int proto_flags, int ctor_flags) +{ + if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, + JS_DupValue(ctx, proto), proto_flags) < 0) + return -1; + if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + JS_DupValue(ctx, func_obj), + ctor_flags) < 0) + return -1; set_cycle_flag(ctx, func_obj); set_cycle_flag(ctx, proto); + return 0; } -void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, - JSValueConst proto) +/* return 0 if OK, -1 if exception */ +int JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, + JSValueConst proto) { - JS_SetConstructor2(ctx, func_obj, proto, - 0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + return JS_SetConstructor2(ctx, func_obj, proto, + 0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } -static void JS_NewGlobalCConstructor2(JSContext *ctx, - JSValue func_obj, - const char *name, - JSValueConst proto) -{ - JS_DefinePropertyValueStr(ctx, ctx->global_obj, name, - JS_DupValue(ctx, func_obj), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_SetConstructor(ctx, func_obj, proto); - JS_FreeValue(ctx, func_obj); -} +#define JS_NEW_CTOR_NO_GLOBAL (1 << 0) /* don't create a global binding */ +#define JS_NEW_CTOR_PROTO_CLASS (1 << 1) /* the prototype class is 'class_id' instead of JS_CLASS_OBJECT */ +#define JS_NEW_CTOR_PROTO_EXIST (1 << 2) /* the prototype is already defined */ +#define JS_NEW_CTOR_READONLY (1 << 3) /* read-only constructor field */ -static JSValueConst JS_NewGlobalCConstructor(JSContext *ctx, const char *name, - JSCFunction *func, int length, - JSValueConst proto) +/* Return the constructor and. Define it as a global variable unless + JS_NEW_CTOR_NO_GLOBAL is set. The new class inherit from + parent_ctor if it is not JS_UNDEFINED. if class_id is != -1, + class_protoclass_id is set. */ +static JSValue JS_NewCConstructor(JSContext *ctx, int class_id, const char *name, + JSCFunction *func, int length, JSCFunctionEnum cproto, int magic, + JSValueConst parent_ctor, + const JSCFunctionListEntry *ctor_fields, int n_ctor_fields, + const JSCFunctionListEntry *proto_fields, int n_proto_fields, + int flags) { - JSValue func_obj; - func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor_or_func, 0); - JS_NewGlobalCConstructor2(ctx, func_obj, name, proto); - return func_obj; -} + JSValue ctor = JS_UNDEFINED, proto, parent_proto; + int proto_class_id, proto_flags, ctor_flags; -static JSValueConst JS_NewGlobalCConstructorOnly(JSContext *ctx, const char *name, - JSCFunction *func, int length, - JSValueConst proto) -{ - JSValue func_obj; - func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor, 0); - JS_NewGlobalCConstructor2(ctx, func_obj, name, proto); - return func_obj; + proto_flags = 0; + if (flags & JS_NEW_CTOR_READONLY) { + ctor_flags = JS_PROP_CONFIGURABLE; + } else { + ctor_flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE; + } + + if (JS_IsUndefined(parent_ctor)) { + parent_proto = JS_DupValue(ctx, ctx->class_protoJS_CLASS_OBJECT); + parent_ctor = ctx->function_proto; + } else { + parent_proto = JS_GetProperty(ctx, parent_ctor, JS_ATOM_prototype); + if (JS_IsException(parent_proto)) + return JS_EXCEPTION; + } + + if (flags & JS_NEW_CTOR_PROTO_EXIST) { + proto = JS_DupValue(ctx, ctx->class_protoclass_id); + } else { + if (flags & JS_NEW_CTOR_PROTO_CLASS) + proto_class_id = class_id; + else + proto_class_id = JS_CLASS_OBJECT; + /* one additional field: constructor */ + proto = JS_NewObjectProtoClassAlloc(ctx, parent_proto, proto_class_id, + n_proto_fields + 1); + if (JS_IsException(proto)) + goto fail; + if (class_id >= 0) + ctx->class_protoclass_id = JS_DupValue(ctx, proto); + } + if (JS_SetPropertyFunctionList(ctx, proto, proto_fields, n_proto_fields)) + goto fail; + + /* additional fields: name, length, prototype */ + ctor = JS_NewCFunction3(ctx, func, name, length, cproto, magic, parent_ctor, + n_ctor_fields + 3); + if (JS_IsException(ctor)) + goto fail; + if (JS_SetPropertyFunctionList(ctx, ctor, ctor_fields, n_ctor_fields)) + goto fail; + if (!(flags & JS_NEW_CTOR_NO_GLOBAL)) { + if (JS_DefinePropertyValueStr(ctx, ctx->global_obj, name, + JS_DupValue(ctx, ctor), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0) + goto fail; + } + JS_SetConstructor2(ctx, ctor, proto, proto_flags, ctor_flags); + + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, parent_proto); + return ctor; + fail: + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, ctor); + return JS_EXCEPTION; } static JSValue js_global_eval(JSContext *ctx, JSValueConst this_val, @@ -36346,7 +38765,6 @@ { double d; - /* XXX: does this work for bigfloat? */ if (unlikely(JS_ToFloat64(ctx, &d, argv0))) return JS_EXCEPTION; return JS_NewBool(ctx, isnan(d)); @@ -36355,12 +38773,10 @@ static JSValue js_global_isFinite(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - BOOL res; double d; if (unlikely(JS_ToFloat64(ctx, &d, argv0))) return JS_EXCEPTION; - res = isfinite(d); - return JS_NewBool(ctx, res); + return JS_NewBool(ctx, isfinite(d)); } /* Object class */ @@ -36378,29 +38794,31 @@ case JS_TAG_OBJECT: case JS_TAG_EXCEPTION: return JS_DupValue(ctx, val); -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_INT); goto set_value; - case JS_TAG_BIG_FLOAT: - obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_FLOAT); - goto set_value; - case JS_TAG_BIG_DECIMAL: - obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_DECIMAL); - goto set_value; -#endif case JS_TAG_INT: case JS_TAG_FLOAT64: obj = JS_NewObjectClass(ctx, JS_CLASS_NUMBER); goto set_value; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: /* XXX: should call the string constructor */ { - JSString *p1 = JS_VALUE_GET_STRING(val); + JSValue str; + str = JS_ToString(ctx, val); /* ensure that we never store a rope */ + if (JS_IsException(str)) + return JS_EXCEPTION; obj = JS_NewObjectClass(ctx, JS_CLASS_STRING); - JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, JS_NewInt32(ctx, p1->len), 0); + if (!JS_IsException(obj)) { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, + JS_NewInt32(ctx, JS_VALUE_GET_STRING(str)->len), 0); + JS_SetObjectData(ctx, obj, JS_DupValue(ctx, str)); + } + JS_FreeValue(ctx, str); + return obj; } - goto set_value; case JS_TAG_BOOL: obj = JS_NewObjectClass(ctx, JS_CLASS_BOOLEAN); goto set_value; @@ -36434,6 +38852,14 @@ val = JS_UNDEFINED; getter = JS_UNDEFINED; setter = JS_UNDEFINED; + if (JS_HasProperty(ctx, desc, JS_ATOM_enumerable)) { + JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_enumerable); + if (JS_IsException(prop)) + goto fail; + flags |= JS_PROP_HAS_ENUMERABLE; + if (JS_ToBoolFree(ctx, prop)) + flags |= JS_PROP_ENUMERABLE; + } if (JS_HasProperty(ctx, desc, JS_ATOM_configurable)) { JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_configurable); if (JS_IsException(prop)) @@ -36442,6 +38868,12 @@ if (JS_ToBoolFree(ctx, prop)) flags |= JS_PROP_CONFIGURABLE; } + if (JS_HasProperty(ctx, desc, JS_ATOM_value)) { + flags |= JS_PROP_HAS_VALUE; + val = JS_GetProperty(ctx, desc, JS_ATOM_value); + if (JS_IsException(val)) + goto fail; + } if (JS_HasProperty(ctx, desc, JS_ATOM_writable)) { JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_writable); if (JS_IsException(prop)) @@ -36450,20 +38882,6 @@ if (JS_ToBoolFree(ctx, prop)) flags |= JS_PROP_WRITABLE; } - if (JS_HasProperty(ctx, desc, JS_ATOM_enumerable)) { - JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_enumerable); - if (JS_IsException(prop)) - goto fail; - flags |= JS_PROP_HAS_ENUMERABLE; - if (JS_ToBoolFree(ctx, prop)) - flags |= JS_PROP_ENUMERABLE; - } - if (JS_HasProperty(ctx, desc, JS_ATOM_value)) { - flags |= JS_PROP_HAS_VALUE; - val = JS_GetProperty(ctx, desc, JS_ATOM_value); - if (JS_IsException(val)) - goto fail; - } if (JS_HasProperty(ctx, desc, JS_ATOM_get)) { flags |= JS_PROP_HAS_GET; getter = JS_GetProperty(ctx, desc, JS_ATOM_get); @@ -36534,6 +38952,7 @@ if (JS_IsException(props)) return -1; p = JS_VALUE_GET_OBJ(props); + /* XXX: not done in the same order as the spec */ if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK) < 0) goto exception; for(i = 0; i < len; i++) { @@ -36547,7 +38966,7 @@ ret = 0; exception: - js_free_prop_enum(ctx, atoms, len); + JS_FreePropertyEnum(ctx, atoms, len); JS_FreeValue(ctx, props); JS_FreeValue(ctx, desc); return ret; @@ -36754,13 +39173,13 @@ } else { if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, JS_DupValue(ctx, desc.value), flags) < 0 || JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, - JS_NewBool(ctx, (desc.flags & JS_PROP_WRITABLE) != 0), flags) < 0) + JS_NewBool(ctx, desc.flags & JS_PROP_WRITABLE), flags) < 0) goto exception1; } if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, - JS_NewBool(ctx, (desc.flags & JS_PROP_ENUMERABLE) != 0), flags) < 0 + JS_NewBool(ctx, desc.flags & JS_PROP_ENUMERABLE), flags) < 0 || JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, - JS_NewBool(ctx, (desc.flags & JS_PROP_CONFIGURABLE) != 0), flags) < 0) + JS_NewBool(ctx, desc.flags & JS_PROP_CONFIGURABLE), flags) < 0) goto exception1; js_free_desc(ctx, &desc); } @@ -36818,12 +39237,12 @@ goto exception; } } - js_free_prop_enum(ctx, props, len); + JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, obj); return r; exception: - js_free_prop_enum(ctx, props, len); + JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, obj); JS_FreeValue(ctx, r); return JS_EXCEPTION; @@ -36903,7 +39322,7 @@ JS_FreeValue(ctx, r); r = JS_EXCEPTION; done: - js_free_prop_enum(ctx, atoms, len); + JS_FreePropertyEnum(ctx, atoms, len); JS_FreeValue(ctx, obj); return r; } @@ -37000,6 +39419,32 @@ return JS_NewBool(ctx, ret); } +static JSValue js_object_hasOwn(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue obj; + JSAtom atom; + JSObject *p; + BOOL ret; + + obj = JS_ToObject(ctx, argv0); + if (JS_IsException(obj)) + return obj; + atom = JS_ValueToAtom(ctx, argv1); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + p = JS_VALUE_GET_OBJ(obj); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, obj); + if (ret < 0) + return JS_EXCEPTION; + else + return JS_NewBool(ctx, ret); +} + static JSValue js_object_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -37015,9 +39460,9 @@ JSObject *p; if (JS_IsNull(this_val)) { - tag = JS_NewString(ctx, "Null"); + tag = js_new_string8(ctx, "Null"); } else if (JS_IsUndefined(this_val)) { - tag = JS_NewString(ctx, "Undefined"); + tag = js_new_string8(ctx, "Undefined"); } else { obj = JS_ToObject(ctx, this_val); if (JS_IsException(obj)) @@ -37138,11 +39583,11 @@ JS_UNDEFINED, JS_UNDEFINED, desc_flags) < 0) goto exception; } - js_free_prop_enum(ctx, props, len); + JS_FreePropertyEnum(ctx, props, len); return JS_DupValue(ctx, obj); exception: - js_free_prop_enum(ctx, props, len); + JS_FreePropertyEnum(ctx, props, len); return JS_EXCEPTION; } @@ -37184,11 +39629,11 @@ return JS_EXCEPTION; res ^= 1; done: - js_free_prop_enum(ctx, props, len); + JS_FreePropertyEnum(ctx, props, len); return JS_NewBool(ctx, res); exception: - js_free_prop_enum(ctx, props, len); + JS_FreePropertyEnum(ctx, props, len); return JS_EXCEPTION; } @@ -37219,10 +39664,8 @@ item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(item)) goto fail; - if (done) { - JS_FreeValue(ctx, item); + if (done) break; - } key = JS_UNDEFINED; value = JS_UNDEFINED; @@ -37260,107 +39703,12 @@ return JS_EXCEPTION; } -#if 0 -/* Note: corresponds to ECMA spec: CreateDataPropertyOrThrow() */ -static JSValue js_object___setOwnProperty(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int ret; - ret = JS_DefinePropertyValueValue(ctx, argv0, JS_DupValue(ctx, argv1), - JS_DupValue(ctx, argv2), - JS_PROP_C_W_E | JS_PROP_THROW); - if (ret < 0) - return JS_EXCEPTION; - else - return JS_NewBool(ctx, ret); -} - -static JSValue js_object___toObject(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_ToObject(ctx, argv0); -} - -static JSValue js_object___toPrimitive(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int hint = HINT_NONE; - - if (JS_VALUE_GET_TAG(argv1) == JS_TAG_INT) - hint = JS_VALUE_GET_INT(argv1); - - return JS_ToPrimitive(ctx, argv0, hint); -} -#endif - -/* return an empty string if not an object */ -static JSValue js_object___getClass(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSAtom atom; - JSObject *p; - uint32_t tag; - int class_id; - - tag = JS_VALUE_GET_NORM_TAG(argv0); - if (tag == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(argv0); - class_id = p->class_id; - if (class_id == JS_CLASS_PROXY && JS_IsFunction(ctx, argv0)) - class_id = JS_CLASS_BYTECODE_FUNCTION; - atom = ctx->rt->class_arrayclass_id.class_name; - } else { - atom = JS_ATOM_empty_string; - } - return JS_AtomToString(ctx, atom); -} - static JSValue js_object_is(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_NewBool(ctx, js_same_value(ctx, argv0, argv1)); } -#if 0 -static JSValue js_object___getObjectData(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_GetObjectData(ctx, argv0); -} - -static JSValue js_object___setObjectData(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - if (JS_SetObjectData(ctx, argv0, JS_DupValue(ctx, argv1))) - return JS_EXCEPTION; - return JS_DupValue(ctx, argv1); -} - -static JSValue js_object___toPropertyKey(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_ToPropertyKey(ctx, argv0); -} - -static JSValue js_object___isObject(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_NewBool(ctx, JS_IsObject(argv0)); -} - -static JSValue js_object___isSameValueZero(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_NewBool(ctx, js_same_value_zero(ctx, argv0, argv1)); -} - -static JSValue js_object___isConstructor(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_NewBool(ctx, JS_IsConstructor(ctx, argv0)); -} -#endif - static JSValue JS_SpeciesConstructor(JSContext *ctx, JSValueConst obj, JSValueConst defaultConstructor) { @@ -37390,14 +39738,6 @@ return species; } -#if 0 -static JSValue js_object___speciesConstructor(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_SpeciesConstructor(ctx, argv0, argv1); -} -#endif - static JSValue js_object_get___proto__(JSContext *ctx, JSValueConst this_val) { JSValue val, ret; @@ -37466,23 +39806,23 @@ static JSValue js_object_propertyIsEnumerable(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSValue obj, res = JS_EXCEPTION; - JSAtom prop = JS_ATOM_NULL; + JSValue obj = JS_UNDEFINED, res = JS_EXCEPTION; + JSAtom prop; JSPropertyDescriptor desc; int has_prop; - obj = JS_ToObject(ctx, this_val); - if (JS_IsException(obj)) - goto exception; prop = JS_ValueToAtom(ctx, argv0); if (unlikely(prop == JS_ATOM_NULL)) goto exception; + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + goto exception; has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop); if (has_prop < 0) goto exception; if (has_prop) { - res = JS_NewBool(ctx, (desc.flags & JS_PROP_ENUMERABLE) != 0); + res = JS_NewBool(ctx, desc.flags & JS_PROP_ENUMERABLE); js_free_desc(ctx, &desc); } else { res = JS_FALSE; @@ -37547,6 +39887,7 @@ JS_CFUNC_DEF("defineProperties", 2, js_object_defineProperties ), JS_CFUNC_DEF("getOwnPropertyNames", 1, js_object_getOwnPropertyNames ), JS_CFUNC_DEF("getOwnPropertySymbols", 1, js_object_getOwnPropertySymbols ), + JS_CFUNC_MAGIC_DEF("groupBy", 2, js_object_groupBy, 0 ), JS_CFUNC_MAGIC_DEF("keys", 1, js_object_keys, JS_ITERATOR_KIND_KEY ), JS_CFUNC_MAGIC_DEF("values", 1, js_object_keys, JS_ITERATOR_KIND_VALUE ), JS_CFUNC_MAGIC_DEF("entries", 1, js_object_keys, JS_ITERATOR_KIND_KEY_AND_VALUE ), @@ -37560,18 +39901,8 @@ JS_CFUNC_MAGIC_DEF("freeze", 1, js_object_seal, 1 ), JS_CFUNC_MAGIC_DEF("isSealed", 1, js_object_isSealed, 0 ), JS_CFUNC_MAGIC_DEF("isFrozen", 1, js_object_isSealed, 1 ), - JS_CFUNC_DEF("__getClass", 1, js_object___getClass ), - //JS_CFUNC_DEF("__isObject", 1, js_object___isObject ), - //JS_CFUNC_DEF("__isConstructor", 1, js_object___isConstructor ), - //JS_CFUNC_DEF("__toObject", 1, js_object___toObject ), - //JS_CFUNC_DEF("__setOwnProperty", 3, js_object___setOwnProperty ), - //JS_CFUNC_DEF("__toPrimitive", 2, js_object___toPrimitive ), - //JS_CFUNC_DEF("__toPropertyKey", 1, js_object___toPropertyKey ), - //JS_CFUNC_DEF("__speciesConstructor", 2, js_object___speciesConstructor ), - //JS_CFUNC_DEF("__isSameValueZero", 2, js_object___isSameValueZero ), - //JS_CFUNC_DEF("__getObjectData", 1, js_object___getObjectData ), - //JS_CFUNC_DEF("__setObjectData", 2, js_object___setObjectData ), JS_CFUNC_DEF("fromEntries", 1, js_object_fromEntries ), + JS_CFUNC_DEF("hasOwn", 2, js_object_hasOwn ), }; static const JSCFunctionListEntry js_object_proto_funcs = { @@ -37705,6 +40036,7 @@ JSValueConst array_arg) { uint32_t len, i; + int64_t len64; JSValue *tab, ret; JSObject *p; @@ -37712,12 +40044,15 @@ JS_ThrowTypeError(ctx, "not a object"); return NULL; } - if (js_get_length32(ctx, &len, array_arg)) + if (js_get_length64(ctx, &len64, array_arg)) return NULL; - if (len > JS_MAX_LOCAL_VARS) { - JS_ThrowInternalError(ctx, "too many arguments"); + if (len64 > JS_MAX_LOCAL_VARS) { + // XXX: check for stack overflow? + JS_ThrowRangeError(ctx, "too many arguments in function call (only %d allowed)", + JS_MAX_LOCAL_VARS); return NULL; } + len = len64; /* avoid allocating 0 bytes */ tab = js_mallocz(ctx, sizeof(tab0) * max_uint32(1, len)); if (!tab) @@ -37929,7 +40264,8 @@ JS_CFUNC_DEF("toString", 0, js_function_toString ), JS_CFUNC_DEF("Symbol.hasInstance", 1, js_function_hasInstance ), JS_CGETSET_DEF("fileName", js_function_proto_fileName, NULL ), - JS_CGETSET_DEF("lineNumber", js_function_proto_lineNumber, NULL ), + JS_CGETSET_MAGIC_DEF("lineNumber", js_function_proto_lineNumber, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("columnNumber", js_function_proto_lineNumber, NULL, 1 ), }; /* Error class */ @@ -37976,7 +40312,8 @@ int argc, JSValueConst *argv, int magic) { JSValue obj, msg, proto; - JSValueConst message; + JSValueConst message, options; + int arg_index; if (JS_IsUndefined(new_target)) new_target = JS_GetActiveFunction(ctx); @@ -38002,12 +40339,9 @@ JS_FreeValue(ctx, proto); if (JS_IsException(obj)) return obj; - if (magic == JS_AGGREGATE_ERROR) { - message = argv1; - } else { - message = argv0; - } + arg_index = (magic == JS_AGGREGATE_ERROR); + message = argvarg_index++; if (!JS_IsUndefined(message)) { msg = JS_ToString(ctx, message); if (unlikely(JS_IsException(msg))) @@ -38016,6 +40350,22 @@ JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } + if (arg_index < argc) { + options = argvarg_index; + if (JS_IsObject(options)) { + int present = JS_HasProperty(ctx, options, JS_ATOM_cause); + if (present < 0) + goto exception; + if (present) { + JSValue cause = JS_GetProperty(ctx, options, JS_ATOM_cause); + if (JS_IsException(cause)) + goto exception; + JS_DefinePropertyValue(ctx, obj, JS_ATOM_cause, cause, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + } + } + if (magic == JS_AGGREGATE_ERROR) { JSValue error_list = iterator_to_array(ctx, argv0); if (JS_IsException(error_list)) @@ -38025,7 +40375,7 @@ } /* skip the Error() function in the backtrace */ - build_backtrace(ctx, obj, NULL, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); + build_backtrace(ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); return obj; exception: JS_FreeValue(ctx, obj); @@ -38067,6 +40417,35 @@ JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; +/* 2 entries for each native error class */ +/* Note: we use an atom to avoid the autoinit definition which does + not work in get_prop_string() */ +static const JSCFunctionListEntry js_native_error_proto_funcs = { +#define DEF(name) \ + JS_PROP_ATOM_DEF("name", name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),\ + JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), + + DEF(JS_ATOM_EvalError) + DEF(JS_ATOM_RangeError) + DEF(JS_ATOM_ReferenceError) + DEF(JS_ATOM_SyntaxError) + DEF(JS_ATOM_TypeError) + DEF(JS_ATOM_URIError) + DEF(JS_ATOM_InternalError) + DEF(JS_ATOM_AggregateError) +#undef DEF +}; + +static JSValue js_error_isError(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewBool(ctx, JS_IsError(ctx, argv0)); +} + +static const JSCFunctionListEntry js_error_funcs = { + JS_CFUNC_DEF("isError", 1, js_error_isError), +}; + /* AggregateError */ /* used by C code. */ @@ -38091,12 +40470,20 @@ JSValueConst obj, int64_t to_pos, int64_t from_pos, int64_t count, int dir) { - int64_t i, from, to; + JSObject *p; + int64_t i, from, to, len; JSValue val; int fromPresent; - /* XXX: should special case fast arrays */ - for (i = 0; i < count; i++) { + p = NULL; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ARRAY || !p->fast_array) { + p = NULL; + } + } + + for (i = 0; i < count; ) { if (dir < 0) { from = from_pos + count - i - 1; to = to_pos + count - i - 1; @@ -38104,16 +40491,43 @@ from = from_pos + i; to = to_pos + i; } - fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val); - if (fromPresent < 0) - goto exception; - - if (fromPresent) { - if (JS_SetPropertyInt64(ctx, obj, to, val) < 0) - goto exception; + if (p && p->fast_array && + from >= 0 && from < (len = p->u.array.count) && + to >= 0 && to < len) { + int64_t l, j; + /* Fast path for fast arrays. Since we don't look at the + prototype chain, we can optimize only the cases where + all the elements are present in the array. */ + l = count - i; + if (dir < 0) { + l = min_int64(l, from + 1); + l = min_int64(l, to + 1); + for(j = 0; j < l; j++) { + set_value(ctx, &p->u.array.u.valuesto - j, + JS_DupValue(ctx, p->u.array.u.valuesfrom - j)); + } + } else { + l = min_int64(l, len - from); + l = min_int64(l, len - to); + for(j = 0; j < l; j++) { + set_value(ctx, &p->u.array.u.valuesto + j, + JS_DupValue(ctx, p->u.array.u.valuesfrom + j)); + } + } + i += l; } else { - if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0) + fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val); + if (fromPresent < 0) goto exception; + + if (fromPresent) { + if (JS_SetPropertyInt64(ctx, obj, to, val) < 0) + goto exception; + } else { + if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0) + goto exception; + } + i++; } } return 0; @@ -38155,8 +40569,7 @@ // from(items, mapfn = void 0, this_arg = void 0) JSValueConst items = argv0, mapfn, this_arg; JSValueConst args2; - JSValue stack2; - JSValue iter, r, v, v2, arrayLike; + JSValue iter, r, v, v2, arrayLike, next_method, enum_obj; int64_t k, len; int done, mapping; @@ -38165,8 +40578,9 @@ this_arg = JS_UNDEFINED; r = JS_UNDEFINED; arrayLike = JS_UNDEFINED; - stack0 = JS_UNDEFINED; - stack1 = JS_UNDEFINED; + iter = JS_UNDEFINED; + enum_obj = JS_UNDEFINED; + next_method = JS_UNDEFINED; if (argc > 1) { mapfn = argv1; @@ -38181,21 +40595,27 @@ iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); if (JS_IsException(iter)) goto exception; - if (!JS_IsUndefined(iter)) { - JS_FreeValue(ctx, iter); + if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) { + if (!JS_IsFunction(ctx, iter)) { + JS_ThrowTypeError(ctx, "value is not iterable"); + goto exception; + } if (JS_IsConstructor(ctx, this_val)) r = JS_CallConstructor(ctx, this_val, 0, NULL); else r = JS_NewArray(ctx); if (JS_IsException(r)) goto exception; - stack0 = JS_DupValue(ctx, items); - if (js_for_of_start(ctx, &stack1, FALSE)) + enum_obj = JS_GetIterator2(ctx, items, iter); + if (JS_IsException(enum_obj)) + goto exception; + next_method = JS_GetProperty(ctx, enum_obj, JS_ATOM_next); + if (JS_IsException(next_method)) goto exception; for (k = 0;; k++) { - v = JS_IteratorNext(ctx, stack0, stack1, 0, NULL, &done); + v = JS_IteratorNext(ctx, enum_obj, next_method, 0, NULL, &done); if (JS_IsException(v)) - goto exception_close; + goto exception; if (done) break; if (mapping) { @@ -38250,15 +40670,15 @@ goto done; exception_close: - if (!JS_IsUndefined(stack0)) - JS_IteratorClose(ctx, stack0, TRUE); + JS_IteratorClose(ctx, enum_obj, TRUE); exception: JS_FreeValue(ctx, r); r = JS_EXCEPTION; done: JS_FreeValue(ctx, arrayLike); - JS_FreeValue(ctx, stack0); - JS_FreeValue(ctx, stack1); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, enum_obj); + JS_FreeValue(ctx, next_method); return r; } @@ -38374,6 +40794,106 @@ return JS_IsArray(ctx, obj); } +static JSValue js_array_at(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue obj, ret; + int64_t len, idx; + JSValue *arrp; + uint32_t count; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (JS_ToInt64Sat(ctx, &idx, argv0)) + goto exception; + + if (idx < 0) + idx = len + idx; + if (idx < 0 || idx >= len) { + ret = JS_UNDEFINED; + } else if (js_get_fast_array(ctx, obj, &arrp, &count) && idx < count) { + ret = JS_DupValue(ctx, arrpidx); + } else { + int present = JS_TryGetPropertyInt64(ctx, obj, idx, &ret); + if (present < 0) + goto exception; + if (!present) + ret = JS_UNDEFINED; + } + JS_FreeValue(ctx, obj); + return ret; + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_with(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, obj, ret, *arrp, *pval; + JSObject *p; + int64_t i, len, idx; + uint32_t count32; + + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (JS_ToInt64Sat(ctx, &idx, argv0)) + goto exception; + + if (idx < 0) + idx = len + idx; + + if (idx < 0 || idx >= len) { + JS_ThrowRangeError(ctx, "invalid array index: %" PRId64, idx); + goto exception; + } + + arr = js_allocate_fast_array(ctx, len); + if (JS_IsException(arr)) + goto exception; + + p = JS_VALUE_GET_OBJ(arr); + i = 0; + pval = p->u.array.u.values; + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (; i < idx; i++, pval++) + *pval = JS_DupValue(ctx, arrpi); + *pval = JS_DupValue(ctx, argv1); + for (i++, pval++; i < len; i++, pval++) + *pval = JS_DupValue(ctx, arrpi); + } else { + for (; i < idx; i++, pval++) + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) + goto fill_and_fail; + *pval = JS_DupValue(ctx, argv1); + for (i++, pval++; i < len; i++, pval++) { + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { + fill_and_fail: + for (; i < len; i++, pval++) + *pval = JS_UNDEFINED; + goto exception; + } + } + } + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) + goto exception; + + ret = arr; + arr = JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + static JSValue js_array_concat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -38447,8 +40967,6 @@ #define special_filter 4 #define special_TA 8 -static int js_typed_array_get_length_internal(JSContext *ctx, JSValueConst obj); - static JSValue js_typed_array___speciesCreate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -38466,7 +40984,7 @@ val = JS_UNDEFINED; if (special & special_TA) { obj = JS_DupValue(ctx, this_val); - len = js_typed_array_get_length_internal(ctx, obj); + len = js_typed_array_get_length_unsafe(ctx, obj); if (len < 0) goto exception; } else { @@ -38589,8 +41107,10 @@ goto exception; args0 = ret; res = JS_Invoke(ctx, arr, JS_ATOM_set, 1, args); - if (check_exception_free(ctx, res)) + if (check_exception_free(ctx, res)) { + JS_FreeValue(ctx, arr); goto exception; + } JS_FreeValue(ctx, ret); ret = arr; } @@ -38621,7 +41141,7 @@ val = JS_UNDEFINED; if (special & special_TA) { obj = JS_DupValue(ctx, this_val); - len = js_typed_array_get_length_internal(ctx, obj); + len = js_typed_array_get_length_unsafe(ctx, obj); if (len < 0) goto exception; } else { @@ -38739,9 +41259,10 @@ int argc, JSValueConst *argv) { JSValue obj, val; - int64_t len, n, res; + int64_t len, n; JSValue *arrp; uint32_t count; + int res; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) @@ -38872,13 +41393,21 @@ return JS_EXCEPTION; } +enum { + ArrayFind, + ArrayFindIndex, + ArrayFindLast, + ArrayFindLastIndex, +}; + static JSValue js_array_find(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int findIndex) + int argc, JSValueConst *argv, int mode) { JSValueConst func, this_arg; JSValueConst args3; JSValue obj, val, index_val, res; - int64_t len, k; + int64_t len, k, end; + int dir; index_val = JS_UNDEFINED; val = JS_UNDEFINED; @@ -38894,7 +41423,17 @@ if (argc > 1) this_arg = argv1; - for(k = 0; k < len; k++) { + k = 0; + dir = 1; + end = len; + if (mode == ArrayFindLast || mode == ArrayFindLastIndex) { + k = len - 1; + dir = -1; + end = -1; + } + + // TODO(bnoordhuis) add fast path for fast arrays + for(; k != end; k += dir) { index_val = JS_NewInt64(ctx, k); if (JS_IsException(index_val)) goto exception; @@ -38908,7 +41447,7 @@ if (JS_IsException(res)) goto exception; if (JS_ToBoolFree(ctx, res)) { - if (findIndex) { + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) { JS_FreeValue(ctx, val); JS_FreeValue(ctx, obj); return index_val; @@ -38922,7 +41461,7 @@ JS_FreeValue(ctx, index_val); } JS_FreeValue(ctx, obj); - if (findIndex) + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) return JS_NewInt32(ctx, -1); else return JS_UNDEFINED; @@ -39074,65 +41613,53 @@ int i; int64_t len, from, newLen; - obj = JS_ToObject(ctx, this_val); - - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id != JS_CLASS_ARRAY || - !p->fast_array || !p->extensible) - goto generic_case; - /* length must be writable */ - if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) - goto generic_case; - /* check the length */ - if (unlikely(JS_VALUE_GET_TAG(p->prop0.u.value) != JS_TAG_INT)) - goto generic_case; - len = JS_VALUE_GET_INT(p->prop0.u.value); - /* we don't support holes */ - if (unlikely(len != p->u.array.count)) - goto generic_case; - newLen = len + argc; - if (unlikely(newLen > INT32_MAX)) - goto generic_case; - if (newLen > p->u.array.u1.size) { - if (expand_fast_array(ctx, p, newLen)) - goto exception; - } - if (unshift && argc > 0) { - memmove(p->u.array.u.values + argc, p->u.array.u.values, - len * sizeof(p->u.array.u.values0)); - from = 0; - } else { - from = len; - } - for(i = 0; i < argc; i++) { - p->u.array.u.valuesfrom + i = JS_DupValue(ctx, argvi); + if (likely(JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT && !unshift)) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (likely(p->class_id == JS_CLASS_ARRAY && p->fast_array && + p->extensible && + p->shape->proto == JS_VALUE_GET_OBJ(ctx->class_protoJS_CLASS_ARRAY) && + ctx->std_array_prototype && + JS_VALUE_GET_TAG(p->prop0.u.value) == JS_TAG_INT && + JS_VALUE_GET_INT(p->prop0.u.value) == p->u.array.count && + (get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE) != 0)) { + /* fast case */ + uint32_t new_len; + new_len = p->u.array.count + argc; + if (likely(new_len <= INT32_MAX)) { + if (unlikely(new_len > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_len)) + return JS_EXCEPTION; + } + for(i = 0; i < argc; i++) + p->u.array.u.valuesp->u.array.count + i = JS_DupValue(ctx, argvi); + p->prop0.u.value = JS_NewInt32(ctx, new_len); + p->u.array.count = new_len; + return JS_NewInt32(ctx, new_len); + } } - p->u.array.count = newLen; - p->prop0.u.value = JS_NewInt32(ctx, newLen); - } else { - generic_case: - if (js_get_length64(ctx, &len, obj)) - goto exception; - newLen = len + argc; - if (newLen > MAX_SAFE_INTEGER) { - JS_ThrowTypeError(ctx, "Array loo long"); + } + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + newLen = len + argc; + if (newLen > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array loo long"); + goto exception; + } + from = len; + if (unshift && argc > 0) { + if (JS_CopySubArray(ctx, obj, argc, 0, len, -1)) goto exception; - } - from = len; - if (unshift && argc > 0) { - if (JS_CopySubArray(ctx, obj, argc, 0, len, -1)) - goto exception; - from = 0; - } - for(i = 0; i < argc; i++) { - if (JS_SetPropertyInt64(ctx, obj, from + i, - JS_DupValue(ctx, argvi)) < 0) - goto exception; - } - if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0) + from = 0; + } + for(i = 0; i < argc; i++) { + if (JS_SetPropertyInt64(ctx, obj, from + i, + JS_DupValue(ctx, argvi)) < 0) goto exception; } + if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0) + goto exception; + JS_FreeValue(ctx, obj); return JS_NewInt64(ctx, newLen); @@ -39210,6 +41737,61 @@ return JS_EXCEPTION; } +// Note: a.toReversed() is a.slice().reverse() with the twist that a.slice() +// leaves holes in sparse arrays intact whereas a.toReversed() replaces them +// with undefined, thus in effect creating a dense array. +// Does not use Array@@species, always returns a base Array. +static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, obj, ret, *arrp, *pval; + JSObject *p; + int64_t i, len; + uint32_t count32; + + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + arr = js_allocate_fast_array(ctx, len); + if (JS_IsException(arr)) + goto exception; + + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + + i = len - 1; + pval = p->u.array.u.values; + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (; i >= 0; i--, pval++) + *pval = JS_DupValue(ctx, arrpi); + } else { + // Query order is observable; test262 expects descending order. + for (; i >= 0; i--, pval++) { + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { + // Exception; initialize remaining elements. + for (; i >= 0; i--, pval++) + *pval = JS_UNDEFINED; + goto exception; + } + } + } + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) + goto exception; + } + + ret = arr; + arr = JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int splice) { @@ -39317,6 +41899,92 @@ return JS_EXCEPTION; } +static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, obj, ret, *arrp, *pval, *last; + JSObject *p; + int64_t i, j, len, newlen, start, add, del; + uint32_t count32; + + pval = NULL; + last = NULL; + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + start = 0; + if (argc > 0) + if (JS_ToInt64Clamp(ctx, &start, argv0, 0, len, len)) + goto exception; + + del = 0; + if (argc > 0) + del = len - start; + if (argc > 1) + if (JS_ToInt64Clamp(ctx, &del, argv1, 0, del, 0)) + goto exception; + + add = 0; + if (argc > 2) + add = argc - 2; + + newlen = len + add - del; + if (newlen > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "invalid array length"); + goto exception; + } + + arr = js_allocate_fast_array(ctx, newlen); + if (JS_IsException(arr)) + goto exception; + + if (newlen <= 0) + goto done; + + p = JS_VALUE_GET_OBJ(arr); + pval = &p->u.array.u.values0; + last = &p->u.array.u.valuesnewlen; + + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (i = 0; i < start; i++, pval++) + *pval = JS_DupValue(ctx, arrpi); + for (j = 0; j < add; j++, pval++) + *pval = JS_DupValue(ctx, argv2 + j); + for (i += del; i < len; i++, pval++) + *pval = JS_DupValue(ctx, arrpi); + } else { + for (i = 0; i < start; i++, pval++) + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) + goto exception; + for (j = 0; j < add; j++, pval++) + *pval = JS_DupValue(ctx, argv2 + j); + for (i += del; i < len; i++, pval++) + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) + goto exception; + } + + assert(pval == last); + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, newlen)) < 0) + goto exception; + +done: + ret = arr; + arr = JS_UNDEFINED; + +exception: + while (pval != last) + *pval++ = JS_UNDEFINED; + + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + static JSValue js_array_copyWithin(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -39625,6 +42293,68 @@ return JS_EXCEPTION; } +// Note: a.toSorted() is a.slice().sort() with the twist that a.slice() +// leaves holes in sparse arrays intact whereas a.toSorted() replaces them +// with undefined, thus in effect creating a dense array. +// Does not use Array@@species, always returns a base Array. +static JSValue js_array_toSorted(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, obj, ret, *arrp, *pval; + JSObject *p; + int64_t i, len; + uint32_t count32; + int ok; + + ok = JS_IsUndefined(argv0) || JS_IsFunction(ctx, argv0); + if (!ok) + return JS_ThrowTypeError(ctx, "not a function"); + + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + arr = js_allocate_fast_array(ctx, len); + if (JS_IsException(arr)) + goto exception; + + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + i = 0; + pval = p->u.array.u.values; + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (; i < len; i++, pval++) + *pval = JS_DupValue(ctx, arrpi); + } else { + for (; i < len; i++, pval++) { + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { + for (; i < len; i++, pval++) + *pval = JS_UNDEFINED; + goto exception; + } + } + } + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) + goto exception; + } + + ret = js_array_sort(ctx, arr, argc, argv); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, ret); + + ret = arr; + arr = JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + typedef struct JSArrayIteratorData { JSValue obj; JSIteratorKindEnum kind; @@ -39651,23 +42381,6 @@ } } -static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab) -{ - JSValue obj; - int i; - - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - for(i = 0; i < len; i++) { - if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, tabi), 0) < 0) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - } - return obj; -} - static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { @@ -39722,8 +42435,8 @@ p = JS_VALUE_GET_OBJ(it->obj); if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail1; } len = p->u.array.count; @@ -39766,17 +42479,894 @@ } } +/* Iterator Wrap */ + +typedef struct JSIteratorWrapData { + JSValue wrapped_iter; + JSValue wrapped_next; +} JSIteratorWrapData; + +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorWrapData *it = p->u.iterator_wrap_data; + if (it) { + JS_FreeValueRT(rt, it->wrapped_iter); + JS_FreeValueRT(rt, it->wrapped_next); + js_free_rt(rt, it); + } +} + +static void js_iterator_wrap_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorWrapData *it = p->u.iterator_wrap_data; + if (it) { + JS_MarkValue(rt, it->wrapped_iter, mark_func); + JS_MarkValue(rt, it->wrapped_next, mark_func); + } +} + +static JSValue js_iterator_wrap_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int *pdone, int magic) +{ + JSIteratorWrapData *it; + JSValue method, ret; + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ITERATOR_WRAP); + if (!it) + return JS_EXCEPTION; + if (magic == GEN_MAGIC_NEXT) { + return JS_IteratorNext(ctx, it->wrapped_iter, it->wrapped_next, 0, NULL, pdone); + } else { + method = JS_GetProperty(ctx, it->wrapped_iter, JS_ATOM_return); + if (JS_IsException(method)) + return JS_EXCEPTION; + if (JS_IsNull(method) || JS_IsUndefined(method)) { + *pdone = TRUE; + return JS_UNDEFINED; + } + ret = JS_IteratorNext2(ctx, it->wrapped_iter, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + return ret; + } +} + +static const JSCFunctionListEntry js_iterator_wrap_proto_funcs = { + JS_ITERATOR_NEXT_DEF("next", 0, js_iterator_wrap_next, GEN_MAGIC_NEXT ), + JS_ITERATOR_NEXT_DEF("return", 0, js_iterator_wrap_next, GEN_MAGIC_RETURN ), +}; + +/* Iterator */ + +static JSValue js_iterator_constructor_getset(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, + JSValue *func_data) +{ + int ret; + + if (argc > 0) { // if setter + if (!JS_IsObject(argv0)) + return JS_ThrowTypeErrorNotAnObject(ctx); + ret = JS_DefinePropertyValue(ctx, this_val, JS_ATOM_constructor, + JS_DupValue(ctx, argv0), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (ret < 0) + return JS_EXCEPTION; + return JS_UNDEFINED; + } else { + return JS_DupValue(ctx, func_data0); + } +} + +static JSValue js_iterator_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSObject *p; + + if (JS_TAG_OBJECT != JS_VALUE_GET_TAG(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + p = JS_VALUE_GET_OBJ(new_target); + if (p->class_id == JS_CLASS_C_FUNCTION && + p->u.cfunc.c_function.generic == js_iterator_constructor) { + return JS_ThrowTypeError(ctx, "abstract class not constructable"); + } + return js_create_from_ctor(ctx, new_target, JS_CLASS_ITERATOR); +} + +static JSValue js_iterator_from(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst obj = argv0; + JSValue method, iter, wrapper; + JSIteratorWrapData *it; + int ret; + + if (!JS_IsObject(obj)) { + if (!JS_IsString(obj)) + return JS_ThrowTypeError(ctx, "Iterator.from called on non-object"); + } + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return JS_EXCEPTION; + if (JS_IsNull(method) || JS_IsUndefined(method)) { + iter = JS_DupValue(ctx, obj); + } else { + iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(iter)) + return JS_EXCEPTION; + } + + wrapper = JS_UNDEFINED; + method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(method)) + goto fail; + + ret = JS_OrdinaryIsInstanceOf(ctx, iter, ctx->iterator_ctor); + if (ret < 0) + goto fail; + if (ret) { + JS_FreeValue(ctx, method); + return iter; + } + + wrapper = JS_NewObjectClass(ctx, JS_CLASS_ITERATOR_WRAP); + if (JS_IsException(wrapper)) + goto fail; + it = js_malloc(ctx, sizeof(*it)); + if (!it) + goto fail; + it->wrapped_iter = iter; + it->wrapped_next = method; + JS_SetOpaque(wrapper, it); + return wrapper; + + fail: + JS_FreeValue(ctx, method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, wrapper); + return JS_EXCEPTION; +} + +typedef enum JSIteratorHelperKindEnum { + JS_ITERATOR_HELPER_KIND_DROP, + JS_ITERATOR_HELPER_KIND_EVERY, + JS_ITERATOR_HELPER_KIND_FILTER, + JS_ITERATOR_HELPER_KIND_FIND, + JS_ITERATOR_HELPER_KIND_FLAT_MAP, + JS_ITERATOR_HELPER_KIND_FOR_EACH, + JS_ITERATOR_HELPER_KIND_MAP, + JS_ITERATOR_HELPER_KIND_SOME, + JS_ITERATOR_HELPER_KIND_TAKE, +} JSIteratorHelperKindEnum; + +typedef struct JSIteratorHelperData { + JSValue obj; + JSValue next; + JSValue func; // predicate (filter) or mapper (flatMap, map) + JSValue inner; // innerValue (flatMap) + int64_t count; // limit (drop, take) or counter (filter, map, flatMap) + JSIteratorHelperKindEnum kind : 8; + uint8_t executing : 1; + uint8_t done : 1; +} JSIteratorHelperData; + +static JSValue js_create_iterator_helper(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSValueConst func; + JSValue obj, method; + int64_t count; + JSIteratorHelperData *it; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + func = JS_UNDEFINED; + count = 0; + + switch(magic) { + case JS_ITERATOR_HELPER_KIND_DROP: + case JS_ITERATOR_HELPER_KIND_TAKE: + { + JSValue v; + double dlimit; + v = JS_ToNumber(ctx, argv0); + if (JS_IsException(v)) + goto fail; + // Check for Infinity. + if (JS_ToFloat64(ctx, &dlimit, v)) { + JS_FreeValue(ctx, v); + goto fail; + } + if (isnan(dlimit)) { + JS_FreeValue(ctx, v); + goto range_error; + } + if (!isfinite(dlimit)) { + JS_FreeValue(ctx, v); + if (dlimit < 0) + goto range_error; + else + count = MAX_SAFE_INTEGER; + } else { + v = JS_ToIntegerFree(ctx, v); + if (JS_IsException(v)) + goto fail; + if (JS_ToInt64Free(ctx, &count, v)) + goto fail; + } + if (count < 0) + goto range_error; + } + break; + case JS_ITERATOR_HELPER_KIND_FILTER: + case JS_ITERATOR_HELPER_KIND_FLAT_MAP: + case JS_ITERATOR_HELPER_KIND_MAP: + { + func = argv0; + if (check_function(ctx, func)) + goto fail; + } + break; + default: + abort(); + break; + } + + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + goto fail; + obj = JS_NewObjectClass(ctx, JS_CLASS_ITERATOR_HELPER); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, method); + goto fail; + } + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, method); + goto fail; + } + it->kind = magic; + it->obj = JS_DupValue(ctx, this_val); + it->func = JS_DupValue(ctx, func); + it->next = method; + it->inner = JS_UNDEFINED; + it->count = count; + it->executing = 0; + it->done = 0; + JS_SetOpaque(obj, it); + return obj; +range_error: + JS_ThrowRangeError(ctx, "must be positive"); +fail: + JS_IteratorClose(ctx, this_val, TRUE); + return JS_EXCEPTION; +} + +static JSValue js_iterator_proto_func(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSValue item, method, ret, func, index_val, r; + JSValueConst args2; + int64_t idx; + int done; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + func = JS_UNDEFINED; + method = JS_UNDEFINED; + + if (check_function(ctx, argv0)) + goto fail; + func = JS_DupValue(ctx, argv0); + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + goto fail_no_close; + + r = JS_UNDEFINED; + + switch(magic) { + case JS_ITERATOR_HELPER_KIND_EVERY: + { + r = JS_TRUE; + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail_no_close; + if (done) + break; + index_val = JS_NewInt64(ctx, idx); + args0 = item; + args1 = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + if (!JS_ToBoolFree(ctx, ret)) { + if (JS_IteratorClose(ctx, this_val, FALSE) < 0) + r = JS_EXCEPTION; + else + r = JS_FALSE; + break; + } + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + case JS_ITERATOR_HELPER_KIND_FIND: + { + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail_no_close; + if (done) + break; + index_val = JS_NewInt64(ctx, idx); + args0 = item; + args1 = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, item); + goto fail; + } + if (JS_ToBoolFree(ctx, ret)) { + if (JS_IteratorClose(ctx, this_val, FALSE) < 0) { + JS_FreeValue(ctx, item); + r = JS_EXCEPTION; + } else { + r = item; + } + break; + } + JS_FreeValue(ctx, item); + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + case JS_ITERATOR_HELPER_KIND_FOR_EACH: + { + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail_no_close; + if (done) + break; + index_val = JS_NewInt64(ctx, idx); + args0 = item; + args1 = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + JS_FreeValue(ctx, ret); + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + case JS_ITERATOR_HELPER_KIND_SOME: + { + r = JS_FALSE; + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail_no_close; + if (done) + break; + index_val = JS_NewInt64(ctx, idx); + args0 = item; + args1 = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + if (JS_ToBoolFree(ctx, ret)) { + if (JS_IteratorClose(ctx, this_val, FALSE) < 0) + r = JS_EXCEPTION; + else + r = JS_TRUE; + break; + } + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + default: + abort(); + break; + } + + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return r; + fail: + JS_IteratorClose(ctx, this_val, TRUE); + fail_no_close: + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; +} + +static JSValue js_iterator_proto_reduce(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue item, method, ret, func, index_val, acc; + JSValueConst args3; + int64_t idx; + int done; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + acc = JS_UNDEFINED; + func = JS_UNDEFINED; + method = JS_UNDEFINED; + if (check_function(ctx, argv0)) + goto exception; + func = JS_DupValue(ctx, argv0); + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + goto exception; + if (argc > 1) { + acc = JS_DupValue(ctx, argv1); + idx = 0; + } else { + acc = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(acc)) + goto exception_no_close; + if (done) { + JS_ThrowTypeError(ctx, "empty iterator"); + goto exception; + } + idx = 1; + } + for (/* empty */; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto exception_no_close; + if (done) + break; + index_val = JS_NewInt64(ctx, idx); + args0 = acc; + args1 = item; + args2 = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, acc); + acc = ret; + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return acc; + exception: + JS_IteratorClose(ctx, this_val, TRUE); + exception_no_close: + JS_FreeValue(ctx, acc); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; +} + +static JSValue js_iterator_proto_toArray(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue item, method, result; + int64_t idx; + int done; + + result = JS_UNDEFINED; + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + return JS_EXCEPTION; + result = JS_NewArray(ctx); + if (JS_IsException(result)) + goto exception; + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) + break; + if (JS_DefinePropertyValueInt64(ctx, result, idx, item, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + if (JS_SetProperty(ctx, result, JS_ATOM_length, JS_NewUint32(ctx, idx)) < 0) + goto exception; + JS_FreeValue(ctx, method); + return result; +exception: + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; +} + static JSValue js_iterator_proto_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_DupValue(ctx, this_val); } +static JSValue js_iterator_proto_get_toStringTag(JSContext *ctx, JSValueConst this_val) +{ + return JS_AtomToString(ctx, JS_ATOM_Iterator); +} + +static JSValue js_iterator_proto_set_toStringTag(JSContext *ctx, JSValueConst this_val, JSValueConst val) +{ + int res; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + if (js_same_value(ctx, this_val, ctx->class_protoJS_CLASS_ITERATOR)) + return JS_ThrowTypeError(ctx, "Cannot assign to read only property"); + res = JS_GetOwnProperty(ctx, NULL, this_val, JS_ATOM_Symbol_toStringTag); + if (res < 0) + return JS_EXCEPTION; + if (res) { + if (JS_SetProperty(ctx, this_val, JS_ATOM_Symbol_toStringTag, JS_DupValue(ctx, val)) < 0) + return JS_EXCEPTION; + } else { + if (JS_DefinePropertyValue(ctx, this_val, JS_ATOM_Symbol_toStringTag, JS_DupValue(ctx, val), JS_PROP_C_W_E) < 0) + return JS_EXCEPTION; + } + return JS_UNDEFINED; +} + +/* Iterator Helper */ + +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorHelperData *it = p->u.iterator_helper_data; + if (it) { + JS_FreeValueRT(rt, it->obj); + JS_FreeValueRT(rt, it->func); + JS_FreeValueRT(rt, it->next); + JS_FreeValueRT(rt, it->inner); + js_free_rt(rt, it); + } +} + +static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorHelperData *it = p->u.iterator_helper_data; + if (it) { + JS_MarkValue(rt, it->obj, mark_func); + JS_MarkValue(rt, it->func, mark_func); + JS_MarkValue(rt, it->next, mark_func); + JS_MarkValue(rt, it->inner, mark_func); + } +} + +static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int *pdone, int magic) +{ + JSIteratorHelperData *it; + JSValue ret; + + *pdone = FALSE; + + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ITERATOR_HELPER); + if (!it) + return JS_EXCEPTION; + if (it->executing) + return JS_ThrowTypeError(ctx, "cannot invoke a running iterator"); + if (it->done) { + *pdone = TRUE; + return JS_UNDEFINED; + } + + it->executing = 1; + + switch (it->kind) { + case JS_ITERATOR_HELPER_KIND_DROP: + { + JSValue item, method; + if (magic == GEN_MAGIC_NEXT) { + method = JS_DupValue(ctx, it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + while (it->count > 0) { + it->count--; + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + if (JS_IsException(item)) { + JS_FreeValue(ctx, method); + goto fail_no_close; + } + JS_FreeValue(ctx, item); + if (magic == GEN_MAGIC_RETURN) + *pdone = TRUE; + if (*pdone) { + JS_FreeValue(ctx, method); + ret = JS_UNDEFINED; + goto done; + } + } + + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail_no_close; + ret = item; + goto done; + } + break; + case JS_ITERATOR_HELPER_KIND_FILTER: + { + JSValue item, method, selected, index_val; + JSValueConst args2; + if (magic == GEN_MAGIC_NEXT) { + method = JS_DupValue(ctx, it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + filter_again: + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + if (JS_IsException(item)) { + JS_FreeValue(ctx, method); + goto fail_no_close; + } + if (*pdone || magic == GEN_MAGIC_RETURN) { + JS_FreeValue(ctx, method); + ret = item; + goto done; + } + index_val = JS_NewInt64(ctx, it->count++); + args0 = item; + args1 = index_val; + selected = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(selected)) { + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, method); + goto fail; + } + if (JS_ToBoolFree(ctx, selected)) { + JS_FreeValue(ctx, method); + ret = item; + goto done; + } + JS_FreeValue(ctx, item); + goto filter_again; + } + break; + case JS_ITERATOR_HELPER_KIND_FLAT_MAP: + { + JSValue item, method, index_val, iter; + JSValueConst args2; + flat_map_again: + if (JS_IsUndefined(it->inner)) { + if (magic == GEN_MAGIC_NEXT) { + method = JS_DupValue(ctx, it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail_no_close; + if (*pdone || magic == GEN_MAGIC_RETURN) { + ret = item; + goto done; + } + index_val = JS_NewInt64(ctx, it->count++); + args0 = item; + args1 = index_val; + ret = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + if (!JS_IsObject(ret)) { + JS_FreeValue(ctx, ret); + JS_ThrowTypeError(ctx, "not an object"); + goto fail; + } + method = JS_GetProperty(ctx, ret, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) { + JS_FreeValue(ctx, ret); + goto fail; + } + if (JS_IsNull(method) || JS_IsUndefined(method)) { + JS_FreeValue(ctx, method); + iter = ret; + } else { + iter = JS_GetIterator2(ctx, ret, method); + JS_FreeValue(ctx, method); + JS_FreeValue(ctx, ret); + if (JS_IsException(iter)) + goto fail; + } + + it->inner = iter; + } + + if (magic == GEN_MAGIC_NEXT) + method = JS_GetProperty(ctx, it->inner, JS_ATOM_next); + else + method = JS_GetProperty(ctx, it->inner, JS_ATOM_return); + if (JS_IsException(method)) { + inner_fail: + JS_IteratorClose(ctx, it->inner, FALSE); + JS_FreeValue(ctx, it->inner); + it->inner = JS_UNDEFINED; + goto fail; + } + if (magic == GEN_MAGIC_RETURN && (JS_IsUndefined(method) || JS_IsNull(method))) { + goto inner_end; + } else { + item = JS_IteratorNext(ctx, it->inner, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto inner_fail; + } + if (*pdone) { + inner_end: + *pdone = FALSE; // The outer iterator must continue. + JS_IteratorClose(ctx, it->inner, FALSE); + JS_FreeValue(ctx, it->inner); + it->inner = JS_UNDEFINED; + goto flat_map_again; + } + ret = item; + goto done; + } + break; + case JS_ITERATOR_HELPER_KIND_MAP: + { + JSValue item, method, index_val; + JSValueConst args2; + if (magic == GEN_MAGIC_NEXT) { + method = JS_DupValue(ctx, it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail_no_close; + if (*pdone || magic == GEN_MAGIC_RETURN) { + ret = item; + goto done; + } + index_val = JS_NewInt64(ctx, it->count++); + args0 = item; + args1 = index_val; + ret = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + goto done; + } + break; + case JS_ITERATOR_HELPER_KIND_TAKE: + { + JSValue item, method; + if (it->count > 0) { + if (magic == GEN_MAGIC_NEXT) { + method = JS_DupValue(ctx, it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + it->count--; + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail_no_close; + ret = item; + goto done; + } + + *pdone = TRUE; + if (JS_IteratorClose(ctx, it->obj, FALSE)) + ret = JS_EXCEPTION; + else + ret = JS_UNDEFINED; + goto done; + } + break; + default: + abort(); + } + + done: + it->done = magic == GEN_MAGIC_NEXT ? *pdone : 1; + it->executing = 0; + return ret; + fail: + /* close the iterator object, preserving pending exception */ + JS_IteratorClose(ctx, it->obj, TRUE); + fail_no_close: + ret = JS_EXCEPTION; + goto done; +} + +static const JSCFunctionListEntry js_iterator_funcs = { + JS_CFUNC_DEF("from", 1, js_iterator_from ), +}; + static const JSCFunctionListEntry js_iterator_proto_funcs = { + JS_CFUNC_MAGIC_DEF("drop", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_DROP ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_FILTER ), + JS_CFUNC_MAGIC_DEF("flatMap", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_FLAT_MAP ), + JS_CFUNC_MAGIC_DEF("map", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_MAP ), + JS_CFUNC_MAGIC_DEF("take", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_TAKE ), + JS_CFUNC_MAGIC_DEF("every", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_EVERY ), + JS_CFUNC_MAGIC_DEF("find", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_FIND), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_FOR_EACH ), + JS_CFUNC_MAGIC_DEF("some", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_SOME ), + JS_CFUNC_DEF("reduce", 1, js_iterator_proto_reduce ), + JS_CFUNC_DEF("toArray", 0, js_iterator_proto_toArray ), JS_CFUNC_DEF("Symbol.iterator", 0, js_iterator_proto_iterator ), + JS_CGETSET_DEF("Symbol.toStringTag", js_iterator_proto_get_toStringTag, js_iterator_proto_set_toStringTag), +}; + +static const JSCFunctionListEntry js_iterator_helper_proto_funcs = { + JS_ITERATOR_NEXT_DEF("next", 0, js_iterator_helper_next, GEN_MAGIC_NEXT ), + JS_ITERATOR_NEXT_DEF("return", 0, js_iterator_helper_next, GEN_MAGIC_RETURN ), + JS_PROP_STRING_DEF("Symbol.toStringTag", "Iterator Helper", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_array_unscopables_funcs = { + JS_PROP_BOOL_DEF("at", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("copyWithin", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("entries", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("fill", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("find", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("findIndex", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("findLast", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("findLastIndex", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("flat", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("flatMap", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("includes", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("keys", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("toReversed", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("toSorted", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("toSpliced", TRUE, JS_PROP_C_W_E), + JS_PROP_BOOL_DEF("values", TRUE, JS_PROP_C_W_E), }; static const JSCFunctionListEntry js_array_proto_funcs = { + JS_CFUNC_DEF("at", 1, js_array_at ), + JS_CFUNC_DEF("with", 2, js_array_with ), JS_CFUNC_DEF("concat", 1, js_array_concat ), JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ), JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ), @@ -39786,8 +43376,10 @@ JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ), JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ), JS_CFUNC_DEF("fill", 1, js_array_fill ), - JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, 0 ), - JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, 1 ), + JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, ArrayFind ), + JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, ArrayFindIndex ), + JS_CFUNC_MAGIC_DEF("findLast", 1, js_array_find, ArrayFindLast ), + JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_array_find, ArrayFindLastIndex ), JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ), JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ), JS_CFUNC_DEF("includes", 1, js_array_includes ), @@ -39799,9 +43391,12 @@ JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ), JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), JS_CFUNC_DEF("reverse", 0, js_array_reverse ), + JS_CFUNC_DEF("toReversed", 0, js_array_toReversed ), JS_CFUNC_DEF("sort", 1, js_array_sort ), + JS_CFUNC_DEF("toSorted", 1, js_array_toSorted ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ), JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ), + JS_CFUNC_DEF("toSpliced", 2, js_array_toSpliced ), JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ), JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ), JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ), @@ -39809,6 +43404,7 @@ JS_ALIAS_DEF("Symbol.iterator", "values" ), JS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ), + JS_OBJECT_DEF("Symbol.unscopables", js_array_unscopables_funcs, countof(js_array_unscopables_funcs), JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_array_iterator_proto_funcs = { @@ -39829,26 +43425,20 @@ if (JS_IsException(val)) return val; switch(JS_VALUE_GET_TAG(val)) { -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: + val = JS_NewInt64(ctx, JS_VALUE_GET_SHORT_BIG_INT(val)); + if (JS_IsException(val)) + return val; + break; case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: { - JSBigFloat *p = JS_VALUE_GET_PTR(val); + JSBigInt *p = JS_VALUE_GET_PTR(val); double d; - bf_get_float64(&p->num, &d, BF_RNDN); + d = js_bigint_to_float64(ctx, p); JS_FreeValue(ctx, val); - val = __JS_NewFloat64(ctx, d); + val = JS_NewFloat64(ctx, d); } break; - case JS_TAG_BIG_DECIMAL: - val = JS_ToStringFree(ctx, val); - if (JS_IsException(val)) - return val; - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return val; - break; -#endif default: break; } @@ -39975,7 +43565,7 @@ int argc, JSValueConst *argv, int magic) { JSValue val; - int base; + int base, flags; double d; val = js_thisNumberValue(ctx, this_val); @@ -39988,9 +43578,18 @@ if (base < 0) goto fail; } + if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { + char buf170; + int len; + len = i64toa_radix(buf1, JS_VALUE_GET_INT(val), base); + return js_new_string8_len(ctx, buf1, len); + } if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; - return js_dtoa(ctx, d, base, 0, JS_DTOA_VAR_FORMAT); + flags = JS_DTOA_FORMAT_FREE; + if (base != 10) + flags |= JS_DTOA_EXP_DISABLED; + return js_dtoa2(ctx, d, base, 0, flags); fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; @@ -40000,7 +43599,7 @@ int argc, JSValueConst *argv) { JSValue val; - int f; + int f, flags; double d; val = js_thisNumberValue(ctx, this_val); @@ -40012,11 +43611,11 @@ return JS_EXCEPTION; if (f < 0 || f > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); - if (fabs(d) >= 1e21) { - return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); - } else { - return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT); - } + if (fabs(d) >= 1e21) + flags = JS_DTOA_FORMAT_FREE; + else + flags = JS_DTOA_FORMAT_FRAC; + return js_dtoa2(ctx, d, 10, f, flags); } static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val, @@ -40037,15 +43636,15 @@ return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); } if (JS_IsUndefined(argv0)) { - flags = 0; + flags = JS_DTOA_FORMAT_FREE; f = 0; } else { if (f < 0 || f > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); f++; - flags = JS_DTOA_FIXED_FORMAT; + flags = JS_DTOA_FORMAT_FIXED; } - return js_dtoa(ctx, d, 10, f, flags | JS_DTOA_FORCE_EXP); + return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED); } static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val, @@ -40070,7 +43669,7 @@ } if (p < 1 || p > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); - return js_dtoa(ctx, d, 10, p, JS_DTOA_FIXED_FORMAT); + return js_dtoa2(ctx, d, 10, p, JS_DTOA_FORMAT_FIXED); } static const JSCFunctionListEntry js_number_proto_funcs = { @@ -40194,10 +43793,7 @@ idx = __JS_AtomToUInt32(prop); if (idx < p1->len) { if (desc) { - if (p1->is_wide_char) - ch = p1->u.str16idx; - else - ch = p1->u.str8idx; + ch = string_get(p1, idx); desc->flags = JS_PROP_ENUMERABLE; desc->value = js_new_string_char(ctx, ch); desc->getter = JS_UNDEFINED; @@ -40290,7 +43886,9 @@ JSString *p1 = JS_VALUE_GET_STRING(val); obj = js_create_from_ctor(ctx, new_target, JS_CLASS_STRING); - if (!JS_IsException(obj)) { + if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); + } else { JS_SetObjectData(ctx, obj, val); JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, JS_NewInt32(ctx, p1->len), 0); } @@ -40302,7 +43900,8 @@ static JSValue js_thisStringValue(JSContext *ctx, JSValueConst this_val) { - if (JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING) + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING || + JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING_ROPE) return JS_DupValue(ctx, this_val); if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { @@ -40352,7 +43951,7 @@ } else { if (JS_ToFloat64(ctx, &d, argvi)) goto fail; - if (d < 0 || d > 0x10ffff || (c = (int)d) != d) + if (isnan(d) || d < 0 || d > 0x10ffff || (c = (int)d) != d) goto range_error; } if (string_buffer_putc(b, c)) @@ -40463,10 +44062,7 @@ if (idx < 0 || idx >= p->len) { ret = JS_NAN; } else { - if (p->is_wide_char) - c = p->u.str16idx; - else - c = p->u.str8idx; + c = string_get(p, idx); ret = JS_NewInt32(ctx, c); } JS_FreeValue(ctx, val); @@ -40474,7 +44070,7 @@ } static JSValue js_string_charAt(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv, int is_at) { JSValue val, ret; JSString *p; @@ -40488,13 +44084,15 @@ JS_FreeValue(ctx, val); return JS_EXCEPTION; } + if (idx < 0 && is_at) + idx += p->len; if (idx < 0 || idx >= p->len) { - ret = js_new_string8(ctx, NULL, 0); - } else { - if (p->is_wide_char) - c = p->u.str16idx; + if (is_at) + ret = JS_UNDEFINED; else - c = p->u.str8idx; + ret = JS_AtomToString(ctx, JS_ATOM_empty_string); + } else { + c = string_get(p, idx); ret = js_new_string_char(ctx, c); } JS_FreeValue(ctx, val); @@ -40602,6 +44200,80 @@ return index; } +/* return the position of the first invalid character in the string or + -1 if none */ +static int js_string_find_invalid_codepoint(JSString *p) +{ + int i; + if (!p->is_wide_char) + return -1; + for(i = 0; i < p->len; i++) { + uint32_t c = p->u.str16i; + if (is_surrogate(c)) { + if (is_hi_surrogate(c) && (i + 1) < p->len + && is_lo_surrogate(p->u.str16i + 1)) { + i++; + } else { + return i; + } + } + } + return -1; +} + +static JSValue js_string_isWellFormed(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue str; + JSString *p; + BOOL ret; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return JS_EXCEPTION; + p = JS_VALUE_GET_STRING(str); + ret = (js_string_find_invalid_codepoint(p) < 0); + JS_FreeValue(ctx, str); + return JS_NewBool(ctx, ret); +} + +static JSValue js_string_toWellFormed(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue str, ret; + JSString *p; + int i; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_STRING(str); + /* avoid reallocating the string if it is well-formed */ + i = js_string_find_invalid_codepoint(p); + if (i < 0) + return str; + + ret = js_new_string16_len(ctx, p->u.str16, p->len); + JS_FreeValue(ctx, str); + if (JS_IsException(ret)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_STRING(ret); + for (; i < p->len; i++) { + uint32_t c = p->u.str16i; + if (is_surrogate(c)) { + if (is_hi_surrogate(c) && (i + 1) < p->len + && is_lo_surrogate(p->u.str16i + 1)) { + i++; + } else { + p->u.str16i = 0xFFFD; + } + } + } + return ret; +} + static JSValue js_string_indexOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int lastIndexOf) { @@ -40684,7 +44356,7 @@ ret = js_is_regexp(ctx, argv0); if (ret) { if (ret > 0) - JS_ThrowTypeError(ctx, "regex not supported"); + JS_ThrowTypeError(ctx, "regexp not supported"); goto fail; } v = JS_ToString(ctx, argv0); @@ -40796,7 +44468,7 @@ args0 = regexp; str = JS_UNDEFINED; if (atom == JS_ATOM_Symbol_matchAll) { - str = JS_NewString(ctx, "g"); + str = js_new_string8(ctx, "g"); if (JS_IsException(str)) goto fail; #if defined(JS_VALUE_CANNOT_BE_CAST) @@ -41251,8 +44923,8 @@ } } if (n > JS_STRING_LEN_MAX) { - JS_ThrowInternalError(ctx, "string too long"); - goto fail2; + JS_ThrowRangeError(ctx, "invalid string length"); + goto fail3; } if (string_buffer_init(ctx, b, n)) goto fail3; @@ -41313,8 +44985,9 @@ len = p->len; if (len == 0 || n == 1) return str; + // XXX: potential arithmetic overflow if (val * len > JS_STRING_LEN_MAX) { - JS_ThrowInternalError(ctx, "string too long"); + JS_ThrowRangeError(ctx, "invalid string length"); goto fail; } if (string_buffer_init2(ctx, b, n * len, p->is_wide_char)) @@ -41360,12 +45033,6 @@ return ret; } -static JSValue js_string___quote(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_ToQuotedString(ctx, this_val); -} - /* return 0 if before the first char */ static int string_prevc(JSString *p, int *pidx) { @@ -41377,10 +45044,10 @@ idx--; if (p->is_wide_char) { c = p->u.str16idx; - if (c >= 0xdc00 && c < 0xe000 && idx > 0) { + if (is_lo_surrogate(c) && idx > 0) { c1 = p->u.str16idx - 1; - if (c1 >= 0xd800 && c1 <= 0xdc00) { - c = (((c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000; + if (is_hi_surrogate(c1)) { + c = from_surrogate(c1, c); idx--; } } @@ -41419,26 +45086,6 @@ return !lre_is_cased(c1); } -static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue a, b; - int cmp; - - a = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(a)) - return JS_EXCEPTION; - b = JS_ToString(ctx, argv0); - if (JS_IsException(b)) { - JS_FreeValue(ctx, a); - return JS_EXCEPTION; - } - cmp = js_string_compare(ctx, JS_VALUE_GET_STRING(a), JS_VALUE_GET_STRING(b)); - JS_FreeValue(ctx, a); - JS_FreeValue(ctx, b); - return JS_NewInt32(ctx, cmp); -} - static JSValue js_string_toLowerCase(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int to_lower) { @@ -41524,23 +45171,38 @@ return JS_EXCEPTION; } +static int js_string_normalize1(JSContext *ctx, uint32_t **pout_buf, + JSValueConst val, + UnicodeNormalizationEnum n_type) +{ + int buf_len, out_len; + uint32_t *buf, *out_buf; + + buf_len = JS_ToUTF32String(ctx, &buf, val); + if (buf_len < 0) + return -1; + out_len = unicode_normalize(&out_buf, buf, buf_len, n_type, + ctx->rt, (DynBufReallocFunc *)js_realloc_rt); + js_free(ctx, buf); + if (out_len < 0) + return -1; + *pout_buf = out_buf; + return out_len; +} + static JSValue js_string_normalize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *form, *p; size_t form_len; - int is_compat, buf_len, out_len; + int is_compat, out_len; UnicodeNormalizationEnum n_type; JSValue val; - uint32_t *buf, *out_buf; + uint32_t *out_buf; val = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(val)) return val; - buf_len = JS_ToUTF32String(ctx, &buf, val); - JS_FreeValue(ctx, val); - if (buf_len < 0) - return JS_EXCEPTION; if (argc == 0 || JS_IsUndefined(argv0)) { n_type = UNICODE_NFC; @@ -41566,22 +45228,96 @@ JS_FreeCString(ctx, form); JS_ThrowRangeError(ctx, "bad normalization form"); fail1: - js_free(ctx, buf); + JS_FreeValue(ctx, val); return JS_EXCEPTION; } JS_FreeCString(ctx, form); } - out_len = unicode_normalize(&out_buf, buf, buf_len, n_type, - ctx->rt, (DynBufReallocFunc *)js_realloc_rt); - js_free(ctx, buf); + out_len = js_string_normalize1(ctx, &out_buf, val, n_type); + JS_FreeValue(ctx, val); if (out_len < 0) return JS_EXCEPTION; val = JS_NewUTF32String(ctx, out_buf, out_len); js_free(ctx, out_buf); return val; } -#endif /* CONFIG_ALL_UNICODE */ + +/* return < 0, 0 or > 0 */ +static int js_UTF32_compare(const uint32_t *buf1, int buf1_len, + const uint32_t *buf2, int buf2_len) +{ + int i, len, c, res; + len = min_int(buf1_len, buf2_len); + for(i = 0; i < len; i++) { + /* Note: range is limited so a subtraction is valid */ + c = buf1i - buf2i; + if (c != 0) + return c; + } + if (buf1_len == buf2_len) + res = 0; + else if (buf1_len < buf2_len) + res = -1; + else + res = 1; + return res; +} + +static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue a, b; + int cmp, a_len, b_len; + uint32_t *a_buf, *b_buf; + + a = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(a)) + return JS_EXCEPTION; + b = JS_ToString(ctx, argv0); + if (JS_IsException(b)) { + JS_FreeValue(ctx, a); + return JS_EXCEPTION; + } + a_len = js_string_normalize1(ctx, &a_buf, a, UNICODE_NFC); + JS_FreeValue(ctx, a); + if (a_len < 0) { + JS_FreeValue(ctx, b); + return JS_EXCEPTION; + } + + b_len = js_string_normalize1(ctx, &b_buf, b, UNICODE_NFC); + JS_FreeValue(ctx, b); + if (b_len < 0) { + js_free(ctx, a_buf); + return JS_EXCEPTION; + } + cmp = js_UTF32_compare(a_buf, a_len, b_buf, b_len); + js_free(ctx, a_buf); + js_free(ctx, b_buf); + return JS_NewInt32(ctx, cmp); +} +#else /* CONFIG_ALL_UNICODE */ +static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue a, b; + int cmp; + + a = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(a)) + return JS_EXCEPTION; + b = JS_ToString(ctx, argv0); + if (JS_IsException(b)) { + JS_FreeValue(ctx, a); + return JS_EXCEPTION; + } + cmp = js_string_compare(ctx, JS_VALUE_GET_STRING(a), JS_VALUE_GET_STRING(b)); + JS_FreeValue(ctx, a); + JS_FreeValue(ctx, b); + return JS_NewInt32(ctx, cmp); +} +#endif /* !CONFIG_ALL_UNICODE */ /* also used for String.prototype.valueOf */ static JSValue js_string_toString(JSContext *ctx, JSValueConst this_val, @@ -41665,7 +45401,7 @@ if (c <= 0xffff) { return js_new_string_char(ctx, c); } else { - return js_new_string16(ctx, p->u.str16 + start, 2); + return js_new_string16_len(ctx, p->u.str16 + start, 2); } } @@ -41753,10 +45489,13 @@ static const JSCFunctionListEntry js_string_proto_funcs = { JS_PROP_INT32_DEF("length", 0, JS_PROP_CONFIGURABLE ), + JS_CFUNC_MAGIC_DEF("at", 1, js_string_charAt, 1 ), JS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt ), - JS_CFUNC_DEF("charAt", 1, js_string_charAt ), + JS_CFUNC_MAGIC_DEF("charAt", 1, js_string_charAt, 0 ), JS_CFUNC_DEF("concat", 1, js_string_concat ), JS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt ), + JS_CFUNC_DEF("isWellFormed", 0, js_string_isWellFormed ), + JS_CFUNC_DEF("toWellFormed", 0, js_string_toWellFormed ), JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), JS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0 ), @@ -41781,8 +45520,6 @@ JS_ALIAS_DEF("trimLeft", "trimStart" ), JS_CFUNC_DEF("toString", 0, js_string_toString ), JS_CFUNC_DEF("valueOf", 0, js_string_toString ), - JS_CFUNC_DEF("__quote", 1, js_string___quote ), - JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ), JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ), @@ -41809,18 +45546,17 @@ JS_PROP_STRING_DEF("Symbol.toStringTag", "String Iterator", JS_PROP_CONFIGURABLE ), }; -#ifdef CONFIG_ALL_UNICODE static const JSCFunctionListEntry js_string_proto_normalize = { +#ifdef CONFIG_ALL_UNICODE JS_CFUNC_DEF("normalize", 0, js_string_normalize ), -}; #endif + JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ), +}; -void JS_AddIntrinsicStringNormalize(JSContext *ctx) +int JS_AddIntrinsicStringNormalize(JSContext *ctx) { -#ifdef CONFIG_ALL_UNICODE - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_STRING, js_string_proto_normalize, - countof(js_string_proto_normalize)); -#endif + return JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_STRING, js_string_proto_normalize, + countof(js_string_proto_normalize)); } /* Math */ @@ -41973,6 +45709,11 @@ return JS_NewFloat64(ctx, r); } +static double js_math_f16round(double a) +{ + return fromfp16(tofp16(a)); +} + static double js_math_fround(double a) { return (float)a; @@ -41981,14 +45722,16 @@ static JSValue js_math_imul(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - int a, b; + uint32_t a, b, c; + int32_t d; - if (JS_ToInt32(ctx, &a, argv0)) + if (JS_ToUint32(ctx, &a, argv0)) return JS_EXCEPTION; - if (JS_ToInt32(ctx, &b, argv1)) + if (JS_ToUint32(ctx, &b, argv1)) return JS_EXCEPTION; - /* purposely ignoring overflow */ - return JS_NewInt32(ctx, a * b); + c = a * b; + memcpy(&d, &c, sizeof(d)); + return JS_NewInt32(ctx, d); } static JSValue js_math_clz32(JSContext *ctx, JSValueConst this_val, @@ -42005,6 +45748,261 @@ return JS_NewInt32(ctx, r); } +typedef enum { + SUM_PRECISE_STATE_FINITE, + SUM_PRECISE_STATE_INFINITY, + SUM_PRECISE_STATE_MINUS_INFINITY, /* must be after SUM_PRECISE_STATE_INFINITY */ + SUM_PRECISE_STATE_NAN, /* must be after SUM_PRECISE_STATE_MINUS_INFINITY */ +} SumPreciseStateEnum; + +#define SP_LIMB_BITS 56 +#define SP_RND_BITS (SP_LIMB_BITS - 53) +/* we add one extra limb to avoid having to test for overflows during the sum */ +#define SUM_PRECISE_ACC_LEN 39 + +#define SUM_PRECISE_COUNTER_INIT 250 + +typedef struct { + SumPreciseStateEnum state; + uint32_t counter; + int n_limbs; /* 'acc' contains n_limbs and is not necessarily + accn_limb - 1 may be 0. 0 indicates minus zero + result when state = SUM_PRECISE_STATE_FINITE */ + int64_t accSUM_PRECISE_ACC_LEN; +} SumPreciseState; + +static void sum_precise_init(SumPreciseState *s) +{ + memset(s->acc, 0, sizeof(s->acc)); + s->state = SUM_PRECISE_STATE_FINITE; + s->counter = SUM_PRECISE_COUNTER_INIT; + s->n_limbs = 0; +} + +static void sum_precise_renorm(SumPreciseState *s) +{ + int64_t v, carry; + int i; + + carry = 0; + for(i = 0; i < s->n_limbs; i++) { + v = s->acci + carry; + s->acci = v & (((uint64_t)1 << SP_LIMB_BITS) - 1); + carry = v >> SP_LIMB_BITS; + } + /* we add a failsafe but it should be never reached in a + reasonnable amount of time */ + if (carry != 0 && s->n_limbs < SUM_PRECISE_ACC_LEN) + s->accs->n_limbs++ = carry; +} + +static void sum_precise_add(SumPreciseState *s, double d) +{ + uint64_t a, m, a0, a1; + int sgn, e, p; + unsigned int shift; + + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & ((1 << 11) - 1); + m = a & (((uint64_t)1 << 52) - 1); + if (unlikely(e == 2047)) { + if (m == 0) { + /* +/- infinity */ + if (s->state == SUM_PRECISE_STATE_NAN || + (s->state == SUM_PRECISE_STATE_MINUS_INFINITY && !sgn) || + (s->state == SUM_PRECISE_STATE_INFINITY && sgn)) { + s->state = SUM_PRECISE_STATE_NAN; + } else { + s->state = SUM_PRECISE_STATE_INFINITY + sgn; + } + } else { + /* NaN */ + s->state = SUM_PRECISE_STATE_NAN; + } + } else if (e == 0) { + if (likely(m == 0)) { + /* zero */ + if (s->n_limbs == 0 && !sgn) + s->n_limbs = 1; + } else { + /* subnormal */ + p = 0; + shift = 0; + goto add; + } + } else { + /* Note: we sum even if state != SUM_PRECISE_STATE_FINITE to + avoid tests */ + m |= (uint64_t)1 << 52; + shift = e - 1; + /* 'p' is the position of a0 in acc. The division is normally + implementation as a multiplication by the compiler. */ + p = shift / SP_LIMB_BITS; + shift %= SP_LIMB_BITS; + add: + a0 = (m << shift) & (((uint64_t)1 << SP_LIMB_BITS) - 1); + a1 = m >> (SP_LIMB_BITS - shift); + if (!sgn) { + s->accp += a0; + s->accp + 1 += a1; + } else { + s->accp -= a0; + s->accp + 1 -= a1; + } + s->n_limbs = max_int(s->n_limbs, p + 2); + + if (unlikely(--s->counter == 0)) { + s->counter = SUM_PRECISE_COUNTER_INIT; + sum_precise_renorm(s); + } + } +} + +static double sum_precise_get_result(SumPreciseState *s) +{ + int n, shift, e, p, is_neg; + uint64_t m, addend; + + if (s->state != SUM_PRECISE_STATE_FINITE) { + switch(s->state) { + default: + case SUM_PRECISE_STATE_INFINITY: + return INFINITY; + case SUM_PRECISE_STATE_MINUS_INFINITY: + return -INFINITY; + case SUM_PRECISE_STATE_NAN: + return NAN; + } + } + + sum_precise_renorm(s); + + /* extract the sign and absolute value */ +#if 0 + { + int i; + printf("len=%d:", s->n_limbs); + for(i = s->n_limbs - 1; i >= 0; i--) + printf(" %014lx", s->acci); + printf("\n"); + } +#endif + n = s->n_limbs; + /* minus zero result */ + if (n == 0) + return -0.0; + + /* normalize */ + while (n > 0 && s->accn - 1 == 0) + n--; + /* zero result. The spec tells it is always positive in the finite case */ + if (n == 0) + return 0.0; + is_neg = (s->accn - 1 < 0); + if (is_neg) { + uint64_t v, carry; + int i; + /* negate */ + /* XXX: do it only when needed */ + carry = 1; + for(i = 0; i < n - 1; i++) { + v = (((uint64_t)1 << SP_LIMB_BITS) - 1) - s->acci + carry; + carry = v >> SP_LIMB_BITS; + s->acci = v & (((uint64_t)1 << SP_LIMB_BITS) - 1); + } + s->accn - 1 = -s->accn - 1 + carry - 1; + while (n > 1 && s->accn - 1 == 0) + n--; + } + /* subnormal case */ + if (n == 1 && s->acc0 < ((uint64_t)1 << 52)) + return uint64_as_float64(((uint64_t)is_neg << 63) | s->acc0); + /* normal case */ + e = n * SP_LIMB_BITS; + p = n - 1; + m = s->accp; + shift = clz64(m) - (64 - SP_LIMB_BITS); + e = e - shift - 52; + if (shift != 0) { + m <<= shift; + if (p > 0) { + int shift1; + uint64_t nz; + p--; + shift1 = SP_LIMB_BITS - shift; + nz = s->accp & (((uint64_t)1 << shift1) - 1); + m = m | (s->accp >> shift1) | (nz != 0); + } + } + if ((m & ((1 << SP_RND_BITS) - 1)) == (1 << (SP_RND_BITS - 1))) { + /* see if the LSB part is non zero for the final rounding */ + while (p > 0) { + p--; + if (s->accp != 0) { + m |= 1; + break; + } + } + } + /* rounding to nearest with ties to even */ + addend = (1 << (SP_RND_BITS - 1)) - 1 + ((m >> SP_RND_BITS) & 1); + m = (m + addend) >> SP_RND_BITS; + /* handle overflow in the rounding */ + if (m == ((uint64_t)1 << 53)) + e++; + if (unlikely(e >= 2047)) { + /* infinity */ + return uint64_as_float64(((uint64_t)is_neg << 63) | ((uint64_t)2047 << 52)); + } else { + m &= (((uint64_t)1 << 52) - 1); + return uint64_as_float64(((uint64_t)is_neg << 63) | ((uint64_t)e << 52) | m); + } +} + +static JSValue js_math_sumPrecise(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue iter, next, item, ret; + uint32_t tag; + int done; + double d; + SumPreciseState s_s, *s = &s_s; + + iter = JS_GetIterator(ctx, argv0, FALSE); + if (JS_IsException(iter)) + return JS_EXCEPTION; + ret = JS_EXCEPTION; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto fail; + sum_precise_init(s); + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) + break; + tag = JS_VALUE_GET_TAG(item); + if (JS_TAG_IS_FLOAT64(tag)) { + d = JS_VALUE_GET_FLOAT64(item); + } else if (tag == JS_TAG_INT) { + d = JS_VALUE_GET_INT(item); + } else { + JS_FreeValue(ctx, item); + JS_ThrowTypeError(ctx, "not a number"); + JS_IteratorClose(ctx, iter, TRUE); + goto fail; + } + sum_precise_add(s, d); + } + ret = __JS_NewFloat64(ctx, sum_precise_get_result(s)); +fail: + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return ret; +} + /* xorshift* random number generator by Marsaglia */ static uint64_t xorshift64star(uint64_t *pstate) { @@ -42101,9 +46099,11 @@ JS_CFUNC_SPECIAL_DEF("cbrt", 1, f_f, cbrt ), JS_CFUNC_DEF("hypot", 2, js_math_hypot ), JS_CFUNC_DEF("random", 0, js_math_random ), + JS_CFUNC_SPECIAL_DEF("f16round", 1, f_f, js_math_f16round ), JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ), JS_CFUNC_DEF("imul", 2, js_math_imul ), JS_CFUNC_DEF("clz32", 1, js_math_clz32 ), + JS_CFUNC_DEF("sumPrecise", 1, js_math_sumPrecise ), JS_PROP_STRING_DEF("Symbol.toStringTag", "Math", JS_PROP_CONFIGURABLE ), JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ), JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ), @@ -42121,45 +46121,12 @@ /* Date */ -#if 0 -/* OS dependent: return the UTC time in ms since 1970. */ -static JSValue js___date_now(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int64_t d; - struct timeval tv; - gettimeofday(&tv, NULL); - d = (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); - return JS_NewInt64(ctx, d); -} -#endif - -/* OS dependent: return the UTC time in microseconds since 1970. */ -static JSValue js___date_clock(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int64_t d; -#if defined(_MSC_VER) // FIXME: more precision - struct _timeb tb; - _ftime(&tb); - d = ( (int64_t)tb.time * 1000000) + (tb.millitm*1000); -#else - struct timeval tv; - gettimeofday(&tv, NULL); - d = (int64_t)tv.tv_sec * 1000000 + tv.tv_usec; -#endif - return JS_NewInt64(ctx, d); -} - /* OS dependent. d = argv0 is in ms from 1970. Return the difference between UTC time and local time 'd' in minutes */ -static int getTimezoneOffset(int64_t time) { -#if defined(_WIN32) - /* XXX: TODO */ - return 0; -#else +static int getTimezoneOffset(int64_t time) +{ time_t ti; - struct tm tm; + int res; time /= 1000; /* convert to seconds */ if (sizeof(time_t) == 4) { @@ -42183,9 +46150,31 @@ } } ti = time; - localtime_r(&ti, &tm); - return -tm.tm_gmtoff / 60; +#if defined(_WIN32) + { + struct tm *tm; + time_t gm_ti, loc_ti; + + tm = gmtime(&ti); + if (!tm) + return 0; + gm_ti = mktime(tm); + + tm = localtime(&ti); + if (!tm) + return 0; + loc_ti = mktime(tm); + + res = (gm_ti - loc_ti) / 60; + } +#else + { + struct tm tm; + localtime_r(&ti, &tm); + res = -tm.tm_gmtoff / 60; + } #endif + return res; } #if 0 @@ -42238,8 +46227,10 @@ { JSObject *p = JS_VALUE_GET_OBJ(val); JSRegExp *re = &p->u.regexp; - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->bytecode)); - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->pattern)); + if (re->bytecode != NULL) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->bytecode)); + if (re->pattern != NULL) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->pattern)); } /* create a string containing the RegExp bytecode */ @@ -42262,6 +46253,9 @@ /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */ for (i = 0; i < len; i++) { switch(stri) { + case 'd': + mask = LRE_FLAG_INDICES; + break; case 'g': mask = LRE_FLAG_GLOBAL; break; @@ -42275,7 +46269,10 @@ mask = LRE_FLAG_DOTALL; break; case 'u': - mask = LRE_FLAG_UTF16; + mask = LRE_FLAG_UNICODE; + break; + case 'v': + mask = LRE_FLAG_UNICODE_SETS; break; case 'y': mask = LRE_FLAG_STICKY; @@ -42286,14 +46283,20 @@ if ((re_flags & mask) != 0) { bad_flags: JS_FreeCString(ctx, str); - return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); + goto bad_flags1; } re_flags |= mask; } JS_FreeCString(ctx, str); } - str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & LRE_FLAG_UTF16)); + /* 'u' and 'v' cannot be both set */ + if ((re_flags & LRE_FLAG_UNICODE_SETS) && (re_flags & LRE_FLAG_UNICODE)) { + bad_flags1: + return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); + } + + str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS))); if (!str) return JS_EXCEPTION; re_bytecode_buf = lre_compile(&re_bytecode_len, error_msg, @@ -42304,37 +46307,34 @@ return JS_EXCEPTION; } - ret = js_new_string8(ctx, re_bytecode_buf, re_bytecode_len); + ret = js_new_string8_len(ctx, (const char *)re_bytecode_buf, re_bytecode_len); js_free(ctx, re_bytecode_buf); return ret; } -/* create a RegExp object from a string containing the RegExp bytecode - and the source pattern */ -static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, - JSValue pattern, JSValue bc) +/* set the RegExp fields */ +static JSValue js_regexp_set_internal(JSContext *ctx, + JSValue obj, + JSValue pattern, JSValue bc) { - JSValue obj; JSObject *p; JSRegExp *re; /* sanity check */ - if (JS_VALUE_GET_TAG(bc) != JS_TAG_STRING || - JS_VALUE_GET_TAG(pattern) != JS_TAG_STRING) { + if (unlikely(JS_VALUE_GET_TAG(bc) != JS_TAG_STRING || + JS_VALUE_GET_TAG(pattern) != JS_TAG_STRING)) { JS_ThrowTypeError(ctx, "string expected"); - fail: + JS_FreeValue(ctx, obj); JS_FreeValue(ctx, bc); JS_FreeValue(ctx, pattern); return JS_EXCEPTION; } - obj = js_create_from_ctor(ctx, ctor, JS_CLASS_REGEXP); - if (JS_IsException(obj)) - goto fail; p = JS_VALUE_GET_OBJ(obj); re = &p->u.regexp; re->pattern = JS_VALUE_GET_STRING(pattern); re->bytecode = JS_VALUE_GET_STRING(bc); + /* Note: cannot fail because the field is preallocated */ JS_DefinePropertyValue(ctx, obj, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0), JS_PROP_WRITABLE); return obj; @@ -42371,7 +46371,7 @@ static JSValue js_regexp_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - JSValue pattern, flags, bc, val; + JSValue pattern, flags, bc, val, obj = JS_UNDEFINED; JSValueConst pat, flags1; JSRegExp *re; int pat_is_regexp; @@ -42397,18 +46397,19 @@ } } re = js_get_regexp(ctx, pat, FALSE); + flags = JS_UNDEFINED; if (re) { pattern = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern)); if (JS_IsUndefined(flags1)) { bc = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode)); + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_REGEXP); + if (JS_IsException(obj)) + goto fail; goto no_compilation; } else { - flags = JS_ToString(ctx, flags1); - if (JS_IsException(flags)) - goto fail; + flags = JS_DupValue(ctx, flags1); } } else { - flags = JS_UNDEFINED; if (pat_is_regexp) { pattern = JS_GetProperty(ctx, pat, JS_ATOM_source); if (JS_IsException(pattern)) @@ -42434,15 +46435,19 @@ goto fail; } } + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_REGEXP); + if (JS_IsException(obj)) + goto fail; bc = js_compile_regexp(ctx, pattern, flags); if (JS_IsException(bc)) goto fail; JS_FreeValue(ctx, flags); no_compilation: - return js_regexp_constructor_internal(ctx, new_target, pattern, bc); + return js_regexp_set_internal(ctx, obj, pattern, bc); fail: JS_FreeValue(ctx, pattern); JS_FreeValue(ctx, flags); + JS_FreeValue(ctx, obj); return JS_EXCEPTION; } @@ -42532,7 +46537,7 @@ if (p->len == 0) { empty_regex: - return JS_NewString(ctx, "(?:)"); + return js_new_string8(ctx, "(?:)"); } string_buffer_init2(ctx, b, p->len, p->is_wide_char); @@ -42594,47 +46599,37 @@ } flags = lre_get_flags(re->bytecode->u.str8); - return JS_NewBool(ctx, (flags & mask) != 0); + return JS_NewBool(ctx, flags & mask); } +#define RE_FLAG_COUNT 8 + static JSValue js_regexp_get_flags(JSContext *ctx, JSValueConst this_val) { - char str8, *p = str; - int res; + char strRE_FLAG_COUNT, *p = str; + int res, i; + static const int flag_atomRE_FLAG_COUNT = { + JS_ATOM_hasIndices, + JS_ATOM_global, + JS_ATOM_ignoreCase, + JS_ATOM_multiline, + JS_ATOM_dotAll, + JS_ATOM_unicode, + JS_ATOM_unicodeSets, + JS_ATOM_sticky, + }; + static const char flag_charRE_FLAG_COUNT = { 'd', 'g', 'i', 'm', 's', 'u', 'v', 'y' }; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); - res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_global)); - if (res < 0) - goto exception; - if (res) - *p++ = 'g'; - res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "ignoreCase")); - if (res < 0) - goto exception; - if (res) - *p++ = 'i'; - res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "multiline")); - if (res < 0) - goto exception; - if (res) - *p++ = 'm'; - res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "dotAll")); - if (res < 0) - goto exception; - if (res) - *p++ = 's'; - res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_unicode)); - if (res < 0) - goto exception; - if (res) - *p++ = 'u'; - res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "sticky")); - if (res < 0) - goto exception; - if (res) - *p++ = 'y'; + for(i = 0; i < RE_FLAG_COUNT; i++) { + res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, flag_atomi)); + if (res < 0) + goto exception; + if (res) + *p++ = flag_chari; + } return JS_NewStringLen(ctx, str, p - str); exception: @@ -42666,12 +46661,20 @@ return JS_EXCEPTION; } -BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) +int lre_check_stack_overflow(void *opaque, size_t alloca_size) { JSContext *ctx = opaque; return js_check_stack_overflow(ctx->rt, alloca_size); } +int lre_check_timeout(void *opaque) +{ + JSContext *ctx = opaque; + JSRuntime *rt = ctx->rt; + return (rt->interrupt_handler && + rt->interrupt_handler(rt, rt->interrupt_opaque)); +} + void *lre_realloc(void *opaque, void *ptr, size_t size) { JSContext *ctx = opaque; @@ -42679,30 +46682,89 @@ return js_realloc_rt(ctx->rt, ptr, size); } +static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue str; + StringBuffer b_s, *b = &b_s; + JSString *p; + uint32_t c, i; + char s16; + + if (!JS_IsString(argv0)) + return JS_ThrowTypeError(ctx, "not a string"); + str = JS_ToString(ctx, argv0); /* must call it to linearlize ropes */ + if (JS_IsException(str)) + return JS_EXCEPTION; + p = JS_VALUE_GET_STRING(str); + string_buffer_init2(ctx, b, 0, p->is_wide_char); + for (i = 0; i < p->len; i++) { + c = string_get(p, i); + if (c < 33) { + if (c >= 9 && c <= 13) { + string_buffer_putc8(b, '\\'); + string_buffer_putc8(b, "tnvfr"c - 9); + } else { + goto hex2; + } + } else if (c < 128) { + if ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z')) { + if (i == 0) + goto hex2; + } else if (strchr(",-=<>#&!%:;@~'`\"", c)) { + goto hex2; + } else if (c != '_') { + string_buffer_putc8(b, '\\'); + } + string_buffer_putc8(b, c); + } else if (c < 256) { + hex2: + snprintf(s, sizeof(s), "\\x%02x", c); + string_buffer_puts8(b, s); + } else if (is_surrogate(c) || lre_is_space(c)) { + snprintf(s, sizeof(s), "\\u%04x", c); + string_buffer_puts8(b, s); + } else { + string_buffer_putc16(b, c); + } + } + JS_FreeValue(ctx, str); + return string_buffer_end(b); +} + static JSValue js_regexp_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); JSString *str; - JSValue str_val, obj, val, groups = JS_UNDEFINED; + JSValue t, ret, str_val, obj, val, groups; + JSValue indices, indices_groups; uint8_t *re_bytecode; - int ret; uint8_t **capture, *str_buf; - int capture_count, shift, i, re_flags; + int rc, capture_count, shift, i, re_flags; int64_t last_index; const char *group_name_ptr; if (!re) return JS_EXCEPTION; + str_val = JS_ToString(ctx, argv0); if (JS_IsException(str_val)) - return str_val; - val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); - if (JS_IsException(val) || - JS_ToLengthFree(ctx, &last_index, val)) { - JS_FreeValue(ctx, str_val); return JS_EXCEPTION; - } + + ret = JS_EXCEPTION; + obj = JS_NULL; + groups = JS_UNDEFINED; + indices = JS_UNDEFINED; + indices_groups = JS_UNDEFINED; + capture = NULL; + + val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); + if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) + goto fail; + re_bytecode = re->bytecode->u.str8; re_flags = lre_get_flags(re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { @@ -42710,36 +46772,35 @@ } str = JS_VALUE_GET_STRING(str_val); capture_count = lre_get_capture_count(re_bytecode); - capture = NULL; if (capture_count > 0) { capture = js_malloc(ctx, sizeof(capture0) * capture_count * 2); - if (!capture) { - JS_FreeValue(ctx, str_val); - return JS_EXCEPTION; - } + if (!capture) + goto fail; } shift = str->is_wide_char; str_buf = str->u.str8; if (last_index > str->len) { - ret = 2; + rc = 2; } else { - ret = lre_exec(capture, re_bytecode, - str_buf, last_index, str->len, - shift, ctx); - } - obj = JS_NULL; - if (ret != 1) { - if (ret >= 0) { - if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { + rc = lre_exec(capture, re_bytecode, + str_buf, last_index, str->len, + shift, ctx); + } + if (rc != 1) { + if (rc >= 0) { + if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto fail; } } else { - JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + if (rc == LRE_RET_TIMEOUT) { + JS_ThrowInterrupted(ctx); + } else { + JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + } goto fail; } - JS_FreeValue(ctx, str_val); } else { int prop_flags; if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { @@ -42757,52 +46818,128 @@ if (JS_IsException(groups)) goto fail; } + if (re_flags & LRE_FLAG_INDICES) { + indices = JS_NewArray(ctx); + if (JS_IsException(indices)) + goto fail; + if (group_name_ptr) { + indices_groups = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(indices_groups)) + goto fail; + } + } for(i = 0; i < capture_count; i++) { - int start, end; + const char *name = NULL; + uint8_t **match = &capture2 * i; + int start = -1; + int end = -1; JSValue val; - if (capture2 * i == NULL || - capture2 * i + 1 == NULL) { + + if (group_name_ptr && i > 0) { + if (*group_name_ptr) name = group_name_ptr; + group_name_ptr += strlen(group_name_ptr) + 1; + } + + if (match0 && match1) { + start = (match0 - str_buf) >> shift; + end = (match1 - str_buf) >> shift; + } + + if (!JS_IsUndefined(indices)) { val = JS_UNDEFINED; - } else { - start = (capture2 * i - str_buf) >> shift; - end = (capture2 * i + 1 - str_buf) >> shift; + if (start != -1) { + val = JS_NewArray(ctx); + if (JS_IsException(val)) + goto fail; + if (JS_DefinePropertyValueUint32(ctx, val, 0, + JS_NewInt32(ctx, start), + prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + if (JS_DefinePropertyValueUint32(ctx, val, 1, + JS_NewInt32(ctx, end), + prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + } + if (name && !JS_IsUndefined(indices_groups)) { + val = JS_DupValue(ctx, val); + if (JS_DefinePropertyValueStr(ctx, indices_groups, + name, val, prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + } + if (JS_DefinePropertyValueUint32(ctx, indices, i, val, + prop_flags) < 0) { + goto fail; + } + } + + val = JS_UNDEFINED; + if (start != -1) { val = js_sub_string(ctx, str, start, end); if (JS_IsException(val)) goto fail; } - if (group_name_ptr && i > 0) { - if (*group_name_ptr) { - if (JS_DefinePropertyValueStr(ctx, groups, group_name_ptr, - JS_DupValue(ctx, val), - prop_flags) < 0) { - JS_FreeValue(ctx, val); - goto fail; - } + + if (name) { + if (JS_DefinePropertyValueStr(ctx, groups, name, + JS_DupValue(ctx, val), + prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; } - group_name_ptr += strlen(group_name_ptr) + 1; } + if (JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags) < 0) goto fail; } - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups, - groups, prop_flags) < 0) + + t = JS_NewInt32(ctx, (capture0 - str_buf) >> shift); + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index, t, prop_flags) < 0) goto fail; - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index, - JS_NewInt32(ctx, (capture0 - str_buf) >> shift), prop_flags) < 0) + + t = str_val; + str_val = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, t, prop_flags) < 0) goto fail; - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, str_val, prop_flags) < 0) - goto fail1; + + t = groups; + groups = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups, + t, prop_flags) < 0) { + goto fail; + } + + if (!JS_IsUndefined(indices)) { + t = indices_groups; + indices_groups = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, indices, JS_ATOM_groups, + t, prop_flags) < 0) { + goto fail; + } + t = indices; + indices = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_indices, + t, prop_flags) < 0) { + goto fail; + } + } } - js_free(ctx, capture); - return obj; + ret = obj; + obj = JS_UNDEFINED; fail: - JS_FreeValue(ctx, groups); + JS_FreeValue(ctx, indices_groups); + JS_FreeValue(ctx, indices); JS_FreeValue(ctx, str_val); -fail1: + JS_FreeValue(ctx, groups); JS_FreeValue(ctx, obj); js_free(ctx, capture); - return JS_EXCEPTION; + return ret; } /* delete portions of a string that match a given regex */ @@ -42861,7 +46998,11 @@ goto fail; } } else { - JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + if (ret == LRE_RET_TIMEOUT) { + JS_ThrowInterrupted(ctx); + } else { + JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + } goto fail; } break; @@ -42881,7 +47022,7 @@ break; } if (end == start) { - if (!(re_flags & LRE_FLAG_UTF16) || (unsigned)end >= str->len || !str->is_wide_char) { + if (!(re_flags & LRE_FLAG_UNICODE) || (unsigned)end >= str->len || !str->is_wide_char) { end++; } else { string_getc(str, &end); @@ -42954,7 +47095,7 @@ { // Symbol.match(str) JSValueConst rx = this_val; - JSValue A, S, result, matchStr; + JSValue A, S, flags, result, matchStr; int global, n, fullUnicode, isEmpty; JSString *p; @@ -42962,22 +47103,27 @@ return JS_ThrowTypeErrorNotAnObject(ctx); A = JS_UNDEFINED; + flags = JS_UNDEFINED; result = JS_UNDEFINED; matchStr = JS_UNDEFINED; S = JS_ToString(ctx, argv0); if (JS_IsException(S)) goto exception; - global = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_global)); - if (global < 0) + flags = JS_GetProperty(ctx, rx, JS_ATOM_flags); + if (JS_IsException(flags)) goto exception; + flags = JS_ToStringFree(ctx, flags); + if (JS_IsException(flags)) + goto exception; + p = JS_VALUE_GET_STRING(flags); + global = (-1 != string_indexof_char(p, 'g', 0)); if (!global) { A = JS_RegExpExec(ctx, rx, S); } else { - fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode)); - if (fullUnicode < 0) - goto exception; + fullUnicode = (string_indexof_char(p, 'u', 0) >= 0 || + string_indexof_char(p, 'v', 0) >= 0); if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto exception; @@ -42996,7 +47142,7 @@ if (JS_IsException(matchStr)) goto exception; isEmpty = JS_IsEmptyString(matchStr); - if (JS_SetPropertyInt64(ctx, A, n++, matchStr) < 0) + if (JS_DefinePropertyValueInt64(ctx, A, n++, matchStr, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; if (isEmpty) { int64_t thisIndex, nextIndex; @@ -43015,12 +47161,14 @@ } } JS_FreeValue(ctx, result); + JS_FreeValue(ctx, flags); JS_FreeValue(ctx, S); return A; exception: JS_FreeValue(ctx, A); JS_FreeValue(ctx, result); + JS_FreeValue(ctx, flags); JS_FreeValue(ctx, S); return JS_EXCEPTION; } @@ -43159,7 +47307,8 @@ it->iterated_string = S; strp = JS_VALUE_GET_STRING(flags); it->global = string_indexof_char(strp, 'g', 0) >= 0; - it->unicode = string_indexof_char(strp, 'u', 0) >= 0; + it->unicode = (string_indexof_char(strp, 'u', 0) >= 0 || + string_indexof_char(strp, 'v', 0) >= 0); it->done = FALSE; JS_SetOpaque(iter, it); @@ -43263,8 +47412,8 @@ // Symbol.replace(str, rep) JSValueConst rx = this_val, rep = argv1; JSValueConst args6; - JSValue str, rep_val, matched, tab, rep_str, namedCaptures, res; - JSString *sp, *rp; + JSValue flags, str, rep_val, matched, tab, rep_str, namedCaptures, res; + JSString *p, *sp, *rp; StringBuffer b_s, *b = &b_s; ValueBuffer v_b, *results = &v_b; int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode; @@ -43280,6 +47429,7 @@ rep_val = JS_UNDEFINED; matched = JS_UNDEFINED; tab = JS_UNDEFINED; + flags = JS_UNDEFINED; rep_str = JS_UNDEFINED; namedCaptures = JS_UNDEFINED; @@ -43296,14 +47446,20 @@ goto exception; rp = JS_VALUE_GET_STRING(rep_val); } - fullUnicode = 0; - is_global = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_global)); - if (is_global < 0) + + flags = JS_GetProperty(ctx, rx, JS_ATOM_flags); + if (JS_IsException(flags)) + goto exception; + flags = JS_ToStringFree(ctx, flags); + if (JS_IsException(flags)) goto exception; + p = JS_VALUE_GET_STRING(flags); + + fullUnicode = 0; + is_global = (-1 != string_indexof_char(p, 'g', 0)); if (is_global) { - fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode)); - if (fullUnicode < 0) - goto exception; + fullUnicode = (string_indexof_char(p, 'u', 0) >= 0 || + string_indexof_char(p, 'v', 0) >= 0); if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto exception; } @@ -43433,6 +47589,7 @@ value_buffer_free(results); JS_FreeValue(ctx, rep_val); JS_FreeValue(ctx, matched); + JS_FreeValue(ctx, flags); JS_FreeValue(ctx, tab); JS_FreeValue(ctx, rep_str); JS_FreeValue(ctx, namedCaptures); @@ -43528,7 +47685,8 @@ if (JS_IsException(flags)) goto exception; strp = JS_VALUE_GET_STRING(flags); - unicodeMatching = string_indexof_char(strp, 'u', 0) >= 0; + unicodeMatching = (string_indexof_char(strp, 'u', 0) >= 0 || + string_indexof_char(strp, 'v', 0) >= 0); if (string_indexof_char(strp, 'y', 0) < 0) { flags = JS_ConcatString3(ctx, "", flags, "y"); if (JS_IsException(flags)) @@ -43591,7 +47749,7 @@ if (js_get_length64(ctx, &numberOfCaptures, z)) goto exception; for(i = 1; i < numberOfCaptures; i++) { - sub = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, z, i)); + sub = JS_GetPropertyInt64(ctx, z, i); if (JS_IsException(sub)) goto exception; if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub, JS_PROP_C_W_E | JS_PROP_THROW) < 0) @@ -43625,6 +47783,7 @@ } static const JSCFunctionListEntry js_regexp_funcs = { + JS_CFUNC_DEF("escape", 1, js_regexp_escape ), JS_CGETSET_DEF("Symbol.species", js_get_this, NULL ), //JS_CFUNC_DEF("__RegExpExec", 2, js_regexp___RegExpExec ), //JS_CFUNC_DEF("__RegExpDelete", 2, js_regexp___RegExpDelete ), @@ -43633,12 +47792,14 @@ static const JSCFunctionListEntry js_regexp_proto_funcs = { JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ), JS_CGETSET_DEF("source", js_regexp_get_source, NULL ), - JS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, 1 ), - JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, 2 ), - JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, 4 ), - JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, 8 ), - JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, 16 ), - JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, 32 ), + JS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, LRE_FLAG_GLOBAL ), + JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, LRE_FLAG_IGNORECASE ), + JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, LRE_FLAG_MULTILINE ), + JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, LRE_FLAG_DOTALL ), + JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE ), + JS_CGETSET_MAGIC_DEF("unicodeSets", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE_SETS ), + JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, LRE_FLAG_STICKY ), + JS_CGETSET_MAGIC_DEF("hasIndices", js_regexp_get_flag, NULL, LRE_FLAG_INDICES ), JS_CFUNC_DEF("exec", 1, js_regexp_exec ), JS_CFUNC_DEF("compile", 2, js_regexp_compile ), JS_CFUNC_DEF("test", 1, js_regexp_test ), @@ -43662,25 +47823,29 @@ ctx->compile_regexp = js_compile_regexp; } -void JS_AddIntrinsicRegExp(JSContext *ctx) +int JS_AddIntrinsicRegExp(JSContext *ctx) { - JSValueConst obj; + JSValue obj; JS_AddIntrinsicRegExpCompiler(ctx); - ctx->class_protoJS_CLASS_REGEXP = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_REGEXP, js_regexp_proto_funcs, - countof(js_regexp_proto_funcs)); - obj = JS_NewGlobalCConstructor(ctx, "RegExp", js_regexp_constructor, 2, - ctx->class_protoJS_CLASS_REGEXP); - ctx->regexp_ctor = JS_DupValue(ctx, obj); - JS_SetPropertyFunctionList(ctx, obj, js_regexp_funcs, countof(js_regexp_funcs)); + obj = JS_NewCConstructor(ctx, JS_CLASS_REGEXP, "RegExp", + js_regexp_constructor, 2, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_regexp_funcs, countof(js_regexp_funcs), + js_regexp_proto_funcs, countof(js_regexp_proto_funcs), + 0); + if (JS_IsException(obj)) + return -1; + ctx->regexp_ctor = obj; ctx->class_protoJS_CLASS_REGEXP_STRING_ITERATOR = - JS_NewObjectProto(ctx, ctx->iterator_proto); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_REGEXP_STRING_ITERATOR, - js_regexp_string_iterator_proto_funcs, - countof(js_regexp_string_iterator_proto_funcs)); + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_regexp_string_iterator_proto_funcs, + countof(js_regexp_string_iterator_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_REGEXP_STRING_ITERATOR)) + return -1; + return 0; } /* JSON */ @@ -43796,9 +47961,15 @@ case TOK_IDENT: if (s->token.u.ident.atom == JS_ATOM_false || s->token.u.ident.atom == JS_ATOM_true) { - val = JS_NewBool(ctx, (s->token.u.ident.atom == JS_ATOM_true)); + val = JS_NewBool(ctx, s->token.u.ident.atom == JS_ATOM_true); } else if (s->token.u.ident.atom == JS_ATOM_null) { val = JS_NULL; + } else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) { + /* Note: json5 identifier handling is ambiguous e.g. is + '{ NaN: 1 }' a valid JSON5 production ? */ + val = JS_NewFloat64(s->ctx, NAN); + } else if (s->token.u.ident.atom == JS_ATOM_Infinity && s->ext_json) { + val = JS_NewFloat64(s->ctx, INFINITY); } else { goto def_token; } @@ -43808,7 +47979,7 @@ default: def_token: if (s->token.val == TOK_EOF) { - js_parse_error(s, "unexpected end of input"); + js_parse_error(s, "Unexpected end of JSON input"); } else { js_parse_error(s, "unexpected token: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); @@ -43903,7 +48074,7 @@ goto fail; } } - js_free_prop_enum(ctx, atoms, len); + JS_FreePropertyEnum(ctx, atoms, len); atoms = NULL; name_val = JS_AtomToValue(ctx, name); if (JS_IsException(name_val)) @@ -43915,7 +48086,7 @@ JS_FreeValue(ctx, val); return res; fail: - js_free_prop_enum(ctx, atoms, len); + JS_FreePropertyEnum(ctx, atoms, len); JS_FreeValue(ctx, val); return JS_EXCEPTION; } @@ -43963,10 +48134,72 @@ StringBuffer *b; } JSONStringifyContext; -static JSValue JS_ToQuotedStringFree(JSContext *ctx, JSValue val) { - JSValue r = JS_ToQuotedString(ctx, val); +static int JS_ToQuotedString(JSContext *ctx, StringBuffer *b, JSValueConst val1) +{ + JSValue val; + JSString *p; + int i; + uint32_t c; + char buf16; + + val = JS_ToStringCheckObject(ctx, val1); + if (JS_IsException(val)) + return -1; + p = JS_VALUE_GET_STRING(val); + + if (string_buffer_putc8(b, '\"')) + goto fail; + for(i = 0; i < p->len; ) { + c = string_getc(p, &i); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + if (string_buffer_putc8(b, '\\')) + goto fail; + if (string_buffer_putc8(b, c)) + goto fail; + break; + default: + if (c < 32 || is_surrogate(c)) { + snprintf(buf, sizeof(buf), "\\u%04x", c); + if (string_buffer_puts8(b, buf)) + goto fail; + } else { + if (string_buffer_putc(b, c)) + goto fail; + } + break; + } + } + if (string_buffer_putc8(b, '\"')) + goto fail; + JS_FreeValue(ctx, val); + return 0; + fail: + JS_FreeValue(ctx, val); + return -1; +} + +static int JS_ToQuotedStringFree(JSContext *ctx, StringBuffer *b, JSValue val) { + int ret = JS_ToQuotedString(ctx, b, val); JS_FreeValue(ctx, val); - return r; + return ret; } static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc, @@ -43975,24 +48208,22 @@ JSValue v; JSValueConst args2; - if (JS_IsObject(val) -#ifdef CONFIG_BIGNUM - || JS_IsBigInt(ctx, val) /* XXX: probably useless */ -#endif - ) { - JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON); - if (JS_IsException(f)) + /* check for object.toJSON method */ + /* ECMA specifies this is done only for Object and BigInt */ + if (JS_IsObject(val) || JS_IsBigInt(ctx, val)) { + JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON); + if (JS_IsException(f)) + goto exception; + if (JS_IsFunction(ctx, f)) { + v = JS_CallFree(ctx, f, val, 1, &key); + JS_FreeValue(ctx, val); + val = v; + if (JS_IsException(val)) goto exception; - if (JS_IsFunction(ctx, f)) { - v = JS_CallFree(ctx, f, val, 1, &key); - JS_FreeValue(ctx, val); - val = v; - if (JS_IsException(val)) - goto exception; - } else { - JS_FreeValue(ctx, f); - } + } else { + JS_FreeValue(ctx, f); } + } if (!JS_IsUndefined(jsc->replacer_func)) { args0 = key; @@ -44009,16 +48240,13 @@ if (JS_IsFunction(ctx, val)) break; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: case JS_TAG_INT: case JS_TAG_FLOAT64: -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: -#endif case JS_TAG_BOOL: case JS_TAG_NULL: -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: -#endif case JS_TAG_EXCEPTION: return val; default: @@ -44048,36 +48276,30 @@ tab = JS_UNDEFINED; prop = JS_UNDEFINED; - switch (JS_VALUE_GET_NORM_TAG(val)) { - case JS_TAG_OBJECT: + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + goto exception; + } + + if (JS_IsObject(val)) { p = JS_VALUE_GET_OBJ(val); cl = p->class_id; if (cl == JS_CLASS_STRING) { val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) goto exception; - val = JS_ToQuotedStringFree(ctx, val); - if (JS_IsException(val)) - goto exception; - return string_buffer_concat_value_free(jsc->b, val); + goto concat_primitive; } else if (cl == JS_CLASS_NUMBER) { val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) goto exception; - return string_buffer_concat_value_free(jsc->b, val); - } else if (cl == JS_CLASS_BOOLEAN) { - ret = string_buffer_concat_value(jsc->b, p->u.object_data); - JS_FreeValue(ctx, val); - return ret; - } -#ifdef CONFIG_BIGNUM - else if (cl == JS_CLASS_BIG_FLOAT) { - return string_buffer_concat_value_free(jsc->b, val); - } else if (cl == JS_CLASS_BIG_INT) { - JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify"); - goto exception; + goto concat_primitive; + } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) + { + /* This will thow the same error as for the primitive object */ + set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data)); + goto concat_primitive; } -#endif v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val); if (JS_IsException(v)) goto exception; @@ -44092,7 +48314,7 @@ sep = JS_ConcatString3(ctx, "\n", JS_DupValue(ctx, indent1), ""); if (JS_IsException(sep)) goto exception; - sep1 = JS_NewString(ctx, " "); + sep1 = js_new_string8(ctx, " "); if (JS_IsException(sep1)) goto exception; } else { @@ -44160,13 +48382,11 @@ if (!JS_IsUndefined(v)) { if (has_content) string_buffer_putc8(jsc->b, ','); - prop = JS_ToQuotedStringFree(ctx, prop); - if (JS_IsException(prop)) { + string_buffer_concat_value(jsc->b, sep); + if (JS_ToQuotedString(ctx, jsc->b, prop)) { JS_FreeValue(ctx, v); goto exception; } - string_buffer_concat_value(jsc->b, sep); - string_buffer_concat_value(jsc->b, prop); string_buffer_putc8(jsc->b, ':'); string_buffer_concat_value(jsc->b, sep1); if (js_json_to_str(ctx, jsc, val, v, indent1)) @@ -44174,7 +48394,7 @@ has_content = TRUE; } } - if (has_content && JS_VALUE_GET_STRING(jsc->gap)->len != 0) { + if (has_content && !JS_IsEmptyString(jsc->gap)) { string_buffer_putc8(jsc->b, '\n'); string_buffer_concat_value(jsc->b, indent); } @@ -44189,29 +48409,27 @@ JS_FreeValue(ctx, indent1); JS_FreeValue(ctx, prop); return 0; + } + concat_primitive: + switch (JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_STRING: - val = JS_ToQuotedStringFree(ctx, val); - if (JS_IsException(val)) - goto exception; - goto concat_value; + case JS_TAG_STRING_ROPE: + return JS_ToQuotedStringFree(ctx, jsc->b, val); case JS_TAG_FLOAT64: if (!isfinite(JS_VALUE_GET_FLOAT64(val))) { val = JS_NULL; } goto concat_value; case JS_TAG_INT: -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: -#endif case JS_TAG_BOOL: case JS_TAG_NULL: concat_value: return string_buffer_concat_value_free(jsc->b, val); -#ifdef CONFIG_BIGNUM + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: - JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify"); + /* reject big numbers: use toJSON method to override */ + JS_ThrowTypeError(ctx, "Do not know how to serialize a BigInt"); goto exception; -#endif default: JS_FreeValue(ctx, val); return 0; @@ -44317,7 +48535,7 @@ int n; if (JS_ToInt32Clamp(ctx, &n, space, 0, 10, 0)) goto exception; - jsc->gap = JS_NewStringLen(ctx, " ", n); + jsc->gap = js_new_string8_len(ctx, " ", n); } else if (JS_IsString(space)) { JSString *p = JS_VALUE_GET_STRING(space); jsc->gap = js_sub_string(ctx, p, 0, min_int(p->len, 10)); @@ -44378,10 +48596,10 @@ JS_OBJECT_DEF("JSON", js_json_funcs, countof(js_json_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; -void JS_AddIntrinsicJSON(JSContext *ctx) +int JS_AddIntrinsicJSON(JSContext *ctx) { /* add JSON as autoinit object */ - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_json_obj, countof(js_json_obj)); + return JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_json_obj, countof(js_json_obj)); } /* Reflect */ @@ -44501,8 +48719,8 @@ atom = JS_ValueToAtom(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) return JS_EXCEPTION; - ret = JS_SetPropertyGeneric(ctx, obj, atom, - JS_DupValue(ctx, val), receiver, 0); + ret = JS_SetPropertyInternal(ctx, obj, atom, + JS_DupValue(ctx, val), receiver, 0); JS_FreeAtom(ctx, atom); if (ret < 0) return JS_EXCEPTION; @@ -44605,7 +48823,7 @@ return s; } -static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj) +static JSValue js_proxy_get_prototype(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret, proto1; @@ -44635,7 +48853,7 @@ JS_FreeValue(ctx, ret); return JS_EXCEPTION; } - if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) { + if (!js_same_value(ctx, proto1, ret)) { JS_FreeValue(ctx, proto1); fail: JS_FreeValue(ctx, ret); @@ -44646,8 +48864,8 @@ return ret; } -static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, - JSValueConst proto_val, BOOL throw_flag) +static int js_proxy_set_prototype(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val) { JSProxyData *s; JSValue method, ret, proto1; @@ -44659,21 +48877,15 @@ if (!s) return -1; if (JS_IsUndefined(method)) - return JS_SetPrototypeInternal(ctx, s->target, proto_val, throw_flag); + return JS_SetPrototypeInternal(ctx, s->target, proto_val, FALSE); args0 = s->target; args1 = proto_val; ret = JS_CallFree(ctx, method, s->handler, 2, args); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); - if (!res) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "proxy: bad prototype"); - return -1; - } else { - return FALSE; - } - } + if (!res) + return FALSE; res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return -1; @@ -44681,7 +48893,7 @@ proto1 = JS_GetPrototype(ctx, s->target); if (JS_IsException(proto1)) return -1; - if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) { + if (!js_same_value(ctx, proto_val, proto1)) { JS_FreeValue(ctx, proto1); JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); return -1; @@ -44691,7 +48903,7 @@ return TRUE; } -static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj) +static int js_proxy_is_extensible(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; @@ -44717,7 +48929,7 @@ return res; } -static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj) +static int js_proxy_prevent_extensions(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; @@ -44817,8 +49029,10 @@ if (JS_IsException(ret)) return JS_EXCEPTION; res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); - if (res < 0) + if (res < 0) { + JS_FreeValue(ctx, ret); return JS_EXCEPTION; + } if (res) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { if (!js_same_value(ctx, desc.value, ret)) { @@ -44849,9 +49063,9 @@ if (!s) return -1; if (JS_IsUndefined(method)) { - return JS_SetPropertyGeneric(ctx, s->target, atom, - JS_DupValue(ctx, value), receiver, - flags); + return JS_SetPropertyInternal(ctx, s->target, atom, + JS_DupValue(ctx, value), receiver, + flags); } atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { @@ -44917,17 +49131,17 @@ } if (flags & JS_PROP_HAS_WRITABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, - JS_NewBool(ctx, (flags & JS_PROP_WRITABLE) != 0), + JS_NewBool(ctx, flags & JS_PROP_WRITABLE), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_ENUMERABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, - JS_NewBool(ctx, (flags & JS_PROP_ENUMERABLE) != 0), + JS_NewBool(ctx, flags & JS_PROP_ENUMERABLE), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_CONFIGURABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, - JS_NewBool(ctx, (flags & JS_PROP_CONFIGURABLE) != 0), + JS_NewBool(ctx, flags & JS_PROP_CONFIGURABLE), JS_PROP_C_W_E); } return ret; @@ -44990,6 +49204,14 @@ if (res < 0) return -1; + /* convert the result_desc.flags to property flags */ + if (result_desc.flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + result_desc.flags |= JS_PROP_GETSET; + } else { + result_desc.flags |= JS_PROP_NORMAL; + } + result_desc.flags &= (JS_PROP_C_W_E | JS_PROP_TMASK); + if (target_desc_ret) { /* convert result_desc.flags to defineProperty flags */ flags1 = result_desc.flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; @@ -45088,13 +49310,11 @@ if (!p->extensible || setting_not_configurable) goto fail; } else { - if (!check_define_prop_flags(desc.flags, flags) || - ((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable)) { + if (!check_define_prop_flags(desc.flags, flags)) goto fail1; - } - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == - JS_PROP_GETSET) { + /* do the missing check from check_define_prop_flags() */ + if (!(desc.flags & JS_PROP_CONFIGURABLE)) { + if ((desc.flags & JS_PROP_TMASK) == JS_PROP_GETSET) { if ((flags & JS_PROP_HAS_GET) && !js_same_value(ctx, getter, desc.getter)) { goto fail1; @@ -45103,27 +49323,26 @@ !js_same_value(ctx, setter, desc.setter)) { goto fail1; } - } - } else if (flags & JS_PROP_HAS_VALUE) { - if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == - JS_PROP_WRITABLE && !(flags & JS_PROP_WRITABLE)) { - /* missing-proxy-check feature */ - goto fail1; - } else if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && - !js_same_value(ctx, val, desc.value)) { - goto fail1; + } else if (!(desc.flags & JS_PROP_WRITABLE)) { + if ((flags & JS_PROP_HAS_VALUE) && + !js_same_value(ctx, val, desc.value)) { + goto fail1; + } } } - if (flags & JS_PROP_HAS_WRITABLE) { - if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | - JS_PROP_WRITABLE)) == JS_PROP_WRITABLE) { - /* proxy-missing-checks */ - fail1: - js_free_desc(ctx, &desc); - fail: - JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty"); - return -1; - } + + /* additional checks */ + if ((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable) + goto fail1; + + if ((desc.flags & JS_PROP_TMASK) != JS_PROP_GETSET && + (desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == JS_PROP_WRITABLE && + (flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { + fail1: + js_free_desc(ctx, &desc); + fail: + JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty"); + return -1; } js_free_desc(ctx, &desc); } @@ -45298,14 +49517,14 @@ } } - js_free_prop_enum(ctx, tab2, len2); + JS_FreePropertyEnum(ctx, tab2, len2); JS_FreeValue(ctx, prop_array); *ptab = tab; *plen = len; return 0; fail: - js_free_prop_enum(ctx, tab2, len2); - js_free_prop_enum(ctx, tab, len); + JS_FreePropertyEnum(ctx, tab2, len2); + JS_FreePropertyEnum(ctx, tab, len); JS_FreeValue(ctx, prop_array); return -1; } @@ -45379,16 +49598,35 @@ return ret; } -static int js_proxy_isArray(JSContext *ctx, JSValueConst obj) -{ - JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); - if (!s) - return FALSE; - if (s->is_revoked) { - JS_ThrowTypeErrorRevokedProxy(ctx); - return -1; +/* `js_resolve_proxy`: resolve the proxy chain + `*pval` is updated with to ultimate proxy target + `throw_exception` controls whether exceptions are thown or not + - return -1 in case of error + - otherwise return 0 + */ +static int js_resolve_proxy(JSContext *ctx, JSValueConst *pval, BOOL throw_exception) { + int depth = 0; + JSObject *p; + JSProxyData *s; + + while (JS_VALUE_GET_TAG(*pval) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(*pval); + if (p->class_id != JS_CLASS_PROXY) + break; + if (depth++ > 1000) { + if (throw_exception) + JS_ThrowStackOverflow(ctx); + return -1; + } + s = p->u.opaque; + if (s->is_revoked) { + if (throw_exception) + JS_ThrowTypeErrorRevokedProxy(ctx); + return -1; + } + *pval = s->target; } - return JS_IsArray(ctx, s->target); + return 0; } static const JSClassExoticMethods js_proxy_exotic_methods = { @@ -45399,6 +49637,10 @@ .has_property = js_proxy_has, .get_property = js_proxy_get, .set_property = js_proxy_set, + .get_prototype = js_proxy_get_prototype, + .set_prototype = js_proxy_set_prototype, + .is_extensible = js_proxy_is_extensible, + .prevent_extensions = js_proxy_prevent_extensions, }; static JSValue js_proxy_constructor(JSContext *ctx, JSValueConst this_val, @@ -45484,25 +49726,36 @@ { JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark }, /* JS_CLASS_PROXY */ }; -void JS_AddIntrinsicProxy(JSContext *ctx) +int JS_AddIntrinsicProxy(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; if (!JS_IsRegisteredClass(rt, JS_CLASS_PROXY)) { - init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY, - countof(js_proxy_class_def)); + if (init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY, + countof(js_proxy_class_def))) + return -1; rt->class_arrayJS_CLASS_PROXY.exotic = &js_proxy_exotic_methods; rt->class_arrayJS_CLASS_PROXY.call = js_proxy_call; } - obj1 = JS_NewCFunction2(ctx, js_proxy_constructor, "Proxy", 2, - JS_CFUNC_constructor, 0); + /* additional fields: name, length */ + obj1 = JS_NewCFunction3(ctx, js_proxy_constructor, "Proxy", 2, + JS_CFUNC_constructor, 0, + ctx->function_proto, countof(js_proxy_funcs) + 2); + if (JS_IsException(obj1)) + return -1; JS_SetConstructorBit(ctx, obj1, TRUE); - JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs, - countof(js_proxy_funcs)); - JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy", - obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs, + countof(js_proxy_funcs))) + goto fail; + if (JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy", + obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0) + goto fail; + return 0; + fail: + JS_FreeValue(ctx, obj1); + return -1; } /* Symbol */ @@ -45614,30 +49867,99 @@ static const JSCFunctionListEntry js_symbol_funcs = { JS_CFUNC_DEF("for", 1, js_symbol_for ), JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ), + JS_PROP_ATOM_DEF("toPrimitive", JS_ATOM_Symbol_toPrimitive, 0), + JS_PROP_ATOM_DEF("iterator", JS_ATOM_Symbol_iterator, 0), + JS_PROP_ATOM_DEF("match", JS_ATOM_Symbol_match, 0), + JS_PROP_ATOM_DEF("matchAll", JS_ATOM_Symbol_matchAll, 0), + JS_PROP_ATOM_DEF("replace", JS_ATOM_Symbol_replace, 0), + JS_PROP_ATOM_DEF("search", JS_ATOM_Symbol_search, 0), + JS_PROP_ATOM_DEF("split", JS_ATOM_Symbol_split, 0), + JS_PROP_ATOM_DEF("toStringTag", JS_ATOM_Symbol_toStringTag, 0), + JS_PROP_ATOM_DEF("isConcatSpreadable", JS_ATOM_Symbol_isConcatSpreadable, 0), + JS_PROP_ATOM_DEF("hasInstance", JS_ATOM_Symbol_hasInstance, 0), + JS_PROP_ATOM_DEF("species", JS_ATOM_Symbol_species, 0), + JS_PROP_ATOM_DEF("unscopables", JS_ATOM_Symbol_unscopables, 0), + JS_PROP_ATOM_DEF("asyncIterator", JS_ATOM_Symbol_asyncIterator, 0), }; /* Set/Map/WeakSet/WeakMap */ -typedef struct JSMapRecord { - int ref_count; /* used during enumeration to avoid freeing the record */ - BOOL empty; /* TRUE if the record is deleted */ - struct JSMapState *map; - struct JSMapRecord *next_weak_ref; - struct list_head link; - struct list_head hash_link; - JSValue key; - JSValue value; -} JSMapRecord; +static BOOL js_weakref_is_target(JSValueConst val) +{ + switch (JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + return TRUE; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + if (p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash != JS_ATOM_HASH_PRIVATE) + return TRUE; + } + break; + default: + break; + } + return FALSE; +} -typedef struct JSMapState { - BOOL is_weak; /* TRUE if WeakSet/WeakMap */ - struct list_head records; /* list of JSMapRecord.link */ - uint32_t record_count; - struct list_head *hash_table; - uint32_t hash_size; /* must be a power of two */ - uint32_t record_count_threshold; /* count at which a hash table - resize is needed */ -} JSMapState; +/* JS_UNDEFINED is considered as a live weakref */ +/* XXX: add a specific JSWeakRef value type ? */ +static BOOL js_weakref_is_live(JSValueConst val) +{ + int *pref_count; + if (JS_IsUndefined(val)) + return TRUE; + pref_count = JS_VALUE_GET_PTR(val); + return (*pref_count != 0); +} + +/* 'val' can be JS_UNDEFINED */ +static void js_weakref_free(JSRuntime *rt, JSValue val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + assert(p->weakref_count >= 1); + p->weakref_count--; + /* 'mark' is tested to avoid freeing the object structure when + it is about to be freed in a cycle or in + free_zero_refcount() */ + if (p->weakref_count == 0 && p->header.ref_count == 0 && + p->header.mark == 0) { + js_free_rt(rt, p); + } + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) { + JSString *p = JS_VALUE_GET_STRING(val); + assert(p->hash >= 1); + p->hash--; + if (p->hash == 0 && p->header.ref_count == 0) { + /* can remove the dummy structure */ + js_free_rt(rt, p); + } + } +} + +/* val must be an object, a symbol or undefined (see + js_weakref_is_target). */ +static JSValue js_weakref_new(JSContext *ctx, JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + p->weakref_count++; + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) { + JSString *p = JS_VALUE_GET_STRING(val); + /* XXX: could return an exception if too many references */ + assert(p->hash < JS_ATOM_HASH_MASK - 2); + p->hash++; + } else { + assert(JS_IsUndefined(val)); + } +#if defined(_MSC_VER) && !defined(__clang__) + return val; +#else + return (JSValue)val; +#endif +} #define MAGIC_SET (1 << 0) #define MAGIC_WEAK (1 << 1) @@ -45660,12 +49982,16 @@ goto fail; init_list_head(&s->records); s->is_weak = is_weak; + if (is_weak) { + s->weakref_header.weakref_type = JS_WEAKREF_TYPE_MAP; + list_add_tail(&s->weakref_header.link, &ctx->rt->weakref_list); + } JS_SetOpaque(obj, s); - s->hash_size = 1; - s->hash_table = js_malloc(ctx, sizeof(s->hash_table0) * s->hash_size); + s->hash_bits = 1; + s->hash_size = 1U << s->hash_bits; + s->hash_table = js_mallocz(ctx, sizeof(s->hash_table0) * s->hash_size); if (!s->hash_table) goto fail; - init_list_head(&s->hash_table0); s->record_count_threshold = 4; arr = JS_UNDEFINED; @@ -45694,15 +50020,13 @@ item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(item)) goto fail; - if (done) { - JS_FreeValue(ctx, item); + if (done) break; - } if (is_set) { ret = JS_Call(ctx, adder, obj, 1, (JSValueConst *)&item); if (JS_IsException(ret)) { JS_FreeValue(ctx, item); - goto fail; + goto fail_close; } } else { JSValue key, value; @@ -45727,7 +50051,7 @@ JS_FreeValue(ctx, item); JS_FreeValue(ctx, key); JS_FreeValue(ctx, value); - goto fail; + goto fail_close; } JS_FreeValue(ctx, key); JS_FreeValue(ctx, value); @@ -45740,11 +50064,10 @@ JS_FreeValue(ctx, adder); } return obj; + fail_close: + /* close the iterator object, preserving pending exception */ + JS_IteratorClose(ctx, iter, TRUE); fail: - if (JS_IsObject(iter)) { - /* close the iterator object, preserving pending exception */ - JS_IteratorClose(ctx, iter, TRUE); - } JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, adder); @@ -45753,7 +50076,7 @@ } /* XXX: could normalize strings to speed up comparison */ -static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key) +static JSValue map_normalize_key(JSContext *ctx, JSValue key) { uint32_t tag = JS_VALUE_GET_TAG(key); /* convert -0.0 to +0.0 */ @@ -45763,27 +50086,65 @@ return key; } +static JSValueConst map_normalize_key_const(JSContext *ctx, JSValueConst key) +{ +#if defined(_MSC_VER) && !defined(__clang__) + return map_normalize_key(ctx, key); +#else + return (JSValueConst)map_normalize_key(ctx, (JSValue)key); +#endif +} + +/* hash multipliers, same as the Linux kernel (see Knuth vol 3, + section 6.4, exercise 9) */ +#define HASH_MUL32 0x61C88647 +#define HASH_MUL64 UINT64_C(0x61C8864680B583EB) + +static uint32_t map_hash32(uint32_t a, int hash_bits) +{ + return (a * HASH_MUL32) >> (32 - hash_bits); +} + +static uint32_t map_hash64(uint64_t a, int hash_bits) +{ + return (a * HASH_MUL64) >> (64 - hash_bits); +} + +static uint32_t map_hash_pointer(uintptr_t a, int hash_bits) +{ +#ifdef JS_PTR64 + return map_hash64(a, hash_bits); +#else + return map_hash32(a, hash_bits); +#endif +} + /* XXX: better hash ? */ -static uint32_t map_hash_key(JSContext *ctx, JSValueConst key) +/* precondition: 1 <= hash_bits <= 32 */ +static uint32_t map_hash_key(JSValueConst key, int hash_bits) { uint32_t tag = JS_VALUE_GET_NORM_TAG(key); uint32_t h; double d; - JSFloat64Union u; + JSBigInt *p; + JSBigIntBuf buf; switch(tag) { case JS_TAG_BOOL: - h = JS_VALUE_GET_INT(key); + h = map_hash32(JS_VALUE_GET_INT(key) ^ JS_TAG_BOOL, hash_bits); break; case JS_TAG_STRING: - h = hash_string(JS_VALUE_GET_STRING(key), 0); + h = map_hash32(hash_string(JS_VALUE_GET_STRING(key), 0) ^ JS_TAG_STRING, hash_bits); + break; + case JS_TAG_STRING_ROPE: + h = map_hash32(hash_string_rope(key, 0) ^ JS_TAG_STRING, hash_bits); break; case JS_TAG_OBJECT: case JS_TAG_SYMBOL: - h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163; + h = map_hash_pointer((uintptr_t)JS_VALUE_GET_PTR(key) ^ tag, hash_bits); break; case JS_TAG_INT: - d = JS_VALUE_GET_INT(key) * 3163; + d = JS_VALUE_GET_INT(key); goto hash_float64; case JS_TAG_FLOAT64: d = JS_VALUE_GET_FLOAT64(key); @@ -45791,61 +50152,77 @@ if (isnan(d)) d = JS_FLOAT64_NAN; hash_float64: - u.d = d; - h = (u.u320 ^ u.u321) * 3163; + h = map_hash64(float64_as_uint64(d) ^ JS_TAG_FLOAT64, hash_bits); + break; + case JS_TAG_SHORT_BIG_INT: + p = js_bigint_set_short(&buf, key); + goto hash_bigint; + case JS_TAG_BIG_INT: + p = JS_VALUE_GET_PTR(key); + hash_bigint: + { + int i; + h = 1; + for(i = p->len - 1; i >= 0; i--) { + h = h * 263 + p->tabi; + } + /* the final step is necessary otherwise h mod n only + depends of p->tabi mod n */ + h = map_hash32(h ^ JS_TAG_BIG_INT, hash_bits); + } break; default: - h = 0; /* XXX: bignum support */ + h = 0; break; } - h ^= tag; return h; } static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, JSValueConst key) { - struct list_head *el; JSMapRecord *mr; uint32_t h; - h = map_hash_key(ctx, key) & (s->hash_size - 1); - list_for_each(el, &s->hash_tableh) { - mr = list_entry(el, JSMapRecord, hash_link); - if (js_same_value_zero(ctx, mr->key, key)) - return mr; + h = map_hash_key(key, s->hash_bits); + for(mr = s->hash_tableh; mr != NULL; mr = mr->hash_next) { + if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) { + /* cannot match */ + } else { + if (js_same_value_zero(ctx, mr->key, key)) + return mr; + } } return NULL; } static void map_hash_resize(JSContext *ctx, JSMapState *s) { - uint32_t new_hash_size, i, h; - size_t slack; - struct list_head *new_hash_table, *el; - JSMapRecord *mr; + uint32_t new_hash_size, h; + int new_hash_bits; + struct list_head *el; + JSMapRecord *mr, **new_hash_table; /* XXX: no reporting of memory allocation failure */ - if (s->hash_size == 1) - new_hash_size = 4; - else - new_hash_size = s->hash_size * 2; - new_hash_table = js_realloc2(ctx, s->hash_table, - sizeof(new_hash_table0) * new_hash_size, &slack); + new_hash_bits = min_int(s->hash_bits + 1, 31); + new_hash_size = 1U << new_hash_bits; + new_hash_table = js_realloc(ctx, s->hash_table, + sizeof(new_hash_table0) * new_hash_size); if (!new_hash_table) return; - new_hash_size += slack / sizeof(*new_hash_table); - for(i = 0; i < new_hash_size; i++) - init_list_head(&new_hash_tablei); + memset(new_hash_table, 0, sizeof(new_hash_table0) * new_hash_size); list_for_each(el, &s->records) { mr = list_entry(el, JSMapRecord, link); - if (!mr->empty) { - h = map_hash_key(ctx, mr->key) & (new_hash_size - 1); - list_add_tail(&mr->hash_link, &new_hash_tableh); + if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) { + } else { + h = map_hash_key(mr->key, new_hash_bits); + mr->hash_next = new_hash_tableh; + new_hash_tableh = mr; } } s->hash_table = new_hash_table; + s->hash_bits = new_hash_bits; s->hash_size = new_hash_size; s->record_count_threshold = new_hash_size * 2; } @@ -45860,23 +50237,15 @@ if (!mr) return NULL; mr->ref_count = 1; - mr->map = s; mr->empty = FALSE; if (s->is_weak) { - JSObject *p = JS_VALUE_GET_OBJ(key); - /* Add the weak reference */ - mr->next_weak_ref = p->first_weak_ref; - p->first_weak_ref = mr; + mr->key = js_weakref_new(ctx, key); } else { - JS_DupValue(ctx, key); + mr->key = JS_DupValue(ctx, key); } -#if defined(JS_VALUE_CANNOT_BE_CAST) - mr->key = key; -#else - mr->key = (JSValue)key; -#endif - h = map_hash_key(ctx, key) & (s->hash_size - 1); - list_add_tail(&mr->hash_link, &s->hash_tableh); + h = map_hash_key(key, s->hash_bits); + mr->hash_next = s->hash_tableh; + s->hash_tableh = mr; list_add_tail(&mr->link, &s->records); s->record_count++; if (s->record_count >= s->record_count_threshold) { @@ -45885,34 +50254,25 @@ return mr; } -/* Remove the weak reference from the object weak - reference list. we don't use a doubly linked list to - save space, assuming a given object has few weak - references to it */ -static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr) +static JSMapRecord *set_add_record(JSContext *ctx, JSMapState *s, + JSValueConst key) { - JSMapRecord **pmr, *mr1; - JSObject *p; - - p = JS_VALUE_GET_OBJ(mr->key); - pmr = &p->first_weak_ref; - for(;;) { - mr1 = *pmr; - assert(mr1 != NULL); - if (mr1 == mr) - break; - pmr = &mr1->next_weak_ref; - } - *pmr = mr1->next_weak_ref; + JSMapRecord *mr; + mr = map_add_record(ctx, s, key); + if (!mr) + return NULL; + mr->value = JS_UNDEFINED; + return mr; } -static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) +/* warning: the record must be removed from the hash table before */ +static void map_delete_record_internal(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) { if (mr->empty) return; - list_del(&mr->hash_link); + if (s->is_weak) { - delete_weak_ref(rt, mr); + js_weakref_free(rt, mr->key); } else { JS_FreeValueRT(rt, mr->key); } @@ -45939,28 +50299,36 @@ } } -static void reset_weak_ref(JSRuntime *rt, JSObject *p) +static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh) { - JSMapRecord *mr, *mr_next; + JSMapState *s = container_of(wh, JSMapState, weakref_header); + struct list_head *el, *el1; + JSMapRecord *mr1, **pmr; + uint32_t h; - /* first pass to remove the records from the WeakMap/WeakSet - lists */ - for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) { - assert(mr->map->is_weak); - assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ - list_del(&mr->hash_link); - list_del(&mr->link); - } + list_for_each_safe(el, el1, &s->records) { + JSMapRecord *mr = list_entry(el, JSMapRecord, link); + if (!js_weakref_is_live(mr->key)) { - /* second pass to free the values to avoid modifying the weak - reference list while traversing it. */ - for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) { - mr_next = mr->next_weak_ref; - JS_FreeValueRT(rt, mr->value); - js_free_rt(rt, mr); + /* even if key is not live it can be hashed as a pointer */ + h = map_hash_key(mr->key, s->hash_bits); + pmr = &s->hash_tableh; + for(;;) { + mr1 = *pmr; + /* the entry may already be removed from the hash + table if the map was resized */ + if (mr1 == NULL) + goto done; + if (mr1 == mr) + break; + pmr = &mr1->hash_next; + } + /* remove from the hash table */ + *pmr = mr1->hash_next; + done: + map_delete_record_internal(rt, s, mr); + } } - - p->first_weak_ref = NULL; /* fail safe */ } static JSValue js_map_set(JSContext *ctx, JSValueConst this_val, @@ -45972,9 +50340,9 @@ if (!s) return JS_EXCEPTION; - key = map_normalize_key(ctx, argv0); - if (s->is_weak && !JS_IsObject(key)) - return JS_ThrowTypeErrorNotAnObject(ctx); + key = map_normalize_key_const(ctx, argv0); + if (s->is_weak && !js_weakref_is_target(key)) + return JS_ThrowTypeError(ctx, "invalid value used as %s key", (magic & MAGIC_SET) ? "WeakSet" : "WeakMap"); if (magic & MAGIC_SET) value = JS_UNDEFINED; else @@ -46000,7 +50368,7 @@ if (!s) return JS_EXCEPTION; - key = map_normalize_key(ctx, argv0); + key = map_normalize_key_const(ctx, argv0); mr = map_find_record(ctx, s, key); if (!mr) return JS_UNDEFINED; @@ -46008,6 +50376,73 @@ return JS_DupValue(ctx, mr->value); } +/* return JS_TRUE or JS_FALSE */ +static JSValue map_delete_record(JSContext *ctx, JSMapState *s, JSValueConst key) +{ + JSMapRecord *mr, **pmr; + uint32_t h; + + key = map_normalize_key_const(ctx, key); + + h = map_hash_key(key, s->hash_bits); + pmr = &s->hash_tableh; + for(;;) { + mr = *pmr; + if (mr == NULL) + return JS_FALSE; + if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) { + /* not valid */ + } else { + if (js_same_value_zero(ctx, mr->key, key)) + break; + } + pmr = &mr->hash_next; + } + + /* remove from the hash table */ + *pmr = mr->hash_next; + + map_delete_record_internal(ctx->rt, s, mr); + return JS_TRUE; +} + +static JSValue js_map_getOrInsert(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + BOOL computed = magic & 1; + JSClassID class_id = magic >> 1; + JSMapState *s = JS_GetOpaque2(ctx, this_val, class_id); + JSMapRecord *mr; + JSValueConst key; + JSValue value; + + if (!s) + return JS_EXCEPTION; + if (computed && !JS_IsFunction(ctx, argv1)) + return JS_ThrowTypeError(ctx, "not a function"); + key = map_normalize_key_const(ctx, argv0); + if (s->is_weak && !js_weakref_is_target(key)) + return JS_ThrowTypeError(ctx, "invalid value used as WeakMap key"); + mr = map_find_record(ctx, s, key); + if (!mr) { + if (computed) { + value = JS_Call(ctx, argv1, JS_UNDEFINED, 1, &key); + if (JS_IsException(value)) + return JS_EXCEPTION; + map_delete_record(ctx, s, key); + } else { + value = JS_DupValue(ctx, argv1); + } + mr = map_add_record(ctx, s, key); + if (!mr) { + JS_FreeValue(ctx, value); + return JS_EXCEPTION; + } + mr->value = value; + } + return JS_DupValue(ctx, mr->value); +} + static JSValue js_map_has(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { @@ -46017,26 +50452,18 @@ if (!s) return JS_EXCEPTION; - key = map_normalize_key(ctx, argv0); + key = map_normalize_key_const(ctx, argv0); mr = map_find_record(ctx, s, key); - return JS_NewBool(ctx, (mr != NULL)); + return JS_NewBool(ctx, mr != NULL); } static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); - JSMapRecord *mr; - JSValueConst key; - if (!s) return JS_EXCEPTION; - key = map_normalize_key(ctx, argv0); - mr = map_find_record(ctx, s, key); - if (!mr) - return JS_FALSE; - map_delete_record(ctx->rt, s, mr); - return JS_TRUE; + return map_delete_record(ctx, s, argv0); } static JSValue js_map_clear(JSContext *ctx, JSValueConst this_val, @@ -46048,9 +50475,13 @@ if (!s) return JS_EXCEPTION; + + /* remove from the hash table */ + memset(s->hash_table, 0, sizeof(s->hash_table0) * s->hash_size); + list_for_each_safe(el, el1, &s->records) { mr = list_entry(el, JSMapRecord, link); - map_delete_record(ctx->rt, s, mr); + map_delete_record_internal(ctx->rt, s, mr); } return JS_UNDEFINED; } @@ -46115,6 +50546,123 @@ return JS_UNDEFINED; } +static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_map) +{ + JSValueConst cb, args2; + JSValue res, iter, next, groups, key, v, prop; + JSAtom key_atom = JS_ATOM_NULL; + int64_t idx; + BOOL done; + + // "is function?" check must be observed before argv0 is accessed + cb = argv1; + if (check_function(ctx, cb)) + return JS_EXCEPTION; + + iter = JS_GetIterator(ctx, argv0, /*is_async*/FALSE); + if (JS_IsException(iter)) + return JS_EXCEPTION; + + key = JS_UNDEFINED; + key_atom = JS_ATOM_NULL; + v = JS_UNDEFINED; + prop = JS_UNDEFINED; + groups = JS_UNDEFINED; + + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + + if (is_map) { + groups = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, 0); + } else { + groups = JS_NewObjectProto(ctx, JS_NULL); + } + if (JS_IsException(groups)) + goto exception; + + for (idx = 0; ; idx++) { + if (idx >= MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "too many elements"); + goto iterator_close_exception; + } + v = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(v)) + goto exception; + if (done) + break; // v is JS_UNDEFINED + + args0 = v; + args1 = JS_NewInt64(ctx, idx); + key = JS_Call(ctx, cb, ctx->global_obj, 2, args); + if (JS_IsException(key)) + goto iterator_close_exception; + + if (is_map) { + prop = js_map_get(ctx, groups, 1, (JSValueConst *)&key, 0); + } else { + key_atom = JS_ValueToAtom(ctx, key); + JS_FreeValue(ctx, key); + key = JS_UNDEFINED; + if (key_atom == JS_ATOM_NULL) + goto iterator_close_exception; + prop = JS_GetProperty(ctx, groups, key_atom); + } + if (JS_IsException(prop)) + goto exception; + + if (JS_IsUndefined(prop)) { + prop = JS_NewArray(ctx); + if (JS_IsException(prop)) + goto exception; + if (is_map) { + args0 = key; + args1 = prop; + res = js_map_set(ctx, groups, 2, args, 0); + if (JS_IsException(res)) + goto exception; + JS_FreeValue(ctx, res); + } else { + prop = JS_DupValue(ctx, prop); + if (JS_DefinePropertyValue(ctx, groups, key_atom, prop, + JS_PROP_C_W_E) < 0) { + goto exception; + } + } + } + res = js_array_push(ctx, prop, 1, (JSValueConst *)&v, /*unshift*/0); + if (JS_IsException(res)) + goto exception; + // res is an int64 + + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, key); + JS_FreeAtom(ctx, key_atom); + JS_FreeValue(ctx, v); + prop = JS_UNDEFINED; + key = JS_UNDEFINED; + key_atom = JS_ATOM_NULL; + v = JS_UNDEFINED; + } + + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return groups; + + iterator_close_exception: + JS_IteratorClose(ctx, iter, TRUE); + exception: + JS_FreeAtom(ctx, key_atom); + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, key); + JS_FreeValue(ctx, v); + JS_FreeValue(ctx, groups); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return JS_EXCEPTION; +} + static void js_map_finalizer(JSRuntime *rt, JSValue val) { JSObject *p; @@ -46131,7 +50679,7 @@ mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { if (s->is_weak) - delete_weak_ref(rt, mr); + js_weakref_free(rt, mr->key); else JS_FreeValueRT(rt, mr->key); JS_FreeValueRT(rt, mr->value); @@ -46139,6 +50687,9 @@ js_free_rt(rt, mr); } js_free_rt(rt, s->hash_table); + if (s->is_weak) { + list_del(&s->weakref_header.link); + } js_free_rt(rt, s); } } @@ -46294,13 +50845,572 @@ } } +static int get_set_record(JSContext *ctx, JSValueConst obj, + int64_t *psize, JSValue *phas, JSValue *pkeys) +{ + JSMapState *s; + int64_t size; + JSValue has = JS_UNDEFINED, keys = JS_UNDEFINED; + + s = JS_GetOpaque(obj, JS_CLASS_SET); + if (s) { + size = s->record_count; + } else { + JSValue v; + double d; + + v = JS_GetProperty(ctx, obj, JS_ATOM_size); + if (JS_IsException(v)) + goto exception; + if (JS_ToFloat64Free(ctx, &d, v) < 0) + goto exception; + if (isnan(d)) { + JS_ThrowTypeError(ctx, ".size is not a number"); + goto exception; + } + if (d < INT64_MIN) + size = INT64_MIN; + else if (d >= 0x8000000000000000ULL /*0x1p63*/) /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ + size = INT64_MAX; + else + size = (int64_t)d; + if (size < 0) { + JS_ThrowRangeError(ctx, ".size must be positive"); + goto exception; + } + } + + has = JS_GetProperty(ctx, obj, JS_ATOM_has); + if (JS_IsException(has)) + goto exception; + if (JS_IsUndefined(has)) { + JS_ThrowTypeError(ctx, ".has is undefined"); + goto exception; + } + if (!JS_IsFunction(ctx, has)) { + JS_ThrowTypeError(ctx, ".has is not a function"); + goto exception; + } + + keys = JS_GetProperty(ctx, obj, JS_ATOM_keys); + if (JS_IsException(keys)) + goto exception; + if (JS_IsUndefined(keys)) { + JS_ThrowTypeError(ctx, ".keys is undefined"); + goto exception; + } + if (!JS_IsFunction(ctx, keys)) { + JS_ThrowTypeError(ctx, ".keys is not a function"); + goto exception; + } + *psize = size; + *phas = has; + *pkeys = keys; + return 0; + + exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + *psize = 0; + *phas = JS_UNDEFINED; + *pkeys = JS_UNDEFINED; + return -1; +} + +/* copy 'this_val' in a new set without side effects */ +static JSValue js_copy_set(JSContext *ctx, JSValueConst this_val) +{ + JSValue newset; + JSMapState *s, *t; + struct list_head *el; + JSMapRecord *mr; + + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + return JS_EXCEPTION; + t = JS_GetOpaque(newset, JS_CLASS_SET); + + // can't clone this_val using js_map_constructor(), + // test262 mandates we don't call the .add method + list_for_each(el, &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (mr->empty) + continue; + if (!set_add_record(ctx, t, mr->key)) + goto exception; + } + return newset; + exception: + JS_FreeValue(ctx, newset); + return JS_EXCEPTION; +} + +static JSValue js_set_isDisjointFrom(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue item, iter, keys, has, next, rv, rval; + int done; + BOOL found; + JSMapState *s; + int64_t size; + int ok; + + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + rval = JS_EXCEPTION; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + goto exception; + if (s->record_count <= size) { + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + found = FALSE; + do { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv0, 1, (JSValueConst *)&item); + JS_FreeValue(ctx, item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok < 0) + goto exception; + found = (ok > 0); + } while (!found); + } else { + iter = JS_Call(ctx, keys, argv0, 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + found = FALSE; + for(;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + found = (NULL != map_find_record(ctx, s, item)); + JS_FreeValue(ctx, item); + if (found) { + JS_IteratorClose(ctx, iter, FALSE); + break; + } + } + } + rval = !found ? JS_TRUE : JS_FALSE; +exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return rval; +} + +static JSValue js_set_isSubsetOf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue item, iter, keys, has, next, rv, rval; + BOOL found; + JSMapState *s; + int64_t size; + int done, ok; + + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + rval = JS_EXCEPTION; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + goto exception; + found = FALSE; + if (s->record_count > size) + goto fini; + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + found = TRUE; + do { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv0, 1, (JSValueConst *)&item); + JS_FreeValue(ctx, item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok < 0) + goto exception; + found = (ok > 0); + } while (found); +fini: + rval = found ? JS_TRUE : JS_FALSE; +exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return rval; +} + +static JSValue js_set_isSupersetOf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue item, iter, keys, has, next, rval; + int done; + BOOL found; + JSMapState *s; + int64_t size; + + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + rval = JS_EXCEPTION; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + goto exception; + found = FALSE; + if (s->record_count < size) + goto fini; + iter = JS_Call(ctx, keys, argv0, 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + found = TRUE; + for(;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + found = (NULL != map_find_record(ctx, s, item)); + JS_FreeValue(ctx, item); + if (!found) { + JS_IteratorClose(ctx, iter, FALSE); + break; + } + } +fini: + rval = found ? JS_TRUE : JS_FALSE; +exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return rval; +} + +static JSValue js_set_intersection(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue newset, item, iter, keys, has, next, rv; + JSMapState *s, *t; + JSMapRecord *mr; + int64_t size; + int done, ok; + + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + newset = JS_UNDEFINED; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + goto exception; + if (s->record_count > size) { + iter = JS_Call(ctx, keys, argv0, 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + if (!map_find_record(ctx, s, item)) { + JS_FreeValue(ctx, item); + } else if (map_find_record(ctx, t, item)) { + JS_FreeValue(ctx, item); // no duplicates + } else { + mr = set_add_record(ctx, t, item); + JS_FreeValue(ctx, item); + if (!mr) + goto exception; + } + } + } else { + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv0, 1, (JSValueConst *)&item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok > 0) { + item = map_normalize_key(ctx, item); + if (map_find_record(ctx, t, item)) { + JS_FreeValue(ctx, item); // no duplicates + } else { + mr = set_add_record(ctx, t, item); + JS_FreeValue(ctx, item); + if (!mr) + goto exception; + } + } else { + JS_FreeValue(ctx, item); + if (ok < 0) + goto exception; + } + } + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return newset; +} + +static JSValue js_set_difference(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue newset, item, iter, keys, has, next, rv; + JSMapState *s, *t; + int64_t size; + int done; + int ok; + + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + newset = JS_UNDEFINED; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + goto exception; + + newset = js_copy_set(ctx, this_val); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + + if (s->record_count <= size) { + iter = js_create_map_iterator(ctx, newset, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + for (;;) { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv0, 1, (JSValueConst *)&item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok < 0) { + JS_FreeValue(ctx, item); + goto exception; + } + if (ok) { + map_delete_record(ctx, t, item); + } + JS_FreeValue(ctx, item); + } + } else { + iter = JS_Call(ctx, keys, argv0, 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + map_delete_record(ctx, t, item); + JS_FreeValue(ctx, item); + } + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return newset; +} + +static JSValue js_set_symmetricDifference(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue newset, item, iter, next, has, keys; + JSMapState *s, *t; + JSMapRecord *mr; + int64_t size; + int done; + BOOL present; + + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + return JS_EXCEPTION; + JS_FreeValue(ctx, has); + + next = JS_UNDEFINED; + newset = JS_UNDEFINED; + iter = JS_Call(ctx, keys, argv0, 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + newset = js_copy_set(ctx, this_val); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + // note the subtlety here: due to mutating iterators, it's + // possible for keys to disappear during iteration; test262 + // still expects us to maintain insertion order though, so + // we first check |this|, then |new|; |new| is a copy of |this| + // - if item exists in |this|, delete (if it exists) from |new| + // - if item misses in |this| and |new|, add to |new| + // - if item exists in |new| but misses in |this|, *don't* add it, + // mutating iterator erased it + item = map_normalize_key(ctx, item); + present = (NULL != map_find_record(ctx, s, item)); + mr = map_find_record(ctx, t, item); + if (present) { + map_delete_record(ctx, t, item); + JS_FreeValue(ctx, item); + } else if (mr) { + JS_FreeValue(ctx, item); + } else { + mr = set_add_record(ctx, t, item); + JS_FreeValue(ctx, item); + if (!mr) + goto exception; + } + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, next); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, keys); + return newset; +} + +static JSValue js_set_union(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue newset, item, iter, next, has, keys, rv; + JSMapState *s; + int64_t size; + int done; + + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + if (get_set_record(ctx, argv0, &size, &has, &keys) < 0) + return JS_EXCEPTION; + JS_FreeValue(ctx, has); + + next = JS_UNDEFINED; + newset = JS_UNDEFINED; + iter = JS_Call(ctx, keys, argv0, 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + + newset = js_copy_set(ctx, this_val); + if (JS_IsException(newset)) + goto exception; + + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = js_map_set(ctx, newset, 1, (JSValueConst *)&item, MAGIC_SET); + JS_FreeValue(ctx, item); + if (JS_IsException(rv)) + goto exception; + JS_FreeValue(ctx, rv); + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, next); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, keys); + return newset; +} + static const JSCFunctionListEntry js_map_funcs = { + JS_CFUNC_MAGIC_DEF("groupBy", 2, js_object_groupBy, 1 ), JS_CGETSET_DEF("Symbol.species", js_get_this, NULL ), }; static const JSCFunctionListEntry js_map_proto_funcs = { JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ), JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ), + JS_CFUNC_MAGIC_DEF("getOrInsert", 2, js_map_getOrInsert, + (JS_CLASS_MAP << 1) | /*computed*/FALSE ), + JS_CFUNC_MAGIC_DEF("getOrInsertComputed", 2, js_map_getOrInsert, + (JS_CLASS_MAP << 1) | /*computed*/TRUE ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ), JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ), @@ -46325,6 +51435,13 @@ JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ), JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ), + JS_CFUNC_DEF("isDisjointFrom", 1, js_set_isDisjointFrom ), + JS_CFUNC_DEF("isSubsetOf", 1, js_set_isSubsetOf ), + JS_CFUNC_DEF("isSupersetOf", 1, js_set_isSupersetOf ), + JS_CFUNC_DEF("intersection", 1, js_set_intersection ), + JS_CFUNC_DEF("difference", 1, js_set_difference ), + JS_CFUNC_DEF("symmetricDifference", 1, js_set_symmetricDifference ), + JS_CFUNC_DEF("union", 1, js_set_union ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ), JS_ALIAS_DEF("keys", "values" ), JS_ALIAS_DEF("Symbol.iterator", "values" ), @@ -46340,6 +51457,10 @@ static const JSCFunctionListEntry js_weak_map_proto_funcs = { JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ), + JS_CFUNC_MAGIC_DEF("getOrInsert", 2, js_map_getOrInsert, + (JS_CLASS_WEAKMAP << 1) | /*computed*/FALSE ), + JS_CFUNC_MAGIC_DEF("getOrInsertComputed", 2, js_map_getOrInsert, + (JS_CLASS_WEAKMAP << 1) | /*computed*/TRUE ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ), JS_PROP_STRING_DEF("Symbol.toStringTag", "WeakMap", JS_PROP_CONFIGURABLE ), @@ -46370,35 +51491,37 @@ countof(js_set_iterator_proto_funcs), }; -void JS_AddIntrinsicMapSet(JSContext *ctx) +int JS_AddIntrinsicMapSet(JSContext *ctx) { int i; JSValue obj1; char bufATOM_GET_STR_BUF_SIZE; for(i = 0; i < 4; i++) { + JSCFunctionType ft; const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Map + i); - ctx->class_protoJS_CLASS_MAP + i = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_MAP + i, - js_map_proto_funcs_ptri, - js_map_proto_funcs_counti); - obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0, - JS_CFUNC_constructor_magic, i); - if (i < 2) { - JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs, - countof(js_map_funcs)); - } - JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_protoJS_CLASS_MAP + i); + ft.constructor_magic = js_map_constructor; + obj1 = JS_NewCConstructor(ctx, JS_CLASS_MAP + i, name, + ft.generic, 0, JS_CFUNC_constructor_magic, i, + JS_UNDEFINED, + js_map_funcs, i < 2 ? countof(js_map_funcs) : 0, + js_map_proto_funcs_ptri, js_map_proto_funcs_counti, + 0); + if (JS_IsException(obj1)) + return -1; + JS_FreeValue(ctx, obj1); } for(i = 0; i < 2; i++) { ctx->class_protoJS_CLASS_MAP_ITERATOR + i = - JS_NewObjectProto(ctx, ctx->iterator_proto); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_MAP_ITERATOR + i, - js_map_proto_funcs_ptri + 4, - js_map_proto_funcs_counti + 4); + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_map_proto_funcs_ptri + 4, + js_map_proto_funcs_counti + 4); + if (JS_IsException(ctx->class_protoJS_CLASS_MAP_ITERATOR + i)) + return -1; } + return 0; } /* Generator */ @@ -46415,12 +51538,6 @@ /* Promise */ -typedef enum JSPromiseStateEnum { - JS_PROMISE_PENDING, - JS_PROMISE_FULFILLED, - JS_PROMISE_REJECTED, -} JSPromiseStateEnum; - typedef struct JSPromiseData { JSPromiseStateEnum promise_state; /* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */ @@ -46445,6 +51562,22 @@ JSValue handler; } JSPromiseReactionData; +JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise) +{ + JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); + if (!s) + return -1; + return s->promise_state; +} + +JSValue JS_PromiseResult(JSContext *ctx, JSValue promise) +{ + JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); + if (!s) + return JS_UNDEFINED; + return JS_DupValue(ctx, s->promise_result); +} + static int js_create_resolving_functions(JSContext *ctx, JSValue *args, JSValueConst promise); @@ -46672,8 +51805,8 @@ else resolution = JS_UNDEFINED; #ifdef DUMP_PROMISE - printf("js_promise_resolving_function_call: is_reject=%d resolution=", is_reject); - JS_DumpValue(ctx, resolution); + printf("js_promise_resolving_function_call: is_reject=%d ", is_reject); + JS_DumpValue(ctx, "resolution", resolution); printf("\n"); #endif if (is_reject || !JS_IsObject(resolution)) { @@ -46890,32 +52023,69 @@ return result_promise; } -#if 0 -static JSValue js_promise___newPromiseCapability(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv) +static JSValue js_promise_withResolvers(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) { JSValue result_promise, resolving_funcs2, obj; - JSValueConst ctor; - ctor = argv0; - if (!JS_IsObject(ctor)) + if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); - result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor); + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); if (JS_IsException(result_promise)) return result_promise; obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, resolving_funcs0); - JS_FreeValue(ctx, resolving_funcs1); - JS_FreeValue(ctx, result_promise); - return JS_EXCEPTION; + if (JS_IsException(obj)) + goto exception; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, + JS_PROP_C_W_E) < 0) { + goto exception; + } + result_promise = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs0, + JS_PROP_C_W_E) < 0) { + goto exception; + } + resolving_funcs0 = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs1, + JS_PROP_C_W_E) < 0) { + goto exception; } - JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, JS_PROP_C_W_E); - JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs0, JS_PROP_C_W_E); - JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs1, JS_PROP_C_W_E); return obj; +exception: + JS_FreeValue(ctx, resolving_funcs0); + JS_FreeValue(ctx, resolving_funcs1); + JS_FreeValue(ctx, result_promise); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_promise_try(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue result_promise, resolving_funcs2, ret, ret2; + BOOL is_reject = 0; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); + if (JS_IsException(result_promise)) + return result_promise; + ret = JS_Call(ctx, argv0, JS_UNDEFINED, argc - 1, argv + 1); + if (JS_IsException(ret)) { + is_reject = 1; + ret = JS_GetException(ctx); + } + ret2 = JS_Call(ctx, resolving_funcsis_reject, JS_UNDEFINED, 1, (JSValueConst *)&ret); + JS_FreeValue(ctx, resolving_funcs0); + JS_FreeValue(ctx, resolving_funcs1); + JS_FreeValue(ctx, ret); + if (JS_IsException(ret2)) { + JS_FreeValue(ctx, result_promise); + return ret2; + } + JS_FreeValue(ctx, ret2); + return result_promise; } -#endif static __exception int remainingElementsCount_add(JSContext *ctx, JSValueConst resolve_element_env, @@ -46967,7 +52137,7 @@ obj = JS_NewObject(ctx); if (JS_IsException(obj)) return JS_EXCEPTION; - str = JS_NewString(ctx, is_reject ? "rejected" : "fulfilled"); + str = js_new_string8(ctx, is_reject ? "rejected" : "fulfilled"); if (JS_IsException(str)) goto fail1; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_status, @@ -47408,8 +52578,9 @@ JS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, PROMISE_MAGIC_all ), JS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all, PROMISE_MAGIC_allSettled ), JS_CFUNC_MAGIC_DEF("any", 1, js_promise_all, PROMISE_MAGIC_any ), + JS_CFUNC_DEF("try", 1, js_promise_try ), JS_CFUNC_DEF("race", 1, js_promise_race ), - //JS_CFUNC_DEF("__newPromiseCapability", 1, js_promise___newPromiseCapability ), + JS_CFUNC_DEF("withResolvers", 0, js_promise_withResolvers ), JS_CGETSET_DEF("Symbol.species", js_get_this, NULL), }; @@ -47425,29 +52596,6 @@ JS_PROP_STRING_DEF("Symbol.toStringTag", "AsyncFunction", JS_PROP_CONFIGURABLE ), }; -static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, - int magic, JSValue *func_data) -{ - return js_create_iterator_result(ctx, JS_DupValue(ctx, argv0), - JS_ToBool(ctx, func_data0)); -} - -static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx, - BOOL done) -{ - JSValueConst func_data1; - -#if defined(JS_VALUE_CANNOT_BE_CAST) - func_data0 = JS_NewBool(ctx, done); -#else - func_data0 = (JSValueConst)JS_NewBool(ctx, done); -#endif - return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap, - 1, 0, 1, func_data); -} - /* AsyncIteratorPrototype */ static const JSCFunctionListEntry js_async_iterator_proto_funcs = { @@ -47509,6 +52657,45 @@ return async_iter; } +static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, JSValue *func_data) +{ + return js_create_iterator_result(ctx, JS_DupValue(ctx, argv0), + JS_ToBool(ctx, func_data0)); +} + +static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx, + BOOL done) +{ + JSValueConst func_data1; + +#if defined(_MSC_VER) && !defined(__clang__) + func_data0 = JS_NewBool(ctx, done); +#else + func_data0 = (JSValueConst)JS_NewBool(ctx, done); +#endif + return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap, + 1, 0, 1, func_data); +} + +static JSValue js_async_from_sync_iterator_close_wrap(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, JSValue *func_data) +{ + JS_Throw(ctx, JS_DupValue(ctx, argv0)); + JS_IteratorClose(ctx, func_data0, TRUE); + return JS_EXCEPTION; +} + +static JSValue js_async_from_sync_iterator_close_wrap_func_create(JSContext *ctx, JSValueConst sync_iter) +{ + return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_close_wrap, + 1, 0, 1, &sync_iter); +} + static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) @@ -47539,11 +52726,13 @@ if (magic == GEN_MAGIC_RETURN) { err = js_create_iterator_result(ctx, JS_DupValue(ctx, argv0), TRUE); is_reject = 0; + goto done_resolve; } else { - err = JS_DupValue(ctx, argv0); - is_reject = 1; + if (JS_IteratorClose(ctx, s->sync_iter, FALSE)) + goto reject; + JS_ThrowTypeError(ctx, "throw is not a method"); + goto reject; } - goto done_resolve; } } value = JS_IteratorNext2(ctx, s->sync_iter, method, @@ -47559,20 +52748,8 @@ goto reject; } - if (JS_IsException(value)) { - JSValue res2; - reject: - err = JS_GetException(ctx); - is_reject = 1; - done_resolve: - res2 = JS_Call(ctx, resolving_funcsis_reject, JS_UNDEFINED, - 1, (JSValueConst *)&err); - JS_FreeValue(ctx, err); - JS_FreeValue(ctx, res2); - JS_FreeValue(ctx, resolving_funcs0); - JS_FreeValue(ctx, resolving_funcs1); - return promise; - } + if (JS_IsException(value)) + goto reject; { JSValue value_wrapper_promise, resolve_reject2; int res; @@ -47580,8 +52757,22 @@ value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&value, 0); if (JS_IsException(value_wrapper_promise)) { + JSValue res2; JS_FreeValue(ctx, value); - goto reject; + if (magic != GEN_MAGIC_RETURN && !done) { + JS_IteratorClose(ctx, s->sync_iter, TRUE); + } + reject: + err = JS_GetException(ctx); + is_reject = 1; + done_resolve: + res2 = JS_Call(ctx, resolving_funcsis_reject, JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs0); + JS_FreeValue(ctx, resolving_funcs1); + return promise; } resolve_reject0 = @@ -47590,13 +52781,23 @@ JS_FreeValue(ctx, value_wrapper_promise); goto fail; } + if (done || magic == GEN_MAGIC_RETURN) { + resolve_reject1 = JS_UNDEFINED; + } else { + resolve_reject1 = + js_async_from_sync_iterator_close_wrap_func_create(ctx, s->sync_iter); + if (JS_IsException(resolve_reject1)) { + JS_FreeValue(ctx, value_wrapper_promise); + JS_FreeValue(ctx, resolve_reject0); + goto fail; + } + } JS_FreeValue(ctx, value); - resolve_reject1 = JS_UNDEFINED; - res = perform_promise_then(ctx, value_wrapper_promise, (JSValueConst *)resolve_reject, (JSValueConst *)resolving_funcs); JS_FreeValue(ctx, resolve_reject0); + JS_FreeValue(ctx, resolve_reject1); JS_FreeValue(ctx, value_wrapper_promise); JS_FreeValue(ctx, resolving_funcs0); JS_FreeValue(ctx, resolving_funcs1); @@ -47647,14 +52848,16 @@ { JS_ATOM_AsyncGenerator, js_async_generator_finalizer, js_async_generator_mark }, /* JS_CLASS_ASYNC_GENERATOR */ }; -void JS_AddIntrinsicPromise(JSContext *ctx) +int JS_AddIntrinsicPromise(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; + JSCFunctionType ft; if (!JS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) { - init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE, - countof(js_async_class_def)); + if (init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE, + countof(js_async_class_def))) + return -1; rt->class_arrayJS_CLASS_PROMISE_RESOLVE_FUNCTION.call = js_promise_resolve_function_call; rt->class_arrayJS_CLASS_PROMISE_REJECT_FUNCTION.call = js_promise_resolve_function_call; rt->class_arrayJS_CLASS_ASYNC_FUNCTION.call = js_async_function_call; @@ -47664,72 +52867,67 @@ } /* Promise */ - ctx->class_protoJS_CLASS_PROMISE = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_PROMISE, - js_promise_proto_funcs, - countof(js_promise_proto_funcs)); - obj1 = JS_NewCFunction2(ctx, js_promise_constructor, "Promise", 1, - JS_CFUNC_constructor, 0); - ctx->promise_ctor = JS_DupValue(ctx, obj1); - JS_SetPropertyFunctionList(ctx, obj1, - js_promise_funcs, - countof(js_promise_funcs)); - JS_NewGlobalCConstructor2(ctx, obj1, "Promise", - ctx->class_protoJS_CLASS_PROMISE); + obj1 = JS_NewCConstructor(ctx, JS_CLASS_PROMISE, "Promise", + js_promise_constructor, 1, JS_CFUNC_constructor, 0, + JS_UNDEFINED, + js_promise_funcs, countof(js_promise_funcs), + js_promise_proto_funcs, countof(js_promise_proto_funcs), + 0); + if (JS_IsException(obj1)) + return -1; + ctx->promise_ctor = obj1; /* AsyncFunction */ - ctx->class_protoJS_CLASS_ASYNC_FUNCTION = JS_NewObjectProto(ctx, ctx->function_proto); - obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor, - "AsyncFunction", 1, - JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC, - ctx->function_ctor); - JS_SetPropertyFunctionList(ctx, - ctx->class_protoJS_CLASS_ASYNC_FUNCTION, - js_async_function_proto_funcs, - countof(js_async_function_proto_funcs)); - JS_SetConstructor2(ctx, obj1, ctx->class_protoJS_CLASS_ASYNC_FUNCTION, - 0, JS_PROP_CONFIGURABLE); + ft.generic_magic = js_function_constructor; + obj1 = JS_NewCConstructor(ctx, JS_CLASS_ASYNC_FUNCTION, "AsyncFunction", + ft.generic, 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC, + ctx->function_ctor, + NULL, 0, + js_async_function_proto_funcs, countof(js_async_function_proto_funcs), + JS_NEW_CTOR_NO_GLOBAL | JS_NEW_CTOR_READONLY); + if (JS_IsException(obj1)) + return -1; JS_FreeValue(ctx, obj1); /* AsyncIteratorPrototype */ - ctx->async_iterator_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->async_iterator_proto, - js_async_iterator_proto_funcs, - countof(js_async_iterator_proto_funcs)); + ctx->async_iterator_proto = + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_OBJECT, + js_async_iterator_proto_funcs, + countof(js_async_iterator_proto_funcs)); + if (JS_IsException(ctx->async_iterator_proto)) + return -1; /* AsyncFromSyncIteratorPrototype */ ctx->class_protoJS_CLASS_ASYNC_FROM_SYNC_ITERATOR = - JS_NewObjectProto(ctx, ctx->async_iterator_proto); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_ASYNC_FROM_SYNC_ITERATOR, - js_async_from_sync_iterator_proto_funcs, - countof(js_async_from_sync_iterator_proto_funcs)); + JS_NewObjectProtoList(ctx, ctx->async_iterator_proto, + js_async_from_sync_iterator_proto_funcs, + countof(js_async_from_sync_iterator_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_ASYNC_FROM_SYNC_ITERATOR)) + return -1; /* AsyncGeneratorPrototype */ ctx->class_protoJS_CLASS_ASYNC_GENERATOR = - JS_NewObjectProto(ctx, ctx->async_iterator_proto); - JS_SetPropertyFunctionList(ctx, - ctx->class_protoJS_CLASS_ASYNC_GENERATOR, - js_async_generator_proto_funcs, - countof(js_async_generator_proto_funcs)); + JS_NewObjectProtoList(ctx, ctx->async_iterator_proto, + js_async_generator_proto_funcs, + countof(js_async_generator_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_ASYNC_GENERATOR)) + return -1; /* AsyncGeneratorFunction */ - ctx->class_protoJS_CLASS_ASYNC_GENERATOR_FUNCTION = - JS_NewObjectProto(ctx, ctx->function_proto); - obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor, - "AsyncGeneratorFunction", 1, - JS_CFUNC_constructor_or_func_magic, - JS_FUNC_ASYNC_GENERATOR, - ctx->function_ctor); - JS_SetPropertyFunctionList(ctx, - ctx->class_protoJS_CLASS_ASYNC_GENERATOR_FUNCTION, - js_async_generator_function_proto_funcs, - countof(js_async_generator_function_proto_funcs)); - JS_SetConstructor2(ctx, ctx->class_protoJS_CLASS_ASYNC_GENERATOR_FUNCTION, - ctx->class_protoJS_CLASS_ASYNC_GENERATOR, - JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); - JS_SetConstructor2(ctx, obj1, ctx->class_protoJS_CLASS_ASYNC_GENERATOR_FUNCTION, - 0, JS_PROP_CONFIGURABLE); + ft.generic_magic = js_function_constructor; + obj1 = JS_NewCConstructor(ctx, JS_CLASS_ASYNC_GENERATOR_FUNCTION, "AsyncGeneratorFunction", + ft.generic, 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC_GENERATOR, + ctx->function_ctor, + NULL, 0, + js_async_generator_function_proto_funcs, countof(js_async_generator_function_proto_funcs), + JS_NEW_CTOR_NO_GLOBAL | JS_NEW_CTOR_READONLY); + if (JS_IsException(obj1)) + return -1; JS_FreeValue(ctx, obj1); + + return JS_SetConstructor2(ctx, ctx->class_protoJS_CLASS_ASYNC_GENERATOR_FUNCTION, + ctx->class_protoJS_CLASS_ASYNC_GENERATOR, + JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); } /* URI handling */ @@ -47830,8 +53028,7 @@ } c = (c << 6) | (c1 & 0x3f); } - if (c < c_min || c > 0x10FFFF || - (c >= 0xd800 && c < 0xe000)) { + if (c < c_min || c > 0x10FFFF || is_surrogate(c)) { js_throw_URIError(ctx, "malformed UTF-8"); goto fail; } @@ -47906,21 +53103,21 @@ if (isURIUnescaped(c, isComponent)) { string_buffer_putc16(b, c); } else { - if (c >= 0xdc00 && c <= 0xdfff) { + if (is_lo_surrogate(c)) { js_throw_URIError(ctx, "invalid character"); goto fail; - } else if (c >= 0xd800 && c <= 0xdbff) { + } else if (is_hi_surrogate(c)) { if (k >= p->len) { js_throw_URIError(ctx, "expecting surrogate pair"); goto fail; } c1 = string_get(p, k); k++; - if (c1 < 0xdc00 || c1 > 0xdfff) { + if (!is_lo_surrogate(c1)) { js_throw_URIError(ctx, "expecting surrogate pair"); goto fail; } - c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; + c = from_surrogate(c, c1); } if (c < 0x80) { encodeURI_hex(b, c); @@ -48028,12 +53225,8 @@ JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), JS_PROP_UNDEFINED_DEF("undefined", 0 ), - - /* for the 'Date' implementation */ - JS_CFUNC_DEF("__date_clock", 0, js___date_clock ), - //JS_CFUNC_DEF("__date_now", 0, js___date_now ), - //JS_CFUNC_DEF("__date_getTimezoneOffset", 1, js___date_getTimezoneOffset ), - //JS_CFUNC_DEF("__date_create", 3, js___date_create ), + JS_PROP_STRING_DEF("Symbol.toStringTag", "global", JS_PROP_CONFIGURABLE ), + JS_CFUNC_DEF("eval", 1, js_global_eval ), }; /* Date */ @@ -48114,7 +53307,7 @@ static char const day_names = "SunMonTueWedThuFriSat"; static __exception int get_date_fields(JSContext *ctx, JSValueConst obj, - double fields9, int is_local, int force) + double fieldsminimum_length(9), int is_local, int force) { double dval; int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0; @@ -48127,7 +53320,7 @@ return FALSE; /* NaN */ d = 0; /* initialize all fields to 0 */ } else { - d = dval; + d = dval; /* assuming -8.64e15 <= dval <= -8.64e15 */ if (is_local) { tz = -getTimezoneOffset(d); d += tz * 60000; @@ -48173,33 +53366,78 @@ return NAN; } -/* The spec mandates the use of 'double' and it fixes the order +/* The spec mandates the use of 'double' and it specifies the order of the operations */ -static double set_date_fields(double fields, int is_local) { - int64_t y; - double days, d, h, m1; - int i, m, md; - - m1 = fields1; - m = fmod(m1, 12); - if (m < 0) - m += 12; - y = (int64_t)(fields0 + floor(m1 / 12)); - days = days_from_year(y); +static double set_date_fields(double fieldsminimum_length(7), int is_local) { + double y, m, dt, ym, mn, day, h, s, milli, time, tv; + int yi, mi, i; + int64_t days; + volatile double temp; /* enforce evaluation order */ - for(i = 0; i < m; i++) { - md = month_daysi; + /* emulate 21.4.1.15 MakeDay ( year, month, date ) */ + y = fields0; + m = fields1; + dt = fields2; + ym = y + floor(m / 12); + mn = fmod(m, 12); + if (mn < 0) + mn += 12; + if (ym < -271821 || ym > 275760) + return NAN; + + yi = ym; + mi = mn; + days = days_from_year(yi); + for(i = 0; i < mi; i++) { + days += month_daysi; if (i == 1) - md += days_in_year(y) - 365; - days += md; + days += days_in_year(yi) - 365; + } + day = days + dt - 1; + + /* emulate 21.4.1.14 MakeTime ( hour, min, sec, ms ) */ + h = fields3; + m = fields4; + s = fields5; + milli = fields6; + /* Use a volatile intermediary variable to ensure order of evaluation + * as specified in ECMA. This fixes a test262 error on + * test262/test/built-ins/Date/UTC/fp-evaluation-order.js. + * Without the volatile qualifier, the compile can generate code + * that performs the computation in a different order or with instructions + * that produce a different result such as FMA (float multiply and add). + */ + time = h * 3600000; + time += (temp = m * 60000); + time += (temp = s * 1000); + time += milli; + + /* emulate 21.4.1.16 MakeDate ( day, time ) */ + tv = (temp = day * 86400000) + time; /* prevent generation of FMA */ + if (!isfinite(tv)) + return NAN; + + /* adjust for local time and clip */ + if (is_local) { + int64_t ti = tv < INT64_MIN ? INT64_MIN : (tv >= 0x8000000000000000ULL/*0x1p63*/) ? INT64_MAX : (int64_t)tv; + tv += getTimezoneOffset(ti) * 60000; + } + return time_clip(tv); +} + +static double set_date_fields_checked(double fieldsminimum_length(7), int is_local) +{ + int i; + double a; + for(i = 0; i < 7; i++) { + a = fieldsi; + if (!isfinite(a)) + return NAN; + fieldsi = trunc(a); + if (i == 0 && fields0 >= 0 && fields0 < 100) + fields0 += 1900; } - days += fields2 - 1; - h = fields3 * 3600000 + fields4 * 60000 + - fields5 * 1000 + fields6; - d = days * 86400000 + h; - if (is_local) - d += getTimezoneOffset(d) * 60000; - return time_clip(d); + return set_date_fields(fields, is_local); } static JSValue get_date_field(JSContext *ctx, JSValueConst this_val, @@ -48228,7 +53466,7 @@ { // _field(obj, first_field, end_field, args, is_local) double fields9; - int res, first_field, end_field, is_local, i, n; + int res, first_field, end_field, is_local, i, n, res1; double d, a; d = NAN; @@ -48239,20 +53477,24 @@ res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0); if (res < 0) return JS_EXCEPTION; - if (res && argc > 0) { - n = end_field - first_field; - if (argc < n) - n = argc; - for(i = 0; i < n; i++) { - if (JS_ToFloat64(ctx, &a, argvi)) - return JS_EXCEPTION; - if (!isfinite(a)) - goto done; - fieldsfirst_field + i = trunc(a); - } - d = set_date_fields(fields, is_local); + res1 = res; + + // Argument coercion is observable and must be done unconditionally. + n = min_int(argc, end_field - first_field); + for(i = 0; i < n; i++) { + if (JS_ToFloat64(ctx, &a, argvi)) + return JS_EXCEPTION; + if (!isfinite(a)) + res = FALSE; + fieldsfirst_field + i = trunc(a); } -done: + + if (!res1) + return JS_NAN; /* thisTimeValue is NaN */ + + if (res && argc > 0) + d = set_date_fields(fields, is_local); + return JS_SetThisTimeValue(ctx, this_val, d); } @@ -48283,7 +53525,7 @@ if (fmt == 2) return JS_ThrowRangeError(ctx, "Date value is NaN"); else - return JS_NewString(ctx, "Invalid Date"); + return js_new_string8(ctx, "Invalid Date"); } y = fields0; @@ -48362,7 +53604,7 @@ break; case 3: pos += snprintf(buf + pos, sizeof(buf) - pos, - "%02d:%02d:%02d %cM", (h + 1) % 12 - 1, m, s, + "%02d:%02d:%02d %cM", (h + 11) % 12 + 1, m, s, (h < 12) ? 'A' : 'P'); break; } @@ -48390,7 +53632,7 @@ // Date(y, mon, d, h, m, s, ms) JSValue rv; int i, n; - double a, val; + double val; if (JS_IsUndefined(new_target)) { /* invoked as function */ @@ -48428,15 +53670,10 @@ if (n > 7) n = 7; for(i = 0; i < n; i++) { - if (JS_ToFloat64(ctx, &a, argvi)) + if (JS_ToFloat64(ctx, &fieldsi, argvi)) return JS_EXCEPTION; - if (!isfinite(a)) - break; - fieldsi = trunc(a); - if (i == 0 && fields0 >= 0 && fields0 < 100) - fields0 += 1900; } - val = (i == n) ? set_date_fields(fields, 1) : NAN; + val = set_date_fields_checked(fields, 1); } has_val: #if 0 @@ -48466,7 +53703,6 @@ // UTC(y, mon, d, h, m, s, ms) double fields = { 0, 0, 1, 0, 0, 0, 0 }; int i, n; - double a; n = argc; if (n == 0) @@ -48474,153 +53710,436 @@ if (n > 7) n = 7; for(i = 0; i < n; i++) { - if (JS_ToFloat64(ctx, &a, argvi)) + if (JS_ToFloat64(ctx, &fieldsi, argvi)) return JS_EXCEPTION; - if (!isfinite(a)) - return JS_NAN; - fieldsi = trunc(a); - if (i == 0 && fields0 >= 0 && fields0 < 100) - fields0 += 1900; } - return JS_NewFloat64(ctx, set_date_fields(fields, 0)); + return JS_NewFloat64(ctx, set_date_fields_checked(fields, 0)); } -static void string_skip_spaces(JSString *sp, int *pp) { - while (*pp < sp->len && string_get(sp, *pp) == ' ') +/* Date string parsing */ + +static BOOL string_skip_char(const uint8_t *sp, int *pp, int c) { + if (sp*pp == c) { *pp += 1; + return TRUE; + } else { + return FALSE; + } } -static void string_skip_non_spaces(JSString *sp, int *pp) { - while (*pp < sp->len && string_get(sp, *pp) != ' ') +/* skip spaces, update offset, return next char */ +static int string_skip_spaces(const uint8_t *sp, int *pp) { + int c; + while ((c = sp*pp) == ' ') *pp += 1; + return c; +} + +/* skip dashes dots and commas */ +static int string_skip_separators(const uint8_t *sp, int *pp) { + int c; + while ((c = sp*pp) == '-' || c == '/' || c == '.' || c == ',') + *pp += 1; + return c; +} + +/* skip a word, stop on spaces, digits and separators, update offset */ +static int string_skip_until(const uint8_t *sp, int *pp, const char *stoplist) { + int c; + while (!strchr(stoplist, c = sp*pp)) + *pp += 1; + return c; } -/* parse a numeric field with an optional sign if accept_sign is TRUE */ -static int string_get_digits(JSString *sp, int *pp, int64_t *pval) { - int64_t v = 0; +/* parse a numeric field (max_digits = 0 -> no maximum) */ +static BOOL string_get_digits(const uint8_t *sp, int *pp, int *pval, + int min_digits, int max_digits) +{ + int v = 0; int c, p = *pp, p_start; - if (p >= sp->len) - return -1; p_start = p; - while (p < sp->len) { - c = string_get(sp, p); - if (!(c >= '0' && c <= '9')) { - if (p == p_start) - return -1; - else - break; - } + while ((c = spp) >= '0' && c <= '9') { + /* arbitrary limit to 9 digits */ + if (v >= 100000000) + return FALSE; v = v * 10 + c - '0'; p++; + if (p - p_start == max_digits) + break; } + if (p - p_start < min_digits) + return FALSE; *pval = v; *pp = p; - return 0; + return TRUE; } -static int string_get_signed_digits(JSString *sp, int *pp, int64_t *pval) { - int res, sgn, p = *pp; +static BOOL string_get_milliseconds(const uint8_t *sp, int *pp, int *pval) { + /* parse optional fractional part as milliseconds and truncate. */ + /* spec does not indicate which rounding should be used */ + int mul = 100, ms = 0, c, p_start, p = *pp; - if (p >= sp->len) - return -1; - - sgn = string_get(sp, p); - if (sgn == '-' || sgn == '+') + c = spp; + if (c == '.' || c == ',') { p++; + p_start = p; + while ((c = spp) >= '0' && c <= '9') { + ms += (c - '0') * mul; + mul /= 10; + p++; + if (p - p_start == 9) + break; + } + if (p > p_start) { + /* only consume the separator if digits are present */ + *pval = ms; + *pp = p; + } + } + return TRUE; +} - res = string_get_digits(sp, &p, pval); - if (res == 0 && sgn == '-') - *pval = -*pval; - *pp = p; - return res; +static uint8_t upper_ascii(uint8_t c) { + return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; } -/* parse a fixed width numeric field */ -static int string_get_fixed_width_digits(JSString *sp, int *pp, int n, int64_t *pval) { - int64_t v = 0; - int i, c, p = *pp; +static BOOL string_get_tzoffset(const uint8_t *sp, int *pp, int *tzp, BOOL strict) { + int tz = 0, sgn, hh, mm, p = *pp; - for(i = 0; i < n; i++) { - if (p >= sp->len) - return -1; - c = string_get(sp, p); - if (!(c >= '0' && c <= '9')) - return -1; - v = v * 10 + c - '0'; - p++; + sgn = spp++; + if (sgn == '+' || sgn == '-') { + int n = p; + if (!string_get_digits(sp, &p, &hh, 1, 0)) + return FALSE; + n = p - n; + if (strict && n != 2 && n != 4) + return FALSE; + while (n > 4) { + n -= 2; + hh /= 100; + } + if (n > 2) { + mm = hh % 100; + hh = hh / 100; + } else { + mm = 0; + if (string_skip_char(sp, &p, ':')) { + /* optional separator */ + if (!string_get_digits(sp, &p, &mm, 2, 2)) + return FALSE; + } else { + if (strict) + return FALSE; /* +-HH is not accepted in strict mode */ + } + } + if (hh > 23 || mm > 59) + return FALSE; + tz = hh * 60 + mm; + if (sgn != '+') + tz = -tz; + } else + if (sgn != 'Z') { + return FALSE; } - *pval = v; *pp = p; - return 0; + *tzp = tz; + return TRUE; } -static int string_get_milliseconds(JSString *sp, int *pp, int64_t *pval) { - /* parse milliseconds as a fractional part, round to nearest */ - /* XXX: the spec does not indicate which rounding should be used */ - int mul = 1000, ms = 0, p = *pp, c, p_start; - if (p >= sp->len) - return -1; - p_start = p; - while (p < sp->len) { - c = string_get(sp, p); - if (!(c >= '0' && c <= '9')) { - if (p == p_start) - return -1; - else - break; - } - if (mul == 1 && c >= '5') - ms += 1; - ms += (c - '0') * (mul /= 10); +static BOOL string_match(const uint8_t *sp, int *pp, const char *s) { + int p = *pp; + while (*s != '\0') { + if (upper_ascii(spp) != upper_ascii(*s++)) + return FALSE; p++; } - *pval = ms; *pp = p; - return 0; + return TRUE; } - -static int find_abbrev(JSString *sp, int p, const char *list, int count) { +static int find_abbrev(const uint8_t *sp, int p, const char *list, int count) { int n, i; - if (p + 3 <= sp->len) { - for (n = 0; n < count; n++) { - for (i = 0; i < 3; i++) { - if (string_get(sp, p + i) != month_namesn * 3 + i) - goto next; - } - return n; - next:; + for (n = 0; n < count; n++) { + for (i = 0;; i++) { + if (upper_ascii(spp + i) != upper_ascii(listn * 3 + i)) + break; + if (i == 2) + return n; } } return -1; } -static int string_get_month(JSString *sp, int *pp, int64_t *pval) { +static BOOL string_get_month(const uint8_t *sp, int *pp, int *pval) { int n; - string_skip_spaces(sp, pp); n = find_abbrev(sp, *pp, month_names, 12); if (n < 0) - return -1; + return FALSE; - *pval = n; + *pval = n + 1; *pp += 3; - return 0; + return TRUE; +} + +/* parse toISOString format */ +static BOOL js_date_parse_isostring(const uint8_t *sp, int fields9, BOOL *is_local) { + int sgn, i, p = 0; + + /* initialize fields to the beginning of the Epoch */ + for (i = 0; i < 9; i++) { + fieldsi = (i == 2); + } + *is_local = FALSE; + + /* year is either yyyy digits or +-yyyyyy */ + sgn = spp; + if (sgn == '-' || sgn == '+') { + p++; + if (!string_get_digits(sp, &p, &fields0, 6, 6)) + return FALSE; + if (sgn == '-') { + if (fields0 == 0) + return FALSE; // reject -000000 + fields0 = -fields0; + } + } else { + if (!string_get_digits(sp, &p, &fields0, 4, 4)) + return FALSE; + } + if (string_skip_char(sp, &p, '-')) { + if (!string_get_digits(sp, &p, &fields1, 2, 2)) /* month */ + return FALSE; + if (fields1 < 1) + return FALSE; + fields1 -= 1; + if (string_skip_char(sp, &p, '-')) { + if (!string_get_digits(sp, &p, &fields2, 2, 2)) /* day */ + return FALSE; + if (fields2 < 1) + return FALSE; + } + } + if (string_skip_char(sp, &p, 'T')) { + *is_local = TRUE; + if (!string_get_digits(sp, &p, &fields3, 2, 2) /* hour */ + || !string_skip_char(sp, &p, ':') + || !string_get_digits(sp, &p, &fields4, 2, 2)) { /* minute */ + fields3 = 100; // reject unconditionally + return TRUE; + } + if (string_skip_char(sp, &p, ':')) { + if (!string_get_digits(sp, &p, &fields5, 2, 2)) /* second */ + return FALSE; + string_get_milliseconds(sp, &p, &fields6); + } + } + /* parse the time zone offset if present: +-HH:mm or +-HHmm */ + if (spp) { + *is_local = FALSE; + if (!string_get_tzoffset(sp, &p, &fields8, TRUE)) + return FALSE; + } + /* error if extraneous characters */ + return spp == '\0'; +} + +static struct { + char name6; + int16_t offset; +} const js_tzabbr = { + { "GMT", 0 }, // Greenwich Mean Time + { "UTC", 0 }, // Coordinated Universal Time + { "UT", 0 }, // Universal Time + { "Z", 0 }, // Zulu Time + { "EDT", -4 * 60 }, // Eastern Daylight Time + { "EST", -5 * 60 }, // Eastern Standard Time + { "CDT", -5 * 60 }, // Central Daylight Time + { "CST", -6 * 60 }, // Central Standard Time + { "MDT", -6 * 60 }, // Mountain Daylight Time + { "MST", -7 * 60 }, // Mountain Standard Time + { "PDT", -7 * 60 }, // Pacific Daylight Time + { "PST", -8 * 60 }, // Pacific Standard Time + { "WET", +0 * 60 }, // Western European Time + { "WEST", +1 * 60 }, // Western European Summer Time + { "CET", +1 * 60 }, // Central European Time + { "CEST", +2 * 60 }, // Central European Summer Time + { "EET", +2 * 60 }, // Eastern European Time + { "EEST", +3 * 60 }, // Eastern European Summer Time +}; + +static BOOL string_get_tzabbr(const uint8_t *sp, int *pp, int *offset) { + for (size_t i = 0; i < countof(js_tzabbr); i++) { + if (string_match(sp, pp, js_tzabbri.name)) { + *offset = js_tzabbri.offset; + return TRUE; + } + } + return FALSE; +} + +/* parse toString, toUTCString and other formats */ +static BOOL js_date_parse_otherstring(const uint8_t *sp, + int fieldsminimum_length(9), + BOOL *is_local) { + int c, i, val, p = 0, p_start; + int num3; + BOOL has_year = FALSE; + BOOL has_mon = FALSE; + BOOL has_time = FALSE; + int num_index = 0; + + /* initialize fields to the beginning of 2001-01-01 */ + fields0 = 2001; + fields1 = 1; + fields2 = 1; + for (i = 3; i < 9; i++) { + fieldsi = 0; + } + *is_local = TRUE; + + while (string_skip_spaces(sp, &p)) { + p_start = p; + if ((c = spp) == '+' || c == '-') { + if (has_time && string_get_tzoffset(sp, &p, &fields8, FALSE)) { + *is_local = FALSE; + } else { + p++; + if (string_get_digits(sp, &p, &val, 1, 0)) { + if (c == '-') { + if (val == 0) + return FALSE; + val = -val; + } + fields0 = val; + has_year = TRUE; + } + } + } else + if (string_get_digits(sp, &p, &val, 1, 0)) { + if (string_skip_char(sp, &p, ':')) { + /* time part */ + fields3 = val; + if (!string_get_digits(sp, &p, &fields4, 1, 2)) + return FALSE; + if (string_skip_char(sp, &p, ':')) { + if (!string_get_digits(sp, &p, &fields5, 1, 2)) + return FALSE; + string_get_milliseconds(sp, &p, &fields6); + } + has_time = TRUE; + if ((spp == '+' || spp == '-') && + string_get_tzoffset(sp, &p, &fields8, FALSE)) { + *is_local = FALSE; + } + } else { + if (p - p_start > 2 && !has_year) { + fields0 = val; + has_year = TRUE; + } else + if ((val < 1 || val > 31) && !has_year) { + fields0 = val + (val < 100) * 1900 + (val < 50) * 100; + has_year = TRUE; + } else { + if (num_index == 3) + return FALSE; + numnum_index++ = val; + } + } + } else + if (string_get_month(sp, &p, &fields1)) { + has_mon = TRUE; + string_skip_until(sp, &p, "0123456789 -/("); + } else + if (has_time && string_match(sp, &p, "PM")) { + if (fields3 < 12) + fields3 += 12; + continue; + } else + if (has_time && string_match(sp, &p, "AM")) { + if (fields3 == 12) + fields3 -= 12; + continue; + } else + if (string_get_tzabbr(sp, &p, &fields8)) { + *is_local = FALSE; + continue; + } else + if (c == '(') { /* skip parenthesized phrase */ + int level = 0; + while ((c = spp) != '\0') { + p++; + level += (c == '('); + level -= (c == ')'); + if (!level) + break; + } + if (level > 0) + return FALSE; + } else + if (c == ')') { + return FALSE; + } else { + if (has_year + has_mon + has_time + num_index) + return FALSE; + /* skip a word */ + string_skip_until(sp, &p, " -/("); + } + string_skip_separators(sp, &p); + } + if (num_index + has_year + has_mon > 3) + return FALSE; + + switch (num_index) { + case 0: + if (!has_year) + return FALSE; + break; + case 1: + if (has_mon) + fields2 = num0; + else + fields1 = num0; + break; + case 2: + if (has_year) { + fields1 = num0; + fields2 = num1; + } else + if (has_mon) { + fields0 = num1 + (num1 < 100) * 1900 + (num1 < 50) * 100; + fields2 = num0; + } else { + fields1 = num0; + fields2 = num1; + } + break; + case 3: + fields0 = num2 + (num2 < 100) * 1900 + (num2 < 50) * 100; + fields1 = num0; + fields2 = num1; + break; + default: + return FALSE; + } + if (fields1 < 1 || fields2 < 1) + return FALSE; + fields1 -= 1; + return TRUE; } static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - // parse(s) JSValue s, rv; - int64_t fields = { 0, 1, 1, 0, 0, 0, 0 }; - double fields17; - int64_t tz, hh, mm; + int fields9; + double fields19; double d; - int p, i, c, sgn, l; + int i, c; JSString *sp; + uint8_t buf128; BOOL is_local; rv = JS_NAN; @@ -48630,145 +54149,33 @@ return JS_EXCEPTION; sp = JS_VALUE_GET_STRING(s); - p = 0; - if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) { - /* ISO format */ - /* year field can be negative */ - if (string_get_signed_digits(sp, &p, &fields0)) - goto done; - - for (i = 1; i < 7; i++) { - if (p >= sp->len) - break; - switch(i) { - case 1: - case 2: - c = '-'; - break; - case 3: - c = 'T'; - break; - case 4: - case 5: - c = ':'; - break; - case 6: - c = '.'; - break; - } - if (string_get(sp, p) != c) - break; - p++; - if (i == 6) { - if (string_get_milliseconds(sp, &p, &fieldsi)) - goto done; - } else { - if (string_get_digits(sp, &p, &fieldsi)) - goto done; - } - } - /* no time: UTC by default */ - is_local = (i > 3); - fields1 -= 1; - - /* parse the time zone offset if present: +-HH:mm or +-HHmm */ - tz = 0; - if (p < sp->len) { - sgn = string_get(sp, p); - if (sgn == '+' || sgn == '-') { - p++; - l = sp->len - p; - if (l != 4 && l != 5) - goto done; - if (string_get_fixed_width_digits(sp, &p, 2, &hh)) - goto done; - if (l == 5) { - if (string_get(sp, p) != ':') - goto done; - p++; - } - if (string_get_fixed_width_digits(sp, &p, 2, &mm)) - goto done; - tz = hh * 60 + mm; - if (sgn == '-') - tz = -tz; - is_local = FALSE; - } else if (sgn == 'Z') { - p++; - is_local = FALSE; - } else { - goto done; - } - /* error if extraneous characters */ - if (p != sp->len) - goto done; - } - } else { - /* toString or toUTCString format */ - /* skip the day of the week */ - string_skip_non_spaces(sp, &p); - string_skip_spaces(sp, &p); - if (p >= sp->len) - goto done; - c = string_get(sp, p); - if (c >= '0' && c <= '9') { - /* day of month first */ - if (string_get_digits(sp, &p, &fields2)) - goto done; - if (string_get_month(sp, &p, &fields1)) - goto done; - } else { - /* month first */ - if (string_get_month(sp, &p, &fields1)) - goto done; - string_skip_spaces(sp, &p); - if (string_get_digits(sp, &p, &fields2)) - goto done; - } - /* year */ - string_skip_spaces(sp, &p); - if (string_get_signed_digits(sp, &p, &fields0)) - goto done; - - /* hour, min, seconds */ - string_skip_spaces(sp, &p); - for(i = 0; i < 3; i++) { - if (i == 1 || i == 2) { - if (p >= sp->len) - goto done; - if (string_get(sp, p) != ':') - goto done; - p++; - } - if (string_get_digits(sp, &p, &fields3 + i)) - goto done; - } - // XXX: parse optional milliseconds? - - /* parse the time zone offset if present: +-HHmm */ - is_local = FALSE; - tz = 0; - for (tz = 0; p < sp->len; p++) { - sgn = string_get(sp, p); - if (sgn == '+' || sgn == '-') { - p++; - if (string_get_fixed_width_digits(sp, &p, 2, &hh)) - goto done; - if (string_get_fixed_width_digits(sp, &p, 2, &mm)) - goto done; - tz = hh * 60 + mm; - if (sgn == '-') - tz = -tz; - break; - } + /* convert the string as a byte array */ + for (i = 0; i < sp->len && i < (int)countof(buf) - 1; i++) { + c = string_get(sp, i); + if (c > 255) + c = (c == 0x2212) ? '-' : 'x'; + bufi = c; + } + bufi = '\0'; + if (js_date_parse_isostring(buf, fields, &is_local) + || js_date_parse_otherstring(buf, fields, &is_local)) { + static int const field_max6 = { 0, 11, 31, 24, 59, 59 }; + BOOL valid = TRUE; + /* check field maximum values */ + for (i = 1; i < 6; i++) { + if (fieldsi > field_maxi) + valid = FALSE; + } + /* special case 24:00:00.000 */ + if (fields3 == 24 && (fields4 | fields5 | fields6)) + valid = FALSE; + if (valid) { + for(i = 0; i < 7; i++) + fields1i = fieldsi; + d = set_date_fields(fields1, is_local) - fields8 * 60000; + rv = JS_NewFloat64(ctx, d); } } - for(i = 0; i < 7; i++) - fields1i = fieldsi; - d = set_date_fields(fields1, is_local) - tz * 60000; - rv = JS_NewFloat64(ctx, d); - -done: JS_FreeValue(ctx, s); return rv; } @@ -48799,9 +54206,7 @@ } switch (hint) { case JS_ATOM_number: -#ifdef CONFIG_BIGNUM case JS_ATOM_integer: -#endif hint_num = HINT_NUMBER; break; case JS_ATOM_string: @@ -48825,6 +54230,7 @@ if (isnan(v)) return JS_NAN; else + /* assuming -8.64e15 <= v <= -8.64e15 */ return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v))); } @@ -48963,324 +54369,38 @@ JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ), }; -void JS_AddIntrinsicDate(JSContext *ctx) +JSValue JS_NewDate(JSContext *ctx, double epoch_ms) { - JSValueConst obj; - - /* Date */ - ctx->class_protoJS_CLASS_DATE = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_DATE, js_date_proto_funcs, - countof(js_date_proto_funcs)); - obj = JS_NewGlobalCConstructor(ctx, "Date", js_date_constructor, 7, - ctx->class_protoJS_CLASS_DATE); - JS_SetPropertyFunctionList(ctx, obj, js_date_funcs, countof(js_date_funcs)); -} - -/* eval */ - -void JS_AddIntrinsicEval(JSContext *ctx) -{ - ctx->eval_internal = __JS_EvalInternal; -} - -#ifdef CONFIG_BIGNUM - -/* Operators */ - -static void js_operator_set_finalizer(JSRuntime *rt, JSValue val) -{ - JSOperatorSetData *opset = JS_GetOpaque(val, JS_CLASS_OPERATOR_SET); - int i, j; - JSBinaryOperatorDefEntry *ent; - - if (opset) { - for(i = 0; i < JS_OVOP_COUNT; i++) { - if (opset->self_opsi) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, opset->self_opsi)); - } - for(j = 0; j < opset->left.count; j++) { - ent = &opset->left.tabj; - for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { - if (ent->opsi) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ent->opsi)); - } - } - js_free_rt(rt, opset->left.tab); - for(j = 0; j < opset->right.count; j++) { - ent = &opset->right.tabj; - for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { - if (ent->opsi) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ent->opsi)); - } - } - js_free_rt(rt, opset->right.tab); - js_free_rt(rt, opset); - } -} - -static void js_operator_set_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSOperatorSetData *opset = JS_GetOpaque(val, JS_CLASS_OPERATOR_SET); - int i, j; - JSBinaryOperatorDefEntry *ent; - - if (opset) { - for(i = 0; i < JS_OVOP_COUNT; i++) { - if (opset->self_opsi) - JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, opset->self_opsi), - mark_func); - } - for(j = 0; j < opset->left.count; j++) { - ent = &opset->left.tabj; - for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { - if (ent->opsi) - JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ent->opsi), - mark_func); - } - } - for(j = 0; j < opset->right.count; j++) { - ent = &opset->right.tabj; - for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { - if (ent->opsi) - JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ent->opsi), - mark_func); - } - } - } -} - - -/* create an OperatorSet object */ -static JSValue js_operators_create_internal(JSContext *ctx, - int argc, JSValueConst *argv, - BOOL is_primitive) -{ - JSValue opset_obj, prop, obj; - JSOperatorSetData *opset, *opset1; - JSBinaryOperatorDef *def; - JSValueConst arg; - int i, j; - JSBinaryOperatorDefEntry *new_tab; - JSBinaryOperatorDefEntry *ent; - uint32_t op_count; - - if (ctx->rt->operator_count == UINT32_MAX) { - return JS_ThrowTypeError(ctx, "too many operators"); - } - opset_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_OPERATOR_SET); - if (JS_IsException(opset_obj)) - goto fail; - opset = js_mallocz(ctx, sizeof(*opset)); - if (!opset) - goto fail; - JS_SetOpaque(opset_obj, opset); - if (argc >= 1) { - arg = argv0; - /* self operators */ - for(i = 0; i < JS_OVOP_COUNT; i++) { - prop = JS_GetPropertyStr(ctx, arg, js_overloadable_operator_namesi); - if (JS_IsException(prop)) - goto fail; - if (!JS_IsUndefined(prop)) { - if (check_function(ctx, prop)) { - JS_FreeValue(ctx, prop); - goto fail; - } - opset->self_opsi = JS_VALUE_GET_OBJ(prop); - } - } - } - /* left & right operators */ - for(j = 1; j < argc; j++) { - arg = argvj; - prop = JS_GetPropertyStr(ctx, arg, "left"); - if (JS_IsException(prop)) - goto fail; - def = &opset->right; - if (JS_IsUndefined(prop)) { - prop = JS_GetPropertyStr(ctx, arg, "right"); - if (JS_IsException(prop)) - goto fail; - if (JS_IsUndefined(prop)) { - JS_ThrowTypeError(ctx, "left or right property must be present"); - goto fail; - } - def = &opset->left; - } - /* get the operator set */ - obj = JS_GetProperty(ctx, prop, JS_ATOM_prototype); - JS_FreeValue(ctx, prop); - if (JS_IsException(obj)) - goto fail; - prop = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_operatorSet); - JS_FreeValue(ctx, obj); - if (JS_IsException(prop)) - goto fail; - opset1 = JS_GetOpaque2(ctx, prop, JS_CLASS_OPERATOR_SET); - if (!opset1) { - JS_FreeValue(ctx, prop); - goto fail; - } - op_count = opset1->operator_counter; - JS_FreeValue(ctx, prop); - - /* we assume there are few entries */ - new_tab = js_realloc(ctx, def->tab, - (def->count + 1) * sizeof(def->tab0)); - if (!new_tab) - goto fail; - def->tab = new_tab; - def->count++; - ent = def->tab + def->count - 1; - memset(ent, 0, sizeof(def->tab0)); - ent->operator_index = op_count; - - for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { - prop = JS_GetPropertyStr(ctx, arg, - js_overloadable_operator_namesi); - if (JS_IsException(prop)) - goto fail; - if (!JS_IsUndefined(prop)) { - if (check_function(ctx, prop)) { - JS_FreeValue(ctx, prop); - goto fail; - } - ent->opsi = JS_VALUE_GET_OBJ(prop); - } - } - } - opset->is_primitive = is_primitive; - opset->operator_counter = ctx->rt->operator_count++; - return opset_obj; - fail: - JS_FreeValue(ctx, opset_obj); - return JS_EXCEPTION; -} - -static JSValue js_operators_create(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return js_operators_create_internal(ctx, argc, argv, FALSE); -} - -static JSValue js_operators_updateBigIntOperators(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue opset_obj, prop; - JSOperatorSetData *opset; - const JSOverloadableOperatorEnum ops2 = { JS_OVOP_DIV, JS_OVOP_POW }; - JSOverloadableOperatorEnum op; - int i; - - opset_obj = JS_GetProperty(ctx, ctx->class_protoJS_CLASS_BIG_INT, - JS_ATOM_Symbol_operatorSet); - if (JS_IsException(opset_obj)) - goto fail; - opset = JS_GetOpaque2(ctx, opset_obj, JS_CLASS_OPERATOR_SET); - if (!opset) - goto fail; - for(i = 0; i < countof(ops); i++) { - op = opsi; - prop = JS_GetPropertyStr(ctx, argv0, - js_overloadable_operator_namesop); - if (JS_IsException(prop)) - goto fail; - if (!JS_IsUndefined(prop)) { - if (!JS_IsNull(prop) && check_function(ctx, prop)) { - JS_FreeValue(ctx, prop); - goto fail; - } - if (opset->self_opsop) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, opset->self_opsop)); - if (JS_IsNull(prop)) { - opset->self_opsop = NULL; - } else { - opset->self_opsop = JS_VALUE_GET_PTR(prop); - } - } - } - JS_FreeValue(ctx, opset_obj); - return JS_UNDEFINED; - fail: - JS_FreeValue(ctx, opset_obj); - return JS_EXCEPTION; + JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_DATE); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JS_SetObjectData(ctx, obj, __JS_NewFloat64(ctx, time_clip(epoch_ms))); + return obj; } -static int js_operators_set_default(JSContext *ctx, JSValueConst obj) +int JS_AddIntrinsicDate(JSContext *ctx) { - JSValue opset_obj; + JSValue obj; - if (!JS_IsObject(obj)) /* in case the prototype is not defined */ - return 0; - opset_obj = js_operators_create_internal(ctx, 0, NULL, TRUE); - if (JS_IsException(opset_obj)) + /* Date */ + obj = JS_NewCConstructor(ctx, JS_CLASS_DATE, "Date", + js_date_constructor, 7, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_date_funcs, countof(js_date_funcs), + js_date_proto_funcs, countof(js_date_proto_funcs), + 0); + if (JS_IsException(obj)) return -1; - /* cannot be modified by the user */ - JS_DefinePropertyValue(ctx, obj, JS_ATOM_Symbol_operatorSet, - opset_obj, 0); + JS_FreeValue(ctx, obj); return 0; } -static JSValue js_dummy_operators_ctor(JSContext *ctx, JSValueConst new_target, - int argc, JSValueConst *argv) -{ - return js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); -} - -static JSValue js_global_operators(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue func_obj, proto, opset_obj; - - func_obj = JS_UNDEFINED; - proto = JS_NewObject(ctx); - if (JS_IsException(proto)) - return JS_EXCEPTION; - opset_obj = js_operators_create_internal(ctx, argc, argv, FALSE); - if (JS_IsException(opset_obj)) - goto fail; - JS_DefinePropertyValue(ctx, proto, JS_ATOM_Symbol_operatorSet, - opset_obj, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - func_obj = JS_NewCFunction2(ctx, js_dummy_operators_ctor, "Operators", - 0, JS_CFUNC_constructor, 0); - if (JS_IsException(func_obj)) - goto fail; - JS_SetConstructor2(ctx, func_obj, proto, - 0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_FreeValue(ctx, proto); - return func_obj; - fail: - JS_FreeValue(ctx, proto); - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; -} - -static const JSCFunctionListEntry js_operators_funcs = { - JS_CFUNC_DEF("create", 1, js_operators_create ), - JS_CFUNC_DEF("updateBigIntOperators", 2, js_operators_updateBigIntOperators ), -}; +/* eval */ -/* must be called after all overloadable base types are initialized */ -void JS_AddIntrinsicOperators(JSContext *ctx) +int JS_AddIntrinsicEval(JSContext *ctx) { - JSValue obj; - - ctx->allow_operator_overloading = TRUE; - obj = JS_NewCFunction(ctx, js_global_operators, "Operators", 1); - JS_SetPropertyFunctionList(ctx, obj, - js_operators_funcs, - countof(js_operators_funcs)); - JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_Operators, - obj, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - /* add default operatorSets */ - js_operators_set_default(ctx, ctx->class_protoJS_CLASS_BOOLEAN); - js_operators_set_default(ctx, ctx->class_protoJS_CLASS_NUMBER); - js_operators_set_default(ctx, ctx->class_protoJS_CLASS_STRING); - js_operators_set_default(ctx, ctx->class_protoJS_CLASS_BIG_INT); - js_operators_set_default(ctx, ctx->class_protoJS_CLASS_BIG_FLOAT); - js_operators_set_default(ctx, ctx->class_protoJS_CLASS_BIG_DECIMAL); + ctx->eval_internal = __JS_EvalInternal; + return 0; } /* BigInt */ @@ -49296,49 +54416,29 @@ case JS_TAG_BOOL: val = JS_NewBigInt64(ctx, JS_VALUE_GET_INT(val)); break; + case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: break; case JS_TAG_FLOAT64: - case JS_TAG_BIG_FLOAT: { - bf_t *a, a_s; - - a = JS_ToBigFloat(ctx, &a_s, val); - if (!bf_is_finite(a)) { - JS_FreeValue(ctx, val); - val = JS_ThrowRangeError(ctx, "cannot convert NaN or Infinity to bigint"); - } else { - JSValue val1 = JS_NewBigInt(ctx); - bf_t *r; - int ret; - if (JS_IsException(val1)) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - r = JS_GetBigInt(val1); - ret = bf_set(r, a); - ret |= bf_rint(r, BF_RNDZ); - JS_FreeValue(ctx, val); - if (ret & BF_ST_MEM_ERROR) { - JS_FreeValue(ctx, val1); - val = JS_ThrowOutOfMemory(ctx); - } else if (ret & BF_ST_INEXACT) { - JS_FreeValue(ctx, val1); - val = JS_ThrowRangeError(ctx, "cannot convert to bigint: not an integer"); + double d = JS_VALUE_GET_FLOAT64(val); + JSBigInt *r; + int res; + r = js_bigint_from_float64(ctx, &res, d); + if (!r) { + if (res == 0) { + val = JS_EXCEPTION; + } else if (res == 1) { + val = JS_ThrowRangeError(ctx, "cannot convert to BigInt: not an integer"); } else { - val = JS_CompactBigInt(ctx, val1); - } + val = JS_ThrowRangeError(ctx, "cannot convert NaN or Infinity to BigInt"); } + } else { + val = JS_CompactBigInt(ctx, r); } - if (a == &a_s) - bf_delete(a); } break; - case JS_TAG_BIG_DECIMAL: - val = JS_ToStringFree(ctx, val); - if (JS_IsException(val)) - break; - goto redo; case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: val = JS_StringToBigIntErr(ctx, val); break; case JS_TAG_OBJECT: @@ -49350,7 +54450,7 @@ case JS_TAG_UNDEFINED: default: JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert to bigint"); + return JS_ThrowTypeError(ctx, "cannot convert to BigInt"); } return val; } @@ -49376,7 +54476,7 @@ return JS_DupValue(ctx, p->u.object_data); } } - return JS_ThrowTypeError(ctx, "not a bigint"); + return JS_ThrowTypeError(ctx, "not a BigInt"); } static JSValue js_bigint_toString(JSContext *ctx, JSValueConst this_val, @@ -49410,191 +54510,73 @@ return js_thisBigIntValue(ctx, this_val); } -static JSValue js_bigint_div(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - bf_t a_s, b_s, *a, *b, *r, *q; - int status; - JSValue q_val, r_val; - - q_val = JS_NewBigInt(ctx); - if (JS_IsException(q_val)) - return JS_EXCEPTION; - r_val = JS_NewBigInt(ctx); - if (JS_IsException(r_val)) - goto fail; - b = NULL; - a = JS_ToBigInt(ctx, &a_s, argv0); - if (!a) - goto fail; - b = JS_ToBigInt(ctx, &b_s, argv1); - if (!b) { - JS_FreeBigInt(ctx, a, &a_s); - goto fail; - } - q = JS_GetBigInt(q_val); - r = JS_GetBigInt(r_val); - status = bf_divrem(q, r, a, b, BF_PREC_INF, BF_RNDZ, magic & 0xf); - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - if (unlikely(status)) { - throw_bf_exception(ctx, status); - goto fail; - } - q_val = JS_CompactBigInt(ctx, q_val); - if (magic & 0x10) { - JSValue ret; - ret = JS_NewArray(ctx); - if (JS_IsException(ret)) - goto fail; - JS_SetPropertyUint32(ctx, ret, 0, q_val); - JS_SetPropertyUint32(ctx, ret, 1, JS_CompactBigInt(ctx, r_val)); - return ret; - } else { - JS_FreeValue(ctx, r_val); - return q_val; - } - fail: - JS_FreeValue(ctx, q_val); - JS_FreeValue(ctx, r_val); - return JS_EXCEPTION; -} - -static JSValue js_bigint_sqrt(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - bf_t a_s, *a, *r, *rem; - int status; - JSValue r_val, rem_val; - - r_val = JS_NewBigInt(ctx); - if (JS_IsException(r_val)) - return JS_EXCEPTION; - rem_val = JS_NewBigInt(ctx); - if (JS_IsException(rem_val)) - return JS_EXCEPTION; - r = JS_GetBigInt(r_val); - rem = JS_GetBigInt(rem_val); - - a = JS_ToBigInt(ctx, &a_s, argv0); - if (!a) - goto fail; - status = bf_sqrtrem(r, rem, a); - JS_FreeBigInt(ctx, a, &a_s); - if (unlikely(status & ~BF_ST_INEXACT)) { - throw_bf_exception(ctx, status); - goto fail; - } - r_val = JS_CompactBigInt(ctx, r_val); - if (magic) { - JSValue ret; - ret = JS_NewArray(ctx); - if (JS_IsException(ret)) - goto fail; - JS_SetPropertyUint32(ctx, ret, 0, r_val); - JS_SetPropertyUint32(ctx, ret, 1, JS_CompactBigInt(ctx, rem_val)); - return ret; - } else { - JS_FreeValue(ctx, rem_val); - return r_val; - } - fail: - JS_FreeValue(ctx, r_val); - JS_FreeValue(ctx, rem_val); - return JS_EXCEPTION; -} - -static JSValue js_bigint_op1(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, - int magic) -{ - bf_t a_s, *a; - int64_t res; - - a = JS_ToBigInt(ctx, &a_s, argv0); - if (!a) - return JS_EXCEPTION; - switch(magic) { - case 0: /* floorLog2 */ - if (a->sign || a->expn <= 0) { - res = -1; - } else { - res = a->expn - 1; - } - break; - case 1: /* ctz */ - if (bf_is_zero(a)) { - res = -1; - } else { - res = bf_get_exp_min(a); - } - break; - default: - abort(); - } - JS_FreeBigInt(ctx, a, &a_s); - return JS_NewBigInt64(ctx, res); -} static JSValue js_bigint_asUintN(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int asIntN) { uint64_t bits; - bf_t a_s, *a = &a_s, *r, mask_s, *mask = &mask_s; - JSValue res; + JSValue res, a; if (JS_ToIndex(ctx, &bits, argv0)) return JS_EXCEPTION; - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) - return JS_EXCEPTION; - r = JS_GetBigInt(res); - a = JS_ToBigInt(ctx, &a_s, argv1); - if (!a) { - JS_FreeValue(ctx, res); + a = JS_ToBigInt(ctx, argv1); + if (JS_IsException(a)) return JS_EXCEPTION; + if (bits == 0) { + JS_FreeValue(ctx, a); + res = __JS_NewShortBigInt(ctx, 0); + } else if (JS_VALUE_GET_TAG(a) == JS_TAG_SHORT_BIG_INT) { + /* fast case */ + if (bits >= JS_SHORT_BIG_INT_BITS) { + res = a; + } else { + uint64_t v; + int shift; + shift = 64 - bits; + v = JS_VALUE_GET_SHORT_BIG_INT(a); + v = v << shift; + if (asIntN) + v = (int64_t)v >> shift; + else + v = v >> shift; + res = __JS_NewShortBigInt(ctx, v); + } + } else { + JSBigInt *r, *p = JS_VALUE_GET_PTR(a); + if (bits >= p->len * JS_LIMB_BITS) { + res = a; + } else { + int len, shift, i; + js_limb_t v; + len = (bits + JS_LIMB_BITS - 1) / JS_LIMB_BITS; + r = js_bigint_new(ctx, len); + if (!r) { + JS_FreeValue(ctx, a); + return JS_EXCEPTION; + } + r->len = len; + for(i = 0; i < len - 1; i++) + r->tabi = p->tabi; + shift = (-bits) & (JS_LIMB_BITS - 1); + /* 0 <= shift <= JS_LIMB_BITS - 1 */ + v = p->tablen - 1 << shift; + if (asIntN) + v = (js_slimb_t)v >> shift; + else + v = v >> shift; + r->tablen - 1 = v; + r = js_bigint_normalize(ctx, r); + JS_FreeValue(ctx, a); + res = JS_CompactBigInt(ctx, r); + } } - /* XXX: optimize */ - r = JS_GetBigInt(res); - bf_init(ctx->bf_ctx, mask); - bf_set_ui(mask, 1); - bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ); - bf_add_si(mask, mask, -1, BF_PREC_INF, BF_RNDZ); - bf_logic_and(r, a, mask); - if (asIntN && bits != 0) { - bf_set_ui(mask, 1); - bf_mul_2exp(mask, bits - 1, BF_PREC_INF, BF_RNDZ); - if (bf_cmpu(r, mask) >= 0) { - bf_set_ui(mask, 1); - bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ); - bf_sub(r, r, mask, BF_PREC_INF, BF_RNDZ); - } - } - bf_delete(mask); - JS_FreeBigInt(ctx, a, &a_s); - return JS_CompactBigInt(ctx, res); + return res; } static const JSCFunctionListEntry js_bigint_funcs = { JS_CFUNC_MAGIC_DEF("asUintN", 2, js_bigint_asUintN, 0 ), JS_CFUNC_MAGIC_DEF("asIntN", 2, js_bigint_asUintN, 1 ), - /* QuickJS extensions */ - JS_CFUNC_MAGIC_DEF("tdiv", 2, js_bigint_div, BF_RNDZ ), - JS_CFUNC_MAGIC_DEF("fdiv", 2, js_bigint_div, BF_RNDD ), - JS_CFUNC_MAGIC_DEF("cdiv", 2, js_bigint_div, BF_RNDU ), - JS_CFUNC_MAGIC_DEF("ediv", 2, js_bigint_div, BF_DIVREM_EUCLIDIAN ), - JS_CFUNC_MAGIC_DEF("tdivrem", 2, js_bigint_div, BF_RNDZ | 0x10 ), - JS_CFUNC_MAGIC_DEF("fdivrem", 2, js_bigint_div, BF_RNDD | 0x10 ), - JS_CFUNC_MAGIC_DEF("cdivrem", 2, js_bigint_div, BF_RNDU | 0x10 ), - JS_CFUNC_MAGIC_DEF("edivrem", 2, js_bigint_div, BF_DIVREM_EUCLIDIAN | 0x10 ), - JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigint_sqrt, 0 ), - JS_CFUNC_MAGIC_DEF("sqrtrem", 1, js_bigint_sqrt, 1 ), - JS_CFUNC_MAGIC_DEF("floorLog2", 1, js_bigint_op1, 0 ), - JS_CFUNC_MAGIC_DEF("ctz", 1, js_bigint_op1, 1 ), }; static const JSCFunctionListEntry js_bigint_proto_funcs = { @@ -49603,1694 +54585,344 @@ JS_PROP_STRING_DEF("Symbol.toStringTag", "BigInt", JS_PROP_CONFIGURABLE ), }; -void JS_AddIntrinsicBigInt(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - JSValueConst obj1; - - rt->bigint_ops.to_string = js_bigint_to_string; - rt->bigint_ops.from_string = js_string_to_bigint; - rt->bigint_ops.unary_arith = js_unary_arith_bigint; - rt->bigint_ops.binary_arith = js_binary_arith_bigint; - rt->bigint_ops.compare = js_compare_bigfloat; - - ctx->class_protoJS_CLASS_BIG_INT = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_BIG_INT, - js_bigint_proto_funcs, - countof(js_bigint_proto_funcs)); - obj1 = JS_NewGlobalCConstructor(ctx, "BigInt", js_bigint_constructor, 1, - ctx->class_protoJS_CLASS_BIG_INT); - JS_SetPropertyFunctionList(ctx, obj1, js_bigint_funcs, - countof(js_bigint_funcs)); -} - -/* BigFloat */ - -static JSValue js_thisBigFloatValue(JSContext *ctx, JSValueConst this_val) -{ - if (JS_IsBigFloat(this_val)) - return JS_DupValue(ctx, this_val); - - if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(this_val); - if (p->class_id == JS_CLASS_BIG_FLOAT) { - if (JS_IsBigFloat(p->u.object_data)) - return JS_DupValue(ctx, p->u.object_data); - } - } - return JS_ThrowTypeError(ctx, "not a bigfloat"); -} - -static JSValue js_bigfloat_toString(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val; - int base; - JSValue ret; - - val = js_thisBigFloatValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (argc == 0 || JS_IsUndefined(argv0)) { - base = 10; - } else { - base = js_get_radix(ctx, argv0); - if (base < 0) - goto fail; - } - ret = js_ftoa(ctx, val, base, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN); - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue js_bigfloat_valueOf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) +static int JS_AddIntrinsicBigInt(JSContext *ctx) { - return js_thisBigFloatValue(ctx, this_val); -} + JSValue obj1; -static int bigfloat_get_rnd_mode(JSContext *ctx, JSValueConst val) -{ - int rnd_mode; - if (JS_ToInt32Sat(ctx, &rnd_mode, val)) - return -1; - if (rnd_mode < BF_RNDN || rnd_mode > BF_RNDF) { - JS_ThrowRangeError(ctx, "invalid rounding mode"); + obj1 = JS_NewCConstructor(ctx, JS_CLASS_BIG_INT, "BigInt", + js_bigint_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_bigint_funcs, countof(js_bigint_funcs), + js_bigint_proto_funcs, countof(js_bigint_proto_funcs), + 0); + if (JS_IsException(obj1)) return -1; - } - return rnd_mode; -} - -static JSValue js_bigfloat_toFixed(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - int64_t f; - int rnd_mode, radix; - - val = js_thisBigFloatValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (JS_ToInt64Sat(ctx, &f, argv0)) - goto fail; - if (f < 0 || f > BF_PREC_MAX) { - JS_ThrowRangeError(ctx, "invalid number of digits"); - goto fail; - } - rnd_mode = BF_RNDNA; - radix = 10; - /* XXX: swap parameter order for rounding mode and radix */ - if (argc > 1) { - rnd_mode = bigfloat_get_rnd_mode(ctx, argv1); - if (rnd_mode < 0) - goto fail; - } - if (argc > 2) { - radix = js_get_radix(ctx, argv2); - if (radix < 0) - goto fail; - } - ret = js_ftoa(ctx, val, radix, f, rnd_mode | BF_FTOA_FORMAT_FRAC); - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static BOOL js_bigfloat_is_finite(JSContext *ctx, JSValueConst val) -{ - BOOL res; - uint32_t tag; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_BIG_FLOAT: - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - res = bf_is_finite(&p->num); - } - break; - default: - res = FALSE; - break; - } - return res; -} - -static JSValue js_bigfloat_toExponential(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - int64_t f; - int rnd_mode, radix; - - val = js_thisBigFloatValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (JS_ToInt64Sat(ctx, &f, argv0)) - goto fail; - if (!js_bigfloat_is_finite(ctx, val)) { - ret = JS_ToString(ctx, val); - } else if (JS_IsUndefined(argv0)) { - ret = js_ftoa(ctx, val, 10, 0, - BF_RNDN | BF_FTOA_FORMAT_FREE_MIN | BF_FTOA_FORCE_EXP); - } else { - if (f < 0 || f > BF_PREC_MAX) { - JS_ThrowRangeError(ctx, "invalid number of digits"); - goto fail; - } - rnd_mode = BF_RNDNA; - radix = 10; - if (argc > 1) { - rnd_mode = bigfloat_get_rnd_mode(ctx, argv1); - if (rnd_mode < 0) - goto fail; - } - if (argc > 2) { - radix = js_get_radix(ctx, argv2); - if (radix < 0) - goto fail; - } - ret = js_ftoa(ctx, val, radix, f + 1, - rnd_mode | BF_FTOA_FORMAT_FIXED | BF_FTOA_FORCE_EXP); - } - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue js_bigfloat_toPrecision(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - int64_t p; - int rnd_mode, radix; - - val = js_thisBigFloatValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (JS_IsUndefined(argv0)) - goto to_string; - if (JS_ToInt64Sat(ctx, &p, argv0)) - goto fail; - if (!js_bigfloat_is_finite(ctx, val)) { - to_string: - ret = JS_ToString(ctx, this_val); - } else { - if (p < 1 || p > BF_PREC_MAX) { - JS_ThrowRangeError(ctx, "invalid number of digits"); - goto fail; - } - rnd_mode = BF_RNDNA; - radix = 10; - if (argc > 1) { - rnd_mode = bigfloat_get_rnd_mode(ctx, argv1); - if (rnd_mode < 0) - goto fail; - } - if (argc > 2) { - radix = js_get_radix(ctx, argv2); - if (radix < 0) - goto fail; - } - ret = js_ftoa(ctx, val, radix, p, rnd_mode | BF_FTOA_FORMAT_FIXED); - } - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static const JSCFunctionListEntry js_bigfloat_proto_funcs = { - JS_CFUNC_DEF("toString", 0, js_bigfloat_toString ), - JS_CFUNC_DEF("valueOf", 0, js_bigfloat_valueOf ), - JS_CFUNC_DEF("toPrecision", 1, js_bigfloat_toPrecision ), - JS_CFUNC_DEF("toFixed", 1, js_bigfloat_toFixed ), - JS_CFUNC_DEF("toExponential", 1, js_bigfloat_toExponential ), -}; - -static JSValue js_bigfloat_constructor(JSContext *ctx, - JSValueConst new_target, - int argc, JSValueConst *argv) -{ - JSValue val; - if (!JS_IsUndefined(new_target)) - return JS_ThrowTypeError(ctx, "not a constructor"); - if (argc == 0) { - bf_t *r; - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - return val; - r = JS_GetBigFloat(val); - bf_set_zero(r, 0); - } else { - val = JS_DupValue(ctx, argv0); - redo: - switch(JS_VALUE_GET_NORM_TAG(val)) { - case JS_TAG_BIG_FLOAT: - break; - case JS_TAG_FLOAT64: - { - bf_t *r; - double d = JS_VALUE_GET_FLOAT64(val); - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - break; - r = JS_GetBigFloat(val); - if (bf_set_float64(r, d)) - goto fail; - } - break; - case JS_TAG_INT: - { - bf_t *r; - int32_t v = JS_VALUE_GET_INT(val); - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - break; - r = JS_GetBigFloat(val); - if (bf_set_si(r, v)) - goto fail; - } - break; - case JS_TAG_BIG_INT: - /* We keep the full precision of the integer */ - { - JSBigFloat *p = JS_VALUE_GET_PTR(val); - val = JS_MKPTR(JS_TAG_BIG_FLOAT, p); - } - break; - case JS_TAG_BIG_DECIMAL: - val = JS_ToStringFree(ctx, val); - if (JS_IsException(val)) - break; - goto redo; - case JS_TAG_STRING: - { - const char *str, *p; - size_t len; - int err; - - str = JS_ToCStringLen(ctx, &len, val); - JS_FreeValue(ctx, val); - if (!str) - return JS_EXCEPTION; - p = str; - p += skip_spaces(p); - if ((p - str) == len) { - bf_t *r; - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - break; - r = JS_GetBigFloat(val); - bf_set_zero(r, 0); - err = 0; - } else { - val = js_atof(ctx, p, &p, 0, ATOD_ACCEPT_BIN_OCT | - ATOD_TYPE_BIG_FLOAT | - ATOD_ACCEPT_PREFIX_AFTER_SIGN); - if (JS_IsException(val)) { - JS_FreeCString(ctx, str); - return JS_EXCEPTION; - } - p += skip_spaces(p); - err = ((p - str) != len); - } - JS_FreeCString(ctx, str); - if (err) { - JS_FreeValue(ctx, val); - return JS_ThrowSyntaxError(ctx, "invalid bigfloat literal"); - } - } - break; - case JS_TAG_OBJECT: - val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); - if (JS_IsException(val)) - break; - goto redo; - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - default: - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert to bigfloat"); - } - } - return val; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue js_bigfloat_get_const(JSContext *ctx, - JSValueConst this_val, int magic) -{ - bf_t *r; - JSValue val; - val = JS_NewBigFloat(ctx); - if (JS_IsException(val)) - return val; - r = JS_GetBigFloat(val); - switch(magic) { - case 0: /* PI */ - bf_const_pi(r, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case 1: /* LN2 */ - bf_const_log2(r, ctx->fp_env.prec, ctx->fp_env.flags); - break; - case 2: /* MIN_VALUE */ - case 3: /* MAX_VALUE */ - { - slimb_t e_range, e; - e_range = (limb_t)1 << (bf_get_exp_bits(ctx->fp_env.flags) - 1); - bf_set_ui(r, 1); - if (magic == 2) { - e = -e_range + 2; - if (ctx->fp_env.flags & BF_FLAG_SUBNORMAL) - e -= ctx->fp_env.prec - 1; - bf_mul_2exp(r, e, ctx->fp_env.prec, ctx->fp_env.flags); - } else { - bf_mul_2exp(r, ctx->fp_env.prec, ctx->fp_env.prec, - ctx->fp_env.flags); - bf_add_si(r, r, -1, ctx->fp_env.prec, ctx->fp_env.flags); - bf_mul_2exp(r, e_range - ctx->fp_env.prec, ctx->fp_env.prec, - ctx->fp_env.flags); - } - } - break; - case 4: /* EPSILON */ - bf_set_ui(r, 1); - bf_mul_2exp(r, 1 - ctx->fp_env.prec, - ctx->fp_env.prec, ctx->fp_env.flags); - break; - default: - abort(); - } - return val; -} - -static JSValue js_bigfloat_parseFloat(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - bf_t *a; - const char *str; - JSValue ret; - int radix; - JSFloatEnv *fe; - - str = JS_ToCString(ctx, argv0); - if (!str) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &radix, argv1)) { - fail: - JS_FreeCString(ctx, str); - return JS_EXCEPTION; - } - if (radix != 0 && (radix < 2 || radix > 36)) { - JS_ThrowRangeError(ctx, "radix must be between 2 and 36"); - goto fail; - } - fe = &ctx->fp_env; - if (argc > 2) { - fe = JS_GetOpaque2(ctx, argv2, JS_CLASS_FLOAT_ENV); - if (!fe) - goto fail; - } - ret = JS_NewBigFloat(ctx); - if (JS_IsException(ret)) - goto done; - a = JS_GetBigFloat(ret); - /* XXX: use js_atof() */ - bf_atof(a, str, NULL, radix, fe->prec, fe->flags); - done: - JS_FreeCString(ctx, str); - return ret; -} - -static JSValue js_bigfloat_isFinite(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValueConst val = argv0; - JSBigFloat *p; - - if (JS_VALUE_GET_NORM_TAG(val) != JS_TAG_BIG_FLOAT) - return JS_FALSE; - p = JS_VALUE_GET_PTR(val); - return JS_NewBool(ctx, bf_is_finite(&p->num)); -} - -static JSValue js_bigfloat_isNaN(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValueConst val = argv0; - JSBigFloat *p; - - if (JS_VALUE_GET_NORM_TAG(val) != JS_TAG_BIG_FLOAT) - return JS_FALSE; - p = JS_VALUE_GET_PTR(val); - return JS_NewBool(ctx, bf_is_nan(&p->num)); -} - -enum { - MATH_OP_ABS, - MATH_OP_FLOOR, - MATH_OP_CEIL, - MATH_OP_ROUND, - MATH_OP_TRUNC, - MATH_OP_SQRT, - MATH_OP_FPROUND, - MATH_OP_ACOS, - MATH_OP_ASIN, - MATH_OP_ATAN, - MATH_OP_ATAN2, - MATH_OP_COS, - MATH_OP_EXP, - MATH_OP_LOG, - MATH_OP_POW, - MATH_OP_SIN, - MATH_OP_TAN, - MATH_OP_FMOD, - MATH_OP_REM, - MATH_OP_SIGN, - - MATH_OP_ADD, - MATH_OP_SUB, - MATH_OP_MUL, - MATH_OP_DIV, -}; - -static JSValue js_bigfloat_fop(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - bf_t a_s, *a, *r; - JSFloatEnv *fe; - int rnd_mode; - JSValue op1, res; - - op1 = JS_ToNumeric(ctx, argv0); - if (JS_IsException(op1)) - return op1; - a = JS_ToBigFloat(ctx, &a_s, op1); - fe = &ctx->fp_env; - if (argc > 1) { - fe = JS_GetOpaque2(ctx, argv1, JS_CLASS_FLOAT_ENV); - if (!fe) - goto fail; - } - res = JS_NewBigFloat(ctx); - if (JS_IsException(res)) { - fail: - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, op1); - return JS_EXCEPTION; - } - r = JS_GetBigFloat(res); - switch (magic) { - case MATH_OP_ABS: - bf_set(r, a); - r->sign = 0; - break; - case MATH_OP_FLOOR: - rnd_mode = BF_RNDD; - goto rint; - case MATH_OP_CEIL: - rnd_mode = BF_RNDU; - goto rint; - case MATH_OP_ROUND: - rnd_mode = BF_RNDNA; - goto rint; - case MATH_OP_TRUNC: - rnd_mode = BF_RNDZ; - rint: - bf_set(r, a); - fe->status |= bf_rint(r, rnd_mode); - break; - case MATH_OP_SQRT: - fe->status |= bf_sqrt(r, a, fe->prec, fe->flags); - break; - case MATH_OP_FPROUND: - bf_set(r, a); - fe->status |= bf_round(r, fe->prec, fe->flags); - break; - case MATH_OP_ACOS: - fe->status |= bf_acos(r, a, fe->prec, fe->flags); - break; - case MATH_OP_ASIN: - fe->status |= bf_asin(r, a, fe->prec, fe->flags); - break; - case MATH_OP_ATAN: - fe->status |= bf_atan(r, a, fe->prec, fe->flags); - break; - case MATH_OP_COS: - fe->status |= bf_cos(r, a, fe->prec, fe->flags); - break; - case MATH_OP_EXP: - fe->status |= bf_exp(r, a, fe->prec, fe->flags); - break; - case MATH_OP_LOG: - fe->status |= bf_log(r, a, fe->prec, fe->flags); - break; - case MATH_OP_SIN: - fe->status |= bf_sin(r, a, fe->prec, fe->flags); - break; - case MATH_OP_TAN: - fe->status |= bf_tan(r, a, fe->prec, fe->flags); - break; - case MATH_OP_SIGN: - if (bf_is_nan(a) || bf_is_zero(a)) { - bf_set(r, a); - } else { - bf_set_si(r, 1 - 2 * a->sign); - } - break; - default: - abort(); - } - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, op1); - return res; -} - -static JSValue js_bigfloat_fop2(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - bf_t a_s, *a, b_s, *b, r_s, *r = &r_s; - JSFloatEnv *fe; - JSValue op1, op2, res; - - op1 = JS_ToNumeric(ctx, argv0); - if (JS_IsException(op1)) - return op1; - op2 = JS_ToNumeric(ctx, argv1); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - return op2; - } - a = JS_ToBigFloat(ctx, &a_s, op1); - b = JS_ToBigFloat(ctx, &b_s, op2); - fe = &ctx->fp_env; - if (argc > 2) { - fe = JS_GetOpaque2(ctx, argv2, JS_CLASS_FLOAT_ENV); - if (!fe) - goto fail; - } - res = JS_NewBigFloat(ctx); - if (JS_IsException(res)) { - fail: - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return JS_EXCEPTION; - } - r = JS_GetBigFloat(res); - switch (magic) { - case MATH_OP_ATAN2: - fe->status |= bf_atan2(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_POW: - fe->status |= bf_pow(r, a, b, fe->prec, fe->flags | BF_POW_JS_QUIRKS); - break; - case MATH_OP_FMOD: - fe->status |= bf_rem(r, a, b, fe->prec, fe->flags, BF_RNDZ); - break; - case MATH_OP_REM: - fe->status |= bf_rem(r, a, b, fe->prec, fe->flags, BF_RNDN); - break; - case MATH_OP_ADD: - fe->status |= bf_add(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_SUB: - fe->status |= bf_sub(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_MUL: - fe->status |= bf_mul(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_DIV: - fe->status |= bf_div(r, a, b, fe->prec, fe->flags); - break; - default: - abort(); - } - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return res; + JS_FreeValue(ctx, obj1); + return 0; } -static const JSCFunctionListEntry js_bigfloat_funcs = { - JS_CGETSET_MAGIC_DEF("PI", js_bigfloat_get_const, NULL, 0 ), - JS_CGETSET_MAGIC_DEF("LN2", js_bigfloat_get_const, NULL, 1 ), - JS_CGETSET_MAGIC_DEF("MIN_VALUE", js_bigfloat_get_const, NULL, 2 ), - JS_CGETSET_MAGIC_DEF("MAX_VALUE", js_bigfloat_get_const, NULL, 3 ), - JS_CGETSET_MAGIC_DEF("EPSILON", js_bigfloat_get_const, NULL, 4 ), - JS_CFUNC_DEF("parseFloat", 1, js_bigfloat_parseFloat ), - JS_CFUNC_DEF("isFinite", 1, js_bigfloat_isFinite ), - JS_CFUNC_DEF("isNaN", 1, js_bigfloat_isNaN ), - JS_CFUNC_MAGIC_DEF("abs", 1, js_bigfloat_fop, MATH_OP_ABS ), - JS_CFUNC_MAGIC_DEF("fpRound", 1, js_bigfloat_fop, MATH_OP_FPROUND ), - JS_CFUNC_MAGIC_DEF("floor", 1, js_bigfloat_fop, MATH_OP_FLOOR ), - JS_CFUNC_MAGIC_DEF("ceil", 1, js_bigfloat_fop, MATH_OP_CEIL ), - JS_CFUNC_MAGIC_DEF("round", 1, js_bigfloat_fop, MATH_OP_ROUND ), - JS_CFUNC_MAGIC_DEF("trunc", 1, js_bigfloat_fop, MATH_OP_TRUNC ), - JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigfloat_fop, MATH_OP_SQRT ), - JS_CFUNC_MAGIC_DEF("acos", 1, js_bigfloat_fop, MATH_OP_ACOS ), - JS_CFUNC_MAGIC_DEF("asin", 1, js_bigfloat_fop, MATH_OP_ASIN ), - JS_CFUNC_MAGIC_DEF("atan", 1, js_bigfloat_fop, MATH_OP_ATAN ), - JS_CFUNC_MAGIC_DEF("atan2", 2, js_bigfloat_fop2, MATH_OP_ATAN2 ), - JS_CFUNC_MAGIC_DEF("cos", 1, js_bigfloat_fop, MATH_OP_COS ), - JS_CFUNC_MAGIC_DEF("exp", 1, js_bigfloat_fop, MATH_OP_EXP ), - JS_CFUNC_MAGIC_DEF("log", 1, js_bigfloat_fop, MATH_OP_LOG ), - JS_CFUNC_MAGIC_DEF("pow", 2, js_bigfloat_fop2, MATH_OP_POW ), - JS_CFUNC_MAGIC_DEF("sin", 1, js_bigfloat_fop, MATH_OP_SIN ), - JS_CFUNC_MAGIC_DEF("tan", 1, js_bigfloat_fop, MATH_OP_TAN ), - JS_CFUNC_MAGIC_DEF("sign", 1, js_bigfloat_fop, MATH_OP_SIGN ), - JS_CFUNC_MAGIC_DEF("add", 2, js_bigfloat_fop2, MATH_OP_ADD ), - JS_CFUNC_MAGIC_DEF("sub", 2, js_bigfloat_fop2, MATH_OP_SUB ), - JS_CFUNC_MAGIC_DEF("mul", 2, js_bigfloat_fop2, MATH_OP_MUL ), - JS_CFUNC_MAGIC_DEF("div", 2, js_bigfloat_fop2, MATH_OP_DIV ), - JS_CFUNC_MAGIC_DEF("fmod", 2, js_bigfloat_fop2, MATH_OP_FMOD ), - JS_CFUNC_MAGIC_DEF("remainder", 2, js_bigfloat_fop2, MATH_OP_REM ), -}; - -/* FloatEnv */ - -static JSValue js_float_env_constructor(JSContext *ctx, - JSValueConst new_target, - int argc, JSValueConst *argv) +/* Minimum amount of objects to be able to compile code and display + error messages. */ +static int JS_AddIntrinsicBasicObjects(JSContext *ctx) { JSValue obj; - JSFloatEnv *fe; - int64_t prec; - int flags, rndmode; - - prec = ctx->fp_env.prec; - flags = ctx->fp_env.flags; - if (!JS_IsUndefined(argv0)) { - if (JS_ToInt64Sat(ctx, &prec, argv0)) - return JS_EXCEPTION; - if (prec < BF_PREC_MIN || prec > BF_PREC_MAX) - return JS_ThrowRangeError(ctx, "invalid precision"); - flags = BF_RNDN; /* RNDN, max exponent size, no subnormal */ - if (argc > 1 && !JS_IsUndefined(argv1)) { - if (JS_ToInt32Sat(ctx, &rndmode, argv1)) - return JS_EXCEPTION; - if (rndmode < BF_RNDN || rndmode > BF_RNDF) - return JS_ThrowRangeError(ctx, "invalid rounding mode"); - flags = rndmode; - } - } - - obj = JS_NewObjectClass(ctx, JS_CLASS_FLOAT_ENV); - if (JS_IsException(obj)) - return JS_EXCEPTION; - fe = js_malloc(ctx, sizeof(*fe)); - if (!fe) - return JS_EXCEPTION; - fe->prec = prec; - fe->flags = flags; - fe->status = 0; - JS_SetOpaque(obj, fe); - return obj; -} - -static void js_float_env_finalizer(JSRuntime *rt, JSValue val) -{ - JSFloatEnv *fe = JS_GetOpaque(val, JS_CLASS_FLOAT_ENV); - js_free_rt(rt, fe); -} - -static JSValue js_float_env_get_prec(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewInt64(ctx, ctx->fp_env.prec); -} - -static JSValue js_float_env_get_expBits(JSContext *ctx, JSValueConst this_val) -{ - return JS_NewInt32(ctx, bf_get_exp_bits(ctx->fp_env.flags)); -} - -static JSValue js_float_env_setPrec(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValueConst func; - int exp_bits, flags, saved_flags; - JSValue ret; - limb_t saved_prec; - int64_t prec; - - func = argv0; - if (JS_ToInt64Sat(ctx, &prec, argv1)) - return JS_EXCEPTION; - if (prec < BF_PREC_MIN || prec > BF_PREC_MAX) - return JS_ThrowRangeError(ctx, "invalid precision"); - exp_bits = BF_EXP_BITS_MAX; - - if (argc > 2 && !JS_IsUndefined(argv2)) { - if (JS_ToInt32Sat(ctx, &exp_bits, argv2)) - return JS_EXCEPTION; - if (exp_bits < BF_EXP_BITS_MIN || exp_bits > BF_EXP_BITS_MAX) - return JS_ThrowRangeError(ctx, "invalid number of exponent bits"); - } - - flags = BF_RNDN | BF_FLAG_SUBNORMAL | bf_set_exp_bits(exp_bits); - - saved_prec = ctx->fp_env.prec; - saved_flags = ctx->fp_env.flags; - - ctx->fp_env.prec = prec; - ctx->fp_env.flags = flags; - - ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); - /* always restore the floating point precision */ - ctx->fp_env.prec = saved_prec; - ctx->fp_env.flags = saved_flags; - return ret; -} - -#define FE_PREC (-1) -#define FE_EXP (-2) -#define FE_RNDMODE (-3) -#define FE_SUBNORMAL (-4) - -static JSValue js_float_env_proto_get_status(JSContext *ctx, JSValueConst this_val, int magic) -{ - JSFloatEnv *fe; - fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV); - if (!fe) - return JS_EXCEPTION; - switch(magic) { - case FE_PREC: - return JS_NewInt64(ctx, fe->prec); - case FE_EXP: - return JS_NewInt32(ctx, bf_get_exp_bits(fe->flags)); - case FE_RNDMODE: - return JS_NewInt32(ctx, fe->flags & BF_RND_MASK); - case FE_SUBNORMAL: - return JS_NewBool(ctx, (fe->flags & BF_FLAG_SUBNORMAL) != 0); - default: - return JS_NewBool(ctx, (fe->status & magic) != 0); - } -} - -static JSValue js_float_env_proto_set_status(JSContext *ctx, JSValueConst this_val, JSValueConst val, int magic) -{ - JSFloatEnv *fe; - int b; - int64_t prec; - - fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV); - if (!fe) - return JS_EXCEPTION; - switch(magic) { - case FE_PREC: - if (JS_ToInt64Sat(ctx, &prec, val)) - return JS_EXCEPTION; - if (prec < BF_PREC_MIN || prec > BF_PREC_MAX) - return JS_ThrowRangeError(ctx, "invalid precision"); - fe->prec = prec; - break; - case FE_EXP: - if (JS_ToInt32Sat(ctx, &b, val)) - return JS_EXCEPTION; - if (b < BF_EXP_BITS_MIN || b > BF_EXP_BITS_MAX) - return JS_ThrowRangeError(ctx, "invalid number of exponent bits"); - fe->flags = (fe->flags & ~(BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT)) | - bf_set_exp_bits(b); - break; - case FE_RNDMODE: - b = bigfloat_get_rnd_mode(ctx, val); - if (b < 0) - return JS_EXCEPTION; - fe->flags = (fe->flags & ~BF_RND_MASK) | b; - break; - case FE_SUBNORMAL: - b = JS_ToBool(ctx, val); - fe->flags = (fe->flags & ~BF_FLAG_SUBNORMAL) | (b ? BF_FLAG_SUBNORMAL: 0); - break; - default: - b = JS_ToBool(ctx, val); - fe->status = (fe->status & ~magic) & ((-b) & magic); - break; - } - return JS_UNDEFINED; -} - -static JSValue js_float_env_clearStatus(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSFloatEnv *fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV); - if (!fe) - return JS_EXCEPTION; - fe->status = 0; - return JS_UNDEFINED; -} - -static const JSCFunctionListEntry js_float_env_funcs = { - JS_CGETSET_DEF("prec", js_float_env_get_prec, NULL ), - JS_CGETSET_DEF("expBits", js_float_env_get_expBits, NULL ), - JS_CFUNC_DEF("setPrec", 2, js_float_env_setPrec ), - JS_PROP_INT32_DEF("RNDN", BF_RNDN, 0 ), - JS_PROP_INT32_DEF("RNDZ", BF_RNDZ, 0 ), - JS_PROP_INT32_DEF("RNDU", BF_RNDU, 0 ), - JS_PROP_INT32_DEF("RNDD", BF_RNDD, 0 ), - JS_PROP_INT32_DEF("RNDNA", BF_RNDNA, 0 ), - JS_PROP_INT32_DEF("RNDA", BF_RNDA, 0 ), - JS_PROP_INT32_DEF("RNDF", BF_RNDF, 0 ), - JS_PROP_INT32_DEF("precMin", BF_PREC_MIN, 0 ), - JS_PROP_INT64_DEF("precMax", BF_PREC_MAX, 0 ), - JS_PROP_INT32_DEF("expBitsMin", BF_EXP_BITS_MIN, 0 ), - JS_PROP_INT32_DEF("expBitsMax", BF_EXP_BITS_MAX, 0 ), -}; - -static const JSCFunctionListEntry js_float_env_proto_funcs = { - JS_CGETSET_MAGIC_DEF("prec", js_float_env_proto_get_status, - js_float_env_proto_set_status, FE_PREC ), - JS_CGETSET_MAGIC_DEF("expBits", js_float_env_proto_get_status, - js_float_env_proto_set_status, FE_EXP ), - JS_CGETSET_MAGIC_DEF("rndMode", js_float_env_proto_get_status, - js_float_env_proto_set_status, FE_RNDMODE ), - JS_CGETSET_MAGIC_DEF("subnormal", js_float_env_proto_get_status, - js_float_env_proto_set_status, FE_SUBNORMAL ), - JS_CGETSET_MAGIC_DEF("invalidOperation", js_float_env_proto_get_status, - js_float_env_proto_set_status, BF_ST_INVALID_OP ), - JS_CGETSET_MAGIC_DEF("divideByZero", js_float_env_proto_get_status, - js_float_env_proto_set_status, BF_ST_DIVIDE_ZERO ), - JS_CGETSET_MAGIC_DEF("overflow", js_float_env_proto_get_status, - js_float_env_proto_set_status, BF_ST_OVERFLOW ), - JS_CGETSET_MAGIC_DEF("underflow", js_float_env_proto_get_status, - js_float_env_proto_set_status, BF_ST_UNDERFLOW ), - JS_CGETSET_MAGIC_DEF("inexact", js_float_env_proto_get_status, - js_float_env_proto_set_status, BF_ST_INEXACT ), - JS_CFUNC_DEF("clearStatus", 0, js_float_env_clearStatus ), -}; - -void JS_AddIntrinsicBigFloat(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - JSValueConst obj1; - - rt->bigfloat_ops.to_string = js_bigfloat_to_string; - rt->bigfloat_ops.from_string = js_string_to_bigfloat; - rt->bigfloat_ops.unary_arith = js_unary_arith_bigfloat; - rt->bigfloat_ops.binary_arith = js_binary_arith_bigfloat; - rt->bigfloat_ops.compare = js_compare_bigfloat; - rt->bigfloat_ops.mul_pow10_to_float64 = js_mul_pow10_to_float64; - rt->bigfloat_ops.mul_pow10 = js_mul_pow10; - - ctx->class_protoJS_CLASS_BIG_FLOAT = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_BIG_FLOAT, - js_bigfloat_proto_funcs, - countof(js_bigfloat_proto_funcs)); - obj1 = JS_NewGlobalCConstructor(ctx, "BigFloat", js_bigfloat_constructor, 1, - ctx->class_protoJS_CLASS_BIG_FLOAT); - JS_SetPropertyFunctionList(ctx, obj1, js_bigfloat_funcs, - countof(js_bigfloat_funcs)); - - ctx->class_protoJS_CLASS_FLOAT_ENV = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_FLOAT_ENV, - js_float_env_proto_funcs, - countof(js_float_env_proto_funcs)); - obj1 = JS_NewGlobalCConstructorOnly(ctx, "BigFloatEnv", - js_float_env_constructor, 1, - ctx->class_protoJS_CLASS_FLOAT_ENV); - JS_SetPropertyFunctionList(ctx, obj1, js_float_env_funcs, - countof(js_float_env_funcs)); -} - -/* BigDecimal */ - -static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val, - BOOL allow_null_or_undefined) -{ - redo: - switch(JS_VALUE_GET_NORM_TAG(val)) { - case JS_TAG_BIG_DECIMAL: - break; - case JS_TAG_NULL: - if (!allow_null_or_undefined) - goto fail; - /* fall thru */ - case JS_TAG_BOOL: - case JS_TAG_INT: - { - bfdec_t *r; - int32_t v = JS_VALUE_GET_INT(val); - - val = JS_NewBigDecimal(ctx); - if (JS_IsException(val)) - break; - r = JS_GetBigDecimal(val); - if (bfdec_set_si(r, v)) { - JS_FreeValue(ctx, val); - val = JS_EXCEPTION; - break; - } - } - break; - case JS_TAG_FLOAT64: - case JS_TAG_BIG_INT: - case JS_TAG_BIG_FLOAT: - val = JS_ToStringFree(ctx, val); - if (JS_IsException(val)) - break; - goto redo; - case JS_TAG_STRING: - { - const char *str, *p; - size_t len; - int err; - - str = JS_ToCStringLen(ctx, &len, val); - JS_FreeValue(ctx, val); - if (!str) - return JS_EXCEPTION; - p = str; - p += skip_spaces(p); - if ((p - str) == len) { - bfdec_t *r; - val = JS_NewBigDecimal(ctx); - if (JS_IsException(val)) - break; - r = JS_GetBigDecimal(val); - bfdec_set_zero(r, 0); - err = 0; - } else { - val = js_atof(ctx, p, &p, 0, ATOD_TYPE_BIG_DECIMAL); - if (JS_IsException(val)) { - JS_FreeCString(ctx, str); - return JS_EXCEPTION; - } - p += skip_spaces(p); - err = ((p - str) != len); - } - JS_FreeCString(ctx, str); - if (err) { - JS_FreeValue(ctx, val); - return JS_ThrowSyntaxError(ctx, "invalid bigdecimal literal"); - } - } - break; - case JS_TAG_OBJECT: - val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); - if (JS_IsException(val)) - break; - goto redo; - case JS_TAG_UNDEFINED: - { - bfdec_t *r; - if (!allow_null_or_undefined) - goto fail; - val = JS_NewBigDecimal(ctx); - if (JS_IsException(val)) - break; - r = JS_GetBigDecimal(val); - bfdec_set_nan(r); - } - break; - default: - fail: - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert to bigdecimal"); - } - return val; -} - -static JSValue js_bigdecimal_constructor(JSContext *ctx, - JSValueConst new_target, - int argc, JSValueConst *argv) -{ - JSValue val; - if (!JS_IsUndefined(new_target)) - return JS_ThrowTypeError(ctx, "not a constructor"); - if (argc == 0) { - bfdec_t *r; - val = JS_NewBigDecimal(ctx); - if (JS_IsException(val)) - return val; - r = JS_GetBigDecimal(val); - bfdec_set_zero(r, 0); - } else { - val = JS_ToBigDecimalFree(ctx, JS_DupValue(ctx, argv0), FALSE); - } - return val; -} - -static JSValue js_thisBigDecimalValue(JSContext *ctx, JSValueConst this_val) -{ - if (JS_IsBigDecimal(this_val)) - return JS_DupValue(ctx, this_val); - - if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(this_val); - if (p->class_id == JS_CLASS_BIG_DECIMAL) { - if (JS_IsBigDecimal(p->u.object_data)) - return JS_DupValue(ctx, p->u.object_data); - } - } - return JS_ThrowTypeError(ctx, "not a bigdecimal"); -} - -static JSValue js_bigdecimal_toString(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val; - - val = js_thisBigDecimalValue(ctx, this_val); - if (JS_IsException(val)) - return val; - return JS_ToStringFree(ctx, val); -} - -static JSValue js_bigdecimal_valueOf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return js_thisBigDecimalValue(ctx, this_val); -} - -static int js_bigdecimal_get_rnd_mode(JSContext *ctx, JSValueConst obj) -{ - const char *str; - size_t size; - int rnd_mode; + JSCFunctionType ft; + int i; - str = JS_ToCStringLen(ctx, &size, obj); - if (!str) + /* warning: ordering is tricky */ + ctx->class_protoJS_CLASS_OBJECT = + JS_NewObjectProtoClassAlloc(ctx, JS_NULL, JS_CLASS_OBJECT, + countof(js_object_proto_funcs) + 1); + if (JS_IsException(ctx->class_protoJS_CLASS_OBJECT)) return -1; - if (strlen(str) != size) - goto invalid_rounding_mode; - if (!strcmp(str, "floor")) { - rnd_mode = BF_RNDD; - } else if (!strcmp(str, "ceiling")) { - rnd_mode = BF_RNDU; - } else if (!strcmp(str, "down")) { - rnd_mode = BF_RNDZ; - } else if (!strcmp(str, "up")) { - rnd_mode = BF_RNDA; - } else if (!strcmp(str, "half-even")) { - rnd_mode = BF_RNDN; - } else if (!strcmp(str, "half-up")) { - rnd_mode = BF_RNDNA; - } else { - invalid_rounding_mode: - JS_FreeCString(ctx, str); - JS_ThrowTypeError(ctx, "invalid rounding mode"); - return -1; - } - JS_FreeCString(ctx, str); - return rnd_mode; -} - -typedef struct { - int64_t prec; - bf_flags_t flags; -} BigDecimalEnv; - -static int js_bigdecimal_get_env(JSContext *ctx, BigDecimalEnv *fe, - JSValueConst obj) -{ - JSValue prop; - int64_t val; - BOOL has_prec; - int rnd_mode; + JS_SetImmutablePrototype(ctx, ctx->class_protoJS_CLASS_OBJECT); - if (!JS_IsObject(obj)) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - prop = JS_GetProperty(ctx, obj, JS_ATOM_roundingMode); - if (JS_IsException(prop)) - return -1; - rnd_mode = js_bigdecimal_get_rnd_mode(ctx, prop); - JS_FreeValue(ctx, prop); - if (rnd_mode < 0) + /* 2 more properties: caller and arguments */ + ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0, + JS_CFUNC_generic, 0, + ctx->class_protoJS_CLASS_OBJECT, + countof(js_function_proto_funcs) + 3 + 2); + if (JS_IsException(ctx->function_proto)) return -1; - fe->flags = rnd_mode; + ctx->class_protoJS_CLASS_BYTECODE_FUNCTION = JS_DupValue(ctx, ctx->function_proto); - prop = JS_GetProperty(ctx, obj, JS_ATOM_maximumSignificantDigits); - if (JS_IsException(prop)) + ctx->global_obj = JS_NewObjectProtoClassAlloc(ctx, ctx->class_protoJS_CLASS_OBJECT, + JS_CLASS_OBJECT, 64); + if (JS_IsException(ctx->global_obj)) + return -1; + ctx->global_var_obj = JS_NewObjectProtoClassAlloc(ctx, JS_NULL, + JS_CLASS_OBJECT, 16); + if (JS_IsException(ctx->global_var_obj)) return -1; - has_prec = FALSE; - if (!JS_IsUndefined(prop)) { - if (JS_ToInt64SatFree(ctx, &val, prop)) - return -1; - if (val < 1 || val > BF_PREC_MAX) - goto invalid_precision; - fe->prec = val; - has_prec = TRUE; - } - prop = JS_GetProperty(ctx, obj, JS_ATOM_maximumFractionDigits); - if (JS_IsException(prop)) + /* Error */ + ft.generic_magic = js_error_constructor; + obj = JS_NewCConstructor(ctx, JS_CLASS_ERROR, "Error", + ft.generic, 1, JS_CFUNC_constructor_or_func_magic, -1, + JS_UNDEFINED, + js_error_funcs, countof(js_error_funcs), + js_error_proto_funcs, countof(js_error_proto_funcs), + 0); + if (JS_IsException(obj)) return -1; - if (!JS_IsUndefined(prop)) { - if (has_prec) { - JS_FreeValue(ctx, prop); - JS_ThrowTypeError(ctx, "cannot provide both maximumSignificantDigits and maximumFractionDigits"); + + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JSValue func_obj; + const JSCFunctionListEntry *funcs; + int n_args; + char bufATOM_GET_STR_BUF_SIZE; + const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), + JS_ATOM_EvalError + i); + n_args = 1 + (i == JS_AGGREGATE_ERROR); + funcs = js_native_error_proto_funcs + 2 * i; + func_obj = JS_NewCConstructor(ctx, -1, name, + ft.generic, n_args, JS_CFUNC_constructor_or_func_magic, i, + obj, + NULL, 0, + funcs, 2, + 0); + if (JS_IsException(func_obj)) { + JS_FreeValue(ctx, obj); return -1; } - if (JS_ToInt64SatFree(ctx, &val, prop)) - return -1; - if (val < 0 || val > BF_PREC_MAX) { - invalid_precision: - JS_ThrowTypeError(ctx, "invalid precision"); + ctx->native_error_protoi = JS_GetProperty(ctx, func_obj, JS_ATOM_prototype); + JS_FreeValue(ctx, func_obj); + if (JS_IsException(ctx->native_error_protoi)) { + JS_FreeValue(ctx, obj); return -1; } - fe->prec = val; - fe->flags |= BF_FLAG_RADPNT_PREC; - has_prec = TRUE; - } - if (!has_prec) { - JS_ThrowTypeError(ctx, "precision must be present"); - return -1; - } - return 0; -} - - -static JSValue js_bigdecimal_fop(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - bfdec_t *a, *b, r_s, *r = &r_s; - JSValue op1, op2, res; - BigDecimalEnv fe_s, *fe = &fe_s; - int op_count, ret; - - if (magic == MATH_OP_SQRT || - magic == MATH_OP_ROUND) - op_count = 1; - else - op_count = 2; - - op1 = JS_ToNumeric(ctx, argv0); - if (JS_IsException(op1)) - return op1; - a = JS_ToBigDecimal(ctx, op1); - if (!a) { - JS_FreeValue(ctx, op1); - return JS_EXCEPTION; - } - if (op_count >= 2) { - op2 = JS_ToNumeric(ctx, argv1); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - return op2; - } - b = JS_ToBigDecimal(ctx, op2); - if (!b) - goto fail; - } else { - op2 = JS_UNDEFINED; - b = NULL; - } - fe->flags = BF_RNDZ; - fe->prec = BF_PREC_INF; - if (op_count < argc) { - if (js_bigdecimal_get_env(ctx, fe, argvop_count)) - goto fail; - } - - res = JS_NewBigDecimal(ctx); - if (JS_IsException(res)) { - fail: - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return JS_EXCEPTION; - } - r = JS_GetBigDecimal(res); - switch (magic) { - case MATH_OP_ADD: - ret = bfdec_add(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_SUB: - ret = bfdec_sub(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_MUL: - ret = bfdec_mul(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_DIV: - ret = bfdec_div(r, a, b, fe->prec, fe->flags); - break; - case MATH_OP_FMOD: - ret = bfdec_rem(r, a, b, fe->prec, fe->flags, BF_RNDZ); - break; - case MATH_OP_SQRT: - ret = bfdec_sqrt(r, a, fe->prec, fe->flags); - break; - case MATH_OP_ROUND: - ret = bfdec_set(r, a); - if (!(ret & BF_ST_MEM_ERROR)) - ret = bfdec_round(r, fe->prec, fe->flags); - break; - default: - abort(); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - ret &= BF_ST_MEM_ERROR | BF_ST_DIVIDE_ZERO | BF_ST_INVALID_OP | - BF_ST_OVERFLOW; - if (ret != 0) { - JS_FreeValue(ctx, res); - return throw_bf_exception(ctx, ret); - } else { - return res; - } -} - -static JSValue js_bigdecimal_toFixed(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - int64_t f; - int rnd_mode; - - val = js_thisBigDecimalValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (JS_ToInt64Sat(ctx, &f, argv0)) - goto fail; - if (f < 0 || f > BF_PREC_MAX) { - JS_ThrowRangeError(ctx, "invalid number of digits"); - goto fail; - } - rnd_mode = BF_RNDNA; - if (argc > 1) { - rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv1); - if (rnd_mode < 0) - goto fail; - } - ret = js_bigdecimal_to_string1(ctx, val, f, rnd_mode | BF_FTOA_FORMAT_FRAC); - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue js_bigdecimal_toExponential(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - int64_t f; - int rnd_mode; - - val = js_thisBigDecimalValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (JS_ToInt64Sat(ctx, &f, argv0)) - goto fail; - if (JS_IsUndefined(argv0)) { - ret = js_bigdecimal_to_string1(ctx, val, 0, - BF_RNDN | BF_FTOA_FORMAT_FREE_MIN | BF_FTOA_FORCE_EXP); - } else { - if (f < 0 || f > BF_PREC_MAX) { - JS_ThrowRangeError(ctx, "invalid number of digits"); - goto fail; - } - rnd_mode = BF_RNDNA; - if (argc > 1) { - rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv1); - if (rnd_mode < 0) - goto fail; - } - ret = js_bigdecimal_to_string1(ctx, val, f + 1, - rnd_mode | BF_FTOA_FORMAT_FIXED | BF_FTOA_FORCE_EXP); - } - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue js_bigdecimal_toPrecision(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - int64_t p; - int rnd_mode; - - val = js_thisBigDecimalValue(ctx, this_val); - if (JS_IsException(val)) - return val; - if (JS_IsUndefined(argv0)) { - return JS_ToStringFree(ctx, val); - } - if (JS_ToInt64Sat(ctx, &p, argv0)) - goto fail; - if (p < 1 || p > BF_PREC_MAX) { - JS_ThrowRangeError(ctx, "invalid number of digits"); - goto fail; - } - rnd_mode = BF_RNDNA; - if (argc > 1) { - rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv1); - if (rnd_mode < 0) - goto fail; - } - ret = js_bigdecimal_to_string1(ctx, val, p, - rnd_mode | BF_FTOA_FORMAT_FIXED); - JS_FreeValue(ctx, val); - return ret; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static const JSCFunctionListEntry js_bigdecimal_proto_funcs = { - JS_CFUNC_DEF("toString", 0, js_bigdecimal_toString ), - JS_CFUNC_DEF("valueOf", 0, js_bigdecimal_valueOf ), - JS_CFUNC_DEF("toPrecision", 1, js_bigdecimal_toPrecision ), - JS_CFUNC_DEF("toFixed", 1, js_bigdecimal_toFixed ), - JS_CFUNC_DEF("toExponential", 1, js_bigdecimal_toExponential ), -}; - -static const JSCFunctionListEntry js_bigdecimal_funcs = { - JS_CFUNC_MAGIC_DEF("add", 2, js_bigdecimal_fop, MATH_OP_ADD ), - JS_CFUNC_MAGIC_DEF("sub", 2, js_bigdecimal_fop, MATH_OP_SUB ), - JS_CFUNC_MAGIC_DEF("mul", 2, js_bigdecimal_fop, MATH_OP_MUL ), - JS_CFUNC_MAGIC_DEF("div", 2, js_bigdecimal_fop, MATH_OP_DIV ), - JS_CFUNC_MAGIC_DEF("mod", 2, js_bigdecimal_fop, MATH_OP_FMOD ), - JS_CFUNC_MAGIC_DEF("round", 1, js_bigdecimal_fop, MATH_OP_ROUND ), - JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigdecimal_fop, MATH_OP_SQRT ), -}; - -void JS_AddIntrinsicBigDecimal(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - JSValueConst obj1; - - rt->bigdecimal_ops.to_string = js_bigdecimal_to_string; - rt->bigdecimal_ops.from_string = js_string_to_bigdecimal; - rt->bigdecimal_ops.unary_arith = js_unary_arith_bigdecimal; - rt->bigdecimal_ops.binary_arith = js_binary_arith_bigdecimal; - rt->bigdecimal_ops.compare = js_compare_bigdecimal; - - ctx->class_protoJS_CLASS_BIG_DECIMAL = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_BIG_DECIMAL, - js_bigdecimal_proto_funcs, - countof(js_bigdecimal_proto_funcs)); - obj1 = JS_NewGlobalCConstructor(ctx, "BigDecimal", - js_bigdecimal_constructor, 1, - ctx->class_protoJS_CLASS_BIG_DECIMAL); - JS_SetPropertyFunctionList(ctx, obj1, js_bigdecimal_funcs, - countof(js_bigdecimal_funcs)); -} - -void JS_EnableBignumExt(JSContext *ctx, BOOL enable) -{ - ctx->bignum_ext = enable; -} - -#endif /* CONFIG_BIGNUM */ - -static const char * const native_error_nameJS_NATIVE_ERROR_COUNT = { - "EvalError", "RangeError", "ReferenceError", - "SyntaxError", "TypeError", "URIError", - "InternalError", "AggregateError", -}; - -/* Minimum amount of objects to be able to compile code and display - error messages. No JSAtom should be allocated by this function. */ -static void JS_AddIntrinsicBasicObjects(JSContext *ctx) -{ - JSValue proto; - int i; - - ctx->class_protoJS_CLASS_OBJECT = JS_NewObjectProto(ctx, JS_NULL); - ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0, - JS_CFUNC_generic, 0, - ctx->class_protoJS_CLASS_OBJECT); - ctx->class_protoJS_CLASS_BYTECODE_FUNCTION = JS_DupValue(ctx, ctx->function_proto); - ctx->class_protoJS_CLASS_ERROR = JS_NewObject(ctx); -#if 0 - /* these are auto-initialized from js_error_proto_funcs, - but delaying might be a problem */ - JS_DefinePropertyValue(ctx, ctx->class_protoJS_CLASS_ERROR, JS_ATOM_name, - JS_AtomToString(ctx, JS_ATOM_Error), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, ctx->class_protoJS_CLASS_ERROR, JS_ATOM_message, - JS_AtomToString(ctx, JS_ATOM_empty_string), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); -#endif - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_ERROR, - js_error_proto_funcs, - countof(js_error_proto_funcs)); - - for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - proto = JS_NewObjectProto(ctx, ctx->class_protoJS_CLASS_ERROR); - JS_DefinePropertyValue(ctx, proto, JS_ATOM_name, - JS_NewAtomString(ctx, native_error_namei), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, proto, JS_ATOM_message, - JS_AtomToString(ctx, JS_ATOM_empty_string), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - ctx->native_error_protoi = proto; } + JS_FreeValue(ctx, obj); - /* the array prototype is an array */ - ctx->class_protoJS_CLASS_ARRAY = - JS_NewObjectProtoClass(ctx, ctx->class_protoJS_CLASS_OBJECT, - JS_CLASS_ARRAY); + /* Array */ + obj = JS_NewCConstructor(ctx, JS_CLASS_ARRAY, "Array", + js_array_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_array_funcs, countof(js_array_funcs), + js_array_proto_funcs, countof(js_array_proto_funcs), + JS_NEW_CTOR_PROTO_CLASS); + if (JS_IsException(obj)) + return -1; + ctx->array_ctor = obj; ctx->array_shape = js_new_shape2(ctx, get_proto_obj(ctx->class_protoJS_CLASS_ARRAY), JS_PROP_INITIAL_HASH_SIZE, 1); - add_shape_property(ctx, &ctx->array_shape, NULL, - JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH); + if (!ctx->array_shape) + return -1; + if (add_shape_property(ctx, &ctx->array_shape, NULL, + JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH)) + return -1; + ctx->std_array_prototype = TRUE; - /* XXX: could test it on first context creation to ensure that no - new atoms are created in JS_AddIntrinsicBasicObjects(). It is - necessary to avoid useless renumbering of atoms after - JS_EvalBinary() if it is done just after - JS_AddIntrinsicBasicObjects(). */ - // assert(ctx->rt->atom_count == JS_ATOM_END); + return 0; } -void JS_AddIntrinsicBaseObjects(JSContext *ctx) +int JS_AddIntrinsicBaseObjects(JSContext *ctx) { - int i; - JSValueConst obj, number_obj; - JSValue obj1; + JSValue obj1, obj2; + JSCFunctionType ft; ctx->throw_type_error = JS_NewCFunction(ctx, js_throw_type_error, NULL, 0); - + if (JS_IsException(ctx->throw_type_error)) + return -1; /* add caller and arguments properties to throw a TypeError */ - obj1 = JS_NewCFunction(ctx, js_function_proto_caller, NULL, 0); - JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_UNDEFINED, - obj1, ctx->throw_type_error, - JS_PROP_HAS_GET | JS_PROP_HAS_SET | - JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); - JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_arguments, JS_UNDEFINED, - obj1, ctx->throw_type_error, - JS_PROP_HAS_GET | JS_PROP_HAS_SET | - JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); - JS_FreeValue(ctx, obj1); + if (JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_UNDEFINED, + ctx->throw_type_error, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE) < 0) + return -1; + if (JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_arguments, JS_UNDEFINED, + ctx->throw_type_error, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE) < 0) + return -1; JS_FreeValue(ctx, js_object_seal(ctx, JS_UNDEFINED, 1, (JSValueConst *)&ctx->throw_type_error, 1)); - ctx->global_obj = JS_NewObject(ctx); - ctx->global_var_obj = JS_NewObjectProto(ctx, JS_NULL); - /* Object */ - obj = JS_NewGlobalCConstructor(ctx, "Object", js_object_constructor, 1, - ctx->class_protoJS_CLASS_OBJECT); - JS_SetPropertyFunctionList(ctx, obj, js_object_funcs, countof(js_object_funcs)); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_OBJECT, - js_object_proto_funcs, countof(js_object_proto_funcs)); + obj1 = JS_NewCConstructor(ctx, JS_CLASS_OBJECT, "Object", + js_object_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_object_funcs, countof(js_object_funcs), + js_object_proto_funcs, countof(js_object_proto_funcs), + JS_NEW_CTOR_PROTO_EXIST); + if (JS_IsException(obj1)) + return -1; + JS_FreeValue(ctx, obj1); /* Function */ - JS_SetPropertyFunctionList(ctx, ctx->function_proto, js_function_proto_funcs, countof(js_function_proto_funcs)); - ctx->function_ctor = JS_NewCFunctionMagic(ctx, js_function_constructor, - "Function", 1, JS_CFUNC_constructor_or_func_magic, - JS_FUNC_NORMAL); - JS_NewGlobalCConstructor2(ctx, JS_DupValue(ctx, ctx->function_ctor), "Function", - ctx->function_proto); - - /* Error */ - obj1 = JS_NewCFunctionMagic(ctx, js_error_constructor, - "Error", 1, JS_CFUNC_constructor_or_func_magic, -1); - JS_NewGlobalCConstructor2(ctx, obj1, - "Error", ctx->class_protoJS_CLASS_ERROR); + ft.generic_magic = js_function_constructor; + obj1 = JS_NewCConstructor(ctx, JS_CLASS_BYTECODE_FUNCTION, "Function", + ft.generic, 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_NORMAL, + JS_UNDEFINED, + NULL, 0, + js_function_proto_funcs, countof(js_function_proto_funcs), + JS_NEW_CTOR_PROTO_EXIST); + if (JS_IsException(obj1)) + return -1; + ctx->function_ctor = obj1; - for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - JSValue func_obj; - int n_args; - n_args = 1 + (i == JS_AGGREGATE_ERROR); - func_obj = JS_NewCFunction3(ctx, (JSCFunction *)js_error_constructor, - native_error_namei, n_args, - JS_CFUNC_constructor_or_func_magic, i, obj1); - JS_NewGlobalCConstructor2(ctx, func_obj, native_error_namei, - ctx->native_error_protoi); + /* Iterator */ + obj2 = JS_NewCConstructor(ctx, JS_CLASS_ITERATOR, "Iterator", + js_iterator_constructor, 0, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_iterator_funcs, countof(js_iterator_funcs), + js_iterator_proto_funcs, countof(js_iterator_proto_funcs), + 0); + if (JS_IsException(obj2)) + return -1; + // quirk: Iterator.prototype.constructor is an accessor property + // TODO(bnoordhuis) mildly inefficient because JS_NewGlobalCConstructor + // first creates a .constructor value property that we then replace with + // an accessor + obj1 = JS_NewCFunctionData(ctx, js_iterator_constructor_getset, + 0, 0, 1, (JSValueConst *)&obj2); + if (JS_IsException(obj1)) { + JS_FreeValue(ctx, obj2); + return -1; + } + if (JS_DefineProperty(ctx, ctx->class_protoJS_CLASS_ITERATOR, + JS_ATOM_constructor, JS_UNDEFINED, + obj1, obj1, + JS_PROP_HAS_GET | JS_PROP_HAS_SET | JS_PROP_CONFIGURABLE) < 0) { + JS_FreeValue(ctx, obj2); + JS_FreeValue(ctx, obj1); + return -1; } + JS_FreeValue(ctx, obj1); + ctx->iterator_ctor = obj2; - /* Iterator prototype */ - ctx->iterator_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->iterator_proto, - js_iterator_proto_funcs, - countof(js_iterator_proto_funcs)); - - /* Array */ - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_ARRAY, - js_array_proto_funcs, - countof(js_array_proto_funcs)); - - obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1, - ctx->class_protoJS_CLASS_ARRAY); - ctx->array_ctor = JS_DupValue(ctx, obj); - JS_SetPropertyFunctionList(ctx, obj, js_array_funcs, - countof(js_array_funcs)); + ctx->class_protoJS_CLASS_ITERATOR_HELPER = + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_iterator_helper_proto_funcs, + countof(js_iterator_helper_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_ITERATOR_HELPER)) + return -1; - /* XXX: create auto_initializer */ - { - /* initialize Array.prototypeSymbol.unscopables */ - char const unscopables = "copyWithin" "\0" "entries" "\0" "fill" "\0" "find" "\0" - "findIndex" "\0" "flat" "\0" "flatMap" "\0" "includes" "\0" "keys" "\0" "values" "\0"; - const char *p = unscopables; - obj1 = JS_NewObjectProto(ctx, JS_NULL); - for(p = unscopables; *p; p += strlen(p) + 1) { - JS_DefinePropertyValueStr(ctx, obj1, p, JS_TRUE, JS_PROP_C_W_E); - } - JS_DefinePropertyValue(ctx, ctx->class_protoJS_CLASS_ARRAY, - JS_ATOM_Symbol_unscopables, obj1, - JS_PROP_CONFIGURABLE); - } + ctx->class_protoJS_CLASS_ITERATOR_WRAP = + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_iterator_wrap_proto_funcs, + countof(js_iterator_wrap_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_ITERATOR_WRAP)) + return -1; /* needed to initialize argumentsSymbol.iterator */ ctx->array_proto_values = JS_GetProperty(ctx, ctx->class_protoJS_CLASS_ARRAY, JS_ATOM_values); + if (JS_IsException(ctx->array_proto_values)) + return -1; - ctx->class_protoJS_CLASS_ARRAY_ITERATOR = JS_NewObjectProto(ctx, ctx->iterator_proto); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_ARRAY_ITERATOR, - js_array_iterator_proto_funcs, - countof(js_array_iterator_proto_funcs)); + ctx->class_protoJS_CLASS_ARRAY_ITERATOR = + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_array_iterator_proto_funcs, + countof(js_array_iterator_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_ARRAY_ITERATOR)) + return -1; /* parseFloat and parseInteger must be defined before Number because of the Number.parseFloat and Number.parseInteger aliases */ - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs, - countof(js_global_funcs)); + if (JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs, + countof(js_global_funcs))) + return -1; /* Number */ - ctx->class_protoJS_CLASS_NUMBER = JS_NewObjectProtoClass(ctx, ctx->class_protoJS_CLASS_OBJECT, - JS_CLASS_NUMBER); - JS_SetObjectData(ctx, ctx->class_protoJS_CLASS_NUMBER, JS_NewInt32(ctx, 0)); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_NUMBER, - js_number_proto_funcs, - countof(js_number_proto_funcs)); - number_obj = JS_NewGlobalCConstructor(ctx, "Number", js_number_constructor, 1, - ctx->class_protoJS_CLASS_NUMBER); - JS_SetPropertyFunctionList(ctx, number_obj, js_number_funcs, countof(js_number_funcs)); + obj1 = JS_NewCConstructor(ctx, JS_CLASS_NUMBER, "Number", + js_number_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_number_funcs, countof(js_number_funcs), + js_number_proto_funcs, countof(js_number_proto_funcs), + JS_NEW_CTOR_PROTO_CLASS); + if (JS_IsException(obj1)) + return -1; + JS_FreeValue(ctx, obj1); + if (JS_SetObjectData(ctx, ctx->class_protoJS_CLASS_NUMBER, JS_NewInt32(ctx, 0))) + return -1; /* Boolean */ - ctx->class_protoJS_CLASS_BOOLEAN = JS_NewObjectProtoClass(ctx, ctx->class_protoJS_CLASS_OBJECT, - JS_CLASS_BOOLEAN); - JS_SetObjectData(ctx, ctx->class_protoJS_CLASS_BOOLEAN, JS_NewBool(ctx, FALSE)); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_BOOLEAN, js_boolean_proto_funcs, - countof(js_boolean_proto_funcs)); - JS_NewGlobalCConstructor(ctx, "Boolean", js_boolean_constructor, 1, - ctx->class_protoJS_CLASS_BOOLEAN); + obj1 = JS_NewCConstructor(ctx, JS_CLASS_BOOLEAN, "Boolean", + js_boolean_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + NULL, 0, + js_boolean_proto_funcs, countof(js_boolean_proto_funcs), + JS_NEW_CTOR_PROTO_CLASS); + if (JS_IsException(obj1)) + return -1; + JS_FreeValue(ctx, obj1); + if (JS_SetObjectData(ctx, ctx->class_protoJS_CLASS_BOOLEAN, JS_NewBool(ctx, FALSE))) + return -1; /* String */ - ctx->class_protoJS_CLASS_STRING = JS_NewObjectProtoClass(ctx, ctx->class_protoJS_CLASS_OBJECT, - JS_CLASS_STRING); - JS_SetObjectData(ctx, ctx->class_protoJS_CLASS_STRING, JS_AtomToString(ctx, JS_ATOM_empty_string)); - obj = JS_NewGlobalCConstructor(ctx, "String", js_string_constructor, 1, - ctx->class_protoJS_CLASS_STRING); - JS_SetPropertyFunctionList(ctx, obj, js_string_funcs, - countof(js_string_funcs)); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_STRING, js_string_proto_funcs, - countof(js_string_proto_funcs)); - - ctx->class_protoJS_CLASS_STRING_ITERATOR = JS_NewObjectProto(ctx, ctx->iterator_proto); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_STRING_ITERATOR, - js_string_iterator_proto_funcs, - countof(js_string_iterator_proto_funcs)); + obj1 = JS_NewCConstructor(ctx, JS_CLASS_STRING, "String", + js_string_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_string_funcs, countof(js_string_funcs), + js_string_proto_funcs, countof(js_string_proto_funcs), + JS_NEW_CTOR_PROTO_CLASS); + if (JS_IsException(obj1)) + return -1; + JS_FreeValue(ctx, obj1); + if (JS_SetObjectData(ctx, ctx->class_protoJS_CLASS_STRING, JS_AtomToString(ctx, JS_ATOM_empty_string))) + return -1; + + ctx->class_protoJS_CLASS_STRING_ITERATOR = + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_string_iterator_proto_funcs, + countof(js_string_iterator_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_STRING_ITERATOR)) + return -1; /* Math: create as autoinit object */ js_random_init(ctx); - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj)); + if (JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj))) + return -1; /* ES6 Reflect: create as autoinit object */ - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj, countof(js_reflect_obj)); + if (JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj, countof(js_reflect_obj))) + return -1; /* ES6 Symbol */ - ctx->class_protoJS_CLASS_SYMBOL = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_SYMBOL, js_symbol_proto_funcs, - countof(js_symbol_proto_funcs)); - obj = JS_NewGlobalCConstructor(ctx, "Symbol", js_symbol_constructor, 0, - ctx->class_protoJS_CLASS_SYMBOL); - JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs, - countof(js_symbol_funcs)); - for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) { - char bufATOM_GET_STR_BUF_SIZE; - const char *str, *p; - str = JS_AtomGetStr(ctx, buf, sizeof(buf), i); - /* skip "Symbol." */ - p = strchr(str, '.'); - if (p) - str = p + 1; - JS_DefinePropertyValueStr(ctx, obj, str, JS_AtomToValue(ctx, i), 0); - } + obj1 = JS_NewCConstructor(ctx, JS_CLASS_SYMBOL, "Symbol", + js_symbol_constructor, 0, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_symbol_funcs, countof(js_symbol_funcs), + js_symbol_proto_funcs, countof(js_symbol_proto_funcs), + 0); + if (JS_IsException(obj1)) + return -1; + JS_FreeValue(ctx, obj1); /* ES6 Generator */ - ctx->class_protoJS_CLASS_GENERATOR = JS_NewObjectProto(ctx, ctx->iterator_proto); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_GENERATOR, - js_generator_proto_funcs, - countof(js_generator_proto_funcs)); - - ctx->class_protoJS_CLASS_GENERATOR_FUNCTION = JS_NewObjectProto(ctx, ctx->function_proto); - obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor, - "GeneratorFunction", 1, - JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR); - JS_SetPropertyFunctionList(ctx, - ctx->class_protoJS_CLASS_GENERATOR_FUNCTION, - js_generator_function_proto_funcs, - countof(js_generator_function_proto_funcs)); - JS_SetConstructor2(ctx, ctx->class_protoJS_CLASS_GENERATOR_FUNCTION, - ctx->class_protoJS_CLASS_GENERATOR, - JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); - JS_SetConstructor2(ctx, obj1, ctx->class_protoJS_CLASS_GENERATOR_FUNCTION, - 0, JS_PROP_CONFIGURABLE); + ctx->class_protoJS_CLASS_GENERATOR = + JS_NewObjectProtoList(ctx, ctx->class_protoJS_CLASS_ITERATOR, + js_generator_proto_funcs, + countof(js_generator_proto_funcs)); + if (JS_IsException(ctx->class_protoJS_CLASS_GENERATOR)) + return -1; + + ft.generic_magic = js_function_constructor; + obj1 = JS_NewCConstructor(ctx, JS_CLASS_GENERATOR_FUNCTION, "GeneratorFunction", + ft.generic, 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR, + ctx->function_ctor, + NULL, 0, + js_generator_function_proto_funcs, + countof(js_generator_function_proto_funcs), + JS_NEW_CTOR_NO_GLOBAL | JS_NEW_CTOR_READONLY); + if (JS_IsException(obj1)) + return -1; JS_FreeValue(ctx, obj1); + if (JS_SetConstructor2(ctx, ctx->class_protoJS_CLASS_GENERATOR_FUNCTION, + ctx->class_protoJS_CLASS_GENERATOR, + JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE)) + return -1; /* global properties */ - ctx->eval_obj = JS_NewCFunction(ctx, js_global_eval, "eval", 1); - JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_eval, - JS_DupValue(ctx, ctx->eval_obj), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + ctx->eval_obj = JS_GetProperty(ctx, ctx->global_obj, JS_ATOM_eval); + if (JS_IsException(ctx->eval_obj)) + return -1; - JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis, - JS_DupValue(ctx, ctx->global_obj), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + if (JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis, + JS_DupValue(ctx, ctx->global_obj), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE) < 0) + return -1; + + /* BigInt */ + if (JS_AddIntrinsicBigInt(ctx)) + return -1; + return 0; } /* Typed Arrays */ static uint8_t const typed_array_size_log2JS_TYPED_ARRAY_COUNT = { 0, 0, 0, 1, 1, 2, 2, -#ifdef CONFIG_BIGNUM - 3, 3, /* BigInt64Array, BigUint64Array */ -#endif - 2, 3 + 3, 3, // BigInt64Array, BigUint64Array + 1, 2, 3 // Float16Array, Float32Array, Float64Array }; static JSValue js_array_buffer_constructor3(JSContext *ctx, JSValueConst new_target, - uint64_t len, JSClassID class_id, + uint64_t len, uint64_t *max_len, + JSClassID class_id, uint8_t *buf, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag) @@ -51298,7 +54930,15 @@ JSRuntime *rt = ctx->rt; JSValue obj; JSArrayBuffer *abuf = NULL; + uint64_t sab_alloc_len; + if (!alloc_flag && buf && max_len && free_func != js_array_buffer_free) { + // not observable from JS land, only through C API misuse; + // JS code cannot create externally managed buffers directly + return JS_ThrowInternalError(ctx, + "resizable ArrayBuffers not supported " + "for externally managed buffers"); + } obj = js_create_from_ctor(ctx, new_target, class_id); if (JS_IsException(obj)) return obj; @@ -51307,18 +54947,26 @@ JS_ThrowRangeError(ctx, "invalid array buffer length"); goto fail; } + if (max_len && *max_len > INT32_MAX) { + JS_ThrowRangeError(ctx, "invalid max array buffer length"); + goto fail; + } abuf = js_malloc(ctx, sizeof(*abuf)); if (!abuf) goto fail; abuf->byte_length = len; + abuf->max_byte_length = max_len ? *max_len : -1; if (alloc_flag) { if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER && rt->sab_funcs.sab_alloc) { + // TOOD(bnoordhuis) resizing backing memory for SABs atomically + // is hard so we cheat and allocate |maxByteLength| bytes upfront + sab_alloc_len = max_len ? *max_len : len; abuf->data = rt->sab_funcs.sab_alloc(rt->sab_funcs.sab_opaque, - max_int(len, 1)); + max_int(sab_alloc_len, 1)); if (!abuf->data) goto fail; - memset(abuf->data, 0, len); + memset(abuf->data, 0, sab_alloc_len); } else { /* the allocation must be done after the object creation */ abuf->data = js_mallocz(ctx, max_int(len, 1)); @@ -51354,18 +55002,19 @@ static JSValue js_array_buffer_constructor2(JSContext *ctx, JSValueConst new_target, - uint64_t len, JSClassID class_id) + uint64_t len, uint64_t *max_len, + JSClassID class_id) { - return js_array_buffer_constructor3(ctx, new_target, len, class_id, + return js_array_buffer_constructor3(ctx, new_target, len, max_len, class_id, NULL, js_array_buffer_free, NULL, TRUE); } static JSValue js_array_buffer_constructor1(JSContext *ctx, JSValueConst new_target, - uint64_t len) + uint64_t len, uint64_t *max_len) { - return js_array_buffer_constructor2(ctx, new_target, len, + return js_array_buffer_constructor2(ctx, new_target, len, max_len, JS_CLASS_ARRAY_BUFFER); } @@ -51373,39 +55022,70 @@ JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL is_shared) { - return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, - is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER, + JSClassID class_id = + is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER; + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, class_id, buf, free_func, opaque, FALSE); } /* create a new ArrayBuffer of length 'len' and copy 'buf' to it */ JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len) { - return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, JS_CLASS_ARRAY_BUFFER, (uint8_t *)buf, js_array_buffer_free, NULL, TRUE); } +static JSValue js_array_buffer_constructor0(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv, + JSClassID class_id) + { + uint64_t len, max_len, *pmax_len = NULL; + JSValue obj, val; + int64_t i; + + if (JS_ToIndex(ctx, &len, argv0)) + return JS_EXCEPTION; + if (argc < 2) + goto next; + if (!JS_IsObject(argv1)) + goto next; + obj = JS_ToObject(ctx, argv1); + if (JS_IsException(obj)) + return JS_EXCEPTION; + val = JS_GetProperty(ctx, obj, JS_ATOM_maxByteLength); + JS_FreeValue(ctx, obj); + if (JS_IsException(val)) + return JS_EXCEPTION; + if (JS_IsUndefined(val)) + goto next; + if (JS_ToInt64Free(ctx, &i, val)) + return JS_EXCEPTION; + // don't have to check i < 0 because len >= 0 + if (len > i || i > MAX_SAFE_INTEGER) + return JS_ThrowRangeError(ctx, "invalid array buffer max length"); + max_len = i; + pmax_len = &max_len; +next: + return js_array_buffer_constructor2(ctx, new_target, len, pmax_len, + class_id); +} + static JSValue js_array_buffer_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - uint64_t len; - if (JS_ToIndex(ctx, &len, argv0)) - return JS_EXCEPTION; - return js_array_buffer_constructor1(ctx, new_target, len); + return js_array_buffer_constructor0(ctx, new_target, argc, argv, + JS_CLASS_ARRAY_BUFFER); } static JSValue js_shared_array_buffer_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - uint64_t len; - if (JS_ToIndex(ctx, &len, argv0)) - return JS_EXCEPTION; - return js_array_buffer_constructor2(ctx, new_target, len, + return js_array_buffer_constructor0(ctx, new_target, argc, argv, JS_CLASS_SHARED_ARRAY_BUFFER); } @@ -51414,11 +55094,26 @@ { JSObject *p = JS_VALUE_GET_OBJ(val); JSArrayBuffer *abuf = p->u.array_buffer; + struct list_head *el, *el1; + if (abuf) { /* The ArrayBuffer finalizer may be called before the typed array finalizers using it, so abuf->array_list is not necessarily empty. */ - // assert(list_empty(&abuf->array_list)); + list_for_each_safe(el, el1, &abuf->array_list) { + JSTypedArray *ta; + JSObject *p1; + + ta = list_entry(el, JSTypedArray, link); + ta->link.prev = NULL; + ta->link.next = NULL; + p1 = ta->obj; + /* Note: the typed array length and offset fields are not modified */ + if (p1->class_id != JS_CLASS_DATAVIEW) { + p1->u.array.count = 0; + p1->u.array.u.ptr = NULL; + } + } if (abuf->shared && rt->sab_funcs.sab_free) { rt->sab_funcs.sab_free(rt->sab_funcs.sab_opaque, abuf->data); } else { @@ -51456,6 +55151,23 @@ return JS_ThrowTypeError(ctx, "ArrayBuffer is detached"); } +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "ArrayBuffer is detached or resized"); +} + +// #sec-get-arraybuffer.prototype.detached +static JSValue js_array_buffer_get_detached(JSContext *ctx, + JSValueConst this_val) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER); + if (!abuf) + return JS_EXCEPTION; + if (abuf->shared) + return JS_ThrowTypeError(ctx, "detached called on SharedArrayBuffer"); + return JS_NewBool(ctx, abuf->detached); +} + static JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValueConst this_val, int class_id) @@ -51467,6 +55179,28 @@ return JS_NewUint32(ctx, abuf->byte_length); } +static JSValue js_array_buffer_get_maxByteLength(JSContext *ctx, + JSValueConst this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (array_buffer_is_resizable(abuf)) + return JS_NewUint32(ctx, abuf->max_byte_length); + return JS_NewUint32(ctx, abuf->byte_length); +} + +static JSValue js_array_buffer_get_resizable(JSContext *ctx, + JSValueConst this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + return JS_NewBool(ctx, array_buffer_is_resizable(abuf)); +} + void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj) { JSArrayBuffer *abuf = JS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER); @@ -51528,6 +55262,167 @@ return NULL; } +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf) +{ + return abuf->max_byte_length >= 0; +} + +// ES #sec-arraybuffer.prototype.transfer +static JSValue js_array_buffer_transfer(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int transfer_to_fixed_length) +{ + JSArrayBuffer *abuf; + uint64_t new_len, *pmax_len, max_len; + + abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER); + if (!abuf) + return JS_EXCEPTION; + if (abuf->shared) + return JS_ThrowTypeError(ctx, "cannot transfer a SharedArrayBuffer"); + if (argc < 1 || JS_IsUndefined(argv0)) + new_len = abuf->byte_length; + else if (JS_ToIndex(ctx, &new_len, argv0)) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + pmax_len = NULL; + if (!transfer_to_fixed_length) { + if (array_buffer_is_resizable(abuf)) { // carry over maxByteLength + max_len = abuf->max_byte_length; + if (new_len > max_len) + return JS_ThrowTypeError(ctx, "invalid array buffer length"); + // TODO(bnoordhuis) support externally managed RABs + if (abuf->free_func == js_array_buffer_free) + pmax_len = &max_len; + } + } + /* create an empty AB */ + if (new_len == 0) { + JS_DetachArrayBuffer(ctx, this_val); + return js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, pmax_len, JS_CLASS_ARRAY_BUFFER); + } else { + uint64_t old_len; + uint8_t *bs, *new_bs; + JSFreeArrayBufferDataFunc *free_func; + + bs = abuf->data; + old_len = abuf->byte_length; + free_func = abuf->free_func; + + /* if length mismatch, realloc. Otherwise, use the same backing buffer. */ + if (new_len != old_len) { + /* XXX: we are currently limited to 2 GB */ + if (new_len > INT32_MAX) + return JS_ThrowRangeError(ctx, "invalid array buffer length"); + + if (free_func != js_array_buffer_free) { + /* cannot use js_realloc() because the buffer was + allocated with a custom allocator */ + new_bs = js_mallocz(ctx, new_len); + if (!new_bs) + return JS_EXCEPTION; + memcpy(new_bs, bs, min_int(old_len, new_len)); + abuf->free_func(ctx->rt, abuf->opaque, bs); + bs = new_bs; + free_func = js_array_buffer_free; + } else { + new_bs = js_realloc(ctx, bs, new_len); + if (!new_bs) + return JS_EXCEPTION; + bs = new_bs; + if (new_len > old_len) + memset(bs + old_len, 0, new_len - old_len); + } + } + /* neuter the backing buffer */ + abuf->data = NULL; + abuf->byte_length = 0; + abuf->detached = TRUE; + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len, pmax_len, + JS_CLASS_ARRAY_BUFFER, + bs, free_func, + NULL, FALSE); + } +} + +static JSValue js_array_buffer_resize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int class_id) +{ + uint32_t size_log2, size_elem; + struct list_head *el; + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSObject *p; + uint8_t *data; + int64_t len; + + abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &len, argv0)) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (!array_buffer_is_resizable(abuf)) + return JS_ThrowTypeError(ctx, "array buffer is not resizable"); + // TODO(bnoordhuis) support externally managed RABs + if (abuf->free_func != js_array_buffer_free) + return JS_ThrowTypeError(ctx, "external array buffer is not resizable"); + if (len < 0 || len > abuf->max_byte_length) { + bad_length: + return JS_ThrowRangeError(ctx, "invalid array buffer length"); + } + // SABs can only grow and we don't need to realloc because + // js_array_buffer_constructor3 commits all memory upfront; + // regular RABs are resizable both ways and realloc + if (abuf->shared) { + if (len < abuf->byte_length) + goto bad_length; + // Note this is off-spec; there's supposed to be a single atomic + // |byteLength| property that's shared across SABs but we store + // it per SAB instead. That means when thread A calls sab.grow(2) + // at time t0, and thread B calls sab.grow(1) at time t1, we don't + // throw a TypeError in thread B as the spec says we should, + // instead both threads get their own view of the backing memory, + // 2 bytes big in A, and 1 byte big in B + abuf->byte_length = len; + } else { + data = js_realloc(ctx, abuf->data, max_int(len, 1)); + if (!data) + return JS_EXCEPTION; + if (len > abuf->byte_length) + memset(&dataabuf->byte_length, 0, len - abuf->byte_length); + abuf->byte_length = len; + abuf->data = data; + } + data = abuf->data; + // update lengths of all typed arrays backed by this array buffer + list_for_each(el, &abuf->array_list) { + ta = list_entry(el, JSTypedArray, link); + p = ta->obj; + if (p->class_id == JS_CLASS_DATAVIEW) + continue; + p->u.array.count = 0; + p->u.array.u.ptr = NULL; + size_log2 = typed_array_size_log2(p->class_id); + size_elem = 1 << size_log2; + if (ta->track_rab) { + if (len >= (int64_t)ta->offset + size_elem) { + p->u.array.count = (len - ta->offset) >> size_log2; + p->u.array.u.ptr = &datata->offset; + } + } else { + if (len >= (int64_t)ta->offset + ta->length) { + p->u.array.count = ta->length >> size_log2; + p->u.array.u.ptr = &datata->offset; + } + } + } + return JS_UNDEFINED; +} + static JSValue js_array_buffer_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int class_id) @@ -51557,7 +55452,7 @@ return ctor; if (JS_IsUndefined(ctor)) { new_obj = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len, - class_id); + NULL, class_id); } else { JSValue args1; args0 = JS_NewInt64(ctx, new_len); @@ -51596,7 +55491,13 @@ static const JSCFunctionListEntry js_array_buffer_proto_funcs = { JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("maxByteLength", js_array_buffer_get_maxByteLength, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("resizable", js_array_buffer_get_resizable, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_DEF("detached", js_array_buffer_get_detached, NULL ), + JS_CFUNC_MAGIC_DEF("resize", 1, js_array_buffer_resize, JS_CLASS_ARRAY_BUFFER ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("transfer", 0, js_array_buffer_transfer, 0 ), + JS_CFUNC_MAGIC_DEF("transferToFixedLength", 0, js_array_buffer_transfer, 1 ), JS_PROP_STRING_DEF("Symbol.toStringTag", "ArrayBuffer", JS_PROP_CONFIGURABLE ), }; @@ -51608,59 +55509,85 @@ static const JSCFunctionListEntry js_shared_array_buffer_proto_funcs = { JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("maxByteLength", js_array_buffer_get_maxByteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("growable", js_array_buffer_get_resizable, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("grow", 1, js_array_buffer_resize, JS_CLASS_SHARED_ARRAY_BUFFER ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_SHARED_ARRAY_BUFFER ), JS_PROP_STRING_DEF("Symbol.toStringTag", "SharedArrayBuffer", JS_PROP_CONFIGURABLE ), }; -static JSObject *get_typed_array(JSContext *ctx, - JSValueConst this_val, - int is_dataview) +static JSObject *get_typed_array(JSContext *ctx, JSValueConst this_val) { JSObject *p; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) goto fail; p = JS_VALUE_GET_OBJ(this_val); - if (is_dataview) { - if (p->class_id != JS_CLASS_DATAVIEW) - goto fail; - } else { - if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && - p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { - fail: - JS_ThrowTypeError(ctx, "not a %s", is_dataview ? "DataView" : "TypedArray"); - return NULL; - } + if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { + fail: + JS_ThrowTypeError(ctx, "not a TypedArray"); + return NULL; } return p; } -/* WARNING: 'p' must be a typed array */ -static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p) +// is the typed array detached or out of bounds relative to its RAB? +// |p| must be a typed array, *not* a DataView +static BOOL typed_array_is_oob(JSObject *p) { - JSTypedArray *ta = p->u.typed_array; - JSArrayBuffer *abuf = ta->buffer->u.array_buffer; - /* XXX: could simplify test by ensuring that - p->u.array.u.ptr is NULL iff it is detached */ - return abuf->detached; -} + JSArrayBuffer *abuf; + JSTypedArray *ta; + int len, size_elem; + int64_t end; -/* WARNING: 'p' must be a typed array. Works even if the array buffer - is detached */ -static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p) + assert(p->class_id >= JS_CLASS_UINT8C_ARRAY); + assert(p->class_id <= JS_CLASS_FLOAT64_ARRAY); + + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return TRUE; + len = abuf->byte_length; + if (ta->offset > len) + return TRUE; + if (ta->track_rab) + return FALSE; + if (len < (int64_t)ta->offset + ta->length) + return TRUE; + size_elem = 1 << typed_array_size_log2(p->class_id); + end = (int64_t)ta->offset + (int64_t)p->u.array.count * size_elem; + return end > len; +} + +// Be *very* careful if you touch the typed array's memory directly: +// the length is only valid until the next call into JS land because +// JS code can detach or resize the backing array buffer. Functions +// like JS_GetProperty and JS_ToIndex call JS code. +// +// Exclusively reading or writing elements with JS_GetProperty, +// JS_GetPropertyInt64, JS_SetProperty, etc. is safe because they +// perform bounds checks, as does js_get_fast_array_element. +static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValueConst obj) { - JSTypedArray *ta = p->u.typed_array; - int size_log2 = typed_array_size_log2(p->class_id); - return ta->length >> size_log2; + JSObject *p; + p = get_typed_array(ctx, obj); + if (!p) + return -1; + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + return -1; + } + return p->u.array.count; } static int validate_typed_array(JSContext *ctx, JSValueConst this_val) { JSObject *p; - p = get_typed_array(ctx, this_val, 0); + p = get_typed_array(ctx, this_val); if (!p) return -1; - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); return -1; } return 0; @@ -51670,18 +55597,18 @@ JSValueConst this_val) { JSObject *p; - p = get_typed_array(ctx, this_val, 0); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; return JS_NewInt32(ctx, p->u.array.count); } static JSValue js_typed_array_get_buffer(JSContext *ctx, - JSValueConst this_val, int is_dataview) + JSValueConst this_val) { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, this_val, is_dataview); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; ta = p->u.typed_array; @@ -51689,43 +55616,46 @@ } static JSValue js_typed_array_get_byteLength(JSContext *ctx, - JSValueConst this_val, - int is_dataview) + JSValueConst this_val) { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, this_val, is_dataview); + int size_log2; + + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) { - if (is_dataview) { - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - } else { - return JS_NewInt32(ctx, 0); - } - } + if (typed_array_is_oob(p)) + return JS_NewInt32(ctx, 0); ta = p->u.typed_array; - return JS_NewInt32(ctx, ta->length); + if (!ta->track_rab) + return JS_NewUint32(ctx, ta->length); + size_log2 = typed_array_size_log2(p->class_id); + return JS_NewInt64(ctx, (int64_t)p->u.array.count << size_log2); } static JSValue js_typed_array_get_byteOffset(JSContext *ctx, - JSValueConst this_val, - int is_dataview) + JSValueConst this_val) { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, this_val, is_dataview); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) { - if (is_dataview) { - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - } else { - return JS_NewInt32(ctx, 0); - } - } + if (typed_array_is_oob(p)) + return JS_NewInt32(ctx, 0); ta = p->u.typed_array; - return JS_NewInt32(ctx, ta->offset); + return JS_NewUint32(ctx, ta->offset); +} + +JSValue JS_NewTypedArray(JSContext *ctx, int argc, JSValueConst *argv, + JSTypedArrayEnum type) +{ + if (type < JS_TYPED_ARRAY_UINT8C || type > JS_TYPED_ARRAY_FLOAT64) + return JS_ThrowRangeError(ctx, "invalid typed array type"); + + return js_typed_array_constructor(ctx, JS_UNDEFINED, argc, argv, + JS_CLASS_UINT8C_ARRAY + type); } /* Return the buffer associated to the typed array or an exception if @@ -51738,11 +55668,11 @@ { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, obj, FALSE); + p = get_typed_array(ctx, obj); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); ta = p->u.typed_array; if (pbyte_offset) *pbyte_offset = ta->offset; @@ -51775,21 +55705,22 @@ JSObject *p; JSObject *src_p; uint32_t i; - int64_t src_len, offset; + int64_t dst_len, src_len, offset; JSValue val, src_obj = JS_UNDEFINED; - p = get_typed_array(ctx, dst, 0); + p = get_typed_array(ctx, dst); if (!p) goto fail; if (JS_ToInt64Sat(ctx, &offset, off)) goto fail; if (offset < 0) goto range_error; - if (typed_array_is_detached(ctx, p)) { + if (typed_array_is_oob(p)) { detached: - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } + dst_len = p->u.array.count; src_obj = JS_ToObject(ctx, src); if (JS_IsException(src_obj)) goto fail; @@ -51802,11 +55733,11 @@ JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer; int shift = typed_array_size_log2(p->class_id); - if (src_abuf->detached) + if (typed_array_is_oob(src_p)) goto detached; src_len = src_p->u.array.count; - if (offset > (int64_t)(p->u.array.count - src_len)) + if (offset > dst_len - src_len) goto range_error; /* copying between typed objects */ @@ -51822,9 +55753,11 @@ } /* otherwise, default behavior is slow but correct */ } else { + // can change |dst| as a side effect; per spec, + // perform the range check against its old length if (js_get_length64(ctx, &src_len, src_obj)) goto fail; - if (offset > (int64_t)(p->u.array.count - src_len)) { + if (offset > dst_len - src_len) { range_error: JS_ThrowRangeError(ctx, "invalid array length"); goto fail; @@ -51845,6 +55778,73 @@ return JS_EXCEPTION; } +static JSValue js_typed_array_at(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSObject *p; + int64_t idx, len; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + len = p->u.array.count; + + // note: can change p->u.array.count + if (JS_ToInt64Sat(ctx, &idx, argv0)) + return JS_EXCEPTION; + + if (idx < 0) + idx = len + idx; + + len = p->u.array.count; + if (idx < 0 || idx >= len) + return JS_UNDEFINED; + return JS_GetPropertyInt64(ctx, this_val, idx); +} + +static JSValue js_typed_array_with(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, val; + JSObject *p; + int64_t idx, len; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + + len = p->u.array.count; + if (JS_ToInt64Sat(ctx, &idx, argv0)) + return JS_EXCEPTION; + + if (idx < 0) + idx = len + idx; + + val = JS_ToPrimitive(ctx, argv1, HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + + if (typed_array_is_oob(p) || idx < 0 || idx >= p->u.array.count) + return JS_ThrowRangeError(ctx, "invalid array index"); + + arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, + p->class_id, len); + if (JS_IsException(arr)) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (JS_SetPropertyInt64(ctx, arr, idx, val) < 0) { + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; + } + return arr; +} + static JSValue js_typed_array_set(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -51864,41 +55864,6 @@ return js_create_array_iterator(ctx, this_val, argc, argv, magic); } -/* return < 0 if exception */ -static int js_typed_array_get_length_internal(JSContext *ctx, - JSValueConst obj) -{ - JSObject *p; - p = get_typed_array(ctx, obj, 0); - if (!p) - return -1; - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return -1; - } - return p->u.array.count; -} - -#if 0 -/* validate a typed array and return its length */ -static JSValue js_typed_array___getLength(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv) -{ - BOOL ignore_detached = JS_ToBool(ctx, argv1); - - if (ignore_detached) { - return js_typed_array_get_length(ctx, argv0); - } else { - int len; - len = js_typed_array_get_length_internal(ctx, argv0); - if (len < 0) - return JS_EXCEPTION; - return JS_NewInt32(ctx, len); - } -} -#endif - static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor, int argc, JSValueConst *argv) { @@ -51910,7 +55875,7 @@ if (JS_IsException(ret)) return ret; /* validate the typed array */ - new_len = js_typed_array_get_length_internal(ctx, ret); + new_len = js_typed_array_get_length_unsafe(ctx, ret); if (new_len < 0) goto fail; if (argc == 1) { @@ -51946,7 +55911,7 @@ int argc1; obj = argv0; - p = get_typed_array(ctx, obj, 0); + p = get_typed_array(ctx, obj); if (!p) return JS_EXCEPTION; ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED); @@ -51969,18 +55934,16 @@ // from(items, mapfn = void 0, this_arg = void 0) JSValueConst items = argv0, mapfn, this_arg; JSValueConst args2; - JSValue stack2; JSValue iter, arr, r, v, v2; int64_t k, len; - int done, mapping; + int mapping; mapping = FALSE; mapfn = JS_UNDEFINED; this_arg = JS_UNDEFINED; r = JS_UNDEFINED; arr = JS_UNDEFINED; - stack0 = JS_UNDEFINED; - stack1 = JS_UNDEFINED; + iter = JS_UNDEFINED; if (argc > 1) { mapfn = argv1; @@ -51995,30 +55958,23 @@ iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); if (JS_IsException(iter)) goto exception; - if (!JS_IsUndefined(iter)) { - JS_FreeValue(ctx, iter); - arr = JS_NewArray(ctx); - if (JS_IsException(arr)) - goto exception; - stack0 = JS_DupValue(ctx, items); - if (js_for_of_start(ctx, &stack1, FALSE)) + if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) { + uint32_t len1; + if (!JS_IsFunction(ctx, iter)) { + JS_ThrowTypeError(ctx, "value is not iterable"); goto exception; - for (k = 0;; k++) { - v = JS_IteratorNext(ctx, stack0, stack1, 0, NULL, &done); - if (JS_IsException(v)) - goto exception_close; - if (done) - break; - if (JS_DefinePropertyValueInt64(ctx, arr, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0) - goto exception_close; } + arr = js_array_from_iterator(ctx, &len1, items, iter); + if (JS_IsException(arr)) + goto exception; + len = len1; } else { arr = JS_ToObject(ctx, items); if (JS_IsException(arr)) goto exception; + if (js_get_length64(ctx, &len, arr) < 0) + goto exception; } - if (js_get_length64(ctx, &len, arr) < 0) - goto exception; v = JS_NewInt64(ctx, len); args0 = v; r = js_typed_array_create(ctx, this_val, 1, args); @@ -52042,17 +55998,12 @@ goto exception; } goto done; - - exception_close: - if (!JS_IsUndefined(stack0)) - JS_IteratorClose(ctx, stack0, TRUE); exception: JS_FreeValue(ctx, r); r = JS_EXCEPTION; done: JS_FreeValue(ctx, arr); - JS_FreeValue(ctx, stack0); - JS_FreeValue(ctx, stack1); + JS_FreeValue(ctx, iter); return r; } @@ -52081,11 +56032,14 @@ int argc, JSValueConst *argv) { JSObject *p; - int len, to, from, final, count, shift; + int len, to, from, final, count, shift, space; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) + p = get_typed_array(ctx, this_val); + if (!p) return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; if (JS_ToInt32Clamp(ctx, &to, argv0, 0, len, len)) return JS_EXCEPTION; @@ -52099,11 +56053,14 @@ return JS_EXCEPTION; } + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + + // RAB may have been resized by evil .valueOf method + space = p->u.array.count - max_int(to, from); count = min_int(final - from, len - to); + count = min_int(count, space); if (count > 0) { - p = JS_VALUE_GET_OBJ(this_val); - if (typed_array_is_detached(ctx, p)) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); shift = typed_array_size_log2(p->class_id); memmove(p->u.array.u.uint8_ptr + (to << shift), p->u.array.u.uint8_ptr + (from << shift), @@ -52119,10 +56076,12 @@ int len, k, final, shift; uint64_t v64; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) + p = get_typed_array(ctx, this_val); + if (!p) return JS_EXCEPTION; - p = JS_VALUE_GET_OBJ(this_val); + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; if (p->class_id == JS_CLASS_UINT8C_ARRAY) { int32_t v; @@ -52134,18 +56093,16 @@ if (JS_ToUint32(ctx, &v, argv0)) return JS_EXCEPTION; v64 = v; - } else -#ifdef CONFIG_BIGNUM - if (p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) { + } else if (p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) { if (JS_ToBigInt64(ctx, (int64_t *)&v64, argv0)) return JS_EXCEPTION; - } else -#endif - { + } else { double d; if (JS_ToFloat64(ctx, &d, argv0)) return JS_EXCEPTION; - if (p->class_id == JS_CLASS_FLOAT32_ARRAY) { + if (p->class_id == JS_CLASS_FLOAT16_ARRAY) { + v64 = tofp16(d); + } else if (p->class_id == JS_CLASS_FLOAT32_ARRAY) { union { float f; uint32_t u32; @@ -52171,9 +56128,11 @@ return JS_EXCEPTION; } - if (typed_array_is_detached(ctx, p)) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + // RAB may have been resized by evil .valueOf method + final = min_int(final, p->u.array.count); shift = typed_array_size_log2(p->class_id); switch(shift) { case 0: @@ -52203,15 +56162,16 @@ } static JSValue js_typed_array_find(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int findIndex) + int argc, JSValueConst *argv, int mode) { JSValueConst func, this_arg; JSValueConst args3; JSValue val, index_val, res; - int len, k; + int len, k, end; + int dir; val = JS_UNDEFINED; - len = js_typed_array_get_length_internal(ctx, this_val); + len = js_typed_array_get_length_unsafe(ctx, this_val); if (len < 0) goto exception; @@ -52223,7 +56183,16 @@ if (argc > 1) this_arg = argv1; - for(k = 0; k < len; k++) { + k = 0; + dir = 1; + end = len; + if (mode == ArrayFindLast || mode == ArrayFindLastIndex) { + k = len - 1; + dir = -1; + end = -1; + } + + for(; k != end; k += dir) { index_val = JS_NewInt32(ctx, k); val = JS_GetPropertyValue(ctx, this_val, index_val); if (JS_IsException(val)) @@ -52235,7 +56204,7 @@ if (JS_IsException(res)) goto exception; if (JS_ToBoolFree(ctx, res)) { - if (findIndex) { + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) { JS_FreeValue(ctx, val); return index_val; } else { @@ -52244,7 +56213,7 @@ } JS_FreeValue(ctx, val); } - if (findIndex) + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) return JS_NewInt32(ctx, -1); else return JS_UNDEFINED; @@ -52266,32 +56235,27 @@ int64_t v64; double d; float f; + uint16_t hf; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) - goto exception; if (len == 0) goto done; if (special == special_lastIndexOf) { k = len - 1; if (argc > 1) { - if (JS_ToFloat64(ctx, &d, argv1)) + int64_t k1; + if (JS_ToInt64Clamp(ctx, &k1, argv1, -1, len - 1, len)) goto exception; - if (isnan(d)) { - k = 0; - } else { - if (d >= 0) { - if (d < k) { - k = d; - } - } else { - d += len; - if (d < 0) - goto done; - k = d; - } - } + k = k1; + if (k < 0) + goto done; } stop = -1; inc = -1; @@ -52305,16 +56269,23 @@ inc = 1; } - p = JS_VALUE_GET_OBJ(this_val); - /* if the array was detached, no need to go further (but no - exception is raised) */ - if (typed_array_is_detached(ctx, p)) { - /* "includes" scans all the properties, so "undefined" can match */ - if (special == special_includes && JS_IsUndefined(argv0) && len > 0) - res = 0; + /* includes function: 'undefined' can be found if searching out of bounds */ + if (len > p->u.array.count && special == special_includes && + JS_IsUndefined(argv0) && k < len) { + res = 0; goto done; } + // RAB may have been resized by evil .valueOf method + len = min_int(len, p->u.array.count); + if (len == 0) + goto done; + if (special == special_lastIndexOf) + k = min_int(k, len - 1); + else + k = min_int(k, len); + stop = min_int(stop, len); + is_bigint = 0; is_int = 0; /* avoid warning */ v64 = 0; /* avoid warning */ @@ -52326,27 +56297,40 @@ } else if (tag == JS_TAG_FLOAT64) { d = JS_VALUE_GET_FLOAT64(argv0); - v64 = d; - is_int = (v64 == d); - } else -#ifdef CONFIG_BIGNUM - if (tag == JS_TAG_BIG_INT) { - JSBigFloat *p1 = JS_VALUE_GET_PTR(argv0); + if (d >= INT64_MIN && d < 0x8000000000000000ULL /*0x1p63*/) { + v64 = d; + is_int = (v64 == d); + } + } else if (tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT) { + JSBigIntBuf buf1; + JSBigInt *p1; + int sz = (64 / JS_LIMB_BITS); + if (tag == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, argv0); + else + p1 = JS_VALUE_GET_PTR(argv0); if (p->class_id == JS_CLASS_BIG_INT64_ARRAY) { - if (bf_get_int64(&v64, &p1->num, 0) != 0) - goto done; + if (p1->len > sz) + goto done; /* does not fit an int64 : cannot be found */ } else if (p->class_id == JS_CLASS_BIG_UINT64_ARRAY) { - if (bf_get_uint64((uint64_t *)&v64, &p1->num) != 0) + if (js_bigint_sign(p1)) + goto done; /* v < 0 */ + if (p1->len <= sz) { + /* OK */ + } else if (p1->len == sz + 1 && p1->tabsz == 0) { + /* 2^63 <= v <= 2^64-1 */ + } else { goto done; + } } else { goto done; } + if (JS_ToBigInt64(ctx, &v64, argv0)) + goto exception; d = 0; is_bigint = 1; - } else -#endif - { + } else { goto done; } @@ -52364,7 +56348,9 @@ pv = p->u.array.u.uint8_ptr; v = v64; if (inc > 0) { - pp = memchr(pv + k, v, len - k); + pp = NULL; + if (pv) + pp = memchr(pv + k, v, len - k); if (pp) res = pp - pv; } else { @@ -52415,6 +56401,42 @@ } } break; + case JS_CLASS_FLOAT16_ARRAY: + if (is_bigint) + break; + if (isnan(d)) { + const uint16_t *pv = p->u.array.u.fp16_ptr; + /* special case: indexOf returns -1, includes finds NaN */ + if (special != special_includes) + goto done; + for (; k != stop; k += inc) { + if (isfp16nan(pvk)) { + res = k; + break; + } + } + } else if (d == 0) { + // special case: includes also finds negative zero + const uint16_t *pv = p->u.array.u.fp16_ptr; + for (; k != stop; k += inc) { + if (isfp16zero(pvk)) { + res = k; + break; + } + } + } else { + hf = tofp16(d); + if (d == fromfp16(hf)) { + const uint16_t *pv = p->u.array.u.fp16_ptr; + for (; k != stop; k += inc) { + if (pvk == hf) { + res = k; + break; + } + } + } + } + break; case JS_CLASS_FLOAT32_ARRAY: if (is_bigint) break; @@ -52463,17 +56485,13 @@ } } break; -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: - if (is_bigint || (is_math_mode(ctx) && is_int && - v64 >= -MAX_SAFE_INTEGER && - v64 <= MAX_SAFE_INTEGER)) { + if (is_bigint) { goto scan64; } break; case JS_CLASS_BIG_UINT64_ARRAY: - if (is_bigint || (is_math_mode(ctx) && is_int && - v64 >= 0 && v64 <= MAX_SAFE_INTEGER)) { + if (is_bigint) { const uint64_t *pv; uint64_t v; scan64: @@ -52487,7 +56505,6 @@ } } break; -#endif } done: @@ -52505,35 +56522,42 @@ { JSValue sep = JS_UNDEFINED, el; StringBuffer b_s, *b = &b_s; - JSString *p = NULL; - int i, n; + JSString *s = NULL; + JSObject *p; + int i, len, oldlen, newlen; int c; - n = js_typed_array_get_length_internal(ctx, this_val); - if (n < 0) - goto exception; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = oldlen = newlen = p->u.array.count; c = ','; /* default separator */ if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv0)) { sep = JS_ToString(ctx, argv0); if (JS_IsException(sep)) goto exception; - p = JS_VALUE_GET_STRING(sep); - if (p->len == 1 && !p->is_wide_char) - c = p->u.str80; + s = JS_VALUE_GET_STRING(sep); + if (s->len == 1 && !s->is_wide_char) + c = s->u.str80; else c = -1; + // ToString(sep) can detach or resize the arraybuffer as a side effect + newlen = p->u.array.count; + len = min_int(len, newlen); } string_buffer_init(ctx, b, 0); /* XXX: optimize with direct access */ - for(i = 0; i < n; i++) { + for(i = 0; i < len; i++) { if (i > 0) { if (c >= 0) { if (string_buffer_putc8(b, c)) goto fail; } else { - if (string_buffer_concat(b, p, 0, p->len)) + if (string_buffer_concat(b, s, 0, s->len)) goto fail; } } @@ -52549,6 +56573,19 @@ goto fail; } } + + // add extra separators in case RAB was resized by evil .valueOf method + i = max_int(1, newlen); + for(/*empty*/; i < oldlen; i++) { + if (c >= 0) { + if (string_buffer_putc8(b, c)) + goto fail; + } else { + if (string_buffer_concat(b, s, 0, s->len)) + goto fail; + } + } + JS_FreeValue(ctx, sep); return string_buffer_end(b); @@ -52565,7 +56602,7 @@ JSObject *p; int len; - len = js_typed_array_get_length_internal(ctx, this_val); + len = js_typed_array_get_length_unsafe(ctx, this_val); if (len < 0) return JS_EXCEPTION; if (len > 0) { @@ -52622,18 +56659,51 @@ return JS_DupValue(ctx, this_val); } +static JSValue js_typed_array_toReversed(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, ret; + JSObject *p; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, + p->class_id, p->u.array.count); + if (JS_IsException(arr)) + return JS_EXCEPTION; + ret = js_typed_array_reverse(ctx, arr, argc, argv); + JS_FreeValue(ctx, arr); + return ret; +} + +static void slice_memcpy(uint8_t *dst, const uint8_t *src, size_t len) +{ + if (dst + len <= src || dst >= src + len) { + /* no overlap: can use memcpy */ + memcpy(dst, src, len); + } else { + /* otherwise the spec mandates byte copy */ + while (len-- != 0) + *dst++ = *src++; + } +} + static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst args2; JSValue arr, val; JSObject *p, *p1; - int n, len, start, final, count, shift; + int n, len, start, final, count, shift, space; arr = JS_UNDEFINED; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) + p = get_typed_array(ctx, this_val); + if (!p) goto exception; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; if (JS_ToInt32Clamp(ctx, &start, argv0, 0, len, len)) goto exception; @@ -52644,9 +56714,6 @@ } count = max_int(final - start, 0); - p = get_typed_array(ctx, this_val, 0); - if (p == NULL) - goto exception; shift = typed_array_size_log2(p->class_id); args0 = this_val; @@ -52660,13 +56727,13 @@ || validate_typed_array(ctx, arr)) goto exception; - p1 = get_typed_array(ctx, arr, 0); - if (p1 != NULL && p->class_id == p1->class_id && - typed_array_get_length(ctx, p1) >= count && - typed_array_get_length(ctx, p) >= start + count) { - memcpy(p1->u.array.u.uint8_ptr, - p->u.array.u.uint8_ptr + (start << shift), - count << shift); + p1 = get_typed_array(ctx, arr); + space = max_int(0, p->u.array.count - start); + count = min_int(count, space); + if (p1 != NULL && p->class_id == p1->class_id) { + slice_memcpy(p1->u.array.u.uint8_ptr, + p->u.array.u.uint8_ptr + (start << shift), + count << shift); } else { for (n = 0; n < count; n++) { val = JS_GetPropertyValue(ctx, this_val, JS_NewInt32(ctx, start + n)); @@ -52689,37 +56756,41 @@ int argc, JSValueConst *argv) { JSValueConst args4; - JSValue arr, byteOffset, ta_buffer; + JSValue arr, ta_buffer; + JSTypedArray *ta; JSObject *p; int len, start, final, count, shift, offset; + BOOL is_auto; - p = get_typed_array(ctx, this_val, 0); + p = get_typed_array(ctx, this_val); if (!p) goto exception; len = p->u.array.count; if (JS_ToInt32Clamp(ctx, &start, argv0, 0, len, len)) goto exception; + shift = typed_array_size_log2(p->class_id); + ta = p->u.typed_array; + /* Read byteOffset (ta->offset) even if detached */ + offset = ta->offset + (start << shift); + final = len; - if (!JS_IsUndefined(argv1)) { + if (JS_IsUndefined(argv1)) { + is_auto = ta->track_rab; + } else { + is_auto = FALSE; if (JS_ToInt32Clamp(ctx, &final, argv1, 0, len, len)) goto exception; } count = max_int(final - start, 0); - byteOffset = js_typed_array_get_byteOffset(ctx, this_val, 0); - if (JS_IsException(byteOffset)) - goto exception; - shift = typed_array_size_log2(p->class_id); - offset = JS_VALUE_GET_INT(byteOffset) + (start << shift); - JS_FreeValue(ctx, byteOffset); - ta_buffer = js_typed_array_get_buffer(ctx, this_val, 0); + ta_buffer = js_typed_array_get_buffer(ctx, this_val); if (JS_IsException(ta_buffer)) goto exception; args0 = this_val; args1 = ta_buffer; args2 = JS_NewInt32(ctx, offset); args3 = JS_NewInt32(ctx, count); - arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args); + arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, is_auto ? 3 : 4, args); JS_FreeValue(ctx, ta_buffer); return arr; @@ -52768,7 +56839,6 @@ return (y < x) - (y > x); } -#ifdef CONFIG_BIGNUM static int js_TA_cmp_int64(const void *a, const void *b, void *opaque) { int64_t x = *(const int64_t *)a; int64_t y = *(const int64_t *)b; @@ -52780,7 +56850,11 @@ uint64_t y = *(const uint64_t *)b; return (y < x) - (y > x); } -#endif + +static int js_TA_cmp_float16(const void *a, const void *b, void *opaque) { + return js_cmp_doubles(fromfp16(*(const uint16_t *)a), + fromfp16(*(const uint16_t *)b)); +} static int js_TA_cmp_float32(const void *a, const void *b, void *opaque) { return js_cmp_doubles(*(const float *)a, *(const float *)b); @@ -52814,7 +56888,6 @@ return JS_NewUint32(ctx, *(const uint32_t *)a); } -#ifdef CONFIG_BIGNUM static JSValue js_TA_get_int64(JSContext *ctx, const void *a) { return JS_NewBigInt64(ctx, *(int64_t *)a); } @@ -52822,7 +56895,10 @@ static JSValue js_TA_get_uint64(JSContext *ctx, const void *a) { return JS_NewBigUint64(ctx, *(uint64_t *)a); } -#endif + +static JSValue js_TA_get_float16(JSContext *ctx, const void *a) { + return __JS_NewFloat64(ctx, fromfp16(*(const uint16_t *)a)); +} static JSValue js_TA_get_float32(JSContext *ctx, const void *a) { return __JS_NewFloat64(ctx, *(const float *)a); @@ -52834,11 +56910,10 @@ struct TA_sort_context { JSContext *ctx; - int exception; + int exception; /* 1 = exception, 2 = detached typed array */ JSValueConst arr; JSValueConst cmp; JSValue (*getfun)(JSContext *ctx, const void *a); - uint8_t *array_ptr; /* cannot change unless the array is detached */ int elt_size; }; @@ -52849,14 +56924,23 @@ JSValueConst argv2; JSValue res; int cmp; + JSObject *p; cmp = 0; if (!psc->exception) { + /* Note: the typed array can be detached without causing an + error */ a_idx = *(uint32_t *)a; b_idx = *(uint32_t *)b; - argv0 = psc->getfun(ctx, psc->array_ptr + + p = JS_VALUE_GET_PTR(psc->arr); + if (a_idx >= p->u.array.count || b_idx >= p->u.array.count) { + /* OOB case */ + psc->exception = 2; + return 0; + } + argv0 = psc->getfun(ctx, p->u.array.u.uint8_ptr + a_idx * (size_t)psc->elt_size); - argv1 = psc->getfun(ctx, psc->array_ptr + + argv1 = psc->getfun(ctx, p->u.array.u.uint8_ptr + b_idx * (size_t)(psc->elt_size)); res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv); if (JS_IsException(res)) { @@ -52879,9 +56963,6 @@ /* make sort stable: compare array offsets */ cmp = (a_idx > b_idx) - (a_idx < b_idx); } - if (validate_typed_array(ctx, psc->arr) < 0) { - psc->exception = 1; - } done: #if defined(JS_VALUE_CANNOT_BE_CAST) JS_FreeValue(ctx, argv0); @@ -52902,7 +56983,6 @@ int len; size_t elt_size; struct TA_sort_context tsc; - void *array_ptr; int (*cmpfun)(const void *a, const void *b, void *opaque); tsc.ctx = ctx; @@ -52910,11 +56990,11 @@ tsc.arr = this_val; tsc.cmp = argv0; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) - return JS_EXCEPTION; if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp)) return JS_EXCEPTION; + len = js_typed_array_get_length_unsafe(ctx, this_val); + if (len < 0) + return JS_EXCEPTION; if (len > 1) { p = JS_VALUE_GET_OBJ(this_val); @@ -52944,7 +57024,6 @@ tsc.getfun = js_TA_get_uint32; cmpfun = js_TA_cmp_uint32; break; -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: tsc.getfun = js_TA_get_int64; cmpfun = js_TA_cmp_int64; @@ -52953,7 +57032,10 @@ tsc.getfun = js_TA_get_uint64; cmpfun = js_TA_cmp_uint64; break; -#endif + case JS_CLASS_FLOAT16_ARRAY: + tsc.getfun = js_TA_get_float16; + cmpfun = js_TA_cmp_float16; + break; case JS_CLASS_FLOAT32_ARRAY: tsc.getfun = js_TA_get_float32; cmpfun = js_TA_cmp_float32; @@ -52965,7 +57047,6 @@ default: abort(); } - array_ptr = p->u.array.u.ptr; elt_size = 1 << typed_array_size_log2(p->class_id); if (!JS_IsUndefined(tsc.cmp)) { uint32_t *array_idx; @@ -52978,51 +57059,58 @@ return JS_EXCEPTION; for(i = 0; i < len; i++) array_idxi = i; - tsc.array_ptr = array_ptr; tsc.elt_size = elt_size; rqsort(array_idx, len, sizeof(array_idx0), js_TA_cmp_generic, &tsc); - if (tsc.exception) - goto fail; - array_tmp = js_malloc(ctx, len * elt_size); - if (!array_tmp) { - fail: - js_free(ctx, array_idx); - return JS_EXCEPTION; - } - memcpy(array_tmp, array_ptr, len * elt_size); - switch(elt_size) { - case 1: - for(i = 0; i < len; i++) { - j = array_idxi; - ((uint8_t *)array_ptr)i = ((uint8_t *)array_tmp)j; - } - break; - case 2: - for(i = 0; i < len; i++) { - j = array_idxi; - ((uint16_t *)array_ptr)i = ((uint16_t *)array_tmp)j; - } - break; - case 4: - for(i = 0; i < len; i++) { - j = array_idxi; - ((uint32_t *)array_ptr)i = ((uint32_t *)array_tmp)j; - } - break; - case 8: - for(i = 0; i < len; i++) { - j = array_idxi; - ((uint64_t *)array_ptr)i = ((uint64_t *)array_tmp)j; + if (tsc.exception) { + if (tsc.exception == 1) + goto fail; + /* detached typed array during the sort: no error */ + } else { + void *array_ptr = p->u.array.u.ptr; + len = min_int(len, p->u.array.count); + if (len != 0) { + array_tmp = js_malloc(ctx, len * elt_size); + if (!array_tmp) { + fail: + js_free(ctx, array_idx); + return JS_EXCEPTION; + } + memcpy(array_tmp, array_ptr, len * elt_size); + switch(elt_size) { + case 1: + for(i = 0; i < len; i++) { + j = array_idxi; + ((uint8_t *)array_ptr)i = ((uint8_t *)array_tmp)j; + } + break; + case 2: + for(i = 0; i < len; i++) { + j = array_idxi; + ((uint16_t *)array_ptr)i = ((uint16_t *)array_tmp)j; + } + break; + case 4: + for(i = 0; i < len; i++) { + j = array_idxi; + ((uint32_t *)array_ptr)i = ((uint32_t *)array_tmp)j; + } + break; + case 8: + for(i = 0; i < len; i++) { + j = array_idxi; + ((uint64_t *)array_ptr)i = ((uint64_t *)array_tmp)j; + } + break; + default: + abort(); + } + js_free(ctx, array_tmp); } - break; - default: - abort(); } - js_free(ctx, array_tmp); js_free(ctx, array_idx); } else { - rqsort(array_ptr, len, elt_size, cmpfun, &tsc); + rqsort(p->u.array.u.ptr, len, elt_size, cmpfun, &tsc); if (tsc.exception) return JS_EXCEPTION; } @@ -53030,20 +57118,37 @@ return JS_DupValue(ctx, this_val); } +static JSValue js_typed_array_toSorted(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arr, ret; + JSObject *p; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, + p->class_id, p->u.array.count); + if (JS_IsException(arr)) + return JS_EXCEPTION; + ret = js_typed_array_sort(ctx, arr, argc, argv); + JS_FreeValue(ctx, arr); + return ret; +} + static const JSCFunctionListEntry js_typed_array_base_funcs = { JS_CFUNC_DEF("from", 1, js_typed_array_from ), JS_CFUNC_DEF("of", 0, js_typed_array_of ), JS_CGETSET_DEF("Symbol.species", js_get_this, NULL ), - //JS_CFUNC_DEF("__getLength", 2, js_typed_array___getLength ), - //JS_CFUNC_DEF("__create", 2, js_typed_array___create ), - //JS_CFUNC_DEF("__speciesCreate", 2, js_typed_array___speciesCreate ), }; static const JSCFunctionListEntry js_typed_array_base_proto_funcs = { JS_CGETSET_DEF("length", js_typed_array_get_length, NULL ), - JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 0 ), - JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 0 ), - JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 0 ), + JS_CFUNC_DEF("at", 1, js_typed_array_at ), + JS_CFUNC_DEF("with", 2, js_typed_array_with ), + JS_CGETSET_DEF("buffer", js_typed_array_get_buffer, NULL ), + JS_CGETSET_DEF("byteLength", js_typed_array_get_byteLength, NULL ), + JS_CGETSET_DEF("byteOffset", js_typed_array_get_byteOffset, NULL ), JS_CFUNC_DEF("set", 1, js_typed_array_set ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_VALUE ), JS_ALIAS_DEF("Symbol.iterator", "values" ), @@ -53059,12 +57164,16 @@ JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce | special_TA ), JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight | special_TA ), JS_CFUNC_DEF("fill", 1, js_typed_array_fill ), - JS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, 0 ), - JS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, 1 ), + JS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, ArrayFind ), + JS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, ArrayFindIndex ), + JS_CFUNC_MAGIC_DEF("findLast", 1, js_typed_array_find, ArrayFindLast ), + JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_typed_array_find, ArrayFindLastIndex ), JS_CFUNC_DEF("reverse", 0, js_typed_array_reverse ), + JS_CFUNC_DEF("toReversed", 0, js_typed_array_toReversed ), JS_CFUNC_DEF("slice", 2, js_typed_array_slice ), JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ), JS_CFUNC_DEF("sort", 1, js_typed_array_sort ), + JS_CFUNC_DEF("toSorted", 1, js_typed_array_toSorted ), JS_CFUNC_MAGIC_DEF("join", 1, js_typed_array_join, 0 ), JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_typed_array_join, 1 ), JS_CFUNC_MAGIC_DEF("indexOf", 1, js_typed_array_indexOf, special_indexOf ), @@ -53073,6 +57182,13 @@ //JS_ALIAS_BASE_DEF("toString", "toString", 2 /* Array.prototype. */), @@@ }; +static const JSCFunctionListEntry js_typed_array_funcs = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 2, 0), + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 4, 0), + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 8, 0), +}; + static JSValue js_typed_array_base_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -53082,7 +57198,8 @@ /* 'obj' must be an allocated typed array object */ static int typed_array_init(JSContext *ctx, JSValueConst obj, - JSValue buffer, uint64_t offset, uint64_t len) + JSValue buffer, uint64_t offset, uint64_t len, + BOOL track_rab) { JSTypedArray *ta; JSObject *p, *pbuffer; @@ -53102,6 +57219,7 @@ ta->buffer = pbuffer; ta->offset = offset; ta->length = len << size_log2; + ta->track_rab = track_rab; list_add_tail(&ta->link, &abuf->array_list); p->u.typed_array = ta; p->u.array.count = len; @@ -53132,10 +57250,8 @@ val = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(val)) goto fail; - if (done) { - JS_FreeValue(ctx, val); + if (done) break; - } if (JS_CreateDataPropertyUint32(ctx, arr, k, val, JS_PROP_THROW) < 0) goto fail; k++; @@ -53183,10 +57299,11 @@ } buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, - len << size_log2); + len << size_log2, + NULL); if (JS_IsException(buffer)) goto fail; - if (typed_array_init(ctx, ret, buffer, 0, len)) + if (typed_array_init(ctx, ret, buffer, 0, len, /*track_rab*/FALSE)) goto fail; for(i = 0; i < len; i++) { @@ -53207,12 +57324,12 @@ static JSValue js_typed_array_constructor_ta(JSContext *ctx, JSValueConst new_target, JSValueConst src_obj, - int classid) + int classid, uint32_t len) { JSObject *p, *src_buffer; JSTypedArray *ta; - JSValue ctor, obj, buffer; - uint32_t len, i; + JSValue obj, buffer; + uint32_t i; int size_log2; JSArrayBuffer *src_abuf, *abuf; @@ -53220,37 +57337,27 @@ if (JS_IsException(obj)) return obj; p = JS_VALUE_GET_OBJ(src_obj); - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } ta = p->u.typed_array; - len = p->u.array.count; src_buffer = ta->buffer; src_abuf = src_buffer->u.array_buffer; - if (!src_abuf->shared) { - ctor = JS_SpeciesConstructor(ctx, JS_MKPTR(JS_TAG_OBJECT, src_buffer), - JS_UNDEFINED); - if (JS_IsException(ctor)) - goto fail; - } else { - /* force ArrayBuffer default constructor */ - ctor = JS_UNDEFINED; - } size_log2 = typed_array_size_log2(classid); - buffer = js_array_buffer_constructor1(ctx, ctor, - (uint64_t)len << size_log2); - JS_FreeValue(ctx, ctor); + buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, + (uint64_t)len << size_log2, + NULL); if (JS_IsException(buffer)) goto fail; /* necessary because it could have been detached */ - if (typed_array_is_detached(ctx, p)) { + if (typed_array_is_oob(p)) { JS_FreeValue(ctx, buffer); - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER); - if (typed_array_init(ctx, obj, buffer, 0, len)) + if (typed_array_init(ctx, obj, buffer, 0, len, /*track_rab*/FALSE)) goto fail; if (p->class_id == classid) { /* same type: copy the content */ @@ -53276,6 +57383,7 @@ int argc, JSValueConst *argv, int classid) { + BOOL track_rab = FALSE; JSValue buffer, obj; JSArrayBuffer *abuf; int size_log2; @@ -53286,7 +57394,8 @@ if (JS_ToIndex(ctx, &len, argv0)) return JS_EXCEPTION; buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, - len << size_log2); + len << size_log2, + NULL); if (JS_IsException(buffer)) return JS_EXCEPTION; offset = 0; @@ -53303,8 +57412,10 @@ offset > abuf->byte_length) return JS_ThrowRangeError(ctx, "invalid offset"); if (JS_IsUndefined(argv2)) { - if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) - goto invalid_length; + track_rab = array_buffer_is_resizable(abuf); + if (!track_rab) + if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) + goto invalid_length; len = (abuf->byte_length - offset) >> size_log2; } else { if (JS_ToIndex(ctx, &len, argv2)) @@ -53320,7 +57431,8 @@ } else { if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { - return js_typed_array_constructor_ta(ctx, new_target, argv0, classid); + return js_typed_array_constructor_ta(ctx, new_target, argv0, + classid, p->u.array.count); } else { return js_typed_array_constructor_obj(ctx, new_target, argv0, classid); } @@ -53332,7 +57444,7 @@ JS_FreeValue(ctx, buffer); return JS_EXCEPTION; } - if (typed_array_init(ctx, obj, buffer, offset, len)) { + if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } @@ -53346,7 +57458,7 @@ if (ta) { /* during the GC the finalizers are called in an arbitrary order so the ArrayBuffer finalizer may have been called */ - if (JS_IsLiveObject(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer))) { + if (ta->link.next) { list_del(&ta->link); } JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); @@ -53368,6 +57480,8 @@ JSValueConst new_target, int argc, JSValueConst *argv) { + BOOL recompute_len = FALSE; + BOOL track_rab = FALSE; JSArrayBuffer *abuf; uint64_t offset; uint32_t len; @@ -53397,6 +57511,9 @@ if (l > len) return JS_ThrowRangeError(ctx, "invalid byteLength"); len = l; + } else { + recompute_len = TRUE; + track_rab = array_buffer_is_resizable(abuf); } obj = js_create_from_ctor(ctx, new_target, JS_CLASS_DATAVIEW); @@ -53407,6 +57524,16 @@ JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } + // RAB could have been resized in js_create_from_ctor() + if (offset > abuf->byte_length) { + goto out_of_bound; + } else if (recompute_len) { + len = abuf->byte_length - offset; + } else if (offset + len > abuf->byte_length) { + out_of_bound: + JS_ThrowRangeError(ctx, "invalid byteOffset or byteLength"); + goto fail; + } ta = js_malloc(ctx, sizeof(*ta)); if (!ta) { fail: @@ -53418,18 +57545,96 @@ ta->buffer = JS_VALUE_GET_OBJ(JS_DupValue(ctx, buffer)); ta->offset = offset; ta->length = len; + ta->track_rab = track_rab; list_add_tail(&ta->link, &abuf->array_list); p->u.typed_array = ta; return obj; } +// is the DataView out of bounds relative to its parent arraybuffer? +static BOOL dataview_is_oob(JSObject *p) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + + assert(p->class_id == JS_CLASS_DATAVIEW); + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return TRUE; + if (ta->offset > abuf->byte_length) + return TRUE; + if (ta->track_rab) + return FALSE; + return (int64_t)ta->offset + ta->length > abuf->byte_length; +} + +static JSObject *get_dataview(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id != JS_CLASS_DATAVIEW) { + fail: + JS_ThrowTypeError(ctx, "not a DataView"); + return NULL; + } + return p; +} + +static JSValue js_dataview_get_buffer(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + JSTypedArray *ta; + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + ta = p->u.typed_array; + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); +} + +static JSValue js_dataview_get_byteLength(JSContext *ctx, JSValueConst this_val) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSObject *p; + + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (dataview_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + if (ta->track_rab) { + abuf = ta->buffer->u.array_buffer; + return JS_NewUint32(ctx, abuf->byte_length - ta->offset); + } + return JS_NewUint32(ctx, ta->length); +} + +static JSValue js_dataview_get_byteOffset(JSContext *ctx, JSValueConst this_val) +{ + JSTypedArray *ta; + JSObject *p; + + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (dataview_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + return JS_NewUint32(ctx, ta->offset); +} + static JSValue js_dataview_getValue(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int class_id) { JSTypedArray *ta; JSArrayBuffer *abuf; - int is_swap, size; + BOOL littleEndian, is_swap; + int size; uint8_t *ptr; uint32_t v; uint64_t pos; @@ -53440,17 +57645,19 @@ size = 1 << typed_array_size_log2(class_id); if (JS_ToIndex(ctx, &pos, argv0)) return JS_EXCEPTION; - is_swap = FALSE; - if (argc > 1) - is_swap = JS_ToBool(ctx, argv1); -#ifndef WORDS_BIGENDIAN - is_swap ^= 1; -#endif + littleEndian = argc > 1 && JS_ToBool(ctx, argv1); + is_swap = littleEndian ^ !is_be(); abuf = ta->buffer->u.array_buffer; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + // order matters: this check should come before the next one if ((pos + size) > ta->length) return JS_ThrowRangeError(ctx, "out of bound"); + // test262 expects a TypeError for this and V8, in its infinite wisdom, + // throws a "detached array buffer" exception, but IMO that doesn't make + // sense because the buffer is not in fact detached, it's still there + if ((int64_t)ta->offset + ta->length > abuf->byte_length) + return JS_ThrowTypeError(ctx, "out of bound"); ptr = abuf->data + ta->offset + pos; switch(class_id) { @@ -53478,7 +57685,6 @@ if (is_swap) v = bswap32(v); return JS_NewUint32(ctx, v); -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: { uint64_t v; @@ -53497,7 +57703,14 @@ return JS_NewBigUint64(ctx, v); } break; -#endif + case JS_CLASS_FLOAT16_ARRAY: + { + uint16_t v; + v = get_u16(ptr); + if (is_swap) + v = bswap16(v); + return __JS_NewFloat64(ctx, fromfp16(v)); + } case JS_CLASS_FLOAT32_ARRAY: { union { @@ -53532,7 +57745,8 @@ { JSTypedArray *ta; JSArrayBuffer *abuf; - int is_swap, size; + BOOL littleEndian, is_swap; + int size; uint8_t *ptr; uint64_t v64; uint32_t v; @@ -53551,18 +57765,16 @@ if (class_id <= JS_CLASS_UINT32_ARRAY) { if (JS_ToUint32(ctx, &v, val)) return JS_EXCEPTION; - } else -#ifdef CONFIG_BIGNUM - if (class_id <= JS_CLASS_BIG_UINT64_ARRAY) { + } else if (class_id <= JS_CLASS_BIG_UINT64_ARRAY) { if (JS_ToBigInt64(ctx, (int64_t *)&v64, val)) return JS_EXCEPTION; - } else -#endif - { + } else { double d; if (JS_ToFloat64(ctx, &d, val)) return JS_EXCEPTION; - if (class_id == JS_CLASS_FLOAT32_ARRAY) { + if (class_id == JS_CLASS_FLOAT16_ARRAY) { + v = tofp16(d); + } else if (class_id == JS_CLASS_FLOAT32_ARRAY) { union { float f; uint32_t i; @@ -53575,17 +57787,19 @@ v64 = u.u64; } } - is_swap = FALSE; - if (argc > 2) - is_swap = JS_ToBool(ctx, argv2); -#ifndef WORDS_BIGENDIAN - is_swap ^= 1; -#endif + littleEndian = argc > 2 && JS_ToBool(ctx, argv2); + is_swap = littleEndian ^ !is_be(); abuf = ta->buffer->u.array_buffer; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + // order matters: this check should come before the next one if ((pos + size) > ta->length) return JS_ThrowRangeError(ctx, "out of bound"); + // test262 expects a TypeError for this and V8, in its infinite wisdom, + // throws a "detached array buffer" exception, but IMO that doesn't make + // sense because the buffer is not in fact detached, it's still there + if ((int64_t)ta->offset + ta->length > abuf->byte_length) + return JS_ThrowTypeError(ctx, "out of bound"); ptr = abuf->data + ta->offset + pos; switch(class_id) { @@ -53595,6 +57809,7 @@ break; case JS_CLASS_INT16_ARRAY: case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: if (is_swap) v = bswap16(v); put_u16(ptr, v); @@ -53606,10 +57821,8 @@ v = bswap32(v); put_u32(ptr, v); break; -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: case JS_CLASS_BIG_UINT64_ARRAY: -#endif case JS_CLASS_FLOAT64_ARRAY: if (is_swap) v64 = bswap64(v64); @@ -53622,19 +57835,18 @@ } static const JSCFunctionListEntry js_dataview_proto_funcs = { - JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 1 ), - JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 1 ), - JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 1 ), + JS_CGETSET_DEF("buffer", js_dataview_get_buffer, NULL ), + JS_CGETSET_DEF("byteLength", js_dataview_get_byteLength, NULL ), + JS_CGETSET_DEF("byteOffset", js_dataview_get_byteOffset, NULL ), JS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, JS_CLASS_INT8_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, JS_CLASS_UINT8_ARRAY ), JS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, JS_CLASS_INT16_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint16", 1, js_dataview_getValue, JS_CLASS_UINT16_ARRAY ), JS_CFUNC_MAGIC_DEF("getInt32", 1, js_dataview_getValue, JS_CLASS_INT32_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint32", 1, js_dataview_getValue, JS_CLASS_UINT32_ARRAY ), -#ifdef CONFIG_BIGNUM JS_CFUNC_MAGIC_DEF("getBigInt64", 1, js_dataview_getValue, JS_CLASS_BIG_INT64_ARRAY ), JS_CFUNC_MAGIC_DEF("getBigUint64", 1, js_dataview_getValue, JS_CLASS_BIG_UINT64_ARRAY ), -#endif + JS_CFUNC_MAGIC_DEF("getFloat16", 1, js_dataview_getValue, JS_CLASS_FLOAT16_ARRAY ), JS_CFUNC_MAGIC_DEF("getFloat32", 1, js_dataview_getValue, JS_CLASS_FLOAT32_ARRAY ), JS_CFUNC_MAGIC_DEF("getFloat64", 1, js_dataview_getValue, JS_CLASS_FLOAT64_ARRAY ), JS_CFUNC_MAGIC_DEF("setInt8", 2, js_dataview_setValue, JS_CLASS_INT8_ARRAY ), @@ -53643,10 +57855,9 @@ JS_CFUNC_MAGIC_DEF("setUint16", 2, js_dataview_setValue, JS_CLASS_UINT16_ARRAY ), JS_CFUNC_MAGIC_DEF("setInt32", 2, js_dataview_setValue, JS_CLASS_INT32_ARRAY ), JS_CFUNC_MAGIC_DEF("setUint32", 2, js_dataview_setValue, JS_CLASS_UINT32_ARRAY ), -#ifdef CONFIG_BIGNUM JS_CFUNC_MAGIC_DEF("setBigInt64", 2, js_dataview_setValue, JS_CLASS_BIG_INT64_ARRAY ), JS_CFUNC_MAGIC_DEF("setBigUint64", 2, js_dataview_setValue, JS_CLASS_BIG_UINT64_ARRAY ), -#endif + JS_CFUNC_MAGIC_DEF("setFloat16", 2, js_dataview_setValue, JS_CLASS_FLOAT16_ARRAY ), JS_CFUNC_MAGIC_DEF("setFloat32", 2, js_dataview_setValue, JS_CLASS_FLOAT32_ARRAY ), JS_CFUNC_MAGIC_DEF("setFloat64", 2, js_dataview_setValue, JS_CLASS_FLOAT64_ARRAY ), JS_PROP_STRING_DEF("Symbol.toStringTag", "DataView", JS_PROP_CONFIGURABLE ), @@ -53666,11 +57877,12 @@ ATOMICS_OP_LOAD, } AtomicsOpEnum; -static void *js_atomics_get_ptr(JSContext *ctx, - JSArrayBuffer **pabuf, - int *psize_log2, JSClassID *pclass_id, - JSValueConst obj, JSValueConst idx_val, - int is_waitable) +static int js_atomics_get_ptr(JSContext *ctx, + void **pptr, + JSArrayBuffer **pabuf, + int *psize_log2, JSClassID *pclass_id, + JSValueConst obj, JSValueConst idx_val, + int is_waitable) { JSObject *p; JSTypedArray *ta; @@ -53678,50 +57890,60 @@ void *ptr; uint64_t idx; BOOL err; - int size_log2; + int size_log2, old_len; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) goto fail; p = JS_VALUE_GET_OBJ(obj); -#ifdef CONFIG_BIGNUM if (is_waitable) err = (p->class_id != JS_CLASS_INT32_ARRAY && p->class_id != JS_CLASS_BIG_INT64_ARRAY); else err = !(p->class_id >= JS_CLASS_INT8_ARRAY && p->class_id <= JS_CLASS_BIG_UINT64_ARRAY); -#else - if (is_waitable) - err = (p->class_id != JS_CLASS_INT32_ARRAY); - else - err = !(p->class_id >= JS_CLASS_INT8_ARRAY && - p->class_id <= JS_CLASS_UINT32_ARRAY); -#endif if (err) { fail: JS_ThrowTypeError(ctx, "integer TypedArray expected"); - return NULL; + return -1; } ta = p->u.typed_array; abuf = ta->buffer->u.array_buffer; if (!abuf->shared) { if (is_waitable == 2) { JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); - return NULL; + return -1; } if (abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return NULL; + return -1; } } + old_len = p->u.array.count; + if (JS_ToIndex(ctx, &idx, idx_val)) { - return NULL; + return -1; } - /* if the array buffer is detached, p->u.array.count = 0 */ - if (idx >= p->u.array.count) { - JS_ThrowRangeError(ctx, "out-of-bound access"); - return NULL; + + if (idx >= old_len) + goto oob; + + if (is_waitable == 1) { + /* notify(): just avoid having an invalid pointer if overflow */ + if (idx >= p->u.array.count) + ptr = NULL; + } else { + /* RevalidateAtomicAccess() */ + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + return -1; + } + if (idx >= p->u.array.count) { + oob: + JS_ThrowRangeError(ctx, "out-of-bound access"); + return -1; + } } + size_log2 = typed_array_size_log2(p->class_id); ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); if (pabuf) @@ -53730,7 +57952,8 @@ *psize_log2 = size_log2; if (pclass_id) *pclass_id = p->class_id; - return ptr; + *pptr = ptr; + return 0; } static JSValue js_atomics_op(JSContext *ctx, @@ -53738,25 +57961,19 @@ int argc, JSValueConst *argv, int op) { int size_log2; -#ifdef CONFIG_BIGNUM uint64_t v, a, rep_val; -#else - uint32_t v, a, rep_val; -#endif void *ptr; JSValue ret; JSClassID class_id; JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, &class_id, - argv0, argv1, 0); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, &class_id, + argv0, argv1, 0)) return JS_EXCEPTION; rep_val = 0; if (op == ATOMICS_OP_LOAD) { v = 0; } else { -#ifdef CONFIG_BIGNUM if (size_log2 == 3) { int64_t v64; if (JS_ToBigInt64(ctx, &v64, argv2)) @@ -53767,9 +57984,7 @@ return JS_EXCEPTION; rep_val = v64; } - } else -#endif - { + } else { uint32_t v32; if (JS_ToUint32(ctx, &v32, argv2)) return JS_EXCEPTION; @@ -53786,7 +58001,6 @@ switch(op | (size_log2 << 3)) { -#ifdef CONFIG_BIGNUM #define OP(op_name, func_name) \ case ATOMICS_OP_ ## op_name | (0 << 3): \ a = func_name((_Atomic(uint8_t) *)ptr, v); \ @@ -53800,18 +58014,7 @@ case ATOMICS_OP_ ## op_name | (3 << 3): \ a = func_name((_Atomic(uint64_t) *)ptr, v); \ break; -#else -#define OP(op_name, func_name) \ - case ATOMICS_OP_ ## op_name | (0 << 3): \ - a = func_name((_Atomic(uint8_t) *)ptr, v); \ - break; \ - case ATOMICS_OP_ ## op_name | (1 << 3): \ - a = func_name((_Atomic(uint16_t) *)ptr, v); \ - break; \ - case ATOMICS_OP_ ## op_name | (2 << 3): \ - a = func_name((_Atomic(uint32_t) *)ptr, v); \ - break; -#endif + OP(ADD, atomic_fetch_add) OP(AND, atomic_fetch_and) OP(OR, atomic_fetch_or) @@ -53829,11 +58032,9 @@ case ATOMICS_OP_LOAD | (2 << 3): a = atomic_load((_Atomic(uint32_t) *)ptr); break; -#ifdef CONFIG_BIGNUM case ATOMICS_OP_LOAD | (3 << 3): a = atomic_load((_Atomic(uint64_t) *)ptr); break; -#endif case ATOMICS_OP_COMPARE_EXCHANGE | (0 << 3): { @@ -53856,7 +58057,6 @@ a = v1; } break; -#ifdef CONFIG_BIGNUM case ATOMICS_OP_COMPARE_EXCHANGE | (3 << 3): { uint64_t v1 = v; @@ -53864,7 +58064,6 @@ a = v1; } break; -#endif default: abort(); } @@ -53889,14 +58088,12 @@ case JS_CLASS_UINT32_ARRAY: ret = JS_NewUint32(ctx, a); break; -#ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: ret = JS_NewBigInt64(ctx, a); break; case JS_CLASS_BIG_UINT64_ARRAY: ret = JS_NewBigUint64(ctx, a); break; -#endif default: abort(); } @@ -53912,14 +58109,12 @@ JSValue ret; JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, NULL, - argv0, argv1, 0); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, NULL, + argv0, argv1, 0)) return JS_EXCEPTION; -#ifdef CONFIG_BIGNUM if (size_log2 == 3) { int64_t v64; - ret = JS_ToBigIntValueFree(ctx, JS_DupValue(ctx, argv2)); + ret = JS_ToBigIntFree(ctx, JS_DupValue(ctx, argv2)); if (JS_IsException(ret)) return ret; if (JS_ToBigInt64(ctx, &v64, ret)) { @@ -53929,9 +58124,7 @@ if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); atomic_store((_Atomic(uint64_t) *)ptr, v64); - } else -#endif - { + } else { uint32_t v; /* XXX: spec, would be simpler to return the written value */ ret = JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv2)); @@ -53967,11 +58160,7 @@ int v, ret; if (JS_ToInt32Sat(ctx, &v, argv0)) return JS_EXCEPTION; - ret = (v == 1 || v == 2 || v == 4 -#ifdef CONFIG_BIGNUM - || v == 8 -#endif - ); + ret = (v == 1 || v == 2 || v == 4 || v == 8); return JS_NewBool(ctx, ret); } @@ -53991,6 +58180,49 @@ #include <mach/mach.h> #endif +#if defined(__aarch64__) && !defined GPAC_CONFIG_IOS +static inline void cpu_pause(void) +{ + asm volatile("yield" ::: "memory"); +} +#elif defined(__x86_64) || defined(__i386__) +static inline void cpu_pause(void) +{ + asm volatile("pause" ::: "memory"); +} +#else +static inline void cpu_pause(void) +{ +} +#endif + +// no-op: Atomics.pause() is not allowed to block or yield to another +// thread, only to hint the CPU that it should back off for a bit; +// the amount of work we do here is a good enough substitute +static JSValue js_atomics_pause(JSContext *ctx, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + double d; + + if (argc > 0) { + switch (JS_VALUE_GET_NORM_TAG(argv0)) { + case JS_TAG_FLOAT64: // accepted if and only if fraction == 0.0 + d = JS_VALUE_GET_FLOAT64(argv0); + if (isfinite(d)) + if (0 == modf(d, &d)) + break; + // fallthru + default: + return JS_ThrowTypeError(ctx, "not an integral number"); + case JS_TAG_UNDEFINED: + case JS_TAG_INT: + break; + } + } + cpu_pause(); + return JS_UNDEFINED; +} + static JSValue js_atomics_wait(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) @@ -54004,24 +58236,21 @@ int ret, size_log2, res; double d; - ptr = js_atomics_get_ptr(ctx, NULL, &size_log2, NULL, - argv0, argv1, 2); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, NULL, &size_log2, NULL, + argv0, argv1, 2)) return JS_EXCEPTION; -#ifdef CONFIG_BIGNUM if (size_log2 == 3) { if (JS_ToBigInt64(ctx, &v, argv2)) return JS_EXCEPTION; - } else -#endif - { + } else { if (JS_ToInt32(ctx, &v32, argv2)) return JS_EXCEPTION; v = v32; } if (JS_ToFloat64(ctx, &d, argv3)) return JS_EXCEPTION; - if (isnan(d) || d > INT64_MAX) + /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ + if (isnan(d) || d >= 0x1p63) timeout = INT64_MAX; else if (d < 0) timeout = 0; @@ -54098,8 +58327,7 @@ JSAtomicsWaiter *waiter; JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &abuf, NULL, NULL, argv0, argv1, 1); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, &abuf, NULL, NULL, argv0, argv1, 1)) return JS_EXCEPTION; if (JS_IsUndefined(argv2)) { @@ -54108,8 +58336,6 @@ if (JS_ToInt32Clamp(ctx, &count, argv2, 0, INT32_MAX, 0)) return JS_EXCEPTION; } - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); n = 0; if (abuf->shared && count > 0) { @@ -54146,6 +58372,7 @@ JS_CFUNC_MAGIC_DEF("load", 2, js_atomics_op, ATOMICS_OP_LOAD ), JS_CFUNC_DEF("store", 3, js_atomics_store ), JS_CFUNC_DEF("isLockFree", 1, js_atomics_isLockFree ), + JS_CFUNC_DEF("pause", 0, js_atomics_pause ), JS_CFUNC_DEF("wait", 4, js_atomics_wait ), JS_CFUNC_DEF("notify", 3, js_atomics_notify ), JS_PROP_STRING_DEF("Symbol.toStringTag", "Atomics", JS_PROP_CONFIGURABLE ), @@ -54155,98 +58382,398 @@ JS_OBJECT_DEF("Atomics", js_atomics_funcs, countof(js_atomics_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; -void JS_AddIntrinsicAtomics(JSContext *ctx) +static int JS_AddIntrinsicAtomics(JSContext *ctx) { /* add Atomics as autoinit object */ - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj)); + return JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj)); } #endif /* CONFIG_ATOMICS */ -void JS_AddIntrinsicTypedArrays(JSContext *ctx) +int JS_AddIntrinsicTypedArrays(JSContext *ctx) { - JSValue typed_array_base_proto, typed_array_base_func; - JSValueConst array_buffer_func, shared_array_buffer_func; - int i; + JSValue typed_array_base_func, typed_array_base_proto, obj; + int i, ret; - ctx->class_protoJS_CLASS_ARRAY_BUFFER = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_ARRAY_BUFFER, - js_array_buffer_proto_funcs, - countof(js_array_buffer_proto_funcs)); - - array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "ArrayBuffer", - js_array_buffer_constructor, 1, - ctx->class_protoJS_CLASS_ARRAY_BUFFER); - JS_SetPropertyFunctionList(ctx, array_buffer_func, - js_array_buffer_funcs, - countof(js_array_buffer_funcs)); - - ctx->class_protoJS_CLASS_SHARED_ARRAY_BUFFER = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_SHARED_ARRAY_BUFFER, - js_shared_array_buffer_proto_funcs, - countof(js_shared_array_buffer_proto_funcs)); - - shared_array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "SharedArrayBuffer", - js_shared_array_buffer_constructor, 1, - ctx->class_protoJS_CLASS_SHARED_ARRAY_BUFFER); - JS_SetPropertyFunctionList(ctx, shared_array_buffer_func, - js_shared_array_buffer_funcs, - countof(js_shared_array_buffer_funcs)); - - typed_array_base_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, typed_array_base_proto, - js_typed_array_base_proto_funcs, - countof(js_typed_array_base_proto_funcs)); + obj = JS_NewCConstructor(ctx, JS_CLASS_ARRAY_BUFFER, "ArrayBuffer", + js_array_buffer_constructor, 1, JS_CFUNC_constructor, 0, + JS_UNDEFINED, + js_array_buffer_funcs, countof(js_array_buffer_funcs), + js_array_buffer_proto_funcs, countof(js_array_buffer_proto_funcs), + 0); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, obj); + + obj = JS_NewCConstructor(ctx, JS_CLASS_SHARED_ARRAY_BUFFER, "SharedArrayBuffer", + js_shared_array_buffer_constructor, 1, JS_CFUNC_constructor, 0, + JS_UNDEFINED, + js_shared_array_buffer_funcs, countof(js_shared_array_buffer_funcs), + js_shared_array_buffer_proto_funcs, countof(js_shared_array_buffer_proto_funcs), + 0); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, obj); + + + typed_array_base_func = + JS_NewCConstructor(ctx, -1, "TypedArray", + js_typed_array_base_constructor, 0, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + js_typed_array_base_funcs, countof(js_typed_array_base_funcs), + js_typed_array_base_proto_funcs, countof(js_typed_array_base_proto_funcs), + JS_NEW_CTOR_NO_GLOBAL); + if (JS_IsException(typed_array_base_func)) + return -1; /* TypedArray.prototype.toString must be the same object as Array.prototype.toString */ - JSValue obj = JS_GetProperty(ctx, ctx->class_protoJS_CLASS_ARRAY, JS_ATOM_toString); + obj = JS_GetProperty(ctx, ctx->class_protoJS_CLASS_ARRAY, JS_ATOM_toString); + if (JS_IsException(obj)) + goto fail; /* XXX: should use alias method in JSCFunctionListEntry */ //@@@ - JS_DefinePropertyValue(ctx, typed_array_base_proto, JS_ATOM_toString, obj, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - - typed_array_base_func = JS_NewCFunction(ctx, js_typed_array_base_constructor, - "TypedArray", 0); - JS_SetPropertyFunctionList(ctx, typed_array_base_func, - js_typed_array_base_funcs, - countof(js_typed_array_base_funcs)); - JS_SetConstructor(ctx, typed_array_base_func, typed_array_base_proto); + typed_array_base_proto = JS_GetProperty(ctx, typed_array_base_func, JS_ATOM_prototype); + if (JS_IsException(typed_array_base_proto)) + goto fail; + ret = JS_DefinePropertyValue(ctx, typed_array_base_proto, JS_ATOM_toString, obj, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, typed_array_base_proto); + if (ret < 0) + goto fail; + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = js_typed_array_constructor }; for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { - JSValue func_obj; char bufATOM_GET_STR_BUF_SIZE; const char *name; + const JSCFunctionListEntry *bpe; - ctx->class_protoi = JS_NewObjectProto(ctx, typed_array_base_proto); - JS_DefinePropertyValueStr(ctx, ctx->class_protoi, - "BYTES_PER_ELEMENT", - JS_NewInt32(ctx, 1 << typed_array_size_log2(i)), - 0); name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); - func_obj = JS_NewCFunction3(ctx, (JSCFunction *)js_typed_array_constructor, - name, 3, JS_CFUNC_constructor_magic, i, - typed_array_base_func); - JS_NewGlobalCConstructor2(ctx, func_obj, name, ctx->class_protoi); - JS_DefinePropertyValueStr(ctx, func_obj, - "BYTES_PER_ELEMENT", - JS_NewInt32(ctx, 1 << typed_array_size_log2(i)), - 0); + bpe = js_typed_array_funcs + typed_array_size_log2(i); + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + bpe, 1, + bpe, 1, + 0); + if (JS_IsException(obj)) { + fail: + JS_FreeValue(ctx, typed_array_base_func); + return -1; + } + JS_FreeValue(ctx, obj); } - JS_FreeValue(ctx, typed_array_base_proto); JS_FreeValue(ctx, typed_array_base_func); /* DataView */ - ctx->class_protoJS_CLASS_DATAVIEW = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, ctx->class_protoJS_CLASS_DATAVIEW, - js_dataview_proto_funcs, - countof(js_dataview_proto_funcs)); - JS_NewGlobalCConstructorOnly(ctx, "DataView", - js_dataview_constructor, 1, - ctx->class_protoJS_CLASS_DATAVIEW); + obj = JS_NewCConstructor(ctx, JS_CLASS_DATAVIEW, "DataView", + js_dataview_constructor, 1, JS_CFUNC_constructor, 0, + JS_UNDEFINED, + NULL, 0, + js_dataview_proto_funcs, countof(js_dataview_proto_funcs), + 0); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, obj); + /* Atomics */ #ifdef CONFIG_ATOMICS - JS_AddIntrinsicAtomics(ctx); + if (JS_AddIntrinsicAtomics(ctx)) + return -1; #endif + return 0; +} + +/* WeakRef */ + +typedef struct JSWeakRefData { + JSWeakRefHeader weakref_header; + JSValue target; +} JSWeakRefData; + +static void js_weakref_finalizer(JSRuntime *rt, JSValue val) +{ + JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF); + if (!wrd) + return; + js_weakref_free(rt, wrd->target); + list_del(&wrd->weakref_header.link); + js_free_rt(rt, wrd); +} + +static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh) +{ + JSWeakRefData *wrd = container_of(wh, JSWeakRefData, weakref_header); + + if (!js_weakref_is_live(wrd->target)) { + js_weakref_free(rt, wrd->target); + wrd->target = JS_UNDEFINED; + } +} + +static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSValueConst arg; + JSValue obj; + + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + arg = argv0; + if (!js_weakref_is_target(arg)) + return JS_ThrowTypeError(ctx, "invalid target"); + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_WEAK_REF); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JSWeakRefData *wrd = js_mallocz(ctx, sizeof(*wrd)); + if (!wrd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + wrd->target = js_weakref_new(ctx, arg); + wrd->weakref_header.weakref_type = JS_WEAKREF_TYPE_WEAKREF; + list_add_tail(&wrd->weakref_header.link, &ctx->rt->weakref_list); + JS_SetOpaque(obj, wrd); + return obj; +} + +static JSValue js_weakref_deref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSWeakRefData *wrd = JS_GetOpaque2(ctx, this_val, JS_CLASS_WEAK_REF); + if (!wrd) + return JS_EXCEPTION; + if (js_weakref_is_live(wrd->target)) + return JS_DupValue(ctx, wrd->target); + else + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry js_weakref_proto_funcs = { + JS_CFUNC_DEF("deref", 0, js_weakref_deref ), + JS_PROP_STRING_DEF("Symbol.toStringTag", "WeakRef", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_weakref_class_def = { + { JS_ATOM_WeakRef, js_weakref_finalizer, NULL }, /* JS_CLASS_WEAK_REF */ +}; + +typedef struct JSFinRecEntry { + struct list_head link; + JSValue target; + JSValue held_val; + JSValue token; +} JSFinRecEntry; + +typedef struct JSFinalizationRegistryData { + JSWeakRefHeader weakref_header; + struct list_head entries; /* list of JSFinRecEntry.link */ + JSContext *realm; + JSValue cb; +} JSFinalizationRegistryData; + +static void js_finrec_finalizer(JSRuntime *rt, JSValue val) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY); + if (frd) { + struct list_head *el, *el1; + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + js_weakref_free(rt, fre->target); + js_weakref_free(rt, fre->token); + JS_FreeValueRT(rt, fre->held_val); + js_free_rt(rt, fre); + } + JS_FreeValueRT(rt, frd->cb); + JS_FreeContext(frd->realm); + list_del(&frd->weakref_header.link); + js_free_rt(rt, frd); + } +} + +static void js_finrec_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY); + struct list_head *el; + if (frd) { + list_for_each(el, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + JS_MarkValue(rt, fre->held_val, mark_func); + } + JS_MarkValue(rt, frd->cb, mark_func); + mark_func(rt, &frd->realm->header); + } +} + +static JSValue js_finrec_job(JSContext *ctx, int argc, JSValueConst *argv) +{ + return JS_Call(ctx, argv0, JS_UNDEFINED, 1, &argv1); +} + +static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh) +{ + JSFinalizationRegistryData *frd = container_of(wh, JSFinalizationRegistryData, weakref_header); + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + + if (!js_weakref_is_live(fre->token)) { + js_weakref_free(rt, fre->token); + fre->token = JS_UNDEFINED; + } + + if (!js_weakref_is_live(fre->target)) { + JSValueConst args2; + args0 = frd->cb; + args1 = fre->held_val; + JS_EnqueueJob(frd->realm, js_finrec_job, 2, args); + + js_weakref_free(rt, fre->target); + js_weakref_free(rt, fre->token); + JS_FreeValueRT(rt, fre->held_val); + list_del(&fre->link); + js_free_rt(rt, fre); + } + } +} + +static JSValue js_finrec_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSValueConst cb; + JSValue obj; + JSFinalizationRegistryData *frd; + + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + cb = argv0; + if (!JS_IsFunction(ctx, cb)) + return JS_ThrowTypeError(ctx, "argument must be a function"); + + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_FINALIZATION_REGISTRY); + if (JS_IsException(obj)) + return JS_EXCEPTION; + frd = js_mallocz(ctx, sizeof(*frd)); + if (!frd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + frd->weakref_header.weakref_type = JS_WEAKREF_TYPE_FINREC; + list_add_tail(&frd->weakref_header.link, &ctx->rt->weakref_list); + init_list_head(&frd->entries); + frd->realm = JS_DupContext(ctx); + frd->cb = JS_DupValue(ctx, cb); + JS_SetOpaque(obj, frd); + return obj; +} + +static JSValue js_finrec_register(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst target, held_val, token; + JSFinalizationRegistryData *frd; + JSFinRecEntry *fre; + + frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY); + if (!frd) + return JS_EXCEPTION; + target = argv0; + held_val = argv1; + token = argc > 2 ? argv2 : JS_UNDEFINED; + + if (!js_weakref_is_target(target)) + return JS_ThrowTypeError(ctx, "invalid target"); + if (js_same_value(ctx, target, held_val)) + return JS_ThrowTypeError(ctx, "held value cannot be the target"); + if (!JS_IsUndefined(token) && !js_weakref_is_target(token)) + return JS_ThrowTypeError(ctx, "invalid unregister token"); + fre = js_malloc(ctx, sizeof(*fre)); + if (!fre) + return JS_EXCEPTION; + fre->target = js_weakref_new(ctx, target); + fre->held_val = JS_DupValue(ctx, held_val); + fre->token = js_weakref_new(ctx, token); + list_add_tail(&fre->link, &frd->entries); + return JS_UNDEFINED; +} + +static JSValue js_finrec_unregister(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY); + JSValueConst token; + BOOL removed; + struct list_head *el, *el1; + + if (!frd) + return JS_EXCEPTION; + token = argv0; + if (!js_weakref_is_target(token)) + return JS_ThrowTypeError(ctx, "invalid unregister token"); + + removed = FALSE; + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + if (js_weakref_is_live(fre->token) && js_same_value(ctx, fre->token, token)) { + js_weakref_free(ctx->rt, fre->target); + js_weakref_free(ctx->rt, fre->token); + JS_FreeValue(ctx, fre->held_val); + list_del(&fre->link); + js_free(ctx, fre); + removed = TRUE; + } + } + return JS_NewBool(ctx, removed); +} + +static const JSCFunctionListEntry js_finrec_proto_funcs = { + JS_CFUNC_DEF("register", 2, js_finrec_register ), + JS_CFUNC_DEF("unregister", 1, js_finrec_unregister ), + JS_PROP_STRING_DEF("Symbol.toStringTag", "FinalizationRegistry", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_finrec_class_def = { + { JS_ATOM_FinalizationRegistry, js_finrec_finalizer, js_finrec_mark }, /* JS_CLASS_FINALIZATION_REGISTRY */ +}; + +int JS_AddIntrinsicWeakRef(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + JSValue obj; + + /* WeakRef */ + if (!JS_IsRegisteredClass(rt, JS_CLASS_WEAK_REF)) { + if (init_class_range(rt, js_weakref_class_def, JS_CLASS_WEAK_REF, + countof(js_weakref_class_def))) + return -1; + } + obj = JS_NewCConstructor(ctx, JS_CLASS_WEAK_REF, "WeakRef", + js_weakref_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + NULL, 0, + js_weakref_proto_funcs, countof(js_weakref_proto_funcs), + 0); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, obj); + + /* FinalizationRegistry */ + if (!JS_IsRegisteredClass(rt, JS_CLASS_FINALIZATION_REGISTRY)) { + if (init_class_range(rt, js_finrec_class_def, JS_CLASS_FINALIZATION_REGISTRY, + countof(js_finrec_class_def))) + return -1; + } + + obj = JS_NewCConstructor(ctx, JS_CLASS_FINALIZATION_REGISTRY, "FinalizationRegistry", + js_finrec_constructor, 1, JS_CFUNC_constructor_or_func, 0, + JS_UNDEFINED, + NULL, 0, + js_finrec_proto_funcs, countof(js_finrec_proto_funcs), + 0); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, obj); + return 0; } /* return -1 if exception (proxy case) or TRUE/FALSE */
View file
gpac-2.4.0.tar.gz/src/quickjs/quickjs.h -> gpac-26.02.0.tar.gz/src/quickjs/quickjs.h
Changed
@@ -27,6 +27,7 @@ #include <stdio.h> #include <stdint.h> +#include <string.h> #ifdef __cplusplus extern "C" { @@ -48,7 +49,6 @@ typedef struct JSRuntime JSRuntime; typedef struct JSContext JSContext; -typedef struct JSObject JSObject; typedef struct JSClass JSClass; typedef uint32_t JSClassID; typedef uint32_t JSAtom; @@ -64,14 +64,21 @@ #define JS_NAN_BOXING #endif +#if defined(__SIZEOF_INT128__) && (INTPTR_MAX >= INT64_MAX) +#define JS_LIMB_BITS 64 +#else +#define JS_LIMB_BITS 32 +#endif + +#define JS_SHORT_BIG_INT_BITS JS_LIMB_BITS + enum { /* all tags with a reference count are negative */ - JS_TAG_FIRST = -11, /* first negative tag */ - JS_TAG_BIG_DECIMAL = -11, - JS_TAG_BIG_INT = -10, - JS_TAG_BIG_FLOAT = -9, + JS_TAG_FIRST = -9, /* first negative tag */ + JS_TAG_BIG_INT = -9, JS_TAG_SYMBOL = -8, JS_TAG_STRING = -7, + JS_TAG_STRING_ROPE = -6, JS_TAG_MODULE = -3, /* used internally */ JS_TAG_FUNCTION_BYTECODE = -2, /* used internally */ JS_TAG_OBJECT = -1, @@ -83,7 +90,8 @@ JS_TAG_UNINITIALIZED = 4, JS_TAG_CATCH_OFFSET = 5, JS_TAG_EXCEPTION = 6, - JS_TAG_FLOAT64 = 7, + JS_TAG_SHORT_BIG_INT = 7, + JS_TAG_FLOAT64 = 8, /* any larger tag is FLOAT64 if JS_NAN_BOXING */ }; @@ -108,6 +116,7 @@ #define JS_VALUE_GET_INT(v) (int)((intptr_t)(v) >> 4) #define JS_VALUE_GET_BOOL(v) JS_VALUE_GET_INT(v) #define JS_VALUE_GET_FLOAT64(v) (double)JS_VALUE_GET_INT(v) +#define JS_VALUE_GET_SHORT_BIG_INT(v) JS_VALUE_GET_INT(v) #define JS_VALUE_GET_PTR(v) (void *)((intptr_t)(v) & ~0xf) #define JS_MKVAL(tag, val) (JSValue)(intptr_t)(((val) << 4) | (tag)) @@ -126,7 +135,12 @@ { return 0; } - + +static inline JSValue __JS_NewShortBigInt(JSContext *ctx, int32_t d) +{ + return JS_MKVAL(JS_TAG_SHORT_BIG_INT, d); +} + #elif defined(JS_NAN_BOXING) typedef uint64_t JSValue; @@ -136,6 +150,7 @@ #define JS_VALUE_GET_TAG(v) (int)((v) >> 32) #define JS_VALUE_GET_INT(v) (int)(v) #define JS_VALUE_GET_BOOL(v) (int)(v) +#define JS_VALUE_GET_SHORT_BIG_INT(v) (int)(v) #define JS_VALUE_GET_PTR(v) (void *)(intptr_t)(v) #define JS_MKVAL(tag, val) (((uint64_t)(tag) << 32) | (uint32_t)(val)) @@ -191,13 +206,23 @@ tag = JS_VALUE_GET_TAG(v); return tag == (JS_NAN >> 32); } - + +static inline JSValue __JS_NewShortBigInt(JSContext *ctx, int32_t d) +{ + return JS_MKVAL(JS_TAG_SHORT_BIG_INT, d); +} + #else /* !JS_NAN_BOXING */ typedef union JSValueUnion { void *ptr; int32_t int32; double float64; +#if JS_SHORT_BIG_INT_BITS == 32 + int32_t short_big_int; +#else + int64_t short_big_int; +#endif } JSValueUnion; typedef struct JSValue { @@ -213,6 +238,7 @@ #define JS_VALUE_GET_INT(v) ((v).u.int32) #define JS_VALUE_GET_BOOL(v) ((v).u.int32) #define JS_VALUE_GET_FLOAT64(v) ((v).u.float64) +#define JS_VALUE_GET_SHORT_BIG_INT(v) ((v).u.short_big_int) #define JS_VALUE_GET_PTR(v) ((v).u.ptr) @@ -252,13 +278,19 @@ return (u.u64 & 0x7fffffffffffffff) > 0x7ff0000000000000; } +static inline JSValue __JS_NewShortBigInt(JSContext *ctx, int64_t d) +{ + JSValue v; + v.tag = JS_TAG_SHORT_BIG_INT; + v.u.short_big_int = d; + return v; +} + #endif /* !JS_NAN_BOXING */ #define JS_VALUE_IS_BOTH_INT(v1, v2) ((JS_VALUE_GET_TAG(v1) | JS_VALUE_GET_TAG(v2)) == 0) #define JS_VALUE_IS_BOTH_FLOAT(v1, v2) (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v1)) && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v2))) -#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v)) -#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) #define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST) /* special values */ @@ -297,10 +329,11 @@ (JS_SetProperty) */ #define JS_PROP_THROW_STRICT (1 << 15) -#define JS_PROP_NO_ADD (1 << 16) /* internal use */ -#define JS_PROP_NO_EXOTIC (1 << 17) /* internal use */ +#define JS_PROP_NO_EXOTIC (1 << 16) /* internal use */ -#define JS_DEFAULT_STACK_SIZE (256 * 1024) +#ifndef JS_DEFAULT_STACK_SIZE +#define JS_DEFAULT_STACK_SIZE (1024 * 1024) +#endif /* JS_Eval() flags */ #define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */ @@ -310,13 +343,15 @@ #define JS_EVAL_TYPE_MASK (3 << 0) #define JS_EVAL_FLAG_STRICT (1 << 3) /* force 'strict' mode */ -#define JS_EVAL_FLAG_STRIP (1 << 4) /* force 'strip' mode */ /* compile but do not run. The result is an object with a JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed with JS_EvalFunction(). */ #define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) /* don't include the stack frames before this eval in the Error() backtraces */ #define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6) +/* allow top-level await in normal script. JS_Eval() returns a + promise. Only allowed with JS_EVAL_TYPE_GLOBAL */ +#define JS_EVAL_FLAG_ASYNC (1 << 7) typedef JSValue JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); @@ -369,24 +404,18 @@ /* the following functions are used to select the intrinsic object to save memory */ JSContext *JS_NewContextRaw(JSRuntime *rt); -void JS_AddIntrinsicBaseObjects(JSContext *ctx); -void JS_AddIntrinsicDate(JSContext *ctx); -void JS_AddIntrinsicEval(JSContext *ctx); -void JS_AddIntrinsicStringNormalize(JSContext *ctx); +int JS_AddIntrinsicBaseObjects(JSContext *ctx); +int JS_AddIntrinsicDate(JSContext *ctx); +int JS_AddIntrinsicEval(JSContext *ctx); +int JS_AddIntrinsicStringNormalize(JSContext *ctx); void JS_AddIntrinsicRegExpCompiler(JSContext *ctx); -void JS_AddIntrinsicRegExp(JSContext *ctx); -void JS_AddIntrinsicJSON(JSContext *ctx); -void JS_AddIntrinsicProxy(JSContext *ctx); -void JS_AddIntrinsicMapSet(JSContext *ctx); -void JS_AddIntrinsicTypedArrays(JSContext *ctx); -void JS_AddIntrinsicPromise(JSContext *ctx); -void JS_AddIntrinsicBigInt(JSContext *ctx); -void JS_AddIntrinsicBigFloat(JSContext *ctx); -void JS_AddIntrinsicBigDecimal(JSContext *ctx); -/* enable operator overloading */ -void JS_AddIntrinsicOperators(JSContext *ctx); -/* enable "use math" */ -void JS_EnableBignumExt(JSContext *ctx, JS_BOOL enable); +int JS_AddIntrinsicRegExp(JSContext *ctx); +int JS_AddIntrinsicJSON(JSContext *ctx); +int JS_AddIntrinsicProxy(JSContext *ctx); +int JS_AddIntrinsicMapSet(JSContext *ctx); +int JS_AddIntrinsicTypedArrays(JSContext *ctx); +int JS_AddIntrinsicPromise(JSContext *ctx); +int JS_AddIntrinsicWeakRef(JSContext *ctx); JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -436,7 +465,11 @@ void JS_FreeAtomRT(JSRuntime *rt, JSAtom v); JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom); JSValue JS_AtomToString(JSContext *ctx, JSAtom atom); -const char *JS_AtomToCString(JSContext *ctx, JSAtom atom); +const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom); +static inline const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) +{ + return JS_AtomToCStringLen(ctx, NULL, atom); +} JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val); /* object class support */ @@ -481,6 +514,17 @@ /* return < 0 if exception or TRUE/FALSE */ int (*set_property)(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + + /* To get a consistent object behavior when get_prototype != NULL, + get_property, set_property and set_prototype must be != NULL + and the object must be created with a JS_NULL prototype. */ + JSValue (*get_prototype)(JSContext *ctx, JSValueConst obj); + /* return < 0 if exception or TRUE/FALSE */ + int (*set_prototype)(JSContext *ctx, JSValueConst obj, JSValueConst proto_val); + /* return < 0 if exception or TRUE/FALSE */ + int (*is_extensible)(JSContext *ctx, JSValueConst obj); + /* return < 0 if exception or TRUE/FALSE */ + int (*prevent_extensions)(JSContext *ctx, JSValueConst obj); } JSClassExoticMethods; typedef void JSClassFinalizer(JSRuntime *rt, JSValue val); @@ -506,7 +550,10 @@ JSClassExoticMethods *exotic; } JSClassDef; +#define JS_INVALID_CLASS_ID 0 JSClassID JS_NewClassID(JSClassID *pclass_id); +/* Returns the class ID if `v` is an object, otherwise returns JS_INVALID_CLASS_ID. */ +JSClassID JS_GetClassID(JSValue v); int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def); int JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id); @@ -554,36 +601,34 @@ static js_force_inline JSValue JS_NewFloat64(JSContext *ctx, double d) { - JSValue v; int32_t val; union { double d; uint64_t u; } u, t; - u.d = d; + if (d >= INT32_MIN && d <= INT32_MAX) { /*for asan tests*/ #ifdef ASAN_ENABLED - if (isnan(d)) - return JS_NAN; + if (isnan(d)) + return JS_NAN; - if (d > 2147483647.0) - return __JS_NewFloat64(ctx, d); + if (d > 2147483647.0) + return __JS_NewFloat64(ctx, d); - if (d < -2147483648.0) - return __JS_NewFloat64(ctx, d); + if (d < -2147483648.0) + return __JS_NewFloat64(ctx, d); #endif - val = (int32_t) d; - t.d = val; - /* -0 cannot be represented as integer, so we compare the bit - representation */ - if (u.u == t.u) { - v = JS_MKVAL(JS_TAG_INT, val); - } else { - v = __JS_NewFloat64(ctx, d); + u.d = d; + val = (int32_t)d; + t.d = val; + /* -0 cannot be represented as integer, so we compare the bit + representation */ + if (u.u == t.u) + return JS_MKVAL(JS_TAG_INT, val); } - return v; + return __JS_NewFloat64(ctx, d); } static inline JS_BOOL JS_IsNumber(JSValueConst v) @@ -601,19 +646,7 @@ static inline JS_BOOL JS_IsBigInt(JSContext *ctx, JSValueConst v) { int tag = JS_VALUE_GET_TAG(v); - return tag == JS_TAG_BIG_INT; -} - -static inline JS_BOOL JS_IsBigFloat(JSValueConst v) -{ - int tag = JS_VALUE_GET_TAG(v); - return tag == JS_TAG_BIG_FLOAT; -} - -static inline JS_BOOL JS_IsBigDecimal(JSValueConst v) -{ - int tag = JS_VALUE_GET_TAG(v); - return tag == JS_TAG_BIG_DECIMAL; + return tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT; } static inline JS_BOOL JS_IsBool(JSValueConst v) @@ -643,7 +676,8 @@ static inline JS_BOOL JS_IsString(JSValueConst v) { - return JS_VALUE_GET_TAG(v) == JS_TAG_STRING; + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING || + JS_VALUE_GET_TAG(v) == JS_TAG_STRING_ROPE; } static inline JS_BOOL JS_IsSymbol(JSValueConst v) @@ -657,9 +691,10 @@ } JSValue JS_Throw(JSContext *ctx, JSValue obj); +void JS_SetUncatchableException(JSContext *ctx, JS_BOOL flag); JSValue JS_GetException(JSContext *ctx); +JS_BOOL JS_HasException(JSContext *ctx); JS_BOOL JS_IsError(JSContext *ctx, JSValueConst val); -void JS_ResetUncatchableError(JSContext *ctx); JSValue JS_NewError(JSContext *ctx); JSValue __js_printf_like(2, 3) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...); JSValue __js_printf_like(2, 3) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...); @@ -715,6 +750,10 @@ #endif } +JS_BOOL JS_StrictEq(JSContext *ctx, JSValueConst op1, JSValueConst op2); +JS_BOOL JS_SameValue(JSContext *ctx, JSValueConst op1, JSValueConst op2); +JS_BOOL JS_SameValueZero(JSContext *ctx, JSValueConst op1, JSValueConst op2); + int JS_ToBool(JSContext *ctx, JSValueConst val); /* return -1 for JS_EXCEPTION */ int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val); static inline int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValueConst val) @@ -730,7 +769,10 @@ int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValueConst val); JSValue JS_NewStringLen(JSContext *ctx, const char *str1, size_t len1); -JSValue JS_NewString(JSContext *ctx, const char *str); +static inline JSValue JS_NewString(JSContext *ctx, const char *str) +{ + return JS_NewStringLen(ctx, str, strlen(str)); +} JSValue JS_NewAtomString(JSContext *ctx, const char *str); JSValue JS_ToString(JSContext *ctx, JSValueConst val); JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val); @@ -757,6 +799,8 @@ JSValue JS_NewArray(JSContext *ctx); int JS_IsArray(JSContext *ctx, JSValueConst val); +JSValue JS_NewDate(JSContext *ctx, double epoch_ms); + JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst receiver, JS_BOOL throw_ref_error); @@ -770,13 +814,13 @@ JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, uint32_t idx); -int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj, - JSAtom prop, JSValue val, +int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValue val, JSValueConst this_obj, int flags); static inline int JS_SetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue val) { - return JS_SetPropertyInternal(ctx, this_obj, prop, val, JS_PROP_THROW); + return JS_SetPropertyInternal(ctx, this_obj, prop, val, this_obj, JS_PROP_THROW); } int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, uint32_t idx, JSValue val); @@ -801,6 +845,8 @@ int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj, int flags); +void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, + uint32_t len); int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, JSAtom prop); @@ -838,6 +884,7 @@ void JS_SetOpaque(JSValue obj, void *opaque); void *JS_GetOpaque(JSValueConst obj, JSClassID class_id); void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id); +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id); /* 'buf' must be zero terminated i.e. bufbuf_len = '\0'. */ JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, @@ -855,6 +902,24 @@ JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len); void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj); uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj); + +typedef enum JSTypedArrayEnum { + JS_TYPED_ARRAY_UINT8C = 0, + JS_TYPED_ARRAY_INT8, + JS_TYPED_ARRAY_UINT8, + JS_TYPED_ARRAY_INT16, + JS_TYPED_ARRAY_UINT16, + JS_TYPED_ARRAY_INT32, + JS_TYPED_ARRAY_UINT32, + JS_TYPED_ARRAY_BIG_INT64, + JS_TYPED_ARRAY_BIG_UINT64, + JS_TYPED_ARRAY_FLOAT16, + JS_TYPED_ARRAY_FLOAT32, + JS_TYPED_ARRAY_FLOAT64, +} JSTypedArrayEnum; + +JSValue JS_NewTypedArray(JSContext *ctx, int argc, JSValueConst *argv, + JSTypedArrayEnum array_type); JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValueConst obj, size_t *pbyte_offset, size_t *pbyte_length, @@ -868,7 +933,15 @@ void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, const JSSharedArrayBufferFunctions *sf); +typedef enum JSPromiseStateEnum { + JS_PROMISE_PENDING, + JS_PROMISE_FULFILLED, + JS_PROMISE_REJECTED, +} JSPromiseStateEnum; + JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs); +JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise); +JSValue JS_PromiseResult(JSContext *ctx, JSValue promise); /* is_handled = TRUE means that the rejection is handled */ typedef void JSHostPromiseRejectionTracker(JSContext *ctx, JSValueConst promise, @@ -881,6 +954,12 @@ void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque); /* if can_block is TRUE, Atomics.wait() can be used */ void JS_SetCanBlock(JSRuntime *rt, JS_BOOL can_block); +/* select which debug info is stripped from the compiled code */ +#define JS_STRIP_SOURCE (1 << 0) /* strip source code */ +#define JS_STRIP_DEBUG (1 << 1) /* strip all debug info including source code */ +void JS_SetStripInfo(JSRuntime *rt, int flags); +int JS_GetStripInfo(JSRuntime *rt); + /* set the IsHTMLDDA internal slot */ void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj); @@ -893,15 +972,29 @@ const char *module_name, void *opaque); typedef JSModuleDef *JSModuleLoaderFunc(JSContext *ctx, const char *module_name, void *opaque); - +typedef JSModuleDef *JSModuleLoaderFunc2(JSContext *ctx, + const char *module_name, void *opaque, + JSValueConst attributes); +/* return -1 if exception, 0 if OK */ +typedef int JSModuleCheckSupportedImportAttributes(JSContext *ctx, void *opaque, + JSValueConst attributes); + /* module_normalize = NULL is allowed and invokes the default module filename normalizer */ void JS_SetModuleLoaderFunc(JSRuntime *rt, JSModuleNormalizeFunc *module_normalize, JSModuleLoaderFunc *module_loader, void *opaque); +/* same as JS_SetModuleLoaderFunc but with attributes. if + module_check_attrs = NULL, no attribute checking is done. */ +void JS_SetModuleLoaderFunc2(JSRuntime *rt, + JSModuleNormalizeFunc *module_normalize, + JSModuleLoaderFunc2 *module_loader, + JSModuleCheckSupportedImportAttributes *module_check_attrs, + void *opaque); /* return the import.meta object of a module */ JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m); JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m); +JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m); /* JS Job support */ @@ -939,8 +1032,8 @@ /* only exported for os.Worker() */ JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels); /* only exported for os.Worker() */ -JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename, - const char *filename); +JSValue JS_LoadModule(JSContext *ctx, const char *basename, + const char *filename); /* C function definition */ typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */ @@ -992,10 +1085,12 @@ const char *name, int length, JSCFunctionEnum cproto, int magic) { - return JS_NewCFunction2(ctx, (JSCFunction *)func, name, length, cproto, magic); + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = func }; + return JS_NewCFunction2(ctx, ft.generic, name, length, cproto, magic); } -void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, - JSValueConst proto); +int JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, + JSValueConst proto); /* C property definition */ @@ -1039,6 +1134,8 @@ #define JS_DEF_PROP_UNDEFINED 7 #define JS_DEF_OBJECT 8 #define JS_DEF_ALIAS 9 +#define JS_DEF_PROP_ATOM 10 +#define JS_DEF_PROP_BOOL 11 /* Note: c++ does not like nested designators */ #define JS_CFUNC_DEF(name, length, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } } @@ -1052,13 +1149,15 @@ #define JS_PROP_INT64_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_INT64, 0, .u = { .i64 = val } } #define JS_PROP_DOUBLE_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_DOUBLE, 0, .u = { .f64 = val } } #define JS_PROP_UNDEFINED_DEF(name, prop_flags) { name, prop_flags, JS_DEF_PROP_UNDEFINED, 0, .u = { .i32 = 0 } } +#define JS_PROP_ATOM_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_ATOM, 0, .u = { .i32 = val } } +#define JS_PROP_BOOL_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_BOOL, 0, .u = { .i32 = val } } #define JS_OBJECT_DEF(name, tab, len, prop_flags) { name, prop_flags, JS_DEF_OBJECT, 0, .u = { .prop_list = { tab, len } } } #define JS_ALIAS_DEF(name, from) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_ALIAS, 0, .u = { .alias = { from, -1 } } } #define JS_ALIAS_BASE_DEF(name, from, base) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_ALIAS, 0, .u = { .alias = { from, base } } } -void JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj, - const JSCFunctionListEntry *tab, - int len); +int JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj, + const JSCFunctionListEntry *tab, + int len); /* C module definition */ @@ -1075,6 +1174,29 @@ JSValue val); int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, const JSCFunctionListEntry *tab, int len); +/* associate a JSValue to a C module */ +int JS_SetModulePrivateValue(JSContext *ctx, JSModuleDef *m, JSValue val); +JSValue JS_GetModulePrivateValue(JSContext *ctx, JSModuleDef *m); + +/* debug value output */ + +typedef struct { + JS_BOOL show_hidden : 8; /* only show enumerable properties */ + JS_BOOL raw_dump : 8; /* avoid doing autoinit and avoid any malloc() call (for internal use) */ + uint32_t max_depth; /* recurse up to this depth, 0 = no limit */ + uint32_t max_string_length; /* print no more than this length for + strings, 0 = no limit */ + uint32_t max_item_count; /* print no more than this count for + arrays or objects, 0 = no limit */ +} JSPrintValueOptions; + +typedef void JSPrintValueWrite(void *opaque, const char *buf, size_t len); + +void JS_PrintValueSetDefaultOptions(JSPrintValueOptions *options); +void JS_PrintValueRT(JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options); +void JS_PrintValue(JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options); #undef js_unlikely #undef js_force_inline @@ -1086,7 +1208,6 @@ /* return -1 if exception (proxy case) or TRUE/FALSE */ int JS_SwitchClassID(JSValue obj, JSClassID class_id); -void *JS_GetOpaque_Nocheck(JSValueConst obj); /*end GPAC patched*/ #ifdef __cplusplus
View file
gpac-2.4.0.tar.gz/src/scene_manager/encode_isom.c -> gpac-26.02.0.tar.gz/src/scene_manager/encode_isom.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / Scene Management sub-project @@ -130,7 +130,6 @@ char szName1024; char *ext; GF_Descriptor *d; - GF_MediaImporter import; GF_MuxInfo *mux = NULL; if (od_sample_rap && src->ESID && gf_isom_get_track_by_id(mp4, src->ESID) ) { @@ -218,13 +217,15 @@ track = gf_isom_get_track_by_id(mp4, src->ESID); if (track) return GF_OK; if (mediaSource) { - memset(&import, 0, sizeof(GF_MediaImporter)); - import.dest = mp4; - import.trackID = src->ESID; - import.orig = gf_isom_open(mediaSource, GF_ISOM_OPEN_READ, NULL); - if (import.orig) { - e = gf_media_import(&import); - gf_isom_delete(import.orig); + GF_MediaImporter *import; + GF_SAFEALLOC(import, GF_MediaImporter); + if (!import) return GF_OUT_OF_MEM; + import->dest = mp4; + import->trackID = src->ESID; + import->orig = gf_isom_open(mediaSource, GF_ISOM_OPEN_READ, NULL); + if (import->orig) { + e = gf_media_import(import); + gf_isom_delete(import->orig); return e; } } @@ -233,7 +234,10 @@ if (!mux->file_name) return GF_OK; - memset(&import, 0, sizeof(GF_MediaImporter)); + GF_MediaImporter *import; + GF_SAFEALLOC(import, GF_MediaImporter); + if (!import) return GF_OUT_OF_MEM; + if (mux->src_url) { ext = gf_url_concatenate(mux->src_url, mux->file_name); strcpy(szName, ext ? ext : mux->file_name); @@ -256,8 +260,8 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("ISO File Encode missing track specifier for AVI import (file#audio, file#video)\n")); return GF_NOT_SUPPORTED; } - if (isVideo) import.trackID = 1; - else import.trackID = 2; + if (isVideo) import->trackID = 1; + else import->trackID = 2; ext = strchr(ext, '#'); if (ext) ext0 = 0; } @@ -265,31 +269,35 @@ if (ext) { ext = strchr(ext, '#'); if (ext) { - import.trackID = atoi(ext+1); + import->trackID = atoi(ext+1); ext0 = 0; } } - import.streamFormat = mux->streamFormat; - import.dest = mp4; - import.esd = src; + import->streamFormat = mux->streamFormat; + import->dest = mp4; + import->esd = src; if (mux->duration) { - import.duration.num = mux->duration; - import.duration.den = 1000; + import->duration.num = mux->duration; + import->duration.den = 1000; + } + import->flags = mux->import_flags | GF_IMPORT_FORCE_MPEG4; + import->video_fps.num = (s32) (1000*mux->frame_rate); + import->video_fps.den = 1000; + import->in_name = szName; + import->initial_time_offset = imp_time; + e = gf_media_import(import); + if (e) { + gf_free(import); + return e; } - import.flags = mux->import_flags | GF_IMPORT_FORCE_MPEG4; - import.video_fps.num = (s32) (1000*mux->frame_rate); - import.video_fps.den = 1000; - import.in_name = szName; - import.initial_time_offset = imp_time; - e = gf_media_import(&import); - if (e) return e; - if (src->OCRESID) { - gf_isom_set_track_reference(mp4, gf_isom_get_track_by_id(mp4, import.final_trackID), GF_ISOM_REF_OCR, src->OCRESID); + gf_isom_set_track_reference(mp4, gf_isom_get_track_by_id(mp4, import->final_trackID), GF_ISOM_REF_OCR, src->OCRESID); } - track = gf_isom_get_track_by_id(mp4, import.final_trackID); + track = gf_isom_get_track_by_id(mp4, import->final_trackID); + gf_free(import); + i=0; while ((d = gf_list_enum(src->extensionDescriptors, &i))) { Bool do_del = GF_FALSE;
View file
gpac-2.4.0.tar.gz/src/scene_manager/loader_bt.c -> gpac-26.02.0.tar.gz/src/scene_manager/loader_bt.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / Scene Management sub-project @@ -107,6 +107,7 @@ u32 def_w, def_h; + unsigned short line_cacheBT_LINE_SIZE; } GF_BTParser; GF_Err gf_bt_parse_bifs_command(GF_BTParser *parser, char *name, GF_List *cmdList); @@ -173,7 +174,7 @@ if (parser->unicode_type) { u8 c1, c2; unsigned short wchar; - unsigned short lBT_LINE_SIZE; + unsigned short *l = parser->line_cache; unsigned short *dst = l; Bool is_ret = 0; u32 last_space_pos, last_space_pos_stream; @@ -396,12 +397,20 @@ for (i=0; i<count; i++) { u32 symb_len, val_len, copy_len; BTDefSymbol *def = (BTDefSymbol *)gf_list_get(parser->def_symbols, i); + if (!def->name) continue; char *start = strstr(parser->line_buffer, def->name); if (!start) continue; + symb_len = (u32) strlen(def->name); if (!strchr(" \n\r\t,{}\'\"", startsymb_len)) continue; val_len = (u32) strlen(def->value); + + if (val_len >= BT_LINE_SIZE || symb_len >= BT_LINE_SIZE) continue; + copy_len = (u32) strlen(start + symb_len) + 1; + + if ((start-parser->line_buffer) + val_len + copy_len > BT_LINE_SIZE) continue; + memmove(start + val_len, start + symb_len, sizeof(char)*copy_len); memcpy(start, def->value, sizeof(char)*val_len); parser->line_size = (u32) strlen(parser->line_buffer); @@ -448,6 +457,7 @@ has_quote = 0; while (go) { if (parser->line_pos+i>=parser->line_size) break; + if (i>=GF_ARRAY_LENGTH(parser->cur_buffer)) break; if (parser->line_bufferparser->line_pos + i == '\"') { if (!has_quote) has_quote = 1; @@ -475,11 +485,13 @@ } if (!go) break; } + if (i >= GF_ARRAY_LENGTH(parser->cur_buffer)) + break; parser->cur_bufferi = parser->line_bufferparser->line_pos + i; i++; if (parser->line_pos+i==parser->line_size) break; } - parser->cur_bufferi = 0; + parser->cur_bufferMIN(i, GF_ARRAY_LENGTH(parser->cur_buffer)-1) = 0; parser->line_pos += i; return parser->cur_buffer; } @@ -1621,7 +1633,7 @@ if ( (!prev_is_insert && !strcmp(str, "AT")) || !strcmp(str, "PROTO") ) { /*only check in current command (but be aware of conditionals..)*/ - if (gf_list_find(parser->bifs_au->commands, parser->cur_com)) { + if (parser->bifs_au && gf_list_find(parser->bifs_au->commands, parser->cur_com)) { break; } continue; @@ -1740,6 +1752,7 @@ str = gf_bt_get_next(parser, 0); name = gf_strdup(str); if (!gf_bt_check_code(parser, '')) { + gf_free(name); return gf_bt_report(parser, GF_BAD_PARAM, " expected in proto declare"); } pID = gf_bt_get_next_proto_id(parser); @@ -3435,7 +3448,7 @@ if (!parser->stream_id) parser->stream_id = parser->base_bifs_id; if (!parser->stream_id || (parser->od_es && (parser->stream_id==parser->od_es->ESID)) ) parser->stream_id = parser->base_bifs_id; - if (parser->bifs_es->ESID != parser->stream_id) { + if (parser->bifs_es && parser->bifs_es->ESID != parser->stream_id) { GF_StreamContext *prev = parser->bifs_es; parser->bifs_es = gf_sm_stream_new(parser->load->ctx, (u16) parser->stream_id, GF_STREAM_SCENE, GF_CODECID_BIFS); /*force new AU if stream changed*/ @@ -3444,14 +3457,20 @@ parser->bifs_au = NULL; } } - if (force_new_com) { + if (parser->bifs_es && force_new_com) { force_new_com = 0; parser->bifs_au = gf_list_last(parser->bifs_es->AUs); parser->au_time = (u32) (parser->bifs_au ? parser->bifs_au->timing : 0) + 1; parser->bifs_au = NULL; } - if (!parser->bifs_au) parser->bifs_au = gf_sm_stream_au_new(parser->bifs_es, parser->au_time, 0, parser->au_is_rap); + if (!parser->bifs_au) { + if (!parser->bifs_es) { + parser->last_error = GF_BAD_PARAM; + break; + } + parser->bifs_au = gf_sm_stream_au_new(parser->bifs_es, parser->au_time, 0, parser->au_is_rap); + } gf_bt_parse_bifs_command(parser, str, parser->bifs_au->commands); if (is_base_stream) parser->stream_id= 0; } @@ -3584,7 +3603,7 @@ if (input_only) return GF_OK; - /*initalize default streams in the context*/ + /*initialize default streams in the context*/ /*chunk parsing*/ if (load->flags & GF_SM_LOAD_CONTEXT_READY) { @@ -3790,34 +3809,42 @@ GF_EXPORT GF_List *gf_sm_load_bt_from_string(GF_SceneGraph *in_scene, char *node_str, Bool force_wrl) { - GF_SceneLoader ctx; - GF_BTParser parser; - memset(&ctx, 0, sizeof(GF_SceneLoader)); - ctx.scene_graph = in_scene; - memset(&parser, 0, sizeof(GF_BTParser)); - parser.line_buffer = node_str; - parser.line_size = (u32) strlen(node_str); - parser.load = &ctx; - parser.top_nodes = gf_list_new(); - parser.undef_nodes = gf_list_new(); - parser.def_nodes = gf_list_new(); - parser.peeked_nodes = gf_list_new(); - parser.is_wrl = force_wrl; - gf_bt_loader_run_intern(&parser, NULL, 1); - gf_list_del(parser.undef_nodes); - gf_list_del(parser.def_nodes); - gf_list_del(parser.peeked_nodes); - while (gf_list_count(parser.def_symbols)) { - BTDefSymbol *d = (BTDefSymbol *)gf_list_get(parser.def_symbols, 0); - gf_list_rem(parser.def_symbols, 0); + GF_SceneLoader *ctx; + GF_BTParser *parser; + GF_SAFEALLOC(ctx, GF_SceneLoader); + if (!ctx) return gf_list_new(); + ctx->scene_graph = in_scene; + GF_SAFEALLOC(parser, GF_BTParser); + if (!parser) { + gf_free(ctx); + return gf_list_new(); + } + parser->line_buffer = node_str; + parser->line_size = (u32) strlen(node_str); + parser->load = ctx; + parser->top_nodes = gf_list_new(); + parser->undef_nodes = gf_list_new(); + parser->def_nodes = gf_list_new(); + parser->peeked_nodes = gf_list_new(); + parser->is_wrl = force_wrl; + gf_bt_loader_run_intern(parser, NULL, 1); + gf_list_del(parser->undef_nodes); + gf_list_del(parser->def_nodes); + gf_list_del(parser->peeked_nodes); + while (gf_list_count(parser->def_symbols)) { + BTDefSymbol *d = (BTDefSymbol *)gf_list_get(parser->def_symbols, 0); + gf_list_rem(parser->def_symbols, 0); gf_free(d->name); gf_free(d->value); gf_free(d); } - gf_list_del(parser.def_symbols); - gf_list_del(parser.scripts); + gf_list_del(parser->def_symbols); + gf_list_del(parser->scripts); + GF_List *result = parser->top_nodes; + gf_free(parser); + gf_free(ctx); - return parser.top_nodes; + return result; } #endif /*GPAC_DISABLE_LOADER_BT*/
View file
gpac-2.4.0.tar.gz/src/scene_manager/loader_svg.c -> gpac-26.02.0.tar.gz/src/scene_manager/loader_svg.c
Changed
@@ -611,8 +611,6 @@ if (parser->command) { gf_assert(parser->command->tag == GF_SG_LSR_NEW_SCENE); parser->command->node = (GF_Node *)root_svg; - } else { - gf_assert(0); } } gf_sg_set_root_node(parser->load->scene_graph, (GF_Node *)root_svg); @@ -833,23 +831,28 @@ we defer the parsing and store them temporarily as strings */ if (anim) { if (!stricmp(att_name, "to")) { - anim->to = gf_strdup(att->value); + if (!anim->to) + anim->to = gf_strdup(att->value); continue; } if (!stricmp(att_name, "from")) { - anim->from = gf_strdup(att->value); + if (!anim->from) + anim->from = gf_strdup(att->value); continue; } if (!stricmp(att_name, "by")) { - anim->by = gf_strdup(att->value); + if (!anim->by) + anim->by = gf_strdup(att->value); continue; } if (!stricmp(att_name, "values")) { - anim->values = gf_strdup(att->value); + if (!anim->values) + anim->values = gf_strdup(att->value); continue; } if ((tag == TAG_SVG_animateTransform) && !stricmp(att_name, "type")) { - anim->type = gf_strdup(att->value); + if (!anim->type) + anim->type = gf_strdup(att->value); continue; } } @@ -860,12 +863,10 @@ if (gf_svg_is_animation_tag(tag)) { /* For xlink:href in animation elements, we try to locate the target of the xlink:href to determine the type of values to be animated */ - if (anim) { + if (anim && !anim->target_id) { anim->target_id = gf_strdup(att->value); /*The target may be NULL, if it has not yet been parsed, we will try to resolve it later on */ anim->target = (SVG_Element *) gf_sg_find_node_by_name(parser->load->scene_graph, anim->target_id + 1); - } else { - gf_assert(0); } continue; } else { @@ -946,17 +947,18 @@ continue; } if (info.fieldType == SVG_ID_datatype) { - /*"when both 'id' and 'xml:id' are specified on the same element but with different values, - the SVGElement::id field must return either of the values but should give precedence to - the 'xml:id' attribute."*/ - if (!node_name || (info.fieldIndex == TAG_XML_ATT_id)) { - node_name = *(SVG_ID *)info.far_ptr; - /* Check if ID start with a digit, which is not a valid ID for a node according to XML (see http://www.w3.org/TR/xml/#id) */ - if (isdigit(node_name0)) { - svg_report(parser, GF_BAD_PARAM, "Invalid value %s for node %s %s", node_name, name, att->name); - node_name = NULL; - } + + /* in this case gf_svg_parse_attribute will have deleted the previous info.far_ptr if it existed + * so we need to overwrite node_name in order to avoid have a use-after-free + */ + + node_name = *(SVG_ID *)info.far_ptr; + /* Check if ID start with a digit, which is not a valid ID for a node according to XML (see http://www.w3.org/TR/xml/#id) */ + if (isdigit(node_name0)) { + svg_report(parser, GF_BAD_PARAM, "Invalid value %s for node %s %s", node_name, name, att->name); + node_name = NULL; } + } else { switch (info.fieldIndex) { case TAG_SVG_ATT_syncMaster: @@ -1771,8 +1773,12 @@ field->field_ptr = &field->new_node; } } else { - gf_assert(parser->command->tag==GF_SG_LSR_NEW_SCENE); - gf_assert(gf_node_get_tag((GF_Node *)elt) == TAG_SVG_svg); + if ((parser->command->tag!=GF_SG_LSR_NEW_SCENE) || (gf_node_get_tag((GF_Node *)elt) != TAG_SVG_svg)) { + gf_list_del_item(parser->node_stack, stack); + gf_free(stack); + gf_node_unregister((GF_Node *)elt, NULL); + return; + } if(!parser->command->node) parser->command->node = (GF_Node *)elt; } @@ -2188,7 +2194,7 @@ } if (e<0) svg_report(parser, e, "Unable to parse chunk: %s", parser ? gf_xml_sax_get_error(parser->sax_parser) : "no parser"); else e = parser->last_error; - + svg_flush_animations(parser); @@ -2248,4 +2254,3 @@ } #endif -
View file
gpac-2.4.0.tar.gz/src/scene_manager/loader_xmt.c -> gpac-26.02.0.tar.gz/src/scene_manager/loader_xmt.c
Changed
@@ -605,6 +605,58 @@ } +static void xmt_remove_link_for_descriptor(GF_XMTParser* parser, GF_Descriptor* desc) { + + u32 i=0; + XMT_ODLink *l, *to_del=NULL; + + if (!desc) return; + + // recursively remove sub descriptors links + if (desc->tag == GF_ODF_IOD_TAG || desc->tag == GF_ODF_OD_TAG) { + GF_Descriptor* subdesc = NULL; + u32 i=0; + while ((subdesc = gf_list_enum(((GF_ObjectDescriptor*)desc)->ESDescriptors, &i))) { + if (subdesc) xmt_remove_link_for_descriptor(parser, subdesc); + } + } + + i=0; + while ((l = (XMT_ODLink*)gf_list_enum(parser->od_links, &i)) ) { + if (l->od && l->od == (GF_ObjectDescriptor*)desc) { + l->od = NULL; + to_del = l; + break; + } + } + + if (to_del) { + gf_list_del_item(parser->od_links, to_del); + if (to_del->desc_name) gf_free(to_del->desc_name); + gf_list_del(to_del->mf_urls); + gf_free(to_del); + } + + + XMT_ESDLink *esdl, *esdl_del=NULL; + i=0; + while ((esdl = (XMT_ESDLink *)gf_list_enum(parser->esd_links, &i))) { + if (esdl->esd && esdl->esd == (GF_ESD*)desc) { + esdl->esd = NULL; + esdl_del = esdl; + break; + } + } + + if (esdl_del) { + gf_list_del_item(parser->esd_links, esdl_del); + if (esdl_del->desc_name) gf_free(esdl_del->desc_name); + gf_free(esdl_del); + } + +} + + static u32 xmt_get_next_node_id(GF_XMTParser *parser) { u32 ID; @@ -1996,6 +2048,7 @@ e = gf_odf_desc_add_desc(parent, desc); if (e) { xmt_report(parser, GF_OK, "Invalid child descriptor"); + xmt_remove_link_for_descriptor(parser, desc); gf_odf_desc_del(desc); return NULL; } @@ -2667,12 +2720,15 @@ gf_list_rem_last(parser->descriptors); if (gf_list_count(parser->descriptors)) return; - if ((parser->doc_type==1) && (parser->state==XMT_STATE_HEAD) && parser->load->ctx && !parser->load->ctx->root_od) { + if ((parser->doc_type==1) && (parser->state==XMT_STATE_HEAD) && parser->load->ctx && !parser->load->ctx->root_od && (desc->tag == GF_ODF_IOD_TAG || desc->tag == GF_ODF_OD_TAG) ) { parser->load->ctx->root_od = (GF_ObjectDescriptor *)desc; } else if (!parser->od_command) { xmt_report(parser, GF_OK, "Warning: descriptor %s defined outside scene scope - skipping", name); + + xmt_remove_link_for_descriptor(parser, desc); gf_odf_desc_del(desc); + } else { switch (parser->od_command->tag) { case GF_ODF_ESD_UPDATE_TAG: @@ -2851,11 +2907,13 @@ attach_node: top = (XMTNodeStack*)gf_list_last(parser->nodes); + Bool node_processed = GF_FALSE; /*add node to command*/ if (!top || (top->container_field.fieldType==GF_SG_VRML_SFCOMMANDBUFFER)) { if (parser->doc_type == 1) { GF_CommandField *inf; Bool single_node = 0; + node_processed = GF_TRUE; if (!parser->command) { gf_assert(0); return; @@ -2962,6 +3020,7 @@ || (tag==TAG_X3D_Script) #endif ) { + node_processed = GF_TRUE; /*it may happen that the script uses itself as a field (not sure this is compliant since this implies a cyclic structure, but happens in some X3D conformance seq)*/ if (!top || (top->node != node)) { @@ -2979,6 +3038,11 @@ } } } + if (!node_processed) { + gf_node_register(node, NULL); + gf_node_unregister(node, NULL); + node_processed = GF_TRUE; + } } else if (parser->current_node_tag==tag) { gf_list_rem_last(parser->nodes); gf_free(top);
View file
gpac-2.4.0.tar.gz/src/scene_manager/scene_dump.c -> gpac-26.02.0.tar.gz/src/scene_manager/scene_dump.c
Changed
@@ -157,6 +157,7 @@ if (!is_final_name) strcat(tmp->filename, ext_name); tmp->trace = gf_fopen(tmp->filename, "wt"); if (!tmp->trace) { + gf_free(tmp->filename); gf_free(tmp); return NULL; } @@ -425,7 +426,7 @@ u32 len, i; u16 *uniLine; if (!str) return; - len = (u32) strlen(str); + len = 1 + (u32) strlen(str); if (!len) return; uniLine = (u16*)gf_malloc(sizeof(u16) * len*4); len = gf_utf8_mbstowcs(uniLine, len, (const char **) &str); @@ -595,10 +596,8 @@ gf_fprintf(sdump->trace, "\"%s\"", str); } else { - u16 *uniLine; - - uniLine = (u16*)gf_malloc(sizeof(short) * (len + 1)); - len = gf_utf8_mbstowcs(uniLine, len, (const char **)&str); + u16 *uniLine = (u16*)gf_malloc(sizeof(u16) * ((len/2)*2 + 2)); + len = gf_utf8_mbstowcs(uniLine, len+1, (const char **)&str); if (len != GF_UTF8_FAIL) { for (i = 0; i<len; i++) { @@ -2785,7 +2784,7 @@ ) { gf_fprintf(sdump->trace, " pointvalue=\"%g %g\"", FIX2FLT(com->send_event_x), FIX2FLT(com->send_event_y) ); } - + switch (com->send_event_name) { case GF_EVENT_KEYDOWN: case GF_EVENT_LONGKEYPRESS: @@ -3437,11 +3436,15 @@ GF_AUContext *au; Bool no_root_found = 1; - sample_list = gf_list_new(); - num_scene = num_od = 0; indent = 0; dumper = gf_sm_dumper_new(ctx->scene_graph, rad_name, is_final_name, ' ', dump_mode); + if (!dumper) { + return GF_IO_ERR; + } + + sample_list = gf_list_new(); + e = GF_OK; /*configure all systems streams we're dumping*/ i=0;
View file
gpac-2.4.0.tar.gz/src/scene_manager/scene_engine.c -> gpac-26.02.0.tar.gz/src/scene_manager/scene_engine.c
Changed
@@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Jean Le Feuvre, Cyril Concolato - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -290,8 +290,6 @@ GF_SceneDumper *dumper = NULL; #endif GF_Err e; - char rad_name4096; - char file_name4096+100; u32 fsize; u8 *buffer = NULL; GF_BitStream *bs = NULL; @@ -302,23 +300,37 @@ u32 buffer_len; const char *cache_dir; char *dump_name; + char *base_name = NULL; + char p_sep2; if (!data) return GF_BAD_PARAM; if (!seng->dump_path) cache_dir = gf_get_default_cache_directory(); else cache_dir = seng->dump_path; + p_sep0 = GF_PATH_SEPARATOR; + p_sep1 = 0; + dump_name = "gpac_scene_engine_dump"; #ifdef DUMP_DIMS_LOG_WITH_TIME start: #endif + if (base_name) { + gf_free(base_name); + base_name=NULL; + } + if (commands && gf_list_count(commands)) { - sprintf(rad_name, "%s%c%s%s", cache_dir, GF_PATH_SEPARATOR, dump_name, "_update"); + gf_dynstrcat(&base_name, cache_dir, NULL); + gf_dynstrcat(&base_name, dump_name, p_sep); + gf_dynstrcat(&base_name, "_update", NULL); } else { #ifndef DUMP_DIMS_LOG_WITH_TIME - sprintf(rad_name, "%s%c%s%s", cache_dir, GF_PATH_SEPARATOR, "rap_", dump_name); + gf_dynstrcat(&base_name, cache_dir, NULL); + gf_dynstrcat(&base_name, "rap_", p_sep); + gf_dynstrcat(&base_name, dump_name, NULL); #else char date_str100, time_str100; time_t now; @@ -327,14 +339,18 @@ tm_tot = localtime(&now); strftime(date_str, 100, "%Yy%mm%dd", tm_tot); strftime(time_str, 100, "%Hh%Mm%Ss", tm_tot); - sprintf(rad_name, "%s%c%s-%s-%s%s", cache_dir, GF_PATH_SEPARATOR, date_str, time_str, "rap_", dump_name); + //format as cache_dir / date_str - time_str -rap_ dump_name); + gf_dynstrcat(&base_name, cache_dir, NULL); + gf_dynstrcat(&base_name, date_str, p_sep); + gf_dynstrcat(&base_name, time_str, "-"); + gf_dynstrcat(&base_name, dump_name, "-rap_"); #endif } #ifndef GPAC_DISABLE_SCENE_DUMP - dumper = gf_sm_dumper_new(seng->ctx->scene_graph, rad_name, GF_FALSE, ' ', GF_SM_DUMP_SVG); + dumper = gf_sm_dumper_new(seng->ctx->scene_graph, base_name, GF_FALSE, ' ', GF_SM_DUMP_SVG); if (!dumper) { - GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("SceneEngine Cannot create SVG dumper for %s.svg\n", rad_name)); + GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("SceneEngine Cannot create SVG dumper for %s.svg\n", base_name)); e = GF_IO_ERR; goto exit; } @@ -350,12 +366,17 @@ #if 0 //unused if(seng->dump_rap) { GF_SceneDumper *dumper = NULL; + if (base_name) { + gf_free(base_name); + base_name = NULL; + } + gf_dynstrcat(&base_name, cache_dir, NULL); + gf_dynstrcat(&base_name, "rap_", p_sep); + gf_dynstrcat(&base_name, dump_name, NULL); - sprintf(rad_name, "%s%c%s%s", cache_dir, GF_PATH_SEPARATOR, "rap_", dump_name); - - dumper = gf_sm_dumper_new(seng->ctx->scene_graph, rad_name, GF_FALSE, ' ', GF_SM_DUMP_SVG); + dumper = gf_sm_dumper_new(seng->ctx->scene_graph, base_name, GF_FALSE, ' ', GF_SM_DUMP_SVG); if (!dumper) { - GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("SceneEngine Cannot create SVG dumper for %s.svg\n", rad_name)); + GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("SceneEngine Cannot create SVG dumper for %s.svg\n", base_name)); e = GF_IO_ERR; goto exit; } @@ -377,11 +398,11 @@ } #endif - sprintf(file_name, "%s.svg", rad_name); + gf_dynstrcat(&base_name, ".svg", NULL); - e = gf_file_load_data(file_name, (u8 **) &buffer, &fsize); + e = gf_file_load_data(base_name, (u8 **) &buffer, &fsize); if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("SceneEngine Error loading SVG dump file %s\n", file_name)); + GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("SceneEngine Error loading SVG dump file %s\n", base_name)); goto exit; } @@ -440,6 +461,7 @@ gf_bs_del(bs); exit: + if (base_name) gf_free(base_name); if (buffer) gf_free(buffer); return e; } @@ -649,7 +671,7 @@ /* We need to create an empty AU for the parser to correctly parse a LASeR Command without SceneUnit */ sc = gf_list_get(seng->ctx->streams, 0); - if (sc->codec_id == GF_CODECID_DIMS) { + if (sc && sc->codec_id == GF_CODECID_DIMS) { gf_seng_create_new_au(sc, 0); } @@ -1136,4 +1158,3 @@ #endif /*GPAC_DISABLE_SENG*/ -
View file
gpac-2.4.0.tar.gz/src/scene_manager/scene_stats.c -> gpac-26.02.0.tar.gz/src/scene_manager/scene_stats.c
Changed
@@ -176,7 +176,7 @@ static void StatSVGPoint(GF_SceneStatistics *stat, SFVec2f *val) { - if (!stat) return; + if (!stat || !val) return; if (stat->max_2d.x < val->x) stat->max_2d.x = val->x; if (stat->max_2d.y < val->y) stat->max_2d.y = val->y; if (stat->min_2d.x > val->x) stat->min_2d.x = val->x; @@ -188,7 +188,7 @@ static void StatSFVec2f(GF_SceneStatistics *stat, SFVec2f *val) { - if (!stat) return; + if (!stat || !val) return; if (stat->max_2d.x < val->x) stat->max_2d.x = val->x; if (stat->max_2d.y < val->y) stat->max_2d.y = val->y; if (stat->min_2d.x > val->x) stat->min_2d.x = val->x; @@ -197,7 +197,7 @@ static void StatSFVec3f(GF_SceneStatistics *stat, SFVec3f *val) { - if (!stat) return; + if (!stat || !val) return; if (stat->max_3d.x < val->x) stat->max_3d.x = val->x; if (stat->max_3d.y < val->y) stat->max_3d.y = val->y; if (stat->max_3d.z < val->z) stat->max_3d.z = val->y; @@ -695,4 +695,3 @@ } #endif /*GPAC_DISABLE_SCENE_STATS*/ -
View file
gpac-2.4.0.tar.gz/src/scene_manager/swf_parse.c -> gpac-26.02.0.tar.gz/src/scene_manager/swf_parse.c
Changed
@@ -108,19 +108,36 @@ if (dst_size < 8) { return; } + //we use 500MB as max size + if (dst_size > 0x1FFFFFFF) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("SWF Parsing Decompressed size too big %u, max 500M\n", dst_size)); + gf_bs_del(read->bs); + read->bs = NULL; + return; + } src = gf_malloc(sizeof(char)*size); dst = gf_malloc(sizeof(char)*dst_size); memset(dst, 0, sizeof(char)*8); gf_bs_read_data(read->bs, src, size); dst_size -= 8; destLen = (uLongf)dst_size; - uncompress((Bytef *) dst+8, &destLen, (Bytef *) src, size); - dst_size += 8; - gf_free(src); - read->mem = dst; - gf_bs_del(read->bs); - read->bs = gf_bs_new(read->mem, dst_size, GF_BITSTREAM_READ); - gf_bs_skip_bytes(read->bs, 8); + int uncompress_res = uncompress((Bytef *) dst+8, &destLen, (Bytef *) src, size) ; + if ( uncompress_res==Z_OK ) { + dst_size += 8; + gf_free(src); + read->mem = dst; + gf_bs_del(read->bs); + read->bs = gf_bs_new(read->mem, dst_size, GF_BITSTREAM_READ); + gf_bs_skip_bytes(read->bs, 8); + } + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("SWF Parsing Fail to uncompress data (%s)\n", zError(uncompress_res))); + gf_free(src); + gf_free(dst); + gf_bs_del(read->bs); + read->mem = NULL; + read->bs = NULL; // signal error to caller since we return void + } } @@ -1818,11 +1835,12 @@ char *frame; GF_Err e=GF_OK; - sprintf(szName, "swf_sound_%d.mp3", snd->ID); + snprintf(szName, GF_ARRAY_LENGTH(szName), "swf_sound_%d.mp3", snd->ID); if (read->localPath) { snd->szFileName = (char*)gf_malloc(sizeof(char)*GF_MAX_PATH); - strcpy(snd->szFileName, read->localPath); - strcat(snd->szFileName, szName); + strncpy(snd->szFileName, read->localPath, GF_MAX_PATH-1); + snd->szFileNameGF_MAX_PATH-1 = 0; + strncat(snd->szFileName, szName, GF_MAX_PATH-1); } else { snd->szFileName = gf_strdup(szName); } @@ -1838,14 +1856,18 @@ while (tot_size<read->size) { u32 toread = read->size - tot_size; if (toread>alloc_size) toread = alloc_size; - swf_read_data(read, frame, toread); - if (gf_fwrite(frame, sizeof(char)*toread, snd->output) != toread) + if (swf_read_data(read, frame, toread) != toread) { e = GF_IO_ERR; + } else { + if (gf_fwrite(frame, sizeof(char)*toread, snd->output) != toread) + e = GF_IO_ERR; + } tot_size += toread; } gf_free(frame); if (e) { + if (snd->szFileName) gf_free(snd->szFileName); gf_free(snd); return e; } @@ -1966,14 +1988,14 @@ case 2: read->sound_stream = snd; if (read->localPath) { - sprintf(szName, "%s/swf_soundstream_%d.mp3", read->localPath, read->current_sprite_id); + snprintf(szName, GF_ARRAY_LENGTH(szName), "%s/swf_soundstream_%d.mp3", read->localPath, read->current_sprite_id); } else { - sprintf(szName, "swf_soundstream_%d.mp3", read->current_sprite_id); + snprintf(szName, GF_ARRAY_LENGTH(szName), "swf_soundstream_%d.mp3", read->current_sprite_id); } read->sound_stream->szFileName = gf_strdup(szName); read->setup_sound(read, read->sound_stream, 0); break; - case 3: + default: swf_report(read, GF_NOT_SUPPORTED, "Unrecognized sound format"); gf_free(snd); break; @@ -2025,6 +2047,10 @@ bytes3 = swf_read_int(read, 8); hdr = GF_4CC(bytes0, bytes1, bytes2, bytes3); size = gf_mp3_frame_size(hdr); + if (!size || size < 4 || tot_size >= read->size) { + e = GF_ISOM_INVALID_MEDIA; + break; + } if (alloc_size<size-4) { frame = (char*)gf_realloc(frame, sizeof(char)*(size-4)); alloc_size = size-4; @@ -2032,9 +2058,16 @@ /*watchout for truncated framesif */ if (tot_size + size >= read->size) size = read->size - tot_size; - swf_read_data(read, frame, size-4); - if (gf_fwrite(bytes, sizeof(char)*4, read->sound_stream->output)!=4) e = GF_IO_ERR; - if (gf_fwrite(frame, sizeof(char)*(size-4), read->sound_stream->output) != size-4) e = GF_IO_ERR; + if (size > 4) { + u32 size_read = swf_read_data(read, frame, size-4); + + if (size_read == size-4) { + if (gf_fwrite(bytes, sizeof(char)*4, read->sound_stream->output)!=4) e = GF_IO_ERR; + if (gf_fwrite(frame, sizeof(char)*(size-4), read->sound_stream->output) != size-4) e = GF_IO_ERR; + } + else + e = GF_IO_ERR; + } if (tot_size + size >= read->size) break; tot_size += size; } @@ -2085,13 +2118,16 @@ /*dump file*/ if (read->localPath) { - sprintf(szName, "%s/swf_jpeg_%d.jpg", read->localPath, ID); + snprintf(szName, GF_ARRAY_LENGTH(szName), "%s/swf_jpeg_%d.jpg", read->localPath, ID); } else { - sprintf(szName, "swf_jpeg_%d.jpg", ID); + snprintf(szName, GF_ARRAY_LENGTH(szName), "swf_jpeg_%d.jpg", ID); } - if (version!=3) + if (version!=3) { file = gf_fopen(szName, "wb"); + if (!file) + return GF_IO_ERR; + } if (version==1 && read->jpeg_hdr_size >= 2) { /*remove JPEG EOI*/ @@ -2103,27 +2139,31 @@ } buf = gf_malloc(sizeof(u8)*size); if (!buf) return GF_OUT_OF_MEM; - swf_read_data(read, (char *) buf, size); - if (version==1) { - if (gf_fwrite(buf, size, file)!=size) - e = GF_IO_ERR; - } else { - u32 i; - for (i=0; i<size; i++) { - if ((i+4<size) - && (bufi==0xFF) && (bufi+1==0xD9) - && (bufi+2==0xFF) && (bufi+3==0xD8) - ) { - memmove(buf+i, buf+i+4, sizeof(char)*(size-i-4)); - size -= 4; - break; + + if (swf_read_data(read, (char *) buf, size) != size) + e = GF_IO_ERR; + else { + if (version==1) { + if (gf_fwrite(buf, size, file)!=size) + e = GF_IO_ERR; + } else { + u32 i; + for (i=0; i<size; i++) { + if ((i+4<size) + && (bufi==0xFF) && (bufi+1==0xD9) + && (bufi+2==0xFF) && (bufi+3==0xD8) + ) { + memmove(buf+i, buf+i+4, sizeof(char)*(size-i-4)); + size -= 4; + break; + } + } + if ((size>3) && (buf0==0xFF) && (buf1==0xD8) && (buf2==0xFF) && (buf3==0xD8)) { + skip = 2; + } + if (version==2) { + if (gf_fwrite(buf+skip, size-skip, file) != size-skip) e = GF_IO_ERR; } - } - if ((buf0==0xFF) && (buf1==0xD8) && (buf2==0xFF) && (buf3==0xD8)) { - skip = 2; - } - if (version==2) { - if (gf_fwrite(buf+skip, size-skip, file) != size-skip) e = GF_IO_ERR; } } if (version!=3) @@ -2161,33 +2201,35 @@ /*read alpha map and decompress it*/ if (size<AlphaPlaneSize) buf = gf_realloc(buf, sizeof(u8)*AlphaPlaneSize); - swf_read_data(read, (char *) buf, AlphaPlaneSize); - - osize = w*h; - dst = gf_malloc(sizeof(char)*osize); - destLen = (uLongf)osize; - uncompress((Bytef *) dst, &destLen, buf, AlphaPlaneSize); - /*write alpha channel*/ - for (j=0; j<(u32)destLen; j++) { - raw4*j + 3 = dstj; - } - gf_free(dst); - /*write png*/ - if (read->localPath) { - sprintf(szName, "%s/swf_png_%d.png", read->localPath, ID); - } else { - sprintf(szName, "swf_png_%d.png", ID); - } + if (swf_read_data(read, (char *) buf, AlphaPlaneSize) == AlphaPlaneSize) { - osize = w*h*4; - buf = gf_realloc(buf, sizeof(char)*osize); - gf_img_png_enc(raw, w, h, w*4, GF_PIXEL_RGBA, (char *)buf, &osize); + osize = w*h; + dst = gf_malloc(sizeof(char)*osize); + destLen = (uLongf)osize; + uncompress((Bytef *) dst, &destLen, buf, AlphaPlaneSize); + /*write alpha channel*/ + for (j=0; j<(u32)destLen; j++) { + raw4*j + 3 = dstj; + } + gf_free(dst); - file = gf_fopen(szName, "wb"); - if (gf_fwrite(buf, osize, file)!=osize) e = GF_IO_ERR; - gf_fclose(file); + /*write png*/ + if (read->localPath) { + snprintf(szName, GF_ARRAY_LENGTH(szName), "%s/swf_png_%d.png", read->localPath, ID); + } else { + snprintf(szName, GF_ARRAY_LENGTH(szName), "swf_png_%d.png", ID); + } + osize = w*h*4; + buf = gf_realloc(buf, sizeof(char)*osize); + gf_img_png_enc(raw, w, h, w*4, GF_PIXEL_RGBA, (char *)buf, &osize); + + file = gf_fopen(szName, "wb"); + if (gf_fwrite(buf, osize, file)!=osize) e = GF_IO_ERR; + gf_fclose(file); + + } gf_free(raw); #endif //GPAC_DISABLE_AV_PARSERS } @@ -2703,9 +2745,9 @@ if (load->svgOutFile) { char svgFileNameGF_MAX_PATH; if (load->localPath) { - sprintf(svgFileName, "%s%c%s.svg", load->localPath, GF_PATH_SEPARATOR, load->svgOutFile); + snprintf(svgFileName, GF_ARRAY_LENGTH(svgFileName), "%s%c%s.svg", load->localPath, GF_PATH_SEPARATOR, load->svgOutFile); } else { - sprintf(svgFileName, "%s.svg", load->svgOutFile); + snprintf(svgFileName, GF_ARRAY_LENGTH(svgFileName), "%s.svg", load->svgOutFile); } svgFile = gf_fopen(svgFileName, "wt"); if (!svgFile) return GF_BAD_PARAM;
View file
gpac-2.4.0.tar.gz/src/scene_manager/swf_svg.c -> gpac-26.02.0.tar.gz/src/scene_manager/swf_svg.c
Changed
@@ -457,9 +457,14 @@ swf_svg_print(read, "</g>\n"); } - read->add_sample(read->user, read->svg_data, read->svg_data_size, read->current_frame*1000/read->frame_rate, (read->current_frame == 0)); - gf_free(read->svg_data); - read->svg_data = NULL; + + if (read->svg_data && read->svg_data_size) + read->add_sample(read->user, read->svg_data, read->svg_data_size, read->current_frame*1000/read->frame_rate, (read->current_frame == 0)); + + if (read->svg_data) { + gf_free(read->svg_data); + read->svg_data = NULL; + } read->svg_data_size = 0; read->empty_frame = GF_TRUE;
View file
gpac-2.4.0.tar.gz/src/scene_manager/text_to_bifs.c -> gpac-26.02.0.tar.gz/src/scene_manager/text_to_bifs.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2026 * All rights reserved * * This file is part of GPAC / Scene Management sub-project @@ -47,7 +47,7 @@ if (strchr(__sep, __str_len)) __str_len = 0; \ else break; \ } \ - + static GF_Err gf_text_guess_format(char *filename, u32 *fmt) { @@ -170,55 +170,61 @@ if (sOK) REM_TRAIL_MARKS(szLine, "\r\n\t ") - if (!sOK || !strlen(szLine)) { - state = 0; - if (au) { - /*if italic or underscore do it*/ - if (font && (italic || underlined || bold)) { - com = gf_sg_command_new(ctx->scene_graph, GF_SG_FIELD_REPLACE); - com->node = font; - gf_node_register(font, NULL); - inf = gf_sg_command_field_new(com); - inf->fieldIndex = style.fieldIndex; - inf->fieldType = style.fieldType; - inf->field_ptr = gf_sg_vrml_field_pointer_new(style.fieldType); - sfstr = (SFString *)inf->field_ptr; - if (bold && italic && underlined) sfstr->buffer = gf_strdup("BOLDITALIC UNDERLINED"); - else if (italic && underlined) sfstr->buffer = gf_strdup("ITALIC UNDERLINED"); - else if (bold && underlined) sfstr->buffer = gf_strdup("BOLD UNDERLINED"); - else if (underlined) sfstr->buffer = gf_strdup("UNDERLINED"); - else if (bold && italic) sfstr->buffer = gf_strdup("BOLDITALIC"); - else if (bold) sfstr->buffer = gf_strdup("BOLD"); - else sfstr->buffer = gf_strdup("ITALIC"); - gf_list_add(au->commands, com); - } + if (!sOK || !strlen(szLine)) { + state = 0; + if (au) { + /*if italic or underscore do it*/ + if (font && (italic || underlined || bold)) { + com = gf_sg_command_new(ctx->scene_graph, GF_SG_FIELD_REPLACE); + com->node = font; + gf_node_register(font, NULL); + inf = gf_sg_command_field_new(com); + inf->fieldIndex = style.fieldIndex; + inf->fieldType = style.fieldType; + inf->field_ptr = gf_sg_vrml_field_pointer_new(style.fieldType); + sfstr = (SFString *)inf->field_ptr; + if (bold && italic && underlined) sfstr->buffer = gf_strdup("BOLDITALIC UNDERLINED"); + else if (italic && underlined) sfstr->buffer = gf_strdup("ITALIC UNDERLINED"); + else if (bold && underlined) sfstr->buffer = gf_strdup("BOLD UNDERLINED"); + else if (underlined) sfstr->buffer = gf_strdup("UNDERLINED"); + else if (bold && italic) sfstr->buffer = gf_strdup("BOLDITALIC"); + else if (bold) sfstr->buffer = gf_strdup("BOLD"); + else sfstr->buffer = gf_strdup("ITALIC"); + gf_list_add(au->commands, com); + } - au = gf_sm_stream_au_new(srt, end, 0, 1); + au = gf_sm_stream_au_new(srt, end, 0, 1); + com = gf_sg_command_new(ctx->scene_graph, GF_SG_FIELD_REPLACE); + com->node = text; + gf_node_register(text, NULL); + inf = gf_sg_command_field_new(com); + inf->fieldIndex = string.fieldIndex; + inf->fieldType = string.fieldType; + inf->field_ptr = gf_sg_vrml_field_pointer_new(string.fieldType); + gf_list_add(au->commands, com); + /*reset font styles so that all AUs are true random access*/ + if (font) { com = gf_sg_command_new(ctx->scene_graph, GF_SG_FIELD_REPLACE); - com->node = text; - gf_node_register(text, NULL); + com->node = font; + gf_node_register(font, NULL); inf = gf_sg_command_field_new(com); - inf->fieldIndex = string.fieldIndex; - inf->fieldType = string.fieldType; - inf->field_ptr = gf_sg_vrml_field_pointer_new(string.fieldType); + inf->fieldIndex = style.fieldIndex; + inf->fieldType = style.fieldType; + inf->field_ptr = gf_sg_vrml_field_pointer_new(style.fieldType); gf_list_add(au->commands, com); - /*reset font styles so that all AUs are true random access*/ - if (font) { - com = gf_sg_command_new(ctx->scene_graph, GF_SG_FIELD_REPLACE); - com->node = font; - gf_node_register(font, NULL); - inf = gf_sg_command_field_new(com); - inf->fieldIndex = style.fieldIndex; - inf->fieldType = style.fieldType; - inf->field_ptr = gf_sg_vrml_field_pointer_new(style.fieldType); - gf_list_add(au->commands, com); - } - au = NULL; } - inf = NULL; - if (!sOK) break; - continue; + au = NULL; } + inf = NULL; + if (!sOK) break; + continue; + } + + if (!gf_utf8_is_legal(sOK, (u32)strlen(szLine))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("srt->bifs Illegal UTF8 data\n")); + e = GF_CORRUPTED_DATA; + goto exit; + } switch (state) { case 0: @@ -309,6 +315,7 @@ len = 0; for (i=0; i<strlen(ptr); i++) { /*FIXME - UTF16 support !!*/ + if (len >= GF_ARRAY_LENGTH(szText)) break; if (ptri & 0x80) { /*non UTF8 (likely some win-CP)*/ if ((ptri+1 & 0xc0) != 0x80) { @@ -323,10 +330,11 @@ i++; } } + if (len >= GF_ARRAY_LENGTH(szText)) break; szTextlen = ptri; len++; } - szTextlen = 0; + szTextMIN(len, GF_ARRAY_LENGTH(szText)-1) = 0; sfstr->buffer = gf_strdup(szText); break; } @@ -437,10 +445,16 @@ break; } while (szLinei+1 && szLinei+1!='}') { + if (i>=GF_ARRAY_LENGTH(szTime)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("sub->bifs Bad frame (line %d): expected \"}\" before %d chars after \"{\"\n", line, GF_ARRAY_LENGTH(szTime))); + e = GF_NON_COMPLIANT_BITSTREAM; + szTime0 = 0; + goto exit; + } szTimei = szLinei+1; i++; } - szTimei = 0; + szTimeMIN(i, GF_ARRAY_LENGTH(szTime)-1) = 0; start = atoi(szTime); if (start<end) { GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("sub->bifs corrupted SUB frame (line %d) - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps\n", line, start, end)); @@ -454,10 +468,16 @@ break; } while (szLinei+1+j && szLinei+1+j!='}') { + if (i>=GF_ARRAY_LENGTH(szTime)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("sub->bifs Bad frame (line %d): expected \"}\" before %d chars after \"{\"\n", line, GF_ARRAY_LENGTH(szTime))); + e = GF_NON_COMPLIANT_BITSTREAM; + szTime0 = 0; + goto exit; + } szTimei = szLinei+1+j; i++; } - szTimei = 0; + szTimeMIN(i, GF_ARRAY_LENGTH(szTime)-1) = 0; end = atoi(szTime); j+=i+2; @@ -517,6 +537,7 @@ sfstr->buffer = gf_strdup(szText); } } +exit: gf_fclose(sub_in); return e; } @@ -539,4 +560,3 @@ return GF_NOT_SUPPORTED; #endif } -
View file
gpac-2.4.0.tar.gz/src/scenegraph/base_scenegraph.c -> gpac-26.02.0.tar.gz/src/scenegraph/base_scenegraph.c
Changed
@@ -91,7 +91,7 @@ gf_node_parent_of(NULL, NULL); gf_sg_get_parent(tmp); gf_node_get_attribute_count(NULL); - gf_xml_node_clone(NULL, NULL, NULL, NULL, 0); + gf_sg_xml_node_clone(NULL, NULL, NULL, NULL, 0); gf_dom_flatten_textContent(NULL); gf_smil_timing_pause(NULL); gf_smil_timing_resume(NULL); @@ -629,7 +629,7 @@ { NodeIDedItem *reg_node = sg->id_node; if (!reg_node) return; - + if (reg_node->node==node) { sg->id_node = reg_node->next; if (sg->id_node_last==reg_node) @@ -1783,6 +1783,8 @@ gf_assert(pSG); /*no user-defined init, consider the scenegraph is only used for parsing/encoding/decoding*/ if (!pSG->NodeCallback) return; + //some broken content may trigger twice an update + if (node->sgprivate->UserPrivate) return; /*internal nodes*/ #ifndef GPAC_DISABLE_VRML @@ -1944,7 +1946,7 @@ if (ns == full->ns) return full->name; xmlns = (char *) gf_sg_get_namespace_qname(node->sgprivate->scenegraph, full->ns); if (!xmlns) return full->name; - sprintf(node->sgprivate->scenegraph->szNameBuffer, "%s:%s", xmlns, full->name); + snprintf(node->sgprivate->scenegraph->szNameBuffer, 99, "%s:%s", xmlns, full->name); return node->sgprivate->scenegraph->szNameBuffer; } #ifndef GPAC_DISABLE_SVG @@ -2083,7 +2085,7 @@ { s32 res = -1; - if (node->sgprivate->tag==TAG_UndefinedNode) return GF_BAD_PARAM; + if (!node || !node->sgprivate || node->sgprivate->tag==TAG_UndefinedNode) return GF_BAD_PARAM; #ifndef GPAC_DISABLE_VRML else if (node->sgprivate->tag == TAG_ProtoNode) { res = gf_sg_proto_get_field_index_by_name(NULL, node, name); @@ -2220,7 +2222,7 @@ return NULL; } else { #ifndef GPAC_DISABLE_SVG - return gf_xml_node_clone(inScene, orig, cloned_parent, id, deep); + return gf_sg_xml_node_clone(inScene, orig, cloned_parent, id, deep); #endif } return NULL; @@ -2251,7 +2253,7 @@ GF_SAFEALLOC(ns, GF_XMLNS); if (!ns) return GF_OUT_OF_MEM; - + ns->xmlns_id = id ? id : gf_crc_32(name, (u32) strlen(name)); ns->name = gf_strdup(name);
View file
gpac-2.4.0.tar.gz/src/scenegraph/commands.c -> gpac-26.02.0.tar.gz/src/scenegraph/commands.c
Changed
@@ -729,6 +729,8 @@ /*attribute modif*/ else if (inf->field_ptr) { GF_FieldInfo a, b; + memset(&a, 0, sizeof(GF_FieldInfo)); + memset(&b, 0, sizeof(GF_FieldInfo)); if (inf->fieldIndex==(u32) -2) { GF_Point2D scale, translate; Fixed rotate; @@ -817,6 +819,8 @@ } } else if (com->fromNodeID) { GF_FieldInfo a, b; + memset(&a, 0, sizeof(GF_FieldInfo)); + memset(&b, 0, sizeof(GF_FieldInfo)); GF_Node *fromNode = gf_sg_find_node(graph, com->fromNodeID); if (!fromNode) return GF_NON_COMPLIANT_BITSTREAM; if (gf_node_get_field(fromNode, com->fromFieldIndex, &b) != GF_OK) return GF_NON_COMPLIANT_BITSTREAM;
View file
gpac-2.4.0.tar.gz/src/scenegraph/dom_events.c -> gpac-26.02.0.tar.gz/src/scenegraph/dom_events.c
Changed
@@ -173,7 +173,8 @@ if (!event_target) return GF_BAD_PARAM; if (event_target->ptr_type == GF_DOM_EVENT_TARGET_NODE) { GF_Node *node = (GF_Node *)event_target->ptr; - node->sgprivate->UserPrivate = NULL; + if (node && node == listener) + node->sgprivate->UserPrivate = NULL; } gf_list_del_item(event_target->listeners, listener); @@ -846,7 +847,7 @@ GF_DOMText *text; GF_SAFEALLOC(text, GF_DOMText); if (!text) return NULL; - + gf_node_setup((GF_Node *)text, TAG_DOMText); text->sgprivate->scenegraph = sg; return text; @@ -903,7 +904,7 @@ GF_DOMUpdates *update; GF_SAFEALLOC(update, GF_DOMUpdates); if (!update) return NULL; - + gf_node_setup((GF_Node *)update, TAG_DOMUpdates); update->sgprivate->scenegraph = parent->sgprivate->scenegraph; update->updates = gf_list_new(); @@ -988,4 +989,3 @@ #endif //GPAC_DISABLE_SVG -
View file
gpac-2.4.0.tar.gz/src/scenegraph/dom_js.c -> gpac-26.02.0.tar.gz/src/scenegraph/dom_js.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2007-2023 + * Copyright (c) Telecom ParisTech 2007-2025 * All rights reserved * * This file is part of GPAC / Scene Graph sub-project @@ -258,14 +258,16 @@ GF_Node *dom_get_node(JSValue obj) { - GF_Node *n = (GF_Node *) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID); if (n && n->sgprivate) return n; return NULL; } GF_Node *dom_get_element(JSContext *c, JSValue obj) { - GF_Node *n = (GF_Node *) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID); if (!n || !n->sgprivate) return NULL; if (n->sgprivate->tag==TAG_DOMText) return NULL; return n; @@ -273,7 +275,8 @@ GF_SceneGraph *dom_get_doc(JSContext *c, JSValue obj) { - GF_SceneGraph *sg = (GF_SceneGraph *) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_SceneGraph *sg = (GF_SceneGraph *) JS_GetAnyOpaque(obj, &_classID); if (sg && !sg->__reserved_null) return sg; return NULL; } @@ -907,7 +910,8 @@ /*dom3 node*/ static void dom_node_finalize(JSRuntime *rt, JSValue obj) { - GF_Node *n = (GF_Node *) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID); /*the JS proto of the svgClass or a destroyed object*/ if (!n) return; if (!n->sgprivate) return; @@ -1442,7 +1446,8 @@ void dom_document_finalize(JSRuntime *rt, JSValue obj) { GF_SceneGraph *sg; - sg = (GF_SceneGraph*) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + sg = (GF_SceneGraph*) JS_GetAnyOpaque(obj, &_classID); /*the JS proto of the svgClass or a destroyed object*/ if (!sg) return; @@ -2690,7 +2695,8 @@ void domDocument_gc_mark(JSRuntime *rt, JSValueConst obj, JS_MarkFunc *mark_func) { GF_SceneGraph *sg; - sg = (GF_SceneGraph*) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + sg = (GF_SceneGraph*) JS_GetAnyOpaque(obj, &_classID); /*the JS proto of the svgClass or a destroyed object*/ if (!sg || !sg->js_data) return; if (!JS_IsUndefined(sg->js_data->document)) @@ -2698,7 +2704,8 @@ } void domElement_gc_mark(JSRuntime *rt, JSValueConst obj, JS_MarkFunc *mark_func) { - GF_Node *n = (GF_Node *) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID); /*the JS proto of the svgClass or a destroyed object*/ if (!n) return; if (!n->sgprivate || !n->sgprivate->interact || !n->sgprivate->interact->js_binding) return;
View file
gpac-2.4.0.tar.gz/src/scenegraph/qjs_common.h -> gpac-26.02.0.tar.gz/src/scenegraph/qjs_common.h
Changed
@@ -243,7 +243,7 @@ void qjs_init_all_modules(JSContext *ctx, Bool no_webgl, Bool for_vrml); -Bool gs_js_context_is_valid(JSContext *ctx); +Bool gf_js_context_is_valid(JSContext *ctx); JSRuntime *gf_js_get_rt(); const char *jsf_get_script_filename(JSContext *c);
View file
gpac-2.4.0.tar.gz/src/scenegraph/svg_attributes.c -> gpac-26.02.0.tar.gz/src/scenegraph/svg_attributes.c
Changed
@@ -716,7 +716,11 @@ static void svg_parse_color(SVG_Color *col, char *attribute_content, GF_Err *out_e) { char *str = attribute_content; - while (strstrlen(attribute_content)-1 == ' ') strstrlen(attribute_content)-1 = 0; + u32 len = (u32) strlen(attribute_content); + while (len && (strstrlen(attribute_content)-1 == ' ')) { + strlen-1 = 0; + len--; + } while (*str != 0 && (*str == ' ' || *str == ',' || *str == ';')) str++; if (!strcmp(str, "currentColor")) { @@ -3427,7 +3431,8 @@ svg_parse_idref(n, (XMLRI*)info->far_ptr, attribute_content); break; case SMIL_AttributeName_datatype: - ((SMIL_AttributeName *)info->far_ptr)->name = gf_strdup(attribute_content); + if (! ((SMIL_AttributeName *)info->far_ptr)->name) + ((SMIL_AttributeName *)info->far_ptr)->name = gf_strdup(attribute_content); break; case SMIL_Times_datatype: smil_parse_time_list(n, *(GF_List **)info->far_ptr, attribute_content); @@ -5736,18 +5741,32 @@ #if USE_GF_PATH static GF_Err svg_path_copy(SVG_PathData *a, SVG_PathData *b) { + if (!b) + return GF_BAD_PARAM; + if (a->contours) gf_free(a->contours); if (a->points) gf_free(a->points); if (a->tags) gf_free(a->tags); - a->contours = (u32 *)gf_malloc(sizeof(u32)*b->n_contours); - a->points = (GF_Point2D *) gf_malloc(sizeof(GF_Point2D)*b->n_points); - a->tags = (u8 *) gf_malloc(sizeof(u8)*b->n_points); - memcpy(a->contours, b->contours, sizeof(u32)*b->n_contours); - a->n_contours = b->n_contours; - memcpy(a->points, b->points, sizeof(GF_Point2D)*b->n_points); - memcpy(a->tags, b->tags, sizeof(u8)*b->n_points); - a->n_alloc_points = a->n_points = b->n_points; + memset(a, 0, sizeof(SVG_PathData)); + + if (b->contours && b->n_contours) { + a->contours = (u32 *)gf_malloc(sizeof(u32)*b->n_contours); + memcpy(a->contours, b->contours, sizeof(u32)*b->n_contours); + a->n_contours = b->n_contours; + } + + if (b->points && b->n_points) { + a->points = (GF_Point2D *) gf_malloc(sizeof(GF_Point2D)*b->n_points); + memcpy(a->points, b->points, sizeof(GF_Point2D)*b->n_points); + a->n_alloc_points = a->n_points = b->n_points; + } + + if (b->tags && b->n_points) { + a->tags = (u8 *) gf_malloc(sizeof(u8)*b->n_points); + memcpy(a->tags, b->tags, sizeof(u8)*b->n_points); + } + a->flags = b->flags; a->bbox = b->bbox; a->fineness = b->fineness;
View file
gpac-2.4.0.tar.gz/src/scenegraph/svg_js.c -> gpac-26.02.0.tar.gz/src/scenegraph/svg_js.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2025 * All rights reserved * * This file is part of GPAC / Scene Graph sub-project @@ -756,7 +756,7 @@ case SVG_Motion_datatype: /*end SVGT 1.2 default traits*/ - /*unimplemented/unnkown/FIXME traits*/ + /*unimplemented/unknown/FIXME traits*/ case SMIL_SyncTolerance_datatype: case SVG_TransformType_datatype: case SVG_TransformList_datatype: @@ -767,7 +767,7 @@ case SMIL_Duration_datatype: case SMIL_RepeatCount_datatype: default: - /*end unimplemented/unnkown/FIXME traits*/ + /*end unimplemented/unknown/FIXME traits*/ return GF_JS_EXCEPTION(c); #endif } @@ -1458,8 +1458,8 @@ static void baseCI_finalize(JSRuntime *rt, JSValue obj) { - /*avoids GCC warning*/ - void *data = JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + void *data = JS_GetAnyOpaque(obj, &_classID); if (data) gf_free(data); }
View file
gpac-2.4.0.tar.gz/src/scenegraph/vrml_js.c -> gpac-26.02.0.tar.gz/src/scenegraph/vrml_js.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / Scene Graph sub-project @@ -510,7 +510,7 @@ t_info.name = "timestamp"; gf_js_lock(priv->js_ctx, 1); - + argv1 = gf_sg_script_to_qjs_field(priv, &t_info, node, 1); argv0 = gf_sg_script_to_qjs_field(priv, &r->FromField, r->FromNode, 1); @@ -751,6 +751,7 @@ static JSValue loadURL(JSContext *c, JSValueConst this_val, int argc, JSValueConst *argv) { + JSClassID _classID; u32 i; GF_JSAPIParam par; GF_JSField *f; @@ -769,7 +770,7 @@ } if (!JS_IsObject(argv0)) return GF_JS_EXCEPTION(c); - f = (GF_JSField *) JS_GetOpaque_Nocheck(argv0); + f = (GF_JSField *) JS_GetAnyOpaque(argv0, &_classID); if (!f || !f->mfvals) return GF_JS_EXCEPTION(c); for (i=0; i<f->mfvals_count; i++) { @@ -997,7 +998,7 @@ if (!JS_IsUndefined(ptr->obj) && is_js_call) { if (ptr->js_ctx) { GF_ScriptPriv *priv; - if (!gs_js_context_is_valid(ptr->js_ctx)) + if (!gf_js_context_is_valid(ptr->js_ctx)) return; priv = JS_GetScriptStack(ptr->js_ctx); gf_list_del_item(priv->jsf_cache, ptr); @@ -1012,8 +1013,9 @@ u32 i; JSValue item; Double d; + JSClassID _classID; char *str = NULL; - GF_JSField *f = JS_GetOpaque_Nocheck(this_val); + GF_JSField *f = JS_GetAnyOpaque(this_val, &_classID); if (!f) return JS_FALSE; if (gf_sg_vrml_is_sf_field(f->field.fieldType)) { @@ -1053,7 +1055,7 @@ break; default: if (JS_IsObject(item)) { - GF_JSField *sf = (GF_JSField *) JS_GetOpaque_Nocheck(item); + GF_JSField *sf = (GF_JSField *) JS_GetAnyOpaque(item, &_classID); sffield_toString(&str, sf->field.far_ptr, sf->field.fieldType); } break; @@ -1173,7 +1175,7 @@ static void node_finalize(JSRuntime *rt, JSValue val) { node_finalize_ex(rt, val, GF_TRUE); - + } static JSValue node_toString(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv) @@ -1347,7 +1349,8 @@ /* Generic field destructor */ static void field_finalize(JSRuntime *rt, JSValue obj) { - GF_JSField *ptr = (GF_JSField *) JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_JSField *ptr = (GF_JSField *) JS_GetAnyOpaque(obj, &_classID); JS_ObjectDestroyed(rt, obj, ptr, 1); if (!ptr) return; @@ -2266,7 +2269,8 @@ static void array_finalize_ex(JSRuntime *rt, JSValue obj, Bool is_js_call) { u32 i; - GF_JSField *ptr = JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_JSField *ptr = JS_GetAnyOpaque(obj, &_classID); JS_ObjectDestroyed(rt, obj, ptr, 1); if (!ptr) return; @@ -2298,7 +2302,8 @@ static JSValue array_getElement(JSContext *c, JSValueConst obj, JSAtom atom, JSValueConst receiver) { u32 idx; - GF_JSField *ptr = JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + GF_JSField *ptr = JS_GetAnyOpaque(obj, &_classID); if (!JS_AtomIsArrayIndex(c, &idx, atom)) { JSValue ret = JS_UNDEFINED; @@ -2306,7 +2311,7 @@ if (!str) return ret; if (!strcmp(str, "length")) { - GF_JSField *f_ptr = JS_GetOpaque_Nocheck(obj); + GF_JSField *f_ptr = JS_GetAnyOpaque(obj, &_classID); if (!f_ptr) { ret = GF_JS_EXCEPTION(c); } else if (f_ptr->field.fieldType==GF_SG_VRML_MFNODE) { @@ -2329,7 +2334,7 @@ JSValue val; if (idx>=ptr->mfvals_count) return JS_NULL; val = ptr->mfvalsidx; -// GF_JSField *sf = JS_GetOpaque_Nocheck(val); +// GF_JSField *sf = JS_GetAnyOpaque(val, &_classID); return JS_DupValue(c, val); } return JS_NULL; @@ -2442,8 +2447,9 @@ GF_JSClass *the_sf_class = NULL; char *str_val; void *sf_slot; + JSClassID _classID; Bool is_append = 0; - GF_JSField *ptr = (GF_JSField *) JS_GetOpaque_Nocheck(obj); + GF_JSField *ptr = (GF_JSField *) JS_GetAnyOpaque(obj, &_classID); if (!ptr) return -1; if (!JS_AtomIsArrayIndex(c, &ind, atom)) { @@ -2819,7 +2825,8 @@ static void field_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { - GF_JSField *jsf = JS_GetOpaque_Nocheck(val); + JSClassID _classID; + GF_JSField *jsf = JS_GetAnyOpaque(val, &_classID); if (!jsf) return; if (!JS_IsUndefined(jsf->obj) && jsf->owner) { JS_MarkValue(rt, jsf->obj, mark_func); @@ -2929,6 +2936,7 @@ void gf_sg_script_to_node_field(JSContext *c, JSValue val, GF_FieldInfo *field, GF_Node *owner, GF_JSField *parent) { Double d; + JSClassID _classID; Bool changed; const char *str_val; GF_JSField *p, *from; @@ -3088,7 +3096,7 @@ break; } - p = (GF_JSField *) JS_GetOpaque_Nocheck(val); + p = (GF_JSField *) JS_GetAnyOpaque(val, &_classID); if (!p) return; len = p->mfvals_count; @@ -3325,14 +3333,14 @@ jsf = NewJSField(priv->js_ctx); \ jsf->owner = parent; \ if(parent) gf_node_get_field(parent, field->fieldIndex, &jsf->field); \ - + #define SETUP_MF_FIELD(_class) \ obj = JS_CallConstructor(priv->js_ctx, priv->_class, 0, NULL);\ if (JS_IsException(obj) ) return obj; \ jsf = (GF_JSField *) JS_GetOpaque(obj, _class.class_id); \ jsf->owner = parent; \ if (parent) gf_node_get_field(parent, field->fieldIndex, &jsf->field); \ - + static GF_JSClass *get_sf_class(u32 mftype) { @@ -3627,7 +3635,8 @@ return obj; if (!jsf) { - jsf = JS_GetOpaque_Nocheck(obj); + JSClassID _classID; + jsf = JS_GetAnyOpaque(obj, &_classID); gf_assert(jsf); } //store field associated with object if needed @@ -4356,6 +4365,7 @@ #ifndef GPAC_DISABLE_SVG GF_ScriptPriv *priv; Bool prev_type; + JSClassID _classID; //JSBool ret = JS_FALSE; GF_DOM_Event *prev_event = NULL; SVG_handlerElement *hdl; @@ -4374,7 +4384,7 @@ priv = JS_GetScriptStack(hdl->js_data->ctx); gf_js_lock(priv->js_ctx, 1); - prev_event = JS_GetOpaque_Nocheck(priv->the_event); + prev_event = JS_GetAnyOpaque(priv->the_event, &_classID); /*break loops*/ if (prev_event && (prev_event->type==event->type) && (prev_event->target==event->target)) { gf_js_lock(priv->js_ctx, 0);
View file
gpac-2.4.0.tar.gz/src/scenegraph/xml_ns.c -> gpac-26.02.0.tar.gz/src/scenegraph/xml_ns.c
Changed
@@ -531,7 +531,7 @@ xmlns = (char *) gf_xml_get_namespace_qname((GF_DOMNode*)node, xml_attributesi.xmlns); if (xmlns) { - sprintf(node->sgprivate->scenegraph->szNameBuffer, "%s:%s", xmlns, xml_attributesi.name); + snprintf(node->sgprivate->scenegraph->szNameBuffer, 99, "%s:%s", xmlns, xml_attributesi.name); return node->sgprivate->scenegraph->szNameBuffer; } return xml_attributesi.name; @@ -664,7 +664,7 @@ xmlns = (char *) gf_sg_get_namespace_qname(n->sgprivate->scenegraph, xml_elementsi.xmlns); if (xmlns) { - sprintf(n->sgprivate->scenegraph->szNameBuffer, "%s:%s", xmlns, xml_elementsi.name); + snprintf(n->sgprivate->scenegraph->szNameBuffer, 99, "%s:%s", xmlns, xml_elementsi.name); return n->sgprivate->scenegraph->szNameBuffer; } return xml_elementsi.name; @@ -760,7 +760,7 @@ while (att) { if (((u32) att->tag == TAG_DOM_ATT_any) && - ((!ns && !strcmp(name, att->name)) || (ns && !strncmp(att->name, ns, len) && !strcmp(att->name+len+1, name))) + ((!ns && !strcmp(name, att->name)) || (ns && !strncmp(att->name, ns, len) && att->namelen && !strcmp(att->name+len+1, name))) ) { field->fieldIndex = att->tag; field->fieldType = att->data_type; @@ -982,7 +982,7 @@ #endif } -GF_Node *gf_xml_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *inst_id, Bool deep) +GF_Node *gf_sg_xml_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *inst_id, Bool deep) { GF_DOMAttribute *att; GF_Node *clone;
View file
gpac-2.4.0.tar.gz/src/utils/alloc.c -> gpac-26.02.0.tar.gz/src/utils/alloc.c
Changed
@@ -496,14 +496,14 @@ typedef struct s_memory_element { - void *ptr; - unsigned int size; - struct s_memory_element *next; + void *ptr; + unsigned int size; + struct s_memory_element *next; #ifndef GPAC_MEMORY_TRACKING_DISABLE_STACKTRACE - char *backtrace_stack; + char *backtrace_stack; #endif - int line; - char *filename; + int line; + char *filename; } memory_element; /*pointer to the first element of the list*/ @@ -539,17 +539,17 @@ element->line = line; #ifndef GPAC_MEMORY_TRACKING_DISABLE_STACKTRACE - if (gf_mem_backtrace_enabled) { - element->backtrace_stack = MALLOC(sizeof(char) * STACK_PRINT_SIZE * SYMBOL_MAX_SIZE); + if (gf_mem_backtrace_enabled) { + element->backtrace_stack = MALLOC(sizeof(char) * STACK_PRINT_SIZE * SYMBOL_MAX_SIZE); if (!element->backtrace_stack) { gf_memory_log(GF_MEMORY_WARNING, ("Mem Fail to register backtrace of allocation\n")); element->backtrace_stack = NULL; } else { store_backtrace(element->backtrace_stack); } - } else { - element->backtrace_stack = NULL; - } + } else { + element->backtrace_stack = NULL; + } #endif element->filename = MALLOC(strlen(filename) + 1); @@ -586,11 +586,11 @@ else *p = curr_element->next; size = curr_element->size; #ifndef GPAC_MEMORY_TRACKING_DISABLE_STACKTRACE - if (curr_element->backtrace_stack) { - FREE(curr_element->backtrace_stack); - } + if (curr_element->backtrace_stack) { + FREE(curr_element->backtrace_stack); + } #endif - FREE(curr_element); + FREE(curr_element); return size; } prev_element = curr_element; @@ -629,8 +629,12 @@ gf_fatal_assert(*p); hash = gf_memory_hash(ptr); sub_list = &((*p)hash); - if (!sub_list) return 0; + //code does nothing as &((*p)hash) always evaluates to true - commenting it +// if (!sub_list) return 0; ret = gf_memory_del_item_stack(sub_list, ptr); + + //code does nothing as &((*p)i) always evaluates to true - commenting it +#if 0 if (ret && !((*p)hash)) { /*check for deletion*/ int i; @@ -640,6 +644,7 @@ FREE(*p); } } +#endif return ret; } @@ -693,13 +698,13 @@ void log_backtrace(unsigned int log_level, memory_element *element) { #ifndef GPAC_MEMORY_TRACKING_DISABLE_STACKTRACE - if (gf_mem_backtrace_enabled) { - gf_memory_log(log_level, "file %s at line %d\n%s\n", element->filename, element->line, element->backtrace_stack); - } else + if (gf_mem_backtrace_enabled) { + gf_memory_log(log_level, "file %s at line %d\n%s\n", element->filename, element->line, element->backtrace_stack); + } else #endif - { - gf_memory_log(log_level, "file %s at line %d\n", element->filename, element->line); - } + { + gf_memory_log(log_level, "file %s at line %d\n", element->filename, element->line); + } } @@ -724,7 +729,7 @@ gf_assert(element); gf_memory_log(GF_MEMORY_ERROR, "MemTracker the block %p was already freed in:\n", ptr); res = GF_FALSE; - log_backtrace(GF_MEMORY_ERROR, element); + log_backtrace(GF_MEMORY_ERROR, element); } /*unlock*/ gf_mx_v(gpac_allocations_lock);
View file
gpac-2.4.0.tar.gz/src/utils/base_encoding.c -> gpac-26.02.0.tar.gz/src/utils/base_encoding.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2022 + * Copyright (c) Telecom ParisTech 2000-2024 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -288,7 +288,10 @@ } if (err==Z_STREAM_END) break; - size *= 2; + if (size >= GF_UINT_MAX/2 - 1) + size = GF_UINT_MAX - 1; + else + size *= 2; *uncompressed_data = (char*)gf_realloc(*uncompressed_data, sizeof(char)*(size+1)); if (!*uncompressed_data) return GF_OUT_OF_MEM; d_stream.avail_out = (u32) (size - d_stream.total_out); @@ -407,7 +410,8 @@ u32 block_size = 4096; u32 done = 0; u32 alloc_size = 0; - u8 block4096; + u8 *block = gf_malloc(4096); + if (!block) return GF_OUT_OF_MEM; u8 *dst_buffer = NULL; if (*uncompressed_data) { @@ -444,12 +448,14 @@ if (ret != LZMA_OK) { GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("LZMA error decompressing data: %d\n", ret )); if (owns_buffer) gf_free(dst_buffer); + gf_free(block); return GF_IO_ERR; } } *uncompressed_data = dst_buffer; *out_size = done; + gf_free(block); return GF_OK; } #else
View file
gpac-2.4.0.tar.gz/src/utils/bitstream.c -> gpac-26.02.0.tar.gz/src/utils/bitstream.c
Changed
@@ -86,6 +86,7 @@ #endif }; +GF_EXPORT GF_Err gf_bs_reassign_buffer(GF_BitStream *bs, const u8 *buffer, u64 BufferSize) { if (!bs) return GF_BAD_PARAM; @@ -233,9 +234,6 @@ } #ifdef GPAC_HAS_FD -#include <unistd.h> -#include <sys/stat.h> -#include <fcntl.h> GF_EXPORT GF_BitStream *gf_bs_from_fd(int fd, u32 mode) @@ -255,12 +253,9 @@ tmp->position = 0; tmp->fd = fd; - struct stat sb; - fstat(fd, &sb); - /*get the size of this file (for read streams)*/ - tmp->position = lseek(fd, 0, SEEK_CUR); - tmp->size = sb.st_size; + tmp->position = lseek_64(fd, 0, SEEK_CUR); + tmp->size = gf_fd_fsize(fd); if (mode == GF_BITSTREAM_FILE_READ) { tmp->cache_read_alloc = gf_opts_get_int("core", "bs-cache-size"); @@ -785,7 +780,13 @@ { u64 orig = bs->position; - if (bs->position+nbBytes > bs->size) return 0; + if (bs->position+nbBytes > bs->size) { + if (!bs->overflow_state) { + bs->overflow_state = 1; + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("BS Attempt to overread bitstream\n")); + } + return 0; + } if (gf_bs_is_align(bs) ) { s32 bytes_read, bytes_read_cache; @@ -1268,7 +1269,7 @@ #ifdef GPAC_HAS_FD if (bs->fd>=0) { - cur = lseek(bs->fd, 0, SEEK_CUR); + cur = lseek_64(bs->fd, 0, SEEK_CUR); end = bs->position; } else #endif @@ -1396,7 +1397,7 @@ } #ifdef GPAC_HAS_FD if (bs->fd>=0) { - lseek(bs->fd, bs->position, SEEK_SET); + lseek_64(bs->fd, bs->position, SEEK_SET); } else #endif { @@ -1490,7 +1491,7 @@ s64 res; #ifdef GPAC_HAS_FD if (bs->fd>=0) { - res = lseek(bs->fd, offset, SEEK_SET); + res = lseek_64(bs->fd, offset, SEEK_SET); if (res>=0) res = 0; } else #endif @@ -1617,9 +1618,7 @@ #ifdef GPAC_HAS_FD if (bs->fd>=0) { - struct stat sb; - fstat(bs->fd, &sb); - bs->size = sb.st_size; + bs->size = gf_fd_fsize(bs->fd); return bs->size; } #endif @@ -1948,6 +1947,8 @@ { bs->overflow_state = reset ? 0 : 2; } + +GF_EXPORT u32 gf_bs_is_overflow(GF_BitStream *bs) { return bs->overflow_state;
View file
gpac-2.4.0.tar.gz/src/utils/configfile.c -> gpac-26.02.0.tar.gz/src/utils/configfile.c
Changed
@@ -110,10 +110,10 @@ } } - if (filePath && ((filePathstrlen(filePath)-1 == '/') || (filePathstrlen(filePath)-1 == '\\')) ) { + if (filePath && strlen(filePath) && ((filePathstrlen(filePath)-1 == '/') || (filePathstrlen(filePath)-1 == '\\')) ) { strcpy(fileName, filePath); strcat(fileName, file_name); - } else if (filePath) { + } else if (filePath && strlen(filePath)) { sprintf(fileName, "%s%c%s", filePath, GF_PATH_SEPARATOR, file_name); } else { strcpy(fileName, file_name); @@ -594,5 +594,3 @@ iniFile->fileName = gf_strdup(fileName); return iniFile->fileName ? GF_OK : GF_OUT_OF_MEM; } - -
View file
gpac-2.4.0.tar.gz/src/utils/constants.c -> gpac-26.02.0.tar.gz/src/utils/constants.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2017-2023 + * Copyright (c) Telecom ParisTech 2017-2025 * All rights reserved * * This file is part of GPAC / filters sub-project @@ -100,12 +100,13 @@ {GF_CODECID_DIRAC, 0xA4, GF_STREAM_VISUAL, "Dirac Video", "dirac", NULL, "video/dirac"}, {GF_CODECID_AC3, 0xA5, GF_STREAM_AUDIO, "AC3 Audio", "ac3", "ac-3", "audio/ac3", .unframe=GF_TRUE}, {GF_CODECID_EAC3, 0xA6, GF_STREAM_AUDIO, "Enhanced AC3 Audio", "eac3", "ec-3", "audio/eac3", .unframe=GF_TRUE}, + {GF_CODECID_AC4, 0, GF_STREAM_AUDIO, "AC4 Audio", "ac4", "ac-4", "audio/ac4", .unframe=GF_TRUE}, {GF_CODECID_TRUEHD, 0, GF_STREAM_AUDIO, "Dolby TrueHD", "mlp", "mlpa", "audio/truehd", .unframe=GF_TRUE}, {GF_CODECID_DRA, 0xA7, GF_STREAM_AUDIO, "DRA Audio", "dra", NULL, "audio/dra"}, {GF_CODECID_G719, 0xA8, GF_STREAM_AUDIO, "G719 Audio", "g719", NULL, "audio/g719"}, - {GF_CODECID_DTS_CA, 0xA9, GF_STREAM_AUDIO, "DTS Coherent Acoustics and Digital Surround Audio", "dstc", NULL, "audio/dts"}, + {GF_CODECID_DTS_CA, 0xA9, GF_STREAM_AUDIO, "DTS Coherent Acoustics and Digital Surround Audio", "dtsc", NULL, "audio/dts"}, {GF_CODECID_DTS_HD_HR_MASTER, 0xAA, GF_STREAM_AUDIO, "DTS-HD High Resolution Audio and DTS-Master Audio", "dtsh", NULL, "audio/dts"}, - {GF_CODECID_DTS_HD_LOSSLESS, 0xAB, GF_STREAM_AUDIO, "DTS-HD Substream containing only XLLAudio", "dstl", NULL, "audio/dts"}, + {GF_CODECID_DTS_HD_LOSSLESS, 0xAB, GF_STREAM_AUDIO, "DTS-HD Substream containing only XLLAudio", "dtsl", NULL, "audio/dts"}, {GF_CODECID_DTS_EXPRESS_LBR, 0xAC, GF_STREAM_AUDIO, "DTS Express low bit rate Audio", "dtse", NULL, "audio/dts"}, {GF_CODECID_DTS_X, 0xB2, GF_STREAM_AUDIO, "DTS-X UHD Audio Profile 2", "dtsx", NULL, "audio/dts"}, {GF_CODECID_DTS_Y, 0xB3, GF_STREAM_AUDIO, "DTS-X UHD Audio Profile 3", "dtsy", NULL, "audio/dts"}, @@ -125,6 +126,7 @@ {GF_CODECID_THEORA, 0xDF, GF_STREAM_VISUAL, "Theora Video", "theo|theora", NULL, "video/theora"}, {GF_CODECID_VORBIS, 0xDD, GF_STREAM_AUDIO, "Vorbis Audio", "vorb|vorbis", NULL, "audio/vorbis"}, {GF_CODECID_OPUS, 0xDE, GF_STREAM_AUDIO, "Opus Audio", "opus", NULL, "audio/opus"}, + {GF_CODECID_IAMF, 0, GF_STREAM_AUDIO, "AOM IAMF (Immersive Audio Model and Formats)", "iamf", NULL, "audio/iamf", .unframe=GF_TRUE}, {GF_CODECID_FLAC, 0, GF_STREAM_AUDIO, "Flac Audio", "flac", "fLaC", "audio/flac", .unframe=GF_TRUE}, {GF_CODECID_SPEEX, 0, GF_STREAM_AUDIO, "Speex Audio", "spx|speex", NULL, "audio/speex"}, {GF_CODECID_SUBPIC, 0xE0, GF_STREAM_TEXT, "VobSub Subtitle", "vobsub", NULL, "text/x-vobsub"}, @@ -151,6 +153,9 @@ {GF_CODECID_VP9, 0, GF_STREAM_VISUAL, "VP9 Video", "vp9|ivf", NULL, "video/vp9", .unframe=GF_TRUE}, {GF_CODECID_VP10, 0, GF_STREAM_VISUAL, "VP10 Video", "vp10|ivf", NULL, "video/vp10"}, + {GF_CODECID_AVS3_VIDEO, 0, GF_STREAM_VISUAL, "AVS3 Video", "avsv|avs3", NULL, "video/avs3"}, + {GF_CODECID_AVS3_AUDIO, 0, GF_STREAM_AUDIO, "AVS3 Audio", "avsa|avs3", NULL, "audio/avs3"}, + {GF_CODECID_MPHA, 0, GF_STREAM_AUDIO, "MPEG-H Audio", "mhas", "mha1", "audio/x-mpegh", .unframe=GF_TRUE}, {GF_CODECID_MHAS, 0, GF_STREAM_AUDIO, "MPEG-H AudioMux", "mhas", "mhm1", "audio/x-mhas", .unframe=GF_TRUE}, @@ -160,19 +165,22 @@ {GF_CODECID_APCS, 0, GF_STREAM_VISUAL, "ProRes Video 422 LT", "prores|apcs", "apcs", "video/prores", GF_CODECID_APCH, .unframe=GF_TRUE}, {GF_CODECID_AP4X, 0, GF_STREAM_VISUAL, "ProRes Video 4444 XQ", "prores|ap4x", "ap4x", "video/prores", GF_CODECID_APCH, .unframe=GF_TRUE}, {GF_CODECID_AP4H, 0, GF_STREAM_VISUAL, "ProRes Video 4444", "prores|ap4h", "ap4h", "video/prores", GF_CODECID_APCH, .unframe=GF_TRUE}, - {GF_CODECID_FFMPEG, 0, GF_STREAM_UNKNOWN, "FFMPEG unmapped codec", "ffmpeg", NULL, NULL}, + {GF_CODECID_FFMPEG, 0, GF_STREAM_UNKNOWN, "FFmpeg unmapped codec", "ffmpeg", NULL, NULL}, {GF_CODECID_TMCD, 0, GF_STREAM_METADATA, "QT TimeCode", "tmcd", NULL, NULL}, + {GF_CODECID_SCTE35, 0, GF_STREAM_METADATA, "SCTE35", "sc35", "evte", NULL}, + {GF_CODECID_EVTE, 0, GF_STREAM_METADATA, "Event Messages", "evte", "evte", NULL}, {GF_CODECID_VVC, 0, GF_STREAM_VISUAL, "VVC Video", "vvc|266|h266", "vvc1", "video/vvc", .unframe=GF_TRUE}, {GF_CODECID_VVC_SUBPIC, 0, GF_STREAM_VISUAL, "VVC Subpicture Video", "vvs1", "vvs1", "video/x-vvc-subpic", .alt_codecid=GF_CODECID_VVC, .unframe=GF_TRUE}, {GF_CODECID_USAC, GF_CODECID_AAC_MPEG4, GF_STREAM_AUDIO, "xHEAAC / USAC Audio", "usac|xheaac", "mp4a", "audio/x-xheaac", .unframe=GF_TRUE}, - {GF_CODECID_FFV1, 0, GF_STREAM_VISUAL, "FFMPEG Video Codec 1", "ffv1", NULL, "video/x-ffv1"}, + {GF_CODECID_FFV1, 0, GF_STREAM_VISUAL, "FFmpeg Video Codec 1", "ffv1", NULL, "video/x-ffv1"}, {GF_CODECID_DVB_SUBS, 0, GF_STREAM_TEXT, "DVB Subtitles", "dvbs", NULL, NULL}, {GF_CODECID_DVB_TELETEXT, 0, GF_STREAM_TEXT, "DVB-TeleText", "dvbs", NULL, NULL}, {GF_CODECID_MSPEG4_V3, 0, GF_STREAM_VISUAL, "MS-MPEG4 V3", "div3", NULL, NULL, GF_CODECID_MSPEG4_V3}, {GF_CODECID_ALAC, 0, GF_STREAM_AUDIO, "Apple Lossless Audio", "caf", NULL, NULL}, + {GF_CODECID_DNXHD, 0, GF_STREAM_VISUAL, "AViD DNxHD", "dnx", "AVdn", "video/dnx"}, }; @@ -236,6 +244,8 @@ return GF_CODECID_AC3; case GF_ISOM_SUBTYPE_EC3: return GF_CODECID_EAC3; + case GF_ISOM_SUBTYPE_AC4: + return GF_CODECID_AC4; case GF_ISOM_SUBTYPE_FLAC: return GF_CODECID_FLAC; case GF_ISOM_SUBTYPE_MP3: @@ -274,6 +284,8 @@ return GF_CODECID_VP9; case GF_ISOM_SUBTYPE_VP10: return GF_CODECID_VP10; + case GF_ISOM_SUBTYPE_AVS3: + return GF_CODECID_AVS3_VIDEO; case GF_QT_SUBTYPE_APCH: return GF_CODECID_APCH; @@ -319,6 +331,12 @@ default: break; } + const char *c4cc = gf_4cc_to_str(isobmftype); + u32 i, count = sizeof(CodecRegistry) / sizeof(CodecIDReg); + for (i=0; i<count; i++) { + if (CodecRegistryi.rfc_4cc && !strncmp(CodecRegistryi.rfc_4cc, c4cc, 4)) + return CodecRegistryi.codecid; + } return 0; } @@ -347,7 +365,8 @@ const char *gf_codecid_name(GF_CodecID codecid) { CodecIDReg *r = gf_codecid_reg_find(codecid); - if (!r) return "Codec Not Supported"; + if (!r) + return "Codec Not Supported"; return r->name; } @@ -494,7 +513,7 @@ if (GF_StreamTypesi.st == streamType) return GF_StreamTypesi.sname; } - return "unkn"; + return "unknown"; } GF_EXPORT @@ -504,11 +523,13 @@ for (i=0; i<nb_st; i++) { if (!stricmp(GF_StreamTypesi.name, val)) return GF_StreamTypesi.st; + if (GF_StreamTypesi.sname && !stricmp(GF_StreamTypesi.sname, val)) + return GF_StreamTypesi.st; if (GF_StreamTypesi.alt_name && !stricmp(GF_StreamTypesi.alt_name, val)) return GF_StreamTypesi.st; } if (strnicmp(val, "unkn", 4) && strnicmp(val, "undef", 5)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Unknow stream type %s\n", val)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Unknown stream type %s\n", val)); } return GF_STREAM_UNKNOWN; } @@ -855,6 +876,8 @@ else if ((nb_chan==2) && (nb_surr==1) && !nb_lfe) return 9; else if ((nb_chan==2) && (nb_surr==2) && !nb_lfe) return 10; else if ((nb_chan==3) && (nb_surr==3) && (nb_lfe==1)) return 11; + else if ((nb_chan==4) && (nb_surr==2) && (nb_lfe==1)) return 11; + else if ((nb_chan==3) && (nb_surr==4) && (nb_lfe==1)) return 12; else if ((nb_chan==11) && (nb_surr==11) && (nb_lfe==2)) return 13; //we miss left / right front center vs left / right front vertical to signal this one @@ -886,17 +909,17 @@ {2, "stereo"/*"2/0.0"*/, GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT }, {3, "3/0.0", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER }, {4, "3/1.0", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_CENTER }, - {5, "3/2.0", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT }, - {6, "3/2.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT | GF_AUDIO_CH_LFE }, - {7, "5/2.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT | GF_AUDIO_CH_LFE }, + {5, "3/2.0", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT }, + {6, "3/2.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_LFE }, + {7, "5/2.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_LFE }, {8, "1+1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT }, {9, "2/1.0", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_REAR_CENTER }, {10, "2/2.0", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT }, {11, "3/3.1", GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_REAR_CENTER | GF_AUDIO_CH_LFE }, {12, "3/4.1", GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT | GF_AUDIO_CH_LFE }, {13, "11/11.2", GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER_LEFT | GF_AUDIO_CH_FRONT_CENTER_RIGHT | GF_AUDIO_CH_SIDE_SURROUND_LEFT | GF_AUDIO_CH_SIDE_SURROUND_RIGHT | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT | GF_AUDIO_CH_REAR_CENTER | GF_AUDIO_CH_LFE | GF_AUDIO_CH_LFE2 | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT | GF_AUDIO_CH_FRONT_TOP_CENTER | GF_AUDIO_CH_SURROUND_TOP_LEFT | GF_AUDIO_CH_SURROUND_TOP_RIGHT | GF_AUDIO_CH_REAR_CENTER_TOP | GF_AUDIO_CH_SIDE_SURROUND_TOP_LEFT | GF_AUDIO_CH_SIDE_SURROUND_TOP_RIGHT | GF_AUDIO_CH_CENTER_SURROUND_TOP | GF_AUDIO_CH_FRONT_BOTTOM_CENTER | GF_AUDIO_CH_FRONT_BOTTOM_LEFT | GF_AUDIO_CH_FRONT_BOTTOM_RIGHT }, - {14, "5/2.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT | GF_AUDIO_CH_LFE | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT }, - {15, "5/5.2", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_REAR_SURROUND_LEFT | GF_AUDIO_CH_REAR_SURROUND_RIGHT | GF_AUDIO_CH_SIDE_SURROUND_LEFT | GF_AUDIO_CH_SIDE_SURROUND_RIGHT | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT | GF_AUDIO_CH_CENTER_SURROUND_TOP | GF_AUDIO_CH_LFE | GF_AUDIO_CH_LFE2 }, + {14, "5/2.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_LFE | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT }, + {15, "5/5.2", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_SIDE_SURROUND_LEFT | GF_AUDIO_CH_SIDE_SURROUND_RIGHT | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT | GF_AUDIO_CH_CENTER_SURROUND_TOP | GF_AUDIO_CH_LFE | GF_AUDIO_CH_LFE2 }, {16, "5/4.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_LFE | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT | GF_AUDIO_CH_SURROUND_TOP_LEFT | GF_AUDIO_CH_SURROUND_TOP_RIGHT }, {17, "6/5.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_LFE | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT | GF_AUDIO_CH_FRONT_TOP_CENTER | GF_AUDIO_CH_SURROUND_TOP_LEFT | GF_AUDIO_CH_SURROUND_TOP_RIGHT | GF_AUDIO_CH_CENTER_SURROUND_TOP }, {18, "6/7.1", GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_BACK_SURROUND_LEFT | GF_AUDIO_CH_BACK_SURROUND_RIGHT | GF_AUDIO_CH_LFE | GF_AUDIO_CH_FRONT_TOP_LEFT | GF_AUDIO_CH_FRONT_TOP_RIGHT | GF_AUDIO_CH_FRONT_TOP_CENTER | GF_AUDIO_CH_SURROUND_TOP_LEFT | GF_AUDIO_CH_SURROUND_TOP_RIGHT | GF_AUDIO_CH_CENTER_SURROUND_TOP }, @@ -1035,20 +1058,19 @@ GF_EXPORT -u16 gf_audio_fmt_get_dolby_chanmap(u32 cicp) +u16 gf_audio_fmt_get_dolby_chanmap_from_layout(u64 layout) { u16 res = 0; - u64 layout = gf_audio_fmt_get_layout_from_cicp(cicp); if (layout & GF_AUDIO_CH_FRONT_LEFT) res |= (1<<15); // 0 if (layout & GF_AUDIO_CH_FRONT_CENTER) res |= (1<<14); //1 if (layout & GF_AUDIO_CH_FRONT_RIGHT) res |= (1<<13); //2 - if (layout & GF_AUDIO_CH_REAR_SURROUND_LEFT) res |= (1<<12); //3 - if (layout & GF_AUDIO_CH_REAR_SURROUND_RIGHT) res |= (1<<11); //4 + if (layout & GF_AUDIO_CH_SURROUND_LEFT) res |= (1<<12); //3 + if (layout & GF_AUDIO_CH_SURROUND_RIGHT) res |= (1<<11); //4 //Lc/Rc if (layout & GF_AUDIO_CH_FRONT_CENTER_LEFT) res |= (1<<11); //5 //Lrs/Rrs - if (layout & GF_AUDIO_CH_SURROUND_LEFT) res |= (1<<9); //6 + if (layout & GF_AUDIO_CH_REAR_SURROUND_LEFT) res |= (1<<9); //6 //Cs if (layout & GF_AUDIO_CH_REAR_CENTER) res |= (1<<8); //7 //Ts @@ -1056,7 +1078,7 @@ //Lsd/Rsd if (layout & GF_AUDIO_CH_SIDE_SURROUND_LEFT) res |= (1<<6); //9 //Lw/Rw - if (layout & GF_AUDIO_CH_FRONT_CENTER_LEFT) res |= (1<<5); //10 + if (layout & GF_AUDIO_CH_WIDE_FRONT_LEFT) res |= (1<<5); //10 //Vhl/Vhr if (layout & GF_AUDIO_CH_FRONT_TOP_LEFT) res |= (1<<4); //11 //Vhc @@ -1068,9 +1090,69 @@ //LFE if (layout & GF_AUDIO_CH_LFE) res |= (1); //15 return res; +} +u32 gf_audio_get_dolby_channel_config_value_from_mask(u32 mask) +{ + const u32 mask_dict2 = { + {0x000002, 1}, {0x000001, 2}, {0x000003, 3}, + {0x008003, 4}, {0x000007, 5}, {0x000047, 6}, + {0x020047, 7}, {0x008001, 9}, {0x000005, 10}, + {0x008047, 11}, {0x00004F, 12}, {0x02FF7F, 13}, + {0x06FF6F, 13}, {0x000057, 14}, {0x040047, 14}, + {0x00145F, 15}, {0x04144F, 15}, {0x000077, 16}, + {0x040067, 16}, {0x000A77, 17}, {0x040A67, 17}, + {0x000A7F, 18}, {0x040A6F, 18}, {0x00007F, 19}, + {0x04006F, 19}, {0x01007F, 20}, {0x05006F, 2} + }; + for (int i=0; i<28; i++) { + if (mask == mask_dicti0) { + return mask_dicti1; + } + } + return mask; +} + +// ETSI TS 103 190-2 V1.3.1 (2018-02) E10.14 presentation_channel_mask_v1 +u32 gf_ac4_dolby_channel_count_from_channel_mask_v1(u32 mask) +{ + const u32 channel_mask_v1_2_channel_count19 = { + 2, // L,R + 1, // C + 2, // Ls,Rs + 2, // Lb,Rb + 2, //Tfl,Tfr + 2, //Tbl,Tbr + 1, //LFE + 2, //Tl,Tr + 2, //Tsl,Tsr + 1, //Tfc + 1, //Tbc + 1, //Tc + 1, //LEF2 + 2, //Bfl,Bfr + 1, //Bfc + 1, //Cb + 2, //Lscr,Rscr + 2, //Lw,Rw + 2 //Vhl,Vhr + }; + u32 count = 0; + for(u32 i = 0; i < 19; i++) { + if(mask % 2 == 1) { + count += channel_mask_v1_2_channel_counti; + } + mask /= 2; + } + return count; } +GF_EXPORT +u16 gf_audio_fmt_get_dolby_chanmap(u32 cicp) +{ + u64 layout = gf_audio_fmt_get_layout_from_cicp(cicp); + return gf_audio_fmt_get_dolby_chanmap_from_layout(layout); +} typedef struct { @@ -1116,6 +1198,7 @@ {GF_PIXEL_GREYSCALE, "grey", "Greyscale 8 bit"}, {GF_PIXEL_ALPHAGREY, "algr", "Alpha+Grey 8 bit"}, {GF_PIXEL_GREYALPHA, "gral", "Grey+Alpha 8 bit"}, + {GF_PIXEL_RGB_332, "rgb8", "RGB 332, 8 bits / pixel"}, {GF_PIXEL_RGB_444, "rgb4", "RGB 444, 12 bits (16 stored) / pixel"}, {GF_PIXEL_RGB_555, "rgb5", "RGB 555, 15 bits (16 stored) / pixel"}, {GF_PIXEL_RGB_565, "rgb6", "RGB 555, 16 bits / pixel"}, @@ -1311,12 +1394,23 @@ switch (pixfmt) { case GF_PIXEL_GREYSCALE: stride = no_in_stride ? width : *out_stride; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = stride * height; planes=1; break; case GF_PIXEL_ALPHAGREY: case GF_PIXEL_GREYALPHA: stride = no_in_stride ? 2*width : *out_stride; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; + size = stride * height; + planes=1; + break; + case GF_PIXEL_RGB_332: + stride = no_in_stride ? width : *out_stride; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = stride * height; planes=1; break; @@ -1324,6 +1418,8 @@ case GF_PIXEL_RGB_555: case GF_PIXEL_RGB_565: stride = no_in_stride ? 2*width : *out_stride; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = stride * height; planes=1; break; @@ -1338,18 +1434,24 @@ case GF_PIXEL_RGBD: case GF_PIXEL_RGBDS: stride = no_in_stride ? 4*width : *out_stride; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = stride * height; planes=1; break; case GF_PIXEL_RGB_DEPTH: stride = no_in_stride ? 3*width : *out_stride; stride_uv = no_in_stride_uv ? width : *out_stride_uv; + if (stride && height >= GF_UINT_MAX / (4*width)) + return GF_FALSE; size = 4 * width * height; planes=1; break; case GF_PIXEL_RGB: case GF_PIXEL_BGR: stride = no_in_stride ? 3*width : *out_stride; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = stride * height; planes=1; break; @@ -1362,6 +1464,8 @@ if (no_in_stride_uv && (stride%2) ) stride_uv+=1; planes=3; + if ((stride && height >= GF_UINT_MAX / stride) || height * stride >= GF_UINT_MAX - stride_uv * uv_height * 2) + return GF_FALSE; size = stride * height + stride_uv * uv_height * 2; break; case GF_PIXEL_YUVA: @@ -1373,6 +1477,8 @@ if (no_in_stride_uv && (stride%2) ) stride_uv+=1; planes=4; + if ((stride && height >= GF_UINT_MAX / (2*stride)) || height * 2*stride >= GF_UINT_MAX - stride_uv * uv_height * 2) + return GF_FALSE; size = 2*stride * height + stride_uv * uv_height * 2; break; case GF_PIXEL_YUV_10: @@ -1383,6 +1489,8 @@ if (no_in_stride_uv && (stride%2) ) stride_uv+=1; planes=3; + if ((stride && height >= GF_UINT_MAX / stride) || height * stride >= GF_UINT_MAX - stride_uv * uv_height * 2) + return GF_FALSE; size = stride * height + stride_uv * uv_height * 2; break; case GF_PIXEL_YUV422: @@ -1392,6 +1500,8 @@ if (no_in_stride_uv && (stride%2) ) stride_uv+=1; planes=3; + if ((stride && height >= GF_UINT_MAX / stride) || height * stride >= GF_UINT_MAX - stride_uv * uv_height * 2) + return GF_FALSE; size = stride * height + stride_uv * height * 2; break; case GF_PIXEL_YUV422_10: @@ -1401,6 +1511,8 @@ if (no_in_stride_uv && (stride%2) ) stride_uv+=1; planes=3; + if ((stride && height >= GF_UINT_MAX / stride) || height * stride >= GF_UINT_MAX - stride_uv * uv_height * 2) + return GF_FALSE; size = stride * height + stride_uv * height * 2; break; case GF_PIXEL_YUV444: @@ -1408,6 +1520,8 @@ uv_height = height; stride_uv = no_in_stride_uv ? stride : *out_stride_uv; planes=3; + if (stride && height >= GF_UINT_MAX / (3*stride)) + return GF_FALSE; size = stride * height * 3; break; case GF_PIXEL_YUVA444: @@ -1415,6 +1529,8 @@ uv_height = height; stride_uv = no_in_stride_uv ? stride : *out_stride_uv; planes=4; + if (stride && height >= GF_UINT_MAX / (4*stride)) + return GF_FALSE; size = stride * height * 4; break; case GF_PIXEL_YUV444_10: @@ -1422,11 +1538,15 @@ uv_height = height; stride_uv = no_in_stride_uv ? stride : *out_stride_uv; planes=3; + if (stride && height >= GF_UINT_MAX / (3*stride)) + return GF_FALSE; size = stride * height * 3; break; case GF_PIXEL_NV12: case GF_PIXEL_NV21: stride = no_in_stride ? width : *out_stride; + if (stride && height/2 >= GF_UINT_MAX / (3*stride)) + return GF_FALSE; size = 3 * stride * height / 2; uv_height = height/2; stride_uv = no_in_stride_uv ? stride : *out_stride_uv; @@ -1439,6 +1559,8 @@ if (height % 2) uv_height++; stride_uv = no_in_stride_uv ? stride : *out_stride_uv; planes=2; + if (stride && height/2 >= GF_UINT_MAX / (3*stride)) + return GF_FALSE; size = 3 * stride * height / 2; break; case GF_PIXEL_UYVY: @@ -1447,6 +1569,8 @@ case GF_PIXEL_YVYU: stride = no_in_stride ? 2*width : *out_stride; planes=1; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = height * stride; break; case GF_PIXEL_UYVY_10: @@ -1455,23 +1579,31 @@ case GF_PIXEL_YVYU_10: stride = no_in_stride ? 4*width : *out_stride; planes=1; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = height * stride; break; case GF_PIXEL_YUV444_PACK: case GF_PIXEL_VYU444_PACK: stride = no_in_stride ? 3 * width : *out_stride; planes=1; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = height * stride; break; case GF_PIXEL_YUVA444_PACK: case GF_PIXEL_UYVA444_PACK: stride = no_in_stride ? 4 * width : *out_stride; planes=1; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = height * stride; break; case GF_PIXEL_YUV444_10_PACK: stride = no_in_stride ? 4 * width : *out_stride; planes = 1; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = height * stride; break; @@ -1491,6 +1623,8 @@ stride = *out_stride; } planes=1; + if (stride && height >= GF_UINT_MAX / stride) + return GF_FALSE; size = height * stride; break; default: @@ -1552,6 +1686,7 @@ { switch (pixfmt) { case GF_PIXEL_GREYSCALE: + case GF_PIXEL_RGB_332: return 1; case GF_PIXEL_ALPHAGREY: case GF_PIXEL_GREYALPHA: @@ -1632,6 +1767,7 @@ case GF_PIXEL_ALPHAGREY: case GF_PIXEL_GREYALPHA: return 2; + case GF_PIXEL_RGB_332: case GF_PIXEL_RGB_444: case GF_PIXEL_RGB_555: case GF_PIXEL_RGB_565: @@ -1707,6 +1843,42 @@ return 0; } +GF_EXPORT +void gf_pixel_get_downsampling(GF_PixelFormat pixfmt, u32 *downsample_w, u32 *downsample_h) +{ + *downsample_w=0; + *downsample_h=0; + switch (pixfmt) { + case GF_PIXEL_YUV: + case GF_PIXEL_YVU: + case GF_PIXEL_YUV_10: + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + case GF_PIXEL_YUVA: + case GF_PIXEL_YUVD: + *downsample_h = 2; + //fallthrough + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY_10: + case GF_PIXEL_VYUY_10: + case GF_PIXEL_YUYV_10: + case GF_PIXEL_YVYU_10: + *downsample_w = 2; + break; + default: + *downsample_w = 1; + *downsample_h = 1; + break; + } +} + static struct pixfmt_to_qt { GF_PixelFormat pfmt; @@ -2001,7 +2173,7 @@ } } if (strcmp(val, "-1")) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknow CICP color primaries type %s\n", val)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknown CICP color primaries type %s\n", val)); } return (u32) -1; } @@ -2071,7 +2243,7 @@ } } if (strcmp(val, "-1")) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknow CICP color transfer type %s\n", val)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknown CICP color transfer type %s\n", val)); } return (u32) -1; } @@ -2132,7 +2304,7 @@ } } if (strcmp(val, "-1")) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknow CICP color matrix type %s\n", val)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknown CICP color matrix type %s\n", val)); } return (u32) -1; } @@ -2310,6 +2482,14 @@ comps_ID0 = 0; comps_ID1 = 7; break; + case GF_PIXEL_RGB_332: + nb_comps=3; + comps_ID0 = 4; + comps_ID1 = 5; + comps_ID2 = 6; + bits0 = bits1 = 3; + bits2 = 2; + break; case GF_PIXEL_RGB_444: nb_comps=3; comps_ID0 = 4; @@ -2683,3 +2863,133 @@ gf_bs_del(bs); return GF_TRUE; } + +GF_EXPORT +const char *gf_format_duration(u64 dur, u32 timescale, char szDur100) +{ + u64 h; + u32 m, s, ms; + szDur0 = 0; + szDur99 = 0; + if (!timescale) timescale = 1; + dur = gf_timestamp_rescale(dur, timescale, 1000); + h = (dur / 3600000); + dur -= h*3600000; + m = (u32) (dur / 60000); + dur -= m*60000; + s = (u32) (dur/1000); + dur -= s*1000; + ms = (u32) (dur); + + if (h<=24) { + snprintf(szDur, 99, "%02d:%02d:%02d.%03d", (u32) h, m, s, ms); + } else { + u32 d = (u32) (h / 24); + h = (u32) (h-24*d); + if (d<=365) { + snprintf(szDur, 99, "%d Days, %02d:%02d:%02d.%03d", d, (u32) h, m, s, ms); + } else { + u32 y=0; + while (d>365) { + y++; + d-=365; + if (y%4) d--; + } + snprintf(szDur, 99, "%d Years %d Days, %02d:%02d:%02d.%03d", y, d, (u32) h, m, s, ms); + } + } + return szDur; +} + +GF_EXPORT +const char* gf_format_timecode(GF_TimeCode *tc, char szTimecode100) +{ + int frame_digits = (tc->max_fps >= 100.0) ? 3 : 2; + snprintf(szTimecode, 99, "%02d:%02d:%02d%c%0*d", tc->hours, tc->minutes, tc->seconds, tc->drop_frame ? ';' : '.', frame_digits, tc->n_frames); + return szTimecode; +} + +GF_EXPORT +u64 gf_timecode_to_timestamp(GF_TimeCode *tc, u32 timescale) +{ + if (!timescale) timescale = 1; + u64 res = (u64) tc->hours * 3600 + (u64) tc->minutes * 60 + (u64) tc->seconds; + res *= timescale; + res += gf_timestamp_rescale(tc->n_frames, (u64) gf_ceil(tc->max_fps), timescale); + return res; +} + +#define TIMECODE_COMPARE(_op) \ + if (value1->hours != value2->hours) return value1->hours _op value2->hours; \ + if (value1->minutes != value2->minutes) return value1->minutes _op value2->minutes; \ + if (value1->seconds != value2->seconds) return value1->seconds _op value2->seconds; \ + return value1->n_frames _op value2->n_frames; + +GF_EXPORT +Bool gf_timecode_less(GF_TimeCode *value1, GF_TimeCode *value2) +{ + TIMECODE_COMPARE(<) +} + +GF_EXPORT +Bool gf_timecode_less_or_equal(GF_TimeCode *value1, GF_TimeCode *value2) +{ + TIMECODE_COMPARE(<=) +} + +GF_EXPORT +Bool gf_timecode_greater(GF_TimeCode *value1, GF_TimeCode *value2) +{ + TIMECODE_COMPARE(>) +} + +GF_EXPORT +Bool gf_timecode_greater_or_equal(GF_TimeCode *value1, GF_TimeCode *value2) +{ + TIMECODE_COMPARE(>=) +} + +GF_EXPORT +Bool gf_timecode_equal(GF_TimeCode *value1, GF_TimeCode *value2) +{ + TIMECODE_COMPARE(==) +} + +GF_EXPORT +u8 gf_cenc_key_info_get_iv_size(const u8 *key_info, u32 key_info_size, u32 idx, u8 *const_iv_size, const u8 **const_iv) +{ + u32 i=0, kpos=3; + if (const_iv_size) *const_iv_size = 0; + if (const_iv) *const_iv = NULL; + + if (!key_info || !key_info_size) + return 0; + + while (1) { + u8 civ_size=0; + const u8 *civ = NULL; + u8 iv_size = key_infokpos; + kpos += 17; + + if (!iv_size) { + if (kpos>key_info_size) + break; + civ_size = key_infokpos; + civ = key_info + kpos + 1; + kpos += 1 + civ_size; + } + + if (kpos>key_info_size) + break; + + if (i+1==idx) { + if (const_iv_size) *const_iv_size = civ_size; + if (const_iv) *const_iv = civ; + return iv_size; + } + i++; + if (kpos==key_info_size) + break; + } + return 0; +}
View file
gpac-2.4.0.tar.gz/src/utils/downloader.c -> gpac-26.02.0.tar.gz/src/utils/downloader.c
Changed
@@ -2,10 +2,10 @@ * GPAC Multimedia Framework * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2025 * All rights reserved * - * This file is part of GPAC / common tools sub-project + * This file is part of GPAC / downloader sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,272 +23,20 @@ * */ -#include <gpac/tools.h> -#ifndef GPAC_DISABLE_NETWORK -#include <gpac/download.h> -#include <gpac/network.h> -#include <gpac/token.h> -#include <gpac/thread.h> -#include <gpac/list.h> -#include <gpac/base_coding.h> -#include <gpac/cache.h> -#include <gpac/filters.h> - -#ifdef GPAC_HAS_SSL -#include <openssl/ssl.h> -#include <openssl/x509.h> -#include <openssl/x509v3.h> -#include <openssl/err.h> -#include <openssl/rand.h> -#endif - -#ifdef GPAC_HAS_HTTP2 -#if defined(_MSC_VER) -typedef SSIZE_T ssize_t; -#define NGHTTP2_STATICLIB -#else -#if defined(WIN32) && defined(GPAC_STATIC_BUILD) -#define NGHTTP2_STATICLIB -#endif -#endif -#include <nghttp2/nghttp2.h> - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) || defined (WIN32) -#pragma comment(lib, "nghttp2") -# endif -#endif - -#endif - - - -#ifdef __USE_POSIX -#include <unistd.h> -#endif - -#define SIZE_IN_STREAM ( 2 << 29 ) - - -#define SESSION_RETRY_COUNT 10 -#define SESSION_RETRY_SSL 8 - -#include <gpac/revision.h> -#define GF_DOWNLOAD_AGENT_NAME "GPAC/"GPAC_VERSION "-rev" GPAC_GIT_REVISION - -//let's be agressive with socket buffer size -#define GF_DOWNLOAD_BUFFER_SIZE 131072 -#define GF_DOWNLOAD_BUFFER_SIZE_LIMIT_RATE GF_DOWNLOAD_BUFFER_SIZE/20 - - -#ifdef GPAC_HAS_HTTP2 -#define HTTP2_BUFFER_SETTINGS_SIZE 128 - - -typedef struct -{ - u8 * data; - u32 size, alloc, offset; -} h2_reagg_buffer; +#include "downloader.h" -typedef struct -{ - Bool do_shutdown; - GF_List *sessions; - nghttp2_session *ng_sess; - - GF_DownloadSession *net_sess; - GF_Mutex *mx; - Bool copy; -} GF_H2_Session; - - -#endif - -static void gf_dm_data_received(GF_DownloadSession *sess, u8 *payload, u32 payload_size, Bool store_in_init, u32 *rewrite_size, u8 *original_payload); -static GF_Err gf_dm_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read); +#ifndef GPAC_DISABLE_NETWORK static void gf_dm_connect(GF_DownloadSession *sess); -GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size); -GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select); - -/*internal flags*/ -enum -{ - GF_DOWNLOAD_SESSION_USE_SSL = 1<<10, - GF_DOWNLOAD_SESSION_THREAD_DEAD = 1<<11, - GF_DOWNLOAD_SESSION_SSL_FORCED = 1<<12, -}; - -enum REQUEST_TYPE -{ - GET = 0, - HEAD = 1, - OTHER = 2 -}; - -/*!the structure used to store an HTTP header*/ -typedef struct -{ - char *name; - char *value; -} GF_HTTPHeader; - - -/** - * This structure handles partial downloads - */ -typedef struct __partialDownloadStruct { - char * url; - u64 startOffset; - u64 endOffset; - char * filename; -} GF_PartialDownload ; - -typedef struct -{ - struct __gf_download_session *sess; -} GF_SessTask; - -struct __gf_download_session -{ - /*this is always 0 and helps differenciating downloads from other interfaces (interfaceType != 0)*/ - u32 reserved; - - struct __gf_download_manager *dm; - GF_Thread *th; - GF_Mutex *mx; - GF_SessTask *ftask; - - Bool in_callback, destroy; - u32 proxy_enabled; - Bool allow_direct_reuse; - - char *server_name; - u16 port; - - char *orig_url; - char *orig_url_before_redirect; - char *remote_path; - GF_UserCredentials * creds; - char cookieGF_MAX_PATH; - DownloadedCacheEntry cache_entry; - Bool reused_cache_entry, from_cache_only; - - //mime type, only used when the session is not cached. - char *mime_type; - GF_List *headers; - - const char *netcap_id; - GF_Socket *sock; - u32 num_retry; - GF_NetIOStatus status; - - u32 flags; - u32 total_size, bytes_done, icy_metaint, icy_count, icy_bytes; - u64 start_time; - - u32 bytes_per_sec; - u64 start_time_utc; - Bool last_chunk_found; - Bool connection_close; - Bool is_range_continuation; - /*0: no cache reconfig before next GET request: 1: try to rematch the cache entry: 2: force to create a new cache entry (for byte-range cases)*/ - u32 needs_cache_reconfig; - /* Range information if needed for the download (cf flag) */ - Bool needs_range; - u64 range_start, range_end; - - u32 connect_time, ssl_setup_time, reply_time, total_time_since_req, req_hdr_size, rsp_hdr_size; - - /*0: GET - 1: HEAD - 2: all the rest - */ - enum REQUEST_TYPE http_read_type; - - GF_Err last_error; - char *init_data; - u32 init_data_size; - Bool server_only_understand_get; - /* True if cache file must be stored on disk */ - Bool use_cache_file; - Bool disable_cache; - /*forces notification of data exchange to be sent regardless of threading mode*/ - Bool force_data_write_callback; - u32 connect_pending; -#ifdef GPAC_HAS_SSL - SSL *ssl; -#endif - - void (*do_requests)(struct __gf_download_session *); - - /*callback for data reception - may not be NULL*/ - gf_dm_user_io user_proc; - void *usr_cbk; - Bool reassigned; - - Bool chunked; - u32 nb_left_in_chunk; - u32 current_chunk_size; - u64 current_chunk_start; - - u64 request_start_time, last_fetch_time; - /*private extension*/ - void *ext; - - char *remaining_data; - u32 remaining_data_size; - - u32 conn_timeout, request_timeout; - - Bool local_cache_only; - Bool server_mode; - //0: not PUT/POST, 1: waiting for body to be completed, 2: body done - u32 put_state; - - u64 last_cap_rate_time; - u64 last_cap_rate_bytes; - u32 last_cap_rate_bytes_per_sec; - - u64 last_chunk_start_time; - u32 chunk_wnd_dur; - u32 chunk_bytes, chunk_header_bytes, cumulated_chunk_header_bytes; - //in bytes per seconds - Double cumulated_chunk_rate; - - u32 connection_timeout_ms; - - u8 *async_req_reply; - u32 async_req_reply_size; - u8 *async_buf; - u32 async_buf_size, async_buf_alloc; - - GF_SockGroup *sock_group; -#ifdef GPAC_HAS_HTTP2 - //HTTP/2 session used by this download session. If not NULL, the mutex, socket and ssl context are moved along sessions - GF_H2_Session *h2_sess; - int32_t h2_stream_id; - h2_reagg_buffer h2_buf; - - nghttp2_data_provider data_io; - u8 *h2_send_data; - u32 h2_send_data_len; - - u8 *h2_local_buf; - u32 h2_local_buf_len, h2_local_buf_alloc; - - u8 *h2_upgrade_settings; - u32 h2_upgrade_settings_len; - u8 h2_headers_seen, h2_ready_to_send, h2_is_eos, h2_data_paused, h2_data_done, h2_switch_sess; - //0: upgrade not started; 1: upgrade headers send; 2: upgrade done; 3: upgrade retry without SSL APN; 4: H2 disabled for session - u8 h2_upgrade_state; -#endif -}; -static GF_Err dm_sess_write(GF_DownloadSession *sess, const u8 *buffer, u32 size); void dm_sess_sk_del(GF_DownloadSession *sess) { +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + curl_destroy(sess); + return; + } +#endif if (sess->sock) { GF_Socket *sock = sess->sock; sess->sock = NULL; @@ -301,1390 +49,24 @@ } } -struct __gf_download_manager -{ - GF_Mutex *cache_mx; - char *cache_directory; - - gf_dm_get_usr_pass get_user_password; - void *usr_cbk; - - GF_List *sessions; - Bool disable_cache, simulate_no_connection, allow_offline_cache, clean_cache; - u32 limit_data_rate, read_buf_size; - u64 max_cache_size; - Bool allow_broken_certificate; - - GF_List *skip_proxy_servers; - GF_List *credentials; - GF_List *cache_entries; - /* FIXME : should be placed in DownloadedCacheEntry maybe... */ - GF_List *partial_downloads; -#ifdef GPAC_HAS_SSL - SSL_CTX *ssl_ctx; -#endif - - GF_FilterSession *filter_session; - -#ifdef GPAC_HAS_HTTP2 - Bool disable_http2; -#endif - - Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool cache_destroy); - void *lc_udta; -}; - -#ifdef GPAC_HAS_SSL - -static void init_prng (void) -{ - char namebuf256; - const char *random_file; - - if (RAND_status ()) return; - - namebuf0 = '\0'; - random_file = RAND_file_name (namebuf, sizeof (namebuf)); - - if (random_file && *random_file) - RAND_load_file(random_file, 16384); - - if (RAND_status ()) return; -} - -#endif - -#if 0 -#define SET_LAST_ERR(_e) \ - {\ - GF_Err __e = _e;\ - if (__e==GF_IP_NETWORK_EMPTY) {\ - gf_assert(sess->status != GF_NETIO_STATE_ERROR); \ - }\ - sess->last_error = _e;\ - }\ - -#else - -#define SET_LAST_ERR(_e) sess->last_error = _e; - -#endif - -/*HTTP2 callbacks*/ -#ifdef GPAC_HAS_HTTP2 - -#ifdef GPAC_HAS_SSL -/* NPN TLS extension client callback. We check that server advertised - the HTTP/2 protocol the nghttp2 library supports. If not, exit - the program. */ -static int h2_select_next_proto_cb(SSL *ssl , unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg) -{ - if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 Server did not advertise " NGHTTP2_PROTO_VERSION_ID)); - } - return SSL_TLSEXT_ERR_OK; -} -#endif - -//detach session from HTTP2 session - the session mutex SHALL be grabbed before calling this -void h2_detach_session(GF_H2_Session *h2_sess, GF_DownloadSession *sess) -{ - if (!h2_sess || !sess) return; - gf_assert(sess->h2_sess == h2_sess); - gf_assert(sess->mx); - - gf_list_del_item(h2_sess->sessions, sess); - if (!gf_list_count(h2_sess->sessions)) { - dm_sess_sk_del(sess); - -#ifdef GPAC_HAS_SSL - if (sess->ssl) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("Downloader shut down SSL context\n")); - SSL_shutdown(sess->ssl); - SSL_free(sess->ssl); - sess->ssl = NULL; - } -#endif - //destroy h2 session - nghttp2_session_del(h2_sess->ng_sess); - gf_list_del(h2_sess->sessions); - gf_mx_v(h2_sess->mx); - gf_mx_del(sess->mx); - gf_free(h2_sess); - } else { - GF_DownloadSession *asess = gf_list_get(h2_sess->sessions, 0); - gf_assert(asess->h2_sess == h2_sess); - gf_assert(asess->sock); - - h2_sess->net_sess = asess; - //swap async buf if any to new active session - if (sess->async_buf_alloc) { - gf_assert(!asess->async_buf); - asess->async_buf = sess->async_buf; - asess->async_buf_alloc = sess->async_buf_alloc; - asess->async_buf_size = sess->async_buf_size; - sess->async_buf_alloc = sess->async_buf_size = 0; - sess->async_buf = NULL; - } - sess->sock = NULL; -#ifdef GPAC_HAS_SSL - sess->ssl = NULL; -#endif - gf_mx_v(sess->mx); - } - - if (sess->h2_buf.data) { - gf_free(sess->h2_buf.data); - memset(&sess->h2_buf, 0, sizeof(h2_reagg_buffer)); - } - sess->h2_sess = NULL; - sess->mx = NULL; -} - -static GF_Err h2_session_send(GF_DownloadSession *sess) -{ - gf_assert(sess->h2_sess); - int rv = nghttp2_session_send(sess->h2_sess->ng_sess); - if (rv != 0) { - if (sess->last_error != GF_IP_CONNECTION_CLOSED) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 session_send error : %s\n", nghttp2_strerror(rv))); - } - if (sess->status != GF_NETIO_STATE_ERROR) { - sess->status = GF_NETIO_STATE_ERROR; - if (rv==NGHTTP2_ERR_NOMEM) SET_LAST_ERR(GF_OUT_OF_MEM) - else SET_LAST_ERR(GF_SERVICE_ERROR) - } - return sess->last_error; - } -// GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 session_send OK\n")); - return GF_OK; -} - -static GF_DownloadSession *h2_get_session(void *user_data, s32 stream_id, Bool can_reassign) -{ - u32 i, nb_sess; - GF_DownloadSession *first_not_assigned = NULL; - GF_H2_Session *h2sess = (GF_H2_Session *)user_data; - - nb_sess = gf_list_count(h2sess->sessions); - for (i=0;i<nb_sess; i++) { - GF_DownloadSession *s = gf_list_get(h2sess->sessions, i); - if (s->h2_stream_id == stream_id) - return s; - - if (s->server_mode && !s->h2_stream_id && !first_not_assigned) { - first_not_assigned = s; - } - } - if (can_reassign && first_not_assigned) { - first_not_assigned->h2_stream_id = stream_id; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 reassigning old server session to new stream %d\n", stream_id)); - gf_assert(first_not_assigned->data_io.source.ptr); - first_not_assigned->total_size = first_not_assigned->bytes_done = 0; - first_not_assigned->status = GF_NETIO_CONNECTED; - first_not_assigned->h2_data_done = 0; - return first_not_assigned; - } - return NULL; -} - -static int h2_header_callback(nghttp2_session *session, - const nghttp2_frame *frame, const uint8_t *name, - size_t namelen, const uint8_t *value, - size_t valuelen, uint8_t flags , - void *user_data) -{ - GF_DownloadSession *sess; - - switch (frame->hd.type) { - case NGHTTP2_HEADERS: - sess = h2_get_session(user_data, frame->hd.stream_id, GF_FALSE); - if (!sess) - return NGHTTP2_ERR_CALLBACK_FAILURE; - if ( - (!sess->server_mode && (frame->headers.cat == NGHTTP2_HCAT_RESPONSE)) - || (sess->server_mode && ((frame->headers.cat == NGHTTP2_HCAT_HEADERS) || (frame->headers.cat == NGHTTP2_HCAT_REQUEST))) - ) { - GF_HTTPHeader *hdrp; - - GF_SAFEALLOC(hdrp, GF_HTTPHeader); - if (hdrp) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d got header %s: %s\n", sess->h2_stream_id, name, value)); - hdrp->name = gf_strdup(name); - hdrp->value = gf_strdup(value); - gf_list_add(sess->headers, hdrp); - } - break; - } - } - return 0; -} - -static int h2_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) -{ - GF_DownloadSession *sess = h2_get_session(user_data, frame->hd.stream_id, GF_TRUE); - if (!sess) { - GF_H2_Session *h2sess = (GF_H2_Session *)user_data; - GF_DownloadSession *par_sess = h2sess->net_sess; - if (!par_sess || !par_sess->server_mode) - return NGHTTP2_ERR_CALLBACK_FAILURE; - - - if (par_sess->user_proc) { - GF_NETIO_Parameter param; - memset(¶m, 0, sizeof(GF_NETIO_Parameter)); - param.msg_type = GF_NETIO_REQUEST_SESSION; - par_sess->in_callback = GF_TRUE; - param.sess = par_sess; - param.reply = frame->hd.stream_id; - par_sess->user_proc(par_sess->usr_cbk, ¶m); - par_sess->in_callback = GF_FALSE; - - if (param.error == GF_OK) { - sess = h2_get_session(user_data, frame->hd.stream_id, GF_FALSE); - } - } - - if (!sess) - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - switch (frame->hd.type) { - case NGHTTP2_HEADERS: - if (sess->server_mode) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d header callback\n", frame->hd.stream_id)); - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) header callback\n", frame->hd.stream_id, sess->remote_path)); - } - break; - } - return 0; -} - -static int h2_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) -{ - GF_DownloadSession *sess; - switch (frame->hd.type) { - case NGHTTP2_HEADERS: - sess = h2_get_session(user_data, frame->hd.stream_id, GF_FALSE); - if (!sess) - return NGHTTP2_ERR_CALLBACK_FAILURE; - - if ( - (!sess->server_mode && (frame->headers.cat == NGHTTP2_HCAT_RESPONSE)) - || (sess->server_mode && ((frame->headers.cat == NGHTTP2_HCAT_HEADERS) || (frame->headers.cat == NGHTTP2_HCAT_REQUEST))) - ) { - sess->h2_headers_seen = 1; - if (sess->server_mode) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 All headers received for stream ID %d\n", sess->h2_stream_id)); - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 All headers received for stream ID %d\n", sess->h2_stream_id)); - } - } - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) data done\n", frame->hd.stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); - sess->h2_data_done = 1; - sess->h2_send_data = NULL; - sess->h2_send_data_len = 0; - } - break; - case NGHTTP2_DATA: - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - sess = h2_get_session(user_data, frame->hd.stream_id, GF_FALSE); - //if no session with such ID this means we got all our bytes and considered the session done, do not throw an error - if (sess) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) data done\n", frame->hd.stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); - sess->h2_data_done = 1; - } - } - break; - case NGHTTP2_RST_STREAM: - sess = h2_get_session(user_data, frame->hd.stream_id, GF_FALSE); - // cancel from remote peer, signal if not done - if (sess && sess->server_mode && !sess->h2_is_eos) { - GF_NETIO_Parameter param; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) canceled\n", frame->hd.stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); - memset(¶m, 0, sizeof(GF_NETIO_Parameter)); - param.msg_type = GF_NETIO_CANCEL_STREAM; - gf_mx_p(sess->mx); - sess->in_callback = GF_TRUE; - param.sess = sess; - sess->user_proc(sess->usr_cbk, ¶m); - sess->in_callback = GF_FALSE; - gf_mx_v(sess->mx); - } - break; - } - - return 0; -} - -static int h2_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) -{ - GF_DownloadSession *sess = h2_get_session(user_data, stream_id, GF_FALSE); - if (!sess) - return NGHTTP2_ERR_CALLBACK_FAILURE; - - if (sess->h2_buf.size + len > sess->h2_buf.alloc) { - sess->h2_buf.alloc = sess->h2_buf.size + (u32) len; - sess->h2_buf.data = gf_realloc(sess->h2_buf.data, sizeof(u8) * sess->h2_buf.alloc); - if (!sess->h2_buf.data) return NGHTTP2_ERR_NOMEM; - } - memcpy(sess->h2_buf.data + sess->h2_buf.size, data, len); - sess->h2_buf.size += (u32) len; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d received %d bytes (%d/%d total) - flags %d\n", sess->h2_stream_id, len, sess->bytes_done, sess->total_size, flags)); - return 0; -} - -static int h2_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) -{ - Bool do_retry = GF_FALSE; - GF_DownloadSession *sess = h2_get_session(user_data, stream_id, GF_FALSE); - if (!sess) - return 0; - - gf_mx_p(sess->mx); - - if (error_code==NGHTTP2_REFUSED_STREAM) - do_retry = GF_TRUE; - else if (sess->h2_sess->do_shutdown && !sess->server_mode && !sess->bytes_done) - do_retry = GF_TRUE; - - if (do_retry) { - sess->h2_sess->do_shutdown = GF_TRUE; - sess->h2_switch_sess = GF_TRUE; - sess->status = GF_NETIO_SETUP; - SET_LAST_ERR(GF_OK) - gf_mx_v(sess->mx); - return 0; - } - - if (error_code) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) closed with error_code=%d\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url, error_code)); - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR(GF_IP_NETWORK_FAILURE) - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) closed\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); - //keep status in DATA_EXCHANGE as this frame might have been pushed while processing another session - } - //stream closed - sess->h2_stream_id = 0; - gf_mx_v(sess->mx); - return 0; -} - -static int h2_error_callback(nghttp2_session *session, const char *msg, size_t len, void *user_data) -{ - if (session) - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 error %s\n", msg)); - return 0; -} - -static ssize_t h2_write_data(GF_DownloadSession *sess, const uint8_t *data, size_t length) -{ - GF_Err e = dm_sess_write(sess, data, (u32) length); - switch (e) { - case GF_OK: - return length; - case GF_IP_NETWORK_EMPTY: - if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) - return length; - return NGHTTP2_ERR_WOULDBLOCK; - case GF_IP_CONNECTION_CLOSED: - SET_LAST_ERR(GF_IP_CONNECTION_CLOSED) - return NGHTTP2_ERR_EOF; - default: - break; - } - return NGHTTP2_ERR_CALLBACK_FAILURE; -} - -static ssize_t h2_send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) -{ - GF_H2_Session *h2sess = (GF_H2_Session *)user_data; - GF_DownloadSession *sess = h2sess->net_sess; - - return h2_write_data(sess, data, length); -} - -static int h2_before_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) -{ - GF_DownloadSession *sess = h2_get_session(user_data, frame->hd.stream_id, GF_FALSE); - if (!sess) - return 0; - sess->h2_ready_to_send = 1; - return 0; -} - - -static ssize_t h2_data_source_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data) -{ - GF_DownloadSession *sess = (GF_DownloadSession *) source->ptr; - - if (!sess->h2_send_data_len) { - sess->h2_send_data = NULL; - if (sess->h2_is_eos) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 send EOS for stream_id %d\n", sess->h2_stream_id)); - *data_flags = NGHTTP2_DATA_FLAG_EOF; - return 0; - } - sess->h2_data_paused = 1; - return NGHTTP2_ERR_DEFERRED; - } - - if (sess->h2_sess->copy) { - u32 copy = (sess->h2_send_data_len > length) ? (u32) length : sess->h2_send_data_len; - memcpy(buf, sess->h2_send_data, copy); - sess->h2_send_data += copy; - sess->h2_send_data_len -= copy; - return copy; - } - - *data_flags = NGHTTP2_DATA_FLAG_NO_COPY; - if (sess->h2_send_data_len > length) { - return length; - } - return sess->h2_send_data_len; -} - -static void h2_flush_send(GF_DownloadSession *sess, Bool flush_local_buf) -{ - char h2_flush1024; - u32 res; - - while (sess->h2_send_data) { - GF_Err e; - u32 nb_bytes = sess->h2_send_data_len; - - if (!sess->h2_local_buf_len || flush_local_buf) { - //read any frame pending from remote peer (window update and co) - e = gf_dm_read_data(sess, h2_flush, 1023, &res); - if ((e<0) && (e != GF_IP_NETWORK_EMPTY)) { - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR(sess->last_error) - break; - } - - h2_session_send(sess); - - //error or regular eos - if (!sess->h2_stream_id) - break; - if (sess->status==GF_NETIO_STATE_ERROR) - break; - } - - if (nb_bytes > sess->h2_send_data_len) continue; - if (!(sess->flags & GF_NETIO_SESSION_NO_BLOCK)) continue; - - if (flush_local_buf) return; - - //stream will now block, no choice but do a local copy... - if (sess->h2_local_buf_len + nb_bytes > sess->h2_local_buf_alloc) { - sess->h2_local_buf_alloc = sess->h2_local_buf_len + nb_bytes; - sess->h2_local_buf = gf_realloc(sess->h2_local_buf, sess->h2_local_buf_alloc); - if (!sess->h2_local_buf) { - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR(sess->last_error) - break; - } - } - memcpy(sess->h2_local_buf + sess->h2_local_buf_len, sess->h2_send_data, nb_bytes); - sess->h2_local_buf_len+=nb_bytes; - sess->h2_send_data = NULL; - sess->h2_send_data_len = 0; - } -} - -static char padding256; - -static int h2_send_data_callback(nghttp2_session *session, nghttp2_frame *frame, const uint8_t *framehd, size_t length, nghttp2_data_source *source, void *user_data) -{ - ssize_t rv; - GF_DownloadSession *sess = (GF_DownloadSession *) source->ptr; - - gf_assert(sess->h2_send_data_len); - gf_assert(sess->h2_send_data_len >= length); - - rv = h2_write_data(sess, (u8 *) framehd, 9); - if (rv<0) { - if (rv==NGHTTP2_ERR_WOULDBLOCK) return NGHTTP2_ERR_WOULDBLOCK; - goto err; - } - - while (frame->data.padlen > 0) { - u32 padlen = (u32) frame->data.padlen - 1; - rv = h2_write_data(sess, padding, padlen); - if (rv<0) { - if (rv==NGHTTP2_ERR_WOULDBLOCK) continue; - goto err; - } - break; - } - while (1) { - rv = h2_write_data(sess, (u8 *) sess->h2_send_data, length); - if (rv<0) { - if (rv==NGHTTP2_ERR_WOULDBLOCK) continue; - goto err; - } - break; - } - sess->h2_send_data += (u32) length; - sess->h2_send_data_len -= (u32) length; - return 0; -err: - - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR(sess->last_error) - return (int) rv; -} - -static void h2_initialize_session(GF_DownloadSession *sess) -{ - int rv; - nghttp2_settings_entry iv2 = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, - {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} - }; - char szMXName100; - nghttp2_session_callbacks *callbacks; - - nghttp2_session_callbacks_new(&callbacks); - nghttp2_session_callbacks_set_send_callback(callbacks, h2_send_callback); - nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, h2_frame_recv_callback); - nghttp2_session_callbacks_set_before_frame_send_callback(callbacks, h2_before_frame_send_callback); - - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, h2_data_chunk_recv_callback); - nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, h2_stream_close_callback); - nghttp2_session_callbacks_set_on_header_callback(callbacks, h2_header_callback); - nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, h2_begin_headers_callback); - nghttp2_session_callbacks_set_error_callback(callbacks, h2_error_callback); - - GF_SAFEALLOC(sess->h2_sess, GF_H2_Session) - sess->h2_sess->sessions = gf_list_new(); - sess->h2_sess->copy = gf_opts_get_bool("core", "h2-copy"); - if (!sess->h2_sess->copy) - nghttp2_session_callbacks_set_send_data_callback(callbacks, h2_send_data_callback); - - if (sess->server_mode) { - nghttp2_session_server_new(&sess->h2_sess->ng_sess, callbacks, sess->h2_sess); - } else { - nghttp2_session_client_new(&sess->h2_sess->ng_sess, callbacks, sess->h2_sess); - } - nghttp2_session_callbacks_del(callbacks); - sess->h2_sess->net_sess = sess; - gf_list_add(sess->h2_sess->sessions, sess); - - sprintf(szMXName, "http2_%p", sess->h2_sess); - sess->h2_sess->mx = gf_mx_new(szMXName); - sess->mx = sess->h2_sess->mx; - sess->chunked = GF_FALSE; - - sess->data_io.read_callback = h2_data_source_read_callback; - sess->data_io.source.ptr = sess; - - if (sess->server_mode) { - sess->h2_stream_id = 1; - if (sess->h2_upgrade_settings) { - rv = nghttp2_session_upgrade2(sess->h2_sess->ng_sess, sess->h2_upgrade_settings, sess->h2_upgrade_settings_len, 0, sess); - gf_free(sess->h2_upgrade_settings); - sess->h2_upgrade_settings = NULL; - - if (rv) { - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR( (rv==NGHTTP2_ERR_NOMEM) ? GF_OUT_OF_MEM : GF_REMOTE_SERVICE_ERROR) - return; - } - } - } - - /* client 24 bytes magic string will be sent by nghttp2 library */ - rv = nghttp2_submit_settings(sess->h2_sess->ng_sess, NGHTTP2_FLAG_NONE, iv, GF_ARRAY_LENGTH(iv)); - if (rv != 0) { - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR((rv==NGHTTP2_ERR_NOMEM) ? GF_OUT_OF_MEM : GF_SERVICE_ERROR) - return; - } - h2_session_send(sess); -} - - -#define NV_HDR(_hdr, _name, _value) { \ - _hdr.name = (uint8_t *)_name;\ - _hdr.value = (uint8_t *)_value;\ - _hdr.namelen = (u32) strlen(_name);\ - _hdr.valuelen = (u32) strlen(_value);\ - _hdr.flags = NGHTTP2_NV_FLAG_NONE;\ - } - -static GF_Err h2_submit_request(GF_DownloadSession *sess, char *req_name, const char *url, const char *param_string, Bool has_body) -{ - u32 nb_hdrs, i; - char *hostport = NULL; - char *path = NULL; - char port20; - nghttp2_nv *hdrs; - - nb_hdrs = gf_list_count(sess->headers); - hdrs = gf_malloc(sizeof(nghttp2_nv) * (nb_hdrs + 4)); - - NV_HDR(hdrs0, ":method", req_name); - NV_HDR(hdrs1, ":scheme", "https"); - - gf_dynstrcat(&hostport, sess->server_name, NULL); - sprintf(port, ":%d", sess->port); - gf_dynstrcat(&hostport, port, NULL); - NV_HDR(hdrs2, ":authority", hostport); - - if (param_string) { - gf_dynstrcat(&path, url, NULL); - if (strchr(sess->remote_path, '?')) { - gf_dynstrcat(&path, param_string, "&"); - } else { - gf_dynstrcat(&path, param_string, "?"); - } - NV_HDR(hdrs3, ":path", path); - } else { - NV_HDR(hdrs3, ":path", url); - } - - for (i=0; i<nb_hdrs; i++) { - GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); - NV_HDR(hdrs4+i, hdr->name, hdr->value); - } - if (has_body) { - gf_assert(sess->data_io.read_callback); - gf_assert(sess->data_io.source.ptr != NULL); - } - - sess->h2_data_done = 0; - sess->h2_headers_seen = 0; - sess->h2_stream_id = nghttp2_submit_request(sess->h2_sess->ng_sess, NULL, hdrs, nb_hdrs+4, has_body ? &sess->data_io : NULL, sess); - sess->h2_ready_to_send = 0; - -#ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_DEBUG)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 send request (has_body %d) for new stream_id %d:\n", has_body, sess->h2_stream_id)); - for (i=0; i<nb_hdrs+4; i++) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("\t%s: %s\n", hdrsi.name, hdrsi.value)); - } - } -#endif - - gf_free(hdrs); - gf_free(hostport); - if (path) gf_free(path); - - if (sess->h2_stream_id < 0) { - return GF_IP_NETWORK_FAILURE; - } - - return GF_OK; -} - - -static void h2_flush_data(GF_DownloadSession *sess, Bool store_in_init) -{ - gf_dm_data_received(sess, (u8 *) sess->h2_buf.data, sess->h2_buf.size, store_in_init, NULL, NULL); - sess->h2_buf.size = 0; -} - -static void h2_flush_data_ex(GF_DownloadSession *sess, u8 *obuffer, u32 size, u32 *nb_bytes) -{ - u32 copy, nb_b_pck; - u8 *data; - *nb_bytes = 0; - if (!sess->h2_buf.size) - return; - - gf_assert(sess->h2_buf.offset<=sess->h2_buf.size); - - nb_b_pck = sess->h2_buf.size - sess->h2_buf.offset; - if (nb_b_pck > size) - copy = size; - else - copy = nb_b_pck; - - data = sess->h2_buf.data + sess->h2_buf.offset; - memcpy(obuffer, data, copy); - *nb_bytes = copy; - gf_dm_data_received(sess, (u8 *) data, copy, GF_FALSE, NULL, NULL); - - if (copy < nb_b_pck) { - sess->h2_buf.offset += copy; - } else { - sess->h2_buf.size = sess->h2_buf.offset = 0; - } - gf_assert(sess->h2_buf.offset<=sess->h2_buf.size); -} - - -#endif - static void sess_connection_closed(GF_DownloadSession *sess) { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - sess->h2_sess->do_shutdown = GF_TRUE; - sess->h2_switch_sess = GF_TRUE; - } -#endif -} - -/* - * Private methods of cache - */ - -//Writes data to the cache. A call to gf_cache_open_write_cache should have been issued before calling this function. -GF_Err gf_cache_write_to_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, const char * data, const u32 size, GF_Mutex *mx); - -/** - * \brief Close the write file pointer of cache - * This function also flushes all buffers, so cache will always be consistent after -\param entry The entry to use -\param sess The download session -\param success 1 if cache write is success, false otherwise -\return GF_OK is everything went fine, GF_BAD_PARAM if entry is NULL, GF_IO_ERR if a failure occurs - */ -GF_Err gf_cache_close_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, Bool success); - -/** - * \brief Open the write file pointer of cache - * This function prepares calls for gf_cache_write_to_cache -\param entry The entry to use -\param sess The download session -\return GF_OK is everything went fine, GF_BAD_PARAM if entry is NULL, GF_IO_ERR if a failure occurs - */ -GF_Err gf_cache_open_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess ); - -/*modify end range when chaining byte-range requests*/ -void gf_cache_set_end_range(DownloadedCacheEntry entry, u64 range_end); - -/*returns 1 if cache is currently open for write*/ -Bool gf_cache_is_in_progress(const DownloadedCacheEntry entry); - -#include <gpac/crypt.h> - -static GF_Err gf_user_credentials_save_digest( GF_DownloadManager * dm, GF_UserCredentials * creds, const char * password, Bool store_info); - -/** - * Find a User's credentials for a given site - */ -GF_UserCredentials* gf_user_credentials_find_for_site(GF_DownloadManager *dm, const char *server_name, const char *user_name) -{ - GF_UserCredentials * cred; - u32 count, i; - if (!dm || !dm->credentials || !server_name || !strlen(server_name)) - return NULL; - count = gf_list_count( dm->credentials); - for (i = 0 ; i < count; i++) { - cred = (GF_UserCredentials*)gf_list_get(dm->credentials, i ); - if (!cred || strcmp(cred->site, server_name)) - continue; - - if (!user_name || !strcmp(user_name, cred->username)) - return cred; - } - -#ifndef GPAC_DISABLE_CRYPTO - char *key = (char*) gf_opts_get_key("credentials", server_name); - if (key) { - bin128 k; - const char *credk = gf_opts_get_key("core", "cred"); - if (credk) { - FILE *f = gf_fopen(credk, "r"); - if (f) { - gf_fread(k, 16, f); - gf_fclose(f); - } else { - credk = NULL; - } - } - char *sep = credk ? strrchr(key, ':') : NULL; - if (sep) { - char szP1024; - GF_SAFEALLOC(cred, GF_UserCredentials); - if (!cred) return NULL; - cred->dm = dm; - sep0 = 0; - strcpy(cred->username, key); - strcpy(szP, sep+1); - sep0 = ':'; - - GF_Crypt *gfc = gf_crypt_open(GF_AES_128, GF_CTR); - gf_crypt_init(gfc, k, NULL); - gf_crypt_decrypt(gfc, szP, (u32) strlen(szP)); - gf_crypt_close(gfc); - gf_user_credentials_save_digest(dm, cred, szP, GF_FALSE); - return cred; - } - } -#endif - return NULL; -} -/** - * \brief Saves the digest for authentication of password and username -\param dm The download manager -\param creds The credentials to fill -\return GF_OK if info has been filled, GF_BAD_PARAM if creds == NULL or dm == NULL, GF_AUTHENTICATION_FAILURE if user did not filled the info. - */ -static GF_Err gf_user_credentials_save_digest( GF_DownloadManager * dm, GF_UserCredentials * creds, const char * password, Bool store_info) { - int size; - char *pass_buf = NULL; - char range_buf1024; - if (!dm || !creds || !password) - return GF_BAD_PARAM; - gf_dynstrcat(&pass_buf, creds->username, NULL); - gf_dynstrcat(&pass_buf, password, ":"); - if (!pass_buf) return GF_OUT_OF_MEM; - size = gf_base64_encode(pass_buf, (u32) strlen(pass_buf), range_buf, 1024); - gf_free(pass_buf); - range_bufsize = 0; - strcpy(creds->digest, range_buf); - creds->valid = GF_TRUE; - -#ifndef GPAC_DISABLE_CRYPTO - if (store_info) { - u32 plen = (u32) strlen(password); - char *key = NULL; - char szPATHGF_MAX_PATH; - const char *cred = gf_opts_get_key("core", "cred"); - if (!cred) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Core No credential key defined, will not store password - use -cred=PATH_TO_KEY to specify it\n")); - return GF_OK; - } - - FILE *f = gf_fopen(cred, "r"); - bin128 k; - if (!f) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Core Failed to open credential key %s, will not store password\n", cred)); - return GF_OK; - } - u32 ksize = (u32) gf_fread(k, 16, f); - gf_fclose(f); - if (ksize!=16) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Core Invalid credential key %s: size %d but expecting 16 bytes, will not store password\n", cred, ksize)); - return GF_OK; - } - - GF_Crypt *gfc = gf_crypt_open(GF_AES_128, GF_CTR); - gf_crypt_init(gfc, k, NULL); - strcpy(szPATH, password); - gf_crypt_encrypt(gfc, szPATH, plen); - gf_crypt_close(gfc); - szPATHplen = 0; - gf_dynstrcat(&key, creds->username, NULL); - gf_dynstrcat(&key, szPATH, ":"); - gf_opts_set_key("credentials", creds->site, key); - gf_free(key); - gf_opts_save(); - } -#endif - return GF_OK; -} - - -static void on_user_pass(void *udta, const char *user, const char *pass, Bool store_info) -{ - GF_UserCredentials *creds = (GF_UserCredentials *)udta; - if (!creds) return; - u32 len = user ? (u32) strlen(user) : 0; - if (len && (user != creds->username)) { - if (len> 49) len = 49; - strncpy(creds->username, user, 49); - creds->usernamelen=0; - } - if (user && pass) { - GF_Err e = gf_user_credentials_save_digest((GF_DownloadManager *)creds->dm, creds, pass, store_info); - if (e != GF_OK) { - creds->valid = GF_FALSE; - } - } else { - creds->valid = GF_FALSE; - } - creds->req_state = GF_CREDS_STATE_DONE; -} - -/** - * \brief Asks the user for credentials for given site -\param dm The download manager -\param creds The credentials to fill -\return GF_OK if info has been filled, GF_BAD_PARAM if creds == NULL or dm == NULL, GF_AUTHENTICATION_FAILURE if user did not filled the info. - */ -static GF_Err gf_user_credentials_ask_password( GF_DownloadManager * dm, GF_UserCredentials * creds, Bool secure) -{ - char szPASS50; - if (!dm || !creds) - return GF_BAD_PARAM; - memset(szPASS, 0, 50); - if (!dm->get_user_password) return GF_AUTHENTICATION_FAILURE; - creds->req_state = GF_CREDS_STATE_PENDING; - if (!dm->get_user_password(dm->usr_cbk, secure, creds->site, creds->username, szPASS, on_user_pass, creds)) { - creds->req_state = GF_CREDS_STATE_NONE; - return GF_AUTHENTICATION_FAILURE; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + sess->hmux_sess->do_shutdown = GF_TRUE; + sess->hmux_switch_sess = GF_TRUE; } - return GF_OK; -} - -GF_UserCredentials * gf_user_credentials_register(GF_DownloadManager * dm, Bool secure, const char * server_name, const char * username, const char * password, Bool valid) -{ - GF_UserCredentials * creds; - if (!dm) - return NULL; - gf_assert( server_name ); - creds = gf_user_credentials_find_for_site(dm, server_name, username); - /* If none found, we create one */ - if (!creds) { - GF_SAFEALLOC(creds, GF_UserCredentials); - if (!creds) return NULL; - creds->dm = dm; - gf_list_insert(dm->credentials, creds, 0); - } - creds->valid = valid; - if (username) { - strncpy(creds->username, username, 49); - creds->username49 = 0; - } else { - creds->username0 = 0; - } - strncpy(creds->site, server_name, 1023); - creds->site1023 = 0; - if (username && password && valid) - gf_user_credentials_save_digest(dm, creds, password, GF_FALSE); - else { - if (GF_OK != gf_user_credentials_ask_password(dm, creds, secure)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, - ("HTTP Failed to get password information.\n")); - gf_list_del_item( dm->credentials, creds); - gf_free( creds ); - creds = NULL; - } - } - return creds; -} - -#ifdef GPAC_HAS_SSL - -static Bool _ssl_is_initialized = GF_FALSE; - -/*! - * initialize the SSL library once for all download managers -\return GF_FALSE if everyhing is OK, GF_TRUE otherwise - */ -Bool gf_ssl_init_lib() { - if (_ssl_is_initialized) - return GF_FALSE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPS Initializing SSL library...\n")); - init_prng(); - if (RAND_status() != 1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPS Error while initializing Random Number generator, failed to init SSL !\n")); - return GF_TRUE; - } - - /* per https://www.openssl.org/docs/man1.1.0/ssl/OPENSSL_init_ssl.html - ** As of version 1.1.0 OpenSSL will automatically allocate all resources that it needs so no explicit initialisation is required. - ** Similarly it will also automatically deinitialise as required. - */ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSL_library_init(); - SSL_load_error_strings(); - SSLeay_add_all_algorithms(); - SSLeay_add_ssl_algorithms(); #endif - - _ssl_is_initialized = GF_TRUE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPS Initalization of SSL library complete.\n")); - return GF_FALSE; } -#ifndef GPAC_DISABLE_LOG -static const char *tls_rt_get_name(int type) -{ - switch(type) { -#ifdef SSL3_RT_HEADER - case SSL3_RT_HEADER: return "TLS header"; -#endif - case SSL3_RT_CHANGE_CIPHER_SPEC: return "TLS change cipher"; - case SSL3_RT_ALERT: return "TLS alert"; - case SSL3_RT_HANDSHAKE: return "TLS handshake"; - case SSL3_RT_APPLICATION_DATA: return "TLS app data"; - default: return "TLS Unknown"; - } -} - -static const char *ssl_msg_get_name(int version, int msg) -{ -#ifdef SSL_CTRL_SET_MSG_CALLBACK - -#ifdef SSL2_VERSION_MAJOR - if (version == SSL2_VERSION_MAJOR) { - switch(msg) { - case SSL2_MT_ERROR: return "Error"; - case SSL2_MT_CLIENT_HELLO: return "Client hello"; - case SSL2_MT_CLIENT_MASTER_KEY: return "Client key"; - case SSL2_MT_CLIENT_FINISHED: return "Client finished"; - case SSL2_MT_SERVER_HELLO: return "Server hello"; - case SSL2_MT_SERVER_VERIFY: return "Server verify"; - case SSL2_MT_SERVER_FINISHED: return "Server finished"; - case SSL2_MT_REQUEST_CERTIFICATE: return "Request CERT"; - case SSL2_MT_CLIENT_CERTIFICATE: return "Client CERT"; - } - } else -#endif - if (version == SSL3_VERSION_MAJOR) { - switch(msg) { - case SSL3_MT_HELLO_REQUEST: return "Hello request"; - case SSL3_MT_CLIENT_HELLO: return "Client hello"; - case SSL3_MT_SERVER_HELLO: return "Server hello"; - #ifdef SSL3_MT_NEWSESSION_TICKET - case SSL3_MT_NEWSESSION_TICKET: return "Newsession Ticket"; - #endif - case SSL3_MT_CERTIFICATE: return "Certificate"; - case SSL3_MT_SERVER_KEY_EXCHANGE: return "Server key exchange"; - case SSL3_MT_CLIENT_KEY_EXCHANGE: return "Client key exchange"; - case SSL3_MT_CERTIFICATE_REQUEST: return "Request CERT"; - case SSL3_MT_SERVER_DONE: return "Server finished"; - case SSL3_MT_CERTIFICATE_VERIFY: return "CERT verify"; - case SSL3_MT_FINISHED: return "Finished"; -#ifdef SSL3_MT_CERTIFICATE_STATUS - case SSL3_MT_CERTIFICATE_STATUS: return "Certificate Status"; -#endif -#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS - case SSL3_MT_ENCRYPTED_EXTENSIONS: return "Encrypted Extensions"; -#endif -#ifdef SSL3_MT_SUPPLEMENTAL_DATA - case SSL3_MT_SUPPLEMENTAL_DATA: return "Supplemental data"; -#endif -#ifdef SSL3_MT_END_OF_EARLY_DATA - case SSL3_MT_END_OF_EARLY_DATA: return "End of early data"; -#endif -#ifdef SSL3_MT_KEY_UPDATE - case SSL3_MT_KEY_UPDATE: return "Key update"; -#endif -#ifdef SSL3_MT_NEXT_PROTO - case SSL3_MT_NEXT_PROTO: return "Next protocol"; -#endif -#ifdef SSL3_MT_MESSAGE_HASH - case SSL3_MT_MESSAGE_HASH: return "Message hash"; -#endif - } - } -#endif - return "Unknown"; -} - -static void ssl_on_log(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *udat) -{ - const char *msg_name, *tls_rt_name; - int msg_type; - char szUnknown32; - const char *szVersion = NULL; - - if (!version) return; -#ifdef SSL3_RT_INNER_CONTENT_TYPE - if (content_type == SSL3_RT_INNER_CONTENT_TYPE) return; -#endif - - switch (version) { -#ifdef SSL2_VERSION - case SSL2_VERSION: szVersion = "SSLv2"; break; -#endif -#ifdef SSL3_VERSION - case SSL3_VERSION: szVersion = "SSLv3"; break; -#endif - case TLS1_VERSION: szVersion = "TLSv1.0"; break; -#ifdef TLS1_1_VERSION - case TLS1_1_VERSION: szVersion = "TLSv1.1"; break; -#endif -#ifdef TLS1_2_VERSION - case TLS1_2_VERSION: szVersion = "TLSv1.2"; break; -#endif -#ifdef TLS1_3_VERSION - case TLS1_3_VERSION: szVersion = "TLSv1.3"; break; -#endif - case 0: break; - default: - snprintf(szUnknown, 31, "(%x)", version); - szVersion = szUnknown; - break; - } - version >>= 8; - if ((version == SSL3_VERSION_MAJOR) && content_type) - tls_rt_name = tls_rt_get_name(content_type); - else - tls_rt_name = ""; - - if (content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { - msg_type = *(char *)buf; - msg_name = "Change cipher spec"; - } else if (content_type == SSL3_RT_ALERT) { - msg_type = (((char *)buf)0 << 8) + ((char *)buf)1; - msg_name = SSL_alert_desc_string_long(msg_type); - } else { - msg_type = *(char *)buf; - msg_name = ssl_msg_get_name(version, msg_type); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("%s %s %s (%d)\n", szVersion, tls_rt_name, msg_name, msg_type)); -} -#endif //GPAC_DISABLE_LOG - -void *gf_dm_ssl_init(GF_DownloadManager *dm, u32 mode) -{ -#if OPENSSL_VERSION_NUMBER > 0x00909000 - const -#endif - SSL_METHOD *meth; - - if (!dm) return NULL; - - gf_mx_p(dm->cache_mx); - /* The SSL has already been initialized. */ - if (dm->ssl_ctx) { - gf_mx_v(dm->cache_mx); - return dm->ssl_ctx; - } - /* Init the PRNG. If that fails, bail out. */ - if (gf_ssl_init_lib()) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPS Failed to properly initialize SSL library\n")); - goto error; - } - - switch (mode) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L - case 0: - meth = SSLv23_client_method(); - break; -#if 0 /*SSL v2 is no longer supported in OpenSSL 1.0*/ - case 1: - meth = SSLv2_client_method(); - break; -#endif - case 2: - meth = SSLv3_client_method(); - break; - case 3: - meth = TLSv1_client_method(); - break; -#else /* for openssl 1.1+ this is the preferred method */ - case 0: - meth = TLS_client_method(); - break; -#endif - default: - goto error; - } - - dm->ssl_ctx = SSL_CTX_new(meth); - if (!dm->ssl_ctx) goto error; - SSL_CTX_set_options(dm->ssl_ctx, SSL_OP_ALL); -// SSL_CTX_set_max_proto_version(dm->ssl_ctx, 0); - - SSL_CTX_set_default_verify_paths(dm->ssl_ctx); - SSL_CTX_load_verify_locations (dm->ssl_ctx, NULL, NULL); - /* SSL_VERIFY_NONE instructs OpenSSL not to abort SSL_connect if the - certificate is invalid. We verify the certificate separately in - ssl_check_certificate, which provides much better diagnostics - than examining the error stack after a failed SSL_connect. */ - SSL_CTX_set_verify(dm->ssl_ctx, SSL_VERIFY_NONE, NULL); - -#ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG) ) { - SSL_CTX_set_msg_callback(dm->ssl_ctx, ssl_on_log); - } -#endif - - -#ifdef GPAC_HAS_HTTP2 - if (!dm->disable_http2) { - SSL_CTX_set_next_proto_select_cb(dm->ssl_ctx, h2_select_next_proto_cb, NULL); -#if OPENSSL_VERSION_NUMBER >= 0x10002000L - SSL_CTX_set_alpn_protos(dm->ssl_ctx, (const unsigned char *)"\x02h2", 3); -#endif - } -#endif - - /* Since fd_write unconditionally assumes partial writes (and handles them correctly), - allow them in OpenSSL. */ - SSL_CTX_set_mode(dm->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); - gf_mx_v(dm->cache_mx); - return dm->ssl_ctx; -error: - if (dm->ssl_ctx) SSL_CTX_free(dm->ssl_ctx); - dm->ssl_ctx = NULL; - gf_mx_v(dm->cache_mx); - return NULL; -} - -#ifdef GPAC_HAS_HTTP2 - -static unsigned char next_proto_list256; -static size_t next_proto_list_len; - -#ifndef OPENSSL_NO_NEXTPROTONEG -static int next_proto_cb(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg) -{ - if (data && len) { - *data = next_proto_list; - *len = (unsigned int)next_proto_list_len; - } - return SSL_TLSEXT_ERR_OK; -} -#endif //OPENSSL_NO_NEXTPROTONEG - -#if OPENSSL_VERSION_NUMBER >= 0x10002000L -static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) -{ - int rv = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); - if (rv != 1) { - return SSL_TLSEXT_ERR_NOACK; - } - return SSL_TLSEXT_ERR_OK; -} -#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ - -#endif - - -void *gf_ssl_server_context_new(const char *cert, const char *key) -{ - const SSL_METHOD *method; - SSL_CTX *ctx; - - method = SSLv23_server_method(); - - ctx = SSL_CTX_new(method); - if (!ctx) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unable to create SSL context\n")); - ERR_print_errors_fp(stderr); - return NULL; - } - SSL_CTX_set_ecdh_auto(ctx, 1); - if (SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM) <= 0) { - ERR_print_errors_fp(stderr); - SSL_CTX_free(ctx); - return NULL; - } - if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0 ) { - ERR_print_errors_fp(stderr); - SSL_CTX_free(ctx); - return NULL; - } - -#ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG) ) { - SSL_CTX_set_msg_callback(ctx, ssl_on_log); - } -#endif - - -#ifdef GPAC_HAS_HTTP2 - if (!gf_opts_get_bool("core", "no-h2")) { - next_proto_list0 = NGHTTP2_PROTO_VERSION_ID_LEN; - memcpy(&next_proto_list1, NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN); - next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; - - SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, NULL); -#ifdef GPAC_ENABLE_COVERAGE - if (gf_sys_is_cov_mode()) { - next_proto_cb(NULL, NULL, NULL, NULL); - h2_error_callback(NULL, NULL, 0, NULL); - on_user_pass(NULL, NULL, NULL, GF_FALSE); - } -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x10002000L - SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL); -#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L - } - -#endif - - return ctx; -} - -void gf_ssl_server_context_del(void *ssl_ctx) -{ - SSL_CTX_free(ssl_ctx); -} -void *gf_ssl_new(void *ssl_ctx, GF_Socket *client_sock, GF_Err *e) -{ - SSL *ssl; - ssl = SSL_new(ssl_ctx); - if (!ssl) { - *e = GF_IO_ERR; - return NULL; - } - SSL_set_fd(ssl, gf_sk_get_handle(client_sock) ); - if (SSL_accept(ssl) <= 0) { - if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG)) { - //this one crashes /exits on windows, only use if debug level - ERR_print_errors_fp(stderr); - } else { - ERR_print_errors_fp(stderr); - GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("SSL Accept failure\n")); - } - SSL_shutdown(ssl); - SSL_free(ssl); - *e = GF_AUTHENTICATION_FAILURE; - return NULL; - } - *e = GF_OK; - return ssl; -} -void gf_ssl_del(void *ssl) -{ - if (ssl) SSL_free(ssl); -} - -static GF_Err gf_ssl_write(GF_DownloadSession *sess, const u8 *buffer, u32 size, u32 *written) -{ - u32 idx=0; - s32 nb_tls_blocks = size/16000; - *written = 0; - while (nb_tls_blocks>=0) { - u32 len, to_write = 16000; - if (nb_tls_blocks==0) - to_write = size - idx*16000; - - len = SSL_write(sess->ssl, buffer + idx*16000, to_write); - nb_tls_blocks--; - idx++; - - if (len != to_write) { - if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) { - int err = SSL_get_error(sess->ssl, len); - if ((err==SSL_ERROR_WANT_READ) || (err==SSL_ERROR_WANT_WRITE)) { - return GF_IP_NETWORK_EMPTY; - } - if (err==SSL_ERROR_SSL) { - char msg1024; - SSL_load_error_strings(); - ERR_error_string_n(ERR_get_error(), msg, 1023); - msg1023=0; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot send, error %s\n", msg)); - } - } - return GF_IP_NETWORK_FAILURE; - } - *written += to_write; - } - return GF_OK; -} - - -#endif /* GPAC_HAS_SSL */ - - -static GF_Err dm_sess_write(GF_DownloadSession *session, const u8 *buffer, u32 size) +GF_Err dm_sess_write(GF_DownloadSession *session, const u8 *buffer, u32 size) { GF_Err e; u32 written=0; GF_DownloadSession *par_sess = session; -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX //if h2 we aggregate pending frames on the session currently holding the HTTP2 session - if (session->h2_sess) par_sess = session->h2_sess->net_sess; + if (session->hmux_sess) par_sess = session->hmux_sess->net_sess; #endif //we are writing and we already have pending data, append and try to flush after @@ -1694,6 +76,8 @@ #ifdef GPAC_HAS_SSL if (session->ssl) { e = gf_ssl_write(session, buffer, size, &written); + if (e==GF_IP_NETWORK_FAILURE) + e = GF_IP_CONNECTION_CLOSED; } else #endif { @@ -1738,273 +122,31 @@ return GF_FALSE; } -static Bool gf_dm_can_handle_url(GF_DownloadManager *dm, const char *url) +Bool gf_dm_can_handle_url(const char *url) { if (!strnicmp(url, "http://", 7)) return GF_TRUE; #ifdef GPAC_HAS_SSL if (!strnicmp(url, "https://", 8)) return GF_TRUE; #endif - return GF_FALSE; -} - -/*! - * Finds an existing entry in the cache for a given URL -\param sess The session configured with the URL -\return NULL if none found, the DownloadedCacheEntry otherwise - */ -DownloadedCacheEntry gf_dm_find_cached_entry_by_url(GF_DownloadSession * sess) -{ - u32 i, count; - gf_assert( sess && sess->dm && sess->dm->cache_entries ); - gf_mx_p( sess->dm->cache_mx ); - count = gf_list_count(sess->dm->cache_entries); - for (i = 0 ; i < count; i++) { - const char * url; - DownloadedCacheEntry e = (DownloadedCacheEntry)gf_list_get(sess->dm->cache_entries, i); - gf_assert(e); - url = gf_cache_get_url(e); - gf_assert( url ); - if (strcmp(url, sess->orig_url)) continue; - - if (! sess->is_range_continuation) { - if (sess->range_start != gf_cache_get_start_range(e)) continue; - if (sess->range_end != gf_cache_get_end_range(e)) continue; - } - /*OK that's ours*/ - gf_mx_v( sess->dm->cache_mx ); - return e; - } - gf_mx_v( sess->dm->cache_mx ); - return NULL; -} - -/** - * Creates a new cache entry - */ -DownloadedCacheEntry gf_cache_create_entry( GF_DownloadManager * dm, const char * cache_directory, const char * url, u64 start_range, u64 end_range, Bool mem_storage, GF_Mutex *mx); - -/*! - * Removes a session for a DownloadedCacheEntry -\param entry The entry -\param sess The session to remove -\return the number of sessions left in the cached entry, -1 if one of the parameters is wrong - */ -s32 gf_cache_remove_session_from_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess); - -Bool gf_cache_set_mime(const DownloadedCacheEntry entry, const char *mime); -Bool gf_cache_set_range(const DownloadedCacheEntry entry, u64 size, u64 start_range, u64 end_range); -Bool gf_cache_set_content(const DownloadedCacheEntry entry, GF_Blob *blob, Bool copy, GF_Mutex *mx); -Bool gf_cache_set_headers(const DownloadedCacheEntry entry, const char *headers); -void gf_cache_set_downtime(const DownloadedCacheEntry entry, u32 download_time_ms); - - -/** - * Removes a cache entry from cache and performs a cleanup if possible. - * If the cache entry is marked for deletion and has no sessions associated with it, it will be - * removed (so some modules using a streaming like cache will still work). - */ -static void gf_dm_remove_cache_entry_from_session(GF_DownloadSession * sess) { - if (sess && sess->cache_entry) { - gf_cache_remove_session_from_cache_entry(sess->cache_entry, sess); - if (sess->dm - /*JLF - not sure what the rationale of this test is, and it prevents cleanup of cache entry - which then results to crash when restarting the session (entry->writeFilePtr is not set back to NULL)*/ - && gf_cache_entry_is_delete_files_when_deleted(sess->cache_entry) - - && (0 == gf_cache_get_sessions_count_for_cache_entry(sess->cache_entry))) - { - u32 i, count; - gf_mx_p( sess->dm->cache_mx ); - count = gf_list_count( sess->dm->cache_entries ); - for (i = 0; i < count; i++) { - DownloadedCacheEntry ex = (DownloadedCacheEntry)gf_list_get(sess->dm->cache_entries, i); - if (ex == sess->cache_entry) { - gf_list_rem(sess->dm->cache_entries, i); - gf_cache_delete_entry( sess->cache_entry ); - sess->cache_entry = NULL; - break; - } - } - gf_mx_v( sess->dm->cache_mx ); - } - } -} - -/*! - * Adds a session to a DownloadedCacheEntry. - * implemented in cache.c -\param entry The entry -\param sess The session to add -\return the number of sessions in the cached entry, -1 if one of the parameters is wrong - */ -s32 gf_cache_add_session_to_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess); -Bool gf_cache_entry_persistent(const DownloadedCacheEntry entry); -void gf_cache_entry_set_persistent(const DownloadedCacheEntry entry); - -static void gf_dm_sess_notify_state(GF_DownloadSession *sess, GF_NetIOStatus dnload_status, GF_Err error); - -static void gf_dm_configure_cache(GF_DownloadSession *sess) -{ - DownloadedCacheEntry entry; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("Downloader gf_dm_configure_cache(%p), cached=%s URL=%s\n", sess, (sess->flags & GF_NETIO_SESSION_NOT_CACHED) ? "no" : "yes", sess->orig_url )); - gf_dm_remove_cache_entry_from_session(sess); - //session is not cached and we don't cache the first URL - if ((sess->flags & GF_NETIO_SESSION_NOT_CACHED) && !(sess->flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE)) { - sess->reused_cache_entry = GF_FALSE; - if (sess->cache_entry) - gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE); - - sess->cache_entry = NULL; - } else { - Bool found = GF_FALSE; - u32 i, count; - entry = gf_dm_find_cached_entry_by_url(sess); - if (!entry) { - if (sess->local_cache_only) { - sess->cache_entry = NULL; - SET_LAST_ERR(GF_URL_ERROR) - return; - } - /* We found the existing session */ - if (sess->cache_entry) { - Bool delete_cache = GF_TRUE; - - if (sess->flags & GF_NETIO_SESSION_KEEP_CACHE) { - delete_cache = GF_FALSE; - } - if (gf_cache_entry_persistent(sess->cache_entry)) - delete_cache = GF_FALSE; - /*! indicate we can destroy file upon destruction, except if disabled at session level*/ - if (delete_cache) - gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry); - - if (!gf_cache_entry_persistent(sess->cache_entry) && !gf_cache_get_sessions_count_for_cache_entry(sess->cache_entry)) { - gf_mx_p( sess->dm->cache_mx ); - /* No session attached anymore... we can delete it */ - gf_list_del_item(sess->dm->cache_entries, sess->cache_entry); - gf_mx_v( sess->dm->cache_mx ); - gf_cache_delete_entry(sess->cache_entry); - } - sess->cache_entry = NULL; - } - entry = gf_cache_create_entry(sess->dm, sess->dm->cache_directory, sess->orig_url, sess->range_start, sess->range_end, (sess->flags&GF_NETIO_SESSION_MEMORY_CACHE) ? GF_TRUE : GF_FALSE, sess->dm->cache_mx); - if (!entry) { - SET_LAST_ERR(GF_OUT_OF_MEM) - return; - } - gf_mx_p( sess->dm->cache_mx ); - gf_list_add(sess->dm->cache_entries, entry); - gf_mx_v( sess->dm->cache_mx ); - sess->is_range_continuation = GF_FALSE; - } - sess->cache_entry = entry; - sess->reused_cache_entry = gf_cache_is_in_progress(entry); - count = gf_list_count(sess->dm->sessions); - for (i=0; i<count; i++) { - GF_DownloadSession *a_sess = (GF_DownloadSession*)gf_list_get(sess->dm->sessions, i); - gf_assert(a_sess); - if (a_sess==sess) continue; - if (a_sess->cache_entry==entry) { - found = GF_TRUE; - break; - } - } - if (!found) { - sess->reused_cache_entry = GF_FALSE; - if (sess->cache_entry) - gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE); - } - gf_cache_add_session_to_cache_entry(sess->cache_entry, sess); - if (sess->needs_range) - gf_cache_set_range(sess->cache_entry, 0, sess->range_start, sess->range_end); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("CACHE Cache setup to %p %s\n", sess, gf_cache_get_cache_filename(sess->cache_entry))); - - if (sess->cache_entry) { - if (sess->flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE) { - sess->flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE; - gf_cache_entry_set_persistent(sess->cache_entry); - } - if ((sess->flags & GF_NETIO_SESSION_MEMORY_CACHE) && (sess->flags & GF_NETIO_SESSION_KEEP_CACHE) ) { - gf_cache_entry_set_persistent(sess->cache_entry); - } - } - - if ( (sess->allow_direct_reuse || sess->dm->allow_offline_cache) && !gf_cache_check_if_cache_file_is_corrupted(sess->cache_entry) - ) { - sess->from_cache_only = GF_TRUE; - sess->connect_time = 0; - sess->status = GF_NETIO_CONNECTED; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP using existing cache entry\n")); - gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); - } - } +#ifdef GPAC_HAS_CURL + if (curl_can_handle_url(url)) return GF_TRUE; +#endif + return GF_FALSE; } -void gf_dm_delete_cached_file_entry(const GF_DownloadManager * dm, const char * url) -{ - GF_Err e; - u32 count, i; - char * realURL; - GF_URL_Info info; - if (!url || !dm) - return; - gf_mx_p( dm->cache_mx ); - gf_dm_url_info_init(&info); - e = gf_dm_get_url_info(url, &info, NULL); - if (e != GF_OK) { - gf_mx_v( dm->cache_mx ); - gf_dm_url_info_del(&info); - return; - } - realURL = gf_strdup(info.canonicalRepresentation); - gf_dm_url_info_del(&info); - gf_assert( realURL ); - count = gf_list_count(dm->cache_entries); - for (i = 0 ; i < count; i++) { - const char * e_url; - DownloadedCacheEntry cache_ent = (DownloadedCacheEntry)gf_list_get(dm->cache_entries, i); - gf_assert(cache_ent); - e_url = gf_cache_get_url(cache_ent); - gf_assert( e_url ); - if (!strcmp(e_url, realURL)) { - /* We found the existing session */ - gf_cache_entry_set_delete_files_when_deleted(cache_ent); - if (0 == gf_cache_get_sessions_count_for_cache_entry( cache_ent )) { - /* No session attached anymore... we can delete it */ - gf_list_rem(dm->cache_entries, i); - gf_cache_delete_entry(cache_ent); - } - /* If deleted or not, we don't search further */ - gf_mx_v( dm->cache_mx ); - gf_free(realURL); - return; - } - } - /* If we are heren it means we did not found this URL in cache */ - gf_mx_v( dm->cache_mx ); - gf_free(realURL); - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CACHE Cannot find URL %s, cache file won't be deleted.\n", url)); -} - -GF_EXPORT -void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url, Bool force) -{ - if (sess && sess->dm && url) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("CACHE Requesting deletion for %s\n", url)); - gf_dm_delete_cached_file_entry(sess->dm, url); - if (sess->local_cache_only && sess->dm->local_cache_url_provider_cbk) - sess->dm->local_cache_url_provider_cbk(sess->dm->lc_udta, (char *) url, GF_TRUE); - } -} void gf_dm_sess_set_header_ex(GF_DownloadSession *sess, const char *name, const char *value, Bool allow_overwrite) { GF_HTTPHeader *hdr; if (!sess) return; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess #ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess || sess->h2_upgrade_settings) { + || sess->h2_upgrade_settings +#endif + ) { if (!stricmp(name, "Transfer-Encoding")) return; if (!stricmp(name, "Connection")) return; @@ -2053,80 +195,17 @@ count = gf_list_count(sess->headers); #ifdef GPAC_HAS_HTTP2 - if (sess->h2_upgrade_settings) { - u32 len; - gf_assert(!sess->h2_sess); - gf_dynstrcat(&rsp_buf, "HTTP/1.1 101 Switching Protocols\r\n" - "Connection: Upgrade\r\n" - "Upgrade: h2c\r\n\r\n", NULL); - - - len = (u32) strlen(rsp_buf); - e = dm_sess_write(sess, rsp_buf, len); - - gf_free(rsp_buf); - rsp_buf = NULL; - - h2_initialize_session(sess); - } - - if (sess->h2_sess) { - nghttp2_nv *hdrs; - - if (response_body) { - no_body = GF_FALSE; - sess->h2_send_data = (u8 *) response_body; - sess->h2_send_data_len = body_len; - sess->h2_is_eos = 1; - } else if (!no_body) { - switch (reply_code) { - case 200: - case 206: - no_body = GF_FALSE; - sess->h2_is_eos = 0; - break; - default: - no_body = GF_TRUE; - break; - } - } - - hdrs = gf_malloc(sizeof(nghttp2_nv) * (count + 1) ); - - sprintf(szFmt, "%d", reply_code); - NV_HDR(hdrs0, ":status", szFmt); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP/2 send reply for stream_id %d (body %d) headers:\n:status: %s\n", sess->h2_stream_id, !no_body, szFmt)); - for (i=0; i<count; i++) { - GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); - NV_HDR(hdrsi+1, hdr->name, hdr->value) - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s: %s\n", hdr->name, hdr->value)); - } - - - gf_mx_p(sess->mx); - - int rv = nghttp2_submit_response(sess->h2_sess->ng_sess, sess->h2_stream_id, hdrs, count+1, no_body ? NULL : &sess->data_io); - - gf_free(hdrs); - - if (rv != 0) { - gf_mx_v(sess->mx); - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 Failed to submit reply: %s\n", nghttp2_strerror(rv))); - return GF_SERVICE_ERROR; - } - h2_session_send(sess); - //in case we have a body already setup with this reply - h2_flush_send(sess, GF_FALSE); - - gf_mx_v(sess->mx); + e = http2_check_upgrade(sess); + if (e) return e; +#endif - //h2_stream_id may still be 0 at this point (typically reply to PUT/POST) - return GF_OK; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + return sess->hmux_sess->send_reply(sess, reply_code, response_body, body_len, no_body); } #endif - sprintf(szFmt, "HTTP/1.1 %d ", reply_code); gf_dynstrcat(&rsp_buf, szFmt, NULL); switch (reply_code) { @@ -2144,6 +223,7 @@ case 206: gf_dynstrcat(&rsp_buf, "Partial Content", NULL); break; case 200: gf_dynstrcat(&rsp_buf, "OK", NULL); break; case 201: gf_dynstrcat(&rsp_buf, "Created", NULL); break; + case 101: gf_dynstrcat(&rsp_buf, "Switching Protocols", NULL); break; default: gf_dynstrcat(&rsp_buf, "ERROR", NULL); break; } @@ -2160,7 +240,7 @@ gf_dynstrcat(&rsp_buf, "\r\n", NULL); if (!rsp_buf) return GF_OUT_OF_MEM; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP send reply for %s:\n%s\n", sess->orig_url, rsp_buf)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s send reply for %s:\n%s\n", sess->log_name, sess->orig_url, rsp_buf)); count = (u32) strlen(rsp_buf); if (response_body) { @@ -2189,26 +269,27 @@ } } -typedef enum -{ - HTTP_NO_CLOSE=0, - HTTP_CLOSE, - HTTP_RESET_CONN, -} HTTPCloseType; -static void gf_dm_disconnect(GF_DownloadSession *sess, HTTPCloseType close_type) +void gf_dm_disconnect(GF_DownloadSession *sess, HTTPCloseType close_type) { gf_assert( sess ); if (sess->connection_close) close_type = HTTP_RESET_CONN; sess->connection_close = GF_FALSE; sess->remaining_data_size = 0; - sess->start_time = 0; if (sess->async_req_reply) gf_free(sess->async_req_reply); sess->async_req_reply = NULL; sess->async_req_reply_size = 0; sess->async_buf_size = 0; + if (sess->cached_file) { + gf_fclose(sess->cached_file); + sess->cached_file = NULL; + } - if (sess->status >= GF_NETIO_DISCONNECTED) { + if ((sess->status >= GF_NETIO_DISCONNECTED) +#ifdef GPAC_HAS_CURL + && !sess->curl_hnd +#endif + ) { if (close_type && sess->use_cache_file && sess->cache_entry) { gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE); } @@ -2220,20 +301,28 @@ if (!sess->server_mode) { Bool do_close = (close_type || !(sess->flags & GF_NETIO_SESSION_PERSISTENT)) ? GF_TRUE : GF_FALSE; -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { do_close = (close_type==HTTP_RESET_CONN) ? GF_TRUE : GF_FALSE; } //if H2 stream is still valid, issue a reset stream - if (sess->h2_sess && sess->h2_stream_id) - nghttp2_submit_rst_stream(sess->h2_sess->ng_sess, NGHTTP2_FLAG_NONE, sess->h2_stream_id, NGHTTP2_NO_ERROR); + if (sess->hmux_sess && (sess->hmux_stream_id>=0) && (sess->put_state!=2)) + sess->hmux_sess->stream_reset(sess, GF_FALSE); +#endif + +#ifdef GPAC_HAS_CURL + //always remove curl session, let multi-connection manager handle disconnect/reconnect + if (sess->curl_hnd) { + sess->local_buf_len = 0; + dm_sess_sk_del(sess); + } else #endif if (do_close) { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - sess->h2_sess->do_shutdown = GF_TRUE; - h2_detach_session(sess->h2_sess, sess); +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + sess->hmux_sess->do_shutdown = GF_TRUE; + hmux_detach_session(sess->hmux_sess, sess); } #endif @@ -2267,9 +356,10 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("Downloader Destroy session URL %s\n", sess->orig_url)); /*self-destruction, let the download manager destroy us*/ - if ((sess->th || sess->ftask) && sess->in_callback) { + if (sess->th || sess->ftask) { sess->destroy = GF_TRUE; - return; + if (sess->ftask->in_task) + return; } gf_dm_disconnect(sess, HTTP_CLOSE); gf_dm_sess_clear_headers(sess); @@ -2285,12 +375,11 @@ if (sess->dm) { gf_mx_p(sess->dm->cache_mx); - gf_list_del_item(sess->dm->sessions, sess); + gf_list_del_item(sess->dm->all_sessions, sess); gf_mx_v(sess->dm->cache_mx); } - gf_dm_remove_cache_entry_from_session(sess); - sess->cache_entry = NULL; + gf_cache_remove_entry_from_session(sess); if (sess->orig_url) gf_free(sess->orig_url); if (sess->orig_url_before_redirect) gf_free(sess->orig_url_before_redirect); if (sess->server_name) gf_free(sess->server_name); @@ -2305,18 +394,23 @@ sess->orig_url = sess->server_name = sess->remote_path; sess->creds = NULL; -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + sess->hmux_sess->setup_session(sess, GF_TRUE); gf_mx_p(sess->mx); - h2_detach_session(sess->h2_sess, sess); + hmux_detach_session(sess->hmux_sess, sess); gf_mx_v(sess->mx); } +#endif +#ifdef GPAC_HAS_HTTP2 if (sess->h2_upgrade_settings) gf_free(sess->h2_upgrade_settings); +#endif - if (sess->h2_local_buf) - gf_free(sess->h2_local_buf); +#if defined(GPAC_HTTPMUX) || defined(GPAC_HAS_CURL) + if (sess->local_buf) + gf_free(sess->local_buf); #endif //free this once we have reassigned the session @@ -2335,31 +429,39 @@ gf_list_del(sess->headers); +#ifdef GPAC_HAS_CURL + gf_assert(!sess->curl_hnd); + if (sess->curl_hdrs) curl_slist_free_all(sess->curl_hdrs); +#endif + +#ifndef GPAC_DISABLE_LOG + if (sess->log_name) gf_free(sess->log_name); +#endif + assert(!sess->ftask || !sess->ftask->in_task || !sess->mx); gf_mx_del(sess->mx); + if (sess->http_buf) gf_free(sess->http_buf); if (sess->ftask) { sess->ftask->sess = NULL; sess->ftask = NULL; } + gf_free(sess); } -void http_do_requests(GF_DownloadSession *sess); - -static void gf_dm_sess_notify_state(GF_DownloadSession *sess, GF_NetIOStatus dnload_status, GF_Err error) +void gf_dm_sess_notify_state(GF_DownloadSession *sess, GF_NetIOStatus dnload_status, GF_Err error) { - if (sess->user_proc) { - GF_NETIO_Parameter par; - sess->in_callback = GF_TRUE; - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.msg_type = dnload_status; - par.error = error; - par.sess = sess; - par.reply = 200; - sess->user_proc(sess->usr_cbk, &par); - sess->in_callback = GF_FALSE; - } + if (!sess->user_proc) return; + GF_NETIO_Parameter par; + sess->in_callback = GF_TRUE; + memset(&par, 0, sizeof(GF_NETIO_Parameter)); + par.msg_type = dnload_status; + par.error = error; + par.sess = sess; + par.reply = 200; + sess->user_proc(sess->usr_cbk, &par); + sess->in_callback = GF_FALSE; } static void gf_dm_sess_user_io(GF_DownloadSession *sess, GF_NETIO_Parameter *par) @@ -2372,19 +474,6 @@ } } -#if 0 //unused -/*! -\brief is download manager thread dead? - * - *Indicates whether the thread has ended -\param sess the download session - */ -Bool gf_dm_is_thread_dead(GF_DownloadSession *sess) -{ - if (!sess) return GF_TRUE; - return (sess->flags & GF_DOWNLOAD_SESSION_THREAD_DEAD) ? GF_TRUE : GF_FALSE; -} -#endif GF_EXPORT GF_Err gf_dm_sess_last_error(GF_DownloadSession *sess) @@ -2404,6 +493,8 @@ void gf_dm_url_info_del(GF_URL_Info * info) { if (!info) return; + if (info->protocol) + gf_free(info->protocol); if (info->canonicalRepresentation) gf_free(info->canonicalRepresentation); if (info->password) @@ -2428,7 +519,7 @@ gf_assert(url); if (!strnicmp(url, "http://", 7)) { info->port = 80; - info->protocol = "http://"; + info->protocol = gf_strdup("http"); return 7; } else if (!strnicmp(url, "https://", 8)) { @@ -2436,13 +527,15 @@ #ifndef GPAC_HAS_SSL return -1; #endif - info->protocol = "https://"; + info->protocol = gf_strdup("https"); return 8; } - else if (!strnicmp(url, "ftp://", 6)) { - info->port = 21; - info->protocol = "ftp://"; - return -1; + char *sep = strstr(url, "://"); + if (sep) { + sep0 = 0; + info->protocol = gf_strdup(url); + sep0 = ':'; + return 3 + (u32) (sep-url); } return -1; } @@ -2452,20 +545,25 @@ char *tmp, *tmp_url, *current_pos, *urlConcatenateWithBaseURL, *ipv6; char * copyOfUrl; s32 proto_offset; + u32 default_port; gf_dm_url_info_del(info); urlConcatenateWithBaseURL = NULL; proto_offset = gf_dm_parse_protocol(url, info); + default_port = info->port; + if (proto_offset > 0) { url += proto_offset; } else { /*relative URL*/ if (!strstr(url, "://")) { u32 i; - info->protocol = "file://"; + gf_dm_url_info_del(info); + gf_dm_url_info_init(info); if (baseURL) { urlConcatenateWithBaseURL = gf_url_concatenate(baseURL, url); /*relative file path*/ if (!strstr(baseURL, "://")) { + info->protocol = gf_strdup("file"); info->canonicalRepresentation = urlConcatenateWithBaseURL; return GF_OK; } @@ -2488,9 +586,11 @@ } for (i=0; i<strlen(info->remotePath); i++) if (info->remotePathi=='\\') info->remotePathi='/'; - info->canonicalRepresentation = (char*)gf_malloc(strlen(info->protocol) + strlen(info->remotePath) + 1); - strcpy(info->canonicalRepresentation, info->protocol); - strcat(info->canonicalRepresentation, info->remotePath); + + info->canonicalRepresentation = NULL; + gf_dynstrcat(&info->canonicalRepresentation, info->protocol, NULL); + gf_dynstrcat(&info->canonicalRepresentation, "://", NULL); + gf_dynstrcat(&info->canonicalRepresentation, info->remotePath, NULL); return GF_OK; } else { @@ -2558,66 +658,29 @@ info->server_name = gf_strdup(current_pos); } - /* builds orig_url */ /* We dont't want orig_url to contain user/passwords for security reasons or mismatch in cache hit */ - { + info->canonicalRepresentation = NULL; + gf_dynstrcat(&info->canonicalRepresentation, info->protocol, NULL); + gf_dynstrcat(&info->canonicalRepresentation, "://", NULL); + gf_dynstrcat(&info->canonicalRepresentation, info->server_name, NULL); + if (info->port && (info->port!=default_port)) { char port8; snprintf(port, sizeof(port)-1, ":%d", info->port); - info->canonicalRepresentation = (char*)gf_malloc(strlen(info->protocol)+strlen(info->server_name)+1+strlen(port)+strlen(info->remotePath)); - strcpy(info->canonicalRepresentation, info->protocol); - strcat(info->canonicalRepresentation, info->server_name); - if (info->port!=80) - strcat(info->canonicalRepresentation, port); - strcat(info->canonicalRepresentation, info->remotePath); + gf_dynstrcat(&info->canonicalRepresentation, port, NULL); } + gf_dynstrcat(&info->canonicalRepresentation, info->remotePath, NULL); + gf_free(copyOfUrl); if (urlConcatenateWithBaseURL) gf_free(urlConcatenateWithBaseURL); return GF_OK; } -char *gf_cache_get_forced_headers(const DownloadedCacheEntry entry); -u32 gf_cache_get_downtime(const DownloadedCacheEntry entry); -Bool gf_cache_is_done(const DownloadedCacheEntry entry); -Bool gf_cache_is_deleted(const DownloadedCacheEntry entry); - -static void gf_dm_sess_reload_cached_headers(GF_DownloadSession *sess) -{ - char *hdrs; - - if (!sess || !sess->local_cache_only) return; - - hdrs = gf_cache_get_forced_headers(sess->cache_entry); - - gf_dm_sess_clear_headers(sess); - while (hdrs) { - char *sep2, *sepL = strstr(hdrs, "\r\n"); - if (sepL) sepL0 = 0; - sep2 = strchr(hdrs, ':'); - if (sep2) { - GF_HTTPHeader *hdr; - GF_SAFEALLOC(hdr, GF_HTTPHeader); - if (!hdr) break; - sep20=0; - hdr->name = gf_strdup(hdrs); - sep20=':'; - sep2++; - while (sep20==' ') sep2++; - hdr->value = gf_strdup(sep2); - gf_list_add(sess->headers, hdr); - } - if (!sepL) break; - sepL0 = '\r'; - hdrs = sepL + 2; - } -} - GF_EXPORT GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse) { Bool socket_changed = GF_FALSE; GF_URL_Info info; - Bool free_proto = GF_FALSE; char *sep_frag=NULL; if (!url) return GF_BAD_PARAM; @@ -2634,13 +697,18 @@ else if (sess->connection_timeout_ms) { u32 diff = (u32) ( gf_sys_clock_high_res() - sess->last_fetch_time) / 1000; if (diff >= sess->connection_timeout_ms) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Timeout reached (%d ms since last vs %d ms timeout), reconnecting\n", diff, sess->connection_timeout_ms)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Timeout reached (%d ms since last vs %d ms timeout), reconnecting\n", sess->log_name, diff, sess->connection_timeout_ms)); socket_changed = GF_TRUE; } } gf_fatal_assert(sess->status != GF_NETIO_WAIT_FOR_REPLY); gf_fatal_assert(sess->status != GF_NETIO_DATA_EXCHANGE); + //always detach task if any + if (sess->ftask) { + sess->ftask->sess = NULL; + sess->ftask = NULL; + } //strip fragment sep_frag = strchr(url, '#'); @@ -2652,40 +720,58 @@ } if (!strstr(url, "://")) { - char c, *sep; + char *sep; gf_dm_url_info_del(&info); + gf_dm_url_info_init(&info); info.port = sess->port; info.server_name = sess->server_name ? gf_strdup(sess->server_name) : NULL; info.remotePath = gf_strdup(url); sep = strstr(sess->orig_url_before_redirect, "://"); gf_assert(sep); - c = sep3; - sep3 = 0; + sep0 = 0; info.protocol = gf_strdup(sess->orig_url_before_redirect); - sep3 = c; - free_proto = GF_TRUE; + sep0 = ':'; } if (sess->port != info.port) { socket_changed = GF_TRUE; sess->port = info.port; } +#ifdef GPAC_HTTPMUX + //safety in case we had a previous error but underlying stream_id was not reset + sess->hmux_stream_id = -1; +#endif - if (sess->from_cache_only) { + if (sess->cached_file) { socket_changed = GF_TRUE; - sess->from_cache_only = GF_FALSE; + gf_fclose(sess->cached_file); + sess->cached_file = NULL; if (sess->cache_entry) { - gf_dm_remove_cache_entry_from_session(sess); + gf_cache_remove_entry_from_session(sess); sess->cache_entry = NULL; } } - - if (!strcmp("http://", info.protocol) || !strcmp("https://", info.protocol)) { + if (sess->log_name) { + gf_free(sess->log_name); + sess->log_name = NULL; + } + if (!strcmp("http", info.protocol) || !strcmp("https", info.protocol)) { + sess->log_name = gf_strdup("HTTP"); if (sess->do_requests != http_do_requests) { sess->do_requests = http_do_requests; socket_changed = GF_TRUE; } - if (!strcmp("https://", info.protocol)) { + Bool use_ssl = !strcmp("https", info.protocol) ? GF_TRUE : GF_FALSE; + //if proxy, check scheme and port + const char *proxy = (sess->flags & GF_NETIO_SESSION_NO_PROXY) ? NULL : gf_opts_get_key("core", "proxy"); + if (proxy) { + if (!strnicmp(proxy, "http://", 7)) use_ssl = GF_FALSE; + else if (!strnicmp(proxy, "https://", 8)) use_ssl = GF_TRUE; + else if (strstr(proxy, ":80")) use_ssl = GF_FALSE; + else if (strstr(proxy, ":443")) use_ssl = GF_TRUE; + } + + if (use_ssl) { if (!(sess->flags & GF_DOWNLOAD_SESSION_USE_SSL)) { sess->flags |= GF_DOWNLOAD_SESSION_USE_SSL; socket_changed = GF_TRUE; @@ -2696,6 +782,13 @@ } } else { sess->do_requests = NULL; + +#ifdef GPAC_HAS_CURL + if (!sess->dm->curl_multi) { + sess->dm->curl_multi = curl_multi_init(); + } +#endif + } if (sess->server_name && info.server_name && !strcmp(sess->server_name, info.server_name)) { @@ -2708,6 +801,16 @@ if (info.canonicalRepresentation) { if (sess->orig_url) gf_free(sess->orig_url); sess->orig_url = gf_strdup(info.canonicalRepresentation); + } else { + if (sess->orig_url) gf_free(sess->orig_url); + sess->orig_url = gf_strdup(info.protocol); + gf_dynstrcat(&sess->orig_url, info.server_name, "://"); + if (info.port) { + char szTmp10; + sprintf(szTmp, ":%u", info.port); + gf_dynstrcat(&sess->orig_url, szTmp, NULL); + } + gf_dynstrcat(&sess->orig_url, info.remotePath, NULL); } if (!sess->orig_url_before_redirect) @@ -2724,25 +827,36 @@ sess->creds = NULL; if (info.userName) { if (! sess->dm) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP Did not found any download manager, credentials not supported\n")); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Did not found any download manager, credentials not supported\n", sess->log_name)); } else - sess->creds = gf_user_credentials_register(sess->dm, !strcmp("https://", info.protocol), sess->server_name, info.userName, info.password, info.userName && info.password); + sess->creds = gf_user_credentials_register(sess->dm, !strcmp("https", info.protocol), sess->server_name, info.userName, info.password, info.userName && info.password); } } - if (free_proto) gf_free((char *) info.protocol); gf_dm_url_info_del(&info); if (sep_frag) sep_frag0='#'; -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - if (sess->h2_sess->do_shutdown) +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + if (sess->hmux_sess->do_shutdown) socket_changed = GF_TRUE; - sess->h2_buf.size = 0; - sess->h2_buf.offset = 0; + sess->hmux_buf.size = 0; + sess->hmux_buf.offset = 0; } #endif + +#ifdef GPAC_HAS_CURL + if (sess->dm->curl_multi && (!sess->do_requests || gf_opts_get_bool("core", "curl")) + //if the caller wants a GF_Socket interface, we cannot use curl + && !(sess->flags & GF_NETIO_SESSION_SHARE_SOCKET) + ) { + GF_Err e = curl_setup_session(sess); + if (e) return e; + } else +#endif + if (sess->sock && !socket_changed) { + if (!sess->do_requests) return GF_NOT_SUPPORTED; sess->status = GF_NETIO_CONNECTED; sess->last_error = GF_OK; //reset number of retry and start time @@ -2751,10 +865,11 @@ sess->needs_cache_reconfig = 1; } else { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { + if (!sess->do_requests) return GF_NOT_SUPPORTED; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { gf_mx_p(sess->mx); - h2_detach_session(sess->h2_sess, sess); + hmux_detach_session(sess->hmux_sess, sess); gf_mx_v(sess->mx); } #endif @@ -2776,6 +891,7 @@ } sess->total_size = 0; sess->bytes_done = 0; + sess->full_resource_size = 0; //could be not-0 after a byte-range request using chunk transfer sess->remaining_data_size = 0; @@ -2790,19 +906,23 @@ sess->use_cache_file = GF_TRUE; gf_dm_configure_cache(sess); sess->bytes_done = 0; - if (sess->cache_entry && gf_cache_is_deleted(sess->cache_entry)) { - sess->status = GF_NETIO_DATA_TRANSFERED; - SET_LAST_ERR(GF_URL_REMOVED) - //return GF_OK; - } else if (! gf_cache_is_done(sess->cache_entry)) { - sess->total_size = 0; - sess->status = GF_NETIO_DATA_EXCHANGE; - } else { + if (sess->cache_entry && gf_cache_is_deleted(sess->cache_entry)) { + sess->status = GF_NETIO_DATA_TRANSFERED; + SET_LAST_ERR(GF_URL_REMOVED) + //return GF_OK; + } else if (! gf_cache_is_done(sess->cache_entry)) { + sess->total_size = 0; + sess->status = GF_NETIO_DATA_EXCHANGE; + } else { sess->total_size = gf_cache_get_content_length(sess->cache_entry); - sess->bytes_done = sess->total_size; - sess->status = GF_NETIO_DATA_TRANSFERED; - SET_LAST_ERR(GF_OK) - } + sess->bytes_done = sess->total_size; + sess->status = GF_NETIO_DATA_TRANSFERED; + if (!sess->cache_entry) { + SET_LAST_ERR(GF_URL_ERROR) + } else { + SET_LAST_ERR(GF_OK) + } + } sess->total_time_since_req = gf_cache_get_downtime(sess->cache_entry); if (sess->total_time_since_req) @@ -2855,13 +975,20 @@ { GF_SessTask *task = callback; GF_DownloadSession *sess = task->sess; - if (!sess) { + if (!sess || sess->destroy) { gf_free(task); + if (sess) sess->ftask = NULL; return GF_FALSE; } + task->in_task = GF_TRUE; Bool ret = gf_dm_session_do_task(sess); + task->in_task = GF_FALSE; if (ret) { - *reschedule_ms = 1; + if (sess->rate_regulated) { + *reschedule_ms = (sess->last_cap_rate_bytes_per_sec > sess->max_data_rate) ? 1000 : 100; + } else { + *reschedule_ms = 1; + }; return GF_TRUE; } gf_assert(sess->ftask); @@ -2884,7 +1011,7 @@ while (!sess->destroy) { Bool ret = gf_dm_session_do_task(sess); if (!ret) break; - gf_sleep(0); + gf_sleep(sess->rate_regulated ? 100 : 0); } sess->flags |= GF_DOWNLOAD_SESSION_THREAD_DEAD; if (sess->destroy) @@ -2893,10 +1020,11 @@ } #endif -static GF_DownloadSession *gf_dm_sess_new_internal(GF_DownloadManager * dm, const char *url, u32 dl_flags, +GF_DownloadSession *gf_dm_sess_new_internal(GF_DownloadManager * dm, const char *url, u32 dl_flags, gf_dm_user_io user_io, void *usr_cbk, GF_Socket *server, + Bool force_server, GF_Err *e) { GF_DownloadSession *sess; @@ -2905,6 +1033,9 @@ if (!sess) { return NULL; } +#ifdef GPAC_HTTPMUX + sess->hmux_stream_id = -1; +#endif sess->headers = gf_list_new(); sess->flags = dl_flags; if (sess->flags & GF_NETIO_SESSION_NOTIFY_DATA) @@ -2912,6 +1043,9 @@ sess->user_proc = user_io; sess->usr_cbk = usr_cbk; sess->creds = NULL; + sess->log_name = gf_strdup("HTTP"); + sess->http_buf_size = dm ? dm->read_buf_size : GF_DOWNLOAD_BUFFER_SIZE; + sess->http_buf = gf_malloc(sess->http_buf_size + 1); sess->conn_timeout = gf_opts_get_int("core", "tcp-timeout"); if (!sess->conn_timeout) sess->conn_timeout = 5000; @@ -2922,7 +1056,7 @@ if (!sess->chunk_wnd_dur) sess->chunk_wnd_dur = 20000; sess->dm = dm; - if (server) { + if (server || force_server) { sess->sock = server; sess->flags = GF_NETIO_SESSION_NOT_THREADED; sess->status = GF_NETIO_CONNECTED; @@ -2931,7 +1065,8 @@ if (e) *e = GF_OK; if (dl_flags & GF_NETIO_SESSION_NO_BLOCK) { sess->flags |= GF_NETIO_SESSION_NO_BLOCK; - gf_sk_set_block_mode(server, GF_TRUE); + if (server) + gf_sk_set_block_mode(server, GF_TRUE); } return sess; } @@ -2940,7 +1075,11 @@ if (dm) sess->disable_cache = dm->disable_cache; - if (! (dl_flags & GF_NETIO_SESSION_NOT_THREADED)) { + if (! (dl_flags & GF_NETIO_SESSION_NOT_THREADED) +#ifdef GPAC_HAS_CURL + && !sess->curl_hnd +#endif + ) { sess->mx = gf_mx_new(url); if (!sess->mx) { gf_free(sess); @@ -2948,6 +1087,11 @@ } } + if ((dm->h3_mode == H3_MODE_FIRST) || (dm->h3_mode == H3_MODE_ONLY)) + sess->flags |= GF_NETIO_SESSION_USE_QUIC; + else if (dm->h3_mode == H3_MODE_AUTO) + sess->flags |= GF_NETIO_SESSION_RETRY_QUIC; + *e = gf_dm_sess_setup_from_url(sess, url, GF_FALSE); if (*e) { GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader failed to create session for %s: %s\n", url, gf_error_to_string(*e))); @@ -2968,79 +1112,24 @@ Bool async, GF_Err *e) { - GF_DownloadSession *sess; - -#if defined(GPAC_HAS_HTTP2) && defined(GPAC_HAS_SSL) - Bool h2_negotiated = GF_FALSE; - if (ssl_sock_ctx) { - const unsigned char *alpn = NULL; - unsigned int alpnlen = 0; - SSL_get0_next_proto_negotiated(ssl_sock_ctx, &alpn, &alpnlen); -#if OPENSSL_VERSION_NUMBER >= 0x10002000L - if (alpn == NULL) { - SSL_get0_alpn_selected(ssl_sock_ctx, &alpn, &alpnlen); - } -#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L - - if (alpn && (alpnlen == 2) && !memcmp("h2", alpn, 2)) { - h2_negotiated = GF_TRUE; - } - } -#endif //GPAC_HAS_HTTP2 && GPAC_HAS_SSL - - sess = gf_dm_sess_new_internal(dm, NULL, async ? GF_NETIO_SESSION_NO_BLOCK : 0, user_io, usr_cbk, server, e); + GF_DownloadSession *sess = gf_dm_sess_new_internal(dm, NULL, async ? GF_NETIO_SESSION_NO_BLOCK : 0, user_io, usr_cbk, server, GF_TRUE, e); + if (!sess) return NULL; #ifdef GPAC_HAS_SSL - if (sess) { + if (ssl_sock_ctx) { sess->ssl = ssl_sock_ctx; - if (ssl_sock_ctx && (sess->flags & GF_NETIO_SESSION_NO_BLOCK)) - SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); - -#if defined(GPAC_HAS_HTTP2) - if (h2_negotiated) { - h2_initialize_session(sess); - } -#endif + gf_dm_sess_server_setup_ssl(sess); } #endif return sess; } -GF_DownloadSession *gf_dm_sess_new_subsession(GF_DownloadSession *sess, u32 stream_id, void *usr_cbk, GF_Err *e) -{ -#ifdef GPAC_HAS_HTTP2 - GF_DownloadSession *sub_sess; - if (!sess->h2_sess || !stream_id) return NULL; - gf_mx_p(sess->mx); - sub_sess = gf_dm_sess_new_internal(NULL, NULL, 0, sess->user_proc, usr_cbk, sess->sock, e); - if (!sub_sess) { - gf_mx_v(sess->mx); - return NULL; - } - gf_list_add(sess->h2_sess->sessions, sub_sess); -#ifdef GPAC_HAS_SSL - sub_sess->ssl = sess->ssl; -#endif - sub_sess->h2_sess = sess->h2_sess; - if (sub_sess->mx) gf_mx_del(sub_sess->mx); - sub_sess->mx = sess->h2_sess->mx; - sub_sess->h2_stream_id = stream_id; - sub_sess->status = GF_NETIO_CONNECTED; - sub_sess->data_io.read_callback = h2_data_source_read_callback; - sub_sess->data_io.source.ptr = sub_sess; - sub_sess->flags = sess->flags; - gf_mx_v(sess->mx); - return sub_sess; -#else - return NULL; -#endif -} u32 gf_dm_sess_subsession_count(GF_DownloadSession *sess) { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) - return gf_list_count(sess->h2_sess->sessions); +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) + return gf_list_count(sess->hmux_sess->sessions); #endif return 1; } @@ -3049,9 +1138,16 @@ void gf_dm_sess_server_reset(GF_DownloadSession *sess) { if (!sess->server_mode) return; + //DO NOT clear headers if H2: the underlying h2 session could have been ended (stream_id=0) and then reassigned + //when processsing another session, so the headers would be the ones of the new stream_id + //for H2, the reset is done when reassigning sessions +#ifdef GPAC_HTTPMUX + if (!sess->hmux_sess) +#endif + gf_dm_sess_clear_headers(sess); - gf_dm_sess_clear_headers(sess); sess->total_size = sess->bytes_done = 0; + sess->async_buf_size = 0; sess->chunk_bytes = 0; sess->chunk_header_bytes = 0; sess->chunked = GF_FALSE; @@ -3065,7 +1161,7 @@ void *usr_cbk, GF_Err *e) { - return gf_dm_sess_new_internal(dm, url, dl_flags, user_io, usr_cbk, NULL, e); + return gf_dm_sess_new_internal(dm, url, dl_flags, user_io, usr_cbk, NULL, GF_FALSE, e); } GF_EXPORT GF_DownloadSession *gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 dl_flags, @@ -3080,7 +1176,7 @@ return NULL; } - if (!gf_dm_can_handle_url(dm, url)) { + if (!gf_dm_can_handle_url(url)) { *e = GF_NOT_SUPPORTED; return NULL; } @@ -3088,16 +1184,37 @@ if (sess && dm) { sess->dm = dm; gf_mx_p(dm->cache_mx); - gf_list_add(dm->sessions, sess); + gf_list_add(dm->all_sessions, sess); gf_mx_v(dm->cache_mx); } return sess; } -static GF_Err gf_dm_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read) + +GF_Err gf_dm_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read) { GF_Err e; + if (sess->cached_file) { + *out_read = (u32) gf_fread(data, data_size, sess->cached_file); + if (! *out_read && gf_cache_is_done(sess->cache_entry)) { + sess->total_size = gf_cache_get_content_length(sess->cache_entry); + if (sess->total_size != sess->bytes_done) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_CORRUPTED_DATA); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Broken cache for %s: %u bytes in cache but %u advertized\n", sess->log_name, sess->orig_url, sess->bytes_done, sess->total_size)); + return GF_CORRUPTED_DATA; + } + } + return GF_OK; + } + +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + return curl_read_data(sess, data, data_size, out_read); + } +#endif + if (sess->dm && sess->dm->simulate_no_connection) { if (sess->sock) { sess->status = GF_NETIO_DISCONNECTED; @@ -3108,6 +1225,9 @@ if (!sess) return GF_BAD_PARAM; + if (sess->server_mode && (sess->flags & GF_NETIO_SESSION_USE_QUIC)) + return GF_IP_NETWORK_EMPTY; + gf_mx_p(sess->mx); if (!sess->sock) { sess->status = GF_NETIO_DISCONNECTED; @@ -3118,65 +1238,26 @@ *out_read = 0; #ifdef GPAC_HAS_SSL - if (sess->ssl) { - s32 size; - - //receive on null buffer (select only, check if data available) - e = gf_sk_receive(sess->sock, NULL, 0, NULL); - //empty and no pending bytes in SSL, network empty - if ((e==GF_IP_NETWORK_EMPTY) && -#if 1 - !SSL_pending(sess->ssl) -#else - //no support for SSL_has_pending in old libSSL and same result can be achieved with SSL_pending - !SSL_has_pending(sess->ssl) -#endif - ) { + if (sess->ssl && !(sess->flags & GF_NETIO_SESSION_USE_QUIC)) { + e = gf_ssl_read_data(sess, data, data_size, out_read); + if (e==GF_NOT_READY) { gf_mx_v(sess->mx); - return e; - } - size = SSL_read(sess->ssl, data, data_size); - if (size < 0) { - int err = SSL_get_error(sess->ssl, size); - if (err==SSL_ERROR_SSL) { - char msg1024; - SSL_load_error_strings(); - ERR_error_string_n(ERR_get_error(), msg, 1023); - msg1023=0; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot read, error %s\n", msg)); - e = GF_IO_ERR; - } else { - e = gf_sk_probe(sess->sock); - } - } else if (!size) - e = GF_IP_NETWORK_EMPTY; - else { - e = GF_OK; - datasize = 0; - *out_read = size; + return GF_IP_NETWORK_EMPTY; } } else #endif - e = gf_sk_receive(sess->sock, data, data_size, out_read); -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - if (*out_read > 0) { - ssize_t read_len = nghttp2_session_mem_recv(sess->h2_sess->ng_sess, data, *out_read); - if(read_len < 0 ) { - *out_read = 0; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 nghttp2_session_mem_recv error: %s\n", nghttp2_strerror((int) read_len))); - return GF_IO_ERR; - } +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + GF_Err hme = sess->hmux_sess->data_received(sess, data, *out_read); + if (hme) { + gf_mx_v(sess->mx); + *out_read = 0; + return hme; } - /* send pending frames - h2_sess may be NULL at this point if the connection was reset during processing of nghttp2_session_mem_recv - this typically happens if we have a refused stream - */ - if (sess->h2_sess) - h2_session_send(sess); } -#endif //GPAC_HAS_HTTP2 +#endif //GPAC_HTTPMUX if (*out_read) sess->last_fetch_time = gf_sys_clock_high_res(); @@ -3185,211 +1266,112 @@ return e; } - -#ifdef GPAC_HAS_SSL - -#define LWR(x) ('A' <= (x) && (x) <= 'Z' ? (x) - 32 : (x)) - -static Bool rfc2818_match(const char *pattern, const char *string) -{ - char d; - u32 i=0, k=0; - while (1) { - char c = LWR(patterni); - if (c == '\0') break; - - if (c=='*') { - /*remove *** patterns*/ - while (c == '*') { - i++; - c = LWR(patterni); - } - /*look for same c character*/ - while (1) { - d = LWR(stringk); - if (d == '\0') break; - /*matched c character, check following substrings*/ - if ((d == c) && rfc2818_match (&patterni, &stringk)) - return GF_TRUE; - else if (d == '.') - return GF_FALSE; - - k++; - } - return (c == '\0') ? GF_TRUE : GF_FALSE; - } else { - if (c != LWR(stringk)) - return GF_FALSE; - } - i++; - k++; - } - return (stringk=='\0') ? GF_TRUE : GF_FALSE; -} -#undef LWR - -#endif - - -#ifdef GPAC_HAS_SSL -Bool gf_ssl_check_cert(SSL *ssl, const char *server_name) -{ - Bool success; - X509 *cert = SSL_get_peer_certificate(ssl); - if (!cert) return GF_TRUE; - - long vresult; - SSL_set_verify_result(ssl, 0); - vresult = SSL_get_verify_result(ssl); - - if (vresult == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("SSL Cannot locate issuer's certificate on the local system, will not attempt to validate\n")); - SSL_set_verify_result(ssl, 0); - vresult = SSL_get_verify_result(ssl); - } - - if (vresult == X509_V_OK) { - char common_name256; - STACK_OF(GENERAL_NAME) *altnames; - GF_List* valid_names; - int i; - - valid_names = gf_list_new(); - - common_name0 = 0; - X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, common_name, sizeof (common_name)); - gf_list_add(valid_names, common_name); - - altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); - if (altnames) { - for (i = 0; i < sk_GENERAL_NAME_num(altnames); ++i) { - const GENERAL_NAME *altname = sk_GENERAL_NAME_value(altnames, i); - if (altname->type == GEN_DNS) - { - #if OPENSSL_VERSION_NUMBER < 0x10100000L - unsigned char *altname_str = ASN1_STRING_data(altname->d.ia5); - #else - unsigned char *altname_str = (unsigned char *)ASN1_STRING_get0_data(altname->d.ia5); - #endif - gf_list_add(valid_names, altname_str); - } - } - } - - success = GF_FALSE; - for (i = 0; i < (int)gf_list_count(valid_names); ++i) { - const char *valid_name = (const char*) gf_list_get(valid_names, i); - if (rfc2818_match(valid_name, server_name)) { - success = GF_TRUE; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("SSL Hostname %s matches %s\n", server_name, valid_name)); - break; - } - } - if (!success) { - if ( gf_opts_get_bool("core", "broken-cert")) { - success = GF_TRUE; - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("SSL Mismatch in certificate names: expected %s\n", server_name)); - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Mismatch in certificate names, try using -broken-cert: expected %s\n", server_name)); - } -#ifndef GPAC_DISABLE_LOG - for (i = 0; i < (int)gf_list_count(valid_names); ++i) { - const char *valid_name = (const char*) gf_list_get(valid_names, i); - GF_LOG(success ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Tried name: %s\n", valid_name)); - } -#endif - } - - gf_list_del(valid_names); - GENERAL_NAMES_free(altnames); - } else { - success = GF_FALSE; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Error verifying certificate %x\n", vresult)); - } - - X509_free(cert); - return success; -} - -#endif - static void gf_dm_connect(GF_DownloadSession *sess) { GF_Err e; + Bool register_sock; + Bool allow_offline = sess->dm ? sess->dm->allow_offline_cache : GF_FALSE; u16 proxy_port = 0; + char szProxyGF_MAX_PATH; const char *proxy; -#ifdef GPAC_HAS_HTTP2 + //offline cache enabled, setup cache before connection + if (!sess->connect_pending && allow_offline) { + gf_dm_configure_cache(sess); + if (sess->cached_file) return; + } + +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + curl_connect(sess); + return; + } +#endif - if (sess->h2_switch_sess) { - sess->h2_switch_sess = 0; +#ifdef GPAC_HTTPMUX + if (sess->hmux_switch_sess) { + sess->hmux_switch_sess = 0; gf_mx_p(sess->mx); - h2_detach_session(sess->h2_sess, sess); + hmux_detach_session(sess->hmux_sess, sess); gf_mx_v(sess->mx); if (sess->num_retry) { SET_LAST_ERR(GF_OK) sess->num_retry--; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) refused by server, retrying and marking session as no longer available\n", sess->h2_stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s stream_id "LLD" (%s) refused by server, retrying and marking session as no longer available\n", sess->log_name, sess->hmux_stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); - sess->h2_stream_id = 0; + sess->hmux_stream_id = -1; } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) refused by server after all retries, marking session as no longer available\n", sess->h2_stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s stream_id "LLD" (%s) refused by server after all retries, marking session as no longer available\n", sess->log_name, sess->hmux_stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); sess->status = GF_NETIO_STATE_ERROR; SET_LAST_ERR(GF_REMOTE_SERVICE_ERROR) - sess->h2_stream_id = 0; + sess->hmux_stream_id = -1; return; } } - if (sess->h2_sess) { + if (sess->hmux_sess && sess->hmux_sess->connected) { sess->connect_time = 0; sess->status = GF_NETIO_CONNECTED; sess->last_error = GF_OK; gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); - gf_dm_configure_cache(sess); + if (!allow_offline) + gf_dm_configure_cache(sess); return; } + Bool attach_to_parent_sess = GF_FALSE; + if (sess->dm && !sess->sock) { +#ifdef GPAC_HAS_HTTP2 + if (!sess->dm->disable_http2 && (sess->h2_upgrade_state<3)) attach_to_parent_sess = GF_TRUE; +#endif + } - if (sess->dm && !sess->dm->disable_http2 && !sess->sock && (sess->h2_upgrade_state<3)) { - u32 i, count = gf_list_count(sess->dm->sessions); + if (attach_to_parent_sess) { + gf_mx_p(sess->dm->cache_mx); + u32 i, count = gf_list_count(sess->dm->all_sessions); for (i=0; i<count; i++) { - GF_DownloadSession *a_sess = gf_list_get(sess->dm->sessions, i); - if (strcmp(a_sess->server_name, sess->server_name)) continue; - if (!a_sess->h2_sess) { + GF_DownloadSession *a_sess = gf_list_get(sess->dm->all_sessions, i); + if (strcmp(a_sess->server_name, sess->server_name) || (a_sess->port != sess->port)) continue; + if (!a_sess->hmux_sess) { +#ifdef GPAC_HAS_HTTP2 //we already ahd a connection to this server with H2 failure, do not try h2 if (a_sess->h2_upgrade_state==4) { sess->h2_upgrade_state=4; break; } - +#endif //we have the same server name //trick in non-block mode, we want to wait for upgrade to be completed //before creating a new socket if ((a_sess != sess) && a_sess->sock && (a_sess->status <= GF_NETIO_WAIT_FOR_REPLY) - && (a_sess->h2_upgrade_state<2) && (sess->flags & GF_NETIO_SESSION_NO_BLOCK) +#ifdef GPAC_HAS_HTTP2 + && (a_sess->h2_upgrade_state<2) && !sess->dm->disable_http2 && !gf_opts_get_bool("core", "no-h2c") +#endif + //TODO: H3 test ) { sess->connect_pending = 1; SET_LAST_ERR(GF_IP_NETWORK_EMPTY) + gf_mx_v(sess->dm->cache_mx); return; } continue; } - if (a_sess->h2_sess->do_shutdown) continue; + if (a_sess->hmux_sess->do_shutdown) continue; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 associating session %s to existing http2 session\n", sess->remote_path ? sess->remote_path : sess->orig_url)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s associating session %s to existing %s session: %s:%u\n", sess->log_name, sess->remote_path ? sess->remote_path : sess->orig_url, a_sess->log_name, a_sess->server_name, a_sess->port)); u32 nb_locks=0; if (sess->mx) { nb_locks = gf_mx_get_num_locks(sess->mx); + if (nb_locks) gf_mx_v(sess->mx); gf_mx_del(sess->mx); } - sess->h2_sess = a_sess->h2_sess; - sess->mx = a_sess->h2_sess->mx; + sess->hmux_sess = a_sess->hmux_sess; + sess->mx = a_sess->hmux_sess->mx; if (nb_locks) gf_mx_p(sess->mx); @@ -3397,27 +1379,42 @@ #ifdef GPAC_HAS_SSL sess->ssl = a_sess->ssl; #endif - sess->data_io.read_callback = h2_data_source_read_callback; - sess->data_io.source.ptr = sess; - gf_list_add(sess->h2_sess->sessions, sess); + sess->proxy_enabled = a_sess->proxy_enabled; + sess->hmux_sess->setup_session(sess, GF_FALSE); + + gf_list_add(sess->hmux_sess->sessions, sess); + //reconfigure cache if (sess->allow_direct_reuse) { gf_dm_configure_cache(sess); - if (sess->from_cache_only) return; + if (sess->cached_file) { + gf_mx_v(sess->dm->cache_mx); + return; + } } sess->connect_time = 0; - sess->status = GF_NETIO_CONNECTED; - gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); - gf_dm_configure_cache(sess); + if (sess->hmux_sess->connected) { + sess->status = GF_NETIO_CONNECTED; + gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); + gf_dm_configure_cache(sess); + } + gf_mx_v(sess->dm->cache_mx); return; - } + } // end all_sessions loop + gf_mx_v(sess->dm->cache_mx); } #endif - Bool register_sock = GF_FALSE; +resetup_socket: + + register_sock = GF_FALSE; if (!sess->sock) { - sess->sock = gf_sk_new_ex(GF_SOCK_TYPE_TCP, sess->netcap_id); + u32 sock_type = GF_SOCK_TYPE_TCP; + if (sess->flags & GF_NETIO_SESSION_USE_QUIC) + sock_type = GF_SOCK_TYPE_UDP; + + sess->sock = gf_sk_new_ex(sock_type, sess->netcap_id); if (sess->sock && (sess->flags & GF_NETIO_SESSION_NO_BLOCK)) gf_sk_set_block_mode(sess->sock, GF_TRUE); @@ -3431,9 +1428,18 @@ /*PROXY setup*/ if (sess->proxy_enabled!=2) { - proxy = NULL; - if (gf_opts_get_bool("core", "proxy-on")) { + proxy = (sess->flags & GF_NETIO_SESSION_NO_PROXY) ? NULL : gf_opts_get_key("core", "proxy"); + if (proxy) { u32 i; + proxy_port = 80; + char *sep = strstr(proxy, "://"); + strcpy(szProxy, sep ? sep+3 : proxy); + sep = strchr(szProxy, ':'); + if (sep) { + proxy_port = atoi(sep+1); + sep0=0; + } + proxy = szProxy; Bool use_proxy=GF_TRUE; for (i=0; i<gf_list_count(sess->dm->skip_proxy_servers); i++) { char *skip = (char*)gf_list_get(sess->dm->skip_proxy_servers, i); @@ -3442,13 +1448,10 @@ break; } } - if (use_proxy) { - proxy_port = gf_opts_get_int("core", "proxy-port"); - if (!proxy_port) proxy_port = 80; - proxy = gf_opts_get_key("core", "proxy-name"); - sess->proxy_enabled = 1; - } else { + if (!use_proxy) { proxy = NULL; + } else { + sess->proxy_enabled = 1; } } else { proxy = NULL; @@ -3464,7 +1467,7 @@ proxy_port = sess->port; } if (!sess->connect_pending) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Connecting to %s:%d for URL %s\n", proxy, proxy_port, sess->remote_path ? sess->remote_path : "undefined")); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connecting to %s:%d for URL %s\n", sess->log_name, proxy, proxy_port, sess->remote_path ? sess->remote_path : "undefined")); } if ((sess->status == GF_NETIO_SETUP) && (sess->connect_pending<2)) { @@ -3481,7 +1484,16 @@ if ((sess->flags & GF_NETIO_SESSION_NO_BLOCK) && !sess->start_time) sess->start_time = now; - e = gf_sk_connect(sess->sock, (char *) proxy, proxy_port, NULL); +#ifdef GPAC_HAS_NGTCP2 + if (sess->flags & GF_NETIO_SESSION_USE_QUIC) { + e = http3_connect(sess, (char *) proxy, proxy_port); + if (sess->num_retry && (e==GF_IP_CONNECTION_FAILURE) ) { + register_sock = GF_FALSE; + e = GF_IP_NETWORK_EMPTY; + } + } else +#endif + e = gf_sk_connect(sess->sock, (char *) proxy, proxy_port, NULL); /*retry*/ if (e == GF_IP_NETWORK_EMPTY) { @@ -3515,9 +1527,39 @@ /*failed*/ if (e) { - if (!sess->cache_entry && sess->dm && sess->dm->allow_offline_cache) { - gf_dm_configure_cache(sess); - if (sess->from_cache_only) return; + if ((sess->flags & GF_NETIO_SESSION_RETRY_QUIC) && !(sess->flags & GF_NETIO_SESSION_USE_QUIC)) { + sess->status = GF_NETIO_SETUP; + sess->connect_pending = 0; + sess->start_time = 0; + gf_sk_group_unregister(sess->sock_group, sess->sock); + gf_sk_del(sess->sock); + sess->sock = NULL; + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s Failed to connect through TCP (%s), retrying with QUIC\n", sess->log_name, gf_error_to_string(e))); + sess->flags |= GF_NETIO_SESSION_USE_QUIC; + sess->flags &= ~GF_NETIO_SESSION_RETRY_QUIC; + goto resetup_socket; + } + if ((sess->flags & GF_NETIO_SESSION_USE_QUIC) + && !(sess->flags & GF_NETIO_SESSION_RETRY_QUIC) + && (sess->dm->h3_mode!=H3_MODE_ONLY) + ) { + sess->flags &= ~GF_NETIO_SESSION_USE_QUIC; + sess->status = GF_NETIO_SETUP; + sess->connect_pending = 0; + sess->start_time = 0; + gf_sk_group_unregister(sess->sock_group, sess->sock); + gf_sk_del(sess->sock); + sess->sock = NULL; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + sess->hmux_sess->destroy(sess->hmux_sess); + gf_list_del(sess->hmux_sess->sessions); + gf_free(sess->hmux_sess); + sess->hmux_sess = NULL; + } +#endif + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s Failed to connect through QUIC (%s), retrying with TCP\n", sess->log_name, gf_error_to_string(e))); + goto resetup_socket; } sess->status = GF_NETIO_STATE_ERROR; SET_LAST_ERR(e) @@ -3527,128 +1569,36 @@ if (register_sock) gf_sk_group_register(sess->sock_group, sess->sock); if (sess->allow_direct_reuse) { gf_dm_configure_cache(sess); - if (sess->from_cache_only) return; + if (sess->cached_file) return; } sess->connect_time = (u32) (gf_sys_clock_high_res() - now); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Connected to %s:%d\n", proxy, proxy_port)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connected to %s:%d\n", sess->log_name, proxy, proxy_port)); gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); -// gf_sk_set_buffer_size(sess->sock, GF_TRUE, GF_DOWNLOAD_BUFFER_SIZE); -// gf_sk_set_buffer_size(sess->sock, GF_FALSE, GF_DOWNLOAD_BUFFER_SIZE); } if (sess->status == GF_NETIO_SETUP) sess->status = GF_NETIO_CONNECTED; #ifdef GPAC_HAS_SSL - if ((!sess->ssl || (sess->connect_pending==2)) && (sess->flags & (GF_DOWNLOAD_SESSION_USE_SSL|GF_DOWNLOAD_SESSION_SSL_FORCED))) { - u64 now = gf_sys_clock_high_res(); - if (sess->dm && !sess->dm->ssl_ctx) - gf_dm_ssl_init(sess->dm, 0); - /*socket is connected, configure SSL layer*/ - if (sess->dm && sess->dm->ssl_ctx) { - int ret; - Bool success; - - if (!sess->ssl) { - sess->ssl = SSL_new(sess->dm->ssl_ctx); - SSL_set_fd(sess->ssl, gf_sk_get_handle(sess->sock)); - SSL_ctrl(sess->ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void*) proxy); - SSL_set_connect_state(sess->ssl); - - if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) - SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); - -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_upgrade_state==3) { - SSL_set_alpn_protos(sess->ssl, NULL, 0); - sess->h2_upgrade_state = 0; - } - //h1 disabled, don't use alpn - else if (sess->h2_upgrade_state==4) { - SSL_set_alpn_protos(sess->ssl, NULL, 0); - } -#endif - } - - sess->connect_pending = 0; - ret = SSL_connect(sess->ssl); - if (ret<=0) { - ret = SSL_get_error(sess->ssl, ret); - if (ret==SSL_ERROR_SSL) { - -#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) && defined(GPAC_HAS_HTTP2) - //error and we tried with alpn for h2, retry without (kill/reconnect) - if (!sess->h2_upgrade_state && !sess->dm->disable_http2) { - SSL_free(sess->ssl); - sess->ssl = NULL; - dm_sess_sk_del(sess); - sess->status = GF_NETIO_SETUP; - sess->h2_upgrade_state = 3; - gf_dm_connect(sess); - return; - } -#endif - - char msg1024; - SSL_load_error_strings(); - ERR_error_string_n(ERR_get_error(), msg, 1023); - msg1023=0; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot connect, error %s\n", msg)); -#ifdef GPAC_HAS_HTTP2 - if (!sess->dm->disable_http2) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("\tYou may want to retry with HTTP/2 support disabled (-no-h2)\n")); - } -#endif - SET_LAST_ERR(GF_SERVICE_ERROR) - } else if ((ret==SSL_ERROR_WANT_READ) || (ret==SSL_ERROR_WANT_WRITE)) { - sess->status = GF_NETIO_SETUP; - sess->connect_pending = 2; - return; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot connect, error %d\n", ret)); - SET_LAST_ERR(GF_REMOTE_SERVICE_ERROR) - } - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("SSL connected\n")); - - -#ifdef GPAC_HAS_HTTP2 - if (!sess->dm->disable_http2) { - const u8 *alpn = NULL; - u32 alpnlen = 0; - SSL_get0_next_proto_negotiated(sess->ssl, &alpn, &alpnlen); -#if OPENSSL_VERSION_NUMBER >= 0x10002000L - if (alpn == NULL) { - SSL_get0_alpn_selected(sess->ssl, &alpn, &alpnlen); - } -#endif - if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("SSL HTTP/2 is not negotiated\n")); - //disable h2 for the session - sess->h2_upgrade_state = 4; - } else { - h2_initialize_session(sess); - } - } -#endif - } - - success = gf_ssl_check_cert(sess->ssl, sess->server_name); - if (!success) { - gf_dm_disconnect(sess, HTTP_RESET_CONN); - sess->status = GF_NETIO_STATE_ERROR; - SET_LAST_ERR(GF_AUTHENTICATION_FAILURE) - gf_dm_sess_notify_state(sess, sess->status, sess->last_error); - } + if ((!sess->ssl || (sess->connect_pending==2)) + && (sess->flags & (GF_DOWNLOAD_SESSION_USE_SSL|GF_DOWNLOAD_SESSION_SSL_FORCED)) + ) { + SSLConnectStatus ret = gf_ssl_try_connect(sess, proxy); + if (ret != SSL_CONNECT_OK) { + if (ret == SSL_CONNECT_RETRY) + gf_dm_connect(sess); - sess->ssl_setup_time = (u32) (gf_sys_clock_high_res() - now); + return; } - } + } else #endif + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s connected\n", sess->log_name)); + } - /*this should be done when building HTTP GET request in case we have range directives*/ - gf_dm_configure_cache(sess); + if (!allow_offline) + gf_dm_configure_cache(sess); } @@ -3772,13 +1722,12 @@ return GF_OK; } -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX static void gf_dm_sess_flush_input(GF_DownloadSession *sess) { - char sHTTPGF_DOWNLOAD_BUFFER_SIZE+1; u32 res; - sHTTP0 = 0; - GF_Err e = gf_dm_read_data(sess, sHTTP, GF_DOWNLOAD_BUFFER_SIZE, &res); + sess->http_buf0 = 0; + GF_Err e = gf_dm_read_data(sess, sess->http_buf, sess->http_buf_size, &res); switch (e) { case GF_IP_NETWORK_EMPTY: case GF_OK: @@ -3809,7 +1758,7 @@ return GF_OK; } if (sess->th) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTP Session already started - ignoring start\n")); + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s Session already started - ignoring start\n", sess->log_name)); return GF_OK; } sess->th = gf_th_new( gf_file_basename(sess->orig_url) ); @@ -3833,10 +1782,17 @@ gf_dm_connect(sess); if (sess->connect_pending) { go = GF_FALSE; + if (!sess->last_error) { + //in case someone forgot to set this - if no error we must be in network empty state while connection is pending + sess->last_error = GF_IP_NETWORK_EMPTY; + } } break; case GF_NETIO_WAIT_FOR_REPLY: case GF_NETIO_CONNECTED: + if (!sess->last_error) + sess->last_error = GF_IP_NETWORK_EMPTY; + sess->do_requests(sess); if (sess->server_mode) { if (sess->status == GF_NETIO_STATE_ERROR) { @@ -3857,18 +1813,20 @@ case GF_NETIO_DATA_EXCHANGE: if (sess->put_state==2) { sess->status = GF_NETIO_DATA_TRANSFERED; - SET_LAST_ERR(GF_OK) + SET_LAST_ERR(GF_OK) go = GF_FALSE; break; } sess->do_requests(sess); break; case GF_NETIO_DATA_TRANSFERED: -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess && sess->server_mode) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess && sess->server_mode) { gf_dm_sess_flush_input(sess); - h2_session_send(sess); + sess->hmux_sess->write(sess); } + //in put and waiting for EOS flush + if ((sess->put_state==1) && sess->hmux_is_eos) return GF_IP_NETWORK_EMPTY; #endif go = GF_FALSE; break; @@ -3880,7 +1838,6 @@ case GF_NETIO_GET_METHOD: case GF_NETIO_GET_HEADER: case GF_NETIO_GET_CONTENT: - case GF_NETIO_PARSE_HEADER: case GF_NETIO_PARSE_REPLY: break; @@ -3929,46 +1886,6 @@ return sess->last_error; } -static Bool gf_dm_needs_to_delete_cache(GF_DownloadManager * dm) -{ - if (!dm) return GF_FALSE; - return dm->clean_cache; -} - -#ifdef BUGGY_gf_cache_cleanup_cache -/*! - * Cleans up the cache at start and stop. - * Note that this method will perform any cleanup if - * Configuration section Downloader/CleanCache is not set, meaning - * that methods that create a "fake" GF_DownloadManager such as - * gf_dm_wget() are not impacted and won't cleanup the cache - * - * FIXME: should be probably threaded to avoid too long start time -\param dm The GF_DownloadManager - */ -static void gf_cache_cleanup_cache(GF_DownloadManager * dm) { - if (gf_dm_needs_to_delete_cache(dm)) { - gf_cache_delete_all_cached_files(dm->cache_directory); - } -} -#endif - -typedef struct -{ - Bool check_size; - u64 out_size; -} cache_probe; - - -static void gf_dm_clean_cache(GF_DownloadManager *dm) -{ - u64 out_size = gf_cache_get_size(dm->cache_directory); - if (out_size >= dm->max_cache_size) { - GF_LOG(dm->max_cache_size ? GF_LOG_WARNING : GF_LOG_INFO, GF_LOG_HTTP, ("Cache Cache size %d exceeds max allowed %d, deleting entire cache\n", out_size, dm->max_cache_size)); - gf_cache_delete_all_cached_files(dm->cache_directory); - } -} - GF_EXPORT GF_DownloadManager *gf_dm_new(GF_FilterSession *fsess) { @@ -3980,7 +1897,7 @@ GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Downloader Failed to allocate downloader\n")); return NULL; } - dm->sessions = gf_list_new(); + dm->all_sessions = gf_list_new(); dm->cache_entries = gf_list_new(); dm->credentials = gf_list_new(); dm->skip_proxy_servers = gf_list_new(); @@ -3990,8 +1907,24 @@ default_cache_dir = NULL; gf_mx_p( dm->cache_mx ); -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HAS_CURL + if (gf_opts_get_bool("core", "curl")) { + dm->curl_multi = curl_multi_init(); + curl_multi_setopt(dm->curl_multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); + dm->curl_mx = gf_mx_new("curl"); + curl_global_init(CURL_GLOBAL_ALL); + } +#endif + dm->disable_http2 = gf_opts_get_bool("core", "no-h2"); +#ifdef GPAC_HAS_NGTCP2 + opt = gf_opts_get_key("core", "h3"); + if (!opt || !strcmp(opt, "auto")) dm->h3_mode = H3_MODE_AUTO; + else if (!strcmp(opt, "first")) dm->h3_mode = H3_MODE_FIRST; + else if (!strcmp(opt, "only")) dm->h3_mode = H3_MODE_ONLY; + else dm->h3_mode = H3_MODE_NO; +#else + dm->h3_mode = H3_MODE_NO; #endif opt = gf_opts_get_key("core", "cache"); @@ -4045,18 +1978,15 @@ dm->allow_offline_cache = gf_opts_get_bool("core", "offline-cache"); - dm->clean_cache = GF_FALSE; dm->allow_broken_certificate = GF_FALSE; + Bool do_clean = GF_TRUE; if ( gf_opts_get_bool("core", "clean-cache")) { - dm->clean_cache = GF_TRUE; - dm->max_cache_size=0; - gf_dm_clean_cache(dm); - } else { - dm->max_cache_size = gf_opts_get_int("core", "cache-size"); - if (dm->max_cache_size) { - gf_dm_clean_cache(dm); - } + dm->max_cache_size = 0; + dm->cur_cache_size = gf_cache_cleanup(dm->cache_directory, dm->max_cache_size); + do_clean = GF_FALSE; } + dm->cache_clean_ms = 1000*gf_opts_get_int("core", "cache-check"); + dm->max_cache_size = gf_opts_get_int("core", "cache-size"); dm->allow_broken_certificate = gf_opts_get_bool("core", "broken-cert"); gf_mx_v( dm->cache_mx ); @@ -4064,9 +1994,9 @@ #ifdef GPAC_HAS_SSL dm->ssl_ctx = NULL; #endif - /* TODO: Not ready for now, we should find a locking strategy between several GPAC instances... - * gf_cache_cleanup_cache(dm); - */ + + if (dm->max_cache_size && do_clean) + dm->cur_cache_size = gf_cache_cleanup(dm->cache_directory, dm->max_cache_size); return dm; } @@ -4084,7 +2014,7 @@ { if (!dm) return; - gf_assert( dm->sessions); + gf_assert( dm->all_sessions); gf_assert( dm->cache_mx ); gf_mx_p( dm->cache_mx ); @@ -4100,12 +2030,12 @@ } /*destroy all pending sessions*/ - while (gf_list_count(dm->sessions)) { - GF_DownloadSession *sess = (GF_DownloadSession *) gf_list_get(dm->sessions, 0); + while (gf_list_count(dm->all_sessions)) { + GF_DownloadSession *sess = (GF_DownloadSession *) gf_list_get(dm->all_sessions, 0); gf_dm_sess_del(sess); } - gf_list_del(dm->sessions); - dm->sessions = NULL; + gf_list_del(dm->all_sessions); + dm->all_sessions = NULL; gf_assert( dm->skip_proxy_servers ); while (gf_list_count(dm->skip_proxy_servers)) { char *serv = (char*)gf_list_get(dm->skip_proxy_servers, 0); @@ -4123,29 +2053,30 @@ gf_list_del( dm->credentials); dm->credentials = NULL; gf_assert( dm->cache_entries ); - { - /* Deletes DownloadedCacheEntry and associated files if required */ - Bool delete_my_files = gf_dm_needs_to_delete_cache(dm); - while (gf_list_count(dm->cache_entries)) { - const DownloadedCacheEntry entry = (const DownloadedCacheEntry)gf_list_get( dm->cache_entries, 0); - gf_list_rem( dm->cache_entries, 0); - if (delete_my_files) - gf_cache_entry_set_delete_files_when_deleted(entry); - gf_cache_delete_entry(entry); - } - gf_list_del( dm->cache_entries ); - dm->cache_entries = NULL; + + /* Deletes DownloadedCacheEntry and associated files if required */ + while (gf_list_count(dm->cache_entries)) { + const DownloadedCacheEntry entry = (const DownloadedCacheEntry)gf_list_pop_front( dm->cache_entries ); + if (!dm->max_cache_size) + gf_cache_entry_set_delete_files_when_deleted(entry); + gf_cache_delete_entry(entry); } + gf_list_del( dm->cache_entries ); + dm->cache_entries = NULL; gf_list_del( dm->partial_downloads ); dm->partial_downloads = NULL; - /* TODO: Not ready for now, we should find a locking strategy between several GPAC instances... - * gf_cache_cleanup_cache(dm); - */ if (dm->cache_directory) gf_free(dm->cache_directory); dm->cache_directory = NULL; +#ifdef GPAC_HAS_CURL + if (dm->curl_multi) { + curl_multi_cleanup(dm->curl_multi); + } + gf_mx_del(dm->curl_mx); +#endif + #ifdef GPAC_HAS_SSL if (dm->ssl_ctx) SSL_CTX_free(dm->ssl_ctx); #endif @@ -4187,7 +2118,7 @@ szDatasess->icy_count = 0; par.error = GF_OK; - par.msg_type = GF_NETIO_PARSE_HEADER; + par.msg_type = GF_NETIO_ICY_META; par.name = "icy-meta"; par.value = szData; par.sess = sess; @@ -4236,11 +2167,11 @@ if (sess->nb_left_in_chunk) { if (sess->nb_left_in_chunk > *payload_size) { sess->nb_left_in_chunk -= (*payload_size); - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP Chunk encoding: still %d bytes to get\n", sess->nb_left_in_chunk)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Chunk encoding: still %d bytes to get\n", sess->log_name, sess->nb_left_in_chunk)); } else { *payload_size = sess->nb_left_in_chunk; sess->nb_left_in_chunk = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP Chunk encoding: last bytes in chunk received\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Chunk encoding: last bytes in chunk received\n", sess->log_name)); } *header_size = 0; return body_start; @@ -4269,7 +2200,7 @@ //cannot parse now, copy over the bytes if (!te_header) { *header_size = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP Chunk encoding: current buffer does not contain enough bytes (%d) to read the size\n", *payload_size)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Chunk encoding: current buffer does not contain enough bytes (%d) to read the size\n", sess->log_name, *payload_size)); return NULL; } @@ -4284,13 +2215,13 @@ if (sep) sep0 = ';'; *header_size = 0; *payload_size = 0; - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP Chunk encoding: fail to read chunk size from buffer %s, aborting\n", body_start)); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Chunk encoding: fail to read chunk size from buffer %s, aborting\n", sess->log_name, body_start)); return NULL; } if (sep) sep0 = ';'; *payload_size = size; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP Chunk Start: Header \"%s\" - header size %d - payload size %d (bytes done %d) at UTC "LLD"\n", body_start, 2+strlen(body_start), size, sess->bytes_done, gf_net_get_utc())); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Chunk Start: Header \"%s\" - header size %d - payload size %d (bytes done %d) at UTC "LLD"\n", sess->log_name, body_start, 2+strlen(body_start), size, sess->bytes_done, gf_net_get_utc())); te_header0 = '\r'; if (!size) @@ -4320,7 +2251,7 @@ if (!runtime) runtime=1; u32 kbps = (u32) ((1000000 * (u64) (sess->bytes_done + sess->cumulated_chunk_header_bytes)) / runtime) / 125; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP bandwidth estimation: download time "LLD" us - bytes %u - chunk rate %u kbps (overall rate rate %u kbps)\n", runtime, sess->bytes_done, sess->bytes_per_sec / 125, kbps)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s bandwidth estimation: download time "LLD" us - bytes %u - chunk rate %u kbps (overall rate rate %u kbps)\n", sess->log_name, runtime, sess->bytes_done, sess->bytes_per_sec / 125, kbps)); } #endif } else { @@ -4329,15 +2260,15 @@ if (!runtime) runtime=1; sess->bytes_per_sec = (u32) ((1000000 * (u64) sess->bytes_done) / runtime); - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP bandwidth estimation: download time "LLD" us - bytes %u - rate %u kbps\n", runtime, sess->bytes_done, sess->bytes_per_sec / 125)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s bandwidth estimation: download time "LLD" us - bytes %u - rate %u kbps\n", sess->log_name, runtime, sess->bytes_done, sess->bytes_per_sec / 125)); } } -static void gf_dm_data_received(GF_DownloadSession *sess, u8 *payload, u32 payload_size, Bool store_in_init, u32 *rewrite_size, u8 *original_payload) +void gf_dm_data_received(GF_DownloadSession *sess, u8 *payload, u32 payload_size, Bool store_in_init, u32 *rewrite_size, u8 *original_payload) { u32 nbBytes, remaining, hdr_size; - u8 *data; + u8* data = NULL; Bool first_chunk_in_payload = GF_TRUE; Bool flush_chunk = GF_FALSE; GF_NETIO_Parameter par; @@ -4391,16 +2322,16 @@ sess->bytes_done += nbBytes; dm_sess_update_download_rate(sess); - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP url %s received %d new bytes (%d kbps)\n", sess->orig_url, nbBytes, 8*sess->bytes_per_sec/1000)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s url %s received %d new bytes (%d kbps)\n", sess->log_name, sess->orig_url, nbBytes, 8*sess->bytes_per_sec/1000)); if (sess->total_size && (sess->bytes_done > sess->total_size)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTP url %s received more bytes than planned!! Got %d bytes vs %d content length\n", sess->orig_url, sess->bytes_done , sess->total_size )); + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s url %s received more bytes than planned!! Got %d bytes vs %d content length\n", sess->log_name, sess->orig_url, sess->bytes_done , sess->total_size )); sess->bytes_done = sess->total_size; } if (sess->icy_metaint > 0) gf_icy_skip_data(sess, (char *) data, nbBytes); else { - if (sess->use_cache_file) + if (sess->use_cache_file && !sess->cached_file) gf_cache_write_to_cache( sess->cache_entry, sess, (char *) data, nbBytes, sess->dm ? sess->dm->cache_mx : NULL); par.msg_type = GF_NETIO_DATA_EXCHANGE; @@ -4415,15 +2346,28 @@ if (sess->total_size && (sess->bytes_done == sess->total_size)) { u64 run_time; -#if 0 //def GPAC_HAS_HTTP2 - if (0 && sess->h2_sess && sess->h2_stream_id) - return; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess && !sess->server_mode) + sess->hmux_stream_id = -1; #endif if (sess->use_cache_file) { + //for chunk transfer or H2/H3 + gf_cache_set_content_length(sess->cache_entry, sess->total_size); gf_cache_close_write_cache(sess->cache_entry, sess, GF_TRUE); - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE url %s saved as %s\n", gf_cache_get_url(sess->cache_entry), gf_cache_get_cache_filename(sess->cache_entry))); + + if (sess->dm && sess->dm->cache_clean_ms) { + sess->dm->cur_cache_size += sess->total_size; + if (sess->dm->cur_cache_size > sess->dm->max_cache_size) { + if (!sess->dm->next_cache_clean) sess->dm->next_cache_clean = gf_sys_clock()+sess->dm->cache_clean_ms; + else if (gf_sys_clock()>sess->dm->next_cache_clean) { + sess->dm->cur_cache_size = gf_cache_cleanup(sess->dm->cache_directory, sess->dm->max_cache_size); + sess->dm->next_cache_clean = 0; + } + } + } } gf_dm_disconnect(sess, HTTP_NO_CLOSE); @@ -4434,11 +2378,12 @@ sess->total_time_since_req = (u32) (gf_sys_clock_high_res() - sess->request_start_time); run_time = gf_sys_clock_high_res() - sess->start_time; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP %s (%d bytes) downloaded in "LLU" us (%d kbps) (%d us since request - got response in %d us)\n", gf_file_basename(gf_cache_get_url(sess->cache_entry)), sess->bytes_done, + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s %s (%d bytes) downloaded in "LLU" us (%d kbps) (%d us since request - got response in %d us)\n", sess->log_name, gf_file_basename(gf_cache_get_url(sess->cache_entry)), sess->bytes_done, run_time, 8*sess->bytes_per_sec/1000, sess->total_time_since_req, sess->reply_time)); if (sess->chunked && (payload_size==2)) payload_size=0; + sess->last_error = GF_OK; } if (rewrite_size && sess->chunked && data) { @@ -4456,16 +2401,18 @@ } } -static Bool dm_exceeds_cap_rate(GF_DownloadManager * dm) +static Bool dm_exceeds_cap_rate(GF_DownloadManager * dm, GF_DownloadSession *for_sess) { u32 cumul_rate = 0; -// u32 nb_sess = 0; - u64 now = gf_sys_clock_high_res(); - u32 i, count = gf_list_count(dm->sessions); + u32 i, count; + gf_mx_p(dm->cache_mx); + u64 now = gf_sys_clock_high_res(); + count = gf_list_count(dm->all_sessions); //check if this fits with all other sessions for (i=0; i<count; i++) { - GF_DownloadSession * sess = (GF_DownloadSession*)gf_list_get(dm->sessions, i); + GF_DownloadSession * sess = (GF_DownloadSession*)gf_list_get(dm->all_sessions, i); + if (for_sess && (sess != for_sess)) continue; //session not running done if (sess->status != GF_NETIO_DATA_EXCHANGE) continue; @@ -4494,13 +2441,19 @@ sess->last_cap_rate_time = now; } } else { + gf_mx_v(dm->cache_mx); return GF_TRUE; } cumul_rate += sess->last_cap_rate_bytes_per_sec; - //nb_sess ++; + if (for_sess) break; } - if ( cumul_rate >= dm->limit_data_rate) + gf_mx_v(dm->cache_mx); + if (for_sess) { + if (cumul_rate >= for_sess->max_data_rate) + return GF_TRUE; + } else if ( cumul_rate >= dm->limit_data_rate) { return GF_TRUE; + } return GF_FALSE; } @@ -4532,9 +2485,6 @@ } } -const u8 *gf_cache_get_content(const DownloadedCacheEntry entry, u32 *size); -void gf_cache_release_content(const DownloadedCacheEntry entry); - GF_EXPORT GF_Err gf_dm_sess_fetch_data(GF_DownloadSession *sess, char *buffer, u32 buffer_size, u32 *read_size) { @@ -4574,10 +2524,19 @@ gf_dm_connect(sess); if (sess->last_error) return sess->last_error; - e = GF_OK; + e = GF_IP_NETWORK_EMPTY; } else if (sess->status < GF_NETIO_DATA_EXCHANGE) { sess->do_requests(sess); - e = sess->last_error; + if (!sess->server_mode + && (sess->status > GF_NETIO_WAIT_FOR_REPLY) + && sess->needs_range + && (sess->rsp_code==200) + ) { + //reset for next call to process if user wants so + sess->needs_range = GF_FALSE; + return GF_IO_BYTE_RANGE_NOT_SUPPORTED; + } + e = sess->last_error ? sess->last_error : GF_IP_NETWORK_EMPTY; } /*we're running but we had data previously*/ else if (sess->init_data) { @@ -4597,44 +2556,57 @@ memmove(sess->init_data, sess->init_data+buffer_size, sizeof(char)*sess->init_data_size); e = GF_OK; } - } else if (sess->local_cache_only) { - u32 to_copy, data_size; - const u8 *ptr; - e = GF_OK; - gf_assert(sess->cache_entry); - //always refresh total size - sess->total_size = gf_cache_get_content_length(sess->cache_entry); + } else if (sess->local_cache_only) { + Bool was_modified; + u32 to_copy, full_cache_size, max_valid_size=0, cache_done; + const u8 *ptr; + e = GF_OK; + gf_assert(sess->cache_entry); + //always refresh total size + sess->total_size = gf_cache_get_content_length(sess->cache_entry); + + ptr = gf_cache_get_content(sess->cache_entry, &full_cache_size, &max_valid_size, &was_modified); - ptr = gf_cache_get_content(sess->cache_entry, &data_size); - if (!ptr) return GF_OUT_OF_MEM; + cache_done = gf_cache_is_done(sess->cache_entry); + //something went wrong, we cannot have less valid bytes than what we had at previous call(s) + if (max_valid_size < sess->bytes_done) + cache_done = 2; - if (sess->bytes_done >= data_size) { - *read_size = 0; + if ((sess->bytes_done >= full_cache_size)|| (cache_done==2)) { + *read_size = 0; gf_cache_release_content(sess->cache_entry); - if (gf_cache_is_done(sess->cache_entry)) { - sess->status = GF_NETIO_DATA_TRANSFERED; - SET_LAST_ERR(GF_OK) - return GF_EOS; - } - return GF_IP_NETWORK_EMPTY; - } - to_copy = data_size - sess->bytes_done; - if (to_copy > buffer_size) to_copy = buffer_size; - - memcpy(buffer, ptr + sess->bytes_done, to_copy); - sess->bytes_done += to_copy; - *read_size = to_copy; - if (gf_cache_is_done(sess->cache_entry)) { - sess->status = GF_NETIO_DATA_TRANSFERED; - SET_LAST_ERR(GF_OK) + if (cache_done==2) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_IP_NETWORK_FAILURE) + return GF_IP_NETWORK_FAILURE; + } + else if (cache_done) { + sess->status = GF_NETIO_DATA_TRANSFERED; + SET_LAST_ERR( GF_OK) + return GF_EOS; + } + return was_modified ? GF_OK : GF_IP_NETWORK_EMPTY; + } + if (!ptr) return GF_OUT_OF_MEM; + + //only copy valid bytes for http + to_copy = max_valid_size - sess->bytes_done; + if (to_copy > buffer_size) to_copy = buffer_size; + + memcpy(buffer, ptr + sess->bytes_done, to_copy); + sess->bytes_done += to_copy; + *read_size = to_copy; + if ((cache_done==1) && (sess->bytes_done == sess->total_size) ) { + sess->status = GF_NETIO_DATA_TRANSFERED; + SET_LAST_ERR( (cache_done==2) ? GF_IP_NETWORK_FAILURE : GF_OK) } else { sess->total_size = 0; } gf_cache_release_content(sess->cache_entry); } else { - if (sess->dm && sess->dm->limit_data_rate) { - if (dm_exceeds_cap_rate(sess->dm)) + if (sess->dm && (sess->dm->limit_data_rate || sess->max_data_rate)) { + if (dm_exceeds_cap_rate(sess->dm, sess->max_data_rate ? sess : NULL)) return GF_IP_NETWORK_EMPTY; if (buffer_size > sess->dm->read_buf_size) @@ -4652,7 +2624,7 @@ if (sess->remaining_data && sess->remaining_data_size) { if (nb_read + sess->remaining_data_size >= buffer_size) { if (!nb_read) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP No HTTP chunk header found for %d bytes, assuming broken chunk transfer and aborting\n", sess->remaining_data_size)); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s No HTTP chunk header found for %d bytes, assuming broken chunk transfer and aborting\n", sess->log_name, sess->remaining_data_size)); return GF_NON_COMPLIANT_BITSTREAM; } break; @@ -4671,8 +2643,9 @@ sess->remaining_data_size = 0; single_read = 0; -#ifdef GPAC_HAS_HTTP2 - if (!sess->h2_sess) +#ifdef GPAC_HTTPMUX + //do NOT call data received if hmux, done once input is empty + if (!sess->hmux_sess || sess->cached_file) #endif gf_dm_data_received(sess, (u8 *) buffer + nb_read, size, GF_FALSE, &single_read, buffer + nb_read); @@ -4683,14 +2656,15 @@ nb_read += single_read; } -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { +#ifdef GPAC_HTTPMUX + //process any received data + if (sess->hmux_sess) { nb_read = 0; - h2_flush_data_ex(sess, buffer, buffer_size, &nb_read); - h2_session_send(sess); + hmux_fetch_data(sess, buffer, buffer_size, &nb_read); + sess->hmux_sess->write(sess); //stream is over and all data flushed, move to GF_NETIO_DATA_TRANSFERED in client mode - if (!sess->h2_stream_id && sess->h2_data_done && !sess->h2_buf.size && !sess->server_mode) { + if ((sess->hmux_stream_id<0) && sess->hmux_data_done && !sess->hmux_buf.size && !sess->server_mode) { sess->status = GF_NETIO_DATA_TRANSFERED; SET_LAST_ERR(GF_OK) } @@ -4705,17 +2679,17 @@ //estimate rate for chunk-transfer - we only do that for fetch_data if (sess->chunked -#ifdef GPAC_HAS_HTTP2 - || sess->h2_sess +#ifdef GPAC_HTTPMUX + || sess->hmux_sess #endif ) gf_dm_sess_estimate_chunk_rate(sess, nb_read); if (! (*read_size) && (e==GF_IP_NETWORK_EMPTY)) { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess && !sess->total_size +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess && !sess->total_size //for client, wait for close - for server move to data_transfered as soon as we're done pushing data - && ((!sess->h2_stream_id && sess->bytes_done) || (sess->h2_data_done && sess->server_mode)) + && (((sess->hmux_stream_id<0) && sess->bytes_done) || (sess->hmux_data_done && sess->server_mode)) ) { sess->status = GF_NETIO_DATA_TRANSFERED; SET_LAST_ERR(GF_OK) @@ -4723,7 +2697,15 @@ } #endif - e = gf_sk_probe(sess->sock); +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + if (sess->status == GF_NETIO_WAIT_FOR_REPLY) + return GF_IP_NETWORK_EMPTY; + } else +#endif + if (sess->sock) + e = gf_sk_probe(sess->sock); + if ((e==GF_IP_CONNECTION_CLOSED) || (sess->request_timeout && (gf_sys_clock_high_res() - sess->last_fetch_time > 1000 * sess->request_timeout)) ) { @@ -4731,6 +2713,9 @@ SET_LAST_ERR(GF_IP_CONNECTION_CLOSED) sess_connection_closed(sess); } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, + ("%s Session timeout for %s after %u ms, aborting\n", sess->log_name, sess->orig_url, sess->request_timeout)); + SET_LAST_ERR(GF_IP_NETWORK_FAILURE) } sess->status = GF_NETIO_STATE_ERROR; @@ -4739,8 +2724,8 @@ } } -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess && sess->h2_buf.size) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess && sess->hmux_buf.size) { return e; } #endif @@ -4766,7 +2751,7 @@ } if (bytes_done) *bytes_done = sess->bytes_done; if (bytes_per_sec) { - if (sess->dm && sess->dm->limit_data_rate && sess->last_cap_rate_bytes_per_sec) { + if (sess->dm && (sess->dm->limit_data_rate || sess->max_data_rate) && sess->last_cap_rate_bytes_per_sec) { *bytes_per_sec = sess->last_cap_rate_bytes_per_sec; } else { *bytes_per_sec = sess->bytes_per_sec; @@ -4800,31 +2785,16 @@ return gf_cache_get_cache_filename(sess->cache_entry); } -#if 0 //unused -/*! - * Tells whether session can be cached on disk. - * Typically, when request has no content length, it deserves being streamed an cannot be cached - * (ICY or MPEG-streamed content -\param sess The session -\param True if a cache can be created - */ -Bool gf_dm_sess_can_be_cached_on_disk(const GF_DownloadSession *sess) -{ - if (!sess) return GF_FALSE; - return gf_cache_get_content_length(sess->cache_entry) != 0; -} -#endif - GF_EXPORT void gf_dm_sess_abort(GF_DownloadSession * sess) { if (sess) { gf_mx_p(sess->mx); -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess && (sess->status==GF_NETIO_DATA_EXCHANGE)) { - nghttp2_submit_rst_stream(sess->h2_sess->ng_sess, NGHTTP2_FLAG_NONE, sess->h2_stream_id, NGHTTP2_NO_ERROR); - h2_session_send(sess); +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess && (sess->status==GF_NETIO_DATA_EXCHANGE)) { + sess->hmux_sess->stream_reset(sess, GF_TRUE); + sess->hmux_sess->write(sess); } #endif gf_dm_disconnect(sess, HTTP_CLOSE); @@ -4838,10 +2808,9 @@ /*! * Sends the HTTP headers \param sess The GF_DownloadSession -\param sHTTP buffer containing the request \return GF_OK if everything went fine, the error otherwise */ -static GF_Err http_send_headers(GF_DownloadSession *sess, char * sHTTP) { +static GF_Err http_send_headers(GF_DownloadSession *sess) { GF_Err e; GF_NETIO_Parameter par; Bool no_cache = GF_FALSE; @@ -4858,23 +2827,32 @@ Bool has_accept, has_connection, has_range, has_agent, has_language, send_profile, has_mime, has_chunk_transfer; assert (sess->status == GF_NETIO_CONNECTED); - gf_dm_sess_clear_headers(sess); + char * sHTTP = sess->http_buf; + gf_assert(sess->remaining_data_size == 0); if (sess->needs_cache_reconfig) { + gf_dm_sess_clear_headers(sess); gf_dm_configure_cache(sess); sess->needs_cache_reconfig = 0; } - if (sess->from_cache_only) { + if (sess->cached_file) { sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res(); sess->req_hdr_size = 0; sess->status = GF_NETIO_WAIT_FOR_REPLY; gf_dm_sess_notify_state(sess, GF_NETIO_WAIT_FOR_REPLY, GF_OK); return GF_OK; } + gf_dm_sess_clear_headers(sess); //in case we got disconnected, reconnect - e = gf_sk_probe(sess->sock); +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) + e = GF_OK; + else +#endif + e = gf_sk_probe(sess->sock); + if (e && (e!=GF_IP_NETWORK_EMPTY)) { sess_connection_closed(sess); if ((e==GF_IP_CONNECTION_CLOSED) && sess->num_retry) { @@ -4888,11 +2866,20 @@ return e; } - /*setup authentification*/ + /*setup authentication*/ strcpy(pass_buf, ""); sess->creds = gf_user_credentials_find_for_site( sess->dm, sess->server_name, NULL); if (sess->creds && sess->creds->valid) { - sprintf(pass_buf, "Basic %s", sess->creds->digest); +#ifdef GPAC_HAS_CURL + //let curl handle authentication methods + if (sess->curl_hnd) { + char szUsrPass101; + u32 len = gf_base64_decode(sess->creds->digest, (u32) strlen(sess->creds->digest), szUsrPass, 100); + szUsrPasslen=0; + curl_easy_setopt(sess->curl_hnd, CURLOPT_USERPWD, szUsrPass); + } else +#endif + sprintf(pass_buf, "Basic %s", sess->creds->digest); } user_agent = gf_opts_get_key("core", "ua"); @@ -4913,8 +2900,8 @@ if (!strcmp(req_name, "GET")) { sess->http_read_type = GET; -#ifdef GPAC_HAS_HTTP2 - if (!sess->h2_sess) +#ifdef GPAC_HTTPMUX + if (!sess->hmux_sess) #endif inject_icy = GF_TRUE; } else if (!strcmp(req_name, "HEAD")) sess->http_read_type = HEAD; @@ -4923,18 +2910,26 @@ if (!strcmp(req_name, "PUT") || !strcmp(req_name, "POST")) sess->put_state = 1; - url = (sess->proxy_enabled==1) ? sess->orig_url : sess->remote_path; + //url is the remote path event if proxy (Host header will point to desired host) + //note that url is not used for CURL, already setup together with proxy + url = sess->remote_path; /*get all headers*/ gf_dm_sess_clear_headers(sess); -#define PUSH_HDR(_name, _value) {\ - GF_SAFEALLOC(hdr, GF_HTTPHeader)\ - hdr->name = gf_strdup(_name);\ - hdr->value = gf_strdup(_value);\ - gf_list_add(sess->headers, hdr);\ +#ifdef GPAC_HTTPMUX + if (!sess->hmux_sess) +#endif + { + //always put port number when proxy is enabled + if (sess->proxy_enabled || ((sess->port!=80) && (sess->port!=443))) { + sprintf(sHTTP, "%s:%u", sess->server_name, sess->port); + PUSH_HDR("Host", sHTTP); + } else { + PUSH_HDR("Host", sess->server_name); } + } has_agent = has_accept = has_connection = has_range = has_language = has_mime = has_chunk_transfer = GF_FALSE; while (1) { @@ -4942,6 +2937,8 @@ par.value = NULL; gf_dm_sess_user_io(sess, &par); if (!par.value) break; + //if name is not set, skip this header + if (!par.name) continue; if (!stricmp(par.name, "Connection")) { if (!stricmp(par.value, "close")) @@ -4972,49 +2969,44 @@ if (!has_accept && (sess->http_read_type!=OTHER) ) PUSH_HDR("Accept", "*/*") -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) has_connection = GF_TRUE; +#endif + - if (!has_connection && !sess->h2_sess && !sess->h2_upgrade_state +#ifdef GPAC_HAS_HTTP2 + //inject upgrade for h2 + if (!has_connection && !sess->hmux_sess +#ifdef GPAC_HAS_CURL + && !sess->curl_hnd +#endif #ifdef GPAC_HAS_SSL && !sess->ssl #endif - && !sess->dm->disable_http2 && (sess->h2_upgrade_state!=2) + && !sess->dm->disable_http2 + && !sess->h2_upgrade_state && !gf_opts_get_bool("core", "no-h2c") ) { - u8 settingsHTTP2_BUFFER_SETTINGS_SIZE; - u32 settings_len; - u8 b64100; - u32 b64len; - - nghttp2_settings_entry h2_settings2 = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, - {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} - }; - - PUSH_HDR("Connection", "Upgrade, HTTP2-Settings") - PUSH_HDR("Upgrade", "h2c") - - - settings_len = (u32) nghttp2_pack_settings_payload(settings, HTTP2_BUFFER_SETTINGS_SIZE, h2_settings, GF_ARRAY_LENGTH(h2_settings)); - b64len = gf_base64_encode(settings, settings_len, b64, 100); - b64b64len = 0; - PUSH_HDR("HTTP2-Settings", b64) - - sess->h2_upgrade_state = 1; + http2_set_upgrade_headers(sess); inject_icy = GF_FALSE; } else +#endif //GPAC_HAS_HTTP2 + + if (sess->proxy_enabled==1) { +#ifdef GPAC_HTTPMUX + if (!sess->hmux_sess) #endif - if (sess->proxy_enabled==1) PUSH_HDR("Proxy-Connection", "Keep-alive") + PUSH_HDR("Proxy-Connection", "Keep-alive") + } else if (!has_connection) { PUSH_HDR("Connection", "Keep-Alive"); } if (has_chunk_transfer -#ifdef GPAC_HAS_HTTP2 - && !sess->h2_sess +#ifdef GPAC_HTTPMUX + && !sess->hmux_sess #endif ) { PUSH_HDR("Transfer-Encoding", "chunked"); @@ -5101,60 +3093,89 @@ param_string = gf_opts_get_key("core", "query-string"); -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { Bool has_body = GF_FALSE; gf_mx_p(sess->mx); - sess->h2_send_data = NULL; + sess->hmux_is_eos = 0; + sess->hmux_send_data = NULL; if (par.data && par.size) { has_body = GF_TRUE; - sess->h2_send_data = (u8 *) par.data; - sess->h2_send_data_len = par.size; - sess->h2_is_eos = GF_TRUE; + sess->hmux_send_data = (u8 *) par.data; + sess->hmux_send_data_len = par.size; + sess->hmux_is_eos = 1; } else if (sess->put_state==1) { has_body = GF_TRUE; } - - e = h2_submit_request(sess, req_name, url, param_string, has_body); + e = sess->hmux_sess->submit_request(sess, req_name, url, param_string, has_body); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Sending request %s %s:%u%s\n", req_name, sess->server_name, sess->port, url)); sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res(); - if (!e) - e = h2_session_send(sess); - //in case we have a body already setup with this request - h2_flush_send(sess, GF_FALSE); + if (!e || (e==GF_IP_NETWORK_EMPTY)) { + e = sess->hmux_sess->send_pending_data(sess); + if (e==GF_IP_NETWORK_EMPTY) e = GF_OK; + } gf_mx_v(sess->mx); - - sess->h2_is_eos = GF_FALSE; goto req_sent; } -#endif // GPAC_HAS_HTTP2 +#endif // GPAC_HTTPMUX + +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + if (!sess->curl_not_http) + curl_easy_setopt(sess->curl_hnd, CURLOPT_CUSTOMREQUEST, req_name); + } else +#endif + { - if (param_string) { - if (strchr(sess->remote_path, '?')) { - sprintf(sHTTP, "%s %s&%s HTTP/1.1\r\nHost: %s\r\n", req_name, url, param_string, sess->server_name); + if (param_string) { + if (strchr(sess->remote_path, '?')) { + sprintf(sHTTP, "%s %s&%s HTTP/1.1\r\n", req_name, url, param_string); + } else { + sprintf(sHTTP, "%s %s?%s HTTP/1.1\r\n", req_name, url, param_string); + } } else { - sprintf(sHTTP, "%s %s?%s HTTP/1.1\r\nHost: %s\r\n", req_name, url, param_string, sess->server_name); + sprintf(sHTTP, "%s %s HTTP/1.1\r\n", req_name, url); } - } else { - sprintf(sHTTP, "%s %s HTTP/1.1\r\nHost: %s\r\n", req_name, url, sess->server_name); } //serialize headers count = gf_list_count(sess->headers); for (i=0; i<count; i++) { hdr = gf_list_get(sess->headers, i); +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + if (sess->curl_not_http) continue; + char szHDR1000; + sprintf(szHDR, "%s: %s", hdr->name, hdr->value); + sess->curl_hdrs = curl_slist_append(sess->curl_hdrs, szHDR); + continue; + } +#endif strcat(sHTTP, hdr->name); strcat(sHTTP, ": "); strcat(sHTTP, hdr->value); strcat(sHTTP, "\r\n"); } +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + if (!sess->curl_not_http) { + curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTPHEADER, sess->curl_hdrs); + } + } +#endif strcat(sHTTP, "\r\n"); +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + gf_assert(!sess->curl_hnd_registered); + } else +#endif if (send_profile || par.data) { u32 len = (u32) strlen(sHTTP); char *tmp_buf = (char*)gf_malloc(sizeof(char)*(len+par.size+1)); @@ -5173,14 +3194,14 @@ s32 read = (s32) gf_fread(tmp_buf+len, par.size, profile); if ((read<0) || (read< (s32) par.size)) { GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, - ("HTTP Error while loading UserProfile, size=%d, should be %d.", read, par.size)); + ("%s Error while loading UserProfile, size=%d, should be %d\n", sess->log_name, read, par.size)); for (; read < (s32) par.size; read++) { tmp_buflen + read = 0; } } gf_fclose(profile); } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTP Error while loading Profile file %s.", user_profile)); + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s Error while loading Profile file %s\n", sess->log_name, user_profile)); } } @@ -5189,7 +3210,7 @@ e = dm_sess_write(sess, tmp_buf, len+par.size); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Sending request at UTC "LLD" %s\n\n", gf_net_get_utc(), tmp_buf)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Sending request to %s %s\n\n", sess->log_name, sess->server_name, tmp_buf)); gf_free(tmp_buf); } else { u32 len = (u32) strlen(sHTTP); @@ -5201,15 +3222,15 @@ #ifndef GPAC_DISABLE_LOG if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP Error sending request %s\n", gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Error sending request %s\n", sess->log_name, gf_error_to_string(e) )); } else { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Sending request at UTC "LLU" %s\n\n", gf_net_get_utc(), sHTTP)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Sending request to %s %s\n\n", sess->log_name, sess->server_name, sHTTP)); } #endif } -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX req_sent: #endif gf_dm_sess_clear_headers(sess); @@ -5228,6 +3249,13 @@ sess->status = GF_NETIO_WAIT_FOR_REPLY; } SET_LAST_ERR(GF_OK) + +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res(); + curl_flush(sess); + } +#endif return GF_OK; } @@ -5235,41 +3263,40 @@ /*! * Parse the remaining part of body \param sess The session -\param sHTTP the data buffer \return The error code if any */ -static GF_Err http_parse_remaining_body(GF_DownloadSession * sess, char * sHTTP) +static GF_Err http_parse_remaining_body(GF_DownloadSession * sess) { GF_Err e; - u32 buf_size = sess->dm ? sess->dm->read_buf_size : GF_DOWNLOAD_BUFFER_SIZE; while (1) { u32 prev_remaining_data_size, size=0, rewrite_size=0; if (sess->status>=GF_NETIO_DISCONNECTED) return GF_REMOTE_SERVICE_ERROR; - if (sess->dm && sess->dm->limit_data_rate && sess->bytes_per_sec) { - if (dm_exceeds_cap_rate(sess->dm)) { - gf_sleep(1); + if (sess->dm && sess->bytes_per_sec && (sess->max_data_rate || sess->dm->limit_data_rate)) { + sess->rate_regulated = GF_FALSE; + if (dm_exceeds_cap_rate(sess->dm, sess->max_data_rate ? sess : NULL)) { + sess->rate_regulated = GF_TRUE; return GF_OK; } } //the data remaining from the last buffer (i.e size for chunk that couldn't be read because the buffer does not contain enough bytes) if (sess->remaining_data && sess->remaining_data_size) { - if (sess->remaining_data_size >= buf_size) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP No HTTP chunk header found for %d bytes, assuming broken chunk transfer and aborting\n", sess->remaining_data_size)); + if (sess->remaining_data_size >= sess->http_buf_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s No HTTP chunk header found for %d bytes, assuming broken chunk transfer and aborting\n", sess->log_name, sess->remaining_data_size)); return GF_NON_COMPLIANT_BITSTREAM; } - memcpy(sHTTP, sess->remaining_data, sess->remaining_data_size); + memcpy(sess->http_buf, sess->remaining_data, sess->remaining_data_size); } - e = gf_dm_read_data(sess, sHTTP + sess->remaining_data_size, buf_size - sess->remaining_data_size, &size); + e = gf_dm_read_data(sess, sess->http_buf + sess->remaining_data_size, sess->http_buf_size - sess->remaining_data_size, &size); if ((e != GF_IP_CONNECTION_CLOSED) && (!size || e == GF_IP_NETWORK_EMPTY)) { if (!sess->total_size && !sess->chunked && (gf_sys_clock_high_res() - sess->start_time > 5000000)) { sess->total_size = sess->bytes_done; gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK); gf_assert(sess->server_name); - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP Disconnected from %s: %s\n", sess->server_name, gf_error_to_string(e))); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Disconnected from %s: %s\n", sess->log_name, sess->server_name, gf_error_to_string(e))); gf_dm_disconnect(sess, HTTP_NO_CLOSE); } return GF_OK; @@ -5279,12 +3306,12 @@ if (sess->sock && (e == GF_IP_CONNECTION_CLOSED)) { u32 len = gf_cache_get_content_length(sess->cache_entry); if (size > 0) { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - h2_flush_data(sess, GF_FALSE); +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + hmux_flush_internal_data(sess, GF_FALSE); } else #endif - gf_dm_data_received(sess, (u8 *) sHTTP, size, GF_FALSE, NULL, NULL); + gf_dm_data_received(sess, (u8 *) sess->http_buf, size, GF_FALSE, NULL, NULL); } if ( ( (len == 0) && sess->use_cache_file) || sess->bytes_done) { @@ -5292,7 +3319,7 @@ // HTTP 1.1 without content length... gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK); gf_assert(sess->server_name); - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP Disconnected from %s: %s\n", sess->server_name, gf_error_to_string(e))); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Disconnected from %s: %s\n", sess->log_name, sess->server_name, gf_error_to_string(e))); if (sess->use_cache_file) gf_cache_set_content_length(sess->cache_entry, sess->bytes_done); e = GF_OK; @@ -5307,52 +3334,39 @@ prev_remaining_data_size = sess->remaining_data_size; sess->remaining_data_size = 0; - sHTTPsize + prev_remaining_data_size = 0; + sess->http_bufsize + prev_remaining_data_size = 0; -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - h2_flush_data(sess, GF_FALSE); - if (!sess->h2_stream_id) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + hmux_flush_internal_data(sess, GF_FALSE); + if (sess->hmux_stream_id<0) { sess->status = GF_NETIO_DATA_TRANSFERED; SET_LAST_ERR(GF_OK) } } else #endif - gf_dm_data_received(sess, (u8 *) sHTTP, size + prev_remaining_data_size, GF_FALSE, &rewrite_size, NULL); + gf_dm_data_received(sess, (u8 *) sess->http_buf, size + prev_remaining_data_size, GF_FALSE, &rewrite_size, NULL); if (sess->chunked) gf_dm_sess_estimate_chunk_rate(sess, rewrite_size); /*socket empty*/ - if (size < buf_size) { + if (size < sess->http_buf_size) { return GF_OK; } } return GF_OK; } -static void notify_headers(GF_DownloadSession *sess, char * sHTTP, s32 bytesRead, s32 BodyStart) +static void notify_error_body(GF_DownloadSession *sess, char * sHTTP, s32 bytesRead, s32 BodyStart) { GF_NETIO_Parameter par; - u32 i, count; - - count = gf_list_count(sess->headers); - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - - for (i=0; i<count; i++) { - GF_HTTPHeader *hdrp = (GF_HTTPHeader*)gf_list_get(sess->headers, i); - par.name = hdrp->name; - par.value = hdrp->value; - - par.error = GF_OK; - par.msg_type = GF_NETIO_PARSE_HEADER; - gf_dm_sess_user_io(sess, &par); - } if (sHTTP) { sHTTPbytesRead=0; - par.error = GF_OK; + par.error = GF_BAD_PARAM; + par.reply = sess->rsp_code; par.data = sHTTP + BodyStart; par.size = (u32) strlen(par.data); par.msg_type = GF_NETIO_DATA_EXCHANGE; @@ -5376,17 +3390,17 @@ /*! * Waits for the response HEADERS, parse the information... and so on \param sess The session -\param sHTTP the data buffer */ -static GF_Err wait_for_header_and_parse(GF_DownloadSession *sess, char * sHTTP) +static GF_Err wait_for_header_and_parse(GF_DownloadSession *sess) { GF_NETIO_Parameter par; - s32 bytesRead, BodyStart; - u32 res, i, buf_size; + s32 bytesRead=0, BodyStart=0; + u32 res, i, buf_size = sess->http_buf_size; s32 LinePos, Pos; u32 method=0; - u32 rsp_code=0, ContentLength, first_byte, last_byte, total_size, range, no_range; + u32 ContentLength=0, first_byte, last_byte, total_size, range, no_range; Bool connection_closed = GF_FALSE; + Bool has_content_length = GF_FALSE; Bool connection_keep_alive = GF_FALSE; u32 connection_timeout=0; char buf1025; @@ -5398,6 +3412,9 @@ Bool upgrade_to_http2 = GF_FALSE; #endif + char *sHTTP = sess->http_buf; + sHTTP0 = 0; + if (sess->creds && sess->creds->req_state) { if (sess->creds->req_state==GF_CREDS_STATE_PENDING) return GF_OK; @@ -5423,7 +3440,6 @@ return e; } - if (sess->server_mode) { gf_fatal_assert( sess->status == GF_NETIO_CONNECTED ); } else { @@ -5435,20 +3451,28 @@ bytesRead = res = 0; new_location = NULL; - if (sess->from_cache_only) { + if (sess->cached_file) { sess->reply_time = (u32) (gf_sys_clock_high_res() - sess->request_start_time); sess->rsp_hdr_size = 0; - sess->total_size = sess->bytes_done = gf_cache_get_content_length(sess->cache_entry); + sess->total_size = gf_cache_get_content_length(sess->cache_entry); + sess->bytes_done = 0; memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.msg_type = GF_NETIO_DATA_TRANSFERED; + par.msg_type = GF_NETIO_DATA_EXCHANGE; par.error = GF_OK; gf_dm_sess_user_io(sess, &par); - gf_dm_disconnect(sess, HTTP_NO_CLOSE); + sess->status = GF_NETIO_DATA_EXCHANGE; +// gf_dm_disconnect(sess, HTTP_NO_CLOSE); return GF_OK; } - buf_size = sess->dm ? sess->dm->read_buf_size : GF_DOWNLOAD_BUFFER_SIZE; +#ifdef GPAC_HAS_CURL + if (sess->curl_hnd) { + e = curl_process_reply(sess, &ContentLength); + if (e) return e; + goto process_reply; + } +#endif //always set start time to the time at last attempt reply parsing sess->start_time = gf_sys_clock_high_res(); @@ -5460,16 +3484,21 @@ sess->chunk_bytes = 0; sess->cumulated_chunk_rate = 0; - sHTTP0 = 0; - while (1) { Bool probe = (!bytesRead || (sess->flags & GF_NETIO_SESSION_NO_BLOCK) ) ? GF_TRUE : GF_FALSE; - e = gf_dm_read_data(sess, sHTTP + bytesRead, buf_size - bytesRead, &res); +#ifdef GPAC_HAS_NGTCP2 + if (sess->server_mode && sess->hmux_sess && (sess->hmux_sess->net_sess->flags & GF_NETIO_SESSION_USE_QUIC)) { + GF_Err h3_check_sess(GF_DownloadSession *sess); + probe = GF_FALSE; + e = h3_check_sess(sess); + } else +#endif + e = gf_dm_read_data(sess, sHTTP + bytesRead, buf_size - bytesRead, &res); -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX /* break as soon as we have a header frame*/ - if (sess->h2_headers_seen) { - sess->h2_headers_seen = 0; + if (sess->hmux_headers_seen) { + sess->hmux_headers_seen = 0; res = 0; bytesRead = 0; BodyStart = 0; @@ -5478,6 +3507,8 @@ break; } #endif + //should not happen, but do it for safety + if (!res && !bytesRead && !e) e = GF_IP_NETWORK_EMPTY; switch (e) { case GF_IP_NETWORK_EMPTY: @@ -5494,6 +3525,8 @@ && sess->request_timeout && (gf_sys_clock_high_res() - sess->request_start_time > 1000 * sess->request_timeout) ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, + ("%s Session timeout for %s after %u ms, aborting\n", sess->log_name, sess->orig_url, sess->request_timeout)); SET_LAST_ERR(GF_IP_NETWORK_FAILURE) sess->status = GF_NETIO_STATE_ERROR; return GF_IP_NETWORK_FAILURE; @@ -5512,9 +3545,9 @@ if (!res && sess->status<=GF_NETIO_CONNECTED) return GF_OK; -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX //we may have received bytes (bytesRead>0) yet none for this session, return GF_IP_NETWORK_EMPTY if empty - if (sess->h2_sess) + if (sess->hmux_sess) return GF_IP_NETWORK_EMPTY; #endif @@ -5529,7 +3562,7 @@ SET_LAST_ERR(GF_IP_CONNECTION_CLOSED) sess_connection_closed(sess); sess->status = GF_NETIO_DISCONNECTED; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Connection closed by client\n", sess->remote_path)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connection closed by client\n", sess->log_name, sess->remote_path)); return GF_IP_CONNECTION_CLOSED; } gf_dm_disconnect(sess, HTTP_RESET_CONN); @@ -5540,16 +3573,16 @@ && !(sess->flags & GF_DOWNLOAD_SESSION_USE_SSL) && !gf_opts_get_bool("core", "no-tls-rcfg") ) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Connection closed by server when processing %s - retrying using SSL\n", sess->remote_path)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connection closed by server when processing %s - retrying using SSL\n", sess->log_name, sess->remote_path)); sess->flags |= GF_DOWNLOAD_SESSION_SSL_FORCED; } else #endif { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Connection closed by server when processing %s - retrying\n", sess->remote_path)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connection closed by server when processing %s - retrying\n", sess->log_name, sess->remote_path)); } sess->status = GF_NETIO_SETUP; } else { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Connection closed by server when processing %s - aborting\n", sess->remote_path)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connection closed by server when processing %s - aborting\n", sess->log_name, sess->remote_path)); SET_LAST_ERR(e) sess->status = GF_NETIO_STATE_ERROR; } @@ -5564,12 +3597,12 @@ } bytesRead += res; -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX //in case we got a refused stream if (sess->status==GF_NETIO_SETUP) { return GF_OK; } - if (sess->h2_sess) + if (sess->hmux_sess) continue; #endif @@ -5599,7 +3632,7 @@ } else { sess->async_req_reply_size -= res; } - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTP End of chunk found while waiting server response when processing %s - retrying\n", sess->remote_path)); + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s End of chunk found while waiting server response when processing %s - retrying\n", sess->log_name, sess->remote_path)); continue; } @@ -5616,7 +3649,7 @@ } } - no_range = range = ContentLength = first_byte = last_byte = total_size = rsp_code = 0; + no_range = range = ContentLength = first_byte = last_byte = total_size = sess->rsp_code = 0; if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) { sHTTP = sess->async_req_reply; @@ -5624,8 +3657,8 @@ sess->async_req_reply_size = 0; } -#ifdef GPAC_HAS_HTTP2 - if (!sess->h2_sess) { +#ifdef GPAC_HTTPMUX + if (!sess->hmux_sess) { #endif if (bytesRead < 0) { e = GF_REMOTE_SERVICE_ERROR; @@ -5636,7 +3669,7 @@ BodyStart = bytesRead; if (BodyStart) sHTTPBodyStart-1 = 0; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP %s\n\n", sHTTP)); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s %s\n\n", sess->log_name, sHTTP)); sess->reply_time = (u32) (gf_sys_clock_high_res() - sess->request_start_time); sess->rsp_hdr_size = BodyStart; @@ -5658,9 +3691,9 @@ } //flush potential body except for PUT/POST if ((method==GF_HTTP_PUT) || (method==GF_HTTP_POST)) - rsp_code = 200; + sess->rsp_code = 200; else - rsp_code = 300; + sess->rsp_code = 300; } else { if (!strncmp("ICY", comp, 3)) { @@ -5677,7 +3710,7 @@ e = GF_REMOTE_SERVICE_ERROR; goto exit; } - rsp_code = (u32) atoi(comp); + sess->rsp_code = (u32) atoi(comp); /*Pos = */gf_token_get(buf, Pos, " \r\n", comp, 400); } @@ -5719,26 +3752,36 @@ } } -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX } #endif + +#ifdef GPAC_HAS_CURL +process_reply: +#endif + if (!sess->server_mode) { Bool cache_no_store = GF_FALSE; + Bool cache_must_revalidate = GF_FALSE; + u32 delta_age = 0; + u32 max_age = 0; + //default pre-processing of headers - needs cleanup, not all of these have to be parsed before checking reply code for (i=0; i<gf_list_count(sess->headers); i++) { char *val; GF_HTTPHeader *hdr = (GF_HTTPHeader*)gf_list_get(sess->headers, i); -#ifdef GPAC_HAS_HTTP2 +#ifdef GPAC_HTTPMUX if (!stricmp(hdr->name, ":status") ) { - rsp_code = (u32) atoi(hdr->value); + sess->rsp_code = (u32) atoi(hdr->value); } else #endif if (!stricmp(hdr->name, "Content-Length") ) { ContentLength = (u32) atoi(hdr->value); + has_content_length=GF_TRUE; - if ((rsp_code<300) && sess->cache_entry) + if ((sess->rsp_code<300) && sess->cache_entry) gf_cache_set_content_length(sess->cache_entry, ContentLength); } @@ -5757,7 +3800,7 @@ if (val) val0 = 0; strlwr(mime); - if (rsp_code<300) { + if (sess->rsp_code<300) { if (sess->cache_entry) { gf_cache_set_mime_type(sess->cache_entry, mime); } else { @@ -5770,13 +3813,19 @@ else if (!stricmp(hdr->name, "Content-Range")) { if (!strnicmp(hdr->value, "bytes", 5)) { val = hdr->value + 5; - if (val0 == ':') val += 1; - while (val0 == ' ') val += 1; + while (strchr(":= ", val0)) + val++; if (val0 == '*') { sscanf(val, "*/%u", &total_size); + sess->full_resource_size = total_size; + sess->rsp_code = 416; + } else if (strstr(val, "/*")) { + sscanf(val, "%u-%u/*", &first_byte, &last_byte); + sess->full_resource_size = 0; } else { sscanf(val, "%u-%u/%u", &first_byte, &last_byte, &total_size); + sess->full_resource_size = total_size; } } } @@ -5794,21 +3843,52 @@ sess->icy_metaint = atoi(hdr->value); } } + else if (!stricmp(hdr->name, "Age")) { + sscanf(hdr->value, "%u", &delta_age); + } else if (!stricmp(hdr->name, "Cache-Control")) { - if (strstr(hdr->value, "no-store")) { - cache_no_store = GF_TRUE; + char *hval = hdr->value; + while (hval0) { + char *hsep = strchr(hval, ','); + if (hsep) hsep0 = 0; + while (hval0==' ') hval++; + char *vsep = strchr(hval, '='); + if (vsep) vsep0 = 0; + if (!strcmp(hval, "no-store")) cache_no_store = GF_TRUE; + else if (!strcmp(hval, "no-cache")) cache_no_store = GF_TRUE; + else if (!strcmp(hval, "private")) { + //we need a way to differentiate proxy modes and client modes + } + else if (!strcmp(hval, "public")) {} + else if (!strcmp(hval, "max-age") && vsep && !max_age) { + sscanf(vsep+1, "%u", &max_age); + } + else if (!strcmp(hval, "s-maxage") && vsep) { + sscanf(vsep+1, "%u", &max_age); + } + else if (!strcmp(hval, "must-revalidate")) cache_must_revalidate = GF_TRUE; + else if (!strcmp(hval, "proxy-revalidate")) cache_must_revalidate = GF_TRUE; + + if (vsep) vsep0 = '='; + if (!hsep) break; + hsep0 = ','; + hval = hsep+1; } } else if (!stricmp(hdr->name, "ETag")) { - if (rsp_code<300) + if (sess->rsp_code<300) gf_cache_set_etag_on_server(sess->cache_entry, hdr->value); } else if (!stricmp(hdr->name, "Last-Modified")) { - if (rsp_code<300) + if (sess->rsp_code<300) gf_cache_set_last_modified_on_server(sess->cache_entry, hdr->value); } else if (!stricmp(hdr->name, "Transfer-Encoding")) { - if (!stricmp(hdr->value, "chunked")) + if (!stricmp(hdr->value, "chunked") +#ifdef GPAC_HAS_CURL + && !sess->curl_hnd +#endif + ) sess->chunked = GF_TRUE; } else if (!stricmp(hdr->name, "X-UserProfileID") ) { @@ -5846,18 +3926,23 @@ if (sess->status==GF_NETIO_DISCONNECTED) return GF_OK; } - if ((sess->flags & GF_NETIO_SESSION_AUTO_CACHE) && !ContentLength && (rsp_code>=200) && (rsp_code<300) ) { + if ((sess->flags & GF_NETIO_SESSION_AUTO_CACHE) && !ContentLength && (sess->rsp_code>=200) && (sess->rsp_code<300) ) { sess->use_cache_file = GF_FALSE; if (sess->cache_entry) { + gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry); gf_cache_remove_session_from_cache_entry(sess->cache_entry, sess); sess->cache_entry = NULL; } } + sess->flags &= ~GF_NETIO_SESSION_NO_STORE; if (cache_no_store) { - if (sess->cache_entry && !ContentLength && !sess->chunked && (rsp_code<300) -#ifdef GPAC_HAS_HTTP2 - && !sess->h2_sess + gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry); + sess->flags |= GF_NETIO_SESSION_NO_STORE; + + if (sess->cache_entry && !ContentLength && !sess->chunked && (sess->rsp_code<300) +#ifdef GPAC_HTTPMUX + && !sess->hmux_sess #endif ) { sess->use_cache_file = GF_FALSE; @@ -5865,6 +3950,11 @@ sess->cache_entry = NULL; } } + else if (sess->cache_entry) { + if (max_age) max_age += delta_age; + gf_cache_set_max_age(sess->cache_entry, max_age, cache_must_revalidate); + } + if (no_range) first_byte = 0; @@ -5874,59 +3964,49 @@ if (connection_keep_alive && !gf_opts_get_bool("core", "no-timeout") && !sess->server_mode -#ifdef GPAC_HAS_HTTP2 - && !sess->h2_sess +#ifdef GPAC_HTTPMUX + && !sess->hmux_sess #endif ) { sess->connection_timeout_ms = connection_timeout*1000; } + } else { + //server mode, start timers as soon as we see the request headers + sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res(); + } + + //if we issued an open-range from end of file till unknown we may get a 416. If the server is indicating + //resource size and it matches our range, move to 206 + if ((sess->rsp_code==416) && (sess->range_start==sess->full_resource_size)) { + sess->rsp_code = 206; + ContentLength = 0; + has_content_length = GF_TRUE; + } + //if no start range, a server may reply with 200 if open end range or if end range is file size + //move this to 200 to avoid triggering a byte range not supported detection + else if (sess->needs_range && (sess->rsp_code==200) && !sess->range_start && (!sess->range_end || (sess->range_end+1==ContentLength))) { + sess->rsp_code = 206; } par.msg_type = GF_NETIO_PARSE_REPLY; par.error = GF_OK; - par.reply = rsp_code; + par.reply = sess->rsp_code; par.value = comp; /* * If response is correct, it means our credentials are correct */ - if (sess->creds && rsp_code != 304) + if (sess->creds && sess->rsp_code != 304) sess->creds->valid = GF_TRUE; #ifdef GPAC_HAS_HTTP2 - if ((rsp_code == 101) && upgrade_to_http2) { - int rv; - u8 settingsHTTP2_BUFFER_SETTINGS_SIZE; - u32 settings_len; - - nghttp2_settings_entry h2_settings2 = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, - {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} - }; - settings_len = (u32) nghttp2_pack_settings_payload(settings, HTTP2_BUFFER_SETTINGS_SIZE, h2_settings, GF_ARRAY_LENGTH(h2_settings)); - - h2_initialize_session(sess); - sess->h2_stream_id = 1; - rv = nghttp2_session_upgrade2(sess->h2_sess->ng_sess, settings, settings_len, (sess->http_read_type==1) ? 1 : 0, sess); - if (rv < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 nghttp2_session_upgrade2 error: %s\n", nghttp2_strerror(rv))); - return GF_IP_NETWORK_FAILURE; - } - //push the body + if ((sess->rsp_code == 101) && upgrade_to_http2) { + char *body = NULL; + u32 body_len = 0; if (bytesRead > BodyStart) { - rv = (int) nghttp2_session_mem_recv(sess->h2_sess->ng_sess, sHTTP + BodyStart , bytesRead - BodyStart); - if (rv < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 nghttp2_session_mem_recv error: %s\n", nghttp2_strerror(rv))); - return GF_IP_NETWORK_FAILURE; - } - //stay in WAIT_FOR_REPLY state and do not flush data, cache is not fully configured yet + body = sHTTP + BodyStart; + body_len = bytesRead - BodyStart; } - //send pending frames - e = h2_session_send(sess); - if (e) return e; - sess->connection_close = GF_FALSE; - sess->h2_upgrade_state = 2; - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP Upgraded connection to HTTP/2\n")); - return GF_OK; + return http2_do_upgrade(sess, body, body_len); } if (sess->h2_upgrade_state<4) sess->h2_upgrade_state = 2; @@ -5934,9 +4014,12 @@ /*try to flush body */ - if ((rsp_code>=300) -#ifdef GPAC_HAS_HTTP2 - && !sess->h2_sess + if ((sess->rsp_code>=300) +#ifdef GPAC_HTTPMUX + && !sess->hmux_sess +#endif +#ifdef GPAC_HAS_CURL + && !sess->curl_hnd #endif ) { u32 start = gf_sys_clock(); @@ -5956,7 +4039,7 @@ break; //does not fit in our buffer, too bad we'll kill the connection - if (bytesRead == GF_DOWNLOAD_BUFFER_SIZE) + if (bytesRead == buf_size) break; } @@ -5967,21 +4050,28 @@ } } -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { u32 count = gf_list_count(sess->headers); for (i=0; i<count; i++) { GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); if (!stricmp(hdr->name, ":method")) { method = http_parse_method(hdr->value); - rsp_code = 200; + sess->rsp_code = 200; } else if (!stricmp(hdr->name, ":path")) { if (sess->orig_url) gf_free(sess->orig_url); sess->orig_url = gf_strdup(hdr->value); } } - } else if (sess->server_mode && !gf_opts_get_bool("core", "no-h2") && !gf_opts_get_bool("core", "no-h2c")) { + } +#endif +#ifdef GPAC_HAS_HTTP2 + else if (sess->server_mode + && !gf_opts_get_bool("core", "no-h2") + && !gf_opts_get_bool("core", "no-h2c") + && !(sess->flags & GF_NETIO_SESSION_USE_QUIC) + ) { Bool is_upgradeable = GF_FALSE; char *h2_settings = NULL; u32 count = gf_list_count(sess->headers); @@ -6004,14 +4094,13 @@ } #endif - if (sess->server_mode) { if (ContentLength) { par.data = sHTTP + BodyStart; par.size = ContentLength; } else if ((BodyStart < (s32) bytesRead) -#ifdef GPAC_HAS_HTTP2 - && !sess->h2_sess +#ifdef GPAC_HTTPMUX + && !sess->hmux_sess #endif ) { if (sess->init_data) gf_free(sess->init_data); @@ -6031,9 +4120,10 @@ } //remember if we can keep the session alive after the transfer is done sess->connection_close = connection_closed; - gf_assert(rsp_code); + gf_assert(sess->rsp_code); + - switch (rsp_code) { + switch (sess->rsp_code) { //100 continue case 100: break; @@ -6048,25 +4138,27 @@ if (sess->dm) gf_list_add(sess->dm->skip_proxy_servers, gf_strdup(sess->server_name)); } + sess->nb_redirect=0; break; /*redirection: extract the new location*/ case 301: case 302: case 303: case 307: - if (!new_location || !strlen(new_location) ) { + if ((sess->nb_redirect > 4) || !new_location || !strlen(new_location) ) { gf_dm_sess_user_io(sess, &par); e = GF_URL_ERROR; goto exit; } while ( - (new_locationstrlen(new_location)-1 == '\n') - || (new_locationstrlen(new_location)-1 == '\r') ) + (new_locationstrlen(new_location)-1 == '\n') + || (new_locationstrlen(new_location)-1 == '\r') ) new_locationstrlen(new_location)-1 = 0; /*reset and reconnect*/ gf_dm_disconnect(sess, HTTP_CLOSE); sess->status = GF_NETIO_SETUP; + sess->nb_redirect++; e = gf_dm_sess_setup_from_url(sess, new_location, GF_FALSE); if (e) { sess->status = GF_NETIO_STATE_ERROR; @@ -6077,23 +4169,26 @@ return e; case 304: { - sess->status = GF_NETIO_PARSE_REPLY; gf_assert(sess->cache_entry); - sess->total_size = gf_cache_get_cache_filesize(sess->cache_entry); + gf_assert(!sess->cached_file); - gf_dm_sess_notify_state(sess, GF_NETIO_PARSE_REPLY, GF_OK); + //special case for resources stored as persistent (mpd, init seg): we don't push the data + if (gf_cache_entry_persistent(sess->cache_entry)) { + gf_dm_sess_notify_state(sess, GF_NETIO_PARSE_REPLY, GF_OK); - gf_dm_disconnect(sess, HTTP_NO_CLOSE); - if (sess->user_proc) { - /* For modules that do not use cache and have problems with GF_NETIO_DATA_TRANSFERED ... */ - const char * filename; - FILE * f; - filename = gf_cache_get_cache_filename(sess->cache_entry); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP Sending data to modules from %s...\n", filename)); - f = gf_fopen(filename, "rb"); - gf_assert(filename); - if (!f) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP FAILED to open cache file %s for reading contents !\n", filename)); + /* Cache file is the most recent */ + sess->status = GF_NETIO_DATA_TRANSFERED; + SET_LAST_ERR(GF_OK) + gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK); + gf_dm_disconnect(sess, HTTP_NO_CLOSE); + return GF_OK; + } + + sess->status = GF_NETIO_PARSE_REPLY; + if (!gf_cache_is_mem(sess->cache_entry)) { + sess->cached_file = gf_cache_open_read(sess->cache_entry); + if (!sess->cached_file) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s FAILED to open cache file %s for reading contents !\n", sess->log_name, gf_cache_get_cache_filename(sess->cache_entry))); /* Ooops, no cache, redownload everything ! */ gf_dm_disconnect(sess, HTTP_NO_CLOSE); sess->status = GF_NETIO_SETUP; @@ -6106,43 +4201,17 @@ } return e; } - - par.error = GF_OK; - par.msg_type = GF_NETIO_PARSE_HEADER; - par.name = "Content-Type"; - par.value = (char *) gf_cache_get_mime_type(sess->cache_entry); - gf_dm_sess_user_io(sess, &par); - - sess->status = GF_NETIO_DATA_EXCHANGE; - if (! (sess->flags & GF_NETIO_SESSION_NOT_THREADED) || sess->force_data_write_callback) { - char file_cache_buff16544; - s32 read = 0; - total_size = gf_cache_get_cache_filesize(sess->cache_entry); - do { - read = (s32) gf_fread(file_cache_buff, 16384, f); - if (read > 0) { - sess->bytes_done += read; - sess->total_size = total_size; - sess->bytes_per_sec = 0xFFFFFFFF; - par.size = read; - par.msg_type = GF_NETIO_DATA_EXCHANGE; - par.error = GF_EOS; - par.reply = 2; - par.data = file_cache_buff; - gf_dm_sess_user_io(sess, &par); - } - } while ( read > 0); - } - gf_fclose(f); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP all data has been sent to modules from %s.\n", filename)); + } else { + //we read from mem cache + sess->local_cache_only = GF_TRUE; } - /* Cache file is the most recent */ - sess->status = GF_NETIO_DATA_TRANSFERED; - SET_LAST_ERR(GF_OK) - par.error = GF_OK; - gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK); - gf_dm_disconnect(sess, HTTP_NO_CLOSE); - return GF_OK; + sess->status = GF_NETIO_DATA_EXCHANGE; + sess->total_size = ContentLength = gf_cache_get_cache_filesize(sess->cache_entry); + + gf_dm_sess_user_io(sess, &par); + sess->status = GF_NETIO_DATA_EXCHANGE; + e = GF_EOS; + break; } case 401: { @@ -6184,7 +4253,7 @@ } return e; } - case 400: + case 400: case 501: /* Method not implemented ! */ if (sess->http_read_type == HEAD) { @@ -6205,7 +4274,7 @@ } gf_dm_sess_user_io(sess, &par); - notify_headers(sess, sHTTP, bytesRead, BodyStart); + notify_error_body(sess, sHTTP, bytesRead, BodyStart); e = GF_REMOTE_SERVICE_ERROR; goto exit; @@ -6220,38 +4289,41 @@ //fall-through /* case 204: - case 504: + case 504: case 404: - case 403: - case 416: + case 403: + case 416: */ - default: + default: gf_dm_sess_user_io(sess, &par); if ((BodyStart < (s32) bytesRead)) { sHTTPbytesRead = 0; - GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("HTTP Failure - body: %s\n", sHTTP + BodyStart)); + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("%s Failure - body: %s\n", sess->log_name, sHTTP + BodyStart)); } - notify_headers(sess, sHTTP, bytesRead, BodyStart); + notify_error_body(sess, sHTTP, bytesRead, BodyStart); - switch (rsp_code) { + switch (sess->rsp_code) { case 204: e = GF_EOS; break; /* File not found */ - case 404: e = GF_URL_ERROR; break; + case 404: + //too early + case 425: + e = GF_URL_ERROR; break; /* Forbidden */ case 403: e = GF_AUTHENTICATION_FAILURE; break; /* Range not accepted */ - case 416: e = GF_SERVICE_ERROR; break; + case 416: + e = GF_SERVICE_ERROR; + break; case 504: e = GF_URL_ERROR; break; default: - if (rsp_code>=500) e = GF_REMOTE_SERVICE_ERROR; + if (sess->rsp_code>=500) e = GF_REMOTE_SERVICE_ERROR; else e = GF_SERVICE_ERROR; break; } goto exit; } - notify_headers(sess, NULL, bytesRead, BodyStart); - if (sess->http_read_type != GET) sess->use_cache_file = GF_FALSE; @@ -6272,9 +4344,9 @@ #ifndef GPAC_DISABLE_LOG if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP Error processing rely from %s: %s\n", sess->server_name, gf_error_to_string(e) ) ); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Error processing rely from %s: %s\n", sess->log_name, sess->server_name, gf_error_to_string(e) ) ); } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP Reply processed from %s\n", sess->server_name ) ); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Reply processed from %s\n", sess->log_name, sess->server_name ) ); } #endif @@ -6288,16 +4360,19 @@ if (e) goto exit; if (sess->icy_metaint != 0) { gf_assert( ! sess->use_cache_file ); - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP ICY protocol detected\n")); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s ICY protocol detected\n", sess->log_name)); if (mime_type && !stricmp(mime_type, "video/nsv")) { gf_cache_set_mime_type(sess->cache_entry, "audio/aac"); } sess->icy_bytes = 0; sess->total_size = SIZE_IN_STREAM; sess->status = GF_NETIO_DATA_EXCHANGE; - } else if (!ContentLength && !sess->chunked -#ifdef GPAC_HAS_HTTP2 - && !sess->h2_sess + } else if (!ContentLength && !has_content_length && !sess->chunked +#ifdef GPAC_HTTPMUX + && !sess->hmux_sess +#endif +#ifdef GPAC_HAS_CURL + && !sess->curl_hnd #endif ) { if (sess->http_read_type == GET) { @@ -6310,15 +4385,16 @@ gf_dm_disconnect(sess, HTTP_NO_CLOSE); return GF_OK; } -#ifdef GPAC_HAS_HTTP2 - } else if (sess->h2_sess && !ContentLength && (sess->http_read_type != GET)) { +#ifdef GPAC_HTTPMUX + } else if (sess->hmux_sess && !ContentLength && (sess->http_read_type != GET)) { gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK); gf_dm_disconnect(sess, HTTP_NO_CLOSE); return GF_OK; #endif } else { sess->total_size = ContentLength; - if (sess->use_cache_file && sess->http_read_type == GET ) { + if (sess->use_cache_file && !sess->cached_file && (sess->http_read_type == GET)) { + e = gf_cache_open_write_cache(sess->cache_entry, sess); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ( "CACHE Failed to open cache, error=%d\n", e)); @@ -6327,12 +4403,18 @@ } sess->status = GF_NETIO_DATA_EXCHANGE; sess->bytes_done = 0; + if (!ContentLength && has_content_length) { + gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK); + gf_dm_disconnect(sess, HTTP_NO_CLOSE); + sess->status = GF_NETIO_DATA_TRANSFERED; + return GF_OK; + } } /* we may have existing data in this buffer ... */ -#ifdef GPAC_HAS_HTTP2 - if (!e && sess->h2_sess) { - h2_flush_data(sess, GF_TRUE); +#ifdef GPAC_HTTPMUX + if (!e && sess->hmux_sess) { + hmux_flush_internal_data(sess, GF_TRUE); } else #endif if (!e && (BodyStart < (s32) bytesRead)) { @@ -6349,15 +4431,15 @@ exit: if (e) { if (e<0) { - GF_LOG((e==GF_URL_ERROR) ? GF_LOG_INFO : GF_LOG_WARNING, GF_LOG_HTTP, ("HTTP Error parsing reply for URL %s: %s (code %d)\n", sess->orig_url, gf_error_to_string(e), rsp_code )); + GF_LOG((e==GF_URL_ERROR) ? GF_LOG_INFO : GF_LOG_WARNING, GF_LOG_HTTP, ("%s Error parsing reply for URL %s: %s (code %d)\n", sess->log_name, sess->orig_url, gf_error_to_string(e), sess->rsp_code )); } else { e = GF_OK; } gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry); - gf_dm_remove_cache_entry_from_session(sess); + gf_cache_remove_entry_from_session(sess); sess->cache_entry = NULL; gf_dm_disconnect(sess, HTTP_NO_CLOSE); - if (connection_closed) + if ((e<0) && connection_closed) sess->status = GF_NETIO_STATE_ERROR; else sess->status = GF_NETIO_DATA_TRANSFERED; @@ -6375,8 +4457,7 @@ */ void http_do_requests(GF_DownloadSession *sess) { - char sHTTPGF_DOWNLOAD_BUFFER_SIZE+1; - sHTTP0 = 0; + sess->http_buf0 = 0; if (sess->reused_cache_entry) { //main session is done downloading, notify - to do we should send progress events on this session also ... @@ -6399,16 +4480,16 @@ switch (sess->status) { case GF_NETIO_CONNECTED: if (sess->server_mode) { - wait_for_header_and_parse(sess, sHTTP); + wait_for_header_and_parse(sess); } else { - http_send_headers(sess, sHTTP); + http_send_headers(sess); } break; case GF_NETIO_WAIT_FOR_REPLY: if (sess->server_mode) { - http_send_headers(sess, sHTTP); + http_send_headers(sess); } else { - wait_for_header_and_parse(sess, sHTTP); + wait_for_header_and_parse(sess); } break; case GF_NETIO_DATA_EXCHANGE: @@ -6433,13 +4514,32 @@ } sess->reassigned = GF_FALSE; } - http_parse_remaining_body(sess, sHTTP); + http_parse_remaining_body(sess); break; default: break; } } +GF_EXPORT +void gf_dm_sess_set_max_rate(GF_DownloadSession *sess, u32 max_rate) +{ + if (sess) { + sess->max_data_rate = max_rate/8; + sess->rate_regulated = GF_FALSE; + } +} +GF_EXPORT +u32 gf_dm_sess_get_max_rate(GF_DownloadSession *sess) +{ + return sess ? 8*sess->max_data_rate : 0; +} +GF_EXPORT +Bool gf_dm_sess_is_regulated(GF_DownloadSession *sess) +{ + return sess ? sess->rate_regulated : GF_FALSE; +} + /** * NET IO for MPD, we don't need this anymore since mime-type can be given by session @@ -6493,9 +4593,17 @@ dnload->range_end = end_range; dnload->needs_range = GF_TRUE; } - if (e == GF_OK) { + while (e == GF_OK) { e = gf_dm_sess_process(dnload); + if (e && (e!=GF_IP_NETWORK_EMPTY)) + break; + if (dnload->status>=GF_NETIO_DATA_TRANSFERED) break; + e = GF_OK; + if (dnload->connect_pending) + gf_sleep(100); } + if (e==GF_OK) + gf_cache_set_content_length(dnload->cache_entry, dnload->total_size); e |= gf_cache_close_write_cache(dnload->cache_entry, dnload, (e == GF_OK) ? GF_TRUE : GF_FALSE); gf_fclose(f); @@ -6506,83 +4614,6 @@ return e; } -#if 0 //unused - -/* -\brief fetches remote file in memory - * - *Fetches remote file in memory. -\param url the data to fetch -\param out_data output data (allocated by function) -\param out_size output data size -\param out_mime if not NULL, pointer will contain the mime type (allocated by function) -\return error code if any - */ -GF_Err gf_dm_get_file_memory(const char *url, char **out_data, u32 *out_size, char **out_mime) -{ - GF_Err e; - FILE * f; - char * f_fn = NULL; - GF_DownloadSession *dnload; - GF_DownloadManager *dm; - - if (!url || !out_data || !out_size) - return GF_BAD_PARAM; - f = gf_file_temp(&f_fn); - if (!f) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("WGET Failed to create temp file for write.\n")); - return GF_IO_ERR; - } - - dm = gf_dm_new(NULL); - if (!dm) { - gf_fclose(f); - gf_file_delete(f_fn); - return GF_OUT_OF_MEM; - } - - dnload = gf_dm_sess_new_simple(dm, (char *)url, GF_NETIO_SESSION_NOT_THREADED, &wget_NetIO, f, &e); - if (!dnload) { - gf_dm_del(dm); - gf_fclose(f); - gf_file_delete(f_fn); - return GF_BAD_PARAM; - } - dnload->use_cache_file = GF_FALSE; - dnload->disable_cache = GF_TRUE; - if (!e) - e = gf_dm_sess_process(dnload); - - if (!e) - e = gf_cache_close_write_cache(dnload->cache_entry, dnload, e == GF_OK); - - if (!e) { - u32 size = (u32) gf_ftell(f); - s32 read; - *out_size = size; - *out_data = (char*)gf_malloc(sizeof(char)* ( 1 + size)); - gf_fseek(f, 0, SEEK_SET); - read = (s32) gf_fread(*out_data, size, f); - if (read != size) { - gf_free(*out_data); - e = GF_IO_ERR; - } else { - (*out_data)size = 0; - if (out_mime) { - const char *mime = gf_dm_sess_mime_type(dnload); - if (mime) *out_mime = gf_strdup(mime); - } - } - } - gf_fclose(f); - gf_file_delete(f_fn); - gf_free(f_fn); - gf_dm_sess_del(dnload); - gf_dm_del(dm); - return e; -} -#endif - GF_EXPORT const char *gf_dm_sess_get_resource_name(GF_DownloadSession *dnload) { @@ -6590,195 +4621,6 @@ } -#if 0 //unused -/*! -\brief Get session original resource url - * - *Returns the original resource URL before any redirection associated with the session -\param sess the download session -\return the associated URL - */ -const char *gf_dm_sess_get_original_resource_name(GF_DownloadSession *dnload) -{ - if (dnload) return dnload->orig_url_before_redirect ? dnload->orig_url_before_redirect : dnload->orig_url; - return NULL; -} - -/*! -\brief fetch session status - * - *Fetch the session current status -\param sess the download session -\return the session status*/ -u32 gf_dm_sess_get_status(GF_DownloadSession *dnload) -{ - return dnload ? dnload->status : GF_NETIO_STATE_ERROR; -} - - -/*! -\brief Reset session - * - *Resets the session for new processing of the same url -\param sess the download session -\return error code if any - */ -GF_Err gf_dm_sess_reset(GF_DownloadSession *sess) -{ - if (!sess) - return GF_BAD_PARAM; - sess->status = GF_NETIO_SETUP; - sess->needs_range = GF_FALSE; - sess->range_start = sess->range_end = 0; - sess->bytes_done = sess->bytes_per_sec = 0; - if (sess->init_data) gf_free(sess->init_data); - sess->init_data = NULL; - sess->init_data_size = 0; - SET_LAST_ERR(GF_OK) - sess->total_size = 0; - sess->start_time = 0; - sess->start_time_utc = 0; - sess->max_chunk_size = 0; - sess->max_chunk_bytes_per_sec = 0; - return GF_OK; -} - -/*! - * Get a range of a cache entry file -\param sess The session -\param startOffset The first byte of the request to get -\param endOffset The last byte of request to get -\param The temporary name for the file created to have a range of the file - */ -const char * gf_cache_get_cache_filename_range( const GF_DownloadSession * sess, u64 startOffset, u64 endOffset ) { - u32 i, count; - if (!sess || !sess->dm || endOffset < startOffset) - return NULL; - count = gf_list_count(sess->dm->partial_downloads); - for (i = 0 ; i < count ; i++) { - GF_PartialDownload * pd = (GF_PartialDownload*)gf_list_get(sess->dm->partial_downloads, i); - gf_assert( pd->filename && pd->url); - if (!strcmp(pd->url, sess->orig_url) && pd->startOffset == startOffset && pd->endOffset == endOffset) { - /* File already created, just return the file */ - return pd->filename; - } - } - { - /* Not found, we are gonna create the file */ - char * newFilename; - GF_PartialDownload * partial; - FILE * fw, *fr; - u32 maxLen; - const char * orig = gf_cache_get_cache_filename(sess->cache_entry); - if (orig == NULL) - return NULL; - /* 22 if 1G + 1G + 2 dashes */ - maxLen = (u32) strlen(orig) + 22; - newFilename = (char*)gf_malloc( maxLen ); - if (newFilename == NULL) - return NULL; - snprintf(newFilename, maxLen, "%s " LLU LLU, orig, startOffset, endOffset); - fw = gf_fopen(newFilename, "wb"); - if (!fw) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("CACHE Cannot open partial cache file %s for write\n", newFilename)); - gf_free( newFilename ); - return NULL; - } - fr = gf_fopen(orig, "rb"); - if (!fr) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("CACHE Cannot open full cache file %s\n", orig)); - gf_free( newFilename ); - gf_fclose( fw ); - } - /* Now, we copy ! */ - { - char copyBuffGF_DOWNLOAD_BUFFER_SIZE+1; - s64 read, write, total; - total = endOffset - startOffset; - read = gf_fseek(fr, startOffset, SEEK_SET); - if (read != startOffset) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("CACHE Cannot seek at right start offset in %s\n", orig)); - gf_fclose( fr ); - gf_fclose( fw ); - gf_free( newFilename ); - return NULL; - } - do { - read = gf_fread(copyBuff, MIN(sizeof(copyBuff), (size_t) total), fr); - if (read > 0) { - total-= read; - write = gf_fwrite(copyBuff, (size_t) read, fw); - if (write != read) { - /* Something bad happened */ - gf_fclose( fw ); - gf_fclose (fr ); - gf_free( newFilename ); - return NULL; - } - } else { - if (read < 0) { - gf_fclose( fw ); - gf_fclose( fr ); - gf_free( newFilename ); - return NULL; - } - } - } while (total > 0); - gf_fclose( fr ); - gf_fclose (fw); - partial = (GF_PartialDownload*)gf_malloc( sizeof(GF_PartialDownload)); - if (partial == NULL) { - gf_free(newFilename); - return NULL; - } - partial->filename = newFilename; - partial->url = sess->orig_url; - partial->endOffset = endOffset; - partial->startOffset = startOffset; - gf_list_add(sess->dm->partial_downloads, partial); - return newFilename; - } - } -} - -/*! - * Reassigns session flags and callbacks. This is only possible if the session is not threaded. -\param sess The session -\param flags The new flags for the session - if flags is 0xFFFFFFFF, existing flags are not modified -\param user_io The new callback function -\param cbk The new user data to be used in the callback function -\param GF_OK or error - */ -GF_Err gf_dm_sess_reassign(GF_DownloadSession *sess, u32 flags, gf_dm_user_io user_io, void *cbk) -{ - /*shall only be called for non-threaded sessions!! */ - if (sess->th) - return GF_BAD_PARAM; - - if (flags == 0xFFFFFFFF) { - sess->user_proc = user_io; - sess->usr_cbk = cbk; - return GF_OK; - } - - if (sess->flags & GF_DOWNLOAD_SESSION_USE_SSL) flags |= GF_DOWNLOAD_SESSION_USE_SSL; - sess->flags = flags; - if (sess->flags & GF_NETIO_SESSION_NOTIFY_DATA) - sess->force_data_write_callback = GF_TRUE; - - sess->user_proc = user_io; - sess->usr_cbk = cbk; - sess->reassigned = sess->init_data ? GF_TRUE : GF_FALSE; - sess->num_retry = SESSION_RETRY_COUNT; - - if (sess->status==GF_NETIO_DISCONNECTED) - sess->status = GF_NETIO_SETUP; - - /*threaded session shall be started with gf_dm_sess_process*/ - return GF_OK; -} -#endif - GF_EXPORT void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec) @@ -6793,17 +4635,6 @@ sprintf(opt, "%d", rate_in_bits_per_sec); //temporary store of maxrate gf_opts_set_key("temp", "maxrate", opt); - - dm->read_buf_size = GF_DOWNLOAD_BUFFER_SIZE; - //when rate is limited, use smaller smaller read size - if (dm->limit_data_rate) dm->read_buf_size = GF_DOWNLOAD_BUFFER_SIZE_LIMIT_RATE; - -#ifdef GPAC_ENABLE_COVERAGE - if (gf_sys_is_cov_mode()) { - dm_exceeds_cap_rate(dm); - } -#endif - } } @@ -6820,10 +4651,10 @@ u32 i, count; if (!dm) return 0; gf_mx_p(dm->cache_mx); - count = gf_list_count(dm->sessions); + count = gf_list_count(dm->all_sessions); for (i=0; i<count; i++) { - GF_DownloadSession *sess = (GF_DownloadSession*)gf_list_get(dm->sessions, i); + GF_DownloadSession *sess = (GF_DownloadSession*)gf_list_get(dm->all_sessions, i); if (sess->status >= GF_NETIO_DATA_TRANSFERED) { if (sess->total_size==sess->bytes_done) { //do not aggregate session if done/interrupted since more than 1/2 a sec @@ -6838,14 +4669,42 @@ return 8*ret; } -Bool gf_dm_sess_is_h2(GF_DownloadSession *sess) +GF_HTTPSessionType gf_dm_sess_is_hmux(GF_DownloadSession *sess) { -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) return GF_TRUE; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + if (sess->hmux_sess->net_sess && sess->hmux_sess->net_sess->flags & GF_NETIO_SESSION_USE_QUIC) + return GF_SESS_TYPE_HTTP3; + if (sess->hmux_sess->net_sess && !sess->hmux_sess->net_sess->sock) + return GF_SESS_TYPE_HTTP3; + return GF_SESS_TYPE_HTTP2; + } +#endif + return GF_SESS_TYPE_HTTP; +} + +Bool gf_dm_sess_use_tls(GF_DownloadSession * sess) +{ +#ifdef GPAC_HAS_SSL + if (sess->ssl) + return GF_TRUE; +#endif + +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess && (sess->hmux_sess->net_sess->flags & GF_NETIO_SESSION_USE_QUIC)) + return GF_TRUE; #endif return GF_FALSE; } +u32 gf_dm_sess_get_resource_size(GF_DownloadSession * sess) +{ + if (!sess) return 0; + if (sess->full_resource_size) return sess->full_resource_size; + if (sess->needs_range) return 0; + return sess->total_size; +} + GF_EXPORT const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name) { @@ -6900,137 +4759,24 @@ } } -GF_EXPORT -GF_Err gf_dm_set_localcache_provider(GF_DownloadManager *dm, Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool is_cache_destroy), void *lc_udta) -{ - if (!dm) - return GF_BAD_PARAM; - dm->local_cache_url_provider_cbk = local_cache_url_provider_cbk; - dm->lc_udta = lc_udta; - return GF_OK; - -} - -GF_EXPORT -DownloadedCacheEntry gf_dm_add_cache_entry(GF_DownloadManager *dm, const char *szURL, GF_Blob *blob, u64 start_range, u64 end_range, const char *mime, Bool clone_memory, u32 download_time_ms) -{ - u32 i, count; - DownloadedCacheEntry the_entry = NULL; - - gf_mx_p(dm->cache_mx ); - if (blob) - GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("HTTP Pushing %s to cache "LLU" bytes (done %s)\n", szURL, blob->size, (blob->flags & GF_BLOB_IN_TRANSFER) ? "no" : "yes")); - count = gf_list_count(dm->cache_entries); - for (i = 0 ; i < count; i++) { - const char * url; - DownloadedCacheEntry e = (DownloadedCacheEntry)gf_list_get(dm->cache_entries, i); - gf_assert(e); - url = gf_cache_get_url(e); - gf_assert( url ); - if (strcmp(url, szURL)) continue; - - if (end_range) { - if (start_range != gf_cache_get_start_range(e)) continue; - if (end_range != gf_cache_get_end_range(e)) continue; - } - /*OK that's ours*/ - the_entry = e; - break; - } - if (!the_entry) { - the_entry = gf_cache_create_entry(dm, "", szURL, 0, 0, GF_TRUE, dm->cache_mx); - if (!the_entry) { - gf_mx_v(dm->cache_mx ); - return NULL; - } - gf_list_add(dm->cache_entries, the_entry); - } - - gf_cache_set_mime(the_entry, mime); - if (blob && ! (blob->flags & GF_BLOB_IN_TRANSFER)) - gf_cache_set_range(the_entry, blob->size, start_range, end_range); - - gf_cache_set_content(the_entry, blob, clone_memory ? GF_TRUE : GF_FALSE, dm->cache_mx); - gf_cache_set_downtime(the_entry, download_time_ms); - gf_mx_v(dm->cache_mx ); - return the_entry; -} - -GF_EXPORT -GF_Err gf_dm_force_headers(GF_DownloadManager *dm, const DownloadedCacheEntry entry, const char *headers) -{ - u32 i, count; - Bool res; - if (!entry) - return GF_BAD_PARAM; - gf_mx_p(dm->cache_mx); - res = gf_cache_set_headers(entry, headers); - count = gf_list_count(dm->sessions); - for (i=0; i<count; i++) { - GF_DownloadSession *sess = gf_list_get(dm->sessions, i); - if (sess->cache_entry != entry) continue; - gf_dm_sess_reload_cached_headers(sess); - } - -#ifdef GPAC_ENABLE_COVERAGE - if (!count && gf_sys_is_cov_mode()) { - gf_dm_sess_reload_cached_headers(NULL); - gf_dm_refresh_cache_entry(NULL); - gf_dm_session_thread(NULL); - gf_cache_are_headers_processed(NULL); - gf_cache_get_start_range(NULL); - gf_cache_get_end_range(NULL); - gf_cache_get_content_length(NULL); - gf_cache_set_end_range(NULL, 0); - gf_cache_get_forced_headers(NULL); - gf_cache_get_downtime(NULL); - } -#endif - gf_mx_v(dm->cache_mx); - if (res) return GF_OK; - return GF_BAD_PARAM; -} - -GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select) +static GF_Err gf_dm_sess_flush_async_close(GF_DownloadSession *sess, Bool no_select, Bool for_close) { if (!sess) return GF_OK; + if (sess->status==GF_NETIO_STATE_ERROR) return sess->last_error; - if (!no_select && (gf_sk_select(sess->sock, GF_SK_SELECT_WRITE)!=GF_OK)) { - return GF_IP_NETWORK_EMPTY; - } -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_local_buf_len) { - u8 was_eos = sess->h2_is_eos; - gf_assert(sess->h2_send_data==NULL); - sess->h2_send_data = sess->h2_local_buf; - sess->h2_send_data_len = sess->h2_local_buf_len; - gf_mx_p(sess->mx); - if (sess->h2_data_paused) { - sess->h2_data_paused = 0; - nghttp2_session_resume_data(sess->h2_sess->ng_sess, sess->h2_stream_id); - } - h2_flush_send(sess, GF_TRUE); - gf_mx_v(sess->mx); - if (sess->h2_send_data_len) { - if (sess->h2_send_data_len<sess->h2_local_buf_len) { - memmove(sess->h2_local_buf, sess->h2_send_data, sess->h2_send_data_len); - sess->h2_local_buf_len = sess->h2_send_data_len; - } - sess->h2_send_data = NULL; - sess->h2_send_data_len = 0; - sess->h2_is_eos = was_eos; - return GF_IP_NETWORK_EMPTY; - } - sess->h2_local_buf_len = 0; - sess->h2_is_eos = 0; + if (!no_select && sess->sock && (gf_sk_select(sess->sock, GF_SK_SELECT_WRITE)!=GF_OK)) { + return sess->async_buf_size ? GF_IP_NETWORK_EMPTY : GF_OK; } -#endif + GF_Err ret = GF_OK; + +#ifdef GPAC_HTTPMUX + if(sess->hmux_sess) + ret = sess->hmux_sess->async_flush(sess, for_close); -#ifdef GPAC_HAS_HTTP2 //if H2 flush the parent session holding the http2 session - if (sess->h2_sess) - sess = sess->h2_sess->net_sess; + if (sess->hmux_sess) + sess = sess->hmux_sess->net_sess; #endif if (sess->async_buf_size) { @@ -7038,16 +4784,25 @@ if (e) return e; if (sess->async_buf_size) return GF_IP_NETWORK_EMPTY; } - return GF_OK; + return ret; +} + +GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select) +{ + return gf_dm_sess_flush_async_close(sess, no_select, GF_FALSE); +} +GF_Err gf_dm_sess_flush_close(GF_DownloadSession *sess) +{ + return gf_dm_sess_flush_async_close(sess, GF_TRUE, GF_TRUE); } u32 gf_dm_sess_async_pending(GF_DownloadSession *sess) { if (!sess) return 0; -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_local_buf_len) return sess->h2_local_buf_len; - if (sess->h2_sess) - sess = sess->h2_sess->net_sess; +#ifdef GPAC_HTTPMUX + if (sess->local_buf_len) return sess->local_buf_len; + if (sess->hmux_sess) + sess = sess->hmux_sess->net_sess; #endif return sess ? sess->async_buf_size : 0; } @@ -7057,55 +4812,9 @@ { GF_Err e = GF_OK; -#ifdef GPAC_HAS_HTTP2 - if (sess->h2_sess) { - if (sess->h2_send_data) - return GF_SERVICE_ERROR; - if (!sess->h2_stream_id) - return GF_URL_REMOVED; - - gf_mx_p(sess->mx); - - GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 Sending %d bytes on stream_id %d\n", size, sess->h2_stream_id)); - - sess->h2_send_data = data; - sess->h2_send_data_len = size; - if (sess->h2_data_paused) { - sess->h2_data_paused = 0; - nghttp2_session_resume_data(sess->h2_sess->ng_sess, sess->h2_stream_id); - } - //if no data, signal end of stream, otherwise regular send - if (!data || !size) { - if (sess->h2_local_buf_len) { - gf_mx_v(sess->mx); - if (sess->h2_is_eos) { - return GF_IP_NETWORK_EMPTY; - } - sess->h2_is_eos = 1; - return GF_OK; - } - sess->h2_is_eos = 1; - h2_session_send(sess); - //stream_id is not yet 0 in case of PUT/PUSH, stream is closed once we get reply from server - } else { - sess->h2_is_eos = 0; - //send the data - h2_flush_send(sess, GF_FALSE); - } - sess->h2_is_eos = 0; - - gf_mx_v(sess->mx); - - if (!data || !size) { - if (sess->put_state) { - sess->put_state = 2; - sess->status = GF_NETIO_WAIT_FOR_REPLY; - return GF_OK; - } - } - if (!sess->h2_stream_id && sess->h2_send_data) - return GF_URL_REMOVED; - return GF_OK; +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) { + return hmux_send_payload(sess, data, size); } #endif @@ -7134,43 +4843,6 @@ return e; } -void gf_dm_sess_flush_h2(GF_DownloadSession *sess) -{ -#ifdef GPAC_HAS_HTTP2 - if (!sess->h2_sess) return; - nghttp2_submit_shutdown_notice(sess->h2_sess->ng_sess); - h2_session_send(sess); - - //commented out as it seems go_away is never set -#if 1 - u64 in_time; - in_time = gf_sys_clock_high_res(); - while (nghttp2_session_want_read(sess->h2_sess->ng_sess)) { - u32 res; - char h2_flush2024; - GF_Err e; - if (gf_sys_clock_high_res() - in_time > 100000) - break; - - //read any frame pending from remote peer (window update and co) - e = gf_dm_read_data(sess, h2_flush, 2023, &res); - if ((e<0) && (e != GF_IP_NETWORK_EMPTY)) { - if (e!=GF_IP_CONNECTION_CLOSED) { - SET_LAST_ERR(e) - sess->status = GF_NETIO_STATE_ERROR; - } - break; - } - - h2_session_send(sess); - } -#else - sess->status = GF_NETIO_DISCONNECTED; -#endif - -#endif -} - GF_Socket *gf_dm_sess_get_socket(GF_DownloadSession *sess) { return sess ? sess->sock : NULL; @@ -7199,7 +4871,7 @@ gf_sk_set_block_mode(sess->sock, GF_TRUE); } sess->flags |= GF_NETIO_SESSION_NO_BLOCK; - //FOR TEST INLY + //FOR TEST ONLY sess->flags &= ~GF_NETIO_SESSION_NOT_THREADED; //mutex may already be created for H2 sessions if (!sess->mx) @@ -7217,887 +4889,4 @@ } -//end GAPC_DISABLE_NETWORK -#elif defined(GPAC_CONFIG_EMSCRIPTEN) -#include <gpac/download.h> -#include <gpac/list.h> -#include <gpac/thread.h> -#include <gpac/filters.h> - -struct __gf_download_manager -{ - GF_FilterSession *fsess; - GF_List *all_sessions; - GF_Mutex *mx; -}; - -GF_DownloadManager *gf_dm_new(GF_DownloadFilterSession *fsess) -{ - GF_DownloadManager *tmp; - GF_SAFEALLOC(tmp, GF_DownloadManager); - if (!tmp) return NULL; - tmp->fsess = fsess; - tmp->all_sessions = gf_list_new(); - tmp->mx = gf_mx_new("cache"); - return tmp; -} -void gf_dm_del(GF_DownloadManager *dm) -{ - if (!dm) return; - gf_list_del(dm->all_sessions); - gf_mx_del(dm->mx); - gf_free(dm); -} - -u32 gf_dm_get_global_rate(GF_DownloadManager *dm) -{ - return 0; -} -void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec) -{ -} - -typedef struct __cache_blob -{ - GF_Blob blob; - char *url, *cache_name; - char *mime; - u64 start_range, end_range; - Bool persistent; -} GF_CacheBlob; - -struct __gf_download_session -{ - GF_DownloadManager *dm; - gf_dm_user_io user_io; - void *usr_cbk; - char method100; - - char *req_url; - char *rsp_url; - /*request headers*/ - char **req_hdrs; - u32 nb_req_hdrs; - u32 req_body_size; - char *req_body; - u64 start_range, end_range; - - /*response headers*/ - char **rsp_hdrs; - u32 nb_rsp_hdrs; - char *mime; //pointer to content-type header or blob - - u32 state, bps; - u64 start_time, start_time_utc; - u64 total_size, bytes_done; - GF_Err last_error; - GF_NetIOStatus netio_status; - u32 dl_flags; - Bool reuse_cache; - u32 ftask; - GF_List *cached_blobs; - - GF_CacheBlob *active_cache; - - Bool destroy, in_callback; - -}; - -EM_JS(int, dm_fetch_cancel, (int sess), { - let fetcher = libgpac._get_fetcher(sess); - if (!fetcher) return -1; - fetcher._controller.abort(); - libgpac._del_fetcher(fetcher); - return 0; -}); - -static void clear_headers(char ***_hdrs, u32 *nb_hdrs) -{ - char **hdrs = *_hdrs; - if (hdrs) { - u32 i=0; - for (i=0; i<*nb_hdrs; i++) { - if (hdrsi) gf_free(hdrsi); - } - gf_free(hdrs); - *_hdrs = NULL; - *nb_hdrs = 0; - } -} - -void cache_blob_del(GF_CacheBlob *b) -{ - if (!b) return; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Removing cache entry of %s (%s - range "LLU"-"LLU")\n", b->url, b->cache_name, b->start_range, b->end_range)); - gf_blob_unregister(&b->blob); - if (b->blob.data) gf_free(b->blob.data); - if (b->url) gf_free(b->url); - if (b->cache_name) gf_free(b->cache_name); - if (b->mime) gf_free(b->mime); - gf_free(b); -} - -static void gf_dm_sess_reset(GF_DownloadSession *sess) -{ - if (!sess) return; - clear_headers(&sess->req_hdrs, &sess->nb_req_hdrs); - clear_headers(&sess->rsp_hdrs, &sess->nb_rsp_hdrs); - if (sess->req_body) gf_free(sess->req_body); - sess->req_body = NULL; - - if (sess->state==1) { - dm_fetch_cancel(EM_CAST_PTR sess); - } - if (sess->req_url) gf_free(sess->req_url); - sess->req_url = NULL; - if (sess->rsp_url) gf_free(sess->rsp_url); - sess->rsp_url = NULL; - sess->mime = NULL; - sess->state = 0; - sess->total_size = 0; - sess->bytes_done = 0; - sess->start_time = 0; - sess->start_range = 0; - sess->end_range = 0; - sess->bps = 0; - sess->last_error = GF_OK; - sess->reuse_cache = GF_FALSE; - sess->netio_status = GF_NETIO_SETUP; -} - - -EM_JS(int, fs_fetch_setup, (), { - if ((typeof libgpac.gpac_fetch == 'boolean') && !libgpac.gpac_fetch) return 2; - if (typeof libgpac._fetcher_set_header == 'function') return 1; - try { - libgpac._fetchers = ; - libgpac._get_fetcher = (sess) => { - for (let i=0; i<libgpac._fetchers.length; i++) { - if (libgpac._fetchersi.sess==sess) return libgpac._fetchersi; - } - return null; - }; - libgpac._del_fetcher = (fetcher) => { - let i = libgpac._fetchers.indexOf(fetcher); - if (i>=0) libgpac._fetchers.splice(i, 1); - }; - libgpac._fetcher_set_header = libgpac.cwrap('gf_dm_sess_push_header', null, 'number', 'string', 'string'); - libgpac._fetcher_set_reply = libgpac.cwrap('gf_dm_sess_async_reply', null, 'number', 'number', 'string'); - } catch (e) { - return 0; - } - return 1; -}); - -static GF_Err gf_dm_setup_cache(GF_DownloadSession *sess) -{ - if (sess->active_cache) { - if (!sess->cached_blobs) { - cache_blob_del(sess->active_cache); - } - else if (sess->active_cache->blob.flags & GF_BLOB_CORRUPTED) { - gf_list_del_item(sess->cached_blobs, sess->active_cache); - cache_blob_del(sess->active_cache); - } - //remove if not persistent - else if (!sess->active_cache->persistent) { - gf_list_del_item(sess->cached_blobs, sess->active_cache); - cache_blob_del(sess->active_cache); - } - sess->active_cache = NULL; - } - //look in session for cache - if (sess->cached_blobs) { - u32 i, count = gf_list_count(sess->cached_blobs); - for (i=0; i<count; i++) { - GF_CacheBlob *cb = gf_list_get(sess->cached_blobs, i); - if (strcmp(cb->url, sess->req_url)) continue; - if (cb->start_range != sess->start_range) continue; - if (cb->end_range != sess->end_range) continue; - if (!cb->blob.size) continue; - sess->active_cache = cb; - sess->reuse_cache = GF_TRUE; - sess->total_size = cb->blob.size; - sess->bytes_done = 0; - sess->netio_status = GF_NETIO_DATA_EXCHANGE; - sess->last_error = GF_OK; - sess->mime = cb->mime; - sess->dl_flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE; - return GF_OK; - } - } - if (!(sess->dl_flags & GF_NETIO_SESSION_MEMORY_CACHE)) { - return GF_OK; - } - - GF_SAFEALLOC(sess->active_cache, GF_CacheBlob); - if (!sess->active_cache) return GF_OUT_OF_MEM; - if (!sess->cached_blobs) sess->cached_blobs = gf_list_new(); - - gf_list_add(sess->cached_blobs, sess->active_cache); - sess->active_cache->cache_name = gf_blob_register(&sess->active_cache->blob); - - sess->active_cache->blob.flags = GF_BLOB_IN_TRANSFER; - sess->active_cache->url = gf_strdup(sess->req_url); - sess->active_cache->start_range = sess->start_range; - sess->active_cache->end_range = sess->end_range; - sess->reuse_cache = GF_FALSE; - //only files marked with GF_NETIO_SESSION_KEEP_FIRST_CACHE are kept in our local cache - //all other cache settings are ignored, we rely on browser cache for this - if (sess->dl_flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE) { - sess->active_cache->persistent = GF_TRUE; - sess->dl_flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE; - } else { - sess->active_cache->persistent = GF_FALSE; - } - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Setting up memory cache for %s - persistent %d - name %s\n", sess->req_url, sess->active_cache->persistent, sess->active_cache->cache_name)); - return GF_OK; -} - -GF_EXPORT -GF_DownloadSession *gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 dl_flags, - gf_dm_user_io user_io, - void *usr_cbk, - GF_Err *e) -{ - GF_NETIO_Parameter par; - GF_DownloadSession *sess; - - int fetch_init = fs_fetch_setup(); - if (fetch_init==2) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Downloader fecth() disabled\n")); - if (e) *e = GF_NOT_SUPPORTED; - return NULL; - } else if (fetch_init==0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Downloader Failed to initialize fetch\n")); - if (e) *e = GF_SERVICE_ERROR; - return NULL; - } - - GF_SAFEALLOC(sess, GF_DownloadSession); - if (!sess) { - if (e) *e = GF_OUT_OF_MEM; - return NULL; - } - sess->dm = dm; - sess->req_url = gf_strdup(url); - if (!sess->req_url) { - gf_free(sess); - if (e) *e = GF_OUT_OF_MEM; - return NULL; - } - sess->user_io = user_io; - sess->usr_cbk = usr_cbk; - - strcpy(sess->method, "GET"); - - sess->dl_flags = dl_flags; - - if (dl_flags & GF_NETIO_SESSION_MEMORY_CACHE) { - sess->cached_blobs = gf_list_new(); - } - - if (user_io) { - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.msg_type = GF_NETIO_GET_METHOD; - sess->user_io(sess->usr_cbk, &par); - if (par.name) strcpy(sess->method, par.name); - - sess->nb_req_hdrs=0; - while (1) { - par.msg_type = GF_NETIO_GET_HEADER; - par.value = NULL; - sess->user_io(sess->usr_cbk, &par); - if (!par.value) break; - - sess->req_hdrs = gf_realloc(sess->req_hdrs, sizeof(char*) * (sess->nb_req_hdrs+2)); - if (!sess->req_hdrs) { - if (e) *e = GF_OUT_OF_MEM; - gf_free(sess); - return NULL; - } - sess->req_hdrssess->nb_req_hdrs = gf_strdup(par.name); - sess->req_hdrssess->nb_req_hdrs+1 = gf_strdup(par.value); - sess->nb_req_hdrs+=2; - } - - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.msg_type = GF_NETIO_GET_CONTENT; - sess->user_io(sess->usr_cbk, &par); - - sess->req_body_size = par.size; - if (par.size && par.data) { - sess->req_body = gf_malloc(sizeof(u8)*par.size); - if (!sess->req_body) { - gf_dm_sess_reset(sess); - gf_list_del(sess->cached_blobs); - if (e) *e = GF_OUT_OF_MEM; - gf_free(sess); - return NULL; - } - memcpy(sess->req_body, par.data, par.size); - } - } - if (e) *e = GF_OK; - *e = gf_dm_setup_cache(sess); - if (*e) { - gf_dm_sess_del(sess); - return NULL; - } - if (dm) { - gf_mx_p(dm->mx); - gf_list_add(dm->all_sessions, sess); - gf_mx_v(dm->mx); - } - - sess->user_io = user_io; - sess->usr_cbk = usr_cbk; - - return sess; -} - -void gf_dm_sess_abort(GF_DownloadSession * sess) -{ - if (sess->state==1) { - dm_fetch_cancel(EM_CAST_PTR sess); - sess->state = 0; - sess->netio_status = GF_NETIO_DISCONNECTED; - sess->last_error = GF_IP_CONNECTION_CLOSED; - if (sess->active_cache && !sess->reuse_cache && (sess->active_cache->blob.flags & GF_BLOB_IN_TRANSFER)) { - sess->active_cache->blob.flags = GF_BLOB_CORRUPTED; - } - } - clear_headers(&sess->rsp_hdrs, &sess->nb_rsp_hdrs); - if (sess->ftask) sess->ftask = 2; -} - -void gf_dm_sess_del(GF_DownloadSession *sess) -{ - if (sess->in_callback) { - sess->destroy = GF_TRUE; - return; - } - gf_dm_sess_reset(sess); - while (gf_list_count(sess->cached_blobs)) { - cache_blob_del( gf_list_pop_back(sess->cached_blobs) ); - } - gf_list_del(sess->cached_blobs); - if (sess->dm) { - gf_mx_p(sess->dm->mx); - gf_list_del_item(sess->dm->all_sessions, sess); - gf_mx_v(sess->dm->mx); - } - gf_free(sess); -} - -const char *gf_dm_sess_get_cache_name(GF_DownloadSession *sess) -{ - if (!sess || !sess->active_cache) return NULL; - return sess->active_cache->cache_name; -} - -void gf_dm_sess_force_memory_mode(GF_DownloadSession *sess, u32 force_keep) -{ - if (!sess) return; - sess->dl_flags |= GF_NETIO_SESSION_MEMORY_CACHE; - if (force_keep==1) { - //cache is handled by the browser if any, or forced by fetch headers provied by users - //sess->dl_flags |= GF_NETIO_SESSION_KEEP_CACHE; - } else if (force_keep==2) { - sess->dl_flags |= GF_NETIO_SESSION_KEEP_FIRST_CACHE; - } -} - -GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse) -{ - gf_dm_sess_reset(sess); - sess->req_url = gf_strdup(url); - if (!sess->req_url) { - sess->state=2; - return GF_OUT_OF_MEM; - } - return gf_dm_setup_cache(sess); -} - -GF_Err gf_dm_sess_set_range(GF_DownloadSession *sess, u64 start_range, u64 end_range, Bool discontinue_cache) -{ - if (!sess) return GF_BAD_PARAM; - if (sess->state==1) return GF_BAD_PARAM; - sess->start_range = start_range; - sess->end_range = end_range; - Bool cache_reset = GF_FALSE; - if ((sess->state!=2) || discontinue_cache) { - cache_reset = GF_TRUE; - } - else if (sess->active_cache) { - if (sess->active_cache->start_range + sess->active_cache->end_range + 1 == start_range) { - sess->active_cache->end_range = end_range; - sess->active_cache->blob.flags |= GF_BLOB_IN_TRANSFER; - } else { - cache_reset = GF_TRUE; - } - } - //set range called before starting session, just adjust cache - if (sess->active_cache && cache_reset && !sess->state) { - sess->active_cache->start_range = sess->start_range; - sess->active_cache->end_range = sess->end_range; - cache_reset=GF_FALSE; - } - if (cache_reset) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Cache discontinuity, reset\n", sess->req_url, sess->active_cache->persistent, sess->active_cache->cache_name)); - GF_Err e = gf_dm_setup_cache(sess); - if (e) return e; - sess->last_error = GF_OK; - } - sess->netio_status = GF_NETIO_CONNECTED; - sess->state = 0; - return GF_OK; -} - - - -EM_JS(int, dm_fetch_init, (int sess, int _url, int _method, int _headers, int nb_headers, int req_body, int req_body_size), { - let url = _url ? libgpac.UTF8ToString(_url) : null; - ret = GPAC.OK; - - let fetcher = libgpac._get_fetcher(sess); - if (!fetcher) { - fetcher = {}; - fetcher.sess = sess; - libgpac._fetchers.push(fetcher); - } - - fetcher._controller = new AbortController(); - let options = { - signal: fetcher._controller.signal, - method: _method ? libgpac.UTF8ToString(_method) : "GET", - mode: libgpac.gpac_fetch_mode || 'cors', - }; - let mime_type = 'application/octet-stream'; - options.headers = {}; - if (_headers) { - for (let i=0; i<nb_headers; i+=2) { - let _s = libgpac.getValue(_headers+i*4, 'i32'); - let h_name = _s ? libgpac.UTF8ToString(_s).toLowerCase() : ""; - _s = libgpac.getValue(_headers+(i+1)*4, 'i32'); - let h_val = _s ? libgpac.UTF8ToString(_s) : ""; - if (h_name.length && h_val.length) { - options.headersh_name = h_val; - if (h_name=='content-type') - mime_type = h_val; - } - } - } - if (typeof libgpac.gpac_extra_headers == 'object') { - for (const hdr in libgpac.gpac_extra_headers) { - options.headershdr = objecthdr; - } - } - - if (req_body) { - let body_ab = new Uint8Array(libgpac.HEAPU8.buffer, req_body, req_body_size); - options.body = new Blob(body_ab, {type: mime_type} ); - } - fetcher._state = 0; - fetch(url, options).then( (response) => { - if (response.ok) { - fetcher._state = 1; - fetcher._bytes = 0; - fetcher._reader = response.body.getReader(); - - let final_url = null; - if (response.redirected) final_url = response.url; - libgpac._fetcher_set_reply(fetcher.sess, response.status, final_url); - - response.headers.forEach((value, key) => { - libgpac._fetcher_set_header(fetcher.sess, key, value); - }); - libgpac._fetcher_set_header(fetcher.sess, 0, 0); - } else { - libgpac._fetcher_set_reply(fetcher.sess, response.status, null); - fetcher._state = 3; - } - }) - .catch( (e) => { - do_log_err('fetcher exception ' + e); - ret = GPAC.REMOTE_SERVICE_ERROR; - libgpac._del_fetcher(fetcher); - }); - return ret; -}); - - -EM_JS(int, dm_fetch_data, (int sess, int buffer, int buffer_size, int read_size), { - let f = libgpac._get_fetcher(sess); - if (!f) return -1; - if (f._state==0) return -44; - if (f._state==3) return -12; - if (f._state==4) return 1; - if (f._state == 1) { - f._state = 0; - f._reader.read().then( (block) => { - if (block.done) { - f._state = 4; - } else { - f._ab = block.value; - f._bytes += f._ab.byteLength; - f._block_pos = 0; - f._state = 2; - } - }) - .catch( e => { - f._state = 0; - }); - if (f._state==0) return -44; - } - - let avail = f._ab.byteLength - f._block_pos; - if (avail<buffer_size) buffer_size = avail; - - //copy array buffer - let src = f._ab.subarray(f._block_pos, f._block_pos+buffer_size); - let dst = new Uint8Array(libgpac.HEAPU8.buffer, buffer, buffer_size); - dst.set(src); - - libgpac.setValue(read_size, buffer_size, 'i32'); - f._block_pos += buffer_size; - if (f._ab.byteLength == f._block_pos) { - f._ab = null; - f._state = 1; - } - return 0; -}); - - -GF_Err gf_dm_sess_fetch_data(GF_DownloadSession *sess, char *buffer, u32 buffer_size, u32 *read_size) -{ - if (sess->reuse_cache) { - u32 remain = sess->active_cache->blob.size - sess->bytes_done; - if (!remain) { - *read_size = 0; - sess->last_error = GF_EOS; - sess->netio_status = GF_NETIO_DATA_TRANSFERED; - return GF_EOS; - } - if (remain < buffer_size) buffer_size = remain; - memcpy(buffer, sess->active_cache->blob.data + sess->bytes_done, buffer_size); - *read_size = buffer_size; - sess->bytes_done += buffer_size; - return GF_OK; - } - if (sess->state == 0) { - if (sess->start_range || sess->end_range) { - char szHdr100; - if (sess->end_range) - sprintf(szHdr, "bytes="LLU"-"LLU, sess->start_range, sess->end_range); - else - sprintf(szHdr, "bytes="LLU"-", sess->start_range); - - sess->req_hdrs = gf_realloc(sess->req_hdrs, sizeof(char*) * (sess->nb_req_hdrs+2)); - if (!sess->req_hdrs) { - sess->state = 2; - return sess->last_error = GF_OUT_OF_MEM; - } - sess->req_hdrssess->nb_req_hdrs = gf_strdup("Range"); - sess->req_hdrssess->nb_req_hdrs+1 = gf_strdup(szHdr); - sess->nb_req_hdrs+=2; - } - - sess->start_time_utc = gf_net_get_utc(); - sess->start_time = gf_sys_clock(); - sess->netio_status = GF_NETIO_CONNECTED; - - if (sess->start_range || sess->end_range) { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Starting download of %s (%s - range "LLU"-"LLU")\n", sess->req_url, sess->active_cache ? sess->active_cache->cache_name : "no cache", sess->start_range, sess->end_range)); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Starting download of %s (%s)\n", sess->req_url, sess->active_cache ? sess->active_cache->cache_name : "no cache")); - } - GF_Err fetch_e = dm_fetch_init( - EM_CAST_PTR sess, - EM_CAST_PTR sess->req_url, - EM_CAST_PTR sess->method, - EM_CAST_PTR sess->req_hdrs, - sess->nb_req_hdrs, - EM_CAST_PTR sess->req_body, - sess->req_body_size - ); - - if (fetch_e) { - gf_dm_sess_reset(sess); - sess->state = 2; - sess->netio_status = GF_NETIO_DISCONNECTED; - return sess->last_error = fetch_e; - } - sess->state = 1; - sess->netio_status = GF_NETIO_WAIT_FOR_REPLY; - } - if (sess->state==2) return sess->last_error; - - *read_size = 0; - GF_Err fetch_err = dm_fetch_data( - EM_CAST_PTR sess, - EM_CAST_PTR buffer, - buffer_size, - EM_CAST_PTR read_size - ); - - if (*read_size) { - u64 ellapsed = gf_sys_clock() - sess->start_time; - sess->bytes_done += *read_size; - if (ellapsed) - sess->bps = (u32) (sess->bytes_done * 1000 / ellapsed); - sess->netio_status = GF_NETIO_DATA_EXCHANGE; - - if (sess->active_cache) { - u32 nb_bytes= *read_size; - sess->active_cache->blob.data = gf_realloc(sess->active_cache->blob.data, sess->active_cache->blob.size + nb_bytes); - if (!sess->active_cache->blob.data) { - sess->active_cache->blob.flags |= GF_BLOB_CORRUPTED; - } else { - memcpy(sess->active_cache->blob.data + sess->active_cache->blob.size, buffer, nb_bytes); - sess->active_cache->blob.size += nb_bytes; - } - } - if (sess->total_size == sess->bytes_done) fetch_err = GF_EOS; - - } else if (!fetch_err) { - return GF_IP_NETWORK_EMPTY; - } else if (fetch_err!=GF_IP_NETWORK_EMPTY) { - sess->state=2; - sess->last_error = fetch_err; - sess->netio_status = GF_NETIO_STATE_ERROR; - if (sess->active_cache) - sess->active_cache->blob.flags = GF_BLOB_CORRUPTED; - } - - if (fetch_err==GF_EOS) { - sess->total_size = sess->bytes_done; - sess->state=2; - sess->last_error = GF_EOS; - sess->netio_status = GF_NETIO_DATA_TRANSFERED; - GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Done downloading %s - rate %d bps\n", sess->req_url, sess->bps)); - if (sess->active_cache) - sess->active_cache->blob.flags &= ~GF_BLOB_IN_TRANSFER; - } - return fetch_err; -} - - -GF_Err gf_dm_sess_get_stats(GF_DownloadSession * sess, const char **server, const char **path, u64 *total_size, u64 *bytes_done, u32 *bytes_per_sec, GF_NetIOStatus *net_status) -{ - if (!sess) return GF_OUT_OF_MEM; - if (total_size) *total_size = sess->total_size; - if (bytes_done) *bytes_done = sess->bytes_done; - if (bytes_per_sec) *bytes_per_sec = sess->bps; - if (net_status) *net_status = sess->netio_status; - return sess->last_error; -} - -const char *gf_dm_sess_mime_type(GF_DownloadSession * sess) -{ - return sess->mime; -} - -GF_Err gf_dm_sess_enum_headers(GF_DownloadSession *sess, u32 *idx, const char **hdr_name, const char **hdr_val) -{ - if( !sess || !idx || !hdr_name || !hdr_val) - return GF_BAD_PARAM; - if (*idx >= sess->nb_rsp_hdrs/2) return GF_EOS; - - u32 i = *idx * 2; - (*hdr_name) = sess->rsp_hdrsi; - (*hdr_val) = sess->rsp_hdrsi+1; - (*idx) = (*idx) + 1; - return GF_OK; -} - -void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url, Bool force) -{ - //unused we automatically purge cache when setting up session - if (!sess || !force) return; - if (sess->state==1) return; - if (sess->active_cache) { - gf_list_del_item(sess->cached_blobs, sess->active_cache); - cache_blob_del(sess->active_cache); - ((GF_DownloadSession *)sess)->active_cache = NULL; - } -} - -GF_Err gf_dm_sess_process_headers(GF_DownloadSession *sess) -{ - //no control of this for fetch - return GF_OK; -} - -GF_EXPORT -void gf_dm_sess_push_header(GF_DownloadSession *sess, const char *hdr, const char *value) -{ - if (!sess) return; - if (!hdr || !value) return; - - if (!stricmp(hdr, "Content-Length")) { - sscanf(value, LLU, &sess->total_size); - } - - sess->rsp_hdrs = gf_realloc(sess->rsp_hdrs, sizeof(char*) * (sess->nb_rsp_hdrs+2)); - if (!sess->rsp_hdrs) return; - - sess->rsp_hdrssess->nb_rsp_hdrs = gf_strdup(hdr); - sess->rsp_hdrssess->nb_rsp_hdrs+1 = gf_strdup(value); - if (!stricmp(hdr, "Content-Type")) { - sess->mime = sess->rsp_hdrssess->nb_rsp_hdrs+1; - //keep a copy of mime - if (sess->active_cache && sess->active_cache->persistent) - sess->active_cache->mime = gf_strdup(sess->mime); - } - sess->nb_rsp_hdrs+=2; -} - -GF_EXPORT -void gf_dm_sess_async_reply(GF_DownloadSession *sess, int rsp_code, const char *final_url) -{ - if (!sess) return; - GF_Err e; - switch (rsp_code) { - case 404: e = GF_URL_ERROR; break; - case 400: e = GF_SERVICE_ERROR; break; - case 401: e = GF_AUTHENTICATION_FAILURE; break; - case 405: e = GF_AUTHENTICATION_FAILURE; break; - case 416: e = GF_SERVICE_ERROR; break; //range error - default: - if (rsp_code>=500) e = GF_REMOTE_SERVICE_ERROR; - else if (rsp_code>=400) e = GF_SERVICE_ERROR; - else e = GF_OK; - break; - } - sess->last_error = e; - - if (final_url) { - if (sess->rsp_url) gf_free(sess->rsp_url); - sess->rsp_url = final_url ? gf_strdup(final_url) : NULL; - } -} - -GF_Err gf_dm_sess_fetch_once(GF_DownloadSession *sess) -{ - GF_NETIO_Parameter par; - char buffer10000; - u32 read = 0; - GF_Err e = gf_dm_sess_fetch_data(sess, buffer, 10000, &read); - if (e==GF_IP_NETWORK_EMPTY) { - - } else if ((e==GF_OK) || (e==GF_EOS)) { - if (sess->user_io && read) { - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.sess = sess; - par.error = sess->last_error; - if (e) { - par.msg_type = GF_NETIO_DATA_TRANSFERED; - } else { - par.msg_type = GF_NETIO_DATA_EXCHANGE; - par.data = buffer; - par.size = read; - } - sess->in_callback = GF_FALSE; - sess->user_io(sess->usr_cbk, &par); - sess->in_callback = GF_FALSE; - } - } else { - if (sess->user_io) { - memset(&par, 0, sizeof(GF_NETIO_Parameter)); - par.sess = sess; - par.msg_type = sess->netio_status; - par.error = sess->last_error; - sess->in_callback = GF_TRUE; - sess->user_io(sess->usr_cbk, &par); - sess->in_callback = GF_FALSE; - } - } - return e; -} - -Bool gf_dm_session_task(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms) -{ - Bool ret = GF_TRUE; - GF_DownloadSession *sess = callback; - if (!sess) return GF_FALSE; - gf_assert(sess->ftask); - if (sess->ftask==2) { - sess->ftask = 0; - return GF_FALSE; - } - - GF_Err e = gf_dm_sess_fetch_once(sess); - if (e==GF_EOS) ret = GF_FALSE; - else if (e && (e!=GF_IP_NETWORK_EMPTY)) ret = GF_FALSE; - - if (ret) { - *reschedule_ms = 1; - return GF_TRUE; - } - sess->ftask = 0; - if (sess->destroy) { - sess->destroy = GF_FALSE; - gf_dm_sess_del(sess); - } - return GF_FALSE; -} - -GF_Err gf_dm_sess_process(GF_DownloadSession *sess) -{ - if (sess->netio_status == GF_NETIO_DATA_TRANSFERED) { - return GF_OK; - } - - if ((sess->dl_flags & GF_NETIO_SESSION_NOT_THREADED) || !sess->dm->fsess) { - return gf_dm_sess_fetch_once(sess); - } - //ignore if task already allocated - if (sess->ftask) return GF_OK; - sess->ftask = GF_TRUE; - return gf_fs_post_user_task(sess->dm->fsess, gf_dm_session_task, sess, "download"); -} - - -const char *gf_dm_sess_get_resource_name(GF_DownloadSession *sess) -{ - if (!sess) return NULL; - return sess->rsp_url ? sess->rsp_url : sess->req_url; -} - -const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name) -{ - u32 i; - if (!sess || !name) return NULL; - for (i=0; i<sess->nb_rsp_hdrs; i+=2) { - char *h = sess->rsp_hdrsi; - if (!stricmp(h, name)) return sess->rsp_hdrsi+1; - } - return NULL; -} - -u64 gf_dm_sess_get_utc_start(GF_DownloadSession * sess) -{ - if (!sess) return 0; - return sess->start_time_utc; -} - -//only used for dash playing local files -u32 gf_dm_get_data_rate(GF_DownloadManager *dm) -{ - return gf_opts_get_int("core", "maxrate"); -} - -void gf_dm_sess_detach_async(GF_DownloadSession *sess) -{ - if (!sess) return; - sess->dl_flags &= ~GF_NETIO_SESSION_NOT_THREADED; - gf_dm_sess_force_memory_mode(sess, 1); -} -void gf_dm_sess_set_netcap_id(GF_DownloadSession *sess, const char *netcap_id) -{ - -} - -#endif // GPAC_CONFIG_EMSCRIPTEN - +#endif //GPAC_DISABLE_NETWORK
View file
gpac-26.02.0.tar.gz/src/utils/downloader.h
Added
@@ -0,0 +1,687 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _DOWNLOADER_H_ +#define _DOWNLOADER_H_ + +#include <gpac/tools.h> + +//regular downloader +#ifndef GPAC_DISABLE_NETWORK +#include <gpac/download.h> +#include <gpac/network.h> +#include <gpac/token.h> +#include <gpac/thread.h> +#include <gpac/list.h> +#include <gpac/base_coding.h> +#include <gpac/filters.h> +#include <gpac/crypt.h> + +#ifdef GPAC_HAS_SSL +#ifdef GPAC_HAS_NGTCP2 +#include <openssl/types.h> +#endif +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#endif + +#ifdef GPAC_HAS_CURL +#include <curl/curl.h> +//we need multi API +#if CURL_AT_LEAST_VERSION(7,80,0) +#ifndef CURLPIPE_MULTIPLEX +#define CURLPIPE_MULTIPLEX 0 +#endif +#else +#undef GPAC_HAS_CURL +#endif + +#endif //CURL + +#ifdef GPAC_HAS_HTTP2 +#if defined(_MSC_VER) +typedef SSIZE_T ssize_t; +#define NGHTTP2_STATICLIB +#else +#if defined(WIN32) && defined(GPAC_STATIC_BUILD) +#define NGHTTP2_STATICLIB +#endif +#endif +#endif + +#ifdef __USE_POSIX +#include <unistd.h> +#endif + +#define SIZE_IN_STREAM ( 2 << 29 ) + + +#define SESSION_RETRY_COUNT 10 +#define SESSION_RETRY_SSL 8 + +#include <gpac/revision.h> +#define GF_DOWNLOAD_AGENT_NAME "GPAC/"GPAC_VERSION "-rev" GPAC_GIT_REVISION + +#define GF_DOWNLOAD_BUFFER_SIZE 50000 +#define GF_DOWNLOAD_BUFFER_SIZE_LIMIT_RATE GF_DOWNLOAD_BUFFER_SIZE/20 + + +#if defined(GPAC_HAS_HTTP2) || defined(GPAC_HAS_NGTCP2) +#define GPAC_HTTPMUX +#endif + +enum { + H3_MODE_NO=0, + H3_MODE_FIRST, + H3_MODE_AUTO, + H3_MODE_ONLY, +}; + +#ifdef GPAC_HTTPMUX +typedef struct +{ + u8 * data; + u32 size, alloc, offset; +} hmux_reagg_buffer; + +typedef struct _http_mux_session +{ + Bool do_shutdown; + GF_List *sessions; + GF_DownloadSession *net_sess; + GF_Mutex *mx; + Bool copy; + Bool connected; + + //underying implementation - all callbacks must be set + + GF_Err (*setup_session)(GF_DownloadSession *sess, Bool is_destroy); + void (*close_session)(GF_DownloadSession *sess); + + //format request and submit it + GF_Err (*submit_request)(GF_DownloadSession *sess, char *req_name, const char *url, const char *param_string, Bool has_body); + //send reply and submit it + GF_Err (*send_reply)(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body); + //write and send any pending packets / signaling / etc + GF_Err (*write)(GF_DownloadSession *sess); + //send stream reset + void (*stream_reset)(GF_DownloadSession *sess, Bool is_abort); + //move to active state if hmux_data_paused was set + GF_Err (*resume)(GF_DownloadSession *sess); + //close underlying connection (QUIC), can be NULL + void (*close)(struct _http_mux_session *hmux); + //destroy underlying muxed session object + void (*destroy)(struct _http_mux_session *hmux); + //notify that some data has been received for the parent muxed session + GF_Err (*data_received)(GF_DownloadSession *sess, const u8 *data, u32 nb_bytes); + //signal new data in hmux_send_data is available (data can be NULL for requests with no body) + GF_Err (*send_pending_data)(GF_DownloadSession *sess); + + //try to flush pending data, return GF_IP_NETWORK_EMPTY if not all data is sent + GF_Err (*async_flush)(GF_DownloadSession *sess, Bool for_close); + + //underlying muxed session object (nghttp2 session, h3 session, etc...) + void *hmux_udta; +} GF_HMUX_Session; + +#endif + +#define GF_NETIO_SESSION_USE_QUIC (1<<28) +#define GF_NETIO_SESSION_RETRY_QUIC (1<<29) +#define GF_NETIO_SESSION_NO_STORE (1<<30) + +/*internal flags*/ +enum +{ + GF_DOWNLOAD_SESSION_USE_SSL = 1<<10, + GF_DOWNLOAD_SESSION_THREAD_DEAD = 1<<11, + GF_DOWNLOAD_SESSION_SSL_FORCED = 1<<12, +}; + +enum REQUEST_TYPE +{ + GET = 0, + HEAD = 1, + OTHER = 2 +}; + +/*!the structure used to store an HTTP header*/ +typedef struct +{ + char *name; + char *value; +} GF_HTTPHeader; + + +/** + * This structure handles partial downloads + */ +typedef struct __partialDownloadStruct { + char * url; + u64 startOffset; + u64 endOffset; + char * filename; +} GF_PartialDownload ; + +typedef struct +{ + //if null, task is detached and object must be discarded + struct __gf_download_session *sess; + Bool in_task; +} GF_SessTask; + +struct __gf_download_session +{ + /*this is always 0 and helps differenciating downloads from other interfaces (interfaceType != 0)*/ + u32 reserved; + + struct __gf_download_manager *dm; + GF_Thread *th; + GF_Mutex *mx; + GF_SessTask *ftask; + + Bool in_callback, destroy; + u32 proxy_enabled; + Bool allow_direct_reuse; + +#ifndef GPAC_DISABLE_LOG + char *log_name; +#endif + char *server_name; + u16 port; + + char *orig_url; + char *orig_url_before_redirect; + char *remote_path; + GF_UserCredentials * creds; + char cookieGF_MAX_PATH; + DownloadedCacheEntry cache_entry; + Bool reused_cache_entry; + FILE *cached_file; + + u32 http_buf_size; + //we alloc http_buf_size+1 for text dump + char *http_buf; + + //mime type, only used when the session is not cached. + char *mime_type; + GF_List *headers; + u32 rsp_code; + + const char *netcap_id; + GF_Socket *sock; + u32 num_retry; + GF_NetIOStatus status; + + u32 flags; + u32 total_size, bytes_done, icy_metaint, icy_count, icy_bytes, full_resource_size; + u64 start_time; + u32 nb_redirect; + + u32 bytes_per_sec; + u32 max_data_rate; + u64 start_time_utc; + Bool rate_regulated; + Bool last_chunk_found; + Bool connection_close; + Bool is_range_continuation; + /*0: no cache reconfig before next GET request: 1: try to rematch the cache entry: 2: force to create a new cache entry (for byte-range cases)*/ + u32 needs_cache_reconfig; + /* Range information if needed for the download (cf flag) */ + Bool needs_range; + u64 range_start, range_end; + + u32 connect_time, ssl_setup_time, reply_time, total_time_since_req, req_hdr_size, rsp_hdr_size; + + /*0: GET + 1: HEAD + 2: all the rest + */ + enum REQUEST_TYPE http_read_type; + + GF_Err last_error; + char *init_data; + u32 init_data_size; + Bool server_only_understand_get; + /* True if cache file must be stored on disk */ + Bool use_cache_file; + Bool disable_cache; + /*forces notification of data exchange to be sent regardless of threading mode*/ + Bool force_data_write_callback; + u32 connect_pending; +#ifdef GPAC_HAS_SSL + SSL *ssl; +#endif + + void (*do_requests)(struct __gf_download_session *); + + /*callback for data reception - may not be NULL*/ + gf_dm_user_io user_proc; + void *usr_cbk; + Bool reassigned; + + Bool chunked; + u32 nb_left_in_chunk; + u32 current_chunk_size; + u64 current_chunk_start; + + u64 request_start_time, last_fetch_time; + /*private extension*/ + void *ext; + + char *remaining_data; + u32 remaining_data_size; + + u32 conn_timeout, request_timeout; + + Bool local_cache_only; + Bool server_mode; + //0: not PUT/POST, 1: waiting for body to be completed, 2: body done + u32 put_state; + + u64 last_cap_rate_time; + u64 last_cap_rate_bytes; + u32 last_cap_rate_bytes_per_sec; + + u64 last_chunk_start_time; + u32 chunk_wnd_dur; + u32 chunk_bytes, chunk_header_bytes, cumulated_chunk_header_bytes; + //in bytes per seconds + Double cumulated_chunk_rate; + + u32 connection_timeout_ms; + + u8 *async_req_reply; + u32 async_req_reply_size; + u8 *async_buf; + u32 async_buf_size, async_buf_alloc; + + GF_SockGroup *sock_group; + +#ifdef GPAC_HTTPMUX + //Muxed HTTP (H/2 or H/3) session used by this download session. If not NULL, the mutex, socket and ssl context are moved along sessions + GF_HMUX_Session *hmux_sess; + hmux_reagg_buffer hmux_buf; + + //negative value means detached + s64 hmux_stream_id; + u8 hmux_headers_seen, hmux_ready_to_send, hmux_is_eos, hmux_data_paused, hmux_data_done, hmux_switch_sess; + u8 *hmux_send_data; + u32 hmux_send_data_len; + + //per-hmux session user data + void *hmux_priv; + +#endif + +#ifdef GPAC_HAS_HTTP2 + //HTTP2 only + u8 *h2_upgrade_settings; + u32 h2_upgrade_settings_len; + //0: upgrade not started; 1: upgrade headers send; 2: upgrade done; 3: upgrade retry without SSL APN; 4: H2 disabled for session + u8 h2_upgrade_state; +#endif + +#ifdef GPAC_HAS_CURL + CURL *curl_hnd; + GF_Err curl_closed; + Bool curl_hnd_registered; + struct curl_slist *curl_hdrs; + Bool curl_not_http; + u32 curl_fake_hcode; +#endif + +#if defined(GPAC_HTTPMUX) || defined(GPAC_HAS_CURL) + u8 *local_buf; + u32 local_buf_len, local_buf_alloc; +#endif + +}; + +#define PUSH_HDR(_name, _value) {\ + GF_SAFEALLOC(hdr, GF_HTTPHeader)\ + hdr->name = gf_strdup(_name);\ + hdr->value = gf_strdup(_value);\ + gf_list_add(sess->headers, hdr);\ + } + + +struct __gf_download_manager +{ + //mutex protecting cache access and all_sessions list + GF_Mutex *cache_mx; + char *cache_directory; + + gf_dm_get_usr_pass get_user_password; + void *usr_cbk; + + GF_List *all_sessions; + Bool disable_cache, simulate_no_connection, allow_offline_cache; + u32 limit_data_rate, read_buf_size; + u64 max_cache_size, cur_cache_size; + Bool allow_broken_certificate; + u32 next_cache_clean, cache_clean_ms; + + GF_List *skip_proxy_servers; + GF_List *credentials; + GF_List *cache_entries; + /* FIXME : should be placed in DownloadedCacheEntry maybe... */ + GF_List *partial_downloads; +#ifdef GPAC_HAS_SSL + SSL_CTX *ssl_ctx; +#endif + + GF_FilterSession *filter_session; + + Bool disable_http2; + u32 h3_mode; + + + Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool cache_destroy); + void *lc_udta; + + +#ifdef GPAC_HAS_CURL + CURLM *curl_multi; + GF_Mutex *curl_mx; + Bool curl_has_close; +#endif + +}; + +#if 0 +#define SET_LAST_ERR(_e) \ + {\ + GF_Err __e = _e;\ + if (__e==GF_IP_NETWORK_EMPTY) {\ + gf_assert(sess->status != GF_NETIO_STATE_ERROR); \ + }\ + sess->last_error = _e;\ + }\ + +#else + +#define SET_LAST_ERR(_e) sess->last_error = _e; + +#endif + + +//cache functions - NOT exported, only downloader has access to it +s32 gf_cache_remove_session_from_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess); +Bool gf_cache_set_mime(const DownloadedCacheEntry entry, const char *mime); +Bool gf_cache_set_range(const DownloadedCacheEntry entry, u64 size, u64 start_range, u64 end_range); +Bool gf_cache_set_content(const DownloadedCacheEntry entry, GF_Blob *blob, Bool copy, GF_Mutex *mx); +Bool gf_cache_set_headers(const DownloadedCacheEntry entry, const char *headers); +void gf_cache_set_downtime(const DownloadedCacheEntry entry, u32 download_time_ms); +void gf_cache_set_max_age(const DownloadedCacheEntry entry, u32 max_age, Bool must_revalidate); +Bool gf_cache_entry_persistent(const DownloadedCacheEntry entry); +void gf_cache_delete_entry( const DownloadedCacheEntry entry ); +GF_Err gf_cache_set_etag_on_server(const DownloadedCacheEntry entry, const char * eTag ); +const char * gf_cache_get_mime_type(const DownloadedCacheEntry entry ); +GF_Err gf_cache_set_mime_type(const DownloadedCacheEntry entry, const char * mime_type ); +const char * gf_cache_get_url(const DownloadedCacheEntry entry ); +GF_Err gf_cache_set_last_modified_on_server(const DownloadedCacheEntry entry, const char * newLastModified ); +const char * gf_cache_get_cache_filename(const DownloadedCacheEntry entry ); +u32 gf_cache_get_cache_filesize(const DownloadedCacheEntry entry); +GF_Err gf_cache_set_content_length(const DownloadedCacheEntry entry, u32 length ); +u32 gf_cache_get_content_length(const DownloadedCacheEntry entry); +GF_Err gf_cache_get_http_headers(const DownloadedCacheEntry entry, const char **etag, const char **last_modif); +u64 gf_cache_cleanup(const char * directory, u64 max_size); +void gf_cache_entry_set_delete_files_when_deleted(const DownloadedCacheEntry entry); +u64 gf_cache_get_start_range( const DownloadedCacheEntry entry ); +u64 gf_cache_get_end_range( const DownloadedCacheEntry entry ); +Bool gf_cache_are_headers_processed(const DownloadedCacheEntry entry); +GF_Err gf_cache_set_headers_processed(const DownloadedCacheEntry entry); +FILE *gf_cache_open_read(const DownloadedCacheEntry entry); +Bool gf_cache_is_mem(const DownloadedCacheEntry entry); +GF_Err gf_cache_write_to_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, const char * data, const u32 size, GF_Mutex *mx); +GF_Err gf_cache_close_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, Bool success); +GF_Err gf_cache_open_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess ); +void gf_cache_set_end_range(DownloadedCacheEntry entry, u64 range_end); +Bool gf_cache_is_in_progress(const DownloadedCacheEntry entry); +char *gf_cache_get_forced_headers(const DownloadedCacheEntry entry); +u32 gf_cache_get_downtime(const DownloadedCacheEntry entry); +u32 gf_cache_is_done(const DownloadedCacheEntry entry); +Bool gf_cache_is_deleted(const DownloadedCacheEntry entry); +const u8 *gf_cache_get_content(const DownloadedCacheEntry entry, u32 *size, u32 *max_valid_size, Bool *was_modified); +void gf_cache_release_content(const DownloadedCacheEntry entry); +void gf_cache_remove_entry_from_session(GF_DownloadSession * sess); + + + + +void gf_dm_data_received(GF_DownloadSession *sess, u8 *payload, u32 payload_size, Bool store_in_init, u32 *rewrite_size, u8 *original_payload); +GF_Err gf_dm_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read); +GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size); +GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select); +GF_Err gf_dm_sess_flush_close(GF_DownloadSession *sess); +void gf_dm_sess_set_header_ex(GF_DownloadSession *sess, const char *name, const char *value, Bool allow_overwrite); + +GF_Err dm_sess_write(GF_DownloadSession *sess, const u8 *buffer, u32 size); +GF_Err gf_ssl_write(GF_DownloadSession *sess, const u8 *buffer, u32 size, u32 *written); + +void dm_sess_sk_del(GF_DownloadSession *sess); + +void gf_dm_sess_clear_headers(GF_DownloadSession *sess); + +GF_DownloadSession *gf_dm_sess_new_internal(GF_DownloadManager * dm, const char *url, u32 dl_flags, + gf_dm_user_io user_io, void *usr_cbk, GF_Socket *server, Bool force_server, GF_Err *e); +void gf_dm_sess_notify_state(GF_DownloadSession *sess, GF_NetIOStatus dnload_status, GF_Err error); +void gf_dm_sess_set_header_ex(GF_DownloadSession *sess, const char *name, const char *value, Bool allow_overwrite); +void gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value); +void gf_dm_sess_reload_cached_headers(GF_DownloadSession *sess); + +GF_Err gf_dm_sess_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body); + +Bool gf_dm_sess_use_tls(GF_DownloadSession * sess); + +typedef enum +{ + HTTP_NO_CLOSE=0, + HTTP_CLOSE, + HTTP_RESET_CONN, +} HTTPCloseType; +void gf_dm_disconnect(GF_DownloadSession *sess, HTTPCloseType close_type); + + +GF_Err gf_dm_get_url_info(const char * url, GF_URL_Info * info, const char * baseURL); +void http_do_requests(GF_DownloadSession *sess); + +void gf_dm_configure_cache(GF_DownloadSession *sess); + +//multiplexed HTTP support (H2, H3) +#ifdef GPAC_HTTPMUX +GF_DownloadSession *hmux_get_session(void *user_data, s64 stream_id, Bool can_reassign); +void hmux_detach_session(GF_HMUX_Session *hmux_sess, GF_DownloadSession *sess); +//get data from hmux session +void hmux_fetch_data(GF_DownloadSession *sess, u8 *obuffer, u32 size, u32 *nb_bytes); +//call gf_dm_data_received with local hmux buffer and set it to 0 +void hmux_flush_internal_data(GF_DownloadSession *sess, Bool store_in_init); + +GF_Err hmux_send_payload(GF_DownloadSession *sess, u8 *data, u32 size); +#endif + +//HTTP2 only +#ifdef GPAC_HAS_HTTP2 +GF_Err http2_check_upgrade(GF_DownloadSession *sess); +void http2_set_upgrade_headers(GF_DownloadSession *sess); +GF_Err http2_do_upgrade(GF_DownloadSession *sess, const char *body, u32 body_len); +#endif + +#ifdef GPAC_HAS_NGTCP2 +GF_Err http3_connect(GF_DownloadSession *sess, char *server, u32 server_port); +#endif + +#ifdef GPAC_HAS_CURL +GF_Err curl_setup_session(GF_DownloadSession* sess); +void curl_flush(GF_DownloadSession *sess); +void curl_destroy(GF_DownloadSession *sess); +Bool curl_can_handle_url(const char *url); +GF_Err curl_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read); +void curl_connect(GF_DownloadSession *sess); +GF_Err curl_process_reply(GF_DownloadSession *sess, u32 *ContentLength); +#endif + +#ifdef GPAC_HAS_SSL +void *gf_dm_ssl_init(GF_DownloadManager *dm, Bool no_quic); +Bool gf_ssl_check_cert(SSL *ssl, const char *server_name); +void gf_dm_sess_server_setup_ssl(GF_DownloadSession *sess); +GF_Err gf_ssl_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read); + +typedef enum +{ + SSL_CONNECT_OK=0, + SSL_CONNECT_WAIT, + SSL_CONNECT_RETRY +} SSLConnectStatus; +SSLConnectStatus gf_ssl_try_connect(GF_DownloadSession *sess, const char *proxy); + +#endif + + +//exported for httpout + +//socket and SSL context ownership is transfered to the download session object +GF_DownloadSession *gf_dm_sess_new_server(GF_DownloadManager *dm, GF_Socket *server, void *ssl_ctx, gf_dm_user_io user_io, void *usr_cbk, Bool async, GF_Err *e); +GF_DownloadSession *gf_dm_sess_new_subsession(GF_DownloadSession *sess, s64 stream_id, void *usr_cbk, GF_Err *e); +u32 gf_dm_sess_subsession_count(GF_DownloadSession *); + +void gf_dm_sess_set_timeout(GF_DownloadSession *sess, u32 timeout); + +GF_Socket *gf_dm_sess_get_socket(GF_DownloadSession *); +GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size); +void gf_dm_sess_clear_headers(GF_DownloadSession *sess); +void gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value); +void gf_dm_sess_set_header_ex(GF_DownloadSession *sess, const char *name, const char *value, Bool allow_overwrite); +GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select); +u32 gf_dm_sess_async_pending(GF_DownloadSession *sess); + +GF_Err gf_dm_sess_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body); +void gf_dm_sess_server_reset(GF_DownloadSession *sess); + +typedef enum +{ + GF_SESS_TYPE_HTTP=0, + GF_SESS_TYPE_HTTP2, + GF_SESS_TYPE_HTTP3, +} GF_HTTPSessionType; +GF_HTTPSessionType gf_dm_sess_is_hmux(GF_DownloadSession *sess); +void gf_dm_sess_close_hmux(GF_DownloadSession *sess); + +void gf_dm_sess_set_sock_group(GF_DownloadSession *sess, GF_SockGroup *sg); + +#ifdef GPAC_HAS_SSL + +void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e); +void *gf_ssl_server_context_new(const char *cert, const char *key, Bool for_quic); +void gf_ssl_server_context_del(void *ssl_server_ctx); +Bool gf_ssl_init_lib(); + +#endif + +#ifdef GPAC_HAS_NGTCP2 + +typedef struct __gf_quic_server GF_QuicServer; +GF_Err gf_dm_quic_server_new(GF_DownloadManager *dm, void *ssl_ctx, GF_QuicServer **oq, const char *ip, u32 port, const char *netcap_id, + Bool (*accept_conn)(void *udta, GF_DownloadSession *sess, const char *address, u32 port), + void *udta); + +void gf_dm_quic_server_del(GF_QuicServer *qs); +GF_Socket *gf_dm_quic_get_socket(GF_QuicServer *qs); +GF_Err gf_dm_quic_process(GF_QuicServer *qs); +GF_Err gf_dm_quic_verify(GF_QuicServer *qs); +void gf_dm_sess_set_callback(GF_DownloadSession *sess, gf_dm_user_io user_io, void *usr_cbk); +#endif + + + +//end GPAC_DISABLE_NETWORK, start EMSCRIPTEN +#elif defined(GPAC_CONFIG_EMSCRIPTEN) +#include <gpac/download.h> +#include <gpac/list.h> +#include <gpac/thread.h> +#include <gpac/filters.h> + +struct __gf_download_manager +{ + GF_FilterSession *fsess; + GF_List *all_sessions; + GF_Mutex *mx; +}; + +typedef struct __cache_blob +{ + GF_Blob blob; + char *url, *cache_name; + char *mime; + u64 start_range, end_range; + Bool persistent; +} GF_CacheBlob; + +struct __gf_download_session +{ + GF_DownloadManager *dm; + gf_dm_user_io user_io; + void *usr_cbk; + char method100; + + char *req_url; + char *rsp_url; + /*request headers*/ + char **req_hdrs; + u32 nb_req_hdrs; + u32 req_body_size; + char *req_body; + u64 start_range, end_range; + + /*response headers*/ + char **rsp_hdrs; + u32 nb_rsp_hdrs; + char *mime; //pointer to content-type header or blob + + u32 state, bps; + u64 start_time, start_time_utc; + u64 total_size, bytes_done; + GF_Err last_error; + GF_NetIOStatus netio_status; + u32 dl_flags; + Bool reuse_cache; + u32 ftask; + GF_List *cached_blobs; + + GF_CacheBlob *active_cache; + + Bool destroy, in_callback; + +}; + + + +#endif // GPAC_CONFIG_EMSCRIPTEN + +#endif //_DOWNLOADER_H_
View file
gpac-26.02.0.tar.gz/src/utils/downloader_cache.c
Added
@@ -0,0 +1,1639 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre, Pierre Souchay + * Copyright (c) Telecom ParisTech 2010-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "downloader.h" + +#ifndef GPAC_DISABLE_NETWORK + +#include <gpac/network.h> +#include <gpac/download.h> +#include <gpac/token.h> +#include <gpac/thread.h> +#include <gpac/list.h> +#include <gpac/base_coding.h> +#include <gpac/tools.h> +#include <gpac/config_file.h> +#include <stdio.h> +#include <string.h> + +#if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500 +#include <unistd.h> +#endif + +static const char * CACHE_SECTION_NAME = "cache"; +static const char * CACHE_SECTION_KEY_URL = "url"; +static const char * CACHE_SECTION_KEY_RANGE = "range"; +static const char * CACHE_SECTION_KEY_ETAG = "ETag"; +static const char * CACHE_SECTION_KEY_MIME_TYPE = "Content-Type"; +static const char * CACHE_SECTION_KEY_CONTENT_SIZE = "Content-Length"; +static const char * CACHE_SECTION_KEY_LAST_MODIFIED = "Last-Modified"; +static const char * CACHE_SECTION_KEY_MAXAGE = "MaxAge"; +static const char * CACHE_SECTION_KEY_MUST_REVALIDATE = "MustRevalidate"; +static const char * CACHE_SECTION_KEY_NOCACHE = "NoCache"; +static const char * CACHE_SECTION_KEY_CREATED = "Created"; +static const char * CACHE_SECTION_KEY_LAST_HIT = "LastHit"; +static const char * CACHE_SECTION_KEY_NUM_HIT = "NumHit"; +static const char * CACHE_SECTION_KEY_INWRITE = "InWrite"; +static const char * CACHE_SECTION_KEY_TODELETE = "ToDelete"; +static const char * CACHE_SECTION_USERS = "users"; + +enum CacheValid +{ + CORRUPTED = 1, + DELETED = 1<<1, + IN_PROGRESS = 1<<2, +}; + +/** +* This opaque structure handles the data from the cache +*/ +struct __DownloadedCacheEntryStruct +{ + // URL of the cache (never NULL) + char *url; + // Hash of the cache (never NULL) + char *hash; + // Name of the cache filename, (NULL for mem) + char *cache_filename; + // Name of the config filename, (NULL for mem) + char *cfg_filename; + // Theorical size of cache if any + u32 contentLength; + // Real size of cache + u32 cacheSize; + // The last modification time on the server + char *serverLastModified; + //The last modification time of the cache if any + char *diskLastModified; + // server ETag if any + char *serverETag; + //disk ETag if any + char * diskETag; + // Mime-type + char * mimeType; + // Write pointer for the cache + FILE * writeFilePtr; + // Bytes written during this cache session + u32 written_in_cache; + // Flag indicating whether we have to revalidate + enum CacheValid flags; + //pointer to write session + const GF_DownloadSession * write_session; + //list of read sessions + GF_List * sessions; + //set to true if disk files for this entry shall be removed upon destroy (due to error) + Bool discard_on_delete; + + u64 max_age; + Bool must_revalidate; + Bool other_in_use; + + //start and end range of the cache + u64 range_start, range_end; + + //append to cache + Bool continue_file; + //contentLength before append + u32 previousRangeContentLength; + //set once headers have been processed + Bool headers_done; + // Set to true if file is not stored on disk + Bool memory_stored; + //allocation + u32 mem_allocated; + u8 *mem_storage; + GF_Blob cache_blob; + + //for external blob only + GF_Blob *external_blob; + char *forced_headers; + u32 downtime; + + //prevent deleting files when deleting entries, typically for init segments + Bool persistent; +}; + +Bool gf_sys_check_process_id(u32 pid); + +Bool gf_cache_entry_persistent(const DownloadedCacheEntry entry) +{ + return entry ? entry->persistent : GF_FALSE; +} +void gf_cache_entry_set_persistent(const DownloadedCacheEntry entry) +{ + if (entry) entry->persistent = GF_TRUE; +} + +static const char * cache_file_prefix = "gpac_cache_"; + +typedef struct +{ + char *name; + u64 created; + u64 last_hit; + u32 nb_hit; + u32 size; +} CacheInfo; + +typedef struct +{ + GF_List *files; + u64 tot_size; +} CacheGather; + +static Bool cache_cleanup_processes(GF_Config *cfg) +{ + u32 i, count=gf_cfg_get_key_count(cfg, CACHE_SECTION_USERS); + for (i=0;i<count;i++) { + const char *opt = gf_cfg_get_key_name(cfg, CACHE_SECTION_USERS, i); + if (!opt) break; + u32 procid; + sscanf(opt, "%u", &procid); + if (gf_sys_check_process_id(procid)) continue; + gf_cfg_set_key(cfg, CACHE_SECTION_USERS, opt, NULL); + count--; + i--; + } + return count ? GF_TRUE : GF_FALSE; +} + +static Bool gather_cache_files(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + CacheGather *gci = (CacheGather *)cbck; + GF_Config *file = gf_cfg_new(NULL, item_path); + u32 i, count, size=0; + const char *opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE); + if (opt) sscanf(opt, "%u", &size); + gci->tot_size += size; + + if (cache_cleanup_processes(file)) { + gf_cfg_del(file); + return GF_FALSE; + } + + CacheInfo *ci; + GF_SAFEALLOC(ci, CacheInfo); + if (ci) ci->name = gf_strdup(item_path); + if (!ci || !ci->name) { + if (ci) gf_free(ci); + gf_cfg_del(file); + return GF_FALSE; + } + ci->size = size; + opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CREATED); + if (opt) sscanf(opt, LLU, &ci->created); + opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_HIT); + if (opt) sscanf(opt, LLU, &ci->last_hit); + opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NUM_HIT); + if (opt) { sscanf(opt, "%u", &ci->nb_hit); if (ci->nb_hit) ci->nb_hit--; } + gf_cfg_del(file); + + count = gf_list_count(gci->files); + for (i=0; i<count; i++) { + CacheInfo *a_ci = gf_list_get(gci->files, i); + //sort by nb hits first + if (ci->nb_hit<a_ci->nb_hit) { + gf_list_insert(gci->files, ci, i); + return GF_FALSE; + } else if (ci->nb_hit>a_ci->nb_hit) continue; + + //then by last hit + if (ci->last_hit<a_ci->last_hit) { + gf_list_insert(gci->files, ci, i); + return GF_FALSE; + } + else if (ci->last_hit>a_ci->last_hit) continue; + + //then by created + if (ci->created<a_ci->created) { + gf_list_insert(gci->files, ci, i); + return GF_FALSE; + } + else if (ci->created>a_ci->created) continue; + + //then by size + if (ci->size<a_ci->size) { + gf_list_insert(gci->files, ci, i); + return GF_FALSE; + } + } + gf_list_add(gci->files, ci); + return 0; +} + +u64 gf_cache_cleanup(const char * directory, u64 max_size) +{ + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s/.lock", directory); + if (!gf_sys_create_lockfile(szLOCK)) + return max_size; + + CacheGather gci = {0}; + gci.files = gf_list_new(); + gf_enum_directory(directory, GF_FALSE, gather_cache_files, &gci, "*.txt"); + u64 cache_size = gci.tot_size; + + while (gf_list_count(gci.files)) { + CacheInfo *ci = gf_list_pop_back(gci.files); + if (cache_size>max_size) { + if (cache_size>ci->size) cache_size -= ci->size; + else cache_size = 0; + gf_file_delete(ci->name); + char *sep = strstr(ci->name, ".txt"); + sep0 = 0; + gf_file_delete(ci->name); + sep0 = '.'; + } + gf_free(ci->name); + gf_free(ci); + } + gf_list_del(gci.files); + gf_file_delete(szLOCK); + return cache_size; +} + +void gf_cache_entry_set_delete_files_when_deleted(const DownloadedCacheEntry entry) +{ + if (entry && !entry->persistent) { + entry->discard_on_delete = GF_TRUE; + } +} + +#define CHECK_ENTRY if (!entry) { GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE entry is null at " __FILE__ ":%d\n", __LINE__)); return GF_BAD_PARAM; } + +/* +* Getters functions +*/ + +const char * gf_cache_get_etag_on_server ( const DownloadedCacheEntry entry ) +{ + return entry ? entry->serverETag : NULL; +} + +const char * gf_cache_get_mime_type ( const DownloadedCacheEntry entry ) +{ + return entry ? entry->mimeType : NULL; +} + + +GF_Err gf_cache_set_headers_processed(const DownloadedCacheEntry entry) +{ + if (!entry) return GF_BAD_PARAM; + entry->headers_done = GF_TRUE; + return GF_OK; +} + +Bool gf_cache_are_headers_processed(const DownloadedCacheEntry entry) +{ + if (!entry) return GF_FALSE; + return entry->headers_done; +} + +GF_Err gf_cache_set_etag_on_server(const DownloadedCacheEntry entry, const char * eTag ) { + if (!entry) + return GF_BAD_PARAM; + if (entry->serverETag) + gf_free(entry->serverETag); + entry->serverETag = eTag ? gf_strdup(eTag) : NULL; + return GF_OK; +} + +GF_Err gf_cache_set_etag_on_disk(const DownloadedCacheEntry entry, const char * eTag ) { + if (!entry) + return GF_BAD_PARAM; + if (entry->diskETag) + gf_free(entry->diskETag); + entry->diskETag = eTag ? gf_strdup(eTag) : NULL; + return GF_OK; +} + +GF_Err gf_cache_set_mime_type(const DownloadedCacheEntry entry, const char * mime_type ) { + if (!entry) + return GF_BAD_PARAM; + if (entry->mimeType) + gf_free(entry->mimeType); + entry->mimeType = mime_type? gf_strdup( mime_type) : NULL; + return GF_OK; +} + +u64 gf_cache_get_start_range( const DownloadedCacheEntry entry ) +{ + return entry ? entry->range_start : 0; +} + +u64 gf_cache_get_end_range( const DownloadedCacheEntry entry ) +{ + return entry ? entry->range_end : 0; +} + +const char * gf_cache_get_url ( const DownloadedCacheEntry entry ) +{ + return entry ? entry->url : NULL; +} + +const char * gf_cache_get_last_modified_on_server ( const DownloadedCacheEntry entry ) +{ + return entry ? entry->serverLastModified : NULL; +} + +GF_Err gf_cache_set_last_modified_on_server ( const DownloadedCacheEntry entry, const char * newLastModified ) +{ + if (!entry) + return GF_BAD_PARAM; + if (entry->serverLastModified) + gf_free(entry->serverLastModified); + entry->serverLastModified = newLastModified ? gf_strdup(newLastModified) : NULL; + return GF_OK; +} + +GF_Err gf_cache_set_last_modified_on_disk ( const DownloadedCacheEntry entry, const char * newLastModified ) +{ + if (!entry) + return GF_BAD_PARAM; + if (entry->diskLastModified) + gf_free(entry->diskLastModified); + entry->diskLastModified = newLastModified ? gf_strdup(newLastModified) : NULL; + return GF_OK; +} + +static GF_LockStatus cache_entry_lock(const char *lockfile) +{ + GF_LockStatus lock_type; + u32 start=gf_sys_clock(); + while (1) { + lock_type = gf_sys_create_lockfile(lockfile); + if (lock_type) break; + if (gf_sys_clock()-start>50) break; + } + return lock_type; +} + + +GF_Err gf_cache_flush_disk_cache ( const DownloadedCacheEntry entry, Bool success ) +{ + char buff100; + CHECK_ENTRY; + if (entry->mem_storage) + return GF_OK; + + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s.lock", entry->cfg_filename); + GF_LockStatus lock_type = cache_entry_lock(szLOCK); + GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename); + + if (success) { + char szSize50; + sprintf(szSize, "%u", entry->written_in_cache); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE, szSize); + } + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE, NULL); + + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_URL, entry->url); + + if (entry->range_start || entry->range_end) { + sprintf(buff, LLD"-"LLD, entry->range_start, entry->range_end); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_RANGE, buff); + } + + if (entry->mimeType) + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MIME_TYPE, entry->mimeType); + if (entry->diskETag) + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_ETAG, entry->diskETag); + if (entry->diskLastModified) + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_MODIFIED, entry->diskLastModified); + + snprintf(buff, 16, "%d", entry->contentLength); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE, buff); + //save and destroy + gf_cfg_del(cfg); + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + return GF_OK; +} + +u32 gf_cache_get_cache_filesize(const DownloadedCacheEntry entry ) +{ + return entry ? entry->cacheSize : -1; +} + +const char * gf_cache_get_cache_filename( const DownloadedCacheEntry entry ) +{ + return entry ? entry->cache_filename : NULL; +} + +GF_Err gf_cache_get_http_headers(const DownloadedCacheEntry entry, const char **etag, const char **last_modif) +{ + if (!entry || !etag || !last_modif) + return GF_BAD_PARAM; + + *etag = *last_modif = NULL; + if (entry->flags & (CORRUPTED|DELETED)) + return GF_OK; + + *etag = entry->diskETag; + *last_modif = entry->diskLastModified; + if (entry->persistent && entry->memory_stored) { + *etag = entry->serverETag; + *last_modif = entry->serverLastModified; + } + return GF_OK; +} + +static void gf_cache_check_if_cache_file_is_corrupted(const DownloadedCacheEntry entry, const char *url, GF_Config *cfg) +{ + const char * keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_URL ); + if ( keyValue == NULL || stricmp ( url, keyValue ) ) { + entry->flags |= CORRUPTED; + return; + } + + keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_RANGE); + if (keyValue) { + u64 s, e; + sscanf(keyValue, LLD"-"LLD, &s, &e); + /*mark as corrupted if not same range (we don't support this for the time being ...*/ + if ((s!=entry->range_start) || (e!=entry->range_end)) { + entry->flags |= CORRUPTED; + return; + } + } + + if (gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NOCACHE) ) { + entry->flags |= CORRUPTED; + return; + } + + FILE *the_cache = NULL; + if (entry->cache_filename && strncmp(entry->cache_filename, "gmem://", 7)) + the_cache = gf_fopen ( entry->cache_filename, "rb" ); + + if ( the_cache ) { + char * endPtr; + const char * keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE); + if (keyValue) { + entry->cacheSize = 0; + entry->flags |= IN_PROGRESS; + gf_fclose ( the_cache ); + return; + } + keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE ); + + entry->cacheSize = ( u32 ) gf_fsize(the_cache); + gf_fclose ( the_cache ); + if (keyValue) { + entry->contentLength = (u32) strtoul( keyValue, &endPtr, 10); + if (*endPtr!='\0' || entry->contentLength != entry->cacheSize) { + entry->flags |= CORRUPTED; + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Corrupted: file and cache info size mismatch\n")); + } + } else { + entry->flags |= CORRUPTED; + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Corrupted: missing cache content size\n")); + } + } else if (!entry->mem_storage) { + entry->flags |= CORRUPTED; + } +} + +#define _CACHE_HASH_SIZE 20 +#define _CACHE_MAX_EXTENSION_SIZE 6 +static const char * default_cache_file_suffix = ".dat"; +static const char * cache_file_info_suffix = ".txt"; + +DownloadedCacheEntry gf_cache_create_entry(const char * cache_directory, const char * url , u64 start_range, u64 end_range, Bool mem_storage, GF_Mutex *mx) +{ + char tmpGF_MAX_PATH; + u8 hash_CACHE_HASH_SIZE; + int sz; + char ext_CACHE_MAX_EXTENSION_SIZE; + DownloadedCacheEntry entry = NULL; + if (!url || !cache_directory) return NULL; + + sz = (u32) strlen ( url ); + if ( sz > GF_MAX_PATH ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, + ("CACHE ERROR, URL is too long (%d chars), more than %d chars.\n", sz, GF_MAX_PATH )); + return NULL; + } + tmp0 = '\0'; + /*generate hash of the full url*/ + if (start_range && end_range) { + snprintf(tmp, GF_MAX_PATH, "%s_"LLD"-"LLD, url, start_range, end_range ); + } else { + strcpy ( tmp, url ); + } + gf_sha1_csum ((u8*) tmp, (u32) strlen ( tmp ), hash ); + tmp0 = 0; + { + int i; + for ( i=0; i<20; i++ ) + { + char t3; + t2 = 0; + sprintf ( t, "%02X", hashi ); + strcat ( tmp, t ); + } + } + assert ( strlen ( tmp ) == (_CACHE_HASH_SIZE * 2) ); + + GF_SAFEALLOC(entry, struct __DownloadedCacheEntryStruct); + if ( !entry ) return NULL; + + entry->url = gf_strdup ( url ); + entry->hash = gf_strdup ( tmp ); + entry->memory_stored = mem_storage; + entry->cacheSize = 0; + entry->contentLength = 0; + entry->serverETag = NULL; + entry->diskETag = NULL; + entry->flags = 0; + entry->diskLastModified = NULL; + entry->serverLastModified = NULL; + entry->range_start = start_range; + entry->range_end = end_range; + + entry->discard_on_delete = GF_FALSE; + entry->write_session = NULL; + entry->sessions = gf_list_new(); + + if (entry->memory_stored) { + entry->cache_filename = (char*)gf_malloc ( strlen ("gmem://") + 8 + strlen("@") + 16 + 1); + } else { + /* Sizeof cache directory + hash + possible extension */ + entry->cache_filename = (char*)gf_malloc ( strlen ( cache_directory ) + strlen(cache_file_prefix) + strlen(tmp) + _CACHE_MAX_EXTENSION_SIZE + 1); + } + + if ( !entry->hash || !entry->url || !entry->cache_filename || !entry->sessions) { + /* Probably out of memory */ + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE Failed to create cache entry, out of memory\n")); + gf_cache_delete_entry ( entry ); + return NULL; + } + + if (entry->memory_stored) { + entry->cache_blob.mx = mx; + entry->cache_blob.data = entry->mem_storage; + entry->cache_blob.size = entry->contentLength; + char *burl = gf_blob_register(&entry->cache_blob); + if (burl) { + strcpy(entry->cache_filename, burl); + gf_free(burl); + } + return entry; + } + + + tmp0 = '\0'; + strcpy ( entry->cache_filename, cache_directory ); + strcat( entry->cache_filename, cache_file_prefix ); + strcat ( entry->cache_filename, entry->hash ); + strcpy ( tmp, url ); + + { + char * parser; + parser = strrchr ( tmp, '?' ); + if ( parser ) + parser0 = '\0'; + parser = strrchr ( tmp, '#' ); + if ( parser ) + parser0 = '\0'; + parser = strrchr ( tmp, '.' ); + if ( parser && ( strlen ( parser ) < _CACHE_MAX_EXTENSION_SIZE ) ) + strncpy(ext, parser, _CACHE_MAX_EXTENSION_SIZE); + else + strncpy(ext, default_cache_file_suffix, _CACHE_MAX_EXTENSION_SIZE); + assert (strlen(ext)); + strcat( entry->cache_filename, ext); + } + + gf_dynstrcat(&entry->cfg_filename, entry->cache_filename, NULL); + gf_dynstrcat(&entry->cfg_filename, cache_file_info_suffix, NULL); + + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s.lock", entry->cfg_filename); + GF_LockStatus lock_type = cache_entry_lock(szLOCK); + if (!lock_type) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to grab cache lock for entry %s, request will not be cached\n", url)); + gf_cache_delete_entry ( entry ); + return NULL; + } + + Bool in_cache = gf_file_exists(entry->cfg_filename); + + GF_Config *cfg = gf_cfg_force_new ( NULL, entry->cfg_filename); + if ( !cfg ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE Failed to create cache entry for %s, request will not be cached\n", url)); + gf_cache_delete_entry ( entry ); + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + return NULL; + } + + if (in_cache) { + gf_cache_check_if_cache_file_is_corrupted(entry, url, cfg); + if ((entry->flags & CORRUPTED) && (gf_cfg_get_key_count(cfg, CACHE_SECTION_USERS)==0)) { + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Cache entry for %s corrupted but no other users, recreating\n", url)); + entry->flags &= ~CORRUPTED; + gf_file_delete(entry->cache_filename); + gf_file_delete(entry->cfg_filename); + gf_cfg_del_section(cfg, CACHE_SECTION_NAME); + gf_cfg_del_section(cfg, CACHE_SECTION_USERS); + } + } + + if (entry->flags & CORRUPTED) { + gf_cfg_del(cfg); + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Incompatible cache entry for %s, request will not be cached\n", url)); + gf_cache_delete_entry ( entry ); + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + return NULL; + } + + gf_cache_set_etag_on_disk(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_ETAG)); + gf_cache_set_etag_on_server(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_ETAG)); + gf_cache_set_mime_type(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MIME_TYPE)); + gf_cache_set_last_modified_on_disk(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_MODIFIED)); + gf_cache_set_last_modified_on_server(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_MODIFIED)); + + const char *opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MAXAGE); + if (opt) sscanf(opt, LLU, &entry->max_age); + opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MUST_REVALIDATE); + if (opt && !strcmp(opt, "true")) entry->must_revalidate = GF_TRUE; + + char szBUF20; + sprintf(szBUF, LLU, gf_net_get_utc()/1000); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_HIT, szBUF); + + u32 nb_hits=0; + opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NUM_HIT ); + if (opt) nb_hits = atoi(opt); + nb_hits++; + sprintf(szBUF, "%u", nb_hits); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NUM_HIT, szBUF); + + if (!mem_storage) { + char szID20; + if (cache_cleanup_processes(cfg)) + entry->other_in_use = GF_TRUE; + sprintf(szID, "%u", gf_sys_get_process_id()); + gf_cfg_set_key(cfg, CACHE_SECTION_USERS, szID, "true"); + } + + //rewrite to disk + gf_cfg_del(cfg); + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + + return entry; +} + +GF_Err gf_cache_set_content_length( const DownloadedCacheEntry entry, u32 length ) +{ + CHECK_ENTRY; + if (entry->continue_file) { + entry->contentLength = entry->previousRangeContentLength + length; + } else { + entry->contentLength = length; + } + return GF_OK; +} + +u32 gf_cache_get_content_length( const DownloadedCacheEntry entry) +{ + if (!entry) return 0; + if (entry->external_blob) { + return entry->external_blob->size; + } + return entry->contentLength; +} + +GF_Err gf_cache_close_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, Bool success ) +{ + GF_Err e = GF_OK; + CHECK_ENTRY; + if (!sess || !entry->write_session || entry->write_session != sess) + return GF_OK; + gf_assert( sess == entry->write_session ); + if (entry->writeFilePtr) { + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, + ("CACHE Closing file %s, %d bytes written.\n", entry->cache_filename, entry->written_in_cache)); + + if (gf_fflush( entry->writeFilePtr ) || gf_fclose( entry->writeFilePtr )) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to flush/close file on disk\n")); + e = GF_IO_ERR; + } + if (!e && success) { + e = gf_cache_set_last_modified_on_disk( entry, gf_cache_get_last_modified_on_server(entry)); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to set last-modified\n")); + } + if (!e) { + e = gf_cache_set_etag_on_disk( entry, gf_cache_get_etag_on_server(entry)); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to set etag\n")); + } + } + } + + e = gf_cache_flush_disk_cache(entry, success); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to flush cache entry on disk after etag/last-modified\n")); + } + +#if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500 + /* On UNIX, be sure to flush all the data */ + sync(); +#endif + entry->writeFilePtr = NULL; + if (GF_OK != e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to fully write file on cache, e=%d\n", e)); + } + + if (!success) { + entry->discard_on_delete = GF_TRUE; + } + } + entry->write_session = NULL; + return e; +} + +GF_Err gf_cache_open_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess) +{ + CHECK_ENTRY; + if (!sess) + return GF_BAD_PARAM; + entry->write_session = sess; + if (!entry->continue_file) { + gf_assert( ! entry->writeFilePtr); + + entry->written_in_cache = 0; + } + entry->flags &= ~CORRUPTED; + + if (entry->memory_stored) { + gf_mx_p(entry->cache_blob.mx); + + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Opening cache file %s for write (%s)...\n", entry->cache_filename, entry->url)); + if (!entry->mem_allocated || (entry->mem_allocated < entry->contentLength)) { + if (entry->contentLength) entry->mem_allocated = entry->contentLength; + else if (!entry->mem_allocated) entry->mem_allocated = 81920; + entry->mem_storage = (u8*)gf_realloc(entry->mem_storage, sizeof(char)* (entry->mem_allocated + 2) ); + } + entry->cache_blob.data = entry->mem_storage; + entry->cache_blob.size = entry->contentLength; + char *burl = gf_blob_register(&entry->cache_blob); + if (burl) { + strcpy(entry->cache_filename, burl); + gf_free(burl); + } + gf_mx_v(entry->cache_blob.mx); + + if (!entry->mem_allocated) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE Failed to create memory storage for file %s\n", entry->url)); + return GF_OUT_OF_MEM; + } + return GF_OK; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Opening cache file %s for write (%s)...\n", entry->cache_filename, entry->url)); + entry->writeFilePtr = gf_fopen(entry->cache_filename, entry->continue_file ? "a+b" : "wb"); + if (!entry->writeFilePtr) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, + ("CACHE Error while opening cache file %s for writting.\n", entry->cache_filename)); + entry->write_session = NULL; + return GF_IO_ERR; + } + + if (entry->continue_file ) + gf_fseek(entry->writeFilePtr, 0, SEEK_END); + + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s.lock", entry->cfg_filename); + GF_LockStatus lock_type = cache_entry_lock(szLOCK); + GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename); + char szUTC20; + sprintf(szUTC, LLU, gf_net_get_utc()/1000); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CREATED, szUTC); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE, "true"); + gf_cfg_del(cfg); + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + + return GF_OK; +} + +GF_Err gf_cache_write_to_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, const char * data, const u32 size, GF_Mutex *mx) { + u32 read; + CHECK_ENTRY; + + if (!data || (!entry->writeFilePtr && !entry->mem_storage) || sess != entry->write_session) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("Incorrect parameter : data=%p, writeFilePtr=%p mem_storage=%p at "__FILE__"\n", data, entry->writeFilePtr, entry->mem_storage)); + return GF_BAD_PARAM; + } + + if (entry->memory_stored) { + if (!entry->cache_blob.mx) + entry->cache_blob.mx = mx; + gf_assert(mx); + gf_mx_p(entry->cache_blob.mx); + if (entry->written_in_cache + size > entry->mem_allocated) { + u32 new_size = MAX(entry->mem_allocated*2, entry->written_in_cache + size); + entry->mem_storage = (u8*)gf_realloc(entry->mem_storage, (new_size+2)); + entry->mem_allocated = new_size; + entry->cache_blob.data = entry->mem_storage; + entry->cache_blob.size = entry->contentLength; + char *burl = gf_blob_register(&entry->cache_blob); + if (burl) { + strcpy(entry->cache_filename, burl); + gf_free(burl); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Reallocating memory cache to %d bytes\n", new_size)); + } + memcpy(entry->mem_storage + entry->written_in_cache, data, size); + entry->written_in_cache += size; + memset(entry->mem_storage + entry->written_in_cache, 0, 2); + entry->cache_blob.size = entry->written_in_cache; + gf_mx_v(entry->cache_blob.mx); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Storing %d bytes to memory\n", size)); + return GF_OK; + } + + read = (u32) gf_fwrite(data, size, entry->writeFilePtr); + if (read > 0) + entry->written_in_cache+= read; + if (read != size) { + /* Something bad happened */ + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, + ("CACHE Error while writting %d bytes of data to cache : has written only %d bytes.", size, read)); + gf_cache_close_write_cache(entry, sess, GF_FALSE); + return GF_IO_ERR; + } + if (gf_fflush(entry->writeFilePtr)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, + ("CACHE Error while flushing data bytes to cache file : %s.", entry->cache_filename)); + gf_cache_close_write_cache(entry, sess, GF_FALSE); + return GF_IO_ERR; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Writing %d bytes to cache\n", size)); + return GF_OK; +} + +void gf_cache_delete_entry( const DownloadedCacheEntry entry ) +{ + if ( !entry ) return; + + if (entry->writeFilePtr) { + /** Cache should have been close before, abornormal situation */ + GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("CACHE Cache for %s has not been closed properly\n", entry->url)); + gf_fclose(entry->writeFilePtr); + } + gf_blob_unregister(&entry->cache_blob); + if (entry->external_blob) { + gf_blob_unregister(entry->external_blob); + } + + if (!entry->mem_storage ) { + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s.lock", entry->cfg_filename); + GF_LockStatus lock_type = cache_entry_lock(szLOCK); + + GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename); + if (cfg) { + char szID20; + sprintf(szID, "%u", gf_sys_get_process_id()); + gf_cfg_set_key(cfg, CACHE_SECTION_USERS, szID, NULL); + + Bool still_in_use = cache_cleanup_processes(cfg); + if (still_in_use && entry->discard_on_delete) { + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_TODELETE, "true"); + entry->discard_on_delete = GF_FALSE; + } else if (!still_in_use) { + if (gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_TODELETE)) { + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_TODELETE, NULL); + entry->discard_on_delete = GF_TRUE; + } + } + gf_cfg_del(cfg); + } + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + + if (entry->discard_on_delete) { + gf_file_delete(entry->cache_filename); + gf_file_delete(entry->cfg_filename); + } + } + if (entry->cfg_filename) gf_free(entry->cfg_filename); + if (entry->cache_filename) gf_free(entry->cache_filename); + if (entry->serverETag) gf_free(entry->serverETag); + if (entry->diskETag) gf_free(entry->diskETag); + if (entry->serverLastModified) gf_free(entry->serverLastModified); + if (entry->diskLastModified) gf_free(entry->diskLastModified); + if (entry->hash) gf_free(entry->hash); + if (entry->url) gf_free(entry->url); + if (entry->mimeType) gf_free(entry->mimeType); + if (entry->mem_storage && entry->mem_allocated) gf_free(entry->mem_storage); + if (entry->forced_headers) gf_free(entry->forced_headers); + + + if (entry->sessions) { + gf_assert( gf_list_count(entry->sessions) == 0); + gf_list_del(entry->sessions); + } + gf_free (entry); +} + + +s32 gf_cache_remove_session_from_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess) { + u32 i; + s32 count; + if (!entry || !sess || !entry->sessions) + return -1; + count = gf_list_count(entry->sessions); + for (i = 0 ; i < (u32)count; i++) { + GF_DownloadSession *s = (GF_DownloadSession*)gf_list_get(entry->sessions, i); + if (s == sess) { + gf_list_rem(entry->sessions, i); + count --; + break; + } + } + if (entry->write_session == sess) { + /* OK, this is not optimal to close it since we are in a mutex, + * but we don't want to risk to have another session opening + * a not fully closed cache entry */ + if (entry->writeFilePtr) { + if (gf_fclose(entry->writeFilePtr)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("CACHE gf_cache_remove_session_from_cache_entry:%d, Failed to properly close cache file '%s' of url '%s', cache may be corrupted !\n", __LINE__, entry->cache_filename, entry->url)); + } + } + entry->writeFilePtr = NULL; + entry->write_session = NULL; + } + return count; +} + +void gf_cache_set_max_age(const DownloadedCacheEntry entry, u32 max_age, Bool must_revalidate) +{ + char szVal100; + if (!entry || entry->mem_storage) return; + + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s.lock", entry->cfg_filename); + GF_LockStatus lock_type = cache_entry_lock(szLOCK); + GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename); + + entry->must_revalidate = must_revalidate; + if (max_age) + entry->max_age = gf_net_get_utc()/1000 + max_age; + sprintf(szVal, LLU, entry->max_age); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MAXAGE, szVal); + gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MUST_REVALIDATE, must_revalidate ? "true" : "false"); + gf_cfg_del(cfg); + + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); +} + +static u32 gf_cache_get_sessions_count_for_cache_entry(const DownloadedCacheEntry entry) +{ + if (!entry) + return 0; + return gf_list_count(entry->sessions); +} + + +s32 gf_cache_add_session_to_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess) { + u32 i; + s32 count; + if (!entry || !sess || !entry->sessions) + return -1; + count = gf_list_count(entry->sessions); + for (i = 0 ; i < (u32)count; i++) { + GF_DownloadSession *s = (GF_DownloadSession*)gf_list_get(entry->sessions, i); + if (s == sess) { + return count; + } + } + gf_list_add(entry->sessions, sess); + return count + 1; +} + +void gf_cache_set_end_range(DownloadedCacheEntry entry, u64 range_end) +{ + if (entry) { + entry->previousRangeContentLength = entry->contentLength; + entry->range_end = range_end; + entry->continue_file = GF_TRUE; + } +} + +Bool gf_cache_is_in_progress(const DownloadedCacheEntry entry) +{ + if (!entry) return GF_FALSE; + if (entry->writeFilePtr) return GF_TRUE; + if (entry->mem_storage && entry->written_in_cache && entry->contentLength && (entry->written_in_cache<entry->contentLength)) + return GF_TRUE; + return GF_FALSE; +} + +Bool gf_cache_set_mime(const DownloadedCacheEntry entry, const char *mime) +{ + if (!entry || !entry->memory_stored) return GF_FALSE; + if (entry->mimeType) gf_free(entry->mimeType); + entry->mimeType = gf_strdup(mime); + return GF_TRUE; +} + +Bool gf_cache_set_range(const DownloadedCacheEntry entry, u64 size, u64 start_range, u64 end_range) +{ + if (!entry || !entry->memory_stored) return GF_FALSE; + entry->range_start = start_range; + entry->range_end = end_range; + entry->contentLength = (u32) size; + entry->continue_file = GF_FALSE; + return GF_TRUE; +} + +Bool gf_cache_set_headers(const DownloadedCacheEntry entry, const char *headers) +{ + if (!entry || !entry->memory_stored) return GF_FALSE; + if (entry->forced_headers) gf_free(entry->forced_headers); + entry->forced_headers = headers ? gf_strdup(headers) : NULL; + return GF_TRUE; +} + +char *gf_cache_get_forced_headers(const DownloadedCacheEntry entry) +{ + if (!entry) return NULL; + return entry->forced_headers; +} +void gf_cache_set_downtime(const DownloadedCacheEntry entry, u32 download_time_ms) +{ + if (entry) entry->downtime = download_time_ms; +} +u32 gf_cache_get_downtime(const DownloadedCacheEntry entry) +{ + if (!entry) return 0; + return entry->downtime; +} +u32 gf_cache_is_done(const DownloadedCacheEntry entry) +{ + if (!entry) return 1; + u32 res = 1; + if (entry->external_blob) { + gf_mx_p(entry->external_blob->mx); + res = (entry->external_blob->flags & GF_BLOB_IN_TRANSFER) ? 0 : 1; + if (res && (entry->external_blob->flags & GF_BLOB_CORRUPTED)) + res = 2; + gf_mx_v(entry->external_blob->mx); + } else if (entry->mem_storage) { + res = (entry->cache_blob.flags & GF_BLOB_IN_TRANSFER) ? 0 : 1; + } else { + if (!(entry->flags & IN_PROGRESS)) return 1; + char szLOCKGF_MAX_PATH; + sprintf(szLOCK, "%s.lock", entry->cfg_filename); + GF_LockStatus lock_type = cache_entry_lock(szLOCK); + GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename); + const char *opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE); + res = opt ? 0 : 1; + if (res) { + entry->flags &= ~IN_PROGRESS; + opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE); + entry->contentLength = atoi(opt); + } + gf_cfg_del(cfg); + if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK); + } + return res; +} +const u8 *gf_cache_get_content(const DownloadedCacheEntry entry, u32 *size, u32 *max_valid_size, Bool *was_modified) +{ + if (!entry) return NULL; + *was_modified = GF_FALSE; + if (entry->external_blob) { + u8 *data; + GF_Err e = gf_blob_get_ex(entry->external_blob, &data, size, NULL); + if (e) return NULL; + *max_valid_size = *size; + if (entry->external_blob->range_valid) { + gf_mx_p(entry->external_blob->mx); + entry->external_blob->range_valid(entry->external_blob, GF_FALSE, 0, max_valid_size); + gf_mx_v(entry->external_blob->mx); + } + if (entry->external_blob->last_modification_time != entry->cache_blob.last_modification_time) { + *was_modified = GF_TRUE; + entry->cache_blob.last_modification_time = entry->external_blob->last_modification_time; + } + return data; + } + *max_valid_size = *size = entry->cache_blob.size; + return entry->cache_blob.data; +} +void gf_cache_release_content(const DownloadedCacheEntry entry) +{ + if (!entry) return; + if (!entry->external_blob) return; + gf_blob_release_ex(entry->external_blob); +} +Bool gf_cache_is_deleted(const DownloadedCacheEntry entry) +{ + if (!entry) return GF_TRUE; + if (entry->flags & DELETED) return GF_TRUE; + return GF_FALSE; +} + +Bool gf_cache_set_content(const DownloadedCacheEntry entry, GF_Blob *blob, Bool copy, GF_Mutex *mx) +{ + if (!entry || !entry->memory_stored) return GF_FALSE; + + if (!blob) { + entry->flags |= DELETED; + if (entry->external_blob) { + gf_blob_unregister(entry->external_blob); + entry->external_blob = NULL; + } + return GF_TRUE; + } + if (blob->mx) + gf_mx_p(blob->mx); + + if (!copy) { + if (entry->mem_allocated) gf_free(entry->mem_storage); + entry->mem_storage = (u8 *) blob->data; + if (!entry->written_in_cache) { + char *burl = gf_blob_register(blob); + if (burl) { + strcpy(entry->cache_filename, burl); + gf_free(burl); + } + } + + entry->written_in_cache = blob->size; + entry->mem_allocated = 0; + entry->cache_blob.data = NULL; + entry->cache_blob.size = 0; + entry->external_blob = blob; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Storing %d bytes to memory from external module\n", blob->size)); + } else { + if (!entry->cache_blob.mx) + entry->cache_blob.mx = mx; + gf_mx_p(entry->cache_blob.mx); + + if (blob->size >= entry->mem_allocated) { + u32 new_size; + new_size = MAX(entry->mem_allocated*2, blob->size+1); + entry->mem_storage = (u8*)gf_realloc(entry->mem_allocated ? entry->mem_storage : NULL, (new_size+2)); + entry->mem_allocated = new_size; + entry->cache_blob.data = entry->mem_storage; + entry->cache_blob.size = entry->contentLength; + if (!entry->written_in_cache) { + char *burl = gf_blob_register(&entry->cache_blob); + if (burl) { + strcpy(entry->cache_filename, burl); + gf_free(burl); + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Reallocating memory cache to %d bytes\n", new_size)); + } + memcpy(entry->mem_storage, blob->data, blob->size); + entry->mem_storageblob->size = 0; + entry->cache_blob.size = entry->written_in_cache = blob->size; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("CACHE Storing %d bytes to cache memory\n", blob->size)); + + gf_mx_v(entry->cache_blob.mx); + + entry->cache_blob.flags = blob->flags; + } + if (blob->flags & GF_BLOB_IN_TRANSFER) + entry->contentLength = 0; + else + entry->contentLength = blob->size; + + if (blob->mx) + gf_mx_v(blob->mx); + + return GF_TRUE; +} + +static Bool gf_cache_entry_can_reuse(const DownloadedCacheEntry entry, Bool skip_revalidate) +{ + if (!entry) return GF_FALSE; + //if corrupted or deleted, we cannot reuse + if (entry->flags & (CORRUPTED|DELETED) ) return GF_FALSE; + if (!entry->max_age) return GF_FALSE; + s64 expires = entry->max_age; + expires -= gf_net_get_utc()/1000; + //max age not reached, allow reuse + if (expires>0) + return GF_TRUE; + if (entry->must_revalidate) + return GF_FALSE; + //we consider that if not expired and no must-revalidate, we can go on + return GF_TRUE; +} + +Bool gf_cache_entry_is_shared(const DownloadedCacheEntry entry) +{ + return entry ? entry->other_in_use : GF_FALSE; +} + +Bool gf_cache_is_mem(const DownloadedCacheEntry entry) +{ + return entry->mem_storage ? GF_TRUE : GF_FALSE; +} +FILE *gf_cache_open_read(const DownloadedCacheEntry entry) +{ + if (!entry) return NULL; + if (entry->mem_storage) return NULL; + return gf_fopen(entry->cache_filename, "r"); +} + + +/*! + * Finds an existing entry in the cache for a given URL +\param sess The session configured with the URL +\return NULL if none found, the DownloadedCacheEntry otherwise + */ +static DownloadedCacheEntry gf_cache_find_entry_by_url(GF_DownloadSession * sess) +{ + u32 i, count; + gf_assert( sess && sess->dm && sess->dm->cache_entries ); + gf_mx_p( sess->dm->cache_mx ); + count = gf_list_count(sess->dm->cache_entries); + for (i = 0 ; i < count; i++) { + const char * url; + DownloadedCacheEntry e = (DownloadedCacheEntry)gf_list_get(sess->dm->cache_entries, i); + gf_assert(e); + url = gf_cache_get_url(e); + gf_assert( url ); + + if (!strncmp(url, "http://gmcast/", 14)) { + char *sep_1 = strchr(url+14, '/'); + char *sep_2 = strchr(sess->orig_url+14, '/'); + if (!sep_1 || !sep_2 || strcmp(sep_1, sep_2)) + continue; + } else if (strcmp(url, sess->orig_url)) continue; + + if (! sess->is_range_continuation) { + if (sess->range_start != gf_cache_get_start_range(e)) continue; + if (sess->range_end != gf_cache_get_end_range(e)) continue; + } + /*OK that's ours*/ + gf_mx_v( sess->dm->cache_mx ); + return e; + } + gf_mx_v( sess->dm->cache_mx ); + return NULL; +} + + +/** + * Removes a cache entry from cache and performs a cleanup if possible. + * If the cache entry is marked for deletion and has no sessions associated with it, it will be + * removed (so some modules using a streaming like cache will still work). + */ +void gf_cache_remove_entry_from_session(GF_DownloadSession * sess) +{ + if (!sess || !sess->cache_entry) return; + + gf_cache_remove_session_from_cache_entry(sess->cache_entry, sess); + if (sess->dm + /*JLF - not sure what the rationale of this test is, and it prevents cleanup of cache entry + which then results to crash when restarting the session (entry->writeFilePtr is not set back to NULL)*/ + && sess->cache_entry->discard_on_delete + + && (0 == gf_cache_get_sessions_count_for_cache_entry(sess->cache_entry))) + { + u32 i, count; + gf_mx_p( sess->dm->cache_mx ); + count = gf_list_count( sess->dm->cache_entries ); + for (i = 0; i < count; i++) { + DownloadedCacheEntry ex = (DownloadedCacheEntry)gf_list_get(sess->dm->cache_entries, i); + if (ex == sess->cache_entry) { + gf_list_rem(sess->dm->cache_entries, i); + gf_cache_delete_entry( sess->cache_entry ); + sess->cache_entry = NULL; + break; + } + } + gf_mx_v( sess->dm->cache_mx ); + } + +} + + +void gf_dm_configure_cache(GF_DownloadSession *sess) +{ + DownloadedCacheEntry entry; + gf_cache_remove_entry_from_session(sess); + + //session is not cached and we don't cache the first URL + if ((sess->flags & GF_NETIO_SESSION_NOT_CACHED) && !(sess->flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE)) { + sess->reused_cache_entry = GF_FALSE; + if (sess->cache_entry) + gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE); + + sess->cache_entry = NULL; + return; + } + + Bool found = GF_FALSE; + u32 i, count; + entry = gf_cache_find_entry_by_url(sess); + if (!entry) { + if (sess->local_cache_only) { + sess->cache_entry = NULL; + SET_LAST_ERR(GF_URL_ERROR) + return; + } + /* We found the existing session */ + if (sess->cache_entry) { + Bool delete_cache = GF_TRUE; + + if (sess->flags & GF_NETIO_SESSION_KEEP_CACHE) { + delete_cache = GF_FALSE; + } + if (gf_cache_entry_persistent(sess->cache_entry)) + delete_cache = GF_FALSE; + + /*! indicate we can destroy file upon destruction, except if disabled at session level*/ + if (delete_cache) + gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry); + + if (!gf_cache_entry_persistent(sess->cache_entry) + && !gf_cache_get_sessions_count_for_cache_entry(sess->cache_entry) + ) { + gf_mx_p( sess->dm->cache_mx ); + /* No session attached anymore... we can delete it */ + gf_list_del_item(sess->dm->cache_entries, sess->cache_entry); + gf_mx_v( sess->dm->cache_mx ); + gf_cache_delete_entry(sess->cache_entry); + } + sess->cache_entry = NULL; + } + Bool use_mem = (sess->flags & (GF_NETIO_SESSION_MEMORY_CACHE | GF_NETIO_SESSION_NO_STORE)) ? GF_TRUE : GF_FALSE; + entry = gf_cache_create_entry(sess->dm->cache_directory, sess->orig_url, sess->range_start, sess->range_end, use_mem, sess->dm->cache_mx); + if (!entry) { + SET_LAST_ERR(GF_OUT_OF_MEM) + return; + } + gf_mx_p( sess->dm->cache_mx ); + gf_list_add(sess->dm->cache_entries, entry); + gf_mx_v( sess->dm->cache_mx ); + sess->is_range_continuation = GF_FALSE; + } + sess->cache_entry = entry; + sess->reused_cache_entry = gf_cache_is_in_progress(entry); + gf_mx_p(sess->dm->cache_mx); + count = gf_list_count(sess->dm->all_sessions); + for (i=0; i<count; i++) { + GF_DownloadSession *a_sess = (GF_DownloadSession*)gf_list_get(sess->dm->all_sessions, i); + gf_assert(a_sess); + if (a_sess==sess) continue; + if (a_sess->cache_entry==entry) { + found = GF_TRUE; + break; + } + } + gf_mx_v(sess->dm->cache_mx); + if (!found) { + sess->reused_cache_entry = GF_FALSE; + if (sess->cache_entry) + gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE); + } + gf_cache_add_session_to_cache_entry(sess->cache_entry, sess); + if (sess->needs_range) + gf_cache_set_range(sess->cache_entry, 0, sess->range_start, sess->range_end); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CACHE Cache for %s setup %s\n", sess->orig_url, gf_cache_get_cache_filename(sess->cache_entry))); + + Bool no_revalidate = GF_FALSE; + if (sess->allow_direct_reuse) + no_revalidate = GF_TRUE; + + if (sess->cache_entry) { + if (sess->flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE) { + sess->flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE; + gf_cache_entry_set_persistent(sess->cache_entry); + } + //this one is only used for init segments in dash/hls + if ((sess->flags & GF_NETIO_SESSION_MEMORY_CACHE) && (sess->flags & GF_NETIO_SESSION_KEEP_CACHE) ) { + gf_cache_entry_set_persistent(sess->cache_entry); + } + + if (gf_cache_entry_can_reuse(sess->cache_entry, sess->dm->allow_offline_cache)) { + no_revalidate = GF_TRUE; + } + //we cannot reuse and others are using the cache file - for now we don't have any other possibility + //than disabling the cache to avoid overwriting the resource + else if (gf_cache_entry_is_shared(sess->cache_entry)) { + gf_cache_remove_entry_from_session(sess); + sess->cache_entry = NULL; + sess->reused_cache_entry = GF_FALSE; + return; + } + } + + if (no_revalidate) + sess->cached_file = gf_cache_open_read(sess->cache_entry); + + if (sess->cached_file) { + sess->connect_time = 0; + sess->status = GF_NETIO_CONNECTED; + const char *mime = gf_cache_get_mime_type(sess->cache_entry); + if (mime) + gf_dm_sess_set_header_ex(sess, "Content-Type", mime, GF_TRUE); + u32 size = gf_cache_get_content_length(sess->cache_entry); + if (size) { + char szHdr20; + sprintf(szHdr, "%u", size); + gf_dm_sess_set_header_ex(sess, "Content-Length", szHdr, GF_TRUE); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s using existing cache entry\n", sess->log_name)); + gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); + } +} + +void gf_dm_delete_cached_file_entry(const GF_DownloadManager * dm, const char * url) +{ + GF_Err e; + u32 count, i; + char * realURL; + GF_URL_Info info; + if (!url || !dm) + return; + gf_mx_p( dm->cache_mx ); + gf_dm_url_info_init(&info); + e = gf_dm_get_url_info(url, &info, NULL); + if (e != GF_OK) { + gf_mx_v( dm->cache_mx ); + gf_dm_url_info_del(&info); + return; + } + realURL = gf_strdup(info.canonicalRepresentation); + gf_dm_url_info_del(&info); + gf_assert( realURL ); + count = gf_list_count(dm->cache_entries); + for (i = 0 ; i < count; i++) { + const char * e_url; + DownloadedCacheEntry cache_ent = (DownloadedCacheEntry)gf_list_get(dm->cache_entries, i); + gf_assert(cache_ent); + e_url = gf_cache_get_url(cache_ent); + gf_assert( e_url ); + if (!strcmp(e_url, realURL)) { + /* We found the existing session */ + gf_cache_entry_set_delete_files_when_deleted(cache_ent); + if (0 == gf_cache_get_sessions_count_for_cache_entry( cache_ent )) { + /* No session attached anymore... we can delete it */ + gf_list_rem(dm->cache_entries, i); + gf_cache_delete_entry(cache_ent); + } + /* If deleted or not, we don't search further */ + gf_mx_v( dm->cache_mx ); + gf_free(realURL); + return; + } + } + /* If we are here it means we did not found this URL in cache */ + gf_mx_v( dm->cache_mx ); + gf_free(realURL); + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CACHE Cannot find URL %s, cache file won't be deleted.\n", url)); +} + +GF_EXPORT +void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url, Bool force) +{ + if (sess && sess->dm && url) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("CACHE Removing cache entry for %s\n", url)); + gf_dm_delete_cached_file_entry(sess->dm, url); + if (sess->local_cache_only && sess->dm->local_cache_url_provider_cbk) + sess->dm->local_cache_url_provider_cbk(sess->dm->lc_udta, (char *) url, GF_TRUE); + } +} + +GF_EXPORT +GF_Err gf_dm_set_localcache_provider(GF_DownloadManager *dm, Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool is_cache_destroy), void *lc_udta) +{ + if (!dm) + return GF_BAD_PARAM; + dm->local_cache_url_provider_cbk = local_cache_url_provider_cbk; + dm->lc_udta = lc_udta; + return GF_OK; + +} + +GF_EXPORT +DownloadedCacheEntry gf_dm_add_cache_entry(GF_DownloadManager *dm, const char *szURL, GF_Blob *blob, u64 start_range, u64 end_range, const char *mime, Bool clone_memory, u32 download_time_ms) +{ + u32 i, count; + DownloadedCacheEntry the_entry = NULL; + + gf_mx_p(dm->cache_mx ); + if (blob) + GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("CACHE Pushing %s to cache "LLU" bytes (done %s)\n", szURL, blob->size, (blob->flags & GF_BLOB_IN_TRANSFER) ? "no" : "yes")); + count = gf_list_count(dm->cache_entries); + for (i = 0 ; i < count; i++) { + const char * url; + DownloadedCacheEntry e = (DownloadedCacheEntry)gf_list_get(dm->cache_entries, i); + gf_assert(e); + url = gf_cache_get_url(e); + gf_assert( url ); + if (strcmp(url, szURL)) continue; + + if (end_range) { + if (start_range != gf_cache_get_start_range(e)) continue; + if (end_range != gf_cache_get_end_range(e)) continue; + } + /*OK that's ours*/ + the_entry = e; + break; + } + if (!the_entry) { + the_entry = gf_cache_create_entry("", szURL, 0, 0, GF_TRUE, dm->cache_mx); + if (!the_entry) { + gf_mx_v(dm->cache_mx ); + return NULL; + } + gf_list_add(dm->cache_entries, the_entry); + } + + gf_cache_set_mime(the_entry, mime); + if (blob && ! (blob->flags & GF_BLOB_IN_TRANSFER)) + gf_cache_set_range(the_entry, blob->size, start_range, end_range); + + gf_cache_set_content(the_entry, blob, clone_memory ? GF_TRUE : GF_FALSE, dm->cache_mx); + gf_cache_set_downtime(the_entry, download_time_ms); + gf_mx_v(dm->cache_mx ); + return the_entry; +} + +void gf_dm_sess_reload_cached_headers(GF_DownloadSession *sess) +{ + char *hdrs; + + if (!sess || !sess->local_cache_only) return; + + hdrs = gf_cache_get_forced_headers(sess->cache_entry); + + gf_dm_sess_clear_headers(sess); + while (hdrs) { + char *sep2, *sepL = strstr(hdrs, "\r\n"); + if (sepL) sepL0 = 0; + sep2 = strchr(hdrs, ':'); + if (sep2) { + GF_HTTPHeader *hdr; + GF_SAFEALLOC(hdr, GF_HTTPHeader); + if (!hdr) break; + sep20=0; + hdr->name = gf_strdup(hdrs); + sep20=':'; + sep2++; + while (sep20==' ') sep2++; + hdr->value = gf_strdup(sep2); + gf_list_add(sess->headers, hdr); + } + if (!sepL) break; + sepL0 = '\r'; + hdrs = sepL + 2; + } +} +GF_EXPORT +GF_Err gf_dm_force_headers(GF_DownloadManager *dm, const DownloadedCacheEntry entry, const char *headers) +{ + u32 i, count; + Bool res; + if (!entry) + return GF_BAD_PARAM; + gf_mx_p(dm->cache_mx); + res = gf_cache_set_headers(entry, headers); + count = gf_list_count(dm->all_sessions); + for (i=0; i<count; i++) { + GF_DownloadSession *sess = gf_list_get(dm->all_sessions, i); + if (sess->cache_entry != entry) continue; + gf_dm_sess_reload_cached_headers(sess); + } + + gf_mx_v(dm->cache_mx); + if (res) return GF_OK; + return GF_BAD_PARAM; +} + +#endif //GPAC_DISABLE_NETWORK
View file
gpac-26.02.0.tar.gz/src/utils/downloader_curl.c
Added
@@ -0,0 +1,509 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "downloader.h" + +#if !defined(GPAC_DISABLE_NETWORK) && defined(GPAC_HAS_CURL) + +#include <curl/curl.h> +//we need multi API +#if CURL_AT_LEAST_VERSION(7,80,0) +#ifndef CURLPIPE_MULTIPLEX +#define CURLPIPE_MULTIPLEX 0 +#endif +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +#pragma comment(lib, "libcurl") +# endif +#endif + + +#ifndef GPAC_DISABLE_LOG +static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp) +{ + (void)clientp; + (void)handle; + + switch(type) { + case CURLINFO_TEXT: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Info: %s", data)); + return 0; + case CURLINFO_HEADER_OUT: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Send header %s\n", data)); + return 0; + case CURLINFO_DATA_OUT: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Send data\n")); + return 0; + case CURLINFO_SSL_DATA_OUT: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Send SSL data\n")); + return 0; + case CURLINFO_HEADER_IN: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Recv header %s\n", data)); + return 0; + case CURLINFO_DATA_IN: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Recv data %d byes\n", size)); + return 0; + case CURLINFO_SSL_DATA_IN: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL Recv SSL data\n")); + return 0; + default: + return 0; + } + return 0; +} +#endif + +static int curl_on_connect(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port) +{ + GF_DownloadSession *sess = clientp; + if (sess->status == GF_NETIO_SETUP) { + sess->status = GF_NETIO_CONNECTED; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL connected\n")); + gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); + gf_dm_configure_cache(sess); + gf_mx_p(sess->dm->curl_mx); + curl_multi_remove_handle(sess->dm->curl_multi, sess->curl_hnd); + sess->curl_hnd_registered = GF_FALSE; + gf_mx_v(sess->dm->curl_mx); + } + return CURL_PREREQFUNC_OK; +} + +static int curl_on_socket_close(void *clientp, curl_socket_t item) +{ + GF_DownloadManager *dm = clientp; + dm->curl_has_close = GF_TRUE; + return 0; +} + +static size_t curl_on_header(char *buffer, size_t size, size_t nitems, void *clientp) +{ + GF_DownloadSession *sess = clientp; + if ((buffer0=='\r') || (buffer0=='\n')) + return nitems * size; + + if (sess->status==GF_NETIO_WAIT_FOR_REPLY) { + //push headers + char *sep = memchr(buffer, ':', nitems*size); + if (sep) { + sep0 = 0; + GF_HTTPHeader *hdrp; + GF_SAFEALLOC(hdrp, GF_HTTPHeader); + if (hdrp) { + u32 len = (u32) strlen(buffer) + 2; + hdrp->name = gf_strdup(buffer); + hdrp->value = gf_malloc(nitems * size - len + 1); + memcpy(hdrp->value, buffer+len, nitems * size - len); + hdrp->valuenitems * size - len = 0; + gf_list_add(sess->headers, hdrp); + } + } + if (!sess->reply_time) + sess->reply_time = (u32) (gf_sys_clock_high_res() - sess->request_start_time); + } + return nitems * size; +} + +static size_t curl_on_data(char *ptr, size_t size, size_t nmemb, void *clientp) +{ + GF_DownloadSession *sess = clientp; + u32 len = (u32) (size*nmemb); + if (sess->local_buf_len + len > sess->local_buf_alloc) { + sess->local_buf_alloc = sess->local_buf_len + len; + sess->local_buf = gf_realloc(sess->local_buf, sizeof(u8) * sess->local_buf_alloc); + if (!sess->local_buf) return 0; + } + memcpy(sess->local_buf + sess->local_buf_len, ptr, len); + sess->local_buf_len += (u32) len; + sess->last_fetch_time = gf_sys_clock_high_res(); + //dirty hack: libcurl may use blocking calls for some protocols (ftp) + //if we know the total size and have all bytes, force a timeout and tach it in curl_flush + if (sess->total_size && (sess->bytes_done + sess->local_buf_len == sess->total_size)) { + curl_easy_setopt(sess->curl_hnd, CURLOPT_TIMEOUT_MS, 1); + } + //GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL sess %s received %d bytes (%d/%d total)\n", sess->orig_url, len, sess->bytes_done, sess->total_size)); + return len; +} +#endif //GPAC_DISABLE_LOG + +GF_Err curl_setup_session(GF_DownloadSession* sess) +{ + CURLcode res; + sess->curl_hnd = curl_easy_init(); + res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PRIVATE, sess); + res = curl_easy_setopt(sess->curl_hnd, CURLOPT_URL, sess->orig_url); + +#ifndef GPAC_DISABLE_LOG + if (gf_opts_get_bool("curl", "trace") && gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_DEBUG)) { + curl_easy_setopt(sess->curl_hnd, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(sess->curl_hnd, CURLOPT_DEBUGFUNCTION, curl_trace); + curl_easy_setopt(sess->curl_hnd, CURLOPT_DEBUGDATA, sess); + } +#endif + + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_BUFFERSIZE, sess->dm->read_buf_size); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_FOLLOWLOCATION, 0L); +#if (CURLPIPE_MULTIPLEX > 0) + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PIPEWAIT, 1L); +#endif + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PREREQFUNCTION, curl_on_connect); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PREREQDATA, sess); + //set close callback at DM level since a same socket can be used by several sessions (H2/H3) + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_CLOSESOCKETFUNCTION, curl_on_socket_close); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_CLOSESOCKETDATA, sess->dm); + + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HEADERFUNCTION, curl_on_header); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HEADERDATA, sess); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_WRITEFUNCTION, curl_on_data); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_WRITEDATA, sess); + //DO NOT SET CURLOPT_TIMEOUT as we have no clue how long the session will be running + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_CONNECTTIMEOUT_MS, sess->conn_timeout); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_ACCEPTTIMEOUT_MS, sess->conn_timeout); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_NOSIGNAL, 1); + if (gf_opts_get_bool("core", "broken-cert")) { + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_SSL_VERIFYPEER, 0); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_SSL_VERIFYHOST, 0); + } + else { + + const char* ca_bundle = gf_opts_get_key("core", "ca-bundle"); + + if (ca_bundle && gf_file_exists(ca_bundle)) { + curl_easy_setopt(sess->curl_hnd, CURLOPT_CAINFO, ca_bundle); + curl_easy_setopt(sess->curl_hnd, CURLOPT_CAPATH, NULL); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("CURL Using CA bundle at %s\n", ca_bundle)); + } +#if CURL_AT_LEAST_VERSION(7,84,0) + else { + const char* curl_bundle = NULL; + curl_easy_getinfo(sess->curl_hnd, CURLINFO_CAINFO, &curl_bundle); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("CURL libcurl CA bundle location: %s\n", curl_bundle)); + + if (!curl_bundle || !gf_file_exists(curl_bundle)) { + + const char* ca_bundle_default = gf_opts_get_key("core", "ca-bundle-default"); + + if (ca_bundle_default) { + curl_easy_setopt(sess->curl_hnd, CURLOPT_CAINFO, ca_bundle_default); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("CURL using default CA bundle at %s\n", ca_bundle_default)); + } + + } + } +#endif + } + + //set HTTP version + if (!res) { + curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); + res = CURLE_UNSUPPORTED_PROTOCOL; + //try H3 + if ((ver->features & CURL_VERSION_HTTP3) && sess->dm && (sess->dm->h3_mode!=H3_MODE_NO)) { +#ifdef CURL_HTTP_VERSION_3ONLY + if (sess->dm->h3_mode==H3_MODE_ONLY) + res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3ONLY); + else +#endif + res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3); + } + //try H2 + if (res && (ver->features & CURL_VERSION_HTTP2) && !gf_opts_get_bool("core", "no-h2")) { + res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0); + } + //fallback 1.1 - we could use CURL_HTTP_VERSION_NONE + if (res==CURLE_UNSUPPORTED_PROTOCOL) + res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); + } + const char *proxy = (sess->flags & GF_NETIO_SESSION_NO_PROXY) ? NULL : gf_opts_get_key("core", "proxy"); + if (proxy) { + sess->proxy_enabled = 1; + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PROXY, proxy); + } + + sess->curl_not_http = GF_FALSE; + if (!sess->do_requests) { + sess->curl_not_http = GF_TRUE; + sess->do_requests = http_do_requests; + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_FOLLOWLOCATION, 1L); + if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_TCP_KEEPALIVE, 1L); + if (sess->log_name) { + gf_free(sess->log_name); + sess->log_name = NULL; + } + sess->log_name = gf_strdup("CURL"); + } + + //set user-defined options + const struct curl_easyoption *opt = curl_easy_option_next(NULL); + while (opt) { + u64 llvalue; + s32 value; + const char *val = gf_opts_get_key("curl", opt->name); + if (!val) { + opt = curl_easy_option_next(opt); + continue; + } + switch (opt->type) { + case CURLOT_LONG: + case CURLOT_VALUES: + sscanf(val, "%d", &value); + if (!res) res = curl_easy_setopt(sess->curl_hnd, opt->id, value); + break; + case CURLOT_OFF_T: + sscanf(val, LLU, &llvalue); + if (!res) res = curl_easy_setopt(sess->curl_hnd, opt->id, llvalue); + break; + case CURLOT_STRING: + if (!res) res = curl_easy_setopt(sess->curl_hnd, opt->id, val); + break; + case CURLOT_OBJECT: + case CURLOT_SLIST: + case CURLOT_CBPTR: + case CURLOT_BLOB: + case CURLOT_FUNCTION: + break; + } + opt = curl_easy_option_next(opt); + } + + if (res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("CURL Failed to setup session: %s\n", curl_easy_strerror(res))); + return GF_SERVICE_ERROR; + } + + sess->status = GF_NETIO_SETUP; + sess->last_error = GF_OK; + //reset number of retry and start time + sess->num_retry = SESSION_RETRY_COUNT; + sess->start_time = 0; + sess->reply_time = 0; + sess->local_buf_len = 0; + return GF_OK; +} + + +void curl_flush(GF_DownloadSession *sess) +{ + gf_mx_p(sess->dm->curl_mx); + if (!sess->curl_hnd_registered) { + curl_multi_add_handle(sess->dm->curl_multi, sess->curl_hnd); + sess->curl_hnd_registered = GF_TRUE; + } + int still_running = 0; + CURLMcode mres = curl_multi_perform(sess->dm->curl_multi, &still_running); + gf_mx_v(sess->dm->curl_mx); + if (mres != CURLM_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("CURL curl_multi_perform() failed: %s\n", curl_multi_strerror(mres) )); + } + + //if no close detected and session has total size set nothing to do + //otherwise check for EOS detection, either error (close) or OK (chunk transfer) + if (!sess->dm->curl_has_close && sess->total_size) return; + //process close messages + sess->dm->curl_has_close = GF_FALSE; + CURLMsg *m=NULL; + int nb_msgs=0; + while (1) { + m = curl_multi_info_read(sess->dm->curl_multi, &nb_msgs); + if (!m) break; + if (m->msg != CURLMSG_DONE) continue; + GF_DownloadSession *a_sess = NULL; + CURLcode res = curl_easy_getinfo(m->easy_handle, CURLINFO_PRIVATE, &a_sess); + if (!a_sess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("CURL failed to retrieve private pointer: %s\n", curl_easy_strerror(res) )); + continue; + } + if (a_sess->curl_hnd_registered) { + curl_multi_remove_handle(sess->dm->curl_multi, a_sess->curl_hnd); + a_sess->curl_hnd_registered = GF_FALSE; + } + if (m->data.result==CURLE_COULDNT_CONNECT) + a_sess->curl_closed = GF_IP_CONNECTION_FAILURE; + else if (m->data.result==CURLE_COULDNT_RESOLVE_HOST) + a_sess->curl_closed = GF_IP_ADDRESS_NOT_FOUND; + else if (m->data.result==CURLE_PARTIAL_FILE) + a_sess->curl_closed = GF_IP_CONNECTION_CLOSED; + else if (m->data.result==CURLE_REMOTE_FILE_NOT_FOUND) + a_sess->curl_closed = GF_URL_ERROR; + else if (m->data.result==CURLE_OK) + a_sess->curl_closed = GF_EOS; + else if (m->data.result==CURLE_REMOTE_ACCESS_DENIED) { + if (a_sess->num_retry) { + a_sess->curl_fake_hcode = 401; + a_sess->num_retry--; + a_sess->last_error = GF_OK; + a_sess->status = GF_NETIO_WAIT_FOR_REPLY; + a_sess->curl_closed = GF_OK; + } else { + a_sess->curl_closed = GF_AUTHENTICATION_FAILURE; + } + } + else if (m->data.result==CURLE_OPERATION_TIMEDOUT) { + //timeout was forced + if (a_sess->total_size && (a_sess->total_size == a_sess->local_buf_len + a_sess->bytes_done)) + a_sess->curl_closed = GF_EOS; + else + a_sess->curl_closed = GF_IP_CONNECTION_FAILURE; + } + else + a_sess->curl_closed = GF_IP_NETWORK_FAILURE; + } +} + +void curl_destroy(GF_DownloadSession *sess) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("CURL closing session\n")); + if (sess->curl_hnd_registered) { + gf_mx_p(sess->dm->curl_mx); + curl_multi_remove_handle(sess->dm->curl_multi, sess->curl_hnd); + gf_mx_v(sess->dm->curl_mx); + } + curl_easy_cleanup(sess->curl_hnd); + sess->curl_hnd = NULL; + sess->curl_hnd_registered = GF_FALSE; + if (sess->curl_hdrs) curl_slist_free_all(sess->curl_hdrs); + sess->curl_hdrs = NULL; + sess->curl_closed = GF_OK; +} + +Bool curl_can_handle_url(const char *url) +{ + curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); + char *prot_sep = strstr(url, "://"); + if (!prot_sep) return GF_FALSE; + u32 i=0, len = (u32) (prot_sep - url); + while (ver && ver->protocolsi) { + if (!strnicmp(ver->protocolsi, url, len)) + return GF_TRUE; + i++; + } + return GF_FALSE; +} + +GF_Err curl_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read) +{ + if (!sess->curl_hnd) return GF_BAD_PARAM; + + if (!sess->local_buf_len) + curl_flush(sess); + + if (!sess->local_buf_len) { + *out_read = 0; + if (sess->curl_closed==GF_EOS) { + //for chunk transfer + if (!sess->total_size) + sess->total_size = sess->bytes_done; + //reutrn OK so that data_received is processed + return GF_OK; + } + else if (sess->curl_closed) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_IP_CONNECTION_CLOSED); + return sess->curl_closed; + } + return GF_IP_NETWORK_EMPTY; + } + if (sess->curl_not_http && !sess->total_size) { + long cl=0; + curl_easy_getinfo(sess->curl_hnd, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); + if (cl>=0) + sess->total_size = cl; + } + u32 nb_copy = MIN(data_size, sess->local_buf_len); + memcpy(data, sess->local_buf, nb_copy); + sess->local_buf_len -= nb_copy; + memmove(sess->local_buf, sess->local_buf + nb_copy, sess->local_buf_len); + *out_read = nb_copy; + return GF_OK; +} + +void curl_connect(GF_DownloadSession*sess) +{ + curl_flush(sess); + if (sess->curl_closed) { + if (sess->cache_entry && sess->cached_file) return; + + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_IP_CONNECTION_FAILURE) + gf_dm_sess_notify_state(sess, sess->status, GF_IP_CONNECTION_FAILURE); + } +} + +GF_Err curl_process_reply(GF_DownloadSession *sess, u32 *ContentLength) +{ + if (!sess->reply_time) { + curl_flush(sess); + if (!sess->reply_time) return GF_IP_NETWORK_EMPTY; + } + if (sess->curl_closed<0) { + gf_dm_disconnect(sess, HTTP_RESET_CONN); + sess->num_retry--; + if (sess->num_retry) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("%s Connection closed by server when processing %s - retrying\n", sess->log_name, sess->remote_path)); + sess->status = GF_NETIO_SETUP; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s Connection closed by server when processing %s - aborting\n", sess->log_name, sess->remote_path)); + SET_LAST_ERR(GF_IP_CONNECTION_CLOSED) + sess->status = GF_NETIO_STATE_ERROR; + } + return GF_IP_CONNECTION_CLOSED; + } + + if (sess->curl_not_http) { + //if not HTTP we have no guarantee that the return code for the URL is known when receiving server first bytes + //eg FTP may need extra round trips before we get OK/ not found. + //We assume by default OK and will throw an error in curl_flush + // curl_fake_hcode can be set in curl_flush to force reprocessing the reply (eg on authentication failure) + sess->rsp_code = sess->curl_fake_hcode ? sess->curl_fake_hcode : 200; + sess->curl_fake_hcode = 0; + long cl=0; + curl_easy_getinfo(sess->curl_hnd, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); + if (cl>=0) + *ContentLength = cl; + } else { + long rsp=505; + curl_easy_getinfo(sess->curl_hnd, CURLINFO_RESPONSE_CODE, &rsp); + sess->rsp_code = rsp; + if (gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_INFO)) { + long http_version; + const char *version="unknown"; + curl_easy_getinfo(sess->curl_hnd, CURLINFO_HTTP_VERSION, &http_version); + switch (http_version) { + case CURL_HTTP_VERSION_1_0: version = "1.0"; break; + case CURL_HTTP_VERSION_1_1: version = "1.1"; break; + case CURL_HTTP_VERSION_2_0: version = "2"; break; + case CURL_HTTP_VERSION_3: version = "3"; break; + } + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("CURL Using HTTP %s\n", version)); + } + } + sess->start_time = gf_sys_clock_high_res(); + sess->start_time_utc = gf_net_get_utc(); + return GF_OK; +} + +#endif //GPAC_DISABLE_NETWORK && HAS_CURL +
View file
gpac-26.02.0.tar.gz/src/utils/downloader_emscripten.c
Added
@@ -0,0 +1,926 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/tools.h> +#if defined(GPAC_CONFIG_EMSCRIPTEN) +#include <gpac/download.h> +#include <gpac/list.h> +#include <gpac/thread.h> +#include <gpac/filters.h> + +struct __gf_download_manager +{ + GF_FilterSession *fsess; + GF_List *all_sessions; + GF_Mutex *mx; +}; + +GF_DownloadManager *gf_dm_new(GF_DownloadFilterSession *fsess) +{ + GF_DownloadManager *tmp; + GF_SAFEALLOC(tmp, GF_DownloadManager); + if (!tmp) return NULL; + tmp->fsess = fsess; + tmp->all_sessions = gf_list_new(); + tmp->mx = gf_mx_new("Downloader"); + return tmp; +} +void gf_dm_del(GF_DownloadManager *dm) +{ + if (!dm) return; + gf_list_del(dm->all_sessions); + gf_mx_del(dm->mx); + gf_free(dm); +} + +u32 gf_dm_get_global_rate(GF_DownloadManager *dm) +{ + return 0; +} +void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec) +{ +} + +typedef struct __cache_blob +{ + GF_Blob blob; + char *url, *cache_name; + char *mime; + u64 start_range, end_range; + Bool persistent; +} GF_CacheBlob; + +struct __gf_download_session +{ + GF_DownloadManager *dm; + gf_dm_user_io user_io; + void *usr_cbk; + char method100; + + char *req_url; + char *rsp_url; + /*request headers*/ + char **req_hdrs; + u32 nb_req_hdrs; + u32 req_body_size; + char *req_body; + u64 start_range, end_range; + + /*response headers*/ + char **rsp_hdrs; + u32 nb_rsp_hdrs; + char *mime; //pointer to content-type header or blob + + u32 state, bps; + u64 start_time, start_time_utc; + u64 total_size, bytes_done; + GF_Err last_error; + GF_NetIOStatus netio_status; + u32 dl_flags; + Bool reuse_cache; + u32 ftask; + GF_List *cached_blobs; + + GF_CacheBlob *active_cache; + + Bool destroy, in_callback; + +}; + +EM_JS(int, dm_fetch_cancel, (int sess), { + let fetcher = libgpac._get_fetcher(sess); + if (!fetcher) return -1; + fetcher._controller.abort(); + libgpac._del_fetcher(fetcher); + return 0; +}); + +static void clear_headers(char ***_hdrs, u32 *nb_hdrs) +{ + char **hdrs = *_hdrs; + if (hdrs) { + u32 i=0; + for (i=0; i<*nb_hdrs; i++) { + if (hdrsi) gf_free(hdrsi); + } + gf_free(hdrs); + *_hdrs = NULL; + *nb_hdrs = 0; + } +} + +void cache_blob_del(GF_CacheBlob *b) +{ + if (!b) return; + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Removing cache entry of %s (%s - range "LLU"-"LLU")\n", b->url, b->cache_name, b->start_range, b->end_range)); + gf_blob_unregister(&b->blob); + if (b->blob.data) gf_free(b->blob.data); + if (b->url) gf_free(b->url); + if (b->cache_name) gf_free(b->cache_name); + if (b->mime) gf_free(b->mime); + gf_free(b); +} + +static void gf_dm_sess_reset(GF_DownloadSession *sess) +{ + if (!sess) return; + clear_headers(&sess->req_hdrs, &sess->nb_req_hdrs); + clear_headers(&sess->rsp_hdrs, &sess->nb_rsp_hdrs); + if (sess->req_body) gf_free(sess->req_body); + sess->req_body = NULL; + + if (sess->state==1) { + dm_fetch_cancel(EM_CAST_PTR sess); + } + if (sess->req_url) gf_free(sess->req_url); + sess->req_url = NULL; + if (sess->rsp_url) gf_free(sess->rsp_url); + sess->rsp_url = NULL; + sess->mime = NULL; + sess->state = 0; + sess->total_size = 0; + sess->bytes_done = 0; + sess->start_time = 0; + sess->start_range = 0; + sess->end_range = 0; + sess->bps = 0; + sess->last_error = GF_OK; + sess->reuse_cache = GF_FALSE; + sess->netio_status = GF_NETIO_SETUP; +} + + +EM_JS(int, fs_fetch_setup, (), { + if ((typeof libgpac.gpac_fetch == 'boolean') && !libgpac.gpac_fetch) return 2; + if (typeof libgpac._fetcher_set_header == 'function') return 1; + try { + libgpac._fetchers = ; + libgpac._get_fetcher = (sess) => { + for (let i=0; i<libgpac._fetchers.length; i++) { + if (libgpac._fetchersi.sess==sess) return libgpac._fetchersi; + } + return null; + }; + libgpac._del_fetcher = (fetcher) => { + let i = libgpac._fetchers.indexOf(fetcher); + if (i>=0) libgpac._fetchers.splice(i, 1); + }; + libgpac._fetcher_set_header = cwrap('gf_dm_sess_push_header', null, 'number', 'string', 'string'); + libgpac._fetcher_set_reply = cwrap('gf_dm_sess_async_reply', null, 'number', 'number', 'string'); + } catch (e) { + return 0; + } + return 1; +}); + +static GF_Err gf_dm_setup_cache(GF_DownloadSession *sess) +{ + if (sess->active_cache) { + if (!sess->cached_blobs) { + cache_blob_del(sess->active_cache); + } + else if (sess->active_cache->blob.flags & GF_BLOB_CORRUPTED) { + gf_list_del_item(sess->cached_blobs, sess->active_cache); + cache_blob_del(sess->active_cache); + } + //remove if not persistent + else if (!sess->active_cache->persistent) { + gf_list_del_item(sess->cached_blobs, sess->active_cache); + cache_blob_del(sess->active_cache); + } + sess->active_cache = NULL; + } + //look in session for cache + if (sess->cached_blobs) { + u32 i, count = gf_list_count(sess->cached_blobs); + for (i=0; i<count; i++) { + GF_CacheBlob *cb = gf_list_get(sess->cached_blobs, i); + if (strcmp(cb->url, sess->req_url)) continue; + if (cb->start_range != sess->start_range) continue; + if (cb->end_range != sess->end_range) continue; + if (!cb->blob.size) continue; + sess->active_cache = cb; + sess->reuse_cache = GF_TRUE; + sess->total_size = cb->blob.size; + sess->bytes_done = 0; + sess->netio_status = GF_NETIO_DATA_EXCHANGE; + sess->last_error = GF_OK; + sess->mime = cb->mime; + sess->dl_flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE; + return GF_OK; + } + } + if (!(sess->dl_flags & GF_NETIO_SESSION_MEMORY_CACHE)) { + return GF_OK; + } + + GF_SAFEALLOC(sess->active_cache, GF_CacheBlob); + if (!sess->active_cache) return GF_OUT_OF_MEM; + if (!sess->cached_blobs) sess->cached_blobs = gf_list_new(); + + gf_list_add(sess->cached_blobs, sess->active_cache); + sess->active_cache->cache_name = gf_blob_register(&sess->active_cache->blob); + + sess->active_cache->blob.flags = GF_BLOB_IN_TRANSFER; + sess->active_cache->url = gf_strdup(sess->req_url); + sess->active_cache->start_range = sess->start_range; + sess->active_cache->end_range = sess->end_range; + sess->reuse_cache = GF_FALSE; + //only files marked with GF_NETIO_SESSION_KEEP_FIRST_CACHE are kept in our local cache + //all other cache settings are ignored, we rely on browser cache for this + if (sess->dl_flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE) { + sess->active_cache->persistent = GF_TRUE; + sess->dl_flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE; + } else { + sess->active_cache->persistent = GF_FALSE; + } + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Setting up memory cache for %s - persistent %d - name %s\n", sess->req_url, sess->active_cache->persistent, sess->active_cache->cache_name)); + return GF_OK; +} + +GF_EXPORT +GF_DownloadSession *gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 dl_flags, + gf_dm_user_io user_io, + void *usr_cbk, + GF_Err *e) +{ + GF_NETIO_Parameter par; + GF_DownloadSession *sess; + + int fetch_init = fs_fetch_setup(); + if (fetch_init==2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Downloader fecth() disabled\n")); + if (e) *e = GF_NOT_SUPPORTED; + return NULL; + } else if (fetch_init==0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Downloader Failed to initialize fetch\n")); + if (e) *e = GF_SERVICE_ERROR; + return NULL; + } + + GF_SAFEALLOC(sess, GF_DownloadSession); + if (!sess) { + if (e) *e = GF_OUT_OF_MEM; + return NULL; + } + sess->dm = dm; + sess->req_url = gf_strdup(url); + if (!sess->req_url) { + gf_free(sess); + if (e) *e = GF_OUT_OF_MEM; + return NULL; + } + sess->user_io = user_io; + sess->usr_cbk = usr_cbk; + + strcpy(sess->method, "GET"); + + sess->dl_flags = dl_flags; + + if (dl_flags & GF_NETIO_SESSION_MEMORY_CACHE) { + sess->cached_blobs = gf_list_new(); + } + + if (user_io) { + memset(&par, 0, sizeof(GF_NETIO_Parameter)); + par.msg_type = GF_NETIO_GET_METHOD; + sess->user_io(sess->usr_cbk, &par); + if (par.name) strcpy(sess->method, par.name); + + sess->nb_req_hdrs=0; + while (1) { + par.msg_type = GF_NETIO_GET_HEADER; + par.value = NULL; + sess->user_io(sess->usr_cbk, &par); + if (!par.value) break; + + sess->req_hdrs = gf_realloc(sess->req_hdrs, sizeof(char*) * (sess->nb_req_hdrs+2)); + if (!sess->req_hdrs) { + if (e) *e = GF_OUT_OF_MEM; + gf_free(sess); + return NULL; + } + sess->req_hdrssess->nb_req_hdrs = gf_strdup(par.name); + sess->req_hdrssess->nb_req_hdrs+1 = gf_strdup(par.value); + sess->nb_req_hdrs+=2; + } + + memset(&par, 0, sizeof(GF_NETIO_Parameter)); + par.msg_type = GF_NETIO_GET_CONTENT; + sess->user_io(sess->usr_cbk, &par); + + sess->req_body_size = par.size; + if (par.size && par.data) { + sess->req_body = gf_malloc(sizeof(u8)*par.size); + if (!sess->req_body) { + gf_dm_sess_reset(sess); + gf_list_del(sess->cached_blobs); + if (e) *e = GF_OUT_OF_MEM; + gf_free(sess); + return NULL; + } + memcpy(sess->req_body, par.data, par.size); + } + } + if (e) *e = GF_OK; + *e = gf_dm_setup_cache(sess); + if (*e) { + gf_dm_sess_del(sess); + return NULL; + } + if (dm) { + gf_mx_p(dm->mx); + gf_list_add(dm->all_sessions, sess); + gf_mx_v(dm->mx); + } + + sess->user_io = user_io; + sess->usr_cbk = usr_cbk; + + return sess; +} + +void gf_dm_sess_abort(GF_DownloadSession * sess) +{ + if (sess->state==1) { + dm_fetch_cancel(EM_CAST_PTR sess); + sess->state = 0; + sess->netio_status = GF_NETIO_DISCONNECTED; + sess->last_error = GF_IP_CONNECTION_CLOSED; + if (sess->active_cache && !sess->reuse_cache && (sess->active_cache->blob.flags & GF_BLOB_IN_TRANSFER)) { + sess->active_cache->blob.flags = GF_BLOB_CORRUPTED; + } + } + clear_headers(&sess->rsp_hdrs, &sess->nb_rsp_hdrs); + if (sess->ftask) sess->ftask = 2; +} + +void gf_dm_sess_del(GF_DownloadSession *sess) +{ + if (sess->in_callback) { + sess->destroy = GF_TRUE; + return; + } + gf_dm_sess_reset(sess); + while (gf_list_count(sess->cached_blobs)) { + cache_blob_del( gf_list_pop_back(sess->cached_blobs) ); + } + gf_list_del(sess->cached_blobs); + if (sess->dm) { + gf_mx_p(sess->dm->mx); + gf_list_del_item(sess->dm->all_sessions, sess); + gf_mx_v(sess->dm->mx); + } + gf_free(sess); +} + +const char *gf_dm_sess_get_cache_name(GF_DownloadSession *sess) +{ + if (!sess || !sess->active_cache) return NULL; + return sess->active_cache->cache_name; +} + +void gf_dm_sess_force_memory_mode(GF_DownloadSession *sess, u32 force_keep) +{ + if (!sess) return; + sess->dl_flags |= GF_NETIO_SESSION_MEMORY_CACHE; + if (force_keep==1) { + //cache is handled by the browser if any, or forced by fetch headers provied by users + //sess->dl_flags |= GF_NETIO_SESSION_KEEP_CACHE; + } else if (force_keep==2) { + sess->dl_flags |= GF_NETIO_SESSION_KEEP_FIRST_CACHE; + } +} + +GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse) +{ + gf_dm_sess_reset(sess); + sess->req_url = gf_strdup(url); + if (!sess->req_url) { + sess->state=2; + return GF_OUT_OF_MEM; + } + return gf_dm_setup_cache(sess); +} + +GF_Err gf_dm_sess_set_range(GF_DownloadSession *sess, u64 start_range, u64 end_range, Bool discontinue_cache) +{ + if (!sess) return GF_BAD_PARAM; + if (sess->state==1) return GF_BAD_PARAM; + sess->start_range = start_range; + sess->end_range = end_range; + Bool cache_reset = GF_FALSE; + if ((sess->state!=2) || discontinue_cache) { + cache_reset = GF_TRUE; + } + else if (sess->active_cache) { + if (sess->active_cache->start_range + sess->active_cache->end_range + 1 == start_range) { + sess->active_cache->end_range = end_range; + sess->active_cache->blob.flags |= GF_BLOB_IN_TRANSFER; + } else { + cache_reset = GF_TRUE; + } + } + //set range called before starting session, just adjust cache + if (sess->active_cache && cache_reset && !sess->state) { + sess->active_cache->start_range = sess->start_range; + sess->active_cache->end_range = sess->end_range; + cache_reset=GF_FALSE; + } + if (cache_reset) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Cache discontinuity, reset\n", sess->req_url, sess->active_cache->persistent, sess->active_cache->cache_name)); + GF_Err e = gf_dm_setup_cache(sess); + if (e) return e; + sess->last_error = GF_OK; + } + sess->netio_status = GF_NETIO_CONNECTED; + sess->state = 0; + return GF_OK; +} + + + +EM_JS(int, dm_fetch_init, (int sess, int _url, int _method, int _headers, int nb_headers, int req_body, int req_body_size), { + let url = _url ? UTF8ToString(_url) : null; + let ret = 0; // libgpac.OK + + let fetcher = libgpac._get_fetcher(sess); + if (!fetcher) { + fetcher = {}; + fetcher.sess = sess; + libgpac._fetchers.push(fetcher); + } + + fetcher._controller = new AbortController(); + let options = { + signal: fetcher._controller.signal, + method: _method ? UTF8ToString(_method) : "GET", + mode: libgpac.gpac_fetch_mode || 'cors', + }; + let mime_type = 'application/octet-stream'; + options.headers = {}; + if (_headers) { + for (let i=0; i<nb_headers; i+=2) { + let _s = getValue(_headers+i*4, 'i32'); + let h_name = _s ? UTF8ToString(_s).toLowerCase() : ""; + _s = getValue(_headers+(i+1)*4, 'i32'); + let h_val = _s ? UTF8ToString(_s) : ""; + if (h_name.length && h_val.length) { + options.headersh_name = h_val; + if (h_name=='content-type') + mime_type = h_val; + } + } + } + if (typeof libgpac.gpac_extra_headers == 'object') { + for (const hdr in libgpac.gpac_extra_headers) { + options.headershdr = objecthdr; + } + } + + if (req_body) { + let body_ab = new Uint8Array(HEAPU8.buffer, req_body, req_body_size); + options.body = new Blob(body_ab, {type: mime_type} ); + } + fetcher._state = 0; + fetch(url, options).then( (response) => { + if (response.ok) { + fetcher._state = 1; + fetcher._bytes = 0; + fetcher._reader = response.body.getReader(); + + let final_url = null; + if (response.redirected) final_url = response.url; + libgpac._fetcher_set_reply(fetcher.sess, response.status, final_url); + + response.headers.forEach((value, key) => { + libgpac._fetcher_set_header(fetcher.sess, key, value); + }); + libgpac._fetcher_set_header(fetcher.sess, 0, 0); + } else { + libgpac._fetcher_set_reply(fetcher.sess, response.status, null); + fetcher._state = 3; + } + }) + .catch( (e) => { + if (e.name !== 'AbortError') printErr('fetcher exception ' + e); + ret = -14; // libgpac.REMOTE_SERVICE_ERROR + libgpac._del_fetcher(fetcher); + }); + return ret; +}); + + +EM_JS(int, dm_fetch_data, (int sess, int buffer, int buffer_size, int read_size), { + let f = libgpac._get_fetcher(sess); + if (!f) return -1; + if (f._state==0) return -44; + if (f._state==3) return -12; + if (f._state==4) return 1; + if (f._state == 1) { + f._state = 0; + f._reader.read().then( (block) => { + if (block.done) { + f._state = 4; + } else { + f._ab = block.value; + f._bytes += f._ab.byteLength; + f._block_pos = 0; + f._state = 2; + } + }) + .catch( e => { + f._state = 0; + }); + if (f._state==0) return -44; + } + + let avail = f._ab.byteLength - f._block_pos; + if (avail<buffer_size) buffer_size = avail; + + //copy array buffer + let src = f._ab.subarray(f._block_pos, f._block_pos+buffer_size); + let dst = new Uint8Array(HEAPU8.buffer, buffer, buffer_size); + dst.set(src); + + setValue(read_size, buffer_size, 'i32'); + f._block_pos += buffer_size; + if (f._ab.byteLength == f._block_pos) { + f._ab = null; + f._state = 1; + } + return 0; +}); + + +GF_Err gf_dm_sess_fetch_data(GF_DownloadSession *sess, char *buffer, u32 buffer_size, u32 *read_size) +{ + if (sess->reuse_cache) { + u32 remain = sess->active_cache->blob.size - sess->bytes_done; + if (!remain) { + *read_size = 0; + sess->last_error = GF_EOS; + sess->netio_status = GF_NETIO_DATA_TRANSFERED; + return GF_EOS; + } + if (remain < buffer_size) buffer_size = remain; + memcpy(buffer, sess->active_cache->blob.data + sess->bytes_done, buffer_size); + *read_size = buffer_size; + sess->bytes_done += buffer_size; + return GF_OK; + } + if (sess->state == 0) { + if (sess->start_range || sess->end_range) { + char szHdr100; + if (sess->end_range) + sprintf(szHdr, "bytes="LLU"-"LLU, sess->start_range, sess->end_range); + else + sprintf(szHdr, "bytes="LLU"-", sess->start_range); + + sess->req_hdrs = gf_realloc(sess->req_hdrs, sizeof(char*) * (sess->nb_req_hdrs+2)); + if (!sess->req_hdrs) { + sess->state = 2; + return sess->last_error = GF_OUT_OF_MEM; + } + sess->req_hdrssess->nb_req_hdrs = gf_strdup("Range"); + sess->req_hdrssess->nb_req_hdrs+1 = gf_strdup(szHdr); + sess->nb_req_hdrs+=2; + } + + sess->start_time_utc = gf_net_get_utc(); + sess->start_time = gf_sys_clock(); + sess->netio_status = GF_NETIO_CONNECTED; + + if (sess->start_range || sess->end_range) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Starting download of %s (%s - range "LLU"-"LLU")\n", sess->req_url, sess->active_cache ? sess->active_cache->cache_name : "no cache", sess->start_range, sess->end_range)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Starting download of %s (%s)\n", sess->req_url, sess->active_cache ? sess->active_cache->cache_name : "no cache")); + } + GF_Err fetch_e = dm_fetch_init( + EM_CAST_PTR sess, + EM_CAST_PTR sess->req_url, + EM_CAST_PTR sess->method, + EM_CAST_PTR sess->req_hdrs, + sess->nb_req_hdrs, + EM_CAST_PTR sess->req_body, + sess->req_body_size + ); + + if (fetch_e) { + gf_dm_sess_reset(sess); + sess->state = 2; + sess->netio_status = GF_NETIO_DISCONNECTED; + return sess->last_error = fetch_e; + } + sess->state = 1; + sess->netio_status = GF_NETIO_WAIT_FOR_REPLY; + } + if (sess->state==2) return sess->last_error; + + *read_size = 0; + GF_Err fetch_err = dm_fetch_data( + EM_CAST_PTR sess, + EM_CAST_PTR buffer, + buffer_size, + EM_CAST_PTR read_size + ); + + if (*read_size) { + u64 ellapsed = gf_sys_clock() - sess->start_time; + sess->bytes_done += *read_size; + if (ellapsed) + sess->bps = (u32) (sess->bytes_done * 1000 / ellapsed); + sess->netio_status = GF_NETIO_DATA_EXCHANGE; + + if (sess->active_cache) { + u32 nb_bytes= *read_size; + sess->active_cache->blob.data = gf_realloc(sess->active_cache->blob.data, sess->active_cache->blob.size + nb_bytes); + if (!sess->active_cache->blob.data) { + sess->active_cache->blob.flags |= GF_BLOB_CORRUPTED; + } else { + memcpy(sess->active_cache->blob.data + sess->active_cache->blob.size, buffer, nb_bytes); + sess->active_cache->blob.size += nb_bytes; + } + } + if (sess->total_size == sess->bytes_done) fetch_err = GF_EOS; + + } else if (!fetch_err) { + return GF_IP_NETWORK_EMPTY; + } else if (fetch_err!=GF_IP_NETWORK_EMPTY) { + sess->state=2; + sess->last_error = fetch_err; + sess->netio_status = GF_NETIO_STATE_ERROR; + if (sess->active_cache) + sess->active_cache->blob.flags = GF_BLOB_CORRUPTED; + } + + if (fetch_err==GF_EOS) { + sess->total_size = sess->bytes_done; + sess->state=2; + sess->last_error = GF_EOS; + sess->netio_status = GF_NETIO_DATA_TRANSFERED; + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Downloader Done downloading %s - rate %d bps\n", sess->req_url, sess->bps)); + if (sess->active_cache) + sess->active_cache->blob.flags &= ~GF_BLOB_IN_TRANSFER; + } + return fetch_err; +} + + +GF_Err gf_dm_sess_get_stats(GF_DownloadSession * sess, const char **server, const char **path, u64 *total_size, u64 *bytes_done, u32 *bytes_per_sec, GF_NetIOStatus *net_status) +{ + if (!sess) return GF_OUT_OF_MEM; + if (total_size) *total_size = sess->total_size; + if (bytes_done) *bytes_done = sess->bytes_done; + if (bytes_per_sec) *bytes_per_sec = sess->bps; + if (net_status) *net_status = sess->netio_status; + return sess->last_error; +} + +const char *gf_dm_sess_mime_type(GF_DownloadSession * sess) +{ + return sess->mime; +} + +GF_Err gf_dm_sess_enum_headers(GF_DownloadSession *sess, u32 *idx, const char **hdr_name, const char **hdr_val) +{ + if( !sess || !idx || !hdr_name || !hdr_val) + return GF_BAD_PARAM; + if (*idx >= sess->nb_rsp_hdrs/2) return GF_EOS; + + u32 i = *idx * 2; + (*hdr_name) = sess->rsp_hdrsi; + (*hdr_val) = sess->rsp_hdrsi+1; + (*idx) = (*idx) + 1; + return GF_OK; +} + +void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url, Bool force) +{ + //unused we automatically purge cache when setting up session + if (!sess || !force) return; + if (sess->state==1) return; + if (sess->active_cache) { + gf_list_del_item(sess->cached_blobs, sess->active_cache); + cache_blob_del(sess->active_cache); + ((GF_DownloadSession *)sess)->active_cache = NULL; + } +} + +GF_Err gf_dm_sess_process_headers(GF_DownloadSession *sess) +{ + //no control of this for fetch + return GF_OK; +} + +GF_EXPORT +void gf_dm_sess_push_header(GF_DownloadSession *sess, const char *hdr, const char *value) +{ + if (!sess) return; + if (!hdr || !value) return; + + if (!stricmp(hdr, "Content-Length")) { + sscanf(value, LLU, &sess->total_size); + } + + sess->rsp_hdrs = gf_realloc(sess->rsp_hdrs, sizeof(char*) * (sess->nb_rsp_hdrs+2)); + if (!sess->rsp_hdrs) return; + + sess->rsp_hdrssess->nb_rsp_hdrs = gf_strdup(hdr); + sess->rsp_hdrssess->nb_rsp_hdrs+1 = gf_strdup(value); + if (!stricmp(hdr, "Content-Type")) { + sess->mime = sess->rsp_hdrssess->nb_rsp_hdrs+1; + //keep a copy of mime + if (sess->active_cache && sess->active_cache->persistent) + sess->active_cache->mime = gf_strdup(sess->mime); + } + sess->nb_rsp_hdrs+=2; +} + +GF_EXPORT +void gf_dm_sess_async_reply(GF_DownloadSession *sess, int rsp_code, const char *final_url) +{ + if (!sess) return; + GF_Err e; + switch (rsp_code) { + case 404: e = GF_URL_ERROR; break; + case 400: e = GF_SERVICE_ERROR; break; + case 401: e = GF_AUTHENTICATION_FAILURE; break; + case 405: e = GF_AUTHENTICATION_FAILURE; break; + case 416: e = GF_SERVICE_ERROR; break; //range error + default: + if (rsp_code>=500) e = GF_REMOTE_SERVICE_ERROR; + else if (rsp_code>=400) e = GF_SERVICE_ERROR; + else e = GF_OK; + break; + } + sess->last_error = e; + + if (final_url) { + if (sess->rsp_url) gf_free(sess->rsp_url); + sess->rsp_url = final_url ? gf_strdup(final_url) : NULL; + } +} + +GF_Err gf_dm_sess_fetch_once(GF_DownloadSession *sess) +{ + GF_NETIO_Parameter par; + char buffer10000; + u32 read = 0; + GF_Err e = gf_dm_sess_fetch_data(sess, buffer, 10000, &read); + if (e==GF_IP_NETWORK_EMPTY) { + + } else if ((e==GF_OK) || (e==GF_EOS)) { + if (sess->user_io && read) { + memset(&par, 0, sizeof(GF_NETIO_Parameter)); + par.sess = sess; + par.error = sess->last_error; + if (e) { + par.msg_type = GF_NETIO_DATA_TRANSFERED; + } else { + par.msg_type = GF_NETIO_DATA_EXCHANGE; + par.data = buffer; + par.size = read; + } + sess->in_callback = GF_FALSE; + sess->user_io(sess->usr_cbk, &par); + sess->in_callback = GF_FALSE; + } + } else { + if (sess->user_io) { + memset(&par, 0, sizeof(GF_NETIO_Parameter)); + par.sess = sess; + par.msg_type = sess->netio_status; + par.error = sess->last_error; + sess->in_callback = GF_TRUE; + sess->user_io(sess->usr_cbk, &par); + sess->in_callback = GF_FALSE; + } + } + return e; +} + +Bool gf_dm_session_task(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms) +{ + Bool ret = GF_TRUE; + GF_DownloadSession *sess = callback; + if (!sess) return GF_FALSE; + gf_assert(sess->ftask); + if (sess->ftask==2) { + sess->ftask = 0; + return GF_FALSE; + } + + GF_Err e = gf_dm_sess_fetch_once(sess); + if (e==GF_EOS) ret = GF_FALSE; + else if (e && (e!=GF_IP_NETWORK_EMPTY)) ret = GF_FALSE; + + if (ret) { + *reschedule_ms = 1; + return GF_TRUE; + } + sess->ftask = 0; + if (sess->destroy) { + sess->destroy = GF_FALSE; + gf_dm_sess_del(sess); + } + return GF_FALSE; +} + +GF_Err gf_dm_sess_process(GF_DownloadSession *sess) +{ + if (sess->netio_status == GF_NETIO_DATA_TRANSFERED) { + return GF_OK; + } + + if ((sess->dl_flags & GF_NETIO_SESSION_NOT_THREADED) || !sess->dm->fsess) { + return gf_dm_sess_fetch_once(sess); + } + //ignore if task already allocated + if (sess->ftask) return GF_OK; + sess->ftask = GF_TRUE; + return gf_fs_post_user_task(sess->dm->fsess, gf_dm_session_task, sess, "download"); +} + + +const char *gf_dm_sess_get_resource_name(GF_DownloadSession *sess) +{ + if (!sess) return NULL; + return sess->rsp_url ? sess->rsp_url : sess->req_url; +} + +const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name) +{ + u32 i; + if (!sess || !name) return NULL; + for (i=0; i<sess->nb_rsp_hdrs; i+=2) { + char *h = sess->rsp_hdrsi; + if (!stricmp(h, name)) return sess->rsp_hdrsi+1; + } + return NULL; +} + +u64 gf_dm_sess_get_utc_start(GF_DownloadSession * sess) +{ + if (!sess) return 0; + return sess->start_time_utc; +} + +//only used for dash playing local files +u32 gf_dm_get_data_rate(GF_DownloadManager *dm) +{ + return gf_opts_get_int("core", "maxrate"); +} + +void gf_dm_sess_detach_async(GF_DownloadSession *sess) +{ + if (!sess) return; + sess->dl_flags &= ~GF_NETIO_SESSION_NOT_THREADED; + gf_dm_sess_force_memory_mode(sess, 1); +} +void gf_dm_sess_set_netcap_id(GF_DownloadSession *sess, const char *netcap_id) +{ + +} + +void gf_dm_sess_set_max_rate(GF_DownloadSession *sess, u32 max_rate) +{ +} + +u32 gf_dm_sess_get_max_rate(GF_DownloadSession *sess) +{ + return 0; +} +Bool gf_dm_sess_is_regulated(GF_DownloadSession *sess) +{ + return GF_FALSE; +} +u32 gf_dm_sess_get_resource_size(GF_DownloadSession * sess) +{ + return sess ? sess->total_size : 0; +} + + +#endif // GPAC_CONFIG_EMSCRIPTEN
View file
gpac-26.02.0.tar.gz/src/utils/downloader_hmux.c
Added
@@ -0,0 +1,281 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "downloader.h" + +#ifndef GPAC_DISABLE_NETWORK + +#ifdef GPAC_HTTPMUX + +//detach session from parent h2 / h3 session - the session mutex SHALL be grabbed before calling this +void hmux_detach_session(GF_HMUX_Session *hmux_sess, GF_DownloadSession *sess) +{ + if (!hmux_sess || !sess) return; + gf_assert(sess->hmux_sess == hmux_sess); + gf_assert(sess->mx); + + gf_list_del_item(hmux_sess->sessions, sess); + if (!gf_list_count(hmux_sess->sessions)) { + if (hmux_sess->close) + hmux_sess->close(hmux_sess); + +#ifdef GPAC_HAS_SSL + if (sess->ssl) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("Downloader shut down SSL context\n")); + SSL_shutdown(sess->ssl); + SSL_free(sess->ssl); + sess->ssl = NULL; + } +#endif + dm_sess_sk_del(sess); + if (hmux_sess->destroy) + hmux_sess->destroy(hmux_sess); + + gf_list_del(hmux_sess->sessions); + gf_mx_v(hmux_sess->mx); + gf_mx_del(sess->mx); + gf_free(hmux_sess); + } else { + GF_DownloadSession *asess = gf_list_get(hmux_sess->sessions, 0); + gf_assert(asess->hmux_sess == hmux_sess); + + hmux_sess->net_sess = asess; + //swap async buf if any to new active session + if (sess->async_buf_alloc) { + gf_assert(!asess->async_buf); + asess->async_buf = sess->async_buf; + asess->async_buf_alloc = sess->async_buf_alloc; + asess->async_buf_size = sess->async_buf_size; + sess->async_buf_alloc = sess->async_buf_size = 0; + sess->async_buf = NULL; + } + sess->sock = NULL; +#ifdef GPAC_HAS_SSL + sess->ssl = NULL; +#endif + gf_mx_v(sess->mx); + } + + if (sess->hmux_buf.data) { + gf_free(sess->hmux_buf.data); + memset(&sess->hmux_buf, 0, sizeof(hmux_reagg_buffer)); + } + sess->hmux_sess = NULL; + sess->mx = NULL; +} + + +GF_DownloadSession *hmux_get_session(void *user_data, s64 stream_id, Bool can_reassign) +{ + u32 i, nb_sess; + GF_DownloadSession *first_not_assigned = NULL; + GF_HMUX_Session *hmux_sess = (GF_HMUX_Session *)user_data; + + nb_sess = gf_list_count(hmux_sess->sessions); + for (i=0;i<nb_sess; i++) { + GF_DownloadSession *s = gf_list_get(hmux_sess->sessions, i); + if (s->hmux_stream_id == stream_id) + return s; + + if (s->server_mode && (s->hmux_stream_id<0) && !first_not_assigned) { + first_not_assigned = s; + } + } + if (can_reassign && first_not_assigned) { + first_not_assigned->hmux_stream_id = stream_id; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s reassigning old server session to new stream %d\n", hmux_sess->net_sess->log_name, stream_id)); + first_not_assigned->status = GF_NETIO_CONNECTED; + first_not_assigned->total_size = first_not_assigned->bytes_done = 0; + first_not_assigned->hmux_data_done = 0; + if (first_not_assigned->remote_path) gf_free(first_not_assigned->remote_path); + first_not_assigned->remote_path = NULL; + //reset internal reaggregation buffer + first_not_assigned->hmux_buf.size = 0; + + gf_dm_sess_clear_headers(first_not_assigned); + return first_not_assigned; + } + return NULL; +} + +void hmux_flush_internal_data(GF_DownloadSession *sess, Bool store_in_init) +{ + gf_dm_data_received(sess, (u8 *) sess->hmux_buf.data, sess->hmux_buf.size, store_in_init, NULL, NULL); + sess->hmux_buf.size = 0; +} + +void hmux_fetch_data(GF_DownloadSession *sess, u8 *obuffer, u32 size, u32 *nb_bytes) +{ + u32 copy, nb_b_pck; + u8 *data; + *nb_bytes = 0; + if (!sess->hmux_buf.size) { + if (sess->hmux_is_eos) { + if (!sess->total_size) { + sess->total_size = sess->bytes_done; + gf_dm_data_received(sess, obuffer, 0, GF_FALSE, NULL, NULL); + } + + if (sess->status==GF_NETIO_DATA_EXCHANGE) + sess->status = GF_NETIO_DATA_TRANSFERED; + } + return; + } + + gf_assert(sess->hmux_buf.offset<=sess->hmux_buf.size); + + nb_b_pck = sess->hmux_buf.size - sess->hmux_buf.offset; + if (nb_b_pck > size) + copy = size; + else + copy = nb_b_pck; + + data = sess->hmux_buf.data + sess->hmux_buf.offset; + memcpy(obuffer, data, copy); + *nb_bytes = copy; + //signal we received data (for event triggering / user callbacks) + gf_dm_data_received(sess, (u8 *) data, copy, GF_FALSE, NULL, NULL); + + if (copy < nb_b_pck) { + sess->hmux_buf.offset += copy; + } else { + sess->hmux_buf.size = sess->hmux_buf.offset = 0; + } + gf_assert(sess->hmux_buf.offset<=sess->hmux_buf.size); +} + +GF_Err hmux_send_payload(GF_DownloadSession *sess, u8 *data, u32 size) +{ + GF_Err e = GF_OK; + if (sess->hmux_send_data) { + e = gf_sk_probe(sess->sock); + if (e) return e; + return GF_SERVICE_ERROR; + } + + if (sess->hmux_stream_id<0) + return GF_URL_REMOVED; + + gf_mx_p(sess->mx); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Sending %d bytes on stream_id "LLD"\n", sess->log_name, size, sess->hmux_stream_id)); + + assert(sess->hmux_send_data==NULL); + sess->hmux_send_data = data; + sess->hmux_send_data_len = size; + if (sess->hmux_data_paused) { + e = sess->hmux_sess->resume(sess); + if (e) { + gf_mx_v(sess->mx); + return e; + } + } + //if no data, signal end of stream, otherwise regular send + if (!data || !size) { + if (sess->local_buf_len) { + gf_mx_v(sess->mx); + if (sess->hmux_is_eos) { + return GF_IP_NETWORK_EMPTY; + } + sess->hmux_is_eos = 1; + sess->hmux_sess->write(sess); + return GF_OK; + } + sess->hmux_is_eos = 1; + sess->hmux_sess->write(sess); + //stream_id is not yet 0 in case of PUT/PUSH, stream is closed once we get reply from server + } else { + sess->hmux_is_eos = 0; + //send the data + e = sess->hmux_sess->send_pending_data(sess); + if (e && (e!=GF_IP_NETWORK_EMPTY)) { + gf_mx_v(sess->mx); + return e; + } + } + gf_mx_v(sess->mx); + + if (!data || !size) { + if (sess->put_state) { + //we are done sending + if (!sess->local_buf_len) { + sess->put_state = 2; + sess->status = GF_NETIO_WAIT_FOR_REPLY; + } + return GF_OK; + } + } + if ((sess->hmux_stream_id<0) && sess->hmux_send_data) + return GF_URL_REMOVED; + return GF_OK; +} + +#endif //GPAC_HTTPMUX + + + +void gf_dm_sess_close_hmux(GF_DownloadSession *sess) +{ +#ifdef GPAC_HTTPMUX + if (sess->hmux_sess) + sess->hmux_sess->close_session(sess); +#endif +} + +GF_DownloadSession *gf_dm_sess_new_subsession(GF_DownloadSession *sess, s64 stream_id, void *usr_cbk, GF_Err *e) +{ +#ifdef GPAC_HTTPMUX + GF_DownloadSession *sub_sess; + if (!sess->hmux_sess) return NULL; + gf_mx_p(sess->mx); + sub_sess = gf_dm_sess_new_internal(NULL, NULL, 0, sess->user_proc, usr_cbk, sess->sock, GF_TRUE, e); + if (!sub_sess) { + gf_mx_v(sess->mx); + return NULL; + } + gf_list_add(sess->hmux_sess->sessions, sub_sess); +#ifdef GPAC_HAS_SSL + sub_sess->ssl = sess->ssl; +#endif + sub_sess->hmux_sess = sess->hmux_sess; + if (sub_sess->mx) gf_mx_del(sub_sess->mx); + sub_sess->mx = sess->hmux_sess->mx; + sub_sess->hmux_stream_id = stream_id; + sub_sess->status = GF_NETIO_CONNECTED; + + sub_sess->hmux_sess->setup_session(sub_sess, GF_FALSE); + + sub_sess->flags = sess->flags; + gf_mx_v(sess->mx); + return sub_sess; +#else + return NULL; +#endif //GPAC_HTTPMUX +} + + + +#endif //GPAC_DISABLE_NETWORK +
View file
gpac-26.02.0.tar.gz/src/utils/downloader_nghttp2.c
Added
@@ -0,0 +1,887 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "downloader.h" + +#if !defined(GPAC_DISABLE_NETWORK) && defined(GPAC_HAS_HTTP2) + +#include <nghttp2/nghttp2.h> + +#define HTTP2_BUFFER_SETTINGS_SIZE 128 + +//link for msvc +#if !defined(__GNUC__) && ( defined(_WIN32_WCE) || defined (WIN32) ) +#pragma comment(lib, "nghttp2") +#endif + + +static void h2_flush_send_ex(GF_DownloadSession *sess, Bool flush_local_buf); + +static int h2_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags , + void *user_data) +{ + GF_DownloadSession *sess; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + if (!sess) + return NGHTTP2_ERR_CALLBACK_FAILURE; + if ( + (!sess->server_mode && (frame->headers.cat == NGHTTP2_HCAT_RESPONSE)) + || (sess->server_mode && ((frame->headers.cat == NGHTTP2_HCAT_HEADERS) || (frame->headers.cat == NGHTTP2_HCAT_REQUEST))) + ) { + GF_HTTPHeader *hdrp; + + GF_SAFEALLOC(hdrp, GF_HTTPHeader); + if (hdrp) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id "LLD" got header %s: %s\n", sess->hmux_stream_id, name, value)); + hdrp->name = gf_strdup(name); + hdrp->value = gf_strdup(value); + gf_list_add(sess->headers, hdrp); + } + break; + } + } + return 0; +} + +static int h2_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) +{ + GF_DownloadSession *sess = hmux_get_session(user_data, frame->hd.stream_id, GF_TRUE); + if (!sess) { + GF_HMUX_Session *h2sess = (GF_HMUX_Session *)user_data; + GF_DownloadSession *par_sess = h2sess->net_sess; + if (!par_sess || !par_sess->server_mode) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + + if (par_sess->user_proc) { + GF_NETIO_Parameter param; + memset(¶m, 0, sizeof(GF_NETIO_Parameter)); + param.msg_type = GF_NETIO_REQUEST_SESSION; + par_sess->in_callback = GF_TRUE; + param.sess = par_sess; + param.stream_id = frame->hd.stream_id; + par_sess->user_proc(par_sess->usr_cbk, ¶m); + par_sess->in_callback = GF_FALSE; + + if (param.error == GF_OK) { + sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + } + } + + if (!sess) + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (sess->server_mode) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d header callback\n", frame->hd.stream_id)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) header callback\n", frame->hd.stream_id, sess->remote_path)); + } + break; + } + return 0; +} + +static int h2_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) +{ + GF_DownloadSession *sess; + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + if (!sess) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + if ( + (!sess->server_mode && (frame->headers.cat == NGHTTP2_HCAT_RESPONSE)) + || (sess->server_mode && ((frame->headers.cat == NGHTTP2_HCAT_HEADERS) || (frame->headers.cat == NGHTTP2_HCAT_REQUEST))) + ) { + sess->hmux_headers_seen = 1; + if (sess->server_mode) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 All headers received for stream ID "LLD"\n", sess->hmux_stream_id)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 All headers received for stream ID "LLD"\n", sess->hmux_stream_id)); + } + } + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) data done\n", frame->hd.stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); + sess->hmux_data_done = 1; + sess->hmux_is_eos = 0; + sess->hmux_send_data = NULL; + sess->hmux_send_data_len = 0; + } + break; + case NGHTTP2_DATA: + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + //if no session with such ID this means we got all our bytes and considered the session done, do not throw an error + if (sess) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) data done\n", frame->hd.stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); + sess->hmux_data_done = 1; + } + } + break; + case NGHTTP2_RST_STREAM: + sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + // cancel from remote peer, signal if not done + if (sess && sess->server_mode && !sess->hmux_is_eos) { + GF_NETIO_Parameter param; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) canceled\n", frame->hd.stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); + memset(¶m, 0, sizeof(GF_NETIO_Parameter)); + param.msg_type = GF_NETIO_CANCEL_STREAM; + gf_mx_p(sess->mx); + sess->in_callback = GF_TRUE; + param.sess = sess; + sess->user_proc(sess->usr_cbk, ¶m); + sess->in_callback = GF_FALSE; + gf_mx_v(sess->mx); + } + break; + } + + return 0; +} + +static int h2_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) +{ + GF_DownloadSession *sess = hmux_get_session(user_data, stream_id, GF_FALSE); + if (!sess) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + if (sess->hmux_buf.size + len > sess->hmux_buf.alloc) { + sess->hmux_buf.alloc = sess->hmux_buf.size + (u32) len; + sess->hmux_buf.data = gf_realloc(sess->hmux_buf.data, sizeof(u8) * sess->hmux_buf.alloc); + if (!sess->hmux_buf.data) return NGHTTP2_ERR_NOMEM; + } + memcpy(sess->hmux_buf.data + sess->hmux_buf.size, data, len); + sess->hmux_buf.size += (u32) len; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id "LLD" received %d bytes (%d/%d total) - flags %d\n", sess->hmux_stream_id, len, sess->hmux_buf.size, sess->total_size, flags)); + return 0; +} + +static int h2_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) +{ + Bool do_retry = GF_FALSE; + GF_DownloadSession *sess = hmux_get_session(user_data, stream_id, GF_FALSE); + if (!sess) + return 0; + + gf_mx_p(sess->mx); + + if (error_code==NGHTTP2_REFUSED_STREAM) + do_retry = GF_TRUE; + else if (sess->hmux_sess->do_shutdown && !sess->server_mode && !sess->bytes_done) + do_retry = GF_TRUE; + + if (do_retry) { + sess->hmux_sess->do_shutdown = GF_TRUE; + sess->hmux_switch_sess = GF_TRUE; + sess->status = GF_NETIO_SETUP; + SET_LAST_ERR(GF_OK) + gf_mx_v(sess->mx); + return 0; + } + + if (error_code) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) closed with error_code=%d\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url, error_code)); + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_IP_NETWORK_FAILURE) + sess->put_state = 0; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 stream_id %d (%s) closed (put state %d)\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url, sess->put_state)); + //except for PUT/POST, keep status in DATA_EXCHANGE as this frame might have been pushed while processing another session + + if (sess->put_state) { + sess->put_state = 0; + sess->status = GF_NETIO_DATA_TRANSFERED; + sess->last_error = GF_OK; + } + } + + //stream closed + sess->hmux_stream_id = -1; + gf_mx_v(sess->mx); + return 0; +} + +static int h2_error_callback(nghttp2_session *session, const char *msg, size_t len, void *user_data) +{ + if (session) + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 error %s\n", msg)); + return 0; +} + +static ssize_t h2_write_data(GF_DownloadSession *sess, const uint8_t *data, size_t length) +{ + GF_Err e = dm_sess_write(sess, data, (u32) length); + switch (e) { + case GF_OK: + return length; + case GF_IP_NETWORK_EMPTY: + if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) + return length; + return NGHTTP2_ERR_WOULDBLOCK; + case GF_IP_CONNECTION_CLOSED: + SET_LAST_ERR(GF_IP_CONNECTION_CLOSED) + return NGHTTP2_ERR_EOF; + default: + break; + } + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + +static ssize_t h2_send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) +{ + GF_HMUX_Session *h2sess = (GF_HMUX_Session *)user_data; + GF_DownloadSession *sess = h2sess->net_sess; + + return h2_write_data(sess, data, length); +} + +static int h2_before_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) +{ + GF_DownloadSession *sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + if (!sess) + return 0; + sess->hmux_ready_to_send = 1; + return 0; +} + + +ssize_t h2_data_source_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data) +{ + GF_DownloadSession *sess = (GF_DownloadSession *) source->ptr; + if (!sess) + return NGHTTP2_ERR_EOF; + + if (!sess->hmux_send_data_len) { + sess->hmux_send_data = NULL; + if (!sess->local_buf_len && sess->hmux_is_eos) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 send EOS for stream_id "LLD"\n", sess->hmux_stream_id)); + *data_flags = NGHTTP2_DATA_FLAG_EOF; + sess->hmux_is_eos = 0; + return 0; + } + sess->hmux_data_paused = 1; + return NGHTTP2_ERR_DEFERRED; + } + + if (sess->hmux_sess->copy) { + u32 copy = (sess->hmux_send_data_len > length) ? (u32) length : sess->hmux_send_data_len; + memcpy(buf, sess->hmux_send_data, copy); + sess->hmux_send_data += copy; + sess->hmux_send_data_len -= copy; + return copy; + } + + *data_flags = NGHTTP2_DATA_FLAG_NO_COPY; + if (sess->hmux_send_data_len > length) { + return length; + } + return sess->hmux_send_data_len; +} + + +static int h2_send_data_callback(nghttp2_session *session, nghttp2_frame *frame, const uint8_t *framehd, size_t length, nghttp2_data_source *source, void *user_data) +{ + char padding256; + ssize_t rv; + + //ultimately we would like to use directly source->ptr but the session could have been destroyed and there is no way to tell nghttp2 to stop + //calling us: we may get called before rst_stream is processed + //we therefore need to validate the session is indeed still alive, but that's slower... +#if 0 + GF_DownloadSession *sess = (GF_DownloadSession *) source->ptr; + if (!sess) + return NGHTTP2_ERR_EOF; + +#else + GF_DownloadSession *sess = hmux_get_session(user_data, frame->hd.stream_id, GF_FALSE); + if (!sess) + return NGHTTP2_ERR_EOF; +#endif + + gf_assert(sess->hmux_send_data_len); + gf_assert(sess->hmux_send_data_len >= length); + + rv = h2_write_data(sess, (u8 *) framehd, 9); + if (rv<0) { + if (rv==NGHTTP2_ERR_WOULDBLOCK) return NGHTTP2_ERR_WOULDBLOCK; + goto err; + } + + while (frame->data.padlen > 0) { + u32 padlen = (u32) frame->data.padlen - 1; + rv = h2_write_data(sess, padding, padlen); + if (rv<0) { + if (rv==NGHTTP2_ERR_WOULDBLOCK) continue; + goto err; + } + break; + } + while (1) { + rv = h2_write_data(sess, (u8 *) sess->hmux_send_data, length); + if (rv<0) { + if (rv==NGHTTP2_ERR_WOULDBLOCK) continue; + goto err; + } + break; + } + sess->hmux_send_data += (u32) length; + sess->hmux_send_data_len -= (u32) length; + return 0; +err: + + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(sess->last_error) + return (int) rv; +} + +static GF_Err h2_session_write(GF_DownloadSession *sess) +{ + gf_assert(sess->hmux_sess); + int rv = nghttp2_session_send(sess->hmux_sess->hmux_udta); + if (rv != 0) { + if (sess->last_error != GF_IP_CONNECTION_CLOSED) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 session_send error : %s\n", nghttp2_strerror(rv))); + } + if (sess->status != GF_NETIO_STATE_ERROR) { + sess->status = GF_NETIO_STATE_ERROR; + if (rv==NGHTTP2_ERR_NOMEM) SET_LAST_ERR(GF_OUT_OF_MEM) + else SET_LAST_ERR(GF_SERVICE_ERROR) + } + return sess->last_error; + } +// GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 session_send OK\n")); + return GF_OK; +} + +static void h2_stream_reset(GF_DownloadSession *sess, Bool is_abort) +{ + nghttp2_submit_rst_stream(sess->hmux_sess->hmux_udta, NGHTTP2_FLAG_NONE, (int32_t) sess->hmux_stream_id, is_abort ? NGHTTP2_CANCEL : NGHTTP2_NO_ERROR); +} + +static GF_Err h2_resume_stream(GF_DownloadSession *sess) +{ + sess->hmux_data_paused = 0; + nghttp2_session_resume_data(sess->hmux_sess->hmux_udta, (int32_t) sess->hmux_stream_id); + return GF_OK; +} + +static void h2_destroy(struct _http_mux_session *hmux_sess) +{ + nghttp2_session_del(hmux_sess->hmux_udta); +} + +static GF_Err h2_data_received(GF_DownloadSession *sess, const u8 *data, u32 nb_bytes) +{ + if (nb_bytes) { + ssize_t read_len = nghttp2_session_mem_recv(sess->hmux_sess->hmux_udta, data, nb_bytes); + if (read_len < 0 ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 nghttp2_session_mem_recv error: %s\n", nghttp2_strerror((int) read_len))); + return GF_IO_ERR; + } + } + /* send pending frames - hmux_sess may be NULL at this point if the connection was reset during processing of the above + this typically happens if we have a refused stream + */ + h2_session_write(sess); + return GF_OK; +} + + +#define NV_HDR(_hdr, _name, _value) { \ + _hdr.name = (uint8_t *)_name;\ + _hdr.value = (uint8_t *)_value;\ + _hdr.namelen = (u32) strlen(_name);\ + _hdr.valuelen = (u32) strlen(_value);\ + _hdr.flags = NGHTTP2_NV_FLAG_NONE;\ + } + +GF_Err h2_submit_request(GF_DownloadSession *sess, char *req_name, const char *url, const char *param_string, Bool has_body) +{ + u32 nb_hdrs, i; + char *hostport = NULL; + char *path = NULL; + char port20; + nghttp2_nv *hdrs; + + nb_hdrs = gf_list_count(sess->headers); + hdrs = gf_malloc(sizeof(nghttp2_nv) * (nb_hdrs + 4)); + + NV_HDR(hdrs0, ":method", req_name); + NV_HDR(hdrs1, ":scheme", "https"); + + gf_dynstrcat(&hostport, sess->server_name, NULL); + sprintf(port, ":%d", sess->port); + gf_dynstrcat(&hostport, port, NULL); + NV_HDR(hdrs2, ":authority", hostport); + + if (param_string) { + gf_dynstrcat(&path, url, NULL); + if (strchr(sess->remote_path, '?')) { + gf_dynstrcat(&path, param_string, "&"); + } else { + gf_dynstrcat(&path, param_string, "?"); + } + NV_HDR(hdrs3, ":path", path); + } else { + NV_HDR(hdrs3, ":path", url); + } + + for (i=0; i<nb_hdrs; i++) { + GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); + NV_HDR(hdrs4+i, hdr->name, hdr->value); + } +#ifndef NDEBUG + if (has_body) { + nghttp2_data_provider *data_io = (nghttp2_data_provider*)sess->hmux_priv; + gf_assert(data_io->read_callback); + gf_assert(data_io->source.ptr != NULL); + } +#endif + sess->hmux_data_done = 0; + sess->hmux_headers_seen = 0; + sess->hmux_stream_id = nghttp2_submit_request(sess->hmux_sess->hmux_udta, NULL, hdrs, nb_hdrs+4, + has_body ? (nghttp2_data_provider *)sess->hmux_priv : NULL, sess); + sess->hmux_ready_to_send = 0; + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_DEBUG)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 send request (has_body %d) for new stream_id "LLD":\n", has_body, sess->hmux_stream_id)); + for (i=0; i<nb_hdrs+4; i++) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("\t%s: %s\n", hdrsi.name, hdrsi.value)); + } + } +#endif + + gf_free(hdrs); + gf_free(hostport); + if (path) gf_free(path); + + if (sess->hmux_stream_id < 0) { + return GF_IP_NETWORK_FAILURE; + } + + return GF_OK; +} + + + +GF_Err h2_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body) +{ + u32 i, count; + char szFmt50; + if (!sess || !sess->server_mode) return GF_BAD_PARAM; + + count = gf_list_count(sess->headers); + + + nghttp2_nv *hdrs; + + if (response_body) { + no_body = GF_FALSE; + sess->hmux_send_data = (u8 *) response_body; + sess->hmux_send_data_len = body_len; + sess->hmux_is_eos = 1; + } else if (!no_body) { + switch (reply_code) { + case 200: + case 206: + no_body = GF_FALSE; + sess->hmux_is_eos = 0; + break; + default: + no_body = GF_TRUE; + break; + } + } + + hdrs = gf_malloc(sizeof(nghttp2_nv) * (count + 1) ); + + sprintf(szFmt, "%d", reply_code); + NV_HDR(hdrs0, ":status", szFmt); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP/2 send reply for stream_id "LLD" (body %d) headers:\n:status: %s\n", sess->hmux_stream_id, !no_body, szFmt)); + for (i=0; i<count; i++) { + GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); + NV_HDR(hdrsi+1, hdr->name, hdr->value) + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s: %s\n", hdr->name, hdr->value)); + } + + + gf_mx_p(sess->mx); + + int rv = nghttp2_submit_response(sess->hmux_sess->hmux_udta, (int32_t) sess->hmux_stream_id, hdrs, count+1, no_body ? NULL : sess->hmux_priv); + + gf_free(hdrs); + + if (rv != 0) { + gf_mx_v(sess->mx); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 Failed to submit reply: %s\n", nghttp2_strerror(rv))); + return GF_SERVICE_ERROR; + } + h2_session_write(sess); + //in case we have a body already setup with this reply + h2_flush_send_ex(sess, GF_FALSE); + + gf_mx_v(sess->mx); + + return GF_OK; +} + +static GF_Err h2_async_flush(GF_DownloadSession *sess, Bool for_close) +{ + if (!sess->local_buf_len && !sess->hmux_is_eos) return GF_OK; + gf_assert(sess->hmux_send_data==NULL); + + //HTTP2: resume data sending, set data to local copy send packets and check what was sent + GF_Err ret = GF_OK; + u8 was_eos = sess->hmux_is_eos; + gf_assert(sess->hmux_send_data==NULL); + gf_mx_p(sess->mx); + + sess->hmux_send_data = sess->local_buf; + sess->hmux_send_data_len = sess->local_buf_len; + if (sess->hmux_data_paused) { + sess->hmux_data_paused = 0; + sess->hmux_sess->resume(sess); + } + h2_flush_send_ex(sess, GF_TRUE); + gf_mx_v(sess->mx); + + if (sess->hmux_send_data_len) { + //we will return empty to avoid pushing data too fast, but we also need to flush the async buffer + ret = GF_IP_NETWORK_EMPTY; + if (sess->hmux_send_data_len<sess->local_buf_len) { + memmove(sess->local_buf, sess->hmux_send_data, sess->hmux_send_data_len); + sess->local_buf_len = sess->hmux_send_data_len; + } else { + //nothing sent, check socket + ret = gf_sk_probe(sess->sock); + } + sess->hmux_send_data = NULL; + sess->hmux_send_data_len = 0; + sess->hmux_is_eos = was_eos; + } else { + sess->local_buf_len = 0; + sess->hmux_send_data = NULL; + sess->hmux_send_data_len = 0; + if (was_eos && !sess->hmux_is_eos) { + if (sess->put_state==1) { + sess->put_state = 2; + sess->status = GF_NETIO_WAIT_FOR_REPLY; + } + } + } + return ret; +} + +static GF_Err h2_setup_session(GF_DownloadSession *sess, Bool is_destroy) +{ + if (is_destroy) { + if (sess->hmux_priv) { + gf_free(sess->hmux_priv); + sess->hmux_priv = NULL; + } + return GF_OK; + } + if (!sess->hmux_priv) { + GF_SAFEALLOC(sess->hmux_priv, nghttp2_data_provider); + if (!sess->hmux_priv) return GF_OUT_OF_MEM; + } + nghttp2_data_provider *data_io = sess->hmux_priv; + data_io->read_callback = h2_data_source_read_callback; + data_io->source.ptr = sess; + return GF_OK; +} + +static void h2_flush_send_ex(GF_DownloadSession *sess, Bool flush_local_buf) +{ + char h2_flush1024; + u32 res; + + while (sess->hmux_send_data) { + GF_Err e; + u32 nb_bytes = sess->hmux_send_data_len; + + if (!sess->local_buf_len || flush_local_buf) { + //read any frame pending from remote peer (window update and co) + e = gf_dm_read_data(sess, h2_flush, 1023, &res); + if ((e<0) && (e != GF_IP_NETWORK_EMPTY)) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(sess->last_error) + break; + } + + h2_session_write(sess); + + //error or regular eos + if (sess->hmux_stream_id<0) + break; + if (sess->status==GF_NETIO_STATE_ERROR) + break; + } + + if (nb_bytes > sess->hmux_send_data_len) continue; + if (!(sess->flags & GF_NETIO_SESSION_NO_BLOCK)) continue; + + if (flush_local_buf) return; + + //stream will now block, no choice but do a local copy... + if (sess->local_buf_len + nb_bytes > sess->local_buf_alloc) { + sess->local_buf_alloc = sess->local_buf_len + nb_bytes; + sess->local_buf = gf_realloc(sess->local_buf, sess->local_buf_alloc); + if (!sess->local_buf) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(sess->last_error) + break; + } + } + if (nb_bytes) { + memcpy(sess->local_buf + sess->local_buf_len, sess->hmux_send_data, nb_bytes); + sess->local_buf_len+=nb_bytes; + } + sess->hmux_send_data = NULL; + sess->hmux_send_data_len = 0; + } +} +static GF_Err h2_data_pending(GF_DownloadSession *sess) +{ + GF_Err e = h2_session_write(sess); + if (e) return e; + h2_flush_send_ex(sess, GF_FALSE); + return GF_OK; +} +static void h2_close_session(GF_DownloadSession *sess) +{ + nghttp2_submit_shutdown_notice(sess->hmux_sess->hmux_udta); + h2_session_write(sess); + +#if 1 + u64 in_time; + in_time = gf_sys_clock_high_res(); + while (nghttp2_session_want_read(sess->hmux_sess->hmux_udta)) { + u32 res; + char h2_flush2024; + GF_Err e; + if (gf_sys_clock_high_res() - in_time > 100000) + break; + + //read any frame pending from remote peer (window update and co) + e = gf_dm_read_data(sess, h2_flush, 2023, &res); + if ((e<0) && (e != GF_IP_NETWORK_EMPTY)) { + if (e!=GF_IP_CONNECTION_CLOSED) { + SET_LAST_ERR(e) + sess->status = GF_NETIO_STATE_ERROR; + } + break; + } + h2_session_write(sess); + } +#endif + sess->status = GF_NETIO_DISCONNECTED; +} + +void h2_initialize_session(GF_DownloadSession *sess) +{ + int rv; + nghttp2_settings_entry iv2 = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, + {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} + }; + char szMXName100; + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_send_callback(callbacks, h2_send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, h2_frame_recv_callback); + nghttp2_session_callbacks_set_before_frame_send_callback(callbacks, h2_before_frame_send_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, h2_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, h2_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, h2_header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, h2_begin_headers_callback); + nghttp2_session_callbacks_set_error_callback(callbacks, h2_error_callback); + + GF_SAFEALLOC(sess->hmux_sess, GF_HMUX_Session) + sess->hmux_sess->sessions = gf_list_new(); + sess->hmux_sess->copy = gf_opts_get_bool("core", "h2-copy"); + if (!sess->hmux_sess->copy) + nghttp2_session_callbacks_set_send_data_callback(callbacks, h2_send_data_callback); + + if (sess->server_mode) { + nghttp2_session_server_new((nghttp2_session**) &sess->hmux_sess->hmux_udta, callbacks, sess->hmux_sess); + } else { + nghttp2_session_client_new((nghttp2_session**) &sess->hmux_sess->hmux_udta, callbacks, sess->hmux_sess); + } + nghttp2_session_callbacks_del(callbacks); + sess->hmux_sess->net_sess = sess; + //setup function pointers + sess->hmux_sess->submit_request = h2_submit_request; + sess->hmux_sess->send_reply = h2_send_reply; + sess->hmux_sess->write = h2_session_write; + sess->hmux_sess->destroy = h2_destroy; + sess->hmux_sess->stream_reset = h2_stream_reset; + sess->hmux_sess->resume = h2_resume_stream; + sess->hmux_sess->data_received = h2_data_received; + sess->hmux_sess->setup_session = h2_setup_session; + sess->hmux_sess->send_pending_data = h2_data_pending; + sess->hmux_sess->async_flush = h2_async_flush; + sess->hmux_sess->close_session = h2_close_session; + + sess->hmux_sess->connected = GF_TRUE; + + gf_list_add(sess->hmux_sess->sessions, sess); + + if (!sess->mx) { + sprintf(szMXName, "http2_%p", sess->hmux_sess); + sess->hmux_sess->mx = gf_mx_new(szMXName); + sess->mx = sess->hmux_sess->mx; + } else { + sess->hmux_sess->mx = sess->mx; + } + sess->chunked = GF_FALSE; + + h2_setup_session(sess, GF_FALSE); + + if (sess->server_mode) { + sess->hmux_stream_id = 1; + if (sess->h2_upgrade_settings) { + rv = nghttp2_session_upgrade2(sess->hmux_sess->hmux_udta, sess->h2_upgrade_settings, sess->h2_upgrade_settings_len, 0, sess); + gf_free(sess->h2_upgrade_settings); + sess->h2_upgrade_settings = NULL; + + if (rv) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR( (rv==NGHTTP2_ERR_NOMEM) ? GF_OUT_OF_MEM : GF_REMOTE_SERVICE_ERROR) + return; + } + } + } + + /* client 24 bytes magic string will be sent by nghttp2 library */ + rv = nghttp2_submit_settings(sess->hmux_sess->hmux_udta, NGHTTP2_FLAG_NONE, iv, GF_ARRAY_LENGTH(iv)); + if (rv != 0) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR((rv==NGHTTP2_ERR_NOMEM) ? GF_OUT_OF_MEM : GF_SERVICE_ERROR) + return; + } + h2_session_write(sess); +} + + + +void http2_set_upgrade_headers(GF_DownloadSession *sess) +{ + GF_HTTPHeader *hdr; + u8 settingsHTTP2_BUFFER_SETTINGS_SIZE; + u32 settings_len; + u8 b64100; + u32 b64len; + + nghttp2_settings_entry h2_settings2 = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, + {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} + }; + + PUSH_HDR("Connection", "Upgrade, HTTP2-Settings") + PUSH_HDR("Upgrade", "h2c") + + + settings_len = (u32) nghttp2_pack_settings_payload(settings, HTTP2_BUFFER_SETTINGS_SIZE, h2_settings, GF_ARRAY_LENGTH(h2_settings)); + b64len = gf_base64_encode(settings, settings_len, b64, 100); + b64b64len = 0; + PUSH_HDR("HTTP2-Settings", b64) + + sess->h2_upgrade_state = 1; +} + +GF_Err http2_do_upgrade(GF_DownloadSession *sess, const char *body, u32 body_len) +{ + int rv; + u8 settingsHTTP2_BUFFER_SETTINGS_SIZE; + u32 settings_len; + + nghttp2_settings_entry h2_settings2 = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, + {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} + }; + settings_len = (u32) nghttp2_pack_settings_payload(settings, HTTP2_BUFFER_SETTINGS_SIZE, h2_settings, GF_ARRAY_LENGTH(h2_settings)); + + h2_initialize_session(sess); + sess->hmux_stream_id = 1; + rv = nghttp2_session_upgrade2(sess->hmux_sess->hmux_udta, settings, settings_len, (sess->http_read_type==1) ? 1 : 0, sess); + if (rv < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 nghttp2_session_upgrade2 error: %s\n", nghttp2_strerror(rv))); + return GF_IP_NETWORK_FAILURE; + } + //push the body + if (body_len) { + rv = (int) nghttp2_session_mem_recv(sess->hmux_sess->hmux_udta, body, body_len); + if (rv < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/2 nghttp2_session_mem_recv error: %s\n", nghttp2_strerror(rv))); + return GF_IP_NETWORK_FAILURE; + } + //stay in WAIT_FOR_REPLY state and do not flush data, cache is not fully configured yet + } + //send pending frames + GF_Err e = h2_session_write(sess); + if (e) return e; + sess->connection_close = GF_FALSE; + sess->h2_upgrade_state = 2; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s Upgraded connection to HTTP/2\n", sess->log_name)); + return GF_OK; +} + +GF_Err http2_check_upgrade(GF_DownloadSession *sess) +{ + GF_Err e = GF_OK; + char *rsp_buf = NULL; + if (!sess || !sess->server_mode) return GF_BAD_PARAM; + if (!sess->h2_upgrade_settings) return GF_OK; + + u32 len; + gf_assert(!sess->hmux_sess); + gf_dynstrcat(&rsp_buf, "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\n" + "Upgrade: h2c\r\n\r\n", NULL); + + len = (u32) strlen(rsp_buf); + e = dm_sess_write(sess, rsp_buf, len); + + gf_free(rsp_buf); + h2_initialize_session(sess); + return e; +} + +#endif //!defined(GPAC_DISABLE_NETWORK) && defined(GPAC_HAS_HTTP2) +
View file
gpac-26.02.0.tar.gz/src/utils/downloader_ngtcp2.c
Added
@@ -0,0 +1,2202 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom Paris 2025-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "downloader.h" + +/* + Functions prefixes: + h3_*: function used for hmux session function pointers + ngq_*: function callback for ngtcp2 lib (quic) + ngh3_*: function callback for nghttp3 lib +*/ + + +#if !defined(GPAC_DISABLE_NETWORK) && defined(GPAC_HAS_NGTCP2) + +#if defined(WIN32) && !defined(__GNUC__) +#pragma comment(lib, "libnghttp3.lib") +#pragma comment(lib, "libngtcp2_crypto_quictls.lib") +#pragma comment(lib, "libngtcp2.lib") +#endif + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_quictls.h> +#include <nghttp3/nghttp3.h> + +#define NGTCP2_STATELESS_RESET_BURST 100 +#define NGTCP2_SV_SCIDLEN 18 +#define MAX_CONNID_LEN 255 +#define MAX_ADDR_LEN 100 + +const u8 *gf_sk_get_address(GF_Socket *sock, u32 *addr_size); + +GF_Err gf_sk_bind_ex(GF_Socket *sock, const char *ifce_ip_or_name, u16 port, const char *peer_name, u16 peer_port, u32 options, + u8 **dst_sock_addr, u32 *dst_sock_addr_len, u8 **src_sock_addr, u32 *src_sock_addr_len); + +GF_Err gf_sk_send_to(GF_Socket *sock, const u8 *buffer, u32 length, const u8 *addr, u32 addr_len, u32 *written); +GF_Err gf_sk_connect_ex(GF_Socket *sock, const char *PeerName, u16 PortNumber, const char *ifce_ip_or_name, Bool use_udp_connect); + + +//amount of bytes we store internally +#define SOCK_BUF_SIZE 200000 + +struct __gf_quic_server +{ + GF_Socket *sock; + GF_DownloadManager *dm; + Bool (*accept_conn)(void *udta, GF_DownloadSession *sess, const char *address, u32 port); + void *udta; + SSL_CTX *ssl_ctx; + + u8 *local_add; + u32 local_add_len; + + GF_List *connections; + u8 secretGF_SHA256_DIGEST_SIZE; + Bool validate_address; + u32 stateless_reset_count; + +}; + +typedef struct +{ + u8 dcidMAX_CONNID_LEN; + u32 dcid_len; + Bool associated; +} ConnIDInfo; + +#define MAC_CONN_ID 9 +typedef struct __gf_quic_connection +{ + GF_QuicServer *server; + + ConnIDInfo conn_idsMAC_CONN_ID; + + u8 addrMAX_ADDR_LEN; + u32 addr_len; + + Bool closed; + u64 drain_period_end; + u64 close_period_end; + u8 closebufNGTCP2_MAX_UDP_PAYLOAD_SIZE; + u32 closebuf_len; + + GF_HMUX_Session *hmux; + + Bool pck_sent; +} GF_QuicServerConnection; + +typedef enum +{ + QUIC_HANDSHAKE_NONE=0, + QUIC_HANDSHAKE_SENT, + QUIC_HANDSHAKE_DONE, + QUIC_HANDSHAKE_ERROR +} QuicHandshakeState; + +typedef struct +{ + //if server, pointer to the server, NULL otherwise + GF_QuicServerConnection *serv_conn; + + // parent hmux downlaoder session + GF_HMUX_Session *hmux; + ngtcp2_path path; + ngtcp2_conn *conn; + ngtcp2_crypto_conn_ref conn_ref; + nghttp3_conn *http_conn; + ngtcp2_ccerr last_error; + + //for both client and server + QuicHandshakeState handshake_state; + u8 cur_buf1500; + u32 cur_buf_len; + ngtcp2_path_storage cur_path; + + //for client + u8 secret32; +} GF_QuicConnection; + +typedef struct +{ + nghttp3_data_reader data_read; + u32 local_buf_sent, local_buf_ack; + Bool wait_ack; + Bool is_put; + + //we use a double buffer so that we can write to the second buffer while sending the first one: + //- we cannot reallocate the first buffer if commited for sent + //- if first buffer is done sending, we aggregate data in secnd buffer if not larger than SOCK_BUF_SIZE/2 + //- when the first buffer is fully acked, we swap first and second + u8 *second_local_buf; + u32 second_local_buf_len, second_local_buf_alloc; +} GF_QuicDataRead; + +static GF_Err quic_handle_error(GF_QuicConnection *qc); +static GF_Err quic_send_packet(GF_QuicServer *qs, const u8 *buf, u32 len, const u8 *to_a, u32 to_alen); +static GF_Err h3_session_write_ex(GF_DownloadSession *sess, GF_QuicConnection *qc); + +static u64 ngtcp2_timestamp() +{ + return 1000*gf_sys_clock_high_res(); +} + +static void ngq_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) +{ + size_t i; + (void)rand_ctx; + for (i = 0; i < destlen; ++i) { + *dest = (uint8_t)gf_rand(); + } +} + +static int ngq_get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, size_t cidlen, void *user_data) +{ + (void)conn; + GF_QuicConnection *qc = user_data; + + if (RAND_bytes(cid->data, (int)cidlen) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + cid->datalen = cidlen; + if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + //server + if (qc->serv_conn) { + u32 conn_idx=0; + for (;conn_idx<MAC_CONN_ID; conn_idx++) { + ConnIDInfo *ci = &qc->serv_conn->conn_idsconn_idx; + if (!ci->associated) { + memcpy(ci->dcid, cid->data, cid->datalen); + ci->dcid_len = cid->datalen; + ci->associated=GF_TRUE; + return 0; + } + } + return NGTCP2_ERR_CALLBACK_FAILURE; + } else { + if (ngtcp2_crypto_generate_stateless_reset_token(token, qc->secret, 32, cid) != + 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int ngq_remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, void *user_data) +{ + GF_QuicConnection *qc = user_data; + //client + if (!qc->serv_conn) return 0; + + u32 conn_idx=0; + for (;conn_idx<MAC_CONN_ID; conn_idx++) { + ConnIDInfo *ci = &qc->serv_conn->conn_idsconn_idx; + if (!memcmp(ci->dcid, cid->data, cid->datalen)) { + ci->associated = GF_FALSE; + return 0; + } + } + return 0; +} + +static int ngq_extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) +{ + return 0; +} + +static void ngq_printf(void *user_data, const char *format, ...) +{ + va_list ap; + (void)user_data; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + fprintf(stderr, "\n"); +} +static void ngq_debug_trace(const char *format, va_list args) +{ + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); +} +static void ngq_debug_trace_noop(const char *format, va_list args) +{ +} + + + +static nghttp3_ssize ngh3_data_source_read_callback( + nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *conn_user_data, void *stream_user_data) +{ + GF_DownloadSession *sess = stream_user_data; + GF_QuicDataRead *qr = sess->hmux_priv; + + if (!sess->hmux_is_eos && !sess->local_buf_len && !qr->second_local_buf_len + ) { + sess->hmux_data_paused = 1; + return NGHTTP3_ERR_WOULDBLOCK; + } + + vec0.len = sess->local_buf_len - qr->local_buf_sent; + if (!vec0.len) { + if (sess->hmux_is_eos && !qr->second_local_buf_len + ) { + *pflags |= NGHTTP3_DATA_FLAG_EOF; + sess->hmux_is_eos = 0; + sess->hmux_data_done = 1; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 stream_id "LLD" sending EOS\n", stream_id)); + + gf_free(qr->second_local_buf); + qr->second_local_buf = NULL; + qr->second_local_buf_len = 0; + qr->second_local_buf_alloc = 0; + return 0; + } + sess->hmux_data_paused = 1; + qr->wait_ack = GF_TRUE; + return NGHTTP3_ERR_WOULDBLOCK; + } + vec0.base = sess->local_buf + qr->local_buf_sent; + qr->local_buf_sent += vec0.len; + return 1; +} + +static GF_Err h3_setup_session(GF_DownloadSession *sess, Bool is_destroy) +{ + if (is_destroy) { + if (sess->hmux_priv) { + GF_QuicDataRead *qr = sess->hmux_priv; + if (qr->second_local_buf) gf_free(qr->second_local_buf); + gf_free(sess->hmux_priv); + sess->hmux_priv = NULL; + } + return GF_OK; + } + + if (!sess->hmux_priv) { + GF_SAFEALLOC(sess->hmux_priv, GF_QuicDataRead); + if (!sess->hmux_priv) return GF_OUT_OF_MEM; + if (sess->log_name) gf_free(sess->log_name); + sess->log_name = gf_strdup("HTTP/3"); + } + GF_QuicDataRead *qr = sess->hmux_priv; + qr->data_read.read_data = ngh3_data_source_read_callback; + return GF_OK; +} + +static int ngh3_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *conn_user_data, void *stream_user_data) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + GF_DownloadSession *sess = stream_user_data; + + ngtcp2_conn_extend_max_stream_offset(qc->conn, stream_id, datalen); + ngtcp2_conn_extend_max_offset(qc->conn, datalen); + + if (!sess) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + if (sess->hmux_buf.size + datalen > sess->hmux_buf.alloc) { + sess->hmux_buf.alloc = sess->hmux_buf.size + (u32) datalen; + sess->hmux_buf.data = gf_realloc(sess->hmux_buf.data, sizeof(u8) * sess->hmux_buf.alloc); + if (!sess->hmux_buf.data) return NGHTTP3_ERR_NOMEM; + } + memcpy(sess->hmux_buf.data + sess->hmux_buf.size, data, datalen); + sess->hmux_buf.size += (u32) datalen; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 stream_id "LLD" received %d bytes (%d/%d total)\n", sess->hmux_stream_id, (u32) datalen, sess->hmux_buf.size, sess->total_size)); + return 0; +} + +static int ngh3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t datalen, void *conn_user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + ngtcp2_conn_extend_max_stream_offset(qc->conn, stream_id, datalen); + ngtcp2_conn_extend_max_offset(qc->conn, datalen); + return 0; +} + +static int ngh3_begin_headers(nghttp3_conn *conn, int64_t stream_id, void *conn_user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + GF_DownloadSession *sess = stream_user_data; + + if (!sess && (qc->hmux->net_sess->hmux_stream_id==-2)) { + sess = qc->hmux->net_sess; + qc->hmux->net_sess->hmux_stream_id = stream_id; + nghttp3_conn_set_stream_user_data(conn, stream_id, sess); + } + + if (!sess) { + GF_DownloadSession *par_sess = qc->hmux->net_sess; + if (!par_sess || !par_sess->server_mode) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + + if (par_sess->user_proc) { + GF_NETIO_Parameter param; + memset(¶m, 0, sizeof(GF_NETIO_Parameter)); + param.msg_type = GF_NETIO_REQUEST_SESSION; + par_sess->in_callback = GF_TRUE; + param.sess = par_sess; + param.stream_id = stream_id; + par_sess->user_proc(par_sess->usr_cbk, ¶m); + par_sess->in_callback = GF_FALSE; + + if (param.error == GF_OK) { + sess = hmux_get_session(qc->hmux, stream_id, GF_FALSE); + } + } + + if (!sess) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + nghttp3_conn_set_stream_user_data(conn, stream_id, sess); + sess->hmux_stream_id = stream_id; + sess->flags |= GF_NETIO_SESSION_USE_QUIC; + } + + GF_QuicDataRead *qr = sess->hmux_priv; + qr->is_put = GF_FALSE; + gf_dm_sess_clear_headers(sess); + return 0; +} + +static int ngh3_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, + void *conn_user_data, void *stream_user_data) +{ + GF_DownloadSession *sess = stream_user_data; + GF_HTTPHeader *hdrp; + + GF_SAFEALLOC(hdrp, GF_HTTPHeader); + if (!hdrp) return NGHTTP3_ERR_NOMEM; + nghttp3_vec v = nghttp3_rcbuf_get_buf(name); + + hdrp->name = gf_malloc(v.len+1); + if (!hdrp->name) { + gf_free(hdrp); + return NGHTTP3_ERR_NOMEM; + } + memcpy(hdrp->name,v.base,v.len+1); + hdrp->namev.len=0; + + v = nghttp3_rcbuf_get_buf(value); + hdrp->value = gf_malloc(v.len+1); + if (!hdrp->value) { + gf_free(hdrp->name); + gf_free(hdrp); + return NGHTTP3_ERR_NOMEM; + } + memcpy(hdrp->value,v.base,v.len+1); + hdrp->valuev.len=0; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 stream_id "LLD" got header %s: %s\n", sess->hmux_stream_id, hdrp->name, hdrp->value)); + gf_list_add(sess->headers, hdrp); + + if (!strcmp(hdrp->name, ":method") && (!stricmp(hdrp->value, "PUT") || !stricmp(hdrp->value, "POST"))) { + GF_QuicDataRead *qr = sess->hmux_priv; + qr->is_put = GF_TRUE; + } + return 0; +} + +static int ngh3_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *conn_user_data, void *stream_user_data) +{ + GF_DownloadSession *sess = stream_user_data; + sess->hmux_headers_seen = 1; + if (sess->server_mode) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 All headers received for stream ID "LLD"\n", sess->hmux_stream_id)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 All headers received for stream ID "LLD"\n", sess->hmux_stream_id)); + } + if (fin && !sess->server_mode) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 stream_id "LLU" (%s) data done\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url)); + sess->hmux_data_done = 1; + sess->hmux_is_eos = 0; + sess->hmux_send_data = NULL; + sess->hmux_send_data_len = 0; + } + return 0; +} + +static int ngh3_begin_trailers(nghttp3_conn *conn, int64_t stream_id, void *conn_user_data, void *stream_user_data) { + return 0; +} + +static int ngh3_recv_trailer(nghttp3_conn *conn, int64_t stream_id, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, + void *conn_user_data, void *stream_user_data) +{ + return 0; +} + +static int ngh3_end_trailers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *conn_user_data, void *stream_user_data) +{ + return 0; +} + +static int ngh3_stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *conn_user_data, void *stream_user_data) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + if (!qc->http_conn) return 0; + int rv = nghttp3_conn_shutdown_stream_read(qc->http_conn, stream_id); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_shutdown_stream_read failed %s\n", nghttp3_strerror(rv) )); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + //TODO : abort put/post or server mode + return 0; +} + +static int ngh3_reset_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *conn_user_data, void *stream_user_data) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + if (!qc->http_conn) return 0; + int rv = ngtcp2_conn_shutdown_stream_write(qc->conn, 0, stream_id, app_error_code); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 ngtcp2_conn_shutdown_stream_write failed %s\n", ngtcp2_strerror(rv) )); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static int ngh3_end_stream(nghttp3_conn *conn, int64_t stream_id, void *conn_user_data, void *stream_user_data) +{ + GF_DownloadSession *sess = stream_user_data; + //session may have been detached once we detect EOS, ignore streamid mismatch + if (!sess || (sess->hmux_stream_id != stream_id)) + return 0; + + GF_QuicDataRead *qr = sess->hmux_priv; + if (sess->put_state) { + sess->put_state = 0; + sess->status = GF_NETIO_DATA_TRANSFERED; + sess->last_error = GF_OK; + } + if ((!sess->server_mode || qr->is_put) && !sess->hmux_data_done) + sess->hmux_is_eos = 1; + + return 0; +} + +static int ngh3_stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *conn_user_data, void *stream_user_data) +{ + Bool do_retry = GF_FALSE; + GF_DownloadSession *sess = stream_user_data; + + //session may have been detached once we detect EOS, ignore streamid mismatch + if (!sess|| (sess->hmux_stream_id != stream_id)) + return 0; + + if (!ngtcp2_is_bidi_stream(stream_id)) { + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + assert(!ngtcp2_conn_is_local_stream(qc->conn, stream_id)); + ngtcp2_conn_extend_max_streams_uni(qc->conn, 1); + } + + gf_mx_p(sess->mx); + + if ((app_error_code==NGHTTP3_H3_EXCESSIVE_LOAD) + || (app_error_code==NGHTTP3_H3_REQUEST_REJECTED) + ) { + do_retry = GF_TRUE; + } else if (sess->hmux_sess->do_shutdown && !sess->server_mode && !sess->bytes_done) { + do_retry = GF_TRUE; + } + + if (do_retry) { + sess->hmux_sess->do_shutdown = GF_TRUE; + sess->hmux_switch_sess = GF_TRUE; + sess->status = GF_NETIO_SETUP; + SET_LAST_ERR(GF_OK) + gf_mx_v(sess->mx); + return 0; + } + + if (app_error_code!=NGHTTP3_H3_NO_ERROR) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 stream_id "LLU" (%s) closed with error_code=%d\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url, app_error_code)); + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_IP_NETWORK_FAILURE) + sess->put_state = 0; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 stream_id "LLU" (%s) closed (put state %d)\n", stream_id, sess->remote_path ? sess->remote_path : sess->orig_url, sess->put_state)); + + if (sess->put_state) { + sess->put_state = 0; + sess->status = GF_NETIO_DATA_TRANSFERED; + sess->last_error = GF_OK; + } + } + + //stream closed + sess->hmux_stream_id = -1; + gf_mx_v(sess->mx); + return 0; +} + +static int ngh3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, uint64_t datalen, + void *conn_user_data, void *stream_user_data) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)conn_user_data; + GF_DownloadSession *sess = stream_user_data; + GF_QuicDataRead *qr = sess->hmux_priv; + + ngtcp2_conn_info ci; + ngtcp2_conn_get_conn_info(qc->conn, &ci); + + gf_assert(sess->hmux_stream_id == stream_id); + + qr->local_buf_ack += datalen; + assert(qr->local_buf_sent >= qr->local_buf_ack); + //only memove once ack + if (qr->local_buf_sent == qr->local_buf_ack) { + gf_mx_p(sess->mx); + assert(sess->local_buf_len >= qr->local_buf_sent); + if (sess->local_buf_len) { + sess->local_buf_len -= qr->local_buf_sent; + if (sess->local_buf_len) + memmove(sess->local_buf, sess->local_buf + qr->local_buf_sent, sess->local_buf_len); + } + qr->local_buf_sent = 0; + qr->local_buf_ack = 0; + + //swap buffers + if (!sess->local_buf_len && qr->second_local_buf_len) { + u8 *tmp_buf = sess->local_buf; + u32 tmp = sess->local_buf_alloc; + sess->local_buf = qr->second_local_buf; + sess->local_buf_alloc = qr->second_local_buf_alloc; + sess->local_buf_len = qr->second_local_buf_len; + qr->second_local_buf = tmp_buf; + qr->second_local_buf_alloc = tmp; + qr->second_local_buf_len = 0; + } + gf_mx_v(sess->mx); + } + qr->wait_ack = GF_FALSE; + if ((sess->local_buf_len || sess->hmux_is_eos) && sess->hmux_data_paused) { + sess->hmux_data_paused = 0; + nghttp3_conn_resume_stream(qc->http_conn, sess->hmux_stream_id); + } + return 0; +} + +static GF_Err h3_setup_http3(GF_HMUX_Session *hmux_sess) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)hmux_sess->hmux_udta; + if (qc->http_conn) return GF_OK; + + if (ngtcp2_conn_get_streams_uni_left(qc->conn) < 3) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC peer does not allow at least 3 unidirectional streams.\n")); + return GF_REMOTE_SERVICE_ERROR; + } + + nghttp3_callbacks callbacks = { + .stream_close = ngh3_stream_close, + .recv_data = ngh3_recv_data, + .deferred_consume = ngh3_deferred_consume, + .begin_headers = ngh3_begin_headers, + .recv_header = ngh3_recv_header, + .end_headers = ngh3_end_headers, + .begin_trailers = ngh3_begin_trailers, + .recv_trailer = ngh3_recv_trailer, + .end_trailers = ngh3_end_trailers, + .stop_sending = ngh3_stop_sending, + .reset_stream = ngh3_reset_stream, + .acked_stream_data = ngh3_acked_stream_data, + .end_stream = ngh3_end_stream + }; + + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + const nghttp3_mem *mem = nghttp3_mem_default(); + + int rv; + if (qc->serv_conn) { + rv = nghttp3_conn_server_new(&qc->http_conn, &callbacks, &settings, mem, hmux_sess->hmux_udta); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_server_new error %s\n", nghttp3_strerror(rv) )); + return GF_SERVICE_ERROR; + } + const ngtcp2_transport_params *params = ngtcp2_conn_get_local_transport_params(qc->conn); + nghttp3_conn_set_max_client_streams_bidi(qc->http_conn, params->initial_max_streams_bidi); + + } else { + rv = nghttp3_conn_client_new(&qc->http_conn, &callbacks, &settings, mem, hmux_sess->hmux_udta); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_client_new error %s\n", nghttp3_strerror(rv) )); + return GF_SERVICE_ERROR; + } + } + hmux_sess->connected = GF_TRUE; + s64 ctrl_stream_id; + rv = ngtcp2_conn_open_uni_stream(qc->conn, &ctrl_stream_id, NULL); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_open_uni_stream: ", ngtcp2_strerror(rv) )); + return GF_SERVICE_ERROR; + } + + rv = nghttp3_conn_bind_control_stream(qc->http_conn, ctrl_stream_id); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_bind_control_stream error %s\n", nghttp3_strerror(rv) )); + return GF_SERVICE_ERROR; + } + s64 qpack_enc_stream_id, qpack_dec_stream_id; + rv = ngtcp2_conn_open_uni_stream(qc->conn, &qpack_enc_stream_id, NULL); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_open_uni_stream error %s\n", ngtcp2_strerror(rv) )); + return GF_SERVICE_ERROR; + } + rv = ngtcp2_conn_open_uni_stream(qc->conn, &qpack_dec_stream_id, NULL); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_open_uni_stream error %s\n", ngtcp2_strerror(rv) )); + return GF_SERVICE_ERROR; + } + rv = nghttp3_conn_bind_qpack_streams(qc->http_conn, qpack_enc_stream_id, qpack_dec_stream_id); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_bind_qpack_streams error %s\n", nghttp3_strerror(rv) )); + return GF_SERVICE_ERROR; + } + return GF_OK; +} + +#define NGH3_HDR(_hdr, _name, _value) { \ + _hdr.name = (uint8_t *)_name;\ + _hdr.value = (uint8_t *)_value;\ + _hdr.namelen = (u32) strlen(_name);\ + _hdr.valuelen = (u32) strlen(_value);\ + _hdr.flags = NGHTTP3_NV_FLAG_NONE;\ + } + + +static GF_Err h3_submit_request(GF_DownloadSession *sess, char *req_name, const char *url, const char *param_string, Bool has_body) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)sess->hmux_sess->hmux_udta; + if (!qc->http_conn) + return GF_SERVICE_ERROR; + + u32 nb_hdrs, i; + char *hostport = NULL; + char *path = NULL; + char port20; + nghttp3_nv *hdrs; + + nb_hdrs = gf_list_count(sess->headers); + hdrs = gf_malloc(sizeof(nghttp3_nv) * (nb_hdrs + 4)); + if (!hdrs) return GF_OUT_OF_MEM; + + NGH3_HDR(hdrs0, ":method", req_name); + NGH3_HDR(hdrs1, ":scheme", "https"); + + gf_dynstrcat(&hostport, sess->server_name, NULL); + sprintf(port, ":%d", sess->port); + gf_dynstrcat(&hostport, port, NULL); + NGH3_HDR(hdrs2, ":authority", hostport); + + if (param_string) { + gf_dynstrcat(&path, url, NULL); + if (strchr(sess->remote_path, '?')) { + gf_dynstrcat(&path, param_string, "&"); + } else { + gf_dynstrcat(&path, param_string, "?"); + } + NGH3_HDR(hdrs3, ":path", path); + } else { + NGH3_HDR(hdrs3, ":path", url); + } + + for (i=0; i<nb_hdrs; i++) { + GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); + NGH3_HDR(hdrs4+i, hdr->name, hdr->value); + } + GF_QuicDataRead *qr = NULL; + + gf_assert((sess->hmux_stream_id<0) || sess->hmux_data_done); + qr = sess->hmux_priv; + gf_assert(qr->local_buf_sent == qr->local_buf_ack); + qr=NULL; + if (has_body) { + qr = (GF_QuicDataRead*)sess->hmux_priv; + gf_assert(qr && qr->data_read.read_data); + } + sess->hmux_data_done = 0; + sess->hmux_headers_seen = 0; + sess->hmux_ready_to_send = 0; + GF_Err e = GF_OK; + int rv = ngtcp2_conn_open_bidi_stream(qc->conn, &sess->hmux_stream_id, sess); + if (rv != 0) { + e = GF_SERVICE_ERROR; + } + + if (!e) { + rv = nghttp3_conn_submit_request(qc->http_conn, sess->hmux_stream_id, hdrs, nb_hdrs+4, + qr ? &qr->data_read : NULL, sess); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_submit_request error %s\n", nghttp3_strerror(rv) )); + e = GF_SERVICE_ERROR; + } + } + +#ifndef GPAC_DISABLE_LOG + if (!e && gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_DEBUG)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/3 send request (has_body %d) for new stream_id "LLD":\n", has_body, sess->hmux_stream_id)); + for (i=0; i<nb_hdrs+4; i++) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("\t%s: %s\n", hdrsi.name, hdrsi.value)); + } + } +#endif + + gf_free(hdrs); + gf_free(hostport); + if (path) gf_free(path); + + if (e) + return e; + + if (sess->hmux_stream_id < 0) { + return GF_IP_NETWORK_FAILURE; + } + return h3_session_write_ex(sess, NULL); +} + + +static GF_Err h3_data_pending(GF_DownloadSession *sess) +{ + //always move pending data to local buffer + if (sess->hmux_send_data) { + gf_mx_p(sess->mx); + GF_QuicDataRead *qr = sess->hmux_priv; + Bool use_main = GF_FALSE; + if (!qr->second_local_buf_len) { + if (!sess->local_buf_alloc) + use_main = GF_TRUE; + else if (sess->local_buf_len + sess->hmux_send_data_len < sess->local_buf_alloc) + use_main = GF_TRUE; + } + + if (use_main) gf_assert(qr->second_local_buf_len==0); + + u8 **buf = use_main ? &sess->local_buf : &qr->second_local_buf; + u32 *size = use_main ? &sess->local_buf_len : &qr->second_local_buf_len; + u32 *allocated = use_main ? &sess->local_buf_alloc : &qr->second_local_buf_alloc; + + if (! *buf || *size + sess->hmux_send_data_len > *allocated) { + *allocated = *size + sess->hmux_send_data_len; + if (*allocated < SOCK_BUF_SIZE/2) *allocated = SOCK_BUF_SIZE/2; + *buf = gf_realloc(*buf, *allocated); + if (! *buf) { + gf_mx_v(sess->mx); + return GF_OUT_OF_MEM; + } + } + memcpy(*buf + *size, sess->hmux_send_data, sess->hmux_send_data_len); + *size += sess->hmux_send_data_len; + sess->hmux_send_data_len = 0; + sess->hmux_send_data = NULL; + gf_mx_v(sess->mx); + if (sess->hmux_data_paused) { + sess->hmux_data_paused = 0; + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + nghttp3_conn_resume_stream(qc->http_conn, sess->hmux_stream_id); + } + } + return h3_session_write_ex(sess, NULL); +} + + +static GF_Err h3_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body) +{ + GF_QuicConnection *qc = (GF_QuicConnection *)sess->hmux_sess->hmux_udta; + nghttp3_nv *hdrs; + char szFmt20; + + u32 i, count = gf_list_count(sess->headers); + + if (response_body) { + no_body = GF_FALSE; + sess->hmux_send_data = (u8 *) response_body; + sess->hmux_send_data_len = body_len; + sess->hmux_is_eos = 1; + } else if (!no_body) { + switch (reply_code) { + case 200: + case 206: + no_body = GF_FALSE; + sess->hmux_is_eos = 0; + break; + default: + no_body = GF_TRUE; + break; + } + } + + hdrs = gf_malloc(sizeof(nghttp3_nv) * (count + 1) ); + + sprintf(szFmt, "%d", reply_code); + NGH3_HDR(hdrs0, ":status", szFmt); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("HTTP/3 send reply for stream_id "LLD" (body %d) headers:\n:status: %s\n", sess->hmux_stream_id, !no_body, szFmt)); + for (i=0; i<count; i++) { + GF_HTTPHeader *hdr = gf_list_get(sess->headers, i); + NGH3_HDR(hdrsi+1, hdr->name, hdr->value) + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("%s: %s\n", hdr->name, hdr->value)); + } + + GF_QuicDataRead *qr = NULL; + if (!no_body) { + if (!sess->hmux_priv) + h3_setup_session(sess, GF_FALSE); + qr = sess->hmux_priv; + gf_assert(qr && qr->data_read.read_data); + } + + gf_mx_p(sess->mx); + + int rv = nghttp3_conn_submit_response(qc->http_conn, sess->hmux_stream_id, hdrs, (count+1), no_body ? NULL : &qr->data_read); + gf_free(hdrs); + if (rv != 0) { + gf_mx_v(sess->mx); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_submit_response error: %s\n", nghttp3_strerror(rv) )); + return GF_BAD_PARAM; + } + + //in case we have a body already setup with this reply + //if none, this will call write anyway + h3_data_pending(sess); + + gf_mx_v(sess->mx); + + return GF_OK; +} + +static GF_Err h3_session_write_ex(GF_DownloadSession *sess, GF_QuicConnection *qc) +{ + GF_Err e, ret; + if (!qc) qc = (GF_QuicConnection *)sess->hmux_sess->hmux_udta; + if (!qc) return GF_BAD_PARAM; + if (qc->serv_conn && qc->serv_conn->closed) { + sess->local_buf_len = 0; + GF_QuicDataRead *qr = sess->hmux_priv; + qr->second_local_buf_len = 0; + return GF_IP_CONNECTION_CLOSED; + } + + //flush blocked packet if any + if (qc->cur_buf_len) { + if (qc->serv_conn) { + e = quic_send_packet(qc->serv_conn->server, qc->cur_buf, qc->cur_buf_len, (const u8 *) qc->cur_path.path.remote.addr, (u32) qc->cur_path.path.remote.addrlen); + } else if (sess && sess->sock) { + u32 written=0; + e = gf_sk_send_ex(sess->sock, qc->cur_buf, qc->cur_buf_len, &written); + } else { + e = GF_IP_CONNECTION_CLOSED; + } + if (e==GF_IP_NETWORK_EMPTY) { + return GF_IP_NETWORK_EMPTY; + } + qc->cur_buf_len = 0; + } + ret = GF_IP_NETWORK_EMPTY; + + u32 max_pay_size = ngtcp2_conn_get_max_tx_udp_payload_size(qc->conn); + u32 path_max_udp_size = ngtcp2_conn_get_path_max_tx_udp_payload_size(qc->conn); + u64 ts = ngtcp2_timestamp(); + + ngtcp2_pkt_info pi; + u32 pay_size=0; + + ngtcp2_path_storage_zero(&qc->cur_path); + for (;;) { + s64 stream_id = -1; + s32 fin = 0; + nghttp3_vec vec256; + u32 nb_vec=0; + vec0.len = 0; + vec0.base = NULL; + + if (qc->http_conn && ngtcp2_conn_get_max_data_left(qc->conn)) { + s32 rv = nghttp3_conn_writev_stream(qc->http_conn, &stream_id, &fin, vec, GF_ARRAY_LENGTH(vec) ); + if (rv < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_writev_stream error %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + + return quic_handle_error(qc); + } + nb_vec = (u32) rv; + } + ngtcp2_ssize ndatalen; + u32 flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + u32 buflen = pay_size >= max_pay_size ? max_pay_size : path_max_udp_size; + + ndatalen=0; + ngtcp2_ssize nwrite = ngtcp2_conn_writev_stream(qc->conn, &qc->cur_path.path, &pi, + qc->cur_buf, buflen, &ndatalen, flags, + stream_id, (ngtcp2_vec*)vec, nb_vec, + ts + ); + + if (nwrite < 0) { + s32 rv; + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + nghttp3_conn_block_stream(qc->http_conn, stream_id); + continue; + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + nghttp3_conn_shutdown_stream_write(qc->http_conn, stream_id); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + rv = nghttp3_conn_add_write_offset(qc->http_conn, stream_id, ndatalen); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 Error in nghttp3_conn_add_write_offset: %s\n", nghttp3_strerror(rv))); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + if (!qc->handshake_state) qc->handshake_state = QUIC_HANDSHAKE_ERROR; + + return quic_handle_error(qc); + } + continue; + } + assert(ndatalen == -1); + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC Error in ngtcp2_conn_write_stream: %s\n", ngtcp2_strerror(nwrite) )); + ngtcp2_ccerr_set_liberr(&qc->last_error, nwrite, NULL, 0); + if (!qc->handshake_state) qc->handshake_state = QUIC_HANDSHAKE_ERROR; + return quic_handle_error(qc); + } + + if (ndatalen >= 0) { + s32 rv = nghttp3_conn_add_write_offset(qc->http_conn, stream_id, ndatalen); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 Error in nghttp3_conn_add_write_offset: %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + if (!qc->handshake_state) qc->handshake_state = QUIC_HANDSHAKE_ERROR; + return quic_handle_error(qc); + } + } + + if (nwrite == 0) { + ngtcp2_conn_update_pkt_tx_time(qc->conn, ts); + return ret; + } + + if (qc->serv_conn) { + e = quic_send_packet(qc->serv_conn->server, qc->cur_buf, nwrite, (const u8 *) qc->cur_path.path.remote.addr, (u32) qc->cur_path.path.remote.addrlen); + } else if (sess && sess->sock) { + u32 written=0; + e = gf_sk_send_ex(sess->sock, qc->cur_buf, nwrite, &written); + } else { + e = GF_IP_CONNECTION_CLOSED; + } + + if (e==GF_IP_NETWORK_EMPTY) { + qc->cur_buf_len = (u32) nwrite; + break; + } + if (e) { + break; + } + ret = GF_OK; + } + return ret; +} +static GF_Err h3_session_write(GF_DownloadSession *sess) +{ + return h3_session_write_ex(sess, NULL); +} + +static void h3_stream_reset(GF_DownloadSession *sess, Bool is_abort) +{ + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + if (is_abort && qc->http_conn && (sess->hmux_stream_id>=0)) { + nghttp3_conn_close_stream(qc->http_conn, sess->hmux_stream_id, NGHTTP3_H3_REQUEST_INCOMPLETE); + } +} + +static GF_Err h3_resume_stream(GF_DownloadSession *sess) +{ + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + GF_QuicDataRead *qr = sess->hmux_priv; + if (!qc->http_conn || (sess->hmux_stream_id<0) || (qc->serv_conn && qc->serv_conn->closed)) + return GF_IP_CONNECTION_CLOSED; + + if (qr->wait_ack) return GF_OK; + + sess->hmux_data_paused = 0; + nghttp3_conn_resume_stream(qc->http_conn, sess->hmux_stream_id); + + return GF_OK; +} + +static void h3_close(struct _http_mux_session *hmux) +{ + GF_QuicConnection *qc = hmux->hmux_udta; + if (!qc->conn || !hmux->net_sess || !hmux->net_sess->sock + || ngtcp2_conn_in_closing_period(qc->conn) + || ngtcp2_conn_in_draining_period(qc->conn) + ) { + return; + } + ngtcp2_path_storage ps; + ngtcp2_path_storage_zero(&ps); + ngtcp2_pkt_info pi; + u8 bufferNGTCP2_MAX_UDP_PAYLOAD_SIZE; + + int nwrite = ngtcp2_conn_write_connection_close(qc->conn, &ps.path, &pi, buffer, NGTCP2_MAX_UDP_PAYLOAD_SIZE, &qc->last_error, ngtcp2_timestamp() ); + + if (nwrite > 0) { + u32 written; + gf_sk_send_ex(hmux->net_sess->sock, buffer, nwrite, &written); + } +} + +static void h3_destroy(struct _http_mux_session *hmux) +{ + GF_QuicConnection *qc = hmux->hmux_udta; + if (qc->http_conn) + nghttp3_conn_del(qc->http_conn); + ngtcp2_conn_del(qc->conn); + if (qc->path.local.addr) gf_free(qc->path.local.addr); + if (qc->path.remote.addr) gf_free(qc->path.remote.addr); + if (qc->serv_conn) { + gf_list_del_item(qc->serv_conn->server->connections, qc->serv_conn); + gf_free(qc->serv_conn); + } + gf_free(qc); +} + +static GF_Err h3_async_flush(GF_DownloadSession *sess, Bool for_close) +{ + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + if (qc->serv_conn && qc->serv_conn->closed) + return GF_IP_CONNECTION_CLOSED; + + //QUIC, resume data sending, send packets and check if we are done pushing local copy + //(we always do a local copy to deal with retransmits) + gf_mx_p(sess->mx); + if (!sess->server_mode) { + u8 buf1500; + u32 res; + //read any frame pending from remote peer (ack, window update and co) + GF_Err e = gf_dm_read_data(sess, buf, 1500, &res); + if ((e<0) && (e != GF_IP_NETWORK_EMPTY)) { + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(sess->last_error) + return e; + } + } + h3_data_pending(sess); + gf_mx_v(sess->mx); + + GF_QuicDataRead *qr = sess->hmux_priv; + if (for_close) { + if (qr->local_buf_sent>qr->local_buf_ack) + return GF_IP_NETWORK_EMPTY; + } + + if (!sess->local_buf_len && !sess->hmux_is_eos) return GF_OK; + gf_assert(sess->hmux_send_data==NULL); + + //EOS reached, we're done + if (sess->hmux_data_done) { + return GF_OK; + } + //EOS signaled on session, wait for everything to be acked + if (sess->hmux_is_eos && (sess->local_buf_len || qr->second_local_buf_len)) { + return GF_IP_NETWORK_EMPTY; + } + + if (sess->local_buf_len + qr->second_local_buf_len > SOCK_BUF_SIZE) { + return GF_IP_NETWORK_EMPTY; + } + return GF_OK; +} + +static GF_Err h3_data_received(GF_DownloadSession *sess, const u8 *data, u32 nb_bytes) +{ + if (nb_bytes) { + GF_QuicConnection *qc = ( GF_QuicConnection *) sess->hmux_sess->hmux_udta; + ngtcp2_path path; + ngtcp2_pkt_info pi = {0}; + + path = qc->path; + + int rv = ngtcp2_conn_read_pkt(qc->conn, &path, &pi, data, (size_t)nb_bytes, ngtcp2_timestamp() ); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv) )); + if (!qc->last_error.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_ccerr_set_tls_alert(&qc->last_error, ngtcp2_conn_get_tls_alert(qc->conn), NULL, 0); + } else { + ngtcp2_ccerr_set_liberr(&qc->last_error, rv, NULL, 0); + } + } + if (!qc->handshake_state) qc->handshake_state = QUIC_HANDSHAKE_ERROR; + return GF_IP_NETWORK_FAILURE; + } + } + /* send pending frames - hmux_sess may be NULL at this point if the connection was reset during processing of the above + this typically happens if we have a refused stream + */ + h3_session_write(sess); + + return GF_OK; +} +static ngtcp2_conn *ngq_get_conn(ngtcp2_crypto_conn_ref *conn_ref) +{ + GF_QuicConnection *qc = conn_ref->user_data; + return qc->conn; +} +static int ngq_handshake_completed(ngtcp2_conn *conn, void *user_data) +{ + GF_QuicConnection *qc = user_data; + if (qc->handshake_state<QUIC_HANDSHAKE_DONE) { + qc->handshake_state = QUIC_HANDSHAKE_DONE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC Handshake done\n")); + } + return 0; +} + +int ngq_recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) +{ + GF_QuicConnection *qc = user_data; + if (!qc->http_conn) return -1; + + int rv = nghttp3_conn_read_stream(qc->http_conn, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + if (rv < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_read_stream error %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + ngtcp2_conn_extend_max_stream_offset(qc->conn, stream_id, rv); + ngtcp2_conn_extend_max_offset(qc->conn, rv); + return 0; +} + +static int ngq_acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = user_data; + int rv = nghttp3_conn_add_ack_offset(qc->http_conn, stream_id, datalen); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_add_ack_offset error %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngq_extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = user_data; + int rv = nghttp3_conn_unblock_stream(qc->http_conn, stream_id); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_unblock_stream error %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static int ngq_recv_rx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level, void *user_data) +{ + GF_QuicConnection *qc = user_data; + if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) return 0; + if (qc->http_conn) return GF_OK; + + GF_Err e = h3_setup_http3(qc->hmux); + if (e) return NGTCP2_ERR_CALLBACK_FAILURE; + return 0; +} + +static int ngq_stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = user_data; + if (!qc->http_conn) return 0; + int rv = nghttp3_conn_shutdown_stream_read(qc->http_conn, stream_id); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_shutdown_stream_read error %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + + +static int ngq_stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = user_data; +// GF_DownloadSession *sess = stream_user_data; + if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + if (!qc->http_conn) return 0; + + if (app_error_code == 0) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + int rv = nghttp3_conn_close_stream(qc->http_conn, stream_id, app_error_code); + switch (rv) { + case 0: + break; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(qc->conn, stream_id)); + ngtcp2_conn_extend_max_streams_uni(qc->conn, 1); + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_close_stream error %s\n", nghttp3_strerror(rv) )); + ngtcp2_ccerr_set_application_error(&qc->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngq_stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + GF_QuicConnection *qc = user_data; + if (!qc->http_conn) return 0; + + int rv = nghttp3_conn_shutdown_stream_read(qc->http_conn, stream_id); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTP/3 nghttp3_conn_shutdown_stream_read error %s\n", nghttp3_strerror(rv) )); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static int ngq_stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) +{ + //GF_QuicConnection *qc = user_data; + if (!ngtcp2_is_bidi_stream(stream_id)) { + return 0; + } + return 0; +} + +static void h3_close_session(GF_DownloadSession *sess) +{ + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + if (!qc->http_conn) return; + if (qc->serv_conn && qc->serv_conn->closed) return; + + nghttp3_conn_submit_shutdown_notice(qc->http_conn); + h3_session_write(sess); + nghttp3_conn_shutdown(qc->http_conn); + h3_session_write(sess); + + sess->status = GF_NETIO_DISCONNECTED; +} + +static GF_Err h3_initialize(GF_DownloadSession *sess, char *server, u32 server_port, + const ngtcp2_pkt_hd *srv_hd, const ngtcp2_cid *srv_ocid, u32 token_type, ngtcp2_path *srv_path, GF_QuicServerConnection *qsc) +{ + GF_QuicConnection *ng_quic; + GF_Err e; + + if (!sess->server_mode && sess->dm && (!sess->dm->ssl_ctx || !sess->ssl)) { + gf_ssl_try_connect(sess, NULL); + if (!sess->ssl) return GF_IO_ERR; + } else if (!sess->ssl) { + return GF_IP_CONNECTION_FAILURE; + } + + GF_SAFEALLOC(sess->hmux_sess, GF_HMUX_Session); + if (!sess->hmux_sess) return GF_OUT_OF_MEM; + GF_SAFEALLOC(ng_quic, GF_QuicConnection); + if (!sess->hmux_sess) { + gf_free(sess->hmux_sess); + sess->hmux_sess = NULL; + return GF_OUT_OF_MEM; + } + ng_quic->serv_conn = qsc; + ng_quic->hmux = sess->hmux_sess; + sess->hmux_sess->hmux_udta = ng_quic; + ng_quic->conn_ref.get_conn = ngq_get_conn; + ng_quic->conn_ref.user_data = ng_quic; + RAND_bytes(ng_quic->secret, 32); + + SSL_set_app_data(sess->ssl, &ng_quic->conn_ref); + + if (qsc) qsc->hmux = sess->hmux_sess; + + if (sess->server_mode) { + u8 *buf; + buf = gf_malloc(sizeof(u8) * srv_path->local.addrlen); + memcpy(buf, srv_path->local.addr, srv_path->local.addrlen); + ng_quic->path.local.addr = (ngtcp2_sockaddr *) buf; + ng_quic->path.local.addrlen = srv_path->local.addrlen; + + buf = gf_malloc(sizeof(u8) * srv_path->remote.addrlen); + memcpy(buf, srv_path->remote.addr, srv_path->remote.addrlen); + ng_quic->path.remote.addr = (ngtcp2_sockaddr *) buf; + ng_quic->path.remote.addrlen = srv_path->remote.addrlen; + } else { + u8 *src, *dst; + u32 src_len, dst_len; + + //bind to first avail port - we don't use port reuse here as we could bind two clients + //to the same server on a single machine, blocking the second client connection + u32 loc_port = 1234; + while (loc_port<0xFFFF) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC try binding port %u\n", loc_port)); + e = gf_sk_bind_ex(sess->sock, "127.0.0.1", loc_port, server, server_port, 0, &dst, &dst_len, &src, &src_len); + if (e==GF_IP_CONNECTION_FAILURE) { + loc_port++; + continue; + } + break; + } + //UDP connect to make sure we only get datagrams for ourselves + if (!e) { + e = gf_sk_connect_ex(sess->sock, (char *) server, server_port, NULL, GF_TRUE); + } + if (e) { + if (src) gf_free(src); + if (dst) gf_free(dst); + goto err; + } + + ng_quic->path.local.addr = (ngtcp2_sockaddr*)src; + ng_quic->path.local.addrlen = src_len; + ng_quic->path.remote.addr = (ngtcp2_sockaddr*)dst; + ng_quic->path.remote.addrlen = dst_len; + } + + + //setup callbacks + ngtcp2_callbacks callbacks = { + .handshake_completed = ngq_handshake_completed, + .recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb, + .encrypt = ngtcp2_crypto_encrypt_cb, + .decrypt = ngtcp2_crypto_decrypt_cb, + .hp_mask = ngtcp2_crypto_hp_mask_cb, + .recv_stream_data = ngq_recv_stream_data, + .acked_stream_data_offset = ngq_acked_stream_data_offset, + .extend_max_local_streams_bidi = ngq_extend_max_local_streams_bidi, + .rand = ngq_rand_cb, + .extend_max_stream_data = ngq_extend_max_stream_data, + .update_key = ngtcp2_crypto_update_key_cb, + .delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb, + .delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + .get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb, + .version_negotiation = ngtcp2_crypto_version_negotiation_cb, + .get_new_connection_id = ngq_get_new_connection_id, + .stream_close = ngq_stream_close, + .stream_reset = ngq_stream_reset, + .stream_stop_sending = ngq_stream_stop_sending, + }; + + if (sess->server_mode) { + callbacks.recv_client_initial = ngtcp2_crypto_recv_client_initial_cb; + callbacks.recv_tx_key = ngq_recv_rx_key; + callbacks.stream_open = ngq_stream_open; + callbacks.remove_connection_id = ngq_remove_connection_id; + //todo ? +// .path_validation = path_validation, + } else { + callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb; + callbacks.client_initial = ngtcp2_crypto_client_initial_cb; + callbacks.recv_rx_key = ngq_recv_rx_key; + //todo ? +// callbacks.recv_version_negotiation = ::recv_version_negotiation, +// callbacks.path_validation = path_validation, +// callbacks.select_preferred_addr = ::select_preferred_address, +// callbacks.handshake_confirmed = ::handshake_confirmed, +// callbacks.recv_new_token = ::recv_new_token, +// callbacks.tls_early_data_rejected = ::early_data_rejected, + } + + + ngtcp2_cid scid; + ngtcp2_settings settings; + ngtcp2_transport_params params; + int rv; + + if (sess->server_mode) + scid.datalen = NGTCP2_SV_SCIDLEN; + else + scid.datalen = 17; + if (RAND_bytes(scid.data, (int)scid.datalen) != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC Failed to get random data for source connection ID\n")); + e = GF_IO_ERR; + goto err; + } + + ngtcp2_settings_default(&settings); + + if (gf_opts_get_bool("core", "h3-trace")) { + settings.log_printf = ngq_printf; + nghttp3_set_debug_vprintf_callback(ngq_debug_trace); + } else { + nghttp3_set_debug_vprintf_callback(ngq_debug_trace_noop); + } + + settings.initial_ts = ngtcp2_timestamp(); + settings.cc_algo = NGTCP2_CC_ALGO_CUBIC; + settings.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + settings.max_window = 0; + settings.max_stream_window = 0; + settings.handshake_timeout = sess->conn_timeout*1000000; + settings.handshake_timeout = UINT64_MAX; + settings.no_pmtud = 0; + settings.ack_thresh = 2; + settings.initial_pkt_num = 1; + + //server config + if (sess->server_mode) { + settings.token = srv_hd->token; + settings.tokenlen = srv_hd->tokenlen; + settings.token_type = token_type; + settings.max_window = 6000000; + settings.max_stream_window = 6000000; + } + + ngtcp2_transport_params_default(¶ms); + + params.initial_max_data = 1024 * 1024; + params.initial_max_stream_data_bidi_local = sess->server_mode ? 0 : 16777216; + params.initial_max_stream_data_bidi_remote = sess->server_mode ? 16777216 : 0; + params.initial_max_stream_data_uni = 16777216; + params.initial_max_streams_bidi = sess->server_mode ? 100 : 0; + params.initial_max_streams_uni = 3; + params.max_idle_timeout = 30 * NGTCP2_SECONDS; + params.active_connection_id_limit = 7; + params.grease_quic_bit = 1; + + if (sess->server_mode) { + params.stateless_reset_token_present = 1; + if (srv_ocid) { + params.original_dcid = *srv_ocid; + params.retry_scid = srv_hd->dcid; + params.retry_scid_present = 1; + } else { + params.original_dcid = srv_hd->dcid; + } + params.original_dcid_present = 1; + + rv = ngtcp2_crypto_generate_stateless_reset_token(params.stateless_reset_token, qsc->server->secret, GF_SHA256_DIGEST_SIZE, &scid); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_crypto_generate_stateless_reset_token error %s\n", ngtcp2_strerror(rv) )); + e = GF_IP_CONNECTION_FAILURE; + goto err; + } + ng_quic->serv_conn->conn_ids0.dcid_len = scid.datalen; + memcpy(ng_quic->serv_conn->conn_ids0.dcid, scid.data, scid.datalen); + + rv = ngtcp2_conn_server_new(&ng_quic->conn, &srv_hd->scid, &scid, &ng_quic->path, NGTCP2_PROTO_VER_V1, + &callbacks, &settings, ¶ms, NULL, ng_quic); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_server_new error %s\n", ngtcp2_strerror(rv) )); + e = GF_IP_CONNECTION_FAILURE; + goto err; + } + } else { + ngtcp2_cid dcid; + dcid.datalen = 18; + if (RAND_bytes(dcid.data, (int)dcid.datalen) != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC Failed to get random data for destination connection ID\n")); + e = GF_IO_ERR; + goto err; + } + + + rv = ngtcp2_conn_client_new(&ng_quic->conn, &dcid, &scid, &ng_quic->path, NGTCP2_PROTO_VER_V1, + &callbacks, &settings, ¶ms, NULL, ng_quic); + if (rv != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_client_new error %s\n", ngtcp2_strerror(rv) )); + e = GF_IP_CONNECTION_FAILURE; + goto err; + } + } + + ngtcp2_conn_set_tls_native_handle(ng_quic->conn, sess->ssl); + + //set hmux callbacks + sess->hmux_sess->sessions = gf_list_new(); + sess->hmux_sess->net_sess = sess; + sess->hmux_sess->submit_request = h3_submit_request; + sess->hmux_sess->send_reply = h3_send_reply; + sess->hmux_sess->write = h3_session_write; + sess->hmux_sess->close = h3_close; + sess->hmux_sess->destroy = h3_destroy; + sess->hmux_sess->stream_reset = h3_stream_reset; + sess->hmux_sess->resume = h3_resume_stream; + sess->hmux_sess->data_received = h3_data_received; + sess->hmux_sess->setup_session = h3_setup_session; + sess->hmux_sess->send_pending_data = h3_data_pending; + sess->hmux_sess->async_flush = h3_async_flush; + sess->hmux_sess->close_session = h3_close_session; + + h3_setup_session(sess, GF_FALSE); + + gf_list_add(sess->hmux_sess->sessions, sess); + + if (!sess->mx) { + char szMXName100; + sprintf(szMXName, "http3_%p", sess->hmux_sess); + sess->hmux_sess->mx = gf_mx_new(szMXName); + sess->mx = sess->hmux_sess->mx; + } else { + sess->hmux_sess->mx = sess->mx; + } + sess->chunked = GF_FALSE; + //don't flush packets now in server mode + if (qsc) return GF_OK; + + return h3_session_write(sess); + +err: + gf_free(sess->hmux_sess); + sess->hmux_sess = NULL; + if (ng_quic->path.local.addr) gf_free(ng_quic->path.local.addr); + if (ng_quic->path.remote.addr) gf_free(ng_quic->path.remote.addr); + gf_free(ng_quic); + return e; +} + +GF_Err http3_connect(GF_DownloadSession *sess, char *server, u32 server_port) +{ + if (!sess->hmux_sess) { + GF_Err e = h3_initialize(sess, server, server_port, NULL, NULL, 0, NULL, NULL); + if (e) return e; + + return GF_IP_NETWORK_EMPTY; + } + if (!sess->hmux_sess) + return GF_IP_CONNECTION_CLOSED; + + u32 res; + GF_Err e = gf_dm_read_data(sess, sess->http_buf, sess->http_buf_size, &res); + if (e) return e; + + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + if (qc->handshake_state==QUIC_HANDSHAKE_ERROR) + return GF_IP_CONNECTION_FAILURE; + if (qc->handshake_state<QUIC_HANDSHAKE_DONE) return GF_IP_NETWORK_EMPTY; + + return h3_setup_http3(sess->hmux_sess); +} + + + +void gf_dm_sess_set_callback(GF_DownloadSession *sess, gf_dm_user_io user_io, void *usr_cbk) +{ + if (!sess) return; + sess->user_proc = user_io; + sess->usr_cbk = usr_cbk; +} + +static Bool ngtcp2_tls_init=GF_FALSE; + +GF_Err gf_dm_quic_server_new(GF_DownloadManager *dm, void *ssl_ctx, GF_QuicServer **oq, const char *ip, u32 port, const char *netcap_id, + Bool (*accept_conn)(void *udta, GF_DownloadSession *sess, const char *address, u32 port), + void *udta) +{ + GF_QuicServer *tmp; + if (!dm || !ssl_ctx || !accept_conn || !port) return GF_BAD_PARAM; + if (!ngtcp2_tls_init) { + if (ngtcp2_crypto_quictls_init() != 0) { + return GF_IO_ERR; + } + ngtcp2_tls_init=GF_TRUE; + } + + GF_SAFEALLOC(tmp, GF_QuicServer); + if (!tmp) return GF_OUT_OF_MEM; + tmp->sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, netcap_id); + gf_sk_set_block_mode(tmp->sock, GF_TRUE); + GF_Err e = gf_sk_bind_ex(tmp->sock, NULL, port, ip, 0, GF_SOCK_REUSE_PORT, NULL, NULL, &tmp->local_add, &tmp->local_add_len); + if (e) { + gf_sk_del(tmp->sock); + gf_free(tmp); + return e; + } + gf_sk_set_block_mode(tmp->sock, GF_TRUE); + gf_sk_server_mode(tmp->sock, GF_TRUE); + tmp->connections = gf_list_new(); + + u8 buf50; + RAND_bytes(buf, 50); + gf_sha256_csum(buf, 50, tmp->secret); + tmp->stateless_reset_count = NGTCP2_STATELESS_RESET_BURST; + + tmp->ssl_ctx = ssl_ctx; + tmp->dm = dm; + tmp->accept_conn = accept_conn; + tmp->udta = udta; + + *oq = tmp; + return GF_OK; +} +void gf_dm_quic_server_del(GF_QuicServer *qs) +{ + gf_list_del(qs->connections); + if (qs->local_add) gf_free(qs->local_add); + gf_sk_del(qs->sock); + gf_free(qs); +} +GF_Socket *gf_dm_quic_get_socket(GF_QuicServer *qs) +{ + return qs ? qs->sock : NULL; +} + +static int quic_verify_token(GF_QuicServer *qs, const ngtcp2_pkt_hd *hd, const u8 *sa, u32 salen) +{ + u64 ts = ngtcp2_timestamp(); + if (ngtcp2_crypto_verify_regular_token(hd->token, hd->tokenlen, + qs->secret, GF_SHA256_DIGEST_SIZE, + (const ngtcp2_sockaddr *)sa, (ngtcp2_socklen) salen, + 3600 * NGTCP2_SECONDS, ts) != 0) { + return -1; + } + return 0; +} +static int quic_verify_retry_token(GF_QuicServer *qs, ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, const u8 *sa, u32 salen) +{ + u64 ts = ngtcp2_timestamp(); + int rv = ngtcp2_crypto_verify_retry_token2( + ocid, hd->token, hd->tokenlen, + qs->secret, GF_SHA256_DIGEST_SIZE, + hd->version, (const ngtcp2_sockaddr *)sa, (ngtcp2_socklen) salen, &hd->dcid, + 10 * NGTCP2_SECONDS, ts); + + switch (rv) { + case 0: + break; + case NGTCP2_CRYPTO_ERR_VERIFY_TOKEN: + return -1; + default: + return 1; + } + return 0; +} + +static GF_Err quic_send_packet(GF_QuicServer *qs, const u8 *buf, u32 len, const u8 *to_a, u32 to_alen) +{ + u32 written; + GF_Err e = gf_sk_send_to(qs->sock, buf, len, to_a, to_alen, &written); + if (e) + return e; + if (written!=len) { + return GF_IP_NETWORK_EMPTY; + } + return GF_OK; +} + +static int quic_send_retry(GF_QuicServer *qs, const ngtcp2_pkt_hd *chd, const u8 *sa, u32 salen, u32 max_pktlen) +{ + ngtcp2_cid scid; + scid.datalen = NGTCP2_SV_SCIDLEN; + if (RAND_bytes(scid.data, scid.datalen) != 1) { + return -1; + } + u8 tokenNGTCP2_CRYPTO_MAX_RETRY_TOKENLEN2; + u64 ts = ngtcp2_timestamp(); + + int tokenlen = ngtcp2_crypto_generate_retry_token2(token, qs->secret, GF_SHA256_DIGEST_SIZE, chd->version, + (const ngtcp2_sockaddr *)sa, (ngtcp2_socklen) salen, &scid, &chd->dcid, ts); + + if (tokenlen < 0) { + return -1; + } + + u32 blen = MIN(NGTCP2_MAX_UDP_PAYLOAD_SIZE, max_pktlen); + u8 bufNGTCP2_MAX_UDP_PAYLOAD_SIZE; + + int nwrite = ngtcp2_crypto_write_retry(buf, blen, chd->version, &chd->scid, &scid, &chd->dcid, token, tokenlen); + if (nwrite < 0) { + return -1; + } + GF_Err e = quic_send_packet(qs, buf, blen, sa, salen); + if (e) return -1; + return 0; +} + +static int quic_send_stateless_reset(GF_QuicServer *qs, u32 pktlen, const u8 *dcid, u32 dcid_len, const u8 *sa, u32 salen) +{ + if (qs->stateless_reset_count == 0) return 0; + qs->stateless_reset_count--; + + ngtcp2_cid cid; + ngtcp2_cid_init(&cid, dcid, dcid_len); + u8 tokenNGTCP2_STATELESS_RESET_TOKENLEN; + + if (ngtcp2_crypto_generate_stateless_reset_token(token, qs->secret, GF_SHA256_DIGEST_SIZE, &cid) != 0) { + return -1; + } + + u32 max_rand_byteslen = NGTCP2_MAX_CIDLEN + 22 - NGTCP2_STATELESS_RESET_TOKENLEN; + u8 rand_bytesNGTCP2_MAX_CIDLEN + 22 - NGTCP2_STATELESS_RESET_TOKENLEN; + u32 rand_byteslen; + + if (pktlen <= 43) { + rand_byteslen = pktlen - NGTCP2_STATELESS_RESET_TOKENLEN - 1; + } else { + rand_byteslen = max_rand_byteslen; + } + + if (RAND_bytes(rand_bytes, rand_byteslen) != 1) { + return -1; + } + + u8 bufNGTCP2_MAX_UDP_PAYLOAD_SIZE; + int nwrite = ngtcp2_pkt_write_stateless_reset(buf, NGTCP2_MAX_UDP_PAYLOAD_SIZE, token, rand_bytes, rand_byteslen); + if (nwrite < 0) { + return -1; + } + GF_Err e = quic_send_packet(qs, buf, nwrite, sa, salen); + if (e) return -1; + return 0; +} + +static int quic_send_stateless_connection_close(GF_QuicServer *qs, const ngtcp2_pkt_hd *chd, const u8 *sa, u32 salen) +{ + u8 bufNGTCP2_MAX_UDP_PAYLOAD_SIZE; + int nwrite = ngtcp2_crypto_write_connection_close(buf, NGTCP2_MAX_UDP_PAYLOAD_SIZE, chd->version, &chd->scid, &chd->dcid, NGTCP2_INVALID_TOKEN, NULL, 0); + if (nwrite < 0) return -1; + GF_Err e = quic_send_packet(qs, buf, nwrite, sa, salen); + if (e) return -1; + return 0; +} + +void quic_start_draining_period(GF_QuicConnection *qc) +{ + qc->serv_conn->drain_period_end = gf_sys_clock_high_res(); + qc->serv_conn->drain_period_end += ngtcp2_conn_get_pto(qc->conn)/1000; + qc->serv_conn->closed = GF_TRUE; +} + +Bool quic_start_closing_period(GF_QuicConnection *qc) +{ + if (!qc->conn) return GF_TRUE; + if (ngtcp2_conn_in_closing_period(qc->conn) || ngtcp2_conn_in_draining_period(qc->conn)) { + return GF_TRUE; + } + + ngtcp2_path_storage ps; + ngtcp2_path_storage_zero(&ps); + ngtcp2_pkt_info pi; + int n = ngtcp2_conn_write_connection_close(qc->conn, &ps.path, &pi, qc->serv_conn->closebuf, NGTCP2_MAX_UDP_PAYLOAD_SIZE, &qc->last_error, ngtcp2_timestamp()); + if (n<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_write_connection_close error %s\n", ngtcp2_strerror(n) )); + return GF_FALSE; + } + qc->serv_conn->closebuf_len = n; + + qc->serv_conn->closed = GF_TRUE; + qc->serv_conn->close_period_end = gf_sys_clock_high_res(); + qc->serv_conn->close_period_end += ngtcp2_conn_get_pto(qc->conn)/1000; //(*3); + return GF_TRUE; +} + +static int quic_send_conn_close(GF_QuicConnection *qc) +{ + assert(qc->serv_conn && qc->serv_conn->closebuf_len); + assert(qc->conn); + assert(!ngtcp2_conn_in_draining_period(qc->conn)); + const ngtcp2_path *path = ngtcp2_conn_get_path(qc->conn); + return quic_send_packet(qc->serv_conn->server, qc->serv_conn->closebuf, qc->serv_conn->closebuf_len, (const u8 *)&path->remote.addr, (u32) path->remote.addrlen); +} + +static GF_Err quic_handle_error(GF_QuicConnection *qc) +{ + if (qc->last_error.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE) { + return GF_IP_CONNECTION_CLOSED; + } + if (! quic_start_closing_period(qc)) { + return GF_IP_CONNECTION_CLOSED; + } + + if (ngtcp2_conn_in_draining_period(qc->conn)) { + return GF_IP_NETWORK_EMPTY; + } + GF_Err e = quic_send_conn_close(qc); + if (e) return e; + return GF_IP_NETWORK_EMPTY; +} + +static void detach_connection(GF_QuicServerConnection *c) +{ + u32 i; + for (i=0;i<MAC_CONN_ID;i++) + c->conn_idsi.associated = 0; + + //freeing the connection is done later +} + +static GF_Err gf_quic_on_data(GF_QuicServerConnection *c, const u8 *data, u32 nb_bytes) +{ + GF_QuicConnection *qc = (GF_QuicConnection *) c->hmux->hmux_udta; + ngtcp2_path path; + ngtcp2_pkt_info pi = {0}; + + path = qc->path; + int rv = ngtcp2_conn_read_pkt(qc->conn, &path, &pi, data, (size_t)nb_bytes, ngtcp2_timestamp() ); + if (rv != 0) { + switch (rv) { + case NGTCP2_ERR_DRAINING: + quic_start_draining_period(qc); + return GF_IP_NETWORK_EMPTY; + case NGTCP2_ERR_RETRY: + return GF_IP_UDP_TIMEOUT; + case NGTCP2_ERR_DROP_CONN: + return GF_IP_CONNECTION_CLOSED; + case NGTCP2_ERR_CRYPTO: + if (!qc->last_error.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_ccerr_set_tls_alert(&qc->last_error, ngtcp2_conn_get_tls_alert(qc->conn), NULL, 0); + } else { + ngtcp2_ccerr_set_liberr(&qc->last_error, rv, NULL, 0); + } + } + return quic_handle_error(qc); + + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("QUIC ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv) )); + + return quic_handle_error(qc); + } + } + return GF_OK; +} + +static GF_Err quic_on_write(GF_QuicServerConnection *c) +{ + GF_QuicConnection *qc = (GF_QuicConnection *) c->hmux->hmux_udta; + if (ngtcp2_conn_in_closing_period(qc->conn) || ngtcp2_conn_in_draining_period(qc->conn)) { + return GF_IP_NETWORK_EMPTY; + } + return h3_session_write_ex(NULL, qc); +} + +static GF_Err gf_quic_create_connection(GF_QuicServer *qs, GF_QuicServerConnection **oc, ngtcp2_version_cid *vc, const u8 *data, u32 data_len, const u8 *s_add, u32 s_add_len) +{ + GF_QuicServerConnection *c; + if (!s_add) return GF_IP_CONNECTION_FAILURE; + if (s_add_len>MAX_ADDR_LEN) return GF_BAD_PARAM; + if (vc->dcidlen>MAX_CONNID_LEN) return GF_BAD_PARAM; + + ngtcp2_pkt_hd hd; + int rv = ngtcp2_accept(&hd, data, data_len); + if (rv != 0) { + if (!(data0 & 0x80) && (data_len >= NGTCP2_SV_SCIDLEN + 22) ) { + quic_send_stateless_reset(qs, data_len, vc->dcid, vc->dcidlen, s_add, s_add_len); + } + return GF_IP_NETWORK_EMPTY; + } + + ngtcp2_cid ocid; + ngtcp2_cid *pocid = NULL; + ngtcp2_token_type token_type = NGTCP2_TOKEN_TYPE_UNKNOWN; + assert(hd.type == NGTCP2_PKT_INITIAL); + + if (qs->validate_address || hd.tokenlen) { + if (hd.tokenlen == 0) { + quic_send_retry(qs, &hd, s_add, s_add_len, data_len * 3); + return GF_IP_NETWORK_EMPTY; + } + if ((hd.token0 != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2) && (hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN)) { + quic_send_stateless_connection_close(qs, &hd, s_add, s_add_len); + return GF_IP_NETWORK_EMPTY; + } + switch (hd.token0) { + case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2: + switch (quic_verify_retry_token(qs, &ocid, &hd, s_add, s_add_len)) { + case 0: + pocid = &ocid; + token_type = NGTCP2_TOKEN_TYPE_RETRY; + break; + case -1: + quic_send_stateless_connection_close(qs, &hd, s_add, s_add_len); + return GF_IP_NETWORK_EMPTY; + case 1: + hd.token = NULL; + hd.tokenlen = 0; + break; + } + break; + case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: + if (quic_verify_token(qs, &hd, s_add, s_add_len) != 0) { + if (qs->validate_address) { + quic_send_retry(qs, &hd, s_add, s_add_len, data_len * 3); + return GF_IP_NETWORK_EMPTY; + } + hd.token = NULL; + hd.tokenlen = 0; + } else { + token_type = NGTCP2_TOKEN_TYPE_NEW_TOKEN; + } + break; + default: + if (qs->validate_address) { + quic_send_retry(qs, &hd, s_add, s_add_len, data_len * 3); + return GF_IP_NETWORK_EMPTY; + } + hd.token = NULL; + hd.tokenlen = 0; + break; + } + } + //we have a new connection + char peer_addressGF_MAX_IP_NAME_LEN; + u32 peer_port; + gf_sk_get_remote_address_port(qs->sock, peer_address, &peer_port); + + GF_Err e; + GF_DownloadSession *sess = gf_dm_sess_new_internal(qs->dm, NULL, GF_NETIO_SESSION_NO_BLOCK, NULL, NULL, NULL, GF_TRUE, &e); + if (e) { + quic_send_stateless_connection_close(qs, &hd, s_add, s_add_len); + return GF_IP_NETWORK_EMPTY; + } + sess->flags |= GF_NETIO_SESSION_USE_QUIC; + if (!qs->accept_conn(qs->udta, sess, peer_address, peer_port)) { + gf_dm_sess_del(sess); + quic_send_stateless_connection_close(qs, &hd, s_add, s_add_len); + return GF_IP_NETWORK_EMPTY; + } + + //setup SSL + sess->ssl = SSL_new(qs->ssl_ctx); + if (!sess->ssl) { + quic_send_stateless_connection_close(qs, &hd, s_add, s_add_len); + sess->status = GF_NETIO_DISCONNECTED; + return GF_IP_CONNECTION_FAILURE; + } + SSL_set_accept_state(sess->ssl); +#ifndef LIBRESSL_VERSION_NUMBER + SSL_set_quic_early_data_enabled(sess->ssl, 1); +#endif // !defined(LIBRESSL_VERSION_NUMBER) + + GF_SAFEALLOC(c, GF_QuicServerConnection) + if (!c) return GF_OUT_OF_MEM; + memcpy(c->addr, s_add, s_add_len); + c->conn_ids0.associated = GF_TRUE; + c->conn_ids0.dcid_len = vc->dcidlen; + memcpy(c->conn_ids0.dcid, vc->dcid, vc->dcidlen); + c->addr_len = s_add_len; + c->server = qs; + gf_list_add(qs->connections, c); + *oc = c; + + ngtcp2_path path; + path.local.addr = (ngtcp2_sockaddr *) qs->local_add; + path.local.addrlen = qs->local_add_len; + path.remote.addr = (ngtcp2_sockaddr *) s_add; + path.remote.addrlen = s_add_len; + + + e = h3_initialize(sess, NULL, 0, &hd, pocid, token_type, &path, c); + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + if (e) { + gf_list_del_item(qs->connections, c); + qc->serv_conn = NULL; + gf_free(qc); + sess->status = GF_NETIO_DISCONNECTED; + quic_send_stateless_connection_close(qs, &hd, s_add, s_add_len); + return GF_IP_CONNECTION_FAILURE; + } + sess->hmux_stream_id = -2; + + e = gf_quic_on_data(c, data, data_len); + if (e) { + if (e == GF_IP_UDP_TIMEOUT) + quic_send_retry(qs, &hd, s_add, s_add_len, data_len * 3); + + return GF_IP_NETWORK_EMPTY; + } + if (quic_on_write(qc->serv_conn)) + return GF_IP_NETWORK_EMPTY; + + return GF_OK; +} + +GF_Err gf_dm_quic_process(GF_QuicServer *qs) +{ + GF_Err e; + u8 buf5000; + u32 nb_read; + GF_QuicServerConnection *qc = NULL; + +restart: + + nb_read=0; + e = gf_sk_receive(qs->sock, buf, 5000, &nb_read); + if (e) return e; + + if (nb_read<22) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC wrong packet size, discarding\n")); + return GF_IP_NETWORK_EMPTY; + } + u32 src_add_len = 0; + const u8 *src_add = gf_sk_get_address(qs->sock, &src_add_len); + if (!src_add) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC Failed to fecth packet address\n")); + return GF_IP_NETWORK_EMPTY; + } + ngtcp2_version_cid vc; + int rv = ngtcp2_pkt_decode_version_cid(&vc, buf, 5000, NGTCP2_SV_SCIDLEN); + switch (rv) { + case 0: + break; + case NGTCP2_ERR_VERSION_NEGOTIATION: + //send_version_negotiation(vc.version, {vc.scid, vc.scidlen}, {vc.dcid, vc.dcidlen}, ep, local_addr, sa, salen); + return GF_IP_NETWORK_EMPTY; + default: + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC Could not decode version and CID: %s\n", ngtcp2_strerror(rv) )); + return GF_IP_NETWORK_EMPTY; + } + + qc = NULL; + u32 i, count = gf_list_count(qs->connections); + for (i=0; i<count; i++) { + qc = gf_list_get(qs->connections, i); + u32 j; + ConnIDInfo *ci=NULL; + for (j=0; j<MAC_CONN_ID; j++) { + ci = &qc->conn_idsj; + if (ci->associated + && (ci->dcid_len == vc.dcidlen) + && !memcmp(ci->dcid, vc.dcid, ci->dcid_len) + ) { + break; + } + ci = NULL; + } + if (ci) break; + qc = NULL; + } + if (!qc) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC new connection detected\n")); + e = gf_quic_create_connection(qs, &qc, &vc, buf, nb_read, src_add, src_add_len); + if (e) return e; + qc = NULL; + goto restart; + } + + GF_QuicConnection *q = qc->hmux->hmux_udta; + if (ngtcp2_conn_in_closing_period(q->conn)) { + if (quic_send_conn_close(q) != 0) { + detach_connection(qc); + return GF_IP_NETWORK_EMPTY; + } + return GF_IP_NETWORK_EMPTY; + } + if (ngtcp2_conn_in_draining_period(q->conn)) { + return GF_IP_NETWORK_EMPTY; + } + + e = gf_quic_on_data(qc, buf, nb_read); + if (e) { + if (e != GF_IP_NETWORK_EMPTY) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("QUIC packet decode error %s\n", gf_error_to_string(e) )); + detach_connection(qc); + return GF_IP_NETWORK_EMPTY; + } + return GF_IP_NETWORK_EMPTY; + } + + goto restart; + + return GF_OK; +} + +GF_Err gf_dm_quic_verify(GF_QuicServer *qs) +{ + if (!qs) return GF_IP_NETWORK_EMPTY; + GF_Err e = GF_IP_NETWORK_EMPTY; + + u32 i, count = gf_list_count(qs->connections); + for (i=0; i<count; i++) { + GF_QuicServerConnection *qc = gf_list_get(qs->connections, i); + if (qc->close_period_end && (qc->close_period_end>gf_sys_clock_high_res()) ) { + GF_QuicConnection *qcpriv = qc->hmux->hmux_udta; + if (ngtcp2_conn_in_closing_period(qcpriv->conn)) { + continue; + } + qc->close_period_end = 0; + detach_connection(qc); + continue; + } + if (qc->drain_period_end && (qc->drain_period_end>gf_sys_clock_high_res()) ) { + GF_QuicConnection *qcpriv = qc->hmux->hmux_udta; + if (ngtcp2_conn_in_draining_period(qcpriv->conn)) { + continue; + } + qc->drain_period_end = 0; + detach_connection(qc); + continue; + } + if (qc->closed) continue; + + if (quic_on_write(qc) == GF_OK) { + e = GF_OK; + } + } + return e; +} + +GF_Err h3_check_sess(GF_DownloadSession *sess) +{ + GF_QuicConnection *qc = sess->hmux_sess->hmux_udta; + if (qc->serv_conn && qc->serv_conn->closed) + return GF_IP_CONNECTION_CLOSED; + return GF_IP_NETWORK_EMPTY; +} + + +#endif // !defined(GPAC_DISABLE_NETWORK) && defined(GPAC_HAS_NGTCP2)
View file
gpac-26.02.0.tar.gz/src/utils/downloader_ssl.c
Added
@@ -0,0 +1,1045 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2025 + * All rights reserved + * + * This file is part of GPAC / downloader sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "downloader.h" +#ifndef GPAC_DISABLE_NETWORK + +#ifdef GPAC_HAS_HTTP2 +#include <nghttp2/nghttp2.h> +#endif + +#ifdef GPAC_HAS_NGTCP2 +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_quictls.h> +#endif + +#ifdef GPAC_HAS_SSL +static void init_prng (void) +{ + char namebuf256; + const char *random_file; + + if (RAND_status ()) return; + + namebuf0 = '\0'; + random_file = RAND_file_name (namebuf, sizeof (namebuf)); + + if (random_file && *random_file) + RAND_load_file(random_file, 16384); + + if (RAND_status ()) return; +} + + +/* NPN TLS extension client callback. We check that server advertised + the HTTP/2 protocol the nghttp2 library supports. If not, exit + the program. */ +static int ssl_select_next_proto_cb(SSL *ssl , unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) +{ +#ifdef GPAC_HAS_HTTP2 + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTP/2 Server did not advertise " NGHTTP2_PROTO_VERSION_ID)); + } +#endif + return SSL_TLSEXT_ERR_OK; +} + + +static Bool _ssl_is_initialized = GF_FALSE; + +/*! + * initialize the SSL library once for all download managers +\return GF_FALSE if everyhing is OK, GF_TRUE otherwise + */ +Bool gf_ssl_init_lib() +{ + if (_ssl_is_initialized) + return GF_FALSE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPS Initializing SSL library...\n")); + init_prng(); + if (RAND_status() != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPS Error while initializing Random Number generator, failed to init SSL !\n")); + return GF_TRUE; + } + + /* per https://www.openssl.org/docs/man1.1.0/ssl/OPENSSL_init_ssl.html + ** As of version 1.1.0 OpenSSL will automatically allocate all resources that it needs so no explicit initialisation is required. + ** Similarly it will also automatically deinitialise as required. + */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_library_init(); + SSL_load_error_strings(); + SSLeay_add_all_algorithms(); + SSLeay_add_ssl_algorithms(); +#endif + + _ssl_is_initialized = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("HTTPS Initialization of SSL library complete.\n")); + return GF_FALSE; +} + +#ifndef GPAC_DISABLE_LOG +static const char *tls_rt_get_name(int type) +{ + switch(type) { +#ifdef SSL3_RT_HEADER + case SSL3_RT_HEADER: return "TLS header"; +#endif + case SSL3_RT_CHANGE_CIPHER_SPEC: return "TLS change cipher"; + case SSL3_RT_ALERT: return "TLS alert"; + case SSL3_RT_HANDSHAKE: return "TLS handshake"; + case SSL3_RT_APPLICATION_DATA: return "TLS app data"; + default: return "TLS Unknown"; + } +} + +static const char *ssl_msg_get_name(int version, int msg) +{ +#ifdef SSL_CTRL_SET_MSG_CALLBACK + +#ifdef SSL2_VERSION_MAJOR + if (version == SSL2_VERSION_MAJOR) { + switch(msg) { + case SSL2_MT_ERROR: return "Error"; + case SSL2_MT_CLIENT_HELLO: return "Client hello"; + case SSL2_MT_CLIENT_MASTER_KEY: return "Client key"; + case SSL2_MT_CLIENT_FINISHED: return "Client finished"; + case SSL2_MT_SERVER_HELLO: return "Server hello"; + case SSL2_MT_SERVER_VERIFY: return "Server verify"; + case SSL2_MT_SERVER_FINISHED: return "Server finished"; + case SSL2_MT_REQUEST_CERTIFICATE: return "Request CERT"; + case SSL2_MT_CLIENT_CERTIFICATE: return "Client CERT"; + } + } else +#endif + if (version == SSL3_VERSION_MAJOR) { + switch(msg) { + case SSL3_MT_HELLO_REQUEST: return "Hello request"; + case SSL3_MT_CLIENT_HELLO: return "Client hello"; + case SSL3_MT_SERVER_HELLO: return "Server hello"; + #ifdef SSL3_MT_NEWSESSION_TICKET + case SSL3_MT_NEWSESSION_TICKET: return "Newsession Ticket"; + #endif + case SSL3_MT_CERTIFICATE: return "Certificate"; + case SSL3_MT_SERVER_KEY_EXCHANGE: return "Server key exchange"; + case SSL3_MT_CLIENT_KEY_EXCHANGE: return "Client key exchange"; + case SSL3_MT_CERTIFICATE_REQUEST: return "Request CERT"; + case SSL3_MT_SERVER_DONE: return "Server finished"; + case SSL3_MT_CERTIFICATE_VERIFY: return "CERT verify"; + case SSL3_MT_FINISHED: return "Finished"; +#ifdef SSL3_MT_CERTIFICATE_STATUS + case SSL3_MT_CERTIFICATE_STATUS: return "Certificate Status"; +#endif +#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS + case SSL3_MT_ENCRYPTED_EXTENSIONS: return "Encrypted Extensions"; +#endif +#ifdef SSL3_MT_SUPPLEMENTAL_DATA + case SSL3_MT_SUPPLEMENTAL_DATA: return "Supplemental data"; +#endif +#ifdef SSL3_MT_END_OF_EARLY_DATA + case SSL3_MT_END_OF_EARLY_DATA: return "End of early data"; +#endif +#ifdef SSL3_MT_KEY_UPDATE + case SSL3_MT_KEY_UPDATE: return "Key update"; +#endif +#ifdef SSL3_MT_NEXT_PROTO + case SSL3_MT_NEXT_PROTO: return "Next protocol"; +#endif +#ifdef SSL3_MT_MESSAGE_HASH + case SSL3_MT_MESSAGE_HASH: return "Message hash"; +#endif + } + } +#endif + return "Unknown"; +} + +static void ssl_on_log(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *udat) +{ + const char *msg_name, *tls_rt_name; + int msg_type; + char szUnknown32; + const char *szVersion = NULL; + + if (!version) return; +#ifdef SSL3_RT_INNER_CONTENT_TYPE + if (content_type == SSL3_RT_INNER_CONTENT_TYPE) return; +#endif + + switch (version) { +#ifdef SSL2_VERSION + case SSL2_VERSION: szVersion = "SSLv2"; break; +#endif +#ifdef SSL3_VERSION + case SSL3_VERSION: szVersion = "SSLv3"; break; +#endif + case TLS1_VERSION: szVersion = "TLSv1.0"; break; +#ifdef TLS1_1_VERSION + case TLS1_1_VERSION: szVersion = "TLSv1.1"; break; +#endif +#ifdef TLS1_2_VERSION + case TLS1_2_VERSION: szVersion = "TLSv1.2"; break; +#endif +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: szVersion = "TLSv1.3"; break; +#endif + case 0: break; + default: + snprintf(szUnknown, 31, "(%x)", version); + szVersion = szUnknown; + break; + } + version >>= 8; + if ((version == SSL3_VERSION_MAJOR) && content_type) + tls_rt_name = tls_rt_get_name(content_type); + else + tls_rt_name = ""; + + if (content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { + msg_type = *(char *)buf; + msg_name = "Change cipher spec"; + } else if (content_type == SSL3_RT_ALERT) { + msg_type = (((char *)buf)0 << 8) + ((char *)buf)1; + msg_name = SSL_alert_desc_string_long(msg_type); + } else { + msg_type = *(char *)buf; + msg_name = ssl_msg_get_name(version, msg_type); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("%s %s %s (%d)\n", szVersion, tls_rt_name, msg_name, msg_type)); +} +#endif //GPAC_DISABLE_LOG + +static char ALPN_PROTOS20; + +void *gf_dm_ssl_init(GF_DownloadManager *dm, Bool no_quic) +{ +#if OPENSSL_VERSION_NUMBER > 0x00909000 + const +#endif + SSL_METHOD *meth; + + if (!dm) return NULL; + + gf_mx_p(dm->cache_mx); + /* The SSL has already been initialized. */ + if (dm->ssl_ctx) { + gf_mx_v(dm->cache_mx); + return dm->ssl_ctx; + } + /* Init the PRNG. If that fails, bail out. */ + if (gf_ssl_init_lib()) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("HTTPS Failed to properly initialize SSL library\n")); + goto error; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + meth = SSLv23_client_method(); +#else + meth = TLS_client_method(); +#endif + + dm->ssl_ctx = SSL_CTX_new(meth); + if (!dm->ssl_ctx) goto error; + SSL_CTX_set_options(dm->ssl_ctx, SSL_OP_ALL); + SSL_CTX_set_default_verify_paths(dm->ssl_ctx); + SSL_CTX_load_verify_locations (dm->ssl_ctx, NULL, NULL); + /* SSL_VERIFY_NONE instructs OpenSSL not to abort SSL_connect if the + certificate is invalid. We verify the certificate separately in + ssl_check_certificate, which provides much better diagnostics + than examining the error stack after a failed SSL_connect. */ + SSL_CTX_set_verify(dm->ssl_ctx, SSL_VERIFY_NONE, NULL); + + if (!gf_opts_get_bool("core", "broken-cert")) { + + const char* ca_bundle = gf_opts_get_key("core", "ca-bundle"); + + if (ca_bundle && gf_file_exists(ca_bundle)) { + SSL_CTX_load_verify_locations(dm->ssl_ctx, ca_bundle, NULL); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("SSL Using CA bundle at %s\n", ca_bundle)); + } + else { + const char* ossl_bundle = X509_get_default_cert_file_env(); + ossl_bundle = getenv(ossl_bundle); + if (!ossl_bundle) + ossl_bundle = X509_get_default_cert_file(); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("SSL OpenSSL CA bundle location: %s\n", ossl_bundle)); + + if (!ossl_bundle || !gf_file_exists(ossl_bundle)) { + + const char* ca_bundle_default = gf_opts_get_key("core", "ca-bundle-default"); + + if (ca_bundle_default) { + SSL_CTX_load_verify_locations(dm->ssl_ctx, ca_bundle_default, NULL); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("SSL Using default CA bundle at %s\n", ca_bundle_default)); + } + } + } + } + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG) ) { + SSL_CTX_set_msg_callback(dm->ssl_ctx, ssl_on_log); + } +#endif + + + ALPN_PROTOS0 = 0; +#ifdef GPAC_HAS_HTTP2 + if (!dm->disable_http2) { + strcat(ALPN_PROTOS, "\x02h2"); + } +#endif + + //configure H3 + if (dm->h3_mode && !no_quic) { +#ifdef GPAC_HAS_NGTCP2 + if (ngtcp2_crypto_quictls_configure_client_context(dm->ssl_ctx) != 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unable to initialize SSL context for QUIC, disabling HTTP3\n")); + dm->h3_mode = H3_MODE_NO; + } else { + strcat(ALPN_PROTOS, "\x02h3"); + } +#endif + } + + if (ALPN_PROTOS0) { + SSL_CTX_set_next_proto_select_cb(dm->ssl_ctx, ssl_select_next_proto_cb, NULL); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(dm->ssl_ctx, ALPN_PROTOS, (u32) strlen(ALPN_PROTOS)); +#endif + } + + /* Since fd_write unconditionally assumes partial writes (and handles them correctly), + allow them in OpenSSL. */ + SSL_CTX_set_mode(dm->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + gf_mx_v(dm->cache_mx); + return dm->ssl_ctx; +error: + if (dm->ssl_ctx) SSL_CTX_free(dm->ssl_ctx); + dm->ssl_ctx = NULL; + gf_mx_v(dm->cache_mx); + return NULL; +} + +#ifdef GPAC_HAS_HTTP2 +void h2_initialize_session(GF_DownloadSession *sess); +#endif + +#if defined(GPAC_HAS_HTTP2) || defined(GPAC_HAS_NGTCP2) + +static unsigned char next_proto_list256; +static size_t next_proto_list_len; + +#ifndef OPENSSL_NO_NEXTPROTONEG +static int next_proto_cb(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg) +{ + if (data && len) { + *data = next_proto_list; + *len = (unsigned int)next_proto_list_len; + } + return SSL_TLSEXT_ERR_OK; +} +#endif //OPENSSL_NO_NEXTPROTONEG + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) +{ + +#ifdef GPAC_HAS_NGTCP2 + ngtcp2_crypto_conn_ref *cref = (ngtcp2_crypto_conn_ref *) SSL_get_app_data(ssl); + if (cref) { + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + uint32_t version = ngtcp2_conn_get_client_chosen_version( cref->get_conn(cref) ); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2: + alpn = "\x2h3"; + alpnlen = 3; + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Unexpected quic protocol version %X\n", version )); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + const unsigned char *p, *end; + for (p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (!strncmp(alpn, p, alpnlen)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Client did not present ALPN %s\n", &alpn1 )); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } +#endif + + +#ifdef GPAC_HAS_HTTP2 + int rv = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); + if (rv == 1) + return SSL_TLSEXT_ERR_OK; +#endif + return SSL_TLSEXT_ERR_NOACK; +} + +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + +#endif //GPAC_HAS_HTTP2 + + +#if !defined(LIBRESSL_VERSION_NUMBER) && defined(GPAC_HAS_NGTCP2) +int ngq_gen_ticket_cb(SSL *ssl, void *arg) +{ + ngtcp2_crypto_conn_ref *cref = (ngtcp2_crypto_conn_ref *) SSL_get_app_data(ssl); + if (!cref) return 0; + int ver = htonl(ngtcp2_conn_get_negotiated_version(cref->get_conn(cref) ) ); + if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver, sizeof(ver))) { + return 0; + } + return 1; +} + +SSL_TICKET_RETURN ngq_decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session, const unsigned char *keyname, size_t keynamelen, SSL_TICKET_STATUS status, void *arg) +{ + switch (status) { + case SSL_TICKET_EMPTY: + case SSL_TICKET_NO_DECRYPT: + return SSL_TICKET_RETURN_IGNORE_RENEW; + } + + uint8_t *pver; + uint32_t ver; + size_t verlen; + + if (!SSL_SESSION_get0_ticket_appdata(session, (void **) &pver, &verlen) || (verlen != sizeof(ver))) { + switch (status) { + case SSL_TICKET_SUCCESS: + return SSL_TICKET_RETURN_IGNORE; + case SSL_TICKET_SUCCESS_RENEW: + default: + return SSL_TICKET_RETURN_IGNORE_RENEW; + } + } + memcpy(&ver, pver, sizeof(ver)); + ngtcp2_crypto_conn_ref *cref = (ngtcp2_crypto_conn_ref *) SSL_get_app_data(ssl); + if (!cref) return SSL_TICKET_RETURN_IGNORE_RENEW; + + if (ngtcp2_conn_get_client_chosen_version(cref->get_conn(cref) ) != ntohl(ver)) { + switch (status) { + case SSL_TICKET_SUCCESS: + return SSL_TICKET_RETURN_IGNORE; + case SSL_TICKET_SUCCESS_RENEW: + default: + return SSL_TICKET_RETURN_IGNORE_RENEW; + } + } + switch (status) { + case SSL_TICKET_SUCCESS: + return SSL_TICKET_RETURN_USE; + case SSL_TICKET_SUCCESS_RENEW: + default: + return SSL_TICKET_RETURN_USE_RENEW; + } +} +#endif // !definedLIBRESSL_VERSION_NUMBER) && defined(GPAC_HAS_NGTCP2) + + +void *gf_ssl_server_context_new(const char *cert, const char *key, Bool for_quic) +{ + const SSL_METHOD *method; + SSL_CTX *ctx; + + method = SSLv23_server_method(); + +#ifdef GPAC_HAS_NGTCP2 + if (for_quic) { + method = TLS_server_method(); + } +#else + if (for_quic) return NULL; +#endif + + ctx = SSL_CTX_new(method); + if (!ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unable to create SSL context\n")); + ERR_print_errors_fp(stderr); + return NULL; + } + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG) ) { + SSL_CTX_set_msg_callback(ctx, ssl_on_log); + } +#endif + + //configure H2 +#ifdef GPAC_HAS_HTTP2 + if (!for_quic && !gf_opts_get_bool("core", "no-h2")) { + next_proto_list0 = NGHTTP2_PROTO_VERSION_ID_LEN; + memcpy(&next_proto_list1, NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN); + next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; + } +#endif + + +#ifdef GPAC_HAS_NGTCP2 + if (for_quic) { + ngtcp2_crypto_quictls_configure_server_context(ctx); + + next_proto_listnext_proto_list_len = 2; + memcpy(&next_proto_listnext_proto_list_len+1, "h3", 2); + next_proto_list_len = 1 + 2; + + SSL_CTX_set_max_early_data(ctx, UINT32_MAX); + + u64 ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE +#ifndef LIBRESSL_VERSION_NUMBER + | SSL_OP_NO_ANTI_REPLAY +#endif // !defined(LIBRESSL_VERSION_NUMBER) + ; + SSL_CTX_set_options(ctx, ssl_opts); + + SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); + +#ifndef LIBRESSL_VERSION_NUMBER + SSL_CTX_set_session_ticket_cb(ctx, ngq_gen_ticket_cb, ngq_decrypt_ticket_cb, NULL); +#endif // !defined(LIBRESSL_VERSION_NUMBER) + + SSL_CTX_set_ciphersuites(ctx, "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"); + SSL_CTX_set1_groups_list(ctx, "X25519:P-256:P-384:P-521"); + } +#endif + +/* if (!for_quic) + SSL_CTX_set_quic_method(ctx, NULL); +*/ +#if defined(GPAC_HAS_HTTP2) || defined(GPAC_HAS_NGTCP2) + if (next_proto_list_len) { + SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, NULL); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + } +#endif + + SSL_CTX_set_default_verify_paths(ctx); + SSL_CTX_set_ecdh_auto(ctx, 1); + +// if (SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM) <= 0) { + if (SSL_CTX_use_certificate_chain_file(ctx, cert) != 1) { + ERR_print_errors_fp(stderr); + SSL_CTX_free(ctx); + return NULL; + } + if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0 ) { + ERR_print_errors_fp(stderr); + SSL_CTX_free(ctx); + return NULL; + } + if (SSL_CTX_check_private_key(ctx) != 1) { + ERR_print_errors_fp(stderr); + SSL_CTX_free(ctx); + return NULL; + } + + return ctx; +} + +void gf_ssl_server_context_del(void *ssl_ctx) +{ + SSL_CTX_free(ssl_ctx); +} +void *gf_ssl_new(void *ssl_ctx, GF_Socket *client_sock, GF_Err *e) +{ + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + *e = GF_IO_ERR; + return NULL; + } + SSL_set_fd(ssl, gf_sk_get_handle(client_sock) ); + if (SSL_accept(ssl) <= 0) { + if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG)) { + //this one crashes /exits on windows, only use if debug level + ERR_print_errors_fp(stderr); + } else { + ERR_print_errors_fp(stderr); + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("SSL Accept failure\n")); + } + SSL_shutdown(ssl); + SSL_free(ssl); + *e = GF_AUTHENTICATION_FAILURE; + return NULL; + } + *e = GF_OK; + return ssl; +} +void gf_ssl_del(void *ssl) +{ + if (ssl) SSL_free(ssl); +} + +Bool gf_ssl_check_cert(SSL *ssl, const char *server_name) +{ + long vresult = SSL_get_verify_result(ssl); + Bool success = (vresult == X509_V_OK); + + if (!success) { + int level = GF_LOG_ERROR; + if (gf_opts_get_bool("core", "broken-cert")) { + success = GF_TRUE; + level = GF_LOG_WARNING; + } + GF_LOG(level, GF_LOG_HTTP, ("SSL Certificate verification failed for domain %s: %s. %s\n", server_name, ERR_error_string(vresult, NULL), (level == GF_LOG_ERROR ? "Use -broken-cert to bypass." : ""))); + } + + return success; +} + +GF_Err gf_ssl_write(GF_DownloadSession *sess, const u8 *buffer, u32 size, u32 *written) +{ + u32 idx=0; + s32 nb_tls_blocks = size/16000; + *written = 0; + while (nb_tls_blocks>=0) { + u32 len, to_write = 16000; + if (nb_tls_blocks==0) + to_write = size - idx*16000; + + len = SSL_write(sess->ssl, buffer + idx*16000, to_write); + nb_tls_blocks--; + idx++; + + if (len != to_write) { + if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) { + int err = SSL_get_error(sess->ssl, len); + if ((err==SSL_ERROR_WANT_READ) || (err==SSL_ERROR_WANT_WRITE)) { + return GF_IP_NETWORK_EMPTY; + } + if (err==SSL_ERROR_SSL) { + char msg1024; + SSL_load_error_strings(); + ERR_error_string_n(ERR_get_error(), msg, 1023); + msg1023=0; + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot send, error %s\n", msg)); + } + } + return GF_IP_NETWORK_FAILURE; + } + *written += to_write; + } + return GF_OK; +} + +GF_Err gf_ssl_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read) +{ + s32 size; + GF_Err e; + + //receive on null buffer (select only, check if data available) + e = gf_sk_receive(sess->sock, NULL, 0, NULL); + //empty and no pending bytes in SSL, network empty + if ((e==GF_IP_NETWORK_EMPTY) && +#if 1 + !SSL_pending(sess->ssl) +#else + //no support for SSL_has_pending in old libSSL and same result can be achieved with SSL_pending + !SSL_has_pending(sess->ssl) +#endif + ) { + return GF_NOT_READY; + } + size = SSL_read(sess->ssl, data, data_size); + if (size < 0) { + int err = SSL_get_error(sess->ssl, size); + if (err==SSL_ERROR_SSL) { + char msg1024; + SSL_load_error_strings(); + ERR_error_string_n(ERR_get_error(), msg, 1023); + msg1023=0; + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot read, error %s\n", msg)); + e = GF_IO_ERR; + } else { + e = gf_sk_probe(sess->sock); + } + } else if (!size) + e = GF_IP_NETWORK_EMPTY; + else { + e = GF_OK; + *out_read = size; + } + return e; +} + +void gf_dm_sess_server_setup_ssl(GF_DownloadSession *sess) +{ + gf_assert(sess->ssl); + if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) + SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); + +#ifdef GPAC_HAS_HTTP2 + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL_get0_next_proto_negotiated(sess->ssl, &alpn, &alpnlen); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(sess->ssl, &alpn, &alpnlen); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + if (alpn && (alpnlen == 2) && !memcmp("h2", alpn, 2)) { + h2_initialize_session(sess); + } +#endif //GPAC_HAS_HTTP2 + +} + +SSLConnectStatus gf_ssl_try_connect(GF_DownloadSession *sess, const char *proxy) +{ + u64 now = gf_sys_clock_high_res(); + if (sess->dm && !sess->dm->ssl_ctx) + gf_dm_ssl_init(sess->dm, (sess->flags & GF_NETIO_SESSION_USE_QUIC) ? GF_FALSE : GF_TRUE); + + /*socket is connected, configure SSL layer*/ + if (!sess->dm || !sess->dm->ssl_ctx) return SSL_CONNECT_OK; + + int ret; + Bool success; + + if (!sess->ssl) { + sess->ssl = SSL_new(sess->dm->ssl_ctx); + SSL_set_fd(sess->ssl, gf_sk_get_handle(sess->sock)); + SSL_ctrl(sess->ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void*) proxy); + SSL_set_connect_state(sess->ssl); + + if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) + SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); + +#ifdef GPAC_HAS_HTTP2 + if (sess->h2_upgrade_state==3) { + SSL_set_alpn_protos(sess->ssl, NULL, 0); + sess->h2_upgrade_state = 0; + } + //h1 disabled, don't use alpn + else if (sess->h2_upgrade_state==4) { + SSL_set_alpn_protos(sess->ssl, NULL, 0); + } +#endif + +#ifdef GPAC_HAS_NGTCP2 + //if not using quic, reset quic methods in ssl + if (!(sess->flags & GF_NETIO_SESSION_USE_QUIC)) + SSL_set_quic_method(sess->ssl, NULL); +#endif + } + + if (sess->flags & GF_NETIO_SESSION_USE_QUIC) + return SSL_CONNECT_RETRY; + + sess->connect_pending = 0; + ret = SSL_connect(sess->ssl); + if (ret<=0) { + ret = SSL_get_error(sess->ssl, ret); + if (ret==SSL_ERROR_SSL) { + +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) && defined(GPAC_HAS_HTTP2) + //error and we tried with alpn for h2, retry without (kill/reconnect) + if (!sess->h2_upgrade_state && !sess->dm->disable_http2) { + SSL_free(sess->ssl); + sess->ssl = NULL; + dm_sess_sk_del(sess); + sess->status = GF_NETIO_SETUP; + sess->h2_upgrade_state = 3; + return SSL_CONNECT_RETRY; + } +#endif + + char msg1024; + SSL_load_error_strings(); + ERR_error_string_n(ERR_get_error(), msg, 1023); + msg1023=0; + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot connect, error %s\n", msg)); + if (!sess->dm->disable_http2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("\tYou may want to retry with HTTP/2 support disabled (-no-h2)\n")); + } + SET_LAST_ERR(GF_SERVICE_ERROR) + } else if ((ret==SSL_ERROR_WANT_READ) || (ret==SSL_ERROR_WANT_WRITE)) { + sess->status = GF_NETIO_SETUP; + sess->connect_pending = 2; + sess->last_error = GF_IP_NETWORK_EMPTY; + return SSL_CONNECT_WAIT; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("SSL Cannot connect, error %d\n", ret)); + SET_LAST_ERR(GF_REMOTE_SERVICE_ERROR) + } + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("SSL connected\n")); + + +#ifdef GPAC_HAS_HTTP2 + if (!sess->dm->disable_http2) { + const u8 *alpn = NULL; + u32 alpnlen = 0; + SSL_get0_next_proto_negotiated(sess->ssl, &alpn, &alpnlen); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(sess->ssl, &alpn, &alpnlen); + } +#endif + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("SSL HTTP/2 is not negotiated\n")); + //disable h2 for the session + sess->h2_upgrade_state = 4; + } else { + h2_initialize_session(sess); + } + } +#endif + } + + success = gf_ssl_check_cert(sess->ssl, sess->server_name); + if (!success) { + gf_dm_disconnect(sess, HTTP_RESET_CONN); + sess->status = GF_NETIO_STATE_ERROR; + SET_LAST_ERR(GF_AUTHENTICATION_FAILURE) + gf_dm_sess_notify_state(sess, sess->status, sess->last_error); + } + + sess->ssl_setup_time = (u32) (gf_sys_clock_high_res() - now); + return SSL_CONNECT_OK; +} + +#endif // GPAC_HAS_SSL + +/** + * \brief Saves the digest for authentication of password and username +\param dm The download manager +\param creds The credentials to fill +\param password the password +\param store_info write to cred file or not +\return GF_OK if info has been filled, GF_BAD_PARAM if creds == NULL or dm == NULL, GF_AUTHENTICATION_FAILURE if user did not filled the info. + */ +static GF_Err gf_user_credentials_save_digest( GF_DownloadManager * dm, GF_UserCredentials * creds, const char * password, Bool store_info) { + int size; + char *pass_buf = NULL; + char range_buf1024; + if (!dm || !creds || !password) + return GF_BAD_PARAM; + gf_dynstrcat(&pass_buf, creds->username, NULL); + gf_dynstrcat(&pass_buf, password, ":"); + if (!pass_buf) return GF_OUT_OF_MEM; + size = gf_base64_encode(pass_buf, (u32) strlen(pass_buf), range_buf, 1024); + gf_free(pass_buf); + range_bufsize = 0; + strcpy(creds->digest, range_buf); + creds->valid = GF_TRUE; + +#ifndef GPAC_DISABLE_CRYPTO + if (store_info) { + u32 plen = (u32) strlen(password); + char *key = NULL; + char szPATHGF_MAX_PATH; + const char *cred = gf_opts_get_key("core", "cred"); + if (!cred) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Core No credential key defined, will not store password - use -cred=PATH_TO_KEY to specify it\n")); + return GF_OK; + } + + FILE *f = gf_fopen(cred, "r"); + bin128 k; + if (!f) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Core Failed to open credential key %s, will not store password\n", cred)); + return GF_OK; + } + u32 ksize = (u32) gf_fread(k, 16, f); + gf_fclose(f); + if (ksize!=16) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Core Invalid credential key %s: size %d but expecting 16 bytes, will not store password\n", cred, ksize)); + return GF_OK; + } + + GF_Crypt *gfc = gf_crypt_open(GF_AES_128, GF_CTR); + gf_crypt_init(gfc, k, NULL); + strcpy(szPATH, password); + gf_crypt_encrypt(gfc, szPATH, plen); + gf_crypt_close(gfc); + szPATHplen = 0; + gf_dynstrcat(&key, creds->username, NULL); + gf_dynstrcat(&key, szPATH, ":"); + gf_opts_set_key("credentials", creds->site, key); + gf_free(key); + gf_opts_save(); + } +#endif + return GF_OK; +} +/** + * Find a User's credentials for a given site + */ +GF_UserCredentials* gf_user_credentials_find_for_site(GF_DownloadManager *dm, const char *server_name, const char *user_name) +{ + GF_UserCredentials * cred; + u32 count, i; + if (!dm || !dm->credentials || !server_name || !strlen(server_name)) + return NULL; + count = gf_list_count( dm->credentials); + for (i = 0 ; i < count; i++) { + cred = (GF_UserCredentials*)gf_list_get(dm->credentials, i ); + if (!cred || strcmp(cred->site, server_name)) + continue; + + if (!user_name || !strcmp(user_name, cred->username)) + return cred; + } + +#ifndef GPAC_DISABLE_CRYPTO + char *key = (char*) gf_opts_get_key("credentials", server_name); + if (key) { + bin128 k; + const char *credk = gf_opts_get_key("core", "cred"); + if (credk) { + FILE *f = gf_fopen(credk, "r"); + if (f) { + gf_fread(k, 16, f); + gf_fclose(f); + } else { + credk = NULL; + } + } + char *sep = credk ? strrchr(key, ':') : NULL; + if (sep) { + char szP1024; + GF_SAFEALLOC(cred, GF_UserCredentials); + if (!cred) return NULL; + cred->dm = dm; + sep0 = 0; + strcpy(cred->username, key); + strcpy(szP, sep+1); + sep0 = ':'; + + GF_Crypt *gfc = gf_crypt_open(GF_AES_128, GF_CTR); + gf_crypt_init(gfc, k, NULL); + gf_crypt_decrypt(gfc, szP, (u32) strlen(szP)); + gf_crypt_close(gfc); + gf_user_credentials_save_digest(dm, cred, szP, GF_FALSE); + return cred; + } + } +#endif + return NULL; +} + + +static void on_user_pass(void *udta, const char *user, const char *pass, Bool store_info) +{ + GF_UserCredentials *creds = (GF_UserCredentials *)udta; + if (!creds) return; + u32 len = user ? (u32) strlen(user) : 0; + if (len && (user != creds->username)) { + if (len> 49) len = 49; + strncpy(creds->username, user, 49); + creds->usernamelen=0; + } + if (user && pass) { + GF_Err e = gf_user_credentials_save_digest((GF_DownloadManager *)creds->dm, creds, pass, store_info); + if (e != GF_OK) { + creds->valid = GF_FALSE; + } + } else { + creds->valid = GF_FALSE; + } + creds->req_state = GF_CREDS_STATE_DONE; +} + +/** + * \brief Asks the user for credentials for given site +\param dm The download manager +\param creds The credentials to fill +\param secure is auth required? +\return GF_OK if info has been filled, GF_BAD_PARAM if creds == NULL or dm == NULL, GF_AUTHENTICATION_FAILURE if user did not filled the info. + */ +static GF_Err gf_user_credentials_ask_password( GF_DownloadManager * dm, GF_UserCredentials * creds, Bool secure) +{ + char szPASS50; + if (!dm || !creds) + return GF_BAD_PARAM; + memset(szPASS, 0, 50); + if (!dm->get_user_password) return GF_AUTHENTICATION_FAILURE; + creds->req_state = GF_CREDS_STATE_PENDING; + if (!dm->get_user_password(dm->usr_cbk, secure, creds->site, creds->username, szPASS, on_user_pass, creds)) { + creds->req_state = GF_CREDS_STATE_NONE; + return GF_AUTHENTICATION_FAILURE; + } + return GF_OK; +} + +GF_UserCredentials * gf_user_credentials_register(GF_DownloadManager * dm, Bool secure, const char * server_name, const char * username, const char * password, Bool valid) +{ + GF_UserCredentials * creds; + if (!dm) + return NULL; + gf_assert( server_name ); + creds = gf_user_credentials_find_for_site(dm, server_name, username); + /* If none found, we create one */ + if (!creds) { + GF_SAFEALLOC(creds, GF_UserCredentials); + if (!creds) return NULL; + creds->dm = dm; + gf_list_insert(dm->credentials, creds, 0); + } + creds->valid = valid; + if (username) { + strncpy(creds->username, username, 49); + creds->username49 = 0; + } else { + creds->username0 = 0; + } + strncpy(creds->site, server_name, 1023); + creds->site1023 = 0; + if (username && password && valid) + gf_user_credentials_save_digest(dm, creds, password, GF_FALSE); + else { + if (GF_OK != gf_user_credentials_ask_password(dm, creds, secure)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, + ("Downloader Failed to get password information.\n")); + gf_list_del_item( dm->credentials, creds); + gf_free( creds ); + creds = NULL; + } + } + return creds; +} + + +#endif //GPAC_DISABLE_NETWORK
View file
gpac-2.4.0.tar.gz/src/utils/error.c -> gpac-26.02.0.tar.gz/src/utils/error.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -25,12 +25,13 @@ #include <gpac/tools.h> #include <gpac/thread.h> +#include <gpac/utf.h> //ugly patch, we have a concurrence issue with gf_4cc_to_str, for now fixed by rolling buffers #define NB_4CC_BUF 10 static char szTYPE_BUFNB_4CC_BUFGF_4CC_MSIZE; -static u32 buf_4cc_idx = NB_4CC_BUF; +static u32 buf_4cc_idx = 0; GF_EXPORT const char *gf_4cc_to_str_safe(u32 type, char szTypeGF_4CC_MSIZE) @@ -46,6 +47,25 @@ if ( ch >= 0x20 && ch <= 0x7E ) { *name = ch; name++; + } else if (!gf_sys_is_test_mode() ) { + char szTmp2; + szTmp0 = 0xc2; + szTmp1 = ch; + if (gf_utf8_is_legal(szTmp, 2)) { + name0 = 0xc2; + name1 = ch; + name+=2; + } else { + szTmp0 = 0xc3; + if (gf_utf8_is_legal(szTmp, 2)) { + name0 = 0xc3; + name1 = ch; + name+=2; + } else { + sprintf(name, "%02X", ch); + name += 2; + } + } } else { sprintf(name, "%02X", ch); name += 2; @@ -60,10 +80,16 @@ const char *gf_4cc_to_str(u32 type) { if (!type) return "00000000"; - if (safe_int_dec(&buf_4cc_idx)==0) - buf_4cc_idx=NB_4CC_BUF; - return gf_4cc_to_str_safe(type, szTYPE_BUFbuf_4cc_idx-1); + // we get the value *before* the increment insuring that other thread get another value + u32 old_idx = safe_int_fetch_add(&buf_4cc_idx, 1); + + // keep our specific value between 0 and NB_4CC_BUF-1 + // we might have an issue when buf_4cc_idx > INT_MAX since our atomics cast to int + // when it gets > UINT_MAX it should just wrap around and be ok + u32 buffer_index = old_idx % NB_4CC_BUF; + + return gf_4cc_to_str_safe(type, szTYPE_BUFbuffer_index); } @@ -183,12 +209,14 @@ u32 type; const char *name; GF_LOG_Level level; + Bool strict; + const char *alt; } global_log_tools = { { GF_LOG_CORE, "core", GF_LOG_WARNING }, { GF_LOG_CODING, "coding", GF_LOG_WARNING }, { GF_LOG_CONTAINER, "container", GF_LOG_WARNING }, - { GF_LOG_NETWORK, "network", GF_LOG_WARNING }, + { GF_LOG_NETWORK, "network", GF_LOG_WARNING, .alt = "net" }, { GF_LOG_HTTP, "http", GF_LOG_WARNING }, { GF_LOG_RTP, "rtp", GF_LOG_WARNING }, { GF_LOG_CODEC, "codec", GF_LOG_WARNING }, @@ -210,9 +238,10 @@ { GF_LOG_DASH, "dash", GF_LOG_WARNING }, { GF_LOG_FILTER, "filter", GF_LOG_WARNING }, { GF_LOG_SCHEDULER, "sched", GF_LOG_WARNING }, - { GF_LOG_ROUTE, "route", GF_LOG_WARNING }, + { GF_LOG_ROUTE, "route", GF_LOG_WARNING, .alt = "flute" }, { GF_LOG_CONSOLE, "console", GF_LOG_INFO }, { GF_LOG_APP, "app", GF_LOG_INFO }, + { GF_LOG_RMTWS, "rmtws", GF_LOG_WARNING }, }; #define GF_LOG_TOOL_MAX_NAME_SIZE (GF_LOG_TOOL_MAX*10) @@ -226,6 +255,8 @@ for (i=0; i<GF_LOG_TOOL_MAX; i++) { if (!strcmp(global_log_toolsi.name, logs)) return global_log_toolsi.type; + if (global_log_toolsi.alt && !strcmp(global_log_toolsi.alt, logs)) + return global_log_toolsi.type; } if (!strcmp(logs, "all")) return GF_LOG_ALL; @@ -247,6 +278,7 @@ while (val && strlen(val)) { void default_log_callback(void *cbck, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vlist); u32 level; + Bool use_strict=GF_FALSE; const char *next_val = NULL; const char *tools = NULL; /*look for log level*/ @@ -282,7 +314,8 @@ return GF_BAD_PARAM; } } - + char *strict_sep = strstr(sep_level+1, "+strict"); + if (strict_sep) strict_sep0 = 0; if (!strnicmp(sep_level+1, "error", 5)) { level = GF_LOG_ERROR; next_val = sep_level+1 + 5; @@ -303,10 +336,20 @@ level = GF_LOG_QUIET; next_val = sep_level+1 + 5; } + else if (!strnicmp(sep_level+1, "strict", 6)) { + level = GF_LOG_DEBUG+1; + next_val = sep_level+1 + 6; + } else { + if (strict_sep) strict_sep0 = '+'; GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unknown log level specified: %s\n", sep_level+1)); return GF_BAD_PARAM; } + if (strict_sep) { + strict_sep0 = '+'; + use_strict = GF_TRUE; + next_val += 7; + } sep_level0 = 0; tools = val; @@ -331,8 +374,12 @@ else { Bool found = GF_FALSE; for (i=0; i<GF_LOG_TOOL_MAX; i++) { - if (!strcmp(global_log_toolsi.name, tools)) { - global_log_toolsi.level = level; + if (!strcmp(global_log_toolsi.name, tools) + || (global_log_toolsi.alt && !strcmp(global_log_toolsi.alt, tools)) + ) { + if (level<=GF_LOG_DEBUG) + global_log_toolsi.level = level; + global_log_toolsi.strict = use_strict; found = GF_TRUE; break; } @@ -683,8 +730,8 @@ { if (logs_extras) { gf_mx_p(logs_mx); - u32 i, count = gf_list_count(logs_extras); - for (i=0;i<count;i++) { + u32 count = logs_extras ? gf_list_count(logs_extras) : 0; //avoid race condition + for (u32 i=0;i<count;i++) { GF_LogExtra *lf = gf_list_get(logs_extras, i); u32 j; for (j=0; j<lf->nb_tools; j++) { @@ -711,7 +758,11 @@ } if (log_tool==GF_LOG_TOOL_MAX) return GF_TRUE; if (log_tool>GF_LOG_TOOL_MAX) return GF_FALSE; - if (global_log_toolslog_tool.level >= log_level) return GF_TRUE; + if (global_log_toolslog_tool.level >= log_level) { + if (global_log_toolslog_tool.strict && (log_level==GF_LOG_ERROR) && (log_tool != GF_LOG_MEMORY)) + gf_log_set_strict_error(GF_TRUE); + return GF_TRUE; + } return GF_FALSE; } @@ -745,6 +796,7 @@ Bool gpac_log_dual = GF_FALSE; Bool last_log_is_lf = GF_TRUE; static u64 gpac_last_log_time=0; +Bool gpac_use_logx = GF_FALSE; static void do_log_time(FILE *logs, const char *fmt) { @@ -830,12 +882,111 @@ -static void *user_log_cbk = NULL; +void *user_log_cbk = NULL; gf_log_cbk log_cbk = default_log_callback_color; static Bool log_exit_on_error = GF_FALSE; #ifdef GPAC_CONFIG_EMSCRIPTEN Bool gpac_log_console = GF_FALSE; #endif +static GF_List *logs_thread_tags = NULL; + +typedef struct +{ + u32 th_id; + Bool tagged; + u32 type; + void *udta; +} GF_LogThreadTag; + +void gf_logs_init() +{ + logs_thread_tags = gf_list_new(); +} + +void gf_logs_close() +{ + if (gpac_log_file) { + gf_fclose(gpac_log_file); + gpac_log_file = NULL; + } + while (gf_list_count(logs_thread_tags)) { + GF_LogThreadTag *tag = gf_list_pop_back(logs_thread_tags); + gf_free(tag); + } + gf_list_del(logs_thread_tags); +} + +static void gf_logs_set_thread_tag_internal(void *tag_val, u32 tag_type, Bool is_tag, Bool is_rem) +{ + if (!is_rem && !gpac_use_logx && (tag_type>1)) + return; + + if (!logs_thread_tags) + return; + + gf_mx_p(logs_mx); + u32 i, count = gf_list_count(logs_thread_tags); + GF_LogThreadTag *tag = NULL; + for (i=0;i<count;i++) { + tag = gf_list_get(logs_thread_tags, i); + if (tag->udta == tag_val) { + if (is_rem) { + gf_list_rem(logs_thread_tags, i); + gf_free(tag); + gf_mx_v(logs_mx); + return; + } + break; + } + tag = NULL; + } + if (is_rem) return; + + if (!tag) { + GF_SAFEALLOC(tag, GF_LogThreadTag) + tag->udta = tag_val; + gf_list_add(logs_thread_tags, tag); + } + gf_mx_v(logs_mx); + tag->tagged = is_tag; + tag->type = tag_type; + tag->th_id = gf_th_id(); +} + +void gf_logs_thread_tag(void *tag_val, u32 tag_type) +{ + gf_logs_set_thread_tag_internal(tag_val, tag_type, GF_TRUE, GF_FALSE); +} +void gf_logs_thread_untag(void *tag_val) +{ + gf_logs_set_thread_tag_internal(tag_val, 0, GF_FALSE, GF_FALSE); +} +void gf_logs_thread_tag_del(void *tag_val) +{ + gf_logs_set_thread_tag_internal(tag_val, 0, GF_FALSE, GF_TRUE); +} + + +void *gf_logs_get_thread_tag(u32 *tag_type, u32 *o_th_id) +{ + u32 th_id = gf_th_id(); + u32 i, count = gf_list_count(logs_thread_tags); + GF_LogThreadTag *highest_tag = NULL; + for (i=0; i<count; i++) { + GF_LogThreadTag *tag = gf_list_get(logs_thread_tags, i); + if (tag->tagged && (tag->th_id == th_id)) { + if (!highest_tag || (highest_tag->type < tag->type)) + highest_tag = tag; + } + } + *o_th_id = th_id; + if (highest_tag) { + *tag_type = highest_tag->type; + return highest_tag->udta; + } + *tag_type = 0; + return NULL; +} GF_EXPORT Bool gf_log_use_color() @@ -843,6 +994,9 @@ return (log_cbk == default_log_callback_color) ? GF_TRUE : GF_FALSE; } +static Bool in_log_callback = GF_FALSE; + + GF_EXPORT void gf_log(const char *fmt, ...) { @@ -853,12 +1007,17 @@ }); } #endif - va_list vl; - va_start(vl, fmt); gf_mx_p(logs_mx); - log_cbk(user_log_cbk, call_lev, call_tool, fmt, vl); + //don't allow GF_LOG to be called from GF_LOG this will likely throw infinite recursion + if (!in_log_callback) { + va_list vl; + va_start(vl, fmt); + in_log_callback = GF_TRUE; + log_cbk(user_log_cbk, call_lev, call_tool, fmt, vl); + in_log_callback = GF_FALSE; + va_end(vl); + } gf_mx_v(logs_mx); - va_end(vl); #ifdef GPAC_CONFIG_EMSCRIPTEN if (gpac_log_console && (call_tool!=GF_LOG_APP)) { EM_ASM({ @@ -874,7 +1033,15 @@ GF_EXPORT void gf_log_va_list(GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vl) { - log_cbk(user_log_cbk, call_lev, call_tool, fmt, vl); + gf_mx_p(logs_mx); + //don't allow GF_LOG to be called from GF_LOG this will likely throw infinite recursion + if (!in_log_callback) { + in_log_callback = GF_TRUE; + log_cbk(user_log_cbk, call_lev, call_tool, fmt, vl); + in_log_callback = GF_FALSE; + } + gf_mx_v(logs_mx); + if (log_exit_on_error && (call_lev==GF_LOG_ERROR) && (call_tool != GF_LOG_MEMORY)) { exit(1); } @@ -920,6 +1087,11 @@ } #else + +void gf_logs_thread_tag(void *tag_val, u32 tag_type){} +void gf_logs_thread_untag(void *tag_val){} +void gf_logs_thread_tag_del(void *tag_val){} + GF_EXPORT void gf_log(const char *fmt, ...) { @@ -1080,6 +1252,8 @@ return "Requires a new instance of the filter to be supported"; case GF_FILTER_NOT_SUPPORTED: return "Not supported by any filter chain"; + case GF_IO_BYTE_RANGE_NOT_SUPPORTED: + return "Byte Range request not supported by server"; default: sprintf(szErrMsg, "Unknown Error (%d)", e); return szErrMsg; @@ -1261,6 +1435,9 @@ #ifdef GPAC_HAS_HTTP2 "GPAC_HAS_HTTP2 " #endif +#ifdef GPAC_HAS_NGTCP2 + "GPAC_HAS_NGTCP2 " +#endif #if defined(_WIN32_WCE) #ifdef GPAC_USE_IGPP @@ -1473,6 +1650,9 @@ #ifdef GPAC_DISABLE_RFAC3 "GPAC_DISABLE_RFAC3 " #endif +#ifdef GPAC_DISABLE_RFAC4 + "GPAC_DISABLE_RFAC4 " +#endif #ifdef GPAC_DISABLE_RFADTS "GPAC_DISABLE_RFADTS " #endif @@ -1539,6 +1719,9 @@ #ifdef GPAC_DISABLE_UFMHAS "GPAC_DISABLE_UFMHAS " #endif +#ifdef GPAC_DISABLE_UFAC4 + "GPAC_DISABLE_UFAC4 " +#endif #ifdef GPAC_DISABLE_UFM4V "GPAC_DISABLE_UFM4V " #endif @@ -2222,11 +2405,12 @@ if (all_num) { u32 div_trail_zero = 1; sscanf(value, LLD"."LLU, &frac->num, &frac->den); - i=0; frac->den = 1; while (i<len) { i++; + if (frac->den > GF_UINT64_MAX / 10) + return GF_FALSE; frac->den *= 10; } //trash trailing zero @@ -2235,13 +2419,14 @@ if (sepi != '0') { break; } + if (div_trail_zero > GF_UINT_MAX / 10) + return GF_FALSE; div_trail_zero *= 10; i--; } - frac->num *= frac->den / div_trail_zero; - frac->num += atoi(sep+1) / div_trail_zero; + frac->num += atoll(sep+1) / div_trail_zero; frac->den /= div_trail_zero; return GF_TRUE; @@ -2262,7 +2447,7 @@ Bool res; if (!frac) return GF_FALSE; res = gf_parse_lfrac(value, &r); - while ((r.num >= 0x80000000) && (r.den > 1000)) { + while ((r.num >= 0x80000000) && (r.den >= 1000)) { r.num /= 1000; r.den /= 1000; } @@ -2304,3 +2489,41 @@ } return NULL; } + +GF_EXPORT +Bool gf_sys_solve_path(const char *url, char szPathGF_MAX_PATH) +{ + char *path; + u32 radlen=6; + Bool rem_name=GF_FALSE; + if (!strncmp(url, "$GCFG", 5)) { + path = (char *)gf_opts_get_filename(); + rem_name = GF_TRUE; + radlen=5; + } else { +#ifdef WIN32 + path = getenv("HOMEPATH"); +#elif defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS) + path = (char *) gf_opts_get_key("core", "docs-dir"); +#else + path = getenv("HOME"); +#endif + } + + if (path && path0) { + strncpy(szPath, path, GF_MAX_PATH-1); + szPathGF_MAX_PATH-1 = 0; + if (rem_name) { + char *sep = strrchr(szPath, '/'); + if (!sep) sep = strrchr(szPath, '\\'); + if (sep) sep0 = 0; + } + u32 len = (u32) strlen(szPath); + if ((szPathlen-1=='/') || (szPathlen-1=='\\')) + szPathlen-1=0; + + strncat(szPath, url+radlen, GF_MAX_PATH-strlen(szPath)-1); + return GF_TRUE; + } + return GF_FALSE; +}
View file
gpac-2.4.0.tar.gz/src/utils/gltools.c -> gpac-26.02.0.tar.gz/src/utils/gltools.c
Changed
@@ -197,10 +197,7 @@ { if (gl_fun_loaded) return; gl_fun_loaded = GF_TRUE; - - if (gf_opts_get_bool("core", "rmt-ogl")) { - rmt_BindOpenGL(); - } + #ifndef GPAC_USE_TINYGL @@ -644,7 +641,7 @@ uniform float _gf_%s_gpu_width;\ "; -//uploaded as RGBA describing 2 bits lost (in A, last byte) + 3 * 10 bit {U,Y,V} , pattern reapeating 6 times +//uploaded as RGBA describing 2 bits lost (in A, last byte) + 3 * 10 bit {U,Y,V} , pattern repeating 6 times //first pattern (x%6==0) is {00b, Cr, Y, Cb} in {A, B, G, R} bytes //we decompose RGBA pixel x and x+1 as follows //.R: 8 bit R + 2 bits G @@ -742,6 +739,20 @@ return ocol;\n\ "; +static char *gl_shader_fun_rgb332 = \ +"vec4 col, ocol;\n\ +float res, col_r, col_g, col_b;\n\ +col = texture2D(_gf_%s_1, _gpacTexCoord.st);\n\ +res = floor( 255.0 * col.r );\n\ +col_r = floor(res / 32.0);\n\ +col_g = floor((res - col_r*32.0)/4.0);\n\ +col_b = floor(res - col_r*128.0 - col_g*4.0);\n\ +ocol.r = col_r / 7.0;\n\ +ocol.g = col_g / 7.0;\n\ +ocol.b = col_b / 3.0;\n\ +ocol.a = 1.0;\n\ +return ocol;\ +"; static char *gl_shader_fun_rgb444 = \ "vec4 col, ocol;\n\ @@ -901,6 +912,10 @@ shader_vars = gl_shader_vars_rgb; shader_fun = gl_shader_fun_alphagrey; break; + case GF_PIXEL_RGB_332: + shader_vars = gl_shader_vars_rgb; + shader_fun = gl_shader_fun_rgb332; + break; case GF_PIXEL_RGB_444: shader_vars = gl_shader_vars_rgb; shader_fun = gl_shader_fun_rgb444; @@ -1152,6 +1167,10 @@ tx->gl_format = GL_RGBA; tx->bytes_per_pix = 4; break; + case GF_PIXEL_RGB_332: + tx->gl_format = GL_LUMINANCE; + tx->bytes_per_pix = 1; + break; case GF_PIXEL_RGB_444: tx->gl_format = GL_LUMINANCE_ALPHA; tx->bytes_per_pix = 2; @@ -1488,7 +1507,7 @@ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); } - + if (pV) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, tx->PBOs2); ptr =(u8 *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);
View file
gpac-2.4.0.tar.gz/src/utils/math.c -> gpac-26.02.0.tar.gz/src/utils/math.c
Changed
@@ -734,6 +734,26 @@ 2D MATRIX TOOLS */ +//sanity checker +#if 0 +void do_check_mx(GF_Matrix2D *_this) +{ + if ( + (ABS(_this->m0)>1000000) + || (ABS(_this->m1)>100000) + || (ABS(_this->m2)>100000) + || (ABS(_this->m3)>100000) + || (ABS(_this->m4)>100000) + || (ABS(_this->m5)>100000) + ) { + exit(120); + } +} +#define CHECK_MATRIX do_check_mx(_this); +#else +#define CHECK_MATRIX +#endif + GF_EXPORT void gf_mx2d_add_matrix(GF_Matrix2D *_this, GF_Matrix2D *from) { @@ -752,6 +772,8 @@ _this->m3 = gf_mulfix(from->m3, bck.m0) + gf_mulfix(from->m4, bck.m3); _this->m4 = gf_mulfix(from->m3, bck.m1) + gf_mulfix(from->m4, bck.m4); _this->m5 = gf_mulfix(from->m3, bck.m2) + gf_mulfix(from->m4, bck.m5) + from->m5; + + CHECK_MATRIX } @@ -773,6 +795,8 @@ _this->m3 = gf_mulfix(bck.m3, with->m0) + gf_mulfix(bck.m4, with->m3); _this->m4 = gf_mulfix(bck.m3, with->m1) + gf_mulfix(bck.m4, with->m4); _this->m5 = gf_mulfix(bck.m3, with->m2) + gf_mulfix(bck.m4, with->m5) + bck.m5; + + CHECK_MATRIX } @@ -785,6 +809,8 @@ tmp.m2 = cx; tmp.m5 = cy; gf_mx2d_add_matrix(_this, &tmp); + + CHECK_MATRIX } @@ -803,6 +829,8 @@ tmp.m1 = -1 * tmp.m3; gf_mx2d_add_matrix(_this, &tmp); gf_mx2d_add_translation(_this, cx, cy); + + CHECK_MATRIX } GF_EXPORT @@ -814,6 +842,8 @@ tmp.m0 = scale_x; tmp.m4 = scale_y; gf_mx2d_add_matrix(_this, &tmp); + + CHECK_MATRIX } GF_EXPORT @@ -829,6 +859,8 @@ tmp.m4 = scale_y; gf_mx2d_add_matrix(_this, &tmp); if (angle) gf_mx2d_add_rotation(_this, cx, cy, angle); + + CHECK_MATRIX } GF_EXPORT @@ -840,6 +872,8 @@ tmp.m1 = skew_x; tmp.m3 = skew_y; gf_mx2d_add_matrix(_this, &tmp); + + CHECK_MATRIX } GF_EXPORT @@ -851,6 +885,8 @@ tmp.m1 = gf_tan(angle); tmp.m3 = 0; gf_mx2d_add_matrix(_this, &tmp); + + CHECK_MATRIX } GF_EXPORT @@ -862,6 +898,8 @@ tmp.m1 = 0; tmp.m3 = gf_tan(angle); gf_mx2d_add_matrix(_this, &tmp); + + CHECK_MATRIX } @@ -920,6 +958,8 @@ #endif gf_mx2d_copy(*_this, tmp); + + CHECK_MATRIX } #endif @@ -992,8 +1032,8 @@ rc->height = MIN(c1.y, MIN(c2.y, MIN(c3.y, c4.y))); rc->y = MAX(c1.y, MAX(c2.y, MAX(c3.y, c4.y))); rc->height = rc->y - rc->height; - gf_assert(rc->height>=0); - gf_assert(rc->width>=0); +// gf_assert(rc->height>=0); +// gf_assert(rc->width>=0); } /*
View file
gpac-26.02.0.tar.gz/src/utils/md5.c
Added
@@ -0,0 +1,226 @@ +/* + * from https://github.com/Zunawe/md5-c, public domain + * + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + * and modified slightly to be functionally identical but condensed into control structures. + */ + +#include <gpac/tools.h> + +typedef struct{ + u64 size; // Size of input in bytes + u32 buffer4; // Current accumulation of hash + u8 input64; // Input to be used in the next step + u8 digest16; // Result of algorithm +} MD5Context; + +/* +void md5Init(MD5Context *ctx); +void md5Update(MD5Context *ctx, u8 *input, size_t input_len); +void md5Finalize(MD5Context *ctx); +*/ +static void md5Step(u32 *buffer, u32 *input); + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xefcdab89 +#define C 0x98badcfe +#define D 0x10325476 + +static u32 S = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +static u32 K = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +static u8 PADDING = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +#define F(X, Y, Z) ((X & Y) | (~X & Z)) +#define G(X, Y, Z) ((X & Z) | (Y & ~Z)) +#define H(X, Y, Z) (X ^ Y ^ Z) +#define I(X, Y, Z) (Y ^ (X | ~Z)) + +/* + * Rotates a 32-bit word left by n bits + */ +u32 rotateLeft(u32 x, u32 n){ + return (x << n) | (x >> (32 - n)); +} + + +/* + * Initialize a context + */ +void md5Init(MD5Context *ctx){ + ctx->size = (u64)0; + + ctx->buffer0 = (u32)A; + ctx->buffer1 = (u32)B; + ctx->buffer2 = (u32)C; + ctx->buffer3 = (u32)D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ +void md5Update(MD5Context *ctx, u8 *input_buffer, size_t input_len){ + u32 input16; + unsigned int offset = ctx->size % 64; + ctx->size += (u64)input_len; + + // Copy each byte in input_buffer into the next space in our context input + for(unsigned int i = 0; i < input_len; ++i){ + ctx->inputoffset++ = (u8)*(input_buffer + i); + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer. + // Every time we fill out a chunk, we run it through the algorithm + // to enable some back and forth between cpu and i/o + if(offset % 64 == 0){ + for(unsigned int j = 0; j < 16; ++j){ + // Convert to little-endian + // The local variable `input` our 512-bit chunk separated into 32-bit words + // we can use in calculations + inputj = (u32)(ctx->input(j * 4) + 3) << 24 | + (u32)(ctx->input(j * 4) + 2) << 16 | + (u32)(ctx->input(j * 4) + 1) << 8 | + (u32)(ctx->input(j * 4)); + } + md5Step(ctx->buffer, input); + offset = 0; + } + } +} + +/* + * Pad the current input to get to 448 bytes, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize(MD5Context *ctx){ + u32 input16; + unsigned int offset = ctx->size % 64; + unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding and undo the changes to size that resulted from the update + md5Update(ctx, PADDING, padding_length); + ctx->size -= (u64)padding_length; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + for(unsigned int j = 0; j < 14; ++j){ + inputj = (u32)(ctx->input(j * 4) + 3) << 24 | + (u32)(ctx->input(j * 4) + 2) << 16 | + (u32)(ctx->input(j * 4) + 1) << 8 | + (u32)(ctx->input(j * 4)); + } + input14 = (u32)(ctx->size * 8); + input15 = (u32)((ctx->size * 8) >> 32); + + md5Step(ctx->buffer, input); + + // Move the result into digest (convert from little-endian) + for(unsigned int i = 0; i < 4; ++i){ + ctx->digest(i * 4) + 0 = (u8)((ctx->bufferi & 0x000000FF)); + ctx->digest(i * 4) + 1 = (u8)((ctx->bufferi & 0x0000FF00) >> 8); + ctx->digest(i * 4) + 2 = (u8)((ctx->bufferi & 0x00FF0000) >> 16); + ctx->digest(i * 4) + 3 = (u8)((ctx->bufferi & 0xFF000000) >> 24); + } +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +static void md5Step(u32 *buffer, u32 *input){ + u32 AA = buffer0; + u32 BB = buffer1; + u32 CC = buffer2; + u32 DD = buffer3; + + u32 E; + + unsigned int j; + + for(unsigned int i = 0; i < 64; ++i){ + switch(i / 16){ + case 0: + E = F(BB, CC, DD); + j = i; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) % 16; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) % 16; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) % 16; + break; + } + + u32 temp = DD; + DD = CC; + CC = BB; + BB = BB + rotateLeft(AA + E + Ki + inputj, Si); + AA = temp; + } + + buffer0 += AA; + buffer1 += BB; + buffer2 += CC; + buffer3 += DD; +} + +/* + * Functions that run the algorithm on the provided input and put the digest into result. + * result should be able to store 16 bytes. + */ +#define GF_MD5_DIGEST_SIZE 16 + +GF_EXPORT +void gf_md5_csum(const void *data, u32 len, u8 outputGF_MD5_DIGEST_SIZE) +{ + MD5Context ctx; + md5Init(&ctx); + md5Update(&ctx, (u8 *)data, len); + md5Finalize(&ctx); + memcpy(output, ctx.digest, 16); +} + +
View file
gpac-2.4.0.tar.gz/src/utils/module.c -> gpac-26.02.0.tar.gz/src/utils/module.c
Changed
@@ -412,7 +412,7 @@ return ifce; err_exit: - GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Core Load interface %s exit label, freing library...\n", inst->name)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Core Load interface %s exit label, freeing library...\n", inst->name)); gf_modules_unload_library(inst); GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Core Load interface %s EXIT.\n", inst->name)); gf_mx_v(pm->mutex);
View file
gpac-2.4.0.tar.gz/src/utils/os_config_init.c -> gpac-26.02.0.tar.gz/src/utils/os_config_init.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2023 + * Copyright (c) Telecom ParisTech 2000-2026 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -335,7 +335,7 @@ #else //dlinfo -#if defined(__DARWIN__) || defined(__APPLE__) +#if defined(__DARWIN__) || defined(__APPLE__) || defined(__FreeBSD__) #include <dlfcn.h> typedef Dl_info _Dl_info; @@ -357,7 +357,7 @@ { char app_pathGF_MAX_PATH; char *sep; -#if (defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_LINUX)) +#if (defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_LINUX) || defined(__FreeBSD__)) u32 size; #endif @@ -419,7 +419,7 @@ return 1; } -#elif defined(GPAC_CONFIG_LINUX) +#elif defined(GPAC_CONFIG_LINUX) || defined(__FreeBSD__) size = readlink("/proc/self/exe", file_path, GF_MAX_PATH-1); if (size>0) { file_pathsize = 0; @@ -456,7 +456,7 @@ } if (path_type==GF_PATH_LIB) { -#if defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_LINUX) +#if defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_LINUX) || defined(__FreeBSD__) _Dl_info dl_info; dl_info.dli_fname = NULL; if (dladdr((void *)get_default_install_path, &dl_info) @@ -601,6 +601,8 @@ sep0 = 0; strcat(app_path, "/share"); if (check_file_exists("gui/gui.bt", app_path, file_path)) return 1; + strcat(app_path, "/gpac"); + if (check_file_exists("gui/gui.bt", app_path, file_path)) return 1; } if (try_lib) { try_lib = GF_FALSE; @@ -845,6 +847,7 @@ gf_cfg_set_key(cfg, "core", "font-dirs", szPath); gf_cfg_set_key(cfg, "core", "cache-size", "100M"); + gf_cfg_set_key(cfg, "core", "cache-check", "0"); #if defined(_WIN32_WCE) gf_cfg_set_key(cfg, "core", "video-output", "gapi"); @@ -882,6 +885,9 @@ gf_cfg_set_key(cfg, "core", "startup-file", gui_path); } + sprintf(gui_path, "%s%cres%cca-bundle.crt", szPath, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + gf_cfg_set_key(cfg, "core", "ca-bundle-default", gui_path); + /*shaders are at the same location*/ sprintf(gui_path, "%s%cshaders%cvertex.glsl", szPath, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); gf_cfg_set_key(cfg, "filter@compositor", "vertshader", gui_path); @@ -1175,6 +1181,18 @@ } if (rescan_fonts) gf_opts_set_key("core", "rescan-fonts", "yes"); + + // if ca-bundle is not set or explicitly disabled (empty string), set to default + const char* ca_bundle = gf_cfg_get_key(cfg, "core", "ca-bundle-default"); + if (!ca_bundle) { + char szShareGF_MAX_PATH; + if (get_default_install_path(szShare, GF_PATH_SHARE)) { + char gui_pathGF_MAX_PATH + 100; + + sprintf(gui_path, "%s%cres%cca-bundle.crt", szShare, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + gf_cfg_set_key(cfg, "core", "ca-bundle-default", gui_path); + } + } } } //no config file found @@ -1389,7 +1407,7 @@ #endif GF_GPACArg GPAC_Args = { - GF_DEF_ARG("tmp", NULL, "specify directory for temporary file creation instead of OS-default temporary file management", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("tmp", NULL, "specify directory for temporary file creation instead of OS-default temporary file management", NULL, NULL, GF_ARG_STRING, GF_ARG_SUBSYS_CORE), GF_DEF_ARG("noprog", NULL, "disable progress messages", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_LOG), GF_DEF_ARG("quiet", NULL, "disable all messages, including errors", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_LOG), GF_DEF_ARG("log-file", "lf", LOGFILE_HELP, NULL, NULL, GF_ARG_STRING, GF_ARG_SUBSYS_LOG), @@ -1405,6 +1423,7 @@ "- warning: logs error+warning messages\n" "- info: logs error+warning+info messages\n" "- debug: logs all messages\n" + "- strict: exit if error for this log tool and use default log level if tool\n" "\n`toolX` can be one of:\n" "- core: libgpac core\n" "- mutex: log all mutex calls\n" @@ -1432,9 +1451,10 @@ "- ctime: media and SMIL timing info from composition engine\n" "- interact: interaction messages (UI events and triggered DOM events and VRML route)\n" "- rti: run-time stats of compositor\n" - "- all: all tools logged - other tools can be specified afterwards. \n" + "- all: all tools logged - other tools can be specified afterwards\n" "The special keyword `ncl` can be set to disable color logs. \n" - "The special keyword `strict` can be set to exit at first error. \n" + "The special keyword `strict` can be set to exit at first error on any tool. \n" + "`levelX` can accept the suffix `+strict` to force strict error only for the given log tool(s). \n" "\nEX -logs=all@info:dash@debug:ncl\n" "This moves all log to info level, dash to debug level and disable color logs" , NULL, NULL, GF_ARG_STRING, GF_ARG_SUBSYS_LOG), @@ -1480,6 +1500,10 @@ GF_DEF_ARG("no-tls-rcfg", NULL, "disable automatic TCP to TLS reconfiguration", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_CORE), GF_DEF_ARG("no-fd", NULL, "use buffered IO instead of file descriptor for read/write - this can speed up operations on small files", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_CORE), GF_DEF_ARG("no-mx", NULL, "disable all mutexes, threads and semaphores (do not use if unsure about threading used)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_CORE), + GF_DEF_ARG("xml-max-csize", NULL, "maximum XML content or attribute size", "100k", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_CORE), + GF_DEF_ARG("users", NULL, "authentication configuration file for users and groups", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_CORE), + + #ifndef GPAC_DISABLE_NETCAP GF_DEF_ARG("netcap", NULL, "set packet capture and filtering rules formatted as CFGRULES. Each `-netcap` argument will define a configuration\n" "CFG is an optional comma-separated list of:\n" @@ -1489,15 +1513,18 @@ "- loop=N: loop capture file N times, or forever if N is not set or negative\n" "- nrt: disable real-time playback\n" "RULES is an optional list of `OPT,OPT2...` with OPT in:\n" - "- m=N: set rule mode - `N` can be `r` for reception only (default), `w` for send only or `rw` for both\n" - "- s=N: set packet start range to `N`\n" - "- e=N: set packet end range to `N` (only used for `r` and `f` rules)\n" - "- n=N: set number of packets to drop to `N` - not set, 0 or 1 means single packet\n" - "- r=N: random drop one packet every `N`\n" - "- f=N: drop first packet every `N`\n" - "- p=P: local port number to filter, if not set the rule applies to all packets\n" - "- o=N: patch packet instead of droping (always true for TCP), replacing byte at offset `N` (0 is first byte, <0 for random)\n" - "- v=N: set patch byte value to `N` (hexa) or negative value for random (default)\n" + "- m=K: set rule mode - `K` can be `r` for reception only (default), `w` for send only or `rw` for both\n" + "- s=K: set packet start range to `K`\n" + "- e=K: set packet end range to `K` - only used for `r` and `f` rules, 0 or not set means rule apply until end\n" + "- n=K: set number of packets to drop to `K` - not set, 0 or 1 means single packet\n" + "- r=K: random drop `n` packet every `K`\n" + "- f=K: drop first `n` packets every `K`\n" + "- d=K: reorder `n` packets after the next `K` packets, can be used with `f` or `r` rules\n" + "- p=K: filter packets on port `K` only, if not set the rule applies to all packets\n" + "- o=K: patch packet instead of dropping (always true for TCP), replacing byte at offset `K` (0 is first byte, <0 for random)\n" + "- v=K: set patch byte value to `K` (hexa) or negative value for random (default)\n" + "- S=K: same as `s` but adds number of capture file reload/loop\n" + "- E=K: same as `e` but adds number of capture file reload/loop\n" "\nEX -netcap=dst=dump.gpc\n" "This will record packets to dump.gpc\n" "\nEX -netcap=src=dump.gpc,id=NC1 -i session1.sdp:NCID=NC1 -i session2.sdp\n" @@ -1507,18 +1534,18 @@ #endif GF_DEF_ARG("cache", NULL, "cache directory location", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), - GF_DEF_ARG("proxy-on", NULL, "enable HTTP proxy", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), - GF_DEF_ARG("proxy-name", NULL, "set HTTP proxy address", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), - GF_DEF_ARG("proxy-port", NULL, "set HTTP proxy port", "80", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), + GF_DEF_ARG("proxy", NULL, "set HTTP proxy server address and port (if no protocol scheme is set, use same as target)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("maxrate", NULL, "set max HTTP download rate in bits per sec. 0 means unlimited", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("no-cache", NULL, "disable HTTP caching", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("offline-cache", NULL, "enable offline HTTP caching (no re-validation of existing resource in cache)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("clean-cache", NULL, "indicate if HTTP cache should be clean upon launch/exit", NULL, NULL, GF_ARG_BOOL, GF_ARG_SUBSYS_HTTP), - GF_DEF_ARG("cache-size", NULL, "specify cache size in bytes", "100M", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), + GF_DEF_ARG("cache-size", NULL, "specify maximum cache size on disk in bytes", "100M", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), + GF_DEF_ARG("cache-check", NULL, "cache clean interval in seconds, 0 only clean cache at startup", "60", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("tcp-timeout", NULL, "time in milliseconds to wait for HTTP/RTSP connect before error", "5000", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("req-timeout", NULL, "time in milliseconds to wait on HTTP/RTSP request before error (0 disables timeout)", "10000", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("no-timeout", NULL, "ignore HTTP 1.1 timeout in keep-alive", "false", NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("broken-cert", NULL, "enable accepting broken SSL certificates", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), + GF_DEF_ARG("ca-bundle", NULL, "path to a custom CA certificates bundle file", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT | GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("user-agent", "ua", "set user agent name for HTTP/RTSP", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("user-profileid", NULL, "set user profile ID (through **X-UserProfileID** entity header) in HTTP requests", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("user-profile", NULL, "set user profile filename. Content of file is appended as body to HTTP HEAD/GET requests, associated Mime is **text/xml**", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), @@ -1527,12 +1554,31 @@ GF_DEF_ARG("cte-rate-wnd", NULL, "set window analysis length in milliseconds for chunk-transfer encoding rate estimation", "20", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("cred", NULL, "path to 128 bits key for credential storage", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), -#ifdef GPAC_HAS_HTTP2 +#if defined(GPAC_HAS_HTTP2) || defined(GPAC_HAS_CURL) GF_DEF_ARG("no-h2", NULL, "disable HTTP2", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), +#endif +#ifdef GPAC_HAS_HTTP2 GF_DEF_ARG("no-h2c", NULL, "disable HTTP2 upgrade (i.e. over non-TLS)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), GF_DEF_ARG("h2-copy", NULL, "enable intermediate copy of data in nghttp2 (default is disabled but may report as broken frames in wireshark)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), #endif +#ifdef GPAC_HAS_CURL + GF_DEF_ARG("curl", NULL, "use CURL instead of GPAC HTTP stack", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), +#endif + +#if defined(GPAC_HAS_NGTCP2) || defined(GPAC_HAS_CURL) + GF_DEF_ARG("h3", NULL, "set HTTP/3 mode\n" + "- no: disable HTTP/3\n" + "- first: force trying first with HTTP/3\n" + "- auto: connect using HTTP 1 or 2 and use HTTP/3 for next request(s) if announced\n" + "- only: only use HTTP/3", "auto", "no|first|auto|only", GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), +#endif + +#ifdef GPAC_HAS_NGTCP2 + GF_DEF_ARG("h3-trace", NULL, "trace QUIC and HTTP3", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HTTP), +#endif + + GF_DEF_ARG("dbg-edges", NULL, "log edges status in filter graph before dijkstra resolution (for debug). Edges are logged as edge_source(status(disable_depth), weight, src_cap_idx -> dst_cap_idx)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("full-link", NULL, "throw error if any PID in the filter graph cannot be linked", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("no-dynf", NULL, "disable dynamically loaded filters", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), @@ -1544,13 +1590,14 @@ GF_DEF_ARG("no-reg", NULL, "disable regulation (no sleep) in session", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("no-reassign", NULL, "disable source filter reassignment in PID graph resolution", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("sched", NULL, "set scheduler mode\n" - "- free: lock-free queues except for task list (default)\n" - "- lock: mutexes for queues when several threads\n" + "- free: lock-free queues except for task list (default on most platforms)\n" + "- lock: mutexes for queues when several threads (default on arm64/aarch64)\n" "- freex: lock-free queues including for task lists (experimental)\n" "- flock: mutexes for queues even when no thread (debug mode)\n" - "- direct: no threads and direct dispatch of tasks whenever possible (debug mode)", "free", "free|lock|flock|freex|direct", GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), + "- direct: no threads and direct dispatch of tasks whenever possible (debug mode)", GPAC_SCHED_DEFAULT, "free|lock|flock|freex|direct", GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("max-chain", NULL, "set maximum chain length when resolving filter links. Default value covers for __ in -> dmx -> reframe -> decode -> encode -> reframe -> mx -> out__. Filter chains loaded for adaptation (e.g. pixel format change) are loaded after the link resolution. Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually", "6", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("max-sleep", NULL, "set maximum sleep time slot in milliseconds when regulation is enabled", "50", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), + GF_DEF_ARG("step-link", NULL, "load filters one by one when solvink a link instead of loading all filters for the solved path", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("threads", NULL, "set N extra thread for the session. -1 means use all available cores", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("no-probe", NULL, "disable data probing on sources and relies on extension (faster load but more error-prone)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_FILTERS), @@ -1562,6 +1609,8 @@ GF_DEF_ARG("buffer-dec", NULL, "default buffer size in microseconds for decoder input pids", "1000000", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_FILTERS), GF_DEF_ARG("buffer-units", NULL, "default buffer size in frames when timing is not available", "1", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_FILTERS), + GF_DEF_ARG("check-props", NULL, "check known property types upon assignment and PID vs packet types upon fetch (in test mode, exit with error code 5 if mismatch)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_FILTERS), + GF_DEF_ARG("gl-bits-comp", NULL, "number of bits per color component in OpenGL", "8", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_VIDEO), GF_DEF_ARG("gl-bits-depth", NULL, "number of bits for depth buffer in OpenGL", "16", NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_VIDEO), GF_DEF_ARG("gl-doublebuf", NULL, "enable OpenGL double buffering", "yes", NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_VIDEO), @@ -1582,22 +1631,31 @@ "- utf16: force UTF-16 little endian\n" "- utf16be: force UTF-16 big endian\n" "- other: attempt to parse anyway", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED|GF_ARG_SUBSYS_TEXT), + GF_DEF_ARG("srt-forced", NULL, "enable SRT with forced subtitles extensions", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_TEXT), - GF_DEF_ARG("rmt", NULL, "enable profiling through Remotery(https://github.com/Celtoys/Remotery). A copy of Remotery visualizer is in gpac/share/vis, usually installed in __/usr/share/gpac/vis__ or __Program Files/GPAC/vis__", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-port", NULL, "set remotery port", "17815", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-reuse", NULL, "allow remotery to reuse port", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-localhost", NULL, "make remotery only accepts localhost connection", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-sleep", NULL, "set remotery sleep (ms) between server updates", "10", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-nmsg", NULL, "set remotery number of messages per update", "10", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-qsize", NULL, "set remotery message queue size in bytes", "131072", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-log", NULL, "redirect logs to remotery (experimental, usually not well handled by browser)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), - GF_DEF_ARG("rmt-ogl", NULL, "make remotery sample opengl calls", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt", NULL, "enable remote monitoring webserver", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt-port", NULL, "set rmt ws port", "6363", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt-localhost", NULL, "make rmt ws only accepts localhost connection", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt-sleep", NULL, "set rmt ws sleep (ms) between server updates", "10", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt-cert", NULL, "rmt ws: certificate file in PEM format to use for TLS mode", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt-pkey", NULL, "rmt ws: private key file in PEM format to use for TLS mode", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("rmt-path", NULL, "rmt ws: path to JS backend", "$GSHARE/scripts/rmt/server.js", NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("userws-port", NULL, "set user ws port", "6364", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("userws-localhost", NULL, "make userws ws only accepts localhost connection", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("userws-sleep", NULL, "set userws sleep (ms) between server updates", "10", NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("userws-cert", NULL, "userws: certificate file in PEM format to use for TLS mode", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + GF_DEF_ARG("userws-pkey", NULL, "userws: private key file in PEM format to use for TLS mode", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_RMT), + + + GF_DEF_ARG("diso-nosize", NULL, "skip box size info when dumping ISOBMFF", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_CORE), GF_DEF_ARG("m2ts-vvc-old", NULL, "hack for old TS streams using 0x32 for VVC instead of 0x33", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), GF_DEF_ARG("piff-force-subsamples", NULL, "hack for PIFF PSEC files generated by 0.9.0 and 1.0 MP4Box with wrong subsample_count inserted for audio", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), GF_DEF_ARG("vvdec-annexb", NULL, "hack for old vvdec+libavcodec supporting only annexB format", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), GF_DEF_ARG("heif-hevc-urn", NULL, "use HEVC URN for alpha and depth in HEIF instead of MPEG-B URN (HEIF first edition)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), GF_DEF_ARG("boxdir", NULL, "use box definitions in the given directory for XML dump", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), + GF_DEF_ARG("no-mabr-patch", NULL, "disable GPAC parsing of patched isom boxes from mabr (will behave like most browsers/players)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), + GF_DEF_ARG("no-cdrf", NULL, "disable cdrf sample dep", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT|GF_ARG_SUBSYS_HACKS), {0} @@ -1944,10 +2002,15 @@ fprintf(helpout, ".TP\n.B %s%s", (flags&GF_PRINTARG_NO_DASH) ? "" : "\\-", arg_name ? arg_name : arg->name); } else if (gen_doc==1) { + fprintf(helpout,"<div markdown class=\"option\">\n"); if (flags&GF_PRINTARG_NO_DASH) { gf_sys_format_help(helpout, flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s", arg_name ? arg_name : arg->name); } else { - gf_sys_format_help(helpout, flags, "<a id=\"%s\">", arg_name ? arg_name : arg->name); + if (arg->flags & (GF_ARG_HINT_ADVANCED|GF_ARG_HINT_EXPERT)) { + gf_sys_format_help(helpout, flags, "<a id=\"%s\">", arg_name ? arg_name : arg->name); + } else { + gf_sys_format_help(helpout, flags, "<a id=\"%s\" data-level=\"basic\">", arg_name ? arg_name : arg->name); + } gf_sys_format_help(helpout, flags | GF_PRINTARG_HIGHLIGHT_FIRST, "-%s", arg_name ? arg_name : arg->name); gf_sys_format_help(helpout, flags, "</a>"); } @@ -2001,6 +2064,9 @@ gf_sys_format_help(helpout, flags | GF_PRINTARG_OPT_DESC, ": %s", gf_sys_localized(arg_subsystem, arg->name, arg->description) ); } gf_sys_format_help(helpout, flags, "\n"); + if(gen_doc==1) { + fprintf(helpout, "</div>\n"); + } } if ((gen_doc==1) && arg->description && strstr(arg->description, "- ")) @@ -2126,6 +2192,8 @@ Bool escape_pipe = GF_FALSE; Bool prev_was_example = GF_FALSE; Bool prev_has_line_after = GF_FALSE; + Bool prev_has_colon = GF_FALSE; + u32 list_depth = 0; u32 gen_doc = 0; u32 is_app_opts = 0; if (flags & GF_PRINTARG_MD) { @@ -2175,6 +2243,8 @@ GF_ConsoleCodes console_code = GF_CONSOLE_RESET; Bool line_before = GF_FALSE; Bool line_after = GF_FALSE; + Bool add_backquote = GF_FALSE; + const char *footer_string = NULL; const char *header_string = NULL; char *next_line = strchr(line, '\n'); @@ -2190,6 +2260,22 @@ } if (!line0) flags &= ~GF_PRINTARG_HIGHLIGHT_FIRST; + //detect list start/end for mkdocs + if (gen_doc==1) { + u32 llev = 0; + if (!strncmp(line, "- ", 2)) llev=1; + else if (!strncmp(line, " - ", 3) || !strncmp(line, " - ", 3)) llev=2; + else if (!strncmp(line, " - ", 6)) llev=3; + if (llev>list_depth) { + list_depth = llev; + fprintf(helpout, "\n"); + } + else if (llev<list_depth) { + list_depth = llev; + fprintf(helpout, "\n"); + } + } + if ((line0=='#') && (line1==' ')) { if (!gen_doc) line+=2; @@ -2226,13 +2312,13 @@ console_code = GF_CONSOLE_YELLOW; if (gen_doc==1) { - header_string = "Example\n```\n"; - footer_string = "\n```"; + header_string = prev_has_colon ? "```\n" : "Example\n```\n"; + footer_string = "\n```\n"; } else if (gen_doc==2) { - header_string = "Example\n.br\n"; + header_string = prev_has_colon ? ".br\n" : "Example\n.br\n"; footer_string = "\n.br\n"; } else { - header_string = "Example:\n"; + header_string = prev_has_colon ? NULL : "Example:\n"; } if (prev_was_example) { @@ -2257,25 +2343,48 @@ ) //look for ": " - && ((tok_sep=strstr(line, ": ")) != NULL ) + && ( ((tok_sep=strstr(line, ": ")) != NULL ) || list_depth) ) { if (!gen_doc) fprintf(helpout, "\t"); while (line0 != '-') { - fprintf(helpout, " "); + if (!list_depth) { + fprintf(helpout, " "); + } line++; line_pos++; } - fprintf(helpout, "* "); + if (list_depth && (gen_doc==1)) { + if (list_depth==3) + fprintf(helpout, " - "); + else if (list_depth==2) + fprintf(helpout, " - "); + else fprintf(helpout, "- "); + //for MD avoid "- #" which corrupts heading levels, enclose with backquote + if (tok_sep && ((line2=='#') || (line3=='#') || (line4=='#'))) { + fprintf(helpout, "`"); + add_backquote=GF_TRUE; + } + } else { + fprintf(helpout, "* "); + } line_pos+=2; if (!gen_doc) gf_sys_set_console_code(helpout, GF_CONSOLE_YELLOW); - tok_sep0 = 0; - fprintf(helpout, "%s", line+2); - line_pos += (u32) strlen(line+2); - tok_sep0 = ':'; - line = tok_sep; + if (tok_sep) { + tok_sep0 = 0; + fprintf(helpout, "%s", line+2); + if (add_backquote) { + fprintf(helpout, "`"); + add_backquote = GF_FALSE; + } + line_pos += (u32) strlen(line+2); + tok_sep0 = ':'; + line = tok_sep; + } else { + line += 2; + } if (!gen_doc) gf_sys_set_console_code(helpout, GF_CONSOLE_RESET); } else if (flags & (GF_PRINTARG_HIGHLIGHT_FIRST | GF_PRINTARG_OPT_DESC)) { @@ -2514,6 +2623,9 @@ } else if (!strncmp(link, "MP4B_GEN", 8)) { fprintf(helpout, "-%s(mp4box-gen-opts/#%s)", line, line); line_pos+=7 + 2* (u32)strlen(line) + (u32)strlen("mp4box-gen-opts"); + } else if (!strncmp(link, "MP4B_IMP", 8)) { + fprintf(helpout, "-%s(mp4box-import-opts/#%s)", line, line); + line_pos+=7 + 2* (u32)strlen(line) + (u32)strlen("mp4box-import-opts"); } else if (strlen(link)) { fprintf(helpout, "-%s(%s/#%s)", line, link, line); line_pos+=7 + 2* (u32)strlen(line) + (u32)strlen(link); @@ -2579,6 +2691,10 @@ } if (!next_line) break; + prev_has_colon = GF_FALSE; + if (line0 && linestrlen(line)-1==':') + prev_has_colon = GF_TRUE; + next_line0=0; if (gen_doc==1) fprintf(helpout, " "); line = next_line+1; @@ -2616,10 +2732,21 @@ s32 dist = 0; u32 match = 0; u32 i; - u32 olen = (u32) strlen(orig); - u32 dlen = (u32) strlen(dst); + u32 olen, dlen; u32 *run; + if (orig == NULL && dst == NULL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, (" gf_sys_word_match: NULL arguments \n")); + return GF_TRUE; + } + if (orig == NULL || dst == NULL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, (" gf_sys_word_match: NULL argument \n")); + return GF_FALSE; + } + + olen = (u32) strlen(orig); + dlen = (u32) strlen(dst); + if ((olen>=3) && (olen<dlen) && !strncmp(orig, dst, olen)) return GF_TRUE; if ((dlen>=3) && (dlen<olen) && !strncmp(orig, dst, dlen))
View file
gpac-2.4.0.tar.gz/src/utils/os_divers.c -> gpac-26.02.0.tar.gz/src/utils/os_divers.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2024 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -140,7 +140,7 @@ { struct timeval now; gettimeofday(&now, NULL); - return (u32) ( ( (now.tv_sec)*1000 + (now.tv_usec) / 1000) - sys_start_time ); + return (u32) ( ( (u64)(now.tv_sec)*1000 + (now.tv_usec) / 1000) - sys_start_time ); } GF_EXPORT @@ -148,7 +148,7 @@ { struct timeval now; gettimeofday(&now, NULL); - return (now.tv_sec)*1000000 + (now.tv_usec) - sys_start_time_hr; + return (u64)(now.tv_sec)*1000000 + (now.tv_usec) - sys_start_time_hr; } #endif @@ -170,7 +170,8 @@ #endif -Bool gf_sys_enable_remotery(Bool start, Bool is_shutdown); +GF_Err gf_sys_enable_rmtws(Bool start); +GF_Err gf_sys_enable_userws(Bool start); static Bool gpac_disable_rti = GF_FALSE; @@ -178,7 +179,6 @@ GF_EXPORT void gf_sleep(u32 ms) { - gf_rmt_begin(sleep, GF_RMT_AGGREGATE); #ifdef WIN32 Sleep(ms); @@ -216,8 +216,6 @@ } while ( sel_err && (errno == EINTR) ); #endif - gf_rmt_end(); - } #ifndef gettimeofday @@ -243,13 +241,13 @@ { int tm_sec; /* seconds after the minute - 0,59 */ int tm_min; /* minutes after the hour - 0,59 */ - int tm_hour; /* hours since midnight - 0,23 */ - int tm_mday; /* day of the month - 1,31 */ + int tm_hour; /* hours since midnight - 0,23 */ + int tm_mday; /* day of the month - 1,31 */ int tm_mon; /* months since January - 0,11 */ - int tm_year; /* years since 1900 */ - int tm_wday; /* days since Sunday - 0,6 */ - int tm_yday; /* days since January 1 - 0,365 */ - int tm_isdst; /* daylight savings time flag */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday - 0,6 */ + int tm_yday; /* days since January 1 - 0,365 */ + int tm_isdst; /* daylight savings time flag */ }; #define _TM_DEFINED #endif /* _TM_DEFINED */ @@ -341,6 +339,8 @@ #elif defined(WIN32) +#include <WinSock2.h> + static s32 gettimeofday(struct timeval *tp, void *tz) { struct _timeb timebuffer; @@ -531,7 +531,7 @@ GF_EXPORT GF_Err gf_prompt_get_size(u32 *width, u32 *height) { - CONSOLE_SCREEN_BUFFER_INFO info; + CONSOLE_SCREEN_BUFFER_INFO info; HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); BOOL ret = GetConsoleScreenBufferInfo(hStdin, &info); @@ -633,12 +633,12 @@ GF_Err gf_prompt_get_size(u32 *width, u32 *height) { #if defined(TIOCGWINSZ) - struct winsize ws; - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != 0) return GF_IO_ERR; + struct winsize ws; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != 0) return GF_IO_ERR; - if (width) *width = ws.ws_col; - if (height) *height = ws.ws_row; - return GF_OK; + if (width) *width = ws.ws_col; + if (height) *height = ws.ws_row; + return GF_OK; #elif defined(WIOCGETD) struct uwdata w; if (ioctl(2, WIOCGETD, &w) != 0) return GF_IO_ERR; @@ -647,9 +647,9 @@ *width = w.uw_width / w.uw_hs; if (height && (w.uw_height > 0)) *height = w.uw_height / w.uw_vs; - return GF_OK; + return GF_OK; #else - return GF_NOT_SUPPORTED; + return GF_NOT_SUPPORTED; #endif } @@ -743,6 +743,8 @@ #ifdef WIN32 +#include <timeapi.h> + static u32 OS_GetSysClockHIGHRES() { LARGE_INTEGER now; @@ -858,6 +860,8 @@ extern FILE *gpac_log_file; extern Bool gpac_log_time_start; extern Bool gpac_log_utc_time; +void gf_logs_init(); +void gf_logs_close(); #endif GF_EXPORT @@ -1065,7 +1069,7 @@ gf_log_reset_file(); #endif if (gf_opts_get_bool("core", "rmt")) - gf_sys_enable_remotery(GF_TRUE, GF_FALSE); + gf_sys_enable_rmtws(GF_TRUE); if (gpac_quiet) { if (gpac_quiet==2) gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_QUIET); @@ -1161,191 +1165,140 @@ } -#ifndef GPAC_DISABLE_REMOTERY -Remotery *remotery_handle=NULL; - -//commented out as it put quite some load on the browser - -gf_log_cbk gpac_prev_default_logs = NULL; - -const char *gf_log_tool_name(GF_LOG_Tool log_tool); -const char *gf_log_level_name(GF_LOG_Level log_level); - -void gpac_rmt_log_callback(void *cbck, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vlist) -{ -#ifndef GPAC_DISABLE_LOG - -#define RMT_LOG_SIZE 5000 - char szMsgRMT_LOG_SIZE; - u32 len; - sprintf(szMsg, "{ \"type\": \"logs\", \"level\": \"%s\" \"tool\": \"%s\", \"value\": \"", gf_log_level_name(level), gf_log_tool_name(tool)); - - len = (u32) strlen(szMsg); - vsnprintf(szMsg, RMT_LOG_SIZE - len - 3, fmt, vlist); - strcat(szMsg, "\"}"); - - rmt_LogText(szMsg); - -#undef RMT_LOG_SIZE +#ifndef GPAC_DISABLE_RMTWS +RMT_WS* rmtws_handle=NULL; +RMT_WS* userws_handle=NULL; +#endif +GF_EXPORT +void* gf_sys_get_rmtws() { +#ifndef GPAC_DISABLE_RMTWS + return (void*)rmtws_handle; +#else + return NULL; #endif +} +GF_EXPORT +void* gf_sys_get_userws() { +#ifndef GPAC_DISABLE_RMTWS + return (void*)userws_handle; +#else + return NULL; +#endif } -static void *rmt_udta = NULL; -gf_rmt_user_callback rmt_usr_cbk = NULL; -static void gpac_rmt_input_handler(const char* text, void* context) -{ - if (text && rmt_usr_cbk) - rmt_usr_cbk(rmt_udta, text); -} -#endif +GF_EXPORT +GF_Err gf_sys_enable_rmtws(Bool start) { +#ifndef GPAC_DISABLE_RMTWS + if (start && !rmtws_handle) { -Bool gf_sys_enable_remotery(Bool start, Bool is_shutdown) -{ -#ifndef GPAC_DISABLE_REMOTERY - if (start && !remotery_handle) { - rmtSettings *rmcfg = rmt_Settings(); + rmtws_handle = rmt_ws_new(); + if (!rmtws_handle) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("core unable to initialize RMT websocket server\n")); + return GF_OUT_OF_MEM; + } + + RMT_Settings *rmcfg = gf_rmt_get_settings(rmtws_handle); rmcfg->port = gf_opts_get_int("core", "rmt-port"); - rmcfg->reuse_open_port = gf_opts_get_bool("core", "rmt-reuse"); rmcfg->limit_connections_to_localhost = gf_opts_get_bool("core", "rmt-localhost"); rmcfg->msSleepBetweenServerUpdates = gf_opts_get_int("core", "rmt-sleep"); - rmcfg->maxNbMessagesPerUpdate = gf_opts_get_int("core", "rmt-nmsg"); - rmcfg->messageQueueSizeInBytes = gf_opts_get_int("core", "rmt-qsize"); - rmcfg->input_handler = gpac_rmt_input_handler; + rmcfg->cert = gf_opts_get_key("core", "rmt-cert"); + rmcfg->pkey = gf_opts_get_key("core", "rmt-pkey"); - rmtError rme = rmt_CreateGlobalInstance(&remotery_handle); - if (rme != RMT_ERROR_NONE) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("core unable to initialize Remotery profiler: error %d\n", rme)); - return GF_FALSE; - } - //OpenGL binding is done upon loading of the driver, otherwise crashes on windows + rmt_ws_run(rmtws_handle); - if (gf_opts_get_bool("core", "rmt-log")) { - gpac_prev_default_logs = gf_log_set_callback(NULL, gpac_rmt_log_callback); - } -#ifdef GPAC_ENABLE_COVERAGE - if (gf_sys_is_cov_mode()) { - gpac_rmt_input_handler(NULL, NULL); - } -#endif - } else if (!start && remotery_handle) { - if (gf_opts_get_bool("core", "rmt-ogl")) - rmt_UnbindOpenGL(); + } else if (!start && rmtws_handle) { - rmt_DestroyGlobalInstance(remotery_handle); + rmt_ws_del(rmtws_handle); + rmtws_handle=NULL; - remotery_handle=NULL; - if (gpac_prev_default_logs != NULL) - gf_log_set_callback(NULL, gpac_prev_default_logs); } - return GF_TRUE; + return GF_OK; #else return GF_NOT_SUPPORTED; #endif } GF_EXPORT -GF_Err gf_sys_profiler_set_callback(void *udta, gf_rmt_user_callback usr_cbk) -{ -#ifndef GPAC_DISABLE_REMOTERY - if (remotery_handle) { - rmt_udta = udta; - rmt_usr_cbk = usr_cbk; - return GF_OK; - } - return GF_BAD_PARAM; -#else - return GF_NOT_SUPPORTED; -#endif -} +GF_Err gf_sys_enable_userws(Bool start) { +#ifndef GPAC_DISABLE_RMTWS + if (start && !userws_handle) { -GF_EXPORT -GF_Err gf_sys_profiler_log(const char *msg) -{ -#ifndef GPAC_DISABLE_REMOTERY - if (remotery_handle) { - rmt_LogText(msg); - return GF_OK; - } - return GF_BAD_PARAM; -#else - return GF_NOT_SUPPORTED; -#endif -} + userws_handle = rmt_ws_new(); + if (!userws_handle) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("core unable to initialize RMT websocket server\n")); + return GF_OUT_OF_MEM; + } + + RMT_Settings *rmcfg = gf_rmt_get_settings(userws_handle); + + rmcfg->port = gf_opts_get_int("core", "userws-port"); + rmcfg->limit_connections_to_localhost = gf_opts_get_bool("core", "userws-localhost"); + rmcfg->msSleepBetweenServerUpdates = gf_opts_get_int("core", "userws-sleep"); + rmcfg->cert = gf_opts_get_key("core", "userws-cert"); + rmcfg->pkey = gf_opts_get_key("core", "userws-pkey"); + + rmt_ws_run(userws_handle); + + + } else if (!start && userws_handle) { + + rmt_ws_del(userws_handle); + userws_handle=NULL; -GF_EXPORT -GF_Err gf_sys_profiler_send(const char *msg) -{ -#ifndef GPAC_DISABLE_REMOTERY - if (remotery_handle) { - rmt_SendText(msg); - return GF_OK; } - return GF_BAD_PARAM; + return GF_OK; #else return GF_NOT_SUPPORTED; #endif } -GF_EXPORT -void gf_sys_profiler_enable_sampling(Bool enable) -{ -#ifndef GPAC_DISABLE_REMOTERY - if (remotery_handle) { - rmt_EnableSampling(enable); - } -#endif -} - -GF_EXPORT -Bool gf_sys_profiler_sampling_enabled() -{ -#ifndef GPAC_DISABLE_REMOTERY - if (remotery_handle) { - return rmt_SamplingEnabled(); - } -#endif - return GF_FALSE; -} #include <gpac/list.h> GF_List *all_blobs = NULL; GF_EXPORT -GF_Err gf_blob_get(const char *blob_url, u8 **out_data, u32 *out_size, u32 *out_flags) +GF_Err gf_blob_get_ex(GF_Blob *blob, u8 **out_data, u32 *out_size, u32 *out_flags) { - GF_Blob *blob = NULL; - if (strncmp(blob_url, "gmem://", 7)) return GF_BAD_PARAM; - if (sscanf(blob_url, "gmem://%p", &blob) != 1) return GF_BAD_PARAM; if (!blob) return GF_BAD_PARAM; if (gf_list_find(all_blobs, blob)<0) return GF_URL_REMOVED; - if (blob->data && blob->mx) - gf_mx_p(blob->mx); + gf_mx_p(blob->mx); if (out_data) *out_data = blob->data; if (out_size) *out_size = blob->size; if (out_flags) *out_flags = blob->flags; return GF_OK; } +GF_EXPORT +GF_Err gf_blob_get(const char *blob_url, u8 **out_data, u32 *out_size, u32 *out_flags) +{ + GF_Blob *blob = NULL; + if (sscanf(blob_url, "gmem://%p", &blob) != 1) return GF_BAD_PARAM; + return gf_blob_get_ex(blob, out_data, out_size, out_flags); +} GF_EXPORT -GF_Err gf_blob_release(const char *blob_url) +GF_Err gf_blob_release_ex(GF_Blob *blob) { - GF_Blob *blob = NULL; - if (strncmp(blob_url, "gmem://", 7)) return GF_BAD_PARAM; - if (sscanf(blob_url, "gmem://%p", &blob) != 1) return GF_BAD_PARAM; - if (!blob) + if (!blob) return GF_BAD_PARAM; if (gf_list_find(all_blobs, blob)<0) return GF_URL_REMOVED; - if (blob->data && blob->mx) - gf_mx_v(blob->mx); - return GF_OK; + gf_mx_v(blob->mx); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_blob_release(const char *blob_url) +{ + GF_Blob *blob = NULL; + if (sscanf(blob_url, "gmem://%p", &blob) != 1) return GF_BAD_PARAM; + return gf_blob_release_ex(blob); } GF_EXPORT @@ -1376,7 +1329,6 @@ GF_Blob *gf_blob_from_url(const char *blob_url) { GF_Blob *blob = NULL; - if (strncmp(blob_url, "gmem://", 7)) return NULL; if (sscanf(blob_url, "gmem://%p", &blob) != 1) return NULL; if (!blob) return NULL; @@ -1386,6 +1338,17 @@ } #endif + +GF_EXPORT +GF_BlobRangeStatus gf_blob_query_range(GF_Blob *blob, Bool check_when_complete, u64 start_offset, u32 size) +{ + if (!blob) return GF_BLOB_RANGE_CORRUPTED; + if (blob->range_valid) return blob->range_valid(blob, check_when_complete, start_offset, &size); + + if (blob->flags & GF_BLOB_IN_TRANSFER) return GF_BLOB_RANGE_IN_TRANSFER; + return GF_BLOB_RANGE_VALID; +} + void gf_init_global_config(const char *profile); void gf_uninit_global_config(Bool discard_config); @@ -1488,6 +1451,7 @@ #endif #ifndef GPAC_DISABLE_LOG + gf_logs_init(); /*by default log subsystem is initialized to error on all tools, and info on console to debug scripts*/ gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_WARNING); gf_log_set_tool_level(GF_LOG_APP, GF_LOG_INFO); @@ -1609,6 +1573,9 @@ void gf_net_close_capture(); #endif +extern GF_List *gfio_delete_handlers; +extern GF_List *allocated_gfios; + GF_EXPORT void gf_sys_close() { @@ -1630,7 +1597,8 @@ psapi_hinst = NULL; #endif - gf_sys_enable_remotery(GF_FALSE, GF_TRUE); + gf_sys_enable_rmtws(GF_FALSE); + gf_sys_enable_userws(GF_FALSE); #ifdef GPAC_HAS_QJS void gf_js_delete_runtime(); @@ -1640,10 +1608,7 @@ gf_uninit_global_config(gpac_discard_config); #ifndef GPAC_DISABLE_LOG - if (gpac_log_file) { - gf_fclose(gpac_log_file); - gpac_log_file = NULL; - } + gf_logs_close(); #endif if (gpac_lang_file) gf_cfg_del(gpac_lang_file); gpac_lang_file = NULL; @@ -1668,6 +1633,9 @@ gf_list_del(all_blobs); all_blobs = NULL; + gf_list_del(gfio_delete_handlers); + gf_list_del(allocated_gfios); + #if !defined(GPAC_DISABLE_NETCAP) && !defined(GPAC_DISABLE_NETWORK) gf_net_close_capture(); #endif @@ -2098,16 +2066,16 @@ Bool gf_sys_get_rti_os(u32 refresh_time_ms, GF_SystemRTInfo *rti, u32 flags) { u64 u_k_time = 0; - kern_return_t kr; - task_info_data_t tinfo; - mach_msg_type_number_t task_info_count; - task_basic_info_t basic_info; - thread_array_t thread_list; - mach_msg_type_number_t thread_count; - thread_info_data_t thinfo; - mach_msg_type_number_t thread_info_count; - thread_basic_info_t basic_info_th; - u32 j, tot_cpu = 0, nb_threads = 0; + kern_return_t kr; + task_info_data_t tinfo; + mach_msg_type_number_t task_info_count; + task_basic_info_t basic_info; + thread_array_t thread_list; + mach_msg_type_number_t thread_count; + thread_info_data_t thinfo; + mach_msg_type_number_t thread_info_count; + thread_basic_info_t basic_info_th; + u32 j, tot_cpu = 0, nb_threads = 0; u32 entry_time = gf_sys_clock(); if (last_update_time && (entry_time - last_update_time < refresh_time_ms)) { @@ -2115,49 +2083,49 @@ return GF_FALSE; } - task_info_count = TASK_INFO_MAX; - kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); - if (kr != KERN_SUCCESS) { - return GF_FALSE; - } - - basic_info = (task_basic_info_t)tinfo; - the_rti.gpac_memory = the_rti.process_memory = basic_info->resident_size; - - // get threads in the task - kr = task_threads(mach_task_self(), &thread_list, &thread_count); - if (kr != KERN_SUCCESS) { - return GF_FALSE; - } - if (thread_count > 0) - nb_threads = (u32) thread_count; - - for (j = 0; j < nb_threads; j++) { - thread_info_count = THREAD_INFO_MAX; - kr = thread_info(thread_listj, THREAD_BASIC_INFO, - (thread_info_t)thinfo, &thread_info_count); - if (kr != KERN_SUCCESS) { + task_info_count = TASK_INFO_MAX; + kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); + if (kr != KERN_SUCCESS) { + return GF_FALSE; + } + + basic_info = (task_basic_info_t)tinfo; + the_rti.gpac_memory = the_rti.process_memory = basic_info->resident_size; + + // get threads in the task + kr = task_threads(mach_task_self(), &thread_list, &thread_count); + if (kr != KERN_SUCCESS) { + return GF_FALSE; + } + if (thread_count > 0) + nb_threads = (u32) thread_count; + + for (j = 0; j < nb_threads; j++) { + thread_info_count = THREAD_INFO_MAX; + kr = thread_info(thread_listj, THREAD_BASIC_INFO, + (thread_info_t)thinfo, &thread_info_count); + if (kr != KERN_SUCCESS) { vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); - return GF_FALSE; - } + return GF_FALSE; + } - basic_info_th = (thread_basic_info_t)thinfo; + basic_info_th = (thread_basic_info_t)thinfo; - if (!(basic_info_th->flags & TH_FLAGS_IDLE)) { - u64 tot_sec = basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; - tot_sec *= 1000000; - tot_sec += basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds; - u_k_time += tot_sec; + if (!(basic_info_th->flags & TH_FLAGS_IDLE)) { + u64 tot_sec = basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; + tot_sec *= 1000000; + tot_sec += basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds; + u_k_time += tot_sec; tot_cpu += basic_info_th->cpu_usage; - } + } - } // for each thread + } // for each thread - the_rti.process_cpu_usage = (tot_cpu * 100) / TH_USAGE_SCALE; + the_rti.process_cpu_usage = (tot_cpu * 100) / TH_USAGE_SCALE; the_rti.total_cpu_usage = the_rti.process_cpu_usage; - kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); - gf_assert(kr == KERN_SUCCESS); + kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); + gf_assert(kr == KERN_SUCCESS); if (last_update_time) { @@ -2301,6 +2269,9 @@ the_rti.physical_memory = EM_ASM_INT(return HEAP8.length); s_mallinfo mi = mallinfo(); the_rti.physical_memory_avail = the_rti.physical_memory - (unsigned int)sbrk(0) + mi.fordblks; +#elif defined(GPAC_CONFIG_FREEBSD) + /* FreeBSD doesn't have /proc/meminfo, would need sysctl to get memory info */ + the_rti.physical_memory = the_rti.physical_memory_avail = 0; #else the_rti.physical_memory = the_rti.physical_memory_avail = 0; f = gf_fopen("/proc/meminfo", "r"); @@ -2513,7 +2484,7 @@ } *pid = '\0'; } - int fd = open(pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + int fd = open(pidfile, O_RDWR | O_CREAT , S_IRUSR | S_IWUSR); if (fd == -1) goto exit; /* Get the flags */ @@ -2742,7 +2713,6 @@ return (s32) (local - remote); } -#if 0 /*! Adds or remove a given amount of microseconds to an NTP timestamp @@ -2751,7 +2721,7 @@ \return adjusted NTP timestamp */ GF_EXPORT -u64 gf_net_add_usec(u64 ntp, s32 usec) +u64 gf_net_ntp_add_usec(u64 ntp, s32 usec) { u64 sec, frac; s64 usec_ntp; @@ -2772,7 +2742,6 @@ ntp |= (sec<<32); return ntp; } -#endif GF_EXPORT @@ -2921,6 +2890,7 @@ year = month = day = h = m = s = 0; oh = om = 0; secs = 0; + Bool has_sep = strchr(val, ':') ? GF_TRUE : GF_FALSE; if (sscanf(val, "%d-%d-%dT%d:%d:%g-%d:%d", &year, &month, &day, &h, &m, &secs, &oh, &om) == 8) { neg_time_zone = GF_TRUE; @@ -2929,6 +2899,8 @@ } else if (sscanf(val, "%d-%d-%dT%d:%d:%gZ", &year, &month, &day, &h, &m, &secs) == 6) { } + else if (sscanf(val, "%d/%d/%dT%d:%d:%gZ", &year, &month, &day, &h, &m, &secs) == 6) { + } else if (sscanf(val, "%3s, %d %3s %d %d:%d:%d", szDay, &day, szMonth, &year, &h, &m, &s)==7) { secs = (Float) s; } @@ -2938,11 +2910,11 @@ else if (sscanf(val, "%3s %3s %d %02d:%02d:%02d %d", szDay, szMonth, &day, &year, &h, &m, &s)==7) { secs = (Float) s; } - else if (sscanf(val, LLU, ¤t_time) == 1 && current_time > 1000000000 && current_time < GF_INT_MAX) { - return current_time * 1000; // guessed raw duration since UTC0 in seconds - } - else if (sscanf(val, LLU, ¤t_time) == 1 && current_time > 1000000000000ULL && current_time < GF_INT_MAX * 1000ULL) { + else if (!has_sep && (sscanf(val, LLU, ¤t_time) == 1) && current_time > 1000000000000ULL && current_time < GF_INT_MAX * 1000ULL) { return current_time; // guessed duration since UTC0 in milliseconds + } + else if (!has_sep && (sscanf(val, LLU, ¤t_time) == 1) && current_time < GF_INT_MAX) { + return current_time * 1000; // guessed raw duration since UTC0 in seconds } else { GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core Cannot parse date string %s\n", val)); return 0; @@ -3178,6 +3150,9 @@ FILE *file = gf_fopen(file_name, "rb"); if (!file) { + if (!gf_file_exists(file_name)) + return GF_URL_ERROR; + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core Cannot open file %s\n", file_name)); return GF_IO_ERR; } @@ -3188,11 +3163,18 @@ #ifndef WIN32 #include <unistd.h> +#include <fcntl.h> + GF_EXPORT u32 gf_sys_get_process_id() { return getpid (); } +GF_EXPORT +Bool gf_sys_check_process_id(u32 pid) +{ + return (getpgid(pid) == -1) ? GF_FALSE : GF_TRUE; +} #else #include <windows.h> GF_EXPORT @@ -3200,7 +3182,69 @@ { return GetCurrentProcessId(); } +GF_EXPORT +Bool gf_sys_check_process_id(u32 pid) +{ + HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + Bool ret = GF_FALSE; + if (processHandle) { + CloseHandle(processHandle); + ret = GF_TRUE; + } + return ret; +} +#endif + +GF_EXPORT +GF_LockStatus gf_sys_create_lockfile(const char *lockname) +{ + char szPID20; + u32 retry = 10; + sprintf(szPID, "%u", gf_sys_get_process_id()); + u32 len = (u32)strlen(szPID); + + while (retry) { + retry--; +#ifdef GPAC_HAS_FD + s32 fd = open(lockname, O_CREAT | O_EXCL | O_WRONLY | O_BINARY, S_IRUSR|S_IWUSR); + if (fd != -1) { + s32 wlen = write(fd, szPID, len); + close(fd); + if (wlen == (s32)len) return GF_LOCKFILE_NEW; + continue; + } +#else +#if defined __STDC_VERSION__ + FILE *f = fopen(lockname, "wx"); +#else + FILE *f = gf_file_exists(lockname) ? NULL : fopen(lockname, "w"); #endif + if (f) { + s32 wlen = (s32) fwrite(szPID, 1, len, f); + fclose(f); + if (wlen == (s32)len) return GF_LOCKFILE_NEW; + continue; + } +#endif + + //existing, check pid + u8 *data=NULL; + u32 size=0, pid; + GF_Err e = gf_file_load_data(lockname, &data, &size); + if (!data || !size || e!=GF_OK) continue; + + sscanf(data, "%u", &pid); + gf_free(data); + //already locked by ourselves + if (pid == gf_sys_get_process_id()) return GF_LOCKFILE_REUSE; + //pid is alive, lock fail + if (gf_sys_check_process_id(pid)) return GF_LOCKFILE_FAILED; + + //file exists but pid dead, grab the lock + gf_file_delete(lockname); + } + return GF_LOCKFILE_FAILED; +} #ifndef WIN32 #include <termios.h>
View file
gpac-2.4.0.tar.gz/src/utils/os_file.c -> gpac-26.02.0.tar.gz/src/utils/os_file.c
Changed
@@ -1,7 +1,7 @@ /* * GPAC - Multimedia Framework C SDK * - * Authors: Jean Le Feuvre - Copyright (c) Telecom ParisTech 2000-2023 + * Authors: Jean Le Feuvre - Copyright (c) Telecom ParisTech 2000-2025 * Romain Bouqueau - Copyright (c) Romain Bouqueau 2015 * All rights reserved * @@ -195,6 +195,49 @@ return GF_OK; } +#include <gpac/list.h> +GF_List *gfio_delete_handlers = NULL; + +GF_EXPORT +GF_Err gf_fileio_register_delete_proc(gfio_delete_proc del_proc) +{ + if (!del_proc) return GF_OK; + + if (!gfio_delete_handlers) gfio_delete_handlers = gf_list_new(); + if (!gfio_delete_handlers) return GF_OUT_OF_MEM; + if (gf_list_find(gfio_delete_handlers, del_proc)>=0) return GF_BAD_PARAM; + return gf_list_add(gfio_delete_handlers, del_proc); +} +GF_EXPORT +void gf_fileio_unregister_delete_proc(gfio_delete_proc del_proc) +{ + if (!del_proc || !gfio_delete_handlers) return; + gf_list_del_item(gfio_delete_handlers, del_proc); + if (!gf_list_count(gfio_delete_handlers)) { + gf_list_del(gfio_delete_handlers); + gfio_delete_handlers = NULL; + } +} + +GF_Err gf_fileio_file_delete(const char *fileName, const char *parent_gfio) +{ + if (!gfio_delete_handlers || !fileName) return GF_OK; + if (!parent_gfio) { + if (strncmp(fileName, "gfio://", 7)) return GF_EOS; + } else { + if (strncmp(parent_gfio, "gfio://", 7)) return GF_EOS; + } + + u32 i, count=gf_list_count(gfio_delete_handlers); + for (i=0; i<count; i++) { + gfio_delete_proc del_proc = gf_list_get(gfio_delete_handlers, i); + GF_Err ret = del_proc(fileName, parent_gfio); + if (ret==GF_EOS) continue; + return ret; + } + return GF_OK; +} + GF_EXPORT GF_Err gf_file_delete(const char *fileName) { @@ -202,6 +245,10 @@ GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("gf_file_delete with no param - ignoring\n")); return GF_OK; } + if (!strncmp(fileName, "gfio://", 7)) { + return gf_fileio_file_delete(fileName, NULL); + } + #if defined(_WIN32_WCE) TCHAR swzNameMAX_PATH; CE_CharToWide((char*)fileName, swzName); @@ -359,6 +406,10 @@ if (!fileName || !newFileName) { e = GF_IO_ERR; } else { + //try direct rename - will fail if moving across file systems but faster for most use cases + int res = rename(fileName, newFileName); + if (!res) return GF_OK; + //use mv command arg1 = gf_sanetize_single_quoted_string(fileName); arg2 = gf_sanetize_single_quoted_string(newFileName); if (snprintf(cmd, sizeof cmd, "mv %s %s", arg1, arg2) >= sizeof cmd) goto error; @@ -683,7 +734,7 @@ struct stat st; #endif - if (!dir || !enum_dir_fct) return GF_BAD_PARAM; + if (!dir || !strlen(dir) || !enum_dir_fct) return GF_BAD_PARAM; if (filter && (!strcmp(filter, "*") || !filter0)) filter=NULL; @@ -768,8 +819,16 @@ } #else - strcpy(path, dir); - if (pathstrlen(path)-1 != '/') strcat(path, "/"); + size_t dir_len = strlen(dir); + if (dir_len < GF_ARRAY_LENGTH(path)) { + strcpy(path, dir); + } + else { + memcpy(path, dir, GF_ARRAY_LENGTH(path)); + path GF_ARRAY_LENGTH(path) - 1 = 0; + } + size_t path_len = strlen(path); + if (path_len && pathpath_len-1 != '/') strcat(path, "/"); #endif #ifdef WIN32 @@ -777,9 +836,10 @@ if (SearchH == INVALID_HANDLE_VALUE) return GF_IO_ERR; #if defined (_WIN32_WCE) - _pathstrlen(_path)-1 = 0; + _pathstrlen(_path) ? strlen(_path)-1:0 = 0; #else - pathwcslen(path)-1 = 0; + size_t path_len = wcslen(path); + path path_len ? path_len-1 : 0 = 0; #endif while (SearchH != INVALID_HANDLE_VALUE) { @@ -986,6 +1046,7 @@ char *res_url; void *udta; + u32 creation_time; u64 bytes_done, file_size_plus_one; Bool main_th; GF_FileIOCacheState cache_state; @@ -1131,12 +1192,15 @@ len = (u32) (next - buf); if (len + blob->pos<blob->size) len++; } - if (len > size) len = size; + if (len + 1 > size) len = size - 1; memcpy(ptr, blob->data+blob->pos, len); blob->pos += len; + ptrlen = 0; return ptr; } +GF_List *allocated_gfios = NULL; + GF_EXPORT GF_FileIO *gf_fileio_new(char *url, void *udta, gfio_open_proc open, @@ -1168,9 +1232,18 @@ if (url) tmp->res_url = gf_strdup(url); - sprintf(szURL, "gfio://%p", tmp); + //add clock as cookie to avoid creating twice th same gfio url in case the above malloc returns + //the same adress as a previously allocated gfio + tmp->creation_time = gf_sys_clock(); + sprintf(szURL, "gfio://%p&%u", tmp, tmp->creation_time); tmp->url = gf_strdup(szURL); tmp->__this = tmp; + //track all allocated gfios so that we don't attempt accessing a freed one ! + gf_mx_p(logs_mx); + if (!allocated_gfios) allocated_gfios = gf_list_new(); + gf_list_add(allocated_gfios, tmp); + gf_mx_v(logs_mx); + return tmp; } @@ -1190,10 +1263,23 @@ void gf_fileio_del(GF_FileIO *gfio) { if (!gfio) return; + gf_mx_p(logs_mx); + if (gf_list_del_item(allocated_gfios, gfio)<0) { + gf_mx_v(logs_mx); + return; + } + if (!gf_list_count(allocated_gfios)) { + gf_list_del(allocated_gfios); + allocated_gfios = NULL; + } + gf_mx_v(logs_mx); + if (gfio->url) gf_free(gfio->url); if (gfio->res_url) gf_free(gfio->res_url); if (gfio->printf_buf) gf_free(gfio->printf_buf); gf_free(gfio); + + } GF_EXPORT @@ -1268,16 +1354,24 @@ GF_FileIO *gf_fileio_from_url(const char *url) { char szURL100; + u32 cookie=0; GF_FileIO *ptr=NULL; if (!url) return NULL; if (strncmp(url, "gfio://", 7)) return NULL; - sscanf(url, "gfio://%p", &ptr); - sprintf(szURL, "gfio://%p", ptr); + sscanf(url, "gfio://%p&%u", &ptr, &cookie); + sprintf(szURL, "gfio://%p&%u", ptr, cookie); if (strcmp(url, szURL)) return NULL; - if (ptr && ptr->url && !strcmp(ptr->url, url) ) { + gf_mx_p(logs_mx); + if (gf_list_find(allocated_gfios, ptr)<0) { + gf_mx_v(logs_mx); + return NULL; + } + gf_mx_v(logs_mx); + + if (ptr && ptr->url && !strcmp(ptr->url, url) && (ptr->creation_time==cookie) ) { return ptr; } return NULL; @@ -1432,25 +1526,29 @@ GF_FileIOBlob *gfio_blob; GF_Err e = gf_blob_get(file_name, &blob_data, &blob_size, &flags); if (e || !blob_data) return NULL; - gf_blob_release(file_name); + gf_blob_release(file_name); - if (flags) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core Attempt at creating a GFIO object on blob corrupted or in transfer, not supported !")); - return NULL; - } + if (flags) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core Attempt at creating a GFIO object on blob corrupted or in transfer, not supported !")); + return NULL; + } GF_SAFEALLOC(gfio_blob, GF_FileIOBlob); if (!gfio_blob) return NULL; gfio_blob->data = blob_data; gfio_blob->size = blob_size; GF_FileIO *res = gf_fileio_new((char *) file_name, gfio_blob, gfio_blob_open, gfio_blob_seek, gfio_blob_read, NULL, gfio_blob_tell, gfio_blob_eof, NULL); - if (!res) return NULL; + if (!res) { + gf_free(gfio_blob); + return NULL; + } res->gets = gfio_blob_gets; if (file_name) gfio_blob->url_crc = gf_crc_32(file_name, (u32) strlen(file_name) ); return res; } +GF_EXPORT GF_FileIO *gf_fileio_from_mem(const char *URL, const u8 *data, u32 size) { GF_FileIOBlob *gfio_blob; @@ -1608,15 +1706,37 @@ if (strpbrk(mode, "wa")) { #if defined(WIN32) u32 err = GetLastError(); - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core system failure for file opening of \"%s\" in mode \"%s\": 0x%08x\n", file_name, mode, err)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core system failure for file opening of \"%s\" in mode \"%s\": 0x%08x (%u opened)\n", file_name, mode, err, gpac_file_handles)); #else - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core system failure for file opening of \"%s\" in mode \"%s\": %d\n", file_name, mode, errno)); + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Core system failure for file opening of \"%s\" in mode \"%s\": %d (%u opened)\n", file_name, mode, errno, gpac_file_handles)); #endif } } return res; } + +GF_EXPORT +s32 gf_fd_open(const char *file_name, u32 oflags, u32 uflags) +{ + if (!file_name) return -1; + +#if defined(WIN32) + wchar_t *wname = gf_utf8_to_wcs(file_name); + if (!wname) return -1; + int res = _wopen(wname, oflags, uflags); + gf_free(wname); + return res; +#elif defined(GPAC_CONFIG_LINUX) && !defined(GPAC_CONFIG_ANDROID) + return open(file_name, oflags, uflags); +#elif (defined(GPAC_CONFIG_FREEBSD) || defined(GPAC_CONFIG_DARWIN)) + return open(file_name, oflags, uflags); +#else + return open(file_name, oflags, uflags); +#endif + return -1; +} + GF_EXPORT FILE *gf_fopen(const char *file_name, const char *mode) { @@ -1702,7 +1822,7 @@ return fio->gets(fio, ptr, (u32) size); u32 i, read, nb_read=0; - for (i=0; i<size; i++) { + for (i=0; i<size-1; i++) { u8 buf1; read = (u32) gf_fileio_read(fio, buf, 1); if (!read) break; @@ -1711,6 +1831,7 @@ nb_read++; if (buf0=='\n') break; } + ptrnb_read=0; if (!nb_read) return NULL; return ptr; } @@ -1834,6 +1955,31 @@ return size; } + +GF_EXPORT +u64 gf_fd_fsize(int fd) +{ + u64 size=0; +#ifdef GPAC_HAS_FD + + if (fd >= 0) { + +#if defined(WIN32) + struct _stat64 sb; + _fstat64(fd, &sb); +#else + struct stat sb; + fstat(fd, &sb); +#endif + size = (u64) sb.st_size; + } + +#endif + return size; +} + + + /** * Returns a pointer to the start of a filepath basename **/ @@ -1877,12 +2023,19 @@ if (basename) { char *ext = strrchr(basename, '.'); - if (ext && !strcmp(ext, ".gz")) { + if (!ext) return NULL; + if (!strcmp(ext, ".gz")) { ext0 = 0; char *ext2 = strrchr(basename, '.'); ext0 = '.'; if (ext2) return ext2; } + //consider that if we have a space after a dot and before any common separator, we have no file extension + u32 i; + for (i=1; exti ; i++) { + if ((exti==':') || (exti=='@') || (exti=='#') || (exti=='?')) break; + if (exti==' ') return NULL; + } return ext; } return NULL;
View file
gpac-2.4.0.tar.gz/src/utils/os_module.c -> gpac-26.02.0.tar.gz/src/utils/os_module.c
Changed
@@ -322,7 +322,7 @@ #ifdef WIN32 gf_enum_directory(pm->dirsi, GF_FALSE, enum_modules, pm, ".dll"); #elif defined(__APPLE__) -#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) /*we are in static build for modules by default*/ #else gf_enum_directory(pm->dirsi, 0, enum_modules, pm, ".dylib");
View file
gpac-2.4.0.tar.gz/src/utils/os_net.c -> gpac-26.02.0.tar.gz/src/utils/os_net.c
Changed
@@ -109,6 +109,7 @@ static int wsa_init = 0; /*end-win32*/ +#define SOCKET_INVALID(_s) (_s==INVALID_SOCKET) #else /*non-win32*/ @@ -130,6 +131,9 @@ #include <sys/types.h> #include <arpa/inet.h> +#if defined(__FreeBSD__) +#include <sys/stat.h> +#endif /*not defined on solaris*/ #if !defined(INADDR_NONE) @@ -141,7 +145,9 @@ #endif -#define INVALID_SOCKET -1 +#define SOCKET_INVALID(_s) (_s<0) +#define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 #define LASTSOCKERROR errno @@ -363,12 +369,6 @@ static u32 ipv6_check_state = 0; #endif -#ifdef _LP64 -#define NULL_SOCKET 0 -#else -#define NULL_SOCKET (SOCKET)NULL -#endif - #ifdef GPAC_HAS_SOCK_UN #include <sys/un.h> #endif @@ -384,7 +384,7 @@ /*socket is bound to a specific dest (server) or source (client) */ GF_SOCK_HAS_PEER = 1<<14, GF_SOCK_IS_UN = 1<<15, - GF_SOCK_HAS_CONNECT = 1<<16, + GF_SOCK_HAS_CONNECT = 1<<16 }; #ifndef GPAC_DISABLE_NETCAP @@ -722,10 +722,12 @@ { u32 send_recv; u32 pck_start, pck_end; + Bool pck_start_is_loop, pck_end_is_loop; s32 rand_every; u32 nb_pck; u32 port; s32 patch_offset, patch_val; + s32 delay; } NetFilterRule; static GF_List *netcap_filters=NULL; @@ -742,6 +744,7 @@ Bool rt; GF_List *rules; u32 nb_rules; + u32 nb_reloads; //target bitstream for read/write @@ -761,6 +764,7 @@ u64 init_time; Bool is_eos; + u64 pck_start_offset; //loaded packet size in bytes - this is the payload of the UDP/TCP packet - if 0, packet fetch failed s32 pck_len; @@ -800,6 +804,9 @@ u32 pck_idx_w; u32 next_pck_range; u32 next_rand; + + u32 reorder_max_pck, reorder_nb_pck, delay_nb_pck; + u64 reorder_seek_pos, reorder_resume_pos; }; static GF_NetcapFilter *gf_net_filter_get(const char *id) @@ -856,7 +863,7 @@ if (!gf_opts_get_bool("core", "no-fd")) { //make sure output dir exists gf_fopen(nf->dst, "mkdir"); - nf->fd = open(nf->dst, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); + nf->fd = gf_fd_open(nf->dst, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); if (nf->fd>=0) nf->cap_bs = gf_bs_from_fd(nf->fd, GF_BITSTREAM_WRITE); } else #endif @@ -913,6 +920,7 @@ static void gf_netcap_load_pck_gpac(GF_NetcapFilter *nf) { + nf->pck_start_offset = gf_bs_get_position(nf->cap_bs); if (gf_bs_available(nf->cap_bs)>4) nf->pck_len = gf_bs_read_u32(nf->cap_bs); else @@ -979,6 +987,11 @@ nf->src_port = 0; } + if (gf_bs_is_overflow(nf->cap_bs)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("NetCap Corrupted netcap file, aborting\n")); + nf->pck_len = -1; + } + //broken packet if (nf->pck_len <0) { nf->dst_v4 = 0; @@ -988,6 +1001,13 @@ } } +enum +{ + PCAP_LT_LOOPBACK=0, + PCAP_LT_ETHERNET=1, + PCAP_LT_CHAOS=5 +}; + static GF_Err pcapng_load_shb(GF_NetcapFilter *nf, Bool is_init) { u32 blen = gf_bs_read_u32(nf->cap_bs); @@ -1019,8 +1039,14 @@ nf->link_typesnf->pcap_num_interfaces = nf->pcap_le ? gf_bs_read_u16_le(nf->cap_bs) : gf_bs_read_u16(nf->cap_bs); gf_bs_skip_bytes(nf->cap_bs, blen-10); - if (nf->link_typesnf->pcap_num_interfaces>1) { + switch (nf->link_typesnf->pcap_num_interfaces) { + case PCAP_LT_LOOPBACK: + case PCAP_LT_ETHERNET: + case PCAP_LT_CHAOS: + break; + default: GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("NetCap Only ethernet and BSD-loopback pcap files are supported, skipping packets\n")); + break; } nf->pcap_num_interfaces++; } @@ -1056,6 +1082,7 @@ nf->is_eos = GF_TRUE; return; } + nf->pck_start_offset = gf_bs_get_position(nf->cap_bs); u32 link_type; if (nf->cap_mode==NETCAP_PCAPNG) { @@ -1074,6 +1101,11 @@ } bsize = nf->pcap_le ? gf_bs_read_u32_le(nf->cap_bs) : gf_bs_read_u32(nf->cap_bs); + if (bsize <= 8) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("NetCap Corrupted PCAP file, aborting\n")); + nf->is_eos = GF_TRUE; + return; + } if (btype!=0x00000006) { gf_bs_skip_bytes(nf->cap_bs, bsize-8); nf->pcapng_trail = 0; @@ -1132,6 +1164,9 @@ SKIP_PCAP_PCK } } + //raw IPv4 + else if (link_type==5) { + } //not supported else { gf_bs_skip_bytes(nf->cap_bs, clen); @@ -1339,6 +1374,10 @@ s32 patch_offset=-1; s32 patch_val=-1; s32 rand_every = 0; + s32 delay = 0; + Bool pck_start_is_loop = GF_FALSE; + Bool pck_end_is_loop = GF_FALSE; + char *rule_str; char *sep = strchr(rules, ''); if (!sep) break; @@ -1364,9 +1403,13 @@ case 'p': port = atoi(rule_str+2); break; + case 'S': + pck_start_is_loop = GF_TRUE; case 's': pck_start = atoi(rule_str+2); break; + case 'E': + pck_end_is_loop = GF_TRUE; case 'e': pck_end = atoi(rule_str+2); break; @@ -1388,6 +1431,13 @@ if (strchr(rule_str, '-')) patch_val =-1; else patch_val = strtol(rule_str+2, NULL, 16); break; + case 'd': + if (!nf->src) { + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("NetCap Reorder rule only supported from pcap playback, ignoring\n")); + break; + } + delay = atoi(rule_str+2); + break; default: GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("NetCap Unknown directive %c, ignoring\n", rule_str0)); } @@ -1405,8 +1455,11 @@ rule->patch_val = patch_val; rule->pck_start = pck_start; rule->pck_end = pck_end>pck_start ? pck_end : 0; + rule->pck_start_is_loop = pck_start_is_loop; + rule->pck_end_is_loop = pck_end_is_loop; rule->rand_every = rand_every; rule->nb_pck = num_pck; + rule->delay = delay; gf_list_add(nf->rules, rule); sep0 = ''; @@ -1455,12 +1508,20 @@ cur_pck = for_send ? nf->pck_idx_w : nf->pck_idx_r; } //check range - if (r->pck_start && (cur_pck < r->pck_start)) continue; - if (r->pck_end && (cur_pck >= r->pck_end)) continue; + u32 pck_start = r->pck_start; + if (r->pck_start && r->pck_start_is_loop) + pck_start += nf->nb_reloads; + if (!pck_start && r->pck_start_is_loop) continue; + u32 pck_end = pck_start ? r->pck_end : 0; + if (r->pck_end && r->pck_end_is_loop) + pck_end += nf->nb_reloads; + + if (pck_start && (cur_pck < pck_start)) continue; + if (pck_end && (cur_pck >= pck_end)) continue; //not repeated pattern case if (!r->rand_every) { - if ((cur_pck >= r->pck_start) && (r->pck_start + r->nb_pck > cur_pck)) {} + if ((cur_pck >= pck_start) && (pck_start + r->nb_pck > cur_pck)) {} else continue; } //repeated pattern case @@ -1468,8 +1529,8 @@ u32 next_pck_range = ci ? ci->next_pck_range : nf->next_pck_range; u32 next_rand = ci ? ci->next_rand : nf->next_rand; //in range, check if we need to recompute - if (next_pck_range < r->pck_start) - next_pck_range = r->pck_start; + if (next_pck_range < pck_start) + next_pck_range = pck_start; if (next_pck_range <= cur_pck) { if (r->rand_every>0) { @@ -1500,7 +1561,17 @@ } if ((r->patch_offset==-1) && !(sock->flags & GF_SOCK_IS_TCP)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("NetCap Droping packet %d\n", cur_pck)); + if (r->delay) { + if (!nf->reorder_seek_pos) { + nf->reorder_seek_pos = nf->pck_start_offset; + nf->reorder_max_pck = r->nb_pck; + nf->delay_nb_pck = r->delay; + nf->reorder_nb_pck = 0; + } + nf->reorder_nb_pck ++; + return GF_TRUE; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("NetCap Dropping packet %d\n", cur_pck)); return GF_TRUE; } u32 bo = (r->patch_offset>=0) ? r->patch_offset : gf_rand(); @@ -1524,10 +1595,12 @@ refetch: //no packet loaded if (!nf->dst_port) { + Bool discard=GF_FALSE; nf->pck_flags=0; nf->read_sock_selected = NULL; if (nf->loops && !gf_bs_available(nf->cap_bs)) { + nf->nb_reloads++; if (nf->loops>0) nf->loops--; @@ -1537,6 +1610,30 @@ gf_bs_skip_bytes(nf->cap_bs, 24); } + if (nf->reorder_seek_pos) { + //we passed all packets to reorder + if (nf->reorder_nb_pck == nf->reorder_max_pck) { + if (!nf->delay_nb_pck) { + //remember pos after reorder + nf->reorder_resume_pos = gf_bs_get_position(nf->cap_bs); + gf_bs_seek(nf->cap_bs, nf->reorder_seek_pos); + nf->reorder_seek_pos = 0; + nf->pcapng_trail = nf->pcap_trail = 0; + nf->reorder_nb_pck = 1; + } else { + nf->delay_nb_pck--; + } + } + } else if (nf->reorder_resume_pos) { + if (nf->reorder_nb_pck == nf->reorder_max_pck) { + gf_bs_seek(nf->cap_bs, nf->reorder_resume_pos); + nf->reorder_resume_pos = 0; + nf->pcapng_trail = nf->pcap_trail = 0; + nf->reorder_nb_pck = 0; + } else { + nf->reorder_nb_pck++; + } + } if (nf->cap_mode==NETCAP_GPAC) gf_netcap_load_pck_gpac(nf); else @@ -1545,7 +1642,9 @@ //eof if (!nf->dst_port) return; //force discard - if ((nf->pck_flags & NETCAP_DROP)) { + if ((nf->pck_flags & NETCAP_DROP)) discard = GF_TRUE; + + if (discard) { gf_bs_skip_bytes(nf->cap_bs, nf->pck_len); nf->dst_port = 0; goto refetch; @@ -1565,6 +1664,20 @@ u32 i=0; GF_Socket *s = NULL; while ((s=gf_list_enum(nf->read_socks, &i))) { + //in pcap mode, check multicast - if enabled, match dst + //in GPC mode with multicast, only multicast address is used, in host port/addr fields + if ((nf->cap_mode!=NETCAP_GPAC) && (s->flags & GF_SOCK_IS_MULTICAST)) { + if (s->cap_info->peer_port != nf->dst_port) + continue; + if (nf->pck_flags & NETCAP_IS_IPV6) { + if (memcmp(s->cap_info->peer_addr_v6, nf->dst_v6, 16)) continue; + } else { + if (s->cap_info->peer_addr_v4 != nf->dst_v4) continue; + } + nf->read_sock_selected = s; + s->cap_info->patch_offset = 0; + break; + } if (s->cap_info->host_port != nf->dst_port) continue; if (nf->pck_flags & NETCAP_IS_IPV6) { @@ -1585,7 +1698,8 @@ s->cap_info->patch_offset = 0; break; } - if (netcap_filter_pck(nf->read_sock_selected, nf->pck_len, GF_FALSE)) + //apply rule(s) except when sending delayed packets + if (!nf->reorder_resume_pos && netcap_filter_pck(nf->read_sock_selected, nf->pck_len, GF_FALSE)) nf->read_sock_selected = NULL; if (!nf->read_sock_selected) { @@ -1599,7 +1713,7 @@ { #ifdef GPAC_HAS_FD if (!gf_opts_get_bool("core", "no-fd")) { - nf->fd = open(nf->src, O_RDONLY); + nf->fd = gf_fd_open(nf->src, O_RDONLY | O_BINARY , S_IRUSR | S_IWUSR); if (nf->fd>=0) nf->cap_bs = gf_bs_from_fd(nf->fd, GF_BITSTREAM_READ); } else #endif @@ -1650,9 +1764,14 @@ nf->link_types0 = gf_bs_read_int(nf->cap_bs, 28); if (nf->pcap_le) nf->link_types0 = ntohl(nf->link_types0); - if (nf->link_types0 > 1) { + switch (nf->link_types0) { + case PCAP_LT_LOOPBACK: + case PCAP_LT_ETHERNET: + case PCAP_LT_CHAOS: + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("NetCap Only ethernet and BSD-loopback pcap files are supported, got %u\n", nf->link_types0)); gf_net_close_capture(); - GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("NetCap Only ethernet and BSD-loopback pcap files are supported\n")); return GF_NOT_SUPPORTED; } } @@ -1752,15 +1871,15 @@ //UDP if (protocol_type==17) { gf_bs_write_u16(nf->cap_bs, sock->cap_info->host_port); - gf_bs_write_u16_le(nf->cap_bs, sock->cap_info->peer_port); + gf_bs_write_u16(nf->cap_bs, sock->cap_info->peer_port); gf_bs_write_u16(nf->cap_bs, 8+length); //skip checksum gf_bs_skip_bytes(nf->cap_bs, 2); } //TCP else if (protocol_type==6) { - gf_bs_write_u16_le(nf->cap_bs, sock->cap_info->host_port); - gf_bs_write_u16_le(nf->cap_bs, sock->cap_info->peer_port); + gf_bs_write_u16(nf->cap_bs, sock->cap_info->host_port); + gf_bs_write_u16(nf->cap_bs, sock->cap_info->peer_port); /*sn = */gf_bs_write_u32(nf->cap_bs, 0); /*ack = */gf_bs_write_u32(nf->cap_bs, 0); gf_bs_write_int(nf->cap_bs, 5, 4); @@ -1836,6 +1955,11 @@ } #endif //GPAC_DISABLE_NETCAP +Bool gf_sk_has_nrt_netcap(GF_Socket *sk) +{ + if (sk && sk->cap_info && !sk->cap_info->nf->rt) return GF_TRUE; + return GF_FALSE; +} GF_EXPORT GF_Socket *gf_sk_new_ex(u32 SocketType, const char *netcap_id) @@ -1879,6 +2003,7 @@ #ifdef WIN32 wsa_init ++; #endif + tmp->socket = INVALID_SOCKET; #ifndef GPAC_DISABLE_NETCAP GF_NetcapFilter *nf = gf_net_filter_get(netcap_id); @@ -1907,7 +2032,7 @@ { u32 nsize=0, psize; s32 res; - if (!sock || !sock->socket) return GF_BAD_PARAM; + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (SendBuffer) { res = setsockopt(sock->socket, SOL_SOCKET, SO_SNDBUF, (char *) &NewSize, sizeof(u32) ); @@ -1938,12 +2063,12 @@ s32 res; #ifdef WIN32 long val = NonBlockingOn; - if (sock->socket) { + if (!SOCKET_INVALID(sock->socket)) { res = ioctlsocket(sock->socket, FIONBIO, &val); if (res) return GF_SERVICE_ERROR; } #else - if (sock->socket) { + if (!SOCKET_INVALID(sock->socket)) { s32 flags = fcntl(sock->socket, F_GETFL, 0); if (NonBlockingOn) flags |= O_NONBLOCK; @@ -1967,21 +2092,21 @@ #ifndef GPAC_DISABLE_NETCAP #define sock_close(_s) \ closesocket(_s->socket);\ - _s->socket = NULL_SOCKET;\ + _s->socket = INVALID_SOCKET;\ if (_s->cap_info) _s->cap_info->host_port = 0; \ #else #define sock_close(_s) \ closesocket(_s->socket);\ - _s->socket = NULL_SOCKET; + _s->socket = INVALID_SOCKET; #endif static void gf_sk_free(GF_Socket *sock) { gf_assert( sock ); - if (!sock->socket) return; + if (SOCKET_INVALID(sock->socket)) return; /*leave multicast*/ if (sock->flags & GF_SOCK_IS_MULTICAST) { @@ -2064,8 +2189,7 @@ static u32 inet_addr_from_name(const char *local_interface); //connects a socket to a remote peer on a given port -GF_EXPORT -GF_Err gf_sk_connect(GF_Socket *sock, const char *PeerName, u16 PortNumber, const char *ifce_ip_or_name) +GF_Err gf_sk_connect_ex(GF_Socket *sock, const char *PeerName, u16 PortNumber, const char *ifce_ip_or_name, Bool use_udp_connect) { s32 ret; #ifdef GPAC_HAS_IPV6 @@ -2078,7 +2202,7 @@ if (sock->flags & GF_SOCK_IS_UN) { #ifdef GPAC_HAS_SOCK_UN struct sockaddr_un server_add; - if (!sock->socket) { + if (SOCKET_INVALID(sock->socket)) { sock->socket = socket(AF_UNIX, (sock->flags & GF_SOCK_IS_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0); if (sock->flags & GF_SOCK_NON_BLOCKING) gf_sk_set_block_mode(sock, GF_TRUE); @@ -2144,10 +2268,9 @@ } } #endif - if (!sock->socket) { + if (SOCKET_INVALID(sock->socket)) { sock->socket = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); - if (sock->socket == INVALID_SOCKET) { - sock->socket = NULL_SOCKET; + if (SOCKET_INVALID(sock->socket)) { continue; } if (sock->flags & GF_SOCK_IS_TCP) { @@ -2167,60 +2290,70 @@ } } - if (sock->flags & GF_SOCK_IS_TCP) { #if defined(WIN32) || defined(_WIN32_WCE) //on winsock we must check writability between two connects for non-blocking sockets - if (sock->flags & GF_SOCK_HAS_CONNECT) { - if (gf_sk_select(sock, GF_SK_SELECT_WRITE) == GF_IP_NETWORK_EMPTY) { - GF_Err e = gf_sk_probe(sock); - if (e && (e != GF_IP_NETWORK_EMPTY)) return e; - return GF_IP_NETWORK_EMPTY; - } + if ((sock->flags & GF_SOCK_IS_TCP) && (sock->flags & GF_SOCK_HAS_CONNECT)) { + if (gf_sk_select(sock, GF_SK_SELECT_WRITE) == GF_IP_NETWORK_EMPTY) { + GF_Err e = gf_sk_probe(sock); + if (e && (e != GF_IP_NETWORK_EMPTY)) return e; + return GF_IP_NETWORK_EMPTY; } + } #endif + if (sock->flags & GF_SOCK_IS_TCP) { GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("Sock_IPV6 Connecting to %s:%d\n", PeerName, PortNumber)); - ret = connect(sock->socket, aip->ai_addr, (int) aip->ai_addrlen); - if (ret == SOCKET_ERROR) { - int err = LASTSOCKERROR; - if (sock->flags & GF_SOCK_NON_BLOCKING) { - switch (err) { - case EINPROGRESS: + } else if (!use_udp_connect) { + goto conn_ok; + } + ret = connect(sock->socket, aip->ai_addr, (int) aip->ai_addrlen); + if (ret == SOCKET_ERROR) { + int err = LASTSOCKERROR; + if (sock->flags & GF_SOCK_NON_BLOCKING) { + switch (err) { + case EINPROGRESS: #if defined(WIN32) || defined(_WIN32_WCE) - case WSAEWOULDBLOCK: + case WSAEWOULDBLOCK: #endif - freeaddrinfo(res); - if (lip) freeaddrinfo(lip); - //remember we issued a first connect - sock->flags |= GF_SOCK_HAS_CONNECT; - return GF_IP_NETWORK_EMPTY; + freeaddrinfo(res); + if (lip) freeaddrinfo(lip); + //remember we issued a first connect + sock->flags |= GF_SOCK_HAS_CONNECT; + return GF_IP_NETWORK_EMPTY; - case EISCONN: - case EALREADY: + case EISCONN: + case EALREADY: #if defined(WIN32) || defined(_WIN32_WCE) - case WSAEISCONN: + case WSAEISCONN: #endif + if (sock->flags & GF_SOCK_IS_TCP) { if (gf_sk_select(sock, GF_SK_SELECT_WRITE) == GF_OK) goto conn_ok; - freeaddrinfo(res); - if (lip) freeaddrinfo(lip); - return GF_IP_NETWORK_EMPTY; } + freeaddrinfo(res); + if (lip) freeaddrinfo(lip); + return GF_IP_NETWORK_EMPTY; } - sock_close(sock); - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("Sock_IPV6 Failed to connect to host %s: %s (%d) - retrying with next host address\n", PeerName, gf_errno_str(err), err )); - continue; } + sock_close(sock); + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("Sock_IPV6 Failed to connect to host %s: %s (%d) - retrying with next host address\n", PeerName, gf_errno_str(err), err )); + continue; + } + conn_ok: + if (sock->flags & GF_SOCK_IS_TCP) { GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("Sock_IPV6 Connected to %s:%d\n", PeerName, PortNumber)); - sock->flags &= ~GF_SOCK_HAS_CONNECT; + } else { + //udp+connect, do not use HAS_PEER + sock->flags &= ~GF_SOCK_HAS_PEER; + } + sock->flags &= ~GF_SOCK_HAS_CONNECT; #ifdef SO_NOSIGPIPE - int value = 1; - setsockopt(sock->socket, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)); + int value = 1; + setsockopt(sock->socket, SOL_SOCKET, SO_NOSIGPIPE, SSO_CAST &value, sizeof(value)); #endif - } memcpy(&sock->dest_addr, aip->ai_addr, aip->ai_addrlen); sock->dest_addr_len = (u32) aip->ai_addrlen; freeaddrinfo(res); @@ -2253,7 +2386,7 @@ } #endif - if (!sock->socket) { + if (SOCKET_INVALID(sock->socket)) { sock->socket = socket(AF_INET, (sock->flags & GF_SOCK_IS_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0); if (sock->flags & GF_SOCK_NON_BLOCKING) gf_sk_set_block_mode(sock, GF_TRUE); @@ -2287,19 +2420,20 @@ GF_Err e = gf_sk_bind(sock, ifce_ip_or_name, PortNumber, PeerName, PortNumber, GF_SOCK_REUSE_PORT); if (e) return e; } - if (!(sock->flags & GF_SOCK_IS_TCP)) { - return GF_OK; - } #if defined(WIN32) || defined(_WIN32_WCE) //on winsock we must check writability between two connects for non-blocking sockets - if (sock->flags & GF_SOCK_HAS_CONNECT) { + if ((sock->flags & GF_SOCK_IS_TCP) && (sock->flags & GF_SOCK_HAS_CONNECT)) { if (gf_sk_select(sock, GF_SK_SELECT_WRITE) == GF_IP_NETWORK_EMPTY) return GF_IP_NETWORK_EMPTY; } #endif - GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("Sock_IPV4 Connecting to %s:%d\n", PeerName, PortNumber)); + if (sock->flags & GF_SOCK_IS_TCP) { + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("Sock_IPV4 Connecting to %s:%d\n", PeerName, PortNumber)); + } else if (!use_udp_connect) { + return GF_OK; + } ret = connect(sock->socket, (struct sockaddr *) &sock->dest_addr, sizeof(struct sockaddr)); if (ret == SOCKET_ERROR) { u32 res = LASTSOCKERROR; @@ -2318,8 +2452,10 @@ #if defined(WIN32) || defined(_WIN32_WCE) case WSAEISCONN: #endif - if (gf_sk_select(sock, GF_SK_SELECT_WRITE) == GF_OK) - return GF_OK; + if (sock->flags & GF_SOCK_IS_TCP) { + if (gf_sk_select(sock, GF_SK_SELECT_WRITE) == GF_OK) + return GF_OK; + } return GF_IP_NETWORK_EMPTY; } } @@ -2327,6 +2463,7 @@ switch (res) { case EAGAIN: + case EINTR: #if defined(WIN32) || defined(_WIN32_WCE) case WSAEWOULDBLOCK: #endif @@ -2344,16 +2481,24 @@ } } sock->flags &= ~GF_SOCK_HAS_CONNECT; - GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("Sock_IPV4 Connected to %s:%d\n", PeerName, PortNumber)); - + if (sock->flags & GF_SOCK_IS_TCP) { + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("Sock_IPV4 Connected to %s:%d\n", PeerName, PortNumber)); + } #endif return GF_OK; } +GF_EXPORT +GF_Err gf_sk_connect(GF_Socket *sock, const char *PeerName, u16 PortNumber, const char *ifce_ip_or_name) +{ + return gf_sk_connect_ex(sock, PeerName, PortNumber, ifce_ip_or_name, GF_FALSE); + +} //binds the given socket to the specified port. If ReUse is true //this will enable reuse of ports on a single machine GF_EXPORT -GF_Err gf_sk_bind(GF_Socket *sock, const char *ifce_ip_or_name, u16 port, const char *peer_name, u16 peer_port, u32 options) +GF_Err gf_sk_bind_ex(GF_Socket *sock, const char *ifce_ip_or_name, u16 port, const char *peer_name, u16 peer_port, u32 options, + u8 **dst_sock_addr, u32 *dst_sock_addr_len, u8 **src_sock_addr, u32 *src_sock_addr_len) { #ifdef GPAC_HAS_IPV6 struct addrinfo *res, *aip; @@ -2368,18 +2513,25 @@ s32 ret = 0; s32 optval; - if (!sock || sock->socket) return GF_BAD_PARAM; + if (dst_sock_addr) { + *dst_sock_addr = NULL; + *dst_sock_addr_len = 0; + } + if (src_sock_addr) { + *src_sock_addr = NULL; + *src_sock_addr_len = 0; + } + //socket must not be created + if (!sock || !SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (ifce_ip_or_name && !strcmp(ifce_ip_or_name, "127.0.0.1")) ifce_ip_or_name = NULL; if (sock->flags & GF_SOCK_IS_UN) { #ifdef GPAC_HAS_SOCK_UN struct sockaddr_un server_un; - if (!sock->socket) { - sock->socket = socket(AF_UNIX, (sock->flags & GF_SOCK_IS_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0); - if (sock->flags & GF_SOCK_NON_BLOCKING) - gf_sk_set_block_mode(sock, GF_TRUE); - } + sock->socket = socket(AF_UNIX, (sock->flags & GF_SOCK_IS_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0); + if (sock->flags & GF_SOCK_NON_BLOCKING) + gf_sk_set_block_mode(sock, GF_TRUE); server_un.sun_family = AF_UNIX; strncpy(server_un.sun_path, peer_name, sizeof(server_un.sun_path)-1); server_un.sun_pathsizeof(server_un.sun_path)-1=0; @@ -2424,10 +2576,22 @@ memcpy(&sock->dest_addr, res->ai_addr, res->ai_addrlen); sock->dest_addr_len = (u32) res->ai_addrlen; freeaddrinfo(res); + + if (dst_sock_addr) { + *dst_sock_addr = gf_malloc(sizeof(u8) * sock->dest_addr_len); + memcpy(*dst_sock_addr, &sock->dest_addr, sock->dest_addr_len); + *dst_sock_addr_len = sock->dest_addr_len; + } } res = gf_sk_get_ifce_ipv6_addr(ifce_ip_or_name, port, af, AI_PASSIVE, type); - if (!res) return GF_IP_ADDRESS_NOT_FOUND; + if (!res) { + if (dst_sock_addr && *dst_sock_addr) { + gf_free(*dst_sock_addr); + *dst_sock_addr = NULL; + } + return GF_IP_ADDRESS_NOT_FOUND; + } /*for all interfaces*/ for (aip=res; aip!=NULL; aip=aip->ai_next) { @@ -2446,11 +2610,11 @@ if (peer_name && peer_port) { if (sock->dest_addr.ss_family==AF_INET) { struct sockaddr_in *r_add = (struct sockaddr_in *) &sock->dest_addr; - sock->cap_info->peer_port = r_add->sin_port; + sock->cap_info->peer_port = ntohs(r_add->sin_port); sock->cap_info->peer_addr_v4 = r_add->sin_addr.s_addr; } else { struct sockaddr_in6 *r_add = (struct sockaddr_in6 *) &sock->dest_addr; - sock->cap_info->peer_port = r_add->sin6_port; + sock->cap_info->peer_port = ntohs(r_add->sin6_port); memcpy(sock->cap_info->peer_addr_v6, &(r_add->sin6_addr), sizeof(bin128)); } } @@ -2460,13 +2624,16 @@ else sock->flags &= ~GF_SOCK_IS_IPV6; freeaddrinfo(res); + if (dst_sock_addr && *dst_sock_addr) { + gf_free(*dst_sock_addr); + *dst_sock_addr = NULL; + } return GF_OK; } } #endif sock->socket = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); - if (sock->socket == INVALID_SOCKET) { - sock->socket = NULL_SOCKET; + if (SOCKET_INVALID(sock->socket)) { continue; } if (options & GF_SOCK_REUSE_PORT) { @@ -2481,26 +2648,49 @@ if (sock->flags & GF_SOCK_NON_BLOCKING) gf_sk_set_block_mode(sock, GF_TRUE); - if (peer_name && peer_port) + if (peer_name && peer_port) { sock->flags |= GF_SOCK_HAS_PEER; + } - if (! (options & GF_SOCK_FAKE_BIND) ) { +#if defined(GPAC_CONFIG_LINUX) || defined(GPAC_CONFIG_DARWIN) + //we use implicit bind (assign on first sendto) - not doing so makes poll/select fail in readers lanched after the sender + if (peer_name && !strcmp(peer_name, "127.0.0.1") && (options & GF_SOCK_IS_SENDER)) { + } else +#endif + { ret = bind(sock->socket, aip->ai_addr, (int) aip->ai_addrlen); if (ret == SOCKET_ERROR) { - GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket bind failed: %s\n", gf_errno_str(LASTSOCKERROR) )); + if (dst_sock_addr && *dst_sock_addr) { + gf_free(*dst_sock_addr); + *dst_sock_addr = NULL; + } sock_close(sock); + if (!(options & GF_SOCK_REUSE_PORT) && (LASTSOCKERROR == EADDRINUSE)) { + return GF_IP_CONNECTION_FAILURE; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket bind failed: %s\n", gf_errno_str(LASTSOCKERROR) )); continue; } } if (aip->ai_family==PF_INET6) sock->flags |= GF_SOCK_IS_IPV6; else sock->flags &= ~GF_SOCK_IS_IPV6; + if (src_sock_addr) { + *src_sock_addr = gf_malloc(sizeof(u8) * res->ai_addrlen); + memcpy(*src_sock_addr, res->ai_addr, res->ai_addrlen); + *src_sock_addr_len = (u32) res->ai_addrlen; + } + freeaddrinfo(res); return GF_OK; } freeaddrinfo(res); + if (dst_sock_addr && *dst_sock_addr) { + gf_free(*dst_sock_addr); + *dst_sock_addr = NULL; + } GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("Socket Cannot bind to ifce %s port %d\n", ifce_ip_or_name ? ifce_ip_or_name : "any", port)); - return GF_IP_CONNECTION_FAILURE; + return GF_IP_NETWORK_FAILURE; #else @@ -2545,6 +2735,11 @@ LocalAdd.sin_addr.s_addr = ip_add; addrlen = sizeof(struct sockaddr_in); + if (src_sock_addr) { + *src_sock_addr = gf_malloc(sizeof(u8) * addrlen); + memcpy(*src_sock_addr, &LocalAdd, addrlen); + *src_sock_addr_len = addrlen; + } if (options & GF_SOCK_REUSE_PORT) { optval = 1; @@ -2555,12 +2750,24 @@ #endif } - if (! (options & GF_SOCK_FAKE_BIND) ) { - /*bind the socket*/ + /*bind the socket*/ +#ifdef GPAC_CONFIG_LINUX + //see above comment + if (peer_name && !strcmp(peer_name, "127.0.0.1") && (options & GF_SOCK_IS_SENDER) ) { + } else +#endif + { ret = bind(sock->socket, (struct sockaddr *) &LocalAdd, (int) addrlen); if (ret == SOCKET_ERROR) { + if (!(options & GF_SOCK_REUSE_PORT) && (LASTSOCKERROR == EADDRINUSE)) { + if (src_sock_addr && *src_sock_addr) { + gf_free(*src_sock_addr); + *src_sock_addr = NULL; + } + return GF_IP_CONNECTION_FAILURE; + } GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("socket cannot bind socket: %s\n", gf_errno_str(LASTSOCKERROR) )); - ret = GF_IP_CONNECTION_FAILURE; + ret = GF_IP_NETWORK_FAILURE; } } @@ -2574,6 +2781,12 @@ else memcpy((char *) &sock->dest_addr.sin_addr, Host->h_addr_list0, sizeof(u32)); } sock->flags |= GF_SOCK_HAS_PEER; + + if (dst_sock_addr && !ret) { + *dst_sock_addr = gf_malloc(sizeof(u8) * sock->dest_addr_len); + memcpy(*dst_sock_addr, &sock->dest_addr, sock->dest_addr_len); + *dst_sock_addr_len = sock->dest_addr_len; + } } if (sock->flags & GF_SOCK_HAS_PEER) { GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("socket socket bound to %08X - port %d - remote peer: %s:%d\n", ip_add, port, peer_name, peer_port)); @@ -2584,6 +2797,12 @@ #endif } +GF_EXPORT +GF_Err gf_sk_bind(GF_Socket *sock, const char *ifce_ip_or_name, u16 port, const char *peer_name, u16 peer_port, u32 options) +{ + return gf_sk_bind_ex(sock, ifce_ip_or_name, port, peer_name, peer_port, options, NULL, NULL, NULL, NULL); +} + Bool gpac_use_poll=GF_TRUE; static GF_Err poll_select(GF_Socket *sock, GF_SockSelectMode mode, u32 usec, Bool force_select) @@ -2605,6 +2824,7 @@ if (ready<0) { switch (LASTSOCKERROR) { case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket cannot poll: %s\n", gf_errno_str(LASTSOCKERROR) )); @@ -2643,6 +2863,7 @@ if (ready == SOCKET_ERROR) { switch (LASTSOCKERROR) { case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("socket select failure: %s\n", gf_errno_str(LASTSOCKERROR))); @@ -2661,8 +2882,7 @@ } //send length bytes of a buffer -GF_EXPORT -GF_Err gf_sk_send_ex(GF_Socket *sock, const u8 *buffer, u32 length, u32 *written) +static GF_Err gf_sk_send_internal(GF_Socket *sock, const u8 *buffer, u32 length, const u8 *address, u32 address_len, u32 *written) { u32 count; s32 res; @@ -2687,7 +2907,7 @@ #endif //the socket must be bound or connected - if (!sock || !sock->socket) + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (! (sock->flags & GF_SOCK_NON_BLOCKING)) { @@ -2699,18 +2919,21 @@ //direct writing count = 0; while (count < length) { + int sflags = 0; +#ifdef MSG_NOSIGNAL + sflags = MSG_NOSIGNAL; +#endif if (sock->flags & GF_SOCK_HAS_PEER) { res = (s32) sendto(sock->socket, (char *) buffer+count, length - count, 0, (struct sockaddr *) &sock->dest_addr, sock->dest_addr_len); + } else if (address && address_len) { + res = (s32) sendto(sock->socket, (char *) buffer+count, length - count, 0, (struct sockaddr *) address, address_len); } else { - int sflags = 0; -#ifdef MSG_NOSIGNAL - sflags = MSG_NOSIGNAL; -#endif res = (s32) send(sock->socket, (char *) buffer+count, length - count, sflags); } if (res == SOCKET_ERROR) { switch (res = LASTSOCKERROR) { case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; #ifndef __SYMBIAN32__ case ENOTCONN: @@ -2734,21 +2957,32 @@ } count += res; if (written) *written += res; + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("socket sent %u bytes\n", count )); } return GF_OK; } GF_EXPORT +GF_Err gf_sk_send_ex(GF_Socket *sock, const u8 *buffer, u32 length, u32 *written) +{ + return gf_sk_send_internal(sock, buffer, length, NULL, 0, written); +} +GF_EXPORT GF_Err gf_sk_send(GF_Socket *sock, const u8 *buffer, u32 length) { - return gf_sk_send_ex(sock, buffer, length, NULL); - + return gf_sk_send_internal(sock, buffer, length, NULL, 0, NULL); +} +GF_EXPORT +GF_Err gf_sk_send_to(GF_Socket *sock, const u8 *buffer, u32 length, const u8 *addr, u32 addr_len, u32 *written) +{ + return gf_sk_send_internal(sock, buffer, length, addr, addr_len, written); } + GF_Err gf_sk_select(GF_Socket *sock, GF_SockSelectMode mode) { //the socket must be bound or connected - if (!sock || !sock->socket) + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; //check write and read @@ -2933,7 +3167,7 @@ return 0xFFFFFFFF; } if (!ret) { - GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("core Interface %s found but has no IPv4 addressing, using INADRR_ANY\n", local_interface)); + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("core Interface %s found but has no IPv4 addressing, using INADDR_ANY\n", local_interface)); return htonl(INADDR_ANY); } return ret; @@ -3019,6 +3253,41 @@ return GF_FALSE; } +typedef struct +{ + const char *ip_or_name; + char *res_v6, *res_v4; +} GetIPInfo; + +static Bool get_ifce_enum(void *cbk, const char *name, const char *IP, u32 flags) +{ + GetIPInfo *info = (GetIPInfo*)cbk; + if (strcmp(info->ip_or_name, name) && strcmp(info->ip_or_name, IP)) return GF_FALSE; + if (flags & GF_NETIF_IPV6) { + if (!info->res_v6) info->res_v6 = gf_strdup(IP); + } else { + if (!info->res_v4) info->res_v4 = gf_strdup(IP); + } + return GF_FALSE; +} + +Bool gf_net_get_adapter_ip(const char *ip_or_name, char **ipv4, char **ipv6) +{ + GetIPInfo info; + info.ip_or_name = ip_or_name; + info.res_v4 = info.res_v6 = NULL; + if (ipv4) *ipv4 = NULL; + if (ipv6) *ipv6 = NULL; + if (!ip_or_name) return GF_FALSE; + Bool res = gf_net_enum_interfaces(get_ifce_enum, &info); + if (!res) return GF_FALSE; + if (ipv4) *ipv4 = info.res_v4; + else if (info.res_v4) gf_free(info.res_v4); + if (ipv6) *ipv6 = info.res_v6; + else if (info.res_v6) gf_free(info.res_v6); + return GF_TRUE; +} + GF_EXPORT GF_Err gf_sk_setup_multicast_ex(GF_Socket *sock, const char *multi_IPAdd, u16 MultiPortNumber, u32 TTL, Bool NoBind, const char *ifce_ip_or_name, const char **src_ip_inc, u32 nb_src_ip_inc, const char **src_ip_exc, u32 nb_src_ip_exc) @@ -3035,7 +3304,8 @@ GF_Err e; u32 local_add_id; - if (!sock || sock->socket) return GF_BAD_PARAM; + //socket must not be created + if (!sock || !SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (TTL > 255) TTL = 255; @@ -3174,8 +3444,7 @@ if ((aip->ai_family==PF_INET) && aip->ai_next && (aip->ai_next->ai_family==PF_INET6)) continue; } sock->socket = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); - if (sock->socket == INVALID_SOCKET) { - sock->socket = NULL_SOCKET; + if (SOCKET_INVALID(sock->socket)) { continue; } @@ -3208,7 +3477,7 @@ } freeaddrinfo(res); - if (!sock->socket) return GF_IP_CONNECTION_FAILURE; + if (SOCKET_INVALID(sock->socket) ) return GF_IP_CONNECTION_FAILURE; /*TODO: copy over other properties (recption buffer size & co)*/ if (sock->flags & GF_SOCK_NON_BLOCKING) @@ -3344,8 +3613,16 @@ #ifndef GPAC_DISABLE_NETCAP if (sock->cap_info && sock->cap_info->nf->cap_mode) { - sock->cap_info->host_port = MultiPortNumber; - sock->cap_info->host_addr_v4 = inet_addr(multi_IPAdd); + //in GPC mode, store in host port/addr + if (sock->cap_info->nf->cap_mode==NETCAP_GPAC) { + sock->cap_info->host_port = MultiPortNumber; + sock->cap_info->host_addr_v4 = inet_addr(multi_IPAdd); + } else { + sock->cap_info->host_port = MultiPortNumber; + sock->cap_info->host_addr_v4 = inet_addr_from_name(ifce_ip_or_name ? ifce_ip_or_name : "127.0.0.1"); + sock->cap_info->peer_port = MultiPortNumber; + sock->cap_info->peer_addr_v4 = inet_addr(multi_IPAdd); + } sock->flags &= ~GF_SOCK_IS_IPV6; sock->flags |= GF_SOCK_IS_MULTICAST; return GF_OK; @@ -3355,6 +3632,8 @@ //IPv4 setup sock->socket = socket(AF_INET, (sock->flags & GF_SOCK_IS_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0); + if (SOCKET_INVALID(sock->socket)) return GF_IP_NETWORK_FAILURE; + if (sock->flags & GF_SOCK_NON_BLOCKING) gf_sk_set_block_mode(sock, GF_TRUE); sock->flags &= ~GF_SOCK_IS_IPV6; @@ -3477,6 +3756,8 @@ if (sk->cap_info) { sg->nb_nfs++; sg->nb_socks = gf_list_count(sg->sockets); + if (sk->cap_info->nf && (gf_list_find(sk->cap_info->nf->read_socks, sk)<0)) + gf_list_add(sk->cap_info->nf->read_socks, sk); return; } #endif @@ -3493,7 +3774,7 @@ sg->fdssg->nb_fds.events = sg->last_mask; sg->fdssg->nb_fds.revents = 0; sk->poll_idx = sg->nb_fds+1; - gf_assert(sg->fdssg->nb_fds.fd != 0); + gf_assert(! SOCKET_INVALID(sg->fdssg->nb_fds.fd) ); sg->nb_fds++; #endif } @@ -3505,7 +3786,7 @@ #ifndef GPAC_DISABLE_NETCAP if (sk->cap_info && sk->cap_info->nf->read_socks) { - if (sg->nb_nfs) + if (sg->nb_nfs && (pidx>=0)) sg->nb_nfs--; sg->nb_socks = gf_list_count(sg->sockets); @@ -3515,6 +3796,7 @@ sk->cap_info->nf->read_sock_selected=NULL; //do not load a new packet here as this could go into infinite loop when netcap-loop=-1 ... } + gf_list_del_item(sk->cap_info->nf->read_socks, sk); return; } #endif @@ -3609,13 +3891,14 @@ sg->last_mask = mask; for (i=0; i<sg->nb_fds; i++) { sg->fdsi.events = mask; - gf_assert(sg->fdsi.fd != 0); + gf_assert(! SOCKET_INVALID(sg->fdsi.fd) ); } } int res = poll(sg->fds, sg->nb_fds, usec_wait/1000); if (res<0) { switch (LASTSOCKERROR) { case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket cannot poll: %s\n", gf_errno_str(LASTSOCKERROR) )); @@ -3667,10 +3950,7 @@ GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket cannot select, BAD descriptor\n")); return GF_IP_CONNECTION_CLOSED; case EAGAIN: - return GF_IP_NETWORK_EMPTY; case EINTR: - /* Interrupted system call*/ - GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket network is lost\n")); return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket cannot select: %s\n", gf_errno_str(LASTSOCKERROR) )); @@ -3767,7 +4047,8 @@ nf->pck_len -= res; if (!nf->pck_len) { nf->dst_port = 0; - gf_netcap_load_pck(nf, 0); + //do not load packet once done as this could lead to packet drop if the target socket is not yet created + //gf_netcap_load_pck(nf, 0); } if (BytesRead) @@ -3776,7 +4057,7 @@ return GF_OK; } #endif - if (!sock->socket) return GF_BAD_PARAM; + if (SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (do_select && !(sock->flags & GF_SOCK_NON_BLOCKING)) { //check read @@ -3785,18 +4066,20 @@ } if (!buffer) return GF_OK; - if (sock->flags & GF_SOCK_HAS_PEER) + if ((sock->flags & GF_SOCK_HAS_PEER) || sock->dest_addr_len) { res = (s32) recvfrom(sock->socket, (char *) buffer, length, 0, (struct sockaddr *)&sock->dest_addr, &sock->dest_addr_len); - else { + } else { res = (s32) recv(sock->socket, (char *) buffer, length, 0); - if (!do_select && (res == 0)) - return GF_IP_CONNECTION_CLOSED; } + if (!do_select && (res == 0)) + return GF_IP_CONNECTION_CLOSED; if (res == SOCKET_ERROR) { res = LASTSOCKERROR; switch (res) { case EAGAIN: + case EINTR: + case ECONNREFUSED: return GF_IP_NETWORK_EMPTY; #if defined(WIN32) || defined(_WIN32_WCE) @@ -3835,6 +4118,7 @@ #endif if (!res) return GF_IP_NETWORK_EMPTY; + GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("socket read %u bytes\n", res )); if (BytesRead) *BytesRead = res; @@ -3857,7 +4141,7 @@ GF_Err gf_sk_listen(GF_Socket *sock, u32 MaxConnection) { s32 i; - if (!sock || !sock->socket) return GF_BAD_PARAM; + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; #ifndef GPAC_DISABLE_NETCAP if (sock->cap_info && sock->cap_info->nf->cap_mode) { @@ -3870,7 +4154,8 @@ MaxConnection = SOMAXCONN; } i = listen(sock->socket, MaxConnection); - if (i == SOCKET_ERROR) return GF_IP_NETWORK_FAILURE; + if (i == SOCKET_ERROR) + return GF_IP_NETWORK_FAILURE; sock->flags |= GF_SOCK_IS_LISTENING; return GF_OK; } @@ -3895,9 +4180,10 @@ sk = accept(sock->socket, (struct sockaddr *) &sock->dest_addr, &client_address_size); //we either have an error or we have no connections - if (sk == INVALID_SOCKET) { + if (SOCKET_INVALID(sk)) { switch (LASTSOCKERROR) { case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("socket accept error: %s\n", gf_errno_str(LASTSOCKERROR))); @@ -3919,7 +4205,7 @@ #ifdef SO_NOSIGPIPE int value = 1; - setsockopt((*newConnection)->socket, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)); + setsockopt((*newConnection)->socket, SOL_SOCKET, SO_NOSIGPIPE, SSO_CAST &value, sizeof(value)); #endif #if defined(WIN32) || defined(_WIN32_WCE) @@ -3940,7 +4226,7 @@ #endif u32 size; - if (!sock || !sock->socket) return GF_BAD_PARAM; + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (Port) { #ifdef GPAC_HAS_IPV6 @@ -3971,9 +4257,49 @@ { u32 one; - if (!sock || !(sock->flags & GF_SOCK_IS_TCP) || !sock->socket) + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; + if (!(sock->flags & GF_SOCK_IS_TCP)) { +#if defined(IPV6_MTU_DISCOVER) || defined(IPV6_PMTUDISC_DO) || defined(IPV6_DONTFRAG) || defined(IP_MTU_DISCOVER) || defined(IP_DONTFRAG) + int val; +#endif +#ifdef GPAC_HAS_IPV6 + sock->dest_addr_len = sizeof(struct sockaddr_storage); +#else + sock->dest_addr_len = sizeof(struct sockaddr); +#endif + if (sock->flags & GF_SOCK_IS_IPV6) { +#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) + val = IPV6_PMTUDISC_DO; + if (setsockopt(sock->socket, IPPROTO_IPV6, IPV6_MTU_DISCOVER, SSO_CAST &val, sizeof(val)) == -1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket Failed to set IPV6_MTU_DISCOVER: %s\n", gf_errno_str(LASTSOCKERROR) )); + } +#endif +#ifdef IPV6_DONTFRAG + val = 1; + if (setsockopt(sock->socket, IPPROTO_IPV6, IPV6_DONTFRAG, SSO_CAST &val, sizeof(val) ) == -1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket Failed to set IPV6_DONTFRAG: %s\n", gf_errno_str(LASTSOCKERROR) )); + } +#endif + } else { +#ifdef IP_MTU_DISCOVER + val = IP_PMTUDISC_DO; + if (setsockopt(sock->socket, IPPROTO_IP, IP_MTU_DISCOVER, SSO_CAST &val, sizeof(val)) == -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("socket Failed to set IP_MTU_DISCOVER: %s\n", gf_errno_str(LASTSOCKERROR) )); + return GF_OK; + } +#endif +#ifdef IP_DONTFRAG + val = 1; + if (setsockopt(sock->socket, IPPROTO_IP, IP_DONTFRAG, SSO_CAST &val, sizeof(val) ) == -1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket Failed to set IP_DONTFRAG: %s\n", gf_errno_str(LASTSOCKERROR) )); + } +#endif + } + return GF_OK; + } + one = serverOn ? 1 : 0; setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, SSO_CAST &one, sizeof(u32)); #ifndef __SYMBIAN32__ @@ -3983,25 +4309,42 @@ } GF_EXPORT -GF_Err gf_sk_get_remote_address(GF_Socket *sock, char *buf) +GF_Err gf_sk_get_remote_address_port(GF_Socket *sock, char *buf, u32 *port) { #ifdef GPAC_HAS_IPV6 char clienthostNI_MAXHOST; char servnameNI_MAXHOST; struct sockaddr_in6 * addrptr = (struct sockaddr_in6 *)(&sock->dest_addr); - if (!sock || !sock->socket) return GF_BAD_PARAM; + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; my_inet_ntop(AF_INET, addrptr, clienthost, NI_MAXHOST); strcpy(buf, clienthost); + if (port) { + *port = ntohs(addrptr->sin6_port); + } if (getnameinfo((struct sockaddr *)addrptr, sock->dest_addr_len, clienthost, NI_MAXHOST, servname, NI_MAXHOST, NI_NUMERICHOST) == 0) { strcpy(buf, clienthost); } #else - if (!sock || !sock->socket) return GF_BAD_PARAM; + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; strcpy(buf, inet_ntoa(sock->dest_addr.sin_addr)); + if (port) { + *port = ntohs(sock->dest_addr.sin_port); + } #endif return GF_OK; } +GF_EXPORT +GF_Err gf_sk_get_remote_address(GF_Socket *sock, char *buf) +{ + return gf_sk_get_remote_address_port(sock, buf, NULL); +} +const u8 *gf_sk_get_address(GF_Socket *sock, u32 *addr_size) +{ + if (!sock || !addr_size) return NULL; + *addr_size = sock->dest_addr_len; + return (const u8 *) &sock->dest_addr; +} #if 0 //unused @@ -4019,7 +4362,7 @@ #endif //the socket must be bound or connected - if (!sock || !sock->socket) return GF_BAD_PARAM; + if (!sock || SOCKET_INVALID(sock->socket)) return GF_BAD_PARAM; if (remoteHost && !remotePort) return GF_BAD_PARAM; //check write @@ -4066,6 +4409,7 @@ if (res == SOCKET_ERROR) { switch (LASTSOCKERROR) { case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("socket sendto error: %s\n", gf_errno_str(LASTSOCKERROR))); @@ -4101,6 +4445,7 @@ switch (res) { case 0: case EAGAIN: + case EINTR: return GF_IP_NETWORK_EMPTY; default: GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("socket probe error: %s\n", gf_errno_str(res))); @@ -4113,11 +4458,115 @@ return GF_OK; } + +GF_EXPORT +char *gf_net_bump_ip_address(const char *in_ip, u32 increment) +{ + if (!in_ip) return NULL; + if (!gf_sk_is_multicast_address(in_ip)) return NULL; + if (!increment) return gf_strdup(in_ip); + + u32 new_range=0; + + char *alloc_ip=NULL; + if (!strnicmp(in_ip, "ff", 2)) { + char *add_end = strstr(in_ip, "::"); + if (!add_end) add_end = 1 + strrchr(in_ip, ':'); + else add_end+=2; + char *sep2 = strrchr(add_end, '/'); + if (sep2) sep20 = 0; + u32 inc; + if (!add_end0) { + inc = 0; + gf_dynstrcat(&alloc_ip, in_ip, NULL); + } else { + char c = add_end0; + add_end0 = 0; + gf_dynstrcat(&alloc_ip, in_ip, NULL); + add_end0 = c; + inc = atoi(add_end); + } + new_range = inc + increment; + char val10; + sprintf(val, ".%u", new_range); + add_end0 = 0; + gf_dynstrcat(&alloc_ip, val, NULL); + if (sep2) { + sep20 = '/'; + gf_dynstrcat(&alloc_ip, sep2, NULL); + } + } else { + char *sep = strrchr(in_ip, '.'); + sep0 =0; + u32 inc = atoi(sep+1); + new_range = inc + increment; + char val10; + sep0 = 0; + gf_dynstrcat(&alloc_ip, in_ip, NULL); + sep0 = '.'; + sprintf(val, ".%u", new_range); + gf_dynstrcat(&alloc_ip, val, NULL); + } + if (!alloc_ip) return NULL; + if (new_range>0xFF) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("IP Dumping IP address beyond 0xFF is not supported\n")); + gf_free(alloc_ip); + return NULL; + } + return alloc_ip; +} + #else //for ntoh/hton #include <arpa/inet.h> #endif //GPAC_DISABLE_NETWORK +GF_EXPORT +GF_Err gf_net_reload_netcap() +{ +#if !defined(GPAC_DISABLE_NETWORK) && !defined(GPAC_DISABLE_NETCAP) + u32 i, count = gf_list_count(netcap_filters); + for (i=0; i<count; i++) { + GF_NetcapFilter *nf = gf_list_get(netcap_filters, i); + + if (nf->cap_bs) gf_bs_del(nf->cap_bs); +#ifdef GPAC_HAS_FD + if (nf->fd>=0) close(nf->fd); +#endif + if (nf->file) gf_fclose(nf->file); + gf_list_del(nf->read_socks); + nf->read_socks = NULL; + nf->nb_reloads++; + nf->init_time = 0; + nf->is_eos = GF_FALSE; + nf->pck_start_offset = 0; + nf->pck_len = 0; + nf->pck_flags = 0; + nf->pck_time = 0; + nf->pcap_num_interfaces = 0; + nf->pcapng_trail = 0; + nf->pcap_trail = 0; + nf->pck_idx_r = 0; + nf->pck_idx_w = 0; + nf->next_pck_range = 0; + nf->next_rand = 0; + nf->reorder_max_pck = 0; + nf->reorder_nb_pck = 0; + nf->delay_nb_pck = 0; + nf->reorder_seek_pos = 0; + nf->reorder_resume_pos = 0; + GF_Err e = GF_OK; + if (nf->dst) { + e = gf_netcap_record(nf); + } + else if (nf->src) { + e = gf_netcap_playback(nf); + } + if (e) return e; + } +#endif + return GF_OK; +} GF_EXPORT u32 gf_htonl(u32 val)
View file
gpac-2.4.0.tar.gz/src/utils/os_thread.c -> gpac-26.02.0.tar.gz/src/utils/os_thread.c
Changed
@@ -81,10 +81,13 @@ #ifndef GPAC_DISABLE_LOG #include <gpac/list.h> static GF_List *thread_bank = NULL; - +static u32 main_th_id = 0; static void log_add_thread(GF_Thread *t) { - if (!thread_bank) thread_bank = gf_list_new(); + if (!thread_bank) { + thread_bank = gf_list_new(); + main_th_id = gf_th_id(); + } gf_list_add(thread_bank, t); } static void log_del_thread(GF_Thread *t) @@ -95,17 +98,20 @@ thread_bank = NULL; } } -static const char *log_th_name(u32 id) + +static const char *log_th_name(u32 id, char szName100) { u32 i, count; if (!id) id = gf_th_id(); + if (main_th_id == id) return "Main Process"; count = gf_list_count(thread_bank); for (i=0; i<count; i++) { GF_Thread *t = (GF_Thread*)gf_list_get(thread_bank, i); if (t->id == id) return t->log_name; } - return "Main Process"; + sprintf(szName, "%u", id); + return szName; } const char *gf_th_log_name(GF_Thread *t) { @@ -284,7 +290,6 @@ if (!t->_signal) return GF_IO_ERR; } - GF_LOG(GF_LOG_INFO, GF_LOG_MUTEX, ("Thread %s Starting\n", t->log_name)); #ifdef WIN32 @@ -506,9 +511,11 @@ #endif /* We filter recursive calls (1 thread calling Lock several times in a row only locks ONCE the mutex. Holder is the current ThreadID of the mutex holder*/ - u32 Holder, HolderCount; + volatile u32 Holder; + u32 HolderCount; #ifndef GPAC_DISABLE_LOG char *log_name; + Bool nolog; #endif }; @@ -552,6 +559,7 @@ return tmp; } + GF_EXPORT void gf_mx_del(GF_Mutex *mx) { @@ -559,10 +567,10 @@ int err; #endif if (!mx) return; - #ifndef GPAC_DISABLE_LOG if (mx->Holder && (gf_th_id() != mx->Holder) && mx->log_name) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MUTEX, ("Mutex %s Destroying mutex from thread %s but hold by thread %s\n", mx->log_name, log_th_name(gf_th_id() ), log_th_name(mx->Holder) )); + char szName1100, szName2100; + GF_LOG(GF_LOG_WARNING, GF_LOG_MUTEX, ("Mutex %s Destroying mutex from thread %s but hold by thread %s\n", mx->log_name, log_th_name(gf_th_id(), szName1), log_th_name(mx->Holder, szName2) )); } #endif @@ -576,7 +584,14 @@ #endif } #else + int max_retries = 10; +retry: err = pthread_mutex_destroy(&mx->hMutex); + if (err == EBUSY && max_retries > 0) { + gf_sleep(10); + max_retries--; + goto retry; + } if (err) { #ifndef GPAC_DISABLE_LOG if (mx->log_name) { @@ -595,6 +610,14 @@ gf_free(mx); } +void gf_mx_toggle_log(GF_Mutex *mx, Bool nolog) +{ +#ifndef GPAC_DISABLE_LOG + if (mx) + mx->nolog = nolog; +#endif +} + GF_EXPORT void gf_mx_v(GF_Mutex *mx) { @@ -611,32 +634,38 @@ mx->HolderCount -= 1; if (mx->HolderCount == 0) { -#ifndef GPAC_DISABLE_LOG - if (mx->log_name) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Released by thread %s\n", mx->log_name, gf_sys_clock(), log_th_name(mx->Holder) )); - } -#endif mx->Holder = 0; + gf_fatal_assert(mx->HolderCount == 0); #ifdef WIN32 { BOOL ret = ReleaseMutex(mx->hMutex); if (!ret) { #ifndef GPAC_DISABLE_LOG - if (mx->log_name) { + if (mx->log_name && !mx->nolog) { + char szName100; DWORD err = GetLastError(); - GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex Couldn't release mutex (thread %s, error %d)\n", log_th_name(mx->Holder), err)); + GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex Couldn't release mutex (thread %s, error %d)\n", log_th_name(mx->Holder, szName), err)); } #endif - + return; } } #else if (pthread_mutex_unlock(&mx->hMutex)) { #ifndef GPAC_DISABLE_LOG - if (mx->log_name) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex Couldn't release mutex (thread %s)\n", log_th_name(mx->Holder))); + if (mx->log_name && !mx->nolog) { + char szName100; + GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex Couldn't release mutex (thread %s)\n", log_th_name(mx->Holder, szName))); } #endif + return; + } +#endif + +#ifndef GPAC_DISABLE_LOG + if (mx->log_name && !mx->nolog) { + char szName100; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s Released by thread %s (hcount %u)\n", mx->log_name, log_th_name(mx->Holder, szName), mx->HolderCount )); } #endif } @@ -656,14 +685,18 @@ if (!mx) return 1; caller = gf_th_id(); if (caller == mx->Holder) { + gf_fatal_assert(mx->HolderCount > 0); + gf_fatal_assert(mx->Holder); mx->HolderCount += 1; return 1; } #ifndef GPAC_DISABLE_LOG - mx_holder_name = mx->Holder ? log_th_name(mx->Holder) : "none"; - if (mx->Holder && mx->log_name) - GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Thread %s waiting a release from thread %s\n", mx->log_name, gf_sys_clock(), log_th_name(caller), mx_holder_name )); + if (mx->Holder && mx->log_name && !mx->nolog) { + char szName100; + mx_holder_name = mx->Holder ? log_th_name(mx->Holder, szName) : "none"; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s Thread %s waiting a release from thread %s (hcount %d)\n", mx->log_name, log_th_name(caller, szName), mx_holder_name, mx->HolderCount )); + } #endif #ifdef WIN32 @@ -676,24 +709,32 @@ } #else retCode = pthread_mutex_lock(&mx->hMutex); - if (retCode != 0 ) { + if (retCode != 0) { #ifndef GPAC_DISABLE_LOG - if (mx->log_name) { - if (retCode == EINVAL) - GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex %p=%s Not properly initialized.\n", mx, mx->log_name)); - if (retCode == EDEADLK) - GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex %p=%s Deadlock detected.\n", mx, mx->log_name)); - } + char name100 = {0}; + if (mx->log_name) + strncpy(name, mx->log_name, sizeof(name)-1); + else + sprintf(name, "id=%u", gf_th_id()); + + if (retCode == EINVAL) + GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex %p:%s Not properly initialized.\n", mx, name)); + if (retCode == EDEADLK) + GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex %p:%s Deadlock detected.\n", mx, name)); #endif /* GPAC_DISABLE_LOG */ gf_assert(0); return 0; } #endif /* NOT WIN32 */ + gf_assert(mx->Holder == 0); + gf_assert(mx->HolderCount == 0); mx->HolderCount = 1; mx->Holder = caller; + gf_fatal_assert(mx->Holder); #ifndef GPAC_DISABLE_LOG - if (mx->log_name) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Grabbed by thread %s\n", mx->log_name, gf_sys_clock(), log_th_name(mx->Holder) )); + if (mx->log_name && !mx->nolog) { + char szName100; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s Grabbed by thread %s\n", mx->log_name, log_th_name(mx->Holder, szName) )); } #endif return 1; @@ -716,13 +757,19 @@ Bool gf_mx_try_lock(GF_Mutex *mx) { u32 caller; - if (!mx) return GF_TRUE; + if (!mx) { + return GF_TRUE; + } caller = gf_th_id(); if (caller == mx->Holder) { + gf_fatal_assert(mx->HolderCount > 0); + gf_fatal_assert(mx->Holder); mx->HolderCount += 1; return GF_TRUE; } + //DO NOT LOG ANYTHING BEFORE RELEASING, this will break logging with mutex@debug in JS where we use try_lock + #ifdef WIN32 /*is the object signaled?*/ switch (WaitForSingleObject(mx->hMutex, 0)) { @@ -732,7 +779,8 @@ case WAIT_TIMEOUT: #ifndef GPAC_DISABLE_LOG if (mx->log_name) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Couldn't be locked by thread %s (grabbed by thread %s)\n", mx->log_name, gf_sys_clock(), log_th_name(caller), log_th_name(mx->Holder) )); + char szName1100, szName2100; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Couldn't be locked by thread %s (grabbed by thread %s)\n", mx->log_name, gf_sys_clock(), log_th_name(caller, szName1), log_th_name(mx->Holder, szName2) )); } #endif return GF_FALSE; @@ -740,9 +788,9 @@ #ifndef GPAC_DISABLE_LOG if (mx->log_name) { GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Mutex %s At %d WaitForSingleObject failed\n", mx->log_name, gf_sys_clock())); - return GF_FALSE; } #endif + return GF_FALSE; default: gf_assert(0); return GF_FALSE; @@ -751,17 +799,23 @@ if (pthread_mutex_trylock(&mx->hMutex) != 0 ) { #ifndef GPAC_DISABLE_LOG if (mx->log_name) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Couldn't release it for thread %s (grabbed by thread %s)\n", mx->log_name, gf_sys_clock(), log_th_name(caller), log_th_name(mx->Holder) )); + char szName1100, szName2100; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s Couldn't release it for thread %s (grabbed by thread %s)\n", mx->log_name, log_th_name(caller, szName1), log_th_name(mx->Holder, szName2) )); } #endif return GF_FALSE; } #endif - mx->Holder = caller; + gf_assert(mx->Holder == 0); + gf_assert(mx->HolderCount == 0); mx->HolderCount = 1; + mx->Holder = caller; + gf_assert(mx->Holder); + gf_assert(mx->HolderCount); #ifndef GPAC_DISABLE_LOG if (mx->log_name) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s At %d Grabbed by thread %s\n", mx->log_name, gf_sys_clock(), log_th_name(mx->Holder) )); + char szName100; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("Mutex %s Grabbed by thread %s\n", mx->log_name, log_th_name(mx->Holder, szName) )); } #endif return GF_TRUE; @@ -895,7 +949,9 @@ } #else +retry: if (sem_wait(sm->hSemaphore) < 0) { + if (errno == EINTR) goto retry; GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Semaphore failed to wait for semaphore: %d\n", errno)); return GF_FALSE; }
View file
gpac-26.02.0.tar.gz/src/utils/rmt_ws.c
Added
@@ -0,0 +1,951 @@ +#include <gpac/tools.h> +#include <gpac/thread.h> +#include <gpac/list.h> +#include <gpac/bitstream.h> +#include <gpac/network.h> +#include <gpac/download.h> +#include <gpac/base_coding.h> + +#ifndef GPAC_DISABLE_RMTWS + + +static const char websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + +struct RMT_WS { + + // The main server thread + GF_Thread* thread; + + // Should we stop the thread? + Bool should_stop; + + RMT_Settings settings; + +}; + + + +GF_DownloadSession *gf_dm_sess_new_server(GF_DownloadManager *dm, GF_Socket *server, void *ssl_ctx, gf_dm_user_io user_io, void *usr_cbk, Bool async, GF_Err *e); +void gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value); +GF_Err gf_dm_sess_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body); +void gf_dm_sess_clear_headers(GF_DownloadSession *sess); +void gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value); +GF_Err dm_sess_write(GF_DownloadSession *session, const u8 *buffer, u32 size); +GF_Err gf_dm_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read); +void httpout_format_date(u64 time, char szDate200, Bool for_listing); + +#ifdef GPAC_HAS_SSL + +void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e); +void *gf_ssl_server_context_new(const char *cert, const char *key); +void gf_ssl_server_context_del(void *ssl_server_ctx); +Bool gf_ssl_init_lib(); + +#endif + +enum { + RMT_WEBSOCKET_CONTINUATION = 0, + RMT_WEBSOCKET_TEXT = 1, + RMT_WEBSOCKET_BINARY = 2, + RMT_WEBSOCKET_CLOSE = 8, + RMT_WEBSOCKET_PING = 9, + RMT_WEBSOCKET_PONG = 10, +}; + +static char RMT_WEBSOCKET_PING_MSG2 = { 0x89, 0x00 }; + +struct __rmt_serverctx { + + RMT_WS* rmt; + + GF_Socket* server_sock; + + GF_SockGroup* sg; + + void *ssl_ctx; + + GF_DownloadManager* dm_sess; + + GF_List* active_clients; + +}; + +struct __rmt_clientctx { + + GF_Socket* client_sock; + RMT_ServerCtx* ctx; + GF_DownloadSession* http_sess; + + char peer_addressGF_MAX_IP_NAME_LEN + 16; + char buffer1024; + + Bool is_ws; + u64 last_active_time; + u64 last_ping_time; + Bool should_close; + + rmt_client_on_data_cbk on_data_cbk; + void* on_data_cbk_task; + + rmt_client_on_del_cbk on_del_cbk; + void* on_del_cbk_task; + +}; + +GF_EXPORT +const char* gf_rmt_get_peer_address(RMT_ClientCtx* client) { + if (client) + return client->peer_address; + return NULL; +} + +GF_EXPORT +void* gf_rmt_client_get_on_data_task(RMT_ClientCtx* client) { + if (client) + return client->on_data_cbk_task; + return NULL; +} + +GF_EXPORT +void gf_rmt_set_on_new_client_cbk(RMT_WS* rmt, void *task, rmt_on_new_client_cbk cbk) { + RMT_Settings *rmtcfg = gf_rmt_get_settings(rmt); + if (rmtcfg) { + rmtcfg->on_new_client_cbk = cbk; + rmtcfg->on_new_client_cbk_task = task; + } +} + +GF_EXPORT +void* gf_rmt_get_on_new_client_task(RMT_WS* rmt) { + if (!rmt) return NULL; + return rmt->settings.on_new_client_cbk_task; +} + +GF_EXPORT +void gf_rmt_client_set_on_del_cbk(RMT_ClientCtx* client, void* task, rmt_client_on_del_cbk cbk) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gf_rmt_client_set_on_del_cbk client %p task %p cbk %p\n", client, task, cbk)); + if (client) { + client->on_del_cbk = cbk; + client->on_del_cbk_task = task; + } +} + +GF_EXPORT +void* gf_rmt_client_get_on_del_task(RMT_ClientCtx* client) { + if (client) + return client->on_del_cbk_task; + return NULL; +} + +GF_EXPORT +RMT_WS* gf_rmt_client_get_rmt(RMT_ClientCtx* client) { + if (client && client->ctx) + return client->ctx->rmt; + return NULL; +} + + +void rmt_clientctx_del(RMT_ClientCtx* client) { + + if (!client) return; + + GF_LOG(GF_LOG_INFO, GF_LOG_RMTWS, ("RMT closing client %s\n", client->peer_address)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_clientctx_del client %p del_cbk %p\n", client, client ? client->on_del_cbk : NULL)); + + if (client->on_del_cbk) { + client->on_del_cbk(client->on_del_cbk_task); + } + + if (client->client_sock) { + if (client->ctx && client->ctx->sg) + gf_sk_group_unregister(client->ctx->sg, client->client_sock); + + if (!client->http_sess) + gf_sk_del(client->client_sock); //socket deleted by gf_dm_sess_del() if http_sess has been created + client->client_sock = NULL; + } + + if (client->http_sess) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("deleting http_sess %p\n", client->http_sess)); + gf_dm_sess_del(client->http_sess); + client->http_sess = NULL; + } + + client->ctx = NULL; + client->on_data_cbk = NULL; + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d rmt_clientctx_del client %p task %p\n", __FILE__, __LINE__, client, client->on_data_cbk_task)); + + if (client->on_data_cbk_task) gf_free(client->on_data_cbk_task); + client->on_data_cbk_task = NULL; + + if (client->on_del_cbk_task) gf_free(client->on_del_cbk_task); + client->on_del_cbk_task = NULL; + + gf_free(client); +} + +void rmt_serverctx_reset(RMT_ServerCtx* ctx) { + if (ctx->active_clients) { + while (gf_list_count(ctx->active_clients)) { + RMT_ClientCtx* client = gf_list_pop_back(ctx->active_clients); + rmt_clientctx_del(client); + client = NULL; + } + gf_list_del(ctx->active_clients); + ctx->active_clients = NULL; + } + + if (ctx->server_sock) { + if (ctx->sg) + gf_sk_group_unregister(ctx->sg, ctx->server_sock); + + gf_sk_del(ctx->server_sock); + ctx->server_sock = NULL; + } + + if (ctx->sg) { gf_sk_group_del(ctx->sg); ctx->sg = NULL; } + + if (ctx->dm_sess) { gf_dm_del(ctx->dm_sess); ctx->dm_sess = NULL; } + + if (ctx->ssl_ctx) { +#ifdef GPAC_HAS_SSL + gf_ssl_server_context_del(ctx->ssl_ctx); +#else + gf_free(ctx->ssl_ctx); +#endif + ctx->ssl_ctx = NULL; + } + + +} + +void rmt_close_client(RMT_ClientCtx* client) { + if (!client) return; + RMT_ServerCtx* ctx = client->ctx; + if (ctx && ctx->active_clients) { + gf_list_del_item(ctx->active_clients, client); + } + rmt_clientctx_del(client); +} + +GF_Err rmt_send_reply(GF_DownloadSession* http_sess, int responseCode, char* response_body, char* content_type) { + + u32 body_size = 0; + char szFmt100; + char szDate200; + + httpout_format_date(gf_net_get_utc(), szDate, GF_FALSE); + gf_dm_sess_set_header(http_sess, "Date", szDate); + gf_dm_sess_set_header(http_sess, "Server", gf_gpac_version()); + + if (response_body) { + body_size = (u32) strlen(response_body); + gf_dm_sess_set_header(http_sess, "Content-Type", content_type ? content_type : "text/html"); + sprintf(szFmt, "%d", body_size); + gf_dm_sess_set_header(http_sess, "Content-Length", szFmt); + + } + + return gf_dm_sess_send_reply(http_sess, responseCode, response_body, response_body ? (u32) strlen(response_body) : 0, (response_body==NULL)); + +} + +static void rmt_on_http_session_data(void *usr_cbk, GF_NETIO_Parameter *parameter) { + + RMT_ClientCtx* client_ctx = (RMT_ClientCtx*) usr_cbk; + if (!client_ctx || !client_ctx->ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT sess io on null session\n")); + return; + } + RMT_Settings* rmt_settings = gf_rmt_get_settings(client_ctx->ctx->rmt); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_on_http_session_data on peer %s param %p msg_type %d \nerror %s \ndata %p \nsize %d \nname %p \nvalue %p \nreply %d\n", + client_ctx->peer_address, + parameter, + parameter->msg_type, + gf_error_to_string(parameter->error), + parameter->data, + parameter->size, + parameter->name, + parameter->value, + parameter->reply + )); + + client_ctx->last_active_time = gf_sys_clock_high_res(); + + const char* durl = gf_dm_sess_get_resource_name(parameter->sess); + const char* ua = gf_dm_sess_get_header(parameter->sess, "User-Agent"); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("requested url: %s with ua %s\n", durl, ua)); + GF_Err e; + + if (parameter->msg_type != GF_NETIO_PARSE_REPLY) { + if (parameter->size) { + strncat(client_ctx->buffer, parameter->data, parameter->size); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("session data is now: %s\n", client_ctx->buffer)); + } + } + + if (parameter->msg_type == GF_NETIO_PARSE_REPLY) { + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("final buffer before response: %s\n", client_ctx->buffer)); + + GF_DownloadSession* http_sess = parameter->sess ; + + if (!client_ctx->is_ws) { + const char* ws_header_version = gf_dm_sess_get_header(http_sess, "Sec-WebSocket-Version"); + const char* ws_header_key = gf_dm_sess_get_header(http_sess, "Sec-WebSocket-Key"); + + if (!ws_header_version || strcmp(ws_header_version, "13") || !ws_header_key) { + + gf_dm_sess_clear_headers(http_sess); + gf_dm_sess_set_header(http_sess, "Connection", "close"); + + rmt_send_reply(http_sess, 404, "only ws connections are accepted", NULL); + client_ctx->should_close = GF_TRUE; + return; + + } + else { + + char* resp_key = gf_strdup(ws_header_key); + gf_dynstrcat(&resp_key, websocket_guid, NULL); + + u32 resp_key_len = (u32) strlen(resp_key); + if (resp_key_len < 1) + return; + + + u8 hashGF_SHA1_DIGEST_SIZE; + gf_sha1_csum( resp_key, resp_key_len, hash ); + + u32 end_b64 = gf_base64_encode(hash, GF_SHA1_DIGEST_SIZE, resp_key, resp_key_len); + resp_keyresp_key_len-1 = 0; + if (end_b64 < resp_key_len) { + resp_keyend_b64 = 0; + } else { + return; + } + + gf_dm_sess_clear_headers(http_sess); + + gf_dm_sess_set_header(http_sess, "Connection", "Upgrade"); + gf_dm_sess_set_header(http_sess, "Upgrade", "websocket"); + gf_dm_sess_set_header(http_sess, "Sec-WebSocket-Accept", resp_key); + + e = rmt_send_reply(http_sess, 101, NULL, NULL); + + if (e==GF_OK) { + client_ctx->is_ws = GF_TRUE; + + if (rmt_settings->on_new_client_cbk && rmt_settings->on_new_client_cbk_task) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d calling on new client cb %p with client %p\n", __FILE__,__LINE__, rmt_settings->on_new_client_cbk, client_ctx)); + rmt_settings->on_new_client_cbk(rmt_settings->on_new_client_cbk_task, client_ctx); + } + } + gf_free(resp_key); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("this should be a ws message right?\n")); + } + + + + memset(&client_ctx->buffer, 0, 1024); + } + + +} + + +GF_Err rmt_create_server(RMT_ServerCtx* ctx) { + + GF_Err e = GF_OK; + + rmt_serverctx_reset(ctx); + + RMT_Settings* rmt_settings = gf_rmt_get_settings(ctx->rmt); + + if (rmt_settings->cert && rmt_settings->pkey) { +#ifdef GPAC_HAS_SSL + if (!gf_file_exists(rmt_settings->cert)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT Certificate file %s not found\n", rmt_settings->cert)); + return GF_IO_ERR; + } + if (!gf_file_exists(rmt_settings->pkey)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT Private key file %s not found\n", rmt_settings->pkey)); + return GF_IO_ERR; + } + if (gf_ssl_init_lib()) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT Failed to initialize OpenSSL library\n")); + return GF_IO_ERR; + } + + Bool prev_noh2 = gf_opts_get_bool("core", "no-h2"); + if (!prev_noh2) + gf_opts_set_key("core", "no-h2", "1"); + + ctx->ssl_ctx = gf_ssl_server_context_new(rmt_settings->cert, rmt_settings->pkey); + + if (!prev_noh2) + gf_opts_set_key("core", "no-h2", "0"); + + if (!ctx->ssl_ctx) return GF_IO_ERR; +#else + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT TLS key/certificate set but GPAC compiled without TLS support\n")); + return GF_NOT_SUPPORTED; + +#endif + } + + ctx->sg = gf_sk_group_new(); + + ctx->server_sock = gf_sk_new(GF_SOCK_TYPE_TCP); + e = gf_sk_bind( ctx->server_sock, + rmt_settings->limit_connections_to_localhost ? "127.0.0.1" : "0.0.0.0", + rmt_settings->port, + NULL, 0, GF_SOCK_REUSE_PORT ); + + if (!e) e = gf_sk_listen(ctx->server_sock, 0); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT failed to start server on port %d: %s\n", rmt_settings->port, gf_error_to_string(e) )); + return e; + } + + gf_sk_group_register(ctx->sg, ctx->server_sock); + + gf_sk_server_mode(ctx->server_sock, GF_TRUE); + GF_LOG(GF_LOG_INFO, GF_LOG_RMTWS, ("RMT Server running on port %d\n", rmt_settings->port)); + + Bool prev_noh2 = gf_opts_get_bool("core", "no-h2"); + if (!prev_noh2) + gf_opts_set_key("core", "no-h2", "1"); + ctx->dm_sess = gf_dm_new(NULL); + if (!prev_noh2) + gf_opts_set_key("core", "no-h2", "0"); + ctx->active_clients = gf_list_new(); + + return e; + +} + +GF_EXPORT +void gf_rmt_client_set_on_data_cbk(RMT_ClientCtx* client, void* task, rmt_client_on_data_cbk cbk) { + if (client) { + client->on_data_cbk = cbk; + client->on_data_cbk_task = task; + } +} + +GF_Err rmt_server_handle_new_client(RMT_ServerCtx* ctx) { + GF_Err e = GF_OK; + + void *ssl_c = NULL; + + RMT_ClientCtx* new_client; + GF_SAFEALLOC(new_client, RMT_ClientCtx); + + new_client->ctx = ctx; + + e = gf_sk_accept(ctx->server_sock, &new_client->client_sock); + gf_sk_group_register(ctx->sg, new_client->client_sock); + + u32 port; + gf_sk_get_remote_address_port(new_client->client_sock, new_client->peer_address, &port); + sprintf(new_client->peer_address + strlen(new_client->peer_address), ":%d", port); //TDOO: size check + GF_LOG(GF_LOG_INFO, GF_LOG_RMTWS, ("RMT connected to remote peer %s\n", new_client->peer_address)); + + +#ifdef GPAC_HAS_SSL + if (ctx->ssl_ctx) { + ssl_c = gf_ssl_new(ctx->ssl_ctx, new_client->client_sock, &e); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT Failed to create TLS session from %s: %s\n", new_client->peer_address, gf_error_to_string(e) )); + rmt_clientctx_del(new_client); + return e; + } + } +#endif + + new_client->http_sess = gf_dm_sess_new_server(ctx->dm_sess, new_client->client_sock, ssl_c, rmt_on_http_session_data, new_client, GF_TRUE, &e); + + new_client->is_ws = GF_FALSE; + new_client->last_active_time = gf_sys_clock_high_res(); + new_client->last_ping_time = gf_sys_clock_high_res(); + + gf_list_add(ctx->active_clients, new_client); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("adding active client socket %p %s\n", new_client, new_client->peer_address)); + + return e; +} + +GF_Err rmt_client_send_payload(RMT_ClientCtx* client, const u8* payload, u64 size, Bool is_binary) { + + GF_Err e = GF_OK; + + GF_BitStream* respbs = gf_bs_new(NULL, size+10, GF_BITSTREAM_WRITE_DYN); + gf_bs_write_int(respbs, 1, 1); //FIN=1 + gf_bs_write_int(respbs, 0, 3); //RSV=0 + gf_bs_write_int(respbs, is_binary ? RMT_WEBSOCKET_BINARY : RMT_WEBSOCKET_TEXT, 4); //opcode=text + gf_bs_write_int(respbs, 0, 1); //masked=0 + + if (size < 126) + gf_bs_write_int(respbs, (s32)size, 7); + else if (size < 65536) { + gf_bs_write_int(respbs, 126, 7); + gf_bs_write_long_int(respbs, size, 16); + } else { + gf_bs_write_long_int(respbs, 127, 7); + gf_bs_write_long_int(respbs, size, 64); + } + + gf_bs_write_data(respbs, payload, (u32)size); + + u8* respbuf=NULL; + u32 respsize=0; + gf_bs_get_content(respbs, &respbuf, &respsize); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("ready to send respbuf of size %d: %.*s\n", respsize, respsize, respbuf)); + e = dm_sess_write(client->http_sess, respbuf, respsize); + + gf_bs_del(respbs); + gf_free(respbuf); + + return e; + +} + +GF_Err rmt_client_handle_ws_payload(RMT_ClientCtx* client, u8* payload, u64 size, Bool is_binary) { + + if (client->on_data_cbk && client->on_data_cbk_task) { + client->on_data_cbk(client->on_data_cbk_task, payload, size, is_binary); + } + + return GF_OK; + + // if (size) + // payload0 = 'A'; + + // return rmt_client_send_payload(client, payload, size, is_binary); + +} + +GF_Err rmt_client_handle_ws_frame(RMT_ClientCtx* client, GF_BitStream* bs) { + + GF_Err e = GF_OK; + + if (gf_bs_available(bs) < 2) return GF_IO_ERR; + + u32 FIN = gf_bs_read_int(bs, 1); //TODO: handle fragmented? + /*u32 RSV =*/ gf_bs_read_int(bs, 3); + u32 opcode = gf_bs_read_int(bs, 4); + u32 masked = gf_bs_read_int(bs, 1); + u64 payload_size = gf_bs_read_int(bs, 7); + if (payload_size == 126) { + if (gf_bs_available(bs) < 2) return GF_IO_ERR; + payload_size = gf_bs_read_int(bs, 16); + } + else if (payload_size == 127) { + if (gf_bs_available(bs) < 8) return GF_IO_ERR; + payload_size = gf_bs_read_long_int(bs, 64); + } + if (gf_bs_available(bs) < 4) return GF_IO_ERR; + char masking_key4 = {0}; + if (masked) { + gf_bs_read_data(bs, (u8*)&masking_key, 4); + } + + u8* extra_payload = NULL; + u32 extra_read = 0; + + if (payload_size + gf_bs_get_position(bs) > gf_bs_get_size(bs)) { + u64 extra_size = payload_size + gf_bs_get_position(bs) - gf_bs_get_size(bs) ; + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("buffer too small for payload_size %llu bs_pos %u bs_size %u => extra_size %llu\n", payload_size, gf_bs_get_position(bs), gf_bs_get_size(bs), extra_size)); + extra_payload = gf_malloc( sizeof(u8) * extra_size ); + + e = GF_OK; + while (!e && extra_read < extra_size) { + u32 new_read = 0; + e = gf_dm_read_data(client->http_sess, extra_payload + extra_read, (u32)(extra_size-extra_read), &new_read); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("extra gf_dm_read_data => %d e=%s\n",extra_read, gf_error_to_string(e))); + extra_read += new_read; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("extra gf_dm_read_data => %d e=%s\n",extra_read, gf_error_to_string(e))); + } + + u8* unmasked_payload = gf_malloc( payload_size * sizeof(u8) + 1); // add 1 to add null to get c string + int i=0; + for (i=0; i<payload_size && gf_bs_available(bs); i++) { + unmasked_payloadi = (u8) ( gf_bs_read_u8(bs) ^ masking_keyi%4 ); + } + + if (extra_payload) { + for (u32 j=0; j<extra_read; j++) { + unmasked_payloadi = (u8) ( extra_payloadj ^ masking_keyi%4 ); + i++; + } + } + + unmasked_payloadi = 0; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("parsed ws message: FIN %d \n opcode %d \n masked %d \n payload_size %d \n masking_key %.*s \n unmasked_payload \n", + FIN, + opcode, + masked, + payload_size, + 4, masking_key + )); + + switch(opcode) { + + case RMT_WEBSOCKET_CLOSE: + client->should_close = GF_TRUE; + break; + + case RMT_WEBSOCKET_TEXT: + case RMT_WEBSOCKET_BINARY: + + e = rmt_client_handle_ws_payload(client, unmasked_payload, payload_size, (opcode==RMT_WEBSOCKET_BINARY)); + + break; + + + case RMT_WEBSOCKET_PING:; + + GF_BitStream* respbs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE_DYN); + gf_bs_write_int(respbs, 1, 1); //FIN=1 + gf_bs_write_int(respbs, 0, 3); //RSV=0 + gf_bs_write_int(respbs, RMT_WEBSOCKET_PONG, 4); //opcode=pong + gf_bs_write_int(respbs, 0, 1); //masked=0 + gf_bs_write_int(respbs, (s32)payload_size, 7); + gf_bs_write_data(respbs, unmasked_payload, (u32)payload_size); + + u8* respbuf=NULL; + u32 respsize=0; + gf_bs_get_content(respbs, &respbuf, &respsize); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("ready to send PONG respbuf of size %d: %.*s\n", respsize, respsize, respbuf)); + e = dm_sess_write(client->http_sess, respbuf, respsize); + + gf_bs_del(respbs); + gf_free(respbuf); + + break; + + + case RMT_WEBSOCKET_CONTINUATION: // not supported yet + case RMT_WEBSOCKET_PONG: // last active timer already reset + default: + break; + } + + + gf_free(unmasked_payload); + if (extra_payload) + gf_free(extra_payload); + + return e; +} + +GF_Err rmt_client_handle_event(RMT_ClientCtx* client) { + GF_Err e = GF_OK; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("select ready for socket %p %s\n", client, client->peer_address)); + + if (!client->is_ws) { + e = gf_dm_sess_process(client->http_sess); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("RMT gf_dm_sess_process: %s\n", gf_error_to_string(e))); + } + else { + u8 buffer1024; //TODO: what size here?? do something like extra_payload?? + u32 read = 0; + e = gf_dm_read_data(client->http_sess, buffer, 1024, &read); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gf_dm_read_data => %d sHTTP %.*s\n", read, read, buffer)); + + client->last_active_time = gf_sys_clock_high_res(); + + GF_BitStream* bs = gf_bs_new(buffer, read, GF_BITSTREAM_READ); + + while (e==GF_OK && gf_bs_available(bs)) { + + e = rmt_client_handle_ws_frame(client, bs); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_handle_ws_frame() returned %s bs has avail %d \n", gf_errno_str(e), gf_bs_available(bs))); + + } + + + gf_bs_del(bs); + + + } + + + return e; +} + +GF_Err rmt_client_send_ping(RMT_ClientCtx* client) { + + GF_Err e = dm_sess_write(client->http_sess, RMT_WEBSOCKET_PING_MSG, 2); + return e; + +} + +GF_EXPORT +GF_Err gf_rmt_client_send_to_ws(RMT_ClientCtx* client, const char* msg, u64 size, Bool is_binary) { + + return rmt_client_send_payload(client, (const u8*) msg, size, is_binary); + +} + +Bool rmt_client_should_close(RMT_ClientCtx* client) { + + // check valid object + if (!client || !client->client_sock || !client->ctx) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("RMT weird dead session in rmt\n")); + return GF_TRUE; + } + RMT_Settings* rmt_settings = gf_rmt_get_settings(client->ctx->rmt); + + if (client->should_close) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("Disconnected socket %p %s because request done\n", client, client->peer_address)); + return GF_TRUE; + } + + // check disconnection + GF_Err e = gf_sk_probe(client->client_sock); + if (e==GF_IP_CONNECTION_CLOSED) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("Disconnected socket %p %s\n", client, client->peer_address)); + return GF_TRUE; + } + + // check timeout + if (rmt_settings->timeout_secs) { + u32 diff_sec = (u32) (gf_sys_clock_high_res() - client->last_active_time)/1000000; + if ( diff_sec > rmt_settings->timeout_secs ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("Disconnected socket %p %s for timeout \n", client, client->peer_address)); + return GF_TRUE; + } + } + + return GF_FALSE; + +} + +GF_Err rmt_server_wait_for_event(RMT_ServerCtx* ctx) { + + RMT_Settings* rmt_settings = gf_rmt_get_settings(ctx->rmt); + + GF_Err e = gf_sk_group_select(ctx->sg, 10, GF_SK_SELECT_BOTH); + + if (e==GF_OK) { + + // event on server socket = new client + if (gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock, GF_SK_SELECT_READ)) { + + e = rmt_server_handle_new_client(ctx); + if (e) + return e; + + } + + // event on one the active client + else { + + // check active sessions + u32 count = gf_list_count(ctx->active_clients); + for (u32 i=0; i<count; i++) { + RMT_ClientCtx* client = gf_list_get(ctx->active_clients, i); + + if (rmt_client_should_close(client)) { + + gf_list_rem(ctx->active_clients, i); + i--; + count--; + rmt_clientctx_del(client); + client = NULL; + continue; + } + + if (gf_sk_group_sock_is_set(client->ctx->sg, client->client_sock, GF_SK_SELECT_WRITE)) { + + // check if we should send ping + if (rmt_settings->ping_secs) { + + u32 diff_sec = (u32) (gf_sys_clock_high_res() - client->last_ping_time)/1000000; + + if ( diff_sec > rmt_settings->ping_secs ) { + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("Sending PING on client %p %s\n", client, client->peer_address)); + e = rmt_client_send_ping(client); + if (!e) { + client->last_ping_time = gf_sys_clock_high_res(); + } + continue; + } + } + + } + + // handle received event + if (gf_sk_group_sock_is_set(client->ctx->sg, client->client_sock, GF_SK_SELECT_READ)) { + + e = rmt_client_handle_event(client); + + } + } + + } + + } + + gf_sleep(rmt_settings->msSleepBetweenServerUpdates); + return e; + +} + +static u32 rmt_ws_thread_main(void* par) { + + RMT_WS* rmt = (RMT_WS*)par; + + RMT_ServerCtx* ctx; + GF_SAFEALLOC(ctx, RMT_ServerCtx); + ctx->rmt = rmt; + + GF_Err e; + + while (!rmt->should_stop) { + + // create socket if not exist + if (!ctx->server_sock) { + + e = rmt_create_server(ctx); + if (e) { + if (ctx) { + rmt_serverctx_reset(ctx); + gf_free(ctx); + ctx = NULL; + } + return e; + } + + continue; + } + + e = rmt_server_wait_for_event(ctx); + + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("RMT thread main request exit! should cleanup\n")); + rmt_serverctx_reset(ctx); + gf_free(ctx); + ctx = NULL; + return GF_OK; + +} + + + +RMT_Settings* gf_rmt_get_settings(RMT_WS* rmt) +{ + if (!rmt) return NULL; + + return &rmt->settings; +} + + +void rmt_ws_del(RMT_WS* rmt) { + + if (rmt && rmt->thread) { + + + rmt->should_stop = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_ws_del() calling stop...\n")); + + gf_th_stop(rmt->thread); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_ws_del() stop returned thread status is now %d\n", gf_th_status(rmt->thread))); + + gf_th_del(rmt->thread); + + rmt->thread = NULL; + + } + + gf_free(rmt); + rmt = NULL; + +} + +RMT_WS* rmt_ws_new() { + RMT_WS* rmt = NULL; + + GF_SAFEALLOC(rmt, RMT_WS); + if (!rmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT_WS unable init rmt thread\n")); + return NULL; + } + + rmt->settings.port = 6363; + rmt->settings.timeout_secs = 20; + rmt->settings.ping_secs = 7; + + rmt->settings.limit_connections_to_localhost = GF_FALSE; + rmt->settings.msSleepBetweenServerUpdates = 50; + + rmt->settings.on_new_client_cbk = NULL; + rmt->settings.on_new_client_cbk_task = NULL; + + rmt->settings.cert = NULL; + rmt->settings.pkey = NULL; + + return rmt; +} + +void rmt_ws_run(RMT_WS* rmt) { + + rmt->thread = gf_th_new("rmt_ws_main_th"); + rmt->should_stop = GF_FALSE; + + GF_Err e = gf_th_run(rmt->thread, rmt_ws_thread_main, rmt); + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RMTWS, ("RMT_WS unable to start websocket thread: %s\n", gf_error_to_string(e))); + rmt_ws_del(rmt); + rmt = NULL; + } + +} + +#else //GPAC_DISABLE_RMTWS + +void gf_rmt_set_on_new_client_cbk(RMT_WS* rmt, void *task, rmt_on_new_client_cbk cbk) { + +} +void* gf_rmt_get_on_new_client_task(RMT_WS* rmt) { + return NULL; +} + +const char* gf_rmt_get_peer_address(RMT_ClientCtx* client) { + return NULL; +} +GF_Err gf_rmt_client_send_to_ws(RMT_ClientCtx* client, const char* msg, u64 size, Bool is_binary) { + return GF_NOT_SUPPORTED; +} + +void gf_rmt_client_set_on_data_cbk(RMT_ClientCtx* client, void* task, rmt_client_on_data_cbk cbk) { + +} +void* gf_rmt_client_get_on_data_task(RMT_ClientCtx* client) { + return NULL; +} + +void gf_rmt_client_set_on_del_cbk(RMT_ClientCtx* client, void* task, rmt_client_on_del_cbk cbk) { + +} +void* gf_rmt_client_get_on_del_task(RMT_ClientCtx* client) { + return NULL; +} + +RMT_WS* gf_rmt_client_get_rmt(RMT_ClientCtx* client) { + return NULL; +} + +#endif
View file
gpac-2.4.0.tar.gz/src/utils/sha256.c -> gpac-26.02.0.tar.gz/src/utils/sha256.c
Changed
@@ -39,10 +39,10 @@ static u32 sha256_endian_read32(u8 *input) { u32 output = 0; - output |= (input0 << 24); - output |= (input1 << 16); - output |= (input2 << 8); - output |= (input3 << 0); + output |= (((u32)input0) << 24); + output |= (((u32)input1) << 16); + output |= (((u32)input2) << 8); + output |= (((u32)input3) << 0); return output; }
View file
gpac-26.02.0.tar.gz/src/utils/unittests
Added
+(directory)
View file
gpac-26.02.0.tar.gz/src/utils/unittests/ut_os_config_init.c
Added
@@ -0,0 +1,58 @@ +#include <gpac/main.h> +#include "tests.h" + +unittest(gf_sys_word_match) +{ + // Test exact match scenario + assert_true(gf_sys_word_match("abc", "abc")); + + // Test short orig, longer dst scenario + assert_true(gf_sys_word_match("abc", "abcd")); + + // Test short dst, longer orig scenario + assert_true(gf_sys_word_match("abcd", "abc")); + + // Test scenario with ':' in orig but not in dst + assert_false(gf_sys_word_match("a:b:c", "xyz")); + + // Test scenario with ':' in dst but not in orig + assert_false(gf_sys_word_match("abc", "x:y:z")); + + // Test strnistr match scenario + assert_true(gf_sys_word_match("abc", "xabcz")); + + // Test repeated characters scenario + assert_false(gf_sys_word_match("aabbc", "axayabaz")); + + // Test match*2 < olen scenario + assert_false(gf_sys_word_match("abc", "xyz")); + + // Test half characters in order scenario + assert_true(gf_sys_word_match("abcd", "aebfcd")); + + // Test null pointer + assert_true(gf_sys_word_match(NULL, NULL)); + assert_false(gf_sys_word_match("abc", NULL)); + assert_false(gf_sys_word_match(NULL, "abc")); + + // Test empty string + assert_true(gf_sys_word_match("", "")); + assert_false(gf_sys_word_match("abc", "")); + assert_false(gf_sys_word_match("", "abc")); + + // Test a very long string + const char *longString = "a very long string that exceeds the normal limit of the function, it may go further and further until it can break the behaviour of the function apparently not easy so lets write more and more"; + assert_true(gf_sys_word_match(longString, longString)); + assert_false(gf_sys_word_match("abc", longString)); + assert_false(gf_sys_word_match(longString, "abc")); + + // Test a non-ASCII buffer + const char *nonAsciiBuffer = "\x01\x02\x03\xFF\xFE\xFD"; + assert_false(gf_sys_word_match("abc", nonAsciiBuffer)); + assert_false(gf_sys_word_match(nonAsciiBuffer, "abc")); + assert_true(gf_sys_word_match(nonAsciiBuffer, nonAsciiBuffer)); + + // Test non-string: will crash because this exported function doesn't provide a str max len argument + //DISABLED: char str3 = {'a', 'b', 'c'}; + //DISABLED: gf_sys_word_match("abc", str); +}
View file
gpac-26.02.0.tar.gz/src/utils/unittests/ut_xml_parser.c
Added
@@ -0,0 +1,9 @@ +#include "tests.h" + +char *xml_translate_xml_string(char *str); +unittest(xml_translate_xml_string) +{ + char *str= xml_translate_xml_string("&"); + assert_equal_str(str, "&"); + gf_free(str); +}
View file
gpac-2.4.0.tar.gz/src/utils/url.c -> gpac-26.02.0.tar.gz/src/utils/url.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2025 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -157,19 +157,23 @@ } - +//set to 0 to disable max URL len - we don't use MAX_PATH as it can be small on some systems +#define MAX_URL_LEN 4096 static char *gf_url_concatenate_ex(const char *parentName, const char *pathName, Bool relative_to_parent) { u32 pathSepCount, i, prot_type; Bool had_sep_count = GF_FALSE; char *outPath, *name, *rad, *tmp2; - char tmpGF_MAX_PATH; + char *tmp = NULL; if (!pathName && !parentName) return NULL; if (!pathName) return gf_strdup(parentName); if (!parentName || !strlen(parentName)) return gf_strdup(pathName); if (!strncmp(pathName, "data:", 5)) return gf_strdup(pathName); + if (!strcmp(pathName, "stderr")) return gf_strdup(pathName); + if (!strcmp(pathName, "stdin")) return gf_strdup(pathName); + if (!strcmp(pathName, "stdout")) return gf_strdup(pathName); if (!strncmp(parentName, "gmem://", 7)) return NULL; if (!strncmp(parentName, "gfio://", 7)) { GF_Err e; @@ -179,10 +183,13 @@ return NULL; return gf_strdup( gf_fileio_url(gfio_new) ); } - if ((strlen(parentName) > GF_MAX_PATH) || (strlen(pathName) > GF_MAX_PATH)) { + +#if MAX_URL_LEN + if ((strlen(parentName) > MAX_URL_LEN) || (strlen(pathName) > MAX_URL_LEN)) { GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("URL too long for concatenation: \n%s\n", pathName)); return NULL; } +#endif while (!strncmp(parentName, "./.", 3) || !strncmp(parentName, ".\\.", 3)) { parentName += 2; @@ -190,6 +197,12 @@ while (!strncmp(pathName, "./.", 3) || !strncmp(pathName, ".\\.", 3)) { pathName += 2; } + //empty path + if (pathName0=='#') { + outPath = gf_strdup(parentName); + gf_dynstrcat(&outPath, pathName, NULL); + goto check_spaces; + } prot_type = URL_GetProtocolType(pathName); if (prot_type != GF_URL_TYPE_RELATIVE) { @@ -297,7 +310,9 @@ } if (!name) name = (char *) pathName; - strcpy(tmp, parentName); + gf_dynstrcat(&tmp, parentName, NULL); + if (!tmp) return NULL; + while (strchr(" \r\n\t", tmpstrlen(tmp)-1)) { tmpstrlen(tmp)-1 = 0; } @@ -329,7 +344,7 @@ if (pathSepCount) had_sep_count = GF_TRUE; - /*remove the last /*/ + /*remove the last */ for (i = (u32) strlen(tmp); i > 0; i--) { //break our path at each separator if ((tmpi-1 == GF_PATH_SEPARATOR) || (tmpi-1 == '/')) { @@ -344,13 +359,13 @@ if (!i) { tmpi = 0; while (pathSepCount) { - strcat(tmp, "../"); + gf_dynstrcat(&tmp, "../", NULL); pathSepCount--; } } //path is relative to current dir else if (!relative_to_parent && (pathName0=='.') && ((pathName1=='/') || (pathName1=='\\') ) ) { - strcat(tmp, "/"); + gf_dynstrcat(&tmp, "/", NULL); } //parent is relative to current dir else if (!had_sep_count && (pathName0=='.') && (tmp0=='.') && ((tmp1=='/') || (tmp1=='\\') ) ) { @@ -380,11 +395,13 @@ len -= sep_len; nb_path_sep--; } - strcpy(tmp, ""); + if (tmp) gf_free(tmp); + tmp=NULL; + gf_dynstrcat(&tmp, "", NULL); while (nb_path_sep--) - strcat(tmp, "../"); + gf_dynstrcat(&tmp, "../", NULL); } else { - strcat(tmp, "/"); + gf_dynstrcat(&tmp, "/", NULL); } i = (u32) strlen(tmp); @@ -411,6 +428,7 @@ } i++; } + if (tmp) gf_free(tmp); return outPath; } GF_EXPORT @@ -459,7 +477,8 @@ for (i=0; i<len; i++) { u8 c = pathi; if (strchr(pce_special, c) != NULL) { - if ((i+2<len) && ((strchr(pce_encoded, pathi+1) == NULL) || (strchr(pce_encoded, pathi+2) == NULL))) { + if (c==' ') count+=2; + else if ((i+2<len) && ((strchr(pce_encoded, pathi+1) == NULL) || (strchr(pce_encoded, pathi+2) == NULL))) { count+=2; } } else if (c>>7) { @@ -476,7 +495,9 @@ u8 c = pathi; if (strchr(pce_special, c) != NULL) { - if ((i+2<len) && ((strchr(pce_encoded, pathi+1) == NULL) || (strchr(pce_encoded, pathi+2) == NULL))) { + if (c==' ') + do_enc = GF_TRUE; + else if ((i+2<len) && ((strchr(pce_encoded, pathi+1) == NULL) || (strchr(pce_encoded, pathi+2) == NULL))) { do_enc = GF_TRUE; } } else if (c>>7) { @@ -513,25 +534,27 @@ } count++; } - if (!count) return gf_strdup(path); + if (count==len) return gf_strdup(path); outpath = (char*)gf_malloc(sizeof(char) * (count + 1)); + u32 d_idx=0; for (i=0; i<len; i++) { u8 c = pathi; if (c=='%') { u32 res; char szChar3; szChar0 = pathi+1; - szChar1 = pathi+1; + szChar1 = pathi+2; szChar2 = 0; sscanf(szChar, "%02X", &res); i += 2; - outpathi = (char) res; + outpathd_idx = (char) res; } else { - outpathi = c; + outpathd_idx = c; } + d_idx++; } - outpathcount = 0; + outpathd_idx = 0; return outpath; }
View file
gpac-2.4.0.tar.gz/src/utils/utf.c -> gpac-26.02.0.tar.gz/src/utils/utf.c
Changed
@@ -103,6 +103,8 @@ #define UNI_SUR_HIGH_END (UTF32)0xDBFF #define UNI_SUR_LOW_START (UTF32)0xDC00 #define UNI_SUR_LOW_END (UTF32)0xDFFF +#undef false +#undef true #define false 0 #define true 1 @@ -457,6 +459,7 @@ ConversionResult res = ConvertUTF16toUTF8(sourceStart, sourceEnd, &targetStart, targetEnd, flags); if (res != conversionOK) return GF_UTF8_FAIL; + if (targetStart >= targetEnd) return GF_UTF8_FAIL; *targetStart = 0; *srcp=NULL; return (u32) strlen(dest); @@ -476,6 +479,7 @@ ConversionFlags flags = strictConversion; ConversionResult res = ConvertUTF8toUTF16(sourceStart, sourceEnd, &targetStart, targetEnd, flags); if (res != conversionOK) return GF_UTF8_FAIL; + if (targetStart >= targetEnd) return GF_UTF8_FAIL; *targetStart = 0; *srcp=NULL; return gf_utf8_wcslen(dest); @@ -738,8 +742,8 @@ size_t source_len; wchar_t* result; if (str == 0) return 0; - source_len = strlen(str); - result = gf_calloc(source_len + 1, sizeof(wchar_t)); + source_len = 1+strlen(str); + result = gf_calloc(source_len, sizeof(wchar_t)); if (!result) return 0; if (gf_utf8_mbstowcs(result, source_len, &str) == GF_UTF8_FAIL) { @@ -755,8 +759,8 @@ size_t source_len; char* result; if (str == 0) return 0; - source_len = wcslen(str); - result = gf_calloc(source_len + 1, UTF8_MAX_BYTES_PER_CHAR); + source_len = 1+wcslen(str); + result = gf_calloc(source_len, UTF8_MAX_BYTES_PER_CHAR); if (!result) return 0; if (gf_utf8_wcstombs(result, source_len * UTF8_MAX_BYTES_PER_CHAR, &str) == GF_UTF8_FAIL) { @@ -766,4 +770,3 @@ return result; } #endif -
View file
gpac-26.02.0.tar.gz/src/utils/xml_bin_custom.c
Added
@@ -0,0 +1,832 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Romain Bouqueau, Jean Le Feuvre + * Copyright (c) Motion Spell, Telecom ParisTech 2024 + * All rights reserved + * + * This file is part of GPAC / common tools sub-project + * + * GPAC 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, or (at your option) + * any later version. + * + * GPAC 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 library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/xml.h> +#include <gpac/internal/isomedia_dev.h> //GF_EventMessageBox + +#define XML_SCAN_INT(_fmt, _value) \ + {\ + if (strstr(att->value, "0x")) { u32 __i; sscanf(att->value+2, "%x", &__i); _value = __i; }\ + else if (strstr(att->value, "0X")) { u32 __i; sscanf(att->value+2, "%X", &__i); _value = __i; }\ + else sscanf(att->value, _fmt, &_value); \ + }\ + + +// SCTE-35 XML parsing ---------------------------------------------- + +static void xml_scte35_parse_splice_time(GF_XMLNode *root, GF_BitStream *bs) +{ + u32 i = 0, j = 0; + GF_XMLNode *node = NULL; + GF_XMLAttribute *att = NULL; + + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + if (!strcmp(att->name, "ptsTime")) { + u64 ptsTime = 0; + if (sscanf(att->value, LLU, &ptsTime) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for ptsTime=\"%s\"\n", att->value)); + + Bool time_specified_flag = GF_TRUE; + gf_bs_write_int(bs, time_specified_flag, 1); + if (time_specified_flag == GF_TRUE) { + gf_bs_write_int(bs, 0xFF/*reserved*/, 6); + gf_bs_write_long_int(bs, ptsTime, 33); + } else { + gf_bs_write_int(bs, 0xFF/*reserved*/, 7); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in SpliceTime\n", att->name)); + } + } + + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown node \"%s\" in SpliceTime\n", node->name)); + } +} + +static void xml_scte35_parse_break_duration(GF_XMLNode *root, GF_BitStream *bs) +{ + u32 i = 0, j = 0; + GF_XMLNode *node = NULL; + GF_XMLAttribute *att = NULL; + + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + if (!strcmp(att->name, "duration")) { + u64 duration = 0; + if (sscanf(att->value, LLU, &duration) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for duration=\"%s\"\n", att->value)); + + gf_bs_write_int(bs, 0/*auto_return*/, 1); + gf_bs_write_int(bs, 0/*reserved*/, 6); + gf_bs_write_long_int(bs, duration, 33); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in SpliceTime\n", att->name)); + } + } + + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown node \"%s\" in SpliceTime\n", node->name)); + } +} + +static void xml_scte35_parse_time_signal(GF_XMLNode *root, GF_BitStream *bs) +{ + u32 i = 0, j = 0; + GF_XMLNode *node = NULL; + GF_XMLAttribute *att = NULL; + + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in TimeSignal\n", att->name)); + } + + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + if (!strcmp(node->name, "SpliceTime")) { + xml_scte35_parse_splice_time(node, bs); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown node \"%s\" in TimeSignal\n", node->name)); + } + } +} + +static void xml_scte35_parse_segmentation_descriptor(GF_XMLNode *root, GF_BitStream *bs) +{ + u64 descriptor_loop_length_pos = gf_bs_get_position(bs); + gf_bs_write_u16(bs, 0/*descriptor_loop_length*/); //placeholder + //for (u16 i=0; i<descriptor_loop_length; i++) { + gf_bs_write_u8(bs, 0x02/*splice_descriptor_tag*/); + gf_bs_write_u8(bs, 0/*descriptor_length*/); //placeholder + gf_bs_write_u32(bs, GF_4CC('C', 'U', 'E', 'I')); + + u32 i = 0, j = 0; + GF_XMLNode *node = NULL; + GF_XMLAttribute *att = NULL; + int segmentationEventId=0; + Bool segmentationEventCancelIndicator = GF_FALSE, webDeliveryAllowedFlag = GF_FALSE, noRegionalBlackoutFlag = GF_FALSE, archiveAllowedFlag = GF_FALSE; + Bool segmentationEventIdComplianceIndicator = GF_FALSE; + Bool programSegmentationFlag = GF_FALSE, segmentationDurationFlag = GF_FALSE, deliveryNotRestrictedFlag = GF_FALSE; + u8 deviceRestrictions = 0; + u8 segmentationTypeId = 0, segmentNum = 0, segmentsExpected = 0; + u8 subSegmentNum = 0, subSegmentsExpected = 0; + u64 segmentationDuration = 0; + + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + if (!strcmp(att->name, "segmentationEventId")) { + segmentationEventId = atoi(att->value); + } else if (!strcmp(att->name, "segmentationEventCancelIndicator")) { + segmentationEventCancelIndicator = atoi(att->value); + } else if (!strcmp(att->name, "segmentationEventIdComplianceIndicator")) { + segmentationEventIdComplianceIndicator = atoi(att->value); + } else if (!strcmp(att->name, "webDeliveryAllowedFlag")) { + webDeliveryAllowedFlag = atoi(att->value); + } else if (!strcmp(att->name, "noRegionalBlackoutFlag")) { + noRegionalBlackoutFlag = atoi(att->value); + } else if (!strcmp(att->name, "archiveAllowedFlag")) { + archiveAllowedFlag = atoi(att->value); + } else if (!strcmp(att->name, "deviceRestrictions")) { + deviceRestrictions = atoi(att->value); + } else if (!strcmp(att->name, "segmentationTypeId")) { + segmentationTypeId = atoi(att->value); + } else if (!strcmp(att->name, "segmentNum")) { + segmentNum = atoi(att->value); + } else if (!strcmp(att->name, "segmentsExpected")) { + segmentsExpected = atoi(att->value); + } else if (!strcmp(att->name, "programSegmentationFlag")) { + programSegmentationFlag = atoi(att->value); + } else if (!strcmp(att->name, "segmentationDurationFlag")) { + segmentationDurationFlag = atoi(att->value); + } else if (!strcmp(att->name, "deliveryNotRestrictedFlag")) { + deliveryNotRestrictedFlag = atoi(att->value); + } else if (!strcmp(att->name, "segmentationDuration")) { + segmentationDuration = atoi(att->value); + } else if (!strcmp(att->name, "subSegmentNum")) { + subSegmentNum = atoi(att->value); + } else if (!strcmp(att->name, "subSegmentsExpected")) { + subSegmentsExpected = atoi(att->value); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in SegmentationDescriptor\n", att->name)); + } + } + + gf_bs_write_u32(bs, segmentationEventId); + + gf_bs_write_int(bs, segmentationEventCancelIndicator, 1); + gf_bs_write_int(bs, segmentationEventIdComplianceIndicator, 1); + gf_bs_write_int(bs, 0x3F/*reserved*/, 6); + if (segmentationEventCancelIndicator == 0) { + gf_bs_write_int(bs, programSegmentationFlag, 1); + gf_bs_write_int(bs, segmentationDurationFlag, 1); + gf_bs_write_int(bs, deliveryNotRestrictedFlag, 1); + if (deliveryNotRestrictedFlag == 0) { + gf_bs_write_int(bs, webDeliveryAllowedFlag, 1); + gf_bs_write_int(bs, noRegionalBlackoutFlag, 1); + gf_bs_write_int(bs, archiveAllowedFlag, 1); + gf_bs_write_int(bs, deviceRestrictions, 2); + } else { + gf_bs_write_int(bs, 0x1F/*reserved*/, 5); + } + if (programSegmentationFlag == 0) { + assert(0); +#if 0 //not implemented + gf_bs_write_u8(bs, component_count); + for (u8 i=0; i<component_count; i++) { + gf_bs_write_u8(bs, componentTag); + gf_bs_write_int(bs, 7, 0x7F/*reserved*/); + gf_bs_write_u8(bs, componentTag); + gf_bs_write_long_int(bs, ptsOffset, 33); + } +#endif + } + if (segmentationDurationFlag == 1) { + gf_bs_write_long_int(bs, segmentationDuration, 40); + } + + gf_bs_write_u8(bs, 0/*u8 segmentation_upid_type*/); + gf_bs_write_u8(bs, 0/*u8 segmentation_upid_length*/); + + gf_bs_write_u8(bs, segmentationTypeId); + gf_bs_write_u8(bs, segmentNum); + gf_bs_write_u8(bs, segmentsExpected); + if (segmentationTypeId == 0x34 || segmentationTypeId == 0x30 || segmentationTypeId == 0x32 + || segmentationTypeId == 0x36 || segmentationTypeId == 0x38 || segmentationTypeId == 0x3A + || segmentationTypeId == 0x44 || segmentationTypeId == 0x46) { + gf_bs_write_u8(bs, subSegmentNum); + gf_bs_write_u8(bs, subSegmentsExpected); + } + } + + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + if (!strcmp(node->name, "SegmentationUpid")) { + GF_XMLAttribute *attx = NULL; + while ((attx = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + if (!strcmp(attx->name, "segmentationUpidType")) { + gf_bs_write_u8(bs, segmentationTypeId); + gf_bs_write_u8(bs, segmentNum); + gf_bs_write_u8(bs, segmentsExpected); +#if 0 //not implemented: seen at the end of TimeSignal + <SegmentationDescriptor segmentationEventId="1207959603" segmentationEventCancelIndicator="0" segmentationEventIdComplianceIndicator="1" + webDeliveryAllowedFlag="1" noRegionalBlackoutFlag="1" archiveAllowedFlag="1" deviceRestrictions="3" + segmentationTypeId="53" segmentNum="0" segmentsExpected="0"> + <SegmentationUpid segmentationUpidType="8">000000003484D6D5</SegmentationUpid> + </SegmentationDescriptor> +#endif + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in SegmentationUpid\n", attx->name)); + } + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown node \"%s\" in TimeSignal\n", node->name)); + } + } + + // write descriptor_loop_length and descriptor_length + { + u64 pos = gf_bs_get_position(bs); + u32 descriptor_length = (u32) (pos - descriptor_loop_length_pos - 2); + gf_bs_seek(bs, descriptor_loop_length_pos + 1); + gf_bs_write_u8(bs, descriptor_length); //descriptor_loop_length + gf_bs_seek(bs, descriptor_loop_length_pos + 3); + gf_bs_write_u8(bs, descriptor_length-2); //descriptor_length + gf_bs_seek(bs, pos); + } +} + +static void xml_scte35_parse_splice_insert(GF_XMLNode *root, GF_BitStream *bs) +{ + u32 i = 0, j = 0; + GF_XMLNode *node = NULL; + GF_XMLAttribute *att = NULL; + + int spliceEventId = 0, spliceEventCancelIndicator = 0, outOfNetworkIndicator = 0, segmentationEventId = 0, programSpliceFlag = 0, durationFlag = 0, spliceImmediateFlag = 0; + + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + if (!strcmp(att->name, "spliceEventId")) { + spliceEventId = atoi(att->value); + } else if (!strcmp(att->name, "spliceEventCancelIndicator")) { + spliceEventCancelIndicator = atoi(att->value); + } else if (!strcmp(att->name, "outOfNetworkIndicator")) { + outOfNetworkIndicator = atoi(att->value); + } else if (!strcmp(att->name, "segmentationEventId")) { + segmentationEventId = atoi(att->value); + } else if (!strcmp(att->name, "programSpliceFlag")) { + programSpliceFlag = atoi(att->value); + } else if (!strcmp(att->name, "durationFlag")) { + durationFlag = atoi(att->value); + } else if (!strcmp(att->name, "spliceImmediateFlag")) { + spliceImmediateFlag = atoi(att->value); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in SpliceInsert\n", att->name)); + } + } + + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + if (!strcmp(node->name, "SpliceTime")) { + xml_scte35_parse_splice_time(node, bs); + } else if (!strcmp(node->name, "BreakDuration")) { + xml_scte35_parse_break_duration(node, bs); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown node \"%s\" in SpliceInsert\n", node->name)); + } + } + +#if 0 //not implemented + u64 splice_time = 0; + gf_bs_write_u32(bs, spliceEventId); + gf_bs_write_int(bs, 1, spliceEventCancelIndicator); + /*reserved = */gf_bs_write_int(bs, 7, 0x7F); + if (spliceEventCancelIndicator == 0) { + gf_bs_write_int(bs, 1, outOfNetworkIndicator); + gf_bs_write_int(bs, 1, programSpliceFlag); + gf_bs_write_int(bs, 1, durationFlag); + gf_bs_write_int(bs, 1, spliceImmediateFlag); + /*reserved = */gf_bs_write_int(bs, 4, 0xF); + + if ((programSpliceFlag == 1) && (spliceImmediateFlag == 0)) { + splice_time = scte35dec_parse_splice_time(bs); + *pts = splice_time + pts_adjustment; + } + + if (programSpliceFlag == 0) { + u32 i; + u32 component_count = gf_bs_read_u8(bs); + for (i=0; i<component_count; i++) { + /*u8 component_tag = */gf_bs_read_u8(bs); + if (spliceImmediateFlag == 0) { + splice_time = scte35dec_parse_splice_time(bs); + *pts = splice_time + pts_adjustment; + } + } + } + if (durationFlag == 1) { + //break_duration() + /*Bool auto_return = */gf_bs_write_int(bs, 1, 0); + /*reserved = */gf_bs_write_int(bs, 6, 0x3F); + gf_bs_write_long_int(bs, 33, dur); + } + } +#else + (void)spliceEventId; + (void)spliceEventCancelIndicator; + (void)outOfNetworkIndicator; + (void)segmentationEventId; + (void)programSpliceFlag; + (void)durationFlag; + (void)spliceImmediateFlag; +#endif +} + +static void xml_scte35_parse_splice_info(GF_XMLNode *root, GF_BitStream *bs) +{ + u32 i = 0, j = 0; + GF_XMLNode *node = NULL; + GF_XMLAttribute *att = NULL; + char xmlns256 = "http://www.scte.org/schemas/35"; + u32 sap_type = 0; + u32 protocol_version = 0; + u64 pts_adjustment = 0; + u32 tier = 0; + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { + if (!strcmp(att->name, "xmlns")) { + snprintf(xmlns, 255, "%s", att->value); + } else if (!strcmp(att->name, "sapType")) { + XML_SCAN_INT("%u", sap_type); + } else if (!strcmp(att->name, "protocolVersion")) { + XML_SCAN_INT("%u", protocol_version); + } else if (!strcmp(att->name, "ptsAdjustment")) { + XML_SCAN_INT(LLU, pts_adjustment); + } else if (!strcmp(att->name, "tier")) { + XML_SCAN_INT("%u", tier); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in SpliceInfoSection\n", att->name)); + } + } + + gf_bs_write_u8(bs, 0xFC); //table_id + gf_bs_write_int(bs, 0/*section_syntax_indicator*/, 1); + gf_bs_write_int(bs, 0/*private_indicator*/, 1); + gf_bs_write_int(bs, sap_type, 2); + u64 section_length_pos = gf_bs_get_position(bs); + gf_bs_write_int(bs, 0, 12); //placeholder + + gf_bs_write_u8(bs, protocol_version); + gf_bs_write_int(bs, 0/*encrypted_packet*/, 1); + gf_bs_write_int(bs, 0/*encryption_algorithm*/, 6); + gf_bs_write_long_int(bs, pts_adjustment, 33); + gf_bs_write_u8(bs, 0/*cw_index*/); + gf_bs_write_int(bs, tier, 12); + u64 splice_command_length_pos = gf_bs_get_position(bs); + gf_bs_write_int(bs, 0, 12); //placeholder + +#define WRITE_CMD_LEN() { \ + u64 pos = gf_bs_get_position(bs); \ + u32 splice_command_length = (u32) (pos - splice_command_length_pos - 3); \ + gf_bs_seek(bs, splice_command_length_pos + 1); \ + gf_bs_write_u8(bs, splice_command_length); \ + gf_bs_seek(bs, pos); \ + } + + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + if (!strcmp(node->name, "TimeSignal")) { + gf_bs_write_u8(bs, 0x06); + xml_scte35_parse_time_signal(node, bs); + WRITE_CMD_LEN(); + continue; + } else if (!strcmp(node->name, "SpliceInsert")) { + gf_bs_write_u8(bs, 0x05); + xml_scte35_parse_splice_insert(node, bs); + WRITE_CMD_LEN(); + break; + } else if (!strcmp(node->name, "SegmentationDescriptor")) { + xml_scte35_parse_segmentation_descriptor(node, bs); + break; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown node \"%s\" in SpliceInfoSection\n", node->name)); + } + } + + // post write section length + { + u64 pos = gf_bs_get_position(bs); + u32 section_length = (u32) (pos + 4 /*CRC32*/ - section_length_pos - 2); + gf_bs_seek(bs, section_length_pos); + gf_bs_write_u16(bs, (sap_type << 12) | section_length); + gf_bs_seek(bs, pos); + } + + // write CRC32 + { + u8 *data = NULL; + u32 size = 0; + GF_BitStream *bs_tmp = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_transfer(bs_tmp, bs, GF_TRUE); + gf_bs_get_content(bs_tmp, &data, &size); + gf_bs_seek(bs, gf_bs_get_size(bs)); + gf_bs_write_u32(bs, gf_crc_32(data+(section_length_pos-1), size - (u32) (section_length_pos-1))); + gf_bs_del(bs_tmp); + gf_free(data); + } + +#undef WRITE_CMD_LEN +} + +// raw SCTE-35 payload +void xml_scte35_parse(GF_XMLNode *root, GF_BitStream *bs) +{ + u32 i=0; + GF_XMLNode *node = NULL; + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + if (node->type) continue; + if (!strcmp(node->name, "SpliceInfoSection")) { + xml_scte35_parse_splice_info(node, bs); + continue; + } + } +} + +// SCTE-35 encapsulated in an EMEB box +void xml_emeb_parse(GF_XMLNode *root, GF_BitStream *bs) +{ + GF_EventMessageBox *emeb = (GF_EventMessageBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EMEB); + gf_isom_box_size((GF_Box*)emeb); + if (gf_isom_box_write((GF_Box*)emeb, bs) != GF_OK) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML EventMessageEmptyBox serialization failed\n")); + gf_isom_box_del((GF_Box*)emeb); +} + +// SCTE-35 encapsulated in an EMIB box +void xml_emib_parse(GF_XMLNode *root, GF_BitStream *bs) +{ + int i = 0; + GF_XMLAttribute *att = NULL; + GF_EventMessageBox *emib = (GF_EventMessageBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EMIB); + + while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &i))) { + if (!stricmp(att->name, "version")) { + if (sscanf(att->value, "%hu", &emib->version) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for version=\"%s\"\n", att->value)); + } else if (!stricmp(att->name, "flags")) { + if (sscanf(att->value, "%u", &emib->flags) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for flags=\"%s\"\n", att->value)); + } else if (!stricmp(att->name, "scheme_id_uri")) { + emib->scheme_id_uri = gf_strdup(att->value); + } else if (!stricmp(att->name, "value")) { + emib->value = gf_strdup(att->value); + } else if (!stricmp(att->name, "presentation_time_delta")) { + if (sscanf(att->value, LLD, &emib->presentation_time_delta) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for presentation_time_delta=\"%s\"\n", att->value)); + } else if (!stricmp(att->name, "event_duration")) { + if (sscanf(att->value, "%u", &emib->event_duration) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for event_duration=\"%s\"\n", att->value)); + } else if (!stricmp(att->name, "event_id")) { + if (sscanf(att->value, "%u", &emib->event_id) != 1) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Invalid value for event_id=\"%s\"\n", att->value)); + } else if (!stricmp(att->name, "message_data")) { + u8 *ptr = att->value; + if (!strnicmp(ptr, "0x", 2)) ptr +=2; + u32 len = (u32)strlen(ptr)/2; + emib->message_data = gf_malloc(len); + emib->message_data_size = len; + for (u32 i=0; i<len; ++i, ptr+=2) { + int val=0; + sscanf(ptr, "%02x", &val); + emib->message_datai = (u8)val; + } + } else if (!stricmp(att->name, "Size")) { + } else if (!stricmp(att->name, "Type")) { + } else if (!stricmp(att->name, "Specification")) { + } else if (!stricmp(att->name, "Container")) { + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Unknown attribute \"%s\" in EventMessageInstanceBox parsing\n", att->name)); + } + } + + u64 start = gf_bs_get_position(bs); + gf_isom_box_size((GF_Box*)emib); + if (gf_isom_box_write((GF_Box*)emib, bs) != GF_OK) + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML EventMessageInstanceBox serialization failed\n")); + + if (!emib->message_data) { + // append emib->message_data as it is the last field + u64 before = gf_bs_get_position(bs); + xml_scte35_parse(root, bs); + u64 end = gf_bs_get_position(bs); + + gf_bs_seek(bs, start); + emib->message_data = (u8*)(uintptr_t)before; //dumb non-null pointer + emib->message_data_size = (u32) (end - before); + gf_isom_box_size((GF_Box*)emib); + gf_isom_full_box_write((GF_Box*)emib, bs); //rewrite size + gf_bs_seek(bs, end); + + emib->message_data = NULL; + emib->message_data_size = 0; + } + + gf_isom_box_del((GF_Box*)emib); +} + + +// generic bit parsing + +#include <gpac/base_coding.h> +#include <gpac/network.h> // gf_url_concatenate + +GF_EXPORT +GF_Err gf_xml_parse_bit_sequence_bs(GF_XMLNode *bsroot, const char *parent_url, const char *base_media_file, GF_BitStream *bs_orig) +{ + u32 i, j; + GF_Err e = GF_OK; + GF_XMLNode *node; + GF_XMLAttribute *att; + GF_BitStream *bs = bs_orig; + u32 enc_base64 = 0; + + i=0; + while ((node = (GF_XMLNode *) gf_list_enum(bsroot->content, &i))) { + u32 nb_bits = 0; + u32 size = 0; + u64 offset = 0; + s64 value = 0; + Bool use_file = GF_FALSE; + bin128 word128; + Float val_float = 0; + Double val_double = 0; + Bool use_word128 = GF_FALSE; + Bool use_text = GF_FALSE; + Bool base64_prefix_bits = 0; + Bool big_endian = GF_TRUE; + Bool has_float = GF_FALSE; + Bool has_double = GF_FALSE; + const char *szFile = NULL; + const char *szString = NULL; + const char *szBase64 = NULL; + const char *szData = NULL; + if (node->type) continue; + + if (!stricmp(node->name, "SCTE35")) { + xml_scte35_parse(node, bs); + continue; + } else if (!stricmp(node->name, "EventMessageEmptyBox")) { + xml_emeb_parse(node, bs); + continue; + } else if (!stricmp(node->name, "EventMessageInstanceBox")) { + xml_emib_parse(node, bs); + continue; + } else if (stricmp(node->name, "BS") ) { + e = gf_xml_parse_bit_sequence_bs(node, parent_url, base_media_file, bs); + if (e) goto exit; + continue; + } + + j=0; + while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!stricmp(att->name, "bits")) { + XML_SCAN_INT("%d", nb_bits); + } else if (!stricmp(att->name, "value")) { + XML_SCAN_INT(LLD, value); + } else if (!stricmp(att->name, "float")) { + sscanf(att->value, "%f", &val_float); + has_float = GF_TRUE; + } else if (!stricmp(att->name, "double")) { + sscanf(att->value, "%lf", &val_double); + has_double = GF_TRUE; + } else if (!stricmp(att->name, "mediaOffset") || !stricmp(att->name, "dataOffset")) { + XML_SCAN_INT(LLU, offset); + use_file = GF_TRUE; + } else if (!stricmp(att->name, "dataLength")) { + XML_SCAN_INT("%u", size); + use_file = GF_TRUE; + } else if (!stricmp(att->name, "mediaFile") || !stricmp(att->name, "dataFile")) { + szFile = att->value; + use_file = GF_TRUE; + } else if (!stricmp(att->name, "text") || !stricmp(att->name, "string")) { + szString = att->value; + } else if (!stricmp(att->name, "fcc")) { + value = GF_4CC(att->value0, att->value1, att->value2, att->value3); + nb_bits = 32; + } else if (!stricmp(att->name, "ID128")) { + e = gf_bin128_parse(att->value, word128); + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Cannot parse ID128\n")); + goto exit; + } + use_word128 = GF_TRUE; + } else if (!stricmp(att->name, "textmode")) { + if (!strcmp(att->value, "yes")) use_text = GF_TRUE; + } else if (!stricmp(att->name, "data64")) { + szBase64 = att->value; + } else if (!stricmp(att->name, "data")) { + szData = att->value; + if (!strnicmp(szData, "0x", 2)) szData += 2; + } else if (!stricmp(att->name, "endian") && !stricmp(att->value, "little")) { + big_endian = GF_FALSE; + } else if (!stricmp(att->name, "base64")) { + if (!stricmp(att->value, "yes") || !stricmp(att->value, "true") ) { + if (!enc_base64) enc_base64 = 1; + } else if (!stricmp(att->value, "start")) { + if (!enc_base64) enc_base64 = 2; + } else if (!stricmp(att->value, "end")) { + if (enc_base64==2) enc_base64 = 3; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Invalid base64 attribute \"%s\", expecting yes/no, start or end\n", att->value)); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + } else if (!stricmp(att->name, "base64Prefix")) { + base64_prefix_bits = atoi(att->value); + } else if (!stricmp(att->name, "id")) { + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Unknown attribute \"%s\", ignoring\n", att->name)); + } + } + + if (enc_base64 && (enc_base64<3)) { + if (bs == bs_orig) { + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!bs) { + e = GF_OUT_OF_MEM; + goto exit; + } + } + } + + if (use_file && !szFile) + szFile = base_media_file; + + if (szString) { + u32 len = (u32) strlen(szString); + if (nb_bits) + gf_bs_write_int(bs, len, nb_bits); + + gf_bs_write_data(bs, szString, len); + } else if (szBase64) { + u32 len = (u32) strlen(szBase64); + char *data = (char *) gf_malloc(sizeof(char)*len); + u32 ret; + if (!data) { + e = GF_OUT_OF_MEM; + goto exit; + } + + ret = (u32) gf_base64_decode((char *)szBase64, len, data, len); + if ((s32) ret >=0) { + gf_bs_write_int(bs, ret, nb_bits); + gf_bs_write_data(bs, data, ret); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Error decoding base64 \"%s\"\n", att->value)); + gf_free(data); + e = GF_BAD_PARAM; + goto exit; + } + gf_free(data); + } else if (szData) { + u32 len = (u32) strlen(szData); + char *data = (char *) gf_malloc(sizeof(char)*len/2); + if (!data) { + e = GF_OUT_OF_MEM; + goto exit; + } + + for (j=0; j<len; j+=2) { + u32 v; + char szV5; + sprintf(szV, "%c%c", szDataj, szDataj+1); + sscanf(szV, "%x", &v); + dataj/2 = v; + } + gf_bs_write_int(bs, len/2, nb_bits); + gf_bs_write_data(bs, data, len/2); + gf_free(data); + } else if (has_float) { + gf_bs_write_float(bs, val_float); + } else if (has_double) { + gf_bs_write_double(bs, val_double); + } else if (nb_bits) { + if (!big_endian) { + if (nb_bits == 16) + gf_bs_write_u16_le(bs, (u32)value); + else if (nb_bits == 32) + gf_bs_write_u32_le(bs, (u32)value); + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Little-endian values can only be 16 or 32-bit\n")); + e = GF_BAD_PARAM; + goto exit; + } + } + else { + if (nb_bits<33) gf_bs_write_int(bs, (s32) value, nb_bits); + else gf_bs_write_long_int(bs, value, nb_bits); + } + } else if (szFile) { + u32 read, remain; + char block1024; + FILE *_tmp = NULL; + if (parent_url) { + char *f_url = gf_url_concatenate(parent_url, szFile); + _tmp = gf_fopen(f_url, use_text ? "rt" : "rb"); + gf_free(f_url); + } else { + _tmp = gf_fopen(szFile, use_text ? "rt" : "rb"); + } + + if (!_tmp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Error opening file \"%s\"\n", szFile)); + e = GF_URL_ERROR; + goto exit; + } + + if (!size) { + size = (u32) gf_fsize(_tmp); + //if offset only copy from offset until end + if ((u64) size > offset) + size -= (u32) offset; + } + remain = size; + gf_fseek(_tmp, offset, SEEK_SET); + while (remain) { + u32 bsize = remain; + if (bsize>1024) bsize=1024; + read = (u32) gf_fread(block, bsize, _tmp); + if ((s32) read < 0) { + gf_fclose(_tmp); + e = GF_IO_ERR; + goto exit; + } + + gf_bs_write_data(bs, block, read); + remain -= bsize; + } + gf_fclose(_tmp); + } else if (use_word128) { + gf_bs_write_data(bs, (char *)word128, 16); + } + + if ((enc_base64==1) || (enc_base64==3)) { + u8 *bs_data; + u32 bs_data_size; + assert (bs != bs_orig); + gf_bs_get_content(bs, &bs_data, &bs_data_size); + gf_bs_del(bs); + enc_base64 = 0; + bs = bs_orig; + if (bs_data) { + u8 *bs_data_out; + u32 res = 2*bs_data_size + 3; + bs_data_out = gf_malloc(sizeof(char) * res); + if (!bs_data_out) { + e = GF_OUT_OF_MEM; + goto exit; + } + res = gf_base64_encode(bs_data, bs_data_size, bs_data_out, res); + bs_data_outres = 0; + if (base64_prefix_bits) { + if (base64_prefix_bits % 8) { + gf_bs_write_int(bs, res, base64_prefix_bits); + } else { + u32 nb_bytes = base64_prefix_bits/8; + if (!big_endian && (nb_bytes==8)) gf_bs_write_u64_le(bs, res); + else if (!big_endian && (nb_bytes==4)) gf_bs_write_u32_le(bs, res); + else if (!big_endian && (nb_bytes==2)) gf_bs_write_u16_le(bs, res); + else + gf_bs_write_int(bs, res, base64_prefix_bits); + } + } + gf_bs_write_data(bs, bs_data_out, res); + gf_free(bs_data); + gf_free(bs_data_out); + } + } + } + +exit: + if (bs != bs_orig) { + if (!e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML base64 encoding context not closed\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + } + gf_bs_del(bs); + } + return e; +} + + +GF_EXPORT +GF_Err gf_xml_parse_bit_sequence(GF_XMLNode *bsroot, const char *parent_url, u8 **data, u32 *data_size) +{ + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!bs) return GF_OUT_OF_MEM; + + gf_xml_parse_bit_sequence_bs(bsroot, parent_url, NULL, bs); + + gf_bs_align(bs); + gf_bs_get_content(bs, data, data_size); + gf_bs_del(bs); + return GF_OK; +}
View file
gpac-2.4.0.tar.gz/src/utils/xml_parser.c -> gpac-26.02.0.tar.gz/src/utils/xml_parser.c
Changed
@@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2023 + * Copyright (c) Telecom ParisTech 2005-2024 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -25,7 +25,6 @@ #include <gpac/xml.h> #include <gpac/utf.h> -#include <gpac/network.h> #ifndef GPAC_DISABLE_ZLIB /*since 0.2.2, we use zlib for xmt/x3d reading to handle gz files*/ @@ -41,10 +40,12 @@ #define XML_INPUT_SIZE 4096 +static u32 XML_MAX_CONTENT_SIZE = 0; + static GF_Err gf_xml_sax_parse_intern(GF_SAXParser *parser, char *current); -static char *xml_translate_xml_string(char *str) +GF_STATIC char *xml_translate_xml_string(char *str) { char *value; u32 size, i, j; @@ -61,7 +62,7 @@ if (stri+1=='#') { char szChar20, *end; u16 wchar2; - u32 val, _len; + u32 val=0, _len; const unsigned short *srcp; strncpy(szChar, str+i, 10); szChar10 = 0; @@ -783,8 +784,10 @@ } if (parser->current_pos+i==parser->line_size) { - if ((parser->line_size>=2*XML_INPUT_SIZE) && !parser->init_state) + if ((parser->line_size >= XML_MAX_CONTENT_SIZE) && !parser->init_state) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML Content size larger than max allowed %u, try increasing limit using `-xml-max-csize`\n", XML_MAX_CONTENT_SIZE)); parser->sax_state = SAX_STATE_SYNTAX_ERROR; + } goto exit; } @@ -1036,7 +1039,9 @@ char *name; entityEnd = strstr(current, ";"); if (!entityEnd) return xml_sax_append_string(parser, current); + entityStart = strrchr(parser->buffer, '&'); + if (!entityStart) return xml_sax_append_string(parser, current); entityEnd0 = 0; len = (u32) strlen(entityStart) + (u32) strlen(current) + 1; @@ -1055,7 +1060,10 @@ parser->in_entity = GF_FALSE; continue; } - gf_assert(ent); + if (!ent) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("SAX Entity not found\n")); + return GF_CORRUPTED_DATA; + } /*truncate input buffer*/ parser->line_size -= (u32) strlen(entityStart); entityStart0 = 0; @@ -1194,7 +1202,7 @@ static GF_Err xml_sax_read_file(GF_SAXParser *parser) { GF_Err e = GF_EOS; - unsigned char szLineXML_INPUT_SIZE+2; + unsigned char szLineXML_INPUT_SIZE+2={0}; #ifdef NO_GZIP if (!parser->f_in) return GF_BAD_PARAM; @@ -1276,11 +1284,11 @@ parser->current_pos = 0; e = gf_xml_sax_init(parser, szLine); - if (!e) { - e = gf_xml_sax_parse(parser, xml_mem_address+4); - if (parser->on_progress) parser->on_progress(parser->sax_cbck, parser->file_pos, parser->file_size); - } - gf_blob_release(fileName); + if (!e) { + e = gf_xml_sax_parse(parser, xml_mem_address+4); + if (parser->on_progress) parser->on_progress(parser->sax_cbck, parser->file_pos, parser->file_size); + } + gf_blob_release(fileName); parser->elt_start_pos = parser->elt_end_pos = 0; parser->elt_name_start = parser->elt_name_end = 0; @@ -1354,6 +1362,9 @@ parser->sax_node_end = on_node_end; parser->sax_text_content = on_text_content; parser->sax_cbck = cbck; + if (!XML_MAX_CONTENT_SIZE) { + XML_MAX_CONTENT_SIZE = gf_opts_get_int("core", "xml-max-csize"); + } return parser; } @@ -1424,7 +1435,7 @@ #endif Bool from_buffer; Bool dobreak=GF_FALSE; - char szLine1XML_INPUT_SIZE+2, szLine2XML_INPUT_SIZE+2, *szLine, *cur_line, *sep, *start, first_c, *result; + char *szLine1, *szLine2, *szLine, *cur_line, *sep, *start, first_c, *result; #define CPYCAT_ALLOC(__str, __is_copy) _len = (u32) strlen(__str);\ @@ -1445,6 +1456,13 @@ result = NULL; + szLine1 = gf_malloc(sizeof(char)*(XML_INPUT_SIZE+2)); + if (!szLine1) return NULL; + szLine2 = gf_malloc(sizeof(char)*(XML_INPUT_SIZE+2)); + if (!szLine2) { + gf_free(szLine1); + return NULL; + } szLine10 = szLine20 = 0; pos=0; if (!from_buffer) { @@ -1458,6 +1476,11 @@ if (att_len<2*XML_INPUT_SIZE) att_len = 2*XML_INPUT_SIZE; alloc_size = att_len; szLine = (char *) gf_malloc(sizeof(char)*alloc_size); + if (!szLine) { + gf_free(szLine1); + gf_free(szLine2); + return NULL; + } strcpy(szLine, parser->buffer + parser->att_name_start); cur_line = szLine; att_len = (u32) strlen(att_value); @@ -1582,6 +1605,8 @@ } exit: gf_free(szLine); + gf_free(szLine1); + gf_free(szLine2); if (!from_buffer) { #ifdef NO_GZIP @@ -1610,6 +1635,7 @@ static void on_peek_node_start(void *cbk, const char *name, const char *ns, const GF_XMLAttribute *attributes, u32 nb_attributes) { struct _peek_type *pt = (struct _peek_type*)cbk; + if (pt->res) gf_free(pt->res); pt->res = gf_strdup(name); pt->parser->suspended = GF_TRUE; } @@ -1649,7 +1675,7 @@ //usually only one :) GF_List *root_nodes; u32 depth; - + Bool keep_valid; void (*OnProgress)(void *cbck, u64 done, u64 tot); void *cbk; }; @@ -1728,7 +1754,7 @@ if (!p_att) break; if (!strcmp(p_att->name, in_att->name)) { dup=GF_TRUE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("SAX Duplicated attribute %s on node %s, ignoring\n", in_att->name, name)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("SAX Duplicated attribute \"%s\" on node \"%s\", ignoring\n", in_att->name, name)); break; } } @@ -1771,6 +1797,7 @@ gf_list_add(node->content, last); } + last->valid_content = par->keep_valid; } static void on_dom_text_content(void *cbk, const char *content, Bool is_cdata) @@ -1801,6 +1828,7 @@ if (!dom) return NULL; dom->root_nodes = gf_list_new(); + dom->keep_valid = 0; return dom; } @@ -1889,6 +1917,14 @@ return e<0 ? e : GF_OK; } +GF_EXPORT +GF_Err gf_xml_dom_enable_passthrough(GF_DOMParser *dom) +{ + if (!dom) return GF_BAD_PARAM; + dom->keep_valid = 1; + return GF_OK; +} + #if 0 //unused GF_XMLNode *gf_xml_dom_create_root(GF_DOMParser *parser, const char* name) { GF_XMLNode * root; @@ -1933,19 +1969,53 @@ static void gf_xml_dom_node_serialize(GF_XMLNode *node, Bool content_only, Bool no_escape, char **str, u32 *alloc_size, u32 *size) { - u32 i, count, vlen; + u32 i, count, vlen, tot_s; char *name; -#define SET_STRING(v) \ - vlen = (u32) strlen(v); \ - if (vlen + (*size) >= (*alloc_size)) { \ - (*alloc_size) += 1024; \ - if (vlen + (*size) >= (*alloc_size)) (*alloc_size) = vlen + (*size) + 1;\ - (*str) = gf_realloc((*str), (*alloc_size)); \ - (*str)(*size) = 0; \ - } \ - strcat((*str), v); \ - *size += vlen; \ +#define SET_STRING(v) \ + vlen = (u32)strlen(v); \ + tot_s = vlen + (*size); \ + if (tot_s >= (*alloc_size)) \ + { \ + (*alloc_size) = MAX(tot_s, 2 * (*alloc_size)) + 1; \ + (*str) = gf_realloc((*str), (*alloc_size)); \ + } \ + memcpy((*str) + (*size), v, vlen + 1); \ + *size += vlen; + +#define SET_STRING_ESCAPED(uv) \ + { \ + u32 tlen; \ + char szChar2; \ + szChar1 = 0; \ + tlen = (u32)strlen(uv); \ + for (int vi = 0; vi < tlen; vi++) \ + { \ + switch (uvvi) \ + { \ + case '&': \ + SET_STRING("&"); \ + break; \ + case '<': \ + SET_STRING("<"); \ + break; \ + case '>': \ + SET_STRING(">"); \ + break; \ + case '\'': \ + SET_STRING("'"); \ + break; \ + case '\"': \ + SET_STRING("""); \ + break; \ + \ + default: \ + szChar0 = uvvi; \ + SET_STRING(szChar); \ + break; \ + } \ + } \ + } switch (node->type) { case GF_XML_CDATA_TYPE: @@ -1961,34 +2031,7 @@ if (no_escape) { SET_STRING(name); } else { - u32 tlen; - char szChar2; - szChar1 = 0; - tlen = (u32) strlen(name); - for (i= 0; i<tlen; i++) { - switch (namei) { - case '&': - SET_STRING("&"); - break; - case '<': - SET_STRING("<"); - break; - case '>': - SET_STRING(">"); - break; - case '\'': - SET_STRING("'"); - break; - case '\"': - SET_STRING("""); - break; - - default: - szChar0 = namei; - SET_STRING(szChar); - break; - } - } + SET_STRING_ESCAPED(name); } return; } @@ -2008,7 +2051,7 @@ GF_XMLAttribute *att = (GF_XMLAttribute*)gf_list_get(node->attributes, i); SET_STRING(att->name); SET_STRING("=\""); - SET_STRING(att->value); + SET_STRING_ESCAPED(att->value); SET_STRING("\" "); } @@ -2022,7 +2065,7 @@ count = gf_list_count(node->content); for (i=0; i<count; i++) { GF_XMLNode *child = (GF_XMLNode*)gf_list_get(node->content, i); - gf_xml_dom_node_serialize(child, GF_FALSE, GF_FALSE, str, alloc_size, size); + gf_xml_dom_node_serialize(child, GF_FALSE, node->valid_content, str, alloc_size, size); } if (!content_only) { SET_STRING("</"); @@ -2053,7 +2096,8 @@ gf_dynstrcat(&str, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", NULL); if (!str) return NULL; - alloc_size = size = (u32) strlen(str) + 1; + alloc_size = size = (u32) strlen(str); + alloc_size = size + 1; gf_xml_dom_node_serialize(node, content_only, no_escape, &str, &alloc_size, &size); return str; } @@ -2160,299 +2204,7 @@ return node; } -#include <gpac/base_coding.h> - -#define XML_SCAN_INT(_fmt, _value) \ - {\ - if (strstr(att->value, "0x")) { u32 __i; sscanf(att->value+2, "%x", &__i); _value = __i; }\ - else if (strstr(att->value, "0X")) { u32 __i; sscanf(att->value+2, "%X", &__i); _value = __i; }\ - else sscanf(att->value, _fmt, &_value); \ - }\ - - -GF_Err gf_xml_parse_bit_sequence_bs(GF_XMLNode *bsroot, const char *parent_url, const char *base_media_file, GF_BitStream *bs_orig) -{ - u32 i, j; - GF_Err e = GF_OK; - GF_XMLNode *node; - GF_XMLAttribute *att; - GF_BitStream *bs = bs_orig; - u32 enc_base64 = 0; - - i=0; - while ((node = (GF_XMLNode *) gf_list_enum(bsroot->content, &i))) { - u32 nb_bits = 0; - u32 size = 0; - u64 offset = 0; - s64 value = 0; - Bool use_file = GF_FALSE; - bin128 word128; - Float val_float = 0; - Double val_double = 0; - Bool use_word128 = GF_FALSE; - Bool use_text = GF_FALSE; - Bool base64_prefix_bits = 0; - Bool big_endian = GF_TRUE; - Bool has_float = GF_FALSE; - Bool has_double = GF_FALSE; - const char *szFile = NULL; - const char *szString = NULL; - const char *szBase64 = NULL; - const char *szData = NULL; - if (node->type) continue; - - if (stricmp(node->name, "BS") ) { - e = gf_xml_parse_bit_sequence_bs(node, parent_url, base_media_file, bs); - if (e) goto exit; - continue; - } - - j=0; - while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { - if (!stricmp(att->name, "bits")) { - XML_SCAN_INT("%d", nb_bits); - } else if (!stricmp(att->name, "value")) { - XML_SCAN_INT(LLD, value); - } else if (!stricmp(att->name, "float")) { - sscanf(att->value, "%f", &val_float); - has_float = GF_TRUE; - } else if (!stricmp(att->name, "double")) { - sscanf(att->value, "%lf", &val_double); - has_double = GF_TRUE; - } else if (!stricmp(att->name, "mediaOffset") || !stricmp(att->name, "dataOffset")) { - XML_SCAN_INT(LLU, offset); - use_file = GF_TRUE; - } else if (!stricmp(att->name, "dataLength")) { - XML_SCAN_INT("%u", size); - use_file = GF_TRUE; - } else if (!stricmp(att->name, "mediaFile") || !stricmp(att->name, "dataFile")) { - szFile = att->value; - use_file = GF_TRUE; - } else if (!stricmp(att->name, "text") || !stricmp(att->name, "string")) { - szString = att->value; - } else if (!stricmp(att->name, "fcc")) { - value = GF_4CC(att->value0, att->value1, att->value2, att->value3); - nb_bits = 32; - } else if (!stricmp(att->name, "ID128")) { - e = gf_bin128_parse(att->value, word128); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Cannot parse ID128\n")); - goto exit; - } - use_word128 = GF_TRUE; - } else if (!stricmp(att->name, "textmode")) { - if (!strcmp(att->value, "yes")) use_text = GF_TRUE; - } else if (!stricmp(att->name, "data64")) { - szBase64 = att->value; - } else if (!stricmp(att->name, "data")) { - szData = att->value; - if (!strnicmp(szData, "0x", 2)) szData += 2; - } else if (!stricmp(att->name, "endian") && !stricmp(att->value, "little")) { - big_endian = GF_FALSE; - } else if (!stricmp(att->name, "base64")) { - if (!stricmp(att->value, "yes") || !stricmp(att->value, "true") ) { - if (!enc_base64) enc_base64 = 1; - } else if (!stricmp(att->value, "start")) { - if (!enc_base64) enc_base64 = 2; - } else if (!stricmp(att->value, "end")) { - if (enc_base64==2) enc_base64 = 3; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Invalid base64 attribute %s, expecting yes/no, start or end\n", att->value)); - e = GF_NON_COMPLIANT_BITSTREAM; - goto exit; - } - } else if (!stricmp(att->name, "base64Prefix")) { - base64_prefix_bits = atoi(att->value); - } else if (!stricmp(att->name, "id")) { - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Unkown attribute %s, ignoring\n", att->name)); - } - } - - if (enc_base64 && (enc_base64<3)) { - if (bs == bs_orig) { - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - if (!bs) { - e = GF_OUT_OF_MEM; - goto exit; - } - } - } - - if (use_file && !szFile) - szFile = base_media_file; - - if (szString) { - u32 len = (u32) strlen(szString); - if (nb_bits) - gf_bs_write_int(bs, len, nb_bits); - - gf_bs_write_data(bs, szString, len); - } else if (szBase64) { - u32 len = (u32) strlen(szBase64); - char *data = (char *) gf_malloc(sizeof(char)*len); - u32 ret; - if (!data) { - e = GF_OUT_OF_MEM; - goto exit; - } - - ret = (u32) gf_base64_decode((char *)szBase64, len, data, len); - if ((s32) ret >=0) { - gf_bs_write_int(bs, ret, nb_bits); - gf_bs_write_data(bs, data, ret); - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Error decoding base64 %s\n", att->value)); - gf_free(data); - e = GF_BAD_PARAM; - goto exit; - } - gf_free(data); - } else if (szData) { - u32 len = (u32) strlen(szData); - char *data = (char *) gf_malloc(sizeof(char)*len/2); - if (!data) { - e = GF_OUT_OF_MEM; - goto exit; - } - - for (j=0; j<len; j+=2) { - u32 v; - char szV5; - sprintf(szV, "%c%c", szDataj, szDataj+1); - sscanf(szV, "%x", &v); - dataj/2 = v; - } - gf_bs_write_int(bs, len/2, nb_bits); - gf_bs_write_data(bs, data, len/2); - gf_free(data); - } else if (has_float) { - gf_bs_write_float(bs, val_float); - } else if (has_double) { - gf_bs_write_double(bs, val_double); - } else if (nb_bits) { - if (!big_endian) { - if (nb_bits == 16) - gf_bs_write_u16_le(bs, (u32)value); - else if (nb_bits == 32) - gf_bs_write_u32_le(bs, (u32)value); - else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Little-endian values can only be 16 or 32-bit\n")); - e = GF_BAD_PARAM; - goto exit; - } - } - else { - if (nb_bits<33) gf_bs_write_int(bs, (s32) value, nb_bits); - else gf_bs_write_long_int(bs, value, nb_bits); - } - } else if (szFile) { - u32 read, remain; - char block1024; - FILE *_tmp = NULL; - if (parent_url) { - char *f_url = gf_url_concatenate(parent_url, szFile); - _tmp = gf_fopen(f_url, use_text ? "rt" : "rb"); - gf_free(f_url); - } else { - _tmp = gf_fopen(szFile, use_text ? "rt" : "rb"); - } - - if (!_tmp) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML Error opening file %s\n", szFile)); - e = GF_URL_ERROR; - goto exit; - } - - if (!size) { - size = (u32) gf_fsize(_tmp); - //if offset only copy from offset until end - if ((u64) size > offset) - size -= (u32) offset; - } - remain = size; - gf_fseek(_tmp, offset, SEEK_SET); - while (remain) { - u32 bsize = remain; - if (bsize>1024) bsize=1024; - read = (u32) gf_fread(block, bsize, _tmp); - if ((s32) read < 0) { - gf_fclose(_tmp); - e = GF_IO_ERR; - goto exit; - } - - gf_bs_write_data(bs, block, read); - remain -= bsize; - } - gf_fclose(_tmp); - } else if (use_word128) { - gf_bs_write_data(bs, (char *)word128, 16); - } - - if ((enc_base64==1) || (enc_base64==3)) { - u8 *bs_data; - u32 bs_data_size; - assert (bs != bs_orig); - gf_bs_get_content(bs, &bs_data, &bs_data_size); - gf_bs_del(bs); - enc_base64 = 0; - bs = bs_orig; - if (bs_data) { - u8 *bs_data_out; - u32 res = 2*bs_data_size + 3; - bs_data_out = gf_malloc(sizeof(char) * res); - if (!bs_data_out) { - e = GF_OUT_OF_MEM; - goto exit; - } - res = gf_base64_encode(bs_data, bs_data_size, bs_data_out, res); - bs_data_outres = 0; - if (base64_prefix_bits) { - if (base64_prefix_bits % 8) { - gf_bs_write_int(bs, res, base64_prefix_bits); - } else { - u32 nb_bytes = base64_prefix_bits/8; - if (!big_endian && (nb_bytes==8)) gf_bs_write_u64_le(bs, res); - else if (!big_endian && (nb_bytes==4)) gf_bs_write_u32_le(bs, res); - else if (!big_endian && (nb_bytes==2)) gf_bs_write_u16_le(bs, res); - else - gf_bs_write_int(bs, res, base64_prefix_bits); - } - } - gf_bs_write_data(bs, bs_data_out, res); - gf_free(bs_data); - gf_free(bs_data_out); - } - } - } - -exit: - if (bs != bs_orig) { - if (!e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("XML/NHML base64 encoding context not closed\n")); - e = GF_NON_COMPLIANT_BITSTREAM; - } - gf_bs_del(bs); - } - return e; -} - -GF_EXPORT -GF_Err gf_xml_parse_bit_sequence(GF_XMLNode *bsroot, const char *parent_url, u8 **data, u32 *data_size) -{ - GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - if (!bs) return GF_OUT_OF_MEM; - - gf_xml_parse_bit_sequence_bs(bsroot, parent_url, NULL, bs); - - gf_bs_align(bs); - gf_bs_get_content(bs, data, data_size); - gf_bs_del(bs); - return GF_OK; -} - -GF_Err gf_xml_get_element_check_namespace(const GF_XMLNode *n, const char *expected_node_name, const char *expected_ns_prefix) { +GF_Err gf_xml_dom_node_check_namespace(const GF_XMLNode *n, const char *expected_node_name, const char *expected_ns_prefix) { u32 i; GF_XMLAttribute *att; @@ -2527,3 +2279,46 @@ gf_fprintf(file, "%s", after); } } + + +GF_XMLNode *gf_xml_dom_node_clone(GF_XMLNode *node) +{ + GF_XMLNode *clone, *child; + GF_XMLAttribute *att; + u32 i; + GF_SAFEALLOC(clone, GF_XMLNode); + if (!clone) return NULL; + + clone->type = node->type; + clone->valid_content = node->valid_content; + clone->orig_pos = node->orig_pos; + if (node->name) + clone->name = gf_strdup(node->name); + if (node->ns) + clone->ns = gf_strdup(node->ns); + + clone->attributes = gf_list_new(); + i = 0; + while ((att = gf_list_enum(node->attributes, &i))) { + GF_XMLAttribute *att_clone; + GF_SAFEALLOC(att_clone, GF_XMLAttribute); + if (!att_clone) { + gf_xml_dom_node_del(clone); + return NULL; + } + att_clone->name = gf_strdup(att->name); + att_clone->value = gf_strdup(att->value); + gf_list_add(clone->attributes, att_clone); + } + clone->content = gf_list_new(); + i=0; + while ((child = gf_list_enum(node->content, &i))) { + GF_XMLNode *child_clone = gf_xml_dom_node_clone(child); + if (!child_clone) { + gf_xml_dom_node_del(clone); + return NULL; + } + gf_list_add(clone->content, child_clone); + } + return clone; +}
View file
gpac-2.4.0.tar.gz/src/utils/zutil.h -> gpac-26.02.0.tar.gz/src/utils/zutil.h
Changed
@@ -132,7 +132,7 @@ # include <unix.h> /* for fdopen */ # else # ifndef fdopen -# define fdopen(fd,mode) NULL /* No fdopen() */ +//# define fdopen(fd,mode) NULL /* No fdopen() */ # endif # endif #endif
View file
gpac-2.4.0.tar.gz/static.mak -> gpac-26.02.0.tar.gz/static.mak
Changed
@@ -52,8 +52,9 @@ ifeq ($(STATIC_BUILD),yes) +LINKFLAGS+=$(ngtcp2_ldflags) $(nghttp3_ldflags) LINKFLAGS+=$(zlib_ldflags) $(opensvc_ldflags) $(ssl_ldflags) $(jpeg_ldflags) $(openjpeg_ldflags) $(png_ldflags) $(mad_ldflags) $(a52_ldflags) $(xvid_ldflags) $(faad_ldflags) -LINKFLAGS+=$(ffmpeg_ldflags) $(ogg_ldflags) $(vorbis_ldflags) $(theora_ldflags) $(nghttp2_ldflags) $(vtb_ldflags) $(caption_ldflags) $(mpeghdec_ldflags) +LINKFLAGS+=$(ffmpeg_ldflags) $(ogg_ldflags) $(vorbis_ldflags) $(theora_ldflags) $(nghttp2_ldflags) $(vtb_ldflags) $(caption_ldflags) $(mpeghdec_ldflags) $(curl_ldflags) endif @@ -64,7 +65,8 @@ CFLAGS+= -DGPAC_STATIC_MODULES -ifeq ($(CONFIG_ALSA),yes) +ifeq ($(CONFIG_ALSA),no) +else OBJS+=../modules/alsa/alsa.o CFLAGS+=-DGPAC_HAS_ALSA EXTRALIBS+= -lasound @@ -127,13 +129,15 @@ endif -ifeq ($(CONFIG_JACK),yes) +ifeq ($(CONFIG_JACK),no) +else OBJS+= ../modules/jack/jack.o CFLAGS+=-DGPAC_HAS_JACK EXTRALIBS+=-ljack endif -ifeq ($(CONFIG_PULSEAUDIO),yes) +ifeq ($(CONFIG_PULSEAUDIO),no) +else OBJS+= ../modules/pulseaudio/pulseaudio.o CFLAGS+=-DGPAC_HAS_PULSEAUDIO EXTRALIBS+=-lpulse -lpulse-simple
View file
gpac-26.02.0.tar.gz/unittests
Added
+(directory)
View file
gpac-26.02.0.tar.gz/unittests/README.md
Added
@@ -0,0 +1,69 @@ +# Unit tests in GPAC + +## Build and run + +Unit tests can be activate with ```configure --unittests```. + +Unit tests are executed each time a build occurs with ```make```. Execution of the tests happens as soon as possible and it will stop on errors. + +## Notes + +Some unit tests require extra visibility of symbols. That's why a second build is required. This is done automatically. + +Alternately you can run the tests with ```make unit_tests``` or look into ```unittests/launch.sh``` to set environment variable. Available options are ```--list``` (or ```-l```) and ```--only``` with a test number. + +The unit tests pre-processing could happen along with other reformatting and checks in a ```precommit``` command as part of developer's best practices. + +These tests are intented to complement existing tests from the testsuite. + +```gpac.c```also contains some unit tests of its own. They are unrelated to the runtime discussed in this document. + +## Writing a unit test + +Include the unit test framework: +``` +#include "tests.h" +``` + +### Static function testing + +If the function is static, replace ```static``` with ```GF_STATIC```. If the function is not static and not exported while you need it, add ```GF_NOT_EXPORTED```. Then add a definition in your unit test file: +``` +char *xml_translate_xml_string(char *str); + +``` + +Then write test functions: +``` +unittest(xml_translate_xml_string) +{ + assert_equal_str(xml_translate_xml_string("&"), "&"); +} +``` + +### Testing a full filter + +You can include the filter source code file in your test file. This has the advantage of allowing to reuse private contexts in the unit tests: +``` +#include "tests.h" +#include "../dec_scte35.c" // include the source code + +... + +unittest(scte35dec_segmentation_end) +{ + SCTE35DecCtx ctx = {0}; + assert_equal(scte35dec_initialize_internal(&ctx), GF_OK, "%d"); + + ctx.segdur = (GF_Fraction){1, 1}; + + u64 pts = 0; + SEND_VIDEO(1); + SEND_EVENT(SCTE35_DUR); + + scte35dec_flush(&ctx); + scte35dec_finalize_internal(&ctx); + + ctx.pck_send(NULL); // trigger final checks +} +```
View file
gpac-26.02.0.tar.gz/unittests/build.sh
Added
@@ -0,0 +1,29 @@ +#!/bin/bash +readonly scriptDir="$( cd "$( dirname "${BASH_SOURCE0}" )" >/dev/null 2>&1 && pwd )" + +echo '#include <stdio.h> +#include <stdlib.h> // EXIT_FAILURE + +int register_test(const char *name, int (*test_function)(void)); +int run_tests(int argc, char *argv); + +#define unittest(ut) \ + int test_##ut(); \ + if(register_test(#ut, test_##ut)) { \ + fprintf(stderr, "registration failed for unit test: %s\n", #ut); \ + return EXIT_FAILURE; \ + } + +int main(int argc, char *argv) +{' + + +calls=$(cd "$scriptDir/.." && find . -path "*/unittests/*.c" -not -path "./unittests/*" | grep -v bin | xargs grep unittest | cut -d ":" -f 2) +for call in $calls; do + echo " $call;" +done + +echo ' + return run_tests(argc, argv); +} +'
View file
gpac-26.02.0.tar.gz/unittests/launch.sh
Added
@@ -0,0 +1,12 @@ +#!/bin/sh + +#windows +export PATH=$PATH:unittests/build/bin/gcc + +#linux +export LD_LIBRARY_PATH=unittests/build/bin/gcc${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH:-} + +#macos +export DYLD_LIBRARY_PATH=unittests/build/bin/gcc${DYLD_LIBRARY_PATH:+:}${DYLD_LIBRARY_PATH:-} + +unittests/build/bin/gcc/unittests
View file
gpac-26.02.0.tar.gz/unittests/tests.c
Added
@@ -0,0 +1,107 @@ +#include "tests.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MAX_NUM_TESTS 1024 + +int tests_passed = 0; +int tests_failed = 0; + +int checks_passed = 0; +int checks_failed = 0; + +static struct TestCase { + const char *name; + void (*test_function)(void); +} testsMAX_NUM_TESTS; + +static unsigned int test_count = 0; // global number of registered tests + +// Function to register a test case +int register_test(const char *name, void (*test_function)(void)) +{ + if(test_count < sizeof(tests) / sizeof(tests0)) { + teststest_count.name = name; + teststest_count.test_function = test_function; + test_count++; + } else { + fprintf(stderr, "Too many tests registered.\n"); + return 1; + } + return 0; +} + +int run_tests(int argc, char *argv) +{ +#ifdef GPAC_MEMORY_TRACKING + gf_sys_init(GF_MemTrackerSimple, NULL); +#endif + + unsigned selected_tests = -1; // all + for(int i = 1; i < argc; ++i) { + if(!strcmp(argvi, "--list") || !strcmp(argvi, "-l")) { + printf("List of tests:\n"); + for(unsigned i = 0; i < test_count; i++) + printf("\ttest%u: \"%s\"... \n", i, testsi.name); + + return EXIT_SUCCESS; + } else if(!strcmp(argvi, "--only")) { + if(i + 1 >= argc) { + fprintf(stderr, "Missing argument for --only\n"); + return EXIT_FAILURE; + } + selected_tests = atoi(argv++i); + if(selected_tests != (unsigned)-1 && selected_tests >= test_count) { + fprintf(stderr, "Test idx %u not found. Exiting.\n", selected_tests); + return EXIT_FAILURE; + } + printf("Selected test: %s... \n", testsselected_tests.name); + } + } + + int ret = EXIT_SUCCESS; + for(unsigned i = 0; i < test_count; i++) { + printf("Test %04d: %s... ", i, testsi.name); + fflush(stdout); + + if(selected_tests != (unsigned)-1 && selected_tests != i) { + printf("Skipping\n"); + continue; + } + + int prev_checks_failed = checks_failed; + testsi.test_function(); + if(checks_failed > prev_checks_failed) { + printf("Failed\n"); + ret = EXIT_FAILURE; + tests_failed++; + if (checks_failed & 0x8000000) { + checks_failed &= ~0x8000000; + printf("Failure is fatal. Aborting test execution.\n"); + break; + } + } else { + printf("Success\n"); + tests_passed++; + } + } + + printf("\n"); + printf("Tests passed: %d\n", tests_passed); + printf("Tests failed: %d\n", tests_failed); + printf("Checks passed: %d\n", checks_passed); + printf("Checks failed: %d\n", checks_failed); + + gf_sys_close(); + +#ifdef GPAC_MEMORY_TRACKING + if (gf_memory_size() || gf_file_handles_count() ) { + gf_log_set_tool_level(GF_LOG_MEMORY, GF_LOG_INFO); + gf_memory_print(); + } +#endif + + return ret; +}
View file
gpac-2.4.0.tar.gz/version.bat -> gpac-26.02.0.tar.gz/version.bat
Changed
@@ -4,9 +4,21 @@ IF NOT EXIST .\.git\NUL GOTO not_git -for /f "delims=" %%a in ('git describe --tags --long') do @set VERSION=%%a -for /f "delims=" %%a in ('git describe --tags --abbrev^=0') do @set TAG=%%a- +for /f "delims=" %%a in ('git describe --tags --long --match "v*" ') do @set VERSION=%%a +for /f "delims=" %%a in ('git describe --tags --abbrev^=0 --match "v*" ') do @set TAG=%%a- for /f "delims=" %%a in ('git rev-parse --abbrev-ref HEAD') do @set BRANCH=%%a + +REM sanitize BRANCH for filename +set "BRANCH=%BRANCH:/=-%" +set "BRANCH=%BRANCH:"=-%" +set "BRANCH=%BRANCH:<=-%" +set "BRANCH=%BRANCH:>=-%" +set "BRANCH=%BRANCH:|=-%" +set "BRANCH=%BRANCH:@=-%" +set "BRANCH=%BRANCH:_=-%" +set "BRANCH=%BRANCH:(=-%" +set "BRANCH=%BRANCH:)=-%" + REM remove anotated tag from VERSION setlocal enabledelayedexpansion call set VERSION=%%VERSION:!TAG!=%% @@ -26,9 +38,9 @@ :not_git echo "not a git dir" -find /c "-DEV" include\gpac\version.h >nul +find /c "-DEV" include\gpac\version.h >nul if %errorlevel% equ 1 goto rel_tag -echo "unknwon tag" +echo "unknown tag" @echo off echo #define GPAC_GIT_REVISION "UNKNOWN_REV" > test.h goto write_file @@ -43,4 +55,3 @@ :done cd /d %OLDDIR% exit/b -
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.